AGENT_GUIDE.md was ~33k tokens, loaded in full by every agent that followed AGENTS.md's 'read it first', whatever the task. A third of it was lookups an agent can get from the code (file map, route table, env var table, settings key list, static asset list) and had already gone stale in several places. - AGENTS.md (~1.8k tokens, always loaded): what the project is, a 'before you touch X, read Y' table, the rules that always apply, workflow (including that a push to master publishes the Docker image), and the traps that have each broken real data or a real phone. It keeps the tool-managed graft block as is. - docs/agents/*.md (11 files, read on demand): the hard-won 'why' and 'do not regress' contracts by topic, sliced from the old guide with nothing hand-rewritten. The derivable lookups are dropped; the few non-derivable facts from them (git-ignored dev compose, built bundles, reference.docx) moved to testing-and-dev.md. - pointer comments at the top of the contract-heavy source files, so the rule surfaces at the point of editing - tests/agentDocs.test.js keeps the docs honest: AGENTS.md has a size budget, every topic file is linked and says when to read it, and any file, function or npm script a doc names must still exist - README and two code comments no longer point at AGENT_GUIDE.md
7.2 KiB
Agents
Joplock is a standalone web client for a stock, unmodified Joplin Server: Node (no framework), server-rendered HTML with htmx fragment swaps, and one large client file (public/app.js). It reads the shared Postgres directly and writes through Joplin Server's own API, so the same account keeps working from Joplin desktop, mobile and CLI.
This file is loaded on every task, so it is short on purpose. It gives the rules and says where the reasons live. It does not say where code lives: use the code index below (it cannot go stale). The topic files in docs/agents/ hold the hard-won "why" and "do not regress" rules. Read the one that matches what you are about to change, before you change it.
Before you touch ... read
| You are changing | Read first |
|---|---|
| the editor: CodeMirror/TinyMCE, mode switching, autosave, markdown round-trip, a phantom "Edited" | docs/agents/editor.md |
| any save path, vaults/encryption, note history/restore, anything reading the editor DOM across a note switch | docs/agents/vaults-and-save-safety.md |
| how notes/notebooks/resources are serialized or written (anything another Joplin client can see) | docs/agents/joplin-fidelity.md |
the notebook tree, drag and drop, sort, pins, sharing, user_items |
docs/agents/notebooks-and-sharing.md |
| upload, paste or drop of images/attachments | docs/agents/editor-uploads.md |
public/cm-extras.js or the CodeMirror bundle |
docs/agents/editor-markdown-extras.md |
| mobile layout, PWA, service worker, viewport/keyboard, static serving, anything iOS | docs/agents/pwa-mobile.md |
text expander, AI autocomplete, /ask |
docs/agents/ai-expander-ask.md |
| settings, themes, admin, backup/recovery, MFA, debug logging | docs/agents/settings-and-admin.md |
| server shape, htmx/fragment conventions, routes, data model, or you are unsure where something belongs | docs/agents/architecture.md |
| tests, Playwright, the dev stack, "my change does not show up" | docs/agents/testing-and-dev.md |
Rules that always apply
- Do not modify Joplin Server source for Joplock features unless explicitly approved.
- The server is authoritative and the browser is ephemeral: no browser-local authoritative storage, and logout must clear client-visible state (shared machines).
- Stay sync-compatible with normal Joplin clients. A Joplock write changes only what the user edited (
docs/agents/joplin-fidelity.md). - Keep the sidecar API app-oriented; do not expose the raw sync/storage model to the frontend.
- Keep changes minimal. Fix the cause, not the symptom, and add a test that fails without the fix. When you fix mobile, check desktop (and the reverse): they share the editor fragment.
Workflow
npm testbefore every commit (CI runs it on Node 20; this machine may run a newer Node).- Pushing to
masterpublishes the Docker image: GitHub Actions runs the tests, then builds and pushesghcr.io/abort-retry-ignore/joplock:latest. Do not commit tomasteror push unless the user asks. - Source edits need
./scripts/rebuild-dev.sh(the image copiesapp/,public/andserver.jsat build time; a container restart is not enough). Browser tests (npm run test:ui) need that dev stack andJOPLOCK_ADMIN_*in the environment; seedocs/agents/testing-and-dev.md. plans/is gitignored local history, not documentation;plans/README.mdlists what is stale. Trust the code and these docs over any plan.- Asset versions and the service worker cache name are computed (
app/assetVersion.js): never hand-edit or "bump" them. - After large changes run
graft buildso the code index matches.
Traps that have each broken real data or a real phone
One line each; the topic file has the detail.
- Inline
onclick="foo()"only seeswindow.foo. A function works in a unit test and is "not defined" in the browser unlesspublic/app.jsassigns it towindow(tests/inlineHandlersExposed.test.js). - Never write one note's text into another. Debounced saves, async fetches and TinyMCE are shared across note switches; every save path has an identity guard (
vaults-and-save-safety.md). - The markdown <-> TinyMCE round trip must be idempotent, or notes show a phantom "Edited" on open and get re-saved (
editor.md). - Do not decode
items.contentfor every item in SQL: it also holds binary resources and one attachment aborts the statement (notebooks-and-sharing.md). - iOS home-screen apps: never set
apple-mobile-web-app-status-bar-style: black-translucent; never guess a keyboard frominnerHeight - visualViewport.height; decide mobile vs desktop from the physical screen, never the window height (pwa-mobile.md). Chromium cannot reproduce any of this: measure on a device with/diag. - DOM ids and classes are a contract with inline JS and tests. Rename them deliberately.
- Floating menus need an opaque background and edge clamping; pure
:hoverstyles go inside@media (hover: hover)(architecture.md,pwa-mobile.md).
Code index
Graft — repo context graph
This repo is indexed in graft/: small linked markdown nodes that explain each
system and carry exact file:line spans, kept in sync with the code through git.
For ANY task here — understanding how something works, finding where code lives,
or scoping a change — get context from the graph before grepping or opening
source files. Re-ask freely (it's cheap) and reuse literal identifiers you
already have (symbol, error string, file name) as the query. New to this repo?
Run graft map first — a token-budgeted orientation (dir clusters, hubs,
hotspots), no LLM, no key.
- Run
graft ask "<your question>" --source→ ranked nodes with the relevant code spans inlined (each hit's ≤8-line crux by default;--fullfor whole definitions when the crux isn't enough). Match the tool to the task shape: for understanding or editing, the top node IS the answer — cite itscovers:file:line spans and edit straight from--source. For exhaustive tasks ("every occurrence / every caller of this pattern"), ranked results are top-N, not complete — rungraft grep "<literal>"instead (exhaustive over indexed files, grouped by enclosing symbol), falling back to rawgrep -rnonly for unindexed files. graft skeleton <file>→ every definition's signature + span, ~10× cheaper than reading the file; use it to skim an API surface.graft callers <symbol>gives precomputed, exact edges — who calls this. Add--direction outfor what it calls, or--depth Nto walk transitively for the full blast radius. For structural questions, skip ranking and use this directly.- Or browse:
graft/INDEX.mdlists every node; follow the links. - Monorepos and folders of multiple repos rank fairly across sub-projects —
hits carry
[scope/]labels naming which one they're from. Narrow withgraft ask "<task>" --in <scope>/once you know where you're working.
If a returned span is truncated ("+N more lines"), open the file at that exact range before finalizing. Only open source files when a node genuinely lacks a needed detail, and then at the exact file:line the node points to — never re-read whole files.
After big code changes, refresh the graph with graft build (deterministic,
no API key, $0).