Lua · The edge of a card app

What a card app cannot do.

A card app gets a real language, so most of the old refusals are gone. Four remain, and each of them is the boundary between a file on a card and the firmware.

It cannot write to the card. io is not opened and there is no path anywhere in the API. State goes through snail.save, which is one kilobyte keyed on a hash of the app's filename. An engine that let a downloaded file name a path would make every app in the Store something you had to audit before installing.

It cannot draw pixels. You get the components — title, text, small, rows, rules, gaps, a sprite, a QR code, a hand of cards, a tile board, a composed trading card — and none of them takes a coordinate. That is what makes it impossible for a card app to paint over the header or land a line half off the panel. A hand-drawn gauge needs C++.

It cannot bind BACK, and it cannot run when it is not on screen. snail.tick(ms) steps an app on a clock of its own down to 600 ms, and that clock stops the moment the app closes. Nothing runs while the screen belongs to something else, and BACK always leaves.

It cannot touch hardware. No SPI, no GPIO, no radio beyond snail.fetch, no second network client of its own.

Which kind to write§

Write a card app for anything that reads, computes and draws with the components. You get it on the device in a minute with a text editor, you can fix it on the train, and you can send the file to someone else and they can run it.

Write a compiled app when it draws something of its own, writes files, drives hardware, or needs to run while the screen belongs to something else. You get the whole machine and you pay a reflash.

The launcher is not supposed to be able to tell you which kind an app is. Billy's Blackjack sits beside Weather with its own icon, and the only difference is that one of them you can edit with the card in a laptop.

The shape of a compiled app§

A compiled app is one folder in the firmware source with one header in it, plus one #include line. It implements five callbacks; the kernel draws the header, footer and status band, pops the navigation stack, and owns every piece of hardware — an app may not touch the display, SPI, or the network clients directly.

struct XtApp {
  const char* id;      // stable, <= 10 chars. NVS namespace, cache dir, log tag.
  const char* name;    // launcher label
  const char* icon;    // an ICON_* glyph
  const char* hint;    // footer text — the KERNEL draws this, you never do
  uint16_t    flags;

  void (*enter)(XtAppCtx&);            // called on open
  void (*leave)(XtAppCtx&);            // release buffers, close sockets
  bool (*key)(XtAppCtx&, XtKey);       // one button; return false to leave
  void (*draw)(XtAppCtx&, XtFrame&);   // paint CONTENT only
  void (*tick)(XtAppCtx&);             // optional
};
The whole lifecycle. enter opens, key handles one button, draw paints, leave cleans up.

Because it goes into the firmware image, a compiled app cannot be loaded at runtime — the SD card is not in the CPU's address space. Installing one means a new firmware build and a reflash.

What that means today§

Snail OS is a commercial product and the firmware source is not distributed, so a compiled app currently ships as part of a firmware release. If your idea needs one, write to the developer — reply to your receipt, or use the address on the main page. A card app needs nobody's permission: write the file, and it runs.