Project health & roadmap¶
A candid snapshot of ZettelFlow's technical debt and a plan to give it a new life. Audit date: 2026-09-25 (plugin v3.3.0).
Health snapshot¶
🟢 Strengths¶
- Clear layering.
main → starters → config → architecture → actions/application/hookswith a consistent singleton pattern and a well-defined action contract. - Good Obsidian DOM hygiene in most places — heavy use of
createEl/registerEvent. - Centralized logging (
Logger) and i18n (en/es). - Deterministic canvas serialization (
json-stable-stringify) with lenient repair (tiny-jsonc). - CI/CD exists — releases on tag, docs on push, Conventional Commits enforced.
🔴 Gaps & risks¶
| # | Issue | Impact | Where |
|---|---|---|---|
| 1 | versions.json missing2.11.0→1.7.2, 2.12.0→1.13.1 | — | repo root |
| 2 | version-bump.mjs missingnpm version | — | repo root |
| 3 | Thin tests on the write paths — pure-logic breadth is now high (405 suites / 3,062 tests at 3.3.0); the vault-mutating paths are the remaining gap, addressed by epic #317 (E2, the safety net) | Limited regression safety net on writes | whole repo |
| 4 | — | tooling | |
| 5 | innerHTML usage (~8) | — | — |
| 6 | el.style.*) | — | — |
| 7 | log.error silenced when logging off | — | Logger.ts |
| 8 | onunload doesn't call unloadComponents()main.onunload tears down components + flushes journal/timeline + unregisters actions (verified by a test, #316) | — | main.ts |
| 9 | Canvas monkey-patching of internal APIs — now guarded (#304): fails soft on missing methods, catches runtime throws and falls back to the original, reports once + a self-check log | Fragile across Obsidian updates, but no longer a hard break | canvas/CanvasPatcher, canvas/…/CanvasPatchStatus |
| 10 | — | — | |
| 11 | es.ts locale behind en.tslocaleParity.test.ts, #316) | — | architecture/lang/locale |
See Obsidian review & scoring for how #1–#6 map to the score.
Revival roadmap¶
Ordered by leverage. Each item is small enough to be a focused PR.
Milestone 1 — Release & submission compliance (unblocks everything)¶
- Restore
version-bump.mjs; generate and commitversions.json(2.11.0→1.7.2,2.12.0→1.13.1). - Add
eslint-plugin-obsidianmd+eslint.config.mjs+npm run lint:obsidian; run it in CI. Done (#85): the 475-problem backlog is burned down to zero and the check is now blocking (part ofnpm run verify, the pre-push hook and CI). Two larger best-practice migrations are deferred with per-file rule relaxations: AbstractInputSuggest (#111) and the declarative settings API (#112). - Fix the
innerHTMLoccurrences and inlineel.style.*assignments — 0 remaining, enforced by the blocking Obsidian lint. - Verify
releases.ymlattachesmain.js/manifest.json/styles.cssunder a tag equal tomanifest.version(#316): modernized togh release create+ a step that fails the release if the tag ≠manifest.version.
Milestone 2 — Quality & safety net¶
- Add a
jest.config(ts-jest) + TDD harness, seeded with pure-logic suites. Next: grow coverage —ContentDTO/NoteDTO, the flow graph traversal (FlowImpl), wizard callbacks. - Migrate inline styles to CSS classes in
src/styles/components/— 0 remaining (blocking lint). - Always allow
log.error; keepdebug/tracegated (wired in the Logger constructor). - Call
ZComponentsManager.unloadComponents()fromonunload(#316; test-verified).
Milestone 3 — Robustness¶
- Harden the canvas patcher: guard every patched method, assert the target shape, graceful fallback + a single Notice + a self-check log (#304,
CanvasPatchStatus). - Audit commands (ids/names), settings headings, and complete
es.ts(#316): a command-id surface test (kebab-case, unique) + a whole-locale fail-on-drift test.
Milestone 4 — Community gallery¶
- Backend removed (#294). The community gallery is fully static (GitHub-backed); there is no FastAPI/MongoDB service to run or maintain. Contributions flow through GitHub (issue form + PR).
Milestone 5 — Product "new life"¶
Ideas to explore (not committed scope):
- A first-run onboarding flow and a bundled example vault/flow.
- Capability disclosures ahead of Obsidian's disclosure labels (declare file-system access).
- More community content and a smoother install → use loop.
How to use this document¶
Treat Milestone 1 as the definition of "ready to ship a compliant update." The obsidian-plugin-quality harness skill re-runs the compliance audit on demand, and the obsidian-plugin-reviewer agent reviews individual PRs against the guidelines.
The settings panel, subtracted (#439)¶
The panel had 62 options in one tab — 44 in ZettelFlowSettingsTab.tsx plus 18 across five settings groups — and nine of them configured nothing.
| What | Why it went |
|---|---|
| The whole Zettelkasten toolkit section (9 rows) | four buttons opening surfaces the menu button already opens, and four rows whose only control was a docs link |
| Unique prefix toggle | the pattern says it: empty means no prefix |
| Enable logger toggle | off is a level, so the toggle said the same thing twice |
| Enable event-driven workflows toggle | the role is the switch (#436) |
| The two canvas selectors | they are roles now, assigned in Your flows (#435) |
| The two gallery entries | a flow usually arrives from the gallery, so it lives with the flows |
Nothing was lost: settingsMigration.ts moves an install that had a prefix or a log level onto the merged control, idempotently and with a unit test per case, and a guardrail test keeps launchers, docs-only rows and the retired toggles out for good.
The tab renders 28 rows; the ceiling asserted by the guardrail is 35, so the next addition is a decision rather than an accident.
Dependencies & security (Dependabot)¶
Nothing flagged so far ships in the plugin. The bundle (main.js) contains only the runtime dependencies in package.json — react, react-dom, three, three-spritetext, 3d-force-graph, @dnd-kit/*, uuid, monkey-around, json-stable-stringify, tiny-jsonc, use-sync-external-store. Every Dependabot alert to date has been against dev/build tooling (transitive deps of esbuild, jest, eslint, sass, commitlint — in package-lock.json) or the docs site tooling (docs/requirements.txt, which builds GitHub Pages). None reach a user, so severity is read through that lens.
The policy, and how to keep it:
- JavaScript:
npm runbuild/test never touch a flagged package at runtime. Fix withnpm audit fix(semver-compatible, lockfile only — never--force, which would downgrade theobsidiantype-stub devDependency to an ancient version and break the build). A bump that needs a major of a dev tool is a deliberate change, not a security scramble. - Docs (
docs/requirements.txt): bump the pinned versions to the patched release, staying within the same major so the MkDocs build does not change behaviour. A major bump (e.g.pymdown-extensions10 → 11) is deferred until it can be validated against a realmkdocs build. .github/dependabot.ymlgroups updates weekly per ecosystem so this stays managed without a wall of per-package PRs.
Accepted, documented residue (dev/docs-only, no runtime exposure, revisited on the next major bump): the obsidian type-stub → moment path-traversal advisory (fixable only by downgrading the type stubs), and the two pymdown-extensions advisories that need v11. Both act only on attacker-controlled input, which for a static docs site of our own authoring does not exist.