All notable changes to the Logseq Progress Bar plugin will be documented in this file.
Format follows Keep a Changelog. This project adheres to Semantic Versioning.
pnpm e2e— live acceptance matrix in the Logseq web app (scripts/e2e/): seeds a "Progress Matrix" page (11 scenarios covering children, checkbox, inherited class-property, strict, depth, renderer-arg overrides, DSL / datalog / compiled / cross-block queries, empty), runs 48 host-settings permutations, flips a status and a checkbox for the real-time path, toggles the theme, edits another page, and proves with host-side counters that the plugin makes zero graph writes and that reads stop after one debounced burst.pnpm e2e:desktop— the same matrix in a second instance of the installed desktop app on a throwaway graph (test-logseq-desktop skill), applying settings by overriding the plugin frame so the shared settings file is never written (proven by content diff). Both runs are green on this release.- Content-Security-Policy meta in
index.html: the plugin iframe can only load its own bundle; no remote scripts, objects, forms or base URLs. src/__sdk_guard__.ts— compile-time SDK-floor guard: typecheck fails if the installed@logseq/libsis older thanminSDKVersion(0.3.4).
- Toolchain: npm → pnpm (
packageManager,pnpm-lock.yaml,pnpm-workspace.yamloverrides), Node 24 (engines,.nvmrc, CI), vite 7.3.7, vitest 5 with v8 coverage,@types/node24. @logseq/libs^0.3.3 → ^0.3.4;minSDKVersion0.3.4;web: trueandeffect: falsedeclared insidepackage.json#logseqnext to the DB flags.- Dev server:
vite-plugin-logseqreplaced by the in-repovite-logseq-safe-plugin.tsso CORS can be limited to the Logseq origins (localhost:3001) instead of*; pinned port 8296 (pnpm dev). src/index.tsis now a thin SDK adapter (the only module importing@logseq/libs); scanning, ident resolution and settings reading take an injectedSdkPort(src/sdk.ts) so every other module runs under vitest without a host. New pure modules:progress.ts(resolve → compute → render pipeline) andslots.ts(slot registry).- Re-renders are debounced:
DB.onChanged, settings, theme and graph events coalesce into one pass (300 ms for DB changes) instead of a full re-render per event. - Tests live next to their modules (
src/*.test.ts); 165 → 212 unit tests plus a 13-feature × 144-settings matrix (src/matrix.test.ts, 1872 cases); 80 % coverage gate on lines/functions/branches/statements with the adapter excluded; tests run in CI and in the release workflow. pnpm audit→ 0 advisories (was 10 under npm, 2 critical): overrides for thedompurify/lodash-esversions@logseq/libspins; the rest cleared by the vite/vitest bumps.
- A class or checkbox property created after the plugin loaded was not known to the ident cache until the next settings change, so tagged-but- unset checkbox blocks were left out of the count. The cache is refreshed before every re-render pass.
- The dev server's CORS restriction was silently overridden to
*by the shared vite plugin; it now really answers only the Logseq origins. - Width and colour values from settings or renderer args are validated
before they reach
style=""(cssWidth,isSafeColor); unsafe values fall back to full width / the neutral colour instead of being injected. - One HTML escaper for labels, tooltips, data attributes and error text
(the attribute escaper used to miss
&and<). - Settings coercion:
defaultDepth0, negative or non-numeric → unlimited (was: a negative depth scanned nothing); JSON settings are shape-checked (an array or a non-string entry no longer breaks the mapping); invaliddisplayFormatfalls back topercent; stringified booleans accepted. - Two overlapping renders of one slot could publish out of order; a per-slot generation counter now drops the stale result.
showOnEmptyplaceholder honours the configured bar width.- All hooks are unsubscribed on unload (
onSettingsChangedand the slash command were leaking); dead.pb-full-widthCSS rule removed.
- The
autoDetectsetting. It was shown in the settings panel but nothing read it (auto-detect mode was never implemented; see REQUIREMENTS §1.1).
Property-ident resolution moves from a single regex to a DB-backed cache.
Logseq's user-property idents have graph-specific suffixes (e.g.
:user.property/check-pgUd_nnQ) and the old regex missed any suffix
containing _ — the bar would silently render 0 % even though the user
could see the checkbox. The new cache resolves friendly names → idents at
plugin init, settings change, and graph switch. The regex stays as a
fallback for cache misses, with its character class widened to match
Logseq's actual squuid alphabet.
A second real-graph case landed in the same pass: a #Checklist-tagged
block whose class declares a checkbox property is rendered ☐ by Logseq's
UI but doesn't have the value stored on the block until the user ticks
it. The plugin now treats these inherited defaults as false (Todo) so
the bar matches what the user sees.
- Property-ident cache (
src/identCache.ts) — one Datascript query resolves every property's:db/ident, title, and type per graph. Aggregator does exact-match lookup against the cache; falls back to the regex only when the cache misses. Eliminates the suffix-alphabet guessing game and collision risk. - Class-property inheritance for unset checkboxes — when a block is
tagged with a class that declares a
:checkboxproperty as a class-property and the value isn't directly stored, the aggregator defaults tofalse(Todo bucket). Without this, untouched checkbox blocks were silently excluded from progress counts. - 33 new unit tests covering the cache (18) and the inherited-default
- resolved-idents paths in the aggregator (15). Suite: 132 → 165.
- Widened the regex-fallback suffix character class from
[A-Za-z0-9]+to[A-Za-z0-9_]+so_-bearing squuid suffixes (e.g.check-pgUd_nnQ) match. AGENTS.md updated to match. ProgressBarConfiggains optionalresolvedIdents,propertyTypes, andtagClassPropsfields, snapshotted from the cache at config-read time. Pure types — no behaviour change for non-DB callers.
- User-property idents with underscore in the random suffix
(
:user.property/<name>-pgUd_nnQ) are now matched. Previously the regex[A-Za-z0-9]+excluded_, so any block carrying such a key rendered as "no value" and was silently dropped from the bar.
Checkbox-property tracking lands as the headline feature: tagged blocks
with a :logseq.property/type :checkbox property roll up into the same
progress bar as enum-status children, with #Checklist → check shipped
as a turnkey default in settings. Wiring the feature up against a real
Logseq DB graph surfaced five SDK-shape bugs that the unit tests didn't
catch (user-property ident hash suffixes, compiled-DSL :in $ % crash,
colon-prefixed :block/tags key, single-property fallback, stale-slot
provideUI noise, plus an aggregator false-filter bug). All fixed and
covered by 27 new tests (suite: 105 → 132).
- Checkbox-property tracking (Phase 8) — tagged blocks with a
:logseq.property/type :checkboxproperty now roll up into the same progress bar as enum-status children.true→ Done bucket + counted as completed;false→ Todo bucket + not completed. ThecompleteValuesmap is bypassed for boolean values, so a checkbox tag needs only a single line inpropertyMappings(e.g.,"Checklist": "check"). - Default
Checklist→checkmapping shipped in settings. The plugin does not create the class or property in the user's graph — users author their own class (any name works; rename in settings if needed). rewriteCompiledDslinsrc/query.ts— round-trips Logseq's compiled simple-query datalog ([:find … :in $ % :where (task ?b #{…})]) back into clean DSL sodslToBlockQuerycan emit rule-free datalog.- Diagnostic: failed datascript queries now log the offending datalog and
queryUUID, not just the error object. - 16 new tests across
aggregator.test.ts(boolean + ident-suffix matching) andquery-rewrite-compiled.test.ts. Suite: 105 → 121.
- User-defined property idents with hash suffixes (
user.property/ check-F44tqsqr) were not matched by the aggregator, causing every user-authored checkbox property to be silently skipped. Matcher now accepts/<name>or/<name>-<hash>via an anchored regex that won't over-match longer property names likecheck-version. - Compiled-DSL query blocks crashed with
:in $ %error. When Logseq stores a simple(task …)query as pre-compiled datalog in:block/title, the rule call(task ?b #{…})requires a rules input the plugin'sdatascriptQuerycall doesn't supply. Pipeline now detects this shape and re-routes throughdslToBlockQuery, which produces native datalog with no%dependency. falsevalues were silently filtered by the aggregator's truthy guard (if (!value) continue), which would have caused unchecked items to never enter the count. Guard tightened tovalue === null.- Stale-slot
provideUIwarnings — when async DB work straddled a slot unmount,logseq.provideUIwas called for slots Logseq had already removed, producingcan not resolve selector target #slot__xxxconsole noise. All threeprovideUIcall sites inrenderProgressBarnow go through asafeProvideUIhelper that re-checksUI.checkSlotValidimmediately before the call and drops the entry fromactiveRenderersif the slot is gone. - Tag reader skipped colon-prefixed
:block/tagskey. In DB graphs,getBlockPropertiesreturns tags under":block/tags"(leading colon) as a plain string array. The reader was checking"tags"and"block/tags"only — every child returnedtags: [], which maderesolvePropertyfall back to the wrong mapped property, so every Checklist/Tido child got silently skipped. Lookup chain extended. - Aggregator fallback used only first property — when no tag matched
(or tag resolution returned
[]),resolvePropertyreturned only the first mapped property, skipping every other tag's mapping. Replaced withresolvePropertyCandidatesthat returns an ordered list and the aggregator tries each in turn. Defensive: a Checklist child with empty tags now still counts via itscheckproperty.
readPropertyValuesimplified — two early-return fast paths duplicated logic the suffix-regex loop already covers. Replaced with a single anchored regex (^:?(.+/)?<name>(?:-[A-Za-z0-9]+)?$) that matches all four observed key shapes: plaincheck,logseq.property/check,:logseq.property/check, and:user.property/check-F44tqsqr. As a side effect, exact-match lookups are now case-insensitive (previously case-sensitive).toBlockDatahalves SDK round-trips per child — the oldgetBlockTagNamesre-calledgetBlockandgetBlockPropertiesfor each block aftertoBlockDatahad already fetched both. Tag extraction is now a pureextractTagNameshelper that operates on the props already in hand.countQueryStatusesswitch consolidated — thedatalog-mapanddatalog-vecbranches differed only in how the inner vector was extracted; merged into a single branch with one ternary.- Bundle size: 120.81 kB → 120.47 kB (−340 bytes). Test suite: 105 → 132 (15 new tests across aggregator, query-rewrite-compiled, and a new scanner-children regression file).
First marketplace-ready release. Plugin is feature-complete for the Logseq DB
graph: renderer macro, slash command, query-block auto-detect (DSL + custom
datascript), real-time updates via DB.onChanged, and theme-aware colors.
- Unified query mode (Phase 7) — query blocks can now use either the simple DSL (
(task "Todo" "Done")) or a full custom datascript query as a{:query [:find … :where …]}map or bare[:find … :where …]vector. Renderer is auto-detected when placed in a query block's title; the explicitquery: <uuid>param still works for cross-block references. Results are pulled as block entities and grouped/counted in the plugin viaaggregate(), sharing the exact code path used by children and page modes.- New
src/query.tsmodule:classifyQuery,extractDatalogVector,parseDSL,dslToBlockQuery,entityToBlockData,enrichPull,QueryParseError. scanner.tsshrinks ~170 LOC;buildStatusCountQueryandtransformCountResultsremoved.enrichPullinjects ref-resolution hints into wildcard(pull ?b [*])patterns so user-supplied datalog returns resolved property titles.
- New
- Core data layer (Phase 1)
- Renderer macro argument parser with pipe-delimited value support (
src/parser.ts) - Plugin settings schema with 12 configurable options (
src/config.ts) - Config resolver merging global settings with per-instance overrides (
src/config.ts) - Block scanner: children/descendants, page-level, and query modes (
src/scanner.ts) - Tag filter with strict and inclusive modes (
src/scanner.ts) - Status aggregator with custom property and completion mapping (
src/aggregator.ts) - Display format labels: percent, fraction, both (
src/aggregator.ts)
- Renderer macro argument parser with pipe-delimited value support (
- UI rendering (Phase 2)
- Segmented pill-shaped progress bar with CSS transitions (
src/renderer.ts) - Color system with 11 default status colors, dark/light variants (
src/colors.ts) - User color overrides via JSON settings (
src/colors.ts) - Hover tooltip with full status breakdown
- Width modes: full, compact, custom pixel values
- Empty/zero state handling (show or hide)
- Theme-aware CSS using Logseq CSS variables (
src/renderer.ts)
- Segmented pill-shaped progress bar with CSS transitions (
- Trigger modes (Phase 3)
- Manual renderer macro:
{{renderer :progress-bar, ...}}(src/index.ts) - Slash command:
/progress-barto insert macro - Query integration (DSL + custom datascript, auto-detected)
- Real-time updates via
DB.onChangedevent listener - Settings change reactivity — bars re-render on settings update
- Manual renderer macro:
- Unit tests — 105 tests across 12 test files
- DB-graph manifest — declared
supportsDB,supportsDBOnly, andminSDKVersion: 0.3.2inpackage.json#logseq - Project scaffolding: Vite + TypeScript, package.json, tsconfig, index.html
- README.md with usage, settings reference, and examples
- SDK upgrade — bumped
@logseq/libsfrom^0.2.8→^0.3.2. No source changes required; the plugin's API surface (Editor, DB, App, UI, provide*, settings lifecycle) is unchanged between 0.2.x and 0.3.x. Host transport moved to MessageChannel (transparent with fallback);onSettingsChangednow emits clean(next, prev)snapshots. - Query-block raw-string resolution accepts any of
(,{,[as a valid query opener (DSL, datalog map, or bare datalog vector). - Verbose info-level logs gated behind a
debugsetting (orwindow.__LOGSEQ_PROGRESSBAR_DEBUG__ = true); errors and warnings still surface.
- In-DB
[status, count]aggregation datalog that only worked for DSL queries. Counting now happens in JS after pulling block entities — one grouping engine for every scan mode.
- Plugin handshake timeout — migrated to
vite-plugin-logseq, upgraded@logseq/libsto^0.2.8, changed dev script tovite build --watch - ESM compatibility — upgraded Vite to
^7.0.0, Vitest to^3.0.0, separatedvitest.config.tsfromvite.config.ts - Query DSL resolution — in DB graphs,
logseq.property/queryis a reference entity, not a raw string. Resolved via datascript pull with inlined#uuid(SDKdatascriptQuerydoesn't pass:inparameters correctly). - Double-bar render — Query-class blocks render the same block title through multiple slot instances; switched
provideUIkey from block-scoped to slot-scoped so each slot renders independently. - Wildcard
(pull ?b [*])returning 0% — wildcard pull leaves status refs as unresolved{:db/id N}stubs.enrichPullnow injects{:logseq.property/<tracked> [:block/title]}hints automatically.
- Auto-detect mode (rendering bars on parent blocks without an explicit renderer) is not yet implemented.
- ESLint + Prettier are not yet configured.