Skip to content

feat(portal): a tabs key in the root nav.json - #408

Merged
MuHamza30 merged 31 commits into
devfrom
saeedjamshaid/nav-json-tabs
Oct 2, 2026
Merged

MuHamza30 merged 31 commits into
devfrom
saeedjamshaid/nav-json-tabs

Conversation

@MuHamza30

@MuHamza30 MuHamza30 commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

The change

The root src/content/nav.json had one list, pages, and it carried two decisions: which top-level folders are tabs, and where Home's nodes go. Naming a folder in pages made it a tab, so a folder could never be ordered inside Home. Unnamed folders all fell together where ... stood, in alphabetical order, and the only way to order them was a parent folder, which moves every address below it.

The root file now has two lists. The plan is in .ai/plans/portal-config.md, sections 5 and 16, added by the first two commits on this branch.

{
  "title": "Overview",
  "tabs": ["tutorials", "apimatic:sdks", "apimatic:api"],
  "pages": ["index", "getting-started", "authentication", "guides", "apimatic:plugin", "..."]
}
  • tabs lists the tabs after Home, in order, and nothing else is a tab: a folder directly under content/, api or apimatic:api, apimatic:sdks and apimatic:plugin. It is valid only in the root file.
  • pages orders Home's sidebar, as every other nav.json orders its folder. A folder named here is a group inside Home, and a token stands for its section. What pages does not name follows in Fumadocs' order: alphabetical, pages before folders, a section among the folders like any other.
  • Home alone. A file with no tabs, "tabs": [] or no file at all makes no tab, so Home is the only tab and the header shows no tab bar. Every section keeps its address inside Home.
  • A fresh portal keeps its tabs. The scaffold and the sample repository's root file name the three sections in tabs, so a portal from quickstart looks as it did.

Commits

  1. Template. navigation-schema.ts extends Fumadocs' metaSchema with tabs, and the docs collection validates nav.json against it, so the key is no longer stripped. groupIntoTabs makes tabs from tabs, keeps a section pages names in Home, and orders the tabs through reorder. portalTabs returns no tab for Home alone.
  2. CLI. PortalNavigation validates tabs: a page, ..., a folder serving /, an unknown name, a duplicate, and a node also in pages are each refused with their own sentence, and tabs below the root is refused. The root pages accepts the tokens and a folder serving /. The content tree names tabs from tabs and leaves sections placed in Home out of the shared-name warning. The notice naming the folders made into tabs is removed, and makesSameTabsAs becomes makesSameSectionsAs. The default fixture moves its tabs to tabs.
  3. E2e and README. A new sections-in-home fixture and build, and the README's tabs bullet rewritten.
  4. SonarCloud. Shared e2e helpers, a table-driven test and a split function, for the duplication and complexity it flagged.
  5. First review. The scaffold writes tabs beside pages; a misspelt setting at the root is answered with tabs among the settings; the README states the real default for an unnamed section; unnamed sections follow everything tabs names; api in tabs is refused beside a page of that name; a mistyped tab is pointed at what tabs accepts.
  6. Second review. A section tabs did not name used to keep a tab at a default place, so {} gave Home and three tabs while every folder went into Home. Now tabs is the whole tab bar: a section it does not name is a folder in Home, in Fumadocs' order, and reorder loses its bands and anchor. A root file with no tabs reads as an empty list, so the refusal of a 2.0.0-beta.1 file goes with the ambiguity it guarded. The scaffold and the sample repository (Name the three generated sections as tabs in nav.json sample-docs-as-code-portal#15) name the three sections in tabs. README and plans updated.

No BREAKING CHANGE: footer: nav.json is new since 1.x, so nothing breaks against the last stable release. The next beta's release notes should say what changed for a beta.1 root file: a folder or token its pages named is in Home until tabs names it.

Checked

  • The unit suite passes: 1547 passing, 47 pending.
  • tsc -p test/tsconfig.json, and ESLint and Prettier on the touched files, pass.
  • E2e: all three builds pass, 47 cases. The default build keeps its four tabs from tabs; the branded build, which has no nav.json, renders no tab bar and serves every section inside Home; the sections-in-home build is unchanged.

🤖 Generated with Claude Code

saeedjamshaid and others added 5 commits September 29, 2026 13:33
The root nav.json's `pages` decides two things at once: which top-level
folders are tabs, and the order of Home's sidebar. A folder cannot be
ordered inside Home without becoming a tab.

