minecodex 1.2.2 → 2.0.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.
Files changed (87) hide show
  1. package/README.md +9 -3
  2. package/features/images/README.md +16 -98
  3. package/features/images/codex-feature.json +6 -23
  4. package/features/images/src/library-prompt-page.mjs +239 -0
  5. package/features/model-slider/THIRD_PARTY_NOTICES.md +1 -1
  6. package/features/notes/DESIGN.md +66 -0
  7. package/features/notes/README.md +18 -99
  8. package/features/notes/assets/icons/book-open-nav.svg +15 -0
  9. package/features/notes/assets/icons/book-open.svg +16 -0
  10. package/features/notes/assets/native-emojis.json +1 -0
  11. package/features/notes/assets/native-prompt-icons.json +28 -0
  12. package/features/notes/codex-feature.json +12 -70
  13. package/features/notes/src/main.mjs +2 -2
  14. package/features/notes/src/product.mjs +287 -613
  15. package/features/notes/src/prompt-schema.mjs +117 -0
  16. package/features/notes/src/quick-prompts-page.mjs +1559 -0
  17. package/features/notes/src/quick-prompts-server.mjs +167 -0
  18. package/features/session-status/DESIGN.md +10 -2
  19. package/features/session-status/README.md +8 -4
  20. package/features/session-status/src/session-status.mjs +268 -24
  21. package/features/sidebar-focus/DESIGN.md +16 -0
  22. package/features/sidebar-focus/codex-feature.json +20 -0
  23. package/features/sidebar-focus/src/sidebar-focus.mjs +134 -0
  24. package/features/sidebar-focus/vendor/lucide-LICENSE +43 -0
  25. package/features/unread-inbox/DESIGN.md +28 -0
  26. package/features/unread-inbox/codex-feature.json +20 -0
  27. package/features/unread-inbox/src/unread-inbox.mjs +360 -0
  28. package/features/usage-meter/DESIGN.md +49 -0
  29. package/features/usage-meter/README.md +18 -0
  30. package/features/usage-meter/codex-feature.json +13 -0
  31. package/features/usage-meter/src/usage-meter.mjs +200 -0
  32. package/package.json +17 -5
  33. package/packages/cli/src/config.mjs +4 -1
  34. package/packages/cli/src/control-server.mjs +2 -2
  35. package/packages/runtime-host/src/codex-injection.mjs +38 -3
  36. package/packages/runtime-host/src/codex-runtime.mjs +41 -20
  37. package/packages/runtime-host/src/feature-registry.mjs +1 -0
  38. package/packages/runtime-host/src/session-status-reader.mjs +40 -16
  39. package/features/notes/assets/icons/circle.svg +0 -15
  40. package/features/notes/assets/icons/ellipsis.svg +0 -17
  41. package/features/notes/assets/icons/external-link.svg +0 -17
  42. package/features/notes/assets/icons/list-todo.svg +0 -19
  43. package/features/notes/assets/icons/message-square-quote.svg +0 -17
  44. package/features/notes/assets/icons/paperclip.svg +0 -15
  45. package/features/notes/assets/icons/star-off.svg +0 -17
  46. package/features/notes/assets/icons/star.svg +0 -15
  47. package/features/notes/assets/icons/sticky-note.svg +0 -16
  48. package/features/notes/assets/icons/trash-2.svg +0 -19
  49. package/features/notes/src/http-server.mjs +0 -580
  50. package/features/notes/web/app.js +0 -1202
  51. package/features/notes/web/codex-artifact-document.svg +0 -1
  52. package/features/notes/web/codex-attachment.svg +0 -1
  53. package/features/notes/web/codex-build.svg +0 -1
  54. package/features/notes/web/codex-code.svg +0 -1
  55. package/features/notes/web/codex-cplusplus.svg +0 -1
  56. package/features/notes/web/codex-css.svg +0 -1
  57. package/features/notes/web/codex-document.svg +0 -1
  58. package/features/notes/web/codex-file.svg +0 -1
  59. package/features/notes/web/codex-folder.svg +0 -1
  60. package/features/notes/web/codex-hashes.svg +0 -1
  61. package/features/notes/web/codex-html.svg +0 -1
  62. package/features/notes/web/codex-image.svg +0 -1
  63. package/features/notes/web/codex-java.svg +0 -1
  64. package/features/notes/web/codex-javascript.svg +0 -1
  65. package/features/notes/web/codex-json.svg +0 -1
  66. package/features/notes/web/codex-notebook.svg +0 -1
  67. package/features/notes/web/codex-pdf.svg +0 -1
  68. package/features/notes/web/codex-pencil.svg +0 -3
  69. package/features/notes/web/codex-php.svg +0 -1
  70. package/features/notes/web/codex-plus.svg +0 -3
  71. package/features/notes/web/codex-presentation.svg +0 -1
  72. package/features/notes/web/codex-python.svg +0 -1
  73. package/features/notes/web/codex-react.svg +0 -1
  74. package/features/notes/web/codex-rust.svg +0 -1
  75. package/features/notes/web/codex-shell.svg +0 -1
  76. package/features/notes/web/codex-skill.svg +0 -1
  77. package/features/notes/web/codex-spreadsheet.svg +0 -1
  78. package/features/notes/web/codex-task.svg +0 -3
  79. package/features/notes/web/codex-terminal.svg +0 -1
  80. package/features/notes/web/codex-toml.svg +0 -1
  81. package/features/notes/web/codex-typescript.svg +0 -1
  82. package/features/notes/web/codex-view-all.svg +0 -3
  83. package/features/notes/web/file-icons.mjs +0 -68
  84. package/features/notes/web/i18n.mjs +0 -237
  85. package/features/notes/web/index.html +0 -15
  86. package/features/notes/web/styles.css +0 -1155
  87. package/features/notes/web/todo-drag.mjs +0 -18
package/README.md CHANGED
@@ -11,10 +11,12 @@ MineCodex 给 Codex Desktop 增加一组轻量、本地优先的扩展功能。
11
11
  | 插件 | 说明 |
12
12
  | --- | --- |
13
13
  | **Model Slider** | 更清晰、更顺滑的模型切换面板:具备品牌标识、常用收藏与流畅交互 |
14
- | **Notes** | 跨会话管理待办事项、常用提示词与文件,可随时从 Codex 工具栏快速复用 |
15
- | **Images** | 集中浏览生成的图片、Prompt 和来源任务,轻松保存原图或快速返回原会话 |
14
+ | **Prompts** | 专页分类管理提示词与调用统计,输入框 Quick Prompts 快速调用 |
15
+ | **Usage Meter** | 在 Help 与 Account 之间显示剩余额度环,悬浮查看周 / 五小时额度,点击进入 Usage 设置 |
16
+ | **Images** | 在原生 Library 图片详情中查看并复制提示词 |
16
17
  | **Hide Upsell Banner** | 隐藏输入框上方的用量提示横幅,仅优化界面展示,不影响正常的用量统计 |
