The production route every Backstage resource lives under. Resolves
the resource from the URL segment against the registry, gates on
user.role === 'fenja', and renders the AdminLayout shell with the
ResourceListView + (optionally) ResourceEditPanel.
POST dispatch keyed by _action:
- save: parses formdata per field.kind (multi-text/multi-select-async
use getAll(), number coerces, others coerce to string), validates
via validateForResource, then routes to ops.update(id) when
?edit=<id> is set or ops.create() when ?new=1. Redirects with
?msg=saved | ?msg=created. On failure, re-renders the panel with
errors + the submitted values.
- delete: calls ops.delete(id), redirects with ?msg=deleted.
- <action.key>: looks up the action in resource.actions and runs its
handler, redirects with ?msg=action_<key>.
404s when the resource key isn't in the registry — most keys won't
resolve until steps 8-10 land. A small .bs-flash banner above the
list surfaces the ?msg= text (or the error message after a failed
save).
Old /admin (?tab=...) continues to work alongside.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Right-slide panel that renders a resource's edit form. Driven by the
URL: ?new=1 opens a fresh form, ?edit=<id> hydrates with the current
item. POSTs back to the same URL with _action (save | delete | <action
key>); the route handler in step 7 dispatches.
- FieldRenderer.astro: dispatches on field.kind, wraps each field with
label + helper text + error state.
- fields/*.astro: one component per kind — Text, Textarea, Markdown
(with Write/Preview toggle), Select, SelectAsync, MultiSelectAsync,
MultiText (with add/remove), Date, Datetime, Number, Readonly.
- ResourceEditPanel.astro: header (title + close X), scrollable body,
sticky footer (save + per-resource secondary actions + destructive
delete when ops.delete is defined and item exists). Scrim closes on
click, Esc, or the close link. Confirm-before-submit honours
action.confirmText. Embedded sub-form sections render a placeholder
until step 8 wires the pulse renderer.
- admin.css: panel chrome + scrim + slide-in keyframes, full field
styling for every kind, mobile full-screen modal collapse.
- preview.astro: exercises every field kind so the panel can be
eyeballed in a logged-in session. Try /admin/preview?new=1 and
/admin/preview?edit=<id>.
Autosave deferred to Phase 2 per the approved deltas.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The shared list-rendering component every resource will use. Reads
URL state (?filter, ?q, ?page) and derives:
- active filter (with isDefault fallback)
- active column set (columnsByFilter[filterKey] override → columns)
- filtered + searched + sorted + paginated row set
Rows are full anchor elements pointing at ?edit=<id> so the table
is fully keyboard-navigable and works without JS. The "+ New" button
is suppressed when resource.form is null (activity, join_requests).
- ResourceListView.astro: page header (eyebrow + serif h1 + optional
description + new-item button), toolbar (search form + filter
chips), grid table with --bs-grid-cols set from column widths,
pagination, mobile card collapse.
- ListCell.astro: discriminated render for text / pill / relative-date
/ number / tag-list columns.
- admin.css: list-view styles plus the full pill palette (decision,
update, note, bts, published, draft, archived, open, closed,
pending, accepted, expired, approved, declined, shipping, in-beta,
exploring, considering, active, departed, pilot, cab, fenja).
- preview.astro: inline sample dispatches resource so the list view
renders against real DB rows. Step 8 moves this to its production
config; this inline copy disappears with the preview route in
step 11.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two-pane Backstage chrome: sticky topbar with the wordmark + " /
Backstage" lockup and a "Back to the portal" link, plus a left
sidebar that walks the resource registry and renders grouped
links with active-state and count badges.
- src/admin/components/AdminLayout.astro — the shell. Pre-resolves
list-counts and notify-counts per resource so the sidebar can
render badges without async work in markup. Renders an empty
state until resources land.
- src/admin/resources/index.ts — empty registry stub. Three groups
declared (publishing, council, system); resources populated in
steps 8–10.
- src/admin/admin.css — Backstage tokens (--admin-sidebar-bg,
--admin-active-accent, etc.) and the shell styles (bs-topbar,
bs-sidebar, bs-resource, bs-count). Mobile collapses the sidebar
above the main pane.
- src/pages/admin/preview.astro — temporary smoke-test route at
/admin/preview. Deleted in step 11 when the new admin replaces
the old one.
Old /admin (?tab=…) is untouched and continues to work.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Establishes the load-bearing type surface for the Backstage rebuild:
- src/admin/resource-types.ts — discriminated unions for Field, Column,
Filter, Action, plus the top-level Resource and ResourceGroup. Strict
per the maintainability bar: a config object missing a required key
fails TypeScript.
- src/admin/validate.ts — validateForResource() derives validation
from the field definitions (required, maxLength, multi-text min/max,
number bounds, date parse, visibleWhen-aware).
- tests/admin-validate.test.ts — 8 cases locking the validator API:
required, maxLength, visibleWhen skip & reveal, multi-text bounds,
number bounds, all-valid, form-null short-circuit.
No consumers yet. Next commit pulls these into admin.css and the
shared layout components.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the ~300-line <style> block from src/pages/admin/index.astro
into a dedicated stylesheet, imported from the page frontmatter.
No rule changes — verbatim extraction so the existing admin UI
continues to render identically.
This is the first commit of the Backstage rebuild: it establishes
the shared admin stylesheet that the resource-pattern components
will consume in subsequent steps.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>