# ADR: The Decision Records index

Status: accepted, 2026-09-03, shipped as 1.0.0.39. Steve asked for a
new tab for the decision records.

## In plain words

This page puts every decision record in one sidebar section (the Decision Records index), in the order the decisions were made and numbered to match the file on disk. The section starts closed, using the browser's own open and close element (`details`).

What that is worth: a developer reaches any record in two clicks and can name it by number, and the organization's decisions can be counted at a glance instead of across four sections.

## Context

The records were filed by topic, which was the right answer at eight of them
and the wrong one at twenty-seven. ADR: App Architecture section had grouped
them under the thing each one decided: hosting decisions under Hosting,
pipeline decisions under CI/CD, and everything else under Best Practices.
Everything else turned out to be most of them.

By last night Best Practices was eighteen rows deep, seventeen of which were
records, and the section had stopped being a menu. There was also no way to
answer "how many decisions are written down, and which one is number twelve"
without reading four sections and counting.

## Decision

**One section, every record, in the order they were decided.** The four topic
sections keep their overviews and nothing else. Hosting is now two rows.

**Numbered to match the file.** ADR-012 in the sidebar is
`docs/decisions/ADR-012-changelog.md` on disk. The number is a separate element beside
the label rather than part of it, which keeps the button's accessible name
exactly the label a reader hears and a test clicks.

**Collapsed by default, as a native `details`.** This was not the first
version. The first version had the section always open, and the screenshot of
it is why this paragraph exists: twenty-seven rows at the bottom of the rail
put the heading right on the fold above the pinned footer, so the new tab read
as empty until you scrolled past it. An index of twenty-seven is a disclosure.

`details` and `summary` rather than state and a button, because the open and
closed semantics, the keyboard handling, and the screen-reader announcement all
come from the element. There is nothing left to get wrong, and it is one fewer
piece of state.

## In the code

The section, and its order (`src/library/sections.ts`; the numbers are in `src/library/records.ts`):

