Skip to content

Commit 20d4d9a

Browse files
committed
fix: TOC double-entry + missing-section bugs after markdown-strip changes
Two bugs from the same root cause: hideSectionSlugs strips sections from the rendered HTML body but DocumentToc reads its markdown items from the precompiled siteData.documentIndex[path].toc list, which still contains the stripped headings. Result on the user side: 1. 'Двойная кнопка Mitigation одна пустая' — TOC shows the original markdown 'Mitigations' heading (anchor target removed from body, so click goes nowhere) PLUS the JSX extraItem 'Mitigations (7)'. Two identical-looking entries, one broken. 2. 'Секция Mitigation исчезла' on Techniques where relatedMitigations.length was 0 — the JSX cards section was guarded with > 0 (correct) but the markdown-side strip ran unconditionally, hiding the markdown body's own '## Mitigations' prose. User saw neither cards nor prose. Fixes: DocumentToc — accepts the same hideSectionSlugs prop. Markdown items filtered through the slug set before rendering; the TOC stays in sync with what's actually rendered in the body. TechniqueDetailPage / GroupDetailPage — hiddenSlugs is now computed conditionally: if (relatedMitigations.length > 0) hiddenSlugs.push('mitigations'); if (relatedExamples.length > 0) hiddenSlugs.push('real-world-examples'); (Group: 'observed-techniques' / 'observed-examples' on equivalent counts.) Same hiddenSlugs passed both to InlineMarkdown (body strip) and to DocumentToc (TOC strip) so they can never disagree. Group page also now exposes Observed Techniques + Worked Examples extraItems to TOC (was missing — only Software used was wired). Build clean. Should resolve both 'двойная кнопка' and 'секция исчезла'.
1 parent 920cf4a commit 20d4d9a

3 files changed

Lines changed: 36 additions & 11 deletions

File tree

src/components/detail-pages/GroupDetailPage.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ export default function GroupDetailPage({
5353
.slice()
5454
.sort((a, b) => b.file.localeCompare(a.file));
5555

56+
// Strip markdown sections only when we render the JSX equivalent.
57+
const hiddenSlugs: string[] = [];
58+
if (observedTechniques.length > 0) hiddenSlugs.push("observed-techniques");
59+
if (groupExamples.length > 0) hiddenSlugs.push("observed-examples");
60+
5661
return (
5762
<section className="document-page technique-detail-page">
5863
<Breadcrumb onBack={onClose} items={breadcrumb} />
@@ -67,8 +72,11 @@ export default function GroupDetailPage({
6772
</div>
6873
<DocumentToc
6974
path={`actors/${actor.file}`}
75+
hideSectionSlugs={hiddenSlugs}
7076
extraItems={[
71-
{ label: `Software used (${usesSoftware.length})`, slug: "section-software" },
77+
...(usesSoftware.length > 0 ? [{ label: `Software used (${usesSoftware.length})`, slug: "section-software" }] : []),
78+
...(observedTechniques.length > 0 ? [{ label: `Observed Techniques (${observedTechniques.length})`, slug: "section-techniques" }] : []),
79+
...(groupExamples.length > 0 ? [{ label: `Worked Examples (${groupExamples.length})`, slug: "section-worked-examples" }] : []),
7280
]}
7381
/>
7482
<div className="detail-actions">
@@ -95,7 +103,7 @@ export default function GroupDetailPage({
95103
<InlineMarkdown
96104
path={`actors/${actor.file}`}
97105
onOpenDoc={onOpenDoc}
98-
hideSectionSlugs={["observed-techniques", "observed-examples"]}
106+
hideSectionSlugs={hiddenSlugs}
99107
/>
100108
</section>
101109

src/components/detail-pages/TechniqueDetailPage.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ export default function TechniqueDetailPage({
7171
.map((sid) => allSpecs.find((s) => s.spec_id === sid))
7272
.filter((s): s is SpecRecord => Boolean(s));
7373

74+
// Strip a markdown section ONLY when we're going to render its JSX
75+
// equivalent — otherwise the user sees neither prose nor cards.
76+
const hiddenSlugs: string[] = [];
77+
if (relatedMitigations.length > 0) hiddenSlugs.push("mitigations");
78+
if (relatedExamples.length > 0) hiddenSlugs.push("real-world-examples");
79+
7480
return (
7581
<section className="document-page technique-detail-page">
7682
<Breadcrumb onBack={onClose} items={breadcrumb} />
@@ -100,6 +106,7 @@ export default function TechniqueDetailPage({
100106
</div>
101107
<DocumentToc
102108
path={technique.sourcePath}
109+
hideSectionSlugs={hiddenSlugs}
103110
extraItems={[
104111
...(techniqueSpecs.length > 0 ? [{ label: "Detection spec", slug: "detection-spec" }] : []),
105112
...(relatedMitigations.length > 0 ? [{ label: `Mitigations (${relatedMitigations.length})`, slug: "section-mitigations" }] : []),
@@ -140,12 +147,14 @@ export default function TechniqueDetailPage({
140147
)}
141148
</header>
142149

143-
{/* Full description first — primary content of the page. */}
150+
{/* Full description first — primary content of the page.
151+
Markdown sections we re-render as JSX cards below are hidden
152+
from this body. Sections without a JSX equivalent stay. */}
144153
<section className="technique-detail-section technique-detail-section-description">
145154
<InlineMarkdown
146155
path={technique.sourcePath}
147156
onOpenDoc={onOpenDoc}
148-
hideSectionSlugs={["mitigations", "real-world-examples"]}
157+
hideSectionSlugs={hiddenSlugs}
149158
/>
150159
</section>
151160

src/components/document/DocumentToc.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,26 @@ type TocItem = { label: string; slug: string };
1717
export default function DocumentToc({
1818
path,
1919
extraItems,
20+
hideSectionSlugs,
2021
}: {
2122
path: string;
2223
extraItems?: ReadonlyArray<TocItem>;
24+
// Mirror of the same prop on InlineMarkdown — slugs hidden from the
25+
// rendered HTML body must also disappear from the TOC, otherwise the
26+
// sidebar shows a heading whose anchor target was removed.
27+
hideSectionSlugs?: ReadonlyArray<string>;
2328
}) {
2429
const indexEntry = siteData.documentIndex[path as keyof typeof siteData.documentIndex];
25-
const markdownItems = (indexEntry?.toc ?? []).map((label) => ({
26-
label,
27-
slug: label
28-
.toLowerCase()
29-
.replace(/[^a-z0-9]+/g, "-")
30-
.replace(/^-|-$/g, ""),
31-
}));
30+
const hidden = new Set(hideSectionSlugs ?? []);
31+
const markdownItems = (indexEntry?.toc ?? [])
32+
.map((label) => ({
33+
label,
34+
slug: label
35+
.toLowerCase()
36+
.replace(/[^a-z0-9]+/g, "-")
37+
.replace(/^-|-$/g, ""),
38+
}))
39+
.filter((item) => !hidden.has(item.slug));
3240
const items: TocItem[] = [...markdownItems, ...(extraItems ?? [])];
3341
if (items.length === 0) return null;
3442

0 commit comments

Comments
 (0)