minecodex 2.0.4 → 2.0.6

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 (100) hide show
  1. package/DESIGN.md +40 -1
  2. package/README.md +9 -3
  3. package/apps/dashboard/DESIGN.md +53 -31
  4. package/apps/dashboard/app.js +55 -39
  5. package/apps/dashboard/assets/OFL.txt +93 -0
  6. package/apps/dashboard/assets/info.svg +15 -0
  7. package/apps/dashboard/assets/moon.svg +13 -0
  8. package/apps/dashboard/assets/refresh-cw.svg +16 -0
  9. package/apps/dashboard/assets/silkscreen.ttf +0 -0
  10. package/apps/dashboard/assets/sun.svg +21 -0
  11. package/apps/dashboard/assets/terrain.js +89 -0
  12. package/apps/dashboard/assets/triangle-alert.svg +15 -0
  13. package/apps/dashboard/favicon.svg +12 -17
  14. package/apps/dashboard/index.html +24 -18
  15. package/apps/dashboard/styles.css +147 -181
  16. package/assets/brand/README.md +19 -0
  17. package/assets/brand/minecodex-icon-dark.svg +9 -0
  18. package/assets/brand/minecodex-icon-light.svg +9 -0
  19. package/assets/brand/minecodex-icon.svg +8 -0
  20. package/assets/brand/references/lucide-LICENSE.txt +43 -0
  21. package/assets/readme/hero.svg +93 -0
  22. package/features/hide-upsell-banner/README.md +7 -5
  23. package/features/hide-upsell-banner/codex-feature.json +2 -2
  24. package/features/hide-upsell-banner/src/hide-upsell-banner.mjs +30 -12
  25. package/features/images/DESIGN.md +117 -41
  26. package/features/images/README.md +26 -16
  27. package/features/images/codex-feature.json +6 -3
  28. package/features/images/src/adapters/codex/image-prompt.mjs +24 -2
  29. package/features/images/src/adapters/codex/image-sources.mjs +19 -0
  30. package/features/images/src/adapters/codex/library-api.mjs +2 -0
  31. package/features/images/src/adapters/codex/library-viewer.mjs +26 -2
  32. package/features/images/src/adapters/codex/native-image-modules.mjs +211 -0
  33. package/features/images/src/adapters/codex/native-image-source.mjs +449 -0
  34. package/features/images/src/backend/local-image-source.mjs +138 -0
  35. package/features/images/src/frontend/cloud-image-actions.mjs +41 -0
  36. package/features/images/src/frontend/local-image-source.mjs +195 -0
  37. package/features/images/src/library-prompt-page.mjs +163 -53
  38. package/features/images/src/library-prompt-source.mjs +9 -3
  39. package/features/images/src/prompt-index.mjs +14 -2
  40. package/features/model-slider/DESIGN.md +2 -0
  41. package/features/model-slider/codex-feature.json +2 -2
  42. package/features/model-slider/src/frontend/install.mjs +78 -11
  43. package/features/notes/DESIGN.md +7 -3
  44. package/features/notes/codex-feature.json +2 -2
  45. package/features/notes/src/frontend/entry.mjs +26 -9
  46. package/features/session-status/DESIGN.md +4 -2
  47. package/features/session-status/codex-feature.json +2 -2
  48. package/features/session-status/src/adapters/codex/session-log.mjs +2 -1
  49. package/features/session-status/src/domain/session-stats.mjs +17 -9
  50. package/features/session-status/src/session-status.mjs +47 -11
  51. package/features/sidebar-focus/DESIGN.md +4 -4
  52. package/features/sidebar-focus/codex-feature.json +3 -2
  53. package/features/sidebar-focus/src/adapters/codex/renderer.mjs +1 -1
  54. package/features/sidebar-focus/src/sidebar-focus.mjs +9 -7
  55. package/features/sites/DESIGN.md +25 -3
  56. package/features/sites/README.md +6 -2
  57. package/features/sites/codex-feature.json +3 -2
  58. package/features/sites/src/adapters/codex/renderer.mjs +25 -4
  59. package/features/sites/src/backend/project-sites.mjs +18 -6
  60. package/features/sites/src/backend/sites-service.mjs +22 -10
  61. package/features/sites/src/domain/discovery-candidates.mjs +53 -0
  62. package/features/sites/src/frontend/entry.mjs +76 -38
  63. package/features/sites/src/frontend/styles.css +3 -3
  64. package/features/unread-inbox/DESIGN.md +24 -25
  65. package/features/unread-inbox/codex-feature.json +3 -2
  66. package/features/unread-inbox/src/adapters/codex/modules.mjs +52 -0
  67. package/features/unread-inbox/src/adapters/codex/renderer.mjs +90 -0
  68. package/features/unread-inbox/src/domain/notifications.mjs +21 -0
  69. package/features/unread-inbox/src/injection-source.mjs +5 -2
  70. package/features/unread-inbox/src/unread-inbox.mjs +212 -297
  71. package/features/usage-meter/codex-feature.json +2 -2
  72. package/package.json +11 -3
  73. package/packages/cli/native/MineCodex.app/Contents/Info.plist +4 -2
  74. package/packages/cli/native/MineCodex.app/Contents/MacOS/MineCodexServiceManager +0 -0
  75. package/packages/cli/native/MineCodex.app/Contents/Resources/MineCodex.icns +0 -0
  76. package/packages/cli/native/MineCodex.app/Contents/_CodeSignature/CodeResources +11 -0
  77. package/packages/cli/native/README.md +31 -0
  78. package/packages/cli/src/browser-control-panel.mjs +48 -0
  79. package/packages/cli/src/commands.mjs +3 -1
  80. package/packages/cli/src/control-server.mjs +6 -3
  81. package/packages/cli/src/platform.mjs +5 -0
  82. package/packages/codex-adapter/README.md +2 -1
  83. package/packages/codex-adapter/src/renderer/environment.mjs +24 -6
  84. package/packages/runtime-host/README.md +10 -5
  85. package/packages/runtime-host/src/codex-injection.mjs +13 -9
  86. package/packages/runtime-host/src/codex-runtime.mjs +26 -5
  87. package/packages/runtime-host/src/feature-registry.mjs +1 -0
  88. package/packages/ui/DESIGN.md +9 -2
  89. package/packages/ui/assets/arrow-up-right.svg +14 -0
  90. package/packages/ui/assets/lucide-LICENSE.txt +43 -0
  91. package/packages/ui/src/components/account-menu.mjs +74 -0
  92. package/packages/ui/src/components/button.mjs +2 -0
  93. package/packages/ui/src/components/navigation-rail.mjs +18 -10
  94. package/packages/ui/src/components/page.mjs +9 -2
  95. package/packages/ui/src/foundation/layers.mjs +1 -0
  96. package/packages/ui/src/install.mjs +6 -1
  97. package/packages/ui/src/native/account-menu.mjs +21 -0
  98. package/packages/ui/src/native/navigation-rail.mjs +6 -1
  99. package/packages/ui/src/native/page.mjs +38 -0
  100. package/packages/ui/src/native/tokens.mjs +4 -0