```ts
/**
 * Every decision record, in the order they were decided, numbered to match the file
 * each one serves: a section of its own, because ninety rows under the topic sections
 * would be a wall. Every section in the sidebar is a closed details (SideNav.tsx).
 * Read from the records themselves in number order, so a new record is in the menu,
 * and in the landing page's count, the moment it is in records.ts.
 */
records: {
  label: 'Decision Records',
  items: (Object.entries(RECORDS) as [DocKey, { number: string }][])
    .sort(([, a], [, b]) => a.number.localeCompare(b.number))
    .map(([key]) => ({ key })),
},
```
*Live from [`src/library/sections.ts`](https://github.com/SteveStout/TheYard/blob/1d6b50b/src/library/sections.ts#L224-L236), region `records-menu`, read from this build at 1d6b50b.*

One shell for both kinds of section (`src/components/layout/SideNav/SideNav.tsx`):

```tsx
/**
 * A sidebar section: a native `details`, closed until somebody asks for it.
 *
 * Closed by default turns the sidebar into a table of contents: the section
 * headings, and the one you want is one click away, rather than a rail that
 * arrives holding about a hundred rows with the reader's own section somewhere
 * inside them. The keyboard and screen-reader behaviour comes from the element
 * rather than from a reimplementation of it, which is why this is a `details`
 * and not a button and a piece of state.
 *
 * The icons-only rail is the exception. There are no headings on that rail:
 * the rows are icons and the words are hidden, so a closed section would be a
 * triangle with nothing to read and nothing to aim at. It keeps the rows
 * (ADR: The sidebar, the addendum on closed sections).
 */
export function RailSectionShell({
  label,
  iconsOnly,
  children,
}: {
  label: string;
  iconsOnly: boolean;
  children: ReactNode;
}) {
  if (iconsOnly) {
    return (
      <section className={styles.section}>
        <h2 className={styles.srOnly}>{label}</h2>
        {children}
      </section>
    );
  }
  return (
    <details
      className={styles.section}
      onToggle={(event) => {
        if (event.currentTarget.open) {
          event.currentTarget.scrollIntoView({ block: 'nearest' });
        }
      }}
    >
      <summary className={styles.sectionToggle}>
        {/* The label stays a heading inside the summary, which HTML allows and
            which keeps the section names in the document outline where a
            screen reader's heading list finds them; the summary is what makes
            it a disclosure. */}
        <h2 className={styles.sectionHeading}>{label}</h2>
      </summary>
      {children}
    </details>
  );
}
```
*Live from [`src/components/layout/RailSectionShell/RailSectionShell.tsx`](https://github.com/SteveStout/TheYard/blob/1d6b50b/src/components/layout/RailSectionShell/RailSectionShell.tsx#L11-L62), region `section-shell`, read from this build at 1d6b50b.*

## Consequences

- The rail is short again. Four overviews, an index, the changelog, About.
- Six browser tests changed, because they used to click a record that was
  always visible and now open the index first. That is the interaction a reader
  performs, so the tests are closer to true than they were.
- A record is now reachable in two clicks from anywhere and one from the index,
  and the numbers make it possible to say "read 023 and 027" and be understood.
- The topic grouping is gone as navigation. It survives where it always
  mattered, in each record's own Files section and in the overviews that link
  them.
- Adding a record is now three lines in one place instead of a decision about
  which section it belongs to.

## Where it sits

This sits in the front end only: the record order is data in src/library/sections.ts and records.ts, and RailSectionShell draws each sidebar section as a native `details` element. Open/closed fits it, meaning new behaviour arrives by adding code instead of editing a core class, because a new record is three lines of data and the shell does not change. The cost was six browser tests rewritten to open the index first, and the topic grouping is gone as navigation. If the index grew into the hundreds, a search box or a split by year would serve a reader better than one long disclosure.

## Files

- [`src/library/sections.ts`](https://github.com/SteveStout/TheYard/blob/main/src/library/sections.ts): the sections and the record order.
- [`src/library/records.ts`](https://github.com/SteveStout/TheYard/blob/main/src/library/records.ts): the numbers.
- [`src/components/layout/SideNav/SideNav.tsx`](https://github.com/SteveStout/TheYard/blob/main/src/components/layout/SideNav/SideNav.tsx): the section shell and the disclosure.
- [`src/components/layout/SideNav/SideNav.module.css`](https://github.com/SteveStout/TheYard/blob/main/src/components/layout/SideNav/SideNav.module.css): the summary's marker and the number's tabular figures.
- [`api/TheYard.Tests/DocumentationCatalogTests.cs`](https://github.com/SteveStout/TheYard/blob/main/api/TheYard.Tests/DocumentationCatalogTests.cs): the test that still holds the sidebar and the served catalog to the same slugs, unchanged by the move.
- [`docs/decisions/ADR-022-app-architecture-group.md`](https://github.com/SteveStout/TheYard/blob/main/docs/decisions/ADR-022-app-architecture-group.md): the grouping this replaces, and why it was right at the time.

## Addendum, 2026-09-09: what is not a record

The SQL Server and Cosmos DB comparison was first filed as a seventieth
record and moved out the same day, and the rule behind the move is worth
writing down because the index had nothing that enforced it. A decision
record holds a decision: something was chosen, something else was declined,
and the record says why and what it costs. A page that lays decisions already
made side by side, however much it draws on the records, decides nothing,
and numbering it would make the index claim a decision that was never made.
The comparison is a document, `docs/sql-vs-cosmos/SQL-VS-COSMOS.md`, with a section of its
own at the top of the sidebar (ADR: The sidebar, addendum); the records it
cites stay where they were, and the count is sixty-nine. The test that holds
the index is unchanged, because it counts files, and the guard for this is
the reader: before a new file gets a number, ask what it decided.

## Addendum, 2026-10-06 (1.0.3.84): the records have a folder

The records moved from the root of `docs/` into `docs/decisions/`, keeping their numbers and their file names, as part of giving every sidebar section a folder (ADR: The sidebar, the addendum on the folders). The index is unchanged: it reads the catalogue, the catalogue names each record by its new path, and the slugs are the same. The tests that count records and check their numbers run without a gap now read `docs/decisions/`, and `DocsFolderTests` fails if a record is written anywhere else.

## Addendum, 2026-10-07 (1.0.3.95): the section reads its rows from the records

The section listed every record by hand, one row a record, and the list had grown to 95 rows in a file the 300-line rule holds; the ninety-fifth record took `src/library/sections.ts` to 301 lines and the gate stopped it. The rows are now read from the records themselves and sorted by number, so a new record is in the sidebar, and in the landing page's count, the moment it is in its run of records, and `sections.ts` no longer grows by a line a record. `sections.test.ts` holds that every record is listed once and in number order, and `DocsFolderTests` reads the runs of records for the section each record is offered under. Where a change goes, from now on, for a new record: the markdown, its slug in `DocumentationCatalog.cs`, and its entry in the run of records it falls in.
