Skip to content

Flagship app: reform spine, shell, parameter search, and composer (feature-flagged) - #1136

Draft
PavelMakarchuk wants to merge 84 commits into
mainfrom
feature/flagship-reform-spine
Draft

Flagship app: reform spine, shell, parameter search, and composer (feature-flagged)#1136
PavelMakarchuk wants to merge 84 commits into
mainfrom
feature/flagship-reform-spine

Conversation

@PavelMakarchuk

Copy link
Copy Markdown
Contributor

Summary

First implementation pass of the flagship app rework: one product spine (reform → simulation → report) with three entry modes, built behind a feature flag so production is unchanged until enabled.

  • Reform domain model + central store: canonical Reform object with provenance (manual | chat | bill | tool), ReformStore (API + localStorage impls) following the existing user-association store patterns, Postgres schema (Drizzle) + /api/reforms route handlers in calculator-app. Handlers degrade to 503 without DATABASE_URL; Neon provisioning via Vercel marketplace is documented in calculator-app/src/db/README.md.
  • Flagship shell: Ask / Tracker / Build / Library nav, dark by default — enable with NEXT_PUBLIC_FLAGSHIP_SHELL=true (or localStorage.setItem('pe-flagship-shell', 'on')). With the flag off, routes and nav are unchanged.
  • Universal parameter search: two-stage engine (token prefilter + fuzzy re-rank) over full hierarchical breadcrumbs — "child tax credit amount" finds gov.irs.credits.ctc.amount in milliseconds across the 52k-parameter US index.
  • Reform composer (in progress on this PR): Ask and Build feed provisions into a shared draft with inline value editing, saved into the reform library.

Testing

  • 34 integration tests run the actual route handlers against in-memory Postgres (PGlite) with the checked-in migration: CRUD, validation battery, SQL-injection/unicode/500-parameter payloads, user isolation, degraded mode
  • Search-quality tests against the real 52k-parameter US metadata + latency regression bound
  • Shell/flag tests for both nav states; full suite green (turbo run test), both typechecks, production build

How to try

cd calculator-app && NEXT_PUBLIC_FLAGSHIP_SHELL=true bun run dev
# open http://localhost:3001/us

Design doc: internal artifact "One app: PolicyEngine flagship design" (Aug 2026).

🤖 Generated with Claude Code

PavelMakarchuk and others added 6 commits August 2, 2026 23:09
Introduces the canonical Reform object with provenance (manual/chat/bill/
tool) as the shared spine of the flagship app rework, following the
existing ingredient + store patterns:

- Reform type built on the existing Parameter/ValueInterval types, with
  a policyId link for materializing into canonical API policies
- ReformStore interface with ApiReformStore (/api/reforms) and
  LocalStorageReformStore implementations, mirroring the
  user-*-association stores
- ReformAdapter for camelCase/snake_case wire conversion
- Central Postgres store: Drizzle schema + checked-in migration and
  /api/reforms route handlers in calculator-app (Web-standard
  Request/Response so app/tsconfig's program stays free of next/server's
  React typings)
- Handlers return 503 when DATABASE_URL is unset, so environments
  without the database degrade cleanly; no existing page imports any of
  this, so prod behavior is unchanged

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
34 integration tests run the actual /api/reforms route handlers against
an in-memory Postgres with the checked-in migration applied: full CRUD,
the complete validation battery, SQL-injection-shaped and unicode
labels, 500-parameter payloads with mixed value types, per-user
isolation, updated-first ordering, 404 paths, and the degraded 503 mode
when no database is configured.

Adds setDbForTesting() injection to the db module and guards the vitest
browser mocks so node-environment suites can run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The four-verb navigation from the flagship design doc, dark by default:
enable with VITE_FLAGSHIP_SHELL=true (NEXT_PUBLIC_FLAGSHIP_SHELL in
calculator-app) or localStorage.setItem('pe-flagship-shell', 'on').

- Ask: natural-language entry stub; examples per country, routes into
  Build until the hosted agent service lands (phase 2)
- Tracker: links to the existing proxied bill tracker until the native
  feed lands (phase 3)
- Build: fronts the existing policies/create pathway
- Library: first real reform-store consumer — lists saved reforms with
  provenance badges, empty and error states, links to reports/households
