minecodex 2.0.5 → 2.0.7

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 (85) hide show
  1. package/DESIGN.md +19 -0
  2. package/README.md +5 -1
  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 +18 -3
  26. package/features/images/README.md +4 -3
  27. package/features/images/codex-feature.json +2 -2
  28. package/features/images/src/adapters/codex/native-image-modules.mjs +220 -0
  29. package/features/images/src/adapters/codex/native-image-source.mjs +113 -198
  30. package/features/images/src/assets/prompts-menu.svg +15 -0
  31. package/features/images/src/frontend/cloud-image-actions.mjs +8 -6
  32. package/features/images/src/frontend/local-image-source.mjs +2 -1
  33. package/features/images/src/library-prompt-page.mjs +10 -4
  34. package/features/images/src/library-prompt-source.mjs +4 -5
  35. package/features/model-slider/codex-feature.json +2 -2
  36. package/features/notes/DESIGN.md +1 -1
  37. package/features/notes/codex-feature.json +2 -2
  38. package/features/notes/src/frontend/entry.mjs +1 -1
  39. package/features/session-status/codex-feature.json +2 -2
  40. package/features/sidebar-focus/DESIGN.md +4 -4
  41. package/features/sidebar-focus/codex-feature.json +3 -2
  42. package/features/sidebar-focus/src/adapters/codex/renderer.mjs +1 -1
  43. package/features/sidebar-focus/src/sidebar-focus.mjs +9 -7
  44. package/features/sites/DESIGN.md +25 -3
  45. package/features/sites/README.md +6 -2
  46. package/features/sites/codex-feature.json +3 -2
  47. package/features/sites/src/adapters/codex/renderer.mjs +25 -4
  48. package/features/sites/src/backend/project-sites.mjs +18 -6
  49. package/features/sites/src/backend/sites-service.mjs +22 -10
  50. package/features/sites/src/domain/discovery-candidates.mjs +53 -0
  51. package/features/sites/src/frontend/entry.mjs +76 -38
  52. package/features/sites/src/frontend/styles.css +3 -3
  53. package/features/unread-inbox/DESIGN.md +24 -25
  54. package/features/unread-inbox/codex-feature.json +3 -2
  55. package/features/unread-inbox/src/adapters/codex/modules.mjs +52 -0
  56. package/features/unread-inbox/src/adapters/codex/renderer.mjs +90 -0
  57. package/features/unread-inbox/src/domain/notifications.mjs +21 -0
  58. package/features/unread-inbox/src/injection-source.mjs +5 -2
  59. package/features/unread-inbox/src/unread-inbox.mjs +212 -297
  60. package/features/usage-meter/codex-feature.json +2 -2
  61. package/package.json +11 -3
  62. package/packages/cli/native/MineCodex.app/Contents/Info.plist +4 -2
  63. package/packages/cli/native/MineCodex.app/Contents/MacOS/MineCodexServiceManager +0 -0
  64. package/packages/cli/native/MineCodex.app/Contents/Resources/MineCodex.icns +0 -0
  65. package/packages/cli/native/MineCodex.app/Contents/_CodeSignature/CodeResources +11 -0
  66. package/packages/cli/native/README.md +31 -0
  67. package/packages/cli/src/browser-control-panel.mjs +48 -0
  68. package/packages/cli/src/commands.mjs +3 -1
  69. package/packages/cli/src/control-server.mjs +6 -3
  70. package/packages/cli/src/platform.mjs +5 -0
  71. package/packages/runtime-host/README.md +10 -5
  72. package/packages/runtime-host/src/codex-injection.mjs +12 -3
  73. package/packages/runtime-host/src/codex-runtime.mjs +10 -0
  74. package/packages/ui/DESIGN.md +5 -1
  75. package/packages/ui/assets/arrow-up-right.svg +13 -0
  76. package/packages/ui/assets/lucide-LICENSE.txt +43 -0
  77. package/packages/ui/assets/minecodex-menu-16.svg +7 -0
  78. package/packages/ui/assets/minecodex-menu-20.svg +7 -0
  79. package/packages/ui/src/components/account-menu.mjs +88 -0
  80. package/packages/ui/src/components/page.mjs +2 -2
  81. package/packages/ui/src/foundation/layers.mjs +1 -0
  82. package/packages/ui/src/install.mjs +3 -0
  83. package/packages/ui/src/native/account-menu.mjs +21 -0
  84. package/packages/ui/src/native/page.mjs +17 -2
  85. package/packages/ui/src/native/tokens.mjs +1 -0
package/DESIGN.md CHANGED
@@ -116,6 +116,7 @@ sidebar规则分状态:
116
116
  层级数字是本版本实现值,不是原生CSS设计token;共同约束应集中维护语义角色与挂载点。
