86 Commits

Author SHA1 Message Date
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
Jakub Zych
4e37d877f1 feat(14.2.1-06): hydrate relation-child update and show ML maps
UpdateChild and ShowChild now lift, apply, and hydrate nested locales the same way as host CRUD, so a child form keeps English on the column and Polish through the writer.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 15:53:24 +02:00
Jakub Zych
424fe9ccc0 feat(14.2.1-05): cover mlmarkdown, child adopt, and regen admin artifacts
MLMarkdownField and relation-child forms share the enabled-locale
inject and adopt merge; OpenAPI, types, docs, and boardwalk dist match.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 15:41:46 +02:00
Jakub Zych
e3b3475b1c feat(14.2.1-03): document ML fields and regenerate admin artifacts
- Document markdown, mltext, and mlmarkdown plus TranslationWriter save semantics
- Extend the SPA registry tests and rebuild committed boardwalk dist

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 13:29:00 +02:00
Jakub Zych
b2ce986604 feat(14.2.1-01): add optional surf LocaleResolver seam
Look up a backpack-published resolver so a compiled translate plugin can
strip an enabled URL prefix and write a validated locale onto context.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 11:54:00 +02:00
Jakub Zych
e723c391fb docs(cabana): widget element contract and payload fixture (quick-261006-eyj)
- acme-demo-lookup fixture is a reorder widget: a click reverses its items,
  sends the new id order as detail.payload and renders the returned items from
  the data attribute and the summer-result event with textContent, no HTTP
- partials-and-widgets gains a Widget element contract subsection listing the
  attributes the SPA sets and the summer-action / summer-result events
- cabana README names both events in the form widgets bullet
2026-10-06 11:18:03 +02:00
Jakub Zych
6af88f9df6 feat(cabana): widget action payload and data channel (quick-261006-eyj)
- pact.AdminActionInput.Payload (json.RawMessage) carries the widget's own
  JSON value untouched; pact.AdminActionResult.Data is passed through as data
- cabana decodes payload with a 64 KiB cap (422 on body), refuses it on the
  toolbar and record routes, and embeds Data once encoded with a 256 KiB cap
  (opaque 500 when larger or unencodable); fill stays filtered
- root .swaggo overrides json.RawMessage so swag keeps record_id and values;
  admin.json and schema.d.ts regenerated (payload?: unknown, data?: unknown)
- TestWidgetPayloadAndData covers pass-through, cap, refusal and data 500
- cabana and pact READMEs, partials-and-widgets and admin-spa docs updated
2026-10-06 11:13:16 +02:00
Jakub Zych
83feeef9fa docs(12.1-05): state the limits of relation locks and locked permission codes
- a relation lock covers the form field only; a relation manager on the same relation does not ask the provider
- a locked permission code must be stored with a value its mode can send
2026-10-05 16:01:10 +02:00
Jakub Zych
df5cace852 feat(12.1-02): writable foreign keys, locked relation options, invisible columns
- FieldRelationContract.WritableForeignKey makes a belongsTo field over a
  protected foreign key writable; the protected key list is unchanged
- cabana.RelationLockProvider names related ids an administrator may not add
  or remove: options and labels carry locked, and a create or update that
  changes the locked subset is 403 before any row is written
- columns.yaml invisible keeps a column searchable and out of the rows
- a controller implementing pact.FilterOptions serves a scope filter's
  choices before the model
- SPA: locked chips and options in RelationField, DataTable skips invisible
  columns
- README, docs, OpenAPI document, TS types and dist updated
2026-10-05 10:58:38 +02:00
Jakub Zych
f50d9b8f10 feat(12.1-02): permissioneditor field in radio or checkbox mode
- type: permissioneditor with mode radio (1, -1) or checkbox (1); the
  controller serves the options per request through
  cabana.PermissionEditorProvider and reads and stores the values
- a save answers 422 for a non-object, an unknown code or a value outside the
  mode's set and 403 for a changed locked code; stored codes that are not
  offered are kept