- Sidebar and CalculatorRouter switch on the flag; with it off, routes
  and nav are byte-identical to before

12 new tests cover the flag, both sidebar states, Ask interactions, and
Library loading/empty/error states.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two-stage search over the full parameter index (~52k entries for the
US): a token AND-prefilter over precomputed breadcrumb+path haystacks
answers common queries in a few milliseconds, with fuzzy re-ranking of
the candidate set; full-fuzzy fallback catches typos. Stress testing
against real US metadata drove the design — a single-stage fuzzy scan
took ~1.3s per multi-word query, far too slow for typeahead.

Breadcrumbs come from the existing getHierarchicalLabels util, so a
query like "child tax credit amount" now finds
gov.irs.credits.ctc.amount even though its leaf label is just "amount"
— the core findability fix the flagship rework promised.

- libs/parameterSearch.ts: entries, index, two-stage search, memoized
  selectors (shared later by the agent's locate stage)
- ParameterSearchBox: keyboard-navigable typeahead showing breadcrumb +
  path
- Build page: search fronts the editor; selecting a parameter shows
  breadcrumb, path, unit, description, and formatted current value
- 18 tests including search-quality assertions against the real 52k
  US metadata and a latency regression bound

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Running the flagship shell locally surfaced three issues:

- The lockfile churn from earlier dependency adds split
  @tanstack/react-query into two copies (root 5.90.20 + app/ 5.84.1),
  so the QueryClient context lookup crashed every calculator page
  during dev SSR. Regenerated bun.lock from the branch-base lockfile so
  only the genuinely new packages are added and a single react-query
  resolves.
- calculator-app routes via per-page Next wrappers, not the shared
  react-router table, so ask/tracker/build/library got thin Next pages
  following the existing pattern, gated by FlagshipGate (404 behavior
  when the flag is off) plus a flag-aware country index redirect.
- FlagshipGate originally fell back to the shared NotFoundPage, whose
  react-router Link crashed under Next SSR whenever the server-side
  flag check disagreed with the client. The gate now uses an
  SSR-stable env check (NEXT_PUBLIC_FLAGSHIP_SHELL is inlined into
  both bundles), reads the localStorage override after mount, and
  renders a router-free fallback.

All four flagship routes now server-render cleanly with no hydration
errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The blanket build/ ignore rule for build output also matched the Next
route segment calculator-app/.../(calculator)/build/, silently dropping
its page from the previous commit. Scope a negation so route source
under calculator-app/src/app survives.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
policyengine-app-v2 Ready Ready Preview Aug 24, 2026 5:33pm
policyengine-calculator Ready Ready Preview Aug 24, 2026 5:33pm
policyengine-calculator-next Ready Ready Preview Aug 24, 2026 5:33pm
policyengine-website Ready Ready Preview Aug 24, 2026 5:33pm

Request Review

…-end

The flagship loop now works without any backend: describe or find a
parameter → editable draft reform → save to library.

- draftReform lib: cross-page draft state (localStorage +
  useSyncExternalStore) with provisions carrying breadcrumb, unit, and
  baseline; converts to the Reform store shape with provenance intact
- ReformPreviewCard: the trust layer — every provision spelled out as
  baseline → new value, editable inline, before anything is saved
- Ask: questions run through the parameter search index; matched
  parameters become suggested provisions with an honest note that
  keyword matching stands in for AI drafting until the hosted service
  lands; recent reforms surface below
- Build: parameter detail card gains "add to draft reform"
- Tracker: native feed of clearly-labeled sample bills demonstrating
  the bill → editable reform bridge (the Modal tracker exposes no
  public JSON API yet); full tracker remains linked
- Library: edit (loads the composer), duplicate, and delete actions
- Shared parameterValues utils (current-value + formatting)

36 new tests cover the draft lifecycle, preview card interactions,
Ask matching, tracker bridge, and library actions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
In the flagship IA a report is the output of a reform, not a thing you
start from scratch — so the shell's primary action opens Build (the
composer) instead of the legacy report wizard. Legacy mode keeps the
existing "New report" button untouched; tests cover both states.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Over half the US index (23k of 42k parameters) is state-specific, plus
392 contributed/experimental parameters — the two main sources of
result clutter:

- Entries carry isContrib and stateCode parsed from the path
- searchParameters takes filters: contributed hidden by default,
  state scope selectable (all / federal only / federal + one state)
