Skip to content

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/hooks with 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 missing — present; maps 2.11.0→1.7.2, 2.12.0→1.13.1 — repo root
2 version-bump.mjs missing — present; run by npm 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 Obsidian-rule backlog (was 475 problems) — cleared and blocking, zero relaxations (#85, #111, #112) — tooling
5 innerHTML usage (~8) — 0 remaining; enforced by the blocking Obsidian lint — —
6 Widespread inline styles (el.style.*) — 0 remaining, enforced by the blocking Obsidian lint — —
7 log.error silenced when logging off — errors always surface (wired in the constructor) — 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 Backend has no auth/CORS/health, dev-only posture — backend removed (#294); the community gallery is fully static — —
11 es.ts locale behind en.ts — full parity, enforced by a fail-on-drift test (localeParity.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 commit versions.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 of npm 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 innerHTML occurrences and inline el.style.* assignments — 0 remaining, enforced by the blocking Obsidian lint.
  • Verify releases.yml attaches main.js / manifest.json / styles.css under a tag equal to manifest.version (#316): modernized to gh 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; keep debug/trace gated (wired in the Logger constructor).
  • Call ZComponentsManager.unloadComponents() from onunload (#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.
  • 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 run build/test never touch a flagged package at runtime. Fix with npm audit fix (semver-compatible, lockfile only — never --force, which would downgrade the obsidian type-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-extensions 10 → 11) is deferred until it can be validated against a real mkdocs build.
  • .github/dependabot.yml groups 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.