dsh-loci 3.0.1 → 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 CHANGED
@@ -4,6 +4,42 @@ 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
+
7
43
  ## [3.0.1] — 2026-11-15
8
44
 
9
45
  The 3.0.0 card was correct markup with no stylesheet, and the screenshot showed it.
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,6 +47,7 @@ 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
52
  'card.pendingSentence': '{n} still need your review',
49
53
  'card.pinned': 'pinned',
@@ -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: 12px 16px 24px; }
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(
@@ -548,6 +562,14 @@ window.__ModuleLoader__.load({
548
562
  metric(t('card.pending'), pending, pending > 0 ? 'is-warn' : ''),
549
563
  metric(t('card.disputed'), disputed, disputed > 0 ? 'is-error' : ''),
550
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,
551
573
  data !== undefined && entries > 0 && budget > 0
552
574
  ? h(
553
575
  'div',
@@ -573,7 +595,21 @@ window.__ModuleLoader__.load({
573
595
  h('span', { className: 'loci-card-foot-grow' }),
574
596
  )
575
597
  : null,
576
- pending > 0 ? h('div', { className: 'loci-card-hint' }, t('card.pendingHint').replace('{n}', String(pending))) : null,
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,
577
613
  );
578
614
  }
579
615
  // ---------------------------------------------------------------- panel
@@ -1136,7 +1172,12 @@ window.__ModuleLoader__.load({
1136
1172
  }
1137
1173
 
1138
1174
  function MemoryPanel({ t }) {
1139
- const [tab, setTab] = useState('feed');
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
+ });
1140
1181
  const [selected, setSelected] = useState(undefined);
1141
1182
  const [nonce, setNonce] = useState(0);
1142
1183
  const tabs = useMemo(
@@ -1180,6 +1221,10 @@ window.__ModuleLoader__.load({
1180
1221
  }
1181
1222
 
1182
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.
1183
1228
  return h(
1184
1229
  'svg',
1185
1230
  {
@@ -1188,10 +1233,28 @@ window.__ModuleLoader__.load({
1188
1233
  viewBox: '0 0 24 24',
1189
1234
  fill: 'none',
1190
1235
  'aria-hidden': true,
1191
- style: { opacity: active ? 1 : 0.75, display: 'block' },
1236
+ style: { opacity: active ? 1 : 0.72, display: 'block' },
1192
1237
  },
1193
- h('path', { d: 'M4 6.5h16M4 12h16M4 17.5h10', stroke: 'currentColor', strokeWidth: 1.7, strokeLinecap: 'round' }),
1194
- h('circle', { cx: 18.5, cy: 17.5, r: 2.2, stroke: 'currentColor', strokeWidth: 1.5 }),
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
+ }),
1195
1258
  );
1196
1259
  }
1197
1260
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-loci",
3
- "version": "3.0.1",
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.0.1';
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'];
@@ -36,7 +36,7 @@ export function openFacadeStore(options = {}) {
36
36
  memoryRoot: root,
37
37
  paths,
38
38
  config,
39
- pluginVersion: '3.0.1',
39
+ pluginVersion: '3.1.0',
40
40
  readOnly: options.readOnly === true,
41
41
  });
42
42
  return { store, paths };