- listState.ts stores each list's query (search, sort, dir, page, per_page,
filter[name]) under summer-admin.list:<controller>; an empty query is
never stored, malformed stored values are dropped like a hand-edited URL
- ListView restores the stored query into the URL before its first list
request when entered without list params; a URL with list params wins
- a 422 forgets the stored query; an out-of-range page clamps to last_page
- logout clears every stored list query
- check-phase10.sh allows browser storage in useSidebar.ts and listState.ts
- list smoke tests start from a clean list state
- rebuilt modules/boardwalk/dist
- enabledContentLocales moves TranslationWriter.DefaultLocale to index 0
and keeps the other enabled codes in the writer's order
- every ML control opens on enabledLocales[0], so the form now shows the
host-column values (content, excerpt, title, slug) instead of an empty
non-default locale
- a DefaultLocale error is logged and keeps the writer's order
- README and forms docs: default locale first
- relationSubquery reads the pivot, key and table names from GORM's parsed
relationship (Schema.Relationships.Relations), never from a convention
- list search matches every relation column through a correlated EXISTS,
so the count query gains no JOIN and belongsToMany search stops failing
- sorting a hasMany or belongsToMany column orders by a correlated
string_agg subquery before the primary-key tiebreak
- polymorphic relations and unknown related columns answer 422
- DryRun smoke test TestListRelationSearchSmoke needs no Docker
scripts/check-phase16.sh runs the layer unit tests, the layer playground
SSR smoke, the site app install/build/tests/smokes, the host boot tests,
the grzyby command tests with -race and check-deploy. Each stage fails on
not ok, fail/skip/todo/cancelled counts, missing named tests, go test
failures, skips, no tests to run, data races or a smoke without ok; it
ends with "Phase 16 gate passed".
- account_internal_test: metadata subtitle reader and writer (NULL, blank,
null, [], invalid JSON, non-objects, key preservation, NULL on the last
key), the attachOne contract, avatar cap, unique-violation mapping,
English fallbacks, and 401 for non-backend principals on all three routes
- account_test (Postgres): profile payload incl. copied WinterCMS metadata
and avatar rows, trimmed profile saves and metadata keys, the forbidden-key
allow-list with unchanged rows, email rules, password rules, the failed
attempt log, Bearer and cookie session effects, CSRF, avatar upload,
refusals (SVG, HTML as PNG, size, part shape, 413, no bucket) with no row
or blob left, replace and remove with blob cleanup
- lagoon: TestBackendAdminMetadataColumn covers type, Winter row round trip,
rollback of only the column and re-migrate
- SPA: AccountView, the user menu account item and avatar image,
setCurrentUser and profileInitials; lang fixture gains myaccount keys
- POST /account/avatar with no multipart part (NextPart io.EOF) or a broken
boundary fell through to the generic 500; it is now the invalid-body 422
- a body over the cap still answers 413 payload_too_large
- cabana.BackendUser is an attach.Owner (MorphName Backend\Models\User,
cabana.BackendUserMorphType) with one public attachOne avatar
(cabana.BackendUserAvatarField), as WinterCMS stores it
- POST /account/avatar stores one file_data image (jpg, jpeg, png, gif or
webp, image guard, at most cabana.AvatarMaxBytes = 2 MiB, under a
server-generated name) and replaces the previous avatar: old rows are
deleted in the transaction, their blobs after the commit
- DELETE /account/avatar removes the avatar and is idempotent
- GET /auth/me and the account routes return avatar {url, thumb_url}
(250x250 crop); cabana.BackendUserAvatar lets plugins read it
- swag annotations, regenerated admin.json and schema.d.ts; route
inventories, CSRF walk (29) and OpenAPI conformance learn both routes
- SPA: avatar upload and remove on My account, avatar image in the user
menu trigger and header; backend::lang.myaccount avatar strings
- rebuilt boardwalk dist; cabana README, users-and-permissions and
admin-spa docs
- lagoon: 202610090001_backend_users_metadata adds WinterCMS's nullable
jsonable backend_users.metadata column (TEXT); rollback drops only it
- cabana.BackendUser.Metadata and Subtitle read metadata.subtitle leniently
(NULL, blank, null, [], invalid JSON and non-strings read as empty)
- GET /auth/me returns subtitle; PUT /account edits the signed-in admin's
own names, email and subtitle, and the password with current_password;
any other key is a 422 and nothing is written
- email: required, valid, 6-255 chars, unique against other admins' email
and login ignoring case (soft-deleted included); a unique-index race is 422
- a password change sets tokens_valid_after, revokes the presented token and
gives a cookie session a fresh cookie
- swag annotation, regenerated admin.json and schema.d.ts; route inventories,
CSRF walk (27) and OpenAPI conformance learn the route
- SPA: My account item in the user menu opens /account (AccountView), which
saves through PUT /account and updates the top bar via setCurrentUser
- backend::lang.myaccount strings (en, pl), rebuilt boardwalk dist
- cabana and lagoon READMEs, users-and-permissions and admin-spa docs
- new.sh gains --update, run inside one application directory (go.mod and
summer.yaml required); it changes only that application
- the framework checkout is read from the app's go.mod replace (single-line
or block form); module-version replaces, missing targets and foreign or
dirty checkouts are refused through the shared check_framework_dir
- a detached checkout moves fast-forward only to the newest release (or
SUMMERCMS_REF) through the shared move_detached; a branch checkout is
pulled with git pull --no-rebase --ff-only, refusing a missing upstream
or a diverged branch with the checkout unchanged
- other sm-*-app directories on the same checkout are named on one
"not rebuilt" line and never built or written
- summer is reinstalled, a missing SUMMER_ADMIN__JWT__SECRET is appended to
this app's .env (openssl, else head and base64; mode kept, never printed),
then summer build runs and the script ends with the migrate and restart
reminder and the shared path_hint
- sunrise.UpdateScript() serves the same script with its last line changed
to main "$@" --update, so truncated downloads still run nothing
- check-phase14.3.sh --forbidden now allows only the literal
SUMMER_ADMIN__JWT__SECRET in new.sh and still fails on any other
SUMMER_ name or on a read or export of one; TestUpdateScript and
TestUpdateScriptRoute are required tests
- NewOptions.AdminSecret: summer new writes SUMMER_ADMIN__JWT__SECRET
to .env (still 0600), generated from 32 crypto/rand bytes when empty
and validated as at least 32 .env-safe characters
- .env.example gets an empty SUMMER_ADMIN__JWT__SECRET= placeholder
with a comment; the secret never reaches the printed next steps
- The next steps and the generated CLAUDE.md explain the admin at
/backend: make:admin-controller, build, migrate and
summer admin:create --email you@example.com --superuser
- Goldens use a fixed, non-secret admin secret fixture
- Scaffolding, installation and users-and-permissions docs updated