Skip to content

Add Git-backed OpenAPI API references - #147

Merged
s3rot0n1n merged 20 commits into
mainfrom
t3/plan-issue-26-architecture
Oct 30, 2026
Merged

s3rot0n1n merged 20 commits into
mainfrom
t3/plan-issue-26-architecture

Conversation

@s3rot0n1n

@s3rot0n1n s3rot0n1n commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Closes #26.

Summary

A team's OpenAPI spec (JSON or YAML, 3.0 or 3.1) lives in its connected GitHub repo. FrontLit imports it on every sync and publishes an API Reference under /docs/<mount>: a landing page, one page per operation, and the raw spec at /openapi.json. Design and decisions: docs/openapi-support.md.

Import and admin (API, web)

  • Spec path and URL path are set in the GitHub connection dialog (Preview → Publish, Change, Turn off). Any GitHub connection with docs or pages can add a spec.
  • Our own validation with located diagnostics (file, line, JSON pointer); a broken spec never fails the sync and the last good version stays live. Limits: 5 MiB, 500 operations.
  • x-internal / x-excluded hide operations from pages, search and the served /openapi.json (empty paths are dropped too).
  • A Markdown page under the API's URL path is rejected (RESERVED_ROUTE); a URL path that clashes with existing docs is rejected (API_MOUNT_CONFLICT).
  • Titles follow Fumadocs: summary, path-item summary, operationId in words, then the bare path.
  • Code samples: cURL, JavaScript, Python, Go, Java and C# (standard libraries only), plus x-codeSamples.
  • Operations are searchable; activation invalidates the site cache through the outbox.

Public site (sites)

  • Operation pages: parameters, request body and response schema trees, examples, code-sample tabs, authentication (omitted when the spec doesn't describe it; "No authentication required" only for an explicit security: []).
  • A Fumadocs-style request panel: method, path and Send with collapsed Server / Authentication / parameter / Body rows. The rows are in the page HTML; the form (≈4.3 KB gzipped) mounts after hydration with no layout shift. Requests go from the browser straight to the API.
  • Sidebar: every tag group is collapsible; only the current group's endpoints ship with the page, the rest load once from the public API on first use. The docs sidebar is rendered once (CSS-only "Browse articles" toggle on narrow screens).
  • Landing page: one card per tag group with a row per endpoint; /openapi.json opens in a new tab.
  • Themed with page-builder primitives in all six presets, light and dark; layout follows the theme's container width.

Also in this PR

Testing

  • API: 104 files, 764 tests (MediaLit's live integration tests need network access). Sites: 66 tests. page-builder: 60 tests. Typecheck and lint pass; CI green.

  • Local browser smoke (MediaLit fork, docs-site test repo): connect, preview, publish, URL-path clash, push-triggered sync, broken spec with diagnostics while the last good version stays live, restore, reserved route, docs linking into the API, all six themes light and dark, phone width.

  • QA (openapi-8784af1, CourseLit's 213-operation spec from platform-based-rewrite): connect via docs setup PR, publish, request panel and validation, sidebar groups, six code tabs, search, /openapi.json equals the committed spec (304 on revalidation), sitemap.

  • QA load test: 213 pages × 3 passes + 213 fresh renders at 8 concurrent: all 852 requests 200, no restarts, sites pod memory flat around 250 Mi (512 Mi limit).

  • PageSpeed Insights on QA:

    Page Device Perf A11y Best practices SEO LCP TBT CLS
    Operation Mobile 97 99 100 66 1.1 s 140 ms 0.002
    Operation Desktop 95 95 100 66 0.3 s 170 ms 0.002
    Landing (213 endpoints) Mobile 100 94 100 58 1.2 s 50 ms 0
    Landing (213 endpoints) Desktop 100 94 100 58 0.3 s 40 ms 0

    SEO is low because the test site is hidden from search engines; the landing page's missing meta description is fixed in 574ade4 (built, not yet on QA: the QA instance was stopped).

Follow-ups

  • Theme palettes: the destructive red is below 4.5:1 against badge text in several presets (Classic, Midnight, Neo-Brutalism, most dark modes).
  • MediaLit and CourseLit APIs need CORS for docs origins (CourseLit also answers preflight OPTIONS with 401) before "Send" works against them.
  • CourseLit's contract: summaries for most routes and correct security (94 operations declare security: []).

🤖 Generated with Claude Code

Rajat and others added 3 commits October 25, 2026 15:37
Import a single-file OpenAPI 3.0/3.1 spec from the connected repository,
publish one docs page per operation plus a landing page under
/docs/<mount>, a lazily loaded "Try it" playground, and /openapi.json.
Adds Tabs, Table, Collapsible, NativeSelect and InlineCode primitives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adopt the site caching work (#141): API reference reads are cached under
docs:<team> and invalidated with the docs events (sites and edge); the
OpenAPI migration is renumbered to 0012 after main's 0011.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-25T11:36:39.752348Z d8c2580 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d8c25800de

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (!isObject(operation)) continue;
const at = ["paths", path, method];
if (isHidden(operation)) {
delete served.paths?.[path]?.[method];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Filter hidden operations from referenced path items

When an OpenAPI 3.1 path references a components.pathItems entry containing an x-internal or x-excluded operation, served.paths[path] is still just the $ref, so deleting the method here does nothing. The published /openapi.json therefore still exposes the hidden operation and its details. Remove hidden operations from the referenced path item or rewrite the exported reference.

Useful? React with 👍 / 👎.

});
if (
!binding ||
binding.generation !== input.generation ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fence API activation against newer syncs

If a newer Git sync is queued and activates while this import is still processing an older commit, the binding's connection generation and API settings can remain unchanged. This check then accepts the stale import, allowing it to replace the newer live spec. Also fence activation against the sync request generation or current branch head.

Useful? React with 👍 / 👎.

</div>
</aside>
<article className="flex min-w-0 flex-col gap-8">
<details className="@3xl:hidden">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use the themed Collapsible for mobile navigation

On mobile, this raw <details> is the docs navigation control outside the page-builder primitives; the available Collapsible primitive supplies the themed typography, focus treatment, and disclosure indicator. Use that primitive here to keep the public-site control consistent with the required UI pattern.

AGENTS.md reference: AGENTS.md:L35-L37

Useful? React with 👍 / 👎.

Rajat and others added 2 commits October 25, 2026 17:39
… built

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in panel after syncs

- A path whose only operations are x-internal/x-excluded no longer leaves an
  empty entry in the served spec, so its name stays private.
- The API reference panel in the GitHub dialog reloads when a sync starts or
  finishes, instead of showing the status from when the dialog opened.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rajat and others added 15 commits October 30, 2026 13:20
…sidebar

Every docs page rendered every endpoint in the sidebar twice (desktop and
mobile), in both the HTML and the RSC payload. With CourseLit's 213
operations each page was ~950 KB and took 2-4 s to render; eight at once
exhausted the 256 MB heap on QA and the sites pod restarted (502s).

Now the group holding the current page lists its endpoints and every other
group links to its section on the API landing page. The same CourseLit page
is ~330 KB and renders in ~0.1 s; all 213 pages load with no errors at 8
concurrent requests under a 256 MB heap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… spec is silent

A spec with no `security` on the operation or at the root says nothing about
authentication. The operation page showed "No authentication required.", which
is wrong for APIs like CourseLit's that require auth but don't describe it.
The slice now stores `security: null` in that case and the page omits the
Authentication section; an explicit `security: []` still reads as no
authentication.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The landing page's openapi.json button forced a download. It now opens the
hosted spec in a new tab, so its URL (what tools import) is visible; the
file can still be saved from there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Next 16.3.x never releases the AbortSignal.any composite that "use cache"
creates for each fill (vercel/next.js#97776), so every rendered route stays
reachable. A 213-endpoint API reference exhausted the 256 MB heap on QA in
minutes. 16.4.3 releases it.

Locally under a 128 MB heap: 639 fresh renders plus 852 cached requests, all
200; heap 136 MB with 2 AbortControllers retained (16.3.4: crashed, 457 MB
and 2,154 retained).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hem all

Bring back expandable tag groups for the whole API reference while keeping
pages small:

- The current group's endpoints ship with the page (as data, not markup).
  The other groups load once from the public api-reference endpoint the
  first time the pointer, focus or a click reaches the sidebar; without
  JavaScript they link to their section on the landing page.
- The docs sidebar is rendered once instead of twice (desktop column plus a
  mobile copy). Narrow layouts show it behind a CSS-only "Browse articles"
  toggle.

CourseLit's 213-operation reference: operation pages 334 KB -> 240 KB; a
docs article 138 KB -> 113 KB; MediaLit's upload page 209 KB -> 168 KB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Titles that fall back to "METHOD /path" have no spaces, so the operation
page's heading ran under the Try it column and sidebar entries were clipped.
Paths now break after each "/", with overflow-wrap as the fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The operation page's title used the Header1 size, which is too large for
long endpoint paths. It's now the page's h1 at the theme's Header2 size, a
step above the Header3 section headings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ummary

Without a summary, operations were titled "METHOD /path", repeating the
method next to its badge in the sidebar, heading and landing page. Titles
now follow Fumadocs (and Redoc): the operation's summary, then the path
item's summary, then the operationId in words ("deleteMedia" -> "Delete
Media"), then the bare path. The browser tab title keeps the method for a
bare path, so GET and POST /x stay distinguishable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Like Fumadocs, the operation page now has a request block under the
description: server URL, method and path, rendered as plain HTML. "Try it"
loads the playground (prefetched on idle or intent, as before) and opens its
form inline below the bar; the bar's button then sends the request, and a
close button hides the form and returns focus. The right column keeps the
code samples and response examples; the method/path line above the title is
gone since the panel shows it. Server fields start collapsed.

Checked in all six themes, light and dark, and at phone width.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The request panel shows its form right away, like Fumadocs: method, path
  and Send, then collapsed rows (Server with its URL, Authentication, each
  parameter location, Body). The page HTML has the same collapsed rows and
  the form mounts into them after hydration, so nothing shifts. Send opens
  the rows with empty required inputs and focuses the first one.
- API landing: one card per tag group with a row per endpoint instead of a
  card per endpoint; a bare-path title isn't repeated next to the path.
- Descriptions no longer add the text renderer's outer block margins.
- page-builder Section clips with overflow-clip, which isn't a scroll
  container, so docs sidebars and the code panel stick again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operation pages now offer cURL, JavaScript, Python, Go, Java and C#, like
Fumadocs. Each uses only its standard library (net/http, java.net.http,
System.Net.Http) and covers JSON, form and multipart bodies; Java points to
a library for multipart, which the JDK can't build. Tab padding is tighter
so the six tabs fit the code column without scrolling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rows aligned to the top with a fixed nudge, which only centred the badge
for one line height. They now align on the text baseline, so the badge
matches the title's first line in every theme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lighthouse accessibility on API pages goes from 88-90 to 96-97:
- API pages: section headings are h2 (sub-headings h3) under the h1, at the
  same sizes as before; nothing skips a level.
- Docs search: the input is a combobox, so its aria-expanded is valid.
- page-builder: the Drawer puts trigger props on the trigger button instead
  of a wrapping span, and footer column titles are h2.

Remaining: the themes' destructive red fails 4.5:1 against badge text in
several presets; that needs a palette decision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lighthouse SEO flagged the landing page for having none. It uses the
spec's description, else "<API title>: <n> endpoints."

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@s3rot0n1n
s3rot0n1n merged commit 1e0f714 into main Oct 30, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenAPI support

1 participant