Fills the 10 M5.2 shells. Four surfaces (settings, settings/api-keys, audit, products) are backed by live tenant-registry data and let the IT_ADMIN do useful work today. Five (projects, settings/users, settings/integrations, billing, support) keep the empty-state shape but pick up richer copy + per-surface CTAs (e.g., billing links to catalog).
Highlights
API keys page: list active + revoked, create form (plaintext shown ONCE in a one-time banner), revoke server action. Argon2id hashing already shipped in M4.2 — this is just the UI.
Audit log: paginated table, cursor-based next-page, action / actor_id GET filters, formatRelative timestamps with full-UTC tooltips. Picks up every event already emitted by tenant.created / catalog.requested / catalog.trial_started / api_key.created / api_key.revoked / keycloak.invite_sent / keycloak.provision_failed.
Settings page: read-only identity + plan + status with colored badge. Editing lands in the M10.1 follow-up.
Products page: live entitlements filtered to enabled. Trial expiry chip with formatRelative. Empty state links to catalog.
ShellEmpty component grew details + cta slots so the five remaining empty pages don't all look identical.
Why
After M5.2 + M11.1 + M12.1, the portal had a working catalog + signup but the rest of the sidebar pointed at empty shells. M10.1 closes that gap: an IT_ADMIN can sign in, see who they are, manage API keys for their tenant, audit everything, and see what products are active. That's the minimum 'this product is real' surface.
Linked milestone: M10.1 (settings editing is the only deferred bit; everything else is shipped).
How
All four real pages use the existing server-action pattern: read state, render, accept POST via <form action={asyncFn}>, redirect with ?ok= / ?err= flash.
Audit pagination uses cursor (id-after) rather than offset so it stays stable as new rows land. Filter form is plain method=GET so the URL is bookmarkable.
API-key plaintext is held in the query string for the redirect (?plaintext=…). Not perfect — it ends up in browser history. Acceptable for dev; M10.1 follow-up moves it to a server-stashed flash cookie when we add real session-flash plumbing.
Format helpers (formatRelative, formatDateTime, truncate) consolidated in src/lib/format.ts so we don't reinvent date strings per page.
Test plan
pnpm lint / typecheck / test (56 cases, 100% src/lib line+branch+function) / build — all green
Playwright canaries: one per surface, signed-out → 403 (10 specs)
Manual: signed-in IT_ADMIN against live dev stack — verify create-key flow, revoke flow, audit pagination, products listing all work
Risk
Plaintext-in-query-string for API key creation lands in browser history. Mitigated by the 'shown once' UI — but a follow-up should move it to a flash cookie.
Audit page has no rate-limit-aware throttling — a tenant with millions of events would scroll forever. M10.2 adds export + better filtering.
Settings editing is deliberately deferred. IT_ADMINs change their tenant identity through the M10.1 follow-up or via direct API call.
Checklist
Unit tests (56 vitest cases, 100% coverage)
E2E specs (10 surface canaries)
Docs updated (CHANGELOG)
Tenant scoping enforced via canSee() + parent layout slug match
CHANGELOG entry under "Added"
## What
Fills the 10 M5.2 shells. Four surfaces (`settings`, `settings/api-keys`, `audit`, `products`) are backed by live tenant-registry data and let the IT_ADMIN do useful work today. Five (`projects`, `settings/users`, `settings/integrations`, `billing`, `support`) keep the empty-state shape but pick up richer copy + per-surface CTAs (e.g., billing links to catalog).
## Highlights
- **API keys page**: list active + revoked, create form (plaintext shown ONCE in a one-time banner), revoke server action. Argon2id hashing already shipped in M4.2 — this is just the UI.
- **Audit log**: paginated table, cursor-based next-page, action / actor_id GET filters, formatRelative timestamps with full-UTC tooltips. Picks up every event already emitted by tenant.created / catalog.requested / catalog.trial_started / api_key.created / api_key.revoked / keycloak.invite_sent / keycloak.provision_failed.
- **Settings page**: read-only identity + plan + status with colored badge. Editing lands in the M10.1 follow-up.
- **Products page**: live entitlements filtered to enabled. Trial expiry chip with formatRelative. Empty state links to catalog.
- **ShellEmpty component** grew `details` + `cta` slots so the five remaining empty pages don't all look identical.
## Why
After M5.2 + M11.1 + M12.1, the portal had a working catalog + signup but the rest of the sidebar pointed at empty shells. M10.1 closes that gap: an IT_ADMIN can sign in, see who they are, manage API keys for their tenant, audit everything, and see what products are active. That's the minimum 'this product is real' surface.
Linked milestone: **M10.1** (settings editing is the only deferred bit; everything else is shipped).
## How
- All four real pages use the existing server-action pattern: read state, render, accept POST via `<form action={asyncFn}>`, redirect with `?ok=` / `?err=` flash.
- Audit pagination uses cursor (id-after) rather than offset so it stays stable as new rows land. Filter form is plain `method=GET` so the URL is bookmarkable.
- API-key plaintext is held in the query string for the redirect (`?plaintext=…`). Not perfect — it ends up in browser history. Acceptable for dev; M10.1 follow-up moves it to a server-stashed flash cookie when we add real session-flash plumbing.
- Format helpers (`formatRelative`, `formatDateTime`, `truncate`) consolidated in `src/lib/format.ts` so we don't reinvent date strings per page.
## Test plan
- [x] `pnpm lint` / `typecheck` / `test` (56 cases, 100% `src/lib` line+branch+function) / `build` — all green
- [x] Playwright canaries: one per surface, signed-out → 403 (10 specs)
- [ ] Manual: signed-in IT_ADMIN against live dev stack — verify create-key flow, revoke flow, audit pagination, products listing all work
## Risk
- Plaintext-in-query-string for API key creation lands in browser history. Mitigated by the 'shown once' UI — but a follow-up should move it to a flash cookie.
- Audit page has no rate-limit-aware throttling — a tenant with millions of events would scroll forever. M10.2 adds export + better filtering.
- Settings editing is deliberately deferred. IT_ADMINs change their tenant identity through the M10.1 follow-up or via direct API call.
## Checklist
- [x] Unit tests (56 vitest cases, 100% coverage)
- [x] E2E specs (10 surface canaries)
- [x] Docs updated (CHANGELOG)
- [x] Tenant scoping enforced via `canSee()` + parent layout slug match
- [x] CHANGELOG entry under "Added"
Four real surfaces backed by live tenant-registry data:
/[slug]/settings read-only tenant identity + plan + status
+ lifecycle dates with badge for status
/[slug]/settings/api-keys full CRUD: list active + revoked keys,
create form, plaintext-shown-once banner,
revoke action
/[slug]/audit paginated table with cursor-based next-page
navigation, action+actor_id GET filters,
formatRelative timestamps, metadata
preview
/[slug]/products live entitlements (filtered to enabled),
trial expiry chip, links to catalog when
empty
Five remaining surfaces upgraded to milestone-aware empty states
(projects / users / integrations / billing / support) with CTAs where
useful — billing links to catalog, support points at oncall@.
ShellEmpty component grew a 'details' string and a 'cta' ReactNode
slot so the empty pages don't all look identical.
Library additions:
src/lib/format.ts formatRelative, formatDateTime, truncate
src/lib/tenant-registry fetchAPIKeys, createAPIKey, revokeAPIKey,
fetchAudit — typed result shapes so server
actions can branch cleanly
Tests:
src/lib/format.test.ts 12 cases, 100% coverage
src/lib/tenant-registry.test +14 cases for new client methods,
100% line+branch+function
tests/e2e/surfaces.spec.ts one canary per of 10 customer-area
routes (signed-out → 403)
CI all green: lint / typecheck / test / build.
Refs: M10.1
CODEOWNERS rules
requested review from Benjamin_Boenisch 2026-05-20 07:19:40 +00:00
sharang
merged commit e387b9a963 into main2026-05-20 07:20:32 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What
Fills the 10 M5.2 shells. Four surfaces (
settings,settings/api-keys,audit,products) are backed by live tenant-registry data and let the IT_ADMIN do useful work today. Five (projects,settings/users,settings/integrations,billing,support) keep the empty-state shape but pick up richer copy + per-surface CTAs (e.g., billing links to catalog).Highlights
details+ctaslots so the five remaining empty pages don't all look identical.Why
After M5.2 + M11.1 + M12.1, the portal had a working catalog + signup but the rest of the sidebar pointed at empty shells. M10.1 closes that gap: an IT_ADMIN can sign in, see who they are, manage API keys for their tenant, audit everything, and see what products are active. That's the minimum 'this product is real' surface.
Linked milestone: M10.1 (settings editing is the only deferred bit; everything else is shipped).
How
<form action={asyncFn}>, redirect with?ok=/?err=flash.method=GETso the URL is bookmarkable.?plaintext=…). Not perfect — it ends up in browser history. Acceptable for dev; M10.1 follow-up moves it to a server-stashed flash cookie when we add real session-flash plumbing.formatRelative,formatDateTime,truncate) consolidated insrc/lib/format.tsso we don't reinvent date strings per page.Test plan
pnpm lint/typecheck/test(56 cases, 100%src/libline+branch+function) /build— all greenRisk
Checklist
canSee()+ parent layout slug matchFour real surfaces backed by live tenant-registry data: /[slug]/settings read-only tenant identity + plan + status + lifecycle dates with badge for status /[slug]/settings/api-keys full CRUD: list active + revoked keys, create form, plaintext-shown-once banner, revoke action /[slug]/audit paginated table with cursor-based next-page navigation, action+actor_id GET filters, formatRelative timestamps, metadata preview /[slug]/products live entitlements (filtered to enabled), trial expiry chip, links to catalog when empty Five remaining surfaces upgraded to milestone-aware empty states (projects / users / integrations / billing / support) with CTAs where useful — billing links to catalog, support points at oncall@. ShellEmpty component grew a 'details' string and a 'cta' ReactNode slot so the empty pages don't all look identical. Library additions: src/lib/format.ts formatRelative, formatDateTime, truncate src/lib/tenant-registry fetchAPIKeys, createAPIKey, revokeAPIKey, fetchAudit — typed result shapes so server actions can branch cleanly Tests: src/lib/format.test.ts 12 cases, 100% coverage src/lib/tenant-registry.test +14 cases for new client methods, 100% line+branch+function tests/e2e/surfaces.spec.ts one canary per of 10 customer-area routes (signed-out → 403) CI all green: lint / typecheck / test / build. Refs: M10.1