# Solution Design: Corpus Tree — universal directory-tree left pane

| | |
| --- | --- |
| **Author(s)** | Ben Stull |
| **Reviewers / approvers** | Ben Stull |
| **Status** | `draft` |
| **Version** | v0.2.0 |
| **Source artifacts** | BDD corpus: §§1.9/4 below (this doc) · Prototype: brainstorm mockups, session 0081.0 (`.superpowers/brainstorm/`, not committed) · Reference: current `rfc-app` §7 Catalog + §22 three-tier + `DocsLayout` flyout nav · Supersedes: — |

**Change log**

| Date | Version | Change | By |
| --- | --- | --- | --- |
| 2026-06-06 | v0.1.0 | Initial draft (discovery session ohm 0081.0) | Ben Stull |
| 2026-06-06 | v0.2.0 | Reworked to the restructured Solution Design standard (two-part front; Pain Points; Business Actors vs Product Personas; renumbered) | Ben Stull |

---

## 1. Business Context

*The business lens — solution-agnostic throughout. No mechanism is proposed until §2.*

### 1.1 Executive Summary

Organizations keep large, living bodies of documentation, and the value in them
depends on people being able to find what they need and trust that it is current
and collectively maintained. This design targets two outcomes: readers can locate
any document by where it sits in a documentation body's own organization, and an
organization can bring an existing body of documentation under collaborative,
reviewed governance without disrupting how that documentation is already
arranged. The benefit accrues to readers (faster, more confident access),
contributors (a reviewed way to improve the docs), and the organization (a
governed, trustworthy knowledge base).

### 1.2 Background

The platform already governs collaboratively-edited documents organized as a
single shallow list within a body. Real organizational documentation — handbooks,
runbooks, design libraries — is instead deeply structured, and most of it already
exists as finished, authoritative content rather than passing through a
proposal-to-acceptance flow. There is currently no way to bring such a body onto
the platform's governance without flattening its structure, which is why
structured documentation bodies stay off-platform today.

### 1.3 Business Actors / Roles

Real-world roles, independent of any product.

| Role | Responsible for (in the business) |
| --- | --- |
| Reader | Finds and reads documents to do their work; needs current, authoritative content |
| Contributor | Proposes new documents and changes to existing ones |
| Maintainer | Reviews proposed changes and decides what becomes authoritative |
| Documentation steward | Owns an existing body of documentation and decides to bring it under collaborative governance |

### 1.4 Problem Statement

A reader cannot navigate a documentation body by its own structure, and an
organization cannot place an existing structured body under collaborative
governance without reorganizing it. The governance model assumes every document
is one entry in a single flat list and passes through a proposal flow — neither
of which holds for an established, deeply-organized documentation body whose
documents already exist as authoritative content.

### 1.5 Pain Points

| # | Pain | Who feels it | Cost / frequency today |
| --- | --- | --- | --- |
| PP-1 | In a large body presented as one flat list, a reader can't tell where a document sits or browse by area | Reader | Every lookup in a sizeable body; slow, error-prone, gives up |
| PP-2 | An existing structured body can't be brought under governance without rearranging its documents into a flat scheme | Documentation steward | Blocks adoption entirely for any living body — a rearrange is a non-starter |
| PP-3 | Contributors to an existing body have no governed, reviewed way to propose changes tied to where each document lives | Contributor, Maintainer | Changes happen outside review, or not at all; no shared record of why |
| PP-4 | When the documentation is briefly unreachable, the reader is left with nothing to orient by | Reader | Intermittent; erodes trust in the body as a dependable source |

### 1.6 Targeted Business Outcomes

| Outcome | Success metric | Baseline → Target | Guardrail (must not regress) | How / when measured |
| --- | --- | --- | --- | --- |
| Structured documentation bodies adopt the platform's governance | bodies of documentation hosted | 0 → ≥1 | existing bodies' usability unaffected | inspection at first onboarding |
| Bringing a body under governance is a decision, not a project | preparatory rearrangement / setup required | a flatten/rework → none | — | inspection at onboarding (PP-2) |
| Readers reliably reach documents in a large body | reader reaches the intended document | not feasible for structured bodies → routine | flat-body access unchanged | manual walkthrough (PP-1) |

> These are threshold/qualitative targets, not funnel metrics: this enables a new
> class of hosted documentation rather than tuning a conversion surface.

### 1.7 Scope (business)

- **In scope:** readers navigating a body by its own organization; contributors
  proposing additions and changes anywhere in a body under review; stewards
  bringing an existing body under governance without rearranging it.
- **Out of scope:** authoring tools beyond proposing/reviewing text documents;
  governance policy changes (who may review/accept) — unchanged.
- **Non-goals:** becoming a general file store for non-document assets — the value
  is governed *documents*, not arbitrary binaries.

### 1.8 Assumptions · Constraints · Dependencies

- **Assumptions:** bodies brought under governance are predominantly prose
  documents (risk: a body that is mostly non-document assets gains little).
- **Constraints:** the existing collaborative-governance model (proposal →
  review → acceptance) is reused, not redefined.
- **Dependencies:** the steward can grant the platform access to the existing
  documentation body.

### 1.9 Business Use Cases

Solution-agnostic; no product, no technology.

**BUC-1 — As a reader, I can find and read a document by where it sits in the body, so that I get authoritative content without knowing an internal name.**

```gherkin
Scenario: BUC-1 — A reader finds and reads a document in a structured body
  Given a body of documentation organized into sections
  When a reader looks for a particular document
  Then they can locate it by where it sits in that organization
  And read its current content
```
- **BUC-1 acceptance criteria:** the reader reaches the intended document and
  reads its current authoritative content.

**BUC-1a — As a reader, when the body is briefly unavailable, I am still oriented and never hit a dead end.**

```gherkin
Scenario: BUC-1a — The documentation is temporarily unavailable
  Given the documentation cannot be reached for a moment
  When a reader tries to access it
  Then they are still shown what was last known to exist, or told clearly how to try again
  And are never left at an empty, unexplained dead end
```

**BUC-2 — As a contributor, I can propose a change to a document, so that improvements are reviewed before they become authoritative.**

```gherkin
Scenario: BUC-2 — A contributor proposes a change to a document
  Given a contributor authorized to change the documentation
  When they propose a change to an existing document
  Then the change enters review before it can become authoritative
  And the document is shown as having a change under review
```
- **BUC-2 acceptance criteria:** a reviewable proposal exists against that
  document; until accepted, the authoritative content is unchanged.

**BUC-2a — As a contributor, when my proposal can't be accepted as placed, I'm told why and nothing is half-done.**

```gherkin
Scenario: BUC-2a — A proposed change cannot be accepted as placed
  Given a contributor proposing a document
  When the chosen placement conflicts with an existing document, or is not allowed
  Then the proposal is refused with a clear, specific reason
  And nothing is partially recorded
```

**BUC-3 — As a contributor, I can introduce a new document anywhere in the body, so that the body grows where the content belongs.**

```gherkin
Scenario: BUC-3 — A contributor introduces a new document anywhere in the body
  Given a contributor authorized to add documentation
  When they propose a new document at a place within the body
  Then it enters review as a draft in that place
  And on acceptance it becomes the authoritative document there
```
- **BUC-3 acceptance criteria:** a draft appears at the chosen place and is
  reviewable; on acceptance it is the authoritative document there.

**BUC-4 — As a documentation steward, I can bring an existing body under collaborative governance, so that it gains review and shared maintenance without disruption.**

```gherkin
Scenario: BUC-4 — A steward brings an existing doc body under governance
  Given an organization with an existing body of documentation
  When that documentation is brought under collaborative governance
  Then all of its existing documents are immediately readable as authoritative
  And none of them had to be rearranged to make that possible
```
- **BUC-4 acceptance criteria:** every existing document is readable and treated
  as authoritative, with no rearrangement and no preparatory data entry.

---

## 2. Solution Proposal

