# Apps region

How kodeOS stores installed apps in flash: the region, the mailbox, install and eviction, and the app data folder with the icon and colour of each app.

## What it is

The apps region is the part of the flash where kodeOS keeps the apps you install. It is the `apps` partition, `0xC40000`, 11.75 MB, and it holds up to 64 apps. Each app is a complete firmware image. The bootloader starts one in place, without copying it, so installing an app is writing it once and starting it is a reset.

Three things sit around the region.

- The **mailbox** (`app_pending`, 6 MB at `0x1A00000`) is where a computer leaves an image with one `esptool` command.
- The **boot request** is the sector at the start of the region that says which app starts next (`internals/boot-model`).
- The **app data folder** is a small folder on the FAT volume or the microSD card that gives an app its icon, colour, version and description without rebuilding it.

`sdk/kdrv/apps` has the byte formats: the directory, the request, the `.kfw` container and the manifest. This page is how kodeOS uses them. AO_AppMgr owns the content of the region and the mailbox (`KBSP_RES_APPS_REGION`, `KBSP_RES_APP_PENDING`). AO_Boot owns the boot request sector (`KBSP_RES_APPS_BOOTREQ`). AO_Storage reads the app data folder.

## How it is connected

```mermaid
flowchart LR
  host["Computer<br/>esptool"]
  subgraph board["On the Kode Dot"]
    src["Launcher, console,<br/>URL"]
    mb["app_pending"]
    am["AO_AppMgr"]
    reg["apps region"]
    boot["AO_Boot<br/>boot request"]
    bl["Bootloader<br/>override"]
    fat["FAT<br/>.kode/appsdata"]
    mb -- "adopt" --> am
    src -- "install" --> am
    am -- "write" --> reg
    am -- "request" --> boot
    boot --> bl
    bl -- "jump" --> reg
    am --> fat
  end
  host -- "write-flash" --> mb
  class host ext
  class mb,reg,fat store
  class am,boot ao
```

*How an image reaches the region and starts*

### How an install behaves

**An install works by name.** The identity of an app is the `project_name` in its image header. An install with the name of an installed app replaces it in one directory commit, so a second install does not use a second entry. A new name is added. The installs that come from a file, the card, a URL, the console or the mailbox all end in the same code in AO_AppMgr.

**The allocator is next-fit and the eviction is first in, first out.** `kdrv_apps_plan()` puts a new image right after the newest one by install order, searching to the end of the region and then wrapping to the start. When nothing fits, the old copy of the app being replaced goes first, then the oldest apps until the image fits. So a new version of an installed app costs no other app unless it is larger than the room its old copy leaves. kodeOS keeps no record of which app you used last, so it has nothing else to rank by.

When all 64 directory entries hold other names, the oldest entry goes too. AO_AppMgr logs each eviction, `evicting '<name>' to make room; its copy is on the card` as a notice or `evicting '<name>' to make room; no copy kept` as a warning, publishes `APP_EVICTED_SIG` for it, and commits the directory, with the victims' flags cleared, before it touches the flash. The old copy of the app being replaced is not an eviction: the log says `the old copy of the app being replaced makes room for its new version`.

**An eviction keeps a copy on the card when it can.** If an install from a file, the card, a URL or the mailbox must evict, AO_AppMgr first copies each victim to `/sdcard/<project>.bin` (skipping one that is already there) and then restarts the install. The victim shows in the launcher as a file you can install again. If a copy fails and the install came from the card, the install fails and nothing is evicted. A terminal install cannot pause for this, so its victims are evicted without a copy.

**The write is paced.** AO_AppMgr writes an image in 32 KB chunks, spaced by a 10 ms gap, so the lower priorities (the screen included) get the CPU between erases. A write holds the cache down while it erases, and nothing in flash runs then.

**Adoption copies the mailbox into the region at boot.** In its initial transition AO_AppMgr asks whether the mailbox holds something worth copying. If it does, it posts `APP_ADOPT_SIG` to itself, because a copy of several megabytes must not run inside an initial transition. The copy goes through a static 4 KB buffer.

**The app data folder is read by the owner of the media.** AO_Storage finds the folder, reads `app.json` and the icon and posts the answer to kgui as events. A row in the launcher resolves each field on its own. For each one it takes the first source that has a value.

## Specifications

### The region

