# Store previews and icons
The plugin store shows each plugin's page with a live preview: your plugin's
real UI half, run by the viewer in a sandbox, drawing every surface it
declares on a strip of tiles. A large surface (a stage, a pane, a column, a
popover) can be expanded to the size of the store's window. The person
browsing can click a tile and use it with the keyboard and pointer before
installing.
What the store does without any work from you:
- It shows each surface your manifest declares, at the size the store gives
it, and sends the plugin the usual `resize` and `visibility` events.
- It runs each command whose `opens` names a surface that must be opened
(a stage, a popover, a pane or a column), so the surface starts as it
would when a person opens it. Give every such command an `opens`.
- For a surface the viewer makes one of per pane, machine or project
(`pane.footer`, `machine.after`, `project.after`, ...), it shows one
instance, for the first pane, machine or project of a sample account.
What the sandbox gives your plugin: a sample account of two machines, two
projects and three panes (one agent working, one waiting); values,
secrets, claims and configuration that start empty and vanish with the
preview; calls that answer `unavailable` (your daemon half never runs);
and no URL opener. When your manifest asks for `events.on:system.agents.*`,
an agent pane finishes or starts a turn every few seconds.
## Your plugin's part: its own preview state
A plugin whose surfaces show data from its daemon, a service or the
account would show only "Loading…" or "No data" in that sandbox. **Each
plugin is responsible for a preview that shows what it really looks like,
and for keeping it current as the plugin changes.**
The store sets the value `<your id>.standard-preview` to `true` before your
plugin activates. It is in your own namespace, so reading it needs no
grant. When it is true:
1. Build your state from sample data instead of reading it: realistic
values that show every part of every surface (a full card, a warning
state, a long name that truncates).
2. Make no calls and start no requests: they answer `unavailable`.
3. Keep animating as usual, so the preview moves as the plugin does.
```rust
const PREVIEW: &str = "my-plugin.standard-preview";
fn activate(&mut self, cx: &mut Context) {
let preview = cx.values().get::<bool>(PREVIEW).ok().flatten() == Some(true);
if preview {
self.state = State::sample();
return;
}
// ... the real start: read values, call the daemon, watch the account
}
```
Keep `State::sample()` beside your real state and update it in the same
change as any new field or surface, so the preview never falls behind. A
unit test that activates the plugin with the preview value set and checks
that it paints without a call is the cheapest way to keep it honest:
```rust
let mut host = MockHost::new("my-plugin");
host.values.insert("my-plugin.standard-preview".into(), "true".into());
```
The preview is still your plugin's real code drawing: only the data is
yours to choose. Show what a person will see, not a better version of it.
## Your plugin's icon
The store shows an icon beside your plugin's name, drawn by terminals that
support Kitty graphics (others show a tile with your plugin's initials).
For a first-party plugin, put a square 256Ă—256 PNG of at most 64 KiB in the
plugin's directory as `icon.png`: the release carries it in the package's
metadata and the registry serves it to the store. Make it show what your
plugin draws, readable at 32 px, with no text.