125 Commits

Author SHA1 Message Date
6312929148 fix(17-06): singleton route retries a lost first-or-create race
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.
2026-10-12 19:31:01 +02:00
872fd6535f feat(17-02): tree reorder page and singleton form in the admin
- 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
2026-10-12 16:18:31 +02:00
d3bf5d8f86 feat(17-02): repeater field and dependent dropdown states
- 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
2026-10-12 16:08:23 +02:00
cb2987c146 feat(17-02): richeditor field on TipTap with the untouched rule
- 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
2026-10-12 15:56:59 +02:00
23f33c3911 feat(17-01): tree reorder page and singleton admin controllers
- 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
2026-10-12 15:35:43 +02:00
46835fd064 feat(17-01): repeater field type and the dependent dropdown save recheck
- cabana: type repeater with nested form.fields (text, textarea, number,
  checkbox, switch, dropdown, richeditor), prompt, minItems, maxItems,
  titleFrom; groups, style, nested repeaters and other types fail boot
- rows decoded before projection: only nested keys, coerced scalars,
  per-row required (field.index.nested), row bounds; ProjectWritableFields
  exempts repeater lists only, nestedValue unchanged
- nested richeditor values sanitized with notices under the row path
- dependent dropdowns rechecked on save, top level and per row; null when
  the sibling takes no choice, 422 form.dependent_invalid otherwise
- dependent-options accepts repeater.field; schema carries RepeaterForm
- backend lang repeater.max_items, repeater.min_items,
  form.dependent_invalid; TestCompanionsRepeaterSmoke; docs
2026-10-12 15:24:01 +02:00
71fa597830 feat(17-01): richeditor field type on the seaglass sanitizer
- 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
2026-10-12 15:15:38 +02:00
0cdff4c258 feat(17-01): menu item type registry and dependent dropdown binding
- pact: HasMenuItemTypes, MenuItemType, MenuItemReference, MenuItemResolve,
  ResolvedMenuItem, MenuItemAlternate, DependentOptionsProvider,
  DependentOptionSet; Option gains Depth (omitempty)
- signpost (new module): boot-time registry with fail-loud key and pattern
  checks, layered URL patterns incl. PatternFrom for list types, host
  overrides at menu_items.patterns, Resolve and ReferenceOptions
- party: signpost.Activate after postcard, before Boot
- cabana: dependsOn on dropdowns (boot checks, controller-only), GET
  fields/{field}/dependent-options with swag, inventories, conformance case
- companions fixture, TestSignpostSmoke, TestCompanionsDependentSmoke, docs
2026-10-12 15:09:14 +02:00
01d2548ab3 fix(14.2): WR-04 validate storage.uploads.public_path_prefix at boot
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.
2026-10-12 11:21:38 +02:00
41a03b9987 fix(14.2): WR-03 uploads route sandboxes files and downloads non-images
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.
2026-10-12 11:20:43 +02:00
c30145ed25 fix(14.2): WR-02 499 only for a 5xx caused by the client going away
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.
2026-10-12 11:19:12 +02:00
fbcfaaf05f fix(14.2): WR-01 access log never records the URL path
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.
2026-10-12 11:17:51 +02:00
2c5c0583f2 test(14.2-04): uploads mount and access log unit tests; the uploads route owns its prefix
- 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
2026-10-11 23:23:38 +02:00
4660f95e5c fix(14.2-03): access log records a cancelled request's 5xx as 499
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.
2026-10-11 20:27:56 +02:00
2d68eab3c0 feat(14.2-01): opt-in http.access_log request log in surf
- 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
2026-10-11 17:42:51 +02:00
0b6d757b40 feat(14.2-01): opt-in storage.uploads.serve route for public uploads
- 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
2026-10-11 17:36:54 +02:00
a0bf8ece05 feat(sieve): add the remaining collection helpers and docs mentions
- 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.
2026-10-11 14:27:13 +02:00
a955d2a3d4 fix(16.3): refresh CSRF coverage golden and blog scaffold layout
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>
2026-10-11 13:53:31 +02:00
60873a3fea docs(16.3-03): ExampleResolveBackend for plugin token auth
- Add compiled ResolveBackend example with acme.blog cover
- Include src= fence in authentication docs (Bearer-only)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-11 02:17:43 +02:00
e17207a00d feat(16.3-02): dialog rules smoke tests and My account API tokens docs
Cover scope *, 422 focus, clipboard copy/fallback, close-without-copy
guard, and document the SPA tokens card on My account.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-11 02:06:43 +02:00
e24e03c47d feat(16.3-01): admin:token CLI, party.Activated, READMEs and docs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-11 01:55:07 +02:00
f388d27604 fix(16.2): WR-06 hand a translatable Decode only strings
liftMLValues validated only object-shaped submissions, so a scalar
submitted for a translatable plugin field reached Decode as nil, a
float64 or a bool although pact promises a string; a type that asserts
a string panicked into an opaque 500. decodePluginFields now passes a
string through, turns null into the empty string and refuses any other
value with a 422 on the field before Decode runs.
2026-10-10 16:45:05 +02:00
c532529d18 fix(16.2): WR-02 check the whole upload body before Upload stores it
The route called the type's Upload on the streamed part and only then
checked that no second part followed, so a refused request left a
public, unreferenced object in the bucket. It now reads the multipart
body whole (ReadForm, spooling past 8 MiB to a temporary file), checks
it holds exactly one file part named file, and only then calls Upload.
The remaining window (a failed record transaction after Upload, an
element that stopped waiting) is documented.
2026-10-10 16:43:59 +02:00
3158bb5b57 fix(16.2): WR-01 check the declared save before a field upload
The field upload route ran only protect, so the create form of a
controller without the create toolbar button could still write files to
the public bucket. It now refuses with 403 where the save route would:
create for {id} 0, update otherwise (operationDeclared).
2026-10-10 16:42:28 +02:00
4adb48a76e feat(16.2-01): translatable plugin fields, element uploads and save notices in the admin
- MLPluginField wraps PluginField in the MLField locale switcher; the
  element gets the active content locale and that locale's string
