---
title: "Cross-references & the shape of the site"
description: "The Lopifi site has six views (the docs, the glossary, the component catalog, the toolbelt, the API object model and the live showcase), and all six are generated from the same files in docs/ and src/."
canonical: "https://lopifi.com/docs/architecture/cross-references"
---

# Cross-references & the shape of the site

> The Lopifi site has six views (the docs, the glossary, the component catalog, the toolbelt, the API object model and the live showcase), and all six are generated from the same files in docs/ and src/.

Source: https://lopifi.com/docs/architecture/cross-references
Part of: Lopifi — Planned: US GAAP books on autopilot, checked by a CPA (https://lopifi.com)

---

# Cross-references & the shape of the site

The Lopifi site has six views (the docs, the glossary, the component catalog, the toolbelt, the API object model and the live showcase), and all six are generated from the same files in `docs/` and `src/`.

That's the advantage and the risk. Six generated views means six separate answers to "what does this link to", and when one is wrong the build still passes. You get a link that lands on the right page at the wrong place, or a page nothing points at.

---

## The one rule

**Every surface links outward the same way, through the same component.**

Let each page invent its own related-links block and you end up with pages nothing points at. With one component, an orphan is a missing entry in a list, and it shows up in a diff.

---

## The components

### `SeeAlso`

The standard "where to go from here" block. It takes a list of typed cross-references and renders them as a ruled list with the kind stated on each row.

```tsx
<SeeAlso
  description="A component is specified in one place, defined in another, and running in a third."
  links={[
    { kind: "doc", href: component.docUrl, title: "Payables — the family spec" },
    { kind: "showcase", href: "/showcase/ap", title: "The family, running" },
    { kind: "term", href: "/glossary/three-way-match", title: "Three-way match" },
  ]}
/>
```

**The kind tells the reader what they'll get.** Each surface gives a different kind of answer:

| Kind | What the reader gets |
|---|---|
| `term` | A word, defined in one sentence |
| `doc` | An argument for why something is the way it is |
| `component` | Code you install and then own |
| `tool` | A number, computed |
| `api` | The shape of a payload |
| `showcase` | The thing running, with figures in it |

If a reader can't tell which of those a link leads to, they have to click to find out, and mostly they don't.

### `TermRef` and automatic glossary linking

A reader who hits *three-way match* in the middle of a payables spec can't tell it's defined elsewhere, and a block of terms at the foot of the page only helps someone who has already finished reading. So the renderer links the **first mention** of each glossary term in a document's prose, with tight limits:

- First mention only, and never a term the document itself defines.
- Never inside a heading, a link, a code span or a code block.
- Never a term that is also ordinary English, like *account*, *period*, *balance* or *statement*. Those stay in the glossary but aren't worth interrupting a sentence for.
- Never more than twelve on one page. Past that, the page turns into a glossary.

Term links are **dotted and in the text color** instead of the accent. An author's link is a destination they chose. An auto-linked term is a definition the build offered, so it looks different.

### `PageHeader aside`

The one place on a page where an illustration may sit: beside the header, and dropped entirely on narrow viewports. See [imagery & isometric](https://lopifi.com/md/docs/foundations/imagery-and-isometric).

---

## Anchors: one algorithm, two consumers

Every document in `docs/` is read on this site and on GitHub, and an author writing `#law-2--data-does-not-re-animate` copied that anchor off the GitHub heading.

So `headingId` in `src/lib/markdown.ts` uses **GitHub's slug algorithm** on purpose, and `scripts/build-glossary.mjs` mirrors it when it records the anchor where a term is defined.

GitHub turns *each space* into a dash instead of collapsing runs of whitespace. A heading with an em dash, a slash or an ampersand loses that character and leaves two spaces behind, so `Law 2 — Data does not re-animate` becomes `law-2--data-does-not-re-animate`, with two dashes. Collapse the whitespace and every anchor like that lands at the top of the page instead of at the section.

The two implementations are in different languages in different directories, and `tests/doc-links.test.ts` checks that they agree.

---

## The glossary and the founder explainers

Many concepts have two pages: a definition at `/glossary/<slug>`, generated from the [lexicon](https://lopifi.com/md/docs/domain/lexicon) and the metric definitions, and a founder explainer at `/accounting/<id>` covering what the idea costs a startup and what to do about it in a given month. They answer different questions, so each keeps a self-referencing canonical.

They don't compete because each names the other through one field:

- An explainer's `links.glossary` is its definition. `/accounting/<id>` renders it as the "Definition" link, and `/glossary/<slug>` lists every explainer whose `links.glossary` is that slug under "For founders".
- **Where the two share a slug, `links.glossary` is that slug.** `/accounting/lead-schedule` points at `/glossary/lead-schedule`, never at a neighboring term. Point a shared slug anywhere else and the glossary page loses its founder link while the explainer shows the wrong definition.

If you add a lexicon term whose slug matches an explainer id, update that explainer's `links.glossary` in the same change.

---

## Section indexes

One per directory under `docs/` — nine today — at `/docs/<section>`.

They exist for three reasons:

1. The source markdown links to directories, written as ``[the domain layer](domain/)``, because on GitHub that's what they are. Those links have to land somewhere on the site.
2. A reader who arrives mid-sequence from a search result needs to see the shape of the section they landed in.
3. Before the indexes, the sidebar was every section's only inbound link, and a sidebar shows a list without the structure behind it.

`SECTIONS` in `src/lib/docs-sections.ts` is the table. It's data instead of markup so the link tests can import it without running JSX through the test transform. It has to match the directories on disk. A document in a directory no section names falls back to "Start here" and sits next to the manifesto, misfiled where nobody notices. The test checks the two match.

---

## What the tests hold

`tests/doc-links.test.ts` is the only thing that keeps this structure from decaying:

- Every relative link resolves to a document or a section index.
- Every anchor resolves to a heading the site **renders**, checked against the rendered HTML instead of a re-implementation of the rule.
- Every glossary term points at a heading that exists.
- Every component points at a family document that exists.
- Every directory under `docs/` has a navigation section.
- No auto-linked term is ever nested inside another link, a heading or a code block.

`tsc` catches none of these, and none of them break the build. The reader finds them.
