Skip to content

feat(ai): calm transcript — narrative voice, grouped activity, and chat-UI polish#32

Merged
ndemianc merged 14 commits into
developfrom
feat/calm-transcript
Jul 23, 2026
Merged

feat(ai): calm transcript — narrative voice, grouped activity, and chat-UI polish#32
ndemianc merged 14 commits into
developfrom
feat/calm-transcript

Conversation

@ndemianc

@ndemianc ndemianc commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Implements docs/CALM-TRANSCRIPT.md (first commit): make an agent run read like a colleague narrating their work — short prose between actions, consecutive actions folded into one expandable card. Reference behavior: Claude Code's transcript.

S1 — the voice (agent.js)

SYSTEM_BASE gains the pair-working register:

  • narrate 1–2 sentences before a tool batch, then call the tool in the SAME turn — narration never replaces action, so the Done: sentinel, the anti-stall nudge, and the max_tokens machinery keep working untouched;
  • interpret results ("what they MEAN"), never restate output; failures are findings; one-line Correction: pattern;
  • ask_user options ordered with the recommendation first;
  • finish = Done: line + a short wrap-up for substantial runs;
  • run_command / read_file / search each carry a required explanation (5–10 imperative words) that becomes the action's label; the raw tool text drops to the tooltip.

S2–S4 — grouped activity (chat.html, webview-only reducer)

  • Consecutive tool entries fold into one collapsible group; assistant/user text, approvals, questions, errors, and run-end close it.
  • Two header states: while running — the LIVE step, present-progressive, with finished members folded to one-line rows beneath it (fixed footprint, output stays readable); when closed — past-tense aggregate, files first then commands ("Read and edited PLAN.md +56 −0, ran 2 commands"), same-file read+edit folded, summed diffstat. A successful group folds to its summary line; failures stay open.
  • Safety (D4): approvals/questions never render inside a group; failing members auto-expand — collapse is for success; user-stop is a choice, not a failure; a running member's Stop is delegated to a header stop button; single-member groups unwrap expanded; late-exiting background commands re-finalize in place.
  • levelcode.ai.chat.groupActivity (default on); off restores the flat timeline exactly.

Polish (from live testing)

  • Calmer chrome: one speaker label per turn (continuations flow as prose); the finished node stops wearing its spinner; tool rows use the UI font so the model's sentence doesn't read like a command line.
  • The stop button was never actually hidden.groupstop { display:inline-flex } outranked the UA [hidden] rule, so it sat beside finished groups offering to stop an exited command. Fixed with the [hidden] escape; a static guard (webviewCss.test.js) now catches this class of bug across every runtime-toggled element.
  • ask_user asks one question at a timen of N, Back, an advance button; nothing posts before the last question; skipping is deliberate ("Skip this one", recorded as empty); the card collapses to "Answers recorded" and unfolds to the full record.
  • Inline file chips centered on the text line (were dropped below the baseline and inflating it).
  • /rme — an undocumented composer command that prints a single reflective line; the words are base64 in source, decoded only when it runs.
  • The circle-check glyph (check-circle) replaces every bare checkmark — Kept, verified, answers recorded, ok-exit, approved, plan-step-done, Synced, Copied, and the menu's selected tick — one outline mark, currentColor (green when done, accent in the menu). The finished group node paints it too and drops its own border so there's a single ring.
  • HTML5 shield for .html/.htm files in place of the "HTML" text pill.

Tests

Zero-dependency Node suites, extraction-pattern (same as shHighlight.test.js) — they exercise the shipped chat.html, not copies:

  • narrativeUi.test.js — grammar: tense conversion, the aggregate sentence (clause order, same-file merge, fallbacks), chip→step, command labels.
  • groupReducer.test.js — DOM behavior against a fake DOM: live→aggregate header, member collapse, failure re-open, unwrap, late background exit, fold-on-success, one-label-per-turn.
  • askWizard.test.js — the one-question-at-a-time flow clicked through: no early post, skip-records-empty, Back preserves/revises, ordered payload, collapse-on-send.
  • webviewCss.test.js — static source invariants no DOM test can see: [hidden] never defeated by display, wizard/collapse specificity, the check-circle shape + reach.

Full extension gate green (22 suites). Every visual change verified in a rendered webview (dark theme), not just asserted.

ndemianc added 3 commits July 23, 2026 11:22
SYSTEM_BASE gains the pair-working register: narrate briefly before a tool
batch and interpret results after (always acting in the SAME turn — narration
never replaces action, so the anti-stall rules keep their teeth), failures as
findings with a one-line Correction: pattern, ask_user options ordered with the
recommendation first and the why in its description, and a Done: line followed
by a short wrap-up for substantial runs (the finish regex already accepts it).

run_command's explanation becomes effectively mandatory (prompt rule + schema
description): 5-10 imperative words that become the action's label in the
activity view — the raw command stays behind the expandable card.
Consecutive tool actions now fold into ONE collapsible card (docs/CALM-TRANSCRIPT.md).
While the group is open its header shows the LIVE step in present-progressive
("Verifying the edit and checking repo state") with a fixed vertical footprint —
completed members fold away instead of scrolling the log. When narration, an
approval, questions, an error, or run-end closes the group, the header flips to a
past-tense aggregate ("Ran 2 commands, read and edited PLAN.md +56 -0", counts
merged per category, same-file read+edit folded, summed diffstat).

The reducer lives entirely in the webview — the extension host posts the same
events as ever. Safety rules (D4): approvals and ask_user questions render at top
level, never inside a group; a failing member (nonzero exit, timeout, verify fail)
marks the group failed and auto-expands it — collapse is for success; a user-stop
is a choice, not a failure. A running member's Stop is delegated to a header
button so a collapsed group never hides the control. Single-member groups unwrap
to the bare card. Background commands that exit after their group closed
re-finalize it in place.

Labels: file ops derive ("Read PLAN.md", "Edit path" → tensed); commands use the
model's explanation (imperative, per the S1 prompt) with the first command
segment as fallback. tenseLabel converts verbs at the start and after and/then/
comma from a small irregular-verb map — no grammar engine.

