This repository’s documentation site is powered by Vite + SvelteKit + SveltePress.
Use packages/web/vite.config.ts as the source of truth for documentation scope:
- Sidebar and important doc routes are defined in
packages/web/vite.config.ts. - Route
/docs/...maps topackages/web/src/routes/docs/.... - Most docs are in
+page.md; some are+page.svelteor route helpers.
If you are working on the Harper repository itself, please pay special attention to the contributors/* pages.
Importantly, all the tools available in this repository are available via just. To learn more, run just --list.
Whenever agents are done making changes, they should run just format before handing work back to the user.
If just format fails, you can use just setup to prepare the relevant dependencies.
Human coders should periodically review this file and manually migrate stable, human-facing guidance into the real documentation website under packages/web so it is available outside agent workflows.
Agents should remind humans of this when possible.
packages/web/vite.config.ts: Sidebar source of truth and canonical map of important docs routes.packages/web/src/routes/docs/about/+page.md: High-level product overview, privacy model, versioning policy, and ecosystem context.packages/web/src/routes/docs/weir/+page.md: Weir rule language reference with syntax, expression types, and examples.packages/web/src/routes/docs/rules/+page.svelte: Live/generated rule catalog (rule names, defaults, and descriptions).packages/web/src/routes/docs/contributors/introduction/+page.md: Entry point for contributors and links to deeper contributor docs../AGENT_POLICY.md: Harper's policy on code generated by large language models. Agents should read this before making ANY changes.
packages/web/src/routes/docs/aboutpackages/web/src/routes/docs/weirpackages/web/src/routes/docs/rulespackages/web/src/routes/docs/integrationspackages/web/src/routes/docs/harperjspackages/web/src/routes/docs/contributors
/docs/about->packages/web/src/routes/docs/about/+page.md/docs/weir->packages/web/src/routes/docs/weir/+page.md/docs/rules->packages/web/src/routes/docs/rules/+page.svelte/docs/integrations/*->packages/web/src/routes/docs/integrations/*/+page.md/docs/harperjs/*->packages/web/src/routes/docs/harperjs/*/+page.md/docs/contributors/*->packages/web/src/routes/docs/contributors/*/+page.md
packages/web/src/routes/docs/about/+page.md: High-level product overview, privacy model, versioning policy, and ecosystem context.packages/web/src/routes/docs/weir/+page.md: Weir rule language reference with syntax, expression types, and examples. Very important if you're asked to write a Weir rule.packages/web/src/routes/docs/rules/+page.svelte: Live/generated rule catalog (rule names, defaults, and descriptions).packages/web/src/routes/docs/integrations/obsidian/+page.md: Obsidian plugin overview, privacy/value comparison, installation, and support links.packages/web/src/routes/docs/integrations/chrome-extension/+page.md: End-user Chrome extension overview and install link.packages/web/src/routes/docs/integrations/firefox-extension/+page.md: End-user Firefox extension overview and install link.packages/web/src/routes/docs/integrations/wordpress/+page.md: Current WordPress guidance, including migration recommendation to Chrome extension and legacy plugin status.packages/web/src/routes/docs/integrations/language-server/+page.md:harper-lsinstall methods, dictionaries, code actions, ignore comments, and full configuration reference.packages/web/src/routes/docs/integrations/visual-studio-code/+page.md: VS Code extension install, command list, and settings reference.packages/web/src/routes/docs/integrations/neovim/+page.md: Neovim setup usingharper-ls, plus optional and common config tweaks.packages/web/src/routes/docs/integrations/helix/+page.md: Helix setup usingharper-ls, plus optional and common config tweaks.packages/web/src/routes/docs/integrations/emacs/+page.md: Emacs setup usingharper-ls, plus optional and common config tweaks.packages/web/src/routes/docs/integrations/zed/+page.md: Zed extension entry point and link to canonical extension README.packages/web/src/routes/docs/integrations/sublime-text/+page.md: Sublime Text setup withharper-lsand LSP package configuration.packages/web/src/routes/docs/harperjs/introduction/+page.md:harper.jsmission, package overview, and installation starting point.packages/web/src/routes/docs/harperjs/linting/+page.md: Coreharper.jslint workflow and linter usage patterns.packages/web/src/routes/docs/harperjs/spans/+page.md: Explains span objects and how to use them to locate/handle lint ranges.packages/web/src/routes/docs/harperjs/configurerules/+page.md: How to programmatically read and setLintConfigto enable/disable rules.packages/web/src/routes/docs/harperjs/node/+page.md: Node.js-specific usage notes, especiallyLocalLintervsWorkerLinter.packages/web/src/routes/docs/harperjs/CDN/+page.md: Browser/CDN usage via unpkg and ESM import patterns.packages/web/src/routes/docs/contributors/introduction/+page.md: Contributor onboarding overview and links to architecture/testing/rule-authoring docs.packages/web/src/routes/docs/contributors/environment/+page.md: Local development environment setup across Rust, Node/pnpm, and optional Nix shell.packages/web/src/routes/docs/contributors/committing/+page.md: Commit message conventions and commit hygiene requirements.packages/web/src/routes/docs/contributors/architecture/+page.md: System architecture and roles of core components likeharper-core,harper-ls, andharper.js.packages/web/src/routes/docs/contributors/dictionary/+page.md: Process for adding or updating curated dictionary entries.packages/web/src/routes/docs/contributors/testing-strategy/+page.md: Harper's risk-driven testing strategy, covering automated checks, production feedback, manual testing, and testability.packages/web/src/routes/docs/contributors/author-a-rule/+page.md: Step-by-step workflow for implementing and testing new grammar rules.packages/web/src/routes/docs/contributors/visual-studio-code/+page.md: How to run, debug, test, and package the VS Code extension locally.packages/web/src/routes/docs/contributors/chrome-extension/+page.md: Internal architecture and local development notes for the browser extensions.packages/web/src/routes/docs/contributors/desktop/+page.md: Placeholder for Harper Desktop contributor documentation.packages/web/src/routes/docs/contributors/website/+page.md: Local development, preview, and validation for writewithharper.com (SvelteKit/SveltePress).packages/web/src/routes/docs/contributors/wordpress/+page.md: How to build and run the WordPress plugin locally.packages/web/src/routes/docs/contributors/obsidian/+page.md: Obsidian-plugin contributor workflow and plugin-specific constraints.packages/web/src/routes/docs/contributors/review/+page.md: PR reviewer playbook, including ways to fetch artifacts and test patches locally.packages/web/src/routes/docs/contributors/local-stats/+page.md: Local stats logging model,stats.txtformat, locations, and privacy behavior.packages/web/src/routes/docs/contributors/brill/+page.md: Brief explanation of Harper’s Brill-tagging approach and further reading link.packages/web/src/routes/docs/contributors/faq/+page.md: Contributor FAQ for conceptual distinctions (for exampleLintervsPatternLinter).
packages/web/src/routes/docs/about/+page.ts: Route behavior helper (ssr = false) for the About page.packages/web/src/routes/docs/harperjs/CDN/example/+server.ts: Serves the HTML example used by theharper.jsCDN documentation page.
https://docs.rs/harper-core/latest/harper_core//docs/harperjs/ref/index.html(generated API reference target)
harper-core: The core grammar checking engine. This is a dependency to pretty much everything related to Harper.harper-ls: A Language Server compatible with a number of text editors, including Neovim, Zed, and Helix. See above linked documentation for more details.harper-cli: A command-line binary for debugging Harper's core engine and markup language support.harper-comments: Provides parsers for a number of programming languages to support linting their comments.harper-wasm: The WebAssembly build target that powers browser and JavaScript integrations such asharper.js.packages/lint-framework: A package containing the tooling necessary to read/write/highlight text on the web for the purpose of linting.packages/components: Shared Svelte component package used by web-facing packages.packages/harper-editor: Shared Svelte editor package used by web-facing packages and Harper Desktop.packages/web: The Harper website, including documentation and a live demo that uses thelint-framework.packages/harper.js: The JavaScript package that usesharper-wasmto lint text from websites or Node.js processes.packages/chrome-plugin: The Harper Chrome Extension - uses thelint-framework. Also support Firefox.packages/obsidian-pluginpackages/wordpress-pluginpackages/vscode-plugin: The Harper Visual Studio Code plugin. Usesharper-ls.harper-desktop: The Harper Desktop app. It is a Tauri v2 + SvelteKit SPA with an offline editor, settings UI, tray/service control, and native overlay highlighter.
There are of course projects in this repository not listed above. If relevant, feel free to poke around.
- Use
pnpmfor frontend packages;harper-desktop/package.jsonpinspnpm@10.10.0. - Dev app:
just dev-desktopfrom repo root. It builds shared web packages, runscargo tauri dev, and Tauri then runspnpm devon port1420. - Start the highlighter process directly:
just dev-desktop-highlighterfrom repo root. - Frontend checks:
pnpm checkfromharper-desktop. - Frontend build only:
pnpm buildfromharper-desktop. - Full desktop checks:
just check-desktopfrom repo root. - Rust checks:
cargo check -p harper-desktop --all-targetsfrom repo root. - Rust formatting/fix loop from repo root:
cargo fmt && cargo check -p harper-desktop --all-targets. - Bundle builds match CI:
just build-desktop-linuxorjust build-desktop-macos. just build-desktop-linuxbuilds deb/rpm/appimage bundles.just build-desktop-macosbuilds app/dmg bundles.
- Harper Desktop lives under
harper-desktopand is part of the root Cargo and pnpm workspaces. - It is a SvelteKit SPA inside Tauri v2.
- SSR is disabled in
harper-desktop/src/routes/+layout.ts. harper-desktop/svelte.config.jsusesadapter-staticwithfallback: "index.html".- Main Rust entrypoint is
harper-desktop/src-tauri/src/main.rs, which callsharper_desktop_lib::run(). - CLI behavior is in
harper-desktop/src-tauri/src/lib.rs: no subcommand runs the normal Tauri app;highlighterruns the native overlay highlighter. - Tauri config lives in
harper-desktop/src-tauri/tauri.conf.json. - Tauri capabilities live in
harper-desktop/src-tauri/capabilities/default.json. - The main Svelte route is
harper-desktop/src/routes/+page.svelte; it switches between the settings UI and editor UI. - The editor UI lives in
harper-desktop/src/lib/EditorView.svelteand uses workspace dependencies onharper.jsandharper-editor. - The settings UI lives under
harper-desktop/src/lib/settings/. - Rust uses local workspace dependencies from
harper-desktop/src-tauri/Cargo.toml, includingharper-coreandharper-dictionary-wordlist. - The Tauri app starts a tray/menu-bar controlled highlighter service on launch and can open editor/settings windows from the tray.
- Editor and settings windows hide on close instead of exiting; quit is handled through the tray menu.
- The highlighter is Rust/egui/winit code under
harper-desktop/src-tauri/src/highlighter/. Highlighteris the public entry point for the overlay system.Windowowns native window/GPU plumbing.WindowManagerowns the winit event loop, monitor windows, cursor hit-testing, and popup selection.RenderStateowns highlight rendering, popup drawing, markdown rendering cache, and popup action dispatch.RenderStateusesActionableLintvalues fromharper-desktop/src-tauri/src/rect.rs.ActionableLintstores lint geometry, the HarperLint, and source text needed for suggestion application and popup actions.- Suggestion popup actions currently include close, apply suggestion, ignore lint, add to dictionary, disable rule, and refresh config.
- Popup hover text is implemented in
RenderStatebutton helpers. - The highlighter read interval is set in
run_highlighter()withwith_read_interval(Duration::from_millis(16)).
- OS integration is behind
harper-desktop/src-tauri/src/os_broker.rs. - macOS uses
harper-desktop/src-tauri/src/mac_broker.rs. - non-macOS currently uses
NoopBroker. - macOS highlighter focus handling depends on
MacBroker.last_focused; clicking the overlay can make the highlighter process focused, so accessibility reads fall back to the last non-highlighter PID. - macOS integrations are bundle-ID based and controlled by persisted
Integrationentries. - Highlighter stdout is reserved for JSON-line IPC. Diagnostics in highlighter paths should use
eprintln!, notprintln!.
- Tauri app state lives in
harper-desktop/src-tauri/src/config.rs. Configcurrently storesmutable_dictionary: MutableDictionary,dialect: Dialect,ignored_lints: IgnoredLints,lint_config: FlatConfig, andintegrations: Vec<Integration>.Config::new()usesMutableDictionary::new(),Dialect::American,IgnoredLints::new(),FlatConfig::new_curated(), and curated default integrations.- Config is persisted under the system config directory in a
harper-desktopfolder. Main settings are serialized toconfig.json; the mutable dictionary is stored separately asdictionary.txt. Config::load_from_system()loads the serialized config and then hydrates the mutable dictionary with the selected dialect.- The Tauri app owns shared config as
Arc<Mutex<Config>>. run_tauri()loads persisted config, createsHighlighterService, starts the highlighter service, and creates the tray/menu-bar UI.- The highlighter process is spawned through
harper-desktop/src-tauri/src/highlighter_process.rs. - IPC is implemented under
harper-desktop/src-tauri/src/communication/. - IPC uses newline-delimited JSON over child stdin/stdout.
- The Tauri app is the protocol server.
- The highlighter process is the protocol client.
- Protocol messages live in
harper-desktop/src-tauri/src/communication/message.rs. - Supported requests include
GetLintConfig,GetDictionary,GetDialect,GetIgnoredLints,GetIntegrations,SetLintConfig,IgnoreLint,AddToDictionary,AddIntegration,RemoveIntegration, andSetIntegrationEnabled. - Supported responses include
GetLintConfig,GetDictionary,GetDialect,GetIgnoredLints,GetIntegrations, andAck. IgnoredLintsis transferred as whole serialized state and merged server-side.AddToDictionarysends only the word; Rust appends it withDictWordMetadata::default().- The highlighter uses a Tokio current-thread runtime to call async IPC client methods from synchronous UI callbacks.
- That runtime is bridge plumbing only; it is not used for linting or UI rendering.
- Keep dictionary construction centralized through
Config::dictionary_from_user_dictionary(user_dictionary: MutableDictionary) -> Arc<MergedDictionary>andConfig::create_dictionary(). - Keep linter construction centralized through
Config::create_linter(). - The same merged dictionary source must be used for both
Document::new_markdown_default(text, &dictionary)andLintGroup::new_curated(dictionary, dialect). - Do not use
Document::new_markdown_default_curated(text)in the main highlighter lint callback when user dictionary words should suppress spelling lints. - Harper spelling lint behavior depends on document token metadata, so a linter with the updated dictionary is not enough if the
Documentwas built with the curated dictionary only. RenderStatemay still use curated document construction for localized source extraction where dictionary membership does not matter.
- Tauri commands in
harper-desktop/src-tauri/src/lib.rsincludeget_lint_config,get_dialect,set_dialect,set_lint_config,get_dictionary,set_dictionary,ignore_lint,add_to_dictionary,get_integrations,add_integration,remove_integration, andset_integration_enabled. - JS helper lives in
harper-desktop/src/lib/client.ts. - JS helper class is named
Clientand exposes static methods for lint config, dialect, dictionary, ignored lints, dictionary additions, and integrations. Client.ignoreLint(...)uses Harper JS ignored-lints export data and invokes the Rust command.Client.addToDictionary(word)sends only the word to Rust.- Vite's dev server must stay on port
1420;harper-desktop/vite.config.jsusesstrictPort: truebecause Tauri expects that port. - Vite intentionally ignores
src-tauri/**during frontend file watching.
- CI installs
wasm-pack,tauri-cli, andcargo-hackwithcargo binstall, then runs the relevant rootjusttask. - Avoid mixing behavior fixes with structural refactors in the highlighter; popup/UI, IPC, dictionary behavior, config persistence, settings UI, and OS-coordinate fixes are easier to review separately.
- Secondary-monitor highlighter coordinates may be wrong. Accessibility rectangles appear to be global screen coordinates, while each overlay window's egui origin is local to that monitor/window. Translate by monitor/window origin or render through one virtual-desktop-sized overlay.
- macOS text range lookup may be wrong after emoji or other non-BMP characters. Harper spans are char-based, while macOS accessibility ranges are NSString/UTF-16 based. Convert spans before calling
AXBoundsForRangeParameterizedAttribute.
When asked to write a new rule, keep these guidelines in mind:
- The user is almost always expecting you to write it to a file. Which file and where is up to you to find out.
- You should include at least 15 total tests, covering a wide variety of cases. Cover false-positives, false negatives, true positives, and if relevant, true negatives.
- You should run any and all tests to ensure that you do no break existing behavior and that your new rule runs the way you expect. At minimum, that means
just test-rust. Make sure to check the snapshots for changes. - After adding a new rule, update
harper-core/default_config.jsonso it is enabled in the curated default config. - Built-in Weir rules can be grouped by putting related
.weirfiles in a directory underharper-core/src/linting/weir_rules; the directory name becomes the single public rule name. - If the rule is related to a closed compound noun, see if you can just add an entry to the existing closed compound linter.
Unless you are specifically requested to write the rule in a specific way, choose the language (Rust or Weir) and methodology that fits the task.
ALWAYS run extensive bullet tests with cargo run --bin harper-cli --release -- lint <TEXT> to make sure the new rule isn't already covered by Harper.
- Draft the core expression
- Encode the match with
expr mainusing words, sequences, alternatives, filters, exceptions, POS tags, wildcards, or punctuation. - Keep the expression minimal but precise; avoid overmatching.
- If a wordlist is needed, include it as its own expression, used with an expression reference.
- Add rule metadata
let message,let description,let kind,let becomes(andlet strategyif needed).- Use
strategy "Exact"when casing must be normalized; otherwise default behavior orMatchCaseas appropriate.
- Add tests (required)
- Include at least 15 tests.
- Tests must cover: true positives, false positives, false negatives, and (if relevant) true negatives.
- Prefer a mix of casing, punctuation, whitespace, and nearby-token variations.
- Sanity-check edge cases
- Ensure exceptions do not block valid matches.
- Ensure replacements are correct and not destructive.
- Run the tests.
- Fix any issues that arise.
Write a Weir rule to a new file with a name of your choosing, including expr main, let fields, and tests. Make sure it has the extension .weir.
Before finishing your turn, run just format or cargo fmt if you have made any changes. This can help diffs if a human decides to make a commit.