Skip to content

fix(studio): save retries, mutation queue circuit breaker, save_failure diagnostics#1366

Open
miguel-heygen wants to merge 1 commit into
mainfrom
fix/studio-save-reliability
Open

fix(studio): save retries, mutation queue circuit breaker, save_failure diagnostics#1366
miguel-heygen wants to merge 1 commit into
mainfrom
fix/studio-save-reliability

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

Problem

Studio save failures can silently drop user work:

  • Code-editor saves fire a single PUT with no retry — any transient network error or oversized payload loses the edit.
  • The DOM-edit mutation queue has no circuit breaker, so a failing server burns through every queued mutation, producing bursts of failed saves.
  • Several failure paths (style/attribute DOM edits) only log to the console, so failures are invisible and undiagnosable.
  • Many commitMutation call sites (GSAP drag, property scrubbing, undo/redo, text fields) are unawaited, surfacing as unhandled promise rejections instead of reported errors.

Fix

  • Exponential-backoff retries on code-editor saves (useFileManager).
  • Circuit breaker on the DOM-edit save queue (utils/domEditSaveQueue.ts): repeated failures pause persistence and show a dismissible alert instead of draining the queue.
  • save_failure telemetry now carries error_message, status_code, and source on every emission path (utils/studioSaveDiagnostics.ts); style/attribute failures emit telemetry instead of only console-warning.
  • Safe wrapper (useSafeGsapCommitMutation) routes unawaited commit failures through telemetry + a user-facing toast rather than unhandledrejection.

Follow-ups (deferred to keep this reviewable)

  • Version/ETag conflict guard on file PUTs (concurrent-tab protection).
  • Offline save queue.

Testing

  • New unit tests for the save queue circuit breaker and save diagnostics.
  • Studio suite fully green (803 tests), typecheck clean, bun run build green, oxlint/oxfmt clean.

…re diagnostics

Save failures could silently drop user work: code-editor saves fired a single
PUT with no retry, DOM-edit failures drained the whole queue against a failing
server, and several failure paths only logged to the console.

- Retry code-editor saves with exponential backoff instead of dropping the
  edit on the first failed PUT.
- Circuit breaker on the DOM-edit save queue: a failing server pauses the
  queue with a user-visible error state instead of burning every queued
  mutation against it.
- save_failure events now carry error_message, status_code, and source on
  every emission path; style/attribute DOM-edit failures that previously only
  logged to the console now emit telemetry too.
- Route unawaited commitMutation call sites (GSAP drag, property scrubbing,
  undo/redo, text fields) through a safe wrapper that reports failures via
  telemetry instead of unhandledrejection.

Follow-ups (deferred): version/ETag conflict guard on file PUTs, offline
save queue.
@github-actions

Copy link
Copy Markdown

Fallow audit report

Found 49 findings.

