Skip to content

feat: add setbuilder document history#427

Merged
thewrz merged 3 commits into
mainfrom
feat/issue-395
Jun 15, 2026
Merged

feat: add setbuilder document history#427
thewrz merged 3 commits into
mainfrom
feat/issue-395

Conversation

@thewrz

@thewrz thewrz commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

Why

WrzDJSet builder edits are destructive and need trustable document history, visible save state, keyboard recovery, and confirmation around dangerous actions.

Closes #395

What

  • Added owner-scoped builder document snapshot GET/PUT APIs that restore slots, locks, slot targets, curve/vibe-window rows, pool sources/tracks, and target settings.
  • Added a reusable frontend document-history hook with labeled commit boundaries, undo/redo stacks, keyboard shortcuts, save state, beforeunload protection, 30s autosave retry, and undo/redo toasts.
  • Wired existing pool imports/removals, slot target edits, curve template application, and vibe-window changes through the history commit API.
  • Added compact topbar undo/redo controls, a brand save menu row, settings modal toggles, and shared confirmation dialogs for recompute/removal flows.
  • Regenerated OpenAPI and dashboard API types.

Testing

  • dashboard: npm run lint
  • dashboard: npx tsc --noEmit
  • dashboard: npm test -- --run
  • server: /home/adam/github/WrzDJ/server/.venv/bin/ruff check .
  • server: /home/adam/github/WrzDJ/server/.venv/bin/ruff format --check .
  • server: /home/adam/github/WrzDJ/server/.venv/bin/bandit -r app -c pyproject.toml -q
  • server: /home/adam/github/WrzDJ/server/.venv/bin/pytest --tb=short -q (2802 passed, coverage gate reached at 88.71%)
  • server: /home/adam/github/WrzDJ/server/.venv/bin/alembic upgrade head && /home/adam/github/WrzDJ/server/.venv/bin/alembic check could not run locally because PostgreSQL rejected the configured wrzdj credentials; no migrations/model columns were added.

Design decisions

  • The frontend history hook treats the backend snapshot as the document boundary, so future pairings/target/transport work can extend the snapshot schema without rewriting undo/redo mechanics.
  • UI-only state such as active slot, hover, playback, and panel mode stays outside document history.
  • Existing persisted mutations remain server-authoritative; commits capture the pre-mutation snapshot, refresh the post-mutation snapshot, and restore via PUT for undo/redo.
  • The existing curve replacement localStorage gate is preserved while the settings modal becomes the consolidated settings surface.
  • Recompute confirmation copy follows the synced prototype and is intentionally distinct from the existing export modal.

Claude assisted with implementation and test coverage.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added document history with undo/redo controls and Ctrl/Cmd keyboard shortcuts, including a live “unsaved changes” indicator and history/settings entry.
    • Enabled automatic and manual saving via restorable document snapshots, including restoring vibe window state.
    • Introduced a centralized confirmation dialog flow for confirmable actions and added a builder settings modal (autosave + interaction options).
  • Tests

    • Added server snapshot API tests (round-trip, validation, ownership isolation) and client-side history hook/commit coverage (manual save + autosave retry).

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3fd85711-f35f-4742-b6a4-ad53fd8af551

📥 Commits

Reviewing files that changed from the base of the PR and between bf08d25 and 27fb1dc.

📒 Files selected for processing (1)
  • server/tests/test_setbuilder_document_snapshot_api.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/tests/test_setbuilder_document_snapshot_api.py

📝 Walkthrough

Walkthrough

