From 2b3fa57c2ccd7daf3ec6bee5eb20c6aeffcc1ba1 Mon Sep 17 00:00:00 2001 From: Oto Macenauer Date: Fri, 6 Nov 2026 12:00:44 +0100 Subject: [PATCH 1/2] fix: restore list markers in single-page docs Tailwind's Preflight in the knowledge base stylesheet sets `list-style: none` on every list, and the action's doc.css set only margin and padding, so bullets and numbers vanished once a doc was re-hosted. - doc.css now declares disc/circle/square for nested `ul`, decimal for `ol`, and a muted marker colour; task lists stay unmarked. - knowledge-base.css adds the same markers in the `base` layer, scoped to `.kb-single-page`, so bundles published by older action versions render correctly without a republish. Being below `kb-app`, it never overrides a doc's own list styling. - The release-process fixture gains a three-level bullet list, and an embedded test asserts the computed list-style-type of each level and of a task list. Closes #138 Co-Authored-By: Claude Opus 5.5 --- .../publish-single-page-docs/src/selftest.js | 3 ++ .../publish-single-page-docs/src/template.js | 7 ++++ scripts/setup-test-apps.mjs | 15 ++++++- src/styles/knowledge-base.css | 12 ++++++ tests/build-integrity.spec.js | 2 + .../platform-overview/assets/doc.css | 7 ++++ .../release-process/assets/doc.css | 7 ++++ .../release-process/index.html | 13 ++++++ tests/web-fragment.spec.js | 40 +++++++++++++++++++ 9 files changed, 105 insertions(+), 1 deletion(-) diff --git a/actions/publish-single-page-docs/src/selftest.js b/actions/publish-single-page-docs/src/selftest.js index 52b45b7..b43d8b3 100644 --- a/actions/publish-single-page-docs/src/selftest.js +++ b/actions/publish-single-page-docs/src/selftest.js @@ -208,6 +208,9 @@ try { const css = readFileSync(join(stage, 'my-service', 'assets', 'doc.css'), 'utf8'); assert.match(css, /--color-kb-500: #af144b;/); assert.match(css, /\.kb-doc\b/); + // The knowledge base's Preflight strips list markers; the doc must restore them. + assert.match(css, /\.kb-doc ul \{ list-style-type: disc; \}/); + assert.match(css, /\.kb-doc ol \{ list-style-type: decimal; \}/); }); check('packs a tarball with kb-docs.json at the root', () => { diff --git a/actions/publish-single-page-docs/src/template.js b/actions/publish-single-page-docs/src/template.js index 9e74039..971f1a2 100644 --- a/actions/publish-single-page-docs/src/template.js +++ b/actions/publish-single-page-docs/src/template.js @@ -124,7 +124,14 @@ body { margin: 0; font-family: var(--font-sans); color: var(--text-body); } /* ── Flow ─────────────────────────────────────────────────────────────────── */ .kb-doc p { margin: 0 0 1.25rem; } .kb-doc ul, .kb-doc ol { margin: 0 0 1.25rem; padding-left: 1.5rem; } +/* Markers are declared, not inherited from the browser default: inside the + knowledge base Tailwind's Preflight sets list-style: none on every list. */ +.kb-doc ul { list-style-type: disc; } +.kb-doc ul ul { list-style-type: circle; } +.kb-doc ul ul ul { list-style-type: square; } +.kb-doc ol { list-style-type: decimal; } .kb-doc li { margin: 0.375rem 0; } +.kb-doc li::marker { color: var(--text-muted); } .kb-doc li > ul, .kb-doc li > ol { margin: 0.375rem 0; } .kb-doc hr { border: 0; border-top: 1px solid var(--border); margin: 2.5rem 0; } .kb-doc img, .kb-doc video { max-width: 100%; height: auto; border-radius: var(--radius-md); } diff --git a/scripts/setup-test-apps.mjs b/scripts/setup-test-apps.mjs index c210649..d8adab9 100644 --- a/scripts/setup-test-apps.mjs +++ b/scripts/setup-test-apps.mjs @@ -105,7 +105,20 @@ const RELEASE_PROCESS_BODY = `

Release Pro
  • Tag the commit.
  • Publish the GitHub Release.
  • The publish-single-page-docs action attaches kb-docs.tar.gz.
  • -`; + +

    Checklist #

    +`; /** * The mermaid bundle the action vendors — the real one, so the suites can diff --git a/src/styles/knowledge-base.css b/src/styles/knowledge-base.css index ed2cc9d..5cdbf4b 100644 --- a/src/styles/knowledge-base.css +++ b/src/styles/knowledge-base.css @@ -350,6 +350,18 @@ a.kb-masthead-link:hover { color: var(--text-heading); } .kb-single-page { padding: 2rem 1.25rem 3.5rem; } } +/* List markers for bundles published before the action's doc.css declared + them. Preflight sets list-style: none on every list, and doc.css used to set + only margin and padding, so bullets and numbers vanished. In `base` — after + Preflight, above it on specificity, below `kb-app` — so any marker the doc's + own stylesheet declares (a task list's none, a newer doc.css) still wins. */ +@layer base { + .kb-single-page ul { list-style-type: disc; } + .kb-single-page ul ul { list-style-type: circle; } + .kb-single-page ul ul ul { list-style-type: square; } + .kb-single-page ol { list-style-type: decimal; } +} + /* ── Tag pills ───────────────────────────────────────────────────────────── */ .kb-tag { display: inline-flex; diff --git a/tests/build-integrity.spec.js b/tests/build-integrity.spec.js index b226078..9d7aefd 100644 --- a/tests/build-integrity.spec.js +++ b/tests/build-integrity.spec.js @@ -268,6 +268,8 @@ test.describe('single-page onboarding', () => { const html = read('platform-overview/index.html'); expect(html).toContain('data-kb-headless="true"'); expect(kbInlineStylesheet(html)).toContain('.kb-single-page'); + // Fallback markers for bundles whose doc.css predates its own list rules. + expect(kbInlineStylesheet(html)).toMatch(/\.kb-single-page ul\s*\{\s*list-style-type:\s*disc/); expect(html.match(/ ul, .kb-doc li > ol { margin: 0.375rem 0; } .kb-doc hr { border: 0; border-top: 1px solid var(--border); margin: 2.5rem 0; } .kb-doc img, .kb-doc video { max-width: 100%; height: auto; border-radius: var(--radius-md); } diff --git a/tests/fixtures/single-page-bundle/release-process/assets/doc.css b/tests/fixtures/single-page-bundle/release-process/assets/doc.css index 501f347..668cb96 100644 --- a/tests/fixtures/single-page-bundle/release-process/assets/doc.css +++ b/tests/fixtures/single-page-bundle/release-process/assets/doc.css @@ -78,7 +78,14 @@ body { margin: 0; font-family: var(--font-sans); color: var(--text-body); } /* ── Flow ─────────────────────────────────────────────────────────────────── */ .kb-doc p { margin: 0 0 1.25rem; } .kb-doc ul, .kb-doc ol { margin: 0 0 1.25rem; padding-left: 1.5rem; } +/* Markers are declared, not inherited from the browser default: inside the + knowledge base Tailwind's Preflight sets list-style: none on every list. */ +.kb-doc ul { list-style-type: disc; } +.kb-doc ul ul { list-style-type: circle; } +.kb-doc ul ul ul { list-style-type: square; } +.kb-doc ol { list-style-type: decimal; } .kb-doc li { margin: 0.375rem 0; } +.kb-doc li::marker { color: var(--text-muted); } .kb-doc li > ul, .kb-doc li > ol { margin: 0.375rem 0; } .kb-doc hr { border: 0; border-top: 1px solid var(--border); margin: 2.5rem 0; } .kb-doc img, .kb-doc video { max-width: 100%; height: auto; border-radius: var(--radius-md); } diff --git a/tests/fixtures/single-page-bundle/release-process/index.html b/tests/fixtures/single-page-bundle/release-process/index.html index b96ff70..94e180d 100644 --- a/tests/fixtures/single-page-bundle/release-process/index.html +++ b/tests/fixtures/single-page-bundle/release-process/index.html @@ -20,6 +20,19 @@

    Steps #

  • Publish the GitHub Release.
  • The publish-single-page-docs action attaches kb-docs.tar.gz.
  • +

    Checklist #

    +
      +
    • Changelog updated +
        +
      • Breaking changes called out +
          +
        • Migration notes linked
        • +
        +
      • +
      +
    • +
    • Docs bundle attached
    • +
    diff --git a/tests/web-fragment.spec.js b/tests/web-fragment.spec.js index 816758c..b54ec7b 100644 --- a/tests/web-fragment.spec.js +++ b/tests/web-fragment.spec.js @@ -223,6 +223,46 @@ test.describe('Single-page docs', () => { expect(await queryInShadow(page, 'nav#sidebar'), 'single-page doc must not render a sidebar').toBeNull(); expect(await queryInShadow(page, '#kb-masthead'), 'masthead missing').not.toBeNull(); }); + + // Preflight sets list-style: none on every list; doc.css has to put the + // markers back, and a task list has to stay marker-free. + test('lists keep their bullets and numbers; task lists stay unmarked', async ({ page }) => { + const markers = (path, selectors) => gotoFragment(page, path) + .then(() => waitForFragmentText(page, /./)) + .then(() => page.evaluate((sels) => { + function search(root, sel) { + const el = root.querySelector(sel); + if (el) return el; + for (const child of root.querySelectorAll('*')) { + if (child.shadowRoot) { + const found = search(child.shadowRoot, sel); + if (found) return found; + } + } + return null; + } + return Object.fromEntries(sels.map((sel) => { + const el = search(document, sel); + return [sel, el ? getComputedStyle(el).listStyleType : null]; + })); + }, selectors)); + + expect(await markers('/knowledge-base/release-process/', [ + 'main.kb-single-page ol', + 'main.kb-single-page ul', + 'main.kb-single-page ul ul', + 'main.kb-single-page ul ul ul', + ])).toEqual({ + 'main.kb-single-page ol': 'decimal', + 'main.kb-single-page ul': 'disc', + 'main.kb-single-page ul ul': 'circle', + 'main.kb-single-page ul ul ul': 'square', + }); + + expect(await markers('/knowledge-base/platform-overview/', [ + 'main.kb-single-page ul.contains-task-list', + ])).toEqual({ 'main.kb-single-page ul.contains-task-list': 'none' }); + }); }); // ───────────────────────────────────────────────────────────────────────────── From 96e8f9872fd2df15608ce58edd1dfb8f8b842713 Mon Sep 17 00:00:00 2001 From: Oto Macenauer Date: Fri, 6 Nov 2026 13:21:38 +0100 Subject: [PATCH 2/2] test: exercise the list-marker fallback against a legacy doc.css Both fixture docs shipped the current doc.css, whose kb-app marker rules override the knowledge base's base-layer fallback, so the computed-style test passed whether or not the fallback worked. release-process now ships the doc.css as published before it declared markers (task-list rules kept) plus a task list; the fragment test checks decimal/disc/circle/square and the unmarked task list against it in both embeddings, and build-integrity asserts all four fallback rules and that the fixture really is legacy. Co-Authored-By: Claude Opus 5.5 --- scripts/setup-test-apps.mjs | 24 +++++++++++++++++-- tests/build-integrity.spec.js | 11 ++++++++- .../release-process/assets/doc.css | 7 ------ .../release-process/index.html | 5 ++++ tests/web-fragment.spec.js | 16 ++++++++----- 5 files changed, 47 insertions(+), 16 deletions(-) diff --git a/scripts/setup-test-apps.mjs b/scripts/setup-test-apps.mjs index d8adab9..2f94faa 100644 --- a/scripts/setup-test-apps.mjs +++ b/scripts/setup-test-apps.mjs @@ -94,7 +94,7 @@ const PLATFORM_OVERVIEW_BODY = `

    Platfor
    flowchart LR
       client --> gateway --> service
    `; -/** Body of doc 2 — a second doc in the same bundle, proving expansion. */ +/** Body of doc 2 — a second doc in the same bundle, proving expansion; nested lists and a task list for the marker fallback. */ const RELEASE_PROCESS_BODY = `

    Release Process #

    Releases are cut from master on demand.

    @@ -118,8 +118,27 @@ const RELEASE_PROCESS_BODY = `

    Release Pro
  • Docs bundle attached
  • + +

    Sign-off #

    +
      +
    • Release notes reviewed
    • +
    • Announcement sent
    `; +/** + * doc.css as the action published it before it declared list markers: the same + * stylesheet minus that block, task-list rules included. Doc 2 ships it, so its + * computed markers come from the knowledge base's own fallback — the current + * doc.css would win over that fallback and hide whether it works. + */ +const LEGACY_DOC_CSS = DOC_CSS.replace( + /\/\* Markers are declared[\s\S]*?\.kb-doc ol \{ list-style-type: decimal; \}\n/, + '', +).replace(/\.kb-doc li::marker \{[^}]*\}\n/, ''); +if (/list-style-type/.test(LEGACY_DOC_CSS) || !/contains-task-list/.test(LEGACY_DOC_CSS)) { + throw new Error('setup-test-apps: could not derive the legacy doc.css from DOC_CSS — update LEGACY_DOC_CSS'); +} + /** * The mermaid bundle the action vendors — the real one, so the suites can * assert a diagram actually renders inside the fragment rather than only that @@ -168,6 +187,7 @@ const bundleDocs = [ tags: ['process'], body: RELEASE_PROCESS_BODY, usesMermaid: false, + legacyCss: true, }, ]; @@ -186,7 +206,7 @@ function writeSinglePageBundle() { usesMermaid: doc.usesMermaid, hasHeading: true, })); - writeFileSync(join(docDir, CSS_PATH), DOC_CSS); + writeFileSync(join(docDir, CSS_PATH), doc.legacyCss ? LEGACY_DOC_CSS : DOC_CSS); if (doc.depthFixture) writeFileSync(join(docDir, dirname(CSS_PATH), 'depth-check.css'), DEPTH_CHECK_CSS); if (doc.usesMermaid) { copyFileSync(MERMAID_BUNDLE, join(docDir, MERMAID_PATH)); diff --git a/tests/build-integrity.spec.js b/tests/build-integrity.spec.js index 9d7aefd..46a867b 100644 --- a/tests/build-integrity.spec.js +++ b/tests/build-integrity.spec.js @@ -269,7 +269,16 @@ test.describe('single-page onboarding', () => { expect(html).toContain('data-kb-headless="true"'); expect(kbInlineStylesheet(html)).toContain('.kb-single-page'); // Fallback markers for bundles whose doc.css predates its own list rules. - expect(kbInlineStylesheet(html)).toMatch(/\.kb-single-page ul\s*\{\s*list-style-type:\s*disc/); + for (const [sel, type] of [ + ['ul', 'disc'], ['ul ul', 'circle'], ['ul ul ul', 'square'], ['ol', 'decimal'], + ]) { + const rule = new RegExp(`\\.kb-single-page ${sel}\\s*\\{\\s*list-style-type:\\s*${type}`); + expect(kbInlineStylesheet(html), `fallback for ${sel}`).toMatch(rule); + } + // The fixture that exercises that fallback really is a pre-marker doc.css. + const legacy = read('release-process/assets/doc.css'); + expect(legacy).not.toContain('list-style-type'); + expect(legacy).toContain('.contains-task-list'); expect(html.match(/ ul, .kb-doc li > ol { margin: 0.375rem 0; } .kb-doc hr { border: 0; border-top: 1px solid var(--border); margin: 2.5rem 0; } .kb-doc img, .kb-doc video { max-width: 100%; height: auto; border-radius: var(--radius-md); } diff --git a/tests/fixtures/single-page-bundle/release-process/index.html b/tests/fixtures/single-page-bundle/release-process/index.html index 94e180d..44d9269 100644 --- a/tests/fixtures/single-page-bundle/release-process/index.html +++ b/tests/fixtures/single-page-bundle/release-process/index.html @@ -33,6 +33,11 @@

    Checklist Sign-off #

    +
      +
    • Release notes reviewed
    • +
    • Announcement sent
    • +
    diff --git a/tests/web-fragment.spec.js b/tests/web-fragment.spec.js index b54ec7b..f39cf6c 100644 --- a/tests/web-fragment.spec.js +++ b/tests/web-fragment.spec.js @@ -224,8 +224,10 @@ test.describe('Single-page docs', () => { expect(await queryInShadow(page, '#kb-masthead'), 'masthead missing').not.toBeNull(); }); - // Preflight sets list-style: none on every list; doc.css has to put the - // markers back, and a task list has to stay marker-free. + // Preflight sets list-style: none on every list, so the markers have to be + // put back, and a task list has to stay marker-free. release-process ships a + // doc.css from before the action declared markers, so its markers come from + // the knowledge base's fallback alone; platform-overview ships the current one. test('lists keep their bullets and numbers; task lists stay unmarked', async ({ page }) => { const markers = (path, selectors) => gotoFragment(page, path) .then(() => waitForFragmentText(page, /./)) @@ -252,11 +254,13 @@ test.describe('Single-page docs', () => { 'main.kb-single-page ul', 'main.kb-single-page ul ul', 'main.kb-single-page ul ul ul', + 'main.kb-single-page ul.contains-task-list', ])).toEqual({ - 'main.kb-single-page ol': 'decimal', - 'main.kb-single-page ul': 'disc', - 'main.kb-single-page ul ul': 'circle', - 'main.kb-single-page ul ul ul': 'square', + 'main.kb-single-page ol': 'decimal', + 'main.kb-single-page ul': 'disc', + 'main.kb-single-page ul ul': 'circle', + 'main.kb-single-page ul ul ul': 'square', + 'main.kb-single-page ul.contains-task-list': 'none', }); expect(await markers('/knowledge-base/platform-overview/', [