Dead code (6)
Severity Rule Location Description
major fallow/unused-export packages/studio/src/components/editor/propertyPanelHelpers.ts:215 Export 'EMPTY_FILTER_VALUE' is never imported by other modules
major fallow/unused-export packages/studio/src/components/editor/propertyPanelHelpers.ts:216 Export 'BOX_SHADOW_PRESETS' is never imported by other modules
major fallow/unused-export packages/studio/src/components/editor/propertyPanelHelpers.ts:271 Export 'clampPanelNumber' is never imported by other modules
major fallow/unused-export packages/studio/src/components/editor/propertyPanelHelpers.ts:484 Export 'computeFitToChildrenSize' is never imported by other modules
major fallow/unused-export packages/studio/src/utils/domEditSaveQueue.ts:24 Export 'DomEditSaveQueueOpenError' is never imported by other modules
major fallow/unused-export packages/studio/src/utils/studioSaveDiagnostics.ts:78 Export 'getStudioSaveAttempt' is never imported by other modules
Duplication (22)
Severity Rule Location Description
minor fallow/code-duplication packages/studio/src/components/editor/EaseCurveEditor.tsx:11 Code clone group 1 (5 lines, 3 instances)
minor fallow/code-duplication packages/studio/src/components/editor/MotionPanelFields.tsx:9 Code clone group 2 (16 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/components/editor/MotionPanelFields.tsx:9 Code clone group 1 (5 lines, 3 instances)
minor fallow/code-duplication packages/studio/src/components/editor/manualEditsDom.ts:155 Code clone group 3 (7 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/components/editor/propertyPanelHelpers.ts:248 Code clone group 1 (5 lines, 3 instances)
minor fallow/code-duplication packages/studio/src/components/editor/propertyPanelHelpers.ts:248 Code clone group 2 (16 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/components/editor/propertyPanelHelpers.ts:319 Code clone group 3 (7 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/contexts/FileManagerContext.tsx:16 Code clone group 4 (41 lines, 3 instances)
minor fallow/code-duplication packages/studio/src/contexts/FileManagerContext.tsx:54 Code clone group 5 (44 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/contexts/FileManagerContext.tsx:55 Code clone group 4 (41 lines, 3 instances)
minor fallow/code-duplication packages/studio/src/hooks/gsapDragCommit.ts:37 Code clone group 6 (12 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/useAnimatedPropertyCommit.ts:43 Code clone group 6 (12 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/useDomEditTextCommits.ts:227 Code clone group 7 (9 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/useDomEditTextCommits.ts:290 Code clone group 7 (9 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/useFileManager.ts:107 Code clone group 8 (8 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/useFileManager.ts:154 Code clone group 8 (8 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/useFileManager.ts:346 Code clone group 9 (31 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/useFileManager.ts:432 Code clone group 9 (31 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/useFileManager.ts:500 Code clone group 5 (44 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/useFileManager.ts:502 Code clone group 4 (41 lines, 3 instances)
minor fallow/code-duplication packages/studio/src/hooks/useGsapScriptCommits.ts:426 Code clone group 10 (34 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/useGsapScriptCommits.ts:469 Code clone group 10 (34 lines, 2 instances)
Health (21)
Severity Rule Location Description
minor fallow/high-crap-score packages/studio/src/components/editor/PropertyPanel.tsx:165 'commitManualOffset' has CRAP score 37.1 (threshold: 30.0, cyclomatic 11)
major fallow/high-crap-score packages/studio/src/components/editor/PropertyPanel.tsx:271 '<arrow>' has CRAP score 97.0 (threshold: 30.0, cyclomatic 19)
major fallow/high-crap-score packages/studio/src/components/editor/propertyPanelHelpers.ts:484 'computeFitToChildrenSize' has CRAP score 56.3 (threshold: 30.0, cyclomatic 14)
minor fallow/high-cognitive-complexity packages/studio/src/components/editor/propertyPanelHelpers.ts:518 'readGsapRuntimeValuesForPanel' has cognitive complexity 24 (threshold: 15)
minor fallow/high-crap-score packages/studio/src/hooks/useAnimatedPropertyCommit.ts:39 'computePercentage' has CRAP score 49.5 (threshold: 30.0, cyclomatic 13)
minor fallow/high-crap-score packages/studio/src/hooks/useAnimatedPropertyCommit.ts:95 'commitAnimatedProperty' has CRAP score 31.6 (threshold: 30.0, cyclomatic 10)
minor fallow/high-crap-score packages/studio/src/hooks/useAppHotkeys.ts:149 'handleUndo' has CRAP score 42.0 (threshold: 30.0, cyclomatic 6)
minor fallow/high-crap-score packages/studio/src/hooks/useAppHotkeys.ts:174 'handleRedo' has CRAP score 42.0 (threshold: 30.0, cyclomatic 6)
critical fallow/high-crap-score packages/studio/src/hooks/useAppHotkeys.ts:261 '<arrow>' has CRAP score 6162.0 (threshold: 30.0, cyclomatic 78)
minor fallow/high-crap-score packages/studio/src/hooks/useAppHotkeys.ts:499 'syncPreviewTimelineHotkey' has CRAP score 30.0 (threshold: 30.0, cyclomatic 5)
major fallow/high-crap-score packages/studio/src/hooks/useAppHotkeys.ts:549 'syncPreviewHistoryHotkey' has CRAP score 72.0 (threshold: 30.0, cyclomatic 8)
major fallow/high-crap-score packages/studio/src/hooks/useDomEditTextCommits.ts:78 'handleDomStyleCommit' has CRAP score 71.3 (threshold: 30.0, cyclomatic 16)
major fallow/high-crap-score packages/studio/src/hooks/useDomEditTextCommits.ts:209 'handleDomTextCommit' has CRAP score 71.3 (threshold: 30.0, cyclomatic 16)
major fallow/high-crap-score packages/studio/src/hooks/useDomEditTextCommits.ts:277 'commitDomTextFields' has CRAP score 71.3 (threshold: 30.0, cyclomatic 16)
minor fallow/high-crap-score packages/studio/src/hooks/useDomEditTextCommits.ts:332 'handleDomTextFieldStyleCommit' has CRAP score 31.6 (threshold: 30.0, cyclomatic 10)
major fallow/high-crap-score packages/studio/src/hooks/useFileManager.ts:89 '<arrow>' has CRAP score 56.0 (threshold: 30.0, cyclomatic 7)
minor fallow/high-crap-score packages/studio/src/hooks/useFileManager.ts:117 'writeProjectFile' has CRAP score 30.0 (threshold: 30.0, cyclomatic 5)
major fallow/high-crap-score packages/studio/src/hooks/useFileManager.ts:244 'openSourceForSelection' has CRAP score 56.0 (threshold: 30.0, cyclomatic 7)
critical fallow/high-crap-score packages/studio/src/hooks/useFileManager.ts:287 'uploadProjectFiles' has CRAP score 182.0 (threshold: 30.0, cyclomatic 13)
critical fallow/high-crap-score packages/studio/src/hooks/usePreviewPersistence.ts:114 'applyCurrentStudioManualEditsToPreview' has CRAP score 110.0 (threshold: 30.0, cyclomatic 10)
minor fallow/high-crap-score packages/studio/src/hooks/usePreviewPersistence.ts:178 '<arrow>' has CRAP score 30.0 (threshold: 30.0, cyclomatic 5)

Generated by fallow.

Comment on lines +127 to +132
const response = await fetch(`/api/projects/${pid}/files/${encodeURIComponent(path)}`, {
method: "PUT",
headers: { "Content-Type": "text/plain" },
body: content,
signal: controller.signal,
});
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.

2 participants