minecodex 2.0.49 → 2.0.50

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.
@@ -18,6 +18,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
18
18
  const modelSelectorNativeToggleHandlers = new Map();
19
19
  const modelSelectorPositionObservers = new Map();
20
20
  const modelSelectorMenuSizeFrames = new Map();
21
+ const modelSelectorOpeningFrames = new Map();
21
22
  let modelSelectorReturnTimer = null;
22
23
  let modelSelectorPendingReturn = false;
23
24
  let modelSelectorEffortTransactionTimer = null;
@@ -91,6 +92,16 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
91
92
  [data-codex-model-slider-trigger][data-codex-model-slider-trigger-placeholder] [class*="_ModelPickerTriggerContent_"] {
92
93
  width: 160px; min-width: 160px !important; max-width: 160px;
93
94
  }
95
+ /* 打开时先完成占位态,避免原生 crossfade 的宽度动画带动弹层。 */
96
+ [data-codex-model-slider-trigger][data-state="open"][data-codex-model-slider-trigger-placeholder] [class*="_ModelPickerTriggerContent_"] > span:has(> span > [class*="_ModelPickerTriggerPlaceholder_"]) {
97
+ width: 160px !important;
98
+ }
99
+ [data-codex-model-slider-trigger][data-state="open"][data-codex-model-slider-trigger-placeholder] span:has(> [class*="_ModelPickerTriggerPlaceholder_"]) {
100
+ opacity: 1 !important;
101
+ }
102
+ [data-codex-model-slider-trigger][data-state="open"][data-codex-model-slider-trigger-placeholder] [class*="_ModelPickerTriggerLabel_"] {
103
+ visibility: hidden;
104
+ }
94
105
  [data-codex-model-slider-trigger] [class*="_ModelPickerTriggerEffortLabel_"] {
95
106
  display: inline-flex; align-items: center; align-self: center;
96
107
  }
@@ -126,6 +137,11 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
126
137
  [data-codex-model-slider-generic-menu] {
127
138
  overflow-x: hidden;
128
139
  transform: translateX(var(--codex-model-slider-menu-center-shift, 0px)) !important;
140
+ /* 居中修正不能继承原生 transform 过渡,否则定位本身会播放位移。 */
141
+ transition-property: opacity, filter !important;
142
+ }
143
+ html[data-codex-model-slider-catalog-ready] [role="menu"][data-state="open"]:has([data-reasoning-slider]):not([data-codex-model-slider-opening-ready]) {
144
+ visibility: hidden !important;
129
145
  }
130
146
  [data-codex-model-slider-generic] { box-sizing: border-box; width: 100%; }
131
147
  [data-codex-model-slider-fast] { border: 0; background: transparent; cursor: pointer; }
@@ -991,11 +1007,13 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
991
1007
  const original = modelSelectorGenericContainers.get(container);
992
1008
  if (!original) return;
993
1009
  original.effectMirror?.disconnect();
1010
+ original.positionObserver?.disconnect();
994
1011
  if (container.isConnected) {
995
1012
  const menu = container.parentElement;
996
1013
  menu?.querySelector(":scope > [data-codex-model-slider-generic]")?.remove();
997
1014
  menu?.removeAttribute("data-codex-model-slider-generic-menu");
998
1015
  menu?.style.removeProperty("--codex-model-slider-menu-center-shift");
1016
+ menu?.removeAttribute("data-codex-model-slider-opening-ready");
999
1017
  container.style.position = original.position;
1000
1018
  container.style.inset = original.inset;
1001
1019
  container.style.opacity = original.opacity;
@@ -1095,11 +1113,10 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1095
1113
  if (!record) return false;
1096
1114
  if (record.closing && !immediate) return true;
1097
1115
  record.reveal?.cancel();
1098
- const placeholder = document.querySelector(
1099
- '[data-codex-intelligence-trigger] [class*="_ModelPickerTriggerPlaceholder_"]',
1100
- );
1101
- if (placeholder?.firstChild && record.placeholderText != null) {
1102
- placeholder.firstChild.nodeValue = record.placeholderText;
1116
+ const placeholder = record.trigger?.querySelector('[class*="_ModelPickerTriggerPlaceholder_"]');
1117
+ const placeholderNode = placeholder && firstVisibleTextNode(placeholder);
1118
+ if (placeholderNode && record.placeholderText != null) {
1119
+ placeholderNode.nodeValue = record.placeholderText;
1103
1120
  }
1104
1121
  record.button.setAttribute("aria-expanded", "false");
1105
1122
  record.closing = true;
@@ -1279,15 +1296,15 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1279
1296
  });
1280
1297
  renderEnhancedModelMenu(menu, selector);
1281
1298
 
