diff --git a/.agents/DECISIONS.md b/.agents/DECISIONS.md index ce515df..e666a61 100644 --- a/.agents/DECISIONS.md +++ b/.agents/DECISIONS.md @@ -19,6 +19,8 @@ The choices that the code does not explain, with the options that were rejected. - **All options go to `codeblocks()`** on Starlight sites, also when the preset is in `ec.config.mjs`; the preset reads them from the registry. - **`codeblocks()` sets `tabWidth: 0`** unless the Starlight config or `ec.config.mjs` sets it, including when `ec.config.mjs` has plugins. Otherwise real tabs never reach visible whitespace or the copied text, with no warning. - **Collapsible sections must come before `pluginCodeblocks()`.** The notation plugin throws otherwise, because our decorations shift the children that the other plugin picks by position. *Rejected:* moving every decoration to `postprocessRenderedBlockGroup` (touches most features for one optional plugin). +- **Astro sites without Starlight use `codeblocks()` from `starlight-codeblocks/astro`**, which returns `[setup, astroExpressiveCode()]`. Astro flattens the array, so Expressive Code stays where the author lists `codeblocks()`, before `mdx()`. It reuses the Starlight path: registry, ec-config override, Markdown plugin, runtimes; page CSS goes through `injectScript('page-ssr')` in place of `customCss`. It fails when the site also lists `expressiveCode()`. Code tabs and inline code highlighting are off by default there, because they change every Markdown page (directive syntax, a page stylesheet), and the API card page styles and script are never added; Starlight sites keep all three, since Starlight already turns on directives. `tabWidth: 0` stays, as on Starlight: it changes only code blocks. `astro-expressive-code` and `@astrojs/starlight` are optional peer dependencies, so each site installs only the one it uses. *Rejected:* adding Expressive Code through `updateConfig` (Astro appends it after `mdx()`, which fails EC's order check); asking the author to add the preset to their own `expressiveCode()` (no registry, so no code tabs, inline code or built-in runtimes). +- **Without Starlight, the integration turns on Sätteri directives for code tabs and gives each unclaimed directive back as its source text**, as Starlight does, in `astro:config:done` so that other integrations claim theirs first. Without it, `16:9` renders as `16`. It slices the source by position. *Rejected:* `mdast-util-to-markdown` as Starlight uses (two dependencies, and the output can differ from the source). - **The preset takes `{ base }`** for sites without Starlight, so site-relative `[!link]` URLs and adapter hrefs get the base. - **The registry is a public contract** for other plugins (starlight-pydocs): it exists after `codeblocks()` runs `config:setup`, and `options.inlineHighlighting` and `options.apiLinks` are `false` or a truthy object. Nothing else is public; `test/starlight.test.ts` guards it. - **Under `unified()`, one remark adapter runs the Sätteri plugin definition**, so each Markdown feature has one implementation. *Rejected:* a remark-native copy of each feature; a warning that the features are off. @@ -29,7 +31,7 @@ The choices that the code does not explain, with the options that were rejected. ## Client code - **One loader in `jsModules`, feature modules emitted as hashed assets next to `ec..js`.** `jsModules` load only on pages with code blocks. *Rejected:* `injectScript('page')` (loads on every page); all feature code inline (every page pays for every feature). -- **Sites without `codeblocks()` get a loader that carries every module's source** and imports from `blob:` URLs. It works with any EC integration, at the cost of a larger `ec..js` there. *Rejected:* an extra Astro integration to install; `data:` URLs (a third larger). +- **Sites without `codeblocks()` get a loader that carries every module's source** and imports from `blob:` URLs. It works with any EC integration, at the cost of a larger `ec..js` there. Sites without Starlight get the full set-up from `starlight-codeblocks/astro`. *Rejected:* making that integration a requirement; `data:` URLs (a third larger). - **Each client module builds on its own, with no shared chunks**, so a file works as a `blob:` module and the 3 kB budget is what a page downloads. The code walkthrough and runtimes are exempt. - **Runtime modules are emitted at a fixed path** (`scb-runtime-.js`, no hash), because code blocks render before the client build, so the URL must be known in advance. - **Anchor positioning with a small script fallback.** *Rejected:* Floating UI (a dependency, over budget). diff --git a/AGENTS.md b/AGENTS.md index a3f51e5..21d3284 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Agent instructions -`starlight-codeblocks` is a Starlight plugin that adds 26 code block features on top of Expressive Code (EC), which renders every code block in Starlight. `packages/starlight-codeblocks/` is the plugin, `docs/` its Starlight docs site (it uses the plugin through `workspace:*`, and defines each feature's syntax and behaviour), and `skills/starlight-codeblocks/` an agent skill for people who use the plugin. +`starlight-codeblocks` is a Starlight plugin that adds 26 code block features on top of Expressive Code (EC), which renders every code block in Starlight. `packages/starlight-codeblocks/` is the plugin, `docs/` its Starlight docs site (it uses the plugin through `workspace:*`, and defines each feature's syntax and behaviour), `examples/astro/` a minimal Astro site without Starlight, and `skills/starlight-codeblocks/` an agent skill for people who use the plugin. - `.agents/DECISIONS.md`: design decisions and the rejected options. Search it before you change existing behaviour. - `.agents/WRITING-STYLE.md`: how the docs read, the feature page template and the glossary. @@ -16,10 +16,10 @@ ``` pnpm install pnpm build Build the package (tsdown) into packages/starlight-codeblocks/dist -pnpm test Build, then Vitest unit tests and the node:test checks in scripts/ +pnpm test Build, then Vitest unit tests, a build of examples/astro and the node:test checks in scripts/ pnpm lint Build, then Biome and type checks (tsc, astro check) pnpm lint:docs Writing-style lint for docs/, the READMEs and skills/ -pnpm docs:build Build the package and the docs site into docs/dist, then render the share cards +pnpm docs:build Build the package, the docs site into docs/dist and examples/astro into docs/dist/examples/astro, then render the share cards pnpm test:e2e docs:build, then Playwright against astro preview of docs/dist pnpm readme:media [slug...] docs:build, then regenerate the README images in .github/assets/readme/ ``` @@ -34,6 +34,7 @@ pnpm readme:media [slug...] docs:build, then regenerate the README images in . Paths are under `packages/starlight-codeblocks/src/` unless they start with `docs/`. - `index.ts`: `codeblocks()`, the Starlight plugin. It validates options (`options.ts`), fills the `globalThis` registry (`registry.ts`) and adds the integration (`integration.ts`). +- `astro.ts`: `codeblocks()` for Astro without Starlight (`starlight-codeblocks/astro`). It does the same and adds `astroExpressiveCode()` itself. `examples/astro/` uses it: `pnpm build && pnpm --filter example-astro build`. - `expressive-code/index.ts`: `createPlugins()`, the preset in order. Core first, notation second; the comments there give the other order constraints. One file per feature, `expressive-code/.ts`. Shared helpers are in `core.ts` and `styles.ts`. - `satteri/`: one mdast plugin for code tabs directive, `{:lang}` inline code, mention link checks and duplicate `id` checks. `remark.ts` runs it on `unified()`; add any context it lacks there, with a case in `test/remark.test.ts`. - `client/.ts`: browser modules, built to `dist/client/scb-..js` and loaded by one loader in `jsModules`. `api-card-page.ts` is the exception: a page script and CSS for API cards on links outside code blocks. diff --git a/README.md b/README.md index d9c56f7..f64533e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ starlight-codeblocks -A Starlight plugin that adds 26 features to code blocks, such as focus, line states, annotations, links to API docs and runnable examples. It builds on Expressive Code, so the code blocks you already have keep working. +A Starlight and Astro plugin that adds 26 features to code blocks, such as focus, line states, annotations, links to API docs and runnable examples. It builds on Expressive Code, so the code blocks you already have keep working. Using an agent? [Point it at the bundled skill](https://ewels.github.io/starlight-codeblocks/agent-skill/). @@ -31,6 +31,8 @@ export default defineConfig({ }); ``` +On an Astro site without Starlight, add `codeblocks()` from `starlight-codeblocks/astro` to `integrations` instead. The [Astro setup](https://ewels.github.io/starlight-codeblocks/install/astro/) page gives the details. + Most features start only when a code block uses their attribute, or a [comment notation](https://ewels.github.io/starlight-codeblocks/comment-notation/) directive such as `# [!code focus]`. A few, such as file icons and colour swatches, apply to every matching block. The [documentation](https://ewels.github.io/starlight-codeblocks/) has a page for each feature, with live examples, and a reference for every option. diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index ad457ae..f678ec6 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -11,6 +11,10 @@ import { sidebar } from './src/sidebar.mjs'; export default defineConfig({ site: 'https://ewels.github.io', base: '/starlight-codeblocks', + redirects: { + '/getting-started': '/starlight-codeblocks/install/starlight/', + '/astro-without-starlight': '/starlight-codeblocks/install/astro/', + }, integrations: [ starlight({ title: 'starlight-codeblocks', @@ -76,7 +80,11 @@ export default defineConfig({ runnable: { timeout: 5000 }, lineStates: { states: { todo: { label: 'To do', colour: { dark: '#c792ea', light: '#7c3aed' } } } }, }), - starlightLinksValidator({ exclude: linksValidatorExclude }), + starlightLinksValidator({ + // The example site builds into docs/dist after the docs, so the validator cannot see it. + exclude: (context) => + linksValidatorExclude(context) || context.link.startsWith('/starlight-codeblocks/examples/'), + }), ], sidebar, }), diff --git a/docs/e2e/carousel.test.ts b/docs/e2e/carousel.test.ts index 1743b7b..642ce11 100644 --- a/docs/e2e/carousel.test.ts +++ b/docs/e2e/carousel.test.ts @@ -164,7 +164,7 @@ test('a button or a dot shows its example, announces it and marks itself, and th }); test('the buttons are a flat list in sidebar order, in even columns, with one dot each', async ({ page }) => { - await page.goto('./getting-started/'); + await page.goto('./install/starlight/'); const sidebarIds = await page .locator('nav[aria-label="Main"] a[href*="/features/"]') .evaluateAll((els) => diff --git a/docs/src/content/docs/configuration.mdx b/docs/src/content/docs/configuration.mdx index 41bc73a..05434d4 100644 --- a/docs/src/content/docs/configuration.mdx +++ b/docs/src/content/docs/configuration.mdx @@ -76,6 +76,6 @@ starlight({ ## Options with an ec.config.mjs file -Some sites keep their Expressive Code options in an `ec.config.mjs` file, next to `astro.config.mjs`. If that file has a `plugins` list, add `pluginCodeblocks()` to it, as in [Sites with an ec.config.mjs file](/starlight-codeblocks/getting-started/#sites-with-an-ecconfigmjs-file). +Some sites keep their Expressive Code options in an `ec.config.mjs` file, next to `astro.config.mjs`. If that file has a `plugins` list, add `pluginCodeblocks()` to it, as in [Sites with an ec.config.mjs file](/starlight-codeblocks/install/starlight/#sites-with-an-ecconfigmjs-file). Give `pluginCodeblocks()` no argument. It reads its options from `codeblocks()`, so all options stay in `astro.config.mjs`. `styleOverrides` can go in either file. diff --git a/docs/src/content/docs/features/code-tabs.mdx b/docs/src/content/docs/features/code-tabs.mdx index 7554937..a3c1d91 100644 --- a/docs/src/content/docs/features/code-tabs.mdx +++ b/docs/src/content/docs/features/code-tabs.mdx @@ -3,10 +3,14 @@ title: Code tabs description: Show several code blocks as one, with editor tabs in the title bar, such as the files of a project or the commands for each package manager. --- -import { LinkCard, TabItem, Tabs } from '@astrojs/starlight/components'; +import { Aside, LinkCard, TabItem, Tabs } from '@astrojs/starlight/components'; import Example from '../../../components/Example.astro'; import Options from '../../../components/Options.astro'; + + Examples often come in several files, or in versions for each package manager or language. Stacked one after the other, they fill the page with code that most readers skip. Code tabs show them as one block, with a tab for each block in the title bar, as in a code editor. export const files = ` @@ -82,7 +86,7 @@ Each code block is a variant. A tab shows the `title` of its variant. A variant ### Package managers -A tab with no `title` shows the `label`, with no file icon. Blocks with the same `sync` key switch together, on this page and on the other pages of the site. Select **pnpm**, and the install command on the [home page](/starlight-codeblocks/) changes too: +A tab with no `title` shows the `label`, with no file icon. Blocks with the same `sync` key switch together, on this page and on the other pages of the site. Select **pnpm**, and every other block with `sync="pm"` changes too: export const packageManagers = ` :::code-tabs{sync="pm"} diff --git a/docs/src/content/docs/features/hidden-lines.mdx b/docs/src/content/docs/features/hidden-lines.mdx index dbf85b2..4ee68bd 100644 --- a/docs/src/content/docs/features/hidden-lines.mdx +++ b/docs/src/content/docs/features/hidden-lines.mdx @@ -102,7 +102,7 @@ With `hiddenLines: false`, the plugin ignores `hidden={range}`, and `[!code hide ## Hidden lines or collapsible sections -Expressive Code has its own plugin for a similar job, [`@expressive-code/plugin-collapsible-sections`](https://expressive-code.com/plugins/collapsible-sections/). Its `collapse={range}` attribute folds lines into a section with a summary line, such as "3 collapsed lines". The plugin is not part of Starlight, so a site must install it and add it to the `plugins` list of its `ec.config.mjs` file. That list replaces the plugins of Starlight, so add `pluginCodeblocks()` to it too, as in [Sites with an ec.config.mjs file](/starlight-codeblocks/getting-started/#sites-with-an-ecconfigmjs-file): +Expressive Code has its own plugin for a similar job, [`@expressive-code/plugin-collapsible-sections`](https://expressive-code.com/plugins/collapsible-sections/). Its `collapse={range}` attribute folds lines into a section with a summary line, such as "3 collapsed lines". The plugin is not part of Starlight, so a site must install it and add it to the `plugins` list of its `ec.config.mjs` file. That list replaces the plugins of Starlight, so add `pluginCodeblocks()` to it too, as in [Sites with an ec.config.mjs file](/starlight-codeblocks/install/starlight/#sites-with-an-ecconfigmjs-file): ```js title="ec.config.mjs" import { pluginCollapsibleSections } from '@expressive-code/plugin-collapsible-sections'; diff --git a/docs/src/content/docs/features/inline-code-highlighting.mdx b/docs/src/content/docs/features/inline-code-highlighting.mdx index 35e5b2a..7c42ef3 100644 --- a/docs/src/content/docs/features/inline-code-highlighting.mdx +++ b/docs/src/content/docs/features/inline-code-highlighting.mdx @@ -7,6 +7,10 @@ import { Aside, LinkCard } from '@astrojs/starlight/components'; import Example from '../../../components/Example.astro'; import Options from '../../../components/Options.astro'; + + Starlight shows inline code in the prose in one colour, with no syntax highlighting. A short expression such as a function call is then harder to read than the same code in a block. A `{:lang}` suffix at the end of the inline code gives it the syntax colours of that language. A site can also set a [default language](#a-default-language) for all inline code.