Build software: make the platform's left-pane navigation a **universal
directory-tree** that mirrors a content repository's own git directory structure,
and address every document by its **path** rather than a flat slug — so a
documentation body is navigated by its real structure while keeping the existing
propose → review → accept governance on every file. The flat list becomes the
degenerate "one folder" case of the tree; a deep body is the general case. A new
`CorpusTree` left-pane component replaces the flat §7 Catalog as the *universal*
idiom (one navigation model for every project type), with **two display modes** —
*structure* (the directory tree) and *flat* (today's ranked list, engaged on
search or a non-path sort) — so the four affordances the flat list provides
(search, lifecycle state, sort, the contribution surface) all survive.

An existing body is onboarded as an ordinary registry project pointed at its
repository, with **no content migration and no state backfill**: a document
present with no lifecycle record is treated as authoritative ("no record =
active").

**Why this approach, over the alternatives:**
- *Do nothing / keep flat + rearrange repos* — rejected: PP-2 makes a rearrange a
  non-starter for a living body.
- *A read-only documentation viewer* — rejected: delivers PP-1 but not PP-3
  (no governed contribution).
- *A second, separate tree view beside the flat catalog* — rejected: forks the
  navigation idiom and doubles the surface; the tree generalizes the flat list
  rather than sitting beside it.

**Solution-specific scope / non-goals:**
- *In:* path-addressed documents; a directory-tree read surface; the dual-mode
  pane; lifecycle by path; propose/edit/review at any path; zero-migration
  onboarding; legacy slug→path URL compatibility.
- *Out:* lazy per-folder tree loading (a scale follow-on; v1 serves the full
  document tree from cache); a rich renderer for non-document files (listed but
  inert); changes to graduation / integer-ID assignment.
- *Non-goals:* merging folders with collections (kept distinct — INV-3); a new
  project *type* (existing types render via the tree — D5).

---

## 3. Product Personas

| Product persona | In rfc-app | Maps to business role(s) |
| --- | --- | --- |
| Reader | Browses a project's corpus and opens documents | Reader |
| Contributor | Holds collection `can_contribute`; proposes/edits docs at any path | Contributor, Maintainer |
| Deployment operator | Registers a content repo as a project and flips the cutover flag | Documentation steward |

## 4. Product Use Cases

UX-level; steps are about the Product Personas (§3). Each links to the Business UC
it realizes.

```gherkin
Scenario: PUC-1 — Browse the directory tree in structure mode (realizes BUC-1)
  Given the left pane is in structure mode
  When the reader opens the project
  Then folders and files are shown in path order
  And each markdown file shows its lifecycle-state badge
  And folders expand and collapse, with expansion persisted across visits
```

```gherkin
Scenario: PUC-2 — Search or non-path sort flips to flat mode (realizes BUC-1)
  Given the pane is in structure mode
  When the reader types a search query or picks a non-path sort
  Then folders are hidden and a ranked flat list of matches is shown
  And clearing the search with a path sort returns the tree with folders restored
```

```gherkin
Scenario: PUC-3 — Open a document by its path (realizes BUC-1)
  When the reader selects a file in the tree
  Then the main column renders that document
  And the tree highlights the corresponding node
  And the document's lifecycle state is visible
```

```gherkin
Scenario: PUC-4 — Propose a new doc at a path (realizes BUC-3)
  Given a contributor with contribute capability
  When they choose "Propose new doc" and give a path like deploy/runbooks/rollback.md
  Then intermediate folders are created in the proposal branch
  And a PR is opened via the bot
  And a super-draft entry appears at that path
```

```gherkin
Scenario: PUC-5 — Propose an edit to an existing doc (realizes BUC-2)
  Given a contributor viewing an existing document
  When they propose an edit
  Then an edit PR is opened against that document's path
  And the file's node shows the in-review badge with the open PR reference
```

```gherkin
Scenario: PUC-6 — Filter and see lifecycle state (realizes BUC-1, BUC-2)
  Given files exist in active, in-review, and super-draft states
  When the reader enables the "in review" state filter
  Then only files with an open edit PR are listed
```

```gherkin
Scenario: PUC-7 — Star a doc; starred pins in flat mode (product-only)
  Given the reader has starred a file
  When the pane is in flat mode
  Then the starred file is pinned above the ranked results
```

```gherkin
Scenario: PUC-8 — Legacy slug URL redirects to its path (product-only, compat)
  When the reader navigates to a legacy /p/:pid/c/:cid/e/:slug URL
  Then they are redirected to the path-addressed URL for that entry
  And the document renders
```

```gherkin
Scenario: PUC-9 — Operator onboards an existing repo (realizes BUC-4)
  Given an existing doc repo and bot read+write access
  When the operator registers it as a project's content repo and flips the cutover flag
  Then every pre-existing file renders as active in the tree
  And no content was moved and no state was backfilled
```

```gherkin
Scenario: PUC-10 — Tree fetch fails (realizes BUC-1a)
  Given a previously cached tree exists
  When the tree-listing fetch to gitea fails
  Then the last good tree is served from cache
  And when no cache exists, a graceful empty state with a retry action is shown
```

```gherkin
Scenario: PUC-11 — Propose at an invalid path (realizes BUC-2a)
  When a contributor proposes a doc at an existing or out-of-repo path
  Then the proposal is rejected with a clear validation error
  And no branch or PR is left behind
```

```gherkin
Scenario: PUC-12 — Non-markdown files are listed but inert (product detail)
  Given the repo contains schemas/app.json
  When the tree is listed
  Then schemas/app.json appears in the tree
  But it has no lifecycle badge and opens no entry view
```

## 5. UX Layout

### 5.1 Left pane — `CorpusTree` (serves PUC-1..PUC-7, PUC-12)

- **Purpose:** navigate a project's corpus and reach any document; surface
  lifecycle state and the contribution affordances.
- **Layout (top → bottom):**
  - **Toolbar:** search input; lifecycle state filter-chips; sort selector
    (path · recent · title · id · state); `+ Propose new doc` action.
  - **Body — structure mode (default):** indented directory tree; folders with
    expand/collapse in path order; file rows show a state dot
    (active / in-review / super-draft) and a star marker; the active file is
    highlighted. Non-markdown files render dimmed and inert.
  - **Body — flat mode (search / non-path sort):** folders hidden; a ranked flat
    list of file rows (path as secondary text); starred pinned to top; pending
    super-drafts grouped.
- **States:** happy: tree/list rendered · empty: "No documents yet" + propose CTA
  if permitted · loading: skeleton rows · error: stale tree if cached, else an
  empty state with Retry · permission: propose control hidden without
  `can_contribute`.
- **Notifications:** inline validation error on invalid propose (PUC-11); existing
  PR/discussion notifications unchanged.

### 5.2 Main column (serves PUC-3, PUC-5)

Unchanged — rendered markdown + discussion + PR/contribution affordances —
addressed by path instead of slug.

## 6. Technical Design

### 6.1 Invariants

- **INV-1:** An entry is addressed by its repo `path` within a
  `(project, collection)`. Slug addressing exists only as a legacy redirect.
- **INV-2:** A markdown file present on `main` with no explicit lifecycle record
  is `active`. Onboarding requires no state backfill.
- **INV-3:** Folders are organizational only. Access control is the collection's;
  a folder never carries permissions.
- **INV-4:** Lifecycle records are keyed by `(collection, path)` and follow file
  renames on merge — no orphaned records survive a `git mv`.
- **INV-5:** The framework names no deployment. The backing repo is supplied via
  the registry (`CLAUDE.md` separation-of-concerns).
- **INV-6:** Non-markdown files are listed but carry no lifecycle and open no
  entry view.
- **INV-7:** The tree pane never renders blank — stale cache or an explicit
  empty/retry state on fetch failure.

### 6.2 High-level architecture

```mermaid
flowchart LR
  CT[CorpusTree pane] -->|GET tree / entry by path| API[API service]
  CT -->|propose / edit at path| API
  API --> CACHE[(TTL cache)]
  CACHE --> GT[Gitea git-trees / raw]
  API -->|branch / PR writes| BOT[Bot account]
  BOT --> GT
  API --> DB[(App DB: lifecycle, stars)]
  REG[rfc-registry] -->|project to content-repo| API
  WH[Gitea webhooks] -->|invalidate / reconcile| CACHE
```

- **CorpusTree** — owns left-pane rendering and mode state; owns no source of
  truth; must never assume a flat namespace.
- **API service** — owns the tree-listing and path-addressed entry/contribution
  endpoints; derives lifecycle state; must never write content except via the bot.
- **App DB** — owns lifecycle records, stars, reviewed-marks keyed by
  `(collection, path)`; system of record for collaboration state.
- **Gitea** — system of record for document content and PRs.
- **Cache** — holds the materialized tree + per-path metadata; invalidated by the
  webhook/PR-merge signals used today (§4.1 reconciler pattern).

### 6.3 Data model & ownership

| Entity | Owned by | Key fields | System of record |
| --- | --- | --- | --- |
| Document (entry) | Gitea | `(project, collection, path)`, body | Gitea repo `main` |
| Lifecycle record | App DB | `(collection, path)`, state, reviewed-by | App DB |
| Star | App DB | `(user, collection, path)` | App DB |
| Open-PR mapping | Gitea | `path` → PR number | Gitea |
| Tree node (materialized) | Cache | `path`, type, `last_commit_at`, state, `open_pr`, `starred_by_me` | derived (cache) |

### 6.4 Interfaces & contracts

- **`GET /api/projects/{pid}/collections/{cid}/tree`** — in: pid, cid, viewer ·
  out: nodes `{path, type, last_commit_at, lifecycle_state, open_pr,
  starred_by_me}` (full markdown tree) · errors: `404` unknown project/collection;
  gitea failure → stale cache or `503` with a retriable marker (INV-7).
- **Path-addressed entry read** — generalize `…/rfcs/{slug}` to a path
  (`…/entries/{path}`); out: existing entry payload; errors: `404`.
- **Propose-at-path / edit / discussion** — generalize existing contribution/PR/
  discussion endpoints from `slug` to `path`; propose-new in: target path, body;
  out: PR ref; errors: `409` path exists, `422` invalid/out-of-repo path
  (PUC-11), `403` no `can_contribute`.

### 6.5 Per–Product-Use-Case design

#### PUC-1 — Browse the tree (realizes BUC-1; honors INV-2, INV-7)

```mermaid
sequenceDiagram
  actor R as Reader
  participant CT as CorpusTree
  participant A as API
  participant C as Cache
  participant G as Gitea
  R->>CT: open project
  CT->>A: GET …/tree
  A->>C: get materialized tree
  alt cache hit
    C-->>A: tree
  else miss
    A->>G: git trees + last-commit
    G-->>A: entries
    A->>A: derive state (no record = active, INV-2)
    A->>C: store
  end
  A-->>CT: nodes
  CT-->>R: structure-mode tree (or stale/empty per INV-7)
```

- **Implementation:** materialize the markdown tree once per cache cycle; join
  per-path lifecycle/star records; "no record = active." Folder expansion is
  client state (localStorage keyed by project/collection).

#### PUC-4 — Propose a new doc at a path (realizes BUC-3; honors INV-1, INV-3)

```mermaid
sequenceDiagram
  actor Cn as Contributor
  participant CT as CorpusTree
  participant A as API
  participant B as Bot
  participant G as Gitea
  Cn->>CT: + Propose new doc (path)
  CT->>A: propose-at-path(path, body)
  A->>A: validate path (unique, in-repo) else 409/422
  A->>B: create branch + file (mkdir -p path)
  B->>G: commit + open PR
  G-->>A: PR ref
  A-->>CT: super-draft at path
```

- **Implementation:** reuse the existing bot/branch/PR flow; only the target path
  and intermediate-folder creation change. PUC-5 (edit), PUC-6 (filter), PUC-8
  (redirect) reuse existing flows with `slug`→`path` substitution and need no new
  sequence.

### 6.6 Non-functional requirements & cross-cutting concerns

- **Security & privacy:** read follows existing project/collection visibility;
  propose gated by `can_contribute` (INV-3); all writes via the bot; secrets stay
  references, never bytes (§6.3-handbook). No new PII.
- **Performance & scale:** v1 returns the full markdown tree from the TTL cache;
  cold build is one git-trees call + a last-commit join. Lazy per-folder loading
  is the scale follow-on if a body's tree is large enough to hurt cold-build
  latency.
- **Availability & resilience:** stale-cache fallback + empty/retry state
  (INV-7); no new hard dependency beyond what the corpus already needs.
- **Observability:** log tree cold-builds, cache hit/miss, gitea-failure
  fallbacks; reuse existing health signals.
- **Accessibility:** the tree uses ARIA `tree`/`treeitem` roles with keyboard
  expand/collapse and roving focus; state conveyed by text/label, not color alone.

### 6.7 Key decisions & alternatives considered

| Decision | Chosen | Alternatives considered | Why chosen |
| --- | --- | --- | --- |
| D1 Interaction model | Full governance lifecycle on any file | read-only viewer; read + light comments | operator intent; reuses existing machinery |
| D2 Topology | Tree becomes universal | tree as a second view; tree only for a new type | one navigation model; no idiom fork |
| D3 Entry identity | Path-addressed | keep slug + side table of paths | path is the natural key for a real body |
| D4 Folders vs collections | Distinct | merge folders into collections | collections are access-control, folders are structure |
| D5 Project type | No new type | a `tree`/`docs` type | "universal" means all types render via tree |
| D6 Build approach | New `CorpusTree`, port flat mode, retire Catalog | evolve Catalog in place; promote DocsLayout flyout | clean dual-mode boundary; safe staged cutover |
| D7 Existing files | "No record = active" | backfill a record per file at onboard | zero-migration onboarding |
| D8 Affordances | Keep all four via dual mode | drop sort/pending on a tree | operator marked all non-negotiable |
| D9 Routing | `e/*` splat + `?pr=` + legacy shim | encode path in slug; new `/f/` segment | least collision; reuses redirect pattern |

### 6.8 Testing strategy

Tests are part of each slice (not a follow-up).

- **Unit (vitest):** `CorpusTree` mode-switching (search→flat, sort→flat,
  clear→structure), badge rendering, expand/collapse persistence, starred-pin,
  flat-mode parity with the retired Catalog.
- **Backend (DI at boundaries):** tree endpoint state derivation (INV-2), open-PR
  detection, path-addressed read/contribution, slug→path migration, rename
  reconciliation (INV-4).
- **Two-tier:** Tier-1 local-Docker gitea for branch/PR flows; Tier-2 PPE for the
  onboard-a-real-repo path (SLICE-4). e2e: browse → open; propose-at-path → PR →
  merge → state transitions; search flatten; legacy redirect.

### 6.9 Failure modes, rollback & flags

- **Failure:** gitea unavailable → **behavior:** stale cache or empty/retry
  (INV-7) → **rollback:** none needed.
- **Failure:** a project regresses on the new pane → **behavior/rollback:** flip
  the **per-project-type cutover flag** off; the Catalog renders again (not
  deleted until parity is proven).
- **Feature flag:** per-project-type cutover flag, default **off**; deployments
  enable per project after validating parity.

## 7. Delivery Plan

### 7.1 Approach / strategy

Riskiest-foundation-first: land the path/state model and tree read behind the
existing flat UI (no user-visible change), then the dual-mode UI behind a flag,
then contribution-at-path, then onboard a real body. Per the handbook execution
convention, **each slice is its own coding session** — plan just-in-time →
execute → verify → ship → merge + version bump — in dependency order; this design
pass happens once and is amended only if a slice proves it wrong. The flag keeps
every intermediate state shippable.

### 7.2 Slicing plan

#### SLICE-1 — Backend foundation → completes (no user-visible PUC)
- **Depends on:** —
- **DoD:** path-keyed lifecycle + slug→path migration + "no record = active"
  (INV-2) + tree endpoint + path-addressed reads; Catalog still renders; backend
  tests green.

#### SLICE-2 — `CorpusTree` frontend → completes PUC-1, PUC-2, PUC-3, PUC-6, PUC-7, PUC-8, PUC-10, PUC-12
- **Depends on:** SLICE-1
- **DoD:** dual-mode pane behind the cutover flag; path routing + legacy shim;
  flat-mode parity; Catalog retired once a project is flipped; unit + e2e green.

#### SLICE-3 — Contribution-at-path → completes PUC-4, PUC-5, PUC-11
- **Depends on:** SLICE-1
- **DoD:** propose/edit/discussion at arbitrary paths; rename reconciliation
  (INV-4); validation (BUC-2a); tests green.

#### SLICE-4 — Onboard an existing doc body → completes PUC-9 (BUC-4)
- **Depends on:** SLICE-2, SLICE-3
- **DoD:** a real external repo registered + bot access + flag flipped; all
  pre-existing files render active with zero migration; Tier-2 PPE validation.

### 7.3 Rollout / launch plan

Pre-v1, single-prod: each slice ships to prod on merge with its CHANGELOG upgrade
steps; the per-project-type flag defaults off, so deployments opt in per project.
Rollback trigger: flag off → Catalog returns (§6.9).

### 7.4 Risks & mitigations

| Risk | Likelihood / impact | Mitigation |
| --- | --- | --- |
| Full-tree fetch slow on a large body | M / M | TTL cache; lazy per-folder loading as a defined follow-on |
| Catalog flat-mode parity gaps after cutover | M / H | port logic verbatim; flag-gated per project; keep Catalog until parity proven |
| Rename leaves orphan state records | M / M | webhook reconciliation (INV-4) + test |
| slug→path migration mis-maps non-`document` entry folders | L / M | resolve entry folder per project type, not hardcoded; migration test |

## 8. Traceability matrix

| Pain | Business UC | Product UC | Slice | Tests |
| --- | --- | --- | --- | --- |
| PP-1 | BUC-1 | PUC-1, PUC-2, PUC-3, PUC-6, PUC-7 | SLICE-2 | `test_tree_*`, `corpustree.*.test` |
| PP-4 | BUC-1a | PUC-10 | SLICE-2 | `test_tree_fallback_*` |
| PP-3 | BUC-2 | PUC-5, PUC-6 | SLICE-3 | `test_edit_at_path_*` |
| PP-3 | BUC-2a | PUC-11 | SLICE-3 | `test_propose_validation_*` |
| PP-3 | BUC-3 | PUC-4 | SLICE-3 | `test_propose_at_path_*` |
| PP-2 | BUC-4 | PUC-9 | SLICE-4 | e2e `onboard_repo_*` |
| PP-1 | (compat) | PUC-8 | SLICE-2 | `test_legacy_redirect_*` |
| PP-1 | (detail) | PUC-12 | SLICE-2 | `test_tree_non_markdown_*` |

## 9. Open Questions & Decisions log

**Open**

| # | Question | Owner | Blocks |
| --- | --- | --- | --- |
| — | none outstanding | — | — |

**Resolved**

| # | Decision | Resolution | Date |
| --- | --- | --- | --- |
| D1 | Interaction model | Full governance lifecycle on any file | 2026-06-06 |
| D2 | Topology | Tree becomes the universal left pane | 2026-06-06 |
| D3 | Entry identity | Path-addressed; slug is legacy-redirect-only | 2026-06-06 |
| D4 | Folders vs collections | Kept distinct | 2026-06-06 |
| D5 | Project type | No new type; existing types render via tree | 2026-06-06 |
| D6 | Build approach | New `CorpusTree`; port flat mode; retire Catalog | 2026-06-06 |
| D7 | Pre-existing files | "No record = active"; zero-migration onboarding | 2026-06-06 |
| D8 | Affordances | Keep all four via dual display mode | 2026-06-06 |
| D9 | Routing | `e/*` splat + `?pr=` + legacy shim | 2026-06-06 |

Deferred (not open): lazy per-folder tree loading; raw/preview view for
non-markdown files.

## 10. Glossary & References

- **Structure mode** — left pane showing the directory tree in path order.
- **Flat mode** — left pane showing a ranked flat list (search/non-path sort);
  the retired Catalog's behavior.
- **Entry** — a markdown document, addressed by its repo path within a
  `(project, collection)`.
- **Lifecycle record** — app-DB row keyed by `(collection, path)` carrying
  non-default state; absence means `active`.
- **References:** `SPEC.md` §7 (catalog), §22 (three-tier), §8 (revision), §13
  (graduation), §20 (versioning); `CLAUDE.md` (separation-of-concerns);
  handbook `solution-design/GUIDE.md` (this doc's standard).
