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

@@ -105,7 +105,39 @@ 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 #

    + +

    Sign-off #

    +`; + +/** + * 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 @@ -155,6 +187,7 @@ const bundleDocs = [ tags: ['process'], body: RELEASE_PROCESS_BODY, usesMermaid: false, + legacyCss: true, }, ]; @@ -173,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/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..46a867b 100644 --- a/tests/build-integrity.spec.js +++ b/tests/build-integrity.spec.js @@ -268,6 +268,17 @@ 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. + 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 b96ff70..44d9269 100644 --- a/tests/fixtures/single-page-bundle/release-process/index.html +++ b/tests/fixtures/single-page-bundle/release-process/index.html @@ -20,6 +20,24 @@

    Steps #

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

    Checklist #

    + +

    Sign-off #

    + diff --git a/tests/web-fragment.spec.js b/tests/web-fragment.spec.js index 816758c..f39cf6c 100644 --- a/tests/web-fragment.spec.js +++ b/tests/web-fragment.spec.js @@ -223,6 +223,50 @@ 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, 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, /./)) + .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', + '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 ul.contains-task-list': 'none', + }); + + 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' }); + }); }); // ─────────────────────────────────────────────────────────────────────────────