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
127 changes: 127 additions & 0 deletions .github/workflows/performance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
name: Performance Validation

on:
pull_request:
push:
branches:
- main
workflow_dispatch:

concurrency:
group: performance-validation-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

env:
CI: true

jobs:
typecheck:
name: Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm exec tsc --noEmit

build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build

unit:
name: Unit Stability
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm exec vitest run tests/save-race.test.ts tests/workspace-race.test.ts

fidelity:
name: Markdown Fidelity
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm exec vitest run tests/fidelity-production.test.ts
- run: node scripts/fidelity-gate.mjs docs 10

perf-smoke:
name: Performance Smoke
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Check web asset budget
run: |
node <<'NODE'
const fs = require("node:fs");
const path = require("node:path");
const assetsDir = path.join(process.cwd(), "dist", "assets");
const maxAssetBytes = 1_500_000;
const assets = fs.existsSync(assetsDir)
? fs.readdirSync(assetsDir).map((name) => path.join(assetsDir, name))
: [];
const oversized = assets
.map((file) => ({ file, size: fs.statSync(file).size }))
.filter(({ size }) => size > maxAssetBytes);
if (oversized.length > 0) {
console.error("Assets above 1.5MB smoke budget:");
for (const { file, size } of oversized) {
console.error(`${path.relative(process.cwd(), file)}: ${size} bytes`);
}
process.exit(1);
}
NODE

web-e2e:
name: Web E2E
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm exec playwright install --with-deps chromium
- run: pnpm test:e2e:web

# Linux Tauri WebDriver notes:
# - Run this only on Linux images with webkit2gtk, xvfb, tauri-driver, and the
# app bundle installed. The web smoke above intentionally does not exercise
# native dialogs, filesystem permissions, or WebView focus behavior.
# - A future native lane should start `tauri-driver`, run the packaged app
# under xvfb, then execute WebDriver specs against 127.0.0.1:4444.
38 changes: 38 additions & 0 deletions docs/performance-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Performance Validation

This repo validates stability and fidelity in separate CI lanes so failures stay easy to triage.

## CI lanes

- `Typecheck`: `pnpm exec tsc --noEmit`
- `Build`: `pnpm build`
- `Unit Stability`: save-race and workspace stale-scan regression tests
- `Markdown Fidelity`: production editor markdown idempotency tests plus the corpus fidelity gate over `docs`
- `Performance Smoke`: production build plus a 1.5 MB per-asset smoke budget
- `Web E2E`: runs headless Playwright against Vite with mocked Tauri APIs

## Local commands

```sh
pnpm install --frozen-lockfile
pnpm exec tsc --noEmit
pnpm build
pnpm exec vitest run tests/save-race.test.ts tests/workspace-race.test.ts
pnpm exec vitest run tests/fidelity-production.test.ts
pnpm exec playwright install chromium
pnpm test:e2e:web
pnpm test:ci
```

## Native Tauri WebDriver

The workflow includes comments for the native Linux WebDriver lane, but does not enable it yet. That lane needs a runner image with `webkit2gtk`, `xvfb`, `tauri-driver`, and the packaged FullMark app available. Once those dependencies are pinned, the lane should:

1. Build the Tauri bundle.
2. Start `tauri-driver` on `127.0.0.1:4444`.
3. Launch the packaged app under `xvfb`.
4. Run WebDriver specs against native filesystem dialogs, focus behavior, and WebView keyboard handling.

The current `web-e2e` lane is intentionally narrower: it proves the frontend
workflows against mocked Tauri APIs, but it does not validate native filesystem
dialogs, platform focus behavior, or packaged-app startup.
71 changes: 71 additions & 0 deletions docs/performance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Performance Harness

FullMark's benchmark and fidelity tests use generated vaults only. The harness
never reads a real user corpus, so CI and local runs are deterministic and safe
to share.

## Profiles

The generator supports these profiles:

