Skip to content

Commit 990f140

Browse files
committed
Color-only active state in the TOC — width can never move again
Two attempts at reserving the bold width (a hidden ghost, then freezing measured rail widths) each traded one artifact for another: the ghost did not stop the fit-content column re-measuring, and the freeze made a long link wrap to two lines when it gained weight. The root cause was letting the active state change a width-affecting property at all: the active TOC link now shifts color only (muted to primary — the clerk thumb already carries the emphasis), the search input is a plain text field (the native search clear button is not emcn chrome), and the ghost/freeze machinery is gone.
1 parent 241be48 commit 990f140

2 files changed

Lines changed: 43 additions & 13 deletions

File tree

‎apps/sim/lib/workspace-files/artifact-stylesheet.ts‎

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -561,18 +561,13 @@ figcaption { font-size: 0.875em; line-height: 1.4285714; color: var(--text-prima
561561
}
562562
.toc-items a:first-of-type { padding-top: 0; }
563563
.toc-items a:last-of-type { padding-bottom: 0; }
564-
/* Hidden ghost at the ACTIVE weight: each link always occupies its bold
565-
width, so the weight flip on highlight cannot jitter the fit-content
566-
column side to side while scrolling. */
567-
.toc-items a::after {
568-
content: attr(data-text);
569-
display: block; height: 0; visibility: hidden; overflow: hidden;
570-
font-weight: 500; pointer-events: none;
571-
}
572564
.toc-items a[data-depth="2"] { padding-left: 20px; }
573565
.toc-items a[data-depth="3"] { padding-left: 32px; }
574566
.toc-items a:hover { color: var(--text-body); }
575-
.toc-items a.is-active { color: var(--text-primary); font-weight: 500; }
567+
/* Color-only active state: a weight flip re-measures the fit-content
568+
column (widen or wrap) — primary-vs-muted is the docs' real signal
569+
anyway, the clerk thumb carries the emphasis. */
570+
.toc-items a.is-active { color: var(--text-primary); }
576571
/* The track: the full outline path in foreground at 10%, and the same path in
577572
full foreground clipped to the active range — fumadocs animates the clip
578573
window, which is the "black segment" that slides as you scroll. */
@@ -711,9 +706,6 @@ export const SIM_ARTIFACT_SHELL = `<script>
711706
a.href = '#' + h.id
712707
a.textContent = h.textContent
713708
a.dataset.depth = h.tagName === 'H2' ? '2' : '3'
714-
// Reserves the active (bolder) width so the fit-content column never
715-
// resizes as the highlight moves — see the ::after ghost in the sheet.
716-
a.dataset.text = h.textContent || ''
717709
tocItems.appendChild(a)
718710
tocLinks.push(a)
719711
}
@@ -727,7 +719,7 @@ export const SIM_ARTIFACT_SHELL = `<script>
727719
title.textContent = document.title
728720
const search = document.createElement('input')
729721
search.className = 'art-search'
730-
search.type = 'search'
722+
search.type = 'text'
731723
search.placeholder = 'Filter sections'
732724
search.setAttribute('aria-label', 'Filter sections on this page')
733725

‎apps/sim/lib/workspace-files/page-compile.ts‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,45 @@ const frontmatterSchema = z.object({
4343
/** Docs-style footer pagination: a markdown link — `[Title](sim:file/<id>)`. */
4444
prev: z.string().optional(),
4545
next: z.string().optional(),
46+
/**
47+
* The SET's sidebar, docs-style: groups of pages under muted labels. Each
48+
* page entry is a markdown link; every page of a set carries the same nav.
49+
*/
50+
nav: z
51+
.array(
52+
z.object({
53+
label: z.string().optional(),
54+
pages: z.array(z.string()).min(1),
55+
})
56+
)
57+
.optional(),
4658
})
4759

60+
/**
61+
* The set navigation as real (hidden) markup, so sim: hrefs resolve through
62+
* the normal link pass and the shell can lift it into the left rail —
63+
* grouped page links with the current page's sections nested beneath it.
64+
*/
65+
function setNavHtml(nav: NonNullable<z.infer<typeof frontmatterSchema>['nav']>): string {
66+
const groups = nav
67+
.map((group) => {
68+
const links = group.pages
69+
.map((entry) => {
70+
const match = entry.match(MD_LINK)
71+
if (!match) return ''
72+
return `<a href="${escapeHtml(match[2])}">${escapeHtml(match[1])}</a>`
73+
})
74+
.filter(Boolean)
75+
.join('')
76+
if (!links) return ''
77+
const label = group.label ? `<h6>${escapeHtml(group.label)}</h6>` : ''
78+
return `<section>${label}${links}</section>`
79+
})
80+
.filter(Boolean)
81+
.join('')
82+
return groups ? `<nav class="set-nav" hidden aria-label="Pages">${groups}</nav>` : ''
83+
}
84+
4885
const MD_LINK = /^\s*\[([^\]]+)\]\(([^)]+)\)\s*$/
4986

5087
const CHEVRON_ATTRS =
@@ -363,6 +400,7 @@ function compileSimPageDocument(source: string, lenient = false): string {
363400
'</head>',
364401
'<body>',
365402
`<div class="page" data-layout="${meta.layout ?? 'docs'}">`,
403+
...(meta.nav ? [setNavHtml(meta.nav)] : []),
366404
`<h1>${escapeHtml(meta.title)}</h1>`,
367405
...(meta.lede ? [`<p class="lede">${escapeHtml(meta.lede)}</p>`] : []),
368406
compileBody(rest, lenient),

0 commit comments

Comments
 (0)