Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/compare/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { IBeamIcon } from "@/components/ui/ibeam-icon";
import { StateSelector } from "@/components/compare/state-selector";
import { ComparisonTable } from "@/components/compare/comparison-table";
import { ComparisonSummary } from "@/components/compare/comparison-summary";
import { STATE_REGISTRY } from "@/content/state-registry";
import { STATE_INDEX } from "@/content/state-index";

const LS_KEY = "ironforge_compare_states";

Expand All @@ -23,7 +23,7 @@ function loadSelected(): string[] {
if (!Array.isArray(parsed)) return [];
return parsed
.filter((c): c is string => typeof c === "string" && /^[A-Z]{2}$/.test(c))
.filter((c) => !!STATE_REGISTRY[c])
.filter((c) => !!STATE_INDEX[c])
.slice(0, 3);
} catch {
return [];
Expand Down Expand Up @@ -113,7 +113,7 @@ export default function ComparePage() {
<p className="text-xs">
Comparing:{" "}
{selected
.map((c) => STATE_REGISTRY[c]?.name ?? c)
.map((c) => STATE_INDEX[c]?.name ?? c)
.join(" vs. ")}
</p>
)}
Expand Down
4 changes: 2 additions & 2 deletions app/wizard/summary/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { PHASE_DEFINITIONS, getPhaseContent } from "@/content/phases";
import type { StateCode } from "@/content/phases";
import type { UserState } from "@/lib/types/wizard";
import { DEFAULT_STATE } from "@/lib/types/wizard";
import { STATE_REGISTRY } from "@/content/state-registry";
import { STATE_INDEX } from "@/content/state-index";
import {
toProgressJson,
toProgressCsv,
Expand Down Expand Up @@ -177,7 +177,7 @@ export default function SummaryPage() {
if (!loaded || !userState.profile.state || !summary) return null;

const stateCode = userState.profile.state as StateCode;
const stateData = STATE_REGISTRY[stateCode];
const stateData = STATE_INDEX[stateCode];

const buildExport = (): ProgressExport => ({
generatedAt: new Date().toISOString(),
Expand Down
4 changes: 2 additions & 2 deletions components/capability/cap-statement-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
SUGGESTED_NAICS,
emptyPastProject,
} from "@/lib/types/cap-statement";
import { STATE_REGISTRY } from "@/content/state-registry";
import { STATE_INDEX } from "@/content/state-index";
import { Plus, Trash2, Upload, X } from "lucide-react";

interface Props {
Expand Down Expand Up @@ -74,7 +74,7 @@ export function CapStatementForm({ data, onChange }: Props) {
reader.readAsDataURL(file);
}

const states = Object.values(STATE_REGISTRY).sort((a, b) =>
const states = Object.values(STATE_INDEX).sort((a, b) =>
a.name.localeCompare(b.name),
);

Expand Down
6 changes: 3 additions & 3 deletions components/compare/state-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
// Copyright (C) 2026 Steel-Tech / StructuPath
import { useMemo, useState } from "react";
import { ChevronDown, Plus, X, Search } from "lucide-react";
import { STATE_REGISTRY } from "@/content/state-registry";
import { STATE_INDEX } from "@/content/state-index";

const ALL_STATES = Object.values(STATE_REGISTRY).sort((a, b) =>
const ALL_STATES = Object.values(STATE_INDEX).sort((a, b) =>
a.name.localeCompare(b.name),
);

Expand Down Expand Up @@ -77,7 +77,7 @@ export function StateSelector({

<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
{slots.map((code, idx) => {
const state = code ? STATE_REGISTRY[code] : null;
const state = code ? STATE_INDEX[code] : null;
const isOpen = openSlot === idx;
const isAddSlot = !state;
const disabled = isAddSlot && idx > selected.length;
Expand Down
4 changes: 2 additions & 2 deletions components/estimator/estimate-results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// Copyright (C) 2026 Steel-Tech / StructuPath
import { formatCurrency, formatCurrencyPrecise } from "@/lib/estimator/calculate";
import type { EstimateInput, EstimateResult } from "@/lib/types/estimator";
import { STATE_REGISTRY } from "@/content/state-registry";
import { STATE_INDEX } from "@/content/state-index";

interface Props {
input: EstimateInput;
Expand All @@ -14,7 +14,7 @@
export function EstimateResults({ input, result }: Props) {
const { labor, materials, equipment, overhead, summary } = result;
const stateName = input.state
? STATE_REGISTRY[input.state]?.name || input.state
? STATE_INDEX[input.state]?.name || input.state
: "—";

// Slice percentages for the stacked bar.
Expand All @@ -33,7 +33,7 @@

<div className="relative flex flex-col md:flex-row md:items-end justify-between gap-6">
<div>
<div className="text-[10px] font-mono text-text-muted tracking-[0.3em] uppercase mb-2">

Check warning on line 36 in components/estimator/estimate-results.tsx

View workflow job for this annotation

GitHub Actions / verify

Comments inside children section of tag should be placed inside braces
// Total Bid Estimate
</div>
<div className="font-mono font-bold text-text-primary tracking-tight">
Expand Down
4 changes: 2 additions & 2 deletions components/gc-directory/gc-search-filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
type ProjectType,
type GcSize,
} from "@/content/gc-directory";
import { STATE_REGISTRY } from "@/content/state-registry";
import { STATE_INDEX } from "@/content/state-index";

export interface GcFilters {
search: string;
Expand All @@ -33,7 +33,7 @@ interface GcSearchFiltersProps {
resultCount: number;
}

const STATES = Object.values(STATE_REGISTRY).sort((a, b) =>
const STATES = Object.values(STATE_INDEX).sort((a, b) =>
a.name.localeCompare(b.name),
);

Expand Down
10 changes: 5 additions & 5 deletions components/onboarding/onboarding-chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { MarkdownRenderer } from "@/components/ui/markdown-renderer";
import { MatrixRain } from "@/components/ui/matrix-rain";
import { TronGrid } from "@/components/ui/tron-grid";
import { DecryptedText } from "@/components/ui/decrypted-text";
import { STATE_REGISTRY } from "@/content/state-registry";
import { STATE_INDEX } from "@/content/state-index";
import { saveUserState } from "@/lib/store/user-profile";
import type { UserProfile } from "@/lib/types/wizard";
import { DEFAULT_PROFILE, DEFAULT_STATE } from "@/lib/types/wizard";
Expand Down Expand Up @@ -201,8 +201,8 @@ export function OnboardingChat({ onExit }: OnboardingChatProps) {
*/
function fallbackQuestionFor(nextStage: Stage, updated: UserProfile): string {
const stateName =
updated.state && STATE_REGISTRY[updated.state]
? STATE_REGISTRY[updated.state].name
updated.state && STATE_INDEX[updated.state]
? STATE_INDEX[updated.state].name
: "";
switch (nextStage) {
case "experience":
Expand All @@ -224,8 +224,8 @@ export function OnboardingChat({ onExit }: OnboardingChatProps) {

function buildSummary(p: UserProfile): string {
const stateName =
p.state && STATE_REGISTRY[p.state]
? STATE_REGISTRY[p.state].name
p.state && STATE_INDEX[p.state]
? STATE_INDEX[p.state].name
: "—";
const lines = [
"**Here's your profile:**",
Expand Down
6 changes: 3 additions & 3 deletions components/wizard/progress-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { PHASE_DEFINITIONS, getPhaseContent } from "@/content/phases";
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 👍 / 👎.

import Link from "next/link";
import { ChevronLeft, ChevronRight, Check, Circle, GitCompare } from "lucide-react";
import { IBeamIcon } from "@/components/ui/ibeam-icon";
Expand Down Expand Up @@ -177,8 +177,8 @@ export function ProgressSidebar({
</Link>
<div className="flex items-center justify-between text-[10px] font-mono text-text-muted">
<span>
{STATE_REGISTRY[userState]
? `${STATE_REGISTRY[userState].emoji} ${STATE_REGISTRY[userState].name.toUpperCase()}`
{STATE_INDEX[userState]
? `${STATE_INDEX[userState].emoji} ${STATE_INDEX[userState].name.toUpperCase()}`
: userState}
</span>
<Link
Expand Down
32 changes: 32 additions & 0 deletions content/state-index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Steel-Tech / StructuPath
import { describe, it, expect } from "vitest";
import { STATE_REGISTRY } from "@/content/state-registry";
import { STATE_INDEX, STATE_LIST } from "@/content/state-index";

// state-index.ts is GENERATED from the registry (scripts/gen-state-index.ts).
// These tests fail if it drifts — e.g. a state added to the registry without
// regenerating the index — so the lightweight client copy can't go stale.
describe("state-index sync with state-registry", () => {
it("covers exactly the same state codes", () => {
expect(Object.keys(STATE_INDEX).sort()).toEqual(
Object.keys(STATE_REGISTRY).sort(),
);
});

it("matches code/name/emoji for every state", () => {
for (const [code, data] of Object.entries(STATE_REGISTRY)) {
expect(STATE_INDEX[code]).toEqual({
code: data.code,
name: data.name,
emoji: data.emoji,
});
}
});

it("STATE_LIST holds every state, sorted by name", () => {
expect(STATE_LIST).toHaveLength(Object.keys(STATE_REGISTRY).length);
const names = STATE_LIST.map((s) => s.name);
expect(names).toEqual([...names].sort((a, b) => a.localeCompare(b)));
});
});
73 changes: 73 additions & 0 deletions content/state-index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Steel-Tech / StructuPath
//
// GENERATED from content/state-registry.ts — do not edit by hand.
// A lightweight {code, name, emoji} index for dropdowns and name/emoji display.
// Components that don't need full StateData import this instead of the registry,
// keeping the ~26KB registry data out of their client bundles.
// Regenerate: npx tsx scripts/gen-state-index.ts
// content/state-index.test.ts asserts this stays in sync with the registry.

export interface StateSummary {
code: string;
name: string;
emoji: string;
}

export const STATE_INDEX: Record<string, StateSummary> = {
AK: { code: "AK", name: "Alaska", emoji: "🐻" },
AL: { code: "AL", name: "Alabama", emoji: "🏈" },
AR: { code: "AR", name: "Arkansas", emoji: "💎" },
AZ: { code: "AZ", name: "Arizona", emoji: "🌵" },
CA: { code: "CA", name: "California", emoji: "🌴" },
CO: { code: "CO", name: "Colorado", emoji: "⛰️" },
CT: { code: "CT", name: "Connecticut", emoji: "🏛️" },
DE: { code: "DE", name: "Delaware", emoji: "🦀" },
FL: { code: "FL", name: "Florida", emoji: "🌞" },
GA: { code: "GA", name: "Georgia", emoji: "🍑" },
HI: { code: "HI", name: "Hawaii", emoji: "🌺" },
IA: { code: "IA", name: "Iowa", emoji: "🌽" },
ID: { code: "ID", name: "Idaho", emoji: "🥔" },
IL: { code: "IL", name: "Illinois", emoji: "🏙️" },
IN: { code: "IN", name: "Indiana", emoji: "🏎️" },
KS: { code: "KS", name: "Kansas", emoji: "🌻" },
KY: { code: "KY", name: "Kentucky", emoji: "🐴" },
LA: { code: "LA", name: "Louisiana", emoji: "⚜️" },
MA: { code: "MA", name: "Massachusetts", emoji: "🎓" },
MD: { code: "MD", name: "Maryland", emoji: "🦀" },
ME: { code: "ME", name: "Maine", emoji: "🦞" },
MI: { code: "MI", name: "Michigan", emoji: "🏗️" },
MN: { code: "MN", name: "Minnesota", emoji: "❄️" },
MO: { code: "MO", name: "Missouri", emoji: "🏛️" },
MS: { code: "MS", name: "Mississippi", emoji: "🎸" },
MT: { code: "MT", name: "Montana", emoji: "🦌" },
NC: { code: "NC", name: "North Carolina", emoji: "🏔️" },
ND: { code: "ND", name: "North Dakota", emoji: "🦬" },
NE: { code: "NE", name: "Nebraska", emoji: "🌾" },
NH: { code: "NH", name: "New Hampshire", emoji: "🏔️" },
NJ: { code: "NJ", name: "New Jersey", emoji: "🏖️" },
NM: { code: "NM", name: "New Mexico", emoji: "🌶️" },
NV: { code: "NV", name: "Nevada", emoji: "🎰" },
NY: { code: "NY", name: "New York", emoji: "🗽" },
OH: { code: "OH", name: "Ohio", emoji: "🏈" },
OK: { code: "OK", name: "Oklahoma", emoji: "🌪️" },
OR: { code: "OR", name: "Oregon", emoji: "🌲" },
PA: { code: "PA", name: "Pennsylvania", emoji: "🔔" },
RI: { code: "RI", name: "Rhode Island", emoji: "⛵" },
SC: { code: "SC", name: "South Carolina", emoji: "🌴" },
SD: { code: "SD", name: "South Dakota", emoji: "🏔️" },
TN: { code: "TN", name: "Tennessee", emoji: "🎸" },
TX: { code: "TX", name: "Texas", emoji: "🤠" },
UT: { code: "UT", name: "Utah", emoji: "🏜️" },
VA: { code: "VA", name: "Virginia", emoji: "🏛️" },
VT: { code: "VT", name: "Vermont", emoji: "🍁" },
WA: { code: "WA", name: "Washington", emoji: "🌲" },
WI: { code: "WI", name: "Wisconsin", emoji: "🧀" },
WV: { code: "WV", name: "West Virginia", emoji: "⛏️" },
WY: { code: "WY", name: "Wyoming", emoji: "🤠" },
};

/** All states, sorted by name — for dropdowns. */
export const STATE_LIST: StateSummary[] = Object.values(STATE_INDEX).sort(
(a, b) => a.name.localeCompare(b.name),
);
48 changes: 48 additions & 0 deletions scripts/gen-state-index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Steel-Tech / StructuPath
//
// Generates content/state-index.ts — a standalone lightweight {code, name,
// emoji} index — from the full STATE_REGISTRY. Run: npx tsx scripts/gen-state-index.ts
import { writeFileSync } from "node:fs";
import { STATE_REGISTRY } from "../content/state-registry";

const entries = Object.values(STATE_REGISTRY)
.sort((a, b) => a.code.localeCompare(b.code))
.map(
(s) =>
` ${s.code}: { code: ${JSON.stringify(s.code)}, name: ${JSON.stringify(
s.name,
)}, emoji: ${JSON.stringify(s.emoji)} },`,
)
.join("\n");

const out = `// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Steel-Tech / StructuPath
//
// GENERATED from content/state-registry.ts — do not edit by hand.
// A lightweight {code, name, emoji} index for dropdowns and name/emoji display.
// Components that don't need full StateData import this instead of the registry,
// keeping the ~26KB registry data out of their client bundles.
// Regenerate: npx tsx scripts/gen-state-index.ts
// content/state-index.test.ts asserts this stays in sync with the registry.

export interface StateSummary {
code: string;
name: string;
emoji: string;
}

export const STATE_INDEX: Record<string, StateSummary> = {
${entries}
};

/** All states, sorted by name — for dropdowns. */
export const STATE_LIST: StateSummary[] = Object.values(STATE_INDEX).sort(
(a, b) => a.name.localeCompare(b.name),
);
`;

writeFileSync(new URL("../content/state-index.ts", import.meta.url), out);
console.log(
`Wrote content/state-index.ts with ${Object.keys(STATE_REGISTRY).length} states`,
);
Loading