Commit graph

2 commits

Author SHA1 Message Date
Randall Stillwell
3864219f0a Backfill: prefer fieldData over top-level + auto-load .env
While running the backfill against production we found that many
cards had user-corrected values trapped in fieldData with stale
OCR originals in the top-level column (e.g. LaGay: top-level
email "lagayferters@yahoo.com" vs fieldData "lagayfenters@yahoo.com").

The previous "only promote when top-level is empty" rule skipped
these — so the list view, search, and CSV export still showed the
stale OCR data even though the detail view showed the correction.

New rule:
  - When fieldData[canonical] is non-empty, promote it to the
    top-level column (regardless of whether the top-level column
    already has a value). Reasoning: pre-fix, the UI saved
    non-core edits only to fieldData, so any non-empty
    fieldData[canonical] is the user's most recent value (or
    matches the OCR original — harmless either way). Verified
    against the prod DB that fieldData never contains
    firstName/lastName/name, so there is no risk of reverting
    user-edited names.

Also:
  - Adds `import "dotenv/config"` so `npx tsx` picks up .env.
  - Logs fill-empty vs overwrite counts and a sample of conflicts
    (top-level → fieldData) so the dry-run is easy to audit.

Applied against prod: 23 form fields flipped, ~3127 values
promoted across 901 cards, 282 names recomputed. Re-run dry-run
reports 0 remaining changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 15:09:08 -05:00
Randall Stillwell
7aedae02fc Keep card name and contact fields in sync after edits
Two bugs caused the cards list and integrations to show stale data
after a user edited a card in the detail view:

1. ResponseCard.name is a denormalized display string set only at
   OCR / survey-submit time. Editing firstName or lastName never
   recomputed it, so the table header and Name column kept the old
   value. PUT /api/cards/[id] now recomputes name from first + last
   whenever either changes (unless the caller passed an explicit
   name). The detail page header reads from in-flight edits so the
   title updates live as the user types.

2. The default form template marked only firstName/lastName as
   isCore. Every other field (email, cellPhone, address, etc.) was
   non-core, so dynamic-field edits landed in ResponseCard.fieldData
   JSON and never touched the top-level columns the list view,
   search, CSV export, and integrations read from. The PUT route
   now promotes any fieldData keys that match canonical columns up
   to those columns; the default template marks all canonical
   fields as isCore so new orgs avoid the problem in the first
   place.

Adds scripts/backfill-core-fields.ts (dry-run by default; pass
--apply to commit) to flip existing FormField rows to isCore = true
where the key matches a canonical column and to promote any
existing fieldData values into empty top-level columns + recompute
stale name values.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-21 08:30:27 -05:00