Skip to content

Architecture

Architecture

Package graph, AssetResolver contract, monorepo layout.

The core tooling lives under the lolay GitHub organization. Copyright Lolay, Inc.

RepoTypeLicenseContents
lolay/nowlineOSS monorepoApache 2.0Engine 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-actionOSS Marketplace mirrorApache 2.0Compiled 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-obsidianOSS Marketplace mirrorApache 2.0Obsidian 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 repositoryCloud control planeApache 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.

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.

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.

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.0
@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.

  • @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.
  • @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 .node addon). See export-determinism.md § PNG rasterizer correction.
  • @nowline/export — Shared export kernel. The single code path from .nowline source to any of the eight artifacts (parse → resolve includes → layout → renderSvg → format adapter), parameterized by a runtime-agnostic HostEnv. Every surface (CLI, MCP, extension, web apps) calls this rather than re-implementing the pipeline; this is what enforces export-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 gantt block, 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.
  • @nowline/cli — Command-line entry point. Wraps core + layout + renderer + every exporter. Compiled to standalone binaries via bun compile. See cli.md and cli-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.md and lsp.md.
  • vscode-extension — VS Code / Cursor extension that boots @nowline/lsp and registers commands. Lives in this monorepo at packages/vscode-extension/. Published as nowline.vscode-nowline on both the VS Code Marketplace and Open VSX from monorepo CI (release.yml) via vsce publish and ovsx publish. No standalone lolay/nowline-vscode repo 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/browser and the inline shell template is replaced by @nowline/preview-shell.
  • @nowline/embed — Browser bundle (esbuild IIFE) that finds ```nowline fenced 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/embed re-exports render / parse from @nowline/browser. See embed.md.
  • GitHub Action (packages/nowline-action/, mirrored to lolay/nowline-action) — Renders .nowline files 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 compiled action.yml + dist/ for Marketplace listing. See embed.md § GitHub Action.
  • @nowline/browser (m4.7) — Single-call browser pipeline. Public API: renderSource(source, options) and parseSource(source, options). Consolidates today’s packages/embed/src/pipeline.ts and packages/vscode-extension/src/preview/render-pipeline.ts; keeps the VS Code branch’s Node fs-backed include resolver as a pluggable hook. Re-exports the canonical showcase example (see examples/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/browser dep so shell-only bundles remain engine-free).
  • @nowline/preview — Layer 2 live-preview controller. mountLivePreview(rootEl, opts) wraps mountPreview and owns the renderSource → applyRenderResult loop with optional debounce. Every entry point is injectable: render?, apply?, beforeRender?. Returns the raw PreviewHandle for direct imperative control. Depends on @nowline/preview-shell + @nowline/browser; kept in a separate package so importing mountPreview alone 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/lsp continues to power the VS Code extension; the worker package only adds a browser packaging without changing what the language server does. See lsp.md for the wire-protocol contract (range deltas on textDocument/didChange).
  • @nowline/mcp (m4.8) — MCP server. Entry point: npx @nowline/mcp (MCP CLI, stdio) or the .mcpb bundle (MCP Desktop, Claude Desktop one-click). Tools: validate, render, read, create, update, delete, list, export. render/export run in-process via the shared @nowline/export kernel — no nowline binary dependency on the canonical path — so output is byte-identical to the CLI per export-determinism.md. Resources: nowline://reference (DSL grammar / man page from nowline.5) and nowline://examples (canonical roadmap examples from examples/). Optional MCP Apps UI variant returns an HTML resource mounting @nowline/browser + @nowline/preview-shell for live in-chat preview. Local only: no network, no auth, no cloud endpoints; open-core boundary enforced. The nowline binary’s --mcp flag starts the same server code hosted by the CLI (power-user path). See mcp.md and cli-distribution.md § MCP distribution.
ConcernChoiceRationale
LanguageTypeScriptSingle language across parser, renderer, CLI, embed. Langium requires TS.
ParserLangiumTypeScript-native, generates typed AST from a grammar file, provides LSP server for free.
Package managerpnpmStrict dependency resolution prevents phantom imports. Fast installs, content-addressable store, superior workspace filtering. Pin via "packageManager": "pnpm@9" in root package.json.
CLI binarybun compileProduces standalone binaries (~55MB) from TypeScript. No Node.js install required for end users.
SVG renderingCustom (TypeScript)SVG is a text format — template-based generation is simpler than a rendering library dependency.
PNG conversionresvg-wasmSVG → 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 generationPDFKitPure JS, no native deps (~2MB). Walks the positioned model to produce true vector PDFs. Bundles cleanly with bun compile — no Chromium dependency.
XLSX generationExcelJSMature (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 bundlingesbuildFast, 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 CDNFirebase 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.
TestingVitestFast, TypeScript-native, compatible with the monorepo structure.
Lint and formatBiomeSingle 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.
CIGitHub ActionsStandard for GitHub-hosted repos.

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:

EnvironmentResolver implementation
@nowline/cliNode 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 external href/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: 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-smoke CI job runs pnpm pack on every publishable package, installs the resulting tarballs into a scratch project, and runs a smoke render against them. This catches package.json exports mistakes and missing dist/ 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 compile produces 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/embed on npm via release.yml. Firebase Hosting deploys serve embed.nowline.io (prod, tag-driven) and embed.nowline.dev (dev + ephemeral PR channels) — two Firebase projects, per-version Cache-Control, per-PR preview channels. See embed.md §Distribution.
  • Action distribution: Built in packages/nowline-action/ (m3.5). On each release, release.yml pushes the compiled action.yml + dist/ to the lolay/nowline-action mirror repo so the Action is listable on GitHub Marketplace. The mirror is a publish target; PRs land in this monorepo.