Memory Appendix
Octopus Docs
How nav/sidebar/prev-next works in frontend/octopus-docs (Fumadocs), and the dev-mode cache gotcha when editing meta.json
Source memory file:
project_octopus_docs.md· Category: Project / investigation This is a verbatim dump of Claude's persistent memory for the Octopus project. Rendered inside a code block so nothing is altered.
---
name: Octopus Docs Site
description: How nav/sidebar/prev-next works in frontend/octopus-docs (Fumadocs), and the dev-mode cache gotcha when editing meta.json
type: project
originSessionId: 68969200-9108-461b-8cf0-2442274153c8
---
## Stack
`frontend/octopus-docs/` — Next.js + Fumadocs (`fumadocs-mdx` + `fumadocs-ui`). Content lives in `content/docs/`. A single catch-all route at `src/app/docs/[[...slug]]/page.tsx` renders every `.mdx`. Static export to `out/`; dev server is `next dev -p 3002`.
## How nav is added
The sidebar, prev/next cards, and TOC are **driven by `meta.json` files**, not by file presence. An `.mdx` file that exists on disk is reachable by URL but won't appear in any nav unless it's listed in `meta.json`.
The site uses **route-group section metas** (a `meta.json` with `"root": true` inside a `(group)/` folder), and the **nearest `root: true` meta owns that whole section's sidebar** — via a *flat* `pages` list of **prefixed subpaths** plus `---Section Name---` separators. Editing the leaf folder's own `meta.json` (e.g. `(guides)/guides/meta.json`) only sets that folder's title; it does NOT drive the sidebar. A page listed only in the leaf meta (not the root meta) still renders by URL but shows up as a hidden `fallback:` node and never appears in the sidebar.
Concretely (Walkthroughs/Best Practices live under one root):
- **`content/docs/(guides)/meta.json`** — `root: true`, `title: "Guides"`. Its `pages` array is the source of truth for BOTH the Walkthroughs and Best Practices sidebars, using entries like `"guides/esim-flow"` and `"best-practices/security"` grouped by `"---Walkthroughs---"` / `"---Best Practices---"`.
- **`content/docs/meta.json`** (top-level) — only lists the route groups: `["(get-started)", "(guides)", "reference", "changelog"]`. Not where individual pages go.
- **`content/docs/(guides)/guides/meta.json`** — leaf; sets the folder title only. Adding a page here alone does nothing for nav.
To add a walkthrough/best-practice page: drop the `.mdx` in the folder, then add `"guides/<slug>"` (or `"best-practices/<slug>"`) to **`(guides)/meta.json`** `pages` in the desired position.
**Why:** Discovered twice — `best-practices/observability.mdx` (old top-level structure), and `guides/sell-on-g2a.mdx` (2026-07, new route-group structure): the page rendered, was schema-valid, vertical-enabled, and in the leaf `(guides)/guides/meta.json`, but was missing from `(guides)/meta.json` → rendered as a `fallback:` node and absent from the sidebar in BOTH dev and a clean `bun run build`. Don't blame dev cache for a *missing* (vs stale-ordered) nav entry — check the `root: true` section meta first.
**How to apply:** To add/reorder a docs page, find the nearest ancestor `meta.json` with `"root": true` and edit its `pages` (prefixed subpaths). Don't rely on the leaf folder meta.
## Dev-mode footer cache gotcha
After editing `meta.json`, the **sidebar updates immediately on next request** (it re-reads the tree per render), but the **prev/next footer cards stay stale** until the `next dev` process is restarted.
Cause: `fumadocs-ui/dist/utils/use-footer-items.js` keeps a module-level `Map` keyed by `root.$id` and caches the flattened page list. HMR mutates the tree in place without bumping `$id`, so subsequent `useFooterItems()` calls return the cached old neighbours. A full process restart clears it; a production `next build` is unaffected.
**Why:** Confirmed by editing `meta.json`, seeing the sidebar mark the new page active, but `/docs/best-practices/handling-errors`'s "next" card still pointed at the old neighbour.
**How to apply:** After any `meta.json` edit, tell the user the change is correct and offer to restart the dev server to verify prev/next visually. Don't waste time debugging "why didn't the cards update" — it's the cache.
## Breadcrumb above the title needs `includeSeparator`
`DocsPage` from `fumadocs-ui/layouts/docs/page` **already renders a built-in breadcrumb** above its children (default `breadcrumbEnabled: true`). Don't add a second `<PageBreadcrumb />` inside `DocsPage` — it double-renders on nested pages (visible as two `<div class="flex items-center gap-1.5 text-sm text-fd-muted-foreground">` siblings).
To make the breadcrumb appear on top-level pages (e.g. show "Best Practices" above the H1), pass through the slot prop:
```tsx
<DocsPage … breadcrumb={{ includeSeparator: true }}>
```
**Why:** The top-level `meta.json` groups pages with `---Section Name---` *separator* entries (not nested folders). With the default `includeSeparator: false`, [`getBreadcrumbItemsFromPath`](../../../node_modules/fumadocs-core/dist/breadcrumb.js) yields zero items for any page that only has separator+page in its path, and the breadcrumb returns `null`. Nested folder pages (e.g. `reference/orders/create-order`) work without the prop because they have real intermediate folder nodes.
**How to apply:** When asked to add or restore the section label above the title, edit `<DocsPage>` props in [src/app/docs/\[\[...slug\]\]/page.tsx](../../../frontend/octopus-docs/src/app/docs/%5B%5B...slug%5D%5D/page.tsx) — don't add a separate `<PageBreadcrumb />` component.