1282
- const placeholder = document.querySelector(
1283
- '[data-codex-intelligence-trigger] [class*="_ModelPickerTriggerPlaceholder_"]',
1284
- );
1285
- const placeholderText = placeholder?.firstChild?.nodeValue ?? null;
1299
+ const trigger = nativeComposerTrigger();
1300
+ const placeholder = trigger?.querySelector('[class*="_ModelPickerTriggerPlaceholder_"]');
1301
+ const placeholderNode = placeholder && firstVisibleTextNode(placeholder);
1302
+ const placeholderText = placeholderNode?.nodeValue ?? null;
1286
1303
  // 保留产品行为:模型列表打开时 chip 显示 Select model。只能原地更新
1287
1304
  // 同一个文本节点(nodeValue),不能 textContent 替换整个节点,否则
1288
1305
  // React 卸载菜单时 removeChild 找不到旧文本节点会触发错误边界。
1289
- if (placeholder?.firstChild) {
1290
- placeholder.firstChild.nodeValue = locale === "zh-CN" ? "选择模型" : "Select model";
1306
+ if (placeholderNode) {
1307
+ placeholderNode.nodeValue = locale === "zh-CN" ? "选择模型" : "Select model";
1291
1308
  }
1292
1309
 
1293
1310
  modelSelectorControllerMenu = {
@@ -1295,6 +1312,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1295
1312
  content,
1296
1313
  parentMenu,
1297
1314
  button: modelButton,
1315
+ trigger,
1298
1316
  placeholderText,
1299
1317
  position: parentMenu.style.position,
1300
1318
  overflow: parentMenu.style.overflow,
@@ -2000,11 +2018,42 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2000
2018
  const previousShift = Number.parseFloat(
2001
2019
  parentMenu.style.getPropertyValue("--codex-model-slider-menu-center-shift"),
2002
2020
  ) || 0;
2003
- const unshiftedCenter = menuRect.left + (menuRect.width / 2) - previousShift;
2004
- const nextShift = triggerRect.left + (triggerRect.width / 2) - unshiftedCenter;
2021
+ const zoom = parentMenu.currentCSSZoom || 1;
2022
+ const unshiftedCenter = menuRect.left + (menuRect.width / 2) - previousShift * zoom;
2023
+ const nextShift = (triggerRect.left + (triggerRect.width / 2) - unshiftedCenter) / zoom;
2005
2024
  parentMenu.style.setProperty("--codex-model-slider-menu-center-shift", `${nextShift}px`);
2006
2025
  }
2007
2026
 
2027
+ function revealCenteredModelPicker(parentMenu) {
2028
+ if (parentMenu.hasAttribute("data-codex-model-slider-opening-ready")) {
2029
+ centerGenericModelPicker(parentMenu);
2030
+ return;
2031
+ }
2032
+ parentMenu.style.visibility = "hidden";
2033
+ if (modelSelectorOpeningFrames.has(parentMenu)) return;
2034
+ let previous = null;
2035
+ const position = () => {
2036
+ modelSelectorOpeningFrames.delete(parentMenu);
2037
+ const trigger = nativeComposerTrigger();
2038
+ if (!parentMenu.isConnected || !trigger || signal.aborted) return;
2039
+ centerGenericModelPicker(parentMenu);
2040
+ const menuRect = parentMenu.getBoundingClientRect();
2041
+ const triggerRect = trigger.getBoundingClientRect();
2042
+ const geometry = [menuRect.left, menuRect.width, triggerRect.left, triggerRect.width];
2043
+ // 等原生 Popper 跟上入口宽度;首次可见的位置必须已经稳定且居中。
2044
+ if (menuRect.width > 0 && triggerRect.width > 0
2045
+ && Math.abs(menuRect.left + menuRect.width / 2 - triggerRect.left - triggerRect.width / 2) < 1
2046
+ && previous && geometry.every((value, index) => Math.abs(value - previous[index]) < 0.5)) {
2047
+ parentMenu.setAttribute("data-codex-model-slider-opening-ready", "");
2048
+ parentMenu.style.removeProperty("visibility");
2049
+ return;
2050
+ }
2051
+ previous = geometry;
2052
+ modelSelectorOpeningFrames.set(parentMenu, requestAnimationFrame(position));
2053
+ };
2054
+ modelSelectorOpeningFrames.set(parentMenu, requestAnimationFrame(position));
2055
+ }
2056
+
2008
2057
  // ========================================================================
2009
2058
  // 通用 picker 增强与恢复 — 菜单/触发器增强、布局居中与恢复降级
2010
2059
  // ========================================================================
@@ -2053,13 +2102,12 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2053
2102
  return true;
2054
2103
  }
2055
2104
  parentMenu.removeAttribute("data-codex-model-slider-template-wait");
