From 6cf0b326da6a2a8dca22ee57aa840a08225ad528 Mon Sep 17 00:00:00 2001 From: Eli Schleifer Date: Fri, 18 Sep 2026 08:08:53 +0000 Subject: [PATCH 01/14] Move Tests and CI icons from top nav to sidebar Match the Merge Queue treatment: tabs carry no icon, and the icon is attached to the left-nav group instead. - Tests: move "flask" onto the Flaky Tests group - CI: wrap the tab's flat page list in a CI group and attach product-ci.svg to it Co-Authored-By: Claude Opus 5 (1M context) --- docs.json | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/docs.json b/docs.json index 6642a924..538b1a99 100644 --- a/docs.json +++ b/docs.json @@ -390,27 +390,32 @@ ] }, "flaky-tests/changelog" - ] + ], + "icon": "flask" } - ], - "icon": "flask" + ] }, { "tab": "CI", - "pages": [ - "ci/index", + "groups": [ { - "group": "Getting Started", - "root": "ci/get-started/index", + "group": "CI", "pages": [ - "ci/get-started/github-actions", - "ci/get-started/buildkite" - ] - }, - "ci/pull-requests", - "ci/ci-tab" - ], - "icon": "/assets/icons/product-ci.svg" + "ci/index", + { + "group": "Getting Started", + "root": "ci/get-started/index", + "pages": [ + "ci/get-started/github-actions", + "ci/get-started/buildkite" + ] + }, + "ci/pull-requests", + "ci/ci-tab" + ], + "icon": "/assets/icons/product-ci.svg" + } + ] }, { "tab": "Dynamic CI", From 3771f98bc3e820d9c043bd0a2a34d1518f46f920 Mon Sep 17 00:00:00 2001 From: Eli Schleifer Date: Fri, 18 Sep 2026 08:10:24 +0000 Subject: [PATCH 02/14] Use trunk2's Tests icon for the Tests sidebar group Replaces Mintlify's built-in "flask" glyph with the exact icon the app uses for the Tests product (lucide FlaskConical, per PRODUCT_CONFIG in trunk2), stored alongside the other product icons. Co-Authored-By: Claude Opus 5 (1M context) --- assets/icons/product-flaky-tests.svg | 5 +++++ docs.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 assets/icons/product-flaky-tests.svg diff --git a/assets/icons/product-flaky-tests.svg b/assets/icons/product-flaky-tests.svg new file mode 100644 index 00000000..def4e55e --- /dev/null +++ b/assets/icons/product-flaky-tests.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/docs.json b/docs.json index 538b1a99..b47775dc 100644 --- a/docs.json +++ b/docs.json @@ -391,7 +391,7 @@ }, "flaky-tests/changelog" ], - "icon": "flask" + "icon": "/assets/icons/product-flaky-tests.svg" } ] }, From 1d8006a0634831e96c440ca4db6f3115f04783da Mon Sep 17 00:00:00 2001 From: Eli Schleifer Date: Fri, 18 Sep 2026 08:11:06 +0000 Subject: [PATCH 03/14] Show test frameworks 5 across The framework grid was 3 columns wide, so each card's logo rendered at a third of the page width. Five columns shrinks the cards, and the images scale with them. Also switch the new Tests icon from currentColor to a literal black stroke, matching the solid-black source convention styles.css documents for custom SVGs (Mintlify renders them as and inverts for dark). Co-Authored-By: Claude Opus 5 (1M context) --- assets/icons/product-flaky-tests.svg | 2 +- flaky-tests/get-started/frameworks/index.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/icons/product-flaky-tests.svg b/assets/icons/product-flaky-tests.svg index def4e55e..d16cd915 100644 --- a/assets/icons/product-flaky-tests.svg +++ b/assets/icons/product-flaky-tests.svg @@ -1,4 +1,4 @@ - + diff --git a/flaky-tests/get-started/frameworks/index.mdx b/flaky-tests/get-started/frameworks/index.mdx index 52b705a3..c9b4323d 100644 --- a/flaky-tests/get-started/frameworks/index.mdx +++ b/flaky-tests/get-started/frameworks/index.mdx @@ -8,7 +8,7 @@ Trunk Flaky Tests uses test results uploaded from your CI jobs to detect flaky t Follow one of the guides below to configure your test framework to output compatible test reports and integrate with Trunk. - + Date: Fri, 18 Sep 2026 08:11:55 +0000 Subject: [PATCH 04/14] Show CI providers 5 across Same treatment as the test frameworks grid: five columns instead of three, so each provider logo renders at a fifth of the page width. Co-Authored-By: Claude Opus 5 (1M context) --- flaky-tests/get-started/ci-providers/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flaky-tests/get-started/ci-providers/index.mdx b/flaky-tests/get-started/ci-providers/index.mdx index d7503e31..07662495 100644 --- a/flaky-tests/get-started/ci-providers/index.mdx +++ b/flaky-tests/get-started/ci-providers/index.mdx @@ -14,7 +14,7 @@ Flaky Test support for source control providers like GitLab and BitBucket is sti ## Quickstart - + Date: Fri, 18 Sep 2026 08:15:19 +0000 Subject: [PATCH 05/14] Use 4 columns, the max Columns supports Mintlify's Columns component accepts cols values from 1 to 4, so the cols={5} on these two grids was out of range. Four across is the widest supported, and still shrinks the logos relative to the original three. Co-Authored-By: Claude Opus 5 (1M context) --- flaky-tests/get-started/ci-providers/index.mdx | 2 +- flaky-tests/get-started/frameworks/index.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flaky-tests/get-started/ci-providers/index.mdx b/flaky-tests/get-started/ci-providers/index.mdx index 07662495..5e7e4110 100644 --- a/flaky-tests/get-started/ci-providers/index.mdx +++ b/flaky-tests/get-started/ci-providers/index.mdx @@ -14,7 +14,7 @@ Flaky Test support for source control providers like GitLab and BitBucket is sti ## Quickstart - + + Date: Fri, 18 Sep 2026 08:25:36 +0000 Subject: [PATCH 06/14] test: inline CI nav icon as data URI Checking whether Mintlify's nav-icon image proxy (which 403s on custom SVG paths) can be bypassed. Revert if this does not render. Co-Authored-By: Claude Opus 5 (1M context) --- docs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs.json b/docs.json index b47775dc..37079cdb 100644 --- a/docs.json +++ b/docs.json @@ -413,7 +413,7 @@ "ci/pull-requests", "ci/ci-tab" ], - "icon": "/assets/icons/product-ci.svg" + "icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEzLjMwNjYgMTMuMzAzOUM4LjIwNjc0IDE4LjQ0NDUgLTAuNDgzMzQ0IDEzLjk5MDYgMC41OTEwMTcgNi43ODk3MUMxLjM2NjE5IDEuNjAxNSA3LjQxMTE3IC0xLjIxMzYgMTEuODg1NCAxLjU2NzVDMTMuNTY0OSAwLjY0OTUzMSAxNS4zNTMzIDIuNDE3NDcgMTQuNDE0OSA0LjA5NzAxQzE2LjIyMzYgNy4wMjA5IDE1LjcxMzcgMTAuODkgMTMuMzEzNCAxMy4zMTA3TDEzLjMwNjYgMTMuMzAzOVpNMTMuNjk0MSA0Ljc5NzM4QzEyLjM1NDYgNS40MTYxNiAxMC44NzkgNC40NDM3OSAxMS4wMTUgMi45NDc4NUMxMS4wMzU0IDIuNzMwMjYgMTEuMTIzOCAyLjUyNjI2IDExLjE3MTQgMi4zMjIyN0M4LjA0MzU0IDAuNTEzNTM2IDQuMDM4NDkgMS43MjM4OSAyLjMxODE1IDQuODQ0OThDMC4yMzc0MyA4LjYxMjA0IDIuMzU4OTUgMTMuMzY1MSA2LjUyNzIgMTQuMzMwNkMxMS40MzY2IDE1LjQ2NjIgMTUuNjY2MSAxMC44NzY0IDE0LjIxNzcgNi4wNjIxM0MxNC4wODg1IDUuNjIwMTUgMTMuODc3NyA1LjIxODk2IDEzLjcwMDkgNC43OTczOEgxMy42OTQxWk0xMy42NjY5IDMuMTQ1MDRDMTMuNjY2OSAyLjY4OTQ2IDEzLjI5OTggMi4zMTU0NyAxMi44Mzc0IDIuMzE1NDdDMTIuMzc1IDIuMzE1NDcgMTIuMDA3OCAyLjY4MjY2IDEyLjAwNzggMy4xNDUwNEMxMi4wMDc4IDMuNjA3NDIgMTIuMzc1IDMuOTc0NjEgMTIuODM3NCAzLjk3NDYxQzEzLjI5OTggMy45NzQ2MSAxMy42NjY5IDMuNjA3NDIgMTMuNjY2OSAzLjE0NTA0WiIgZmlsbD0iYmxhY2siLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik04LjAwMDAyIDkuMjQ0NDZDOC42ODczIDkuMjQ0NDYgOS4yNDQ0NiA4LjY4NzMgOS4yNDQ0NiA4LjAwMDAyQzkuMjQ0NDYgNy4zMTI3MyA4LjY4NzMgNi43NTU1NyA4LjAwMDAyIDYuNzU1NTdDNy4zMTI3MyA2Ljc1NTU3IDYuNzU1NTcgNy4zMTI3MyA2Ljc1NTU3IDguMDAwMDJDNi43NTU1NyA4LjY4NzMgNy4zMTI3MyA5LjI0NDQ2IDguMDAwMDIgOS4yNDQ0NlpNOC4wMDAwMiAxMC4zMTExQzkuMjc2NDEgMTAuMzExMSAxMC4zMTExIDkuMjc2NDEgMTAuMzExMSA4LjAwMDAyQzEwLjMxMTEgNi43MjM2MiA5LjI3NjQxIDUuNjg4OSA4LjAwMDAyIDUuNjg4OUM2LjcyMzYyIDUuNjg4OSA1LjY4ODkgNi43MjM2MiA1LjY4ODkgOC4wMDAwMkM1LjY4ODkgOS4yNzY0MSA2LjcyMzYyIDEwLjMxMTEgOC4wMDAwMiAxMC4zMTExWiIgZmlsbD0iIzA4MDkwRCIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTUuNzM1NDkgMTIuMjc5NkM1LjczNTQ5IDEyLjU3NDIgNS40OTY3MSAxMi44MTMgNS4yMDIxNiAxMi44MTNMMy4xMjA3NyAxMi44MTNMMy4xMjA3NyAxNC44OTQ0QzMuMTIwNzcgMTUuMTg4OSAyLjg4MTk5IDE1LjQyNzcgMi41ODc0MyAxNS40Mjc3QzIuMjkyODggMTUuNDI3NyAyLjA1NDEgMTUuMTg4OSAyLjA1NDEgMTQuODk0NEwyLjA1NDEgMTEuNzQ2M0w1LjIwMjE2IDExLjc0NjNDNS40OTY3MSAxMS43NDYzIDUuNzM1NDkgMTEuOTg1MSA1LjczNTQ5IDEyLjI3OTZaIiBmaWxsPSIjMDgwOTBEIi8+Cjwvc3ZnPgo=" } ] }, From 085e760ebb892d53551efa06bedec1dfd9d1c766 Mon Sep 17 00:00:00 2001 From: Eli Schleifer Date: Fri, 18 Sep 2026 08:28:10 +0000 Subject: [PATCH 07/14] Inline nav icons so they actually render MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A nav icon pointing at a repo file gets rewritten to /_mintlify/image//, which is unsigned — CloudFront answers 403 MissingKey, so the sidebar never loads and the group header shows a blank where the icon belongs. This hit all three product icons, including Merge Queue on production; it was just less obvious next to a long label than next to "CI". Page images are unaffected (those get signed mintcdn.com URLs), so the fix is to hand Mintlify an icon it has no reason to proxy: a data URI. scripts/embed-nav-icons.py regenerates them from the SVGs, which stay the source of truth under assets/icons/. Verified on the branch preview: the sidebar icon now loads at 16x16 instead of reporting naturalWidth 0. Co-Authored-By: Claude Opus 5 (1M context) --- docs.json | 4 +-- scripts/embed-nav-icons.py | 73 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 2 deletions(-) create mode 100755 scripts/embed-nav-icons.py diff --git a/docs.json b/docs.json index 37079cdb..3546ceeb 100644 --- a/docs.json +++ b/docs.json @@ -164,7 +164,7 @@ }, "merge-queue/changelog" ], - "icon": "/assets/icons/product-merge-queue.svg" + "icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzcxOTJfNzM0OSkiPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTMuODc3MjMgNS4yOTE5QzQuNjU4NTMgNS4yOTE5IDUuMjkxOSA0LjY1ODUzIDUuMjkxOSAzLjg3NzIzQzUuMjkxOSAzLjA5NTk0IDQuNjU4NTMgMi40NjI1NyAzLjg3NzIzIDIuNDYyNTdDMy4wOTU5NCAyLjQ2MjU3IDIuNDYyNTcgMy4wOTU5NCAyLjQ2MjU3IDMuODc3MjNDMi40NjI1NyA0LjY1ODUzIDMuMDk1OTQgNS4yOTE5IDMuODc3MjMgNS4yOTE5Wk0zLjg3NzIzIDYuNTA0NDdDNS4zMjgyMiA2LjUwNDQ3IDYuNTA0NDcgNS4zMjgyMiA2LjUwNDQ3IDMuODc3MjNDNi41MDQ0NyAyLjQyNjI1IDUuMzI4MjIgMS4yNSAzLjg3NzIzIDEuMjVDMi40MjYyNSAxLjI1IDEuMjUgMi40MjYyNSAxLjI1IDMuODc3MjNDMS4yNSA1LjMyODIyIDIuNDI2MjUgNi41MDQ0NyAzLjg3NzIzIDYuNTA0NDdaIiBmaWxsPSIjMDgwOTBEIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMy4zNTE5MSAxNC4wMjI0TDMuMzUxOTEgNS40NTM1OEw0LjU2NDQ4IDUuNDUzNThMNC41NjQ0OCAxNC4wMjI0QzQuNTY0NDggMTQuMzU3MiA0LjI5MzA0IDE0LjYyODcgMy45NTgyIDE0LjYyODdDMy42MjMzNiAxNC42Mjg3IDMuMzUxOTEgMTQuMzU3MiAzLjM1MTkxIDE0LjAyMjRaIiBmaWxsPSIjMDgwOTBEIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTIuMTIyNyAxMy4zNzU4QzEyLjkwNCAxMy4zNzU4IDEzLjUzNzQgMTIuNzQyNCAxMy41Mzc0IDExLjk2MTFDMTMuNTM3NCAxMS4xNzk4IDEyLjkwNCAxMC41NDY0IDEyLjEyMjcgMTAuNTQ2NEMxMS4zNDE0IDEwLjU0NjQgMTAuNzA4MSAxMS4xNzk4IDEwLjcwODEgMTEuOTYxMUMxMC43MDgxIDEyLjc0MjQgMTEuMzQxNCAxMy4zNzU4IDEyLjEyMjcgMTMuMzc1OFpNMTIuMTIyNyAxNC41ODgzQzEzLjU3MzcgMTQuNTg4MyAxNC43NSAxMy40MTIxIDE0Ljc1IDExLjk2MTFDMTQuNzUgMTAuNTEwMSAxMy41NzM3IDkuMzMzODYgMTIuMTIyNyA5LjMzMzg2QzEwLjY3MTcgOS4zMzM4NiA5LjQ5NTQ4IDEwLjUxMDEgOS40OTU0OCAxMS45NjExQzkuNDk1NDggMTMuNDEyMSAxMC42NzE3IDE0LjU4ODMgMTIuMTIyNyAxNC41ODgzWiIgZmlsbD0iIzA4MDkwRCIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTkuNjk3NiAyLjc0NTUxQzExLjM3MTggMi43NDU1MSAxMi43MjkgNC4xMDI3MyAxMi43MjkgNS43NzY5NEwxMi43MjkgOS4zMzM4MUMxMi43MjkgOS42Njg2NSAxMi40NTc2IDkuOTQwMDkgMTIuMTIyNyA5Ljk0MDA5QzExLjc4NzkgOS45NDAwOSAxMS41MTY0IDkuNjY4NjUgMTEuNTE2NCA5LjMzMzgxTDExLjUxNjQgNS43NzY5NEMxMS41MTY0IDQuNzcyNDEgMTAuNzAyMSAzLjk1ODA4IDkuNjk3NiAzLjk1ODA4TDguNDQ0NjEgMy45NTgwOEM4LjEwOTc3IDMuOTU4MDggNy44MzgzMiAzLjY4NjY0IDcuODM4MzIgMy4zNTE4QzcuODM4MzIgMy4wMTY5NiA4LjEwOTc3IDIuNzQ1NTEgOC40NDQ2MSAyLjc0NTUxTDkuNjk3NiAyLjc0NTUxWiIgZmlsbD0iIzA4MDkwRCIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzcxOTJfNzM0OSI+CjxyZWN0IHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K" } ] }, @@ -391,7 +391,7 @@ }, "flaky-tests/changelog" ], - "icon": "/assets/icons/product-flaky-tests.svg" + "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+CjxwYXRoIGQ9Ik0xNCAydjZhMiAyIDAgMCAwIC4yNDUuOTZsNS41MSAxMC4wOEEyIDIgMCAwIDEgMTggMjJINmEyIDIgMCAwIDEtMS43NTUtMi45Nmw1LjUxLTEwLjA4QTIgMiAwIDAgMCAxMCA4VjIiLz4KPHBhdGggZD0iTTYuNDUzIDE1aDExLjA5NCIvPgo8cGF0aCBkPSJNOC41IDJoNyIvPgo8L3N2Zz4K" } ] }, diff --git a/scripts/embed-nav-icons.py b/scripts/embed-nav-icons.py new file mode 100755 index 00000000..21ee477d --- /dev/null +++ b/scripts/embed-nav-icons.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +"""Inline the custom navigation icons in docs.json as data URIs. + +Mintlify rewrites a nav `icon` that points at a repo file (e.g. +`/assets/icons/product-ci.svg`) to `/_mintlify/image//`, +and that proxy URL is unsigned: CloudFront answers 403 MissingKey, so the +`` never loads and the group header shows +its label with a blank space where the icon belongs. Page images are fine +— those are rewritten to *signed* mintcdn.com URLs — the bug is specific +to nav icons. + +The workaround is to give Mintlify an icon it has no reason to proxy: a +`data:` URI. These icons are ~1-2 KB, so the inlined cost is trivial. + +The SVGs under assets/icons/ stay the source of truth. Edit one, re-run +this script, and commit the regenerated docs.json: + + python3 scripts/embed-nav-icons.py + +Drop an entry from ICONS (and restore the plain path in docs.json) if +Mintlify ever signs nav-icon URLs properly. +""" + +import base64 +import json +import pathlib +import re + +ROOT = pathlib.Path(__file__).resolve().parent.parent +DOCS_JSON = ROOT / "docs.json" + +# Nav group -> the SVG whose contents that group's icon should carry. +ICONS = { + "Merge Queue": "assets/icons/product-merge-queue.svg", + "Flaky Tests": "assets/icons/product-flaky-tests.svg", + "CI": "assets/icons/product-ci.svg", +} + + +def data_uri(svg_path: pathlib.Path) -> str: + encoded = base64.b64encode(svg_path.read_bytes()).decode("ascii") + return f"data:image/svg+xml;base64,{encoded}" + + +def main() -> None: + docs = json.loads(DOCS_JSON.read_text()) + raw = DOCS_JSON.read_text() + + for group, rel_path in ICONS.items(): + uri = data_uri(ROOT / rel_path) + found = False + for tab in docs["navigation"]["tabs"]: + for nav_group in tab.get("groups", []): + if nav_group.get("group") == group and "icon" in nav_group: + # Rewrite in the raw text so the rest of docs.json keeps + # its hand-authored key order and formatting. + raw = re.sub( + r'("group": "%s",(?:.|\n)*?"icon": )"[^"]*"' % re.escape(group), + lambda m: m.group(1) + json.dumps(uri), + raw, + count=1, + ) + found = True + if not found: + raise SystemExit(f'no nav group named "{group}" carries an icon') + + DOCS_JSON.write_text(raw) + json.loads(raw) # fail loudly rather than commit invalid JSON + print(f"inlined {len(ICONS)} nav icons into docs.json") + + +if __name__ == "__main__": + main() From 837ec822d20d819e384e9f4743bef08657b8d978 Mon Sep 17 00:00:00 2001 From: Eli Schleifer Date: Fri, 18 Sep 2026 08:40:48 +0000 Subject: [PATCH 08/14] Add CLAUDE.md covering the nav-icon script Documents why nav icons are inlined as data URIs, how to regenerate them after editing an SVG, and the two gotchas that pass Mintlify's checks while rendering wrong (repo-file nav icons, cols above 4). Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..60862bce --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,61 @@ +# Working in this repo + +Mintlify docs site. `docs.json` holds the navigation; content is `.mdx` under +the product directories. Preview locally with `mint dev` (see CONTRIBUTING.md). + +## Scripts + +### `scripts/embed-nav-icons.py` — navigation icons + +Run this after editing any product icon SVG, and commit the regenerated +`docs.json`: + +```sh +python3 scripts/embed-nav-icons.py +``` + +A navigation `icon` in `docs.json` **cannot** point at a repo file. Mintlify +rewrites such a path to `/_mintlify/image//`, which is unsigned, +so CloudFront answers `403 MissingKey`: the sidebar `` loads nothing and +the group header shows a blank gap where the icon belongs. This is specific to +nav icons — images inside a page are rewritten to *signed* `mintcdn.com` URLs +and are fine. + +So the icons are inlined into `docs.json` as `data:` URIs, which Mintlify has no +reason to proxy. The SVGs under `assets/icons/` stay the source of truth; the +script regenerates the inlined copies from them. Add a new product icon by +dropping the SVG in `assets/icons/` and adding a `group -> path` entry to the +script's `ICONS` map. + +If Mintlify ever signs nav-icon URLs, drop the entry and restore the plain path. +Built-in icon names (`"flask"`, Lucide/Font Awesome) are unaffected by this bug +and can be used instead of a custom SVG at any time. + +### `scripts/sync-changelog.py` — changelog navigation + +Regenerates the changelog nav across the sites it appears on from each +`changelog/*.mdx` frontmatter. See the script's docstring. + +## Conventions + +- **Custom SVGs are authored solid black.** `styles.css` inverts them for dark + mode, so do not add theme variants or use `currentColor` — it resolves to + black inside an `` and defeats nothing, but the literal color is what the + stylesheet's comment documents. +- **`` accepts 1-4.** A larger value is not an error and does + not fail the build; it just does not render that many across. + +## Verifying a change + +Mintlify's checks pass on things that are visibly broken — an out-of-range prop +or an icon that 403s fails nothing. For anything visual, open the branch's +preview deployment and look at it: + +```sh +id=$(gh api repos/trunk-io/docs2/deployments --jq '.[0].id') +gh api repos/trunk-io/docs2/deployments/$id/statuses --jq '.[0].environment_url' +``` + +The sidebar renders client-side, so curl will not show it. When checking an +image, read its `naturalWidth` — a broken image is present in the DOM and +reports 0. From 149aa9409068577b22c0e005bf790aa1810a0059 Mon Sep 17 00:00:00 2001 From: Eli Schleifer Date: Fri, 18 Sep 2026 08:43:04 +0000 Subject: [PATCH 09/14] Title the CI landing page Overview The sidebar group, the eyebrow, and the H1 all read "CI", so the page stuttered. Merge Queue and Flaky Tests already solve this: the landing page is titled "Overview" and the product name is carried by the group header, with og:title keeping the product name for search and social. Co-Authored-By: Claude Opus 5 (1M context) --- ci/index.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/index.mdx b/ci/index.mdx index 322ec255..0729c5c0 100644 --- a/ci/index.mdx +++ b/ci/index.mdx @@ -1,7 +1,7 @@ --- -title: "CI" +title: "Overview" description: "See every job your CI runs - what it costs, how often it fails, and why a pull request is red — without instrumenting your pipelines." -og:title: "Trunk CI" +og:title: "Trunk CI overview" --- Trunk CI records what your CI actually did. Every workflow run, every job, every From 4ba562beec7b060581195fd061c1f7bc4e26a261 Mon Sep 17 00:00:00 2001 From: Eli Schleifer Date: Fri, 18 Sep 2026 08:44:20 +0000 Subject: [PATCH 10/14] Hide the Dynamic CI and Workspaces tabs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both are gated per page (groups: ["dynamic-ci"] / ["firewatch"]) and Mintlify does strip those pages from a signed-out visitor's navigation — but it leaves the emptied tab behind, so the product names still showed in the top nav. There is no tab-level group restriction in docs.json, so hide the tabs outright until Mintlify drops emptied tabs on its own. Tradeoff: hidden applies to everyone, so authorized users no longer get a tab either and reach these docs by direct link. Co-Authored-By: Claude Opus 5 (1M context) --- docs.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs.json b/docs.json index 3546ceeb..c31711ad 100644 --- a/docs.json +++ b/docs.json @@ -419,12 +419,14 @@ }, { "tab": "Dynamic CI", + "hidden": true, "pages": [ "dynamic-ci/index" ] }, { "tab": "Workspaces", + "hidden": true, "groups": [ { "group": "Overview", From 18b694c4d62e065b5f824f7ea7904c8863ff6e70 Mon Sep 17 00:00:00 2001 From: Eli Schleifer Date: Fri, 18 Sep 2026 08:46:38 +0000 Subject: [PATCH 11/14] Inline the Trunk Platform page icon too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit index.mdx's `icon:` frontmatter pointed at /assets/icons/trunk-glyph.svg and hit the same unsigned-proxy 403 as the group icons — the icon beside "Trunk Platform" in the nav was never loading. Extends the script to patch page frontmatter as well. Checked the other repo-file icons while here: the ones under setup/integrations/github and ci/get-started resolve to signed mintcdn URLs and load fine, so page content is genuinely unaffected and stays on plain paths. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 10 +++++---- index.mdx | 2 +- scripts/embed-nav-icons.py | 44 ++++++++++++++++++++++++++++++++------ 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 60862bce..f2fa1c44 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,18 +14,20 @@ Run this after editing any product icon SVG, and commit the regenerated python3 scripts/embed-nav-icons.py ``` -A navigation `icon` in `docs.json` **cannot** point at a repo file. Mintlify +A navigation `icon` **cannot** point at a repo file — neither a group icon in +`docs.json` nor a page's `icon:` frontmatter. Mintlify rewrites such a path to `/_mintlify/image//`, which is unsigned, so CloudFront answers `403 MissingKey`: the sidebar `` loads nothing and the group header shows a blank gap where the icon belongs. This is specific to -nav icons — images inside a page are rewritten to *signed* `mintcdn.com` URLs -and are fine. +nav icons — anything inside a page, including a ``, is +rewritten to a *signed* `mintcdn.com` URL and loads fine, so leave those as +plain paths. So the icons are inlined into `docs.json` as `data:` URIs, which Mintlify has no reason to proxy. The SVGs under `assets/icons/` stay the source of truth; the script regenerates the inlined copies from them. Add a new product icon by dropping the SVG in `assets/icons/` and adding a `group -> path` entry to the -script's `ICONS` map. +script's `GROUP_ICONS` map (or `PAGE_ICONS`, for a page's frontmatter icon). If Mintlify ever signs nav-icon URLs, drop the entry and restore the plain path. Built-in icon names (`"flask"`, Lucide/Font Awesome) are unaffected by this bug diff --git a/index.mdx b/index.mdx index 779ba79e..37a8c614 100644 --- a/index.mdx +++ b/index.mdx @@ -2,7 +2,7 @@ title: "Trunk Platform" description: "Ship Software as Fast as AI Writes It" og:title: "Trunk Platform Documentation" -icon: "/assets/icons/trunk-glyph.svg" +icon: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2My41OTk5OTk5OTk5OTk5OTQgNjMuNTk5OTk5OTk5OTk5OTk0IiB3aWR0aD0iMzEwIiBoZWlnaHQ9IjMxMCI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNS4zMDAwMDAwMDAwMDAwMDEsNS4zMDAwMDAwMDAwMDAwMDEpIj48bWFzayBpZD0iYSIgd2lkdGg9IjUwIiBoZWlnaHQ9IjUwIiB4PSIwIiB5PSIwIiBtYXNrVW5pdHM9InVzZXJTcGFjZU9uVXNlIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNDkuNDguMTY0SDBWNTBoNDkuNDh6Ii8+PC9tYXNrPjxnIGZpbGw9IiMwODA5MGIiIG1hc2s9InVybCgjYSkiPjxwYXRoIGQ9Ik0yNC43NCA1MHYtNS40MTZjMTAuNjc2IDAgMTkuMzYyLTguNzQ4IDE5LjM2Mi0xOS41MDJTMzUuNDE2IDUuNTggMjQuNzQgNS41OGMtMTAuNjc3IDAtMTkuMzYzIDguNzQ5LTE5LjM2MyAxOS41MDJIMEMwIDExLjM0MiAxMS4wOTguMTY0IDI0Ljc0LjE2NHMyNC43NCAxMS4xNzggMjQuNzQgMjQuOTE4UzM4LjM4IDUwIDI0Ljc0IDUwIi8+PHBhdGggZD0iTTEyLjcyNiAyNS40NzVINy4zNWMwLTkuODEgNy44LTE3Ljc5MSAxNy4zODktMTcuNzkxdjUuNTAxYy02LjYyMyAwLTEyLjAxMiA1LjUxMy0xMi4wMTIgMTIuMjkiLz48cGF0aCBkPSJNMzQuNzc1IDI1LjQ3NWgtNS4zNzdjMC0yLjYzLTIuMDkyLTQuNzctNC42NjMtNC43Ny0yLjU3IDAtNC42NjEgMi4xNC00LjY2MSA0Ljc3aC01LjM3OGMwLTUuNjY0IDQuNTA0LTEwLjI3MSAxMC4wNC0xMC4yNzFzMTAuMDM5IDQuNjA3IDEwLjAzOSAxMC4yNyIvPjwvZz48L2c+PC9zdmc+" --- AI generates code at machine speed, but code review, CI, and delivery still move at human pace. That gap is widening, and it gets worse every time you add another AI agent to the loop. diff --git a/scripts/embed-nav-icons.py b/scripts/embed-nav-icons.py index 21ee477d..f530ae31 100755 --- a/scripts/embed-nav-icons.py +++ b/scripts/embed-nav-icons.py @@ -12,13 +12,18 @@ The workaround is to give Mintlify an icon it has no reason to proxy: a `data:` URI. These icons are ~1-2 KB, so the inlined cost is trivial. +The same applies to a page's `icon:` frontmatter, which Mintlify renders +in the navigation. Icons used in page *content* — a ``, +say — are not affected: those resolve to signed mintcdn.com URLs and load +fine, so leave them as plain paths. + The SVGs under assets/icons/ stay the source of truth. Edit one, re-run -this script, and commit the regenerated docs.json: +this script, and commit the regenerated files: python3 scripts/embed-nav-icons.py -Drop an entry from ICONS (and restore the plain path in docs.json) if -Mintlify ever signs nav-icon URLs properly. +Drop an entry from GROUP_ICONS / PAGE_ICONS (and restore the plain path) +if Mintlify ever signs nav-icon URLs properly. """ import base64 @@ -29,24 +34,45 @@ ROOT = pathlib.Path(__file__).resolve().parent.parent DOCS_JSON = ROOT / "docs.json" -# Nav group -> the SVG whose contents that group's icon should carry. -ICONS = { +# docs.json nav group -> the SVG whose contents that group's icon carries. +GROUP_ICONS = { "Merge Queue": "assets/icons/product-merge-queue.svg", "Flaky Tests": "assets/icons/product-flaky-tests.svg", "CI": "assets/icons/product-ci.svg", } +# Page with an `icon:` in its frontmatter -> the SVG that icon carries. +PAGE_ICONS = { + "index.mdx": "assets/icons/trunk-glyph.svg", +} + def data_uri(svg_path: pathlib.Path) -> str: encoded = base64.b64encode(svg_path.read_bytes()).decode("ascii") return f"data:image/svg+xml;base64,{encoded}" +def embed_page_icons() -> None: + for page, rel_path in PAGE_ICONS.items(): + path = ROOT / page + text = path.read_text() + patched, count = re.subn( + r'^icon: "[^"]*"$', + lambda m: 'icon: "%s"' % data_uri(ROOT / rel_path), + text, + count=1, + flags=re.M, + ) + if not count: + raise SystemExit(f"{page} has no icon: line in its frontmatter") + path.write_text(patched) + + def main() -> None: docs = json.loads(DOCS_JSON.read_text()) raw = DOCS_JSON.read_text() - for group, rel_path in ICONS.items(): + for group, rel_path in GROUP_ICONS.items(): uri = data_uri(ROOT / rel_path) found = False for tab in docs["navigation"]["tabs"]: @@ -66,7 +92,11 @@ def main() -> None: DOCS_JSON.write_text(raw) json.loads(raw) # fail loudly rather than commit invalid JSON - print(f"inlined {len(ICONS)} nav icons into docs.json") + embed_page_icons() + print( + f"inlined {len(GROUP_ICONS)} group icons into docs.json " + f"and {len(PAGE_ICONS)} page icons" + ) if __name__ == "__main__": From 5f4de8ccc3accaf004a17c4476d0cb5ed6215093 Mon Sep 17 00:00:00 2001 From: Eli Schleifer Date: Fri, 18 Sep 2026 08:50:01 +0000 Subject: [PATCH 12/14] Reach Dynamic CI and Workspaces from the Overview tab Hiding the tabs took them away from the people who are allowed to see them too, so move both sections into the Overview tab's sidebar, below Trunk Platform. Nothing leaks: Mintlify does filter group-gated pages out of the navigation, and the Overview tab has index to keep it non-empty, so the emptied-tab bug that forced the hiding never comes up. The two hidden tabs are removed rather than duplicated, so the page lists stay single-sourced. Page URLs are unchanged. If Mintlify starts dropping tabs whose pages are all filtered, this can go back to being two tabs. Co-Authored-By: Claude Opus 5 (1M context) --- docs.json | 110 +++++++++++++++++++++++++++--------------------------- 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/docs.json b/docs.json index c31711ad..a3da9158 100644 --- a/docs.json +++ b/docs.json @@ -40,7 +40,60 @@ { "tab": "Overview", "pages": [ - "index" + "index", + { + "group": "Dynamic CI", + "pages": [ + "dynamic-ci/index" + ] + }, + { + "group": "Workspaces", + "pages": [ + { + "group": "Overview", + "pages": [ + "trunk-workspaces/index", + "trunk-workspaces/getting-started" + ] + }, + { + "group": "Using workspaces", + "pages": [ + "trunk-workspaces/workspaces/home", + "trunk-workspaces/workspaces/agent", + "trunk-workspaces/workspaces/cooperation", + "trunk-workspaces/workspaces/lifecycle", + "trunk-workspaces/workspaces/connect", + "trunk-workspaces/workspaces/share" + ] + }, + { + "group": "Access policy", + "pages": [ + "trunk-workspaces/access-policy/aws", + "trunk-workspaces/access-policy/tailscale", + "trunk-workspaces/access-policy/network-egress" + ] + }, + { + "group": "Templating", + "pages": [ + "trunk-workspaces/templating/lifecycle", + "trunk-workspaces/templating/environment-variables", + "trunk-workspaces/templating/tailscale", + "trunk-workspaces/templating/aws", + "trunk-workspaces/templating/composability" + ] + }, + { + "group": "Reference", + "pages": [ + "trunk-workspaces/desktop-reference" + ] + } + ] + } ] }, { @@ -417,61 +470,6 @@ } ] }, - { - "tab": "Dynamic CI", - "hidden": true, - "pages": [ - "dynamic-ci/index" - ] - }, - { - "tab": "Workspaces", - "hidden": true, - "groups": [ - { - "group": "Overview", - "pages": [ - "trunk-workspaces/index", - "trunk-workspaces/getting-started" - ] - }, - { - "group": "Using workspaces", - "pages": [ - "trunk-workspaces/workspaces/home", - "trunk-workspaces/workspaces/agent", - "trunk-workspaces/workspaces/cooperation", - "trunk-workspaces/workspaces/lifecycle", - "trunk-workspaces/workspaces/connect", - "trunk-workspaces/workspaces/share" - ] - }, - { - "group": "Access policy", - "pages": [ - "trunk-workspaces/access-policy/aws", - "trunk-workspaces/access-policy/tailscale", - "trunk-workspaces/access-policy/network-egress" - ] - }, - { - "group": "Templating", - "pages": [ - "trunk-workspaces/templating/lifecycle", - "trunk-workspaces/templating/environment-variables", - "trunk-workspaces/templating/tailscale", - "trunk-workspaces/templating/aws", - "trunk-workspaces/templating/composability" - ] - }, - { - "group": "Reference", - "pages": [ - "trunk-workspaces/desktop-reference" - ] - } - ] - }, { "tab": "Setup", "groups": [ From 545304de79cf52943e4d6ddd80c65cf43bc2ec80 Mon Sep 17 00:00:00 2001 From: Eli Schleifer Date: Fri, 18 Sep 2026 08:52:49 +0000 Subject: [PATCH 13/14] Document how the gated sections are navigated Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index f2fa1c44..bba9260b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,6 +47,21 @@ Regenerates the changelog nav across the sites it appears on from each - **`` accepts 1-4.** A larger value is not an error and does not fail the build; it just does not render that many across. +## Gated sections + +Dynamic CI and Workspaces are restricted per page with `groups:` frontmatter +(`["dynamic-ci"]` / `["firewatch"]`). They live in the **Overview** tab's +sidebar rather than in tabs of their own: Mintlify filters gated *pages* out of +the navigation but leaves the emptied tab behind, so a tab of their own would +show the product name to signed-out visitors. Nested under Overview the shells +are empty groups, which do not render, and the tab still has `index` to stand +on. Empty group names do remain in the page's JSON payload — they are not +secret, just not displayed. + +A tab marked `"hidden": true` is invisible to everyone, authorized users +included, but its pages stay reachable by URL and still render that tab's own +sidebar. That is how Changelog works. + ## Verifying a change Mintlify's checks pass on things that are visibly broken — an out-of-range prop From 9f104d4581bf686e1fb5080dc1bce5597939d6b7 Mon Sep 17 00:00:00 2001 From: Eli Schleifer Date: Fri, 18 Sep 2026 08:56:26 +0000 Subject: [PATCH 14/14] Label the gated sections as beta in the sidebar Co-Authored-By: Claude Opus 5 (1M context) --- docs.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs.json b/docs.json index a3da9158..8843e9c4 100644 --- a/docs.json +++ b/docs.json @@ -42,13 +42,13 @@ "pages": [ "index", { - "group": "Dynamic CI", + "group": "Dynamic CI (beta)", "pages": [ "dynamic-ci/index" ] }, { - "group": "Workspaces", + "group": "Workspaces (beta)", "pages": [ { "group": "Overview",