- record responses carry the stored permissions as an object
- SPA: PermissionEditorField with sections by tab, locked rows and a read-only
  mode for the preview
- README, docs, OpenAPI document, TS types and dist updated
2026-10-05 10:44:50 +02:00
Jakub Zych
a1c6bb1ce6 feat(12.1-02): password and form-only fields, rules per operation and preset
- pact.FormVirtualFields lists form fields that are not model columns: never
  bound, filled or projected; their values reach the Form hooks through
  cabana.VirtualFieldsFromContext when the field's context allows the operation
- type: password is a masked field that must be listed as virtual
- pact.FormRules supplies the rule set per operation and replaces the model's
  Rules() for admin saves; a rule on a virtual field sees the submitted value
- preset on a text field follows another text field on the create form
- SPA: PasswordField, preset handling in FormView, empty password left out of
  an update
- README, docs, OpenAPI document, TS types and dist updated
2026-10-05 10:35:08 +02:00
Jakub Zych
a65c670574 feat(12.1-02): read-only preview screen with a status hint and record actions
- config_form.yaml preview block (optional headerPartial), reported in the form schema as preview
- fields with context: preview show only on the preview screen and are never written
- form messages preview and edit; recordActions without a preview block stops boot
- SPA route {id}/preview, PreviewView and PreviewField, record actions in the footer
- mapWinterUrl maps preview/:id; the update form returns to the preview
- summer-callout partial style classes for status hints
- README, docs, OpenAPI document, TS types and the embedded build updated
2026-10-05 00:10:48 +02:00
Jakub Zych
71073bc8a2 feat(12.1-01): cabana.ForbiddenError answers a refused write with 403
- hooks and bulk, record, toolbar and widget actions may return it
- 403 forbidden with the localized message and field details; the write's
  transaction is rolled back; other errors stay the opaque 500
- form shows a refused save as a persistent banner and keeps the values;
  a refused delete is a toast
- smoke tests, OpenAPI notes, dist, README, docs
2026-10-04 23:53:34 +02:00
Jakub Zych
61d5fc72ad feat(12.1-01): list row states from one controller call per page
- pact.ListRowStates with the fixed RowState set deleted, negative, disabled
- list response meta.row_states keyed by row id; unknown values dropped
- list messages rowStateDeleted, rowStateNegative, rowStateDisabled
- update writes through the scope the load used, so a soft-deleted record
  a controller includes stays soft-deleted
- DataTable row state badges and text styles
- roster fixture, smoke tests, OpenAPI, TS types, dist, READMEs, docs
2026-10-04 23:45:44 +02:00
Jakub Zych
e0ccced76a feat(12.1-01): declared record actions with an applicability rule
- pact.HasAdminRecordActions with AdminRecordAction (Applies, Run)
- config_form.yaml recordActions, compiled fail-loud
- show response meta.actions lists the permitted actions that apply
- POST .../{controller}/{id}/actions/{action}: record loaded and locked
  through the form scope; 404 out of scope, 409 when it does not apply
- RecordActions.vue with confirm and request flow (mounted by plan 02)
- roster fixture, smoke tests, OpenAPI, TS types, READMEs, docs
2026-10-04 23:37:30 +02:00
Jakub Zych
a879d6388c feat(12.1-01): declared bulk actions on admin lists
- pact.HasAdminBulkActions with AdminBulkAction, its input and result
- config_list.yaml bulkActions, compiled fail-loud, needs showCheckboxes
- POST .../{controller}/bulk/{action}: ids resolved and locked through the
  list scope in one transaction; partial selection is 409
