{"version":3,"sources":["optimistic.mjs"],"sourcesContent":["// OPTIMISTIC CHANGES, ONE RULE FOR ALL OF THEM (the owner, Oct 12: \"try to speed up speed perception so things appear\n// instantaneous and use rollbacks on the client if necessary\").\n//\n// The player's change is drawn the moment they make it; the server still decides. `apply` paints and returns its own\n// undo; `send` asks the server; a refusal runs the undo and says why. The server stays the only source of truth for\n// items and tokens: nothing here writes anything, it only decides what the screen shows while the answer is out.\n//\n// KEYED CHANGES. Two changes to the same thing (a switch flipped on and off again, a price typed twice) can be out at\n// once, and their answers can come back in either order. With a `key`:\n//   - a STALE CONFIRM never wins: an answer for an older change does not repaint over a newer one (its `confirm` is\n//     kept, and replayed only if every newer change is then refused, since its answer is then the server's state);\n//   - a refused change that a newer one has already replaced is not undone on screen (the newer one stands); instead\n//     the newer one inherits its undo, so if that is refused too, the screen goes back to what was there before both;\n//   - `once`: a second press while the first is out does nothing (no double-apply on a retry or a double click).\n//\n// `apply` must look the things it changes up FRESH and return an undo that does the same: a background refresh can\n// replace the model between the paint and the rollback (tests/optimistic-ui.test.mjs pins that trap for app.js).\n\n// The words a refusal is told in: the server's own sentence, unless it is a transport word that says nothing.\nconst GENERIC = /^(internal|INTERNAL|unavailable|deadline[- ]exceeded|Not ready|Failed to fetch|Load failed|NetworkError.*|network.?error)$/i;\nexport function refusalText(e, fallback) {\n  const m = e && typeof e.message === 'string' ? e.message.trim() : '';\n  if (m && !GENERIC.test(m)) return m;\n  return fallback || 'That change didn’t stick, so it was undone.';\n}\n\nexport function createOptimist({ say = () => {} } = {}) {\n  const live = new Map();   // key -> the changes to it, oldest first, until all of them have answered\n  let seq = 0;\n  const busy = (key) => { const l = live.get(key); return !!(l && l.some((o) => o.state === 'pending')); };\n  const tidy = (key, list) => { if (list && list.every((o) => o.state !== 'pending')) live.delete(key); };\n\n  async function run({ key = null, apply = null, send, confirm = null, failMsg = '', quiet = false, once = false } = {}) {\n    if (key != null && once && busy(key)) return { ok: false, busy: true };\n    const op = { id: ++seq, state: 'pending', undo: null, r: undefined, confirm };\n    const list = key == null ? null : (live.get(key) || live.set(key, []).get(key));\n    if (list) list.push(op);\n    try { op.undo = apply ? (apply() || null) : null; }\n    catch (e) { op.state = 'failed'; tidy(key, list); throw e; }\n    try {\n      const r = await send();\n      op.state = 'ok'; op.r = r;\n      const later = list ? list.slice(list.indexOf(op) + 1).filter((o) => o.state !== 'failed') : [];\n      const stale = later.length > 0;\n      if (!stale && confirm) { try { confirm(r); } catch (_) {} }\n      tidy(key, list);\n      return { ok: true, r, stale };\n    } catch (e) {\n      op.state = 'failed';\n      const at = list ? list.indexOf(op) : -1;\n      const heir = list ? list.slice(at + 1).find((o) => o.state !== 'failed') : null;\n      if (heir) {\n        heir.undo = op.undo;                     // the newer change stands; its undo now goes back past this one too\n        tidy(key, list);\n        return { ok: false, e, superseded: true };\n      }\n      try { if (op.undo) op.undo(); } catch (_) {}\n      // An older change the server took is the state now: its answer, held back as stale, is the one to show.\n      const prev = list ? list.slice(0, at).reverse().find((o) => o.state !== 'failed') : null;\n      if (prev && prev.state === 'ok' && prev.confirm) { try { prev.confirm(prev.r); } catch (_) {} }\n      // Told once: a newer change to the same thing already refused, and said so, covers this one too.\n      const told = list ? list.slice(at + 1).some((o) => o.said) : false;\n      if (!quiet && !told) { say(refusalText(e, failMsg)); op.said = true; }\n      tidy(key, list);\n      return { ok: false, e };\n    }\n  }\n  return { run, busy, get size() { return live.size; } };\n}\n"],"mappings":"AAmBA,MAAM,QAAU,8HACT,gBAAS,YAAY,EAAG,SAAU,CACvC,MAAM,EAAI,GAAK,OAAO,EAAE,SAAY,SAAW,EAAE,QAAQ,KAAK,EAAI,GAClE,OAAI,GAAK,CAAC,QAAQ,KAAK,CAAC,EAAU,EAC3B,UAAY,6CACrB,CAEO,gBAAS,eAAe,CAAE,IAAM,IAAM,CAAC,CAAE,EAAI,CAAC,EAAG,CACtD,MAAM,KAAO,IAAI,IACjB,IAAI,IAAM,EACV,MAAM,KAAQ,KAAQ,CAAE,MAAM,EAAI,KAAK,IAAI,GAAG,EAAG,MAAO,CAAC,EAAE,GAAK,EAAE,KAAM,GAAM,EAAE,QAAU,SAAS,EAAI,EACjG,KAAO,CAAC,IAAK,OAAS,CAAM,MAAQ,KAAK,MAAO,GAAM,EAAE,QAAU,SAAS,GAAG,KAAK,OAAO,GAAG,CAAG,EAEtG,eAAe,IAAI,CAAE,IAAM,KAAM,MAAQ,KAAM,KAAM,QAAU,KAAM,QAAU,GAAI,MAAQ,GAAO,KAAO,EAAM,EAAI,CAAC,EAAG,CACrH,GAAI,KAAO,MAAQ,MAAQ,KAAK,GAAG,EAAG,MAAO,CAAE,GAAI,GAAO,KAAM,EAAK,EACrE,MAAM,GAAK,CAAE,GAAI,EAAE,IAAK,MAAO,UAAW,KAAM,KAAM,EAAG,OAAW,OAAQ,EACtE,KAAO,KAAO,KAAO,KAAQ,KAAK,IAAI,GAAG,GAAK,KAAK,IAAI,IAAK,CAAC,CAAC,EAAE,IAAI,GAAG,EACzE,MAAM,KAAK,KAAK,EAAE,EACtB,GAAI,CAAE,GAAG,KAAO,OAAS,MAAM,GAAK,IAAc,OAC3C,EAAG,CAAE,SAAG,MAAQ,SAAU,KAAK,IAAK,IAAI,EAAS,CAAG,CAC3D,GAAI,CACF,MAAM,EAAI,MAAM,KAAK,EACrB,GAAG,MAAQ,KAAM,GAAG,EAAI,EAExB,MAAM,OADQ,KAAO,KAAK,MAAM,KAAK,QAAQ,EAAE,EAAI,CAAC,EAAE,OAAQ,GAAM,EAAE,QAAU,QAAQ,EAAI,CAAC,GACzE,OAAS,EAC7B,GAAI,CAAC,OAAS,QAAW,GAAI,CAAE,QAAQ,CAAC,CAAG,MAAY,CAAC,CACxD,YAAK,IAAK,IAAI,EACP,CAAE,GAAI,GAAM,EAAG,KAAM,CAC9B,OAAS,EAAG,CACV,GAAG,MAAQ,SACX,MAAM,GAAK,KAAO,KAAK,QAAQ,EAAE,EAAI,GAC/B,KAAO,KAAO,KAAK,MAAM,GAAK,CAAC,EAAE,KAAM,GAAM,EAAE,QAAU,QAAQ,EAAI,KAC3E,GAAI,KACF,YAAK,KAAO,GAAG,KACf,KAAK,IAAK,IAAI,EACP,CAAE,GAAI,GAAO,EAAG,WAAY,EAAK,EAE1C,GAAI,CAAM,GAAG,MAAM,GAAG,KAAK,CAAG,MAAY,CAAC,CAE3C,MAAM,KAAO,KAAO,KAAK,MAAM,EAAG,EAAE,EAAE,QAAQ,EAAE,KAAM,GAAM,EAAE,QAAU,QAAQ,EAAI,KACpF,GAAI,MAAQ,KAAK,QAAU,MAAQ,KAAK,QAAW,GAAI,CAAE,KAAK,QAAQ,KAAK,CAAC,CAAG,MAAY,CAAC,CAE5F,MAAM,KAAO,KAAO,KAAK,MAAM,GAAK,CAAC,EAAE,KAAM,GAAM,EAAE,IAAI,EAAI,GAC7D,MAAI,CAAC,OAAS,CAAC,OAAQ,IAAI,YAAY,EAAG,OAAO,CAAC,EAAG,GAAG,KAAO,IAC/D,KAAK,IAAK,IAAI,EACP,CAAE,GAAI,GAAO,CAAE,CACxB,CACF,CACA,MAAO,CAAE,IAAK,KAAM,IAAI,MAAO,CAAE,OAAO,KAAK,IAAM,CAAE,CACvD","names":[],"file":"optimistic.mjs"}