The tweakable plan
Instead of a plan sorted by execution order, ask for one sorted by how likely you are to change each part — so the decisions worth your attention surface first and the mechanical work sinks to the bottom.
Write an implementation plan for annotation export as HTML, but lead with the decisions I'm most likely to tweak: data model changes, new type interfaces, and anything user-facing. Bury the mechanical refactoring at the bottom — I trust you on that part.
Implementation plan: Annotation export
Acme · export a review's annotations as a shareable PDF or CSV · branch feat/annotation-export
Decisions you'll probably want to change
High tweak likelihoodThree areas where I made a judgment call you might disagree with. Each flagged choice shows the alternative I considered — toggle to compare.
A1 · Data model — new annotation_exports table
- Export stays valid even if the review is archived.
- Renderer reads one row — no N+1 across annotations + annotation_replies.
Cost: ~40 KB/row for a heavy review; snapshots can go stale relative to the live thread.
- No duplicate data, no staleness question.
- Re-downloading the same export can produce a different file — surprising for audit use.
Pick this if: exports are working documents, not records. One line to tell me: “use live join.”
Cost: blob lifecycle to manage; I'd add a 30-day TTL sweep (Section C, item 6).
Pick this if: storage/compliance rules make stored copies awkward.
A2 · New type interfaces
'xlsx' or 'srt' later is one union member + one renderer.TimecodeRange lets producers export just the notes for one scene. If nobody asked for this, cutting it removes ~½ day.'wont_fix'.A3 · UX flow
Why step 3 is weak: most exports will finish in under 3 seconds, so a fire-and-forget toast may feel like overkill — but a 400-annotation review with drawings takes ~20 s, which is too long to block. A hybrid (wait up to 4 s, then fall back to notify) is nicer and adds ~half a day. Your call.
Sequencing
Execution orderThe order I'd actually build it in. Every step lands green on CI; nothing user-visible ships until step 5 flips the flag.
- 1Migration + model. Create annotation_exports, wire the Sequelize model, factory, fixtures.0.5d
- 2Types + request validation. Everything in A2, plus zod schemas on POST /api/reviews/:id/exports.0.25d
- 3Renderers. CSV first (trivial), then PDF via the existing @acme/render service.1d
- 4Worker job + blob upload. Queue on exports.render; retries ×3 then status = failed.0.25d
- 5UI: menu item, modal, toast, Exports tab. Behind flag export_annotations.0.5d
- 6Tests, docs, flag rollout to the Acme team.0.25d
Mechanical work (trust me)
Low tweak likelihoodRefactors and plumbing the feature needs. No judgment calls here — collapsed on purpose.
Boring but necessary 8 tasks · ~0.5d total, folded into the estimates above
- Extract
AnnotationSerializerfromapi/annotations.tsintolib/serializers/so the export worker can reuse it. Pure move, no behavior change. - Finish the
CommentMarker→AnnotationMarkerrename — 3 files still import the deprecated alias. - Add
annotation_exportsto the fixture loader and the CI database-reset script. - Register
exports.renderinworkers/index.tsand add it to the dead-letter alert list. - Create feature flag
export_annotationsinflags.yaml, default off. - Add a nightly TTL sweep for expired export blobs to
jobs/cleanup.ts(only if Choice ② stays as-is). - Extend
openapi.yamlwith the two new endpoints and regenerate the client. - Move the timecode-formatting helper from
player/utils.tstolib/time.ts— the PDF renderer needs it and shouldn't import from the player bundle.
Tweak these three things
The highest-leverage replies you could send. Copy one, edit, send — I'll revise the plan.