levelcode.ai.chat.groupActivity (default on) rides the config message; off
restores the flat timeline exactly.

test/narrativeUi.test.js pins the grammar via the shHighlight extraction pattern
(19 tests, including compiling chat.html's whole script to catch syntax slips).
Full extension gate: 19 suites green.
Copilot AI review requested due to automatic review settings July 23, 2026 15:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements the “calm transcript” spec (docs/CALM-TRANSCRIPT.md) for LevelCode AI agent runs: a more narrative agent voice (system prompt changes) plus a webview-only activity reducer that groups consecutive tool actions into a single collapsible “activity” card with live/progressive headers while running and aggregate/past-tense summaries when finished.

Changes:

  • Updates the agent system prompt in agent.js to enforce a short narrative register and make run_command.explanation effectively mandatory.
  • Adds grouped-activity rendering + reducer logic to the chat webview (media/chat.html), with a new toggle setting exposed via config plumbing.
  • Introduces unit tests that extract and validate the new pure UI functions directly from chat.html, and documents the full design/spec in docs/CALM-TRANSCRIPT.md.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
docs/CALM-TRANSCRIPT.md Adds the detailed spec and rationale for calm transcript voice + grouped activity UI.
extensions/levelcode-ai/agent.js Revises the agent system prompt to narrate actions and require command explanations.
extensions/levelcode-ai/extension.js Sends the new groupActivity config flag to the webview.
extensions/levelcode-ai/media/chat.html Implements grouped-activity UI (CSS + reducer + labeling/aggregation helpers) and config toggle handling.
extensions/levelcode-ai/package.json Adds levelcode.ai.chat.groupActivity setting (default on) with description.
extensions/levelcode-ai/test/narrativeUi.test.js Adds extraction-pattern unit tests for chat.html grouping/labeling functions and script compilation.
Comments suppressed due to low confidence (1)

extensions/levelcode-ai/media/chat.html:1851

  • If verifyCards[m.id] is missing (e.g. after checkpoint restore/pruning), verifyDoneFinish currently returns early and leaves verifySteps[m.id] behind. Clean up verifySteps even when the DOM card is gone to avoid leaking state across long sessions.
    // Group bookkeeping: ok and unrunnable (a config issue, not the agent's code) close clean;
    // a real verify failure marks the group failed and auto-expands it.
    groupStepDone(verifySteps[m.id], !(m.ok || m.unrunnable));
    delete verifySteps[m.id];

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 2669 to 2671
lastModelId = m.modelId || m.model || ''; // gateway sends modelId (the real id); BYOK model IS the id
if (typeof m.groupActivity === 'boolean'){ groupsOn = m.groupActivity; } // calm-transcript toggle
renderRouting();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b655f95. Setting groupActivity false now calls closeGroup() first, so any open group is finalized and the timeline returns fully flat immediately.

Comment on lines +1416 to +1419
let curGroup = null; // open group state, or null
const termSteps = {}; // run id → step (a command's exit updates its group later)
const editSteps = {}; // edit id → step (re-renders update the counts in place)
const verifySteps = {}; // verify id → step

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b655f95. editSteps[id] is now released in resolveEditCard alongside editCards[id], and all three step maps (edit/term/verify) are pruned on checkpoint restore next to their card maps.

Comment on lines +1791 to +1794
// Group bookkeeping: a user-stop is a choice, not a failure — only timeouts and nonzero exits
// mark the group failed (and auto-expand it).
groupStepDone(termSteps[m.id], m.how === 'timeout' || (m.how !== 'stopped' && m.code !== 0));
delete termSteps[m.id];

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b655f95. termExitFinish's early-return path now settles the group step and delete termSteps[id] before returning, so no step state is left behind when the card is gone.

…er and labels

Four defects found by re-reading the reference transcript against what the
grouping actually did:

1. The aggregate read its clauses backwards ("Ran 2 commands, read and edited
   PLAN.md"). Files lead, commands trail; a lone command is "a command", not
   "1 command".

2. Groups were created collapsed, which hid the output of the step the header
   claims to be running — the opposite of the fixed-footprint behavior it was
   meant to give. The group body now stays open and each member collapses to a
   one-line row as it finishes (collapseMember), so only finished work folds.
   A failing step re-opens its own row and the group; the single-member unwrap
   hands the card back expanded.

3. read_file and search derived their labels from arguments ("Read src/x.js")
   where the reference shows the model's own sentence. Both now take a required
   explanation, same as run_command; it titles the row and the raw tool text
   drops to the tooltip.

4. None of the DOM behavior was executed by a test — only the pure grammar
   functions and a syntax compile, which is why 2 shipped. test/groupReducer.js
   extracts the real reducer functions and drives them through a fake DOM:
   live header, member collapse, failure re-open, user-stop-is-not-failure,
   unwrap, late background exit, idempotent close, grouping-off, label/tooltip.

Full extension gate green (20 suites).
Copilot AI review requested due to automatic review settings July 23, 2026 16:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

extensions/levelcode-ai/agent.js:51

  • run_command’s explanation is described as “ALWAYS provide…”, but it’s not included in the tool schema’s required list. If the model omits it, the command row/group header loses its semantic label (a key part of calm transcript).
	{ name: 'run_command', description: 'Run a shell command in the workspace root (or a named workspace folder via "folder" in multi-root workspaces). Requires approval. Pass background:true for commands that do not exit on their own (servers, watchers) so the agent is not blocked — it returns immediately and you read progress later with read_command_output.', input_schema: { type: 'object', properties: { command: { type: 'string' }, explanation: { type: 'string', description: 'ALWAYS provide: 5-10 words, active voice, imperative — what this command does ("Run the extension unit tests", "Find the insertion point in section 10"). Shown to the user as this action\'s label.' }, folder: { type: 'string', description: 'multi-root workspaces only: the workspace folder NAME to run in; defaults to the first folder' }, background: { type: 'boolean', description: 'true = start it and keep working without waiting (dev servers, watchers, tail -f). Returns immediately with an id; poll read_command_output for its output/status.' } }, required: ['command'] } },

Comment thread extensions/levelcode-ai/agent.js Outdated
Comment on lines +45 to +46
{ name: 'read_file', description: 'Read a workspace file (path relative to the workspace root; in a multi-root workspace use the folder-name prefix exactly as list_files shows it).', input_schema: { type: 'object', properties: { path: { type: 'string' }, explanation: { type: 'string', description: 'ALWAYS provide: 3-8 words, active voice, imperative — WHY you are reading this ("Read the runAgent call site", "Read agent.js tool definitions"). Shown to the user as this action\'s label.' } }, required: ['path'] } },
{ name: 'search', description: 'Search file contents for a literal string. Returns file:line snippets.', input_schema: { type: 'object', properties: { query: { type: 'string' }, explanation: { type: 'string', description: 'ALWAYS provide: 3-8 words, active voice, imperative — what you are looking for ("Find every postMessage call site"). Shown to the user as this action\'s label.' } }, required: ['query'] } },

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b655f95. explanation added to required for both read_file and search (and run_command).

Comment on lines +1539 to +1544
function collapseMember(el){
if (!el || !el.classList){ return; }
if (el.classList.contains('tl-cmd')){ el.classList.add('collapsed'); return; }
const det = el.querySelector && el.querySelector('details.diffwrap');
if (det){ det.open = false; }
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b655f95. groupAppend no longer collapses a running member — it folds only non-running members on insert; groupStepDone folds a member once it completes. Verified in a rendered webview with an actually-running command: the running npm test card stays expanded with its streaming output while finished members fold to one-line rows.

Comment on lines +1821 to +1824
// Group bookkeeping: a user-stop is a choice, not a failure — only timeouts and nonzero exits
// mark the group failed (and auto-expand it).
groupStepDone(termSteps[m.id], m.how === 'timeout' || (m.how !== 'stopped' && m.code !== 0));
delete termSteps[m.id];

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b655f95. Collapse-on-success now happens at completion (groupStepDone, success branch), not at insertion — so groups stay compact but the active command remains readable while it runs.

Comment on lines +1580 to +1587
g.closed = true;
g.el.classList.remove('running');
g.label.textContent = groupAggregate(g.steps);
g.counts.innerHTML = groupCountsHtml(g);
g.state.innerHTML = codicon(g.failed ? 'circle-slash' : 'check');
g.state.className = 'cmdstate groupstate ' + (g.failed ? 'bad' : 'ok');
g.stopBtn.hidden = true;
if (g.failed){ g.el.classList.remove('collapsed'); }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b655f95. finalizeGroup now detects a still-running member: it keeps the live header and the Stop control and returns without settling; the member's later groupStepDone re-enters and completes the group. So a running background command is never shown as done and never loses its Stop button. Added a reducer test for this.

… one speaker label

From a real run: the grouping worked but the surrounding chrome undid it.

- A finished group never folded. `.tl-group.collapsed .groupbody{display:none}`
  existed but finalizeGroup never applied it, so a closed group showed its
  aggregate header AND every finished row — N+1 lines where the reference shows
  one. Successful groups now fold to the summary line; failures stay open, and a
  header the user clicked keeps the state they chose.

- A finished group kept wearing its spinner: the rail node stayed on the `sync`
  glyph while a second check sat at the end of the header. The rail now carries
  the outcome and the trailing state clears on success — one glyph, not three.

- Tool rows were monospace. That was right when they read `read src/agent.js`;
  now they carry the model's own sentence, and prose in the editor font read
  like a command line.

- "LEVELCODE AI" was stamped over every narration block — four times in one
  turn, chopping a single train of thought into separate announcements. One
  label per turn; continuations flow as prose with a tightened gap.

groupReducer covers all of it (14 tests): fold-on-success, userToggled respected,
failure stays open, rail glyph swap, label-once-per-turn, re-armed by a new user
message. Full gate green (20 suites).
Copilot AI review requested due to automatic review settings July 23, 2026 16:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

extensions/levelcode-ai/media/chat.html:1565

  • groupAppend() always calls collapseMember(el), and collapseMember() collapses every .tl-cmd card. Since addTermRun() creates command cards as .tl.tl-cmd.running, this hides the running command's .cmdbox/streamed output while the group is open (CSS: .tl-cmd.collapsed .cmdbox { display: none; }). That contradicts the spec’s “live step output stays readable” behavior and makes it hard to monitor long-running commands without extra clicks.
  function groupAppend(el, step){
    if (!groupsOn){ log.appendChild(el); return; }
    const g = curGroup || openGroup();
    collapseMember(el);
    g.body.appendChild(el);

extensions/levelcode-ai/media/chat.html:1619

  • After expanding a running command card (so output is visible), successful completion should re-collapse it back to a one-line row so the group footprint stays calm. Right now only failures auto-expand; success never folds members back down, so a finished command can remain expanded inside the group.
  function groupStepDone(step, failed){
    if (!step){ return; }
    step.status = failed ? 'fail' : 'done';
    const g = step.group; if (!g){ return; }
    if (failed){

extensions/levelcode-ai/agent.js:34

  • The new “explanation” guidance is internally inconsistent: SYSTEM_BASE requires 3–10 words for run_command, read_file, and search, but the tool schemas specify 3–8 for read_file/search and 5–10 for run_command. Aligning these avoids confusing the model about what format is required.
	'- EVERY run_command, read_file and search MUST include "explanation": 3-10 words, active voice, imperative, saying what you are doing and why ("Run the extension unit tests", "Find the insertion point in section 10", "Read the runAgent call site"). It becomes that action\'s label in the user\'s activity view — never omit it.',

Spotted in a real run: a red stop button sat beside a finished group, offering
to stop a command that had already exited.

The reducer was right — finalizeGroup does set `stopBtn.hidden = true`. The CSS
was wrong: `.tl-group .groupstop { display: inline-flex }` outranks the UA
stylesheet's `[hidden] { display: none }`, so the attribute was inert and the
button had been painted since the group opened, running or not. chat.html
already carried four hand-written escapes for this same trap (#workbar,
.modewrap, .modemenu, .st-approvals); this was the missing fifth.

No DOM test could have caught it — the fake DOM has no stylesheet, so it
faithfully reported a hidden attribute the browser then ignored. So the guard is
static: webviewCss.test.js collects every element that ships the `hidden`
attribute and fails if a class rule forces `display` without a matching
`[hidden]` escape. Verified against the pre-fix file, where it flags .groupstop
and nothing else.

Full gate green (21 suites).
Copilot AI review requested due to automatic review settings July 23, 2026 16:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

extensions/levelcode-ai/agent.js:34

  • SYSTEM_BASE says every run_command/read_file/search explanation should be “3-10 words”, but the tool schemas below specify 3–8 for read_file/search and 5–10 for run_command. This mismatch can confuse the model and makes the requirement harder to follow consistently; align the wording in one place (SYSTEM_BASE is the most visible).
	'- EVERY run_command, read_file and search MUST include "explanation": 3-10 words, active voice, imperative, saying what you are doing and why ("Run the extension unit tests", "Find the insertion point in section 10", "Read the runAgent call site"). It becomes that action\'s label in the user\'s activity view — never omit it.',

extensions/levelcode-ai/media/chat.html:1436

  • The new step registries (termSteps/editSteps/verifySteps) appear to be never cleared when an edit is resolved (resolveEditCard deletes editCards[m.id] but not editSteps[m.id]) and they’re also not cleared on a chat reset. Over a long session this can retain stale step objects (and detached DOM nodes via step.card/group), and late exit messages can still try to update state that the UI has discarded.
  const termSteps = {};             // run id → step (a command's exit updates its group later)
  const editSteps = {};             // edit id → step (re-renders update the counts in place)
  const verifySteps = {};           // verify id → step

Several questions rendered stacked in one card under a single "Send answers"
button. Answering the first and missing the rest was easy — and the agent then
proceeded on defaults the user never chose, which is worse than not asking.

The card now shows one question at a time:
- an `n of N` counter, Back to revise, and an advance button;
- NOTHING is posted before the last question — the button advances until then;
- skipping is deliberate: with nothing picked the button reads "Skip this one"
  (quiet style) and that answer posts as empty rather than silently defaulted;
- on send the card collapses to "Answers recorded — click to review", unfolding
  to every question with what was picked;
- one question keeps the plain card, no step chrome.

Also fixes a specificity trap in the new CSS: `.questions.wizard .qblock.cur`
(4 classes) outranked `.questions.collapsed .qblock` (3), so collapsing the card
mid-wizard would have left the current question on screen. The override matches
at equal weight and comes later; webviewCss pins the source order.

askWizard.test.js (12 tests) clicks the real function through a fake DOM: no
early post, skip-records-empty, Back preserves and revises, ordered payload,
multiSelect, single-question path, and the collapsed record. Gate green (22).
Copilot AI review requested due to automatic review settings July 23, 2026 16:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

extensions/levelcode-ai/agent.js:45

  • read_file is documented as requiring an explanation (and downstream UI uses it as a label), but the tool schema still only requires path. Making explanation required will better enforce the new contract across providers/models.
	{ name: 'read_file', description: 'Read a workspace file (path relative to the workspace root; in a multi-root workspace use the folder-name prefix exactly as list_files shows it).', input_schema: { type: 'object', properties: { path: { type: 'string' }, explanation: { type: 'string', description: 'ALWAYS provide: 3-8 words, active voice, imperative — WHY you are reading this ("Read the runAgent call site", "Read agent.js tool definitions"). Shown to the user as this action\'s label.' } }, required: ['path'] } },

extensions/levelcode-ai/agent.js:46

  • search is documented as requiring an explanation (and downstream UI uses it as a label), but the tool schema still only requires query. Making explanation required will better enforce the new contract across providers/models.
	{ name: 'search', description: 'Search file contents for a literal string. Returns file:line snippets.', input_schema: { type: 'object', properties: { query: { type: 'string' }, explanation: { type: 'string', description: 'ALWAYS provide: 3-8 words, active voice, imperative — what you are looking for ("Find every postMessage call site"). Shown to the user as this action\'s label.' } }, required: ['query'] } },

Comment thread extensions/levelcode-ai/media/chat.html Outdated
d.innerHTML = '<div class="tl-rail"><span class="tl-node">' + (IC[icon] ? codicon(icon) : esc(icon || '•')) + '</span></div>'
+ '<div class="tl-body"><span class="toolt">' + esc(text || '') + '</span></div>';
log.appendChild(d); scrollIfStuck();
+ '<div class="tl-body"><span class="toolt" title="' + esc(text || '') + '">' + esc(shown) + '</span></div>';

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b655f95. The title now uses escAttr() (which escapes "), so search "foo" no longer breaks the attribute. Pinned with a test.

Comment thread extensions/levelcode-ai/agent.js Outdated
{ name: 'write_file', description: 'Create a new file (or fully overwrite a short one) with the COMPLETE content. For edits to existing files, prefer edit_file. Applied immediately; the user reviews it with Keep/Undo.', input_schema: { type: 'object', properties: { path: { type: 'string' }, content: { type: 'string' } }, required: ['path', 'content'] } },
{ name: 'delete_file', description: 'Delete an EXISTING workspace file (e.g. removing a file during a refactor). Applied immediately; the user reviews it with Keep/Undo, and the per-turn checkpoint can restore it. To RENAME or move a file: write_file the new path, then delete_file the old one.', input_schema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } },
{ name: 'run_command', description: 'Run a shell command in the workspace root (or a named workspace folder via "folder" in multi-root workspaces). Requires approval. Pass background:true for commands that do not exit on their own (servers, watchers) so the agent is not blocked — it returns immediately and you read progress later with read_command_output.', input_schema: { type: 'object', properties: { command: { type: 'string' }, explanation: { type: 'string' }, folder: { type: 'string', description: 'multi-root workspaces only: the workspace folder NAME to run in; defaults to the first folder' }, background: { type: 'boolean', description: 'true = start it and keep working without waiting (dev servers, watchers, tail -f). Returns immediately with an id; poll read_command_output for its output/status.' } }, required: ['command'] } },
{ name: 'run_command', description: 'Run a shell command in the workspace root (or a named workspace folder via "folder" in multi-root workspaces). Requires approval. Pass background:true for commands that do not exit on their own (servers, watchers) so the agent is not blocked — it returns immediately and you read progress later with read_command_output.', input_schema: { type: 'object', properties: { command: { type: 'string' }, explanation: { type: 'string', description: 'ALWAYS provide: 5-10 words, active voice, imperative — what this command does ("Run the extension unit tests", "Find the insertion point in section 10"). Shown to the user as this action\'s label.' }, folder: { type: 'string', description: 'multi-root workspaces only: the workspace folder NAME to run in; defaults to the first folder' }, background: { type: 'boolean', description: 'true = start it and keep working without waiting (dev servers, watchers, tail -f). Returns immediately with an id; poll read_command_output for its output/status.' } }, required: ['command'] } },

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b655f95. explanation is now in run_command's required.

…shed-group node

Two polish issues from a real transcript:

- File-reference chips (the "MD REPLIES.md" pills in prose) sat below the text
  baseline. `vertical-align: -2px` pushed them down and `line-height: 1.7`
  inflated their box; a line with a chip was also ~4px taller than its
  neighbours. Now `vertical-align: middle` with a trimmed box — measured centered
  to 1px on the text line, and 20px tall against a 24px line so it no longer
  stretches the line.

- A finished group's rail node showed a blue check inside the accent-tinted
  circle — the accent fill is the *running* identity, and every other done tick
  (Kept, verified, command-ok) is a quiet green. The node now recolours on close:
  success speaks that same green in a neutral ring (.tl-group.gok), failure the
  error colour (.tl-group.gfailed). No coloured circle competing with the glyph.

Verified in a dark-theme render of the real webview: chip midpoint 47px vs the
text line's 46px, node colour rgb(78,201,122) = #4ec97a not the accent blue.
groupReducer pins the gok/gfailed state classes. Gate green (22 suites).
Copilot AI review requested due to automatic review settings July 23, 2026 17:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (4)

extensions/levelcode-ai/media/chat.html:1599

  • groupAppend() collapses every appended command card via collapseMember(), including currently-running commands/verify cards. Since .tl-cmd.collapsed hides .cmdbox, this prevents users from seeing live output while a step runs, contradicting the “running step stays readable” requirement.
  function groupAppend(el, step){
    if (!groupsOn){ log.appendChild(el); return; }
    const g = curGroup || openGroup();
    collapseMember(el);
    g.body.appendChild(el);

extensions/levelcode-ai/media/chat.html:1654

  • Successful running members are never collapsed when they finish. With the fix to keep running steps expanded, a command/verify card will stay expanded after completion, so the group won’t settle back into one-line rows. Collapsing successful members on completion keeps the group’s vertical footprint calm while still auto-expanding failures.
  function groupStepDone(step, failed){
    if (!step){ return; }
    step.status = failed ? 'fail' : 'done';
    const g = step.group; if (!g){ return; }
    if (failed){

extensions/levelcode-ai/agent.js:46

  • The system prompt says read_file/search MUST include an explanation (used as the activity label), but the tool input schemas don’t require it. Marking explanation as required makes the “calm transcript” labeling invariant enforceable at the schema level.
	{ name: 'read_file', description: 'Read a workspace file (path relative to the workspace root; in a multi-root workspace use the folder-name prefix exactly as list_files shows it).', input_schema: { type: 'object', properties: { path: { type: 'string' }, explanation: { type: 'string', description: 'ALWAYS provide: 3-8 words, active voice, imperative — WHY you are reading this ("Read the runAgent call site", "Read agent.js tool definitions"). Shown to the user as this action\'s label.' } }, required: ['path'] } },
	{ name: 'search', description: 'Search file contents for a literal string. Returns file:line snippets.', input_schema: { type: 'object', properties: { query: { type: 'string' }, explanation: { type: 'string', description: 'ALWAYS provide: 3-8 words, active voice, imperative — what you are looking for ("Find every postMessage call site"). Shown to the user as this action\'s label.' } }, required: ['query'] } },

extensions/levelcode-ai/agent.js:51

  • run_command’s explanation is described as “ALWAYS provide”, but the schema still only requires command. If explanation is required for the activity label, it should be required in the schema as well to prevent unlabeled command cards.
	{ name: 'run_command', description: 'Run a shell command in the workspace root (or a named workspace folder via "folder" in multi-root workspaces). Requires approval. Pass background:true for commands that do not exit on their own (servers, watchers) so the agent is not blocked — it returns immediately and you read progress later with read_command_output.', input_schema: { type: 'object', properties: { command: { type: 'string' }, explanation: { type: 'string', description: 'ALWAYS provide: 5-10 words, active voice, imperative — what this command does ("Run the extension unit tests", "Find the insertion point in section 10"). Shown to the user as this action\'s label.' }, folder: { type: 'string', description: 'multi-root workspaces only: the workspace folder NAME to run in; defaults to the first folder' }, background: { type: 'boolean', description: 'true = start it and keep working without waiting (dev servers, watchers, tail -f). Returns immediately with an id; poll read_command_output for its output/status.' } }, required: ['command'] } },

Comment on lines 2078 to +2082
if (!card){
card = document.createElement('div'); card.className = 'editcard';
editCards[m.id] = card; log.appendChild(card);
editCards[m.id] = card;
const st = {
kind: m.deleted ? 'delete' : (m.exists ? 'edit' : 'create'),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b655f95. New grouped edit cards now default the diff closed (wasOpen = !groupsOn), so they render as a one-line row on first paint. Verified in a rendered webview: an editApplied inside a group renders with details.diffwrap closed.

An undocumented composer command. `/rme` renders a single reflective line as a
calm remind-me card (serif italic behind a soft accent rule). Handled locally in
doSend alongside /skills — no LLM call, no network.

The words themselves are kept out of the source: stored base64 and decoded
(UTF-8 aware, the text is Cyrillic) only when the command runs, so they surface
in the app and not to anyone scanning the file. Not listed in any command help
or autocomplete — it's meant to be found, not advertised.

Verified in a rendered webview: /rme echoes the command and prints the exact
line; the plaintext appears nowhere in the repo. Compile + full gate green.
Copilot AI review requested due to automatic review settings July 23, 2026 18:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

extensions/levelcode-ai/media/chat.html:1611

  • Inside grouped activity, collapseMember() collapses every .tl-cmd immediately, including running commands. That defeats the intended behavior (and the nearby comment in addTermRun) that live command output is visible by default while the step runs.
  function collapseMember(el){
    if (!el || !el.classList){ return; }
    if (el.classList.contains('tl-cmd')){ el.classList.add('collapsed'); return; }
    const det = el.querySelector && el.querySelector('details.diffwrap');
    if (det){ det.open = false; }

Comment on lines +1525 to +1542
const cmds = steps.filter((s) => s.kind === 'cmd').length;
const searches = steps.filter((s) => s.kind === 'search' || s.kind === 'list').length;
const reads = of('read'), edits = of('edit'), creates = of('create'), dels = of('delete');
// File work leads, commands trail ("Read and edited extension.js, ran a command") — the reference
// ordering: what changed matters more than how many shells it took.
const parts = [];
const readOnly = reads.filter((p) => edits.indexOf(p) < 0 && creates.indexOf(p) < 0);
const readEdited = edits.filter((p) => reads.indexOf(p) >= 0);
if (readEdited.length && !readOnly.length && readEdited.length === edits.length){
parts.push('read and edited ' + files(readEdited));
} else {
if (readOnly.length){ parts.push('read ' + files(readOnly)); }
if (edits.length){ parts.push('edited ' + files(edits)); }
}
if (creates.length){ parts.push('created ' + files(creates)); }
if (dels.length){ parts.push('deleted ' + files(dels)); }
if (searches){ parts.push('searched the workspace'); }
if (cmds){ parts.push('ran ' + (cmds === 1 ? 'a command' : cmds + ' commands')); }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b655f95. groupAggregate now counts list steps separately from search and renders "listed files" rather than folding them into "searched the workspace". Added a test.

New `check-circle` icon — a thin ring with a rounded tick, drawn as strokes
(fill:none overrides the svg-level fill=currentColor) so it reads as the outline
mark from the design. currentColor, so it inherits green in "done" contexts and
the accent in the menu.

Swapped in at every status tick: Kept / Deleted-fallback, verified — no problems,
Answers recorded (title + verdict), a command's ok exit, an approved command,
plan step done, the account "Synced" line, and the Copied confirmation. The
approvals + mode menus' selected mark now paints the same glyph instead of a
literal ✓ (data-ico="check-circle", filled by the broadened [data-ico] pass).

Left alone on purpose: the group rail node, whose .tl-node already IS the ring —
a glyph-ring inside it would double up.

Verified in a dark-theme render: menu tick is the ring-check in the accent
colour, Kept/verified/exit all show the green ring-check, and exactly one bare
check (the node) remains. webviewCss pins the glyph shape, the menu wiring, and
that no other bare check survives. Gate green (22 suites).
Copilot AI review requested due to automatic review settings July 23, 2026 18:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

extensions/levelcode-ai/media/chat.html:2101

  • In grouped-activity mode, new edit cards default wasOpen = true, but groupAppend(card, st) runs before card.innerHTML is populated. That means collapseMember() can’t close the <details.diffwrap>, and the edit diff renders expanded inside the activity group, defeating the “collapsed member rows” invariant.
    let wasOpen = true;

extensions/levelcode-ai/media/chat.html:2162

  • editSteps entries are created for each edit card (for in-place count updates), but they’re never cleaned up when an edit card is resolved. Over long sessions this can leak step objects and keep references alive unnecessarily.
    card.classList.add('resolved');
    delete editCards[m.id];
  }

Comment on lines +1449 to +1456
// A remind-me line. The words are kept out of the source as base64 and decoded only here, so
// they surface only when someone runs /rme — not to anyone scanning the file. UTF-8 aware decode
// (the text is Cyrillic), so atob's byte string is run back through TextDecoder.
var RME_B64 = '0KHQsNC80L7QtNC+0YHRgtCw0YLQvdGW0YHRgtGMLCDQt9Cx0YPQtNC+0LLQsNC90LAsINGJ0L7QsSDRg9C90LjQutC90YPRgtC4INC/0L7QutC40L3Rg9GC0L7RgdGC0ZYsIOKAlCDRhtC1INGB0LDQvNC1INGC0LUsINGJ0L4g0YLRgNC40LzQsNGUINC70Y7QtNC10Lkg0L3QsCDQstGW0LTRgdGC0LDQvdGWLg==';
function rmeText(){
try { return new TextDecoder().decode(Uint8Array.from(atob(RME_B64), function(c){ return c.charCodeAt(0); })); }
catch (e) { return ''; }
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping this intentionally — the product owner explicitly requested /rme and that its text stay out of the source (it's a private easter-egg reminder, not user-facing product copy). It's handled locally with no LLM/network call, isn't advertised in any command list, and the source carries a comment explaining the encoding. Happy to move the string to a resource file if you'd prefer, but it stays in this PR at the owner's request.

… .html files

Two follow-ups to the circle-check work.

- The finished group's rail node was a bare check inside .tl-node's own gray
  ring — a check in a circle, but a *double* ring once the glyph is itself a
  ring-check. The done node now paints check-circle (or circle-slash on failure)
  and drops its own border (border-color: transparent), so the glyph's ring is
  the only ring; the node keeps its background to mask the rail line, and the
  glyph grows to 18px to fill it. One clean green ring-check.

- .html/.htm files wear the HTML5 shield instead of the "HTML" text pill — a
  self-contained two-tone-orange shield with the white "5", no pill chrome
  around it. Other extensions keep their lettered badges. Verified the mark
  renders as the real HTML5 logo at size.

groupReducer + webviewCss updated: the node paints the ring-check and drops its
border. Gate green (22 suites).
Copilot AI review requested due to automatic review settings July 23, 2026 18:27

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

extensions/levelcode-ai/media/chat.html:1470

  • The /rme reminder text is intentionally obfuscated as base64 and decoded at runtime. Shipping obfuscated user-visible strings in the webview makes the code harder to audit, localize, and review for unwanted content, and can trigger security review concerns. Prefer keeping the reminder text as plain source (or as a localized/resource constant) and avoid atob/TextDecoder here unless there’s a strong, documented reason.
  // A remind-me line. The words are kept out of the source as base64 and decoded only here, so
  // they surface only when someone runs /rme — not to anyone scanning the file. UTF-8 aware decode
  // (the text is Cyrillic), so atob's byte string is run back through TextDecoder.
  var RME_B64 = '0KHQsNC80L7QtNC+0YHRgtCw0YLQvdGW0YHRgtGMLCDQt9Cx0YPQtNC+0LLQsNC90LAsINGJ0L7QsSDRg9C90LjQutC90YPRgtC4INC/0L7QutC40L3Rg9GC0L7RgdGC0ZYsIOKAlCDRhtC1INGB0LDQvNC1INGC0LUsINGJ0L4g0YLRgNC40LzQsNGUINC70Y7QtNC10Lkg0L3QsCDQstGW0LTRgdGC0LDQvdGWLg==';
  function rmeText(){
    try { return new TextDecoder().decode(Uint8Array.from(atob(RME_B64), function(c){ return c.charCodeAt(0); })); }
    catch (e) { return ''; }

extensions/levelcode-ai/media/chat.html:2504

  • This change introduces a new local slash command /rme and its associated UI output, but the PR description/spec for calm transcript doesn’t mention adding new commands. If /rme is intended to ship, it should be documented (discoverability + maintenance); otherwise consider removing it from this PR to keep scope focused on transcript voice/grouping.
    // Slash commands — handled locally (deterministic, no LLM call).
    if (/^\/skills\b/i.test(t)){ add('user', esc(t)); forceStick(); input.value = ''; auto(); vscode.postMessage({ type: 'listSkills' }); return; }
    if (/^\/rme\b/i.test(t)){ add('user', esc(t)); forceStick(); input.value = ''; auto(); addReminder(); return; }
    add('user', render(t)); forceStick(); input.value = ''; auto();

extensions/levelcode-ai/agent.js:46

  • The system prompt says read_file/search MUST include an "explanation" that becomes the action label, but the tool input_schemas don’t require it. Marking explanation as required here makes the constraint enforceable at the tool-schema level (and improves model compliance) instead of relying only on prompt wording.
	{ name: 'read_file', description: 'Read a workspace file (path relative to the workspace root; in a multi-root workspace use the folder-name prefix exactly as list_files shows it).', input_schema: { type: 'object', properties: { path: { type: 'string' }, explanation: { type: 'string', description: 'ALWAYS provide: 3-8 words, active voice, imperative — WHY you are reading this ("Read the runAgent call site", "Read agent.js tool definitions"). Shown to the user as this action\'s label.' } }, required: ['path'] } },
	{ name: 'search', description: 'Search file contents for a literal string. Returns file:line snippets.', input_schema: { type: 'object', properties: { query: { type: 'string' }, explanation: { type: 'string', description: 'ALWAYS provide: 3-8 words, active voice, imperative — what you are looking for ("Find every postMessage call site"). Shown to the user as this action\'s label.' } }, required: ['query'] } },

extensions/levelcode-ai/agent.js:51

  • Same as read_file/search: the prompt and schema text say run_command explanation is ALWAYS provided and it drives the user-visible activity label, but the JSON schema doesn’t require it. Adding it to required makes the tool contract match the "MUST" wording and reduces unlabeled command cards.
	{ name: 'run_command', description: 'Run a shell command in the workspace root (or a named workspace folder via "folder" in multi-root workspaces). Requires approval. Pass background:true for commands that do not exit on their own (servers, watchers) so the agent is not blocked — it returns immediately and you read progress later with read_command_output.', input_schema: { type: 'object', properties: { command: { type: 'string' }, explanation: { type: 'string', description: 'ALWAYS provide: 5-10 words, active voice, imperative — what this command does ("Run the extension unit tests", "Find the insertion point in section 10"). Shown to the user as this action\'s label.' }, folder: { type: 'string', description: 'multi-root workspaces only: the workspace folder NAME to run in; defaults to the first folder' }, background: { type: 'boolean', description: 'true = start it and keep working without waiting (dev servers, watchers, tail -f). Returns immediately with an id; poll read_command_output for its output/status.' } }, required: ['command'] } },

@ndemianc ndemianc changed the title feat(ai): calm transcript — narrative voice + grouped activity feat(ai): calm transcript — narrative voice, grouped activity, and chat-UI polish Jul 23, 2026
…ema, leaks

Copilot review of the calm-transcript PR surfaced several real issues:

- **Running members were collapsed on insertion**, hiding the live command's
  output behind a chevron — the opposite of the intended fixed-footprint header
  (finished members fold, the running one stays readable). groupAppend now folds
  only non-running members; groupStepDone folds a member when it completes
  successfully. This is the behavior the original screenshots asked for.

- **A group could claim to be finished while a background command still ran.**
  finalizeGroup now detects a still-running member, keeps the live header and the
  Stop control, and lets that member's exit re-enter and settle the group — so a
  running command is never misrepresented as done nor loses its only Stop button.

- **Grouped edit cards rendered with the full diff open** (groupAppend ran before
  the card's innerHTML existed, so collapseMember couldn't close it). New grouped
  edit cards now default the diff closed — a one-line row.

- **The tooltip attribute wasn't quote-safe.** search chips carry `search "foo"`;
  esc() doesn't escape quotes, so the raw " closed the title="…" attribute. Uses
  escAttr now.

- **`explanation` is now schema-required** on run_command / read_file / search,
  matching the "MUST include" contract (was prose-only; schema allowed omission).

- **Step-map leaks**: editSteps was never cleared (now released in resolveEditCard
  and pruned on checkpoint restore alongside its card); termSteps leaked on
  termExitFinish's early return (now settled + deleted there too).

- **Aggregate accuracy**: listing files was folded into "searched the workspace";
  it's now its own "listed files" clause.

- **groupActivity off mid-run** now closes any open group so the timeline returns
  fully flat immediately.

Kept intentionally (product owner's explicit request): the `/rme` command and its
base64 payload — see the reply on that thread.

groupReducer +3 tests (running-stays-expanded, still-running-close, quote-escape),
narrativeUi +1 (list ≠ search). Every change verified in a rendered webview with
an actually-running command. Gate green (22 suites).
Copilot AI review requested due to automatic review settings July 23, 2026 18:56
@ndemianc

Copy link
Copy Markdown
Contributor Author

Addressed the review in b655f95. Summary:

Real bugs fixed

  • Running members no longer collapse on insert — the running command stays expanded (live output visible); finished members fold. Collapse-on-success moved to completion time. Verified in a rendered webview with an actually-running command.
  • A group can no longer claim "done" while a background command runsfinalizeGroup keeps the live header + Stop control and settles only when the member exits.
  • Grouped edit cards render as a one-line row (diff closed) instead of full-open.
  • Tooltip title is quote-safe (escAttr) — search chips carry ".
  • explanation is schema-required on run_command / read_file / search.
  • Step-map leaks closed: editSteps released on resolve + pruned on checkpoint restore; termSteps settled on the early-return path.
  • Aggregate accuracy: "listed files" is its own clause, not "searched the workspace".
  • groupActivity off unwraps any open group so the timeline is fully flat immediately.

Kept intentionally

  • /rme and its encoded string — explicit product-owner request; local-only, unadvertised, commented. Detail on that thread.

Tests: groupReducer +3, narrativeUi +1. Full extension gate green (22 suites).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

extensions/levelcode-ai/agent.js:51

  • The SYSTEM_BASE rules describe explanation as "3–10 words" for run_command/read_file/search, but the run_command schema currently says "5–10 words". Aligning these avoids conflicting constraints in the prompt+schema.
	{ name: 'run_command', description: 'Run a shell command in the workspace root (or a named workspace folder via "folder" in multi-root workspaces). Requires approval. Pass background:true for commands that do not exit on their own (servers, watchers) so the agent is not blocked — it returns immediately and you read progress later with read_command_output.', input_schema: { type: 'object', properties: { command: { type: 'string' }, explanation: { type: 'string', description: 'REQUIRED: 5-10 words, active voice, imperative — what this command does ("Run the extension unit tests", "Find the insertion point in section 10"). Shown to the user as this action\'s label.' }, folder: { type: 'string', description: 'multi-root workspaces only: the workspace folder NAME to run in; defaults to the first folder' }, background: { type: 'boolean', description: 'true = start it and keep working without waiting (dev servers, watchers, tail -f). Returns immediately with an id; poll read_command_output for its output/status.' } }, required: ['command', 'explanation'] } },

Comment thread extensions/levelcode-ai/agent.js
Comment thread docs/CALM-TRANSCRIPT.md Outdated
Comment thread docs/CALM-TRANSCRIPT.md Outdated
Comment thread docs/CALM-TRANSCRIPT.md Outdated
Second review pass flagged doc drift and a word-count mismatch:

- The `explanation` length was three different numbers — SYSTEM_BASE said 3-10,
  run_command's schema 5-10, read_file/search 3-8, the doc 5-10. Unified to
  **3-8 words** everywhere (prompt, all three schemas, D6). Every example given
  already fits that range.

- D3 claimed "no event schema changes" — but the `agentTool` payload gained
  `label`/`kind`/`path` (types unchanged, fields added, filled by agent.js).
  Reworded to say exactly that.

- "Not doing (yet)" still listed model-written labels for reads/searches as
  unbuilt; this PR implements them (required `explanation`, used as the row
  label). Removed that bullet and updated §1's baseline note to point at D6/S1.

Docs + prompt only; no behavior change. Gate green.
Copilot AI review requested due to automatic review settings July 23, 2026 19:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comment thread extensions/levelcode-ai/media/chat.html
…t restore

Review caught that the new calm-transcript state outlives a log wipe:

- The `reset` handler cleared the log HTML and the card/step maps but left
  `curGroup` pointing at the now-detached `.groupbody` and `turnLabeled` set.
  The next run would append into disconnected DOM (nothing shows), and the first
  assistant line would render as an unlabeled continuation. Reset both now.

- `applyCheckpointRestored` removes this turn's (and later turns') DOM and pruned
  only the card maps — the step maps and a possibly-stranded `curGroup` were left
  behind. It now prunes editSteps/termSteps/verifySteps too and drops the group
  if its element was removed. (This also completes the earlier request to prune
  step maps on checkpoint restore, not just on reset.)

- `pruneDetached` is now dual-mode: it follows `.card` for step-map values, so
  the same helper works for both card and step maps.

webviewCss +2 static guards. Gate green (22 suites).
Copilot AI review requested due to automatic review settings July 23, 2026 19:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

extensions/levelcode-ai/media/chat.html:1647

  • When a group is closed while a member is still running, closeGroup() sets curGroup = null and then finalizeGroup() calls refreshGroupHead(). Because refreshGroupHead() always reads from curGroup, that call becomes a no-op and the closed-but-still-running group header won’t refresh (label/counts/stop wiring) as the underlying step state changes.
  function refreshGroupHead(){
    const g = curGroup; if (!g){ return; }

extensions/levelcode-ai/media/chat.html:1679

  • finalizeGroup() tries to keep the header live when a member is still running, but it calls refreshGroupHead() after closeGroup() has already nulled curGroup, so the refresh is skipped. After updating refreshGroupHead to accept an explicit group, pass the group here so the closed group’s header stays in sync until the running member exits.
    if (g.steps.some((s) => s.status === 'running')){
      g.el.classList.remove('collapsed');   // never hide a command that is still going
      refreshGroupHead();
      return;

@ndemianc
ndemianc merged commit 3c6833f into develop Jul 23, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants