Skip to content

Phase 12: Live Show View (Vue 3 migration)#1058

Merged
Tim020 merged 17 commits into
feature/vue3-migrationfrom
feature/vue3-migration-phase-12
May 19, 2026
Merged

Phase 12: Live Show View (Vue 3 migration)#1058
Tim020 merged 17 commits into
feature/vue3-migrationfrom
feature/vue3-migration-phase-12

Conversation

@Tim020
Copy link
Copy Markdown
Contributor

@Tim020 Tim020 commented May 18, 2026

Summary

  • Ports the real-time live show execution view to Vue 3 + Pinia (5 new components)
  • Ports the cue assignment editor to Vue 3 (3 new components, completing the Cue Configuration tab)
  • Fixes all V2 parity gaps identified during pre-release audit
  • Full visual parity pass: page-by-page Playwright comparison identified and resolved 15+ Bootstrap 4→5 visual differences

Live Show View

  • ShowLiveView.vue: session header bar, elapsed time counter, interval overlay with colour-coded countdown, Splitpanes layout with optional stage manager pane
  • ScriptViewPane.vue: compiled-script-first loading with page-by-page fallback, keyboard/wheel navigation (leader only), lazy page loading via MutationObserver, add-cue and start-interval modals (Vuelidate v2), WebSocket SCRIPT_SCROLL dispatch
  • ScriptLineViewer.vue: normal mode rendering — cue buttons with colour overrides, act/scene labels, interval banners at all act boundaries, stage direction styling, cue column left/right preference
  • ScriptLineViewerCompact.vue: compact 2-column mode with cues as prefix/ident rows above content
  • StageManagerPane.vue: ordered scene list with setting/striking props/scenery/crew per scene, auto-expands current + next scene tracking session follow data

Cue Assignment Editor (Cue Configuration tab)

  • ScriptLineCueEditor.vue: per-line cue display with add/edit/delete modals, RBAC-filtered cue type options (write-permission mask per type), and duplicate-ident validation
  • JumpToCueModal.vue: fuzzy cue search with exact/suggestion/no-match states and similarity % badge; auto-navigates on single exact match
  • CueEditor.vue: page navigator with localStorage('cueEditPage') persistence, Go To Page modal, and adjacent-page pre-fetching; replaces PlaceholderView in Cue Configuration tab

Store changes

  • stores/script.ts: adds cues state, cuesForLine getter, loadCues(), addNewCue(), editCue(), deleteCue(), and searchCues() actions
  • router/index.ts: replaces PlaceholderView for /live with ShowLiveView

Visual parity fixes (Bootstrap 4 → 5)

Global CSS (dark.scss SCSS variable overrides):

  • Border-radius: .375rem.25rem (BS4 default)
  • Table cell padding: .5rem.75rem (BS4 default)
  • Dropdown item padding: 1rem1.5rem (BS4 default)
  • Link decoration: none (BS5 Reboot adds underline)
  • Active navbar route: teal #00bc8c highlight (V2 dark.scss rule)
  • Vertical nav-pills: full-width + centered (BS4 block behaviour)

Per-component fixes:

  • ConfigActs: Interval After badge → check-square/x-square SVG icons (match V2)
  • ScriptRevisions: checkmark span → check-square SVG; add Edit button; remove size="sm"; chevron SVG collapse toggle
  • CrewList: "Add Crew Member" → "New Crew Member"
  • MicList: "Add Microphone" → "New Microphone"
  • SessionTagDropdown: ✏️ emoji → inline pencil-fill Bootstrap icon SVG
  • ConfigCast: explicit 'First Name'/'Last Name' labels (BVN auto-label only capitalises first word)
  • AboutUser: text-center + scope="row" to match V2's centred table

Bug fixes (V2 parity)

  • Session wiring: currentShowSession in HomeView.vue was hardcoded null — now wired to showStore.currentSession
  • Router push path: START_SHOW/STOP_SHOW WS handlers corrected for Vue Router 4 base handling
  • No-leader toast: persistent toast stored as handle; dismissed by electedLeader() and getShowSessionData()
  • v-once cache collision: Removed v-once from double-nested v-for
  • Cue rendering: Fixed contrastColor() call signature
  • Splitpanes background: Added :deep CSS override for dark-mode pane background
  • Stage Manager toggle: Added to Live Config navbar dropdown
  • Interval banner: Changed to show at all act boundaries (V2 behaviour)
  • cue_position_right default: Changed to false to match backend column default
  • Modal keyboard blocking: Arrow/Page/wheel navigation blocked when modals open
  • Reconnect refresh: ws.onopen re-fetches session data after reconnect

Test plan

  • npm run build — no errors
  • npm run typecheck — passes
  • npm run ci-lint — passes
  • npm run test:run — 23/23 pass
  • Page-by-page visual comparison (14 pages) — all major differences resolved
  • Browser at /ui-new/live — script loads, session header renders, cues display
  • Cue Configuration tab: navigation, modals, add/edit/delete all functional
  • No JavaScript console errors

🤖 Generated with Claude Code

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>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 18, 2026

Client V3 Test Results

23 tests  ±0   23 ✅ ±0   0s ⏱️ ±0s
 2 suites ±0    0 💤 ±0 
 1 files   ±0    0 ❌ ±0 

Results for commit 613dde5. ± Comparison against base commit c67ae7a.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 18, 2026

Client Test Results

128 tests  ±0   128 ✅ ±0   0s ⏱️ ±0s
  6 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit 613dde5. ± Comparison against base commit c67ae7a.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 19, 2026

Python Test Results

  1 files  ±0    1 suites  ±0   1m 35s ⏱️ +2s
603 tests ±0  603 ✅ ±0  0 💤 ±0  0 ❌ ±0 
608 runs  ±0  608 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 613dde5. ± Comparison against base commit c67ae7a.

♻️ This comment has been updated with latest results.

Tim020 and others added 16 commits May 19, 2026 10:00
…e 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>
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>
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>
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>
…tring

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>
Matches V2 styling — compact viewer already had this correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
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>
- 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>
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>
- 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>
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>
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>
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>
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>
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>
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
5.9% Duplication on New Code (required ≤ 3%)
B 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

@Tim020 Tim020 merged commit 2bb49ec into feature/vue3-migration May 19, 2026
16 of 17 checks passed
@Tim020 Tim020 deleted the feature/vue3-migration-phase-12 branch May 19, 2026 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant