frogConvert

Adding a mode

The checklist for shipping a new top-level surface, distilled from building Compress

Adding a mode

frogConvert has three top-level modes: Converter, PDF Editor, Compress. This is the playbook for adding a fourth, written down while building Compress (v3.0.0) so the next surface doesn't rediscover each step by shipping it broken first. Items marked ⚠ are the ones Compress actually got wrong along the way; they are where a reviewer should look hardest.

The shape of a mode

A mode is a module singleton, not a component instance: state lives at module scope, init<Name>Workspace() binds DOM, cleanup() drops DOM references but keeps state (mode switches must not lose work), resetAll() is the destructive cousin. PdfWorkspace and CompressWorkspace are the references. The module is lazy: main.ts imports it on first entry, so a visitor who never opens it never downloads it. Only the already-resolved module handle is used for cleanup() on exit.

The checklist

Core wiring

The parts that are easy to forget

Each of these is invisible in a demo and real in use. Compress missed several on the first pass:

Shared decisions belong in one module

Every one of these was a real bug, and every one of them had the same shape: a decision that looked local, copied into a second place, and then diverging.

Failure states, before polish

Design these first; they are the feature. Every async entry point must leave the surface somewhere actionable:

Accessibility

Verification bar

What "done" meant for Compress, and should mean for the next mode:

Documentation

The one-sentence version

A mode is done when a stranger can deep-link into it, drop the wrong file, lose their network, press Stop, restore yesterday's session, and hear it all through a screen reader - and at no point does the surface lie to them or strand them.

All documentation