Architecture
Architecture
Package graph, AssetResolver contract, monorepo layout.
Organization and Repositories
Section titled “Organization and Repositories”The core tooling lives under the lolay GitHub organization. Copyright Lolay, Inc.
| Repo | Type | License | Contents |
|---|---|---|---|
lolay/nowline | OSS monorepo | Apache 2.0 | Engine packages (core, layout, renderer, exporters), CLI, LSP, in-tree VS Code extension, browser embed (@nowline/embed), GitHub Action source (packages/nowline-action/), examples, docs |
lolay/nowline-action | OSS Marketplace mirror | Apache 2.0 | Compiled GitHub Action — action.yml and minified dist/ populated by release.yml on every tag. No source code; the source lives in this monorepo at packages/nowline-action/. The mirror exists so the Action is listable on GitHub Marketplace (which requires action.yml at repo root). |
lolay/nowline-obsidian | OSS Marketplace mirror | Apache 2.0 | Obsidian plugin — manifest.json, README.md, LICENSE, and release assets populated by release.yml on every tag. No source code; the source lives in this monorepo at packages/obsidian-plugin/. The mirror exists because Obsidian’s community directory requires manifest.json and release assets at repo root — the same root-repo constraint as lolay/nowline-action. |
| Infrastructure repository | Cloud control plane | Apache 2.0 (private during bootstrap) | Terraform for every GCP project, billing link, IAM grant, Workload Identity Federation pool, org policy, and Firebase Hosting resource Lolay operates for Nowline — including the two nowline-embed-{prod,dev} projects that back the embed CDN. No application code, no cloud resources outside of TF. This monorepo’s release.yml deploys consume the infra’s WIF outputs (via GitHub environment-scoped variables) but never the other way around. See embed.md § Distribution for the responsibility split. |
This shape mirrors Mermaid’s split: parser-coupled packages (parser, layout, renderer, embed, plugins, and IDE extensions) live together in the monorepo because cross-package changes are common; write-only mirror repos exist only where a registry or marketplace requires files at a repository root (the GitHub Action and the Obsidian plugin).
Proprietary web apps (free viewer, Pro editor, enterprise) are developed in private repositories owned by Lolay, Inc. They consume the OSS packages documented here via npm.
Dependency Rule
Section titled “Dependency Rule”Dependencies flow one direction: downstream consumers depend on OSS, never the reverse. All paid apps consume OSS packages via npm. No OSS package may import from a proprietary repo.
Infrastructure dependencies flow the same way: the infrastructure repository provisions the cloud projects every other repo’s deploy pipeline targets. This repo’s release.yml consumes the infra repo’s terraform output values (wired into GitHub environment-scoped variables per the infra repo’s ops/runbook.md); it never reaches back to mutate cloud state directly. The only documented exception is Squarespace DNS — no Terraform provider exists, so DNS edits are manual against the infra repo’s ops/dns.md inventory.
Discoverability
Section titled “Discoverability”lolay/nowline is the hub. Its README links to all satellite OSS repos. Each satellite repo links back to lolay/nowline. GitHub topics (nowline, roadmap, dsl) reinforce discoverability under the lolay org.
OSS Monorepo Structure (lolay/nowline)
Section titled “OSS Monorepo Structure (lolay/nowline)”Managed with pnpm workspaces. All packages share a single version and release cycle.
nowline/ packages/ core/ # @nowline/core — Langium grammar, parser, AST, validation layout/ # @nowline/layout — Positioning engine (AST → positioned model) renderer/ # @nowline/renderer — SVG renderer (positioned model → SVG) export-core/ # @nowline/export-core — Shared types, unit converter, PDF page-size parser, font resolver export-png/ # @nowline/export-png — PNG via @resvg/resvg-wasm (pure WASM, byte-reproducible across hosts) export/ # @nowline/export — shared export kernel: parse→resolve→layout→renderSvg→format adapter; one code path for every surface (see export-determinism.md) export-pdf/ # @nowline/export-pdf — Vector PDF via PDFKit + svg-to-pdfkit export-html/ # @nowline/export-html — Self-contained HTML page with inline pan/zoom export-mermaid/ # @nowline/export-mermaid — Markdown + Mermaid `gantt` block export-xlsx/ # @nowline/export-xlsx — Five-sheet workbook via ExcelJS export-msproj/ # @nowline/export-msproj — MS Project import XML cli/ # @nowline/cli — `nowline` CLI, every export format, ~70 MB standalone binary lsp/ # @nowline/lsp — Language server (validation, completion, navigation) vscode-extension/ # VS Code / Cursor extension wrapping @nowline/lsp embed/ # @nowline/embed — esbuild IIFE that renders `nowline` blocks in the browser nowline-action/ # GitHub Action source (mirrored to lolay/nowline-action on release) browser/ # @nowline/browser (m4.7) — single-call browser pipeline (renderSource / parseSource); consolidates embed + VS Code render-pipeline glue preview-shell/ # @nowline/preview-shell (m4.7) — framework-agnostic viewport chrome (zoom/pan/fit/minimap/diagnostic table); consumed by VS Code webview and downstream browser apps lsp-worker/ # @nowline/lsp-worker (m4.7) — Web Worker packaging of @nowline/lsp + CodeMirror client adapter mcp/ # @nowline/mcp (m4.8) — MCP server: typed tool surface over the CLI's capabilities (validate/render/read/create/update/delete/list/export); nowline://reference + nowline://examples resources; stdio + .mcpb; local only, no auth grammars/ nowline.tmLanguage.json # TextMate grammar for syntax highlighting examples/ # Example .nowline files (also `nowline --init` templates) — m4.7 adds showcase.nowline as the canonical "couple of swimlanes + linear flow + one parallel block + anchor + milestone" sample tests/ # Renderer manual-validation fixtures (one stressed axis per file) scripts/ # Repo-wide build / packaging scripts specs/ # Design specs for the OSS tooling package.json # Workspace root LICENSE # Apache 2.0Package Dependency Graph
Section titled “Package Dependency Graph”@nowline/core ├── @nowline/layout │ ├── @nowline/renderer │ │ ├── (used by @nowline/cli, @nowline/vscode-extension) │ │ └── @nowline/browser (m4.7) │ │ ├── @nowline/embed (m4 — re-exports render/parse from @nowline/browser after m4.7) │ │ └── (used by @nowline/vscode-extension via a thin shim after m4.7; used directly by downstream browser apps) │ └── @nowline/export-core │ ├── @nowline/export-html │ ├── @nowline/export-pdf │ ├── @nowline/export-png │ ├── @nowline/export-mermaid │ ├── @nowline/export-xlsx │ └── @nowline/export-msproj └── @nowline/lsp ├── @nowline/vscode-extension (Node-side LSP client) └── @nowline/lsp-worker (m4.7 — browser-side Web Worker packaging)
@nowline/preview-shell (m4.7) — standalone, no engine deps; consumed by @nowline/vscode-extension's webview and downstream browser apps.
@nowline/mcp (m4.8) depends on @nowline/core (validate, read, create, update, delete, list) and on the shared @nowline/export kernel for render/export, which it runs in-process — no `nowline` CLI shell-out on the canonical `npx @nowline/mcp` path. This keeps it byte-identical to the CLI and the extension per export-determinism.md. Optionally depends on @nowline/browser + @nowline/preview-shell for the MCP Apps UI variant. No cloud deps; local only.
@nowline/export (the shared kernel) depends on core, layout, renderer, export-core, and every @nowline/export-*. It is the single export code path; @nowline/cli, @nowline/mcp, vscode-extension, and the downstream web apps all consume it (rather than each re-implementing parse→layout→render→format). See export-determinism.md.
@nowline/cli depends on @nowline/export (and transitively the above).Dependencies flow downward only. No upward or sideways imports. The graph is enforced by package.json declarations, not just convention.
Core layers
Section titled “Core layers”- @nowline/core — Langium grammar, parser, typed AST, and validator. Pure TypeScript; no DOM, no Node-specific APIs in the hot path. Zero internal deps.
- @nowline/layout — Layout engine. Takes the AST and produces a positioned model (x/y coordinates, dimensions, connection points). Pure computation, no rendering.
- @nowline/renderer — SVG renderer. Takes the positioned model and produces a deterministic SVG string. Reads local assets referenced from the roadmap (e.g. company logos) via an injectable asset resolver so the package stays browser-safe — see § Local Asset Resolution.
Export packages
Section titled “Export packages”- @nowline/export-core — Shared types, unit converter, PDF page-size parser, 5-step font resolver, bundled DejaVu fonts. The other
export-*packages depend on this for common plumbing. - @nowline/export-png — PNG via
@resvg/resvg-wasm(pure WebAssembly; byte-reproducible across hosts and bundlable into a single universal artifact, unlike the native@resvg/resvg-js.nodeaddon). Seeexport-determinism.md§ PNG rasterizer correction. - @nowline/export — Shared export kernel. The single code path from
.nowlinesource to any of the eight artifacts (parse → resolve includes → layout →renderSvg→ format adapter), parameterized by a runtime-agnosticHostEnv. Every surface (CLI, MCP, extension, web apps) calls this rather than re-implementing the pipeline; this is what enforcesexport-determinism.md. - @nowline/export-pdf — Vector PDF via
pdfkit+svg-to-pdfkit. - @nowline/export-html — Self-contained HTML page with inline pan/zoom JS.
- @nowline/export-mermaid — Markdown file with a Mermaid
ganttblock, for embedding in READMEs and wikis that already render Mermaid. - @nowline/export-xlsx — Five-sheet workbook via
exceljs. - @nowline/export-msproj — MS Project import XML.
Surfaces
Section titled “Surfaces”- @nowline/cli — Command-line entry point. Wraps core + layout + renderer + every exporter. Compiled to standalone binaries via
bun compile. Seecli.mdandcli-distribution.md. - @nowline/lsp — Language server. Reuses core’s parser/validator behind the LSP wire protocol so editors get the same diagnostics as the CLI. See
ide.mdandlsp.md. - vscode-extension — VS Code / Cursor extension that boots
@nowline/lspand registers commands. Lives in this monorepo atpackages/vscode-extension/. Published asnowline.vscode-nowlineon both the VS Code Marketplace and Open VSX from monorepo CI (release.yml) viavsce publishandovsx publish. No standalonelolay/nowline-vscoderepo is planned: both marketplaces publish from a built package directory and impose no repo-root constraint. After m4.7, the webview’s render pipeline is a thin shim over@nowline/browserand the inline shell template is replaced by@nowline/preview-shell. - @nowline/embed — Browser bundle (esbuild IIFE) that finds
```nowlinefenced code blocks in a page and renders them client-side. Mirrors Mermaid’s embed surface (initialize/render/parse/init). Consumes core + layout + renderer as workspace deps. After m4.7,@nowline/embedre-exportsrender/parsefrom@nowline/browser. Seeembed.md. - GitHub Action (
packages/nowline-action/, mirrored tololay/nowline-action) — Renders.nowlinefiles in CI for hosts that strip<script>tags (GitHub READMEs, issue comments). Shells out to@nowline/cli, so it couples loosely to the engine through a stable command-line interface. Source ships in the monorepo for lock-step PRs with the CLI it drives; the mirror repo carries the compiledaction.yml+dist/for Marketplace listing. Seeembed.md§ GitHub Action. - @nowline/browser (m4.7) — Single-call browser pipeline. Public API:
renderSource(source, options)andparseSource(source, options). Consolidates today’spackages/embed/src/pipeline.tsandpackages/vscode-extension/src/preview/render-pipeline.ts; keeps the VS Code branch’s Nodefs-backed include resolver as a pluggable hook. Re-exports the canonical showcase example (seeexamples/showcase.nowline) as a string so downstream apps don’t copy-paste it. - @nowline/preview-shell (m4.7) — Framework-agnostic viewport chrome. Public API:
mountPreview(rootEl, options) → { setSvg, setDiagnostics, dispose, fitPage, fitWidth, ... }. Hoists ~1000 LOC of zoom/pan/fit/minimap/diagnostic-table logic out of the VS Code webview’s inline template into a reusable ES module. No opinion on text editor or message bus. Also exports the shared render-convention helpers:applyRenderResult,classifyRenderResult,themeOverrideToDiagramTheme,nowOverrideToToday(type-only@nowline/browserdep so shell-only bundles remain engine-free). - @nowline/preview — Layer 2 live-preview controller.
mountLivePreview(rootEl, opts)wrapsmountPreviewand owns therenderSource → applyRenderResultloop with optional debounce. Every entry point is injectable:render?,apply?,beforeRender?. Returns the rawPreviewHandlefor direct imperative control. Depends on@nowline/preview-shell+@nowline/browser; kept in a separate package so importingmountPreviewalone never pulls in the render engine. - @nowline/lsp-worker (m4.7) — Browser-side packaging of
@nowline/lsp. Ships a Web Worker entry (@nowline/lsp-worker/worker) and a thin client adapter (@nowline/lsp-worker/client) that CodeMirror’s@codemirror/lint/@codemirror/autocomplete/ hover extensions can consume via standard LSP-over-postMessage. The Node-side@nowline/lspcontinues to power the VS Code extension; the worker package only adds a browser packaging without changing what the language server does. Seelsp.mdfor the wire-protocol contract (range deltas ontextDocument/didChange). - @nowline/mcp (m4.8) — MCP server. Entry point:
npx @nowline/mcp(MCP CLI, stdio) or the.mcpbbundle (MCP Desktop, Claude Desktop one-click). Tools:validate,render,read,create,update,delete,list,export.render/exportrun in-process via the shared@nowline/exportkernel — nonowlinebinary dependency on the canonical path — so output is byte-identical to the CLI perexport-determinism.md. Resources:nowline://reference(DSL grammar / man page fromnowline.5) andnowline://examples(canonical roadmap examples fromexamples/). Optional MCP Apps UI variant returns an HTML resource mounting@nowline/browser+@nowline/preview-shellfor live in-chat preview. Local only: no network, no auth, no cloud endpoints; open-core boundary enforced. Thenowlinebinary’s--mcpflag starts the same server code hosted by the CLI (power-user path). Seemcp.mdandcli-distribution.md§ MCP distribution.
Technology Choices
Section titled “Technology Choices”| Concern | Choice | Rationale |
|---|---|---|
| Language | TypeScript | Single language across parser, renderer, CLI, embed. Langium requires TS. |
| Parser | Langium | TypeScript-native, generates typed AST from a grammar file, provides LSP server for free. |
| Package manager | pnpm | Strict dependency resolution prevents phantom imports. Fast installs, content-addressable store, superior workspace filtering. Pin via "packageManager": "pnpm@9" in root package.json. |
| CLI binary | bun compile | Produces standalone binaries (~55MB) from TypeScript. No Node.js install required for end users. |
| SVG rendering | Custom (TypeScript) | SVG is a text format — template-based generation is simpler than a rendering library dependency. |
| PNG conversion | resvg-wasm | SVG → PNG rasterization via @resvg/resvg-wasm. Pure WebAssembly: one binary works in Node, Bun, and the browser, bundles into a single universal artifact, and rasterizes bit-reproducibly across hosts (native @resvg/resvg-js is faster but is a platform-specific .node addon and can vary by CPU/SIMD). Chosen so every surface shares one rasterizer per export-determinism.md. |
| PDF generation | PDFKit | Pure JS, no native deps (~2MB). Walks the positioned model to produce true vector PDFs. Bundles cleanly with bun compile — no Chromium dependency. |
| XLSX generation | ExcelJS | Mature (13M weekly downloads), excellent data/formatting/auto-filter support. ~1 MB JS — negligible impact on the ~55 MB CLI binary. No chart support; stacked-bar Gantt sheet deferred. |
| Embed bundling | esbuild | Fast, zero-config bundling of core + layout + renderer into a single IIFE browser script. Same toolchain the VS Code extension already uses; one esbuild config covers both. |
| Embed CDN | Firebase Hosting (two projects) | embed.nowline.io (prod, tag-driven, Cache-Control: immutable per-version) and embed.nowline.dev (latest main + per-PR ephemeral preview channels). Branded URLs, custom headers, per-version telemetry. Provisioned by Terraform in the infrastructure repository (stack: stacks/embed/, milestone m7) — this monorepo’s release pipeline deploys the built bundle to projects the infra repo owns, via WIF. See embed.md § Distribution and the deploy runbook in the infrastructure repository. |
| Testing | Vitest | Fast, TypeScript-native, compatible with the monorepo structure. |
| Lint and format | Biome | Single Rust binary handling lint, format, and import organization. Type-aware rules in v2.4 cover the promise hygiene we want (noFloatingPromises, noMisusedPromises) without a typescript-eslint dependency. Replaces the aspirational ESLint+Prettier reference that was never wired up. |
| CI | GitHub Actions | Standard for GitHub-hosted repos. |
Local Asset Resolution
Section titled “Local Asset Resolution”The DSL allows local file references for assets rendered inside the chart — currently the roadmap logo: property (dsl.md § Roadmap logo: and logo-size:). These files must be read, sanitized, and inlined into the output artifact so the result is self-contained.
To keep @nowline/renderer browser-safe (no fs, no path, no process), the renderer never reads files directly. It accepts an AssetResolver — an injectable function with the signature (relPath: string) => Promise<AssetBytes | null> — and the caller supplies an environment-appropriate implementation:
| Environment | Resolver implementation |
|---|---|
@nowline/cli | Node resolver rooted at the input file’s directory. Rejects paths that escape the root (no .. traversal beyond the project root), rejects absolute paths outside an explicit --asset-root, and returns null on missing files so the renderer can emit its warning. |
| Browser embed (m4) | Resolver that looks up assets in an author-supplied map (inline data: URIs or pre-bundled bytes). No network fetches — the embed script never hits disk or remote servers for DSL-referenced assets. |
Downstream consumers (e.g. a hosted editor or an MCP server) plug in their own resolvers against the same contract without adding new environment coupling to @nowline/renderer.
Embedding format is decided by the renderer from the resolved bytes + original extension:
.svg— parsed, sanitized (strip<script>, strip externalhref/src, strip<foreignObject>), namespaced, and inlined as a<g>subtree..png,.jpg/.jpeg,.webp— wrapped in<image href="data:image/<type>;base64,...">. The renderer does not re-encode; it passes the raw bytes through.
The resolver abstraction is reused for any future asset-bearing property (e.g. per-entity icons) without adding new environment coupling to @nowline/renderer.
Build and Release
Section titled “Build and Release”- Build: TypeScript compilation, plus an esbuild step in
packages/embed/and (m3.5)packages/nowline-action/. Both bundlers emit minified JS plus source maps. - Test: Vitest across all packages. A workspace-wide
pack-and-smokeCI job runspnpm packon every publishable package, installs the resulting tarballs into a scratch project, and runs a smoke render against them. This catchespackage.jsonexportsmistakes and missingdist/files that workspace symlinks would otherwise mask — the dogfooding signal that a sibling-repo split would buy, recovered without leaving the monorepo. - Lint and format: Biome (single tool, type-aware rules, single config). See
CONTRIBUTING.md§ “Linting and formatting” for the rule overrides we adopted and why. - Release: Single version across all packages in this repo. npm publish for library packages. GitHub Releases for CLI binaries.
- CLI distribution:
bun compileproduces binaries for macOS (arm64, x64), Linux (x64, arm64), Windows (x64, arm64). Published to Homebrew (macOS, Linux, WSL), apt-get, GitHub Releases (Windows .exe direct download), and npm. - Embed distribution: Built in
packages/embed/, published as@nowline/embedon npm viarelease.yml. Firebase Hosting deploys serveembed.nowline.io(prod, tag-driven) andembed.nowline.dev(dev + ephemeral PR channels) — two Firebase projects, per-versionCache-Control, per-PR preview channels. Seeembed.md§Distribution. - Action distribution: Built in
packages/nowline-action/(m3.5). On each release,release.ymlpushes the compiledaction.yml+dist/to thelolay/nowline-actionmirror repo so the Action is listable on GitHub Marketplace. The mirror is a publish target; PRs land in this monorepo.