# Cicadel dev: complete application documentation Build applications using Cicadel. Start with the quick start or AI-agent guide; fetch focused Markdown guides as needed. Source: development checkout. --- Source: `CHANGELOG.md` Markdown: https://cicadel.fauzaanu.com/en/dev/releases/changelog/index.md # Changelog ## Unreleased — initial experimental framework - Made accounts and formsets standard project defaults; added offline `makemigrations` and `generatequeries` management commands from the shared model registry. - Consolidated migration application, planning, verification, adoption and recovery on the recorded-history runner. Removed the separate `database.Migrate`/`Migrator` executor; existing Goose histories require verified adoption. - Preserved first-party handwritten SQL with reviewed historical manifests and verified legacy serial/unique/timestamp-default support. Updated application guides and installable contracts to describe core defaults. - Breaking: renamed Yutqin to Cicadel, including Go modules and packages, CLI executables, environment variables, generated artifacts, cache paths, and framework-owned database identifiers. - Added typed installable runtime model catalogs with explicit app/table/type ownership, offline validation and isolated discovery. Recorded migrations remain authoritative. - Completed application foundation tooling: all operational settings overrides and redacted diagnostics, sorted command help and exit statuses, structured/deployment checks, and safe component/CSS rebuild/restart through `cicadel dev`. Added generated-executable and reload acceptance tests; see `docs/foundation.md`. - Added a green-and-black Tailwind default theme and optional `startproject --accounts` browser starter with compiled login/recovery/reset/account/password-change pages, CSRF protection, PostgreSQL cookie sessions and shared account revocation. - Added SMTP with verified STARTTLS/implicit TLS, batch connection reuse, MIME alternatives and attachments, CC/BCC, reply-to and console/file/capture/discard backends. - Added named logging configuration with levels, filters, propagation, JSON/text/custom handlers, file rotation, optional error email, grouped credential redaction, correlated panic records and private GORM timing. - Reconciled current authentication, component, PostgreSQL migration, mail and logging documentation; administration remains a separate later battery. - Completed standalone typed forms and accessible widgets; added revision-based stale-edit detection, bounded upload fields with compensating cleanup and multipart CSRF integration, and atomic model-form/formset administration. See `docs/forms-completion.md`. - Added core `contrib/formsets`: bounded related-row collections with scoped identities, add/delete limits, parent locking, transactional hooks and atomic saves; added the core `Form.InTransaction` composition API. - Added `forms/model`: explicit GORM field allowlists, typed binding, scoped relationship checks, authorized row-locked updates, safe constraint errors and transactional validation/save hooks. Verified with PostgreSQL concurrency and rollback tests; see `docs/model-forms.md`. - Added the installable-app foundation: manifests, API/dependency compatibility checks, namespaced commands/checks/resources, route-scoped middleware and per-host startup/cleanup. - Added `startpackage`, generated `project/apps.go`, `manage.go apps`, and `apptest.Check`; tested independent Go-module install/upgrade/removal and PostgreSQL migration/data retention. - Implemented React/Cotton-inspired compiled component UI; retained legacy Go templates for compatibility and completed the component-engine acceptance checks. - Added a source-linked Django battery inventory with explicit support decisions and completion criteria. - Reclassified existing integrations as partial batteries; model-driven PostgreSQL migrations now have a documented supported subset, other database backends are excluded, and broader package-system contracts remain planned. - Removed the experimental native `locale` package; i18n/l10n and GIS are third-party-only by design. - Django-familiar project/app scaffolding, typed settings, explicit app registration and management commands. - Standard HTTP routing with namespaced reversal, escaped embedded templates, response helpers and static assets. - PostgreSQL/GORM integration and locked, versioned Goose migrations. - Sessions, password primitives, CSRF protection, bounded forms and explicit authorization. - Working articles and catalog/admin examples with real HTTP/database tests. - API helpers, mail transport/capture, bounded storage/cache, observability hooks and River jobs. - PostgreSQL integration/race tests, fuzz targets, generated-project validation, vulnerability checks and performance baselines. Requires Go 1.26.10+ to incorporate standard-library security fixes. No upgrade migration is required from a prior Cicadel release because none has been published by this implementation. --- Source: `SECURITY.md` Markdown: https://cicadel.fauzaanu.com/en/dev/sources/SECURITY.md # Security reporting This repository is currently private. Report vulnerabilities to the repository owner through a confidential channel or a security issue within this private repository, accessible only to its authorized collaborators. Do not post exploit details, credentials, or user data to public mirrors or unrelated issue trackers. If repository access is unavailable, contact the owner privately rather than publishing details. Maintainers should reproduce the report, assess affected versions, prepare a fix and regression test, coordinate disclosure with the reporter, and publish upgrade instructions when the fix is released. Avoid copying private reports into public release notes before coordinated disclosure. Before making the repository public, enable GitHub private vulnerability reporting and replace this private-repository process with its security-advisory reporting URL. The GitHub private-reporting endpoint is not available for this private repository; no public reporting address is claimed here. Cicadel is experimental. Local tests and automated scanning do not substitute for independent security review or application-specific authorization, rate limiting, deployment hardening, and account lifecycle controls. Supported security fixes initially target the latest prerelease only. Stable-release support windows will be defined before 1.0. --- Source: `contrib/accounts/README.md` Markdown: https://cicadel.fauzaanu.com/en/dev/ref/accounts/index.md # Accounts Optional PostgreSQL accounts using installable app API 1. This source module uses `replace github.com/fauzaanu/cicadel => ../..` for repository development; no remote module release is claimed. In a generated application's `project.InstalledApps(db)`, construct `accounts.New(accounts.Options{DB: db, DeliverRecovery: deliver})`, handle its error, and append `service.HTTPApp("/accounts")`. Run `manage.go migrate` before serving. The migration is recorded SQL, never AutoMigrate. Removing registration preserves data. The host owns its database pool. The HTTP endpoints accept POST JSON with `Content-Type: application/json`: | Path | Fields | Authentication | | --- | --- | --- | | login | Username, Password | Credentials; returns token | | logout | empty object | Bearer token | | change-password | OldPassword, Password | Bearer token | | request-recovery | Username | Public; invokes delivery callback | | reset-password | Token, Password | Recovery token | | revoke | empty object | Bearer token; revokes every session | Clients send `Authorization: Bearer `. These routes use no ambient cookies. Install `service.Middleware` explicitly on host routes requiring current-user context; use `UserFromContext` and reject anonymous requests where needed. The middleware does not itself require authentication. For browser applications, construct `service.Browser(sessions)` and wrap routes with `browser.Middleware`. This composes SCS LoadAndSave, CSRF validation and account resolution in order. Use `browser.Login`, `browser.Logout`, `browser.CSRFToken` and `browser.RequireLogin` in HTML handlers. The account token is held only in the server-side session; disablement and revocation are checked on every request. Do not additionally wrap these routes with SCS/CSRF middleware. SCS login alone does not create an accounts session. Existing SCS APIs remain compatible. This package does not change forms, uploads or admin. Tokens contain cryptographic randomness and only SHA-256 digests are stored. Password changes and recovery revoke all sessions and recovery tokens in one transaction. Recovery consumption locks the user and rechecks token deletion, so concurrent redemption succeeds once. Session creation serializes with password changes. `SetActive(false)` revokes sessions and recovery tokens. Disabled users cannot log in, resolve current-user context or use permissions. Passwords must contain 12–72 bytes. `Policy` adds application validation; bcrypt cost defaults to 12 (configurable 10–16). Successful password authentication upgrades lower costs without downgrading stronger hashes or overwriting a concurrent password change. `Backend` can verify another credential source but must return an existing local account; central disabled-user checks still apply. `SessionLifetime` defaults to 24 hours and `RecoveryLifetime` to 30 minutes; both are absolute lifetimes of at least one minute. Optional `IdleTimeout` expires sessions after inactivity; successful current-user resolution refreshes activity. PostgreSQL is the persistent store. Schedule `manage.go accounts:cleanup` to remove expired sessions/recovery records. No background worker is started. Transport security, request rate limits, and recovery delivery belong to the host. Delivery must send the raw token only to the account's previously verified recovery destination; never to an arbitrary request-supplied address. Avoid logging tokens or credentials. Groups use `CreateGroup`, `AddMember`, `RemoveMember`, `Grant`, and `Deny`. `HasPermission` checks exact application-defined model/action strings such as `posts.change`. There is no superuser bypass or implicit object access. Apply an explicit host policy for each object in addition to model permissions. OAuth/OIDC, social login and MFA remain outside scope. The `accountui` package supplies compiled HTML account screens. Administration integration remains separate. Run `CICADEL_TEST_DATABASE_URL=postgres://...?... go test -race ./...` with a disposable PostgreSQL database and schema creation privileges. Tests isolate and remove their schema. Parent module tests do not include this nested module. `sh contrib/accounts/verify-host.sh` generates a separate application, registers the package via local module replacements, applies its public migration contract, and verifies login/logout over HTTP. Use a disposable database; the verification applies accounts migrations and removes its test user afterward. ## Browser pages Use `service.Browser(sessions)` and `browser.HTMLApp(Pages{Brand: "My app"})` for the default compiled HTML screens. Register that app once and migrate before serving. It owns its cookie/CSRF middleware and embedded CSS; other protected routes apply `browser.Middleware` followed by `browser.RequireLogin`. `UserFromContext` is shared with the bearer adapter. The browser stores the account token only in the server-side SCS store, rotates login cookies and checks revocation/disablement on every request. Migration 002 adds the PostgreSQL `accounts_browser_sessions` table used by the starter. Custom session stores remain supported. Use `cicadel startproject DIRECTORY MODULE` for a wired project, including verified account provisioning and SMTP recovery delivery. Read the [complete authentication guide](https://cicadel.fauzaanu.com/en/dev/topics/authentication/index.md) for the route table, configuration, middleware ownership and testing. There is no public signup or administrator panel in this starter. This module owns `.templ` source and generated Go/CSS. Run `ui build` and `ui check --json` using the matching framework CLI, in addition to its tests. --- Source: `contrib/formsets/README.md` Markdown: https://cicadel.fauzaanu.com/en/dev/ref/formsets/index.md # Formsets Core FORM-03 battery for bounded collections of model forms and atomic inline related-object editing. PostgreSQL 18 and GORM are supported. This is a separate Go module, using experimental Cicadel app API 1 and the new `forms/model` API. The declared app version is `v0.1.0`; no published module release is claimed. For local development, require `github.com/fauzaanu/cicadel/contrib/formsets v0.0.0` and replace it with this directory; also replace the framework module with the repository root. Go does not inherit a dependency's replace directives. Consumers must pin a real framework/module release once published. The generated consumer test verifies local source integration, not remote versioned installation. Standard projects register `formsets.App()` in `project/apps.go` for inventory and provider validation. Retain this entry when adding local apps. The package owns no routes, schemas, assets, workers or migrations; removing it never deletes application data. The default project includes this module through the public installable contract. ## Define and submit a collection ```go set, err := formsets.New[LineItem](db, formsets.Config[LineItem]{ Model: model.Config[LineItem]{Fields: []model.Field{ {Name: "Description", Required: true, MaxRunes: 200}, {Name: "Quantity", Required: true}, }}, Prefix: "lines", MaxForms: 100, MinItems: 1, MaxItems: 50, AllowDelete: true, }) // Handle configuration errors during setup. collection := formsets.Inline[Order, LineItem](orderID, "OrderID", func(tx *gorm.DB) *gorm.DB { return tx.Where("owner_id = ?", userID) }) posted, err := forms.Parse(r) // Handle parse failure; use body limits, authentication and CSRF middleware. bound := set.Bind(posted.Values) result, err := bound.Save(r.Context(), collection) ``` Use imports `github.com/fauzaanu/cicadel/contrib/formsets`, `github.com/fauzaanu/cicadel/forms`, and `model "github.com/fauzaanu/cicadel/forms/model"`. Define parent/child models and recorded PostgreSQL migrations normally, including foreign keys and unique constraints. Never include the parent foreign key in editable fields. The parent identity is trusted route/application input; parent authorization is mandatory. The URL-encoded wire format is: ```text lines-TOTAL_FORMS=3 lines-0-id=12 lines-0-description=Updated description lines-0-quantity=2 lines-1-id= lines-1-description=New item lines-1-quantity=1 lines-2-id=15 lines-2-DELETE=on ``` Indexes are zero-based and contiguous. `TOTAL_FORMS` is required exactly once, canonical decimal, and limited before allocating rows. MaxForms defaults to 100 and must be between 1 and 1000. MaxItems defaults to MaxForms; it limits stored items, including existing rows omitted from this submission. MinItems defaults to zero. IDs must be canonical positive integers within the model key type; blank means new. Integer primary keys are supported; composite/UUID/string keys are explicitly rejected in this first formset contract. Duplicate identities, repeated control values, malformed/out-of-range indexes, invalid typed input and unauthorized rows fail the whole batch. Unknown model fields cannot cause mass assignment. A deleted row bypasses ordinary required field validation; deleting an unsaved extra row simply discards it. Blank extra rows are otherwise validated normally. Omitting a stored row preserves it. Deletes are disabled unless AllowDelete is true. Boolean delete values are `on`, `1`, `true`, `0`, `false`, or empty. Prefixes are ASCII identifiers; editable input names cannot be `id`, `DELETE`, or contain hyphens. `Bound.Errors()` returns nil or an independent `*formsets.Errors` snapshot. `Rows[index]` contains field messages (empty field key is row-wide); `General` contains collection messages. `Values(index)` and `Len()` support redisplay in your existing typed components. No widget/HTML generation is added. Save-time validation can also return `*Errors`; other errors are operational and must not be shown verbatim. Parent authorization failure is `gorm.ErrRecordNotFound`. ## Atomic persistence Inline locks the authorized parent `FOR UPDATE`, even for empty collections. Existing submitted child identities are reloaded and locked in key order under the parent scope before any writes. Item limits are calculated from the stored collection, not a client-provided initial count. Explicit deletes run first, then creates/updates run in submission order. This permits replacing a deleted unique value. Swapping two surviving unique values is not implicitly staged; PostgreSQL constraints decide whether it is valid. Every save/delete, GORM hook, model-form check/hook, and formset hook uses the same outer transaction. Any failure rolls back the entire batch. Results are returned only after commit: Saved contains persisted rows in submission order; Deleted contains removed IDs. GORM soft-delete behavior and authored database cascade rules apply; this package does not invent cascades. `BeforeDelete` and `AfterDelete` accept `(context.Context, *gorm.DB, *T)`. Changing deletion identity is rejected. `Validate(context.Context, *gorm.DB)` is a final cross-row validation hook: query the transaction and return `*Errors` for safe collection messages. It should be read-only. Model hooks cannot move saved rows outside the collection; persisted ownership fields are checked. Limits are also rechecked after hooks. Use the supplied transaction for audit or join-table writes; defer external side effects until commit or use an outbox. For other collection structures, supply a `Collection[T]` with a trusted Lock, Scope, Seed and Protected field names. Lock must serialize all membership changes (including insertions into an empty collection); Scope filters rows on the supplied transaction; Seed supplies trusted initial values. All writers—including code outside this package—must honor the same collection lock for count guarantees. Normal database deadlock/serialization errors remain operational errors; callers may retry the complete operation. Repeated submissions are separate operations, not implicit idempotent requests. Do not construct sets on an existing transaction; the public Save result assumes ownership of the outer commit. The core's `Form.InTransaction(tx)` is the general composition seam used here. Individual model-form results inside that transaction remain provisional until the outer commit. It accepts PostgreSQL transaction handles only and retains the original model/naming configuration. ## Verification and boundaries Tests cover malformed management data, bounded allocation, identity tampering, scoped parent/child ownership, mixed add/edit/delete, omitted-row preservation, zero values, replacement under uniqueness, limits, concurrent additions, and rollback including deletion/audit hooks. A generated independent project registers the app and exercises the API through HTTP form submissions. Lifecycle checks verify removal does not perform schema/data cleanup. Tests create isolated SQL fixtures, not AutoMigrate-managed schemas. This completes FORM-03 for the documented integer-key collection contract. Nested inline collections, automatic relation/widget inference, client-side row editors, string/composite identities are excluded. FORM-01 widgets/validators are complete; see [forms completion](https://cicadel.fauzaanu.com/en/dev/topics/forms-completion/index.md). See [TESTING.md](https://cicadel.fauzaanu.com/en/dev/sources/contrib/formsets/TESTING.md) for reproducible checks. `Model.VersionField` enables per-row `_version` validation for edits and deletes. `IgnoreEmptyNew` optionally discards wholly empty new rows. `AdminInline[P,T]` provides a typed inline editor and transactional adapter for `admin.Config[P]`. Its parent authorization, ownership and child validation are covered by `admin_test.go`. See the complete forms guide for configuration. --- Source: `contrib/migrations/README.md` Markdown: https://cicadel.fauzaanu.com/en/dev/ref/migrations/index.md # Model-driven migrations (experimental installable) This independent Go module implements Cicadel's standard automated migration battery. It uses GORM model metadata, compares it with recorded model history, and writes **Goose SQL migration files**. Generation never opens a database. Applying migrations remains an explicit, separate command. For the default project workflow, use [the migration guide](https://cicadel.fauzaanu.com/en/dev/topics/migrations/index.md). The lower-level APIs and standalone tools below operate on the same engine and support existing hosts and history maintenance. ## Try the complete workflow From this repository: ```sh cd contrib/migrations/example go run . migrations:makemigrations notes --dry-run go run . migrations:makemigrations notes --name initial DATABASE_URL='postgres://user:password@localhost/app?sslmode=disable' go run . migrate ``` Edit `models/note.go`, adding `Published bool` with the tag `` `gorm:"not null;default:false"` ``, then: ```sh go run . migrations:makemigrations notes --name published go run . migrations:makemigrations notes --check go run . migrate ``` Keep `DATABASE_URL` in the environment when applying. `--check` exits unsuccessfully if an unrecorded model change exists; it does not check which files the database has applied. `--dry-run` prints the candidate file. Both modes leave files and directories untouched. An unchanged model set writes nothing. ## Install and register The source module lives at `github.com/fauzaanu/cicadel/contrib/migrations` and has its own `go.mod`, tests and dependencies. Framework management imports this module as its canonical migration implementation. There is no published package release yet. For local development, add a project `replace` for this module to the checkout's `contrib/migrations` directory and require it at `v0.0.0`; likewise replace the framework with this checkout. Before publication, pin a released framework version and remove the development replace. The manifest's `v0.1.0` is the experimental contract version, not an existing tag. ```go import migrations "github.com/fauzaanu/cicadel/contrib/migrations" // In your installed-app factory: generator, err := migrations.New(migrations.Config{ Apps: []migrations.Definition{{ Name: "blog", Directory: "apps/blog/migrations", Models: []any{&blog.Author{}, &blog.Post{}}, }}, }) // Check err, then register your blog app and generator with cicadel.New. // Give the blog app Migrations: os.DirFS("apps/blog/migrations") for development, // or embed the committed migrations in the production executable. ``` Run `migrations:makemigrations blog` through `management.Manager`. The app registers this as a `BuildCommands` contribution. **Your project builder must inspect `app.IsBuildCommand(args[0])` before opening the database or acquiring resources.** The manager bypasses runtime hooks, provider checks and global runtime checks for these commands. Constructors must also remain free of database work. See the [runnable host](https://github.com/fauzaanu/cicadel/blob/main/contrib/migrations/example/manage.go) for the full wiring; ordinary existing project builders are not automatically rewritten by installing this module. `models.go` and `models/*.go` both work: these are ordinary imported Go structs. Explicitly register every managed model in `Definition.Models`. Alternatively, generate this registry using the build-time discovery command below. Use the same GORM naming strategy in `Definition.NamingStrategy` and your runtime GORM configuration. Each directory belongs to exactly one app. Cross-app relationships require explicit pinned historical references, described below. ## File format and review `00001_initial.sql` contains a model-state JSON comment followed by `-- +goose Up` and `-- +goose Down` SQL. Later files record a hash of the preceding file. The snapshot and SQL are published together as one file, with an exclusive generation lock and an atomic no-overwrite publish. Every generation revalidates the history and regenerates its expected SQL to detect accidental edits. Commit these files. SQL from old files runs independently of current model definitions. Review both directions before applying. Down statements can drop tables, columns and data; their existence is not a promise of lossless rollback. Use the checksum-aware `Runner` for PostgreSQL transactions, execution history, locking and schema verification. Rollback uses an explicit target with `cicadel-migrate -target APP=VERSION -allow-data-loss apply`, as shown below. Handwritten histories require reviewed per-version snapshots and the explicit import/adoption protocol below. Legacy generated formats require contiguous versions; format 3 supports explicit branches and merges. Do not mix unrecorded SQL into a managed directory. Use recorded data/schema declarations instead. Generation validates immutable history offline; the runner additionally detects applied-history changes and managed-schema drift against the live database. Generation does not prove constraints will fit existing data. ## Supported slice - PostgreSQL only; single or composite primary keys, optional integer identity. - Built-in boolean, integer, float, string, byte and time fields; nullable columns, string lengths, and scalar defaults. Unsigned 64-bit values are limited to the PostgreSQL signed BIGINT range; choose signed types for predictable portability. - Creating tables; adding ordinary nullable columns or non-null columns with an explicit scalar default; adding ordinary/unique indexes. - Single/composite foreign keys to registered primary or unique keys in the same app, including belongs-to/has-one/has-many declarations and reference actions. Explicit join models represent relationship tables. All tables and indexes are created before foreign keys. Named check constraints are supported. - Deterministic output, no-op detection, SQL preview, CI checks and reverse SQL. Undeclared removals, renames and alterations are rejected. Explicit declarations are described below. Remaining unsupported features include implicit many-to-many join tables, native user-defined SQL types, unregistered custom storage mappings, expression/default SQL and advanced indexes. Versioned built-in storage mappings for custom fields and serializers are supported as described below. Explicit `gorm:"-"`/migration-ignore fields remain unmanaged. Unsupported GORM features may still be used with the existing handwritten migration workflow. Other databases are excluded. Dependency graphs, compatible merges, replacement bundles, verified baselines and managed-schema drift checks are supported with the explicit boundaries documented below. ## GORM and GEN GORM is already the runtime ORM. Normal Go models and GORM queries work today; `AutoMigrate` is not called. This package generates schema migration files, and The recorded-history runner applies them. [GORM GEN](https://gorm.io/gen/index.html) can additionally generate type-safe query APIs from existing structs (`ApplyBasic`), or generate structs from live tables. It does not replace historical schema diffing. GEN is not a dependency of this package; the separate [querygen installable](https://cicadel.fauzaanu.com/en/dev/ref/querygen/index.md) generates queries from authored models and tests GORM version compatibility. ## Verification ```sh go test -race ./... go vet ./... CICADEL_TEST_DATABASE_URL='postgres://.../disposable_test_database' go test -race ./... ``` The PostgreSQL integration test creates and drops its `ym_*` fixture tables and `cicadel_modeltest_versions`. Use a disposable database. CI runs this nested module explicitly: root `go test ./...` does not traverse nested modules. ## Build-time model discovery Opt models in with `//cicadel:model` immediately above their exported struct declarations. Helpers without that marker are excluded even in `models.go`. Select the installed app packages explicitly; no runtime directory scanning occurs. From the consumer module (after installing this module): ```sh go run github.com/fauzaanu/cicadel/contrib/migrations/cmd/cicadel-models --out modelregistry/models.gen.go ./apps/blog ./apps/blog/models go run github.com/fauzaanu/cicadel/contrib/migrations/cmd/cicadel-models --check --out modelregistry/models.gen.go ./apps/blog ./apps/blog/models ``` Use `modelregistry.Models()` in `Definition.Models`. The output package must be separate from model packages to avoid import cycles. Go package/type analysis respects imports, GOOS, GOARCH and `--tags`; excludes test files; rejects aliases, generic and unexported model types. Run `--check` for every supported build configuration before compilation. A configuration with a different model set requires a separate registry/build configuration, not silently shared history. `--check` detects a stale or missing registry without writing anything. The runnable example uses generated discovery; its `models.Models()` function also demonstrates the explicit-registration escape hatch. Regenerate it from this module using: ```sh go run ./cmd/cicadel-models --out example/modelregistry/models.gen.go ./example/models ``` ## Declared PostgreSQL changes (format 2) Explicit declarations were introduced in format 2; new files use format 3. Format-1 and format-2 files remain readable and byte-for-byte immutable; the next file links their existing hash. No conversion or rewrite is necessary. Pass `Options.Changes` in Go or `--changes changes.json` to `makemigrations`. Declarations run in order before inferred additions. For example, after renaming `Body` to `Text` in the model (keeping its table name): ```json [{"kind":"rename_column","table":"demo_notes","name":"body","to":"text"}] ``` ```sh go run . migrations:makemigrations notes --name rename_body --changes changes.json ``` Declarations are one migration's intent, not permanent model settings. Omit them on subsequent no-op/check runs. Reusing a stale declaration fails validation. Supported declarations: - `rename_table` (`table`, `to`) and `rename_column` (`table`, `name`, `to`) preserve data and update recorded references. Implicit PK/identity names are renamed with their owning objects. - `drop_table` and `drop_column` require a nonempty `reason` acknowledging data loss. Drop dependent indexes/FKs explicitly first; no implicit CASCADE. - `drop_index`, `drop_check` and `drop_foreign_key` explicitly remove constraints. A changed model declaration then adds its replacement through the ordinary diff. - `alter_column` supplies the complete target `column`. Type/size changes require an authored `using` expression and `reason`; `reverse_using` is optional and is an author's explicit claim that the reverse conversion is appropriate. Making a column non-null requires `backfill`, an SQL expression for NULL rows. Defaults may be changed or removed. Primary/identity alterations still fail. - `data` supplies `up` and optional `down` SQL against historical table/column names, independently of current Go types. It must not change schema. SQL is trusted developer input; never interpolate request input into declarations. A removal, backfill, conversion without `reverse_using`, or data operation without `down` makes the migration irreversible: its Down raises an error. Restore a backup or write a reviewed forward repair. Even an explicit inverse conversion may lose information; review it against real data. A reversible schema change is not a data backup. New constraints may fail against existing rows; PostgreSQL rolls back the transactional migration, permitting correction and retry. Preview/check validate all declarations and historical files before writing. Embedded Goose directives in authored expressions/SQL are rejected. PostgreSQL checks SQL syntax and data-dependent constraints when applying; offline generation does not claim to prove arbitrary SQL expressions safe or valid. ## PostgreSQL catalog, execution and recovery `Load(definitions...)` reads historical files without current models. Its catalog orders migrations by version-level dependencies plus each app's previous version. Set `Options.Dependencies` to refs such as `Ref{App: "accounts", Version: 2}` when creating a dependent migration. Missing dependencies, cycles and inconsistent applied prefixes fail planning. A target cannot orphan another app's dependency. Format-3 histories support explicit branches, merges and replacement bundles as described below; duplicate IDs and missing parent versions are rejected. A standalone command avoids starting application runtime hooks: ```sh # From contrib/migrations, after generating example migrations: go run ./cmd/cicadel-migrate -app notes=example/migrations show go run ./cmd/cicadel-migrate -app notes=example/migrations -offline sql DATABASE_URL='postgres://.../app' go run ./cmd/cicadel-migrate -app notes=example/migrations plan DATABASE_URL='postgres://.../app' go run ./cmd/cicadel-migrate -app notes=example/migrations apply DATABASE_URL='postgres://.../app' go run ./cmd/cicadel-migrate -app notes=example/migrations verify # Roll back to a target after reviewing its Down SQL and taking a backup: DATABASE_URL='postgres://.../app' go run ./cmd/cicadel-migrate -app notes=example/migrations -target notes=1 -allow-data-loss apply ``` Repeat `-app APP=DIRECTORY` for **every managed app**, including dependencies. `show` inspects artifacts offline; `status` reports applied/pending state from the database. `migrate`, `showmigrations` and `sqlmigrate` alias `apply`, `status` and `sql`, respectively. `plan` and `sql` compare against applied history. `-offline` explicitly assumes a fresh database. `-timeout` bounds connection, lock and execution time. Plans are rechecked under the shared Goose advisory lock. The new `Runner` maintains `cicadel_migration_checksums` alongside existing `cicadel_APP_versions` Goose history. Every migration's SQL, checksum and Goose record commit atomically. A failure rolls back that migration; previously committed migrations remain applied. Correct the data prerequisite and retry the same immutable SQL. Do not edit an applied migration or rewrite its checksum. Missing applied files, modified checksums, inconsistent dependencies and drift fail explicitly. A commit error is reported as uncertain: inspect before retrying. For an existing **generated** Goose history, use `adopt` once. Adoption verifies the actual managed schema against the snapshot of the applied prefix before recording checksums. It never marks pending migrations applied or overwrites checksums. Unrecorded handwritten histories are not automatically adopted. Existing schemas without history use the explicit initial-baseline workflow below. Schema verification renders expected tables in a randomly named scratch schema inside a transaction that is always rolled back. It requires database CREATE privilege. Managed columns, defaults, identities, keys, constraints, indexes, user-trigger presence and row-level-security enablement are compared; unrelated tables are ignored. This is schema verification, not verification of row contents, permissions, sequence counters, every PostgreSQL extension feature or external processes that change schema without taking the migration lock. Management commands and the standalone tooling use this same checksum-aware runner. The former core Goose executor has been removed. Existing Goose records require verified adoption before further migrations; applied SQL is never silently replayed. ### Authored schema SQL and initial baselines `Record(definition, historicalState, options)` is the model-independent recording API. `RecordData(definition, up, down, options)` uses the latest recorded state without loading models. Both use the same atomic file publication and hash chain. For handwritten schema SQL, a `Change{Kind: "schema", After: &state, Up: sql, Down: reverseSQL, Reason: "..."}` records the complete resulting historical state. Missing Down makes it irreversible. The transactional runner verifies that SQL actually produced the declared state **before committing**; it also checks tables declared removed are gone. Only schema features represented by `State` can be verified. Transaction-control statements and concurrent DDL are rejected. Explicit handwritten SQL is trusted developer code. To adopt an existing database with no migration history, generate schema-only initial files from its intended models, review them, then run: ```sh DATABASE_URL='postgres://.../app' go run ./cmd/cicadel-migrate -app notes=example/migrations -reason 'Adopt existing release after schema review and backup' baseline ``` Baseline requires exact managed-schema verification and no existing Goose history for those apps or checksum-managed history in the database. Include all initial apps in the same operation. Only generated initial schema files are eligible; authored data SQL and later versions cannot be skipped this way. Initial SQL is **not executed**. `cicadel_migration_baselines` permanently records the checksums, reason and time of adoption, separately from execution history. Existing rows remain intact. Later migrations execute normally. Existing Goose histories use `adopt` instead; neither workflow can rewrite an existing checksum. ### Branches and replacements (format 3) New files now use format 3, recording explicit parent IDs and hashes. Formats 1 and 2 remain readable without rewriting. Version IDs are unique positive integers; format-3 IDs may have gaps so independently developed branches can allocate separate IDs. Legacy formats retain their original contiguous-history contract. Use `--parent VERSION --version NEW_ID` to generate a deliberate branch. On combining branches, ordinary generation/application rejects multiple heads. Create an explicit merge using `makemigrations --merge` with the combined models, or, independently of models: ```sh go run ./cmd/cicadel-migrate -app notes=example/migrations -name merge_branches merge ``` Compatible disjoint schema changes merge deterministically. Concurrent changes to the same schema object, including duplicate additions, fail rather than inventing a destructive resolution. Resolve unapplied conflicting work explicitly; never edit a migration already applied to any deployment. Authored data SQL still requires human review for semantic conflicts; schema snapshots cannot prove data operations commute. Target versions select their ancestor closure, not every smaller numeric ID. Rollback follows reverse dependency order. Record a cross-app dependency with `--depends accounts:2` (repeat as needed), or `Options.Dependencies`. All dependency apps must be supplied when loading/applying. Data SQL can be recorded offline with `-up-file` and optional `-down-file` followed by the `data` command; it reads historical state without importing current models. Consolidate an app's ancestor history into a replacement: ```sh go run ./cmd/cicadel-migrate -app notes=example/migrations -target notes=3 -name initial_history squash ``` The `.squash.json` bundle is canonical, checksummed and verified against every original source. Fresh installations execute an eligible consecutive bundle in one transaction and audit it in `cicadel_migration_replacements`. Partially applied histories execute remaining originals, preserving data SQL order. Cross-app ordering that interleaves bundle members also falls back to originals. Rollback uses original reverse operations. SQL is consolidated, not optimized away. **Retain the originals and the bundle**: deletion of either executed history is an error, and originals remain necessary for older deployments. This workflow supports replacement without assuming all users have already upgraded. ### PostgreSQL capabilities and nontransactional recovery `Renderer("postgres")` exposes the versioned `SchemaRenderer` contract and its capabilities. Other databases are rejected. `Render` is offline and accepts historical before/after states and explicit declarations. PostgreSQL 18.4 is the locally tested version; CI runs PostgreSQL 18. Ordinary schema operations run transactionally. No support for unrelated database providers is implied. One explicitly supported nontransactional operation is a single concurrent index addition, selected with `Options.ConcurrentIndex` or `makemigrations --concurrent-index INDEX_NAME`. Its reverse drops that index concurrently. Mixing it with other schema changes is rejected before publication; arbitrary nontransactional SQL and transactional replacement of concurrent-index migrations are unsupported. Before concurrent DDL, the runner commits a dirty marker. Failure or interruption blocks ordinary apply/plan until recovery is deliberate. For a failed unique index, inspect PostgreSQL's invalid index and duplicate rows, repair the data and remove the known failed index explicitly. Then: ```sh # Certify that the pre-migration schema is restored; this does not rerun SQL: DATABASE_URL='postgres://.../app' go run ./cmd/cicadel-migrate -app notes=example/migrations -target notes=3 -recovery retry recover # Retry the immutable migration: DATABASE_URL='postgres://.../app' go run ./cmd/cicadel-migrate -app notes=example/migrations apply ``` If DDL completed but the process stopped before recording completion, use `-recovery complete` instead. It records completion only after verifying the post-migration schema. Both actions are audited in `cicadel_migration_recoveries`. Neither action blindly executes SQL, discards a dirty marker, drops an index, or claims transactional rollback of nontransactional work. ### Cross-app model relationships Pin the referenced app's recorded schema in the dependent definition: ```go migrations.Definition{ Name: "notes", Directory: "apps/notes/migrations", Models: []any{¬es.Note{}}, References: []migrations.ReferenceSource{{ App: "accounts", Version: 1, Directory: "apps/accounts/migrations", }}, } ``` The referenced model may appear in a GORM relationship field, but do not register its table as owned by both apps. Generation resolves the pinned historical state, records it in the dependent snapshot, and automatically adds the version-level dependency. Catalog loading checks that the recorded referenced snapshot matches the referenced migration. Every dependency app must be supplied to the runner. Cross-app destructive changes require deliberate ordering: for example, first record removal of a dependent FK, then rename the referenced table with a dependency on that removal, then add the FK against the new pinned snapshot. The planner will not guess how to coordinate a destructive change across apps. Explicit join models with composite keys can refer to multiple app snapshots. ## Project initialization and runnable lifecycle The [project workflow guide](https://cicadel.fauzaanu.com/en/dev/topics/migrations/index.md) installs the commands with Go tool directives and initializes an offline entrypoint. Run `go tool cicadel-migrations-init -app blog=./apps/blog` after marking authored models. Existing files are never overwritten. The separate-consumer test exercises actual local module installation, discovery across `models.go` and `models/`, regeneration checks, offline migration generation and PostgreSQL application. From this module, run the complete disposable lifecycle: ```sh DATABASE_URL='postgres://.../disposable_database' go run ./example/workflow ``` It preserves generated artifacts for review and removes its own uniquely named schema. The walkthrough covers initial generation/application, a cross-app FK, rename/rollback/reapply, conversion/backfill, historical data SQL, transactional failure/retry and concurrent-index repair/recovery. Its integration test runs in CI. ### Importing an existing handwritten history `ImportGoose(definition, []LegacySnapshot{...})` requires a reviewed schema state and reason for **every** handwritten SQL version. It preserves all SQL bytes and writes `.cicadel-legacy.json`, binding file hashes, historical states and declared dependencies. `Reversible` defaults to false; a legacy Down is not evidence that lost rows can be recovered. Later generated files link the imported fingerprints. The CLI accepts the corresponding JSON array: ```sh go tool cicadel-migrate -app blog=apps/blog/migrations -states-json reviewed_states.json import DATABASE_URL='postgres://.../existing_app' go tool cicadel-migrate -app blog=apps/blog/migrations adopt ``` Existing applied prefixes are verified against their actual recorded schema before adoption. Fresh installations execute the original SQL and generated upgrades. Tests cover fresh, initial-prefix and fully-applied legacy histories. Editing either imported SQL or its metadata changes the migration fingerprint. Supported import directives are transactional Up/Down and StatementBegin/StatementEnd. NO TRANSACTION, environment substitution, unknown directives, transaction control and CRLF source files fail before publication; convert unsupported histories deliberately rather than silently changing their execution semantics. Snapshots must use supported PostgreSQL state metadata. Adoption verifies schema, not historical row contents: reviewing old data SQL and its provenance remains the operator's responsibility. ### Package-owned embedded histories Runtime catalogs can use `Definition{Name: "packageapp", Source: migrationFS}` with an `fs.FS` rooted at that app's migrations. This supports independently installed packages and executables without source-tree access. Use `//go:embed all:migrations` and `fs.Sub` when importing histories: the `all:` prefix includes `.cicadel-legacy.json`. Generation/import/squash require a writable source checkout and reject an embedded source. They never write into a dependency cache. The separate-consumer integration test applies an embedded package history. ### Custom fields and serializers Register explicit storage metadata in `Definition.CustomTypes`, keyed by the explicit GORM `type` tag when present, otherwise its field data type. For example: ```go CustomTypes: map[string]migrations.CustomType{ "uuid": {Version: 1, SQL: "uuid"}, "jsonb": {Version: 1, SQL: "jsonb"}, "numeric": {Name: "money", Version: 1, SQL: "numeric(12,2)"}, }, ``` `SnapshotWithOptions` accepts the same registry through `SnapshotOptions`. Mappings use supported built-in PostgreSQL storage types and record their version in historical state. Storage changes require explicit alteration declarations; old SQL replays without the current custom Go field implementation. Native enum and domain lifecycle management and arbitrary custom SQL types are unsupported. ## Handwritten schema metadata Reviewed import snapshots can represent serial primary keys, named UNIQUE constraints and the `now()` timestamp default. These are explicit historical metadata, not inferred from the live database during generation. Verification compares their PostgreSQL catalog signatures, including constraints and sequence properties. Other SQL default expressions remain unsupported. Original SQL bytes and applied versions are preserved during verified adoption. --- Source: `contrib/migrations/example/migrations/README.md` Markdown: https://cicadel.fauzaanu.com/en/dev/sources/contrib/migrations/example/migrations/README.md Generated SQL belongs here. Run commands from the example directory. Commit generated files; review their Up and Down sections before applying. --- Source: `contrib/querygen/README.md` Markdown: https://cicadel.fauzaanu.com/en/dev/ref/querygen/index.md # Typed query generation This separately installable module generates GORM query APIs from authored Go structs. PostgreSQL is the only supported database. It does not generate models, read database schemas or generate/apply schema migrations. Tested compatibility: GORM **v1.31.2**, GEN **v0.3.29**, dbresolver **v1.6.2**. GEN's default dbresolver v1.5.3 fails to compile against this GORM version (`undefined: gorm.Stmt`); keep the explicit v1.6.2 requirement. GEN transitively depends on other vendor drivers through upstream datatypes; these are not Cicadel database integrations and do not change the PostgreSQL-only contract. From this module: ```sh go run ./example/generate go test -race ./... CICADEL_TEST_DATABASE_URL='postgres://.../disposable_database' go test -race ./... ``` Regeneration tests compare every generated query file byte-for-byte with the committed example and run twice. PostgreSQL tests compile and execute typed create, filter and update queries. A separate temporary consumer module resolves this module through a local replacement and compiles/runs queries; this is local installation verification, not evidence of a published release. For local installation, require `github.com/fauzaanu/cicadel/contrib/querygen` at `v0.0.0`, replacing it with this directory and replacing the framework with the checkout. Run `go mod tidy`. No package release has been published yet. Standard projects run `go run manage.go generatequeries` offline from registered `ModelOf` declarations. The generated `query/` directory is reviewed and committed. Existing custom hosts can register `querygen.New(querygen.Config{Models: registry.Models(), OutputDirectory: "query"})` as an installed app. Run `querygen:generate` through the host's management entrypoint, which must dispatch build commands before opening a database. Explicit models work equally well without discovery. The command writes generated queries to a dedicated output directory. Review and commit generated files; remove obsolete generated files deliberately when removing models. Do not store authored code in that directory. Use `query.Use(db)` for instance-local query objects. The example deliberately avoids GEN's package-global default database. Runtime models remain authoritative. See the upstream [struct-based DAO configuration](https://gorm.io/gen/dao.html). Catalog integration: pass `Config{Apps: installedApps, OutputDirectory: "query"}` to consume app-owned `ModelOf[T]` declarations directly. `Apps` and `Models` are mutually exclusive. Ownership and declared tables are validated before output; tables must match GORM's default naming strategy, including `TableName` methods. The factory snapshots declaration slices. Generation remains offline, does not start apps, and never changes historical migration snapshots. Query model Go names must be unique across selected apps and exported/importable. --- Source: `docs/ai.md` Markdown: https://cicadel.fauzaanu.com/en/dev/intro/ai/index.md # Build with an AI agent Give your agent a task and the documentation index for your application's version. The index links to focused Markdown guides; the full bundle is available when the agent needs broader context. ## Choose the documentation version Use **Documentation version** on this page. The links and copyable prompts change with the selected version. `dev` follows ongoing development; released versions will remain frozen. - [Application documentation index](https://cicadel.fauzaanu.com/en/dev/llms.txt) - [Complete application documentation](https://cicadel.fauzaanu.com/en/dev/llms-full.txt) The root `/llms.txt` currently points to `dev`. Use the version-specific URL in saved project instructions. ## Start an application ```text Use https://cicadel.fauzaanu.com/en/dev/llms.txt as your Cicadel documentation index. Create a new application in ./myapp with module example.com/myapp. The application should: [describe the users, screens, data and permissions]. Read the relevant guides before choosing APIs. Show concrete Go and .templ code. Configure PostgreSQL for the standard accounts and recorded-migration workflow. Register apps explicitly, rebuild generated UI, and run checks and tests. Verify the resulting HTTP behavior and report the local URL and any remaining setup. ``` ## Add a feature to an existing application ```text Read https://cicadel.fauzaanu.com/en/dev/llms.txt. First inspect this project's go.mod, registered apps, routes and existing UI. Add [feature] using the project's conventions and matching Cicadel version. Implement its handlers, components, validation and permissions. If the schema changes, generate and review a recorded PostgreSQL migration. Run the affected tests and the project's normal checks. Summarize the result. ``` ## Fix a bug ```text Use https://cicadel.fauzaanu.com/en/dev/llms.txt and the relevant API guide. Reproduce this problem: [steps, expected result, actual result]. Find the failing request or data flow, add a regression test, and fix it. Preserve existing authorization, CSRF checks and migration history. Run the test against the real affected behavior and report what passed. ``` ## Give the agent useful context Include the application module path, the screen or route you want to change, expected behavior, and who may access the data. Supply secrets through your agent's secure mechanism, not a copied prompt. For specialized work, use the [component-authoring skill](https://cicadel.fauzaanu.com/en/dev/sources/skills/cicadel-components/SKILL.md) or [installable-app skill](https://cicadel.fauzaanu.com/en/dev/sources/skills/cicadel-installables/SKILL.md). They are optional instructions for building applications and reusable packages. ## Framework design is separate Application work does not require the framework roadmap or implementation history. Those documents have a [separate framework index](https://cicadel.fauzaanu.com/en/dev/framework/llms.txt). Read [the philosophy](https://cicadel.fauzaanu.com/en/dev/internals/philosophy/index.md) only when you want the reasoning behind Cicadel's design. --- Source: `docs/api.md` Markdown: https://cicadel.fauzaanu.com/en/dev/ref/api/index.md # API index The source packages include Go documentation; inspect them with `go doc github.com/fauzaanu/cicadel/PACKAGE`. | Package | Main entry points | | --- | --- | | `cicadel` | `App`, `Manifest`, `Requirement`, `New`, `Start`/`Close`, `InstalledApps`, `Resources`, `Check`, `SystemChecks`, `SystemCheck`, `CheckMessage`, `Run`/`Serve`, `Health` | | `settings` | `Defaults`, `Load`, `LoadWith`, `Settings.Validate` | | `urls` | `Path`, `Include`, `New`, `Router.Reverse` | | `response` | `HTML`, `JSON`, `Redirect`, `Error` | | `ui` | `Definition[P,S]`, `Content`, `Text`, `Fragment`, `Group`, `Renderer`, `Merge`, `URL`, `Resolve`, `Diagnostic`, `Committed` | | `ui/layout`, `ui/form` | Optional minimal document and accessible field/CSRF components | | `uic` | `Compile`, `Generate`, `GoDiagnostics`; `cicadel ui generate/build/check`, `ui css install/build/check` | | `templates` | Deprecated experimental compatibility: `New`, `Engine.Render`, `Engine.HTML` | | `database` | `Open`, `Migrator`, `Migrate` | | `apptest` | `Check`: per-instance lifecycle smoke checks for app authors | | `management` | `Manager.Run`, `Manager.CommandNames`, `UsageError`, `ExitCode`, custom `Command` | | `forms` | `Parse`, legacy `Form.Required`/`Valid`; typed `New`, `Typed[T]`, built-in fields/validators, `Definition.Bind`, `Value[T]` | | `forms/upload` | `Parse`, `Middleware`, `FromRequest`, `Batch.Save`/`Close`, `CleanupError` | | `forms/model` | `New[T]`, `Config[T]`, `Field`, `Bind`, `Bound.Create`/`Update`, `Related[R]`, `InTransaction`, `Errors`; [model-form contract](https://cicadel.fauzaanu.com/en/dev/topics/model-forms/index.md) | | `security` | `NewSessions`, `Login`, `Logout`, `CSRF`, `RequireLogin`, password helpers | | `admin` | `Routes[T]`, explicit `Config[T]`, `Field`, permission `Action` | | `api` | `Decode`, `Pagination`, `Fail`, generic `List[T]` | | `static` | `Handler` | | `observability` | `NewLogger`, `NewLogging`, `Logger`, `Middleware`, `RequestID`, `Event`, `OpenRotatingFile`, `NewEmailHandler`, `NewQueryLogger` | | `jobs` | `Migrate`, `NewProducer`, `InsertTx`, `NewWorker` | | `mail` | `Message.Bytes`, `Sender.Send`/`SendMany`, `SMTP`, `Transport`, `RenderComponent`, `Render`, `Capture`, `Console`, `File`, `Discard` | | `storage` | `Store`, `NewLocal`, `HTTP`, `ValidateKey` | | `cache` | `NewMemory`, `Get`, `Set`, `Delete` | The CLI is `go run ./cmd/cicadel startproject DIRECTORY MODULE`, `startapp NAME [DIRECTORY]`, or `startpackage DIRECTORY MODULE NAME`. See [installable apps](https://cicadel.fauzaanu.com/en/dev/topics/installable-apps/index.md). The full workflow is in [the tutorial](https://cicadel.fauzaanu.com/en/dev/intro/tutorial/index.md); integration boundaries and limitations are in [extensions](https://cicadel.fauzaanu.com/en/dev/topics/extensions/index.md). Native i18n/l10n APIs are intentionally absent. See [the battery support policy](https://cicadel.fauzaanu.com/en/dev/sources/batteries.md) for package-extension plans and current capability gaps. The approved UI architecture is [composable components](https://cicadel.fauzaanu.com/en/dev/topics/component-ui/index.md). The compiler/runtime and typed library contracts are implemented; legacy template calls remain a separate compatibility API. Tailwind is the first-party default; see [CSS tooling](https://cicadel.fauzaanu.com/en/dev/howto/tailwind/index.md) and the [tested component authoring guide](https://cicadel.fauzaanu.com/en/dev/howto/component-authoring/index.md). The first-party [formsets module](https://cicadel.fauzaanu.com/en/dev/ref/formsets/index.md) adds `New[T]`, `Inline[P,T]`, `Collection[T]`, `Bound.Save` and `App` for atomic collections. See [complete forms](https://cicadel.fauzaanu.com/en/dev/topics/forms-completion/index.md) for widgets, revision tokens, uploads and atomic administration inlines. The first-party [accounts module](https://cicadel.fauzaanu.com/en/dev/ref/accounts/index.md) exports `Service.Browser`, `Browser.HTMLApp`, `Browser.Middleware`, `Browser.Login`/`Logout`, `Browser.RequireLogin`, and typed `accountui.Page`. See [authentication](https://cicadel.fauzaanu.com/en/dev/topics/authentication/index.md), [email](https://cicadel.fauzaanu.com/en/dev/topics/email/index.md) and [logging](https://cicadel.fauzaanu.com/en/dev/topics/logging/index.md) for complete integration contracts. The [foundation guide](https://cicadel.fauzaanu.com/en/dev/internals/foundation/index.md) documents `cicadel dev`, command help and structured deployment checks, configuration precedence and lifecycle contracts. --- Source: `docs/authentication.md` Markdown: https://cicadel.fauzaanu.com/en/dev/topics/authentication/index.md # Browser authentication and the accounts starter Cicadel has one core accounts service and two explicit HTTP adapters. Browser applications use server-rendered HTML, server-side SCS sessions and CSRF checks. API clients can use the JSON bearer adapter. Both adapters consult the same PostgreSQL users, account sessions, recovery tokens, groups and permissions. Disabling an account, resetting its password or revoking its sessions affects both adapters. A browser never receives the account bearer token in HTML or JSON. ## Ask your agent ```text Read https://cicadel.fauzaanu.com/en/dev/topics/authentication/index.md. Add browser sign-in to my application using the accounts starter or adapter. Use PostgreSQL sessions, CSRF protection and verified recovery addresses. Show the registration and route protection code. Test sign-in, sign-out, and rejection of revoked sessions; keep secrets outside source code. ``` ## Create the starter From the framework checkout: ```sh go build -o bin/cicadel ./cmd/cicadel ./bin/cicadel startproject /tmp/myapp example.com/myapp cd /tmp/myapp go mod edit -replace=github.com/fauzaanu/cicadel=/absolute/path/to/Cicadel go mod edit -replace=github.com/fauzaanu/cicadel/contrib/accounts=/absolute/path/to/Cicadel/contrib/accounts go mod edit -replace=github.com/fauzaanu/cicadel/contrib/formsets=/absolute/path/to/Cicadel/contrib/formsets go mod edit -replace=github.com/fauzaanu/cicadel/contrib/migrations=/absolute/path/to/Cicadel/contrib/migrations go mod edit -replace=github.com/fauzaanu/cicadel/contrib/querygen=/absolute/path/to/Cicadel/contrib/querygen go mod tidy export DATABASE_URL='postgres://USER:PASSWORD@localhost/myapp?sslmode=disable' export CICADEL_DEBUG=true export CICADEL_SECURE_COOKIES=false go run manage.go migrate CICADEL_USER_PASSWORD='a local account password' go run manage.go createuser owner@example.com go run manage.go runserver ``` Use an email address whose ownership you have verified before provisioning the account. This starter treats the stored username as that verified recovery address. Requests cannot supply a separate delivery address. There is no public registration endpoint or administrator panel. The service's `Create` API remains available for application-owned provisioning and invitation workflows. The accounts module has no published release yet. The commands above use local checkout paths; commit your resolved Go dependencies and replace machine-specific paths before distributing the application. `startproject` always includes accounts, formsets and the standard migration workflow; runtime commands require PostgreSQL. ## Screens and actions The default prefix is `/accounts`; `accounts.Pages.Prefix` can change it. | Route | GET | POST | | --- | --- | --- | | `/accounts` | Signed-in account and session actions | — | | `/accounts/login` | Login form | Rotate session and sign in | | `/accounts/recover` | Recovery form | Deliver a reset link to the verified destination | | `/accounts/reset` | Reset form with token | Consume the token once and replace the password | | `/accounts/change-password` | Current/new password form | Replace password and revoke all sessions | | `/accounts/logout` | — | Revoke current account session and destroy its cookie session | | `/accounts/revoke` | — | Revoke all account sessions and sign out | | `/accounts/style.css` | Embedded compiled stylesheet | — | To customize the screens, compose your own pages with `contrib/accounts/accountui`. ## Integrate an existing application In your project setup, create the service and browser adapter, then register the returned app: ```go service, err := accounts.New(accounts.Options{ DB: db, DeliverRecovery: DeliverRecovery, }) if err != nil { return err } browser, err := service.Browser(security.NewSessions(store, true)) if err != nil { return err } authApp, err := browser.HTMLApp(accounts.Pages{ Brand: "My application", Logger: logger, }) if err != nil { return err } installed = append(installed, authApp) ``` This setup fragment uses your PostgreSQL `db`, shared session `store`, logger, recovery callback and existing `[]cicadel.App`. Pass `installed` to the project's `cicadel.New` call. Register the adapter once and run `migrate` before serving. The generated accounts starter includes the full setup in `project/application.go`. Protect another app's handler with the browser adapter: ```go protected := browser.Middleware(browser.RequireLogin(http.HandlerFunc(dashboard))) route := urls.Path("GET", "/dashboard", "dashboard:index", protected.ServeHTTP) ``` Add `route` to that app's routes. Import `net/http` and `github.com/fauzaanu/cicadel/urls`; `dashboard` is your handler. Check object-level permissions inside it. `HTMLApp` owns LoadAndSave → CSRF → account-resolution middleware for its routes. Do not wrap those routes with those same middleware again. For another app's protected routes, apply `browser.Middleware` then `browser.RequireLogin`, and use `accounts.UserFromContext` inside handlers. `RequireLogin` checks a freshly resolved account, rather than only a stored SCS user ID. Object permissions still require explicit application policy and scoped database queries. The starter uses PostgreSQL `accounts_browser_sessions` through SCS's configurable table name. Migration 002 adds the table without rewriting the existing accounts history. A custom store may also be supplied. Sessions are HttpOnly, SameSite=Lax, secure by default, rotated on login and cleared at logout. Account expiry and revocation are checked on every browser request. SCS expiry can end a session before the associated account token expires; `accounts:cleanup` removes expired tokens. Browser forms are bounded to 8 KiB and all unsafe methods require CSRF. For API-only clients use `service.HTTPApp(prefix)` and `service.Middleware` with explicit `Authorization: Bearer …`. Register either adapter once under the `accounts` app name. If a host needs both, combine their routes into that one app and scope cookie middleware to the HTML routes; never apply cookie CSRF middleware to a bearer-only API by accident. ## Recovery delivery and deployment Configure these starter environment variables: | Variable | Purpose | | --- | --- | | `CICADEL_PUBLIC_URL` | HTTPS origin used to construct reset links | | `CICADEL_MAIL_FROM` | Sender mailbox | | `CICADEL_SMTP_ADDRESS` | SMTP `host:port` with STARTTLS | | `CICADEL_SMTP_USERNAME`, `CICADEL_SMTP_PASSWORD` | Optional SMTP credentials | | `CICADEL_LOG_FORMAT`, `CICADEL_LOG_LEVEL` | JSON/text format and log threshold | `project.DeliverRecovery` is the application-owned callback. It sends to the stored verified address, constructs a local reset route using the configured origin and passes the token only to the mail transport. Known and unknown users receive the same recovery response. Delivery errors are logged without tokens, addresses or server response text. Missing delivery configuration is handled without revealing whether a username exists. Passwords must contain 12–72 bytes; applications may add their own password policy. Use HTTPS and secure cookies, configure allowed hosts, apply gateway rate limits to login/recovery endpoints and schedule `manage.go accounts:cleanup`. Never log query strings on reset routes at the proxy. Reset pages send `no-referrer`, `no-store` and a restrictive content security policy. See [email](https://cicadel.fauzaanu.com/en/dev/topics/email/index.md), [logging](https://cicadel.fauzaanu.com/en/dev/topics/logging/index.md) and [operations](https://cicadel.fauzaanu.com/en/dev/howto/operations/index.md) for configuration details. --- Source: `docs/cache.md` Markdown: https://cicadel.fauzaanu.com/en/dev/topics/cache/index.md # Cache application data `cache.Backend` is the context-aware integration boundary for replaceable byte caches. A miss returns `(nil, false, nil)`; an outage returns an error. Callers must explicitly choose whether to fall back to their authoritative data source. Errors remain discoverable with `errors.Is`. Do not use cache contents for session revocation, authorization decisions or distributed locking. `NewMemoryBackend(capacity, maxValueBytes)` supplies a concurrent, bounded local implementation. It copies values, expires entries at the TTL boundary, and evicts the oldest written entry when full. The existing `Memory` and `Store` APIs remain compatible. Local instances do not coordinate across application processes. ```go backend, err := cache.NewMemoryBackend(1000, 64*1024) if err != nil { return err } registry, err := cache.NewRegistry(map[string]cache.Alias{ "articles": {Backend: backend, Namespace: "production:blog", Version: "v1"}, }) if err != nil { return err } articles, err := registry.Lookup("articles") if err != nil { return err } err = articles.Set(ctx, "public:42", []byte("cached payload"), time.Minute) ``` Construct a registry at startup and inject clients into applications. Alias maps are copied. Each alias includes its name, deployment/application namespace and version in a length-framed SHA-256 key. Raw keys are not sent to the backend; keys must contain 1–1024 bytes and TTLs must be positive. Hashing does not encrypt values. Explicitly include every tenant, locale, permission or representation variation in the logical key where applicable. `Delete` invalidates one logical key; deleting a missing key succeeds. Changing Version selects a fresh key space, leaving old entries to expire. All processes must adopt that version to agree on the active generation. In-flight readers or writers can race with deletion, so invalidation does not provide transactional consistency. There is deliberately no global flush operation. Backends must be concurrent, preserve byte ownership, implement positive TTLs, and report operational errors. Cancellation can race with a completed mutation; an error is not evidence of rollback. The built-in adapter checks cancellation before entering its short in-memory operation. Network adapters should pass the context through to their transport. The built-in backend is local to one process. Use a suitable shared backend when multiple instances must share entries. ## Ask your agent ```text Read https://cicadel.fauzaanu.com/en/dev/topics/cache/index.md. Cache [expensive read] in this application. Include tenant and permission variation in the key, set a TTL, define invalidation after writes, and keep the database authoritative. Test hits, misses and backend failures. ``` ## Coalescing expensive loads Create a reusable `cache.NewLoader(client)` and call `loader.Get(ctx, key, ttl, load)`, where `load` accepts a context and returns `([]byte, error)`. Hits bypass the function. Concurrent misses for the same key share the first caller's function and TTL; different keys proceed independently. The cache is rechecked before loading to close the miss/join race. Successful loads populate the cache; load and backend failures propagate and can be retried. Each result is copied independently. Caller cancellation stops only that caller's wait. The load retains the first caller's context values, but its lifetime ends when all waiters cancel or the operation finishes. Load functions must honor cancellation and must not recurse into the same loader/key. Use keys that include all authorization and tenant variations, and use one consistent load policy per loader. Coordination is local to a loader instance, not across processes. Deletion and concurrent writes still have the ordinary best-effort cache consistency semantics described above. ## Bounded load execution For request-serving applications, use `cache.NewBoundedLoader(client, 32, 5*time.Second)` to cap concurrent distinct loads and give each shared operation a deadline. Both limits must be positive. Hits remain available at capacity; callers joining an existing flight share its slot. New misses receive `cache.ErrLoaderBusy` immediately, discoverable through `errors.Is`; choose an application-specific retry or fallback policy. The deadline covers backend rechecking, loading and writing. Functions and backends must cooperate with cancellation. An abandoned operation retains its execution slot until it actually returns, preventing repeated canceled requests from creating unlimited replacement goroutines. A noncooperative function can therefore exhaust capacity; Go cannot forcibly terminate it. Existing `NewLoader` behavior remains compatible and unbounded. Neither constructor provides distributed admission control or transactional invalidation. Race-tested evidence: `TestBoundedLoaderAdmissionAndAbandonedExecution` verifies capacity rejection, abandoned-write prevention and slot reuse; `TestBoundedLoaderDeadline` verifies deadline propagation and limit validation. --- Source: `docs/component-authoring.md` Markdown: https://cicadel.fauzaanu.com/en/dev/howto/component-authoring/index.md # Write Cicadel components with a React mental model Start here when writing UI. Use [the component reference](https://cicadel.fauzaanu.com/en/dev/topics/component-ui/index.md) for the full contract, [Tailwind](https://cicadel.fauzaanu.com/en/dev/howto/tailwind/index.md) for styling, and [`$cicadel-components`](https://cicadel.fauzaanu.com/en/dev/sources/skills/cicadel-components/SKILL.md) as the reusable agent skill. The examples below are copied into a generated app and executed by `TestComponentAuthoringGuide`; they are not pseudocode. ## Ask your agent ```text Read https://cicadel.fauzaanu.com/en/dev/howto/component-authoring/index.md. Add [screen] to my application using typed Cicadel components. Inspect the current app structure, compose the page from explicit props and slots, and connect its handler and route. Build the UI and test its rendered output. ``` ## Translate what you know | React habit | Cicadel equivalent | | --- | --- | | Function component with typed props | Private `.templ` function; public `ui.Definition[Props, Slots]` contract | | `…` | `@Card.Call(CardProps{Title: "Hello"}, CardSlots{Children: ...})` | | `children` / a ReactNode prop | `ui.Content` fields named `Children`, `Footer`, etc. | | Fragment `<>…` | Private markup helper via `ui.Fragment(helper(...))`, or `ui.Group(...)` | | `{title}` | `{ p.Title }` (text escapes automatically) | | `{items.map(...)}` | A multiline Go `for` block | | Conditional JSX | A multiline Go `if` or `switch` block | | Default/destructured props | Go zero values plus a pure `Defaults` function | | `className`, `htmlFor` | HTML `class`, `for` | | Boolean HTML prop | `disabled?={ p.Disabled }` | | Utility classes | Tailwind v4, complete class strings in source | | Context provider / hook state | Explicit typed inputs; handlers load data and process POSTs | | `onClick` / hydration | Not part of this server engine; use links/forms for server interactions | | `key` | No reconciliation runtime, so no key prop; keep DOM IDs unique for labels | Think “props down, markup composed, HTML returned.” Cicadel borrows composition, not React's JavaScript runtime. Do not introduce JSX, hooks, npm or a browser CDN script to author these components. Tailwind uses a pinned standalone build tool. ## A complete card and list page Generate a project with module path `example.com/componentguide`, run `go mod tidy`, and install the CSS compiler once with `go tool cicadel ui css install`. Add the following files. Their package is an ordinary local Go app; no new module is needed. ### Public contract: apps/dashboard/card.go ```go package dashboard import ( "fmt" "github.com/fauzaanu/cicadel/ui" ) type CardProps struct { Title string Tone string } type CardSlots struct { Children, Footer ui.Content } var Card = ui.Definition[CardProps, CardSlots]{ Name: "dashboard:card", Source: "apps/dashboard/card.templ", Defaults: func(p CardProps) CardProps { if p.Tone == "" { p.Tone = "neutral" } return p }, Validate: func(p CardProps, s CardSlots) error { if err := ui.Required("Title", p.Title); err != nil { return err } if s.Children.Empty() { return fmt.Errorf("Children slot is required") } if p.Tone != "neutral" && p.Tone != "success" { return fmt.Errorf("unknown Tone %q", p.Tone) } return nil }, Body: card, } func cardClasses(tone string) string { if tone == "success" { return "rounded-xl border border-emerald-300 bg-emerald-50 p-6" } return "rounded-xl border border-slate-200 bg-white p-6" } ``` Zero `Tone` means neutral; zero `Title` or absent children is an error. Footer is optional. False/zero are not universally “missing.” Use pointer props only when omission must differ from a supplied false/zero. Defaults must not mutate objects referenced by caller-owned maps, slices, or pointers. ### Markup: apps/dashboard/card.templ ```templ package dashboard templ card(p CardProps, s CardSlots) {