- Search box grows a scope dropdown and contributed opt-in, state and
  contributed badges on result rows, and a "N matches hidden by
  filters" hint so filtered-out results are never a silent mystery
- Leaf labels get their own strong fuse weight (0.3) and join the
  fast-path haystack — they are often the most information-dense part
  of the hierarchy
- Ask inherits the same defaults (no contributed suggestions)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four entry points don't earn a 300px persistent sidebar — that's
dashboard chrome, and the flagship's front door (Ask) wants to be
full-width and calm. In flagship mode StandardLayout now renders a slim
tab bar (Ask · Tracker · Build · Library + "New reform") under the
header and no sidebar; content gets the full viewport width.

The legacy sidebar layout is byte-identical with the flag off, and
Sidebar.tsx reverts to its pre-flagship form since it no longer renders
in the shell. Tests cover both layouts and the top nav interactions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PavelMakarchuk and others added 30 commits August 11, 2026 12:49
Replace the hand-rolled tab and toggle buttons with the shared design
system components the report-output redesign and one-off tools use:
the radix Tabs pill list, SegmentedControl for the dollars/percent
chart toggle, and MetricCard (hero revenue card with trend chips,
invertArrow poverty metrics) inside bordered report cards. The title
takes the report layout's teal 3xl treatment, and the sponsor/date
attribution row gets aligned icons.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pill tab bar reads cleaner as the line variant — hairline rule
under the full bar, teal underline on the active tab, wider gaps.
The bill summary paragraph moves above the metric cards in Overview.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Selecting a state in the scope dropdown now returns only that state's
parameters (previously it kept federal too, labeled "Federal + CA");
options read "CA only" to match. The scope and contributed filters move
above the search input, and the "N matches hidden by filters" hint is
removed. The Ask page's three section tiles stretch to equal height.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ic contract

For countryId uk, Ask now streams real turns from policyengine-uk-chat
through a same-origin proxy route (no CORS coordination, no service
changes): markdown answers, tool activity lines, follow-up suggestion
chips, and a draft bridge — the validated reform JSON the service's
simulation tools carry is mapped onto our parameter index and offered
as 'Add to draft' provisions for the composer. Transport failure falls
back to the keyword matcher; the US path is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds /api/us-ask/chat/message — Claude (claude-opus-5) with deterministic
parameter-search tools (search_parameters, get_parameter, validate_reform)
over live policyengine-us metadata, streaming the same SSE event shapes
as the UK chat service so the Ask client and the chat→draft bridge work
unchanged for both countries. The agent drafts and validates reforms;
impacts still come from running the report. The tool layer is pure and
unit-tested in app/src/libs/flagship/usAskAgent.ts; the route holds only
the streaming loop and a cached metadata index.

The chat client generalizes to askChat.ts with per-country endpoint
resolution: UK stays on by default, US is opt-in via NEXT_PUBLIC_US_ASK=on
and needs a server-side ANTHROPIC_API_KEY (the route 503s without one and
Ask falls back to keyword matching).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Next.js inlines process.env.NEXT_PUBLIC_* textually into the client
bundle, so the dynamic process.env[name] lookup was always undefined in
the browser and the US opt-in flag never enabled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The add-to-draft card only exists when the model calls validate_reform;
a turn that described the reform in prose and told the user to add it
left nothing to click. Make the tool call a hard precondition for
proposing a reform, and validate in the first response when the user's
message already fully specifies the change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
For fully specified imperatives the validate_reform card is the answer;
cap the accompanying text at a sentence or two and reserve longer
explanations for open questions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A multi-provision directive burned all six tool turns on research and
returned an empty answer with no card. Raise the budget to ten turns
and, past it, re-run with tool_choice none so the model must close
with prose instead of ending tool-hungry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ack record

Two validation surfaces on the bill report, one tab:

- External checks for this bill: the tracker pipeline's
  validation_metadata (official fiscal note, third-party analyses,
  accepted range, within-range verdict, discrepancy explanation) joins
  into the bill feed and renders as a comparison table, with a
  within-range chip in the report header.

