Skip to content

Fix CSV newline bug + valibot validation pattern#25

Merged
bradburch merged 3 commits into
custom-servicesfrom
fix/csv-newline-and-valibot-validation
Jul 14, 2026
Merged

Fix CSV newline bug + valibot validation pattern#25
bradburch merged 3 commits into
custom-servicesfrom
fix/csv-newline-and-valibot-validation

Conversation

@bradburch

Copy link
Copy Markdown
Owner

Two fixes from a "reinvented-wheel" audit — the codebase is deliberately platform-first (WebCrypto, Intl, hono/jwt), so only two spots warranted change.

1. CSV parser: newlines inside quoted fields (bug)

parseCsvRows split rows on newlines before handling quotes, so a newline inside a quoted field (a multi-line Notes value pasted from Excel/Sheets) was treated as a row break — shredding one logical row into several and silently corrupting client/pet imports (server/routes/admin.ts import path).

Rewrote as a single-pass state machine that only breaks rows on newlines outside quotes (CRLF/CR/LF handled, no phantom empty rows). All prior behavior preserved; kept dep-free rather than pulling in papaparse for a narrow format.

Adds LF + CRLF embedded-newline tests (fail against the old code, pass now).

2. valibot validation pattern (ergonomics)

Converted the /identify and /verify handlers from untyped json().catch() + scattered typeof guards to inline valibot schemas + a single safeParse. Email validation reuses the existing EMAIL_RE via v.regex, so every error response (400/403/502/503/200) is byte-for-byte unchanged. This is the reference pattern for migrating the remaining as-cast route sites.

Chose valibot over zod for its tree-shakeable function API — matters for the embeddable widget bundle.

Verification

  • Full suite: 46 files / 338 tests pass
  • tsc -b clean, eslint clean on changed files
  • Error-response contract confirmed unchanged by existing auth tests

🤖 Generated with Claude Code

bradburch and others added 3 commits July 14, 2026 14:24
parseCsvRows split the input into rows before handling quotes, so a
newline embedded in a quoted field (e.g. a multi-line Notes value pasted
from a spreadsheet) was treated as a row break — shredding one logical
row into several and silently corrupting client/pet imports.

Rewrite as a single-pass state machine that only breaks rows on newlines
outside quotes (CRLF/CR/LF all handled). All prior behavior preserved;
adds LF and CRLF embedded-newline tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the untyped json().catch() + hand-rolled typeof guards in the
/identify and /verify handlers with inline valibot schemas and a single
safeParse. Email validation reuses the existing EMAIL_RE via v.regex, so
every error response is byte-for-byte unchanged. Serves as the reference
pattern for migrating the remaining route validation sites.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The import format is Client Email, Client Name, Pet Name, Pet Type — the
newline-in-quoted-field fix is generically correct, but the rationale
wrongly cited a Notes column. Point it at an actual quoted field instead.

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

Copy link
Copy Markdown
Owner Author

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Reviewed across five independent passes (bug scan, git-history regression, prior-PR review comments, code-comment/contract compliance, dependency conventions). Notable invariants verified as preserved:

  • CSV parser: CRLF collapses to one row break, "" escaping intact, and the blank-line-as-[''] row alignment from d57d092 (which admin.ts positional row numbering depends on) still holds.
  • Auth routes: /identify and /verify error responses are byte-for-byte unchanged; the constant-time code compare, attempt cap, invite-only gate, and fail-closed dev gating live outside the diff and are untouched.
  • valibot is scoped to server/routes/, not src/shared/ (whose zero-runtime-dependency rule is preserved).

One factual nit surfaced and fixed in 62f1eb1: the parser docstring wrongly cited a Notes import column (the format is Client Email, Client Name, Pet Name, Pet Type); the fix itself is generically correct for any quoted field.

🤖 Generated with Claude Code

@bradburch bradburch merged commit 2c2d55d into custom-services Jul 14, 2026
2 checks passed
@bradburch bradburch deleted the fix/csv-newline-and-valibot-validation branch July 14, 2026 21:42
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