Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .config/lefthook.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
commit-msg:
jobs:
- name: conventional commits
run: npx commitlint --edit {1}
run: bun x commitlint --edit {1}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ playground/.astro/
.env.local
.DS_Store
!scripts/*
*.tgz
60 changes: 38 additions & 22 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ sites. `playground/` is a small Starlight site that consumes it, and
import the package by name and resolve it through Node's self-reference. The
wiki in `docs/wiki/` is the published documentation — keep it in step with the
code. It is grouped into `guide/` (using the plugin) and `contributing/`
(changing it), each with a `README.md` index, plus `Troubleshooting.md` and
`Roadmap.md` at the root; new pages belong in one of those two folders.
(changing it), each with a `README.md` index, plus `Troubleshooting.md` at the
root; new pages belong in one of those two folders.

```
bun run build:lib # bundle src/ -> dist/ (+ .d.ts, grammar data)
Expand All @@ -26,11 +26,11 @@ bun run grammar:check # the committed grammar snapshot matches its derivation
```

`dev`, `build`, `check` and `test:site` all run `build:lib` first, because both
host sites import the plugin from `dist/` — the artifact a site installs.
Importing `../src/index.ts` there instead puts the plugin inside the Vite module
runner that loads `astro.config.mjs`, and its dynamic `import()` of
host sites import the plugin by package name, which resolves to `dist/` — the
artifact a site installs. Importing `../src/index.ts` there instead puts the
plugin inside the Vite module runner that loads `astro.config.ts`, and its dynamic `import()` of
`@astrojs/markdown-satteri` then dies with "module runner has been closed".
`examples/astro/astro.config.mjs` imports `@purview-dev/astroc4/integration` and
`examples/astro/astro.config.ts` imports `@purview-dev/astroc4/integration` and
its page imports `@purview-dev/astroc4/components/LikeC4View.astro`; inside this
repository those resolve the way an installed site resolves them, through
`package.json` `exports`.
Expand Down Expand Up @@ -66,20 +66,21 @@ running: `bun scripts/smoke.ts http://localhost:4321`. The static build passes
even when islands fail to hydrate — only the smoke test proves they draw nodes.
For highlighting, the smoke test's "distinct colours per block" check is the
proof that the grammar reached Expressive Code: the playground has no
`ec.config.mjs` on purpose.
Expressive Code config on purpose.

The plain-Astro path has its own two proofs: `bun run check` runs `astro check`
over `examples/astro` (where `App.Locals` has no `t`, so a Starlight-ism in the
components fails there first), and `bun run dev:astro` plus
`bun scripts/hydration.ts http://localhost:4322` shows the island drawing with
the fallback palette. `bun run test:site` covers both host sites from the tarball,
the fallback palette (`test:site` also asserts the example's `--likec4-accent`
override reaches the shell). `bun run test:site` covers both host sites from the tarball,
and runs the **full smoke suite** against the packed copy of the playground — the
same assertions the local loop uses, but on the installed package rather than
`dist/` next to the repo.

Hydration has its own dev-only failure mode: Vite served React's CommonJS
`react/compiler-runtime` raw, and every island threw "does not provide an export
named 'c'" while `astro build` stayed green. `src/index.ts` now adds it to
named 'c'" while `astro build` stayed green. `src/integration.ts` adds it to
`vite.optimizeDeps.include`; `bun run test:site` (Chromium) and
`bun scripts/hydration.ts <url>` are what catch a regression, not the build.

Expand Down Expand Up @@ -107,8 +108,8 @@ reports node/edge counts per view in ~2s; a view that lays out to 0 nodes
`withLikeC4Language` does.
- **Integration-time output goes through Astro's logger**, never `console`: the
logger honours the CLI log level and prefixes the message with the
integration's name. `registerFences` forks it to `likec4-fences` so the label
matches what `Troubleshooting.md` quotes. `console` belongs to browser code
integration's name (`[@purview-dev/astroc4]` from the Starlight plugin, which
is the label `Troubleshooting.md` quotes). `console` belongs to browser code
(`render-icon.tsx`, `ErrorBoundary.tsx`) and to `scripts/`.
- **`keywords` is release metadata, not decoration.** Astro's integrations
library indexes npm by keyword: `astro-integration` (and `withastro`) put the
Expand All @@ -123,12 +124,21 @@ reports node/edge counts per view in ~2s; a view that lays out to 0 nodes
Related: ordering between `astroc4()` and `astroExpressiveCode()` does not
matter (Astro applies both mdast plugins and `markdown.rehypePlugins`);
`examples/astro` lists Expressive Code first so that stays true.
- **No Tailwind, no host aliases.** Components are styled with plain CSS over a
`--likec4-*` layer that defaults to Starlight's `--sl-color-*` tokens, with
scheme-neutral fallbacks for plain Astro (an undefined `var()` with no fallback
is invalid at computed-value time, so every token needs one). A host's Tailwind
doesn't scan this package, so utility classes would silently be unstyled.
`--sl-color-white` is semantic (highest-contrast text), not literal white.
- **No Tailwind, no host aliases.** Components are styled with plain CSS. A
host's Tailwind doesn't scan this package, so utility classes would silently
be unstyled.
- **`--likec4-*` tokens are read, never declared, by components.** Each reads
them into a private `--_likec4-*` with the fallback chain
(`var(--likec4-border, var(--sl-color-gray-5, rgba(…)))`), so a site's value on
any ancestor inherits through instead of losing to the scoped selector.
Every chain ends in a scheme-neutral value (an undefined `var()` with no
fallback is invalid). `tokens.test.ts` enforces it; `.md` fence placeholders in
`src/fences.ts` use the same chains inline. `--sl-color-white` is semantic
(highest-contrast text), not literal white.
- **Project configs are read like LikeC4 reads them** (`src/projects.ts`): every
name in LikeC4's `ConfigFilenames` (a `satisfies` check fails the type-check if
LikeC4 changes the list), JSON names via JSON5, JS/TS via `bundle-require` with
`likec4/config` stubbed, and `name` defaulting to the folder name.
- **Two entry points, one implementation.** `src/index.ts` is the Starlight
plugin and `src/integration.ts` is the `AstroIntegration`; the plugin calls the
integration. Anything both hosts need belongs in `integration.ts`, never in the
Expand All @@ -142,7 +152,7 @@ reports node/edge counts per view in ~2s; a view that lays out to 0 nodes
(`IslandLabels`) — `t()` doesn't exist in the browser. Placeholders are
`{name}`, filled by `format()`, never i18next's `{{name}}`.
- **The fence plugin is Sätteri mdast, not remark**, appended to the site's
existing processor in `src/index.ts` so Starlight's and other
existing processor in `src/integration.ts` so Starlight's and other
integrations' plugins survive.
- **Don't import `likec4` (the Node API) at module scope** in anything
`src/index.ts` loads — see the comments in `src/fences.ts`.
Expand All @@ -151,11 +161,17 @@ reports node/edge counts per view in ~2s; a view that lays out to 0 nodes
- **`client:only` fallbacks need `slot="fallback"`**, or they're never painted.
- Imports the fence writes into pages use the package name
(`@purview-dev/astroc4/components/…`), because they resolve from the site, not from here.
- **Everything is TypeScript; `dist/` is built, never committed.** `bun run
build:lib` bundles the Node entry points with Bun (`.d.ts` from `tsc`) and
- **Everything is TypeScript, configs included (`astro.config.ts`); `dist/` is
built, never committed.** Relative imports use `.ts` specifiers. `bun run
build:lib` bundles the Node entry points with Bun (`.d.ts` from `tsc`, with
`.ts` specifiers rewritten to `.js`) and
copies the two grammar data files next to the bundle, where
`import.meta.url` finds them. `exports` points the two entry points and the
grammar JSON at `dist/`, and the components at `src/components/`: `.astro`
and `.tsx` are compiled by the consuming site, which is also why
`src/index.ts` sets `ssr.noExternal`. There are no hand-written `.d.ts` files
— the old ones shadowed the implementation's own types.
`src/integration.ts` sets `ssr.noExternal`. There are no hand-written `.d.ts`
files — the old ones shadowed the implementation's own types. Expressive Code
only reads a config named `ec.config.mjs`, so `examples/astro` passes its
options inline instead.
- **Comments only where required**: a non-obvious why (a workaround, a
load-bearing order, a platform quirk). No file/path headers, no narration.
81 changes: 81 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,87 @@ All notable changes to this project are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0-prerelease.6] - 2026-11-09

### Added

- **Fences work in plain `.md`.** A ` ```likec4 ` fence in a `.md` page — which
cannot hold a component — is compiled and laid out at build time like one in
`.mdx`, written as a placeholder carrying the model, and mounted in the browser
by a page script the integration injects. The script loads the diagram code
only on a page that has a placeholder. Replaces the "fences only render as
diagrams in .mdx files" warning; `FenceOptions.reporter` and `mdx` are
deprecated and unused.
- **`imageAliases` just work.** A project's own `likec4.config.json` can declare
`"@": "../../.assets/images"` and its model can say `icon @/azure/entra.svg`
with no change for astroc4. Every icon LikeC4 compiles to a local file —
through an alias or a path relative to the `.c4` file — is mapped to
`/_astroc4/<project>/<path>` (`@brand/…` adds a `brand/` segment, `..` becomes
`__up`), served from the original file in `astro dev`, copied into the build
output, and rendered (SVGs inline, by LikeC4). Files under `public/` keep their
public URL. No build-machine `file://` path reaches the client bundle any more,
for any icon, and nothing is written into the site's source tree.
- **Named projects.** `projects: { architecture: '../../docs/architecture/likec4' }`
compiles LikeC4 projects from anywhere — a sibling folder, another checkout, an
absolute path — alongside the ones discovered under the site root. They are
linked into `.astro/astroc4/workspace` (LikeC4 ignores anything under
`node_modules`), and edits reload in `astro dev`.
- **`<LikeC4View project="…">`** names a configured or discovered project, and
**`<LikeC4View file="diagrams/flow.c4">`** renders one `.c4` file from `src/`,
compiled at build time. Fences take `project="…"` and `file="…"` (relative to
the page) too.
- A missing icon, a missing or config-less project folder, two projects with the
same name, and two icons served at the same URL each fail the build, naming
the files involved.
- **Every LikeC4 config file name.** Projects are found by `.likec4rc`,
`.likec4.config.json` and `likec4.config.json` (read as JSON5) and by
`likec4.config.{ts,mts,cts,js,mjs,cjs}` (bundled with esbuild, with
`likec4/config` stubbed so an external project need not resolve it), as LikeC4
reads them. A config without `name` is named after its folder, as in LikeC4.
Adds `bundle-require`, `esbuild` and `json5` as dependencies; LikeC4 already
depends on all three.
- **Empty fences check `project` at build time**, in `.md` as well as `.mdx`: an
unknown name, or none on a site with several projects, fails the build instead
of showing an error box in the browser.
- **`.md` fences get the shell options**: `title` defaults to "Diagram", and
`collapsible`/`defaultOpen` work as in `.mdx`.

### Fixed

- **`--likec4-*` colour overrides work.** Components declared the tokens on
their own elements in scoped styles, so a site's `.likec4-shell { --likec4-… }`
lost on specificity and a value on an ancestor never reached the frame, badge
or placeholder. They now only read the public tokens (through private
`--_likec4-*` properties carrying the fallbacks), so a value set on `:root` or
any ancestor applies everywhere, `.md` fences included.
- **A site running under Bun without React no longer breaks Vite's optimizer.**
The `react/compiler-runtime` check used `createRequire`, which under Bun also
finds packages in Bun's global cache, so the entry was added for a package
Vite could not find. It now looks in `node_modules`, as Vite does.
- `bun run test:site` passes on Windows when `TEMP` is an 8.3 short path: the
plain-Astro dev server 404'd its own `before-hydration.js` because Vite
resolved the site to its long path.

### Changed

- TypeScript throughout: `astro.config.ts` in the playground and the example,
`.ts` relative import specifiers (rewritten to `.js` in the published
declarations), and `examples/astro` passes `likec4Language` to
`expressiveCode()` instead of an `ec.config.mjs`. Code comments trimmed to the
ones that explain a non-obvious reason.

- **Breaking: no implicit "first project".** With more than one project,
`<LikeC4View>` and empty fences need `project`; leaving it out, or naming an
unknown project, now fails the build listing the names, instead of rendering
whichever project was found first (or an error box in the browser).
`projectId` still works as a deprecated alias of `project`. A site that sets
`vitePlugin.workspace` or `languageServices` keeps the old in-browser
resolution, and `projects` is ignored there with a warning.
- New translation key `likec4.error.projectRequired`.
- **A fence without `view="…"` shows the first view its source declares.** It
showed LikeC4's automatic `index` landscape instead, which LikeC4 always lists
first — so a snippet's own `view x of system { … }` never appeared.

## [1.0.0-prerelease.5] - 2026-11-05

### Added
Expand Down
Loading
Loading