{ p.Title }

@s.Children
if !s.Footer.Empty() {
@s.Footer
}
} ``` `@s.Children` inserts a typed fragment. `{ p.Title }` inserts escaped text. They are deliberately different operations. Use complete literal utility names in Go variants; Tailwind cannot evaluate `"bg-" + color + "-50"`. ### Composition: apps/dashboard/page.templ ```templ package dashboard import "github.com/fauzaanu/cicadel/ui" import "github.com/fauzaanu/cicadel/ui/layout" templ itemFooter() { Back to home } templ dashboardBody(rows []Item) {

Dashboard

if len(rows) == 0 {

No items yet.

} for _, row := range rows { @Card.Call(CardProps{Title: row.Title, Tone: row.Tone}, CardSlots{ Children: ui.Text(row.Description), Footer: ui.Fragment(itemFooter()), }) }
} templ dashboardPage(rows []Item) { @layout.Page.Call(layout.Props{Title: "Dashboard", Stylesheet: "/static/app.css"}, layout.Slots{Children: ui.Fragment(dashboardBody(rows))}) } ``` Private helpers need no separate public definition when they don't declare a reusable validated contract. `ui.Text` is for request text; `ui.Fragment` adapts compiled markup, never strings. `ui.Group` combines already-typed fragments. ### Handler and registration: apps/dashboard/app.go ```go package dashboard import ( "context" "log/slog" "net/http" "github.com/fauzaanu/cicadel" "github.com/fauzaanu/cicadel/response" "github.com/fauzaanu/cicadel/ui" "github.com/fauzaanu/cicadel/urls" ) type Item struct { Title, Description, Tone string } type LoadItems func(context.Context) ([]Item, error) func New(load LoadItems) cicadel.App { return cicadel.App{ Name: "dashboard", Components: []ui.Export{Card.Export(1)}, Routes: []urls.Route{urls.Path("GET", "/dashboard", "dashboard:index", func(w http.ResponseWriter, r *http.Request) { rows, err := load(r.Context()) if err != nil { response.Error(w, 500); return } err = (ui.Renderer{}).HTML(r.Context(), w, 200, ui.Fragment(dashboardPage(rows))) if err != nil { slog.ErrorContext(r.Context(), "render dashboard", "error", err) if !ui.Committed(err) { response.Error(w, 500) } } }), }, } } ``` The loader is explicit and can query through your application's DB; the component never discovers a request-wide map. Keep permissions in the handler/query scope. For the runnable demo, replace the starter's empty app list with this loader: ```go package project import ( "context" "example.com/componentguide/apps/dashboard" "github.com/fauzaanu/cicadel" "gorm.io/gorm" ) func InstalledApps(_ *gorm.DB) ([]cicadel.App, error) { return []cicadel.App{dashboard.New(func(ctx context.Context) ([]dashboard.Item, error) { return []dashboard.Item{{Title: "Ready", Description: "Typed props and slots work.", Tone: "success"}}, ctx.Err() })}, nil } ``` For your own module, substitute its import path. Local direct calls to `Card` use that definition. For an overridable library, resolve through `ComponentRegistry` and inject the resulting definition into the page factory; see [the override recipe](https://cicadel.fauzaanu.com/en/dev/sources/skills/cicadel-components/references/patterns.md#libraries-and-project-overrides). ### Behavioral tests: apps/dashboard/app_test.go ```go package dashboard import ( "context" "strings" "testing" "github.com/fauzaanu/cicadel" "github.com/fauzaanu/cicadel/apptest" "github.com/fauzaanu/cicadel/ui" ) func TestDashboard(t *testing.T) { attack := `` app := apptest.New(t, apptest.Settings(), []cicadel.App{ New(func(context.Context) ([]Item, error) { return []Item{{Title: attack, Description: attack}}, nil }), }) res := apptest.NewClient(app).Get(t, "/dashboard").Status(t, 200) res.Contains(t, `rel="stylesheet"`).Contains(t, `href="/static/app.css"`) res.Contains(t, "Back to home").Contains(t, "border-slate-200") if strings.Contains(string(res.Body), "