eklavya 1.76.0 → 1.77.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.
package/README.md CHANGED
@@ -122,7 +122,7 @@ npx eklavya --version
122
122
 
123
123
  The [CLI reference](https://eklavya-run.web.app/docs/cli/) lists all commands and
124
124
  flags. `feedback generate` reviews one of your earlier prompts, using what your later prompts in that session had to add, when `feedback.enabled`,
125
- memory and `providers.observer` are set. Once a day, after your first answer, the Learning dashboard opens one card on arrival (a milestone, a welcome back, the most overdue concept and so on), chosen by the engine in `assets/daily-card.cjs`; the Daily card switch on Settings, Companion, turns it off. The dashboard reads the database (its Concepts page shows each concept's tags and filters by one with `?tag=`) and has five guarded writes: a setting
125
+ memory and `providers.observer` are set. Once a day, after your first answer, the Learning dashboard opens one card on arrival (a milestone, a welcome back, the most overdue concept and so on), chosen by the engine in `assets/daily-card.cjs`; the Daily card switch on Settings, Companion, turns it off. Dashboard counts of 1,000 or more read short (1K, 1.2K, 1M). The dashboard reads the database (its Concepts page shows each concept's tags and filters by one with `?tag=`) and has five guarded writes: a setting
126
126
  change, a correction of a missed answer (which can also complete a level), and acknowledging, deleting or counting the opening of a
127
127
  feedback item (`POST /api/feedback/acknowledge`, `/delete`, `/opened`; the reads are `GET /api/feedback` and `/api/feedback/list`). An open page listens to
128
128
  `GET /api/events`, a stream of the change cursor (a counter that moves on every
@@ -739,9 +739,9 @@
739
739
  @keyframes toast-in { from { opacity:0; transform:translateY(10px); } }
740
740
  /* The daily card (see DAILY): one native dialog, square, hairline, the archer on top. */
741
741
  dialog.daily { width:min(440px, calc(100vw - 32px)); max-height:calc(100vh - 32px); max-height:calc(100dvh - 32px); margin:auto; padding:0;
742
- border:1px solid var(--line-2); border-radius:0; background:var(--panel); color:var(--ink); box-shadow:var(--shadow-lg);
742
+ border:1px solid var(--line-2); border-radius:0; background:var(--panel); color:var(--ink); box-shadow:0 0 0 1px var(--line-2), 0 24px 80px rgba(0,0,0,.6);
743
743
  overflow-x:hidden; overflow-y:auto; }
744
- dialog.daily::backdrop { background:color-mix(in srgb, var(--bg) 70%, transparent); }
744
+ dialog.daily::backdrop { background:color-mix(in srgb, var(--ink) 55%, transparent); backdrop-filter:blur(10px) saturate(.8); }
745
745
  dialog.daily[open] { animation:daily-in var(--duration-slow) var(--ease); }
746
746
  @keyframes daily-in { from { opacity:0; transform:translateY(16px); } }
747
747
  .daily__x { position:absolute; top:var(--space-2); right:var(--space-2); width:32px; height:32px; display:grid; place-items:center;
@@ -761,8 +761,8 @@
761
761
  .daily__btn--quiet { border-color:transparent; background:transparent; color:var(--dim); }
762
762
  .say { position:relative; background:var(--bg); border:1.5px solid var(--ink); padding:10px 14px; max-width:260px; font:600 13.5px/1.4 var(--font-body);
763
763
  margin-bottom:26px; }
764
- .say::after { content:""; position:absolute; right:-9px; bottom:14px; width:14px; height:14px; background:var(--bg);
765
- border-right:1.5px solid var(--ink); border-top:1.5px solid var(--ink); transform:rotate(45deg); }
764
+ .say::after { content:""; position:absolute; left:-9px; bottom:14px; width:14px; height:14px; background:var(--bg);
765
+ border-left:1.5px solid var(--ink); border-bottom:1.5px solid var(--ink); transform:rotate(45deg); }
766
766
  .daily__row { display:flex; gap:var(--space-4); align-items:center; }
767
767
  .daily__big { font:900 56px/.9 var(--font-disp); letter-spacing:-.05em; font-variant-numeric:tabular-nums; }
768
768
  .daily__lbl { font:500 10.5px/1 var(--font-mono); letter-spacing:.18em; text-transform:uppercase; color:var(--faint); }
@@ -798,6 +798,8 @@
798
798
  @keyframes confetti { 0% { opacity:1; transform:translateY(0) rotate(0); } 100% { opacity:0; transform:translateY(480px) rotate(540deg); } }
799
799
  .streak__talk { display:flex; align-items:flex-end; gap:var(--space-1); max-width:100%; }
800
800
  .streak__talk .say { margin-bottom:30px; }
801
+ /* Here the archer stands to the right of the bubble, so its tail points right. */
802
+ .streak__talk .say::after { left:auto; right:-9px; border-left:0; border-bottom:0; border-right:1.5px solid var(--ink); border-top:1.5px solid var(--ink); }
801
803
  @media (prefers-reduced-motion:reduce) { dialog.daily[open], .daily__flame.is-live { animation:none; }
802
804
  .daily__ring .f, .daily__bar > b, .daily__bars i { transition:none; } .daily__confetti { display:none; } }
803
805
  dialog.dlg { width:min(460px, calc(100vw - 32px)); margin:auto; padding:0; border:1px solid var(--line-2); border-radius:0;
@@ -1386,7 +1388,10 @@ const stamp = (ts) => {
1386
1388
  return d.toLocaleDateString(undefined, { month:'short', day:'numeric' })
1387
1389
  + ', ' + d.toLocaleTimeString(undefined, { hour:'2-digit', minute:'2-digit' });
1388
1390
  };
1389
- const plural = (n, w) => `${n} ${w}${n === 1 ? '' : 's'}`;
1391
+ /** Counts of 1,000 and up read short: 1K, 1.2K, 1M. Below that, the number as it is. Strings pass through. */
1392
+ const COMPACT = new Intl.NumberFormat('en', { notation:'compact', maximumFractionDigits:1 });
1393
+ const fmtN = (n) => (typeof n === 'number' && Math.abs(n) >= 1000 ? COMPACT.format(n) : n);
1394
+ const plural = (n, w) => `${fmtN(n)} ${w}${n === 1 ? '' : 's'}`;
1390
1395
 
1391
1396
  const TIER = { 1:'recall', 2:'mechanism', 3:'judgement', 4:'failure modes', 5:'design' };
1392
1397
  const tierLabel = (t) => `T${t}${TIER[t] ? ' ' + TIER[t] : ''}`;
@@ -1717,7 +1722,7 @@ const statePill = (c) =>
1717
1722
  function tiles(items) {
1718
1723
  return `<div class="stats">` + items.map(([k, v, n, kind, go]) =>
1719
1724
  `<div class="tile${kind ? ' is-' + kind : ''}"${go ? ` data-go="${esc(go)}" tabindex="0" role="link"` : ''}>`
1720
- + `<b>${esc(k)}</b><strong>${esc(v)}</strong><span>${esc(n)}</span></div>`
1725
+ + `<b>${esc(k)}</b><strong>${esc(fmtN(v))}</strong><span>${esc(n)}</span></div>`
1721
1726
  ).join('') + `</div>`;
1722
1727
  }
1723
1728
 
@@ -2654,8 +2659,8 @@ function viewLearningDashboard() {
2654
2659
  answered moves a score.`),
2655
2660
 
2656
2661
  tiles([
2657
- ['Accuracy', answered ? pct(passed, answered) + '%' : '—', answered ? `${passed} right of ${answered}` : 'No assessments yet', answered && pct(passed, answered) >= 70 ? 'good' : '', LH('sessions')],
2658
- ['Mastered', mastered.length, `of ${seen.length} asked`, mastered.length ? 'good' : '', LH('concepts', 'mastered')],
2662
+ ['Accuracy', answered ? pct(passed, answered) + '%' : '—', answered ? `${fmtN(passed)} right of ${fmtN(answered)}` : 'No assessments yet', answered && pct(passed, answered) >= 70 ? 'good' : '', LH('sessions')],
2663
+ ['Mastered', mastered.length, `of ${fmtN(seen.length)} asked`, mastered.length ? 'good' : '', LH('concepts', 'mastered')],
2659
2664
  ['Due now', due.length, due.length ? 'ready for review' : 'nothing waiting', due.length ? 'warn' : '', LH('review')],
2660
2665
  toCorrectTile(),
2661
2666
  ]),
@@ -3418,10 +3423,10 @@ function memTiles() {
3418
3423
  const { scoped, events, pending, entries, sessions } = memCounts();
3419
3424
  const everywhere = scoped ? 'all projects · ' : '';
3420
3425
  return tiles([
3421
- ['Observations', entries, scoped ? 'live in this project' : `${M.indexed} indexed`, '', MH('timeline')],
3426
+ ['Observations', entries, scoped ? 'live in this project' : `${fmtN(M.indexed)} indexed`, '', MH('timeline')],
3422
3427
  ['Sessions', sessions, 'captured or remembered', '', MH('sessions')],
3423
- ['Pending', pending, pending ? `of ${events} evidence events` : `all ${events} distilled`, pending ? 'warn' : '', MH('health')],
3424
- ['Reused', M.reused, `${everywhere}${M.exposed} delivered`, '', MH('reuse')],
3428
+ ['Pending', pending, pending ? `of ${fmtN(events)} evidence events` : `all ${fmtN(events)} distilled`, pending ? 'warn' : '', MH('health')],
3429
+ ['Reused', M.reused, `${everywhere}${fmtN(M.exposed)} delivered`, '', MH('reuse')],
3425
3430
  ]);
3426
3431
  }
3427
3432
 
@@ -3514,7 +3519,7 @@ function viewMemoryDashboard() {
3514
3519
 
3515
3520
  return [
3516
3521
  pageHead('What does memory hold?', [
3517
- `${M.processed} distilled`, `${M.indexed} indexed`, `${M.reused} reused`, `${M.exposed} delivered`, `${M.assessed} assessed`,
3522
+ `${fmtN(M.processed)} distilled`, `${fmtN(M.indexed)} indexed`, `${fmtN(M.reused)} reused`, `${fmtN(M.exposed)} delivered`, `${fmtN(M.assessed)} assessed`,
3518
3523
  ], `Captured, processed, stored, retrieved, delivered, assessed — six different things that can happen to one
3519
3524
  piece of evidence, counted separately. None of them is an answer: only a question you answered moves
3520
3525
  mastery. Reuse, delivery and assessment are counted across all projects.`),
@@ -3738,13 +3743,13 @@ function viewHealth() {
3738
3743
  !H.capture.enabled ? 'memory is disabled' : !H.capture.newest_event ? 'nothing has been captured yet' : '',
3739
3744
  Q.failed ? `${plural(Q.failed, 'job')} failed after retries` : '',
3740
3745
  Q.paused ? `${plural(Q.paused, 'job')} paused, waiting on a decision` : '',
3741
- H.spool_dropped ? `${H.spool_dropped} events dropped by the spool` : '',
3746
+ H.spool_dropped ? `${fmtN(H.spool_dropped)} events dropped by the spool` : '',
3742
3747
  ].filter(Boolean);
3743
3748
  return [
3744
3749
  pageHead('Health', [
3745
3750
  `last capture ${esc(H.capture.newest_event ? ago(H.capture.newest_event) : 'never')}`,
3746
3751
  H.capture.enabled ? `capture is ${esc(H.capture.mode)}` : 'memory off',
3747
- `${Q.pending} queued${Q.pending ? `, oldest ${esc(oldest)}` : ''}`,
3752
+ `${fmtN(Q.pending)} queued${Q.pending ? `, oldest ${esc(oldest)}` : ''}`,
3748
3753
  ], `Where memory stops, when it stops. Capture, processing, indexing, retrieval and delivery fail in
3749
3754
  different ways and this page keeps them apart. Error classes only — no log dumps, no keys, no raw
3750
3755
  request bodies. Transient failures retry on their own; auth, quota and missing need you.`),
@@ -4984,6 +4989,9 @@ function fbEmpty(F) {
4984
4989
  } else if (F.failed) {
4985
4990
  state = 'error';
4986
4991
  body = `<p>Couldn't review the last session. It will try again at a later start.</p>`;
4992
+ } else if (F.acknowledged > 0) {
4993
+ state = 'happy';
4994
+ body = `<p>The next feedback is on its way. Eklavya will share it once it's available.</p>`;
4987
4995
  } else {
4988
4996
  body = `<p>Nothing to review yet. When a session ends, the next one you start reviews one of its prompts.</p>`;
4989
4997
  }
@@ -5157,7 +5165,7 @@ const fbDecide = (it) => `<section class="card fb__card fb__decide" aria-labelle
5157
5165
  <div class="fb__dh"><eklavya-mascot state="info" variant="face" size="32"></eklavya-mascot><h3 id="fb-decide-h">Done reading?</h3></div>
5158
5166
  <p class="fb__dp">${esc(FB.rulePending)}</p>
5159
5167
  ${fbActions(it.id)}
5160
- <p class="fb__foot"><span>1 waiting</span><span>${esc(S.feedback.acknowledged + ' in history')}</span></p></section>`;
5168
+ <p class="fb__foot"><span>1 waiting</span><span>${esc(fmtN(S.feedback.acknowledged) + ' in history')}</span></p></section>`;
5161
5169
 
5162
5170
  /**
5163
5171
  * One item: the verdict, the two prompts, what worked, what it left out and the
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eklavya",
3
3
  "displayName": "Eklavya",
4
- "version": "1.76.0",
4
+ "version": "1.77.0",
5
5
  "description": "Learn while your agent works. Turns coding-agent generation time into adaptive, Socratic learning grounded in the code being written.",
6
6
  "author": {
7
7
  "name": "Ajay Kumar"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eklavya",
3
- "version": "1.76.0",
3
+ "version": "1.77.0",
4
4
  "description": "Learn while your agent works — local knowledge graph, spaced repetition, and quiz gating for agent-assisted development.",
5
5
  "license": "MIT",
6
6
  "repository": {