Repository navigation
feat(portal): generate the SDK and context plugin pages - #360
Merged
Merged
Conversation
saeedjamshaid
force-pushed
the
saeedjamshaid/generated-pages
branch
from
September 24, 2026 13:51
32812e7 to
7ea144d
Compare
saeedjamshaid
changed the base branch from
saeedjamshaid/portal-config
to
dev
September 24, 2026 13:51
saeedjamshaid
marked this pull request as ready for review
September 24, 2026 14:12
MuHamza30
reviewed
Sep 24, 2026
MuHamza30
requested changes
Sep 24, 2026
MuHamza30
reviewed
Sep 24, 2026
Contributor
|
SonarCloud reports 6 new issues on this PR. The critical one is |
Step 1 of the plan, run on a prepared default fixture. The build side held: the collection publishes only the word generated, no build path reaches the output, and the .md twins carry the page bodies. Under vite dev an added generated page never appears and a removed one fails every request until a restart, so the plan gains a serve-only Vite plugin that treats an add or remove as an edit to source.ts. Tailwind already scans the generated directory, so the @source line is dropped. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The types for step 2 of the generated-pages plan. GeneratedPages answers
the SDKs page, a page per language in the order the languages block
lists them, and the context plugin page when there is a plugin block,
with each section's nav.json. PageTemplate fills {{key}} placeholders
and refuses any other double-brace form.
A content page served under /sdks or /context-plugin is refused with
the file and its address, apimatic:plugin joins the nav.json tokens,
and an entry naming a section gets its token as a hint. resolveConfig
becomes resolveSettings and answers the generated pages too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Step 3 of the generated-pages plan. Three placeholder templates ship in portal-pages/, and PortalPagesService renders them into the project's generated/ directory: a file is written only when it changes, and pages or section folders no longer called for are deleted, so portal serve reloads only what an edit changed. prepare writes the directory and names it as generatedDir; applyConfig takes the settings pair and regenerates the pages beside the appearance files. The package root is looked up in one place, env-info, which the formatter also normalises from CRLF to LF. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Step 4 of the generated-pages plan. The template compiles generated/ as a second collection over a relative literal, so the browser bundle names no build path, and routes, prerenders and renders .md twins for its pages beside the user's. Each generated folder at the root is a tab, named by its own nav.json: apimatic:sdks and apimatic:plugin each claim their own folder, unnamed sections collect before the API reference in the CLI's order rather than Fumadocs' path order, and the synthetic SDKs tab is gone. A serve-only Vite plugin passes an add or remove under generated/ on as an edit to source.ts, since the macro's glob is not expanded again on its own and a removed page otherwise failed every request. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… live Step 5 of the generated-pages plan. The portal serve note now says that a language added to or removed from apimatic.json updates the SDK pages and that the plugin block adds or removes the Context Plugin tab, and that only a page added to content still needs a restart. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…lans Step 6 of the generated-pages plan. The default fixture's nav.json places the SDKs tab with apimatic:sdks, and the branded fixture gains a plugin block, so the end-to-end builds prove the SDK and context plugin pages, their Markdown twins, the token and the default tab order. The portal generate and serve descriptions and the README's 2.0 notes say what the generated pages are and how they reload, and the navigation, portal-config and fumadocs-portal plans carry dated amendments. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The review of the portal-config work had applyConfig rename each file over its target, because the watching dev server could read one half-written. The generated pages are watched the same way, so the writer now replaces them the same way, and the reload plugin reacts only to the pages and nav.json files the collection reads, not to the temporary file each is written through. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A negated include.members passes as soon as one of the names is missing, so a stray SDKs tab went unnoticed while the Context Plugin tab was absent. Both cases now compare the whole tab list. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The comment above parseSettings said a malformed plugin block gets no page, but only a block that is not an object does: nothing in the block is read, as the plan decided, and the backend refuses an invalid one. The comment and the plan row now say so, and a test pins that a block with an identity the plugin commands would refuse still gets the page. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The portal's SDK pages had a display-name table of their own beside the quickstart prompt's choices, and the two already disagreed on "TypeScript". LANGUAGE_NAMES now sits beside the Language enum, and the prompt's choices take their labels from it in the same order, so the prompt says "TypeScript" as well. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
applyConfig and the generated pages each read a file, compared it with what they meant to write and renamed the new contents over it, with the same loop and the same comment. FileService.replaceContentsIfChanged now does that once and answers whether it wrote, and both call it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The reservedAddresses case took reportSourceProblem to a cognitive complexity of 16, one over SonarCloud's limit. Its body moves unchanged into reportReservedAddresses, which the case calls. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
saeedjamshaid
force-pushed
the
saeedjamshaid/generated-pages
branch
from
September 24, 2026 16:51
afcd875 to
167a450
Compare
|
MuHamza30
approved these changes
Sep 24, 2026
sohail2721
pushed a commit
that referenced
this pull request
Sep 25, 2026
#360 landed and rebuilt `LANGUAGE_CHOICES` around a `LANGUAGE_NAMES` map, which is the same display order reached a better way. Dev's construction wins, and `UPCOMING_LANGUAGES` goes on deriving itself from it, so the refusal still reads `Ruby, Java, PHP, Go`. `languageLabel` was searching the choices for a label the map now holds directly. It reads the map. The identical private copy in `code-samples.ts` is left alone: #352 is still editing that file, and this is not the branch to take it from. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
github-actions Bot
pushed a commit
that referenced
this pull request
Sep 28, 2026
# [2.0.0-beta.1](v1.5.0...v2.0.0-beta.1) (2026-09-28) * build!: move to Node 24 and TypeScript 7 ([#347](#347)) ([2528d6e](2528d6e)) * feat(quickstart)!: one funnel, and the language question ([#363](#363)) ([c270135](c270135)), closes [#361](#361) * feat(sdk)!: retire v3 generation and let a publish record itself ([#359](#359)) ([c48f91b](c48f91b)), closes [#358](#358) [#358](#358) [apimatic-io#2233](https://github.com/apimatic-io/issues/2233) ### Bug Fixes * **config:** keep the package configuration a publish did not write ([#392](#392)) ([bc52735](bc52735)) * **config:** nest publishing under the language ([#350](#350)) ([fbacf74](fbacf74)) * **deps:** take fumadocs 16.15.15, which fixes bold text in a built portal ([#388](#388)) ([fa178ab](fa178ab)) * **plugin:** ask only for the plugin's identity when the languages are recorded ([#389](#389)) ([d721819](d721819)), closes [apimatic/apimatic-io#2258](https://github.com/apimatic/apimatic-io/issues/2258) * **portal:** bundle the Geist fonts instead of loading them from Google Fonts ([#366](#366)) ([95f2f7e](95f2f7e)) * **portal:** drop the TypeScript definitions panel from operation pages ([#353](#353)) ([042eb89](042eb89)) * **portal:** give a run the temp directory the filesystem names, not an alias ([#377](#377)) ([d71d23f](d71d23f)) * **portal:** keep the encoding a page slug carries ([#396](#396)) ([0bec5f4](0bec5f4)), closes [apimatic/apimatic-io#2270](https://github.com/apimatic/apimatic-io/issues/2270) * **portal:** keep the header's links on one line ([#398](#398)) ([40dc472](40dc472)) * **portal:** keep the portal name on one line in the header ([#395](#395)) ([f3d352c](f3d352c)) * **portal:** keep the preview starting until its first page answers ([#399](#399)) ([5359442](5359442)) * **portal:** name the operations a shared page comes from, in words a spec's author knows ([#387](#387)) ([01197a7](01197a7)) * **portal:** offer the page actions on API reference pages ([#382](#382)) ([22f4873](22f4873)), closes [apimatic/apimatic-io#2247](https://github.com/apimatic/apimatic-io/issues/2247) [#383](#383) * **portal:** refuse tag and operationId names a URL or a Windows folder cannot hold ([#400](#400)) ([0ea01b2](0ea01b2)), closes [apimatic/apimatic-io#2262](https://github.com/apimatic/apimatic-io/issues/2262) [#396](#396) * **portal:** render a request body in a media type Fumadocs has no adapter for ([#386](#386)) ([9f89ad3](9f89ad3)) * **portal:** render an operation whose response example has no value ([#373](#373)) ([1dc84ad](1dc84ad)) * **portal:** render split OpenAPI specifications correctly ([#354](#354)) ([d97cda6](d97cda6)) * **portal:** stop printing an operation's raw description under its title ([#383](#383)) ([25b9f6e](25b9f6e)), closes [apimatic/apimatic-io#2242](https://github.com/apimatic/apimatic-io/issues/2242) * **quickstart:** download the sample spec from its new openapi.json path ([#401](#401)) ([ebc6743](ebc6743)) * the release test's high and medium findings ([#375](#375)) ([7fe41e1](7fe41e1)) ### Features * code samples support ([#352](#352)) ([7b82568](7b82568)) * **config:** replace portal.json and plugin-config.json with apimatic.json ([#348](#348)) ([506f67f](506f67f)) * local plugin generation ([#358](#358)) ([5ba70de](5ba70de)), closes [#351](#351) [#346](#346) [#348](#348) [#318](#318) * **portal:** build the documentation portal locally with Fumadocs ([#343](#343)) ([073d09a](073d09a)) * **portal:** build the SDK and Context Plugin pages from real templates and the portal artifacts ([#369](#369)) ([0efd7b3](0efd7b3)) * **portal:** fetch the artifacts from /portal-artifacts ([#361](#361)) ([0e823f3](0e823f3)) * **portal:** generate the SDK and context plugin pages ([#360](#360)) ([338b14a](338b14a)) * **portal:** give the portal block its v2 shape, with branding and tabs ([#355](#355)) ([d0fb189](d0fb189)) * **portal:** lead each SDK card with its install command ([#376](#376)) ([119e9d6](119e9d6)), closes [#375](#375) * **portal:** let the root nav.json decide the tabs, and fix the nav.json QA findings ([#371](#371)) ([ecf7666](ecf7666)) * **portal:** order the sidebar from nav.json ([#346](#346)) ([306e4f9](306e4f9)) * **portal:** point to api transform when src/spec has no OpenAPI 3.x document ([#364](#364)) ([eefc917](eefc917)) * **portal:** the SDK Overview design, on the SDKs, language and plugin pages ([#391](#391)) ([fd3fe34](fd3fe34)) * **quickstart:** suggest validating with an AI agent when a spec fails ([#385](#385)) ([36fd2bb](36fd2bb)) ### Reverts * **portal:** offer the page actions on API reference pages ([#382](#382)) ([#397](#397)) ([c087ef9](c087ef9)), closes [#388](#388) [apimatic/apimatic-io#2247](https://github.com/apimatic/apimatic-io/issues/2247) ### BREAKING CHANGES * `apimatic sdk save-changes` is removed, along with `--codegen-version`, `--stability`, `--track-changes` and `--skip-changes` on `sdk generate` and `--codegen-version`, `--stability` and `--update-plugin-config` on `sdk publish`. Java, Ruby, Go and PHP cannot be generated until they reach v4. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(sdk): keep --stability, which outlived the version it came with Retiring v3 took `--stability` with it on the grounds that there was one answer left. There is one answer *today*: v4 renders every language at beta and they reach stable one at a time, so the level is a choice that survives the version being fixed. The flag is back on both commands, defaulting to stable as before, and the level a language offers is a table rather than a constant — `stableLevels` of one is answered without asking, and the interactive flow puts the question back the moment a language has two. `sdk publish` names the level in its summary only when the user chose it, read from oclif's parse metadata, because `--stability stable` and the default are the same string and the doc's block has no such row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(sdk): keep --codegen-version, with one version in it Retiring v3 removed the flag along with the version, on the grounds that a choice of one is not a choice. But the flag is how a caller says which generator they want, and there will be a v5: removing it makes that move a release that changes what people get, rather than a value they pass. `CodeGenerationVersion` now holds `V4` alone, and stays an enum so the next one is a line in it. The flag is back on both commands, accepting `v4`, defaulting to `v4`, and refusing `v3` by name rather than generating something else quietly. `sdk publish` names it in its telemetry again. It is not written down. `codegenVersion` left `apimatic.json` when the service took over deciding the generator, so the flag says what to generate with and nothing records what was used. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(config): drop codegenVersion from the published schema * `apimatic quickstart` no longer offers a choice between a portal and an SDK. It always builds a portal, and `apimatic sdk generate` is the command for an SDK alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(quickstart): ask which languages the portal covers The one question quickstart asks besides the spec. Every language a plugin can carry comes up checked, because a first portal covering everything it can is what a single Enter should give, and the four it cannot are named in the prompt rather than left out in silence. The answer is written to `apimatic.json` through `recordLanguages` before anything is built, because that file is what says which SDKs a portal documents — every command after this one reads it rather than the answer that produced it. `languageLabel` moves to `types/sdk/generate.ts`, where both prompts that name a language can reach it, and TypeScript is spelled the way its own project spells it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(quickstart): name the plugin after the folder, without asking The `plugin` block is what asks the service for a context plugin, so quickstart has to write one — and the journey allows it no question about plugins. The identity comes from the directory the user is standing in. A folder name is not a plugin id. `Acme Payments` and `acme_payments` are ordinary names and neither is the kebab-case `pluginId` has to be, so the id is made from the name while the name itself is kept as typed — the id goes to `gh repo create`, the name is what a reader sees. A directory whose name survives none of that falls back, because writing an id the config would then refuse is worse than not deriving one. `0.1.0` for the version: every plugin starts somewhere. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(quickstart): keep generated files out of the repository Every command that builds a portal writes `src/static/sdk/`, `src/static/plugin.zip` and an expanded `./plugin` into the project. None of it belongs in a repository, and `portal serve` writes it too — so without this a preview leaves a clean checkout dirty. `/plugin/` is the one that matters. `plugin publish` runs `git init` inside that directory and pushes it as its own repository; a parent tracking it would nest one repository inside another. Quickstart writes the entries because quickstart is what makes the project. It appends what is missing and rewrites nothing, so a `.gitignore` the user wrote survives and re-running in an adopted directory stacks no duplicates. The paths are ignored here before anything on this branch produces them: the placement lands with the `/portal-artifacts` call, and a user whose first build dirties their tree has already been failed by then. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(quickstart): end in the preview again, and say the coming languages under the answer Two things, both about where a line belongs. The rebase onto dev quietly took dev's ending for the wizard -- the one that stops at the scaffold because "the wizard does not ask for the project's SDK languages yet". It does, three lines above that comment. The handoff to `portal serve`, and the `onAfterServe` the serve action took for it, had both gone; nothing failed, because no test described the funnel's own ending. One does now. `Next Steps` loses its first two items with them. It told the user to name their SDK languages and then start a preview; the wizard has just done both. What is left is what to change now that the portal is on screen. The coming-soon languages move out of the multiselect's message and under the answer, in grey. Inside the message they rendered above the languages the user had picked, in the green a submitted answer is drawn in, reading like a fourth option rather than a footnote to the three. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(portal): give quickstart the api transform hint, with the format it needs Quickstart checks the spec itself before it writes the project, so the transform hint portal generate and portal serve now give never reached it. A Postman collection or a RAML file was told only that it was not an OpenAPI document, with nothing to do about it. Both now end on one sentence, which names --format=openapi3yaml: the transform refuses to run without a format, so the command as printed before would have failed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(quickstart): ignore only what is still generated into the project * engines.node is now >=24.0.0. Node 22 is no longer supported. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(sdk): build the validation service once configDir is assigned `validationService` was initialised from `this.configDir` in a property initialiser, but `configDir` is a constructor parameter property. The code only worked because TypeScript's downlevel emit assigns parameter properties before property initialisers; under the native class-field semantics that a modern target selects, `this.configDir` is still undefined at that point and the service would be constructed with no config directory. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * build: compile with TypeScript 7, keeping TypeScript 6 for linting TypeScript 7 is the native compiler. On this repo it builds `src` in ~1.3s against ~4.7s, and type-checks the suite in ~1.0s against ~4.8s. Emitted output is unchanged apart from sourcemaps, temp-variable naming and declaration ordering. typescript-eslint cannot run on it: every published version, canary included, declares `typescript >=4.8.4 <6.1.0` and throws at module init on TS 7, which would take out `lint`, `posttest` and the pre-commit hook. So the two sit side by side in Microsoft's documented arrangement — `tsc` is TypeScript 7 and builds, bare `typescript` is the TypeScript 6 shim that only the linter loads. Naming them this way rather than aliasing TypeScript 7 keeps the compiler each script gets written down, and survives `pnpm update --latest`. TypeScript 6 stopped sweeping node_modules/@types, so the test project names the globals the suite needs; the source project resolves its types through `node:` imports and needs no list. `target` moves to es2024 now that the engine floor is Node 24, which also selects native class-field semantics. `ts-node` goes: it drives the classic compiler API, which TypeScript 7 no longer ships, and the suite runs on tsx. `@types/mocha` was six years behind mocha and had to be named explicitly. The `typescript.tsdk` pin is dropped. It is deprecated in favour of `js/ts.tsdk.path`, inert unless a developer opts in by hand, and would now point at a TypeScript 6 build within a patch of what VS Code already bundles. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: test on Node 24 and 26, and release on 24 The engine floor is Node 24, so the 22 legs go. The second leg is 26 rather than the floor alone: 26 becomes LTS on 2026-10-28, inside this major's life, so it is what most developers will be running. The job count is unchanged. Releasing on Node 24 drops the `npm install -g npm@latest` step, whose only purpose was that Node 22 bundles npm 10.9.8 while OIDC trusted publishing needs 11.5.1. Node 24 bundles 11.19.0. The constraint moves to a comment on the matrix, since pinning that job back to 22 would silently break publishing. `npm-tag-latest.yml` carried a `node: [ '14' ]` matrix that no step consumed; the job never sets up Node at all. The standalone pnpm in test.yml is kept rather than reverted to corepack. Its original reason is gone, but it is a SHA-pinned executable that needs no Node of its own, where corepack would resolve pnpm from the registry at run time and need the three-step setup-node dance the other workflows use. The comment now states the reason that actually holds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(vscode): repair the CLI debug configuration `preLaunchTask` named a task the TypeScript extension provides, and that provider is not registered when the extension stands down for TypeScript 7, so the configuration would fail to start. The npm task provider supplies `build` regardless, and still rebuilds before launching. `program` pointed at `bin/run`, deleted in the ESM migration; it only launched because Node appends `.js`. The forty commented-out argument lines carried another machine's absolute paths. `.gitignore` re-ignored `.vscode/launch.json` in its custom section, cancelling the allowlist above it. Inert while the file is tracked, but it would have refused a re-add. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: record the two-compiler toolchain and how to remove it The `typescript` dependency no longer being the compiler is surprising enough to look like a mistake, so state why it is there, which command gets which compiler, and the condition for deleting the arrangement. Also corrects the build commands, which documented `tsc -b` although the project has no references and the scripts call plain `tsc`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: remove unused action * fix: broken test * fix: mock fs broken in node 26 * ci: install pnpm standalone in the build and release workflows Corepack is not distributed with Node 25 and newer, so the Node 26 leg of check_build could not bootstrap pnpm. Also correct the release floor: npm 11.5.1, which trusted publishing needs, first ships in Node 24.5.0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(hooks): check the running Node against the declared engine range Read engines.node from the manifest oclif has already parsed and compare with semver, so package.json is the single source of the floor and a minor-version floor is honoured. Test the hook's behaviour on both sides of the range, and hold the CLI's range to the Node floors the portal template's dependencies declare. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: name the e2e test that depends on the two-compiler arrangement Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore: untrack launch.json and drop leftover config launch.json goes back to being a personal, ignored file. mocha.opts named ts-node and has been unused since mocha 8. ESLint now skips Claude Code worktrees under .claude. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(portal): bind the prerender preview server to 127.0.0.1 The prerender pass fetched pages from a preview server on `localhost`. On macOS it listened on `::1` only; a stalled connect under load fell back to 127.0.0.1 and was refused, failing the portal build. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
🎉 This PR is included in version 2.0.0-beta.1 🎉 The release is available on: Your semantic-release bot 📦🚀 |
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.


Builds on #355, now merged into
dev. Implements.ai/plans/generated-pages.md: the CLI generates an SDKs page, one page per configured language, and a context plugin page whenapimatic.jsonhas apluginblock, and injects them into the portal as a second Fumadocs collection./sdks, one page per language at/sdks/<language>in the order thelanguagesblock lists them, positioned byapimatic:sdks./context-plugin, positioned byapimatic:plugin, present only with apluginblock./sdksand/context-pluginare reserved: a content page served there is refused, naming each file.portal serve, adding or removing a language or thepluginblock updates the pages without a restart.portal-pages/are placeholders; the design team's pages and the backend data land in later PRs.Progress
Verification
Rebased onto
devafter #355's squash merge. #355's review hadapplyConfigreplace files whole while the preview watches, so the last commit does the same for the generated pages.Run locally on Windows after the rebase:
pnpm test: 1039 passing, ESLint clean.APIMATIC_E2E=1): 32 passing, including the template type-check, the SDK and context plugin pages with their.mdtwins, the default tab order, and no build path published.vite devon the rebased branch: adding and removing thepluginblock and a language throughapplyConfigreached the page tree within half a second, with no failed request.🤖 Generated with Claude Code