docs: complete and verify the split documents — every route, CLI, features, architecture and 22 ADRs #274
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!274
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/264"
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?
Summary
The content pass #263 left for later: the documents under
docs/are now true against the code and complete.docs/api.md: every one of the 66 registered routes has exactly one section in one shape: purpose, Request, Response, Errors (codes frominternal/apimsg), Languages, Writes. The 24 stubs are written. Behaviour that was split across feature paragraphs ("Translations on the wire", "Server-described forms" and others) is merged into each route's own section. Shared shapes are stated once under Conventions and linked: notation, request/response rules, errors and coded messages, page addressing, content trees, page answers, forms, sidecar labels, translations and writes. Every key was read from the handler and the Go types it marshals. An adversarial review then re-checked 40 claims against the code.docs/harness.md: the usage block is checked against the binary's output. Every command's flags, exit codes and--jsonshape are checked againstcmd/harness. A new## Checksection lists the rulesinternal/checkcmdapplies. The "ten commands" wording is gone.docs/features.md: checked againstinternal/addcmd,internal/removecmd,internal/initcmdand the shipped bundles.requiresis now stated once. It went from 585 to 470 lines.docs/architecture.md: rewritten as a narrative in reading order, 459 → 198 lines (≤ half), with no wire shapes. Each decision is one sentence plus a link to its ADR.docs/adr/: 22 records. Each one is dated to its originating commit, which was confirmed withgit log -S/--grep. Together they cover every decision #264 names: rich text (0006), media gate (0004), publish job (0018), branch mode (0011), branch bundles (0008), publish anyway (0014), host-navigation safety (0010),requires(0021,78e181e) and no aliases (0017,e4d8138). The others carry the rationale the docs and commits already stated.docs/host-navigation.md: two editorial link fixes only. The v1 shape was compared with deckle-hosted's producer (server/hostnav.go) and the editor's consumer, and they match.scaffold/site/AGENTS.md: no sidecar fact proved wrong against the engine, so it is unchanged.docs/.docs-syncis updated. No code changed since the baselinec33d375.Verification
go test ./...passes, after buildingweb/dist. That includesdocs/docs_test.go, the route test ininternal/server, the command test incmd/harness,scaffold/skills_test.goandinternal/element/agentsdoc_test.go.go vet ./...is clean.go run ./cmd/harnessmatches the usage block indocs/harness.mdexactly. Everyharness <cmd> -hwas run.grep -rn 'ten commands' docs README.mdreturns nothing.grep -c 'Documented in #264' docs/api.mdreturns 0.wc -l docs/architecture.mdis 198, against 459 after #263.Sampled routes: marshalled type vs documented keys
pageResponse(page, type, tree, layout, translation,omitempty);translationInfo(mode, source, changed, untranslated, synced);missingTranslationResponse(page, missing, type, title, file, source){page, type, tree, layout: PageLayout | null, translation?}; Translation (all five); missing answer (all six)savePageRequest(page, lang, path, name, values, body *string, items *[]map, children *savePageChildren{order:[{uid, enabled *bool}], removed}) →pageResponse{page, lang?, path, name, values, body?, items?, children?: {order: {uid, enabled?}[], removed}}→ page answerpagetree.Tree(lang, root);pagetree.Node(path, permalink, address, lang, translation?, l10n?, kind, type, title, titleCode?, weight, date?, childOrder?, hideChildPages?, containerFor?, managedBy?, hidden?, problem?, missing?, dir?, children) +enabledfrom MarshalJSON{lang, root: TreeNode}with every key,?exactly on the omitempty onesdeleteResponse(deleted, translations,omitempty, changed){page, lang?}→{deleted, translations?, changed}formDescription(fields, type?, items?, children?, synced?, errors?);element.FormField(16 tags);itemsDescription(element, label, i18n?, itemLabel, itemI18n?, summary?, fields, rows);itemRow(uid, enabled, fields)elementsResponse(elements, errors);element.Schema(16 tags);element.Param(11 tags, LabelCode-);sidecardir.LoadError(file, error)labelCodecorrectly absent on paramselementInsertRequest(page, lang, slot, parent, index *int, name) →insertResponse(pageResponse + path, uid){page, lang?, slot?, parent?, index, name}→ page answer +{path, uid}elementTransferRequest(lang, fromPage, from, toPage, toSlot, toParent, toIndex *int, mode) →transferResponse(source pageResponse, target insertResponse){lang?, fromPage, from, toPage, toSlot?, toParent?, toIndex, mode}→{source: PageAnswer, target: PageAnswer & {path, uid}}elementBodyResponse(body){body: string}moveRefsResponse(pages, media, translations,omitempty *transferPlan{moved, dropped}){pages, media, translations?: {moved, dropped}}translatePageRequest(page, lang, mode, subpages) →translatePageResponse(pageResponse + created, skipped,omitempty){page, lang, mode?, subpages?}→ page answer +{created, skipped?}gitStateResponse(state, reason, reasonCode?, reasonArgs?, lastSnapshotError, undoAvailable, mode, branch, tip, unsynced, unsyncedReason, undoOp?, undoPage?, undoLang?, undoTime?, undoPages?)pageStateResponse(page, removed, type,omitempty, tree,omitempty, also,omitempty){page, lang?}→{page, removed, type?, tree?, also?}publishJobState(202: job, phase);publishEmptyResponse(empty, pushed, message, code);disabledLinkWarning/disabledLink/linkingPage(disabledLinks; page, title, linkedFrom; page, lang?, title);publishJobRunningResponse(error, code, args, job)publishJobState(job, phase, commit?, pushed?, feedback?, message?, messageCode?, status?, error?, code?, args?, retryable?, hugoOutput?)pendingResponse(changes, unavailable?, unavailableCode?, unavailableArgs?);pendingChange(kind, page?, title?, titleCode?, path?, status, elements?, properties?, resources?);elementCounts(added, changed, removed)mediaUploadResponse(ref, name, width, height)file,target: "library",folder?→{ref, name, width, height}mediaLibraryResponse(folders, items);mediaItem(ref, name, size, type)mediaDeletePlanResponse(delete, keep);mediaKeepEntry(ref, pages)Corrected claims (was / now / verified in)
docs/api.md — conventions
{href, newTab}/ now:{href, newTab, title}/ verified in: element.LinkValue, internal/element/ftype_link.go:57items[<i>].<name>andbody/ now:childrentoo / verified in: childrenField, handleSavePage (server.go)errors/ verified in: pageAndTree, serializePage, element.go:96-145translationon GET /api/page, page/save and element/enabled / now: on every page answer in a non-default language / verified in: pageAnswer (translation.go:275) and callers{file, error}/ verified in: sidecardir.LoadError (sidecardir.go:179)docs/api.md — site and forms
{hugo: {state, previewUrl, restarts, lastExit, lastBuildError, output, version, source}}/ verified in: statusResponse/hugoStatus (server.go:290-326){title, languages: {code,label}[], acceptLanguage}with parse rules / verified in: siteResponse, acceptLanguageTags (server.go:353-461){name, label, labelCode?, widget, sortable}/ now: alsoi18n?/ verified in: editormodule.FieldInfo.I18n{field, label, labelCode?, options}/ now: alsoi18n?/ verified in: editormodule.Filter.I18n{fields, type?, items?, children?, errors?}/ now: addssynced?/ verified in: formDescription (forms.go:80-122){name, widget, settings, group, tab, value}/ now: alsolabel,labelCode: "label.enabled",required: false/ verified in: element.EnabledField (element/form.go:124-136){form, field, value}/ now: alsoparams?/ verified in: forms.go:792-797400 field.unknown {field},400 form.noItemRows {field}/ verified in: forms.go:405-423<name>Target/ now:<name>,<name>Target,<name>Title/ verified in: elemops/save.go:184-208 MergeParamsdocs/api.md — pages
hiddenonly on a module-managed entry whose parent's type sets hideChildPages / verified in: pagetree/pagetree.go:185-207, annotate.go:30{lang, root: TreeNode}with every Node key (path, kind, type, titleCode?, weight, date?, childOrder?, hideChildPages?, problem?, missing?, dir?, children, enabled …) / verified in: pagetree.Tree, pagetree.Node + MarshalJSON (pagetree.go:91-243)urlis the url itself / now: only a root-relative url is verbatim; a relative one lands under the language prefix / verified in: pagetree/build.go:497-503{page, elements: {uid, name, label, i18n?, summary}[]}; opening snapshotsrepair translationorstamp uids/ verified in: server.go:835-866, linktargets.go:48-136slotchild moved or toggled answers "422" / now: plain 422 element.slotMove / element.slotToggle {name}, not a field error / verified in: elemops/children.go:117-122valuesis the whole form (a declared param left out is removed); item rows keep their echoed uid; unknown params of a stored row dropped / verified in: elemops/save.go:155-161,236-257slugwhen a sibling's translation has that address / now: translation.slugTaken for any sibling holding the address in that language (incl. untranslated siblings' would-be addresses, grouping dirs) / verified in: pageops/translation_settings.go translationAddressTakentypekey → 400 page.typeImmutable; absent slug keeps, "" deletes; unchanged enabled writes nothing / verified in: pageops/settings.go:249-286{parent, lang, type, title, …}/ now: parent and type optional; blank title on quick create is 400 page.titleEmpty / verified in: pageops/create.go:143-195indexrequired (0..count−1), enabled required, page.rootDisable only for false / verified in: server/pageops.go:207-415, pageops/reorder.go:45{deleted, changed}/ now:{deleted, translations?, changed}/ verified in: deleteResponse (server/pageops.go:450-454)docs/api.md — elements and links
transfersnapshot for both pages; same-page or copysave/ verified in: element_transfer.go:291-327save, none when nothing changes / verified in: element_enabled.go:84-90{pages, media}without semantics / now: pages covers every uid in the subtree incl. source page; media the bundle filenames toPage lacks; translations? omitempty / verified in: moveRefsResponse (element_transfer.go:412){kind, label, found, address?, fallback?}/ verified in: linkResolveResponse (linktargets.go:155-301)docs/api.md — translations and git
translatesnapshot naming every created page / verified in: translation_pages.go:115, git.go:533also= translations only / now: also partner pages of a multi-page step (transfer, relocation, translate with subpages) / verified in: git.go:769-805,842-855{page, removed, type?, tree?, also?}(pageStateResponse), no layout/translation / verified in: git.go:680-713docs/api.md — publish
200 {empty: true, pushed, message}/ now:{empty: true, pushed: false, message, code: "publish.empty"}/ verified in: publishEmptyResponse (publish.go:269-282){warning: {disabledLinks}}/ now:{warning: {disabledLinks: {page, title, linkedFrom: {page, lang?, title}[]}[]}}with sort and match rules / verified in: disabledLinkWarning (publish.go:305-330)409 {error: "a publish is already running", job}/ now:409 {error, code: "publish.alreadyRunning", args: {}, job}; body decoded first / verified in: publishJobRunningResponse (publish.go:149)409 publish.noRepoor409 publish.unavailable {detail}/ verified in: requireGit (git.go:126-196)500 publish.listChanges {detail}/ verified in: publish.go:417-420{job, phase, commit, pushed, feedback, message?}/ now: addsmessageCode?;feedbackis"polling" | "static"/ verified in: publishJobState (publish.go:125-139){job, phase, pushed, message}/ now: addsmessageCode: "publish.empty"/ verified in: jobState (publish.go:237-239){…, status, error, retryable?, hugoOutput?}/ now: addscode,args?; full per-status code table (422 mediaMissing/mediaUnverifiable/buildFailed/buildTimeout, 502 pushRejected, 500 …) / verified in: publishJobState, publishset.go:131-182404 publish.noSuchJob/ verified in: publish.go:515-518?commit=required;{phase: publishing|live|failed|unknown|static, runUrl, message?, code?}/ verified in: publishStatusResponse (publish.go:687-715), forge/poller.go{discarded}with409 discard.noRepo|discard.unavailable,500 discard.allFailed/ verified in: pendingResponse, pendingChange (pending.go:38-86), discardAllResponse (pending.go:542)docs/api.md — media
file,target("library"),folder?; response{ref, name, width, height}/ verified in: mediaUploadResponse (internal/server/media.go:76-81, 90-168)args.limit/ now: 15 MiB (15728640 bytes) file, body 1 MiB more / verified in: internal/media/gate.go:9, media.go:67docs/api.md — integration review
409 page.rootExists · 422 · 500 page.writeRoot/ now: adds400 request.malformed,404 editor.unknownLang/ verified in: internal/server/pageops.go:229-236 (decodeBody, opsRootDefault)404 editor.unknownLang/ now: added / verified in: internal/server/pageops.go:196 → opsRootlang"validated only" / now: validated only for a bundle reference, not read for a library one / verified in: internal/server/media.go:504-530, 824-847docs/harness.md
harness check --jsonparagraph links "The ten commands" / now: wording removed;grep -rn 'ten commands' docs README.mdis empty (README never had it) / verified in: cmd/harness/main.gocommandstable (10 commands incl.version)features --json/add --jsonexamples showversion5 (news) and 2 (categories),installedVersion1 / now: news v6, categories v3 (actualharness add newsoutput) / verified in: scaffold/features/*/feature.yaml viaharness features --jsonharness add --jsonfailure object / now:{formatVersion, feature, status: conflict|error, conflicts?, error}/ verified in: cmd/harness/features.gofailureOutputfeatures --jsonfailure not documented / now:{"formatVersion":1,"error":"..."}(no status) / verified in: cmd/harness/features.gofeaturesOutputcheck --jsondescribed as one-liner, "(not a Hugo site)" only / now: full shape with severity, area values, counts, failure object / verified in: cmd/harness/check.gocheckOutput,checkFailureOutput; internal/checkcmd/check.goFinding,Counts, Area constantsreconcile --jsonshape not documented (only prose) / now: success and failure objects,resolved[{path,winner}]/ verified in: cmd/harness/reconcile.goreconcileOutput,reconcileFailureOutputcheckprose lists only part of the rules / now: adds errors for slot shortcode that is not a wrapper, two pages at one address, translation without source, unreadable hugo.toml language config, hugo.toml/harness.yaml language disagreement; warnings for connected-translation structure and default-language-suffixed page / verified in: internal/checkcmd/check.go:516, 556, 587, 605-641, 733, 547remove"blocked" lists only content in use / now: a vendored file the owner customised also counts as in use (blocks without --purge) / verified in: internal/removecmd/run.go "Refuse or purge",Result.CustomizedFilesremove --jsonversionunmentioned / now:versionomitted when no receipt / failed early (omitempty) / verified in: cmd/harness/features.goremoveOutputremove --jsonerror: "carries every array empty" only for early failures / now: error carries partial report if writing began, else empty arrays; text mode prints a "re-run to finish" hint / verified in: cmd/harness/features.go removeCmdlang addJSON:hugoVersionalways present / now: only with statusadded(omitempty);already-presenthashome: "", nohugoVersion/ verified in: cmd/harness/lang.golangAddOutput; ranlang add detwicelang removenot-present/errorlanguage label unspecified / now:language.labelis""/ verified in: internal/langcmd/langcmd.go Remove; ranlang remove frlang addrefusals omit missing harness.yaml/hugo.toml / now: both required ("not a deckle site") / verified in: internal/langcmd/langcmd.goloadSitehugo.versionis taken as-is when set andhugo.binaryis not; the binary (or PATH hugo) is probed otherwise / verified in: internal/langcmd/langcmd.gocheckHugoservelifecycle only listed output lines / now: start order, git degradation, branch-mode startup sync, DECKLE_FORGE_TOKEN publish feedback, media dir creation, exit 1 causes, SIGINT/SIGTERM exit 0, usage errors (exit 2) for non-loopback--addrand bad preview flags / verified in: cmd/harness/main.goserve,parseServeArgs,buildForgeClient,startupSyncdisableKindsedit; fieldsedit/removed/added/after/gap/ verified in: internal/addcmd/receipt.goReceipt, internal/addcmd/config.goConfigEdit; actualharness add newsreceipt (18 files, version 6)-hbehaviour undocumented / now: Conventions bullet: single dash accepted, positional either side of flags,<cmd> -hprints flags and exits 2,harness -h/--help= unknown command exit 2 / verified in: ran everyharness <cmd> -hharness.yamlsection:hugo.version"exact" only / now: invalid format is a load error; emptylanguageslist, bad/duplicate code load errors;hugo.binaryrelative to site dir; whatinitwrites / verified in: internal/siteconfig/siteconfig.goLoad,validateLanguages; internal/initcmd/init.go; generated harness.yaml--jsonshapes live under## Machine-readable output(one###per command);harness langlinks to them / verified in: n/a (restructure; no test pins it)docs/features.md
@@TITLE@@,@@LANG@@,@@HUGO_VERSION@@in five files / now: four markers,@@LANG_LABEL@@too (the language label in harness.yaml) / verified in: internal/initcmd/init.go:44-49,266-272; scaffold/site/harness.yaml:7assets/media/user_upload// now: listed (scaffold/site/assets/media/user_upload/.gitkeep) / verified in: internal/initcmd/init.go buildPlan; scaffold/site treeallow.childOfis "one of the site's own" (unenforced prose) / now:childOfmust NOT be a page type the bundle ships;pageTypemust be one it ships; pairs may not repeat / verified in: internal/addcmd/manifest.go parseManifest (allow loop)requiresrules described as "acyclic, which a test proves" / now: dropped from the key table (not a manifest-validation rule; only the three checks valid name / not self / not repeated are) / verified in: internal/addcmd/manifest.go parseManifest (requires loop){deleted, changed}" in features.md / now: link to api.md#get-apipagedelete-plan (wire shape belongs to api.md) / verified in: docs/api.md:433 headingfind scaffold/features -type f(news 18 excl. feature.yaml, categories 11), scaffold/features/*/feature.yamlconflict, requirement failure statuserrorneverconflict/ now: unchanged, confirmed / verified in: cmd/harness/features.go:363-372, isRequirementFailuredocs/architecture.md
img, hasrequired: false; the theme always emitsalt/ verified in: scaffold/editor/elements/img.yaml:29-35, scaffold/themes/deckle/layouts/partials/ce/img.html:41-50dafdf11(#201),354a2ac(#202),2b929f7(#203)ce46b73(PR #229),9da8dc8(PR #258), web/src/i18n/{en,de}.json{deleted, changed}/ now:{deleted, translations?, changed}(moved to api.md) / verified in: internal/server/pageops.go:450-454 (deleteResponse)childrentoo (code trans.connectedStructure) / verified in: internal/server/element_transfer.go:200-203, server.go:1003, translation_ops.go:53-64docs/host-navigation.md
i18non the wire, see Sidecar labels on the wire below)" (line ~472; that section is in docs/api.md, not below in this file) / now: link[Sidecar labels on the wire](api.md#sidecar-labels-on-the-wire)/ verified in: docs/api.md:145 (### Sidecar labels on the wire)Follow-ups filed (behaviour documented as it is, per #264's scope)
POST /api/media/deletewith a page deletes any file in the bundle, the page's own_index.mdincluded.deckle: save before synccommit shows up as an undo of a page named "before sync".harness initwrites--langand--titleintoharness.yamlwithout validating them.path/pagenaming, uncoded text, absolute paths in args, stale comments.No uncatalogued error code was found: every code a handler writes is defined in
internal/apimsg.Closes #264
🤖 Generated with Claude Code
[autoland] verdict: pass
Lander verdict: CONCERNS (non-blocking; approved, not auto-merged)
What vouches: CI aggregate
success:ci / build-and-testrango vet ./...,go test ./...(includingdocs/docs_test.go, the route and command drift tests,scaffold/skills_test.goandagentsdoc_test.go) and the frontend build and tests.ci / releasewas skipped as expected.Conventions:
Closes #264.docs/and matches #264's scope.main.Acceptance criteria checked:
ten commandsis gone.Date:matches its commit.Spot checks against the code:
docs/api.md: 24 former stubs plus about 25 other routes checked; no wrong claims.docs/api.mdexist ininternal/apimsg.mutatingRoutesin both directions.The PR's own goal is "true against the code". These sentences are not:
Wrong claims
docs/architecture.md:49-50says "Mutating requests run under one handler-level lock applied inServeHTTP; reads never take it."GET /api/pageandGET /api/page/elementstake it (uid repair), and/api/publish-alllocks itself outsideServeHTTP(internal/server/mutation.go:15-21, 44-50).docs/adr/0004-media-gate.md:21-22says "the stored extension always matches the sniffed type"..jpg, alpha becomes.png(internal/media/process.go:87-97; the same at20192b6).docs/harness.md:454(add --json,addedrow) says a receipt whose "requires, version or steps differ … is rewritten".requireschange triggers a rewrite (internal/addcmd/add.go:920-921). A different version or steps with no file missing givesalready-installed, and the receipt is left as is.docs/features.md:253-259states it correctly, so two documents now disagree on one fact.docs/harness.md:489-490saysremove --jsonleaves outversiononly when there is no receipt or the remove failed before reading one.internal/removecmd/run.go:222comes before:225).docs/adr/0003-wip-snapshots-are-the-undo-store.md:18-20says undo is a restore "through the normal save path".9d2732f:internal/server/git.goRestorePaths; nowgit.go:842RestorePathsFiltered).docs/adr/0007-module-shell-with-state-in-the-url.md:sessionStorageprecisely so it survived a reload (b079daa^:web/src/selection.ts:48-52).7696f9a(/edit/<lang>/<page>/<uid>), and the paragraph otherwise reads as current state.docs/adr/0001-canonical-round-trip-content-engine.md:20-21says "the server is the only canonicalizer".internal/removecmd/purge.go:170-193). "never the browser" would be accurate.Material omissions
docs/harness.md## Check, errors (:254-261): front matter that does not decode is a content error (internal/checkcmd/check.go:678-681) and is not listed.docs/harness.md## Check, warnings (:262-271): a connected translation whose elements lack a uid or share one (check.go:578-580) is not listed.docs/architecture.mdor any other document, althoughdocs/adr/README.mdsays the narrative links each record. The other 21 are linked.web/src/i18n/glossary.mdand thatscaffold/langfiles_test.goholds each one complete. That fact now appears in no document.Minor (no action needed to land)
docs/api.md: a few rare filesystem 500s are not listed:page.readLanguagesandpage.listTranslationsonmedia/bundle,thumbanddelete, andpage.listTranslationson pageundoanddiscard.error.unexpectedon librarymedia/delete.docs/api.md:1686: the pending page rowtitleisomitempty.docs/architecture.md:147: "never force-pushed" should be "never a plain--force", since the lease push is--force-with-lease.docs/adr/0017:35: "lang addtouches no content". It still createscontent/_index.<code>.md.None of this blocks landing: the documents are far more accurate than on
main. Items 1–7 are short sentence fixes. Fixing them before merge keeps the ADR records, which are never edited to match newer code, correct from the start.