Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
dd98ad2
docs(portal): rewrite the portal-config plan for apimatic.json
saeedjamshaid Sep 23, 2026
13e049f
feat(portal): give the portal block its nested v2 shape
saeedjamshaid Sep 23, 2026
d2d7ccf
feat(portal): describe apimatic.json in a JSON schema for editors
saeedjamshaid Sep 23, 2026
2182a36
feat(portal): apply the portal block's site, brand and layout settings
saeedjamshaid Sep 23, 2026
8b6a703
feat(portal): render the top level of the portal as tabs
saeedjamshaid Sep 23, 2026
3ce7635
feat(portal): group and filter the API reference as portal.api says
saeedjamshaid Sep 23, 2026
fafbbc9
feat(portal): re-apply apimatic.json while portal serve runs
saeedjamshaid Sep 23, 2026
403f254
feat(portal): scaffold the populated block and stop quickstart at it
saeedjamshaid Sep 23, 2026
4cdbefe
fix(portal): close the gaps the branch review found
saeedjamshaid Sep 23, 2026
98d7432
refactor(portal): list a path item's operations in one place in the A…
saeedjamshaid Sep 24, 2026
bc41506
refactor(portal): walk to a folder's first page without nested ternaries
saeedjamshaid Sep 24, 2026
595b2d5
refactor(portal): compare file paths with FilePath.isEqual
saeedjamshaid Sep 24, 2026
0074bbc
refactor(portal): count a token value's parentheses without a nested …
saeedjamshaid Sep 24, 2026
b98c922
refactor(portal): test for a rejected colour channel with includes
saeedjamshaid Sep 24, 2026
b8f6cb4
refactor(portal): quote a list of allowed values in one place
saeedjamshaid Sep 24, 2026
5d6495a
refactor(portal): keep what the preview shows of apimatic.json in Pre…
saeedjamshaid Sep 24, 2026
00ae3b4
test(portal): read the source-problem report without its colours
saeedjamshaid Sep 24, 2026
4688d00
refactor(portal): have scaffold answer with the apimatic.json it wrote
saeedjamshaid Sep 24, 2026
e7f790e
fix(portal): report a fault re-applying apimatic.json instead of drop…
saeedjamshaid Sep 24, 2026
9c64a98
test(portal): hold the template's types to the CLI's in the compiler
saeedjamshaid Sep 24, 2026
d9654da
refactor(portal): name the quickstart option of reportSourceProblem
saeedjamshaid Sep 24, 2026
072e42b
style(portal): declare BrandConfig.defaults before parse, as its sibl…
saeedjamshaid Sep 24, 2026
09f5aef
refactor(portal): return the Google Fonts address as a UrlPath
saeedjamshaid Sep 24, 2026
4a3a12f
refactor(portal): drop a link check that isWebAddress already makes
saeedjamshaid Sep 24, 2026
1e93fb0
refactor(portal): write the languages example once for both messages
saeedjamshaid Sep 24, 2026
a6816e1
docs(portal): trim the comments this branch added to what the code ca…
saeedjamshaid Sep 24, 2026
8a19120
refactor(portal): take the primary colour in hex only
saeedjamshaid Sep 24, 2026
96bcb02
refactor(portal): drop the layout setting and keep the notebook layout
saeedjamshaid Sep 24, 2026
37067ad
refactor(portal): drop the colour preset and keep the neutral theme
saeedjamshaid Sep 24, 2026
aa96507
refactor(portal): drop the font setting and keep Geist
saeedjamshaid Sep 24, 2026
765ff02
refactor(portal): drop the home page's call to action
saeedjamshaid Sep 24, 2026
aec3172
refactor(portal): drop the API reference settings
saeedjamshaid Sep 24, 2026
81e2453
refactor(portal): drop the raw colour token overrides
saeedjamshaid Sep 24, 2026
cc4184c
docs(portal): list only the portal settings that remain in the upgrad…
saeedjamshaid Sep 24, 2026
3709a61
docs(portal): name only the remaining namespaces in the draft release…
saeedjamshaid Sep 24, 2026
e0054dc
docs(portal): record the first-release cuts in the fumadocs-portal plan
saeedjamshaid Sep 24, 2026
13c942f
docs(portal): mark the portal-config plan's leftovers of the cut sett…
saeedjamshaid Sep 24, 2026
f5fbd18
test(portal): hold app.css to importing the theme before the generate…
saeedjamshaid Sep 24, 2026
b370944
fix(portal): name a repeated tag as a cause of two pages at one path
saeedjamshaid Sep 24, 2026
4350c7f
docs(portal): say what the filter and the branded fixture actually do
saeedjamshaid Sep 24, 2026
4388ed2
fix(portal): watch apimatic.json's directory by its real path
saeedjamshaid Sep 24, 2026
943872f
test(portal): let the watch go live before the first save
saeedjamshaid Sep 24, 2026
b247a16
docs(portal): plan the generated SDK pages
saeedjamshaid Sep 24, 2026
fe3fa92
docs(portal): give the context plugin page a tab of its own
saeedjamshaid Sep 24, 2026
d56b7a9
fix(portal): refuse a header link whose dot segments lead to another …
saeedjamshaid Sep 24, 2026
b831fbe
fix(portal): drop a tag group once every tag under it is hidden
saeedjamshaid Sep 24, 2026
e4c59f4
fix(portal): replace the appearance files whole while the preview wat…
saeedjamshaid Sep 24, 2026
cbcafc5
style(portal): format the payload-slimming tests
saeedjamshaid Sep 24, 2026
06a82b4
refactor: read JSON objects through one check
saeedjamshaid Sep 24, 2026
d8c681a
fix(portal): keep OpenAPI 3.2 sibling operations out of each page's p…
saeedjamshaid Sep 24, 2026
511e5c9
refactor(portal): look up a file's spelling on disk in the file service
saeedjamshaid Sep 24, 2026
8a1ea73
refactor(portal): drop the accessors nothing reads
saeedjamshaid Sep 24, 2026
abb4983
fix(portal): refuse an empty segment in a header link, as the schema …
saeedjamshaid Sep 24, 2026
00c1782
fix(portal): drop a cycle of tag groups that nothing staying holds up
saeedjamshaid Sep 24, 2026
05b0676
refactor(portal): drop additionalOperations from each page outright
saeedjamshaid Sep 24, 2026
6d0a68f
refactor(portal): read nav.json and a specification's info through is…
saeedjamshaid Sep 24, 2026
0dbdafb
style(portal): keep the review fixes' comments to one line each
saeedjamshaid Sep 24, 2026
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
30 changes: 24 additions & 6 deletions .ai/plans/apimatic-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

