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.
- package/DESIGN.md +40 -1
- package/README.md +9 -3
- package/apps/dashboard/DESIGN.md +53 -31
- package/apps/dashboard/app.js +55 -39
- package/apps/dashboard/assets/OFL.txt +93 -0
- package/apps/dashboard/assets/info.svg +15 -0
- package/apps/dashboard/assets/moon.svg +13 -0
- package/apps/dashboard/assets/refresh-cw.svg +16 -0
- package/apps/dashboard/assets/silkscreen.ttf +0 -0
- package/apps/dashboard/assets/sun.svg +21 -0
- package/apps/dashboard/assets/terrain.js +89 -0
- package/apps/dashboard/assets/triangle-alert.svg +15 -0
- package/apps/dashboard/favicon.svg +12 -17
- package/apps/dashboard/index.html +24 -18
- package/apps/dashboard/styles.css +147 -181
- package/assets/brand/README.md +19 -0
- package/assets/brand/minecodex-icon-dark.svg +9 -0
- package/assets/brand/minecodex-icon-light.svg +9 -0
- package/assets/brand/minecodex-icon.svg +8 -0
- package/assets/brand/references/lucide-LICENSE.txt +43 -0
- package/assets/readme/hero.svg +93 -0
- package/features/hide-upsell-banner/README.md +7 -5
- package/features/hide-upsell-banner/codex-feature.json +2 -2
- package/features/hide-upsell-banner/src/hide-upsell-banner.mjs +30 -12
- package/features/images/DESIGN.md +117 -41
- package/features/images/README.md +26 -16
- package/features/images/codex-feature.json +6 -3
- package/features/images/src/adapters/codex/image-prompt.mjs +24 -2
- package/features/images/src/adapters/codex/image-sources.mjs +19 -0
- package/features/images/src/adapters/codex/library-api.mjs +2 -0
- package/features/images/src/adapters/codex/library-viewer.mjs +26 -2
- package/features/images/src/adapters/codex/native-image-modules.mjs +211 -0
- package/features/images/src/adapters/codex/native-image-source.mjs +449 -0
- package/features/images/src/backend/local-image-source.mjs +138 -0
- package/features/images/src/frontend/cloud-image-actions.mjs +41 -0
- package/features/images/src/frontend/local-image-source.mjs +195 -0
- package/features/images/src/library-prompt-page.mjs +163 -53
- package/features/images/src/library-prompt-source.mjs +9 -3
- package/features/images/src/prompt-index.mjs +14 -2
- package/features/model-slider/DESIGN.md +2 -0
- package/features/model-slider/codex-feature.json +2 -2
- package/features/model-slider/src/frontend/install.mjs +78 -11
- package/features/notes/DESIGN.md +7 -3
- package/features/notes/codex-feature.json +2 -2
- package/features/notes/src/frontend/entry.mjs +26 -9
- package/features/session-status/DESIGN.md +4 -2
- package/features/session-status/codex-feature.json +2 -2
- package/features/session-status/src/adapters/codex/session-log.mjs +2 -1
- package/features/session-status/src/domain/session-stats.mjs +17 -9
- package/features/session-status/src/session-status.mjs +47 -11
- package/features/sidebar-focus/DESIGN.md +4 -4
- package/features/sidebar-focus/codex-feature.json +3 -2
- package/features/sidebar-focus/src/adapters/codex/renderer.mjs +1 -1
- package/features/sidebar-focus/src/sidebar-focus.mjs +9 -7
- package/features/sites/DESIGN.md +25 -3
- package/features/sites/README.md +6 -2
- package/features/sites/codex-feature.json +3 -2
- package/features/sites/src/adapters/codex/renderer.mjs +25 -4
- package/features/sites/src/backend/project-sites.mjs +18 -6
- package/features/sites/src/backend/sites-service.mjs +22 -10
- package/features/sites/src/domain/discovery-candidates.mjs +53 -0
- package/features/sites/src/frontend/entry.mjs +76 -38
- package/features/sites/src/frontend/styles.css +3 -3
- package/features/unread-inbox/DESIGN.md +24 -25
- package/features/unread-inbox/codex-feature.json +3 -2
- package/features/unread-inbox/src/adapters/codex/modules.mjs +52 -0
- package/features/unread-inbox/src/adapters/codex/renderer.mjs +90 -0
- package/features/unread-inbox/src/domain/notifications.mjs +21 -0
- package/features/unread-inbox/src/injection-source.mjs +5 -2
- package/features/unread-inbox/src/unread-inbox.mjs +212 -297
- package/features/usage-meter/codex-feature.json +2 -2
- package/package.json +11 -3
- package/packages/cli/native/MineCodex.app/Contents/Info.plist +4 -2
- package/packages/cli/native/MineCodex.app/Contents/MacOS/MineCodexServiceManager +0 -0
- package/packages/cli/native/MineCodex.app/Contents/Resources/MineCodex.icns +0 -0
- package/packages/cli/native/MineCodex.app/Contents/_CodeSignature/CodeResources +11 -0
- package/packages/cli/native/README.md +31 -0
- package/packages/cli/src/browser-control-panel.mjs +48 -0
- package/packages/cli/src/commands.mjs +3 -1
- package/packages/cli/src/control-server.mjs +6 -3
- package/packages/cli/src/platform.mjs +5 -0
- package/packages/codex-adapter/README.md +2 -1
- package/packages/codex-adapter/src/renderer/environment.mjs +24 -6
- package/packages/runtime-host/README.md +10 -5
- package/packages/runtime-host/src/codex-injection.mjs +13 -9
- package/packages/runtime-host/src/codex-runtime.mjs +26 -5
- package/packages/runtime-host/src/feature-registry.mjs +1 -0
- package/packages/ui/DESIGN.md +9 -2
- package/packages/ui/assets/arrow-up-right.svg +14 -0
- package/packages/ui/assets/lucide-LICENSE.txt +43 -0
- package/packages/ui/src/components/account-menu.mjs +74 -0
- package/packages/ui/src/components/button.mjs +2 -0
- package/packages/ui/src/components/navigation-rail.mjs +18 -10
- package/packages/ui/src/components/page.mjs +9 -2
- package/packages/ui/src/foundation/layers.mjs +1 -0
- package/packages/ui/src/install.mjs +6 -1
- package/packages/ui/src/native/account-menu.mjs +21 -0
- package/packages/ui/src/native/navigation-rail.mjs +6 -1
- package/packages/ui/src/native/page.mjs +38 -0
- package/packages/ui/src/native/tokens.mjs +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minecodex",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "Lightweight, local-first plugins for the Codex desktop app.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"features/session-status/vendor/lucide-LICENSE",
|
|
42
42
|
"features/session-status/vendor/phosphor-LICENSE",
|
|
43
43
|
"apps/dashboard/app.js",
|
|
44
|
+
"apps/dashboard/assets",
|
|
44
45
|
"apps/dashboard/favicon.svg",
|
|
45
46
|
"apps/dashboard/index.html",
|
|
46
47
|
"apps/dashboard/styles.css",
|
|
@@ -65,7 +66,13 @@
|
|
|
65
66
|
"packages/codex-adapter/src",
|
|
66
67
|
"packages/codex-adapter/package.json",
|
|
67
68
|
"packages/ui/package.json",
|
|
68
|
-
"DESIGN.md"
|
|
69
|
+
"DESIGN.md",
|
|
70
|
+
"assets/brand/minecodex-icon.svg",
|
|
71
|
+
"assets/brand/minecodex-icon-light.svg",
|
|
72
|
+
"assets/brand/minecodex-icon-dark.svg",
|
|
73
|
+
"assets/brand/references/lucide-LICENSE.txt",
|
|
74
|
+
"assets/readme/hero.svg",
|
|
75
|
+
"packages/ui/assets"
|
|
69
76
|
],
|
|
70
77
|
"engines": {
|
|
71
78
|
"node": ">=22.5.0"
|
|
@@ -80,7 +87,8 @@
|
|
|
80
87
|
"pack:check": "npm pack --dry-run",
|
|
81
88
|
"pack:audit": "node scripts/audit-package.mjs",
|
|
82
89
|
"prepublishOnly": "npm test && npm run check && npm run pack:audit",
|
|
83
|
-
"test:ui": "node --test packages/ui/test/*.test.mjs"
|
|
90
|
+
"test:ui": "node --test packages/ui/test/*.test.mjs",
|
|
91
|
+
"build:icons": "node scripts/build-brand-icons.mjs"
|
|
84
92
|
},
|
|
85
93
|
"repository": {
|
|
86
94
|
"type": "git",
|
|
@@ -14,12 +14,14 @@
|
|
|
14
14
|
<string>6.0</string>
|
|
15
15
|
<key>CFBundleName</key>
|
|
16
16
|
<string>MineCodex</string>
|
|
17
|
+
<key>CFBundleIconFile</key>
|
|
18
|
+
<string>MineCodex.icns</string>
|
|
17
19
|
<key>CFBundlePackageType</key>
|
|
18
20
|
<string>APPL</string>
|
|
19
21
|
<key>CFBundleShortVersionString</key>
|
|
20
|
-
<string>1.0.
|
|
22
|
+
<string>1.0.1</string>
|
|
21
23
|
<key>CFBundleVersion</key>
|
|
22
|
-
<string>
|
|
24
|
+
<string>2</string>
|
|
23
25
|
<key>LSBackgroundOnly</key>
|
|
24
26
|
<true/>
|
|
25
27
|
<key>LSMinimumSystemVersion</key>
|
|
Binary file
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
<dict>
|
|
5
5
|
<key>files</key>
|
|
6
6
|
<dict>
|
|
7
|
+
<key>Resources/MineCodex.icns</key>
|
|
8
|
+
<data>
|
|
9
|
+
mSztCiWeEvLkAINXLTTBTVSA0f8=
|
|
10
|
+
</data>
|
|
7
11
|
<key>Resources/MineCodexServiceHost</key>
|
|
8
12
|
<data>
|
|
9
13
|
kB0iYBcULzqcS1yOgYpY3LZjeq4=
|
|
@@ -18,6 +22,13 @@
|
|
|
18
22
|
mGWkf+X17Pau3uy8xUUB1s+JXofeKYKNRzFPk3FXeaA=
|
|
19
23
|
</data>
|
|
20
24
|
</dict>
|
|
25
|
+
<key>Resources/MineCodex.icns</key>
|
|
26
|
+
<dict>
|
|
27
|
+
<key>hash2</key>
|
|
28
|
+
<data>
|
|
29
|
+
Wg3Q9bbvnrEwou/t4h9Gk/nWmlwm0UGGt+qR4+LySAI=
|
|
30
|
+
</data>
|
|
31
|
+
</dict>
|
|
21
32
|
<key>Resources/MineCodexServiceHost</key>
|
|
22
33
|
<dict>
|
|
23
34
|
<key>hash2</key>
|
|
@@ -16,3 +16,34 @@ Rebuild deliberately with:
|
|
|
16
16
|
```bash
|
|
17
17
|
npm run build:service
|
|
18
18
|
```
|
|
19
|
+
|
|
20
|
+
## Icon bundle revision 1.0.1
|
|
21
|
+
|
|
22
|
+
This is an explicit native resource revision: `CFBundleIconFile` now points to
|
|
23
|
+
`Contents/Resources/MineCodex.icns`, bundle version is `2`, and the outer app is
|
|
24
|
+
re-signed. The service host executable remains byte-for-byte unchanged. The
|
|
25
|
+
manager's ad-hoc designated requirement changes when its Info.plist changes.
|
|
26
|
+
The service identifier and launch-agent plist remain unchanged.
|
|
27
|
+
|
|
28
|
+
The existing `MacPlatformAdapter.installService()` is the migration path: run the
|
|
29
|
+
old installed manager's `unregister` before replacing its bundle; verify the new
|
|
30
|
+
staged bundle; replace it; then register and kickstart the new service. An old
|
|
31
|
+
registration failure aborts replacement. Use `mcx install` / the normal update
|
|
32
|
+
flow; do not copy a new icon or re-sign the installed app in place. System approval
|
|
33
|
+
may be required after re-registration; preserve macOS's decision and existing CLI
|
|
34
|
+
reporting. Real Background Items approval and UI upgrade acceptance must be checked
|
|
35
|
+
on a target installation before release.
|
|
36
|
+
|
|
37
|
+
The icon uses the filled project mark on a neutral rounded backing so one ICNS
|
|
38
|
+
remains legible in light and dark system surfaces. SVG artwork is generated at
|
|
39
|
+
`assets/brand/minecodex-service-icon.svg` by `npm run build:icons`. Rasterize that
|
|
40
|
+
SVG into an iconset at 16/32/128/256/512px and each size's @2x resolution, then run
|
|
41
|
+
`iconutil -c icns <iconset> -o app-template/Contents/Resources/MineCodex.icns`.
|
|
42
|
+
Commit the resulting template ICNS with any deliberate native artwork revision.
|
|
43
|
+
`npm run build:service` copies it into the built bundle before signing. Ordinary
|
|
44
|
+
JavaScript releases continue shipping the existing signed bundle unchanged.
|
|
45
|
+
|
|
46
|
+
Validation for this revision: strict deep signature verification, universal
|
|
47
|
+
architectures, ICNS structure and plist checks, system icon loading through
|
|
48
|
+
`NSWorkspace`, installer regressions, and npm package audit. These checks do not
|
|
49
|
+
register a background item or change an installed service.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// 标签页枚举留在平台适配层;只匹配控制台 origin 的根页或一次性登录页。
|
|
2
|
+
export const CONTROL_PANEL_BROWSER_SCRIPT = `
|
|
3
|
+
ObjC.import('CoreServices');
|
|
4
|
+
function run(argv) {
|
|
5
|
+
const url = argv[0], origin = argv[1];
|
|
6
|
+
const bundle = ObjC.unwrap(ObjC.castRefToObject($.LSCopyDefaultHandlerForURLScheme($('http'))));
|
|
7
|
+
const chromium = ['com.google.Chrome', 'com.google.Chrome.canary', 'org.chromium.Chromium', 'com.microsoft.edgemac', 'com.brave.Browser', 'com.vivaldi.Vivaldi'];
|
|
8
|
+
if (bundle !== 'com.apple.Safari' && chromium.indexOf(bundle) < 0) return JSON.stringify({mode:'open', reuseSupported:false});
|
|
9
|
+
const browser = Application(bundle);
|
|
10
|
+
if (!browser.running()) return JSON.stringify({mode:'open', reuseSupported:true});
|
|
11
|
+
try {
|
|
12
|
+
const windows = browser.windows();
|
|
13
|
+
for (let w = 0; w < windows.length; w++) {
|
|
14
|
+
const tabs = windows[w].tabs();
|
|
15
|
+
for (let t = 0; t < tabs.length; t++) {
|
|
16
|
+
const candidate = tabs[t].url();
|
|
17
|
+
if (candidate !== origin + '/' && candidate.indexOf(origin + '/auth?') !== 0) continue;
|
|
18
|
+
if (bundle === 'com.apple.Safari') windows[w].currentTab = tabs[t];
|
|
19
|
+
else windows[w].activeTabIndex = t + 1;
|
|
20
|
+
windows[w].minimized = false;
|
|
21
|
+
windows[w].index = 1;
|
|
22
|
+
browser.activate();
|
|
23
|
+
return JSON.stringify({mode:'reused', reuseSupported:true});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return JSON.stringify({mode:'open', reuseSupported:true});
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return JSON.stringify({mode:'failed', code:error.errorNumber || 0});
|
|
29
|
+
}
|
|
30
|
+
}`;
|
|
31
|
+
|
|
32
|
+
export async function openMacControlPanel(execFile, url, origin) {
|
|
33
|
+
const target = new URL(url), base = new URL(origin);
|
|
34
|
+
if (target.origin !== base.origin || base.protocol !== "http:" || base.hostname !== "127.0.0.1"
|
|
35
|
+
|| target.pathname !== "/auth" || !target.searchParams.get("exchange")) throw new Error("Invalid control panel URL");
|
|
36
|
+
let stdout;
|
|
37
|
+
try { ({ stdout } = await execFile("/usr/bin/osascript", ["-l", "JavaScript", "-e", CONTROL_PANEL_BROWSER_SCRIPT, url, base.origin], { timeout:10_000 })); }
|
|
38
|
+
catch { throw new Error("无法连接默认浏览器,请重试打开 MineCodex。"); }
|
|
39
|
+
const result = JSON.parse(stdout);
|
|
40
|
+
if (result.mode === "failed") {
|
|
41
|
+
throw new Error(result.code === -1743
|
|
42
|
+
? "无法定位 MineCodex 标签页。请在系统设置的自动化权限中允许控制默认浏览器后重试。"
|
|
43
|
+
: "无法定位默认浏览器中的 MineCodex 标签页,请重试。");
|
|
44
|
+
}
|
|
45
|
+
if (result.mode === "reused") return result;
|
|
46
|
+
await execFile("/usr/bin/open", [url]);
|
|
47
|
+
return { mode:"opened", reuseSupported:result.reuseSupported };
|
|
48
|
+
}
|
|
@@ -187,7 +187,9 @@ async function openCommand({ paths, platform, output, serviceProbe, fetchImpl })
|
|
|
187
187
|
if (!exchangeResponse.ok) throw new Error(`MineCodex browser exchange failed: HTTP ${exchangeResponse.status}`);
|
|
188
188
|
const { exchange } = await exchangeResponse.json();
|
|
189
189
|
if (!exchange) throw new Error("MineCodex browser exchange did not return a short-lived value.");
|
|
190
|
-
|
|
190
|
+
const url = `${controlOrigin()}/auth?exchange=${encodeURIComponent(exchange)}`;
|
|
191
|
+
if (platform.openControlPanel) await platform.openControlPanel(url, controlOrigin());
|
|
192
|
+
else await platform.openBrowser(url);
|
|
191
193
|
};
|
|
192
194
|
if (reporter) await reporter.step(2, 2, "Opening the control panel", openControlPanel);
|
|
193
195
|
else await openControlPanel();
|
|
@@ -17,6 +17,7 @@ const CONTENT_TYPES = new Map([
|
|
|
17
17
|
[".html", "text/html; charset=utf-8"],
|
|
18
18
|
[".js", "text/javascript; charset=utf-8"],
|
|
19
19
|
[".svg", "image/svg+xml"],
|
|
20
|
+
[".ttf", "font/ttf"],
|
|
20
21
|
]);
|
|
21
22
|
|
|
22
23
|
function tokenMatches(candidate, expected) {
|
|
@@ -197,10 +198,10 @@ async function iconResponse(id, paths, response) {
|
|
|
197
198
|
iconPath = path.join(paths.featuresRoot, id, "assets", "ui", "sparkles.svg");
|
|
198
199
|
} else if (id === "notes") {
|
|
199
200
|
iconPath = path.join(paths.featuresRoot, id, "assets", "icons", "book-open.svg");
|
|
200
|
-
} else if (
|
|
201
|
+
} else if (FEATURE_IDS.includes(id)) {
|
|
201
202
|
const manifest = JSON.parse(await readFile(path.join(paths.featuresRoot, id, "codex-feature.json"), "utf8"));
|
|
202
203
|
const viewBox = manifest.icon?.viewBox ?? "0 0 24 24";
|
|
203
|
-
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}" fill="none" stroke="currentColor" stroke-width="1
|
|
204
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round">${manifest.icon.markup}</svg>`;
|
|
204
205
|
response.writeHead(200, { "Content-Type": "image/svg+xml", "Cache-Control": "public, max-age=3600" });
|
|
205
206
|
response.end(svg);
|
|
206
207
|
return;
|
|
@@ -209,7 +210,9 @@ async function iconResponse(id, paths, response) {
|
|
|
209
210
|
return;
|
|
210
211
|
}
|
|
211
212
|
response.writeHead(200, { "Content-Type": "image/svg+xml", "Cache-Control": "public, max-age=3600" });
|
|
212
|
-
|
|
213
|
+
// 仅统一控制台图标,不改变插件在 Codex 内使用的原始资源。
|
|
214
|
+
const svg = (await readFile(iconPath, "utf8")).replace(/stroke-width="[^"]+"/g, 'stroke-width="2.1"');
|
|
215
|
+
response.end(svg);
|
|
213
216
|
}
|
|
214
217
|
|
|
215
218
|
async function staticResponse(url, paths, response) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { openMacControlPanel } from "./browser-control-panel.mjs";
|
|
1
2
|
import { execFile as execFileCallback } from "node:child_process";
|
|
2
3
|
import { mkdir, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
4
|
import os from "node:os";
|
|
@@ -704,6 +705,10 @@ export class MacPlatformAdapter {
|
|
|
704
705
|
await this.execFile("/usr/bin/open", [url]);
|
|
705
706
|
}
|
|
706
707
|
|
|
708
|
+
async openControlPanel(url, origin) {
|
|
709
|
+
return openMacControlPanel(this.execFile, url, origin);
|
|
710
|
+
}
|
|
711
|
+
|
|
707
712
|
async openNativeCodex({ cdpPort } = {}) {
|
|
708
713
|
const installation = await this.codex();
|
|
709
714
|
if (!Number.isInteger(cdpPort) || cdpPort <= 0 || cdpPort > 65_535) {
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
| 接口 | 结果与约束 |
|
|
8
8
|
| --- | --- |
|
|
9
|
-
| `composer(element?, {threadOnly}?)` | 默认仅可见的 thread 输入框;Prompts
|
|
9
|
+
| `composer(element?, {threadOnly, purpose}?)` | 默认仅可见的 Codex thread 输入框;Prompts 使用 `purpose:"insertion"` 支持 Chat、Work、Codex、Temporary Chat 与面板 chat bar。优先当前焦点所在输入框,并可显式绑定插入目标。返回 editable/root/body/add/threadId;非thread不提供身份,隐藏祖先返回 null |
|
|
10
10
|
| `currentThreadId(element?)` | 从合格输入框取得 UUID;不从残留 sidebar 选中项猜当前线程 |
|
|
11
|
+
| `composerSurface(element?)` | 返回可见正式会话外壳 root/threadId,独立于编辑区是否存在或暂时 inert;供会话状态呈现与宿主统计订阅使用。真正隐藏、移入浮层、切换 home 或卸载时返回 null;不能作为文字插入许可 |
|
|
11
12
|
| `rail()` / `railSlot(ownGroup)` | 可见原生 rail 与模板/滚动区/固定组句柄;不包含插件菜单策略 |
|
|
12
13
|
| `currentFiber(element)` / `props(element,predicate,limit?)` | 仅供 native Adapter 使用;优先匹配当前 committed props,支持最近祖先的 scope |
|
|
13
14
|
| `report(name,available,reason?)` / `diagnostics()` | 最新能力状态的副本;不持有凭据、原始账户数据或用户内容 |
|
|
@@ -18,12 +18,17 @@ export function createCodexEnvironment() {
|
|
|
18
18
|
}
|
|
19
19
|
return null;
|
|
20
20
|
}
|
|
21
|
-
function composer(element = null, {threadOnly=true} = {}) {
|
|
22
|
-
const
|
|
21
|
+
function composer(element = null, {threadOnly=true, purpose="thread"} = {}) {
|
|
22
|
+
const insertion = purpose === "insertion";
|
|
23
|
+
const rootSelector = insertion ? '[data-codex-composer-root], [data-chatgpt-composer]' : '[data-codex-composer-root]';
|
|
24
|
+
const candidates = element ? [element] : [...document.querySelectorAll(insertion
|
|
25
|
+
? '[data-codex-composer="true"][contenteditable="true"], [data-chatgpt-composer] [data-composer-markdown][contenteditable="true"]'
|
|
26
|
+
: '[data-codex-composer="true"][contenteditable="true"]')];
|
|
27
|
+
if (!element && insertion) candidates.sort((a,b) => Number(Boolean(b.closest(rootSelector)?.contains(document.activeElement))) - Number(Boolean(a.closest(rootSelector)?.contains(document.activeElement))));
|
|
23
28
|
for (const editable of candidates) {
|
|
24
|
-
const root = editable.closest(
|
|
25
|
-
if (!editable.isConnected || !root || (threadOnly && root.getAttribute('data-composer-placement') !== 'thread')
|
|
26
|
-
|| editable.closest('[hidden], [inert], [aria-hidden="true"], [data-floating-chat-surface], [data-right-panel-composer-overlay-content]')
|
|
29
|
+
const root = editable.closest(rootSelector);
|
|
30
|
+
if (!editable.isConnected || !root || (!insertion && threadOnly && root.getAttribute('data-composer-placement') !== 'thread')
|
|
31
|
+
|| editable.closest(insertion ? '[hidden], [inert], [aria-hidden="true"]' : '[hidden], [inert], [aria-hidden="true"], [data-floating-chat-surface], [data-right-panel-composer-overlay-content]')
|
|
27
32
|
|| !editable.checkVisibility({ visibilityProperty:true })) continue;
|
|
28
33
|
const value = root.getAttribute('data-composer-placement') === 'thread' ? root.querySelector('[data-above-composer-portal]')?.getAttribute('data-above-composer-conversation-id') : null;
|
|
29
34
|
const threadId = value?.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i)?.[0] ?? null;
|
|
@@ -34,6 +39,19 @@ export function createCodexEnvironment() {
|
|
|
34
39
|
report("composer", false, "active-thread-composer-unavailable");
|
|
35
40
|
return null;
|
|
36
41
|
}
|
|
42
|
+
// 状态属于会话外壳;发送时编辑区可能 inert 或重建,不代表离开会话。
|
|
43
|
+
function composerSurface(element = null) {
|
|
44
|
+
const roots = element ? [element.closest('[data-codex-composer-root]')] : document.querySelectorAll('[data-codex-composer-root]');
|
|
45
|
+
for (const root of roots) {
|
|
46
|
+
if (!root?.isConnected || root.getAttribute('data-composer-placement') !== 'thread'
|
|
47
|
+
|| root.closest('[hidden], [aria-hidden="true"], [data-floating-chat-surface], [data-right-panel-composer-overlay-content]')
|
|
48
|
+
|| !root.checkVisibility({visibilityProperty:true})) continue;
|
|
49
|
+
const value = root.querySelector('[data-above-composer-portal]')?.getAttribute('data-above-composer-conversation-id');
|
|
50
|
+
const threadId = value?.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i)?.[0] ?? null;
|
|
51
|
+
return {root,threadId};
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
37
55
|
function rail() {
|
|
38
56
|
const node = [...document.querySelectorAll('[data-app-navigation-rail]')].find(node => node.getBoundingClientRect().width > 0) ?? null;
|
|
39
57
|
report("navigation-rail", Boolean(node), node ? null : "rail-unavailable");
|
|
@@ -47,7 +65,7 @@ export function createCodexEnvironment() {
|
|
|
47
65
|
const group = [...(scroll?.children ?? [])].find(child => child !== ownGroup && child.classList.contains('flex-col') && child.classList.contains('shrink-0'));
|
|
48
66
|
return node && template && scroll ? { rail:node, template, scroll, group } : null;
|
|
49
67
|
}
|
|
50
|
-
return { composer, rail, railSlot, currentThreadId:element => composer(element)?.threadId ?? null,
|
|
68
|
+
return { composer, composerSurface, rail, railSlot, currentThreadId:element => composer(element)?.threadId ?? null,
|
|
51
69
|
// 仅供 UI/插件 native Adapter 使用,交互层不读取 React props。
|
|
52
70
|
currentFiber, props, report, diagnostics:() => [...capabilities.values()].map(item => ({...item})) };
|
|
53
71
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Codex Runtime Host
|
|
2
2
|
|
|
3
|
-
本机个人 Codex
|
|
3
|
+
本机个人 Codex 功能的统一运行宿主。它是统一 Injector 与生命周期宿主,负责:
|
|
4
4
|
|
|
5
5
|
- 连接或启动支持 loopback CDP 的隔离 Codex 桌面实例;
|
|
6
6
|
- 从同级项目的 `codex-feature.json` 注册 Sidebar 或 Conversation toolbar 入口;
|
|
7
7
|
- 挂载 full-page、Pinned Summary,并把 Detail iframe Surface 优先注册为 Codex 原生右侧 Task Tab;
|
|
8
8
|
- 把 Codex 当前 resolved Theme Tokens 同步给每个 Surface;
|
|
9
9
|
- 执行经过 manifest allowlist、Surface 身份和 renderer token 校验的 Host actions;
|
|
10
|
-
-
|
|
10
|
+
- 维护共享 CDP、注入与可信通信;插件专有的 Codex 协议由各功能 Adapter 维护。
|
|
11
11
|
|
|
12
12
|
各功能项目继续拥有自己的数据、HTTP API 与页面。宿主不读取业务数据库;单个
|
|
13
13
|
功能服务失效不会阻止 Codex 或其他功能运行。
|
|
@@ -173,8 +173,7 @@ Host 会再次发送当前状态。功能页面可据此暂停隐藏状态下的
|
|
|
173
173
|
| `Runtime.addBinding`、`DOM.setFileInputFiles`、注入脚本 | high-risk CDP | 必须使用隔离 profile 验收 |
|
|
174
174
|
| `togglePinnedSummary`、`composer.addFiles` 字符串 | 探索证据 | 当前实现不把它们当契约 |
|
|
175
175
|
|
|
176
|
-
这是非官方 CDP / DOM Adapter,不是公开的 Codex Contribution API。Codex
|
|
177
|
-
在这个宿主中修复;各功能的数据和 Web UI不需要跟随重写。
|
|
176
|
+
这是非官方 CDP / DOM Adapter,不是公开的 Codex Contribution API。Codex 更新后按协议所有者修复:共享注入和生命周期归宿主,专有原生组件与 API 归对应插件 Adapter;领域规则与 Web UI不应直接跟随外部协议重写。
|
|
178
177
|
|
|
179
178
|
|
|
180
179
|
## 共享弹窗与浮层
|
|
@@ -197,8 +196,14 @@ config.ui.surface(popover); // 只统一材质,保留功能的定位与内容
|
|
|
197
196
|
|
|
198
197
|
page-script 可以用 manifest 的 `entry.resources` 声明本功能内的 CSS/图标资源,宿主读取后通过 `config.resources` 一次传入。JSON 作为数据、其他文件作为文本;越出功能目录的路径会被拒绝。它不改变 host action、服务启动或消息协议。
|
|
199
198
|
|
|
200
|
-
Images 提示词面板沿用原生
|
|
199
|
+
Images 提示词面板沿用原生 File info 的附属信息面板结构;iframe 页面继续通过宿主接收主题。会话统计与云 Library 业务归所属插件,Runtime Host 只组合业务能力和外部连接。领域规则与架构层参见根 [ARCHITECTURE.md](../../ARCHITECTURE.md)。
|
|
201
200
|
|
|
202
201
|
`entry.sourceFactory` 可声明本插件内的源码工厂,导出 `createPageScriptSource()` 并返回 Renderer 函数体字符串。工厂路径不能越出插件;纯领域函数由插件工厂序列化,宿主不维护规则副本。Images 使用这一接口共享图片提示词溯源规则。
|
|
203
202
|
|
|
204
203
|
Model Slider 的模型规则、原生控制器适配和专有呈现位于插件 `src/domain` / `src/frontend`,安装源码由插件工厂组装。宿主仅提供配置、locale、共享 UI、图标与刷新调度,维护 ensure/dispose 接线;原生菜单开合和节点恢复由插件负责。
|
|
204
|
+
|
|
205
|
+
## 项目控制台入口
|
|
206
|
+
|
|
207
|
+
Runtime Host 在账号菜单 Settings 下挂载 MineCodex 项目入口,不属于可开关的插件。`minecodex/open-control-panel` 是独立 builtin action,必须通过对应 renderer 的 binding token 验证;它不接受任意 URL 或 shell 参数,固定调用包内 CLI 的 `gui` 命令。组件的项目图标、外链箭头与菜单模板由共享 UI 提供。
|
|
208
|
+
|
|
209
|
+
CLI 继续通过一次性 browser exchange 打开控制台。macOS 默认 Safari 与 Chrome/Chromium、Edge、Brave、Vivaldi 支持查找现有控制台标签页并激活;没有匹配页才调用系统默认浏览器打开登录地址。自动化权限被拒绝时返回错误,不用重复开页掩盖失败。其他浏览器和 Windows 保留系统 URL 打开行为,目前不保证标签页复用。
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import { createCodexEnvironment } from "../../codex-adapter/src/renderer/environment.mjs";
|
|
2
3
|
// 注入源生成:createInjectionSource 产出注入到 Codex Renderer 的自包含脚本字符串。
|
|
3
4
|
// 该字符串经 CDP 注入后独立执行,因此 install 函数体必须自包含;
|
|
@@ -8,7 +9,9 @@ import { createUIInstallerSource } from "../../ui/src/install.mjs";
|
|
|
8
9
|
import { randomBytes } from "node:crypto";
|
|
9
10
|
import { CODEX_THEME_TOKENS, CODEX_THEME_TOKEN_ALIASES, MINECODEX_THEME_BINDINGS } from "../../ui/src/native/tokens.mjs";
|
|
10
11
|
|
|
11
|
-
export const RUNTIME_VERSION =
|
|
12
|
+
export const RUNTIME_VERSION = 108;
|
|
13
|
+
const PROJECT_ICON = readFileSync(new URL("../../../assets/brand/minecodex-icon.svg", import.meta.url), "utf8");
|
|
14
|
+
const EXTERNAL_ICON = readFileSync(new URL("../../ui/assets/arrow-up-right.svg", import.meta.url), "utf8");
|
|
12
15
|
export const HOST_BINDING_NAME = "__codexPersonalHostAction";
|
|
13
16
|
export const CSP_BOOTSTRAP_VERSION = 1;
|
|
14
17
|
export const CSP_BOOTSTRAP_KEY = "__mineCodexCspBootstrapVersion";
|
|
@@ -204,6 +207,8 @@ export function createInjectionSource(features, {
|
|
|
204
207
|
} : null,
|
|
205
208
|
hostActions: new Set(feature.hostActions ?? []),
|
|
206
209
|
}));
|
|
210
|
+
featureUI.accountMenuEntry?.({ label:"MineCodex", icon:config.projectIcon, externalIcon:config.externalIcon,
|
|
211
|
+
onOpen:() => requestPageHostAction("minecodex", "open-control-panel", {}) });
|
|
207
212
|
const featureById = new Map(features.map((feature) => [feature.id, feature]));
|
|
208
213
|
const sidebarFeatures = features.filter((feature) => feature.entry?.kind === "sidebar");
|
|
209
214
|
const toolbarFeatures = features.filter((feature) => feature.entry?.kind === "conversation-toolbar");
|
|
@@ -812,10 +817,7 @@ export function createInjectionSource(features, {
|
|
|
812
817
|
}
|
|
813
818
|
|
|
814
819
|
function currentComposer() {
|
|
815
|
-
|
|
816
|
-
if (marked) return marked;
|
|
817
|
-
return Array.from(document.querySelectorAll('[contenteditable="true"][role="textbox"]'))
|
|
818
|
-
.find((element) => isVisibleToolbarControl(element)) ?? null;
|
|
820
|
+
return codex.composer(null, { purpose: "insertion" })?.editable ?? null;
|
|
819
821
|
}
|
|
820
822
|
|
|
821
823
|
function rememberComposerSelection() {
|
|
@@ -828,9 +830,9 @@ export function createInjectionSource(features, {
|
|
|
828
830
|
savedComposerThread = threadIdentity();
|
|
829
831
|
}
|
|
830
832
|
|
|
831
|
-
function insertComposerText(text) {
|
|
833
|
+
function insertComposerText(text, target = null) {
|
|
832
834
|
if (typeof text !== "string" || !text) throw new Error("Text is required");
|
|
833
|
-
const composer = currentComposer();
|
|
835
|
+
const composer = target ? codex.composer(target, { purpose: "insertion" })?.editable : currentComposer();
|
|
834
836
|
if (!composer) throw new Error("The active Composer was not found");
|
|
835
837
|
const selection = document.getSelection();
|
|
836
838
|
let fallback = true;
|
|
@@ -2173,14 +2175,14 @@ export function createInjectionSource(features, {
|
|
|
2173
2175
|
}
|
|
2174
2176
|
|
|
2175
2177
|
function requestPageHostAction(featureId, action, payload) {
|
|
2176
|
-
if (!featureById.get(featureId)?.hostActions.has(action)) return Promise.reject(new Error("Host action is not registered"));
|
|
2178
|
+
if (!(featureId === "minecodex" && action === "open-control-panel") && !featureById.get(featureId)?.hostActions.has(action)) return Promise.reject(new Error("Host action is not registered"));
|
|
2177
2179
|
if (typeof globalThis[config.bindingName] !== "function") return Promise.reject(new Error("Host bridge is unavailable"));
|
|
2178
2180
|
return new Promise((resolve, reject) => {
|
|
2179
2181
|
const requestId = crypto.randomUUID();
|
|
2180
2182
|
const timer = setTimeout(() => {
|
|
2181
2183
|
pendingHostActions.delete(requestId);
|
|
2182
2184
|
reject(new Error("Host request timed out"));
|
|
2183
|
-
}, 10_000);
|
|
2185
|
+
}, featureId === "minecodex" ? 30_000 : 10_000);
|
|
2184
2186
|
pendingHostActions.set(requestId, { kind: "page-script", resolve, reject, timer });
|
|
2185
2187
|
try {
|
|
2186
2188
|
globalThis[config.bindingName](JSON.stringify({ token: config.bindingToken, featureId, requestId, action, payload }));
|
|
@@ -2441,6 +2443,8 @@ export function createInjectionSource(features, {
|
|
|
2441
2443
|
} : null,
|
|
2442
2444
|
}));
|
|
2443
2445
|
return `(${install.toString()})(${JSON.stringify({
|
|
2446
|
+
projectIcon: PROJECT_ICON,
|
|
2447
|
+
externalIcon: EXTERNAL_ICON,
|
|
2444
2448
|
version: RUNTIME_VERSION,
|
|
2445
2449
|
sessionId: runtimeSessionId,
|
|
2446
2450
|
bindingName,
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { execFile as execFileCallback } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { LocalImageSource } from "../../../features/images/src/backend/local-image-source.mjs";
|
|
1
5
|
import { cloudImagesAction } from "../../../features/images/src/backend/cloud-library.mjs";
|
|
2
6
|
// CodexRuntime:Codex Renderer 生命周期与 host action 执行。
|
|
3
7
|
// 注入源(createInjectionSource)与 CDP 客户端分别在 codex-injection.mjs / codex-cdp.mjs。
|
|
@@ -34,6 +38,7 @@ import {
|
|
|
34
38
|
} from "./host-actions.mjs";
|
|
35
39
|
import { SitesService } from "../../../features/sites/src/backend/sites-service.mjs";
|
|
36
40
|
import { SessionStatusReader, threadIdFromLocation } from "../../../features/session-status/src/adapters/codex/session-reader.mjs";
|
|
41
|
+
import { createCodexEnvironment } from "../../codex-adapter/src/renderer/environment.mjs";
|
|
37
42
|
|
|
38
43
|
|
|
39
44
|
// 保持公共入口兼容:历史 import 从本文件继续可用。
|
|
@@ -48,11 +53,9 @@ export {
|
|
|
48
53
|
} from "./codex-injection.mjs";
|
|
49
54
|
|
|
50
55
|
export const SESSION_STATUS_THREAD_ID_EXPRESSION = `(() => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return card.querySelector('[data-above-composer-portal]')?.getAttribute('data-above-composer-conversation-id');
|
|
55
|
-
}).filter(Boolean))];
|
|
56
|
+
const codex = (${createCodexEnvironment.toString()})();
|
|
57
|
+
return [...new Set([...document.querySelectorAll('[data-codex-composer-root]')]
|
|
58
|
+
.map(root => codex.composerSurface(root)?.threadId).filter(Boolean))];
|
|
56
59
|
})()`;
|
|
57
60
|
|
|
58
61
|
export class CodexRuntime {
|
|
@@ -76,9 +79,12 @@ export class CodexRuntime {
|
|
|
76
79
|
onStatusChange = null,
|
|
77
80
|
sitesService = null,
|
|
78
81
|
onManagedCodexPidChange = null,
|
|
82
|
+
openControlPanel = () => promisify(execFileCallback)(process.execPath, [fileURLToPath(new URL("../../cli/bin/mcx.mjs", import.meta.url)), "gui"], { timeout:20_000 }),
|
|
79
83
|
}) {
|
|
84
|
+
this.openControlPanel = openControlPanel;
|
|
80
85
|
this.features = features;
|
|
81
86
|
this.sitesService = sitesService ?? (features.some(feature => feature.id === "sites") ? new SitesService() : null);
|
|
87
|
+
this.localImageSource = features.some(feature => feature.id === "images") ? new LocalImageSource({dataDir:path.join(profileDir,"minecodex-image-gen")}) : null;
|
|
82
88
|
this.profileDir = profileDir;
|
|
83
89
|
this.appPath = appPath;
|
|
84
90
|
this.cdpPort = cdpPort;
|
|
@@ -523,6 +529,11 @@ export class CodexRuntime {
|
|
|
523
529
|
return;
|
|
524
530
|
}
|
|
525
531
|
if (request.token !== this.bindingTokens.get(targetId) || typeof request.requestId !== "string") return;
|
|
532
|
+
if (request.featureId === "minecodex" && request.action === "open-control-panel") {
|
|
533
|
+
try { await this.openControlPanel(); await this.resolveHostAction(client, request.requestId, {ok:true,result:{opened:true}}); }
|
|
534
|
+
catch (error) { await this.resolveHostAction(client, request.requestId, {ok:false,error:{code:"CONTROL_PANEL_FAILED",message:error.stderr?.trim() || error.message}}); }
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
526
537
|
const feature = this.features.find((candidate) => candidate.id === request.featureId);
|
|
527
538
|
if (feature?.id === "session-status" && request.action === "refresh-session-status") {
|
|
528
539
|
if (this.sessionStatusReader && this.clients.get(targetId) === client && !this.stopping) {
|
|
@@ -549,6 +560,15 @@ export class CodexRuntime {
|
|
|
549
560
|
});
|
|
550
561
|
return;
|
|
551
562
|
}
|
|
563
|
+
if (feature.id === "images" && request.action === "local-image-source") {
|
|
564
|
+
try {
|
|
565
|
+
const result = await this.localImageSource.action(request.payload);
|
|
566
|
+
await this.resolveHostAction(client, request.requestId, {ok:true,result});
|
|
567
|
+
} catch (error) {
|
|
568
|
+
await this.resolveHostAction(client, request.requestId, {ok:false,error:{code:"LOCAL_IMAGE_SOURCE_FAILED",message:error.message}});
|
|
569
|
+
}
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
552
572
|
if (feature.id === "sites" && request.action === "sites") {
|
|
553
573
|
try {
|
|
554
574
|
const result = await this.sitesService.action(request.payload);
|
|
@@ -812,6 +832,7 @@ export class CodexRuntime {
|
|
|
812
832
|
if (this.stopPromise) return this.stopPromise;
|
|
813
833
|
this.stopPromise = (async () => {
|
|
814
834
|
this.stopping = true;
|
|
835
|
+
await this.localImageSource?.close();
|
|
815
836
|
this.targetRefreshQueued = false;
|
|
816
837
|
this.monitorWake?.();
|
|
817
838
|
await this.monitorPromise?.catch(() => {});
|
package/packages/ui/DESIGN.md
CHANGED
|
@@ -13,15 +13,18 @@
|
|
|
13
13
|
- 原生适配层:解析实际token、主题、zoom和能力状态;共享锚点仅包含真实跨插件需求。原生事实遵循根规范,不在每个组件重新猜测。
|
|
14
14
|
- 材质与层级基础:按Dialog、Dropdown、Popover、ComposerMenu、Page的原生分支提供有限选项,将遮罩与Portal规则同所选组件匹配。
|
|
15
15
|
- Dialog:提供容器、标题、Close及操作区的结构;插件填表单、列表或确认内容。
|
|
16
|
-
- Button
|
|
17
|
-
- Navbar:使用成对 default/selected 图标管理展示状态;原生图标与选中状态适配集中在 native 层,保留原生路由与 sidebar hover
|
|
16
|
+
- Button:通过 `action(node, "danger-solid")` 提供原生实色警戒分支,保留既有 `danger` soft 分支;通用 token 与适用规则见根 DESIGN.md。表达操作角色和状态,保留Cancel ghost、实际次级outline与主操作solid的语义区分;业务动作由插件传入。
|
|
17
|
+
- Navbar:使用成对 default/selected 图标管理展示状态;原生图标与选中状态适配集中在 native 层,保留原生路由与 sidebar hover。退出注入页面时按当前 committed 原生状态恢复选中属性,不回放进入时的旧状态;原生目的地变化时及时移除替身图标,原节点复用或重渲染后仍保证只显示一个图标。
|
|
18
18
|
- Tooltip:处理声明式标记、hover/focus、aria关联、关闭与卸载,不接管原生Tooltip。
|
|
19
|
+
- Page 的 `sidebar:"none"` 分支通过 native/page Adapter 取得原生无上下文侧栏页面壳;`releasePage` 释放时区分返回原页面和用户主动导航,禁用时清理。
|
|
19
20
|
- 页面、菜单与浮层:提供对应类型的外壳、挂载和通用交互,不读取提示词、会话或站点业务数据。
|
|
20
21
|
|
|
21
22
|
## 接入约束
|
|
22
23
|
|
|
23
24
|
当前page-script通过 `config.ui` 使用公共能力;现有API包括 `heading`、`footer`、`action`、`overlay`、`surface`。`surface(node, kind, options)` 显式区分原生材质;`page`、`mount`、`trapFocus`、`composerMenu`、`tooltipSurface`、`navigationRail` 维护实际复用的层级、焦点与定位。`entry.resources` 通过 `config.resources` 提供插件自身 CSS 与图标,通用视觉仍由组件库维护。
|
|
24
25
|
|
|
26
|
+
独立全页面且无专属二级导航的功能使用 `config.ui.page(node, { sidebar:"none" })`。移除页面前调用 `config.ui.releasePage(node)`;若用户正在主动选择原生导航,则传 `{ restoreNavigation:false }`,避免恢复动作覆盖用户选择。`sidebar:"none"` 页面通过 `onNavigate` 接收原生路由提交后的退出通知;一级、二级和程序化导航共用该契约,悬浮展开与加载中的通知不关闭页面。Adapter 在通知前取消路由订阅并放弃恢复旧位置,功能回调负责移除内容与恢复自身状态。默认 `sidebar:"inherit"` 保留现有接入行为,不自动迁移其他功能。页面壳的产品规则与验收标准见根 DESIGN.md 的「全页面功能的页面壳选择」。
|
|
27
|
+
|
|
25
28
|
Renderer安装函数经序列化注入,必须自包含。组件代码与Node/CDP隔离,宿主负责组装注入源码、生命周期和iframe主题桥;不为整理目录引入不必要的打包系统。
|
|
26
29
|
|
|
27
30
|
通用生命周期契约由组件库维护。原生已经提供的焦点圈定、Escape和关闭机制优先复用;插件继续拥有业务状态、内容更新和具体回调。迁移时同步触发点焦点恢复、异步卸载及原生节点归属验证。
|
|
@@ -29,3 +32,7 @@ Renderer安装函数经序列化注入,必须自包含。组件代码与Node/C
|
|
|
29
32
|
## 验证边界
|
|
30
33
|
|
|
31
34
|
组件验证关注可注入性、结构与回调、状态清理、挂载/焦点以及所选原生类型的实际行为,不镜像每条CSS写实现测试。布局、材质和原生状态变化按风险做隔离验收;只移动文件时验证导入、序列化、发布名单与已有功能回归。
|
|
35
|
+
|
|
36
|
+
## 账号菜单接入
|
|
37
|
+
|
|
38
|
+
`accountMenuEntry({label, icon, externalIcon, onOpen})` 通过 native/account-menu Adapter 获取原生 Settings 模板与关闭句柄。组件只修改脱离 React 的副本,负责重建去重、相邻键盘导航与卸载;回调拥有打开动作,组件不读取控制台 token 或浏览器状态。通用位置与视觉规则见根 DESIGN.md。
|
|
@@ -0,0 +1,14 @@
|
|
|
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="M7 7h10v10" />
|
|
13
|
+
<path d="M7 17 17 7" />
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lucide Icons and Contributors
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
The following Lucide icons are derived from the Feather project:
|
|
20
|
+
|
|
21
|
+
airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out
|
|
22
|
+
|
|
23
|
+
The MIT License (MIT) (for the icons listed above)
|
|
24
|
+
|
|
25
|
+
Copyright (c) 2013-present Cole Bemis
|
|
26
|
+
|
|
27
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
28
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
29
|
+
in the Software without restriction, including without limitation the rights
|
|
30
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
31
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
32
|
+
furnished to do so, subject to the following conditions:
|
|
33
|
+
|
|
34
|
+
The above copyright notice and this permission notice shall be included in all
|
|
35
|
+
copies or substantial portions of the Software.
|
|
36
|
+
|
|
37
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
38
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
39
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
40
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
41
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
42
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
43
|
+
SOFTWARE.
|