Skip to content

Phase 2: split export layer + fix PDF truncation#8

Merged
projectamazonph merged 3 commits into
mainfrom
fix/phase2-export-refactor
Jul 18, 2026
Merged

Phase 2: split export layer + fix PDF truncation#8
projectamazonph merged 3 commits into
mainfrom
fix/phase2-export-refactor

Conversation

@projectamazonph

@projectamazonph projectamazonph commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 2 of the SOLID remediation roadmap (FIX-PLAN.md): refactor the export layer. The export/route.ts was a 200-line monolith that owned auth, DOCX generation, and a hand-rolled PDF builder that silently truncated content at the bottom of the page (if (y < 50) break — data loss on long resumes/letters).

What changed

  • src/lib/export/types.ts — shared ExportRequest, MAX_CONTENT_LENGTH (50k), filename sanitizer.
  • src/lib/export/docx.ts — DOCX builder extracted (SRP).
  • src/lib/export/pdf.ts — PDF builder extracted and paginated. Long documents now flow onto new pages instead of being cut off — fixes the truncation bug (verified: a 1000-line doc spans multiple pages with no lost lines).
  • src/app/api/export/route.ts — shrunk to ~50 lines: auth, validation, type dispatch, and a content-length guard only.

Kept the manual Courier PDF approach (no external font files) to avoid the pdfkit font-resolution issues the original code warned about — so no new dependency was added (per AGENTS.md guardrails). The pdfkit dependency remains present but still unused; removing it is a separate cleanup.

Tests / verification

  • Added 11 unit tests (export-pdf.test.ts: pagination + no-truncation + escaping; export-docx.test.ts: valid DOCX zip output; types helpers) — all passing.
  • tsc --noEmit: clean. eslint: clean.

🤖 Generated with opencode

Summary by CodeRabbit

  • New Features

    • Added consistent AI-powered workflows for interview coaching, assessment scoring, resume reviews, and cover-letter generation.
    • Added DOCX and PDF downloads with improved formatting, pagination, and safe filenames.
    • Added safeguards for invalid or oversized requests.
  • Documentation

    • Updated project setup, database, environment variables, testing instructions, and design-system documentation.
    • Added a phased remediation roadmap and clarified the current light “Field Manual” design.
  • Bug Fixes

    • Removed the deprecated browser-based AI fallback and outdated subscription references.

Ryan Roland Dabao added 3 commits July 19, 2026 01:18
- Rewrite AGENTS.md/README design system to the live 'Field Manual' light theme
  (the 'Ethereal Glass' dark design was reverted)
- Correct tech stack: PostgreSQL-only (no SQLite), Bun/CI-npm, pdfkit note
- Fix REMEDIATION_PLAN false claims (download route is 53 lines, not 879; SQLite)
- Mark REDESIGN-PLAN.md as superseded
- Delete truly-dead code (zero references):
  browser-llm-integration.ts (338-line unused fake rule-based AI)
- NOTE: UpgradeModal/SubscriptionBanner/pricing.ts are compile-time shims still
  imported by components; their removal is deferred to Phase 3/4 (entitlement +
  component refactor), not Phase 0.
- Add FIX-PLAN.md tracking the full SOLID/UI remediation roadmap
Extract shared AI logic out of the 4 route handlers into a single tested
abstraction, fixing SRP/OCP/DIP violations:

- src/lib/ai/json.ts: central JSON extraction from free-text LLM responses
- src/lib/ai/client.ts: AIProvider interface + ZAIProvider with 30s timeout
  and abort support (swap provider without touching routes — DIP)
- src/lib/ai/handlers.ts: createAIHandler factory centralizes auth, input
  validation, model call, parsing, and error handling (OCP: add a feature
  by adding a config, not copying a route)
- src/lib/ai/{coach,resume,cover-letter,assessment}.ts: per-feature prompt
  + validation + fallback configs (SRP)

Routes shrink to single-line exports. Behavior is preserved exactly:
coach/cover-letter return graceful fallbacks on parse failure; resume/
assessment return 500. No new dependencies (validateShape replaces zod).

Tests: add 17 unit tests for json.ts + handlers.ts (all passing).
tsc --noEmit and eslint clean.
The export route was a 200-line monolith that owned auth, DOCX generation, and
a hand-rolled PDF builder that SILENTLY TRUNCATED content at the bottom of the
page (if (y < 50) break).

