- Landing shows the tool tiles only when signed in and a sign-in card
otherwise; the login callback now returns to /.
- New public /help page: intro plus a per-tool Carbon accordion.
- Nav restructured to Home, {services}, divider, Help, Sign In/Out in
both the header and the side nav; the header right keeps only the
identity chip. Side-nav logo enlarged, border removed, spacing
equalised. Sign-out modal no longer flashes before it is defined.
- Per-deploy asset versioning (?v=) with immutable asset caching and
no-store HTML; help step lists show their numbers again.
- Tests updated and extended (48 total).
Crop / fingerprint-recognition / logout Carbon icons (16px, inlined into the
shared header so they inherit the theme colour) in the side-nav-link
'title-icon' slot — the slot is named title-icon, not nav-icon.
'Load next queued' did nothing because the running server had been started
without its env file (a restart used '-f .env' from tools/, where no .env
exists), so the cropper was unconfigured and every action answered 503 —
and none of that was visible, because notifications never rendered either:
- notifications used cds-toast/cds-toast-item, which the Carbon bundle does
not define; a cds-toast-notification element renders nothing standalone in
this build. The cropper now uses cds-inline-notification in the fixed
toast viewport (renders, themed, close button) like the wizard
- an unconfigured deployment now says so on the page (503 'Cropper
unavailable') instead of rendering actions that all fail, and logs a
startup warning naming the missing credentials
- tests: notifications use a registered element; unconfigured page 503
- favicon is the Youreka logo (64px PNG generated from the source mark),
on the tools pages and server error pages
- signing out asks first: a Carbon cds-modal (Cancel / danger Sign out)
shared by the header and the mobile side nav; Cancel closes, Sign out
confirms and logs out
- the current tool is now visibly selected: Carbon marks active nav via
a[aria-current=page] INSIDE the shadow, but aria-current sits on the
host, so the rule never matched — the header item now gets a blue
underline and the side-nav link a blue left border (via ::part(link)
for colour, plus the host for the marker)
- logo and wordmark are middle-aligned inside a flex brand wrapper
- tests: favicon/modal/signout-trigger markup and the active-tool marking
on both navs (45 total)
- all pages (landing, both tools, server error pages) render on Carbon's
g100 theme via <body class="app cds--g100">; token consumers follow
automatically, including component shadow styles through the inherited
--cds-* custom properties
- dark base on <html> so no light flash around the viewport
- step chips and tool icons use the brand blue: layer-selected-inverse
inverts to a LIGHT token in g100, which had made them white-on-white
- page titles breathe: .page-head top margin 1.5rem -> 2.75rem
- new test asserting every page renders on g100 (44 total)
- header: Carbon ui-shell (cds-header) with the Youreka logo (from
idid-enterprise-portal), Image Cropper / Fingerprint Agent nav items
(aria-current on the active tool), the agent status tag on the wizard,
and the operator chip + Sign out on desktop
- mobile: cds-side-nav (logo above the items, both tools, a divider, then
Sign out) toggled by the ui-shell menu button; account actions move out
of the header below 1056px, the nav is hidden at desktop widths
- the header is built once in server.js (appHeader) and injected into the
landing, both tools and error pages — no per-page duplication
- removed my duplicate menu-button toggle: ui-shell coordinates the menu
button and side nav itself (the duplicate cancelled it out)
- fixes: 'cds-header' was hidden by a selector-list typo
(cds-header, cds-side-nav:not(:defined)); the collapsed side nav no
longer captures taps
Tools banner logo asset added (public/assets/youreka-logo.png).
The pages are rendered by the server, which already holds the session — so
/fingerprint, /cropper and the landing now embed window.__SESSION__ (JSON,
with '<' escaped) instead of each fetching /auth/session on load:
- /fingerprint gets token, identity, agentUrl and the permitted domains
(the same cached context the endpoint returned); /cropper gets identity
- landing nav is now server-rendered (Sign in vs user chip + Sign out);
its client fetch script is deleted
- per-user HTML is sent with Cache-Control: no-store and Vary: Cookie
- express.static runs with index:false so '/' can be session-aware while
assets stay session-free
- GET /auth/session remains as the documented JSON contract (and the
canonical place the domain-filter caching is exercised in tests)
Client code shrinks by a fetch + error path per page; 3 new tests
(injection, escaping, landing header) — 43 total.
HIGH
- async handlers: Express 4 does not catch rejected promises and Node 22
exits on unhandled rejections — a malformed body ({} to /cropper/dequeue
etc.) could hang a request or kill the process. Every cropper route is
now wrapped (throw → 500 via next), request bodies are validated up
front (400), and JSON.parse of queue items is guarded. Server-wide
error middleware added, honouring err.status (413 for oversized bodies).
- error pages HTML-escape all interpolated values — the login failure page
rendered upstream (portal) error text raw.
MEDIUM
- outbound calls carry AbortSignal timeouts (5s OpenFGA verify, 15s
cropper upstreams) — a hung upstream can no longer pin a request.
- body limits scoped: /cropper gets 50mb (picture data URLs), everything
else 1mb (previously 50mb globally, memory-DoS surface pre-auth).
- cropper save validates the client-supplied domain against the catalog;
picture fetches validate the Minio object key (id-domains/ prefix, no ..).
LOW
- static assets served before the session middleware (no PocketBase load
per asset); landing header builds DOM nodes with textContent instead of
innerHTML; picture load checks upstream ok.
5 new tests (40 total) covering malformed bodies, malformed queue items,
unknown domains, object-key traversal, and error-page escaping.
Centered card with navbar, lock icon, sans-serif stack (error pages
previously rendered in the browser default serif), and action-aware
copy: permission denial offers only 'Back to Tools' instead of a
pointless 'Try again'; auth failures keep Try again + Back to Tools.
- Smart Photo Editor ported from id-image-bot: queue/by-ID picture
loading, vue-croppa editing, carvekit background cleanup, nodered
review flow, a2loft save — restyled to the tools design language
- unified login: YOUreka session identity, phone prefilled, OTP only at
save (a2loft's requirement); artist-login step removed
- crop_image @ domain:idid OpenFGA gate on every cropper route: cached
5-min on reads, live on save, fail closed
- credentials (minio/azure/nodered/carvekit) via env, sole exception to
hardcoded config; 503 when unconfigured; native FormData replaces
form-data; new deps minio + azure-storage
- 10 new tests (35 total) with fake minio/queue/nodered/removebg/a2loft