console-tenants-polishrun.md02_define/output/spec.mdThe tenant pages are the console's daily driver for staff and the surface an invited partner
spends the most time in, and they carry the console-ui-polish batch's irritations in their most
concentrated form. Four tables — the directory, the deleted view, and the detail page's Users and
Pending invitations — inherited the old narrow column and now sit in a shell that freed real width
without anything using it; at tablet and half-screen widths they fall back to scrolling inside the
table container, so a row's action can sit off-screen. Every zero state is a bare muted sentence,
including the happy "no tenants have been deleted" case, which reads as a broken page rather than
good news. None of the four data-backed routes has a loading state, so each one pops in. Staff
ergonomics and partner-facing credibility both run through this surface, so cleaning it up advances
Q2-2026 Objective 1 — Establish Product-Market Fit with Vendor Partners — under the Scale the
bridge initiative. console-shell-sidebar established the frame and the brand baseline; this stub
is the third of four that restyle the pages inside it.
A presentation pass over apps/console/app/(console)/tenants/** — directory, detail, new-tenant,
deleted list and permanent-removal confirmation — plus the two components those pages own,
tenant-status.tsx and subscription-card.tsx. Per Jamie's ruling (2026-08-27) this run is
visual/UX only: every server action, form field name, hidden input, validation rule, notice code,
notice wording and access check stays exactly as it is; only what renders changes.
loading.tsx for each tenants route that performs a server
read before rendering — /tenants, /tenants/[tenantId], /tenants/deleted and
/tenants/deleted/[tenantId]/remove — shaped like the page behind it, using the Skeleton
primitive @sustentus/ui already exports. /tenants/new reads nothing and gets none.Alerts, and the badges in
TenantStatus and SubscriptionCard are brought onto the brand typography, spacing and colour
established by the shell stub (per the brand-guidelines skill), correct in both light and dark
themes. The badge semantics are unchanged: lifecycle keeps lifecycleVariant, setup keeps its
neutral | info | success mapping, subscription status keeps STATUS_VARIANT; the destructive
emphasis on the removal path stays destructive.action="/tenants", method="GET", the search and
page params, buildDirectoryQuery); the removal page stays a page with a typed-name
confirmation checked server-side; SubscriptionCard stays read-only and still carries no money.Context budget: no scope.md exists behind this batch — it was cut from Jamie's intake
interrogation (2026-08-27) and the rulings are quoted in .icm/intake/console-ui-polish/
(breakdown.md + this stub), so there are no Q-n numbers to carry. The five tenant page
sources and the two components were read in place of
technical/architecture/repository-structure, to pin the exact behaviour this run must preserve.
/tenants, /tenants/[tenantId], /tenants/deleted and
/tenants/deleted/[tenantId]/remove shows a loading skeleton shaped like the page instead
of an abrupt pop-in.TenantStatus / SubscriptionCard
badges render on the brand typography and palette, correct in both light and dark themes,
with lifecycle, setup and subscription-status badge semantics unchanged.required, maxLength,
type and helper copy, and the removal flow the same typed-name confirmation and Cancel —
restyled only; both still post to the same server actions with the same field names./tenants/deleted —
requireViewer, requireStaff, scopeOf and isConsoleStaff are untouched./pipeline scope. The existing search and pagination are restyled
in place, and their query contract is preserved.tenants/**/actions.ts, the create/suspend/
delete/recover/remove lifecycle, or the recovery-window rules.proxy.ts, requireViewer/requireStaff, isConsoleStaff, the
console-access marker, and the tenant scoping in scopeOf/listForConsole/findForConsole.packages/ui — its Skeleton, Table, Card and Badge
primitives are consumed as-is, so this run cannot collide with the parallel stubs 2 and 4.console-dashboard-activity-polish) and the
auth, invitation-accept and no-access surfaces (console-auth-access-polish).SubscriptionCard writable, or surfacing any amount from console-billing.ts.apps/web-derived patterns;
the reachability criteria above are the requirement.03_build/output/notes.mdfeat: console-tenants-polish — responsive tables, designed empty states, loading skeletons (code),
chore: console-tenants-polish — build notes (this file)ci-status.sh on the pushed head — Build hands off only on GREEN; the PR's
checks on that commit are the durable record.New console-local components (apps/console/components/) — none of them go in
packages/ui: the spec excludes it so this run cannot collide with the parallel stubs 2 and 4,
and each is consumed by exactly one app. Two of them turned out to collide with stub 2 anyway —
see the merge section below.
empty-state.tsx: icon, title, the sentence the surface already said, and the action it
already offered. The description is the old copy verbatim — the wording is what distinguishes
cases that look alike (a partner with no assignments from a platform with no tenants; a page
past the end from a search that matched nothing), so only the frame is new.page-header.tsx: back link, title, description, a leading slot for the tenant avatar, an
actions slot, and children for status badges. Four of the five tenant pages had the same
hand-rolled header; now they render one.notice-alert.tsx: the ok | bad → default | destructive mapping, in one place. Three pages
carried their own copy of that ternary.detail-field.tsx: a labelled value, shared by the Details and Subscription cards — they sit
directly above one another, so their labels had to match exactly.console-skeleton.tsx: PageHeaderSkeleton, TableSkeleton, SectionCardSkeleton, consumed
by all five loading.tsx files.Responsive tables. All four tables fold lower-priority columns into the primary cell below
lg rather than relying on the table container's horizontal scroll: directory (Status → name
cell), deleted (Deleted date + window → tenant cell), Users (Email → name cell), Pending
invitations (Role + Invited → email cell). Action cells gained align-top so a folded row's
button stays beside the name it belongs to, and the deleted view's two buttons stack full-width
below sm.
The fold is at lg, not md, and that is the load-bearing decision: below 768px the shell's
sidebar is an offcanvas drawer so a page gets the whole viewport, but between 768 and 1024 it is
still the expanded desktop sidebar — roughly 256px of it — so a "tablet" viewport leaves the
content box under 500px. Folding at md would have looked right in a browser resize and shipped
the exact overflow the stub was raised about.
Empty states. Directory (four cases, with a heading that separates "list ran out" from
"directory is empty"), deleted view, tenant Users, Pending invitations, and the two
assigned-partner cases. The tenant whose Clerk organisation is missing became a destructive
Alert rather than an empty state — the spec calls for it to stay an explained anomaly, and an
empty state would have read as "no users yet" for a tenant whose roster simply cannot be reached.
Loading skeletons. loading.tsx for /tenants, /tenants/[tenantId], /tenants/deleted
and /tenants/deleted/[tenantId]/remove, each shaped like the page behind it.
Brand pass. Headings, cards, forms and banners onto the shared tokens and the Typography
scale the shell established; Select triggers go full-width on narrow rows; the removal warning
gains an icon and a title. Long identifiers (clerkOrgId, email addresses) wrap instead of
forcing the row wide.
Deliberate small fixes made in passing, both presentational and both in files this run
already rewrites: <Link><Button> became <Button asChild><Link> on the deleted view's
"Remove permanently" and its pager (a <button> inside an <a> is invalid HTML and the
directory already used asChild); SubscriptionCard's three fields move from a lopsided
2-column grid to sm:grid-cols-2 lg:grid-cols-3, matching the Details card above it.
tenant-status.tsx is unchanged, deliberately. It renders two Badges whose variants come
from the shared semantic tokens, so it already satisfies the brand criterion; the only thing left
to change there is the shared badge palette itself, which the spec excludes. See Notes for Verify.
No tests. The diff is page markup and presentational components — CONVENTIONS.md → Testing →
What not to test lists page markup explicitly, and no pure logic changed (console-lifecycle.ts,
console-roles.ts, subscription-labels.ts, console-tenants.ts are all untouched, and their
existing unit tests still describe them accurately).
Context budget: within the Inputs table — the spec's touches: files, /CONVENTIONS.md,
apps/console/AGENTS.md, the brand-guidelines skill, and the @sustentus/ui primitives the
pages consume (skeleton, badge, alert, table, lib/variants.ts, globals.css) to confirm
what was already available and theme-correct. The architecture docs pages were not needed.
lg;
two columns (name + Open) at every width under it, and the shell already clamps page-level
horizontal scroll.lg; Recover, Remove permanently, the role Select with Save, Remove and Revoke
invitation all sit inside the content box at 768 and above./tenants/new (see the deviation
below).lifecycleVariant, the setup map, STATUS_VARIANT) untouched.required,
maxLength, type and helper copy; same typed-name confirmation and Cancel; same server
actions and field names.NoticeAlert. The one exception is the removal page's
name-mismatch message, which keeps its own inline Alert because it emphasises the exact
string to type and so carries markup rather than being a plain sentence.requireViewer, requireStaff, scopeOf,
isConsoleStaff, the staff-gated Deleted link, Lifecycle panel and Assigned-partner panel
are unchanged; no read was added, widened or removed.console-dashboard-activity-polish (stub 2)Stub 2 shipped to main while this run was in Build, and it had independently created its own
empty-state.tsx and page-header.tsx in apps/console/components/ — an add/add conflict on both
files, plus a near-miss console-skeletons.tsx (plural) against this run's console-skeleton.tsx
(singular), which also collided on the exported name PageHeaderSkeleton. Keeping packages/ui
out of scope stopped the two runs colliding there; it did not stop them colliding in the app,
because both needed the same two components and neither could see the other's branch.
Resolved by taking main as the baseline and folding this run's needs into it, never the reverse:
empty-state.tsx — main's version adopted wholesale. Its API is the better one: the icon is
passed as a component (icon={Users}) so the component owns its own sizing, and it has a
compact mode. Every call site in this run was converted to that form, and the assign-partner
empty state now uses compact instead of the hand-rolled className="py-6" it had.page-header.tsx — main's heading block kept verbatim, with this run's four extras
(backHref/backLabel, actions, leading, children) added as opt-in props. A caller
passing only title and description returns the bare block with no wrapper, so main's four
existing call sites render byte-identically; description became optional for the removal page,
which has none.console-skeleton.tsx deleted; its TableSkeleton and SectionCardSkeleton moved into
main's console-skeletons.tsx, and PageHeaderSkeleton gained withBack/withActions,
defaulting off so main's no-arg <PageHeaderSkeleton /> is unchanged. Two files a letter apart
exporting the same symbol is precisely the rot CONVENTIONS.md → Keeping the codebase lean warns
about, so consolidating was not optional./tenants/new reads nothing and
gets no skeleton. It gets one. Next's loading.tsx boundary covers nested segments, so
tenants/loading.tsx would have stood in for it and shown a directory table skeleton on the
way to a two-field form. requireViewer() also goes to Clerk on every request, so there is a
real moment to fill. Reverse it by deleting new/loading.tsx if you would rather have the
inherited shape.badgeVariantConfig's semantic variants are bg-success-100 text-success-800 with no dark: override, and those scale steps are defined once in
@theme rather than being redefined under .dark — so a status badge is the same light-tinted
chip in both themes. It is legible (light fill, dark text) and it is how every badge in every
app already renders; it is not something this run introduced. Changing it means changing the
shared palette, which the spec puts out of scope — worth a look on the preview, and a
/pipeline tweak if you dislike it.lg choice above is built around.EmptyState replaced the dashed-border frame with its muted circular icon and
its type scale. That is the intended outcome — one zero state across the console — but it means
the tenant empty states are worth eyeballing beside the dashboard and activity ones on the
preview, since this is the first time the two sets render side by side.PageHeader and console-skeletons.tsx are now shared with stub 2's shipped surfaces. The
extras are additive and the no-extras path returns main's exact block, but the dashboard,
activity, notifications and email-delivery pages are worth a glance to confirm their headings and
loading shapes are unchanged — they are the callers this run did not set out to touch.04_verify/output/verify.md9145f53 — settled via ci-status.sh after the review fixes; the head carrying
this file is re-verified at hand-off and named there.tenant-management
(apps/console) and web built for this commit; agentic-interface, demo, docs,
help-centre, marketing, storybook were reported skipped for this diff and so have no
preview to demonstrate anything on. The console preview is the only one this change is visible
in, and it is the one that built.process.env|mongoose|Schema|requireStaff|requireViewer|isConsoleStaff|scopeOf|clerkClient|stripe|payment
is a code comment. No new process.env read, so nothing to add to turbo.json → globalEnv.complexity: standard dictates; the CI Claude review is
disabled — its workflow run reports skipped, so /code-review was run instead). Two findings,
both fixed on branch in 9145f53.The agent demonstrated nothing on the preview, and does not claim to. Every (console) route
is behind Clerk, and the preview deployment additionally sits behind Vercel deployment protection:
an unauthenticated request to /tenants and /tenants/deleted returns a vercel.com/login SSO
redirect, so not even the sign-in redirect is reachable from here. The agent-run half of this smoke
is therefore limited to tracing each criterion's code path in the diff — which is what the lines
below record. Every criterion needs an operator on the preview before this gate can pass.
Status
TableHead/TableCell carry hidden lg:table-cell, with a folded TenantStatus at
mt-2 lg:hidden inside the name cell; action cell text-right align-top. Operator:
confirm at ~800px and ~1000px with the sidebar expanded, then collapsed to the icon rail.Deleted column
folds (date + window label reproduced at lg:hidden), its actions stack
flex-col … sm:flex-row; Users Email folds; Pending Role + Invited fold into the email
cell. Recover, Remove permanently, the role Select + Save, Remove and Revoke invitation are
all outside the folded columns. Operator: confirm each is clickable without scrolling the
table sideways.description is the exact sentence the surface showed before.
Operator: needs a tenant with no users, a tenant with no pending invitations, an empty
search, a page past the end, and an empty deleted list to see them.loading.tsx exists for /tenants,
/tenants/[tenantId], /tenants/deleted, /tenants/deleted/[tenantId]/remove and
/tenants/new. Operator: navigate between them and confirm no abrupt pop-in, and that
each skeleton is shaped like the page behind it.tenant-status.tsx is not in the diff, and
lifecycleVariant / the setup map / STATUS_VARIANT are unchanged). Operator: toggle
light/dark on every tenant surface. See the badge note in Findings.main: the
set of name=/id=/type=/required/maxLength/action=/autoComplete attributes is
identical on both pages, with one intended exception — Cancel became a single <a> via
Button asChild, so the type="button" that stopped a nested <button> submitting the form
is no longer needed. Operator: submit both forms, including the wrong-name case.main: all three
NOTICES maps are byte-identical in codes, wording and tone; the only textual difference is
the shared NoticeTone alias replacing the inline "ok" | "bad". Operator: trigger a
representative ok and bad notice on the detail and deleted pages.requireViewer/requireStaff/isConsoleStaff/scopeOf/service-scoping expression is
identical to main (the only two textual differences are a JSX brace moving as a ternary
became a prop, and partnerId gaining an <AlertTitle> wrapper); the Lifecycle card
(L391–444) and Assigned-partner card (L446–526) are each fully enclosed by their
{staff ? … : null} guard; readConsoleRoster/readConsoleAccountById remain behind
staff ?. Operator: this is the one to actually test — sign in as a partner and confirm
only assigned tenants are listed, no Deleted link, no Lifecycle or Assigned-partner panel,
and /tenants/deleted refuses.SubscriptionCard stays read-only and still surfaces no amounts
(agent, by diff)break-all on the email cells was inert — TableCell in packages/ui/src/base/table.tsx
hard-codes whitespace-nowrap, which tailwind-merge does not drop for a break-* class and
which white-space inherits to the Typography children. Long addresses would still have forced
the row wide and scrolled the table sideways — the precise behaviour the column fold exists to
remove, so this would have silently defeated acceptance criterion 2 on the surface it matters
most. Fixed on branch (9145f53): whitespace-normal paired at all three sites.SectionCardSkeleton did not match the card it stands in for — it drew lines * 2
placeholders in sm:grid-cols-2 while the Details and Subscription cards render three fields in
sm:grid-cols-2 lg:grid-cols-3, so above 1024px the placeholder reflowed differently from the
content replacing it. Fixed on branch (9145f53): takes fields (default 3) and mirrors the
real grid.41580f7) — console-dashboard-activity-polish
shipped to main mid-Build having independently created its own empty-state.tsx and
page-header.tsx, plus a console-skeletons.tsx that collided with this run's
console-skeleton.tsx on both filename and the exported PageHeaderSkeleton. Resolved by
taking main as the baseline: its EmptyState adopted wholesale (it is absent from this
branch's diff, which is the proof), its PageHeader block kept verbatim with this run's extras
added opt-in, and the duplicate skeletons file deleted into main's. Accepted with a note for
Ship: keeping packages/ui out of scope prevented a collision there but not in the app —
parallel stubs touching one app will keep meeting like this, and stub 4
(console-auth-access-polish) is still to come against these same shared components.badgeVariantConfig's semantic variants
are bg-success-100 text-success-800 with no dark: override, and those scale steps are defined
once in @theme rather than being redefined under .dark, so a status badge is the same
light-tinted chip in both themes. Legible, pre-existing across every app, and changing it means
changing the shared palette — which this run's spec excludes. Worth a /pipeline tweak if the
operator dislikes it on the preview.5f42cf0): neither Quality nor Pipeline, though both had fired on
the Define pushes 25 minutes earlier from the same session. The gate was obtained by dispatching
quality.yaml through its own declared workflow_dispatch trigger (full checks path, confirmed
step-by-step: install, format, lint, typecheck, tests), and the stage:build label by
project-labels.sh, its documented manual fallback. No empty commit was pushed. Every push since
has triggered normally, so this looks transient — but if it recurs, Verify and Ship both gate on
a settled GREEN that would never arrive.Context budget: within the Inputs table. The extra reads were packages/ui/src/base/table.tsx and
src/lib/variants.ts — needed to confirm the two findings above are real rather than plausible —
and origin/main's versions of the four collided component files, to resolve the merge.
05_ship/output/changelog.mdThe dashboard and activity surfaces were refreshed earlier this week. The tenant pages are next — the directory, a tenant's detail page, the deleted list and the permanent-removal confirmation. Nothing new to do here and nothing new on show: the same tenants, the same actions, the same people who could see them before.
Tables stop running off the side of the screen. The directory, the deleted list, and a tenant's users and pending invitations used to scroll sideways on a half-screen window or a tablet, which could put a row's Open, Recover or Revoke out of reach. The columns that matter least now fold underneath the name they belong to, so every row's action stays where you can click it. Nothing is lost, only moved — and a long email address wraps instead of stretching the row.
Empty is a state, not a blank. A tenant with no users yet, a search that matched nothing, a page past the end of the list — each now says what it means and what would fill it. The deleted list's "no tenants have been deleted" reads as the good news it is, rather than as a page that failed to load.
Pages arrive in shape. Opening the directory, a tenant, the deleted list or a removal confirmation now shows the outline of what is coming, which fills in as it loads. Nothing jumps into place after a pause.
It looks like Sustentus. Headings, cards, forms and the notice banners follow the same type and colour as the rest of the product, in both light and dark. Status, setup and subscription badges keep exactly the meanings they had.
Everything else is where you left it. Creating a tenant asks for the same two things, permanent removal still asks you to type the tenant's name, and a partner still sees only the tenants assigned to them.
The sign-in and invitation screens are the last of these.
05_ship/output/investor-update.mdWho it's for: Sustentus staff and partners What shipped: The console's tenant pages fold their columns instead of scrolling sideways, and gained empty states, loading skeletons and brand styling in both themes. Why it matters: Every row action stays reachable at tablet width — Scale the Bridge, Objective 1: Establish Product-Market Fit with Vendor Partners.
Same data, same actions, same access.
Dig deeper: https://github.com/sustentus/sustentus/pull/909 · https://help.sustentus.com/changelog/2026-08-28-console-tenants-polish
05_ship/output/release.mdci-status.sh on the head this file is pushed with, established after this
stage's last push — GREEN is the only verdict the merge proceeds on. The run's earlier heads
were green too: 315edb3 carried the verify record and built the console preview
(tenant-management) plus web.apps/docs/app/technical/applications/console/page.mdx documents the console's routes, access
model, scoping rules and data, none of which this run touches. Checked rather than assumed: the
page makes no claim about table layout, empty states, loading behaviour or component structure
(grepped for each), so there is nothing in it this presentation pass makes stale. The Routes
table's five /tenants* rows describe what each route does, and all five still do it.service-journey step and no feature-role-matrix entity is
touched, and no persona gained or lost a capability. (Same finding as the two sibling stubs;
settled at the intake cut.)2026-08-28-console-tenants-polish (in this PR) +
ship note below.ship-note.yaml
dispatches it to #product-update when the PR merges. Both Dig deeper links are filled (PR
and the live changelog URL); no placeholder remains for its guard to catch..icm/runs/console-tenants-polish/ to
apps/docs/archive/pipeline-runs/ and the epic with it. console-auth-access-polish (#910)
shipped and closed out while this PR sat at the gate, so all four stubs are in
.icm/intake/console-ui-polish/_done/ and this is the batch's last live run —
.icm/intake/console-ui-polish/ is archived to apps/docs/archive/pipeline-intake/ in the same
step. (An earlier draft of this line said one stub was still in flight; that stopped being true
when #910 merged.)Status head and cell
carry hidden lg:table-cell with a folded TenantStatus inside the name cell. Traced in the
diff at Verify.Deleted, Email, Role and
Invited fold; Recover, Remove permanently, the role Select with Save, Remove and Revoke
invitation all sit outside the folded columns. Traced in the diff at Verify.description confirmed to be the exact sentence the surface showed before. The
Clerk-organisation-missing case stays an explained anomaly, not an empty state.loading.tsx on /tenants, /tenants/[tenantId],
/tenants/deleted and /tenants/deleted/[tenantId]/remove. One deliberate deviation:
/tenants/new also gets one, which the spec said it would not. Next's loading.tsx boundary
covers nested segments, so without it the directory's table skeleton would stand in for a
two-field form. Reversible by deleting that one file.tenant-status.tsx is not in the diff, and
lifecycleVariant, the setup map and STATUS_VARIANT are unchanged). Noted, not fixed:
the semantic badge variants have no dark: override, so a status chip is light-tinted in
both themes. Pre-existing across every app; fixing it means changing the shared palette,
which this spec excludes. Worth a /pipeline tweak.main: the set of
name=/id=/type=/required/maxLength/action=/autoComplete attributes is identical
on both pages, with one intended exception (Cancel became a single <a> via Button asChild,
so the type="button" that stopped a nested <button> submitting is no longer needed).main: all three NOTICES maps
are byte-identical in codes, wording and tone; the only textual difference is the shared
NoticeTone alias replacing the inline "ok" | "bad".requireViewer/requireStaff/isConsoleStaff/scopeOf/service-scoping expression is
identical to main; the Lifecycle and Assigned-partner cards are each fully enclosed by
their {staff ? … : null} guard; the roster reads stay behind staff ?. Confirmed by the
security review, which found nothing.How these were verified. All eight were established by code-path tracing in the diff plus the
code review (two findings, both fixed on branch in 9145f53) and the security review, recorded in
04_verify/output/verify.md. The agent demonstrated none of them on the preview: every
(console) route is behind Clerk and the preview additionally sits behind Vercel deployment
protection, which answers an unauthenticated request with a vercel.com/login redirect — so the
whole on-preview half of the Definition of Done was the operator's, and every DoD box in
verify.md was deliberately left unticked. Ticking Ready to merge is Jamie's confirmation that
it was done.
The parallel-stub collisions, and how they resolved. This run met the rest of its batch twice
in the same file, and both times the resolution was to take main as the baseline.
Stub 2, mid-Build. console-dashboard-activity-polish shipped having independently created
its own empty-state.tsx and page-header.tsx, plus a console-skeletons.tsx that collided
with this run's console-skeleton.tsx on both filename and the exported PageHeaderSkeleton.
Resolved in 41580f7: main's EmptyState adopted wholesale, its PageHeader block kept
verbatim with this run's extras opt-in, and the duplicate skeletons file deleted into main's.
Stub 4, at the gate. console-auth-access-polish (#910) shipped while this PR waited on
Ready to merge, adding TableProps, TableRowsSkeleton and TableCardSkeleton to that
same console-skeletons.tsx — a content conflict against this run's TableProps,
TableSkeleton and SectionCardSkeleton. Resolved by keeping main's three verbatim and
renaming this run's to TenantTableSkeleton / TenantTableProps, so nothing shipped changes
shape and the file no longer implies there is one canonical table skeleton. The two are
genuinely different: the tenant tables share width evenly because they fold columns below
lg; the access tables are fixed-width and do not.
Resolving it surfaced a real defect in this run's own PageHeaderSkeleton. It wraps the title
and subtitle in a flex row to make room for the opt-in action buttons, which left the text
block auto-width — so the subtitle's w-full max-w-md resolved against a shrink-to-fit parent
and collapsed to roughly the title's width on every caller passing no actions. That is four
shipped pages (activity, notifications, notification delivery, and #910's new access page),
and it would have been this run silently narrowing stub 2's and stub 4's skeletons. Fixed in
the merge with min-w-0 flex-1 on the text block.
Context budget: within the Inputs table. technical/applications/console/page.mdx was read and
grepped to establish the no-impact finding rather than assert it; the two sibling stubs' archived
release.md and changelog pages were read for the batch's established voice and docs-impact
precedent.