Skip to content

Collaborative Script Editing#905

Open
Tim020 wants to merge 52 commits intodevfrom
feature/collaborative-editing
Open

Collaborative Script Editing#905
Tim020 wants to merge 52 commits intodevfrom
feature/collaborative-editing

Conversation

@Tim020
Copy link
Contributor

@Tim020 Tim020 commented Feb 11, 2026

No description provided.

Tim020 and others added 5 commits February 11, 2026 15:06
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>
@Tim020 Tim020 added the claude Issues created by Claude label Feb 11, 2026
@Tim020 Tim020 linked an issue Feb 11, 2026 that may be closed by this pull request
@github-actions github-actions bot added server Pull requests changing back end code xlarge-diff labels Feb 11, 2026
@github-actions
Copy link

github-actions bot commented Feb 11, 2026

Client Test Results

136 tests  +37   136 ✅ +37   0s ⏱️ ±0s
  7 suites + 3     0 💤 ± 0 
  1 files   ± 0     0 ❌ ± 0 

Results for commit 899e42c. ± Comparison against base commit 59368b5.

♻️ This comment has been updated with latest results.

@github-actions
Copy link

github-actions bot commented Feb 11, 2026

Python Test Results

  1 files  ±  0    1 suites  ±0   1m 22s ⏱️ +13s
709 tests +147  709 ✅ +147  0 💤 ±0  0 ❌ ±0 
714 runs  +147  714 ✅ +147  0 💤 ±0  0 ❌ ±0 

Results for commit 899e42c. ± Comparison against base commit 59368b5.

♻️ This comment has been updated with latest results.

Tim020 and others added 6 commits February 11, 2026 15:26
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>
@github-actions github-actions bot added the client Pull requests changing front end code label Feb 11, 2026
Tim020 and others added 4 commits February 11, 2026 19:27
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>
Tim020 and others added 7 commits February 15, 2026 14:46
…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>
@github-actions github-actions bot added the idea label Feb 21, 2026
Tim020 and others added 16 commits February 23, 2026 12:32
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>
Tim020 and others added 4 commits March 4, 2026 02:12
…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>
Tim020 and others added 2 commits March 5, 2026 00:00
- 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>
@Tim020 Tim020 marked this pull request as ready for review March 5, 2026 00:19
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>
@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 5, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
4.2% Duplication on New Code (required ≤ 3%)
E Security Rating on New Code (required ≥ A)
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude Issues created by Claude client Pull requests changing front end code idea server Pull requests changing back end code xlarge-diff

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Collaborative script editing

1 participant