eklavya 1.45.0 → 1.46.0

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.
Files changed (36) hide show
  1. package/README.md +11 -1
  2. package/dist/artifacts.js +26 -0
  3. package/dist/artifacts.js.map +1 -1
  4. package/dist/assets/artifact-template.html +21 -0
  5. package/dist/assets/dashboard.html +248 -28
  6. package/dist/assets/tutor/SKILL.md +2 -1
  7. package/dist/assets/tutor/references/writing-mcq.md +11 -1
  8. package/dist/cli.js +44 -12
  9. package/dist/cli.js.map +1 -1
  10. package/dist/dashboard.js +159 -47
  11. package/dist/dashboard.js.map +1 -1
  12. package/dist/hooks/checkpoint-quiz.js +3 -2
  13. package/dist/hooks/checkpoint-quiz.js.map +1 -1
  14. package/dist/hooks/memory-lib.js +1 -1
  15. package/dist/hooks/memory-lib.js.map +1 -1
  16. package/dist/hooks/stop-quiz-check.js +3 -2
  17. package/dist/hooks/stop-quiz-check.js.map +1 -1
  18. package/dist/memory/recall.js +1 -1
  19. package/dist/migrations/019_attempt_corrections.sql +41 -0
  20. package/dist/plugin/.claude-plugin/plugin.json +1 -1
  21. package/dist/plugin/agents/explainer.md +9 -5
  22. package/dist/plugin/skills/tutor/SKILL.md +2 -1
  23. package/dist/plugin/skills/tutor/references/writing-mcq.md +11 -1
  24. package/dist/store.js +142 -14
  25. package/dist/store.js.map +1 -1
  26. package/dist/telemetry-send.js +9 -7
  27. package/dist/telemetry-send.js.map +1 -1
  28. package/dist/tools/get_learner_profile.js +5 -3
  29. package/dist/tools/get_learner_profile.js.map +1 -1
  30. package/dist/tools/get_session_quiz_plan.js +1 -1
  31. package/dist/tools/get_session_quiz_plan.js.map +1 -1
  32. package/dist/tools/record_attempt.js +48 -8
  33. package/dist/tools/record_attempt.js.map +1 -1
  34. package/dist/user-skill/eklavya/SKILL.md +7 -3
  35. package/dist/user-skill/eklavya-artifacts/SKILL.md +10 -3
  36. package/package.json +1 -1
package/README.md CHANGED
@@ -48,6 +48,14 @@ Code's learning loop.
48
48
  | Maintain memory | `memory_write`, `memory_correct`, `memory_delete`, `memory_collections` |
49
49
  | Inspect code | `code_outline`, `code_find_symbol` |
50
50
 
51
+ `record_attempt` returns the `attempt_id` of the row it wrote. For a
52
+ multiple-choice question, also pass `correct` (the right option's label,
53
+ verbatim), which lets the learner correct a missed answer from its explainer
54
+ page in the dashboard, where the server grades the new pick, and `option_notes`
55
+ (the note under each option, in order), which that page shows under each option. A `correct` that is not one of
56
+ the options, or notes of the wrong length, is stored as nothing and reported
57
+ back (`correct_mismatch`, `option_notes_mismatch`); the answer is still kept.
58
+
51
59
  Search first, choose relevant entries, then use `memory_get` for their full
52
60
  content. Pass `memory_get` the `receipt_id` a recall block names to link the
53
61
  read to that recall. Calls to `memory_search`, `memory_get`, `memory_timeline`
@@ -68,7 +76,9 @@ npx eklavya db-path
68
76
  ```
69
77
 
70
78
  The [CLI reference](https://eklavya-run.web.app/docs/cli/) lists all commands and
71
- flags. [Configuration](https://eklavya-run.web.app/docs/configuration/) lists
79
+ flags. The dashboard reads the database and has two guarded writes: a setting
80
+ change and a correction of a missed answer
81
+ ([what it is, underneath](https://eklavya-run.web.app/docs/dashboard/#what-it-is-underneath)). [Configuration](https://eklavya-run.web.app/docs/configuration/) lists
72
82
  defaults and scopes. The [memory guide](https://eklavya-run.web.app/docs/memory/)
73
83
  covers retrieval, processing, providers, privacy and sync.
74
84
 
package/dist/artifacts.js CHANGED
@@ -98,6 +98,7 @@ export function createArtifact(opts) {
98
98
  '{{PROJECT_NAME}}': escHtml(path.basename(project) || project),
99
99
  '{{KIND}}': kind,
100
100
  '{{CONCEPT}}': escHtml(opts.concept ?? ''),
101
+ '{{ATTEMPT}}': opts.attempt ? String(opts.attempt) : '',
101
102
  '{{CREATED}}': now.toISOString(),
102
103
  '{{EYEBROW}}': kind === 'explainer' ? 'Explainer' : 'Artifact',
103
104
  };
@@ -114,6 +115,10 @@ function meta(head, name) {
114
115
  const m = re.exec(head);
115
116
  return m ? unescHtml(m[1]) : null;
116
117
  }
118
+ /** A positive integer, or null: an `--attempt` that is anything else links nothing. */
119
+ export function attemptId(raw) {
120
+ return raw && /^[1-9]\d{0,15}$/.test(raw) ? Number(raw) : null;
121
+ }
117
122
  function unescHtml(s) {
118
123
  return s.replace(/&(amp|lt|gt|quot|#39);/g, (_, e) => ({ amp: '&', lt: '<', gt: '>', quot: '"', '#39': "'" })[e]);
119
124
  }
@@ -128,6 +133,26 @@ function readHead(file) {
128
133
  fs.closeSync(fd);
129
134
  }
130
135
  }
136
+ /** The attempt a page was written for, from its head; null when none or unreadable. */
137
+ export function readAttempt(file) {
138
+ try {
139
+ return attemptId(meta(readHead(file), 'eklavya:attempt'));
140
+ }
141
+ catch {
142
+ return null;
143
+ }
144
+ }
145
+ /** The dashboard id (`<folder>/<file>`) of a file inside the artifact root, or null. */
146
+ export function artifactIdOf(file, root = artifactsDir()) {
147
+ let id;
148
+ try {
149
+ id = path.relative(fs.realpathSync(root), fs.realpathSync(file)).split(path.sep).join('/');
150
+ }
151
+ catch {
152
+ return null;
153
+ }
154
+ return resolveArtifact(id, root) ? id : null;
155
+ }
131
156
  /**
132
157
  * Every artifact on disk, newest first. A page with no Eklavya metadata — one
133
158
  * copied in by hand — is still listed, titled from its `<title>` or filename.
@@ -173,6 +198,7 @@ export function listArtifacts(root = artifactsDir()) {
173
198
  project: meta(head, 'eklavya:project') || null,
174
199
  kind: kind === 'explainer' ? 'explainer' : 'artifact',
175
200
  concept: meta(head, 'eklavya:concept') || null,
201
+ attempt: attemptId(meta(head, 'eklavya:attempt')),
176
202
  created: meta(head, 'eklavya:created') || st.mtime.toISOString(),
177
203
  bytes: st.size,
178
204
  });
@@ -1 +1 @@
1
- {"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,7 @@
8
8
  <meta name="eklavya:project" content="{{PROJECT}}">
9
9
  <meta name="eklavya:kind" content="{{KIND}}">
10
10
  <meta name="eklavya:concept" content="{{CONCEPT}}">
11
+ <meta name="eklavya:attempt" content="{{ATTEMPT}}">
11
12
  <meta name="eklavya:created" content="{{CREATED}}">
12
13
  <!-- Branding and share metadata. The icon is the fixed mark, inlined so the page
13
14
  stays one file; og:image is only fetched by whatever the page is shared to. -->
@@ -98,6 +99,8 @@
98
99
  ol.options li.right{border-color:var(--spot);background:var(--spot-soft)}
99
100
  ol.options li.picked::after,ol.options li.right::after{display:block;margin-top:4px;
100
101
  font:400 11px var(--font-mono);letter-spacing:var(--tracking-caps);text-transform:uppercase}
102
+ /* The one-line note under each option, as the question showed it. */
103
+ ol.options li .note{display:block;color:var(--dim);font-size:13px;margin-top:2px}
101
104
  ol.options li.picked::after{content:"Your answer";color:var(--error)}
102
105
  ol.options li.right::after{content:"Right answer";color:var(--spot)}
103
106
 
@@ -138,6 +141,9 @@
138
141
  linear-gradient(90deg,var(--grid) 1px,transparent 1px);background-size:72px 72px}
139
142
  main{background:var(--bg);border-left:1px solid var(--line-2);border-right:1px solid var(--line-2);
140
143
  padding-left:40px;padding-right:40px;min-height:100vh}
144
+ /* In the dashboard's viewer the frame is as tall as the page, so a
145
+ viewport-high floor would grow it every time it reported its height. */
146
+ .framed main{min-height:0}
141
147
  }
142
148
  @media (max-width:640px){ main{padding:76px 16px 64px!important} .actions{top:12px;right:12px} }
143
149
 
@@ -171,6 +177,21 @@
171
177
  <button type="button" onclick="saveHtml()" aria-label="Download this page as an HTML file">HTML</button>
172
178
  </div>
173
179
  <script>
180
+ /* Inside the dashboard's explainer viewer: report this page's height so the
181
+ viewer shows it as one document, and take the viewer's ground. Opened on its
182
+ own, it does neither. */
183
+ (function(){
184
+ if(window.parent===window) return;
185
+ var root=document.documentElement;
186
+ root.classList.add('framed');
187
+ function post(){ parent.postMessage({type:'eklavya:height',h:root.scrollHeight},'*'); }
188
+ addEventListener('load',post);
189
+ if(window.ResizeObserver) new ResizeObserver(post).observe(root);
190
+ addEventListener('message',function(e){
191
+ var d=e.data;
192
+ if(e.source===parent&&d&&d.type==='eklavya:mode'&&(d.mode==='ink'||d.mode==='paper')) root.setAttribute('data-mode',d.mode);
193
+ });
194
+ })();
174
195
  function saveHtml(){
175
196
  var name=(location.pathname.split('/').pop()||'').replace(/\.html?$/,'')||'artifact';
176
197
  var blob=new Blob(['<!DOCTYPE html>\n'+document.documentElement.outerHTML],{type:'text/html'});
@@ -262,6 +262,53 @@
262
262
  .art__thumb { display:block; aspect-ratio:16/10; background:var(--panel); border:1px solid var(--line-2); padding:var(--space-3); }
263
263
  .art__open:hover .art__thumb { border-color:var(--spot); }
264
264
  .art__thumb img { display:block; width:100%; height:100%; object-fit:contain; }
265
+ .tag { font-family:var(--font-mono); font-size:11px; letter-spacing:var(--tracking-caps); text-transform:uppercase; }
266
+ .tag.warn { color:var(--warning); }
267
+ .tag.ok { color:var(--spot); }
268
+
269
+ /* The explainer viewer: the agent's page, framed sandboxed, then this page's
270
+ own correction bar. The frame takes the page's reported height, so the two
271
+ scroll as one document; with no report it stays 80vh and scrolls inside. */
272
+ .viewer { display:block; width:100%; height:80vh; border:0; background:var(--bg); }
273
+ .fixbar { display:flex; flex-direction:column; align-items:flex-start; gap:var(--space-2);
274
+ padding:var(--space-5); border:1px solid var(--line-2); background:var(--panel); }
275
+ .fixbar p { margin:0; }
276
+ .fixbar__note { color:var(--dim); font-size:13px; }
277
+ .fixbar__done { display:flex; align-items:center; gap:var(--space-2); color:var(--ink); }
278
+ .fixbar__done svg { width:16px; height:16px; color:var(--spot); flex:none; }
279
+ .btn { font-family:var(--font-mono); font-size:12px; letter-spacing:.06em; text-transform:uppercase;
280
+ background:var(--spot); color:var(--spot-ink); border:1px solid var(--spot); padding:10px 16px; cursor:pointer;
281
+ transition:background var(--duration-fast) var(--ease), color var(--duration-fast) var(--ease); }
282
+ .btn:hover { background:var(--mass); color:var(--spot); }
283
+ .btn:focus-visible, .fix__opt:focus-visible, .set__btn:focus-visible { outline:none; box-shadow:var(--ring); }
284
+
285
+ /* The correction modal: a native <dialog>, so focus, Escape and the top
286
+ layer come with it. Square, hairline, no shadow; the backdrop blurs. */
287
+ dialog.fix { width:min(560px, calc(100vw - 32px)); max-height:calc(100vh - 32px); overflow:auto; margin:auto;
288
+ padding:var(--space-6); border:1px solid var(--line-2); border-radius:0; background:var(--panel); color:var(--ink); }
289
+ dialog.fix::backdrop { background:color-mix(in srgb, var(--ink) 40%, transparent); backdrop-filter:blur(6px); }
290
+ dialog.fix[open] { animation:fix-in 180ms var(--ease); }
291
+ @keyframes fix-in { from { opacity:0; transform:translateY(8px); } }
292
+ .fix__eyebrow { margin:0 0 var(--space-3); font:400 11px/1 var(--font-mono); letter-spacing:var(--tracking-caps-wide);
293
+ text-transform:uppercase; color:var(--spot); }
294
+ .fix__q { margin:0 0 var(--space-5); font:800 clamp(20px,3vw,26px)/1.15 var(--font-disp); letter-spacing:var(--tracking-tight); }
295
+ .fix__opts { display:flex; flex-direction:column; gap:var(--space-2); }
296
+ .fix__opt { display:flex; flex-direction:column; gap:2px; text-align:left; width:100%; padding:10px 14px;
297
+ border:1px solid var(--line-2); background:var(--bg); color:var(--ink); font:inherit; cursor:pointer; }
298
+ .fix__opt:hover:not(:disabled) { border-color:var(--spot); }
299
+ .fix__opt:disabled { cursor:default; }
300
+ .fix__n { color:var(--dim); font-size:13px; }
301
+ .fix__tag { font:400 11px var(--font-mono); letter-spacing:var(--tracking-caps); text-transform:uppercase; }
302
+ .fix__tag:empty { display:none; }
303
+ .fix__opt.is-wrong { border-color:var(--error); }
304
+ .fix__opt.is-wrong .fix__tag { color:var(--error); }
305
+ .fix__opt.is-right { border-color:var(--spot); background:var(--spot-soft); }
306
+ .fix__opt.is-right .fix__tag { color:var(--spot); }
307
+ .fix__foot { display:flex; align-items:center; gap:var(--space-4); margin-top:var(--space-5); }
308
+ .fix__msg { flex:1; margin:0; font-size:13.5px; }
309
+ .fix__msg.wrong { color:var(--error); }
310
+ .fix__msg.warn { color:var(--warning); }
311
+ .fix__msg.ok { color:var(--spot); }
265
312
  .g2 > *, .g3 > * { margin-top:0 !important; }
266
313
 
267
314
  /* ---------- stat tiles ---------- */
@@ -720,6 +767,16 @@
720
767
  </main>
721
768
  </div>
722
769
 
770
+ <dialog class="fix" id="fix" aria-labelledby="fix-q">
771
+ <p class="fix__eyebrow">Correct your answer</p>
772
+ <h2 class="fix__q" id="fix-q"></h2>
773
+ <div class="fix__opts" id="fix-opts" role="group" aria-labelledby="fix-q"></div>
774
+ <div class="fix__foot">
775
+ <p class="fix__msg" id="fix-msg" role="status"></p>
776
+ <button type="button" class="set__btn" id="fix-close">Close</button>
777
+ </div>
778
+ </dialog>
779
+
723
780
  <div class="picker" id="wf-menu" role="menu" aria-label="Workflows" hidden></div>
724
781
  <div class="picker" id="proj-menu" role="listbox" aria-labelledby="proj-label" hidden></div>
725
782
  <div class="picker" id="sel-menu" role="listbox" hidden></div>
@@ -832,14 +889,14 @@ const scopeName = () => (PROJECT === null ? 'all projects' : projectById(PROJECT
832
889
  /** Inventory rows in scope — one, or all of them. */
833
890
  const projectsInScope = () => INV.projects.filter((p) => PROJECT === null || p.id === PROJECT);
834
891
 
835
- /** One row per calendar day in scope: { day, passed, missed, skipped, total }. */
892
+ /** One row per calendar day in scope: { day, passed, corrected, missed, skipped, total }. */
836
893
  function dailyInScope() {
837
894
  const m = new Map();
838
895
  for (const d of S.daily) {
839
896
  if (!inScope(d.repo)) continue;
840
- const cur = m.get(d.day) ?? { day:d.day, passed:0, missed:0, skipped:0, total:0 };
841
- cur.passed += d.passed; cur.missed += d.missed; cur.skipped += d.skipped;
842
- cur.total = cur.passed + cur.missed + cur.skipped;
897
+ const cur = m.get(d.day) ?? { day:d.day, passed:0, corrected:0, missed:0, skipped:0, total:0 };
898
+ cur.passed += d.passed; cur.corrected += d.corrected ?? 0; cur.missed += d.missed; cur.skipped += d.skipped;
899
+ cur.total = cur.passed + cur.corrected + cur.missed + cur.skipped;
843
900
  m.set(d.day, cur);
844
901
  }
845
902
  return m;
@@ -853,7 +910,7 @@ function fillDays(map, n, endKey) {
853
910
  const cur = new Date(end.getTime() - (n - 1) * DAY_MS);
854
911
  for (let i = 0; i < n; i++) {
855
912
  const k = dayKey(cur);
856
- out.push(map.get(k) ?? { day:k, passed:0, missed:0, skipped:0, total:0 });
913
+ out.push(map.get(k) ?? { day:k, passed:0, corrected:0, missed:0, skipped:0, total:0 });
857
914
  cur.setUTCDate(cur.getUTCDate() + 1);
858
915
  }
859
916
  return out;
@@ -935,6 +992,8 @@ function sessionLabel(s) {
935
992
  ============================================================ */
936
993
  const meter = (p, kind) => `<div class="meter${kind ? ' ' + kind : ''}"><i style="width:${Math.max(0, Math.min(100, p))}%"></i></div>`;
937
994
  const gradeBadge = (a) => {
995
+ // A miss corrected from its explainer: the row stays, labelled for what it became.
996
+ if (a.corrected_at) return `<span class="grade pass">corrected${a.corrected_try ? ` · try ${a.corrected_try}` : ''}</span>`;
938
997
  if (a.outcome === 'declined') return '<span class="grade">skipped</span>';
939
998
  if (a.outcome === 'dont_know') return '<span class="grade">blanked</span>';
940
999
  return `<span class="grade ${a.grade >= 3 ? 'pass' : 'miss'}">${a.grade}/5</span>`;
@@ -1084,7 +1143,11 @@ const slice = (arr, p, per = PER) => arr.slice((p - 1) * per, p * per);
1084
1143
  Charts — hand-rolled SVG. No library, no network, both grounds.
1085
1144
  ============================================================ */
1086
1145
 
1087
- /** Stacked daily bars: right at the base, then missed, then skipped. */
1146
+ /**
1147
+ * Stacked daily bars: right at the base, then corrected (a miss answered right
1148
+ * later from its explainer, counted once, on the day of the miss), then
1149
+ * missed, then skipped.
1150
+ */
1088
1151
  function chartActivity(el, days) {
1089
1152
  const tip = el.parentElement.querySelector('.tip');
1090
1153
  const W = Math.max(320, el.parentElement.clientWidth || 900);
@@ -1093,7 +1156,7 @@ function chartActivity(el, days) {
1093
1156
  const step = (W - PAD_L) / days.length;
1094
1157
  const bw = Math.min(26, Math.max(4, step - 5));
1095
1158
  const y = (v) => (H - PAD_B) - (v / max) * (H - PAD_B - 10);
1096
- const SEGMENTS = [['passed', 'var(--spot)'], ['missed', 'var(--warning)'], ['skipped', 'var(--faint-2)']];
1159
+ const SEGMENTS = [['passed', 'var(--spot)'], ['corrected', 'var(--spot-soft)'], ['missed', 'var(--warning)'], ['skipped', 'var(--faint-2)']];
1097
1160
 
1098
1161
  let out = '';
1099
1162
  for (let i = 0; i <= 2; i++) {
@@ -1109,7 +1172,9 @@ function chartActivity(el, days) {
1109
1172
  if (!d[key]) continue;
1110
1173
  const h = (d[key] / max) * (H - PAD_B - 10);
1111
1174
  const inset = acc ? 2 : 0; // a 2px surface gap between stacked fills
1112
- seg += `<rect x="${x}" y="${y(acc) - h + inset}" width="${bw}" height="${Math.max(1, h - inset)}" fill="${color}"/>`;
1175
+ // The soft fill gets a hairline in the accent, or it vanishes against the panel.
1176
+ const edge = key === 'corrected' ? ' stroke="var(--spot)" stroke-width="1"' : '';
1177
+ seg += `<rect x="${x}" y="${y(acc) - h + inset}" width="${bw}" height="${Math.max(1, h - inset)}" fill="${color}"${edge}/>`;
1113
1178
  acc += d[key]; top = color;
1114
1179
  }
1115
1180
  out += `<rect class="hit" x="${PAD_L + i * step}" y="0" width="${step}" height="${H - PAD_B}" data-i="${i}" tabindex="0"/>`
@@ -1127,7 +1192,7 @@ function chartActivity(el, days) {
1127
1192
  const show = () => {
1128
1193
  const d = days[+hit.dataset.i];
1129
1194
  tip.innerHTML = `<div class="mono">${esc(fmtDay(d.day))}</div>`
1130
- + [['Right', d.passed], ['Missed', d.missed], ['Skipped', d.skipped]]
1195
+ + [['Right', d.passed], ['Corrected', d.corrected], ['Missed', d.missed], ['Skipped', d.skipped]]
1131
1196
  .map(([k, v]) => `<div class="r"><span>${k}</span><span>${v}</span></div>`).join('');
1132
1197
  const r = hit.getBoundingClientRect(), p = el.parentElement.getBoundingClientRect();
1133
1198
  tip.style.left = (r.left - p.left + r.width / 2) + 'px';
@@ -1440,11 +1505,12 @@ function viewLearningDashboard() {
1440
1505
  card('Answers, last 30 days', null,
1441
1506
  `<div class="legend">
1442
1507
  <span><b style="background:var(--spot)"></b>Right</span>
1508
+ <span><b style="background:var(--spot-soft);box-shadow:inset 0 0 0 1px var(--spot)"></b>Corrected</span>
1443
1509
  <span><b style="background:var(--warning)"></b>Missed</span>
1444
1510
  <span><b style="background:var(--faint-2)"></b>Skipped</span>
1445
1511
  </div>
1446
1512
  <div class="chart"><div class="tip" id="tip"></div><svg id="c-activity" role="img"
1447
- aria-label="Daily answers, split into right, missed and skipped"></svg></div>`),
1513
+ aria-label="Daily answers, split into right, corrected, missed and skipped"></svg></div>`),
1448
1514
 
1449
1515
  `<div class="g2">
1450
1516
  ${card('Weakest five', null,
@@ -1587,7 +1653,7 @@ function viewConcept(slug) {
1587
1653
 
1588
1654
  tiles([
1589
1655
  ['Score', c.seen ? Math.round(c.score * 100) + '%' : '—', decayed ? `decayed from ${Math.round(c.stored_score * 100)}%` : 'last five grades, recent first', c.mastered ? 'good' : c.score < 0.5 && c.seen ? 'warn' : ''],
1590
- ['State', c.mastered ? 'mastered' : c.seen ? (c.due ? 'due' : 'learning') : 'not asked', c.seen ? `${c.passed} of ${c.attempts} right` : 'no questions yet', c.due ? 'warn' : ''],
1656
+ ['State', c.mastered ? 'mastered' : c.seen ? (c.due ? 'due' : 'learning') : 'not asked', c.seen ? `${c.passed} of ${c.attempts} right${c.corrected ? ` · ${c.corrected} corrected` : ''}` : 'no questions yet', c.due ? 'warn' : ''],
1591
1657
  ['Next review', c.owed ? whenDue(c.next_review) : '—', c.interval_d ? `every ${Math.round(c.interval_d)}d · ease ${c.ease ? c.ease.toFixed(2) : '—'} · ${plural(c.reps, 'rep')}` : 'not scheduled', c.due ? 'warn' : ''],
1592
1658
  ]),
1593
1659
 
@@ -1856,9 +1922,9 @@ function viewProjects() {
1856
1922
  const m = new Map();
1857
1923
  for (const d of S.daily) {
1858
1924
  if (pid(d.repo) !== p.id) continue;
1859
- const cur = m.get(d.day) ?? { day:d.day, passed:0, missed:0, skipped:0, total:0 };
1860
- cur.passed += d.passed; cur.missed += d.missed; cur.skipped += d.skipped;
1861
- cur.total = cur.passed + cur.missed + cur.skipped;
1925
+ const cur = m.get(d.day) ?? { day:d.day, passed:0, corrected:0, missed:0, skipped:0, total:0 };
1926
+ cur.passed += d.passed; cur.corrected += d.corrected ?? 0; cur.missed += d.missed; cur.skipped += d.skipped;
1927
+ cur.total = cur.passed + cur.corrected + cur.missed + cur.skipped;
1862
1928
  m.set(d.day, cur);
1863
1929
  }
1864
1930
  chartActivity(el, fillDays(m, 30));
@@ -2462,8 +2528,10 @@ function viewHealth() {
2462
2528
  Artifacts — the pages an agent wrote under ~/.eklavya/artifacts/.
2463
2529
  `S.artifacts` is read from the files on every load; opening one is a plain
2464
2530
  link to `/artifacts/<id>`, which the server serves sandboxed, in a new tab.
2531
+ An explainer for a missed question that can still be corrected opens in
2532
+ the viewer instead (`#/artifacts/view/<id>`), with the correction bar.
2465
2533
  ============================================================ */
