Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/bridge-maintenance-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ jobs:
run: node --test tools/tests/calorieapp_embed_readiness.test.mjs tools/tests/wordpress_site_session.test.mjs
- name: Check responsive header correction lifecycle
run: node --test tools/tests/wordpress_site_layout.test.mjs
- name: Check floating shortcut destinations and current-page visibility
run: node --test tools/tests/wordpress_site_navigation.test.mjs
- name: Verify deterministic plugin package
run: |
python -m unittest tools.tests.test_build_wordpress_plugin_release
Expand Down
17 changes: 17 additions & 0 deletions contracts/identity-bridge/v1/code-provenance.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,16 @@
"declared_distribution_licence": "GPL-2.0-or-later",
"clearance_status": "contributor-source-and-similarity-review-pending"
},
{
"path": "assets/calorieapp-logo.svg",
"origin_class": "existing-project-brand-asset-vector-extraction",
"repository_first_add_commit": "7191b0fc150fd5e4c1e928f0ba95282af7b52bf2",
"declared_distribution_licence": "project-mark-no-general-trademark-licence-granted",
"clearance_status": "requested-use-on-existing-project-website-no-expanded-mark-licence",
"source": "https://drive.google.com/file/d/1FMepF9HinYi9Pd5AuR7BSOO6peY5gIPu/view",
"source_file": "calorieappdraft3.ai",
"adaptation": "Original phone-mark vector paths and colours retained; surrounding artboard and border omitted."
},
{
"path": "assets/calorieapp-site-layout.css",
"origin_class": "project-repository-material",
Expand All @@ -149,6 +159,13 @@
"declared_distribution_licence": "GPL-2.0-or-later",
"clearance_status": "contributor-source-and-similarity-review-pending"
},
{
"path": "assets/calorieapp-site-navigation.js",
"origin_class": "project-repository-material",
"repository_first_add_commit": "7191b0fc150fd5e4c1e928f0ba95282af7b52bf2",
"declared_distribution_licence": "GPL-2.0-or-later",
"clearance_status": "contributor-source-and-similarity-review-pending"
},
{
"path": "assets/calorieapp-site-session.js",
"origin_class": "project-repository-material",
Expand Down
66 changes: 66 additions & 0 deletions docs/STEP_3_SESSION_NAVIGATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Step 3: session labels and floating shortcuts

## Requested changes

- Move the existing authenticated CalorieApp-page logout control from above
the iframe into the XUMM widget. Use “Log out” on all website controls, with
an accessible explanation that the website and app sessions end on this device.
- Hide a floating Home shortcut on Home and a floating CalorieApp shortcut on
CalorieApp, including equivalent URLs without `index.php` or trailing slashes.
- Replace the floating Integrated Exchange shortcut with CalorieApp and the
original transparent phone mark, retaining Brizy's icon size and wrapper.
- Prepare the app's matching label and Open Food Facts contribution footer in
a separate main-based PR: https://github.com/CalorieToken/CalorieApp/pull/129.

## Maintenance boundary

Plugin 0.3.22 starts at installed maintenance merge
`6f754c837cf80b05422d347b5a43c512aa3c9dea` (0.3.21). This branch must not be merged
into app main. The full embed controller, PHP authentication methods, accepted
startup URL and session validation are preserved. The site-session script moves
the same button/status nodes after the embed controller has captured them; it
does not register another logout handler for the embedded page.

The navigation script targets existing Brizy icon links in fixed containers.
It does not rewrite inline exchange content, the header logo, other floating
destinations, or create a new floating bar where one is absent. The live Home
and FAQ pages have the older exchange shortcut; the current CalorieApp page
has no such Brizy shortcuts. Its current-page rule is ready for any matching
shortcuts subsequently added to that template.

## Logo source

The transparent SVG uses the nine original phone-mark paths from page 1 of
`calorieappdraft3.ai`, in the CalorieToken Google Drive:
https://drive.google.com/file/d/1FMepF9HinYi9Pd5AuR7BSOO6peY5gIPu/view

The phone mark is visually matched to `logo app.png`. Vector forms, speaker,
home button, fork, knife and original colours are retained. The artboard,
surrounding square and duplicate fork/knife paths are excluded. There is no
embedded raster image, white background, script or external asset in the SVG.
Its source and mark boundary are recorded in the release provenance and notices.

## Verification

The real embed and site-session scripts run together in the existing session
test. After relocation, the captured logout button still requests app logout,
recovers after a timeout, and completes WordPress logout on the accepted iframe
message. The other session tests retain origin/source/locale and retry coverage.
Navigation tests cover Home, CalorieApp, FAQ, URL variants, inline content,
unrelated icons, shared wrappers and invalid external asset configuration.
The existing responsive header tests cover changes in the account card height.

Run:

```sh
node --test tools/tests/calorieapp_embed_readiness.test.mjs tools/tests/wordpress_site_session.test.mjs tools/tests/wordpress_site_layout.test.mjs tools/tests/wordpress_site_navigation.test.mjs
php tools/tests/wordpress_site_session_markup.test.php
python -m unittest tools.tests.test_build_wordpress_plugin_release
python tools/build_wordpress_plugin_release.py --expected-version 0.3.22
```

CI also lints plugin PHP. The original vector extraction is rendered and
visually inspected locally. No candidate is installed by this PR. Native
desktop/mobile rendering of the candidate follows the user's installation
approval; synthetic DOM checks are not a live browser rendering claim.
The previously observed small-desktop card clipping remains a separate followup.
2 changes: 1 addition & 1 deletion tools/build_wordpress_plugin_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"THIRD_PARTY_NOTICES.md",
f"{PLUGIN_SLUG}.php",
)
RELEASE_GLOBS = ("includes/*.php", "assets/*.css", "assets/*.js", "config/*.json")
RELEASE_GLOBS = ("includes/*.php", "assets/*.css", "assets/*.js", "assets/*.svg", "config/*.json")
FORBIDDEN_SUFFIXES = (
".bak",
".db",
Expand Down
39 changes: 38 additions & 1 deletion tools/tests/calorieapp_embed_readiness.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ function element(hidden = true) {
const listeners = {};
return {
hidden,
children: [],
appendChild(child) {
if (child.parentElement) {
child.parentElement.children = child.parentElement.children.filter(item => item !== child);
}
this.children.push(child);
child.parentElement = this;
},
href: "",
src: "",
style: {},
Expand Down Expand Up @@ -59,7 +67,7 @@ test("Xaman waits for CalorieApp and verified completion refreshes WordPress", a
siteLogoutButton.dataset = {
logoutUrl:
"https://calorietoken.net/wp-login.php?action=logout&redirect_to=calorieapp",
idleLabel: "Log out of website and CalorieApp",
idleLabel: "Log out",
};
siteLogoutButton.textContent = siteLogoutButton.dataset.idleLabel;
const siteLogoutStatus = element(true);
Expand Down Expand Up @@ -386,6 +394,35 @@ test("Xaman waits for CalorieApp and verified completion refreshes WordPress", a
assert.equal(fetchCalls.at(-1), "/authorize");
assert.equal(status.textContent, "CalorieApp startup failed");

// WordPress queues the site controller after the embed controller. Exercise
// both real scripts: the existing button must keep its handler after moving.
const actions = element(false);
actions.appendChild(siteLogoutButton);
actions.appendChild(siteLogoutStatus);
const embedShell = element(false);
embedShell.appendChild(actions);
selectors.set(".calorieapp-site-session-actions", actions);
const config = { dataset: {
appPage: "https://calorietoken.net/index.php/calorieapp/",
frameSrc: appOrigin + "/?embedded=1&locale=nl",
appOrigin, locale: "nl",
} };
document.querySelector = (selector) => ({
"[data-calorieapp-site-integration]": config,
"[data-calorieapp-embed]": root,
".xl-card": legacySigninCard,
})[selector] ?? null;
const siteSource = await readFile(new URL(
"../../wordpress-plugins/calorieapp-identity-bridge/assets/calorieapp-site-session.js",
import.meta.url
), "utf8");
vm.runInNewContext(siteSource, {
document, window, URL,
fetch() { throw new Error("Relocating logout must not create another session flow"); },
});
assert.equal(legacySigninCard.children[0], actions);
assert.equal(embedShell.children.length, 0, "remove the separate control above the iframe");

const logoutRequest = {
data: { type: "calorieapp:logout:request", locale: "nl" },
origin: appOrigin,
Expand Down
121 changes: 121 additions & 0 deletions tools/tests/wordpress_site_navigation.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
import vm from "node:vm";

const source = await readFile(new URL(
"../../wordpress-plugins/calorieapp-identity-bridge/assets/calorieapp-site-navigation.js",
import.meta.url
), "utf8");
const site = "https://calorietoken.net";
const app = `${site}/index.php/calorieapp/`;
const logo = `${site}/wp-content/plugins/calorieapp-identity-bridge/assets/calorieapp-logo.svg`;

function harness({ pathname = "/index.php/faq/", embedded = false, isHome = "0", appLogo = logo } = {}) {
const config = { dataset: { homePage: `${site}/`, appPage: app, appLogo, isHome } };
const body = {};
const floatingWrapper = { parentElement: body, position: "fixed", hidden: false };
const inlineWrapper = { parentElement: body, position: "relative" };
function link(href, floating = true) {
const container = {
parentElement: floating ? floatingWrapper : inlineWrapper,
position: "static", hidden: false,
style: { setProperty(name, value) { this[name] = value; } },
};
const item = {
href, attributes: {},
closest() { return container; },
setAttribute(name, value) { this.attributes[name] = value; },
querySelector() { return this.icon; },
icon: { replaceWith(image) { item.image = image; item.icon = image; } },
};
return { item, container };
}
const home = link(`${site}/`);
const legacy = link(`${site}/index.php/integrated-exchange/`);
const currentApp = link(app);
const inline = link(`${site}/index.php/integrated-exchange/`, false);
const inlineHome = link(`${site}/`, false);
const other = link(`${site}/index.php/contact/`);
const foreign = link("https://example.com/index.php/calorieapp/");
const fragment = link(`${site}/#top`);
const links = [home, legacy, currentApp, inline, inlineHome, other, foreign, fragment];
let created = 0;
const document = {
body, readyState: "complete",
querySelector(selector) {
if (selector === "[data-calorieapp-site-integration]") return config;
if (selector === "[data-calorieapp-embed]") return embedded ? {} : null;
return null;
},
querySelectorAll(selector) {
assert.equal(selector, ".brz-icon__container a[href]");
return links.map(link => link.item);
},
createElement(tag) {
assert.equal(tag, "img");
created++;
return { style: {}, attributes: {}, setAttribute(name, value) { this.attributes[name] = value; } };
},
};
const context = { document, URL, window: {
location: { href: site + pathname },
getComputedStyle(node) { return { position: node.position }; },
} };
const run = () => vm.runInNewContext(source, context);
run();
return { home, legacy, currentApp, inline, inlineHome, other, foreign, fragment, floatingWrapper, run, count: () => created };
}

test("Home hides its own floating shortcut and retains the original transparent app mark", () => {
for (const pathname of ["/", "/?campaign=test", "/index.php/"]) {
const h = harness({ pathname });
assert.equal(h.home.container.hidden, true);
assert.equal(h.home.container.style.display, "none");
assert.equal(h.floatingWrapper.hidden, false, "a shared wrapper must not hide its other shortcuts");
assert.equal(h.legacy.item.href, app);
assert.equal(h.legacy.item.image.src, logo);
assert.equal(h.legacy.item.image.style.width, "1em");
assert.equal(h.legacy.item.image.style.height, "1em");
assert.equal(h.legacy.item.image.attributes["aria-hidden"], "true");
assert.equal(h.legacy.item.attributes["aria-label"], "Open CalorieApp");
}
assert.equal(harness({ pathname: "/welcome/", isHome: "1" }).home.container.hidden, true);
});

test("CalorieApp hides both its canonical and former exchange shortcuts, retaining Home", () => {
for (const options of [
{ pathname: "/index.php/calorieapp/" },
{ pathname: "/calorieapp?locale=nl" },
{ pathname: "/app-preview/", embedded: true },
]) {
const h = harness(options);
assert.equal(h.legacy.container.hidden, true);
assert.equal(h.currentApp.container.hidden, true);
assert.equal(h.home.container.hidden, false);
assert.equal(h.home.item.attributes["aria-label"], "Go to Home");
}
});

test("FAQ retains both shortcuts; inline content, other icons and fragment links remain intact", () => {
const h = harness();
assert.equal(h.home.container.hidden, false);
assert.equal(h.currentApp.container.hidden, false);
assert.equal(h.currentApp.item.image.src, logo);
assert.match(h.inline.item.href, /integrated-exchange/);
for (const link of [h.inline, h.inlineHome, h.other, h.foreign, h.fragment]) {
assert.equal(link.container.hidden, false);
assert.equal(link.item.image, undefined);
assert.deepEqual(link.item.attributes, {});
}
const count = h.count();
h.run();
assert.equal(h.count(), count, "reinitialization must not duplicate assets");
});

test("foreign asset configuration cannot rewrite website navigation", () => {
const h = harness({ pathname: "/", appLogo: "https://untrusted.example/logo.svg" });
assert.equal(h.count(), 0);
assert.equal(h.home.container.hidden, false);
assert.match(h.legacy.item.href, /integrated-exchange/);
});
13 changes: 10 additions & 3 deletions tools/tests/wordpress_site_session.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function harness({ embedded = false, signedIn = false, storage = new Map(), stor
const card = element();
const body = element();
const button = element();
button.dataset = { logoutUrl: `${site}/wp-login.php?action=logout&_wpnonce=test`, idleLabel: "Sign out both" };
button.dataset = { logoutUrl: `${site}/wp-login.php?action=logout&_wpnonce=test`, idleLabel: "Log out" };
const status = element();
status.hidden = true;
const actions = element();
Expand All @@ -51,13 +51,20 @@ function harness({ embedded = false, signedIn = false, storage = new Map(), stor
return item;
}
const appFrame = frame();
const root = { dataset: { appOrigin: app, locale: "en" }, querySelector: () => appFrame };
const root = {
dataset: { appOrigin: app, locale: "en" },
querySelector(selector) {
if (selector === ".calorieapp-embed-frame") return appFrame;
if (selector === ".calorieapp-site-session-actions") return signedIn ? actions : null;
return null;
},
};
const document = {
body, readyState: "complete",
querySelector(selector) {
if (selector === "[data-calorieapp-site-integration]") return config;
if (selector === "[data-calorieapp-embed]") return embedded ? root : null;
if (selector === "[data-calorieapp-sitewide-session-actions]") return signedIn ? actions : null;
if (selector === "[data-calorieapp-sitewide-session-actions]") return signedIn && !embedded ? actions : null;
if (selector === ".xl-card") return card;
return null;
},
Expand Down
4 changes: 4 additions & 0 deletions tools/tests/wordpress_site_session_markup.test.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ function check($condition, $message): void {
check($enqueued_scripts === ['calorieapp-identity-bridge-site-session'], 'The footer queues only the smaller site controller on ordinary pages.');
check(str_contains($html, 'data-calorieapp-site-integration'), 'Anonymous pages need the common sign-in navigation.');
check(str_contains($html, 'data-locale="en"'), 'The renderer resolves the canonical locale.');
check(str_contains($html, 'data-home-page="https://calorietoken.net/"'), 'Floating navigation uses the configured website home.');
check(str_contains($html, 'assets/calorieapp-logo.svg'), 'Floating navigation receives the bundled original vector logo.');
check(str_contains($html, 'https://calorieapp-backend-rvul.onrender.com/health?resume_login=true'), 'Use the existing accepted startup route.');
check(!str_contains($html, 'data-calorieapp-sitewide-session-actions'), 'Anonymous pages must not claim a signed-in session.');
check(!str_contains($html, '<iframe'), 'An idle website page must not load an app frame.');
Expand All @@ -65,6 +67,8 @@ function check($condition, $message): void {
$html = render($bridge);
check(substr_count($html, 'data-calorieapp-sitewide-session-actions') === 1, 'Authenticated non-app pages need one joint-logout control.');
check(str_contains($html, 'synthetic-nonce'), 'Use the nonce-protected WordPress logout URL.');
check(str_contains($html, 'data-idle-label="Log out"'), 'Use the short, consistent visible logout label.');
check(str_contains($html, 'Log out of CalorieToken.net and CalorieApp on this device'), 'Explain which sessions are ended on this device.');
check(str_contains($html, rawurlencode(home_url('/index.php/about/'))), 'Logout returns to the current website page.');
check(!str_contains($html, '<iframe'), 'The sign-out frame must be created only after a click.');

Expand Down
10 changes: 10 additions & 0 deletions wordpress-plugins/calorieapp-identity-bridge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ The browser endpoint is intentionally not REST. XUMM Login establishes a normal

Details are in SECURITY.md and CONFIGURATION.md.

Version 0.3.22 moves the embedded page's existing joint-logout button into the
XUMM account card after the embed controller initializes. Both website controls
use the short label “Log out”, with an accessible explanation of the sessions
ended on this device. The existing embed controller and its logout handshake
are unchanged. Floating Brizy shortcuts omit the current page and use the
original CalorieApp phone mark as a transparent SVG. The former floating
Integrated Exchange shortcut now opens CalorieApp. Inline content links and
other floating icons are unchanged. The app's own button wording and Open Food
Facts contribution footer are prepared separately against app main.

Version 0.3.21 starts the website refinement on the accepted 0.3.20 maintenance
line. Separate presentation assets compact and centre the Brizy/XUMM header
card at widths up to 768px. The card moves with the mobile header, so it no
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ and an approved user-provisioning setting.
`contracts/identity-bridge/v1/locales.json` in the CalorieApp repository. Its
presence must stay synchronized by the contract tooling.

## CalorieApp project mark

`assets/calorieapp-logo.svg` contains the existing CalorieApp phone mark,
extracted from the project's `calorieappdraft3.ai` Illustrator design in the
CalorieToken Google Drive. The original vector paths and colours are retained;
the surrounding artboard and button border are omitted for transparency.
This is an existing project brand asset, not a newly drawn logo. Use of the mark
on this project website was requested by the project owner; no broader rights
to the mark are granted by the plugin's software licence.

Source: https://drive.google.com/file/d/1FMepF9HinYi9Pd5AuR7BSOO6peY5gIPu/view

## Source and contribution limitation

Repository history currently contains commits attributed to the `xrpbanks`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@
font-size: 14px;
}

.xl-card > [data-calorieapp-sitewide-session-actions] {
.xl-card > .calorieapp-site-session-actions {
margin: 8px 0 0;
max-width: 100%;
}

.xl-card > [data-calorieapp-sitewide-session-actions] .calorieapp-site-logout {
.xl-card > .calorieapp-site-session-actions .calorieapp-site-logout {
min-height: 32px;
max-width: 100%;
padding: 5px 10px;
Expand Down
Loading