2056
- parentMenu.style.removeProperty("visibility");
2057
2105
  const canUpgradeFallback = Boolean(
2058
2106
  existing?.querySelector("[data-codex-model-slider-fallback]") && nativeTemplateReady,
2059
2107
  );
2060
2108
  if (existing?.getAttribute("data-codex-model-slider-structure-key") === structureKey
2061
2109
  && existing.hasAttribute("data-codex-model-slider-fast-transitioning") && !canUpgradeFallback) {
2062
- centerGenericModelPicker(parentMenu);
2110
+ revealCenteredModelPicker(parentMenu);
2063
2111
  return true;
2064
2112
  }
2065
2113
  const currentFast = existing?.querySelector("[data-codex-model-slider-rail]")?.getAttribute("data-fast-mode");
@@ -2068,11 +2116,11 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2068
2116
  if (existing?.getAttribute("data-codex-model-slider-structure-key") === structureKey
2069
2117
  && currentFast != null && currentFast === nativeFast && !canUpgradeFallback) {
2070
2118
  existing.setAttribute("data-codex-model-slider-generic", key);
2071
- centerGenericModelPicker(parentMenu);
2119
+ revealCenteredModelPicker(parentMenu);
2072
2120
  return true;
2073
2121
  }
2074
2122
  if (existing?.getAttribute("data-codex-model-slider-generic") === key && !canUpgradeFallback) {
2075
- centerGenericModelPicker(parentMenu);
2123
+ revealCenteredModelPicker(parentMenu);
2076
2124
  return true;
2077
2125
  }
2078
2126
  const nativeContainer = parentMenu.firstElementChild;
@@ -2090,7 +2138,13 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2090
2138
  }
2091
2139
  modelSelectorGenericContainers.get(nativeContainer)?.effectMirror?.disconnect();
