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
- Add comprehensive property mapping system with metadata mappings support
- Implement bidirectional status mapping (symbol to metadata value)
- Add direct frontmatter update capability for Bases entry tasks
- Include default status mappings as fallback for common values
- Deprecate calendar DayView and MonthView components
- Improve property key resolution with custom metadata mappings
- Add debug logging for status conversion troubleshooting
Remove redundant custom date adapter implementations in favor of the DateFnsAdapter exported directly from @taskgenius/calendar library.
- Delete local DateFnsAdapter utility class
- Remove inline NormalizedDateFnsAdapter from calendar component
- Import DateFnsAdapter from @taskgenius/calendar package