Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
142 commits
Select commit Hold shift + click to select a range
6f21392
test: group test files by feature
PathGao Oct 26, 2026
db240be
refactor: name the home-screen gate showsHomeScreen and test it directly
PathGao Oct 26, 2026
27b541d
refactor(editor): move the list and table key handlers into editorKey…
PathGao Oct 26, 2026
c06de03
test: run preview link, hover and copy handlers instead of reading th…
PathGao Oct 26, 2026
9b90bc8
refactor(editor): move the outline cache, completion provider and fro…
PathGao Oct 26, 2026
0e08b14
test: drive the lossy-save guard, open file mode and save dialogs thr…
PathGao Oct 26, 2026
cff55d8
refactor(editor): move the action and key-command tables into editorA…
PathGao Oct 26, 2026
9acb27f
test: run the editor keymap from editorActions.ts instead of cutting …
PathGao Oct 26, 2026
5d8dc2a
test: run title-bar popovers and export gate instead of reading Title…
PathGao Oct 26, 2026
3ecd4b4
test: pin the file history signature with the type checker and drop m…
PathGao Oct 26, 2026
5dd2f63
test: run the choice dialog, context-menu routing and footnote toolti…
PathGao Oct 26, 2026
e060485
refactor: move window-ending flows into sessions/windowExit and test …
PathGao Oct 26, 2026
1d4ba65
test: run the export pipeline instead of reading export.ts
PathGao Oct 26, 2026
fbbd483
test: run fold layout and the tab store's preference instead of readi…
PathGao Oct 26, 2026
a9e2469
test: run the updater gate instead of reading its source
PathGao Oct 26, 2026
9117b80
test: pin repeated-heading slugs in Rust instead of reading markdown.rs
PathGao Oct 26, 2026
051e0bd
test: drive tab transfer races and store-built tabs instead of readin…
PathGao Oct 26, 2026
f42b713
test: run the outline navigator, diff view and open-mode options inst…
PathGao Oct 26, 2026
1fee119
test: run the editor menu items and Save Image As instead of reading …
PathGao Oct 26, 2026
c8befe4
refactor(editor): move the create and update options into editorOptio…
PathGao Oct 26, 2026
6a15a27
test: check the export menu gate against a real exportAsHtml run
PathGao Oct 26, 2026
9a1bc30
test: load a hostile theme instead of reading theme.ts
PathGao Oct 26, 2026
ea1f483
test: run Copy Reference and the find seed instead of reading the viewer
PathGao Oct 26, 2026
028b9fe
test: render the tab position options from the store and check them t…
PathGao Oct 26, 2026
8637e33
test: run the window registry and the cold-start show in Rust instead…
PathGao Oct 26, 2026
801c5a3
test: drive two stores' effects instead of counting setItem in settin…
PathGao Oct 26, 2026
eb69bc9
refactor(editor): move tab activation, the content listener and statu…
PathGao Oct 26, 2026
71cc96a
test: drive EditorDocument instead of lifting the activation effect o…
PathGao Oct 26, 2026
a934061
refactor(editor): define the app's Monaco themes in editorTheme.ts
PathGao Oct 26, 2026
c581e56
test: run the store's preview width instead of reading settings.svelt…
PathGao Oct 26, 2026
f4efe18
refactor: move viewer saves and conflict bar into sessions/externalCh…
PathGao Oct 26, 2026
fcfb35f
test: run the title bar platform instead of reading TitleBar.svelte
PathGao Oct 26, 2026
2b3f7c7
test: run the theme effect instead of reading the viewer
PathGao Oct 26, 2026
acc488d
test: drag past the split bounds through the tab store
PathGao Oct 26, 2026
8394f25
refactor: move the file watcher effect into fileWatch.svelte.ts and d…
PathGao Oct 26, 2026
054ee48
test: tokenize sample Markdown instead of scraping Monaco's grammar text
PathGao Oct 26, 2026
5e05ce3
test: run the clipboard retain helper in Rust instead of reading comm…
PathGao Oct 26, 2026
4217a38
refactor: route the viewer's recent-file list through createRecentFil…
PathGao Oct 26, 2026
b76ad85
test: run the preview patch step and fold observation instead of read…
PathGao Oct 26, 2026
a607e27
refactor(editor): move the queued sync, the reveal jumps and the scro…
PathGao Oct 26, 2026
f346658
test: drive EditorJumps instead of reading the reveal and sync queue …
PathGao Oct 26, 2026
9d844c1
Merge p2-A5-components
PathGao Oct 26, 2026
223f97a
test: run release.mjs against a copy of the manifests instead of read…
PathGao Oct 26, 2026
c25a33c
test: run the preview-cache refresh condition instead of lifting it o…
PathGao Oct 26, 2026
5c7466e
Merge p2-A6-sessions
PathGao Oct 26, 2026
dc76cd8
test: run the close-last-tab flow instead of reading the viewer
PathGao Oct 26, 2026
6fb607d
Merge p2-A4-editor-keymap
PathGao Oct 26, 2026
7d2656f
test: drive window-state restore and persistence through the real win…
PathGao Oct 26, 2026
9a21140
test: point the untrack check at the theme effect module
PathGao Oct 26, 2026
097ef6a
Revert "test: run the close-last-tab flow instead of reading the viewer"
PathGao Oct 26, 2026
8a81c2c
refactor(editor): move clipboard text, format tools, paste guard, mou…
PathGao Oct 26, 2026
d850371
refactor: build the tab context menus in tabContextMenu.ts and run th…
PathGao Oct 26, 2026
aaf11af
test: type the scroll-sync line in the queued-sync test
PathGao Oct 26, 2026
b92a4b8
refactor: move the modal backdrop's context-menu handler out of Modal…
PathGao Oct 26, 2026
c9b1f34
Merge p2-A3-editor-editing
PathGao Oct 26, 2026
113f515
test: run the front-matter field state instead of reading the viewer
PathGao Oct 26, 2026
b01cdca
refactor: move the viewer's tab-transfer options and movers into sess…
PathGao Oct 26, 2026
4e51522
Merge p2-A7-misc
PathGao Oct 26, 2026
4fe8c38
refactor: move view toggles and scroll sync into sessions/viewMode an…
PathGao Oct 26, 2026
3262a99
refactor: route file-changed events through externalChange.handleFile…
PathGao Oct 26, 2026
2e5c329
test: run the preview layout, export context and outline dismissal in…
PathGao Oct 26, 2026
eb16994
test: give the externalChange stubs a resolveExternalChange
PathGao Oct 26, 2026
5f7c815
test: run both splitter routes instead of counting their calls
PathGao Oct 26, 2026
3af0205
refactor: name the Open dialog filters MARKDOWN_OPEN_FILTERS and test…
PathGao Oct 26, 2026
51da16f
test: run the preview patch revision instead of reading the viewer
PathGao Oct 26, 2026
338dd45
refactor: route dropped files through fileDrop.handleDrop and test th…
PathGao Oct 26, 2026
4980c39
refactor: open new-tab links through documentSession.openInNewTab and…
PathGao Oct 26, 2026
9b5d576
refactor: suppress tab-switch transitions through motion.commitWithou…
PathGao Oct 26, 2026
bd4fd37
refactor: move front matter edits, preview scroll sync and the cold-s…
PathGao Oct 26, 2026
5173965
Merge p2-A2-viewer-render
PathGao Oct 26, 2026
7b561ad
refactor: pull open-document delivery into tested helpers and documen…
PathGao Oct 26, 2026
d7739ee
Merge p2-A1-viewer-docs
PathGao Oct 26, 2026
96650e6
docs: say where a new test goes, and point references at the merged f…
PathGao Oct 26, 2026
984150c
Merge p2-A8-viewer-windows
PathGao Oct 26, 2026
5b6ed18
test: give the viewMode externalChange stub resolveExternalChange
PathGao Oct 26, 2026
479f777
docs: carry the PR1 test-file references onto the phase 2 tree
PathGao Oct 26, 2026
6b1bbfd
Merge tests-by-feature (references already applied)
PathGao Oct 26, 2026
0e9eb0c
test: drop the unused runEditorHandler from keymapHarness
PathGao Oct 26, 2026
0163f53
refactor: move the viewer command table out of MarkdownViewer.svelte
PathGao Oct 26, 2026
36ce17b
test: run geometry save and second-launch paths on Tauri's mock runtime
PathGao Oct 26, 2026
8b7c14f
refactor: let viewMode record the preview scroll anchor for the outline
PathGao Oct 26, 2026
d1ab99a
Merge p2-F4-rust
PathGao Oct 26, 2026
4148be9
refactor: move the editor's paste, copy, cut and drop handlers into e…
PathGao Oct 26, 2026
3219cfc
test: move the Monaco chord ownership and editing-key tests out of th…
PathGao Oct 26, 2026
f28f817
docs: point the chord ownership comments at editor.chords.spec.ts
PathGao Oct 26, 2026
ef0fbac
test: drop auto-save and last-tab checks that behavior tests already …
PathGao Oct 26, 2026
313bc13
test: drop the theme-effect untrack check, settings.themeEffect.spec.…
PathGao Oct 26, 2026
4f07d4d
test: move the preview cache refresh test into preview.renderedHtml.s…
PathGao Oct 26, 2026
d4d8111
refactor: move the localized editor action registration into editorLo…
PathGao Oct 26, 2026
9a4ff44
Merge p2-F2-viewer
PathGao Oct 26, 2026
596c555
Merge p2-F1-editor
PathGao Oct 26, 2026
239655c
test: run the settings open effect against a settling Tauri bridge
PathGao Oct 26, 2026
7d3c192
refactor: render the settings panes from exported row data
PathGao Oct 26, 2026
34e4dd8
Merge p2-F3-settings
PathGao Oct 26, 2026
e75a834
test: mount the update dialog for its package-managed phase and resto…
PathGao Oct 26, 2026
ae88cb7
test: mount the context menu to show it leaves focus on the selection
PathGao Oct 26, 2026
b1eb34c
refactor: move the editor mount sequence into mountEditor.ts
PathGao Oct 26, 2026
3dae132
test: check the editor mount order against mountEditor
PathGao Oct 26, 2026
ac9ce1b
test: mount the tab and title bar for the window organization actions
PathGao Oct 26, 2026
679ac47
test: check the settings row shape on the mounted panes
PathGao Oct 26, 2026
6fd7b05
test: delete checks that only a mounted viewer or editor, or a layout…
PathGao Oct 26, 2026
7a3407d
test: hot exit writes no file with auto-save off
PathGao Oct 26, 2026
f917f97
test: point stale comments at the files the cases live in now
PathGao Oct 26, 2026
35de4cb
test: keymapHarness no longer imports the source readers
PathGao Oct 26, 2026
108e27f
Merge p2-F1-editor
PathGao Oct 26, 2026
9c55f3b
Merge p2-W5-cleanup
PathGao Oct 26, 2026
6767b6f
test: the editor's keydown sets the AltGr context key; drop the statu…
PathGao Oct 26, 2026
d49ef3e
docs: tests run the real code; checks files are the shrinking exception
PathGao Oct 26, 2026
b5ad6ae
Merge origin/master (#1030 squash of tests-by-feature, same tree)
PathGao Oct 26, 2026
8197a84
test: each test sets up the state it needs, so shuffled order passes
PathGao Oct 26, 2026
35b9304
fix: helpers keep the reads the inline conditions skipped
PathGao Oct 26, 2026
04d013e
test: type translation keys so tsc rejects a key English does not define
PathGao Oct 26, 2026
8facb1d
Merge origin/master (#1031) into tests-run-the-code
PathGao Oct 26, 2026
923d658
build: generate typed Tauri bindings with tauri-specta
PathGao Oct 26, 2026
ced2234
test: async commands are checked by the compiler; drop checks somethi…
PathGao Oct 26, 2026
ec00dc7
test(rust): keep the mock-runtime tests off Windows
PathGao Oct 26, 2026
4359880
test: renamed settings read their old key and never write it, run on …
PathGao Oct 26, 2026
61a68c3
test: move the stress-document front matter case and the outline pref…
PathGao Oct 26, 2026
cf8d466
test: check startup chunks for Monaco and parse the release configs
PathGao Oct 26, 2026
77bfcce
test: parse tauri.conf, capabilities and locales for the config cases
PathGao Oct 26, 2026
86561d8
test: run the app.html first-paint script on the store's theme; move …
PathGao Oct 26, 2026
ceb1ac2
refactor: call Tauri commands through the generated bindings
PathGao Oct 26, 2026
1304b6f
refactor: listen to Rust-emitted events through the typed bindings
PathGao Oct 26, 2026
9f59fae
test: the session module owns the legacy snapshot keys, so the restor…
PathGao Oct 26, 2026
5eaaf12
test: move the Monaco IME patch and KaTeX font cases to behavior file…
PathGao Oct 26, 2026
31037f1
test: parse the stylesheets for the CSS cases
PathGao Oct 26, 2026
adac9cf
test: a rendered heading is not a flex container under the app styles…
PathGao Oct 26, 2026
12ed5de
test: drop the cross-language checks the generated bindings replace
PathGao Oct 26, 2026
417fa0f
test: run the release guard and the signing step under bash with a st…
PathGao Oct 26, 2026
5d53609
Merge p3-P3d-misc
PathGao Oct 26, 2026
aa71be0
test: pin the theme.txt words the startup reader already finds on disk
PathGao Oct 26, 2026
ef5ff3b
Merge p3-P3c-data
PathGao Oct 26, 2026
8f0aa40
test: resolve the print and export cascade in jsdom
PathGao Oct 26, 2026
58246c7
Merge p3-P3a-specta
PathGao Oct 26, 2026
cf62fb4
build: keep the bindings exporter a dev-dependency on every platform
PathGao Oct 26, 2026
cc9a69b
Merge p3-P3e-print
PathGao Oct 26, 2026
8eb4fea
Merge origin/master (#1032 squash of tests-run-the-code, same tree)
PathGao Oct 26, 2026
ac33e8c
build: lint the frontend conventions with ESLint
PathGao Oct 26, 2026
9980512
test: replace the source-reading checks with lint rules
PathGao Oct 26, 2026
cc9a8ba
Merge p3-P3b-eslint
PathGao Oct 26, 2026
1e797f1
Merge origin/master (#1033, #1034) into p3-integration
PathGao Oct 26, 2026
6059555
build(windows): embed the app manifest into test binaries too
PathGao Oct 26, 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
30 changes: 28 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ jobs:
- name: check frontend
run: npm run check

# The code conventions no type can express (one implementation of a fixed
# behaviour, Rust reached only through the generated bindings, ...), as
# lint rules in eslint.config.js. Each message says why.
- name: lint frontend conventions
run: npm run lint

- name: run frontend behavior tests
run: npm test

Expand All @@ -127,6 +133,17 @@ jobs:
- name: run frontend behavior tests (vitest)
run: npm run test:vitest

# Monaco is most of the app's JavaScript and only the editor needs it, so
# Editor.svelte loads it with `await import()`. A static import anywhere in
# the startup graph folds it back into the chunks every window parses on
# first paint, and nothing else fails. This builds the frontend and reads
# the Vite manifest (which chunks load at startup) and their source maps
# (which modules each chunk holds). Measured: about 6 seconds.
- name: monaco is not in the startup chunks
run: |
npx vite build --sourcemap
node scripts/check-startup-chunk.mjs

# Nothing had ever checked Rust formatting, so master drifted to 47
# rustfmt diffs across all six source files -- not because anyone
# disagreed with the tool, but because `cargo fmt` is a thing you have to
Expand All @@ -143,10 +160,19 @@ jobs:
cd src-tauri
cargo fmt --check

# `--locked` fails the run when Cargo.lock no longer matches Cargo.toml
# (a version bump that skipped the lock), instead of rewriting it here.
- name: run cargo test
run: |
cd src-tauri
cargo test
cargo test --locked

# `cargo test` just ran `export_bindings`, which rewrites
# src/lib/bindings.ts from the registered commands and events. A Rust
# command or event changed without the regenerated file committed leaves a
# diff here, before the frontend can drift from what Rust answers.
- name: check generated bindings are committed
run: git diff --exit-code src/lib/bindings.ts

# `-D warnings` is only defensible because the count is currently zero:
# the two that existed (a redundant `'static` on a const, a `push_str`
Expand All @@ -159,4 +185,4 @@ jobs:
- name: run clippy
run: |
cd src-tauri
cargo clippy --all-targets -- -D warnings
cargo clippy --locked --all-targets -- -D warnings
25 changes: 13 additions & 12 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,11 @@ src-tauri/
- Export singleton instances (e.g., `export const tabManager = new TabManager()`)

### Tauri Invoke
Use `invoke()` from `@tauri-apps/api/core` for Rust commands:
Call Rust commands through the generated bindings (`cargo test` regenerates
`src/lib/bindings.ts`); a raw `invoke` is a lint error:
```typescript
import { invoke } from '@tauri-apps/api/core';
const result = await invoke<string>('command_name', { arg: value });
import { commands } from '../bindings.js'; // relative to the caller
const result = await commands.commandName(value);
```

### File Operations
Expand All @@ -158,28 +159,28 @@ All file operations go through Rust commands - never use Node.js fs APIs.
- **Frontend**: two runners, split by glob so a file belongs to exactly one.
- `npm test` runs `scripts/*.test.ts` (`node --test --import tsx`).
- `npm run test:vitest` runs `scripts/*.spec.ts` (vitest, jsdom, Svelte plugin).
- **CI**: Runs `npm audit`, `npm run check`, `npm test`, `npm run test:vitest` and
`cargo test` on PRs
- **CI**: Runs `npm audit`, `npm run check`, `npm run lint`, `npm test`,
`npm run test:vitest` and `cargo test` on PRs

Tests run the real code and assert what it does. A test does not read source as text —
no regex over a file, no slicing a function body, no cutting code out of a component and
evaluating it: such a test guards a spelling, goes red on a rename and stays green on a
regression. When the logic sits in a component where a test cannot call it, move it into a
`.ts` or `.svelte.ts` module the component calls, and test the module.

The `*.checks.*` files are the one exception, and are meant to shrink: they hold contract
checks (a Tauri command or event name, a config key, an i18n key — the spelling that joins
Rust and TypeScript) and convention checks ("one implementation of this behavior"), until
those move to generated bindings and lint rules.
What a run cannot show is held elsewhere. Command and event names, arguments and returns
are generated from Rust into `src/lib/bindings.ts` (tauri-specta), so a mismatch is a
`npm run check` error. Code conventions ("one implementation of this behavior", "call
through this wrapper") are lint rules in `eslint.config.js` (`npm run lint`), plus
`src-tauri/clippy.toml` and `scripts/conventions.test.ts` for the Rust ones clippy cannot
express.

### Where a test goes

Tests are grouped by feature, not by the fix that added them:

- `scripts/<feature>.test.ts` and `scripts/<feature>.spec.ts` hold behavior tests (the
runner rules below decide which of the two).
- `scripts/<feature>.checks.test.ts` / `.checks.spec.ts` hold the cases that read source as
text, kept apart from the behavior tests.
- `scripts/<feature>.<area>.test.ts` / `.spec.ts` is for a part of a feature that must run
in its own process (module mocks, a process-wide shim such as `installShimDom()`) or would
make the main file unreadable.
Expand Down Expand Up @@ -279,7 +280,7 @@ mandatory for `tauri-plugin-updater` to compare versions correctly.
The lock file is the one that gets forgotten, because nothing in the editing
loop reads it — a bump without it lands green and stays wrong until the next
person's `cargo build` rewrites the line and leaves them a dirty working tree.
`scripts/release.checks.test.ts` asserts the three agree.
`scripts/release.workflows.test.ts` asserts the three agree.

## Releasing

Expand Down
4 changes: 2 additions & 2 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ If you ever lose the private key:

### 5. `alecdotdev/Markpad` is load-bearing

The updater endpoint in `src-tauri/tauri.conf.json` points at **`sftwrdotdev/Markpad`**. (`scripts/release.checks.test.ts` holds that name and the endpoint together — if the repository ever moves again, this line moves with it.)
The updater endpoint in `src-tauri/tauri.conf.json` points at **`sftwrdotdev/Markpad`**. (`scripts/release.workflows.test.ts` holds that name and the endpoint together — if the repository ever moves again, this line moves with it.)

That only helps builds made from here on. The endpoint is compiled into the binary, so every copy up to and including v2.7.0 asks GitHub for `https://github.com/alecdotdev/Markpad/releases/latest/download/latest.json` and reaches the current feed only because GitHub answers with a 301. Those installs use that redirect for as long as they run.

Expand Down Expand Up @@ -112,7 +112,7 @@ Back it up in the same place as the minisign private key, and treat it with the

## Per-release workflow

The workflow uses `npm ci`, so its installed dependency graph is exactly the committed lockfile. Do not replace it with `npm install` in release jobs. `scripts/release.checks.test.ts` guards that.
The workflow uses `npm ci`, so its installed dependency graph is exactly the committed lockfile. Do not replace it with `npm install` in release jobs. `scripts/release.workflows.test.ts` guards that.

[`snapcraft.yaml`](snapcraft.yaml) builds nothing. It packages the `.deb` the release already shipped (`plugin: dump`, `source-type: deb`), so the snap carries the same binary as the `.deb` and the AppImage. The same test asserts it is packaging rather than building.

Expand Down
Loading
Loading