Plan: craig-intake as the Single Public Intake Portal
On this page
Status
This was a program, not a single MR. Phase 1 was fully specified below and tracked at issue granularity; Phases 2–5 each received their own committed plan (now archived alongside this one), iterated through contextless review before that phase was coded. All five phases are complete and epic &60 is closed.
Phases 1–3 are complete (2026-06-26). Phase 1 (theming foundation): the
standalone public portal renders in the active jurisdiction’s palette + OS
light/dark. Phase 2 (integration aids): the PII-guarded debug CpsRequest panel,
the gated view-keys endpoint, and the view-keys page + per-profile nav. Phase 3
(parity migration): the edge reached full parity with craig-web’s public report
flow — shared craig-i18n, the staged accessible wizard, public attachments
(endpoint + success-view UI), and the themed UI now mounted in integrated mode
(L7). Phase 4 (mandated-reporter signing) is complete — SHINES-scoped browser
signing with a PII-free accountability audit, the keyring approval-gate UX, and
the structural Signed ⇒ /public 422 gate. Phase 5 (the last) removed craig-web’s
duplicate public /report* outright — preceded by the edge field-parity remediation
(R0–R3) that made the edge a verified superset. The program is complete; epic &60 is
closed: craig-intake is the single public report portal for every deployment, reached
by path-based ingress.
| Step | Description | Status |
|---|---|---|
P1.1 (#709) |
Program plan (this file) + ADR-036 dated amendment + ADR-043; nav-linked. |
Done (2026-06-25) — MR !806 |
P1.2 (#710) |
Share the renderer: move |
Done (2026-06-25) — MR !807 |
P1.3 (#711) |
craig-intake themes from the active bundle at boot (replicated resolver wrapper + state-bundle features); serve |
Done (2026-06-26) — MR !808 |
P1.4 (#712) |
Convert |
Done (2026-06-26) — MR !809 |
P2 |
Integration aids (JSON panel PII-guarded, nav, view-keys) — its own committed plan before coding. |
Done (2026-06-26) — plan MR !811; #713 (!812), #714 (!813), #715 (page + per-profile nav) |
P3 |
Parity migration (i18n via a shared |
Done (2026-06-26) — plan MR !816; #716 (!817), #717 (!818), #718 (!819), #719 (!820), #720 (!821), #721 (!822) |
P4 |
Mandated-reporter signing (server-side enforce + browser signing) — committed plan. |
Done (2026-06-27) — plan MR !826; #727 (!827), #728 (!828), #729 (!829), #730 (!830) |
P5 |
Remove craig-web |
Done (2026-06-28) — remediation #742/#741/#739/#740 (!833/!834/!835/!837); P5.1 strip #736 (!838); P5.2 archive + epic close #737. Epic &60 complete. |
Epic: &60
Phase 1 issues: #709, #710, #711, #712
Branch convention: feature/Make craig-intake the one themed public reporting portal for all deployments, delete craig-web’s duplicate public /report, and add integration aids + mandated-reporter signing. A multi-phase program; Phase 1 (theming) is fully specified here. per issue (this MR: feature/intake-portal-phase1-plan)
Context
CRAIG runs three public-facing intake surfaces today, and only two of them are themed:
-
craig-web caseworker
/intake/*— the authenticated worker UI, fully themed via the Orchard token system (ADR-036). -
craig-web public
/report— the anonymous public wizard (Askamareport_base.html+report-wizard.js), themed, i18n + a11y complete; it proxies the actual submission to craig-intake’s API (intake_url). -
craig-intake standalone
/report— the edge’s own self-contained public form (services/craig-intake/static/report.html, served only in standalone mode per Standalone Mode + ADR-042). It hand-rollsstatic/css/intake-public.csswith ~27 hardcoded hex colors and is therefore unthemed — it ignores the active jurisdiction’s palette and OS dark-mode entirely.
Two problems follow. First, the standalone portal looks nothing like the rest of the product — a jarring break for a public safety form that any jurisdiction may deploy. ADR-036 deferred per-jurisdiction theming of the edge as future work precisely because, at the time, theme tokens were consumed by craig-web only. That deferral is now the blocker.
Second, surfaces 2 and 3 are duplicates: two public report forms, two field sets
to keep in lockstep, two CSS systems, two places a divergence bug can hide (CRAIG
has already been bitten by form/server divergence — #639, #650). The architecturally
honest move is to make the edge (craig-intake) the single public portal for
all deployments and delete craig-web’s public /report, keeping craig-web as the
authenticated caseworker BFF. ADR-043
records that decision; this plan executes it, theming first.
The theming pipeline already does almost everything needed. ADR-036
resolves the active jurisdiction bundle at boot, takes its ThemeContribution,
renders one CSS string, and serves it from GET /assets/theme.css under the strict
CSP (style-src 'self'). The renderer is a pure &ThemeContribution → CSS
function. The only reason the edge can’t reuse it is that the renderer lives inside
the craig-web binary. Phase 1 lifts the renderer into the shared craig-state-bundle
crate so both binaries call the identical code, then themes the edge from its active
bundle — per-jurisdiction, not a hard-coded palette.
Scope
In scope (this program):
-
Phase 1 — share the token→CSS renderer; theme the edge from the active bundle.
-
Phase 2 — integration aids: a PII-guarded debug JSON panel, top-of-page nav, a view-registered-keys page.
-
Phase 3 — parity: carry craig-web’s i18n + accessibility + staged-wizard UX
attachments + org-key endpoints to the edge, and mount the embedded UI in integrated mode. -
Phase 4 — server-side-enforced mandated-reporter signing + browser signing UI.
-
Phase 5 — delete craig-web’s public
/report*outright; route/reportto the edge at the ingress.
Out of scope:
-
Any change to the authenticated caseworker UI in craig-web (it stays, themed, as is).
-
Re-architecting the edge into a stateful service — ADR-017 (stateless edge) is retained throughout (L1).
-
Backwards-compatibility shims or HTTP redirects for the old craig-web
/report(L6 — Phase 5 deletes it outright; the ingress routes the path). -
Runtime theme reload (ADR-036 keeps theme compiled-in; unchanged).
Locked constraints
These are the program-level invariants every phase honors. They were settled during plan review and are not re-litigated per phase.
| # | Constraint |
|---|---|
L1 |
Service split (architectural — all phases). Keep the edge-vs-BFF split (ADR-017).
Only the public form consolidates into craig-intake; integrated deploys reach it by
path-based ingress routing of |
L2 |
Per-jurisdiction theming (Phase 1). Theming reaches the edge via the shared crate,
materialized at boot from the active jurisdiction bundle (mirror craig-web’s
|
L3 |
JSON panel — PII guarded (Phase 2). Shines-only, config-gated default OFF
( |
L4 |
Mandated signing — server-side (Phase 4). |
L5 |
i18n + a11y parity gate (gates Phase 5). Consolidation must carry over craig-web’s
i18n + accessibility — don’t regress the public safety form. Retiring craig-web’s form
(P5) is gated on this parity. Erratum (2026-06-27): a pre-deletion audit found this
was read too narrowly — Phase 3 carried i18n/a11y/wizard but the edge NONE/cases form’s
field set diverged from craig-web’s (911 banner, |
L6 |
No backwards-compat shims or redirects (Phase 5). craig-web’s |
L7 |
Embedded UI in integrated mode (Phase 3 — gates Phase 5). craig-intake must serve
the public UI in integrated mode — today integrated boots |
Phase map (execution order)
Phase 1 is fully specified in this file (the "Design" + "Steps" sections below);
Phases 2-5 are a scoped roadmap, each planned in its own committed .adoc before it is
coded. The table shows the whole dependency chain at a glance.
| Phase | Goal | Depends on | Risk | Rough size |
|---|---|---|---|---|
1 Theming foundation |
Share the token→CSS pipeline; theme the edge from the active bundle |
— |
Low (refactor) |
3 issues |
2 Integration aids |
JSON panel (PII-guarded), nav, view-keys |
P1 |
Low–Med |
~3 issues |
3 Parity migration |
i18n, a11y/wizard, attachments, org-keys, + mount UI in integrated mode (L7) |
P1 |
High (open design) |
~6 issues |
4 Mandated signing |
Record the signer + browser signing + structural server gate (SHINES-scoped); see the committed plan |
P1 (+P2 view-keys, P3 wizard) |
Medium |
4 issues + 2 follow-ups |
5 Remove craig-web |
Delete outright; ingress routes |
P3 (incl. L7) |
Medium (breaking) |
~2 issues |
ADRs (land in Phase 1, this MR)
-
ADR-036 — dated amendment (refinement, not reversal; matches ADR-036’s existing 2026-06-23 amendment precedent): the palette→CSS renderer becomes shared in
craig-state-bundleand craig-intake becomes a second theme consumer; the §2 invariant (CSS from a compiled-in palette, no runtime TOML) is preserved; §4’s "materialization is BFF-local" is refined to "host-local" (each binary resolves its own active bundle and holds its ownArc<str>; only the pure renderer is shared); the "Out of scope: per-jurisdiction theming of craig-intake" bullet is superseded. -
ADR-043 — new "craig-intake is the single public intake portal" (supersedes the informal two-surface model): the edge renders the themed/i18n/a11y public UI for all modes, jurisdiction-aware via the active bundle; the embedded UI is mounted in integrated mode (L7);
/reportreaches the edge by path-based ingress routing, no redirect (L6); craig-web keeps only the authenticated caseworker UI; the service split (ADR-017) is retained; retiring craig-web’s form is gated on i18n+a11y parity (L5).
Design (Phase 1)
The cycle constraint (deviation from the program brief)
The program brief sketched "move active_contribution() into craig-state-bundle
so both services resolve identically." That is not possible and the committed plan
corrects it: active_contribution()’s helper `candidate_bundles()
(services/craig-web/src/bundle.rs:23) names the concrete bundle crates
(craig_state_ga::GeorgiaBundle, craig_state_tx_stub::TxStubBundle), which depend
on craig-state-bundle. Moving the resolver into the crate would make the crate
depend on its own dependents — a dependency cycle.
To resolve the dependency cycle, Phase 1 splits the refactoring into two parts — what can move into the shared crate (cycle-free) versus what must stay per-binary:
-
Shared (cycle-free, moves into
craig-state-bundle): the pure renderermaterialize_theme_css+ its private helpers (render_palette_css,render_block,validate_palette) + theThemeBootErrortype. These operate only on&ThemeContribution(already a crate type) and reference no concrete bundle crate. -
Per-binary (stays, replicated): the active-bundle resolver wrapper (
candidate_bundles()+active_contribution()), because it names the concrete bundle crates.resolve_active_bundleis already shared incraig-state-bundle(activation.rs); each binary keeps a thin wrapper over it that lists its own enabled concrete bundles. craig-web keeps itsbundle.rsunchanged; craig-intake gets an equivalentbundle.rsof its own (P1.3).
This replication is small (~30 lines) and is the correct cost of the no-cycle rule —
the alternative (a registry of Box<dyn StateBundle> constructors in the crate) would
re-introduce the cycle or demand a plugin-registration dance ADR-038 already rejected
for N=2.
What moves, and the public-API discipline
craig-state-bundle carries ![deny(unreachable_pub)] + ![deny(missing_docs)]
(crates/craig-state-bundle/src/lib.rs:3). That gate forces the right shape:
-
materialize_theme_cssandThemeBootErrorflip frompub(crate)topub, gain the crate-rootpub usere-export besideThemeContribution(lib.rs:67), and keep their existing///docs. -
render_palette_css,render_block,validate_palettestay private to thethememodule —unreachable_pubrejects apubitem with no re-export, so the gate mechanically prevents freezing the helpers into the public API. That is exactly the desired boundary. -
ThemeBootErrorusesthiserror; confirmcraig-state-bundle’s `[dependencies]carriesthiserror(add it if absent —unused_crate_dependencieswill flag a stale add, so only add it if the moved code needs it).
The generated-CSS banner comment (theme.rs:124, today "Generated by craig-web
from the active state bundle …") becomes service-neutral (e.g. "Generated from
the active state bundle (ADR-036 / design/token-schema.adoc)") so both binaries emit
byte-identical CSS. The only craig-web output change in this whole phase is that
one comment line — behavior is otherwise unchanged.
craig-intake themes from the active bundle (not a hard-coded palette)
The edge resolves + materializes exactly as craig-web does
(services/craig-web/src/main.rs:136,167):
// craig-intake boot (standalone today; integrated in P3 per L7)
let (_jurisdiction, contribution) = bundle::active_contribution()?; // intake's own wrapper
let theme_css: Arc<str> = craig_state_bundle::materialize_theme_css(&contribution.theme)?;
So standalone-SHINES resolves to the Georgia / Simple Statehouse palette, and any
other deployment resolves its own — per-jurisdiction theming for every deployment,
which is the whole point of L2. The Arc<str> is held and served from a new
GET /assets/theme.css route in services/craig-intake/src/ui.rs, mirroring
craig-web’s theme_css_handler (main.rs:259). Boot fails fast on a
structurally-incomplete palette (ADR-036 §8), the same posture as the BFF.
The edge must not gain a runtime toml dependency: craig-state-bundle parses
TOML only at build time (its build.rs), so adding it as a runtime dep is safe.
The verification step asserts this.
Steps (Phase 1)
Step P1.1 — Plan + ADRs (#709, this MR)
Files: this plan; docs/modules/ROOT/pages/adrs/adr-036-token-and-theme-export.adoc
(amendment); docs/modules/ROOT/pages/adrs/adr-043-craig-intake-single-public-portal.adoc
(new); docs/modules/ROOT/nav.adoc (ADR-043 index entry + this plan under Plans);
CHANGELOG.adoc.
Commit the program plan, the ADR-036 dated amendment, and ADR-043. No code. Iterate the plan through contextless review before merge.
Step P1.2 — Share the renderer (#710)
Spec: ADR-036 §4 amendment (2026-06-25) is the authoritative description of this move; read it alongside this step.
Files: crates/craig-state-bundle/src/theme.rs (receive the renderer + helpers
ThemeBootError), crates/craig-state-bundle/src/lib.rs (re-export
materialize_theme_css + ThemeBootError), crates/craig-state-bundle/Cargo.toml
(thiserror if needed); services/craig-web/src/theme.rs (delete the moved defs;
keep the route handler + AppState.theme_css), services/craig-web/src/main.rs
(import the shared materialize_theme_css).
-
Move
materialize_theme_css,validate_palette,render_palette_css,render_block,ThemeBootError, and theCORE_TOKENSconst from craig-web’stheme.rsintocraig-state-bundle’s `theme.rs. Makematerialize_theme_css
ThemeBootErrorpub; keep the helpers + const private. Re-export the twopubitems fromlib.rs. -
Make the banner string service-neutral (drop "by craig-web").
-
craig-web
main.rs:167callscraig_state_bundle::materialize_theme_css(…); delete craig-web’s local copies. The route handler (theme_css_handler)
AppState.theme_cssare unchanged. -
Move every unit test in craig-web’s
theme.rsthat assertsmaterialize_theme_css/validate_palette/ThemeBootErrorbehavior (the rendered-string + boot-fail cases —materialize_default_emits_root_and_mode_blocks,materialize_accepts_a_declared_complete_palette,materialize_emits_two_axis_high_contrast_and_select_arrow,generated_css_emits_no_legacy_color_alias,materialize_rejects_a_palette_with_mismatched_modes,materialize_rejects_a_palette_missing_a_core_token). The static-stylesheet lints (static_stylesheets_have_no_raw_hex_colors,static_stylesheets_have_no_legacy_color_var_refs,accent_token_is_never_a_text_color, thestrip_block_commentshelper) STAY in craig-web — they scan craig-web’sstatic/css/*and belong to the consumer. -
The byte-identical guarantee: craig-web’s
/assets/theme.cssbody changes by only the banner comment line. Confirm via the existingcontainstests (they don’t pin the banner) + a manual diff during review.
Step P1.3 — Edge themes from the active bundle (#711)
Files: services/craig-intake/Cargo.toml (add craig-state-bundle + a [features]
block + optional craig-state-ga/craig-state-tx-stub), new
services/craig-intake/src/bundle.rs (replicated resolver wrapper),
services/craig-intake/src/main.rs (resolve + materialize at boot; pass the
Arc<str> into the UI layer), services/craig-intake/src/ui.rs (new
/assets/theme.css route + handler).
-
Cargo.toml. Mirror craig-web (
services/craig-web/Cargo.toml:15-25):[features] default = ["state-ga"] state-ga = ["dep:craig-state-ga"] state-tx-stub = ["dep:craig-state-tx-stub"] [dependencies] craig-state-bundle = { workspace = true } craig-state-ga = { workspace = true, optional = true } craig-state-tx-stub = { workspace = true, optional = true } -
bundle.rs. Replicate craig-web’sservices/craig-web/src/bundle.rs(candidate_bundles()+active_contribution()+ thecompile_error!no-bundle guard + theactive_contribution_fortest seam). It is a near-verbatim copy — the only per-binary content is which concrete bundles it lists, which is identical to craig-web’s here. (A future refactor could macro this; for N=2 the copy is clearer and avoids the cycle.) -
Boot. In the standalone boot path (
boot_standalone,main.rs:224— the only mode that serves the embedded UI today), resolveactive_contribution()?, materializecraig_state_bundle::materialize_theme_css(&contribution.theme)?into anArc<str>. Integrated-mode theming is out of scope for P1.3: integrated bootsembed_ui=false(no UI to theme), so it gains the resolve+materialize step only when the UI is mounted in integrated mode (P3 / L7). P1.3 touches the standalone path only. -
Thread it through
UiConfig.craig-intakealready passes runtime state to the UI handlers as a singleExtension<UiConfig>(the struct atui.rs:11, layered atui.rs:58viaroutes(config: UiConfig), read atui.rs:69). Add atheme_css: Arc<str>field toUiConfig, set it from the materialized string at boot, and have the new handler read it fromExtension<UiConfig>. This mirrors craig-web’sAppState.theme_css(main.rs:184,259) and keeps the handler signature to a single existing extractor — prefer this over a separate newtype layer. -
Route. Add
.route("/assets/theme.css", get(theme_css_handler))to the UI router (ui.rs:21-58); the handler returnsconfig.theme_css(Arc<str>) withContent-Type: text/css, mirroringserve_intake_public_css(ui.rs:108) but reading the materialized string instead ofinclude_str!. -
Boot fails fast on a structurally-incomplete palette (the
?onmaterialize_theme_css). Add a boot test asserting the GA palette materializes via the replicatedactive_contribution_for(Some("georgia"))seam.
Step P1.4 — Tokenize the edge CSS + link order (#712)
Files: services/craig-intake/static/css/intake-public.css (hex → var(--token)),
services/craig-intake/static/report.html, keygen.html, status.html (link order).
-
Convert every hardcoded hex in
intake-public.cssto its semantic token per the map below. Rule: each color maps to its role — backgrounds to--page/--surface/--sunken, text to--body/--heading/--muted/--on-primary, accents to the--primary/--danger/--success/--info/--warningfamilies. -
In each HTML file, add
<link rel="stylesheet" href="/assets/theme.css">before the existing<link … href="/static/css/intake-public.css">(report.html:16,keygen.html:14,status.html:11) so the bundle tokens are defined before the component CSS consumes them. -
The edge CSS must end with zero raw hexes outside any documented carve-out (mirror craig-web’s
static_stylesheets_have_no_raw_hex_colorslint posture; if a token genuinely has no semantic home, surface it in review rather than inventing one).
hex → token map (complete)
The authoritative, full per-selector mapping of every hex literal in the current
intake-public.css. Verified against:
-
the current file (
services/craig-intake/static/css/intake-public.css); -
the 28-token contract (the Theme Token Schema);
-
the live palette (
crates/craig-state-bundle/theme/simple-statehouse.toml) — every target token below is confirmed to exist with both light + dark values.
P1.4 applies this map. If the CSS file has diverged by P1.4, surface any uncovered hex in review — do not guess a token.
| hex | selector(s) / role | token |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
A multi-color rule tokenizes every color in place — e.g.
.error-box { background: #fef2f2; border: 1px solid #fca5a5; color: #991b1b; }
becomes
.error-box { background: var(--danger-surface); border: 1px solid var(--danger); color: var(--danger-ink); }.
Phases 2-5 (scoped roadmap)
These are not binding specs. Each phase gets its own committed .adoc, iterated
through contextless review, before it is coded — that phase-specific plan is the
authority when it lands. This roadmap exists only to orient the program and to record
the agreed shape; rough issue weights are in parentheses.
Phase 2 — Integration aids (deps: P1)
-
JSON debug panel (L3, PII guarded): capture the mapped
CpsRequestinShinesSink::forward(aftermapper::map, before the POST) as a prettyString. Do not add it to the backend-agnosticAcceptedReport(that would leak an optional debug field into every public response schema); carry it via a Shines-scoped seam and let the handler attach it to the Shines response only, under L3’s guards (explicit-ack enablement,Cache-Control: no-store, never logged, kept out of the OpenAPI/SDK contract). UI panel below the success card + a CSP-safecopy-helper.js(clipboard +execCommandfallback; secure-context note). -
Nav + view-keys: report/keygen nav gains "Register a Key" / "View Keys" (Shines-gated); a new keyring
GET /keys(metadata only, default-off gate) + an intake proxy + a view-keys page. -
Rough issues: JSON-panel (3), nav + view-keys-endpoint (3), view-keys-UI (2).
Phase 3 — Parity migration (deps: P1; the hard one)
Done (2026-06-26) — see the archived Phase-3 plan
+ ADR-044. The design spike resolved to a shared
craig-i18n crate (Fluent), and the org-keys endpoints were descoped (the prior /public/v1/keys* sketch
was never built + a public org directory contradicts ADR-017 §D5; ADR-044 reconciles where central partner-org
key registration lives instead). The list below is the original sketch, superseded by the committed plan.
|
-
Design spike first: the i18n strategy — Askama server-render in craig-intake vs. a client-side JSON message-catalog (the edge serves static
include_str!HTML; craig-web has ~312report-keys + the|tFluent infra). Decide + document before building. *(Resolved: sharedcraig-i18ncrate — ADR-044.) -
Then: migrate the i18n keys; bring accessibility + the staged-wizard UX up to craig-web’s level; add an attachments UI; mount the embedded public UI in integrated mode (L7) — enable
embed_uifor integrated. -
Issues (per the committed plan): i18n-infra (5), edge-i18n (5), a11y/wizard (5), attach-backend (3), attach-UI (2), embed-ui-integrated (2).
Phase 4 — Mandated-reporter signing (deps: P1; benefits from P2 view-keys + P3 wizard)
Detailed design + the resolved decisions: the
committed Phase-4 plan + the
ADR-043 2026-06-27 amendment. SHINES-scoped + a
structural soft gate (the public form rejects mandated exactly where the Signed backend is wired);
integrated individual signing is a deferred follow-up, and Phase 5 is not gated on it.
-
P4.1 signer-evidence (backend):
/signed/v1/reportsrecords the verified signer (a re-verifiablereport.signedaudit event + asignerfield on the forward) instead of discarding it — the actual non-repudiation value. SHINES has no report store, so this is what makes signing accountable. -
P4.2 signing-UI:
/report’s `mandatedbranch signs in-browser to the SDK header contract ({alg,kid,jti,iat}— craig-web’s helper omitsjti/iat) via atype=modulebridge, POSTs/signed/v1/reports; a requiredmandated_reporter_category; a kid-bearing key file. -
P4.3 approval-UX: register → pending → operator-approve → sign, with per-kid status (the keyring by-kid active lookup), graceful "awaiting approval" handling, and key re-import.
-
P4.4 server-gate (last):
Signed⇒/publicrejectsmandated(422), defense-in-depth behind the UI. -
Issues (per the committed plan): signer-evidence (3), signing-UI (5), approval-UX (3), server-gate (2); +2 deferred follow-ups (integrated individual signing; keyring-receipt hardening).
Phase 5 — Remove craig-web /report (deps: P3 parity complete, incl. L7)
-
Delete outright — no redirect, no compat shim (L6): remove
public_report_routes(),routes/report.rs,templates/report/,report-wizard.js, thereport-locale keys,clients/intake.rs(verify no other consumer, then delete), and the now-unusedintake_urlconfig./reportreaches the edge by *path-based ingress routing (not an HTTP redirect) — requires L7 (integrated intake already serving the UI). Retarget thepublic-report*e2e specs
screenshots to the edge. Update nav +architecture.adoc/services.adoc/ deployment-guide + ADR-043. -
Rough issues: strip-craig-web-public-report (3), e2e + docs retarget (3).
Files Touched (Phase 1)
| File | Change |
|---|---|
|
This plan (P1.1) |
|
Dated amendment: shared renderer + intake as second consumer (P1.1) |
|
New ADR (P1.1) |
|
ADR-043 index entry + this plan under Plans § Active (P1.1) |
|
Receive the renderer + helpers + |
|
Delete moved defs → import shared; keep route handler + |
|
|
|
Replicated resolver; boot materialize; |
|
hex → |
|
Link |
|
|
Verification (Phase 1)
cargo fmt --all
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo nextest run -p craig-state-bundle -p craig-web -p craig-intake
# The edge must gain NO runtime toml from craig-state-bundle (intake already pulls
# toml via `config`; craig-state-bundle must NOT appear among toml's runtime dependants):
cargo tree -p craig-intake -e normal -i toml
cargo xtask plan-lint && cargo xtask check-docs
cargo xtask e2e --no-refresh -- --project=intake-ui-shines
Assertions:
-
standalone
/report+/keygen+/report/statusrender in the active bundle’s palette, and the OSprefers-color-schemeflip applies (it didn’t before); -
craig-web is visually unchanged — its
/assets/theme.cssdiffers only by the banner comment line; -
intake-public.csshas zero raw hexes outside any documented carve-out; -
the
intake-ui-shinese2e project stays green (tests/e2e/specs/intake-embedded-ui-shines.spec.ts,tests/e2e/specs/intake-keygen-signed.spec.ts).
Process & conventions (program-wide)
-
Epic first, then child issues with weights (1/2/3/5/8) + type+priority labels +
epic_idlinkage (/relatefor siblings); one issue per shippable unit. -
Each phase: a committed
.adoc(navPlanned→Active), iterated through 3-4 contextless review rounds before coding; branch + MR per issue (standard cadence);Closes #N+ a closing comment; plan Status flips after each step (canonical tokens only — each MR flips the previous MR’s step toDoneon its branch). -
Multi-MR: only a phase’s final MR touches
.claude/CLAUDE.mdstatus tables, and only if that phase changed a documented status surface (e.g. P5). Earlier MRs update Antora docs. -
Mid-execution discoveries → a GitLab issue +
/relate, never a "future work" section here. Plans are specs; the tracker is the backlog. -
On program completion: archive each phase plan (nav → Archive), run the Plan Completion Audit.
Documentation Updates
-
ADR-036 amendment (P1.1)
-
ADR-043 new (P1.1)
-
nav.adoc— ADR-043 + this plan (P1.1) -
CHANGELOG.adoc—== Unreleasedper MR -
Shared Crates —
craig-state-bundlegainsmaterialize_theme_css+ThemeBootErrorin its public surface (P1.2) -
Services / Standalone Intake — Backend-Profile Architecture — the edge now serves
/assets/theme.css(P1.3) -
Phase 2 (integration aids): Standalone Intake — Backend-Profile Architecture (debug panel + view-keys list/page) + craig-intake-keyring — Signer-Key Registry Sidecar (gated
GET /keys) +configuration-reference.adoc(the two flags) (P2.1–P2.3)