Every action button vanished on every file. The header read "No role" for a
user who has one.
CAUSE: the JWT carries no roles claim — only user_id / name / email / sub /
exp / iat. Roles live in the login RESPONSE BODY and at GET /usr/me, nowhere
else. The provider restored a session by decoding the token, so any page
refresh produced a user with an empty role list, and actionsFor() filtered
every action away.
Two fixes, and the second matters more than the first:
* ZinoProvider now fetches /usr/me whenever it has a token but no roles, so
a restored session recovers the real permission set. A fresh login already
has them from the login response, so this costs no extra round trip there.
* actionsFor() FAILS OPEN. With no roles known it returns every action the
stage allows and lets the server refuse what it must. There is a real
window where the console cannot know permissions — a restored session
before /usr/me answers, or that call failing — and filtering on an empty
list in that window hid every button and made a working app look broken.
The server's refusal is safe and legible ("user 29533 does not have
permission for activity ..."), and the form shows it verbatim. Hiding a
button the user needs is the worse failure, because nothing on screen
explains it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The console was wearing an approximation of the bank. Everything here was
read off HDFC Bank's own production assets instead.
The mark was inside out. It had been drawn as a blue square with a red
square inset; HDFC's is the reverse — a red ground, a white channel cross
splitting it into four corner blocks, and a small blue square at the
centre. Every coordinate now comes verbatim from the SVG the bank serves
in its own site header, including the "HDFC BANK" wordmark outlines, so
the lockup is the bank's rather than a redrawing of it. The favicon in
index.html carried the same inversion and is fixed with the same numbers.
Open Sans replaces the Segoe UI stack. hdfcbank.com preloads OpenSans
Regular / SemiBold / Bold / ExtraBold and sets its whole site in it. It is
self-hosted through @fontsource-variable/open-sans rather than pulled from
a CDN, for the reason the API URL is read at runtime: this build gets
promoted between environments and dropped behind bank proxies, and a font
that sometimes fails to arrive is a product that sometimes looks like
someone else's.
The header stops being a navy band. HDFC puts the full lockup on a light
ground on every one of its own digital surfaces, and a navy header made
the real logo unusable — the lockup's own blue bar would have dissolved
into it. The blue now does its work as the rule under the header, the
active queue and the primary button, which is how the bank uses it. The
lockup's 3px white keyline is the bank's own, so it sits correctly on the
panel in dark mode too and no reversed variant is needed.
Palette, grounded rather than invented: the page canvas is #F0F6FB, the
tint hdfcbank.com uses behind content; the good/ tokens sit on the hue of
the bank's #00B947 instead of a generic emerald, darkened to #00713D
because #00B947 is 2.6:1 on white and unreadable as a label. The two brand
hexes were already right and are unchanged.
Also closes three AA failures the palette claimed it did not have —
--color-faint in light, and --color-faint / --color-brand in dark, each
below 4.5:1 on --color-panel3. White on --color-signal (4.31:1) is left
alone knowingly: darkening HDFC red for the Decline button would have made
it a different red from the logo beside it.
Custom frontend for the HDFC loan-origination demo (dev, org 83 / app 524,
workflow hdfc_wf_loan). MSME and personal loan files: agents assemble the
evidence, rules compute capacity, credit decides.
Stack and platform contract follow the Flight-Disruption-Management console,
which runs against this same cluster:
* API client carries its predecessor's hard-won notes — instance_id goes
over the wire as a NUMBER (a quoted id fails the int64 decode), org_id as
a STRING, record views answer under `data` OR `records`, and audit rows
need the TRIGGER_ prefix filtered or every activity appears three times.
* VITE_ZINO_API_URL is read at RUNTIME from the config.js the server writes
at placement, never compiled in, so one artifact is promoted between
environments unchanged. Missing config fails loudly.
* base: './' plus a router basename from <base href>, so one build serves
any mount path.
* Forms are read from the LIVE activity schema — no field definitions in
this repo. Add a field in Studio, redeploy, it appears.
Written for this app:
* EvidencePanel, the centrepiece. Three independent income sources side by
side with the widest pair marked; every computed ratio shown against the
threshold it was tested on; and a visible line between what a rule
computed and what a model wrote (rules are never violet).
* Queues are the one record view filtered server-side on
current_state_name — the sidebar is the pipeline. Income variance is
surfaced in the list, not only on the file.
* Application 360 with the evidence panel above the offer and the sanction,
so the screen reads in the order the decision was made.
* HDFC palette where red is never decoration: the logo block and declines
only. The referred queue's amber is the only amber in the pipeline.
Known gaps, documented in README rather than hidden: OCR uploads render as a
visible pending row instead of a control that pretends to work, and Credit
Assessment is still performed by a human pending the agent wiring.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>