dsh-plugin-lookatstudy 0.11.1 → 0.12.1

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.
package/lib/client.js CHANGED
@@ -22,7 +22,14 @@ window.__ModuleLoader__.load({
22
22
  * fonts, and radii come from `--dsw-*` tokens wherever the concept exists.
23
23
  */
24
24
  const STUDY_CSS = `
25
- .lks-root{font-family:var(--dsw-font-family)}
25
+ /* State inks: small colored text must clear WCAG AA (4.5:1), but the host's
26
+ raw state tokens (success/warn primaries) sit at 2.3–2.6:1 on light
27
+ backgrounds, and success/error *label* aliases are not published by this
28
+ host at all (the tv-chip used to reference them and silently inherit).
29
+ Mixing the state color toward label-primary keeps the hue while pulling
30
+ luminance to the readable pole — and works in BOTH themes, because
31
+ label-primary is always the current theme's high-contrast ink. */
32
+ .lks-root{--lks-warn-ink:color-mix(in srgb,var(--dsw-alias-state-warn-label) 45%,var(--dsw-alias-label-primary));--lks-ok-ink:color-mix(in srgb,var(--dsw-alias-state-success-primary) 40%,var(--dsw-alias-label-primary));--lks-err-ink:color-mix(in srgb,var(--dsw-alias-state-error-primary) 55%,var(--dsw-alias-label-primary));font-family:var(--dsw-font-family)}
26
33
  .lks-root :where(button){font-family:var(--dsw-font-family);cursor:pointer;border:none;background:none;padding:0}
27
34
  .lks-root :where(a){color:var(--dsw-alias-state-business-primary)}
28
35
 
@@ -42,9 +49,9 @@ window.__ModuleLoader__.load({
42
49
  /* Direction carrier (a container query cannot style the container itself). */
43
50
  .lks-body{flex:1;min-height:0;min-width:0;display:flex;gap:16px}
44
51
  .lks-col{display:flex;flex-direction:column;min-width:0}
45
- .lks-colhead{flex:none;font-size:12px;font-weight:600;color:var(--dsw-alias-label-tertiary);letter-spacing:.06em;text-transform:uppercase;margin-bottom:10px}
52
+ .lks-colhead{flex:none;font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary);letter-spacing:.06em;text-transform:uppercase;margin-bottom:10px}
46
53
  .lks-col-rail{flex:0 1 260px;min-width:180px;overflow-y:auto;padding:2px 6px}
47
- .lks-col-tutor{flex:0 1 auto;width:min(100%,var(--dsh-composer-card-max-width,780px),42cqi);min-width:360px}
54
+ .lks-col-tutor{flex:0 1 auto;width:min(100%,var(--dsh-composer-card-max-width,780px),42cqi);min-width:360px;overflow:hidden}
48
55
  .lks-col-bb{flex:1 1 0;min-width:320px;overflow-y:auto;padding:2px 6px}
49
56
  /* While the study view is mounted in wide mode, the host composer card is
50
57
  shifted under the tutor column (the transcript's width axis) instead of the
@@ -60,7 +67,7 @@ window.__ModuleLoader__.load({
60
67
  .lks-switch{display:none;flex-direction:row;gap:2px;align-self:center;margin-bottom:10px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:999px;padding:3px}
61
68
  .lks-switch-btn{border-radius:999px;padding:5px 18px;font-size:14px;color:var(--dsw-alias-label-tertiary);background:transparent;transition:background .12s ease,color .12s ease}
62
69
  .lks-switch-btn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}
63
- .lks-switch-btn.on{background:var(--dsw-alias-state-business-primary);color:#fff;font-weight:600}
70
+ .lks-switch-btn.on{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 86%,#000);color:#fff;font-weight:600}
64
71
  @container lksstudy (max-width: 1220px){
65
72
  .lks-body{flex-direction:column}
66
73
  .lks-switch{display:flex}
@@ -77,37 +84,37 @@ window.__ModuleLoader__.load({
77
84
  .lks-rail-head .lks-rail-select{flex:1;min-width:0;margin-bottom:0}
78
85
  .lks-rail-head .lks-btn{padding:3px 8px;font-size:13px}
79
86
  .lks-rail-title{font-size:16px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
80
- .lks-rail-sub{font-size:13px;color:var(--dsw-alias-label-tertiary);margin:4px 0 6px}
87
+ .lks-rail-sub{font-size:13px;color:var(--dsw-alias-label-secondary);margin:4px 0 6px}
81
88
  .lks-masterybar{height:5px;border-radius:3px;background:var(--dsw-alias-bg-layer-3);overflow:hidden;margin-bottom:8px}
82
- .lks-masterybar i{display:block;height:100%;background:var(--dsw-alias-state-business-primary);transition:width .3s ease}
89
+ .lks-masterybar i{display:block;height:100%;width:100%;background:var(--dsw-alias-state-success-primary);transform-origin:0 50%;transition:transform .3s ease}
83
90
  .lks-masterybar.gold i{background:var(--dsw-alias-state-warn-primary)}
84
91
  .lks-search{width:100%;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-primary);font-family:inherit;font-size:13.5px;padding:5px 9px;margin-bottom:4px}
85
92
  .lks-search:focus{outline:none;border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
86
- .lks-sec-num{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;border-radius:50%;background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label);font-size:11px;font-weight:700;margin-right:4px}
87
- .lks-tag.due{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}
93
+ .lks-sec-num{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;border-radius:50%;background:var(--dsw-alias-state-warn-tertiary);color:var(--lks-warn-ink);font-size:11px;font-weight:700;margin-right:4px}
94
+ .lks-tag.due{background:var(--dsw-alias-state-warn-tertiary);color:var(--lks-warn-ink)}
88
95
  .lks-import{margin-top:8px}
89
96
  .lks-import .lks-inputrow{margin-top:0}
90
97
  .lks-inputrow{display:flex;gap:8px;align-items:flex-end;flex:none;margin-top:8px}
91
98
  .lks-input{flex:1;min-width:0;resize:none;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;color:var(--dsw-alias-label-primary);font-family:inherit;font-size:14px;line-height:1.6;padding:8px 10px}
92
99
  .lks-input:focus{outline:none;border-color:var(--dsw-alias-state-business-primary)}
93
100
  .lks-input:disabled{opacity:.55}
94
- .lks-import-hint{font-size:12.5px;color:var(--dsw-alias-label-tertiary);margin-top:6px;line-height:1.7}
101
+ .lks-import-hint{font-size:12.5px;color:var(--dsw-alias-label-secondary);margin-top:6px;line-height:1.7}
95
102
  .lks-duebox{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:10px 12px;margin:8px 0 14px;font-size:13.5px}
96
103
  .lks-duebox .lks-due-item{color:var(--dsw-alias-label-secondary);margin-top:4px;display:flex;justify-content:space-between;gap:10px}
97
- .lks-duebox .lks-over{color:var(--dsw-alias-state-warn-label);flex:none}
104
+ .lks-duebox .lks-over{color:var(--lks-warn-ink);flex:none}
98
105
 
99
- .lks-sec{font-size:12px;color:var(--dsw-alias-label-tertiary);text-transform:uppercase;letter-spacing:.05em;margin:14px 0 4px}
106
+ .lks-sec{font-size:12px;color:var(--dsw-alias-label-secondary);text-transform:uppercase;letter-spacing:.05em;margin:14px 0 4px}
100
107
  /* Collapsible section head: a real button (keyboard can toggle it), sticky so
101
108
  the current chapter stays identified while 4000px of nodes scroll under it. */
102
- .lks-sechead{display:flex;align-items:center;gap:6px;width:100%;box-sizing:border-box;font-size:12px;font-weight:600;color:var(--dsw-alias-label-tertiary);text-transform:uppercase;letter-spacing:.05em;text-align:left;padding:6px 4px;margin:14px 0 4px;border-radius:6px;position:sticky;top:0;z-index:1;background:var(--dsw-alias-bg-base)}
109
+ .lks-sechead{display:flex;align-items:center;gap:6px;width:100%;box-sizing:border-box;font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary);text-transform:uppercase;letter-spacing:.05em;text-align:left;padding:6px 4px;margin:14px 0 4px;border-radius:6px;position:sticky;top:0;z-index:1;background:var(--dsw-alias-bg-base)}
103
110
  .lks-sechead:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}
104
- .lks-sechead:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}
111
+ .lks-sechead:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
105
112
  .lks-sechead-t{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
106
113
  .lks-sechead-c{flex:none;font-size:10px}
107
114
  .lks-sechead-n{flex:none;font-size:11px;text-transform:none;letter-spacing:0}
108
115
  .lks-node{display:flex;align-items:center;gap:8px;width:100%;text-align:left;padding:6px 9px;border-radius:8px;margin:1px 0;cursor:pointer}
109
116
  .lks-node:hover{background:var(--dsw-alias-interactive-bg-hover)}
110
- .lks-node:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}
117
+ .lks-node:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
111
118
  .lks-node.focus{background:var(--dsw-alias-bg-layer-3);outline:1px solid var(--dsw-alias-border-l2)}
112
119
  .lks-node .lks-g{width:18px;text-align:center;flex:none}
113
120
  .lks-node .lks-t{flex:1;font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@@ -117,13 +124,13 @@ window.__ModuleLoader__.load({
117
124
  .lks-node[aria-disabled='true']{opacity:.45;cursor:not-allowed}
118
125
  .lks-node[aria-disabled='true']:hover{background:none}
119
126
  .lks-tag{font-size:11px;border-radius:4px;padding:0 4px;flex:none}
120
- .lks-tag.weak{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}
121
- .lks-tag.fric{background:var(--dsw-alias-state-error-primary);color:#fff}
127
+ .lks-tag.weak{background:var(--dsw-alias-state-warn-tertiary);color:var(--lks-warn-ink)}
128
+ .lks-tag.fric{background:var(--dsw-alias-state-error-tertiary);color:var(--lks-err-ink)}
122
129
  .lks-bar{width:44px;height:4px;border-radius:2px;background:var(--dsw-alias-bg-layer-3);overflow:hidden;flex:none}
123
- .lks-bar i{display:block;height:100%;background:var(--dsw-alias-state-success-primary)}
124
- .lks-pct{font-size:12px;color:var(--dsw-alias-label-tertiary);flex:none;width:32px;text-align:right}
130
+ .lks-bar i{display:block;height:100%;width:100%;background:var(--dsw-alias-state-success-primary);transform-origin:0 50%}
131
+ .lks-pct{font-size:12px;color:var(--dsw-alias-label-secondary);flex:none;width:32px;text-align:right}
125
132
 
126
- .lks-empty{color:var(--dsw-alias-label-tertiary);text-align:center;padding:48px 8px;font-size:14px;line-height:2}
133
+ .lks-empty{color:var(--dsw-alias-label-secondary);text-align:center;padding:48px 8px;font-size:14px;line-height:2}
127
134
  .lks-actbar{display:flex;flex-direction:row;justify-content:flex-end;flex:none;padding-bottom:8px}
128
135
 
129
136
  /* ── middle column: the tutor ── */
@@ -132,31 +139,43 @@ window.__ModuleLoader__.load({
132
139
  .lks-msg.user{align-self:flex-end;white-space:pre-wrap;background:var(--dsw-alias-state-business-tertiary);color:var(--dsw-alias-label-primary-bluish)}
133
140
  .lks-msg.assistant{align-self:flex-start;background:var(--dsw-alias-bg-layer-2)}
134
141
  .lks-msg.streaming{opacity:.7}
135
- .lks-msg.thinking{align-self:flex-start;display:inline-flex;align-items:center;gap:8px;font-size:13px;color:var(--dsw-alias-label-tertiary);background:none;border-radius:0;padding:2px}
136
- .lks-msg.tool{align-self:flex-start;font-size:12px;color:var(--dsw-alias-label-tertiary);background:none;border-radius:0;padding:1px 2px}
137
- .lks-msg.tool.ok{color:var(--dsw-alias-state-success-primary)}
138
- .lks-msg.tool.bad{color:var(--dsw-alias-state-error-primary)}
142
+ .lks-msg.thinking{align-self:flex-start;display:inline-flex;align-items:center;gap:8px;font-size:13px;color:var(--dsw-alias-label-secondary);background:none;border-radius:0;padding:2px}
139
143
  .lks-msg.error{align-self:stretch;color:var(--dsw-alias-state-error-primary);font-size:13px;background:none;border-radius:0;padding:1px 2px}
140
144
  /* quiz-taking surface: the tutor's A–D options become clickable answers on the last settled reply */
141
145
  .lks-turn{display:flex;flex-direction:column;align-items:flex-start;max-width:94%}
142
146
  .lks-quiz{display:flex;flex-direction:column;gap:6px;width:100%;margin:2px 0 4px}
143
147
  .lks-opt{display:flex;align-items:baseline;gap:10px;text-align:left;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:10px;padding:8px 14px;font-size:14px;line-height:1.5;color:var(--dsw-alias-label-secondary);cursor:pointer;font-family:inherit;transition:border-color .12s ease,color .12s ease,background .12s ease}
144
148
  .lks-opt:hover{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-state-business-tertiary)}
145
- .lks-opt:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}
149
+ .lks-opt:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
146
150
  .lks-optletter{font-weight:700;color:var(--dsw-alias-label-primary-bluish);flex:none}
147
151
 
148
152
  /* soul pills (native tool-row trigger language: 28px transparent, tinted active) */
149
153
  .lks-pills{display:inline-flex;align-items:center;height:28px}
150
154
  .lks-pill{display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px;border:none;border-radius:24px;background:transparent;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px;font-weight:500;transition:background .12s ease,color .12s ease}
151
155
  .lks-pill:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}
152
- .lks-pill:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}
156
+ .lks-pill:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
153
157
  .lks-pill.on{background:var(--dsw-alias-state-business-tertiary);color:var(--dsw-alias-label-primary-bluish)}
154
158
  .lks-pill.on:hover{background:var(--dsw-alias-state-business-tertiary);color:var(--dsw-alias-label-primary-bluish)}
155
159
 
160
+ /* SVG glyph alignment (status icons, tag badges, dock segments, icon buttons) */
161
+ .lks-g svg{display:block;margin:0 auto}
162
+ .lks-tag{display:inline-flex;align-items:center;gap:2px;line-height:1}
163
+ .lks-dockseg{display:inline-flex;align-items:center;gap:3px}
164
+ .lks-dock-due svg,.lks-dock-streak svg{flex:none}
165
+ .lks-dock-due.lks-muted svg,.lks-dock-streak.lks-muted svg{opacity:.75}
166
+ .lks-btn svg,.lks-viewtab svg,.lks-sechead svg{flex:none}
167
+ .lks-viewtab svg,.lks-btn svg{align-self:center}
168
+
156
169
  /* ic_ds_* glyph carriers + busy spinner (native .8s linear spin) */
157
170
  @keyframes lks-spin{to{transform:rotate(360deg)}}
158
171
  .lks-spin{animation:lks-spin .8s linear infinite}
159
172
 
173
+ /* dormant state: pills/starters stay visible but inert (aria-disabled, same
174
+ contract as locked lesson rows — focusable so the hint explains why) */
175
+ .lks-pill[aria-disabled='true'],.lks-starter[aria-disabled='true']{opacity:.45;cursor:not-allowed}
176
+ .lks-pill[aria-disabled='true']:hover,.lks-starter[aria-disabled='true']:hover{background:transparent;border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary)}
177
+ .lks-dormant{font-size:13px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border:1px dashed var(--dsw-alias-border-l2);border-radius:10px;padding:10px 12px;margin:8px 0;line-height:1.7}
178
+
160
179
  /* starter chips */
161
180
  .lks-dock{display:flex;align-items:center;flex-wrap:wrap;gap:6px;padding:0 2px 6px;flex:none}
162
181
  .lks-starter{border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:4px 12px;font-size:13px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);transition:border-color .12s ease,color .12s ease}
@@ -168,7 +187,8 @@ window.__ModuleLoader__.load({
168
187
  .lks-banner{display:flex;align-items:center;gap:10px;background:var(--dsw-alias-state-business-tertiary);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:10px 14px;margin:8px 0;font-size:14px;flex:none}
169
188
  .lks-banner .lks-why{flex:1;color:var(--dsw-alias-label-secondary)}
170
189
  .lks-btn{display:inline-flex;align-items:center;gap:6px;border-radius:8px;padding:6px 14px;font-size:13.5px;font-weight:600;flex:none}
171
- .lks-btn.primary{background:var(--dsw-alias-state-business-primary);color:#fff}
190
+ .lks-btn:focus-visible,.lks-starter:focus-visible,.lks-switch-btn:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
191
+ .lks-btn.primary{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 86%,#000);color:#fff}
172
192
  .lks-btn.primary:hover{filter:brightness(1.1)}
173
193
  .lks-btn.primary:disabled{opacity:.5;cursor:default;filter:none}
174
194
  .lks-btn.ghost{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);border:1px solid var(--dsw-alias-border-l2)}
@@ -176,10 +196,18 @@ window.__ModuleLoader__.load({
176
196
 
177
197
  /* ── right column: blackboard ── */
178
198
  .lks-lessonhead h2{font-size:18px;margin:0 0 4px;font-weight:700}
179
- .lks-lessonhead .lks-meta{color:var(--dsw-alias-label-tertiary);font-size:13px;margin-bottom:4px}
199
+ .lks-lessonhead .lks-meta{color:var(--dsw-alias-label-secondary);font-size:13px;margin-bottom:4px}
180
200
  .lks-chips{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0}
181
201
  .lks-chip{border-radius:999px;padding:2px 10px;font-size:12.5px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary)}
182
- .lks-chip.weak{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}
202
+ .lks-chip.weak{background:var(--dsw-alias-state-warn-tertiary);color:var(--lks-warn-ink)}
203
+
204
+ /* blackboard view tabs (same joined-capsule button language as the narrow
205
+ mode switcher — buttons, not tabs, against the host's view tabs above) */
206
+ .lks-viewtabs{display:flex;gap:2px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:999px;padding:3px;width:max-content;margin:10px 0}
207
+ .lks-viewtab{border-radius:999px;padding:4px 16px;font-size:13.5px;color:var(--dsw-alias-label-secondary);background:transparent;transition:background .12s ease,color .12s ease}
208
+ .lks-viewtab:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
209
+ .lks-viewtab.on{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 86%,#000);color:#fff;font-weight:600}
210
+ .lks-viewtab:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
183
211
 
184
212
  /* rendered markdown (host-sanitized lesson bodies and assistant replies) */
185
213
  .lks-prose{font-size:16px;line-height:1.75}
@@ -195,7 +223,7 @@ window.__ModuleLoader__.load({
195
223
  .lks-prose ul,.lks-prose ol{padding-left:22px;margin:6px 0}
196
224
  .lks-prose img{max-width:100%}
197
225
  /* width guards for CDN-rendered artifacts (both teach and diagram panes are
198
- .lks-prose): mermaid/markmap/elk SVGs carry fixed width attributes; KaTeX
226
+ .lks-prose): mermaid/elk SVGs carry fixed width attributes; KaTeX
199
227
  display math can exceed the column; both must degrade to fit/scroll
200
228
  internally instead of stretching the blackboard. */
201
229
  .lks-prose svg{max-width:100%;height:auto}
@@ -204,11 +232,15 @@ window.__ModuleLoader__.load({
204
232
  /* Cornell notes zones */
205
233
  .lks-bb-notes{margin-top:14px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l1)}
206
234
  .lks-zone{margin-bottom:14px}
207
- .lks-zone h4{font-size:14px;color:var(--dsw-alias-label-secondary);margin:0 0 8px;font-weight:600}
235
+ .lks-zone-h{font-size:14px;color:var(--dsw-alias-label-secondary);margin:0 0 8px;font-weight:600}
208
236
  .lks-note{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:10px 14px;margin-bottom:8px}
209
237
  .lks-note .lks-note-src{float:right;font-size:11.5px;color:var(--dsw-alias-label-secondary)}
210
238
  .lks-note .lks-note-title{font-weight:600;font-size:13px}
211
- .lks-note .lks-note-text{margin-top:4px;white-space:pre-wrap;font-size:13px;color:var(--dsw-alias-label-secondary)}
239
+ .lks-note .lks-note-text{margin-top:4px;font-size:13px;color:var(--dsw-alias-label-secondary);line-height:1.65}
240
+ .lks-note .lks-note-text p{margin:4px 0}
241
+ .lks-note .lks-note-text table{border-collapse:collapse;margin:6px 0;display:block;max-width:100%;overflow-x:auto}
242
+ .lks-note .lks-note-text th,.lks-note .lks-note-text td{border:1px solid var(--dsw-alias-border-l2);padding:4px 8px;font-size:12.5px}
243
+ .lks-note .lks-note-text code{font-family:var(--dsw-font-markdown-code);background:var(--dsw-alias-bg-layer-3);border-radius:4px;padding:1px 4px;font-size:.95em}
212
244
  .lks-note .lks-note-q{margin-top:6px;color:var(--dsw-alias-label-tertiary);font-size:12px;border-left:2px solid var(--dsw-alias-state-warn-primary);padding-left:8px}
213
245
 
214
246
  /* inline error text (write-action failures) */
@@ -217,24 +249,25 @@ window.__ModuleLoader__.load({
217
249
  /* settings section (settings.section entry inside the host settings shell) */
218
250
  .lks-settings{display:flex;flex-direction:column;gap:18px;font-family:var(--dsw-font-family);color:var(--dsw-alias-label-primary)}
219
251
  .lks-set-row h3{margin:0 0 4px;font-size:15px;font-weight:600}
220
- .lks-set-hint{margin:0 0 10px;font-size:13px;color:var(--dsw-alias-label-tertiary)}
252
+ .lks-set-hint{margin:0 0 10px;font-size:13px;color:var(--dsw-alias-label-secondary)}
221
253
  .lks-set-state{margin-left:10px;font-size:13px;color:var(--dsw-alias-label-secondary)}
222
254
  .lks-set-stats{margin:0;padding-left:18px;font-size:14px;line-height:1.9}
223
255
  .lks-set-path{font-family:var(--dsw-font-markdown-code);font-size:12.5px;background:var(--dsw-alias-bg-layer-3);border-radius:6px;padding:3px 8px;word-break:break-all}
224
256
 
225
257
  /* composer dock status pill (conversation.composer.dock entry) */
226
- .lks-dockpill{display:inline-flex;gap:10px;align-items:center;font-size:12px;color:var(--dsw-alias-label-tertiary)}
258
+ .lks-dockpill{display:inline-flex;gap:10px;align-items:center;font-size:12px;color:var(--dsw-alias-label-secondary)}
227
259
  .lks-dockseg{white-space:nowrap}
228
- .lks-dock-due{color:var(--dsw-alias-state-warn-label)}
260
+ .lks-dock-due{color:var(--lks-warn-ink)}
229
261
  .lks-dock-streak{color:var(--dsw-alias-state-business-primary)}
262
+ .lks-dockseg.lks-muted{color:var(--dsw-alias-label-secondary)}
230
263
 
231
264
  /* keyed tool.call.toolview cards (conversation tab tool rows) */
232
265
  .lks-tv{display:flex;flex-direction:column;gap:4px;padding:6px 10px;border-radius:10px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);font-size:13px}
233
266
  .lks-tv.err{border-color:var(--dsw-alias-state-error-primary)}
234
267
  .lks-tv-head{font-weight:600;color:var(--dsw-alias-label-secondary);font-size:12px}
235
268
  .lks-tv-chip{align-self:flex-start;border-radius:999px;padding:2px 10px;font-size:12.5px;font-weight:600}
236
- .lks-tv-chip.ok{background:var(--dsw-alias-state-success-tertiary);color:var(--dsw-alias-state-success-label)}
237
- .lks-tv-chip.bad{background:var(--dsw-alias-state-error-tertiary);color:var(--dsw-alias-state-error-label)}
269
+ .lks-tv-chip.ok{background:var(--dsw-alias-state-success-tertiary);color:var(--lks-ok-ink)}
270
+ .lks-tv-chip.bad{background:var(--dsw-alias-state-error-tertiary);color:var(--lks-err-ink)}
238
271
  .lks-tv-line{color:var(--dsw-alias-label-secondary);line-height:1.6;white-space:pre-wrap}
239
272
  `;
240
273
  /**
@@ -260,6 +293,11 @@ window.__ModuleLoader__.load({
260
293
  * importing the host package root would drag its CSS modules into the
261
294
  * inlined bundle. Re-sync these three paths if the host icon set redesigns
262
295
  * them; scripts/verify.mjs pins the path data.
296
+ *
297
+ * 2026-09-01 critique round: seven more ic_ds glyphs vendored (play/plus/
298
+ * trash/refresh/globe/goal/warning) plus plugin-original learning-domain
299
+ * companions (lock/star/book/crown/bolt/power/pin/flame) drawn in the same
300
+ * language — those are NOT ic_ds, see their section comment below.
263
301
  * @module dsh-plugin-lookatstudy/client/icons
264
302
  */
265
303
  /** ic_ds_think_outline_16 */
@@ -309,6 +347,249 @@ window.__ModuleLoader__.load({
309
347
  fill: "currentColor"
310
348
  }));
311
349
  }