17
18
  | **Session Status** | 在输入框下方查看当前任务的步骤、观测速度、Token 用量与上下文明细 |
19
+ | **Task Switcher** | 点击聊天标题或标签页前的任务按钮,查看并切换工作中与未读会话 |
18
20
 
19
21
  ## 安装
20
22
 
@@ -54,7 +56,7 @@ mcx open
54
56
 
55
57
  ## 工作原理
56
58
 
57
- MineCodex 统一管理 Codex 内的菜单入口、主题风格与界面适配。Model Slider、Notes、Images 各自管理数据与操作页面;Session Status 只读汇总本机 Codex 会话事件,Hide Upsell Banner 优化界面展示。所有功能按需开启,关闭后即停止运行。
59
+ MineCodex 统一管理 Codex 内的菜单入口、主题风格与界面适配。Model Slider、Prompts 各自管理操作界面,Images 增强原生 Library 的提示词查看;Session Status 只读汇总本机 Codex 会话事件,Hide Upsell Banner 优化界面展示。所有功能按需开启,关闭后即停止运行。
58
60
 
59
61
  ## 开发
60
62
 
@@ -69,3 +71,7 @@ npm run pack:audit
69
71
  ## 许可
70
72
 
71
73
  MineCodex 是非官方独立扩展项目,与 OpenAI 无关联。项目自有代码以 MIT License 授权;第三方图标、品牌和兼容资产遵循各自的权利说明,见 [`features/model-slider/THIRD_PARTY_NOTICES.md`](features/model-slider/THIRD_PARTY_NOTICES.md) 与 [`features/session-status/vendor/lucide-LICENSE`](features/session-status/vendor/lucide-LICENSE)。
74
+
75
+ ### Sidebar Focus / 侧栏聚焦
76
+
77
+ 展开侧栏时,点击铃铛与搜索之间的聚焦按钮,保留当前任务路径并收起其他分支。再次点击恢复原先布局;切换任务时跟随路径,手动调整分支时退出聚焦。
@@ -1,107 +1,25 @@
1
- # CodexImageHost
1
+ # Images · Library 提示词
2
2
 
3
- CodexImageHost 给 Codex 增加一个轻量的 `Images` 入口。它自动汇集不同 Thread 中的 ImageGen PNG,但不复制图片。
3
+ Images 在 Codex 原生 **Library → 图片详情**中补充提示词查看能力。
4
4
 
5
- ## 当前功能
5
+ - 有可追溯的提示词时,在缩放胶囊左侧显示 **Prompt / 提示词**。
6
+ - 点击后,使用原生 Version history 相同布局和 design token 的右侧面板展示内容。
7
+ - 面板只展示提示词正文,支持一键复制;优先使用生成提示词,否则追溯该图片的用户输入。
8
+ - 未找到提示词、普通上传图片或来源对话不可用时,不显示按钮。
9
+ - 切换图片或关闭详情会移除旧内容;Escape 先关闭提示词面板。
6
10
 
7
- - 服务先初始化路径索引并开放 HTTP health,再在后台扫描 `~/.codex/generated_images` 中已有的图片;扫描完成后开始文件监听。
8
- - 运行时监听新图片,自动加入 Images。
9
- - 通过 All images、Chats 或有图片的 Project 筛选来源;数量直接显示在筛选菜单中。
10
- - 工具栏内直接提供 2–6 列 Grid 滑条,使用无描边的 4px 轨道;首次打开按可用宽度给一个初始列数,之后只记住用户自己的选择。每张卡片显示来源 Project 或独立 Chat,以及生成时间。
11
- - 可按 Thread 聚合成图板;进入图板后,左侧图片选择带只保留该 Thread 的图片,关闭详情即返回原图板位置。
12
- - 标题栏固定在独立滚动区上方;图片按 36 张一批渐进加载,滚动条保持至少 48px 的可抓取高度。
13
- - 点击图片后,以左侧图片选择带 + 右侧详情区的 20/80 布局查看原图。
14
- - 按需读取该次 ImageGen 实际使用的 Prompt,并显示 Project、Thread、尺寸、时间与文件大小。
15
- - 通过 `Save as` 打开 macOS 系统保存面板并保存原始 PNG。
16
- - 从详情区通过 `Open Thread` 打开真实来源 Thread。
17
- - 已归档 Thread 的图片仍可查看和 Save as,但 `Open Thread` 会禁用并在悬浮时说明原因。
18
- - 通过上层同级项目 `CodexRuntimeHost` 启动一个统一宿主,在 Codex 左侧栏注册 Images。
11
+ 新版不再在 Sites 下方注入独立 Images 入口,也不启动第二套图库服务。
12
+ 图片浏览、编辑与下载使用 Codex 原生能力。读取仅在本机原生 Renderer 内完成,
13
+ 沿用当前登录态,不复制图片或持久化来源对话。
19
14
 
20
- 不包含收藏按钮、图片副本、Asset Store、标签、Collection 或删除功能。Codex 的 PNG 是原图,SQLite 只是路径索引。
15
+ ## 开发
21
16
 
22
- ## 运行
23
-
24
- 需要 Node.js 22.5 或更高版本,不需要安装第三方 npm 依赖。
25
-
26
- 启动统一宿主;它会读取 `codex-feature.json`、启动 Images 服务并注册侧边入口:
27
-
28
- ```bash
29
- npm start
30
- ```
31
-
32
- 只启动独立 Web UI:
33
-
34
- ```bash
35
- npm run service
36
- ```
37
-
38
- 然后打开 <http://127.0.0.1:47831>。
39
-
40
- 运行测试:
17
+ 统一宿主通过 `codex-feature.json` 注册 `src/library-prompt-page.mjs` 页面脚本。
18
+ 原生 DOM 或 API 契约缺失时不注入。视觉契约见 [DESIGN.md](DESIGN.md)。
41
19
 
