Migrate to Vue 3 with Quasar v2 and vee-validate v4#348
Open
claude[bot] wants to merge 28 commits into
Open
Conversation
Thorough analysis of the Vue 2 codebase (359 components, 17 mixins, 17 Vuex modules) with a phased migration strategy using @vue/compat. Phase 0: Pre-migration on Vue 2 (remove filters, event bus, vue-cookies) Phase 1: Core dependency swap (Vue 3 + compat, Quasar v2, Vuex 4, Router 4) Phase 2: Stabilize dev build (fix Quasar v1->v2 breaks, re-enable plugins) Phase 3: Incremental migration (Composition API, composables, remove compat) https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
- Replace all 27 template filter pipes (| numeral) with formatNumber() method calls - Create src/utils/formatNumber.js utility wrapping numeral.js directly - Remove vue-numeral-filter plugin from boot/plugins.js and package.json - Remove unused capitalize filter from contribute/spell/index.vue - Replace Vue event bus (new Vue()) with mitt in src/event-bus.js - Update 5 consumer files to use mitt API (emit/on/off instead of $emit/$on/$off) - Remove vue-cookies (already fully migrated to Quasar Cookies plugin) - Add mitt and numeral as direct dependencies - SSR build verified passing https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
Upgrade core framework to Vue 3 with @vue/compat compatibility layer:
- vue 2.7 → 3.3, quasar 1.x → 2.x, vuex 3 → 4, vue-router 4
- @quasar/app → @quasar/app-webpack v3
- Rewrite quasar.conf.js → quasar.config.js (Quasar v2 format)
- Add @vue/compat configureCompat({ MODE: 2 }) boot file
- Rewrite store (createStore) and router (createRouter) for Vue 3 APIs
- Update all boot files: Vue.use()/Vue.component() → app.use()/app.component()
- Disable incompatible plugins: vee-validate, vue-shortkey, vue-snotify
- Remove vuefire (unused), vue-croppa (disabled), vue2-gtm (disabled)
- Fix <template v-for> key placement for Vue 3 compiler (22 files)
- Replace process.browser with typeof window !== 'undefined' (6 files)
- Remove dotenv from client bundle (patreon.js)
- Update ESLint: babel-eslint → @babel/eslint-parser, vue3-essential
- SPA dev server boots successfully with compat deprecation warnings
https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
When no .env.{NODE_ENV}.local file exists, dotenv returns undefined
for parsed, leaving webpack's DefinePlugin with no replacements.
webpack 5 doesn't provide a global process object, causing
"ReferenceError: process is not defined" in firebase.js.
Now falls back to .env.dist when the environment-specific file is missing.
https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
Comprehensive plan covering 7 ordered steps to stabilize the dev build: - Step 1: Fix .sync, ::v-deep, beforeDestroy, .native, $scopedSlots (59 files) - Step 2: Replace vue-snotify with Quasar Notify + Dialog (46 files) - Step 3: Replace vue-shortkey with custom Vue 3 directive (14 files) - Step 4: Upgrade splitpanes, vuedraggable, verify flicking/vue-qr - Step 5: Replace vue-croppa with vue-advanced-cropper (1 file) - Step 6: Migrate vee-validate v3 to v4 (51 files) - Step 7: Re-add Google Tag Manager (Vue 3 version) Based on full codebase audit: 73 snotify calls, 24 keyboard shortcuts, 51 vee-validate consumers, 41 v-deep occurrences, 12 confirm dialogs. https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
Replace deprecated star routes with the Vue Router 4 param syntax: - /encounters/* → /encounters/:pathMatch(.*)* - * → /:pathMatch(.*)* Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace .sync modifier with v-model: on q-table (6 files) - Replace ::v-deep with :deep() CSS combinator (38 files) - Replace beforeDestroy → beforeUnmount (6 files) - Replace destroyed → unmounted (2 files) - Remove .native event modifier from hk-popover (1 file) - Replace $scopedSlots → $slots (3 files) 51 files changed total. https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
…f/$emit - Replace all Vue.filter() usage with direct method calls and imported utilities - Replace Vue.set/$set with direct property assignment (reactive in Vue 3) - Replace Vue.delete/$delete with delete operator - Replace $on/$off/$emit event bus patterns with mitt event emitter - Update 46 files across components, views, and mixins - Net reduction of 512 lines by removing Vue 2 boilerplate https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
… and component updates - Replace Vue.set/Vue.delete with direct property assignment across all components and stores - Migrate vee-validate from v3 to v4 with composition API setup - Replace vue-snotify with Quasar Notify plugin for notifications - Add custom v-shortkey directive to replace vue-shortkey plugin - Update package.json dependencies for Vue 3 compatibility - Remove deprecated snotify CSS styles - Update store modules to use reactive patterns instead of Vue.set/Vue.delete https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
…t/$delete
- Migrate handleSubmit(callback) → handleSubmit($event, callback) for v4 in 13 files
- Fix { validate, valid } slot → { meta, validate } + meta.valid in 3 files
- Replace remaining errors[0] → errorMessage in ReminderForm.vue
- Remove all remaining $set/$delete calls across 23 files (124 occurrences)
- Update planning docs to mark Phase 2 as complete
https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
- Fix smart quote characters in confirmAction messages (Spells.vue, Monsters.vue) - Restore missing if-condition and fix brace mismatch in RunEncounter.vue watcher - Fix broken .then() chain in Profile.vue addVoucher method - Fix &:deep(.pane) SCSS nesting that Sass can't suffix in RunCampaign.vue - Run npm install to resolve missing declared dependencies https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
- Fix dotenv fallback in quasar.config.js (parsed:{} is truthy on missing
file, so .env.dist was never loaded and the app failed to boot)
- Default optional env keys (LOCAL_CHARACTER_SYNC_ID, MONSTER_GENERATOR_*)
so webpack always replaces process.env references
- Guard Cookies.parseSSR behind process.env.SERVER in App.vue preFetch
- Rename modelValue computed to inputModel in hk-input/hk-select (collided
with the prop of the same name)
- Fall back to a unique field name in hk-input/hk-select (vee-validate v4
Field crashes on undefined name)
- Migrate leftover $root.$on/$emit to mitt EventBus (crumble breadcrumb
+ 5 compendium views)
- Replace Vue 2 @egjs/vue-flicking with @egjs/vue3-flicking and opt it out
of compat mode
- Rename QTable :data to :rows (Quasar v2) in 18 occurrences
- Add .planning/VERIFICATION_REPORT.md with audit results, compat-debt
inventory, manual smoke-test checklist and acceptance criteria
https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
- ssr.pwa is now gated on ctx.prod. In dev, Workbox's GenerateSW was emitting a new service-worker.js on every HMR poll; skipWaiting + clientsClaim then claimed all open clients and forced a refresh, which triggered another build, repeat. PWA only matters in prod anyway. - mini-css-extract-plugin's 'Conflicting order' warnings are silenced via ignoreOrder: true. Every flagged module is a Vue scoped style (data-v-xxxxx attribute selectors), so cross-component specificity can't actually conflict and the warnings were pure noise.
No dependency version changes; npm 10.9.7 reshuffled 'peer' markers while verifying the build. https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
- Convert all deprecated slot="name"/slot-scope attributes to v-slot syntax across 120 template files (382 + 47 occurrences); verified by eslint vue/no-deprecated-slot-attribute = 0 and headless-browser audit - Remove all Vue.set/Vue.delete from 8 Vuex store modules (149 calls) — direct assignment/delete, splice for the one array case (state.log) - Remove template $set/$delete usages (trackCampaign, campaign share, EditCampaign) - Wrap async component registrations in defineAsyncComponent (boot file + 5 view/component files + Drawer.vue dynamic loader with markRaw) - Rename transition classes to Vue 3 -from suffixes (GenerateMonster) - Keep class/style on wrapper div in hk-input/hk-select (Vue 3 moves them into $attrs with inheritAttrs: false) - Fix broken v-else-if on slot template in ContentSideRight (agent interruption casualty), styles tag typo in SpellCreator, stale ValidationProvider import in hk-action-roll-form - Remove dead Vuex 3 store.js https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
- Add Vue 3 v-model contract (modelValue prop + update:modelValue emit) alongside the Vue 2 contract to 16 custom components, so the COMPONENT_V_MODEL compat flag can be disabled without touching every consumer; display-only value-prop components identified and left as-is - Add deep: true to the 3 array watchers that rely on in-place mutation (legacy Manual targeted, party xp/health allPlayers); other array watchers verified safe by reference replacement - Replace Vue 2-only vue-qr with qrcode.vue + CSS logo overlay in PlayerLink (RENDER_FUNCTION compat source) - Clear stale module declarations from typings (vue-shortkey, vue-qr, vue-numeral-filter all removed earlier) https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
- Replace 36 Vue 2 render(c) router passthroughs with a shared
{ render: () => h(RouterView) } component (the last render-function
compat dependency)
- Remove the vue → @vue/compat webpack alias, the compat boot file,
and the @vue/compat dependency
- Drop the Flicking compatConfig opt-outs (no compat runtime to opt
out of anymore)
Verified: production build succeeds; headless audit of 13 routes shows
zero Vue warnings and zero errors (only sandbox-environment network
noise). The app now runs on the standard vue@3 runtime.
https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
- Rewrite src-ssr for @quasar/app-webpack v3: middlewares (render, compression, api) + production-export.js replace the v1 extension.js/ index.js; /api routes fail soft when firebaseServiceAccountKey.json is absent so SSR keeps working without server credentials - Enable boot files server-side (component/directive registration and prototype extensions are needed during server render); GTM guarded to client, firebase-auth stays client-only - Fix hydration mismatches: framework config dark: true (template hard-codes body--dark), SSR-stable useId() for vee-validate fallback field names, and invalid <thead>/<tr> HTML nesting in 7 table files (browser reparenting breaks hydration) - Fix Vue 2-only is= usages: App.vue and character-descriptions to <component :is>, Initiative tbody to <transition-group tag="tbody">, campaign Players dynamic root restructured (named slots are dropped on dynamic plain elements in Vue 3) - Restore Quasar Meta: v1 meta() option no longer works in v2; wrapped in createMetaMixin — SSR now renders per-route <title>, OG/Twitter tags and canonical links again - Enable build.vueCompiler for runtime-compiled character descriptions Verified: quasar dev -m ssr and quasar build -m ssr both work; the production server renders real content with correct meta; SPA build still passes; headless hydration audit shows no functional mismatches. https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
Brings in NPC groups, campaign view refactor (#341), DM screen splitter persistence (#339), Set Initiative NPC button (#344), and monster filter changes (#345), re-expressed in Vue 3 syntax where main's code still used Vue 2 patterns: - slot="x" attributes -> v-slot templates (NpcGroupManager, BasicInfo, Npcs, Entities dialogs) - $set/$delete/Vue.set/Vue.delete -> direct assignment (npcGroups and npcs store modules, hk-filter, Entities, BasicInfo) - vue-snotify -> Quasar notify utils (NpcGroupManager, Entities, Npcs) - vee-validate v3 slot props -> v4 errorMessage API (BasicInfo) - QTable :data -> :rows; () => import() -> defineAsyncComponent - process.browser -> typeof window guard in browserDetect (app failed to boot server- and client-side otherwise) - adopted main's boot/prototypes.js (server-enabled for SSR) Additionally fixes a latent Quasar v2 event-contract gap the merge review surfaced: 101 @input handlers and 43 :value bindings on Quasar form components renamed to @update:model-value / :model-value (Quasar v2 components no longer emit input or read value; the handlers were dead and several bindings one-way only). Verified: SPA + SSR builds pass; headless audit of 10 routes renders with zero Vue warnings. https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
One-off dev script committed by accident during Phase 2; the migration is complete and the script flagged 2 high-severity path-injection findings in code scanning. https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
Reproduced SonarCloud's BUG-type rules locally with eslint-plugin-sonarjs over the full PR diff and fixed everything it found: - hk-card/hk-tip: identical || operands (the $scopedSlots → $slots rename made both sides the same expression; deduplicated) - trackCampaign/Meters: duplicate 'computed' option (merge artifact — JS silently discards the first block; npcSettings/allySettings merged into the single computed block so they work again) - hk-table: explicit boolean returns in the search filter callback - npcs store: explicit return in Promise.all map callback - Entities: ternary used as statement → if/else https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
Web:InputWithoutLabelCheck — the sole Major bug behind the Sonar Reliability gate. Adds id + associated label + aria-label to the hidden file input in hk-image-uploader. https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
npm 10 on Linux drops darwin-only optional deps from the lockfile when mutating the tree (npm/cli#4828); npm 11 in the node:24 Docker image then rejects the lock with 'Missing: fsevents@2.3.3'. Regenerated the resolution with npm 11 so the staging image builds again. Verified npm ci passes under both npm 10 and npm 11. https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
hk-pane forwards its slots through q-scroll-area with
v-bind="scope". When a forwarded slot is invoked without props,
scope is undefined and guardReactiveProps(undefined) returns null,
so renderSlot reads null.key and the whole campaign DM screen dies
with 'Cannot read properties of null (reading key)'. Bind scope || {}
like hk-select already does.
Not caught by the route audits because hk-pane only renders on the
auth-gated Run Campaign / Run Encounter screens.
https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
Documents the merge procedure, per-pattern conversion cheat sheet, git conflict-resolution strategies, the mandatory post-merge pattern sweep, and every runtime pitfall encountered during the migration and the 2.42.0 merge (dead Quasar @input handlers, duplicate component options, slot passthrough null scope, fsevents lockfile drops, SSR table nesting, stale service workers, reading minified stack traces). Also updates CLAUDE.md: tech stack now reflects Vue 3 / Quasar 2 / Webpack 5, references the guide, and adds the post-migration constraints (dual v-model contract, model-value events, npm 11 lockfile regeneration). https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
|
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.



What
The complete Vue 3 migration: Vue 3 + Quasar 2 + Vuex 4 + Vue Router 4 + vee-validate v4, with
@vue/compatfully removed and SSR re-enabled on the@quasar/app-webpackv3 architecture.Replaces #347 (same commits) — recreated so the long-lived migration branch has a proper name (
vue3-migration) and targetsdevelopper Git Flow.Highlights
is=usage); app runs on plainvue@3model-valueevents/props, QTablerows,createMetaMixin— restores SSR meta/OG tags that had been silently broken)origin/main2.42.0 merged in, with its Vue 2 feature code re-expressed in Vue 3VUE3_MIGRATION_GUIDE.mdTesting notes
Auth-gated screens (DM screen, run encounter, character builder) need manual staging verification — automated audits only cover public routes.
🤖 Generated with Claude Code
https://claude.ai/code/session_013pNZHMppjJ2iGVjxjHKkpQ
Generated by Claude Code