package/DESIGN.md CHANGED
@@ -44,7 +44,8 @@ Token 名称是实现依据;括号内数值是本版本观测值。读取实
44
44
  | Cancel 等取消操作(`ghost`) | 透明、无描边;hover 才出现底色 | `--color-text-secondary`、`--color-background-primary-ghost-hover` |
45
45
  | 有实际功能的次级操作(outline,现有API tone为`secondary`) | 透明底、内描边 | `--color-text-primary`、`--color-border-primary-outline`、`--color-background-primary-outline-hover` |
46
46
  | 主操作(solid,现有API tone为`primary`) | 实色底与配对文字色 | `--color-background-primary-solid`、`--color-text-primary-solid`、`--color-background-primary-solid-hover` |
47
- | 警戒操作(现有`danger`) | 警戒色,独立于主操作;插件负责确认业务 | `--color-background-danger-soft-alpha`、`--color-background-danger-soft-alpha-hover`、`--color-text-danger` |
47
+ | 轻量警戒操作(现有`danger`) | 警戒色,独立于主操作;插件负责确认业务 | `--color-background-danger-soft-alpha`、`--color-background-danger-soft-alpha-hover`、`--color-text-danger` |
48
+ | 实色警戒操作(`danger-solid`;原生 React 为 `dangerSolid`) | 用于明确的删除/移除动作,实色警戒底与配对文字;操作范围和二次确认由业务决定 | `--color-background-danger-solid`、`--color-text-danger-solid`;Library 动作条 hover 为 solid 90% alpha |
48
49
 
49
50
  尺寸:`--control-size-lg`(36px)、`--control-font-size-md`(13px)、`--font-weight-normal`(原生按钮400)、`--radius-full`。水平 gutter 为 `--control-gutter-md × --control-gutter-pill-scaling`(15.96px),图文 gap 为 `--button-gap-lg`。焦点使用 `--color-ring`;禁用保留语义并阻止 hover 反馈。
50
51
 
@@ -65,6 +66,12 @@ Dropdown 的 `--shadow-dropdown-overlay` 当前指向 `--shadow-xl-spread`,该
65
66
 
66
67
  Composer菜单的锚点是实际输入框主体(当前语义标记 `data-composer-body`),不是包含diff和请求队列的外层Root。挂载位置、可用高度、zoom与碰撞行为沿用对应原生组件,不为修复坐标问题盲目提高z-index。
67
68
 
69
+ ## Viewer 附属信息面板
70
+
71
+ Codex 26.930.21537(2026-10-02 原生 File info DOM 与计算样式实测):viewer 右侧附属信息面板使用不透明 `bg-surface-elevated-secondary`、`rounded-2xl`(20px)和 Electron `elevation-prominent`;没有容器模糊,也不额外叠加 CSS border 或 `shadow-lg`。该 elevation 自带 0.5px 边缘及两层柔和投影,应直接沿用原生 utility。此分支不同于 Dialog、Dropdown 和页面内容面。
72
+
73
+ 标题栏使用固定高度、原生 text-base/semibold 标题及圆形 `button-toolbar` 的 `bg-text/5` 按钮分支,hover 为 `bg-text/10`。面板宽度、viewer 工具栏避让及正文间距由所选原生信息面板的业务布局确定,在功能 DESIGN.md 记录。Images Prompt 采用当前 File info 分支。
74
+
68
75
  ## Page 与 sidebar
69
76
 
70
77
  页面内容面使用 `--color-surface`,当前不透明、无模糊,`--radius-xl-base`(12px);外部PageSurface使用 `--app-shell-page-surface-shadow`,当前由 `--elevation-stroke-subtle` 与 `--shadow-card` 组成。区分内容面和窗口外壳,不重复施加阴影。
@@ -76,6 +83,18 @@ sidebar规则分状态:
76
83
  | 非peeking sidebar-navigation | `--color-surface` 65%(oklab混合),无CSS模糊、无浮窗阴影;可见桌面程度还取决于窗口材质 |
77
84
  | `data-slate-sidebar-peeking=true` | `--app-shell-sidebar-popover-background`强制alpha=1;`--app-shell-sidebar-popover-radius`当前为radius-xl-base减spacing(8px);`--hairline`、`--app-shell-sidebar-popover-border`内边缘与`--shadow-card` |