42
20
  ```bash
43
- npm test
44
- ```
45
-
46
- 服务运行时,路径索引默认保存在 `~/.codex-image-host/library.sqlite`。统一宿主暂时沿用 `~/.codex-image-host/codex-profile`,避免迁移后丢失现有专用 Codex 实例状态。项目不会写入或删除 `~/.codex/generated_images` 中的文件。
47
-
48
- ## 精简结构
49
-
50
- ```text
51
- ~/.codex/generated_images
52
- │ scan + watch
53
- ▼
54
- ImageLibrary ── SQLite path index
55
- │ Codex state + session index
56
- │ ├─ Project / Chat / Thread 名称与 Archive 状态
57
- │ └─ revised_prompt(按需读取 Session JSONL)
58
- ▼
59
- HTTP + Images UI
60
- │
61
- └── codex-feature.json
62
- │ sibling discovery
63
- ▼
64
- ../CodexRuntimeHost
65
- └── Codex sidebar + Theme Bridge + open Thread
66
- ```
67
-
68
- - `src/image-library.mjs`:扫描、监听和路径索引,是产品核心。
69
- - `src/thread-metadata.mjs`:读取 Codex 当前 Project、projectless Chat、Thread 名称与 Archive 状态。
70
- - `src/prompt-index.mjs`:按 Thread 与 ImageGen item 定位真实 `revised_prompt`。
71
- - `src/http-server.mjs`:图片列表、来源 facets、Thread 图板、原图读取、系统 Save as 和静态页面。
72
- - `codex-feature.json`:Images 对统一宿主的最小声明,不包含图片业务逻辑。
73
- - `../CodexRuntimeHost`:可复用的 CDP 集成,统一负责多个个人功能的入口、Surface、主题与导航。
74
- - `web/`:零框架 Images 页面。
75
-
76
- ## Codex 设计语言与主题
77
-
78
- - Images 不维护独立品牌皮肤,而是通过 Theme Bridge 读取当前 Codex Renderer 已解析的 semantic tokens。
79
- - 支持 Codex 的明亮 / 暗黑主题,并监听根节点 theme class 与 style 变化。
80
- - 用户修改 accent、ink 或 surface 后,新的 resolved tokens 会同步到 iframe,不需要重新读取或猜测内部配置文件位置。
81
- - 侧边栏 Images 入口克隆原生 Plugins 控件的布局与状态,再替换为 Codex 图标体系中的独立 Image 图标;iframe 内不导入 Codex 私有 React 模块或 hashed CSS bundle,以免 Codex 更新后直接失效。
82
- - 原生左侧栏隐藏时,Images 标题左侧显示同源图标入口;点击由 RuntimeHost 转发给原生侧栏开关,恢复后入口自动隐藏。
83
-
84
- 取证、token Interface 和代码复用边界见 [`docs/CODEX_DESIGN_LANGUAGE.md`](docs/CODEX_DESIGN_LANGUAGE.md)。
85
-
86
- ## 当前边界
87
-
88
- - Images 服务需要保持运行,才会持续监听新图片;侧边栏注册由 `CodexRuntimeHost` 统一维持。
89
- - 每次服务启动都会先开放 health,再在后台补扫停机期间新增的图片并刷新来源名称;初始补扫完成后,运行期间的新图片由文件监听自动加入。手动 Refresh 会再次补扫来源和 Archive 状态,但不会重置筛选、图板模式、详情、滚动位置或列数。刷新 ring 至少显示 1 秒。
90
- - 侧边栏使用 Codex 当前未公开的 CDP / DOM 能力,Codex UI 更新后只需调整统一宿主;独立 Web UI 和路径索引不受影响。
91
- - 当前 Codex 的 CSP 会屏蔽普通本地 iframe。统一宿主会先启用 CSP bypass 并注册 document-start 脚本;尚未完成该初始化的首次或旧版 document 会受控 reload 一次。完成初始化后会留下 document 标记,后续 RuntimeHost 重启只替换旧的 MineCodex runtime,不再刷新 Codex 页面(Issue #1)。
92
- - 详情打开时会从本机 Session JSONL 按需读取该 ImageGen item 的真实 `revised_prompt` 并缓存到索引;历史记录缺少该事件时明确显示 Prompt 不可用,不从附近文本猜测。
93
- - 当前没有发现 Codex 会因运行时间增长而自动清理 `generated_images` 的证据。如果未来观察到真实清理,再决定是否增加可选备份。
94
-
95
- Phase 0 的原图、来源元数据与 Thread 导航证据见 [`spikes/phase0/README.md`](spikes/phase0/README.md)。
96
-
97
- 需要验证 Codex 内的 iframe 是否真正加载时,可在 `npm start` 运行期间执行:
98
-
99
- ```bash
100
- node spikes/phase0/cdp-probe.mjs frame-check --port 9231 \
101
- --url http://127.0.0.1:47831/
102
-
103
- node spikes/phase0/cdp-probe.mjs overlay-check --port 9231 \
104
- --url http://127.0.0.1:47831/
21
+ node --test features/images/test/library-prompt-page.test.mjs
105
22
  ```
106
23
 
107
- `overlay-check` 会打开 Codex 原生帮助菜单,验证菜单能覆盖 Images,同时确认 iframe 本身仍可交互。
24
+ 旧 `src/image-library.mjs`、索引、独立 `web/` 与服务脚本暂保留为历史工具,
25
+ 不属于当前插件入口;已有本地图片和缓存不会被迁移或删除。
@@ -7,32 +7,15 @@
7
7
  "zh-CN": "图片"
8
8
  },
9
9
  "description": {
10
- "en": "Browse generated images, inspect prompts and sources, and return to the original task.",
11
- "zh-CN": "集中浏览生成图片,查看提示词和来源任务,并保存原图或返回原任务。"
10
+ "en": "View image prompts inside the native Library image viewer.",
11
+ "zh-CN": "在原生 Library 图片详情中查看并复制提示词。"
12
12
  },
13
- "surfaceUrl": "http://127.0.0.1:47831/",
14
- "healthUrl": "http://127.0.0.1:47831/api/health",
15
- "runtimePortEnvironment": "CODEX_IMAGE_HOST_PORT",
16
- "pageScript": {
17
- "resource": "src/chatgpt-image-source-page.mjs"
18
- },
19
- "hostActions": [
20
- "import-generated-image",
21
- "create-image-edit-thread",
22
- "open-sidebar",
23
- "list-cloud-images",
24
- "import-cloud-image"
25
- ],
26
- "placement": {
27
- "after": "sites",
28
- "order": 10
29
- },
30
- "command": [
31
- "node",
32
- "src/main.mjs"
33
- ],
34
13
  "icon": {
35
14
  "viewBox": "0 0 24 24",
36
15
  "markup": "<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"></rect><circle cx=\"9\" cy=\"9\" r=\"2\"></circle><path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"></path>"
16
+ },
17
+ "entry": {
18
+ "kind": "page-script",
19
+ "script": "src/library-prompt-page.mjs"
37
20
  }
