Implements the M3-frontend slice of the §22 multi-project track, per
docs/superpowers/specs/2026-06-03-m3-frontend-design.md (design merged in
#10). Completes the runtime-config cut 0.33.0 (M3-backend Plan A) began.
Frontend:
- DeploymentProvider boots GET /api/deployment → {name, tagline,
defaultProjectId, projects}; brandTitle() neutral 'RFC' pre-fetch fallback.
- /p/:projectId/* routing with generic /e/<slug> segment. ProjectLayout
fetches /api/projects/:id, applies per-project theme (reset on switch),
provides ProjectContext, guards the corpus (served only for the default;
others get NotServedPlaceholder — decouples this slice from Plan B).
- Directory at / (2+ projects) with N=1 redirect into the single project;
ProjectSwitcher in deployment chrome; entry-noun by project type.
- VITE_APP_NAME hard cut: removed from vite.config + index.html; the 6 brand
reads now use deployment.name via context; static <title>RFC</title> + JS
document.title. Internal /rfc·/proposals links → /p/<project>/e|proposals
via lib/entryPaths.
Backend:
- GET /api/deployment returns default_project_id (the guard contract).
- Server-side 308s: /rfc/<slug>, /rfc/<slug>/pr/<n>, /proposals/<n> →
/p/<default>/… . nginx (testing + prod) routes /rfc/ and /proposals/ to
the backend.
Tests: 3 new backend redirect/deployment tests (438 pass); Vitest unit for
DeploymentProvider, ProjectLayout (theme/guard/404), Directory (11 pass);
clean build with no VITE_APP_NAME. Playwright e2e deferred until Tier-1 seeds
a registry (see CHANGELOG 0.35.0 step 5).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remediates the rfc-app application + deploy-config findings from the
Session 0026 security audit. Cut as the "v0.25.0-security-hardening"
branch (from v0.24.0); reversioned to 0.27.0 on rebase onto main since
v0.26.0 (#28) shipped while this was in flight.
- C1 (Critical): single sanitizeHtml.js chokepoint (DOMPurify) for every
marked→innerHTML / dangerouslySetInnerHTML sink (MarkdownPreview,
ProposalView x2, Editor); rel=noopener hook on target=_blank links.
- H1: per-account OTC-verify lockout (migration 023, auto-applied) +
per-IP throttle via new ratelimit.py; wired on otc verify/request +
passcode check/verify.
- M1: device_trust.lookup() single indexed-row read — cookie value is now
"<row_id>.<raw_token>"; bcrypt-checks one row, not a global table scan.
(Behavior change: existing device-trust cookies re-prompt once.)
- M2: HTTP security headers (CSP/HSTS/XFO/XCTO/Referrer-Policy) at nginx.
- M4: session cookie Secure-by-default (SESSION_COOKIE_SECURE opt-out).
- M5: bounce webhook fails CLOSED (503) when secret unset, instead of open;
RFC_APP_INSECURE_BOUNCE_WEBHOOK=1 dev opt-in.
- L2/L3: per-IP cooldown + check-endpoint throttle.
- L4: systemd sandbox knobs. L8/I1: nginx server_tokens off + TLS1.0/1.1 out.
VERSION + frontend/package.json → 0.27.0; CHANGELOG documents the upgrade
steps (incl. the out-of-band nginx + systemd apply, which the flotilla
deploy gesture does not perform).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds an optional "What will you be using this for?" capture as a sibling
to the required justification on both propose surfaces, per roadmap #26.
- propose-RFC modal (ProposeModal): optional textarea below the required
"Why is this RFC needed?" pitch, labeled "What will you be using this
RFC for? (optional)".
- propose-PR modal (PRModal): optional textarea below the required
description, labeled "What will you be using this change for?".
- Backend: ProposeBody / OpenPRBody gain an optional `proposed_use_case`
(NULL/omitted accepted, no min, 8000-char cap matching the existing
free-text bound). Persisted to a new canonical side table
`proposed_use_cases` keyed by PR number, mirrored onto the cache
columns added by migration 021. Returned on the proposal list/detail,
RFC detail (by slug), and PR detail endpoints.
- Display: ProposalView, RFCView (main only), and PRView render the
captured use case with a muted "left blank" treatment when NULL.
- migration 021: nullable `proposed_use_case` on cached_rfcs/cached_prs
plus the reconcile-proof `proposed_use_cases` truth table.
- New vertical test_proposed_use_case_vertical: persists+returns when
supplied, accepted as NULL/omitted, for both surfaces.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brings the §1 bot wrapper, the §4 cache (webhook + reconciler), the
§5 schema (six numbered migrations), Gitea OAuth + §6 user
provisioning, the §7 catalog left pane, and the propose-to-merge
vertical: propose modal opens an idea PR against the meta repo, an
owner merges from the pending-idea view, the cache picks it up via
webhook or reconciler sweep, and the catalog renders the new
super-draft.
Per §1 the bot is the only Git writer; every commit, branch
creation, and PR merge carries the §6.5 On-behalf-of: trailer and
an `actions` audit row. Per §4 the cache is never written from a
user action — it's webhook+reconciler only.
Covered by `backend/tests/test_propose_vertical.py` against an
in-process Gitea simulator.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>