- list schema offers declared actions per principal, with confirm text
- admin SPA bulk actions menu with confirm, busy state and failure toasts
- acme.roster fixture, tracer test, OpenAPI, TS types, dist, READMEs, docs
2026-10-04 23:28:30 +02:00
Jakub Zych
b5d20b3bfd fix(14-05): surf answers OPTIONS on CORS paths with Laravel's HandleCors headers
- every OPTIONS on a CORS path: 204 with Cache-Control no-cache, private
- a preflight echoes the requested method (upper-cased) and headers when * allows any, with Vary and PHP's default Content-Type, as recorded from PHP
- README and the routing docs describe the answer
2026-10-04 00:00:01 +02:00
Jakub Zych
7c2c43359f feat(14-04): fetchguard.IsPrivateAddr exposes the dial guard's address classification
The golem SSRF guard checks a URL's resolved addresses before it connects,
as PHP's SSRFGuard does, with the same table the dial guard uses.
2026-10-03 22:49:19 +02:00
Jakub Zych
5101ecb49c feat(14-03): tide keeps binary upstream bodies and compares every *_url upload by shape
- A response body that is not valid UTF-8 (a cover image) is written as a
  YAML !!binary scalar, never masked and replayed byte for byte
- The upload URL normalizer covers every key ending in _url (cover_url),
  not only url and thumb_url
- README and parity-testing docs updated
2026-10-03 21:34:52 +02:00
Jakub Zych
cdd8d710fa feat(14-02): conga.Describe reports a registered job's kind, queue, attempts and timeout
- lets a plugin test assert what its Jobs() registers, such as the CSV
  match job's 240 s timeout, without reaching into conga internals
- README API table, jobs docs page and an example
2026-10-03 20:56:49 +02:00
Jakub Zych
0a7635b12a fix(14-02): parity:upstream writes its sidecar on interrupt or SIGTERM
- the command context had no signal handling, so stopping the proxy
  killed it before Flush and no sidecar was ever written
- a background proxy (SIGINT ignored by the shell) now stops on SIGTERM
2026-10-03 20:06:42 +02:00
Jakub Zych
58e6324da8 feat(14-01): beachcomber DropIndex and EnsureIndex for reindex tooling
- optional IndexDropper reports whether a dropped index existed; DropIndex falls back to Flush
- optional IndexEnsurer creates an empty index from its schema; EnsureIndex is a no-op otherwise
- typesense implements both (404 is already absent; ensure reuses collection creation)
2026-10-03 20:01:36 +02:00
Jakub Zych
7241704e93 feat(14-01): sunscreen redacting slog handler installed by every generated main
- Wrap redacts sensitive keys at any depth and scrubs Bearer, sk- and x-api-key shapes
- InstallDefault is the first statement of the generated run; hello main regenerated
- surf test pins that recovered panics echo no credential
- sunscreen README, root modules row and the logging docs page
2026-10-03 20:01:36 +02:00
Jakub Zych
ee0004fb65 feat(14-01): record vendor calls with summer parity:upstream and replay them offline
- WriteUpstream masks vars, hashes long base64 JSON strings and refuses unmasked Authorization/X-Api-Key
- multipart requests recorded as ordered parts; the fake compares parts and hashed payloads
- loopback CONNECT recording proxy with a local ECDSA parity CA, script and forward modes
- parity:upstream command, README and parity docs
2026-10-03 19:55:42 +02:00
Jakub Zych
e6a67134d1 feat(14-01): fetchguard client covers PUT, multipart, bearer and a trusted mode
- TrustedMode (declared after PublicOnlyMode) lifts the scheme, host and dial checks for Client only
- PutJSON, PostMultipart with FormField/FormFile, Bearer
- tests for modes, redirects, multipart order, body cap and the scheme guard
- README, root modules row and outbound HTTP docs describe the client and its test seam
2026-10-03 19:42:37 +02:00
Jakub Zych
2b94dfd2d2 feat(13-01): add the prohibited rule and dated-download and notification masks
- lagoon.ValidateRequest supports Laravel 9 prohibited (!required, not
  implicit); with no catalog line its message is validation.prohibited
- tide compares Content-Disposition with real calendar dates masked on both
  sides; a different name, an invalid date or a one-sided date still diffs
- tide.NormalizePublications masks a Carbon +00:00 $.data.payload.created_at
  and an uncaptured positive integer $.data.payload.id as {{id}}
- the album-date test's outside-album sibling moves off payload.created_at,
  which now has its own mask
