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 +48 -42
- package/dist/artifacts.js +2 -2
- package/dist/artifacts.js.map +1 -1
- package/dist/assets/artifact-template.html +2 -2
- package/dist/assets/dashboard.html +196 -57
- package/dist/assets/tokens.css +6 -4
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/agents/explainer.md +5 -3
- package/dist/plugin/cli/CLAUDE.md +60 -108
- package/dist/plugin/hooks/CLAUDE.md +118 -369
- package/dist/plugin/skills/CLAUDE.md +106 -298
- package/dist/user-skill/eklavya-artifacts/SKILL.md +23 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Eklavya
|
|
2
2
|
|
|
3
|
-
[Eklavya](https://github.com/ProjectAJ14/eklavya)
|
|
4
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
##
|
|
24
|
+
## Standalone MCP connection
|
|
22
25
|
|
|
23
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
40
|
+
## Available tools
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
| Tool | Purpose |
|
|
42
|
+
| Purpose | Tools |
|
|
43
43
|
|---|---|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
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
|
-
|
|
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
|
|
59
|
-
eklavya
|
|
60
|
-
eklavya
|
|
61
|
-
eklavya
|
|
62
|
-
eklavya
|
|
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
|
|
68
|
-
//
|
|
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
|
}
|
package/dist/artifacts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEvD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D,4EAA4E;AAC5E,MAAM,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC;AAiB7B,kGAAkG;AAClG,MAAM,UAAU,eAAe,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IACzD,MAAM,EAAE,QAAQ,EAAE,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,QAAQ;QAAE,OAAO,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC5C,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,KAAa;IACjC,MAAM,CAAC,GAAG,KAAK;SACZ,SAAS,CAAC,MAAM,CAAC;SACjB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;SACrB,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;SACZ,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACtB,OAAO,CAAC,IAAI,UAAU,CAAC;AACzB,CAAC;AAED,MAAM,OAAO,GAAG,CAAC,CAAS,EAAE,EAAE,CAC5B,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC;AAE9G,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;AAC9C,CAAC;AAED,6FAA6F;AAC7F,SAAS,YAAY;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC;QAC7D,OAAO,GAAG,CAAC,OAAO,CAAC,mCAAmC,EAAE,EAAE,CAAC,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,
|
|
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{ --
|
|
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:
|
|
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:
|
|
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:
|
|
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(--
|
|
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:
|
|
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(--
|
|
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(--
|
|
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
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
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
|
|
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
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
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',
|
|
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
|
-
|
|
1786
|
-
|
|
1787
|
-
const
|
|
1788
|
-
|
|
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="
|
|
1794
|
-
|
|
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
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
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) =>
|
|
1935
|
+
${(M.tags ?? []).map((x) => opt(x.tag, `${x.tag} (${x.n})`, t.tag === x.tag)).join('')}
|
|
1806
1936
|
</select>
|
|
1807
|
-
<
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
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
|
|
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]
|
|
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
|
});
|
package/dist/assets/tokens.css
CHANGED
|
@@ -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.
|
|
44
|
-
redefined per ground below: an orange that carries on ink is
|
|
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
|
}
|