# App icon

Give an installed app an icon, a colour, a name, a version and a description with a small folder next to the app, sent over the cable or copied to the card.

## What it is

In Apps, the launcher, every app is one row: a round disc on the left and the name of the app. The disc shows the app's picture. Without one it shows the apps glyph on the app's colour, or on Medium Grey when the app gave no colour. The version is in the details of the app, which TOP held opens.

You change all of that with a folder named after the app. The folder holds a text file, `app.json`, and a picture. Nothing is rebuilt: the launcher reads the folder at its next scan.

The folder sets six things: `name`, `category`, `version`, `description`, `icon` and `color`. Only `kode`, the format revision, is required. A key you leave out takes its value from the next source, the app's manifest or the board's defaults, and never becomes empty. `internals/apps-region` covers how the board reads the folder, with the events and the memory.

You can put the folder in the file that installs the app, a `.kfw` container, or send it to an app that is already installed. Both ways end in the same place on the board: `/storage/.kode/appsdata/<project>/` on the internal FAT. With a card reader you can copy the same folder to `/.kode/appsdata/<project>/` on the microSD card.

## How it is connected

```mermaid
flowchart LR
  subgraph pc["On your computer"]
    dir["app.json<br/>+ icon.png"] -- "cable" --> put["kcli.py<br/>appdata put"]
    dir -- "container" --> kfw["mkkfw.py<br/>--appdata"]
  end
  subgraph board["On the Kode Dot"]
    am["AO_AppMgr<br/>install"]
    fat["FAT: /storage/.kode/<br/>appsdata/scope"]
    st["AO_Storage"]
    gui["AO_Gui<br/>the row"]
    am --> fat
    fat --> st --> gui
  end
  put --> fat
  kfw -- "install" --> am
  class dir app
  class am,st,gui ao
  class fat store
```

*How a folder becomes a row*

`<project>` is the **project name** of the app, the `project_name` that ESP-IDF writes into the `.bin` from the `project()` line of its `CMakeLists.txt`. It is the identity of the app, and it is not the name on the row. `kcli.py cmd app_list` and `kcli.py appdata ls` print it.

Two apps can share a display name, so the board finds the folder by the project name and nothing else.

The converter runs on the computer. The board decodes no PNG or JPEG, because decoding needs memory it does not have to spare.

`tools/kappdata.py` checks `app.json` against the limits below, cuts the picture to a square, scales it to 64 by 64 keeping its transparency, and writes the raw `.k64` file. It needs Pillow (`python3 -m pip install pillow`) in the Python that runs `kcli.py`, and only for a PNG or JPEG.

The board looks for the folder on the FAT first and on the card second, and uses the first one it finds as a whole. It never takes a missing value from the card's folder. The console writes only to the FAT, because a microSD card that fails on write breaks every later read on that volume.

## Specifications

### app.json

One flat JSON object, every value a string, ASCII only (`0x20` to `0x7E`), at most 1024 bytes. The only escapes are `\"` and `\\`. There are no numbers, arrays or nesting. The board refuses the whole file on any fault and never applies part of it.

| Key | Limit | Used for |
| --- | --- | --- |
| `kode` | Required, must be `"1"` | The format revision. Anything else is refused with `revision` |
| `name` | 31 characters | The row and the details |
| `category` | 63 characters, a comma list, lowercased on read | The folder the app is filed in: the first item, in the plural |
| `version` | 15 characters | The details of the app |
| `description` | 52 characters | The details of the app |
| `icon` | 40 characters, a relative path inside the folder, no leading `/`, no `\`, no `..` | The round disc. Without this key the board reads `icon.k64` in the folder. The host tools (`appdata put`, `mkkfw.py`) take the first file that exists of `icon.png`, `icon.jpg`, `icon.jpeg`, `icon.k64`, convert it and write the key as `icon.k64` |
| `color` | Exactly `#RRGGBB` | The ground of the disc when the app has no picture, in place of Medium Grey |

A key the reader does not know is ignored whatever its length, so a file from a newer tool works on an older board. A description of 53 characters makes the board refuse the whole file with `syntax`. The limit comes from the app sheet, and the parser enforces it.

| `json=` status | Meaning |
| --- | --- |
| `ok` | Parsed |
| `none` | No `app.json` in the folder |
| `size` | Longer than 1024 bytes |
| `syntax` | Outside the subset, or a field over its limit |
| `revision` | `kode` missing, or not `"1"` |

### The icon

| Thing | Value |
| --- | --- |
| File | `icon.k64`, exactly 12288 bytes, no header |
| Colour plane | 64 by 64 pixels of RGB565, little-endian, row after row: 8192 bytes |
| Alpha plane | 64 by 64 bytes of transparency: 4096 bytes |
| Transfer | The console sends it in 4096-byte parts, so exactly three |
| On screen | A row draws it at the disc's size, 80 px; the details draw it at 64 px |
| Any other length | Refused with `size` |
| No icon, or refused | The apps glyph on the app's colour. The launcher draws it itself. The console still calls this `value=monogram` |

