# Installable applications

An installable app is a Go package that provides a `cicadel.App`: routes, commands, components, and optionally database migrations. Install its module, configure its factory, and register the returned app in your project.

First-party installable packages are core defaults composed through the same public contracts as your own packages. Standard scaffolding registers accounts and formsets and supplies migration and query-generation commands. Installing an additional third-party module with `go get` does not activate it.

## Ask your agent

```text
Read https://cicadel.fauzaanu.com/en/0.2.3/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 provided by the package author:

```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.

In `project/apps.go`, import the package and add its constructor to your existing 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
}
```

This adds a package while retaining the standard formset registration; the project builder composes accounts. Preserve your other entries when adding it to an existing project. Database-backed factories can use the supplied `*gorm.DB`; follow that package's constructor signature.

## Create and use 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 with the Go example 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 package scaffold includes this route, a command, typed options, and tests. Use `startapp notes` instead when you only need an app inside one project.

## 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` describes the package's compatibility metadata.

## 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 and collects them with `go run manage.go collectstatic DIR`.
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.3/howto/static-files/index.md).

## Add migrations and dependencies

For a package with PostgreSQL tables, embed recorded SQL migrations and declare the database requirement:

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

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

Use the returned filesystem as `App.Migrations`, and set `Manifest.Providers` to `[]string{"postgres"}`. Import `embed` and `io/fs`. Handle the filesystem error in your factory, then run the host's `go run manage.go migrate` before serving requests. Follow [the migration guide](https://cicadel.fauzaanu.com/en/0.2.3/topics/migrations/index.md) for generated model migrations.

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 are example compatibility bounds, not a claim that an accounts v1 release exists. The host must register the dependency too. Missing or incompatible dependencies fail startup checks.

## Remove a project app

To disable an app, remove its constructor from `InstalledApps`. Remove unused imports and run `go mod tidy`. This does not delete tables or stored data; data removal must be a separate, deliberate operation.

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

### External translation helpers

Localization remains a third-party responsibility. An installable can supply
request middleware through `App.Middleware`, store its language selection under
an unexported context key, and expose a plain-text helper accepting
`context.Context`. Render the result with normal `.templ` text expressions or
`ui.Text`; translated strings must not become trusted HTML merely because they
came from a catalog. Keep language selection in the request context rather than
in package globals so concurrent requests cannot change each other's output.

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

`internal/scaffold/translation_test.go` verifies this public boundary with a
separately versioned module installed 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 rebuilding the generated application. Its tiny two-language lookup
is test data, not a production translation implementation.

Disabling a package means omitting its registration from `InstalledApps` while
keeping its Go module available. Routes, commands, static files and owned resources disappear
from the application; existing PostgreSQL data and recorded migration history
remain. Restore registration before running migrations: the migration runner
rejects an incomplete catalog containing previously applied package migrations.
Re-enabling the same package restores its surfaces and allows its recorded
history to be checked again. `TestInstallUpgradeRemoveExternalPackage` exercises
this sequence after a version upgrade, before removing the module entirely.
