samagotchi 0.168.0 → 0.169.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -1
  3. data/docs/architecture.md +1 -1
  4. data/docs/configuration.md +1 -1
  5. data/docs/guardrails.md +2 -2
  6. data/docs/hooks.md +2 -2
  7. data/docs/internals/block-renderers.md +1 -1
  8. data/docs/internals/bridge.md +16 -4
  9. data/docs/internals/inline-widgets.md +1 -1
  10. data/docs/internals/objectives.md +2 -1
  11. data/docs/internals/remote-sessions.md +1 -1
  12. data/docs/internals/web-frontend.md +62 -22
  13. data/docs/plugins.md +1 -1
  14. data/docs/releasing.md +10 -6
  15. data/docs/testing.md +12 -0
  16. data/lib/samagotchi/bridge.rb +12 -2
  17. data/lib/samagotchi/bundles/system/manifest.yml +1 -1
  18. data/lib/samagotchi/client_id.rb +3 -0
  19. data/lib/samagotchi/command_intake.rb +7 -7
  20. data/lib/samagotchi/compat.rb +14 -5
  21. data/lib/samagotchi/engine/objectives.rb +10 -1
  22. data/lib/samagotchi/guardrails/approvals.rb +8 -3
  23. data/lib/samagotchi/session_continue.rb +8 -11
  24. data/lib/samagotchi/session_manager/removal.rb +3 -3
  25. data/lib/samagotchi/session_stores.rb +79 -0
  26. data/lib/samagotchi/turn_stages/objectives.rb +6 -8
  27. data/lib/samagotchi/version.rb +1 -1
  28. data/lib/samagotchi/web/public/app.js +3 -1
  29. data/lib/samagotchi/web/public/block_watch.js +1 -1
  30. data/lib/samagotchi/web/public/cards_place.js +5 -1
  31. data/lib/samagotchi/web/public/index.html +58 -39
  32. data/lib/samagotchi/web/public/markdown_place.js +1 -1
  33. data/lib/samagotchi/web/public/question_cards.js +20 -6
  34. data/lib/samagotchi/web/public/stage_asking.js +283 -0
  35. data/lib/samagotchi/web/public/stage_frame.js +7 -11
  36. data/lib/samagotchi/web/public/stage_model.js +37 -11
  37. data/lib/samagotchi/web/public/stage_opened.js +28 -18
  38. data/lib/samagotchi/web/public/stage_slots.js +13 -89
  39. data/lib/samagotchi/web/public/stage_view.js +202 -160
  40. data/lib/samagotchi/web/public/stage_waiting.js +180 -0
  41. data/lib/samagotchi/web/public/stream_handlers.js +1 -1
  42. data/lib/samagotchi/web/public/turn_model.js +1 -1
  43. data/lib/samagotchi/web/session_show.rb +14 -4
  44. data/lib/samagotchi/worker.rb +3 -4
  45. metadata +4 -1
@@ -1,5 +1,5 @@
1
- // The stage view's live slots (stage_view.js): the headline, the asking
2
- // text, the "now" row, the trail and the chip's ticks, each drawn from
1
+ // The stage view's live slots (stage_view.js): the headline, the "now"
2
+ // row, the trail and the chip's ticks, each drawn from
3
3
  // liveSlots (stage_model.js) and patched only when what it shows changes
4
4
  // (+shown+), so an unchanged slot keeps its nodes: a selection in it
5
5
  // survives, and its animations never restart (the ticks' blink; the
@@ -15,29 +15,13 @@ export function node(tag, cls, parent = null) {
15
15
  return el;
16
16
  }
17
17
 
