dsh-loci 3.0.0 → 3.1.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/CHANGELOG.md +80 -0
- package/client.js +73 -8
- package/package.json +1 -1
- package/src/host/index.js +1 -1
- package/src/mcp/store-facade.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,86 @@ All notable changes to this project are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the project follows
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html) from 1.0.0.
|
|
6
6
|
|
|
7
|
+
## [3.1.0] — 2026-11-15
|
|
8
|
+
|
|
9
|
+
Four things from looking at the live sidebar and panel.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **The sidebar icon now has the same weight as the menu it sits in.** Native
|
|
14
|
+
`sidebar.panellist` entries receive `{ size, active }`; this one used `size` correctly but
|
|
15
|
+
drew inside 4–20 of a 24 viewBox with a small off-centre dot, so beside the shipped icons
|
|
16
|
+
it read thinner and smaller. Redrawn as a symmetric **layers** glyph spanning 3–21 at
|
|
17
|
+
stroke 1.8 — the optical weight of its neighbours, and a shape that says layered memory.
|
|
18
|
+
|
|
19
|
+
- **The panel is a readable column, not edge-to-edge text.** The scroll container's
|
|
20
|
+
horizontal padding became `max(16px, calc((100% - 860px) / 2))`, so rows sit in a centred
|
|
21
|
+
measure on a wide window without touching any tab component.
|
|
22
|
+
|
|
23
|
+
- **"N item(s) waiting for you in Review" is now a way in.** It was a sentence telling a
|
|
24
|
+
person to go somewhere else and find the tab. It is a button: it opens the panel already
|
|
25
|
+
on Review. The card and the panel mount separately, so the request travels through module
|
|
26
|
+
scope and is consumed once.
|
|
27
|
+
|
|
28
|
+
- **The card says what it knows, not only how much.** Commit `4065c98` (2026-11-13) recorded
|
|
29
|
+
the brief for this surface: *"not a dashboard … read the card and think 'it got that, it
|
|
30
|
+
recorded that, we agreed on that'"*, with one short paragraph of what the agent
|
|
31
|
+
understands in this project and pending work as one quiet line, never a number. 3.0.0
|
|
32
|
+
replaced that paragraph with counters. The counters stay — they were asked for and they
|
|
33
|
+
are useful — and one line of content goes back under them: a `Latest` row showing the most
|
|
34
|
+
recently trusted entry, from `GET /api/loci/knowledge?limit=1`.
|
|
35
|
+
|
|
36
|
+
### Not changed
|
|
37
|
+
|
|
38
|
+
- `docs/PRIOR-ART.md` still records that the card should be scoped to **this project's read
|
|
39
|
+
set** rather than every scope. It currently is not: `/stats` and `/knowledge` called
|
|
40
|
+
without parameters enumerate the whole store, and the sidebar card has no access to the
|
|
41
|
+
session's cwd. Scoping the card is a real open item, not something to assert.
|
|
42
|
+
|
|
43
|
+
## [3.0.1] — 2026-11-15
|
|
44
|
+
|
|
45
|
+
The 3.0.0 card was correct markup with no stylesheet, and the screenshot showed it.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- **The card was never styled outside the panel.** Every `.loci-*` rule lives in one CSS
|
|
50
|
+
template string that only `MemoryPanel` and `SettingsSection` render as a `<style>`
|
|
51
|
+
element. In the sidebar, with the panel closed, no `<style>` existed — so the card's DOM
|
|
52
|
+
was right and its layout was simply absent. That is what "bare text in the sidebar"
|
|
53
|
+
looked like, and it predates 3.0.0: the first screenshot showed it too. The card now
|
|
54
|
+
renders the stylesheet itself, and so does its rail variant.
|
|
55
|
+
|
|
56
|
+
- **`card.pending` was defined twice.** The old value was the sentence
|
|
57
|
+
`'{n} still need your review'`; 3.0.0 added a bare `'Pending'` label for the metric row,
|
|
58
|
+
and the later definition won — so the card printed the sentence with an unsubstituted
|
|
59
|
+
`{n}`. The sentence is now `card.pendingSentence` and the label stays `Pending`.
|
|
60
|
+
|
|
61
|
+
- **The version chip rendered as `v3. 0. 0`.** The chip used
|
|
62
|
+
`ui-monospace, SFMono-Regular, Menlo, monospace`, which on Windows falls back to a
|
|
63
|
+
monospace face whose period is centred in a wide advance; at 8.5px that reads as gaps
|
|
64
|
+
between the digits. Measured with `getComputedStyle`: `letter-spacing: normal`,
|
|
65
|
+
`word-spacing: 0`, width 25.50 — the spacing was correct, the **glyph** was not. The chip
|
|
66
|
+
now inherits the UI font with tabular figures, at 9px.
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- **`scripts/shot-card.mjs`** renders the sidebar card to a PNG using the stylesheet
|
|
71
|
+
extracted from `client.js` and the DOM the component produces, through headless Edge. It
|
|
72
|
+
cannot catch a logic mistake, only a drawing one — but a drawing mistake is exactly what
|
|
73
|
+
shipped twice, and a screenshot is the only review that shows it.
|
|
74
|
+
|
|
75
|
+
### Not added
|
|
76
|
+
|
|
77
|
+
Four guard files from the previous draft (a separate client-i18n test, an English-only test,
|
|
78
|
+
a CSS-shape assertion and a manifest check) were collapsed into **one** test file and one
|
|
79
|
+
manifest assertion. Guards that outnumber the behaviour they protect get deleted by the next
|
|
80
|
+
person, which is worse than not having them.
|
|
81
|
+
|
|
82
|
+
### Corrected in the record
|
|
83
|
+
|
|
84
|
+
The 3.0.0 log entry said the card had been verified. It had not been looked at. The entry
|
|
85
|
+
above records what the screenshot showed.
|
|
86
|
+
|
|
7
87
|
## [3.0.0] — 2026-11-15
|
|
8
88
|
|
|
9
89
|
The sidebar card was not a card, and the plugin had a second language it did not need.
|
package/client.js
CHANGED
|
@@ -27,6 +27,9 @@ window.__ModuleLoader__.load({
|
|
|
27
27
|
const API = '/api/loci';
|
|
28
28
|
const ENTRY_ID = 'dsh-loci';
|
|
29
29
|
const PANEL_ID = 'loci';
|
|
30
|
+
// The card and the panel mount separately, so a request to open a specific tab has
|
|
31
|
+
// to travel through module scope. The panel consumes it once, then clears it.
|
|
32
|
+
let requestedTab;
|
|
30
33
|
|
|
31
34
|
const EN = {
|
|
32
35
|
'meta.title': 'Loci memory palace',
|
|
@@ -44,8 +47,9 @@ window.__ModuleLoader__.load({
|
|
|
44
47
|
'card.opened': 'Opened',
|
|
45
48
|
'card.dead': 'Dead',
|
|
46
49
|
'card.pendingHint': '{n} item(s) waiting for you in Review.',
|
|
50
|
+
'card.latest': 'Latest',
|
|
47
51
|
'card.updated': 'updated',
|
|
48
|
-
'card.
|
|
52
|
+
'card.pendingSentence': '{n} still need your review',
|
|
49
53
|
'card.pinned': 'pinned',
|
|
50
54
|
'card.open': 'Open memory',
|
|
51
55
|
'card.check': 'Check for updates',
|
|
@@ -172,7 +176,7 @@ window.__ModuleLoader__.load({
|
|
|
172
176
|
.loci-card-root.is-rail { flex: 0 0 auto; padding: 6px; align-items: center; justify-content: center; }
|
|
173
177
|
.loci-card-head { display: flex; align-items: center; gap: 6px; min-width: 0; width: 100%; }
|
|
174
178
|
.loci-card-title { font-size: 13px; font-weight: 700; letter-spacing: -.01em; white-space: nowrap; }
|
|
175
|
-
.loci-card-ver { flex: 0 0 auto; margin: 0; padding: 0; border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font-size:
|
|
179
|
+
.loci-card-ver { flex: 0 0 auto; margin: 0; padding: 0; border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font-size: 9px; line-height: 1.2; font-family: inherit; font-variant-numeric: tabular-nums; cursor: pointer; white-space: nowrap; }
|
|
176
180
|
.loci-card-ver:hover { color: var(--dsw-alias-label-primary); }
|
|
177
181
|
.loci-card-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--dsw-alias-brand-primary); flex: 0 0 auto; }
|
|
178
182
|
.loci-card-dot.is-warn { background: var(--dsw-alias-state-warn-primary); }
|
|
@@ -194,6 +198,11 @@ window.__ModuleLoader__.load({
|
|
|
194
198
|
.loci-card-foot-grow { flex: 1 1 auto; }
|
|
195
199
|
.loci-card-empty { font-size: 9.5px; color: var(--dsw-alias-label-secondary); }
|
|
196
200
|
.loci-card-hint { font-size: 9px; color: var(--dsw-alias-label-secondary); }
|
|
201
|
+
.loci-card-known { display: flex; gap: 5px; align-items: baseline; min-width: 0; }
|
|
202
|
+
.loci-card-known-tag { flex: 0 0 auto; font-size: 8px; letter-spacing: .05em; text-transform: uppercase; color: var(--dsw-alias-label-secondary); }
|
|
203
|
+
.loci-card-known-text { min-width: 0; font-size: 9.5px; color: var(--dsw-alias-label-primary); overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
|
|
204
|
+
.loci-card-hint-action { align-self: flex-start; margin: 0; padding: 2px 6px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); font: inherit; font-size: 9px; line-height: 1.2; cursor: pointer; text-align: left; }
|
|
205
|
+
.loci-card-hint-action:hover { border-color: var(--dsw-alias-brand-primary); color: var(--dsw-alias-brand-primary); }
|
|
197
206
|
.loci-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--dsw-alias-brand-primary); flex: 0 0 auto; }
|
|
198
207
|
|
|
199
208
|
.loci-bar { display: flex; align-items: center; gap: 12px; padding: 10px 16px; border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
@@ -203,7 +212,7 @@ window.__ModuleLoader__.load({
|
|
|
203
212
|
.loci-tab:hover { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
|
|
204
213
|
.loci-tab.is-active { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); font-weight: 600; }
|
|
205
214
|
.loci-grow { flex: 1 1 auto; }
|
|
206
|
-
.loci-body { flex: 1 1 auto; min-height: 0; overflow: auto; padding:
|
|
215
|
+
.loci-body { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 14px max(16px, calc((100% - 860px) / 2)) 28px; }
|
|
207
216
|
.loci-btn { border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); border-radius: 6px; padding: 3px 9px; font: inherit; cursor: pointer; }
|
|
208
217
|
.loci-btn:hover { border-color: var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-2); }
|
|
209
218
|
.loci-btn:disabled { opacity: .5; cursor: default; }
|
|
@@ -438,6 +447,7 @@ window.__ModuleLoader__.load({
|
|
|
438
447
|
// read as bare text inside the sidebar rather than as a card — and the audit's own
|
|
439
448
|
// "no dashboard" objection was about refusing to interrupt, not about hiding data.
|
|
440
449
|
const stats = useJson('/stats', { intervalMs: 30_000 });
|
|
450
|
+
const knowledge = useJson('/knowledge?limit=1', { intervalMs: 30_000 });
|
|
441
451
|
const config = useJson('/config');
|
|
442
452
|
const update = useJson('/update');
|
|
443
453
|
const [checking, setChecking] = useState(false);
|
|
@@ -468,6 +478,10 @@ window.__ModuleLoader__.load({
|
|
|
468
478
|
const injected = usage.reduce((total, row) => total + Number(row.injections ?? 0), 0);
|
|
469
479
|
const opened = usage.reduce((total, row) => total + Number(row.reads ?? 0), 0);
|
|
470
480
|
const dead = usage.filter((row) => Number(row.injections ?? 0) > 0 && Number(row.reads ?? 0) === 0).length;
|
|
481
|
+
// What the agent currently understands, newest first. Numbers answer "how much";
|
|
482
|
+
// this answers "what".
|
|
483
|
+
const top = knowledge.data?.items?.[0];
|
|
484
|
+
const latest = typeof top?.title === 'string' && top.title.length > 0 ? top.title : null;
|
|
471
485
|
|
|
472
486
|
if (!wide) {
|
|
473
487
|
return h(
|
|
@@ -479,6 +493,7 @@ window.__ModuleLoader__.load({
|
|
|
479
493
|
'aria-label': t('card.open'),
|
|
480
494
|
onClick: () => open?.(),
|
|
481
495
|
},
|
|
496
|
+
h('style', null, CSS),
|
|
482
497
|
h('svg', { width: 18, height: 18, viewBox: '0 0 24 24', fill: 'none', 'aria-hidden': true },
|
|
483
498
|
h('path', { d: 'M4 6.5h16M4 12h16M4 17.5h10', stroke: 'currentColor', strokeWidth: 1.7, strokeLinecap: 'round' }),
|
|
484
499
|
h('circle', { cx: 18.5, cy: 17.5, r: 2.2, stroke: 'currentColor', strokeWidth: 1.5 }),
|
|
@@ -507,6 +522,7 @@ window.__ModuleLoader__.load({
|
|
|
507
522
|
if (event.key === 'Enter' || event.key === ' ') open?.();
|
|
508
523
|
},
|
|
509
524
|
},
|
|
525
|
+
h('style', null, CSS),
|
|
510
526
|
h(
|
|
511
527
|
'div',
|
|
512
528
|
{ className: 'loci-card-head' },
|
|
@@ -546,6 +562,14 @@ window.__ModuleLoader__.load({
|
|
|
546
562
|
metric(t('card.pending'), pending, pending > 0 ? 'is-warn' : ''),
|
|
547
563
|
metric(t('card.disputed'), disputed, disputed > 0 ? 'is-error' : ''),
|
|
548
564
|
),
|
|
565
|
+
data !== undefined && latest !== null
|
|
566
|
+
? h(
|
|
567
|
+
'div',
|
|
568
|
+
{ className: 'loci-card-known' },
|
|
569
|
+
h('span', { className: 'loci-card-known-tag' }, t('card.latest')),
|
|
570
|
+
h('span', { className: 'loci-card-known-text' }, latest),
|
|
571
|
+
)
|
|
572
|
+
: null,
|
|
549
573
|
data !== undefined && entries > 0 && budget > 0
|
|
550
574
|
? h(
|
|
551
575
|
'div',
|
|
@@ -571,7 +595,21 @@ window.__ModuleLoader__.load({
|
|
|
571
595
|
h('span', { className: 'loci-card-foot-grow' }),
|
|
572
596
|
)
|
|
573
597
|
: null,
|
|
574
|
-
pending > 0
|
|
598
|
+
pending > 0
|
|
599
|
+
? h(
|
|
600
|
+
'button',
|
|
601
|
+
{
|
|
602
|
+
type: 'button',
|
|
603
|
+
className: 'loci-card-hint loci-card-hint-action',
|
|
604
|
+
onClick: (event) => {
|
|
605
|
+
event.stopPropagation();
|
|
606
|
+
requestedTab = 'review';
|
|
607
|
+
open?.();
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
t('card.pendingHint').replace('{n}', String(pending)),
|
|
611
|
+
)
|
|
612
|
+
: null,
|
|
575
613
|
);
|
|
576
614
|
}
|
|
577
615
|
// ---------------------------------------------------------------- panel
|
|
@@ -1134,7 +1172,12 @@ window.__ModuleLoader__.load({
|
|
|
1134
1172
|
}
|
|
1135
1173
|
|
|
1136
1174
|
function MemoryPanel({ t }) {
|
|
1137
|
-
const [tab, setTab] = useState(
|
|
1175
|
+
const [tab, setTab] = useState(() => {
|
|
1176
|
+
// "N item(s) waiting for you in Review" in the card is a way in, not a remark.
|
|
1177
|
+
const wanted = requestedTab;
|
|
1178
|
+
requestedTab = undefined;
|
|
1179
|
+
return wanted ?? 'feed';
|
|
1180
|
+
});
|
|
1138
1181
|
const [selected, setSelected] = useState(undefined);
|
|
1139
1182
|
const [nonce, setNonce] = useState(0);
|
|
1140
1183
|
const tabs = useMemo(
|
|
@@ -1178,6 +1221,10 @@ window.__ModuleLoader__.load({
|
|
|
1178
1221
|
}
|
|
1179
1222
|
|
|
1180
1223
|
function PanelIcon({ size, active }) {
|
|
1224
|
+
// Three stacked sheets: the product is a layered index, and the glyph has to read at
|
|
1225
|
+
// the same optical weight as the shipped icons beside it. The previous drawing lived
|
|
1226
|
+
// in 4–20 of the viewBox with a small off-centre dot, which made it look thinner and
|
|
1227
|
+
// smaller than its neighbours in the sidebar.
|
|
1181
1228
|
return h(
|
|
1182
1229
|
'svg',
|
|
1183
1230
|
{
|
|
@@ -1186,10 +1233,28 @@ window.__ModuleLoader__.load({
|
|
|
1186
1233
|
viewBox: '0 0 24 24',
|
|
1187
1234
|
fill: 'none',
|
|
1188
1235
|
'aria-hidden': true,
|
|
1189
|
-
style: { opacity: active ? 1 : 0.
|
|
1236
|
+
style: { opacity: active ? 1 : 0.72, display: 'block' },
|
|
1190
1237
|
},
|
|
1191
|
-
h('path', {
|
|
1192
|
-
|
|
1238
|
+
h('path', {
|
|
1239
|
+
d: 'M12 3 21 7.5 12 12 3 7.5 12 3Z',
|
|
1240
|
+
stroke: 'currentColor',
|
|
1241
|
+
strokeWidth: 1.8,
|
|
1242
|
+
strokeLinejoin: 'round',
|
|
1243
|
+
}),
|
|
1244
|
+
h('path', {
|
|
1245
|
+
d: 'M3.6 12.2 12 16.4l8.4-4.2',
|
|
1246
|
+
stroke: 'currentColor',
|
|
1247
|
+
strokeWidth: 1.8,
|
|
1248
|
+
strokeLinecap: 'round',
|
|
1249
|
+
strokeLinejoin: 'round',
|
|
1250
|
+
}),
|
|
1251
|
+
h('path', {
|
|
1252
|
+
d: 'M3.6 16.8 12 21l8.4-4.2',
|
|
1253
|
+
stroke: 'currentColor',
|
|
1254
|
+
strokeWidth: 1.8,
|
|
1255
|
+
strokeLinecap: 'round',
|
|
1256
|
+
strokeLinejoin: 'round',
|
|
1257
|
+
}),
|
|
1193
1258
|
);
|
|
1194
1259
|
}
|
|
1195
1260
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-loci",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Layered long-term memory for DeepSeek Harness: the prompt sees only a stable index, full text is one lookup away. Bi-temporal facts, strict scope isolation, verified-only injection.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/host/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import { registerCommands } from './commands.js';
|
|
|
23
23
|
import { runDoctor } from './doctor.js';
|
|
24
24
|
|
|
25
25
|
/** Version stamped into the database and diagnostics. */
|
|
26
|
-
export const VERSION = '3.
|
|
26
|
+
export const VERSION = '3.1.0';
|
|
27
27
|
|
|
28
28
|
/** Optional collaborators; without them the plugin still serves tools. */
|
|
29
29
|
export const inject = ['systemPrompt', 'tools'];
|