From 3f50fd977f5ea293ba04d893e773034ed2371e14 Mon Sep 17 00:00:00 2001
From: Brad Edwards
Date: Sun, 12 Jul 2026 07:47:59 +0200
Subject: [PATCH 1/3] feat: restore review dashboard polish
---
README.md | 9 +-
changelog.d/33.changed.md | 5 +
docs/access-control.md | 3 +-
...-front-end-product-readiness-boundaries.md | 153 ++++
docs/deployment.md | 5 +-
docs/go-live-checklist.md | 2 +-
docs/prerequisites.md | 2 +-
docs/setup.md | 3 +-
.../accounts/templates/accounts/account.html | 5 +-
.../accounts/account_confirm_delete.html | 31 +
.../templates/registration/login.html | 46 +-
src/aces_scenario_workbench/accounts/views.py | 11 +-
src/aces_scenario_workbench/settings.py | 19 +-
src/aces_scenario_workbench/urls.py | 3 +-
.../workbench/management/commands/doctor.py | 1 +
.../workbench/static/workbench/app.css | 817 ++++++++++++++++--
.../workbench/static/workbench/app.js | 128 ++-
.../workbench/static/workbench/brand-mark.svg | 7 +
.../workbench/templates/404.html | 11 +
.../workbench/templates/500.html | 11 +
.../workbench/templates/workbench/base.html | 44 +-
.../templates/workbench/dashboard.html | 8 +-
.../templates/workbench/evidence_detail.html | 1 +
.../templates/workbench/landing.html | 74 +-
.../templates/workbench/privacy.html | 7 +
.../workbench/revision_overview.html | 362 +++++---
.../templates/workbench/step_detail.html | 1 +
.../templates/workbench/tactic_detail.html | 1 +
.../templates/workbench/technique_detail.html | 1 +
.../workbench/views.py | 66 +-
tests/test_account.py | 12 +-
tests/test_hardening.py | 55 +-
tests/test_review.py | 8 +
tests/test_views.py | 2 +
34 files changed, 1611 insertions(+), 303 deletions(-)
create mode 100644 changelog.d/33.changed.md
create mode 100644 docs/decisions/adrs/001-front-end-product-readiness-boundaries.md
create mode 100644 src/aces_scenario_workbench/accounts/templates/accounts/account_confirm_delete.html
create mode 100644 src/aces_scenario_workbench/workbench/static/workbench/brand-mark.svg
create mode 100644 src/aces_scenario_workbench/workbench/templates/404.html
create mode 100644 src/aces_scenario_workbench/workbench/templates/500.html
diff --git a/README.md b/README.md
index ee6f223..51e1c9f 100644
--- a/README.md
+++ b/README.md
@@ -33,8 +33,9 @@ current directory). Point it at PostgreSQL by setting `DATABASE_URL`:
export DATABASE_URL=postgres://user:pass@localhost:5432/workbench
```
-The Django admin (`/admin/`) is available to administrators for creating
-projects and managing membership.
+The Django admin defaults to `/control/` and is available to administrators for
+creating projects and managing membership. Set `ACES_WORKBENCH_ADMIN_PATH` to
+use a deployment-specific admin path.
## Documentation
@@ -81,7 +82,7 @@ rate-limits password-reset and invitation requests. `aces-workbench doctor`
reports the live posture. `aces-workbench serve` is the development server and
runs in debug mode; host with the container path in
[`docs/deployment.md`](docs/deployment.md). Keep the Django admin
-(`/admin/`) restricted to administrators.
+restricted to administrators.
## CLI
@@ -95,7 +96,7 @@ aces-workbench manage [...] # any Django management command
```
Administrators create projects and invite members from the Django admin
-(`/admin/`); import a scenario pack's ATLAS technique projection with
+(`/control/` by default); import a scenario pack's ATLAS technique projection with
`aces-workbench import --project `.
## Development
diff --git a/changelog.d/33.changed.md b/changelog.d/33.changed.md
new file mode 100644
index 0000000..8f7c2da
--- /dev/null
+++ b/changelog.d/33.changed.md
@@ -0,0 +1,5 @@
+### Changed
+
+- Restored the authenticated review dashboard experience with branded chrome,
+ overview metrics, tactic bars, progression cards, richer module tiles,
+ clickable review rows, safer admin routing, and polished public/auth flows.
diff --git a/docs/access-control.md b/docs/access-control.md
index 4d9c300..903ec80 100644
--- a/docs/access-control.md
+++ b/docs/access-control.md
@@ -14,7 +14,8 @@ existence in exactly two ways, both driven by an administrator:
## Inviting someone
-In the Django admin (`/admin/`):
+In the Django admin (`/control/` by default, unless `ACES_WORKBENCH_ADMIN_PATH`
+is set):
1. Go to **Invitations → Add**.
2. Enter the person's **email**, the **project**, and their **role**.
diff --git a/docs/decisions/adrs/001-front-end-product-readiness-boundaries.md b/docs/decisions/adrs/001-front-end-product-readiness-boundaries.md
new file mode 100644
index 0000000..b96198f
--- /dev/null
+++ b/docs/decisions/adrs/001-front-end-product-readiness-boundaries.md
@@ -0,0 +1,153 @@
+# ADR-001: Front-End Product Readiness Boundaries
+
+Date: 2026-07-12
+
+Status: Accepted
+
+## Context
+
+Issue #33 is a cross-cutting product-readiness repair. It touches public entry,
+authenticated review chrome, dashboard density, branding, cookie notice, admin
+exposure, accessibility, forms, error pages, and print output.
+
+The repository already has important boundaries that must remain intact:
+
+- ACES packs and review bundles are the source of scenario content; the
+ workbench database owns collaboration state only.
+- Project membership is the authorization boundary, enforced in
+ `workbench.access` and `workbench.authz`.
+- Account creation is invite-only through the existing accounts app.
+- Security posture is centralized in Django settings, middleware, CSP,
+ django-axes, django-ratelimit, CSRF, and the `doctor` command.
+- `prototype/` is non-authoritative seed material; production UI belongs in the
+ Django views, templates, and static assets.
+
+## Decision
+
+The front-end overhaul remains a server-rendered Django application. It must
+restore the dashboard-quality experience through existing views, templates, and
+static assets rather than introducing a separate client app, a duplicate domain
+schema, or persisted reporting tables.
+
+Dashboard metrics, tactic bars, progression cards, digest/integrity details, and
+print output are derived from the existing `Revision` object graph
+(`tactics`, `steps`, `techniques`, `evidence`, `metadata`,
+`content_digest`). They are presentation context, not new persistence.
+
+Brand, metadata, legal chrome, footer links, and default page structure are
+centralized in `workbench/base.html` and static files under
+`workbench/static/workbench/`. Assets and fonts must be same-origin static
+assets; do not add CDN-hosted fonts, scripts, or images that would weaken the
+current CSP and offline install story.
+
+The Django admin is not a public landing target. It is mounted through one
+normalized setting such as `ACES_WORKBENCH_ADMIN_PATH`, with the root URL
+configuration, CSP admin exclusion, documentation, `doctor`, and tests all
+deriving from the same value. The default must not be the conventional
+`admin/`. Treat the admin path as noise reduction, not as a secret or a
+substitute for staff authentication and deployment-layer restriction.
+
+Public entry remains invite-only: anonymous users get a clear sign-in path,
+privacy/cookie links, and product context, but not open registration. Staff-only
+admin navigation may be shown after authentication using Django's `admin:index`
+URL, never as a public call to action.
+
+Cookie notice is informational for the current cookie/storage surface:
+session/CSRF cookies plus the local theme preference in `localStorage`. Do not
+add analytics or non-essential storage as part of this issue. If future
+non-essential storage is introduced, consent categories belong behind one
+central legal/chrome seam rather than scattered per-template checks.
+
+Custom 404, 429, and 500 pages use the same branded base where safe and must not
+leak exception details. The existing 429 rate-limit handling remains canonical.
+
+Print/PDF support is HTML-first: use print styles and, if needed, a print-mode
+variant of the revision overview. Do not add a server-side PDF renderer or
+headless browser dependency unless a later requirement explicitly needs it.
+
+## Required Cross-Cutting Contracts
+
+Security gates the implementation must pass:
+
+- Authentication: reuse Django auth views, the custom email user model,
+ password validators, django-axes, and django-ratelimit. Do not create open
+ registration or parallel login/reset flows.
+- Authorization: use `login_required`, `access.member_project`,
+ `access.scoped_revision`, and `authz.can_contribute`; hiding links is not
+ authorization.
+- CSRF and destructive actions: account deletion and all state-changing forms
+ remain POST plus CSRF. Add a confirmation step for account deletion without
+ bypassing the existing export/delete account contract.
+- Admin exposure: validate the configured admin path as a relative URL path
+ without query strings, fragments, absolute URLs, backslashes, or `..`
+ segments. Keep the path out of public anonymous chrome.
+- CSP: keep scripts same-origin plus nonce. No inline event handlers, inline
+ style attributes, or remote scripts/fonts. If a pre-paint theme script remains
+ inline, it must carry the existing per-request CSP nonce.
+- Cookie/storage notice: disclose session, CSRF, and theme-preference storage.
+ Any banner dismissal storage must itself be covered by the notice.
+- Error envelopes: UI errors render branded templates; API upload errors keep
+ the existing JSON `{"detail": ...}` shape and must not expose secrets or
+ stack traces.
+- OS/runtime exposure: new operational knobs belong in environment variables
+ read by settings, not command-line arguments. Admin path configuration is not
+ a secret; do not print secrets, tokens, or configured secret values.
+
+Canonical incumbents to build on:
+
+- Settings/env helpers in `aces_scenario_workbench.settings`.
+- Readiness reporting in `workbench.management.commands.doctor`.
+- URL names from `accounts.urls`, `workbench.urls`, and Django `admin:index`.
+- Account forms/views in `accounts.forms` and `accounts.views`.
+- Project authorization helpers in `workbench.access` and `workbench.authz`.
+- Collaboration context/actions in `workbench.collab`.
+- Projection parsing/import rules in `workbench.ingest`.
+- Existing test suites for hardening, auth, accounts, review views, collab,
+ ingest, CLI, and onboarding.
+- Local gates: `make check`, `uv run ruff check .`,
+ `uv run ruff format --check .`, and `uv run pytest`.
+
+Extensibility seams:
+
+- Admin mount path: one normalized setting used by URLs, CSP, docs, doctor, and
+ tests.
+- Legal chrome: one footer/cookie-notice surface for privacy, terms/cookie copy,
+ and any future consent categories.
+- Brand metadata: base-template blocks/defaults for title, description,
+ Open Graph/Twitter text, theme color, and icon assets.
+- Tier display: one CSS token/data-attribute vocabulary for `quick`,
+ `intermediate`, and `advanced`; do not encode colors independently in each
+ template.
+- Print mode: a revision-overview rendering seam that can later feed a real PDF
+ service if required.
+
+## Non-Goals
+
+- No rewrite to a SPA or client-side routing architecture.
+- No model rename or migration from `Step`/`path_step` to "Module" as part of
+ front-end polish; choose display labels deliberately while preserving storage
+ and URL contracts.
+- No new ingestion schema, review-state workflow, role model, or authorization
+ hierarchy.
+- No analytics, tracking pixels, third-party asset CDNs, or non-essential
+ cookies/storage.
+- No server-side PDF generation dependency.
+- No broad refactor of domain services, admin models, or migrations merely to
+ support visual polish.
+
+## Anti-Patterns To Avoid
+
+- Linking anonymous users to the admin or treating an obscured admin path as the
+ only protection.
+- Recomputing authorization in templates instead of using the server-side
+ helpers.
+- Duplicating review summary data in new tables when it can be derived from a
+ revision.
+- Adding a second form-validation layer in templates or JavaScript that diverges
+ from Django forms and model choices.
+- Hard-coding `/admin/`, tier colors, legal links, or metadata in multiple
+ templates.
+- Using inline handlers, unsafe-inline CSP, remote fonts, or remote UI scripts
+ for convenience.
+- Rendering false accessibility state from the server, such as a theme toggle
+ `aria-pressed` value that can only be corrected later by JavaScript.
diff --git a/docs/deployment.md b/docs/deployment.md
index 237957c..dfb5fe8 100644
--- a/docs/deployment.md
+++ b/docs/deployment.md
@@ -54,6 +54,7 @@ Set these in the container/host environment for a hosted deployment:
| `ACES_WORKBENCH_ALLOWED_HOSTS` | comma-separated hostnames |
| `ACES_WORKBENCH_CSRF_TRUSTED_ORIGINS` | comma-separated origins for the public URL |
| `ACES_WORKBENCH_CACHE_URL` | shared cache for rate limiting behind multiple workers, e.g. `redis://host:6379/0` |
+| `ACES_WORKBENCH_ADMIN_PATH` | admin URL path without leading slash (default `control`) |
| `ACES_WORKBENCH_SECURE_COOKIES` | secure session/CSRF cookies (default on when debug is off) |
| `ACES_WORKBENCH_SSL_REDIRECT` | redirect HTTP→HTTPS at the app (default on when debug is off) |
| `ACES_WORKBENCH_HSTS_SECONDS` | HSTS max-age (default `31536000` when debug is off) |
@@ -66,7 +67,9 @@ The HTTPS controls are on by default outside debug mode; the app trusts the
`X-Forwarded-Proto` header so the redirect does not loop behind a TLS-terminating
proxy. A Content-Security-Policy with a per-request script nonce (no inline
scripts) is applied to every response except the Django admin, which ships inline
-scripts it does not nonce — keep `/admin/` restricted to administrators. The
+scripts it does not nonce — keep the configured admin path restricted to
+administrators. The default path is `/control/`; set
+`ACES_WORKBENCH_ADMIN_PATH` for a deployment-specific path. The
in-memory rate-limit cache is per worker; set `ACES_WORKBENCH_CACHE_URL` to a
shared cache so limits hold across processes.
diff --git a/docs/go-live-checklist.md b/docs/go-live-checklist.md
index 7b6abd6..1e6f5cc 100644
--- a/docs/go-live-checklist.md
+++ b/docs/go-live-checklist.md
@@ -30,7 +30,7 @@ Work through this before exposing an instance to the internet. See
understand why a warning is acceptable for your deployment.
- [ ] Migrations are applied (`aces-workbench migrate`).
- [ ] The first administrator exists (`aces-workbench createadmin`).
-- [ ] The Django admin (`/admin/`) is restricted to administrators.
+- [ ] The configured Django admin path is restricted to administrators.
- [ ] Sign in over HTTPS, create a project, and send yourself an invitation to
confirm email delivery end to end.
diff --git a/docs/prerequisites.md b/docs/prerequisites.md
index 3027280..bb44147 100644
--- a/docs/prerequisites.md
+++ b/docs/prerequisites.md
@@ -46,7 +46,7 @@ Everything above, plus — decide and provision these up front:
- **A shared cache** (for example Redis, via `ACES_WORKBENCH_CACHE_URL`) if you
run more than one worker process, so login and request rate limits are counted
across all workers.
-- Somewhere to keep the Django admin (`/admin/`) restricted to administrators.
+- Somewhere to keep the configured Django admin path restricted to administrators.
!!! tip
Set these as environment variables before the first public start, then run
diff --git a/docs/setup.md b/docs/setup.md
index 3a9b4c0..6f13731 100644
--- a/docs/setup.md
+++ b/docs/setup.md
@@ -37,7 +37,8 @@ used and sessions reset on every restart.
## 3. Create projects and add people
-Sign in at `/accounts/login/` and open the Django admin at `/admin/`.
+Sign in at `/accounts/login/` and open the Django admin at `/control/` unless
+you changed `ACES_WORKBENCH_ADMIN_PATH`.
- **Create a project**: Admin → Projects → Add.
- **Add a member you can manage directly**: Admin → Users → Add (this sets a
diff --git a/src/aces_scenario_workbench/accounts/templates/accounts/account.html b/src/aces_scenario_workbench/accounts/templates/accounts/account.html
index a55fc84..4fa7b3d 100644
--- a/src/aces_scenario_workbench/accounts/templates/accounts/account.html
+++ b/src/aces_scenario_workbench/accounts/templates/accounts/account.html
@@ -22,10 +22,7 @@
Delete your account
This permanently deletes your account and the comments and decisions you
authored. This cannot be undone.
+ This permanently removes your account and the comments and decisions you
+ authored. Activity records remain for audit history with your identity
+ removed. This action cannot be undone.
+