docs: split the root README into owned documents under docs/ and guard it with drift tests #266
No reviewers
Labels
No labels
bug
enhancement
needs-triage
ready-for-agent
ready-for-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/deckle!266
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/263"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #263
The 3,182-line README is now a 243-line orientation page. Its content moved without rewording to the document that owns each kind of fact, and four drift tests keep it that way. The rewriting #263 left out (correcting and condensing prose, the 20 stubbed route sections, extracting ADRs) is filed as #265.
Review the move with
git diff --color-moved=dimmed-zebra origin/main..HEAD -- README.md docs/ scaffold/site/AGENTS.md.Where the content went
README.md(243 lines)docs/api.md## Conventions(Server-described forms, Coded messages, Sidecar labels on the wire, Translations on the wire), then one### METHOD /api/pathheading for each of the 66 registered routes: 15 hold moved material, 31 a one-line pointer to the convention section that covers them, and 20 the stubDocumented in #265.docs/harness.mdshblock), serving (the Quick start paragraph that namedGET /api/status), reverse proxy,harness lang,harness.yaml, the receipt, machine-readable outputdocs/host-navigation.md### Host navigationand all of its####children, verbatim, including "What the editor accepts" (see Deviations)docs/features.mddocs/architecture.md###per bold-lead paragraph, plus the Multilingual sites modeldocs/adr/README.mddocs/agents/docs-map.mddocs/.docs-syncCLAUDE.mdText changed in the move
Moved text was copied byte for byte, apart from heading levels and link targets that point into other files. These are the exceptions, each fixing a reference the move made false:
/api/formbullet: "Coded messages below", "Sidecar labels on the wire below" and "the page tree below" now say "above".POST /api/page/save.docs/host-navigation.md#the-editors-language.shsynopsis block (which listedreconcilein a different place from the binary) is replaced by the exactusage()text, as the issue prefers. The section is called "Commands", not "The ten commands".A coverage script splits the original README into 437 units (paragraphs, list items, tables, fenced blocks). Each unit is either in exactly one new place, or accounted for above or in the AGENTS.md table below. (The one unit found twice is a sentence the original README also contains twice.)
Configuration reference →
scaffold/site/AGENTS.mdEach unit of the nine sidecar sections was compared with AGENTS.md:
scaffold/themes/deckle/README.md, which AGENTS.md links to: theslotwrapper, the layout resolution order and inheritance.### Backend layoutssection, and additions to "Element sidecar schema", "Body policy", "Page types" (including Content-root authority) and "Editor modules".GET /api/modules,/api/categories,/api/module/entriesand/api/treein api.md, and a short paragraph in architecture.md.No AGENTS.md heading was renamed.
go test ./scaffold/ ./internal/element/is green.Full disposition table (every unit)
editor/elements/<name>.yaml, where<name>is the shortcode name. Worked example"editor/elements/<name>.yaml)"version: 1 # required, integer >= 1editor/elements/<name>.yaml)"label: Hero # requirededitor/elements/<name>.yaml)"icon: hero # optionaleditor/elements/<name>.yaml)"group: content # structure | content | inline (default content)editor/elements/<name>.yaml)"policy: rte # none | markdown | rte (default none)editor/elements/<name>.yaml)" — Body policyallowedInline: [button] # rte-only: inline elements offered as chips# allow: [bold, italic, link, bulletList, orderedList, h2, h3, h4]tableincluded)allowedIn: [page] # parent element names, plus "page" for top level# allowedAt: [post, page] # page-type namestype: text # one of the eleven field types (multiselect: page-type fields only)label: Heading/required: true# default: ... # any spelling Hugo reads for the field type# options: [a, b] # required for type select, forbidden otherwiseeditor/elements/<name>.yaml)"# tab: appearance # form tab: general (default) | appearancetabpicks the tab of the editor form"tab(access legal on a page-type field)numbernot0)multiselectcategoriesfield typecategoriesholds a selection of the site's categories: the category pages"categoriesparagraph)categoriesfield typecategorieswith another name;" (3 items)categoriesparagraph)categoriesfield typecategoriesparagraph)categoriesfield typeGET /api/categories?lang=({categories: [{slug, title}]}, read fresh on every request)"### GET /api/categories)categoriesfield typecategoriesfield typecategoriesfield typecategoriesparagraph)categoriesfield typecategories(field type) bulletbodyis either a scalar policy (body: rte) or a mapping."allowandallowedInlineare legal only whenpolicy: rte."allowrestricts formatting to a subset of {…} — an absentallowmeans the full set"tableopt-in; the README's set is stale)h1is never allowed."allowedInlinenames other loaded elements whosegroupisinline; each must exist"markdownorrtebody"editor/pagetypes/<name>.yaml. A page records its type"editor/pagetypes/<name>.yaml)" (opening paragraph)version: 1 # required, must be exactly 1editor/pagetypes/<name>.yaml)"label: Page # optional; defaults to the type nameeditor/pagetypes/<name>.yaml)"icon: page # optionaleditor/pagetypes/<name>.yaml)"# singleton: true # optional; hides create handles for this typeeditor/pagetypes/<name>.yaml)"children: [page, section] # page types creatable beneath this one# elements: [hero, notice] # element allowlist for pages of this typeelementsbulletfields: # page-level front-matter fields (same vocabulary)+titlefieldhiddenfield,tab: access # form tab: general (default) | appearance | access,default: falsehiddenfield;tabbullet)childrenandelementsdistinguish three states, preserved to the wire: absent/null = unrestricted"children) / added (elements, new bullet)childrenbullet; newelementsbullettabpicks the properties form tab it sits on"tabbulletsingletonis optional and purely presentational: the editor hides create-handles"singletonbullettype,draft,weight,aliases,backendLayout,"defaultsbulletcategoriestype —name: categories"categories(field type) bulletshownFieldspicks which fields the page's properties form shows"shownFieldsbulletnews-itemgets only "Backend layout")"news-item" — news-item ships in the news bundle, not the scaffold)shownFieldsbulletshownFields: [teaser, backendLayout] # title + teaser + own layout pickershownFieldsline)hideChildPages: truehides a page's managed children from the page tree"hideChildPagesbullethideChildPagesbullethideChildPagesbullet/api/treepayload (markedhidden)"hiddenmark onGET /api/tree→ docs/api.md)hideChildPagesbullet ("stay in the editor's tree data")create: trueon an optional field asks for it in the create dialog"createbulletversion: 1/singleton: true/children: [page, section]harness addextends this list when a feature bundle asks for it"childrenbullet; "Feature bundles"_root.yamlmechanism has been removed; a leftover"editor/backendlayouts/<name>.yaml. A backend layout is a"editor/backendlayouts/<name>.yaml)"version,label,allowedAt,rows/columns,name,label,colspan,inherit)editor/backendlayouts/<name>.yaml)"elementsallowlist and the layout'sallowedAtfollow the same"editor/backendlayouts/<name>.yaml)"editor/backendlayouts/<name>.yaml)"slotwrapper shortcode" + fenced blockslotis reserved and harness-managed: it is never offered in the insert"editor/backendlayouts/<name>.yaml)"editor/backendlayouts/<name>.yaml)"editor/modules/<name>.yaml; the file name is the module"addvendors/removedeletes/hand-written)editor/modules/<name>.yaml)"; "Layout" (editor/modules/bullet); "Feature bundles"; "Adding or changing an element" (dotted name = language file)internal/sidecardir: one per file, …)" and "validated against the loaded page types (internal/editormodule)"editor/modules/<name>.yaml)" (opening paragraph)version,label,icon,pageTypes,views,columns,cardroles,sort,search,filters,pageSize,edit# list | cards; two or more views give a switcherGET /api/treemarks both (containerFor,managedBy)."### GET /api/tree)GET /api/moduleshands the editor no field types: each field the module" … "a declared field's label has none."### GET /api/modules)sort, missing sort field last)selectandmultiselect, …) is a load error: they match differently"filtersbullet)createGET /api/module/entries, which serves every datetime as RFC3339)"### GET /api/module/entries)edititem — Properties (the page".yaml/.yml, any tag such asde/pt-BR, not a content language, dotted name = language file, text only keyed by name)pagepath.ValidLangTagaccepts" and "internal/sidecardiralone recognises the name (split at the first dot …) … every other walker (harness add, its receipts,harness sidecar) asks it."version, failed sidecar skips its files, changes nothing but labels).de.yamlbeside every sidecar underscaffold/editor/and under both bundles'editor/trees"web/src/i18n/glossary.md; a scaffold test holds each complete (every text key of its source translated).selectoption values,feature.yamlmanifests and the starter home page are not translated."selectvalues untranslated is also stated in "Element sidecar schema" — Language fileGuards
Each guard fails with a message naming the owning document. These were captured by breaking each invariant once:
internal/server/routes_doc_test.go).route()now dispatches through anapiRoutestable: the same 66 paths and handlers, unknown/api/paths still 404. That table is the one owner of the route set.TestAPIDocNamesEveryRouteholds it equal to the###route headings in docs/api.md and rejects/api/host(reserved for the host).TestMutatingRoutesAreRegisteredchecks everymutatingRouteskey against the table.cmd/harness/docs_test.go). Acommandstable drivesrun()and thecommands:part ofusage(); the printed usage text is byte-identical to before.langgets alangSubcommandstable. docs/harness.md must carry the usage text verbatim and a bullet for every command, includingversion. The README's command table must have one row per command, each with its usage summary.docs/docs_test.go). At most 300 lines; no harness/api/path (a forge URL likegit.cloonar.com/api/v1/…is allowed); no json/yaml block; the Documentation section links every owning doc.docs/docs_test.go). Every relative link and#anchorin README.md anddocs/**/*.mdmust resolve.The heading and anchor and link rules live once, in the new
internal/mddoc.scaffold/skills_test.go's privateheadings()was removed in favour of it.Deviations and things to check
.claude/skills/is not tracked in this repo. The lab'sinfo/excludeexcludes it, and it is a vendored skill bundle. So the requested edits toupdate-docs/SKILL.mdandtriage/AGENT-BRIEF.mdcannot ship from this branch. The two rules live indocs/agents/docs-map.mdinstead, whichupdate-docsstep 3 already treats as overriding its generic steps. Proposed text for the vendored skills:docs/agents/docs-map.md) — never 'document it in the README'."docs/adr/, and that path now exists. Nothing to change there./api/hostdata, so it is part of the versioned contract. The issue's table listed it for api.md but also said all of Host navigation's children move verbatim./api/host, which is reserved and not a route.scaffold/skills_test.gokeeps its hand-writtenharnessCommandsmap. It is an existing second copy of the command set that thescaffoldpackage cannot import frompackage main. Worth a follow-up.inherit. AGENTS.md did not import that sentence.allowtoken set omittedtable.go test ./scaffold/confirms they still resolve. They are untouched.web/src/api/*.ts,internal/serverandinternal/removecmdcomments that cited README sections now cite their new document.Verification
go test ./...green;go vet ./...and gofmt clean.wc -l README.mdgives 243;grep -cE '(^|[^[:alnum:]_])/api/' README.mdgives 0.harness initinto a scratch directory, thenharness check:ok: no problems found, exit 0.🤖 Generated with Claude Code
[autoland] verdict: pass
Lander verdict: CONCERNS (non-blocking). Approved, not merged. Autoland only merges a clean PASS.
Signal relied on:
labctl pr checks 266gavesuccess(ci / build-and-test: npm build and vitest,go vet ./...,go test ./..., build). The new drift tests indocs/,internal/serverandcmd/harnessran there. I didn't re-run them.Verified locally:
apiRoutes(internal/server/server.go:244) has the same 66 path→handler pairs as main'sroute()switch, and unknown/api/paths still return 404.harnesswith no args, an unknown command,lang, andlang zzgive byte-identical stderr and exit 2 on main and on this head.docs/, AGENTS.md and the theme README for each one. Outside "Configuration reference", every unit is found verbatim (ignoring link targets), except the edits listed under "Text changed in the move". I spot-checked a sample of the "dropped" rows in the sidecar disposition table againstscaffold/site/AGENTS.md, and AGENTS.md states those facts..claude/is excluded byinfo/excludeand is not tracked, so the skill edits really can't ship from this branch.origin/main(d7bc3dd).Concerns:
The stubs point at a duplicate follow-up issue. Issue #263's acceptance criterion says the stubs point at #264. #264 is the maintainer's ready-for-agent follow-up. This PR filed #265 (needs-triage, created 1.5 minutes after #264, "referred to there as #ISSUE2") and points everything at it:
Documented in #265.stubs indocs/api.md, e.g. lines 291, 542 and 614.internal/server/routes_doc_test.go:63and:108.Two open issues now cover the same work. Either retarget the stubs and the test to #264 and close #265 as a duplicate, or decide which issue survives.
A second heading and fence parser in the test code.
cmd/harness/docs_test.go:46-80(markdownHeadingplussectionAround) re-implements "what is a heading / what is a fenced block" outsideinternal/mddoc. It only handles```fences: no~~~, and no check on fence length. The PR says "the heading and anchor and link rules live once, in the newinternal/mddoc", and CLAUDE.md's "Every rule has exactly one owner" forbids adding a sibling. The fix is to givemddoc.Headingan offset or line and use it here.Unmet acceptance criterion, needs a human.
.claude/skills/update-docs/SKILL.md(the condense/remove rule) and the triage AGENT-BRIEF (the docs-map line) are vendored and untracked, so the PR couldn't change them. The rules live indocs/agents/docs-map.mdinstead. The proposed skill text in the PR body still has to go to the vendored skill bundle.Non-blocking note, out of scope per the issue: the scaffolded
add-backend-layout/SKILL.mdstill points at AGENTS.md "Layout" for the schema. That still resolves through the bullet's new pointer, but it could name the new AGENTS.md "Backend layouts" section directly.f91bec45fe3efc24eeeax
Addressed the lander's concerns (head is now
3efc24e):Documented in #264.stubs indocs/api.md, plus the hint and fixture ininternal/server/routes_doc_test.go, now name #264. #265 is closed as a duplicate; nothing references it any more.cmd/harness/docs_test.gono longer hasmarkdownHeading/sectionAround.mddoc.HeadinggainedOffset(byte offset of the heading line) andinternal/mddocgainedSection(doc, at), the one owner of "which section holds this offset", with its own test (tilde fences, nested fences, a fenced#that is not a heading). The harness test callsmddoc.Section.update-docs/SKILL.md, triageAGENT-BRIEF.md) still need a human:.claude/skills/is untracked here. The proposed text is in the PR body under "Deviations".Also: the non-blocking note —
add-backend-layout/SKILL.mdnow points at AGENTS.md "Backend layouts" directly.Merged
origin/main(the branch was behind by #268). Main's four README edits since the branch point moved verbatim into the owning documents: theswitchonCode/offCodesettings, theerrors-at-rest list shape and the Enabled switch's coded hints intodocs/api.md(Server-described forms, Coded messages); the upload gate's refusal codes under### POST /api/media/uploadindocs/api.mdwith a link from the Media gate paragraph; the baseline snapshot,snapshotOpsandundoOpintodocs/architecture.md"Git backbone" (with anundoOppointer under### GET /api/git/state); branch mode's first-undo paragraph into "Branch mode"; the Single-step undo limitation stays in the README.docs/.docs-syncis bumped to the fix commit.Verified:
go vet ./...,go test ./...green (including the four drift tests and the newmddoctests), vitest 3210 passed, gofmt clean.[autoland] verdict: fix-done
[autoland] verdict: pass
Lander verdict: CONCERNS (non-blocking). Approved, not merged. Autoland only merges a clean PASS.
Signal relied on:
labctl pr checks 266 --waitgavesuccesson head3efc24e:ci / build-and-test(npm build and vitest,go vet ./...,go test ./..., build) passed, andci / releasewas skipped. That covers the four drift tests, the newmddocSectiontests,scaffold/skills_test.goandinternal/element/agentsdoc_test.go. I didn't re-run them.Verified locally on this head:
#265any more.docs/api.mdhas 19Documented in #264.stubs, plus thePOST /api/media/uploadsection, whose remaining request/response gap points at #264.routes_doc_test.go:63and:108name #264. #265 is closed as a duplicate.markdownHeadingandsectionAroundare gone fromcmd/harness/docs_test.go, which now callsmddoc.Section(internal/mddoc/mddoc.go:112). That function is built onHeadings, so it uses the same fence rules (tilde fences, fence length).TestSectioncovers a fenced#.add-backend-layout/SKILL.mdnow names AGENTS.md "Backend layouts" directly.apiRoutes(internal/server/server.go:246) holds the same 66 paths asorigin/main'sroute()switch, and unknown/api/paths still 404.origin/main(#268) kept main's text. Each of main's README edits sinced7bc3ddis in its owning document, word for word except for added links:switchonCode/offCode, the at-resterrorslist and the Enabled hint codes:docs/api.md:26,:473,:488,:129.docs/api.md:754-762, linked from Media gate atdocs/architecture.md:129.snapshotOpsandundoOp:docs/architecture.md:146-166, with a pointer atdocs/api.md:651.docs/architecture.md:254.README.md:208)./api/path.docs/.docs-sync=c33d375(the last content commit) and 2026-10-13.Concerns:
.claude/skills/update-docs/SKILL.mdcarries the condense/remove rule; the triage brief guide names the docs map"..claude/is excluded byinfo/excludeand is not tracked onmain(git ls-tree origin/main .claudeis empty), so no PR in this repo can satisfy it. Merging auto-closes #263, and the follow-up would then exist only in this PR's "Deviations" section. Before or right after merging, either apply the proposed text to the vendored skill bundle, or waive the criterion on #263.Non-blocking note: the PR description still says the follow-up is "filed as #265" and that the stubs read
Documented in #265.. Both changed inc33d375. Worth correcting before the merge, so the merge commit's message doesn't point readers at the closed duplicate.