`icon=` status in `appdata ls` and `status=` on the icon line of `appdata info`: `ok`, `none` (no picture), `size` (not a 64 by 64 `.k64`), `path` (the `icon` key names a file that is not there), `busy` (the board was in the middle of a transfer; it asks again at the next scan).

### Putting the folder on the board

| Way | Command | What happens |
| --- | --- | --- |
| In the container | `python3 tools/mkkfw.py --target app --name <project> --appdata <DIR> build/<project>.bin <project>.kfw`, then `python3 tools/kcli.py -p <port> install <project>.kfw --dst app` | The tool validates the JSON and converts the picture on the computer, naming the field and writing no file if one is wrong, and rewrites the `icon` key as `icon.k64`. On install the board replaces the FAT folder whole. A container with no folder inside removes the app's folder, so an update never leaves the old icon. A bare `.bin` leaves it as it is |
| Over the cable | `python3 tools/kcli.py -p <port> appdata put <project> <DIR>` | Validates, converts, creates the FAT folder, uploads `app.json` and `icon.k64` and prints what the board resolved. All in one session, because opening the port resets the board |
| With a card reader | Copy `app.json` and `icon.k64` to `/.kode/appsdata/<project>/` on the card, put the card in, open and close the settings screen or restart | The picture must already be `.k64`. `put` writes no `.k64` on your computer, so read it back with `kcli file-get` from `/storage/.kode/appsdata/<project>/` |

If the board cannot read the folder in a container it installs the app anyway and logs one warning.

### Checking and removing

| Command | Console verb | What it prints |
| --- | --- | --- |
| `kcli.py appdata ls` | `appdata_ls` | One record per project: `project=`, `medium=`, `json=`, `icon=`, `app=`. A folder with no app is `app=orphan` |
| `kcli.py appdata info <project>` | `appdata_info name=` | `project=... medium=fs|sd|none`, then one `field=` line per key: `name`, `category`, `version`, `description`, `icon`, `color` |
| `kcli.py appdata rm <project>` | `appdata_rm name=` | Deletes the FAT folder and its files. Leaves the card alone. Refuses a name that does not lead into `/storage/.kode/appsdata/` |

Each `field=` line has `src=`: `folder` (your `app.json`), `binary` (the app's manifest or build), `table` (the board's category override table) or `default` (no source had a value). The icon line says `value=icon` or `value=monogram` (no picture), and never the path.

When no source has a category, `appdata info` prints `other`, and the launcher files the app in no folder: it is a loose row at the root of Apps, under the folders. An app whose first category is `other` is a loose row too.

## Gotchas

> **WARNING**
>
> **`appdata info` says `medium=none`.** The board found no folder with that project name. Check the spelling against `appdata ls`: a folder named after the display name, or with a typo, shows there as `app=orphan`.

> **WARNING**
>
> **The row still shows the apps glyph.** Read `json=` and `icon=` in `appdata ls`. A `json=syntax` is almost always a description over 52 characters, a missing comma or an accent. An `icon=size` is a `.k64` that is not 12288 bytes. An `icon=path` is an `icon` key that names a missing file.

> **NOTE**
>
> **A category from the folder wins over the board's category table.** The table still applies to an app that has no folder.

> **NOTE**
>
> **The row changes while Apps is open, and your selection does not move.** The launcher asks again for every row at each scan, and a scan rebuilds the whole list. Leave Apps for the face and come back to force one.

## Example code

An `app.json` for an installed app whose project name is `scope`. The key `icon` is left out, so the host tool takes `icon.png` from the same folder.

```json
{
  "kode": "1",
  "name": "Scope",
  "category": "tools",
  "version": "0.4",
  "description": "Two-channel oscilloscope on the expansion header.",
  "color": "#8E8E93"
}
```

Put the folder on the board and check it. The first command validates, converts `icon.png` and uploads both files. The answer of the second shows `src=folder` on every field and `status=ok` on the icon line.

```sh
# app_icon.sh - 2026-10-28 - from Kode with love
#
# Sends the folder scope-folder (app.json and icon.png) to a Kode Dot for the
# installed app "scope", then asks the board what it resolved. Every field
# line of the answer ends with src=folder and the icon line with status=ok.
# Opening the port resets the board, so each command opens its own session.
python3 tools/kcli.py -p /dev/cu.usbmodem101 --settle 5 appdata put scope ./scope-folder
python3 tools/kcli.py -p /dev/cu.usbmodem101 --settle 5 appdata info scope
```

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

## In the SDK

- [`components/kdrv/apps/src/kapp_data.c`](https://github.com/kodediy/kode-sdk/blob/main/components/kdrv/apps/src/kapp_data.c): the `app.json` reader on the board.
- [`tools/kappdata.py`](https://github.com/kodediy/KodeOS/blob/main/tools/kappdata.py): validation and the `.k64` converter.
- [`tools/mkkfw.py`](https://github.com/kodediy/KodeOS/blob/main/tools/mkkfw.py): packs a folder into a `.kfw` container.
