Skip to content

PDF annotations never save: EmbedPDF's numeric subtype fails the mutator's string schema #855

Description

@InfinityBowman

PDF annotations are rejected before they leave the browser, so no highlight has ever been saved in production.

Impact

A user hit this live on 2026-09-22: nine annotation.add / annotation.update rejections between 22:21 and 22:43 UTC on one checklist, each one surfacing a "Change rejected" toast. Every active production project exports zero annotations rows (checked all four: 1660, 140, 1455 and 460 answers respectively, no annotations anywhere), so this has been broken since the sync-engine migration in ba6f6c8c and simply went unnoticed until someone tried to highlight.

Cause

packages/web/src/components/pdf/embedpdf/react/src/viewer.tsx:220 spreads the raw EmbedPDF annotation object after the explicit fields:

onAnnotationAdd({
  id: annotationId,
  type: annotation.type,
  pageIndex: event.pageIndex,
  ...annotation,
})

annotation.type is EmbedPDF's PdfAnnotationSubtype, a numeric enum (HIGHLIGHT = 9), so type arrives as a number. The mutator declares type: z.string() (packages/shared/src/sync/mutators.ts:1087), and the annotations row schema does the same (packages/shared/src/sync/schema.ts:127). The sync client validates args before sending, so the mutation fails fast with InvalidArgs and never reaches the Durable Object. annotation.update fails identically through updates.type (mutators.ts:1152).

The update path has the same spread at viewer.tsx:234.

Decision needed

Two ways to close it, and the choice affects the synced schema:

  1. Coerce at the call site - map the subtype to its name (PdfAnnotationSubtypeName) or String(...) before handing it to the mutator. No schema change, but the stored value's meaning changes depending on which we pick.
  2. Widen annotations.type and both mutators to accept the numeric subtype. Truer to the source data, but it is a schema change on a synced table.

Either way the ...annotation spread should stop clobbering the fields written above it.

Verification

  • Reproduce: open any checklist with a PDF, draw a highlight, expect a "Change rejected" toast and a client.sync.mutation_rejected line with code: InvalidArgs.
  • Fixed when a highlight survives a reload and the project's sync-admin export contains annotations rows.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions