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
23 changes: 23 additions & 0 deletions .agents/checks/app-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Check: the line between the web view and the Mac

Applies to changes in `src-tauri/`, `src/lib/files.ts`, `src/lib/links.ts`, and `src/ai/client.ts`.
The web view shows text from documents; the Rust side can touch files, the keychain, and other
programs.

## Report

- A new `#[tauri::command]` that takes a path, URL, or program from the web view without checking
it, the way `document_path` and `safe_url` do in `src-tauri/src/lib.rs`.
- New permissions in `src-tauri/capabilities/default.json`, or a looser `csp` in
`src-tauri/tauri.conf.json`.
- The API key, or Claude account details such as the email, reaching the web view. They stay in
`src-tauri/src/ai.rs` and `src-tauri/src/claude_code.rs`.
- A save that can overwrite another app's change without asking: `write_text` must keep its
`expected` check.
- `claude` run with tools, MCP servers, settings, or saved sessions turned on. See the flags in
`src-tauri/src/claude_code.rs`.

## Fine

- New commands that only read the app's own state.
- Error messages that name the file the user picked.
25 changes: 25 additions & 0 deletions .agents/checks/editor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Check: how the editor is built

Applies to changes in `src/editor/` and the panels that read it. The design is in "How the
editor works" in `CLAUDE.md`.

## Report

- Alternatives, ghosts, or versions changed other than through `setLayers`, in the same
transaction as the text.
- A layer command without a test that does, undoes, and redoes it, and checks the saved Markdown.
- A command that dispatches on its own instead of returning a `TransactionSpec`, or null when it
doesn't apply.
- Undertext's own syntax (`{{ }}`, `{- -}`, `:::alt`) placed in the CodeMirror document. The
document is the page as plain Markdown; the layers sit beside it.
- React state that copies part of the editor state and isn't rebuilt from it on each update, so
the two can disagree.

## Fine

- New helpers in their own module, with tests on `EditorState`.
- Decorations computed from the editor state on each update.
- `EditorInfo`, which `describe` in `src/editor/setup.ts` rebuilds from the editor state on each
update for the React panels.
- `src/editor/format.ts` using the Markdown parser from `@lezer/markdown`. It uses nothing else;
`scripts/rules.test.mjs` checks that.
21 changes: 21 additions & 0 deletions .agents/checks/file-format.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Check: saved files keep the user's writing

Applies to changes in `src/editor/format.ts`, and to anything that changes what a save writes.
Saved files are the user's data. A wrong change here loses writing, and no undo brings it back.

## Report

- A change to how files are read or written without a round-trip test in
`src/editor/format.test.ts`.
- A file the current version saved that the new version reads differently, or text lost on
save. Run real older content through the new code to show it opens the same.
- Plain text that reads as Undertext syntax after a save: `{{`, `{-`, a line reading `:::alt` or
`<!-- overflow -->`. The save must escape it.
- Undertext syntax read inside code spans or code blocks.
- The File format section of `README.md` no longer matching the code.

## Fine

- New syntax that older files can't contain, with tests that it reads and writes back unchanged.
- A known loss that is already listed under "Known limitations" in `README.md`, if the change
doesn't make it worse.
17 changes: 17 additions & 0 deletions .agents/checks/privacy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Check: nothing private or secret

Applies to every change. The repository is public.

## Report

- Keys, tokens, passwords, or account emails in code, tests, fixtures, docs, or commit messages.
- Someone's real writing or personal documents in tests, fixtures, or the welcome document.
- Paths that name a person's machine, such as a home folder, in committed files.
- An AI request that sends text before the user picks an AI feature, or sends more than the
Privacy paragraph in `README.md` says.

## Fine

- `ANTHROPIC_API_KEY` read from the environment.
- Sample text written for the repository.
- Generic install paths such as `/opt/homebrew` or `/Applications`.
37 changes: 37 additions & 0 deletions .agents/checks/tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Check: tests not weakened, new behavior tested

Applies to every change. The rules are in the "Tests" section of `CLAUDE.md`; this check follows
them.

## Report

- A test deleted, skipped, or marked `.only`.
- An assertion made looser: an exact value replaced by `toContain`, `toBeTruthy`, or
`not.toBeNull()`; fewer cases in a list; a `toBe` on text turned into a length check.
- An expected value changed to match new output, when the owner didn't ask for the new behavior.
- In a refactor: any changed test input or expected value.
- New behavior with no test, or a test that would still pass with the feature broken. For
example, it asserts only that a command returned something, not what the text became.
- A command that changes layers without a test that undoes and redoes it and checks the saved
Markdown.
- A multi-step test that checks only the end, where a step that does nothing would still pass.
- An expected value taken from the code under test, such as an error code the TypeScript side
matches.
- A fake of code in `src/` that decides the result.
- A new test the report doesn't show failing once.

## Fine

- A test moved, renamed, or reaching the code by a new path, with the same inputs and expected
values.
- A hash or id taken from the code, asserted equal or not equal, when another test pins that code.
Example: the fingerprints in `scripts/verify.test.mjs`.
- Stubs the test never calls, such as the app actions in the palette test in
`src/editor/lab.test.ts`.
- `not.toBeNull()` as a guard before using the value, as in the `apply` helper in
`src/editor/layers.test.ts`, or when null is the failure ruled out and a nearby case pins the
null, as for `matchScore`.
- A skipped test that needs the network or an account, with a comment saying how to run it, like
the `#[ignore]` test in `src-tauri/src/claude_code.rs`.
- Tooling tests in `scripts/` that swap the real checks for a fake one in a throwaway repository.
- An expected value that changed because the owner asked for the new behavior. Quote the request.
17 changes: 17 additions & 0 deletions .agents/checks/words.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Check: UI text, docs, and instructions

Applies to every change that adds or edits words people or agents read.

## Report

- UI text or docs that aren't plain words, short sentences, and active voice.
- A new shortcut missing from the Shortcuts table in `README.md` or from the command palette, or
listed there with the wrong keys.
- Instructions in `CLAUDE.md`, the skills, the checks, or `docs/agents.md` that contradict each
other, or describe behavior the code doesn't have. `scripts/docs.test.mjs` checks paths and
commands; this check covers what the words claim.

## Fine

- English only. The UI has no translations.
- Technical terms the reader needs, such as `TransactionSpec` in a developer doc.
11 changes: 11 additions & 0 deletions .claude/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "web",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["dev"],
"port": 1420
}
]
}
35 changes: 35 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"ask": [
"Bash(git push *)",
"Bash(gh pr merge *)",
"Bash(* --no-verify)",
"Bash(* --no-verify *)"
]
},
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/scripts/guard.mjs\" turn-start",
"timeout": 10
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/scripts/guard.mjs\" turn-end",
"timeout": 30
}
]
}
]
}
}
34 changes: 34 additions & 0 deletions .claude/skills/build-feature/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
name: build-feature
description: Builds a new feature that fits the editor's existing shape, with a test for each behavior. Use when asked to "add", "implement", "support", "build a feature", or "make it possible to" do something new.
---

# Build a feature

1. **Read the closest feature** and follow its shape. For an editor command, that is its function
in `src/editor/commands.ts` or `src/editor/keys.ts`, its key in `src/editor/setup.ts`, its entry
in `src/palette/commands.ts`, and its tests.
2. **Settle the decisions in one round.** List the choices only the owner can make (behavior,
shortcut, names, file format), each with your recommendation, and ask them all at once. Done
when each one has an answer.
3. **Write done as behaviors** a test or a look at the app can check. For example: "⌘⇧D on a
paragraph with versions adds one more, and undo removes it."
4. **Build in thin slices, test first,** at the public interface: a command that returns a
`TransactionSpec`, tested on `EditorState`. For each slice: write the test, see it fail, make it
pass, and run `pnpm verify`.
5. **Update what the change touches,** from the table below.
6. Report with the `verify` skill.

| When you touch | Also update |
| --- | --- |
| A shortcut or command | The keymap in `src/editor/setup.ts`, or the keys in `src/App.tsx`; `src/palette/commands.ts`; the Shortcuts table in `README.md`; the context menu (`buildMenu` in `src/App.tsx`) if it belongs there |
| The file format | `src/editor/format.ts` and its round-trip tests; the File format section of `README.md`. Older files must still open. |
| Something a new user should learn | The welcome document, `src/welcome.ts` |
| A Rust command | `generate_handler!` in `src-tauri/src/lib.rs`; the `invoke` calls that use it, in `src/lib/files.ts`, `src/lib/links.ts`, or `src/ai/client.ts`; `src-tauri/capabilities/default.json` if it needs a permission; a Rust test |
| A new kind of layer | `src/editor/layers.ts`; `src/editor/format.ts` and the File format section of `README.md`; `src/editor/clipboard.ts`; `src/editor/ink.ts`; undo and redo tests |
| An AI task | `src/ai/tasks.ts`; a `mock` result for the browser preview; the AI section of `README.md` |
| How the editor is built | "How the editor works" in `CLAUDE.md` |
| A decision with reasons | "Decisions taken" in `docs/codemirror-plan.md` |

If the code you build on needs restructuring, do that first, as its own change, with the
`refactor` skill.
29 changes: 29 additions & 0 deletions .claude/skills/fix-bug/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
name: fix-bug
description: Fixes a bug so it stays fixed, with a regression test that proves the fix is needed. Use when something is "broken", "wrong", "a bug", "a regression", "doesn't work", or "used to work".
---

# Fix a bug

1. **Restate the symptom** in one line: what you did, what happened, what should happen. If the
report is vague, write your restatement and ask before you start.
2. **Reproduce it at the lowest layer that shows it.** A test on `EditorState` in
`src/editor/*.test.ts` beats one through React. A test in the Rust file's `mod tests` beats the
app. Run it: it must fail, for the reason in the report. Done when you have that failing test.
- Example, for "Enter in a list item leaves a stray space", in `src/editor/keys.test.ts`. This
bug is real and still open:
`expect(run(stateFor("1. one‸ two"), enter).text).toBe("1. one\n2. ‸two")`
- Some bugs show only in WebKit: caret, IME, dead keys. Reproduce those in `pnpm tauri dev` and
write the steps down. Still add a test for any part the editor state can show.
3. **Find the root cause.** Say why it happens and cite the line. Don't patch the symptom in a
caller.
4. **Make the smallest fix.** No restructuring in the same change. Note it for the `refactor`
skill instead.
5. **Prove the fix is needed:** undo it and see the test fail, then restore it and see it pass.
6. Run `pnpm verify`, then report with the `verify` skill.

The new test stays as the regression test. Never change an existing test's expected value to make
it pass, unless the owner says the old behavior was wrong.

After three fixes that didn't work, stop. Say what you tried and what you learned, then rethink the
cause before you try again.
23 changes: 23 additions & 0 deletions .claude/skills/learn/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
name: learn
description: Turns a correction or a repeated mistake into a check that catches it next time. Use when the owner says "don't do that again", "you keep doing this", "remember this", "this happened before", or corrects the same thing twice.
---

# Learn from a mistake

1. **Name the mistake** in one line, with the example that showed it.
2. **Pick the strongest structure that holds it,** in this order:
1. a type, in TypeScript or Rust
2. a test, in `src/`, `src-tauri/`, or `scripts/`
3. a script check: a step in `scripts/verify-steps.mjs`, or the gate in `scripts/guard.mjs`
4. a review check in `.agents/checks/`, for what needs judgment
5. a line in `CLAUDE.md` or a skill, only for what nothing else can hold

A rule that is already written down and still broken goes straight to code.
3. **Prove it catches the mistake:** make the mistake on purpose and see the check fail.
4. **Delete the instructions it replaces** in `CLAUDE.md`, the skills, the checks, and `docs/`.
5. **Update the rules table** in `docs/agents.md`: the rule, what holds it, and how sure.
6. Make it its own small change. Commit it when the owner asks.

Claude's memory notes hold the owner's preferences, such as how they like commits. They are not
the place for a rule a check can hold.
25 changes: 25 additions & 0 deletions .claude/skills/refactor/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
name: refactor
description: Changes the structure of code without changing what it does, bugs included. Use when asked to "refactor", "clean up", "restructure", "move", "rename", "split", "extract", or "tidy" code.
---

