Field types defined once in Go: one definition reads, checks and writes every value #163
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#163
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
Each of the eleven field types gets one definition in Go that owns everything about the type: what a sidecar may declare for it, how a value in any spelling Hugo reads becomes the type's standard value, which errors it raises, and how it is written to a file. Every server path that reads, checks or writes a field value goes through it. Today this knowledge is spread over per-type switches in five packages, and they disagree or get skipped in the places listed below.
This is the server half of the change the maintainer asked for in #158's triage. The browser half (server-described forms) is #164.
Current behavior
Verified on
mainin triage (throwaway tests, since deleted):element.ParseParams, and an unknown type is a load error. But adatetimedefault:must be exact RFC3339:2026-07-10(quoted or not),2026-07-10 09:00:00and2026-07-10T09:00:00all fail to load, while front matter accepts any spelling Hugo reads since #151.Schema.ValidateValuesandCoerceValuesfor element saves (item rows included), properties saves and page create. The site checks (CheckPageParams,CheckCategoryParams) are separate functions that each save path has to call:coerceSeededFields) runs the category check but not the page check;elemops.ParseAndValidateBody) checks the body policy and which inline elements are allowed, but never the inline elements' params: no type check, no site check.pageops.fieldValue(properties),server.entryValue(module list) andpagetree(the page date). They disagree:tags: events): the module list reads["events"], properties read[], and the next properties save deletes it;count: abc): properties read0and a save writes0; the module list leaves it out.CoerceValues, then the shortcode sink (MergeParams; categories comma-joined inshortcodeValue) or the front-matter sink (FrontMatterChanges). A new element gets its defaults and zeros per type inelemops.NewShortcode. A link's<name>Targetcompanion is handled in seven Go files.internal/editormodule/load.go, the sorter ininternal/server/modules.go), and which params label an element in the element list (elemops.ElementSummary).Desired behavior
One definition per type. Each of the eleven types is defined once, in one Go package, and owns:
options,pageType,default), which sidecar lists may use it (multiselectonly in page-type fields), and name binding (categories);The link type owns its
<name>Targetcompanion.One sanitizer per type. It takes a value in any spelling Hugo reads, from a file, a submitted save or a sidecar
default:, and returns the type's standard value, or "unreadable". What "unreadable" means depends on where the value came from:default:: a load error naming the file.Every server path uses it, so none can skip a step: sidecar loading, the properties read, the module list, the page date, element saves (item rows included), properties saves, page create, and the rich-text body save. The body save now sanitizes and checks every inline element's params against that element's sidecar, and an error names the inline element and the param.
Standard values stay as today, except:
datetime: any spellingcontent.DateRFC3339reads, from any source, sidecar defaults included. A value without an offset is read as UTC. It is written as a quoted RFC3339 string.multiselect: a single string reads as a one-item list (properties read it as empty before).number: an unreadable value is "no value" (properties read it as0before).Everything else behaves as today. The browser is not changed: it keeps its own checks until #164.
Decisions
0.0is a real value, and writing it silently changes content.Key interfaces
element.FieldType,validFieldTypes,ParseParams/buildParam,normalizeDefault(internal/element).Schema.ValidateValues,Schema.CoerceValues(internal/element/values.go).fieldValue,ReadSettings,SaveSettings(internal/pageops/settings.go);coerceSeededFields(internal/pageops/create.go);CheckPageParams,CheckCategoryParams(internal/pageops).entryValue,stringListand the module sorter (internal/server/modules.go); the field rules ininternal/editormodule/load.go.NewShortcode,MergeParams,shortcodeValue,FrontMatterChanges,ParseAndValidateBody,ElementSummary(internal/elemops).content.DateRFC3339(internal/content/meta.go); the page date ininternal/pagetree.handleSavePage(internal/server/server.go), where each path's own site-check calls collapse into the shared check.internal/element/agentsdoc_test.go) reads the type list from wherever it now lives.Acceptance criteria
datetimedefault:written as2026-07-10,"2026-07-10",2026-07-10 09:00:00or2026-07-10T09:00:00loads, for element params and page-type fields alike, and is served as RFC3339.default: next tuesdayis a load error naming the file.tags: eventsin a multiselect field reads as["events"]in properties and in the module list, and a properties save keeps it.count: abcin a number field reads as the field's default, else empty, in properties, and the module list leaves it out. A properties save that doesn't touch it never writes0.start="2026-07-10"in an inline element is accepted and stored as RFC3339.pagefield that names a missing page or a page of the wrong type, as a properties save does.datetimein any spelling Hugo reads is accepted by element saves, properties saves and page create, and stored as RFC3339.datetimedefaults in any spelling, a single string in a multiselect, and body saves checking inline elements (CLAUDE.md rule).How to verify
make test. It buildsweb/distfirst, whichweb/embed.goneeds; a barego test ./...fails in a fresh worktree. The theme's Hugo build tests skip withouthugoon PATH; CI installs extended 0.163.3.pagefield.Assumptions to check
content.DateRFC3339reads the same spellings as Hugo'stime.AsTime(both build on spf13/cast), before AGENTS.md says "any spelling Hugo reads" for defaults.Out of scope
timeZonefor values without an offset.renderType).Blocked by
None - can start immediately