# `DocShell.Presentation.StaticGenerator`
[🔗](https://github.com/futhr/doc_shell/blob/v2.0.0/lib/doc_shell/presentation/static_generator.ex#L1)

Builds navigation, search, and content indexes from extracted entries.

This is the default `DocShell.Presentation.Source` — the one
`DocShell.Build` uses unless a host substitutes its own. It takes the flat
list of entries the extractors produced and derives the three indexes a
documentation site needs:

  * **navigation** — one `DocShell.Presentation.NavigationItem` per entry,
    sorted by kind, title, then ID, so modules, guides, notebooks, and release notes group
    together and each group reads alphabetically.
  * **search** — one `DocShell.Presentation.SearchEntry` per entry, with the
    document flattened to plain text and optional precomputed tokens.
  * **content** — a map from entry id to its AST nodes, so a renderer can
    load one page without parsing the whole set.

## Module members

A module's navigation item does not carry its members' documentation, which
for a large application can be most of the documentation there is. Its
`meta["members"]` lists one reference per member that is not `@doc false`:

    %{"id" => "function:run/1", "title" => "run/1", "anchor" => "run/1", "kind" => "function"}

`anchor` follows ExDoc's fragment convention (`t:` for types, `c:` for
callbacks), so a renderer can link to the member on the module's page. The
documentation itself is in `modules.json`. A module whose `members` value is
not a list of name/arity records fails projection with an error naming the
module. Other kinds keep their metadata unchanged.

## Deliberately flat

Every navigation item comes back with no children. That is not an omission:
DocShell has no way to know whether your guides should nest under a section,
whether modules should group by namespace, or whether the tree should follow
the file layout at all. Those are product decisions, and a package that
guessed at them would be wrong for most hosts and hard to override for the
rest.

A host that wants structure has two options: reshape the flat list after
`DocShell.Build.run/1` returns it, or implement
`DocShell.Presentation.GraphProjector` and own categorization outright.

## Paths

Entry paths default to `/docs/{kind}/{id}`, which is a placeholder more than
a recommendation. Hosts routing documentation anywhere else pass a
`:path_builder` function:

    StaticGenerator.project(
      entries: entries,
      path_builder: fn entry -> "/handbook/" <> entry["id"] end
    )

The same function builds both navigation and search paths, so a search
result and a nav link can never disagree about where a document lives.

## Empty entries

Entries with no content are dropped. `DocShell.Generate.ExDoc` returns every
module it can read, including ones marked `@moduledoc false`, because
coverage reporting needs them — but a navigation tree listing every internal
module as a blank page helps nobody. `skip_empty: false` keeps them.

## Search text

Search content preserves inline text adjacency and separates block elements,
which means code blocks, table cells, and link text are all searchable and
no markup leaks into the index.

`tokens` defaults to `[]`. With `search_tokens: true`, the same text is
downcased and split on non-alphanumeric runs. Enable this when the host's
search backend needs precomputed tokens.

`search_members: true` adds module member names/arities, signatures and parsed
documentation to the containing page's search text. It does not create member
routes or change content ASTs, and does not override `skip_empty`. Malformed
member records/Markdown return an error tagged with the module ID. The default
stays `false` so existing search payloads and extraction cost are unchanged.

## Options

  * `:entries` — the extracted entries to project; defaults to `[]`
  * `:path_builder` — a function from entry to path; defaults to
    `default_path/1`
  * `:skip_empty` — drop entries with no content; defaults to `true`
  * `:search_tokens` — populate `SearchEntry.tokens`; defaults to `false`
  * `:search_members` — include module member text; defaults to `false`

# `default_path`

```elixir
@spec default_path(map()) :: String.t()
```

Returns the fallback path for an entry: `/docs/{kind}/{id}`.

Public so a host writing its own `:path_builder` can fall back to it for
kinds it does not handle specially.

## Examples

    iex> DocShell.Presentation.StaticGenerator.default_path(%{"kind" => "guide", "id" => "intro"})
    "/docs/guide/intro"

---

*Consult [api-reference.md](api-reference.md) for complete listing*