# Refactor

The structure changes. The behavior doesn't, bugs included. A bug you find goes in a note, for a
separate fix with the `fix-bug` skill.

1. **Pin today's behavior** at the public interface, with tests that pass now. For UI, take
"before" screenshots in the browser preview. Done when the pins cover everything you will move.
2. **Delete proven-dead code** first: nothing calls it, and `pnpm verify` passes without it. Its
own tests go with it; name them in the report.
3. **Move one concept at a time,** in small steps. Run `pnpm verify` after each step. The pins stay
green.
4. **Migrate every caller,** then delete the old API. No shims left behind.
5. **Compare** "after" screenshots with "before".
6. **Keep it only if you can name what got easier to read.** If you can't, revert it.

Existing tests may change how they reach the code: paths, names, call sites. Never their inputs or
expected values. When the editor moved to CodeMirror, 1 of the 8 list tests was ported, and a fix
for stray spaces broke without any test noticing. Port every case.

Report with the `verify` skill. Name the test files whose expected values you left unchanged.
47 changes: 47 additions & 0 deletions .claude/skills/review-changes/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
name: review-changes
description: Reviews a change with fresh reviewers who see the task and the diff but none of the author's reasoning, then settles each finding with evidence. Use when the owner says "review this", "ready for a PR", "before I merge", or "before a release".
---

# Review a change

A review costs several agent runs. Run it when the owner says the change is ready, or asks for a
review. While you work, `pnpm verify` is the check.

1. **Run `pnpm verify`.** Review only a change that passes it.
2. **Check what reviewers will see:** `git status --short`. Ask the owner about any untracked file
that doesn't belong to the change. Never `git add -A`.
3. **Write the brief.** Give the task in the owner's words, what done means, and the decisions
made. Say where to read the change: `git diff <base>` for every commit and edit since the base
branch, and the untracked files from `git status`. Never give your opinion of the change, or
where you think the problems are. Add this rule, word for word, with the paths filled in:
> The working tree is read-only for you. To run or break code, make your own worktree, copy
> in the uncommitted and untracked files, and link the installed packages:
> `git worktree add --detach <folder> HEAD`
> `git ls-files -z -m -o --exclude-standard | xargs -0 -I{} rsync -R "{}" <folder>/`
> `ln -s <checkout>/node_modules <folder>/node_modules`
> Delete there any file the change deletes. Never run `pnpm install` in it: through the link,
> that would empty the checkout's packages. For Rust, set
> `CARGO_TARGET_DIR=<checkout>/src-tauri/target` to skip a cold build. When you are done,
> run `git worktree remove --force <folder>`.
4. **Start the reviewers in parallel,** each a fresh subagent with the brief:
- **Task review, always:** missing pieces, changes nobody asked for, cases not handled, and
tests that would pass with the feature broken. In a refactor, any changed test input or
expected value. Read each new rule against every skill, check, and script, and report any
that disagree.
- **Blast-radius review,** when the change alters code or config others rely on:
`src/editor/format.ts` (files already saved on disk), `src/editor/layers.ts`, the Rust
commands in `src-tauri/src/`, `src-tauri/tauri.conf.json`,
`src-tauri/capabilities/default.json`, `package.json`, `.claude/settings.json`, or
`scripts/`. It names the one fact that makes the change safe, and proves it by running code.
It also looks where a text search can't: files saved by older versions, library versions,
timing.
- **Code review:** one reviewer for each check in `.agents/checks/` that applies to the diff.
5. **Reviewers rate each finding** on the ladder in the `verify` skill: said, cited, traced, ran,
seen.
6. **Settle each finding** at the rung its claim needs: the cited line for what the diff shows, a
test or command that runs the code for behavior. Then fix it, dismiss it with that evidence
(never with an argument), or leave it open for the owner. Look again when new evidence comes
in.
7. **Fix, run `pnpm verify`,** and give a large fix its own fresh review.
8. **Report** each finding, what you did with it, and the evidence. Open findings go first.
Loading
Loading