Skip to content

Commit c03a2fe

Browse files
committed
feat(levelcode-ai): circle-check glyph everywhere a check is shown
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).
1 parent ba146d7 commit c03a2fe

2 files changed

Lines changed: 50 additions & 16 deletions

File tree

extensions/levelcode-ai/media/chat.html

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -976,12 +976,12 @@
976976
<button type="button" class="modeopt" role="option" data-agent="1">
977977
<span class="moico mono">&lt;/&gt;</span>
978978
<span class="motext"><span class="moname">Agent</span><span class="modesc">Edits files across your workspace</span></span>
979-
<span class="mocheck">&#10003;</span>
979+
<span class="mocheck" data-ico="check-circle"></span>
980980
</button>
981981
<button type="button" class="modeopt" role="option" data-agent="0">
982982
<span class="moico" aria-hidden="true"><svg class="ci" viewBox="0 0 16 16" fill="currentColor"><path d="M8 2c3.31 0 6 2.24 6 5s-2.69 5-6 5c-.6 0-1.19-.07-1.74-.2L3.5 13.5l.66-2.3C3.13 10.3 2 8.77 2 7c0-2.76 2.69-5 6-5Z"/></svg></span>
983983
<span class="motext"><span class="moname">Chat</span><span class="modesc">Ask questions — no file edits</span></span>
984-
<span class="mocheck">&#10003;</span>
984+
<span class="mocheck" data-ico="check-circle"></span>
985985
</button>
986986
</div>
987987
</span>
@@ -1008,12 +1008,12 @@
10081008
<button type="button" class="modeopt" role="option" data-autopilot="0">
10091009
<span class="moico" data-ico="shield" aria-hidden="true"></span>
10101010
<span class="motext"><span class="moname">Default Approvals</span><span class="modesc">Asks before running each command</span></span>
1011-
<span class="mocheck">&#10003;</span>
1011+
<span class="mocheck" data-ico="check-circle"></span>
10121012
</button>
10131013
<button type="button" class="modeopt" role="option" data-autopilot="1">
10141014
<span class="moico" data-ico="rocket" aria-hidden="true"></span>
10151015
<span class="motext"><span class="moname">Autopilot</span><span class="modesc">Runs commands for you — only irreversible ones ask</span></span>
1016-
<span class="mocheck">&#10003;</span>
1016+
<span class="mocheck" data-ico="check-circle"></span>
10171017
</button>
10181018
</div>
10191019
</span>
@@ -1149,6 +1149,10 @@
11491149
'warning': { vb: '0 0 16 16', p: '<path d="M14.831 11.965L9.206 1.714C8.965 1.274 8.503 1 8 1C7.497 1 7.035 1.274 6.794 1.714L1.169 11.965C1.059 12.167 1 12.395 1 12.625C1 13.383 1.617 14 2.375 14H13.625C14.383 14 15 13.383 15 12.625C15 12.395 14.941 12.167 14.831 11.965ZM13.625 13H2.375C2.168 13 2 12.832 2 12.625C2 12.561 2.016 12.5 2.046 12.445L7.671 2.195C7.736 2.075 7.863 2 8 2C8.137 2 8.264 2.075 8.329 2.195L13.954 12.445C13.984 12.501 14 12.561 14 12.625C14 12.832 13.832 13 13.625 13ZM8.75 11.25C8.75 11.664 8.414 12 8 12C7.586 12 7.25 11.664 7.25 11.25C7.25 10.836 7.586 10.5 8 10.5C8.414 10.5 8.75 10.836 8.75 11.25ZM7.5 9V5.5C7.5 5.224 7.724 5 8 5C8.276 5 8.5 5.224 8.5 5.5V9C8.5 9.276 8.276 9.5 8 9.5C7.724 9.5 7.5 9.276 7.5 9Z"/>' },
11501150
'edit': { vb: '0 0 16 16', p: '<path d="M14.236 1.76386C13.2123 0.740172 11.5525 0.740171 10.5289 1.76386L2.65722 9.63549C2.28304 10.0097 2.01623 10.4775 1.88467 10.99L1.01571 14.3755C0.971767 14.5467 1.02148 14.7284 1.14646 14.8534C1.27144 14.9783 1.45312 15.028 1.62432 14.9841L5.00978 14.1151C5.52234 13.9836 5.99015 13.7168 6.36433 13.3426L14.236 5.47097C15.2596 4.44728 15.2596 2.78755 14.236 1.76386ZM11.236 2.47097C11.8691 1.8378 12.8957 1.8378 13.5288 2.47097C14.162 3.10413 14.162 4.1307 13.5288 4.76386L12.75 5.54269L10.4571 3.24979L11.236 2.47097ZM9.75002 3.9569L12.0429 6.24979L5.65722 12.6355C5.40969 12.883 5.10023 13.0595 4.76117 13.1465L2.19447 13.8053L2.85327 11.2386C2.9403 10.8996 3.1168 10.5901 3.36433 10.3426L9.75002 3.9569Z"/>' },
11511151
'check': { vb: '0 0 16 16', p: '<path d="M13.6572 3.13573C13.8583 2.9465 14.175 2.95614 14.3643 3.15722C14.5535 3.35831 14.5438 3.675 14.3428 3.86425L5.84277 11.8642C5.64597 12.0494 5.33756 12.0446 5.14648 11.8535L1.64648 8.35351C1.45121 8.15824 1.45121 7.84174 1.64648 7.64647C1.84174 7.45121 2.15825 7.45121 2.35351 7.64647L5.50976 10.8027L13.6572 3.13573Z"/>' },
1152+
// check-in-a-ring, drawn as strokes (fill:none overrides the svg-level fill=currentColor) — the
1153+
// outline look from the design: a thin ring with a rounded, slightly bolder tick. currentColor,
1154+
// so it inherits green in "done" contexts and the accent in the menu.
1155+
'check-circle': { vb: '0 0 16 16', p: '<circle cx="8" cy="8" r="6.35" fill="none" stroke="currentColor" stroke-width="1"/><path d="M5.05 8.25 L7 10.2 L11.15 5.85" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>' },
11521156
'circle-slash': { vb: '0 0 16 16', p: '<path d="M11.8746 3.41833C9.51718 1.42026 5.98144 1.53327 3.75736 3.75736C1.53327 5.98144 1.42026 9.51719 3.41833 11.8746L11.8746 3.41833ZM12.5817 4.12543L4.12543 12.5817C6.48282 14.5797 10.0186 14.4667 12.2426 12.2426C14.4667 10.0186 14.5797 6.48282 12.5817 4.12543ZM3.05025 3.05025C5.78392 0.316582 10.2161 0.316582 12.9497 3.05025C15.6834 5.78392 15.6834 10.2161 12.9497 12.9497C10.2161 15.6834 5.78392 15.6834 3.05025 12.9497C0.316583 10.2161 0.316582 5.78392 3.05025 3.05025Z"/>' },
11531157
'stop-circle': { vb: '0 0 16 16', p: '<path d="M6 5C5.44772 5 5 5.44772 5 6V10C5 10.5523 5.44772 11 6 11H10C10.5523 11 11 10.5523 11 10V6C11 5.44772 10.5523 5 10 5H6ZM1 8C1 4.13401 4.13401 1 8 1C11.866 1 15 4.13401 15 8C15 11.866 11.866 15 8 15C4.13401 15 1 11.866 1 8ZM8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2Z"/>' },
11541158
'shield': { vb: '0 0 16 16', p: '<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"/>' },
@@ -1408,7 +1412,7 @@
14081412
btn.title = 'Copy this response'; btn.innerHTML = codicon('copy') + ' Copy';
14091413
btn.onclick = () => {
14101414
vscode.postMessage({ type: 'copy', text: raw });
1411-
btn.innerHTML = codicon('check') + ' Copied'; btn.classList.add('done');
1415+
btn.innerHTML = codicon('check-circle') + ' Copied'; btn.classList.add('done');
14121416
try { clearTimeout(btn._t); } catch(e){}
14131417
btn._t = setTimeout(() => { btn.innerHTML = codicon('copy') + ' Copy'; btn.classList.remove('done'); }, 1200);
14141418
};
@@ -1777,7 +1781,7 @@
17771781
card.querySelector('.tl-body').innerHTML =
17781782
'<div class="cmdhead"><span class="cmdverb">' + (approved ? 'Approved' : 'Skipped') + '</span>'
17791783
+ '<span class="cmdchips">' + chips + '</span>'
1780-
+ '<span class="cmdstate ' + (approved ? 'ok' : 'bad') + '">' + codicon(approved ? 'check' : 'circle-slash') + '</span></div>';
1784+
+ '<span class="cmdstate ' + (approved ? 'ok' : 'bad') + '">' + codicon(approved ? 'check-circle' : 'circle-slash') + '</span></div>';
17811785
// Deciding is user intent to continue (like send), so re-engage auto-scroll for the run output
17821786
// that follows — by here `stuck` is usually false, since reading the command scrolls the log up.
17831787
// Must run after the collapse above, not before: that shrink clamps scrollTop down on its own,
@@ -1888,7 +1892,7 @@
18881892
let icon, text, cls;
18891893
if (m.how === 'stopped'){ icon = 'circle-slash'; text = 'stopped'; cls = 'bad'; }
18901894
else if (m.how === 'timeout'){ icon = 'warning'; text = 'timed out'; cls = 'bad'; }
1891-
else { const ok = m.code === 0; icon = ok ? 'check' : 'circle-slash'; text = 'exit ' + m.code; cls = ok ? 'ok' : 'bad'; }
1895+
else { const ok = m.code === 0; icon = ok ? 'check-circle' : 'circle-slash'; text = 'exit ' + m.code; cls = ok ? 'ok' : 'bad'; }
18921896
st.innerHTML = codicon(icon) + ' ' + text + (dur ? ' · ' + dur : '');
18931897
st.className = 'termstatus ' + cls;
18941898
card.classList.remove('running'); // stops the node pulse
@@ -1940,7 +1944,7 @@
19401944
st.innerHTML = codicon('warning') + ' verify command couldn’t run — check settings';
19411945
st.className = 'termstatus'; // neutral: it’s a config issue, not a code failure
19421946
} else if (m.ok){
1943-
st.innerHTML = codicon('check') + ' verified — no problems';
1947+
st.innerHTML = codicon('check-circle') + ' verified — no problems';
19441948
st.className = 'termstatus ok';
19451949
} else if (m.exhausted){
19461950
st.innerHTML = codicon('warning') + ' still failing — left for you to review';
@@ -2084,8 +2088,8 @@
20842088
card.querySelectorAll('.qopt').forEach((b) => { b.disabled = true; });
20852089
card.querySelector('.qnotes').disabled = true;
20862090
const title = card.querySelector('.qtitle');
2087-
if (title){ title.innerHTML = codicon('check') + ' Answers recorded — click to review'; }
2088-
card.querySelector('.qbtns').innerHTML = '<span class="qverdict">' + codicon('check') + ' Answers recorded</span>';
2091+
if (title){ title.innerHTML = codicon('check-circle') + ' Answers recorded — click to review'; }
2092+
card.querySelector('.qbtns').innerHTML = '<span class="qverdict">' + codicon('check-circle') + ' Answers recorded</span>';
20892093
};
20902094
render();
20912095
}
@@ -2149,9 +2153,9 @@
21492153
const isDel = card.classList.contains('deletedcard');
21502154
const map = isDel
21512155
? { kept: codicon('trash') + ' Deleted', undone: codicon('circle-slash') + ' Recreated' }
2152-
: { kept: codicon('check') + ' Kept', undone: codicon('circle-slash') + ' Undid', 'kept-user-edited': codicon('edit') + ' Kept (you edited it)' };
2156+
: { kept: codicon('check-circle') + ' Kept', undone: codicon('circle-slash') + ' Undid', 'kept-user-edited': codicon('edit') + ' Kept (you edited it)' };
21532157
const btns = card.querySelector('.abtns');
2154-
if (btns){ btns.innerHTML = '<span class="verdict">' + (map[m.verdict] || (isDel ? codicon('trash') + ' Deleted' : codicon('check') + ' Kept')) + '</span>'; }
2158+
if (btns){ btns.innerHTML = '<span class="verdict">' + (map[m.verdict] || (isDel ? codicon('trash') + ' Deleted' : codicon('check-circle') + ' Kept')) + '</span>'; }
21552159
const det = card.querySelector('details.diffwrap'); if (det) { det.open = false; } // collapse so resolved cards don't stack tall
21562160
card.classList.add('resolved');
21572161
delete editCards[m.id];
@@ -2338,7 +2342,7 @@
23382342
let items = '';
23392343
for (const t of todos){
23402344
const st = t.status === 'done' ? 'done' : t.status === 'in_progress' ? 'doing' : 'todo';
2341-
const ic = st === 'done' ? codicon('check') : st === 'doing' ? codicon('sync') : codicon('circle');
2345+
const ic = st === 'done' ? codicon('check-circle') : st === 'doing' ? codicon('sync') : codicon('circle');
23422346
items += '<div class="pitem ' + st + '"><span class="pic">' + ic + '</span><span class="ptitle">' + esc(t.title) + '</span></div>';
23432347
}
23442348
const head = '<div class="phead"><span class="ptoggle">' + codicon('chevron-down') + '</span>'
@@ -2497,8 +2501,9 @@
24972501
const approvalsLabel = document.getElementById('approvalsLabel');
24982502
const approvalsIco = document.getElementById('approvalsIco');
24992503
let autopilotOn = false;
2500-
// Paint the menu option glyphs from the codicon set once (shield / rocket).
2501-
approvalsMenu.querySelectorAll('.moico[data-ico]').forEach((s) => { s.innerHTML = codicon(s.getAttribute('data-ico')); });
2504+
// Paint every menu glyph from the codicon set once — option icons (shield / rocket) and the
2505+
// selected-tick (check-circle) in both the approvals and mode menus.
2506+
document.querySelectorAll('[data-ico]').forEach((s) => { s.innerHTML = codicon(s.getAttribute('data-ico')); });
25022507
function syncApprovalsMenu(){
25032508
approvalsMenu.querySelectorAll('.modeopt').forEach((b) => {
25042509
b.classList.toggle('active', (b.getAttribute('data-autopilot') === '1') === autopilotOn);
@@ -2627,7 +2632,7 @@
26272632
+ upgradeBtn
26282633
+ '<button class="abtn" data-act="manage">Manage account</button>'
26292634
+ '<button class="abtn" data-act="signout">Sign out</button>'
2630-
+ '<div class="synced">' + codicon('check') + ' Synced: settings · skills · keymaps</div>'
2635+
+ '<div class="synced">' + codicon('check-circle') + ' Synced: settings · skills · keymaps</div>'
26312636
+ privacyNote(gateway);
26322637
} else {
26332638
acctCard.classList.add('lc-modal'); // signed-out → the calm LevelCode sign-in modal

extensions/levelcode-ai/test/webviewCss.test.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,33 @@ test('collapsing the ask_user card outranks the wizard rule that reveals a quest
8383
assert.ok(override > wizardRule, 'the override must come later to win at equal specificity');
8484
});
8585

86+
// The circle-check glyph is one icon shared by every "done" tick and the menu's selected mark.
87+
// Pin its shape and its reach so a refactor can't quietly drop it back to a bare check.
88+
test('the check-circle glyph is an outline: a ring AND a stroked tick', () => {
89+
const m = /'check-circle':\s*\{[^}]*p:\s*'([^']*)'/.exec(html);
90+
assert.ok(m, "IC no longer defines 'check-circle'");
91+
const p = m[1];
92+
assert.ok(/<circle[^>]*stroke="currentColor"/.test(p), 'the ring is drawn as a stroke');
93+
assert.ok(/<path[^>]*fill="none"[^>]*stroke="currentColor"/.test(p), 'the tick is a stroked path, not a fill');
94+
});
95+
96+
test('the menu selected-tick uses the circle-check, not a literal ✓', () => {
97+
const ticks = html.match(/<span class="mocheck"[^>]*>/g) || [];
98+
assert.ok(ticks.length >= 2, 'menu tick spans still present');
99+
assert.ok(ticks.every((t) => /data-ico="check-circle"/.test(t)), 'every mocheck is painted from the glyph');
100+
assert.ok(!/mocheck[^>]*>&#10003;/.test(html) && !/mocheck[^>]*>\s*/.test(html), 'no literal check left in the menu');
101+
// and the paint pass is broad enough to reach them (not just .moico)
102+
assert.ok(/querySelectorAll\('\[data-ico\]'\)[\s\S]{0,80}codicon\(/.test(html),
103+
'the [data-ico] paint pass must cover .mocheck, not only .moico');
104+
});
105+
106+
test('every status tick migrated — the only bare codicon(\'check\') left is the group rail node', () => {
107+
// The group node is intentionally a bare check inside .tl-node (already a ring); a glyph-ring
108+
// there would double up. Everything else must be the circle-check.
109+
const bare = [...html.matchAll(/codicon\('check'\)/g)].length;
110+
const nodeLine = /g\.node\.innerHTML = codicon\(g\.failed \? 'circle-slash' : 'check'\)/.test(html);
111+
assert.strictEqual(bare, 0, "no plain codicon('check') should remain — use codicon('check-circle')");
112+
assert.ok(nodeLine, 'the group rail node keeps its ternary check (it supplies its own ring)');
113+
});
114+
86115
console.log('webviewCss: ' + n + ' tests passed');

0 commit comments

Comments
 (0)