Skip to content

feat: Polish — ship progress export (U11) - #3

Merged
Steel-tech merged 10 commits into
mainfrom
feat/polish-phase
Jun 18, 2026
Merged

feat: Polish — ship progress export (U11)#3
Steel-tech merged 10 commits into
mainfrom
feat/polish-phase

Conversation

@Steel-tech

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

Copy link
Copy Markdown
Owner

First slice of Phase 3 (Polish) of the maximization roadmap. Stacked on PR #2 (foundation) → auto-retargets down the chain as the parent PRs merge.

U11 — finish/cull half-built features

  • Progress export (was "coming soon"). The summary dashboard's disabled Export button now downloads the user's progress as JSON (complete structured record) or CSV (per-phase table). Pure builders in lib/export/progress-export.ts, fully tested (valid JSON, CSV header/rows, comma-escaping, empty-progress well-formed).
  • Removed the dead "d.c." state alias in the onboarding parser — DC isn't one of the 50 supported states, so it always fell through to not-found. Added the parser's first unit tests.

Verification

  • 59 tests across 10 files, all green · tsc --noEmit clean · lint clean.

Deferred (rest of Phase 3 — verification-gated, not done here)

  • U12 AI prompt caching — needs a live ANTHROPIC_API_KEY to verify cache hits (cache_read_input_tokens); shipping it blind would be unverified. Best done where the key is available.
  • U12 big-file refactors (split the 617-line wizard page, extract SSE parsing) — behavior-preserving but best verified by running the actual wizard (which drives the SSE via the chat API).
  • U13 performance/PWA — needs runtime Lighthouse/bundle measurement.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a legal disclaimer component displaying state regulatory data verification status.
    • Added progress export functionality allowing downloads in JSON and CSV formats.
  • Tests

    • Comprehensive test suites added across components and utilities.
  • Chores

    • Configured GitHub Actions CI workflow for automated testing and linting.
    • Set up Vitest unit testing and Playwright E2E testing frameworks.
    • Improved API middleware with enhanced CSRF protection for state-changing requests.

Steel-tech and others added 8 commits June 17, 2026 13:42
Set up the project's first test infrastructure: Vitest (jsdom + Testing
Library) for unit/component tests, Playwright for E2E, and a GitHub
Actions workflow gating PRs on lint + typecheck + test.

Replace the no-op ESLint config with eslint-config-next's native flat
core-web-vitals rules (no FlatCompat). Pre-existing react-hooks
violations are downgraded to warnings so the gate is green from day one,
to be burned down in the depth/polish pass. Seed one passing test per
layer (unit, component, e2e); e2e runs locally and is not in the CI gate.

Implements U2 of docs/plans/2026-06-17-001-feat-maximize-ironforge-plan.md

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

Move the inline rule-of-thumb figures (burden %, $/ton, profit, bonding,
etc.) into lib/estimator/constants.ts; clamp negative/NaN numeric inputs
at the calculation boundary so a bypassed HTML min hint can't poison a
bid; derive the bonding markup from PROFIT_PCT/CONTINGENCY_PCT so it
can't drift from the profit/contingency constants. Add 13 tests: golden
total, per-ton + compounding-order locks, monopolistic-WC and
prevailing-wage behavior, and clamping edge cases.

Implements U3 of docs/plans/2026-06-17-001-feat-maximize-ironforge-plan.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make data provenance a first-class field: StateVerification
(lastVerified / sourceUrl / effectiveDate) on StateData, backfilled for
the hand-crafted WA and OR states. getStateVerification() resolves a
well-formed view for every state — verified states carry their date,
machine-generated states report unverified rather than a misleading one.

A persistent, non-dismissable LegalDisclaimer renders on every wizard
step: "not legal advice — verify with your state agency", plus the
last-verified date for verified states or a "not independently verified"
notice for the machine-generated ones.

Implements U5 of docs/plans/2026-06-17-001-feat-maximize-ironforge-plan.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
getPhaseContent fell back to Washington's content for any state missing
from the registry — silently presenting one state's regulatory facts as
another's. Replace all six fallbacks with an explicit, labeled
"data unavailable for <state>" phase that points the user at their own
agency. Add a contract test asserting all 50 states x 7 phases resolve to
valid non-empty steps, that a non-WA state gets generated (not
Washington) content, that an unknown code gets the labeled phase (never
Washington), and that the chat-route step lookup finds a phase's steps.

