Skip to content

feat(build): migrate to Vue 3 + bootstrap-vue-next#773

Merged
ErikBjare merged 9 commits intoActivityWatch:vue3from
TimeToBuildBob:bob/vue3-migration
Mar 23, 2026
Merged

feat(build): migrate to Vue 3 + bootstrap-vue-next#773
ErikBjare merged 9 commits intoActivityWatch:vue3from
TimeToBuildBob:bob/vue3-migration

Conversation

@TimeToBuildBob
Copy link
Copy Markdown
Contributor

@TimeToBuildBob TimeToBuildBob commented Feb 27, 2026

Summary

Phase 1 of Vue 3 migration (requested in #772):

  • Vue: 2.7 → 3.5
  • Vue Router: 3.x → 4.x (createRouter, createWebHashHistory)
  • Bootstrap: 4.6 → 5.3
  • bootstrap-vuebootstrap-vue-next (0.25)
  • Build: Remove all @vue/cli-* / webpack deps, keep Vite only
  • FullCalendar: @fullcalendar/vue@fullcalendar/vue3 (v6)
  • vue-chartjs: 4.x → 5.x (+ chart.js 3 → 4)
  • @vue/test-utils: 1.x → 2.x

Key changes

  • src/main.js: new Vue(...).$mount()createApp(App).mount()
  • src/route.js: new VueRouter()createRouter()
  • src/util/filters.js: Vue 2 Vue.filter() → named function exports
  • Stub plugin for Vue 2-only packages (vue-awesome, vue-color, vue-datetime, vue-d3-sunburst) — renders placeholder components until proper Vue 3 alternatives are added
  • Components using Vue.extend()defineComponent()
  • Header/Footer renamed to AwHeader/AwFooter (reserved HTML names in Vue 3)

Build status

Build succeeds: ✓ 2191 modules transformed, built in 26s

What's left (Phase 2+)

  • Replace stub components with real Vue 3 alternatives (icons, color picker, datetime, sunburst)
  • Migrate bootstrap-vue component names (<b-button>, etc.) to bootstrap-vue-next equivalents
  • Replace {{ value | filterName }} template filter syntax
  • Fix Sass deprecation warnings
  • Replace vuedraggable@2 usage

Test plan

  • Verify npm run build succeeds
  • Test basic navigation in browser (router working)
  • Check that Activity view loads data from aw-server
  • Check no console errors on page load

Important

Migrate from Vue 2 to Vue 3, updating components, routing, and build tools, while stubbing Vue 2-only packages.

  • Migration to Vue 3:
    • Update src/main.js to use createApp() instead of new Vue().
    • Replace Vue.filter() with named function exports in src/util/filters.js.
    • Use defineComponent() instead of Vue.extend() in components like QueryOptions.vue and SelectCategories.vue.
    • Stub vue-awesome, vue-color, vue-datetime, vue-d3-sunburst in vite.config.js.
  • Library Updates:
    • Upgrade vue-router to 4.x and update src/route.js to use createRouter().
    • Replace @fullcalendar/vue with @fullcalendar/vue3 in Calendar.vue.
    • Upgrade vue-chartjs to 5.x in TimelineBarChart.vue.
  • Build System:
    • Remove @vue/cli-* and webpack dependencies, use Vite exclusively.
    • Update package.json scripts to use Vite for serving and building.
    • Add vue2StubPlugin in vite.config.js to handle Vue 2-only packages.
  • Component Changes:
    • Rename Header and Footer components to AwHeader and AwFooter.
    • Replace vue-awesome icons with IconPlaceholder.vue.
    • Register components asynchronously in src/main.js using defineAsyncComponent().
  • Miscellaneous:
    • Update vite.config.js to handle CSP and auto-inject scripts.
    • Adjust routing in src/route.js for Vue Router 4.x syntax.
    • Update src/stores/index.js to use Pinia without PiniaVuePlugin.

This description was created by Ellipsis for 5500d9b. You can customize this summary. It will automatically update as commits are pushed.

Phase 1 of Vue 3 migration (aw-webui#772):
- vue 2.7 -> 3.5, vue-router 3 -> 4
- bootstrap-vue -> bootstrap-vue-next (bootstrap 4 -> 5)
- chart.js 3 -> 4 (required by vue-chartjs v5)
- @vitejs/plugin-vue2 -> @vitejs/plugin-vue
- Remove webpack/@vue/cli-* packages entirely
- Remove Vue 2-only packages: vue-datetime, vue-color, vue-awesome, vuedraggable v2
- Add vuedraggable v4, @fullcalendar/vue3 v6
- Update main.js: createApp() + createBootstrap() (bootstrap-vue-next API)
- Update route.js: createRouter() + createWebHashHistory() (Vue Router 4 API)
- Update stores/index.js: remove PiniaVuePlugin (Vue 2 only)
- Update filters.js: export functions instead of Vue.filter() (removed in Vue 3)
- Fix Vue.extend() -> defineComponent() in 3 components
- Fix Calendar.vue: @fullcalendar/vue -> @fullcalendar/vue3
- Fix TimelineBarChart.vue: vue-chartjs/legacy -> vue-chartjs (v5 API)
- Add vite.config.js stub plugin for Vue 2-only packages
- Disable transformAssetUrls to allow runtime-served assets (logo.png)
- Add IconPlaceholder.vue stub for vue-awesome icon component
- Fix Header/Footer component names (reserved in Vue 3 ESLint rules)

Build: 2191 modules transformed, dist generated successfully.
Components still use Vue 2 Options API - individual migration in follow-up commits.
Copy link
Copy Markdown
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Looks good to me! 👍

Reviewed everything up to 5500d9b in 16 seconds. Click for details.
  • Reviewed 729 lines of code in 14 files
  • Skipped 1 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_jGB2URIKIUFsDPmO

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 27, 2026

Greptile Summary

This is a well-executed Phase 1 Vue 3 migration that successfully gets the build passing while deferring UI polish (icon replacements, bootstrap-vue-next component renames, filter syntax in templates) to Phase 2. All core Vue 2 → Vue 3 API changes are applied correctly across the 33 changed files.

Key changes:

  • new Vue({...}).$mount()createApp(App).use(...).mount('#app') with correct plugin/property registration order
  • Vue Router 3 → 4: createRouter + createWebHashHistory, catch-all route updated to /:pathMatch(.*)*
  • Global filters (Vue.filter(...)) replaced with named function exports, every call site updated to method-style invocation
  • $emit('input', ...)$emit('update:modelValue', ...) across all custom-input components for Vue 3 v-model compatibility
  • beforeDestroy lifecycle hook renamed to beforeUnmount in all affected components
  • Vue.extend()defineComponent() in TypeScript components
  • Header/Footer renamed to AwHeader/AwFooter to avoid collision with reserved HTML element names
  • Stub Vite plugin (vue2StubPlugin) elegantly bridges the gap for Vue 2-only packages (vue-awesome, vue-color, vue-datetime, vue-d3-sunburst) so the build proceeds without them
  • Previously flagged issues (mount ordering of $aw, vue-router in devDependencies) are resolved in this head SHA

Two non-blocking suggestions remain:

  • jest.config.cjs should add moduleNameMapper entries for the now-absent Vue 2 packages so future component tests don't fail to resolve those imports
  • transformAssetUrls: false in the Vite Vue plugin disables all template asset URL resolution — a more targeted configuration would preserve bundling for genuinely local assets

Confidence Score: 4/5

  • Safe to merge as a Phase 1 migration; build succeeds and all core Vue 3 API changes are correctly applied.
  • All previously flagged issues ($aw mount ordering, vue-router in devDependencies) are resolved. The Vue 2 → Vue 3 migration patterns are consistently correct throughout: createApp, defineComponent, update:modelValue events, beforeUnmount hooks, named filter exports, and Vue Router 4 syntax. Two P2 suggestions remain (Jest module mappers for removed packages, overly broad transformAssetUrls setting) but neither blocks the primary goal of this phase.
  • jest.config.cjs — missing moduleNameMapper entries for vue-awesome/vue-color/vue-datetime will silently break any new component-level unit tests that touch those transitive imports.

Important Files Changed

Filename Overview
src/main.js Correctly migrated from new Vue({...}).$mount() to createApp(App).mount('#app'); plugins, global properties, and async component registrations all handled properly before mount.
src/route.js Clean Vue Router 4 migration: createRouter + createWebHashHistory used, catch-all route updated from path: '*' to path: '/:pathMatch(.*)*'.
vite.config.js Adds vue2StubPlugin to stub removed Vue 2-only packages at build time; duplicate name: 'html-transform' plugin names fixed; transformAssetUrls: false is broader than necessary (see comment).
src/util/filters.js Vue 2 global filters correctly replaced with plain named function exports; every call site in the PR is updated to use method-style invocation.
jest.config.cjs Updated to @vue/vue3-jest and removed CLI preset, but moduleNameMapper is missing stubs for the now-absent vue-awesome, vue-color, and vue-datetime packages — future component tests will fail to resolve those imports.
src/stores/index.js Correctly removes PiniaVuePlugin and Vue.use() call; Pinia is now registered via app.use(pinia) in main.js as required by Vue 3.
src/components/Header.vue Renamed to AwHeader to avoid collision with reserved HTML element name in Vue 3; slot syntax migrated from slot="button-content" to #button-content.
src/components/InputTimeInterval.vue beforeDestroy renamed to beforeUnmount, $emit('input') updated to $emit('update:modelValue'), and filter call converted to method — all correct Vue 3 migrations.
package.json Removes all @vue/cli-* / webpack deps; upgrades Vue to 3.5, Bootstrap to 5.3, chart.js to 4.x, FullCalendar to v6, vue-chartjs to 5.x, vuedraggable to v4, @vue/test-utils to v2; vue-router correctly placed in dependencies.
src/components/IconPlaceholder.vue New minimal stub component replacing vue-awesome icons during migration; renders a <span data-icon="..."> placeholder with clear TODO comment for Vue 3 replacement.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["main.js\ncreatApp(App)"] --> B["app.use(createBootstrap())"]
    A --> C["app.use(router)\nVue Router 4"]
    A --> D["app.use(pinia)\nPinia (no PiniaVuePlugin)"]
    A --> E["app.config.globalProperties\n$aw · PRODUCTION · COMMIT_HASH"]
    A --> F["app.mixin(asyncErrorCaptured)"]
    A --> G["app.component(...)\ndefineAsyncComponent × 22 globals"]
    A --> H["app.mount('#app')"]
    H --> I["configureClient()\nrequires settings store"]

    subgraph Build["Vite build"]
        J["vue2StubPlugin\nresolveId / load"] --> K["Stubs:\nvue-awesome\nvue-color\nvue-datetime\nvue-d3-sunburst"]
        L["@vitejs/plugin-vue\ntransformAssetUrls: false"]
        M["VitePWA"]
    end

    subgraph Router["src/route.js"]
        N["createRouter()\ncreateWebHashHistory()"]
        O["/:pathMatch(.*)*\n→ NotFound"]
    end

    subgraph Filters["src/util/filters.js"]
        P["Named exports\niso8601 · shortdate · shorttime\nfriendlytime · friendlyduration\nfriendlyperiod · orderBy"]
    end
Loading

Comments Outside Diff (1)

  1. jest.config.cjs, line 20-23 (link)

    P2 Jest missing stubs for removed Vue 2 packages

    vue-awesome, vue-color, and vue-datetime were removed from package.json but are still imported in many components (e.g. import 'vue-awesome/icons/circle' in CategoryTree.vue, import { Compact } from 'vue-color' in ColorPicker.vue). The vite vue2StubPlugin handles these at build time, but Jest does not invoke Vite plugins.

    Writing any new jsdom test that mounts a component with those transitive imports will fail with Cannot find module 'vue-awesome/...'.

    Add moduleNameMapper entries so Jest resolves these to a small in-tree stub:

    (IconPlaceholder.vue is already a minimal stub component; it can double as the mock for all these packages in tests.)

Reviews (3): Last reviewed commit: "fix(vue3): set $aw global property befor..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

15 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

…ility

@vue/eslint-config-typescript ^10 requires eslint-plugin-vue ^8, which
conflicts with ^9 (needed for Vue 3). Upgrading to ^11 resolves the
ERESOLVE peer dependency conflict that was breaking npm ci in CI.
- Bump ESLint ecmaVersion from 2017 to 2020 (supports dynamic import())
- Remove broken regex-style override patterns (ESLint uses globs, not regex)
- Remove recursive vue-eslint-parser override that caused stack overflow
- Replace @vue/cli-plugin-babel/preset with @babel/preset-env in babel config
- Remove @vue/cli-plugin-unit-jest preset from Jest config (package removed)
- Replace @vue/vue2-jest with @vue/vue3-jest for .vue file transforms
- Add testEnvironmentOptions to fix @vue/test-utils browser build resolution
- Add @vue/vue3-jest to devDependencies
@codecov
Copy link
Copy Markdown

codecov bot commented Feb 28, 2026

Codecov Report

❌ Patch coverage is 0% with 71 lines in your changes missing coverage. Please review.
✅ Project coverage is 25.57%. Comparing base (0dad0fd) to head (0d6f503).
⚠️ Report is 2 commits behind head on master.

Files with missing lines Patch % Lines
src/main.js 0.00% 64 Missing ⚠️
src/util/filters.js 0.00% 6 Missing ⚠️
src/route.js 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #773      +/-   ##
==========================================
- Coverage   25.71%   25.57%   -0.14%     
==========================================
  Files          30       30              
  Lines        1754     1783      +29     
  Branches      307      316       +9     
==========================================
+ Hits          451      456       +5     
+ Misses       1281     1258      -23     
- Partials       22       69      +47     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The e2e tests connect to http://127.0.0.1:27180 but Vite defaults to
binding on 'localhost' which may resolve to ::1 (IPv6) on Ubuntu 24.04
CI runners, causing ConnectionRefused errors.
Vue 3 breaking changes that were missed in the initial migration:

- $emit('input') → $emit('update:modelValue') in all custom components
  that support v-model (InputTimeInterval, QueryOptions, SelectCategories,
  SelectCategoriesOrPattern, ColorPicker). This was causing Timeline view
  to be stuck on "Loading..." because daterange was never emitted.

- beforeDestroy → beforeUnmount lifecycle hook (InputTimeInterval,
  Activity, CategorizationSettings). Vue 3 renamed this hook.

- {{ value | filterName }} pipe syntax → {{ filterName(value) }} function
  calls across all 14 affected components. Vue 3 removed global filters;
  functions are imported from ~/util/filters and exposed via methods.
Resolve merge conflicts:
- package.json: keep master's dompurify ^3.3.2, exports-loader, imports-loader
- package-lock.json: regenerated via npm install

Build verified: vite build succeeds, all 59 tests pass.
@ErikBjare
Copy link
Copy Markdown
Member

@greptileai review

@TimeToBuildBob Thinking about merging this into a separate branch from master for now, maybe a vue3 branch? We don't want to break the prod aw-webui ahead of v0.14 launch.

@TimeToBuildBob
Copy link
Copy Markdown
Contributor Author

Good call on the vue3 branch — keeps master stable for v0.14 while giving this PR a home to iterate on.

Before merging (even into vue3), the Greptile review flagged one P1 worth fixing: Header.vue still uses Vue 2 slot="button-content" syntax on the Activity and Tools dropdowns (lines ~22 and ~55). In clean Vue 3, this means the navbar dropdown labels won't render. Two-line fix:

- template(slot="button-content")
+ template(#button-content)

Minor issues that can land later:

  • vue-router is in devDependencies but it's a runtime import — should be in dependencies
  • Both setCsp and autoInject Vite plugins share the name 'html-transform' — distinct names would help with debug traces

- Header.vue: fix Vue 2 slot syntax (slot= → #) on Activity and Tools
  dropdown buttons — broken in Vue 3, caused empty navbar dropdowns
- vite.config.js: give setCsp/autoInject unique plugin names ('set-csp'
  and 'auto-inject') instead of duplicate 'html-transform'
- package.json: move vue-router from devDependencies to dependencies
  (it's a runtime import, not a build tool)
In Vue 3, app.config.globalProperties must be set before mounting.
Move $aw alongside the other global properties (PRODUCTION, COMMIT_HASH,
$isAndroid) before the mount() call. configureClient() stays after mount
since it depends on the settings store being initialized.
@TimeToBuildBob
Copy link
Copy Markdown
Contributor Author

Addressed Greptile review findings in two commits:

  • 009404eHeader.vue: fix Vue 2 slot="button-content"#button-content on Activity and Tools dropdowns (broken in Vue 3); vite.config.js: unique plugin names (set-csp, auto-inject); package.json: move vue-router to dependencies
  • 0d6f503main.js: move $aw = getClient() before app.mount() (global properties must be set pre-mount in Vue 3)

@ErikBjare ErikBjare changed the base branch from master to vue3 March 23, 2026 10:38
@ErikBjare
Copy link
Copy Markdown
Member

I changed the base branch for this PR to vue3.

@greptileai review

@TimeToBuildBob merge when ready, make sure to keep track of the follow-up plan and PRs. Might make sense to advertise the vue3 branch in related issues and maybe even master README.

Comment on lines +101 to +107
vue({
// Disable asset URL transformation so that runtime-provided assets
// (like /logo.png served by aw-server) are not treated as build-time imports.
template: {
transformAssetUrls: false,
},
}),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 transformAssetUrls: false disables all template asset resolution

Setting transformAssetUrls: false is a blanket override that prevents @vitejs/plugin-vue from turning any src="..." attribute in a template into a proper module import. This means local component-relative images (e.g. <img src="./icon.svg">) won't be bundled correctly in a future component that adds them.

The real goal is to avoid treating the server-served /logo.png as a build-time import. A more targeted fix is to configure only the asset types/tags you want rather than disabling the feature entirely:

Suggested change
vue({
// Disable asset URL transformation so that runtime-provided assets
// (like /logo.png served by aw-server) are not treated as build-time imports.
template: {
transformAssetUrls: false,
},
}),
vue({
template: {
// Only transform img[src] and source[src]; skip script[src] so that
// runtime-provided paths (like /logo.png from aw-server) are left alone.
transformAssetUrls: {
img: ['src', 'srcset'],
source: ['src', 'srcset'],
},
},
}),

This keeps bundling for genuinely local images while leaving absolute/protocol paths (served by aw-server at runtime) untouched.

@ErikBjare
Copy link
Copy Markdown
Member

I just looked at the screenshot posted by CI at 0d6f503#commitcomment-180290865

You should use your own vision abilities to build and inspect screenshots and iterate until issues are fixed. You can even compare with old screenshots.

Major visual fixes for the Vue 3 migration:

- Replace empty IconPlaceholder with @fortawesome/vue-fontawesome — maps all
  50+ vue-awesome icon names to Font Awesome 6 equivalents, fixing missing
  icons throughout the app (navbar, footer, buckets, settings, etc.)
- Migrate Bootstrap 4 → 5 utility classes across all 30+ Vue files:
  .ml-* → .ms-*, .mr-* → .me-*, .pl-* → .ps-*, .pr-* → .pe-*,
  .float-left → .float-start, .float-right → .float-end,
  .text-left → .text-start, .text-right → .text-end
- Fix Buckets.vue: replace Vue 2 $root.$emit modal control with
  bootstrap-vue-next v-model pattern
- Fix Home.vue: add null guard for serverStore.info access that
  crashed render before server info loaded
- Fix Buckets.vue: add null guard for serverStore.info.hostname
- Update vite.config.js: add comment explaining why transformAssetUrls:
  false is needed (runtime /logo.png from aw-server, not a build asset)

Co-authored-by: Bob <bob@superuserlabs.org>
@TimeToBuildBob
Copy link
Copy Markdown
Contributor Author

Visual fixes based on CI screenshot inspection

Inspected all 20 CI screenshots across three server backends. Found and fixed six categories of issues:

1. Missing icons (empty spans everywhere)

The IconPlaceholder.vue stub rendered empty <span> elements. Replaced with @fortawesome/vue-fontawesome:

  • Installed @fortawesome/fontawesome-svg-core, free-solid-svg-icons, free-regular-svg-icons, free-brands-svg-icons, vue-fontawesome
  • Created icon name mapping from all 50+ vue-awesome names to Font Awesome 6 equivalents
  • Icons now render properly in navbar, footer, buckets, settings, etc.

2. Bootstrap 4 → 5 class migration (30+ files)

All BS4 utility classes were still in use. Migrated across all Vue files:

  • .ml-*.ms-*, .mr-*.me-* (margin)
  • .pl-*.ps-*, .pr-*.pe-* (padding)
  • .float-left/right.float-start/end
  • .text-left/right.text-start/end

This fixes the footer spacing issues (links running together) and general layout.

3. Vue 2 modal pattern → Vue 3

Buckets.vue used $root.$emit('bv::show::modal') which doesn't work in Vue 3. Replaced with v-model binding on b-modal.

4. Null guard for server info

Home.vue and Buckets.vue accessed serverStore.info.version/.hostname without null checks — info starts as null, causing render crashes before server data loads.

5. transformAssetUrls explanation

Added comment explaining why transformAssetUrls: false is correct — /logo.png is served at runtime by aw-server, not a build asset. Greptile's suggestion to use a targeted allowlist doesn't work because Vite still tries to resolve /logo.png as a build import.

Follow-up plan for vue3 branch

After merge, these items remain for follow-up PRs:

  • Replace vue-color stub with a Vue 3 color picker
  • Replace vue-datetime stub with a Vue 3 datetime picker
  • Replace vue-d3-sunburst stub with Vue 3 compatible version
  • Investigate [object Promise] rendering in Buckets "Tools" section
  • Test logo rendering end-to-end (broken image in CI may be test env issue — aw-server provides /logo.png at runtime)
  • Advertise vue3 branch in related issues and master README

@TimeToBuildBob
Copy link
Copy Markdown
Contributor Author

Note: CI isn't running because the Build workflow only triggers on PRs targeting master. Now that the base is vue3, we'll need to either:

  1. Add vue3 to the workflow trigger branches, or
  2. Verify builds manually

Build and lint pass locally:

✓ built in 28.50s (vite build)
✓ eslint clean (npm run lint)

@TimeToBuildBob
Copy link
Copy Markdown
Contributor Author

Ready to merge — all visual fixes applied (icons, BS5 classes, Vue 3 patterns), merge state is CLEAN. I don't have merge permissions on this repo though, so you'll need to click the button @ErikBjare.

Follow-up plan for the vue3 branch after merge:

  • Replace vue-color, vue-datetime, vue-d3-sunburst stubs with Vue 3 alternatives
  • Investigate [object Promise] in Buckets Tools section
  • Advertise vue3 branch in related issues and master README

@ErikBjare
Copy link
Copy Markdown
Member

@TimeToBuildBob Add vue3 to the workflow trigger branches

@ErikBjare ErikBjare merged commit 8dd580d into ActivityWatch:vue3 Mar 23, 2026
@ErikBjare
Copy link
Copy Markdown
Member

Merged this, do the CI stuff in follow-up PR with the remaining fixes

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants