Competitor wrappers all suffer the same lifecycle bug: calling `shutdown()` and then `boot()` again leaves the widget in a broken state. We need a deterministic state machine.
Evidence
Contract — state machine
`idle → loading → ready → shutdown → idle`
- Listener registry lives at adapter level, not in the client's React/Vue closure. Survives shutdown; re-binds on next boot.
- `shutdown()` actually removes: script tag, window globals, CSS injected, iframe, registered listeners.
- `reconfigure(newOpts)` = `shutdown() + load(newOpts)` atomically; no intermediate `idle` observable.
- `boot()` is idempotent & singleton-by-config-hash. Called twice with same opts ⇒ returns existing handle. Called with different opts ⇒ clean teardown first.
- `destroy()` is the hard kill (removes all state); `shutdown()` keeps the user session data.
Competitor wrappers all suffer the same lifecycle bug: calling `shutdown()` and then `boot()` again leaves the widget in a broken state. We need a deterministic state machine.
Evidence
Contract — state machine
`idle → loading → ready → shutdown → idle`