Phase 13: Electron support (Vue 3 migration)#1070
Merged
Merged
Conversation
- client-v3 router: detect file:// protocol and use createWebHashHistory() so deep-linking and refresh work correctly when loaded via Electron - client-v3 App.vue: fix switchServer() to use router.push() instead of window.location.href (works in both hash and history modes); hide "Switch to Classic UI" link in Electron (no web server to switch to) - client-v3 platform/electron.ts: extend Window augmentation with full IPC surface (getAllConnections, addConnection, deleteConnection, setActiveConnection, checkVersion, discoverServersWithVersionCheck); export ElectronConnection, ElectronVersionCheckResult, ElectronDiscoveredServer interfaces - client-v3 ServerSelector.vue: full Vue 3 port of the V2 Options API component (saved connections, mDNS discovery, manual add, status polling) using script setup, Vuelidate 4, useConfirm, and MDI icons - client App.vue: hide "Switch to New UI" link in Electron (no /ui-new/ path exists in Electron build) - electron/main.ts: fix dev-mode static path (../client/dist-electron was resolving relative to electron/dist/, needed ../../client/dist-electron) Electron continues to serve the V2 frontend; V3 Electron support is fully scaffolded for the Phase 14 cutover. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Merged
Tim020
added a commit
that referenced
this pull request
May 24, 2026
* Vue 3 migration: Phase 0 — skeleton & dual-serving infrastructure (#1035) * Add Vue 3 migration skeleton (Phase 0) — issue #1033 Creates client-v3/ alongside the existing Vue 2 client/, serving a placeholder Vue 3 app at /ui-new/ via a new RootControllerV3. Both frontends build and serve independently. Backend: RootControllerV3 + /ui-new/assets/ and /ui-new/ handlers in app_server.py (registered before the Vue 2 catch-all route). client-v3 stack: Vue 3.5 / Pinia 3 / Vue Router 5 / Bootstrap-Vue-Next 0.45 / Vite 8 / ESLint 10 / TypeScript strict mode. CI: lint, typecheck, and test jobs added for client-v3 in nodelint.yml and client-test.yml. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix formatting --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Vue 3 migration: Phase 1 — core infrastructure (#1033) (#1037) * Add Vue 3 core infrastructure (Phase 1) — issue #1033 Pinia stores (user/auth, system/RBAC, websocket), useWebSocket() composable, HTTP interceptor, full router with beforeEach guard, platform/utils/logger ports, API types, constants, full BVN navbar App.vue, and stub views. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix BVN component rendering and visual parity with Vue 2 - Switch to BApp wrapper + unplugin-vue-components/BootstrapVueNextResolver for automatic per-component tree-shaken imports (no global plugin needed) - Add bootstrap-vue-next/dist/bootstrap-vue-next.css import in main.ts - Add data-bs-theme="dark" to BNavbar so text renders white on info background (Bootstrap 5 equivalent of Bootstrap 4's type="dark") - Add components.d.ts to tsconfig includes for GlobalComponents type augmentation - Gitignore components.d.ts (auto-generated by unplugin-vue-components on build) - Fix NotFoundView copy and centering to match Vue 2 404View exactly Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix npm ci lockfile sync for @emnapi/core and @emnapi/runtime These are transitive deps of @rolldown/binding-wasm32-wasi (cpu: wasm32). npm doesn't install the wasm32 binding on macOS arm64, so their lockfile entries were missing. Added as optionalDependencies to force inclusion. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lockfile: add @emnapi/core and @emnapi/runtime as explicit devDeps These are transitive deps of @rolldown/binding-wasm32-wasi (cpu: wasm32). npm skips that binding on macOS arm64 so its deps never get lockfile entries, causing npm ci to fail on Linux CI. Adding them explicitly to devDependencies forces npm to resolve and record their entries in the lockfile on all platforms. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Migrate Phase 2 read-only pages to Vue 3 (Home, About, Help) (#1038) - stores/help.ts: Pinia port of Vuex help module with manifest loading, document cache, and Fuse.js full-text search - components/MarkdownRenderer.vue: async marked v18 API via watch+ref; :deep() CSS selectors; import.meta.env.BASE_URL for portable help links across base paths - views/HomeView.vue: reads systemStore.currentShow/settings + userStore.currentUser; currentShowSession stubbed null until Phase 6 - views/AboutView.vue: static content port - views/HelpView.vue: BVN port with sticky sidebar, debounced search, dynamic navbar height offset - views/help/HelpDocView.vue: cache-first doc loading, watch route.params.slug - router: wire /about and /help routes; fix /help child redirect to absolute path; fix catch-all to render NotFoundView in-place (no URL redirect) matching Vue 2 behaviour Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Vue 3 migration: Phase 3 — authentication pages (Login, ForcePasswordChange) (#1039) - Port LoginView and ForcePasswordChangeView to BVN + @vuelidate/core v2 - Add useFormValidation and usePasswordValidation composables - Add changePassword() action to user store - Wire /force-password-change route to real component Bug fixes discovered during verification: - stores/user: authToken was a non-reactive getter reading localStorage, causing Pinia to cache null on first access and never re-evaluate; moved to reactive state field with _setToken/_clearToken actions keeping localStorage in sync - http-interceptor: exclude login endpoint from 401 handling to avoid logout cascade on bad credentials - stores/websocket: websocketHealthy getter incorrectly required authenticated=true; corrected to match Vue 2 behaviour (connection only) - views/LoginView: missing @submit.prevent on BForm caused native form submission - main.ts: import theme-sugar.css and create toast.ts singleton with position top-right; replace scattered useToast() calls with the shared instance Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Vue 3 migration: Phase 4 — user settings page (/me) (#1041) * Vue 3 migration: Phase 4 — user settings page (/me) - views/user/SettingsView.vue: 6-tab pill-vertical shell (About, Settings, Stage Direction Styles, Cue Colour Preferences, Change Password, API Token) - components/user/settings/AboutUser.vue: titleCase + sorted BTableSimple - components/user/settings/UserSettingsConfig.vue: 7-field settings form, PATCH /api/v1/user/settings, vuelidate with notNull/notNullAndGreaterThanZero - components/user/settings/ChangePassword.vue: 3-field form using usePasswordValidation, sends old_password for settings context - components/user/settings/ApiToken.vue: generate/regenerate/revoke with BModal confirmations, #append slot for copy button - components/user/settings/CueColourPreferences.vue: cue types fetched locally (show store not yet available), BModal ref pattern, contrastColor - components/user/settings/StageDirectionStyles.vue: stage direction styles fetched locally, v-model:pressed for toggle buttons, no Vue 3 filters - js/customValidators.ts: notNull, notNullAndGreaterThanZero validators - stores/user.ts: 8 CRUD actions for overrides, changePassword accepts oldPassword, logout clears cueColourOverrides - router/index.ts: /me wired to SettingsView (was PlaceholderView) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix BTabs content pane width in user settings page Add content-class="flex-fill" to BTabs so the tab pane fills the available horizontal space (BVN doesn't auto-fill unlike BV2). Also add w-100 to BTableSimple in AboutUser for full-width rendering. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Update migration plan: Phase 4 complete + BVN layout gotchas Mark Phase 4 as done. Add two BVN-specific notes to the reference section that will apply to all future phases: - BTabs vertical requires content-class="flex-fill" to fill width - BTableSimple requires class="w-100" to be full-width Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Remove plans/VUE3_MIGRATION_PLAN.md from git tracking File is covered by .gitignore and should not be committed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix V3 user settings visual parity with V2 - dark.scss: override body font to Avenir (matching V2's index.html inline style) and add .b-form-group margin-bottom (BVN dropped Bootstrap 4's .form-group built-in 1rem margin) - AboutUser.vue: wrap rows in <BTbody> so Bootstrap 5's deep child selector (.table > :not(caption) > * > *) matches and row borders render correctly - UserSettingsConfig.vue: label-cols="auto" → label-cols="4" for consistent 33% label column across all form rows - SettingsView.vue: content-class="flex-fill text-start" to fill horizontal space and reset text-align inherited from #app { text-align: center } Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Vue 3 migration: Phase 5 — system configuration page (/config) (#1042) * Migrate system configuration page to Vue 3 (Phase 5) (#1035) Ports the admin /config section — Shows, System, Settings, Users, Logs, Backups — from Vue 2 Vuex to Vue 3 Pinia with bootstrap-vue-next. Key patterns introduced: - BModal via ref<InstanceType<typeof BModal>> (replaces v-b-modal directive) - #footer slot (replaces #modal-footer in BVN) - window.confirm replaces $bvModal.msgBoxConfirm - SSE log streaming via Web Streams API (response.body.getReader()) - setTimeout-based polling with onBeforeUnmount cleanup - Dynamic Vuelidate rules computed from server-returned setting types - Cross-field date validators using helpers.withMessage Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add reusable ConfirmDialog composable to replace window.confirm Creates a singleton useConfirm() composable backed by a BModal-based ConfirmDialog component, replacing all window.confirm() calls with a styled, accessible modal dialog that matches the rest of the UI. ConfirmDialog is mounted once in App.vue; all callers share the same instance via module-level reactive state. Supports title, okVariant, okTitle, and cancelTitle options mirroring BV2's msgBoxConfirm API. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix ConfigView tab layout to match V2 (horizontal, lazy) V2 used plain horizontal tabs with lazy mounting, not the vertical pill layout carried over from the user settings page. Also restores lazy prop so tab content only mounts on first activation, avoiding all polling timers (sessions, users) starting simultaneously on page load. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix Settings accordion animation: use v-model instead of :visible on BCollapse BVN's BCollapse :visible prop sets localNoAnimation=true in useShowHide, bypassing the Bootstrap 5 collapsing transition entirely. Switching to :model-value/@update:model-value keeps animation enabled. Expanded state changed from string[] to Record<string, boolean> to support v-model binding per category key. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix Settings form control spacing by removing inline margin-bottom override Bootstrap 5 has no built-in .form-group margin; the global dark.scss adds margin-bottom: 1rem to .b-form-group, but the inline style="margin-bottom: 0" copied from V2 was overriding it, causing rows to appear compacted. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Vue 3 migration: Phase 6 — show configuration foundation (#1043) - Add stores/show.ts: full Pinia port of V2 show Vuex module (cast, characters, character groups, acts, scenes, cue types, sessions, microphones, mic allocations, script modes, session tags, stageManagerMode persisted via pinia-plugin-persistedstate) - Add js/micConflictUtils.ts: mic conflict detection utilities (ported from V2) - Add views/show/ShowConfigView.vue: sticky vertical nav sidebar + RouterView shell - Wire /show-config parent route to ShowConfigView.vue (was PlaceholderView) - Move scriptModes state/action from system.ts to show.ts; update ConfigShows.vue - Fill in GET_CAST_LIST, ELECTED_LEADER, NO_LEADER, SCRIPT_SCROLL WS action handlers Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Vue 3 migration: Phase 7 — show configuration basic tabs (#1044) Ports the four core show config tabs (Show, Acts & Scenes, Cast, Characters) to Vue 3 + BVN, using Pinia, Vuelidate, and vue-multiselect. - Add getShowDetails() + updateShow() actions to systemStore - Add useStatsTable() composable (port of statsTableMixin) - ConfigShow: detail table with titleCase keys + edit modal - ConfigActsAndScenes: Acts CRUD (linked-list order, loop validator) and Scenes CRUD (2-column layout, per-act previous/first scene) - ConfigCast: cast list CRUD + CastLineStats (dynamic act/scene cols) - ConfigCharacters: character CRUD + CharacterGroups (vue-multiselect) + CharacterLineStats - Wire four child routes in router; remaining placeholders unchanged Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Vue 3 migration: Phase 8 — Cues & Sessions config tabs (#1045) * Vue 3 migration: Phase 8 — cue types and session management (#phase8) Port ConfigCues (cue types CRUD + import + counts stats) and ConfigSessions (session list with start/stop + session tag CRUD + import) to Vue 3. Adds useCueDisplay composable, scriptRevisions state + getter + action to show store, and contrast-color type declarations. Cue Configuration tab deferred to Phase 11. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix getScriptRevisions API unwrapping and contrastColor strict-ESM crash getScriptRevisions was storing the raw API response object instead of response.revisions. The contrast-color library's standalone function uses `this.namedColors` internally which is undefined in strict ESM — replaced with an inline YIQ formula in utils.ts used by all session + cue components. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Replace explicit WS actionMap with convention-based Pinia store dispatch WS ACTION names (SCREAMING_SNAKE_CASE) are automatically routed to the matching camelCase action on any instantiated Pinia store, so adding a new store action is all that's needed to handle the corresponding WS event — no registration or map entries required. Only four special cases remain for actions that can't follow the naming convention: TOKEN_REFRESH, SHOW_CHANGED, USER_LOGOUT, WS_SETTINGS_CHANGED. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Vue 3 Migration Phase 9: Microphone Configuration (#1048) * Add Vue 3 migration Phase 9: Microphone configuration Ports the microphone configuration tab including CRUD, scene×character allocation grid with delta tracking and conflict display, SVG timeline with three view modes (mic/character/cast) and PNG export, scene density heatmap, and resource availability grid. Key fix: add `lazy` to BTabs to prevent BVN's eager tab rendering from causing MicAllocations to mount before parent data is loaded, which would leave internalState empty and break allocation toggling. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix ConfigMics tab loading: use v-if="loaded" instead of BTabs lazy BVN renders all tab panels simultaneously; wrapping the BTabs in a v-if="loaded" guard (with a spinner in v-else) prevents MicAllocations from mounting before the parent has fetched microphone data, matching the V2 pattern exactly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Vue 3 migration: Phase 10 — Stage configuration (#1049) Ports the Stage config tab from V2 to V3, including: - Crew CRUD (CrewList.vue) with first/last name fields - Scenery types + items CRUD (SceneryList.vue) with cascade delete warning - Prop types + items CRUD (PropsList.vue) - Stage Manager (StageManager.vue): scene navigation, allocation cards, SET/STRIKE boundary detection, crew assignments, orphan detection via blockOrphanUtils - SVG props/scenery timeline (StageTimeline.vue) with view mode toggle and PNG export - SVG crew timeline (CrewTimeline.vue) with hard/soft conflict detection - Timeline side panel (TimelineSidePanel.vue) for crew assignment editing - Pinia store (stores/stage.ts) with 8 state arrays, parameterised getters, and 28 actions; POST/PATCH requests use camelCase keys matching the API - blockOrphanUtils.ts + tests copied from V2 (pure TS, no Vue deps) - ConfigStage.vue shell with 5 tabs; router updated to replace PlaceholderView Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Vue 3 migration: Phase 11 — Script & Revisions configuration (#1057) * Vue 3 migration: Phase 11 — Script & Revisions configuration Ports the most complex config section of the Vue 2 UI to Vue 3/Pinia: - Stores: script.ts, scriptConfig.ts (with exported computePageStatus) - Composables: useScriptNavigation, useScriptDisplay - Utilities: scriptUtils.ts, mruSortUtils.ts (ported from V2) - Components: RevisionDetailModal, RevisionGraph (D3 hierarchy + zoom), ScriptRevisions (table + branch/load/delete modals), CompiledScripts, StageDirectionStyles (CRUD + import), ScriptLinePart, ScriptLineViewer, ScriptLineEditor, BulkActSceneModal, ScriptEditor (full non-collaborative editor) - Views: ConfigScript (Script + Stage Direction Styles tabs), ConfigScriptRevisions (Revisions + Compiled Scripts tabs) - Router: both show-config-script routes now point to real views Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix bugs found during Phase 11 browser testing - Replace structuredClone with JSON round-trip in scriptConfig store and ScriptLineEditor: Vue reactive Proxy objects (Pinia state) cannot be structuredCloned in some environments - Fix canRequestEdit/currentEditor field name mapping (backend returns camelCase, store was reading snake_case) - Reload current page after stopEditing() clears tmpScript so lines remain visible in view mode - Complete StyleForm render function in StageDirectionStyles with all form fields (description, bold/italic/underline toggles, text format select, text colour picker, background colour toggle + picker) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix sticky navbar and transparent sticky header in V3 script editor BVN's :sticky="true" generates class sticky-true (not Bootstrap's sticky-top), so the top navbar scrolled away instead of staying fixed. Replace with class="sticky-top" on BNavbar in App.vue. Also define --body-background in dark.scss as an alias for --bs-body-bg, so the script editor sticky header (and timeline components) have a solid background instead of transparent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Remove redundant hr below script sticky header The sticky header already has border-bottom via CSS, so the separate <hr /> created a double divider with extra gap below the header. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Phase 12: Live Show View (Vue 3 migration) (#1058) * Add Phase 12: Live Show View (Vue 3 migration) Ports the real-time live show execution view to Vue 3 + Pinia: - ShowLiveView.vue: session header, elapsed time, interval overlay with countdown, Splitpanes layout - ScriptViewPane.vue: compiled/page-by-page script loading, keyboard/wheel navigation, script leader mode, lazy page loading via MutationObserver, add-cue and start-interval modals - ScriptLineViewer.vue: normal mode with cue buttons, act/scene labels, interval banners, stage direction styling, cue position left/right support - ScriptLineViewerCompact.vue: compact 2-column mode with cues as rows - StageManagerPane.vue: scene list with setting/striking props/scenery/crew, auto-expand on session follow Adds cues state + loadCues/addNewCue actions to script store. Updates /live route from PlaceholderView to ShowLiveView. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix three live view bugs: session guard, splitpanes background, v-once collision - router: add proper /live session guard matching V2 — redirect home if no active session or WebSocket is unhealthy (was placeholder comment) - ShowLiveView: override splitpanes v4 pane background with correct specificity (:deep(.default-theme.splitpanes .splitpanes__pane)) so the dark body colour shows instead of the library's default light grey - ScriptViewPane: remove v-once from ScriptLineViewer/Compact in the double-nested v-for — Vue 3 shares _cache[] across all outer iterations so every page rendered page 1's cached VNodes, causing all 1275 line IDs to appear as page_1_* and making cross-page navigation impossible Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Wire show session into App.vue: fix navbar state and live redirect currentShowSession was hardcoded to null (Phase 6 placeholder), causing three bugs: Live nav item always disabled, System/Show Config items never hidden during sessions, and no auto-redirect to /live on initial load. - Import useShowStore and useRouter in App.vue - currentShowSession now reads from showStore.currentSession (reactive) - awaitWSConnect fetches session data after WS connects, then redirects to /live if a session is already active and the user isn't there yet (matches V2 App.vue awaitWSConnect behaviour) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix live redirect to use full path matching WS composable convention router.push('/live') with createWebHistory('/ui-new/') navigates to the V2 app at /live instead of the V3 app at /ui-new/live. The WS composable already uses the full path explicitly (router.push('/ui-new/live')) and checks router.currentRoute.value.path against '/ui-new/live'. Apply the same pattern in awaitWSConnect. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Revert live redirect path: useRouter() prepends base automatically router.push('/ui-new/live') via useRouter() causes double-base /ui-new/ui-new/live. The direct module import in useWebSocket.ts bypasses base handling and needs the full path; useRouter() in components does not. Revert to router.push('/live') with currentRoute path check against '/live'. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix cues not rendering: contrastColor called with object instead of string contrastColor(utils.ts) expects a plain string but the template passed { bgColor: '...' }. This threw TypeError inside the v-once BContainer block; Vue caught the error and cached the failed (empty) render for the cue column. Fix: call contrastColor(cueBackgroundColour(cue)) directly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add variant="success" to add-cue buttons in ScriptLineViewer Matches V2 styling — compact viewer already had this correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix V2 parity issues in live show view - Add Stage Manager toggle to Live Config navbar dropdown - Fix needsIntervalBanner to show at all act boundaries (not just interval_after=true) - Fix cue_position_right default to false (left) matching backend default - Block keyboard/wheel navigation when interval or cue modal is open - Move add-cue "+" button outside BButtonGroup for independent rounded corners - Add variant="success" to "+" button for correct green styling Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix START_SHOW/STOP_SHOW router push paths Vue Router 4 applies the base path (/ui-new/) internally regardless of whether the router is accessed via useRouter() or direct import. Using full paths like /ui-new/live caused double-prepending to /ui-new/ui-new/live. Also fix the currentRoute.value.path guard comparisons — the path property returns the route without the base prefix. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix V2 parity gaps found during pre-release audit - HomeView: wire currentShowSession to showStore.currentSession (was null placeholder) - App.vue: add CreateUser component to the no-admin-user setup screen - App.vue: call getRbacRoles() in awaitWSConnect so navbar RBAC is ready before first navigation - Router: /live route now requiresAuth: false (unauthenticated clients can join live view) - Router: already-logged-in redirect returns from.fullPath instead of / unconditionally - Router: /force-password-change gains requiresPasswordChange: true meta - Router: remove unused PlaceholderView import - stores/system: settingsChanged skips re-fetch when show ID unchanged; redirects away from /show-config and /live when no show is loaded (matching V2 behaviour) - stores/show: noLeader toast is now persistent (duration: 0) and dismissed when a leader is elected or getShowSessionData finds an active leader - useWebSocket: re-fetch show session data on reconnect after errors Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add cue assignment editor to Phase 12 (Cue Configuration tab) Ports the V2 CueEditor/ScriptLineCueEditor/JumpToCueModal trio to V3, completing the Cue Configuration tab previously showing a placeholder. - ScriptLineCueEditor: per-line cue display with add/edit/delete modals, RBAC-filtered cue type options, and duplicate-ident validation - JumpToCueModal: fuzzy cue search with exact/suggestion/no-match states - CueEditor: page navigator with localStorage persistence, Go To Page modal, and adjacent-page pre-fetching for smooth scrolling - script store: adds editCue, deleteCue, and searchCues actions - ConfigCues: replaces PlaceholderView with CueEditor in Cue Configuration tab Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix cue column visual parity: remove border-right, square add button - Remove border-right from .cue-column (V2 had no vertical divider line) - Move "+" button inside BButtonGroup (matches V2 behaviour) - Add .add-cue-button CSS to make the "+" button a square icon-sized element, matching V2's plus-square-fill icon appearance Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Use inline plus-square-fill SVG for add-cue button Replaces the variant="success" text "+" button with an inline SVG that faithfully reproduces V2's b-icon-plus-square-fill icon: default button variant (dark background in dark mode) with a 1em × 1em green filled- square icon, exactly matching the original cue column appearance. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix add-cue icon colour to match V2 (#06BC8C) SVG fill was using Bootstrap 5's default success green (#198754). V2's b-icon-plus-square-fill variant="success" resolved to the Bootswatch darkly success colour (#06BC8C), which is the correct value. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Restore 15px base font size to match V2 Bootswatch darkly Bootstrap 4 Bootswatch darkly sets $font-size-base: 0.9375rem (15px). Bootstrap 5 Bootswatch darkly drops this override, defaulting to 1rem (16px). Every rem-based size — line heights, paddings, margins, button heights — was 6.7% larger, reducing visible content per screen. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix navbar padding and font weight to match V2 Bootstrap 4 defaulted $navbar-padding-x to $spacer (1rem = 16px); Bootstrap 5 changed it to null → 0px, making navbar content flush to the viewport edge. Restore with $navbar-padding-x: 1rem. V2 App.vue had a global `nav a { font-weight: bold }` rule making the navbar brand and nav-links bold (fontWeight 700). V3 never carried this over. Add equivalent scoped to .navbar so tab nav-links are unaffected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix visual parity between V3 and V2 UI across all pages Resolves ~15 visual differences identified by page-by-page Playwright comparison to make the V3 migration look identical to V2. Global CSS (dark.scss SCSS variable overrides before Bootstrap imports): - border-radius: .375rem → .25rem (matching Bootstrap 4 default) - table-cell-padding: .5rem → .75rem (matching Bootstrap 4 default) - dropdown-item-padding-x: 1rem → 1.5rem (matching Bootstrap 4 default) - link-decoration: none (Bootstrap 5 Reboot adds underline by default) - Active navbar link: add #nav-collapse a.router-link-active teal colour - Vertical nav-pills: add full-width + centered rule for sidebar pills Per-component fixes: - ConfigActs: interval_after badge → check-square/x-square SVG icons - ScriptRevisions: ✓ span → check-square SVG; add Edit button; remove size="sm" from table buttons; replace text ▼/▲ with chevron SVG icons - CrewList: "Add Crew Member" → "New Crew Member" (match V2 label) - MicList: "Add Microphone" → "New Microphone" (match V2 label) - SessionTagDropdown: ✏️ emoji → inline pencil-fill SVG icon - ConfigCast: add explicit 'First Name'/'Last Name' labels (BVN auto-label only capitalises first word unlike BV2) - AboutUser: add text-center to match V2's centred table alignment Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Phase 12: Integrate unplugin-icons with Material Design Icons (#1059) * Phase 12: Integrate unplugin-icons with Material Design Icons Replaces all inline SVGs and Unicode text-character icon substitutes across the V3 UI with auto-imported MDI components (unplugin-icons + unplugin-vue- components resolver). Removes the bi and ph iconify packages after user chose MDI following a side-by-side preview page comparison. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Replace remaining text-character icon substitutes with MDI components - StageManagerPane: ▼/▶ expand indicators → IMdiChevronDown/IMdiChevronRight; 📌 pin emoji → IMdiPin - TimelineSidePanel: ⚠ Conflicts heading → IMdiAlert - RevisionGraph: +/-/↺ zoom buttons → IMdiMagnifyPlus/IMdiMagnifyMinus/IMdiRefresh (V2 used b-icon-zoom-in, b-icon-zoom-out, b-icon-arrow-clockwise) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Phase 12c: Dual-UI integration (#1061) * Phase 12c: Dual-UI integration (default UI setting, user preference, navbar toggle) - Dockerfile: split into parallel build_v2/build_v3 stages (BuildKit builds them concurrently); Python stage copies assets from both via --from directives - CI: add V3 install + build steps to build-server.yml frontend job - Backend: add default_ui system setting (choice: old/new, default old); when set to "new" the root "/" redirects to "/ui-new/" - Backend: add preferred_ui field to UserSettings model + CheckConstraint; Alembic migration 11311df29aa4 - V2 App.vue: "Switch to New UI" navbar link; post-login redirect to /ui-new/ when preferred_ui == "new" - V3 App.vue: "Switch to Classic UI" navbar link; post-login redirect to / when preferred_ui == "old" - V2 + V3 user settings pages: preferred_ui dropdown (system default / Classic / New) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix Phase 12c dual-UI integration bugs - RootController.get(): make async and await digi_settings.get() so the default_ui redirect actually evaluates; add _switch bypass - Both navbar toggle links now include ?_switch=1 so clicking them bypasses server-side and client-side redirects, preventing loops - V2 awaitWSConnect: also check system default_ui when preferred_ui is null, matching the full redirect logic from the plan - V2 Settings: add real isValidUi validator to preferred_ui so Vuelidate tracks it as a dirty-able field and enables Submit - V3 UserSettingsConfig: replace v$.$anyDirty with computed formDirty (JSON.stringify comparison) to avoid Vue 3 watcher async race - V2 vite.config: add cleanV2StaticPlugin to preserve server/static/ui-new/ when rebuilding V2; set emptyOutDir: false Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix V2 settings dirty tracking and cross-UI redirect loop - V2 Settings: replace $anyDirty (unreliable for nested Vuelidate v0 groups) with a computed formDirty using JSON.stringify comparison, matching the approach used in V3; also track savedSettings so Reset and Submit buttons correctly reflect unsaved changes - V3 App: redirect to /?_switch=1 (not /) when preferred_ui='old', preventing an infinite loop when default_ui='new' causes the server to immediately redirect / back to /ui-new/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Remove ?_switch=1 query param from URL after cross-UI navigation After the redirect decision has been made, use router.replace() with the current route path (base-stripped) to clean up the _switch param from the URL bar without adding a history entry. Uses $route.path (V2) and router.currentRoute.value.path (V3) rather than window.location.pathname to avoid Vue Router's base-path doubling in the V3 app (which is mounted at /ui-new/). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Phase 13: Electron support for Vue 3 frontend (#1070) - client-v3 router: detect file:// protocol and use createWebHashHistory() so deep-linking and refresh work correctly when loaded via Electron - client-v3 App.vue: fix switchServer() to use router.push() instead of window.location.href (works in both hash and history modes); hide "Switch to Classic UI" link in Electron (no web server to switch to) - client-v3 platform/electron.ts: extend Window augmentation with full IPC surface (getAllConnections, addConnection, deleteConnection, setActiveConnection, checkVersion, discoverServersWithVersionCheck); export ElectronConnection, ElectronVersionCheckResult, ElectronDiscoveredServer interfaces - client-v3 ServerSelector.vue: full Vue 3 port of the V2 Options API component (saved connections, mDNS discovery, manual add, status polling) using script setup, Vuelidate 4, useConfirm, and MDI icons - client App.vue: hide "Switch to New UI" link in Electron (no /ui-new/ path exists in Electron build) - electron/main.ts: fix dev-mode static path (../client/dist-electron was resolving relative to electron/dist/, needed ../../client/dist-electron) Electron continues to serve the V2 frontend; V3 Electron support is fully scaffolded for the Phase 14 cutover. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix CodeQL alert 1154 and add client-v3 to CI tooling (#1071) Guard convention-based Pinia dispatch with Object.hasOwn() to prevent accidental dispatch to prototype-inherited method names (CodeQL js/unvalidated-dynamic-method-call #1154). Add dependabot npm entry for /client-v3 and a client-v3 labeler rule so automated dependency updates and PR labels work once client-v3 lands in dev. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix SonarCloud reliability issues (PR #1036) (#1072) * Fix SonarCloud reliability issues for PR #1036 HIGH: Use run_in_executor for file read in async RootController.get() to avoid blocking the Tornado event loop (python:S7493). MEDIUM: Remove dead ternary in blankLine() — both branches returned [] (typescript:S3923). Associate form labels with their BFormInput controls via for/id attributes in ScriptViewPane.vue (Web:S6853, 3 instances). LOW/quality-gate: Replace bare parseInt() with Number.parseInt() across 12 client-v3 files (36 occurrences) to satisfy typescript:S7773 and improve the SonarCloud reliability rating from C toward A. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix negated ternary condition in ScriptEditor.vue (typescript:S7735) Flip storedPage != null ternary to non-negated form so the null/default case comes first: storedPage == null ? 1 : Number.parseInt(storedPage, 10). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: resolve SonarCloud HIGH and MEDIUM maintainability issues (#1073) * fix: resolve SonarCloud HIGH and MEDIUM maintainability issues Addresses 9 HIGH cognitive complexity (S3776), 8 deep nesting (S2004), 10 inner-function scope (S7721), 1 duplicate import (S3863), 8 JSON clone (S7784), and 3 CSS contrast (S7924) issues flagged on PR #1036. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: revert structuredClone in ScriptLineEditor — Vue reactive Proxy incompatible structuredClone cannot clone Vue reactive Proxy objects. ScriptLineEditor receives props.modelValue as a reactive proxy from the store, causing a DataCloneError at runtime. Revert to JSON round-trip (same pattern as scriptConfig.ts:74) with an explanatory comment. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: revert structuredClone in computePageStatus — Pinia reactive arrays cannot be cloned actualScriptPage, insertedLines, and tmpScriptPage all originate from Pinia reactive state (Proxy objects). structuredClone throws DataCloneError on these at runtime. Revert to JSON round-trip, matching the existing pattern at line 74. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address critical error handling issues from PR #1036 review (#1074) - TOKEN_REFRESH WS handler: fix data shape bug — handler received msg.DATA but was reading payload.DATA.access_token (i.e. msg.DATA.DATA), always undefined; corrected to (data as { access_token: string }).access_token - WS_AUTH_ERROR: add toast notification and logout call so the user is informed and redirected rather than left in a silently broken state - refreshToken(): wrap fetch in try/catch so network errors return false rather than propagating as unhandled rejections - login(): wrap post-login data fetches (getRbacRoles, getCurrentUser, getCurrentRbac, getUserSettings, setupTokenRefresh) in try/catch with token rollback to prevent half-logged-in ghost state - App.vue startup: wrap startup sequence in try/catch; add startupError state with a visible error message and Retry button instead of a permanent spinner on failure - getMaxPage(): return boolean success flag; saveScript() in ScriptEditor aborts with a toast if getMaxPage() fails rather than proceeding with potentially stale page count Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Post-merge review fixes: 401 queuing, revision toasts, ws.onopen, logout await, dropdown fix (#1075) * Post-merge review fixes: 401 queuing, revision toasts, onopen guard, logout await - Queue concurrent 401s during token refresh rather than immediately logging out; replay with the new token on success or resolve with a synthetic 401 on failure (http-interceptor.ts) - Parse server response body in addScriptRevision, deleteScriptRevision, loadScriptRevision error branches so 409 conflict messages reach the user (stores/show.ts) - Wrap ws.onopen async body in try/catch so getShowSessionData() rejection doesn't become an unhandled promise rejection (useWebSocket.ts) - Move Sign Out click handler to an awaited handleLogout() so async logout steps complete before the UI updates (App.vue) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix script line editor dropdown button and positioning The arrow toggle on each script line's Edit button had two bugs: 1. @click on BDropdown fell through to the root DOM element (BVN does not declare 'click' in its emits), so both the split button and the toggle arrow triggered editLine. Fixed by splitting into a BButtonGroup containing a plain BButton for 'Edit' and a standalone BDropdown for the context menu toggle. 2. boundary="window" applied BVN's position-static class to the BDropdown root, which changed the dropdown menu's offsetParent and caused Floating UI to compute a (0,0) transform — placing the menu at the top-left of the script editor container. Removed boundary="window"; without it the BDropdown root stays positioned and Floating UI anchors the menu correctly next to the toggle button. Same fix applied to the 'Add Dialogue' split dropdown in ScriptEditor. Verified via Playwright: Edit enters edit mode, toggle opens menu in the correct right-aligned position within the viewport. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix 88 SonarQube maintainability issues on Vue 3 migration PR (#1076) Mechanical fixes across 30 files in client-v3/: - S7765: indexOf() !== -1 → .includes() (ScriptLineViewer) - S7770: arrow fn equivalent to Boolean → Boolean (ScriptLineViewer) - S7755: [length - 1] → .at(-1) (blockOrphanUtils) - S7754: .find() existence check → .some() (ConfigActs, ConfigScenes, system) - S7784: JSON.parse/stringify → // NOSONAR where structuredClone can't be used on Pinia Proxy objects (scriptConfig, ScriptLineEditor) - S7746: return Promise.resolve() → return (CueEditor, ScriptViewPane) - S7723: Array() → new Array() (ScriptViewPane) - S3863: duplicate imports from same module merged (ScriptLinePart, CueColourPreferences) - S1128: unused imports removed — log (ScriptEditor), computed (StageDirectionStyles) - S7735: negated conditions flipped to positive form in 20+ locations across show, script, scriptConfig stores and multiple components - S6582: manual null guards replaced with optional chaining (logger, useScriptNavigation, useStatsTable, micConflictUtils, router) - S7721: _handleOk/_handleHidden moved to module scope (useConfirm) - S6571: redundant EntityType | string union → string (useTimeline) - S4325: unnecessary type assertion removed (user store) - S3358: nested ternary extracted to if/else (ScriptEditor, CrewTimeline) - S7735: show.ts byId getter pattern and orderedScenes traversal - S6582: router optional chain + negated condition (router/index) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix remaining SonarQube maintainability issues (round 2) (#1077) - Remove redundant return statements in CueEditor and ScriptViewPane - Replace .filter().length > 0 with .some() in system.ts (two more instances) - Fix WCAG AA contrast failures in App.vue (#e74c3c → #a93226, #00bc8c → #007a5e) - Use node: protocol for built-in imports in vite/vitest configs - Replace rgba backgrounds with solid equivalents in ResourceAvailability.vue - Extract "index.html" string literal to constant in controllers.py - Remove empty <style scoped> block from LoginView.vue - Mark ScriptCut type alias with NOSONAR Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
file://protocol at module load time and usescreateWebHashHistory()instead ofcreateWebHistory('/ui-new/')— required for Electron's local file loading where HTML5 history is unavailableApp.vue: FixesswitchServer()to userouter.push('/electron/server-selector')instead ofwindow.location.href(works correctly in both hash and history modes); hides "Switch to Classic UI" link in Electron (no web server serves/in Electron)platform/electron.ts: ExtendsWindowaugmentation with the full IPC surface (getAllConnections,addConnection,deleteConnection,setActiveConnection,checkVersion,discoverServersWithVersionCheck); exportsElectronConnection,ElectronVersionCheckResult,ElectronDiscoveredServerinterfaces for use inServerSelectorServerSelector.vue: Full Vue 3 port of the V2 component — saved connections list with live status badges, mDNS auto-discovery (15s interval), manual server add with version check, connection deletion, server status polling (30s interval). Usesscript setup,@vuelidate/core,useConfirm,toast, and MDI icons consistent with Phase 12bApp.vue: Hides "Switch to New UI" link in Electron (no/ui-new/path in Electron build)electron/main.ts: Fixes long-standing path bug in dev/start mode —__dirnameresolves toelectron/dist/so the path toclient/dist-electronrequired../../not../Electron continues to serve the V2 frontend for now. V3 is fully scaffolded for the Phase 14 cutover, at which point
electron/main.tsand thebuildscript will be pointed atclient-v3in a single coordinated change.Test plan
cd client-v3 && npm run typecheck— passescd client-v3 && npm run ci-lint— passes/ui-new/electron/server-selector— redirects to home (not Electron)/?_switch=1cd electron && npm run start): loads V2 frontend correctly🤖 Generated with Claude Code