2466
- const ARTIFACT_KINDS = ['all', 'explainer', 'artifact'];
2534
+ const ARTIFACT_KINDS = ['all', 'explainer', 'artifact', 'to-correct'];
2467
2535
  const artifactsInScope = () => (S.artifacts ?? []).filter((a) => inScope(a.project));
2468
2536
  const artifactUrl = (a) => '/artifacts/' + a.id.split('/').map(encodeURIComponent).join('/');
2469
2537
 
@@ -2475,12 +2543,17 @@ function artifactCard(a) {
2475
2543
  const concept = !a.concept ? ''
2476
2544
  : known ? ` · <a class="link" data-go="${esc(LH('concept', a.concept))}">${esc(a.concept)}</a>`
2477
2545
  : ` · ${esc(a.concept)}`;
2546
+ const tag = a.correction === 'open' ? ' · <span class="tag warn">To correct</span>'
2547
+ : a.correction === 'done' ? ' · <span class="tag ok">Corrected</span>' : '';
2548
+ // A correctable explainer opens in the viewer, where the correction lives;
2549
+ // every other page is a plain link to the sandboxed file, in a new tab.
2550
+ const open = a.correction ? `href="${esc(AH('view', a.id))}"` : `href="${esc(artifactUrl(a))}" target="_blank" rel="noopener"`;
2478
2551
  return `<article class="art">
2479
- <a class="art__open" href="${esc(artifactUrl(a))}" target="_blank" rel="noopener">
2552
+ <a class="art__open" ${open}>
2480
2553
  <span class="art__thumb"><img src="${esc(thumbUrl(a, window.eklavyaGround.read()))}" data-thumb="${esc(thumbUrl(a, '@'))}" alt="" loading="lazy" decoding="async"></span>
2481
2554
  <span class="title">${esc(a.title)}</span></a>
2482
2555
  ${a.description ? `<p class="ctx">${esc(a.description)}</p>` : ''}
2483
- <p class="slug">${esc(a.kind)} · ${esc(stamp(a.created))}${concept}</p>
2556
+ <p class="slug">${esc(a.kind)} · ${esc(stamp(a.created))}${concept}${tag}</p>
2484
2557
  </article>`;
2485
2558
  }
2486
2559
 
@@ -2490,7 +2563,7 @@ function viewArtifacts(param) {
2490
2563
  const all = artifactsInScope();
2491
2564
  const q = t.q.trim().toLowerCase();
2492
2565
  const list = all.filter((a) => {
2493
- if (kind !== 'all' && a.kind !== kind) return false;
2566
+ if (kind === 'to-correct' ? a.correction !== 'open' : kind !== 'all' && a.kind !== kind) return false;
2494
2567
  if (q && ![a.title, a.description, a.concept ?? '', pname(a.project), a.id].join(' ').toLowerCase().includes(q)) return false;
2495
2568
  return true;
2496
2569
  });
@@ -2504,6 +2577,7 @@ function viewArtifacts(param) {
2504
2577
  }
2505
2578
  const counts = { all: all.length, explainer: all.filter((a) => a.kind === 'explainer').length };
2506
2579
  counts.artifact = all.length - counts.explainer;
2580
+ counts['to-correct'] = all.filter((a) => a.correction === 'open').length;
2507
2581
  const projects = new Set(all.map((a) => pid(a.project))).size;
2508
2582
  const chip = (k, label) => `<button class="chip" data-go="${esc(AH('dashboard', k === 'all' ? null : k))}" aria-pressed="${kind === k}">${label} ${counts[k]}</button>`;
2509
2583
  const body = !list.length
@@ -2516,16 +2590,150 @@ function viewArtifacts(param) {
2516
2590
  plural(all.length, 'page'), plural(counts.explainer, 'explainer'),
2517
2591
  `<a class="link" data-go="${esc(AH('projects'))}">${plural(projects, 'project')}</a>`,
2518
2592
  ], `Pages your agent wrote for you — explainers after a missed question, and anything you asked to have
2519
- turned into a page. Each opens in a new tab, from <code>~/.eklavya/artifacts/</code>.`),
2593
+ turned into a page. Each opens in a new tab, from <code>~/.eklavya/artifacts/</code>. An explainer for a
2594
+ missed question opens here instead, with a <b>Correct your answer</b> button under it: pick the right
2595
+ option and the miss shows as corrected. That counts as a pass for the concept's score, but not toward a
2596
+ commit gate or a level, and the concept still comes back for review.`),
2520
2597
  card(null, null,
2521
2598
  `<div class="filters">