Changes:
- src/lib/export/types.ts: shared ExportRequest + MAX_CONTENT_LENGTH (50k) + filename sanitizer
- src/lib/export/docx.ts: DOCX builder extracted (SRP)
- src/lib/export/pdf.ts: PDF builder extracted AND paginated. Long documents now
  flow onto new pages instead of being cut off — fixes the data-loss bug.
- src/app/api/export/route.ts: shrunk to ~50 lines — auth, validation, type
  dispatch, and a content-length guard only.

Kept the manual Courier PDF approach (no external fonts) to avoid the pdfkit
font-resolution issues noted in the original code, rather than adding a new
dependency.

Tests: add 11 unit tests (pdf pagination/no-truncation, docx validity, helpers).
tsc --noEmit and eslint clean.
@vercel

vercel Bot commented Jul 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
interview-lab Ready Ready Preview, Comment Jul 18, 2026 5:42pm

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR updates project documentation, removes the browser LLM implementation, centralizes AI route handling, adds AI configurations and JSON parsing, extracts DOCX/PDF generation, adds export limits, and introduces unit tests for AI and export behavior.

Changes

AI and export refactor

Layer / File(s) Summary
Documentation and remediation plans
AGENTS.md, README.md, FIX-PLAN.md, REDESIGN-PLAN.md, REMEDIATION_PLAN.md
Documentation now describes the Field Manual light theme, PostgreSQL setup, current scripts and environment variables, revised test coverage, and sequenced remediation work.
Shared AI handling and route integration
src/lib/ai/*, src/app/api/ai/*, __tests__/unit/ai-*.test.ts
AI provider abstractions, timeout handling, JSON extraction, reusable request handling, endpoint-specific configurations, delegated routes, and handler/parser tests are added.
Document export generation and API flow
src/lib/export/*, src/app/api/export/route.ts, __tests__/unit/export-*.test.ts
DOCX/PDF generation and export constraints are centralized, the API delegates to those generators, and tests cover output validity, pagination, escaping, filenames, and content limits.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AI Route
  participant createAIHandler
  participant AI Provider
  Client->>AI Route: POST request
  AI Route->>createAIHandler: delegate request
  createAIHandler->>AI Provider: submit prompts
  AI Provider-->>createAIHandler: return model response
  createAIHandler-->>Client: return JSON result or error
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers summary and tests, but it omits the required Problem, Scope, Acceptance criteria, Risk and rollback, and Documentation sections. Rewrite the PR description to match the template and add explicit Problem, Solution, Scope, Acceptance criteria, Validation results, Risk/rollback, and Documentation sections.
Docstring Coverage ⚠️ Warning Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the export-layer split and PDF truncation fix.
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.
✨ 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 fix/phase2-export-refactor

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 17

🧹 Nitpick comments (3)
__tests__/unit/export-docx.test.ts (1)

1-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Mirror the source layout for export tests.

  • __tests__/unit/export-docx.test.ts#L1-L22: move to the test path corresponding to src/lib/export/docx.ts.
  • __tests__/unit/export-pdf.test.ts#L1-L64: move to the test path corresponding to src/lib/export/pdf.ts.

As per coding guidelines, tests must be “under __tests__/ mirroring the src/ structure.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@__tests__/unit/export-docx.test.ts` around lines 1 - 22, Move the tests from
__tests__/unit/export-docx.test.ts (lines 1-22) to the __tests__/ path mirroring
src/lib/export/docx.ts, preserving the generateDocx test suite unchanged. Also
move __tests__/unit/export-pdf.test.ts (lines 1-64) to the corresponding
mirrored path for src/lib/export/pdf.ts, without changing its test behavior.

Source: Coding guidelines

__tests__/unit/ai-handlers.test.ts (1)

1-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Mirror the source path under __tests__/.

Move this to __tests__/lib/ai/handlers.test.ts so its location mirrors src/lib/ai/handlers.ts.

As per coding guidelines, "Use Vitest for unit, API, component, and stress tests, with tests under __tests__/ mirroring the src/ structure."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@__tests__/unit/ai-handlers.test.ts` around lines 1 - 115, Move the test file
from the top-level __tests__ location into __tests__/lib/ai/handlers.test.ts so
it mirrors the source module path src/lib/ai/handlers.ts. Preserve all existing
test contents and behavior.

Source: Coding guidelines

__tests__/unit/ai-json.test.ts (1)

1-2: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move this test to mirror the source structure.

Place it at __tests__/lib/ai/json.test.ts rather than __tests__/unit/ai-json.test.ts.

As per coding guidelines, tests under __tests__/ must mirror the src/ structure. <coding_guidelines>

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@__tests__/unit/ai-json.test.ts` around lines 1 - 2, Move the test file from
the unit-level location to __tests__/lib/ai/json.test.ts so it mirrors the
source module imported by extractJson. Preserve the existing test contents and
behavior without other changes.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@__tests__/unit/ai-json.test.ts`:
- Around line 13-26: Add a regression test in the extractJson test suite for
text containing multiple JSON objects, asserting that extractJson returns the
first JSON value rather than null or combining later segments. Use the existing
extraction test style and preserve current expectations for prose, fenced JSON,
and embedded arrays.

In `@AGENTS.md`:
- Line 27: Update AGENTS.md:27 and README.md:47 to document the shipped custom
paginated PDF builder in src/lib/export/pdf.ts instead of describing pdfkit as
the implementation; keep the PDF export descriptions consistent across both
files. Leave FIX-PLAN.md:66-68 unchanged as future-work planning for a possible
pdfkit migration.

In `@FIX-PLAN.md`:
- Around line 19-24: Insert one blank line between the preceding paragraph and
the table in FIX-PLAN.md, leaving the table content unchanged.
- Line 3: Remove or defer the future-dated July 19, 2026 claims: update the
audit date in FIX-PLAN.md line 3 and the README-correction date in
REMEDIATION_PLAN.md line 115 to a current or non-future date, preserving the
surrounding plan content.

In `@README.md`:
- Around line 168-177: Update the documentation index link label for
REDESIGN-PLAN.md to identify it as a superseded historical redesign plan, while
preserving the existing link target and surrounding documentation.
- Around line 152-153: Update the README test inventory to remove the stale
subscription.test.ts entry and change the project-tree API test count from 218
to 187, keeping the detailed table total and all other test entries consistent.
- Around line 40-47: Align the package-manager guidance in README.md and
AGENTS.md with the repository’s npm-based CI by making npm the documented source
of truth. Update any contributor commands that reference Bun and ensure the
committed lockfile and documentation consistently use npm without introducing a
competing package-manager lockfile.

In `@src/app/api/export/route.ts`:
- Around line 14-27: Update the request validation around the destructuring in
the export route to require that content is a string and, when provided, title
is also a string before checking length or calling sanitizeFilename. Return the
existing 400 validation response for invalid types, while preserving valid type
and content handling.

In `@src/lib/ai/assessment.ts`:
- Around line 39-52: Update the validate callback around validateShape to verify
assessmentTitle is a non-empty string and userAnswers is a valid non-empty value
of the expected runtime type before casting to AssessmentBody. Return the
existing 400 required-fields error for invalid or missing values, while
preserving the current length check and successful return for valid requests.
- Around line 3-20: Add a server-owned, user-visible truthfulness disclaimer
field to the JSON response contract in ASSESSMENT_PROMPT, alongside the existing
assessment feedback fields, and instruct the model to populate it consistently.
Keep the “Never guarantee” instruction, but do not rely on generated prose
alone; ensure the assessment response schema explicitly includes the stable
warning required for AI-generated coaching content.
- Around line 54-55: Update buildUserPrompt to serialize non-string
body.userAnswers with JSON.stringify before interpolation, while preserving
string answers unchanged and retaining the existing prompt formatting and length
behavior.

In `@src/lib/ai/coach.ts`:
- Around line 19-34: Update the coaching response JSON contract, fallback
responses, and normalization logic in coach.ts to include a truthWarnings field
containing actionable warnings for potentially unsupported claims. Ensure the
field is always present with a consistent collection shape, including fallback
and normalized responses, so clients can surface warnings for AI-generated
resume and coaching content.

In `@src/lib/ai/json.ts`:
- Around line 16-29: The JSON extraction logic in the parsing function should
replace greedy objectMatch and arrayMatch regexes with balanced-delimiter
scanning that respects quoted strings and escaped characters. Return the first
valid balanced object or array payload, including nested structures, and add
regression coverage for multiple adjacent/following payloads and nested
payloads.

In `@src/lib/ai/resume.ts`:
- Around line 42-51: Update the validators in src/lib/ai/resume.ts lines 42-51,
src/lib/ai/coach.ts lines 98-103, and src/lib/ai/cover-letter.ts lines 47-56 to
require each request field to be a trimmed, non-empty string before casting or
applying length checks: resumeText, question, userAnswer, and jobDescription
respectively. Return the existing 400 validation response for invalid values,
and preserve the current length-limit behavior for valid strings.

In `@src/lib/export/pdf.ts`:
- Around line 23-39: Update the line-processing logic in the PDF export routine
to wrap every styled text line to the available page width using Courier
character measurements before vertical pagination and Tj generation. Preserve
each line’s existing text styling, indentation, and blank-line handling across
wrapped segments, including the corresponding logic around the additional
pagination range.
- Around line 82-111: Correct object allocation in the PDF export flow around
addObject so every /Pages, /Page, /Contents, and font reference uses the actual
assigned object IDs rather than hard-coded values. Reserve IDs or reorder
emission so the catalog, pages tree, page objects, content streams, and fonts
remain consistent, then add a parser-based regression test covering at least a
one-page export and validating all cross-references.
- Around line 35-36: Update the PDF stream generation around allLines and the
final document serialization to encode the stream once, use the resulting byte
array’s exact length for /Length, and write those same bytes without a later
Latin-1 conversion. Replace the current font/encoding choice with one that
preserves the supported Unicode input, including the bullet character, while
keeping text rendering unchanged.

---

Nitpick comments:
In `@__tests__/unit/ai-handlers.test.ts`:
- Around line 1-115: Move the test file from the top-level __tests__ location
into __tests__/lib/ai/handlers.test.ts so it mirrors the source module path
src/lib/ai/handlers.ts. Preserve all existing test contents and behavior.

In `@__tests__/unit/ai-json.test.ts`:
- Around line 1-2: Move the test file from the unit-level location to
__tests__/lib/ai/json.test.ts so it mirrors the source module imported by
extractJson. Preserve the existing test contents and behavior without other
changes.

In `@__tests__/unit/export-docx.test.ts`:
- Around line 1-22: Move the tests from __tests__/unit/export-docx.test.ts
(lines 1-22) to the __tests__/ path mirroring src/lib/export/docx.ts, preserving
the generateDocx test suite unchanged. Also move
__tests__/unit/export-pdf.test.ts (lines 1-64) to the corresponding mirrored
path for src/lib/export/pdf.ts, without changing its test behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1a4e5ad0-3564-4fd0-8126-b18ee568ad4a

📥 Commits

Reviewing files that changed from the base of the PR and between e4a6078 and 809ddbc.

📒 Files selected for processing (25)
  • AGENTS.md
  • FIX-PLAN.md
  • README.md
  • REDESIGN-PLAN.md
  • REMEDIATION_PLAN.md
  • __tests__/unit/ai-handlers.test.ts
  • __tests__/unit/ai-json.test.ts
  • __tests__/unit/export-docx.test.ts
  • __tests__/unit/export-pdf.test.ts
  • src/app/api/ai/assessment-score/route.ts
  • src/app/api/ai/coach/route.ts
  • src/app/api/ai/cover-letter/route.ts
  • src/app/api/ai/resume-review/route.ts
  • src/app/api/export/route.ts
  • src/lib/ai/assessment.ts
  • src/lib/ai/client.ts
  • src/lib/ai/coach.ts
  • src/lib/ai/cover-letter.ts
  • src/lib/ai/handlers.ts
  • src/lib/ai/json.ts
  • src/lib/ai/resume.ts
  • src/lib/browser-llm-integration.ts
  • src/lib/export/docx.ts
  • src/lib/export/pdf.ts
  • src/lib/export/types.ts
💤 Files with no reviewable changes (1)
  • src/lib/browser-llm-integration.ts

Comment on lines +13 to +26
it('extracts JSON from prose', () => {
const text = 'Here is your result:\n{"score": 8, "ok": true}\nHope that helps.';
expect(extractJson(text)).toEqual({ score: 8, ok: true });
});

it('extracts JSON wrapped in code fences', () => {
const text = '```json\n{"a":1}\n```';
expect(extractJson(text)).toEqual({ a: 1 });
});

it('extracts an array embedded in text', () => {
const text = 'words ["x","y"] more words';
expect(extractJson(text)).toEqual(['x', 'y']);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Cover responses containing multiple JSON segments.

The greedy object matcher in src/lib/ai/json.ts captures from the first { through the last }, so multiple JSON blocks produce null. Add a regression test to establish first-value extraction.

Proposed test
+  it('extracts the first JSON object when later JSON follows', () => {
+    const text = 'Result: {"score":8} Metadata: {"cached":false}';
+    expect(extractJson(text)).toEqual({ score: 8 });
+  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('extracts JSON from prose', () => {
const text = 'Here is your result:\n{"score": 8, "ok": true}\nHope that helps.';
expect(extractJson(text)).toEqual({ score: 8, ok: true });
});
it('extracts JSON wrapped in code fences', () => {
const text = '```json\n{"a":1}\n```';
expect(extractJson(text)).toEqual({ a: 1 });
});
it('extracts an array embedded in text', () => {
const text = 'words ["x","y"] more words';
expect(extractJson(text)).toEqual(['x', 'y']);
});
it('extracts JSON from prose', () => {
const text = 'Here is your result:\n{"score": 8, "ok": true}\nHope that helps.';
expect(extractJson(text)).toEqual({ score: 8, ok: true });
});
it('extracts JSON wrapped in code fences', () => {
const text = '
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@__tests__/unit/ai-json.test.ts` around lines 13 - 26, Add a regression test
in the extractJson test suite for text containing multiple JSON objects,
asserting that extractJson returns the first JSON value rather than null or
combining later segments. Use the existing extraction test style and preserve
current expectations for prose, fenced JSON, and embedded arrays.

Comment thread AGENTS.md
| **Animation** | Framer Motion v12 | Scroll reveals, stagger, hover physics |
| **Fonts** | Space Grotesk (headings) + Plus Jakarta Sans (body) | No Inter/Roboto |
| **AI** | Z AI Web Dev SDK | Coaching, scoring, content generation |
| **Export** | docx, PDF (pdfkit), Excel (exceljs) | Resource downloads |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File inventory ==\n'
git ls-files AGENTS.md README.md FIX-PLAN.md src/lib/export/pdf.ts

printf '\n== Relevant snippets ==\n'
for f in AGENTS.md README.md FIX-PLAN.md src/lib/export/pdf.ts; do
  if [[ -f "$f" ]]; then
    echo "--- $f ---"
    wc -l "$f"
    case "$f" in
      AGENTS.md) sed -n '1,80p' "$f" | cat -n ;;
      README.md) sed -n '35,60p' "$f" | cat -n ;;
      FIX-PLAN.md) sed -n '55,80p' "$f" | cat -n ;;
      src/lib/export/pdf.ts) sed -n '1,260p' "$f" | cat -n ;;
    esac
    echo
  fi
done

printf '\n== Search for pdfkit usage ==\n'
rg -n --hidden --glob '!**/.git/**' '\bpdfkit\b|manual builder|paginate|silent truncation|content size guard|Export' .

printf '\n== Search for export PDF implementation references ==\n'
rg -n --hidden --glob '!**/.git/**' 'src/lib/export/pdf\.ts|export/pdf|pdf\.ts' .

Repository: projectamazonph/Interview-lab

Length of output: 20065


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Hello"

Repository: projectamazonph/Interview-lab

Length of output: 173


Align the PDF export docs with the shipped implementation. AGENTS.md and README.md should describe the custom paginated PDF builder in src/lib/export/pdf.ts consistently; leave FIX-PLAN.md as future work if you still want a pdfkit migration plan.

📍 Affects 3 files
  • AGENTS.md#L27-L27 (this comment)
  • FIX-PLAN.md#L66-L68
  • README.md#L47-L47
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` at line 27, Update AGENTS.md:27 and README.md:47 to document the
shipped custom paginated PDF builder in src/lib/export/pdf.ts instead of
describing pdfkit as the implementation; keep the PDF export descriptions
consistent across both files. Leave FIX-PLAN.md:66-68 unchanged as future-work
planning for a possible pdfkit migration.

Comment thread FIX-PLAN.md
@@ -0,0 +1,133 @@
# Interview Lab — Thorough Fix Plan

> Audited 2026-07-19. A sequenced, merge-ready plan. Each phase: **goal → files → changes → verification**.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the future-dated documentation claims.

Both documents claim work occurred on July 19, 2026, but the current date is July 18, 2026.

  • FIX-PLAN.md#L3-L3: correct or defer the July 19 audit date.
  • REMEDIATION_PLAN.md#L115-L115: correct or defer the July 19 README-correction date.
📍 Affects 2 files
  • FIX-PLAN.md#L3-L3 (this comment)
  • REMEDIATION_PLAN.md#L115-L115
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@FIX-PLAN.md` at line 3, Remove or defer the future-dated July 19, 2026
claims: update the audit date in FIX-PLAN.md line 3 and the README-correction
date in REMEDIATION_PLAN.md line 115 to a current or non-future date, preserving
the surrounding plan content.

Comment thread FIX-PLAN.md
Comment on lines +19 to +24
| 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 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the required blank line before the table.

The table needs a blank line after the preceding paragraph to satisfy Markdown linting.

🧰 Tools
🪛 markdownlint-cli2 (0.23.0)

[warning] 19-19: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@FIX-PLAN.md` around lines 19 - 24, Insert one blank line between the
preceding paragraph and the table in FIX-PLAN.md, leaving the table content
unchanged.

Source: Linters/SAST tools

Comment thread README.md
Comment on lines +40 to +47
| **Runtime** | Bun (CI uses npm) |
| **Database** | Prisma v6 + PostgreSQL (PostgreSQL only) |
| **Auth** | Custom JWT sessions (jose) + HttpOnly cookies |
| **UI** | Tailwind CSS v4 + custom glass design system |
| **UI** | Tailwind CSS v4 + "Field Manual" light design system |
| **Icons** | Phosphor Icons (light weight) |
| **Fonts** | Space Grotesk (headings) + Plus Jakarta Sans (body) |
| **AI** | Z AI Web Dev SDK for coaching, scoring, content generation |
| **Export** | docx, PDF (pdfkit), Excel (exceljs) |
| **Export** | docx, PDF (manual builder / pdfkit), Excel (exceljs) |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== files ==\n'
git ls-files | rg '^(README\.md|AGENTS\.md|REMEDIATION_PLAN\.md|remediation_plan\.md|package\.json|bun\.lockb|bun\.lock|package-lock\.json|pnpm-lock\.yaml|yarn.lock|\.github/workflows/.*)$'

printf '\n== README snippets ==\n'
sed -n '1,220p' README.md

printf '\n== AGENTS.md ==\n'
if [ -f AGENTS.md ]; then sed -n '1,220p' AGENTS.md; fi

printf '\n== REMEDIATION_PLAN.md ==\n'
if [ -f REMEDIATION_PLAN.md ]; then sed -n '1,220p' REMEDIATION_PLAN.md; fi

printf '\n== package.json ==\n'
if [ -f package.json ]; then sed -n '1,240p' package.json; fi

printf '\n== ci/workflow refs to bun/npm ==\n'
rg -n '\b(bun|npm|pnpm|yarn)\b' .github/workflows package.json README.md AGENTS.md REMEDIATION_PLAN.md 2>/dev/null || true

Repository: projectamazonph/Interview-lab

Length of output: 25475


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== .github/workflows/ci.yml ==\n'
sed -n '1,220p' .github/workflows/ci.yml

printf '\n== package-lock root metadata ==\n'
python3 - <<'PY'
import json
from pathlib import Path
p = Path("package-lock.json")
data = json.loads(p.read_text())
print(data.get("name"))
print(data.get("lockfileVersion"))
print("packages-root:", data.get("packages", {}).get("", {}).keys())
PY

Repository: projectamazonph/Interview-lab

Length of output: 2896


Align the package-manager guidance with CI

README.md (and AGENTS.md) still point contributors to Bun, but CI runs npm ci/npm run ... and the repo only has package-lock.json. Pick one package manager as the source of truth and update the docs and lockfile to match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 40 - 47, Align the package-manager guidance in
README.md and AGENTS.md with the repository’s npm-based CI by making npm the
documented source of truth. Update any contributor commands that reference Bun
and ensure the committed lockfile and documentation consistently use npm without
introducing a competing package-manager lockfile.

Comment thread src/lib/ai/json.ts
Comment on lines +16 to +29
const objectMatch = trimmed.match(/\{[\s\S]*\}/);
if (objectMatch) {
try {
return JSON.parse(objectMatch[0]) as T;
} catch {
// malformed; fall through
}
}

const arrayMatch = trimmed.match(/\[[\s\S]*\]/);
if (arrayMatch) {
try {
return JSON.parse(arrayMatch[0]) as T;
} catch {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Extract one balanced JSON value instead of greedy delimiters.

At Line 16 and Line 25, the regex consumes from the first opening delimiter through the last closing delimiter. A response containing {"a":1} followed later by another brace-delimited fragment becomes unparsable, despite containing a valid first payload. Scan balanced delimiters while respecting quoted strings, and add regression cases for multiple and nested payloads.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/ai/json.ts` around lines 16 - 29, The JSON extraction logic in the
parsing function should replace greedy objectMatch and arrayMatch regexes with
balanced-delimiter scanning that respects quoted strings and escaped characters.
Return the first valid balanced object or array payload, including nested
structures, and add regression coverage for multiple adjacent/following payloads
and nested payloads.

Comment thread src/lib/ai/resume.ts
Comment on lines +42 to +51
validate: (body) => {
const shape = validateShape(body, ['resumeText']);
if (!shape.ok) {
return { ok: false, status: 400, error: 'Resume text is required' };
}
const resumeText = (body as ResumeReviewBody).resumeText;
if (typeof resumeText === 'string' && resumeText.length > 20000) {
return { ok: false, status: 400, error: 'Resume is too long' };
}
return { ok: true, value: body as ResumeReviewBody };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate required request values as non-empty strings. validateShape checks only key presence, then each config casts unknown; JSON values such as { "resumeText": 7 } reach the model instead of receiving a 400.

  • src/lib/ai/resume.ts#L42-L51: require trimmed string resumeText before applying the length limit.
  • src/lib/ai/coach.ts#L98-L103: require trimmed string question and userAnswer.
  • src/lib/ai/cover-letter.ts#L47-L56: require trimmed string jobDescription before applying the length limit.
📍 Affects 3 files
  • src/lib/ai/resume.ts#L42-L51 (this comment)
  • src/lib/ai/coach.ts#L98-L103
  • src/lib/ai/cover-letter.ts#L47-L56
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/ai/resume.ts` around lines 42 - 51, Update the validators in
src/lib/ai/resume.ts lines 42-51, src/lib/ai/coach.ts lines 98-103, and
src/lib/ai/cover-letter.ts lines 47-56 to require each request field to be a
trimmed, non-empty string before casting or applying length checks: resumeText,
question, userAnswer, and jobDescription respectively. Return the existing 400
validation response for invalid values, and preserve the current length-limit
behavior for valid strings.

Comment thread src/lib/export/pdf.ts
Comment on lines +23 to +39
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) {
allLines.push({ text: '', size: 4, bold: false, indent: 0, color: '0 0 0' });
continue;
}
if (trimmed.startsWith('### ')) {
allLines.push({ text: trimmed.replace('### ', ''), size: 12, bold: true, indent: 0, color: '0 0 0' });
} else if (trimmed.startsWith('## ')) {
allLines.push({ text: trimmed.replace('## ', ''), size: 14, bold: true, indent: 0, color: '0 0 0' });
} else if (trimmed.startsWith('# ')) {
allLines.push({ text: trimmed.replace('# ', ''), size: 16, bold: true, indent: 0, color: '0 0 0' });
} else if (trimmed.startsWith('- ') || trimmed.startsWith('• ')) {
allLines.push({ text: `• ${trimmed.replace(/^[-•]\s*/, '')}`, size: 11, bold: false, indent: 20, color: '0 0 0' });
} else {
allLines.push({ text: trimmed, size: 11, bold: false, indent: 0, color: '0 0 0' });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Wrap lines before paginating them.

Each input line becomes one Tj operation, so long lines extend beyond the right page boundary and remain visually truncated. Split each styled line according to Courier width and available horizontal space before applying vertical pagination.

Also applies to: 85-93

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/export/pdf.ts` around lines 23 - 39, Update the line-processing logic
in the PDF export routine to wrap every styled text line to the available page
width using Courier character measurements before vertical pagination and Tj
generation. Preserve each line’s existing text styling, indentation, and
blank-line handling across wrapped segments, including the corresponding logic
around the additional pagination range.