- Model track record: a new /api/model-validation route serves compact
  US-level comparable rows from the PolicyEngine scorecard (PE vs
  external analyses like Urban's State of the Safety Net) for the
  programs the bill's provisions touch, cached server-side; rows are
  labeled held-out vs calibrated so the honesty distinction survives
  into the UI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Radix unmounts inactive tab panels, so the scorecard fetch only
started when the Validation tab was opened. Lift it into a
useModelTrackRecord hook called at page level so it runs in parallel
with the rest of the report, and show a loading note for the rare case
the tab opens before it lands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Budgetary gains an average-per-household card and, when the tracker's
validation pass found external estimates, a magnitude-plotted
comparison chart (PolicyEngine vs fiscal-note estimates or range) with
signed labels. Poverty gains a grouped before/after bar chart for
overall and child rates. Distribution gets value labels and a taller
canvas.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The prose findings duplicated what the report now shows properly —
headline metrics as tiles, external checks on the report's Validation
tab, and provenance under Notes and sources.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The bill detail duplicated the report's overview (summary, tiles,
provisions) as an extra hop with nothing of its own. Scored bill cards
now navigate straight to the report; the detail remains the launchpad
for bills without computed impacts and the editor for saved reforms.
The report content column widens 900→1040 inside a 1480 shell so tabs
and charts use the space.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Also gitignore calculator-app/.vercel from the CLI link.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The findings prose reiterated the impact tabs and the validation tab;
what the tab uniquely owns is provenance — model version, data
version, and computed date.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The deployed score repository at policyengine.org/scorecard publishes
its data under /scorecard/data; reading the Urban shard directly means
the report's track-record rows always match what a click-through to
the scorecard shows, including per-row held-out/seeded relationships
and the shard's row_defaults.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Urban comparisons validate the model's baseline representation of
the programs a bill touches; they are not reform-matched. Retitle and
caption them so they can't be read as a check of the bill's estimate,
which only the fiscal-note section claims.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
External numbers now flow through ValidationClaim — a policy-keyed row
mirroring the deployed scorecard's data shape — with an adapter from
the tracker's validation_metadata. Bill checks render from claims, so
when fiscal notes move into the scorecard as a source, the swap is the
adapter, not the UI. Claim rows also show the PE-to-external ratio.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two costs made each Build navigation pay ~700ms: ParameterSearchBox
built its own index per mount, and index creation eagerly constructed
a Fuse instance over ~20k entries that the fast prefilter rarely
needs. The box now accepts the store-memoized index (built once per
session) and Fuse construction is lazy, deferred to the first query
the prefilter can't answer. Warm tab switches drop to ~150ms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
entriesByPath rebuilt a 20k-entry Map per mount and addablePaths
rebuilt a 20k Set per render. Both are now reselect selectors computed
once per metadata load, bringing Build's warm switch time level with
the other tabs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The report is the main event; adjusting stays one click away on the
slim edge tab.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The deployed scorecard's data layout changed (shards back to a single
comparison file), 502ing the track-record route and leaving Validation
tabs blank. The route now tries each known layout in order and
normalizes both row shapes, with the repo's committed file as final
fallback; a failed fetch renders a note linking to the scorecard
instead of nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The blank-tab regression shipped because nothing asserted what renders
when the scorecard is unreachable. Cover unavailable, loading, empty,
and populated states of the track-record section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nchors it

Rhode Island-style validations triangulate from scaled third-party
scores; calling that a fiscal-note range would over-claim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A second-pass verifier now re-checks every stored claim against its
source; the panel shows the outcome (re-verified / partially /
disputed / unverifiable) with the date.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each validation_metadata row now pins what it checked (validated_against:
PE estimate, model version, bill status). billFeed compares the snapshot
to the live run and flags drift; the chip drops its verdict and asks for
a re-check, with a note explaining why.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The report page's Validation section now renders the model track record
for the drafted provisions and an opt-in "Find external estimates"
action. It dispatches a web-search agent that hunts official and
third-party scores of the same or similar proposals and returns
structured findings with honest comparability tiers (direct / similar /
context) — figures only as sources state them, never constructed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The localStorage override now only works where FLAGSHIP_OVERRIDE=allow
is set (dev servers and the beta deployment) — production builds have
no runtime activation path. All flagship API routes (us-ask, uk-chat
proxy, validate-estimate, model-validation, reforms/reports persistence)
404 unless NEXT_PUBLIC_FLAGSHIP_SHELL is on, so flag-off deployments
expose no new surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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