# Installable applications

An installable app is a Go package that returns a `cicadel.App`. It brings
routes, commands, components and, optionally, database migrations. You register
it in `project/apps.go`.

First-party packages are core defaults. They use the same public contracts as
your own packages. The standard scaffolding registers accounts and formsets and
supplies the migration and query-generation commands.

> [!NOTE]
> Running `go get` on a third-party module does not activate it. An app is active
> only after you register it in `InstalledApps`.

## How apps are composed

![cicadel.New validates each installed App's contributions, orders the apps by dependency and builds one Application](https://github.com/fauzaanu/cicadel/blob/2678611d308376ca8045c8741ee9d4d7814a3fec/docs/diagrams/app-composition.svg)

`cicadel.New` takes the list of apps and builds one `Application`:

1. It validates each app: name, commands, checks, routes, middleware and manifest.
2. It checks declared dependencies and orders apps so dependencies come first.
3. It resolves each app's typed settings.
4. It builds the event dispatcher, component registry, static manifest and routes.

Any error stops startup.

## Ask your agent

```text
Read https://cicadel.fauzaanu.com/en/0.2.7/topics/installable-apps/index.md.
Add [package and version] to my existing Cicadel application.
Inspect its constructor and required configuration, then register it in
project/apps.go without replacing existing apps. Check its dependencies and routes.
Review any database migrations before applying them. Run app checks and tests.
```

## Install a published package

Use the module path and version the package author gives you:

```sh
go get example.com/your-org/greetings@v0.1.0
```

`example.com/your-org/greetings` is a placeholder, not a published Cicadel
package. The next section shows how to create a local package with this API.

Import the package in `project/apps.go` and add its constructor to your app list:

```go
package project

import (
    "os"
    greetings "example.com/your-org/greetings"
    "github.com/fauzaanu/cicadel"
    "github.com/fauzaanu/cicadel/contrib/formsets"
    "gorm.io/gorm"
)

func InstalledApps(_ *gorm.DB) ([]cicadel.App, error) {
    return []cicadel.App{
        formsets.App(),
        greetings.New(greetings.Options{
            Greeting: "Welcome",
            Output: os.Stdout,
        }),
    }, nil
}
```

- Keep the standard `formsets.App()` entry and your other apps.
- You do not list accounts here. The project builder composes it.
- Database-backed factories can use the supplied `*gorm.DB`. Follow the package's
  constructor signature.

## Create a local package

From an existing Cicadel project, scaffold a separate module beside it:

```sh
go tool cicadel startpackage ../greetings example.com/your-org/greetings greetings
cd ../greetings
go get github.com/fauzaanu/cicadel@main
go mod tidy
go test -race ./...
cd ../myapp
go mod edit -replace=example.com/your-org/greetings=../greetings
go get example.com/your-org/greetings@v0.1.0
```

Register it as shown above, then run:

```sh
go run manage.go apps
go run manage.go check
go run manage.go greetings:hello
# Welcome
go run manage.go runserver
```

`GET /greetings/` returns `{"greeting":"Welcome"}`. The scaffold includes this
route, a command, typed options and tests.

> [!TIP]
> Need an app inside one project only? Use `startapp notes` instead.

## What a package returns

This small app exports one named route:

<!-- example: installable/apps/hello/app.go -->
```go
package hello

import (
    "net/http"
    "github.com/fauzaanu/cicadel"
    "github.com/fauzaanu/cicadel/response"
    "github.com/fauzaanu/cicadel/urls"
)

func New() cicadel.App {
    return cicadel.App{
        Name: "hello",
        Manifest: &cicadel.Manifest{
            Module: "example.com/hello",
            Version: "v0.1.0",
            API: cicadel.AppAPIVersion,
        },
        Routes: []urls.Route{
            urls.Path("GET", "/hello/", "hello:index",
                func(w http.ResponseWriter, r *http.Request) {
                    if err := response.JSON(w, http.StatusOK, map[string]string{"message": "Hello"}); err != nil {
                        response.Error(w, http.StatusInternalServerError)
                    }
                }),
        },
    }
}
```

- `Name` owns the route and command namespace. Keep it stable across upgrades,
  especially when the app owns migration history.
- Go selects the module version. `Manifest.Version` is the package's
  compatibility metadata.

## Declare settings

1. Give the package a typed `Options` struct.
2. Tag the fields a deployment may override with `setting:"NAME"` or
   `setting:"NAME,secret"`.
3. Return `Settings: settings.NewPackage(options)` on the app.

The project passes values in Go. `CICADEL_<APP>_<NAME>` overrides them.
`manage.go apps` lists them with secrets redacted, and `manage.go check` reports
invalid keys. The `startpackage` scaffold does this for its greeting. See
[package settings](https://cicadel.fauzaanu.com/en/0.2.7/howto/package-settings/index.md).

## Emit and handle events

Events let apps react to each other without direct calls.

- Declare each event with `events.New[Payload]("<app name>", "name")`.
- List it in `App.Events` and call `Event.Emit(ctx, payload)`.
- Subscribe to other apps' events with `Event.Handle(fn)` in `App.Subscriptions`.

`cicadel.New` rejects subscriptions to events no installed app declares.
Handlers run synchronously in dependency order. The `startpackage` scaffold emits
a `greeted` event. See [events](https://cicadel.fauzaanu.com/en/0.2.7/howto/events/index.md).

## Publish static files

Put public CSS, images and scripts in an embedded directory and return it as
`App.Static`.

- The host serves each file at `/static/<app name>/<path>` with a content-hashed URL.
- `go run manage.go collectstatic DIR` collects them.
- Resolve URLs in handlers with `static.URL(r.Context(), "greetings", "site.css")`.

Do not add your own asset routes. `App.Resources` stays private. The
`startpackage` scaffold publishes `assets/static/`. See
[static files](https://cicadel.fauzaanu.com/en/0.2.7/howto/static-files/index.md).

## Add migrations

For a package with PostgreSQL tables, embed recorded SQL migrations:

```go
//go:embed migrations/*.sql
var migrations embed.FS

func migrationFiles() (fs.FS, error) {
    return fs.Sub(migrations, "migrations")
}
```

1. Import `embed` and `io/fs`.
2. Use the returned filesystem as `App.Migrations`. Handle its error in your factory.
3. Set `Manifest.Providers` to `[]string{"postgres"}`.
4. Run the host's `go run manage.go migrate` before serving requests.

Follow [the migration guide](https://cicadel.fauzaanu.com/en/0.2.7/topics/migrations/index.md) for generated model migrations.

## Depend on another app

If your package needs another installed app, declare it in `Manifest.Requires`:

```go
Requires: []cicadel.Requirement{
    {Name: "accounts", Version: cicadel.VersionRange{
        Min: "v1.0.0", Before: "v2.0.0",
    }},
},
```

These bounds are an example. They do not claim an accounts v1 release exists.
The host must register the dependency too. Missing or incompatible dependencies
fail startup checks.

## Remove or disable an app

To remove an app:

1. Delete its constructor from `InstalledApps`.
2. Remove unused imports.
3. Run `go mod tidy`.

To disable an app, omit its registration but keep its Go module available.

Either way, its routes, commands, static files and owned resources disappear
from the application. Existing PostgreSQL data and recorded migration history
remain. Deleting data is a separate, deliberate operation.

> [!WARNING]
> Restore the registration before running migrations. The migration runner
> rejects an incomplete catalog that contains previously applied package
> migrations.

Re-enabling the package restores its surfaces and lets its recorded history be
checked again. `TestInstallUpgradeRemoveExternalPackage` exercises this sequence
after a version upgrade, before removing the module entirely.

## External translation helpers

Localization is a third-party responsibility. An installable can:

- supply request middleware through `App.Middleware`;
- store its language selection under an unexported context key;
- expose a plain-text helper that accepts `context.Context`.

Render the result with normal `.templ` text expressions or `ui.Text`. Translated
strings must not become trusted HTML because they came from a catalog. Keep the
language selection in the request context, not in package globals, so concurrent
requests cannot change each other's output.

App middleware wraps only that app's routes. It does not translate host routes or
other installed apps. Hosts that want translated components on their own routes
must compose the extension's middleware there. The extension decides fallback
languages, missing-key handling and catalog ownership. Cicadel does not prescribe
translation catalogs or language negotiation.

`internal/scaffold/translation_test.go` checks this boundary. It installs a
separately versioned module through a local Go proxy into a generated consumer.
It covers Unicode, fallback text, escaped untrusted keys, concurrent requests,
isolation from host routes, unregistration, and `go mod tidy` removal followed by
a rebuild. Its tiny two-language lookup is test data, not a production
translation implementation.

## Related

- Component exports: [component libraries](https://cicadel.fauzaanu.com/en/0.2.7/topics/component-ui/index.md#installable-libraries-and-overrides).
- Package lifecycle and compatibility: the
  [package authoring reference](https://cicadel.fauzaanu.com/en/0.2.7/sources/skills/cicadel-installables/SKILL.md).