350
+ /** Vendored 2026-09-01 for the critique icon round (same figma source as the three above). */
351
+ /** IconPlayOutline16 */
352
+ function IconPlayOutline16({ size = 16, className }) {
353
+ return (0, react.createElement)("svg", {
354
+ width: size,
355
+ height: size,
356
+ className,
357
+ viewBox: "0 0 16 16",
358
+ fill: "none",
359
+ xmlns: "http://www.w3.org/2000/svg"
360
+ }, (0, react.createElement)("path", {
361
+ d: "M14.1446 8C14.1446 4.6062 11.3938 1.85539 8 1.85539C4.6062 1.85539 1.85539 4.6062 1.85539 8C1.85539 11.3938 4.6062 14.1446 8 14.1446C11.3938 14.1446 14.1446 11.3938 14.1446 8ZM15.511 8C15.511 12.148 12.148 15.511 8 15.511C3.85202 15.511 0.489014 12.148 0.489014 8C0.489014 3.85202 3.85202 0.489014 8 0.489014C12.148 0.489014 15.511 3.85202 15.511 8Z",
362
+ fill: "currentColor"
363
+ }), (0, react.createElement)("path", {
364
+ d: "M10.5617 8.42578C10.852 8.21614 10.852 7.78386 10.5617 7.57422L7.25708 5.18751C6.90974 4.93666 6.42436 5.18484 6.42436 5.61329V10.3867C6.42436 10.8152 6.90974 11.0633 7.25708 10.8125L10.5617 8.42578Z",
365
+ fill: "currentColor"
366
+ }));
367
+ }
368
+ /** IconPlusOutline16 */
369
+ function IconPlusOutline16({ size = 16, className }) {
370
+ return (0, react.createElement)("svg", {
371
+ width: size,
372
+ height: size,
373
+ className,
374
+ viewBox: "0 0 16 16",
375
+ fill: "none",
376
+ xmlns: "http://www.w3.org/2000/svg"
377
+ }, (0, react.createElement)("path", {
378
+ d: "M8.64453 1.5V7.34961H14.5V8.65039H8.64453V14.5H7.34473V8.65039H1.5V7.34961H7.34473V1.5H8.64453Z",
379
+ fill: "currentColor"
380
+ }));
381
+ }
382
+ /** IconTrashOutline16 */
383
+ function IconTrashOutline16({ size = 16, className }) {
384
+ return (0, react.createElement)("svg", {
385
+ width: size,
386
+ height: size,
387
+ className,
388
+ viewBox: "0 0 16 16",
389
+ fill: "none",
390
+ xmlns: "http://www.w3.org/2000/svg"
391
+ }, (0, react.createElement)("path", {
392
+ d: "M14.4782 4.84067L14.2138 10.1152C14.1102 12.1872 14.067 13.0115 13.3866 13.9607C13.1044 14.3546 12.7498 14.6912 12.3424 14.9535C11.8239 15.2872 11.2415 15.4316 10.5585 15.4998C9.88727 15.5668 9.04946 15.5656 7.99998 15.5656C6.95051 15.5656 6.1127 15.5668 5.44142 15.4998C4.75851 15.4316 4.17602 15.2872 3.65753 14.9535C3.25012 14.6912 2.89559 14.3546 2.61332 13.9607C1.93296 13.0115 1.88979 12.1872 1.78619 10.1152L1.52179 4.84067L2.89006 4.77277L3.15343 10.0463C3.26221 12.2218 3.32452 12.6015 3.72646 13.1624C3.90825 13.4161 4.13686 13.6334 4.39927 13.8023C4.66204 13.9714 5.00263 14.0792 5.57825 14.1367C6.16562 14.1953 6.92298 14.1963 7.99998 14.1963C9.07699 14.1963 9.83434 14.1953 10.4217 14.1367C10.9973 14.0792 11.3379 13.9714 11.6007 13.8023C11.8631 13.6334 12.0917 13.4161 12.2735 13.1624C12.6755 12.6015 12.7378 12.2218 12.8465 10.0463L13.1099 4.77277L14.4782 4.84067ZM5.43011 6.22849H6.7994V11.3909H5.43011V6.22849ZM9.20056 6.22849H10.5699V11.3909H9.20056V6.22849ZM8.53597 0.434431C9.17976 0.434431 9.6522 0.426926 10.0966 0.571258C10.2357 0.616451 10.3717 0.672554 10.502 0.738948C10.9182 0.951107 11.2464 1.29099 11.7015 1.74612L12.4978 2.54136H15.3742V3.91169H0.625732V2.54136H3.50218L4.29845 1.74612C4.75358 1.29099 5.08174 0.951107 5.49801 0.738948C5.62831 0.672554 5.76425 0.616451 5.90334 0.571258C6.34776 0.426926 6.82021 0.434431 7.46399 0.434431H8.53597ZM7.46399 1.80476C6.73208 1.80476 6.51641 1.81187 6.32617 1.87369C6.25545 1.89667 6.18668 1.92533 6.12041 1.95907C5.96398 2.03878 5.82348 2.16253 5.44142 2.54136H10.5585C10.1765 2.16253 10.036 2.03878 9.87955 1.95907C9.81329 1.92533 9.74452 1.89667 9.6738 1.87369C9.48356 1.81187 9.26789 1.80476 8.53597 1.80476H7.46399Z",
393
+ fill: "currentColor"
394
+ }));
395
+ }
396
+ /** IconRefreshOutline16 */
397
+ function IconRefreshOutline16({ size = 16, className }) {
398
+ return (0, react.createElement)("svg", {
399
+ width: size,
400
+ height: size,
401
+ className,
402
+ viewBox: "0 0 16 16",
403
+ fill: "none",
404
+ xmlns: "http://www.w3.org/2000/svg"
405
+ }, (0, react.createElement)("path", {
406
+ d: "M7.92136 0.349152C10.3744 0.349234 12.5564 1.5052 13.9557 3.29894L15.1281 2.12759C15.3303 1.92546 15.6767 2.06943 15.6767 2.35538V5.53923C15.6766 5.71626 15.5329 5.85976 15.3559 5.86002H12.171C11.8854 5.8597 11.7426 5.51465 11.9443 5.31249L12.9641 4.29056C11.8237 2.74305 9.98908 1.74106 7.92136 1.74097C4.46436 1.74097 1.66233 4.543 1.66233 8C1.66233 11.457 4.46436 14.259 7.92136 14.259C11.3782 14.2589 14.1804 11.4569 14.1804 8H15.5722C15.5722 12.2251 12.1465 15.6507 7.92136 15.6508C3.69614 15.6508 0.270508 12.2252 0.270508 8C0.270508 3.77478 3.69614 0.349152 7.92136 0.349152Z",
407
+ fill: "currentColor"
408
+ }));
409
+ }
410
+ /** IconGlobeOutline14 */
411
+ function IconGlobeOutline14({ size = 16, className }) {
412
+ return (0, react.createElement)("svg", {
413
+ width: size,
414
+ height: size,
415
+ className,
416
+ viewBox: "0 0 16 16",
417
+ fill: "none",
418
+ xmlns: "http://www.w3.org/2000/svg"
419
+ }, (0, react.createElement)("path", {
420
+ d: "M7.00018 0.353516C10.6708 0.353535 13.6468 3.32958 13.6469 7.00018C13.6468 10.6708 10.6708 13.6468 7.00018 13.6469C3.32957 13.6468 0.353535 10.6708 0.353516 7.00018C0.353535 3.32957 3.32957 0.353531 7.00018 0.353516ZM5.44643 7.59661C5.49463 8.97506 5.70762 10.191 6.02136 11.0793C6.20141 11.5891 6.40328 11.9585 6.59898 12.1889C6.79501 12.4196 6.93213 12.454 7.00018 12.454C7.06822 12.454 7.20533 12.4197 7.40138 12.1889C7.59708 11.9585 7.79895 11.589 7.979 11.0793C8.29274 10.191 8.50574 8.97506 8.55394 7.59661H5.44643ZM1.57861 7.59661C1.80785 9.70467 3.2386 11.4509 5.1715 12.1388C5.07135 11.9317 4.97972 11.7098 4.89746 11.477C4.53084 10.4391 4.30224 9.0828 4.25357 7.59661H1.57861ZM9.74679 7.59661C9.69813 9.0828 9.46952 10.4391 9.1029 11.477C9.0206 11.7099 8.92818 11.9316 8.82797 12.1388C10.7613 11.4511 12.1925 9.70496 12.4218 7.59661H9.74679ZM5.1706 1.8616C3.23814 2.54963 1.80876 4.29604 1.5795 6.40376H4.25357C4.30224 4.91756 4.53083 3.56129 4.89746 2.5234C4.97968 2.29066 5.07051 2.0686 5.1706 1.8616ZM7.00018 1.54637C6.93213 1.54638 6.79503 1.5807 6.59898 1.81145C6.40332 2.04177 6.20139 2.41058 6.02136 2.92012C5.70754 3.80851 5.49461 5.02499 5.44643 6.40376H8.55394C8.50575 5.025 8.29282 3.80851 7.979 2.92012C7.79898 2.41059 7.59705 2.04177 7.40138 1.81145C7.20531 1.58067 7.06823 1.54637 7.00018 1.54637ZM8.82887 1.8616C8.92902 2.0687 9.02064 2.29053 9.1029 2.5234C9.46953 3.56129 9.69812 4.91756 9.74679 6.40376H12.4209C12.1916 4.29575 10.7618 2.54943 8.82887 1.8616Z",
421
+ fill: "currentColor"
422
+ }));
423
+ }
424
+ /** IconGoalOutline16 */
425
+ function IconGoalOutline16({ size = 16, className }) {
426
+ return (0, react.createElement)("svg", {
427
+ width: size,
428
+ height: size,
429
+ className,
430
+ viewBox: "0 0 16 16",
431
+ fill: "none",
432
+ xmlns: "http://www.w3.org/2000/svg"
433
+ }, (0, react.createElement)("path", {
434
+ d: "M8 0C8.31451 0 8.62464 0.019379 8.92969 0.0546875C8.48228 0.403371 8.0952 0.825758 7.78809 1.30469C4.18586 1.41664 1.2998 4.37061 1.2998 8C1.2998 11.7003 4.29969 14.7002 8 14.7002C11.6297 14.7002 14.5829 11.8136 14.6943 8.21094C15.1734 7.90377 15.5956 7.51688 15.9443 7.06934C15.9797 7.37473 16 7.68512 16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8C0 3.58172 3.58172 0 8 0ZM7.0166 3.6084C7.00658 3.73765 7 3.86817 7 4C7 4.31845 7.03098 4.62973 7.08789 4.93164C5.76489 5.32438 4.7998 6.54958 4.7998 8C4.7998 9.76731 6.23269 11.2002 8 11.2002C9.45065 11.2002 10.6749 10.2345 11.0674 8.91113C11.3696 8.96818 11.6812 9 12 9C12.1315 9 12.2617 8.99239 12.3906 8.98242C11.9423 10.995 10.1477 12.5 8 12.5C5.51472 12.5 3.5 10.4853 3.5 8C3.5 5.85255 5.00435 4.05702 7.0166 3.6084Z",
435
+ fill: "currentColor"
436
+ }), (0, react.createElement)("path", {
437
+ d: "M7.5 8.62109L9.12109 7",
438
+ fill: "currentColor"
439
+ }), (0, react.createElement)("path", {
440
+ d: "M9.08245 3.35798L11.8651 0.575334C11.895 0.545384 11.9463 0.56391 11.9502 0.606086L12.2362 3.69859C12.2384 3.72259 12.2574 3.74159 12.2814 3.74378L15.3697 4.02583C15.4119 4.02968 15.4305 4.08101 15.4005 4.11098L12.618 6.89351C12.6086 6.90289 12.5959 6.90816 12.5826 6.90816L9.11781 6.90815C9.09019 6.90816 9.06781 6.88577 9.06781 6.85816L9.06781 3.39333C9.06781 3.38007 9.07308 3.36735 9.08245 3.35798Z",
441
+ fill: "currentColor"
442
+ }));
443
+ }
444
+ /** IconWarningOutline16 */
445
+ function IconWarningOutline16({ size = 16, className }) {
446
+ return (0, react.createElement)("svg", {
447
+ width: size,
448
+ height: size,
449
+ className,
450
+ viewBox: "0 0 16 16",
451
+ fill: "none",
452
+ xmlns: "http://www.w3.org/2000/svg"
453
+ }, (0, react.createElement)("path", {
454
+ d: "M6.3002 3.32843L7.69986 3.32843L7.69986 7.79657H6.3002L6.3002 3.32843Z",
455
+ fill: "currentColor"
456
+ }), (0, react.createElement)("path", {
457
+ d: "M6.3002 9.01935H7.69986V10.6711H6.3002V9.01935Z",
458
+ fill: "currentColor"
459
+ }), (0, react.createElement)("path", {
460
+ d: "M12.6328 6.99976C12.6328 3.88874 10.111 1.36694 7 1.36694C3.88899 1.36695 1.3672 3.88875 1.36719 6.99976C1.36719 10.1108 3.88899 12.6326 7 12.6326C10.111 12.6326 12.6328 10.1108 12.6328 6.99976ZM13.8582 6.99976C13.8582 10.7873 10.7876 13.8579 7 13.8579C3.21244 13.8579 0.141846 10.7873 0.141846 6.99976C0.141857 3.2122 3.21245 0.141612 7 0.141602C10.7876 0.141602 13.8581 3.21219 13.8582 6.99976Z",
461
+ fill: "currentColor"
462
+ }));
463
+ }
464
+ /**
465
+ * Plugin-original companions for learning-domain glyphs the host set does not
466
+ * carry (lock/star/book/crown/bolt/power/pin/flame) — authored here in the
467
+ * same 16px currentColor filled language as the ic_ds set, but they are NOT
468
+ * ic_ds glyphs and must be re-drawn (not re-synced) if the style drifts.
469
+ */
470
+ /** IconLockFill16 */
471
+ function IconLockFill16({ size = 16, className }) {
472
+ return (0, react.createElement)("svg", {
473
+ width: size,
474
+ height: size,
475
+ className,
476
+ viewBox: "0 0 16 16",
477
+ fill: "none",
478
+ xmlns: "http://www.w3.org/2000/svg"
479
+ }, (0, react.createElement)("path", {
480
+ "fill-rule": "evenodd",
481
+ "clip-rule": "evenodd",
482
+ d: "M5 6.5V5a3 3 0 0 1 6 0v1.5h.6c.77 0 1.4.63 1.4 1.4v5.7c0 .77-.63 1.4-1.4 1.4H4.4A1.4 1.4 0 0 1 3 13.6V7.9c0-.77.63-1.4 1.4-1.4H5Zm1.444 0h3.112V5a1.556 1.556 0 0 0-3.112 0v1.5Z",
483
+ fill: "currentColor"
484
+ }));
485
+ }
486
+ /** IconStarFill16 */
487
+ function IconStarFill16({ size = 16, className }) {
488
+ return (0, react.createElement)("svg", {
489
+ width: size,
490
+ height: size,
491
+ className,
492
+ viewBox: "0 0 16 16",
493
+ fill: "none",
494
+ xmlns: "http://www.w3.org/2000/svg"
495
+ }, (0, react.createElement)("path", {
496
+ d: "M8 1.6l1.86 3.77 4.16.6-3.01 2.94.71 4.14L8 11.42l-3.72 1.96.71-4.15-3.01-2.93 4.16-.6L8 1.6Z",
497
+ fill: "currentColor"
498
+ }));
499
+ }
500
+ /** IconBookFill16 */
501
+ function IconBookFill16({ size = 16, className }) {
502
+ return (0, react.createElement)("svg", {
503
+ width: size,
504
+ height: size,
505
+ className,
506
+ viewBox: "0 0 16 16",
507
+ fill: "none",
508
+ xmlns: "http://www.w3.org/2000/svg"
509
+ }, (0, react.createElement)("path", {
510
+ "fill-rule": "evenodd",
511
+ "clip-rule": "evenodd",
512
+ d: "M8 3.1C6.6 2.15 4.65 1.75 2.5 1.75c-.5 0-1 .03-1.5.1v10.7c.5-.07 1-.1 1.5-.1 2.15 0 4.1.4 5.5 1.35 1.4-.95 3.35-1.35 5.5-1.35.5 0 1 .03 1.5.1V1.85c-.5-.07-1-.1-1.5-.1-2.15 0-4.1.4-5.5 1.35ZM7.2 4.9v7.3C5.9 11.55 4.3 11.3 2.6 11.3V3.45c1.7 0 3.3.25 4.6 1.45Zm1.6 0c1.3-1.2 2.9-1.45 4.6-1.45v7.85c-1.7 0-3.3.25-4.6.9V4.9Z",
513
+ fill: "currentColor"
514
+ }));
515
+ }
516
+ /** IconCrownFill16 */
517
+ function IconCrownFill16({ size = 16, className }) {
518
+ return (0, react.createElement)("svg", {
519
+ width: size,
520
+ height: size,
521
+ className,
522
+ viewBox: "0 0 16 16",
523
+ fill: "none",
524
+ xmlns: "http://www.w3.org/2000/svg"
525
+ }, (0, react.createElement)("path", {
526
+ d: "M1.7 11.2 1 4.9l3.6 2.2L8 2.6l3.4 4.5L15 4.9l-.7 6.3H1.7Zm.5 1.4h11.6v1.5H2.1v-1.5Z",
527
+ fill: "currentColor"
528
+ }));
529
+ }
530
+ /** IconBoltFill16 */
531
+ function IconBoltFill16({ size = 16, className }) {
532
+ return (0, react.createElement)("svg", {
533
+ width: size,
534
+ height: size,
535
+ className,
536
+ viewBox: "0 0 16 16",
537
+ fill: "none",
538
+ xmlns: "http://www.w3.org/2000/svg"
539
+ }, (0, react.createElement)("path", {
540
+ d: "M9.6 1 3.4 9.3h3.4L6.3 15l6.3-8.5H9.2L9.6 1Z",
541
+ fill: "currentColor"
542
+ }));
543
+ }
544
+ /** IconPowerFill16 */
545
+ function IconPowerFill16({ size = 16, className }) {
546
+ return (0, react.createElement)("svg", {
547
+ width: size,
548
+ height: size,
549
+ className,
550
+ viewBox: "0 0 16 16",
551
+ fill: "none",
552
+ xmlns: "http://www.w3.org/2000/svg"
553
+ }, (0, react.createElement)("path", {
554
+ d: "M7.2 1h1.6v5.6H7.2V1Z",
555
+ fill: "currentColor"
556
+ }), (0, react.createElement)("path", {
557
+ "fill-rule": "evenodd",
558
+ "clip-rule": "evenodd",
559
+ d: "M4.55 3.15A6 6 0 1 0 11.45 3.15L12.5 4.2a7.5 7.5 0 1 1-9 0l1.05-1.05Z",
560
+ fill: "currentColor"
561
+ }));
562
+ }
563
+ /** IconPinFill16 */
564
+ function IconPinFill16({ size = 16, className }) {
565
+ return (0, react.createElement)("svg", {
566
+ width: size,
567
+ height: size,
568
+ className,
569
+ viewBox: "0 0 16 16",
570
+ fill: "none",
571
+ xmlns: "http://www.w3.org/2000/svg"
572
+ }, (0, react.createElement)("path", {
573
+ "fill-rule": "evenodd",
574
+ "clip-rule": "evenodd",
575
+ d: "M8 1.4a4.8 4.8 0 0 1 4.8 4.8c0 3.4-4.8 8.4-4.8 8.4S3.2 9.6 3.2 6.2A4.8 4.8 0 0 1 8 1.4Zm0 2.7a2.1 2.1 0 1 0 0 4.2 2.1 2.1 0 0 0 0-4.2Z",
576
+ fill: "currentColor"
577
+ }));
578
+ }
579
+ /** IconFlameFill16 */
580
+ function IconFlameFill16({ size = 16, className }) {
581
+ return (0, react.createElement)("svg", {
582
+ width: size,
583
+ height: size,
584
+ className,
585
+ viewBox: "0 0 16 16",
586
+ fill: "none",
587
+ xmlns: "http://www.w3.org/2000/svg"
588
+ }, (0, react.createElement)("path", {
589
+ d: "M8.9 1.2c.4 2.5-.9 3.9-2.1 5.2C5.6 7.7 4.4 9 4.4 10.9a3.9 3.9 0 0 0 7.8 0c0-1.6-.7-2.9-1.5-4-.3.8-.8 1.4-1.6 1.7.5-2.4-.2-5.4-1.2-7.4Z",
590
+ fill: "currentColor"
591
+ }));
592
+ }
312
593
  //#endregion
