diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..bba9260b --- /dev/null +++ b/CLAUDE.md @@ -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//`, 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 — 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 `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 `` 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. + +## 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. diff --git a/assets/icons/product-flaky-tests.svg b/assets/icons/product-flaky-tests.svg new file mode 100644 index 00000000..d16cd915 --- /dev/null +++ b/assets/icons/product-flaky-tests.svg @@ -0,0 +1,5 @@ + + + + + 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 diff --git a/docs.json b/docs.json index 6642a924..8843e9c4 100644 --- a/docs.json +++ b/docs.json @@ -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" + ] + } + ] + } ] }, { @@ -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" } ] }, @@ -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=" } ] }, diff --git a/flaky-tests/get-started/ci-providers/index.mdx b/flaky-tests/get-started/ci-providers/index.mdx index d7503e31..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 - + + /`, +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 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 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()