minecodex 2.0.5 → 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 (82) hide show
  1. package/DESIGN.md +15 -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 +13 -2
  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 +211 -0
  29. package/features/images/src/adapters/codex/native-image-source.mjs +70 -177
  30. package/features/images/src/frontend/cloud-image-actions.mjs +8 -6
  31. package/features/images/src/frontend/local-image-source.mjs +2 -1
  32. package/features/images/src/library-prompt-page.mjs +6 -1
  33. package/features/images/src/library-prompt-source.mjs +3 -5
  34. package/features/model-slider/codex-feature.json +2 -2
  35. package/features/notes/DESIGN.md +1 -1
  36. package/features/notes/codex-feature.json +2 -2
  37. package/features/notes/src/frontend/entry.mjs +1 -1
  38. package/features/session-status/codex-feature.json +2 -2
  39. package/features/sidebar-focus/DESIGN.md +4 -4
  40. package/features/sidebar-focus/codex-feature.json +3 -2
  41. package/features/sidebar-focus/src/adapters/codex/renderer.mjs +1 -1
  42. package/features/sidebar-focus/src/sidebar-focus.mjs +9 -7
  43. package/features/sites/DESIGN.md +25 -3
  44. package/features/sites/README.md +6 -2
  45. package/features/sites/codex-feature.json +3 -2
  46. package/features/sites/src/adapters/codex/renderer.mjs +25 -4
  47. package/features/sites/src/backend/project-sites.mjs +18 -6
  48. package/features/sites/src/backend/sites-service.mjs +22 -10
  49. package/features/sites/src/domain/discovery-candidates.mjs +53 -0
  50. package/features/sites/src/frontend/entry.mjs +76 -38
  51. package/features/sites/src/frontend/styles.css +3 -3
  52. package/features/unread-inbox/DESIGN.md +24 -25
  53. package/features/unread-inbox/codex-feature.json +3 -2
  54. package/features/unread-inbox/src/adapters/codex/modules.mjs +52 -0
  55. package/features/unread-inbox/src/adapters/codex/renderer.mjs +90 -0
  56. package/features/unread-inbox/src/domain/notifications.mjs +21 -0
  57. package/features/unread-inbox/src/injection-source.mjs +5 -2
  58. package/features/unread-inbox/src/unread-inbox.mjs +212 -297
  59. package/features/usage-meter/codex-feature.json +2 -2
  60. package/package.json +11 -3
  61. package/packages/cli/native/MineCodex.app/Contents/Info.plist +4 -2
  62. package/packages/cli/native/MineCodex.app/Contents/MacOS/MineCodexServiceManager +0 -0
  63. package/packages/cli/native/MineCodex.app/Contents/Resources/MineCodex.icns +0 -0
  64. package/packages/cli/native/MineCodex.app/Contents/_CodeSignature/CodeResources +11 -0
  65. package/packages/cli/native/README.md +31 -0
  66. package/packages/cli/src/browser-control-panel.mjs +48 -0
  67. package/packages/cli/src/commands.mjs +3 -1
  68. package/packages/cli/src/control-server.mjs +6 -3
  69. package/packages/cli/src/platform.mjs +5 -0
  70. package/packages/runtime-host/README.md +10 -5
  71. package/packages/runtime-host/src/codex-injection.mjs +10 -3
  72. package/packages/runtime-host/src/codex-runtime.mjs +10 -0
  73. package/packages/ui/DESIGN.md +5 -1
  74. package/packages/ui/assets/arrow-up-right.svg +14 -0
  75. package/packages/ui/assets/lucide-LICENSE.txt +43 -0
  76. package/packages/ui/src/components/account-menu.mjs +74 -0
  77. package/packages/ui/src/components/page.mjs +2 -2
  78. package/packages/ui/src/foundation/layers.mjs +1 -0
  79. package/packages/ui/src/install.mjs +3 -0
  80. package/packages/ui/src/native/account-menu.mjs +21 -0
  81. package/packages/ui/src/native/page.mjs +17 -2
  82. package/packages/ui/src/native/tokens.mjs +1 -0
@@ -1,152 +1,15 @@
1
- // 原生组件、构建产物与 React 上下文只在此适配;不复制图库或编辑器。
2
- export function createNativeImageSource(codex, { read = async url => (await fetch(url)).text(), load = url => import(url), requestHostAction, libraryApi, createCloudImageActions } = {}) {
3
- let connecting;
1
+ import {createNativeImageModules} from "./native-image-modules.mjs";
2
+
3
+ // 原生呈现与上下文只在此适配;构建产物发现由专用模块提供。
4
+ export function createNativeImageSource(codex, { requestHostAction, libraryApi, createCloudImageActions, signal, modules = createNativeImageModules() } = {}) {
5
+ const connect = modules.gallery;
4
6
  const opened = new Map();
5
7
 
6
- function imported(source, local) {
7
- for (const match of source.matchAll(/import\{([^}]+)\}from["']([^"']+)["']/g)) {
8
- for (const binding of match[1].split(",")) {
9
- const parts = binding.trim().split(/\s+as\s+/);
10
- if (parts.at(-1) === local) return { name: parts[0], file: match[2] };
11
- }
12
- }
13
- throw new Error(`Native Images import contract changed: ${local}`);
14
- }
15
-
16
- async function connect() {
17
- if (!connecting) connecting = (async () => {
18
- const urls = [...document.querySelectorAll('link[rel="modulepreload"],script[src]')].map(node => node.href || node.src);
19
- const initialUrl = urls.find(url => /\/app-initial-[^/]+\.js/.test(url));
20
- const mainUrl = urls.find(url => /\/app-main-[^/]+\.js/.test(url));
21
- if (!initialUrl || !mainUrl) throw new Error("Native Images modules are unavailable");
22
- const [initialSource, mainSource] = await Promise.all([read(initialUrl), read(mainUrl)]);
23
- const asset = (name, references = initialSource) => {
24
- const file = references.match(new RegExp(`(?:["'./])(${name}-[\\w-]+\\.js)`))?.[1];
25
- if (!file) throw new Error(`Native Images capability is unavailable: ${name}`);
26
- return new URL(file, initialUrl).href;
27
- };
28
- const module = async (name,references) => {
29
- const url = asset(name,references);
30
- const [source, exports] = await Promise.all([read(url), load(url)]);
31
- // 调用产物公开的惰性初始化器,不扫描或执行任意导出函数。
32
- for (const value of Object.values(exports)) {
33
- if (typeof value === "function" && /^function ([\w$]+)\(\)\{return\(\1=/.test(String(value))) value();
34
- }
35
- return {url, source, exports};
36
- };
37
- const [masonry, layout, sourceImage, menu, selection, initial] = await Promise.all([
38
- module("library-masonry"), module("library-item-layout"), module("generated-image-source"),
39
- module("generated-image-context-menu"), module("library-selection-toolbar"), load(initialUrl),
40
- ]);
41
- const [moveDialog,dialogs] = await Promise.all([module("library-move-dialog",selection.source),module("library-item-dialogs",selection.source)]);
42
- const [canvas,canvasLayout,filePreview] = await Promise.all([
43
- module("image-side-panel"),module("preview-layout"),module("library-cloud-file-preview"),
44
- ]);
45
- const component = (exports, predicate, name) => {
46
- const matches = Object.values(exports).filter(value => typeof value === "function" && predicate(String(value)));
47
- if (matches.length !== 1) throw new Error(`Native Images contract changed: ${name}`);
48
- return matches[0];
49
- };
50
- const binding = async (source, url, local) => {
51
- if (!local) throw new Error("Native Images component contract changed");
52
- const importedBinding = imported(source, local);
53
- return (await load(new URL(importedBinding.file, url).href))[importedBinding.name];
54
- };
55
- const routerLocal = selection.source.match(/get\(([\w$]+)\)\.prepareNavigation\(/)?.[1];
56
- const routerAtom = await binding(selection.source, selection.url, routerLocal);
57
- const switchLocal = selection.source.match(/([\w$]+)\(e,i\);let _=t.length===1/)?.[1];
58
- const switchMode = await binding(selection.source, selection.url, switchLocal);
59
- const draftLocal = selection.source.match(/let u=([\w$]+)\(e,\[\]\);e.set/)?.[1];
60
- const createChatDraft = await binding(selection.source, selection.url, draftLocal);
61
- const rootOwner = mainSource.match(/\(0,([\w$]+)\.createRoot\)/)?.[1];
62
- const rootInit = rootOwner && mainSource.match(new RegExp(`${rootOwner}=([\\w$]+)\\(\\)`))?.[1];
63
- const reactOwner = masonry.source.match(/([\w$]+)\.useRef\b/)?.[1];
64
- const reactInit = reactOwner && masonry.source.match(new RegExp(`${reactOwner}=([\\w$]+)\\(\\)`))?.[1];
65
- const [reactFactory, rootFactory] = await Promise.all([
66
- binding(masonry.source, masonry.url, reactInit), binding(mainSource, mainUrl, rootInit),
67
- ]);
68
- const React = reactFactory(), ReactDOM = rootFactory();
69
- const portalOwner=canvas.source.match(/\(0,([\w$]+)\.createPortal\)/)?.[1];
70
- const portalInit=portalOwner&&canvas.source.match(new RegExp(`${portalOwner}=([\\w$]+)\\(\\)`))?.[1];
71
- const createPortal=(await binding(canvas.source,canvas.url,portalInit))().createPortal;
72
- const dropdownLocal = selection.source.match(/\(0,[\w$]+\.jsxs?\)\(([\w$]+),\{contentWidth:`menu`/)?.[1];
73
- const itemsLocal = selection.source.match(/\(0,[\w$]+\.jsxs?\)\(([\w$]+)\.Item,\{/)?.[1];
74
- const buttonLocal = selection.source.match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{ref:[^,]+,color:`mediaOverlay`/)?.[1];
75
- const iconLocal = selection.source.match(/color:`mediaOverlay`[^;]+?children:\(0,[\w$]+\.jsx\)\(([\w$]+),\{\}\)/)?.[1];
76
- const [Dropdown, Menu, Button, MoreIcon] = await Promise.all([dropdownLocal, itemsLocal, buttonLocal, iconLocal].map(local => binding(selection.source, selection.url, local)));
77
- const checkboxFunction = component(selection.exports,source=>/onCheckedChange:/.test(source)&&/viewMode:/.test(source),"selection checkbox");
78
- const checkboxLocal = String(checkboxFunction).match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{className:`cursor-interaction`,variant:/)?.[1];
79
- const Checkbox = await binding(selection.source,selection.url,checkboxLocal);
80
- const rangeSelection = component(selection.exports,source=>/^function [\w$]+\(\{orderedIds:/.test(source)&&/extendRange:/.test(source),"range selection");
81
- const selectionLocal = selection.source.match(/\.set\(([\w$]+),new Set\)/)?.[1];
82
- const selectionExport = selection.source.match(/export\{([^}]+)\}/)?.[1].split(",").map(value=>value.trim().split(/\s+as\s+/)).find(parts=>parts[0]===selectionLocal)?.at(-1);
83
- const nativeSelectedIds = selection.exports[selectionExport];
84
- if (!nativeSelectedIds) throw new Error("Native selection state contract changed");
85
- const clearPrefix = selection.source.slice(selection.source.indexOf("id:`libraryNext.selection.clear`"));
86
- const clearLocal = clearPrefix.match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{\}\)/)?.[1];
87
- const ClearIcon = await binding(selection.source,selection.url,clearLocal);
88
- const dialogLocal = dialogs.source.match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{open:[^,]+,showDialogClose:!1/)?.[1];
89
- const contentLocal = dialogs.source.match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{as:`form`,onSubmit:/)?.[1];
90
- const [Dialog,DialogContent] = await Promise.all([dialogLocal,contentLocal].map(local=>binding(dialogs.source,dialogs.url,local)));
91
- const menuCoreFile = menu.source.match(/from["']([^"']*generated-image-context-menu-[^"']+)["']/)?.[1];
92
- const menuCoreUrl = menuCoreFile ? new URL(menuCoreFile,menu.url).href : menu.url;
93
- const menuCoreSource = menuCoreFile ? await read(menuCoreUrl) : menu.source;
94
- const imageActions = component(menu.exports,source=>/onOpenedInNewTab:/.test(source),"image actions");
95
- const imageActionSource = String(imageActions);
96
- const imageFileLocal = imageActionSource.match(/let\{file:[\w$]+\}=([\w$]+)\(/)?.[1];
97
- const attachLocal = imageActionSource.match(/await ([\w$]+)\(e,i,\[n\],/)?.[1];
98
- const downloadLocal = imageActionSource.match(/await ([\w$]+)\(\{downloadSrc:/)?.[1];
99
- const [imageFile,attachChatImages,downloadImage] = await Promise.all([imageFileLocal,attachLocal,downloadLocal].map(local=>binding(menuCoreSource,menuCoreUrl,local)));
100
- const toolbarClass = selection.source.match(/className:`(pointer-events-auto flex h-toolbar[^`]+)`/)?.[1];
101
- const toolbarPositionClass = selection.source.match(/className:`(pointer-events-none fixed inset-x-4 bottom-6[^`]+)`/)?.[1];
102
- if (!toolbarClass || !toolbarPositionClass) throw new Error("Native selection toolbar layout changed");
103
- const libraryIcon = async label => {
104
- const marker = selection.source.indexOf(`defaultMessage:\`${label}\``);
105
- const prefix = selection.source.slice(Math.max(0, marker - 500), marker);
106
- const match = [...prefix.matchAll(/leftIcon:\(0,[\w$]+\.jsx\)\(([\w$]+),\{icon:\{(assets:\{)?16:([\w$]+),20:([\w$]+)/g)].at(-1);
107
- if (!match) throw new Error(`Native Library icon contract changed: ${label}`);
108
- const [Icon, small, medium] = await Promise.all([match[1], match[3], match[4]].map(local => binding(selection.source, selection.url, local)));
109
- const sizes = {16:small, 20:medium};
110
- return React.createElement(Icon, {icon:match[2] ? {assets:sizes} : sizes});
111
- };
112
- const upload = component(initial, source => /async function/.test(source) && /libraryPersistenceMode:/.test(source) && /uploadPurpose:/.test(source), "Library upload");
113
- const setFavorite = component(initial, source => /target_ref:/.test(source) && /showToast:/.test(source), "Library favorite");
114
- const auth = String(setFavorite).match(/accountId:e.get\(([\w$]+)\),userId:e.get\(([\w$]+)\)/);
115
- const [accountAtom,userAtom] = await Promise.all([auth?.[1],auth?.[2]].map(local => binding(initialSource,initialUrl,local)));
116
- if (!accountAtom || !userAtom) throw new Error("Native Library account contract changed");
117
- const toastLocal = selection.source.match(/get\(([\w$]+)\)\.danger\(/)?.[1];
118
- const toastAtom = await binding(selection.source, selection.url, toastLocal);
119
- const favoritePrefix = selection.source.slice(Math.max(0,selection.source.indexOf("id:`library.favorites.add`")-2000),selection.source.indexOf("id:`library.favorites.add`")+500);
120
- const favoriteAsset = favoritePrefix.match(/\{16:([\w$]+),20:([\w$]+)\}:\{16:([\w$]+),20:([\w$]+)\}/);
121
- if (!favoriteAsset) throw new Error("Native Library favorite icon contract changed");
122
- const favoriteIconLocal = favoritePrefix.match(/icon:\{assets:/)?.index;
123
- const favoriteIconOwner = favoritePrefix.slice(0,favoriteIconLocal).match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{$/)?.[1];
124
- const [FavoriteIcon, savedSmall, savedMedium, emptySmall, emptyMedium] = await Promise.all([favoriteIconOwner,...favoriteAsset.slice(1)].map(local => binding(selection.source,selection.url,local)));
125
- const favoriteIcon = saved => React.createElement(FavoriteIcon,{icon:{assets:saved?{16:savedSmall,20:savedMedium}:{16:emptySmall,20:emptyMedium}}});
126
- const [chatIcon, downloadIcon, folderIcon, deleteIcon] = await Promise.all([
127
- libraryIcon("Chat about this"), libraryIcon("Download"), libraryIcon("Add to folder"), libraryIcon("{kind, select, directory {Delete folder} other {Delete}}"),
128
- ]);
129
- // 文件画布使用原生 Home Composer 的控制器、附件 scope 和输入组件。
130
- const composerLocals=["kr","Qn","$n","rr","ke","hn","tn","P"];
131
- const [useComposerController,useHomeComposer,Composer,ComposerSurface,ScopeProvider,composerScope,createComposerValue,composerRouteScope] = await Promise.all(composerLocals.map(local=>binding(filePreview.source,filePreview.url,local)));
132
- if (!React.createElement || !ReactDOM.createRoot || !Menu.Item) throw new Error("Native Images rendering contract changed");
133
- return {
134
- React, ReactDOM, createPortal, Dropdown, Menu, Button, MoreIcon, chatIcon, downloadIcon, folderIcon, favoriteIcon, routerAtom, switchMode, createChatDraft, upload, setFavorite, toastAtom, accountAtom, userAtom,
135
- MoveDialog:moveDialog.exports.MoveLibraryItemsDialog,
136
- Checkbox,rangeSelection,nativeSelectedIds,ClearIcon,Dialog,DialogContent,ConfirmationContent:dialogs.exports.LibraryItemConfirmationContent,
137
- Canvas:canvas.exports.t,CanvasLayout:canvasLayout.exports.r,
138
- useComposerController,useHomeComposer,Composer,ComposerSurface,ScopeProvider,composerScope,createComposerValue,composerRouteScope,
139
- editImage:component(initial,source=>/async function/.test(source)&&/requireDispatchAcceptance:/.test(source)&&/sourceAttachment:/.test(source)&&/imageVariant/.test(source),"image edit submission"),
140
- imageFile,attachChatImages,downloadImage,toolbarClass,toolbarPositionClass,deleteIcon,
141
- Masonry: component(masonry.exports, source => /\{items:[^}]+animate:/.test(source), "masonry"),
142
- Skeleton: component(masonry.exports, source => /square:/.test(source) && /Loading library/.test(source), "skeleton"),
143
- Card: component(layout.exports, source => /actionIndicator:/.test(source) && /documentHeader:/.test(source), "card"),
144
- useImage: component(sourceImage.exports, source => /shouldLoadFileDataUrl:/.test(source), "image resolver"),
145
- imageMenu: component(menu.exports, source => /onOpenedInNewTab:/.test(source), "image actions"),
146
- openImage: component(initial, source => /availableImageCount:/.test(source) && /openInEditor:/.test(source) && /entrypoint:/.test(source), "image preview"),
147
- };
148
- })().catch(error => { connecting = null; throw error; });
149
- return connecting;
8
+ function uploadFile({base64,name}) {
9
+ const decoded=atob(base64),bytes=new Uint8Array(decoded.length);
10
+ // 直接写入字节,避免大图经字符串迭代器创建中间数组。
11
+ for(let index=0;index<decoded.length;index++)bytes[index]=decoded.charCodeAt(index);
12
+ return new File([bytes],name,{type:"image/png"});
150
13
  }
151
14
 
152
15
  function environment(anchor) {
@@ -189,7 +52,7 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
189
52
  }
190
53
 
191
54
  async function mountNavigation(container, anchor, onSelect) {
192
- const native = await connect();
55
+ const native = await modules.navigation();
193
56
  const context = environment(anchor);
194
57
  const root = native.ReactDOM.createRoot(container);
195
58
  return {
@@ -200,7 +63,6 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
200
63
  selectedKey,
201
64
  tabs:[...contract.props.tabs, {key:"imagegen", name:"Image Gen"}],
202
65
  onSelect:key => {
203
- if (key === "imagegen") context.libraryScope?.set(native.nativeSelectedIds,new Set());
204
66
  onSelect(key);
205
67
  if (key !== "imagegen") navigationContract(anchor).props.onSelect(key);
206
68
  },
@@ -216,20 +78,20 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
216
78
  }
217
79
 
218
80
  async function mount(container, anchor, {onOpen, onPrompt, onRetry, onHidden}) {
219
- const native = await connect();
81
+ let native = await connect();
220
82
  const context = environment(anchor);
221
83
  const {React, ReactDOM} = native;
222
84
  const h = React.createElement;
223
85
  let failure, currentContent, folderDialog, removeDialog, canvasImage, disposed=false, batchBusy=false;
224
- let currentImages=[],currentLoading=true,selectedIds=new Set(),selectionAnchor=null;
86
+ let currentImages=[],currentLoading=true,selectedIds=new Set(),selectionAnchor=null,openRevision=0;
225
87
  const scope = context.libraryScope;
226
88
  if (!scope) throw new Error("Native Library context is unavailable");
227
89
  const {accountId,userId} = scope.value;
228
90
  const message = (id,defaultMessage) => context.intl.formatMessage({id,defaultMessage});
229
91
  const toast = () => scope.get(native.toastAtom);
230
- const assertAccount = () => {
231
- if (disposed || scope.get(native.accountAtom) !== accountId || scope.get(native.userAtom) !== userId) throw new Error("Library account changed");
232
- };
92
+ const sameAccount = () => scope.get(native.accountAtom) === accountId && scope.get(native.userAtom) === userId;
93
+ const isCurrent = () => !disposed && sameAccount();
94
+ const assertAccount = () => {if (!isCurrent()) throw new Error("Library action is no longer current");};
233
95
  const reference = (image,operation,extra={}) => requestHostAction("local-image-source",{operation,id:image.id,accountId,userId,...extra});
234
96
  async function favoriteStatus(id) {
235
97
  const result = await libraryApi.favoriteStatus(id);
@@ -239,13 +101,12 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
239
101
  }
240
102
  const cloud = createCloudImageActions({
241
103
  reference, assertAccount,
242
- fileNode:id => libraryApi.fileNode(id), favoriteStatus,
104
+ favoriteStatus,
243
105
  setFavorite:(libraryFileId,favoriteId,isFavorited) => native.setFavorite(scope,{libraryFileId,favoriteId,isFavorited,surface:"library_grid",showToast:true}),
244
106
  upload:async file => {
245
- const bytes = Uint8Array.from(atob(file.base64),value => value.charCodeAt(0));
246
107
  const pendingToast = toast().info(message("minecodex.images.saving","Saving image to Library…"),{duration:0,hasCloseButton:false});
247
108
  try {
248
- const uploaded = await native.upload(scope,new File([bytes],file.name,{type:"image/png"}),{
109
+ const uploaded = await native.upload(scope,uploadFile(file),{
249
110
  libraryPersistenceMode:"required",storeInLibrary:true,uploadPurpose:"library",beforeRequest:assertAccount,
250
111
  });
251
112
  return uploaded.libraryFileId;
@@ -255,13 +116,21 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
255
116
  async function folder(image) {
256
117
  const images=Array.isArray(image)?image:[image];
257
118
  const registered=[];
258
- for(const item of images)registered.push(await cloud.registered(item));
259
- const entries=registered.map(item=>item.entry),created=registered.some(item=>item.created);
119
+ const entries=[];
120
+ for (const item of images) {
121
+ const result=await cloud.registered(item);
122
+ const node=await libraryApi.fileNode(result.libraryFileId);
123
+ assertAccount();
124
+ registered.push(result);
125
+ entries.push({node});
126
+ }
127
+ const created=registered.some(item=>item.created);
260
128
  let moved = false;
261
129
  folderDialog = h(native.MoveDialog,{
262
130
  accountId,userId,entries,initialPath:[],
263
131
  onMoved:() => {moved=true;},
264
132
  onClose:() => {
133
+ if (!isCurrent()) return;
265
134
  folderDialog=null;render(currentContent);
266
135
  if (!moved) toast().info(created
267
136
  ? message("minecodex.images.folderCanceledSaved","Image saved to Library, but not added to a folder.")
@@ -271,7 +140,7 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
271
140
  render(currentContent);
272
141
  }
273
142
  function operationError(error) {
274
- if (disposed) return;
143
+ if (!isCurrent()) return;
275
144
  toast().danger(error.code === "UPLOAD_UNCONFIRMED"
276
145
  ? message("minecodex.images.uploadUnconfirmed","Image upload is pending or could not be confirmed. Check Library before uploading again.")
277
146
  : message("minecodex.images.libraryActionFailed","Couldn’t complete the Library action. Please try again."));
@@ -279,6 +148,7 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
279
148
  }
280
149
  const root = ReactDOM.createRoot(container, {onUncaughtError:error => {failure=error;onRetry(error);}});
281
150
  function render(content) {
151
+ if (!isCurrent()) return;
282
152
  currentContent = content;
283
153
  let tree = h(React.Fragment,null,content,folderDialog,removeDialog,selectionToolbar(),canvasImage?h(ImageCanvas,{key:canvasImage.id,image:canvasImage}):null);
284
154
  for (const provider of context.providers) tree = h(provider.type, {value:provider.value}, tree);
@@ -293,7 +163,7 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
293
163
  selectedIds=next.selectedIds;selectionAnchor=next.anchor;refresh();
294
164
  }
295
165
  const selectedImages = () => currentImages.filter(image=>selectedIds.has(image.id));
296
- function clearSelection() {selectedIds=new Set();selectionAnchor=null;removeDialog=null;refresh();}
166
+ function clearSelection() {openRevision++;selectedIds=new Set();selectionAnchor=null;removeDialog=null;refresh();}
297
167
  async function performBatch(action) {
298
168
  if (batchBusy) return;
299
169
  batchBusy=true;refresh();
@@ -307,13 +177,13 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
307
177
  h(native.DialogContent,{as:"form",onSubmit:event=>{
308
178
  event.preventDefault();if(pending)return;pending=true;update();refresh();
309
179
  void requestHostAction("local-image-source",{operation:"hide",ids:images.map(image=>image.id)}).then(result=>{
310
- if(disposed)return;
180
+ if(!isCurrent())return;
311
181
  const hidden=new Set(result.hiddenIds);
312
182
  currentImages=currentImages.filter(image=>!hidden.has(image.id));
313
183
  selectedIds=new Set([...selectedIds].filter(id=>!hidden.has(id)));
314
184
  pending=false;removeDialog=null;onHidden?.(result.hiddenIds);refresh();
315
185
  toast().info(message("minecodex.images.hidden","Hidden from Image Gen"));
316
- }).catch(error=>{pending=false;update();refresh();operationError(error);});
186
+ }).catch(error=>{if(!isCurrent())return;pending=false;update();refresh();operationError(error);});
317
187
  }},h(native.ConfirmationContent,{
318
188
  title:context.intl.formatMessage({id:"minecodex.images.hideTitle",defaultMessage:"Delete {count, plural, one {image} other {# images}} from Image Gen?"},{count:images.length}),
319
189
  description:message("minecodex.images.hideDescription","These images will only be hidden from Image Gen. Local originals and cloud copies will be kept."),
@@ -347,11 +217,13 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
347
217
  }
348
218
  document.addEventListener("keydown",onSelectionKey);
349
219
  async function startChat(images) {
220
+ assertAccount();
350
221
  const conversationId=native.createChatDraft(context.scope,[]);
351
222
  native.switchMode(context.scope,"chat");
352
223
  const files=[];
353
- for(const image of images){const file=await reference(image,"upload-file");files.push(native.imageFile(`data:image/png;base64,${file.base64}`,file.name).file);}
224
+ for(const image of images){const file=await reference(image,"upload-file");assertAccount();files.push(native.imageFile(`data:image/png;base64,${file.base64}`,file.name).file);}
354
225
  if (!await native.attachChatImages(context.scope,conversationId,files,{isTemporaryChat:false})) throw new Error("Could not attach selected images");
226
+ assertAccount();
355
227
  const navigate=context.scope.get(native.routerAtom).prepareNavigation("/",{state:{codexAppMode:"chat",initialChatGptConversationId:conversationId,focusComposerNonce:Date.now()}});
356
228
  if(!navigate)throw new Error("Native Chat navigation is unavailable");
357
229
  clearSelection();await navigate();
@@ -359,9 +231,19 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
359
231
  function localImage(image) {
360
232
  return {id:image.imageGenerationItemId, src:image.sourcePath, previewSrc:image.sourcePath, alt:image.name, tabTitle:image.name, width:image.width, height:image.height};
361
233
  }
362
- function open(image, options) {
234
+ async function open(image, options) {
235
+ if (!options) {
236
+ assertAccount();
237
+ const revision=++openRevision;
238
+ const canvas=await modules.canvas();
239
+ // 切换来源或重置列表后,不让晚到的画布覆盖当前页面。
240
+ if(revision!==openRevision || !isCurrent())return false;
241
+ native={...native,...canvas};
242
+ opened.set(image.sourcePath,image);
243
+ canvasImage=image;refresh();return true;
244
+ }
245
+ if (signal?.aborted || !sameAccount()) return;
363
246
  opened.set(image.sourcePath, image);
364
- if (!options) {canvasImage=image;refresh();return;}
365
247
  const {readOnly=false,scope=context.scope,chatGptConversationId}=options;
366
248
  const result = native.openImage(scope, {
367
249
  chatGptConversationId, alt:image.name, title:image.name, attachmentId:`image-playground:${image.imageGenerationItemId}`,
@@ -408,8 +290,7 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
408
290
  assertCurrent();
409
291
  if(!attachment.current)attachment.current=(async()=>{
410
292
  const file=await reference(image,"upload-file");assertCurrent();
411
- const bytes=Uint8Array.from(atob(file.base64),value=>value.charCodeAt(0));
412
- const uploaded=await native.upload(context.scope,new File([bytes],file.name,{type:"image/png"}),{storeInLibrary:false,beforeRequest:assertCurrent,signal:abort.signal});
293
+ const uploaded=await native.upload(context.scope,uploadFile(file),{storeInLibrary:false,beforeRequest:assertCurrent,signal:abort.signal});
413
294
  return {...uploaded,width:image.width,height:image.height,imageReference:true};
414
295
  })().catch(error=>{attachment.current=null;throw error;});
415
296
  return attachment.current;
@@ -463,6 +344,7 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
463
344
  return h(native.CanvasLayout,{assetName:image.name,contentOwnsHeader:true,onClose:requestClose,children:canvas});
464
345
  }
465
346
  async function chatAbout(image) {
347
+ assertAccount();
466
348
  const conversationId = native.createChatDraft(context.scope, []);
467
349
  native.switchMode(context.scope, "chat");
468
350
  const navigate = context.scope.get(native.routerAtom).prepareNavigation("/", {
@@ -471,24 +353,31 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
471
353
  if (!navigate) throw new Error("Native Chat navigation is unavailable");
472
354
  await navigate();
473
355
  await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
474
- const composer = document.querySelector('[data-codex-composer="true"]') ?? document.querySelector('[contenteditable="true"]');
356
+ // 导航会正常卸载图库;只把面板移交给目标 Chat 的可见输入框。
357
+ if (signal?.aborted || !sameAccount()) return;
358
+ const composer = codex.composer(null,{purpose:"insertion"})?.editable;
475
359
  if (!composer) throw new Error("Native Chat composer is unavailable");
476
360
  const chat = environment(composer);
477
- open(image, {readOnly:true, scope:chat.scope, chatGptConversationId:conversationId});
361
+ await open(image, {readOnly:true, scope:chat.scope, chatGptConversationId:conversationId});
478
362
  }
479
363
  function ImageCard({image}) {
480
364
  const [saved,setSaved] = React.useState(false);
481
365
  const [busy,setBusy] = React.useState(false);
366
+ const active=React.useRef(true);
367
+ const favoriteRevision=React.useRef(0);
368
+ React.useEffect(()=>{active.current=true;return()=>{active.current=false;favoriteRevision.current++;};},[]);
482
369
  async function perform(action) {
483
370
  if (busy) return;
484
371
  setBusy(true);
485
- try {await action();} catch(error) {operationError(error);} finally {setBusy(false);}
372
+ try {assertAccount();await action();} catch(error) {operationError(error);} finally {if(active.current&&isCurrent())setBusy(false);}
486
373
  }
487
374
  async function refreshFavorite(open) {
375
+ const revision=++favoriteRevision.current;
488
376
  if (!open) return;
489
377
  try {
490
378
  const cached = await reference(image,"reference");
491
- setSaved(cached.libraryFileId ? !!(await favoriteStatus(cached.libraryFileId)) : false);
379
+ const value=cached.libraryFileId ? !!(await favoriteStatus(cached.libraryFileId)) : false;
380
+ if (active.current && isCurrent() && revision===favoriteRevision.current) setSaved(value);
492
381
  } catch(error) {console.error("MineCodex favorite status",error);}
493
382
  }
494
383
  const resolved = native.useImage({src:image.sourcePath, conversationId:image.threadId, hostId:"local", shouldLoadFileDataUrl:true});
@@ -509,13 +398,17 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
509
398
  const promptIcon = menuGlyph ? h("span", {className:"inline-flex size-4 items-center justify-center", "aria-hidden":true, dangerouslySetInnerHTML:{__html:menuGlyph.outerHTML}}) : null;
510
399
  const menuItems = [
511
400
  {id:"chat-about", icon:native.chatIcon, text:context.intl.formatMessage({id:"libraryNext.actions.chatAboutItem",defaultMessage:"Chat about this"}),onSelect:() => {void chatAbout(image).catch(onRetry);}},
401
+ {id:"view-prompts", icon:promptIcon, text:context.intl.formatMessage({id:"minecodex.images.viewPrompts",defaultMessage:"View prompts"}),onSelect:() => {void open(image).then(opened=>{if(opened&&isCurrent())onPrompt(image);}).catch(operationError);}},
512
402
  {id:"favorite",icon:native.favoriteIcon(saved),text:saved
513
403
  ? message("library.favorites.remove","Remove from Favorites")
514
- : message("library.favorites.add","Add to Favorites"),onSelect:() => {void perform(async () => setSaved(await cloud.favorite(image)));}},
404
+ : message("library.favorites.add","Add to Favorites"),onSelect:() => {void perform(async () => {
405
+ favoriteRevision.current++;
406
+ const value=await cloud.favorite(image);
407
+ if(active.current&&isCurrent()){favoriteRevision.current++;setSaved(value);}
408
+ });}},
515
409
  action("download-a-copy", native.downloadIcon, {id:"libraryNext.actions.download", defaultMessage:"Download"}),
516
410
  {id:"folder",icon:native.folderIcon,text:message("libraryNext.actions.addToFolder","Add to folder"),onSelect:() => {void perform(() => folder(image));}},
517
411
  {id:"delete",icon:native.deleteIcon,tone:"danger",text:message("libraryNext.actions.delete","Delete"),onSelect:()=>confirmRemove([image])},
518
- {id:"view-prompts", icon:promptIcon, text:context.intl.formatMessage({id:"minecodex.images.viewPrompts",defaultMessage:"View prompts"}),onSelect:() => {open(image);onPrompt(image);}},
519
412
  ].filter(Boolean);
520
413
  const dropdown = h(native.Dropdown, {
521
414
  contentWidth:"menu", align:"end", onOpenChange:refreshFavorite,
@@ -534,17 +427,17 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
534
427
  },children:h(native.Checkbox,{className:"cursor-interaction",variant:"overlay",checked,disabled:batchBusy,"aria-label":context.intl.formatMessage({id:"libraryNext.selection.item",defaultMessage:"Select {name}"},{name:image.name}),onCheckedChange:value=>select(image,value===true)})});
535
428
  return h(native.Card,{name:image.name,selection,selected:checked,selectionActive:selectedIds.size>0,onSelect:event=>{
536
429
  if(selectedIds.size || event.shiftKey){select(image,!checked,event.shiftKey);return true;}return false;
537
- },onOpen:() => onOpen(image,open),actions:dropdown,children:h("span",{className:"relative flex min-h-0 w-full flex-1 items-center justify-center",children:h("span",{className:"relative flex w-full items-center justify-center",style:{aspectRatio:cardRatio},children:picture})})});
430
+ },onOpen:() => {void Promise.resolve(onOpen(image,open)).catch(operationError);},actions:dropdown,children:h("span",{className:"relative flex min-h-0 w-full flex-1 items-center justify-center",children:h("span",{className:"relative flex w-full items-center justify-center",style:{aspectRatio:cardRatio},children:picture})})});
538
431
  }
539
432
  return {
540
433
  render({images,loading}) {
541
434
  if (failure) throw failure;
542
435
  currentImages=images;currentLoading=loading;
543
- if(loading){selectedIds=new Set();selectionAnchor=null;removeDialog=null;}
436
+ if(loading){openRevision++;selectedIds=new Set();selectionAnchor=null;removeDialog=null;scope.set(native.nativeSelectedIds,new Set());}
544
437
  refresh();
545
438
  },
546
439
  clearSelection,
547
- dispose() {disposed=true;document.removeEventListener("keydown",onSelectionKey);root.unmount();},
440
+ dispose() {disposed=true;openRevision++;document.removeEventListener("keydown",onSelectionKey);root.unmount();},
548
441
  };
549
442
  }
550
443
 
@@ -1,12 +1,13 @@
1
1
  // 工作流只管理云端身份与操作顺序;上传协议和原生组件由 Adapter 提供。
2
- export function createCloudImageActions({reference,upload,fileNode,favoriteStatus,setFavorite,assertAccount}) {
2
+ export function createCloudImageActions({reference,upload,favoriteStatus,setFavorite,assertAccount}) {
3
3
  const pending = new Map();
4
4
  function registered(image) {
5
5
  if (!pending.has(image.id)) {
6
6
  const task = (async () => {
7
7
  assertAccount();
8
8
  const cached = await reference(image,"reference");
9
- if (cached.libraryFileId) return {entry:{node:await fileNode(cached.libraryFileId)},created:false};
9
+ assertAccount();
10
+ if (cached.libraryFileId) return {libraryFileId:cached.libraryFileId,created:false};
10
11
  const unconfirmed = () => Object.assign(new Error("Library upload is pending or could not be confirmed"),{code:"UPLOAD_UNCONFIRMED"});
11
12
  if (cached.status === "uploading") throw unconfirmed();
12
13
  const file = await reference(image,"upload-file");
@@ -18,7 +19,7 @@ export function createCloudImageActions({reference,upload,fileNode,favoriteStatu
18
19
  // 先保存身份;收藏失败或取消文件夹选择后仍可复用上传结果。
19
20
  await reference(image,"register",{libraryFileId,fingerprint:file.fingerprint});
20
21
  assertAccount();
21
- return {entry:{node:await fileNode(libraryFileId)},created:true};
22
+ return {libraryFileId,created:true};
22
23
  })();
23
24
  pending.set(image.id,task);
24
25
  const release = () => {if (pending.get(image.id) === task) pending.delete(image.id);};
@@ -29,10 +30,11 @@ export function createCloudImageActions({reference,upload,fileNode,favoriteStatu
29
30
  return {
30
31
  registered,
31
32
  async favorite(image) {
32
- const {entry} = await registered(image);
33
- const favoriteId = await favoriteStatus(entry.node.id);
33
+ const {libraryFileId} = await registered(image);
34
+ const favoriteId = await favoriteStatus(libraryFileId);
35
+ assertAccount();
36
+ if (!await setFavorite(libraryFileId,favoriteId,!favoriteId)) throw new Error("Library favorite could not be changed");
34
37
  assertAccount();
35
- if (!await setFavorite(entry.node.id,favoriteId,!favoriteId)) throw new Error("Library favorite could not be changed");
36
38
  return !favoriteId;
37
39
  },
38
40
  };
@@ -183,7 +183,8 @@ export function installLocalImageSource(config, signal, sources, native) {
183
183
  });
184
184
  observer.observe(document.body, {childList:true, subtree:true, attributes:true, attributeFilter:["aria-selected"]});
185
185
  void scan();
186
- const cleanup = () => {observer.disconnect(); unmount(); pendingPrompt = null;};
186
+ let cleaned = false;
187
+ const cleanup = () => {if(cleaned)return;cleaned=true;observer.disconnect();unmount();pendingPrompt=null;};
187
188
  signal.addEventListener("abort", cleanup, {once:true});
188
189
  return {
189
190
  cleanup,
@@ -3,6 +3,7 @@ const PANEL_ID = "minecodex-library-prompt";
3
3
  const zh = () => (document.documentElement.lang || navigator.language || "en").toLowerCase().startsWith("zh");
4
4
  const label = (en, cn) => zh() ? cn : en;
5
5
  let mounted = null;
6
+ let localImageSource = null;
6
7
  const libraryApi=createLibraryApi();
7
8
  let scheduled = false;
8
9
  let candidate = null;
@@ -257,6 +258,9 @@ function localPromptRequested(){openPendingLocalPrompt();void scan();}
257
258
  document.addEventListener("minecodex:image-prompt",localPromptRequested);
258
259
  const localeObserver = new MutationObserver(() => mounted?.refreshLocale());
259
260
  localeObserver.observe(document.documentElement, { attributes:true, attributeFilter:["lang"] });
261
+ localImageSource = installLocalImageSource(config, signal, createImageSourcesAdapter(config.codex), createNativeImageSource(config.codex, {
262
+ requestHostAction:config.requestHostAction, libraryApi, createCloudImageActions, signal,
263
+ }));
260
264
  const observer = new MutationObserver(() => {
261
265
  if (scheduled) return;
262
266
  scheduled = true;
@@ -270,7 +274,8 @@ function escape(event) {
270
274
  closePanel(mounted, true);
271
275
  }
272
276
  document.addEventListener("keydown", escape, true);
273
- const cleanup = () => { document.removeEventListener("minecodex:image-prompt",localPromptRequested); candidate?.request.abort(); candidate = null; observer.disconnect(); localeObserver.disconnect(); document.removeEventListener("keydown", escape, true); unmount(); };
277
+ let cleaned = false;
278
+ const cleanup = () => {if(cleaned)return;cleaned=true;document.removeEventListener("minecodex:image-prompt",localPromptRequested);candidate?.request.abort();candidate=null;observer.disconnect();localeObserver.disconnect();document.removeEventListener("keydown",escape,true);unmount();localImageSource.cleanup();};
274
279
  signal.addEventListener("abort", cleanup, { once: true });
275
280
  scan();
276
281
  return cleanup;
@@ -1,6 +1,7 @@
1
1
  import { createCloudImageActions } from "./frontend/cloud-image-actions.mjs";
2
2
  import { createImageSourcesAdapter } from "./adapters/codex/image-sources.mjs";
3
3
  import { createNativeImageSource } from "./adapters/codex/native-image-source.mjs";
4
+ import {createNativeImageModules, discoverFilePreviewComposerBindings} from "./adapters/codex/native-image-modules.mjs";
4
5
  import { installLocalImageSource } from "./frontend/local-image-source.mjs";
5
6
  import { createLibraryApi } from "./adapters/codex/library-api.mjs";
6
7
  import { createLibraryViewerAdapter } from "./adapters/codex/library-viewer.mjs";
@@ -9,10 +10,7 @@ import { promptResolverSource } from "./adapters/codex/prompt-resolver-source.mj
9
10
 
10
11
  export async function createPageScriptSource() {
11
12
  const source=await readFile(new URL("./library-prompt-page.mjs",import.meta.url),"utf8");
12
- const functions={createCloudImageActions,createImageSourcesAdapter,createNativeImageSource,installLocalImageSource,createLibraryApi,createLibraryViewerAdapter};
13
+ const functions={createCloudImageActions,createImageSourcesAdapter,discoverFilePreviewComposerBindings,createNativeImageModules,createNativeImageSource,installLocalImageSource,createLibraryApi,createLibraryViewerAdapter};
13
14
  const declarations=Object.entries(functions).map(([name,fn])=>`const ${name}=${fn.toString()};`).join("\n");
14
- return `${declarations}
15
- let localImageSource=null;
16
- const promptFromConversation=${promptResolverSource()};
17
- ${source.replace("const observer =", "localImageSource=installLocalImageSource(config,signal,createImageSourcesAdapter(config.codex),createNativeImageSource(config.codex,{requestHostAction:config.requestHostAction,libraryApi:createLibraryApi(),createCloudImageActions}));\nconst observer =").replace("return cleanup;", "return () => { cleanup(); localImageSource.cleanup(); };")}`;
15
+ return `${declarations}\nconst promptFromConversation=${promptResolverSource()};\n${source}`;
18
16
  }
@@ -7,8 +7,8 @@
7
7
  "zh-CN": "模型滑条"
8
8
  },
9
9
  "description": {
10
- "en": "A clearer, faster model picker with provider identity, favorites, and smoother interaction.",
11
- "zh-CN": "让模型选择更清楚、更快,提供品牌识别、常用收藏和更顺畅的交互。"
10
+ "en": "Switch models and thinking levels. Keep favorites within reach.",
11
+ "zh-CN": "切换模型与思考强度,快速找到常用收藏。"
12
12
  },
13
13
  "entry": {
14
14
  "kind": "composer-model-selector"
@@ -54,7 +54,7 @@ Header 为 Prompts;右侧依次为排序图标、分类调节图标、常驻
54
54
 
55
55
  拖拽排序使用等高 Drop Placeholder,拖影跟随指针并显式按源行圆角裁切,不留下矩形外边界;同组条目以 180ms ease-out 让位,落位淡入 140ms。放到范围外或取消恢复原位,不写入排序。遵循 prefers-reduced-motion。
56
56
 
57
- 页面选择通用 Page 的 `sidebar:"none"` 分支,自身不带常驻二级侧栏,保留原生 Home、Space、Scheduled sidebar 的悬浮叠加与导航行为。关闭页面恢复原页面位置,不改变用户的侧栏展开偏好。
57
+ 页面选择通用 Page 的 `sidebar:"none"` 分支,自身不带常驻二级侧栏,保留原生 Home、Space、Scheduled sidebar 的悬浮叠加与导航行为。关闭页面恢复原页面位置,不改变用户的侧栏展开偏好。原生二级入口(如 Space → Images)提交导航后,通过共享 Page 的 `onNavigate` 关闭 Prompts、编辑浮层并释放底层 inert;保留用户刚选择的目标路由。仅悬浮展开侧栏不退出 Prompts。
58
58
 
59
59
  提示词编辑沿用原生 Project 的复合标题输入:40px 图标段位于标题前,细分隔线,共用圆角边框与 focus-within 状态。图标选择入口不再单独占一行;Title 通过 aria-label 标识。
60
60
 
@@ -7,8 +7,8 @@
7
7
  "zh-CN": "提示词"
8
8
  },
9
9
  "description": {
10
- "en": "Organize prompts, track usage, and insert them from the composer.",
11
- "zh-CN": "分类管理提示词、统计调用次数,并从输入框快速调用。"
10
+ "en": "Organize reusable prompts and insert them with one click.",
11
+ "zh-CN": "整理常用提示词,一键填入输入框。"
12
12
  },
13
13
  "surfaceUrl": "http://127.0.0.1:47832/",
14
14
  "healthUrl": "http://127.0.0.1:47832/api/health",
@@ -365,7 +365,7 @@ function openPage() {
365
365
  pageFocus = document.activeElement;
366
366
  page = element("section");
367
367
  page.setAttribute(MARKER, "page");
368
- config.ui.page(page, { sidebar:"none" });
368
+ config.ui.page(page, { sidebar:"none", onNavigate:() => closePage(false, false) });
369
369
  page.setAttribute("aria-label", text("Prompts", "提示词"));
370
370
  document.body.append(page);
371
371
  // 只隔离主内容的焦点,保留导航栏与原生窗口控件。
@@ -4,8 +4,8 @@
4
4
  "version": "0.1.0",
5
5
  "label": { "en": "Session Status", "zh-CN": "会话状态" },
6
6
  "description": {
7
- "en": "Shows conversation steps, observed output speed, token usage and context details below the composer.",
8
- "zh-CN": "在输入框下方显示对话步骤、观测输出速度、Token 用量与上下文明细。"
7
+ "en": "See response speed, token usage and context space.",
8
+ "zh-CN": "查看输出速度、Token 用量与上下文占用。"
9
9
  },
10
10
  "entry": { "kind": "page-script", "script": "src/session-status.mjs", "sourceFactory": "src/injection-source.mjs" },
11
11
  "icon": {
@@ -5,13 +5,13 @@
5
5
  保留当前任务所在分类与 Project,收起其他分支。再次点击恢复进入聚焦前的展开状态;聚焦期间切换任务跟随路径。用户手动调整分支时退出聚焦并保留手动布局,无撤销浮层。
6
6
 
7
7
  - 入口:展开侧栏的 Activity 与 Search 之间,随原生侧栏一起隐藏。
8
- - 图标:固定使用 Lucide list-chevrons-down-up,开启和退出聚焦不切换图标。16px,24px viewBox,描边 1.575 units(实际 1.05px)。
9
- - 控件:继承原生 Activity 的 Button 类与 28px 热区。未激活为 secondary / ghost,激活为 info / soft,与原生 View activity 一致;aria-pressed 表达聚焦开关状态。
10
- - 中英文标题:Focus current task / 聚焦当前任务;Exit focus / 退出聚焦。
8
+ - 图标:整理操作使用 Lucide list-chevrons-down-up;可恢复布局时切换为 list-chevrons-up-down。16px,24px viewBox,描边 1.575 units(实际 1.05px)。
9
+ - 控件:继承原生 Activity 的 Button 类与 28px 热区。两种状态均使用 secondary / ghost,以图标表达下一步动作,不显示蓝色模式选中态;aria-pressed 保留可恢复状态的可访问语义。
10
+ - 中英文标题:Focus current task / 聚焦当前任务;Restore layout / 恢复布局。
11
11
  - 折叠通过原生回调执行;React 元数据仅用于读取稳定分类、项目和会话身份。
12
12
  - 无可识别当前路径时禁用;Activity 列表展示期间暂停整理。
13
13
  - 状态仅作用于当前窗口;卸载不额外改变用户布局。
14
14
 
15
- 根据聚焦状态显式设置 data-color 与 data-variant,直接复用原生语义 token 适配明暗模式,不写死蓝色或背景色。Activity 开启时按钮原生 disabled,暂停聚焦操作,关闭后恢复可用性。
15
+ 显式使用中性 data-color 与 data-variant,直接复用原生语义 token 适配明暗模式,不写死颜色或背景色。Activity 开启时按钮原生 disabled,暂停聚焦操作,关闭后恢复可用性。
16
16
 
17
17
  Tooltip 由共享组件库通过 `data-minecodex-tooltip` 管理;插件只提供随聚焦状态变化的文案,不维护独立材质、定位或显示事件。克隆入口时移除原控件的 `title` 与 `aria-describedby`。
@@ -7,14 +7,15 @@
7
7
  "zh-CN": "侧栏聚焦"
8
8
  },
9
9
  "description": {
10
- "en": "Keep the current task path expanded and collapse other branches.",
11
- "zh-CN": "保留当前任务路径,收起其他分支。"
10
+ "en": "Fold unrelated branches to keep the current task in view.",
11
+ "zh-CN": "收起无关分支,专注当前任务路径。"
12
12
  },
13
13
  "entry": {
14
14
  "kind": "page-script",
15
15
  "script": "src/sidebar-focus.mjs",
16
16
  "sourceFactory": "src/injection-source.mjs"
17
17
  },
18
+ "icon": {"viewBox": "0 0 24 24", "markup": "<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" />\n <path d=\"M9 3v18\" />"},
18
19
  "placement": {
19
20
  "order": 80
20
21
  }