- READMEs and docs describe the rule and both masks
2026-10-03 06:32:46 +02:00
Jakub Zych
55a4092019 feat(13-01): queue jobs whose worker ships later while a worker runs
- a kind no plugin registered always inserts through the insert-only River
  client, so Dispatch and Enqueue no longer fail River's unknown-kind check
  while the in-process worker runs
- while a worker runs, such a kind must name a queue no worker serves;
  an empty queue, default, scheduled, a configured queue or a registered
  job's queue is ErrUnregisteredKindQueue and nothing is written
- README and docs/services/jobs.md describe jobs whose worker ships later
2026-10-03 06:28:40 +02:00
Jakub Zych
fbdeb20126 feat(13-01): register overlapping constrained routes in surf
- compile groups routes ServeMux refuses side by side into overlap families
  and registers each under one generated method-less pattern
- the family handler tries members in registration order on literals and
  Where constraints, sets their path values and runs their own wrapped chain
- no match answers the bare 404; a method mismatch answers ServeMux's 405
  and Allow for the same table without the overlap
- unsupported shapes (same shape, {name...}, shadowing route) fail at boot
- README and docs/services/routing.md describe the behaviour
2026-10-03 06:22:20 +02:00
Jakub Zych
55314e41f8 fix(admin): datetime popover clock, unsaved confirm, inferred list dates
BM UAT on v0.1.1 showed a date-only calendar for datetime fields, a stuck discard dialog, and raw ISO timestamps when columns.yaml omitted type. The picker now edits time in the popover, confirm sits above the calendar, and omitted time.Time / Date / TimeOfDay columns compile as datetime / date / time.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-03 05:58:24 +02:00
Jakub Zych
516f9c9025 fix(12.2): close code-review blockers on uploads, JSON caps, and pivot fill
Keep form save behind in-flight uploads, make retries idempotent via X-Upload-Id, cap remaining JSON bodies, and surface pending pivot type errors instead of zeroing them.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-02 23:10:48 +02:00
Jakub Zych
ea33296799 feat(12.2-04): add the fileupload field with deferred uploads on the form session key
- sessionKey.ts: one 32-byte base64url key per form mount, sent only in headers
- api/files.ts: FileRoutes over the record and child file routes, XHR upload with progress, 401 refresh and retry
- FileuploadField and FileCaptionModal per UI-SPEC section 3: dropzone, image grid, rows, per-item states, client pre-checks, reorder, protected previews
- FormView provides FORM_SESSION, counts pending changes as dirty and sends X-Session-Key on create and update
- fileupload lang keys in en and pl, admin-spa docs note, deferred smoke test, rebuilt dist
2026-10-02 19:22:22 +02:00
Jakub Zych
fe9e8baaf1 feat(12.2-03): defer relation work on unsaved records and add child file routes
- record id 0 with X-Session-Key manages deferrable relations: create, link, unlink, delete and pivot edits are held in deferred_bindings
- the record's create save applies relation bindings with the file bindings; an ineligible link is a 422 on the relation-manager field
- child forms upload files through .../records/{child}/files/{field} keyed by X-Child-Session-Key; the child save commits them
- boot refuses a deferrable relation with create whose related model no plugin lists in Models()
2026-10-02 19:08:16 +02:00
Jakub Zych
afb05b6ee4 feat(12.2-03): add parent-scoped child show, update, delete and pivot routes
- loadChild finds a child with one query carrying the parent predicate; a foreign child is 404
- GET/PUT .../records/{child} and POST .../delete (all or nothing) per relation kind
- hasMany link adopts NULL-key rows and unlink clears the key; pending created children are never candidates
- link accepts pivot values for one id through the pivot.form whitelist; GET/PUT .../pivot/{child}
- Link and Unlink share linkRelated/unlinkRelated for the deferred commit
2026-10-02 18:44:34 +02:00
Jakub Zych
48a5b8045a feat(12.2-03): add hasMany relation contracts, relation forms and child create
- RelationContract gains Kind (empty is belongsToMany) and ForeignKey, with kind-aware boot checks
- manage.form, view.form and pivot.form compile against the related or pivot model; $/ paths resolve inside the plugin
- view toolbarButtons accept create|update|delete|link|unlink, each the capability of its routes
- POST .../relations/{name}/records creates a child through the manage form; the server sets the hasMany key
- relation schema carries kind, deferrable and the localized forms; 17 new relation message keys in en and pl
2026-10-02 18:37:13 +02:00
Jakub Zych
67d4c7ff13 feat(12.2-02): add the datepicker field with server-side bounds and date list columns
- type: datepicker compiles the D-20 keys; format maps to displayFormat with WinterCMS's momentFormat table
- boot fails when the mode does not match the column's Go type (time.Time, lagoon.Date, lagoon.TimeOfDay)
- datepicker is a writable scalar field; minDate and maxDate are rechecked on save
- columns.yaml accepts type: date and type: time; Scanner/Valuer structs are columns, not relations
- conformance fixture carries date and datetime fields; README, forms and lists docs
2026-10-02 18:19:04 +02:00
Jakub Zych
e54fd257ee feat(12.2-02): add file removal, caption, reorder and protected downloads
- DELETE, PUT and POST reorder under .../{id}/files/{field}, each scoped by one parent query (404 for a foreign file)
- protected download and thumb routes: is_public=false only, nosniff, private no-store, sandbox CSP, inline only for jpeg/png/gif/webp
- the save applies deferred removals, replaces attachOne files and rechecks maxFiles and required
- blobs of deleted files are removed after commit
- swagger2openapi emits binary content for file responses
- admin OpenAPI, TS types, conformance, README and attachments docs
2026-10-02 18:11:56 +02:00
Jakub Zych
044e0450ef feat(12.2-02): add the fileupload field with deferred uploads committed on save
- type: fileupload compiles the D-08 keys and binds to the model's attach.Relation at boot
- X-Session-Key (cabana.SessionKeyHeader) carries the form session key; RecordInput.SessionKey
- GET and POST .../{id}/files/{field}: list with pending uploads, multipart upload into attach.Store
- the create and update save attaches the session's pending files in its transaction
- swagger2openapi folds formData parameters into a multipart requestBody
- admin OpenAPI, TS types, conformance cases, README and forms docs
2026-10-02 18:04:34 +02:00
Jakub Zych
8818d7b023 feat(12.2-01): add deferred:purge, its daily framework schedule and relation child hooks
- deferred:purge [--days] in lagoon.RuntimeCommands (purge_days, default 5)
- lagoon.FrameworkSchedule entry at purge_at (default 03:00, empty disables)
- conga prepends framework entries as summercms.lagoon[i]:<command>
- pact.Relation{Before,After}{Create,Update,Delete} optional hooks
- lagoon, conga and pact READMEs, scheduling and setup docs
2026-10-02 17:45:41 +02:00
Jakub Zych
f48a886f94 feat(12.2-01): add lagoon.Date and lagoon.TimeOfDay with Fill and required support
- Date (DATE) and TimeOfDay (TIME) with Scanner, Valuer, JSON and text forms
- Fill falls back to encoding.TextUnmarshaler for string sources after
  every existing conversion, so time.Time and the new types fill from JSON
- required treats a zero time.Time, Date or TimeOfDay as empty
- lagoon README, models and casts-and-validation docs
2026-10-02 17:40:48 +02:00
Jakub Zych
19f4cf8232 feat(12.2-01): add deferred bindings, guarded upload store and purge
- deferred_bindings migration set under summercms.deferred with backend_user_id
- lagoon.DeferredBind/Unbind/Bindings/Forget/Slaves scoped by DeferredKey
- lagoon.PurgeDeferred with SKIP LOCKED batches and after-commit blob deletes
- attach.Store with the ported image guard, extension and MIME limits
- attach.Relation, attach.HasRelations, attach.BlobKeys, File.ThumbKey
- lagoon README and attachments docs
2026-10-02 17:36:43 +02:00