Skip to content

feat(#233): expand snapshot JSON to capture timeline state (Phase 1 of Resource Optimiser)#234

Merged
NickMonrad merged 2 commits intomainfrom
feature/optimiser-phase1-snapshot-expansion
Apr 29, 2026
Merged

feat(#233): expand snapshot JSON to capture timeline state (Phase 1 of Resource Optimiser)#234
NickMonrad merged 2 commits intomainfrom
feature/optimiser-phase1-snapshot-expansion

Conversation

@NickMonrad
Copy link
Copy Markdown
Owner

Summary

Phase 1 of 4 for Resource Optimiser (#233). Expands BacklogSnapshot.snapshot JSON to capture full project timeline state so future optimiser runs can be safely rolled back.

No schema change — purely an expansion of the existing JSON blob shape, gated behind schemaVersion: 2. Legacy v1 snapshots (top-level epic array) are still readable and rollback-able.

Changes

Snapshot v2 shape

buildSnapshot() now captures:

  • epics — full epic/feature/story/task tree (existing)
  • project — project-level scheduling fields (hoursPerDay, startDate, etc.)
  • resourceTypes — all RTs with counts, allocations, named resource refs
  • namedResources — start/end weeks, allocations
  • timelineEntries + storyTimelineEntries — current schedule
  • epicDependencies + featureDependencies — manual dep links
  • overheadItems — project overhead config

Rollback handler

  • Detects v1 vs v2 via Array.isArray() / schemaVersion check
  • v1 path unchanged (backwards compat)
  • v2 path runs in a single $transaction:
    1. Upsert ResourceTypes (build rtNameMap for FK resolution)
    2. Upsert NamedResources
    3. Recreate epic/feature/story/task tree (fresh cuid()s, ID maps for FKs)
    4. Restore project fields
    5. Restore TimelineEntries + StoryTimelineEntries (mapped via epicIdMap/storyIdMap)
    6. Restore EpicDependencies + FeatureDependencies (mapped via id maps)
    7. Restore OverheadItems
  • Pre-rollback auto-snapshot (trigger: 'pre_rollback') created BEFORE the transaction; deleted if transaction fails

CSV import refactor

csv.ts now calls shared buildSnapshot() for its auto-snapshot instead of inline epic-only capture.

Test mocks

setup.ts updated with upsert, deleteMany, createMany stubs for the new transaction methods.

Review fixes applied (sub-agent code review cycle)

  • 🔴 HIGH — Transaction ordering: RTs now restored BEFORE epic tree so task FKs resolve correctly when an RT was deleted-then-restored
  • 🟡 MEDIUM — Replaced manual findUnique + branch with atomic prisma.upsert for RT and NamedResource (avoids TOCTOU)
  • 🟡 MEDIUM — Pre-rollback snapshot wrapped in try/catch — on transaction failure, the pre-rollback snapshot is deleted so users don't accumulate spurious entries

Tests

  • npx tsc --noEmit — ✅ clean
  • npm test (server) — ✅ 142/142 passing

Phases

  • Phase 1 — Snapshot expansion (this PR)
  • Phase 2 — Extract scheduler engine into pure lib
  • Phase 3 — POST /projects/:id/optimise endpoint with modes (Speed / Utilisation / Balanced)
  • Phase 4 — Frontend optimiser drawer + result preview/apply

Refs #233

NickMonrad and others added 2 commits April 29, 2026 15:40
- Snapshot JSON now versioned (schemaVersion: 2)
- Captures: project settings, resource types, named resources, timeline entries, story timeline entries, epic deps, feature deps, overhead items
- Rollback restores all captured state in a transaction
- Auto-creates pre_rollback snapshot before applying any rollback
- CSV import snapshot now uses shared buildSnapshot() to ensure full state capture
- Backwards compatible with v1 (array-shape) snapshots — legacy ones treated as epics-only

Prerequisite for #233 Phase 2-4 (Resource Optimiser).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…or handling

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@NickMonrad NickMonrad merged commit baa38f9 into main Apr 29, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant