dsh-plugin-lookatstudy 0.12.0 → 0.12.2
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 +553 -234
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +21 -16
- package/package.json +1 -1
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
|
-
.
|
|
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-
|
|
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-
|
|
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-
|
|
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(--
|
|
87
|
-
.lks-tag.due{background:var(--dsw-alias-state-warn-tertiary);color:var(--
|
|
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-
|
|
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(--
|
|
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-
|
|
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-
|
|
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-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-
|
|
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-
|
|
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(--
|
|
121
|
-
.lks-tag.fric{background:var(--dsw-alias-state-error-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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-
|
|
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(--
|
|
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}
|
|
@@ -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
|
|
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;
|
|
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,25 +249,35 @@ 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-
|
|
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-
|
|
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(--
|
|
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)}
|
|
263
|
+
|
|
264
|
+
/* concept map legend (amber = weak) */
|
|
265
|
+
.lks-cmap-legend{display:inline-flex;align-items:center;gap:5px;font-size:12px;color:var(--dsw-alias-label-secondary);margin-top:4px}
|
|
266
|
+
.lks-cmap-legend i{width:10px;height:10px;border-radius:3px;background:var(--dsw-alias-state-warn-tertiary,#fef5e7);border:1px solid var(--dsw-alias-state-warn-primary,#dd8629)}
|
|
230
267
|
|
|
231
268
|
/* keyed tool.call.toolview cards (conversation tab tool rows) */
|
|
232
269
|
.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
270
|
.lks-tv.err{border-color:var(--dsw-alias-state-error-primary)}
|
|
234
271
|
.lks-tv-head{font-weight:600;color:var(--dsw-alias-label-secondary);font-size:12px}
|
|
235
272
|
.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(--
|
|
237
|
-
.lks-tv-chip.bad{background:var(--dsw-alias-state-error-tertiary);color:var(--
|
|
273
|
+
.lks-tv-chip.ok{background:var(--dsw-alias-state-success-tertiary);color:var(--lks-ok-ink)}
|
|
274
|
+
.lks-tv-chip.bad{background:var(--dsw-alias-state-error-tertiary);color:var(--lks-err-ink)}
|
|
238
275
|
.lks-tv-line{color:var(--dsw-alias-label-secondary);line-height:1.6;white-space:pre-wrap}
|
|
276
|
+
/* exam star card + the failure moment */
|
|
277
|
+
.lks-tv-stars{display:inline-flex;align-items:center;gap:5px;font-size:13px;font-weight:600;color:var(--lks-warn-ink)}
|
|
278
|
+
.lks-tv-stars .dim{opacity:.22}
|
|
279
|
+
.lks-tv-stars-label{font-weight:400;color:var(--dsw-alias-label-secondary)}
|
|
280
|
+
.lks-tv-failnote{margin-top:4px;font-size:12.5px;color:var(--dsw-alias-label-secondary);line-height:1.6}
|
|
239
281
|
`;
|
|
240
282
|
/**
|
|
241
283
|
* Inject the study stylesheet into `document.head` (idempotent).
|
|
@@ -260,6 +302,11 @@ window.__ModuleLoader__.load({
|
|
|
260
302
|
* importing the host package root would drag its CSS modules into the
|
|
261
303
|
* inlined bundle. Re-sync these three paths if the host icon set redesigns
|
|
262
304
|
* them; scripts/verify.mjs pins the path data.
|
|
305
|
+
*
|
|
306
|
+
* 2026-09-01 critique round: seven more ic_ds glyphs vendored (play/plus/
|
|
307
|
+
* trash/refresh/globe/goal/warning) plus plugin-original learning-domain
|
|
308
|
+
* companions (lock/star/book/crown/bolt/power/pin/flame) drawn in the same
|
|
309
|
+
* language — those are NOT ic_ds, see their section comment below.
|
|
263
310
|
* @module dsh-plugin-lookatstudy/client/icons
|
|
264
311
|
*/
|
|
265
312
|
/** ic_ds_think_outline_16 */
|
|
@@ -309,6 +356,249 @@ window.__ModuleLoader__.load({
|
|
|
309
356
|
fill: "currentColor"
|
|
310
357
|
}));
|
|
311
358
|
}
|
|
359
|
+
/** Vendored 2026-09-01 for the critique icon round (same figma source as the three above). */
|
|
360
|
+
/** IconPlayOutline16 */
|
|
361
|
+
function IconPlayOutline16({ size = 16, className }) {
|
|
362
|
+
return (0, react.createElement)("svg", {
|
|
363
|
+
width: size,
|
|
364
|
+
height: size,
|
|
365
|
+
className,
|
|
366
|
+
viewBox: "0 0 16 16",
|
|
367
|
+
fill: "none",
|
|
368
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
369
|
+
}, (0, react.createElement)("path", {
|
|
370
|
+
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",
|
|
371
|
+
fill: "currentColor"
|
|
372
|
+
}), (0, react.createElement)("path", {
|
|
373
|
+
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",
|
|
374
|
+
fill: "currentColor"
|
|
375
|
+
}));
|
|
376
|
+
}
|
|
377
|
+
/** IconPlusOutline16 */
|
|
378
|
+
function IconPlusOutline16({ size = 16, className }) {
|
|
379
|
+
return (0, react.createElement)("svg", {
|
|
380
|
+
width: size,
|
|
381
|
+
height: size,
|
|
382
|
+
className,
|
|
383
|
+
viewBox: "0 0 16 16",
|
|
384
|
+
fill: "none",
|
|
385
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
386
|
+
}, (0, react.createElement)("path", {
|
|
387
|
+
d: "M8.64453 1.5V7.34961H14.5V8.65039H8.64453V14.5H7.34473V8.65039H1.5V7.34961H7.34473V1.5H8.64453Z",
|
|
388
|
+
fill: "currentColor"
|
|
389
|
+
}));
|
|
390
|
+
}
|
|
391
|
+
/** IconTrashOutline16 */
|
|
392
|
+
function IconTrashOutline16({ size = 16, className }) {
|
|
393
|
+
return (0, react.createElement)("svg", {
|
|
394
|
+
width: size,
|
|
395
|
+
height: size,
|
|
396
|
+
className,
|
|
397
|
+
viewBox: "0 0 16 16",
|
|
398
|
+
fill: "none",
|
|
399
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
400
|
+
}, (0, react.createElement)("path", {
|
|
401
|
+
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",
|
|
402
|
+
fill: "currentColor"
|
|
403
|
+
}));
|
|
404
|
+
}
|
|
405
|
+
/** IconRefreshOutline16 */
|
|
406
|
+
function IconRefreshOutline16({ size = 16, className }) {
|
|
407
|
+
return (0, react.createElement)("svg", {
|
|
408
|
+
width: size,
|
|
409
|
+
height: size,
|
|
410
|
+
className,
|
|
411
|
+
viewBox: "0 0 16 16",
|
|
412
|
+
fill: "none",
|
|
413
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
414
|
+
}, (0, react.createElement)("path", {
|
|
415
|
+
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",
|
|
416
|
+
fill: "currentColor"
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
/** IconGlobeOutline14 */
|
|
420
|
+
function IconGlobeOutline14({ size = 16, className }) {
|
|
421
|
+
return (0, react.createElement)("svg", {
|
|
422
|
+
width: size,
|
|
423
|
+
height: size,
|
|
424
|
+
className,
|
|
425
|
+
viewBox: "0 0 16 16",
|
|
426
|
+
fill: "none",
|
|
427
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
428
|
+
}, (0, react.createElement)("path", {
|
|
429
|
+
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",
|
|
430
|
+
fill: "currentColor"
|
|
431
|
+
}));
|
|
432
|
+
}
|
|
433
|
+
/** IconGoalOutline16 */
|
|
434
|
+
function IconGoalOutline16({ size = 16, className }) {
|
|
435
|
+
return (0, react.createElement)("svg", {
|
|
436
|
+
width: size,
|
|
437
|
+
height: size,
|
|
438
|
+
className,
|
|
439
|
+
viewBox: "0 0 16 16",
|
|
440
|
+
fill: "none",
|
|
441
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
442
|
+
}, (0, react.createElement)("path", {
|
|
443
|
+
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",
|
|
444
|
+
fill: "currentColor"
|
|
445
|
+
}), (0, react.createElement)("path", {
|
|
446
|
+
d: "M7.5 8.62109L9.12109 7",
|
|
447
|
+
fill: "currentColor"
|
|
448
|
+
}), (0, react.createElement)("path", {
|
|
449
|
+
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",
|
|
450
|
+
fill: "currentColor"
|
|
451
|
+
}));
|
|
452
|
+
}
|
|
453
|
+
/** IconWarningOutline16 */
|
|
454
|
+
function IconWarningOutline16({ size = 16, className }) {
|
|
455
|
+
return (0, react.createElement)("svg", {
|
|
456
|
+
width: size,
|
|
457
|
+
height: size,
|
|
458
|
+
className,
|
|
459
|
+
viewBox: "0 0 16 16",
|
|
460
|
+
fill: "none",
|
|
461
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
462
|
+
}, (0, react.createElement)("path", {
|
|
463
|
+
d: "M6.3002 3.32843L7.69986 3.32843L7.69986 7.79657H6.3002L6.3002 3.32843Z",
|
|
464
|
+
fill: "currentColor"
|
|
465
|
+
}), (0, react.createElement)("path", {
|
|
466
|
+
d: "M6.3002 9.01935H7.69986V10.6711H6.3002V9.01935Z",
|
|
467
|
+
fill: "currentColor"
|
|
468
|
+
}), (0, react.createElement)("path", {
|
|
469
|
+
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",
|
|
470
|
+
fill: "currentColor"
|
|
471
|
+
}));
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Plugin-original companions for learning-domain glyphs the host set does not
|
|
475
|
+
* carry (lock/star/book/crown/bolt/power/pin/flame) — authored here in the
|
|
476
|
+
* same 16px currentColor filled language as the ic_ds set, but they are NOT
|
|
477
|
+
* ic_ds glyphs and must be re-drawn (not re-synced) if the style drifts.
|
|
478
|
+
*/
|
|
479
|
+
/** IconLockFill16 */
|
|
480
|
+
function IconLockFill16({ size = 16, className }) {
|
|
481
|
+
return (0, react.createElement)("svg", {
|
|
482
|
+
width: size,
|
|
483
|
+
height: size,
|
|
484
|
+
className,
|
|
485
|
+
viewBox: "0 0 16 16",
|
|
486
|
+
fill: "none",
|
|
487
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
488
|
+
}, (0, react.createElement)("path", {
|
|
489
|
+
"fill-rule": "evenodd",
|
|
490
|
+
"clip-rule": "evenodd",
|
|
491
|
+
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",
|
|
492
|
+
fill: "currentColor"
|
|
493
|
+
}));
|
|
494
|
+
}
|
|
495
|
+
/** IconStarFill16 */
|
|
496
|
+
function IconStarFill16({ size = 16, className }) {
|
|
497
|
+
return (0, react.createElement)("svg", {
|
|
498
|
+
width: size,
|
|
499
|
+
height: size,
|
|
500
|
+
className,
|
|
501
|
+
viewBox: "0 0 16 16",
|
|
502
|
+
fill: "none",
|
|
503
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
504
|
+
}, (0, react.createElement)("path", {
|
|
505
|
+
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",
|
|
506
|
+
fill: "currentColor"
|
|
507
|
+
}));
|
|
508
|
+
}
|
|
509
|
+
/** IconBookFill16 */
|
|
510
|
+
function IconBookFill16({ size = 16, className }) {
|
|
511
|
+
return (0, react.createElement)("svg", {
|
|
512
|
+
width: size,
|
|
513
|
+
height: size,
|
|
514
|
+
className,
|
|
515
|
+
viewBox: "0 0 16 16",
|
|
516
|
+
fill: "none",
|
|
517
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
518
|
+
}, (0, react.createElement)("path", {
|
|
519
|
+
"fill-rule": "evenodd",
|
|
520
|
+
"clip-rule": "evenodd",
|
|
521
|
+
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",
|
|
522
|
+
fill: "currentColor"
|
|
523
|
+
}));
|
|
524
|
+
}
|
|
525
|
+
/** IconCrownFill16 */
|
|
526
|
+
function IconCrownFill16({ size = 16, className }) {
|
|
527
|
+
return (0, react.createElement)("svg", {
|
|
528
|
+
width: size,
|
|
529
|
+
height: size,
|
|
530
|
+
className,
|
|
531
|
+
viewBox: "0 0 16 16",
|
|
532
|
+
fill: "none",
|
|
533
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
534
|
+
}, (0, react.createElement)("path", {
|
|
535
|
+
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",
|
|
536
|
+
fill: "currentColor"
|
|
537
|
+
}));
|
|
538
|
+
}
|
|
539
|
+
/** IconBoltFill16 */
|
|
540
|
+
function IconBoltFill16({ size = 16, className }) {
|
|
541
|
+
return (0, react.createElement)("svg", {
|
|
542
|
+
width: size,
|
|
543
|
+
height: size,
|
|
544
|
+
className,
|
|
545
|
+
viewBox: "0 0 16 16",
|
|
546
|
+
fill: "none",
|
|
547
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
548
|
+
}, (0, react.createElement)("path", {
|
|
549
|
+
d: "M9.6 1 3.4 9.3h3.4L6.3 15l6.3-8.5H9.2L9.6 1Z",
|
|
550
|
+
fill: "currentColor"
|
|
551
|
+
}));
|
|
552
|
+
}
|
|
553
|
+
/** IconPowerFill16 */
|
|
554
|
+
function IconPowerFill16({ size = 16, className }) {
|
|
555
|
+
return (0, react.createElement)("svg", {
|
|
556
|
+
width: size,
|
|
557
|
+
height: size,
|
|
558
|
+
className,
|
|
559
|
+
viewBox: "0 0 16 16",
|
|
560
|
+
fill: "none",
|
|
561
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
562
|
+
}, (0, react.createElement)("path", {
|
|
563
|
+
d: "M7.2 1h1.6v5.6H7.2V1Z",
|
|
564
|
+
fill: "currentColor"
|
|
565
|
+
}), (0, react.createElement)("path", {
|
|
566
|
+
"fill-rule": "evenodd",
|
|
567
|
+
"clip-rule": "evenodd",
|
|
568
|
+
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",
|
|
569
|
+
fill: "currentColor"
|
|
570
|
+
}));
|
|
571
|
+
}
|
|
572
|
+
/** IconPinFill16 */
|
|
573
|
+
function IconPinFill16({ size = 16, className }) {
|
|
574
|
+
return (0, react.createElement)("svg", {
|
|
575
|
+
width: size,
|
|
576
|
+
height: size,
|
|
577
|
+
className,
|
|
578
|
+
viewBox: "0 0 16 16",
|
|
579
|
+
fill: "none",
|
|
580
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
581
|
+
}, (0, react.createElement)("path", {
|
|
582
|
+
"fill-rule": "evenodd",
|
|
583
|
+
"clip-rule": "evenodd",
|
|
584
|
+
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",
|
|
585
|
+
fill: "currentColor"
|
|
586
|
+
}));
|
|
587
|
+
}
|
|
588
|
+
/** IconFlameFill16 */
|
|
589
|
+
function IconFlameFill16({ size = 16, className }) {
|
|
590
|
+
return (0, react.createElement)("svg", {
|
|
591
|
+
width: size,
|
|
592
|
+
height: size,
|
|
593
|
+
className,
|
|
594
|
+
viewBox: "0 0 16 16",
|
|
595
|
+
fill: "none",
|
|
596
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
597
|
+
}, (0, react.createElement)("path", {
|
|
598
|
+
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",
|
|
599
|
+
fill: "currentColor"
|
|
600
|
+
}));
|
|
601
|
+
}
|
|
312
602
|
//#endregion
|
|
313
603
|
//#region src/client/data.ts
|
|
314
604
|
/**
|
|
@@ -1231,77 +1521,6 @@ window.__ModuleLoader__.load({
|
|
|
1231
1521
|
return hubId;
|
|
1232
1522
|
}
|
|
1233
1523
|
//#endregion
|
|
1234
|
-
//#region src/client/diagrams.ts
|
|
1235
|
-
/**
|
|
1236
|
-
* Blackboard diagram views (SPEC Phase 2): the lesson concept map (ELK layered
|
|
1237
|
-
* layout via the vendored cmap-elk-layout + CDN elkjs, draw.io-flavored SVG
|
|
1238
|
-
* skin rendered plugin-side). Degrades honestly: any CDN/ELK failure leaves a
|
|
1239
|
-
* one-line notice instead of a broken pane.
|
|
1240
|
-
*
|
|
1241
|
-
* The lesson mind map view was RETIRED with upstream v0.26.0 (2026-09-01
|
|
1242
|
-
* port): body text that has headings already shows its structure, text without
|
|
1243
|
-
* headings yields a first-line-truncated node graph nobody can read — both
|
|
1244
|
-
* lose — and this concept map already covers the semantic-structure need at
|
|
1245
|
-
* higher quality. Its three CDN imports went with it.
|
|
1246
|
-
*/
|
|
1247
|
-
/** Lesson concept map: the lesson node + its concepts (weak ones accented),
|
|
1248
|
-
* laid out by the vendored ELK pipeline, skinned draw.io-style (plugin-original,
|
|
1249
|
-
* upstream's skin is coupled to its token pipeline). Mastery shades the nodes. */
|
|
1250
|
-
async function renderLessonConceptMap(container, lessonTitle, concepts) {
|
|
1251
|
-
const layout = await layoutConceptMap([{
|
|
1252
|
-
id: "lesson",
|
|
1253
|
-
label: lessonTitle
|
|
1254
|
-
}, ...concepts.map((c) => ({
|
|
1255
|
-
id: `c-${c.title}`,
|
|
1256
|
-
label: c.title
|
|
1257
|
-
}))], concepts.map((c) => ({
|
|
1258
|
-
from: "lesson",
|
|
1259
|
-
to: `c-${c.title}`
|
|
1260
|
-
})));
|
|
1261
|
-
const masteryBy = new Map(concepts.map((c) => [`c-${c.title}`, c.masteryPct]));
|
|
1262
|
-
const ns = "http://www.w3.org/2000/svg";
|
|
1263
|
-
const svg = document.createElementNS(ns, "svg");
|
|
1264
|
-
svg.setAttribute("width", String(Math.max(320, layout.width)));
|
|
1265
|
-
svg.setAttribute("height", String(Math.max(200, layout.height)));
|
|
1266
|
-
svg.setAttribute("viewBox", `0 0 ${layout.width} ${layout.height}`);
|
|
1267
|
-
svg.setAttribute("style", "max-width:100%;background:var(--dsw-surface-1, #fff);border:1px solid var(--dsw-border-1, #d0d7de);border-radius:8px");
|
|
1268
|
-
for (const e of layout.edges) {
|
|
1269
|
-
const path = document.createElementNS(ns, "path");
|
|
1270
|
-
path.setAttribute("d", e.pts.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" "));
|
|
1271
|
-
path.setAttribute("fill", "none");
|
|
1272
|
-
path.setAttribute("stroke", "var(--dsw-border-2, #8b949e)");
|
|
1273
|
-
path.setAttribute("stroke-width", "1.5");
|
|
1274
|
-
svg.append(path);
|
|
1275
|
-
}
|
|
1276
|
-
for (const n of layout.nodes) {
|
|
1277
|
-
const g = document.createElementNS(ns, "g");
|
|
1278
|
-
g.setAttribute("transform", `translate(${n.x},${n.y})`);
|
|
1279
|
-
const rect = document.createElementNS(ns, "rect");
|
|
1280
|
-
const isLesson = n.id === "lesson";
|
|
1281
|
-
const mastery = masteryBy.get(n.id);
|
|
1282
|
-
rect.setAttribute("width", String(n.w));
|
|
1283
|
-
rect.setAttribute("height", String(n.h));
|
|
1284
|
-
rect.setAttribute("rx", "6");
|
|
1285
|
-
rect.setAttribute("fill", mastery !== void 0 && mastery < 70 ? "#fff3d9" : "var(--dsw-surface-2, #f6f8fa)");
|
|
1286
|
-
rect.setAttribute("stroke", isLesson ? "var(--dsw-accent-1, #0969da)" : "var(--dsw-border-2, #8b949e)");
|
|
1287
|
-
rect.setAttribute("stroke-width", isLesson ? "2" : "1");
|
|
1288
|
-
g.append(rect);
|
|
1289
|
-
n.lines.forEach((line, li) => {
|
|
1290
|
-
const label = document.createElementNS(ns, "text");
|
|
1291
|
-
label.setAttribute("x", String(n.w / 2));
|
|
1292
|
-
label.setAttribute("y", String(n.h / 2 - (n.lines.length - 1) * 7 + li * 14 + 4));
|
|
1293
|
-
label.setAttribute("text-anchor", "middle");
|
|
1294
|
-
label.setAttribute("font-size", isLesson ? "13" : "12");
|
|
1295
|
-
label.setAttribute("font-weight", isLesson ? "600" : "400");
|
|
1296
|
-
label.setAttribute("fill", "var(--dsw-ink-1, #1f2328)");
|
|
1297
|
-
label.textContent = line;
|
|
1298
|
-
g.append(label);
|
|
1299
|
-
});
|
|
1300
|
-
svg.append(g);
|
|
1301
|
-
}
|
|
1302
|
-
container.append(svg);
|
|
1303
|
-
}
|
|
1304
|
-
//#endregion
|
|
1305
1524
|
//#region src/client/locale.ts
|
|
1306
1525
|
/** Simplified Chinese dictionary (also the fallback locale). */
|
|
1307
1526
|
const ZH = {
|
|
@@ -1313,19 +1532,19 @@ window.__ModuleLoader__.load({
|
|
|
1313
1532
|
"pane.tutor": "导师",
|
|
1314
1533
|
"pane.bb": "黑板",
|
|
1315
1534
|
"viewtab.teach": "讲解",
|
|
1316
|
-
"viewtab.cmap": "
|
|
1317
|
-
"viewtab.cmap.title": "
|
|
1535
|
+
"viewtab.cmap": "概念图",
|
|
1536
|
+
"viewtab.cmap.title": "本课概念之间的关系图",
|
|
1318
1537
|
"loading": "加载中…",
|
|
1319
1538
|
"start.title": "一键准备学习:建立学习工作区、开启会话并让导师就位",
|
|
1320
1539
|
"start.go": "开始学习",
|
|
1321
1540
|
"start.enter": "进入学习",
|
|
1322
1541
|
"start.busy": "正在准备学习区…",
|
|
1323
1542
|
"soul.direct": "直讲",
|
|
1324
|
-
"soul.direct.hint": "
|
|
1543
|
+
"soul.direct.hint": "精讲:先讲清楚,再确认懂没懂",
|
|
1325
1544
|
"soul.guide": "引导",
|
|
1326
|
-
"soul.guide.hint": "
|
|
1545
|
+
"soul.guide.hint": "引导:让你自己往前推一步,导师递台阶",
|
|
1327
1546
|
"soul.practice": "实战",
|
|
1328
|
-
"soul.practice.hint": "
|
|
1547
|
+
"soul.practice.hint": "实战:在真实世界的乱问题里学",
|
|
1329
1548
|
"zone.understand": "🧠 理解区 — 知识结构",
|
|
1330
1549
|
"zone.record": "📝 记录区 — 我的话",
|
|
1331
1550
|
"zone.practice": "✍️ 练习区 — 答题日志",
|
|
@@ -1335,24 +1554,25 @@ window.__ModuleLoader__.load({
|
|
|
1335
1554
|
"rail.empty.placeholder": "GitHub 仓库链接,如 microsoft/AI-For-Beginners",
|
|
1336
1555
|
"rail.empty.button": "导入",
|
|
1337
1556
|
"rail.empty.demo": "导入示例课程",
|
|
1338
|
-
"rail.mastered": "{mastered}/{total}
|
|
1557
|
+
"rail.mastered": "毕业 {mastered}/{total} 课",
|
|
1339
1558
|
"rail.avg": "平均掌握度 {pct}%",
|
|
1340
1559
|
"rail.avg.none": "尚无掌握度数据",
|
|
1341
1560
|
"rail.search": "搜索课时…(多关键词空格分隔)",
|
|
1342
1561
|
"rail.locate.title": "在课程树中定位当前焦点课时(自动展开所在章节)",
|
|
1343
|
-
"rail.locate": "
|
|
1344
|
-
"rail.due": "
|
|
1562
|
+
"rail.locate": "回到当前课时",
|
|
1563
|
+
"rail.due": "待复习 {count}",
|
|
1345
1564
|
"rail.due.over": "超{days}天",
|
|
1346
1565
|
"rail.due.start": "开始复习",
|
|
1347
1566
|
"rail.due.tag": "这课时的复习今天到期(SM-2)",
|
|
1348
1567
|
"rail.delete": "删除本课程",
|
|
1349
1568
|
"rail.delete.confirm": "确认删除?",
|
|
1350
|
-
"rail.delete.title.confirm": "再点一次确认删除(含全部进度与笔记)",
|
|
1569
|
+
"rail.delete.title.confirm": "再点一次确认删除(含全部进度与笔记)。反悔前可先在设置页备份状态文件",
|
|
1351
1570
|
"rail.section.collapse": "折叠本章节",
|
|
1352
1571
|
"rail.section.expand": "展开本章节({count} 课时)",
|
|
1353
1572
|
"rail.section.count": "{count} 课",
|
|
1354
1573
|
"rail.lesson.opening": "正在打开课时会话…",
|
|
1355
|
-
"rail.
|
|
1574
|
+
"rail.lesson.openHint": "(点击将打开本课专属会话)",
|
|
1575
|
+
"rail.import.toggle": "导入课程",
|
|
1356
1576
|
"rail.import.close": "收起导入",
|
|
1357
1577
|
"tag.weak": "{count} 个薄弱知识点,测验会优先考察",
|
|
1358
1578
|
"tag.friction": "{count} 次卡点记录(你说\"不懂\"时导师记下的)",
|
|
@@ -1366,12 +1586,11 @@ window.__ModuleLoader__.load({
|
|
|
1366
1586
|
"chip.unattributed": "未归因",
|
|
1367
1587
|
"chip.correct": "✓ 答对 · {concept}",
|
|
1368
1588
|
"chip.wrong": "✗ 答错 · {concept}",
|
|
1369
|
-
"chip.import": "📦 导入课程",
|
|
1370
|
-
"chip.concepts": "🧠 提炼知识点",
|
|
1371
1589
|
"row.thinking": "导师思考中…",
|
|
1372
1590
|
"row.thinking.title": "导师正在推理,回复马上就来",
|
|
1373
1591
|
"quiz.title": "点击选项作答,也可以直接打字回答",
|
|
1374
1592
|
"quiz.answer": "选 {letter}:{text}",
|
|
1593
|
+
"tutor.dormant": "学习模式已关闭 — 点右上「▶ 开始学习」开启导师,按钮恢复可用。",
|
|
1375
1594
|
"tutor.empty": "对话会出现在这里",
|
|
1376
1595
|
"tutor.empty.hint": "在下方输入框和导师说话",
|
|
1377
1596
|
"proposal.text": "导师提议你已掌握「{lesson}」:{rationale}",
|
|
@@ -1380,10 +1599,15 @@ window.__ModuleLoader__.load({
|
|
|
1380
1599
|
"bb.empty": "黑板还空着",
|
|
1381
1600
|
"bb.empty.hint": "在左侧课程树选择一课",
|
|
1382
1601
|
"bb.mastery": "掌握度 {pct}%",
|
|
1602
|
+
"cmap.legend": "琥珀色 = 薄弱知识点(掌握度 <70%)",
|
|
1603
|
+
"cmap.node.pct": "掌握度 {pct}%",
|
|
1604
|
+
"bb.strategy": "本课学法",
|
|
1383
1605
|
"bb.fallback.cmap.empty": "概念图需要先由导师定义本课概念",
|
|
1384
1606
|
"bb.fallback.cmap": "概念图渲染不可用(布局引擎加载失败) — 已回退讲解视图",
|
|
1385
1607
|
"bb.notes": "笔记",
|
|
1386
1608
|
"bb.notes.empty": "这一课还没有笔记",
|
|
1609
|
+
"tv.exam.stars": "本次 {n}★(历史最佳 {best}★)",
|
|
1610
|
+
"tv.exam.fail": "这轮没拿到星——错题就是下一课的地图,从每题的解析看起。",
|
|
1387
1611
|
"active.off.title": "注册 study 工具并载入导师人格,之后普通对话和现在一样",
|
|
1388
1612
|
"active.on.title": "注销 study 工具与导师人格;学习进度保留,可随时重新开启",
|
|
1389
1613
|
"active.off": "▶ 开始学习",
|
|
@@ -1394,8 +1618,8 @@ window.__ModuleLoader__.load({
|
|
|
1394
1618
|
"settings.mode.hint": "导师的讲解风格;也可在学习页随时切换",
|
|
1395
1619
|
"settings.studyMode": "学习模式",
|
|
1396
1620
|
"settings.studyMode.hint": "开启后注册 study_* 工具并载入导师人格;进度与笔记始终保留",
|
|
1397
|
-
"settings.on": "
|
|
1398
|
-
"settings.off": "
|
|
1621
|
+
"settings.on": "状态:已开启",
|
|
1622
|
+
"settings.off": "状态:已关闭",
|
|
1399
1623
|
"settings.turnOn": "开启",
|
|
1400
1624
|
"settings.turnOff": "关闭",
|
|
1401
1625
|
"settings.stats": "学习统计",
|
|
@@ -1408,8 +1632,8 @@ window.__ModuleLoader__.load({
|
|
|
1408
1632
|
"settings.stateFile": "状态文件",
|
|
1409
1633
|
"settings.stateFile.hint": "学习进度存放于本机 JSON,换机可迁移",
|
|
1410
1634
|
"dock.title": "学习状态:待复习 {due} · 连续 {streak} 天 · Lv{level} — 点上方「学习」页签进入",
|
|
1411
|
-
"dock.due": "
|
|
1412
|
-
"dock.streak": "
|
|
1635
|
+
"dock.due": "{count}",
|
|
1636
|
+
"dock.streak": "{days}天",
|
|
1413
1637
|
"dock.lv": "Lv{level}",
|
|
1414
1638
|
"prompt.import": "导入课程:用 study_import_github 抓取 {url}",
|
|
1415
1639
|
"prompt.lesson": "学习「{title}」:用 study_lesson 打开这一课开始学习。",
|
|
@@ -1428,19 +1652,19 @@ window.__ModuleLoader__.load({
|
|
|
1428
1652
|
"pane.tutor": "Tutor",
|
|
1429
1653
|
"pane.bb": "Board",
|
|
1430
1654
|
"viewtab.teach": "Teach",
|
|
1431
|
-
"viewtab.cmap": "
|
|
1432
|
-
"viewtab.cmap.title": "The lesson's
|
|
1655
|
+
"viewtab.cmap": "Concepts",
|
|
1656
|
+
"viewtab.cmap.title": "The lesson's concepts relate",
|
|
1433
1657
|
"loading": "Loading…",
|
|
1434
1658
|
"start.title": "One click to study: workspace + session + tutor, all set up",
|
|
1435
1659
|
"start.go": "Start learning",
|
|
1436
1660
|
"start.enter": "Enter study",
|
|
1437
1661
|
"start.busy": "Preparing the study area…",
|
|
1438
1662
|
"soul.direct": "Direct",
|
|
1439
|
-
"soul.direct.hint": "
|
|
1663
|
+
"soul.direct.hint": "Explain first, then check understanding",
|
|
1440
1664
|
"soul.guide": "Guide",
|
|
1441
|
-
"soul.guide.hint": "
|
|
1665
|
+
"soul.guide.hint": "You push one step forward; the tutor hands you the stairs",
|
|
1442
1666
|
"soul.practice": "Practice",
|
|
1443
|
-
"soul.practice.hint": "
|
|
1667
|
+
"soul.practice.hint": "Learn inside real, messy problems",
|
|
1444
1668
|
"zone.understand": "🧠 Understand — knowledge structure",
|
|
1445
1669
|
"zone.record": "📝 Record — in my own words",
|
|
1446
1670
|
"zone.practice": "✍️ Practice — answer log",
|
|
@@ -1450,24 +1674,25 @@ window.__ModuleLoader__.load({
|
|
|
1450
1674
|
"rail.empty.placeholder": "GitHub repo link, e.g. microsoft/AI-For-Beginners",
|
|
1451
1675
|
"rail.empty.button": "Import",
|
|
1452
1676
|
"rail.empty.demo": "Import the demo course",
|
|
1453
|
-
"rail.mastered": "{mastered}/{total}
|
|
1677
|
+
"rail.mastered": "{mastered}/{total} lessons graduated",
|
|
1454
1678
|
"rail.avg": "avg mastery {pct}%",
|
|
1455
1679
|
"rail.avg.none": "no mastery data yet",
|
|
1456
1680
|
"rail.search": "Search lessons… (space-separated keywords)",
|
|
1457
1681
|
"rail.locate.title": "Locate the current focus lesson in the tree (auto-expands its section)",
|
|
1458
|
-
"rail.locate": "
|
|
1459
|
-
"rail.due": "
|
|
1682
|
+
"rail.locate": "Back to current lesson",
|
|
1683
|
+
"rail.due": "{count} due",
|
|
1460
1684
|
"rail.due.over": "{days}d overdue",
|
|
1461
1685
|
"rail.due.start": "Start reviewing",
|
|
1462
1686
|
"rail.due.tag": "this lesson's review is due today (SM-2)",
|
|
1463
1687
|
"rail.delete": "Delete this course",
|
|
1464
1688
|
"rail.delete.confirm": "Delete?",
|
|
1465
|
-
"rail.delete.title.confirm": "Click again to confirm (all progress and notes included)",
|
|
1689
|
+
"rail.delete.title.confirm": "Click again to confirm (all progress and notes included). Back up via the state file in Settings first",
|
|
1466
1690
|
"rail.section.collapse": "Collapse this section",
|
|
1467
1691
|
"rail.section.expand": "Expand this section ({count} lessons)",
|
|
1468
1692
|
"rail.section.count": "{count} lessons",
|
|
1469
1693
|
"rail.lesson.opening": "Opening the lesson session…",
|
|
1470
|
-
"rail.
|
|
1694
|
+
"rail.lesson.openHint": " (opens this lesson's own session)",
|
|
1695
|
+
"rail.import.toggle": "Import a course",
|
|
1471
1696
|
"rail.import.close": "Collapse import",
|
|
1472
1697
|
"tag.weak": "{count} weak concepts — quizzes target them first",
|
|
1473
1698
|
"tag.friction": "{count} friction marks (logged when you said you were stuck)",
|
|
@@ -1481,12 +1706,11 @@ window.__ModuleLoader__.load({
|
|
|
1481
1706
|
"chip.unattributed": "unattributed",
|
|
1482
1707
|
"chip.correct": "✓ correct · {concept}",
|
|
1483
1708
|
"chip.wrong": "✗ wrong · {concept}",
|
|
1484
|
-
"chip.import": "📦 course import",
|
|
1485
|
-
"chip.concepts": "🧠 concepts defined",
|
|
1486
1709
|
"row.thinking": "tutor is thinking…",
|
|
1487
1710
|
"row.thinking.title": "The tutor is reasoning; the reply is coming",
|
|
1488
1711
|
"quiz.title": "Click an option to answer, or just type",
|
|
1489
1712
|
"quiz.answer": "Choose {letter}: {text}",
|
|
1713
|
+
"tutor.dormant": "Study mode is off — click \"▶ Start learning\" above to wake the tutor and re-enable these controls.",
|
|
1490
1714
|
"tutor.empty": "The conversation will appear here",
|
|
1491
1715
|
"tutor.empty.hint": "Talk to the tutor in the input below",
|
|
1492
1716
|
"proposal.text": "The tutor proposes you have mastered \"{lesson}\": {rationale}",
|
|
@@ -1495,10 +1719,15 @@ window.__ModuleLoader__.load({
|
|
|
1495
1719
|
"bb.empty": "The blackboard is empty",
|
|
1496
1720
|
"bb.empty.hint": "Pick a lesson in the course tree on the left",
|
|
1497
1721
|
"bb.mastery": "mastery {pct}%",
|
|
1722
|
+
"cmap.legend": "Amber = weak concept (mastery < 70%)",
|
|
1723
|
+
"cmap.node.pct": "mastery {pct}%",
|
|
1724
|
+
"bb.strategy": "Approach",
|
|
1498
1725
|
"bb.fallback.cmap.empty": "The concept map needs the tutor to define this lesson's concepts first",
|
|
1499
1726
|
"bb.fallback.cmap": "Concept map unavailable (layout engine failed to load) — fell back to the teach view",
|
|
1500
1727
|
"bb.notes": "Notes",
|
|
1501
1728
|
"bb.notes.empty": "No notes for this lesson yet",
|
|
1729
|
+
"tv.exam.stars": "This attempt {n}★ (best {best}★)",
|
|
1730
|
+
"tv.exam.fail": "No stars this round — the wrong answers are the map for the next one; start with their explanations.",
|
|
1502
1731
|
"active.off.title": "Register the study tools and load the tutor persona; plain chats stay as they are",
|
|
1503
1732
|
"active.on.title": "Retire the study tools and tutor persona; progress is kept and you can re-enable anytime",
|
|
1504
1733
|
"active.off": "▶ Start learning",
|
|
@@ -1509,8 +1738,8 @@ window.__ModuleLoader__.load({
|
|
|
1509
1738
|
"settings.mode.hint": "The tutor's explanation style; switchable anytime in the study tab too",
|
|
1510
1739
|
"settings.studyMode": "Study mode",
|
|
1511
1740
|
"settings.studyMode.hint": "On registers the study_* tools and the tutor persona; progress and notes are always kept",
|
|
1512
|
-
"settings.on": "
|
|
1513
|
-
"settings.off": "
|
|
1741
|
+
"settings.on": "Status: on",
|
|
1742
|
+
"settings.off": "Status: off",
|
|
1514
1743
|
"settings.turnOn": "Turn on",
|
|
1515
1744
|
"settings.turnOff": "Turn off",
|
|
1516
1745
|
"settings.stats": "Study stats",
|
|
@@ -1523,8 +1752,8 @@ window.__ModuleLoader__.load({
|
|
|
1523
1752
|
"settings.stateFile": "State file",
|
|
1524
1753
|
"settings.stateFile.hint": "Your progress lives in a local JSON file — copy it to migrate machines",
|
|
1525
1754
|
"dock.title": "Study status: {due} due · {streak}d streak · Lv{level} — open the Study tab above to continue",
|
|
1526
|
-
"dock.due": "
|
|
1527
|
-
"dock.streak": "
|
|
1755
|
+
"dock.due": "{count}",
|
|
1756
|
+
"dock.streak": "{days}d",
|
|
1528
1757
|
"dock.lv": "Lv{level}",
|
|
1529
1758
|
"prompt.import": "Import a course: fetch {url} with study_import_github",
|
|
1530
1759
|
"prompt.lesson": "Study \"{title}\": open this lesson with study_lesson and start teaching.",
|
|
@@ -1596,15 +1825,96 @@ window.__ModuleLoader__.load({
|
|
|
1596
1825
|
return activeT(key, params);
|
|
1597
1826
|
}
|
|
1598
1827
|
//#endregion
|
|
1828
|
+
//#region src/client/diagrams.ts
|
|
1829
|
+
/**
|
|
1830
|
+
* Blackboard diagram views (SPEC Phase 2): the lesson concept map (ELK layered
|
|
1831
|
+
* layout via the vendored cmap-elk-layout + CDN elkjs, draw.io-flavored SVG
|
|
1832
|
+
* skin rendered plugin-side). Degrades honestly: any CDN/ELK failure leaves a
|
|
1833
|
+
* one-line notice instead of a broken pane.
|
|
1834
|
+
*
|
|
1835
|
+
* The lesson mind map view was RETIRED with upstream v0.26.0 (2026-09-01
|
|
1836
|
+
* port): body text that has headings already shows its structure, text without
|
|
1837
|
+
* headings yields a first-line-truncated node graph nobody can read — both
|
|
1838
|
+
* lose — and this concept map already covers the semantic-structure need at
|
|
1839
|
+
* higher quality. Its three CDN imports went with it.
|
|
1840
|
+
*/
|
|
1841
|
+
/** Lesson concept map: the lesson node + its concepts (weak ones accented),
|
|
1842
|
+
* laid out by the vendored ELK pipeline, skinned draw.io-style (plugin-original,
|
|
1843
|
+
* upstream's skin is coupled to its token pipeline). Mastery shades the nodes. */
|
|
1844
|
+
async function renderLessonConceptMap(container, lessonTitle, concepts) {
|
|
1845
|
+
const layout = await layoutConceptMap([{
|
|
1846
|
+
id: "lesson",
|
|
1847
|
+
label: lessonTitle
|
|
1848
|
+
}, ...concepts.map((c) => ({
|
|
1849
|
+
id: `c-${c.title}`,
|
|
1850
|
+
label: c.title
|
|
1851
|
+
}))], concepts.map((c) => ({
|
|
1852
|
+
from: "lesson",
|
|
1853
|
+
to: `c-${c.title}`
|
|
1854
|
+
})));
|
|
1855
|
+
const masteryBy = new Map(concepts.map((c) => [`c-${c.title}`, c.masteryPct]));
|
|
1856
|
+
const ns = "http://www.w3.org/2000/svg";
|
|
1857
|
+
const svg = document.createElementNS(ns, "svg");
|
|
1858
|
+
svg.setAttribute("width", String(Math.max(320, layout.width)));
|
|
1859
|
+
svg.setAttribute("height", String(Math.max(200, layout.height)));
|
|
1860
|
+
svg.setAttribute("viewBox", `0 0 ${layout.width} ${layout.height}`);
|
|
1861
|
+
svg.setAttribute("style", "max-width:100%;background:var(--dsw-alias-bg-layer-1, #fff);border:1px solid var(--dsw-alias-border-l2, #d0d7de);border-radius:8px");
|
|
1862
|
+
for (const e of layout.edges) {
|
|
1863
|
+
const path = document.createElementNS(ns, "path");
|
|
1864
|
+
path.setAttribute("d", e.pts.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" "));
|
|
1865
|
+
path.setAttribute("fill", "none");
|
|
1866
|
+
path.setAttribute("stroke", "var(--dsw-alias-border-l2, #8b949e)");
|
|
1867
|
+
path.setAttribute("stroke-width", "1.5");
|
|
1868
|
+
svg.append(path);
|
|
1869
|
+
}
|
|
1870
|
+
for (const n of layout.nodes) {
|
|
1871
|
+
const g = document.createElementNS(ns, "g");
|
|
1872
|
+
g.setAttribute("transform", `translate(${n.x},${n.y})`);
|
|
1873
|
+
const rect = document.createElementNS(ns, "rect");
|
|
1874
|
+
const isLesson = n.id === "lesson";
|
|
1875
|
+
const mastery = masteryBy.get(n.id);
|
|
1876
|
+
rect.setAttribute("width", String(n.w));
|
|
1877
|
+
rect.setAttribute("height", String(n.h));
|
|
1878
|
+
rect.setAttribute("rx", "6");
|
|
1879
|
+
rect.setAttribute("fill", mastery !== void 0 && mastery < 70 ? "var(--dsw-alias-state-warn-tertiary, #fef5e7)" : "var(--dsw-alias-bg-layer-2, #f6f8fa)");
|
|
1880
|
+
rect.setAttribute("stroke", isLesson ? "var(--dsw-alias-state-business-primary, #0969da)" : "var(--dsw-alias-border-l2, #8b949e)");
|
|
1881
|
+
rect.setAttribute("stroke-width", isLesson ? "2" : "1");
|
|
1882
|
+
g.append(rect);
|
|
1883
|
+
n.lines.forEach((line, li) => {
|
|
1884
|
+
const label = document.createElementNS(ns, "text");
|
|
1885
|
+
label.setAttribute("x", String(n.w / 2));
|
|
1886
|
+
label.setAttribute("y", String(n.h / 2 - (n.lines.length - 1) * 7 + li * 14 + 4));
|
|
1887
|
+
label.setAttribute("text-anchor", "middle");
|
|
1888
|
+
label.setAttribute("font-size", isLesson ? "13" : "12");
|
|
1889
|
+
label.setAttribute("font-weight", isLesson ? "600" : "400");
|
|
1890
|
+
label.setAttribute("fill", "var(--dsw-alias-label-primary, #1f2328)");
|
|
1891
|
+
label.textContent = line;
|
|
1892
|
+
g.append(label);
|
|
1893
|
+
});
|
|
1894
|
+
const title = document.createElementNS(ns, "title");
|
|
1895
|
+
const label = n.lines.join(" ");
|
|
1896
|
+
title.textContent = isLesson || mastery === void 0 ? label : `${label} · ${tr("cmap.node.pct", { pct: mastery })}`;
|
|
1897
|
+
g.append(title);
|
|
1898
|
+
svg.append(g);
|
|
1899
|
+
}
|
|
1900
|
+
container.append(svg);
|
|
1901
|
+
const legend = document.createElement("span");
|
|
1902
|
+
legend.className = "lks-cmap-legend";
|
|
1903
|
+
const swatch = document.createElement("i");
|
|
1904
|
+
legend.append(swatch, document.createTextNode(tr("cmap.legend")));
|
|
1905
|
+
container.append(legend);
|
|
1906
|
+
}
|
|
1907
|
+
//#endregion
|
|
1599
1908
|
//#region src/client/views.tsx
|
|
1600
1909
|
/**
|
|
1601
1910
|
* The study tab: one `conversation.view` entry rendering the whole plugin —
|
|
1602
1911
|
* a simplified LookatStudy inside dsh, three columns in the tab. Left: the
|
|
1603
1912
|
* course map (selector, tree, due box, one-click demo import). Middle: the
|
|
1604
|
-
* tutor conversation — a read-only
|
|
1913
|
+
* tutor conversation — a read-only transcript folded from the session
|
|
1605
1914
|
* snapshot (user text, assistant text rendered through the plugin's own
|
|
1606
|
-
* markdown pipeline
|
|
1607
|
-
*
|
|
1915
|
+
* markdown pipeline; tool calls stay in the host conversation's toolview
|
|
1916
|
+
* cards — a mirrored second record diverged) plus the soul pills, starters,
|
|
1917
|
+
* pending proposal banner, and a reverse-channel input. Right: the blackboard —
|
|
1608
1918
|
* focus-lesson 讲解 and the Cornell 笔记 zones. Nothing outside this tab
|
|
1609
1919
|
* touches dsh chrome.
|
|
1610
1920
|
* @module dsh-plugin-lookatstudy/client/views
|
|
@@ -1633,13 +1943,13 @@ window.__ModuleLoader__.load({
|
|
|
1633
1943
|
["record", "zone.record"],
|
|
1634
1944
|
["practice", "zone.practice"]
|
|
1635
1945
|
];
|
|
1636
|
-
/** Status
|
|
1637
|
-
function
|
|
1638
|
-
if (kind === "exam") return
|
|
1639
|
-
if (status === "mastered") return
|
|
1640
|
-
if (status === "in_progress") return
|
|
1641
|
-
if (status === "available") return
|
|
1642
|
-
return
|
|
1946
|
+
/** Status icon for one lesson row (LookatStudy's map icon semantics, SVG glyphs). */
|
|
1947
|
+
function statusIcon(kind, status) {
|
|
1948
|
+
if (kind === "exam") return (0, react.createElement)(IconGoalOutline16, { size: 14 });
|
|
1949
|
+
if (status === "mastered") return (0, react.createElement)(IconCrownFill16, { size: 14 });
|
|
1950
|
+
if (status === "in_progress") return (0, react.createElement)(IconBookFill16, { size: 14 });
|
|
1951
|
+
if (status === "available") return (0, react.createElement)(IconStarFill16, { size: 14 });
|
|
1952
|
+
return (0, react.createElement)(IconLockFill16, { size: 14 });
|
|
1643
1953
|
}
|
|
1644
1954
|
/** LookatStudy's exam gate: an exam node opens only when every sibling study lesson reached mastery ≥50%. */
|
|
1645
1955
|
function examOpen(lessons) {
|
|
@@ -1734,40 +2044,6 @@ window.__ModuleLoader__.load({
|
|
|
1734
2044
|
flush();
|
|
1735
2045
|
return runs.at(-1) ?? [];
|
|
1736
2046
|
}
|
|
1737
|
-
/**
|
|
1738
|
-
* Learning-aware label for one settled tool call; null keeps the generic
|
|
1739
|
-
* 🔧-name chip. Pure (translator defaults to the active one, per call).
|
|
1740
|
-
* @param name - tool call name.
|
|
1741
|
-
* @param argsRaw - the call's raw JSON args.
|
|
1742
|
-
* @param t - translator.
|
|
1743
|
-
*/
|
|
1744
|
-
function toolChipLabel(name, argsRaw, t = tr) {
|
|
1745
|
-
if (name === "study_record_answer") {
|
|
1746
|
-
let concept = t("chip.unattributed");
|
|
1747
|
-
let correct = false;
|
|
1748
|
-
try {
|
|
1749
|
-
const args = JSON.parse(argsRaw);
|
|
1750
|
-
if (typeof args.concept === "string" && args.concept !== "") concept = args.concept;
|
|
1751
|
-
correct = args.correct === true;
|
|
1752
|
-
} catch {}
|
|
1753
|
-
return correct ? {
|
|
1754
|
-
label: t("chip.correct", { concept }),
|
|
1755
|
-
tone: "ok"
|
|
1756
|
-
} : {
|
|
1757
|
-
label: t("chip.wrong", { concept }),
|
|
1758
|
-
tone: "bad"
|
|
1759
|
-
};
|
|
1760
|
-
}
|
|
1761
|
-
if (name === "study_import_github" || name === "study_import_markdown" || name === "study_import_folder") return {
|
|
1762
|
-
label: t("chip.import"),
|
|
1763
|
-
tone: "ok"
|
|
1764
|
-
};
|
|
1765
|
-
if (name === "study_define_concepts") return {
|
|
1766
|
-
label: t("chip.concepts"),
|
|
1767
|
-
tone: "ok"
|
|
1768
|
-
};
|
|
1769
|
-
return null;
|
|
1770
|
-
}
|
|
1771
2047
|
function textOf(blocks) {
|
|
1772
2048
|
return blocks.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n\n").trim();
|
|
1773
2049
|
}
|
|
@@ -1775,8 +2051,10 @@ window.__ModuleLoader__.load({
|
|
|
1775
2051
|
return blocks.filter((b) => b.kind === "text").map((b) => b.text).join("\n\n").trim();
|
|
1776
2052
|
}
|
|
1777
2053
|
/**
|
|
1778
|
-
* Fold conversation nodes (plus the streaming partial) into render rows
|
|
1779
|
-
*
|
|
2054
|
+
* Fold conversation nodes (plus the streaming partial) into render rows.
|
|
2055
|
+
* Tool results are skipped: the host conversation renders the rich
|
|
2056
|
+
* `tool.call.toolview` cards, and the tutor column stays a pure persona
|
|
2057
|
+
* surface (a transcript mirror there grew a second, diverging record).
|
|
1780
2058
|
* @param nodes - finalized conversation nodes from the snapshot.
|
|
1781
2059
|
* @param partial - the in-flight assistant partial, or null.
|
|
1782
2060
|
* @param t - translator (defaults to the active one, per call).
|
|
@@ -1804,14 +2082,6 @@ window.__ModuleLoader__.load({
|
|
|
1804
2082
|
});
|
|
1805
2083
|
break;
|
|
1806
2084
|
}
|
|
1807
|
-
case "tool-result":
|
|
1808
|
-
rows.push({
|
|
1809
|
-
key: `t${node.seq}`,
|
|
1810
|
-
role: "tool",
|
|
1811
|
-
text: node.call?.name ?? node.callId,
|
|
1812
|
-
call: node.call
|
|
1813
|
-
});
|
|
1814
|
-
break;
|
|
1815
2085
|
case "turn-error": rows.push({
|
|
1816
2086
|
key: `e${node.seq}`,
|
|
1817
2087
|
role: "error",
|
|
@@ -1932,7 +2202,7 @@ window.__ModuleLoader__.load({
|
|
|
1932
2202
|
onClick: () => {
|
|
1933
2203
|
activate(!data.active);
|
|
1934
2204
|
}
|
|
1935
|
-
}, data.active ? tr("active.on") : tr("active.off"))), (0, react.createElement)("div", { className: "lks-body" }, (0, react.createElement)("div", { className: "lks-switch" }, ...[
|
|
2205
|
+
}, 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" }, ...[
|
|
1936
2206
|
{
|
|
1937
2207
|
id: "rail",
|
|
1938
2208
|
labelKey: "pane.rail"
|
|
@@ -1948,6 +2218,7 @@ window.__ModuleLoader__.load({
|
|
|
1948
2218
|
].map((p) => (0, react.createElement)("button", {
|
|
1949
2219
|
key: p.id,
|
|
1950
2220
|
className: `lks-switch-btn${pane === p.id ? " on" : ""}`,
|
|
2221
|
+
"aria-pressed": String(pane === p.id),
|
|
1951
2222
|
onClick: () => {
|
|
1952
2223
|
setPane(p.id);
|
|
1953
2224
|
try {
|
|
@@ -2015,6 +2286,21 @@ window.__ModuleLoader__.load({
|
|
|
2015
2286
|
const [selectedCourse, setSelectedCourse] = (0, react.useState)("");
|
|
2016
2287
|
const [query, setQuery] = (0, react.useState)("");
|
|
2017
2288
|
const [confirmDelete, setConfirmDelete] = (0, react.useState)(false);
|
|
2289
|
+
(0, react.useEffect)(() => {
|
|
2290
|
+
if (!confirmDelete) return;
|
|
2291
|
+
const disarm = () => {
|
|
2292
|
+
setConfirmDelete(false);
|
|
2293
|
+
};
|
|
2294
|
+
const onKey = (e) => {
|
|
2295
|
+
if (e.key === "Escape") disarm();
|
|
2296
|
+
};
|
|
2297
|
+
window.addEventListener("click", disarm);
|
|
2298
|
+
window.addEventListener("keydown", onKey);
|
|
2299
|
+
return () => {
|
|
2300
|
+
window.removeEventListener("click", disarm);
|
|
2301
|
+
window.removeEventListener("keydown", onKey);
|
|
2302
|
+
};
|
|
2303
|
+
}, [confirmDelete]);
|
|
2018
2304
|
const [showImport, setShowImport] = (0, react.useState)(false);
|
|
2019
2305
|
/** Per-section open overrides (courseId/sectionTitle → open?), on top of the frontier default. */
|
|
2020
2306
|
const [secOpen, setSecOpen] = (0, react.useState)({});
|
|
@@ -2041,10 +2327,14 @@ window.__ModuleLoader__.load({
|
|
|
2041
2327
|
}, ...data.courses.map((c) => (0, react.createElement)("option", {
|
|
2042
2328
|
key: c.courseId,
|
|
2043
2329
|
value: c.courseId
|
|
2044
|
-
}, c.title))) : (0, react.createElement)("div", {
|
|
2330
|
+
}, c.title))) : (0, react.createElement)("div", {
|
|
2331
|
+
className: "lks-rail-title",
|
|
2332
|
+
title: course.title
|
|
2333
|
+
}, course.title), (0, react.createElement)("button", {
|
|
2045
2334
|
className: `lks-btn ${confirmDelete ? "primary" : "ghost"}`,
|
|
2046
2335
|
title: confirmDelete ? tr("rail.delete.title.confirm") : tr("rail.delete"),
|
|
2047
|
-
onClick: () => {
|
|
2336
|
+
onClick: (e) => {
|
|
2337
|
+
e.stopPropagation();
|
|
2048
2338
|
if (!confirmDelete) {
|
|
2049
2339
|
setConfirmDelete(true);
|
|
2050
2340
|
return;
|
|
@@ -2054,13 +2344,13 @@ window.__ModuleLoader__.load({
|
|
|
2054
2344
|
setSelectedCourse("");
|
|
2055
2345
|
}, reportError);
|
|
2056
2346
|
}
|
|
2057
|
-
}, confirmDelete ? tr("rail.delete.confirm") :
|
|
2347
|
+
}, confirmDelete ? tr("rail.delete.confirm") : (0, react.createElement)(IconTrashOutline16, { size: 14 }))), (0, react.createElement)("div", { className: "lks-rail-sub" }, tr("rail.mastered", {
|
|
2058
2348
|
mastered: course.mastered,
|
|
2059
2349
|
total: course.total
|
|
2060
2350
|
})), (0, react.createElement)("div", {
|
|
2061
2351
|
className: `lks-masterybar${course.avgMasteryPct === 100 ? " gold" : ""}`,
|
|
2062
2352
|
title: course.avgMasteryPct === null ? tr("rail.avg.none") : tr("rail.avg", { pct: course.avgMasteryPct })
|
|
2063
|
-
}, (0, react.createElement)("i", { style: {
|
|
2353
|
+
}, (0, react.createElement)("i", { style: { transform: `scaleX(${(course.avgMasteryPct ?? 0) / 100})` } })), (0, react.createElement)("input", {
|
|
2064
2354
|
className: "lks-search",
|
|
2065
2355
|
type: "search",
|
|
2066
2356
|
placeholder: tr("rail.search"),
|
|
@@ -2107,7 +2397,7 @@ window.__ModuleLoader__.load({
|
|
|
2107
2397
|
});
|
|
2108
2398
|
}, 60);
|
|
2109
2399
|
}
|
|
2110
|
-
}, 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", {
|
|
2400
|
+
}, (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", {
|
|
2111
2401
|
key: d.lessonId,
|
|
2112
2402
|
className: "lks-due-item"
|
|
2113
2403
|
}, (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", {
|
|
@@ -2141,7 +2431,7 @@ window.__ModuleLoader__.load({
|
|
|
2141
2431
|
type: "button",
|
|
2142
2432
|
className: `lks-node${lesson.focus ? " focus" : ""}`,
|
|
2143
2433
|
"aria-disabled": locked || void 0,
|
|
2144
|
-
title: jumping === lesson.id ? tr("rail.lesson.opening") : statusTitle(lesson.kind, locked ? "locked" : lesson.status)
|
|
2434
|
+
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")}`,
|
|
2145
2435
|
onClick: () => {
|
|
2146
2436
|
if (locked) return;
|
|
2147
2437
|
if (lesson.kind === "exam") {
|
|
@@ -2151,19 +2441,22 @@ window.__ModuleLoader__.load({
|
|
|
2151
2441
|
}
|
|
2152
2442
|
openLessonThread(lesson);
|
|
2153
2443
|
}
|
|
2154
|
-
}, (0, react.createElement)("span", { className: "lks-g" }, jumping === lesson.id ?
|
|
2444
|
+
}, (0, react.createElement)("span", { className: "lks-g" }, jumping === lesson.id ? (0, react.createElement)(IconLoadingOutline16, {
|
|
2445
|
+
size: 14,
|
|
2446
|
+
className: "lks-spin"
|
|
2447
|
+
}) : statusIcon(lesson.kind, locked ? "locked" : lesson.status)), (0, react.createElement)("span", { className: "lks-t" }, lesson.title), lesson.due ? (0, react.createElement)("span", {
|
|
2155
2448
|
className: "lks-tag due",
|
|
2156
2449
|
title: tr("rail.due.tag")
|
|
2157
|
-
},
|
|
2450
|
+
}, (0, react.createElement)(IconRefreshOutline16, { size: 11 })) : null, lesson.weakConcepts > 0 ? (0, react.createElement)("span", {
|
|
2158
2451
|
className: "lks-tag weak",
|
|
2159
2452
|
title: tr("tag.weak", { count: lesson.weakConcepts })
|
|
2160
|
-
},
|
|
2453
|
+
}, (0, react.createElement)(IconBoltFill16, { size: 10 }), String(lesson.weakConcepts)) : null, lesson.frictionCount > 0 ? (0, react.createElement)("span", {
|
|
2161
2454
|
className: "lks-tag fric",
|
|
2162
2455
|
title: tr("tag.friction", { count: lesson.frictionCount })
|
|
2163
|
-
},
|
|
2456
|
+
}, (0, react.createElement)(IconWarningOutline16, { size: 10 }), String(lesson.frictionCount)) : null, lesson.masteryPct !== null ? (0, react.createElement)("span", {
|
|
2164
2457
|
className: "lks-bar",
|
|
2165
2458
|
title: tr("tag.mastery", { pct: lesson.masteryPct })
|
|
2166
|
-
}, (0, react.createElement)("i", { style: {
|
|
2459
|
+
}, (0, react.createElement)("i", { style: { transform: `scaleX(${lesson.masteryPct / 100})` } })) : null, lesson.masteryPct !== null ? (0, react.createElement)("span", {
|
|
2167
2460
|
className: "lks-pct",
|
|
2168
2461
|
title: tr("tag.mastery.short")
|
|
2169
2462
|
}, `${lesson.masteryPct}%`) : null);
|
|
@@ -2174,7 +2467,7 @@ window.__ModuleLoader__.load({
|
|
|
2174
2467
|
onClick: () => {
|
|
2175
2468
|
setShowImport(!showImport);
|
|
2176
2469
|
}
|
|
2177
|
-
}, showImport ? tr("rail.import.close") : tr("rail.import.toggle")), showImport ? (0, react.createElement)(ImportRow, { send }) : null);
|
|
2470
|
+
}, showImport ? tr("rail.import.close") : (0, react.createElement)(IconPlusOutline16, { size: 13 }), tr("rail.import.toggle")), showImport ? (0, react.createElement)(ImportRow, { send }) : null);
|
|
2178
2471
|
})();
|
|
2179
2472
|
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 }));
|
|
2180
2473
|
}
|
|
@@ -2184,17 +2477,6 @@ window.__ModuleLoader__.load({
|
|
|
2184
2477
|
key: row.key,
|
|
2185
2478
|
className: "lks-msg user"
|
|
2186
2479
|
}, row.text);
|
|
2187
|
-
if (row.role === "tool") {
|
|
2188
|
-
const chip = row.call === void 0 || row.call === null ? null : toolChipLabel(row.call.name, row.call.argsRaw);
|
|
2189
|
-
if (chip !== null) return (0, react.createElement)("div", {
|
|
2190
|
-
key: row.key,
|
|
2191
|
-
className: `lks-msg tool ${chip.tone}`
|
|
2192
|
-
}, chip.label);
|
|
2193
|
-
return (0, react.createElement)("div", {
|
|
2194
|
-
key: row.key,
|
|
2195
|
-
className: "lks-msg tool"
|
|
2196
|
-
}, `🔧 ${row.text}`);
|
|
2197
|
-
}
|
|
2198
2480
|
if (row.role === "error") return (0, react.createElement)("div", {
|
|
2199
2481
|
key: row.key,
|
|
2200
2482
|
className: "lks-msg error"
|
|
@@ -2248,6 +2530,7 @@ window.__ModuleLoader__.load({
|
|
|
2248
2530
|
}, [rows.length]);
|
|
2249
2531
|
const proposal = data?.pendingProposals[0] ?? null;
|
|
2250
2532
|
const lesson = data?.lesson ?? null;
|
|
2533
|
+
const dormant = data?.active !== true;
|
|
2251
2534
|
/** Mode switches are the only fallible write left here (host route); surface failures inline. */
|
|
2252
2535
|
const fire = (action) => {
|
|
2253
2536
|
action.then(() => {
|
|
@@ -2256,10 +2539,10 @@ window.__ModuleLoader__.load({
|
|
|
2256
2539
|
setError(err instanceof Error ? err.message : String(err));
|
|
2257
2540
|
});
|
|
2258
2541
|
};
|
|
2259
|
-
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", {
|
|
2542
|
+
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", {
|
|
2260
2543
|
className: "lks-transcript",
|
|
2261
2544
|
ref: scrollRef
|
|
2262
|
-
}, 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", {
|
|
2545
|
+
}, 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", {
|
|
2263
2546
|
lesson: proposal.lessonTitle,
|
|
2264
2547
|
rationale: proposal.rationale
|
|
2265
2548
|
})), (0, react.createElement)("button", {
|
|
@@ -2281,9 +2564,11 @@ window.__ModuleLoader__.load({
|
|
|
2281
2564
|
}, ...MODES$1.map((mode) => (0, react.createElement)("button", {
|
|
2282
2565
|
key: mode.id,
|
|
2283
2566
|
className: `lks-pill${data?.mode === mode.id ? " on" : ""}`,
|
|
2284
|
-
|
|
2567
|
+
"aria-pressed": String(data?.mode === mode.id),
|
|
2568
|
+
"aria-disabled": dormant || void 0,
|
|
2569
|
+
title: dormant ? tr("tutor.dormant") : tr(mode.hintKey),
|
|
2285
2570
|
onClick: () => {
|
|
2286
|
-
fire(setMode(mode.id));
|
|
2571
|
+
if (!dormant) fire(setMode(mode.id));
|
|
2287
2572
|
}
|
|
2288
2573
|
}, tr(mode.labelKey)))), lesson !== null && lesson.starters.length > 0 ? (0, react.createElement)("div", {
|
|
2289
2574
|
className: "lks-dock",
|
|
@@ -2291,9 +2576,10 @@ window.__ModuleLoader__.load({
|
|
|
2291
2576
|
}, ...lesson.starters.map((s) => (0, react.createElement)("button", {
|
|
2292
2577
|
key: s.label,
|
|
2293
2578
|
className: "lks-starter",
|
|
2294
|
-
|
|
2579
|
+
"aria-disabled": dormant || void 0,
|
|
2580
|
+
title: dormant ? tr("tutor.dormant") : s.message,
|
|
2295
2581
|
onClick: () => {
|
|
2296
|
-
send(s.message);
|
|
2582
|
+
if (!dormant) send(s.message);
|
|
2297
2583
|
}
|
|
2298
2584
|
}, s.label))) : null, (0, react.createElement)(ActionError, { error }));
|
|
2299
2585
|
}
|
|
@@ -2324,21 +2610,23 @@ window.__ModuleLoader__.load({
|
|
|
2324
2610
|
lesson?.lessonId,
|
|
2325
2611
|
lesson?.concepts.length
|
|
2326
2612
|
]);
|
|
2327
|
-
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 })}`)
|
|
2613
|
+
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", {
|
|
2328
2614
|
key: c.title,
|
|
2329
2615
|
className: `lks-chip${c.weak ? " weak" : ""}`
|
|
2330
2616
|
}, `${c.title} ${c.masteryPct}%${c.weak ? " ⚡" : ""}`))) : null), (0, react.createElement)("div", { className: "lks-viewtabs" }, (0, react.createElement)("button", {
|
|
2331
2617
|
className: `lks-viewtab${pane === "teach" ? " on" : ""}`,
|
|
2618
|
+
"aria-pressed": String(pane === "teach"),
|
|
2332
2619
|
onClick: () => {
|
|
2333
2620
|
setPane("teach");
|
|
2334
2621
|
}
|
|
2335
2622
|
}, tr("viewtab.teach")), (0, react.createElement)("button", {
|
|
2336
2623
|
className: `lks-viewtab${pane === "cmap" ? " on" : ""}`,
|
|
2624
|
+
"aria-pressed": String(pane === "cmap"),
|
|
2337
2625
|
title: tr("viewtab.cmap.title"),
|
|
2338
2626
|
onClick: () => {
|
|
2339
2627
|
setPane("cmap");
|
|
2340
2628
|
}
|
|
2341
|
-
}, tr("viewtab.cmap"))), pane === "teach" ? (0, react.createElement)("div", {
|
|
2629
|
+
}, (0, react.createElement)(IconGlobeOutline14, { size: 13 }), tr("viewtab.cmap"))), pane === "teach" ? (0, react.createElement)("div", {
|
|
2342
2630
|
className: "lks-prose",
|
|
2343
2631
|
ref: proseRef,
|
|
2344
2632
|
dangerouslySetInnerHTML: { __html: lesson.html }
|
|
@@ -2351,10 +2639,13 @@ window.__ModuleLoader__.load({
|
|
|
2351
2639
|
}, tr("bb.notes.empty")) : ZONES.filter(([zone]) => lesson.notes.some((n) => n.zone === zone)).map(([zone, labelKey]) => (0, react.createElement)("div", {
|
|
2352
2640
|
key: zone,
|
|
2353
2641
|
className: "lks-zone"
|
|
2354
|
-
}, (0, react.createElement)("
|
|
2642
|
+
}, (0, react.createElement)("div", { className: "lks-zone-h" }, tr(labelKey)), ...lesson.notes.filter((n) => n.zone === zone).map((n) => (0, react.createElement)("div", {
|
|
2355
2643
|
key: n.id,
|
|
2356
2644
|
className: "lks-note"
|
|
2357
|
-
}, (0, react.createElement)("span", { className: "lks-note-src" }, n.source), (0, react.createElement)("div", { className: "lks-note-title" }, n.title), (0, react.createElement)("div", {
|
|
2645
|
+
}, (0, react.createElement)("span", { className: "lks-note-src" }, n.source), (0, react.createElement)("div", { className: "lks-note-title" }, n.title), (0, react.createElement)("div", {
|
|
2646
|
+
className: "lks-note-text",
|
|
2647
|
+
dangerouslySetInnerHTML: { __html: renderMarkdown(n.text) }
|
|
2648
|
+
}), n.quote !== null ? (0, react.createElement)("div", { className: "lks-note-q" }, `“${n.quote}”`) : null))))));
|
|
2358
2649
|
return (0, react.createElement)("div", { className: "lks-col lks-col-bb" }, (0, react.createElement)("div", { className: "lks-colhead" }, tr("col.bb")), body);
|
|
2359
2650
|
}
|
|
2360
2651
|
//#endregion
|
|
@@ -2517,20 +2808,23 @@ window.__ModuleLoader__.load({
|
|
|
2517
2808
|
* button's limitation), so the tooltip points at the Study tab instead.
|
|
2518
2809
|
* @module dsh-plugin-lookatstudy/client/dock
|
|
2519
2810
|
*/
|
|
2520
|
-
/** Pure projection of the pill's segments (testable without React). */
|
|
2811
|
+
/** Pure projection of the pill's segments (testable without React). `muted` marks zero-value segments — an empty state must not wear a warning color. */
|
|
2521
2812
|
function dockSegments(progress) {
|
|
2522
2813
|
return [
|
|
2523
2814
|
{
|
|
2524
2815
|
key: "due",
|
|
2525
|
-
text: tr("dock.due", { count: progress.dueCount })
|
|
2816
|
+
text: tr("dock.due", { count: progress.dueCount }),
|
|
2817
|
+
muted: progress.dueCount === 0
|
|
2526
2818
|
},
|
|
2527
2819
|
{
|
|
2528
2820
|
key: "streak",
|
|
2529
|
-
text: tr("dock.streak", { days: progress.streak })
|
|
2821
|
+
text: tr("dock.streak", { days: progress.streak }),
|
|
2822
|
+
muted: progress.streak === 0
|
|
2530
2823
|
},
|
|
2531
2824
|
{
|
|
2532
2825
|
key: "lv",
|
|
2533
|
-
text: tr("dock.lv", { level: progress.level })
|
|
2826
|
+
text: tr("dock.lv", { level: progress.level }),
|
|
2827
|
+
muted: false
|
|
2534
2828
|
}
|
|
2535
2829
|
];
|
|
2536
2830
|
}
|
|
@@ -2552,8 +2846,8 @@ window.__ModuleLoader__.load({
|
|
|
2552
2846
|
})
|
|
2553
2847
|
}, ...segments.map((s) => (0, react.createElement)("span", {
|
|
2554
2848
|
key: s.key,
|
|
2555
|
-
className: `lks-dockseg lks-dock-${s.key}`
|
|
2556
|
-
}, s.text)));
|
|
2849
|
+
className: `lks-dockseg lks-dock-${s.key}${s.muted ? " lks-muted" : ""}`
|
|
2850
|
+
}, s.key === "due" ? (0, react.createElement)(IconBoltFill16, { size: 11 }) : s.key === "streak" ? (0, react.createElement)(IconFlameFill16, { size: 11 }) : null, s.text)));
|
|
2557
2851
|
}
|
|
2558
2852
|
//#endregion
|
|
2559
2853
|
//#region src/client/toolviews.tsx
|
|
@@ -2616,9 +2910,34 @@ window.__ModuleLoader__.load({
|
|
|
2616
2910
|
function StudyDueView({ toolName, block }) {
|
|
2617
2911
|
return studyToolCard(toolName, block);
|
|
2618
2912
|
}
|
|
2619
|
-
/**
|
|
2913
|
+
/** Stars from the exam card's presentationMeta line (`N★`); null when absent. Pure. */
|
|
2914
|
+
function examStars(block) {
|
|
2915
|
+
const lines = metaLines(block);
|
|
2916
|
+
for (const line of lines) {
|
|
2917
|
+
const m = /^([0-3])★\s*\(?(?:best\s*([0-3])★)?/.exec(line.trim()) ?? /^(?:本次\s*)?([0-3])★/.exec(line.trim());
|
|
2918
|
+
if (m !== null) return {
|
|
2919
|
+
stars: Number(m[1]),
|
|
2920
|
+
best: Number(m[2] ?? m[1])
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
return null;
|
|
2924
|
+
}
|
|
2925
|
+
/** study_exam_result star card — and the failure moment finally gets its line. */
|
|
2620
2926
|
function StudyExamView({ toolName, block }) {
|
|
2621
|
-
|
|
2927
|
+
const r = examStars(block);
|
|
2928
|
+
const stars = r === null ? null : (0, react.createElement)("span", { className: "lks-tv-stars" }, [
|
|
2929
|
+
0,
|
|
2930
|
+
1,
|
|
2931
|
+
2
|
|
2932
|
+
].map((i) => (0, react.createElement)("span", {
|
|
2933
|
+
key: i,
|
|
2934
|
+
className: i < r.stars ? "full" : "dim"
|
|
2935
|
+
}, "★")), (0, react.createElement)("span", { className: "lks-tv-stars-label" }, tr("tv.exam.stars", {
|
|
2936
|
+
n: r.stars,
|
|
2937
|
+
best: r.best
|
|
2938
|
+
})));
|
|
2939
|
+
const fail = r !== null && r.stars === 0 ? (0, react.createElement)("div", { className: "lks-tv-failnote" }, tr("tv.exam.fail")) : null;
|
|
2940
|
+
return studyToolCard(toolName, block, (0, react.createElement)("span", null, stars, fail));
|
|
2622
2941
|
}
|
|
2623
2942
|
//#endregion
|
|
2624
2943
|
//#region src/client/index.ts
|