Audited 2026-07-19. A sequenced, merge-ready plan. Each phase: goal → files → changes → verification. Phases are independently shippable; Phase 0 must land first.
Why first: docs/architecture are wrong. Fix the map before the journey.
Files: AGENTS.md, README.md, REDESIGN-PLAN.md, REMEDIATION_PLAN.md, docs/architecture.md
- Rewrite AGENTS.md "Design System" section: replace "Ethereal Glass / OLED black / sky
#007EFF" with the live "Field Manual" light theme (#FAFAF7bg,#FF6B35accent,FieldCard/FieldButton). - README Tech Stack: change
UIrow to "Tailwind v4 + Field Manual design system"; fixExportrow ("pdfkit" → "pdfkit + manual PDF builder" or remove pdfkit after Phase 3). REMEDIATION_PLAN.md: delete false claim thatdownloads/[id]/route.tsis 879 lines (it's 53).- Mark
REDESIGN-PLAN.mdas superseded (glass redesign was reverted) — or delete it.
| File | Reason |
|---|---|
src/lib/browser-llm-integration.ts (338 lines) |
Not imported anywhere; fake rule-based AI |
src/components/interview-lab/UpgradeModal.tsx |
Stub, no-op |
src/components/interview-lab/SubscriptionBanner.tsx |
Stub, no-op |
src/lib/pricing.ts (tier config) — optional |
Only powers stubs; keep if PricingPage ever real |
Verify: grep -r "browser-llm-integration\|UpgradeModal\|SubscriptionBanner" src → no results; bun run build passes.
Highest leverage: 4 routes share ~75-line duplicated JSON-extract loop, each calls the SDK directly.
export interface AIProvider {
complete(system: string, user: string, opts?: { schema?: ZodSchema; signal?: AbortSignal }): Promise<unknown>;
}
export class ZAIProvider implements AIProvider { /* timeout 30s, abort, retry-once */ }
export const ai: AIProvider = new ZAIProvider();- Add
AbortSignal.timeout(30000)+try/catcharoundZAI.create().chat.completions.create.
export function createAIHandler<T>(buildPrompt, schema: ZodSchema<T>) {
return async (req) => { /* auth → call ai.complete with schema → validate → return */ };
}- Centralize
extractJsonArray/extractJsonObjectparsing into tested utilsrc/lib/ai/json.ts.
src/lib/ai/coach.ts,resume.ts,cover-letter.ts,assessment.ts— each exportsbuildPrompt()+ a zodschema.- Refactor
src/app/api/ai/{coach,resume-review,cover-letter,assessment-score}/route.tsto ~15 lines each callingcreateAIHandler.
src/lib/ai/schemas.ts:CoachFeedbackSchema,ResumeReviewSchema,CoverLetterSchema,AssessmentScoreSchema(replaces runtimeascasts).
Verify: __tests__/ai/client.test.ts (mock z-ai-web-dev-sdk), handlers.test.ts (valid + malformed JSON → 500), bun run test.
- Move
generateDocxout ofexport/route.tsinto its own module.
- Use already-installed
pdfkit(currently unused). Fixes silent truncation (if (y < 50) break) by paginating. - Add
contentsize guard (reject > 50k chars) to prevent abuse.
- Becomes ~12 lines: auth → dispatch to
docx.ts/pdf.ts.
Verify: export.test.ts generates real .docx/.pdf, asserts multi-page content isn't truncated.
export interface EntitlementService { canAccess(feature): boolean; tier: string; }
export class FreeEntitlement implements EntitlementService { canAccess() { return true; } }subscription-guard.tsreturns real interface;use-subscription.tsreturnsFreeEntitlementhonestly (remove fake-1/no-op that implies gating).
Verify: subscription.test.ts.
src/components/interview-lab/mock-interview/{Setup,ActiveSession,Complete,useInterview}.tsx
ResumeLabraw red<div>→Alertcomponent (already built inui/).- Add skeletons to
MockInterview/ResumeLabto matchDashboardView.
QuestionBank: remove nested interactive (inner "Practice" button insiderole="button"div) → make card a real<article>with a separate button.
Verify: bun run lint, component render tests.
- Replace in-memory
Mapwith Upstash Redis (@upstash/ratelimit) so it works across Vercel serverless. - Trust
x-forwarded-foronly behind Vercel proxy; addx-vercel-ipfallback.
JetBrains Mono/Interdeclared but unused → align to Space Grotesk + Plus Jakarta Sans (per spec) or update spec.
- Gate
log: ['query']behind explicitLOG_QUERIES=1env (confirm off in prod).
| Phase | Work | Est. | Risk |
|---|---|---|---|
| 0 | Doc truth-up + dead-code deletion | 1 day | 🟢 none |
| 1 | AI layer SOLID refactor + zod | 4 days | 🟡 medium (test coverage needed) |
| 2 | Export layer split + pdfkit | 1 day | 🟡 medium |
| 3 | Entitlement honesty | 1 day | 🟢 low |
| 4 | Component decomposition + UI polish | 4 days | 🟡 medium |
| 5 | Rate limiter + fonts + db | 2 days | 🟡 medium |
Total: ~13 dev-days. Phases are independently shippable; Phase 0 must land first.
"Phase 0: Reconcile docs & remove dead code" — smallest diff, zero behavioral risk, unblocks everything.