@stofone/dsh-agent-roster 1.0.0 → 1.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,23 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.1.0] - 2026-11-03
8
+
9
+ ### The library reads as spaces, not groups
10
+
11
+ 1.0.0 made the space a real level in the format. The panel still showed collections as small labels, so the
12
+ level existed in the files and not on screen. Each group header is now the space itself: its id sits in the
13
+ same column as the roster ids (the register keeps one spine), the right side states two facts you can check
14
+ on the spot — how many rosters it holds, how many are already here — and one button pulls the whole space.
15
+
16
+ Rows also stopped lying about state. A roster that is already on this machine says so, and its action
17
+ becomes **Open** instead of **Pull**: the panel no longer offers to fetch something you already have. The
18
+ local group drops the per-row "local" label, because the group header already says it.
19
+
20
+ On a narrow panel the space header folds into two lines (id + action, then title + facts) instead of
21
+ squeezing a long space name into a vertical stack one character wide.
22
+
23
+
7
24
  ## [1.0.0] - 2026-11-03
8
25
 
9
26
  ### A space is the thing a repository holds
@@ -668,6 +685,7 @@ point is now one search field above the tabs.
668
685
  [Unreleased]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.12.0...main
669
686
  [0.13.1]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.13.0...v0.13.1
670
687
  [1.0.0]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.13.1...v1.0.0
688
+ [1.1.0]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v1.0.0...v1.1.0
671
689
  [0.13.0]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.12.0...v0.13.0
672
690
  [0.12.0]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.11.1...v0.12.0
673
691
  [0.11.1]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.11.0...v0.11.1
package/README.md CHANGED
@@ -326,6 +326,11 @@ response, the tag strip summarises every tag in view with its count (multiple ta
326
326
  segmented control switches between **by source** (this machine, then one group per collection) and **flat**
327
327
  (one list, source named per row). Tag filtering is view state only; it never writes back to a file.
328
328
 
329
+ By source, each group's head **is the space**: its id sits in the same column as the roster ids, the
330
+ right side states two facts you can check on the spot — how many rosters it holds, how many are already
331
+ here — and carries one button that pulls the whole space. A roster that is already here says so in its row
332
+ and its button reads **Open**: the panel never asks you to pull something you already have.
333
+
329
334
  Multiple words are AND, not OR: `data kubernetes` returns nothing rather than every roster that merely
330
335
  mentions "data". Ranking is a pure function in `lib/search.js`, so it is unit-tested rather than eyeballed.
331
336
 
package/README.zh.md CHANGED
@@ -323,6 +323,10 @@ node bin/cli.mjs compile --roster examples-orchestration/rosters/core-review \
323
323
  多选是「且」;右侧可以在**按来源**(本机 + 每个 collection 各一组)与**平铺**(一份清单,来源写在行内)
324
324
  之间切换。标签筛选只影响视图,不写回文件。
325
325
 
326
+ 按来源时,每一组的头就是**空间**本身:等宽的空间 id 落在与档案 id 同一列,右侧写两件当场能核对的事实
327
+ ——「几份 · 已装几份」——并带一个「拉取整个空间」按钮。已经在本机的那份,行内写「已在本机」,
328
+ 按钮变成「打开」:面板不会让你去拉一份自己已经有的档案。窄屏上这个头折成两行,空间名不会被挤成竖排。
329
+
326
330
  多个词是「且」不是「或」:`data kubernetes` 一条都不返,而不是把所有提到 data 的档案都堆上来。
327
331
  排序是 `lib/search.js` 里的纯函数,所以它被单测钉住,而不是靠肉眼看着顺。
328
332
 
package/client.js CHANGED
@@ -1373,14 +1373,19 @@ window.__ModuleLoader__.load({
1373
1373
  map.set(key, {
1374
1374
  key,
1375
1375
  scope: entry.scope,
1376
- title: entry.scope === "local" ? t("catalog.local") : entry.collectionTitle ?? entry.collectionId ?? "",
1376
+ id: entry.scope === "local" ? t("catalog.local") : entry.collectionId ?? "",
1377
+ title: entry.scope === "local" ? "" : entry.collectionTitle ?? "",
1377
1378
  entries: [],
1378
1379
  });
1379
1380
  }
1380
1381
  map.get(key).entries.push(entry);
1381
1382
  }
1382
- return [...map.values()];
1383
- }, [filtered, mode]);
1383
+ // 空间头上只放两件能当场核对的事实:有几份、其中几份本机已经有。
1384
+ return [...map.values()].map((group) => ({
1385
+ ...group,
1386
+ installed: group.entries.filter((entry) => installed.has(entry.rosterId)).length,
1387
+ }));
1388
+ }, [filtered, mode, installed, t]);
1384
1389
 
