Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3a33c40
Fix security & correctness findings from code audit
claude Jun 9, 2026
693413b
deps: clear all critical + 3 high advisories (low-risk subset)
claude Jun 9, 2026
9214dfa
deps: commit lockfile for security fixes; correct lockfile-status err…
claude Jun 10, 2026
f06dda9
deps: upgrade Electron 37 -> 40 (clears electron advisory)
claude Jun 10, 2026
bc61ed0
test(e2e): repair stale Playwright smoke test for current UI + Electr…
claude Jun 10, 2026
60602aa
deps: bump @rollup/plugin-terser 0.4->1.0 (clears serialize-javascrip…
claude Jun 10, 2026
80b7025
deps: upgrade music-metadata 7->11 (clears ASF DoS High + file-type m…
claude Jun 10, 2026
59d9015
chore: mark package-lock.json as generated so GitHub collapses it in …
claude Jun 10, 2026
e513836
chore: treat package-lock.json as binary in diffs (-diff) so it's exc…
claude Jun 10, 2026
c53fb58
chore: drop -diff on lockfile (no effect in PR view, costs local text…
claude Jun 10, 2026
b70680b
security: replace new Function() math eval with a safe arithmetic par…
claude Jun 10, 2026
0f10f95
build: migrate ESLint 8 -> 9 (flat config) + typescript-eslint 8 + es…
claude Jun 10, 2026
2405646
build: remove legacy eslintrc files superseded by the flat config
claude Jun 10, 2026
afedf4c
style: apply eslint --fix autofixes from the ESLint 9 migration
claude Jun 10, 2026
50f70a8
feat: migrate to Svelte 5 + Vite 8 + TypeScript 5 (compatibility mode)
claude Jun 10, 2026
fe99671
fix(svelte5): preserve pre-migration type strictness (disable strict …
claude Jun 10, 2026
47e27ee
docs: add per-platform BUILDING.md (Linux/macOS/Windows 10/11) + fix …
claude Jun 10, 2026
709450b
build: add Dockerfile + .dockerignore for a reproducible Linux build …
claude Jun 10, 2026
07e3c26
ci: use npm ci everywhere now that the lockfile is committed
claude Jun 10, 2026
b5ef392
style(svelte5): close self-closing non-void HTML elements + enforce g…
claude Jun 10, 2026
9394672
a11y(svelte5): fix the zero-risk a11y warnings (alt text, icon-button…
claude Jun 10, 2026
21f81fd
a11y(svelte5): acknowledge intentional autofocus with svelte-ignore (8)
claude Jun 10, 2026
0778479
a11y(svelte5): add keyboard activation to the focusable color swatches
claude Jun 10, 2026
fa4797d
docs: add ACCESSIBILITY.md — scope/roadmap for operator-UI keyboard a11y
claude Jun 10, 2026
e163a7a
fix(vite): restore dev index.html entry so `npm start` works
claude Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
21 changes: 21 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Keep the Docker build context small and force a clean install/build in the image.
.git
node_modules
build
dist
out
public/build
public/*.js.map
snap

# generated tsconfigs (created by scripts/preBuild.js)
config/typescript/tsconfig.*.prod.json

# local + CI noise
*.log
test-output
test-results
.DS_Store
.idea
.vscode
*.env
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Mark the lockfile as generated so GitHub collapses it in diffs.
# NOTE: GitHub reads this from the repository's DEFAULT branch, so it only takes
# effect for PRs once this file is merged to the default branch — not within the PR that adds it.
# It collapses the rendered diff; it does NOT remove the file from a PR's +/- line counts
# (GitHub has no supported way to do that).
package-lock.json linguist-generated=true
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
node-version: 22

- name: Install dependencies
run: npm install
run: npm ci

- name: Build app
run: npm run build
43 changes: 43 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: CI

# Fast checks on every PR and on pushes to main, so type errors and formatting
# regressions are caught before merge (the full Playwright/build workflows are manual).
on:
pull_request:
push:
branches: [main]

jobs:
checks:
name: Format & type-check
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v4

- name: Install Node.js and NPM
uses: actions/setup-node@v4
with:
node-version: 22

- name: Install libraries
run: |
sudo apt-get update
sudo apt-get install -y libfontconfig1-dev uuid-dev libltc-dev --fix-missing

- name: Install dependencies
run: npm ci

# Formatting and svelte type-check currently have a pre-existing backlog, so they are
# reported but non-blocking for now. Flip continue-on-error off once the backlog is cleared.
- name: Check formatting
run: npm run test:format
continue-on-error: true

- name: Type-check (svelte)
run: npm run test:svelte
continue-on-error: true

# Blocking gate: a broken build fails the PR.
- name: Build
run: npm run build
6 changes: 3 additions & 3 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
- name: Install libraries
run: sudo apt-get install libfontconfig1-dev
run: sudo apt-get install -y libfontconfig1-dev uuid-dev libltc-dev
- name: Install dependencies
run: npm install
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Build
Expand Down
9 changes: 4 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ jobs:
node-version: 22

- name: Install dependencies
# npm ci is better, but requires package-lock.json file
run: npm install
run: npm ci

- name: Build and release app
env:
Expand Down Expand Up @@ -51,7 +50,7 @@ jobs:
sudo apt-get install -y libfontconfig1-dev uuid-dev libltc-dev --fix-missing

- name: Install dependencies
run: npm install
run: npm ci

- name: Build and release app
env:
Expand Down Expand Up @@ -84,7 +83,7 @@ jobs:
# run: npm run replace-electron

- name: Install dependencies
run: npm install
run: npm ci

- name: Install FPM
run:
Expand Down Expand Up @@ -127,7 +126,7 @@ jobs:
run: npm install -g node-gyp

- name: Install dependencies
run: npm install
run: npm ci

- name: Install dmg-license
run: npm i dmg-license
Expand Down
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# NPM issue with build on GitHub Actions
# Error: Cannot find module @rollup/rollup-x-gnu. npm has a bug related to optional dependencies.
# package-lock.json
# package-lock.json is committed for reproducible installs (npm ci). The old
# @rollup/rollup-x-gnu optional-deps bug that prompted ignoring it was fixed in npm 10.

/node_modules/
/public/build/
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22
85 changes: 85 additions & 0 deletions ACCESSIBILITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Accessibility plan — operator UI keyboard navigation

Scope/roadmap for finishing the Svelte accessibility work that was deferred during the Svelte 5 migration. This is a **planning doc**, not a spec — it inventories what's left, the fix patterns, the risks, and a phased approach so the work can be done deliberately (with hands-on keyboard/screen-reader testing, which is the part that can't be automated).

## Background

- FreeShow is an operator-driven presentation app. Historically a11y was **not enforced**: the build filters all `a11y_*` warnings via `onwarn` in `vite.config.mjs` and `config/building/vite.config.servers.mjs`, so they never fail a build.
- The Svelte 5 migration (svelte-check 4) surfaces them in `npm run test:svelte` (report-only — not a CI gate).
- A first pass already landed the **zero-risk** fixes (alt text, icon-button `aria-label`, `aria-selected`, `svelte-ignore a11y_autofocus`, and 2 keyboard-completions), taking svelte-check from **144 → 125 a11y warnings**. This doc covers the remaining **~125**, which require per-component judgment + manual QA.

## What's left (125 warnings)

By category:

| Count | Warning | Typical fix |
|---|---|---|
| ~66 | element with a mouse/click/dblclick/drag/etc. handler **must have an ARIA role** | add a role (`button`, `listbox`, …) + make it focusable + keyboard, or convert to a native element |
| 22 | non-interactive element with a click event **must have a keyboard handler** | add `on:keydown` (Enter/Space) |
| 6 | non-interactive element **cannot have a non-negative `tabindex`** | add a role, or set `tabindex={-1}` |
| 5 | **form label not associated** with a control | `for={id}` / `aria-labelledby`, or restructure |
| 3 | non-interactive element **should not have mouse/keyboard listeners** | move the listener to a real control, or add a role |

By component area (where to focus):

| Warnings | Area | Notes |
|---|---|---|
| 27 | `components/inputs` (Material*) | **shared form controls** — high leverage, keyboard a11y clearly wanted, contained |
| 14 | `server/remote` | separate web app (touch/keyboard on user devices) |
| 13 | `components/edit` | editor — `EditboxCropping.svelte` (9) is dense |
| 12 | `components/output` | preview/controls |
| 11 | `components/timeline` | already has custom interactions |
| 10 | `components/system` | `SelectElem.svelte` (6) — the **drag/drop wrapper used app-wide** |
| 9 | `components/main` | |
| 8 | `components/drawer` | |
| 7 | `components/slide` | slide grids — already have arrow-key navigation |

Highest-warning files: `MaterialColorInput` (14), `EditboxCropping` (9), `Timeline` (6), `SelectElem` (6), `TimelineEasing` (5), `SpotifyController` (5), `MaterialDropdown` (5).

## Fix patterns

The codebase already has helpers in `src/frontend/utils/clickable.ts`:
- **`triggerClickOnEnterSpace(event)`** — a `keydown` handler that calls the element's own `.click()` on Enter/Space (reuses the existing `on:click`, no logic duplication). It already guards inputs/textareas/`.slide`.
- **`createKeydownHandler(cb)`** — same guards, calls a custom callback.

Per category:

- **Clickable `<div>`/`<span>` (button-like):** prefer a native `<button>` **only when no CSS depends on the tag** (buttons carry default styling/reset, so this can regress visuals). Otherwise keep the element and add `role="button" tabindex="0" on:keydown={triggerClickOnEnterSpace}` — preserves styling, adds a real tab stop + keyboard activation.
- **Already focusable, missing keyboard:** just add `on:keydown={triggerClickOnEnterSpace}` (no new tab stop). These are the safest.
- **`tabindex` on a non-interactive element:** decide intent — if it's meant to be operable, add a role; if it's just a focus target, `tabindex={-1}`.
- **Form labels:** add `for={id}` when the control has an `id`; the Material floating-label components (`MaterialColorInput`, `MaterialFilePicker`, `MaterialFolderPicker`, `MaterialPopupButton`, `MaterialToggleButtons`) label custom controls — use `aria-labelledby`/`aria-label` on the control, or restructure (note: CSS targets `label`, so don't just swap the tag).
- **Listeners on non-interactive elements:** move the handler to a child `<button>`, or give the element a role if it really is the control.
- **Drag handlers (`dragover`, etc.):** these often aren't "buttons" — pick the semantically correct role (e.g. the element is a drop target) or `aria-*` rather than forcing `role="button"`.
- **i18n:** any `aria-label`/`aria-*` text should go through `translateText(...)` to match the rest of the app (decide this convention up front).

## Risk & testing (the reason this is deliberate, not mechanical)

- **Adding `tabindex` changes tab order.** Across the operator UI this is the biggest behavioral risk — every new tab stop reshuffles keyboard navigation. Each affected screen needs a manual tab-through.
- **Key conflicts:** Enter/Space activation can collide with existing key handling (e.g. text inputs, the slide grid, the timeline). `triggerClickOnEnterSpace` guards inputs/textareas/`.slide`, but timeline/editor interactions need checking.
- **Areas with existing navigation** (slide grid, timeline) may not want per-element tab stops at all — confirm the intended keyboard model before adding them.
- **Automated coverage is thin:** the single Playwright E2E test walks one happy path. It will catch crashes/regressions in that path but **not** keyboard/AT behavior. Real verification is manual: keyboard-only navigation per area, plus ideally a screen-reader pass (VoiceOver / NVDA).

## Phased plan

- **Phase 0 — safe wins (done).** alt/aria-label/aria-selected/autofocus + 2 keyboard-completions. _(this PR)_
- **Phase 1 — shared input components (`components/inputs`, ~27).** Highest leverage, lowest debate (form controls should be keyboard-operable). Fixes here propagate everywhere. Start with `MaterialColorInput` (14) and `MaterialDropdown` (5).
- **Phase 2 — shared interaction wrappers (`SelectElem` + `system`, ~10).** Very high leverage (used app-wide) but **high risk** — a change here affects every list/drag interaction. Careful, with heavy QA.
- **Phase 3 — per-area (`edit`, `output`, `timeline`, `drawer`, `slide`, `main`).** Larger and judgment-heavy; decide the keyboard model for grid/timeline areas first.
- **Phase 4 — server web apps (`server/remote`, `server/stage`, ~19).** A distinct user-facing surface (touch/keyboard on devices); own pass with its own QA.
- **Phase 5 — enforce (policy gate).** If a11y is to be maintained: drop the `a11y_*` `onwarn` filters, get svelte-check a11y to zero, and add it to CI (start non-blocking, then blocking).

## Policy decisions to make first

1. **Do we commit to enforcing a11y?** If yes → Phase 5 (remove the `onwarn` suppression + CI gate). If no → this stays best-effort and the warnings stay suppressed/report-only.
2. **Keyboard model for grid/timeline areas:** per-element tab stops vs. roving-`tabindex`/arrow-key navigation (the latter is usually better UX for grids but more work).
3. **`aria-label` i18n convention** (use `translateText`?).
4. **Scope priority:** operator UI (main window) first; the **output/stage display windows are non-interactive presentations** (lowest priority); the remote/stage **web apps** are their own user-facing surface.

## Non-goals

- The output/stage *projection* windows (read-only display — not operated by keyboard).
- A full WCAG 2.x audit / color-contrast / screen-reader optimization beyond clearing the svelte-check a11y warnings.

## Rough effort

Phase 1 ≈ half a day; Phase 2 ≈ half a day + careful QA; Phase 3 ≈ 1–2 days + QA; Phase 4 ≈ half a day. Plus hands-on keyboard/AT testing throughout. **~3–5 days of deliberate work**, gated on someone able to do the manual testing.
Loading