dc5345cef4
Solution Design for making the left pane a universal git-directory tree:
host existing documentation repos as path-addressed corpora, full
governance lifecycle per file, dual-mode (structure/flat) pane replacing
the §7 Catalog, zero-migration onboarding ("no record = active").
Discovery output of ohm session 0081.0. Conforms to handbook §3.3
Solution Design standard. Sliced SLICE-1..4 in §7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
628 lines
29 KiB
Markdown
628 lines
29 KiB
Markdown
# 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).
|