Implements U4 of docs/plans/2026-06-17-001-feat-maximize-ironforge-plan.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The proxy enforced same-origin only on /api/chat. Generalize it to every
state-changing API request (non-GET/HEAD on /api/*), so /api/bid-review
and /api/onboarding are no longer open to cross-site calls. Move the full
security-header set (CSP, HSTS, frame/permissions policy) into
next.config.ts as the single source of truth — it covers static assets
the middleware matcher excludes — and drop the duplicate weaker set.
Narrow the proxy matcher to /api/*. Rate limiting stays in the route
handlers (in-memory, per-instance; a durable limiter is plan U10).

Implements U6 of docs/plans/2026-06-17-001-feat-maximize-ironforge-plan.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From the foundation code review (no P0/P1; these are the high-value P2/P3s):
- Clamp estimator inputs to MAX_FIELD_VALUE (1e9) so an absurd or tampered
  value (e.g. 1e307) can't overflow the bid to Infinity/NaN — the clamp's
  stated invariant was false for large finite values.
- Add the flagged missing tests: overflow finiteness; estimator cost-line
  flags in the "on" direction, non-union burden, and cost-per-sqft; wizard
  navigation (getNextStep/getPrevStep across phase boundaries); and the
  unavailable / legal-federal / unknown-phase content paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI's node 22 npm rejected the lockfile (Missing @swc/helpers@0.5.23) while
node 24 (local dev + lockfile generator) accepts it via `npm ci`. Align
CI to node 24 so the clean install matches the committed lockfile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the disabled "Export Progress — coming soon" button on the
summary dashboard with working JSON and CSV downloads (pure builders in
lib/export/progress-export.ts, tested). Remove the dead "d.c." alias in
the onboarding parser — DC isn't one of the 50 supported states, so it
always fell through to not-found — and add the parser's first tests.

Implements U11 of docs/plans/2026-06-17-001-feat-maximize-ironforge-plan.md

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

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 292f70ae-4cda-4cf4-9429-1528253f2493

📥 Commits

Reviewing files that changed from the base of the PR and between 8297806 and 6fe8e35.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (33)
  • .github/workflows/ci.yml
  • .gitignore
  • app/wizard/[phase]/[step]/page.tsx
  • app/wizard/summary/page.tsx
  • components/ui/badge.test.tsx
  • components/wizard/legal-disclaimer.test.tsx
  • components/wizard/legal-disclaimer.tsx
  • components/wizard/step-content.tsx
  • content/phases.test.ts
  • content/phases.ts
  • content/state-registry.test.ts
  • content/state-registry.ts
  • eslint.config.mjs
  • lib/ai/models.test.ts
  • lib/ai/onboarding-parser.test.ts
  • lib/ai/onboarding-parser.ts
  • lib/chat/stream.test.ts
  • lib/chat/stream.ts
  • lib/estimator/calculate.test.ts
  • lib/estimator/calculate.ts
  • lib/estimator/constants.ts
  • lib/export/progress-export.test.ts
  • lib/export/progress-export.ts
  • lib/types/content.ts
  • lib/utils.test.ts
  • next.config.ts
  • package.json
  • playwright.config.ts
  • proxy.test.ts
  • proxy.ts
  • tests/e2e/home.spec.ts
  • vitest.config.ts
  • vitest.setup.ts

📝 Walkthrough

Walkthrough

This PR bootstraps a full test infrastructure (Vitest, Playwright, CI), refactors the Next.js middleware to a uniform CSRF same-origin gate with CSP/HSTS headers, extracts a shared SSE stream utility consumed by the wizard chat flow, introduces a StateVerification-driven LegalDisclaimer component rendered in every wizard step, replaces WA fallback content with explicit unavailability placeholders, adds a progress export module (JSON/CSV), and centralizes estimator constants with numeric input clamping.

Changes

Phase 1 Foundation

Layer / File(s) Summary
Test tooling and CI setup
package.json, vitest.config.ts, vitest.setup.ts, playwright.config.ts, .github/workflows/ci.yml, eslint.config.mjs, .gitignore
Configures Vitest (jsdom, SWC, path aliases), Playwright E2E for Chromium, a GitHub Actions verify job running lint/typecheck/test, ESLint flat config with Next.js core-web-vitals rules and warn-level hook overrides, new dev dependencies, and test artifact gitignore entries.
CSRF middleware refactor and security headers
proxy.ts, proxy.test.ts, next.config.ts
Replaces the per-IP rate-limiter and route-specific CSRF check with a uniform isSameOrigin() gate returning 403 for non-GET/HEAD API requests with mismatched or missing Origin. Matcher narrows to /api/:path*. Adds HSTS and a dev-aware CSP to next.config.ts; removes Permissions-Policy.
Shared SSE stream utility and wizard integration
lib/chat/stream.ts, lib/chat/stream.test.ts, app/wizard/[phase]/[step]/page.tsx
Adds parseSseChunk and readChatStream for client-side SSE frame parsing with newline-boundary buffering. Wizard step page replaces its inline reader loop with readChatStream. Tests cover frame parsing, chunk-split reassembly, multi-frame chunks, and empty streams.
StateVerification type, registry lookup, and LegalDisclaimer component
lib/types/content.ts, content/state-registry.ts, content/state-registry.test.ts, components/wizard/legal-disclaimer.tsx, components/wizard/legal-disclaimer.test.tsx, components/wizard/step-content.tsx
Adds StateVerification interface, extends StateData, populates OR/WA registry entries, and exports getStateVerification(). New LegalDisclaimer client component renders verified/unverified messaging and is injected into StepContent via profile?.state.
Phase unavailability for missing state registry data
content/phases.ts, content/phases.test.ts
Adds unavailablePhase(phaseId, state) which builds a placeholder phase with a data-unavailable step and AI context, replacing the WA fallback across all six phase getters. Tests cover full state×phase contract, unknown-code behavior, legal-federal content, and getNextStep/getPrevStep navigation boundaries.
Progress export module and summary page integration
lib/export/progress-export.ts, lib/export/progress-export.test.ts, app/wizard/summary/page.tsx
Adds ProgressExportPhase, ProgressExport, toProgressJson, toProgressCsv, and downloadTextFile. Summary page builds a ProgressExport from computed summary data and exposes JSON/CSV download buttons. Tests validate JSON round-tripping, CSV structure, and RFC 4180 escaping.
Estimator constants extraction and numeric hardening
lib/estimator/constants.ts, lib/estimator/calculate.ts, lib/estimator/calculate.test.ts
Extracts ESTIMATE_CONSTANTS, PROJECT_TYPE_FACTOR, MONOPOLISTIC_WC_STATES, and MAX_FIELD_VALUE into a new constants module. calculateEstimate clamps inputs via clampNonNegative and replaces all embedded literals. Tests pin a golden totalBid, verify compounding order, and cover zero/NaN/overflow and optional cost-line flags.
Miscellaneous unit tests and parser fix
lib/ai/onboarding-parser.ts, lib/ai/onboarding-parser.test.ts, lib/ai/models.test.ts, lib/utils.test.ts, components/ui/badge.test.tsx, tests/e2e/home.spec.ts
Removes the "d.c." alias from parseState. Adds tests for parseState, MODELS (non-empty IDs, retired model regression), cn utility, Badge rendering, and a Playwright smoke test asserting the home page loads with HTTP status <400.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Browser (Wizard Step)
  participant proxy as proxy.ts Middleware
  participant ChatAPI as /api/chat
  participant readChatStream as readChatStream

  Browser->>proxy: POST /api/chat (Origin: same-host)
  proxy->>proxy: isSameOrigin(req) → true
  proxy->>ChatAPI: NextResponse.next()
  ChatAPI-->>Browser: 200 SSE stream (ReadableStream)
  Browser->>readChatStream: readChatStream(res.body, setStreamingContent)
  loop SSE frames
    readChatStream->>readChatStream: parseSseChunk → extract text delta
    readChatStream-->>Browser: onProgress(accumulated text)
  end
  readChatStream-->>Browser: final assembled message string
  Browser->>Browser: append assistant message to chat history
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Steel-tech/IronForge#4: Contains the same Phase 1 foundation changes including LegalDisclaimer, getStateVerification, unavailablePhase fallback logic, and estimator constants — directly overlapping code-level changes.

Poem

🐇 Hop hop, the tests now run with glee,
A CI pipeline watches over thee!
The CSRF gate stands firm and tall,
No cross-origin foes shall pass at all.
Progress exports flow as JSON streams,
And disclaimers guard the legal seams. ⚖️

✨ 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/polish-phase

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

@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: ed3beac39a

ℹ️ 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".

phasesComplete: summary.phasesComplete,
totalPhases: PHASE_DEFINITIONS.length,
startedAt: summary.startedAtIso,
phases: summary.phaseSummaries.map((p) => ({

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 Include step-level checklist state in JSON export

When a user downloads JSON to preserve or audit their wizard progress, this builder serializes summary.phaseSummaries rather than userState.progress, so the file only has per-phase counts and loses every visited step id and completedChecklist item id. Two different users can produce identical exports even though they checked different items, making the progress export unrecoverable/incomplete; include the step-level progress (or raw userState.progress) in the JSON payload.

Useful? React with 👍 / 👎.

Pull the ~25-line manual SSE parse loop out of the 617-line wizard step
page into lib/chat/stream.ts (parseSseChunk + readChatStream), unit-tested
in isolation: text frames, [DONE], malformed JSON, non-string text,
cumulative progress, empty stream. Behavior-preserving — the page now
calls readChatStream(res.body, setStreamingContent).

Part of U12 of docs/plans/2026-06-17-001-feat-maximize-ironforge-plan.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Steel-tech
Steel-tech changed the base branch from feat/foundation-hardening to main June 18, 2026 06:26
readChatStream fed each network read straight to parseSseChunk with no
carry-over, so a `data:` frame split mid-line across reads dropped the
delta silently (latent bug carried over from the pre-extraction inline
code). Buffer until a newline, parse complete lines only, retain the
trailing partial for the next read, and flush on stream end.

Adds the coverage the review flagged: split-frame reassembly,
multiple frames per chunk, no-trailing-newline flush, and CSV
quote/newline escaping + exact-row assertions for the export.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Steel-tech
Steel-tech merged commit fdac743 into main Jun 18, 2026
1 of 2 checks passed
@Steel-tech
Steel-tech deleted the feat/polish-phase branch June 18, 2026 06:32
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