- isMLField treats a translatable plugin field as a locale map in the form
  state, FormView adopt and the relation child modal
- summer-upload posts the file with the admin session to the field upload
  route and answers summer-upload-result {id, url} or {id, error}
- noticeLines shows meta.notices under each field as a warning status line
  (text only) and in the saved toast; the stored value is reloaded
- docs/backend/field-types.md documents the full element contract;
  modules/boardwalk/dist rebuilt
2026-10-10 11:25:26 +02:00
8fa92db96d feat(16.2-01): translatable plugin field types, untouched skip, writes, list values and uploads
- translatable types lift, store and hydrate through the ML path
  (isMLField); Decode and Load run per locale and notices carry the locale
- on update a value equal to the served value (Load of the stored host
  value or exact translation) is neither decoded nor written
- FieldWrite, FieldWritesFromContext and WithFieldWrites expose what a save
  writes to the Form hooks
- list columns may use a plugin alias and show the type's ListValue
- POST .../{id}/fields/{field}/upload hands the file to the type's Upload
  (guard, X-Requested-With, permissions, field context, record scope, body
  cap); OpenAPI, route inventories, README and docs updated
2026-10-10 11:19:24 +02:00
717d46ec3d feat(16.2-01): compile, decode and load plugin field types in cabana
- compileFieldTypes builds a per-boot field type registry (alias, core and
  cross-plugin collisions, element prefix, assets, option keys) passed down
  the compile functions; settings and relation forms refuse plugin aliases
- fields.yaml accepts a plugin alias with the general keys plus the type's
  options (typeOptions, element, translatable in the schema); CheckOptions
  runs at boot; CompileFormWithTypes for plugin tests
- save decodes plugin values inside the transaction (FormFieldError is a
  422, other errors opaque 500) and answers meta.notices (FieldNotice) only
  when a type reported something; show and save serve values through Load
- GET /field-types lists the types with versioned asset URLs; OpenAPI,
  schema.d.ts, README and docs/backend/field-types.md updated
2026-10-10 11:06:12 +02:00
cd25af0878 fix(admin): scope remembered list state to the signed-in admin and recover from bad restored queries
- Store list queries under summer-admin.list:<admin id>:<controller>; me()
  and setCurrentUser set the owner, which removes other admins' keys and
  the unscoped keys of older builds; nothing is read or written without one
- clearUser removes every stored query, so logout, session expiry and an
  unrecoverable 401 (expireSession, now used by main.ts) all clear them
- A list request built from a restored query that fails for any reason
  (422, 500, network) forgets the query and loads the plain list once
- A failing URL query keeps the error and offers a reset button
  (backend::lang.list.reset_list, en and pl)
