Dashboard contract slice a#29
Open
juicycleff wants to merge 85 commits intomainfrom
Open
Conversation
…plan Slice (a) of the new declarative dashboard contract: single-endpoint API with kind-discriminated envelope, intent + slots schema, server-side permission filtering, multiplexed SSE for subscriptions, per-contributor version negotiation. The React shell and contributor migrations are separate slices. DESIGN.md captures the locked-in design decisions; IMPLEMENTATION_PLAN.md breaks the work into 15 phases of TDD-driven tasks. Adds a .gitignore exception so design docs co-located with the package are tracked despite the repo-wide **/*.md ignore.
The previous TestCanonicalCodes_AllPresent only verified that constants are non-empty string literals — it could not fail short of someone deleting a constant from the list itself. The replacement asserts the expected count, uniqueness of wire values, and that every Err* sentinel has a non-empty Code.
…ntKind Phase 10's POST handler passes req.Kind (wire envelope type) directly into Action.Kind when invoking a Warden. Action.Kind was inadvertently typed as IntentKind (the manifest-side enum) which would have caused a type mismatch. The wire Kind is the right boundary for the Warden — it sees the action as the caller framed it, not as the registry classifies the intent.
…ss-contributor extensions Phase 6 of Dashboard Contract slice (a): - registry.go/registry_test.go: Registry interface (Register, Contributor, Intent, HighestVersion, All, MergedGraph). Indexed by (contributor, intent, version); HighestVersion tracks the highest non-deprecated version per (contributor, intent), falling back to a deprecated version only if no active version is registered. - slots.go/slots_test.go: built-in slot catalog (DefaultSlotCatalog) with page.shell, resource.list, dashboard.grid, form.edit. SlotDef carries Accepts, Cardinality, and Extensible. MaxSlotDepth = 8. Registration runs checkDepth, checkCycle, validateGraphSlots over the manifest's graph. - Cross-contributor slot extensions: applyExtension walks dotted slot paths (e.g. main.detailDrawer.fields), enforces the Extensible flag, and validates added intents against the target slot's Accepts list. Each Register call deep-copies the manifest's graph into r.mergedGraphs[contributor], then applies its extends: directives against the merged graph of the target contributor — so extensions never mutate original manifests.
…capability checks
…tion mode warnings
Adds an optional Contract *contract.ContractManifest field to the legacy contributor.Manifest so a contributor can publish a contract-style manifest in parallel with the existing templ-based one. Tagged "contract,omitempty" so legacy contributors that don't opt in keep their JSON payloads unchanged. Round-trip + omitempty tests exercise the new field.
Hooks the contract package into the running dashboard extension: - New Extension fields hold a contract.Registry, contract.WardenRegistry, and contract.AuditEmitter, all initialised in NewExtension. The streamBroker stays nil in slice (a); slice (c) provides the SubscriptionSource needed to instantiate it. - registerRoutes now mounts POST /api/dashboard/v1 and GET /api/dashboard/v1/capabilities alongside the existing JSON API. When a streamBroker is present, the SSE stream + control routes also register. The stream route uses router.GET (not router.EventStream) because StreamBroker.ServeStream owns its own SSE framing as an http.HandlerFunc, while router.EventStream's SSEHandler shape (func(Context, Stream) error) doesn't match. - RegisterContributor and the auto-discovery + remote-upsert paths now mirror any contract manifest published on the legacy Manifest.Contract field into the contract registry, validating against the warden registry first via loader.Validate. Explicit-add paths fail closed (validation error rolls back the legacy registration); auto-discovery logs and continues. CSRF + idempotency header validation and a real Dispatcher are deferred to slices (b) and (c); slice (a) wires transport.NilDispatcher so every intent dispatch returns CodeUnavailable instead of nil-panicking.
…er/validate/build/extend
…design Three-tier dispatcher API (function table + contributor interface + generic typed wrappers), narrow handler signature with optional Result, subscription handlers via channel + stop, MetricsEmitter interface for slice-b observability wiring, and a P2 pilot scope: extensions.list, services.list, services.detail, and a metrics.cpu replace-mode subscription wired against the existing collector.DataCollector.
…anonical error mapping
…n and broker source adapter
Adds form.edit and form.field intents plus the shadcn primitives they need (Input, Label, Textarea, Checkbox). - form.edit: optional data-binding to a query intent for prefill, fields rendered through the 'fields' slot, gathers field values into a record and submits via node.op as a kind=command. Manifest payload bindings (e.g., id from parent.id) merge with field values on submit. FormStateContext threads values+setter+submitting state to children. - form.field: branches by props.kind (text/email/number/password/ textarea/checkbox), labelled with shadcn Label, controlled inputs. Reads values and writes back via the form context. Test setup gains ResizeObserver + pointer-capture stubs that Radix primitives require under jsdom. 2 new tests cover field-render-and-submit (text + checkbox) and data-binding prefill from a query intent. Total 19 tests.
…esource.detail, dashboard.grid)
Adds the three resource-shaped intents that turn a query result into
admin-tool UI:
- resource.list: shadcn Table with columns from props.columns (or
inferred from the first row), rowActions slot rendered per row in
the trailing column, detailDrawer slot opened in a shadcn Sheet on
row click. Both slots see the row data via ParentProvider so child
intents (action.button, resource.detail) can resolve { from:
parent.<field> } payload bindings without re-fetching. Empty-state
copy is configurable via props.emptyMessage. Supports three common
data shapes: array, { items: [] }, or { <key>: [] }.
- resource.detail: dl/dt/dd typography in a shadcn Card. Pulls data
from node.data when present, otherwise reuses the nearest parent
context (so list+drawer flows don't re-query). props.fields narrows
and orders the displayed fields.
- dashboard.grid: responsive Tailwind CSS grid (1 col mobile, props.
columns at md+). Renders the widgets slot.
Two new shadcn primitives added: Table (with TableHeader/Body/Row/
Head/Cell) and Sheet (Radix Dialog with side variants — right by
default for resource.list's detailDrawer).
Test setup adds an EventSource stub globally so subscription-using
intents (metric.counter, audit.tail) don't generate unhandled
rejections in tests that don't drive SSE events.
4 new tests cover resource.list (rows, columns, empty state),
resource.detail (parent-context binding), and dashboard.grid (slot
rendering). Total 23 tests across 7 files.
Subscribes to a subscription intent declared in node.data, buffers the last props.bufferSize events (default 200), and renders them in a scrollable monospace pane inside a Card. Auto-scroll-to-bottom unless the user has scrolled up — sticky-bottom UX matches admin-tool conventions for log tails. Two display modes: - 'json' (default): JSON.stringify the payload - 'line': pluck the .line field for raw text feeds Adds shadcn ScrollArea primitive (Radix-based) for the scroll region.
README expanded from quickstart-only to a full developer-facing guide: project structure with per-file purposes, theming overview, embedding explanation, and pointer to ARCHITECTURE.md for the deep dive. ARCHITECTURE.md is the new deep-dive: pipeline diagram, the five core concepts (graph, registry, slots, contributor/parent contexts, bindings), step-by-step walkthrough for authoring a new intent (with real code), guidance on adding shadcn primitives, testing strategy, performance budget, known limitations, and rationale for the major tech choices (shadcn vendored, TanStack Query, Zustand, CSS variables). Adds a .gitignore exception so shell/*.md are tracked alongside the already-tracked design + plan docs.
…Base UI
Per directive: 'use sadcn baseui and not radix.' shadcn ships parallel
Radix-based and Base UI-based variants of the same components. This
slice swaps the dashboard shell to the Base UI variant
(@base-ui-components/react). Public component imports (@/components/ui/*)
and the v1 vocabulary are unchanged; only the primitive layer
underneath shifted.
Removed: @radix-ui/react-{slot,separator,dropdown-menu,dialog,avatar,
scroll-area,select,checkbox,label,alert-dialog}.
Added: @base-ui-components/react@1.0.0-rc.0.
Rewrites in src/components/ui/ — public component names and prop
shapes preserved by the wrappers:
- button.tsx — local Slot helper for asChild (Base UI has no Slot
primitive; uses render prop instead).
- separator.tsx — Base UI Separator.
- avatar.tsx — Avatar.Root / .Image / .Fallback.
- dropdown-menu.tsx — Menu façade (Base UI calls it Menu, not
DropdownMenu). Trigger wrapper translates asChild → render.
- alert-dialog.tsx — AlertDialog with Trigger asChild→render.
- sheet.tsx — Dialog with side variants and Trigger asChild→render.
- scroll-area.tsx — ScrollArea.Root / .Viewport / .Scrollbar / .Thumb.
- checkbox.tsx — Checkbox.Root / .Indicator. onCheckedChange unchanged.
- label.tsx — plain <label> + cva (Base UI doesn't ship Label).
card.tsx, alert.tsx, skeleton.tsx, table.tsx, input.tsx, textarea.tsx
were pure-styling primitives — no change.
Form test updated: Base UI Checkbox renders span[role=checkbox] with
a Base-generated id, so getByLabelText doesn't traverse via htmlFor.
Test now uses getByRole('checkbox').
Bundle: ~140KB gzipped JS (was ~120KB on Radix). Within the 300KB
budget. CSS unchanged at 5KB. All 23 tests + lint + Go suite green.
- Added auto-discovery for ContractContributorAware in the dashboard extension to register contract contributors. - Introduced new streaming contract handlers for managing channels, connections, rooms, and presence. - Created manifest.yaml for the streaming contract, defining intents and capabilities. - Implemented various query and mutation handlers for stats, connections, rooms, and presence management. - Enhanced the streaming extension to support both legacy and new contract paths during migration.
contract_test.go exercises stats, kick-connection, delete-room, send-message, set-presence, config, and presence-list handlers via a stub Manager. Covers the canonical-error mapping (BadRequest, NotFound, Unavailable) for each mutation. manifest_test.go loads the embedded manifest.yaml and validates it against an empty WardenRegistry. Asserts contributor name, ≥14 declared intents (9 reads + 5 mutations), and 6 routes.
… CoreContributor pages Extends the pilot manifest and handlers to cover the four CoreContributor pages the templ dashboard serves today: Overview, Health, Metrics report, and Traces. - types.go: add OverviewResponse, HealthList/Entry, MetricsReportResponse, TraceSummaryDTO, TracesList, TraceDetailInput, TraceDetailResponse. - overview.go, health.go, metrics_report.go, traces.go: query handlers projecting collector data into the wire types; nil providers return CodeUnavailable; trace.detail returns CodeNotFound on miss and CodeBadRequest on empty id. - manifest.yaml: register five new intents (overview, health, metrics-report, traces.list, traces.detail) and four new graph routes (/, /health, /metrics, /traces) under the existing core-contract contributor. - pilot.go: new Deps fields (Overview, Health, MetricsReport, Traces) wired into Register; partial wiring tolerated. - extension.go: pass e.collector and e.traceStore as the new providers. - slice_h_test.go: unit tests for each handler covering happy path, CodeUnavailable, CodeNotFound, and CodeBadRequest error mapping.
…ct to React shell The contract React shell (slice d) plus the slice-(h) pilot now serve every page CoreContributor provided. This slice removes the duplicate templ stack and forwards old paths to /dashboard/contract/app/* via 302 so existing bookmarks keep working. Removed: - extensions/dashboard/core_contributor.go (CoreContributor + manifest + RenderPage/RenderWidget/RenderSettings) - NewCoreContributor registration in extension.go - Ten CoreContributor-only templ pages and their _templ.go artifacts: overview, health, metrics, metrics_all, metrics_collector_detail, metrics_detail, services, extensions, traces, trace_detail - The eight matching *_helpers.go files that fed them - The ten templ-rendering page methods on PagesManager (~250 LOC) Added: - redirectTo / redirectTraceDetail helpers in pages.go that 302 to the shell. /metrics/all, /metrics/collectors/:name, /metrics/detail/*name collapse onto /contract/app/metrics; slice (j) adds proper deep-link routes when those detail pages get rebuilt React-side. - extensions/dashboard/contract/SLICE_I_DESIGN.md Kept (still used by extension contributors): - ui/shell/, layouts/, ui/components.templ, ui/widgets.templ, ui/metrics.templ, ui/tables.templ, ui/pages/error.templ. Net: ~15k LOC of generated templ + helpers removed; one contract pilot + one React shell remain. go build ./... + go test ./... clean.
…:id deep-link
The contract React shell never actually fetched live graphs — POST kind=graph
404'd because page.shell isn't a registered intent (it's a vocabulary marker
for slot validation). The slice (d) shell tests passed by mocking the response.
This slice wires the graph endpoint end-to-end and lays the foundation for
deep-link detail routes.
Server:
- transport/http.go: special-case kind=graph in ServeHTTP — bypass the intent
table and dispatch via GraphBuilder.BuildWithParams. Map ErrNotFound /
ErrPermissionDenied to wire codes + matching HTTP status. CSRF and
idempotency stay command-only as before.
- registry.go: new Registry.MatchRoute(contributor, route) that adds :name
pattern matching alongside existing exact matching. Exact wins when both
match. Returns extracted name->value params.
- graph.go: GraphBuilder.BuildWithParams threads params through the
visibleWhen filter; legacy Build() delegates.
- envelope.go: ResponseMeta.RouteParams carries the extracted segments.
- pilot/manifest.yaml: add /traces/:id deep-link route binding traces.detail
with id from route.id. Slice (h)'s /traces drawer pattern stays.
- pilot/graph_test.go: new HTTP-level tests covering exact match, :id
extraction, NotFound mapping, and exact-wins-over-param ordering.
Shell:
- contract/client.ts: graph() now returns { node, routeParams }; new
sendEnvelope() variant returns the full Response so meta is reachable.
- contract/hooks.ts: useContractGraph returns GraphResult.
- runtime/context.tsx: new RouteParamsProvider + useRouteParams.
- App.tsx: wraps the renderer with RouteParamsProvider so :name placeholders
flow into bindings.
- intents/{resource.detail,action.button,action.menu,form.edit}.tsx: pass
route into resolvePayload/resolveValue so `{ from: route.id }` payloads
resolve when the user lands on /traces/abc directly.
- contract.test.ts: extended for new shape; new test covers route param
surfacing.
Slice (i) cleanup:
- pages.go: redirectTraceDetail now forwards /dashboard/traces/:id to
/contract/app/traces/:id (path-style) instead of ?id=… query string.
go build ./... + go test ./... + pnpm test (24 React tests) + pnpm build clean.
…ubscription
Closes two related gaps:
1. The audit emitter (slice b) wrote to stdout/Logger and disappeared. There
was no way for the dashboard to surface audited commands.
2. The audit.tail vocabulary intent + React component (slice e) had no
matching server-side handler — clicking the audit widget produced
nothing.
Added:
- contract/audit_store.go:
- AuditStore interface + AuditFilter (Limit/Contributor/Intent/User/Result).
- In-memory ring-buffer impl (default cap 1000), fan-out subscriptions,
non-blocking sends so a slow consumer can't block command writes.
- RecordingAuditEmitter that wraps an inner emitter (existing log-based
one) and persists to the store.
- contract/pilot/audit.go:
- AuditProvider interface + AuditRecordDTO with RFC3339Nano timestamps.
- audit.list query handler (filters + nil-store -> CodeUnavailable).
- audit.tail subscription handler (streams Append events; cancellation
tears down the subscriber cleanly).
- pilot/manifest.yaml:
- Two new intents (audit.list query, audit.tail subscription/append).
- auditList named query with 5s staleTime cache.
- New /audit route under Operations nav rendering audit.tail.
- contract/slots.go: extend page.shell.main Accepts to include audit.tail
so the new top-level route validates.
- extension.go:
- Construct e.auditStore at NewExtension time.
- Wrap the chosen audit emitter (log/structured) with RecordingAuditEmitter
so commands flow into both log lines AND the store.
- Pass e.auditStore as pilot.Deps.Audit.
Tests:
- audit_store_test.go: append/list ordering, intent filter, limit clamping,
ring truncation, subscribe broadcast, cancel-closes-channel,
RecordingEmitter fan-out.
- pilot/audit_test.go: handler projection (timestamp formatting),
CodeUnavailable on nil provider, subscription streams Appends,
CodeUnavailable from subscription handler.
- pilot/types_test.go: bumped manifest counts (intents 9->11, routes 8->9).
go build + go test ./... clean (37 packages green).
…o non-default base paths work
Reported: hosting Forge on port 7901 with the dashboard mounted at a
non-default base produced 404s on /api/dashboard/v1 because the React shell
hardcoded both that path and the React Router basename to /dashboard.
Server: extension.go's makeShellSPAHandler now buffers index.html and
injects a small <script> just before </head> that exposes
window.__FORGE_DASHBOARD__ = { basePath, contractBase, shellBase }, derived
from e.config.BasePath. Shell:
- runtime/config.ts (new): reads the injected globals at module load and
exports basePath / contractBase / shellBase. Falls back to /dashboard so
Vite dev mode + unit tests + direct module imports still resolve.
- contract/client.ts, contract/sse.ts: default baseURL now comes from
contractBase instead of the hardcoded literal.
- auth/principal.ts: /principal fetch is `${contractBase}/principal`.
- App.tsx: BrowserRouter basename comes from shellBase.
Test setup pins __FORGE_DASHBOARD__ to /api/dashboard/v1 so the existing
MSW handlers keep matching.
24 React tests + 37 Go packages green; pnpm lint and pnpm build clean.
…l login gate
Replaces the bare GraphRenderer mount with a proper shadcn-style dashboard
layout (sidebar + topbar + content) and adds an optional login gate that
auth extensions like authsome can plug into without writing React.
## Layout
- Vendored shadcn's `<Sidebar>` block on the existing Base UI primitives:
new `components/ui/sidebar.tsx` exposing the canonical SidebarProvider /
Sidebar / SidebarMenu / SidebarTrigger / SidebarInset / useSidebar API,
built on Sheet (mobile drawer), local Slot helper (asChild), cookie
persistence, and Cmd+B shortcut. Plus `components/ui/breadcrumb.tsx`.
- New `runtime/layout.tsx::DashboardLayout` composes the chrome: sidebar
with nav groups, topbar with breadcrumb + theme toggle, sidebar footer
with user badge.
- `runtime/navigation.ts::useNavigation` calls a new contract query.
- `App.tsx::PageRoute` wraps the renderer in DashboardLayout; `page.shell`
intent simplifies to render only its main slot (chrome moved up).
- Tailwind + index.css gain the shadcn `--sidebar-*` tokens (light + dark).
- jsdom test setup polyfills `matchMedia` for the new mobile-breakpoint
hook.
## Navigation pilot query
- `contract/pilot/navigation.go` walks `Registry.All()`, projects each
contributor's top-level routes whose graph node carries Nav metadata,
groups by Nav.Group, sorts groups by the same priority order the legacy
templ sidebar uses, sorts items within a group by Nav.Priority.
- Manifest: registers `navigation` query intent (capability=read), 60s
staleTime cache. Wired through `pilot.Register`.
- Tests cover group ordering, in-group priority sort, skipping routes
without Nav (e.g. /traces/:id detail), nil registry => CodeUnavailable.
## Optional login gate
- Principal endpoint refactored to `NewPrincipalHandler(opts)` distinguishing
three responses:
- 200 `{authenticated:false}` when auth is disabled (shell skips gate).
- 401 `{code:"UNAUTHENTICATED",loginPath:...}` when enabled but unauthed.
- 200 with full principal when authenticated.
Backwards-compatible `HandleAPIPrincipalHTTP` preserved.
- Shell `usePrincipalStore` adds `authRequired` + `loginPath` derived from
the new envelope shapes.
- `auth/AuthGate.tsx` blocks the layout while the principal loads, then
passes through (auth disabled or signed in) or replaces the tree with
the built-in `LoginScreen`.
- `auth/LoginScreen.tsx` is a minimal email+password form that issues a
`kind: command, intent: <loginOp>` envelope (default `auth.login`,
configurable via `window.__FORGE_DASHBOARD__.loginOp`). On 200 it
reloads the principal so the gate releases.
- Server bootstrap injection (extension.go) extends the existing slice (i)
config map with `authEnabled`, `loginPath`, `loginOp`. Shell config.ts
surfaces them as `authEnabled`, `loginPath`, `loginOp` exports.
- aware.go documents the authsome integration shape: implement
DashboardAuthAware (SetAuthChecker + EnableAuth) plus
ContractContributorAware (register `auth.login` command + optional
`/login` graph route to override the built-in form).
## Tests
Go: navigation_test.go (3 cases), principal_test.go (+2 cases for
auth-disabled vs auth-enabled responses).
React: auth.test.tsx (5 cases — gate pass-through, gate redirects,
LoginScreen happy path, error path, principal store envelope handling),
layout.test.tsx (2 cases — sidebar nav groups render from contract,
breadcrumb fallback).
Manifest counts bumped (intents 11 -> 12).
go build + go test ./... clean (37 packages green); pnpm test 32 pass;
pnpm lint clean; pnpm build clean.
…in route, polish built-in form
Two follow-ons to slice (l):
1. The auth extension now owns the login UI by default. AuthGate fetches
useContractGraph(loginContributor, "/login") before falling back to the
built-in LoginScreen. authsome (or any extension) registers a single
`/login` graph route under its contributor (default "auth") to take
over — no React code, just YAML + a command intent.
- extension.go: bootstrap config gains `loginContributor: "auth"`.
- runtime/config.ts: surfaces `loginContributor` (default "auth").
- auth/AuthGate.tsx: when authRequired, fetches the contract /login
graph; renders it via GraphRenderer wrapped in
ContributorProvider/RouteParamsProvider when the extension owns it.
404 / no graph → built-in LoginScreen.
2. Built-in LoginScreen polished to match the latest shadcn login-03 layout
that ships with the Sidebar block from ui.shadcn.com:
- Centered card on a subtle bg-muted/40 background.
- Brand lockup above the card (LayoutDashboard icon + "Forge Dashboard"
by default; override via prop).
- "Welcome back" heading + concise description copy.
- Email/password inputs with placeholder, "Forgot password?" link
beside the password label, full-width primary submit button.
- Inline error block with AlertCircle icon for failed auth.
- Footer caption beneath the card.
Tests: AuthGate test split into three cases — pass-through, contract-owned
/login override, fallback to built-in form on 404. 33 React tests green.
…handlers via ctx Auth extensions registering an `auth.login` command via the contract path need to write a session cookie on the HTTP response — the contract handler shape (in, principal -> out, error) doesn't carry the writer. Slice (l)'s AuthGate routes login through the contract; the command handler had no way to set the cookie. Adds two small dashauth helpers: - WithHTTP(ctx, w, r) ctx - ResponseWriterFromContext(ctx) http.ResponseWriter - RequestFromContext(ctx) *http.Request The contract transport handler now stashes both before dispatching, so authsome (and any future extension that legitimately needs HTTP escape hatches — downloads, redirects) can pull them via dashauth. Pure data handlers ignore them. Documented as an escape hatch.
… + role gate
Brings the dashboard's login UX up to the latest shadcn login-04 reference
and adds two integration seams the auth extension uses to drive the form:
1. **Visual layer (LoginCard).** New `auth/login-card.tsx` is the canonical
login surface — brand lockup, "Welcome to {brand}" heading, optional
"Don't have an account? Sign up" line, email + Login button, "Or"
separator, social provider button grid (Apple/Google/GitHub/Microsoft/
Facebook/Discord glyphs ship inline; unknown providers fall back to a
neutral circle), terms/privacy footer. Styled via the existing primitives
plus a new shadcn-style `field.tsx` (Field/FieldGroup/FieldLabel/
FieldDescription/FieldSeparator) that other shadcn blocks slot into.
2. **AuthLoginForm intent (`auth.login.form`).** New React component
registered in the intent registry — the dashboard's contract `/login`
route renders this. It fetches an `auth.config` query from the active
contributor (authsome) and projects the result into LoginCard.
Authsome owns brand, signup link, terms/privacy, and the configured
list of social providers; the shell renders whatever ships, no React
change required when authsome enables a new provider.
Submits the password block via the existing `kind:command` envelope
to the configured loginOp. Social buttons POST to each provider's
`authStartURL` (returned by auth.config) and navigate to the upstream
`auth_url`, matching authsome's social plugin start-flow contract.
`slots.go` declares `auth.login.form` as a leaf vocabulary intent.
3. **Built-in fallback aligned.** LoginScreen now renders LoginCard with
hardcoded defaults so deployments without an auth extension still get
the polished login-04 visual. Visuals are byte-identical to the
contract-rendered path; only the data plane differs.
4. **Required-roles gate.** New `WithRequiredRoles([]string)` option +
`Extension.SetRequiredRoles` API. The principal handler returns 403
`{code:"PERMISSION_DENIED", message, requiredRoles}` for users who
don't carry a matching role. React `usePrincipalStore` adds
`accessDenied` / `accessDeniedMessage` / `requiredRoles`; AuthGate
renders an "Access denied" panel (ShieldAlert + message + retry button)
when the gate fires. Auth extensions like authsome wire it via
`SetRequiredRoles` from their own role config so dashboards can be
locked down to specific user populations without writing handler code.
Tests:
- React: 9 auth tests (was 7) — added access-denied panel rendering,
store handling of 403 envelope, plus the fallback LoginScreen still
exercising password command flow with the new "Login" button label.
- Go: principal handler tests cover 200 anonymous, 401 unauth, 403
required-role gate; transport + pilot suites unaffected.
- 35 React tests / 15 Go dashboard packages green; lint + build clean.
Authsome wires `auth.config` + `auth.login.form` /login route in a
companion commit on the authsome repo.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.