Guidance for Claude Code in this repository. Non-discoverable landmines
(Logseq DB SDK shapes, inheritance paths, design boundaries) live in
AGENTS.md — read it first; this file is the operating manual.
- Plan mode default — any non-trivial task (3+ steps or an architectural decision) starts in plan mode; re-plan when something goes sideways instead of pushing on.
- Self-improvement loop — after any correction from the owner, add the
pattern to
tasks/lessons.md; read it at session start. - Verification before done —
pnpm typecheck && pnpm testafter every change;pnpm e2ebefore claiming a behaviour works; never mark a task complete without proof. - No graph mutation — the plugin reads and renders, never writes.
pnpm e2ecounts every SDK call and fails on a single write. - DRY · KISS · YAGNI — applied as a review pass before merge; delete before adding; three similar lines beat a premature abstraction.
- Conventional commits by category (
feat:/fix:/refactor:/test:/chore(deps):/chore(security):/docs:/chore(release):). No push, tag or release without the owner's go-ahead.
- Plan (plan mode) → write checkboxes into
tasks/todo.md. - TDD per task: RED (failing test next to the module) → GREEN → REFACTOR.
CHANGELOG.md[Unreleased]entry as each change lands.- Live verification (
pnpm e2e, andpnpm e2e:desktopfor changes the desktop host could see differently). - Docs (
README.md,REQUIREMENTS.md,AGENTS.md, this file) in the same pass; commit per category.
logseq-progressbar renders {{renderer :progress-bar}} as a segmented
progress bar over the statuses of a block's descendants or a query's
results. DB graphs only (supportsDBOnly), web: true, effect: false
(the plugin never touches parent.document; provideUI / provideStyle
go through the SDK).
- TypeScript (ES2020), Vite 7 (esbuild minify, no sourcemaps).
@logseq/libs^0.3.4 (npmnexttag;latestis the legacy 0.0.x line).src/__sdk_guard__.tsfails typecheck belowminSDKVersion.- pnpm 12 (
packageManager,pnpm-lock.yaml; overrides live inpnpm-workspace.yaml— pnpm 12 ignorespackage.json#pnpm). Node 24 (.nvmrc); the shell default may be 22 —nvm usefirst. The Node 24 corepack shim may lack pnpm;/opt/homebrew/bin/pnpmworks. - vitest 5 +
@vitest/coverage-v8, 80 % gate on all four metrics (src/index.tsand the guard excluded).
pnpm install # deps (pnpm audit must stay at 0)
pnpm dev # Vite dev server at http://localhost:8296 (CORS limited to localhost:3001; HMR reloads the plugin)
pnpm watch # rebuild dist/ on change (no server)
pnpm build # production build → dist/
pnpm typecheck # tsc --noEmit (includes the SDK-floor guard)
pnpm test # vitest run --coverage (CI-blocking; unit + 1872-case matrix)
pnpm test:watch
pnpm e2e # live matrix in the Logseq web app (~4 min; see scripts/e2e/progress-matrix.sh header)
pnpm e2e:desktop # live matrix in a second desktop instance (see scripts/e2e/desktop-matrix.sh header)
pnpm audit # must report "No known vulnerabilities found"- Web app at
http://localhost:3001:pnpm watchin~/Projects/src/github.com/logseq(pnpm, not yarn).pnpm e2eregisters the plugin from:8296viaLSPluginCore.register(keylogseq-progressbar), seeds "Progress Matrix" idempotently, runs the matrix and prints counters.KEEP=1 pnpm e2eleaves the tab open. The in-memory Demo graph is wiped by a page reload; the registration survives. - Desktop app:
scripts/e2e/launch-desktop.sh 9333(launch.sh plus the timer-throttling flags) →cdp.mjs graph <throwaway containing "test">→pnpm build→pnpm e2e:desktop. The plugin must be an unpacked install from this folder so the instance loads it aslsp://logseq.com/external/…. Settings are applied by overriding the plugin frame'slogseq.settingsgetter; the runner diffs~/.logseq/settings/logseq-progressbar.jsonbefore/after. Remove the throwaway graph from the instance's All graphs page, then stop with the skill'sstop.sh(logseq graph removeafter the stop works too but leaves the entry in~/.logseq/graphs.edn). - Payload rules (
playwright-cli eval/cdp.mjs eval): one expression, no top-level;, no arrow functions, result stored onwindow.__…and polled. - Host-side counters: wrap
logseq.api.<fn>in the host page; the plugin's SDK calls arrive there by name. The test's own writes go through the saved originals so they are not counted.
| File | Role |
|---|---|
src/index.ts |
Thin adapter — the only import "@logseq/libs". Builds SdkPort, tracks slots, debounces re-renders (300 ms DB / 100 ms settings, graph / 50 ms theme), refreshes the ident cache before each pass, owns safeProvideUI, unsubscribes everything in beforeunload. Excluded from coverage. |
src/sdk.ts |
SdkPort: datascriptQuery, getBlock, getBlockProperties, getPageBlocksTree. |
src/progress.ts |
resolveInstance → computeStats (query mode, else children) → renderTemplate. |
src/slots.ts |
Slot registry with a per-slot generation counter (stale renders are dropped). |
src/settings.ts |
Schema, readGlobalConfig(raw) with defensive coercion, resolveConfig. |
src/scanner.ts, src/query.ts |
Children/page walk; query classify → datalog → entity adapter (see docs/query-progress-bar.md). |
src/identCache.ts |
Property ident / type / class-property cache from two Datascript queries. |
src/aggregator.ts, src/colors.ts, src/renderer.ts |
Counting, palette + colour validation, HTML + width validation + CSS. |
src/matrix.test.ts |
Every feature × every settings permutation. |
scripts/e2e/ |
Live matrices; seed.browser.js + matrix.browser.js shared by both runners. |
vite-logseq-safe-plugin.ts |
Dev-only: HMR plugin reload, writes the dev dist/index.html. Must not touch cors. |
Property lookup, class-property inheritance, compiled-DSL rewriting and the
{:db/id N} pull stubs are explained in AGENTS.md.
- Auto-detect mode is not implemented (REQUIREMENTS §1.1); the setting that advertised it was removed in 1.3.0.
fullwidth is not block-wide: the host mounts the macro slotinline-flexin the title, so the bar renders at its minimum width next to the title (README screenshot look). A block-wide bar needs a block-level slot, which the host does not offer.- Every slot re-renders on any DB change (bounded by the 300 ms
debounce). Query mode can depend on any block, so the hook is not
page-filtered.
getCurrentPageis not used at all. - Slot hooks cannot be unsubscribed (
onMacroRendererSlottedreturns void); the host drops them on unload. - Ident cache and new classes: refreshed before every pass since 1.3.0; before that a class created after load was invisible until a settings change.
- Setting a class property tags the block (
status→#Task); seeAGENTS.md. - Desktop test instance: a hidden Electron window throttles timers to
one wake per minute after ~5 min;
launch-desktop.shpasses the flags that disable it, and the runner measures a 1 s timer first. Never run two matrices on one instance. - Query mode is incomplete (assessed live 2026-10-15,
tasks/todo.mdPhase 9):or/not/ relativebetween/priority/page-tagsare ignored or inverted silently, user and checkbox properties are not read in query mode,:inputsand compiled rules other than(task ?b #{…})fail. The host'slogseq.api.qis broken for DSL in the current build, so the plugin must keep (and finish) its own compiler. scanPageis unused by production code (kept for REQUIREMENTS §2).