78
85
 
86
+ ### 全页面功能的页面壳选择
87
+
88
+ 后续功能开发独立全页面时,先按页面自身的导航需求选择页面壳。没有专属二级导航的页面默认使用无二级侧栏的 Page 分支,以原生 Projects 为行为参照;只有任务确实需要常驻二级导航时才选择带侧栏的布局。不能因入口来自 Home、Space、Scheduled 就继承它们的常驻二级侧栏。
89
+
90
+ 页面是否带常驻二级侧栏,与用户的侧栏展开/收起偏好是两个独立语义。无二级侧栏页面只保留 Navbar,内容从 Navbar 右侧开始,不调用侧栏收起动作,也不修改用户的展开偏好。悬浮 Home、Space、Scheduled 等入口时,原生二级侧栏仍叠加在页面上,页面内容不移位;鼠标可进入操作,离开后沿用原生退出行为。
91
+
92
+ 页面壳、原生导航适配与恢复由共享 UI 维护,功能只声明所需分支并负责自己的内容。新功能不得复制侧栏状态机、直接切换原生侧栏按钮,或另写隐藏侧栏的 CSS。退出页面时区分返回原页面与用户主动选择原生导航;卸载/禁用时释放页面壳并恢复所属状态。
93
+
94
+ 验收至少覆盖:从 Home、Space、Scheduled 进入时无常驻二级侧栏;原有展开偏好不变;三个入口的悬浮侧栏覆盖页面且可操作,内容不移位;退出、原生导航与卸载恢复正确。
95
+
96
+ 原生依据与当前实现:Codex 26.930.21537 的源码与隔离窗口确认该布局由路由决定。当前注入 Page 借用 `/projects` 作为原生页面壳,关闭时恢复原位置,用户主动选择原生导航时交还路由;这是版本相关的适配实现,不是功能的业务路由规范。后续原生接口变化只更新共享 Adapter,不改变上述页面语义。
97
+
79
98
  保留原生的固定展开、收起与hover状态。hover sidebar应能覆盖注入页面,鼠标能进入操作,移回页面后可正常收起;不要通过页面层级、pointer-events或额外遮罩挡住它。原生overflow和contain切换负责浮出与裁切,插件不复制一套侧栏状态机。
80
99
 
81
100
  ## 左侧 Navbar
@@ -97,6 +116,7 @@ sidebar规则分状态:
97
116
  层级数字是本版本实现值,不是原生CSS设计token;共同约束应集中维护语义角色与挂载点。
98
117
 
99
118
  - 内容页面位于原生sidebar悬浮层下方;MineCodex当前Page使用20,原生左侧栏40。
119
+ - 通知使用共享 `notification` 挂载角色(当前30),位于内容页面之上、原生sidebar与模态浮层之下;通知业务负责在原生侧栏 peeking 时暂停呈现,不抢占原生导航。
100
120
  - 原生Dialog、Dropdown、Popover和Composer Floating父容器通常为50;同数值不保证相同层级,仍受挂载顺序与stacking context影响。
101
121
  - 模态层挂在窗口根容器/原生Portal,遮罩覆盖整个窗口;不要嵌在Page的低层级容器内。原生`dialog.showModal()`使用top layer,不能仅用z-index推断。
102
122
  - Tooltip使用专用角色与层级;现有MineCodex实现值不是原生标准,不传播为通用token。
@@ -147,3 +167,22 @@ Navbar 在入口右侧居中,使用 `data-minecodex-tooltip-side="right"`;
147
167
  这里的规范名称是稳定的项目映射键。Codex 更新名称时补充原生别名,不能跟着重命名插件消费的项目 token。新增实际使用的语义才增加映射;不预建完整的第三方 token 系统。`native/theme.mjs` 为直接注入容器写入同一快照,Runtime Host 将它传给 iframe;卸载恢复原值。主题变化丢失某个 token 时清除旧项目值,继续使用该组件既有的明确默认值或不可用状态,不复用上一主题颜色。
148
168
 
149
169
  这些映射不改变原生组件分类、材质分支和 hover sidebar 层级,也不重写原生 Tailwind utility class。插件复用 native Adapter 返回的原生模板;特殊样式使用项目 token,Dialog、Button、Tooltip 等通用表现继续由组件库维护。