- Document remembered list state in docs/backend/lists-and-filters.md
- Rebuild modules/boardwalk/dist
2026-10-09 23:53:19 +02:00
bd8626e5fc fix(cabana): send the default content locale first in enabledLocales
- 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
2026-10-09 20:58:12 +02:00
06d75cb654 fix(cabana): search and sort to-many relation columns through subqueries
- 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
2026-10-09 20:56:53 +02:00
64d21c8caf feat(cabana): add backend user avatars to My account and the top bar
- 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
2026-10-09 17:45:43 +02:00
143eca5a1f feat(cabana): add the My account page with subtitle and password change
- 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
2026-10-09 17:38:04 +02:00
Jakub Zych
9b3803c098 feat(sunrise): update an application in place with summercms.io/update
- 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
2026-10-08 20:15:50 +02:00
Jakub Zych
f70d80ec7e feat(build): make summer new apps admin-ready
- 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
2026-10-08 17:20:19 +02:00
Jakub Zych
da21670870 feat(surf): print the serve URL and admin status on startup
- Add surf.Router.AdminPrefix (additive): the admin mount path and true
  when BuildRouter mounted the cabana admin, false otherwise
- serve binds with net.Listen before printing, so a busy port fails
  without a misleading URL, then prints the listen address with a
  browser URL and an admin status line: the admin URL, a warning naming
  summer admin:create when backend_users is empty or missing, or
  admin: off with the steps to add an admin controller
- The administrator count is bounded by a 2 s timeout and never fails boot
- route:list ends with the same admin status line
- surf and cabana READMEs and the console and routing docs describe the output
2026-10-08 16:36:21 +02:00
Jakub Zych
50c8cee541 fix(summer): forward SIGINT and SIGTERM to the app binary
- runAppBinary registers SIGINT and SIGTERM after its build and recursion
  checks and runChild forwards each signal to the app binary, then waits
  for it to exit, so stopping summer serve frees the port (WR-08)
- newAppCmd keeps the inherited stdin, env and output, and a context
  cancel sends SIGTERM with a 15-second appShutdownGrace before SIGKILL
- the child stays in summer's process group so the admin password
  prompts can read the terminal
- helper-process tests cover SIGINT, SIGTERM and context cancel
- console introduction notes the signal forwarding
2026-10-08 16:12:01 +02:00
Jakub Zych
b9ef034272 fix(summer): delegate admin, route and key commands and pass all delegate flags through
- delegateCommands() builds every delegate from one table of name,
  description and common-flags hint; toolCommands appends it
- route:list, key:generate, admin:create and admin:reset-password are new
  delegates; the child keeps summer's stdin, so the password prompts work
- migrate:rollback, queue:work and schedule:run lose their hand-written
  flag copies: --plugin, --queue and --once now pass through verbatim
- the command list marks delegates with "(flags pass to the app binary)"
  and the help command prints a Long text with the common flags
- console introduction, setup-and-maintenance and the root README
  describe the passthrough behaviour and the new delegates
2026-10-08 16:03:24 +02:00
Jakub Zych
14f6982c4d feat(bonfire): add Passthrough and Long command fields
- Command.Passthrough turns off cobra flag parsing and uses arbitrary
  args, so every argument after the command name, including --help, -h
  and --, reaches Run through Input.Args unchanged
- Command.Long sets cobra's Long text for the help command; without it
  the description is shown as before
- both fields are additive: commands without Passthrough parse flags and
  reject unknown ones exactly as before
- README and docs/console/writing-commands.md describe both fields
2026-10-08 15:49:39 +02:00
Jakub Zych
ddf1178c51 feat(sunrise): print the PATH hint for the user's login shell
- new.sh reads the basename of SHELL (via ${SHELL:-} under set -u) and
  prints fish_add_path "<dir>" for fish, which keeps it for new sessions,
  export PATH="<dir>:$PATH" naming ~/.zshrc or ~/.bashrc for zsh and
  bash, and today's generic export line with "your shell profile" for
  any other, empty or unset shell
- dq_escape escapes the install directory for the target shell's double
  quotes: backslash, double quote and dollar always, plus backtick and a
  single-quoted ! for POSIX shells; a per-character loop, so bash 3.2
  and 5.2 behave the same
- the behaviour harness defaults SHELL to empty so cases do not depend on
  the developer's login shell, adds an unset-SHELL mode and covers fish,
  zsh, bash, empty, unset, unknown and two metacharacter directories
- TestBootstrapEndToEnd pins SHELL=/bin/bash
- sunrise README and docs/setup/installation.md describe the per-shell
  hint; the manual PATH section gains fish_add_path (go env GOPATH)/bin
