Repository navigation
Conversation
The app gains types for the Public API's v2 routes, generated with openapi-typescript from a checked-in copy of its OpenAPI document (app/src/api/publicApi/openapi.json), and a small openapi-fetch client that sends the existing v2 credentials and unwraps the success envelope. - `bun run public-api:generate` writes schema.d.ts from openapi.json. - `bun run public-api:refresh <source>` copies the document from an API origin, a URL or a file, then regenerates. - `bun run public-api:check` fails when schema.d.ts does not match the document; the new PR job "Public API client is current" runs it. The app has no required checks, so it stays advisory. Nothing calls the client yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…itch
v3 gains one place that chooses its Public API routes, the constant
`V3_API_ROUTES` in `libs/flagship/v3ApiRoutes.ts`. It stays 'v1' until
the switch, so nothing users reach changes: the workspace and the older
report builder still load the whole model from `GET /{country}/metadata`.
With the constant at 'v2', a v3 workspace page loads only
`GET /v2/model-catalog` at start: the catalog version's PolicyEngine.py
version, entities, basic inputs, regions, years and datasets, in the
shape the metadata store already holds. Parameters and variables start
empty, to be loaded on demand, and no whole parameter tree is built.
The workspace counts as ready only with metadata from the routes it uses,
and a retry asks those routes again. The older report builder stays on
v1 either way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…routes
On the v2 routes v3 does not download the whole metadata, so its search,
add bar and draft editor need another source for which parameters a
reform can take. That source is the catalog version's search file
(`GET /v2/parameters/search-file`), named by version so the browser can
keep it.
- `fetchParameterSearchFile` reads the file and refuses a format this app
does not read.
- `buildV2SearchCatalog` turns it into the collection v3's existing search
builders take (unchanged): v1's fallback labels for unlabelled
parameters and nodes ("bracket N" for a bracket), the parameters a
reform cannot change kept for their labels only, and no values (they
load on demand). Which parameters a draft can take comes from each
entry's editability for today's date. It is built once per file and date.
- `useV3SearchCatalog` gives search entries, the index, the addable paths,
concept clusters, labels and a status: from the whole metadata on the
v1 routes, from the search file on the v2 routes, where it stays
'loading' until the file arrives and 'error' with a retry if it fails.
`useV3SearchFileInBackground` starts the download once a page renders.
Nothing reads it yet; the constant `V3_API_ROUTES` is still 'v1'.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the v2 routes the metadata store starts with no parameters. Views now have a way to name the parameters they show and have them load: - `fetchCatalogParameters` asks `GET /v2/model-catalog/parameters` for up to 100 names a request, at the catalog version, and returns each parameter in v1's shape (values dated as v1 dated them, v1's fallback label where none is stored), the labels of every node above them for breadcrumbs, each parameter's catalog ID and the names the version does not have. - The store's `catalogParametersLoaded` merges them, for the current country and catalog version only. - `useEnsureV3Parameters(names)` loads what the store lacks in one request and reports loading, error (with a retry) or ready; names already loaded, or that the version does not have, are not asked for again. `useLoadV3Parameters` returns a function that resolves once names are loaded, for actions such as adding a parameter. On the v1 routes, where the whole metadata holds every parameter, both report ready with the metadata and send nothing. `V3_API_ROUTES` is still 'v1', so users reach none of this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the v2 routes the metadata store holds only the parameters loaded on demand, so each v3 view that reads current law or parameter labels now names what it shows and waits for it, with the loading or error state it already has. On the v1 routes the same hooks report the metadata's own status, so what users see is unchanged. - Build: waits for the search file and for the draft's parameters (its provisions, their groups' members once the search file names them, and the indexes a value can grow with); a picked parameter's values load before it joins the draft, and a failure says so. The add bar, the draft table and the provision details read what a draft can take from `useV3SearchCatalog`, and the comparison picker waits for the compared reform's parameters. - Ask: matches from the search file, says it is loading until the file is in, loads a match's current law before adding it, and loads a chat reform's parameters before building its draft. - Your reforms, a report and a bill's report load the parameters of the reform, policy or bill they show in one request, and retry them. - The v3 page layout starts the search file's download behind the page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…log loads On the v2 routes a v3 page (Ask, Build, Your reforms) now waits for the small `/v2/model-catalog` behind one short loading page, and says "Could not load the model" with a retry if it fails. Everything else the page shows waits in its own skeletons, with no time limit, as before. On the v1 routes the page shows at once, as today. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ildren On the v2 routes there is no whole parameter tree in the store, so the add bar's policy browser now reads the tree one level at a time from `GET /v2/parameters/children`, listing only what a reform can change: the top level when it opens, then each folder as it is opened, one request a level (every page of it), each kept for the session. The trail's labels come from the levels above the open folder; a folder opened from search loads them. A folder's parameters load their current law in one request so the rows show it. Placeholders stand for a level until it arrives, with no time limit, and a failure offers a retry. The browser's markup moved into `PolicyBrowserView`, shared by the whole-tree browser (the v1 routes, unchanged) and the on-demand one; `V3PolicyBrowser` picks between them by `V3_API_ROUTES`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With `V3_API_ROUTES` at 'v2', a reform saved to the account becomes a
`/v2/policies` policy (`POST /v2/policies`, for the loaded catalog
version and its tax-benefit model) and its `/v2/user-policies` row points
at it with `policy_id`. Each dated value names its parameter's catalog
ID, which comes with the parameters loaded on demand; any not loaded yet
load first. Editing a reform's parameters makes a new policy and repoints
the same row with `PATCH /v2/user-policies/{id}` (`policy_id`), so its ID,
name and date stay; a rename only patches the name. Rows saved before the
switch that hold only a v1 policy still open, read from
`GET /{country}/policy/{id}`. Each v2 policy is fetched once per session.
On the v1 routes, and for reforms kept in the browser, nothing changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Parameters loaded on demand live in the metadata store, which every metadata load empties, but the queries that loaded them stayed cached, so after another page reloaded the metadata (for example the older report builder, then Build again through browser history) a draft or a reform kept its placeholder and a save failed for lack of catalog IDs. Each load of the v2 model catalog now has an ID in the store (`v2LoadId`, the load's request ID). Batch queries are keyed by it, and `catalogParametersLoaded` takes parameters only for the current load, so after a reload the same names are asked for again and arrive in the new store. The v2 catalog version and load have one selector each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The report hooks parsed each policy against the metadata store as soon
as the metadata was ready. On the v2 routes that is the small model
catalog, with no parameters loaded yet, so parsing failed ("Missing
parameter metadata") and the report page, which takes the names to load
from the parsed policies, never asked for them.
`useV3Policies` now reads each policy as stored (`GET /{country}/policy/
{id}`, under its own query key), makes sure of the parameters the stored
policies name (loaded in one request on v2; already in on v1), and only
then parses them; a policy that cannot be read or parsed is listed as
failed, and retry asks again for what failed. `useFlagshipReport` and
`useFlagshipHouseholdPolicies` both use it, so they share the stored
policies as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The country index and the tracker's old address only send the browser on (into v3 when it is on), yet they started v1's whole-model download first. `MetadataBootstrap` now picks a page's routes from all its segments with `metadataRoutesForPage`: once `V3_API_ROUTES` is 'v2' those two pages load nothing, and the page they redirect to loads its own. Before the switch they load v1 as today. The country-ID architecture document describes the routes argument. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…loads The on-demand policy browser treated a level that was still loading like a path the tree does not hold, so a deep folder opened from search showed the top-level grid until every level of its trail had arrived. Now any level of the open folder's trail that is still loading keeps the placeholders up, and only a path that is missing once every level is in falls back to the top level. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…when they fail On the v2 routes a bill's or a saved reform's parameters load when its detail opens. The bill detail showed its provisions and enabled "Run impact report" and "Open as draft reform" before they arrived, and the saved-reform detail kept its placeholders forever when the load failed. Now, as on the bill's report page, both details show placeholders and keep their actions disabled while the parameters load, and a failed load shows "Could not load the parameters" with a retry. On the v1 routes neither detail waits, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the v2 routes a listed match's current law loads after the match shows, and the chat's validated reform loads its parameters before it can join the draft. The page discarded the match rows' load status, so a row read "current: —" while loading and after a failure; the chat built its draft step from missing values when the load failed; and a failed load for a picked match was an unhandled rejection. Now a match row reads "current: loading…" until its value arrives and "current: could not load" after a failure, with an alert and a retry. A picked match whose parameter fails to load stays out of the draft and the page says so, as Build does. The chat's draft step waits for the reform's current law, and a failed load says so with a retry instead of offering provisions without it. The search file's failure message is an alert whose "Try again" is the design system's button. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…talog's conversion without casts Six v2 requests cast the app's country ID to the countries the Public API serves. They now take it from publicApiCountryId, whose type comes from the generated client and whose list of countries that type checks; a country the v2 routes do not serve throws before any request. The model catalog's conversion keeps only the region types v1 listed through a type guard, so its entries need no cast, and types its entities as the catalog's. The report pages' test reform policy moves to the report policy fixtures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… switch Besides the country index and the old bill tracker, three more pages only send the browser on into v3: the old Home launcher (to Build), the old Library (to Reforms) and the paused Ask page (to Build). After the switch they now load nothing, like the other two, and the page each one lands on loads its own metadata. Before the switch they load as they do today. A page that redirects into the older report builder keeps v1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… report as it was On the v2 routes, when the parameters a report's policies name failed to load (or the model catalog had failed), no policy was parsed, none was listed as failed and nothing was loading, so the Report page said "Loading report…" for good and the household breakdowns kept their spinner. Such a policy is now listed as failed: the Report page shows "Could not load the reform policy" with a retry, the household breakdowns show their error, and retrying asks for the parameters again. A policy that names no parameter (current law) parses regardless. The v2 hook memoises what it parses on the stored policies and the loaded parameters, so the policies keep their identity across renders. On the v1 routes the report and household hooks read and parse the policies as they did before v3 loaded parameters on demand: once the whole metadata is in, without waiting for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ey fail The open folder's parameters load their current law once they are listed, but the tree dropped that load's status, so the rows showed no value while it loaded and after it failed. Now each row holds a placeholder for its value while it loads, and a failed load shows "Could not load current law for these parameters" with a retry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…asks for it useReformStore read the parameters and the v2 catalog through refs set on render, but the catalog IDs of the parameters from the Redux store. All three are now read from the Redux store, through selectors for the v2 parts (selectV2Catalog, selectV2ParameterIds), when the reform store asks for them. Metadata that arrived since the last render is used, and its arrival still does not rebuild the reform store. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… from its parameters Reform.policyId held a v1 numeric policy ID or, on the v2 routes, a /v2/policies UUID. The report rerun read it as a number, so a report compared with a reform saved as a v2 policy could not be run again when its baseline simulation could not be read. policyId now holds only the v1 ID, and v2PolicyId holds the UUID. A rerun compares with a reform that has only a v2 policy through its parameters, from which the run makes a v1 policy, as Build's runs do. The v2 reform store's create and PATCH bodies now use the generated UserPolicyCreateRequest and UserPolicyPatchRequest types, so they are checked against the OpenAPI document. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the v2 routes the draft's current-law values load after the page opens, and the table shows placeholders until they arrive. "Save" and "Save and run" stayed enabled meanwhile, though saving keeps only the values that differ from current law. Both now stay disabled while those values load, or after the load fails. On the v1 routes they behave as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… say so On the v2 routes a saved reform chosen as the comparison loads current law for its parameters, and "Save and run" waits while it loads. When that load failed, the run was offered again with no message, and what the comparison changes was read without current law. The run bar now shows "Could not load current law for the comparison" with a retry, and "Save and run" stays disabled until it loads. On the v1 routes nothing changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The OpenAPI document is now the Public API's final v2 metadata routes, exported from the stack ending at the parameter search file. What changed in the generated types: - The model catalog's parameters and parameter summaries always carry `economy` and `household` (each may still be null). - A catalog variable always carries `is_input_variable`, `hidden_input`, `module_name`, `adds_parameter_path`, `subtracts_parameter_path` and `enum_values` (each may be null), and gains `unit` and `definition_period`. - A search file parameter's `editable_changes` may be left out but is never null. - The metadata routes declare 422 for malformed or unknown query parameters in place of 400. `/v2/parameters/children` declares ETag, Cache-Control and Vary on 200 and a 304; the search file declares those headers on its 304. Several descriptions are reworded. The app reads these fields as before (a null `economy` or `household` still reads as true, and missing editability changes as none), so no code changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…search file The on-demand browser printed each folder's `child_count` from `/v2/parameters/children`, which counts the folder's direct children a reform can change, and hid only folders with none. v1 counts the parameters a draft can add at or below a folder and hides folders with none. Each folder's count now comes from the addable parameters in the search file the app already downloads (editable on today's date), by path prefix, so the counts match v1 and a folder with nothing to add is hidden. The browser shows only once the search file is in, since the add bar waits for it, so a count is never unknown. The test fixture now gives the Public API's direct-child counts, and holds a folder whose only parameter a draft cannot edit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nly on v2 With the switch off, some failures showed differently from today: - The Reforms page read any failed parameter status as a failure, so on a v1 metadata failure the bill detail replaced its provisions with "Could not load the parameters" and disabled its actions, and the saved-reform detail disabled its report button. - The Report page said "Could not load the reform policy" when a v1 policy request failed. - The household breakdowns said "Could not load policy N" instead of the request's own message, and no longer counted a failed refetch of a policy already in hand as an error. These failure displays now appear only on the v2 routes, and the household hook's v1 path is again the code it had before v3 loaded parameters on demand. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ew question The chat's validated reform loads its parameters before it can join the draft, and the step then updated whichever turn was newest. A question asked during that load received the earlier answer's draft step, and the earlier turn kept saying it was loading. The step now updates the turn its question started, as its retry already did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…API values where they arrive Both reform stores now build their `/v2/user-policies` bodies from the generated UserPolicyCreateRequest and UserPolicyPatchRequest types. The hand-written bodies and their policy-reference union are gone, along with the type test of that union; the v1 store sends the same fields as before. A parameter value the Public API sends is typed only as JSON. The catalog parameters and a v2 policy's values are now checked where they arrive, and a value that is not a parameter value fails there, naming its parameter, instead of being cast to the app's value type. Values a reform sends to `/v2/policies` are checked as finite JSON before the request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bun.lock had been written by a newer Bun than the repository's pinned bun@1.2.21, which added a `configVersion` field and dropped the optional `@edge-runtime` entries. It is now written by bun@1.2.21 from the base lockfile, so it changes only to add openapi-fetch and openapi-typescript and the packages they depend on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1443
What this adds
v3 (Build, Reforms, the report pages and Ask) can load its model metadata from the Public API's v2 routes a piece at a time, and save reforms as
/v2/policiespolicies. All of it is merged unused: it runs only once the switch below is turned on.app/src/api/publicApi/schema.d.tsis generated with openapi-typescript 7.13.0 from a checked-in copy of the Public API's OpenAPI document (app/src/api/publicApi/openapi.json).bun run public-api:generatewrites the types,bun run public-api:refresh <source>copies the document from an API origin, a URL or a file and regenerates, andbun run public-api:checkfails when the types do not match the document.GET /v2/model-catalog(entities, basic inputs, regions, years, datasets and the catalog version), converted to the shape v1's metadata has, so everything that reads the metadata store keeps working. Pages that only redirect into v3 (the country index, the old Home, Library and tracker addresses, and the paused Ask page) load nothing.GET /v2/model-catalog/parameters?name=…(up to 100 names a request) into the metadata store when the view names them. They are requested again after the model catalog reloads.GET /v2/parameters/children(reform_changeable=true). Each folder's count is the number of parameters a draft can add at or below it, taken from the search file, as v1 counts the whole tree; a folder with none is hidden.GET /v2/parameters/search-file, turned into the same search index v1 builds, with editability on today's date fromeditableandeditable_changes./v2/policies:/v2/policiespolicy, and the/v2/user-policiesrow points at it withpolicy_id.PATCH.legacy_policy_id) still opens.policyId) and its v2 policy ID (v2PolicyId) in separate fields.UserPolicyCreateRequestandUserPolicyPatchRequesttypes./v2/policiesare checked as finite JSON before the request.Merged unused: the switch
Every v2 path sits behind
V3_API_ROUTESinapp/src/libs/flagship/v3ApiRoutes.ts. It is a constant, not a runtime flag or an environment variable, and it stays'v1'here. Hooks and components choose their v1 or v2 implementation from it when the module loads. Tests that cover the v2 paths mock it to'v2'.With
'v1', the app keeps today's behaviour:GET /{country}/metadatafor the whole model, and v1 policies for saved reforms.Turning the switch on is a later pull request.
Environment variables
None new.
BASE_URLinapp/src/constants.ts. It is built fromVITE_API_URL, or fromNEXT_PUBLIC_API_URLmapped onto it in calculator-app, and falls back tohttps://api.policyengine.org. This PR does not change where those variables come from, and I did not verify their providers here.bun install --frozen-lockfileand runsbun run public-api:checkinapp/.Declared deviations
The generated document
The OpenAPI document keeps the Public API's internal migration stage numbers (Stage 10, 11 and 12A) in some route descriptions. They are kept as generated.
What it waits for
Testing
CI runs on this branch are cancelled at the author's request; the evidence is local.
bun run check(tsc, ESLint, Prettier),bun run stylelintand the full app suite passed before the commit. The calculator-app's type check and lint ran where it changed.make typecheckandmake lint(app and calculator-app) pass, the app suite passes with 6,059 tests (10 skipped) on Node 26 as CI uses, the website tests pass with 483, andbun run public-api:checkreports the types match the document.Review
Three review rounds, each fix test-first:
This replaces #1346 and #1347, which were closed on 2026-10-20 without merging.
🤖 Generated with Claude Code