117
117
 
118
118
  - 内容页面位于原生sidebar悬浮层下方;MineCodex当前Page使用20,原生左侧栏40。
119
+ - 通知使用共享 `notification` 挂载角色(当前30),位于内容页面之上、原生sidebar与模态浮层之下;通知业务负责在原生侧栏 peeking 时暂停呈现,不抢占原生导航。
119
120
  - 原生Dialog、Dropdown、Popover和Composer Floating父容器通常为50;同数值不保证相同层级,仍受挂载顺序与stacking context影响。
120
121
  - 模态层挂在窗口根容器/原生Portal,遮罩覆盖整个窗口;不要嵌在Page的低层级容器内。原生`dialog.showModal()`使用top layer,不能仅用z-index推断。
121
122
  - Tooltip使用专用角色与层级;现有MineCodex实现值不是原生标准,不传播为通用token。
@@ -171,3 +172,21 @@ Navbar 在入口右侧居中,使用 `data-minecodex-tooltip-side="right"`;
171
172
  ### Library 多选动作条的实色警戒按钮
172
173
 
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 安装包源码与隔离 DOM):`profile-dropdown-items` 的 Settings 使用原生菜单行,紧接 Help 前的 Separator;`onOpenSettings` / `settingsShortcut` 标识其行为,外层 `onClose` 关闭账号面板。MineCodex 行插在 Settings 与 Separator 之间,复制脱离 React 的原生行,保留原始 Settings 及其所属层级。左侧使用实心项目标识,右侧使用 Lucide ArrowUpRight;右侧沿用快捷键区域的间距,图标使用原生 Help 右侧箭头的 `text-tertiary` 色与 opacity 1(当前浅色为 rgba(13,13,13,0.482)),不套左侧图标的 0.75 / hover、focus 时 1 的透明度。隔离 DOM 确认左侧普通态与 Settings 同为 rgb(13,13,13)、opacity 0.75,hover / focus 时为 1。
187
+
188
+ 左侧保留原生 CompactSource / LeadingSource 两套结构,只替换其中 SVG;分别继承原生 SVG 的 width、height、class 和 style。菜单标识复用当前 `codex-light-16` / `codex-light-20` 的真实填充外圈路径,引用存于 `assets/brand/references/codex-menu-16.svg` / `codex-menu-20.svg`;内部镐子仍从品牌源生成,由 `npm run build:icons` 输出两套 `packages/ui/assets/minecodex-menu-*.svg`。不能将 24px 品牌外圈缩小代替原生菜单的路径粗细。账号图标沿用原生 `currentColor` 与 `--profile-menu-icon-opacity`(桌面默认 0.75,行 hover / focus 时为 1),不另设固定灰色。此项为安装包源码核对,实际窗口视觉仍待手动验收。
189
+
190
+ 右侧箭头的两段几何合并在单个 SVG path 中一次描边,避免 `currentColor` 自带 alpha 时,两个独立 path 在交叉点重复叠色。保留原资源的形状与端点规则;24px viewBox 中 stroke-width 为 1.575,在 16px 显示时实际线宽 1.05px,与原生 Help 箭头的线条粗细对齐。
191
+
192
+ 入口随账号菜单重建恢复且只出现一次;补齐 Settings → MineCodex → Help 的上下键导航与 Enter/Space,保留原生 Escape 关闭行为。点击经已认证 Host bridge 调用 `mcx gui`;成功后关闭菜单,失败时显示实际错误。原生 UI 手动验收本次因 Computer Use 拒绝操作应用而未完成;隔离 DOM 契约页验证了位置、明暗颜色、点击、键盘、重建去重与清理。
package/README.md CHANGED
@@ -16,7 +16,7 @@ MineCodex 给 Codex Desktop 增加一组轻量、本地优先的扩展功能。
16
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
 
@@ -77,3 +77,7 @@ MineCodex 是非官方独立扩展项目,与 OpenAI 无关联。项目自有
77
77
  ### Sidebar Focus / 侧栏聚焦
78
78
 
