sage-mem 1.3.0 → 1.5.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 +1 -0
- package/README.zh.md +1 -0
- package/lib/client.js +137 -163
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -499,6 +499,7 @@ Module boundaries (since 1.2.0):
|
|
|
499
499
|
- `lib/assembly/` — the four mounts: autodream / retrieval tools / observation / write gate
|
|
500
500
|
- `lib/memory/` — pure memory-layer functions and file operations; `lib/autodream/` — the autodream engine
|
|
501
501
|
- `lib/shared/` — zero-dependency cross-layer rules: file-name rule / local timestamps / the two size limits (**exactly one copy each**; `test/duplication-test.mjs` turns red otherwise)
|
|
502
|
+
- UI side: `client/*.js` is split per screen (`05-shared.js` holds the cross-screen primitives and type tables); `npm run build:client` concatenates them into `lib/client.js`
|
|
502
503
|
- Dependency direction: `memory` never imports `autodream`, `recall` depends only on `memory`, `assembly` never imports the composition root (`test/structure-test.mjs` turns red if that changes)
|
|
503
504
|
|
|
504
505
|
## Contributing
|
package/README.zh.md
CHANGED
|
@@ -503,6 +503,7 @@ memory/ 目录(markdown 文件,4 类)
|
|
|
503
503
|
- `lib/assembly/` —— 四块挂载:autodream / 检索工具 / 观测 / 写入门
|
|
504
504
|
- `lib/memory/` —— 记忆层的纯函数与文件操作;`lib/autodream/` —— 自动做梦引擎
|
|
505
505
|
- `lib/shared/` —— 跨层零依赖的小判据:文件名规则 / 本地时间戳 / 两个体积上限(**只许一份**,`test/duplication-test.mjs` 会红)
|
|
506
|
+
- 界面侧:`client/*.js` 按屏分片(`05-shared.js` 放跨屏小基元与类型词表),`npm run build:client` 拼成 `lib/client.js`
|
|
506
507
|
- 依赖方向:`memory` 不认识 `autodream`,`recall` 只依赖 `memory`,`assembly` 不反向依赖组合根(`test/structure-test.mjs` 会红)
|
|
507
508
|
|
|
508
509
|
## 参与开发
|
package/lib/client.js
CHANGED
|
@@ -22,13 +22,68 @@ window.__ModuleLoader__.load({
|
|
|
22
22
|
let react = require("react");
|
|
23
23
|
const h = react.createElement;
|
|
24
24
|
|
|
25
|
+
// ── 共享基元(1.4.0):跨屏复用的小东西只有这一份 ─────────────
|
|
26
|
+
// 这些名字原本在 10 / 20 / 50 三个分片各写一遍(isStr/isNum/strSchema/parseSchema/
|
|
27
|
+
// unwrap/fmtSize/hexA,以及类型词表 3 份)。分片共享同一个 factory 作用域,
|
|
28
|
+
// 所以收敛到本片之后,后面的分片直接用、不再各写一份。
|
|
29
|
+
const isStr = (v) => { if (typeof v !== "string") throw new Error("expected string"); };
|
|
30
|
+
const isBool = (v) => { if (typeof v !== "boolean") throw new Error("expected boolean"); };
|
|
31
|
+
const isNum = (v) => { if (typeof v !== "number") throw new Error("expected number"); };
|
|
32
|
+
function parseSchema(shape) {
|
|
33
|
+
return {
|
|
34
|
+
parse(value) {
|
|
35
|
+
if (value === null || typeof value !== "object") throw new Error("sage-mem: invalid payload");
|
|
36
|
+
for (const [key, check] of Object.entries(shape)) {
|
|
37
|
+
if (!(key in value)) throw new Error("sage-mem: missing field " + key);
|
|
38
|
+
check(value[key]);
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const strSchema = { parse(v) { isStr(v); return v; } };
|
|
45
|
+
|
|
46
|
+
/** 拆 typert remote 的 {ok, value} 信封。 */
|
|
47
|
+
function unwrap(result) {
|
|
48
|
+
return result && result.ok ? result.value : result;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 字节数 → 人话。三个档位对记忆文件足够(单条上限 512 KB)。 */
|
|
52
|
+
function fmtSize(n) {
|
|
53
|
+
if (n < 1024) return n + " B";
|
|
54
|
+
if (n < 1024 * 1024) return (n / 1024).toFixed(1) + " KB";
|
|
55
|
+
return (n / 1024 / 1024).toFixed(1) + " MB";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** #rrggbb + alpha → rgba()。星图的辉光/底盘用。 */
|
|
59
|
+
function hexA(hex, a) {
|
|
60
|
+
const n = parseInt(hex.slice(1), 16);
|
|
61
|
+
return "rgba(" + ((n >> 16) & 255) + "," + ((n >> 8) & 255) + "," + (n & 255) + "," + a.toFixed(3) + ")";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── 类型词表(唯一定义) ──
|
|
65
|
+
// 文件列表的排序 / 筛选 / 徽章、星图的 kind 标签与配色,全部从这里取。
|
|
66
|
+
const TYPE_ORDER = ["user", "feedback", "project", "reference"];
|
|
67
|
+
const TYPE_SORT = ["user", "feedback", "project", "reference", "special"];
|
|
68
|
+
const TYPE_LABEL = { user: "用户", feedback: "反馈", project: "项目", reference: "参考", special: "特殊" };
|
|
69
|
+
const TYPE_META = {
|
|
70
|
+
user: ["👤", TYPE_LABEL.user],
|
|
71
|
+
feedback: ["📌", TYPE_LABEL.feedback],
|
|
72
|
+
project: ["📁", TYPE_LABEL.project],
|
|
73
|
+
reference: ["🔗", TYPE_LABEL.reference],
|
|
74
|
+
};
|
|
75
|
+
// 星图配色:深色档(夜空)与浅色档(暖纸 + 矿物颜料)各一套,色相错开才分得清类型。
|
|
76
|
+
const TYPE_COLOR_DARK = {
|
|
77
|
+
user: "#e0c07a", feedback: "#7ec8da", project: "#ac9edc", reference: "#92ceaa", special: "#bcc3d0",
|
|
78
|
+
};
|
|
79
|
+
const TYPE_COLOR_LIGHT = {
|
|
80
|
+
user: "#a4652c", feedback: "#3a6f8f", project: "#6a5b8f", reference: "#4a7c63", special: "#5b5750",
|
|
81
|
+
};
|
|
25
82
|
const StarmapRuntime = (function (require, _react) {
|
|
26
83
|
let react = _react;
|
|
27
84
|
const h = react.createElement;
|
|
28
85
|
// ── 手写 strict codec(浏览器无 zod,typert 只要求 parse 方法)──
|
|
29
|
-
|
|
30
|
-
const isNum = (v) => { if (typeof v !== "number") throw new Error("expected number"); };
|
|
31
|
-
const strSchema = { parse(v) { isStr(v); return v; } };
|
|
86
|
+
// isStr / isNum / strSchema 来自 05-shared.js(这个 IIFE 在 factory 作用域内,看得到)。
|
|
32
87
|
const starSchema = {
|
|
33
88
|
parse(v) {
|
|
34
89
|
if (v === null || typeof v !== "object") throw new Error("invalid star");
|
|
@@ -81,11 +136,6 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
81
136
|
],
|
|
82
137
|
};
|
|
83
138
|
|
|
84
|
-
/** 拆 typert remote 的 {ok, value} 信封。 */
|
|
85
|
-
function unwrap(result) {
|
|
86
|
-
return result && result.ok ? result.value : result;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
139
|
/** 错误详情尽量展开,避免 "[object Object]" 式黑洞。 */
|
|
90
140
|
function detail(e) {
|
|
91
141
|
if (!e) return "未知错误";
|
|
@@ -114,15 +164,10 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
114
164
|
// 深空画布上够辨认,又不抢眼。
|
|
115
165
|
//
|
|
116
166
|
// 两档取值:`color` = 深色档(夜空画布),`light` = 浅色档(纸本设色)。
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
const KIND_META = {
|
|
120
|
-
|
|
121
|
-
feedback: { label: "反馈", color: "#7ec8da", light: "#3a6f8f" },
|
|
122
|
-
project: { label: "项目", color: "#ac9edc", light: "#6a5b8f" },
|
|
123
|
-
reference: { label: "参考", color: "#92ceaa", light: "#4a7c63" },
|
|
124
|
-
special: { label: "特殊", color: "#bcc3d0", light: "#5b5750" },
|
|
125
|
-
};
|
|
167
|
+
// 类型标签与配色来自 05-shared.js 的唯一定义;这里只是把它们组装成星图用的 meta。
|
|
168
|
+
const KIND_META = {};
|
|
169
|
+
for (const k of TYPE_SORT) KIND_META[k] = { label: TYPE_LABEL[k], color: TYPE_COLOR_DARK[k], light: TYPE_COLOR_LIGHT[k] };
|
|
170
|
+
|
|
126
171
|
/**
|
|
127
172
|
* 宿主主题挂钩 —— 与 ui-theme 同一处:body[data-ds-dark-theme] 存在即深色档。
|
|
128
173
|
*/
|
|
@@ -180,16 +225,6 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
180
225
|
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
181
226
|
}
|
|
182
227
|
|
|
183
|
-
function hexA(hex, a) {
|
|
184
|
-
const n = parseInt(hex.slice(1), 16);
|
|
185
|
-
return "rgba(" + ((n >> 16) & 255) + "," + ((n >> 8) & 255) + "," + (n & 255) + "," + a.toFixed(3) + ")";
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function fmtBytes(n) {
|
|
189
|
-
if (n >= 1024) return (n / 1024).toFixed(1) + " KB";
|
|
190
|
-
return n + " B";
|
|
191
|
-
}
|
|
192
|
-
|
|
193
228
|
function fmtDate(ms) {
|
|
194
229
|
if (!ms) return "";
|
|
195
230
|
const d = new Date(ms);
|
|
@@ -391,39 +426,12 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
391
426
|
return blocks;
|
|
392
427
|
}
|
|
393
428
|
|
|
394
|
-
// ── overlay 开合的模块级 store(footer 按钮与 overlay 共享)──
|
|
395
|
-
const uiStore = { open: false, subs: new Set() };
|
|
396
429
|
/** client context 由 apply 存入;Slot 组件经 props 或此兜底获取。 */
|
|
397
430
|
let appCtx = null;
|
|
398
|
-
function setOpen(v) {
|
|
399
|
-
uiStore.open = !!v;
|
|
400
|
-
uiStore.subs.forEach((fn) => fn(uiStore.open));
|
|
401
|
-
}
|
|
402
|
-
function useOpen() {
|
|
403
|
-
const s = react.useState(uiStore.open);
|
|
404
|
-
react.useEffect(() => {
|
|
405
|
-
const fn = (v) => s[1](v);
|
|
406
|
-
uiStore.subs.add(fn);
|
|
407
|
-
return () => { uiStore.subs.delete(fn); };
|
|
408
|
-
}, []);
|
|
409
|
-
return s[0];
|
|
410
|
-
}
|
|
411
|
-
|
|
412
431
|
// ── 样式(data-plugin-css 约定,幂等注入)──
|
|
413
432
|
const CSS = [
|
|
414
|
-
".smap-footer-btn{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--dsw-alias-border-l1,transparent);background:transparent;color:var(--dsw-alias-label-secondary,inherit);border-radius:8px;padding:4px 10px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap;}",
|
|
415
|
-
".smap-fab{position:fixed;right:0;top:15%;transform:translateY(-50%);z-index:900;width:36px;height:64px;border-radius:12px 0 0 12px;border:1px solid var(--dsw-alias-border-l1);border-right:none;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-brand-primary);font-size:17px;cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:.5;pointer-events:auto;transition:opacity .15s,background .15s,width .15s;box-shadow:-4px 0 18px rgba(0,0,0,.35);font-family:inherit;}",
|
|
416
|
-
".smap-fab:hover{opacity:1;width:46px;background:var(--dsw-alias-bg-layer-1);}",
|
|
417
|
-
".smap-footer-btn:hover{color:var(--dsw-alias-label-primary,inherit);border-color:var(--dsw-alias-border-l2,#8886);}",
|
|
418
|
-
".smap-footer-icon{font-size:13px;line-height:1;}",
|
|
419
|
-
".smap-veil{position:fixed;inset:0;z-index:1000;background:var(--dsw-alias-bg-mask-1,rgba(0,0,0,.45));backdrop-filter:blur(3px);display:flex;align-items:center;justify-content:center;pointer-events:auto;animation:smap-fade-in .18s ease-out;}",
|
|
420
|
-
".smap-modal{width:min(980px,94vw);height:min(680px,92vh);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:16px;box-shadow:0 24px 80px rgba(0,0,0,.55);padding:16px 18px;display:flex;flex-direction:column;overflow:hidden;pointer-events:auto;animation:smap-pop-in .22s cubic-bezier(.2,.9,.3,1.15);}",
|
|
421
|
-
"@keyframes smap-fade-in{from{opacity:0}to{opacity:1}}",
|
|
422
|
-
"@keyframes smap-pop-in{from{opacity:0;transform:scale(.96) translateY(10px)}to{opacity:1;transform:none}}",
|
|
423
433
|
".smap-card{background:var(--dsw-alias-bg-layer-1);border-radius:10px;padding:12px 14px;color:var(--dsw-alias-label-primary);display:flex;flex-direction:column;flex:1;min-height:0;}",
|
|
424
434
|
".smap-head{display:flex;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap;}",
|
|
425
|
-
".smap-title{font-size:15px;font-weight:600;letter-spacing:.06em;}",
|
|
426
|
-
".smap-sub{font-size:11px;color:var(--dsw-alias-label-secondary);}",
|
|
427
435
|
".smap-spacer{flex:1;}",
|
|
428
436
|
".smap-search{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;color:var(--dsw-alias-label-primary);font-size:12px;padding:4px 9px;outline:none;width:160px;font-family:inherit;}",
|
|
429
437
|
".smap-search:focus{border-color:var(--dsw-alias-border-l2);}",
|
|
@@ -1358,7 +1366,7 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
1358
1366
|
if (s.mtimeMs > newest) newest = s.mtimeMs;
|
|
1359
1367
|
}
|
|
1360
1368
|
legendKids.push(h("span", { key: "count", className: "smap-count" },
|
|
1361
|
-
viewStars.length + " 条 · " +
|
|
1369
|
+
viewStars.length + " 条 · " + fmtSize(totalBytes) + (newest ? " · 最近更新 " + fmtDate(newest) : "") +
|
|
1362
1370
|
(showArchive && archivedN ? " · 档案馆 " + archivedN + " 颗暗星" : "") +
|
|
1363
1371
|
" · 单击看" + (showArchive && archivedN ? "详情" : "全文")));
|
|
1364
1372
|
|
|
@@ -1367,7 +1375,7 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
1367
1375
|
const kids = [
|
|
1368
1376
|
h("div", { key: "t", className: "smap-tip-title" }, hover.star.title),
|
|
1369
1377
|
h("div", { key: "k", className: "smap-tip-kind" },
|
|
1370
|
-
meta.label + " · " + hover.star.file + " · " +
|
|
1378
|
+
meta.label + " · " + hover.star.file + " · " + fmtSize(hover.star.bytes)),
|
|
1371
1379
|
];
|
|
1372
1380
|
if (hover.star.archived === true) {
|
|
1373
1381
|
// 悬浮也要说清它是档案馆里的:暗星外表已经不同,别让人猜。
|
|
@@ -1391,7 +1399,7 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
1391
1399
|
h("button", { className: "smap-btn", onClick: () => setSelected(null), title: "关闭" }, "×")),
|
|
1392
1400
|
h("div", { key: "meta", className: "smap-panel-meta" },
|
|
1393
1401
|
h("span", { className: "smap-meta-dot", style: { background: kindColor(meta) } }),
|
|
1394
|
-
meta.label + " · " + selected.star.file + " · " +
|
|
1402
|
+
meta.label + " · " + selected.star.file + " · " + fmtSize(selected.star.bytes) +
|
|
1395
1403
|
// 归档星的 mtime 是「当年写下」的时间,跟「什么时候收起来的」不是一回事 ——
|
|
1396
1404
|
// 这里只留「已归档」,具体时间在下面的归档块里给。
|
|
1397
1405
|
(selected.star.archived === true
|
|
@@ -1494,42 +1502,11 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
1494
1502
|
body);
|
|
1495
1503
|
}
|
|
1496
1504
|
|
|
1497
|
-
// ── 全屏 overlay ─────────────────────────────────────────────
|
|
1498
|
-
function StarmapOverlay() {
|
|
1499
|
-
const open = useOpen();
|
|
1500
|
-
react.useEffect(() => {
|
|
1501
|
-
if (!open) return undefined;
|
|
1502
|
-
const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
|
|
1503
|
-
document.addEventListener("keydown", onKey);
|
|
1504
|
-
return () => document.removeEventListener("keydown", onKey);
|
|
1505
|
-
}, [open]);
|
|
1506
|
-
if (!open) return null;
|
|
1507
|
-
return h("div", {
|
|
1508
|
-
className: "smap-veil",
|
|
1509
|
-
onMouseDown: (e) => { if (e.target === e.currentTarget) setOpen(false); },
|
|
1510
|
-
},
|
|
1511
|
-
h("div", { className: "smap-modal" },
|
|
1512
|
-
h(StarMap, { ctx: appCtx, onClose: () => setOpen(false) })));
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
// ── 窗口右缘浮标(唯一入口,不绑实例/会话/Run 卡片)────────────
|
|
1516
|
-
function StarmapEdgeTab() {
|
|
1517
|
-
const open = useOpen();
|
|
1518
|
-
if (open) return null;
|
|
1519
|
-
return h("button", {
|
|
1520
|
-
type: "button",
|
|
1521
|
-
className: "smap-fab",
|
|
1522
|
-
onClick: () => setOpen(true),
|
|
1523
|
-
title: "记忆星图 — 把 sage-mem 的记忆画成一片星空",
|
|
1524
|
-
"aria-label": "记忆星图",
|
|
1525
|
-
}, "✦");
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
1505
|
const inject = ["slots", "remote"];
|
|
1529
1506
|
|
|
1530
1507
|
/**
|
|
1531
1508
|
* Client 插件:$mount strict remote contribution(第三方 remote 不在
|
|
1532
|
-
* 宿主的固定 remote
|
|
1509
|
+
* 宿主的固定 remote 清单里,必须自己挂),再注册星图子槽(B 方案起不再有右缘浮标与全屏 overlay)。
|
|
1533
1510
|
*/
|
|
1534
1511
|
async function apply(ctx) {
|
|
1535
1512
|
appCtx = ctx;
|
|
@@ -1559,22 +1536,7 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
1559
1536
|
|
|
1560
1537
|
|
|
1561
1538
|
// ── 手写 strict codec(浏览器无 zod,typert 只要求 parse 方法)──
|
|
1562
|
-
|
|
1563
|
-
return {
|
|
1564
|
-
parse(value) {
|
|
1565
|
-
if (value === null || typeof value !== "object") throw new Error("sage-mem: invalid payload");
|
|
1566
|
-
for (const [key, check] of Object.entries(shape)) {
|
|
1567
|
-
if (!(key in value)) throw new Error("sage-mem: missing field " + key);
|
|
1568
|
-
check(value[key]);
|
|
1569
|
-
}
|
|
1570
|
-
return value;
|
|
1571
|
-
},
|
|
1572
|
-
};
|
|
1573
|
-
}
|
|
1574
|
-
const isStr = (v) => { if (typeof v !== "string") throw new Error("expected string"); };
|
|
1575
|
-
const isBool = (v) => { if (typeof v !== "boolean") throw new Error("expected boolean"); };
|
|
1576
|
-
const isNum = (v) => { if (typeof v !== "number") throw new Error("expected number"); };
|
|
1577
|
-
const strSchema = { parse(v) { isStr(v); return v; } };
|
|
1539
|
+
// 基元(parseSchema / isStr / isBool / isNum / strSchema)在 05-shared.js。
|
|
1578
1540
|
const fileSchema = parseSchema({ "file": isStr, "type": isStr, "description": isStr, "size": isNum });
|
|
1579
1541
|
const listSchema = { parse(v) { if (!Array.isArray(v)) throw new Error("expected array"); v.forEach((f) => fileSchema.parse(f)); return v; } };
|
|
1580
1542
|
const readSchema = parseSchema({ "name": isStr, "content": isStr });
|
|
@@ -2072,15 +2034,11 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2072
2034
|
// ── 工具行 + 工具面板(体检 / 归档候选 / 保留名原样编辑)──
|
|
2073
2035
|
// 三个新入口收在一行**轻量**文字按钮里:不跟主工具栏抢重量级,也不各占一块版面。
|
|
2074
2036
|
// 面板一次只开一个(Section 里一个 tool 状态),否则文件列表会被顶到屏幕外。
|
|
2075
|
-
".smem-tools{display:flex;align-items:center;gap:4px;flex-wrap:wrap;}",
|
|
2076
|
-
".smem-tool-btn{border:0;background:transparent;color:var(--dsw-alias-label-secondary);font-size:12px;font-family:inherit;cursor:pointer;padding:2px 8px;border-radius:6px;}",
|
|
2077
|
-
".smem-tool-btn:hover{color:var(--dsw-alias-label-primary);}",
|
|
2078
|
-
'.smem-tool-btn[data-on="1"]{color:var(--dsw-alias-brand-primary,#4d76e6);background:var(--dsw-alias-bg-layer-2);}',
|
|
2079
2037
|
// 工具抽屉:关着时**零占位**(工具栏里只有一个「工具 ▾」),点开才占版面。
|
|
2080
2038
|
// ⚠️ 可发现性不靠抽屉本身,靠头部带常显的「体检 N 个问题」徽标 —— 那是整个
|
|
2081
2039
|
// 抽屉方案能成立的前提(否则三个能力就退回「藏起来」了)。
|
|
2082
2040
|
".smem-drawer{position:relative;flex:none;}",
|
|
2083
|
-
".smem-drawer-pop{position:absolute;right:0;top:calc(100% + 6px);z-index:6;min-width:236px;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,.10);padding:6px;display:flex;flex-direction:column;gap:2px;}",
|
|
2041
|
+
".smem-drawer-pop{position:absolute;right:0;top:calc(100% + 6px);z-index:6;min-width:236px;max-width:min(320px,calc(100vw - 24px));background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,.10);padding:6px;display:flex;flex-direction:column;gap:2px;}",
|
|
2084
2042
|
".smem-drawer-item{display:flex;align-items:center;gap:8px;border:0;background:transparent;font-family:inherit;font-size:12.5px;color:var(--dsw-alias-label-primary);text-align:left;padding:6px 8px;border-radius:8px;cursor:pointer;}",
|
|
2085
2043
|
".smem-drawer-item:hover{background:var(--dsw-alias-bg-layer-2);}",
|
|
2086
2044
|
'.smem-drawer-item[data-on="1"]{background:var(--dsw-alias-bg-layer-2);font-weight:600;}',
|
|
@@ -2131,7 +2089,7 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2131
2089
|
'.smem-subtab--on:after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:2px;border-radius:2px 2px 0 0;background:var(--dsw-alias-label-primary);}',
|
|
2132
2090
|
".smem-subtab-n{opacity:.6;margin-left:5px;font-variant-numeric:tabular-nums;}",
|
|
2133
2091
|
// 五个注入参数排两列:一行放两个,扫读反而更清楚(吸收方案 A 的做法)。
|
|
2134
|
-
".smem-ad-grid2{display:grid;grid-template-columns:repeat(auto-fit,minmax(
|
|
2092
|
+
".smem-ad-grid2{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:4px 18px;}",
|
|
2135
2093
|
".smem-ad-field{display:flex;align-items:center;gap:8px;min-width:0;}",
|
|
2136
2094
|
".smem-ad-field .smem-autodream-key{min-width:96px;}",
|
|
2137
2095
|
".smem-actions{display:flex;gap:6px;}",
|
|
@@ -2155,7 +2113,7 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2155
2113
|
'.smem-tab--on:after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:2px;border-radius:2px 2px 0 0;background:var(--dsw-alias-label-primary);}',
|
|
2156
2114
|
// 高度预算压到 62vh(原 70vh):设置弹窗本身高度有限,
|
|
2157
2115
|
// 70vh 在较矮的设置弹窗视口下会把星图底部的图例和计数挤出面板之外。
|
|
2158
|
-
".smem-starmap{display:flex;flex-direction:column;height:
|
|
2116
|
+
".smem-starmap{display:flex;flex-direction:column;height:clamp(320px,55vh,620px);min-height:280px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;overflow:hidden;}",
|
|
2159
2117
|
// ── 「自动做梦」面板 ──
|
|
2160
2118
|
// 融入型:全部走宿主变量,控件复用已有的 chip / input / btn 语言,不另造一套。
|
|
2161
2119
|
".smem-autodream{display:flex;flex-direction:column;gap:12px;}",
|
|
@@ -2202,23 +2160,21 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2202
2160
|
".smem-update{display:flex;align-items:center;gap:10px;flex-wrap:wrap;border:1px solid var(--dsw-alias-brand-primary,#4d76e6);border-radius:8px;padding:6px 10px;font-size:12px;background:var(--dsw-alias-bg-layer-1);}",
|
|
2203
2161
|
".smem-update-link{color:var(--dsw-alias-brand-primary,#4d76e6);text-decoration:underline;}",
|
|
2204
2162
|
".smem-update-x{margin-left:auto;border:0;background:transparent;color:inherit;cursor:pointer;font-size:14px;line-height:1;padding:0 2px;}",
|
|
2163
|
+
// ── 1.5.0:动作分组 / 长文 clamp / 空态骨架 / 窄窗口 ──
|
|
2164
|
+
".smem-actions-sep{width:1px;height:16px;background:var(--dsw-alias-border-l2);margin:0 2px;align-self:center;}",
|
|
2165
|
+
".smem-clamp-2{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;cursor:pointer;}",
|
|
2166
|
+
".smem-clamp-2--open{-webkit-line-clamp:unset;display:block;}",
|
|
2167
|
+
".smem-skeleton{display:flex;flex-direction:column;gap:10px;padding:6px 0;}",
|
|
2168
|
+
".smem-skeleton-row{height:44px;border-radius:8px;background:var(--dsw-alias-bg-layer-2);opacity:.55;}",
|
|
2169
|
+
".smem-empty{display:flex;flex-direction:column;align-items:flex-start;gap:8px;}",
|
|
2170
|
+
".smem-tabs-sep{width:1px;align-self:stretch;margin:6px 2px;background:var(--dsw-alias-border-l2);}",
|
|
2171
|
+
// 窄窗口(≤420px):头部带与筛选条各让一行,卡片动作允许换行、分隔符隐藏。
|
|
2172
|
+
"@media (max-width:420px){.smem-root{gap:10px;}.smem-head{gap:6px;}.smem-card-title{min-width:0;word-break:break-word;}.smem-actions{flex-wrap:wrap;}.smem-actions-sep{display:none;}.smem-num{width:56px;}.smem-search{min-width:100px;}}",
|
|
2205
2173
|
// ── 底部一行仓库链接(引流,不抢视线)──
|
|
2206
2174
|
".smem-star{display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap;font-size:12px;color:var(--dsw-alias-label-tertiary,#8b93a1);padding:2px 2px 0;}",
|
|
2207
2175
|
".smem-star-link{color:var(--dsw-alias-brand-primary,#4d76e6);text-decoration:underline;}",
|
|
2208
2176
|
].join("\n");
|
|
2209
2177
|
|
|
2210
|
-
const TYPE_META = {
|
|
2211
|
-
user: ["👤", "用户"],
|
|
2212
|
-
feedback: ["📌", "反馈"],
|
|
2213
|
-
project: ["📁", "项目"],
|
|
2214
|
-
reference: ["🔗", "参考"],
|
|
2215
|
-
};
|
|
2216
|
-
const TYPE_ORDER = ["user", "feedback", "project", "reference"];
|
|
2217
|
-
// 筛选条上的显示名:emoji 在筛选条里太吵,只用文字;special 是星图的兜底类,
|
|
2218
|
-
// 文件列表里出现它说明 frontmatter 写了 type: special。
|
|
2219
|
-
const TYPE_LABEL = { user: "用户", feedback: "反馈", project: "项目", reference: "参考", special: "特殊" };
|
|
2220
|
-
const TYPE_SORT = ["user", "feedback", "project", "reference", "special"];
|
|
2221
|
-
|
|
2222
2178
|
/**
|
|
2223
2179
|
* 把输入框里的一串标签切成数组。
|
|
2224
2180
|
*
|
|
@@ -2467,17 +2423,6 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2467
2423
|
tagLine + "\nmetadata:\n node_type: memory\n type: " + type + "\n---\n\n";
|
|
2468
2424
|
}
|
|
2469
2425
|
|
|
2470
|
-
function fmtSize(n) {
|
|
2471
|
-
if (n < 1024) return n + " B";
|
|
2472
|
-
if (n < 1024 * 1024) return (n / 1024).toFixed(1) + " KB";
|
|
2473
|
-
return (n / 1024 / 1024).toFixed(1) + " MB";
|
|
2474
|
-
}
|
|
2475
|
-
|
|
2476
|
-
/** 拆 typert remote 的 {ok, value} 信封。 */
|
|
2477
|
-
function unwrap(result) {
|
|
2478
|
-
return result && result.ok ? result.value : result;
|
|
2479
|
-
}
|
|
2480
|
-
|
|
2481
2426
|
function Chip(props) {
|
|
2482
2427
|
return h("button", {
|
|
2483
2428
|
type: "button",
|
|
@@ -2539,6 +2484,8 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2539
2484
|
]
|
|
2540
2485
|
: [
|
|
2541
2486
|
h("button", { type: "button", className: "smem-btn", key: "edit", onClick: function () { props.onEdit(item.file); } }, "编辑"),
|
|
2487
|
+
// 语义分组:左边只放「编辑」(安全、可逆),右边放「锁定 / 归档 / 删除」(对这条记忆的退役/保护决定)。
|
|
2488
|
+
h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
2542
2489
|
/**
|
|
2543
2490
|
* 锁定排在「编辑」与「归档」之间(用户 2026-10-11 指定):
|
|
2544
2491
|
* 它是「别归档这条」的开关,紧挨着归档才说得通。
|
|
@@ -2589,6 +2536,9 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2589
2536
|
function ArchivedCard(props) {
|
|
2590
2537
|
const item = props.item;
|
|
2591
2538
|
const meta = TYPE_META[item.type] || TYPE_META.reference;
|
|
2539
|
+
// 描述与归档理由都可能很长:默认各 clamp(描述 3 行 / 理由 2 行),点一下展开全文。
|
|
2540
|
+
const sDescOpen = react.useState(false);
|
|
2541
|
+
const descOpen = sDescOpen[0], setDescOpen = sDescOpen[1];
|
|
2592
2542
|
return h("div", { className: "smem-card smem-card--archived", key: item.file }, [
|
|
2593
2543
|
h("div", { className: "smem-card-head", key: "head" }, [
|
|
2594
2544
|
h("span", { className: "smem-badge", key: "badge", title: "类型 " + String(item.type) }, meta[0] + " " + meta[1]),
|
|
@@ -2604,9 +2554,18 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2604
2554
|
// 描述留在活动卡片里的同一个位置(标签下面),归档留痕块再往下 ——
|
|
2605
2555
|
// 否则「归档理由」像卡片结尾,后面孤零零一行描述没人认得它是什么。
|
|
2606
2556
|
item.description
|
|
2607
|
-
? h("div", {
|
|
2557
|
+
? h("div", {
|
|
2558
|
+
className: "smem-narrative", key: "desc",
|
|
2559
|
+
"data-clamp": descOpen ? "0" : "1",
|
|
2560
|
+
title: descOpen ? "点击收起" : "点击展开全文",
|
|
2561
|
+
onClick: function () { setDescOpen(!descOpen); },
|
|
2562
|
+
}, String(item.description))
|
|
2608
2563
|
: null,
|
|
2609
|
-
h("div", {
|
|
2564
|
+
h("div", {
|
|
2565
|
+
className: "smem-ar-reason smem-clamp-2" + (descOpen ? " smem-clamp-2--open" : ""),
|
|
2566
|
+
key: "why", title: "归档理由:" + (item.archivedReason || "(没写)"),
|
|
2567
|
+
onClick: function () { setDescOpen(!descOpen); },
|
|
2568
|
+
}, "归档理由:" + (item.archivedReason || "(没写)")),
|
|
2610
2569
|
props.editing
|
|
2611
2570
|
? h("div", { className: "smem-ar-edit", key: "edit" }, [
|
|
2612
2571
|
h("div", { className: "smem-ar-hint", key: "note" },
|
|
@@ -2643,7 +2602,8 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2643
2602
|
disabled: props.busy,
|
|
2644
2603
|
onClick: function () { props.onEdit(item.file); },
|
|
2645
2604
|
}, "编辑"),
|
|
2646
|
-
|
|
2605
|
+
h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
2606
|
+
h("button", {
|
|
2647
2607
|
type: "button", className: "smem-btn smem-btn-primary", key: "restore",
|
|
2648
2608
|
disabled: props.busy,
|
|
2649
2609
|
onClick: function () { props.onRestore(item.file); },
|
|
@@ -2766,7 +2726,7 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2766
2726
|
standalone ? null : toolClose(props),
|
|
2767
2727
|
]),
|
|
2768
2728
|
r.problems
|
|
2769
|
-
? h("div", {
|
|
2729
|
+
? h("div", { key: "secs" }, [
|
|
2770
2730
|
section("索引悬空(索引里有、文件没了)", r.dangling, (x) => x.file),
|
|
2771
2731
|
section("漏索引(文件在、索引里没有)", r.unlisted, (x) => x.file),
|
|
2772
2732
|
section("type 与前缀对不上", r.typeMismatch, (x) => x.file + " · type=" + x.type + ",文件名前缀=" + x.prefix),
|
|
@@ -3252,7 +3212,7 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
3252
3212
|
subtab("settings", "设置"),
|
|
3253
3213
|
subtab("records", "记录", String(snaps.length + runs.length + reports.length)),
|
|
3254
3214
|
]),
|
|
3255
|
-
segOf === "run" ? h("div", {
|
|
3215
|
+
segOf === "run" ? h("div", { key: "seg-run" }, [
|
|
3256
3216
|
h("div", { className: "smem-autodream-sec", key: "top" }, [
|
|
3257
3217
|
h("div", { className: "smem-autodream-row", key: "r" }, [
|
|
3258
3218
|
h("button", {
|
|
@@ -3376,7 +3336,7 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
3376
3336
|
: null,
|
|
3377
3337
|
]),
|
|
3378
3338
|
]) : null,
|
|
3379
|
-
segOf === "settings" ? h("div", {
|
|
3339
|
+
segOf === "settings" ? h("div", { key: "seg-set" }, [
|
|
3380
3340
|
// 自动归档那一段(三档策略 + 三个阈值)**已挪到「自动归档」页签**(v0.9.9):
|
|
3381
3341
|
// 归档是确定性规则、不调模型,和这一页的「让模型整理」不是一件事;
|
|
3382
3342
|
// 同一件事只在一处设定,这里不再重复摆一份。
|
|
@@ -3477,7 +3437,7 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
3477
3437
|
]) : null,
|
|
3478
3438
|
// ── 回滚点(新)──────────────────────────────────────────────
|
|
3479
3439
|
// 只改这一个文件、只用既有 h() 写法;数据全走 remote.autodream。
|
|
3480
|
-
segOf === "records" ? h("div", {
|
|
3440
|
+
segOf === "records" ? h("div", { key: "seg-rec" }, [
|
|
3481
3441
|
h("div", { className: "smem-autodream-sec", key: "snap" }, [
|
|
3482
3442
|
h("div", { className: "smem-autodream-row", key: "h" }, [
|
|
3483
3443
|
h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, "回滚点"),
|
|
@@ -3706,7 +3666,7 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
3706
3666
|
});
|
|
3707
3667
|
}
|
|
3708
3668
|
|
|
3709
|
-
return h("div", {
|
|
3669
|
+
return h("div", { }, [
|
|
3710
3670
|
// ── ① 它做什么(这一屏存在的理由写在最前面)──
|
|
3711
3671
|
h("div", { className: "smem-autodream-sec", key: "what" }, [
|
|
3712
3672
|
h("div", { className: "smem-autodream-row", key: "h" }, [
|
|
@@ -3898,7 +3858,8 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
3898
3858
|
const rawBusy = sRawBusy[0], setRawBusy = sRawBusy[1];
|
|
3899
3859
|
const sRawLoading = react.useState(false);
|
|
3900
3860
|
const rawLoading = sRawLoading[0], setRawLoading = sRawLoading[1];
|
|
3901
|
-
|
|
3861
|
+
// renderSlot:shell 会经 props 传进来(见 80-apply 的 children 声明),
|
|
3862
|
+
// 但本插件改用并列 tab 渲染星图,不再调用它 —— 声明保留给 shell,这里不取。
|
|
3902
3863
|
|
|
3903
3864
|
function load() {
|
|
3904
3865
|
setLoading(true);
|
|
@@ -4420,18 +4381,29 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
4420
4381
|
// banner 只在**专门看归档**时出现,切到全部时不重复说一遍。
|
|
4421
4382
|
let filesView;
|
|
4422
4383
|
if (loading && totalCount === 0) {
|
|
4423
|
-
filesView = h("div", { className: "smem-
|
|
4384
|
+
filesView = h("div", { className: "smem-skeleton", key: "loading", "data-loading": "1" }, [0, 1, 2].map(function (i) {
|
|
4385
|
+
return h("div", { className: "smem-skeleton-row", key: i });
|
|
4386
|
+
}));
|
|
4424
4387
|
} else if (totalCount === 0) {
|
|
4425
|
-
filesView = h("div", { className: "smem-empty", key: "empty" },
|
|
4426
|
-
"
|
|
4388
|
+
filesView = h("div", { className: "smem-empty", key: "empty" }, [
|
|
4389
|
+
h("div", { key: "t" }, "还没有记忆文件。对话中值得记住的事会写到这里,也可以手动写入。"),
|
|
4390
|
+
h("button", { type: "button", className: "smem-btn smem-btn-primary", key: "add", onClick: openAdd }, "+ 添加记忆"),
|
|
4391
|
+
]);
|
|
4427
4392
|
} else if (statusFilter === "archived" && archiveItems.length === 0) {
|
|
4428
|
-
filesView = h("div", { className: "smem-empty", key: "empty" },
|
|
4429
|
-
"档案馆是空的 —— 在活动记忆那张卡上点「归档」,它就会收到这里。")
|
|
4393
|
+
filesView = h("div", { className: "smem-empty", key: "empty" }, [
|
|
4394
|
+
h("div", { key: "t" }, "档案馆是空的 —— 在活动记忆那张卡上点「归档」,它就会收到这里。"),
|
|
4395
|
+
h("button", { type: "button", className: "smem-btn", key: "back", onClick: function () { setStatusFilter("active"); } }, "回文件列表"),
|
|
4396
|
+
]);
|
|
4430
4397
|
} else if (statusFilter === "active" && items.length === 0) {
|
|
4431
|
-
filesView = h("div", { className: "smem-empty", key: "empty" },
|
|
4432
|
-
"活动记忆是空的 —— 状态筛到「已归档」还能捞出 " + archiveItems.length + " 条。")
|
|
4398
|
+
filesView = h("div", { className: "smem-empty", key: "empty" }, [
|
|
4399
|
+
h("div", { key: "t" }, "活动记忆是空的 —— 状态筛到「已归档」还能捞出 " + archiveItems.length + " 条。"),
|
|
4400
|
+
h("button", { type: "button", className: "smem-btn", key: "go", onClick: function () { setStatusFilter("archived"); } }, "切到已归档"),
|
|
4401
|
+
]);
|
|
4433
4402
|
} else if (filtered.length === 0) {
|
|
4434
|
-
filesView = h("div", { className: "smem-empty", key: "empty" },
|
|
4403
|
+
filesView = h("div", { className: "smem-empty", key: "empty" }, [
|
|
4404
|
+
h("div", { key: "t" }, "没有符合搜索或筛选条件的文件。"),
|
|
4405
|
+
h("button", { type: "button", className: "smem-btn", key: "clear", onClick: function () { setStatusFilter("all"); setTypeFilter([]); setTagFilter([]); setQuery(""); } }, "清除筛选"),
|
|
4406
|
+
]);
|
|
4435
4407
|
} else {
|
|
4436
4408
|
const list = h("div", { className: "smem-list", key: "list" }, filtered.map(function (it) {
|
|
4437
4409
|
if (it.archived === true) {
|
|
@@ -4491,25 +4463,26 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
4491
4463
|
}
|
|
4492
4464
|
|
|
4493
4465
|
return h("div", { className: "smem-root" }, [
|
|
4494
|
-
h("div", { className: "smem-tabs", key: "tabs" }, [
|
|
4495
|
-
h("button", { type: "button", className: "smem-tab" + (tab === "files" ? " smem-tab--on" : ""), key: "f", onClick: function () { setTab("files"); } }, "文件列表"),
|
|
4496
|
-
h("button", { type: "button", className: "smem-tab" + (tab === "star" ? " smem-tab--on" : ""), key: "s", onClick: function () { setTab("star"); } }, "记忆星图"),
|
|
4497
|
-
h("button", { type: "button", className: "smem-tab" + (tab === "autodream" ? " smem-tab--on" : ""), key: "d", onClick: function () { setTab("autodream"); } }, "自动做梦"),
|
|
4466
|
+
h("div", { className: "smem-tabs", key: "tabs", role: "tablist" }, [
|
|
4467
|
+
h("button", { type: "button", className: "smem-tab" + (tab === "files" ? " smem-tab--on" : ""), key: "f", role: "tab", "aria-selected": tab === "files" ? "true" : "false", onClick: function () { setTab("files"); } }, "文件列表"),
|
|
4468
|
+
h("button", { type: "button", className: "smem-tab" + (tab === "star" ? " smem-tab--on" : ""), key: "s", role: "tab", "aria-selected": tab === "star" ? "true" : "false", onClick: function () { setTab("star"); } }, "记忆星图"),
|
|
4469
|
+
h("button", { type: "button", className: "smem-tab" + (tab === "autodream" ? " smem-tab--on" : ""), key: "d", role: "tab", "aria-selected": tab === "autodream" ? "true" : "false", onClick: function () { setTab("autodream"); } }, "自动做梦"),
|
|
4470
|
+
// 视觉分组:左边「看」(文件列表 / 星图),右边「设置 · 自动化」。
|
|
4471
|
+
h("span", { className: "smem-tabs-sep", key: "sep", "aria-hidden": "true" }),
|
|
4498
4472
|
// 「自动归档」排在自动做梦之后:归档是**确定性规则**(按闲置天数算,不调模型),
|
|
4499
4473
|
// 和「让模型整理记忆」是两件事。塞在自动做梦的设置里用户找不到
|
|
4500
4474
|
// (2026-10-11 用户原话:「扔这里连我都找不到」)。
|
|
4501
|
-
h("button", { type: "button", className: "smem-tab" + (tab === "archive" ? " smem-tab--on" : ""), key: "ar", onClick: function () { setTab("archive"); } }, "自动归档"),
|
|
4475
|
+
h("button", { type: "button", className: "smem-tab" + (tab === "archive" ? " smem-tab--on" : ""), key: "ar", role: "tab", "aria-selected": tab === "archive" ? "true" : "false", onClick: function () { setTab("archive"); } }, "自动归档"),
|
|
4502
4476
|
// 体检再排在「自动归档」之后、独立成一屏:它只报告不修改,和「文件列表」里的
|
|
4503
4477
|
// 增删改是两件事,塞在工具抽屉里会让人以为它是个动作按钮。
|
|
4504
|
-
h("button", { type: "button", className: "smem-tab" + (tab === "audit" ? " smem-tab--on" : ""), key: "a", onClick: openAuditTab }, "体检"),
|
|
4478
|
+
h("button", { type: "button", className: "smem-tab" + (tab === "audit" ? " smem-tab--on" : ""), key: "a", role: "tab", "aria-selected": tab === "audit" ? "true" : "false", onClick: openAuditTab }, "体检"),
|
|
4505
4479
|
]),
|
|
4506
4480
|
// 四个 tab 共用的一条头部带:我在哪 · 目录状态 · 活动/归档计数 · 体检徽标 · 待重启徽标 · 主操作。
|
|
4507
4481
|
// 层级形状固定:导航=下划线(.smem-tabs)、筛选=chip、动作=按钮,三种不混用。
|
|
4508
4482
|
h("div", { className: "smem-head", key: "head" }, [
|
|
4509
4483
|
h("span", { className: "smem-title", key: "t" }, "记忆管理"),
|
|
4510
|
-
|
|
4511
|
-
h("span", { className: "smem-dot" + (dirOk ? "" : " smem-dot--bad"), key: "dot", title: dirOk ? "memory 目录可读" : "memory 目录不可读" }),
|
|
4512
|
-
h("span", { className: "smem-muted", key: "st" }, dirOk ? "目录可读" : "目录不可读"),
|
|
4484
|
+
// 目录状态收进状态点的 title(不可读时下面另有一条 smem-err,错误本来就该显眼)。
|
|
4485
|
+
h("span", { className: "smem-dot" + (dirOk ? "" : " smem-dot--bad"), key: "dot", title: (dirOk ? "memory 目录可读" : "memory 目录不可读") + " · " + String(TAB_LABEL[tab] || "") }),
|
|
4513
4486
|
h("span", { className: "smem-badge", key: "cnt" }, headerCount),
|
|
4514
4487
|
// 体检徽标:**抽屉关着也一直看得见**,点它就切到「体检」那一屏。
|
|
4515
4488
|
h("button", {
|
|
@@ -4560,6 +4533,7 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
4560
4533
|
type: "button", key: "btn",
|
|
4561
4534
|
className: "smem-btn" + (drawer ? " smem-btn-primary" : ""),
|
|
4562
4535
|
"aria-expanded": drawer ? "true" : "false",
|
|
4536
|
+
"aria-haspopup": "menu",
|
|
4563
4537
|
title: "归档候选 / 保留名编辑",
|
|
4564
4538
|
onClick: function () { setDrawer(!drawer); },
|
|
4565
4539
|
}, "工具 ▾"),
|
|
@@ -4679,8 +4653,8 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
4679
4653
|
: null,
|
|
4680
4654
|
])
|
|
4681
4655
|
: null,
|
|
4682
|
-
error ? h("div", { className: "smem-err", key: "err" }, "⚠ " + error) : null,
|
|
4683
|
-
notice && !error ? h("div", { className: "smem-ok", key: "ok" }, "✓ " + notice) : null,
|
|
4656
|
+
error ? h("div", { className: "smem-err", key: "err", role: "alert" }, "⚠ " + error) : null,
|
|
4657
|
+
notice && !error ? h("div", { className: "smem-ok", key: "ok", role: "status" }, "✓ " + notice) : null,
|
|
4684
4658
|
formOpen
|
|
4685
4659
|
? h("div", { className: "smem-form", key: "form" }, [
|
|
4686
4660
|
h("div", { className: "smem-form-title", key: "ft" }, editingFile ? "编辑 " + editingFile : "添加记忆"),
|
package/package.json
CHANGED