313
594
  //#region src/client/data.ts
314
595
  /**
@@ -651,7 +932,7 @@ window.__ModuleLoader__.load({
651
932
  docker: "dockerfile"
652
933
  };
653
934
  let deps = {};
654
- const CDN$1 = {
935
+ const CDN = {
655
936
  katexJs: "https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.js",
656
937
  katexCss: "https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.css",
657
938
  shiki: "https://esm.sh/shiki@1.29.2",
@@ -677,11 +958,11 @@ window.__ModuleLoader__.load({
677
958
  if (!document.querySelector(`link[data-lks-katex]`)) {
678
959
  const link = document.createElement("link");
679
960
  link.rel = "stylesheet";
680
- link.href = CDN$1.katexCss;
961
+ link.href = CDN.katexCss;
681
962
  link.setAttribute("data-lks-katex", "1");
682
963
  document.head.append(link);
683
964
  }
684
- if (window.katex === void 0) await loadScript(CDN$1.katexJs);
965
+ if (window.katex === void 0) await loadScript(CDN.katexJs);
685
966
  const katex = window.katex;
686
967
  if (katex === void 0) throw new Error("katex missing after load");
687
968
  return katex;
@@ -692,7 +973,7 @@ window.__ModuleLoader__.load({
692
973
  function defaultLoadShiki() {
693
974
  shikiPromise ??= import(
694
975
  /* @vite-ignore */
