console-dashboard-activity-polishrun.md02_define/output/spec.mdThe landing dashboard, the activity trail and the notification surfaces are what both console
audiences see first and most — a partner lands on the dashboard the moment they accept an
invitation, and staff live on these pages all day. They also carry the batch's confirmed
irritations hardest: every zero state is a bare muted sentence where the design wants a designed
one, every page pops in with no loading state because each is a server component that awaits its
reads before anything renders, and the stat cards, feed rows and notification rows read as default
shadcn rather than Sustentus. console-shell-sidebar moved the frame to a sidebar shell, set the
brand baseline in the chrome and freed the width; these pages have not been restyled to sit inside
it. Partners judge Sustentus by exactly this surface, so the polish advances Q2-2026 Objective 1 —
Establish Product-Market Fit with Vendor Partners — under the Scale the Bridge initiative.
A presentation pass over the dashboard, /activity, /notifications, the staff-only
/notifications/delivery page and the components those surfaces share, inside the shell the first
stub established. Visual/UX only (Jamie, 2026-08-27): no read changes, no new figure, no new
filter, no change to scoping or gating.
/activity, no notifications in the centre and in the bell popover, and no failures on
/notifications/delivery. It lives in apps/console/components/ so stubs 3 and 4 can adopt it;
packages/ui is not touched. Today's audience-aware wording is preserved — a partner's zero
state still says "on the tenants assigned to you", staff's still says "in the console" — and the
past-the-end-of-the-list messages on both paginated pages stay distinct from true-zero, keeping
their "back to the first page" way out./activity, /notifications or
/notifications/delivery shows a skeleton that mirrors the page it is loading — stat cards as
cards, feed and notification rows as rows — built on the Skeleton primitive @sustentus/ui
already exports, instead of the current blank pause while the server read resolves. Whether that
is a route-level loading.tsx or a Suspense boundary per region is Build's to pick.failureSummary() read is still skipped for
a partner, so a partner's dashboard shows no email block at all and pays for no cross-tenant
count.brand-guidelines skill, consistent with the shell
chrome, and correct in both light and dark themes.Data, scoping and reads are untouched: staff see the estate, a partner sees the tenants carrying their id, and the "counts + activity is the whole of it" ruling from the console batch still stands.
Context budget: read the five target files and their components in lieu of
technical/architecture/repository-structure to pin exact behaviour, plus the shipped
console-shell-sidebar spec for the baseline this builds on. No scope.md exists for this batch —
the breakdown records it as Jamie's intake interrogation (2026-08-27); rulings trace there and to
this run's two answers.
/activity, /notifications, the bell
popover, and /notifications/delivery — each keeping today's audience-specific wording./activity and /notifications, a page number past the end still reads as past-the-end
(not as "you have nothing") and still offers the way back to the first page./activity, /notifications and /notifications/delivery
shows a skeleton shaped like the page being loaded, not a blank pause or an abrupt pop-in./notifications/delivery.failureSummary() read is still skipped for them.proxy.ts, requireViewer/requireStaff, isConsoleStaff, the
console-access marker. Which viewer sees which surface is unchanged.packages/ui; the empty-state component is console-local this run.(auth) / no-access surfaces (stub 4).loading.tsx vs a
Suspense boundary per region) and the exact composition of the empty-state component are Build's
to pick; the criteria above are the requirement.03_build/output/notes.mdfeat: console-dashboard-activity-polish — empty states, skeletons and brand passtenant-management (apps/console) + web previews; the other six Vercel projects were
skipped for this diffcomponents/empty-state.tsx (new): the console's one zero state — icon, title,
description, optional action, plus a compact mode for the bell popover. Console-local so
stubs 3 and 4 can adopt it; packages/ui untouched, as the spec requires. Wording stays the
caller's, because what is empty depends on who is looking.components/page-header.tsx (new): the heading block all four pages open with, replacing four
separately-drifting copies of the same h1 + muted line.components/console-skeletons.tsx (new): loading shapes that mirror what they stand in for —
header, two stat cards, feed rows, the email block, and a list card with its pager row.activity/loading.tsx, notifications/loading.tsx,
notifications/delivery/loading.tsx (new): route-level loading for the three leaf surfaces.
Deliberately not a (console)/loading.tsx: a segment-root loading file is inherited by
/tenants/**, which would put a dashboard-shaped skeleton on stub 3's pages.(console)/page.tsx: the dashboard's three reads each moved behind their own Suspense
boundary instead of one Promise.all before first paint — heading and layout arrive
immediately, each region fills as its read lands, and the reads still run concurrently. The
email-delivery region renders only for staff, so a partner's dashboard still skips the
failureSummary() read entirely.components/email-delivery-alert.tsx: now renders in both states. The all-clear (success
variant, MailCheck) replaces the previous render-nothing-at-zero behaviour per Jamie's ruling
(2026-08-28); the failure alert is unchanged, including the misconfigured-key wording.components/stat-card.tsx: brand pass — the figure carries text-primary at 3xl with
tabular-nums, the label above it drops back to small and muted.components/activity-feed.tsx, components/notification-list.tsx: emptyMessage: string
became empty: ReactNode, so callers hand in a designed state instead of a bare line. The
notification row gained a primary unread dot, aria-hidden and paired with the existing
semibold label rather than carrying the unread state alone.PageHeader and EmptyState, keeping every
audience-specific string and the past-the-end-of-pagination distinction./activity, /notifications, the bell popover (compact), /notifications/delivery; each
keeps its staff/partner wording.SearchX state naming the real total,
with the existing "back to the first page" button untouched below it.loading.tsx on the three leaf routes, per-region
Suspense fallbacks on the dashboard./notifications/delivery.text-primary, bg-muted,
text-muted-foreground, the success/error Alert variants), so both themes follow.max-w-2xl text-balance, and the existing flex-wrap row and pager layouts are unchanged.apps/console/app/(console)/tenants/page.tsx has a local emptyMessage helper of its own. It
is stub 3's surface and was left alone; the name is coincidental, not a missed call site.04_verify/output/verify.md76b4954 — the head carrying every code line of this run, including the two
Verify fixes below; settled via ci-status.sh after that push. The doc-only commit adding this
file is verified green immediately after it is pushed, and its SHA is reported at the gate.tenant-management (apps/console — the only app this
run touches) did build for 76b4954, and that is the deployment the operator's smoke will
reach via the branch alias
(tenant-management-git-claude-console-dashboard-2f982c-sustentus.vercel.app). The agent still
cannot reach it: the preview sits behind Vercel deployment protection and answers 302 to
vercel.com/sso-api. See Finding 1.countForConsole, readRecentActivity,
readActivityPage, readCentrePage, failureSummary, recentFailures) is the pre-existing
call with the pre-existing arguments; turbo.json → globalEnv is untouched because no
process.env read was added.Review diff against CONVENTIONS.md job was skipped on the commit carrying the code and ran
only on the markdown-only head, so there were no CI review comments to triage (Finding 3).
2 findings, both fixed on branch.Scope: the branch diff against origin/main. The one authorisation-relevant change is that the
staff-gated email-delivery read moved from an inline ternary in the dashboard's Promise.all into
a child server component rendered only when staff is true. Verified:
requireViewer() still runs at the top of the page (live Clerk read, not
a session claim), isConsoleStaff(viewer.access) still computes staff from it, scopeOf(viewer)
still bounds the tenant count, and proxy.ts / requireStaff() are untouched.EmailDelivery is the component that calls
failureSummary(), and it is not rendered for a partner — so the read is skipped, not merely
hidden, exactly as before."use client" in the diff), so viewer is never serialised toward the browser. The only client
component in the area, NotificationBell, is unchanged and still receives server-rendered
children.dangerouslySetInnerHTML, no eval, no raw HTML; all new text is
JSX-escaped literals. No new file, path, query or secret handling anywhere in the diff.Agent half — code-path tracing only. No criterion below is agent-demonstrated on a preview; the agent could not reach one (see Findings 1 and 3).
/ and /activity; nothing is publicly reachable (agent)EmptyState — dashboard feed,
/activity, /notifications, bell popover (compact), /notifications/delivery (agent)SearchX state and the "back to the first
page" control below it, on both paginated pages (agent)loading.tsx exists for /activity, /notifications and
/notifications/delivery; the dashboard uses three per-region Suspense boundaries (agent)failureSummary() read (agent; see Security review)Operator half — not yet performed. Every line below is outstanding.
#build (operator)/ and /activity both
answer 302 to vercel.com/sso-api, before Clerk is even involved, so there is no surface the
agent can demonstrate anything on. Not a defect in the change — recorded so the gate is not
read as though the agent smoked it.
A related bookkeeping note on which commit carries the preview: Vercel skips
tenant-management on commits that touch only .icm/**, so the doc-only heads of this run
(af25bb8, and the commit adding this file) have no console preview of their own, while the
code heads (9ab0339, 76b4954) do. The branch alias always serves the newest successful
build — 76b4954 — which is the code under review. A skipped project is recorded as skipped
throughout this file, never quoted as a green preview, and no cosmetic commit was pushed to
re-trigger a build: that is the "kick CI" move the pipeline forbids.email-delivery-alert.tsx. The
zero-failure branch read "Notification email is getting through", inferred from total === 0
alone. Both counts in failureSummary() come from failure rows, and successful sends are
never recorded, so an environment with no provider key and no administrative actions in the
window produces the same zero as a healthy one: staff would have seen a green all-clear while
console email was entirely dead. Its 24-hour window also contradicted the 30-day list it links
to. Fixed on branch: the state now reports the absence of failures ("No notification email
has failed in the last 24 hours"), says outright that this is not proof of delivery, and names
both windows. The component comment records why the wording must not be strengthened.9ab0339 — the commit carrying
every line of the change — Review diff against CONVENTIONS.md is skipped; it ran and passed
only on af25bb8, which contains nothing but markdown. So the check being green on the PR head
says nothing about the code. The agent ran /code-review at medium in its place, which is what
found Finding 2. Not fixed on branch — this is a repo-wide CI trigger issue, not this run's
to change; raising it for a chore lane rather than widening this PR.notification-list.tsx. The new dot was aria-hidden and
the comment justified it as "paired with the semibold label", but font-semibold carries no
accessible semantics, and the bell popover renders no Mark read control — so nothing in the
accessibility tree distinguished read from unread there. Fixed on branch: an sr-only
"Unread." label now sits beside the decorative dot.Context budget: read packages/services/src/db/services/console-email-delivery-log/index.ts beyond
the Inputs table, to establish what failureSummary() can and cannot know for Finding 2.
05_ship/output/changelog.mdThe sidebar arrived last week; the pages inside it are being refreshed one surface at a time. This is the first of them — the dashboard you land on, the activity trail, and everywhere notifications appear. Nothing new to do here, and nothing new on show: the same numbers, the same entries, the same people who could see them before.
Nothing is no longer a blank space. A quiet morning, a newly assigned partner, a page past the end of the trail — each now says what it means and what would fill it, instead of a lone grey sentence. Being told "nothing has happened yet" is different from wondering whether the page loaded, and the two no longer look alike.
Pages arrive in shape. Opening the dashboard, the activity trail, notifications or email delivery now shows the outline of what is coming — the cards, the rows — which fills in as it loads. Nothing jumps into place after a pause.
It looks like Sustentus. Tenant counts, feed entries and notification rows follow the same type and colour as the rest of the product, in both light and dark. Unread notifications are now marked as unread for screen readers too, not by weight alone.
Email delivery says so when it is fine. For the Sustentus team, the dashboard used to mention notification email only when something had failed, so silence was ambiguous. It now confirms when nothing has failed in the last 24 hours — and says plainly that this is the absence of failures rather than proof of delivery, since successful sends are not recorded.
The tenant pages and the sign-in screens are next.
05_ship/output/investor-update.mdWho it's for: Sustentus staff and invited partners What shipped: The dashboard, activity trail and notification surfaces gained designed empty states, loading skeletons and brand styling in both themes. Why it matters: Partners judge us by this surface — Scale the Bridge, Objective 1: Establish Product-Market Fit with Vendor Partners.
Same data, same access — presentation only.
Dig deeper: https://github.com/sustentus/sustentus/pull/907 · https://help.sustentus.com/changelog/2026-08-28-console-dashboard-activity-polish
05_ship/output/release.md76b4954 carried the code and
built the console preview)apps/docs/app/technical/applications/console/page.mdx — the Home dashboard's
email-delivery bullet and the delivery-log passage above it said the block appears "only when the
trailing 24 hours contain a failed send". It now renders in both states, so both passages were
corrected, including why the all-clear reports the absence of failures rather than proof of
delivery.service-journey step and no feature-role-matrix entity is touched, and
no persona gained or lost a capability.#product-update by ship-note.yaml on the merge.icm/runs/console-dashboard-activity-polish/ to
apps/docs/archive/pipeline-runs/. The console-ui-polish epic is not finished — two stubs
remain in flight (console-tenants-polish, console-auth-access-polish), so the intake folder
stays put./activity,
/notifications, the bell popover (compact), /notifications/delivery; each keeps its
staff/partner wording. Traced in the diff at Verify.loading.tsx on the three leaf routes, per-region Suspense
on the dashboard. Traced in the diff at Verify./notifications/delivery. Corrected during Verify so the all-clear no longer asserts
delivery it cannot know.failureSummary() read —
the component performing it is not rendered for a partner. Confirmed by the security review.How these were verified. All eight were established by code-path tracing in the diff plus the
code and security reviews, recorded in 04_verify/output/verify.md. The agent demonstrated none of
them on the preview: the console preview sits behind Vercel deployment protection and answers 302 to
Vercel SSO, so the whole on-preview half of the Definition of Done was the operator's. Ticking
Ready to merge is Jamie's confirmation that it was done.