Status: in progress, 2026-09-22. Branch `saeedjamshaid/apimatic-config`, cut from `dev`.

**Amended 2026-09-23** by `.ai/plans/portal-config.md`, whose PR changes this
plan in five places, each noted where it applies: the `portal generate` half of
"requires a `languages` entry" lands in that PR (section 1); the portal path
now reads `languages` findings, and an unknown `portal` key gets no near-miss
hint (section 2); its portal-config bullet is superseded (section 10); and the
release notes name the new `portal` namespaces and the `languages` requirement
(section 13).

## 1. Goal and scope

One file, `src/apimatic.json`, sitting where `src/portal.json` and
Expand Down Expand Up @@ -29,7 +37,10 @@ blocks it finds and writes the ones it does not; then `portal generate`
requires `portal` and at least one entry in `languages`, and `plugin generate`
requires `plugin` and at least one `languages` entry carrying what plugin
generation needs. Where a decision below is shaped by what a later PR will do,
its row says so.
its row says so. *(Amended 2026-09-23: the `portal generate` half, for
`portal serve` too, lands with `.ai/plans/portal-config.md`, as at least one
`languages` entry keyed by a known language; the `plugin generate` half stays
in this series.)*

The backend is changed alongside this series to read `plugin` and `languages`
from the `apimatic.json` inside the `src/` it is sent, and this PR merges only
Expand All @@ -55,13 +66,13 @@ called); the backend change itself.
| Location | `<input>/src/apimatic.json`, where the two files it replaces sit today. Decided 2026-09-22 (section 11): `sdk generate` and `plugin generate` upload `src/` wholesale and the backend reads `plugin` and `languages` from the `apimatic.json` inside that upload, so the file travels with `src/`. The portal block rides along and the server ignores it. |
| Root keys | `$schema`, `schemaVersion`, `portal`, `plugin`, `languages`. All optional at the file level; each command requires what it needs (section 4). |
| `schemaVersion` | Optional. Accepted when absent or `1`. Any other value is an error naming the CLI version that reads it, so a future format is refused rather than misread. Written by the create path only (writer row). |
| `$schema` | Accepted and ignored, as the portal-config plan already says. Not written by the scaffold until the schema file exists. |
| `$schema` | Accepted and ignored, as the portal-config plan already says. Not written by the scaffold until the schema file exists. *(Amended 2026-09-23: `apimatic.schema.json` exists, and the quickstart scaffold writes `$schema` first in the file; files the plugin and publishing commands create do not carry it.)* |
| Ownership per block | `portal` is user-authored. In this release the CLI writes it once, from quickstart, and never again; the quickstart PR that adopts an existing directory (section 1) writes it into a file that already exists, which is why the writer below knows no block. `plugin` and `languages` are shared, as `plugin-config.json` was designed to be: the user may edit them and the CLI merges into them after `plugin generate` and `sdk publish`. |
| Validation per block | `portal` keeps today's strictness: every field validated, unknown fields reported with the near-miss hint. `plugin` and `languages` keep today's leniency: shape checks that protect the merge, unknown fields preserved, and the two `plugin` checks that make the file `unreadable` today — `PLUGIN_ID_PATTERN` on `pluginId`, semver on `pluginVersion` — kept as `plugin` findings (decided 2026-09-22, section 11). The two policies already exist; they now apply to blocks instead of files. |
| Validation per block | `portal` keeps today's strictness: every field validated, unknown fields reported with the near-miss hint. `plugin` and `languages` keep today's leniency: shape checks that protect the merge, unknown fields preserved, and the two `plugin` checks that make the file `unreadable` today — `PLUGIN_ID_PATTERN` on `pluginId`, semver on `pluginVersion` — kept as `plugin` findings (decided 2026-09-22, section 11). The two policies already exist; they now apply to blocks instead of files. *(Amended 2026-09-23: the near-miss hints are gone. An unknown `portal` key is reported by its dotted path with no hint, as the portal-config plan decides for keys that never shipped.)* |
| Byte-order mark | Stripped, then parsed. `portal.json` strips one today and `plugin-config.json` refuses one as `unreadable`; with one file the parser has to pick, and stripping is the only choice that cannot break a project that works today. The plugin path's byte-order-mark `reason` is deleted. Windows is where a byte-order mark comes from — Notepad and PowerShell redirection write it — and it is now the portal's file too. Reading past a mark is enough only while the file stays here: `plugin generate` zips `src/` and sends it to a parser that is not this one, so that path takes the mark off the file before the zip (section 6). Only the mark is removed; the rest of the file is written back byte for byte, so a layout the CLI never chose survives a rewrite it never asked for. |
| Unknown root keys | Ignored, and preserved untouched by the writers — the same leniency `plugin` and `languages` get, for the same reason: a file written by a later CLI that adds a root block must still be readable by this one, which is what `schemaVersion` exists to gate instead. No near-miss hint at the root; a misspelled block is reported only as the required block being absent. |
| A required block is absent | Reported by the command that needs it, naming the block and nothing else: `'portal' is required`. That is the whole root-level report, so a user who wrote `portla` is told what is missing rather than what is unrecognised. |
| Which findings reach which command | Every finding carries the block it came from. `portal generate` and `portal serve` see root-level and `portal` findings; the plugin path sees root-level, `plugin` and `languages`, and never `portal`. A malformed `languages` entry therefore cannot fail a portal build, which is what the ownership split above and the invariants in section 4 both require. When `portal generate` later reads `languages` (the series' last PR, and `x-codeSamples` after it), it opts into those findings deliberately and this row is revisited. |
| Which findings reach which command | Every finding carries the block it came from. `portal generate` and `portal serve` see root-level and `portal` findings; the plugin path sees root-level, `plugin` and `languages`, and never `portal`. A malformed `languages` entry therefore cannot fail a portal build, which is what the ownership split above and the invariants in section 4 both require. When `portal generate` later reads `languages` (the series' last PR, and `x-codeSamples` after it), it opts into those findings deliberately and this row is revisited. *Revisited 2026-09-23 as anticipated (`.ai/plans/portal-config.md`, section 8): the portal path reads `root` and `languages` findings, so a malformed `languages` block now fails a portal build; the plugin path is unchanged.* |
| Writer | Read the document, hand the whole of it to the caller's `apply`, write back the whole of what comes back. The writer knows no block: this release's two callers replace `plugin` and `languages`, and the quickstart PR replaces `portal` without reopening it. Every key the caller did not touch keeps its position, a block the caller adds is appended after the last key — so section 3's order is what a file created whole looks like, and a project that publishes SDKs before it builds a plugin ends up with `languages` before `plugin` — and the file is written with the indentation it already uses and the trailing newline as found. A file the writer creates gets two spaces, a trailing newline and `schemaVersion: 1`; a merge into a file that lacks `schemaVersion` adds nothing (decided 2026-09-22, section 11). Today's two writers disagree on the newline (the plugin writer omits it, the portal scaffold appends it), so it is decided here rather than left to whichever runs first. Preserving indentation and the newline is new behaviour, not preserved behaviour (section 8). The write goes through `FileService.replaceContents`: the content is written to a temporary file beside the target and renamed over it, so a torn write cannot destroy a hand-authored `portal` block, and the temporary file is removed if the rename fails, so a failed write leaves nothing behind. A file that exists but cannot be parsed is left alone, as today. |
| Legacy files | `src/plugin-config.json` is not read, not imported and not mentioned by the CLI. Same rule as the 1.x portal setup: the release notes carry the move, the CLI carries no migration messaging. `src/portal.json` needs no rule: no released CLI ever wrote one — npm `latest` is 1.5.0 and `beta` is 1.3.0-beta.2, and both trees predate the Fumadocs portal — so only a directory built from `dev` holds one. The break users actually meet is `APIMATIC-BUILD.json`'s portal settings and `src/plugin-config.json`, and that is what the notes describe (section 9). |
| `--update-plugin-config` | Keeps its name this release; only its description changes to say `apimatic.json`. Renaming is a separate decision. |
Expand Down Expand Up @@ -433,7 +444,10 @@ major.
generated command blocks, so it is not edited by hand: the README is
regenerated once the command and flag strings change, and the regeneration is
part of step 7.
- `.ai/plans/portal-config.md`: the schema file becomes `apimatic.schema.json`
- *(Superseded 2026-09-23: `.ai/plans/portal-config.md` was rewritten for
`apimatic.json` and is implemented; its schema covers the whole file and its
`portal` block is the nested one. The bullet below is kept as it was.)*
`.ai/plans/portal-config.md`: the schema file becomes `apimatic.schema.json`
with the `portal` block as one definition; `$schema` URL follows. The
scaffold section writes `src/apimatic.json`. Its `sdks.languages` is dropped
and its "Rejected: top-level `languages`" entry reversed: the portal derives
Expand Down Expand Up @@ -596,7 +610,11 @@ whole series, not this PR alone.
record them again, then delete the old file. The server reads `apimatic.json`
from the uploaded `src/`.
- The documentation portal is described by the `portal` block of
`src/apimatic.json` (`title`, `description`, `logo`, `siteUrl`);
`src/apimatic.json` — `site`, `brand`, `navigation` and `ai` (amended
2026-09-23, when it listed the flat `dev` keys, and 2026-09-24, when it listed
the `home`, `api` and `advanced` namespaces the first release cut; see
`.ai/plans/portal-config.md` section 15) — and needs at
least one entry in the file's `languages` block;
`APIMATIC-BUILD.json` no longer configures it. `apimatic quickstart` scaffolds
the file. Portals are built on your machine from `src/spec/`, `src/content/`
and `src/static/`.
Expand Down
22 changes: 21 additions & 1 deletion .ai/plans/fumadocs-portal.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ src/
static/ served verbatim at the site root
```

*Amended 2026-09-23:* the v1 shape below is superseded by the nested block of
`.ai/plans/portal-config.md` (`site`, `brand`, `navigation`, `home`, `api`, `ai`,
`advanced`), which also requires a top-level `languages` entry. The text below
is kept as it was. *Amended 2026-09-24:* the first release trims that block to
`site`, `brand`, `navigation` and `ai` (that plan's section 15).

The `portal` block, v1 shape:

```json
Expand Down Expand Up @@ -107,6 +113,20 @@ was itself based on the official `tanstack-start-openapi` and
`tanstack-start-spa` Fumadocs examples. Kept as the spike has it (Geist fonts
from Google Fonts at runtime, Fumadocs page actions) except where noted.

*Amended 2026-09-23* (`.ai/plans/portal-config.md`, sections 4 and 6): the
fixed `neutral.css` import and the Geist lines have left `app.css`. The preset,
the fonts and the colour overrides are in a `src/styles/theme.css` the CLI
generates, which `app.css` imports last, and the fonts load through a head
link. The identity literal substituted into `portal.ts` is gone: `portal.ts`
imports a CLI-written `portal.identity.json`, and `portal.config.json` holds
only what the build set-up reads. The layout is chosen in `src/lib/layout.tsx`.

*Amended 2026-09-24* (`.ai/plans/portal-config.md`, section 15): the first
release cuts the preset, the fonts and the layout. `app.css` imports
`neutral.css` again and sets Geist and Geist Mono in `@theme`, `__root.tsx`
links their Google Fonts stylesheet, the generated `theme.css` holds only the
primary colour's two rules, and `layout.tsx` renders the notebook layout alone.

- `vite.config.ts`: `fumadocsMdx()`, `tailwindcss()`, `react()`, and
`tanstackStart({ spa: { enabled: true, maskPath: '/spa-shell', prerender: { enabled: true } }, pages: [...], prerender: { crawlLinks: false }, importProtection: { behavior: 'error' } })`.
No `nitro()`. The `pages` list is mandatory: without it only the shell is
Expand Down Expand Up @@ -276,7 +296,7 @@ Follow the five-layer conventions in `.ai/instructions.md` and the skills in
- Large, densely connected APIs still yield large output folders (section 5:
a Stripe-shaped spec approaches 1 GB at full size). The remaining lever is the
second copy of each page's payload; see section 12.
- Generated site is not self-contained: Geist fonts load from Google Fonts at runtime and Fumadocs' page actions link to ChatGPT/Claude/Cursor (decided to keep as-is for v1).
- Generated site is not self-contained: Geist fonts load from Google Fonts at runtime and Fumadocs' page actions link to ChatGPT/Claude/Cursor (decided to keep as-is for v1). *Amended 2026-09-23:* the fonts are now a shortlist in `portal.brand.fonts`, each still from Google Fonts, and `system` for both makes no font request at all; `portal.ai.pageActions: false` removes the external links. *Amended 2026-09-24:* the shortlist is cut for the first release, so Geist and Geist Mono are fixed and load from Google Fonts again, as in v1; `portal.ai.pageActions: false` still removes the external links.
- Verified on Windows only; macOS/Linux via the new CI matrix.
- Try-it playground requires CORS on the customer's API (Fumadocs' proxy needs a server). Out of scope.
- Fumadocs UI is mid-transition to `@fumadocs/base-ui`; fumadocs-openapi has had three majors in about a year; several single-maintainer and 0.x packages in the tree (`lucide-react`, `yuku-analyzer` with native binaries, `zbsearch`, `h3` RC). Mitigated by pinning and the e2e test.
Expand Down
Loading
Loading