# Package Format

Armature packages are distributed as `.pkg` files -- ZIP archives with a standard internal structure.

## ZIP Structure

```
my-package.pkg
├── manifest.json        # required -- package metadata
├── js/                  # browser-side JavaScript
│   └── index.js
├── css/                 # stylesheets
│   └── styles.css
├── script.star          # Starlark entry point
├── star/                # additional Starlark modules
├── assets/              # static assets (images, etc.)
└── migrations/          # schema migration files
```

Only `manifest.json` is required. All other directories are optional and included only if present in the source.

## manifest.json Reference

```json
{
  "id": "my-package",
  "title": "My Package",
  "type": "surface",
  "tier": "browser",
  "version": "1.0.0",
  "description": "What this package does.",
  "icon": "📦",
  "author": "your-name",
  "route": "/s/my-package",
  "auth": "authenticated",
  "permissions": [],
  "api_routes": [],
  "db_tables": {},
  "settings": {},
  "hooks": {},
  "exports": [],
  "schema_version": 1
}
```

### Required Fields

| Field | Description |
|-------|-------------|
| `id` | Unique kebab-case identifier |
| `title` | Human-readable display name |
| `type` | `surface`, `extension`, `full`, `library`, `workflow` |
| `tier` | `browser`, `starlark`, `sidecar` |
| `version` | Semver version string |

### Optional Fields

| Field | Description |
|-------|-------------|
| `description` | Short description |
| `icon` | Emoji for sidebar/menu display |
| `author` | Package author |
| `surfaces` | Array of surface entries with per-path access, title, layout (see below) |
| `route` | *(deprecated — use `surfaces`)* URL path for surfaces |
| `auth` | Default access level for all surfaces: `authenticated`, `public`, `admin` |
| `layout` | Default layout for all surfaces: `single`, `editor` |
| `permissions` | Array of required capabilities (`db.write`, `http`, `notifications`, `secrets`, `realtime.publish`) |
| `api_routes` | Array of `{"method": "GET", "path": "/items"}` |
| `db_tables` | Table definitions with columns and indexes |
| `settings` | User-configurable settings with type, label, description, default |
| `hooks` | Event bus subscription patterns |
| `exports` | Functions exported for cross-package calls via `lib.require()` |
| `depends` | Array of package IDs this package depends on |
| `slots` | Named UI injection points (host surfaces declare these) |
| `contributes` | Slot contributions this package injects into other surfaces |
| `capabilities` | Environment requirements: `{"required": [...], "optional": [...]}` |
| `schema_version` | Integer for additive schema migrations |
| `form_template` | Typed form template (v0.9.5). Fields array or fieldsets for progressive forms. Validated at install. |

## Multi-Surface Packages

A package can serve multiple pages, each with its own path, access level,
title, and layout. Declare a `surfaces` array in the manifest:

```json
{
  "id": "bug-tracker",
  "title": "Bug Tracker",
  "type": "full",
  "auth": "authenticated",
  "layout": "single",
  "surfaces": [
    { "path": "/",       "title": "Dashboard" },
    { "path": "/submit", "access": "public", "title": "Report a Bug" },
    { "path": "/:id",    "title": "Bug Detail", "nav": false },
    { "path": "/admin",  "access": "admin",  "title": "Settings", "nav": false }
  ]
}
```

### Surface Entry Fields

| Field    | Type   | Default        | Description |
|----------|--------|----------------|-------------|
| `path`   | string | **required**   | Relative to `/s/{pkg-id}`. Supports `:param` segments. |
| `access` | string | package `auth` | `public`, `authenticated`, `admin`, `group:{name}`. |
| `title`  | string | package `title`| Human label. Used in nav if `nav: true`. |
| `layout` | string | package `layout`| `single`, `editor`, or future layouts. |
| `nav`    | bool   | see rules      | Show in sidebar navigation. |

### Nav Visibility Rules

- `path: "/"` defaults to `nav: true` (primary entry point)
- All others default to `nav: false` (sub-pages)
- Explicit `"nav": true` overrides — a package can put multiple entries in nav
- The sidebar links to the first surface with `nav: true`

### Backward Compatibility