170
+
171
+
172
+ ### Library 多选动作条的实色警戒按钮
173
+
174
+ Codex 26.930.21537(2026-10-02 原生源码与隔离窗口):Library 选中动作条的 Delete 使用 `dangerSolid`、full radius、toolbar size,图标 16px,警戒底与白色配对文字;hover 使用 danger solid 90% alpha,禁用保留原生 40% opacity 与禁止交互。确认弹窗也使用实色警戒提交按钮。共享 DOM 入口为 `ui.action(button, "danger-solid")`,和既有 soft `danger` 分支并存,不迁移原有功能的语义。通用组件只提供视觉和交互状态,删除确认、隐藏/回收站/永久删除的范围由所属功能明确告知。
175
+
176
+ ## MineCodex 项目品牌图标
177
+
178
+ 项目标识采用实心镐子与 Codex 原生六瓣外圈,唯一可编辑源为 `assets/brand/minecodex-icon.svg`。内部镐子使用 `translate(6.24 6.24) scale(.48)`,纯填充、无描边;保留已确认的留白比例。此标识代表 MineCodex,不替换插件功能图标、模型厂商图标或 Codex 原生应用图标。
179
+
180
+ 内联使用 `currentColor` 跟随所在表面的语义文字色;外链明暗版本分别使用 `#0d0d0d` / `#fafafa`,透明背景。favicon 在 SVG 内通过 `prefers-color-scheme` 适配系统主题,Dashboard 标题通过 CSS mask 跟随页面 `--ink`。明暗版本、favicon 和 README 标识由 `npm run build:icons` 从同一源文件生成,修改几何后运行该命令。
181
+
182
+ macOS 后台服务 App 在明确的原生资源修订 1.0.1 中接入实心图标,采用浅色圆角底板,保证在明暗系统表面均可辨认。ICNS 来自同一项目几何。安装器先注销旧后台项,再验证并替换 bundle,最后重新注册;不能直接修改已安装的签名资源。后续普通 JavaScript 发布保留此原生 bundle 的签名身份,升级流程与验收边界见 `packages/cli/native/README.md`。
183
+
184
+ ### 账号菜单中的 MineCodex 入口
185
+
186
+ Codex 26.930.21537(2026-10-03 安装包源码):`profile-dropdown-items` 的 Settings 使用原生菜单行,紧接 Help 前的 Separator;`onOpenSettings` / `settingsShortcut` 标识其行为,外层 `onClose` 关闭账号面板。MineCodex 行插在 Settings 与 Separator 之间,复制脱离 React 的原生行,保留原始 Settings 及其所属层级。左侧使用实心项目标识,右侧使用 Lucide ArrowUpRight,复用 Settings 快捷键区域的 class、间距与 `text-codex-description` 颜色。
187
+
188
+ 入口随账号菜单重建恢复且只出现一次;补齐 Settings → MineCodex → Help 的上下键导航与 Enter/Space,保留原生 Escape 关闭行为。点击经已认证 Host bridge 调用 `mcx gui`;成功后关闭菜单,失败时显示实际错误。原生 UI 手动验收本次因 Computer Use 拒绝操作应用而未完成;隔离 DOM 契约页验证了位置、明暗颜色、点击、键盘、重建去重与清理。
package/README.md CHANGED
@@ -13,10 +13,10 @@ MineCodex 给 Codex Desktop 增加一组轻量、本地优先的扩展功能。
13
13
  | **Model Slider** | 更清晰、更顺滑的模型切换面板:具备品牌标识、常用收藏与流畅交互 |
14
14
  | **Prompts** | 专页分类管理提示词与调用统计,输入框 Quick Prompts 快速调用 |
15
15
  | **Usage Meter** | 在 Help 与 Account 之间显示剩余额度环,悬浮查看周 / 五小时额度,点击进入 Usage 设置 |
16
- | **Images** | 在原生 Library 图片详情中查看并复制提示词 |
16
+ | **Images** | 在原生 Images 中浏览本地 Image Gen 图片,查看并复制提示词 |
17
17
  | **Hide Upsell Banner** | 隐藏输入框上方的用量提示横幅,仅优化界面展示,不影响正常的用量统计 |
18
18
  | **Session Status** | 在输入框下方查看当前任务的步骤、观测速度、Token 用量与上下文明细 |
19
- | **Task Switcher** | 点击聊天标题或标签页前的任务按钮,查看并切换工作中与未读会话 |
19
+ | **Task Switcher** | 会话侧栏不可见时显示通知条,点击跳转到已完成未读或需要处理的会话 |
20
20
 
21
21
  ## 安装
22
22
 
@@ -56,7 +56,7 @@ mcx open
56
56
 
57
57
  ## 工作原理
58
58
 
59
- MineCodex 统一管理 Codex 内的菜单入口、主题风格与界面适配。Model Slider、Prompts 各自管理操作界面,Images 增强原生 Library 的提示词查看;Session Status 只读汇总本机 Codex 会话事件,Hide Upsell Banner 优化界面展示。所有功能按需开启,关闭后即停止运行。
59
+ MineCodex 统一管理 Codex 内的菜单入口、主题风格与界面适配。Model Slider、Prompts 各自管理操作界面,Images 在原生 Images 中接入本地生成图片和提示词查看;Session Status 只读汇总本机 Codex 会话事件,Hide Upsell Banner 优化界面展示。所有功能按需开启,关闭后即停止运行。
60
60
 
61
61
  ## 开发
62
62
 
@@ -68,6 +68,8 @@ npm run pack:check
68
68
  npm run pack:audit
