- money_field_test.go: compile gates (moved from the smoke file and
extended), model column gate, save-time value check, list projection
- settings secret projection and put without a database; tabs, settings
richeditor sanitize and the danger flag as internal unit tests
- Postgres: TestMoneyFieldSave, TestSettingsSecretPostgres and the Phase 18
payload cases of TestPhase10OpenAPIConformance
- vitest: money.ts helpers spec, MoneyField blur-only and server-error cases
- gate arrays and the cabana README Testing section name every test
A scale-2 money field over a four-decimal column (the documented fee
example) was served "12.5000" and answered money.too_precise when the
untouched value was saved back. Trailing zeros carry no precision, so the
server check and the SPA parser count only significant fraction digits;
the value still reaches the column unchanged, never rounded or truncated.
- cabana validateMoneyValue and admin parseMoneyInput trim trailing zeros
before the scale comparison
- forms docs and cabana README name the rule; embedded dist rebuilt
- MoneyField spec asserts 12.5000 at scale 2 is accepted, 12.5010 refused
- the settings schema passes the settings model as the DropdownOptionsProvider
- boot refuses a dropdown method on a settings model without DropdownOptions
- cabana README and docs/backend/settings.md describe it
- type: password on a settings form binds a lagoon.Encrypted column as a
write-only secret (any other column fails boot naming the field): the
GET omits it from data and reports stored, env or empty per field in
meta.secrets; on PUT an absent key or "" keeps the value, null clears
it, a non-string is a 422; the answer never carries a value
- cabana.SecretFallbacks (field to config key) gives the env state;
cabana.SettingsMeta, SecretStored/SecretEnv/SecretEmpty exported
- settings forms accept richeditor: the Put sanitizes through seaglass
like a controller save and returns meta.notices; repeater and
dependsOn stay refused (settingsFormOnly next to controllerFormOnly)
- backend::lang secret.* phrases in en and pl
- admin SPA: PasswordField secret status line (stored with Remove stored
value, env, empty, pending clear with Undo), SettingsFormView tabs with
error-tab activation and the secrets/notices meta, settings payload
that never sends an untouched secret; regenerated OpenAPI and TS
types; rebuilt dist
- vault fixture, TestSettingsSecretCompile, Docker TestSettingsSecretSmoke
and TestSettingsRichEditorSmoke, conform settings fixture with a
password field; settings.md sections, forms.md notes, cabana README
- columns.yaml type: money with currency or currencyFrom (exactly one),
never searchable, not sortable by default; every list row carries the
currencyFrom column's value under its own key; the schema serves
currency and currencyFrom on the column
- pact.AdminRecordAction.Danger, served as danger on cabana.RecordAction
(absent when false); RecordActions.vue passes it to the confirm dialog
- CellValue money kind through formatMoney with the row's code, the em
dash when empty and the amount alone without a code; DataTable aligns
money headers and cells to the right
- PreviewView renders Edit only when a registered value field allows
update (F6)
- main.css .summer-table* and .summer-stat__note kit classes (F7) over
--c-* variables; cabana README kit rows
- fixture record actions void (danger) and resend; TestMoneyListCompile,
TestMoneyListSmoke and TestRecordActionDangerSmoke; regenerated OpenAPI
and TS types; rebuilt dist; lists-and-filters and admin-controllers docs
- type: money on fields.yaml with currency or currencyFrom, scale (2..4,
default 4) and max; boot fails naming the field on two sources, none,
a bad scale or max, a column that is not a string or a Scanner/Valuer,
or a currencyFrom that is not a string column
- a save answers 422 money.invalid, money.too_precise or money.too_large
on the field and fills the accepted decimal string unchanged; the same
check runs on controller, settings and relation child saves
- backend::lang money.* phrases in en and pl
- admin SPA: money.ts (parseMoneyInput, trimMoneyDisplay, formatMoney over
Intl with the string itself), MoneyField.vue with the currency suffix,
blur validation and the read-only box, preview through the control,
the field scope on PreviewView; regenerated OpenAPI and TS types,
rebuilt modules/boardwalk/dist
- acme.payments fixture with TestMoneyFieldCompile and the Docker
TestMoneyFieldSmoke; forms.md Money section and cabana README
signpost.Registry.Pattern now takes an item override only when it starts
with a single "/" and holds no surrounding whitespace or control
characters (signpost.ValidItemPattern); a javascript: URL or a
protocol-relative //host falls through to the next layer.
- form-action 'self': injected markup cannot post a native form off-origin
- frame-src 'none': no framed content; a sandboxed srcdoc preview is not
fetched and still renders
- header tests, README and admin-spa docs name the directives
Two first requests to GET .../singleton both missed the record; the
loser's insert hit the unique index and the route answered 500. A
unique violation from SingletonRecord now reruns the transaction once,
which finds the winner's record. TestSingleton fires six concurrent
first requests and requires 200 with the same id from each.
- signpost: activation refusals, host overrides, D-02 pattern layers
incl. PatternFrom, ReferenceOptions depths
- seaglass: removal report, bluemonday bypass corpus, Check
- pact Option.Depth omitempty and the menu item type contracts; party
activates menu item types after Register and before the first Boot
- cabana: repeater compile and decode, dependsOn compile and recheck,
richeditor keys and notices, reorder and singleton boot checks
(Docker-free); repeater save, dependent recheck, richeditor save,
reorder and singleton routes on Postgres; conformance cases for the
documented 404, 409 and 422 answers of the new routes
- admin: richeditor value contract, link popover, notices and chunk
failure; repeater bounds, remove and reorder, collapsed rows;
dependent dropdown states and last request wins; reorder
announcements, partition switch and tree rules; SingletonForm spec
- gate: required names in the go, admin and postgres stages
- scripts/check-phase17.sh: stages go, plugins, admin, layers, docs and
postgres across the framework, four plugin repositories, the host and
both layers; go test -json, vitest JSON and TAP detectors, required
layer test files, --self-test, --all with NOT RUN postgres and
PHASE17_REQUIRE_POSTGRES
- TestRegistryDuplicateKey: a duplicate key fails Activate naming both
plugin IDs, nothing is published
- TestSanitizeByteStable: clean editor HTML comes back byte for byte
- ReorderView: indented tree with drag handle, move up/down, outdent and
indent (buttons and arrow keys on the handle), pointer drag before, after
or inside a row and never into its own subtree; one POST with the whole
node set 400 ms after the last move, flushed on leave; a failed save
restores the saved tree, a 409 reloads; loading, empty, load-failure and
403 states; partition selector
- ListView: outline Reorder button before New record, passing the filtered
partition
- SettingsIndexView opens a singleton controller's record through
GET …/singleton; FormView in singleton mode has no Delete and returns to
the settings index
- reorder.* and messages.list.reorder phrase keys (en, pl), smoke spec, docs
- RepeaterField: rows with drag handle, index, titleFrom title, error chip,
move up/down (buttons, ArrowUp/ArrowDown on the handle, native drag and
drop), expand/collapse, remove (asks only for a row holding a value),
add with nested defaults, maxItems counter, empty state, collapse all;
every change edits form state only
- 422 row paths (content.3.answer) open the row, mark it and focus the
nested control; the banner no longer repeats row messages
- DropdownField dependsOn: waiting, loading, not used, empty, loaded with
depth indent, failed with retry; cached per sibling value, last request
wins, a stale value is cleared after a sibling change and announced
- FormView provides form feedback (errors and notices by row path, the
polite live region, its confirm dialog) and the options cache; FormGrid
provides the sibling scope for repeater rows
- repeater.* and form.dependent_* phrase keys (en, pl), smoke specs, docs
- RichEditorField: Reka toolbar, link popover with the http/https/mailto/tel
and relative URL policy, Edit HTML view, lossy note, skeleton while the
lazily imported TipTap chunk loads, HTML view fallback when it fails
- untouched values are submitted byte-identical; after an edit the value is
the editor HTML (empty paragraph -> ""); a value from outside (the save's
sanitized answer) reloads the editor
- read-only renders through a client display allowlist, never as stored
- TipTap 3.31.4 (core, pm, starter-kit) pinned exact after the package check
- richeditor.* phrase keys (en, pl), smoke specs, forms docs
- cabana: config_reorder.yaml (title, nameFrom, modelClass, parentColumn,
sortColumn, maxDepth, partition) with fail-loud boot checks and the
built-in reorder toolbar button; list schema carries reorder
- GET/POST {controller}/reorder: scoped set (ListExtendQuery plus the
partition) locked FOR UPDATE, 409 reorder_stale on any set difference,
422 for duplicates, foreign parents, cycles and maxDepth, both columns
written without model hooks
- pact.AdminSingleton: GET {controller}/singleton (first or create, form
scope checked), form schema singleton, list/create/bulk/delete 404,
no config_list.yaml needed
- swag, inventories, token classes, CSRF walk, conformance cases, backend
lang reorder.stale and load_failed, reorder and singleton smoke tests, docs
- seaglass (new module): bluemonday v1.0.27 policy ported from Journal
without the Blog Editor rules, byte-stable token comparison,
SanitizeReport, Sanitize, Check, Removals
- cabana: type richeditor (common keys plus size); every create and update
value sanitized before Fill, non-string values are a 422, removals become
meta.notices; settings and relation forms refuse the type
- backend lang richeditor.removed_* and markup_changed (en, pl plurals)
- TestSeaglassSmoke, TestCompanionsRichEditorSmoke, README and docs
The prefix was only trimmed. Without a leading slash ServeMux read its first
segment as a host pattern, so every upload silently answered 404, and a CDN
URL panicked in ServeMux and surfaced as a misleading route conflict. With
storage.uploads.serve on, BuildRouter now refuses a prefix that is not an
absolute URL path (scheme, host, query, fragment, whitespace, wildcard
braces, empty, . or .. segments, or a bare /) with an error naming the key.
The route answered with the blob's stored Content-Type, which attach.Store
sniffs from the bytes, so a public text upload starting with <html> was
served as text/html on the application origin; nosniff does not help when
the declared type is already HTML. Every response now carries a sandboxing
Content-Security-Policy, and a successful response whose type is not a
raster image (JPEG, PNG, GIF, WebP, AVIF) gets Content-Disposition:
attachment, on GET and HEAD. Covers stay inline.
Every 5xx became 499 once the request context was cancelled, so a panic or
a real database error on a request the browser had abandoned passed the
phase gate's 5xx scan. Now a 5xx is logged as 499 only when the client had
already gone when the status was written and the handler did not panic;
recoverJSON and recoverBare mark the access log entry. Every record of an
abandoned request carries client_gone=true. The gate self-test plants a
5xx with client_gone=true and must refuse it.
An unmatched request logged its path. A token route reached with another
method (405) or with a trailing slash (404) is answered by ServeMux before
any route handler records the pattern, so the share or invitation token in
the path reached the log. The record now carries method, pattern, status
and duration only.
- TestUploadsMount: key off registers nothing; on, a public blob is served
with its content type and nosniff, HEAD has no body, private, missing,
partition and encoded traversal paths 404, an unpublished bucket or
database 404s, a custom prefix is honoured
- mountUploads refuses a plugin route at or under the prefix, so
BuildRouter fails instead of only an identical pattern failing at
Assemble (README and storage docs updated)
- TestAccessLog: pattern and status for 200, handler 404 and recovered
panic, path only for unmatched requests, locale-prefixed requests keep
the pattern, integer duration_ms, no query, header or Authorization
value, ResponseController flush still works
A client that goes away mid-request cancels the context; a handler then
fails on context.Canceled and writes a 500 nobody receives. The request
log now records it as 499 (client closed request) so a grep for 5xx shows
real server errors only.
- one Info record "http request" per request with method, matched route
pattern, status and duration_ms; path without the query only when no
route matched
- the matched route records its pattern through a request-scoped entry,
so locale prefix rewrites do not hide it
- logs through the published *slog.Logger or slog.Default; off by default
- surf README, logging and configuration docs describe the key
- surf mounts GET {storage.uploads.public_path_prefix}/{path...} on
attach.StaticHandlerPublic when storage.uploads.serve is true
- bucket and database resolved per request; X-Content-Type-Options: nosniff
- surf README, storage and configuration docs describe the key
- The process-global debounce map was keyed by token ID, so a stamp from one test database suppressed a later write in another and turned TestBackendTokenPostgres red depending on order
- Each compositeGuard now owns a stampThrottle; the backend guard and the ResolveBackend guard of one activation share it, so a single-app binary still writes last_used_at at most once a minute per token
- TestTokenLastUsedThrottlePerGuard pins the per-guard isolation
TestMain scanned os.Args for a literal -test.short, but go test passes
-test.short=true, so beachcomber, conga, lagoon and lighthouse started a
Postgres container even under -short. Parse the flags and use
testing.Short() instead; skip cabana's newListService fixture under -short
like adminGorm already does.
- Add TryMap, Filter, Reduce, KeyBy, GroupBy, Uniq, UniqBy, Partition
and Find, plus the Set type built with SetOf (Has, Add, Slice).
- Add unit tests at 100% statement coverage and a runnable example for
every function and Set method.
- Complete the module README: contracts, use cases, Pluck rationale,
standard-library pointers and the full API reference.
- docs/architecture/introduction.md: add a Utilities row for sieve.
- docs/setup/coming-from-wintercms.md: map Laravel collection methods
to sieve and narrow the not-provided row to the fluent collection
object.
Client-side canSubmit now matches the 422 rule so Submit stays disabled
with an empty scope set (WR-02).
Co-authored-by: Cursor <cursoragent@cursor.com>
Account token POST/DELETE routes made TestPhase10Coverage expect 32 unsafe
routes; token_scopes.go is hand-authored and excluded from the scaffold pin.
Co-authored-by: Cursor <cursoragent@cursor.com>
Account token create/revoke added two state-changing mounts; update the
Phase 10 CSRF walk count so the release gate inventory stays green.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add compiled ResolveBackend example with acme.blog cover
- Include src= fence in authentication docs (Bearer-only)
Co-authored-by: Cursor <cursoragent@cursor.com>