This PR implements snapshot-based undo/redo, autosave, and destructive-action confirmations for the set builder (#395). It adds server-side SetDocumentSnapshot Pydantic schemas, GET/PUT /sets/{set_id}/document endpoints, a build_snapshot/restore_snapshot service, a frontend useSetDocumentHistory hook with commit/undo/redo/saveNow/keyboard shortcuts, new UI components (BuilderBrandMenu, HistoryControls, ConfirmActionDialog, BuilderSettingsModal), and threads the commit/snapshot/confirmation API through PoolPanel, ImportModal, BuilderWorkspace, and CurvePanel.

Changes

Set Builder Document History System

Layer / File(s) Summary
Server snapshot schemas and service
server/app/schemas/setbuilder.py, server/app/services/setbuilder/document_snapshot.py, server/app/api/setbuilder.py, server/openapi.json, dashboard/lib/api-types.generated.ts
Pydantic models for SetDocumentSnapshot sub-types with field constraints (bpm bounds, energy ranges [0–10], string length limits, max array sizes) and cross-field validators (bpm_ceiling ≥ bpm_floor, pool tracks reference existing sources); build_snapshot/restore_snapshot service functions using deterministic ordering (by position/id for stable history serialization); two new owner-scoped GET/PUT endpoints (60/min and 30/min rate limits) wired to the service with 404 and 422 error handling.
Generated types and API client methods
dashboard/lib/api-types.ts, dashboard/lib/api.ts
Exported SetDocument* type aliases mapping to generated schema types; getSetDocument(setId) and putSetDocument(setId, snapshot) methods added to ApiClient for authenticated GET/PUT of /api/setbuilder/sets/{setId}/document.
useSetDocumentHistory hook
dashboard/app/(dj)/setbuilder/components/useSetDocumentHistory.ts, .../__tests__/useSetDocumentHistory.test.tsx
Core client hook exporting BuilderCommit type and managing undo/redo stacks (max 50), dirty/saving/error state, commit(label, action) that captures before-snapshot and appends to undo stack, restore(direction) for undo/redo via putSetDocument, saveNow(), keyboard shortcuts (⌘Z/Ctrl+Z, ⇧⌘Z/Ctrl+Y, ⌘S) with input-aware filtering, beforeunload guard, 30s autosave interval, and toast auto-clear. Test suite covers commit/undo/redo round-trip, manual save without history growth, and autosave retry after failed save.
New top-bar and modal UI components
dashboard/app/(dj)/setbuilder/components/BuilderBrandMenu.tsx, ...HistoryControls.tsx, ...ConfirmActionDialog.tsx, ...BuilderSettingsModal.tsx, setbuilder.module.css
BuilderBrandMenu renders save/dirty status with "ago" timer, 1s interval refresh, and menu close on outside-click/Escape; HistoryControls shows undo/redo/settings buttons with depth badges and optional next-action labels; ConfirmActionDialog is an alertdialog with kind-based icon (warning/danger/neutral), cancel/confirm buttons, and backdrop handling; BuilderSettingsModal provides checkbox sections for Autosave, Edit prompts (energy-mismatch, replacements, curve-point labels), Interaction (double-click play, curve scrubbing, slot markers), and Agent (chimes, auto-expand pairings); CSS adds brand-menu dropdown, save-state dot with .saveDotDirty pulsing animation, history icon buttons with depth badges, and confirm dialog styling.
PoolPanel and ImportModal commit/snapshot wiring
dashboard/app/(dj)/setbuilder/components/PoolPanel.tsx, ...ImportModal.tsx, .../__tests__/ImportModal.test.tsx
PoolPanelProps extended with optional snapshot, snapshotVersion, commit, confirmRemovals, requestConfirmation; pool init effect hydrates from snapshot when present (keyed by version), otherwise fetches via api.getPool(setId) with cancellation handling; removeTracks/removeSource optionally gated by requestConfirmation when confirmRemovals enabled, routed through commit(label, action) when provided; ImportModal gains runImport helper that executes import action directly or wraps in commit(label, action) for event, playlist, URL, and manual track flows; test verifies commit is called with label Import event requests.
BuilderWorkspace and CurvePanel commit/snapshot/confirmation wiring
dashboard/app/(dj)/setbuilder/components/BuilderWorkspace.tsx, ...CurvePanel.tsx
BuilderWorkspaceProps extended with optional snapshot, snapshotVersion, commit and replacement-suggestion/confirmation props; slot init effect re-runs on snapshotVersion change; CurvePanelProps extended with windowsFromSnapshot helper (normalizes vibe-window times to current total slot duration), suggestReplacementsSetting prop (overrides localStorage), and optional commit/requestConfirmation props; vibe-window loading restores from snapshot when available (keyed by version), falling back to session-deduped API fetch; persistWindows, retarget, and applyTemplate wrapped with commit labels; applyTemplate rewritten as async, optionally gated by requestConfirmation when confirmRecompute enabled, rebuilds targets and vibe windows before state update.
BuilderPage orchestration
dashboard/app/(dj)/setbuilder/[setId]/page.tsx
Page derives numeric setId from use(params), initializes useSetDocumentHistory(numericSetId), and adds localStorage-backed builderSettings (autosave, replacement-suggestion toggles, confirmations). Implements promise-based requestConfirmation/closeConfirm flow to gate destructive actions. Replaces top bar with BuilderBrandMenu (wired to history dirty/saving/error/save callback) and HistoryControls (undo/redo depths/labels/settings callback). Wires PoolPanel, BuilderWorkspace with snapshot/version/commit props and confirmation handlers; updates recompute to run through history.commit(...) and gate on builderSettings.confirmRecompute. Renders BuilderSettingsModal, ConfirmActionDialog, and separate history.toast region.
Server API tests
server/tests/test_setbuilder_document_snapshot_api.py
Four tests covering document snapshot endpoints: round-trip destructive-mutation restore (GET, mutate, restore, logical-equivalence assertion), ignore-client-primary-keys restore (GET, overwrite id fields, restore, assert server-generated ids differ), owner-isolation 404s (another DJ user fails GET/PUT), and 422 validation for track referencing missing source. Includes helpers _without_keys, _logical_snapshot (id-agnostic comparison), and _create_seeded_set for test data setup.

Sequence Diagram(s)

sequenceDiagram
  participant DJ as DJ (Browser)
  participant BuilderPage
  participant useSetDocumentHistory
  participant ApiClient
  participant Server

  DJ->>BuilderPage: performs mutating action (e.g. move slot)
  BuilderPage->>useSetDocumentHistory: commit("Move slot 3 → 5", action)
  useSetDocumentHistory->>useSetDocumentHistory: clone snapshot → undoStack
  useSetDocumentHistory->>ApiClient: action() mutating API call
  useSetDocumentHistory->>ApiClient: getSetDocument(setId)
  ApiClient->>Server: GET /api/setbuilder/sets/{id}/document
  Server-->>ApiClient: SetDocumentSnapshot
  useSetDocumentHistory-->>BuilderPage: isDirty=true, undoDepth+1

  DJ->>BuilderPage: clicks Undo (or ⌘Z)
  BuilderPage->>useSetDocumentHistory: undo()
  useSetDocumentHistory->>ApiClient: putSetDocument(setId, undoSnapshot)
  ApiClient->>Server: PUT /api/setbuilder/sets/{id}/document
  Server-->>ApiClient: SetDocumentSnapshot (restored)
  useSetDocumentHistory-->>BuilderPage: snapshot updated, toast "Undid Move slot 3 → 5"

  DJ->>BuilderPage: autosave interval fires (30s, dirty)
  BuilderPage->>useSetDocumentHistory: saveNow()
  useSetDocumentHistory->>ApiClient: putSetDocument(setId, currentSnapshot)
  ApiClient->>Server: PUT /api/setbuilder/sets/{id}/document
  Server-->>ApiClient: SetDocumentSnapshot
  useSetDocumentHistory-->>BuilderPage: isDirty=false, lastSavedAt updated
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~90 minutes

Possibly related PRs

  • wrzonance/WrzDJ#415: Both PRs modify the setbuilder curve editor integration (notably CurvePanel.tsx and related replacement/vibe-window/template handling), with the main PR extending CurvePanel to add snapshot/history/confirmation/commit wiring on top of the curve-editor work from PR #415.
  • wrzonance/WrzDJ#414: Main PR extends the existing pool-import UI by wiring PoolPanel/ImportModal into the new set-document history/commit + confirmation flow, directly building on the PoolPanel/ImportModal introduced in PR #414.
  • wrzonance/WrzDJ#424: Both PRs modify the set-builder BuilderPage recompute flow and confirmation UI—main PR refactors it into ConfirmActionDialog + history.commit(...) while PR #424 adds an earlier modal-based confirmation approach.

Poem

🐰 Hop, hop, commit! The slots all line up neat,
Undo the chaos, redo the beat,
A pulsing dot glows — "Unsaved, beware!"
⌘Z saves the DJ from pulling their hair,
Autosave whispers every thirty seconds true,
The builder now remembers everything you do! 🎵

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: add setbuilder document history' directly and clearly summarizes the main change: implementing a document history system with undo/redo for the set builder.
Linked Issues check ✅ Passed The PR comprehensively addresses all coding requirements from issue #395: snapshot-based history with commit API, topbar undo/redo buttons with badges, keyboard shortcuts (Ctrl+Z, Ctrl+Y, Ctrl+S), save indicators with autosave, confirmation dialogs for destructive actions, and settings modal toggles.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing the document history system as specified in #395. No unrelated modifications were introduced outside the history system, confirmation dialogs, settings modal, or necessary API/schema updates.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-395

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
dashboard/app/(dj)/setbuilder/components/PoolPanel.tsx (1)

87-101: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cancel stale getPool responses in the snapshot-loading effect.

Line 96 starts api.getPool(setId) without cancellation. If snapshot becomes available before that request resolves, the late response can overwrite the snapshot-derived pool state.

Suggested fix
   useEffect(() => {
+    let cancelled = false;
     setVibes(new Map());
     setShowVibes(false);
     setVibesLoaded(false);
+    setLoaded(false);
     if (snapshot) {
       setPool(snapshot.pool);
       setLoaded(true);
-      return;
+      return () => {
+        cancelled = true;
+      };
     }
     api
       .getPool(setId)
-      .then(setPool)
-      .catch(() => setToast('Failed to load pool'))
-      .finally(() => setLoaded(true));
+      .then((next) => {
+        if (!cancelled) setPool(next);
+      })
+      .catch(() => {
+        if (!cancelled) setToast('Failed to load pool');
+      })
+      .finally(() => {
+        if (!cancelled) setLoaded(true);
+      });
+    return () => {
+      cancelled = true;
+    };
   }, [setId, snapshot, snapshotVersion]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/app/`(dj)/setbuilder/components/PoolPanel.tsx around lines 87 -
101, The useEffect hook in PoolPanel.tsx initializes an async api.getPool(setId)
request without any cancellation mechanism. If the snapshot dependency changes
before the request completes, the stale response will overwrite the pool state
derived from the snapshot. Add an AbortController to the api.getPool call and
implement proper cleanup in the useEffect return statement to cancel the pending
request when dependencies change or the component unmounts. Check whether the
request was aborted before calling setPool to prevent stale state updates.
dashboard/app/(dj)/setbuilder/components/CurvePanel.tsx (1)

162-183: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent stale getVibeWindows responses from overwriting newer state.

Line 162 starts an async fetch that is never cancelled/guarded; if snapshot arrives or setId changes before it resolves, Line 170 can still call setWindows(...) with stale data.

Suggested fix
-  useEffect(() => {
-    if (snapshot || totalSec <= 0 || windowsLoadedFor.current === setId) return;
+  useEffect(() => {
+    if (snapshot || totalSec <= 0 || windowsLoadedFor.current === setId) return;
+    let cancelled = false;
     // Mark eagerly so in-flight fetches dedupe; roll back on failure so a
     // transient error doesn't block the load for the rest of the session.
     windowsLoadedFor.current = setId;
     api
       .getVibeWindows(setId)
       .then((resp) => {
+        if (cancelled) return;
         setWindows(
           resp.windows.map((w, i) => ({
             id: `w-${i}-${w.t0_sec}`,
             t0: Math.min(1, w.t0_sec / totalSec),
             t1: Math.min(1, w.t1_sec / totalSec),
             label: w.label,
           })),
         );
       })
       .catch(() => {
+        if (cancelled) return;
         if (windowsLoadedFor.current === setId) windowsLoadedFor.current = null;
         setWindows([]);
       });
-  }, [setId, totalSec]);
+    return () => {
+      cancelled = true;
+    };
+  }, [setId, totalSec, snapshot]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/app/`(dj)/setbuilder/components/CurvePanel.tsx around lines 162 -
183, The async getVibeWindows API call in the useEffect hook can result in stale
data being written to state if snapshot arrives or setId changes before the
promise resolves. Guard the setWindows call in the .then() callback by adding a
condition that checks whether the current setId still matches the one that
initiated the fetch, and similarly ensure the guard accounts for snapshot
changes. Use the existing windowsLoadedFor.current pattern or add a comparable
check to prevent the .then() callback from calling setWindows if the component
dependencies have shifted, ensuring only fresh data updates the state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dashboard/app/`(dj)/setbuilder/[setId]/page.tsx:
- Around line 93-103: The requestConfirmation function overwrites the
confirmResolver without resolving any existing pending promise, causing the
caller to hang indefinitely. Before creating a new promise in
requestConfirmation, check if confirmResolver is already set and if so, resolve
the pending confirmation by calling closeConfirm(false) to clean up the previous
state before setting the new action and resolver.

In `@dashboard/app/`(dj)/setbuilder/components/useSetDocumentHistory.ts:
- Around line 55-75: The useEffect hook in useSetDocumentHistory resets the
undo/redo stacks and autosave state when setId changes, but it does not reset
the snapshot/snapshotRef state. This creates a race condition where if a
mutation runs between the state reset and the new document fetch completing,
fetchCurrent() could use the previous set's snapshot as history input. Reset the
snapshot and snapshotRef state at the beginning of the useEffect hook (along
with the other state resets) to ensure the old snapshot is cleared before the
new fetch begins, preventing cross-set snapshot reuse.
- Around line 100-176: The commit, restore, and saveNow functions can execute
concurrently if the user rapidly triggers them (via keyboard shortcuts or button
clicks), causing interleaved state updates and inconsistent undo/redo stacks.
Add an in-flight guard using useRef to track whether an operation is currently
executing. At the start of each of these three functions, check the guard flag
and return early if an operation is already in progress. Set the guard to true
before performing async work and reset it to false in the finally block of each
function to ensure the lock is properly released even if errors occur.

In `@dashboard/app/`(dj)/setbuilder/setbuilder.module.css:
- Around line 748-749: The keyframe name `savePulse` uses camelCase but
stylelint requires kebab-case for keyframe names. Rename the keyframe from
`savePulse` to `save-pulse` in two locations: update the keyframe definition at
line 945 and update the animation reference in the animation property at line
748-749 to use `save-pulse` instead of `savePulse`.

In `@server/app/services/setbuilder/document_snapshot.py`:
- Around line 110-175: The code directly persists client-supplied ID values as
primary keys for SetPoolSource, SetPoolTrack, SetSlot, and SetCurvePoint
records, which allows ID collisions that cause integrity errors. Remove the id
parameter assignments from all four object creations to let the database
auto-generate primary keys. Maintain a mapping dictionary that tracks the
relationship between the snapshot's original source IDs and the newly generated
database IDs for sources. When creating SetPoolTrack records, replace the direct
track.source_id reference with a lookup into this mapping dictionary to use the
correct new source ID. Apply the same pattern for SetSlot by maintaining a track
ID mapping and using it to resolve track_id foreign key references. This ensures
foreign key consistency while preventing ID collision attacks.

In `@server/openapi.json`:
- Around line 7649-7716: The SetDocumentSnapshot-Input schema requires only
settings and pool, while SetDocumentSnapshot-Output requires all four fields
including curve_points and slots, breaking round-trip compatibility for the
replace-all snapshot API. Update the required fields array in
SetDocumentSnapshot-Input to include curve_points and slots alongside settings
and pool so the input schema matches the output schema requirements. After
making this change, regenerate all downstream types that depend on these
schemas.
- Around line 16683-16759: The GET and PUT endpoints for document snapshots
(operationId values
get_document_snapshot_api_setbuilder_sets__set_id__document_get and
put_document_snapshot_api_setbuilder_sets__set_id__document_put) are missing the
404 response definition in their responses objects. Add a 404 response entry to
both endpoints that references the HTTPValidationError schema with an
appropriate description indicating the set was not found or is not accessible,
ensuring the generated dashboard client has the complete error contract for
owner-isolation scenarios.

In `@server/tests/test_setbuilder_document_snapshot_api.py`:
- Around line 114-137: The test performs three destructive mutations (PATCH on
the target endpoint, DELETE on the sources endpoint, and PUT on the vibe-windows
endpoint) but never checks their response status codes. If any of these
operations fail, the subsequent restore assertions can still pass without
proving an actual mutate-then-restore round trip. Add status code assertions
(checking for 200 or appropriate success code) immediately after each of the
three destructive client calls, before the restore operation. Additionally, add
an assertion to verify that the restored settings match the original settings
(assert restored["settings"] == original["settings"]) alongside the existing
assertions for slots, pool, and curve_points to fully cover the snapshot
contract.

---

Outside diff comments:
In `@dashboard/app/`(dj)/setbuilder/components/CurvePanel.tsx:
- Around line 162-183: The async getVibeWindows API call in the useEffect hook
can result in stale data being written to state if snapshot arrives or setId
changes before the promise resolves. Guard the setWindows call in the .then()
callback by adding a condition that checks whether the current setId still
matches the one that initiated the fetch, and similarly ensure the guard
accounts for snapshot changes. Use the existing windowsLoadedFor.current pattern
or add a comparable check to prevent the .then() callback from calling
setWindows if the component dependencies have shifted, ensuring only fresh data
updates the state.

In `@dashboard/app/`(dj)/setbuilder/components/PoolPanel.tsx:
- Around line 87-101: The useEffect hook in PoolPanel.tsx initializes an async
api.getPool(setId) request without any cancellation mechanism. If the snapshot
dependency changes before the request completes, the stale response will
overwrite the pool state derived from the snapshot. Add an AbortController to
the api.getPool call and implement proper cleanup in the useEffect return
statement to cancel the pending request when dependencies change or the
component unmounts. Check whether the request was aborted before calling setPool
to prevent stale state updates.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 44892d82-7078-4661-b79e-a02850d58ed0

📥 Commits

Reviewing files that changed from the base of the PR and between ae6dad6 and 867b309.

📒 Files selected for processing (21)
  • dashboard/app/(dj)/setbuilder/[setId]/page.tsx
  • dashboard/app/(dj)/setbuilder/components/BuilderBrandMenu.tsx
  • dashboard/app/(dj)/setbuilder/components/BuilderSettingsModal.tsx
  • dashboard/app/(dj)/setbuilder/components/BuilderWorkspace.tsx
  • dashboard/app/(dj)/setbuilder/components/ConfirmActionDialog.tsx
  • dashboard/app/(dj)/setbuilder/components/CurvePanel.tsx
  • dashboard/app/(dj)/setbuilder/components/HistoryControls.tsx
  • dashboard/app/(dj)/setbuilder/components/ImportModal.tsx
  • dashboard/app/(dj)/setbuilder/components/PoolPanel.tsx
  • dashboard/app/(dj)/setbuilder/components/__tests__/ImportModal.test.tsx
  • dashboard/app/(dj)/setbuilder/components/__tests__/useSetDocumentHistory.test.tsx
  • dashboard/app/(dj)/setbuilder/components/useSetDocumentHistory.ts
  • dashboard/app/(dj)/setbuilder/setbuilder.module.css
  • dashboard/lib/api-types.generated.ts
  • dashboard/lib/api-types.ts
  • dashboard/lib/api.ts
  • server/app/api/setbuilder.py
  • server/app/schemas/setbuilder.py
  • server/app/services/setbuilder/document_snapshot.py
  • server/openapi.json
  • server/tests/test_setbuilder_document_snapshot_api.py

Comment thread dashboard/app/(dj)/setbuilder/[setId]/page.tsx
Comment thread dashboard/app/(dj)/setbuilder/components/useSetDocumentHistory.ts
Comment thread dashboard/app/(dj)/setbuilder/components/useSetDocumentHistory.ts Outdated
Comment thread dashboard/app/(dj)/setbuilder/setbuilder.module.css Outdated
Comment thread server/app/services/setbuilder/document_snapshot.py
Comment thread server/openapi.json
Comment thread server/openapi.json
Comment thread server/tests/test_setbuilder_document_snapshot_api.py Outdated
Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dashboard/app/`(dj)/setbuilder/components/BuilderWorkspace.tsx:
- Around line 81-95: The catch block in the loadSlots useCallback function
clears both pool and slots to empty arrays on fetch failure, which loses
workspace state even though a history snapshot is available for restoration.
Instead of clearing these states with setPool([]) and setSlots([]), restore the
workspace from the history snapshot that the component already receives, making
undo/redo restoration resilient to transient network failures. Replace the error
handler logic to use the snapshot as the restore source rather than clearing to
empty arrays.

In `@dashboard/app/`(dj)/setbuilder/setbuilder.module.css:
- Around line 511-553: The CSS selectors .confirmDialog, .confirmHeader,
.confirmIcon, and .confirmBody are defined twice in the file, causing styling
collisions where legacy positioning properties leak into the new modal
definition. Either rename the selectors in this block to unique names (e.g.,
.newConfirmDialog, .newConfirmHeader, .newConfirmIcon, .newConfirmBody) to avoid
the collision entirely, or if keeping the names, ensure this definition includes
explicit CSS resets (like resetting top, left, transform if they exist in the
legacy definition) to fully override all legacy positioning properties and
prevent misplacement of the ConfirmActionDialog modal.

In `@server/app/api/setbuilder.py`:
- Around line 712-737: The SetDocumentSnapshot schema used in the response_model
for get_document_snapshot and put_document_snapshot endpoints does not include
pairing state, which prevents pairings from being fully round-tripped through
undo/redo/autosave operations. Extend the SetDocumentSnapshot schema to include
a field for pairing state, update the document_snapshot.build_snapshot function
to serialize the pairing state from the set_obj when building the snapshot, and
update the document_snapshot.restore_snapshot function to deserialize and
restore the pairing state along with the other document state. The endpoint
response models will automatically work with the extended schema once these
changes are made.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 71027893-aad1-4694-b3c7-deccf76f714f

📥 Commits

Reviewing files that changed from the base of the PR and between 867b309 and 9b1c07c.

📒 Files selected for processing (21)
  • dashboard/app/(dj)/setbuilder/[setId]/page.tsx
  • dashboard/app/(dj)/setbuilder/components/BuilderBrandMenu.tsx
  • dashboard/app/(dj)/setbuilder/components/BuilderSettingsModal.tsx
  • dashboard/app/(dj)/setbuilder/components/BuilderWorkspace.tsx
  • dashboard/app/(dj)/setbuilder/components/ConfirmActionDialog.tsx
  • dashboard/app/(dj)/setbuilder/components/CurvePanel.tsx
  • dashboard/app/(dj)/setbuilder/components/HistoryControls.tsx
  • dashboard/app/(dj)/setbuilder/components/ImportModal.tsx
  • dashboard/app/(dj)/setbuilder/components/PoolPanel.tsx
  • dashboard/app/(dj)/setbuilder/components/__tests__/ImportModal.test.tsx
  • dashboard/app/(dj)/setbuilder/components/__tests__/useSetDocumentHistory.test.tsx
  • dashboard/app/(dj)/setbuilder/components/useSetDocumentHistory.ts
  • dashboard/app/(dj)/setbuilder/setbuilder.module.css
  • dashboard/lib/api-types.generated.ts
  • dashboard/lib/api-types.ts
  • dashboard/lib/api.ts
  • server/app/api/setbuilder.py
  • server/app/schemas/setbuilder.py
  • server/app/services/setbuilder/document_snapshot.py
  • server/openapi.json
  • server/tests/test_setbuilder_document_snapshot_api.py
💤 Files with no reviewable changes (1)
  • server/openapi.json
✅ Files skipped from review due to trivial changes (1)
  • dashboard/lib/api-types.generated.ts
🚧 Files skipped from review as they are similar to previous changes (15)
  • dashboard/app/(dj)/setbuilder/components/tests/ImportModal.test.tsx
  • dashboard/app/(dj)/setbuilder/components/tests/useSetDocumentHistory.test.tsx
  • dashboard/lib/api-types.ts
  • dashboard/app/(dj)/setbuilder/components/ConfirmActionDialog.tsx
  • dashboard/app/(dj)/setbuilder/components/HistoryControls.tsx
  • dashboard/app/(dj)/setbuilder/components/BuilderBrandMenu.tsx
  • dashboard/lib/api.ts
  • server/app/services/setbuilder/document_snapshot.py
  • dashboard/app/(dj)/setbuilder/components/BuilderSettingsModal.tsx
  • dashboard/app/(dj)/setbuilder/components/useSetDocumentHistory.ts
  • dashboard/app/(dj)/setbuilder/components/PoolPanel.tsx
  • server/tests/test_setbuilder_document_snapshot_api.py
  • server/app/schemas/setbuilder.py
  • dashboard/app/(dj)/setbuilder/components/ImportModal.tsx
  • dashboard/app/(dj)/setbuilder/components/CurvePanel.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dashboard/app/`(dj)/setbuilder/components/BuilderWorkspace.tsx:
- Around line 81-95: The catch block in the loadSlots useCallback function
clears both pool and slots to empty arrays on fetch failure, which loses
workspace state even though a history snapshot is available for restoration.
Instead of clearing these states with setPool([]) and setSlots([]), restore the
workspace from the history snapshot that the component already receives, making
undo/redo restoration resilient to transient network failures. Replace the error
handler logic to use the snapshot as the restore source rather than clearing to
empty arrays.

In `@dashboard/app/`(dj)/setbuilder/setbuilder.module.css:
- Around line 511-553: The CSS selectors .confirmDialog, .confirmHeader,
.confirmIcon, and .confirmBody are defined twice in the file, causing styling
collisions where legacy positioning properties leak into the new modal
definition. Either rename the selectors in this block to unique names (e.g.,
.newConfirmDialog, .newConfirmHeader, .newConfirmIcon, .newConfirmBody) to avoid
the collision entirely, or if keeping the names, ensure this definition includes
explicit CSS resets (like resetting top, left, transform if they exist in the
legacy definition) to fully override all legacy positioning properties and
prevent misplacement of the ConfirmActionDialog modal.

In `@server/app/api/setbuilder.py`:
- Around line 712-737: The SetDocumentSnapshot schema used in the response_model
for get_document_snapshot and put_document_snapshot endpoints does not include
pairing state, which prevents pairings from being fully round-tripped through
undo/redo/autosave operations. Extend the SetDocumentSnapshot schema to include
a field for pairing state, update the document_snapshot.build_snapshot function
to serialize the pairing state from the set_obj when building the snapshot, and
update the document_snapshot.restore_snapshot function to deserialize and
restore the pairing state along with the other document state. The endpoint
response models will automatically work with the extended schema once these
changes are made.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 71027893-aad1-4694-b3c7-deccf76f714f

📥 Commits

Reviewing files that changed from the base of the PR and between 867b309 and 9b1c07c.

📒 Files selected for processing (21)
  • dashboard/app/(dj)/setbuilder/[setId]/page.tsx
  • dashboard/app/(dj)/setbuilder/components/BuilderBrandMenu.tsx
  • dashboard/app/(dj)/setbuilder/components/BuilderSettingsModal.tsx
  • dashboard/app/(dj)/setbuilder/components/BuilderWorkspace.tsx
  • dashboard/app/(dj)/setbuilder/components/ConfirmActionDialog.tsx
  • dashboard/app/(dj)/setbuilder/components/CurvePanel.tsx
  • dashboard/app/(dj)/setbuilder/components/HistoryControls.tsx
  • dashboard/app/(dj)/setbuilder/components/ImportModal.tsx
  • dashboard/app/(dj)/setbuilder/components/PoolPanel.tsx
  • dashboard/app/(dj)/setbuilder/components/__tests__/ImportModal.test.tsx
  • dashboard/app/(dj)/setbuilder/components/__tests__/useSetDocumentHistory.test.tsx
  • dashboard/app/(dj)/setbuilder/components/useSetDocumentHistory.ts
  • dashboard/app/(dj)/setbuilder/setbuilder.module.css
  • dashboard/lib/api-types.generated.ts
  • dashboard/lib/api-types.ts
  • dashboard/lib/api.ts
  • server/app/api/setbuilder.py
  • server/app/schemas/setbuilder.py
  • server/app/services/setbuilder/document_snapshot.py
  • server/openapi.json
  • server/tests/test_setbuilder_document_snapshot_api.py
💤 Files with no reviewable changes (1)
  • server/openapi.json
✅ Files skipped from review due to trivial changes (1)
  • dashboard/lib/api-types.generated.ts
🚧 Files skipped from review as they are similar to previous changes (15)
  • dashboard/app/(dj)/setbuilder/components/tests/ImportModal.test.tsx
  • dashboard/app/(dj)/setbuilder/components/tests/useSetDocumentHistory.test.tsx
  • dashboard/lib/api-types.ts
  • dashboard/app/(dj)/setbuilder/components/ConfirmActionDialog.tsx
  • dashboard/app/(dj)/setbuilder/components/HistoryControls.tsx
  • dashboard/app/(dj)/setbuilder/components/BuilderBrandMenu.tsx
  • dashboard/lib/api.ts
  • server/app/services/setbuilder/document_snapshot.py
  • dashboard/app/(dj)/setbuilder/components/BuilderSettingsModal.tsx
  • dashboard/app/(dj)/setbuilder/components/useSetDocumentHistory.ts
  • dashboard/app/(dj)/setbuilder/components/PoolPanel.tsx
  • server/tests/test_setbuilder_document_snapshot_api.py
  • server/app/schemas/setbuilder.py
  • dashboard/app/(dj)/setbuilder/components/ImportModal.tsx
  • dashboard/app/(dj)/setbuilder/components/CurvePanel.tsx
🛑 Comments failed to post (3)
dashboard/app/(dj)/setbuilder/components/BuilderWorkspace.tsx (1)

81-95: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use snapshot as a restore source instead of clearing workspace on fetch failure.

On Line 93-94, a transient getSetSlots/getPool failure drops slots and pool to empty, even though this component already receives history snapshot. That makes undo/redo restoration brittle during network blips.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/app/`(dj)/setbuilder/components/BuilderWorkspace.tsx around lines
81 - 95, The catch block in the loadSlots useCallback function clears both pool
and slots to empty arrays on fetch failure, which loses workspace state even
though a history snapshot is available for restoration. Instead of clearing
these states with setPool([]) and setSlots([]), restore the workspace from the
history snapshot that the component already receives, making undo/redo
restoration resilient to transient network failures. Replace the error handler
logic to use the snapshot as the restore source rather than clearing to empty
arrays.
dashboard/app/(dj)/setbuilder/setbuilder.module.css (1)

511-553: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid selector collisions between legacy and current confirm dialog styles.

ConfirmActionDialog uses .confirmDialog, .confirmHeader, .confirmIcon, and .confirmBody, but those selectors are defined twice. The later block does not reset all earlier positioning fields, so legacy offsets can leak into the new modal and misplace it.

Suggested minimal fix
-.confirmDialog {
+.confirmWrap .confirmDialog {
   position: absolute;
   left: 50%;
   top: 45%;
   transform: translate(-50%, -50%);
   width: min(440px, calc(100vw - 2rem));
   overflow: hidden;
   border: 1px solid rgba(245, 158, 11, 0.35);
   border-radius: 12px;
   background: var(--card);
   box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
 }
 
-.confirmHeader {
+.confirmWrap .confirmHeader {
   display: flex;
   align-items: center;
   gap: 0.75rem;
   padding: 1rem 1.1rem 0;
 }
 
-.confirmIcon {
+.confirmWrap .confirmIcon {
   display: grid;
   place-items: center;
   width: 2.2rem;
   height: 2.2rem;
   border-radius: 10px;
   background: rgba(245, 158, 11, 0.12);
   color: `#f59e0b`;
   font-weight: 800;
 }
 
-.confirmBody {
+.confirmWrap .confirmBody {
   padding: 0.8rem 1.1rem 1rem;
   color: var(--text-secondary);
   font-size: 0.8125rem;
   line-height: 1.55;
 }

Also applies to: 1936-2066

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/app/`(dj)/setbuilder/setbuilder.module.css around lines 511 - 553,
The CSS selectors .confirmDialog, .confirmHeader, .confirmIcon, and .confirmBody
are defined twice in the file, causing styling collisions where legacy
positioning properties leak into the new modal definition. Either rename the
selectors in this block to unique names (e.g., .newConfirmDialog,
.newConfirmHeader, .newConfirmIcon, .newConfirmBody) to avoid the collision
entirely, or if keeping the names, ensure this definition includes explicit CSS
resets (like resetting top, left, transform if they exist in the legacy
definition) to fully override all legacy positioning properties and prevent
misplacement of the ConfirmActionDialog modal.
server/app/api/setbuilder.py (1)

712-737: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Include pairings in the snapshot contract used by document GET/PUT.

At Line 712 and Line 729, the endpoint contract is fixed to SetDocumentSnapshot, but that schema does not carry pairing state. Given the history requirements, pairing mutations cannot be fully round-tripped through undo/redo/autosave via this API surface. Extend the snapshot schema/service and this endpoint contract so pairings are serialized and restored with the rest of the document state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/app/api/setbuilder.py` around lines 712 - 737, The SetDocumentSnapshot
schema used in the response_model for get_document_snapshot and
put_document_snapshot endpoints does not include pairing state, which prevents
pairings from being fully round-tripped through undo/redo/autosave operations.
Extend the SetDocumentSnapshot schema to include a field for pairing state,
update the document_snapshot.build_snapshot function to serialize the pairing
state from the set_obj when building the snapshot, and update the
document_snapshot.restore_snapshot function to deserialize and restore the
pairing state along with the other document state. The endpoint response models
will automatically work with the extended schema once these changes are made.

@thewrz

thewrz commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator Author

CodeRabbit body-only items addressed in bf08d25: PoolPanel now ignores late getPool responses after snapshot or set changes, and CurvePanel now ignores late getVibeWindows responses under the same conditions.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
server/tests/test_setbuilder_document_snapshot_api.py (2)

130-130: ⚡ Quick win

Assert setup-call success before consuming response JSON.

Line [130], Line [191], and Line [238] consume .json() without first asserting expected status codes. Add explicit status assertions so failures are reported at the failing call site instead of surfacing later as downstream assertion/key errors.

Suggested hardening patch
-    original = client.get(f"/api/setbuilder/sets/{set_id}/document", headers=auth_headers).json()
+    original_resp = client.get(f"/api/setbuilder/sets/{set_id}/document", headers=auth_headers)
+    assert original_resp.status_code == 200, original_resp.json()
+    original = original_resp.json()
...
-    payload = client.get(f"/api/setbuilder/sets/{set_id}/document", headers=auth_headers).json()
+    payload_resp = client.get(f"/api/setbuilder/sets/{set_id}/document", headers=auth_headers)
+    assert payload_resp.status_code == 200, payload_resp.json()
+    payload = payload_resp.json()
...
     created = client.post(
         "/api/setbuilder/sets", json={"name": "Bad Snapshot"}, headers=auth_headers
     )
+    assert created.status_code == 201, created.json()
     set_id = created.json()["id"]

Also applies to: 191-191, 235-239

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/tests/test_setbuilder_document_snapshot_api.py` at line 130, Add
explicit status code assertions before consuming the response JSON at three
locations in the test file. At line 130 where the original variable is assigned
from the GET request to the document endpoint, at line 191, and at lines
235-239, insert status code assertions (likely assert_status_code(200) or
similar) immediately after each client call and before calling .json() on the
response. This ensures that request failures are caught and reported at the
failing call site rather than surfacing later as downstream errors when trying
to access missing keys in the response JSON.

195-207: ⚡ Quick win

Assert source_id remapping explicitly in the primary-key restore test.

Lines [195]-[207] verify regenerated row IDs, but they do not explicitly verify that restored tracks reference restored source IDs (not the injected client source_id). Adding this closes a key data-integrity assertion for the remap contract.

Suggested assertion addition
     restored = restore.json()
+    restored_source_ids = {source["id"] for source in restored["pool"]["sources"]}
     assert restored["slots"][0]["id"] != 9901
     assert restored["curve_points"][0]["id"] != 9902
     assert restored["pool"]["sources"][0]["id"] != 9903
     assert all(track["id"] != 9904 for track in restored["pool"]["tracks"])
+    assert all(track["source_id"] in restored_source_ids for track in restored["pool"]["tracks"])
+    assert all(track["source_id"] != 9903 for track in restored["pool"]["tracks"])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/tests/test_setbuilder_document_snapshot_api.py` around lines 195 -
207, The test in the file server/tests/test_setbuilder_document_snapshot_api.py
(lines 195-207) verifies that injected IDs for slots, curve_points, sources, and
tracks are regenerated, but it does not explicitly assert that the restored
tracks have their source_id remapped away from the injected value of 9903. Add
an assertion after the existing track assertions to verify that all restored
tracks do not have source_id equal to 9903, following the same pattern used for
the track id assertion (all(track["source_id"] != 9903 for track in
restored["pool"]["tracks"])). This closes the data-integrity verification gap
for the source_id remapping contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@server/tests/test_setbuilder_document_snapshot_api.py`:
- Line 130: Add explicit status code assertions before consuming the response
JSON at three locations in the test file. At line 130 where the original
variable is assigned from the GET request to the document endpoint, at line 191,
and at lines 235-239, insert status code assertions (likely
assert_status_code(200) or similar) immediately after each client call and
before calling .json() on the response. This ensures that request failures are
caught and reported at the failing call site rather than surfacing later as
downstream errors when trying to access missing keys in the response JSON.
- Around line 195-207: The test in the file
server/tests/test_setbuilder_document_snapshot_api.py (lines 195-207) verifies
that injected IDs for slots, curve_points, sources, and tracks are regenerated,
but it does not explicitly assert that the restored tracks have their source_id
remapped away from the injected value of 9903. Add an assertion after the
existing track assertions to verify that all restored tracks do not have
source_id equal to 9903, following the same pattern used for the track id
assertion (all(track["source_id"] != 9903 for track in
restored["pool"]["tracks"])). This closes the data-integrity verification gap
for the source_id remapping contract.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a2a906b2-def2-42a2-94f6-ec6053239f4c

📥 Commits

Reviewing files that changed from the base of the PR and between 9b1c07c and bf08d25.

📒 Files selected for processing (11)
  • dashboard/app/(dj)/setbuilder/[setId]/page.tsx
  • dashboard/app/(dj)/setbuilder/components/CurvePanel.tsx
  • dashboard/app/(dj)/setbuilder/components/PoolPanel.tsx
  • dashboard/app/(dj)/setbuilder/components/useSetDocumentHistory.ts
  • dashboard/app/(dj)/setbuilder/setbuilder.module.css
  • dashboard/lib/api-types.generated.ts
  • server/app/api/setbuilder.py
  • server/app/schemas/setbuilder.py
  • server/app/services/setbuilder/document_snapshot.py
  • server/openapi.json
  • server/tests/test_setbuilder_document_snapshot_api.py
✅ Files skipped from review due to trivial changes (1)
  • dashboard/lib/api-types.generated.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • dashboard/app/(dj)/setbuilder/components/PoolPanel.tsx
  • dashboard/app/(dj)/setbuilder/[setId]/page.tsx
  • server/app/schemas/setbuilder.py
  • server/app/api/setbuilder.py
  • server/openapi.json
  • dashboard/app/(dj)/setbuilder/components/useSetDocumentHistory.ts
  • dashboard/app/(dj)/setbuilder/components/CurvePanel.tsx
  • server/app/services/setbuilder/document_snapshot.py

@thewrz

thewrz commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator Author

CodeRabbit body-only test nitpicks addressed in 27fb1dc: added status assertions before consuming snapshot/setup response JSON and asserted restored tracks reference remapped source ids rather than the injected source id.

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.

WrzDJSet: Undo/redo, autosave, destructive-action confirmations

1 participant