German editor: seven uncoded strings and two small defects the review found (Enabled hints, upload refusal, at-rest required error, undo op word, digest plural, dialog overflow, first undo) #267
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#267
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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
A German-locale review of the editor found seven defects that read as "missing translations" but are not catalog gaps: in five of them the server hands the client English text with no message code, so the client has nothing to look up; one is a CSS overflow; one is an undo-availability gap. Fix all seven with the mechanisms the repository already has (coded messages via
apimsg, thecode+argsfield-error shape,noteCode-style code companions, a per-op catalog entry, a baseline WIP snapshot).Each item is independent and may land as its own PR referencing this issue.
Current behavior
Enabled hints stay English. The hints under the Enabled switch are plain Go constants with no code:
internal/pageops/settings.go(pageEnabledOn/pageEnabledOff) andinternal/server/forms.go(elementEnabledOn/elementEnabledOff). They travel assettings.on/settings.offof theswitchfield andSwitchView(web/src/widgets/controls.tsx) renders them verbatim. Only the translation-language case is localized, becauseElementForm.accessFieldsoverrides the hints client-side withform.enabled.onLang/offLang.Upload refusal stays English. The multipart/oversize-body errors are coded (
library.upload*), but the media gate's refusals are not:writeMediaReject(internal/server/media.go) passes amedia.RejectErrorthroughapimsg.FromError, which wraps it aserror.unexpectedwith the English text indetail("unsupported upload: detected …; allowed types are JPEG, PNG, WebP, AVIF and PDF",internal/media/process.go). The catalog'sserver.error.unexpectedis{detail}, so the text passes through.Required-field error on a freshly inserted element stays English. A save's 422 carries
{field, msg, code, args}anddecodeFieldErrorstranslates it. The form description's validation-at-resterrors(issue #172) ismap[string]stringbuilt fromFieldError.Msgonly (form.restErrors,internal/server/forms.go). A freshly inserted element opens with its required field already empty, so the at-rest path is what the user sees: "this field is required" instead ofserver.field.required's German.Undo tooltip shows the raw op word.
undoStepText(web/src/undoinfo.ts) insertsinfo.opas the server wrote it into the snapshot subject: "Rückgängig: save auf Leistungen". The vocabulary is fixed and server-owned (snapshotWIPcallers):save,create,rename,move,reorder,transfer,translate,undo,discard,stamp uids,repair translation,media upload,media delete,media rename,media move,media folder create|rename|move|delete, plus whateveropthe settings/enable saves pass throughsnapshotPage."1 Images" / "1 Bilder". The children digest's
labelis the item element's sidecar label, which sidecars write as a plural noun.element.digest.itemsLabelled.oneis still{count} {label}(web/src/components/ElementCard.tsxdigestCount), so the one-form cannot singularize a label it does not own.Long page paths cut off in the "Linked pages are disabled" dialog.
.dialogis 380px wide;.disabled-links__path(web/src/styles.css) is a monospace inline span with no wrapping rule, andul.disabled-linkshasoverflow: auto, so a long path overflows horizontally and looks truncated.The first change after the editor starts cannot be undone.
UndoParentresolvesrefs/deckle/wip^(internal/gitrepo/snapshot.goWipParent). No baseline snapshot is taken at startup, and the firstSnapshotWIPcommits without a parent, so until a second snapshot existsundoAvailableis false andPOST /api/page/undohas nothing to restore. Branch mode has the same gap while the draft tip equals or descends from main.Desired behavior
The four Enabled hints are
apimsgcodes. The switch field'ssettingscarriesonCode/offCodebesideon/off(thenote/noteCodepattern);SwitchViewresolves the hint through the same display helper that resolvesnote. The client-side translation override inaccessFieldsstays as the one place the translation-language variant is worded.The three gate reasons (
RejectOversize,RejectUnsupported,RejectCorrupt) answer with their own codes and args (limit/size,detected,type), rendered throughcodedErrorlike every other server refusal.error.unexpectedis never the vehicle for a message the server authored.The form description's at-rest
errorscarry the same coded shape as a 422'serrorslist, and the client folds them withdecodeFieldErrors(one owner for "several messages on one key joined;", which today lives both inrestErrorsand indecodeFieldErrors). Wire shape change:errorsbecomes a list of{field, msg, code, args}; document it in the README's form-description section.The client translates the op through a catalog key per op (
undo.op.<op>), falling back to the raw word for an unknown op. A vitest/Go drift test pins the vocabulary: every op stringsnapshotWIP/snapshotPage/snapshotRelocationis called with has a key inen.json(theinternal/pagepath/testdatafixture pattern or an exported list, not a hand-kept comment).Pick one, state it in the PR: (a) the one-form drops the sidecar label and reads like
element.digest.items.one("1 Eintrag" / "1 item"), or (b) uses the×shape the element variant already has ("1 × Bilder"). Do not add a singular label to the sidecar schema in this issue..disabled-links__pathwraps (overflow-wrap: anywhere) and the list no longer scrolls horizontally.A baseline snapshot exists before the first mutation changes the tree, so the first change is undoable in a fresh session. Take it where the evidence says it is cheapest: at server start when the repo has no wip tip (WIP mode) or lazily in
snapshotWIPbefore the mutation when no tip exists. Branch mode: decide and document whether the same baseline applies (a draft commit equal to main) or the gap is accepted; say which in the README's undo section.Decisions
restErrors' duplicate of the"; "join; the server serves the list, the client folds, as it does for the 422.Key interfaces
internal/apimsgdefine,web/src/i18n/servermessages.jsonexport (internal/apimsg/export_test.go) andweb/src/test/i18nParity.test.ts: new codes must appear inen.jsonandde.json.element.FormField.Settings(internal/element/form.go) andSwitchView(web/src/widgets/controls.tsx).media.RejectError(internal/media/gate.go),writeMediaRejectandwriteError/codedError(internal/server/media.go,server.go).formDescription.Errors,form.restErrors(internal/server/forms.go);decodeFieldErrors(web/src/api/client.ts);ElementForm.routeErrors,PageFormsetErrors.undoStepText/undoTooltip(web/src/undoinfo.ts),parseSnapshotSubject(internal/server/git.go).gitrepo.SnapshotWIP,WipParent,UndoParent(internal/gitrepo/snapshot.go,branch.go);undoState(internal/server/git.go).Documentation and skills
README.md: form-descriptionerrorsshape (item 3), the switch field'sonCode/offCode(item 1), the upload error codes (item 2), the undo section's baseline semantics (item 7).scaffold/site/AGENTS.md: the element-form and page-type sections that describe the Enabled switch (around the "Element sidecar schema" and "Page types" headings) only if the documented field shape changes; the agentsdoc drift test must stay green.scaffold/site/.claude/skills/: no skill walks through these behaviours; confirm withgrep -rn 'Enabled\|undo\|upload' scaffold/site/.claude/skills/in the PR and say so.web/src/i18n/glossary.md: add the undo op words and gate refusal wording to the German glossary.Acceptance criteria
snapshotWIPop has no catalog key.refs/deckle/wip, the first save reportsundoAvailable: trueandPOST /api/page/undorestores the pre-save file.go test ./...andnpm testgreen, includingi18nParity,i18nLeak,agentsdoc_test.go,skills_test.go;servermessages.jsonregenerated.How to verify
go test ./internal/apimsg/...regeneratesservermessages.json;npm test -- i18nParitylists any catalog key missing inen.json/de.json.GET /api/formfor an element and a page withAccept-Language: de; checksettings.onCodeand theerrorsshape.curl -F file=@x.svg …/api/media/uploadanswers 422 with acodeother thanerror.unexpected.git update-ref -d refs/deckle/wipin a test site, start the server, save once,GET /api/git/state.Assumptions to check
formDescription.Errorsas a map (grepdescription.errors/desc.Errorsin client and tests).settingsis free-form on the wire for the switch widget (it ismap[string]any), so addingonCode/offCodebreaks no widget test.describe(input)text is safe as an arg (it names a sniffed type, not user text).Out of scope