From 610b751de2bd3503168cddc28045618ffda6c193 Mon Sep 17 00:00:00 2001 From: Luca Pattocchio <15154851+Kasui92@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:21:51 +0200 Subject: [PATCH 1/4] ci: add scheduled run against latest astro to catch processor drift --- .github/workflows/ci.yml | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d97dc8..48d949b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,9 @@ on: push: branches: [main] pull_request: + schedule: + - cron: '0 6 * * 1' + workflow_dispatch: jobs: typecheck: @@ -26,8 +29,6 @@ jobs: - uses: actions/setup-node@v4 with: node-version: '24' - cache: pnpm - cache-dependency-path: example/pnpm-lock.yaml - working-directory: example run: pnpm install - working-directory: example @@ -39,3 +40,26 @@ jobs: # renaming its default markdown processor out from under us. - name: Smoke test rendered output run: node scripts/smoke-test.mjs + + # `example/` pins astro ^7, so the job above never sees a new major. + # Red here means the next Astro major needs checking, not that main is broken. + astro-latest: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 10 + - uses: actions/setup-node@v4 + with: + node-version: '24' + - working-directory: example + run: pnpm install + - working-directory: example + run: pnpm add astro@latest + - working-directory: example + run: pnpm build + - name: Smoke test rendered output against latest Astro + run: node scripts/smoke-test.mjs From 22ee9dab849b5bac57ae32c3902607313f74b0ab Mon Sep 17 00:00:00 2001 From: Luca Pattocchio <15154851+Kasui92@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:58:31 +0200 Subject: [PATCH 2/4] fix: inject stylesheets at page-ssr so dual-theme code colours reach the build --- index.ts | 26 +++++++++++--------------- scripts/smoke-test.mjs | 24 +++++++++++++++++++++++- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/index.ts b/index.ts index 49b1bdd..8485fad 100644 --- a/index.ts +++ b/index.ts @@ -36,18 +36,8 @@ export interface JaamdOptions { /** * Skip injecting the default CSS variable fallbacks (`jaamd/default`). * - * `` already imports `jaamd/default` (and the main - * stylesheet) statically in its own frontmatter, so this option has no - * effect for consumers using that component — the defaults are always - * present there, extracted by Astro/Vite's normal CSS pipeline. - * - * This flag only affects the fallback copy injected via the client-side - * "page" script, which exists for custom wrappers that render markdown - * without ``. Note that CSS side-effect imports inside - * an injected script are not reliably retained by Rollup in a static - * build (confirmed missing from production output in testing) — prefer - * importing `jaamd/default` directly in your own wrapper rather than - * relying on this option. + * Has no effect when rendering through ``, which imports + * them statically; it applies to custom wrappers. * @default false */ noDefault?: boolean; @@ -165,16 +155,22 @@ export default function jaamd(options: JaamdOptions = {}): AstroIntegration { markdown: markdownUpdate, }); - // "page" stage: bundled by Vite, tree-shaken, no duplicate injection const isDualTheme = typeof theme === "object" && theme.light && theme.dark; + + // Stylesheets go through "page-ssr". CSS imported from the client "page" + // stage is dropped by Rollup in static builds. injectScript( - "page", + "page-ssr", (!noDefault ? `import "jaamd/default"; ` : "") + (isDualTheme ? `import "jaamd/shiki-dual"; ` : "") + `import "jaamd/styles"; -` + +`, + ); + + injectScript( + "page", `import { initMarkdownEnhancements } from "jaamd/client"; ` + `function __jaamdRun() { initMarkdownEnhancements(${JSON.stringify(selector)}); } diff --git a/scripts/smoke-test.mjs b/scripts/smoke-test.mjs index 6a6c6c3..ada1977 100644 --- a/scripts/smoke-test.mjs +++ b/scripts/smoke-test.mjs @@ -13,7 +13,8 @@ import { readFileSync, existsSync } from "node:fs"; import { join } from "node:path"; -const PAGE = join(process.cwd(), "example", "dist", "demo", "index.html"); +const DIST = join(process.cwd(), "example", "dist"); +const PAGE = join(DIST, "demo", "index.html"); if (!existsSync(PAGE)) { console.error(`✗ built page not found: ${PAGE}\n Did \`npm run build\` run in ./example?`); @@ -22,6 +23,13 @@ if (!existsSync(PAGE)) { const html = readFileSync(PAGE, "utf8"); +// Stylesheets the page actually links, concatenated. +const css = [...html.matchAll(/]+rel="stylesheet"[^>]+href="([^"]+)"/g)] + .map((m) => join(DIST, m[1].replace(/^\//, ""))) + .filter(existsSync) + .map((f) => readFileSync(f, "utf8")) + .join("\n"); + /** @type {{ name: string, ok: boolean, hint: string }[]} */ const results = []; @@ -92,6 +100,20 @@ check( "no stylesheet reached the page; the CSS import chain is broken", ); +check( + "markdown styles shipped", + css.includes("--jaamd-"), + "markdown.css did not reach the linked stylesheets", +); + +// Shiki emits only --shiki-light/--shiki-dark on spans in dual mode; without the +// rules that read them, code renders with no colour at all. +check( + "dual-theme code colours shipped", + css.includes("var(--shiki-light)") && css.includes("var(--shiki-dark)"), + "shiki-dual.css did not reach the CSS; inject it at the page-ssr stage, not page", +); + check( "client enhancements bundled", /]+type="module"/.test(html), From a87a01645272d0ae66f8d78cc08ef9f2cd1eaddc Mon Sep 17 00:00:00 2001 From: Luca Pattocchio <15154851+Kasui92@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:05:34 +0200 Subject: [PATCH 3/4] refactor(styles): derive theme tokens from seed colours with color-mix --- src/styles/variables.css | 275 ++++++++++++++------------------------- 1 file changed, 100 insertions(+), 175 deletions(-) diff --git a/src/styles/variables.css b/src/styles/variables.css index db7d920..d26eb0f 100644 --- a/src/styles/variables.css +++ b/src/styles/variables.css @@ -2,51 +2,43 @@ * JAAMD — Default Variables & Full CSS Variable Reference * ============================================================ * - * This file serves a dual purpose: - * * 1. FALLBACK THEME: - * it is automatically imported by the integration so all - * --jaamd-* vars have sensible values even if you never - * set any yourself. You can opt out with `noDefault: true` - * in jaamd({…}). + * imported automatically by the integration so all --jaamd-* + * vars have values even if you never set any. Opt out with + * `noDefault: true` in jaamd({…}). * - * All defaults live inside @layer jaamd.defaults, so any - * :root rule you write outside a layer always wins — no - * matter the load order. + * Defaults live in @layer jaamd.defaults, so any :root rule + * you write outside a layer wins regardless of load order. * * 2. DOCUMENTATION: - * every --jaamd-* property is listed here with a comment - * describing its role. Copy any variable you want to override - * into your own :root block (no layer needed). + * every --jaamd-* property is listed with its role. + * + * Theming: override the SEED tokens below and the rest follows. + * Every other value is derived from them with color-mix(). You can + * still override any individual token by name. * * Importable as: import "jaamd/default"; * @import "jaamd/default.css"; * ============================================================ */ -/* Establish the layer so its priority is always below unlayered CSS. */ @layer jaamd.defaults; @layer jaamd.defaults { :root { /* ── Typography ─────────────────────────────────────────── */ - /** Sans-serif font stack for body text */ --jaamd-font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; - - /** Serif font stack (used when --jaamd-font-sans is overridden to a serif) */ --jaamd-font-serif: Georgia, "Times New Roman", Times, serif; - - /** Monospace font stack for code */ --jaamd-font-mono: "SF Mono", Consolas, "Liberation Mono", Menlo, Courier, monospace; - - /** Base font size for all scaled elements */ --jaamd-font-size: 1.125rem; - - /** Line height for body text, lists and code blocks */ --jaamd-line-height: 1.6; - /* ── Core colors ────────────────────────────────────────── */ + /* ══ SEED TOKENS ═════════════════════════════════════════ */ + /* Override these to rebrand. Everything below derives from them. */ + + /** Page background these surfaces sit on */ + --jaamd-bg: #faf8f5; /** Default body/paragraph text color */ --jaamd-color-fg: #3a3a3a; @@ -54,219 +46,152 @@ /** Headings, strong, table headers, summary — brighter than fg */ --jaamd-color-fg-bright: #1a1a1a; - /** Accent / primary color (links, active tab label, summary arrow) */ + /** Accent color (links, active tab label, summary arrow) */ --jaamd-color-primary: #2d2d2d; - /** Slightly lighter accent (link :hover, blockquote text) */ - --jaamd-color-primary-light: #4a4a4a; - - /** Confirmation colour — the copy button flashes this after a successful copy */ + /** Confirmation colour — the copy button flashes this after a copy */ --jaamd-color-success: #22c55e; + /** Alert accent hues — the only alert values that cannot be derived */ + --jaamd-alert-note-color: #0969da; + --jaamd-alert-tip-color: #1a7f37; + --jaamd-alert-important-color: #8250df; + --jaamd-alert-warning-color: #9a6700; + --jaamd-alert-caution-color: #cf222e; - /* ── Headings ───────────────────────────────────────────── */ - - /** Border-bottom color of h2 */ - --jaamd-heading-border-color: rgb(from var(--jaamd-color-primary) r g b / 0.4); - - - /* ── Inline code ( outside a
) ───────────────── */
-
-  /** Background of inline code */
-  --jaamd-code-bg:     #e8e4df;
 
