docs(api-reference): add v2 service landing pages - #2833
Open
aaronmichaelacosta wants to merge 27 commits into
Open
aaronmichaelacosta wants to merge 27 commits into
aaronmichaelacosta wants to merge 27 commits into
Conversation
aaronmichaelacosta
force-pushed
the
APPEX-1609/v2-service-pages
branch
2 times, most recently
from
September 4, 2026 22:26
94680be to
f7b4089
Compare
aaronmichaelacosta
force-pushed
the
APPEX-956/deprecation-policy
branch
from
September 4, 2026 22:33
0f6b32a to
9400249
Compare
aaronmichaelacosta
force-pushed
the
APPEX-1609/v2-service-pages
branch
from
September 4, 2026 22:33
f7b4089 to
1bb0023
Compare
2 tasks
Contributor
Author
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
1 task
aaronmichaelacosta
force-pushed
the
APPEX-956/deprecation-policy
branch
from
September 8, 2026 17:17
9400249 to
691f1ba
Compare
aaronmichaelacosta
force-pushed
the
APPEX-1609/v2-service-pages
branch
2 times, most recently
from
September 8, 2026 17:27
84d07fd to
310ed6d
Compare
aaronmichaelacosta
force-pushed
the
APPEX-956/deprecation-policy
branch
from
September 8, 2026 17:27
691f1ba to
547a2d0
Compare
aaronmichaelacosta
changed the base branch from
APPEX-956/deprecation-policy
to
graphite-base/2833
September 9, 2026 17:26
aaronmichaelacosta
force-pushed
the
APPEX-1609/v2-service-pages
branch
from
September 9, 2026 17:27
d9aad15 to
4d634d9
Compare
aaronmichaelacosta
changed the base branch from
graphite-base/2833
to
APPEX-956/deprecation-policy
September 9, 2026 17:27
Add a per-version API changelog (api-reference/v1/Changelog, api-reference/v2/Changelog) generated from the OpenAPI specs' git history with oasdiff. One <Update> block per day the spec changed, rendered as Stripe-style tables: a colored change-verb chip, the oasdiff description, and the endpoint as a method Badge linking to its generated reference page. Repetitive changes (enum values, format flips) are coalesced into single rows, and rss: true gives subscribers a feed per API version. The page fully regenerates on every run, so the backfill is retroactive (v1 to 2026-05-28, v2 to 2026-07-14) and self-correcting. Endpoint links are built only from the current spec, so removed endpoints render unlinked instead of 404ing. The update-openapi-specs cron now installs a checksum-pinned oasdiff, regenerates both pages after the mirror and sort steps, and ships them in the same signed-commit PR as the spec change.
Replace the split method-badge + underlined path link with a single Badge pill containing method and path, wrapped in a raw anchor (a markdown link would keep Mintlify's bottom-border underline). <wbr/> elements after each path segment let long paths wrap inside the pill instead of overflowing the table, without adding characters that survive copy-paste; path braces are entity-escaped for JSX. Tables gain an api-changelog-table wrapper div styled in styles.css: Mintlify puts min-width: 150px on every td, which held the Change column open well past its chip.
Rewrite oasdiff's JSON Schema property paths into the field reference a consumer actually writes, and stop filing routine enum additions under a breaking-change heading. - Drop `allOf[#/components/schemas/protos.*]` segments. These specs are protobuf-generated and wrap every described $ref in a single-element allOf (the OpenAPI 3.0 workaround for $ref siblings being ignored), so the segment is composition bookkeeping that the wire payload has no level for. Removing it is more accurate as well as shorter, and it was the only place an internal proto package name reached the page. - Fold an `items` segment into its parent as `[]` and join with `.`, so `automations/items/filters/allOf[...]/conditions/items/type` renders as `automations[].filters.conditions[].type`. - Demote `response-property-enum-value-added` to INFO. oasdiff rates it WARN on the theory that a consumer may switch exhaustively over the enum, but these enums come from protobuf, where they are open by construction; 46 routine additions were burying the actual removals. Field-name case is deliberately untouched: the specs emit both `deployment_id` and `deploymentId` depending on the service, and the changelog has to name the key the endpoint actually accepts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pages already carried `rss: true`, but Mintlify would have produced a
poor feed from them:
- It emits one entry per Markdown heading inside an `<Update>`, so our
severity sections turned 3 dated entries into 6 for v1 and 10 into 12
for v2 -- titled "Changes" (9x), "Breaking changes", "Potentially
breaking changes", undated and repeated across every day.
- Feed entries "contain pure Markdown only ... exclude components, code,
and HTML elements", and our rows are a JSX table of Badges and links, so
subscribers would have received near-empty items.
Emit an explicit `rss={{ title, description }}` per `<Update>`, which is
what Mintlify recommends for updates containing excluded content. That
pins the feed to one dated item per day with a prose summary, and
decouples it from the heading structure -- otherwise a re-level that adds
or drops a section heading counts as "modifying headings inside an
existing Update" and republishes the entry to subscribers.
Also add `--rss-url`, which renders subscribe instructions on the page.
Mintlify only emits a `<link rel="alternate">` autodiscovery tag for
`rss: true` pages and no visible affordance, so the page has to say it.
Fixes a pre-existing summary bug along the way: the noun was attached only
via the "other" bucket, so a breaking-only day read "1 breaking" instead
of "1 breaking change". The count logic is now shared between the on-page
summary and the RSS description so the two cannot drift.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The caveat duplicates what the v2 Introduction page already states, and on a page whose whole purpose is to label breaking changes individually it read as a blanket disclaimer over entries that are mostly additive. The generator keeps its --note flag; the v2 invocation just stops passing one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mintlify "publishes entries when you add new Update components and when you modify headings inside of existing Update components". Our severity sections were Markdown headings, and which of them a given day has depends on the level mapping -- so demoting a check (LEVEL_OVERRIDES) or bumping oasdiff removes or adds a heading inside months-old entries and re-notifies every subscriber about them. Today's enum demotion would have done exactly that. Render the section labels in bold instead. With no headings inside an <Update>, the only remaining publish trigger is adding a new one. The rss= prop already supplies the feed title, so nothing depended on these being headings, and their anchors were colliding anyway -- every day emitted the same #changes. Verified on the PR preview deployment beforehand: an edit outside the Update blocks left all 10 v2 items' guid and pubDate untouched, confirming both are per-block rather than per-file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Regrouping the public API into its new services re-tags roughly 200 endpoints without touching a path, a request, or a response. oasdiff reports each one twice -- `api-tag-added` and `api-tag-removed` -- so the reorganisation would land as ~400 rows on a single date. Measured, not guessed: re-tagging the 6 MiscService operations produced exactly 12 changes. A day of nothing but re-tagging would therefore create a new <Update>, and a new <Update> is the one thing that still publishes to RSS subscribers. The feed's largest entry ever would be an internal filing change, and any real change shipped the same day would be buried under it. Drop both checks. A tag decides which nav group an endpoint renders under; oasdiff itself rates both INFO. Not entirely free -- some SDK generators namespace by tag, so a consumer generating a client from the published spec could see a method move class -- but it is not a change to the HTTP contract, and that caveat is recorded next to the exclusion. Real changes shipped alongside a re-tag still appear; only the tag rows are dropped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ilter
Selecting "Breaking" in the side panel did nothing on either changelog
page: the chip lit up and the URL gained ?tags=Breaking, but all 10 v2
(and all 3 v1) updates stayed on screen.
The cause is Mintlify's <Update> visibility predicate, which reduces to
"hide me only if I have tags and none of them is selected":
if (!filters.length) return true;
const active = filters.filter(f => f.active);
return !(active.length !== 0 && tags?.length)
|| tags.some(t => active.some(f => f.tag === t));
For an <Update> with no tags= at all, `tags` is undefined, `tags?.length`
is falsy, and the first clause short-circuits to true -- it is visible no
matter what is selected. We tagged only the days containing a level-3
change, so the other days had no tags to fail to match and nothing could
ever be hidden. Confirmed with a two-<Update> scratch page: one tagged +
one untagged filters nothing, while tagging both hides the non-match.
So give every <Update> a tag naming the day's highest severity --
Breaking, Potentially breaking, or Non-breaking. The chips are OR'd and
individually toggleable, so "show me anything that might break me" is
Breaking + Potentially breaking, and each severity is now also visible at
a glance under its date.
This rules out the structural suspects, incidentally: the scratch repro
had no table, no <div className="api-changelog-table">, no rss= prop and
no headings, and still failed -- so none of those, and not d4157be's
switch from headings to bold, had anything to do with it.
The regenerated pages change only <Update> opening lines, adding an
attribute. That is neither a new <Update> nor a heading edit inside an
existing one, so it does not trip the feed republish triggers d4157be
documented.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two presentation fixes for rows that span several endpoints: - The endpoint cell stacks its pills with `<br/>`, which gave them no leading and read as one solid block. Adds a little vertical rhythm, scoped to the generated table so no badge elsewhere is affected. - The "and N more" hovercard lists full endpoint paths, and a path is one unbreakable token. Mintlify caps the popup at 16rem, so the text ran ~190px past the rounded background instead of wrapping inside it. Allows a break mid-path and widens the cap so a path takes two lines, not five. Verified at 1440px and 390px: no overflow, still bounded by the viewport. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
oasdiff reports per endpoint and per property, so one change to a shared type fanned out into a row per place it surfaced. Adding a single enum value to `TriggerSource` produced six near-identical rows -- the same sentence, once for each automations endpoint, split again across the request and response copies of the property. The reader had to diff six rows by eye to work out they were one change. Two folds, both lossless: - `merge_across_endpoints` folds rows whose wording is *identical* into one carrying an `endpoints` list. Only identical wording merges, so nothing is summarised away. Endpoint-scoped checks (`endpoint-*`, `api-*`) are exempt: two endpoints added on the same day both read "endpoint added", and folding those would hide the endpoints. - `merge_enum_additions` then folds the request and response copies of one property together and lists the values once, normalising the wrapper path to its leaf. The endpoint cell shows the first three and puts the rest behind an "and N more" hovercard, so a broad change stays a readable height without making any endpoint unreachable. RSS is untouched: the feed description is built from the raw, unmerged changes, so its endpoint count stays exact and nothing is truncated there. 193 rows to 112 across the two changelogs; the Aug 20 v2 entry goes from 10 rows to 4, and `TRIGGER_SOURCE_PATCH_READY` from 6 rows to 1. Both files regenerate byte-identical from the committed specs with oasdiff 1.28.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The "and N more" hovercard ran its paths together with commas. Paths are
long enough to wrap inside a 28rem popup, so the separator landed mid-line
and there was no way to see where one path ended and the next began -- five
endpoints read as one run-on string.
One bullet per line instead. The tip is now passed as a JSX expression
rather than an attribute string so the newlines survive, with `json.dumps`
doing the escaping. That also drops the entity encoding of `{` and `}`:
they open an expression as bare JSX text, but are ordinary characters
inside a string literal, and encoding them showed the reader `{`
instead of `{`. `white-space: pre-line` renders the breaks while still
collapsing spaces and wrapping long paths, which `pre` would not.
Verified in the browser: bullets on separate lines, braces intact, no
overflow. Both changelogs regenerate byte-identical from the committed
specs with oasdiff 1.28.0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two problems, both found by running the deprecation lifecycle through the pinned oasdiff (v1.28.0) rather than reasoning about it. **A deprecation announcement was filed as "Non-breaking."** oasdiff rates `endpoint-deprecated` INFO, which put it under "Changes" with that tag. The announcement is the entire advance warning a caller gets, and "Non-breaking" hides it from the one reader who most needs it: someone filtering the changelog for what is going to break them. Re-levelled to potentially breaking -- true on the day it ships, where "Breaking" would cry wolf. This is not hypothetical; three v1 policies endpoints are already deprecated, and that day moves from "6 changes / Non-breaking" to "3 potentially breaking · 3 other changes". **A removal done right produced no entry at all.** oasdiff reports a removal only when something went wrong with it -- gone with no deprecation, or gone before its published sunset date. Retire an endpoint exactly as the policy prescribes and `oasdiff changelog` says "No changes to report", so the day the endpoint stopped answering was the one day missing from the changelog. The changelog recorded only the removals we botched. There is no oasdiff flag for that case; the check does not exist upstream. So `sunset_removals` diffs the two snapshots' operation sets directly and synthesizes the entry, staying quiet whenever oasdiff already had something to say so a removal never lands twice. Both need `x-sunset` in the published spec to work, which protobuf-tools#202 now emits -- `x-deprecation` alone is invisible to oasdiff. Not doing the third thing considered here: `--deprecation-days-stable 180` would re-check every historical deprecation against *today*, so regenerating the page would retroactively flag long-served windows as too short. The notice windows are enforced at the proto level by protobuf-tools#203, which is the right layer -- it fails the change before it ships rather than narrating it afterwards. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds a Deprecation Policy page alongside the changelog in both API versions, from the customer-facing half of the APPEX-956 policy doc. The internal appendix (notice channels, the 410-vs-301 decision record, open gaps) is not published. The body lives in a snippet imported by both version pages. Terms-of-Use duplicates its one sentence per version, but 30 lines of policy would drift, and the repo already shares longer content this way (see snippets/metrics.mdx). One content change from the source doc: it was written before we decided to publish a changelog, so "Finding out what is deprecated today" had no push channel -- only the reference, response headers, and support. It now leads with the changelog and its RSS feed, which records every deprecation tagged Deprecated on the day it ships. Deliberately not carried over: the in-app banner and admin email named in the internal appendix. Neither exists yet (appendix gap 4), so publishing them would commit us to channels we cannot serve. The invented /api/migrations/<resource> URL (gap 5) appears only in the appendix and is not published either. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Stable endpoints had two clocks: 6 months for a breaking change, 3 months for removing an already-deprecated optional field. The short one does not survive contact with how the clock actually starts. Per styleguide 13.2 the clock only runs once the notice is public -- `Deprecation` and `Sunset` headers live, spec marked. A field marked deprecated with no sunset date has therefore started no clock at all, so the 3 months would be the entire notice a customer gets, not a follow-on to time already served. That is half the window for something oasdiff rates `response-optional-property-removed` at WARN, potentially breaking: "optional" says the server may omit the field, not that nobody reads it. One window for stable, whatever the change. Simpler to state, simpler to honour, and it errs long on a public commitment. Styleguide 13.1 transcribes this table and still carries the 3-month row; that needs the same edit in semgrep-app. Its link to api-deprecation-policy.md is also dangling -- that file does not exist in that repo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Takes all twelve suggestions from the review on #2832: the intro and summary sentences, the maturity table's header and all three rows, the undocumented-endpoints paragraph, the security and legal exception (now two paragraphs), the "Identify deprecated endpoints" heading, and the four-bullet list in its label-prefixed form. Two of the twelve needed a judgement call rather than a substitution: - One was a question -- "Do we add callouts in the API docs? If so, this info would benefit from being in a callout box." We do; <Note> appears 152 times in this repo. The summary paragraph is now a <Note>, using the suggested wording. - The frontmatter description suggestion was left on the v1 page, but v1 and v2 are deliberate duplicates importing the same snippet, so applying it to one only would have made them diverge. Both are updated. The undocumented-endpoints suggestion carried a double space after its first period, which is dropped as a typo rather than reproduced. Style and voice only. Nothing here changes what the policy commits to, including the six-month window. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Doubles the beta notice period from 30 days to 60. 30 days is a short window for a customer to notice a deprecation, schedule the work, and ship it -- particularly for teams on a monthly release train, where it can amount to a single opportunity to react. Stable stays at 6 months and experimental still promises nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Connor's review, plus two accuracy fixes the 410 write-up turned up.
Reviewer-requested:
- Move the maturity-badge sentence above the table, so the question the
table raises ("how do I know which one an endpoint is?") is answered
before it is asked rather than after.
- State stable's window as 180 days. Beta was already in days, so the
column no longer mixes units, and a month-length assumption cannot
change what the commitment means.
- Rewrite the stable row, which said only "covered by this policy". Beta
is covered too, so the cell distinguished nothing.
- Soften the exception. It claimed to be "the only exception" and named
a legal obligation specifically; both are more absolute than we can
actually promise. Same care, less cornering.
- Restore what counts as a breaking change, from the APPEX-956 draft.
Split into what will not change without notice and what may change at
any time, because the second half is what a caller has to build for --
tolerate new fields, do not match on error strings.
APPEX-956 wanted that definition to be a link to the oasdiff ruleset in
APPEX-959, so it would be mechanical rather than prose. APPEX-959 is
cancelled, so prose is what is left.
Accuracy, found while writing the sunset section against the
implementation in semgrep-app#31680:
- The `Link` header is the machine-readable pointer, and it is omitted
when a removal has no replacement. "Returns 410 with a machine-readable
pointer to its replacement" promised it unconditionally.
- Document the sunset response itself: headers, body, and which parts are
stable enough to parse. `error` wording is not.
semgrep-app#31680 is still a draft, so this must not publish before it
ships -- the page would describe a response nothing returns yet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two claims on this page were ahead of the generator, and both are now true rather than aspirational. "Tagged as Deprecated" described a chip in the Change column as though it were a filter. The filter tag was "Non-breaking", so a reader who followed this page's advice -- subscribe, watch for what will break you -- was told to filter for exactly the thing that hid the notice. The generator now files deprecations as potentially breaking, so say both: what the row is marked, and which filter it survives. Also say that the removal itself lands in the changelog. It did not previously; a removal that served its full notice window produced no entry at all, which made "the changelog records every deprecation" true and "the changelog tells you when the endpoint went away" false. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Twelve landing pages under the v2 dropdown, one per service in the Stable API proposal, in a new "Services" nav group above the generated Endpoints group. Each describes what the service covers and where its endpoints will appear. They stay in v2 rather than a new unversioned section: customers already know the v1/v2 split, so v1 drains and disappears instead of a third concept arriving. Every page is empty of endpoints today, by design. Endpoints arrive one at a time as each is defined in its new service, migrated, and promoted, so the pages fill up as the migration lands. The placeholder block is what a generated endpoint list will replace once there is real data to generate from -- building it now would duplicate the slug rules in generate_api_changelog.py before we know the shape of what it renders. Also drops the blanket-experimental framing, which the new services contradict: - the dropdown was labelled "v2 (Experimental)", which claims the whole API is experimental while the page inside it explains per-endpoint maturity badges and the deprecation policy promises per-endpoint notice - the Introduction opened with "The v2 API is under active development", same problem; the rest of that sentence is kept, since not deprecating v1 until v2 covers it is still true Not addressed here: the Introduction's maturity list still says stable endpoints get "no breaking changes", while the deprecation policy says they get six months' notice before one. Two different public promises on adjacent pages, and reconciling them is an editorial call, not a scaffolding change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The v2 Introduction told customers "No breaking changes will be made to this API" for stable endpoints. The deprecation policy on the next page over says stable endpoints get six months' notice *before a breaking change* -- which concedes they happen. Two different public promises, and the weaker one is the honest one. Point the bullet at the policy rather than restating it, so there is one customer-facing definition of what each maturity level owes you (which is what the policy was written to be). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mintlify builds an endpoint's page URL from its first tag: /api-reference/v2/<tag-slug>/<summary-slug> So regrouping the public API into its new services moves every affected page, even though the path, request and response are untouched. Today's /api-reference/v2/miscservice/check-container-health becomes /api-reference/v2/deployments/... and the old URL 404s for anyone who bookmarked it, linked it, or reached it from search. Derive the redirects instead of hand-maintaining ~200 of them: diff the committed spec against the working tree, compute both URLs with the same slug rules the changelog already uses, and emit one redirect per difference. Runs in the spec-sync workflow, so a tag change and its redirect land in the same PR. docs.json joins add-paths, or the redirects would be generated and then left behind. Chains collapse rather than accumulate: an endpoint that moves A -> B and later B -> C leaves A -> C, not A -> B pointing at a page that no longer exists. docs.json is patched textually. A json round-trip is not byte-identical against the checked-in formatting, so it would bury a two-line change in a whole-file diff. Slug rules are imported from generate_api_changelog rather than copied -- they were reverse-engineered against the rendered site, and two independent copies would drift into broken links. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Services and endpoints were two separate things in the sidebar: a "Services" group of twelve descriptions, and an autogenerated "Endpoints" group of per-tag subgroups. Clicking a service in the second one opened whichever endpoint happened to sort first. Mintlify supports a `root` page on a nav group -- "clicking the group title in the sidebar navigation opens the root page" -- but only on an explicitly declared group, not an autogenerated one. So the endpoint list has to be enumerated, which is why this generates it rather than asking anyone to maintain 226 entries by hand. One group per tag now, each with its landing page as `root` and its endpoints as pages. The separate "Services" group is gone; its pages are the roots. A tag with no landing page still gets a working group, so this degrades gracefully for the services that do not have one yet. Landing pages bind by slugged display name, not by tag name, so they attach both before and after the endpoints are regrouped in semgrep-app. Today's spec still carries the old service tags and yields 48 groups, 7 of which already find their page; once the re-tagging deploys that collapses to twelve, all with pages, without touching this code. Runs in the spec-sync workflow after the sort step, whose ordering it inherits, so a newly published endpoint lands in the nav in the same PR that publishes it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The first version declared each service group at the dropdown's top level. Mintlify renders a top-level group as a flat section heading, so the services stopped being collapsible -- every endpoint of every service was expanded at once. The collapsibility came from the autogenerated `Endpoints` group nesting them one level down, and removing that group removed the nesting with it. Nest them inside a `Services` group instead. 27 other groups in this docs.json already nest this way, so this follows the file's own pattern rather than inventing one. Adds a regression test asserting the groups land nested rather than top-level, since the symptom is only visible in the rendered sidebar and `mintlify validate` passes either way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The landing pages carried a hardcoded "This service has no published endpoints yet", which was wrong the moment the first endpoint was filed. Each page now imports a generated table of its endpoints: method badge, summary linked to the endpoint's reference page, and the lead sentence of its description. Generated per landing page rather than per spec tag, so a tag with no page produces nothing and a page whose service is still empty gets an accurate empty state -- generated, so it cannot go stale, which is what the hardcoded sentence could not manage. Prose on the page stays hand-written. Endpoint URLs come from generate_api_changelog's slug rules rather than a second copy: those were reverse-engineered against the rendered site, and two copies would drift into broken links. Also fixes a URL regression this work exposed. The previous commit replaced the autogenerated endpoint group, which carried `directory: api-reference/v2`, with explicit groups that did not -- so Mintlify served every endpoint from /api-reference/<tag>/ instead of /api-reference/<version>/<tag>/. That silently moved every existing endpoint URL and would have collided v1 with v2. `mintlify validate` passes either way; the only thing that caught it was fetching the URLs, where all 202 generated links 404'd. The nav generator now emits the object form of `openapi` with `directory`. Verified against the re-tagged spec from semgrep-app on a local dev server: all 202 links 200, every landing page renders, and the tables show linked summaries with descriptions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
38 of 43 entries under a service carry a maturity pill. In the narrow nav rail the pill wraps onto its own line, roughly doubling each entry's height and making a service look far larger than it is. Maturity is already stated on the endpoint page and defined in the deprecation policy, so hide it in the rail -- but keep `deprecated`, which is rare and worth interrupting for. Hides the pill's wrapper rather than the pill: the wrapper carries `h-[1lh]` inside a flex-wrap row, so hiding only the pill left a line-tall empty box that wrapped below any two-line title, showing up as a stray gap under exactly those entries. Also turns off Mintlify's `hyphens: auto` on nav titles, which was breaking words mid-syllable -- "Preview a detection poli-cy apply". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The step ran generate_api_nav.py over v1 as well as v2, which would have regrouped the v1 dropdown from its flat "Endpoints" list into per-service groups on the next sync. Nothing asked for that: v1 has no service landing pages, so the groups would open an arbitrary endpoint, and regrouping moves every v1 endpoint's page URL -- Mintlify builds it from the first tag. It also meant the committed docs.json did not match what the workflow produced, so the next run would have opened an unreviewed nav change. With the v1 call gone, running the step reproduces the committed file exactly. The redirect step still covers v1. It is a no-op while v1 keeps its flat group, and it is what would catch the URL moves if v1 is ever regrouped deliberately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
aaronmichaelacosta
force-pushed
the
APPEX-1609/v2-service-pages
branch
from
September 21, 2026 17:10
4d634d9 to
041f108
Compare
A job is the parent category of both a scan and an Agentic Workflows job, so the two land as one service rather than two that would have to be merged later. Eleven service pages instead of twelve. The snippet and the nav entry are regenerated output: jobs-endpoints.mdx shows the empty state until the spec carries a Jobs tag, and the Scans nav group loses its root now that no page backs it. Pairs with the semgrep-app directory merge on APPEX-1609/new-api-v2-directory-structure. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
aaronmichaelacosta
force-pushed
the
APPEX-956/deprecation-policy
branch
from
September 22, 2026 21:52
37ee39b to
adb9a7d
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What this adds
Eleven service landing pages under the v2 API — one per service in the Stable API proposal — each opening as the root of its own nav group, above that service's endpoints.
Each page says what the service covers and lists its endpoints in a generated table. The table and the nav group are both rendered from the spec in the sync workflow, so a service with nothing published yet shows a generated empty state rather than a placeholder that goes stale, and pages fill up as the migration lands.
They live under v2 rather than in a new unversioned section, per the call in [this thread] — customers already know the v1/v2 split, so v1 drains and disappears instead of a third concept showing up.
Scans and Agentic Workflows are one
Jobsservice, not two: a job is the parent category of both a scan and a workflow job, so merging them now avoids merging them later. That's why it's eleven pages and not twelve.Also drops the blanket "experimental" framing
The new services contradict it, in two places:
Merge this before the semgrep-app side reaches prod
Mintlify builds an endpoint's page URL from its first tag, so the regrouping in semgrep-app moves 203 of the 206 v2 endpoint pages.
scripts/generate_openapi_redirects.pyhere is what turns those moves into redirects; without it the first spec sync after that deploy leaves every old URL 404ing. v1 is deliberately untouched by that change, so no v1 URL moves.Nothing is urgent in this direction: with this merged, the docs render as they do today until the app change deploys.
One thing left alone
The Introduction's maturity list still says stable endpoints get "no breaking changes", while the deprecation policy says they get six months' notice before one. Two different public promises on adjacent pages. Reconciling them is an editorial call rather than a scaffolding change, so I've left it — happy to fold it in if you'd rather.
Test plan
uv run pytest scripts/tests— 196 passed, 2 skippednpx mintlify@latest validatepassesStacked on #2832. Pairs with the semgrep-app directory merge (APPEX-1609).
🤖 Generated with Claude Code