| Thing | Value |
| --- | --- |
| Partition `apps` | Data, subtype `0x40`, `0xC40000`, size `0xBC0000` (11.75 MB), up to the FAT at `0x1800000` |
| Partition `app_pending` | Data, subtype `0x41`, `0x1A00000`, 6 MB, non-executable |
| Directory copies | A at region offset `0x0000`, B at `0x1000`, one 4 KB sector each |
| Boot request | Region offset `0x2000`, 20 bytes in a 4 KB sector |
| First image | Region offset `0x10000` (`KAPPS_DATA_OFF`); every image starts on a 64 KB boundary (`KAPPS_ALIGN`) |
| Apps | Up to 64 (`KAPPS_MAX_ENTRIES`); the launcher lists up to 64 of each kind (`KODE_APP_LIST_MAX`) |
| Largest install | The data area: 11.75 MB less the 64 KB in front of it |
| Largest mailbox push | `0x5FF000` bytes: 6 MB less the sector that holds the intent word |
| Eviction | The old copy of the app being replaced first, then the oldest install, by `seq` |
| Directory commit | Writes the copy not in use with `gen` plus 1 and a new CRC, then swaps. A power cut mid-commit leaves the old directory |

The region ends where the FAT begins and crosses the 16 MB mark on the way. That is legal because the cache maps 32-bit addresses (`CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH`). To grow the region, space has to come from `storage`.

### Adoption of the mailbox

AO_AppMgr consumes the mailbox (it erases the first sector and the intent sector) only when the outcome is final, so a failed directory write is retried at the next boot.

| Case | What AO_AppMgr does |
| --- | --- |
| The image fails `esp_image_verify()` in the region | The result repeats every time, so it consumes the mailbox and does not retry |
| The intent sector holds `KOTA` | No directory is involved. It checks the image fits the free kodeOS slot, writes it there, consumes the mailbox before the reset, then asks AO_Boot to switch slot |
| A plain app | One install by name in one commit, evicting the oldest app first if the 64 entries are full. It consumes the mailbox after the commit, then launches the app with a sticky request (`KBOOT_RUN_STICKY`, state `ALWAYS`). This is the only place that state is written |

`install src="pending" dst=os|app` runs the same adoption with the destination named by the command. For `dst=app`, `run=on` writes a `NEW` request, never `ALWAYS`.

### Verification

| Check | When | Where | What it checks |
| --- | --- | --- | --- |
| `kdrv_apps_pending_present()` | At boot, before the copy | The mailbox | The length only: the segment headers of a bare image, or the header of a `.kfw` container. It catches a torn upload. No hash |
| `kdrv_apps_pending_verify()` | Only when the install must evict an installed app | The mailbox | The full `esp_image_verify()`, so a broken upload costs nobody an app |
| `esp_image_verify()` in `kdrv_apps_write_end()` | After the copy | The region | The full check with the hash. An app starts only after this passes |

### The app data folder

| Thing | Value |
| --- | --- |
| Path on the FAT | `/storage/.kode/appsdata/<project>/` |
| Path on the card | `/sdcard/.kode/appsdata/<project>/` |
| `<project>` | The `project_name` of the app, up to 31 characters, from the characters `A-Z a-z 0-9 _ - .` only. Any other name has no folder |
| Which medium wins | The FAT first, then the card. The first folder found is used whole; fields are never merged across media |
| Files | `app.json` (at most 1024 bytes) and the icon `icon.k64` (exactly 12288 bytes) |
| Cost on the FAT | 20 KB of 4 KB clusters for a folder with an icon: one for the folder, one for `app.json`, three for the icon |
| Console writes | To the FAT only. A microSD card shipped with some units fails on write, and one failed write breaks every later read on that volume |
| Icon format | 64 by 64 RGB565 little-endian (8192 bytes), then a 64 by 64 alpha plane (4096 bytes). No header. A row draws it at the 80 px disc; the details draw it at 64 px |
| Decoding | The board decodes no PNG or JPEG. `tools/kappdata.py` converts on the computer |

`app.json` is one flat object of string values. These are its keys:

| Key | Limit |
| --- | --- |
| `kode` | Required, `"1"` |
| `name` | 31 characters |
| `category` | 63 characters, a comma list lowercased on read |
| `version` | 15 characters |
| `description` | 52 characters, in the details of the app |
| `icon` | 40 characters, a relative path with no `..` |
| `color` | `#RRGGBB` |

Unknown keys are ignored. The whole file is refused with `size`, `syntax` or `revision` on any fault, and the launcher then draws the app as if it had no folder. `internals/app-icon` has the steps.

Who writes the folder, and what an install does to it:

| What is installed | The folder |
| --- | --- |
| A `.kfw` container with an app data block (`KFW_FLAG_APPDATA`) | Replaced whole by the block's `app.json` and icon |
| A container without a block | Deleted |
| A bare `.bin` image (it starts with `0xE9`) | Left as it is |

The install ends only after AO_Storage has answered, so a launch never runs before the folder exists. A write that fails is one warning and the install still succeeds. An erased app takes its folder with it. An install never touches a folder on the card.

Which source gives each field in the launcher:

