Skip to content

perf: split lightweight state-index out of the registry (drop ~26KB from client routes) - #7

Merged
Steel-tech merged 2 commits into
mainfrom
feat/phase-3-registry-split
Jun 18, 2026
Merged

perf: split lightweight state-index out of the registry (drop ~26KB from client routes)#7
Steel-tech merged 2 commits into
mainfrom
feat/phase-3-registry-split

Conversation

@Steel-tech

@Steel-tech Steel-tech commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Highest-value remaining Phase 3 perf item from the roadmap (docs/plans/2026-06-17-001).

Problem

content/state-registry.ts (2860 lines, ~26KB gzipped) was imported by 17 "use client" components, so the full regulatory dataset shipped in the client bundle of every route that touched it — including routes that only needed a state's name/emoji for a dropdown or label.

Fix

  • Generated content/state-index.ts — a standalone {code, name, emoji} index (1.4KB gzipped), produced by scripts/gen-state-index.ts. Standalone (not derived from the registry at runtime) so importing it doesn't pull the registry back in.
  • content/state-index.test.ts asserts the index never drifts from the registry (same codes, matching name/emoji) — adding a state to the registry without regenerating fails CI.
  • Repointed 8 light consumers to the index. Routes whose entire client tree is now light — the wizard flow, wizard/summary, capability-statement, gc-directory — drop the ~26KB registry.

Classification was compiler-enforced, not guessed

Repointed candidates, then let tsc reject any that actually needed full StateData. It caught app/page.tsx (renders state tax fields) — reverted to the registry. Consumers that genuinely render full data (home tax info, estimator calc, starter-kit, comparison-table/summary, unions) intentionally stay on the registry.

Verification

  • 74 tests green (+3 sync-test) · tsc --noEmit clean · lint 0 errors (22 warnings) · production build compiles.

Not done (future, deliberate)

  • Home /, /estimator, /compare, /starter-kit, /unions still carry the registry because they render full per-state data client-side. Moving those to server-component data loading (pass only what's needed as props) is the next-tier win — a larger RSC refactor for its own PR.
  • CI bundle-size budget to lock in the gain.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores

    • Optimized internal state data structure for improved performance and maintainability.
  • Tests

    • Added comprehensive test coverage to ensure state data consistency and accuracy across all pages and components.

Steel-tech and others added 2 commits June 18, 2026 01:06
content/state-registry.ts (~26KB gzipped) shipped to the client via every
component that imports it. Many only need {code, name, emoji} for dropdowns
and labels. Add a standalone generated state-index.ts (1.4KB gzipped) so those
components can stay off the full registry. Generated by scripts/gen-state-index.ts;
state-index.test.ts asserts it never drifts from the registry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8 client components that only read {code, name, emoji} now import the
lightweight state-index instead of the full STATE_REGISTRY, so their routes
(the wizard flow, wizard/summary, capability-statement, gc-directory) drop
the ~26KB registry from the client bundle. Consumers that render full
StateData (home tax info, estimator calc, starter-kit, comparison, unions)
intentionally keep the registry — typecheck enforces the split.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4631da1f-f327-45db-bdf4-128bd9ba11a6

📥 Commits

Reviewing files that changed from the base of the PR and between 46b5828 and 350d2f1.

📒 Files selected for processing (11)
  • app/compare/page.tsx
  • app/wizard/summary/page.tsx
  • components/capability/cap-statement-form.tsx
  • components/compare/state-selector.tsx
  • components/estimator/estimate-results.tsx
  • components/gc-directory/gc-search-filters.tsx
  • components/onboarding/onboarding-chat.tsx
  • components/wizard/progress-sidebar.tsx
  • content/state-index.test.ts
  • content/state-index.ts
  • scripts/gen-state-index.ts

📝 Walkthrough

Walkthrough

A new codegen script (scripts/gen-state-index.ts) generates content/state-index.ts, exporting a StateSummary interface, a STATE_INDEX record, and a STATE_LIST array derived from STATE_REGISTRY. A synchronization test validates parity. All eight consumer files (pages and components) are updated to import and use STATE_INDEX instead of STATE_REGISTRY.

Changes

STATE_INDEX Codegen and Consumer Migration

Layer / File(s) Summary
Generator script and generated STATE_INDEX module
scripts/gen-state-index.ts, content/state-index.ts
gen-state-index.ts reads STATE_REGISTRY, sorts entries, and writes content/state-index.ts exporting StateSummary interface, STATE_INDEX record keyed by state code, and STATE_LIST sorted by name.
Synchronization tests
content/state-index.test.ts
Vitest suite asserts STATE_INDEX keys, per-entry code/name/emoji values, and STATE_LIST length and sort order all match STATE_REGISTRY.
Consumer migration across pages and components
app/compare/page.tsx, app/wizard/summary/page.tsx, components/capability/cap-statement-form.tsx, components/compare/state-selector.tsx, components/estimator/estimate-results.tsx, components/gc-directory/gc-search-filters.tsx, components/onboarding/onboarding-chat.tsx, components/wizard/progress-sidebar.tsx
All eight files replace STATE_REGISTRY imports and lookups with STATE_INDEX, covering state code validation, dropdown/select population, state name and emoji display, and print-header label rendering.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 A new index hops into view,
Replacing the registry, fresh and true.
The codegen script writes with a flourish,
Eight consumers updated to nourish.
Tests confirm the names and emoji align—
Every state code in neat sorted line! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: extracting a lightweight state-index from the registry to optimize bundle size, with the specific benefit quantified (~26KB reduction).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/phase-3-registry-split

Comment @coderabbitai help to get the list of available commands and usage tips.

@Steel-tech
Steel-tech merged commit 0dd55d1 into main Jun 18, 2026
1 of 2 checks passed
@Steel-tech
Steel-tech deleted the feat/phase-3-registry-split branch June 18, 2026 07:08

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 350d2f1942

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

import type { StateCode } from "@/content/phases";
import type { WizardProgress } from "@/lib/types/wizard";
import { STATE_REGISTRY } from "@/content/state-registry";
import { STATE_INDEX } from "@/content/state-index";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop counting wizard pages as registry-free

In the wizard client bundle, this new index import does not remove the full registry: the same component imports getPhaseContent from @/content/phases, and content/phases.ts imports STATE_REGISTRY to generate phase content. Because app/wizard/[phase]/[step]/page.tsx renders this client component, the wizard flow still ships the registry despite this change; split the phase definitions/light metadata from the registry-backed generators before treating this route as lightweight.

Useful? React with 👍 / 👎.

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