-  /** Border of inline code */
-  --jaamd-code-border: #d0cbc6;
+  /* ══ DERIVED SCALE ═══════════════════════════════════════ */
+  /* Tints of fg over bg, used by every surface below. */
 
-  /** Text color of inline code */
-  --jaamd-code-fg:     #1a1a1a;
+  --jaamd-surface-1: color-mix(in oklab, var(--jaamd-color-fg)  6%, var(--jaamd-bg));
+  --jaamd-surface-2: color-mix(in oklab, var(--jaamd-color-fg) 10%, var(--jaamd-bg));
+  --jaamd-surface-3: color-mix(in oklab, var(--jaamd-color-fg) 15%, var(--jaamd-bg));
+  --jaamd-surface-4: color-mix(in oklab, var(--jaamd-color-fg) 20%, var(--jaamd-bg));
+  --jaamd-border:        color-mix(in oklab, var(--jaamd-color-fg) 26%, var(--jaamd-bg));
+  --jaamd-border-strong: color-mix(in oklab, var(--jaamd-color-fg) 42%, var(--jaamd-bg));
 
+  /** Slightly lighter accent (link :hover, blockquote text) */
+  --jaamd-color-primary-light: color-mix(in oklab, var(--jaamd-color-primary) 83%, var(--jaamd-bg));
 