2026-10-08 15:34:42 +02:00
Jakub Zych
e0640220f2 docs(14.3-03): document summer new and the one-command start
- installation: one-liner first, what the script does and its overrides,
  running the generated app, summer new without the script, then the
  examples/hello walkthrough; the obsolete known-issues callout is gone (D-18)
- go modules and workspaces: what summer new writes (relative replace,
  framework detection order, committed relative go.work, no root go.sum);
  plugin:add adds a relative go.work path
- console scaffolding: Creating an application with --framework, --no-plugin,
  --no-git, the files written, first commit and naming convention (D-01)
- console introduction, llms note in site.yaml, root README quick start
2026-10-08 12:08:35 +02:00
Jakub Zych
90582522c7 docs(14.3-03): open the installation guide with the one-command start
- curl -fsSL https://summercms.io/new | bash -s -- demo and the follow-up
  compose, build, migrate and serve commands come first (D-18)
2026-10-08 12:02:10 +02:00
Jakub Zych
cc7386d22e fix(14.3-01): ship http body limits in examples/hello
- add examples/hello/config/http.yaml so serve, route:list and
  TestTypedItemRoute work without a manual config step
- drop the matching setup step and known-issue notes from README.md and
  docs/setup/installation.md
2026-10-08 11:40:20 +02:00
Jakub Zych
fa25b4e000 fix(cabana): show to-many relation list columns joined with commas 2026-10-07 18:43:17 +02:00
Jakub Zych
0b09acdb0c feat(admin): open controller-link settings entries from the settings index
A settings entry with a controller opens that controller's route from
its card (settingsPath); singleton cards still open /settings/<code>.
On a linked controller with no navigation entry of its own the rail
marks Settings current, the breadcrumbs read Settings > entry label
(plus the record title on record and create routes) and the page title
uses the entry label. The settings docs describe the admin behaviour;
the embedded admin shell is rebuilt.
2026-10-07 18:24:54 +02:00
Jakub Zych
4a9b0896b1 feat(cabana): settings entries that link to an admin controller
pact.SettingsItem gains an additive Controller field, the equivalent of a
WinterCMS registerSettings 'url' => Backend::url(...) entry. A link entry
declares no Model, Form or NewModel and needs no AdminFS; start-up fails
when it combines Controller with a singleton form or a model, or names an
unregistered controller. Settings codes stay one namespace.

GET /settings lists a link entry with its controller only when the
principal passes the item's permissions and may open the controller.
Registry.Setting never returns a link entry, so the singleton settings
endpoints answer 404 for its code. SettingsEntry carries controller,
empty for singletons; the OpenAPI document, generated SPA types and
settings fixture follow, and the pact and cabana READMEs and the
settings docs describe the link.
2026-10-07 18:22:01 +02:00
Jakub Zych
aa3a7c2e90 docs(cabana): document mltextarea field type
- forms.md: field-types row, ML section and example, lift paragraph,
  preset refusal
- admin-controllers.md and the cabana README name mltextarea
2026-10-06 21:37:08 +02:00
Jakub Zych
5932172148 docs(cabana): document preset on mltext fields
- schema_types.go and the form-schema swag description name text or mltext
- regenerated admin/openapi/admin.json and admin/src/api/schema.d.ts
- README and docs/backend/forms.md describe the per-locale rules
2026-10-06 21:20:25 +02:00
Jakub Zych
a0116dfbb9 fix(admin): render markdown preview from server-sanitized HTML
- MarkdownField posts the source to POST /markdown/preview when Preview
  opens and again 300 ms after a change while open; stale answers dropped
- the pane binds only data.html of a 2xx answer; a refusal is a text notice
- mlmarkdown previews the active locale and follows a locale switch
- .summer-markdown style kit restores headings, lists, code and tables
- vitest coverage, forms.md and rebuilt modules/boardwalk/dist
2026-10-06 20:57:51 +02:00
Jakub Zych
b492e79f2b feat(cabana): add markdown preview admin route
- POST {prefix}/api/v1/markdown/preview renders {markdown} through
  cabana.RenderMarkdown in the backend-guarded group behind requireAjax
- refused output is a 422 validation_failed on markdown with a fixed message
- swag annotation, regenerated admin.json and schema.d.ts
- route inventories, CSRF walk (26) and OpenAPI conformance learn the route
- README and docs/backend/forms.md document the route
2026-10-06 20:53:07 +02:00
Jakub Zych
2f03128a58 fix(admin): drop ML copy-from locale chrome (quick-261006-s0v)
Copy overwrote the active locale with the other locale's often-empty string, so the editor went blank. Keep a single synchronized locale picker.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 20:12:47 +02:00