pi-skill-evolve 0.1.7 → 0.1.8

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.
@@ -29,6 +29,21 @@ export const RANGE_LABEL: Record<RangeKey, string> = {
29
29
  /** 状态下拉的可选值:既有 run 终态,也有待处理页自己的两种(running / stale) */
30
30
  export const STATUS_OPTIONS = ["running", "stale", "reviewed", "applied", "rejected", "failed", "analyzed"] as const;
31
31
 
32
+ /**
33
+ * 状态下拉的中文标签(用户 2026-11-08:「这里的下拉应该是中文」)。
34
+ * 值仍然是状态机原文(URL 与过滤逻辑不变),只改给人看的那一侧;
35
+ * 与徽章文案保持同一套词——同一个概念全站只能有一个中文名。
36
+ */
37
+ export const STATUS_LABEL: Record<(typeof STATUS_OPTIONS)[number], string> = {
38
+ running: "复盘中",
39
+ stale: "卡死",
40
+ reviewed: "待入队",
41
+ applied: "已采纳",
42
+ rejected: "已拒绝",
43
+ failed: "失败",
44
+ analyzed: "待评审",
45
+ };
46
+
32
47
  export interface ListFilter {
33
48
  skill: string;
34
49
  /** 空串 = 全部 */
@@ -21,7 +21,7 @@ import {
21
21
  telemetryOffWhileEnabled,
22
22
  } from "../flags.ts";
23
23
  import type { WebView } from "./data.ts";
24
- import { RANGE_KEYS, RANGE_LABEL, STATUS_OPTIONS, type ListFilter, type ViewPage } from "./filter.ts";
24
+ import { RANGE_KEYS, RANGE_LABEL, STATUS_LABEL, STATUS_OPTIONS, type ListFilter, type ViewPage } from "./filter.ts";
25
25
  import { listHref, type ListExtras } from "./list-query.ts";
26
26
  import { escapeHtml, formatStamp, formatTimeSeconds } from "./text.ts";
27
27
  import type { WebControls } from "./activity.ts";
@@ -110,7 +110,8 @@ ${warn}${backlog}`;
110
110
  function statusSelect(current: string): string {
111
111
  const options = [`<option value="">全部状态</option>`];
112
112
  for (const status of STATUS_OPTIONS) {
113
- options.push(`<option value="${status}"${status === current ? " selected" : ""}>${status}</option>`);
113
+ // value 仍是状态机原文(过滤逻辑不变),只把展示换中文
114
+ options.push(`<option value="${status}"${status === current ? " selected" : ""}>${STATUS_LABEL[status]}</option>`);
114
115
  }
115
116
  return options.join("");
116
117
  }
@@ -129,7 +130,7 @@ export function renderFilterForm(view: WebView, filter: ListFilter, extras: List
129
130
  const keep = extraInputs(extras);
130
131
  return `<form class="filter" method="get">
131
132
  <input type="hidden" name="view" value="${view}">
132
- ${keep}<label>技能 <input type="text" name="skill" value="${escapeHtml(filter.skill)}" placeholder="全部技能" size="16"></label>
133
+ ${keep}<label>技能 <input type="text" name="skill" value="${escapeHtml(filter.skill)}" placeholder="技能名包含…" size="16"></label>
133
134
  <label>状态 <select name="status">${statusSelect(filter.status)}</select></label>
134
135
  <label>时间 <select name="range">${rangeSelect(filter.range)}</select></label>
135
136
  <button type="submit">筛选</button>
@@ -0,0 +1,57 @@
1
+ /**
2
+ * 筛选栏文案的验收测试(用户 2026-11-08:「这里的下拉应该是中文, 但是这个搜索框是干嘛的?」)。
3
+ *
4
+ * 两条口径:
5
+ * 1. 状态下拉的 **value 仍是状态机原文**(URL / 过滤逻辑一个字符都不改),只把给人看的一侧换中文;
6
+ * 中文名与列表徽章必须同一套词,同一个概念全站只能有一个名字。
7
+ * 2. 技能那个输入框是**筛选条件**,不是搜索框也不是下拉:placeholder 必须写成「技能名包含…」,
8
+ * 让人一眼知道「填一部分就够」。
9
+ *
10
+ * 纪律:纯字符串断言,不碰文件系统、不起服务。
11
+ */
12
+ import { test } from "node:test";
13
+ import assert from "node:assert/strict";
14
+
15
+ import { parseListQuery, STATUS_LABEL, STATUS_OPTIONS } from "../lib/web/filter.ts";
16
+ import { renderFilterForm } from "../lib/web/render-controls.ts";
17
+
18
+ const FILTER = parseListQuery(new URLSearchParams(""));
19
+ const EXTRAS = { live: false, open: "all", only: false } as never;
20
+
21
+ function form(): string {
22
+ return renderFilterForm("pending", FILTER, EXTRAS);
23
+ }
24
+
25
+ test("状态下拉每一项都显示中文,value 保持状态机原文", () => {
26
+ const html = form();
27
+ for (const status of STATUS_OPTIONS) {
28
+ assert.ok(html.includes(`value="${status}"`), `value 必须是 ${status}`);
29
+ assert.ok(html.includes(`>${STATUS_LABEL[status]}<`), `${status} 要显示成 ${STATUS_LABEL[status]}`);
30
+ }
31
+ });
32
+
33
+ test("状态下拉里不出现英文状态词", () => {
34
+ const html = form();
35
+ const select = html.slice(html.indexOf('name="status"'), html.indexOf("</select>", html.indexOf('name="status"')));
36
+ for (const status of STATUS_OPTIONS) {
37
+ assert.ok(!select.includes(`>${status}<`), `不应再出现英文项 >${status}<`);
38
+ }
39
+ });
40
+
41
+ test("中文标签覆盖七个可选状态,且没有空值", () => {
42
+ assert.equal(Object.keys(STATUS_LABEL).length, STATUS_OPTIONS.length);
43
+ for (const label of Object.values(STATUS_LABEL)) assert.ok(label.trim().length > 0);
44
+ });
45
+
46
+ test("技能输入框的 placeholder 说明它是筛选条件", () => {
47
+ const html = form();
48
+ assert.ok(html.includes('name="skill"'));
49
+ assert.ok(html.includes('placeholder="技能名包含…"'), "要告诉人这里填的是「技能名的一部分」");
50
+ assert.ok(!html.includes('placeholder="全部技能"'), "旧 placeholder 让人误以为是静态字段");
51
+ });
52
+
53
+ test("筛选表单仍然是 GET + 保留 view", () => {
54
+ const html = form();
55
+ assert.ok(html.includes('method="get"'));
56
+ assert.ok(html.includes('<input type="hidden" name="view" value="pending">'));
57
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-skill-evolve",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "技能进化:采集会话中的技能曝光/加载,异步复盘、生成改进提案并独立评审,附本机 Web 管理面板",
5
5
  "keywords": [
6
6
  "pi-package",