-  /* ── Fenced code blocks (
) ─────────────────────────── */
 
-  /** Background of code blocks (overrides Shiki when not using a Shiki theme) */
-  --jaamd-pre-bg:     #eeeae5;
+  /* ── Headings ───────────────────────────────────────────── */
 
-  /** Border of code blocks */
-  --jaamd-pre-border: #c8c4bf;
+  --jaamd-heading-border-color: rgb(from var(--jaamd-color-primary) r g b / 0.4);
 
-  /** Fallback text color inside code blocks */
-  --jaamd-pre-fg:     #2d2d2d;
 
+  /* ── Inline code ( outside a 
) ───────────────── */
 
-  /* ── Copy-to-clipboard button ───────────────────────────── */
+  --jaamd-code-bg:     var(--jaamd-surface-2);
+  --jaamd-code-border: var(--jaamd-surface-4);
+  --jaamd-code-fg:     var(--jaamd-color-fg-bright);
 
-  /** Copy button background (idle) */
-  --jaamd-copy-btn-bg:           #d8d4cf;
 
-  /** Copy button border (idle) */
-  --jaamd-copy-btn-border:       #a8a39e;
+  /* ── Fenced code blocks (
) ─────────────────────────── */
 
-  /** Copy button icon color (idle) */
-  --jaamd-copy-btn-fg:           #2d2d2d;
+  --jaamd-pre-bg:     var(--jaamd-surface-1);
+  --jaamd-pre-border: var(--jaamd-border);
+  --jaamd-pre-fg:     var(--jaamd-color-primary);
 
-  /** Copy button background on hover */
-  --jaamd-copy-btn-hover-bg:     #c8c4bf;
 
-  /** Copy button border on hover */
-  --jaamd-copy-btn-hover-border: #9a958f;
+  /* ── Copy-to-clipboard button ───────────────────────────── */
 
-  /** Copy button icon color on hover */
-  --jaamd-copy-btn-hover-fg:     #1a1a1a;
+  --jaamd-copy-btn-bg:           var(--jaamd-surface-4);
+  --jaamd-copy-btn-border:       var(--jaamd-border-strong);
+  --jaamd-copy-btn-fg:           var(--jaamd-color-primary);
+  --jaamd-copy-btn-hover-bg:     var(--jaamd-border);
+  --jaamd-copy-btn-hover-border: color-mix(in oklab, var(--jaamd-color-fg) 48%, var(--jaamd-bg));
+  --jaamd-copy-btn-hover-fg:     var(--jaamd-color-fg-bright);
 
 
   /* ── Blockquote ─────────────────────────────────────────── */
 
-  /** Blockquote background fill */
-  --jaamd-blockquote-bg:     #eeeae5;
-
-  /** Blockquote left-border accent color */
-  --jaamd-blockquote-border: #5a5a5a;
-
-  /** Blockquote paragraph text color */
-  --jaamd-blockquote-fg:     #4a4a4a;
+  --jaamd-blockquote-bg:     var(--jaamd-surface-1);
+  --jaamd-blockquote-border: color-mix(in oklab, var(--jaamd-color-fg) 81%, var(--jaamd-bg));
+  --jaamd-blockquote-fg:     var(--jaamd-color-primary-light);
 
 
   /* ── Emphasis ───────────────────────────────────────────── */
 