695
- CDN$1.shiki
976
+ CDN.shiki
696
977
  ).then((mod) => {
697
978
  const create = mod.createHighlighter;
698
979
  if (create === void 0) throw new Error("shiki createHighlighter missing");
@@ -723,12 +1004,12 @@ window.__ModuleLoader__.load({
723
1004
  mermaidPromise ??= (async () => {
724
1005
  const mermaid = (await import(
725
1006
  /* @vite-ignore */
726
- CDN$1.mermaid
1007
+ CDN.mermaid
727
1008
  )).default;
728
1009
  try {
729
1010
  const elk = await import(
730
1011
  /* @vite-ignore */
731
- CDN$1.mermaidElk
1012
+ CDN.mermaidElk
732
1013
  );
733
1014
  mermaid.registerLayoutLoaders(elk.default);
734
1015
  } catch {}
@@ -868,36 +1149,6 @@ window.__ModuleLoader__.load({
868
1149
  } catch {}
869
1150
  }
870
1151
  //#endregion
871
- //#region src/vendor/mindmap-markdown.ts
872
- /**
873
- * mindmap-markdown —— 讲解 markdown → 思维导图源文本的纯函数预处理(v0.21)。
874
- *
875
- * 思维导图是**结构概览**:代码块整段进脑图节点只是噪声(markmap-lib 还会为它
876
- * 内嵌 hljs 高亮 HTML),图片也无处安放。喂给 markmap 前剥离:
877
- * - 围栏代码块(``` 或 ~~~,含缩进围栏)→ 单行「代码块」占位(保住结构位置)
878
- * - 图片 ![alt](url) → alt 文字
879
- * - HTML 注释整段剥除
880
- * 其余(标题层级/列表/粗体/链接文字)原样保留 —— 层级就是脑图骨架。
881
- */
882
- function mindmapMarkdown(md) {
883
- const out = [];
884
- let fence = null;
885
- let fenceLen = 0;
886
- for (const line of md.split("\n")) {
887
- const open = /^(\s*)(`{3,}|~{3,})/.exec(line);
888
- if (fence == null) {
889
- if (open) {
890
- fence = (open[2] ?? "")[0] ?? "`";
891
- fenceLen = (open[2] ?? "").length;
892
- out.push(`${open[1] ?? ""}- 代码块`);
893
- continue;
894
- }
895
- out.push(line);
896
- } else if (new RegExp(`^\\s*\\${fence === "~" ? "~" : "`"}{${fenceLen},}\\s*$`).test(line)) fence = null;
897
- }
898
- return out.join("\n").replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/<!--[\s\S]*?-->/g, "");
899
- }
900
- //#endregion
901
1152
  //#region src/vendor/cmap-elk-layout.ts
902
1153
  /** 字符宽度估算:CJK 全宽,ASCII 约 0.58em,其余(BBOP…)同 ASCII。 */
903
1154
  function estTextWidth(text, fontSize = 13) {
@@ -1263,52 +1514,17 @@ window.__ModuleLoader__.load({
1263
1514
  //#endregion
1264
1515
  //#region src/client/diagrams.ts
1265
1516
  /**
1266
- * Blackboard diagram views (SPEC Phase 2): the lesson mind map (markmap via
1267
- * CDN, markdown preprocessed by the vendored mindmap-markdown) and the lesson
1268
- * concept map (ELK layered layout via the vendored cmap-elk-layout + CDN elkjs,
1269
- * draw.io-flavored SVG skin rendered plugin-side). Both degrade honestly: any
1270
- * CDN/ELK failure leaves a one-line notice instead of a broken pane.
1517
+ * Blackboard diagram views (SPEC Phase 2): the lesson concept map (ELK layered
1518
+ * layout via the vendored cmap-elk-layout + CDN elkjs, draw.io-flavored SVG
1519
+ * skin rendered plugin-side). Degrades honestly: any CDN/ELK failure leaves a
1520
+ * one-line notice instead of a broken pane.
1521
+ *
1522
+ * The lesson mind map view was RETIRED with upstream v0.26.0 (2026-09-01
1523
+ * port): body text that has headings already shows its structure, text without
1524
+ * headings yields a first-line-truncated node graph nobody can read — both
1525
+ * lose — and this concept map already covers the semantic-structure need at
1526
+ * higher quality. Its three CDN imports went with it.
1271
1527
  */
1272
- const CDN = {
1273
- markmapLib: "https://esm.sh/markmap-lib@0.18.12",
1274
- markmapView: "https://esm.sh/markmap-view@0.18.10"
1275
- };
1276
- let markmapPromise = null;
1277
- async function loadMarkmap() {
1278
- markmapPromise ??= (async () => {
1279
- const lib = await import(
1280
- /* @vite-ignore */
1281
- CDN.markmapLib
1282
- );
1283
- const view = await import(
1284
- /* @vite-ignore */
1285
- CDN.markmapView
1286
- );
1287
- const Transformer = lib.Transformer ?? {};
1288
- const d3 = await import(
1289
- /* @vite-ignore */
1290
- "https://esm.sh/d3@7"
1291
- );
1292
- return {
1293
- transformer: new Transformer(),
1294
- Markmap: view.Markmap,
1295
- d3
1296
- };
1297
- })();
1298
- return markmapPromise;
1299
- }
1300
- /** Render the lesson markdown as a mind map into an empty container. */
1301
- async function renderMindmap(container, markdown) {
1302
- const { transformer, Markmap, d3 } = await loadMarkmap();
1303
- const { root } = transformer.transform(mindmapMarkdown(markdown));
1304
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
1305
- svg.setAttribute("style", "width:100%;min-height:420px");
1306
- container.append(svg);
1307
- window["markmap"] = { d3 };
1308
- const mm = Markmap.create(svg, { autoFit: true });
1309
- mm.setData(root);
1310
- mm.fit();
1311
- }
1312
1528
  /** Lesson concept map: the lesson node + its concepts (weak ones accented),
1313
1529
  * laid out by the vendored ELK pipeline, skinned draw.io-style (plugin-original,
1314
1530
  * upstream's skin is coupled to its token pipeline). Mastery shades the nodes. */
@@ -1378,21 +1594,19 @@ window.__ModuleLoader__.load({
1378
1594
  "pane.tutor": "导师",
1379
1595
  "pane.bb": "黑板",
1380
1596
  "viewtab.teach": "讲解",
1381
- "viewtab.mind": "🧠 脑图",
1382
- "viewtab.cmap": "🕸 概念图",
1383
- "viewtab.mind.title": "本课结构一图流(markmap)",
1384
- "viewtab.cmap.title": "本课概念关系(ELK 布局)",
1597
+ "viewtab.cmap": "概念图",
1598
+ "viewtab.cmap.title": "本课概念之间的关系图",
1385
1599
  "loading": "加载中…",
1386
1600
  "start.title": "一键准备学习:建立学习工作区、开启会话并让导师就位",
1387
1601
  "start.go": "开始学习",
1388
1602
  "start.enter": "进入学习",
1389
1603
  "start.busy": "正在准备学习区…",
1390
1604
  "soul.direct": "直讲",
1391
- "soul.direct.hint": "direct 精讲:先讲清楚,再确认懂没懂",
1605
+ "soul.direct.hint": "精讲:先讲清楚,再确认懂没懂",
1392
1606
  "soul.guide": "引导",
1393
- "soul.guide.hint": "guide 引导:让你自己往前推一步,导师递台阶",
1607
+ "soul.guide.hint": "引导:让你自己往前推一步,导师递台阶",
1394
1608
  "soul.practice": "实战",
1395
- "soul.practice.hint": "practice 实战:在真实世界的乱问题里学",
1609
+ "soul.practice.hint": "实战:在真实世界的乱问题里学",
1396
1610
  "zone.understand": "🧠 理解区 — 知识结构",
1397
1611
  "zone.record": "📝 记录区 — 我的话",
1398
1612
  "zone.practice": "✍️ 练习区 — 答题日志",
@@ -1402,24 +1616,25 @@ window.__ModuleLoader__.load({
1402
1616
  "rail.empty.placeholder": "GitHub 仓库链接,如 microsoft/AI-For-Beginners",
1403
1617
  "rail.empty.button": "导入",
1404
1618
  "rail.empty.demo": "导入示例课程",
1405
- "rail.mastered": "{mastered}/{total} 已掌握",
1619
+ "rail.mastered": "毕业 {mastered}/{total} ",
1406
1620
  "rail.avg": "平均掌握度 {pct}%",
1407
1621
  "rail.avg.none": "尚无掌握度数据",
1408
1622
  "rail.search": "搜索课时…(多关键词空格分隔)",
1409
1623
  "rail.locate.title": "在课程树中定位当前焦点课时(自动展开所在章节)",
1410
- "rail.locate": "📍 回到当前课时",
1411
- "rail.due": "🔁 待复习 {count}",
1624
+ "rail.locate": "回到当前课时",
1625
+ "rail.due": "待复习 {count}",
1412
1626
  "rail.due.over": "超{days}天",
1413
1627
  "rail.due.start": "开始复习",
1414
1628
  "rail.due.tag": "这课时的复习今天到期(SM-2)",
1415
1629
  "rail.delete": "删除本课程",
1416
1630
  "rail.delete.confirm": "确认删除?",
1417
- "rail.delete.title.confirm": "再点一次确认删除(含全部进度与笔记)",
1631
+ "rail.delete.title.confirm": "再点一次确认删除(含全部进度与笔记)。反悔前可先在设置页备份状态文件",
1418
1632
  "rail.section.collapse": "折叠本章节",
1419
1633
  "rail.section.expand": "展开本章节({count} 课时)",
1420
1634
  "rail.section.count": "{count} 课",
1421
1635
  "rail.lesson.opening": "正在打开课时会话…",
1422
- "rail.import.toggle": "+ 导入课程",
1636
+ "rail.lesson.openHint": "(点击将打开本课专属会话)",
1637
+ "rail.import.toggle": "导入课程",
1423
1638
  "rail.import.close": "收起导入",
1424
1639
  "tag.weak": "{count} 个薄弱知识点,测验会优先考察",
1425
1640
  "tag.friction": "{count} 次卡点记录(你说\"不懂\"时导师记下的)",
@@ -1433,12 +1648,11 @@ window.__ModuleLoader__.load({
1433
1648
  "chip.unattributed": "未归因",
1434
1649
  "chip.correct": "✓ 答对 · {concept}",
1435
1650
  "chip.wrong": "✗ 答错 · {concept}",
1436
- "chip.import": "📦 导入课程",
1437
- "chip.concepts": "🧠 提炼知识点",
1438
1651
  "row.thinking": "导师思考中…",
1439
1652
  "row.thinking.title": "导师正在推理,回复马上就来",
1440
1653
  "quiz.title": "点击选项作答,也可以直接打字回答",
1441
1654
  "quiz.answer": "选 {letter}:{text}",
1655
+ "tutor.dormant": "学习模式已关闭 — 点右上「▶ 开始学习」开启导师,按钮恢复可用。",
1442
1656
  "tutor.empty": "对话会出现在这里",
1443
1657
  "tutor.empty.hint": "在下方输入框和导师说话",
1444
1658
  "proposal.text": "导师提议你已掌握「{lesson}」:{rationale}",
@@ -1447,7 +1661,7 @@ window.__ModuleLoader__.load({
1447
1661
  "bb.empty": "黑板还空着",
1448
1662
  "bb.empty.hint": "在左侧课程树选择一课",
1449
1663
  "bb.mastery": "掌握度 {pct}%",
1450
- "bb.fallback.mind": "脑图渲染不可用(网络受限) — 已回退讲解视图",
1664
+ "bb.strategy": "本课学法",
1451
1665
  "bb.fallback.cmap.empty": "概念图需要先由导师定义本课概念",
1452
1666
  "bb.fallback.cmap": "概念图渲染不可用(布局引擎加载失败) — 已回退讲解视图",
1453
1667
  "bb.notes": "笔记",
@@ -1471,11 +1685,13 @@ window.__ModuleLoader__.load({
1471
1685
  "settings.stats.xp": "XP {xp} · Lv{level}({pct}%)",
1472
1686
  "settings.stats.today": "今日 {xp}/{goal}",
1473
1687
  "settings.stats.streak": "连续 {days} 天(最长 {best},剩冻结 {freeze})",
1688
+ "settings.about": "关于",
1689
+ "settings.about.hint": "当前运行的构建版本,点击查看该版更新内容。",
1474
1690
  "settings.stateFile": "状态文件",
1475
1691
  "settings.stateFile.hint": "学习进度存放于本机 JSON,换机可迁移",
1476
1692
  "dock.title": "学习状态:待复习 {due} · 连续 {streak} 天 · Lv{level} — 点上方「学习」页签进入",
1477
- "dock.due": "{count}",
1478
- "dock.streak": "🔥{days}d",
1693
+ "dock.due": "{count}",
1694
+ "dock.streak": "{days}",
1479
1695
  "dock.lv": "Lv{level}",
1480
1696
  "prompt.import": "导入课程:用 study_import_github 抓取 {url}",
1481
1697
  "prompt.lesson": "学习「{title}」:用 study_lesson 打开这一课开始学习。",
@@ -1494,21 +1710,19 @@ window.__ModuleLoader__.load({
1494
1710
  "pane.tutor": "Tutor",
1495
1711
  "pane.bb": "Board",
1496
1712
  "viewtab.teach": "Teach",
1497
- "viewtab.mind": "🧠 Mind map",
1498
- "viewtab.cmap": "🕸 Concepts",
1499
- "viewtab.mind.title": "The lesson as one markmap graph",
1500
- "viewtab.cmap.title": "The lesson's concept relations (ELK layout)",
1713
+ "viewtab.cmap": "Concepts",
1714
+ "viewtab.cmap.title": "The lesson's concepts relate",
1501
1715
  "loading": "Loading…",
1502
1716
  "start.title": "One click to study: workspace + session + tutor, all set up",
1503
1717
  "start.go": "Start learning",
1504
1718
  "start.enter": "Enter study",
1505
1719
  "start.busy": "Preparing the study area…",
1506
1720
  "soul.direct": "Direct",
1507
- "soul.direct.hint": "direct: explain first, then check understanding",
1721
+ "soul.direct.hint": "Explain first, then check understanding",
1508
1722
  "soul.guide": "Guide",
1509
- "soul.guide.hint": "guide: you push one step forward, the tutor hands you the stairs",
1723
+ "soul.guide.hint": "You push one step forward; the tutor hands you the stairs",
1510
1724
  "soul.practice": "Practice",
1511
- "soul.practice.hint": "practice: learn inside real messy problems",
1725
+ "soul.practice.hint": "Learn inside real, messy problems",
1512
1726
  "zone.understand": "🧠 Understand — knowledge structure",
1513
1727
  "zone.record": "📝 Record — in my own words",
1514
1728
  "zone.practice": "✍️ Practice — answer log",
@@ -1518,24 +1732,25 @@ window.__ModuleLoader__.load({
1518
1732
  "rail.empty.placeholder": "GitHub repo link, e.g. microsoft/AI-For-Beginners",
1519
1733
  "rail.empty.button": "Import",
1520
1734
  "rail.empty.demo": "Import the demo course",
1521
- "rail.mastered": "{mastered}/{total} mastered",
1735
+ "rail.mastered": "{mastered}/{total} lessons graduated",
1522
1736
  "rail.avg": "avg mastery {pct}%",
1523
1737
  "rail.avg.none": "no mastery data yet",
1524
1738
  "rail.search": "Search lessons… (space-separated keywords)",
1525
1739
  "rail.locate.title": "Locate the current focus lesson in the tree (auto-expands its section)",
1526
- "rail.locate": "📍 Back to current lesson",
1527
- "rail.due": "🔁 {count} due",
1740
+ "rail.locate": "Back to current lesson",
1741
+ "rail.due": "{count} due",
1528
1742
  "rail.due.over": "{days}d overdue",
1529
1743
  "rail.due.start": "Start reviewing",
1530
1744
  "rail.due.tag": "this lesson's review is due today (SM-2)",
1531
1745
  "rail.delete": "Delete this course",
1532
1746
  "rail.delete.confirm": "Delete?",
1533
- "rail.delete.title.confirm": "Click again to confirm (all progress and notes included)",
1747
+ "rail.delete.title.confirm": "Click again to confirm (all progress and notes included). Back up via the state file in Settings first",
1534
1748
  "rail.section.collapse": "Collapse this section",
1535
1749
  "rail.section.expand": "Expand this section ({count} lessons)",
1536
1750
  "rail.section.count": "{count} lessons",
1537
1751
  "rail.lesson.opening": "Opening the lesson session…",
1538
- "rail.import.toggle": " Import a course",
1752
+ "rail.lesson.openHint": " (opens this lesson's own session)",
1753
+ "rail.import.toggle": "Import a course",
1539
1754
  "rail.import.close": "Collapse import",
1540
1755
  "tag.weak": "{count} weak concepts — quizzes target them first",
1541
1756
  "tag.friction": "{count} friction marks (logged when you said you were stuck)",
@@ -1549,12 +1764,11 @@ window.__ModuleLoader__.load({
1549
1764
  "chip.unattributed": "unattributed",
1550
1765
  "chip.correct": "✓ correct · {concept}",
1551
1766
  "chip.wrong": "✗ wrong · {concept}",
1552
- "chip.import": "📦 course import",
1553
- "chip.concepts": "🧠 concepts defined",
1554
1767
  "row.thinking": "tutor is thinking…",
1555
1768
  "row.thinking.title": "The tutor is reasoning; the reply is coming",
1556
1769
  "quiz.title": "Click an option to answer, or just type",
1557
1770
  "quiz.answer": "Choose {letter}: {text}",
1771
+ "tutor.dormant": "Study mode is off — click \"▶ Start learning\" above to wake the tutor and re-enable these controls.",
1558
1772
  "tutor.empty": "The conversation will appear here",
1559
1773
  "tutor.empty.hint": "Talk to the tutor in the input below",
1560
1774
  "proposal.text": "The tutor proposes you have mastered \"{lesson}\": {rationale}",
@@ -1563,7 +1777,7 @@ window.__ModuleLoader__.load({
1563
1777
  "bb.empty": "The blackboard is empty",
1564
1778
  "bb.empty.hint": "Pick a lesson in the course tree on the left",
1565
1779
  "bb.mastery": "mastery {pct}%",
1566
- "bb.fallback.mind": "Mind map unavailable (network-restricted) — fell back to the teach view",
1780
+ "bb.strategy": "Approach",
1567
1781
  "bb.fallback.cmap.empty": "The concept map needs the tutor to define this lesson's concepts first",
1568
1782
  "bb.fallback.cmap": "Concept map unavailable (layout engine failed to load) — fell back to the teach view",
1569
1783
  "bb.notes": "Notes",
@@ -1587,11 +1801,13 @@ window.__ModuleLoader__.load({
1587
1801
  "settings.stats.xp": "XP {xp} · Lv{level} ({pct}%)",
1588
1802
  "settings.stats.today": "Today {xp}/{goal}",
1589
1803
  "settings.stats.streak": "Streak {days}d (best {best}, {freeze} freeze left)",
1804
+ "settings.about": "About",
1805
+ "settings.about.hint": "The version of the running build — click to see its release notes.",
1590
1806
  "settings.stateFile": "State file",
1591
1807
  "settings.stateFile.hint": "Your progress lives in a local JSON file — copy it to migrate machines",
1592
1808
  "dock.title": "Study status: {due} due · {streak}d streak · Lv{level} — open the Study tab above to continue",
1593
- "dock.due": "{count}",
1594
- "dock.streak": "🔥{days}d",
1809
+ "dock.due": "{count}",
1810
+ "dock.streak": "{days}d",
1595
1811
  "dock.lv": "Lv{level}",
1596
1812
  "prompt.import": "Import a course: fetch {url} with study_import_github",
1597
1813
  "prompt.lesson": "Study \"{title}\": open this lesson with study_lesson and start teaching.",
@@ -1668,10 +1884,11 @@ window.__ModuleLoader__.load({
1668
1884
  * The study tab: one `conversation.view` entry rendering the whole plugin —
1669
1885
  * a simplified LookatStudy inside dsh, three columns in the tab. Left: the
1670
1886
  * course map (selector, tree, due box, one-click demo import). Middle: the
1671
- * tutor conversation — a read-only mini transcript folded from the session
1887
+ * tutor conversation — a read-only transcript folded from the session
1672
1888
  * snapshot (user text, assistant text rendered through the plugin's own
1673
- * markdown pipeline, tool chips) plus the soul pills, starters, pending
1674
- * proposal banner, and a reverse-channel input. Right: the blackboard
1889
+ * markdown pipeline; tool calls stay in the host conversation's toolview
1890
+ * cards a mirrored second record diverged) plus the soul pills, starters,
1891
+ * pending proposal banner, and a reverse-channel input. Right: the blackboard —
1675
1892
  * focus-lesson 讲解 and the Cornell 笔记 zones. Nothing outside this tab
1676
1893
  * touches dsh chrome.
1677
1894
  * @module dsh-plugin-lookatstudy/client/views
@@ -1700,13 +1917,13 @@ window.__ModuleLoader__.load({
1700
1917
  ["record", "zone.record"],
1701
1918
  ["practice", "zone.practice"]
1702
1919
  ];
1703
- /** Status glyph for one lesson row (LookatStudy's map icons). */
1704
- function glyph(kind, status) {
1705
- if (kind === "exam") return "🎯";
1706
- if (status === "mastered") return "👑";
1707
- if (status === "in_progress") return "📖";
1708
- if (status === "available") return "⭐";
1709
- return "🔒";
1920
+ /** Status icon for one lesson row (LookatStudy's map icon semantics, SVG glyphs). */
1921
+ function statusIcon(kind, status) {
1922
+ if (kind === "exam") return (0, react.createElement)(IconGoalOutline16, { size: 14 });
1923
+ if (status === "mastered") return (0, react.createElement)(IconCrownFill16, { size: 14 });
1924
+ if (status === "in_progress") return (0, react.createElement)(IconBookFill16, { size: 14 });
1925
+ if (status === "available") return (0, react.createElement)(IconStarFill16, { size: 14 });
1926
+ return (0, react.createElement)(IconLockFill16, { size: 14 });
1710
1927
  }
1711
1928
  /** LookatStudy's exam gate: an exam node opens only when every sibling study lesson reached mastery ≥50%. */
1712
1929
  function examOpen(lessons) {
@@ -1801,40 +2018,6 @@ window.__ModuleLoader__.load({
1801
2018
  flush();
1802
2019
  return runs.at(-1) ?? [];
1803
2020
  }
1804
- /**
1805
- * Learning-aware label for one settled tool call; null keeps the generic
1806
- * 🔧-name chip. Pure (translator defaults to the active one, per call).
1807
- * @param name - tool call name.
1808
- * @param argsRaw - the call's raw JSON args.
1809
- * @param t - translator.
1810
- */
1811
- function toolChipLabel(name, argsRaw, t = tr) {
1812
- if (name === "study_record_answer") {
1813
- let concept = t("chip.unattributed");
1814
- let correct = false;
1815
- try {
1816
- const args = JSON.parse(argsRaw);
1817
- if (typeof args.concept === "string" && args.concept !== "") concept = args.concept;
1818
- correct = args.correct === true;
1819
- } catch {}
1820
- return correct ? {
1821
- label: t("chip.correct", { concept }),
1822
- tone: "ok"
1823
- } : {
1824
- label: t("chip.wrong", { concept }),
1825
- tone: "bad"
1826
- };
1827
- }
1828
- if (name === "study_import_github" || name === "study_import_markdown" || name === "study_import_folder") return {
1829
- label: t("chip.import"),
1830
- tone: "ok"
1831
- };
1832
- if (name === "study_define_concepts") return {
1833
- label: t("chip.concepts"),
1834
- tone: "ok"
1835
- };
1836
- return null;
1837
- }
1838
2021
  function textOf(blocks) {
1839
2022
  return blocks.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n\n").trim();
1840
2023
  }
@@ -1842,8 +2025,10 @@ window.__ModuleLoader__.load({
1842
2025
  return blocks.filter((b) => b.kind === "text").map((b) => b.text).join("\n\n").trim();
1843
2026
  }
1844
2027
  /**
1845
- * Fold conversation nodes (plus the streaming partial) into render rows;
1846
- * tool activity condenses to one muted chip per settled call.
2028
+ * Fold conversation nodes (plus the streaming partial) into render rows.
2029
+ * Tool results are skipped: the host conversation renders the rich
2030
+ * `tool.call.toolview` cards, and the tutor column stays a pure persona
2031
+ * surface (a transcript mirror there grew a second, diverging record).
1847
2032
  * @param nodes - finalized conversation nodes from the snapshot.
1848
2033
  * @param partial - the in-flight assistant partial, or null.
1849
2034
  * @param t - translator (defaults to the active one, per call).
@@ -1871,14 +2056,6 @@ window.__ModuleLoader__.load({
1871
2056
  });
1872
2057
  break;
1873
2058
  }
1874
- case "tool-result":
1875
- rows.push({
1876
- key: `t${node.seq}`,
1877
- role: "tool",
1878
- text: node.call?.name ?? node.callId,
1879
- call: node.call
1880
- });
1881
- break;
1882
2059
  case "turn-error": rows.push({
1883
2060
  key: `e${node.seq}`,
1884
2061
  role: "error",
@@ -1999,7 +2176,7 @@ window.__ModuleLoader__.load({
1999
2176
  onClick: () => {
2000
2177
  activate(!data.active);
2001
2178
  }
2002
- }, data.active ? tr("active.on") : tr("active.off"))), (0, react.createElement)("div", { className: "lks-body" }, (0, react.createElement)("div", { className: "lks-switch" }, ...[
2179
+ }, data.active ? (0, react.createElement)(IconPowerFill16, { size: 13 }) : (0, react.createElement)(IconPlayOutline16, { size: 13 }), data.active ? tr("active.on") : tr("active.off"))), (0, react.createElement)("div", { className: "lks-body" }, (0, react.createElement)("div", { className: "lks-switch" }, ...[
2003
2180
  {
2004
2181
  id: "rail",
2005
2182
  labelKey: "pane.rail"
@@ -2015,6 +2192,7 @@ window.__ModuleLoader__.load({
2015
2192
  ].map((p) => (0, react.createElement)("button", {
2016
2193
  key: p.id,
2017
2194
  className: `lks-switch-btn${pane === p.id ? " on" : ""}`,
2195
+ "aria-pressed": String(pane === p.id),
2018
2196
  onClick: () => {
2019
2197
  setPane(p.id);
2020
2198
  try {
@@ -2082,6 +2260,21 @@ window.__ModuleLoader__.load({
2082
2260
  const [selectedCourse, setSelectedCourse] = (0, react.useState)("");
2083
2261
  const [query, setQuery] = (0, react.useState)("");
2084
2262
  const [confirmDelete, setConfirmDelete] = (0, react.useState)(false);
2263
+ (0, react.useEffect)(() => {
2264
+ if (!confirmDelete) return;
2265
+ const disarm = () => {
2266
+ setConfirmDelete(false);
2267
+ };
2268
+ const onKey = (e) => {
2269
+ if (e.key === "Escape") disarm();
2270
+ };
2271
+ window.addEventListener("click", disarm);
2272
+ window.addEventListener("keydown", onKey);
2273
+ return () => {
2274
+ window.removeEventListener("click", disarm);
2275
+ window.removeEventListener("keydown", onKey);
2276
+ };
2277
+ }, [confirmDelete]);
2085
2278
  const [showImport, setShowImport] = (0, react.useState)(false);
2086
2279
  /** Per-section open overrides (courseId/sectionTitle → open?), on top of the frontier default. */
2087
2280
  const [secOpen, setSecOpen] = (0, react.useState)({});
@@ -2108,10 +2301,14 @@ window.__ModuleLoader__.load({
2108
2301
  }, ...data.courses.map((c) => (0, react.createElement)("option", {
2109
2302
  key: c.courseId,
2110
2303
  value: c.courseId
2111
- }, c.title))) : (0, react.createElement)("div", { className: "lks-rail-title" }, course.title), (0, react.createElement)("button", {
2304
+ }, c.title))) : (0, react.createElement)("div", {
2305
+ className: "lks-rail-title",
2306
+ title: course.title
2307
+ }, course.title), (0, react.createElement)("button", {
2112
2308
  className: `lks-btn ${confirmDelete ? "primary" : "ghost"}`,
2113
2309
  title: confirmDelete ? tr("rail.delete.title.confirm") : tr("rail.delete"),
2114
- onClick: () => {
2310
+ onClick: (e) => {
2311
+ e.stopPropagation();
2115
2312
  if (!confirmDelete) {
2116
2313
  setConfirmDelete(true);
2117
2314
  return;
@@ -2121,13 +2318,13 @@ window.__ModuleLoader__.load({
2121
2318
  setSelectedCourse("");
2122
2319
  }, reportError);
2123
2320
  }
2124
- }, confirmDelete ? tr("rail.delete.confirm") : "🗑")), (0, react.createElement)("div", { className: "lks-rail-sub" }, tr("rail.mastered", {
2321
+ }, confirmDelete ? tr("rail.delete.confirm") : (0, react.createElement)(IconTrashOutline16, { size: 14 }))), (0, react.createElement)("div", { className: "lks-rail-sub" }, tr("rail.mastered", {
2125
2322
  mastered: course.mastered,
2126
2323
  total: course.total
2127
2324
  })), (0, react.createElement)("div", {
2128
2325
  className: `lks-masterybar${course.avgMasteryPct === 100 ? " gold" : ""}`,
2129
2326
  title: course.avgMasteryPct === null ? tr("rail.avg.none") : tr("rail.avg", { pct: course.avgMasteryPct })
2130
- }, (0, react.createElement)("i", { style: { width: `${course.avgMasteryPct ?? 0}%` } })), (0, react.createElement)("input", {
2327
+ }, (0, react.createElement)("i", { style: { transform: `scaleX(${(course.avgMasteryPct ?? 0) / 100})` } })), (0, react.createElement)("input", {
2131
2328
  className: "lks-search",
2132
2329
  type: "search",
2133
2330
  placeholder: tr("rail.search"),
@@ -2174,7 +2371,7 @@ window.__ModuleLoader__.load({
2174
2371
  });
2175
2372
  }, 60);
2176
2373
  }
2177
- }, tr("rail.locate")) : null, data.dueCount > 0 ? (0, react.createElement)("div", { className: "lks-duebox" }, tr("rail.due", { count: data.dueCount }), ...data.due.map((d) => (0, react.createElement)("div", {
2374
+ }, (0, react.createElement)(IconPinFill16, { size: 13 }), tr("rail.locate")) : null, data.dueCount > 0 ? (0, react.createElement)("div", { className: "lks-duebox" }, (0, react.createElement)(IconRefreshOutline16, { size: 13 }), tr("rail.due", { count: data.dueCount }), ...data.due.map((d) => (0, react.createElement)("div", {
2178
2375
  key: d.lessonId,
2179
2376
  className: "lks-due-item"
2180
2377
  }, (0, react.createElement)("span", null, d.lessonTitle), d.overdueDays > 0 ? (0, react.createElement)("span", { className: "lks-over" }, tr("rail.due.over", { days: d.overdueDays })) : null)), (0, react.createElement)("button", {
@@ -2208,7 +2405,7 @@ window.__ModuleLoader__.load({
2208
2405
  type: "button",
2209
2406
  className: `lks-node${lesson.focus ? " focus" : ""}`,
2210
2407
  "aria-disabled": locked || void 0,
2211
- title: jumping === lesson.id ? tr("rail.lesson.opening") : statusTitle(lesson.kind, locked ? "locked" : lesson.status),
2408
+ title: jumping === lesson.id ? tr("rail.lesson.opening") : `${lesson.title} — ${statusTitle(lesson.kind, locked ? "locked" : lesson.status)}${locked || lesson.kind === "exam" ? "" : tr("rail.lesson.openHint")}`,
2212
2409
  onClick: () => {
2213
2410
  if (locked) return;
2214
2411
  if (lesson.kind === "exam") {
@@ -2218,19 +2415,22 @@ window.__ModuleLoader__.load({
2218
2415
  }
2219
2416
  openLessonThread(lesson);
2220
2417
  }
2221
- }, (0, react.createElement)("span", { className: "lks-g" }, jumping === lesson.id ? "⏳" : glyph(lesson.kind, locked ? "locked" : lesson.status)), (0, react.createElement)("span", { className: "lks-t" }, lesson.title), lesson.due ? (0, react.createElement)("span", {
2418
+ }, (0, react.createElement)("span", { className: "lks-g" }, jumping === lesson.id ? (0, react.createElement)(IconLoadingOutline16, {
2419
+ size: 14,
2420
+ className: "lks-spin"
2421
+ }) : statusIcon(lesson.kind, locked ? "locked" : lesson.status)), (0, react.createElement)("span", { className: "lks-t" }, lesson.title), lesson.due ? (0, react.createElement)("span", {
2222
2422
  className: "lks-tag due",
2223
2423
  title: tr("rail.due.tag")
2224
- }, "🔁") : null, lesson.weakConcepts > 0 ? (0, react.createElement)("span", {
2424
+ }, (0, react.createElement)(IconRefreshOutline16, { size: 11 })) : null, lesson.weakConcepts > 0 ? (0, react.createElement)("span", {
2225
2425
  className: "lks-tag weak",
2226
2426
  title: tr("tag.weak", { count: lesson.weakConcepts })
2227
- }, `⚡${lesson.weakConcepts}`) : null, lesson.frictionCount > 0 ? (0, react.createElement)("span", {
2427
+ }, (0, react.createElement)(IconBoltFill16, { size: 10 }), String(lesson.weakConcepts)) : null, lesson.frictionCount > 0 ? (0, react.createElement)("span", {
2228
2428
  className: "lks-tag fric",
2229
2429
  title: tr("tag.friction", { count: lesson.frictionCount })
2230
- }, `😣${lesson.frictionCount}`) : null, lesson.masteryPct !== null ? (0, react.createElement)("span", {
2430
+ }, (0, react.createElement)(IconWarningOutline16, { size: 10 }), String(lesson.frictionCount)) : null, lesson.masteryPct !== null ? (0, react.createElement)("span", {
2231
2431
  className: "lks-bar",
2232
2432
  title: tr("tag.mastery", { pct: lesson.masteryPct })
2233
- }, (0, react.createElement)("i", { style: { width: `${lesson.masteryPct}%` } })) : null, lesson.masteryPct !== null ? (0, react.createElement)("span", {
2433
+ }, (0, react.createElement)("i", { style: { transform: `scaleX(${lesson.masteryPct / 100})` } })) : null, lesson.masteryPct !== null ? (0, react.createElement)("span", {
2234
2434
  className: "lks-pct",
2235
2435
  title: tr("tag.mastery.short")
2236
2436
  }, `${lesson.masteryPct}%`) : null);
@@ -2241,7 +2441,7 @@ window.__ModuleLoader__.load({
2241
2441
  onClick: () => {
2242
2442
  setShowImport(!showImport);
2243
2443
  }
2244
- }, showImport ? tr("rail.import.close") : tr("rail.import.toggle")), showImport ? (0, react.createElement)(ImportRow, { send }) : null);
2444
+ }, showImport ? tr("rail.import.close") : (0, react.createElement)(IconPlusOutline16, { size: 13 }), tr("rail.import.toggle")), showImport ? (0, react.createElement)(ImportRow, { send }) : null);
2245
2445
  })();
2246
2446
  return (0, react.createElement)("div", { className: "lks-col lks-col-rail" }, (0, react.createElement)("div", { className: "lks-colhead" }, tr("col.rail")), body, (0, react.createElement)(ActionError, { error }));
2247
2447
  }
@@ -2251,17 +2451,6 @@ window.__ModuleLoader__.load({
2251
2451
  key: row.key,
2252
2452
  className: "lks-msg user"
2253
2453
  }, row.text);
2254
- if (row.role === "tool") {
2255
- const chip = row.call === void 0 || row.call === null ? null : toolChipLabel(row.call.name, row.call.argsRaw);
2256
- if (chip !== null) return (0, react.createElement)("div", {
2257
- key: row.key,
2258
- className: `lks-msg tool ${chip.tone}`
2259
- }, chip.label);
2260
- return (0, react.createElement)("div", {
2261
- key: row.key,
2262
- className: "lks-msg tool"
2263
- }, `🔧 ${row.text}`);
2264
- }
2265
2454
  if (row.role === "error") return (0, react.createElement)("div", {
2266
2455
  key: row.key,
2267
2456
  className: "lks-msg error"
@@ -2315,6 +2504,7 @@ window.__ModuleLoader__.load({
2315
2504
  }, [rows.length]);
2316
2505
  const proposal = data?.pendingProposals[0] ?? null;
2317
2506
  const lesson = data?.lesson ?? null;
2507
+ const dormant = data?.active !== true;
2318
2508
  /** Mode switches are the only fallible write left here (host route); surface failures inline. */
2319
2509
  const fire = (action) => {
2320
2510
  action.then(() => {
@@ -2323,10 +2513,10 @@ window.__ModuleLoader__.load({
2323
2513
  setError(err instanceof Error ? err.message : String(err));
2324
2514
  });
2325
2515
  };
2326
- return (0, react.createElement)("div", { className: "lks-col lks-col-tutor" }, (0, react.createElement)("div", { className: "lks-colhead" }, tr("col.tutor")), (0, react.createElement)("div", {
2516
+ return (0, react.createElement)("div", { className: "lks-col lks-col-tutor" }, (0, react.createElement)("div", { className: "lks-colhead" }, tr("col.tutor")), dormant ? (0, react.createElement)("div", { className: "lks-dormant" }, tr("tutor.dormant")) : null, (0, react.createElement)("div", {
2327
2517
  className: "lks-transcript",
2328
2518
  ref: scrollRef
2329
- }, rows.length === 0 ? (0, react.createElement)("div", { className: "lks-empty" }, tr("tutor.empty"), (0, react.createElement)("br"), tr("tutor.empty.hint")) : rows.map((row, i) => chatRowElement(row, i === lastAssistant ? { send } : void 0))), proposal !== null ? (0, react.createElement)("div", { className: "lks-banner" }, (0, react.createElement)("span", null, "🎓"), (0, react.createElement)("span", { className: "lks-why" }, tr("proposal.text", {
2519
+ }, rows.length === 0 && !dormant ? (0, react.createElement)("div", { className: "lks-empty" }, tr("tutor.empty"), (0, react.createElement)("br"), tr("tutor.empty.hint")) : rows.map((row, i) => chatRowElement(row, !dormant && i === lastAssistant ? { send } : void 0))), proposal !== null ? (0, react.createElement)("div", { className: "lks-banner" }, (0, react.createElement)("span", null, "🎓"), (0, react.createElement)("span", { className: "lks-why" }, tr("proposal.text", {
2330
2520
  lesson: proposal.lessonTitle,
2331
2521
  rationale: proposal.rationale
2332
2522
  })), (0, react.createElement)("button", {
@@ -2348,9 +2538,11 @@ window.__ModuleLoader__.load({
2348
2538
  }, ...MODES$1.map((mode) => (0, react.createElement)("button", {
2349
2539
  key: mode.id,
2350
2540
  className: `lks-pill${data?.mode === mode.id ? " on" : ""}`,
2351
- title: tr(mode.hintKey),
2541
+ "aria-pressed": String(data?.mode === mode.id),
2542
+ "aria-disabled": dormant || void 0,
2543
+ title: dormant ? tr("tutor.dormant") : tr(mode.hintKey),
2352
2544
  onClick: () => {
2353
- fire(setMode(mode.id));
2545
+ if (!dormant) fire(setMode(mode.id));
2354
2546
  }
2355
2547
  }, tr(mode.labelKey)))), lesson !== null && lesson.starters.length > 0 ? (0, react.createElement)("div", {
2356
2548
  className: "lks-dock",
@@ -2358,9 +2550,10 @@ window.__ModuleLoader__.load({
2358
2550
  }, ...lesson.starters.map((s) => (0, react.createElement)("button", {
2359
2551
  key: s.label,
2360
2552
  className: "lks-starter",
2361
- title: s.message,
2553
+ "aria-disabled": dormant || void 0,
2554
+ title: dormant ? tr("tutor.dormant") : s.message,
2362
2555
  onClick: () => {
2363
- send(s.message);
2556
+ if (!dormant) send(s.message);
2364
2557
  }
2365
2558
  }, s.label))) : null, (0, react.createElement)(ActionError, { error }));
2366
2559
  }
@@ -2376,42 +2569,38 @@ window.__ModuleLoader__.load({
2376
2569
  enhanceRendered(el).catch(() => {});
2377
2570
  }, [pane, lesson?.html]);
2378
2571
  (0, react.useEffect)(() => {
2379
- if (pane !== "mind" && pane !== "cmap" || diagRef.current === null || lesson === null) return;
2572
+ if (pane !== "cmap" || diagRef.current === null || lesson === null) return;
2380
2573
  const el = diagRef.current;
2381
2574
  el.textContent = "";
2382
- (pane === "mind" ? renderMindmap(el, lesson.markdown) : lesson.concepts.length === 0 ? Promise.reject(/* @__PURE__ */ new Error("no concepts")) : renderLessonConceptMap(el, lesson.title, lesson.concepts.map((c) => ({
2575
+ (lesson.concepts.length === 0 ? Promise.reject(/* @__PURE__ */ new Error("no concepts")) : renderLessonConceptMap(el, lesson.title, lesson.concepts.map((c) => ({
2383
2576
  title: c.title,
2384
2577
  masteryPct: c.masteryPct
2385
2578
  })))).catch((err) => {
2386
2579
  console.error("lks diagram pane failed:", pane, err);
2387
- el.textContent = pane === "mind" ? tr("bb.fallback.mind") : lesson.concepts.length === 0 ? tr("bb.fallback.cmap.empty") : tr("bb.fallback.cmap");
2580
+ el.textContent = lesson.concepts.length === 0 ? tr("bb.fallback.cmap.empty") : tr("bb.fallback.cmap");
2388
2581
  });
2389
2582
  }, [
2390
2583
  pane,
2391
2584
  lesson?.lessonId,
2392
2585
  lesson?.concepts.length
2393
2586
  ]);
2394
- const body = lesson === null ? (0, react.createElement)("div", { className: "lks-empty" }, tr("bb.empty"), (0, react.createElement)("br"), tr("bb.empty.hint")) : (0, react.createElement)("div", null, (0, react.createElement)("div", { className: "lks-lessonhead" }, (0, react.createElement)("h2", null, lesson.title), (0, react.createElement)("div", { className: "lks-meta" }, `${lesson.courseTitle} · ${lesson.status}` + (lesson.masteryPct === null ? "" : ` · ${tr("bb.mastery", { pct: lesson.masteryPct })}`) + ` · ${lesson.strategy}`), lesson.concepts.length > 0 ? (0, react.createElement)("div", { className: "lks-chips" }, ...lesson.concepts.map((c) => (0, react.createElement)("span", {
2587
+ const body = lesson === null ? (0, react.createElement)("div", { className: "lks-empty" }, tr("bb.empty"), (0, react.createElement)("br"), tr("bb.empty.hint")) : (0, react.createElement)("div", null, (0, react.createElement)("div", { className: "lks-lessonhead" }, (0, react.createElement)("h2", null, lesson.title), (0, react.createElement)("div", { className: "lks-meta" }, `${lesson.courseTitle} · ${statusTitle("study", lesson.status)}` + (lesson.masteryPct === null ? "" : ` · ${tr("bb.mastery", { pct: lesson.masteryPct })}`)), (0, react.createElement)("div", { className: "lks-meta" }, `${tr("bb.strategy")}:${lesson.strategy}`), lesson.concepts.length > 0 ? (0, react.createElement)("div", { className: "lks-chips" }, ...lesson.concepts.map((c) => (0, react.createElement)("span", {
2395
2588
  key: c.title,
2396
2589
  className: `lks-chip${c.weak ? " weak" : ""}`
2397
2590
  }, `${c.title} ${c.masteryPct}%${c.weak ? " ⚡" : ""}`))) : null), (0, react.createElement)("div", { className: "lks-viewtabs" }, (0, react.createElement)("button", {
2398
2591
  className: `lks-viewtab${pane === "teach" ? " on" : ""}`,
2592
+ "aria-pressed": String(pane === "teach"),
2399
2593
  onClick: () => {
2400
2594
  setPane("teach");
2401
2595
  }
2402
2596
  }, tr("viewtab.teach")), (0, react.createElement)("button", {
2403
- className: `lks-viewtab${pane === "mind" ? " on" : ""}`,
2404
- title: tr("viewtab.mind.title"),
2405
- onClick: () => {
2406
- setPane("mind");
2407
- }
2408
- }, tr("viewtab.mind")), (0, react.createElement)("button", {
2409
2597
  className: `lks-viewtab${pane === "cmap" ? " on" : ""}`,
2598
+ "aria-pressed": String(pane === "cmap"),
2410
2599
  title: tr("viewtab.cmap.title"),
2411
2600
  onClick: () => {
2412
2601
  setPane("cmap");
2413
2602
  }
2414
- }, tr("viewtab.cmap"))), pane === "teach" ? (0, react.createElement)("div", {
2603
+ }, (0, react.createElement)(IconGlobeOutline14, { size: 13 }), tr("viewtab.cmap"))), pane === "teach" ? (0, react.createElement)("div", {
2415
2604
  className: "lks-prose",
2416
2605
  ref: proseRef,
2417
2606
  dangerouslySetInnerHTML: { __html: lesson.html }
@@ -2424,10 +2613,13 @@ window.__ModuleLoader__.load({
2424
2613
  }, tr("bb.notes.empty")) : ZONES.filter(([zone]) => lesson.notes.some((n) => n.zone === zone)).map(([zone, labelKey]) => (0, react.createElement)("div", {
2425
2614
  key: zone,
2426
2615
  className: "lks-zone"
2427
- }, (0, react.createElement)("h4", null, tr(labelKey)), ...lesson.notes.filter((n) => n.zone === zone).map((n) => (0, react.createElement)("div", {
2616
+ }, (0, react.createElement)("div", { className: "lks-zone-h" }, tr(labelKey)), ...lesson.notes.filter((n) => n.zone === zone).map((n) => (0, react.createElement)("div", {
2428
2617
  key: n.id,
2429
2618
  className: "lks-note"
2430
- }, (0, react.createElement)("span", { className: "lks-note-src" }, n.source), (0, react.createElement)("div", { className: "lks-note-title" }, n.title), (0, react.createElement)("div", { className: "lks-note-text" }, n.text), n.quote !== null ? (0, react.createElement)("div", { className: "lks-note-q" }, `“${n.quote}”`) : null))))));
2619
+ }, (0, react.createElement)("span", { className: "lks-note-src" }, n.source), (0, react.createElement)("div", { className: "lks-note-title" }, n.title), (0, react.createElement)("div", {
2620
+ className: "lks-note-text",
2621
+ dangerouslySetInnerHTML: { __html: renderMarkdown(n.text) }
2622
+ }), n.quote !== null ? (0, react.createElement)("div", { className: "lks-note-q" }, `“${n.quote}”`) : null))))));
2431
2623
  return (0, react.createElement)("div", { className: "lks-col lks-col-bb" }, (0, react.createElement)("div", { className: "lks-colhead" }, tr("col.bb")), body);
2432
2624
  }
2433
2625
  //#endregion
@@ -2572,7 +2764,11 @@ window.__ModuleLoader__.load({
2572
2764
  days: progress.streak,
2573
2765
  best: progress.longestStreak,
2574
2766
  freeze: progress.freezeCount
2575
- })))), (0, react.createElement)("section", { className: "lks-set-row" }, (0, react.createElement)("h3", null, tr("settings.stateFile")), (0, react.createElement)("p", { className: "lks-set-hint" }, tr("settings.stateFile.hint")), data === null ? null : (0, react.createElement)("code", { className: "lks-set-path" }, data.statePath)), (0, react.createElement)(ActionError, { error }));
2767
+ })))), (0, react.createElement)("section", { className: "lks-set-row" }, (0, react.createElement)("h3", null, tr("settings.about")), (0, react.createElement)("p", { className: "lks-set-hint" }, tr("settings.about.hint")), data?.version ? (0, react.createElement)("a", {
2768
+ href: `https://github.com/Kaiji-Z/dsh-plugin-lookatstudy/releases/tag/v${data.version}`,
2769
+ target: "_blank",
2770
+ rel: "noreferrer"
2771
+ }, `v${data.version}`) : null), (0, react.createElement)("section", { className: "lks-set-row" }, (0, react.createElement)("h3", null, tr("settings.stateFile")), (0, react.createElement)("p", { className: "lks-set-hint" }, tr("settings.stateFile.hint")), data === null ? null : (0, react.createElement)("code", { className: "lks-set-path" }, data.statePath)), (0, react.createElement)(ActionError, { error }));
2576
2772
  }
2577
2773
  //#endregion
2578
2774
  //#region src/client/dock.tsx
@@ -2586,20 +2782,23 @@ window.__ModuleLoader__.load({
2586
2782
  * button's limitation), so the tooltip points at the Study tab instead.
2587
2783
  * @module dsh-plugin-lookatstudy/client/dock
2588
2784
  */
2589
- /** Pure projection of the pill's segments (testable without React). */
2785
+ /** Pure projection of the pill's segments (testable without React). `muted` marks zero-value segments — an empty state must not wear a warning color. */
2590
2786
  function dockSegments(progress) {
2591
2787
  return [
2592
2788
  {
2593
2789
  key: "due",
2594
- text: tr("dock.due", { count: progress.dueCount })
2790
+ text: tr("dock.due", { count: progress.dueCount }),
2791
+ muted: progress.dueCount === 0
2595
2792
  },
2596
2793
  {
2597
2794
  key: "streak",
2598
- text: tr("dock.streak", { days: progress.streak })
2795
+ text: tr("dock.streak", { days: progress.streak }),
2796
+ muted: progress.streak === 0
2599
2797
  },
2600
2798
  {
2601
2799
  key: "lv",
2602
- text: tr("dock.lv", { level: progress.level })
2800
+ text: tr("dock.lv", { level: progress.level }),
2801
+ muted: false
2603
2802
  }
2604
2803
  ];
2605
2804
  }
@@ -2621,8 +2820,8 @@ window.__ModuleLoader__.load({
2621
2820
  })
2622
2821
  }, ...segments.map((s) => (0, react.createElement)("span", {
2623
2822
  key: s.key,
2624
- className: `lks-dockseg lks-dock-${s.key}`
2625
- }, s.text)));
2823
+ className: `lks-dockseg lks-dock-${s.key}${s.muted ? " lks-muted" : ""}`
2824
+ }, s.key === "due" ? (0, react.createElement)(IconBoltFill16, { size: 11 }) : s.key === "streak" ? (0, react.createElement)(IconFlameFill16, { size: 11 }) : null, s.text)));
2626
2825
  }
2627
2826
  //#endregion
2628
2827
  //#region src/client/toolviews.tsx