2092
2140
  if (!modelSelectorGenericContainers.has(nativeContainer)) {
2141
+ // Popper 会在后续帧改写 wrapper;同一轮微任务内重新居中,避免露出一帧旧位置。
2142
+ const positionObserver = new MutationObserver(() => centerGenericModelPicker(parentMenu));
2143
+ if (parentMenu.parentElement?.hasAttribute("data-radix-popper-content-wrapper")) {
2144
+ positionObserver.observe(parentMenu.parentElement, { attributes: true, attributeFilter: ["style"] });
2145
+ }
2093
2146
  modelSelectorGenericContainers.set(nativeContainer, {
2147
+ positionObserver,
2094
2148
  position: nativeContainer.style.position,
2095
2149
  inset: nativeContainer.style.inset,
2096
2150
  opacity: nativeContainer.style.opacity,
@@ -2110,12 +2164,14 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2110
2164
  shell.setAttribute("data-codex-model-slider-structure-key", structureKey);
2111
2165
  replaceModelSliderShell(parentMenu, existing, shell);
2112
2166
  modelSelectorGenericContainers.get(nativeContainer).effectMirror = syncClonedPowerEffects(nativeContainer, shell);
2113
- centerGenericModelPicker(parentMenu);
2167
+ revealCenteredModelPicker(parentMenu);
2114
2168
  return true;
2115
2169
  }
2116
2170
 
2117
2171
  function enhanceNativeModelPicker(parentMenu, selector) {
2118
2172
  if (enhanceGenericModelPicker(parentMenu, selector)) return;
2173
+ parentMenu.setAttribute("data-codex-model-slider-opening-ready", "");
2174
+ parentMenu.style.removeProperty("visibility");
2119
2175
  const toggle = parentMenu.querySelector("[data-model-picker-view-toggle]");
2120
2176
  if (toggle && typeof nativeComposerModelController()?.onSelectModel === "function") {
2121
2177
  // 私有 DOM 无法满足 MCX shell 契约时,保留原生菜单作为降级路径。
@@ -2171,10 +2227,20 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2171
2227
 
2172
2228
  function enhanceComposerModelTrigger(trigger, selector) {
2173
2229
  if (!modelSelectorTriggerObservers.has(trigger)) {
2174
- const observer = new MutationObserver(queueEnsure);
2175
- observer.observe(trigger, { childList: true, subtree: true, characterData: true });
2230
+ const observer = new MutationObserver(() => {
2231
+ enhanceComposerModelTrigger(trigger, selector);
2232
+ queueEnsure();
2233
+ });
2234
+ observer.observe(trigger, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ["data-state"] });
2176
2235
  modelSelectorTriggerObservers.set(trigger, observer);
2177
2236
  }
2237
+ const openPlaceholder = trigger.getAttribute("data-state") === "open"
2238
+ && trigger.querySelector('[class*="_ModelPickerTriggerPlaceholder_"]');
2239
+ if (openPlaceholder && modelSelectorControllerMenu?.trigger === trigger && !modelSelectorControllerMenu.closing) {
2240
+ const textNode = firstVisibleTextNode(openPlaceholder);
2241
+ const label = locale === "zh-CN" ? "选择模型" : "Select model";
2242
+ if (textNode && textNode.nodeValue !== label) textNode.nodeValue = label;
2243
+ }
2178
2244
  const nativeLabels = Array.from(trigger.querySelectorAll(
2179
2245
  '[class*="_ModelPickerTriggerModelText_"]:not([data-codex-model-slider-trigger-label])',
2180
2246
  ));
@@ -2182,7 +2248,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2182
2248
  trigger.setAttribute("data-codex-model-slider-trigger", "");
2183
2249
  trigger.toggleAttribute(
2184
2250
  "data-codex-model-slider-trigger-placeholder",
2185
- isComposerModelPlaceholder(trigger.innerText),
2251
+ isComposerModelPlaceholder(openPlaceholder?.textContent || trigger.innerText),
2186
2252
  );
2187
2253
  restoreTriggerLabelWithoutNative(trigger, selector);
2188
2254
  return;
@@ -2199,7 +2265,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2199
2265
  trigger.setAttribute("data-codex-model-slider-trigger-raw", raw);
2200
2266
  // 区分占位态(Select model / Select effort)与真实模型态:占位态沿用
2201
2267
  // 原生宽度契约,真实模型态让芯片 HUG 完整名称,避免第三方长名被截断。
2202
- const placeholder = isComposerModelPlaceholder(label);
2268
+ const placeholder = isComposerModelPlaceholder(openPlaceholder?.textContent || label);
2203
2269
  trigger.toggleAttribute("data-codex-model-slider-trigger-placeholder", placeholder);
2204
2270
  for (const nativeLabel of nativeLabels) {
2205
2271
  let displayLabel = nativeLabel.nextElementSibling;
@@ -2216,12 +2282,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2216
2282
  }
2217
2283
  if (displayLabel.textContent !== label) displayLabel.textContent = label;
2218
2284
  }
2219
- // 原生开合的临时 min-width 钉定与弹层水平居中都基于对 Measurement
2220
- // 探针的测量;把探针文本同步为 MCX 显示标签,原生机制即可测得
2221
- // 真实显示宽度。保留 React 持有的子节点,避免切模型时卸载失败。
2222
- const measurement = trigger.querySelector('[class*="_ModelPickerTriggerMeasurement_"]');
2223
- const measurementText = measurement && firstVisibleTextNode(measurement);
2224
- if (measurementText && measurementText.nodeValue !== label) measurementText.nodeValue = label;
2285
+ // Measurement 属于原生 openLabel,占位宽度不能用真实模型名覆盖。
2225
2286
  }
2226
2287
 
2227
2288
  function ensureModelSelector() {
@@ -2284,6 +2345,11 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2284
2345
  modelSelectorPositionObservers.clear();
2285
2346
  for (const frame of modelSelectorMenuSizeFrames.values()) cancelAnimationFrame(frame);
2286
2347
  modelSelectorMenuSizeFrames.clear();
2348
+ for (const [menu, frame] of modelSelectorOpeningFrames) {
2349
+ cancelAnimationFrame(frame);
2350
+ menu.style.removeProperty("visibility");
2351
+ }
2352
+ modelSelectorOpeningFrames.clear();
2287
2353
  modelSelectorReturnTimer = null;
2288
2354
  modelSelectorEffortTransactionTimer = null;
2289
2355
  modelSelectorPendingOutsideDismiss = false;
@@ -2318,6 +2384,8 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2318
2384
  document.querySelectorAll("[data-codex-model-slider-generic]").forEach((element) => element.remove());
2319
2385
  document.querySelectorAll("[data-codex-model-slider-generic-menu]")
2320
2386
  .forEach((element) => element.removeAttribute("data-codex-model-slider-generic-menu"));
2387
+ document.querySelectorAll("[data-codex-model-slider-opening-ready]")
2388
+ .forEach((element) => element.removeAttribute("data-codex-model-slider-opening-ready"));
2321
2389
  document.querySelectorAll("[data-codex-model-slider-menu]")
2322
2390
  .forEach((element) => element.removeAttribute("data-codex-model-slider-menu"));
2323
2391
  document.querySelectorAll("[data-codex-model-slider-trigger]").forEach((element) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecodex",
3
- "version": "2.0.49",
3
+ "version": "2.0.50",
4
4
  "description": "Lightweight, local-first plugins for the Codex desktop app.",
5
5
  "license": "MIT",
6
6
  "type": "module",