Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# 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` **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/<project>/<path>`, which is unsigned,
so CloudFront answers `403 MissingKey`: the sidebar `<img>` loads nothing and
the group header shows a blank gap where the icon belongs. This is specific to
nav icons — anything inside a page, including a `<Card icon="...">`, 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 `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
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 `<img>` and defeats nothing, but the literal color is what the
stylesheet's comment documents.
- **`<Columns cols={n}>` 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
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.
5 changes: 5 additions & 0 deletions assets/icons/product-flaky-tests.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions ci/index.mdx
Original file line number Diff line number Diff line change
@@ -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
Expand Down
139 changes: 72 additions & 67 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,60 @@
{
"tab": "Overview",
"pages": [
"index"
"index",
{
"group": "Dynamic CI (beta)",
"pages": [
"dynamic-ci/index"
]
},
{
"group": "Workspaces (beta)",
"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"
]
}
]
}
]
},
{
Expand Down Expand Up @@ -164,7 +217,7 @@
},
"merge-queue/changelog"
],
"icon": "/assets/icons/product-merge-queue.svg"
"icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzcxOTJfNzM0OSkiPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTMuODc3MjMgNS4yOTE5QzQuNjU4NTMgNS4yOTE5IDUuMjkxOSA0LjY1ODUzIDUuMjkxOSAzLjg3NzIzQzUuMjkxOSAzLjA5NTk0IDQuNjU4NTMgMi40NjI1NyAzLjg3NzIzIDIuNDYyNTdDMy4wOTU5NCAyLjQ2MjU3IDIuNDYyNTcgMy4wOTU5NCAyLjQ2MjU3IDMuODc3MjNDMi40NjI1NyA0LjY1ODUzIDMuMDk1OTQgNS4yOTE5IDMuODc3MjMgNS4yOTE5Wk0zLjg3NzIzIDYuNTA0NDdDNS4zMjgyMiA2LjUwNDQ3IDYuNTA0NDcgNS4zMjgyMiA2LjUwNDQ3IDMuODc3MjNDNi41MDQ0NyAyLjQyNjI1IDUuMzI4MjIgMS4yNSAzLjg3NzIzIDEuMjVDMi40MjYyNSAxLjI1IDEuMjUgMi40MjYyNSAxLjI1IDMuODc3MjNDMS4yNSA1LjMyODIyIDIuNDI2MjUgNi41MDQ0NyAzLjg3NzIzIDYuNTA0NDdaIiBmaWxsPSIjMDgwOTBEIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMy4zNTE5MSAxNC4wMjI0TDMuMzUxOTEgNS40NTM1OEw0LjU2NDQ4IDUuNDUzNThMNC41NjQ0OCAxNC4wMjI0QzQuNTY0NDggMTQuMzU3MiA0LjI5MzA0IDE0LjYyODcgMy45NTgyIDE0LjYyODdDMy42MjMzNiAxNC42Mjg3IDMuMzUxOTEgMTQuMzU3MiAzLjM1MTkxIDE0LjAyMjRaIiBmaWxsPSIjMDgwOTBEIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTIuMTIyNyAxMy4zNzU4QzEyLjkwNCAxMy4zNzU4IDEzLjUzNzQgMTIuNzQyNCAxMy41Mzc0IDExLjk2MTFDMTMuNTM3NCAxMS4xNzk4IDEyLjkwNCAxMC41NDY0IDEyLjEyMjcgMTAuNTQ2NEMxMS4zNDE0IDEwLjU0NjQgMTAuNzA4MSAxMS4xNzk4IDEwLjcwODEgMTEuOTYxMUMxMC43MDgxIDEyLjc0MjQgMTEuMzQxNCAxMy4zNzU4IDEyLjEyMjcgMTMuMzc1OFpNMTIuMTIyNyAxNC41ODgzQzEzLjU3MzcgMTQuNTg4MyAxNC43NSAxMy40MTIxIDE0Ljc1IDExLjk2MTFDMTQuNzUgMTAuNTEwMSAxMy41NzM3IDkuMzMzODYgMTIuMTIyNyA5LjMzMzg2QzEwLjY3MTcgOS4zMzM4NiA5LjQ5NTQ4IDEwLjUxMDEgOS40OTU0OCAxMS45NjExQzkuNDk1NDggMTMuNDEyMSAxMC42NzE3IDE0LjU4ODMgMTIuMTIyNyAxNC41ODgzWiIgZmlsbD0iIzA4MDkwRCIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTkuNjk3NiAyLjc0NTUxQzExLjM3MTggMi43NDU1MSAxMi43MjkgNC4xMDI3MyAxMi43MjkgNS43NzY5NEwxMi43MjkgOS4zMzM4MUMxMi43MjkgOS42Njg2NSAxMi40NTc2IDkuOTQwMDkgMTIuMTIyNyA5Ljk0MDA5QzExLjc4NzkgOS45NDAwOSAxMS41MTY0IDkuNjY4NjUgMTEuNTE2NCA5LjMzMzgxTDExLjUxNjQgNS43NzY5NEMxMS41MTY0IDQuNzcyNDEgMTAuNzAyMSAzLjk1ODA4IDkuNjk3NiAzLjk1ODA4TDguNDQ0NjEgMy45NTgwOEM4LjEwOTc3IDMuOTU4MDggNy44MzgzMiAzLjY4NjY0IDcuODM4MzIgMy4zNTE4QzcuODM4MzIgMy4wMTY5NiA4LjEwOTc3IDIuNzQ1NTEgOC40NDQ2MSAyLjc0NTUxTDkuNjk3NiAyLjc0NTUxWiIgZmlsbD0iIzA4MDkwRCIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzcxOTJfNzM0OSI+CjxyZWN0IHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K"
}
]
},
Expand Down Expand Up @@ -390,78 +443,30 @@
]
},
"flaky-tests/changelog"
]
],
"icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+CjxwYXRoIGQ9Ik0xNCAydjZhMiAyIDAgMCAwIC4yNDUuOTZsNS41MSAxMC4wOEEyIDIgMCAwIDEgMTggMjJINmEyIDIgMCAwIDEtMS43NTUtMi45Nmw1LjUxLTEwLjA4QTIgMiAwIDAgMCAxMCA4VjIiLz4KPHBhdGggZD0iTTYuNDUzIDE1aDExLjA5NCIvPgo8cGF0aCBkPSJNOC41IDJoNyIvPgo8L3N2Zz4K"
}
],
"icon": "flask"
},
{
"tab": "CI",
"pages": [
"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",
"pages": [
"dynamic-ci/index"
]
},
{
"tab": "Workspaces",
"tab": "CI",
"groups": [
{
"group": "Overview",
"pages": [
"trunk-workspaces/index",
"trunk-workspaces/getting-started"
]
},
{
"group": "Using workspaces",
"group": "CI",
"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"
]
"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": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEzLjMwNjYgMTMuMzAzOUM4LjIwNjc0IDE4LjQ0NDUgLTAuNDgzMzQ0IDEzLjk5MDYgMC41OTEwMTcgNi43ODk3MUMxLjM2NjE5IDEuNjAxNSA3LjQxMTE3IC0xLjIxMzYgMTEuODg1NCAxLjU2NzVDMTMuNTY0OSAwLjY0OTUzMSAxNS4zNTMzIDIuNDE3NDcgMTQuNDE0OSA0LjA5NzAxQzE2LjIyMzYgNy4wMjA5IDE1LjcxMzcgMTAuODkgMTMuMzEzNCAxMy4zMTA3TDEzLjMwNjYgMTMuMzAzOVpNMTMuNjk0MSA0Ljc5NzM4QzEyLjM1NDYgNS40MTYxNiAxMC44NzkgNC40NDM3OSAxMS4wMTUgMi45NDc4NUMxMS4wMzU0IDIuNzMwMjYgMTEuMTIzOCAyLjUyNjI2IDExLjE3MTQgMi4zMjIyN0M4LjA0MzU0IDAuNTEzNTM2IDQuMDM4NDkgMS43MjM4OSAyLjMxODE1IDQuODQ0OThDMC4yMzc0MyA4LjYxMjA0IDIuMzU4OTUgMTMuMzY1MSA2LjUyNzIgMTQuMzMwNkMxMS40MzY2IDE1LjQ2NjIgMTUuNjY2MSAxMC44NzY0IDE0LjIxNzcgNi4wNjIxM0MxNC4wODg1IDUuNjIwMTUgMTMuODc3NyA1LjIxODk2IDEzLjcwMDkgNC43OTczOEgxMy42OTQxWk0xMy42NjY5IDMuMTQ1MDRDMTMuNjY2OSAyLjY4OTQ2IDEzLjI5OTggMi4zMTU0NyAxMi44Mzc0IDIuMzE1NDdDMTIuMzc1IDIuMzE1NDcgMTIuMDA3OCAyLjY4MjY2IDEyLjAwNzggMy4xNDUwNEMxMi4wMDc4IDMuNjA3NDIgMTIuMzc1IDMuOTc0NjEgMTIuODM3NCAzLjk3NDYxQzEzLjI5OTggMy45NzQ2MSAxMy42NjY5IDMuNjA3NDIgMTMuNjY2OSAzLjE0NTA0WiIgZmlsbD0iYmxhY2siLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik04LjAwMDAyIDkuMjQ0NDZDOC42ODczIDkuMjQ0NDYgOS4yNDQ0NiA4LjY4NzMgOS4yNDQ0NiA4LjAwMDAyQzkuMjQ0NDYgNy4zMTI3MyA4LjY4NzMgNi43NTU1NyA4LjAwMDAyIDYuNzU1NTdDNy4zMTI3MyA2Ljc1NTU3IDYuNzU1NTcgNy4zMTI3MyA2Ljc1NTU3IDguMDAwMDJDNi43NTU1NyA4LjY4NzMgNy4zMTI3MyA5LjI0NDQ2IDguMDAwMDIgOS4yNDQ0NlpNOC4wMDAwMiAxMC4zMTExQzkuMjc2NDEgMTAuMzExMSAxMC4zMTExIDkuMjc2NDEgMTAuMzExMSA4LjAwMDAyQzEwLjMxMTEgNi43MjM2MiA5LjI3NjQxIDUuNjg4OSA4LjAwMDAyIDUuNjg4OUM2LjcyMzYyIDUuNjg4OSA1LjY4ODkgNi43MjM2MiA1LjY4ODkgOC4wMDAwMkM1LjY4ODkgOS4yNzY0MSA2LjcyMzYyIDEwLjMxMTEgOC4wMDAwMiAxMC4zMTExWiIgZmlsbD0iIzA4MDkwRCIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTUuNzM1NDkgMTIuMjc5NkM1LjczNTQ5IDEyLjU3NDIgNS40OTY3MSAxMi44MTMgNS4yMDIxNiAxMi44MTNMMy4xMjA3NyAxMi44MTNMMy4xMjA3NyAxNC44OTQ0QzMuMTIwNzcgMTUuMTg4OSAyLjg4MTk5IDE1LjQyNzcgMi41ODc0MyAxNS40Mjc3QzIuMjkyODggMTUuNDI3NyAyLjA1NDEgMTUuMTg4OSAyLjA1NDEgMTQuODk0NEwyLjA1NDEgMTEuNzQ2M0w1LjIwMjE2IDExLjc0NjNDNS40OTY3MSAxMS43NDYzIDUuNzM1NDkgMTEuOTg1MSA1LjczNTQ5IDEyLjI3OTZaIiBmaWxsPSIjMDgwOTBEIi8+Cjwvc3ZnPgo="
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion flaky-tests/get-started/ci-providers/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Flaky Test support for source control providers like GitLab and BitBucket is sti

## Quickstart

<Columns cols={3}>
<Columns cols={4}>
<Card
title="Atlassian Bamboo"
href="./atlassian-bamboo"
Expand Down
2 changes: 1 addition & 1 deletion flaky-tests/get-started/frameworks/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<Columns cols={3}>
<Columns cols={4}>
<Card
title="Android"
href="./android"
Expand Down
2 changes: 1 addition & 1 deletion index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
103 changes: 103 additions & 0 deletions scripts/embed-nav-icons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/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/<project>/<path>`,
and that proxy URL is unsigned: CloudFront answers 403 MissingKey, so the
`<img class="sidebar-group-icon">` 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 same applies to a page's `icon:` frontmatter, which Mintlify renders
in the navigation. Icons used in page *content* — a `<Card icon="...">`,
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 files:

python3 scripts/embed-nav-icons.py

Drop an entry from GROUP_ICONS / PAGE_ICONS (and restore the plain path)
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"

# 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 GROUP_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
embed_page_icons()
print(
f"inlined {len(GROUP_ICONS)} group icons into docs.json "
f"and {len(PAGE_ICONS)} page icons"
)


if __name__ == "__main__":
main()
Loading