diff --git a/.dockerignore b/.dockerignore
index 141c909..197930b 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,3 +1,3 @@
node_modules
npm-debug*
-.nuxt
\ No newline at end of file
+dist
diff --git a/.eslintrc.js b/.eslintrc.js
deleted file mode 100644
index 010a527..0000000
--- a/.eslintrc.js
+++ /dev/null
@@ -1,53 +0,0 @@
-module.exports = {
- root: true,
- env: {
- browser: true,
- node: true
- },
- parserOptions: {
- parser: 'babel-eslint'
- },
- extends: [
- '@nuxtjs',
- 'plugin:nuxt/recommended'
- ],
- plugins: [
- ],
- // add your custom rules here
- rules: {
- indent: ["warn", 2, { SwitchCase: 1 }],
- semi: [2, "always"],
- "no-unused-vars": [
- "warn",
- { vars: "all", args: "after-used", ignoreRestSiblings: false },
- ],
- "vue/html-indent": [
- "warn",
- 2,
- {
- attribute: 1,
- baseIndent: 1,
- closeBracket: 0,
- alignAttributesVertically: true,
- ignores: [],
- },
- ],
- "vue/max-attributes-per-line": [
- "warn",
- {
- singleline: 3,
- multiline: {
- max: 4,
- allowFirstLine: true,
- },
- },
- ],
- "prefer-const": [
- "error",
- {
- destructuring: "all",
- ignoreReadBeforeAssign: false,
- },
- ],
- },
-}
diff --git a/.superpowers/sdd/progress.md b/.superpowers/sdd/progress.md
new file mode 100644
index 0000000..4df4c16
--- /dev/null
+++ b/.superpowers/sdd/progress.md
@@ -0,0 +1,47 @@
+# Migration progress ledger — COMPLETE
+
+All 15 tasks done. Branch: migration/vue3 (38 commits off master @ 575ee82).
+
+FINAL STATE, verified through the PRODUCTION path (pnpm build + node server/index.js):
+ lint PASS | typecheck PASS (strict) | build PASS
+ All 5 routes load with 0 app console errors and 0 warnings.
+ SPA fallback survives a hard refresh of /jobs. /api/clients returns the 5 real clients.
+
+Stack: Vue 3.5.39 / Vuetify 4.1.4 / Vite 8.1.4 / vue-router 4.6.4 (unplugin-vue-router),
+
diff --git a/components/Client.vue b/components/Client.vue
deleted file mode 100644
index bddc812..0000000
--- a/components/Client.vue
+++ /dev/null
@@ -1,879 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ client.HostName }}
- mdi-cancel
- mdi-sleep
- mdi-alarm-snooze
- mdi-lan-disconnect
- mdi-timer-play-outline
- mdi-movie-open-cog-outline
-
-
-
-
-
-
- mdi-pause-circle-outline
-
-
- Pause
-
-
-
-
-
- mdi-play-circle-outline
-
- Resume
-
-
-
-
-
-
-
-
- mdi-power
-
- Shut Down
-
-
-
-
- Shut down service
-
- Do you really want to shut down {{ client.HostName }}'s service? This action is irreversible with the web-gui. You'll have
- to restart it physically on the client machine.
-
-
-
-
- Cancel
- Hit me
-
-
-
-
-
-
-
-
-
- {{ errorMessage }}
-
- Status: {{ activeProcess.text }}
- Estimating
-
- {{ client.InFile }}
- {{ pauseErrorText }}
-
-
-
-
-
-
-
-
-
-
- {{ key }}
-
-
- {{ value }}
-
-
-
-
-
-
-
-
-
-
- Remaining
-
-
- {{ remaining }}
-
-
-
-
-
-
- Speed
-
-
- {{ client.Encoder.Speed }}
-
-
-
-
-
-
- Slice
-
-
- {{ client.Encoder.Slice }}/{{ client.Encoder.OfSlices }}
-
-
-
-
-
-
- FPS
-
-
- {{ client.Encoder.Fps }}
-
-
-
-
-
-
-
-
-
- {{ activeProcessProgress }}%
-
-
-
-
-
-
-
-
-
-
-
-
- mdi-format-list-bulleted
-
-
- Process Info
-
-
-
-
-
- mdi-console
-
-
- Encoder Log
-
-
-
-
-
- mdi-text-box-outline
-
-
- Main Log
-
-
-
-
-
- mdi-text-box-remove-outline
-
-
- Error Log
-
-
-
-
-
- mdi-text-box-plus-outline
-
-
- Processed Log
-
-
-
-
-
- mdi-text-box-minus-outline
-
-
- Skipped Log
-
-
-
-
-
-
-
-
-
-
-
-
- {{ key }}
-
-
- {{ value }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ item }}
-
-
-
-
-
-
-
-
-
-
- No encoder data available yet
-
-
-
-
-
-
-
-
Main Log
-
-
-
-
-
- {{ item }}
-
-
-
-
-
-
-
-
-
-
-
-
Error Log
-
-
-
-
-
-
- {{ item }}
-
-
-
-
-
-
-
-
-
-
-
-
Processed Log
-
-
-
-
-
- {{ item }}
-
-
-
-
-
-
-
-
-
-
-
-
Skipped Log
-
-
-
-
-
- {{ item }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/components/EncoderConfig.vue b/components/EncoderConfig.vue
deleted file mode 100644
index 7a90c33..0000000
--- a/components/EncoderConfig.vue
+++ /dev/null
@@ -1,241 +0,0 @@
-
-
-
-
Encoder Configuration
- Enter new encoder configuration
-
-
- To push changes to the client, you need to hit upload config
-
-
-
-
-
-
-
- mdi-circle-edit-outline
- mdi-checkbox-marked-circle-outline
-
-
- mdi-delete
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- mdi-delete
-
-
-
- Do you really want to delete the config for {{ tagInternal }}?
-
-
- Cancel
- Delete
-
-
-
-
-
-
-
-
-
-
diff --git a/components/JobDataTable.vue b/components/JobDataTable.vue
deleted file mode 100644
index 7f42d51..0000000
--- a/components/JobDataTable.vue
+++ /dev/null
@@ -1,204 +0,0 @@
-
-
-
-
-
-
- {{ value }}
- {{ item.Path }}
-
-
-
-
-
- mdi-delete-circle-outline
- mdi-circle-edit-outline
-
-
-
-
-
-
-
- Delete Job
- Do you really want to delete the following job?
-
-
-
- {{ editItem.Name }}
- {{ editItem.Path }}
-
-
-
-
-
- Cancel
- Delete Job
-
-
-
-
-
-
-
-
-
diff --git a/components/Logo.vue b/components/Logo.vue
deleted file mode 100644
index 6c72854..0000000
--- a/components/Logo.vue
+++ /dev/null
@@ -1,79 +0,0 @@
-
-
-
-
-
diff --git a/components/Modules/AgeSettings.vue b/components/Modules/AgeSettings.vue
deleted file mode 100644
index 314b9ed..0000000
--- a/components/Modules/AgeSettings.vue
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-
-
-
- mdi-help-circle-outline
-
-
-
-
-
-
Maximum duplicate file age in days before it is no longer protected from replacement
-
-
-
-
-
-
-
-
->
\ No newline at end of file
diff --git a/components/Modules/AudioSettings.vue b/components/Modules/AudioSettings.vue
deleted file mode 100644
index f26b773..0000000
--- a/components/Modules/AudioSettings.vue
+++ /dev/null
@@ -1,91 +0,0 @@
-
-
- Accuracy
-
- mdi-help-circle-outline
-
-
-
-
-
- Determines how lenient or strict the encoder should be with the audio format detection.
-
-
-
-
- mdi-circle-small low will detect audio if tuner log includes a reference
-
-
-
- mdi-circle-small med will detect audio if tuner log includes a reference and the metadata log has a tag
-
-
-
- mdi-circle-small high will detect audio if tuner log exclusively lists it
-
-
-
-
-
-
-
- {{ accIcon(props.value) }}
-
-
-
-
-
-
-
->
\ No newline at end of file
diff --git a/components/Modules/DuplicateLengthCheckSettings.vue b/components/Modules/DuplicateLengthCheckSettings.vue
deleted file mode 100644
index 13cbcb1..0000000
--- a/components/Modules/DuplicateLengthCheckSettings.vue
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
- Length Difference %
-
- mdi-help-circle-outline
-
-
-
-
-
This setting configures how much the duplicate file and the replacement file are allowed to differ in length
-
- Setting the threshold to a value greater than 0 allows the new file to be {{ settingsInternal.threshold }}% shorter
-
-
-
-
-
-
-
-
-
- mdi-percent
-
-
-
-
-
-
-
->
\ No newline at end of file
diff --git a/components/Modules/ErrorReplaceSettings.vue b/components/Modules/ErrorReplaceSettings.vue
deleted file mode 100644
index b0ecf86..0000000
--- a/components/Modules/ErrorReplaceSettings.vue
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
-
-
- mdi-help-circle-outline
-
-
-
-
-
-
- The minimum amount of errors the duplicate file is allowed to have before errors are matched with the new file.
-
-
- Once the threshold is crossed, if the new file has less errors, it will be considered eligible for replacement.
-
-
-
-
-
-
-
-
-
->
\ No newline at end of file
diff --git a/components/Modules/ErrorSkipSettings.vue b/components/Modules/ErrorSkipSettings.vue
deleted file mode 100644
index a3f0964..0000000
--- a/components/Modules/ErrorSkipSettings.vue
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-
-
-
- mdi-help-circle-outline
-
-
-
-
-
-
The maximum allowed amount of errors before a re-encode is prohibited
-
-
-
-
-
-
-
-
->
\ No newline at end of file
diff --git a/components/Modules/LengthSettings.vue b/components/Modules/LengthSettings.vue
deleted file mode 100644
index f8dc574..0000000
--- a/components/Modules/LengthSettings.vue
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
- Length Difference %
-
- mdi-help-circle-outline
-
-
-
-
-
- This setting configures how much difference in the recorded length vs the broadcasted length of a video file is
- allowed.
-
-
-
-
-
-
-
-
-
- mdi-percent
-
-
-
-
-
-
-
->
\ No newline at end of file
diff --git a/components/Modules/LogMatchSettings.vue b/components/Modules/LogMatchSettings.vue
deleted file mode 100644
index 2f9a31a..0000000
--- a/components/Modules/LogMatchSettings.vue
+++ /dev/null
@@ -1,88 +0,0 @@
-
-
- Mode
-
- mdi-help-circle-outline
-
-
-
-
-
Log file matching criteria for include and exclude matches
-
-
-
- mdi-circle-small include mode: allow if both or include matches
-
-
-
- mdi-circle-small neutral mode: reject if both or exclude matches
-
-
-
- mdi-circle-small exclude mode: reject if exclude matches
-
-
-
-
-
-
-
- {{ accIcon(props.value) }}
-
-
-
-
-
-
-
->
\ No newline at end of file
diff --git a/components/Modules/MaxSizeSettings.vue b/components/Modules/MaxSizeSettings.vue
deleted file mode 100644
index 6661807..0000000
--- a/components/Modules/MaxSizeSettings.vue
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-
-
-
- mdi-help-circle-outline
-
-
-
-
-
- Maximum unencoded allowed file size in Gigabytes. Files beyond this threshold will be ignored
-
-
-
-
-
-
-
-
->
\ No newline at end of file
diff --git a/components/Modules/Module.vue b/components/Modules/Module.vue
deleted file mode 100644
index 481ab7a..0000000
--- a/components/Modules/Module.vue
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-
- {{ name }}
-
-
-
-
-
-
- Settings
-
-
-
-
-
-
-
-
-
->
\ No newline at end of file
diff --git a/components/Property.vue b/components/Property.vue
deleted file mode 100644
index 40c3178..0000000
--- a/components/Property.vue
+++ /dev/null
@@ -1,93 +0,0 @@
-
-
-
-
- mdi-circle-edit-outline
- mdi-checkbox-marked-circle-outline
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- mdi-close
-
-
-
-
-
-
\ No newline at end of file
diff --git a/components/README.md b/components/README.md
deleted file mode 100644
index a079f10..0000000
--- a/components/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# COMPONENTS
-
-**This directory is not required, you can delete it if you don't want to use it.**
-
-The components directory contains your Vue.js Components.
-
-_Nuxt.js doesn't supercharge these components._
diff --git a/components/SimpleList.vue b/components/SimpleList.vue
deleted file mode 100644
index 0b71467..0000000
--- a/components/SimpleList.vue
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
- {{ type }}s
-
-
-
- {{ icon }}
-
-
- {{ item }}
-
- mdi-close
-
-
-
-
- mdi-plus
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/components/TextDataTable.vue b/components/TextDataTable.vue
deleted file mode 100644
index c184e6a..0000000
--- a/components/TextDataTable.vue
+++ /dev/null
@@ -1,208 +0,0 @@
-
-
-
-
-
-
-
-
-
- mdi-delete
- {{ selected.length > 0 ? selected.length : "" }}
-
-
-
-
- Delete {{ selected.length }} Fields
-
-
- Do you really want to delete the following fields?
-
-
-
- {{ field.Value }}
-
-
-
-
-
- Cancel
- Hit me
-
-
-
-
-
-
- mdi-plus
-
-
-
-
- {{ formTitle }}
-
-
-
-
-
-
-
-
-
-
-
-
- Cancel
- {{ actionButton }}
-
-
-
-
-
-
-
- mdi-pencil
-
-
-
- mdi-delete
-
-
- Delete {{ item.Value }}
- Do you really want to delete {{ item.Value }}?
-
-
-
- Cancel
- Hit me
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/components/VuetifyLogo.vue b/components/VuetifyLogo.vue
deleted file mode 100644
index 9a60937..0000000
--- a/components/VuetifyLogo.vue
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
diff --git a/docs/TOFIX.md b/docs/TOFIX.md
new file mode 100644
index 0000000..aa88de0
--- /dev/null
+++ b/docs/TOFIX.md
@@ -0,0 +1,198 @@
+# Avior — Vue 3 / Vuetify 4 migration: outstanding visual issues
+
+Handoff document. The functional migration is complete and verified (data, API, routing,
+behaviour). What remains is visual fidelity: Vuetify 4 is Material Design 3 and changed a
+lot of component defaults, and the port left a number of genuinely broken layouts behind.
+
+Branch: `migration/vue3`. All items below were reported by the user against the running app.
+
+---
+
+## How to work on this
+
+```bash
+# the new app
+pnpm dev # Vite (5173) — has host:true and allowedHosts:true, reachable by hostname
+pnpm dev:api # Express + Mongo (10009)
+
+# production path (what the user has been testing)
+pnpm build && node server/index.js # serves dist/ + /api on 10009
+
+# the OLD Nuxt app, for side-by-side comparison — THIS IS THE GROUND TRUTH
+cd ../avior-nuxt-reference && pnpm dev # pinned to port 3300
+```
+
+`../avior-nuxt-reference` is a git worktree pinned to commit `baff6fe`, the last commit
+where Nuxt still ran. Its `nuxt.config.js` is locally patched to pin port 3300 and set
+`http.baseURL` to match (under SSR, `@nuxt/http` otherwise falls back to `localhost:3000`
+and its own API calls time out).
+
+Environment: MongoDB at `mongodb://192.168.178.75:27017/Avior`. Daemons VDR-U/-1/-2 at
+`192.168.178.61:10000-10002`. Phoenix and VAVA are genuinely offline.
+
+### Do not guess — measure
+
+Every fix below that stuck was found by measuring computed styles in BOTH apps with
+Playwright and diffing them. Reading Vuetify's CSS is unreliable: `vuetify/styles`
+resolves to `lib/styles/main.css`, NOT `dist/vuetify.css`, and component styles are
+injected per-component by `vite-plugin-vuetify`. `scripts/inspect-page.mjs` is the
+headless inspector (console + semantic structure dump + screenshot). Write throwaway
+Playwright scripts under `scripts/` to compare computed styles; several examples are in
+the git history.
+
+---
+
+## STATUS
+
+The user has walked every page and signed the app off as usable. What follows is the
+small residue plus the patterns worth knowing before touching the layout again.
+
+## STILL OPEN (minor, measured, nobody has complained since)
+
+### 1. Adjacent button gaps that shifted
+Vue 3's compiler condenses away the whitespace-only text nodes between elements that
+Vue 2 kept. Buttons are `inline-flex`, so a bare HTML space between two of them used to
+render as a 4px gap, and any pair that got its spacing that way now touches. Fixed on
+config and globalconfig (Import/Export). Swept every page and tab; these two still
+differ and were left alone:
+
+| Where | Old gap | New gap |
+|---|---|---|
+| `/` — refresh icon → "Ping Offline" | 8px | 32px |
+| `/jobs` — New → Reassign → Delete | 20px | 16px |
+
+The `/` one is the odd one out: its gap got BIGGER, so the whitespace rule does not
+explain it and it needs its own look.
+
+### 2. Global config toolbar: "buttons have no mx to the right"
+Reported once, then not raised again. Measured against the reference at 1600px and the
+two apps AGREE: search field inset 16px from the card's left edge, last button inset
+24px from the right, in both. Either it was about something else, or it only shows at a
+different viewport width. Reproduce at the user's width before changing anything.
+
+---
+
+## The two things that caused most of this
+
+Worth reading before any further layout work — nearly every issue in the table below is
+one of these two.
+
+### Vuetify 4 rebuilt the grid on flexbox `gap`
+Vuetify 2's `.v-row` carried a `-12px` margin and its `.v-col` carried 12px of padding.
+Vuetify 4's row has NO negative margin, its col has NO padding, and it adds a **24px
+margin between consecutive rows**. Consequences, all of which bit:
+
+- Markup that leaned on the negative margin cancelling a container's padding gains
+ height (resolution rows, the expanded client panel, module SETTINGS gaps).
+- A `v-col` with **no `v-row` parent collapses entirely** — v4 computes a column's
+ flex-basis from custom properties that `.v-row` declares, so with no row the calc is
+ invalid and the column shrink-wraps to its content. This hit the audio-format lists
+ and the encoder Tag field. A DOM sweep for it has a blind spot: `v-window-item` mounts
+ lazily and some components only render once a selection is made, so a bare `.v-col`
+ can be absent from the DOM at scan time. Grep the templates as well.
+- Anything positioned with a fixed margin tuned against the old 12px col padding is now
+ off (the encoder help buttons).
+
+### `hideDetails: 'auto'` removed dead space AND accidental space
+Vuetify 4 permanently reserves ~26px under every input for a validation message it may
+never show. Collapsing that (plugins/vuetify.ts) was right — it was dead space nearly
+everywhere. But a few layouts were quietly leaning on it for their spacing, and each
+broke in a different way:
+
+- the two encoder selects overlapped;
+- the encoder select ran into the card's bottom border;
+- the client select stopped pushing the tab bar down (22px);
+- the module settings fields got STRETCHED by the help button (see below);
+- the slider tick labels went flush against the card edge.
+
+If you find a cramped, overlapping, or spilling field, suspect this first.
+
+The stretched-field one is the sneakiest and I misdiagnosed it twice: the settings row
+is a flex container, so it defaults to `align-items: stretch`, and the help button's
+margins made it TALLER than the 56px field — so the field stretched to the button, and
+the dead space underneath the value looked exactly like a leftover validation slot.
+Vuetify 2 hid it because its field reserved a message slot and was therefore taller than
+the button. `align-center` on the row is the fix.
+
+---
+
+## FIXED (for reference — these are the failure patterns to look for)
+
+Every one of these was a broken port, not an MD3 aesthetic. The pattern is almost always:
+*a Vuetify 2 default that Vuetify 4 changed, silently.* `vue-tsc` catches NONE of them —
+unknown attributes on a Vue component fall through to `$attrs` with no type error.
+
+| Symptom | Root cause | Commit |
+|---|---|---|
+| Everything the wrong font | Roboto was never installed. Vuetify's CSS asks for it but does not ship it; `@nuxtjs/vuetify` used to inject it from Google Fonts. Now self-hosted via `@fontsource/roboto`. | `7e12c1c` |
+| Headings rendered at body size | Vuetify 4 DELETED the old typography classes. `.display-1`, `.headline`, `.title`, `.body-1`, `.text-h4`, `.text-overline` do not exist and are not aliased. Mapped to the MD3 scale. | `7e12c1c` |
+| Status icons huge | Vuetify 4 derives an icon-button's glyph size from the button size (em-based); Vuetify 2's `.v-icon` was a fixed 24px. | `eafcc63` |
+| Icon buttons were filled coloured discs | Vuetify 2's `icon` prop implied flat styling and `color` tinted the ICON. In Vuetify 4 `icon` only sets the shape; `variant` defaults to `elevated`, so `color` became a background. All 30 needed `variant="text"`. | `1f86e77` |
+| Card header margins doubled | Vuetify 2's `large` icon button was 44px; Vuetify 4's `size="large"` is 56px. The 12px inflated the flex row. | `b457f9f` |
+| Paragraphs too far apart | Vuetify 4 REMOVED Vuetify 2's global `* { margin: 0; padding: 0 }` reset, so every `` picked up the browser's 1em UA margin. Restored in `src/styles/reset.css` — **it must live in Vuetify's `vuetify-core.reset` sublayer**; a bare `@layer reset` orders AFTER Vuetify's layers and starts beating `ma-0`. | `9fad632` |
+| Green bar spanned the whole page | `VSystemBar` calls `useLayoutItem` in Vuetify 4 — it is a LAYOUT component and hoists out of the card. No prop restores inline behaviour. Replaced with a plain `v-sheet` strip. | `9fad632` |
+| Progress arc started at 9 o'clock | Vuetify 2's arc started at 3 o'clock so the code passed `rotate="-90"`. Vuetify 4 already applies `rotate(calc(-90deg + Xdeg))` internally, so the inherited -90 rotated it a further quarter turn. | `9fad632` |
+| Buttons/tabs not uppercase, wrong weight | MD3 dropped the uppercase default and re-specced button text (`uppercase→none`, `1.25px→0.11px`, `500→400`, `14px→16px`). Restored in `src/styles/vuetify2-compat.css`. **These rules are UNLAYERED on purpose** — Vuetify sets them in a cascade layer, and layer order beats specificity, so a rule inside `vuetify-overrides` still lost. | `a01fccd` |
+| No stripes on the encoder bar | THREE stacked bugs: (a) Vuetify 2's `VProgressLinear` defaulted to `color="primary"`, Vuetify 4 has no default → white bar, and the stripes are white at 25% alpha; (b) `background-size` went from `40px 40px` to `8px` (the bar's height); (c) `background-repeat` went from `repeat` to `no-repeat`. Any one alone hides them completely. | `1dbadca` |
+| Grey box inputs everywhere | Vuetify 4 defaults inputs to `variant="filled"`. This app is an underlined-input design (Vuetify 2's default). Set once via `createVuetify`'s `defaults` — the idiomatic v4 mechanism. | `5e60102` |
+| Module card: switch on its own line, title glued to top | Vuetify 4's `v-card-title` is `display: block` with nowrap/ellipsis, not a flex row, so `v-spacer` did nothing. The original's `pt-0` was harmless in v2 (the switch's margins filled the gap) but glues the title to the card edge in v4. | `5e60102` |
+| Client select showed `[object Object]` | `selectedClient` was initialised to `{}`. Vuetify 2 rendered that blank; Vuetify 4 falls back to `String(value)` for a model it cannot resolve against `items`. `null` is the correct empty model. | `5e60102` |
+| SimpleList add-field would not stretch, caret offset | The input was nested in ``, which in Vuetify 4 is `overflow: hidden` / `nowrap` with a fixed line-height. It belongs in the list item's content slot. | `5e60102` |
+| Search bar grey stripe, no left margin | Vuetify 4's `v-toolbar` paints the `surface-light` token, which the dark theme sets to `#424242` — a washed-out band. v2 used `#272727` on a `#1E1E1E` card: surface, one step up. A dedicated `toolbar` theme colour (`#2A2A2A`), NOT a retuned `surface-light` — VSelect, VSlider, VAlert and VTimePicker share that one and the time-picker clock face would go black. `.v-toolbar__content` also has no padding of its own now (v2's was `4px 16px`), hence `px-4`. | `cbb02f7`, `e19cf7e` |
+| NO animations anywhere | Not a broken port. Windows' Accessibility → Visual effects → "Animation effects" is off, so Chrome reports `prefers-reduced-motion: reduce` and Vuetify 4 honours it. Vuetify 2 ignored the preference entirely. Confirmed by reading `SPI_GETCLIENTAREAANIMATION`, the flag Chrome maps to the media query. Vuetify gates motion in TWO layers and both had to go: CSS (several transitions are only *declared* inside a `no-preference` query, so under `reduce` they do not exist and no override can restore them — `build/force-motion.ts` rewrites the queries at build time) and JS (every transition's `disabled` prop defaults to `PREFERS_REDUCED_MOTION()`, so Vue's `` is off outright — `src/force-motion.ts` patches `matchMedia`, and must be imported BEFORE Vuetify because the prop default is evaluated once at module load). Deliberately overrides an accessibility preference, at the user's request. | `7936b64` |
+| Resolution rows 104px tall, not 72px | Vuetify 4 rebuilt the grid on flexbox `gap`. v2's `.v-row` had a `-12px` margin that cancelled the container's padding, so the container added NO vertical height; v4's row has no negative margin and `.v-col` has no padding at all, so the container's 16px top/bottom started counting. `py-0` on the container. | `2ca8274` |
+| Audio format lists collapsed, delete X against the text | The cols had no `v-row` parent. v2's `.col-md-6` carried `flex: 0 0 50%` on the class, so it sized inside any flex parent; v4 computes flex-basis from custom properties that **`.v-row` declares**, so with no row the calc is invalid and the column shrink-wraps. Swept the whole app — this was the only `v-col` without a row. | `d3d956e` |
+| Caret sat below the text in add-item fields | v4 pads a field's input asymmetrically (16px top, 0 bottom) to reserve room for a FLOATING label, but a `solo` field's label does not float — it stays centred. So the label rendered 8px above the text box. v2's solo label already behaved as a placeholder, so `placeholder` is the faithful equivalent. | `90b819d` |
+| Sliders far too heavy | MD3 re-specced them: track `2px→4px`, thumb `12px→20px`, overall `32px→58px`. Restored with v4's own `trackSize`/`thumbSize`/`density` props as global defaults. | `b7c6e0d` |
+| Dead space under every input | v4 permanently reserves ~26px beneath a field for a validation message whether or not one can appear. `hideDetails: 'auto'` collapses it and expands only when a rule actually fails. | `b7c6e0d` |
+| Module cards had no contrast | Cards are `#242424` on the card surface `#212121` — a few values apart, so they read as one flat slab. The modules pane uses the theme `background` token (`#121212`). | `b7c6e0d` |
+| Import/Export buttons touching | The original never declared a gap — it got 4px from the whitespace text node between two `inline-flex` buttons. Vue 3's compiler condenses whitespace-only nodes between elements away. Declared explicitly. | `b7c6e0d` |
+| Expanded client panel 286px, not 204px | The switch reserved a 26px validation slot (`VSwitch` → `hideDetails: 'auto'`), AND v4 puts a **24px margin between consecutive rows** (`.v-row + .v-row`) where v2 gave them `-12px` — so the original's `pt-4`, which existed to compensate for that negative margin, stacked on top of a gap that already existed. | `89b9f36` |
+| Log text lost its left margin, rows too far apart | A porting error, not a Vuetify default. The original kept the list item's default `px-4` and put `pa-0 pr-2` on the inner ``; v4 deleted that wrapper so the port moved `pa-0` onto the list item, killing the horizontal padding AND adding vertical padding on top of `my-2`. `py-0 pr-2`. | `210b2a2` |
+| Job rows / settings client rows cramped | Vuetify 2 sized a list item from what was inside its `v-list-item-content`, so a title + subtitle automatically got the taller two-line box. v4 deleted that wrapper and must be told: `lines="two"`. The five log rows in Client.vue are subtitle-only and stay single-line. | `210b2a2`, `40716a3` |
+| Home icon 12px below the buttons | The original's `pt-3` existed because v2's `v-list-item-icon` was TOP-aligned. v4's `#prepend` is already centred, so the padding just dropped it. | `210b2a2` |
+| Disabled buttons rendered as ENABLED | v2 neutralised them (`bg rgba(255,255,255,.12)`, `text rgba(255,255,255,.3)`); v4 keeps the button's own colour, so /settings' Submit sat in solid blue and the data-table's delete glowed red while unclickable. Restored app-wide in `vuetify2-compat.css` with v2's literal dark values (the theme is dark-only). | `40716a3` |
+| Checkbox glyph 20px right of its column | Two causes stacked: the row's `px-3` was no longer cancelled by `.v-row`'s dropped `-12px` margin, and v4's checkbox reserves an **8px ripple gutter inside the control** so its glyph no longer starts at the component's left edge. `ml-n2` takes that back. | `5f4c501` |
+| Encoder Tag field collapsed / misaligned / buttons off-centre | `v-col` with no `v-row` (see grid section), then the new row needed `px-3` because v4's col has no padding, then the buttons' `mt-3` could not centre them without it — `d-flex align-center` on the column instead. | `d3d956e`, `78293bb` |
+| Module setting fields stretched tall | See the `hideDetails` section — flex `align-items: stretch` plus a help button made taller than the field by its own margins. All five fields now measure 56px, as in the reference. | `793452d` |
+| Card titles cramped | v4 halved a card title's vertical padding (`16px` → `8px 16px`, 64px → 44px) on EVERY card. Restored as a `VCardTitle` default **class** (`py-4`), not a CSS rule — the components that deliberately zero it (Module.vue's header, Client.vue's four log headers) carry `pb-0`, which beats `py-4` on source order as a fellow utility. An unlayered CSS override would have outranked and broken all five. | `26c033e` |
+| Module cards had no contrast (again) | `bg-background` (#121212) made the pane the same colour as the page. A dedicated `module-pane` token (#1A1A1A) sits between the page and the cards (#242424). | `793452d` |
+
+---
+
+## Known pre-existing bugs — DO NOT "fix" these silently
+
+Found during the migration, present in the Vuetify 2 original too. They were deliberately
+preserved so the migration would not smuggle in behaviour changes. They are the user's
+call:
+
+- `jobs.vue`: `selectedJobs.length` bug means the Delete button never shows a count.
+- `jobs.vue`: `reassignJobs()`'s `idx % 5` batching is INERT — `idx` is a client-ID string,
+ so both branches are always false and the batch never flushes.
+- `Client.vue`: a `:v-show=` binding that has never done anything; a duplicated
+ `showProcessedLog = false`; the author's own `//todo: BUGGED!!!!` dead branch.
+- `Client.vue` emits `offlineClient` as an OBJECT from `onclose` but a bare STRING from
+ `onerror`; `index.vue`'s handler would throw on the string. Unreachable today.
+- `Age/ErrorReplace/ErrorSkip/MaxSizeSettings`: a `watch` on `selectedFormat`, which does
+ not exist in their data or props — so they have NEVER emitted `newdata`.
+ `SizeApproxSettings` has the same problem via a missing `deep: true`. It does not matter
+ in practice: `settingsInternal` is the SAME object reference as
+ `config.Modules.X.Settings`, so `v-model` mutates the config in place (proved by changing
+ AgeModule MaxAge 5→99 and confirming the export contained 99).
+- `config.vue`: `color="gray darken-3"` is an invalid palette key ("grey" is the real one).
+ It was ALREADY a no-op typo in Vuetify 2, so hyphenating it would ADD colour the page
+ never had.
+- `v-col xs="6"`: dead in BOTH versions. Vuetify 2's `VCol` breakpoints were
+ `['sm','md','lg','xl']` — there has never been an `xs` prop. Converting it to `cols`
+ would ADD a span the page never had.
+- Client ordering on `/` is nondeterministic in BOTH versions — neither sorts; both push
+ clients in whatever order their `/alive` probe wins. Verified across three reloads each.
+ If active-first sorting is wanted, that is a new feature, not a fix.
+
+## Never verified — needs the user
+
+- **No Docker image has ever been built.** Docker is not installed on this machine and it
+ was scoped out. The multi-stage Dockerfile, `node:24-alpine`, and the `VITE_COMMIT_SHA`
+ build arg (which drives the footer's commit hash) are all untested.
+- Client.vue's FileWalker / Mover render paths were never exercised (no daemon was in
+ those states).
diff --git a/pages/clients.txt b/docs/clients.txt
similarity index 100%
rename from pages/clients.txt
rename to docs/clients.txt
diff --git a/docs/superpowers/baselines/config.txt b/docs/superpowers/baselines/config.txt
new file mode 100644
index 0000000..01a03c4
--- /dev/null
+++ b/docs/superpowers/baselines/config.txt
@@ -0,0 +1,66 @@
+# http://localhost:3300/config
+
+title: Avior - powered by Walzen Group
+
+body text length: 124
+
+table rows: 0
+
+## headings (0)
+ (none)
+
+## nav items (5)
+ - Overview -> /
+ - Job Manager -> /jobs
+ - Client Configuration -> /config
+ - Global Configuration -> /globalconfig
+ - Frontend Settings -> /settings
+
+## tabs (0)
+ (none)
+
+## buttons (4)
+ -
+ -
+ -
+ - Client
+
+## links (7)
+ - Overview -> /
+ - Job Manager -> /jobs
+ - Client Configuration -> /config
+ - Global Configuration -> /globalconfig
+ - Frontend Settings -> /settings
+ - Icon -> https://dryicons.com/icon/love-file-icon-6200
+ - dev -> https://github.com/spiritreader/avior-nuxt/commit/dev
+
+## inputs (1)
+ - input[text] Client
+
+## table columns (0)
+ (none)
+
+## list items (5)
+ - Overview
+ - Job Manager
+ - Client Configuration
+ - Global Configuration
+ - Frontend Settings
+
+## vuetify components rendered (16)
+ - v-app-bar
+ - v-application
+ - v-btn
+ - v-footer
+ - v-icon
+ - v-input
+ - v-label
+ - v-list
+ - v-list-item
+ - v-main
+ - v-messages
+ - v-navigation-drawer
+ - v-select
+ - v-sheet
+ - v-text-field
+ - v-toolbar
\ No newline at end of file
diff --git a/docs/superpowers/baselines/globalconfig.txt b/docs/superpowers/baselines/globalconfig.txt
new file mode 100644
index 0000000..00ff848
--- /dev/null
+++ b/docs/superpowers/baselines/globalconfig.txt
@@ -0,0 +1,97 @@
+# http://localhost:3300/globalconfig
+
+title: Avior - powered by Walzen Group
+
+body text length: 220
+
+table rows: 0
+
+## headings (0)
+ (none)
+
+## nav items (5)
+ - Overview -> /
+ - Job Manager -> /jobs
+ - Client Configuration -> /config
+ - Global Configuration -> /globalconfig
+ - Frontend Settings -> /settings
+
+## tabs (5)
+ - CLIENTS
+ - NAME EXCLUDE
+ - SUB EXCLUDE
+ - LOG INCLUDE
+ - LOG EXCLUDE
+
+## buttons (11)
+ -
+ -
+ -
+ - NEW
+ - VDR-U
+ - VDR-U-1
+ - PHOENIX
+ - VAVA
+ - VDR-U-2
+ - MMDG
+ - VDR
+
+## links (12)
+ - Overview -> /
+ - Job Manager -> /jobs
+ - Client Configuration -> /config
+ - Global Configuration -> /globalconfig
+ - Frontend Settings -> /settings
+ - CLIENTS -> #tab-1
+ - NAME EXCLUDE -> #tab-2
+ - SUB EXCLUDE -> #tab-3
+ - LOG INCLUDE -> #tab-4
+ - LOG EXCLUDE -> #tab-5
+ - Icon -> https://dryicons.com/icon/love-file-icon-6200
+ - dev -> https://github.com/spiritreader/avior-nuxt/commit/dev
+
+## inputs (0)
+ (none)
+
+## table columns (0)
+ (none)
+
+## list items (12)
+ - Overview
+ - Job Manager
+ - Client Configuration
+ - Global Configuration
+ - Frontend Settings
+ - VDR-U
+ - VDR-U-1
+ - PHOENIX
+ - VAVA
+ - VDR-U-2
+ - MMDG
+ - VDR
+
+## vuetify components rendered (24)
+ - v-app-bar
+ - v-application
+ - v-btn
+ - v-card
+ - v-footer
+ - v-icon
+ - v-item-group
+ - v-list
+ - v-list-group
+ - v-list-item
+ - v-main
+ - v-navigation-drawer
+ - v-overlay
+ - v-sheet
+ - v-slide-group
+ - v-tab
+ - v-tabs
+ - v-tabs-bar
+ - v-tabs-items
+ - v-tabs-slider
+ - v-tabs-slider-wrapper
+ - v-toolbar
+ - v-window
+ - v-window-item
\ No newline at end of file
diff --git a/docs/superpowers/baselines/index.txt b/docs/superpowers/baselines/index.txt
new file mode 100644
index 0000000..80c323d
--- /dev/null
+++ b/docs/superpowers/baselines/index.txt
@@ -0,0 +1,86 @@
+# http://localhost:3300/
+
+title: Avior - powered by Walzen Group
+
+body text length: 263
+
+table rows: 0
+
+## headings (0)
+ (none)
+
+## nav items (5)
+ - Overview -> /
+ - Job Manager -> /jobs
+ - Client Configuration -> /config
+ - Global Configuration -> /globalconfig
+ - Frontend Settings -> /settings
+
+## tabs (0)
+ (none)
+
+## buttons (26)
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ - PING OFFLINE
+
+## links (7)
+ - Overview -> /
+ - Job Manager -> /jobs
+ - Client Configuration -> /config
+ - Global Configuration -> /globalconfig
+ - Frontend Settings -> /settings
+ - Icon -> https://dryicons.com/icon/love-file-icon-6200
+ - dev -> https://github.com/spiritreader/avior-nuxt/commit/dev
+
+## inputs (0)
+ (none)
+
+## table columns (0)
+ (none)
+
+## list items (5)
+ - Overview
+ - Job Manager
+ - Client Configuration
+ - Global Configuration
+ - Frontend Settings
+
+## vuetify components rendered (14)
+ - v-app-bar
+ - v-application
+ - v-btn
+ - v-btn-toggle
+ - v-card
+ - v-footer
+ - v-icon
+ - v-item-group
+ - v-list
+ - v-list-item
+ - v-main
+ - v-navigation-drawer
+ - v-sheet
+ - v-toolbar
\ No newline at end of file
diff --git a/docs/superpowers/baselines/jobs.txt b/docs/superpowers/baselines/jobs.txt
new file mode 100644
index 0000000..c1ee15e
--- /dev/null
+++ b/docs/superpowers/baselines/jobs.txt
@@ -0,0 +1,81 @@
+# http://localhost:3300/jobs
+
+title: Avior - powered by Walzen Group
+
+body text length: 293
+
+table rows: 0
+
+## headings (0)
+ (none)
+
+## nav items (5)
+ - Overview -> /
+ - Job Manager -> /jobs
+ - Client Configuration -> /config
+ - Global Configuration -> /globalconfig
+ - Frontend Settings -> /settings
+
+## tabs (0)
+ (none)
+
+## buttons (13)
+ -
+ -
+ -
+ - NEW
+ - REASSIGN
+ - DELETE
+ - VDR-U 32 assigned
+ - VDR-U-1 32 assigned
+ - PHOENIX 0 assigned
+ - VAVA 0 assigned
+ - VDR-U-2 0 assigned
+ - MMDG 0 assigned
+ - VDR 0 assigned
+
+## links (7)
+ - Overview -> /
+ - Job Manager -> /jobs
+ - Client Configuration -> /config
+ - Global Configuration -> /globalconfig
+ - Frontend Settings -> /settings
+ - Icon -> https://dryicons.com/icon/love-file-icon-6200
+ - dev -> https://github.com/spiritreader/avior-nuxt/commit/dev
+
+## inputs (0)
+ (none)
+
+## table columns (0)
+ (none)
+
+## list items (13)
+ - Overview
+ - Job Manager
+ - Client Configuration
+ - Global Configuration
+ - Frontend Settings
+ - NEW REASSIGN DELETE
+ - VDR-U 32 assigned
+ - VDR-U-1 32 assigned
+ - PHOENIX 0 assigned
+ - VAVA 0 assigned
+ - VDR-U-2 0 assigned
+ - MMDG 0 assigned
+ - VDR 0 assigned
+
+## vuetify components rendered (14)
+ - v-app-bar
+ - v-application
+ - v-btn
+ - v-card
+ - v-footer
+ - v-icon
+ - v-list
+ - v-list-group
+ - v-list-item
+ - v-main
+ - v-navigation-drawer
+ - v-sheet
+ - v-system-bar
+ - v-toolbar
\ No newline at end of file
diff --git a/docs/superpowers/baselines/settings.txt b/docs/superpowers/baselines/settings.txt
new file mode 100644
index 0000000..3ad7f62
--- /dev/null
+++ b/docs/superpowers/baselines/settings.txt
@@ -0,0 +1,81 @@
+# http://localhost:3300/settings
+
+title: Avior - powered by Walzen Group
+
+body text length: 624
+
+table rows: 0
+
+## headings (1)
+ - Settings
+
+## nav items (5)
+ - Overview -> /
+ - Job Manager -> /jobs
+ - Client Configuration -> /config
+ - Global Configuration -> /globalconfig
+ - Frontend Settings -> /settings
+
+## tabs (0)
+ (none)
+
+## buttons (9)
+ -
+ -
+ -
+ - SUBMIT
+ -
+ -
+ -
+ -
+ -
+
+## links (7)
+ - Overview -> /
+ - Job Manager -> /jobs
+ - Client Configuration -> /config
+ - Global Configuration -> /globalconfig
+ - Frontend Settings -> /settings
+ - Icon -> https://dryicons.com/icon/love-file-icon-6200
+ - dev -> https://github.com/spiritreader/avior-nuxt/commit/dev
+
+## inputs (2)
+ - input[text] Client Name
+ - input[text] Add Address
+
+## table columns (0)
+ (none)
+
+## list items (11)
+ - Overview
+ - Job Manager
+ - Client Configuration
+ - Global Configuration
+ - Frontend Settings
+ - Add Address
+ - Phoenix http://192.168.178.60:10000, http://10.10.10.85:10000, http://172.31.0.85:10000
+ - VAVA http://10.11.194.2:10000, http://192.168.178.51:10000
+ - VDR-U http://192.168.178.61:10000, http://vdr-u.wan.walzen.org:10000, http://10.11.194.61:10000
+ - VDR-U-1 http://192.168.178.61:10001, http://vdr-u.wan.walzen.org:10001, http://10.11.194.61:10001
+ - VDR-U-2 http://192.168.178.61:10002, http://vdr-u.wan.walzen.org:10002, http://10.11.194.61:10002
+
+## vuetify components rendered (19)
+ - v-app-bar
+ - v-application
+ - v-btn
+ - v-card
+ - v-counter
+ - v-divider
+ - v-footer
+ - v-form
+ - v-icon
+ - v-input
+ - v-label
+ - v-list
+ - v-list-item
+ - v-main
+ - v-messages
+ - v-navigation-drawer
+ - v-sheet
+ - v-text-field
+ - v-toolbar
\ No newline at end of file
diff --git a/docs/superpowers/plans/2026-07-13-nuxt2-to-vue3-migration.md b/docs/superpowers/plans/2026-07-13-nuxt2-to-vue3-migration.md
new file mode 100644
index 0000000..e06605f
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-13-nuxt2-to-vue3-migration.md
@@ -0,0 +1,1600 @@
+# Nuxt 2 to Vue 3 SPA Migration — Implementation Plan
+
+> For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+Goal: Migrate the Avior admin dashboard from Nuxt 2 / Vue 2 / Vuetify 2 (SSR) to a Vue 3 / Vite / Vuetify 4 single-page app served by a standalone Express server, on pnpm, with `
+