2522
2599
  <div class="grow"><input type="search" id="aq" value="${esc(t.q)}" placeholder="Search artifacts" aria-label="Search artifacts"></div>
2523
2600
  </div>
2524
- <div class="chips">${chip('all','All')}${chip('explainer','Explainers')}${chip('artifact','Other pages')}</div>
2601
+ <div class="chips">${chip('all','All')}${chip('explainer','Explainers')}${chip('to-correct','To correct')}${chip('artifact','Other pages')}</div>
2525
2602
  <div style="margin-top:var(--space-4)">${body}</div>`),
2526
2603
  ].join('');
2527
2604
  }
2528
2605
 
2606
+ /* ---------- the explainer viewer and its correction ----------
2607
+ The agent's page is framed sandboxed without same-origin, so it can never
2608
+ read or write the API; the bar and the modal around it are this page's own
2609
+ code, and the server grades every pick. */
2610
+ const FRAME_SANDBOX = 'allow-scripts allow-modals allow-downloads allow-popups allow-popups-to-escape-sandbox';
2611
+ /** The correction on screen: `{ attempt, artifact, q }`, `q` being /api/attempts/correction's answer. */
2612
+ let FIX = null;
2613
+
2614
+ function viewArtifact(id) {
2615
+ const a = (S.artifacts ?? []).find((x) => x.id === id);
2616
+ if (!a) return viewNotFound(id, false);
2617
+ if (a.correction) fill('fixbar', `/api/attempts/correction?id=${a.attempt}`, (d) => fixBar(a, d));
2618
+ // The page's own ground follows this one; a page made before the viewer ignores it.
2619
+ AFTER.push(() => {
2620
+ const f = $('art-frame');
2621
+ f?.addEventListener('load', () => f.contentWindow?.postMessage({ type:'eklavya:mode', mode:window.eklavyaGround.read() }, '*'));
2622
+ });
2623
+ return `<a class="page__back" data-go="${esc(AH('dashboard'))}">← Artifacts</a>
2624
+ <iframe class="viewer" id="art-frame" src="${esc(artifactUrl(a))}?embed" title="${esc(a.title)}" sandbox="${FRAME_SANDBOX}"></iframe>
2625
+ ${a.correction ? `<div class="fixbar" id="fixbar">${loader('Loading the question…')}</div>` : ''}`;
2626
+ }
2627
+
2628
+ function fixBar(a, d) {
2629
+ FIX = { attempt: a.attempt, artifact: a.id, q: d };
2630
+ const done = d.corrected_at !== null;
2631
+ armLeave(!done);
2632
+ return done
2633
+ ? `<p class="fixbar__done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg>Corrected on try ${d.tries} · ${esc(stamp(d.corrected_at))}</p>`
2634
+ : `<button type="button" class="btn" id="fix-open">Correct your answer</button>
2635
+ <p class="fixbar__note">Not now? Correct it later from Dashboard → Artifacts.</p>`;
2636
+ }
2637
+
2638
+ /** The options in a new order each time: the page above shows the right one at its letter. */
2639
+ function shuffled(n) {
2640
+ const order = [...Array(n).keys()];
2641
+ for (let tries = 0; tries < 5; tries++) {
2642
+ for (let i = n - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [order[i], order[j]] = [order[j], order[i]]; }
2643
+ if (order.some((v, i) => v !== i)) break;
2644
+ }
2645
+ return order;
2646
+ }
2647
+
2648
+ function fixSay(text, kind) {
2649
+ const m = $('fix-msg');
2650
+ m.textContent = text;
2651
+ m.className = 'fix__msg' + (kind ? ' ' + kind : '');
2652
+ }
2653
+
2654
+ function openFix() {
2655
+ const d = FIX.q;
2656
+ $('fix-q').textContent = d.question;
2657
+ $('fix-opts').innerHTML = shuffled(d.options.length).map((i) =>
2658
+ `<button type="button" class="fix__opt" data-pick="${esc(d.options[i])}"><span>${esc(d.options[i])}</span>`
2659
+ + `${d.option_notes?.[i] ? `<span class="fix__n">${esc(d.option_notes[i])}</span>` : ''}<span class="fix__tag"></span></button>`).join('');
2660
+ fixSay('', '');
2661
+ $('fix').showModal();
2662
+ $('fix-opts').querySelector('button').focus();
2663
+ }
2664
+
2665
+ function pickFix(btn) {
2666
+ const all = [...$('fix-opts').querySelectorAll('button')];
2667
+ const reopen = () => all.forEach((b) => { b.disabled = !!b.dataset.wrong; });
2668
+ all.forEach((b) => { b.disabled = true; });
2669
+ postJson('/api/attempts/retry', { attempt_id: FIX.attempt, picked: btn.dataset.pick })
2670
+ .then((r) => {
2671
+ if (r.corrected) {
2672
+ btn.classList.add('is-right');
2673
+ btn.querySelector('.fix__tag').textContent = 'Right answer';
2674
+ fixSay('Corrected. Your stats now count this as answered.', 'ok');
2675
+ FIX.q = { ...FIX.q, tries: r.tries, corrected_at: new Date().toISOString() };
2676
+ const row = S.artifacts.find((x) => x.id === FIX.artifact);
2677
+ if (row) row.correction = 'done';
2678
+ // Every other count on the page was drawn from the payload it booted with:
2679
+ // say so now rather than waiting for the poll.
2680
+ $('stale').hidden = false;
2681
+ armLeave(false);
2682
+ $('fix-close').focus();
2683
+ return;
2684
+ }
2685
+ btn.classList.add('is-wrong');
2686
+ btn.dataset.wrong = '1';
2687
+ btn.querySelector('.fix__tag').textContent = 'Not this one';
2688
+ reopen();
2689
+ fixSay('Still incorrect. Please correct your answer.', 'wrong');
2690
+ all.find((b) => !b.disabled)?.focus();
2691
+ })
2692
+ .catch((e) => { reopen(); fixSay(e.message, 'warn'); });
2693
+ }
2694
+
2695
+ $('fix-opts').addEventListener('click', (e) => {
2696
+ const b = e.target.closest('.fix__opt');
2697
+ if (b && !b.disabled) pickFix(b);
2698
+ });
2699
+ // Arrow keys walk the options that can still be picked; Enter and Space pick (native buttons).
2700
+ $('fix-opts').addEventListener('keydown', (e) => {
2701
+ const step = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[e.key];
2702
+ if (!step) return;
2703
+ e.preventDefault();
2704
+ const live = [...$('fix-opts').querySelectorAll('button:not(:disabled)')];
2705
+ const i = live.indexOf(document.activeElement);
2706
+ live[(i + step + live.length) % live.length]?.focus();
2707
+ });
2708
+ $('fix-close').addEventListener('click', () => $('fix').close());
2709
+ // However it closes -- the button, Escape -- the bar shows where things stand.
2710
+ $('fix').addEventListener('close', () => {
2711
+ const bar = $('fixbar');
2712
+ const a = FIX && S.artifacts.find((x) => x.id === FIX.artifact);
2713
+ if (bar && a) { bar.innerHTML = fixBar(a, FIX.q); $('fix-open')?.focus(); }
2714
+ });
2715
+ $('view').addEventListener('click', (e) => { if (e.target.closest('#fix-open')) openFix(); });
2716
+
2717
+ /**
2718
+ * Closing the tab with a correction still open asks first. Browsers show their
2719
+ * own generic prompt -- a page cannot set its text -- so the bar carries ours.
2720
+ */
2721
+ const holdLeave = (e) => { e.preventDefault(); e.returnValue = ''; };
2722
+ let LEAVE = false;
2723
+ function armLeave(on) {
2724
+ if (on === LEAVE) return;
2725
+ LEAVE = on;
2726
+ window[on ? 'addEventListener' : 'removeEventListener']('beforeunload', holdLeave);
2727
+ }
2728
+
2729
+ // The framed page reports its height; only that frame's message counts.
2730
+ addEventListener('message', (e) => {
2731
+ const f = $('art-frame');
2732
+ if (!f || e.source !== f.contentWindow || e.data?.type !== 'eklavya:height') return;
2733
+ const h = Number(e.data.h);
2734
+ if (Number.isFinite(h)) f.style.height = Math.min(20000, Math.max(200, h)) + 'px';
2735
+ });
2736
+
2529
2737
  function viewArtifactProjects() {
2530
2738
  const by = new Map();
2531
2739
  for (const a of artifactsInScope()) {
@@ -2730,6 +2938,20 @@ function controlValue(f, el) {
2730
2938
  return el.value;
2731
2939
  }
2732
2940
 
2941
+ /**
2942
+ * Every write the page makes. The server's `acceptWrite` wants the per-start
2943
+ * token from the page's own meta tag and JSON; this sends both. Resolves with
2944
+ * the parsed reply, rejects with the server's own error text.
2945
+ */
2946
+ function postJson(path, body) {
2947
+ const token = document.querySelector('meta[name="eklavya-token"]')?.content ?? '';
2948
+ return fetch(path, {
2949
+ method: 'POST',
2950
+ headers: { 'content-type': 'application/json', 'x-eklavya-token': token },
2951
+ body: JSON.stringify(body),
2952
+ }).then((r) => r.json().then((d) => { if (!r.ok) throw new Error(d.error || `${r.status}`); return d; }));
2953
+ }
2954
+
2733
2955
  /**
2734
2956
  * Saves one setting and redraws the settings in place — not through `render()`,
2735
2957
  * which would scroll to the top and lose the control the reader just used.
@@ -2740,14 +2962,8 @@ function saveSetting(key, change) {
2740
2962
  const say = (text, warn) => { if (msg) { msg.textContent = text; msg.className = warn ? 'warn' : 'here'; } };
2741
2963
  fieldError(key, null);
2742
2964
  say('saving…');
2743
- const token = document.querySelector('meta[name="eklavya-token"]')?.content ?? '';
2744
2965
  const gen = GEN;
2745
- fetch('/api/settings', {
2746
- method: 'POST',
2747
- headers: { 'content-type': 'application/json', 'x-eklavya-token': token },
2748
- body: JSON.stringify({ scope, project: SETS?.project?.id, key, ...change }),
2749
- })
2750
- .then((r) => r.json().then((d) => { if (!r.ok) throw new Error(d.error || `${r.status}`); }))
2966
+ postJson('/api/settings', { scope, project: SETS?.project?.id, key, ...change })
2751
2967
  .then(() => fetch(settingsUrl()).then((r) => r.json()))
2752
2968
  .then((d) => {
2753
2969
  const el = $('settings');
@@ -2848,6 +3064,7 @@ const WORKFLOWS = {
2848
3064
  ],
2849
3065
  pages: {
2850
3066
  dashboard: { label: 'Dashboard', view: (p) => viewArtifacts(p) },
3067
+ view: { parent: 'dashboard', view: (p) => viewArtifact(p) },
2851
3068
  projects: { label: 'Projects', view: () => viewArtifactProjects() },
2852
3069
  },
2853
3070
  },
@@ -3470,6 +3687,8 @@ function render(keepFocus) {
3470
3687
 
3471
3688
  AFTER.length = 0;
3472
3689
  LAZY.clear();
3690
+ // A screen that still has a correction open re-arms this when it draws its bar.
3691
+ armLeave(false);
3473
3692
  $('view').innerHTML = !pg ? viewNotFound('#/' + CUR.wf + '/' + CUR.page, false)
3474
3693
  : CUR.bad ? viewNotFound(CUR.param, true)
3475
3694
  : pg.view(CUR.param);
@@ -3610,7 +3829,8 @@ addEventListener('hashchange', () => { if (INV) { closePicker(false); syncFromUr
3610
3829
  let resizeTimer = 0;
3611
3830
  addEventListener('resize', () => {
3612
3831
  clearTimeout(resizeTimer);
3613
- resizeTimer = setTimeout(() => { if (S) render({ id:'q', pos:null }); }, 120);
3832
+ // Not the viewer: a re-render would reload the framed page and lose its scroll.
3833
+ resizeTimer = setTimeout(() => { if (S && CUR.page !== 'view') render({ id:'q', pos:null }); }, 120);
3614
3834
  });
3615
3835
 
3616
3836
  document.querySelectorAll('[data-ground]').forEach((b) => {
@@ -183,7 +183,8 @@ scales, how long feedback may be, the sequence a blank earns, and what
183
183
  `already_taught` changes.
184
184
 
185
185
  **A missed answer can come back with `explain`.** That is `explain_on_wrong` at
186
- work: follow its `instruction` exactly. The page is written in the background
186
+ work: follow its `instruction` exactly. Hand the explainer its `attempt_id` and
187
+ `option_notes` too: the page shows the notes and can be corrected. The page is written in the background
187
188
  and opens by itself, so give the verdict, say the page is on its way, and go
188
189
  back to the task — no longer explanation, no waiting. When the developer asks
189
190
  for something to be explained as a page ("explain this to me", "make me a page