Skip to content

feat(admin-ui): scaffold + 7 screens + same-origin mount (phase 13.1)#59

Merged
BangRocket merged 1 commit into
mainfrom
phase-13-slice-1-ui-scaffold
May 5, 2026
Merged

feat(admin-ui): scaffold + 7 screens + same-origin mount (phase 13.1)#59
BangRocket merged 1 commit into
mainfrom
phase-13-slice-1-ui-scaffold

Conversation

@BangRocket
Copy link
Copy Markdown
Owner

Summary

Lands the admin web UI per `docs/admin-ui-design.md` (PR #58). Vite + React 18 + TypeScript + Tailwind + TanStack Query, lives at `apps/admin-ui/`, ships inside the production Docker image, mounted at `/admin/*` on the existing MyPalace server. No CORS, no new endpoints, no extra service.

Surface (v1)

  • Login — admin key in `sessionStorage`, `X-Palace-Key` header on every call. Closing the tab signs out. `401` from any API call clears the key + bounces to login.
  • Health (default) — backend-by-backend status from `/ready`, polling every 10s.
  • Tenants — list / create / drop (with the same 409-data-present guard the API enforces).
  • Keys — list (toggle-able revoked), mint with one-time plaintext display ("⚠ save this now"), revoke.
  • Stats — per-tenant or `ALL` with row counts, 7d activity, FSRS health, top users.
  • Audit — paginated browse with key_id / path_prefix filters and status-class chips.
  • Memories — read-only per-user browser. Write paths stay on the API for safety.

Server changes

  • `mypalace/main.py:_mount_admin_ui()` — looks for `/app/static/admin` (production image) or `apps/admin-ui/dist` (dev tree). `StaticFiles` for `/admin/assets/*` + SPA catch-all → `index.html`.
  • `mypalace/auth/scopes.py` — `/admin` and `/admin/` added to public allowlist (the page must render before login). `/v1/admin/` still requires admin scope; tripwire test asserts this.
  • `Dockerfile` — multi-stage: Node-24-alpine builds the UI, Python image copies `dist/` to `/app/static/admin/`.

Test plan

  • 13 vitest tests: auth-storage round-trip + `request()` (header injection, 401-clears-key, query encoding, 204, JSON body, error messages, key-preserved-on-403)
  • 9 pytest tests: public-path classification (incl. tripwire that `/v1/admin/*` stays auth'd) + SPA index serving
  • TypeScript strict + `noUncheckedIndexedAccess`; tsc clean
  • Vite build: 232 KB JS / 71 KB gzipped; CSS 3.5 KB gzipped
  • Server suite passes (587, +9 new)
  • Ruff clean

Out of scope (per design doc)

Personality / entity-alias screens, memory editing, WebSocket events, manual theme toggle, E2E Playwright. Re-evaluate v2 once usage patterns settle.

🤖 Generated with Claude Code

Lands the admin web UI per docs/admin-ui-design.md. Vite + React 18 +
TypeScript + Tailwind + TanStack Query, lives at apps/admin-ui/, ships
inside the production Docker image, mounted at /admin/* on the existing
MyPalace server. No CORS, no new endpoints, no extra service.

UI surface (v1):
- Login   — admin key in sessionStorage, sent as X-Palace-Key on every
  call. Closing the tab signs out. 401 from any API call clears the key
  and bounces back to login.
- Health  — backend-by-backend status from /ready, polling every 10s.
- Tenants — list / create / drop (with the same 409-data-present guard
  the API enforces).
- Keys    — list (with show-revoked toggle), mint (one-time plaintext
  display, with the "save it now" warning), revoke.
- Stats   — per-tenant or ALL with row counts, 7d activity, FSRS health,
  top users.
- Audit   — paginated browse with key_id / path_prefix filters and
  status_class chips.
- Memories — read-only per-user browser (write paths stay on the API
  for safety, per design §1).

Server side:
- mypalace/main.py grows _mount_admin_ui() — looks for the built bundle
  at /app/static/admin (production image) or apps/admin-ui/dist (dev
  tree). Mounts /admin/assets/* via StaticFiles and /admin/{full_path}
  → index.html for SPA routing. Logs at INFO if disabled (no bundle).
- mypalace/auth/scopes.py: /admin and /admin/* added to the public
  allowlist so the page can render before login. /v1/admin/* still
  requires the admin scope — tripwire test asserts this.
- Dockerfile: multi-stage build adds a node:24-alpine ui-build stage
  that runs `npm install && npm run build`, copies dist/ into the
  Python image at /app/static/admin/.

Tests:
- 13 vitest tests cover the auth-storage roundtrip and the request()
  client (header injection, 401 → key wipe, query encoding, 204, JSON
  body, error messages, key-preserved-on-403).
- 9 pytest tests cover the public-path classification (incl. the
  tripwire that /v1/admin/* must NOT be public) and SPA index.html
  serving.
- TypeScript strict + noUncheckedIndexedAccess on; tsc clean.
- Vite build: 232 KB JS / 71 KB gzipped. CSS 3.5 KB gzipped.

Server suite 587 passes (was 578; +9 new mount tests). Ruff clean.

Out of scope for this slice (per design):
- Personality / entity-alias screens (defer to v2).
- Memory editing / write paths (stay on the API).
- WebSocket event streaming.
- Light/dark theme toggle (just match prefers-color-scheme).
- E2E Playwright tests (re-evaluate once usage patterns settle).
@BangRocket BangRocket merged commit 3438420 into main May 5, 2026
@BangRocket BangRocket deleted the phase-13-slice-1-ui-scaffold branch May 5, 2026 23:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant