Move diagram pipeline target controls out of Developer mode so Circuit (Circuitikz) and Circuitikz + SVG preview are discoverable in normal settings. Document the current constrained circuitikz export boundary, multi-format SVG/PNG/PDF review flow, and localized MDX publishing policy across the docs site.
17 KiB
Notemd Architecture Overview
Updated: 2026-07-10
System Architecture
flowchart TB
subgraph User["Obsidian User Interface"]
CMD["Command Palette"]
SIDEBAR["Notemd Workbench"]
SETTINGS["Settings Tab"]
end
subgraph Plugin["NotemdPlugin (src/main.ts)"]
LOAD["loadSettings / saveSettings"]
DISPATCH["Command Dispatch"]
BATCH["Batch Processing"]
end
subgraph LLM["LLM Pipeline"]
PROV["Provider Registry<br/>(src/llmProviders.ts)"]
TOKEN["Token Resolution<br/>(resolveProviderTokenLimit)"]
CACHE["Response Cache<br/>(llmResponseCache)"]
TRANS["Transport Layer<br/>5 runtimes"]
end
subgraph Diagram["Diagram Platform"]
PROMPT["Spec Prompt<br/>(diagramSpecPrompt)"]
GEN["Generation Service<br/>(generateDiagramArtifact)"]
PARSE["Spec Parser<br/>(parseDiagramSpecResponse)"]
RENDER["Renderer Service<br/>(RendererRegistry)"]
HOST["Preview Host<br/>(IframeRenderHost)"]
end
subgraph Output["Output"]
VAULT["Vault Files<br/>(.md, .canvas, .json)"]
PREVIEW["Diagram Preview Modal"]
EXPORT["Source / SVG / PNG / PDF Export"]
end
CMD --> DISPATCH
SIDEBAR --> DISPATCH
SETTINGS --> LOAD
DISPATCH --> PROV
PROV --> TOKEN
TOKEN --> CACHE
CACHE --> TRANS
TRANS --> GEN
TRANS --> BATCH
GEN --> PROMPT
PROMPT --> PARSE
PARSE --> RENDER
RENDER --> HOST
HOST --> PREVIEW
HOST --> EXPORT
RENDER --> VAULT
BATCH --> VAULT
LLM Calling Pipeline
sequenceDiagram
participant User
participant Plugin as NotemdPlugin
participant Provider as Provider Registry
participant Token as Token Resolution
participant Cache as Response Cache
participant Transport as Transport Layer
participant API as LLM API
User->>Plugin: Execute action (process, translate, generate)
Plugin->>Provider: getLLMProviderDefinition(name)
Provider-->>Plugin: LLMProviderDefinition (transport, apiKeyMode, ...)
Plugin->>Token: resolveProviderTokenLimit(provider, model, maxTokens)
Token->>Token: KNOWN_MODEL_MAX_OUTPUT_TOKENS lookup
Token-->>Plugin: token limit (number | undefined)
Plugin->>Cache: buildCacheKey(provider, model, prompt, content)
Plugin->>Cache: getCachedResponse(cacheKey)
alt Cache hit
Cache-->>Plugin: cached response
Plugin-->>User: result
else Cache miss
Plugin->>Transport: callLLM(provider, prompt, content, settings)
Note over Transport: Routes to one of 5 runtimes<br/>openai-compatible | anthropic | google<br/>azure-openai | ollama
Transport->>API: HTTP request (with retry logic)
API-->>Transport: response
Transport-->>Plugin: result
Plugin->>Cache: setCachedResponse(cacheKey, result)
Plugin-->>User: result
end
Token Resolution Logic
User config (maxTokens, provider.maxOutputTokens)
→ resolveProviderTokenLimit()
→ Connection test? → return 1
→ Provider maxOutputTokens override set?
→ Known model? → min(override, knownModelMax)
→ Unknown model? → override (as-is)
→ Global maxTokens set?
→ Known model?
→ maxTokens === DEFAULT? → knownModelMax (auto)
→ Otherwise → min(maxTokens, knownModelMax)
→ Unknown model?
→ maxTokens === DEFAULT? → undefined (API decides, Cline-aligned)
→ Otherwise → maxTokens (user value)
→ Otherwise → knownModelMax ?? undefined
Supported Transports
| Transport | Provider Count | Protocol |
|---|---|---|
openai-compatible |
22 providers | OpenAI Chat Completions API |
anthropic |
1 | Anthropic Messages API |
google |
1 | Google Gemini API |
azure-openai |
1 | Azure OpenAI Deployment API |
ollama |
1 | Ollama Native API |
Diagram Rendering Platform
flowchart LR
subgraph Input["Input"]
MD["Markdown Content"]
INTENT["Preferred Intent<br/>(optional)"]
end
subgraph Spec["Spec Plane"]
PLAN["DiagramPlan<br/>(intent inference)"]
PROMPT2["DiagramSpec Prompt"]
LLM["LLM Invocation"]
PARSE2["Spec Parser"]
VALIDATE["Spec Validator"]
end
subgraph Render["Render Plane"]
REGISTRY["RendererRegistry<br/>8 renderers"]
SERVICE["RendererService"]
CACHE2["RenderCache"]
end
subgraph Target["Output Targets"]
MERMAID["Mermaid<br/>(flowchart, sequence, class, ER, state, mindmap)"]
CANVAS["JSON Canvas<br/>(canvasMap)"]
VEGA["Vega-Lite<br/>(dataChart)"]
HTML["HTML Fallback"]
FIGURE["Editable HTML/SVG"]
BOARD["Draw.io / Drawnix"]
CIRCUIT["Circuitikz"]
end
subgraph Host["Preview Layer"]
IFRAME["IframeRenderHost"]
MODAL["DiagramPreviewModal"]
EXPORT2["Source / SVG / PNG / PDF Export"]
end
MD --> PLAN
INTENT --> PLAN
PLAN --> PROMPT2
PROMPT2 --> LLM
LLM --> PARSE2
PARSE2 --> VALIDATE
VALIDATE --> SERVICE
SERVICE --> REGISTRY
REGISTRY --> MERMAID
REGISTRY --> CANVAS
REGISTRY --> VEGA
REGISTRY --> HTML
REGISTRY --> FIGURE
REGISTRY --> BOARD
REGISTRY --> CIRCUIT
MERMAID --> IFRAME
CANVAS --> IFRAME
VEGA --> IFRAME
IFRAME --> MODAL
MODAL --> EXPORT2
Supported Diagram Intents
| Intent | Render Target | Renderer | Preview | Export |
|---|---|---|---|---|
mindmap |
mermaid | MermaidRenderer | modal/iframe | SVG, PNG |
flowchart |
mermaid | MermaidRenderer | modal/iframe | SVG, PNG |
sequence |
mermaid | MermaidRenderer | modal/iframe | SVG, PNG |
classDiagram |
mermaid | MermaidRenderer | modal/iframe | SVG, PNG |
erDiagram |
mermaid | MermaidRenderer | modal/iframe | SVG, PNG |
stateDiagram |
mermaid | MermaidRenderer | modal/iframe | SVG, PNG |
canvasMap |
json-canvas | JsonCanvasRenderer | modal/iframe | source, SVG, PNG, PDF |
dataChart |
vega-lite | VegaLiteRenderer | modal/iframe (sandboxed) | source, SVG, PNG, PDF |
circuit |
circuitikz | CircuitikzRenderer | SVG companion or source-only preview | .tex, SVG, PNG, PDF |
Explicit Render Targets
The spec-first pipeline can also force a render target independently from the inferred intent for Generate diagram and Preview diagram. The standard Summarise as Mermaid diagram command remains Mermaid-compatible.
| Render target | Artifact boundary | Runtime dependency policy |
|---|---|---|
editable-html-svg |
Self-contained HTML with semantic inline SVG | no external editor runtime |
drawio |
.drawio XML plus SVG/MD review companions |
no diagrams.net runtime in the plugin |
drawnix |
.drawnix JSON subset plus SVG/MD review companions |
no Drawnix or Plait runtime in the plugin |
circuitikz |
validated .tex source plus SVG/MD review companions |
no bundled LaTeX or TikZJax runtime |
Circuitikz support is intentionally constrained. The front-end settings now expose Circuit (Circuitikz) as a preferred diagram type and Circuitikz + SVG preview as a preferred render target without requiring Developer mode, but the renderer still accepts only a validated DiagramSpec(intent: "circuit", circuitSpec). It writes deterministic circuitikz TeX and a reviewable SVG companion; real LaTeX/TikZJax compile evidence remains an optional maintainer smoke check.
Module Map
| Module | Responsibility |
|---|---|
src/main.ts |
Plugin entrypoint, command registration, orchestration |
src/llmProviders.ts |
26 provider definitions, metadata, KNOWN_MODEL table |
src/llmUtils.ts |
Transport dispatch, token resolution, retry, response cache |
src/fileUtils.ts |
File processing, Mermaid repair, concept extraction |
src/searchUtils.ts |
Web research, Tavily/DuckDuckGo integration |
src/translate.ts |
Translation pipeline with chunking |
src/promptUtils.ts |
Task-specific prompts (legacy + spec-first) |
src/diagram/ |
Diagram domain model, adapters, renderers |
src/rendering/ |
Render host, preview, export, theme |
src/ui/ |
Settings tab, sidebar, modals, welcome screen |
src/i18n/ |
22 locales, task language policy |
src/operations/ |
Operation registry, host adapters, capability/contract export, reusable command orchestration |
src/batchProgressStore.ts |
Interrupt-resume batch state persistence |
src/providerDiagnostics.ts |
LLM provider connection diagnostics |
CLI Boundary Reality
Current host evidence matters:
- the local stable wrapper
obsidian-clion this machine exposes desktop/debug entrypoints such ashelp,version,vaults,vault,doctor,native,gui, anddebug - the underlying official
obsidianCLI already supportscommandsandcommand id=<command-id>, and it can list/execute plugin-registered commands - however, this is still only a command trigger surface, not a mature plugin integration protocol with typed arguments, result contracts, capability metadata, or stable automation semantics
That means Notemd's future CLI story still cannot stop at "reuse sidebar buttons from the terminal". The real extraction targets are lower-level capabilities that already have partial independent shape:
src/providerDiagnostics.tssrc/diagram/diagramGenerationService.tssrc/workflowButtons.tssrc/batchProgressStore.ts- config/profile semantics such as
LLMProviderConfig.localOnly
The architectural gap is that src/main.ts still owns too much orchestration, UI lifecycle, and Obsidian runtime coupling. Until a host-neutral operation layer exists, plugin command IDs can be triggered from the official CLI, but they still remain product surfaces rather than stable engineering APIs.
The gap is smaller than before:
src/operations/diagramGenerateOperation.tsnow carries reusable diagram execution below the command layersrc/operations/providerDiagnosticCommand.tsnow carries provider-diagnostic command orchestration below the command layersrc/operations/diagramCommandHostAdapter.tsnow carries Mermaid/artifact save completion, direct Vega-Lite preview orchestration, and the public diagram command wrappers (runGenerateDiagramCommandWithHost,runPreviewExperimentalDiagramCommandWithHost) below the command layersrc/operations/configProfileCommands.tsnow carries provider-profile import/export plus CLI capability/contract export orchestration below the command layersrc/operations/providerDiagnosticReportPersistence.tsnow carries collision-safe provider-diagnostic report file creation below the command layersrc/operations/providerDiagnosticCommandHostAdapter.tsnow carries developer-diagnostic host loading, report-persistence wiring, and notice shaping below the command layersrc/operations/configProfileCommandHostAdapter.tsnow carries config/profile state persistence, CLI export notice shaping, and import/export error mapping below the command layersrc/operations/providerConnectionTestCommandHostAdapter.tsnow carries shared provider connection test loading plus both the raw test runner and the interactive busy/reporter wrapper, and is now reused by the command path and the settings tabsrc/operations/noteProcessingCommandHostAdapter.tsnow carries not onlyprocess-current-add-links,process-folder-add-links,batch-generate-from-titles,generate-from-title, andresearch-and-summarize, but alsotranslate-current-file,batch-translate-folder,extract-concepts-current,extract-concepts-folder,extract-original-text, andextract-concepts-and-generate-titlessrc/operations/utilityCommandHostAdapter.tsnow carries current-file duplicate checks, duplicate cleanup, batch Mermaid fix, and single/batch formula-fix command orchestration belowsrc/main.ts;check-for-duplicatesis no longer inlined inside command registrationsrc/operations/utilityCommandHostAdapter.tsnow also owns duplicate-deletion confirmation plus the no-file/success notice semantics for duplicate cleanup and batch Mermaid repair, so those user-surface effects no longer leak fromsrc/fileUtils.tssrc/operations/registry.tsnow also covers the remaining selection/export-adjacent automation seams:editor.create-link-and-generate,provider.profile.export,provider.profile.import,cli.capability-manifest.export, andcli.invocation-contract.exportnow share the same registry/capability/contract surface as the earlier batches- Write-heavy contract enrichment is now proven across the first
src/fileUtils.tssub-slice as well:processFile()returnsProcessFileResult,generateContentForTitle()returnsGenerateContentForTitleResult,batchGenerateContentForTitles()returnsBatchGenerateContentForTitlesResult, andrunProcessFolderWithNotemdCommandWithHost()now returnsBatchProcessFolderResultwithsavedCount,fileResults,errors, andcancelled src/fileUtils.tsno longer decides the user-surface "no eligible markdown files" batch-generation outcome by itself; it returns structured batch state andsrc/operations/noteProcessingCommandHostAdapter.tsnow owns the no-file notice semantics- The remaining
src/fileUtils.tstail is now landed too:batchFixMermaidSyntaxInFolder()returnsBatchMermaidFixResult,checkAndRemoveDuplicateConceptNotes()returnsConceptDedupeResult, destructive confirmation is injected from the host adapter, and batch Mermaid no-file handling is now host-owned instead of utility-owned src/operations/registry.tsnow models the richerfile.process-add-links,file.process-folder-add-links,content.generate-from-title,content.batch-generate-from-titles,mermaid.batch-fix,concept.dedupe,translate.*, andformula.*result schemas directly, so capability export and invocation-contract export no longer flatten those flows into path-only or count-only semanticssrc/fileUtils.tsandsrc/extractOriginalText.tsnow accept narrower runtime contexts instead of the concreteNotemdPluginclass, which shows the boundary work has moved beyond wrapper extraction into utility host-coupling reductionsrc/main.tsnow mainly retains command registration, host construction, and the deeper diagram execution helpers; the previous highest-value public direct command surfaces now delegate through host adapters instead of inlining busy/reporter/preview lifecycle logic- The newly-landed direct-surface wrapper batch covers
testLlmConnectionCommand,generateDiagramCommand, andpreviewExperimentalDiagramCommand; each now returns a structured result boundary instead of remaining fire-and-forget UI glue - The latest refinement is that
diagram.generateshould be read as the host-neutral generation contract, not as a synonym for the shipped active-file commands. Its operation-levelsafe/read-onlymetadata describes the explicitsourceMarkdown -> DiagramGenerationResultcore, while the mapped command bindings still truthfully carryrequires-active-file/write-filesemantics. - The next real gap is therefore no longer the public command entrypoints themselves: typed contracts already exist for
diagram.previewandprovider.connection.test, the substantive save/artifact execution path now lives insrc/operations/diagramCommandExecution.ts, anddiagram.generatenow returns explicit follow-through details (kind,outputPath,previewOpened,autoFixAttempted,artifactTarget) alongside the backward-compatible top-leveloutputPath/previewOpenedfields. - The maintainer-local semantic verification layer is now no longer just prose:
npm run verify:diagram-semanticsgenerates a secret-free Markdown checklist template with repo gates, vault-aware CLI checks, and Mermaid / JSON Canvas / Vega-Lite evidence sections without relying on tracked vault paths or live secrets. - The ordered convergence path is now explicit: keep
diagram.generateas the host-neutral core, treat the newly landed typed follow-through as the command-completion layer beneath it, then move to packaging/semantic-verification convergence work and only after that reopen stronger public CLI claims or broader architectural reshaping.
Key Design Decisions
- Spec-first diagram generation: LLM emits structured
DiagramSpecJSON, not raw Mermaid syntax. Decouples intent from renderer. - Transport-driven dispatch: OpenAI-compatible providers share one runtime. No per-provider code paths.
- Cline-aligned token resolution: Unknown models defer to API provider. Known models use metadata table.
- Operation-core vs command-binding split: Registry operation metadata can describe a host-neutral reusable core even when the shipped commands remain active-file, write-file, or preview-bound surfaces.
diagram.generateis the current proof case. - Iframe-host preview: Vega-Lite and HTML rendered in sandboxed iframe. Mermaid rendered inline.
- LocalOnly provider storage: API keys can be device-local while workflow settings sync.
- Response caching: Identical LLM calls within 5-minute TTL return cached results.
Verification
npm run build— TypeScript compilation + esbuild bundlenpm test -- --runInBand— the full Jest matrix currently covers 137 suites and 871 tests; in a/.worktrees/checkout usenpx jest --runInBand --config /tmp/notemd-worktree-jest.cjsbecause the repo Jest ignore pattern excludes worktree pathsnpm run audit:i18n-ui— No hardcoded UI stringsnpm run audit:render-host— Render host self-contained in main.jsgit diff --check— Whitespace hygiene