| Field | 1st | 2nd | 3rd | Default |
| --- | --- | --- | --- | --- |
| Name | Folder | Image manifest | | Project name |
| Category | Folder | `app_cat` override table | Image manifest | Empty (the app is a loose row at the root of Apps) |
| Version | Folder | The image's `esp_app_desc_t.version` (installed apps only) | | Empty |
| Description | Folder | | | Empty |
| Icon | Folder `icon` path | Folder `icon.k64` | | The apps glyph on the app's colour (the console calls it `monogram`) |
| Colour | Folder | | | Medium Grey |

The identity of a row is always the project name. The folder changes only its label.

The answer to a request for one folder is three posted events, in this order:

1. `APPDATA_FIELDS_SIG` with every field and two statuses (`json_status`: `ok`, `none`, `size`, `syntax`, `revision`; `icon_status`: `ok`, `none`, `size`, `path`, `busy`).
2. Zero or three `APPDATA_ICON_SIG` blocks of 4096 bytes each.
3. `APPDATA_END_SIG`.

AO_Storage takes the three icon blocks before it posts anything, so an icon arrives complete or not at all. While a console upload holds the jumbo pool, the fields event still goes out with `icon_status=busy` and kgui asks again at the next scan.

kgui keeps one 12 KB PSRAM buffer for each of its 128 list entries (64 installed apps and 64 files), about 1.5 MB, taken once.

### Console

| Command | What it does |
| --- | --- |
| `install src="terminal" dst=app run=on name="<project>"` | Streams a `.bin` or `.kfw` over the console into the region (`kcli.py install FILE --dst app`). `name` is the image's project name, which the board cannot read before the room is made; kcli and kble send it. Each app the install evicts prints `>\|evicted name="<project>" kept=off` before `>\|ready` |
| `install src="pending" dst=app` | Adopts the mailbox now |
| `app_list`, `app_run name="<project>"`, `app_erase name="<project>"` | List, start with a `NEW` request, erase |
| `appdata_ls`, `appdata_info name=`, `appdata_rm name=` | List every folder and orphan, show what the board resolved for one app, delete the FAT folder of one app |
| `kcli.py appdata put NAME DIR` | Validate on the computer, convert the icon, create the folder and upload it in one session |

## Gotchas

> **WARNING**
>
> **An app disappeared after you installed another one.** The region was full or had 64 entries, and the oldest install was evicted. With a card in the slot, an install from a file, a URL or the mailbox copies each evicted app to `/sdcard/<project>.bin` first, and the launcher lists it again as a file. A console install makes no copy. A new version of an installed app evicts nothing while it fits the room of its old copy; a console install finds the old copy only when it carries `name=`.

> **WARNING**
>
> **A mailbox push fails on `esptool` itself, or the image is discarded at boot.** The mailbox takes at most `0x5FF000` bytes, and for a kodeOS update the image must also fit the 4 MB slot. A larger app goes through `install dst=app` over the console, whose ceiling is the region.

> **NOTE**
>
> **The icon on the card does not show.** The board looks at the FAT first. As long as a folder exists there, the card's folder is hidden, even if the FAT folder has no icon. Delete the FAT folder with `appdata_rm` and the card's shows again at the next scan.

> **NOTE**
>
> **After you change kode, kaos or kdrv, installed apps still run the old code.** An app is a whole firmware and carries its own copy of everything under `kode.h`. Rebuild and install the apps that depend on the change.

## Example code

Install an app over the cable, start it and read the board's list. Each command prints what it did.

1. Install and start: `python3 tools/kcli.py -p <port> --settle 5 install build/my_app.bin --dst app`. The tool prints `installed and booting` and the app starts.
2. Hold BOT and LEFT for about one second to go back to kodeOS. Apps opens with the app in focus, in its folder if it names a category.
3. List the region: `python3 tools/kcli.py -p <port> --settle 5 cmd app_list`. One record per installed app comes back, then `ok`.
4. Start it from the console: `python3 tools/kcli.py -p <port> --settle 5 cmd 'app_run name="my_app"'`. The name is the `project()` name of the build.

**TBD**: these steps have not been run on a Kode Dot from this page.

## In the SDK

- [`components/kdrv/apps`](https://github.com/kodediy/kode-sdk/tree/main/components/kdrv/apps): the region driver, `kapps_fmt.h`, the container decoder and the `app.json` reader.
- [`components/kaos/appmgr/src/appmgr_ao.c`](https://github.com/kodediy/kode-sdk/blob/main/components/kaos/appmgr/src/appmgr_ao.c): install, adoption, eviction and keeping a copy on the card.
- [`tools/kappdata.py`](https://github.com/kodediy/KodeOS/blob/main/tools/kappdata.py): the host side of the app data folder.
