From eb770e4817fe271ee969b309dc93772971e1369f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=7BAI=7Df=20D=2E=20M=C3=BCller?= Date: Fri, 15 May 2026 09:20:43 +0200 Subject: [PATCH] fix(doc-page): restore deep-link scrolling and show AsciiDoc section anchor icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related fixes for the long-form workflow pages (brownfield, spec-driven-development, brownfield-experiment-report, etc.): 1. Deep links like /brownfield#phase-0-5-socratic-code-theory-recovery landed on the page but did not scroll to the section. The pre-rendered HTML has the section IDs, so the browser jumps there initially — but then the SPA boots, doc-page.js replaces #doc-content with a fresh "Loading..." shell, and the scroll target is destroyed. After the async fragment fetch completes, we now re-scroll to the URL hash. 2. Asciidoctor already emits before each heading (sectanchors:true is set in render-docs.js), but the link was invisible. Add the AsciiDoc-standard § symbol that fades in on heading hover, so readers can grab a link to any section. scroll-margin-top keeps the heading from disappearing behind the sticky header when the user follows such a link. Co-Authored-By: Claude Opus 4.7 (1M context) --- website/src/components/doc-page.js | 12 +++++++ website/src/styles/main.css | 53 ++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/website/src/components/doc-page.js b/website/src/components/doc-page.js index 9f3ff9e..9002caa 100644 --- a/website/src/components/doc-page.js +++ b/website/src/components/doc-page.js @@ -64,6 +64,18 @@ export async function loadDocContent(docPath) { // Attach click-to-load handlers for any YouTube placeholders in the doc. // Keeps us DSGVO-compliant: YouTube is only contacted after user consent. hydrateYouTubeFacades(contentEl) + + // Restore deep-link scrolling: if the URL has a hash (e.g. #phase-0-5), + // the browser tried to scroll there before the SPA replaced #doc-content + // with this newly fetched HTML. Re-scroll to the target now that the + // section exists in the DOM. + if (window.location.hash) { + const id = decodeURIComponent(window.location.hash.slice(1)) + const target = document.getElementById(id) + if (target) { + target.scrollIntoView({ behavior: 'auto', block: 'start' }) + } + } } catch (error) { console.error('Failed to load documentation:', error) contentEl.innerHTML = ` diff --git a/website/src/styles/main.css b/website/src/styles/main.css index 162e056..226b531 100644 --- a/website/src/styles/main.css +++ b/website/src/styles/main.css @@ -520,3 +520,56 @@ body { height: 100%; border: 0; } + +/* AsciiDoc section anchors: visible chain icon on hover, like the default + Asciidoctor stylesheet. Asciidoctor renders + before each heading when sectanchors:true is set in render-docs.js. */ +.asciidoc-content h1, +.asciidoc-content h2, +.asciidoc-content h3, +.asciidoc-content h4, +.asciidoc-content h5, +.asciidoc-content h6 { + position: relative; + scroll-margin-top: 5rem; +} + +.asciidoc-content h1 > a.anchor, +.asciidoc-content h2 > a.anchor, +.asciidoc-content h3 > a.anchor, +.asciidoc-content h4 > a.anchor, +.asciidoc-content h5 > a.anchor, +.asciidoc-content h6 > a.anchor { + position: absolute; + left: -1.25rem; + top: 0; + bottom: 0; + display: flex; + align-items: center; + width: 1.25rem; + opacity: 0; + text-decoration: none; + color: var(--color-text-secondary, #6b7280); + transition: opacity 0.15s ease-in-out; +} + +.asciidoc-content h1:hover > a.anchor, +.asciidoc-content h2:hover > a.anchor, +.asciidoc-content h3:hover > a.anchor, +.asciidoc-content h4:hover > a.anchor, +.asciidoc-content h5:hover > a.anchor, +.asciidoc-content h6:hover > a.anchor, +.asciidoc-content a.anchor:focus { + opacity: 1; +} + +.asciidoc-content a.anchor::before { + content: '\00a7'; /* § — same convention as the default Asciidoctor stylesheet */ + font-size: 0.85em; + font-weight: normal; +} + +.asciidoc-content a.anchor:hover { + color: var(--color-link, #2563eb); + text-decoration: none; +}