diff --git a/frontend/src/pages/TechniquePage.tsx b/frontend/src/pages/TechniquePage.tsx index 6eafdc7..fa6f918 100644 --- a/frontend/src/pages/TechniquePage.tsx +++ b/frontend/src/pages/TechniquePage.tsx @@ -172,63 +172,22 @@ export default function TechniquePage() { }; }, [slug, selectedVersion]); - // Scroll to hash fragment after technique loads (key moments, section anchors, etc.) - useEffect(() => { - if (!technique) return; - const hash = window.location.hash.slice(1); - if (hash) { - const el = document.getElementById(hash); - if (el) { - el.scrollIntoView({ behavior: "smooth", block: "start" }); - } - } - }, [technique]); - - if (loading) { - return
Loading technique…
; - } - - if (notFound) { - return ( -
-

Technique Not Found

-

The technique “{slug}” doesn’t exist.

- - Back to Home - -
- ); - } - - if (error || !technique) { - return ( -
- Error: {error ?? "Unknown error"} -
- ); - } - - // Overlay snapshot fields when viewing a historical version - const isHistorical = selectedVersion !== "current" && versionDetail != null; - const overlay = isHistorical - ? snapshotToOverlay(versionDetail.content_snapshot) - : null; - - const displayTitle = overlay?.title ?? technique.title; - const displaySummary = overlay?.summary ?? technique.summary; - const displayCategory = overlay?.topic_category ?? technique.topic_category; - const displayTags = overlay?.topic_tags ?? technique.topic_tags; - const displaySections = overlay?.body_sections ?? technique.body_sections; - const displayFormat = overlay?.body_sections_format ?? technique.body_sections_format ?? "v1"; - const displayChains = overlay?.signal_chains ?? technique.signal_chains; - const displayPlugins = overlay?.plugins ?? technique.plugins; - const displayQuality = overlay?.source_quality ?? technique.source_quality; - // --- Scroll-spy: activeId for ToC and ReadingHeader --- + // IMPORTANT: These hooks must be above early returns to maintain + // consistent hook ordering across renders (React rules of hooks). const [activeId, setActiveId] = useState(""); const [h1Visible, setH1Visible] = useState(true); const h1Ref = useRef(null); + // Overlay snapshot fields when viewing a historical version + const isHistorical = selectedVersion !== "current" && versionDetail != null; + const overlay = (isHistorical && technique) + ? snapshotToOverlay(versionDetail.content_snapshot) + : null; + + const displaySections = technique ? (overlay?.body_sections ?? technique.body_sections) : null; + const displayFormat = technique ? (overlay?.body_sections_format ?? technique.body_sections_format ?? "v1") : "v1"; + // Build flat list of all section/subsection IDs for observation const allSectionIds = useMemo(() => { if (displayFormat !== "v2" || !Array.isArray(displaySections)) return []; @@ -292,8 +251,53 @@ export default function TechniquePage() { return () => observer.disconnect(); }, [allSectionIds]); + // Scroll to hash fragment after technique loads + useEffect(() => { + if (!technique) return; + const hash = window.location.hash.slice(1); + if (hash) { + const el = document.getElementById(hash); + if (el) { + el.scrollIntoView({ behavior: "smooth", block: "start" }); + } + } + }, [technique]); + const currentSectionHeading = sectionHeadingMap.get(activeId) ?? ""; + if (loading) { + return
Loading technique…
; + } + + if (notFound) { + return ( +
+

Technique Not Found

+

The technique “{slug}” doesn’t exist.

+ + Back to Home + +
+ ); + } + + if (error || !technique) { + return ( +
+ Error: {error ?? "Unknown error"} +
+ ); + } + + // Re-derive display fields now that we know technique is non-null + const displayTitle = overlay?.title ?? technique.title; + const displaySummary = overlay?.summary ?? technique.summary; + const displayCategory = overlay?.topic_category ?? technique.topic_category; + const displayTags = overlay?.topic_tags ?? technique.topic_tags; + const displayChains = overlay?.signal_chains ?? technique.signal_chains; + const displayPlugins = overlay?.plugins ?? technique.plugins; + const displayQuality = overlay?.source_quality ?? technique.source_quality; + return (
{/* Reading header — v2 pages only */}