A gliding envelope as the hero with Officer Duck riding it, and a post-office
sorting desk for the splash.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The app icon is flattened onto black because iOS rejects an alpha channel, and
copying that straight over made every dock tile an opaque black square. The tile
wants the frame's own rounded edge with the corners cut away — which is what the
icon this replaces already had, and what tile: bare means.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The dock tile takes assets/icon.png when the plugin ships one and falls back to
the manifest's lucide glyph when it does not. This is the same icon the phone
shows, resized — one mark per product, wherever it appears.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Clicking "Add Account" on a new install did nothing at all — no modal, no
error, nothing in the console.
EmailList early-returns an onboarding empty state when there is no mail, and
that branch carries the "Add Account" button. But <AccountsModal /> was only
mounted in the OTHER return — the one that renders when mail exists. The button
flipped the shared EMAIL_ACCOUNTS_OPEN global and nothing was listening.
A catch-22, and it closed the only door in: the sole other trigger is in the
toolbar, which is also in the mail-exists branch. So an install with no account
could never reach the dialog that adds one, and the failure was silent in both
directions — nothing threw, and the state flag really did flip.
Mount it in both branches. The empty state is exactly when it is needed most.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
IMAP and Gmail sync into a per-account SQLite store, with the mail read and
composed in the browser.
moved whole from src/servers/sidecar/email (14 files), src/servers/api/email,
officer_db/src/email, and the Screens/Dashboard/Email tree. what changed on the
way out:
- credentials are encrypted. the column was plaintext jsonb holding an IMAP app
password that the SMTP transport read straight out of the row; it is now
AES-256-GCM under this plugin's own 'email' secret-store purpose, sealed and
opened in queries.ts alone so nothing above it can hold ciphertext by
accident. no plaintext fallback on read - a value that does not verify is an
error.
- the router derives its prefix from mountPrefix() instead of the literal
'/api/email', which was only correct for a first-party publisher.
- the frontend became panels.ts + layout.ts. the two panel appTypes that were
null are now declared, because the platform screen used to override them by
panel id and a plugin has no such hook.
- account management became a modal. it was a section in the platform's
Settings, which a plugin cannot contribute to, and a panel does not work
either: PluginScreen renders the workspace locked, so a panel outside the
default layout is unreachable. it follows Compose, which had already solved
this.
- 17 palette classes replaced with semantic tokens.
not verified: this has never run. it was switched off in the platform on
2026-08-13 and its table was never created, so nothing has exercised sync,
sending or the UI since. the first install is the first test.