18
- // +headline+, +asking+, +now+, +trail+, +ticks+: the slots' elements.
19
- // +taskStop+: the page's stopper (task_stop.js), for the stop-task button
20
- // on a running task_wait; +gens()+ the running turn's gens (the button's
21
- // command, read at its click); +onChange+ redraws (the stage's
22
- // scheduleRefresh). +renderMarkdown(text)+: the asking text's HTML
23
- // (Promise<string|null>; null keeps the raw text: markdown off, a failed
24
- // read); +linkRefs(el, refs)+: the page's ref pass on the slot (a
25
- // plugin's +refs+ paired first, then the path:line ones).
26
- export function createStageSlots({
27
- headline, asking, now, trail, ticks, taskStop = null, gens, onChange,
28
- renderMarkdown = () => Promise.resolve(null), linkRefs = () => {},
29
- }) {
18
+ // +headline+, +now+, +trail+, +ticks+: the slots' elements. +taskStop+:
19
+ // the page's stopper (task_stop.js), for the stop-task button on a running
20
+ // task_wait; +gens()+ the running turn's gens (the button's command, read
21
+ // at its click); +onChange+ redraws (the stage's scheduleRefresh).
22
+ export function createStageSlots({ headline, now, trail, ticks, taskStop = null, gens, onChange }) {
30
23
  // What each slot shows, so an unchanged slot is left alone.
31
- const shown = { headline: null, now: null, trail: null, asking: null, askingRefs: "" };
32
- // The refs the asking text is paired with (its pending question's).
33
- let askingRefs = null;
34
- // The last asking text sent to render and its answer (in flight or
35
- // done, HTML only): the same question drawn again (after a reset, a
36
- // blank between) asks no second time.
37
- let rendering = { text: null, html: null };
38
- // The asking text shows in its final form (its markdown swapped in, or
39
- // a null render left the raw text): askingSettled.
40
- let askingFinal = false;
24
+ const shown = { headline: null, now: null, trail: null };
41
25
 
42
26
  function drawHeadline({ text, thinking }) {
43
27
  const key = `${thinking ? "t" : "n"}|${text}`;
@@ -55,61 +39,6 @@ export function createStageSlots({
55
39
  headline.classList.add("fresh");
56
40
  }
57
41
 
58
- // Patched only when the text changes (a late chunk to the closed gen
59
- // rewrites the same text): a selection in it survives. A new text reads
60
- // from its top: drawn raw at once, then swapped for its markdown when
61
- // that comes back while the slot still shows the same text (the text is
62
- // final: it shows only once the call that asks runs). +refs+: a plugin's
63
- // refs on it (the pending question's), paired on both draws; new refs on
64
- // the same text (a second question in the step) are paired in place.
65
- function drawAsking(text, refs = null) {
66
- asking.hidden = !text;
67
- const refsKey = text && Array.isArray(refs) && refs.length ? JSON.stringify(refs) : "";
68
- if (shown.asking === text) {
69
- if (shown.askingRefs === refsKey) return;
70
- shown.askingRefs = refsKey;
71
- askingRefs = refsKey ? refs : null;
72
- linkRefs(asking, askingRefs);
73
- return;
74
- }
75
- shown.asking = text;
76
- shown.askingRefs = refsKey;
77
- askingRefs = refsKey ? refs : null;
78
- asking.classList.remove("markdown", "md-compact");
79
- asking.textContent = text;
80
- asking.scrollTop = 0;
81
- askingFinal = false;
82
- if (!text) return;
83
- linkRefs(asking, askingRefs);
84
- if (rendering.text !== text) {
85
- let html;
86
- try { html = Promise.resolve(renderMarkdown(text)).catch(() => null); } catch (_) { html = Promise.resolve(null); }
87
- rendering = { text, html };
88
- }
89
- const pending = rendering;
90
- pending.html.then((html) => {
91
- // A null isn't kept: the next draw of this text asks again.
92
- if (!html && rendering === pending) rendering = { text: null, html: null };
93
- if (shown.asking !== text || askingFinal) return;
94
- // The raw text is final (markdown off, a failed read).
95
- if (!html) {
96
- askingFinal = true;
97
- onChange?.();
98
- return;
99
- }
100
- // Swapped once: a blank-then-same draw while in flight waits twice.
101
- if (asking.classList.contains("markdown")) return;
102
- // Sanitised on the server (POST /api/render), as an answer's HTML is.
103
- asking.innerHTML = html;
104
- asking.classList.add("markdown", "md-compact");
105
- asking.scrollTop = 0;
106
- linkRefs(asking, askingRefs);
107
- askingFinal = true;
108
- // Its height changed: the stage refits.
109
- onChange?.();
110
- });
111
- }
112
-
113
42
  function drawNow(tool, quiet) {
114
43
  // A task_wait's stop button, as the row's (task_stop.js): in the key
115
44
  // with its in-flight state, so a redraw keeps a clicked one disabled.
@@ -177,17 +106,12 @@ export function createStageSlots({
177
106
  while (ticks.children.length > list.length) ticks.lastChild.remove();
178
107
  }
179
108
 
180
- // A new turn (or none): the slots empty, the asking text hidden, the next
181
- // draw of each slot a fresh one.
109
+ // A new turn (or none): the slots empty, the next draw of each slot a
110
+ // fresh one.
182
111
  function reset() {
183
- for (const slot of [headline, asking, now, trail, ticks]) slot.replaceChildren();
184
- asking.hidden = true;
185
- asking.classList.remove("markdown", "md-compact");
186
- shown.headline = shown.now = shown.trail = shown.asking = null;
187
- shown.askingRefs = "";
188
- askingRefs = null;
189
- askingFinal = false;
112
+ for (const slot of [headline, now, trail, ticks]) slot.replaceChildren();
113
+ shown.headline = shown.now = shown.trail = null;
190
114
  }
191
115
 
192
- return { drawHeadline, drawAsking, drawNow, drawTrail, drawTicks, reset, askingSettled: () => askingFinal };
116
+ return { drawHeadline, drawNow, drawTrail, drawTicks, reset };
193
117
  }