Skip to content

Vue 3 migration#1036

Draft
Tim020 wants to merge 15 commits into
devfrom
feature/vue3-migration
Draft

Vue 3 migration#1036
Tim020 wants to merge 15 commits into
devfrom
feature/vue3-migration

Conversation

@Tim020
Copy link
Copy Markdown
Contributor

@Tim020 Tim020 commented May 13, 2026

No description provided.

)

* 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>
@github-actions github-actions Bot added github GitHub actions related issue or pull request server Pull requests changing back end code xlarge-diff git labels May 13, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 13, 2026

Client V3 Test Results

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

Results for commit 2a89e5b.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 13, 2026

Client Test Results

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

Results for commit 2a89e5b. ± Comparison against base commit 9750f23.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 13, 2026

Python Test Results

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

Results for commit 2a89e5b. ± Comparison against base commit 9750f23.

♻️ This comment has been updated with latest results.

@Tim020 Tim020 linked an issue May 13, 2026 that may be closed by this pull request
* 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>
@github-actions github-actions Bot added the client Pull requests changing front end code label May 14, 2026
Comment thread client-v3/src/composables/useWebSocket.ts Fixed
Tim020 and others added 7 commits May 14, 2026 22:00
- 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>
…Change) (#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)

- 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>
* 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>
- 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>
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 — 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>
const storeMap = (pinia as unknown as { _s: Map<string, Record<string, unknown>> })._s;
for (const store of storeMap.values()) {
if (typeof store[camelAction] === 'function') {
await (store[camelAction] as (d: Record<string, unknown>) => Promise<void>)(data);
Tim020 and others added 3 commits May 17, 2026 22:42
* 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>
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

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>
@github-actions github-actions Bot removed github GitHub actions related issue or pull request client Pull requests changing front end code server Pull requests changing back end code git labels May 18, 2026
Tim020 and others added 3 commits May 18, 2026 23:53
* 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

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

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
6.8% Duplication on New Code (required ≤ 3%)
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate front end to Vue.js 3.x

2 participants