1385
1390
  function toggleTag(tag) {
1386
1391
  setSelected((current) => {
@@ -1419,15 +1424,16 @@ window.__ModuleLoader__.load({
1419
1424
  { className: "rstr-row-stats" },
1420
1425
  h("span", { className: "rstr-stat" }, t("catalog.members", { count: String(entry.members ?? 0) })),
1421
1426
  entry.scope === "remote" && installed.has(entry.rosterId)
1422
- ? h(Badge, { tone: "ok" }, t("catalog.installed"))
1427
+ ? h("span", { className: "rstr-row-here" }, t("catalog.here"))
1423
1428
  : null,
1424
- h(
1425
- "span",
1426
- { className: "rstr-stat" },
1427
- entry.scope === "local"
1428
- ? t("catalog.local")
1429
- : t("catalog.remoteFrom", { collection: entry.collectionTitle ?? entry.collectionId ?? "" }),
1430
- ),
1429
+ // 本机组里每行再写一次「本机」是废话:所在的分组头已经说了。
1430
+ entry.scope === "local"
1431
+ ? null
1432
+ : h(
1433
+ "span",
1434
+ { className: "rstr-stat" },
1435
+ t("catalog.remoteFrom", { collection: entry.collectionTitle ?? entry.collectionId ?? "" }),
1436
+ ),
1431
1437
  ),
1432
1438
  h(
1433
1439
  "div",
@@ -1439,7 +1445,8 @@ window.__ModuleLoader__.load({
1439
1445
  className: "rstr-btn rstr-btn-sm",
1440
1446
  disabled: busy,
1441
1447
  onClick: async () => {
1442
- if (entry.scope === "local") {
1448
+ // 已经在本机的那份,按钮就该是「打开」——不能让人去拉一份自己已经有的。
1449
+ if (entry.scope === "local" || installed.has(entry.rosterId)) {
1443
1450
  onOpen(entry.rosterId);
1444
1451
  return;
1445
1452
  }
@@ -1452,7 +1459,7 @@ window.__ModuleLoader__.load({
1452
1459
  }
1453
1460
  },
1454
1461
  },
1455
- entry.scope === "local" ? t("catalog.open") : t("catalog.pull"),
1462
+ entry.scope === "local" || installed.has(entry.rosterId) ? t("catalog.open") : t("catalog.pull"),
1456
1463
  ),
1457
1464
  ),
1458
1465
  ),
@@ -1549,14 +1556,22 @@ window.__ModuleLoader__.load({
1549
1556
  : groups.map((group) =>
1550
1557
  h(
1551
1558
  "div",
1552
- { key: group.key, className: "rstr-group" },
1553
- mode === "flat" || group.title === ""
1559
+ { key: group.key, className: "rstr-space" },
1560
+ mode === "flat"
1554
1561
  ? null
1555
1562
  : h(
1556
1563
  "div",
1557
- { className: "rstr-group-head" },
1558
- h("span", { className: "rstr-group-title" }, group.title),
1559
- h("span", { className: "rstr-group-count" }, String(group.entries.length)),
1564
+ { className: "rstr-space-head" },
1565
+ h("code", { className: "rstr-space-id", title: group.id }, group.id),
1566
+ h("span", { className: "rstr-space-title" }, group.title),
1567
+ h(
1568
+ "span",
1569
+ { className: "rstr-space-facts" },
1570
+ t("catalog.facts", {
1571
+ count: String(group.entries.length),
1572
+ installed: String(group.installed),
1573
+ }),
1574
+ ),
1560
1575
  group.scope === "remote" && typeof onPullSpace === "function"
1561
1576
  ? h(
1562
1577
  "button",
@@ -2735,13 +2750,37 @@ window.__ModuleLoader__.load({
2735
2750
  }
2736
2751
  .rstr-catalog .rstr-search { max-width: 560px; }
2737
2752
 
2738
- .rstr-group { display: flex; flex-direction: column; min-width: 0; }
2753
+ .rstr-space { display: flex; flex-direction: column; min-width: 0; }
2754
+ .rstr-space + .rstr-space { margin-top: 24px; }
2755
+ .rstr-space-head {
2756
+ display: grid;
2757
+ grid-template-columns: minmax(110px, 176px) minmax(0, 1fr) auto auto;
2758
+ align-items: baseline;
2759
+ gap: 8px 16px;
2760
+ padding: 16px 8px 6px 0;
2761
+ border-bottom: 1px solid var(--dsw-alias-border-l2);
2762
+ min-width: 0;
2763
+ }
2764
+ .rstr-space-id {
2765
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
2766
+ font-size: 13px;
2767
+ font-weight: 600;
2768
+ color: var(--dsw-alias-label-primary);
2769
+ overflow: hidden;
2770
+ text-overflow: ellipsis;
2771
+ white-space: nowrap;
2772
+ }
2773
+ .rstr-space-title { font-size: 13px; color: var(--dsw-alias-label-secondary); min-width: 0; }
2774
+ .rstr-space-facts {
2775
+ font-size: 11px;
2776
+ color: var(--dsw-alias-label-tertiary);
2777
+ font-variant-numeric: tabular-nums;
2778
+ white-space: nowrap;
2779
+ }
2780
+ .rstr-row-here { font-size: 11px; color: var(--dsw-alias-label-tertiary); }
2739
2781
 
2740
- .rstr-group-head { display: flex; align-items: baseline; gap: 8px; padding: 10px 0 4px; border-bottom: 1px solid var(--dsw-alias-border-l2); }
2741
2782
 
2742
- .rstr-group-title { font-size: 12px; font-weight: 600; letter-spacing: 0.02em; color: var(--dsw-alias-label-secondary); }
2743
2783
 
2744
- .rstr-group-count { font-size: 11px; color: var(--dsw-alias-label-tertiary); font-variant-numeric: tabular-nums; }
2745
2784
 
2746
2785
  .rstr-results { display: flex; flex-direction: column; min-width: 0; padding: 0 28px 28px; border-top: 1px solid var(--dsw-alias-border-l2); }
2747
2786
 
@@ -3080,6 +3119,16 @@ window.__ModuleLoader__.load({
3080
3119
  .rstr-tag { border-left: none; border-bottom: 2px solid transparent; border-radius: 0; white-space: nowrap; }
3081
3120
  .rstr-tag-on { border-left: none; border-bottom-color: var(--dsw-alias-state-business-primary); background: transparent; }
3082
3121
  .rstr-tag-clear { margin-top: 0; flex: 0 0 auto; }
3122
+ /* 空间头:窄屏由一列 id/标题/事实/按钮的四列网格折成两行 */
3123
+ .rstr-space-head {
3124
+ grid-template-columns: minmax(0, 1fr) auto;
3125
+ row-gap: 2px;
3126
+ padding-top: 14px;
3127
+ }
3128
+ .rstr-space-id { grid-area: 1 / 1; }
3129
+ .rstr-space-head > .rstr-btn { grid-area: 1 / 2; }
3130
+ .rstr-space-title { grid-area: 2 / 1; }
3131
+ .rstr-space-facts { grid-area: 2 / 2; text-align: right; }
3083
3132
  .rstr-row-body { grid-template-columns: minmax(0, 1fr) auto; }
3084
3133
  .rstr-row-id { grid-column: 1; }
3085
3134
  .rstr-row-text { grid-column: 1; }
package/locale/en.json CHANGED
@@ -320,6 +320,8 @@
320
320
  "refresh": "Refresh",
321
321
  "installed": "Installed",
322
322
  "pullSpace": "Pull whole space",
323
- "pulledSpace": "Space pulled: {files} files written, {skipped} skipped"
323
+ "pulledSpace": "Space pulled: {files} files written, {skipped} skipped",
324
+ "facts": "{count} rosters · {installed} here",
325
+ "here": "Already here"
324
326
  }
325
327
  }
package/locale/zh.json CHANGED
@@ -320,6 +320,8 @@
320
320
  "refresh": "刷新",
321
321
  "installed": "已安装",
322
322
  "pullSpace": "拉取整个空间",
323
- "pulledSpace": "空间已拉取:写入 {files} 个文件,跳过 {skipped} 个"
323
+ "pulledSpace": "空间已拉取:写入 {files} 个文件,跳过 {skipped} 个",
324
+ "facts": "{count} 份 · 已装 {installed}",
325
+ "here": "已在本机"
324
326
  }
325
327
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stofone/dsh-agent-roster",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "多 Agent 团队的静态成员档案定义与校验层:档案库在面板里看得见、改得动,团队成员的人设、模型与工具边界固化成可校验、可版本化的文件,并在 dsh-agent-teams 创建成员时把档案桥接给运行时。",
5
5
  "type": "module",
6
6
  "main": "index.js",