If `surfaces` is absent, the kernel synthesizes one entry from the legacy
`auth` and `layout` fields. No existing packages break.

### Client-Side Navigation

Within a multi-surface package, use `sw.navigate()` for SPA-style routing:

```js
const path = window.__SURFACE_PATH__ || '/';
const params = window.__SURFACE_PARAMS__ || {};

if (path === '/')           renderDashboard();
if (path === '/submit')     renderSubmitForm();
if (path === '/:id')        renderDetail(params.id);

// Navigate to another surface within the package
sw.navigate('/submit');
sw.navigate('/:id', { id: 'bug-42' });

// Listen for navigation events (including back/forward)
sw.on('surface.navigate', ({ path, params }) => {
    renderView(path, params);
});
```

## Composability: Slots and Contributions

Packages compose with each other through named UI injection points (**slots**) and **contributions**.

### Declaring Slots (Host Surfaces)

Surfaces declare slots where other extensions can inject UI:

```json
{
  "slots": {
    "toolbar-actions": {
      "description": "Toolbar action buttons",
      "context": {
        "noteId": "string — current note ID",
        "getContent": "function — returns note body text"
      }
    }
  }
}
```

Slot names are namespaced at runtime as `{package-id}:{slot-name}` (e.g., `notes:toolbar-actions`). The `context` field documents what data the slot provides — it is not enforced at runtime.

### Contributing to Slots

Extensions declare which slots they inject into:

```json
{
  "contributes": {
    "notes:toolbar-actions": {
      "label": "Dictate",
      "icon": "🎤",
      "description": "Voice-to-text dictation"
    }
  }
}
```

Contributing extensions can be installed before or after their host surface — the coupling is soft. The admin can view all slots and their contributors at `GET /api/v1/admin/slots`.

### Cross-Package Function Calls

Any package that declares `exports` can be called via `lib.require()`, not just library-type packages. The caller must declare the target in its `depends` array:

```json
{
  "depends": ["image-gen"],
  "permissions": ["api.http"]
}
```

## Package Lifecycle

1. **Install**: Upload a `.pkg` file via Admin > Packages or `POST /api/v1/admin/packages/install`. The kernel extracts the archive, creates database tables, and registers routes.

2. **Enable**: Activate the package so its routes, hooks, and UI become available. `PUT /api/v1/admin/packages/:id/enable`.

3. **Disable**: Deactivate without removing data. `PUT /api/v1/admin/packages/:id/disable`.

4. **Update**: Upload a new `.pkg` with a higher semver version. Schema changes must be additive (new columns/tables only). `POST /api/v1/admin/packages/:id/update`.

5. **Export**: Download the installed package as a `.pkg` archive. `GET /api/v1/admin/packages/:id/export`.

6. **Delete**: Remove the package and its data. `DELETE /api/v1/admin/packages/:id`.

## Bundled vs User-Installed

**Bundled packages** ship inside the Docker image at `/app/bundled-packages`. On first boot, a curated default set is auto-installed. Behavior:

- First boot: curated defaults are installed and enabled.
- Subsequent boots: already-installed packages are skipped.
- Admin uninstalls: the package stays uninstalled (never force-reinstalled).
- Control via `BUNDLED_PACKAGES` env var: empty = defaults, `"*"` = all, or comma-separated IDs.

**User-installed packages** are uploaded through the Admin UI or API. They follow the same lifecycle but are not tied to the Docker image.

## Building Packages

Packages are built by zipping the standard directories alongside `manifest.json`:

```bash
# Build all packages in the packages/ directory
cd packages && bash build.sh

# Build a single package
cd packages && bash build.sh my-package
```

Output goes to `dist/my-package.pkg`.

### Manual Build

```bash
cd packages/my-package
zip -r ../../dist/my-package.pkg manifest.json js/ css/ script.star
```

The build script automatically includes whichever standard directories exist: `js/`, `css/`, `assets/`, `script.star`, `star/`, `migrations/`.

### Custom Docker Image

To bundle custom packages into a Docker image:

1. Place your package in `packages/your-package/` with a `manifest.json`.
2. Run `cd packages && bash build.sh all`.
3. Build the image: `docker build -t my-armature .`

The Dockerfile builds all packages and copies them into the bundled packages directory.