- `small`: 32 markdown notes across a shallow tree.
- `medium`: 750 notes with frontmatter, wikilinks, and mixed `.md`/`.mdx`.
- `large`: 5,000 notes across a deeper tree.
- `chaos`: 1,200 notes plus hidden folders, non-markdown files, long names, and empty directories.
- `flat-50k`: 50,000 markdown notes in one directory.
- `deep-5k`: 5,000 notes distributed through a deep tree.
- `wide-dirs`: 3,000 notes spread across many sibling directories.
- `mixed-large`: 10,000 notes plus non-markdown and hidden-path noise.
- `unicode-paths`: 420 notes with Unicode directory and file names.

## Commands

```sh
npm run test:unit
npm run test:fidelity
npm run test:perf
npm run test:e2e:web
npm run test:ci
```

Run every performance profile when you need a full local sweep:

```sh
node scripts/perf-bench.mjs --profiles all --sample-size 256
```

The benchmark records these critical paths:

- generated vault creation
- markdown file discovery
- sampled `stat` calls
- sampled file read and frontmatter parse
- MiniSearch index construction
- representative query execution
- sampled TipTap markdown round-trip

## Artifacts

`scripts/perf-bench.mjs` writes JSON and Markdown reports. By default, reports
and generated vaults are written under the OS temp directory. Set
`FULLMARK_KEEP_ARTIFACTS=1` to keep generated vaults and reports under `.tmp`:

```sh
FULLMARK_KEEP_ARTIFACTS=1 npm run test:perf
```

Set `FULLMARK_ARTIFACT_DIR=/path/to/reports` to choose an explicit report
directory. Generated vault content is deterministic for a profile and seed.

## Headless web E2E

`npm run test:e2e:web` runs Playwright headlessly against Vite. The tests mock
Tauri's dialog, event, filesystem, and command APIs before the app loads, then
exercise the real React UI for workspace open, markdown-only tree filtering,
source editing and save, quick switching, and reader mode.

Install the browser once on a fresh machine:

```sh
pnpm exec playwright install chromium
```
11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,28 @@
"name": "fullmark",
"private": true,
"version": "0.1.5",
"packageManager": "pnpm@10.18.2",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build:install": "scripts/build-install.sh",
"preview": "vite preview",
"tauri": "tauri"
"tauri": "tauri",
"test:unit": "vitest run",
"test:fidelity": "node scripts/fidelity-gate.mjs --profiles small,unicode-paths --limit 120",
"test:perf": "node scripts/perf-bench.mjs --profiles small,medium,chaos --sample-size 128",
"test:e2e:web": "playwright test --config playwright.config.ts",
"test:e2e:tauri": "vitest run --passWithNoTests tests/e2e/tauri/**/*.test.ts",
"test:ci": "vitest run && node scripts/fidelity-gate.mjs --profiles small,unicode-paths --limit 120 && node scripts/perf-bench.mjs --profiles small --sample-size 32 && pnpm test:e2e:web"
},
"dependencies": {
"@floating-ui/dom": "^1.7.6",
"@floating-ui/react": "^0.27.19",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "^2.7.1",
"@tauri-apps/plugin-fs": "^2.5.1",
"@tauri-apps/plugin-opener": "^2.5.4",
"@tiptap/core": "^3.23.1",
"@tiptap/extension-code-block": "^3.23.1",
"@tiptap/extension-code-block-lowlight": "^3.23.1",
Expand All @@ -40,6 +48,7 @@
"zustand": "^5.0.13"
},
"devDependencies": {
"@playwright/test": "^1.60.0",
"@tailwindcss/vite": "^4.3.0",
"@tauri-apps/cli": "^2",
"@types/node": "^25.6.2",
Expand Down
28 changes: 28 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
testDir: "tests/e2e/web",
timeout: 30_000,
expect: {
timeout: 5_000,
},
fullyParallel: true,
reporter: process.env.CI ? [["github"], ["list"]] : "list",
use: {
baseURL: "http://127.0.0.1:1420",
trace: "retain-on-failure",
screenshot: "only-on-failure",
},
webServer: {
command: "pnpm dev --host 127.0.0.1",
url: "http://127.0.0.1:1420",
reuseExistingServer: !process.env.CI,
timeout: 30_000,
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
});
Loading
Loading