jacobinwwey_obsidian-NotEMD/docs/architecture.md
Jacobinwwey c5a1e12c4d docs: add bilingual architecture overview with Mermaid diagrams
- System architecture diagram (user → plugin → LLM → diagram → output)
- LLM calling pipeline sequence diagram (cache, token resolution, transports)
- Diagram rendering platform flowchart (spec plane → render plane → targets)
- Token resolution logic decision tree
- Transport protocol table (5 runtimes, 25 providers)
- Diagram intent support matrix (8 intents, renderers, preview, export)
- Module map (13 modules with responsibilities)
- Key design decisions (6 items)
- Bilingual: docs/architecture.md + docs/architecture.zh-CN.md

notebook-navigator pattern #5 (low priority) — COMPLETE
2026-05-02 05:28:44 -05:00

7.7 KiB

Notemd Architecture Overview

Updated: 2026-05-02

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["SVG / PNG 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 21 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/>7 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"]
    end

    subgraph Host["Preview Layer"]
        IFRAME["IframeRenderHost"]
        MODAL["DiagramPreviewModal"]
        EXPORT2["SVG / PNG 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
    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 SVG, source
dataChart vega-lite VegaLiteRenderer modal/iframe (sandboxed) SVG, source

Module Map

Module Responsibility
src/main.ts Plugin entrypoint, command registration, orchestration
src/llmProviders.ts 25 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/batchProgressStore.ts Interrupt-resume batch state persistence
src/providerDiagnostics.ts LLM provider connection diagnostics

Key Design Decisions

  1. Spec-first diagram generation: LLM emits structured DiagramSpec JSON, not raw Mermaid syntax. Decouples intent from renderer.
  2. Transport-driven dispatch: 21 OpenAI-compatible providers share one runtime. No per-provider code paths.
  3. Cline-aligned token resolution: Unknown models defer to API provider. Known models use metadata table.
  4. Iframe-host preview: Vega-Lite and HTML rendered in sandboxed iframe. Mermaid rendered inline.
  5. LocalOnly provider storage: API keys can be device-local while workflow settings sync.
  6. Response caching: Identical LLM calls within 5-minute TTL return cached results.

Verification

  • npm run build — TypeScript compilation + esbuild bundle
  • npm test -- --runInBand — 109 suites, 585 tests
  • npm run audit:i18n-ui — No hardcoded UI strings
  • npm run audit:render-host — Render host self-contained in main.js
  • git diff --check — Whitespace hygiene