/** * editor_storm.js — the editor's ONE storm selection. [Lane B, SPRINT15 gate 2.2] * * D scored the wildnight while looking at the southerly: B's SCORE IT and C's * WIND panel each carried a private `stormKey`, independently defaulted — the * exact two-harnesses-disagreeing failure this repo keeps paying for, arrived * inside a single page. This module is the fix: one storm selection, one storm * LIST, owned here; every panel that shows or uses a storm reads and writes * THIS and never a private copy. * * Deliberately not on A's `EDITOR.on` — emit is private to editor.js, and * widening A's seam for one dropdown is more contract than the problem needs. * B owns this file; C's panel subscribes (THREADS 2026-07-20, gate 2.2 * proposal). The default is the southerly — C's old default, kept on purpose: * the WIND panel is live the moment the page opens and needs a storm that * shows something, while SCORE IT is an explicit click behind a visible * dropdown. (A funnel authored under the gentle storm looks like it does * nothing — C's own comment.) * * No I/O here: this holds the NAME, not the storm def. Fetching defs stays * with the panels (loadStorm), which already cache. */ /** Keep in step with data/storms/ — THE list; panels must not carry their own. */ export const STORM_KEYS = [ 'storm_01_gentle', 'storm_02_wildnight', 'storm_02b_icenight', 'storm_03_southerly', 'storm_03b_earlybuster', ]; export const DEFAULT_STORM = 'storm_03_southerly'; let key = DEFAULT_STORM; const subs = new Set(); export const stormSel = { /** The currently selected storm key. */ get key() { return key; }, /** * Change the selection. No-op on the same key; throws on a stranger — a * typo'd storm silently becoming the default is setHardware's lesson again. * `source` is a free label ('score', 'wind', …) so a subscriber can skip * reacting to its own write. */ set(next, source = null) { if (next === key) return; if (!STORM_KEYS.includes(next)) { throw new Error(`unknown storm "${next}" — the editor knows: ${STORM_KEYS.join(', ')}`); } key = next; for (const fn of [...subs]) { try { fn(key, source); } catch (err) { console.error('[storm] subscriber threw:', err); } } }, /** Subscribe to changes; returns an unsubscribe function. */ onChange(fn) { subs.add(fn); return () => subs.delete(fn); }, STORM_KEYS, }; // For consoles and selftests — same object, not a copy. globalThis.EDITOR_STORM = stormSel;