Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 6 additions & 5 deletions .github/WORKFLOW_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ This repository is configured with automated dependency updates and release buil

### 3. CI (`.github/workflows/ci.yml`)

- **Job (`ci`)**: on pull requests, `npm ci` then **`npm run ci`** on Ubuntu, Windows, and macOS
(format, lint, typecheck, production audit, commercial compliance verify, `test:coverage`,
`generate:channels:check`, the full `verify:*` suite, and `build` — see `package.json` for the
exact step list).
- **Job (`e2e-smoke`)**: Playwright smoke specs on Ubuntu and Windows.
- **Job (`ci`)**: `npm ci` then **`npm run ci`** (format, lint, typecheck, production audit,
commercial compliance verify, `test:coverage`, `generate:channels:check`, the full `verify:*`
suite, and `build` — see `package.json` for the exact step list). Pull requests run it on Ubuntu
and Windows; pushes add macOS.
- **Job (`e2e-smoke`)**: Playwright smoke specs on Ubuntu and Windows, on pushes only. macOS and the
smoke are push-only to keep Actions minutes down, so run the smoke specs locally before pushing.
- **Stricter local gate** (includes Electron-linked native check): `npm run local:gate` (=
`verify:installed-electron` + `npm run ci` + `verify:native-modules`).
- Triggered on pull requests and pushes to main/master/develop/RegressionFixes
Expand Down
27 changes: 16 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# Pull requests: run the same gate as packaged in `npm run ci` (format, lint, audit,
# compliance verify, tests + coverage, IPC contracts, production webpack build).
# PR CI also verifies Electron-linked native modules load on all three OSes. The
# macos-latest runner builds x64 while production ships arm64 — the x64 run still
# exercises the rebuild toolchain; arm64 ABI alignment is verified pre-release via
# `npm run local:gate` (see the inline note on the rebuild step below).
# compliance verify, tests + coverage, IPC contracts, production webpack build) on
# Ubuntu and Windows, and verify Electron-linked native modules load there.
#
# Push to long-lived branches (main/develop/RegressionFixes) runs the same gate to
# catch regressions that land via non-PR pushes (e.g. hotfix commits direct-to-branch).
# Push to long-lived branches (main/develop/RegressionFixes) runs the same gate on
# all three OSes plus the E2E smoke. macOS and the E2E smoke are push-only to keep
# Actions minutes down (macOS bills at ten times the Linux rate), so they report
# after a merge, not before it: the ruleset "Main: required checks" requires only
# `ci (ubuntu-latest)`, `ci (windows-latest)` and `CodeQL`. Run `npm run local:gate`
# and the smoke specs locally before pushing. The macos-latest runner builds x64
# while production ships arm64 — the x64 run still exercises the rebuild toolchain;
# arm64 ABI alignment is verified pre-release via `npm run local:gate` (see the
# inline note on the rebuild step below).

name: CI

Expand All @@ -33,7 +37,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
os: ${{ fromJSON(github.event_name == 'pull_request' && '["ubuntu-latest","windows-latest"]' || '["ubuntu-latest","windows-latest","macos-latest"]') }}
timeout-minutes: 60
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -99,13 +103,14 @@ jobs:
# The full `npm run test:e2e` suite (including @live-llm specs) is run manually
# before releases; there is no nightly E2E workflow.
e2e-smoke:
if: github.event_name != 'pull_request'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
# 45, not 20: each spec relaunches the app (~17 s/test on windows-latest),
# so the 91-test smoke list alone needs ~27 min plus ~5 min of npm ci +
# so the 101-test smoke list alone needs ~29 min plus ~5 min of npm ci +
# native rebuild + dev builds before Playwright even starts.
timeout-minutes: 45
steps:
Expand All @@ -126,7 +131,7 @@ jobs:
run: npm run build:dev
- name: Run xvfb-friendly E2E smoke
if: runner.os == 'Linux'
run: xvfb-run --auto-servernum -- npx playwright test test/e2e/app-launch.spec.js test/e2e/whisper-variant-loader.spec.js test/e2e/session-persistence.spec.js test/e2e/undo-redo.spec.js test/e2e/organize-phase.spec.js test/e2e/search-modal.spec.js test/e2e/settings-backup-restore.spec.js test/e2e/accessibility.spec.js --reporter=list
run: xvfb-run --auto-servernum -- npx playwright test test/e2e/app/app-launch.spec.js test/e2e/discover/whisper-variant-loader.spec.js test/e2e/app/session-persistence.spec.js test/e2e/organize/undo-redo.spec.js test/e2e/organize/organize-phase.spec.js test/e2e/search/search-modal.spec.js test/e2e/settings/settings-backup-restore.spec.js test/e2e/app/accessibility.spec.js --reporter=list
env:
CI: 'true'
# pw:browser* forwards the Electron child's stdout/stderr from spawn
Expand All @@ -135,7 +140,7 @@ jobs:
DEBUG: 'pw:browser*'
- name: Run E2E smoke (Windows)
if: runner.os == 'Windows'
run: npx playwright test test/e2e/app-launch.spec.js test/e2e/whisper-variant-loader.spec.js test/e2e/session-persistence.spec.js test/e2e/undo-redo.spec.js test/e2e/organize-phase.spec.js test/e2e/search-modal.spec.js test/e2e/settings-backup-restore.spec.js test/e2e/accessibility.spec.js --reporter=list
run: npx playwright test test/e2e/app/app-launch.spec.js test/e2e/discover/whisper-variant-loader.spec.js test/e2e/app/session-persistence.spec.js test/e2e/organize/undo-redo.spec.js test/e2e/organize/organize-phase.spec.js test/e2e/search/search-modal.spec.js test/e2e/settings/settings-backup-restore.spec.js test/e2e/app/accessibility.spec.js --reporter=list
env:
CI: 'true'
- name: Upload E2E artifacts on failure
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed

- **An image that arrives just as a text model starts loading no longer starts the vision runtime
beside it.** The image looked for a loading model before the load began and found none. The vision
runtime now checks the memory budget again at the moment it starts; if a load began meanwhile, the
image waits for it, makes room and is sent once more. Such a refusal does not count toward turning
vision off.
- **An embedding model put on the CPU beside a text model returns to the GPU when there is room.**
After a text-model load that failed, or once the text model was unloaded, it stayed on the CPU
until some other load replaced it.
- **A text model larger than the memory budget no longer unloads models that hold no GPU memory.**
An embedding model on the CPU and Whisper on the CPU were unloaded to make room, which freed
nothing, and were loaded again for the next file.

## [5.2.0] - 2026-11-10

Cloud analysis now covers audio and video, and video frames are sent only with your consent and the
Expand Down
8 changes: 5 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ npm run test:e2e # Playwright E2E tests
npm run lint # ESLint check
npm run format:check # Prettier check
npm run ci # format:check + lint + typecheck + audit:production + compliance:commercial:verify + test:coverage + verify:ipc-contracts + build, plus the full verify:* suite (channels, ipc handlers/schemas/enforcement, mcp + ai-provider contracts, analysis routing, electron fuses, env docs, version tag) — package.json is the authoritative list
npm run local:gate # verify:installed-electron + ci + verify:native-modules (one-shot pre-push convenience). GitHub Actions `CI` is configured for Windows + Ubuntu + macOS when Actions are enabled for the repo. Cloud CI has been off at the org/repo setting at times — `dependabot.yml` notes local gate as the only guaranteed gate. The e2e-smoke job is restricted to Windows + Ubuntu.
npm run local:gate # verify:installed-electron + ci + verify:native-modules (one-shot pre-push convenience). GitHub Actions `CI` runs Ubuntu + Windows on pull requests and adds macOS + the e2e-smoke job on pushes to main (trimmed 2026-11-11 to cut Actions minutes; required checks are `ci (ubuntu-latest)`, `ci (windows-latest)` and `CodeQL`), when Actions are enabled for the repo. Cloud CI has been off at the org/repo setting at times — `dependabot.yml` notes local gate as the only guaranteed gate. The e2e-smoke job is restricted to Windows + Ubuntu.
npm run dist:win # Build Windows installer
```

Expand Down Expand Up @@ -188,8 +188,10 @@ These are tracked gaps that the project slash commands help address:
counts are the output of [`.github/workflows/ci.yml`](.github/workflows/ci.yml) — treat CI logs as
source of truth rather than inline numbers here.
- ~~Coverage not enforced in CI~~ - CI now runs `npm run test:coverage` (enforces thresholds).
Cross-platform matrix (Windows, macOS, Linux). Coverage artifacts uploaded.
- ~~Single-platform CI~~ - CI runs on `windows-latest`, `ubuntu-latest`, and `macos-latest`.
Cross-platform matrix (Windows and Linux on pull requests; macOS added on pushes to main).
Coverage artifacts uploaded.
- ~~Single-platform CI~~ - CI runs on `windows-latest` and `ubuntu-latest` for pull requests, plus
`macos-latest` on pushes to main.
- ~~No IPC handler coverage check~~ - `npm run verify:ipc-handlers` statically verifies 100% of
`IPC_CHANNELS` have registered handlers. `npm run ci` (run by every CI job) transitively invokes
`npm run verify:ipc-contracts` — there is no standalone `ipc-contract-check` job.
Expand Down
4 changes: 4 additions & 0 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,10 @@ Notes:

E2E automation notes:

- Specs are grouped by product area under `test/e2e/<area>/` (`app`, `setup`, `discover`,
`organize`, `search`, `settings`, `workflows`, `live-llm`, `manual`).
[`test/e2e/README.md`](../test/e2e/README.md) lists every spec, the CI smoke set and what each
spec needs. Run one area with `npx playwright test test/e2e/<area>`.
- Every `launchApp()` profile is isolated and seeded with `modelAutoDownloadSkipped: true`
(`E2E_SEED_SETTINGS` in `test/e2e/helpers/electronApp.js`), so a run never starts a model download
and needs no network. A spec that needs the first-run model wizard keeps the seed and calls
Expand Down
4 changes: 2 additions & 2 deletions docs/commercial-use-inventory.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"generatedAt": "2026-11-10T23:39:10.960Z",
"generatedAt": "2026-11-12T01:40:35.862Z",
"projectLicense": {
"riskLevel": "allowed",
"reason": "Project license is PolyForm Noncommercial 1.0.0 for personal/noncommercial use. Commercial rights are sold separately; third-party dependency obligations remain governed by their own licenses.",
Expand Down Expand Up @@ -18725,7 +18725,7 @@
},
{
"packageName": "officeparser",
"version": "8.6.0",
"version": "8.7.0",
"license": "MIT",
"riskLevel": "allowed",
"directRuntime": true,
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
"tour:record:studio": "cross-env STRATOSORT_REDACT_PATHS=1 node scripts/record-product-tour.js --topic=studio",
"tour:compose": "node scripts/compose-walkthrough-media.js",
"tour:compose:lessons": "node scripts/compose-walkthrough-media.js --topic=lessons",
"test:soak": "cross-env SOAK=1 SOAK_MINUTES=12 npx playwright test test/e2e/soak.spec.js --timeout=1200000",
"test:soak": "cross-env SOAK=1 SOAK_MINUTES=12 npx playwright test test/e2e/manual/soak.spec.js --timeout=1200000",
"models:check": "node scripts/setup-models.js --check",
"models:download": "node scripts/setup-models.js --download",
"dist": "npm run build && npm run generate:nsis && npm run setup:vision-runtime && npm run setup:vision-runtime:check && npm run setup:ffmpeg-runtime && npm run setup:ffmpeg-runtime:check && npm run setup:tessdata-runtime && npm run setup:tessdata-runtime:check && npm run setup:vc-redist -- --windows-host-only && npm run setup:vc-redist:check -- --windows-host-only && electron-builder --publish never --config electron-builder.json",
Expand Down Expand Up @@ -228,7 +228,7 @@
"mammoth": "^1.13.0",
"minimatch": "^11.0.5",
"node-llama-cpp": "^3.22.3",
"officeparser": "^8.6.0",
"officeparser": "^8.7.0",
"p-queue": "^10.0.0",
"pdfjs-dist": "6.5.438",
"pino": "^10.4.0",
Expand Down
5 changes: 3 additions & 2 deletions playwright.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ const { defineConfig } = require('@playwright/test');
const path = require('path');

module.exports = defineConfig({
// Directory containing E2E test files
// Directory containing E2E test files. Specs are grouped by product area in
// subfolders (app/, setup/, discover/, ...); see test/e2e/README.md for the index.
testDir: './test/e2e',

// Test file pattern
// Test file pattern (recursive, so every area folder is picked up)
testMatch: '**/*.spec.js',

// Maximum time one test can run (3 minutes for analysis tests)
Expand Down
26 changes: 25 additions & 1 deletion src/main/ipc/files/fileOperationHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,28 @@ function getAnalysisHistoryService() {
return null;
}

/**
* Stored analysis of a file that was just moved, so learning can key the move
* on the file's category. The history entry follows the file to `destPath`;
* `sourcePath` covers a path update that did not land.
* @param {string} sourcePath
* @param {string} destPath
* @returns {Promise<Record<string, any>|null>} Analysis, or null when the file was never analyzed
*/
async function getMovedFileAnalysis(sourcePath, destPath) {
const historyService = getAnalysisHistoryService();
if (!historyService?.getAnalysisByPath) return null;
try {
const entry =
(await historyService.getAnalysisByPath(destPath)) ||
(await historyService.getAnalysisByPath(sourcePath));
return entry?.analysis || null;
} catch (error) {
logger.debug('[FILE-OPS] Analysis lookup for learning failed:', error?.message);
return null;
}
}

/**
* @param {{ sourcePath: string|null|undefined, destPath: string, log: import('../ipcWrappers').LoggerLike, context: string }} args
*/
Expand Down Expand Up @@ -700,7 +722,9 @@ async function moveOneFile(
await learningService.recordFileMove(
moveValidation.source,
moveValidation.destination,
null, // No analysis available for manual moves
// Without the category the pattern is `<ext>:unknown:<folder>`, which
// an analyzed file's lookup never matches.
await getMovedFileAnalysis(moveValidation.source, moveValidation.destination),
FEEDBACK_SOURCES.MANUAL_MOVE
);
}
Expand Down
Loading
Loading