# createNavigation

Creates a `Navigation` over a list of content entries. It builds the sidebar
tree a single time and binds the four lookups a page needs to that one tree, so
the rail, the breadcrumbs, and the pager can never disagree about what the site
contains.

```ts
import { createNavigation } from "@teasim/astra";

const docs = createNavigation(entries, { base: "/docs" });

docs.sidebar;                              // the tree, as getSidebar builds it
docs.prevNext("guides/installing");        // { prev, next }
docs.breadcrumbs("guides/installing");     // [{ label, href, current }, …]
docs.sections("guides/installing");        // top level, with the current one marked
docs.find("guides/installing");            // one node, or null

// prevNext reads the entry's own frontmatter, so a page hand-wires nothing.
docs.prevNext("intro");                    // honours `next: false` in intro's frontmatter

// An explicit override still wins.
docs.prevNext("intro", { next: "guides/installing" });

// Nothing here reads astro:content, touches the filesystem, or needs a build.
createNavigation([{ slug: "a", title: "Alpha" }]).sidebar[0].href; // "/a"
```

## API Reference

### Signature

```ts
createNavigation(entries: readonly ContentEntry[], options?: SidebarOptions): Navigation;
```

### Parameters

| Parameter | Type | Required | Notes |
| --- | --- | --- | --- |
| `entries` | `readonly ContentEntry[]` | Yes | Usually `collection.map(toContentEntry)`. Order is not significant — the tree sorts by `sidebar.order`, then label |
| `options` | `SidebarOptions` | No | `base`, `trailingSlash`, `includeDrafts`, `transform` |

#### SidebarOptions

| Option | Type | Default | Notes |
| --- | --- | --- | --- |
| `base` | `string` | `"/"` | Prefix for every `href`; a path or an absolute URL. A path with no leading slash is still rooted |
| `trailingSlash` | `boolean` | `false` | Append `/` to every `href` except the root |
| `includeDrafts` | `boolean` | `false` | Keep entries marked `draft` |
| `transform` | `(nodes: SidebarNode[]) => SidebarNode[]` | — | Reshape the finished tree |

### Returns

Returns a `Navigation` — the tree plus four lookups bound to it.

| Member | Type | Notes |
| --- | --- | --- |
| `sidebar` | `SidebarNode[]` | The finished tree, already sorted and pruned |
| `find(slug)` | `SidebarNode \| null` | `null` for a heading, an unknown slug, or an empty one |
| `breadcrumbs(slug)` | `Breadcrumb[]` | Ancestry, `current: true` on the last crumb; `[]` for an unknown slug |
| `prevNext(slug, overrides?)` | `PrevNext` | Both ends `SidebarLink \| null`; never wraps |
| `sections(slug)` | `SidebarSection[]` | The top level, with the one holding `slug` marked `current` |

### Throws

**Nothing.** Bad content does the most sensible thing and carries on — two
entries that normalise to one slug become one node, last wins. That is how a
duplicate slug becomes one silently missing page, so run `validateContent` on
the same entries when you want the problems named instead — the scaffolded
`src/helpers/docs.ts` does exactly that, in dev only.

## Agent Contract

| Field | Value |
| --- | --- |
| Kind | factory |
| Canonical name | `createNavigation` |
| Aliases | None |
| Mutates input | No |
| Returns | `Navigation` |
| Reads `astro:content` | No |
| Needs a build | No |
| Configuration dependencies | None |
| Related | `getSidebar`, `getPrevNext`, `getBreadcrumbs`, `getSidebarSections`, `findSidebarNode` |

## Agent Notes

- Generate `createNavigation(entries, options)` for a page. The five underlying
  functions stay exported for a test driving one answer, or a page that wants
  one without the rest.
- Do not thread `{ prev: entry.data.prev, next: entry.data.next }` into
  `prevNext` — it reads the entry's frontmatter itself. A mapper that forgets
  makes those two schema fields silently inert.
- Build it once per page, not once per lookup. Each call rebuilds the tree.
- Map entries with `toContentEntry`, not by hand: a hand mapper that drops
  `externalLink`, `prev`, `next`, or `previousSlug` makes those fields inert and
  `validateContent` cannot report what it never receives.
- Both ends of `prevNext` are `SidebarLink`, whose `slug` and `href` are never
  `null`. Do not generate a null check for them.
- `breadcrumbs` keeps headings and `prevNext` skips them, so a trail is not a
  slice of the reading order. Do not derive one from the other.
- Pass `base: import.meta.env.BASE_URL` in an Astro project so every `href`
  survives a deploy under a subpath.