The amendment gives the root file a `tabs` list for the tab bar and leaves
`pages` ordering Home, as it orders every other folder. The apimatic:
tokens can go in either list, and stay refused below the root. A beta.1
root file that relies on the old reading is refused until 2.0.0, and a
portal whose only tab is Home hides the tab bar.

Section 5 of portal-config.md states the rules and section 16 the
delivery; portal-navigation.md and generated-pages.md point to them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The beta.1 refusal no longer counts a folder serving `/`, which beta.1
itself refused, so no beta.1 file can hold one. The navigation context
gains the subfolders with a page in them, since `childNames` cannot tell
a page from a folder and both new refusals need to.

Section 16 now names the doc comments and existing tests that state the
old rule, holds every setting the CLI accepts to the template's schema,
and says the e2e suite is red between the template and CLI steps. The
serve paragraph speaks of sections rather than tabs, and
portal-navigation.md counts `tabs` as the third setting now that `root`
is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The root nav.json's `pages` carried two decisions: which top-level
folders are tabs, and where Home's nodes go. A folder could not be
ordered inside Home without becoming a tab. `tabs` now takes the first
decision, and `pages` orders Home's sidebar as every other nav.json
orders its folder (.ai/plans/portal-config.md, sections 5 and 16).

- navigation-schema.ts extends Fumadocs' metaSchema with `tabs`, typed
  as `pages`, and the docs collection validates nav.json against it,
  so the key survives. Unknown keys are still stripped.
- A folder is a tab when `tabs` lists it. The API reference and each
  generated section are tabs unless the root `pages` names them, which
  places them in Home at the address they always had.
- The tabs are ordered by `tabs` through `reorder`, with the defaults
  the root `pages` gave before for what it leaves unnamed.
- portalTabs returns no tab when Home is the only one, so the header
  shows no switcher over a single choice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CLI holds `tabs` to the rules the template now reads it by
(.ai/plans/portal-config.md, sections 5 and 16).

- `tabs` is known in the root nav.json and refused below it with a
  sentence of its own. Its entries are a folder with a page under it,
  `api` or `apimatic:api`, `apimatic:sdks` and `apimatic:plugin`. A
  page, `...`, a folder serving `/`, an unknown name, a duplicate and a
  node `pages` also names are each refused.
- The root `pages` may now name a token, `api` or a folder serving `/`,
  each placed in Home. Below the root a token is still refused, and the
  sentence names both places it can go.
- A root file with no `tabs` whose `pages` names a folder, `api` or a
  token is refused, since 2.0.0-beta.1 made tabs of those entries and
  reading it under the new rule would move them into Home's sidebar.
  The check goes before 2.0.0 ships.
- The content tree takes folder tabs from `tabs`, and leaves a section
  placed in Home out of the tabs and out of the shared-name warning.
- The notice naming the folders the file made tabs of goes: `tabs` says
  so in its own name. makesSameTabsAs becomes makesSameSectionsAs.
- The default fixture moves its tabs from `pages` to `tabs`.

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

- A third e2e build, from the sections-in-home fixture, whose root
  nav.json places the API reference, the SDKs and the Context Plugin in
  Home with `pages` and names no tab. It checks the prerendered sidebar
  holds Home alone with each section where `pages` names it, that each
  section keeps its address, and that the header renders no tab bar.
- The README's bullet on tabs is rewritten for `tabs` and `pages`, the
  tokens placeable in either, and the refusal of a 2.0.0-beta.1 file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MuHamza30 and others added 2 commits September 29, 2026 15:47
- entryErrors hands each entry to listEntryError, which brings the
  function under the cognitive-complexity limit.
- The e2e builds share portalBuilt, which registers the before and after
  hooks and reads the output, and tabPositions, which finds each tab's
  link in the header, instead of repeating both per describe block.
- The tab-order defaults in tabs.test.ts are one table-driven loop.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…can act on

Found reviewing the tabs change before handing it over.

- The scaffold writes `"tabs": []` beside `pages`, so a folder named in a
  scaffolded file is placed in Home rather than refused.
- The refusal of a root file with no `tabs` no longer leans on beta.1
  history: it says the file does not say whether the entries are tabs or
  in Home's sidebar, and how to say either.
- A misspelt setting at the root is answered with `tabs` among the
  settings, which the list left out.
- The README states the real default for a section named in neither
  list: after the folders `tabs` names, before a reference not named
  ahead of them.
