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 */}