69
69
  ```
70
70
 
71
+ 维护者发布流程见 [GitHub Actions 发布说明](docs/npm-publishing.md)。发布工作流仅允许维护者本人触发,使用 npm 可信发布,无需长期访问令牌。
72
+
71
73
  ## 许可
72
74
 
73
75
  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)。
@@ -75,3 +77,7 @@ MineCodex 是非官方独立扩展项目,与 OpenAI 无关联。项目自有
75
77
  ### Sidebar Focus / 侧栏聚焦
76
78
 
77
79
  展开侧栏时,点击铃铛与搜索之间的聚焦按钮,保留当前任务路径并收起其他分支。再次点击恢复原先布局;切换任务时跟随路径,手动调整分支时退出聚焦。
80
+
81
+ ### 从账号菜单打开控制台
82
+
83
+ 在 Codex Navbar 底部打开账号菜单,点击 Settings 下方的 **MineCodex ↗**,即可在外部默认浏览器打开本地控制台,等同于 `mcx gui`。macOS 默认 Safari、Chrome/Chromium、Edge、Brave 和 Vivaldi 会定位已有控制台标签页,没有时才新建;首次定位可能需要系统自动化权限。其他浏览器与 Windows 仍由系统打开 URL。
@@ -1,45 +1,67 @@
1
1
  # MineCodex Dashboard Design System
2
2
 
3
- ## Theme
3
+ The standalone control panel follows the root DESIGN.md semantic color and interaction
4
+ contract. It uses local light/dark tokens because no Codex renderer theme bridge exists here.
4
5
 
5
- The control panel is a restrained Codex-native settings surface. It follows system light/dark
6
- appearance, uses one system sans family, and reserves accent color for enabled switches, focus,
7
- and the primary restart action.
6
+ ## Layout and reference
8
7
 
9
- ## Color
8
+ Reference: VPN Dashboard settings source, inspected 2026-10-03. Its card grid uses a 16px
9
+ radius/gap, top icon and trailing arrow, followed by a title and description. The live URL
10
+ required login; this reference is source-based, not a live visual match.
10
11
 
11
- Use semantic OKLCH tokens rather than brand colors:
12
+ MineCodex uses one centered 1064px column without a sidebar or category groups. The header
13
+ holds the shared brand and appearance control; a runtime notice appears below only when the service needs attention.
14
+ There is no tagline or visible section introduction. Save feedback remains available to screen readers.
15
+ Nine independent cards form a three-column grid, two columns at 960px and one at 600px.
16
+ Each card has an icon and switch at the top, name and purpose below, and a readable actual-state line
17
+ at the bottom of every card. Enabled switches and runtime state remain distinct information. A card is not itself clickable. The footer shows only the MineCodex version
18
+ and adjacent update check (16px gap, regular weight, refresh icon), with persistent update feedback below it. No install path or plugin versions.
12
19
 
13
- - Canvas: neutral near-white / near-black.
14
- - Surface: one subtle elevation step from the canvas.
15
- - Ink: high-contrast neutral foreground.
16
- - Secondary and tertiary ink: still meet AA for their text size.
17
- - Border: quiet neutral separator.
18
- - Accent: Codex-like blue used only for action and active state.
19
- - Success and warning: paired with labels or icons, never color alone.
20
+ ## Tokens and components
20
21
 
21
- ## Typography
22
+ - Canvas: cool #edf1f7 / deep blue #101722, with a decorative ASCII voxel world.
23
+ - Surface: white / #1b2533, with a hard 3px lower edge for a block-like silhouette; no blur.
24
+ - Border: #d7dfe9 / #334359; blue #3478d4 / #71a8f3 switches and active state accents.
25
+ - Ink, secondary ink, accent, success, warning and danger use the local semantic CSS tokens.
26
+ - `--card-radius`: 8px; `--grid-gap`: 16px; `--card-padding`: 24px.
27
+ - Card minimum height: 216px desktop, 204px mobile; content can grow without clipping.
28
+ - System sans family; pixel brand title 36–64px/700, feature name 16px/600, description 14px/1.65,
29
+ state metadata 13px/1.5. Each description is one short statement of purpose; remove promotional filler while preserving each feature’s actual state.
30
+ - Feature icons: 24px display size, uniform 2.1-unit stroke in a 24×24 viewBox, matching Prompts. The control-server normalizes only dashboard output; original injected assets stay unchanged.
31
+ - Icons: existing manifest assets, with official Lucide bell, panel-left and globe for
32
+ Task Switcher, Sidebar Focus and Sites. Lucide ISC license is retained in
33
+ `packages/ui/assets/lucide-LICENSE.txt`.
34
+ - Checkbox switches expose switch semantics, feature labels, descriptions and state text.
35
+ Track is 36×21px with a 32px-high label hit area. Pending requests disable only their switch;
36
+ failures restore the previous value and focus. Enabled and active remain distinct facts.
37
+ - Appearance uses the VPN reference’s two-segment sun/moon capsule (32px segments, 4px gap and padding). A sliding selected circle and aria-pressed expose selection. It initially follows the system; manual choices persist locally.
38
+ - Restart and error notices use a neutral card, colored left edge, status icon, compact copy and trailing action; toast errors share that material. Restart notice appears only for a pending restart. No automatic restart.
39
+ - Update checks retain loading, result and retryable failure states; installation stays with
40
+ the existing `mcx update` flow.
22
41
 
23
- Use `-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`. Page title is 20px/600,
24
- plugin names 15px/600, body text 13px/1.5, and compact metadata 12px. No display type,
25
- uppercase tracking, or fluid heading sizes.
42
+ ## Motion
26
43
 
27
- ## Layout
44
+ `--ease-out: cubic-bezier(0.23, 1, 0.32, 1)`. Switch thumb: 180ms transform;
45
+ button press: 140ms transform to scale(0.97). No stagger, card lift, layout motion or continuous
46
+ loading shimmer. Hover is limited to fine pointers. Keyboard actions disable transitions;
47
+ reduced-motion preferences disable movement. Focus rings remain visible in both themes.
28
48
 
29
- The page uses a centered 880px content column with a compact header, one restart notice when
30
- needed, a vertical plugin settings group, and a low-emphasis diagnostics footer. At narrow widths
31
- the rows wrap their status and switch controls beneath the copy without horizontal scrolling.
49
+ ## Project identity
32
50
 
33
- ## Components
51
+ The masthead uses self-hosted Silkscreen Bold (SIL OFL, bundled license in assets/OFL.txt),
52
+ with a stone-colored face, dark outline and stepped text shadow inspired by Minecraft lettering.
53
+ The shared pickaxe mark is 76px desktop / 48px mobile. Body and state text retain the legible
54
+ project sans-serif contract. This is a dashboard-specific visual treatment; the brand geometry
55
+ and other injected plugin UI remain governed by root DESIGN.md.
34
56
 
35
- - Plugin row: icon, name, description, actual-state label, and native checkbox switch.
36
- - Status dot: always accompanied by readable state text.
37
- - Restart notice: inline surface between header and plugin group, not a modal.
38
- - Buttons: one primary restart button and quiet secondary actions with full hover, focus,
39
- active, disabled, and loading states.
40
- - Loading: skeleton rows preserve layout; errors use an inline retry surface.
57
+ Dashboard icons are official Lucide SVGs, covered by packages/ui/assets/lucide-LICENSE.txt.
58
+ All font and icon assets are served locally and included in the npm package.
41
59
 
42
- ## Motion
60
+ ## ASCII agent world
43
61
 
44
- Use 160–200ms ease-out transitions only for switch position, button feedback, and the restart
45
- notice appearing. Disable nonessential transitions under `prefers-reduced-motion: reduce`.
62
+ A fixed, pointer-transparent and aria-hidden canvas renders deterministic terraced terrain,
63
+ ASCII water, paths and coding nodes (>_, {}, git, </>, [AI], $). It rotates around its vertical
64
+ axis once every 240 seconds, rendered at at most 20 fps and device pixel ratio capped at 2.
65
+ The world stays behind opaque cards; text and controls never rotate. Reduced motion freezes
66
+ the view, hidden tabs pause the animation. The terrain module has no external dependency
67
+ or network request and does not consume user or session data.
@@ -3,13 +3,11 @@ document.documentElement.lang = locale;
3
3
 
4
4
  const copy = {
5
5
  en: {
6
- subtitle: "Personal plugins for Codex",
7
6
  connecting: "Connecting…",
8
7
  runtimeActive: "Active in Codex",
9
8
  runtimeStopped: "Runtime unavailable",
10
9
  runtimeNotInjected: "Runtime running — restart Codex to apply plugins",
11
- plugins: "Plugins",
12
- pluginsCopy: "Choose which enhancements appear in Codex.",
10
+ plugins: "Your extensions",
13
11
  restartTitle: "Restart Codex to apply changes",
14
12
  restartCopy: "Your plugin choices are saved. Restart when you are ready.",
15
13
  restart: "Restart Codex",
@@ -30,10 +28,9 @@ const copy = {
30
28
  loadErrorCopy: "Check that the background service is running.",
31
29
  retry: "Try again",
32
30
  version: "Version",
33
- installation: "Installation",
34
- runtimeHost: "RuntimeHost",
35
- running: "Running",
36
- stopped: "Stopped",
31
+ darkAppearance: "Dark appearance",
32
+ lightAppearance: "Light appearance",
33
+ updateFailed: "Couldn’t check for updates. Please try again.",
37
34
  checkUpdates: "Check for updates",
38
35
  checkingUpdates: "Checking…",
39
36
  current: "You’re up to date.",
@@ -43,13 +40,11 @@ const copy = {
43
40
  failureDetail: "Health or renderer status could not be confirmed.",
44
41
  },
45
42
  "zh-CN": {
46
- subtitle: "为 Codex 准备的个人插件",
47
43
  connecting: "正在连接…",
48
44
  runtimeActive: "已在 Codex 中生效",
49
45
  runtimeStopped: "运行服务不可用",
50
46
  runtimeNotInjected: "运行服务已启动——重启 Codex 以应用插件",
51
- plugins: "插件",
52
- pluginsCopy: "选择要在 Codex 中启用的增强功能。",
47
+ plugins: "功能扩展",
53
48
  restartTitle: "重启 Codex 以应用更改",
54
49
  restartCopy: "插件选择已经保存,可以在准备好后统一重启。",
55
50
  restart: "重启 Codex",
@@ -70,10 +65,9 @@ const copy = {
70
65
  loadErrorCopy: "请检查后台服务是否正在运行。",
71
66
  retry: "重试",
72
67
  version: "版本",
73
- installation: "安装位置",
74
- runtimeHost: "运行服务",
75
- running: "运行中",
76
- stopped: "已停止",
68
+ darkAppearance: "深色外观",
69
+ lightAppearance: "浅色外观",
70
+ updateFailed: "暂时无法检查更新,请稍后重试。",
77
71
  checkUpdates: "检查更新",
78
72
  checkingUpdates: "正在检查…",
79
73
  current: "当前已是最新版本。",
@@ -85,7 +79,6 @@ const copy = {
85
79
  }[locale];
86
80
 
87
81
  const elements = {
88
- subtitle: document.querySelector("#subtitle"),
89
82
  runtimeDot: document.querySelector("#runtime-dot"),
90
83
  runtimeLabel: document.querySelector("#runtime-label"),
91
84
  restartNotice: document.querySelector("#restart-notice"),
@@ -93,7 +86,6 @@ const elements = {
93
86
  restartCopy: document.querySelector("#restart-copy"),
94
87
  restartButton: document.querySelector("#restart-button"),
95
88
  pluginsHeading: document.querySelector("#plugins-heading"),
96
- pluginsCopy: document.querySelector("#plugins-copy"),
97
89
  saveState: document.querySelector("#save-state"),
98
90
  pluginList: document.querySelector("#plugin-list"),
99
91
  loadError: document.querySelector("#load-error"),
@@ -102,9 +94,9 @@ const elements = {
102
94
  retryButton: document.querySelector("#retry-button"),
103
95
  versionLabel: document.querySelector("#version-label"),
104
96
  versionValue: document.querySelector("#version-value"),
105
- installLabel: document.querySelector("#install-label"),
106
- hostLabel: document.querySelector("#host-label"),
107
- hostValue: document.querySelector("#host-value"),
97
+ themeToggle: document.querySelector(".theme-toggle"),
98
+ themeButtons: [...document.querySelectorAll("[data-theme-choice]")],
99
+ updateResult: document.querySelector("#update-result"),
108
100
  updateButton: document.querySelector("#update-button"),
109
101
  toast: document.querySelector("#toast"),
110
102
  };
@@ -114,20 +106,16 @@ let toastTimer = null;
114
106
  const pendingFeatureIds = new Set();
115
107
 
116
108
  function applyStaticCopy() {
117
- elements.subtitle.textContent = copy.subtitle;
118
109
  elements.runtimeLabel.textContent = copy.connecting;
119
110
  elements.restartTitle.textContent = copy.restartTitle;
120
111
  elements.restartCopy.textContent = copy.restartCopy;
121
112
  elements.restartButton.querySelector(".button-label").textContent = copy.restart;
122
113
  elements.pluginsHeading.textContent = copy.plugins;
123
- elements.pluginsCopy.textContent = copy.pluginsCopy;
124
114
  elements.errorTitle.textContent = copy.loadError;
125
115
  elements.errorCopy.textContent = copy.loadErrorCopy;
126
116
  elements.retryButton.textContent = copy.retry;
127
117
  elements.versionLabel.textContent = copy.version;
128
- elements.installLabel.textContent = copy.installation;
129
- elements.hostLabel.textContent = copy.runtimeHost;
130
- elements.updateButton.textContent = copy.checkUpdates;
118
+ elements.updateButton.querySelector(".button-label").textContent = copy.checkUpdates;
131
119
  }
132
120
 
133
121
  function featureState(feature) {
@@ -164,23 +152,25 @@ function createPluginCard(feature) {
164
152
  icon.setAttribute("aria-hidden", "true");
165
153
  const image = element("img");
166
154
  image.alt = "";
167
- image.width = 18;
168
- image.height = 18;
155
+ image.width = 24;
156
+ image.height = 24;
169
157
  icon.append(image);
170
158
 
171
159
  const pluginCopy = element("div", "plugin-copy");
172
160
  const titleLine = element("div", "plugin-title-line");
173
161
  const name = element("h3", "plugin-name");
174
- const version = element("span", "plugin-version");
175
- titleLine.append(name, version);
162
+ titleLine.append(name);
176
163
  const description = element("p", "plugin-description");
177
164
  pluginCopy.append(titleLine, description);
178
165
 
179
- const control = element("div", "plugin-control");
166
+ const heading = element("div", "plugin-topline");
180
167
  const label = element("label", "switch");
181
168
  const input = element("input");
182
169
  input.type = "checkbox";
183
170
  input.name = `feature-${feature.id}`;
171
+ input.setAttribute("role", "switch");
172
+ input.setAttribute("aria-describedby", `description-${feature.id} state-${feature.id}`);
173
+ description.id = `description-${feature.id}`;
184
174
  const track = element("span", "switch-track");
185
175
  track.setAttribute("aria-hidden", "true");
186
176
  label.append(input, track);
@@ -188,9 +178,10 @@ function createPluginCard(feature) {
188
178
  const dot = element("span", "status-dot");
189
179
  dot.setAttribute("aria-hidden", "true");
190
180
  const stateLabel = element("span", "plugin-state-label");
181
+ stateLabel.id = `state-${feature.id}`;
191
182
  pluginState.append(dot, stateLabel);
192
- control.append(label, pluginState);
193
- card.append(icon, pluginCopy, control);
183
+ heading.append(icon, label);
184
+ card.append(heading, pluginCopy, pluginState);
194
185
  return card;
195
186
  }
196
187
 
@@ -199,14 +190,12 @@ function updatePluginCard(card, feature) {
199
190
  const input = card.querySelector('input[type="checkbox"]');
200
191
  const image = card.querySelector(".plugin-icon img");
201
192
  const name = card.querySelector(".plugin-name");
202
- const version = card.querySelector(".plugin-version");
203
193
  const description = card.querySelector(".plugin-description");
204
194
  const dot = card.querySelector(".plugin-state .status-dot");
205
195
  const stateLabel = card.querySelector(".plugin-state-label");
206
196
  card.dataset.featureId = feature.id;
207
- image.src = feature.iconUrl;
197
+ image.src = `${feature.iconUrl}?v=stroke-2.1`;
208
198
  name.textContent = feature.label;
209
- version.textContent = feature.version;
210
199
  description.textContent = feature.description;
211
200
  input.checked = Boolean(feature.enabled);
212
201
  input.disabled = pendingFeatureIds.has(feature.id);
@@ -249,13 +238,13 @@ function renderRuntime() {
249
238
  && Number(renderer.injectedTargets) > 0,
250
239
  );
251
240
  const active = runtimeRunning && rendererCurrent;
241
+ elements.runtimeLabel.closest(".overview").hidden = active;
252
242
  elements.runtimeDot.className = `status-dot ${active ? "is-active" : "is-warning"}`;
253
243
  elements.runtimeLabel.textContent = active
254
244
  ? copy.runtimeActive
255
245
  : runtimeRunning
256
246
  ? copy.runtimeNotInjected
257
247
  : copy.runtimeStopped;
258
- elements.hostValue.textContent = runtimeRunning ? copy.running : copy.stopped;
259
248
  elements.versionValue.textContent = state?.version ?? "—";
260
249
  elements.restartNotice.hidden = !state?.pendingRestart;
261
250
  }
@@ -302,6 +291,7 @@ async function load() {
302
291
  elements.loadError.hidden = false;
303
292
  elements.runtimeDot.className = "status-dot is-warning";
304
293
  elements.runtimeLabel.textContent = copy.runtimeStopped;
294
+ elements.runtimeLabel.closest(".overview").hidden = true;
305
295
  }
306
296
  }
307
297
 
@@ -369,19 +359,45 @@ elements.retryButton.addEventListener("click", () => {
369
359
 
370
360
  elements.updateButton.addEventListener("click", async () => {
371
361
  elements.updateButton.disabled = true;
372
- elements.updateButton.textContent = copy.checkingUpdates;
362
+ elements.updateButton.querySelector(".button-label").textContent = copy.checkingUpdates;
363
+ elements.updateResult.hidden = true;
373
364
  try {
374
365
  const response = await fetch("/api/update");
375
366
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
376
367
  const update = await response.json();
377
- showToast(update.available ? copy.updateAvailable(update.latest) : copy.current);
368
+ elements.updateResult.textContent = update.available ? copy.updateAvailable(update.latest) : copy.current;
369
+ elements.updateResult.hidden = false;
378
370
  } catch {
379
- showToast(copy.loadErrorCopy);
371
+ elements.updateResult.textContent = copy.updateFailed;
372
+ elements.updateResult.hidden = false;
380
373
  } finally {
381
374
  elements.updateButton.disabled = false;
382
- elements.updateButton.textContent = copy.checkUpdates;
375
+ elements.updateButton.querySelector(".button-label").textContent = copy.checkUpdates;
383
376
  }
384
377
  });
385
378
 
379
+ const systemTheme = matchMedia("(prefers-color-scheme: dark)");
380
+ let selectedTheme = null;
381
+ try { selectedTheme = localStorage.getItem("minecodex-appearance"); } catch { /* 存储不可用时仍允许本次切换。 */ }
382
+ function applyTheme() {
383
+ const dark = selectedTheme === "dark" || (selectedTheme !== "light" && systemTheme.matches);
384
+ document.documentElement.dataset.theme = dark ? "dark" : "light";
385
+ elements.themeToggle.setAttribute("aria-label", locale === "zh-CN" ? "外观" : "Appearance");
386
+ for (const button of elements.themeButtons) {
387
+ button.setAttribute("aria-label", button.dataset.themeChoice === "light" ? copy.lightAppearance : copy.darkAppearance);
388
+ button.setAttribute("aria-pressed", String(button.dataset.themeChoice === (dark ? "dark" : "light")));
389
+ }
390
+ }
391
+ elements.themeToggle.addEventListener("click", (event) => {
392
+ const button = event.target.closest("[data-theme-choice]");
393
+ if (!button) return;
394
+ selectedTheme = button.dataset.themeChoice;
395
+ try { localStorage.setItem("minecodex-appearance", selectedTheme); } catch { /* 保留内存中的选择。 */ }
396
+ applyTheme();
397
+ });
398
+ systemTheme.addEventListener("change", applyTheme);
399
+ document.addEventListener("keydown", () => { document.documentElement.dataset.input = "keyboard"; });
400
+ document.addEventListener("pointerdown", () => { document.documentElement.dataset.input = "pointer"; });
386
401
  applyStaticCopy();
402
+ applyTheme();
387
403
  void load();
@@ -0,0 +1,93 @@
1
+ Copyright 2001 The Silkscreen Project Authors (https://github.com/googlefonts/silkscreen)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ https://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,15 @@
1
+ <svg
2
+ xmlns="http://www.w3.org/2000/svg"
3
+ width="24"
4
+ height="24"
5
+ viewBox="0 0 24 24"
6
+ fill="none"
7
+ stroke="currentColor"
8
+ stroke-width="2"
9
+ stroke-linecap="round"
10
+ stroke-linejoin="round"
11
+ >
12
+ <circle cx="12" cy="12" r="10" />
13
+ <path d="M12 16v-4" />
14
+ <path d="M12 8h.01" />
15
+ </svg>
@@ -0,0 +1,13 @@
1
+ <svg
2
+ xmlns="http://www.w3.org/2000/svg"
3
+ width="24"
4
+ height="24"
5
+ viewBox="0 0 24 24"
6
+ fill="none"
7
+ stroke="currentColor"
8
+ stroke-width="2"
9
+ stroke-linecap="round"
10
+ stroke-linejoin="round"
11
+ >
12
+ <path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401" />
13
+ </svg>
@@ -0,0 +1,16 @@
1
+ <svg
2
+ xmlns="http://www.w3.org/2000/svg"
3
+ width="24"
4
+ height="24"
5
+ viewBox="0 0 24 24"
6
+ fill="none"
7
+ stroke="currentColor"
8
+ stroke-width="2"
9
+ stroke-linecap="round"
10
+ stroke-linejoin="round"
11
+ >
12
+ <path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" />
13
+ <path d="M21 3v5h-5" />
14
+ <path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" />
15
+ <path d="M8 16H3v5" />
16
+ </svg>
@@ -0,0 +1,21 @@
1
+ <svg
2
+ xmlns="http://www.w3.org/2000/svg"
3
+ width="24"
4
+ height="24"
5
+ viewBox="0 0 24 24"
6
+ fill="none"
7
+ stroke="currentColor"
8
+ stroke-width="2"
9
+ stroke-linecap="round"
10
+ stroke-linejoin="round"
11
+ >
12
+ <circle cx="12" cy="12" r="4" />
13
+ <path d="M12 2v2" />
14
+ <path d="M12 20v2" />
15
+ <path d="m4.93 4.93 1.41 1.41" />
16
+ <path d="m17.66 17.66 1.41 1.41" />
17
+ <path d="M2 12h2" />
18
+ <path d="M20 12h2" />
19
+ <path d="m6.34 17.66-1.41 1.41" />
20
+ <path d="m19.07 4.93-1.41 1.41" />
21
+ </svg>