Comment thread src/lib/export/pdf.ts
Comment on lines +35 to +36
} else if (trimmed.startsWith('- ') || trimmed.startsWith('• ')) {
allLines.push({ text: `• ${trimmed.replace(/^[-•]\s*/, '')}`, size: 11, bold: false, indent: 20, color: '0 0 0' });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Use consistent PDF text encoding and stream lengths.

Buffer.byteLength(stream) counts UTF-8 bytes, but the final document is serialized as Latin-1. The generated alone is counted as three bytes but written as one, producing an incorrect /Length; other Unicode characters are also corrupted. Encode stream bytes once, use their exact length, and use a font/encoding capable of preserving supported input.

Also applies to: 91-94, 110-111, 129-129

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/export/pdf.ts` around lines 35 - 36, Update the PDF stream generation
around allLines and the final document serialization to encode the stream once,
use the resulting byte array’s exact length for /Length, and write those same
bytes without a later Latin-1 conversion. Replace the current font/encoding
choice with one that preserves the supported Unicode input, including the bullet
character, while keeping text rendering unchanged.

Comment thread src/lib/export/pdf.ts
Comment on lines +82 to +111
addObject('<< /Type /Catalog /Pages 2 0 R >>');
const contentObjIds: number[] = [];

for (const pageLines of pages) {
let stream = '';
let py = MARGIN_TOP;
for (const item of pageLines) {
const fontName = item.bold ? '/F2' : '/F1';
const x = MARGIN_LEFT + item.indent;
stream += `BT\n${fontName} ${item.size} Tf\n${item.color} rg\n${x} ${py} Td\n(${escapePdfText(item.text)}) Tj\nET\n`;
py -= item.size + 4;
}
const streamObjId = addObject(`<< /Length ${Buffer.byteLength(stream)} >>\nstream\n${stream}\nendstream`);
contentObjIds.push(streamObjId);
}

// Pages object (object 2) references all page objects. Page objects are laid out
// immediately after this one at positions 3, 5, 7, ... (each page gets one object),
// matching the content-stream object ids produced above.
const pagesKids = pages.map((_, i) => `${3 + i * 2} 0 R`).join(' ');
addObject(`<< /Type /Pages /Kids [${pagesKids}] /Count ${pages.length} >>`);

for (let i = 0; i < pages.length; i++) {
addObject(
`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${PAGE_WIDTH} ${PAGE_HEIGHT}] /Contents ${contentObjIds[i]} 0 R /Resources << /Font << /F1 5 0 R /F2 6 0 R >> >> >>`,
);
}

addObject('<< /Type /Font /Subtype /Type1 /BaseFont /Courier /Encoding /WinAnsiEncoding >>');
addObject('<< /Type /Font /Subtype /Type1 /BaseFont /Courier-Bold /Encoding /WinAnsiEncoding >>');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift

Build object references from their actual IDs.

Content streams consume object IDs starting at 2, but the catalog still points /Pages to object 2, while page and font references are also hard-coded. Even a one-page export therefore has invalid cross-references.

Reserve IDs up front or emit objects in this order: catalog, pages tree, page objects, content streams, fonts. Add a parser-based regression test after correcting the layout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/export/pdf.ts` around lines 82 - 111, Correct object allocation in
the PDF export flow around addObject so every /Pages, /Page, /Contents, and font
reference uses the actual assigned object IDs rather than hard-coded values.
Reserve IDs or reorder emission so the catalog, pages tree, page objects,
content streams, and fonts remain consistent, then add a parser-based regression
test covering at least a one-page export and validating all cross-references.

@projectamazonph
projectamazonph merged commit 809ddbc into main Jul 18, 2026
3 of 4 checks passed
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