Skip to content

Commit c29de36

Browse files
ndemiancclaude
andcommitted
feat(ai): finished-plan auto-collapse + dismiss; drop the footer "LevelCode Cloud" chip
Two chat-UI cosmetics (both in media/chat.html, non-overlapping regions). Plan bar — a completed checklist used to sit fully expanded (up to 38vh) long after the run ended, with no way to clear it. Now, matching how Claude Code / Cursor handle a finished plan: - auto-collapse to the (green) header the moment it first hits N/N — rising-edge latch, so a manual re-expand of a done plan sticks on later re-renders, and the state resets when the plan is cleared (agentStart / newChat / dismiss); - a dismiss (×) in the header closes it outright — reveals on header hover, stays visible once the plan is done (its primary use), and also clears a plan stranded by an aborted/errored run. stopPropagation so it doesn't also toggle the collapse. The × is a styled literal, not codicon('close') — 'close' isn't in the webview's codicon subset (it would render as text). Footer — removed the st-local item (home icon + "LevelCode Cloud" / "Local" / "Direct"). The st-key chip already conveys the mode + plan ("Gateway · Pro+", "BYO key", "Direct · no key"), so st-local was redundant; renderRouting now drives st-key only. Updated the three comments that referenced st-local. Verified: every <script> block in chat.html parses (0 syntax errors); webviewCss + creditFormat suites (which parse chat.html) still green; full gate 24 suites. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c1f778d commit c29de36

1 file changed

Lines changed: 22 additions & 10 deletions

File tree

