From 1ec7e2785c3efb2cde5cff7bf05898376a95183e Mon Sep 17 00:00:00 2001 From: Adrian Kahali Date: Thu, 14 May 2026 17:07:53 -0400 Subject: [PATCH 1/4] add tabs --- src/components/Tab.astro | 27 ++++ src/components/Tabs.astro | 212 ++++++++++++++++++++++++++ src/components/callouts/Callout.astro | 4 +- src/styles/global.css | 54 +++++++ 4 files changed, 295 insertions(+), 2 deletions(-) create mode 100644 src/components/Tab.astro create mode 100644 src/components/Tabs.astro diff --git a/src/components/Tab.astro b/src/components/Tab.astro new file mode 100644 index 0000000..afc5752 --- /dev/null +++ b/src/components/Tab.astro @@ -0,0 +1,27 @@ +--- +import type { icons } from "lucide-react"; +import Icon from "./Icon.astro"; + +interface Props { + title: string; + icon?: keyof typeof icons; + class?: string; +} + +const { title, icon, class: className } = Astro.props; +--- + +
+ { + icon && ( + + ) + } + +
diff --git a/src/components/Tabs.astro b/src/components/Tabs.astro new file mode 100644 index 0000000..7db7a87 --- /dev/null +++ b/src/components/Tabs.astro @@ -0,0 +1,212 @@ +--- +interface Props { + defaultIndex?: number; + class?: string; +} + +const { defaultIndex = 0, class: className } = Astro.props; +--- + +
+ +
+ +
+
+ + + + + + diff --git a/src/components/callouts/Callout.astro b/src/components/callouts/Callout.astro index af3ad35..0b2271a 100644 --- a/src/components/callouts/Callout.astro +++ b/src/components/callouts/Callout.astro @@ -98,7 +98,7 @@ const iconClass = `mt-0.5 h-4 w-4 shrink-0 ${c.text}`; class:list={[ "min-w-0", c.text, - "[&_a]:border-current [&_a]:text-current [&_code]:text-current [&_p]:leading-relaxed", + "[&_a]:border-current [&_a]:text-current [&_code]:text-current [&_p]:leading-relaxed [&_p]:text-current [&_strong]:text-current [&_li]:text-current", ]} > { @@ -106,7 +106,7 @@ const iconClass = `mt-0.5 h-4 w-4 shrink-0 ${c.text}`;

{title}

) } -
+
diff --git a/src/styles/global.css b/src/styles/global.css index 82fc166..8689253 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -240,6 +240,60 @@ opacity: 1; } +@layer components { + .prose { + --tw-prose-headings: var(--color-stone-900); + --tw-prose-body: var(--color-stone-600); + --tw-prose-links: var(--color-stone-900); + --tw-prose-bold: var(--color-stone-800); + --tw-prose-code: var(--color-stone-700); + } + .dark .prose { + --tw-prose-invert-headings: var(--color-stone-100); + --tw-prose-invert-body: var(--color-stone-300); + --tw-prose-invert-links: var(--color-stone-100); + --tw-prose-invert-bold: var(--color-stone-200); + --tw-prose-invert-code: var(--color-stone-300); + } + + .prose :is(h1, h2, h3, h4, h5, h6):not(:where([class~='not-prose'], [class~='not-prose'] *)) { + font-weight: 600; + } + + .prose a:not(:where([class~='not-prose'], [class~='not-prose'] *)) { + text-decoration: underline; + text-underline-offset: 2px; + text-decoration-color: var(--color-stone-300); + } + .prose a:hover:not(:where([class~='not-prose'], [class~='not-prose'] *)) { + text-decoration-color: var(--color-stone-500); + } + .dark .prose a:not(:where([class~='not-prose'], [class~='not-prose'] *)) { + text-decoration-color: var(--color-stone-600); + } + .dark .prose a:hover:not(:where([class~='not-prose'], [class~='not-prose'] *)) { + text-decoration-color: var(--color-stone-400); + } + + .prose code:not(:where([class~='not-prose'], [class~='not-prose'] *)) { + border-radius: var(--radius-md); + background-color: var(--color-stone-100); + padding-inline: 0.375rem; + padding-block: 0.125rem; + } + .prose code:not(:where([class~='not-prose'], [class~='not-prose'] *))::before, + .prose code:not(:where([class~='not-prose'], [class~='not-prose'] *))::after { + content: none; + } + .dark .prose code:not(:where([class~='not-prose'], [class~='not-prose'] *)) { + background-color: var(--color-stone-800); + } + + .prose th:not(:where([class~='not-prose'], [class~='not-prose'] *)) { + text-align: left; + } +} + /* Docs assistant — shimmer text for in-flight tool indicators */ @keyframes docs-assistant-shimmer { 0% { From 427f579823fd4c5c92e3d1168756feaae719c7e9 Mon Sep 17 00:00:00 2001 From: Adrian Kahali Date: Fri, 15 May 2026 11:52:04 -0400 Subject: [PATCH 2/4] filter out headings --- src/lib/cleanup-headings.ts | 47 +++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/src/lib/cleanup-headings.ts b/src/lib/cleanup-headings.ts index 1c3e3cb..9a239d1 100644 --- a/src/lib/cleanup-headings.ts +++ b/src/lib/cleanup-headings.ts @@ -1,26 +1,47 @@ import type { MarkdownHeading } from 'astro' -function extractRawHeadingTexts(body: string): string[] { - const headingRegex = /^(#{2,3})\s+(.+)$/gm - const texts: string[] = [] - let match - while ((match = headingRegex.exec(body)) !== null) { - const rawText = match[2].trim() - // strip jsx expressions from raw text - const text = rawText.replace(/\{[^}]*\}/g, '').trim() - texts.push(text) +interface ParsedHeading { + text: string + insideTabs: boolean +} + +function parseHeadings(body: string): ParsedHeading[] { + const out: ParsedHeading[] = [] + const lines = body.split('\n') + let tabsDepth = 0 + + for (const line of lines) { + const opens = line.match(/]*?(?/g) + const closes = line.match(/<\/Tabs>/g) + + const headingMatch = line.match(/^\s*(#{2,3})\s+(.+)$/) + if (headingMatch) { + const rawText = headingMatch[2] + .trim() + .replace(/\{[^}]*\}/g, '') + .trim() + out.push({ text: rawText, insideTabs: tabsDepth > 0 }) + } + + if (opens) tabsDepth += opens.length + if (closes) tabsDepth -= closes.length + if (tabsDepth < 0) tabsDepth = 0 } - return texts + + return out } export const cleanupHeadings = (body: string, headings: MarkdownHeading[]): MarkdownHeading[] => { - const rawTexts = extractRawHeadingTexts(body ?? '') + const parsed = parseHeadings(body ?? '') + return headings .filter((h) => h.depth >= 2 && h.depth <= 3) .map((h, index) => ({ depth: h.depth, slug: h.slug, - // use cleaned text from raw body if available, fallback to processed text - text: rawTexts[index] ?? h.text, + text: parsed[index]?.text ?? h.text, + insideTabs: parsed[index]?.insideTabs ?? false, })) + .filter((h) => !h.insideTabs) + .map(({ depth, slug, text }) => ({ depth, slug, text })) } From 57d99cd6e723c65fefd8fc7f4bcf0b3c7c330a81 Mon Sep 17 00:00:00 2001 From: Adrian Kahali Date: Fri, 15 May 2026 11:58:07 -0400 Subject: [PATCH 3/4] add barrel for tabs --- src/components/{ => tabs}/Tab.astro | 2 +- src/components/{ => tabs}/Tabs.astro | 0 src/components/tabs/index.ts | 2 ++ 3 files changed, 3 insertions(+), 1 deletion(-) rename src/components/{ => tabs}/Tab.astro (93%) rename src/components/{ => tabs}/Tabs.astro (100%) create mode 100644 src/components/tabs/index.ts diff --git a/src/components/Tab.astro b/src/components/tabs/Tab.astro similarity index 93% rename from src/components/Tab.astro rename to src/components/tabs/Tab.astro index afc5752..983cb12 100644 --- a/src/components/Tab.astro +++ b/src/components/tabs/Tab.astro @@ -1,6 +1,6 @@ --- import type { icons } from "lucide-react"; -import Icon from "./Icon.astro"; +import Icon from "../Icon.astro"; interface Props { title: string; diff --git a/src/components/Tabs.astro b/src/components/tabs/Tabs.astro similarity index 100% rename from src/components/Tabs.astro rename to src/components/tabs/Tabs.astro diff --git a/src/components/tabs/index.ts b/src/components/tabs/index.ts new file mode 100644 index 0000000..0d5e110 --- /dev/null +++ b/src/components/tabs/index.ts @@ -0,0 +1,2 @@ +export { default as Tabs } from './Tabs.astro' +export { default as Tab } from './Tab.astro' From 30e1e2c99dd628cfa45e28a44f8d417988fccd32 Mon Sep 17 00:00:00 2001 From: Adrian Kahali Date: Fri, 15 May 2026 13:09:18 -0400 Subject: [PATCH 4/4] address greptile --- src/lib/cleanup-headings.ts | 59 ++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/src/lib/cleanup-headings.ts b/src/lib/cleanup-headings.ts index 9a239d1..036b674 100644 --- a/src/lib/cleanup-headings.ts +++ b/src/lib/cleanup-headings.ts @@ -5,29 +5,52 @@ interface ParsedHeading { insideTabs: boolean } -function parseHeadings(body: string): ParsedHeading[] { - const out: ParsedHeading[] = [] +function stripFences(body: string): string { const lines = body.split('\n') - let tabsDepth = 0 - + const out: string[] = [] + let opener: string | null = null for (const line of lines) { - const opens = line.match(/]*?(?/g) - const closes = line.match(/<\/Tabs>/g) - - const headingMatch = line.match(/^\s*(#{2,3})\s+(.+)$/) - if (headingMatch) { - const rawText = headingMatch[2] - .trim() - .replace(/\{[^}]*\}/g, '') - .trim() - out.push({ text: rawText, insideTabs: tabsDepth > 0 }) + const m = line.match(/^[ \t]{0,3}(`{3,}|~{3,})/) + if (opener === null) { + if (m) { + opener = m[1] + out.push('') + continue + } + out.push(line) + } else { + if (m && m[1][0] === opener[0] && m[1].length >= opener.length) { + opener = null + } + out.push('') } - - if (opens) tabsDepth += opens.length - if (closes) tabsDepth -= closes.length - if (tabsDepth < 0) tabsDepth = 0 } + return out.join('\n') +} +const TOKEN_RE = /|<\/Tabs>|^[ \t]*#{2,3}[ \t]+.+$/gm + +function parseHeadings(body: string): ParsedHeading[] { + const cleaned = stripFences(body) + const out: ParsedHeading[] = [] + let tabsDepth = 0 + for (const match of cleaned.matchAll(TOKEN_RE)) { + const tok = match[0] + if (tok.startsWith('$/.test(tok)) tabsDepth++ + } else { + const hm = tok.match(/^[ \t]*(#{2,3})[ \t]+(.+)$/) + if (hm) { + const rawText = hm[2] + .trim() + .replace(/\{[^}]*\}/g, '') + .trim() + out.push({ text: rawText, insideTabs: tabsDepth > 0 }) + } + } + } return out }