Decompose the monolithic plugin entry into bootstrap modules and extracted
pure functions (src/bootstrap, src/modules), advance dataflow project
resolution, and reconcile the Phase 0 contract tests.
Fixes bundled in:
- gantt: fix year-zoom timeline so year/month labels render and a full year
fits on screen. Major-label gate no longer keyed on single-day width; add
a Year minor-label branch; formatMinorTick('Year') returns a real month
name; lower MIN_DAY_WIDTH to 2; drop per-render logging; register the
offscreen-indicator click handlers once to stop a listener leak.
- project: stop the Projects view fragmenting into one task per file.
determineTgProject() gains an applyDefaultNaming option (default true,
preserving the public API / File Source / unit tests); the dataflow cache
and worker-sync paths pass false so they match ProjectData.worker.
- build: restore RegExpCursor in progress-bar-widget (the SearchCursor
migration was incomplete -- SearchCursor cannot do regex search).
Pre-commit hook (npm run build) verified manually; hook could not spawn on
this Windows Git Bash setup. Tests: 1718 passing; production build green.
Phase 0 of the v10 refactor: stability foundation. 7 commits, 71 new
passing tests, 10 of 12 DoD items checked. The 2 deferred items
(delete TaskView.ts, fold settings-migration.ts into the registry)
are documented in PHASE0_DEFERRED.md and assigned to Worktree D
sub-PRs in Phase 3.
Highlights:
- MigrationRegistry with version-keyed atomic steps + tombstones
- onunload async cleanup race fixed; TaskIndexer listener leak fixed
- Per-task worker timeout (8s default) with kill + main-thread fallback
- Cache invariants checker (debug-mode safety net)
- LocalStorageCache version-mismatch fix on read
- Typed cache scope map for Phase 1+ settings consolidation
- Last-resort cleanup in Orchestrator.rebuild()
- 5 critical-path integration tests + W0 fixture infrastructure
Phase 1-4 work is split into 5 self-contained worktree briefs in
.v10-worktrees/. See .v10-worktrees/README.md for the index.
Splits the v10 Phase 1-4 work into 5 self-contained briefs in
.v10-worktrees/, one per worktree. Each brief is meant to be handed
to a single agent or developer:
worktree-a-banners.md — Phase 1 deprecation banners (parallel with B)
worktree-b-archiver.md — Phase 1 Archiver pure functions (parallel with A)
worktree-c-readonly.md — Phase 2 read-only mode + 10.0.0-beta.1 cut
worktree-d-cliff.md — Phase 3 the cliff (5 sub-PRs: D.2 → D.1 → D.3 → D.4 → D.5)
worktree-e-cleanup.md — Phase 4 cleanup + calendar-sync v0.1.0 release
Each brief includes:
- Setup commands (git worktree add, branch off, npm install)
- Files to modify and files to create
- Numbered task list with calendar estimates
- Definition of Done (per worktree)
- Conflicts to watch (cross-worktree)
- Open questions affecting THIS worktree (subset of the 5 main-plan questions)
- "Don't do these things" guardrails
The 5 main-plan open questions have been resolved with sensible defaults
(documented in .v10-worktrees/README.md). User can override before any
worktree commits to a particular answer; most decisions don't kick in until
Worktree D, giving a 3-4 week runway.
Phase 0 deferred items (TaskView.ts deletion, settings-migration.ts fold)
are explicitly assigned to Worktree D sub-PRs (D.4 and D.2 respectively),
with full resolution paths in PHASE0_DEFERRED.md.
WORKTREE_PLAN.md updated with a header pointer to the new briefs and
preserved as the umbrella overview.
PHASE0_DEFERRED.md tracks the two W6 items that couldn't ship in Phase 0:
1. Delete src/pages/TaskView.ts — blocked on uncommitted multi-cycle
context menu changes; resolution path documents how to port to
FluentTaskView before deletion. Assigned to Phase 3 sub-PR D.4.
2. Fold src/utils/settings-migration.ts into the registry — has runtime
callers (repairStatusCycles every load + W1 fallback path) that need
proper relocation, not just delete. Resolution path documents the
four steps. Assigned to Phase 3 sub-PR D.2.
WORKTREE_PLAN.md is the operational follow-up to the v10 plan
(~/.claude/plans/dynamic-mixing-pie.md). Splits Phase 1-4 into 5
worktrees with explicit conflict maps, sequencing diagram, and DoD
subsets per worktree:
Worktree A — Phase 1 banners (parallel to B)
Worktree B — Phase 1 Archiver (parallel to A)
Worktree C — Phase 2 read-only (sequential after A)
Worktree D — Phase 3 cliff cluster (D.1-D.5 sequential within)
Worktree E — Phase 4 cleanup
Identifies the hot zone (src/index.ts and the 14 settings tabs) and
gives strict sequencing rules so multi-developer execution doesn't
collide. Solo-dev path is A→B→C→D.2→D.1→D.3→D.4→D.5→E.
Adds three integration tests that exercise the full dataflow pipeline
end-to-end. These are the vital-signs tests that any future Phase 1
refactor needs to keep green.
W5.1 — Orchestrator.roundtrip.test.ts (4 tests)
The single most important test. Drives parse → augment → cache → query
through processFileImmediate (bypassing the 300ms debounce), with
workers forced off so parsing routes through ConfigurableTaskParser
in main thread. Asserts that:
- tasks added to a file end up in the index
- file modifications are reflected in the next query
- file deletions remove tasks from the index
- dispose releases all event listeners
W5.2 — Orchestrator.settingsChange.test.ts (4 tests)
Verifies cache invalidation on settings change. Phase 1 settings
consolidation will rely on this contract:
- parser scope clears the raw namespace
- augment scope clears augmented + project namespaces
- SETTINGS_CHANGED event fires with the scopes payload
- typed onSettingsFieldsChanged path also clears the right caches
W5.4 — CacheInvariants.sequence.test.ts (2 tests)
Drives the cache invariants checker through a realistic sequence
of operations (process / modify / delete / settings-change) and
asserts the checker reports ok at every step. The smoke test in
CacheInvariants.smoke.test.ts validates the checker's correctness;
this validates it doesn't false-positive on normal usage. I3
(indexer ↔ augmented namespace agreement) is filtered out as a
known transitional concern documented in invariants.ts.
Plan W5.3 (worker fallback) is already covered by WorkerTimeout.test.ts
which exercises the orchestrator's main-thread fallback path on a
WorkerTimeoutError. Plan W5.5 (migration tombstone) is already covered
by legacy-bundle-0.test.ts which parameterizes over real fixture data.
Also adds vault.adapter.{stat,exists} to FakeVault — needed by
processFileImmediate for mtime cache validation.
10 new tests; 83/83 Phase 0 tests pass; full suite stable at 39
pre-existing failures (1417/1557 pass, +71 from baseline 1346).
Replaces the ad-hoc settings migration calls in loadSettings with a
typed, atomic, version-keyed registry. Foundational infrastructure
for Phase 1's deprecation work — every v10 deprecation will register
a tombstone step here.
Capabilities
------------
- Atomic: clones settings, runs all applicable steps in-memory, commits
only if every step succeeds. On any throw the original object is
untouched.
- Dry-run: run({dryRun: true}) returns the diff without committing,
so Phase 1's deprecation modals can show a preview before applying.
- Version-keyed: steps declare a targetVersion (semver). Registry runs
every step where targetVersion ∈ (fromVersion, toVersion], in semver
order. fromVersion is read from settings._meta.lastMigratedVersion,
toVersion comes from manifest.version.
- Tombstone-aware: kind="tombstone" steps are first-class. Phase 1 will
use these to retire deprecated fields, optionally salvaging into
successor fields.
- Duplicate-id rejection: prevents Phase 1 PRs from accidentally
shadowing existing tombstones.
Phase 0 scope
-------------
The legacy bundle step (v0.0.1-legacy-bundle) wraps the THREE existing
migration paths the plugin used before this commit:
1. migrateSettings (multi-cycle status)
2. migrateInheritanceSettings (projectConfig.metadataConfig → fileMetadataInheritance)
3. fluentIntegration default backfill (inlined to avoid Component dep)
Bundle is byte-equivalent to the legacy direct calls — verified by
parameterized tests over 4 realistic data.json fixtures (legacy
multi-cycle, legacy inheritance, fresh install, partial fluent).
The sentinel tombstone (v0.0.2-sentinel-tombstone) targets a synthetic
field _meta._sentinelMarker that production never has, exercising the
tombstone code path without touching any real settings. The plan
originally suggested tombstoning taskStatusCycle/taskStatusMarks but
22+ files in the codebase still read those — Phase 1 audits readers
first, tombstone last. The sentinel doc includes that checklist.
Wiring
------
loadSettings now stashes the raw savedData on a transient field
(__transient_savedData__), runs the registry, then strips the field.
The legacy bundle step pulls savedData from there to detect old
projectConfig.metadataConfig.* keys that get dropped by the merge with
DEFAULT_SETTINGS. On registry failure (which shouldn't happen given
atomicity, but defensive) we fall back to the legacy direct calls so
the user is never left in a half-migrated state.
Discovery: SettingsMigrationManager exists in the codebase but is
never wired into index.ts — it's effectively dead code. Phase 1
audit will decide whether to delete it or wire it up. The plan
referenced it as an existing call site but reality says otherwise.
25/25 W1 tests pass (registry 14, legacy bundle 11). All other Phase 0
tests still green (73/73 mine).
W4a — Typed cache scope map
Adds src/dataflow/cache/scope-map.ts: a single source of truth mapping
settings field paths to the cache scopes they invalidate. Field paths
are dot-separated and match by longest prefix, so callers can pass
either a parent path or a leaf without knowing the granularity. The
map currently covers every field that existing onSettingsChange call
sites refer to (only fileMetadataInheritance.* in IndexSettingsTab —
which incidentally was passing the wrong scope for years; the map
records the correct one for Phase 1's migration to fix).
Adds Orchestrator.onSettingsFieldsChanged(fields) as a sibling of the
existing onSettingsChange(scopes). Phase 0 ships the typed entry
point but does NOT migrate any callers — Phase 1+ switches them
progressively as features are touched. The legacy method is kept as
the single delegation target so behavior is unchanged.
W4b — LocalStorageCache version-mismatch read fix
local-storage-cache.ts:91 had a half-implemented version check:
storeFile() was tagging entries with currentVersion, but loadFile()
never validated it. Plugin upgrades did NOT auto-invalidate stale
wrapper-level cache entries — the inner Storage records had their
own version validation, but the outer wrapper layer was dead code.
Now loadFile checks the version, returns null on mismatch, and
prunes the stale entry.
W4b — Cache invariants checker
Adds src/dataflow/cache/invariants.ts with checkCacheInvariants(orch),
a debug-mode safety net that walks Storage namespaces and reports
violations of four invariants:
I1 — every raw entry has an augmented counterpart
I2 — every cache record's version matches plugin currentVersion
I3 — augmented namespace agrees with in-memory indexer file set
I4 — getStats() namespace counts are sane and additive
Never throws; returns a typed report with ok/violations/stats.
W5.4 will exercise it more thoroughly across realistic operations.
26/26 W4 tests pass.
Three related stability fixes that the v10 deprecation work needs to
lean on. Each one closes a real bug that was discovered during plan
validation:
W2 — onunload async cleanup race
The plugin's onunload() fired dataflowOrchestrator.cleanup() with a
floating .catch() and never awaited it, so workers, event refs, and
debounced timers could outlive Obsidian's teardown. Now the async
cleanup is captured into a `plugin.unloadComplete` promise that
tests and any external observer can await. Repository.cleanup()
also unloads the underlying TaskIndexer Component, fixing a real
listener leak: TaskIndexer's vault modify/delete/create handlers
were never deregistered across plugin reloads.
The obsidian mock's Component.unload() previously didn't drain
registered events, hiding leak bugs in jest. Fixed in the mock so
listener-leak tests work for any future cleanup work.
W2-bis — last-resort cleanup on rebuild failure
Orchestrator.rebuild() now wraps its work in try/catch. On any
thrown error every cache namespace is cleared so the next plugin
load can't read a partial cache that looks complete. Snapshot/restore
is deferred to a later phase; the namespace clear is the floor.
W3 — per-task worker timeout with kill + main-thread fallback
TaskWorkerManager now arms a per-task timeout (default 8s,
configurable) when dispatching work. On timeout the hung worker is
terminated, the active promise rejects with WorkerTimeoutError,
a replacement worker is spawned, and the queue keeps draining.
Previously a hung worker would hold its slot until the 30s circuit
breaker tripped after 10 failures.
WorkerOrchestrator counts WorkerTimeoutError separately in
metrics.taskWorkerTimeouts and skips its retry-with-backoff path
for timeouts (a worker that just hung will likely hang on the
same file again — fall back to main thread immediately).
22/22 integration tests pass (Lifecycle, Rebuild, WorkerTimeout, smoke).
Phase 0 W0 of the v10 refactor: introduce a buildOrchestrator()
helper that boots a real DataflowOrchestrator with mock App/Vault/
MetadataCache, plus an InMemoryStorage double and a localforage
jest mock so the cache layer can construct under jsdom.
This is the foundation that subsequent stability work (lifecycle
hazard fix, worker timeout, cache invariants, migration tombstones,
critical-path integration tests) depends on. Smoke tests verify
fixture construction, vault event plumbing, and clean dispose.
12/12 smoke tests pass; existing suite unchanged.
- Add .env.example with GOOGLE_CLIENT_SECRET_B64 placeholder
- Update esbuild config to inject env vars via define at build time
- Add env.d.ts for TypeScript process.env type declarations
- Use base64 encoding for basic obfuscation of injected secrets
This prevents OAuth client secrets from being committed directly to
the repository. Developers need to create .env with their own secrets.
- Update README installation links to correct GitHub repo
- Change version badge to dynamic release tag
- Simplify DEVELOPMENT.md directory structure documentation
- Add PRIVACY.md with privacy policy information
Introduce a new SettingsModal component that provides a full-featured
settings interface using Obsidian's vertical tabs layout pattern.
The modal includes categorized navigation, deep search functionality,
and mobile-responsive design with back navigation.
Key changes:
- Add SettingsModal with searchable settings, category grouping, and
smooth transitions between tabs
- Refactor ProjectSettingsTab with comprehensive documentation and
clearer section organization
- Support boolean metadata values for project detection (uses filename
as project name when `project: true`)
- Integrate settings modal into FluentTopNavigation
- Add corresponding SCSS styles for the modal layout
- Convert all .css files to .scss format
- Add sass and esbuild-sass-plugin dependencies
- Configure esbuild with sassPlugin for SCSS compilation
- Update all component imports from .css to .scss
- Add _variables.scss for shared SCSS variables
- Add native-layout.scss for new layout styles
- Add TimerStatisticsPanel component for displaying active and completed timers
- Implement completed timer history with configurable max records
- Add timer controls (start/pause/resume/stop) to task list and tree views
- Integrate timer auto-start option in quick capture settings
- Add Working On navigation item for viewing active timers
- Refactor timer manager with unified storage abstraction
- Add i18n translations for timer features (en/zh-cn)
- Add timer statistics CSS styles