samagotchi 0.149.0 → 0.151.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -1
- data/README.md +4 -2
- data/docs/architecture.md +4 -4
- data/docs/bundle-authoring.md +1 -1
- data/docs/cli.md +59 -24
- data/docs/configuration.md +6 -6
- data/docs/internals/background-tasks.md +1 -1
- data/docs/internals/block-renderers.md +96 -0
- data/docs/internals/bridge.md +53 -9
- data/docs/internals/bundles.md +43 -14
- data/docs/internals/objectives.md +7 -2
- data/docs/internals/web-frontend.md +2 -2
- data/docs/internals/web-push.md +2 -2
- data/docs/internals/working-set.md +5 -2
- data/docs/memory.md +48 -10
- data/docs/plugins.md +49 -3
- data/docs/sessions.md +5 -4
- data/docs/testing.md +5 -0
- data/lib/samagotchi/atomic_file.rb +3 -0
- data/lib/samagotchi/block_renderers.rb +115 -0
- data/lib/samagotchi/bootstrap_command.rb +5 -2
- data/lib/samagotchi/bridge/card_places.rb +93 -0
- data/lib/samagotchi/bridge/card_store.rb +224 -82
- data/lib/samagotchi/bridge.rb +12 -4
- data/lib/samagotchi/bundle_command/try/trace.rb +1 -1
- data/lib/samagotchi/bundle_command.rb +91 -68
- data/lib/samagotchi/bundler_env.rb +24 -0
- data/lib/samagotchi/bundles/dev/manifest.yml +2 -2
- data/lib/samagotchi/bundles/github-pr/manifest.yml +3 -3
- data/lib/samagotchi/bundles/github-pr/plugin.rb +2 -1
- data/lib/samagotchi/bundles/known-names/hooks/known_names.rb +3 -1
- data/lib/samagotchi/bundles/known-names/manifest.yml +3 -2
- data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +2 -2
- data/lib/samagotchi/bundles/source-links/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/memory_guide.md +5 -5
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/chi_home.rb +4 -2
- data/lib/samagotchi/chi_shim.rb +2 -1
- data/lib/samagotchi/child_env.rb +10 -3
- data/lib/samagotchi/engine.rb +19 -1
- data/lib/samagotchi/git_env.rb +43 -0
- data/lib/samagotchi/guardrails/context.rb +2 -0
- data/lib/samagotchi/guardrails/targets.rb +4 -1
- data/lib/samagotchi/memory_bundle/asset_installer.rb +23 -3
- data/lib/samagotchi/memory_bundle/builder.rb +5 -3
- data/lib/samagotchi/memory_bundle/file_standing.rb +167 -0
- data/lib/samagotchi/memory_bundle/installed_bundle.rb +70 -6
- data/lib/samagotchi/memory_bundle/installed_copy.rb +1 -0
- data/lib/samagotchi/memory_bundle/installer.rb +162 -52
- data/lib/samagotchi/memory_bundle/listing.rb +4 -3
- data/lib/samagotchi/memory_bundle/manifest.rb +23 -2
- data/lib/samagotchi/memory_bundle/merger.rb +209 -8
- data/lib/samagotchi/memory_bundle/placeholder.rb +2 -1
- data/lib/samagotchi/memory_bundle/profile.rb +7 -3
- data/lib/samagotchi/memory_bundle/provenance.rb +182 -22
- data/lib/samagotchi/memory_bundle/remerge.rb +167 -0
- data/lib/samagotchi/memory_bundle/shipped_history.rb +79 -0
- data/lib/samagotchi/memory_bundle/shipped_update.rb +89 -24
- data/lib/samagotchi/memory_bundle/source.rb +3 -3
- data/lib/samagotchi/memory_bundle/status.rb +21 -3
- data/lib/samagotchi/memory_bundle/system_bundle.rb +98 -33
- data/lib/samagotchi/memory_bundle/text_merge.rb +399 -0
- data/lib/samagotchi/memory_bundle/trash.rb +58 -2
- data/lib/samagotchi/memory_bundle/uninstaller.rb +26 -6
- data/lib/samagotchi/objectives_check.rb +37 -7
- data/lib/samagotchi/project_env.rb +3 -2
- data/lib/samagotchi/self_report.rb +2 -0
- data/lib/samagotchi/session_chain.rb +2 -7
- data/lib/samagotchi/session_commands.rb +15 -9
- data/lib/samagotchi/session_manager.rb +10 -7
- data/lib/samagotchi/steer.rb +17 -0
- data/lib/samagotchi/terminal_ui.rb +4 -4
- data/lib/samagotchi/testing/boundary.rb +1 -0
- data/lib/samagotchi/testing/minitest.rb +3 -0
- data/lib/samagotchi/testing/rspec.rb +3 -0
- data/lib/samagotchi/testing/sandbox.rb +3 -1
- data/lib/samagotchi/tools/delegate.rb +2 -1
- data/lib/samagotchi/tools/task_runtime.rb +13 -10
- data/lib/samagotchi/turn_checkpoint.rb +11 -5
- data/lib/samagotchi/turn_flow.rb +2 -1
- data/lib/samagotchi/update_command.rb +76 -28
- data/lib/samagotchi/update_hint.rb +4 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +22 -12
- data/lib/samagotchi/web/auto_update.rb +8 -8
- data/lib/samagotchi/web/auto_update_record.rb +6 -4
- data/lib/samagotchi/web/block_renderer_routes.rb +32 -0
- data/lib/samagotchi/web/markdown_renderer.rb +39 -6
- data/lib/samagotchi/web/message_display.rb +3 -3
- data/lib/samagotchi/web/public/app.js +7 -3
- data/lib/samagotchi/web/public/block_frame.js +202 -0
- data/lib/samagotchi/web/public/block_host.js +502 -0
- data/lib/samagotchi/web/public/block_open.js +13 -0
- data/lib/samagotchi/web/public/block_renderers.css +20 -0
- data/lib/samagotchi/web/public/block_source.js +37 -0
- data/lib/samagotchi/web/public/bubbles.js +11 -5
- data/lib/samagotchi/web/public/card.js +17 -10
- data/lib/samagotchi/web/public/cards_place.js +10 -5
- data/lib/samagotchi/web/public/claim_link.js +4 -2
- data/lib/samagotchi/web/public/details_motion.js +2 -1
- data/lib/samagotchi/web/public/history_reveal.js +4 -1
- data/lib/samagotchi/web/public/index.html +20 -14
- data/lib/samagotchi/web/public/markdown_place.js +2 -3
- data/lib/samagotchi/web/public/stage_model.js +5 -6
- data/lib/samagotchi/web/public/stage_view.js +8 -7
- data/lib/samagotchi/web/public/text_roots.js +6 -0
- data/lib/samagotchi/web/public/turn_block.js +34 -19
- data/lib/samagotchi/web/public/turn_html.js +4 -1
- data/lib/samagotchi/web/public/turn_model.js +1 -1
- data/lib/samagotchi/web/public/update_sheet.js +8 -3
- data/lib/samagotchi/web/public/working_set_view.js +10 -7
- data/lib/samagotchi/web/push/notifier.rb +7 -9
- data/lib/samagotchi/web/session_hub.rb +2 -1
- data/lib/samagotchi/web/session_show.rb +1 -1
- data/lib/samagotchi/web/update_ops.rb +2 -1
- data/lib/samagotchi/worker.rb +4 -2
- metadata +17 -1
|
@@ -46,7 +46,7 @@ export function turnAnswerEl(turnEls) {
|
|
|
46
46
|
// (bubbles.js createBubbles)
|
|
47
47
|
// @param followWokenWorker () follows the worker a card's command woke
|
|
48
48
|
// @param api {getSession, sendCommand} (data.js)
|
|
49
|
-
// @return {cardElement(id), upsertCard(card, {before}), placeSnapshotCards(entries),
|
|
49
|
+
// @return {cardElement(id), upsertCard(card, {before}), placeSnapshotCards(entries, {before}),
|
|
50
50
|
// refreshCardBodies(), onCardAction(event)}
|
|
51
51
|
const HELD_UNTIL_ANSWERED = /^\/objectives --card /;
|
|
52
52
|
|
|
@@ -63,9 +63,12 @@ export function createCardsPlace({
|
|
|
63
63
|
// running turn's card as a row of its step; else before +before+ or at the
|
|
64
64
|
// end of the history). A running turn's card with actions asks the user
|
|
65
65
|
// something (check-in's Nudge / Keep going / Stop): it goes under the turn's
|
|
66
|
-
// block, not into a step that collapses when the next one starts.
|
|
66
|
+
// block, not into a step that collapses when the next one starts. A turn
|
|
67
|
+
// still sliding away arrives first: its list of pieces holds this card's
|
|
68
|
+
// element, and an update may swap that (drawCard, a notice to a card).
|
|
67
69
|
function upsertCard(card, { before = null } = {}) {
|
|
68
70
|
if (!card || !card.id) return;
|
|
71
|
+
turnDom.settleFold?.();
|
|
69
72
|
const existing = cardElement(card.id);
|
|
70
73
|
if (existing) {
|
|
71
74
|
const drawn = drawCard(existing, card);
|
|
@@ -81,11 +84,13 @@ export function createCardsPlace({
|
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
// A snapshot's cards and notices, each above the first turn after it; a
|
|
84
|
-
// turn's own notice where it showed live (placeTurnNotice).
|
|
85
|
-
|
|
87
|
+
// turn's own notice where it showed live (placeTurnNotice). +end+: what
|
|
88
|
+
// closes the history below them (a failed turn's prompt), for those with
|
|
89
|
+
// no turn after them; one shown after that prompt went back stays below it.
|
|
90
|
+
function placeSnapshotCards(entries, { before: end = null } = {}) {
|
|
86
91
|
const users = turnPromptBubbles(historyEl);
|
|
87
92
|
for (const entry of entries) {
|
|
88
|
-
const before = cardPlace(users, entry);
|
|
93
|
+
const before = cardPlace(users, entry) || (entry.after_failed ? null : end);
|
|
89
94
|
if (isCard(entry)) upsertCard(entry, { before });
|
|
90
95
|
else if (entry.type === "question") placeResolvedQuestion(entry, before);
|
|
91
96
|
else if (entry.type === "guardrail_warning") bubbles.showGuardrailWarning(entry.message, entry.label || "guardrails", { before });
|
|
@@ -325,12 +325,14 @@ export function showClaim(container, claim, { roots: rootSelector, opts = {}, do
|
|
|
325
325
|
const hit = findClaim(texts.map((t) => t.text), claim, opts);
|
|
326
326
|
if (!hit && !turn) return null;
|
|
327
327
|
const el = hit ? roots[hit.at] : turn.anchor;
|
|
328
|
+
const start = hit && pointAt(texts[hit.at], hit.start);
|
|
329
|
+
const end = hit && pointAt(texts[hit.at], hit.end, true);
|
|
328
330
|
revealInHistory(el);
|
|
331
|
+
// The text can be somewhere its root isn't: a drawn block's hidden source.
|
|
332
|
+
if (start) revealInHistory(start.node.parentElement);
|
|
329
333
|
el.classList.add(HIT_CLASS);
|
|
330
334
|
el.scrollIntoView?.({ block: "center" });
|
|
331
335
|
if (hit) {
|
|
332
|
-
const start = pointAt(texts[hit.at], hit.start);
|
|
333
|
-
const end = pointAt(texts[hit.at], hit.end, true);
|
|
334
336
|
const highlights = win?.CSS?.highlights;
|
|
335
337
|
if (start && end && highlights && typeof win.Highlight === "function") {
|
|
336
338
|
const range = doc.createRange();
|
|
@@ -56,7 +56,8 @@ export function installDetailsMotion({ root, selector, motion = canAnimate }) {
|
|
|
56
56
|
const summary = e.target.closest?.("summary");
|
|
57
57
|
const details = summary?.parentElement;
|
|
58
58
|
if (!details || !details.matches?.(selector) || summary.parentElement !== details) return;
|
|
59
|
-
|
|
59
|
+
// A live part of the summary (a steps block's footprint opens its own view).
|
|
60
|
+
if (e.target.closest("a, button, input, label, .ws-foot") !== null && e.target !== summary) return;
|
|
60
61
|
if (!motion(details)) return;
|
|
61
62
|
e.preventDefault();
|
|
62
63
|
if (busy.has(details)) return;
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// hides +el+ unfolds: a closed <details> around it (a turn's steps block, a
|
|
3
3
|
// step, a diff), an answer or a delegate report folded (answer_fold.js: its row follows
|
|
4
4
|
// the bubble's class; the jump's unfold isn't saved), a superseded version
|
|
5
|
-
// of a Revise stack (revise_stack.js: its stub opens, not saved)
|
|
5
|
+
// of a Revise stack (revise_stack.js: its stub opens, not saved), a drawn
|
|
6
|
+
// block's source (block_source.js). Every
|
|
6
7
|
// jump goes through it (claim_link.js showClaim, refs.js showRow and so the
|
|
7
8
|
// working set's step tags, verdict_card.js "View check"), so a new way to
|
|
8
9
|
// hide history is opened in one place.
|
|
@@ -11,6 +12,7 @@
|
|
|
11
12
|
// own: static JS is cached at unversioned URLs, and a new export on an old
|
|
12
13
|
// module would blank the page.
|
|
13
14
|
|
|
15
|
+
import { revealBlockSource } from "./block_source.js";
|
|
14
16
|
import { openStubOver } from "./revise_stack.js";
|
|
15
17
|
|
|
16
18
|
// Opens each closed <details> that holds +el+ (not +el+ itself: a fold the
|
|
@@ -19,6 +21,7 @@ import { openStubOver } from "./revise_stack.js";
|
|
|
19
21
|
export function revealInHistory(el) {
|
|
20
22
|
if (!el) return null;
|
|
21
23
|
openStubOver(el);
|
|
24
|
+
revealBlockSource(el);
|
|
22
25
|
el.closest?.(".bubble.folded")?.classList.remove("folded");
|
|
23
26
|
for (let details = el.parentElement?.closest("details"); details; details = details.parentElement?.closest("details")) {
|
|
24
27
|
details.open = true;
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
<link rel="stylesheet" href="/assets/composer.css">
|
|
16
16
|
<link rel="stylesheet" href="/assets/forms.css">
|
|
17
17
|
<link rel="stylesheet" href="/assets/objectives.css">
|
|
18
|
+
<link rel="stylesheet" href="/assets/block_renderers.css">
|
|
18
19
|
<style>
|
|
19
20
|
/* Icon masks shared by the phone info bar and the all view's cards (the stroke only shapes the mask; the icon takes currentColor); the image icon is composer.css's. */
|
|
20
21
|
:root{--icon-archive:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2' y='2.75' width='12' height='3.5' rx='1'/%3E%3Cpath d='M3.25 6.25v6a1 1 0 0 0 1 1h7.5a1 1 0 0 0 1-1v-6M6.5 9h3'/%3E%3C/svg%3E");--icon-unarchive:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2' y='2.75' width='12' height='3.5' rx='1'/%3E%3Cpath d='M3.25 6.25v6a1 1 0 0 0 1 1h7.5a1 1 0 0 0 1-1v-6M8 11.5V8.25M6.5 9.75 8 8.25l1.5 1.5'/%3E%3C/svg%3E")}
|
|
@@ -346,10 +347,16 @@ button.system-one-wrong,.system-one-wrong.marked{flex:none;padding:1px 6px;borde
|
|
|
346
347
|
.thinking-body{margin-top:6px;font-size:13px;color:var(--text-3);white-space:pre-wrap;word-break:break-word;font-style:italic;max-height:240px;overflow:auto}
|
|
347
348
|
/* The turn block (the stage's cloud, the history): one block per turn, its steps as collapsed rows, the live one open on top. */
|
|
348
349
|
.turn-work{border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:12px;background:var(--bg-panel);padding:8px 12px;font-size:14px}
|
|
349
|
-
.turn-work>summary{cursor:pointer;color:var(--text-muted);font-size:13px;font-weight:600;user-select:none;list-style:none}
|
|
350
|
+
.turn-work>summary{position:relative;padding-left:13px;cursor:pointer;color:var(--text-muted);font-size:13px;font-weight:600;user-select:none;list-style:none}
|
|
350
351
|
.turn-work>summary::-webkit-details-marker{display:none}
|
|
351
|
-
.turn-work>summary::before{content:"▸
|
|
352
|
-
|
|
352
|
+
.turn-work>summary::before{content:"▸";position:absolute;left:0;color:var(--accent)}
|
|
353
|
+
/* The summary's line: flex on this span, not on <summary> (WebKit's flex on a summary has had quirks). The chevron and the steps toggle the block, the footprint opens the turn's working set (working_set_view.js footClick). */
|
|
354
|
+
.tw-sum{display:flex;align-items:baseline;gap:10px;min-width:0}
|
|
355
|
+
/* The count gives way before the footprint, which is what the line is for. */
|
|
356
|
+
.ws-foot~.turn-steps{margin-left:auto;flex:0 100 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;font-size:12px;font-weight:500;color:var(--text-muted);white-space:nowrap}
|
|
357
|
+
/* A phone has no count to tap, and the chevron is then the only way to the steps: its hit area spreads over the block's padding, the glyph stays where it is. */
|
|
358
|
+
@media (max-width:600px){.ws-foot~.turn-steps{display:none}.turn-work>summary::before{left:-12px;top:-8px;width:36px;box-sizing:border-box;padding:8px 0 8px 12px}}
|
|
359
|
+
.turn-work[open]>summary::before{content:"▾"}
|
|
353
360
|
/* Closed, a block keeps its box (the bar, the border, the width): a click changes its height only (details_motion.js); thinking and the recap the same. */
|
|
354
361
|
/* A step's body (thinking, narration, rows, cards) sits --step-indent right of its summary, so the thinking
|
|
355
362
|
chevron and the row marks share one column under the step's own chevron. The summary takes it back with a
|
|
@@ -399,11 +406,11 @@ button.system-one-wrong,.system-one-wrong.marked{flex:none;padding:1px 6px;borde
|
|
|
399
406
|
.gen:not(.live) .gen-text{font-size:14px;color:var(--text-2)}
|
|
400
407
|
.gen-text:empty{display:none}
|
|
401
408
|
.gen .activity-body{margin-top:6px}
|
|
402
|
-
/* One gen, no thinking, no tool rows: nothing to stack yet, so the narration streams like a plain answer bubble. */
|
|
403
|
-
.turn-work:not(:has(.gen~.gen,.thinking-body,.activity-body)){border:0;padding:0;background:transparent}
|
|
404
|
-
.turn-work:not(:has(.gen~.gen,.thinking-body,.activity-body))>summary,.turn-work:not(:has(.gen~.gen,.thinking-body,.activity-body)) .gen>summary{display:none}
|
|
405
|
-
.turn-work:not(:has(.gen~.gen,.thinking-body,.activity-body)) .gen{margin:0;padding:0;border:0;background:transparent}
|
|
406
|
-
.turn-work:not(:has(.gen~.gen,.thinking-body,.activity-body)) .gen-text{margin:0;background:var(--bg-panel);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:12px;padding:12px 14px;opacity:.95}
|
|
409
|
+
/* One gen, no thinking, no tool rows: nothing to stack yet, so the narration streams like a plain answer bubble. A step that closed with text (a line steered in after it, its answer gone from the block) is a row: its summary is the way to its text. */
|
|
410
|
+
.turn-work:not(:has(.gen~.gen,.thinking-body,.activity-body,.gen:not(.live)>.gen-text)){border:0;padding:0;background:transparent}
|
|
411
|
+
.turn-work:not(:has(.gen~.gen,.thinking-body,.activity-body,.gen:not(.live)>.gen-text))>summary,.turn-work:not(:has(.gen~.gen,.thinking-body,.activity-body,.gen:not(.live)>.gen-text)) .gen>summary{display:none}
|
|
412
|
+
.turn-work:not(:has(.gen~.gen,.thinking-body,.activity-body,.gen:not(.live)>.gen-text)) .gen{margin:0;padding:0;border:0;background:transparent}
|
|
413
|
+
.turn-work:not(:has(.gen~.gen,.thinking-body,.activity-body,.gen:not(.live)>.gen-text)) .gen-text{margin:0;background:var(--bg-panel);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:12px;padding:12px 14px;opacity:.95}
|
|
407
414
|
@media(max-width:600px){.gen{--step-indent:8px}}
|
|
408
415
|
/* A question card asks for input like the composer card, so it wears the same glass (the recipe above); only its warm tint is its own. */
|
|
409
416
|
.bubble.question{border-left:3px solid var(--warn);background:color-mix(in srgb,var(--warn) 7%,var(--glass-bg));padding:10px 12px}
|
|
@@ -603,6 +610,7 @@ button.system-one-wrong,.system-one-wrong.marked{flex:none;padding:1px 6px;borde
|
|
|
603
610
|
.card-head{display:flex;align-items:baseline;gap:8px;margin-bottom:4px}
|
|
604
611
|
.card-title{font-size:14px;font-weight:600;color:var(--text-bright)}
|
|
605
612
|
.card-source{font-size:11px;color:var(--text-faint);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
613
|
+
.card-label{font-size:11px;color:var(--text-faint)}
|
|
606
614
|
.card-body{font-size:13.5px;color:var(--text-2);white-space:normal;line-height:1.5}
|
|
607
615
|
.card-body p{margin:0 0 6px}.card-body p:last-child{margin-bottom:0}
|
|
608
616
|
.card-body ul,.card-body ol{margin:0 0 6px 20px}
|
|
@@ -978,11 +986,11 @@ body.working-set .ts-live{position:relative;padding-right:264px}
|
|
|
978
986
|
}
|
|
979
987
|
body.working-set .turn-stage.waiting .ts-live{min-height:calc(var(--ts-live-h) + var(--ws-row-h,0px))}
|
|
980
988
|
body.working-set .turn-stage.held-wait:not(.waiting) .ts-live{min-height:max(calc(var(--ts-live-h) + var(--ws-row-h,0px)),var(--live-held,0px))}
|
|
981
|
-
/* A finished turn's footprint,
|
|
982
|
-
.ws-foot{
|
|
983
|
-
@media(hover:hover){.ws-foot:hover{
|
|
989
|
+
/* A finished turn's footprint, its block's summary line (working_set_view.js): a click opens its list. */
|
|
990
|
+
.ws-foot{flex:0 1 auto;min-width:0;padding:0 6px;border-radius:6px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px;font-weight:500;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer}
|
|
991
|
+
@media(hover:hover){.ws-foot:hover{background:var(--bg-hover)}}
|
|
984
992
|
.ws-foot:focus-visible{outline:2px solid var(--accent-ring);outline-offset:1px}
|
|
985
|
-
.ws-foot .w{font-family:ui-sans-serif,system-ui,sans-serif}
|
|
993
|
+
.ws-foot .w{font-family:ui-sans-serif,system-ui,sans-serif;font-weight:600;color:var(--text-muted)}
|
|
986
994
|
.ws-foot .sep{color:var(--border-strong)}
|
|
987
995
|
.ws-foot .e{color:var(--accent)}
|
|
988
996
|
.ws-foot .k{color:var(--think-text)}
|
|
@@ -1327,8 +1335,6 @@ body.has-session.composer-folded #infoBar{border-top-color:transparent}
|
|
|
1327
1335
|
.us-row-head span{flex:1 1 0;min-width:0;text-align:right;color:var(--text-2);font-size:12px;overflow-wrap:anywhere}
|
|
1328
1336
|
.us-detail{margin-top:4px;color:var(--text-muted);font-size:12px;overflow-wrap:anywhere}
|
|
1329
1337
|
.us-kept{margin-top:6px;color:var(--warn-text);font-size:12px}
|
|
1330
|
-
.us-kept ul{margin:3px 0 0;padding-left:18px;color:var(--text-2)}
|
|
1331
|
-
.us-kept li{overflow-wrap:anywhere}
|
|
1332
1338
|
.us-foot{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-top:10px}
|
|
1333
1339
|
@media(max-width:600px){#updateSheet{position:fixed;inset:auto 8px 8px;width:auto;max-width:none;max-height:min(80dvh,calc(100dvh - 16px));margin:0}}
|
|
1334
1340
|
.ctx-pop-kind{font-size:11px;color:var(--text-muted)}
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
// page's state comes in as getters (capabilities, roots, currentDir), read
|
|
5
5
|
// at each call.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const TEXTS = ".bubble.output, .gen:not(.live) > .gen-text";
|
|
7
|
+
import { TEXTS } from "./text_roots.js";
|
|
8
|
+
|
|
10
9
|
// The closed steps' texts still shown raw: a live turn's (a reload's come
|
|
11
10
|
// rendered, turn_block.js textTemplate).
|
|
12
11
|
const RAW_STEPS = ".gen:not(.live) > .gen-text:not(.markdown)";
|
|
@@ -226,12 +226,11 @@ export function handOffDue({ ended, pending, inUse, quietSinceMs, nowMs }) {
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
// A handed-off turn's nodes in the order the turn block leaves them: the
|
|
229
|
-
// prompt bubble (none for a reminder turn), the block,
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
return [prompt, block, footprint, ...extras, ...answer, timing, end, ...kept, ...queued].filter(Boolean);
|
|
229
|
+
// prompt bubble (none for a reminder turn), the block, the extras in arrival
|
|
230
|
+
// order, the answer's pieces, the timing line, the end line, the kept cards,
|
|
231
|
+
// then the lines queued for after the turn (they run next).
|
|
232
|
+
export function handOffOrder({ prompt = null, block = null, extras = [], answer = [], timing = null, end = null, kept = [], queued = [] }) {
|
|
233
|
+
return [prompt, block, ...extras, ...answer, timing, end, ...kept, ...queued].filter(Boolean);
|
|
235
234
|
}
|
|
236
235
|
|
|
237
236
|
// The rows that wait for the running turn's end to run as their own: a
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// stage_frame.js's, and the in-use watch that times the hand-off is
|
|
16
16
|
// stage_watch.js's.
|
|
17
17
|
import { createTurnBlock } from "./turn_block.js";
|
|
18
|
+
import { openAfterEnd } from "./block_open.js";
|
|
18
19
|
import { chipLabelText, flashOf, handOffDue, handOffOrder, headlineOf, isPlain, liveSlots, MARKS, miniToolText, placeFor, placeKind, QUEUED_CHILDREN, stepSlotText } from "./stage_model.js";
|
|
19
20
|
import { createStageSlots, node } from "./stage_slots.js";
|
|
20
21
|
import { createStageFrame, reducedMotion } from "./stage_frame.js";
|
|
@@ -687,7 +688,7 @@ export function createStageView({
|
|
|
687
688
|
// the pieces arrive in the history and it follows a reader who was at its
|
|
688
689
|
// end. Not with +immediate+ (another turn takes the stage at once), a
|
|
689
690
|
// reduced motion, a hidden page or a collapsed stage: all at once.
|
|
690
|
-
let folding = null; // { pieces, block, wasAtEnd, animate } while it slides
|
|
691
|
+
let folding = null; // { pieces, block, endKind, wasAtEnd, animate } while it slides
|
|
691
692
|
function handOff({ immediate = false } = {}) {
|
|
692
693
|
if (folding && immediate) finishFold();
|
|
693
694
|
if (!holding || !endKind) return;
|
|
@@ -695,12 +696,12 @@ export function createStageView({
|
|
|
695
696
|
view.chronological();
|
|
696
697
|
const block = blockEl && blockEl.parentNode === cloudBody ? blockEl : null;
|
|
697
698
|
// The turn's set joins the page's turns (a plain one's too: the turns
|
|
698
|
-
// keep their places); its footprint
|
|
699
|
+
// keep their places); its footprint takes the block's summary line.
|
|
699
700
|
const footprint = workingSet ? workingSet.handOff(view.current().turn, promptWords()) : null;
|
|
701
|
+
if (block && footprint) block.querySelector(":scope > summary > .tw-sum")?.prepend(footprint);
|
|
700
702
|
const pieces = handOffOrder({
|
|
701
703
|
prompt: promptSlot.firstElementChild,
|
|
702
704
|
block,
|
|
703
|
-
footprint: block ? footprint : null,
|
|
704
705
|
extras: [...extras.children],
|
|
705
706
|
answer: [...answer.children],
|
|
706
707
|
timing: timingSlot.firstElementChild,
|
|
@@ -708,7 +709,7 @@ export function createStageView({
|
|
|
708
709
|
queued: [...queuedSlot.children],
|
|
709
710
|
});
|
|
710
711
|
const animate = !immediate && !reducedMotion() && !document.hidden && !collapsed;
|
|
711
|
-
folding = { pieces, block: blockEl, wasAtEnd: historyNearBottom(), animate };
|
|
712
|
+
folding = { pieces, block: blockEl, endKind, wasAtEnd: historyNearBottom(), animate };
|
|
712
713
|
releaseTurn();
|
|
713
714
|
if (!animate) return finishFold();
|
|
714
715
|
stage.classList.add("leaving");
|
|
@@ -721,9 +722,9 @@ export function createStageView({
|
|
|
721
722
|
const fold = folding;
|
|
722
723
|
if (!fold) return;
|
|
723
724
|
folding = null;
|
|
724
|
-
//
|
|
725
|
-
//
|
|
726
|
-
if (fold.block) fold.block.open = fold.block.classList.contains("outline");
|
|
725
|
+
// The stage held it open; here it takes its place in the history:
|
|
726
|
+
// closed, unless the turn was cut short with an outline (block_open.js).
|
|
727
|
+
if (fold.block) fold.block.open = openAfterEnd(fold.endKind, fold.block.classList.contains("outline"));
|
|
727
728
|
historyEl.querySelector(":scope > .hint")?.remove();
|
|
728
729
|
for (const el of fold.pieces) {
|
|
729
730
|
historyEl.appendChild(el);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// The texts the page treats as text it wrote out of a model's words: the
|
|
2
|
+
// answers and the closed steps' texts. Cards, question replies, the asking
|
|
3
|
+
// slot and delegate reports aren't among them. markdown_place.js links refs on
|
|
4
|
+
// these; block_host.js draws in these. A module of its own: static JS is cached
|
|
5
|
+
// at unversioned URLs, and a new export on an old module would blank the page.
|
|
6
|
+
export const TEXTS = ".bubble.output, .gen:not(.live) > .gen-text";
|
|
@@ -17,14 +17,17 @@
|
|
|
17
17
|
// walks childNodes (or reads firstChild/lastChild) must skip comments;
|
|
18
18
|
// element queries, :empty and textContent are unaffected.
|
|
19
19
|
//
|
|
20
|
-
// A block that holds one gen with no thinking and no tool rows hides its
|
|
20
|
+
// A block that holds one live gen with no thinking and no tool rows hides its
|
|
21
21
|
// chrome (CSS :has), so a plain answer streams like today's bubble, and is
|
|
22
|
-
// removed at the end: the answer bubble takes its place.
|
|
22
|
+
// removed at the end: the answer bubble takes its place. A lone step that
|
|
23
|
+
// closed with text keeps its chrome: a line steered in after it left it alone
|
|
24
|
+
// in the block when the answer went, and its summary is the way to its text.
|
|
23
25
|
import { authorBadge, PAIR } from "./participant_names.js";
|
|
24
26
|
import { applyRowTemplate } from "./review_apply.js";
|
|
25
27
|
import { html, nothing, render } from "./vendor/lit-html.js";
|
|
26
28
|
import { answerRefsOf, setAnswerRefs } from "./answer_refs.js";
|
|
27
29
|
import { bindRowRefs } from "./refs.js";
|
|
30
|
+
import { openAfterEnd } from "./block_open.js";
|
|
28
31
|
import { leavesBlock } from "./card.js";
|
|
29
32
|
import { failedTurnText, normalize, sourceLabel } from "./format.js";
|
|
30
33
|
import { thumbsTemplate } from "./images.js";
|
|
@@ -132,7 +135,7 @@ function nodesOf(template) {
|
|
|
132
135
|
// ---- A finished block's outline ----
|
|
133
136
|
|
|
134
137
|
// A finished block as its outline (turn_model.js outlineRuns of +gens+, its
|
|
135
|
-
// steps in order):
|
|
138
|
+
// steps in order): each narrated step a one-line row as before, each
|
|
136
139
|
// run of steps that said nothing hidden behind one "N steps" fold that
|
|
137
140
|
// shows them. The live turn's end and a reload call it alike. The folds sit
|
|
138
141
|
// between the steps, not in them (`:scope > .gen` still counts steps), each
|
|
@@ -178,7 +181,6 @@ export function outlineBlock(blockEl, gens) {
|
|
|
178
181
|
const step = e.target.closest?.(".gen.folded");
|
|
179
182
|
if (step?.parentElement === blockEl) unfold.get(step)?.();
|
|
180
183
|
});
|
|
181
|
-
blockEl.open = true;
|
|
182
184
|
return true;
|
|
183
185
|
}
|
|
184
186
|
|
|
@@ -214,7 +216,7 @@ export function createTurnBlock({
|
|
|
214
216
|
let turn = null;
|
|
215
217
|
let blockEl = null;
|
|
216
218
|
let blockSummaryEl = null;
|
|
217
|
-
let
|
|
219
|
+
let blockStepsEl = null;
|
|
218
220
|
// gen → a step's live state (newStep), its rendered nodes between
|
|
219
221
|
// part.startNode and end.
|
|
220
222
|
const genEls = new Map();
|
|
@@ -237,16 +239,20 @@ export function createTurnBlock({
|
|
|
237
239
|
blockEl = document.createElement("details");
|
|
238
240
|
blockEl.className = "turn-work";
|
|
239
241
|
blockEl.open = true;
|
|
240
|
-
blockToggled = false;
|
|
241
242
|
blockSummaryEl = document.createElement("summary");
|
|
243
|
+
const line = document.createElement("span");
|
|
244
|
+
line.className = "tw-sum";
|
|
245
|
+
blockStepsEl = document.createElement("span");
|
|
246
|
+
blockStepsEl.className = "turn-steps";
|
|
247
|
+
line.appendChild(blockStepsEl);
|
|
248
|
+
blockSummaryEl.appendChild(line);
|
|
242
249
|
blockEl.appendChild(blockSummaryEl);
|
|
243
|
-
blockSummaryEl.addEventListener("click", () => { blockToggled = true; });
|
|
244
250
|
updateBlockSummary();
|
|
245
251
|
blockHost(blockEl);
|
|
246
252
|
}
|
|
247
253
|
|
|
248
254
|
function updateBlockSummary() {
|
|
249
|
-
if (
|
|
255
|
+
if (blockStepsEl && turn) blockStepsEl.textContent = model.blockSummary(turn, { running: !turn.ended });
|
|
250
256
|
}
|
|
251
257
|
|
|
252
258
|
// The step rendered again from its state (stepTemplate). Each live step
|
|
@@ -621,6 +627,7 @@ export function createTurnBlock({
|
|
|
621
627
|
placeAnswer(pieces, { plain: true, blockEl });
|
|
622
628
|
blockEl = null;
|
|
623
629
|
blockSummaryEl = null;
|
|
630
|
+
blockStepsEl = null;
|
|
624
631
|
} else {
|
|
625
632
|
placeAnswer(bubble ? [...cards, bubble] : [...cards, ...(endNote ? [endNote] : [])], { plain: false, blockEl });
|
|
626
633
|
}
|
|
@@ -649,11 +656,12 @@ export function createTurnBlock({
|
|
|
649
656
|
if (blockEl) {
|
|
650
657
|
updateBlockSummary();
|
|
651
658
|
blockEl.classList.add("done");
|
|
652
|
-
// The outline (turn_model.js outlineRuns)
|
|
653
|
-
//
|
|
659
|
+
// The outline (turn_model.js outlineRuns), for when it is opened. Closed
|
|
660
|
+
// as a reload draws it, unless the turn was cut short (block_open.js).
|
|
661
|
+
// The stage decides again as the block reaches the history.
|
|
654
662
|
chronological();
|
|
655
663
|
const outlined = outlineBlock(blockEl, model.blockGens(turn));
|
|
656
|
-
|
|
664
|
+
blockEl.open = openAfterEnd(kind, outlined);
|
|
657
665
|
for (const el of blockEl.querySelectorAll(".plugin-card")) {
|
|
658
666
|
if (leavesBlock({ warn: el.classList.contains("warn"), kind })) kept.push(el);
|
|
659
667
|
}
|
|
@@ -682,7 +690,7 @@ export function createTurnBlock({
|
|
|
682
690
|
turn = null;
|
|
683
691
|
blockEl = null;
|
|
684
692
|
blockSummaryEl = null;
|
|
685
|
-
|
|
693
|
+
blockStepsEl = null;
|
|
686
694
|
// Clear every ticker timer before the map goes (a live step mid-dwell).
|
|
687
695
|
for (const entry of genEls.values()) stopTicker(entry);
|
|
688
696
|
genEls.clear();
|
|
@@ -821,7 +829,7 @@ function failedLineTemplate(record) {
|
|
|
821
829
|
// template, images.js thumbsTemplate). +workingSet+ (the stage's
|
|
822
830
|
// web.working_set): each block stamped with its
|
|
823
831
|
// turn's ordinal and its rows with theirs (working_set_view.js), and the
|
|
824
|
-
// turn's footprint
|
|
832
|
+
// turn's footprint on its summary line.
|
|
825
833
|
export function turnHistoryNodes(items, timing, { thumbs, workingSet = false, now = Date.now() }) {
|
|
826
834
|
const timingTemplate = (group) => {
|
|
827
835
|
const record = group.record;
|
|
@@ -861,9 +869,11 @@ export function turnHistoryNodes(items, timing, { thumbs, workingSet = false, no
|
|
|
861
869
|
};
|
|
862
870
|
const steerTemplate = (i, bubble) => steerRowTemplate({ source: items[i].source, text: items[i].content }, { bubble });
|
|
863
871
|
const parts = [];
|
|
864
|
-
// Each block's steps, in order: its outline once the nodes exist;
|
|
865
|
-
// its turn's place among the turns
|
|
872
|
+
// Each block's steps, in order: its outline once the nodes exist; how its
|
|
873
|
+
// turn ended (whether it stays open); and its turn's place among the turns
|
|
874
|
+
// (the working set's stamp).
|
|
866
875
|
const outlines = [];
|
|
876
|
+
const blockEnds = [];
|
|
867
877
|
const blockTurns = [];
|
|
868
878
|
let turnOrdinal = -1;
|
|
869
879
|
const groups = turnGroups(items, timing);
|
|
@@ -903,10 +913,14 @@ export function turnHistoryNodes(items, timing, { thumbs, workingSet = false, no
|
|
|
903
913
|
// Its text knows its message's refs (a question's), as an answer does.
|
|
904
914
|
at: group.steps[k].i,
|
|
905
915
|
}));
|
|
906
|
-
|
|
916
|
+
// Its summary line: the working set's footprint, when the turn touched
|
|
917
|
+
// something, then the steps (the live block's, stage_view.js handOff).
|
|
918
|
+
const foot = workingSet ? footprintTemplate(groupItems(group, turnOrdinal), turnOrdinal) : nothing;
|
|
919
|
+
parts.push(html`<details class="turn-work done"><summary><span class="tw-sum">${foot}<span class="turn-steps">${model.blockSummary({ gens, ended: true })}</span></span></summary>${steps}</details>`);
|
|
907
920
|
outlines.push(gens);
|
|
921
|
+
// No record: a turn saved before them, or running at the reload.
|
|
922
|
+
blockEnds.push(group.record?.status ?? "completed");
|
|
908
923
|
blockTurns.push(turnOrdinal);
|
|
909
|
-
if (workingSet) parts.push(footprintTemplate(groupItems(group, turnOrdinal), turnOrdinal));
|
|
910
924
|
}
|
|
911
925
|
// Steers with no step to sit in, or in a turn that shows no block: on
|
|
912
926
|
// their own after the prompt.
|
|
@@ -941,9 +955,10 @@ export function turnHistoryNodes(items, timing, { thumbs, workingSet = false, no
|
|
|
941
955
|
parts.push(failedLineTemplate(group.record));
|
|
942
956
|
}
|
|
943
957
|
const out = nodesOf(parts);
|
|
944
|
-
// A finished turn's block as the live end leaves it: its outline,
|
|
958
|
+
// A finished turn's block as the live end leaves it: its outline, closed
|
|
959
|
+
// unless the turn was cut short.
|
|
945
960
|
const blocks = [...out.children].filter((el) => el.matches(".turn-work"));
|
|
946
|
-
blocks.forEach((block, i) => outlineBlock(block, outlines[i] || []));
|
|
961
|
+
blocks.forEach((block, i) => { block.open = openAfterEnd(blockEnds[i], outlineBlock(block, outlines[i] || [])); });
|
|
947
962
|
if (workingSet) stampHistory(blocks, blockTurns);
|
|
948
963
|
// Each answer bubble and step text knows its message's refs
|
|
949
964
|
// (answer_refs.js; the page pairs them after the render), the index
|
|
@@ -284,7 +284,10 @@ export function answerBubbleTemplate(message, { cls = "", at = null, turnId = nu
|
|
|
284
284
|
export function noteBubbleTemplate(note) {
|
|
285
285
|
if (note?.objectives) return objectivesEntryTemplate(note, timeTitle(note.at));
|
|
286
286
|
const from = note?.label ? `note from ${note.label}` : "note";
|
|
287
|
-
|
|
287
|
+
// A wake turn's note opens that turn (TURN_PROMPT, bubbles.js); a class, not
|
|
288
|
+
// data-turn-id, which marks a claim's anchor (claim_link.js).
|
|
289
|
+
const cls = note?.turn_start ? "bubble note turn-start" : "bubble note";
|
|
290
|
+
return html`<div class=${cls} title=${timeTitle(note?.at)}><div class="note-line">${from}</div><div class="note-text">${note?.content ?? ""}</div></div>`;
|
|
288
291
|
}
|
|
289
292
|
|
|
290
293
|
// A plugin's steer (a nudge put into the running turn): one line that
|
|
@@ -279,7 +279,7 @@ export function isNarrated(gen) {
|
|
|
279
279
|
// them): each narrated step a row, each run of the others between (tool-only,
|
|
280
280
|
// thinking-only) one fold ({fold: [indices]}), so every mid-turn text's first
|
|
281
281
|
// line reads without a click and the steps that said nothing take one line.
|
|
282
|
-
// [] when no step said anything:
|
|
282
|
+
// [] when no step said anything: nothing to outline.
|
|
283
283
|
// A last run of one thinking-only step stays a row (no "· 1 step" at the end).
|
|
284
284
|
export function outlineRuns(gens) {
|
|
285
285
|
if (!gens.some(isNarrated)) return [];
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { html, nothing, render } from "./vendor/lit-html.js";
|
|
2
2
|
|
|
3
|
+
// chi update's own words for the row (FileStanding owns what it says of a file).
|
|
4
|
+
// One that speaks of edited files runs long, so it gets the row's whole width.
|
|
3
5
|
function installRow(row) {
|
|
4
|
-
const
|
|
6
|
+
const count = (list) => (Array.isArray(list) ? list.length : 0);
|
|
7
|
+
const behind = count(row.kept) > 0;
|
|
8
|
+
const long = behind || count(row.merged) > 0 || count(row.earlier) > 0;
|
|
9
|
+
const status = row.status || "working";
|
|
5
10
|
const versions = [row.from, row.to].filter(Boolean).join(" → ");
|
|
6
11
|
return html`<li class="us-row">
|
|
7
|
-
<div class="us-row-head"><strong>${row.component || "chi update"}</strong
|
|
12
|
+
<div class="us-row-head"><strong>${row.component || "chi update"}</strong>${long ? nothing : html`<span>${status}</span>`}</div>
|
|
8
13
|
${versions ? html`<div class="us-detail">${versions}</div>` : nothing}
|
|
9
|
-
${
|
|
14
|
+
${long ? html`<div class=${behind ? "us-kept" : "us-detail"}>${status}</div>` : nothing}
|
|
10
15
|
</li>`;
|
|
11
16
|
}
|
|
12
17
|
|
|
@@ -95,17 +95,18 @@ function footTag(label) {
|
|
|
95
95
|
return html` <button type="button" class="ws-step p" data-ws="jump" data-at=${atAttr(first.at)} title=${title}>${text}</button>`;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
// The footprint
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
98
|
+
// The footprint of a finished turn's +items+ (turn +ordinal+), nothing for a
|
|
99
|
+
// turn that touched nothing: "touched web/public/ · 9 files · ◆ 3 memories
|
|
100
|
+
// s1–5 · +1 url · edited 2". It sits in the turn block's summary line, so a
|
|
101
|
+
// span (phrasing content). A click opens the expanded view at that turn; a
|
|
102
|
+
// step tag jumps to its call.
|
|
102
103
|
export function footprintTemplate(items, ordinal) {
|
|
103
104
|
const fp = footprint(items, BUDGETS.footprint);
|
|
104
105
|
if (!fp) return nothing;
|
|
105
106
|
const sep = html` <span class="sep">·</span> `;
|
|
106
107
|
const parts = fp.labels.map((l, i) => html`${i ? sep : nothing}<span class=${`l ${l.kind}${l.state && l.state !== "live" ? ` ${l.state}` : ""}`} title=${labelTitle(l)}>${l.glyph ? html`<span class="k">${l.glyph}</span> ` : nothing}${labelText({ ...l, glyph: "" })}</span>${footTag(l)}`);
|
|
107
108
|
const text = footprintText(items, BUDGETS.footprint);
|
|
108
|
-
return html`<
|
|
109
|
+
return html`<span class="ws-foot" role="button" tabindex="0" aria-haspopup="dialog" aria-expanded="false" data-ws-turn=${ordinal} data-ws-sig=${footSig(items)} aria-label=${`Working set of this turn: ${text.replace(/^touched /, "")}. Open the list.`}><span class="w">touched</span> ${parts}${fp.tail ? html`${sep}<span class="w">${fp.tail}</span>` : nothing}${fp.edited ? html`${sep}<span class="e">edited ${fp.edited}</span>` : nothing}${fp.stale ? html`${sep}<span class="s">${fp.stale} stale</span>` : nothing}${fp.forgotten ? html`${sep}<span class="f">${fp.forgotten} forgotten</span>` : nothing}${fp.dropped ? html`${sep}<span class="f">${fp.dropped} dropped</span>` : nothing}</span>`;
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
// What a footprint shows that can change after it is drawn: its items'
|
|
@@ -418,10 +419,12 @@ export function createWorkingSet({ historyEl, stageRoot = () => null, state, ses
|
|
|
418
419
|
}
|
|
419
420
|
});
|
|
420
421
|
|
|
421
|
-
// A footprint: its step tag jumps, the rest opens that turn's view.
|
|
422
|
+
// A footprint: its step tag jumps, the rest opens that turn's view. Inside
|
|
423
|
+
// a <summary>, the click is cancelled so it doesn't toggle the block.
|
|
422
424
|
function footClick(e) {
|
|
423
425
|
const foot = e.target.closest?.(".ws-foot");
|
|
424
426
|
if (!foot) return;
|
|
427
|
+
if (foot.closest("summary")) e.preventDefault();
|
|
425
428
|
const tag = e.target.closest(".ws-step[data-at]");
|
|
426
429
|
if (tag) {
|
|
427
430
|
const at = parseAt(tag.dataset.at);
|
|
@@ -563,7 +566,7 @@ export function createWorkingSet({ historyEl, stageRoot = () => null, state, ses
|
|
|
563
566
|
// The rail's first-sight times, for the popover's fresh rows.
|
|
564
567
|
setSeen(fn) { seenAt = fn; },
|
|
565
568
|
// The running turn hands off: its set joins the turns; its footprint
|
|
566
|
-
//
|
|
569
|
+
// (null: it touched nothing) goes into its block's summary.
|
|
567
570
|
handOff(turn, prompt = "") {
|
|
568
571
|
const ordinal = turns.length;
|
|
569
572
|
const items = itemsOfTurn(turn, ordinal);
|
|
@@ -5,6 +5,7 @@ require "uri"
|
|
|
5
5
|
require "time"
|
|
6
6
|
require_relative "attention"
|
|
7
7
|
require_relative "../../log"
|
|
8
|
+
require_relative "../../memory_bundle/file_standing"
|
|
8
9
|
|
|
9
10
|
module Samagotchi
|
|
10
11
|
module Web
|
|
@@ -52,10 +53,10 @@ module Samagotchi
|
|
|
52
53
|
AUTO_NOTICES = {
|
|
53
54
|
"installed" => lambda do |version, notes|
|
|
54
55
|
["installed", "chi #{version} is installed",
|
|
55
|
-
"chi web and its sessions switch over when you restart them.#{
|
|
56
|
+
"chi web and its sessions switch over when you restart them.#{file_words(notes)}"]
|
|
56
57
|
end,
|
|
57
58
|
"updated" => lambda do |version, notes|
|
|
58
|
-
["updated", "chi updated to #{version}", "#{restarted_words(notes[:restarted], notes[:left])}#{
|
|
59
|
+
["updated", "chi updated to #{version}", "#{restarted_words(notes[:restarted], notes[:left])}#{file_words(notes)}"]
|
|
59
60
|
end,
|
|
60
61
|
"restart_failed" => lambda do |version, notes|
|
|
61
62
|
["restart_failed", "chi #{version} is installed; chi web didn't restart",
|
|
@@ -79,13 +80,10 @@ module Samagotchi
|
|
|
79
80
|
"#{words}."
|
|
80
81
|
end
|
|
81
82
|
|
|
82
|
-
# "
|
|
83
|
-
def self.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
else
|
|
87
|
-
""
|
|
88
|
-
end
|
|
83
|
+
# " Merged the update into 1 file you edited. 2 files stay behind." ("" for none).
|
|
84
|
+
def self.file_words(notes)
|
|
85
|
+
words = MemoryBundle::FileStanding.counts_sentence(merged: notes[:merged].to_i, behind: notes[:kept].to_i)
|
|
86
|
+
words.empty? ? "" : " #{words}"
|
|
89
87
|
end
|
|
90
88
|
|
|
91
89
|
# A show carries +open+ only while its JSON stays this small: one
|
|
@@ -216,7 +216,8 @@ module Samagotchi
|
|
|
216
216
|
# `chi_update` frames are the page's), and not kept for a snapshot.
|
|
217
217
|
# @param kind [String] installed, updated or failed
|
|
218
218
|
# @param version [String] the release it is about
|
|
219
|
-
# @param notes [Hash] detail:, kept: (
|
|
219
|
+
# @param notes [Hash] detail:, kept:, merged: (counts of edited files that
|
|
220
|
+
# stayed behind and that took the update), restarted:, left:
|
|
220
221
|
def publish_notice(kind:, version:, **notes)
|
|
221
222
|
@monitor.synchronize { emit("chi_notice", { kind: kind, version: version, **notes }) }
|
|
222
223
|
nil
|
|
@@ -80,7 +80,7 @@ module Samagotchi
|
|
|
80
80
|
plugin_warning: turn_snapshot && turn_snapshot["plugin_warning"],
|
|
81
81
|
# Plugins' slow setup still running (chi.init): the page's init row.
|
|
82
82
|
init_tasks: snapshot_list(turn_snapshot, "init_tasks"),
|
|
83
|
-
cards: cards_for_display(live_or_saved(turn_snapshot, "cards") { saved_cards(id) }, cwd: session.working_directory),
|
|
83
|
+
cards: cards_for_display(live_or_saved(turn_snapshot, "cards") { saved_cards(id, messages: raw_messages) }, cwd: session.working_directory),
|
|
84
84
|
# The composer's / autocomplete; the built-ins until a worker
|
|
85
85
|
# names its plugins' too.
|
|
86
86
|
commands: turn_snapshot&.fetch("commands", nil) || SessionCommands.builtin_registry.listing,
|
|
@@ -175,7 +175,8 @@ module Samagotchi
|
|
|
175
175
|
def perform_install(version, auto)
|
|
176
176
|
argv = update_argv(version)
|
|
177
177
|
said = nil
|
|
178
|
-
|
|
178
|
+
env = auto ? ChildEnv.unbundled.merge(ChildEnv::UNATTENDED_UPDATE => "1") : ChildEnv.unbundled
|
|
179
|
+
output, success = @runner.call(argv, env: env,
|
|
179
180
|
progress: lambda { |line|
|
|
180
181
|
said = line unless line.strip.empty?
|
|
181
182
|
@hub.publish_update(state: "running", rows: [], progress: line,
|