From 6f5e2eceb72f7670cd78bb1343cdafd638aa54d4 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 12 Aug 2026 17:33:46 -0700 Subject: [PATCH 1/8] improvement(docs): make the API reference read as code, and unify the type token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The API-page font override matched every span/div/p inside the page, which outranks the .font-mono class on specificity, so every parameter name, type, and identifier silently rendered in the body sans face. Exclude .font-mono so code tokens stay monospace. Consolidate the three divergent type-slot treatments — plain scalar, union, and schema reference each carried their own chip definition, differing in size, weight, face, and box height — onto one code token that reuses the docs inline-code recipe and the platform's 20px chip height. Demote the row metadata: 'required' and 'header' were filled pills, 'required' on the error token, making a constraint the loudest element on the page and a page of required parameters read as a page of alarms. Both are now uncontained text, leaving the type token as the only box on the row. Pin the two 'application/json' labels to one treatment; the Request Body and Response headers rendered the same string at different weights and faces. --- apps/docs/app/global.css | 237 ++++++++++++++++++++++++--------------- 1 file changed, 146 insertions(+), 91 deletions(-) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index 6373ec36d75..32c5518b7d9 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -912,16 +912,20 @@ video { display: none !important; } -/* Ensure API reference pages use the same font as the rest of the docs */ +/* Ensure API reference pages use the same font as the rest of the docs. + `.font-mono` is excluded: parameter names, types, and paths are code tokens + and must stay monospace. Without the exclusion this rule outranks the + `.font-mono` class rule on specificity (id + element beats one class) and + every identifier on the page silently renders in the body sans face. */ #nd-page:has(.api-page-header), #nd-page:has(.api-page-header) h2, #nd-page:has(.api-page-header) h3, #nd-page:has(.api-page-header) h4, -#nd-page:has(.api-page-header) p, -#nd-page:has(.api-page-header) span, -#nd-page:has(.api-page-header) div, -#nd-page:has(.api-page-header) label, -#nd-page:has(.api-page-header) button { +#nd-page:has(.api-page-header) p:not(.font-mono), +#nd-page:has(.api-page-header) span:not(.font-mono), +#nd-page:has(.api-page-header) div:not(.font-mono), +#nd-page:has(.api-page-header) label:not(.font-mono), +#nd-page:has(.api-page-header) button:not(.font-mono) { font-family: var(--font-geist-sans), ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; } @@ -1162,20 +1166,27 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { position: relative; } +/* Status-code trigger. Sits directly beside the content-type label in the + Response header, so it carries the same face, size, and weight — the chevron + is what marks it as interactive, not extra typographic weight. */ +#nd-page:has(.api-page-header) button.response-section-dropdown-trigger, .response-section-dropdown-trigger { display: flex; align-items: center; gap: 0.25rem; - padding: 0.125rem 0.25rem; - font-size: 0.875rem; - font-weight: 500; - color: var(--color-fd-muted-foreground); + height: 1.25rem; + padding: 0 0.25rem; + font-size: 0.78125rem; + line-height: 1.25rem; + font-weight: 400; + color: var(--text-secondary); background: none; border: none; cursor: pointer; border-radius: 0.375rem; transition: color 0.15s; - font-family: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; + font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; } .response-section-dropdown-trigger:hover { color: var(--color-fd-foreground); @@ -1234,10 +1245,21 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { height: 0.875rem; } +/* Content-type label. The Response header renders this class while the Request + Body header renders a fumadocs `code.text-xs`; left alone the two sit at + different weights (400 vs 500) and faces, so the lower one reads lighter than + the upper one despite being the same string. Both are pinned here. */ +#nd-page:has(.api-page-header) span.response-section-content-type, .response-section-content-type { - font-size: 0.875rem; - color: var(--color-fd-muted-foreground); - font-family: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; + font-size: 0.78125rem; + line-height: 1.25rem; + font-weight: 400; + color: var(--text-secondary); + background: none; + border: none; + padding: 0; + font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; } /* Response schema container — remove border to match Path Parameters style */ @@ -1262,25 +1284,75 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { order: 1; } -/* Type badge — order 2, grey pill */ +/* Type token — order 2. One recipe for every shape the type slot takes: + a scalar (`string`), a union (`string | null`, a flex wrapper of links), and + a schema reference (`Workflow summary`, a button that opens the schema). + It reuses the docs inline-code recipe (--surface-4 + --border-1 + mono) so a + type reads as code wherever it appears, and the explicit 20px box height — + the platform ChipTag height — keeps a union the same height as a scalar + instead of the 26px its nested links would otherwise force. */ #nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose - > span.text-sm.font-mono.text-fd-muted-foreground { + > span.text-sm.font-mono.text-fd-muted-foreground, +#nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose > button, +#nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose > span:has(> button) { order: 2; - background-color: var(--surface-5); - color: var(--text-secondary); - padding: 0.1875rem 0.5rem; + display: inline-flex; + align-items: center; + height: 1.25rem; + gap: 0.375rem; + background-color: var(--surface-4); + border: 1px solid var(--border-1); + color: var(--text-body); + padding: 0 0.3125rem; border-radius: 0.375rem; - font-size: var(--text-xs); - line-height: 1.125rem; - font-weight: 500; - font-family: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; + font-size: 0.78125rem; + line-height: 1; + font-weight: 400; + font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; } -html.dark - #nd-page:has(.api-page-header) + +/* Union members and schema references carry their own underline, which makes + them read heavier than a plain scalar sitting in the identical box. Defer it + to hover so the affordance survives without breaking the row's even weight. */ +/* Nested union members inherit the token's own face and colour from the slot + above them. The reset must not be extended to a slot that *is* the button + (a schema reference), whose parent is the row — inheriting there would pull + the row's 14px sans back in and undo the token styling. */ +#nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose - > span.text-sm.font-mono.text-fd-muted-foreground { - background-color: var(--surface-4); + > span.text-sm.font-mono.text-fd-muted-foreground + :is(a, button) { + text-decoration: none; + color: inherit; + font-size: inherit; + font-family: inherit; +} +#nd-page:has(.api-page-header) + .flex.flex-wrap.items-center.gap-3.not-prose + > button.text-sm.font-mono.text-fd-muted-foreground { + text-decoration: none; +} +#nd-page:has(.api-page-header) + .flex.flex-wrap.items-center.gap-3.not-prose + > span.text-sm.font-mono.text-fd-muted-foreground + :is(a, button):hover, +#nd-page:has(.api-page-header) + .flex.flex-wrap.items-center.gap-3.not-prose + > button.text-sm.font-mono.text-fd-muted-foreground:hover { + text-decoration: underline; + text-underline-offset: 2px; +} + +/* Union separator — recede it so `string | null` reads as one token rather + than two competing ones. */ +#nd-page:has(.api-page-header) + .flex.flex-wrap.items-center.gap-3.not-prose + > span.text-sm.font-mono.text-fd-muted-foreground + > span { + color: var(--text-muted, var(--text-secondary)); + opacity: 0.6; } /* Hide the "*" inside the name span — we'll add "required" as a ::after on the flex row */ @@ -1288,21 +1360,22 @@ html.dark display: none; } -/* Required badge — order 3, red pill */ +/* Required marker — order 3. Constraint metadata, not an error: it carries the + error *text* colour for scannability but no error fill, so a page of eight + required parameters does not read as eight alarms. Uncontained, so the type + token stays the only box on the row. */ #nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose:has(span.text-red-400)::after { content: "required"; order: 3; display: inline-flex; align-items: center; - background-color: var(--badge-error-bg); color: var(--badge-error-text); - padding: 0.1875rem 0.5rem; - border-radius: 0.375rem; - font-size: var(--text-xs); - line-height: 1.125rem; - font-weight: 500; - font-family: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; + font-size: 0.78125rem; + line-height: 1.25rem; + font-weight: 400; + font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; } /* Optional "?" indicator — hide it */ #nd-page:has(.api-page-header) @@ -1341,61 +1414,48 @@ html.dark > .flex.flex-wrap.items-center.gap-3.not-prose > span.text-sm.font-mono.text-fd-muted-foreground::after { content: "string"; - font-size: var(--text-xs); - line-height: 1.125rem; - font-weight: 500; - font-family: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; - background-color: var(--surface-5); - color: var(--text-secondary); - padding: 0.1875rem 0.5rem; + font-size: 0.78125rem; + line-height: 1; + font-weight: 400; + font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; + background-color: var(--surface-4); + border: 1px solid var(--border-1); + color: var(--text-body); + height: 1.25rem; + padding: 0 0.3125rem; border-radius: 0.375rem; display: inline-flex; align-items: center; } -html.dark - #nd-page:has(.api-page-header) - div.my-4 - > .flex.flex-wrap.items-center.gap-3.not-prose - > span.text-sm.font-mono.text-fd-muted-foreground::after { - background-color: var(--surface-4); -} -/* "header" badge via ::before on the auth flex row */ +/* "header" location via ::before on the auth flex row — uncontained metadata, + matching the `required` marker rather than the type token. */ #nd-page:has(.api-page-header) div.my-4 > .flex.flex-wrap.items-center.gap-3.not-prose::before { content: "header"; order: 3; display: inline-flex; align-items: center; - background-color: var(--surface-5); color: var(--text-secondary); - padding: 0.1875rem 0.5rem; - border-radius: 0.375rem; - font-size: var(--text-xs); - line-height: 1.125rem; - font-weight: 500; - font-family: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; -} -html.dark - #nd-page:has(.api-page-header) - div.my-4 - > .flex.flex-wrap.items-center.gap-3.not-prose::before { - background-color: var(--surface-4); + font-size: 0.78125rem; + line-height: 1.25rem; + font-weight: 400; + font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; } -/* "required" badge via ::after on the auth flex row — red pill */ +/* "required" marker via ::after on the auth flex row */ #nd-page:has(.api-page-header) div.my-4 > .flex.flex-wrap.items-center.gap-3.not-prose::after { content: "required"; order: 4; display: inline-flex; align-items: center; - background-color: var(--badge-error-bg); color: var(--badge-error-text); - padding: 0.1875rem 0.5rem; - border-radius: 0.375rem; - font-size: var(--text-xs); - line-height: 1.125rem; - font-weight: 500; - font-family: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; + font-size: 0.78125rem; + line-height: 1.25rem; + font-weight: 400; + font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; } /* Hide "In: header" text below auth property — redundant with the header badge */ #nd-page:has(.api-page-header) div.my-4 .prose-no-margin p:has(> code) { @@ -1425,36 +1485,31 @@ html.dark border-color: var(--surface-active); } -/* Body/Callback section "application/json" label — remove inline code styling */ +/* Body/Callback section "application/json" label — strip the inline-code chrome + and match `.response-section-content-type` exactly. These two render the same + string in the Request Body and Response headers; left divergent, the Response + one reads visibly lighter than the Request Body one on the same page. */ #nd-page:has(.api-page-header) .flex.gap-2.items-center.justify-between p.not-prose code.text-xs, #nd-page:has(.api-page-header) .flex.justify-between.gap-2.items-end p.not-prose code.text-xs { background: none !important; border: none !important; padding: 0 !important; - color: var(--color-fd-muted-foreground) !important; - font-size: 0.875rem !important; - font-family: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif !important; + color: var(--text-secondary) !important; + font-size: 0.78125rem !important; + line-height: 1.25rem !important; + font-weight: 400 !important; + font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace !important; } -/* Object/array type triggers in property rows — order 2 + badge chip styling */ +/* Object/array type triggers only need their order here — the type-token rule + above owns their chrome. They previously carried a second, divergent chip + definition (--surface-5, 11px sans, weight 500), which is why a union or a + schema reference rendered a different size, weight, and face than the plain + scalar sitting next to it. */ #nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose > button, #nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose > span:has(> button) { order: 2; - background-color: var(--surface-5); - color: var(--text-secondary); - padding: 0.1875rem 0.5rem; - border-radius: 0.375rem; - font-size: var(--text-xs); - line-height: 1.125rem; - font-weight: 500; - font-family: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; -} -html.dark #nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose > button, -html.dark - #nd-page:has(.api-page-header) - .flex.flex-wrap.items-center.gap-3.not-prose - > span:has(> button) { - background-color: var(--surface-4); } /* Section headings (Authorization, Path Parameters, etc.) — consistent top spacing */ From 482b0865fa0a7d6cddbf2f4c4288b7130e3dd39e Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 12 Aug 2026 17:38:50 -0700 Subject: [PATCH 2/8] improvement(docs): mono status-code tabs, and match fumadocs' lucide icons to emcn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Status codes in the example panel are numeric literals and render as code everywhere else on the page, including the Response header's own trigger, but fumadocs rendered the strip in the body sans face. Language tabs sit in a separate container and stay sans — those are product names, not code. fumadocs draws a few lucide glyphs on API pages that its client-component overrides do not expose (the heading anchor and the code-block copy button). emcn strokes at 1.55 and lucide at 2, so those icons read heavier than every icon around them; match the weight. --- apps/docs/app/global.css | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index 32c5518b7d9..094aa9cef0c 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -1529,6 +1529,27 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { overflow-wrap: break-word !important; } +/* Status-code tabs in the example panel. These are numeric literals and read as + code everywhere else on the page — including the Response header's own status + trigger — but fumadocs renders the strip in the body sans face. Language tabs + sit in a different container and stay sans: those are product names, not code. */ +#nd-page:has(.api-page-header) .flex.gap-3\.5.overflow-x-auto.not-prose > button { + font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; + font-size: 0.78125rem; + font-weight: 400; +} + +/* fumadocs renders a handful of lucide glyphs on API pages that we cannot reach + through its client-component overrides (the heading anchor and the code-block + copy button). emcn draws at 1.55; lucide ships at 2, so those few icons read + noticeably heavier than every icon around them. Match the weight so the set + looks like one family. */ +#nd-page svg[class*="lucide"], +#nd-docs-layout svg[class*="lucide"] { + stroke-width: 1.55; +} + /* Callout/alert — transparent background, no shadow, hide colored bar, add padding */ div.rounded-xl.border.bg-fd-card.shadow-md:has(> [role="none"]) { background-color: transparent !important; From f26cf0b286aedd7bd4694f0ee6affc514deb9a73 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 12 Aug 2026 17:51:57 -0700 Subject: [PATCH 3/8] fix(docs): align the auth type chip with every other property row, and wrap example code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The auth row collapses its real `` type and renders the chip through ::after, so the span is only a wrapper — but it still matched the type-token rule and kept that rule's border, height, and gap. The border drew a second empty box around the real chip and the gap opened in front of it, because the collapsed text remains an anonymous flex item; together they pushed the chip right by roughly 8px that no other row had. Example-panel code overflowed sideways instead of wrapping: fumadocs sizes the block with `w-max`, so it grew to its longest line inside a 400px scroller and the existing pre-wrap never applied. Cap the width, switch break-all to overflow-wrap anywhere so only unfittable tokens split, and reserve room for the copy button fumadocs floats over the first line. --- apps/docs/app/global.css | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index 094aa9cef0c..a0f93c56f14 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -1399,6 +1399,11 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { > span.font-medium.font-mono.text-fd-primary { order: 1; } +/* On an auth row the real type text is ``, which is collapsed to zero + and replaced by the `::after` chip below. That makes this span a bare + wrapper, so it has to give up the whole type-token box it otherwise matches — + border and height included. Leaving those on wrapped the real chip in a + second, empty bordered box and pushed it right by the outer border and gap. */ #nd-page:has(.api-page-header) div.my-4 > .flex.flex-wrap.items-center.gap-3.not-prose @@ -1407,7 +1412,13 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { font-size: 0; padding: 0 !important; background: none !important; + border: none !important; + height: auto !important; line-height: 0; + /* The collapsed `` text is still an anonymous flex item beside the + `::after` chip, so the token rule's gap would open a hole in front of the + chip that no other property row has. */ + gap: 0 !important; } #nd-page:has(.api-page-header) div.my-4 @@ -1518,15 +1529,26 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { margin-bottom: 0.25rem !important; } -/* Code examples in right column — wrap long lines instead of horizontal scroll */ +/* Code examples in right column — wrap long lines instead of scrolling sideways. + fumadocs sizes the block with `w-max`, so it grows to its longest line inside + a ~400px scroller and `pre-wrap` alone never takes effect; the width cap is + what actually enables wrapping. `anywhere` breaks only a token that cannot + fit on its own line, unlike `break-all`, which would split every line at the + character that happens to land on the edge. + The right padding clears the floating copy button, which fumadocs positions + over the first line. */ #nd-page:has(.api-page-header) pre { + width: 100% !important; + max-width: 100% !important; + padding-right: 2.25rem; white-space: pre-wrap !important; - word-break: break-all !important; + word-break: normal !important; + overflow-wrap: anywhere !important; } #nd-page:has(.api-page-header) pre code { width: 100% !important; - word-break: break-all !important; - overflow-wrap: break-word !important; + word-break: normal !important; + overflow-wrap: anywhere !important; } /* Status-code tabs in the example panel. These are numeric literals and read as From 7d301ff755a0bac0c01560d626846777b8e3dfa5 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 12 Aug 2026 18:04:52 -0700 Subject: [PATCH 4/8] revert(docs): let example code overflow instead of wrapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wrapping restarts every continuation line at column zero, and in a JSON body indentation is what carries nesting depth — so a wrapped response misreports its own structure. A hanging indent keeps the depth but needs the shiki lines forced from flex rows to blocks, which breaks the line rhythm. Removes the pre-wrap rules rather than repointing them: fumadocs sizes the block with w-max, so the previous rule never took effect and overflow was already the behaviour on the page. --- apps/docs/app/global.css | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index a0f93c56f14..e5cd556a33c 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -1529,27 +1529,13 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { margin-bottom: 0.25rem !important; } -/* Code examples in right column — wrap long lines instead of scrolling sideways. - fumadocs sizes the block with `w-max`, so it grows to its longest line inside - a ~400px scroller and `pre-wrap` alone never takes effect; the width cap is - what actually enables wrapping. `anywhere` breaks only a token that cannot - fit on its own line, unlike `break-all`, which would split every line at the - character that happens to land on the edge. - The right padding clears the floating copy button, which fumadocs positions - over the first line. */ -#nd-page:has(.api-page-header) pre { - width: 100% !important; - max-width: 100% !important; - padding-right: 2.25rem; - white-space: pre-wrap !important; - word-break: normal !important; - overflow-wrap: anywhere !important; -} -#nd-page:has(.api-page-header) pre code { - width: 100% !important; - word-break: normal !important; - overflow-wrap: anywhere !important; -} +/* Example-panel code scrolls sideways rather than wrapping, which is fumadocs' + own behaviour and is deliberate here: indentation carries the nesting depth + of a JSON body, and a wrapped continuation line restarts at column zero and + misreports that depth. A hanging indent would preserve it, but the lines are + laid out as flex rows and forcing them to blocks to get one breaks the line + rhythm. Long values are clipped instead, as they are in every other API + reference. */ /* Status-code tabs in the example panel. These are numeric literals and read as code everywhere else on the page — including the Response header's own status From c6a77184473df87b7481e18fdf3013897c67d440 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 12 Aug 2026 18:39:19 -0700 Subject: [PATCH 5/8] fix(docs): tighten array type tokens and keep the union separator legible An `array` slot holds its angle brackets as bare text nodes, which become anonymous flex items, so the slot's gap prised `array<` and `>` away from the type they wrap. Drop the gap and let the union separator carry its own margin; this also makes the auth row's gap override redundant. The separator was dimmed twice, by a muted token and again by opacity, which on the dark chip fill left `string | null` reading as `string null`. --- apps/docs/app/global.css | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index e5cd556a33c..c649f0e75a2 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -1300,7 +1300,10 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { display: inline-flex; align-items: center; height: 1.25rem; - gap: 0.375rem; + /* No gap: an `array` slot holds its brackets as bare text nodes, which + become anonymous flex items, so any gap here would prise `array<` and `>` + away from the type they wrap. The union separator spaces itself instead. */ + gap: 0; background-color: var(--surface-4); border: 1px solid var(--border-1); color: var(--text-body); @@ -1345,14 +1348,16 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { text-underline-offset: 2px; } -/* Union separator — recede it so `string | null` reads as one token rather - than two competing ones. */ +/* Union separator — a step below the members it divides, but still legible: + dimming the muted token further made `string | null` read as `string null` + on the dark chip fill, which loses the "or". Carries its own margin because + the slot has no gap. */ #nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose > span.text-sm.font-mono.text-fd-muted-foreground > span { + margin: 0 0.375rem; color: var(--text-muted, var(--text-secondary)); - opacity: 0.6; } /* Hide the "*" inside the name span — we'll add "required" as a ::after on the flex row */ @@ -1415,10 +1420,6 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { border: none !important; height: auto !important; line-height: 0; - /* The collapsed `` text is still an anonymous flex item beside the - `::after` chip, so the token rule's gap would open a hole in front of the - chip that no other property row has. */ - gap: 0 !important; } #nd-page:has(.api-page-header) div.my-4 From 3eee09bfd058ff94a669b3518cca8a5fcf5de835 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 12 Aug 2026 18:51:38 -0700 Subject: [PATCH 6/8] fix(docs): restore the hidden API key description, and drop dead API-reference CSS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rule hiding the trailing `In: header` line matched `p:has(> code)`, which is a shape, not a target — every scheme description in our specs cites a status code, so the whole explanation of personal vs workspace-scoped keys was display:none on every API reference page. Match the last child instead, and shorten the description to one line now that it renders. The dropdown trigger's hover rule had been left below a new id-qualified base rule that outranked it, so the trigger could no longer change colour on hover. Removes what does not run: the four `::-webkit-scrollbar` rules (specifying a non-auto scrollbar-width makes Chromium ignore them, and Firefox never had them) and an `order: 2` block whose selectors and declaration the type-token rule above it already carried. Names the two values the API reference repeats — the monospace stack, written out eleven times, and the 12.5px code size, written nine — as --font-mono-stack and --text-code. Also drops four !important declarations that already won on specificity, a --text-muted fallback that can never fire, and a lucide selector subsumed by the one beside it. --- apps/docs/app/global.css | 135 ++++++++---------- apps/docs/openapi-v2-billing.json | 2 +- apps/docs/openapi-v2-files-audit.json | 2 +- apps/docs/openapi-v2-knowledge.json | 2 +- apps/docs/openapi-v2-logs.json | 2 +- apps/docs/openapi-v2-resources.json | 2 +- apps/docs/openapi-v2-tables.json | 2 +- apps/docs/openapi-v2-workflows.json | 2 +- .../lib/api/contracts/v2/openapi/shared.ts | 2 +- 9 files changed, 69 insertions(+), 82 deletions(-) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index c649f0e75a2..d293dac82b0 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -42,6 +42,16 @@ body { --text-small: 13px; --text-base: 15px; --text-md: 16px; + + /* Code-token size for the API reference: the type chips and the metadata + beside them. A step between --text-xs and --text-small, named because nine + rules have to agree on it. */ + --text-code: 0.78125rem; + + /* The monospace stack, resolved once. Declared here rather than repeated at + each callsite so a face change is one edit, not eleven. */ + --font-mono-stack: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; } /* Pure white light mode background */ @@ -220,30 +230,17 @@ body { --shadow-overlay: 0 10px 30px rgba(0, 0, 0, 0.3); } -/* Scrollbars — platform thumb tokens, transparent track */ +/* Scrollbars — platform thumb tokens, transparent track. + Standard properties only: specifying a non-auto `scrollbar-width` or + `scrollbar-color` makes Chromium ignore every `::-webkit-scrollbar*` rule on + that element, and Firefox never supported them, so a webkit block here would + be dead everywhere it was meant to apply. Hover shading is not expressible + through the standard properties and is dropped rather than faked. */ * { scrollbar-width: thin; scrollbar-color: var(--scrollbar-thumb-color) transparent; } -*::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -*::-webkit-scrollbar-track { - background: transparent; -} - -*::-webkit-scrollbar-thumb { - background-color: var(--scrollbar-thumb-color); - border-radius: 9999px; -} - -*::-webkit-scrollbar-thumb:hover { - background-color: var(--scrollbar-thumb-hover-color); -} - /* Font family utilities */ .font-sans { font-family: var(--font-geist-sans), ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, @@ -251,8 +248,7 @@ body { } .font-mono { - font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - "Liberation Mono", "Courier New", monospace; + font-family: var(--font-mono-stack); } /* Platform UI font — Season Sans, used by the chip chrome to match the main app */ @@ -672,8 +668,7 @@ aside[data-sidebar], code, pre, pre code { - font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - "Liberation Mono", "Courier New", monospace; + font-family: var(--font-mono-stack); } /* Inline code — neutral colors aligned with sim design system */ @@ -1176,7 +1171,7 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { gap: 0.25rem; height: 1.25rem; padding: 0 0.25rem; - font-size: 0.78125rem; + font-size: var(--text-code); line-height: 1.25rem; font-weight: 400; color: var(--text-secondary); @@ -1185,9 +1180,11 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { cursor: pointer; border-radius: 0.375rem; transition: color 0.15s; - font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - "Liberation Mono", "Courier New", monospace; + font-family: var(--font-mono-stack); } +/* Carries the same id-qualified prefix as the base rule above; without it the + base rule outranks this one and the trigger never changes colour on hover. */ +#nd-page:has(.api-page-header) button.response-section-dropdown-trigger:hover, .response-section-dropdown-trigger:hover { color: var(--color-fd-foreground); } @@ -1251,15 +1248,14 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { the upper one despite being the same string. Both are pinned here. */ #nd-page:has(.api-page-header) span.response-section-content-type, .response-section-content-type { - font-size: 0.78125rem; + font-size: var(--text-code); line-height: 1.25rem; font-weight: 400; color: var(--text-secondary); background: none; border: none; padding: 0; - font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - "Liberation Mono", "Courier New", monospace; + font-family: var(--font-mono-stack); } /* Response schema container — remove border to match Path Parameters style */ @@ -1309,11 +1305,10 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { color: var(--text-body); padding: 0 0.3125rem; border-radius: 0.375rem; - font-size: 0.78125rem; + font-size: var(--text-code); line-height: 1; font-weight: 400; - font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - "Liberation Mono", "Courier New", monospace; + font-family: var(--font-mono-stack); } /* Union members and schema references carry their own underline, which makes @@ -1357,7 +1352,7 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { > span.text-sm.font-mono.text-fd-muted-foreground > span { margin: 0 0.375rem; - color: var(--text-muted, var(--text-secondary)); + color: var(--text-muted); } /* Hide the "*" inside the name span — we'll add "required" as a ::after on the flex row */ @@ -1376,11 +1371,10 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { display: inline-flex; align-items: center; color: var(--badge-error-text); - font-size: 0.78125rem; + font-size: var(--text-code); line-height: 1.25rem; font-weight: 400; - font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - "Liberation Mono", "Courier New", monospace; + font-family: var(--font-mono-stack); } /* Optional "?" indicator — hide it */ #nd-page:has(.api-page-header) @@ -1415,10 +1409,10 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { > span.text-sm.font-mono.text-fd-muted-foreground { order: 2; font-size: 0; - padding: 0 !important; - background: none !important; - border: none !important; - height: auto !important; + padding: 0; + background: none; + border: none; + height: auto; line-height: 0; } #nd-page:has(.api-page-header) @@ -1426,11 +1420,10 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { > .flex.flex-wrap.items-center.gap-3.not-prose > span.text-sm.font-mono.text-fd-muted-foreground::after { content: "string"; - font-size: 0.78125rem; + font-size: var(--text-code); line-height: 1; font-weight: 400; - font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - "Liberation Mono", "Courier New", monospace; + font-family: var(--font-mono-stack); background-color: var(--surface-4); border: 1px solid var(--border-1); color: var(--text-body); @@ -1449,11 +1442,10 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { display: inline-flex; align-items: center; color: var(--text-secondary); - font-size: 0.78125rem; + font-size: var(--text-code); line-height: 1.25rem; font-weight: 400; - font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - "Liberation Mono", "Courier New", monospace; + font-family: var(--font-mono-stack); } /* "required" marker via ::after on the auth flex row */ @@ -1463,14 +1455,18 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { display: inline-flex; align-items: center; color: var(--badge-error-text); - font-size: 0.78125rem; + font-size: var(--text-code); line-height: 1.25rem; font-weight: 400; - font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - "Liberation Mono", "Courier New", monospace; -} -/* Hide "In: header" text below auth property — redundant with the header badge */ -#nd-page:has(.api-page-header) div.my-4 .prose-no-margin p:has(> code) { + font-family: var(--font-mono-stack); +} +/* Hide the trailing "In: header" line below an auth property — redundant with + the header marker on the row itself. Matched by position, not by shape: + fumadocs emits the description first and this line last, and every scheme + description in our specs mentions a status code, so a `p:has(> code)` match + also hides the description — which is the whole explanation of personal vs + workspace-scoped keys. */ +#nd-page:has(.api-page-header) div.my-4 .prose-no-margin > p:last-child:has(> code) { display: none !important; } @@ -1507,21 +1503,10 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { border: none !important; padding: 0 !important; color: var(--text-secondary) !important; - font-size: 0.78125rem !important; + font-size: var(--text-code) !important; line-height: 1.25rem !important; font-weight: 400 !important; - font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - "Liberation Mono", "Courier New", monospace !important; -} - -/* Object/array type triggers only need their order here — the type-token rule - above owns their chrome. They previously carried a second, divergent chip - definition (--surface-5, 11px sans, weight 500), which is why a union or a - schema reference rendered a different size, weight, and face than the plain - scalar sitting next to it. */ -#nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose > button, -#nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose > span:has(> button) { - order: 2; + font-family: var(--font-mono-stack) !important; } /* Section headings (Authorization, Path Parameters, etc.) — consistent top spacing */ @@ -1543,18 +1528,20 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { trigger — but fumadocs renders the strip in the body sans face. Language tabs sit in a different container and stay sans: those are product names, not code. */ #nd-page:has(.api-page-header) .flex.gap-3\.5.overflow-x-auto.not-prose > button { - font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - "Liberation Mono", "Courier New", monospace; - font-size: 0.78125rem; + font-family: var(--font-mono-stack); + font-size: var(--text-code); font-weight: 400; } -/* fumadocs renders a handful of lucide glyphs on API pages that we cannot reach - through its client-component overrides (the heading anchor and the code-block - copy button). emcn draws at 1.55; lucide ships at 2, so those few icons read - noticeably heavier than every icon around them. Match the weight so the set - looks like one family. */ -#nd-page svg[class*="lucide"], +/* Any lucide glyph fumadocs still renders itself — the heading anchor and the + code-block copy button on API pages, and whatever else its chrome draws. + emcn strokes at 1.55 and lucide ships at 2, so those icons read noticeably + heavier than every icon around them. + Deliberately layout-wide rather than API-scoped: the goal is one icon weight + across the docs. The glyphs themselves can be replaced properly by passing + `renderHeading` and `renderCodeBlock` to `createAPIPage` — the app already + owns emcn-based Heading and CodeBlock components for MDX pages — which would + retire this rule. */ #nd-docs-layout svg[class*="lucide"] { stroke-width: 1.55; } diff --git a/apps/docs/openapi-v2-billing.json b/apps/docs/openapi-v2-billing.json index ecf30cabc96..f611771391b 100644 --- a/apps/docs/openapi-v2-billing.json +++ b/apps/docs/openapi-v2-billing.json @@ -248,7 +248,7 @@ "type": "apiKey", "in": "header", "name": "X-API-Key", - "description": "Your Sim API key, personal or workspace-scoped. Generate one from the Sim dashboard under Settings > API Keys. A workspace API key is not accepted everywhere: operations that act on behalf of a specific human — administrative reads, secret access, and irreversible or governance-affecting writes — always reject it, whatever role the key carries. Each such operation says so in its own description, and the rejection surfaces as `403` unless the operation conceals unauthorized resources, in which case it is reported as `404`. Use a personal API key for those." + "description": "Your Sim API key, personal or workspace-scoped. Generate one under Settings > API Keys. Operations that reject workspace keys say so in their own description." } }, "headers": { diff --git a/apps/docs/openapi-v2-files-audit.json b/apps/docs/openapi-v2-files-audit.json index 54432ad9883..7e981126eeb 100644 --- a/apps/docs/openapi-v2-files-audit.json +++ b/apps/docs/openapi-v2-files-audit.json @@ -1952,7 +1952,7 @@ "type": "apiKey", "in": "header", "name": "X-API-Key", - "description": "Your Sim API key, personal or workspace-scoped. Generate one from the Sim dashboard under Settings > API Keys. A workspace API key is not accepted everywhere: operations that act on behalf of a specific human — administrative reads, secret access, and irreversible or governance-affecting writes — always reject it, whatever role the key carries. Each such operation says so in its own description, and the rejection surfaces as `403` unless the operation conceals unauthorized resources, in which case it is reported as `404`. Use a personal API key for those." + "description": "Your Sim API key, personal or workspace-scoped. Generate one under Settings > API Keys. Operations that reject workspace keys say so in their own description." } }, "headers": { diff --git a/apps/docs/openapi-v2-knowledge.json b/apps/docs/openapi-v2-knowledge.json index 754dc3ade5c..01ae9282ec0 100644 --- a/apps/docs/openapi-v2-knowledge.json +++ b/apps/docs/openapi-v2-knowledge.json @@ -1954,7 +1954,7 @@ "type": "apiKey", "in": "header", "name": "X-API-Key", - "description": "Your Sim API key, personal or workspace-scoped. Generate one from the Sim dashboard under Settings > API Keys. A workspace API key is not accepted everywhere: operations that act on behalf of a specific human — administrative reads, secret access, and irreversible or governance-affecting writes — always reject it, whatever role the key carries. Each such operation says so in its own description, and the rejection surfaces as `403` unless the operation conceals unauthorized resources, in which case it is reported as `404`. Use a personal API key for those." + "description": "Your Sim API key, personal or workspace-scoped. Generate one under Settings > API Keys. Operations that reject workspace keys say so in their own description." } }, "headers": { diff --git a/apps/docs/openapi-v2-logs.json b/apps/docs/openapi-v2-logs.json index f25b32e5dbb..9ca4c9cb168 100644 --- a/apps/docs/openapi-v2-logs.json +++ b/apps/docs/openapi-v2-logs.json @@ -363,7 +363,7 @@ "type": "apiKey", "in": "header", "name": "X-API-Key", - "description": "Your Sim API key, personal or workspace-scoped. Generate one from the Sim dashboard under Settings > API Keys. A workspace API key is not accepted everywhere: operations that act on behalf of a specific human — administrative reads, secret access, and irreversible or governance-affecting writes — always reject it, whatever role the key carries. Each such operation says so in its own description, and the rejection surfaces as `403` unless the operation conceals unauthorized resources, in which case it is reported as `404`. Use a personal API key for those." + "description": "Your Sim API key, personal or workspace-scoped. Generate one under Settings > API Keys. Operations that reject workspace keys say so in their own description." } }, "headers": { diff --git a/apps/docs/openapi-v2-resources.json b/apps/docs/openapi-v2-resources.json index a1186dc1054..2f03ec3e899 100644 --- a/apps/docs/openapi-v2-resources.json +++ b/apps/docs/openapi-v2-resources.json @@ -2016,7 +2016,7 @@ "type": "apiKey", "in": "header", "name": "X-API-Key", - "description": "Your Sim API key, personal or workspace-scoped. Generate one from the Sim dashboard under Settings > API Keys. A workspace API key is not accepted everywhere: operations that act on behalf of a specific human — administrative reads, secret access, and irreversible or governance-affecting writes — always reject it, whatever role the key carries. Each such operation says so in its own description, and the rejection surfaces as `403` unless the operation conceals unauthorized resources, in which case it is reported as `404`. Use a personal API key for those." + "description": "Your Sim API key, personal or workspace-scoped. Generate one under Settings > API Keys. Operations that reject workspace keys say so in their own description." } }, "headers": { diff --git a/apps/docs/openapi-v2-tables.json b/apps/docs/openapi-v2-tables.json index 809446dc7b9..64093eeed92 100644 --- a/apps/docs/openapi-v2-tables.json +++ b/apps/docs/openapi-v2-tables.json @@ -3666,7 +3666,7 @@ "type": "apiKey", "in": "header", "name": "X-API-Key", - "description": "Your Sim API key, personal or workspace-scoped. Generate one from the Sim dashboard under Settings > API Keys. A workspace API key is not accepted everywhere: operations that act on behalf of a specific human — administrative reads, secret access, and irreversible or governance-affecting writes — always reject it, whatever role the key carries. Each such operation says so in its own description, and the rejection surfaces as `403` unless the operation conceals unauthorized resources, in which case it is reported as `404`. Use a personal API key for those." + "description": "Your Sim API key, personal or workspace-scoped. Generate one under Settings > API Keys. Operations that reject workspace keys say so in their own description." } }, "headers": { diff --git a/apps/docs/openapi-v2-workflows.json b/apps/docs/openapi-v2-workflows.json index e71582276f2..1d9df284ce8 100644 --- a/apps/docs/openapi-v2-workflows.json +++ b/apps/docs/openapi-v2-workflows.json @@ -2048,7 +2048,7 @@ "type": "apiKey", "in": "header", "name": "X-API-Key", - "description": "Your Sim API key, personal or workspace-scoped. Generate one from the Sim dashboard under Settings > API Keys. A workspace API key is not accepted everywhere: operations that act on behalf of a specific human — administrative reads, secret access, and irreversible or governance-affecting writes — always reject it, whatever role the key carries. Each such operation says so in its own description, and the rejection surfaces as `403` unless the operation conceals unauthorized resources, in which case it is reported as `404`. Use a personal API key for those." + "description": "Your Sim API key, personal or workspace-scoped. Generate one under Settings > API Keys. Operations that reject workspace keys say so in their own description." } }, "headers": { diff --git a/apps/sim/lib/api/contracts/v2/openapi/shared.ts b/apps/sim/lib/api/contracts/v2/openapi/shared.ts index a2de98da422..cb376ef7039 100644 --- a/apps/sim/lib/api/contracts/v2/openapi/shared.ts +++ b/apps/sim/lib/api/contracts/v2/openapi/shared.ts @@ -144,7 +144,7 @@ export const V2_API_KEY_SECURITY_SCHEMES = { in: 'header', name: 'X-API-Key', description: - 'Your Sim API key, personal or workspace-scoped. Generate one from the Sim dashboard under Settings > API Keys. A workspace API key is not accepted everywhere: operations that act on behalf of a specific human — administrative reads, secret access, and irreversible or governance-affecting writes — always reject it, whatever role the key carries. Each such operation says so in its own description, and the rejection surfaces as `403` unless the operation conceals unauthorized resources, in which case it is reported as `404`. Use a personal API key for those.', + 'Your Sim API key, personal or workspace-scoped. Generate one under Settings > API Keys. Operations that reject workspace keys say so in their own description.', }, } as const satisfies Readonly> From 5a53f662188457d13c8cb122d2d389340fde1e5d Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 12 Aug 2026 19:01:00 -0700 Subject: [PATCH 7/8] refactor(docs): define the API-reference chrome once, and cut the commentary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The metadata face — size, leading, weight, mono stack — was written out in seven rules that a comment asked future readers to keep in sync by hand; it is now one rule those seven consume, each adding only its own colour, content, and order. The auth row's chip likewise re-derived all eleven declarations of the type token and now joins that rule, keeping only its label. Comments were running longer than the rules they documented — 88 added comment lines against 73 declarations. Trimmed to the load-bearing facts: cascade traps, browser behaviour, and the bugs a rule prevents. Dropped the block narrating why the wrap rules were reverted, which duplicated its own commit message. Also retires a scrollbar token left unreferenced by the webkit removal, moves the last two fumadocs colours in our own components onto platform tokens, and brings the callout icon to 1.55 so the docs really do have one icon weight. --- apps/docs/app/global.css | 190 ++++++++++++++------------------------- 1 file changed, 67 insertions(+), 123 deletions(-) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index d293dac82b0..77a0d4385b6 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -43,13 +43,10 @@ body { --text-base: 15px; --text-md: 16px; - /* Code-token size for the API reference: the type chips and the metadata - beside them. A step between --text-xs and --text-small, named because nine - rules have to agree on it. */ + /* Code-token size for the API reference — a deliberate sixth step, between + --text-caption and --text-small, because the mono face reads small at 12px. */ --text-code: 0.78125rem; - /* The monospace stack, resolved once. Declared here rather than repeated at - each callsite so a face change is one edit, not eleven. */ --font-mono-stack: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } @@ -144,7 +141,6 @@ body { --selection-dark: #264f78; --highlight-search-active: #f6ad55; --scrollbar-thumb-color: #c0c0c0; - --scrollbar-thumb-hover-color: #a8a8a8; --shadow-subtle: 0 2px 4px 0 rgba(0, 0, 0, 0.08); --shadow-medium: 0 4px 12px rgba(0, 0, 0, 0.1); --shadow-overlay: 0 10px 30px rgba(0, 0, 0, 0.11); @@ -226,16 +222,13 @@ body { --code-line-number: #a8a8a8; --selection-bg: #264f78; --scrollbar-thumb-color: #5a5a5a; - --scrollbar-thumb-hover-color: #6a6a6a; --shadow-overlay: 0 10px 30px rgba(0, 0, 0, 0.3); } -/* Scrollbars — platform thumb tokens, transparent track. - Standard properties only: specifying a non-auto `scrollbar-width` or - `scrollbar-color` makes Chromium ignore every `::-webkit-scrollbar*` rule on - that element, and Firefox never supported them, so a webkit block here would - be dead everywhere it was meant to apply. Hover shading is not expressible - through the standard properties and is dropped rather than faked. */ +/* Scrollbars — platform thumb tokens, transparent track. A non-auto + `scrollbar-width`/`scrollbar-color` makes Chromium ignore every + `::-webkit-scrollbar*` rule on the element, so no webkit block here. Hover + shading is not expressible through the standard properties. */ * { scrollbar-width: thin; scrollbar-color: var(--scrollbar-thumb-color) transparent; @@ -908,10 +901,8 @@ video { } /* Ensure API reference pages use the same font as the rest of the docs. - `.font-mono` is excluded: parameter names, types, and paths are code tokens - and must stay monospace. Without the exclusion this rule outranks the - `.font-mono` class rule on specificity (id + element beats one class) and - every identifier on the page silently renders in the body sans face. */ + `.font-mono` is excluded: this selector (id + element) outranks the + `.font-mono` class rule, so without it every code identifier renders sans. */ #nd-page:has(.api-page-header), #nd-page:has(.api-page-header) h2, #nd-page:has(.api-page-header) h3, @@ -1161,9 +1152,26 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { position: relative; } -/* Status-code trigger. Sits directly beside the content-type label in the - Response header, so it carries the same face, size, and weight — the chevron - is what marks it as interactive, not extra typographic weight. */ +/* API-reference metadata face — the status trigger, the content-type label, the + `required` / `header` markers, and the status-code tabs. Defined once; each + consumer below adds only its own colour, content, and order. The `code.text-xs` + label further down needs `!important` to beat fumadocs and stays separate. */ +#nd-page:has(.api-page-header) button.response-section-dropdown-trigger, +.response-section-dropdown-trigger, +#nd-page:has(.api-page-header) span.response-section-content-type, +.response-section-content-type, +#nd-page:has(.api-page-header) + .flex.flex-wrap.items-center.gap-3.not-prose:has(span.text-red-400)::after, +#nd-page:has(.api-page-header) div.my-4 > .flex.flex-wrap.items-center.gap-3.not-prose::before, +#nd-page:has(.api-page-header) div.my-4 > .flex.flex-wrap.items-center.gap-3.not-prose::after, +#nd-page:has(.api-page-header) .flex.gap-3\.5.overflow-x-auto.not-prose > button { + font-size: var(--text-code); + line-height: 1.25rem; + font-weight: 400; + font-family: var(--font-mono-stack); +} + +/* Status-code trigger — matches the content-type label beside it. */ #nd-page:has(.api-page-header) button.response-section-dropdown-trigger, .response-section-dropdown-trigger { display: flex; @@ -1171,22 +1179,18 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { gap: 0.25rem; height: 1.25rem; padding: 0 0.25rem; - font-size: var(--text-code); - line-height: 1.25rem; - font-weight: 400; color: var(--text-secondary); background: none; border: none; cursor: pointer; border-radius: 0.375rem; transition: color 0.15s; - font-family: var(--font-mono-stack); } /* Carries the same id-qualified prefix as the base rule above; without it the base rule outranks this one and the trigger never changes colour on hover. */ #nd-page:has(.api-page-header) button.response-section-dropdown-trigger:hover, .response-section-dropdown-trigger:hover { - color: var(--color-fd-foreground); + color: var(--text-primary); } .response-section-chevron { @@ -1234,7 +1238,7 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { color: var(--text-primary); } .response-section-dropdown-item-selected { - color: var(--color-fd-foreground); + color: var(--text-primary); } .response-section-check { @@ -1242,20 +1246,15 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { height: 0.875rem; } -/* Content-type label. The Response header renders this class while the Request - Body header renders a fumadocs `code.text-xs`; left alone the two sit at - different weights (400 vs 500) and faces, so the lower one reads lighter than - the upper one despite being the same string. Both are pinned here. */ +/* Content-type label. The Response header renders this class; the Request Body + header renders a fumadocs `code.text-xs`. Keep the two in sync — the same + string at different weights reads as one being lighter than the other. */ #nd-page:has(.api-page-header) span.response-section-content-type, .response-section-content-type { - font-size: var(--text-code); - line-height: 1.25rem; - font-weight: 400; color: var(--text-secondary); background: none; border: none; padding: 0; - font-family: var(--font-mono-stack); } /* Response schema container — remove border to match Path Parameters style */ @@ -1280,18 +1279,20 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { order: 1; } -/* Type token — order 2. One recipe for every shape the type slot takes: - a scalar (`string`), a union (`string | null`, a flex wrapper of links), and - a schema reference (`Workflow summary`, a button that opens the schema). - It reuses the docs inline-code recipe (--surface-4 + --border-1 + mono) so a - type reads as code wherever it appears, and the explicit 20px box height — - the platform ChipTag height — keeps a union the same height as a scalar - instead of the 26px its nested links would otherwise force. */ +/* Type token — order 2. Covers every shape the slot takes: scalar span, union + wrapper, schema-reference button, and the auth row's `::after` label. Reuses + the docs inline-code recipe, so a type reads as code wherever it appears; the + explicit 20px height keeps a union level with a scalar, which its nested + links would otherwise push to 26px. */ #nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose > span.text-sm.font-mono.text-fd-muted-foreground, #nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose > button, -#nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose > span:has(> button) { +#nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose > span:has(> button), +#nd-page:has(.api-page-header) + div.my-4 + > .flex.flex-wrap.items-center.gap-3.not-prose + > span.text-sm.font-mono.text-fd-muted-foreground::after { order: 2; display: inline-flex; align-items: center; @@ -1311,13 +1312,9 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { font-family: var(--font-mono-stack); } -/* Union members and schema references carry their own underline, which makes - them read heavier than a plain scalar sitting in the identical box. Defer it - to hover so the affordance survives without breaking the row's even weight. */ -/* Nested union members inherit the token's own face and colour from the slot - above them. The reset must not be extended to a slot that *is* the button - (a schema reference), whose parent is the row — inheriting there would pull - the row's 14px sans back in and undo the token styling. */ +/* Underline deferred to hover so links don't read heavier than a plain scalar + in the same box. The button that *is* the slot needs its own rule: it cannot + `inherit`, which would pull the row's 14px sans back in. */ #nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose > span.text-sm.font-mono.text-fd-muted-foreground @@ -1343,10 +1340,8 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { text-underline-offset: 2px; } -/* Union separator — a step below the members it divides, but still legible: - dimming the muted token further made `string | null` read as `string null` - on the dark chip fill, which loses the "or". Carries its own margin because - the slot has no gap. */ +/* Union separator — dimmed one step, no further: `string | null` started + reading as `string null` on the chip fill. Own margin; the slot has no gap. */ #nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose > span.text-sm.font-mono.text-fd-muted-foreground @@ -1360,10 +1355,8 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { display: none; } -/* Required marker — order 3. Constraint metadata, not an error: it carries the - error *text* colour for scannability but no error fill, so a page of eight - required parameters does not read as eight alarms. Uncontained, so the type - token stays the only box on the row. */ +/* Required marker — order 3. Error text colour but no fill: eight required + params on one page should not read as eight alarms. */ #nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose:has(span.text-red-400)::after { content: "required"; @@ -1371,10 +1364,6 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { display: inline-flex; align-items: center; color: var(--badge-error-text); - font-size: var(--text-code); - line-height: 1.25rem; - font-weight: 400; - font-family: var(--font-mono-stack); } /* Optional "?" indicator — hide it */ #nd-page:has(.api-page-header) @@ -1398,11 +1387,9 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { > span.font-medium.font-mono.text-fd-primary { order: 1; } -/* On an auth row the real type text is ``, which is collapsed to zero - and replaced by the `::after` chip below. That makes this span a bare - wrapper, so it has to give up the whole type-token box it otherwise matches — - border and height included. Leaving those on wrapped the real chip in a - second, empty bordered box and pushed it right by the outer border and gap. */ +/* Auth rows collapse the real `` text to zero and draw the chip in the + `::after` below, so this span is a bare wrapper: it must drop the type-token + box it matches, or the chip renders inside a second, empty bordered box. */ #nd-page:has(.api-page-header) div.my-4 > .flex.flex-wrap.items-center.gap-3.not-prose @@ -1415,23 +1402,13 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { height: auto; line-height: 0; } +/* Only the label — the box comes from the shared type-token rule above, which + this pseudo-element is a member of. */ #nd-page:has(.api-page-header) div.my-4 > .flex.flex-wrap.items-center.gap-3.not-prose > span.text-sm.font-mono.text-fd-muted-foreground::after { content: "string"; - font-size: var(--text-code); - line-height: 1; - font-weight: 400; - font-family: var(--font-mono-stack); - background-color: var(--surface-4); - border: 1px solid var(--border-1); - color: var(--text-body); - height: 1.25rem; - padding: 0 0.3125rem; - border-radius: 0.375rem; - display: inline-flex; - align-items: center; } /* "header" location via ::before on the auth flex row — uncontained metadata, @@ -1442,10 +1419,6 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { display: inline-flex; align-items: center; color: var(--text-secondary); - font-size: var(--text-code); - line-height: 1.25rem; - font-weight: 400; - font-family: var(--font-mono-stack); } /* "required" marker via ::after on the auth flex row */ @@ -1455,17 +1428,10 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { display: inline-flex; align-items: center; color: var(--badge-error-text); - font-size: var(--text-code); - line-height: 1.25rem; - font-weight: 400; - font-family: var(--font-mono-stack); } -/* Hide the trailing "In: header" line below an auth property — redundant with - the header marker on the row itself. Matched by position, not by shape: - fumadocs emits the description first and this line last, and every scheme - description in our specs mentions a status code, so a `p:has(> code)` match - also hides the description — which is the whole explanation of personal vs - workspace-scoped keys. */ +/* Hide the trailing "In: header" line — redundant with the header marker. + Matched by position, not shape: descriptions contain a `code` too (status + codes), so a bare `p:has(> code)` also hid the API-key description. */ #nd-page:has(.api-page-header) div.my-4 .prose-no-margin > p:last-child:has(> code) { display: none !important; } @@ -1493,10 +1459,8 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { border-color: var(--surface-active); } -/* Body/Callback section "application/json" label — strip the inline-code chrome - and match `.response-section-content-type` exactly. These two render the same - string in the Request Body and Response headers; left divergent, the Response - one reads visibly lighter than the Request Body one on the same page. */ +/* Body/Callback "application/json" label — strip inline-code chrome and keep in + sync with `.response-section-content-type`; same string, two headers. */ #nd-page:has(.api-page-header) .flex.gap-2.items-center.justify-between p.not-prose code.text-xs, #nd-page:has(.api-page-header) .flex.justify-between.gap-2.items-end p.not-prose code.text-xs { background: none !important; @@ -1515,33 +1479,13 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { margin-bottom: 0.25rem !important; } -/* Example-panel code scrolls sideways rather than wrapping, which is fumadocs' - own behaviour and is deliberate here: indentation carries the nesting depth - of a JSON body, and a wrapped continuation line restarts at column zero and - misreports that depth. A hanging indent would preserve it, but the lines are - laid out as flex rows and forcing them to blocks to get one breaks the line - rhythm. Long values are clipped instead, as they are in every other API - reference. */ - -/* Status-code tabs in the example panel. These are numeric literals and read as - code everywhere else on the page — including the Response header's own status - trigger — but fumadocs renders the strip in the body sans face. Language tabs - sit in a different container and stay sans: those are product names, not code. */ -#nd-page:has(.api-page-header) .flex.gap-3\.5.overflow-x-auto.not-prose > button { - font-family: var(--font-mono-stack); - font-size: var(--text-code); - font-weight: 400; -} +/* Example-panel code overflows rather than wraps: a wrapped line restarts at + column zero and misreports the JSON nesting depth. */ -/* Any lucide glyph fumadocs still renders itself — the heading anchor and the - code-block copy button on API pages, and whatever else its chrome draws. - emcn strokes at 1.55 and lucide ships at 2, so those icons read noticeably - heavier than every icon around them. - Deliberately layout-wide rather than API-scoped: the goal is one icon weight - across the docs. The glyphs themselves can be replaced properly by passing - `renderHeading` and `renderCodeBlock` to `createAPIPage` — the app already - owns emcn-based Heading and CodeBlock components for MDX pages — which would - retire this rule. */ +/* fumadocs' own lucide glyphs (heading anchor, copy button) ship at stroke-width + 2 while emcn strokes at 1.55, so they read heavier than everything near them. + Layout-wide on purpose: one icon weight across the docs. Retired once + createAPIPage is given renderHeading/renderCodeBlock. */ #nd-docs-layout svg[class*="lucide"] { stroke-width: 1.55; } @@ -1569,7 +1513,7 @@ div.not-prose.rounded-md.border.bg-fd-card.p-2 { div.rounded-xl.border.bg-fd-card.shadow-md:has(> [role="none"]) > svg { fill: none !important; color: var(--color-fd-foreground) !important; - stroke-width: 1.75 !important; + stroke-width: 1.55 !important; flex-shrink: 0; width: 1rem !important; height: 1rem !important; From 256147f4d16bb7d9a21999e945a396711d6fca56 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 12 Aug 2026 19:22:11 -0700 Subject: [PATCH 8/8] fix(docs): keep the union separator in the type token's own face MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `|` between union members is a classless span, so the page-wide `span:not(.font-mono)` rule assigned it the body sans face while the members beside it stayed mono — one chip rendering in two faces. Applies the inherit reset to every descendant of a type token rather than just its links, so anything fumadocs nests there later is covered too. --- apps/docs/app/global.css | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index 77a0d4385b6..6414f5797e3 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -1312,13 +1312,18 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs { font-family: var(--font-mono-stack); } -/* Underline deferred to hover so links don't read heavier than a plain scalar - in the same box. The button that *is* the slot needs its own rule: it cannot - `inherit`, which would pull the row's 14px sans back in. */ +/* Everything inside a type token inherits the token's own face, size, and ink. + Applied to every descendant, not just the links: a union's `|` separator is a + classless `span`, so the page-wide `span:not(.font-mono)` rule assigned it the + body sans face and one chip rendered in two faces. Anything fumadocs nests in + here later is covered by the same reset. + Underline is deferred to hover so links don't read heavier than a plain scalar + in the same box. The button that *is* the slot needs its own rule below: it + cannot `inherit`, which would pull the row's 14px sans back in. */ #nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose > span.text-sm.font-mono.text-fd-muted-foreground - :is(a, button) { + * { text-decoration: none; color: inherit; font-size: inherit;