38
21
  }
@@ -0,0 +1,239 @@
1
+ // 仅增强原生 Library 图片详情;不创建第二个图库或侧栏入口。
2
+ const PANEL_ID = "minecodex-library-prompt";
3
+ const zh = () => (document.documentElement.lang || navigator.language || "en").toLowerCase().startsWith("zh");
4
+ const label = (en, cn) => zh() ? cn : en;
5
+ let mounted = null;
6
+ let apiPromise = null;
7
+ let scheduled = false;
8
+ let candidate = null;
9
+
10
+ function libraryImage(header) {
11
+ const key = Object.keys(header).find(key => key.startsWith("__reactFiber$"));
12
+ let fiber = key ? header[key] : null;
13
+ let selected = null;
14
+ for (let depth = 0; fiber && depth < 80; depth++, fiber = fiber.return) {
15
+ const file = fiber.memoizedProps?.file;
16
+ if (!file || !/^libfile_[\w-]+$/.test(file.id ?? "") || !file.mime_type?.startsWith("image/")) continue;
17
+ if (selected && selected.id !== file.id) break;
18
+ selected = file;
19
+ if (file.origination_thread_id && file.origination_message_id) break;
20
+ }
21
+ if (!selected) return null;
22
+ // 只保留关联字段,签名下载地址和图片数据不进入插件。
23
+ return Object.fromEntries(["id", "file_id", "current_version_number", "origination_thread_id", "origination_message_id"].map(key => [key, selected[key]]));
24
+ }
25
+
26
+ async function nativeApi() {
27
+ if (!apiPromise) apiPromise = (async () => {
28
+ const initialUrl = [...document.querySelectorAll('link[rel="modulepreload"],script[src]')]
29
+ .map(element => element.href || element.src).find(url => /\/app-initial-[^/]+\.js/.test(url));
30
+ if (!initialUrl) throw new Error("Library API unavailable");
31
+ const initialSource = await (await fetch(initialUrl, { signal })).text();
32
+ const metadataAsset = initialSource.match(/library-file-metadata-[\w-]+\.js/)?.[0];
33
+ if (!metadataAsset) throw new Error("Library metadata unavailable");
34
+ const metadataUrl = new URL(metadataAsset, initialUrl).href;
35
+ const source = await (await fetch(metadataUrl, { signal })).text();
36
+ const apiLocal = source.match(/([\w$]+)\.safePost\(`\/files\/library`/)?.[1];
37
+ const imports = [...source.matchAll(/import\{([^}]+)\}from["']([^"']+)["']/g)]
38
+ .find(match => match[1].split(",").some(binding => binding.trim().split(/\s+as\s+/).at(-1) === apiLocal));
39
+ const apiExport = imports?.[1].split(",").map(s => s.trim().split(/\s+as\s+/)).find(parts => parts.at(-1) === apiLocal)?.[0];
40
+ const initLocal = source.match(/function ([\w$]+)\(\)\{return\(\1=/)?.[1];
41
+ const exports = source.match(/export\{([^}]+)\}/)?.[1].split(",").map(s => s.trim().split(/\s+as\s+/)) ?? [];
42
+ const initExport = exports.find(parts => parts[0] === initLocal)?.at(-1);
43
+ const metadata = await import(metadataUrl);
44
+ if (typeof metadata[initExport] !== "function" || !apiExport) throw new Error("Library API contract changed");
45
+ metadata[initExport]();
46
+ const api = (await import(new URL(imports[2], metadataUrl).href))[apiExport];
47
+ if (typeof api?.safeGet !== "function") throw new Error("Library API unavailable");
48
+ return api;
49
+ })().catch(error => { apiPromise = null; throw error; });
50
+ return apiPromise;
51
+ }
52
+
53
+ function promptFromConversation(file, conversation) {
54
+ const node = conversation.mapping?.[file.origination_message_id];
55
+ const part = node?.message?.content?.parts?.find(part => part && typeof part === "object"
56
+ && part.content_type === "image_asset_pointer"
57
+ && [file.file_id, `sediment://${file.file_id}`].includes(part.asset_pointer));
58
+ if (!part || !["assistant", "tool"].includes(node.message.author?.role)
59
+ || !(part.metadata?.generation?.gen_id || part.metadata?.dalle?.gen_id)) return null;
60
+ const prompt = part.metadata?.dalle?.prompt;
61
+ if (typeof prompt === "string" && prompt.trim()) return { text: prompt, kind: "generation" };
62
+ let ancestor = conversation.mapping[node.parent];
63
+ const visited = new Set();
64
+ while (ancestor && !visited.has(ancestor.id)) {
65
+ visited.add(ancestor.id);
66
+ if (ancestor.message?.author?.role === "user") {
67
+ const text = ancestor.message.content?.parts?.filter(part => typeof part === "string").join("\n");
68
+ return text?.trim() ? { text, kind: "request" } : null;
69
+ }
70
+ ancestor = conversation.mapping[ancestor.parent];
71
+ }
72
+ return null;
73
+ }
74
+
75
+ function element(tag, className, text) {
76
+ const node = document.createElement(tag);
77
+ node.className = className;
78
+ if (text != null) node.textContent = text;
79
+ return node;
80
+ }
81
+
82
+ // 复用旧 Images 的 Lucide copy/check 图形,统一为 16px 图标按钮。
83
+ function copyGlyph(copied = false) {
84
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
85
+ for (const [key, value] of Object.entries({ viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" })) svg.setAttribute(key, value);
86
+ svg.innerHTML = copied
87
+ ? '<path d="m20 6-11 11-5-5"></path>'
88
+ : '<rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path>';
89
+ return svg;
90
+ }
91
+
92
+ function closePanel(record, restoreFocus = false) {
93
+ record.request?.abort();
94
+ record.request = null;
95
+ record.panel.hidden = true;
96
+ record.button.setAttribute("aria-expanded", "false");
97
+ if (restoreFocus && record.button.isConnected) record.button.focus();
98
+ }
99
+
100
+ function showPrompt(record) {
101
+ record.panel.hidden = false;
102
+ record.button.setAttribute("aria-expanded", "true");
103
+ record.close.focus();
104
+ }
105
+
106
+ async function resolvePrompt(file, request) {
107
+ if (!file.origination_thread_id || !file.origination_message_id) return null;
108
+ const api = await nativeApi();
109
+ request.signal.throwIfAborted();
110
+ const conversation = await api.safeGet("/conversation/{conversation_id}", {
111
+ parameters: { path: { conversation_id: file.origination_thread_id } },
112
+ signal: AbortSignal.any([signal, request.signal, AbortSignal.timeout(25_000)]),
113
+ });
114
+ return promptFromConversation(file, conversation);
115
+ }
116
+
117
+ function unmount() {
118
+ if (!mounted) return;
119
+ closePanel(mounted);
120
+ clearTimeout(mounted.copyTimer);
121
+ mounted.button.remove();
122
+ mounted.panel.remove();
123
+ mounted = null;
124
+ }
125
+
126
+ async function scan() {
127
+ scheduled = false;
128
+ if (signal.aborted) return;
129
+ const header = [...document.querySelectorAll('[data-testid="viewer-header"]')].find(header => header.getBoundingClientRect().width > 0 && libraryImage(header));
130
+ const file = header && libraryImage(header);
131
+ const identity = file && JSON.stringify(file);
132
+ if (candidate?.header === header && candidate?.identity === identity
133
+ && (!mounted || (mounted.button.isConnected && mounted.panel.isConnected))) return;
134
+ candidate?.request.abort();
135
+ unmount();
136
+ candidate = null;
137
+ if (!file) return;
138
+ const next = { header, identity, request: new AbortController() };
139
+ candidate = next;
140
+ let prompt;
141
+ try { prompt = await resolvePrompt(file, next.request); }
142
+ catch { return; }
143
+ // 异步结果只能写回当前图片;没有提示词时不展示入口。
144
+ if (!prompt || signal.aborted || candidate !== next || !header.isConnected) return;
145
+ // 缩放项在图片详情中是百分比菜单;沿用它的胶囊类,不固定构建 hash。
146
+ const zoom = [...header.querySelectorAll('button[aria-haspopup="menu"]')].find(button => /^\d+(?:\.\d+)?%$/.test(button.textContent.trim()));
147
+ const group = zoom?.parentElement?.parentElement;
148
+ const host = header.closest('[role="presentation"]');
149
+ if (!zoom || !group || !host) return;
150
+ const capsuleClass = zoom.className.split(/\s+/).filter(name => !/^_(?:dropdown|zoom)_/.test(name)).join(" ");
151
+ const button = element("button", capsuleClass, label("Prompt", "提示词"));
152
+ button.type = "button";
153
+ button.dataset.minecodexLibraryPrompt = "trigger";
154
+ button.setAttribute("aria-expanded", "false");
155
+ button.setAttribute("aria-controls", PANEL_ID);
156
+ button.style.width = "max-content";
157
+ button.style.minWidth = "0";
158
+ button.style.flex = "0 0 auto";
159
+ button.style.paddingInline = "calc(var(--spacing) * 3)";
160
+ group.prepend(button);
161
+ // 对齐原生 Library Version history 的面板、标题和内容区 token。
162
+ const panel = element("aside", "absolute inset-x-2 top-toolbar bottom-2 z-30 ms-auto mt-2 flex max-w-75 flex-col overflow-hidden rounded-2xl border border-default bg-surface shadow-lg select-none");
163
+ panel.id = PANEL_ID;
164
+ panel.setAttribute("aria-label", label("Image prompt", "图片提示词"));
165
+ panel.hidden = true;
166
+ panel.style.setProperty("-webkit-app-region", "no-drag");
167
+ panel.style.paddingBottom = "calc(var(--spacing) * 3)";
168
+ const heading = element("div", "flex items-center justify-between py-2 ps-4 pe-2");
169
+ const close = element("button", "flex size-8 shrink-0 items-center justify-center rounded-full text-default hover:bg-primary-ghost-hover focus-visible:outline-ring");
170
+ close.type = "button";
171
+ close.setAttribute("aria-label", label("Close prompt", "关闭提示词"));
172
+ // 直接复用原生关闭图标,避免手绘近似图形。
173
+ const nativeClose = [...header.querySelectorAll("button")].at(-1)?.querySelector("svg");
174
+ if (nativeClose) close.append(nativeClose.cloneNode(true));
175
+ else close.textContent = "×";
176
+ const actions = element("div", "flex items-center gap-1");
177
+ const copy = element("button", close.className);
178
+ copy.type = "button";
179
+ function copyFeedback(copied = false, failed = false) {
180
+ const text = failed ? label("Try copying again", "重试复制") : copied ? label("Copied", "已复制") : label("Copy prompt", "复制提示词");
181
+ copy.title = text;
182
+ copy.setAttribute("aria-label", text);
183
+ copy.replaceChildren(copyGlyph(copied));
184
+ }
185
+ copyFeedback();
186
+ actions.append(copy, close);
187
+ heading.append(element("span", "text-base font-semibold text-default", label("Prompt", "提示词")), actions);
188
+ const body = element("div", "min-h-0 flex-1 overflow-y-auto px-3");
189
+ body.style.paddingTop = "var(--spacing)";
190
+ body.append(
191
+ element("p", "px-1 text-sm leading-5 text-default whitespace-pre-wrap break-words select-text", prompt.text),
192
+ );
193
+ panel.append(heading, body);
194
+ host.append(panel);
195
+ const record = { header, file, identity, button, panel, body, close, request: null,
196
+ refreshLocale() {
197
+ button.textContent = label("Prompt", "提示词");
198
+ panel.setAttribute("aria-label", label("Image prompt", "图片提示词"));
199
+ close.setAttribute("aria-label", label("Close prompt", "关闭提示词"));
200
+ heading.firstElementChild.textContent = label("Prompt", "提示词");
201
+ copyFeedback();
202
+ },
203
+ };
204
+ mounted = record;
205
+ copy.addEventListener("click", async () => {
206
+ try {
207
+ await navigator.clipboard.writeText(prompt.text);
208
+ if (mounted !== record) return;
209
+ copyFeedback(true);
210
+ } catch {
211
+ if (mounted !== record) return;
212
+ copyFeedback(false, true);
213
+ }
214
+ clearTimeout(record.copyTimer);
215
+ record.copyTimer = setTimeout(() => copyFeedback(), 2000);
216
+ });
217
+ button.addEventListener("click", () => panel.hidden ? showPrompt(record) : closePanel(record));
218
+ close.addEventListener("click", () => closePanel(record, true));
219
+ }
220
+
221
+ const localeObserver = new MutationObserver(() => mounted?.refreshLocale());
222
+ localeObserver.observe(document.documentElement, { attributes:true, attributeFilter:["lang"] });
223
+ const observer = new MutationObserver(() => {
224
+ if (scheduled) return;
225
+ scheduled = true;
226
+ queueMicrotask(scan);
227
+ });
228
+ observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ["src", "data-state", "inert"] });
229
+ function escape(event) {
230
+ if (event.key !== "Escape" || !mounted || mounted.panel.hidden) return;
231
+ event.preventDefault();
232
+ event.stopImmediatePropagation();
233
+ closePanel(mounted, true);
234
+ }
235
+ document.addEventListener("keydown", escape, true);
236
+ const cleanup = () => { candidate?.request.abort(); candidate = null; observer.disconnect(); localeObserver.disconnect(); document.removeEventListener("keydown", escape, true); unmount(); };
237
+ signal.addEventListener("abort", cleanup, { once: true });
238
+ scan();
239
+ return cleanup;
@@ -39,7 +39,7 @@ license text, including the Feather-derived icon notice, is preserved in
39
39
 
40
40
  ## Codex compatibility assets
41
41
 
42
- File-type and action glyphs under `features/notes/web/codex-*.svg` mirror the Codex
42
+ Prompt category glyphs and emoji metadata under `features/notes/assets/` mirror the Codex
43
43
  Desktop visual language for compatibility. Codex, ChatGPT, OpenAI, and related marks
44
44
  and assets remain the property of OpenAI and their respective rights holders. These
45
45
  assets are not relicensed by MineCodex's MIT License.
@@ -0,0 +1,66 @@
1
+ # Prompts 设计约定
2
+
3
+ ## 范围与数据
4
+ Prompts 页面负责新增、编辑、分类、Emoji、排序和统计;Quick Prompts Popover 只浏览和插入。移除旧顶部 Notes 入口、固定摘要、Todo 和 Files 界面。沿用 `notes` 功能 ID 与 SQLite,保留已有提示词、历史 Todo / Files 数据和用户文件。
5
+
6
+ ## 原生依据
7
+ 2026-09-27 在隔离 Codex 实测导航扩展区、Library 页面及 Add files and more。
8
+ - Prompts 导航项位于固定扩展区末尾,顺序跟随扩展区而非硬编码 Projects。仅定位 `data-app-navigation-rail`,不匹配设置菜单。
9
+ - Quick Prompts 位于 Add 与 Change permissions 之间,克隆 Add 的按钮 classes 与响应式图标容器。入口采用 Lucide book-open;20px 图标描边折算 1.75px(24 viewBox / stroke-width 2.1)。
10
+ - 页面沿用 Library 的大标题和胶囊 Tabs;分类图标取自当前 Codex 原生 SymbolBrowser / project appearance catalogue,记录于 `assets/native-prompt-icons.json`,不手绘路径。
11
+
12
+ | 角色 | Token / 权威来源 | 值或用途 |
13
+ | --- | --- | --- |
14
+ | 输入框入口热区 | 原生 Add 的 h-token-button-composer / aspect-square | 28×28px |
15
+ | 入口图标 | 原生 CompactSource / LeadingSource | 16 / 20px |
16
+ | 字体 | --font-sans-default、--text-sm | 13px,原生字重 |
17
+ | 页面标题 | --text-heading-lg | 24px / 36px,600 |
18
+ | 页面内容 | 原生 Library header | 最大 1440px,左右 48px / 窄屏 30px |
19
+ | Tabs | 原生 Library tablist | 36px 高,胶囊,左右 16px |
20
+ | 文字 | --color-text-primary / secondary / tertiary | 标题、操作、辅助信息 |
21
+ | 面板 / 页面 | --color-surface-elevated-secondary、--color-token-border-default | 原生 surface、1px 边框,无额外阴影 |
22
+ | Popover 圆角 | --radius-suggestion-menu,回退 --radius-2xl | 20px |
23
+ | Popover 内边距 | --spacing | 4px,底部保留内边距 |
24
+ | Popover 列表行 | --suggestion-menu-item-padding / --app-menu-item-padding / --padding-row-y、--padding-row-x | 5px 8px |
25
+ | 行圆角 | --suggestion-menu-item-radius / --radius-xl | 15px |
26
+ | Hover | --color-background-primary-ghost-hover | 原生中性色 |
27
+ | Popover 位置 | 输入框上方 | 与输入框等宽,间隔 4px |
28
+
29
+ 分类颜色复用原生选择器的亮 / 暗模式色值:blue #0285ff / #339cff、green #04b84c / #40c977、orange #fb6a22 / #ff8549、pink #ff66ad / #ff8cc1、purple #924ff7 / #ad7bf9、red #fa423e / #ff6764、yellow #ffc300 / #ffd240;black 使用原生次级文字 token。
30
+
31
+ ## 页面
32
+ Header 为 Prompts;右侧依次为排序图标、分类调节图标、常驻搜索框、New prompt。搜索框直接采用原生 Library 的透明背景、1px outline、32px 高、胶囊圆角、12px 水平 padding 和 16px 搜索 SVG。分类管理使用原生 sliders 图标,不再放在 Tabs 行。排序菜单沿用原生 Library 的 4px gutter、20px radius、5px / 8px item padding、90% elevated surface 与 shadow-xl-spread。Tabs 仅包含 All、分类和 Other。默认分类生图、编程、文案、研究、翻译、效率,允许新增、改名、删除、换图标和颜色。删除分类仅解除归属,保留提示词和统计。
33
+
34
+ 分类管理在 440px 宽居中模态面板中完成,复用原生 New folder 弹窗的 20px padding、25px radius、弱描边与背景遮罩。分类编辑保留箭头返回和关闭按钮,不使用文字 Back 或整页替换。模态内限制 Tab 焦点,Escape 关闭后恢复入口焦点。提示词编辑使用同一结构,宽 520px。
35
+
36
+ 单条提示词通过图标弹出框选择 Emoji,沿用原生 Thread SymbolBrowser 的搜索和八列网格,不使用文本输入框;可恢复继承分类图标。分类选择为图标加名称的自定义菜单,不使用浏览器 select。列表显示标题、正文摘要;Manual order 和 Most used 显示累计调用次数,Recently used 在相同位置显示上次调用时间,未调用显示 Never used;点击进入编辑。手动排序采用右侧三横手柄拖拽,提示词限制组内排序;分类管理同样拖拽排序。Alt + ↑ / ↓ 提供键盘操作。另支持按调用次数、最近调用排序。搜索及统计排序期间不显示手动重排控件,避免操作含义不明确。
37
+
38
+ ## Popover
39
+ 入口 Tooltip 为 Quick Prompts,面板不重复显示标题,不包含新增、编辑、收藏、删除或管理操作。最上方显示最近实际调用的 3 条,后面按分类与组内手动顺序展示全部提示词;最近项目仍留在所属分类,避免分类内容随调用跳动。空分类隐藏。
40
+
41
+ 点击提示词只插入当前输入框,保留原草稿且不发送。Escape 关闭并回到入口;点击外部关闭;方向键移动列表焦点。原生 Add 或权限面板打开时关闭。切换输入框时关闭,异步结果不得插入另一会话。沿用原生焦点与 hover,无额外进场动画。
42
+
43
+ ## 统计
44
+ 成功插入后原子增加 `use_count` 并更新 `called_at`,保存于现有 SQLite。创建、编辑、复制和排序不计数。历史次数未知,从 0 开始,不从旧 `last_used_at` 推断。删除分类、切换页面、重启不清零。
45
+
46
+ 本地数据只以 textContent 和表单值渲染,不执行提示词里的 HTML。页面关闭恢复底层原生视图的焦点能力;功能禁用移除所有本功能入口和视图。
47
+
48
+ ## 空状态与编辑细节
49
+ 空分类和搜索无结果沿用 Scheduled 的 48px Emoji、18px medium 标题、次级说明布局;空分类提供 New prompt。All 分组副标题采用 text-sm、500 字重与 text-tertiary。输入框 focus 只用原生 border-ring 1px 边框,无外圈。模态 Back / Close 图形与内容边缘光学对齐,热区向外扩展。Delete 使用警戒样式;非空分类删除前独立确认,明确提示词移到 Other,内容与调用次数保留。
50
+
51
+ 导航栏 book-open 单独采用 20px 容器、27×27 viewBox,实际轮廓约 16.15px,描边折算 1.33px,与原生导航一致。Lucide 两个描边子路径合为一个 path,一次绘制半透明 currentColor,避免书脊交叠变深。输入框入口尺寸不变。
52
+
53
+ 拖拽排序使用等高 Drop Placeholder,原生拖影跟随指针;同组条目以 180ms ease-out 让位,落位淡入 140ms。放到范围外或取消恢复原位,不写入排序。遵循 prefers-reduced-motion。
54
+
55
+ 页面外壳圆角直接使用原生 PageSurface 的 `--radius-xl-base`(当前 12px),不使用经过角半径缩放的 `--radius-2xl`,避免覆盖层和原生底层边缘错位。
56
+
57
+ 深浅主题层级:页面使用 `--color-surface`;弹窗/菜单使用 `--color-surface-elevated-secondary`。标题跟随原生 heading-lg(`--text-heading-lg`、`--font-weight-medium`、1.2 行高)。正文用 text-primary,摘要用 text-secondary,分组与辅助信息用 text-tertiary。主按钮使用 background-primary-solid / text-primary-solid 配对;警戒按钮使用 danger-soft-alpha,不使用浅色固定粉红底。边界统一使用 `--color-border` 或输入框的 `--color-border-primary-outline`。
58
+
59
+ 提示词编辑沿用原生 Project 的复合标题输入:40px 图标段位于标题前,细分隔线,共用圆角边框与 focus-within 状态。图标选择入口不再单独占一行;Title 通过 aria-label 标识。
60
+
61
+ Prompts 覆盖页打开时暂存并移除原生导航项的 data-selected / aria-current,使用 data-suppress-active-style 还原未选中表现。关闭页面或禁用插件时恢复原生属性;观察原生重渲染,确保同时只有一个当前导航项。
62
+
63
+ Category 下拉面板与触发栏等宽、左右对齐,宽度包含内边距;每次打开按当前触发栏尺寸计算。
64
+
65
+ ## 语言
66
+ 界面名称、操作、Tooltip、无障碍标签、空状态、错误提示与图标/颜色名称按 Codex 的语言显示中英文;日期按对应 locale 格式化。用户保存的提示词与分类名称原样展示,不随界面语言改写。
@@ -1,106 +1,25 @@
1
- # CodexNotes
1
+ # Prompts
2
2
 
3
- CodexNotes 是一个完全本地的 Codex 个人调用层:在当前对话右上角通过一个
4
- Notes Toolbar Button 快速查看 Todo、Prompts 和 Files;单个分区超过 5 条时才会
5
- 显示 `View all`,并通过 RuntimeHost 在 Codex 原生右侧工作区打开互不重复、跟随
6
- 当前 Task 的完整 Tab;内部 capability 不可用时显示明确错误。
3
+ 导航栏扩展区最后的 **Prompts** 页面管理提示词;输入框 Add 与权限按钮之间的 **Quick Prompts** 只浏览和调用。
7
4
 
8
- 目标交互与 Codex 原生 Summary 一致:窄空间使用 `Toggle notes` Popover,
9
- 宽空间使用 `Toggle pinned notes`,并在 `shift` / `gutter` 模式中参与对话
10
- 布局。视觉、间距、圆角、阴影和高度规则使用 Host resolved tokens 与原生
11
- Summary contract,详见 `PRD.md`。
5
+ ## 管理页面
6
+ - All 与分类 Tabs,搜索、新增、编辑、删除。
7
+ - 默认分类:生图、编程、文案、研究、翻译、效率;支持新增、改名、删除与排序。
8
+ - 分类图标和颜色复用 Codex 原生 SymbolBrowser 的资源。单条提示词可设置一个 Emoji,否则继承分类图标。
9
+ - 使用右侧三横手柄拖拽调整组内顺序,也可按调用次数、最近调用排序。
10
+ - 删除分类会将提示词移至其他,保留正文、Emoji 和统计。
12
11
 
13
- 当前本地实现已经完成响应式 Summary realignment:`overlay / shift / gutter`
14
- 状态、Toolbar 文案、对话布局位移、Popover 定位、自然高度与单一内滚动区均由
15
- RuntimeHost 和同一份 Summary 内容组件协作完成。
12
+ ## 快速调用
13
+ - Tooltip 为 Quick Prompts;面板不显示重复标题或管理操作。
14
+ - 最近实际调用的 3 条排在最上面,然后按分类和手动顺序显示全部提示词。最近项目仍保留在所属分类;空分类隐藏。
15
+ - 点击只插入当前输入框,保留草稿,不发送消息。
16
+ - Escape、点击外部、切换会话或打开原生 Add 面板会关闭;方向键移动焦点。
16
17
 
17
- 所有用户可见的页面文案、按钮标题、ARIA 标签、Toast 与错误提示均支持 English / 简体中文;
18
- 嵌入 Codex 时跟随 RuntimeHost 传入的客户端语言,独立打开时跟随 URL `locale` 参数或
19
- 浏览器语言。功能 manifest 同时提供两种语言,协议 action id 保持稳定且不参与翻译。
18
+ ## 统计与存储
19
+ 只有成功插入输入框后才增加一次调用计数,并更新最近调用时间。浏览、编辑、创建、复制和排序不增加计数。SQLite 持久保存 `use_count` 与 `called_at`;历史次数未知,从 0 开始,不从旧的更新时间推算。切换页面和重启不会清零。
20
20
 
21
- 产品数据与 Web UI 独立于 Codex 客户端运行。Codex 中的 Toolbar、Pinned
22
- Summary、Tabs、Composer 插入和原生附件结果由同级的
23
- `../CodexRuntimeHost` 统一适配;这是一层可替换的非官方 CDP / private React / DOM
24
- Adapter,不是公开的 Codex Contribution API。
21
+ 功能 ID 仍为 `notes`,沿用已有配置开关、CodexNotes 数据库和提示词。新增分类、Emoji、排序和统计字段;保留历史 Todo / Files 数据,不删除用户文件。原有收藏标记保留,但不参与新界面排序。
25
22
 
26
- ## 本地运行
23
+ 运行入口为 `src/main.mjs` → `quick-prompts-server.mjs`,仅开放提示词、分类、排序、调用记录及健康检查。页面经已认证 Runtime Host binding 访问固定允许的接口。旧 HTTP 服务、网页、Todo / Files 操作及文件清理代码已删除。`prompt-schema.mjs` 只负责向前迁移;`product.mjs` 负责提示词、分类和统计的事务读写。历史 Todo / Files 表保持原样。
27
24
 
28
- 需要 Node.js 22.5 或更高版本。
29
-
30
- ```bash
31
- npm install
32
- npm start
33
- ```
34
-
35
- 默认地址是 `http://127.0.0.1:47832/`,可直接访问以下 Surface:
36
-
37
- - `/?surface=summary`
38
- - `/?surface=todo`
39
- - `/?surface=prompts`
40
- - `/?surface=files`
41
-
42
- 默认数据库位于:
43
-
44
- ```text
45
- ~/Library/Application Support/CodexNotes/codex-notes.sqlite
46
- ```
47
-
48
- 开发时可以通过环境变量隔离数据与端口:
49
-
50
- ```bash
51
- CODEX_NOTES_DATA_DIR=/tmp/codexnotes-dev \
52
- CODEX_NOTES_PORT=47832 \
53
- npm start
54
- ```
55
-
56
- `CODEX_NOTES_DATABASE` 可以直接指定数据库文件;`CODEX_NOTES_HOST` 只允许
57
- `127.0.0.1`、`localhost` 或 `::1`,避免无认证的本地能力被暴露到局域网。
58
- 数据库文件启动时会收紧为仅当前用户可读写(`0600`)。
59
-
60
- ## 在 Codex 中运行
61
-
62
- `codex-feature.json` 声明了 Conversation toolbar 入口、Pinned Summary、三个
63
- Detail Tab 和允许使用的 Host actions。共享宿主会扫描同级项目并在服务离线时
64
- 执行本项目的 `command`:
65
-
66
- ```bash
67
- cd ../CodexRuntimeHost
68
- npm start
69
- ```
70
-
71
- CodexNotes 不拥有 Injector,不修改 ChatGPT 应用包,也不读取其他功能项目的
72
- 数据库或 API。
73
-
74
- ## 数据语义
75
-
76
- - Todo 是无日期、无提醒、无 Agent 自动写入的手动清单;拖拽顺序持久化。
77
- - Todo 摘要最多显示 5 条,Active 优先;未满时由 Completed 补足,并以填充勾选、删除线和弱化文字呈现。单击主条目或圆形 Checkbox 都切换完成状态,Edit 与 Delete 是悬浮次操作。
78
- - Prompts 保存可选标题与必填多行纯文本;单击主条目直接 Insert,Edit 与 More 是悬浮次操作;Insert 或编辑成功后更新最近使用。
79
- - Prompts 使用 Star / Unstar 标记常用内容;所有条目都保留相同的 18px leading slot,Star 图标与 Files 类型图标共享 16px、secondary 灰色和相同 leading 间距。
80
- - 现有 SQLite 会在启动时把 `notes` 表及其 `pinned` 字段原地迁移为 `prompts` / `starred`,保留全部历史数据。
81
- - Files 只保存用户主动固定文件的 canonical absolute path 和元数据,不复制字节。
82
- - Summary 中的图片缩略图通过流式响应读取,支持 ETag / `304 Not Modified`,并拒绝超过 64 MiB 的预览请求;普通状态刷新不会重复等待全量文件扫描。
83
- - 单击文件名会 Attach:优先产生 Codex 原生附件,失败时由 Host 明确降级为 Composer 路径插入。
84
- - 行内 External Link 次操作会在 macOS Finder 或 Windows Explorer 中显示并选中磁盘源文件;CodexNotes 不提供文件重命名。
85
- - 取消固定永远不删除、移动或改写磁盘源文件。
86
- - 一个 SQLite writer 和 SSE revision stream 为所有 Surface / 窗口提供同一份状态。
87
-
88
- ## 验证
89
-
90
- ```bash
91
- npm test
92
- npm run check
93
- ```
94
-
95
- 产品验收与 Host 验收记录见 [IMPLEMENTATION_AUDIT.md](./IMPLEMENTATION_AUDIT.md),
96
- 当前实现证据与接手说明见 [HANDOFF.md](./HANDOFF.md),Phase 0 的非官方接口分类见
97
- [spikes/phase0/README.md](./spikes/phase0/README.md)。
98
- 视觉对照与阻断级问题结论见 [design-qa.md](./design-qa.md)。
99
- 后续视觉修改应先查阅 [codex-design-tokens.csv](./codex-design-tokens.csv):该表区分
100
- Codex 原生 token、项目派生语义和 Summary / Commit 的组件级固定值,避免把不同
101
- surface 的圆角、阴影或交互状态混用。
102
-
103
- ## 当前边界
104
-
105
- 第一版没有搜索、标签、独立文件预览器、自动索引、云同步、账号、Undo、自动备份或
106
- Today’s images 聚合。完整范围以 [PRD.md](./PRD.md) 为准。
25
+ 服务仅监听 loopback,默认端口 47832。沿用 CODEX_NOTES_PORT、CODEX_NOTES_DATA_DIR、CODEX_NOTES_DATABASE。设计依据见 [DESIGN.md](./DESIGN.md)。入口采用 Lucide book-open,拖拽手柄采用 Lucide menu。分类 SVG 来自当前 Codex 原生图标目录,未手绘替代路径。