79
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>
@@ -0,0 +1,89 @@
1
+ // 纯装饰字符地形;不读取会话数据,也不参与界面交互。
2
+ const canvas = document.querySelector('#agent-world');
3
+ const context = canvas.getContext('2d');
4
+ const reduced = matchMedia('(prefers-reduced-motion: reduce)');
5
+ const points = [];
6
+ const agents = [];
7
+ const heightAt = (x, z) => {
8
+ const ridge = Math.sin(x * .16 + Math.cos(z * .13)) * 2.4 + Math.cos(z * .21) * 1.8;
9
+ const island = Math.max(0, 1 - Math.hypot(x / 31, z / 26));
10
+ return Math.max(-1, Math.floor(island * 9 + ridge - 2));
11
+ };
12
+ for (let z = -28; z <= 28; z++) {
13
+ for (let x = -32; x <= 32; x++) {
14
+ const y = heightAt(x, z);
15
+ if (x * x / 1100 + z * z / 850 > 1.15) continue;
16
+ const path = Math.abs(z - Math.round(Math.sin(x * .12) * 9)) < 1;
17
+ points.push({ x, y, z, glyph: y < 0 ? '~' : path ? ':' : y > 5 ? '#' : '+', kind: path ? 'path' : 'land' });
18
+ // 用垂直字符层显示台阶断面,保留体素地形的厚度。
19
+ const bottom = Math.min(heightAt(x + 1, z), heightAt(x, z + 1));
20
+ for (let h = bottom + 1; h < y; h++) points.push({ x, y: h, z, glyph: '|', kind: 'side' });
21
+ }
22
+ }
23
+ for (const [x, z, label] of [[-20, -9, '>_'], [20, 9, '{ }'], [-8, 20, 'git'], [10, -20, '</>'], [-23, 15, '[AI]'], [24, -12, '$']]) {
24
+ const base = heightAt(x, z);
25
+ for (let y = base + 1; y <= base + 5; y++) points.push({ x, y, z, glyph: y === base + 5 ? '[]' : '|', kind: 'node' });
26
+ agents.push({ x, y: base + 7, z, glyph: label, kind: 'node' });
27
+ }
28
+ let width = 0, height = 0, frame = 0, angle = .65, last = 0;
29
+ let dark = false;
30
+ function resize() {
31
+ width = innerWidth;
32
+ height = innerHeight;
33
+ const ratio = Math.min(devicePixelRatio || 1, 2);
34
+ canvas.width = Math.round(width * ratio);
35
+ canvas.height = Math.round(height * ratio);
36
+ context.setTransform(ratio, 0, 0, ratio, 0, 0);
37
+ draw();
38
+ }
39
+ function draw() {
40
+ if (!context || !width) return;
41
+ context.clearRect(0, 0, width, height);
42
+ const scale = Math.max(width / 62, height / 42);
43
+ const sin = Math.sin(angle), cos = Math.cos(angle);
44
+ const project = (point) => {
45
+ const x = point.x * cos - point.z * sin;
46
+ const depth = point.x * sin + point.z * cos;
47
+ return { ...point, sx: width / 2 + x * scale, sy: height * .58 + (depth * .48 - point.y * .95) * scale, depth };
48
+ };
49
+ const visible = [...points, ...agents].map(project).sort((a, b) => a.depth - b.depth);
50
+ const fontSize = Math.max(10, Math.min(14, scale * .52));
51
+ context.font = `${fontSize}px ui-monospace, SFMono-Regular, Consolas, monospace`;
52
+ context.textAlign = 'center';
53
+ // 同一字符格只保留前景点,避免山体投影堆叠成杂乱笔画。
54
+ const cells = new Map();
55
+ for (const point of visible) cells.set(`${Math.round(point.sx / (fontSize * .7))}:${Math.round(point.sy / fontSize)}`, point);
56
+ for (const p of cells.values()) {
57
+ if (p.sx < -30 || p.sx > width + 30 || p.sy < -20 || p.sy > height + 20) continue;
58
+ const alpha = p.kind === 'node' ? .48 : p.kind === 'path' ? .34 : p.kind === 'side' ? .14 : .22;
59
+ context.fillStyle = dark ? `rgba(112,166,245,${alpha})` : `rgba(52,101,174,${alpha})`;
60
+ context.fillText(p.glyph, p.sx, p.sy);
61
+ }
62
+ }
63
+ function tick(time) {
64
+ if (time - last >= 50) {
65
+ if (last) angle = (angle + Math.min(time - last, 100) * Math.PI * 2 / 240000) % (Math.PI * 2);
66
+ last = time;
67
+ draw();
68
+ }
69
+ frame = requestAnimationFrame(tick);
70
+ }
71
+ function syncMotion() {
72
+ cancelAnimationFrame(frame);
73
+ last = 0;
74
+ if (!document.hidden && !reduced.matches) frame = requestAnimationFrame(tick);
75
+ else draw();
76
+ }
77
+ function syncTheme() {
78
+ dark = document.documentElement.dataset.theme === 'dark';
79
+ draw();
80
+ }
81
+ new MutationObserver(syncTheme).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
82
+ addEventListener('resize', resize);
83
+ document.addEventListener('visibilitychange', syncMotion);
84
+ reduced.addEventListener('change', syncMotion);
85
+ addEventListener('pagehide', () => cancelAnimationFrame(frame));
86
+ addEventListener('pageshow', syncMotion);
87
+ syncTheme();
88
+ resize();
89
+ syncMotion();