Context
Follow-up surfaced by CE code review of #26. The Letters → Capture flow in that PR stamps a letterId onto every ask capture so AskSheet can surface a clickable "From — " pill linking back to the originating letter. The pill works on the local-only path (write → persist → reload from ss:v1:*), but is silently dropped on the backend round-trip.
Symptom
After a full session + backend snapshot reload (State.applyBackendSnapshot → Captures.upsertBackend), the ask capture loses its letterId because StudentSpaceReflectionCaptureSnapshot doesn't carry the field. The pill disappears from replay; the capture text + reframe + thread are intact.
Layered fix
| Layer |
Change |
| DB migration |
Add nullable letter_id text column to mirror_entries (see src/db/schema.ts:65) |
| Queries |
MirrorEntryRow type in src/db/queries.ts auto-picks up the column; verify select lists include it |
| Write path |
src/server/run-mirror.handler.server.ts (and any other path inserting into mirror_entries) forwards letterId from the request body |
| Read path |
mapShellMirrorEntries at src/lib/student-space/backend-snapshot.ts:266 forwards entry.letterId into the snapshot |
| Type |
Add letterId: string | null to StudentSpaceReflectionCaptureSnapshot |
| Engine schema |
Relax the mergeCapture typeof === 'string' guard for letterId to allow null (so backend-cleared values don't trip the validator) |
Why this wasn't in #26
The fix crosses a DB migration boundary and the user-visible failure mode is narrow (clickable back-reference only — capture data is intact). Bundling it would mix UI work with schema work and inflate review surface. Better as its own PR with focused migration review.
Related
Context
Follow-up surfaced by CE code review of #26. The Letters → Capture flow in that PR stamps a
letterIdonto everyaskcapture so AskSheet can surface a clickable "From — " pill linking back to the originating letter. The pill works on the local-only path (write → persist → reload fromss:v1:*), but is silently dropped on the backend round-trip.Symptom
After a full session + backend snapshot reload (
State.applyBackendSnapshot→Captures.upsertBackend), theaskcapture loses itsletterIdbecauseStudentSpaceReflectionCaptureSnapshotdoesn't carry the field. The pill disappears from replay; the capture text + reframe + thread are intact.Layered fix
letter_id textcolumn tomirror_entries(seesrc/db/schema.ts:65)MirrorEntryRowtype insrc/db/queries.tsauto-picks up the column; verify select lists include itsrc/server/run-mirror.handler.server.ts(and any other path inserting intomirror_entries) forwardsletterIdfrom the request bodymapShellMirrorEntriesatsrc/lib/student-space/backend-snapshot.ts:266forwardsentry.letterIdinto the snapshotletterId: string | nulltoStudentSpaceReflectionCaptureSnapshotmergeCapturetypeof === 'string'guard forletterIdto allownull(so backend-cleared values don't trip the validator)Why this wasn't in #26
The fix crosses a DB migration boundary and the user-visible failure mode is narrow (clickable back-reference only — capture data is intact). Bundling it would mix UI work with schema work and inflate review surface. Better as its own PR with focused migration review.
Related
.context/compound-engineering/ce-code-review/20260521-142136-d51fbba5/correctness.json