Skip to content
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
054c662
test(sync): cover the modified-time merge path for shows/projects/sta…
lucaxs09 Jun 10, 2026
c1830d3
Fixed Next on media finished sometimes triggering twice
vassbo Jun 11, 2026
9940dea
Fixed errors
vassbo Jun 11, 2026
034f370
Backup & Cloud stores .show files directly in the zip
vassbo Jun 11, 2026
7466432
Fixed Text edit item order #3380
vassbo Jun 15, 2026
0d2bde1
Fixed slide Specific outputs not working properly with shortcuts #3370
vassbo Jun 15, 2026
3f0f455
Removed leftover comment
vassbo Jun 15, 2026
3823fb0
Repair show/lyrics search scorer, add caching, and support exact-phra…
jcfurey Jun 16, 2026
11d01ea
Locale placeholders
vassbo Jun 17, 2026
388e87b
Fixed NDI Audio issue
vassbo Jun 17, 2026
9e03a26
Updated Korean language
vassbo Jun 17, 2026
2488291
Scripture shows now prioritize the category icon
vassbo Jun 17, 2026
ee6818d
Added CSS variable --slide-group-color
vassbo Jun 17, 2026
598e759
Action custom activation on Foreground/background specific videos
vassbo Jun 17, 2026
41ce640
Tweaks
vassbo Jun 17, 2026
b46e09e
Fixed project media export not working with auto located media files
vassbo Jun 17, 2026
3c9c847
Tweak
vassbo Jun 17, 2026
0fc5587
Fixed slides sometimes getting out of sync when changing rapidly
vassbo Jun 17, 2026
d8c01e0
Slide loading tweaks
vassbo Jun 17, 2026
15bcbf4
Changed protected cleanup to last accessed
vassbo Jun 17, 2026
e62a4f4
Fixed minimize timer
vassbo Jun 17, 2026
2e6bbbd
Security & dependency hardening: clear npm advisories + commit lockfi…
jcfurey Jun 17, 2026
b7c095f
feat: migrate to Svelte 5 + Vite 8 + TypeScript 5 (compatibility mode)
claude Jun 10, 2026
9627180
fix(svelte5): preserve pre-migration type strictness (disable strict …
claude Jun 10, 2026
5f09651
build(svelte5): reconcile deps for standalone migration off dev
claude Jun 16, 2026
e502591
fix(svelte5): build on Vite 8 (type-only imports) + restore |global t…
claude Jun 16, 2026
84030aa
fix(svelte5): bump vitest 2→4 so `npm ci` installs on vite 8
claude Jun 16, 2026
172eac4
fix(svelte5): resolve remote logo via asset import (Vite 8 iife impor…
claude Jun 17, 2026
a5f2efc
build(svelte5): rebase onto dev (post-#3384), regenerate lockfile, ad…
claude Jun 17, 2026
6b287be
fix(svelte5): drop legacy a11y- check in vite.config.mjs
claude Jun 17, 2026
f94218d
refactor(svelte5): address review nits (comments + dead asset fallback)
claude Jun 17, 2026
e8252fb
refactor(svelte5): shorten the Auth.svelte logo-import comment
claude Jun 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Collapse the generated lockfile in diffs.
package-lock.json linguist-generated=true
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: CI

# Manual activation for now (run from the Actions tab); can be wired to pull_request later.
on:
workflow_dispatch:

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 this run.
- name: Build
run: npm run build
2 changes: 1 addition & 1 deletion .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ 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
- name: Install dependencies
Expand Down
11 changes: 3 additions & 8 deletions config/building/vite.config.servers.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import sveltePreprocess from 'svelte-preprocess'
import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte'
import { resolve } from 'path'
import { copyFileSync, mkdirSync, existsSync, readFileSync, createReadStream } from 'fs'

Expand Down Expand Up @@ -128,17 +127,13 @@ function copyServerFilesPlugin(serverId) {
export default defineConfig({
plugins: [
svelte({
preprocess: sveltePreprocess({
typescript: serverConfig.typescript ? {
tsconfigFile: `./config/typescript/tsconfig.server${production ? '.prod' : ''}.json`,
} : false,
}),
preprocess: vitePreprocess(),
compilerOptions: {
dev: !production,
},
onwarn: (warning, handler) => {
// disable A11y warnings
if (warning.code.startsWith('a11y-')) return
if (warning.code.startsWith('a11y_')) return
handler(warning)
},
}),
Expand Down
45 changes: 0 additions & 45 deletions config/linting/eslint.svelte.js

This file was deleted.

2 changes: 2 additions & 0 deletions config/testing/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { defineConfig } from "@playwright/test"

export default defineConfig({
// the single E2E walks the whole app with several built-in delays; give it headroom
timeout: 90_000,
// 'github' for GitHub Actions CI to generate annotations, plus a concise 'dot'
// default 'line' when running locally
reporter: [[process.env.CI ? "html" : "line", { outputFolder: "test-output/playwright-report" }]],
Expand Down
71 changes: 42 additions & 29 deletions config/testing/start.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,14 @@ test("Launch electron app", async () => {
})
console.log(appPath)

// Get the first window that the app opens, wait if necessary.
const window = await electronApp.firstWindow()
// The app opens a small loading/splash window first, then the main window.
// firstWindow() can return the splash, so explicitly pick the main window (it loads index.html).
let window = electronApp.windows().find((w) => w.url().includes("index.html"))
for (let i = 0; i < 20 && !window; i++) {
await delay(500)
window = electronApp.windows().find((w) => w.url().includes("index.html"))
}
if (!window) window = await electronApp.firstWindow()

// Direct Electron console to Node terminal.
window.on("console", console.log)
Expand All @@ -50,30 +56,39 @@ test("Launch electron app", async () => {
// Capture a screenshot.
// await window.screenshot({ path: "intro.png" })

// Initial setup
// Set language to English
await window.locator(".main .dropdownElem").getByRole("button").click({ timeout: 5 * timeoutMs })
await window.locator(".main .dropdownElem .dropdown #id_English").click({ timeout: timeoutMs })
// This triggers the Electron open dialog, mocked above
await window.locator(".main .showElem").getByRole("button").click()
await window.getByText("Get Started!").click({ timeout: timeoutMs })
// This depends on whether there is existing shows to be loaded.
// As the existing show folder is also mocked to be a tmp folder,
// this is not expected.
// await window.getByTestId("alert.ack.check").click({ timeout: timeoutMs })
// Wait for the app UI to be interactive: either the first-run setup popup or the main top bar.
await window.locator(".popup button.start, .top").first().waitFor({ timeout: 10 * timeoutMs })

// First-run setup popup (Initialize.svelte) — only shown when the app isn't initialized yet.
// It can be absent if a previous run already initialized the user data, so guard it.
const setupStart = window.locator(".popup button.start")
let didSetup = false
if ((await setupStart.count()) > 0) {
const setupPopup = window.locator(".popup")

// Set language to English (it is the default, but select it explicitly so the English text selectors below stay stable)
await setupPopup.locator(".dropdown-trigger").first().click({ timeout: 5 * timeoutMs })
await setupPopup.locator("li[role=option]").filter({ hasText: "English" }).first().click({ timeout: timeoutMs })

// Give time to save initial state
// await delay(4_000)
// Set the data location via the folder picker; this triggers the Electron open dialog, mocked above
await setupPopup.locator(".button-trigger").first().click({ timeout: timeoutMs })

// Finish setup ("Get started!")
await setupStart.click({ timeout: timeoutMs })
didSetup = true
}

// skip onboarding flow
await window.locator("#guideButtons").getByText("Skip").click({ timeout: timeoutMs })
// skip the onboarding guide (it opens right after a fresh setup; its overlay otherwise intercepts clicks)
const skipGuide = window.locator("#guideButtons").getByText("Skip")
if (didSetup) await skipGuide.waitFor({ timeout: 5 * timeoutMs })
if ((await skipGuide.count()) > 0) await skipGuide.click({ timeout: timeoutMs })

// Create a new project, then try creating a new show under the project
await window.locator("#leftPanel").getByText("New project").click({ timeout: timeoutMs })
await window.getByText("New project").first().click({ timeout: timeoutMs })
await window.getByText("New show").first().click({ timeout: timeoutMs })

// Expect the pop up to be visible
await expect(window.getByText("Name")).toBeVisible({ timeout: timeoutMs })
// Expect the create-show popup to be visible (the name input is part of it)
await expect(window.locator("#name")).toBeVisible({ timeout: timeoutMs })

// Fill name of show
await window.locator("#name").fill("New Test Show", { timeout: timeoutMs })
Expand All @@ -91,19 +106,17 @@ test("Launch electron app", async () => {
// Click new show
await window.getByTestId("create.show.popup.new.show").click({ timeout: timeoutMs })

// Try changing group for Chorus
await window.locator("#group").getByTitle("Chorus").click({ timeout: timeoutMs })
// Try changing group for Chorus (group names render as text in the #group list)
await window.locator("#group").getByText("Chorus").first().click({ timeout: timeoutMs })
//await window.getByText("Change group").hover({ timeout: timeoutMs })
await window.locator("#group").getByText("Verse").click({ timeout: 5 * timeoutMs })
await window.locator("#group").getByText("Verse").first().click({ timeout: 5 * timeoutMs })

// Verify the group changing was successful
await expect(window.locator("#group").getByTitle("Verse")).toBeVisible({ timeout: timeoutMs })
await expect(window.locator("#group").getByText("Verse").first()).toBeVisible({ timeout: timeoutMs })

// Manual save!
await window.locator(".top").getByText("FreeShow").click({ button: "right", timeout: timeoutMs })
await window.getByText("Save", { exact: true }).click({ timeout: timeoutMs })
// await window.keyboard.press("Control+S")
// await window.keyboard.press("Meta+S")
// Manual save via keyboard shortcut (Ctrl+S) — robust across menu changes; the app also auto-saves
await window.keyboard.press("Escape")
await window.keyboard.press("Control+s")
await delay(5_000)
} catch (ex) {
console.log("Taking screenshot")
Expand Down
Loading