libero-mcp 0.2.9 → 0.2.11
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.
|
@@ -401,7 +401,8 @@ export class SqliteTimelineRepo {
|
|
|
401
401
|
tb.status as status,
|
|
402
402
|
tb.title as title,
|
|
403
403
|
c.emoji as category_emoji,
|
|
404
|
-
c.name as category_name
|
|
404
|
+
c.name as category_name,
|
|
405
|
+
c.color as category_color
|
|
405
406
|
FROM time_blocks tb
|
|
406
407
|
LEFT JOIN categories c ON tb.category_id = c.id
|
|
407
408
|
WHERE tb.user_id = ?
|
|
@@ -42,6 +42,71 @@ export function categoryAsciiToken(name) {
|
|
|
42
42
|
return `[${latin[0].toUpperCase()}]`;
|
|
43
43
|
return `[${name.trim()[0]}]`;
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* S9 v0.2: hex 颜色 → 方块 emoji(粗色相映射,6-7 色)
|
|
47
|
+
*
|
|
48
|
+
* 设计意图(见 docs/specs/S9-v0.2-design-plan-colored-tiles.md):
|
|
49
|
+
* - 15 个分类压缩到 7 种方块色——保留视觉区分度,避免 15 色用户认不过来
|
|
50
|
+
* - 用 HSL hue 粗分(不用精确匹配 hex,避免脆弱)
|
|
51
|
+
* - 灰阶 / 深色 → ⬛(视觉暗色,区分显著)
|
|
52
|
+
* - 无效输入 → 🟦(默认蓝,跟原 spec v0.1 兼容)
|
|
53
|
+
*/
|
|
54
|
+
export function colorToTile(hex) {
|
|
55
|
+
if (!hex || typeof hex !== "string" || !hex.startsWith("#"))
|
|
56
|
+
return "🟦";
|
|
57
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
58
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
59
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
60
|
+
// 解析失败 → fallback
|
|
61
|
+
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b))
|
|
62
|
+
return "🟦";
|
|
63
|
+
const { h, s, l } = rgbToHsl(r, g, b);
|
|
64
|
+
// 灰阶(饱和度 < 12%)或过暗(亮度 < 0.35)→ ⬛
|
|
65
|
+
// 设计:睡眠(#34495E l=0.29 s=0.29)、内务(#95A5A6 s=0.09)、闲散(#BDC3C7) 等
|
|
66
|
+
// 深色 / 中性色都归黑,区分显著
|
|
67
|
+
if (s < 0.12 || l < 0.35)
|
|
68
|
+
return "⬛";
|
|
69
|
+
// 色相分段(0-360)
|
|
70
|
+
if (h < 15 || h >= 345)
|
|
71
|
+
return "🟥"; // 红
|
|
72
|
+
if (h < 45)
|
|
73
|
+
return "🟧"; // 橙
|
|
74
|
+
if (h < 70)
|
|
75
|
+
return "🟨"; // 黄
|
|
76
|
+
if (h < 165)
|
|
77
|
+
return "🟩"; // 绿
|
|
78
|
+
if (h < 240)
|
|
79
|
+
return "🟦"; // 蓝(含青)
|
|
80
|
+
if (h < 315)
|
|
81
|
+
return "🟪"; // 紫(240-315)
|
|
82
|
+
return "🟥"; // 粉红归红
|
|
83
|
+
}
|
|
84
|
+
/** RGB → HSL(h: 0-360, s/l: 0-1) */
|
|
85
|
+
function rgbToHsl(r, g, b) {
|
|
86
|
+
const rN = r / 255;
|
|
87
|
+
const gN = g / 255;
|
|
88
|
+
const bN = b / 255;
|
|
89
|
+
const max = Math.max(rN, gN, bN);
|
|
90
|
+
const min = Math.min(rN, gN, bN);
|
|
91
|
+
const l = (max + min) / 2;
|
|
92
|
+
let h = 0;
|
|
93
|
+
let s = 0;
|
|
94
|
+
if (max !== min) {
|
|
95
|
+
const d = max - min;
|
|
96
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
97
|
+
switch (max) {
|
|
98
|
+
case rN:
|
|
99
|
+
h = ((gN - bN) / d + (gN < bN ? 6 : 0)) * 60;
|
|
100
|
+
break;
|
|
101
|
+
case gN:
|
|
102
|
+
h = ((bN - rN) / d + 2) * 60;
|
|
103
|
+
break;
|
|
104
|
+
default:
|
|
105
|
+
h = ((rN - gN) / d + 4) * 60;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { h, s, l };
|
|
109
|
+
}
|
|
45
110
|
function timerKey(status) {
|
|
46
111
|
return status === "paused" ? "timer_paused" : "timer_running";
|
|
47
112
|
}
|
|
@@ -59,6 +124,17 @@ export function resolveCellSymbol(cell, format) {
|
|
|
59
124
|
const key = timerKey(cell.status);
|
|
60
125
|
return format === "emoji" ? FIXED_EMOJI[key] : FIXED_ASCII[key];
|
|
61
126
|
}
|
|
127
|
+
// S9 v0.2: planned 块带 tile 时优先用分类色方块,否则 fallback 到默认 🟦
|
|
128
|
+
if (cell.kind === "planned") {
|
|
129
|
+
if (format === "emoji") {
|
|
130
|
+
if (cell.tile && cell.tile.trim())
|
|
131
|
+
return cell.tile;
|
|
132
|
+
return FIXED_EMOJI.planned;
|
|
133
|
+
}
|
|
134
|
+
// ascii 用 category 首字母(如 [W]),否则默认 [#]
|
|
135
|
+
return cell.ascii ?? FIXED_ASCII.planned;
|
|
136
|
+
}
|
|
137
|
+
// unverified / empty
|
|
62
138
|
const key = cell.kind;
|
|
63
139
|
return format === "emoji" ? FIXED_EMOJI[key] : FIXED_ASCII[key];
|
|
64
140
|
}
|
|
@@ -92,6 +168,16 @@ export function mergeTimelineCells(candidates) {
|
|
|
92
168
|
function legendMeaning(symbol) {
|
|
93
169
|
if (LEGEND_ZH[symbol])
|
|
94
170
|
return LEGEND_ZH[symbol];
|
|
171
|
+
// S9 v0.2: 方块色 = 计划块(按分类上色)
|
|
172
|
+
if (symbol === "🟦" ||
|
|
173
|
+
symbol === "🟪" ||
|
|
174
|
+
symbol === "🟩" ||
|
|
175
|
+
symbol === "🟥" ||
|
|
176
|
+
symbol === "🟧" ||
|
|
177
|
+
symbol === "🟨" ||
|
|
178
|
+
symbol === "⬛") {
|
|
179
|
+
return "计划";
|
|
180
|
+
}
|
|
95
181
|
// completed category emoji / ascii letter
|
|
96
182
|
if (symbol.startsWith("[") && symbol.endsWith("]"))
|
|
97
183
|
return "实绩";
|
|
@@ -250,6 +336,15 @@ export function renderTimelineCells(input) {
|
|
|
250
336
|
}
|
|
251
337
|
if (boundary)
|
|
252
338
|
pushEllipsis();
|
|
339
|
+
// S9 v0.2: 末尾拼图例(按本次实际出现的色)
|
|
340
|
+
// 设计:图例短一行,只列实际出现过的符号;颜色块统一标"计划·X 类"
|
|
341
|
+
if (Object.keys(legend).length > 1) {
|
|
342
|
+
const legendParts = [];
|
|
343
|
+
for (const [sym, meaning] of Object.entries(legend)) {
|
|
344
|
+
legendParts.push(`${sym} ${meaning}`);
|
|
345
|
+
}
|
|
346
|
+
textLines.push(`图例:${legendParts.join(" · ")}`);
|
|
347
|
+
}
|
|
253
348
|
return { rows, rendered_text: textLines.join("\n"), legend };
|
|
254
349
|
}
|
|
255
350
|
export function computeDefaultWindow(now) {
|
|
@@ -272,6 +367,18 @@ function cellFromBlock(b) {
|
|
|
272
367
|
label: b.title,
|
|
273
368
|
};
|
|
274
369
|
}
|
|
370
|
+
// S9 v0.2: planned 块按分类上色(不再统一 🟦)
|
|
371
|
+
// unverified 保持 ❓ 不上色(状态优先于分类)
|
|
372
|
+
if (b.status === "planned") {
|
|
373
|
+
const ascii = categoryAsciiToken(b.category_name);
|
|
374
|
+
const tile = colorToTile(b.category_color);
|
|
375
|
+
return {
|
|
376
|
+
kind: "planned",
|
|
377
|
+
tile,
|
|
378
|
+
ascii,
|
|
379
|
+
label: b.title,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
275
382
|
return { kind: b.status, label: b.title };
|
|
276
383
|
}
|
|
277
384
|
function cellFromTimer(t) {
|