editor_storm.js owns THE storm selection and THE storm list; SCORE IT's dropdown is a view of it (default = southerly, C's old default, kept on purpose). The score panel renders scoreSite's progress ticks — flight x/y, elapsed — and audit.html gets the same line. C's half: subscribe editor.wind.js per the THREADS proposal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
67 lines
2.5 KiB
JavaScript
67 lines
2.5 KiB
JavaScript
/**
|
|
* 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;
|