eklavya 1.27.0 → 1.28.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
@@ -1,27 +1,29 @@
1
- # eklavya
1
+ # Eklavya
2
2
 
3
- [Eklavya](https://github.com/ProjectAJ14/eklavya) — learn while your agent works. This package is
4
- the whole thing: the installer, the MCP server, the CLI, and the Claude Code plugin it installs.
3
+ [Eklavya](https://github.com/ProjectAJ14/eklavya) teaches the concepts behind
4
+ agent-written code and keeps local project memory. This package includes the
5
+ Claude Code plugin, installer, CLI and MCP server.
5
6
 
6
- ## Install
7
+ ## Install in Claude Code
8
+
9
+ With Node 22 or newer:
7
10
 
8
11
  ```bash
9
12
  npx eklavya install
10
13
  ```
11
14
 
12
- That checks your Node version, installs the runtime and its SQLite driver, registers the plugin
13
- with Claude Code and enables it, installs the chat skill into `~/.claude/skills/`, and creates the
14
- database. Restart Claude Code afterwards, then run `/eklavya:setup` to pick a mode. Re-running it
15
- is how you upgrade; `npx eklavya uninstall` removes it and keeps your history unless you pass
16
- `--purge`.
15
+ Follow the settings prompts, then restart Claude Code. The installer registers
16
+ the plugin, installs the managed runtime and chat/artifact skills, and prepares
17
+ local storage. `/eklavya:setup` lets you revisit settings.
17
18
 
18
- Requires **Node 22+** — below that the SQLite driver has no prebuilt binary and would need a C++
19
- toolchain to compile.
19
+ Read the [installation guide](https://eklavya-run.web.app/docs/installing/) for
20
+ verification and [updates and removal](https://eklavya-run.web.app/docs/updates/)
21
+ for maintenance. Uninstall preserves learner data unless you explicitly use
22
+ `--purge`.
20
23
 
21
- ## As a standalone MCP server
24
+ ## Standalone MCP connection
22
25
 
23
- The server runs on its own, so any MCP client can use it — this is what makes Eklavya
24
- editor-agnostic.
26
+ An MCP client can connect directly:
25
27
 
26
28
  ```json
27
29
  {
@@ -31,41 +33,45 @@ editor-agnostic.
31
33
  }
32
34
  ```
33
35
 
34
- > Renamed from `eklavya-mcp` after 1.7.0, and the separate `eklavya-mcp` binary folded into
35
- > `eklavya serve` at the same time — one package, one command. The old package still exists on
36
- > npm so older installs keep resolving, but it is no longer updated.
36
+ This exposes tools. Automatic capture, checkpoints and session instructions
37
+ depend on host integration; an MCP connection alone does not provide Claude
38
+ Code's learning loop.
37
39
 
38
- State lives in `~/.eklavya/knowledge.db` (SQLite, WAL). Override with `EKLAVYA_HOME` or `EKLAVYA_DB`.
40
+ ## Available tools
39
41
 
40
- ## Tools
41
-
42
- | Tool | Purpose |
42
+ | Purpose | Tools |
43
43
  |---|---|
44
- | `get_learner_profile` | what the developer already knows; call before teaching |
45
- | `log_session_concepts` | record what the current task exercises, with code context |
46
- | `get_session_quiz_plan` | what to ask, and at which difficulty tier |
47
- | `record_attempt` | grade 0–5, update mastery and the review schedule |
48
- | `get_gate_status` | whether this session's quiz gate has passed |
49
- | `upsert_concepts` | grow the graph, with slug normalization and dedupe |
50
- | `get_concept_graph` | a domain in prerequisite order |
51
- | `get_config` / `set_config` | global and per-repo settings |
44
+ | Learner state and concepts | `get_learner_profile`, `log_session_concepts`, `upsert_concepts`, `get_concept_graph` |
45
+ | Questions and assessment | `get_session_quiz_plan`, `record_attempt`, `get_gate_status` |
46
+ | Settings | `get_config`, `set_config` |
47
+ | Find and read memory | `memory_search`, `memory_get`, `memory_timeline`, `memory_file_history`, `memory_status` |
48
+ | Maintain memory | `memory_write`, `memory_correct`, `memory_delete`, `memory_collections` |
49
+ | Inspect code | `code_outline`, `code_find_symbol` |
52
50
 
53
- `session_id` is optional on every tool — the server resolves the current session itself.
51
+ Search first, choose relevant entries, then use `memory_get` for their full
52
+ content. Session-aware learning and configuration tools resolve the current session
53
+ when `session_id` is omitted. In `memory_timeline`, it instead filters results
54
+ to one session; omission shows the project timeline. Other tools may not accept
55
+ that argument.
54
56
 
55
- ## CLI
57
+ ## CLI and data
56
58
 
57
59
  ```bash
58
- eklavya install # install into Claude Code, runtime included
59
- eklavya uninstall [--purge] # remove it; --purge also deletes your history
60
- eklavya doctor # check the install
61
- eklavya config get
62
- eklavya config set quiz.enforced true [--project] # gate commits on one codebase
63
- eklavya config set focus concept # what it teaches
64
- eklavya config set focus learn --topic caching
65
- eklavya export-rules --out rules.md # the tutor pedagogy as Markdown
66
- eklavya dashboard [--port <n>] [--no-open] # your learning history, served locally and opened
67
- # in your browser (--no-open just prints the URL)
68
- eklavya db-path
60
+ npx eklavya doctor
61
+ npx eklavya memory status
62
+ npx eklavya config get
63
+ npx eklavya dashboard
64
+ npx eklavya db-path
69
65
  ```
70
66
 
67
+ The [CLI reference](https://eklavya-run.web.app/docs/cli/) lists all commands and
68
+ flags. [Configuration](https://eklavya-run.web.app/docs/configuration/) lists
69
+ defaults and scopes. The [memory guide](https://eklavya-run.web.app/docs/memory/)
70
+ covers retrieval, processing, providers, privacy and sync.
71
+
72
+ State is local under `~/.eklavya/`, including `knowledge.db` (SQLite, WAL).
73
+ `EKLAVYA_HOME` and `EKLAVYA_DB` override those paths. Questions and memory have
74
+ independent switches. External processing is optional and explicitly configured;
75
+ see [your data](https://eklavya-run.web.app/docs/your-data/).
76
+
71
77
  MIT.
package/dist/artifacts.js CHANGED
@@ -64,8 +64,8 @@ function inlineTokens() {
64
64
  return css.replace(/@import\s+url\([^)]*\)[^;]*;\s*/gi, '');
65
65
  }
66
66
  catch {
67
- // A build without the tokens still yields a readable page: the template's
68
- // own fallbacks cover the roles it names.
67
+ // A build without the tokens still writes the page; it renders unstyled
68
+ // until the tokens are restored, rather than failing the command.
69
69
  return '';
70
70
  }
71
71
  }
@@ -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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"}
@@ -20,7 +20,7 @@
20
20
  /*{{TOKENS}}*/
21
21
  </style>
22
22
  <style>
23
- :root{ --ok:var(--spot); --maxw:880px; }
23
+ :root{ --maxw:880px; }
24
24
 
25
25
  *{box-sizing:border-box}
26
26
  body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.65 var(--font-body)}
@@ -80,7 +80,7 @@
80
80
  border:1px solid var(--line-2);border-radius:0;background:var(--panel);color:var(--ink);
81
81
  font:500 12px var(--font-mono);letter-spacing:.06em;cursor:pointer}
82
82
  .actions button:hover{border-color:var(--spot);color:var(--spot)}
83
- .actions button:focus-visible{outline:none;box-shadow:0 0 0 2px var(--bg),0 0 0 4px var(--spot)}
83
+ .actions button:focus-visible{outline:none;box-shadow:var(--ring)}
84
84
 
85
85
  @media screen{
86
86
  body{background-image:linear-gradient(var(--grid) 1px,transparent 1px),
@@ -97,7 +97,7 @@
97
97
  the viewport, so it never opens off-screen on a phone or a short window. */
98
98
  .picker { position:fixed; z-index:40; min-width:220px; max-width:calc(100vw - 16px);
99
99
  background:var(--panel); border:1px solid var(--line-2); padding:var(--space-1) 0;
100
- box-shadow:0 12px 32px rgba(0,0,0,.28); overflow-y:auto; }
100
+ box-shadow:var(--shadow-lg); overflow-y:auto; }
101
101
  .picker[hidden] { display:none; }
102
102
  .picker__label { font-family:var(--font-mono); font-size:10px; letter-spacing:var(--tracking-caps);
103
103
  text-transform:uppercase; color:var(--faint); padding:var(--space-2) var(--space-4) var(--space-1); margin:0; }
@@ -105,7 +105,7 @@
105
105
  background:none; border:0; color:var(--dim); font:inherit; font-size:14px;
106
106
  padding:var(--space-2) var(--space-4); cursor:pointer; }
107
107
  .picker button:hover, .picker button:focus-visible { background:var(--mass); color:var(--ink); box-shadow:none; outline:none; }
108
- .picker button:focus-visible { box-shadow:inset 2px 0 0 var(--spot); }
108
+ .picker button:focus-visible { box-shadow:inset 2px 0 0 var(--spot); } /* inset: the picker scrolls, so an outset ring would clip */
109
109
  .picker button[aria-checked="true"] { color:var(--ink); }
110
110
  .picker button span { flex:1; }
111
111
  .picker .check { width:15px; height:15px; color:var(--spot); visibility:hidden; }
@@ -119,6 +119,8 @@
119
119
  transition:color var(--duration-fast) var(--ease), background var(--duration-fast) var(--ease); }
120
120
  nav.side__nav a:hover { color:var(--ink); background:var(--mass); }
121
121
  nav.side__nav a[aria-current="page"] { color:var(--ink); border-left-color:var(--spot); background:var(--mass); }
122
+ /* --faint misses 4.5:1 on --mass in ink */
123
+ nav.side__nav a[aria-current="page"] i, nav.side__nav a:hover i { color:var(--dim); }
122
124
  nav.side__nav a svg { width:17px; height:17px; flex:none; color:var(--faint); }
123
125
  nav.side__nav a[aria-current="page"] svg { color:var(--spot); }
124
126
  nav.side__nav a i { margin-left:auto; font-style:normal; font-family:var(--font-mono);
@@ -266,10 +268,10 @@
266
268
  .meter.mute > i { background:var(--faint-2); }
267
269
 
268
270
  .pill { display:inline-block; font-family:var(--font-mono); font-size:11px; padding:2px 8px;
269
- border-radius:var(--radius-pill); background:var(--mass); color:var(--dim); white-space:nowrap; }
271
+ border-radius:0; background:var(--mass); color:var(--dim); white-space:nowrap; }
270
272
  .pill.ok { background:var(--spot-soft); color:var(--spot); }
271
273
  .pill.due { color:var(--warning); }
272
- .pill.new { color:var(--faint); }
274
+ .pill.new { color:var(--dim); }
273
275
 
274
276
  .grade { font-family:var(--font-mono); font-size:11.5px; padding:2px 8px; white-space:nowrap;
275
277
  background:var(--mass); color:var(--dim); }
@@ -286,7 +288,7 @@
286
288
  .seg > span { display:block; height:100%; }
287
289
  .seg__key { display:flex; gap:var(--space-5); flex-wrap:wrap; margin-top:var(--space-3);
288
290
  font-size:12.5px; color:var(--dim); }
289
- .seg__key b { display:inline-block; width:9px; height:9px; margin-right:var(--space-2); }
291
+ .seg__key b { display:inline-block; width:9px; height:9px; margin-right:var(--space-2); border:1px solid var(--line-2); }
290
292
 
291
293
  /* ---------- charts ---------- */
292
294
  .legend { display:flex; gap:var(--space-5); font-size:12.5px; color:var(--dim);
@@ -298,6 +300,7 @@
298
300
  .axis { font-family:var(--font-mono); font-size:10px; fill:var(--faint); }
299
301
  .hit { fill:transparent; cursor:pointer; }
300
302
  .hit:hover + .col, .hit:focus-visible + .col { opacity:.72; }
303
+ .hit:focus-visible { outline:2px solid var(--spot); outline-offset:-2px; }
301
304
  .tip { position:absolute; pointer-events:none; opacity:0; transform:translate(-50%,-100%);
302
305
  background:var(--bg); border:1px solid var(--line-2); color:var(--ink);
303
306
  padding:var(--space-3); font-size:var(--text-xs); line-height:1.6; white-space:nowrap;
@@ -355,6 +358,66 @@
355
358
  summary.qa__meta::before { content:'▸'; color:var(--faint); }
356
359
  details[open] > summary.qa__meta::before { content:'▾'; color:var(--spot); }
357
360
  summary.qa__meta:focus-visible { outline:none; box-shadow:var(--ring); }
361
+
362
+ /* ---------- memory timeline ----------
363
+ One line per entry on a vertical rule. A row is a <details>: the title is
364
+ the summary, the snippet and the link to the full entry open under it, so
365
+ the list stays scannable and detail costs one click. */
366
+ .counts { margin:var(--space-2) 0 0; font-family:var(--font-mono); font-size:12px; color:var(--faint); }
367
+ .counts .warn { color:var(--warning); }
368
+ .tl { list-style:none; margin:var(--space-4) 0 0; padding:0; position:relative; }
369
+ .tl::before { content:''; position:absolute; left:67px; top:8px; bottom:8px; width:1px; background:var(--line-2); }
370
+ .tl-day { display:flex; align-items:baseline; gap:var(--space-3); padding:var(--space-5) 0 var(--space-2) 88px; position:relative; }
371
+ .tl-day:first-child { padding-top:0; }
372
+ .tl-day::before { content:''; position:absolute; left:62px; top:calc(var(--space-5) + 6px); width:11px; height:11px; background:var(--line-2); }
373
+ .tl-day:first-child::before { top:6px; }
374
+ .tl-day h2 { margin:0; font-family:var(--font-disp); font-size:var(--text-lg); font-weight:var(--weight-bold); letter-spacing:-.02em; }
375
+ .tl-day span { font-family:var(--font-mono); font-size:10.5px; letter-spacing:var(--tracking-caps); text-transform:uppercase; color:var(--spot); }
376
+ .tl-gap { padding:var(--space-2) 0 var(--space-2) 88px; font-family:var(--font-mono); font-size:11.5px; color:var(--faint); }
377
+ .tl-item > summary { list-style:none; cursor:pointer; display:grid; grid-template-columns:52px minmax(0,1fr) auto;
378
+ align-items:center; column-gap:36px; min-height:44px; padding-right:var(--space-3); position:relative; }
379
+ .tl-item > summary::-webkit-details-marker { display:none; }
380
+ .tl-item > summary::before { content:''; position:absolute; left:62px; top:50%; width:11px; height:11px; margin-top:-5.5px;
381
+ box-sizing:border-box; border-radius:50%; border:2px solid var(--dim); background:var(--bg); }
382
+ .tl-item > summary:hover { background:var(--panel); }
383
+ .tl-item > summary:focus-visible { outline:none; box-shadow:var(--ring); }
384
+ .tl-item[open] > summary::before { background:var(--spot); border-color:var(--spot); }
385
+ .tl-item[open] { background:var(--panel); }
386
+ .tl-time { font-family:var(--font-mono); font-size:12px; color:var(--faint); text-align:right; font-variant-numeric:tabular-nums; }
387
+ .tl-title { font-size:15px; font-weight:var(--weight-medium); color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
388
+ .tl-item[open] > summary .tl-title { font-weight:var(--weight-semibold); }
389
+ .tl-type { font-family:var(--font-mono); font-size:10.5px; letter-spacing:.12em; text-transform:uppercase; color:var(--faint); white-space:nowrap; }
390
+ .tl-body { padding:0 var(--space-4) var(--space-4) 88px; display:grid; gap:var(--space-3); }
391
+ .tl-body p { margin:0; color:var(--dim); font-size:13.5px; max-width:72ch; overflow-wrap:anywhere; }
392
+ .tl-meta { display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-2) var(--space-4);
393
+ font-family:var(--font-mono); font-size:11.5px; color:var(--faint); }
394
+ .tl-meta .link { margin-left:auto; font-family:var(--font-body); font-size:13.5px; font-weight:var(--weight-semibold); }
395
+ .tl-item.gone .tl-title { text-decoration:line-through; color:var(--faint); }
396
+ /* A session is a larger ring; its observations fold under it. */
397
+ .tl-session > summary::before { width:15px; height:15px; margin-top:-7.5px; left:60px; border-color:var(--spot); }
398
+ .tl-session[open] > summary::before { background:var(--bg); }
399
+ .tl-session[open] { background:none; }
400
+ .tl-session > summary .tl-title { font-weight:var(--weight-medium); }
401
+ .tl-session > summary .tl-type { color:var(--spot); }
402
+ .tl-kids { list-style:none; margin:0; padding:0; }
403
+ .tl-kids .tl-item > summary::before { left:64px; width:7px; height:7px; margin-top:-3.5px; border-color:var(--faint-2); }
404
+ .tl-kids .tl-item[open] > summary::before { border-color:var(--spot); }
405
+ .tl-kids .tl-title { font-size:14px; padding-left:var(--space-6); }
406
+ .tl-kids .tl-body { padding-left:112px; }
407
+ .tl-kids .tl-gap { padding-left:112px; }
408
+ #mwhen, #mtype, #mtag { max-width:100%; min-width:0; }
409
+ /* Narrow: the title gets the width and two lines; the type drops under it. */
410
+ @media (max-width:560px) {
411
+ .tl-item > summary { grid-template-columns:52px minmax(0,1fr); padding:var(--space-2) var(--space-2) var(--space-2) 0; }
412
+ .tl-item > summary::before { top:calc(var(--space-2) + 11px); margin-top:0; }
413
+ .tl-session > summary::before { top:calc(var(--space-2) + 9px); }
414
+ .tl-kids .tl-item > summary::before { top:calc(var(--space-2) + 13px); }
415
+ .tl-time { align-self:start; padding-top:3px; }
416
+ .tl-title { white-space:normal; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
417
+ .tl-type { grid-column:2; margin-top:2px; }
418
+ .tl-kids .tl-type { padding-left:var(--space-6); }
419
+ .tl-body, .tl-kids .tl-body, .tl-kids .tl-gap { padding-left:88px; }
420
+ }
358
421
  .raw { font-family:var(--font-mono); font-size:12px; line-height:1.55; white-space:pre-wrap;
359
422
  word-break:break-word; color:var(--dim); background:var(--mass); padding:var(--space-3);
360
423
  max-height:320px; overflow:auto; margin:var(--space-3) 0 0; }
@@ -393,7 +456,7 @@
393
456
  background:none; border:1px solid var(--line-2); color:var(--ink); width:34px; height:34px; cursor:pointer; }
394
457
  .side__close svg { width:16px; height:16px; }
395
458
  .side__brand { padding-right:64px; }
396
- .scrim { position:fixed; inset:0; z-index:25; background:rgba(0,0,0,.45); }
459
+ .scrim { position:fixed; inset:0; z-index:25; background:color-mix(in srgb, var(--bg) 70%, transparent); }
397
460
  .scrim.is-open { display:block; }
398
461
  body.drawer-open { overflow:hidden; }
399
462
  main { padding:var(--space-6) var(--space-5) var(--space-16); }
@@ -1119,7 +1182,7 @@ function viewLearningDashboard() {
1119
1182
  ${card('Where you stand', 'The catalogue is a library, not a to-do list — untouched concepts are not debt.',
1120
1183
  segbar([
1121
1184
  { label:'Mastered', n:mastered.length, color:'var(--spot)' },
1122
- { label:'Learning', n:seen.length - mastered.length, color:'var(--warning)' },
1185
+ { label:'Learning', n:seen.length - mastered.length, color:'color-mix(in srgb, var(--spot) 45%, var(--mass))' },
1123
1186
  { label:'Not asked', n:Math.max(0, scoped.length - seen.length), color:'var(--mass)' },
1124
1187
  ]) + `<p style="margin:var(--space-4) 0 0;color:var(--faint);font-size:13.5px">
1125
1188
  A concept is mastered at score 0.7 with two or more passes. Score decays about
@@ -1699,7 +1762,7 @@ function viewDomain(name) {
1699
1762
  </div>`,
1700
1763
  card(null, null, segbar([
1701
1764
  { label:'Mastered', n:seen.filter((c) => c.mastered).length, color:'var(--spot)' },
1702
- { label:'Learning', n:seen.filter((c) => !c.mastered).length, color:'var(--warning)' },
1765
+ { label:'Learning', n:seen.filter((c) => !c.mastered).length, color:'color-mix(in srgb, var(--spot) 45%, var(--mass))' },
1703
1766
  { label:'Not asked', n:all.length - seen.length, color:'var(--mass)' },
1704
1767
  ])),
1705
1768
  card(null, null, table([{t:'Concept'},{t:'Tier'},{t:'Score'},{t:'Asked',n:1},{t:'State'},{t:'From your code'}], rows)
@@ -1725,24 +1788,72 @@ function memoryQuery(type) {
1725
1788
  return p.toString();
1726
1789
  }
1727
1790
 
1791
+ const dayName = (d) => d.toLocaleDateString(undefined, { weekday:'short', day:'numeric', month:'short' });
1792
+ const clock = (d) => d.toLocaleTimeString(undefined, { hour:'2-digit', minute:'2-digit' });
1793
+
1794
+ /** One entry on the timeline: a line that opens to its snippet and a link to the full entry. */
1795
+ function tlEntry(e) {
1796
+ const d = parse(e.occurred_at);
1797
+ const more = e.narrative_length > String(e.snippet ?? '').length;
1798
+ const gone = e.deleted_at || e.superseded_by;
1799
+ return `<li><details class="tl-item${gone ? ' gone' : ''}" data-entry="${esc(e.id)}">
1800
+ <summary><span class="tl-time">${d ? esc(clock(d)) : '—'}</span>
1801
+ <span class="tl-title">${esc(e.title)}</span><span class="tl-type">${esc(e.type ?? e.kind)}</span></summary>
1802
+ <div class="tl-body">
1803
+ ${e.snippet ? `<p>${esc(e.snippet)}${more ? '…' : ''}</p>` : ''}
1804
+ <div class="tl-meta"><span>${esc(pname(e.project))}</span><span>${plural(e.event_count, 'evidence event')}</span>
1805
+ ${entryMark(e)}<a class="link" data-go="${esc(MH('entry', e.id))}">Open entry →</a></div>
1806
+ </div></details></li>`;
1807
+ }
1808
+
1809
+ /**
1810
+ * The page's entries as a timeline: grouped by local day, and an entry that
1811
+ * shares a session with a session summary on this page folds under that
1812
+ * summary. Grouping is per page — a session whose summary sits on the next page
1813
+ * shows its observations as ordinary lines here — because the server pages
1814
+ * entries, not sessions.
1815
+ */
1728
1816
  function drawMemory(d) {
1729
1817
  T.memory.page = d.page; // the server clamps a page number a filter shrank
1730
- const rows = d.rows.map((e) => {
1731
- const gone = e.deleted_at || e.superseded_by;
1732
- const more = e.narrative_length > String(e.snippet ?? '').length;
1733
- return `<tr class="row${gone ? ' gone' : ''}" data-go="${esc(MH('entry', e.id))}">
1734
- <td class="slug" style="white-space:nowrap">${esc(stamp(e.occurred_at))}</td>
1735
- <td style="min-width:280px"><span class="title">${esc(e.title)}</span>
1736
- <div class="ctx">${esc(e.snippet ?? '')}${more ? '…' : ''}</div></td>
1737
- <td><span class="pill">${esc(e.type ?? e.kind)}</span></td>
1738
- <td style="min-width:120px"><div class="tags">${tagPills(e.tags)}</div></td>
1739
- <td class="slug">${esc(pname(e.project))}</td>
1740
- <td class="num">${e.event_count}</td>
1741
- <td>${entryMark(e)}</td>
1742
- </tr>`;
1743
- });
1744
- return table([{t:'When'},{t:'Observation'},{t:'Type'},{t:'Tags'},{t:'Project'},{t:'Evidence',n:1},{t:''}], rows)
1745
- + pager('memory', d.total, d.page, d.per);
1818
+ if (!d.rows.length) return '<p class="empty">Nothing in memory matches these filters.</p>';
1819
+ const summaries = new Map(d.rows.filter((e) => e.kind === 'session_summary' && e.session_id).map((e) => [e.session_id, e]));
1820
+ const kids = new Map();
1821
+ for (const e of d.rows) {
1822
+ if (e.kind === 'session_summary' || !summaries.has(e.session_id)) continue;
1823
+ if (!kids.has(e.session_id)) kids.set(e.session_id, []);
1824
+ kids.get(e.session_id).push(e);
1825
+ }
1826
+ const now = new Date();
1827
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
1828
+ const out = [];
1829
+ let last = null;
1830
+ for (const e of d.rows) {
1831
+ if (e.kind !== 'session_summary' && summaries.has(e.session_id)) continue; // drawn under its summary
1832
+ const at = parse(e.occurred_at);
1833
+ const day = at ? new Date(at.getFullYear(), at.getMonth(), at.getDate()) : null;
1834
+ if (day && (!last || day.getTime() !== last.getTime())) {
1835
+ if (last) {
1836
+ const gap = Math.round((last - day) / DAY_MS) - 1;
1837
+ const after = new Date(day.getFullYear(), day.getMonth(), day.getDate() + 1);
1838
+ const before = new Date(last.getFullYear(), last.getMonth(), last.getDate() - 1);
1839
+ if (gap === 1) out.push(`<li class="tl-gap">Nothing on ${esc(dayName(after))}</li>`);
1840
+ if (gap > 1) out.push(`<li class="tl-gap">Nothing from ${esc(dayName(after))} to ${esc(dayName(before))}</li>`);
1841
+ }
1842
+ const note = day.getTime() === today ? 'Today' : Math.round((today - day.getTime()) / DAY_MS) === 1 ? 'Yesterday' : '';
1843
+ out.push(`<li class="tl-day"><h2>${esc(dayName(day))}</h2>${note ? `<span>${note}</span>` : ''}</li>`);
1844
+ last = day;
1845
+ }
1846
+ const inner = kids.get(e.session_id);
1847
+ if (e.kind === 'session_summary' && inner) {
1848
+ out.push(`<li><details class="tl-item tl-session">
1849
+ <summary><span class="tl-time">${at ? esc(clock(at)) : '—'}</span>
1850
+ <span class="tl-title">${esc(e.title.replace(/^Session:\s*/, ''))}</span>
1851
+ <span class="tl-type">Session · ${plural(inner.length, 'observation')}</span></summary>
1852
+ <ol class="tl-kids">${inner.map(tlEntry).join('')}
1853
+ <li class="tl-gap"><a class="link" data-go="${esc(MH('entry', e.id))}">The session summary →</a></li></ol></details></li>`);
1854
+ } else out.push(tlEntry(e));
1855
+ }
1856
+ return `<ol class="tl" aria-label="Timeline">${out.join('')}</ol>` + pager('memory', d.total, d.page, d.per);
1746
1857
  }
1747
1858
 
1748
1859
  /**
@@ -1751,25 +1862,41 @@ function drawMemory(d) {
1751
1862
  * selector; indexing, reuse, delivery and assessment are only aggregated for
1752
1863
  * the whole database, and say so rather than pretend to be this project's.
1753
1864
  */
1754
- function memTiles() {
1865
+ function memCounts() {
1755
1866
  const M = S.memory;
1756
1867
  const scoped = PROJECT !== null;
1757
1868
  const inv = projectsInScope();
1758
1869
  const sum = (f) => inv.reduce((a, p) => a + f(p), 0);
1759
- const events = scoped ? sum((p) => p.memory.events) : M.captured;
1760
- const pending = scoped ? sum((p) => p.memory.pending) : M.pending;
1761
- const entries = scoped ? sum((p) => p.memory.entries) : M.entries;
1870
+ return {
1871
+ scoped,
1872
+ events: scoped ? sum((p) => p.memory.events) : M.captured,
1873
+ pending: scoped ? sum((p) => p.memory.pending) : M.pending,
1874
+ entries: scoped ? sum((p) => p.memory.entries) : M.entries,
1875
+ sessions: scoped ? sum((p) => p.memory.sessions) : INV.projects.reduce((a, p) => a + p.memory.sessions, 0),
1876
+ };
1877
+ }
1878
+
1879
+ function memTiles() {
1880
+ const M = S.memory;
1881
+ const { scoped, events, pending, entries, sessions } = memCounts();
1762
1882
  const everywhere = scoped ? 'all projects · ' : '';
1763
1883
  return tiles([
1764
1884
  ['Observations', entries, scoped ? `live here · ${M.indexed} indexed across all projects`
1765
1885
  : `${M.entries_total} written · ${M.superseded} superseded · ${M.deleted} deleted · ${M.indexed} indexed`, '', MH('timeline')],
1766
1886
  ['Evidence', events, `${events - pending} distilled · ${pending} still pending`, pending ? 'warn' : '', MH('health')],
1767
- ['Sessions', scoped ? sum((p) => p.memory.sessions) : INV.projects.reduce((a, p) => a + p.memory.sessions, 0), 'that captured or remembered', '', MH('sessions')],
1887
+ ['Sessions', sessions, 'that captured or remembered', '', MH('sessions')],
1768
1888
  ['Reused', M.reused, `${everywhere}${M.exposed} actually delivered`, '', MH('reuse')],
1769
1889
  ['Assessed', M.assessed, everywhere + 'evidence concepts you were asked about', '', LH('concepts')],
1770
1890
  ]);
1771
1891
  }
1772
1892
 
1893
+ /** `YYYY-MM-DD` for n days before today, local — what a date input holds. */
1894
+ const daysAgo = (n) => {
1895
+ const d = new Date(); d.setDate(d.getDate() - n);
1896
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
1897
+ };
1898
+ const WHEN = { today:0, '7d':6, '30d':29 };
1899
+
1773
1900
  function viewMemory(param) {
1774
1901
  const t = T.memory;
1775
1902
  const M = S.memory;
@@ -1782,35 +1909,41 @@ function viewMemory(param) {
1782
1909
  t.until = CUR.query.get('to') || '';
1783
1910
  fill('mem-rows', '/api/memory?' + memoryQuery(type), drawMemory);
1784
1911
 
1785
- const chip = (k, label, n) =>
1786
- `<button class="chip" data-mtype="${esc(k)}" aria-pressed="${type === k}">${esc(label)}${n === undefined ? '' : ' ' + n}</button>`;
1787
- const typeChips = types.map((x) => chip(x.type, x.type, x.n)).join('')
1788
- + (type !== 'all' && !types.some((x) => x.type === type) ? chip(type, type, 0) : '');
1912
+ // A preset is only a `from` date, so the URL stays the one date format and a
1913
+ // shared link reads the same; anything else is a custom range.
1914
+ const when = !t.since && !t.until ? 'any'
1915
+ : !t.until && Object.keys(WHEN).find((k) => daysAgo(WHEN[k]) === t.since) || 'custom';
1916
+ const opt = (v, label, on) => `<option value="${esc(v)}"${on ? ' selected' : ''}>${esc(label)}</option>`;
1917
+ const typeOpts = opt('all', 'All types', type === 'all')
1918
+ + types.map((x) => opt(x.type, `${x.type} (${x.n})`, type === x.type)).join('')
1919
+ + (type !== 'all' && !types.some((x) => x.type === type) ? opt(type, `${type} (0)`, true) : '');
1920
+ const { entries, sessions, pending } = memCounts();
1789
1921
 
1790
1922
  return [
1791
1923
  `<div>
1792
1924
  <h1 class="page__title">Timeline</h1>
1793
- <p class="page__sub">What your sessions actually did, distilled into observations and kept with the raw
1794
- evidence behind each one. Corrections supersede rather than overwrite and deletions are marked, so what
1795
- you see is the audit trail and not a tidied version of it.</p>
1925
+ <p class="counts">${plural(entries, 'observation')} · ${plural(sessions, 'session')}${pending
1926
+ ? ` · <a class="link warn" data-go="${esc(MH('health'))}">${pending} evidence pending</a>` : ''}</p>
1796
1927
  </div>`,
1797
1928
 
1798
- memTiles(),
1799
-
1800
- card(null, null,
1801
- `<div class="filters">
1802
- <div class="grow"><input type="search" id="mq" value="${esc(t.q)}" placeholder="Search titles, narrative, facts and files…" aria-label="Search memory"></div>
1929
+ `<div>
1930
+ <div class="filters">
1931
+ <div class="grow"><input type="search" id="mq" value="${esc(t.q)}" placeholder="Search memory" aria-label="Search memory"></div>
1932
+ <select id="mtype" aria-label="Type">${typeOpts}</select>
1803
1933
  <select id="mtag" aria-label="Tag">
1804
1934
  <option value="all">All tags</option>
1805
- ${(M.tags ?? []).map((x) => `<option value="${esc(x.tag)}"${t.tag === x.tag ? ' selected' : ''}>${esc(x.tag)} (${x.n})</option>`).join('')}
1935
+ ${(M.tags ?? []).map((x) => opt(x.tag, `${x.tag} (${x.n})`, t.tag === x.tag)).join('')}
1806
1936
  </select>
1807
- <input type="date" id="mfrom" value="${esc(t.since)}" aria-label="From date">
1808
- <input type="date" id="mto" value="${esc(t.until)}" aria-label="To date">
1809
- </div>
1810
- <div class="chips" id="mtypes">
1811
- ${chip('all', 'All')}${typeChips}
1937
+ <select id="mwhen" aria-label="When">
1938
+ ${opt('any', 'Any time', when === 'any')}${opt('today', 'Today', when === 'today')}
1939
+ ${opt('7d', 'Last 7 days', when === '7d')}${opt('30d', 'Last 30 days', when === '30d')}
1940
+ ${opt('custom', 'Custom range', when === 'custom')}
1941
+ </select>
1942
+ ${when === 'custom' ? `<input type="date" id="mfrom" value="${esc(t.since)}" aria-label="From date">
1943
+ <input type="date" id="mto" value="${esc(t.until)}" aria-label="To date">` : ''}
1812
1944
  </div>
1813
- <div style="margin-top:var(--space-4)" id="mem-rows"><p class="empty">Loading the timeline…</p></div>`),
1945
+ <div id="mem-rows"><p class="empty">Loading the timeline…</p></div>
1946
+ </div>`,
1814
1947
  ].join('');
1815
1948
  }
1816
1949
 
@@ -2497,7 +2630,7 @@ function renderNav(wf, active) {
2497
2630
  const id = `grp-${wf}-${g.id}`;
2498
2631
  return `<div class="nav__group">
2499
2632
  <button type="button" class="nav__toggle" id="tog-${wf}-${g.id}" data-group="${g.id}" aria-expanded="${open}" aria-controls="${id}">
2500
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>${esc(g.label)}
2633
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>${esc(g.label)}
2501
2634
  </button>
2502
2635
  <div class="nav__items" id="${id}" role="group" aria-label="${esc(g.label)}"${open ? '' : ' hidden'}>${g.items.map(item).join('')}</div>
2503
2636
  </div>`;
@@ -2779,13 +2912,6 @@ function onActivate(e) {
2779
2912
  }
2780
2913
  const tab = e.target.closest('[data-tab]');
2781
2914
  if (tab) { T.review.page = 1; return goTo(LH('review', tab.dataset.tab === 'due' ? null : tab.dataset.tab)); }
2782
- const mtype = e.target.closest('[data-mtype]');
2783
- if (mtype) {
2784
- T.memory.page = 1;
2785
- const q = CUR.query;
2786
- return goTo(MH('timeline', mtype.dataset.mtype === 'all' ? null : mtype.dataset.mtype,
2787
- { tag: q.get('tag'), from: q.get('from'), to: q.get('to') }));
2788
- }
2789
2915
  const go = e.target.closest('[data-go]');
2790
2916
  if (go) return goTo(go.dataset.go);
2791
2917
  }
@@ -2795,7 +2921,7 @@ $('view').addEventListener('click', onActivate);
2795
2921
  // are divs and table rows, so Enter and Space have to be wired by hand.
2796
2922
  $('view').addEventListener('keydown', (e) => {
2797
2923
  if (e.key !== 'Enter' && e.key !== ' ') return;
2798
- if (!e.target.closest('[data-go],[data-state],[data-tab],[data-page],[data-mtype]')) return;
2924
+ if (!e.target.closest('[data-go],[data-state],[data-tab],[data-page]')) return;
2799
2925
  e.preventDefault();
2800
2926
  onActivate(e);
2801
2927
  });
@@ -2822,6 +2948,19 @@ $('view').addEventListener('change', (e) => {
2822
2948
  if (e.target.id === 'dom') { T.concepts.page = 1; setQuery({ domain: e.target.value }); }
2823
2949
  if (e.target.id === 'sort') { T.concepts.sort = e.target.value; T.concepts.page = 1; render(); }
2824
2950
  if (e.target.id === 'mtag') { T.memory.page = 1; setQuery({ tag: e.target.value }); }
2951
+ if (e.target.id === 'mtype') {
2952
+ T.memory.page = 1;
2953
+ const q = CUR.query;
2954
+ goTo(MH('timeline', e.target.value === 'all' ? null : e.target.value,
2955
+ { tag: q.get('tag'), from: q.get('from'), to: q.get('to') }));
2956
+ }
2957
+ if (e.target.id === 'mwhen') {
2958
+ T.memory.page = 1;
2959
+ const v = e.target.value;
2960
+ // Custom keeps the range's start, closes it at today and reveals the date boxes.
2961
+ if (v === 'custom') setQuery({ from: CUR.query.get('from') || daysAgo(6), to: daysAgo(0) });
2962
+ else setQuery({ from: v === 'any' ? null : daysAgo(WHEN[v]), to: null });
2963
+ }
2825
2964
  if (e.target.id === 'mfrom') { T.memory.page = 1; setQuery({ from: e.target.value }); }
2826
2965
  if (e.target.id === 'mto') { T.memory.page = 1; setQuery({ to: e.target.value }); }
2827
2966
  });
@@ -40,10 +40,9 @@
40
40
  --vd-900: #052E2B;
41
41
 
42
42
  /* ---------- SEMANTIC (real errors and warnings only) ----------
43
- Kept off the accent hue so state never reads as branding. Both are
44
- redefined per ground below: an orange that carries on ink is 2.3:1 on
45
- cream, which fails as text and as a chart fill alike. */
46
- --error: #E5484D;
43
+ Kept off the accent hue so state never reads as branding. --warning and
44
+ --error are redefined per ground below: an orange that carries on ink is
45
+ 2.3:1 on cream, which fails as text and as a chart fill alike. */
47
46
  --info: #4A9EFF;
48
47
 
49
48
  /* ---------- TYPE FAMILIES ----------
@@ -144,6 +143,7 @@
144
143
  --glow-low: rgba(25,150,136,.10);
145
144
  --shot-op: .28; /* the arrow scene, as ambient art */
146
145
  --warning: #E8920C;
146
+ --error: #FF6166; /* 4.82:1 on its own 12% tint over --panel */
147
147
  --code-bg: var(--mass); /* the well behind a code block */
148
148
  --shadow-lg: 0 12px 28px rgba(0,0,0,.45);
149
149
  --ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--spot);
@@ -173,6 +173,7 @@
173
173
  --glow-low: rgba(14,110,102,.05);
174
174
  --shot-op: .5;
175
175
  --warning: #7A4B00; /* 5.21:1 on --mass */
176
+ --error: #A8201A; /* 4.70:1 on its own 12% tint over --panel */
176
177
  /* A syntax theme's dimmest token (comments) lands at 3.9:1 on --mass. The
177
178
  well is lightened rather than the theme repainted, which lifts every
178
179
  token at once; the frame's border is what still reads it as a block. */
@@ -197,4 +198,5 @@
197
198
  --term-spot: var(--vd-300);
198
199
  --term-cmd: #8ECF7A;
199
200
  --term-str: #E6C060;
201
+ --term-err: #E5484D;
200
202
  }