Conversation
Adds the backend foundation for collaborative script editing (Phase 1 Batch 1): - pycrdt dependency for CRDT/Yjs-compatible document sync - ScriptDraft model following CompiledScript file-pointer pattern - draft_script_path setting for draft file storage - ERROR_SCRIPT_DRAFT_ACTIVE constant for 409 conflict responses Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Chains from fbb1b6bd8707 (CrewAssignment). Creates script_drafts table with unique constraint on revision_id and CASCADE delete from script_revisions. Also fixes FK reference in ScriptDraft model (user table, not users). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 1 Batch 3 of collaborative editing: - line_to_ydoc.py: Two-phase conversion (main thread DB query + background thread Y.Doc construction) with selectinload for N+1 avoidance - script_room_manager.py: ScriptRoom (Y.Doc + client tracking + save lock) and RoomManager (lazy room creation, periodic checkpointing with atomic writes, idle eviction, stale draft cleanup) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 1 Batch 4 of collaborative editing: - WebSocket: JOIN_SCRIPT_ROOM, LEAVE_SCRIPT_ROOM, YJS_SYNC (2-step), YJS_UPDATE, YJS_AWARENESS handlers with base64 binary transport - on_close: Auto-remove client from collaborative editing rooms - App server: Initialize RoomManager on startup, create draft_script_path directory, clean up stale draft records and unreferenced files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
30 tests covering: - build_ydoc: empty script, single line, linked list traversal, multi-page, unordered input, multiple line parts, null handling - Base64 round-trip: full state and incremental updates - CRDT convergence: concurrent field edits, concurrent text inserts, offline edit and reconnect - ScriptRoom: client add/remove, sync state, apply update, broadcast with sender exclusion, failed write resilience, dirty tracking Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Client Test Results136 tests 136 ✅ 0s ⏱️ Results for commit cd4f20e. ♻️ This comment has been updated with latest results. |
Python Test Results 1 files 1 suites 1m 21s ⏱️ Results for commit cd4f20e. ♻️ This comment has been updated with latest results. |
Phase 2 Steps 2.1-2.4: - yjs and lib0 dependencies - ScriptDocProvider: custom Yjs provider using existing WebSocket connection with base64 binary transport and OP code messages - useYjsBinding: Vue 2.7 reactive bindings for Y.Map, Y.Text, Y.Array - scriptDraft Vuex module: room state, Y.Doc lifecycle, provider management, collaborator tracking - WebSocket message routing: Yjs OPs handled in SOCKET_ONMESSAGE and dispatched to HANDLE_DRAFT_MESSAGE Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…5-2.6) Add a dual-write bridge between Y.Doc and TMP_SCRIPT Vuex state so existing ScriptLineEditor/ScriptLinePart components continue to work unchanged while collaborative sync happens through the Y.Doc. - Create yjsBridge.js with Y.Doc↔TMP_SCRIPT conversion utilities - Join/leave draft room in ScriptEditor lifecycle hooks - Set up observeDeep on Y.Doc pages for remote change propagation - Use 'local-bridge' transaction origin to prevent observer loops - Wire add/delete/update line operations to Y.Doc - Sync from Y.Doc on page navigation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ScriptEditor.vue assumed CURRENT_REVISION was already populated in Vuex, but it's only loaded by the ScriptRevisions component on a different route. Adding GET_SCRIPT_REVISIONS to ScriptEditor's beforeMount ensures the revision ID is available for joining the collaborative editing room. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove updateYDocLine bridge function — components now write directly to Y.Map/Y.Text. ScriptLinePart gains yPartMap prop with: - @input handler for keystroke-level Y.Text sync - Y.Map writes for character/group dropdown changes - Y.Text and Y.Map observers for remote change handling - Lifecycle setup/teardown for observers Export nullToZero/zeroToNull from yjsBridge for component use. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ScriptLineEditor gains yLineMap prop with: - Y.Map writes for act_id, scene_id, stage_direction_style_id on change - yPartMap pass-through to ScriptLinePart children via getYPartMap() - addLinePart creates Y.Map structure in Y.Doc for new parts - Y.Map observer for remote changes to line-level fields - Lifecycle setup/teardown for observers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- lineChange() is now a no-op in collab mode (components write directly to Y.Map/Y.Text; observer handles TMP_SCRIPT sync) - Remove syncingFromYDoc guard flag and local-bridge origin skip from the Y.Doc deep observer — all changes flow through to TMP_SCRIPT - Add getYLineMap() and pass y-line-map prop to ScriptLineEditor - Rework add/delete/insert line operations: build complete lineObj (with inherited act_id/scene_id) before writing to Y.Doc - Extract addLineOfType() helper to reduce duplication across addNewLine, addStageDirection, addCueLine, addSpacing - Remove addLineToYDoc/deleteLineFromYDoc wrappers (inlined) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server broadcasts ROOM_MEMBERS on client join/leave/disconnect so all participants have an up-to-date collaborator list. Clients send YJS_AWARENESS messages with their current editing position (page and line index) which are relayed to other room members for line-level presence tracking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CollaboratorPanel shows room members with role badges and awareness tooltips in the sticky header. ScriptLineViewer gets a colored left border and username badge when another user is editing that line. Colors are deterministically assigned by user ID. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move Y.Doc and ScriptDocProvider out of Vuex reactive state into module-level variables. Vue 2 deeply observes all state objects, which caused it to track Y.Doc internal properties as reactive dependencies — triggering infinite re-renders after Yjs transactions. Also adds loop guards to nextActs/nextScenes linked-list traversals and updates _broadcastAwareness to use the new DRAFT_PROVIDER getter. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ecycle Remove single-editor lock so multiple users can co-edit via CRDTs. Add backend RBAC enforcement on REQUEST_SCRIPT_EDIT, REQUEST_SCRIPT_CUTS, and JOIN_SCRIPT_ROOM. Establish mutual exclusion between edit and cuts modes with is_cutting session flag. Close rooms when the last editor leaves (checkpoint + ROOM_CLOSED broadcast to viewers). Frontend: new getters for editor/cutter state, draft-aware cue editing, revision switching blocked during active editing, ROOM_CLOSED WS routing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Show tooltip explaining why the button is disabled (active editors, active cutter, or unsaved draft). Uses span wrappers with manual border-radius to preserve button-group joined styling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement SAVE_SCRIPT_DRAFT WebSocket handler and the full Y.Doc→DB save pipeline for collaborative script editing. Backend: - ydoc_to_lines.py: two-pass _save_script_page() — new lines (UUID _id), changed lines (new ScriptLine + migrate CueAssociation and ScriptCuts), unchanged (pointer-only updates), deleted lines (cleanup). Fix: populate new_line_id_map/new_part_id_map for changed lines so the Y.Doc is correctly patched with new DB ids after each save. - line_helpers.py: extracted validate_line() + create_new_line() shared helpers used by both REST API and WS save paths - script_room_manager.py: save_draft() with per-room asyncio.Lock; save_room() broadcasts SCRIPT_SAVED + triggers compilation; discard_room() + _delete_draft() for draft discard flow - ws_controller.py: SAVE_SCRIPT_DRAFT + DISCARD_SCRIPT_DRAFT handlers - web_decorators.py: no_active_script_draft decorator for REST endpoints - config.py: hasDraft uses room._dirty (unsaved changes only) - script.py: REST write endpoints protected by @no_active_script_draft; line creation/update delegates to line_helpers - yjs_debug.py: debug utility for Y.Doc state inspection Frontend: - yjsBridge.js: UUID _id for new lines/parts; deleteYDocLine() pushes real DB ids to deleted_line_ids before removing from Y.Array - ScriptLineEditor.vue: UUID for new parts in addPartToYDoc() - scriptDraft.js: SAVE_SCRIPT_DRAFT dispatch, SCRIPT_SAVED/SAVE_ERROR handling, saveError state - ScriptEditor.vue: resume/discard draft modal, save UX with error feedback, draft status indicator in toolbar Tests: 44 unit tests in test_ydoc_to_lines.py, save_draft/save_room integration tests, WS handler tests (all passing) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ws_controller: add missing `await` to both room.apply_update() call sites (YJS_SYNC step=2 and YJS_UPDATE). The method is async and acquires save_lock before mutating the Y.Doc; calling it without await meant the lock was never held and updates could interleave with an in-progress save. - web_decorators: no_active_script_draft now raises HTTP 409 (Conflict) instead of 400 to match the REST API Compatibility spec in the plan. - Save progress UI: SAVE_PROGRESS messages were never reaching the frontend — not in the HANDLE_DRAFT_MESSAGE whitelist in main.js. Added routing + savePage/saveTotalPages state in scriptDraft.js + DRAFT_SAVE_PROGRESS getter. ScriptEditor.vue now shows "Saving page X of Y (P%)" in the save toast, and the toast opens when isSaving turns true (not just for the user who clicked Save) so collaborating editors also see progress. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two-part fix for the bug where editing after navigating to page 80+ via incrPage/decrPage produced no YJS_UPDATE messages at the server. Round 1 — Frontend (client-side): - ScriptEditor.vue: incrPage() and decrPage() now call syncCurrentPageFromYDoc() after loading the new page, matching the existing goToPageInner() behaviour. Without this, TMP_SCRIPT held stale REST API data while Y.Doc had a different line count, causing getYLineMap() to return null and yPartMap to be null in ScriptLinePart. - Added diagnostic log.debug calls to getYLineMap(), setupYDocBridge() (ScriptEditor.vue), onTextInput() (ScriptLinePart.vue), and _onDocUpdate() (ScriptDocProvider.js) for future failure diagnosis. - ScriptDocProvider._onDocUpdate now explicitly guards and logs when suppressed due to !_connected or !_synced. - Added loglevel import to ScriptLinePart.vue. Round 2 — Backend data repair (root cause fix): - build_ydoc traverses the linked list from the head and halts at the first broken next_line_id. Cross-page stitching pointers were broken in production revisions, causing build_ydoc to omit all pages after the first break from the Y.Doc (while the REST API, which queries WHERE page=N directly, returned those pages correctly). - Migration c2f8d4a6e0b3: repairs all broken cross-page pointers using the page-walk algorithm; deletes true orphans. 24 pointer fixes and 4 orphan deletions across the affected revisions. - Migration d3e9f0c1a2b4: adds composite self-referential FK constraints on script_line_revision_association — (revision_id, next_line_id) and (revision_id, previous_line_id) both reference (revision_id, line_id), DEFERRABLE INITIALLY DEFERRED — to prevent future corruption. - ScriptLineRevisionAssociation.__table_args__ updated to declare the new constraints so tests and Alembic autogenerate stay in sync. - Diagnostic tool: server/utils/script/diagnose_linked_list.py. Tests added: - TestBuildYdocBrokenChain: regression tests for traversal-halts-at- broken-pointer behaviour in build_ydoc. - TestRevisionLinkedListFKConstraints: verifies composite FK constraints reject cross-revision pointers and accept valid chains. - test_branch_preserves_linked_list_integrity: walks the full linked list after branching to catch future regressions in pointer copying. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All 9 collaborative editing server messages now use {"OP": "NOOP", "ACTION": "<name>"}
so the existing framework ACTION dispatch path handles them automatically — no
special-case routing needed anywhere.
Backend:
- ws_controller.py: COLLAB_ERROR and YJS_SYNC (steps 0 & 2) use NOOP + ACTION
- script_room_manager.py: YJS_UPDATE, YJS_AWARENESS, ROOM_MEMBERS, SAVE_PROGRESS,
SAVE_ERROR, SCRIPT_SAVED, ROOM_CLOSED all use NOOP + ACTION
Frontend:
- main.js: remove 14-line collab OP whitelist (now redundant with NOOP + ACTION)
- websocket.js: remove 8 dead case stubs from SOCKET_ONMESSAGE switch
- ScriptDocProvider.js: remove handleMessage() router; replace private
_handleSync/Update/Awareness with public applySync/applyUpdate/applyAwareness
(each takes DATA payload directly with embedded connection + room_id guards)
- scriptDraft.js: replace HANDLE_DRAFT_MESSAGE with 9 individual Vuex actions —
one per message type; COLLAB_ERROR now properly logs server errors
Tests:
- Backend: all OP assertions updated to NOOP + ACTION (58 passing)
- New ScriptDocProvider.test.js: 13 tests for applySync/applyUpdate/applyAwareness
- New scriptDraft.test.js: 21 tests for all 9 individual Vuex actions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents load/create/delete revision operations from running while a collaborative edit session is active or an unsaved draft exists. Backend: - Add _revision_is_locked() helper (checks DB draft + in-memory room) - GET /revisions annotates each revision with has_draft field - POST /revisions returns 409 if parent revision is locked - POST /revisions/current returns 409 if current revision has active room - DELETE /revisions returns 409 if target revision is locked - Broadcast GET_SCRIPT_REVISIONS after every code path that creates or destroys a ScriptDraft (STOP_SCRIPT_EDIT, on_close, SAVE/DISCARD draft) - Add TestRevisionLifecycleGuards (8 tests); fix test_ws_controller for new GET_SCRIPT_REVISIONS message in checkpoint sequence Frontend: - Surface backend 409 error messages in revision action toast errors - Replace canChangeRevisions with targeted canLoadRevision / canDeleteRevision computed props and revisionHasDraft() method - Add v-b-tooltip.hover on span wrappers for disabled buttons; use '' not null so tooltips clear correctly when condition lifts - Add .btn-group-item CSS using :not(:last-child)/:not(:first-child) so solo Load button retains all rounded corners Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove the IS_DRAFT_ACTIVE && DRAFT_YDOC scaffolding guards added in Phase 2 for incremental development. Y.Doc is now the only editing path with no non-collab fallback. Frontend (ScriptEditor.vue): - Rename data.loaded → data.dataLoaded; add computed loaded property gated on IS_DRAFT_SYNCED so the loading spinner shows until Y.Doc sync completes (loaded = dataLoaded && (!IS_DRAFT_ACTIVE || IS_DRAFT_SYNCED)) - Delete lineChange() method and its @input binding; Y.Doc deep observer is the only sync path - Remove non-collab else-branches from addLineOfType(), deleteLine(), and insertLineAt() — all three now always write to Y.Doc directly - Remove IS_DRAFT_SYNCED guard from goToPageInner(), incrPage(), and decrPage() — syncCurrentPageFromYDoc() is always called - Simplify getYLineMap() — remove IS_DRAFT_ACTIVE check; retain DRAFT_YDOC and pageArray boundary null guards (still needed for the sync-window between IS_CURRENT_EDITOR and Y.Doc page population) - Remove ADD_BLANK_LINE, DELETE_LINE, INSERT_BLANK_LINE, SET_LINE from mapMutations (call sites deleted; mutations kept for Phase 8 cleanup) Backend (web_decorators.py): - Convert no_active_script_draft wrapper to async def - Return JSON 409 response (set_status + finish) instead of raising HTTPError, consistent with other 409 endpoints - After the existing DB draft check, also check the in-memory room manager so the decorator blocks REST writes within the first 30 seconds of a collab session (before the first checkpoint) Tests (test/utils/web/test_web_decorators.py): - 4 new tests covering no-draft pass-through, DB draft 409, in-memory room 409, and empty room pass-through Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Block collaborative editing write operations when a live show session is active. The guard prevents script edits from reaching the Y.Doc during a running show, with defence-in-depth on both server and client. Backend: - Add ERROR_EDIT_BLOCKED_BY_LIVE_SESSION constant (constants.py) - Add _is_live_session_active() async helper to WsController that checks current_session_id in settings and verifies the ShowSession exists in DB - Guard REQUEST_SCRIPT_EDIT and REQUEST_SCRIPT_CUTS before RBAC check; return REQUEST_EDIT_FAILURE with the new error constant - Guard JOIN_SCRIPT_ROOM, YJS_SYNC step 2, YJS_UPDATE, and SAVE_SCRIPT_DRAFT; return COLLAB_ERROR if live session active - Not blocked: LEAVE_SCRIPT_ROOM, YJS_SYNC step 1, YJS_AWARENESS, DISCARD_SCRIPT_DRAFT (none of these write to the Y.Doc) - 4 new tests in TestLiveSessionGuards (703 total backend tests) Frontend: - CAN_REQUEST_EDIT getter: return false when CURRENT_SHOW_SESSION is set - CAN_REQUEST_CUTS getter: return false when CURRENT_SHOW_SESSION is set - ScriptEditor: CURRENT_SHOW_SESSION in mapGetters; editDisabledReason and cutsDisabledReason surface live session message before other reasons - ScriptRevisions: canLoadRevision and canDeleteRevision check !CURRENT_SHOW_SESSION; load tooltip updated - New scriptConfig.test.js: 9 Vitest getter tests covering CAN_REQUEST_EDIT and CAN_REQUEST_CUTS under all combinations of live session and editor/cutter/draft state (136 total frontend tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eliminates TMP_SCRIPT as the intermediary view cache for the script editor. Y.Doc is now the sole source of truth during a draft session; components read directly from a plain-object snapshot (localPageScript) built by an observeDeep observer, avoiding Vue 2's reactivity hazard with Y.Map objects. Frontend changes: - ScriptEditor: replace TMP_SCRIPT-based rendering with localPageScript[] (plain JS objects rebuilt from Y.Doc on every change and page navigation) - ScriptLineEditor: replace previousLineFn/nextLineFn callbacks with plain previousLine/nextLine props; remove TMP_SCRIPT/ALL_DELETED_LINES getters - scriptConfig.js: remove all TMP_SCRIPT state, mutations, actions, getters - scriptDraft.js: add IS_DRAFT_DIRTY getter - yjsBridge.js: remove ydocLineToPlain() and syncPageFromYDoc() - CueEditor.vue, script.js: remove dead TMP_SCRIPT references Bug fixes from manual two-tab testing: - Fix blank page after Stop Editing: IS_DRAFT_ACTIVE watcher now calls LOAD_SCRIPT_PAGE when transitioning out of draft mode - Fix cut mode lines not clickable: canEdit now includes IS_CURRENT_CUTTER - Fix get_or_create_room race condition: two simultaneous JOIN_SCRIPT_ROOM messages for the same revision each created their own ScriptRoom; the second write to _rooms[revision_id] orphaned the first client so get_room_for_client() returned None and REQUEST_SCRIPT_EDIT silently skipped the role upgrade. Fixed with per-revision asyncio.Lock + double-checked locking in RoomManager.get_or_create_room(). Tests: 703 backend, 136 frontend — all passing. Linting clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Simplifies the collaborative editing implementation to match DigiScript's single-room reality (one show → one revision → one editing room at a time). Backend: - RoomManager: _rooms dict → single _room attribute; remove ROOM_IDLE_TIMEOUT, _eviction_handle, _evict_room(); add close_active_room() and has_unsaved_changes(); maintenance loop is checkpoint-only - ws_controller JOIN_SCRIPT_ROOM: server looks up current revision (client no longer sends revision_id); role read from Session.is_editor directly; room_id removed from all broadcast payloads; REQUEST_SCRIPT_CUTS uses has_unsaved_changes() - app_server _show_changed: calls close_active_room() before SHOW_CHANGED broadcast - config.py: async GET uses has_unsaved_changes() replacing duplicate draft check Frontend: - ScriptDocProvider: remove revisionId param, room_id from all messages, and room_id filter guards in applySync/applyUpdate/applyAwareness - scriptDraft.js: roomId state → isRoomActive boolean; JOIN_DRAFT_ROOM simplified - ScriptEditor.vue: JOIN_DRAFT_ROOM calls drop revisionId; loaded computed now shows spinner during IS_CURRENT_EDITOR && !IS_DRAFT_ACTIVE gap; onEditClick only shows resume/discard modal when no editors are already in the room Tests: 709 backend + 136 frontend passing; ruff + eslint clean Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…k lines - scriptConfig.js: inline the `reason` variable in REQUEST_EDIT_FAILURE - scriptDraft.js: remove description from _ydoc JSDoc (duplicates module docstring); remove all @type annotations and blank lines from state properties; remove blank lines between mutations, actions, and getters; remove superfluous docstrings from self-documenting action/getter members (LEAVE_DRAFT_ROOM, YJS_SYNC, YJS_UPDATE, YJS_AWARENESS, ROOM_MEMBERS, ROOM_CLOSED, SCRIPT_SAVED, SAVE_PROGRESS, SAVE_ERROR, COLLAB_ERROR, and all single-line getter @returns comments); remove inline comment from DRAFT_YDOC body that duplicated its docstring - ScriptDocProvider.js: rewrite last module-docstring paragraph to remove "no longer" historical language; remove superfluous _socket getter docstring Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove superfluous JSDoc/inline comments across ScriptDocProvider.js, yjsBridge.js, ScriptEditor.vue, ScriptLineEditor.vue, ScriptLinePart.vue, CollaboratorPanel.vue, and main.js - Extract collaborator colour generation to shared collabColors.js utility using HSL golden-angle hue spacing; remove hardcoded palettes from CollaboratorPanel.vue and ScriptLineViewer.vue - Add IS_DRAFT_LAST_SAVED and DRAFT_SAVE_ERROR getters to scriptDraft.js; refactor ScriptEditor.vue watcher to use getters instead of $store.state - Guard save toast display with IS_CURRENT_EDITOR so viewers don't receive save notifications - Add DELETE /api/v1/show/script/draft HTTP endpoint with RBAC; replace WebSocket + setTimeout in discardAndStartFresh() with synchronous HTTP call - Add discard_room_by_revision() to ScriptRoomManager for HTTP context Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove non-standard :statuscode: fields; use plain reST format consistent with the rest of the codebase. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove @param/@returns/@type JSDoc annotations and function-level JSDoc blocks that restate what the code already says. Retain module-level docblocks that document non-obvious architectural decisions (Vue 2 reactivity gotcha in scriptDraft, bidirectional sync pattern in useYjsBinding, WS protocol flow in ScriptDocProvider, Y.Doc schema in yjsBridge). Convert applySync JSDoc to plain // comments to preserve the step-0 behaviour explanation without the annotation noise. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove autosave system (ScriptEditor.vue, Settings.vue, UserSettings model, migration) - Convert confirmDiscardDraft to HTTP DELETE; add error branch to discardAndStartFresh - Remove loopCount guards from nextActs/nextScenes; remove superfluous comments - Remove blank lines between Vuex sections in scriptDraft.js - Fix double backtick in draft.py docstring - Rename _handle_collab_op -> _handle_script_room_op; move REQUEST_SCRIPT_EDIT/CUTS and STOP_SCRIPT_EDIT into the handler with own session blocks - Add 409 guard in shows.py POST when a draft is active - Simplify script_room_manager.py: get_active_room(), has_unsaved_changes(), discard_active_room(), remove close_room(); update all callers - Update tests to match new API signatures Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Safety and robustness fixes from consolidated review:
Backend:
- C-1: Guard on_message against JSON/key errors (malformed WS messages)
- C-2: Guard ScriptRoom.apply_update and send COLLAB_ERROR on failure
- C-3: Add finally to on_close _broadcast to guarantee close_active_room runs
- C-4: Wrap on_close DB cleanup in try/except to prevent silent failures
- C-5: Null-guard session entry in REQUEST_SCRIPT_EDIT/CUTS (race with on_close)
- C-8: Fix no_active_script_draft null guard for missing script/revision
- H-1: Add RBAC guards (editor check + Role.WRITE) to SAVE/DISCARD_SCRIPT_DRAFT
- H-2: Broadcast COLLAB_ERROR warning when show changes while draft is active
- H-3: Move blocking file I/O to run_in_executor in checkpoint/load/delete
- H-4/H-5: Distinguish WebSocketClosedError (debug) from unexpected errors (warning)
- H-6: Log notification failures in save_room instead of silently swallowing
- H-11: Notify clients via COLLAB_ERROR before discarding a corrupt draft file
- M-7: Remove manual self.on_close() call from write_message (Tornado handles it)
- M-8: Null-guard digi_settings.get("current_show") in on_close
- SQ-4: Add explanatory comments to intentionally empty Alembic migration
Frontend:
- C-6: Remove orphaned setupAutoSave() call in saveScript()
- C-7: Apply _unwrapYjsValue() to keyed initial population in useYMap()
- H-7: Surface sync timeout failure to user via SET_SYNC_ERROR + toast watcher
- H-8: Surface COLLAB_ERROR to user via SET_COLLAB_ERROR + toast watcher
- H-9: Fix discardAndStartFresh to only hide modal on success; add error toasts
- H-10: Show warning toast when getMaxScriptPage() fails
- M-3: Return false from applySync/applyUpdate/applyAwareness catch blocks
- M-9: Add null guards in _ydocLineToPlain for missing parts; wrap _syncLocalPageScript in try/catch
- M-13: Fix applyAwareness to return false for empty/failed payloads (was true)
All 709 backend tests and 136 frontend tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Resolved conflicts: - client/package.json: kept lib0 (Yjs dep) + updated lodash to 4.18.1 - client/package-lock.json: regenerated from resolved package.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|




No description provided.