-  /** Color of  / italic text */
-  --jaamd-em-fg: #4a4a4a;
+  --jaamd-em-fg: var(--jaamd-color-primary-light);
 
 
   /* ── Horizontal rule (
) ─────────────────────────────── */ - /** Color of the hr border line */ --jaamd-hr-color: rgb(from var(--jaamd-color-primary) r g b / 0.4); /* ── Tables ─────────────────────────────────────────────── */ - /** Table cell border color (also used for box-shadow outline) */ - --jaamd-table-border: #b8b3ae; - - /** header row background */ - --jaamd-table-header-bg: #eeeae5; - - /** :hover background */ - --jaamd-table-hover-bg: #e0dcd7; - - - /* ── Code tabs (.code-tabs) ───────────────────────────────── */ - - /** Outer border and tab-strip bottom border */ - --jaamd-tabs-border: #c8c4bf; + --jaamd-table-border: color-mix(in oklab, var(--jaamd-color-fg) 34%, var(--jaamd-bg)); + --jaamd-table-header-bg: var(--jaamd-surface-1); + --jaamd-table-hover-bg: var(--jaamd-surface-3); - /** Tab strip header background */ - --jaamd-tabs-header-bg: #e0dcd7; - /** Inactive tab button :hover background */ - --jaamd-tabs-btn-hover-bg: #d0cbc6; + /* ── Code tabs (.code-tabs) ─────────────────────────────── */ - /** Active tab button background (matches the panel below it) */ - --jaamd-tabs-btn-active-bg: #eeeae5; + --jaamd-tabs-border: var(--jaamd-border); + --jaamd-tabs-header-bg: var(--jaamd-surface-3); + --jaamd-tabs-btn-hover-bg: color-mix(in oklab, var(--jaamd-color-fg) 18%, var(--jaamd-bg)); + --jaamd-tabs-btn-active-bg: var(--jaamd-surface-1); - /* ── Details / Summary ───────────────────────────────────── */ + /* ── Details / Summary ──────────────────────────────────── */ - /**
block background */ - --jaamd-details-bg: #eeeae5; + --jaamd-details-bg: var(--jaamd-surface-1); + --jaamd-details-border: var(--jaamd-border); - /**
block border */ - --jaamd-details-border: #c8c4bf; - - /* ── Spoiler ─────────────────────────────────────────────── */ + /* ── Spoiler ────────────────────────────────────────────── */ /** Hidden spoiler text+background color (same value hides text) */ - --jaamd-spoiler-hidden-color: #1a1a1a; - - /** Revealed spoiler background */ - --jaamd-spoiler-revealed-bg: #eeeae5; - - /** Revealed spoiler text color */ - --jaamd-spoiler-revealed-fg: #3a3a3a; + --jaamd-spoiler-hidden-color: var(--jaamd-color-fg-bright); + --jaamd-spoiler-revealed-bg: var(--jaamd-surface-1); + --jaamd-spoiler-revealed-fg: var(--jaamd-color-fg); - /* ── Alert blocks (.markdown-alert) ────────────────────── */ + /* ── Alert blocks (.markdown-alert) ─────────────────────── */ - /* Note */ - --jaamd-alert-note-color: #0969da; - --jaamd-alert-note-bg: #dff0fd; - - /* Tip */ - --jaamd-alert-tip-color: #1a7f37; - --jaamd-alert-tip-bg: #dafbe1; - - /* Important */ - --jaamd-alert-important-color: #8250df; - --jaamd-alert-important-bg: #fbefff; - - /* Warning */ - --jaamd-alert-warning-color: #9a6700; - --jaamd-alert-warning-bg: #fff8c5; - - /* Caution */ - --jaamd-alert-caution-color: #cf222e; - --jaamd-alert-caution-bg: #ffebe9; + --jaamd-alert-note-bg: color-mix(in oklab, var(--jaamd-alert-note-color) 12%, var(--jaamd-bg)); + --jaamd-alert-tip-bg: color-mix(in oklab, var(--jaamd-alert-tip-color) 12%, var(--jaamd-bg)); + --jaamd-alert-important-bg: color-mix(in oklab, var(--jaamd-alert-important-color) 12%, var(--jaamd-bg)); + --jaamd-alert-warning-bg: color-mix(in oklab, var(--jaamd-alert-warning-color) 12%, var(--jaamd-bg)); + --jaamd-alert-caution-bg: color-mix(in oklab, var(--jaamd-alert-caution-color) 12%, var(--jaamd-bg)); } -/* ── Dark mode overrides ─────────────────────────────────── */ +/* ── Dark mode ────────────────────────────────────────────── + Only the seeds change; every derived token follows. */ html.dark { + --jaamd-bg: #0d1117; --jaamd-color-fg: #c9d1d9; --jaamd-color-fg-bright: #e6edf3; --jaamd-color-primary: #c9d1d9; - --jaamd-color-primary-light: #8b949e; - - --jaamd-heading-border-color: rgb(from var(--jaamd-color-primary) r g b / 0.3); - - --jaamd-code-bg: #161b22; - --jaamd-code-border: #30363d; - --jaamd-code-fg: #e6edf3; - - --jaamd-pre-bg: #161b22; - --jaamd-pre-border: #30363d; - --jaamd-pre-fg: #c9d1d9; - --jaamd-copy-btn-bg: #21262d; - --jaamd-copy-btn-border: #30363d; - --jaamd-copy-btn-fg: #c9d1d9; - --jaamd-copy-btn-hover-bg: #30363d; - --jaamd-copy-btn-hover-border: #484f58; - --jaamd-copy-btn-hover-fg: #e6edf3; + --jaamd-surface-1: color-mix(in oklab, var(--jaamd-color-fg) 7%, var(--jaamd-bg)); + --jaamd-surface-2: color-mix(in oklab, var(--jaamd-color-fg) 10%, var(--jaamd-bg)); + --jaamd-surface-3: color-mix(in oklab, var(--jaamd-color-fg) 13%, var(--jaamd-bg)); + --jaamd-surface-4: color-mix(in oklab, var(--jaamd-color-fg) 18%, var(--jaamd-bg)); + --jaamd-border: color-mix(in oklab, var(--jaamd-color-fg) 22%, var(--jaamd-bg)); + --jaamd-border-strong: color-mix(in oklab, var(--jaamd-color-fg) 36%, var(--jaamd-bg)); - --jaamd-blockquote-bg: #161b22; - --jaamd-blockquote-border: #484f58; - --jaamd-blockquote-fg: #8b949e; + --jaamd-table-border: var(--jaamd-border); + --jaamd-blockquote-border: var(--jaamd-border-strong); + --jaamd-copy-btn-hover-border: var(--jaamd-border-strong); - --jaamd-em-fg: #8b949e; - --jaamd-hr-color: rgb(from var(--jaamd-color-primary) r g b / 0.3); - - --jaamd-table-border: #30363d; - --jaamd-table-header-bg: #161b22; - --jaamd-table-hover-bg: #1c2128; - - --jaamd-tabs-border: #30363d; - --jaamd-tabs-header-bg: #1c2128; - --jaamd-tabs-btn-hover-bg: #21262d; - --jaamd-tabs-btn-active-bg: #161b22; - - --jaamd-details-bg: #161b22; - --jaamd-details-border: #30363d; + --jaamd-alert-note-color: #58a6ff; + --jaamd-alert-tip-color: #3fb950; + --jaamd-alert-important-color: #a371f7; + --jaamd-alert-warning-color: #d29922; + --jaamd-alert-caution-color: #f85149; - --jaamd-spoiler-hidden-color: #e6edf3; - --jaamd-spoiler-revealed-bg: #161b22; - --jaamd-spoiler-revealed-fg: #c9d1d9; + --jaamd-color-primary-light: color-mix(in oklab, var(--jaamd-color-primary) 71%, var(--jaamd-bg)); + --jaamd-heading-border-color: rgb(from var(--jaamd-color-primary) r g b / 0.3); + --jaamd-hr-color: rgb(from var(--jaamd-color-primary) r g b / 0.3); - --jaamd-alert-note-color: #58a6ff; - --jaamd-alert-note-bg: rgba(56, 139, 253, 0.1); - --jaamd-alert-tip-color: #3fb950; - --jaamd-alert-tip-bg: rgba(46, 160, 67, 0.1); - --jaamd-alert-important-color: #a371f7; - --jaamd-alert-important-bg: rgba(163, 113, 247, 0.1); - --jaamd-alert-warning-color: #d29922; - --jaamd-alert-warning-bg: rgba(187, 128, 9, 0.1); - --jaamd-alert-caution-color: #f85149; - --jaamd-alert-caution-bg: rgba(248, 81, 73, 0.1); + --jaamd-alert-note-bg: color-mix(in oklab, var(--jaamd-alert-note-color) 10%, var(--jaamd-bg)); + --jaamd-alert-tip-bg: color-mix(in oklab, var(--jaamd-alert-tip-color) 10%, var(--jaamd-bg)); + --jaamd-alert-important-bg: color-mix(in oklab, var(--jaamd-alert-important-color) 10%, var(--jaamd-bg)); + --jaamd-alert-warning-bg: color-mix(in oklab, var(--jaamd-alert-warning-color) 10%, var(--jaamd-bg)); + --jaamd-alert-caution-bg: color-mix(in oklab, var(--jaamd-alert-caution-color) 10%, var(--jaamd-bg)); } } From 2e16cbbdedb5d955086a32cdcca4c23408537c8a Mon Sep 17 00:00:00 2001 From: Luca Pattocchio <15154851+Kasui92@users.noreply.github.com> Date: Mon, 10 Aug 2026 18:16:44 +0200 Subject: [PATCH 4/4] docs: trim source comments to what the code cannot say --- index.ts | 41 ++++++----------------- scripts/smoke-test.mjs | 14 +++----- src/components/MarkdownContent.astro | 20 ++--------- src/components/MarkdownContent.astro.d.ts | 11 ++---- src/plugins/remark-code-tabs.ts | 19 +++-------- src/scripts/enhancements.ts | 12 ------- src/scripts/modules/spoilers.ts | 10 ++---- src/scripts/utils.ts | 13 ++----- src/styles/shiki-dual.css | 15 ++------- src/styles/variables.css | 25 +++----------- src/themes/dracula/dark.css | 9 +---- src/themes/dracula/index.css | 9 +---- src/themes/nord/dark.css | 9 +---- src/themes/nord/index.css | 9 +---- src/themes/one-dark/dark.css | 9 +---- src/themes/one-dark/index.css | 9 +---- 16 files changed, 42 insertions(+), 192 deletions(-) diff --git a/index.ts b/index.ts index 8485fad..c6a7fb2 100644 --- a/index.ts +++ b/index.ts @@ -22,13 +22,8 @@ export interface JaamdOptions { selector?: string; /** - * Shiki syntax-highlighting theme. - * - * - **string** — single theme name (e.g. `"github-light"`). - * - **{ light, dark }** — enables dual-theme mode. Shiki outputs CSS - * variables for both themes and JAAMD injects the switching CSS. - * Use together with a `.dark` class on `` for dark-mode toggling. - * + * Shiki theme. `{ light, dark }` enables dual-theme mode, which switches on a + * `.dark` class on ``. * @default "github-light" */ theme?: string | { light: string; dark: string }; @@ -56,12 +51,7 @@ export interface JaamdOptions { }; } -/** - * jaamd — Just Another Astro Markdown - * - * Registers remark plugins and injects the stylesheet automatically. - * Supports `astro add jaamd`. - */ +/** Registers jaamd's remark plugins and injects its stylesheets. */ export default function jaamd(options: JaamdOptions = {}): AstroIntegration { const { selector = ".jaamd-content", @@ -100,12 +90,8 @@ export default function jaamd(options: JaamdOptions = {}): AstroIntegration { const markdownUpdate: Record = { shikiConfig: mergedShikiConfig }; - // Astro pre-fills its default processor here, so seeing this name means - // the user did not override it and it is safe to replace. - // The name is an implementation detail, not a public API: if Astro - // renames it, jaamd stops registering its plugins and only logs a - // warning. That is why CI asserts on the rendered HTML, and why the - // peer range is capped. Re-verify on every Astro major. + // Not public API. If Astro renames it, jaamd silently skips its plugins; + // the scheduled CI run against latest Astro is what catches that. const ASTRO_DEFAULT_PROCESSOR = "satteri"; const currentProcessor = existingMarkdown.processor; @@ -121,8 +107,7 @@ export default function jaamd(options: JaamdOptions = {}): AstroIntegration { const target = isUnified ? currentProcessor : unified(); const existing: unknown[] = target.options.remarkPlugins ?? []; - // Registering a remark plugin twice re-registers its micromark - // extensions, so skip anything the user already wired up. + // Registering a plugin twice re-registers its micromark extensions. const nameOf = (p: unknown): string => { const fn = Array.isArray(p) ? p[0] : p; return typeof fn === "function" ? fn.name : ""; @@ -132,9 +117,8 @@ export default function jaamd(options: JaamdOptions = {}): AstroIntegration { (p) => !existing.includes(p) && !existingNames.has(nameOf(p)), ); - // A new array rather than an unshift: the processor may be a - // module-scope object shared between configs, and mutating it in - // place would corrupt it and stack duplicates across setup runs. + // New array, not unshift: the processor may be shared between configs, + // and mutating it stacks duplicates across setup runs. target.options.remarkPlugins = [...missing, ...existing]; markdownUpdate.processor = target; @@ -145,12 +129,9 @@ export default function jaamd(options: JaamdOptions = {}): AstroIntegration { updateConfig({ vite: { - ssr: { - // Ensure jaamd source files (including .astro components) are - // processed by Vite transforms (i.e. the Astro compiler) rather - // than being treated as pre-bundled external modules. - noExternal: ["jaamd"], - }, + // Without this, jaamd's .astro sources are treated as pre-bundled + // externals and never reach the Astro compiler. + ssr: { noExternal: ["jaamd"] }, }, markdown: markdownUpdate, }); diff --git a/scripts/smoke-test.mjs b/scripts/smoke-test.mjs index ada1977..b6de5e6 100644 --- a/scripts/smoke-test.mjs +++ b/scripts/smoke-test.mjs @@ -1,13 +1,9 @@ #!/usr/bin/env node /** - * Post-build smoke test for the example site. + * Post-build assertions on ./example. A green `astro build` proves nothing: + * jaamd only warns when it cannot register its plugins. * - * When jaamd cannot recognise Astro's default markdown processor it logs a - * warning and skips its remark plugins, and the build still exits 0. So a green - * build proves nothing about whether alerts and code-tabs rendered. Asserting on - * the produced HTML is what catches Astro renaming that processor. - * - * Usage: node scripts/smoke-test.mjs (after building ./example) + * Usage: node scripts/smoke-test.mjs */ import { readFileSync, existsSync } from "node:fs"; @@ -106,8 +102,8 @@ check( "markdown.css did not reach the linked stylesheets", ); -// Shiki emits only --shiki-light/--shiki-dark on spans in dual mode; without the -// rules that read them, code renders with no colour at all. +// In dual mode Shiki only sets --shiki-light/--shiki-dark; without these rules +// code renders with no colour at all. check( "dual-theme code colours shipped", css.includes("var(--shiki-light)") && css.includes("var(--shiki-dark)"), diff --git a/src/components/MarkdownContent.astro b/src/components/MarkdownContent.astro index 01c84e8..ce34a0c 100644 --- a/src/components/MarkdownContent.astro +++ b/src/components/MarkdownContent.astro @@ -5,27 +5,11 @@ import "../styles/variables.css"; import "../styles/markdown.css"; /** - * MarkdownContent - * - * Wraps markdown-rendered HTML. Client-side enhancements are initialised - * automatically by the jaamd() integration via its injected page script. - * - * The full generic Polymorphic type is declared in MarkdownContent.astro.d.ts - * and is used automatically by TypeScript when importing this component. - * The inline Props here is intentionally simplified to avoid Astro's parser - * misreading TypeScript generics (e.g. ) as HTML tags. + * Kept deliberately non-generic: Astro's parser reads `` as + * an HTML tag. The real generic type lives in MarkdownContent.astro.d.ts. */ type Props = { as?: HTMLTag; - /** - * Extra CSS classes to append to the wrapper element. - * The `jaamd-content` class is always present — it is the selector used - * by the JS enhancements and must not be removed. - * - * @example - * // Renders:
- * - */ class?: string; }; diff --git a/src/components/MarkdownContent.astro.d.ts b/src/components/MarkdownContent.astro.d.ts index 87c7008..f1be535 100644 --- a/src/components/MarkdownContent.astro.d.ts +++ b/src/components/MarkdownContent.astro.d.ts @@ -2,15 +2,8 @@ import type { HTMLTag, Polymorphic } from "astro/types"; export type Props = Polymorphic<{ as: Tag; - /** - * Extra CSS classes to append to the wrapper element. - * The `jaamd-content` class is always present — it is the selector used - * by the JS enhancements and must not be removed. - * - * @example - * // Renders:
- * - */ + /** Appended to the always-present `jaamd-content` class, which the JS + * enhancements select on. */ class?: string; }>; diff --git a/src/plugins/remark-code-tabs.ts b/src/plugins/remark-code-tabs.ts index 79a9896..ceb2d78 100644 --- a/src/plugins/remark-code-tabs.ts +++ b/src/plugins/remark-code-tabs.ts @@ -11,22 +11,11 @@ function escapeHtml(value: string): string { } /** - * Remark plugin: tabbed code blocks. + * Tabbed code blocks: a `:::code-tabs` container wrapping fenced blocks. + * The meta string after the language is the tab label, falling back to the + * language, then "Tab N". * - * Syntax: - * :::code-tabs - * ```bash npm - * npm install - * ``` - * ```bash pnpm - * pnpm install - * ``` - * ::: - * - * The meta string (text after the language) becomes the tab label. - * Falls back to the language identifier, then "Tab N". - * - * Requires remark-directive to be registered before this plugin. + * Requires remark-directive to run before this plugin. */ const remarkCodeTabs: Plugin<[], Root> = () => { return (tree: Root) => { diff --git a/src/scripts/enhancements.ts b/src/scripts/enhancements.ts index 201ebad..2519dfb 100644 --- a/src/scripts/enhancements.ts +++ b/src/scripts/enhancements.ts @@ -1,13 +1,3 @@ -/** - * jaamd client-side enhancements - * - * Plain ES module — bundled by Vite as part of the Astro build. - * No external runtime dependencies. - * - * Each feature lives in its own module; this file is the public entry point - * that wires them all together via `initMarkdownEnhancements`. - */ - import { addHeadingLinks } from "./modules/heading-links.js"; import { addCopyButtons } from "./modules/copy-buttons.js"; import { addImageLightbox } from "./modules/lightbox.js"; @@ -16,8 +6,6 @@ import { initCodeTabs } from "./modules/code-tabs.js"; import { initSpoilers } from "./modules/spoilers.js"; import { initDetails } from "./modules/details.js"; -// ─── Public API ─────────────────────────────────────────────────────────────── - export function initMarkdownEnhancements( selector: string = ".jaamd-content", ): void { diff --git a/src/scripts/modules/spoilers.ts b/src/scripts/modules/spoilers.ts index 15f910e..362e75d 100644 --- a/src/scripts/modules/spoilers.ts +++ b/src/scripts/modules/spoilers.ts @@ -1,13 +1,7 @@ import { qsa } from "../utils.js"; -// ─── Spoilers ───────────────────────────────────────────────────────────────── - -/** - * `.spoiler` is authored as plain markup (e.g. ``) and so - * carries no semantics. Promote it to a button: focusable, operable with - * Enter/Space, and reporting its state. Without this the content is - * unreachable without a mouse. - */ +/** `.spoiler` is authored as plain markup; this gives it button semantics so it + * is reachable without a mouse. */ export function initSpoilers(selector: string): void { qsa(document, `${selector} .spoiler`).forEach((el) => { if (el.dataset.spoilerInit) return; diff --git a/src/scripts/utils.ts b/src/scripts/utils.ts index ab72a15..c6183d6 100644 --- a/src/scripts/utils.ts +++ b/src/scripts/utils.ts @@ -1,12 +1,4 @@ -// ─── Shared DOM utilities ───────────────────────────────────────────────────── - -/** - * Slugify heading text for use as an element id. - * - * Keeps any Unicode letter or number, so CJK, Cyrillic, Arabic and accented - * Latin headings survive instead of collapsing to an empty string under an - * ASCII-only `\w` filter. - */ +/** Unicode-aware on purpose: `\w` would collapse CJK and Cyrillic headings to "". */ export function slugify(text: string): string { return text .toLowerCase() @@ -16,8 +8,7 @@ export function slugify(text: string): string { .replace(/^-+|-+$/g, ""); } -/** Appends `-1`, `-2`, ... until the id is free, so repeated headings never - * share an anchor. */ +/** Appends `-1`, `-2`, … until the id is free. */ export function uniqueElementId(base: string): string { const seed = base || "section"; if (!document.getElementById(seed)) return seed; diff --git a/src/styles/shiki-dual.css b/src/styles/shiki-dual.css index d443843..414b9b1 100644 --- a/src/styles/shiki-dual.css +++ b/src/styles/shiki-dual.css @@ -1,15 +1,6 @@ -/* ============================================================ - * JAAMD — Shiki Dual-Theme Switching - * ============================================================ - * - * Injected automatically when theme is { light, dark }. - * Applies the correct Shiki CSS-variable set based on html.dark. - * - * Importable as: import "jaamd/shiki-dual"; - * @import "jaamd/shiki-dual.css"; - * ============================================================ */ +/* Shiki dual-theme switching. Injected when `theme` is { light, dark }. */ -/* Light mode (default) — use --shiki-light-* variables */ +/* Light mode (default) */ .jaamd-content .astro-code span, .jaamd-content .shiki span { color: var(--shiki-light) !important; @@ -18,7 +9,7 @@ text-decoration: var(--shiki-light-text-decoration) !important; } -/* Dark mode — switch to --shiki-dark-* variants */ +/* Dark mode */ html.dark .jaamd-content .astro-code span, html.dark .jaamd-content .shiki span { color: var(--shiki-dark) !important; diff --git a/src/styles/variables.css b/src/styles/variables.css index d26eb0f..5ac7618 100644 --- a/src/styles/variables.css +++ b/src/styles/variables.css @@ -1,25 +1,10 @@ -/* ============================================================ - * JAAMD — Default Variables & Full CSS Variable Reference - * ============================================================ +/* JAAMD default theme, and the reference for every --jaamd-* variable. * - * 1. FALLBACK THEME: - * imported automatically by the integration so all --jaamd-* - * vars have values even if you never set any. Opt out with - * `noDefault: true` in jaamd({…}). + * To rebrand, override the seed tokens below; the rest derive from them via + * color-mix(). Any individual token can still be overridden by name. * - * Defaults live in @layer jaamd.defaults, so any :root rule - * you write outside a layer wins regardless of load order. - * - * 2. DOCUMENTATION: - * every --jaamd-* property is listed with its role. - * - * Theming: override the SEED tokens below and the rest follows. - * Every other value is derived from them with color-mix(). You can - * still override any individual token by name. - * - * Importable as: import "jaamd/default"; - * @import "jaamd/default.css"; - * ============================================================ */ + * Values live in @layer jaamd.defaults, so an unlayered :root rule always wins. + * Opt out entirely with `noDefault: true`. */ @layer jaamd.defaults; diff --git a/src/themes/dracula/dark.css b/src/themes/dracula/dark.css index 39e4f4c..a4e7633 100644 --- a/src/themes/dracula/dark.css +++ b/src/themes/dracula/dark.css @@ -1,11 +1,4 @@ -/* ============================================================ - * JAAMD Theme — Dracula - * Pairs with Shiki theme: "dracula" or "dracula-soft" - * ============================================================ - * - * Import: @import "jaamd/themes/dracula/dark"; - * import "jaamd/themes/dracula/dark"; - * ============================================================ */ +/* JAAMD theme — Dracula (dark mode only). Pairs with Shiki theme "dracula" or "dracula-soft". */ html.dark { --jaamd-color-fg: #f8f8f2; diff --git a/src/themes/dracula/index.css b/src/themes/dracula/index.css index bbd56e3..f38db66 100644 --- a/src/themes/dracula/index.css +++ b/src/themes/dracula/index.css @@ -1,11 +1,4 @@ -/* ============================================================ - * JAAMD Theme — Dracula - * Pairs with Shiki theme: "dracula" or "dracula-soft" - * ============================================================ - * - * Import: @import "jaamd/themes/dracula"; - * import "jaamd/themes/dracula"; - * ============================================================ */ +/* JAAMD theme — Dracula. Pairs with Shiki theme "dracula" or "dracula-soft". */ :root { --jaamd-font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; diff --git a/src/themes/nord/dark.css b/src/themes/nord/dark.css index 7c95a00..4845b29 100644 --- a/src/themes/nord/dark.css +++ b/src/themes/nord/dark.css @@ -1,11 +1,4 @@ -/* ============================================================ - * JAAMD Theme — Nord - * Pairs with Shiki theme: "nord" - * ============================================================ - * - * Import: @import "jaamd/themes/nord/dark"; - * import "jaamd/themes/nord/dark"; - * ============================================================ */ +/* JAAMD theme — Nord (dark mode only). Pairs with Shiki theme "nord". */ html.dark { --jaamd-color-fg: #d8dee9; diff --git a/src/themes/nord/index.css b/src/themes/nord/index.css index 9f81f6b..155b29f 100644 --- a/src/themes/nord/index.css +++ b/src/themes/nord/index.css @@ -1,11 +1,4 @@ -/* ============================================================ - * JAAMD Theme — Nord - * Pairs with Shiki theme: "nord" - * ============================================================ - * - * Import: @import "jaamd/themes/nord"; - * import "jaamd/themes/nord"; - * ============================================================ */ +/* JAAMD theme — Nord. Pairs with Shiki theme "nord". */ :root { --jaamd-font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; diff --git a/src/themes/one-dark/dark.css b/src/themes/one-dark/dark.css index da51e64..01e22b3 100644 --- a/src/themes/one-dark/dark.css +++ b/src/themes/one-dark/dark.css @@ -1,11 +1,4 @@ -/* ============================================================ - * JAAMD Theme — One Dark - * Pairs with Shiki theme: "one-dark-pro" - * ============================================================ - * - * Import: @import "jaamd/themes/one-dark/dark"; - * import "jaamd/themes/one-dark/dark"; - * ============================================================ */ +/* JAAMD theme — One Dark (dark mode only). Pairs with Shiki theme "one-dark-pro". */ html.dark { --jaamd-color-fg: #abb2bf; diff --git a/src/themes/one-dark/index.css b/src/themes/one-dark/index.css index 7b1d44d..8356c52 100644 --- a/src/themes/one-dark/index.css +++ b/src/themes/one-dark/index.css @@ -1,11 +1,4 @@ -/* ============================================================ - * JAAMD Theme — One Dark - * Pairs with Shiki theme: "one-dark-pro" - * ============================================================ - * - * Import: @import "jaamd/themes/one-dark"; - * import "jaamd/themes/one-dark"; - * ============================================================ */ +/* JAAMD theme — One Dark. Pairs with Shiki theme "one-dark-pro". */ :root { --jaamd-font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;