- Comments trimmed to one line each; a dead guard in isTabFolder and a
  membership scan in the content walk are gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@sohail2721 sohail2721 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Three findings from a review of this branch, each reproduced against 20543bb with both transformers applied. The first two look like bugs, the third is a usability gap in the error text.

Comment thread portal-template/src/lib/navigation.ts Outdated
Comment thread src/types/portal/portal-navigation.ts Outdated
Comment thread src/types/portal/portal-navigation.ts Outdated
MuHamza30 and others added 3 commits September 30, 2026 09:45
The unnamed sections' tab position reused the sidebar's anchor, which
sits after the last named user content. A tabs list naming only the
reference and tokens has none, so the anchor fell to the front and the
unnamed sections landed ahead of a reference the file deliberately put
first: tabs ["apimatic:api"] gave Home, SDKs, Context Plugin, API
Reference.

In the tab bar every folder is named, so the unnamed band now follows
everything `tabs` names, a named reference included, with an unnamed
reference still last. The sidebar keeps its own anchor. The README
states the rule as one sentence.

Found in review (PR #408, first comment).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With a page at `content/api.md`, naming `api` in `pages` is refused,
since the entry positions the mounted reference and the page could
never be placed. The same name in `tabs` was accepted before the
shared-name test ran, letting through the quietly wrong sidebar the
validator exists to refuse. The tab entry now gives the refusal
`pages` gives, from one shared sentence.

Found in review (PR #408, second comment).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The not-found sentence for a tabs entry borrowed the pages hint, which
could point at an entry tabs then refuses: a capitalised page name was
answered with "Did you mean 'authentication'?", and following it earned
"'authentication' is a page" one run later. A near miss of `api` named
only the token, although the folder name is accepted too.

The tabs path now has a hint of its own: a folder near miss stays a
suggestion, a section is offered as its token, `api` is offered in both
spellings, and a page's name is answered in one message with where a
page can go.

Found in review (PR #408, third comment).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@sohail2721 sohail2721 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Second pass on 0f9900a. The three earlier findings are fixed and their tests pin the cases; the full unit suite passes on this head after a build. Three further findings below: one worth fixing before merge (the README line endings), two minor.

Comment thread README.md Outdated
Comment thread src/types/portal/content-tree.ts Outdated
Comment thread src/types/portal/portal-navigation.ts Outdated
MuHamza30 and others added 3 commits September 30, 2026 11:55
An edit on this branch rewrote every line ending from CRLF to LF, so
the whole file diffed and hid the 24 lines that actually changed, and
any other branch touching the README would conflict on every line. The
endings are back as dev stores them; the words are untouched.

Found in review (PR #408).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The tab list behind the shared-name warning emitted the folders, then
the generated sections, then the reference, ignoring where `tabs` put
the tokens: with `tabs` naming a section before a folder, the warning
listed them the other way around, and the doc comment still promised
the report's order. The list now walks `tabs` and emits each entry in
place, then the unnamed sections and an unnamed reference, mirroring
what the template renders.

Found in review (PR #408).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ne helper

The tabs hint matched a folder near miss on the lowered name alone,
while the page lookup beside it also stripped a stray extension, so
'Guides.md' got no suggestion in `tabs` although the same entry in
`pages` did. The lowering, stripping and matching now live in one
nearMiss helper that answers what an entry was meant as, and each list
phrases its own sentence over it, so the two cannot drift again.

Found in review (PR #408).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

… a folder in Home

A generated section and the API reference were tabs unless the root `pages`
placed them in Home, so `{}` and `"tabs": []` gave Home and three tabs while
every folder went into Home, and the tab bar was not what `tabs` said. Now a
node is a tab exactly when `tabs` names it: a folder by name, the reference
by `api` or its token, a section by its token. Everything else is in Home,
where `pages` orders it and, unnamed, Fumadocs' own order holds, a section
among the folders like any other. `reorder` loses its bands and anchor, and
the root is no longer reordered without a `pages` of its own. A file with no
`tabs`, or no file, leaves Home alone with no tab bar.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
MuHamza30 and others added 2 commits September 30, 2026 15:45
… scaffold the sections as tabs

With `tabs` the whole tab bar, a root file without it has one reading, no
tab, so the refusal of a 2.0.0-beta.1 file goes: there is nothing left to
ask. The content tree names a tab for each `tabs` entry and for nothing
else, so a section the file does not name is compared with no tab's name.
The scaffold writes `apimatic:sdks`, `apimatic:plugin` and `apimatic:api`
in `tabs`, so a fresh portal keeps the tabs it had; the sample repository's
root file does the same. The default fixture names the plugin's token too,
and the branded build, which has no `nav.json`, asserts no tab bar.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The README bullet and the plans said a section named in neither list kept a
tab at a default place, and described the beta.1 refusal. Both now say that
`tabs` is the whole tab bar after Home, that what it does not name is in
Home in Fumadocs' order, and that a file with no `tabs` makes no tab, with a
history entry for the review that decided it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@saeedjamshaid saeedjamshaid left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed at b387807c. The behaviour matches the plan. Two things I'd want before merge: a word to projects that lose their tabs on upgrade (inline at portal-navigation.ts:123), and the prompt text below that still assumes every section is a tab. The rest are code-quality points against the standard #374 set, most of them one rule living in several places.

Two points sit outside the diff:

  • src/prompts/portal/serve.ts:54 still says removing the plugin block "removes the Context Plugin tab". The plugin is a tab only when tabs names it.
  • src/prompts/portal/source.ts:315-316 still says "the tabs of the SDK pages and the context plugin keep their names".

On #374's sixth principle nothing changes: PortalNavigation worded its errors itself before this PR too. NearMiss is a step toward keying them by kind, though suggestion and tabSuggestion still word their sentences inside the type.

Comment thread src/types/portal/portal-navigation.ts Outdated
Comment thread portal-template/src/lib/navigation.ts
Comment thread src/types/portal/portal-navigation.ts Outdated
Comment thread src/types/portal/portal-navigation.ts Outdated
Comment thread src/types/portal/portal-navigation.ts Outdated
Comment thread portal-template/src/lib/navigation.ts Outdated
Comment thread portal-template/src/lib/tabs.ts Outdated
Comment thread test/portal-template/navigation-schema.test.ts Outdated
Comment thread portal-template/src/lib/navigation-schema.ts
Comment thread test/resources/portal-inputs/sections-in-home/src/content/nav.json Outdated
MuHamza30 and others added 4 commits September 30, 2026 17:35
…he tab list and the hints

`api` and `apimatic:api` being one node was ruled on in five places. Now
`target` says once what an entry names -- the rest entry, an `apimatic:`
string that positions nothing here, the API reference, a section, a folder,
a page, an empty folder or nothing -- and the duplicate check, each list's
rules, the tabs the content tree names and the near-miss hint read that
answer. `NavigationSettings.tabs` carries the resolved tabs, undefined for
a file with no list. The lists are typed `'pages' | 'tabs'` with a constant
each, an entry with its list is one named type, and the sentences a page in
`tabs` and an empty folder get are written once. A list of the wrong type no
longer hides the other list's entries, and a page named `api` is refused
under either spelling of the reference, since it could be positioned under
neither.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The scaffold rebuilt `TOKENS` from its parts; the one list now serves both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…I accepts

The test hard-coded `tabs`, so a setting added to `ROOT_FIELDS` would have
been validated by the CLI and stripped by the template with nothing failing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A root file with no `tabs`, or no root file, makes no tab, and a
2.0.0-beta.1 project had the sections as tabs, so the upgrade was silent.
The build now says so once, naming the sections in Home and both ways to
settle it: the tokens in `tabs` to have them back, or `"tabs": []` to keep
the sidebar, which says nothing. Under `portal serve` it is given once, like
the other notices, and again when the plugin block changes what is in Home.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
MuHamza30 and others added 6 commits September 30, 2026 17:36
A root `title` names the Home tab, and one tab draws no tab bar, so with
nothing in `tabs` the name showed nowhere and the CLI accepted a setting
that did nothing. It now says so once. The sections-in-home fixture drops
the title it never showed, and its build looks for a Home link instead.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…lugin tabs

They are tabs only when `tabs` names them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`portalTabs` builds its list and returns none for a single entry, which
says "one choice switches nothing" directly and covers a tab dropped for
having no link. `HOME_TAB_ID` goes back to `navigation.ts`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`groupIntoTabs` matched each folder to a `tabs` entry and `reorder` then
matched the entries back to folders. `nodeNamed` now resolves an entry to
its node for both, and the tabs are the nodes `tabs` names, in its order;
`isTab`, `isTabFolder` and `lists` go. The section check runs the cheap
path test before the storage read.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The rule lives in the code and in the plan; the doc comments keep only what
the code does not say.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The two notices, the entry resolver, and the alphabetical place of an
unnamed section kept on purpose.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@MuHamza30

Copy link
Copy Markdown
Contributor Author

The two points outside the diff are reworded in 0fb23fd: removing the plugin block removes the Context Plugin pages, and the shared-name warning says the SDKs and Context Plugin sections keep their names.

Each finding has a commit of its own from 001ca25 to d7c46c4. On the new head the unit suite passes (1559), the three e2e builds pass (47), and typecheck, ESLint and Prettier are clean.

… and the schema test

The notice built its suggestion inside a nested template literal, and the
schema test passed the same field to the assertion twice.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@saeedjamshaid saeedjamshaid left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Second pass at 019ec6f7. 12 of the 13 threads are fixed as asked, and I've resolved them; the alphabetical-order one stands as decided. One fix brought in a new refusal, inline below. Two minor points, neither blocking:

  • The no-tabs warning names the generated sections and the API reference, but in beta.1 a user folder the root pages named was a tab too. A beta.1 project with {"pages": ["index", "tutorials", "..."]} that follows the warning's "tabs" line loses its Tutorials tab without a word. Naming those folders in the suggested line would cover it.
  • TabEntry (portal-navigation.ts:68) and TabOwner (portal-tabs.ts:7) still share three kinds (API reference, section, folder), differing in a folder's name against its directory, plus Home. Much smaller than before; fine to leave if the split is wanted.

Comment thread src/types/portal/portal-navigation.ts Outdated
MuHamza30 and others added 3 commits October 2, 2026 10:41
…oken

Resolving both spellings to the reference had the shared-name refusal fire
for `apimatic:api` too, so every scaffolded portal that added `content/api.md`
failed to build, though the entry was the token and the page was never meant
to be positioned. Only `api` spelled out might have meant the page, so only
that spelling is refused, as on dev; `target()` is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Before `tabs` existed a folder the root `pages` named was a tab as well, so a
2.0.0-beta.1 project that followed the notice's suggested line got its sections
back and lost those tabs without a word. The notice now names them, and the
line leads with them in `pages` order, as beta.1 showed them; a `(group)` folder
serving the home page is left out, since `tabs` refuses it. Under `portal
serve` a folder newly named gives the notice again, once.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The third-review entry said the default order is rarely seen; it is seen by
every existing portal the moment a release adds a section, since the scaffold
writes `nav.json` once, so that release must tell them to name its token. The
entry also claimed the `api` page was refused under either spelling, which the
fix before this corrects.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@MuHamza30

Copy link
Copy Markdown
Contributor Author

The two minor points and the plan correction, each in its own commit:

  • 4cbbdb7: the no-tabs notice names the folders the root pages named as well, and its suggested line leads with them in pages order, as beta.1 showed them. A (group) folder serving the home page is left out, since tabs refuses it. Under portal serve a folder newly named gives the notice again, once.
  • TabEntry and TabOwner stay split: TabOwner carries Home and a folder's directory, which the validator never has.
  • 30e7885: the third-review entry now says a release that adds a section lands it alphabetically in Home in every existing portal, so that release must tell them to name its token; and a fourth-review entry records this round.

On the new head the unit suite passes (1560), and typecheck, ESLint and Prettier are clean.

@saeedjamshaid saeedjamshaid left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Third pass at 30e7885f. The apimatic:api fix and the plan correction are in, and I've resolved that thread; keeping TabEntry and TabOwner apart is fine for the reason given. One change left on the no-tabs notice, inline.

Comment thread src/prompts/portal/source.ts Outdated
The line the notice offered was refused when followed: a folder or token the
root `pages` named went into `tabs` too, and a node is in one list or the
other. A folder in `pages` is in Home because that is what `pages` means, so
the folders go, and the line names only the sections and the reference that
nothing places; once `pages` places them all, the notice says nothing. The
validator answers with what `pages` places, so the `api` spelling stays ruled
on in one place, and the text that leaned on beta.1 is in present terms: the
CLI's messages describe how `nav.json` works now, not what it did.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Oct 2, 2026

Copy link
Copy Markdown

@MuHamza30
MuHamza30 merged commit 9fcf772 into dev Oct 2, 2026
10 checks passed
@MuHamza30

Copy link
Copy Markdown
Contributor Author

QA testing plan

What changed

src/content/nav.json (the root one) now has a tabs list that decides the portal's tabs.

  • tabs names the tabs after Home, in order. Only what is listed becomes a tab: a top-level folder by name, and the generated sections by token (apimatic:sdks, apimatic:plugin, apimatic:api or api).
  • Home holds everything else: top-level pages, folders not in tabs, and sections not in tabs. pages orders Home's sidebar; title names the Home tab.
  • No tabs, or no root nav.json: no tab bar at all (Home only). The build prints a warning with the exact line to add. "tabs": [] means "I want no tab bar" and prints nothing.
  • A new portal is scaffolded with all three sections as tabs.
  • Before this, every section and every folder named in pages was a tab automatically.

Scenarios

Tabs and layout

# Setup Expect
1 Fresh portal (scaffold), build Tabs: Home, SDKs, Context Plugin (if plugin block), API Reference. Root nav.json has "tabs": ["apimatic:sdks","apimatic:plugin","apimatic:api"].
2 "tabs": ["guides", "apimatic:api"] with a content/guides/ folder Tab bar: Home, Guides, API Reference, in that order. SDKs appear as a folder in Home's sidebar.
3 "tabs": ["apimatic:api", "guides"] Same tabs, API Reference before Guides.
4 "tabs": ["api"] Same as apimatic:api.
5 "tabs": [] No tab bar. Sections and folders all in Home's sidebar. No warning.
6 Root nav.json without tabs, e.g. {"pages": ["index", "..."]} No tab bar. Build warns once, naming the sections now in Home and suggesting "tabs": [...] with their tokens. Pasting that line makes them tabs with no error.
7 No root nav.json at all Same as 6, worded "There is no content/nav.json".
8 No tabs, but pages names a section, e.g. {"pages": ["index", "apimatic:sdks", "..."]} Warning lists only what pages does not name (API Reference, plugin). If pages names all sections and api, no warning.
9 {"title": "Docs", "tabs": []} Warning: the title shows nowhere because Home is the only tab. Build still succeeds.
10 {"title": "Docs", "tabs": ["apimatic:api"]} Home tab is labelled "Docs". No warning.
11 Folder in tabs whose nav.json has a title Tab shows that title; otherwise the folder name.
12 Section in Home ("pages": ["index", "apimatic:sdks"], not in tabs) SDKs folder sits in Home's sidebar where pages puts it; /sdks still opens.

Errors (build must fail with a clear message)

# Setup Expect message about
13 "tabs": ["authentication"] where that is a page A page cannot be a tab; use pages.
14 "tabs": ["..."] ... cannot be a tab.
15 "tabs": ["guides"] and "pages": ["guides"] Named in both lists; keep one. Same for api in one and apimatic:api in the other.
16 "tabs": ["guides", "guides"] Listed more than once.
17 tabs inside a subfolder's nav.json Only read in the root file.
18 "tabs": ["apimatic:foo"] Not a token; lists the valid ones.
19 "tabs": ["Guides"] or ["guides.md"] Hint: did you mean guides?
20 "tabs": ["API"] Hint: use api or apimatic:api.
21 "tabs": "guides" (not an array) plus a bad pages entry Both errors reported in one run.
22 content/api.md exists and "pages": ["index", "api"] Refused: api is the reference's mount point; rename the page.
23 content/api.md exists and "tabs": ["apimatic:api"] Builds fine (the scaffold default must not break).
24 Empty folder (no pages) in tabs Refused: folder has no page.

portal serve

# Action Expect
25 Start with no tabs, then save the file unchanged Warning once at start, not again on each save.
26 While serving, add "tabs": [...] Tab bar appears; no more warning.
27 While serving, add a plugin block with no tabs set Warning repeats once, now naming Context Plugin too.
28 Introduce any error from 13-24 while serving Error shown; preview keeps the last good build.

Upgrade

# Setup Expect
29 A 2.0.0-beta.1 project (root nav.json with pages only) Builds, tab bar gone, warning from scenario 6. Adding the suggested line restores the section tabs. A folder that used to be a tab must be added to tabs by hand and removed from pages.

Also check: llms.txt / Copy Markdown still work, and the tab's first page opens correctly for the API Reference tab (operations grouped under folders).

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 2.0.0-beta.3 🎉

The release is available on:

Your semantic-release bot 📦🚀

saeedjamshaid added a commit that referenced this pull request Oct 5, 2026
…writes

Since #408 a portal with no root nav.json has no tab bar, and the API
reference is a collapsed folder in Home, so the fixture's home page no
longer linked a page under /api/api. The file quickstart scaffolds names
the three sections as tabs, which puts that link back and keeps the
fixture what a fresh portal looks like.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants