@proteus-vue/render-backend 0.3.0-beta.21 → 0.3.0-beta.26

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/dist/index.js CHANGED
@@ -111,6 +111,8 @@ var SEMANTIC_HEADLESS_MAP = {
111
111
  "shell.segment": "segment",
112
112
  "shell.popover": "popover",
113
113
  "shell.action-sheet": "action-sheet",
114
+ "shell.toast": "toast",
115
+ "ui.loading": "loading",
114
116
  "ui.rich-text": "rich-text",
115
117
  "ui.avatar": "avatar",
116
118
  "ui.media": "media",
@@ -293,6 +295,9 @@ var SEMANTIC_WEB_MAP = {
293
295
  "shell.segment": { tag: "div", className: "proteus-segment" },
294
296
  "shell.popover": { tag: "div", className: "proteus-popover" },
295
297
  "shell.action-sheet": { tag: "div", className: "proteus-action-sheet" },
298
+ // ★2026-09-30 补缺(U19/S8 上批加了 SEMANTIC_BACKEND_MAP 但后端控件表漏补——M3 readback 门禁抓出)
299
+ "shell.toast": { tag: "div", className: "proteus-toast" },
300
+ "ui.loading": { tag: "div", className: "proteus-loading" },
296
301
  "ui.rich-text": { tag: "div", className: "proteus-rich-text" },
297
302
  "ui.avatar": { tag: "div", className: "proteus-avatar" },
298
303
  "ui.media": { tag: "div", className: "proteus-media" },
@@ -406,6 +411,9 @@ function createVueDomBackend(doc) {
406
411
  }
407
412
 
408
413
  // src/native.ts
414
+ function isBatchAdapter(a) {
415
+ return typeof a.commit === "function";
416
+ }
409
417
  function createMockNativeAdapter() {
410
418
  const ops = [];
411
419
  return {
@@ -484,6 +492,9 @@ var SEMANTIC_NATIVE_MAPS = {
484
492
  "shell.segment": "UISegmentedControl",
485
493
  "shell.popover": "UIPopoverController",
486
494
  "shell.action-sheet": "UIAlertController.actionSheet",
495
+ // ★2026-09-30 补缺(同 vue-dom 注释)
496
+ "shell.toast": "UIView.toast",
497
+ "ui.loading": "UIActivityIndicatorView",
487
498
  "ui.rich-text": "UITextView.attributed",
488
499
  "ui.avatar": "UIImageView.avatar",
489
500
  "ui.media": "AVPlayerView",
@@ -554,6 +565,9 @@ var SEMANTIC_NATIVE_MAPS = {
554
565
  "shell.segment": "TabLayout",
555
566
  "shell.popover": "PopupWindow",
556
567
  "shell.action-sheet": "BottomSheet",
568
+ // ★2026-09-30 补缺(同 vue-dom 注释)
569
+ "shell.toast": "Toast",
570
+ "ui.loading": "ProgressBar",
557
571
  "ui.rich-text": "TextView.html",
558
572
  "ui.avatar": "ImageView.avatar",
559
573
  "ui.media": "VideoView",
@@ -624,6 +638,8 @@ var SEMANTIC_NATIVE_MAPS = {
624
638
  "shell.segment": "Segmented",
625
639
  "shell.popover": "Popup",
626
640
  "shell.action-sheet": "ActionSheet",
641
+ "shell.toast": "promptAction.showToast",
642
+ "ui.loading": "LoadingProgress",
627
643
  "ui.rich-text": "RichText",
628
644
  "ui.avatar": "Image.avatar",
629
645
  "ui.media": "Video",
@@ -655,11 +671,23 @@ var SEMANTIC_NATIVE_MAPS = {
655
671
  }
656
672
  };
657
673
  function createNativeBackend(adapter, platform = "ios") {
658
- const viewAdapter = adapter ?? createMockNativeAdapter();
674
+ const rawAdapter = adapter ?? createMockNativeAdapter();
675
+ const viewAdapter = rawAdapter;
676
+ const batchAdapter = isBatchAdapter(rawAdapter) ? rawAdapter : null;
659
677
  const semanticMap = SEMANTIC_NATIVE_MAPS[platform];
660
678
  const id = platform === "ios" ? "native-ios" : platform === "android" ? "native-android" : "native-harmony";
661
679
  let nextId = 1;
662
680
  const nodes = /* @__PURE__ */ new Map();
681
+ const pending = [];
682
+ let hostCalls = 0;
683
+ const queueOrCall = (op, imperative) => {
684
+ if (batchAdapter) {
685
+ pending.push(op);
686
+ return;
687
+ }
688
+ hostCalls++;
689
+ imperative();
690
+ };
663
691
  function ensureNode(handle) {
664
692
  const n = handle;
665
693
  if (!n || typeof n.id !== "number") throw new Error("NativeBackend: \u975E\u6CD5\u53E5\u67C4");
@@ -680,7 +708,10 @@ function createNativeBackend(adapter, platform = "ios") {
680
708
  text: "",
681
709
  handle: null
682
710
  };
683
- descriptor.handle = viewAdapter.createView(descriptor);
711
+ queueOrCall({ op: "create", id: descriptor.id, type: viewType, props: descriptor.props }, () => {
712
+ descriptor.handle = viewAdapter.createView(descriptor);
713
+ });
714
+ if (!batchAdapter) descriptor.handle = descriptor.handle ?? descriptor;
684
715
  nodes.set(descriptor.id, descriptor);
685
716
  return descriptor;
686
717
  },
@@ -697,6 +728,9 @@ function createNativeBackend(adapter, platform = "ios") {
697
728
  handle: null
698
729
  };
699
730
  descriptor.handle = descriptor;
731
+ queueOrCall({ op: "create", id: descriptor.id, type: "text", props: {} }, () => {
732
+ });
733
+ if (batchAdapter) pending.push({ op: "text", id: descriptor.id, text });
700
734
  return descriptor;
701
735
  },
702
736
  insert(child, parent, anchor) {
@@ -714,7 +748,9 @@ function createNativeBackend(adapter, platform = "ios") {
714
748
  p.children.push(c);
715
749
  }
716
750
  c.parent = p;
717
- viewAdapter.insertView(c.handle, p.handle, anchor ? ensureNode(anchor).handle : void 0);
751
+ queueOrCall({ op: "insert", id: c.id, parentId: p.id, anchorId: anchor ? ensureNode(anchor).id : void 0 }, () => {
752
+ viewAdapter.insertView(c.handle, p.handle, anchor ? ensureNode(anchor).handle : void 0);
753
+ });
718
754
  },
719
755
  remove(child) {
720
756
  const c = ensureNode(child);
@@ -724,7 +760,7 @@ function createNativeBackend(adapter, platform = "ios") {
724
760
  c.parent = null;
725
761
  }
726
762
  nodes.delete(c.id);
727
- viewAdapter.removeView(c.handle);
763
+ queueOrCall({ op: "remove", id: c.id }, () => viewAdapter.removeView(c.handle));
728
764
  },
729
765
  patchProp(el, key, prev, next) {
730
766
  const n = ensureNode(el);
@@ -733,15 +769,159 @@ function createNativeBackend(adapter, platform = "ios") {
733
769
  } else {
734
770
  n.props[key] = next;
735
771
  }
736
- viewAdapter.updateView(n.handle, key, prev, next);
772
+ queueOrCall({ op: "patch", id: n.id, key, prev, next }, () => viewAdapter.updateView(n.handle, key, prev, next));
737
773
  },
738
774
  setText(el, text) {
739
775
  const n = ensureNode(el);
740
776
  n.text = text;
741
- viewAdapter.setViewText(n.handle, text);
777
+ queueOrCall({ op: "text", id: n.id, text }, () => viewAdapter.setViewText(n.handle, text));
742
778
  },
743
779
  measure() {
744
780
  return { width: 0, height: 0 };
781
+ },
782
+ // ★★C1 批量模式的两个接口(逐节点模式下:flush 为空操作、hostCalls 反映真实调用数)
783
+ /** 提交本帧累积的操作(**一次跨边界调用**——批处理红线的落点) */
784
+ flush() {
785
+ if (!batchAdapter) return;
786
+ if (pending.length === 0) return;
787
+ hostCalls++;
788
+ const batch = pending.splice(0, pending.length);
789
+ batchAdapter.commit(batch);
790
+ },
791
+ /** 已发生的宿主调用数(判据:批量模式下应 == flush 次数,不随节点数增长) */
792
+ hostCalls() {
793
+ return hostCalls;
794
+ }
795
+ };
796
+ }
797
+
798
+ // src/selfdraw-batch.ts
799
+ function createSelfDrawBatchAdapter(host, opts) {
800
+ const mirror = /* @__PURE__ */ new Map();
801
+ const rootOrder = [];
802
+ let calls = 0;
803
+ let lastKind = null;
804
+ let mounted = false;
805
+ const normalize = opts.normalizeProps ?? ((p) => p);
806
+ function removeSubtree(id) {
807
+ const n = mirror.get(id);
808
+ if (!n) return;
809
+ for (const c of n.children) removeSubtree(c);
810
+ mirror.delete(id);
811
+ const at = rootOrder.indexOf(id);
812
+ if (at >= 0) rootOrder.splice(at, 1);
813
+ }
814
+ function specs() {
815
+ const out = [];
816
+ const visit = (id) => {
817
+ const n = mirror.get(id);
818
+ if (!n) return;
819
+ const spec = { id: n.id, parentId: n.parentId, ...normalize(n.props) };
820
+ if (n.text !== void 0) spec.text = n.text;
821
+ out.push(spec);
822
+ for (const c of n.children) visit(c);
823
+ };
824
+ for (const r of rootOrder) visit(r);
825
+ return out;
826
+ }
827
+ return {
828
+ commit(ops) {
829
+ let structural = false;
830
+ const patched = /* @__PURE__ */ new Map();
831
+ const patch = (id, key, value) => {
832
+ const s = patched.get(id) ?? {};
833
+ if (value === null || value === void 0) delete s[key];
834
+ else s[key] = value;
835
+ patched.set(id, s);
836
+ };
837
+ for (const op of ops) {
838
+ switch (op.op) {
839
+ case "create": {
840
+ mirror.set(op.id, {
841
+ id: op.id,
842
+ type: op.type,
843
+ props: { ...op.props },
844
+ parentId: null,
845
+ children: []
846
+ });
847
+ rootOrder.push(op.id);
848
+ structural = true;
849
+ break;
850
+ }
851
+ case "insert": {
852
+ const n = mirror.get(op.id);
853
+ if (!n) break;
854
+ if (n.parentId !== null) {
855
+ const old = mirror.get(n.parentId);
856
+ if (old) {
857
+ const i = old.children.indexOf(n.id);
858
+ if (i >= 0) old.children.splice(i, 1);
859
+ }
860
+ } else {
861
+ const i = rootOrder.indexOf(n.id);
862
+ if (i >= 0) rootOrder.splice(i, 1);
863
+ }
864
+ n.parentId = op.parentId;
865
+ if (op.parentId === null) {
866
+ rootOrder.push(n.id);
867
+ } else {
868
+ const p = mirror.get(op.parentId);
869
+ if (p) {
870
+ const at = op.anchorId === void 0 ? -1 : p.children.indexOf(op.anchorId);
871
+ if (at >= 0) p.children.splice(at, 0, n.id);
872
+ else p.children.push(n.id);
873
+ }
874
+ }
875
+ structural = true;
876
+ break;
877
+ }
878
+ case "remove": {
879
+ removeSubtree(op.id);
880
+ structural = true;
881
+ break;
882
+ }
883
+ case "patch": {
884
+ const n = mirror.get(op.id);
885
+ if (!n) break;
886
+ if (op.next === null || op.next === void 0) delete n.props[op.key];
887
+ else n.props[op.key] = op.next;
888
+ patch(op.id, op.key, op.next);
889
+ break;
890
+ }
891
+ case "text": {
892
+ const n = mirror.get(op.id);
893
+ if (!n) break;
894
+ n.text = op.text;
895
+ patch(op.id, "text", op.text);
896
+ break;
897
+ }
898
+ }
899
+ }
900
+ if (!mounted) {
901
+ host.mount(JSON.stringify({ viewport: opts.viewport, nodes: specs() }));
902
+ mounted = true;
903
+ lastKind = "mount";
904
+ } else if (structural) {
905
+ host.update(JSON.stringify({ viewport: opts.viewport, nodes: specs() }));
906
+ lastKind = "update";
907
+ } else {
908
+ const arr = [...patched.entries()].map(([id, style]) => ({ id, style }));
909
+ host.updatePatches(JSON.stringify(arr));
910
+ lastKind = "updatePatches";
911
+ }
912
+ calls++;
913
+ },
914
+ hostCalls() {
915
+ return calls;
916
+ },
917
+ lastCallKind() {
918
+ return lastKind;
919
+ },
920
+ nodeSpecs() {
921
+ return specs();
922
+ },
923
+ commitCount() {
924
+ return calls;
745
925
  }
746
926
  };
747
927
  }
@@ -805,6 +985,8 @@ var SEMANTIC_FLUTTER_MAP = {
805
985
  "shell.segment": "SegmentedButton",
806
986
  "shell.popover": "showMenu",
807
987
  "shell.action-sheet": "showModalBottomSheet",
988
+ "shell.toast": "ScaffoldMessenger.showSnackBar",
989
+ "ui.loading": "CircularProgressIndicator",
808
990
  "ui.rich-text": "RichText",
809
991
  "ui.avatar": "CircleAvatar",
810
992
  "ui.media": "VideoPlayer",
@@ -1185,8 +1367,17 @@ var TAG_SEMANTIC_MAP = {
1185
1367
  "p-segment": "shell.segment",
1186
1368
  "p-drawer": "shell.drawer",
1187
1369
  "p-modal": "shell.modal",
1370
+ // ★★★GP4-c(2026-10-03):登录失效拦截弹窗——与 p-modal **同语义**(模态弹窗)、
1371
+ // **不同可取消性**(本组件不可取消:无关闭按钮/点遮罩不关;唯一出口是登录态恢复)。
1372
+ // ★不登记 ⇒ 编译期按"未注册自定义组件"输出 ⇒ MP 端不渲染(M3 门禁会当场红)
1373
+ "p-auth-gate": "shell.modal",
1188
1374
  "p-popover": "shell.popover",
1189
1375
  "p-toast": "shell.toast",
1376
+ // ★★★GP4-a/b(2026-10-03):两个**浮层宿主**同属其能力的语义(multi-tag 别名——同 ui.loading 的先例)。
1377
+ // · p-toast-host:Toast 队列的渲染端(构建期按需注入)
1378
+ // · p-loading-host:Loading 多实例的渲染端(同上)
1379
+ // ★不登记 ⇒ 编译期按"未注册自定义组件"输出 ⇒ **MP 端不渲染**(M3 门禁当场抓出)
1380
+ "p-toast-host": "shell.toast",
1190
1381
  "p-action-sheet": "shell.action-sheet",
1191
1382
  "p-split": "layout.split",
1192
1383
  // ★已落地绑定(G-32 S10 分栏语义——layout.split 承载)
@@ -1227,6 +1418,13 @@ var TAG_SEMANTIC_MAP = {
1227
1418
  "p-formfactor": "layout.formfactor",
1228
1419
  // ★★Fluid System v2:柔性形态容器
1229
1420
  "p-loading": "ui.loading",
1421
+ // ★★★GP4-b(2026-10-03):Loading **多实例宿主**与**区域遮罩**同属 ui.loading 语义(multi-tag 别名,
1422
+ // 与上一行 `router-link`/`p-view` 的"同一语义多标签"同款)。
1423
+ // · p-loading-host:命令式多实例的渲染端(由构建期按需注入——用户一般不手写)
1424
+ // · p-loading-region:区域遮罩(就地包裹,用户手写)
1425
+ // ★不登记 ⇒ 编译期按"未注册自定义组件"输出 ⇒ MP 端不渲染(M3 门禁当场抓出——它是对的)
1426
+ "p-loading-host": "ui.loading",
1427
+ "p-loading-region": "ui.loading",
1230
1428
  "p-scale": "ui.scale",
1231
1429
  "p-skeleton": "ui.skeleton",
1232
1430
  "p-mask": "shell.mask",
@@ -1303,7 +1501,9 @@ var UI = [
1303
1501
  { id: "U17", kind: "ui", semantic: "ui.picker", tag: "p-picker", props: ["mode", "start", "end", "indicatorStyle", "indicatorClass", "maskClass", "maskStyle", "immediateChange"], mpEquiv: "<picker>", tier: "L1", status: "implemented" },
1304
1502
  { id: "U18", kind: "ui", semantic: "ui.form", tag: "p-form", props: ["model", "rules", "layout"], mpEquiv: "\u7EC4\u5408", tier: "L1", status: "implemented" },
1305
1503
  // ★#405 语义登记批:反馈/状态类组件
1306
- { id: "U19", kind: "ui", semantic: "ui.loading", tag: "p-loading", props: ["size", "text"], mpEquiv: "wx.showLoading \u90E8\u5206", tier: "L2", status: "planned" },
1504
+ // ★2026-09-30 反馈组件补齐:组件本体(p-loading)与 MP 产物测试早已存在,
1505
+ // 缺的只是 SEMANTIC_BACKEND_MAP 的后端映射登记 ⇒ 补齐后据实转 implemented(**修记账非修能力**)
1506
+ { id: "U19", kind: "ui", semantic: "ui.loading", tag: "p-loading", props: ["size", "text"], mpEquiv: "wx.showLoading \u90E8\u5206", tier: "L1", status: "implemented" },
1307
1507
  { id: "U20", kind: "ui", semantic: "ui.scale", tag: "p-scale", props: ["level", "density", "baseSize"], mpEquiv: "\u65E0\uFF08\u65E0\u969C\u788D\u6863\u4F4D\uFF09", tier: "L2", status: "planned" },
1308
1508
  { id: "U21", kind: "ui", semantic: "ui.skeleton", tag: "p-skeleton", props: ["rows", "avatar", "animated"], mpEquiv: "\u65E0", tier: "L2", status: "planned" },
1309
1509
  // ★能力颗粒度对齐 C2:进度条 / 表单标签
@@ -1331,7 +1531,8 @@ var SHELL = [
1331
1531
  { id: "S5", kind: "shell", semantic: "shell.drawer", tag: "p-drawer", props: ["side", "width", "overlay"], mpEquiv: "\u7EC4\u5408", tier: "L1", status: "implemented" },
1332
1532
  { id: "S6", kind: "shell", semantic: "shell.modal", tag: "p-modal", props: ["open", "dismissible", "sheet", "dialog", "alert"], mpEquiv: "<modal>+wx.showModal", tier: "L1", status: "implemented" },
1333
1533
  { id: "S7", kind: "shell", semantic: "shell.popover", tag: "p-popover", props: ["trigger", "placement"], mpEquiv: "\u7EC4\u5408", tier: "L1", status: "implemented" },
1334
- { id: "S8", kind: "shell", semantic: "shell.toast", tag: "p-toast", props: ["message", "duration", "type"], mpEquiv: "wx.showToast", tier: "L1", status: "planned" },
1534
+ // ★2026-09-30 同上(p-toast 已有 MP 产物测试 + 7 端映射 ⇒ 据实转 implemented)
1535
+ { id: "S8", kind: "shell", semantic: "shell.toast", tag: "p-toast", props: ["message", "duration", "type"], mpEquiv: "wx.showToast", tier: "L1", status: "implemented" },
1335
1536
  { id: "S9", kind: "shell", semantic: "shell.action-sheet", tag: "p-action-sheet", props: ["actions", "cancel"], mpEquiv: "wx.showActionSheet", tier: "L1", status: "implemented" },
1336
1537
  { id: "S10", kind: "shell", semantic: "layout.split", tag: "p-split", props: ["breakpoint", "ratio", "collapse"], mpEquiv: "\u65E0\uFF08\u5206\u680F\u5E03\u5C40\uFF09", tier: "L1", status: "implemented" },
1337
1538
  // ★已落地绑定:分栏语义由 layout.split 承载(G-32 文档为 shell.split——机器事实以实现为准)
@@ -1392,9 +1593,13 @@ var CAPABILITY = [
1392
1593
  cap("C20", "calendar", "useCalendar()", "wx.addPhoneCalendar", "CalendarAPI"),
1393
1594
  cap("C21", "phone-call", "usePhoneCall()", "wx.makePhoneCall", "Result<void>"),
1394
1595
  cap("C22", "sms", "useSMS()", "wx.??\uFF08\u53D7\u9650\uFF09", "Result<void>"),
1395
- cap("C23", "app-lifecycle", "useAppLifecycle()", "App.onLaunch/onShow", "LifecycleHooks"),
1396
- cap("C24", "page-lifecycle", "usePageLifecycle()", "Page.onLoad/onShow", "LifecycleHooks"),
1397
- cap("C25", "background", "useBackground()", "wx.onBackground", "BackgroundAPI"),
1596
+ // ★★2026-09-30:C23/C24/C25 App 宿主腿落地(此前仅 wx/web 两桥 ⇒ App 端形同虚设)——
1597
+ // App 桥 `packages/api/src/capability-app.ts`(总线 + 三能力 + 虚拟栈页面事件翻译器);
1598
+ // 真机判据 `check:host-runtime` ①~⑤(冷启动补 launch / 真实栈驱动页面生命周期 /
1599
+ // 壳真实事件逐条驱动总线 / 启动参数经 job 泵 / 阶段快照)。
1600
+ { id: "C23", kind: "capability", semantic: "capability.app-lifecycle", api: "useAppLifecycle()", props: ["LifecycleHooks"], mpEquiv: "App.onLaunch/onShow", tier: "L1", status: "implemented" },
1601
+ { id: "C24", kind: "capability", semantic: "capability.page-lifecycle", api: "usePageLifecycle()", props: ["LifecycleHooks"], mpEquiv: "Page.onLoad/onShow", tier: "L1", status: "implemented" },
1602
+ { id: "C25", kind: "capability", semantic: "capability.background", api: "useBackground()", props: ["BackgroundAPI"], mpEquiv: "wx.onBackground", tier: "L1", status: "implemented" },
1398
1603
  // 7.3 通信/数据(10)
1399
1604
  cap("C26", "fetch", "useFetch()", "wx.request", "Promise<T>"),
1400
1605
  cap("C27", "websocket", "useWebSocket()", "wx.connectSocket", "WSConnection"),
@@ -1420,15 +1625,15 @@ var CAPABILITY = [
1420
1625
  cap("C45", "shortcut", "useShortcut()", "wx.addToDesktop", "Result<void>"),
1421
1626
  cap("C46", "in-app-purchase", "useInAppPurchase()", "wx.requestPayment \u6269\u5C55", "Result<Receipt>"),
1422
1627
  // 7.5 平台特有(4——C47-C50,此前 7.4 末 C46,总 50)
1423
- { id: "C47", kind: "capability", semantic: "capability.mini-program", api: "useMiniProgram()", props: ["MPContext"], mpEquiv: "wx.navigateToMiniProgram", tier: "L1", status: "planned" },
1424
- { id: "C48", kind: "capability", semantic: "capability.embedded", api: "useEmbedded()", props: ["HostContext"], mpEquiv: "\u65E0\uFF08\u88AB\u5BBF\u4E3B\u5D4C\u5165\uFF09", tier: "L1", status: "planned" },
1628
+ { id: "C47", kind: "capability", semantic: "capability.mini-program", api: "useMiniProgram()", props: ["MPContext"], mpEquiv: "wx.navigateToMiniProgram", tier: "L1", status: "implemented" },
1629
+ { id: "C48", kind: "capability", semantic: "capability.embedded", api: "useEmbedded()", props: ["HostContext"], mpEquiv: "\u65E0\uFF08\u88AB\u5BBF\u4E3B\u5D4C\u5165\uFF09", tier: "L1", status: "implemented" },
1425
1630
  { id: "C49", kind: "capability", semantic: "capability.live", api: "useLive()", props: ["LiveRoom"], mpEquiv: "wx...\uFF08\u76F4\u64AD\u7EC4\u4EF6\uFF09", tier: "L1", status: "planned" },
1426
- { id: "C50", kind: "capability", semantic: "capability.extension", api: "useExtension()", props: ["ExtensionAPI"], mpEquiv: "\u65E0\uFF08\u63D2\u4EF6/\u6269\u5C55\u70B9 G-21\uFF09", tier: "L1", status: "planned" },
1631
+ { id: "C50", kind: "capability", semantic: "capability.extension", api: "useExtension()", props: ["ExtensionAPI"], mpEquiv: "\u65E0\uFF08\u63D2\u4EF6/\u6269\u5C55\u70B9 G-21\uFF09", tier: "L1", status: "implemented" },
1427
1632
  // ★颗粒度对齐 C(2026-09-11):C51 小程序热更新(wx.getUpdateManager)
1428
- { id: "C51", kind: "capability", semantic: "capability.update", api: "useUpdate()", props: ["UpdateManagerAPI"], mpEquiv: "wx.getUpdateManager", tier: "L1", status: "planned" },
1633
+ { id: "C51", kind: "capability", semantic: "capability.update", api: "useUpdate()", props: ["UpdateManagerAPI"], mpEquiv: "wx.getUpdateManager", tier: "L1", status: "implemented" },
1429
1634
  // ★颗粒度对齐 C3(2026-09-11):相册 / Worker(对齐小程序媒体与多线程 API——纯 Hook,无 C-IR 节点 → planned)
1430
1635
  { id: "C52", kind: "capability", semantic: "capability.album", api: "useAlbum()", props: ["AlbumAPI"], mpEquiv: "wx.chooseMedia/saveImageToPhotosAlbum/previewMedia", tier: "L1", status: "planned" },
1431
- { id: "C53", kind: "capability", semantic: "capability.worker", api: "useWorker()", props: ["WorkerHandle"], mpEquiv: "wx.createWorker", tier: "L1", status: "planned" },
1636
+ { id: "C53", kind: "capability", semantic: "capability.worker", api: "useWorker()", props: ["WorkerHandle"], mpEquiv: "wx.createWorker", tier: "L1", status: "implemented" },
1432
1637
  // ★颗粒度对齐 C3 批 2:收货地址 / WiFi / 微信运动(对齐小程序系统 API——纯 Hook → planned)
1433
1638
  { id: "C54", kind: "capability", semantic: "capability.address", api: "useAddress()", props: ["ShippingAddress"], mpEquiv: "wx.chooseAddress", tier: "L1", status: "planned" },
1434
1639
  { id: "C55", kind: "capability", semantic: "capability.wifi", api: "useWifi()", props: ["WifiAPI"], mpEquiv: "wx.getConnectedWifi/getWifiList/connectWifi", tier: "L1", status: "planned" },
@@ -1448,7 +1653,7 @@ var CAPABILITY = [
1448
1653
  { id: "C65", kind: "capability", semantic: "capability.privacy", api: "usePrivacy()", props: ["PrivacyAPI"], mpEquiv: "wx.getPrivacySetting/openPrivacyContract/requirePrivacyAuthorize", tier: "L1", status: "planned" },
1449
1654
  // ★权威标尺缺口补齐批 D(2026-09-12):性能 / 预加载 / 图像编辑(通用能力,无资质门槛)
1450
1655
  { id: "C66", kind: "capability", semantic: "capability.performance", api: "usePerformance()", props: ["PerformanceAPI"], mpEquiv: "wx.getPerformance/reportPerformance", tier: "L1", status: "planned" },
1451
- { id: "C67", kind: "capability", semantic: "capability.preload", api: "usePreload()", props: ["PreloadAPI"], mpEquiv: "wx.preloadAssets/preloadSkylineView/preloadWebview/preDownloadSubpackage", tier: "L1", status: "planned" },
1656
+ { id: "C67", kind: "capability", semantic: "capability.preload", api: "usePreload()", props: ["PreloadAPI"], mpEquiv: "wx.preloadAssets/preloadSkylineView/preloadWebview/preDownloadSubpackage", tier: "L1", status: "implemented" },
1452
1657
  { id: "C68", kind: "capability", semantic: "capability.image-edit", api: "useImageEdit()", props: ["ImageEditAPI"], mpEquiv: "wx.cropImage/editImage", tier: "L1", status: "planned" },
1453
1658
  // ★权威标尺缺口补齐批 E(2026-09-12):网络底层 / 媒体高级
1454
1659
  { id: "C69", kind: "capability", semantic: "capability.socket", api: "useSocket()", props: ["UDPSocketHandle / TCPSocketHandle"], mpEquiv: "wx.createUDPSocket/createTCPSocket", tier: "L1", status: "planned" },
@@ -1456,16 +1661,37 @@ var CAPABILITY = [
1456
1661
  // ★权威标尺缺口补齐批 F(2026-09-12):录屏/缓存/空闲/窗口/导航拦截
1457
1662
  { id: "C71", kind: "capability", semantic: "capability.screen-capture", api: "useScreenCapture()", props: ["ScreenCaptureAPI"], mpEquiv: "wx.getScreenRecordingState/onScreenRecordingStateChanged/onUserCaptureScreen/checkIsPictureInPictureActive", tier: "L1", status: "planned" },
1458
1663
  { id: "C72", kind: "capability", semantic: "capability.cache-manager", api: "useCacheManager()", props: ["CacheManagerHandle"], mpEquiv: "wx.createCacheManager", tier: "L1", status: "planned" },
1459
- { id: "C73", kind: "capability", semantic: "capability.idle", api: "useIdle()", props: ["IdleAPI"], mpEquiv: "wx.requestIdleCallback/cancelIdleCallback", tier: "L1", status: "planned" },
1460
- { id: "C74", kind: "capability", semantic: "capability.window", api: "useWindow()", props: ["WindowAPI"], mpEquiv: "wx.setWindowSize", tier: "L1", status: "planned" },
1461
- { id: "C75", kind: "capability", semantic: "capability.navigation-guard", api: "useNavigationGuard()", props: ["NavigationGuardAPI"], mpEquiv: "wx.enableAlertBeforeUnload/disableAlertBeforeUnload", tier: "L1", status: "planned" },
1664
+ { id: "C73", kind: "capability", semantic: "capability.idle", api: "useIdle()", props: ["IdleAPI"], mpEquiv: "wx.requestIdleCallback/cancelIdleCallback", tier: "L1", status: "implemented" },
1665
+ { id: "C74", kind: "capability", semantic: "capability.window", api: "useWindow()", props: ["WindowAPI"], mpEquiv: "wx.setWindowSize", tier: "L1", status: "implemented" },
1666
+ { id: "C75", kind: "capability", semantic: "capability.navigation-guard", api: "useNavigationGuard()", props: ["NavigationGuardAPI"], mpEquiv: "wx.enableAlertBeforeUnload/disableAlertBeforeUnload", tier: "L1", status: "implemented" },
1462
1667
  // ★权威标尺缺口补齐批 G(2026-09-12):AR/XR / iBeacon / 局域网 / 翻译 / 海报 / 设备探测
1463
1668
  { id: "C76", kind: "capability", semantic: "capability.ar", api: "useAR()", props: ["ARAPI"], mpEquiv: "wx.createVKSession/isVKSupport", tier: "L1", status: "planned" },
1464
1669
  { id: "C77", kind: "capability", semantic: "capability.beacon", api: "useBeacon()", props: ["BeaconAPI"], mpEquiv: "wx.onBeaconServiceChange/onBeaconUpdate", tier: "L1", status: "planned" },
1465
1670
  { id: "C78", kind: "capability", semantic: "capability.local-service", api: "useLocalService()", props: ["LocalServiceAPI"], mpEquiv: "wx.onLocalServiceFound/Lost/ResolveFail/DiscoveryStop", tier: "L1", status: "planned" },
1466
1671
  { id: "C79", kind: "capability", semantic: "capability.translation", api: "useTranslation()", props: ["TranslationAPI"], mpEquiv: "wx.onUserTriggerTranslation/onUserOffTranslation", tier: "L1", status: "planned" },
1467
1672
  { id: "C80", kind: "capability", semantic: "capability.poster", api: "usePoster()", props: ["PosterAPI"], mpEquiv: "wx.onGeneratePoster", tier: "L1", status: "planned" },
1468
- { id: "C81", kind: "capability", semantic: "capability.device-capability", api: "useDeviceCapability()", props: ["DeviceCapabilityAPI"], mpEquiv: "wx.checkDeviceSupportHevc", tier: "L1", status: "planned" }
1673
+ { id: "C81", kind: "capability", semantic: "capability.device-capability", api: "useDeviceCapability()", props: ["DeviceCapabilityAPI"], mpEquiv: "wx.checkDeviceSupportHevc", tier: "L1", status: "planned" },
1674
+ // ★C82(2026-09-29):WebAssembly 跨平台实现——**超官方清单**能力(官方 301 API 里无 wasm)。
1675
+ // 三端形态有实质差异(双源取证:微信官方文档 /framework/performance/wasm.html +
1676
+ // miniprogram-api-typings/lib.wx.wasm.d.ts):
1677
+ // MP `WXWebAssembly.instantiate(path)` 只收**代码包路径**(.wasm/.wasm.br)· 无 compile/validate ·
1678
+ // v2.13.0+ 全局 / v2.15.0+ Worker;Web 与 App-iOS(JSC) 走标准 `WebAssembly`(收字节 · 有 compile/validate);
1679
+ // App-Android 由**宿主 wasm3** 执行(QuickJS 内建无 WASM——双端真机验证 add(2,40)=42)。
1680
+ // ⇒ 归一入口 `instantiate({bytes}|{path})` + 能力位 `supportsStreaming`/`supportsPathLoad`。
1681
+ { id: "C82", kind: "capability", semantic: "capability.webassembly", api: "useWebAssembly()", props: ["WebAssemblyAPI"], mpEquiv: "WXWebAssembly.instantiate\uFF08\u5B98\u65B9\u6587\u6863 performance/wasm\uFF09", tier: "L1", status: "implemented" },
1682
+ // ★★NC1 首个声明式能力(2026-09-30):来源 = 权威标尺修复后浮现的真实缺口之一
1683
+ // (快照抽取器修复前 `wx.setKeepScreenOn` 结构性不可见,见 mp-spec-coverage 的 SPEC_PLANNED)。
1684
+ // 落地方式 = **声明式**(`packages/api/src/bridge-decls/keep-screen-on.ts` → 生成 → 合并进 bridge):
1685
+ // MP `wx.setKeepScreenOn` / Web **Screen Wake Lock**(`navigator.wakeLock.request('screen')`)——
1686
+ // 双端都有真实对等(非降级对等)。status 标 implemented:桥与 Hook 已落地且测试覆盖
1687
+ // (含双端真跑 + 幂等 + 缺 API 降级)。
1688
+ { id: "C83", kind: "capability", semantic: "capability.keep-screen-on", api: "useKeepScreenOn()", props: ["KeepScreenOnState"], mpEquiv: "wx.setKeepScreenOn\uFF08Web \u5BF9\u7B49\uFF1AScreen Wake Lock API\uFF09", tier: "L1", status: "implemented" },
1689
+ // ★★NC1 第二个声明式能力(2026-09-30):**用户裁定**「openDocument 属原生能力落地范畴,
1690
+ // 目标就是 99% 的业务代码不需要写原生代码」⇒ 落地而非搁置。
1691
+ // 形态 = 声明式(bridge-decls/open-document.ts → 生成 → 合并);MP `wx.openDocument` /
1692
+ // Web `window.open`(浏览器查看器);★Web 诚实边界:office 格式通常转下载而非预览。
1693
+ // status = implemented:桥与 Hook 已落地 + 测试覆盖(含"弹窗被拦截 ⇒ Err"的防静默判据)。
1694
+ { id: "C84", kind: "capability", semantic: "capability.document-preview", api: "useOpenDocument()", props: ["DocumentPath"], mpEquiv: "wx.openDocument\uFF08Web \u5BF9\u7B49\uFF1Awindow.open \u2192 \u6D4F\u89C8\u5668\u67E5\u770B\u5668\uFF09", tier: "L1", status: "implemented" }
1469
1695
  ];
1470
1696
  var ENGINEERING = [
1471
1697
  { id: "E1", kind: "engineering", semantic: "engineering.state", api: "useState()", mpEquiv: "data", tier: "L1", status: "planned" },
@@ -1611,6 +1837,10 @@ var MP_API_GROUPS = [
1611
1837
  { mp: "wx.onUserTriggerTranslation/onUserOffTranslation\uFF08\u7FFB\u8BD1\uFF09", proteus: "useTranslation", status: "ok", group: "api" },
1612
1838
  { mp: "wx.onGeneratePoster\uFF08\u5206\u4EAB\u6D77\u62A5\uFF09", proteus: "usePoster", status: "ok", group: "api" },
1613
1839
  { mp: "wx.checkDeviceSupportHevc\uFF08\u8BBE\u5907\u80FD\u529B\u63A2\u6D4B\uFF09", proteus: "useDeviceCapability", status: "ok", group: "api" },
1840
+ // ★C82:**超官方清单**能力(官方 301 API 列表里无 wasm)——`WXWebAssembly` 属官方「小程序运行时」
1841
+ // 文档(performance/wasm)确认的运行时能力 ⇒ 矩阵按 mpEquiv 如实登记;
1842
+ // 不进 spec 覆盖度分母(否则分母不变而 covered 虚增,覆盖率失真——见 mp-spec-coverage 注释)
1843
+ { mp: "WXWebAssembly.instantiate\uFF08WebAssembly \u8DE8\u5E73\u53F0\uFF0C\u5B98\u65B9\u6587\u6863 performance/wasm\uFF09", proteus: "useWebAssembly", status: "ok", group: "api" },
1614
1844
  { mp: "wx.getFileSystemManager/*\uFF08\u6587\u4EF6 30+\uFF09", proteus: "useFileSystem", status: "ok", group: "api" },
1615
1845
  { mp: "wx.compressFile/unzip", proteus: "useArchive", status: "ok", group: "api" },
1616
1846
  { mp: "wx.set/get/remove/clearStorage(+Sync)", proteus: "useStorage", status: "ok", group: "api" },
@@ -1707,38 +1937,2020 @@ function degradeProp(tag, prop) {
1707
1937
  if (tag && HOST_TAGS[tag]) {
1708
1938
  return { mp: "supported", web: "fallback", behavior: HOST_TAGS[tag] };
1709
1939
  }
1710
- const tagProp = tag ? TAG_PROP_MP_FALLBACK[tag] : void 0;
1711
- const tagPropBehavior = tagProp ? tagProp[prop] : void 0;
1712
- if (tagPropBehavior) return { mp: "fallback", web: "supported", behavior: tagPropBehavior };
1713
- const webExt = WEB_EXTENSION_MP_FALLBACK[prop];
1714
- if (webExt) return { mp: "fallback", web: "supported", behavior: webExt };
1715
- return { mp: "supported", web: "supported" };
1940
+ const tagProp = tag ? TAG_PROP_MP_FALLBACK[tag] : void 0;
1941
+ const tagPropBehavior = tagProp ? tagProp[prop] : void 0;
1942
+ if (tagPropBehavior) return { mp: "fallback", web: "supported", behavior: tagPropBehavior };
1943
+ const webExt = WEB_EXTENSION_MP_FALLBACK[prop];
1944
+ if (webExt) return { mp: "fallback", web: "supported", behavior: webExt };
1945
+ return { mp: "supported", web: "supported" };
1946
+ }
1947
+ function degradationOf(prim) {
1948
+ const out = {};
1949
+ for (const p of prim.props ?? []) out[p] = degradeProp(prim.tag, p);
1950
+ return out;
1951
+ }
1952
+ var DEGRADATION_TABLE = (() => {
1953
+ const out = {};
1954
+ for (const prim of PRIMITIVE_CATALOG) {
1955
+ if (!prim.tag) continue;
1956
+ out[prim.tag] = degradationOf(prim);
1957
+ }
1958
+ return out;
1959
+ })();
1960
+ var MP_UNSUPPORTED_PROPS = (() => {
1961
+ const kebab = (s) => s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
1962
+ const out = {};
1963
+ for (const [tag, table] of Object.entries(DEGRADATION_TABLE)) {
1964
+ const set = /* @__PURE__ */ new Set();
1965
+ for (const [prop, entry] of Object.entries(table)) {
1966
+ if (entry.mp === "unsupported") set.add(kebab(prop));
1967
+ }
1968
+ if (set.size) out[tag] = set;
1969
+ }
1970
+ return out;
1971
+ })();
1972
+
1973
+ // ../slot-runtime/src/opcode.ts
1974
+ var PropKeyTable = class _PropKeyTable {
1975
+ constructor() {
1976
+ this.keys = [];
1977
+ this.index = /* @__PURE__ */ new Map();
1978
+ }
1979
+ /** 分配(已存在则返回原下标——**幂等**) */
1980
+ intern(key) {
1981
+ const hit = this.index.get(key);
1982
+ if (hit !== void 0) return hit;
1983
+ const id = this.keys.length;
1984
+ this.keys.push(key);
1985
+ this.index.set(key, id);
1986
+ return id;
1987
+ }
1988
+ /** 反查(诊断 / explain 用;运行时不走这条路径) */
1989
+ keyOf(id) {
1990
+ return this.keys[id];
1991
+ }
1992
+ get size() {
1993
+ return this.keys.length;
1994
+ }
1995
+ /** 导出(进 PatchTable 产物) */
1996
+ toArray() {
1997
+ return [...this.keys];
1998
+ }
1999
+ /** 导入(消费方重建) */
2000
+ static fromArray(keys) {
2001
+ const t = new _PropKeyTable();
2002
+ for (const k of keys) t.intern(k);
2003
+ return t;
2004
+ }
2005
+ };
2006
+ var StringPool = class _StringPool {
2007
+ constructor() {
2008
+ this.values = [];
2009
+ this.index = /* @__PURE__ */ new Map();
2010
+ }
2011
+ intern(s) {
2012
+ const hit = this.index.get(s);
2013
+ if (hit !== void 0) return hit;
2014
+ const id = this.values.length;
2015
+ this.values.push(s);
2016
+ this.index.set(s, id);
2017
+ return id;
2018
+ }
2019
+ valueOf(id) {
2020
+ return this.values[id];
2021
+ }
2022
+ get size() {
2023
+ return this.values.length;
2024
+ }
2025
+ toArray() {
2026
+ return [...this.values];
2027
+ }
2028
+ static fromArray(values) {
2029
+ const p = new _StringPool();
2030
+ for (const v of values) p.intern(v);
2031
+ return p;
2032
+ }
2033
+ };
2034
+
2035
+ // ../slot-runtime/src/anim-curve.ts
2036
+ var AnimCurve = {
2037
+ LINEAR: 0,
2038
+ EASE_OUT_CUBIC: 1,
2039
+ EASE_IN_CUBIC: 2,
2040
+ EASE_IN_OUT_CUBIC: 3,
2041
+ SPRING_APPROX: 4
2042
+ };
2043
+ var TABLE_N = 65;
2044
+ function springApprox(u) {
2045
+ return 1 - Math.exp(-6 * u) * Math.cos(10 * u);
2046
+ }
2047
+ function buildTable(curve) {
2048
+ const t = new Float64Array(TABLE_N);
2049
+ for (let i = 0; i < TABLE_N; i++) {
2050
+ const u = i / (TABLE_N - 1);
2051
+ t[i] = curve === AnimCurve.EASE_OUT_CUBIC ? 1 - (1 - u) ** 3 : curve === AnimCurve.EASE_IN_CUBIC ? u ** 3 : curve === AnimCurve.EASE_IN_OUT_CUBIC ? u < 0.5 ? 4 * u ** 3 : 1 - (-2 * u + 2) ** 3 / 2 : curve === AnimCurve.SPRING_APPROX ? springApprox(u) : u;
2052
+ }
2053
+ t[0] = 0;
2054
+ t[TABLE_N - 1] = 1;
2055
+ return t;
2056
+ }
2057
+ var TABLES = [
2058
+ buildTable(AnimCurve.LINEAR),
2059
+ buildTable(AnimCurve.EASE_OUT_CUBIC),
2060
+ buildTable(AnimCurve.EASE_IN_CUBIC),
2061
+ buildTable(AnimCurve.EASE_IN_OUT_CUBIC),
2062
+ buildTable(AnimCurve.SPRING_APPROX)
2063
+ ];
2064
+
2065
+ // ../slot-runtime/src/buffer.ts
2066
+ var OPS_MAGIC = 1347376720;
2067
+ var OPS_VERSION = 2;
2068
+ var OPS_HEADER_BYTES = 20;
2069
+ function opSize(op) {
2070
+ switch (op.op) {
2071
+ case 1 /* SET_PROP */:
2072
+ case 2 /* SET_STYLE */:
2073
+ return 11;
2074
+ case 3 /* SET_TEXT */:
2075
+ return 9;
2076
+ case 4 /* SET_ATTRS */:
2077
+ return 7 + 6 * op.attrs.length;
2078
+ case 5 /* TOGGLE_VIS */:
2079
+ return 6;
2080
+ case 16 /* INSERT_BLOCK */:
2081
+ return 10;
2082
+ case 17 /* REMOVE_NODE */:
2083
+ return 5;
2084
+ case 18 /* MOVE_NODE */:
2085
+ return 10;
2086
+ case 32 /* LIST_SET */:
2087
+ return 9;
2088
+ case 33 /* LIST_SPLICE */:
2089
+ return 15 + 4 * op.itemKeyRefs.length;
2090
+ case 34 /* LIST_UPDATE */:
2091
+ return 17;
2092
+ case 48 /* CALL_COMPONENT_UPDATE */:
2093
+ return 13;
2094
+ default: {
2095
+ const never = op;
2096
+ throw new Error(`\u672A\u77E5\u6307\u4EE4\uFF1A${JSON.stringify(never)}`);
2097
+ }
2098
+ }
2099
+ }
2100
+ var ByteWriter = class {
2101
+ constructor(size) {
2102
+ this.pos = 0;
2103
+ this.buf = new Uint8Array(size);
2104
+ }
2105
+ u8(v) {
2106
+ this.buf[this.pos++] = v & 255;
2107
+ }
2108
+ u16(v) {
2109
+ this.buf[this.pos++] = v & 255;
2110
+ this.buf[this.pos++] = v >>> 8 & 255;
2111
+ }
2112
+ u32(v) {
2113
+ this.buf[this.pos++] = v & 255;
2114
+ this.buf[this.pos++] = v >>> 8 & 255;
2115
+ this.buf[this.pos++] = v >>> 16 & 255;
2116
+ this.buf[this.pos++] = v >>> 24 & 255;
2117
+ }
2118
+ f32(v) {
2119
+ const f = new Float32Array(1);
2120
+ f[0] = v;
2121
+ const b = new Uint8Array(f.buffer);
2122
+ this.buf[this.pos++] = b[0];
2123
+ this.buf[this.pos++] = b[1];
2124
+ this.buf[this.pos++] = b[2];
2125
+ this.buf[this.pos++] = b[3];
2126
+ }
2127
+ str(s) {
2128
+ const bytes = utf8Encode(s);
2129
+ if (bytes.length > 65535) throw new Error(`\u5B57\u7B26\u4E32\u8FC7\u957F\uFF08>65535B\uFF09\uFF1A${bytes.length}`);
2130
+ this.u16(bytes.length);
2131
+ this.buf.set(bytes, this.pos);
2132
+ this.pos += bytes.length;
2133
+ }
2134
+ done() {
2135
+ if (this.pos !== this.buf.length) throw new Error(`\u5199\u5165\u957F\u5EA6\u4E0D\u7B26\uFF1A${this.pos} != ${this.buf.length}`);
2136
+ return this.buf;
2137
+ }
2138
+ };
2139
+ function utf8Encode(s) {
2140
+ const out = [];
2141
+ for (let i = 0; i < s.length; i++) {
2142
+ let cp = s.charCodeAt(i);
2143
+ if (cp >= 55296 && cp <= 56319 && i + 1 < s.length) {
2144
+ const lo = s.charCodeAt(i + 1);
2145
+ if (lo >= 56320 && lo <= 57343) {
2146
+ cp = (cp - 55296 << 10) + (lo - 56320) + 65536;
2147
+ i++;
2148
+ }
2149
+ }
2150
+ if (cp < 128) out.push(cp);
2151
+ else if (cp < 2048) out.push(192 | cp >> 6, 128 | cp & 63);
2152
+ else if (cp < 65536) out.push(224 | cp >> 12, 128 | cp >> 6 & 63, 128 | cp & 63);
2153
+ else out.push(240 | cp >> 18, 128 | cp >> 12 & 63, 128 | cp >> 6 & 63, 128 | cp & 63);
2154
+ }
2155
+ return Uint8Array.from(out);
2156
+ }
2157
+ var OpBuffer = class {
2158
+ constructor() {
2159
+ this.ops = [];
2160
+ }
2161
+ push(op) {
2162
+ this.ops.push(op);
2163
+ }
2164
+ get count() {
2165
+ return this.ops.length;
2166
+ }
2167
+ /** 只读快照(调试 / explain / 测试用) */
2168
+ snapshot() {
2169
+ return this.ops.slice();
2170
+ }
2171
+ clear() {
2172
+ this.ops.length = 0;
2173
+ }
2174
+ /**
2175
+ * 编码并提交,随后**清空**。
2176
+ *
2177
+ * @param keys 属性键表(进入 Header,供消费方还原 keyId → 字符串)
2178
+ * @param strings 字符串池(同上)
2179
+ * @param sink 提交出口(App 端 = 一次 JSI 调用)
2180
+ * @returns 本次提交的字节数
2181
+ */
2182
+ flush(keys, strings, sink) {
2183
+ const bytes = encodeOps(this.ops, keys, strings);
2184
+ const n = this.ops.length;
2185
+ sink(bytes, n);
2186
+ this.ops.length = 0;
2187
+ return bytes.length;
2188
+ }
2189
+ };
2190
+ function collectRefs(ops) {
2191
+ const k = /* @__PURE__ */ new Set();
2192
+ const s = /* @__PURE__ */ new Set();
2193
+ for (const op of ops) {
2194
+ switch (op.op) {
2195
+ case 1 /* SET_PROP */:
2196
+ case 2 /* SET_STYLE */:
2197
+ k.add(op.keyId);
2198
+ break;
2199
+ case 4 /* SET_ATTRS */:
2200
+ for (const a of op.attrs) k.add(a.keyId);
2201
+ break;
2202
+ case 3 /* SET_TEXT */:
2203
+ s.add(op.textRef);
2204
+ break;
2205
+ case 33 /* LIST_SPLICE */:
2206
+ for (const r of op.itemKeyRefs) s.add(r);
2207
+ break;
2208
+ case 34 /* LIST_UPDATE */:
2209
+ s.add(op.itemKeyRef);
2210
+ break;
2211
+ // TOGGLE_VIS / INSERT_BLOCK / REMOVE_NODE / MOVE_NODE / LIST_SET /
2212
+ // CALL_COMPONENT_UPDATE:无池引用(若将来新增,**在此处补一支**)
2213
+ default:
2214
+ break;
2215
+ }
2216
+ }
2217
+ return { keyIds: [...k].sort((a, b) => a - b), strRefs: [...s].sort((a, b) => a - b) };
2218
+ }
2219
+ function remapOf(used) {
2220
+ const m = /* @__PURE__ */ new Map();
2221
+ used.forEach((old, i) => m.set(old, i));
2222
+ return m;
2223
+ }
2224
+ function remapOp(op, kMap, sMap) {
2225
+ switch (op.op) {
2226
+ case 1 /* SET_PROP */:
2227
+ case 2 /* SET_STYLE */:
2228
+ return { ...op, keyId: kMap.get(op.keyId) ?? 0 };
2229
+ case 4 /* SET_ATTRS */:
2230
+ return { ...op, attrs: op.attrs.map((a) => ({ keyId: kMap.get(a.keyId) ?? 0, value: a.value })) };
2231
+ case 3 /* SET_TEXT */:
2232
+ return { ...op, textRef: sMap.get(op.textRef) ?? 0 };
2233
+ case 33 /* LIST_SPLICE */:
2234
+ return { ...op, itemKeyRefs: op.itemKeyRefs.map((r) => sMap.get(r) ?? 0) };
2235
+ case 34 /* LIST_UPDATE */:
2236
+ return { ...op, itemKeyRef: sMap.get(op.itemKeyRef) ?? 0 };
2237
+ default:
2238
+ return op;
2239
+ }
2240
+ }
2241
+ function encodeOps(ops, keys, strings) {
2242
+ const allKeys = keys.toArray();
2243
+ const allStrs = strings.toArray();
2244
+ const used = collectRefs(ops);
2245
+ const keyArr = used.keyIds.map((i) => allKeys[i]).filter((x) => x !== void 0);
2246
+ const strArr = used.strRefs.map((i) => allStrs[i]).filter((x) => x !== void 0);
2247
+ const kMap = remapOf(used.keyIds.filter((i) => allKeys[i] !== void 0));
2248
+ const sMap = remapOf(used.strRefs.filter((i) => allStrs[i] !== void 0));
2249
+ let size = OPS_HEADER_BYTES;
2250
+ for (const k of keyArr) size += 2 + utf8Encode(k).length;
2251
+ for (const s of strArr) size += 2 + utf8Encode(s).length;
2252
+ for (const op of ops) size += opSize(op);
2253
+ const w = new ByteWriter(size);
2254
+ w.u32(OPS_MAGIC);
2255
+ w.u32(OPS_VERSION);
2256
+ w.u32(ops.length);
2257
+ w.u32(keyArr.length);
2258
+ w.u32(strArr.length);
2259
+ for (const k of keyArr) w.str(k);
2260
+ for (const s of strArr) w.str(s);
2261
+ for (const op of ops) encodeOp(w, remapOp(op, kMap, sMap));
2262
+ return w.done();
2263
+ }
2264
+ function encodeOp(w, op) {
2265
+ w.u8(op.op);
2266
+ switch (op.op) {
2267
+ case 1 /* SET_PROP */:
2268
+ case 2 /* SET_STYLE */:
2269
+ w.u32(op.nodeId);
2270
+ w.u16(op.keyId);
2271
+ w.f32(op.value);
2272
+ return;
2273
+ case 3 /* SET_TEXT */:
2274
+ w.u32(op.nodeId);
2275
+ w.u32(op.textRef);
2276
+ return;
2277
+ case 4 /* SET_ATTRS */:
2278
+ w.u32(op.nodeId);
2279
+ w.u16(op.attrs.length);
2280
+ for (const a of op.attrs) {
2281
+ w.u16(a.keyId);
2282
+ w.f32(a.value);
2283
+ }
2284
+ return;
2285
+ case 5 /* TOGGLE_VIS */:
2286
+ w.u32(op.nodeId);
2287
+ w.u8(op.visible ? 1 : 0);
2288
+ return;
2289
+ case 16 /* INSERT_BLOCK */:
2290
+ w.u32(op.blockId);
2291
+ w.u32(op.refNodeId);
2292
+ w.u8(op.pos);
2293
+ return;
2294
+ case 17 /* REMOVE_NODE */:
2295
+ w.u32(op.nodeId);
2296
+ return;
2297
+ case 18 /* MOVE_NODE */:
2298
+ w.u32(op.nodeId);
2299
+ w.u32(op.refNodeId);
2300
+ w.u8(op.pos);
2301
+ return;
2302
+ case 32 /* LIST_SET */:
2303
+ w.u32(op.listId);
2304
+ w.u32(op.dataRef);
2305
+ return;
2306
+ case 33 /* LIST_SPLICE */:
2307
+ w.u32(op.listId);
2308
+ w.u32(op.start);
2309
+ w.u32(op.delCount);
2310
+ w.u16(op.itemKeyRefs.length);
2311
+ for (const r of op.itemKeyRefs) w.u32(r);
2312
+ return;
2313
+ case 34 /* LIST_UPDATE */:
2314
+ w.u32(op.listId);
2315
+ w.u32(op.itemKeyRef);
2316
+ w.u32(op.slotId);
2317
+ w.f32(op.value);
2318
+ return;
2319
+ case 48 /* CALL_COMPONENT_UPDATE */:
2320
+ w.u32(op.componentId);
2321
+ w.u32(op.slotId);
2322
+ w.f32(op.value);
2323
+ return;
2324
+ default: {
2325
+ const never = op;
2326
+ throw new Error(`\u672A\u77E5\u6307\u4EE4\uFF1A${JSON.stringify(never)}`);
2327
+ }
2328
+ }
2329
+ }
2330
+
2331
+ // ../slot-runtime/src/slot.ts
2332
+ function createSlot(spec, keys, strings, initial, registry) {
2333
+ const nodeId = spec.nodeId;
2334
+ const keyId = spec.keyId ?? 0;
2335
+ const listId = spec.listId ?? 0;
2336
+ const listSlotId = spec.listSlotId ?? spec.id;
2337
+ const slot = {
2338
+ id: spec.id,
2339
+ nodeId,
2340
+ kind: spec.kind,
2341
+ value: initial,
2342
+ dirty: false,
2343
+ emit(next, buf) {
2344
+ switch (spec.kind) {
2345
+ case "text":
2346
+ buf.push({ op: 3 /* SET_TEXT */, nodeId, textRef: strings.intern(String(next)) });
2347
+ return;
2348
+ case "prop":
2349
+ buf.push({ op: 1 /* SET_PROP */, nodeId, keyId, value: toF32(next) });
2350
+ return;
2351
+ case "style":
2352
+ buf.push({ op: 2 /* SET_STYLE */, nodeId, keyId, value: toF32(next) });
2353
+ return;
2354
+ case "visibility":
2355
+ buf.push({ op: 5 /* TOGGLE_VIS */, nodeId, visible: Boolean(next) });
2356
+ return;
2357
+ case "list-item": {
2358
+ const item = next;
2359
+ const nodeId2 = registry?.resolveNode(listId, item.key, listSlotId);
2360
+ if (nodeId2 !== void 0) {
2361
+ if ((spec.itemKind ?? "style") === "text") {
2362
+ buf.push({ op: 3 /* SET_TEXT */, nodeId: nodeId2, textRef: strings.intern(String(item.value)) });
2363
+ } else {
2364
+ buf.push({ op: 2 /* SET_STYLE */, nodeId: nodeId2, keyId, value: toF32(item.value) });
2365
+ }
2366
+ return;
2367
+ }
2368
+ buf.push({
2369
+ op: 34 /* LIST_UPDATE */,
2370
+ listId,
2371
+ itemKeyRef: strings.intern(item.key),
2372
+ slotId: listSlotId,
2373
+ value: toF32(item.value)
2374
+ });
2375
+ return;
2376
+ }
2377
+ case "list-data":
2378
+ buf.push({ op: 32 /* LIST_SET */, listId, dataRef: toF32(next) });
2379
+ return;
2380
+ case "attrs": {
2381
+ const attrs = next;
2382
+ buf.push({ op: 4 /* SET_ATTRS */, nodeId, attrs });
2383
+ return;
2384
+ }
2385
+ case "component-prop":
2386
+ buf.push({ op: 48 /* CALL_COMPONENT_UPDATE */, componentId: nodeId, slotId: listSlotId, value: toF32(next) });
2387
+ return;
2388
+ default: {
2389
+ const never = spec.kind;
2390
+ throw new Error(`\u672A\u77E5\u69FD\u4F4D\u79CD\u7C7B\uFF1A${String(never)}`);
2391
+ }
2392
+ }
2393
+ }
2394
+ };
2395
+ return slot;
2396
+ }
2397
+ function toF32(v) {
2398
+ if (typeof v === "number") return v;
2399
+ if (typeof v === "boolean") return v ? 1 : 0;
2400
+ throw new Error(`\u69FD\u4F4D\u503C\u5FC5\u987B\u662F number/boolean\uFF0C\u6536\u5230 ${typeof v}\uFF08\u6587\u672C\u8BF7\u7528 kind='text'\uFF09`);
2401
+ }
2402
+ var microtaskScheduler = {
2403
+ schedule(cb) {
2404
+ void Promise.resolve().then(cb);
2405
+ }
2406
+ };
2407
+ var SlotRuntime = class {
2408
+ constructor(keys, strings, sink, scheduler = microtaskScheduler) {
2409
+ this.keys = keys;
2410
+ this.strings = strings;
2411
+ this.sink = sink;
2412
+ this.scheduler = scheduler;
2413
+ this.buffer = new OpBuffer();
2414
+ this.dirty = [];
2415
+ this.pending = false;
2416
+ this.stats = { flushes: 0, opsEmitted: 0, bytesSent: 0, shortCircuits: 0 };
2417
+ /**
2418
+ * ★★**帧号**(P2-5 新增):每次 `flush()` 调用(**包括无脏槽位的空调用**)自增。
2419
+ *
2420
+ * 【为什么需要(v-memo 的组语义要求帧边界)】memo 组在"同一帧内"只判定一次脏:
2421
+ * 若一帧里有多个源变化(多次 `writeSlotsOfSource`)触到同一组的多个槽位,
2422
+ * 第一次判定"依赖变了"之后,同帧其余槽位必须**照常写**(组语义 = 子树整体更新)。
2423
+ * 而判定一次的判据只有帧边界——`flush()` 是这套系统的天然帧边界。
2424
+ * ★与 `stats.flushes` 的区别:后者只在**真的提交了字节**时自增(空 flush 不计);
2425
+ * 本计数每次 flush 调用都自增(帧边界语义)。
2426
+ */
2427
+ this.frame = 0;
2428
+ }
2429
+ /** 帧号(v-memo 门用;只读) */
2430
+ get frameId() {
2431
+ return this.frame;
2432
+ }
2433
+ /** 槽位写入(方案 §3.3):相等即短路;否则标脏 + 排帧 */
2434
+ setSlot(slot, next) {
2435
+ if (Object.is(slot.value, next)) {
2436
+ this.stats.shortCircuits++;
2437
+ return;
2438
+ }
2439
+ slot.value = next;
2440
+ if (!slot.dirty) {
2441
+ slot.dirty = true;
2442
+ this.dirty.push(slot);
2443
+ }
2444
+ this.scheduleFlush();
2445
+ }
2446
+ /** 排帧(幂等——同帧多次调用只排一次) */
2447
+ scheduleFlush() {
2448
+ if (this.pending) return;
2449
+ this.pending = true;
2450
+ this.scheduler.schedule(() => this.flush());
2451
+ }
2452
+ /**
2453
+ * 立即 flush(测试 / 确定性驱动用)
2454
+ *
2455
+ * 【为什么单独暴露】真机上「Vsync 何时来」不可控;测量装置纪律要求用例能**确定性地**
2456
+ * 驱动一次提交(本仓四次踩过"测量装置污染读数")。
2457
+ */
2458
+ flush() {
2459
+ this.frame++;
2460
+ this.pending = false;
2461
+ for (const slot of this.dirty) {
2462
+ slot.emit(slot.value, this.buffer);
2463
+ slot.dirty = false;
2464
+ }
2465
+ this.dirty.length = 0;
2466
+ if (this.buffer.count === 0) return;
2467
+ this.stats.opsEmitted += this.buffer.count;
2468
+ this.stats.bytesSent += this.buffer.flush(this.keys, this.strings, this.sink);
2469
+ this.stats.flushes++;
2470
+ }
2471
+ getStats() {
2472
+ return { ...this.stats };
2473
+ }
2474
+ /** 待发射的脏槽位数(诊断) */
2475
+ get dirtyCount() {
2476
+ return this.dirty.length;
2477
+ }
2478
+ };
2479
+
2480
+ // ../slot-runtime/src/table.ts
2481
+ function resolveDynamicClasses(classValue, rules) {
2482
+ const active = collectActiveClasses(classValue);
2483
+ if (active.size === 0 || rules.length === 0) return {};
2484
+ const normal = {};
2485
+ const important = {};
2486
+ for (const r of rules) {
2487
+ if (r.classes.length === 0) continue;
2488
+ if (!r.classes.every((c) => active.has(c))) continue;
2489
+ for (const [k, v] of Object.entries(r.decls)) {
2490
+ if (r.important.includes(k)) important[k] = v;
2491
+ else normal[k] = v;
2492
+ }
2493
+ }
2494
+ return { ...normal, ...important };
2495
+ }
2496
+ function collectActiveClasses(v) {
2497
+ const out = /* @__PURE__ */ new Set();
2498
+ const walk = (x) => {
2499
+ if (!x) return;
2500
+ if (typeof x === "string") {
2501
+ for (const c of x.split(/\s+/)) if (c) out.add(c);
2502
+ return;
2503
+ }
2504
+ if (Array.isArray(x)) {
2505
+ for (const e of x) walk(e);
2506
+ return;
2507
+ }
2508
+ if (typeof x === "object") {
2509
+ for (const [k, vv] of Object.entries(x)) if (vv) out.add(k);
2510
+ return;
2511
+ }
2512
+ };
2513
+ walk(v);
2514
+ return out;
2515
+ }
2516
+
2517
+ // ../slot-runtime/src/dynamic-class.ts
2518
+ function bitmapOfDynamicClasses(active, classes) {
2519
+ let b = 0;
2520
+ for (let i = 0; i < classes.length; i++) if (active.has(classes[i])) b |= 1 << i;
2521
+ return b >>> 0;
2522
+ }
2523
+ function groupStateOf(bitmap, groupBits) {
2524
+ for (let i = 0; i < groupBits.length; i++) if ((bitmap & 1 << groupBits[i]) !== 0) return i + 1;
2525
+ return 0;
2526
+ }
2527
+ function applyDynamicClassPlan(classValue, plan, out) {
2528
+ const tables = plan.tables;
2529
+ const fields = Object.keys(tables);
2530
+ if (fields.length === 0) return 0;
2531
+ const active = collectActiveClasses(classValue);
2532
+ const bitmap = bitmapOfDynamicClasses(active, plan.classes);
2533
+ const groups = plan.mutexGroups;
2534
+ let groupStates = null;
2535
+ if (groups && groups.length > 0) {
2536
+ groupStates = groups.map((bits) => groupStateOf(bitmap, bits));
2537
+ }
2538
+ let written = 0;
2539
+ for (const field of fields) {
2540
+ const t = tables[field];
2541
+ let v;
2542
+ if (t.kind === "bits") {
2543
+ const masked = (bitmap & (t.mask ?? 0)) >>> 0;
2544
+ v = t.values[t.map[masked]];
2545
+ } else {
2546
+ v = t.values[groupStates ? groupStates[t.group ?? 0] : 0];
2547
+ }
2548
+ out[field] = v;
2549
+ written++;
2550
+ }
2551
+ return written;
2552
+ }
2553
+
2554
+ // ../slot-runtime/src/expr.ts
2555
+ var PURE_CALLS = {
2556
+ // —— Math(纯计算)——
2557
+ "Math.abs": Math.abs,
2558
+ "Math.ceil": Math.ceil,
2559
+ "Math.floor": Math.floor,
2560
+ "Math.round": Math.round,
2561
+ "Math.trunc": Math.trunc,
2562
+ "Math.sign": Math.sign,
2563
+ "Math.sqrt": Math.sqrt,
2564
+ "Math.cbrt": Math.cbrt,
2565
+ "Math.pow": Math.pow,
2566
+ "Math.exp": Math.exp,
2567
+ "Math.log": Math.log,
2568
+ "Math.log2": Math.log2,
2569
+ "Math.log10": Math.log10,
2570
+ "Math.min": Math.min,
2571
+ "Math.max": Math.max,
2572
+ // —— 类型转换(call 形态,非 new)——
2573
+ "String": String,
2574
+ "Number": Number,
2575
+ "Boolean": Boolean,
2576
+ // —— 解析 / 判定(全局)——
2577
+ "parseInt": parseInt,
2578
+ "parseFloat": parseFloat,
2579
+ "isNaN": isNaN,
2580
+ "isFinite": isFinite,
2581
+ "Number.isFinite": Number.isFinite,
2582
+ "Number.isInteger": Number.isInteger,
2583
+ "Number.isNaN": Number.isNaN,
2584
+ // —— 数组判定 ——
2585
+ "Array.isArray": Array.isArray
2586
+ };
2587
+ var PURE_METHODS = {
2588
+ // —— Array(非变异)——
2589
+ join: (recv, sep) => Array.isArray(recv) ? recv.join(sep === void 0 ? "," : String(sep)) : void 0,
2590
+ slice: (recv, a, b) => typeof recv === "string" || Array.isArray(recv) ? recv.slice(a, b) : void 0,
2591
+ concat: (recv, ...rest) => Array.isArray(recv) ? recv.concat(...rest) : void 0,
2592
+ indexOf: (recv, x) => typeof recv === "string" || Array.isArray(recv) ? recv.indexOf(x) : void 0,
2593
+ includes: (recv, x) => typeof recv === "string" || Array.isArray(recv) ? recv.includes(x) : void 0,
2594
+ // —— String(非变异)——
2595
+ toUpperCase: (recv) => typeof recv === "string" ? recv.toUpperCase() : void 0,
2596
+ toLowerCase: (recv) => typeof recv === "string" ? recv.toLowerCase() : void 0,
2597
+ trim: (recv) => typeof recv === "string" ? recv.trim() : void 0,
2598
+ trimStart: (recv) => typeof recv === "string" ? recv.trimStart() : void 0,
2599
+ trimEnd: (recv) => typeof recv === "string" ? recv.trimEnd() : void 0,
2600
+ charAt: (recv, i) => typeof recv === "string" ? recv.charAt(i) : void 0,
2601
+ padStart: (recv, n, pad) => typeof recv === "string" ? recv.padStart(n, pad) : void 0,
2602
+ padEnd: (recv, n, pad) => typeof recv === "string" ? recv.padEnd(n, pad) : void 0,
2603
+ repeat: (recv, n) => typeof recv === "string" ? recv.repeat(n) : void 0,
2604
+ substring: (recv, a, b) => typeof recv === "string" ? recv.substring(a, b) : void 0,
2605
+ startsWith: (recv, x) => typeof recv === "string" ? recv.startsWith(x) : void 0,
2606
+ endsWith: (recv, x) => typeof recv === "string" ? recv.endsWith(x) : void 0,
2607
+ // —— Number(非变异)——
2608
+ toFixed: (recv, n) => typeof recv === "number" ? recv.toFixed(n) : void 0,
2609
+ toString: (recv) => recv === void 0 || recv === null ? void 0 : String(recv)
2610
+ };
2611
+ var GLOBAL_CONST_MEMBERS = {
2612
+ "Math.PI": Math.PI,
2613
+ "Math.E": Math.E,
2614
+ "Math.LN2": Math.LN2,
2615
+ "Math.LN10": Math.LN10,
2616
+ "Math.LOG2E": Math.LOG2E,
2617
+ "Math.LOG10E": Math.LOG10E,
2618
+ "Math.SQRT2": Math.SQRT2,
2619
+ "Math.SQRT1_2": Math.SQRT1_2,
2620
+ "Number.MAX_SAFE_INTEGER": Number.MAX_SAFE_INTEGER,
2621
+ "Number.MIN_SAFE_INTEGER": Number.MIN_SAFE_INTEGER,
2622
+ "Number.EPSILON": Number.EPSILON,
2623
+ "Number.MAX_VALUE": Number.MAX_VALUE,
2624
+ "Number.MIN_VALUE": Number.MIN_VALUE
2625
+ };
2626
+ function toNum(v) {
2627
+ if (typeof v === "number") return v;
2628
+ if (typeof v === "boolean") return v ? 1 : 0;
2629
+ if (typeof v === "string") return v.trim() === "" ? 0 : Number(v);
2630
+ if (v === null) return 0;
2631
+ if (v === void 0) return NaN;
2632
+ return NaN;
2633
+ }
2634
+ function evalExpr(p, ctx) {
2635
+ switch (p.k) {
2636
+ case "root":
2637
+ return ctx.read(p.name);
2638
+ case "lit":
2639
+ return p.v;
2640
+ case "undef":
2641
+ return void 0;
2642
+ case "mem": {
2643
+ const o = evalExpr(p.obj, ctx);
2644
+ if (o == null) return void 0;
2645
+ return o[p.key];
2646
+ }
2647
+ case "memdyn": {
2648
+ const o = evalExpr(p.obj, ctx);
2649
+ if (o == null) return void 0;
2650
+ const k = evalExpr(p.key, ctx);
2651
+ return o[String(k)];
2652
+ }
2653
+ case "un": {
2654
+ const v = evalExpr(p.arg, ctx);
2655
+ const uop = p.op;
2656
+ switch (uop) {
2657
+ case "!":
2658
+ return !v;
2659
+ case "-":
2660
+ return -toNum(v);
2661
+ case "+":
2662
+ return +toNum(v);
2663
+ default:
2664
+ throw new Error(`\u672A\u77E5\u4E00\u5143\u8FD0\u7B97\u7B26\uFF1A${String(uop)}`);
2665
+ }
2666
+ }
2667
+ case "bin": {
2668
+ const l = evalExpr(p.l, ctx);
2669
+ const r = evalExpr(p.r, ctx);
2670
+ const bop = p.op;
2671
+ switch (bop) {
2672
+ case "+":
2673
+ if (typeof l === "string" || typeof r === "string") return String(l) + String(r);
2674
+ return toNum(l) + toNum(r);
2675
+ case "-":
2676
+ return toNum(l) - toNum(r);
2677
+ case "*":
2678
+ return toNum(l) * toNum(r);
2679
+ case "/":
2680
+ return toNum(l) / toNum(r);
2681
+ case "%":
2682
+ return toNum(l) % toNum(r);
2683
+ case "===":
2684
+ return l === r;
2685
+ case "!==":
2686
+ return l !== r;
2687
+ case "<":
2688
+ return l < r;
2689
+ case ">":
2690
+ return l > r;
2691
+ case "<=":
2692
+ return l <= r;
2693
+ case ">=":
2694
+ return l >= r;
2695
+ default:
2696
+ throw new Error(`\u672A\u77E5\u4E8C\u5143\u8FD0\u7B97\u7B26\uFF1A${String(bop)}`);
2697
+ }
2698
+ }
2699
+ case "logi": {
2700
+ const l = evalExpr(p.l, ctx);
2701
+ if (p.op === "&&") return l ? evalExpr(p.r, ctx) : l;
2702
+ if (p.op === "||") return l ? l : evalExpr(p.r, ctx);
2703
+ return l === null || l === void 0 ? evalExpr(p.r, ctx) : l;
2704
+ }
2705
+ case "cond":
2706
+ return evalExpr(p.t, ctx) ? evalExpr(p.c, ctx) : evalExpr(p.a, ctx);
2707
+ case "obj": {
2708
+ const o = {};
2709
+ for (const pr of p.props) o[pr.key] = evalExpr(pr.value, ctx);
2710
+ return o;
2711
+ }
2712
+ case "arr":
2713
+ return p.items.map((x) => evalExpr(x, ctx));
2714
+ case "mcall": {
2715
+ const recv = evalExpr(p.recv, ctx);
2716
+ if (recv === void 0 || recv === null) return void 0;
2717
+ const impl = PURE_METHODS[p.method];
2718
+ if (!impl) {
2719
+ throw new Error(`\u8868\u8FBE\u5F0F\u7A0B\u5E8F\u5F15\u7528\u4E86\u975E\u767D\u540D\u5355\u65B9\u6CD5\uFF1A${p.method}\uFF08\u89C1 slot-runtime/expr.ts \u7684 PURE_METHODS\uFF09`);
2720
+ }
2721
+ const args = p.args.map((a) => evalExpr(a, ctx));
2722
+ return impl(recv, ...args);
2723
+ }
2724
+ case "call": {
2725
+ const fn = PURE_CALLS[p.fn];
2726
+ if (typeof fn !== "function") {
2727
+ throw new Error(`\u8868\u8FBE\u5F0F\u7A0B\u5E8F\u5F15\u7528\u4E86\u975E\u767D\u540D\u5355\u51FD\u6570\uFF1A${p.fn}\uFF08\u89C1 slot-runtime/expr.ts \u7684 PURE_CALLS\uFF09`);
2728
+ }
2729
+ const args = p.args.map((a) => evalExpr(a, ctx));
2730
+ return fn(...args);
2731
+ }
2732
+ default: {
2733
+ const never = p;
2734
+ throw new Error(`\u672A\u77E5\u8868\u8FBE\u5F0F\u8282\u70B9\uFF1A${JSON.stringify(never)}`);
2735
+ }
2736
+ }
2737
+ }
2738
+
2739
+ // ../slot-runtime/src/list-registry.ts
2740
+ var ListRegistry = class {
2741
+ constructor() {
2742
+ /** listId → itemKey → (itemSlotId → nodeId) */
2743
+ this.items = /* @__PURE__ */ new Map();
2744
+ /** 统计(诊断:解释"这次 item 更新为什么没走快路径") */
2745
+ this.hits = 0;
2746
+ this.misses = 0;
2747
+ }
2748
+ /**
2749
+ * 登记一个列表项
2750
+ *
2751
+ * @param listId 编译期分配的列表 id
2752
+ * @param itemKey 该行的稳定 key(**绝不用下标**——下标在 splice 后会命中错项,方案坑位 #5)
2753
+ * @param slotNodes item 内槽位 id → 该槽位对应的**节点 id**
2754
+ */
2755
+ registerItem(listId, itemKey, slotNodes) {
2756
+ let byKey = this.items.get(listId);
2757
+ if (!byKey) {
2758
+ byKey = /* @__PURE__ */ new Map();
2759
+ this.items.set(listId, byKey);
2760
+ }
2761
+ const m = slotNodes instanceof Map ? slotNodes : new Map(Object.entries(slotNodes).map(([k, v]) => [Number(k), v]));
2762
+ byKey.set(itemKey, m);
2763
+ }
2764
+ /** 批量登记(列表首帧渲染后一次性登记全部项——避免逐项调用) */
2765
+ registerItems(listId, entries) {
2766
+ for (const e of entries) this.registerItem(listId, e.itemKey, e.slotNodes);
2767
+ }
2768
+ /** 解析:该列表项内某槽位对应的节点 id(未登记 ⇒ undefined,调用方回退 LIST_UPDATE) */
2769
+ resolveNode(listId, itemKey, slotId) {
2770
+ const hit = this.items.get(listId)?.get(itemKey)?.get(slotId);
2771
+ if (hit === void 0) this.misses++;
2772
+ else this.hits++;
2773
+ return hit;
2774
+ }
2775
+ /** 移除项(splice 删除时调用,防止 key 被复用时命中已删项) */
2776
+ removeItem(listId, itemKey) {
2777
+ return this.items.get(listId)?.delete(itemKey) ?? false;
2778
+ }
2779
+ /** 整体清空某个列表(LIST_SET 整体换数据源时) */
2780
+ clearList(listId) {
2781
+ this.items.delete(listId);
2782
+ }
2783
+ /** 清空全部(卸载页面时) */
2784
+ clear() {
2785
+ this.items.clear();
2786
+ }
2787
+ get size() {
2788
+ let n = 0;
2789
+ for (const byKey of this.items.values()) n += byKey.size;
2790
+ return n;
2791
+ }
2792
+ get stats() {
2793
+ return { hits: this.hits, misses: this.misses, items: this.size };
2794
+ }
2795
+ };
2796
+
2797
+ // ../slot-runtime/src/runtime.ts
2798
+ var VaporRuntime = class {
2799
+ constructor(table, rt, evaluators, registry, onComponentProp, nodeIdOffset = 0, skipNodeIds, onPaintProp) {
2800
+ this.table = table;
2801
+ this.rt = rt;
2802
+ this.evaluators = evaluators;
2803
+ this.registry = registry;
2804
+ this.onComponentProp = onComponentProp;
2805
+ this.nodeIdOffset = nodeIdOffset;
2806
+ this.skipNodeIds = skipNodeIds;
2807
+ this.onPaintProp = onPaintProp;
2808
+ this.slots = /* @__PURE__ */ new Map();
2809
+ this.slotById = /* @__PURE__ */ new Map();
2810
+ this.evalImpls = /* @__PURE__ */ new Map();
2811
+ this.sourcesOfSlot = /* @__PURE__ */ new Map();
2812
+ /**
2813
+ * ★★未能实例化求值器的槽位(**诊断,不许静默**)
2814
+ *
2815
+ * 【为什么必须有(本仓实测)】表达式引用外层别名或含运算时编译器给 `expr` 形态,
2816
+ * 而 `expr` 只支持纯路径 ⇒ `impl` 为 undefined。首版**直接 continue** ⇒
2817
+ * 该槽位永不写、且无任何提示(静默不更新的典型)。
2818
+ */
2819
+ this.uninstantiatedSlots = [];
2820
+ /** ★行内槽位的按键值缓存(`slotId:key` → 上次值)——只发变化行 */
2821
+ /**
2822
+ * ★★行内值缓存(**免字符串拼接**,2026-09-29 优化)
2823
+ *
2824
+ * 【为什么改(本仓实测的瓶颈)】原实现每 (槽位 × 行) 都做一次
2825
+ * `` `${spec.slotId}:${key}` `` 字符串拼接 + Map 查存;1000 行 × 3 槽位 = 3000 次
2826
+ * ⇒ 在 `V11` 测得单行更新 **JS 侧 6.76ms**(扫描成本主导,而增量只有 1 条指令)。
2827
+ * ⇒ 改**嵌套 Map**(listId → slotId → key → value):键是数字/字符串原值,无拼接、无临时字符串。
2828
+ * ★正确性不变(同一 (listId, slotId, key) 三元组仍是唯一键)。
2829
+ */
2830
+ this.itemValueCache = /* @__PURE__ */ new Map();
2831
+ this.loaded = false;
2832
+ /* ── ★★P2-5(2026-10-03):v-once / v-memo 的运行时状态 ── */
2833
+ /**
2834
+ * v-once:已写过的槽位 id(**只写一次**——首次写入后永久冻结)。
2835
+ * ★为什么记在运行时(而不是把槽位从表里删掉):表是编译产物(只读、可序列化);
2836
+ * 而"写过没有"是**实例态**(页面重挂载 ⇒ 重新写一次,与官方"重挂载重新渲染"一致)。
2837
+ */
2838
+ this.onceWritten = /* @__PURE__ */ new Set();
2839
+ /** ★批次 30:每节点上次由动态 `:class` 施加的引擎字段(关掉类 ⇒ 需清除这些字段) */
2840
+ this.lastClassFields = /* @__PURE__ */ new Map();
2841
+ /** v-memo:各组的**依赖基线**(上一次比较时的值;缺省 = 还没建过基线 ⇒ 首帧必脏) */
2842
+ this.memoBaseline = /* @__PURE__ */ new Map();
2843
+ /**
2844
+ * v-memo:组在本**帧**已被判定为"依赖变了"的标记(值 = 帧号)。
2845
+ *
2846
+ * 【为什么按帧记(组语义的关键)】一帧里多个源变化可能触到同组多个槽位——
2847
+ * 第一次判定"变了"之后,**同帧其余槽位必须照常写**(组 = 子树整体更新);
2848
+ * 而"一次判定管一帧"的边界只有 `flush()`(见 `SlotRuntime.frameId`)。
2849
+ */
2850
+ this.memoDirtyFrame = /* @__PURE__ */ new Map();
2851
+ /* ── ★★★P3-3(2026-10-03):可见性变化日志(`<Transition>` 的**驱动源**)──
2852
+ *
2853
+ * 【为什么运行时记、桥来消费】`v-show` / `v-if` 的切换在运行时表现为 `visible` 槽位写入
2854
+ * ⇒ 编成 `TOGGLE_VIS` 指令(内核只管**应用**可见性,不知道"要不要过渡")。
2855
+ * 而"这个节点有没有过渡声明"是**编译产物**(`LayoutNode.transition`)、
2856
+ * "把动画交给谁播"是**宿主**(`proteusHost.animStart`)——桥正好两头都有。
2857
+ * ⇒ 运行时只负责**记事实**(谁、变成什么),桥负责**查声明 + 转发**(分层正确、可单测)。
2858
+ *
2859
+ * 【为什么要去重】`relink` 会重写全部槽位(含未变的可见性)⇒ 不去重会把"每次 relink"
2860
+ * 当成一次切换(过渡被反复触发)。⇒ 与上一状态比较,**只在真的翻转时记**。
2861
+ */
2862
+ this.visibilityLog = [];
2863
+ this.lastVisible = /* @__PURE__ */ new Map();
2864
+ }
2865
+ /**
2866
+ * 从订阅表重建求值函数(把**可序列化的声明**变成可执行函数)
2867
+ *
2868
+ * 【为什么单独一步(而不是直接吃函数)】方案 §4.4 要求产物可序列化
2869
+ * (跨端禁 eval)⇒ 声明与实现分离:声明随产物下发,实现在各端本地重建。
2870
+ * 本方法就是「本地重建」的参考实现:
2871
+ * · `member` —— 纯路径访问,**免解析**(热路径主力)
2872
+ * · `const` —— 常量
2873
+ * · `expr` —— 表达式文本(各端按自己的能力求值;本实现给出最简单的成员回退)
2874
+ */
2875
+ static buildEvaluators(specs) {
2876
+ const out = /* @__PURE__ */ new Map();
2877
+ for (const s of specs) {
2878
+ switch (s.form) {
2879
+ case "member": {
2880
+ const segs = (s.path ?? "").split(".").filter(Boolean);
2881
+ const [root, ...rest] = segs;
2882
+ out.set(s.evaluatorId, (ctx) => {
2883
+ let v = ctx.read(root);
2884
+ for (const seg of rest) {
2885
+ if (v == null || typeof v !== "object") return void 0;
2886
+ v = v[seg];
2887
+ }
2888
+ return v;
2889
+ });
2890
+ break;
2891
+ }
2892
+ case "program": {
2893
+ const prog = s.program;
2894
+ if (!prog) break;
2895
+ out.set(s.evaluatorId, (ctx) => evalExpr(prog, ctx));
2896
+ break;
2897
+ }
2898
+ case "const": {
2899
+ const text = (s.expr ?? "").trim();
2900
+ const parsed = /^-?\d+(\.\d+)?$/.test(text) ? Number(text) : text === "true" ? true : text === "false" ? false : text.replace(/^['"]|['"]$/g, "");
2901
+ out.set(s.evaluatorId, () => parsed);
2902
+ break;
2903
+ }
2904
+ case "expr": {
2905
+ const expr = (s.expr ?? "").trim();
2906
+ if (/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/.test(expr)) {
2907
+ const segs = expr.split(".");
2908
+ const [root, ...rest] = segs;
2909
+ out.set(s.evaluatorId, (ctx) => {
2910
+ let v = ctx.read(root);
2911
+ for (const seg of rest) {
2912
+ if (v == null || typeof v !== "object") return void 0;
2913
+ v = v[seg];
2914
+ }
2915
+ return v;
2916
+ });
2917
+ }
2918
+ break;
2919
+ }
2920
+ default:
2921
+ break;
2922
+ }
2923
+ }
2924
+ return out;
2925
+ }
2926
+ /** 注册订阅(方案 §4.3 Step 5:源变化 → 求值 → 直写槽位) */
2927
+ load(ctx, subscribe) {
2928
+ const unknownSources = [];
2929
+ const unsupportedEvaluators = [];
2930
+ let l1Slots = 0;
2931
+ for (const src of this.table.sources) {
2932
+ for (const spec of src.slots) {
2933
+ const impl = this.evaluators.get(spec.evaluatorId);
2934
+ if (!impl) {
2935
+ unsupportedEvaluators.push({ evaluatorId: spec.evaluatorId, reason: "\u6C42\u503C\u51FD\u6570\u672A\u5B9E\u4F8B\u5316\uFF08\u5F62\u6001\u4E0D\u652F\u6301\uFF09" });
2936
+ continue;
2937
+ }
2938
+ let slot = this.slots.get(spec.slotId);
2939
+ if (!slot) {
2940
+ slot = createSlot(
2941
+ {
2942
+ id: spec.slotId,
2943
+ // ★P1-3:子组件的 nodeId 统一加偏移(内核/宿主在**父树空间**里认节点)
2944
+ nodeId: spec.nodeId + this.nodeIdOffset,
2945
+ kind: spec.kind,
2946
+ keyId: this.rt.keys.intern(spec.propKey),
2947
+ listId: spec.listId ?? spec.slotId,
2948
+ listSlotId: spec.itemSlotId ?? spec.slotId,
2949
+ itemKind: spec.itemKind,
2950
+ itemValueField: spec.itemValueField,
2951
+ itemKeyField: spec.itemKeyField,
2952
+ scope: spec.scope
2953
+ },
2954
+ this.rt.keys,
2955
+ this.rt.strings,
2956
+ void 0,
2957
+ this.registry
2958
+ // ★传入注册表(缺省 ⇒ list-item 回退 LIST_UPDATE,保持 V3 行为)
2959
+ );
2960
+ this.slots.set(spec.slotId, slot);
2961
+ }
2962
+ this.slotById.set(spec.slotId, { slot, spec, evalId: spec.evaluatorId });
2963
+ const deps = this.sourcesOfSlot.get(spec.slotId) ?? [];
2964
+ deps.push(src.sourceName);
2965
+ this.sourcesOfSlot.set(spec.slotId, deps);
2966
+ l1Slots++;
2967
+ }
2968
+ }
2969
+ for (const src of this.table.sources) {
2970
+ subscribe(src.sourceName, () => {
2971
+ this.writeSlotsOfSource(src.sourceName, ctx);
2972
+ });
2973
+ }
2974
+ for (const src of this.table.sources) {
2975
+ for (const spec of src.slots) {
2976
+ if (spec.kind !== "list-item") continue;
2977
+ if (!this.evaluators.get(spec.evaluatorId)) {
2978
+ this.uninstantiatedSlots.push({ slotId: spec.slotId, evaluatorId: spec.evaluatorId, propKey: spec.propKey });
2979
+ }
2980
+ }
2981
+ }
2982
+ this.loaded = true;
2983
+ return {
2984
+ l1Slots,
2985
+ l0Slots: this.table.l0Slots.length,
2986
+ unknownSources,
2987
+ unsupportedEvaluators,
2988
+ // ★行内槽位的未实例化清单(与 unsupportedEvaluators 同性质:**上报而非静默**)
2989
+ uninstantiatedSlots: this.uninstantiatedSlots.slice()
2990
+ };
2991
+ }
2992
+ /** 写入某源驱动的全部槽位(源变化时由订阅回调触发) */
2993
+ writeSlotsOfSource(sourceName, ctx) {
2994
+ for (const src of this.table.sources) {
2995
+ if (src.sourceName !== sourceName) continue;
2996
+ this.writeListItems(src, ctx);
2997
+ for (const spec of src.slots) {
2998
+ if (spec.kind === "list-item") continue;
2999
+ if (spec.kind === "list-data") continue;
3000
+ if (spec.once && this.onceWritten.has(spec.slotId)) continue;
3001
+ if (this.skipNodeIds?.has(spec.nodeId + this.nodeIdOffset)) continue;
3002
+ if (spec.propKey === "paint.class" && this.onPaintProp) {
3003
+ const implC = this.evaluators.get(spec.evaluatorId);
3004
+ if (implC) {
3005
+ const cv = implC(ctx);
3006
+ const plan = this.table.classPlans?.[String(spec.nodeId)];
3007
+ let fields;
3008
+ if (plan) {
3009
+ fields = {};
3010
+ applyDynamicClassPlan(cv, plan, fields);
3011
+ } else {
3012
+ fields = resolveDynamicClasses(cv, this.table.classRules ?? []);
3013
+ }
3014
+ const nid = spec.nodeId + this.nodeIdOffset;
3015
+ const nextKeys = new Set(Object.keys(fields));
3016
+ const prev = this.lastClassFields.get(nid);
3017
+ if (prev) {
3018
+ for (const k of prev) if (!nextKeys.has(k)) this.onPaintProp(nid, `paint.${k}`, void 0);
3019
+ }
3020
+ for (const [fk, fv] of Object.entries(fields)) this.onPaintProp(nid, `paint.${fk}`, fv);
3021
+ this.lastClassFields.set(nid, nextKeys);
3022
+ const eC = this.slotById.get(spec.slotId);
3023
+ if (eC) eC.slot.value = cv;
3024
+ }
3025
+ continue;
3026
+ }
3027
+ if (spec.propKey.startsWith("paint.") && this.onPaintProp) {
3028
+ const impl2 = this.evaluators.get(spec.evaluatorId);
3029
+ if (impl2) {
3030
+ const v = impl2(ctx);
3031
+ this.onPaintProp(spec.nodeId + this.nodeIdOffset, spec.propKey, v);
3032
+ const e2 = this.slotById.get(spec.slotId);
3033
+ if (e2) e2.slot.value = v;
3034
+ }
3035
+ continue;
3036
+ }
3037
+ if (spec.memoId !== void 0 && !this.memoGroupDirty(spec.memoId, ctx)) continue;
3038
+ const entry = this.slotById.get(spec.slotId);
3039
+ const impl = this.evaluators.get(spec.evaluatorId);
3040
+ if (!entry || !impl) continue;
3041
+ const value = impl(ctx);
3042
+ if (spec.kind === "component-prop" && this.onComponentProp) {
3043
+ const propName = spec.propKey.startsWith("component.") ? spec.propKey.slice("component.".length) : spec.propKey;
3044
+ this.onComponentProp(spec.nodeId, propName, value);
3045
+ entry.slot.value = value;
3046
+ continue;
3047
+ }
3048
+ this.rt.setSlot(entry.slot, value);
3049
+ if (spec.once) this.onceWritten.add(spec.slotId);
3050
+ if (spec.kind === "visibility") {
3051
+ const now = Boolean(value);
3052
+ const prev = this.lastVisible.get(spec.nodeId);
3053
+ if (prev !== void 0 && prev !== now) {
3054
+ this.visibilityLog.push({ nodeId: spec.nodeId, visible: now });
3055
+ }
3056
+ this.lastVisible.set(spec.nodeId, now);
3057
+ }
3058
+ }
3059
+ }
3060
+ }
3061
+ /**
3062
+ * ★★**v-memo 组脏判定**(P2-5):依赖逐项比较(`Object.is`),任一变化即"脏"。
3063
+ *
3064
+ * 【语义(对齐 Vue `withMemo`)】依赖未变 ⇒ **跳过**该子树更新;变了 ⇒ 照常更新,
3065
+ * 并把本次依赖值记为基线供下轮比较。
3066
+ * 【诚实边界】组定义缺失(产物异常)⇒ 返回 true(照常更新)——**不静默冻结**:
3067
+ * "少一层优化"可接受,"该更新的不更新"不可接受。
3068
+ */
3069
+ memoGroupDirty(memoId, ctx) {
3070
+ const fid = this.rt.frameId;
3071
+ if (this.memoDirtyFrame.get(memoId) === fid) return true;
3072
+ const group = this.table.memoGroups?.find((g) => g.memoId === memoId);
3073
+ if (!group) return true;
3074
+ const now = [];
3075
+ for (const prog of group.deps) {
3076
+ try {
3077
+ now.push(evalExpr(prog, ctx));
3078
+ } catch {
3079
+ now.push(void 0);
3080
+ }
3081
+ }
3082
+ const prev = this.memoBaseline.get(memoId);
3083
+ const changed = !prev || now.some((v, i) => !Object.is(v, prev[i]));
3084
+ if (changed) {
3085
+ this.memoBaseline.set(memoId, now);
3086
+ this.memoDirtyFrame.set(memoId, fid);
3087
+ }
3088
+ return changed;
3089
+ }
3090
+ /**
3091
+ * ★列表行内槽位通道:按行求值 → 与上一轮**按 key 缓存**的值 diff → 只发变化行
3092
+ *
3093
+ * 【关键:行作用域求值】`item.w` 的求值需要**当前行**绑定到 v-for 作用域。
3094
+ * 故每行构造一个 `rowCtx`:`read(scope)` 返回该行,其余名透传原 ctx。
3095
+ * (编译器在槽位上给了 `scope` 才能这么做——见 SlotSubscription.scope)
3096
+ */
3097
+ writeListItems(src, ctx) {
3098
+ const itemSlots = src.slots.filter((x) => x.kind === "list-item");
3099
+ if (itemSlots.length === 0) return;
3100
+ const rowsCache = /* @__PURE__ */ new Map();
3101
+ const rowsOfList2 = (listId, spec) => {
3102
+ const cached = rowsCache.get(listId);
3103
+ if (cached) return cached;
3104
+ const out = [];
3105
+ const keyOf2 = (r, i) => spec.itemKeyField && r && r[spec.itemKeyField] !== void 0 ? String(r[spec.itemKeyField]) : String(i);
3106
+ const segs = (spec.sourceExpr ?? "").split(".").filter(Boolean);
3107
+ const topRows = ctx.read(src.sourceName);
3108
+ if (!Array.isArray(topRows)) return out;
3109
+ const walkSegs = segs[0] === src.sourceName ? segs.slice(1) : segs;
3110
+ let current = topRows.map((row) => ({ row, ancestors: [] }));
3111
+ for (let seg = 0; seg < walkSegs.length; seg++) {
3112
+ const field = walkSegs[seg];
3113
+ const next = [];
3114
+ for (const c of current) {
3115
+ const arr = c.row?.[field];
3116
+ if (!Array.isArray(arr)) continue;
3117
+ for (const x of arr) {
3118
+ next.push({ row: x, ancestors: [...c.ancestors, c.row] });
3119
+ }
3120
+ }
3121
+ current = next;
3122
+ }
3123
+ for (let i = 0; i < current.length; i++) {
3124
+ out.push({ key: keyOf2(current[i].row, i), row: current[i].row, ancestors: current[i].ancestors });
3125
+ }
3126
+ rowsCache.set(listId, out);
3127
+ return out;
3128
+ };
3129
+ const rowCtxCache = /* @__PURE__ */ new Map();
3130
+ for (const spec of itemSlots) {
3131
+ const impl = this.evaluators.get(spec.evaluatorId);
3132
+ if (!impl) {
3133
+ this.uninstantiatedSlots.push({ slotId: spec.slotId, evaluatorId: spec.evaluatorId, propKey: spec.propKey });
3134
+ continue;
3135
+ }
3136
+ const scope = spec.scope ?? "";
3137
+ const rows = rowsOfList2(spec.listId ?? -1, spec);
3138
+ for (const rowRef of rows) {
3139
+ const key = rowRef.key;
3140
+ const row = rowRef.row;
3141
+ const ancestors = rowRef.ancestors;
3142
+ const cached = rowCtxCache.get(rowRef);
3143
+ const rowCtx = cached && cached.scope === scope ? cached.ctx : this.makeRowCtx(scope, row, ancestors, ctx);
3144
+ if (!(cached && cached.scope === scope)) rowCtxCache.set(rowRef, { scope, ctx: rowCtx });
3145
+ const value = impl(rowCtx);
3146
+ this.diffAndEmit(spec, key, value);
3147
+ }
3148
+ }
3149
+ }
3150
+ /**
3151
+ * ★★行级失效:**只重算指定行**的 list-item 槽位(2026-09-29 新增)
3152
+ *
3153
+ * 【为什么需要(本仓真机实测的瓶颈)】粗粒度触发(`triggers.get('list')` ⇒ `relink`)只知道
3154
+ * "源变了"、不知道"哪一行变了" ⇒ 只能**全表重扫**:1000 行 × 3 槽位 = 3000 次求值 + diff。
3155
+ * 真机 `V11` 实测该段 **7.03ms**(列表更新总 11ms)——而 Vapor 的设计本意是
3156
+ * **O(1) 槽位直写**(改哪行算哪行)。本方法就是那个 O(1) 入口。
3157
+ *
3158
+ * 【调用方责任】提供**变更行的身份**(`key` + 行对象 + 祖先链)——它天然知道(编译器产出的
3159
+ * 行作用域效应 / 应用层的不可变更新都携带这些)。**不知道时不要调用**,走 `relink` 全扫(正确但慢)。
3160
+ *
3161
+ * 【正确性】与全扫路径**共用** `makeRowCtx` / `diffAndEmit` / `itemValueCache`(唯一实现
3162
+ * ⇒ 两条路径不会漂移);缓存是实例级的 ⇒ 全扫与行级失效交替调用也保持一致。
3163
+ *
3164
+ * @param listId 目标列表 id(订阅表里的 `listId`)
3165
+ * @param key 行标识(= `itemKeyField` 对应的值,与 `emitListItem` 的键一致)
3166
+ * @param row 该行的**数据对象**
3167
+ * @param ancestors 该行的祖先行链(顶层列表传空数组;嵌套列表按「自外向内」)
3168
+ */
3169
+ relinkRow(listId, key, row, ancestors = [], ctx) {
3170
+ const evalCtx = ctx ?? this.lastCtx ?? { read: () => void 0 };
3171
+ for (const spec of this.listSpecsOf(listId)) {
3172
+ const impl = this.evaluators.get(spec.evaluatorId);
3173
+ if (!impl) continue;
3174
+ const rowCtx = this.makeRowCtx(spec.scope ?? "", row, ancestors, evalCtx);
3175
+ this.diffAndEmit(spec, key, impl(rowCtx));
3176
+ }
3177
+ }
3178
+ /** scope(v-for 别名)→ listId 反查(`relinkRow` 用;作用域由订阅表声明,不猜) */
3179
+ listIdOfScope(scope) {
3180
+ for (const src of this.table.sources) {
3181
+ for (const sl of src.slots) if (sl.kind === "list-item" && (sl.scope ?? "") === scope) return sl.listId ?? -1;
3182
+ }
3183
+ return void 0;
3184
+ }
3185
+ /** 该列表的全部 list-item 槽位(`relinkRow` 用;与 `writeListItems` 同一数据源) */
3186
+ listSpecsOf(listId) {
3187
+ const out = [];
3188
+ for (const src of this.table.sources) {
3189
+ for (const sl of src.slots) if (sl.kind === "list-item" && (sl.listId ?? -1) === listId) out.push(sl);
3190
+ }
3191
+ return out;
3192
+ }
3193
+ /** ★行作用域上下文(**唯一实现**):当前行别名 + 各层祖先别名 —— `writeListItems` 与 `relinkRow` 共用 */
3194
+ makeRowCtx(scope, row, ancestors, ctx) {
3195
+ if (!scope) return ctx;
3196
+ const lid = this.listIdOfScope(scope);
3197
+ const ancestorScopes = lid === void 0 ? [] : this.ancestorScopesOf(lid);
3198
+ return makeScopedRead(scope, row, ancestors, ancestorScopes, ctx);
3199
+ }
3200
+ /** ★值 diff + 发射(**唯一实现**):嵌套 Map 免拼接:listId/slotId 均为数字键 */
3201
+ diffAndEmit(spec, key, value) {
3202
+ const listId = spec.listId ?? -1;
3203
+ let bySlot = this.itemValueCache.get(listId);
3204
+ if (!bySlot) {
3205
+ bySlot = /* @__PURE__ */ new Map();
3206
+ this.itemValueCache.set(listId, bySlot);
3207
+ }
3208
+ let byKey = bySlot.get(spec.slotId);
3209
+ if (!byKey) {
3210
+ byKey = /* @__PURE__ */ new Map();
3211
+ bySlot.set(spec.slotId, byKey);
3212
+ }
3213
+ if (byKey.get(key) === value) return;
3214
+ byKey.set(key, value);
3215
+ this.emitListItem(spec, key, value);
3216
+ }
3217
+ /** 发一条行内更新指令:解析得到 nodeId 就发普通指令,否则回退 LIST_UPDATE */
3218
+ emitListItem(spec, key, value) {
3219
+ const nodeId = this.registry?.resolveNode(spec.listId ?? -1, key, spec.itemSlotId ?? -1);
3220
+ if (nodeId !== void 0 && this.skipNodeIds?.has(nodeId)) return;
3221
+ if (nodeId !== void 0) {
3222
+ if ((spec.itemKind ?? "style") === "text") {
3223
+ this.rt.buffer.push({ op: 3 /* SET_TEXT */, nodeId, textRef: this.rt.strings.intern(String(value)) });
3224
+ } else {
3225
+ this.rt.buffer.push({
3226
+ op: 2 /* SET_STYLE */,
3227
+ nodeId,
3228
+ keyId: this.rt.keys.intern(spec.propKey),
3229
+ value: typeof value === "number" ? value : Number(value) || 0
3230
+ });
3231
+ }
3232
+ } else {
3233
+ this.rt.buffer.push({
3234
+ op: 34 /* LIST_UPDATE */,
3235
+ listId: spec.listId ?? -1,
3236
+ itemKeyRef: this.rt.strings.intern(key),
3237
+ slotId: spec.itemSlotId ?? -1,
3238
+ value: typeof value === "number" ? value : Number(value) || 0
3239
+ });
3240
+ }
3241
+ }
3242
+ /** ★首帧同步:把所有 L1 槽位按当前源值写一遍(否则首屏不会出现这些值) */
3243
+ relink(ctx) {
3244
+ this.lastCtx = ctx;
3245
+ for (const src of this.table.sources) {
3246
+ this.writeSlotsOfSource(src.sourceName, ctx);
3247
+ }
3248
+ }
3249
+ /**
3250
+ * 某列表的**别名链**(自外向内;末位是它自身的 scope)
3251
+ *
3252
+ * 【为什么需要】`rowCtx` 要按位置把「祖先行」绑给对应的外层别名——
3253
+ * 而位置对应关系依赖"自外向内"的稳定顺序(由 `parentListId` 逐级上溯构造)。
3254
+ */
3255
+ ancestorScopesOf(listId) {
3256
+ const chain = [];
3257
+ let cur = listId;
3258
+ const allSlots = this.table.sources.flatMap((x) => x.slots);
3259
+ let guard = 0;
3260
+ while (cur !== void 0 && guard < 32) {
3261
+ const spec = allSlots.find((x) => x.kind === "list-item" && x.listId === cur);
3262
+ if (!spec) break;
3263
+ chain.unshift(spec.scope ?? "");
3264
+ cur = spec.parentListId;
3265
+ guard++;
3266
+ }
3267
+ return chain;
3268
+ }
3269
+ /** 取某源的行数组(列表源 ⇒ 数组;非数组返回空)——仅供「无 :key 时用下标兜底」 */
3270
+ rowsOfSource(sourceName, ctx) {
3271
+ const v = ctx.read(sourceName);
3272
+ return Array.isArray(v) ? v : [];
3273
+ }
3274
+ /**
3275
+ * ★★★**取走可见性变化**(P3-3;取走即复位——与 `takePatches` 同款"自上次取走以来"语义)。
3276
+ *
3277
+ * 桥侧用法:`relink`/手势回调后 drain ⇒ 对每个变化查模板的 `transition` 声明 ⇒
3278
+ * 有声明则调 `proteusHost.animStart(...)`(宿主把动画交给内核)。
3279
+ */
3280
+ takeVisibilityChanges() {
3281
+ if (this.visibilityLog.length === 0) return [];
3282
+ const out = this.visibilityLog.slice();
3283
+ this.visibilityLog.length = 0;
3284
+ return out;
3285
+ }
3286
+ /** 某槽位是否已建立订阅(诊断:确认"这个槽位真的被接管了") */
3287
+ hasSlot(slotId) {
3288
+ return this.slots.has(slotId);
3289
+ }
3290
+ get loaded_() {
3291
+ return this.loaded;
3292
+ }
3293
+ /** 该槽位由哪些源驱动(诊断 / explain) */
3294
+ depsOf(slotId) {
3295
+ return this.sourcesOfSlot.get(slotId) ?? [];
3296
+ }
3297
+ /** L1 槽位表(供宿主对账:哪些槽位由 L1 接管) */
3298
+ slotIds() {
3299
+ return [...this.slots.keys()].sort((a, b) => a - b);
3300
+ }
3301
+ };
3302
+ function makeScopedRead(scope, row, ancestors, ancestorScopes, ctx) {
3303
+ if (!scope) return ctx;
3304
+ return {
3305
+ read: (n) => {
3306
+ if (n === scope) return row;
3307
+ const idx = ancestorScopes.indexOf(n);
3308
+ if (idx >= 0 && idx < ancestors.length) return ancestors[idx];
3309
+ return ctx.read(n);
3310
+ }
3311
+ };
3312
+ }
3313
+
3314
+ // ../slot-runtime/src/instantiate.ts
3315
+ function applyStyleField(target, propKey, key, v, classRules, classPlan) {
3316
+ if (propKey === "paint.class") {
3317
+ let fields;
3318
+ if (classPlan) {
3319
+ fields = {};
3320
+ applyDynamicClassPlan(v, classPlan, fields);
3321
+ } else {
3322
+ fields = resolveDynamicClasses(v, classRules ?? []);
3323
+ }
3324
+ for (const [k, val] of Object.entries(fields)) target[k] = val;
3325
+ return;
3326
+ }
3327
+ ;
3328
+ target[key] = v;
3329
+ }
3330
+ function setNormText(obj, value) {
3331
+ const t = value === void 0 || value === null ? "" : String(value);
3332
+ obj["text"] = normalizeWhiteSpace(t, obj["whiteSpace"]);
3333
+ }
3334
+ function normalizeWhiteSpace(text, ws) {
3335
+ switch (ws) {
3336
+ case "pre":
3337
+ case "pre-wrap":
3338
+ return text;
3339
+ // 原样保留
3340
+ case "pre-line":
3341
+ return text.replace(/[ \t\r\f\v]+/g, " ").split("\n").map((l) => l.trim()).join("\n");
3342
+ default:
3343
+ return text.replace(/\s+/g, " ").trim();
3344
+ }
3345
+ }
3346
+ function engineFieldOf(propKey) {
3347
+ if (propKey === "text.content") return { kind: "text" };
3348
+ const m = propKey.match(/^(?:layout|paint|text)\.(.+)$/);
3349
+ if (!m) return null;
3350
+ return { kind: "style", key: m[1] };
3351
+ }
3352
+ function evalTextSegments(segs, read) {
3353
+ let out = "";
3354
+ for (const s of segs) {
3355
+ if ("text" in s) {
3356
+ out += s.text;
3357
+ continue;
3358
+ }
3359
+ try {
3360
+ const v = evalExpr(s.expr, { read });
3361
+ out += v === void 0 || v === null ? "" : String(v);
3362
+ } catch {
3363
+ }
3364
+ }
3365
+ return out;
3366
+ }
3367
+ function rowsOfList(listId, meta, table, read) {
3368
+ if (!table) return [];
3369
+ const allSlots = table.sources.flatMap((s) => s.slots);
3370
+ const itemSlots = allSlots.filter((x) => x.kind === "list-item" && x.listId === listId);
3371
+ const spec = itemSlots[0] ?? allSlots.find((x) => x.kind === "list-data" && x.listId === listId);
3372
+ if (!spec) return [];
3373
+ const srcName = table.sources.find((s) => s.slots.some((x) => x.listId === listId))?.sourceName ?? "";
3374
+ const segs = (meta?.sourceField || spec.sourceExpr || "").split(".").filter(Boolean);
3375
+ const topRows = read(srcName);
3376
+ if (!Array.isArray(topRows)) return [];
3377
+ const walkSegs = segs[0] === srcName ? segs.slice(1) : segs;
3378
+ let cur = topRows;
3379
+ for (const field of walkSegs) {
3380
+ const next = [];
3381
+ for (const r of cur) {
3382
+ const arr = r?.[field];
3383
+ if (!Array.isArray(arr)) continue;
3384
+ for (const x of arr) next.push(x);
3385
+ }
3386
+ cur = next;
3387
+ }
3388
+ return cur;
3389
+ }
3390
+ function instantiateTemplate(tpl, opts) {
3391
+ const nodes = [];
3392
+ const idOffset = opts.idOffset ?? 0;
3393
+ const instNotes = [];
3394
+ const maxTemplateId = tpl.nodes.reduce((m, n) => Math.max(m, n.id), 0);
3395
+ let nextId = opts.firstRowInstanceId ?? maxTemplateId + 1;
3396
+ let allocated = 0;
3397
+ let reused = 0;
3398
+ const rowLists = new Map(tpl.lists.map((l) => [l.listId, l]));
3399
+ const byId = /* @__PURE__ */ new Map();
3400
+ let valuesFilled = 0;
3401
+ const evaluators = opts.table ? VaporRuntime.buildEvaluators(opts.table.evaluators) : /* @__PURE__ */ new Map();
3402
+ const ancestorScopesOf = (listId) => {
3403
+ const chain = [];
3404
+ let cur = listId;
3405
+ let guard = 0;
3406
+ while (cur !== void 0 && guard < 32) {
3407
+ const meta = rowLists.get(cur);
3408
+ if (!meta) break;
3409
+ chain.unshift(meta.scope ?? "");
3410
+ cur = meta.parentListId;
3411
+ guard++;
3412
+ }
3413
+ return chain;
3414
+ };
3415
+ const evalInitial = (evaluatorId, ctx) => {
3416
+ const impl = evaluators.get(evaluatorId);
3417
+ if (!impl) return { ok: false };
3418
+ try {
3419
+ return { ok: true, value: impl(ctx) };
3420
+ } catch {
3421
+ return { ok: false };
3422
+ }
3423
+ };
3424
+ const virtualRows = [];
3425
+ const emit = (n, id, parentId, ctx = { read: opts.read }) => {
3426
+ const out = { id, parentId };
3427
+ if (n.style) for (const [k, v] of Object.entries(n.style)) {
3428
+ ;
3429
+ out[k] = v;
3430
+ }
3431
+ if (n.text !== void 0) setNormText(out, n.text);
3432
+ if (n.textSegments && n.textSegments.length > 0) {
3433
+ setNormText(out, evalTextSegments(n.textSegments, ctx.read));
3434
+ }
3435
+ if (n.tag) out.tag = n.tag;
3436
+ if (n.component) out.component = n.component;
3437
+ if (n.slotOutlet) out.slotOutlet = n.slotOutlet;
3438
+ if (n.slotFor) out.slotFor = n.slotFor;
3439
+ out.id = id + idOffset;
3440
+ out.parentId = parentId === null ? null : parentId + idOffset;
3441
+ nodes.push(out);
3442
+ byId.set(id, out);
3443
+ };
3444
+ const cloneRow = (listId, row, itemKey, first, rowIndex, parentOverrideId, collect, ancestors = []) => {
3445
+ const meta = rowLists.get(listId);
3446
+ const idMap = /* @__PURE__ */ new Map();
3447
+ let rowRootId = 0;
3448
+ const myIds = [];
3449
+ for (const tplId of meta.subtreeIds) {
3450
+ const engineId = first ? tplId : nextId++;
3451
+ if (!first) allocated++;
3452
+ else reused++;
3453
+ idMap.set(tplId, engineId);
3454
+ if (tplId === meta.rowRootId) rowRootId = engineId;
3455
+ }
3456
+ const rowRead = makeScopedRead(meta.scope ?? "", row, ancestors, ancestorScopesOf(listId), { read: opts.read });
3457
+ for (const tplId of meta.subtreeIds) {
3458
+ const tn = tpl.nodes.find((x) => x.id === tplId);
3459
+ const engineId = idMap.get(tplId);
3460
+ const tplParent = tn.parentId;
3461
+ let parentId = tplParent === null ? null : idMap.get(tplParent) ?? tplParent;
3462
+ if (parentOverrideId !== void 0 && tplId === meta.rowRootId) parentId = parentOverrideId;
3463
+ emit(tn, engineId, parentId, rowRead);
3464
+ myIds.push(engineId);
3465
+ }
3466
+ for (const inner of tpl.lists) {
3467
+ if (inner.parentListId !== listId) continue;
3468
+ let arr = row;
3469
+ for (const seg of (inner.sourceField ?? "").split(".").filter(Boolean)) {
3470
+ arr = arr?.[seg];
3471
+ }
3472
+ if (!Array.isArray(arr)) continue;
3473
+ const innerKeyField = opts.table?.sources.flatMap((s) => s.slots).find((x) => x.kind === "list-item" && x.listId === inner.listId)?.itemKeyField;
3474
+ for (let j = 0; j < arr.length; j++) {
3475
+ const innerRow = arr[j];
3476
+ const innerKey = innerKeyField && innerRow?.[innerKeyField] !== void 0 ? String(innerRow[innerKeyField]) : String(j);
3477
+ cloneRow(inner.listId, innerRow, innerKey, first && j === 0, j, rowRootId, myIds, [...ancestors, row]);
3478
+ }
3479
+ }
3480
+ virtualRows.push({
3481
+ index: rowIndex,
3482
+ key: itemKey,
3483
+ root: rowRootId + idOffset,
3484
+ ids: myIds.map((x) => x + idOffset)
3485
+ });
3486
+ if (collect) collect.push(...myIds);
3487
+ if (opts.table) {
3488
+ const itemSlots = opts.table.sources.flatMap((s) => s.slots).filter((x) => x.kind === "list-item" && x.listId === listId);
3489
+ if (itemSlots.length > 0) {
3490
+ const slotNodes = {};
3491
+ const rowCtx = makeScopedRead(meta.scope ?? "", row, ancestors, ancestorScopesOf(listId), {
3492
+ read: opts.read
3493
+ });
3494
+ for (const sl of itemSlots) {
3495
+ const mapped = idMap.get(sl.nodeId);
3496
+ if (mapped !== void 0) slotNodes[sl.itemSlotId] = mapped;
3497
+ if (mapped === void 0) continue;
3498
+ const target = byId.get(mapped);
3499
+ if (!target) continue;
3500
+ const f = engineFieldOf(sl.propKey);
3501
+ if (!f) continue;
3502
+ const ev0 = evalInitial(sl.evaluatorId, rowCtx);
3503
+ let v;
3504
+ if (ev0.ok) {
3505
+ v = ev0.value;
3506
+ } else {
3507
+ const field = sl.itemValueField;
3508
+ if (!field) continue;
3509
+ v = row[field];
3510
+ if (v === void 0) continue;
3511
+ }
3512
+ if (f.kind === "text") {
3513
+ setNormText(target, v);
3514
+ } else {
3515
+ applyStyleField(target, sl.propKey, f.key, v, opts.table?.classRules, opts.table?.classPlans?.[String(sl.nodeId)]);
3516
+ }
3517
+ valuesFilled++;
3518
+ }
3519
+ if (opts.registry && Object.keys(slotNodes).length > 0) {
3520
+ const shifted = {};
3521
+ for (const [k, v] of Object.entries(slotNodes)) shifted[Number(k)] = v + idOffset;
3522
+ opts.registry.registerItem(listId, itemKey, shifted);
3523
+ }
3524
+ }
3525
+ }
3526
+ return rowRootId;
3527
+ };
3528
+ const rowMemberIds = /* @__PURE__ */ new Set();
3529
+ for (const l of tpl.lists) for (const id of l.subtreeIds) rowMemberIds.add(id);
3530
+ for (const n of tpl.nodes) {
3531
+ if (rowMemberIds.has(n.id)) {
3532
+ const meta = tpl.lists.find((l) => l.rowRootId === n.id);
3533
+ if (!meta) continue;
3534
+ if (meta.parentListId !== void 0) continue;
3535
+ const rows = rowsOfList(meta.listId, meta, opts.table, opts.read);
3536
+ for (let i = 0; i < rows.length; i++) {
3537
+ const row = rows[i];
3538
+ const keyOf2 = () => {
3539
+ const keyField = opts.table?.sources.flatMap((s) => s.slots).find((x) => x.kind === "list-item" && x.listId === meta.listId)?.itemKeyField;
3540
+ return keyField && row[keyField] !== void 0 ? String(row[keyField]) : String(i);
3541
+ };
3542
+ cloneRow(meta.listId, row, keyOf2(), i === 0, i);
3543
+ }
3544
+ continue;
3545
+ }
3546
+ emit(n, n.id, n.parentId);
3547
+ }
3548
+ if (opts.table) {
3549
+ for (const sl of opts.table.constantSlots ?? []) {
3550
+ const target = byId.get(sl.nodeId);
3551
+ if (!target) continue;
3552
+ const f = engineFieldOf(sl.propKey);
3553
+ if (!f) continue;
3554
+ const evS = evalInitial(sl.evaluatorId, { read: opts.read });
3555
+ if (!evS.ok) continue;
3556
+ const v = evS.value;
3557
+ if (f.kind === "text") {
3558
+ setNormText(target, v);
3559
+ } else {
3560
+ applyStyleField(target, sl.propKey, f.key, v, opts.table?.classRules, opts.table?.classPlans?.[String(sl.nodeId)]);
3561
+ }
3562
+ valuesFilled++;
3563
+ }
3564
+ for (const src of opts.table.sources) {
3565
+ for (const sl of src.slots) {
3566
+ if (sl.kind === "list-item" || sl.kind === "list-data" || sl.kind === "component-prop") continue;
3567
+ const target = byId.get(sl.nodeId);
3568
+ if (!target) continue;
3569
+ const f = engineFieldOf(sl.propKey);
3570
+ if (!f) continue;
3571
+ const evS = evalInitial(sl.evaluatorId, { read: opts.read });
3572
+ let v;
3573
+ if (evS.ok) {
3574
+ v = evS.value;
3575
+ } else {
3576
+ v = opts.read(src.sourceName);
3577
+ if (v === void 0) continue;
3578
+ }
3579
+ if (f.kind === "text") {
3580
+ setNormText(target, v);
3581
+ } else {
3582
+ applyStyleField(target, sl.propKey, f.key, v, opts.table?.classRules, opts.table?.classPlans?.[String(sl.nodeId)]);
3583
+ }
3584
+ valuesFilled++;
3585
+ }
3586
+ }
3587
+ }
3588
+ const componentMounts = [];
3589
+ const slotMounts = [];
3590
+ const droppedNodeIds = /* @__PURE__ */ new Set();
3591
+ let componentNodes = 0;
3592
+ const subtreeOf = (list, rootId) => {
3593
+ const doomed = /* @__PURE__ */ new Set([rootId]);
3594
+ let grew = true;
3595
+ while (grew) {
3596
+ grew = false;
3597
+ for (const x of list) {
3598
+ if (x.parentId !== null && !doomed.has(x.id) && doomed.has(x.parentId)) {
3599
+ doomed.add(x.id);
3600
+ grew = true;
3601
+ }
3602
+ }
3603
+ }
3604
+ return doomed;
3605
+ };
3606
+ const dissolveOutlets = (list, fills, evalProps) => {
3607
+ const consumed = /* @__PURE__ */ new Set();
3608
+ const placed = [];
3609
+ for (const outlet of list.filter((x) => x.slotOutlet)) {
3610
+ if (list.indexOf(outlet) < 0) continue;
3611
+ const name = outlet.slotOutlet.name;
3612
+ const fill = fills.get(name);
3613
+ const useFill = fill !== void 0 && fill.length > 0 && !consumed.has(name);
3614
+ if (useFill) {
3615
+ consumed.add(name);
3616
+ const outletProps = evalProps ? evalProps(outlet) : {};
3617
+ const doomed = subtreeOf(list, outlet.id);
3618
+ for (const id of doomed) droppedNodeIds.add(id);
3619
+ const idx = list.findIndex((x) => x.id === outlet.id);
3620
+ const kept = list.filter((x) => !doomed.has(x.id));
3621
+ kept.splice(Math.min(idx, kept.length), 0, ...fill);
3622
+ list.length = 0;
3623
+ list.push(...kept);
3624
+ for (const r of fill) {
3625
+ r.parentId = outlet.parentId;
3626
+ delete r.slotFor;
3627
+ }
3628
+ slotMounts.push({ outletNodeId: outlet.id, name, filled: true, contentIds: fill.map((x) => x.id), fallbackIds: [] });
3629
+ placed.push({ name, fill, outlet, props: outletProps });
3630
+ } else {
3631
+ const kids = list.filter((x) => x.parentId === outlet.id);
3632
+ const fallbackIds = [];
3633
+ if (kids.length > 0) {
3634
+ for (const k of kids) k.parentId = outlet.parentId;
3635
+ fallbackIds.push(...kids.map((x) => x.id));
3636
+ list.splice(list.indexOf(outlet), 1);
3637
+ droppedNodeIds.add(outlet.id);
3638
+ } else if (typeof outlet.text === "string" && outlet.text !== "") {
3639
+ delete outlet.slotOutlet;
3640
+ outlet.tag = "p-text";
3641
+ } else {
3642
+ list.splice(list.indexOf(outlet), 1);
3643
+ droppedNodeIds.add(outlet.id);
3644
+ }
3645
+ slotMounts.push({ outletNodeId: outlet.id, name, filled: false, contentIds: [], fallbackIds });
3646
+ }
3647
+ }
3648
+ return { consumed, placed };
3649
+ };
3650
+ if (opts.table?.componentIs && opts.table.componentIs.length > 0) {
3651
+ for (const ci of opts.table.componentIs) {
3652
+ const target = byId.get(ci.nodeId);
3653
+ if (!target) continue;
3654
+ const impl = evaluators.get(ci.evaluatorId);
3655
+ if (!impl) {
3656
+ instNotes.push(`\u52A8\u6001\u7EC4\u4EF6 \`:is="${ci.expr}"\`\uFF08\u8282\u70B9 ${ci.nodeId}\uFF09\u6C42\u503C\u5668\u672A\u5B9E\u4F8B\u5316 \u21D2 \u672A\u89E3\u6790`);
3657
+ continue;
3658
+ }
3659
+ let name;
3660
+ try {
3661
+ name = impl({ read: opts.read });
3662
+ } catch (e) {
3663
+ instNotes.push(`\u52A8\u6001\u7EC4\u4EF6 \`:is="${ci.expr}"\` \u6C42\u503C\u629B\u9519\uFF08${String(e?.message ?? e)}\uFF09\u21D2 \u672A\u89E3\u6790`);
3664
+ continue;
3665
+ }
3666
+ if (name === void 0 || name === null || name === "") {
3667
+ const doomed = subtreeOf(nodes, ci.nodeId);
3668
+ for (const id of doomed) droppedNodeIds.add(id);
3669
+ for (let i = nodes.length - 1; i >= 0; i--) if (doomed.has(nodes[i].id)) nodes.splice(i, 1);
3670
+ instNotes.push(`\u52A8\u6001\u7EC4\u4EF6 \`:is="${ci.expr}"\` \u6C42\u503C\u4E3A\u5047\uFF08${String(name)}\uFF09\u21D2 \u8282\u70B9\u5DF2\u6458\u9664\uFF08Vue \u540C\uFF09`);
3671
+ continue;
3672
+ }
3673
+ if (typeof name !== "string") {
3674
+ instNotes.push(
3675
+ `\u52A8\u6001\u7EC4\u4EF6 \`:is="${ci.expr}"\` \u6C42\u503C\u4E0D\u662F\u5B57\u7B26\u4E32\uFF08${typeof name}\uFF09\u21D2 \u672A\u89E3\u6790\uFF08\u672C\u5B9E\u73B0\u6309**\u7EC4\u4EF6\u540D**\u67E5\u6CE8\u518C\u8868\uFF1B\u7EC4\u4EF6\u5BF9\u8C61\u5F62\u6001\u4E3A\u540E\u7EED\u6279\u6B21\uFF09`
3676
+ );
3677
+ continue;
3678
+ }
3679
+ target.component = name;
3680
+ delete target.componentIs;
3681
+ instNotes.push(`\u52A8\u6001\u7EC4\u4EF6 \`:is="${ci.expr}"\` \u21D2 \u89E3\u6790\u4E3A ${name}\uFF08\u5B9E\u4F8B\u5316\u671F\u4E00\u6B21\u6027\u89E3\u6790\uFF09`);
3682
+ }
3683
+ }
3684
+ if (opts.components) {
3685
+ const depth = opts.componentDepth ?? 0;
3686
+ const boundaries = nodes.filter((n) => n.component);
3687
+ for (const boundary of boundaries) {
3688
+ if (droppedNodeIds.has(boundary.id)) continue;
3689
+ const name = boundary.component;
3690
+ const def = opts.components[name];
3691
+ if (!def) {
3692
+ instNotes.push(`\u7EC4\u4EF6 ${name}\uFF08\u8FB9\u754C\u8282\u70B9 ${boundary.id}\uFF09\u672A\u5728\u6CE8\u518C\u8868\u91CC \u21D2 \u5185\u90E8\u7559\u7A7A\uFF08\u5360\u4F4D\uFF09`);
3693
+ continue;
3694
+ }
3695
+ if (depth >= 8) {
3696
+ instNotes.push(`\u7EC4\u4EF6 ${name} \u5C55\u5F00\u6DF1\u5EA6\u8D85\u4E0A\u9650\uFF088\uFF09\u21D2 \u505C\u6B62\u9012\u5F52\uFF08\u9632\u81EA\u5F15\u7528\uFF09`);
3697
+ continue;
3698
+ }
3699
+ const boundaryLocalId = boundary.id - idOffset;
3700
+ const props = {};
3701
+ if (opts.table) {
3702
+ const allSlots = [
3703
+ ...opts.table.sources.flatMap((src) => src.slots.map((sl) => ({ sl, srcName: src.sourceName }))),
3704
+ ...(opts.table.constantSlots ?? []).map((sl) => ({ sl, srcName: void 0 }))
3705
+ ];
3706
+ for (const { sl, srcName } of allSlots) {
3707
+ if (sl.kind !== "component-prop" || sl.nodeId !== boundaryLocalId) continue;
3708
+ const propName = sl.propKey.startsWith("component.") ? sl.propKey.slice("component.".length) : sl.propKey;
3709
+ if (srcName !== void 0) {
3710
+ props[propName] = opts.read(srcName);
3711
+ } else {
3712
+ const impl = evaluators.get(sl.evaluatorId);
3713
+ if (impl) {
3714
+ try {
3715
+ props[propName] = impl({ read: opts.read });
3716
+ } catch {
3717
+ props[propName] = void 0;
3718
+ }
3719
+ }
3720
+ }
3721
+ }
3722
+ }
3723
+ const childRead = (n) => {
3724
+ if (Object.prototype.hasOwnProperty.call(props, n)) return props[n];
3725
+ const d = def.data;
3726
+ if (d && Object.prototype.hasOwnProperty.call(d, n)) return d[n];
3727
+ return opts.read(n);
3728
+ };
3729
+ const ctx = { read: childRead };
3730
+ const childRegistry = opts.registry ? new ListRegistry() : void 0;
3731
+ const childOffset = nextId;
3732
+ const childInst = instantiateTemplate(def.template, {
3733
+ viewport: opts.viewport,
3734
+ read: childRead,
3735
+ table: def.table,
3736
+ registry: childRegistry,
3737
+ idOffset: idOffset + childOffset,
3738
+ components: opts.components,
3739
+ componentDepth: depth + 1
3740
+ });
3741
+ const contentRoots = nodes.filter((x) => x.parentId === boundary.id && x.slotFor);
3742
+ if (contentRoots.length > 0 || childInst.slotMounts?.length) {
3743
+ const fills = /* @__PURE__ */ new Map();
3744
+ const scopeOf = /* @__PURE__ */ new Map();
3745
+ const bindingsOf = /* @__PURE__ */ new Map();
3746
+ for (const r of contentRoots) {
3747
+ const sf = r.slotFor;
3748
+ const arr = fills.get(sf.name) ?? [];
3749
+ arr.push(r);
3750
+ fills.set(sf.name, arr);
3751
+ if (!scopeOf.has(sf.name)) scopeOf.set(sf.name, sf.scope);
3752
+ if (!bindingsOf.has(sf.name)) bindingsOf.set(sf.name, sf.scopeBindings);
3753
+ }
3754
+ const childEvaluators = def.table ? VaporRuntime.buildEvaluators(def.table.evaluators) : /* @__PURE__ */ new Map();
3755
+ const evalOutletProps = (outlet) => {
3756
+ const out = {};
3757
+ const names = outlet.slotOutlet.props ?? [];
3758
+ const outletLocal = outlet.id - (idOffset + childOffset);
3759
+ const allSlots = def.table ? [...def.table.sources.flatMap((s) => s.slots), ...def.table.constantSlots ?? []] : [];
3760
+ for (const propName of names) {
3761
+ const sl = allSlots.find((s) => s.nodeId === outletLocal && s.propKey === `attr.${propName}`);
3762
+ if (!sl) continue;
3763
+ const impl = childEvaluators.get(sl.evaluatorId);
3764
+ if (!impl) continue;
3765
+ try {
3766
+ out[propName] = impl({ read: childRead });
3767
+ } catch {
3768
+ out[propName] = void 0;
3769
+ }
3770
+ }
3771
+ return out;
3772
+ };
3773
+ const { consumed, placed } = dissolveOutlets(childInst.nodes, fills, evalOutletProps);
3774
+ for (const p of placed) {
3775
+ const scopeVar = scopeOf.get(p.name);
3776
+ const scopeBindings = bindingsOf.get(p.name);
3777
+ if (!scopeVar && !scopeBindings) continue;
3778
+ const rootIds = new Set(p.fill.map((x) => x.id));
3779
+ const byIdOfChild = new Map(childInst.nodes.map((x) => [x.id, x]));
3780
+ const subtree = [];
3781
+ for (const x of childInst.nodes) {
3782
+ let cur = x;
3783
+ let guard = 0;
3784
+ while (cur !== void 0 && guard++ < 64) {
3785
+ if (rootIds.has(cur.id)) {
3786
+ subtree.push(x);
3787
+ break;
3788
+ }
3789
+ cur = cur.parentId === null ? void 0 : byIdOfChild.get(cur.parentId);
3790
+ }
3791
+ }
3792
+ const scopedRead = (n2) => {
3793
+ if (scopeVar && n2 === scopeVar) return p.props;
3794
+ if (scopeBindings) {
3795
+ const hit = scopeBindings.find((b) => b.local === n2);
3796
+ if (hit) return p.props[hit.key];
3797
+ }
3798
+ return opts.read(n2);
3799
+ };
3800
+ const scopeTag = scopeVar ?? scopeBindings.map((b) => b.local).join(",");
3801
+ for (const node of subtree) {
3802
+ const tn = tpl.nodes.find((x) => x.id === node.id - idOffset);
3803
+ if (tn?.textSegments && tn.textSegments.length > 0) {
3804
+ setNormText(node, evalTextSegments(tn.textSegments, scopedRead));
3805
+ }
3806
+ }
3807
+ let appliedScoped = 0;
3808
+ for (const sc of opts.table?.slotScopedSlots ?? []) {
3809
+ if (scopeVar !== void 0 ? sc.scope !== scopeVar : !scopeBindings.some((b) => b.local === sc.scope)) continue;
3810
+ const target = subtree.find((x) => x.id === sc.nodeId + idOffset);
3811
+ if (!target) continue;
3812
+ const impl = evaluators.get(sc.evaluatorId);
3813
+ if (!impl) continue;
3814
+ const f = engineFieldOf(sc.propKey);
3815
+ if (!f) continue;
3816
+ let v;
3817
+ try {
3818
+ v = impl({ read: scopedRead });
3819
+ } catch {
3820
+ continue;
3821
+ }
3822
+ if (f.kind === "text") {
3823
+ setNormText(target, v);
3824
+ } else {
3825
+ applyStyleField(target, sc.propKey, f.key, v, opts.table?.classRules, opts.table?.classPlans?.[String(sc.nodeId)]);
3826
+ }
3827
+ appliedScoped++;
3828
+ valuesFilled++;
3829
+ }
3830
+ instNotes.push(
3831
+ `\u63D2\u69FD ${name}#${p.name}\uFF1A\u4F5C\u7528\u57DF ${scopeVar ? `\`${scopeVar}\`` : `\u89E3\u6784 \`{ ${scopeBindings.map((b) => b.local).join(", ")} }\``} \u7ED1\u5B9A\u51FA\u53E3 props ${JSON.stringify(p.props)}\uFF08\u6587\u672C\u6BB5\u91CD\u6C42\u503C ${subtree.filter((x) => tpl.nodes.find((t) => t.id === x.id - idOffset)?.textSegments?.length).length} \u8282\u70B9 \xB7 \u4F5C\u7528\u57DF\u6837\u5F0F ${appliedScoped} \u5904\uFF09`
3832
+ );
3833
+ }
3834
+ for (const [nm, roots] of fills) {
3835
+ if (consumed.has(nm)) {
3836
+ for (const r of roots) {
3837
+ const i = nodes.indexOf(r);
3838
+ if (i >= 0) nodes.splice(i, 1);
3839
+ }
3840
+ continue;
3841
+ }
3842
+ for (const r of roots) {
3843
+ const doomed = subtreeOf(nodes, r.id);
3844
+ for (const id of doomed) droppedNodeIds.add(id);
3845
+ for (let i = nodes.length - 1; i >= 0; i--) if (doomed.has(nodes[i].id)) nodes.splice(i, 1);
3846
+ }
3847
+ instNotes.push(`\u63D2\u69FD ${name}#${nm} \u7684\u5185\u5BB9\u65E0\u51FA\u53E3\u63A5\u4F4F\uFF08\u5B50\u7EC4\u4EF6\u6CA1\u6709\u540C\u540D <slot>\uFF09\u21D2 \u672A\u6E32\u67D3`);
3848
+ }
3849
+ }
3850
+ for (const r of childInst.nodes) if (r.parentId === null) r.parentId = boundary.id;
3851
+ nodes.push(...childInst.nodes);
3852
+ componentNodes += childInst.nodes.length;
3853
+ valuesFilled += childInst.stats.valuesFilled;
3854
+ nextId = childOffset + childInst.stats.maxLocalId + 1;
3855
+ componentMounts.push({
3856
+ boundaryNodeId: boundary.id,
3857
+ name,
3858
+ props,
3859
+ ctx,
3860
+ table: def.table,
3861
+ registry: childRegistry,
3862
+ nodeIds: childInst.nodes.map((x) => x.id),
3863
+ idOffset: idOffset + childOffset,
3864
+ // ★P1-3 emits:边界在**本树**的 id 空间 —— 宿主要加 idOffset 才是它看到的 id
3865
+ treeOffset: idOffset
3866
+ });
3867
+ for (const m of childInst.componentMounts ?? []) componentMounts.push(m);
3868
+ for (const sm of childInst.slotMounts ?? []) slotMounts.push(sm);
3869
+ for (const d of childInst.stats.droppedNodeIds ?? []) droppedNodeIds.add(d);
3870
+ for (const nt of childInst.notes ?? []) instNotes.push(nt);
3871
+ }
3872
+ }
3873
+ if ((opts.componentDepth ?? 0) === 0) {
3874
+ dissolveOutlets(nodes, /* @__PURE__ */ new Map());
3875
+ for (const x of nodes) {
3876
+ const rec = x;
3877
+ if (rec.slotFor !== void 0) delete rec.slotFor;
3878
+ if (rec.slotOutlet !== void 0) delete rec.slotOutlet;
3879
+ }
3880
+ }
3881
+ return {
3882
+ viewport: opts.viewport,
3883
+ nodes,
3884
+ stats: {
3885
+ reusedTemplateIds: reused,
3886
+ allocatedIds: allocated,
3887
+ rows: nodes.length,
3888
+ valuesFilled,
3889
+ // ★P1-3:本树 local 高水位(父级据此推进自己的分配器——**不含** idOffset)
3890
+ maxLocalId: nextId - 1,
3891
+ componentNodes,
3892
+ // ★P1-3 插槽分发:丢弃节点(最终 id 空间)——桥传给运行时 `skipNodeIds`
3893
+ ...droppedNodeIds.size > 0 ? { droppedNodeIds: [...droppedNodeIds] } : {}
3894
+ },
3895
+ // ★只有**恰好一个**列表时才给虚拟化描述(多个列表 ⇒ 行号空间不同源,宿主按行号二分会错配)
3896
+ virtual: virtualRows.length > 0 && tpl.lists.length === 1 ? { rows: virtualRows } : void 0,
3897
+ ...componentMounts.length > 0 ? { componentMounts } : {},
3898
+ ...slotMounts.length > 0 ? { slotMounts } : {},
3899
+ ...instNotes.length > 0 ? { notes: instNotes } : {}
3900
+ };
1716
3901
  }
1717
- function degradationOf(prim) {
1718
- const out = {};
1719
- for (const p of prim.props ?? []) out[p] = degradeProp(prim.tag, p);
1720
- return out;
3902
+
3903
+ // ../slot-runtime/src/dispatch.ts
3904
+ function createDispatchState() {
3905
+ return { onceFired: /* @__PURE__ */ new Set() };
1721
3906
  }
1722
- var DEGRADATION_TABLE = (() => {
1723
- const out = {};
1724
- for (const prim of PRIMITIVE_CATALOG) {
1725
- if (!prim.tag) continue;
1726
- out[prim.tag] = degradationOf(prim);
1727
- }
1728
- return out;
1729
- })();
1730
- var MP_UNSUPPORTED_PROPS = (() => {
1731
- const kebab = (s) => s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
1732
- const out = {};
1733
- for (const [tag, table] of Object.entries(DEGRADATION_TABLE)) {
1734
- const set = /* @__PURE__ */ new Set();
1735
- for (const [prop, entry] of Object.entries(table)) {
1736
- if (entry.mp === "unsupported") set.add(kebab(prop));
3907
+ var keyOf = (nodeId, event, handler) => `${nodeId}:${event}:${handler}`;
3908
+ function indexEventBindings(bindings) {
3909
+ const byNodeEvent = /* @__PURE__ */ new Map();
3910
+ for (const b of bindings) byNodeEvent.set(`${b.nodeId}:${b.event}`, b);
3911
+ return byNodeEvent;
3912
+ }
3913
+ function dispatchGesture(chain, event, index, state, run) {
3914
+ const fired = [];
3915
+ const skippedSelf = [];
3916
+ const skippedOnce = [];
3917
+ let handler = "";
3918
+ let stopped = false;
3919
+ const hit = chain.length > 0 ? chain[0] : -1;
3920
+ for (const id of chain) {
3921
+ const b = index.get(`${id}:${event}`) ?? index.get(`${id}:tap`);
3922
+ if (!b) continue;
3923
+ if (b.componentEmit) continue;
3924
+ if (b.self && id !== hit) {
3925
+ skippedSelf.push(id);
3926
+ continue;
3927
+ }
3928
+ const onceKey = keyOf(id, b.event, b.handler);
3929
+ if (b.once && state.onceFired.has(onceKey)) {
3930
+ skippedOnce.push(id);
3931
+ continue;
3932
+ }
3933
+ if (!run(b.handler, id)) continue;
3934
+ if (b.once) state.onceFired.add(onceKey);
3935
+ fired.push(id);
3936
+ if (!handler) handler = b.handler;
3937
+ if (b.stop) {
3938
+ stopped = true;
3939
+ break;
1737
3940
  }
1738
- if (set.size) out[tag] = set;
1739
3941
  }
1740
- return out;
1741
- })();
3942
+ return { fired, handler, stopped, skippedSelf, skippedOnce };
3943
+ }
3944
+
3945
+ // ../slot-runtime/src/directives.ts
3946
+ var HOST_DIRECTIVE_SPECS = {
3947
+ animate: {
3948
+ argKind: "anim-preset",
3949
+ argHint: "\u52A8\u753B\u9884\u8BBE\uFF08fade / slide-up / slide-down / slide-left / slide-right / zoom / fade-slide-up\uFF09",
3950
+ desc: "\u503C\u53D8\u5316\uFF08\u6216\u9996\u6B21\u6C42\u503C\u4E3A\u771F\uFF09\u65F6\u5728\u8BE5\u8282\u70B9**\u64AD\u4E00\u6B21**\u9884\u8BBE\u52A8\u753B\uFF08\u8D70\u5185\u6838\u52A8\u753B\u901A\u9053\uFF0C\u4E0E <Transition> \u540C\u4E00\u5957\uFF09\u3002\u8BED\u4E49\u5BF9\u9F50\uFF1A\u6307\u4EE4\u7684 mounted\uFF08\u9996\u8BC4 truthy \u5373\u64AD\uFF09\u4E0E updated\uFF08\u503C\u53D8\u5316\u5373\u64AD\uFF09\u3002"
3951
+ }
3952
+ };
3953
+ var HOST_DIRECTIVE_NAMES = Object.keys(HOST_DIRECTIVE_SPECS);
1742
3954
 
1743
3955
  // src/dispatcher.ts
1744
3956
  var DispatcherError = class extends Error {
@@ -2273,6 +4485,133 @@ function createWebHostRuntime(opts = {}) {
2273
4485
  return rt;
2274
4486
  }
2275
4487
 
4488
+ // src/quickjs-host.ts
4489
+ function createQuickJsHostRuntime(opts = {}) {
4490
+ const id = opts.id ?? "quickjs";
4491
+ const capabilities = {
4492
+ threads: { main: true, background: false, count: 1 },
4493
+ // ★诚实声明:无后台线程
4494
+ engine: opts.engine ?? "quickjs",
4495
+ // 桥**机制**恒在(registerNativeHandler/invokeNative/拒绝协议都存在);transport 是可选出口
4496
+ nativeBridge: true,
4497
+ lifecycle: "full",
4498
+ // 宿主壳转发 onPause/onResume ⇒ full
4499
+ frameDriver: opts.frameDriver ?? "manual"
4500
+ };
4501
+ const refusals = [];
4502
+ const lifecycleEvents = [];
4503
+ const handlers = /* @__PURE__ */ new Map();
4504
+ const refuse = (op, reason) => {
4505
+ refusals.push({ op, reason, state: rt.state });
4506
+ throw new Error(`[${id}] \u804C\u8D23\u8FB9\u754C\u62D2\u7EDD ${op}\uFF1A${reason}\uFF08state=${rt.state}\uFF09`);
4507
+ };
4508
+ const rt = {
4509
+ id,
4510
+ state: "created",
4511
+ threads: ["main"],
4512
+ workers: [],
4513
+ queue: [],
4514
+ capabilities,
4515
+ lifecycleEvents,
4516
+ refusals,
4517
+ nativeHandlers: [],
4518
+ bootstrap() {
4519
+ if (rt.state === "destroyed") refuse("bootstrap", "\u5DF2\u9500\u6BC1\u7684\u8FD0\u884C\u65F6\u4E0D\u53EF\u590D\u6D3B\uFF08G-39.1\uFF1A\u751F\u547D\u5468\u671F\u552F\u4E00\u62E5\u6709\uFF09");
4520
+ if (rt.state === "suspended") refuse("bootstrap", "\u5DF2\u6302\u8D77\u72B6\u6001\u4E0B\u8BF7\u7528 resume()\uFF08bootstrap \u53EA\u8D1F\u8D23 created \u8D77\u6B65\uFF09");
4521
+ rt.state = "running";
4522
+ return rt;
4523
+ },
4524
+ suspend() {
4525
+ if (rt.state !== "running") refuse("suspend", "\u4EC5 running \u53EF\u6302\u8D77\uFF08\u91CD\u590D suspend / \u672A bootstrap \u90FD\u88AB\u62D2\u7EDD\uFF09");
4526
+ rt.state = "suspended";
4527
+ lifecycleEvents.push("suspend");
4528
+ },
4529
+ resume() {
4530
+ if (rt.state !== "suspended") refuse("resume", '\u4EC5 suspended \u53EF\u6062\u590D\uFF08\u9632"\u6CA1\u6302\u8D77\u8FC7\u5374\u4E0A\u62A5 resume"\u7684\u5047\u4E8B\u4EF6\uFF09');
4531
+ rt.state = "running";
4532
+ lifecycleEvents.push("resume");
4533
+ },
4534
+ destroy() {
4535
+ if (rt.state === "destroyed") refuse("destroy", "\u91CD\u590D\u9500\u6BC1\uFF08\u7B2C\u4E8C\u6B21 destroy \u901A\u5E38\u662F\u4E0A\u5C42\u7684\u91CD\u590D\u6E05\u7406\u2014\u2014\u771F\u5B9E\u7F3A\u9677\uFF09");
4536
+ rt.state = "destroyed";
4537
+ rt.queue = [];
4538
+ rt.workers = [];
4539
+ rt.threads = ["main"];
4540
+ lifecycleEvents.push("destroy");
4541
+ },
4542
+ createWorker() {
4543
+ if (rt.state === "destroyed") refuse("createWorker", "\u5DF2\u9500\u6BC1\u7684\u8FD0\u884C\u65F6\u4E0D\u80FD\u521B\u5EFA\u6267\u884C\u57DF");
4544
+ const w = {
4545
+ id: `w${rt.workers.length + 1}`,
4546
+ thread: `worker${rt.workers.length + 1}`,
4547
+ real: false
4548
+ };
4549
+ rt.workers.push(w);
4550
+ rt.threads.push(w.thread);
4551
+ return w;
4552
+ },
4553
+ postMessage() {
4554
+ return true;
4555
+ },
4556
+ enqueue(task, priority = 2) {
4557
+ if (rt.state === "destroyed") refuse("enqueue", "\u5DF2\u9500\u6BC1\u7684\u8FD0\u884C\u65F6\u4E0D\u518D\u63A5\u6536\u4EFB\u52A1\uFF08\u9632\u60AC\u7A7A\u56DE\u8C03\uFF09");
4558
+ rt.queue.push({ task, priority });
4559
+ },
4560
+ nextTick(fn) {
4561
+ if (rt.state === "destroyed") refuse("nextTick", "\u5DF2\u9500\u6BC1\u7684\u8FD0\u884C\u65F6\u4E0D\u518D\u63A5\u6536\u4EFB\u52A1");
4562
+ rt.queue.push({ task: fn, priority: 0 });
4563
+ },
4564
+ drain() {
4565
+ rt.queue.sort((a, b) => a.priority - b.priority);
4566
+ const out = [];
4567
+ while (rt.queue.length) {
4568
+ const { task } = rt.queue.shift();
4569
+ out.push(task());
4570
+ }
4571
+ return out;
4572
+ },
4573
+ /** ★宿主帧驱动(Android Choreographer / iOS CADisplayLink):一帧消费一次队列 */
4574
+ pumpFrame() {
4575
+ if (rt.state === "destroyed") return 0;
4576
+ if (rt.state === "suspended") return 0;
4577
+ rt.queue.sort((a, b) => a.priority - b.priority);
4578
+ let n = 0;
4579
+ while (rt.queue.length) {
4580
+ const { task } = rt.queue.shift();
4581
+ task();
4582
+ n++;
4583
+ }
4584
+ return n;
4585
+ },
4586
+ registerNativeHandler(name, handler) {
4587
+ handlers.set(name, handler);
4588
+ rt.nativeHandlers.push(name);
4589
+ },
4590
+ runOnThread(thread, task) {
4591
+ if (thread === "main") {
4592
+ rt.enqueue(task, 2);
4593
+ return;
4594
+ }
4595
+ return refuse("runOnThread(background)", "\u672C\u5BBF\u4E3B capabilities.threads.background=false\uFF08\u5355\u7EBF\u7A0B JS \u5F15\u64CE\uFF1B\u8C03\u7528\u65B9\u5E94\u67E5 capabilities \u540E\u964D\u7EA7\uFF09");
4596
+ },
4597
+ async invokeNative(name, args) {
4598
+ if (rt.state === "destroyed") refuse("invokeNative", "\u5DF2\u9500\u6BC1\u7684\u8FD0\u884C\u65F6\u4E0D\u80FD\u8C03\u539F\u751F");
4599
+ const h = handlers.get(name);
4600
+ if (h) return h(args);
4601
+ if (opts.transport) {
4602
+ const json = opts.transport.call(name, JSON.stringify(args ?? null));
4603
+ try {
4604
+ return JSON.parse(json);
4605
+ } catch {
4606
+ return json;
4607
+ }
4608
+ }
4609
+ return refuse("invokeNative", `\u539F\u751F\u65B9\u6CD5\u300C${name}\u300D\u672A\u6CE8\u518C\u4E14\u65E0 transport\uFF08\u5DF2\u6CE8\u518C\uFF1A${rt.nativeHandlers.join(", ") || "\uFF08\u7A7A\uFF09"}\uFF09`);
4610
+ }
4611
+ };
4612
+ return rt;
4613
+ }
4614
+
2276
4615
  // src/hot-switch.ts
2277
4616
  function createBackendSwitcher(dispatch) {
2278
4617
  let currentIR = null;
@@ -4056,10 +6395,10 @@ function analyzeOwnershipSource(source, opts = {}) {
4056
6395
  if (src) src.activeBorrows++;
4057
6396
  continue;
4058
6397
  }
4059
- const call = txt.match(CALL_RE);
4060
- if (call) {
4061
- const name = call[1];
4062
- const op = call[2];
6398
+ const call2 = txt.match(CALL_RE);
6399
+ if (call2) {
6400
+ const name = call2[1];
6401
+ const op = call2[2];
4063
6402
  const info = vars.get(name);
4064
6403
  if (!info) continue;
4065
6404
  if (op === "transferTo") {
@@ -4404,10 +6743,10 @@ function insertScopeDrops(source) {
4404
6743
  }
4405
6744
  continue;
4406
6745
  }
4407
- const call = t.match(/([A-Za-z_$][\w$]*)\s*\.\s*(?:drop|transferTo)\s*\(/);
4408
- if (call) {
6746
+ const call2 = t.match(/([A-Za-z_$][\w$]*)\s*\.\s*(?:drop|transferTo)\s*\(/);
6747
+ if (call2) {
4409
6748
  for (const p of pending) {
4410
- if (p.varName === call[1] && !p.disposed) {
6749
+ if (p.varName === call2[1] && !p.disposed) {
4411
6750
  p.disposed = true;
4412
6751
  break;
4413
6752
  }
@@ -4627,6 +6966,605 @@ function formatMatrixReport(report) {
4627
6966
  }
4628
6967
  return lines.join("\n");
4629
6968
  }
6969
+
6970
+ // ../contracts/src/layers.ts
6971
+ var LAYER_PRIMITIVES = ["layer-content", "layer-navigation", "layer-mask", "layer-popout"];
6972
+
6973
+ // ../contracts/src/mount-layers.ts
6974
+ var MOUNT_LAYERS = ["global", "page", "overlay"];
6975
+ var MOUNT_LAYER_ORDER = {
6976
+ global: 0,
6977
+ page: 1,
6978
+ overlay: 2
6979
+ };
6980
+ var MOUNT_LAYER_NODE_OFFSET = {
6981
+ global: 1,
6982
+ page: 2,
6983
+ overlay: 3
6984
+ };
6985
+ function mountLayerContainerPlans() {
6986
+ return [...MOUNT_LAYERS].sort((a, b) => MOUNT_LAYER_ORDER[a] - MOUNT_LAYER_ORDER[b]).map((layer) => ({
6987
+ layer,
6988
+ nodeOffset: MOUNT_LAYER_NODE_OFFSET[layer],
6989
+ order: MOUNT_LAYER_ORDER[layer],
6990
+ frame: "fullscreen"
6991
+ }));
6992
+ }
6993
+
6994
+ // ../contracts/src/scroll.ts
6995
+ var DOCK_REQUIRES_LAYER = LAYER_PRIMITIVES.includes("layer-navigation");
6996
+
6997
+ // src/screen-executor.ts
6998
+ function screenContentFromLayoutTemplate(template, viewport) {
6999
+ const nodes = template.nodes.map((n) => {
7000
+ const out = {
7001
+ id: n.id,
7002
+ parentId: n.parentId,
7003
+ // ★展平:`style` 的引擎字段提到顶层(内核 NodeDto 读顶层键)
7004
+ ...n.style ?? {}
7005
+ };
7006
+ if (typeof n.text === "string" && n.text.length > 0) out.text = n.text;
7007
+ if (typeof n.tag === "string" && n.tag.length > 0) out.semantic = n.tag;
7008
+ return out;
7009
+ });
7010
+ return viewport ? { viewport, nodes } : { nodes };
7011
+ }
7012
+ function createScreenExecutor(opts) {
7013
+ const { host, anim, plan, contentOf, onScreenMounted, onEvent } = opts;
7014
+ const nodes = /* @__PURE__ */ new Map();
7015
+ const stats = {
7016
+ commands: 0,
7017
+ transitions: 0,
7018
+ forward: 0,
7019
+ back: 0,
7020
+ skippedNone: 0,
7021
+ destroyed: { pop: 0, reset: 0, freeze: 0 },
7022
+ visibility: { shown: 0, hidden: 0 },
7023
+ mounts: { first: 0, rebuild: 0 },
7024
+ errors: []
7025
+ };
7026
+ let txn = { exiting: null, mounted: /* @__PURE__ */ new Map() };
7027
+ const emit = (type, screenId, detail) => {
7028
+ onEvent?.({ type, screenId, detail });
7029
+ };
7030
+ async function runCommand(cmd) {
7031
+ stats.commands++;
7032
+ switch (cmd.op) {
7033
+ case "mount": {
7034
+ const rebuild = cmd.rebuild === true;
7035
+ const name = cmd.name ?? cmd.screenId;
7036
+ const path = cmd.path ?? "";
7037
+ const content = contentOf?.({ name, path });
7038
+ const node = await host.mountScreen({
7039
+ screenId: cmd.screenId,
7040
+ name,
7041
+ path,
7042
+ params: cmd.params ?? null,
7043
+ rebuild,
7044
+ ...content ? { content } : {},
7045
+ // ★GP3-c:三层容器计划(由**契约**算,宿主只消费——偏移量而非绝对 id,见类型注释)
7046
+ layerContainers: mountLayerContainerPlans()
7047
+ });
7048
+ nodes.set(cmd.screenId, node);
7049
+ txn.mounted.set(cmd.screenId, rebuild);
7050
+ if (rebuild) stats.mounts.rebuild++;
7051
+ else stats.mounts.first++;
7052
+ emit("mount", cmd.screenId, { rebuild, node, contentNodes: content?.nodes.length ?? 0 });
7053
+ onScreenMounted?.(cmd.screenId, rebuild);
7054
+ return;
7055
+ }
7056
+ case "exit": {
7057
+ txn.exiting = { screenId: cmd.screenId, transition: cmd.transition, destroy: null };
7058
+ emit("exit", cmd.screenId, { transition: cmd.transition });
7059
+ return;
7060
+ }
7061
+ case "unmount": {
7062
+ const reason = cmd.reason ?? "pop";
7063
+ if (txn.exiting && txn.exiting.screenId === cmd.screenId) {
7064
+ txn.exiting.destroy = reason;
7065
+ emit("unmount-deferred", cmd.screenId, { reason });
7066
+ return;
7067
+ }
7068
+ await host.destroyScreen(cmd.screenId, reason, nodes.get(cmd.screenId));
7069
+ nodes.delete(cmd.screenId);
7070
+ stats.destroyed[reason]++;
7071
+ emit("destroy", cmd.screenId, { reason });
7072
+ return;
7073
+ }
7074
+ case "enter": {
7075
+ const incomingNode = nodes.get(cmd.screenId);
7076
+ if (incomingNode === void 0) {
7077
+ stats.errors.push(`enter(${cmd.screenId})\uFF1A\u5C4F\u5B50\u6811\u4E0D\u5B58\u5728\uFF08mount \u672A\u6267\u884C\uFF1F\uFF09`);
7078
+ emit("error", cmd.screenId, "enter-without-subtree");
7079
+ return;
7080
+ }
7081
+ const exiting = txn.exiting;
7082
+ const outgoingNode = exiting ? nodes.get(exiting.screenId) : void 0;
7083
+ const mountedHere = txn.mounted.get(cmd.screenId);
7084
+ const direction = mountedHere === false ? "forward" : "back";
7085
+ const transition = cmd.transition;
7086
+ const batch = plan(transition, { incoming: incomingNode, outgoing: outgoingNode }, { direction });
7087
+ const normalized = typeof transition === "string" ? transition : "none";
7088
+ await host.setScreenVisible(cmd.screenId, true, incomingNode);
7089
+ stats.visibility.shown++;
7090
+ emit("visible", cmd.screenId, true);
7091
+ const hasAnims = batch.incoming.anims.length > 0 || batch.outgoing.anims.length > 0;
7092
+ if (hasAnims) {
7093
+ await anim.playRouteTransition(batch, { direction, transition: normalized });
7094
+ stats.transitions++;
7095
+ if (direction === "forward") stats.forward++;
7096
+ else stats.back++;
7097
+ emit("transition", cmd.screenId, { direction, transition: normalized, anims: batch.incoming.anims.length + batch.outgoing.anims.length });
7098
+ } else {
7099
+ stats.skippedNone++;
7100
+ emit("transition-skipped", cmd.screenId, { transition: normalized });
7101
+ }
7102
+ if (exiting) {
7103
+ if (exiting.destroy !== null) {
7104
+ await host.destroyScreen(exiting.screenId, exiting.destroy, outgoingNode);
7105
+ nodes.delete(exiting.screenId);
7106
+ stats.destroyed[exiting.destroy]++;
7107
+ emit("destroy", exiting.screenId, { reason: exiting.destroy, deferred: true });
7108
+ } else if (outgoingNode !== void 0) {
7109
+ await host.setScreenVisible(exiting.screenId, false, outgoingNode);
7110
+ stats.visibility.hidden++;
7111
+ emit("visible", exiting.screenId, false);
7112
+ }
7113
+ }
7114
+ txn = { exiting: null, mounted: /* @__PURE__ */ new Map() };
7115
+ return;
7116
+ }
7117
+ }
7118
+ }
7119
+ let queue = Promise.resolve();
7120
+ return {
7121
+ applyCommands(commands) {
7122
+ queue = queue.then(async () => {
7123
+ for (const c of commands) await runCommand(c);
7124
+ });
7125
+ return queue;
7126
+ },
7127
+ stats: () => JSON.parse(JSON.stringify(stats)),
7128
+ subtreeNode: (screenId) => nodes.get(screenId)
7129
+ };
7130
+ }
7131
+
7132
+ // src/screen-executor-host.ts
7133
+ var SCREEN_ANIM_DONE_KEY = "__proteusHostScreenAnimDone";
7134
+ var instSeq = 0;
7135
+ function call(channel, method, args) {
7136
+ const raw = channel(method, JSON.stringify(args ?? null));
7137
+ let parsed;
7138
+ try {
7139
+ parsed = JSON.parse(raw);
7140
+ } catch (e) {
7141
+ throw new Error(`[screen-host] ${method} \u56DE\u6267\u975E JSON\uFF1A${String(raw).slice(0, 120)}`);
7142
+ }
7143
+ if (parsed && parsed.ok === false) {
7144
+ throw new Error(`[screen-host] ${method} \u5931\u8D25${parsed.missing ? "\uFF08\u672A\u5B9E\u73B0\uFF09" : ""}\uFF1A${parsed.reason ?? "\u65E0\u539F\u56E0"}`);
7145
+ }
7146
+ return parsed && typeof parsed === "object" && "data" in parsed ? parsed.data : parsed;
7147
+ }
7148
+ function createHostScreenPorts(opts) {
7149
+ const channel = opts.invoke;
7150
+ const pending = /* @__PURE__ */ new Map();
7151
+ let tokenSeq = 0;
7152
+ let hookInstalled = false;
7153
+ const INSTANCE_ID = `s${++instSeq}-`;
7154
+ const resolveOne = (token, payload) => {
7155
+ const r = pending.get(token);
7156
+ if (!r) return false;
7157
+ pending.delete(token);
7158
+ r(payload);
7159
+ return true;
7160
+ };
7161
+ if (opts.installAnimDoneHook !== false) {
7162
+ const g = globalThis;
7163
+ const REGISTRY_KEY = `${SCREEN_ANIM_DONE_KEY}__registry`;
7164
+ let registry = g[REGISTRY_KEY];
7165
+ if (!Array.isArray(registry)) {
7166
+ registry = [];
7167
+ g[REGISTRY_KEY] = registry;
7168
+ }
7169
+ const handler = (token, resultJson) => {
7170
+ if (typeof token !== "string") return "bad-token";
7171
+ const r = registry;
7172
+ for (let i = 0; i < r.length; i++) {
7173
+ if (r[i](token, resultJson)) return "ok";
7174
+ }
7175
+ return "unknown-token";
7176
+ };
7177
+ registry.push((token, payload) => resolveOne(token, payload));
7178
+ try {
7179
+ g[SCREEN_ANIM_DONE_KEY] = handler;
7180
+ hookInstalled = true;
7181
+ } catch {
7182
+ try {
7183
+ Object.defineProperty(g, SCREEN_ANIM_DONE_KEY, { value: handler, writable: true, configurable: true });
7184
+ hookInstalled = true;
7185
+ } catch {
7186
+ }
7187
+ }
7188
+ }
7189
+ const tree = {
7190
+ mountScreen(screen) {
7191
+ const d = call(channel, "screen.mount", {
7192
+ screenId: screen.screenId,
7193
+ name: screen.name,
7194
+ path: screen.path,
7195
+ params: screen.params,
7196
+ rebuild: screen.rebuild,
7197
+ // ★★★GP3-c:**三层容器计划**随 mount 下发(宿主照此建树——偏移量,宿主加到自己分配的根 id 上)。
7198
+ // 执行器已按契约算好(见 screen-executor.ts);本层只做**透传**(不再重复计算——
7199
+ // 本仓纪律:同一件事两份实现 = 修一份等于没修)。
7200
+ ...screen.layerContainers ? { layerContainers: screen.layerContainers } : {},
7201
+ // ★★★阶段 1(2026-10-04 · App 三端对齐 B1+B2):**屏内容**(真实页面渲染产物)透传。
7202
+ // 执行器按 `contentOf` 提供者解析后放入 mountScreen 入参;本层只做转发(与上方同款纪律)。
7203
+ ...screen.content ? { content: screen.content } : {}
7204
+ });
7205
+ const rootNodeId = Number(d?.rootNodeId ?? 0);
7206
+ if (!rootNodeId) {
7207
+ throw new Error(`[screen-host] screen.mount \u672A\u8FD4\u56DE rootNodeId\uFF08${JSON.stringify(d)}\uFF09\u2014\u2014\u5BBF\u4E3B\u5B9E\u73B0\u4E0D\u5B8C\u6574`);
7208
+ }
7209
+ return rootNodeId;
7210
+ },
7211
+ setScreenVisible(screenId, visible, rootNodeId) {
7212
+ call(channel, "screen.visible", { screenId, visible, rootNodeId });
7213
+ },
7214
+ destroyScreen(screenId, reason, rootNodeId) {
7215
+ call(channel, "screen.destroy", { screenId, reason, rootNodeId });
7216
+ }
7217
+ };
7218
+ const anim = {
7219
+ playRouteTransition(plan, ctx) {
7220
+ const anims = [...plan.incoming.anims, ...plan.outgoing.anims];
7221
+ if (anims.length === 0) return;
7222
+ const token = `${INSTANCE_ID}screen-anim-${++tokenSeq}`;
7223
+ const d = call(channel, "screen.anim", {
7224
+ anims,
7225
+ durationMs: plan.durationMs,
7226
+ direction: ctx.direction,
7227
+ transition: ctx.transition,
7228
+ token
7229
+ });
7230
+ if (d?.immediate === true) return;
7231
+ return new Promise((resolve) => {
7232
+ pending.set(token, () => resolve());
7233
+ });
7234
+ }
7235
+ };
7236
+ return {
7237
+ tree,
7238
+ anim,
7239
+ shared: {
7240
+ async rect(screenId, nodeId) {
7241
+ const d = call(channel, "screen.rect", { screenId, nodeId });
7242
+ if (!d || typeof d.x !== "number") {
7243
+ throw new Error(`[screen-host] screen.rect \u672A\u8FD4\u56DE\u51E0\u4F55\uFF08${JSON.stringify(d)}\uFF09`);
7244
+ }
7245
+ return { x: d.x, y: d.y ?? 0, w: d.width ?? 0, h: d.height ?? 0 };
7246
+ },
7247
+ fly(opts2) {
7248
+ const token = `screen-shared-${++tokenSeq}`;
7249
+ const d = call(channel, "screen.shared", { ...opts2, token });
7250
+ if (!d || (d.started ?? 0) < 1) {
7251
+ return Promise.reject(new Error(`[screen-host] screen.shared \u672A\u542F\u52A8\uFF08${JSON.stringify(d)}\uFF09`));
7252
+ }
7253
+ const geom = { fromRect: d.fromRect, toRect: d.toRect };
7254
+ return new Promise((resolve) => {
7255
+ pending.set(token, (v) => {
7256
+ let extra = {};
7257
+ if (typeof v === "string") {
7258
+ try {
7259
+ const o = JSON.parse(v);
7260
+ if (o && typeof o === "object") extra = o;
7261
+ } catch {
7262
+ }
7263
+ } else if (v && typeof v === "object") {
7264
+ extra = v;
7265
+ }
7266
+ resolve({ ...geom, ...extra });
7267
+ });
7268
+ });
7269
+ }
7270
+ },
7271
+ get pendingAnimations() {
7272
+ return pending.size;
7273
+ },
7274
+ get animDoneHookInstalled() {
7275
+ return hookInstalled;
7276
+ }
7277
+ };
7278
+ }
7279
+
7280
+ // src/screen-runtime.ts
7281
+ function createScreenRuntime(opts) {
7282
+ const base = opts.contentIdBase;
7283
+ const instances = /* @__PURE__ */ new Map();
7284
+ const note = opts.onNote ?? (() => {
7285
+ });
7286
+ function make(name) {
7287
+ const art = opts.artifacts[name];
7288
+ if (!art) throw new Error(`[screen-runtime] \u65E0\u8BE5\u5C4F\u4EA7\u7269\uFF1A${name}`);
7289
+ const data = { ...art.data, ...opts.seedData?.[name] ?? {} };
7290
+ const read = (n) => data[n];
7291
+ let inst = instantiateTemplate(art.tpl, { viewport: opts.viewport, read, table: art.table, registry: new ListRegistry() });
7292
+ let localIdsOrdered = inst.nodes.map((n) => n.id);
7293
+ function rebuild() {
7294
+ inst = instantiateTemplate(art.tpl, { viewport: opts.viewport, read, table: art.table, registry: new ListRegistry() });
7295
+ localIdsOrdered = inst.nodes.map((n) => n.id);
7296
+ }
7297
+ if (inst.stats.componentNodes === 0 && inst.nodes.some((n) => n.component)) {
7298
+ note(`[screen-runtime] ${name}: \u6A21\u677F\u542B\u7EC4\u4EF6\u8FB9\u754C\u4F46\u672C\u7248\u672A\u5C55\u5F00\uFF08\u8BDA\u5B9E\u8FB9\u754C\uFF09`);
7299
+ }
7300
+ const keys = new PropKeyTable();
7301
+ const strings = new StringPool();
7302
+ const slotRt = new SlotRuntime(keys, strings, (bytes) => {
7303
+ opts.applyOps(JSON.stringify(Array.from(bytes)));
7304
+ });
7305
+ const vapor = new VaporRuntime(art.table, slotRt, VaporRuntime.buildEvaluators(art.table.evaluators), new ListRegistry());
7306
+ const evalCtx = { read };
7307
+ vapor.load(evalCtx, () => {
7308
+ });
7309
+ vapor.relink(evalCtx);
7310
+ slotRt.flush();
7311
+ const byNodeEvent = indexEventBindings(art.events);
7312
+ const dispatchState = createDispatchState();
7313
+ function runHandler(handlerName, _nodeId, payload) {
7314
+ const acts = art.handlers[handlerName];
7315
+ if (!acts) return false;
7316
+ for (const a of acts) {
7317
+ if (a.op === "emit") {
7318
+ note(`[screen-runtime] ${name}: handler\u300C${handlerName}\u300D\u542B $emit\u2014\u2014\u672C\u7248\u65E0\u53BB\u5904\uFF08\u5DF2\u5FFD\u7565\uFF09`);
7319
+ continue;
7320
+ }
7321
+ if (a.op === "nav") {
7322
+ const tgt = a.target;
7323
+ if (tgt && opts.navigate) opts.navigate(tgt);
7324
+ else note(`[screen-runtime] ${name}: $nav('${tgt ?? ""}') \u65E0 navigate \u51FA\u53E3\uFF08\u672A\u88C5\u914D\uFF09`);
7325
+ continue;
7326
+ }
7327
+ const ctx2 = { read: (n) => n === "$event" ? payload : data[n] };
7328
+ const v = evalExpr(a.program, ctx2);
7329
+ if (!a.source) continue;
7330
+ if (a.op === "add") {
7331
+ const cur = data[a.source];
7332
+ data[a.source] = (typeof cur === "number" ? cur : 0) + (typeof v === "number" ? v : 0);
7333
+ } else if (a.op === "set") {
7334
+ data[a.source] = v;
7335
+ }
7336
+ }
7337
+ return true;
7338
+ }
7339
+ function localIdOf(kernelId) {
7340
+ if (typeof base !== "number") return kernelId;
7341
+ const idx = kernelId - base;
7342
+ return idx >= 0 && idx < localIdsOrdered.length ? localIdsOrdered[idx] : kernelId;
7343
+ }
7344
+ function refreshData() {
7345
+ rebuild();
7346
+ vapor.relink(evalCtx);
7347
+ slotRt.flush();
7348
+ }
7349
+ return {
7350
+ content() {
7351
+ return { viewport: inst.viewport, nodes: inst.nodes };
7352
+ },
7353
+ dispatch(type, chain) {
7354
+ const localChain = chain.map(localIdOf);
7355
+ const r = dispatchGesture(localChain, type, byNodeEvent, dispatchState, (h, id) => runHandler(h, id));
7356
+ if (r.fired.length > 0) refreshData();
7357
+ return { handled: r.fired.length > 0, fired: r.fired };
7358
+ },
7359
+ flush() {
7360
+ slotRt.flush();
7361
+ },
7362
+ refresh() {
7363
+ refreshData();
7364
+ },
7365
+ data() {
7366
+ return data;
7367
+ },
7368
+ snapshot() {
7369
+ return { ...data };
7370
+ }
7371
+ };
7372
+ }
7373
+ return {
7374
+ has: (name) => Boolean(opts.artifacts[name]),
7375
+ instance(name) {
7376
+ let it = instances.get(name);
7377
+ if (!it) {
7378
+ it = make(name);
7379
+ instances.set(name, it);
7380
+ }
7381
+ return it;
7382
+ },
7383
+ /** ★跨调用状态导出(每屏数据浅拷贝)——一次性 VM 宿主持有、下次回灌(见 seedData) */
7384
+ snapshot() {
7385
+ const out = {};
7386
+ for (const [name, it] of instances) out[name] = it.snapshot();
7387
+ return out;
7388
+ }
7389
+ };
7390
+ }
7391
+
7392
+ // src/host-invoke-contract.ts
7393
+ var HOST_SCREEN_METHODS = [
7394
+ "screen.mount",
7395
+ "screen.visible",
7396
+ "screen.destroy",
7397
+ "screen.anim",
7398
+ "screen.rect",
7399
+ "screen.shared",
7400
+ "screen.stats"
7401
+ ];
7402
+ var HOST_CAPABILITY_METHODS = [
7403
+ // 宿主上下文 / 热更新 / 窗口
7404
+ "host.context",
7405
+ "update.check",
7406
+ "update.apply",
7407
+ "window.setSize",
7408
+ // Worker(G-39 后台线程)
7409
+ "worker.create",
7410
+ "worker.post",
7411
+ "worker.terminate",
7412
+ // 空闲回调 / 预加载 / 扩展
7413
+ "idle.request",
7414
+ "idle.cancel",
7415
+ "preload.assets",
7416
+ "extension.load",
7417
+ // 跳其他小程序(App 端对齐 Web:通常无对等)
7418
+ "mini-program.navigate",
7419
+ // WebAssembly(宿主运行时;★引擎内置 WASM 的端可不实现——见 HOST_METHOD_OPTIONAL)
7420
+ "webassembly.instantiate",
7421
+ "webassembly.validate",
7422
+ "webassembly.call",
7423
+ "webassembly.release",
7424
+ // 宿主自报调用记账(判据"能力经壳执行")
7425
+ "native.calls"
7426
+ ];
7427
+ var HOST_METHOD_OPTIONAL = {
7428
+ // iOS JSC 内建 WebAssembly(`typeof WebAssembly === 'object'`)⇒ 壳不实现 `webassembly.*`。
7429
+ ios: ["webassembly.instantiate", "webassembly.validate", "webassembly.call", "webassembly.release"]
7430
+ // 鸿蒙:能力族尚未接线(memUsage/gc 之外)——★**缺失已登记**(非合法省略;门禁以"覆盖率"报告,不静默)。
7431
+ };
7432
+ var HOST_INVOKE_METHODS = [...HOST_CAPABILITY_METHODS, ...HOST_SCREEN_METHODS];
7433
+
7434
+ // src/tab-bar-spec.ts
7435
+ var TAB_BAR_SPEC = {
7436
+ height: 56,
7437
+ surface: "#ffffff",
7438
+ line: "#dcdfe5",
7439
+ brand: "#5b5bd6",
7440
+ text3: "#5f6673",
7441
+ badgeBg: "#d64545",
7442
+ iconSize: 19,
7443
+ labelSize: 10,
7444
+ badge: { radius: 8, fontSize: 11, offsetX: 4, offsetY: 4, padX: 4 },
7445
+ icons: { index: "\u2302", messages: "\u2709", mine: "\u263A" },
7446
+ labels: { index: "\u9996\u9875", messages: "\u6D88\u606F", mine: "\u6211\u7684" }
7447
+ };
7448
+
7449
+ // src/superapp-runtime.ts
7450
+ function createSuperappRuntime(opts) {
7451
+ const note = opts.onNote ?? (() => {
7452
+ });
7453
+ const cbName = opts.gestureCbName ?? "__proteusRuntimeGesture";
7454
+ const rt = createScreenRuntime({
7455
+ artifacts: opts.artifacts,
7456
+ applyOps: (ops) => {
7457
+ opts.host.applyOps(ops);
7458
+ },
7459
+ viewport: opts.viewport,
7460
+ ...opts.navigate ? { navigate: opts.navigate } : {},
7461
+ ...opts.seedData ? { seedData: opts.seedData } : {},
7462
+ onNote: note
7463
+ });
7464
+ let cur = "";
7465
+ let lastHostReply = null;
7466
+ const devEvents = [];
7467
+ if (typeof opts.host.onGesture === "function") {
7468
+ opts.host.onGesture(cbName);
7469
+ }
7470
+ ;
7471
+ globalThis[cbName] = (type, kernelId, chainJson) => {
7472
+ let chain = [];
7473
+ try {
7474
+ chain = chainJson ? JSON.parse(chainJson) : [];
7475
+ } catch {
7476
+ note(`[superapp-runtime] \u624B\u52BF\u94FE JSON \u89E3\u6790\u5931\u8D25\uFF1A${String(chainJson).slice(0, 40)}`);
7477
+ }
7478
+ const r = dispatch(type, chain);
7479
+ devEvents.push({ type, id: kernelId, chain, handled: r.handled, fired: r.fired, time: Date.now() });
7480
+ if (devEvents.length > 200) devEvents.shift();
7481
+ return JSON.stringify(r);
7482
+ };
7483
+ function hostOk(reply) {
7484
+ try {
7485
+ const o = JSON.parse(reply);
7486
+ return o?.ok !== false;
7487
+ } catch {
7488
+ return true;
7489
+ }
7490
+ }
7491
+ function dispatch(type, chain) {
7492
+ if (!cur) return { handled: false, fired: [] };
7493
+ return rt.instance(cur).dispatch(type, chain);
7494
+ }
7495
+ const scrollByScreen = /* @__PURE__ */ new Map();
7496
+ const history = [];
7497
+ let curIdx = -1;
7498
+ return {
7499
+ mountScreen(name) {
7500
+ if (!rt.has(name)) {
7501
+ note(`[superapp-runtime] \u65E0\u8BE5\u5C4F\u8FD0\u884C\u671F\u4EA7\u7269\uFF1A${name}`);
7502
+ return false;
7503
+ }
7504
+ if (name === cur) return true;
7505
+ if (cur && typeof opts.host.getScroll === "function") {
7506
+ scrollByScreen.set(cur, opts.host.getScroll());
7507
+ }
7508
+ const existing = history.indexOf(name);
7509
+ let restore = 0;
7510
+ if (existing >= 0 && existing < curIdx) {
7511
+ history.length = existing + 1;
7512
+ curIdx = existing;
7513
+ restore = scrollByScreen.get(name) ?? 0;
7514
+ } else {
7515
+ history.length = curIdx + 1;
7516
+ history.push(name);
7517
+ curIdx = history.length - 1;
7518
+ scrollByScreen.set(name, 0);
7519
+ restore = 0;
7520
+ }
7521
+ const inst = rt.instance(name);
7522
+ const reply = opts.host.mount(JSON.stringify({ viewport: inst.content().viewport, nodes: inst.content().nodes }));
7523
+ lastHostReply = reply;
7524
+ if (!hostOk(reply)) {
7525
+ note(`[superapp-runtime] \u5BBF\u4E3B mount \u5931\u8D25\uFF1A${String(reply).slice(0, 200)}`);
7526
+ return false;
7527
+ }
7528
+ if (typeof opts.host.setScroll === "function") opts.host.setScroll(restore);
7529
+ cur = name;
7530
+ return true;
7531
+ },
7532
+ mountScreenInto(name) {
7533
+ if (!rt.has(name)) {
7534
+ note(`[superapp-runtime] \u65E0\u8BE5\u5C4F\u8FD0\u884C\u671F\u4EA7\u7269\uFF1A${name}`);
7535
+ return false;
7536
+ }
7537
+ const inst = rt.instance(name);
7538
+ const reply = opts.host.mount(JSON.stringify({ viewport: inst.content().viewport, nodes: inst.content().nodes }));
7539
+ lastHostReply = reply;
7540
+ if (!hostOk(reply)) {
7541
+ note(`[superapp-runtime] \u5BBF\u4E3B mount\uFF08\u91CD\u6302\uFF09\u5931\u8D25\uFF1A${String(reply).slice(0, 200)}`);
7542
+ return false;
7543
+ }
7544
+ cur = name;
7545
+ return true;
7546
+ },
7547
+ dispatchGesture: dispatch,
7548
+ current: () => cur,
7549
+ // ★DevTools 元素内省(决策 #674):当前屏已实例化节点(避免暴露整个 rt/instance 给桥)。
7550
+ currentContent: () => {
7551
+ if (!cur) return null;
7552
+ try {
7553
+ return rt.instance(cur).content();
7554
+ } catch {
7555
+ return null;
7556
+ }
7557
+ },
7558
+ // ★排空式:返回自上次调用以来的手势 trace,并清空(宿主每次轮询取走)。
7559
+ devEvents: () => {
7560
+ const out = devEvents.slice();
7561
+ devEvents.length = 0;
7562
+ return out;
7563
+ },
7564
+ snapshot: () => rt.snapshot(),
7565
+ lastHostReply: () => lastHostReply
7566
+ };
7567
+ }
4630
7568
  export {
4631
7569
  Borrow,
4632
7570
  CONTAINER_PROFILES,
@@ -4636,7 +7574,11 @@ export {
4636
7574
  FIVE_ATOMIC_STEPS,
4637
7575
  FORK_SIGNATURES,
4638
7576
  HOSTS,
7577
+ HOST_CAPABILITY_METHODS,
4639
7578
  HOST_ENGINE_MATRIX,
7579
+ HOST_INVOKE_METHODS,
7580
+ HOST_METHOD_OPTIONAL,
7581
+ HOST_SCREEN_METHODS,
4640
7582
  Managed,
4641
7583
  ManagedRegistry,
4642
7584
  OWNERSHIP_ERRORS,
@@ -4644,7 +7586,9 @@ export {
4644
7586
  OwnershipError,
4645
7587
  OwnershipGraph,
4646
7588
  PAGE_STATE_TRANSITIONS,
7589
+ SCREEN_ANIM_DONE_KEY,
4647
7590
  SENSITIVE_CAPABILITIES,
7591
+ TAB_BAR_SPEC,
4648
7592
  Weak,
4649
7593
  analyzeOwnershipSource,
4650
7594
  analyzePss,
@@ -4662,6 +7606,7 @@ export {
4662
7606
  createFlutterBackend,
4663
7607
  createHeadlessBackend,
4664
7608
  createHostRuntimeStub,
7609
+ createHostScreenPorts,
4665
7610
  createHybridRenderer,
4666
7611
  createMiniProgramContainer,
4667
7612
  createMockNativeAdapter,
@@ -4672,12 +7617,17 @@ export {
4672
7617
  createPageOwnership,
4673
7618
  createProteusRenderer,
4674
7619
  createProteusRendererForBackend,
7620
+ createQuickJsHostRuntime,
4675
7621
  createQuotaManager,
4676
7622
  createQuotaTracker,
4677
7623
  createResourcePool,
7624
+ createScreenExecutor,
7625
+ createScreenRuntime,
7626
+ createSelfDrawBatchAdapter,
4678
7627
  createSinglePageContainer,
4679
7628
  createStackContainer,
4680
7629
  createSuperAppContainer,
7630
+ createSuperappRuntime,
4681
7631
  createVueDomBackend,
4682
7632
  createVueRendererOptions,
4683
7633
  createWebHostRuntime,
@@ -4703,6 +7653,7 @@ export {
4703
7653
  runHybridConformance,
4704
7654
  runPss,
4705
7655
  scanRepoForFork,
7656
+ screenContentFromLayoutTemplate,
4706
7657
  semanticSequence,
4707
7658
  textureRef,
4708
7659
  toPlainTree,