extensions/levelcode-ai/media/chat.html

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,10 @@
555555
.plancard .phtitle { flex: 1 1 auto; min-width: 0; }
556556
.plancard .pcount { flex: 0 0 auto; opacity: .75; font-weight: 500; font-variant-numeric: tabular-nums; font-family: var(--vscode-editor-font-family, ui-monospace, monospace); font-size: 11px; padding: 1px 8px; border-radius: 999px; background: rgba(127,127,127,.14); }
557557
.plancard.alldone .pcount { opacity: 1; color: var(--vscode-gitDecoration-addedResourceForeground, #4ec97a); background: color-mix(in srgb, var(--vscode-gitDecoration-addedResourceForeground, #4ec97a) 16%, transparent); }
558+
/* Dismiss (×): reveals on header hover, and stays visible once the plan is done (its primary use). */
559+
.plancard .pdismiss { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; color: var(--muted); font-size: 14px; line-height: 1; opacity: 0; cursor: pointer; border-radius: 6px; transition: opacity .12s, background .12s; }
560+
.plancard .phead:hover .pdismiss, .plancard.alldone .pdismiss { opacity: .55; }
561+
.plancard .pdismiss:hover { opacity: 1; background: rgba(127,127,127,.16); }
558562
.plancard .pprog { height: 3px; margin: 0 12px 2px; border-radius: 3px; background: rgba(127,127,127,.16); overflow: hidden; }
559563
.plancard .pprog .pfill { height: 100%; border-radius: 3px; background: var(--accent); transition: width .4s cubic-bezier(.4,0,.2,1); }
560564
.plancard.alldone .pprog .pfill { background: var(--vscode-gitDecoration-addedResourceForeground, #4ec97a); }
@@ -700,7 +704,7 @@
700704

701705
/* Group the plan/mode chip (st-key, e.g. "Gateway · Pro+") beside the account avatar on the
702706
RIGHT — a Claude-Code-style cluster. Pure flex order, so no DOM move of the big inline SVGs:
703-
st-local + context ring stay left; grow pushes; st-key then the avatar sit together right. */
707+
the context ring stays left; grow pushes; st-key then the avatar sit together right. */
704708
#status .grow { order: 1; }
705709
#status #st-key { order: 2; }
706710
#status .acct { order: 3; }
@@ -737,7 +741,7 @@
737741
.lc-spin .c1 { opacity: .5; } .lc-spin .c2 { opacity: .78; } .lc-spin .c3 { opacity: 1; }
738742
}
739743
#status .item { display: inline-flex; align-items: center; gap: 4px; }
740-
/* Approvals dropdown, seated in the status bar between LevelCode Cloud (st-local) and the context ring.
744+
/* Approvals dropdown, seated in the status bar just before the context ring.
741745
Its menu reuses .modemenu and opens UPWARD (the status bar is at the very bottom). */
742746
#status .st-approvals { position: relative; display: inline-flex; }
743747
#status .st-approvals[hidden] { display: none; }
@@ -1005,7 +1009,6 @@
10051009
<path d="M29,71 L60,49 L91,71" opacity=".78"/>
10061010
<path d="M29,50 L60,28 L91,50"/>
10071011
</svg>
1008-
<span class="item" id="st-local" title="Requests go directly from your machine to the provider — no LevelCode backend."><svg class="ci" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M7.31299 1.26164C7.69849 0.897163 8.30151 0.897163 8.68701 1.26164L13.5305 5.84098C13.8302 6.12431 14 6.51853 14 6.93094V12.5002C14 13.3286 13.3284 14.0002 12.5 14.0002H10.5C9.67157 14.0002 9 13.3286 9 12.5002V10.0002C9 9.72407 8.77614 9.50021 8.5 9.50021H7.5C7.22386 9.50021 7 9.72407 7 10.0002V12.5002C7 13.3286 6.32843 14.0002 5.5 14.0002H3.5C2.67157 14.0002 2 13.3286 2 12.5002V6.93094C2 6.51853 2.1698 6.12431 2.46948 5.84098L7.31299 1.26164ZM8 1.98828L3.15649 6.56762C3.0566 6.66207 3 6.79347 3 6.93094V12.5002C3 12.7763 3.22386 13.0002 3.5 13.0002H5.5C5.77614 13.0002 6 12.7763 6 12.5002V10.0002C6 9.17179 6.67157 8.50022 7.5 8.50022H8.5C9.32843 8.50022 10 9.17179 10 10.0002V12.5002C10 12.7763 10.2239 13.0002 10.5 13.0002H12.5C12.7761 13.0002 13 12.7763 13 12.5002V6.93094C13 6.79347 12.9434 6.66207 12.8435 6.56762L8 1.98828Z"/></svg> Local</span>
10091012
<span class="item" id="st-key" title="Bring your own API key, stored in your OS keychain."><svg class="ci" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M9.25 7.25C9.25 7.76258 8.94148 8.2031 8.5 8.39599V9.50358C8.5 9.77973 8.27614 10.0036 8 10.0036C7.72386 10.0036 7.5 9.77973 7.5 9.50358V8.39599C7.05852 8.2031 6.75 7.76258 6.75 7.25C6.75 6.55964 7.30964 6 8 6C8.69036 6 9.25 6.55964 9.25 7.25ZM7.14309 2.04175C6.78097 2.2883 6.21583 2.61563 5.42482 2.91681C4.6399 3.21566 3.90375 3.36204 3.36353 3.43333C3.09405 3.46889 2.87509 3.48554 2.72547 3.49331C2.6507 3.49719 2.5934 3.49885 2.55593 3.49954L2.50489 3.50003C2.37157 3.49872 2.24323 3.55072 2.14842 3.64449C2.05344 3.73841 2 3.86643 2 4V6.75508C2 9.40779 3.4013 11.8632 5.68525 13.2124L7.74707 14.4305C7.90399 14.5232 8.09891 14.5232 8.2558 14.4304L10.3162 13.2126C12.5993 11.8632 14 9.40823 14 6.7561V4C14 3.86598 13.9462 3.73757 13.8507 3.64358C13.7552 3.54964 13.626 3.49794 13.4921 3.50007L13.4421 3.49981C13.4048 3.49928 13.3478 3.49787 13.2735 3.49426C13.1246 3.48705 12.9066 3.47109 12.6384 3.43602C12.1006 3.36573 11.3679 3.21959 10.5869 2.91771C9.79733 2.61248 9.22913 2.28442 8.86335 2.03774C8.68039 1.91435 8.54795 1.81124 8.46371 1.74141C8.4256 1.70981 8.38768 1.6777 8.35191 1.64343C8.2576 1.55073 8.13037 1.49913 7.99807 1.50001C7.86585 1.50089 7.73916 1.55434 7.64611 1.64819L7.53744 1.74536C7.45475 1.81517 7.32423 1.91842 7.14309 2.04175ZM3 6.75508V4.47725C3.14066 4.46608 3.30705 4.44945 3.49436 4.42473C4.09055 4.34605 4.90577 4.18447 5.78065 3.85136C6.64943 3.52057 7.28362 3.15585 7.70588 2.86835C7.82102 2.78996 7.92034 2.71735 8.00434 2.65277C8.08878 2.71677 8.18861 2.78886 8.30421 2.86682C8.72757 3.15233 9.362 3.51631 10.2264 3.85045C11.0994 4.18794 11.9134 4.34976 12.5088 4.42759C12.6947 4.45189 12.86 4.46809 13 4.47888V6.7561C13 9.05461 11.7861 11.1822 9.80736 12.3518L8.00133 13.4192L6.19388 12.3515C4.21446 11.1821 3 9.0541 3 6.75508Z"/></svg> Direct · BYO key</span>
10101013
<span class="st-approvals" id="approvalsWrap">
10111014
<span class="item stap" id="approvals" role="button" tabindex="0" aria-haspopup="listbox" aria-expanded="false" title="How the agent handles command approvals">
@@ -2377,24 +2380,36 @@
23772380
renderBgTasks();
23782381
}
23792382
const planBar = document.getElementById('planBar');
2380-
let planCollapsed = false; // persists across the agent's repeated update_plan re-renders
2383+
let planCollapsed = false; // persists across the agent's repeated update_plan re-renders (within a run)
2384+
let planWasDone = false; // rising-edge latch → auto-collapse a finished plan exactly once
23812385
function renderPlan(todos){
2382-
if (!todos || !todos.length){ planBar.style.display = 'none'; planBar.innerHTML = ''; return; }
2386+
// Empty list = clear (agentStart / newChat / dismiss). Reset the collapse state so the NEXT plan
2387+
// starts fresh and expanded rather than inheriting a previous run's collapsed/done latches.
2388+
if (!todos || !todos.length){ planBar.style.display = 'none'; planBar.innerHTML = ''; planCollapsed = false; planWasDone = false; return; }
23832389
const total = todos.length, done = todos.filter(t => t && t.status === 'done').length;
23842390
const pct = Math.round(done / total * 100), allDone = done === total;
2391+
// A finished checklist shouldn't keep hogging up to 38vh: auto-collapse it to the (green) header the
2392+
// moment it first completes. Rising-edge only, so a manual re-expand of a done plan sticks afterward.
2393+
if (allDone && !planWasDone){ planCollapsed = true; }
2394+
planWasDone = allDone;
23852395
let items = '';
23862396
for (const t of todos){
23872397
const st = t.status === 'done' ? 'done' : t.status === 'in_progress' ? 'doing' : 'todo';
23882398
const ic = st === 'done' ? codicon('check-circle') : st === 'doing' ? codicon('sync') : codicon('circle');
23892399
items += '<div class="pitem ' + st + '"><span class="pic">' + ic + '</span><span class="ptitle">' + esc(t.title) + '</span></div>';
23902400
}
23912401
const head = '<div class="phead"><span class="ptoggle">' + codicon('chevron-down') + '</span>'
2392-
+ '<span class="phtitle">Plan</span><span class="pcount">' + done + '/' + total + '</span></div>'
2402+
+ '<span class="phtitle">Plan</span><span class="pcount">' + done + '/' + total + '</span>'
2403+
+ '<span class="pdismiss" title="Dismiss plan" role="button" aria-label="Dismiss plan">&times;</span></div>'
23932404
+ '<div class="pprog"><div class="pfill" style="width:' + pct + '%"></div></div>';
23942405
planBar.innerHTML = '<div class="plancard' + (allDone ? ' alldone' : '') + (planCollapsed ? ' collapsed' : '') + '">'
23952406
+ head + '<div class="pitems">' + items + '</div></div>';
23962407
const phead = planBar.querySelector('.phead');
23972408
if (phead){ phead.onclick = () => { planCollapsed = !planCollapsed; const c = planBar.querySelector('.plancard'); if (c){ c.classList.toggle('collapsed', planCollapsed); } }; }
2409+
// Dismiss (×): close the finished — or abandoned — plan outright. stopPropagation so the header's
2410+
// collapse-toggle doesn't also fire. Clearing resets the latches via the empty-todos branch above.
2411+
const pdismiss = planBar.querySelector('.pdismiss');
2412+
if (pdismiss){ pdismiss.onclick = (e) => { e.stopPropagation(); renderPlan([]); }; }
23982413
planBar.style.display = 'block';
23992414
scrollIfStuck(); // log shrank as the sticky plan appeared — keep pinned if we were at bottom
24002415
}
@@ -2731,7 +2746,7 @@
27312746
renderRouting();
27322747
}
27332748

2734-
// Footer routing indicator: st-local = WHERE requests go, st-key = the key/plan.
2749+
// Footer routing indicator: st-key = the mode + plan (e.g. "Gateway · Pro+", "BYO key", "Direct · no key").
27352750
// Driven by the latest 'config' (provider) + 'account' (signedIn, mode, plan).
27362751
function setStatusItem(id, label, title){
27372752
const el = document.getElementById(id);
@@ -2742,13 +2757,10 @@
27422757
function renderRouting(){
27432758
const gateway = account.signedIn && account.mode === 'gateway';
27442759
if (gateway){
2745-
setStatusItem('st-local', 'LevelCode Cloud', 'Requests run through your metered LevelCode Cloud plan (gateway).');
27462760
setStatusItem('st-key', 'Gateway' + (account.plan ? ' · ' + account.plan : ''), 'Metered gateway — no provider key needed.');
27472761
} else if (lastProvider === 'ollama'){
2748-
setStatusItem('st-local', 'Local', 'Requests go to a local model on your machine (Ollama) — no key, no backend.');
27492762
setStatusItem('st-key', 'Direct · no key', 'Local model — no API key needed.');
27502763
} else {
2751-
setStatusItem('st-local', 'Direct', 'Requests go directly from your machine to your provider — no LevelCode backend.');
27522764
setStatusItem('st-key', 'BYO key', 'Bring your own API key, stored in your OS keychain.');
27532765
}
27542766
}

0 commit comments

Comments
 (0)