← All examples
Know your unknowns · Pre-implementation

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.

The prompt
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.
What Claude produced

Implementation plan: Annotation export

Acme · export a review's annotations as a shareable PDF or CSV · branch feat/annotation-export

Effort~2.5 dev-days
Files touched13 (3 new)
RiskLow–medium
Migrations1, additive
↑ tweak ↓ trust
This plan is sorted by likelihood-of-tweaking, not execution order. Read top to bottom = most worth your attention first. Section A holds every judgment call; Section B is the build order; Section C is mechanical and safe to skip entirely.
A

Decisions you'll probably want to change

High tweak likelihood

Three 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

annotation_exports NEW
iduuid pk
review_iduuid → reviews
requested_byuuid → users
statusenum(queued, rendering, ready, failed)
optionsjsonb
snapshot ①jsonb
① no snapshot column — renderer JOINs annotations live at render time
artifact_url ②text null
rendered_attimestamptz null
② no stored artifact — GET /exports/:id/download renders on demand
created_attimestamptz
Choice ① Snapshot vs. live join
Plan's pick — denormalized snapshot
Copy the annotation payloads into snapshot jsonb when the export is requested. The export is a record of the review at that moment: later edits, resolutions, and deletions don't rewrite history.
  • 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.

Alternative — live join at render time
Skip the snapshot; the renderer joins annotations when the file is generated. Exports always reflect current state.
  • 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.”

Choice ② Stored artifact vs. render-on-demand
Plan's pick — render once, store in blob storage
The worker renders the PDF/CSV once and writes artifact_url. Downloads are a signed-URL redirect — cheap, cacheable, and shareable with reviewers who lack Acme accounts.

Cost: blob lifecycle to manage; I'd add a 30-day TTL sweep (Section C, item 6).

Alternative — render on every download
No stored file: GET /exports/:id/download streams a fresh render. Zero storage, but ~2–4 s per download on large reviews, and no shareable public link.

Pick this if: storage/compliance rules make stored copies awkward.

A2 · New type interfaces

// src/types/exports.ts (new)
export interface ExportRequest {
reviewId: ReviewId;
format: 'pdf' | 'csv';1
range: 'all' | 'unresolved' | TimecodeRange;2
includeReplies: boolean; // default true
includeDrawings: boolean; // default false3
}
 
export interface AnnotationSnapshot {
id: AnnotationId;
timecode: string; // "00:04:12.08"
author: { id: UserId; name: string };
body: string;
state: 'open' | 'resolved';4
replies: ReplySnapshot[];
}
1Started with PDF + CSV. Adding 'xlsx' or 'srt' later is one union member + one renderer.
2TimecodeRange lets producers export just the notes for one scene. If nobody asked for this, cutting it removes ~½ day.
3Drawings (frame markups) rasterize into the PDF. Off by default because it triples render time.
4Deliberately drops the internal 5-state machine to 2 states in the export. Tell me if reviewers need to see 'wont_fix'.

A3 · UX flow

Step 1
Export button
New item in the review toolbar overflow menu, next to “Copy share link”.
Step 2
Options modal
Format, range, include-replies, include-drawings. Reuses ModalForm from settings.
Step 3
Weakest part of this plan
Background job + toast
Export renders in a worker; user gets a “we'll notify you” toast and can navigate away.
Step 4
Ready notification
Bell notification + download link; also listed under review → Exports tab.

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.

B

Sequencing

Execution order

The order I'd actually build it in. Every step lands green on CI; nothing user-visible ships until step 5 flips the flag.

  1. 1Migration + model. Create annotation_exports, wire the Sequelize model, factory, fixtures.0.5d
  2. 2Types + request validation. Everything in A2, plus zod schemas on POST /api/reviews/:id/exports.0.25d
  3. 3Renderers. CSV first (trivial), then PDF via the existing @acme/render service.1d
  4. 4Worker job + blob upload. Queue on exports.render; retries ×3 then status = failed.0.25d
  5. 5UI: menu item, modal, toast, Exports tab. Behind flag export_annotations.0.5d
  6. 6Tests, docs, flag rollout to the Acme team.0.25d
C

Mechanical work (trust me)

Low tweak likelihood

Refactors 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 AnnotationSerializer from api/annotations.ts into lib/serializers/ so the export worker can reuse it. Pure move, no behavior change.
  • Finish the CommentMarkerAnnotationMarker rename — 3 files still import the deprecated alias.
  • Add annotation_exports to the fixture loader and the CI database-reset script.
  • Register exports.render in workers/index.ts and add it to the dead-letter alert list.
  • Create feature flag export_annotations in flags.yaml, default off.
  • Add a nightly TTL sweep for expired export blobs to jobs/cleanup.ts (only if Choice ② stays as-is).
  • Extend openapi.yaml with the two new endpoints and regenerate the client.
  • Move the timecode-formatting helper from player/utils.ts to lib/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.

“Switch Choice ① to live join — exports here are working docs, not records.”
“Cut TimecodeRange from ExportRequest; nobody has asked for per-scene export.”
“Do the hybrid for step 3: wait up to 4s inline, then fall back to notify.”