@cardstack/boxel-cli 0.11.0-unstable.1 → 0.11.0-unstable.3
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/bundled-realms/pretui/components/agent-mark.gts +98 -0
- package/bundled-realms/pretui/components/agent-question.gts +340 -37
- package/bundled-realms/pretui/components/answer-footer.gts +249 -0
- package/bundled-realms/pretui/components/card-embed.gts +523 -0
- package/bundled-realms/pretui/components/card-pills.gts +725 -0
- package/bundled-realms/pretui/components/code-block.gts +87 -45
- package/bundled-realms/pretui/components/collapsed-turn.gts +133 -12
- package/bundled-realms/pretui/components/compaction-chip.gts +81 -67
- package/bundled-realms/pretui/components/composer-cap.gts +516 -0
- package/bundled-realms/pretui/components/context-ring.gts +108 -0
- package/bundled-realms/pretui/components/diff-block.gts +1002 -31
- package/bundled-realms/pretui/components/diff-stat.gts +102 -0
- package/bundled-realms/pretui/components/jump-chip.gts +86 -53
- package/bundled-realms/pretui/components/markdown-stream.gts +799 -0
- package/bundled-realms/pretui/components/markdown-surface.gts +71 -0
- package/bundled-realms/pretui/components/plan-mark.gts +147 -0
- package/bundled-realms/pretui/components/prose.gts +185 -8
- package/bundled-realms/pretui/components/prose.md +6 -8
- package/bundled-realms/pretui/components/quick-look.gts +377 -0
- package/bundled-realms/pretui/components/reactions.gts +136 -0
- package/bundled-realms/pretui/components/recommendation.gts +67 -36
- package/bundled-realms/pretui/components/result-card.gts +58 -22
- package/bundled-realms/pretui/components/segmented-control.gts +13 -8
- package/bundled-realms/pretui/components/session-prep.gts +12 -11
- package/bundled-realms/pretui/components/sliding-highlight.gts +8 -3
- package/bundled-realms/pretui/components/speaker-label.gts +132 -0
- package/bundled-realms/pretui/components/state-glyph.gts +121 -0
- package/bundled-realms/pretui/components/step-list.gts +62 -60
- package/bundled-realms/pretui/components/stick-to-bottom.gts +1150 -0
- package/bundled-realms/pretui/components/system-line.gts +143 -0
- package/bundled-realms/pretui/components/task-checklist.gts +184 -0
- package/bundled-realms/pretui/components/thinking.gts +63 -94
- package/bundled-realms/pretui/components/tool-change-table.gts +285 -0
- package/bundled-realms/pretui/components/tool-gate.gts +462 -0
- package/bundled-realms/pretui/components/tool-group.gts +920 -0
- package/bundled-realms/pretui/components/tool-icon.ts +64 -0
- package/bundled-realms/pretui/components/tool-meta.gts +82 -0
- package/bundled-realms/pretui/components/tool-plan.gts +197 -0
- package/bundled-realms/pretui/components/tool-row.gts +2087 -0
- package/bundled-realms/pretui/components/turn-changes.gts +227 -0
- package/bundled-realms/pretui/components/user-bubble.gts +575 -0
- package/bundled-realms/pretui/components/work-item.gts +185 -48
- package/bundled-realms/pretui/docs/scroll-anchoring-protocol.md +136 -0
- package/bundled-realms/pretui/internal/markdown-projection.ts +438 -0
- package/bundled-realms/pretui/internal/mount-queue.ts +79 -0
- package/bundled-realms/pretui/internal/tool-phrases.ts +168 -0
- package/bundled-realms/pretui/package.json +2 -1
- package/bundled-test-harness/assets/{ai-module-creation-test-beRNwH-e.js → ai-module-creation-test-B6FQKqIl.js} +1 -1
- package/bundled-test-harness/assets/{app-QAuw0l8v.js → app-KpPqdyX3.js} +4 -4
- package/bundled-test-harness/assets/{binary-upload-dedupe-test-bI8T0QLl.js → binary-upload-dedupe-test-DN00y1tM.js} +1 -1
- package/bundled-test-harness/assets/{can-invoke-test-DVaU_yRT.js → can-invoke-test-Bl-EUFM3.js} +1 -1
- package/bundled-test-harness/assets/{card-chooser-paging-test-DnxMnXKR.js → card-chooser-paging-test-BNuZRHX3.js} +1 -1
- package/bundled-test-harness/assets/{card-chooser-test-DUSnLr-u.js → card-chooser-test-wKfPOOT4.js} +1 -1
- package/bundled-test-harness/assets/{card-def-field-def-relationships-test-GBUr3tFK.js → card-def-field-def-relationships-test-CduQ6zan.js} +1 -1
- package/bundled-test-harness/assets/{card-delete-test-CQveO1VC.js → card-delete-test-DKxolj5o.js} +1 -1
- package/bundled-test-harness/assets/{check-correctness-test-D2M2p66N.js → check-correctness-test-B-kgHztp.js} +1 -1
- package/bundled-test-harness/assets/choreo-BEHW-NOc.js +2 -0
- package/bundled-test-harness/assets/{choreo-CJfBb_EY.js → choreo-C2cpCfhA.js} +164 -13
- package/bundled-test-harness/assets/{choreo-feature-reel-test-D2OrygZv.js → choreo-feature-reel-test-CPI_6nkT.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-w07xqtO-.js → choreo-gallery-BxA9cyzF.js} +3 -3
- package/bundled-test-harness/assets/{choreo-gallery-build-order-test-yQg4JER6.js → choreo-gallery-build-order-test-CLVoseAJ.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-compositor-test-C2THCXJN.js → choreo-gallery-compositor-test-BpT5Ia6B.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-cover-test-D4T_Tk4s.js → choreo-gallery-cover-test-DL_fdSsh.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-crossing-stage-test-tHUPJ-Hy.js → choreo-gallery-crossing-stage-test-BcKHhpOK.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-demos-alive-test-BOtBOnEZ.js → choreo-gallery-demos-alive-test-KufLhXG8.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-drift-test-D_6U1Z2c.js → choreo-gallery-drift-test-Cx58rYIA.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-escort-test-DDBU0n4C.js → choreo-gallery-escort-test-Wz-emmID.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-films-test-CgW1NSdr.js → choreo-gallery-films-test-BFrVs5WW.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-filter-test-DUIsNwdO.js → choreo-gallery-filter-test-DCKQkhwe.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-fold-test-Chzlxvdr.js → choreo-gallery-fold-test-UtpDgpG9.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-guide-pages-test-DOWnqALT.js → choreo-gallery-guide-pages-test-rXDU9JLM.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-guides-test-Dra2J4LJ.js → choreo-gallery-guides-test-DfVFdIuU.js} +3 -3
- package/bundled-test-harness/assets/{choreo-gallery-hang-test-zzn1eiB-.js → choreo-gallery-hang-test-Cj7MbkFf.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-highlight-test-CW99hncQ.js → choreo-gallery-highlight-test-D6CF3IYZ.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-inline-edit-test-CYFm1kUz.js → choreo-gallery-inline-edit-test-LfHWsbqc.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-interruption-test-CYIBrKV9.js → choreo-gallery-interruption-test-CvQQAgTp.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-lone-stack-test-DL8R3SG7.js → choreo-gallery-lone-stack-test-D7Zci7fm.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-navigation-test-Dcv04kDx.js → choreo-gallery-navigation-test-DB1EnBEW.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-playhead-transport-test-Cfz7GsXl.js → choreo-gallery-playhead-transport-test-CtBlc6AJ.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-poplayout-subtree-test-CwoklBkW.js → choreo-gallery-poplayout-subtree-test-BQbmjsvI.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-presence-modes-test-BDCIZzJ0.js → choreo-gallery-presence-modes-test-CIdXVG1K.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-presentation-test-DlyiHwFf.js → choreo-gallery-presentation-test-C_rPFp29.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-reduced-motion-test-DVz5SKNR.js → choreo-gallery-reduced-motion-test-CH5w8Lw-.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-sheet-standalone-test-CpD_-BvS.js → choreo-gallery-sheet-standalone-test-CDgI4mGX.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-sheet-test-BjvAKHDL.js → choreo-gallery-sheet-test-5lsFX7Rk.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-site-test-oq_7y_PN.js → choreo-gallery-site-test-DbMa2xeC.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-stage-Bzr4_8Xd.js → choreo-gallery-stage-b3Sk86hp.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-stage-keyboard-test-a83d2euJ.js → choreo-gallery-stage-keyboard-test-ds6fT5Eg.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-stages-test-C9Sl1kH6.js → choreo-gallery-stages-test-ahioKCct.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-subdivision-test-BVVnkOwk.js → choreo-gallery-subdivision-test-g7anTDhT.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-table-plan-test-B0YdYEA0.js → choreo-gallery-table-plan-test-Bozc4uUf.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-theater-test-BLA147xw.js → choreo-gallery-theater-test-kE535d-b.js} +3 -3
- package/bundled-test-harness/assets/{choreo-gallery-tuning-test-DSXiNzCh.js → choreo-gallery-tuning-test-6rHkQBnZ.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-widget-room-test-BI574PdO.js → choreo-gallery-widget-room-test-0Vq-uTo1.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-wires-test-B6Mz2WZl.js → choreo-gallery-wires-test-CdtnAWYO.js} +1 -1
- package/bundled-test-harness/assets/clip-BLfuHi8g.js +2 -0
- package/bundled-test-harness/assets/{clip-C_pXjVvY.js → clip-q1QE5f73.js} +1 -1
- package/bundled-test-harness/assets/{codeblocks-test-AdolhdCA.js → codeblocks-test-BDD7qK_V.js} +1 -1
- package/bundled-test-harness/assets/{compaction-test-DvyAQq56.js → compaction-test-BweolkhG.js} +1 -1
- package/bundled-test-harness/assets/{container-DIF_9lZ9.css → container-B5B6u-xm.css} +29 -22
- package/bundled-test-harness/assets/{container-DvvqmNpg.js → container-BnJjIM6J.js} +9 -6
- package/bundled-test-harness/assets/{create-listing-modal-test-D95JfQab.js → create-listing-modal-test-FvcAKpIa.js} +1 -1
- package/bundled-test-harness/assets/{debug-message-test-BcNtj7Wr.js → debug-message-test-CJ18Evt7.js} +1 -1
- package/bundled-test-harness/assets/{externals-6ZWftYIb.js → externals-BWjhmCD4.js} +12 -12
- package/bundled-test-harness/assets/{fallback-banner-live-delete-test-5lnN1pv2.js → fallback-banner-live-delete-test-C2H3BrT-.js} +1 -1
- package/bundled-test-harness/assets/{fallback-banner-test-DFb_m_Kq.js → fallback-banner-test-C10aYjkj.js} +1 -1
- package/bundled-test-harness/assets/{fallback-models-test-p3YIwj1L.js → fallback-models-test-BxoPiaGF.js} +1 -1
- package/bundled-test-harness/assets/{file-attachment-test-CmyjNWRR.js → file-attachment-test-DeKyzYaz.js} +1 -1
- package/bundled-test-harness/assets/film-B68PjRlr.js +2 -0
- package/bundled-test-harness/assets/{film-D7vtJO5q.js → film-CeUpnHN_.js} +6 -6
- package/bundled-test-harness/assets/{film-DN0A8JHx.js → film-DBZqyqW4.js} +6 -6
- package/bundled-test-harness/assets/{general-test-CX-TFDuX.js → general-test-BewzrpoB.js} +1 -1
- package/bundled-test-harness/assets/{glimmer-motion-and-choreo-in-cards-test-BHte2t9G.js → glimmer-motion-and-choreo-in-cards-test-HA-INN3d.js} +14 -14
- package/bundled-test-harness/assets/{glimmer-motion-and-choreo-test-BPNVeIio.js → glimmer-motion-and-choreo-test-BZ0ZuuAw.js} +3 -3
- package/bundled-test-harness/assets/{index-query-engine-test-Dz7Q4Xmk.js → index-query-engine-test-BtI8Y2LR.js} +1 -1
- package/bundled-test-harness/assets/{instance-filter-matcher-test-BoLU3bVg.js → instance-filter-matcher-test-BHXHGHjE.js} +1 -1
- package/bundled-test-harness/assets/{main-Dlgp8c7v.js → main-CqAHZ2kK.js} +1 -1
- package/bundled-test-harness/assets/{message-tool-test-CfjRSJvt.js → message-tool-test-NMaPJFsd.js} +1 -1
- package/bundled-test-harness/assets/{model-cost-tier-test-mZG84Uso.js → model-cost-tier-test-BgbfN2LH.js} +1 -1
- package/bundled-test-harness/assets/{operation-lowering-test-B0KpI1xu.js → operation-lowering-test-C4IaOjsA.js} +1 -1
- package/bundled-test-harness/assets/{operator-mode-card-chooser-test-KWS8lq16.js → operator-mode-card-chooser-test-Bwe47Rn7.js} +1 -1
- package/bundled-test-harness/assets/{operator-mode-links-test-CViFt0Ws.js → operator-mode-links-test-BU4jkC-B.js} +1 -1
- package/bundled-test-harness/assets/{operator-mode-test-D0gZM2yw.js → operator-mode-test-Bh0rGM9z.js} +1 -1
- package/bundled-test-harness/assets/{operator-mode-ui-test-CEvHJ9DS.js → operator-mode-ui-test-whgUw0ju.js} +1 -1
- package/bundled-test-harness/assets/{overlay-menu-items-test-Pw80RXsr.js → overlay-menu-items-test-Cwn1vr8q.js} +1 -1
- package/bundled-test-harness/assets/{overlays-BnOM360u.js → overlays-BOzLbVDK.js} +1 -1
- package/bundled-test-harness/assets/{overlays-kBVfa4i8.js → overlays-C_PuGDGh.js} +1 -1
- package/bundled-test-harness/assets/{past-sessions-test-CW0-Bjav.js → past-sessions-test-Bh24N-Kz.js} +1 -1
- package/bundled-test-harness/assets/{permission-checking-realm-test-D5uXQypS.js → permission-checking-realm-test-DKnnAy0f.js} +1 -1
- package/bundled-test-harness/assets/{plate-CFvCDv2t.js → plate-BL7UtyKE.js} +1 -1
- package/bundled-test-harness/assets/plate-CqG3igCJ.js +2 -0
- package/bundled-test-harness/assets/player-BJfsgdSI.js +2 -0
- package/bundled-test-harness/assets/{player-DzuYjrTU.js → player-Da_CIsp_.js} +1 -1
- package/bundled-test-harness/assets/{realm-config-policy-standing-test-Btatheni.js → realm-config-policy-standing-test-DngdYypq.js} +1 -1
- package/bundled-test-harness/assets/{realm-config-routing-rule-edit-test-Dh5zW13o.js → realm-config-routing-rule-edit-test-_rkFU8SN.js} +1 -1
- package/bundled-test-harness/assets/{realm-config-settings-test-BJa8gv_C.js → realm-config-settings-test-CR9NH0Zn.js} +1 -1
- package/bundled-test-harness/assets/{reasoning-test-2Zp37cQB.js → reasoning-test-0lH10kRl.js} +1 -1
- package/bundled-test-harness/assets/{room-message-test-BsrON13e.js → room-message-test-C-PddE4Y.js} +1 -1
- package/bundled-test-harness/assets/{run-BMyvn_UL.js → run-D4n4LPcK.js} +96 -6
- package/bundled-test-harness/assets/{run-realm-code-BxktQf6R.js → run-realm-code-C8N7DtN-.js} +1 -1
- package/bundled-test-harness/assets/{run-realm-code-test-7grpTos-.js → run-realm-code-test-DBdp147q.js} +1 -1
- package/bundled-test-harness/assets/{schema-generation-test-CM9ggQji.js → schema-generation-test-CB1Hb7GQ.js} +1 -1
- package/bundled-test-harness/assets/{scrolling-test-BER00htB.js → scrolling-test-3PhVin8h.js} +1 -1
- package/bundled-test-harness/assets/{searchable-option-test-BBr5BPow.js → searchable-option-test-BAdOiL4U.js} +1 -1
- package/bundled-test-harness/assets/{sending-test-BJGobs2E.js → sending-test-KSmu1tSJ.js} +1 -1
- package/bundled-test-harness/assets/{skills-test-GPOsHRN6.js → skills-test-D-dlcFpd.js} +1 -1
- package/bundled-test-harness/assets/{src-Df148w_b.js → src-B_DEhPze.js} +4 -4
- package/bundled-test-harness/assets/{src-Cf2wv-Le.js → src-BoTgwqUA.js} +2 -2
- package/bundled-test-harness/assets/{store-test-QvvHimzH.js → store-test-CEW7xQjZ.js} +1 -1
- package/bundled-test-harness/assets/{test-support-rNHuieEc.js → test-support-FXDPpIl3.js} +1 -1
- package/bundled-test-harness/assets/{test-support-C6N_2X-l.js → test-support-VX6ukQNR.js} +1 -1
- package/bundled-test-harness/assets/{tests-CmsQ-Z_3.js → tests-QYqqa6nv.js} +84 -84
- package/bundled-test-harness/assets/{text-input-validator-test-CYYH2N0L.js → text-input-validator-test-D8iNOWep.js} +1 -1
- package/bundled-test-harness/assets/{titles-B1pTaIfP.js → titles-DFJj5O0E.js} +1 -1
- package/bundled-test-harness/assets/titles-xGlgu7ys.js +2 -0
- package/bundled-test-harness/assets/{to-device-streaming-test-Ctqa0bVp.js → to-device-streaming-test-trCX-505.js} +1 -1
- package/bundled-test-harness/assets/{token-usage-test-BQVbK3X0.js → token-usage-test-C6sULooH.js} +1 -1
- package/bundled-test-harness/assets/{tools-DU8Rc15s.js → tools-UJfD-_al.js} +1 -1
- package/bundled-test-harness/assets/{tools-test-B13yvEKE.js → tools-test-Brav31AV.js} +1 -1
- package/bundled-test-harness/assets/{worker-DHBEBihX.js → worker-9ph9_Lpv.js} +31 -26
- package/bundled-test-harness/index.html +8 -8
- package/bundled-test-harness/tests/index.html +8 -8
- package/bundled-types/choreo/beacons.ts +16 -4
- package/bundled-types/choreo/changeset.ts +25 -5
- package/bundled-types/choreo/choreo.gts +245 -3
- package/bundled-types/choreo/far.ts +39 -0
- package/bundled-types/choreo/index.ts +2 -0
- package/bundled-types/choreo/run.ts +104 -1
- package/bundled-types/host-app/lib/realm-runner/worker.ts +8 -51
- package/bundled-types/runtime-common/realm-runner-script.d.ts +3 -0
- package/package.json +1 -1
- package/bundled-test-harness/assets/choreo-B3Z9QhDH.js +0 -2
- package/bundled-test-harness/assets/clip-BjcXuTwN.js +0 -2
- package/bundled-test-harness/assets/film-DMDPP7ma.js +0 -2
- package/bundled-test-harness/assets/plate-66qnQ-M9.js +0 -2
- package/bundled-test-harness/assets/player-pygg1PwA.js +0 -2
- package/bundled-test-harness/assets/titles-Ccpcl-jq.js +0 -2
|
@@ -206,6 +206,35 @@ interface Signature {
|
|
|
206
206
|
/** outline the region and its participants; console.table each run */
|
|
207
207
|
debug?: boolean;
|
|
208
208
|
id?: string;
|
|
209
|
+
/**
|
|
210
|
+
* Sit this region out of render passes: while true it takes no snapshot
|
|
211
|
+
* and joins no barrier, so it plays nothing and costs nothing, and its
|
|
212
|
+
* participants stay registered and its block stays rendered. A page with
|
|
213
|
+
* many regions of which few can change at once (a message per region in
|
|
214
|
+
* a long transcript) keeps the rest idle; the pass that sets it back to
|
|
215
|
+
* false is the first one the region watches again.
|
|
216
|
+
*/
|
|
217
|
+
idle?: boolean;
|
|
218
|
+
/**
|
|
219
|
+
* Wire only what can be seen: the region measures and animates only the
|
|
220
|
+
* participants inside its scroller's view, or close enough to it
|
|
221
|
+
* (`@inViewMargin` views beyond each edge, default 1) that a move may
|
|
222
|
+
* bring them on screen. The rest are left to layout: no snapshot, no
|
|
223
|
+
* measuring, no motion. A participant coming within the margin is wired
|
|
224
|
+
* and one leaving it unwired, and neither counts as an arrival or a
|
|
225
|
+
* removal. A pass that moves something farther than the margin is a
|
|
226
|
+
* jump, not a change, and lands as layout puts it: animating it would
|
|
227
|
+
* fly blocks past ones that were never wired.
|
|
228
|
+
*/
|
|
229
|
+
inView?: boolean;
|
|
230
|
+
inViewMargin?: number;
|
|
231
|
+
/**
|
|
232
|
+
* The most animation time, in ms, one painted frame may advance: a run
|
|
233
|
+
* behind a long frame slows down instead of skipping ahead, so nothing
|
|
234
|
+
* jumps on a busy page. Unset, a frame advances by the time since the
|
|
235
|
+
* last one (up to 40 ms).
|
|
236
|
+
*/
|
|
237
|
+
maxFrameStep?: number;
|
|
209
238
|
/**
|
|
210
239
|
* A `c.Camera3D` cue moved the shot. Called every frame it changes —
|
|
211
240
|
* scrubs included, since the pose is sampled from the score — with a
|
|
@@ -353,6 +382,29 @@ function reducesMotion(root: Element): boolean {
|
|
|
353
382
|
);
|
|
354
383
|
}
|
|
355
384
|
|
|
385
|
+
/** whether an element scrolls its content on either axis */
|
|
386
|
+
function scrolls(el: Element): boolean {
|
|
387
|
+
const { overflowX, overflowY } = getComputedStyle(el);
|
|
388
|
+
return [overflowX, overflowY].some(
|
|
389
|
+
(o) => o === 'auto' || o === 'scroll' || o === 'overlay',
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** the nearest ancestor that scrolls vertically: what `@inView` calls the view */
|
|
394
|
+
function scrollerOf(el: Element): HTMLElement | null {
|
|
395
|
+
for (let p = el.parentElement; p; p = p.parentElement) {
|
|
396
|
+
const overflow = getComputedStyle(p).overflowY;
|
|
397
|
+
if (
|
|
398
|
+
overflow === 'auto' ||
|
|
399
|
+
overflow === 'scroll' ||
|
|
400
|
+
overflow === 'overlay'
|
|
401
|
+
) {
|
|
402
|
+
return p;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
|
|
356
408
|
/**
|
|
357
409
|
* The barrier books one post-render callback for every region taking part in a
|
|
358
410
|
* pass. `joinPass(this)` is where the region's PassHost contract (measurePass /
|
|
@@ -392,6 +444,9 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
|
|
|
392
444
|
private arrived = new Set<ChoreoNode>();
|
|
393
445
|
/** destroyed before the pass that removed them has been processed */
|
|
394
446
|
private claimed = new Set<ChoreoNode>();
|
|
447
|
+
/** every beacon name this region's score has read, so a pass measures
|
|
448
|
+
* those beacons and not the whole page's */
|
|
449
|
+
readonly beaconNames = new Set<string>();
|
|
395
450
|
/** removed participants this region is keeping alive in its orphan layer */
|
|
396
451
|
private orphans = new Set<ChoreoNode>();
|
|
397
452
|
/** participants whose Presence is letting them go: removed once, not on every pass while they leave */
|
|
@@ -443,6 +498,7 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
|
|
|
443
498
|
this.run = undefined;
|
|
444
499
|
// a quiet region must not take its pause to the grave
|
|
445
500
|
this.unquiet();
|
|
501
|
+
this.viewObserver?.disconnect();
|
|
446
502
|
for (const node of [...this.orphans, ...this.claimed]) {
|
|
447
503
|
this.drop(node);
|
|
448
504
|
}
|
|
@@ -474,6 +530,7 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
|
|
|
474
530
|
register(node: ChoreoNode) {
|
|
475
531
|
this.participants.add(node);
|
|
476
532
|
this.arrived.add(node);
|
|
533
|
+
this.watchView(node);
|
|
477
534
|
if (this.args.debug && node.element) {
|
|
478
535
|
(node.element as HTMLElement).style.outline = '1px dotted #16a34a';
|
|
479
536
|
// the seam lints (§5.3): inside a region the timeline is the only
|
|
@@ -495,9 +552,120 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
|
|
|
495
552
|
// a destroyed participant may still be claimed (below); the sets that decide that survive until drop()
|
|
496
553
|
this.participants.delete(node);
|
|
497
554
|
this.arrived.delete(node);
|
|
555
|
+
this.unwatchView(node);
|
|
498
556
|
};
|
|
499
557
|
}
|
|
500
558
|
|
|
559
|
+
/** sat out a pass since the last one it watched (`@idle`) */
|
|
560
|
+
private slept = false;
|
|
561
|
+
|
|
562
|
+
/* ---- @inView: what is wired ---- */
|
|
563
|
+
|
|
564
|
+
/** participants inside the view or its margin, as the observer last saw them */
|
|
565
|
+
private nearView = new Set<ChoreoNode>();
|
|
566
|
+
/** what the last pass animated: kept wired until a pass lets it go, so a
|
|
567
|
+
* block that leaves the margin mid-flight is not cut loose halfway */
|
|
568
|
+
private flying = new Set<ChoreoNode>();
|
|
569
|
+
private viewNodes = new WeakMap<Element, ChoreoNode>();
|
|
570
|
+
private viewObserver?: IntersectionObserver;
|
|
571
|
+
private viewRoot: HTMLElement | null = null;
|
|
572
|
+
/** the `@inViewMargin` the current observer was made with */
|
|
573
|
+
private viewMarginUsed?: number;
|
|
574
|
+
|
|
575
|
+
private get viewMargin() {
|
|
576
|
+
return this.args.inViewMargin ?? 1;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
private watchView(node: ChoreoNode) {
|
|
580
|
+
const el = node.element;
|
|
581
|
+
if (!this.args.inView || !el) {
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (!this.viewObserver) {
|
|
585
|
+
this.viewRoot = this.element ? scrollerOf(this.element) : null;
|
|
586
|
+
this.viewMarginUsed = this.viewMargin;
|
|
587
|
+
this.viewObserver = new IntersectionObserver(
|
|
588
|
+
(entries) => {
|
|
589
|
+
for (const entry of entries) {
|
|
590
|
+
const seen = this.viewNodes.get(entry.target);
|
|
591
|
+
if (!seen) {
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
if (entry.isIntersecting) {
|
|
595
|
+
this.nearView.add(seen);
|
|
596
|
+
} else {
|
|
597
|
+
this.nearView.delete(seen);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
root: this.viewRoot,
|
|
603
|
+
rootMargin: `${this.viewMargin * 100}% 0px`,
|
|
604
|
+
},
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
this.viewNodes.set(el, node);
|
|
608
|
+
this.viewObserver.observe(el);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* `@inView` and `@inViewMargin` can change after participants registered:
|
|
613
|
+
* switched on (or given a new margin), the region observes every current
|
|
614
|
+
* participant and wires the ones already near the view now (the observer
|
|
615
|
+
* reports only after the next frame); switched off, it lets the observer
|
|
616
|
+
* go and wires everything again.
|
|
617
|
+
*/
|
|
618
|
+
private syncViewWatch() {
|
|
619
|
+
if (
|
|
620
|
+
this.args.inView &&
|
|
621
|
+
this.viewObserver &&
|
|
622
|
+
this.viewMarginUsed !== this.viewMargin
|
|
623
|
+
) {
|
|
624
|
+
// the observer's margin is fixed when it is made: a new margin makes a
|
|
625
|
+
// new observer
|
|
626
|
+
this.viewObserver.disconnect();
|
|
627
|
+
this.viewObserver = undefined;
|
|
628
|
+
this.nearView.clear();
|
|
629
|
+
}
|
|
630
|
+
if (this.args.inView && !this.viewObserver) {
|
|
631
|
+
for (const node of this.participants) {
|
|
632
|
+
this.watchView(node);
|
|
633
|
+
}
|
|
634
|
+
if (this.viewObserver) {
|
|
635
|
+
const near = this.nearViewBand();
|
|
636
|
+
for (const node of this.participants) {
|
|
637
|
+
const r = node.element?.getBoundingClientRect();
|
|
638
|
+
if (r && r.bottom >= near.top && r.top <= near.bottom) {
|
|
639
|
+
this.nearView.add(node);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
} else if (!this.args.inView && this.viewObserver) {
|
|
644
|
+
this.viewObserver.disconnect();
|
|
645
|
+
this.viewObserver = undefined;
|
|
646
|
+
this.viewRoot = null;
|
|
647
|
+
this.nearView.clear();
|
|
648
|
+
this.flying.clear();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
private unwatchView(node: ChoreoNode) {
|
|
653
|
+
this.nearView.delete(node);
|
|
654
|
+
if (node.element) {
|
|
655
|
+
this.viewObserver?.unobserve(node.element);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/** the view and its margin, in viewport px; read once per pass */
|
|
660
|
+
private nearViewBand() {
|
|
661
|
+
const r = this.viewRoot
|
|
662
|
+
? this.viewRoot.getBoundingClientRect()
|
|
663
|
+
: { bottom: window.innerHeight, top: 0 };
|
|
664
|
+
const height = r.bottom - r.top;
|
|
665
|
+
const margin = height * this.viewMargin;
|
|
666
|
+
return { bottom: r.bottom + margin, margin, top: r.top - margin };
|
|
667
|
+
}
|
|
668
|
+
|
|
501
669
|
claim(node: ChoreoNode) {
|
|
502
670
|
if (this.orphans.has(node)) {
|
|
503
671
|
return true;
|
|
@@ -520,8 +688,20 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
|
|
|
520
688
|
// (`layout` / `layoutId`) snapshot here too, so `{{motion layout=true}}`
|
|
521
689
|
// works inside a <Choreo> without a <LayoutGroup> wrapped around it.
|
|
522
690
|
snapshotOnRender();
|
|
691
|
+
if (this.args.idle) {
|
|
692
|
+
this.slept = true;
|
|
693
|
+
return undefined;
|
|
694
|
+
}
|
|
695
|
+
if (this.slept) {
|
|
696
|
+
// what arrived while the region sat out was never watched arriving:
|
|
697
|
+
// waking must not replay it. This pass's own arrivals register after
|
|
698
|
+
// this, as its modifiers install.
|
|
699
|
+
this.slept = false;
|
|
700
|
+
this.arrived.clear();
|
|
701
|
+
}
|
|
523
702
|
if (!this.passPending) {
|
|
524
703
|
this.passPending = true;
|
|
704
|
+
this.syncViewWatch();
|
|
525
705
|
this.snapshot();
|
|
526
706
|
// every region announces here, while Glimmer is still rendering, so the
|
|
527
707
|
// barrier knows the whole cast before anybody measures
|
|
@@ -534,9 +714,14 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
|
|
|
534
714
|
private snapshot() {
|
|
535
715
|
this.snapshots.clear();
|
|
536
716
|
this.rootSnapshot = this.element?.getBoundingClientRect();
|
|
717
|
+
const wired = this.args.inView ? this.nearView : undefined;
|
|
537
718
|
for (const node of this.participants) {
|
|
538
719
|
const el = node.element;
|
|
539
|
-
if (
|
|
720
|
+
if (
|
|
721
|
+
el?.isConnected &&
|
|
722
|
+
!this.orphans.has(node) &&
|
|
723
|
+
(!wired || wired.has(node) || this.flying.has(node))
|
|
724
|
+
) {
|
|
540
725
|
this.snapshots.set(node, measure(el));
|
|
541
726
|
}
|
|
542
727
|
}
|
|
@@ -601,6 +786,7 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
|
|
|
601
786
|
role: node.role,
|
|
602
787
|
type,
|
|
603
788
|
});
|
|
789
|
+
const near = this.args.inView ? this.nearViewBand() : undefined;
|
|
604
790
|
const candidates = new Set([
|
|
605
791
|
...this.participants,
|
|
606
792
|
...this.claimed,
|
|
@@ -617,6 +803,15 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
|
|
|
617
803
|
const prior = this.orphanBounds.get(node);
|
|
618
804
|
removed.push(sprite(node, 'removed', prior));
|
|
619
805
|
} else if (this.arrived.has(node)) {
|
|
806
|
+
if (near) {
|
|
807
|
+
const r = el.getBoundingClientRect();
|
|
808
|
+
if (r.bottom < near.top || r.top > near.bottom) {
|
|
809
|
+
// it lands out of reach of the view: layout places it
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
// wired now: the observer reports only after the next frame
|
|
813
|
+
this.nearView.add(node);
|
|
814
|
+
}
|
|
620
815
|
inserted.push(
|
|
621
816
|
sprite(node, 'inserted', undefined, bounds(measure(el), after)),
|
|
622
817
|
);
|
|
@@ -667,6 +862,24 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
|
|
|
667
862
|
};
|
|
668
863
|
}
|
|
669
864
|
}
|
|
865
|
+
// a jump: something moved farther than the margin, past blocks that
|
|
866
|
+
// were never wired. It lands as layout puts it; what was leaving goes now.
|
|
867
|
+
if (near && kept.some((s) => Math.abs(s.delta?.y ?? 0) > near.margin)) {
|
|
868
|
+
for (const s of removed) {
|
|
869
|
+
this.drop(s.node);
|
|
870
|
+
}
|
|
871
|
+
inserted.length = 0;
|
|
872
|
+
kept.length = 0;
|
|
873
|
+
removed.length = 0;
|
|
874
|
+
}
|
|
875
|
+
if (near) {
|
|
876
|
+
this.flying = new Set(
|
|
877
|
+
[
|
|
878
|
+
...inserted,
|
|
879
|
+
...kept.filter((s) => s.delta && (s.delta.x || s.delta.y)),
|
|
880
|
+
].map((s) => s.node),
|
|
881
|
+
);
|
|
882
|
+
}
|
|
670
883
|
this.arrived.clear();
|
|
671
884
|
this.snapshots.clear();
|
|
672
885
|
const claimed = [...this.claimed];
|
|
@@ -725,7 +938,10 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
|
|
|
725
938
|
[...kept, ...inserted.filter((s) => s.type === 'kept')],
|
|
726
939
|
// measured in the same window as `final`, and re-measured every pass: a
|
|
727
940
|
// beacon can move without this region rendering at all
|
|
728
|
-
|
|
941
|
+
// the beacons this score has asked for before, measured now; a name it
|
|
942
|
+
// asks for the first time is measured when the step reads it (Glimmer
|
|
943
|
+
// reads a step's args as it compiles, after this)
|
|
944
|
+
measureBeacons(after, this.beaconNames),
|
|
729
945
|
measureZoom,
|
|
730
946
|
// the frame's own size in the same final layout, local pixels — the
|
|
731
947
|
// camera's centre reference, so aim terms are internally consistent
|
|
@@ -733,7 +949,11 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
|
|
|
733
949
|
height: after.height / measureZoom,
|
|
734
950
|
width: after.width / measureZoom,
|
|
735
951
|
},
|
|
736
|
-
groundOf(root),
|
|
952
|
+
() => groundOf(root),
|
|
953
|
+
(name) => {
|
|
954
|
+
this.beaconNames.add(name);
|
|
955
|
+
return measureBeacons(after, [name]).get(name) ?? null;
|
|
956
|
+
},
|
|
737
957
|
);
|
|
738
958
|
this.changeset = changeset;
|
|
739
959
|
|
|
@@ -860,6 +1080,9 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
|
|
|
860
1080
|
// aloft, kept-motion cannot justify a recompile either: the score it
|
|
861
1081
|
// would produce is the degraded one above.
|
|
862
1082
|
(this.orphans.size > 0 || !changeset.sprites({ type: 'moved' }).length) &&
|
|
1083
|
+
// …and only while the layout it compiled in still stands: a reflow or
|
|
1084
|
+
// a scroll under a leaver still aloft is a real move, not noise
|
|
1085
|
+
this.layoutOf() === this.layoutPrint &&
|
|
863
1086
|
// …but an EDITED timeline is never noise: a changed tree replays,
|
|
864
1087
|
// exactly as it would with no orphans aloft (the build-order
|
|
865
1088
|
// transport's delay nudge, made while a leaver is frozen mid-park)
|
|
@@ -1010,6 +1233,7 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
|
|
|
1010
1233
|
// arriving, and every one it can see on its way out. A region in that
|
|
1011
1234
|
// state is not watching a departure, it is inside one — see `park`.
|
|
1012
1235
|
doomed: !kept.length && !inserted.length && removed.length > 0,
|
|
1236
|
+
maxFrameStep: this.args.maxFrameStep,
|
|
1013
1237
|
});
|
|
1014
1238
|
|
|
1015
1239
|
if (this.args.quiet) {
|
|
@@ -1082,6 +1306,24 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
|
|
|
1082
1306
|
`${el.offsetWidth},${el.offsetHeight}`,
|
|
1083
1307
|
);
|
|
1084
1308
|
}
|
|
1309
|
+
// A scroll moves every participant in page space and none of them in
|
|
1310
|
+
// the offsets above, so the scroll offset of the scroller the region
|
|
1311
|
+
// sits in is part of where its participants stand: a follow settled into
|
|
1312
|
+
// a pass mid-run is a real move, and the run is recompiled to carry it.
|
|
1313
|
+
// The walk stops at that scroller (the document's own scroll counts only
|
|
1314
|
+
// when no element scroller holds the region), so scrolling an outer
|
|
1315
|
+
// scroller or the page around it does not cancel the region's runs, for
|
|
1316
|
+
// the same reason the offset walk above stops at the region's edge.
|
|
1317
|
+
let sx = 0;
|
|
1318
|
+
let sy = 0;
|
|
1319
|
+
for (let a = root?.parentElement; a; a = a.parentElement) {
|
|
1320
|
+
sx += a.scrollLeft;
|
|
1321
|
+
sy += a.scrollTop;
|
|
1322
|
+
if (a !== document.documentElement && scrolls(a)) {
|
|
1323
|
+
break;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
parts.push(`scroll@${Math.round(sx)},${Math.round(sy)}`);
|
|
1085
1327
|
return parts.join('|');
|
|
1086
1328
|
}
|
|
1087
1329
|
|
|
@@ -103,11 +103,50 @@ export function setBeforeMeasure(fn: () => void) {
|
|
|
103
103
|
beforeMeasure = fn;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/**
|
|
107
|
+
* SETTLE: state that the pass implies but that is not markup, settled after
|
|
108
|
+
* this pass's mounts and before any region measures. A scroll owner (a list
|
|
109
|
+
* that keeps its scroll at the newest content, or holds a row in place while
|
|
110
|
+
* rows above it change) holds its anchor here: a scroll position is state,
|
|
111
|
+
* so it lands in the same pass as the change that moved it, and every region
|
|
112
|
+
* measures the page as the reader will see it. An element the scroll kept
|
|
113
|
+
* still then reads as not moved and gets no motion; only what really moved
|
|
114
|
+
* on screen does.
|
|
115
|
+
*
|
|
116
|
+
* A settler only runs inside a barrier, and a barrier runs only when a
|
|
117
|
+
* region joins a pass: with no region mounted, every region idle, or motion
|
|
118
|
+
* off, no settler runs. So the owner sets its scroll through its own path as
|
|
119
|
+
* well (on render or on resize, as it would with no Choreo on the page), and
|
|
120
|
+
* the settler only makes that happen before regions measure. State never
|
|
121
|
+
* depends on a settler (ADR 007 §1.1).
|
|
122
|
+
*
|
|
123
|
+
* Settlers run in registration order; one that throws is reported and the
|
|
124
|
+
* pass goes on. Returns the unregister function.
|
|
125
|
+
*/
|
|
126
|
+
const settlers = new Set<() => void>();
|
|
127
|
+
export function registerSettle(fn: () => void): () => void {
|
|
128
|
+
settlers.add(fn);
|
|
129
|
+
return () => {
|
|
130
|
+
settlers.delete(fn);
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
106
134
|
function runBarrier() {
|
|
107
135
|
booked = false;
|
|
108
136
|
const hosts = [...intents];
|
|
109
137
|
intents.clear();
|
|
110
138
|
beforeMeasure();
|
|
139
|
+
for (const settle of [...settlers]) {
|
|
140
|
+
// a settler is app code: one that throws must not keep the regions that
|
|
141
|
+
// joined this pass from measuring, or they would stay pending for good
|
|
142
|
+
try {
|
|
143
|
+
settle();
|
|
144
|
+
} catch (e) {
|
|
145
|
+
queueMicrotask(() => {
|
|
146
|
+
throw e;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
111
150
|
const passes: Pass[] = [];
|
|
112
151
|
for (const host of hosts) {
|
|
113
152
|
const pass = host.measurePass();
|
|
@@ -11,10 +11,12 @@ export type { Arming, ArmingOptions, ArmingRegion } from './arming.ts';
|
|
|
11
11
|
export { createArming } from './arming.ts';
|
|
12
12
|
export { beacon } from './beacon.ts';
|
|
13
13
|
export type { BeaconRef } from './beacons.ts';
|
|
14
|
+
export { registerBeacon } from './beacons.ts';
|
|
14
15
|
export type { Changeset } from './changeset.ts';
|
|
15
16
|
export type { ChoreoContext } from './choreo.gts';
|
|
16
17
|
export { Choreo } from './choreo.gts';
|
|
17
18
|
export { easeIn, easeInAndOut, easeOut } from './easings.ts';
|
|
19
|
+
export { registerSettle } from './far.ts';
|
|
18
20
|
export type { GestureRef } from './gesture.ts';
|
|
19
21
|
export {
|
|
20
22
|
type ChoreoHost,
|
|
@@ -184,6 +184,13 @@ interface RunOptions {
|
|
|
184
184
|
inherit?: Velocities;
|
|
185
185
|
/** where the prior run's ambient loops stood, to carry them on from */
|
|
186
186
|
loops?: LoopPhases;
|
|
187
|
+
/**
|
|
188
|
+
* The most animation time, in ms, one painted frame may advance. Unset,
|
|
189
|
+
* a frame advances by the time since the last one (the frame loop caps
|
|
190
|
+
* that at 40 ms). Set, a run behind a long frame slows down rather than
|
|
191
|
+
* skipping: it lands later, and no frame jumps.
|
|
192
|
+
*/
|
|
193
|
+
maxFrameStep?: number;
|
|
187
194
|
/** a camera step landed or the run ended: the frame's new resting state */
|
|
188
195
|
onCamera?(state: CameraState): void;
|
|
189
196
|
/**
|
|
@@ -396,6 +403,18 @@ interface Track {
|
|
|
396
403
|
wire?: SVGPathElement;
|
|
397
404
|
}
|
|
398
405
|
|
|
406
|
+
let warnedPrivateFields = false;
|
|
407
|
+
/** @maxFrameStep reads motion-dom's private animation fields; say once when they are gone */
|
|
408
|
+
function warnPrivateFieldsMoved() {
|
|
409
|
+
if (warnedPrivateFields) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
warnedPrivateFields = true;
|
|
413
|
+
console.warn(
|
|
414
|
+
'@cardstack/choreo: motion-dom no longer exposes the animation fields @maxFrameStep reads (_animation, startTime, holdTime); a long frame will skip tweens ahead instead of slowing them',
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
399
418
|
export class ChoreoRun implements Run {
|
|
400
419
|
cues: Cue[];
|
|
401
420
|
velocities: Velocities = new Map();
|
|
@@ -454,6 +473,17 @@ export class ChoreoRun implements Run {
|
|
|
454
473
|
private ended = false;
|
|
455
474
|
private resolveFinished!: () => void;
|
|
456
475
|
private ticking = false;
|
|
476
|
+
/**
|
|
477
|
+
* The next tick is the first since the clock started (or resumed): it
|
|
478
|
+
* paints the run where it stands and advances nothing. The frame loop's
|
|
479
|
+
* delta on that tick is the time since ITS last frame, up to 40 ms, so a
|
|
480
|
+
* run started by a render pass would otherwise open with that whole step
|
|
481
|
+
* on its own clock: every cue the clock starts, gates, or drives frame by
|
|
482
|
+
* frame (a camera, a wire, a later cue's start) would begin that far in.
|
|
483
|
+
*/
|
|
484
|
+
private freshClock = false;
|
|
485
|
+
/** the frame timestamp holdBack saw last, while a maxFrameStep applies */
|
|
486
|
+
private lastStamp: number | undefined;
|
|
457
487
|
private autoTimer: ReturnType<typeof setTimeout> | undefined;
|
|
458
488
|
|
|
459
489
|
constructor(compiled: Compiled, options: RunOptions) {
|
|
@@ -895,6 +925,12 @@ export class ChoreoRun implements Run {
|
|
|
895
925
|
return;
|
|
896
926
|
}
|
|
897
927
|
this.ticking = true;
|
|
928
|
+
this.freshClock = true;
|
|
929
|
+
this.lastStamp = undefined;
|
|
930
|
+
if (this.options.maxFrameStep) {
|
|
931
|
+
// read runs before update: the animations tick after the hold-back
|
|
932
|
+
frame.read(this.holdBack, true);
|
|
933
|
+
}
|
|
898
934
|
frame.update(this.tick, true);
|
|
899
935
|
}
|
|
900
936
|
|
|
@@ -902,15 +938,82 @@ export class ChoreoRun implements Run {
|
|
|
902
938
|
if (this.ticking) {
|
|
903
939
|
this.ticking = false;
|
|
904
940
|
cancelFrame(this.tick);
|
|
941
|
+
cancelFrame(this.holdBack);
|
|
905
942
|
}
|
|
906
943
|
}
|
|
907
944
|
|
|
945
|
+
/**
|
|
946
|
+
* maxFrameStep for the animations themselves. The run's own clock is
|
|
947
|
+
* capped in tick; the tweens and springs it started run on motion-dom's
|
|
948
|
+
* frame clock, which takes the real time since their start, so a long
|
|
949
|
+
* frame would still skip that much of them. Before they update, each one
|
|
950
|
+
* driven on the main thread has its start moved on by the time past the
|
|
951
|
+
* cap: it resumes from where the last frame left it, plus one step.
|
|
952
|
+
* Animations on the compositor (WAAPI) kept playing through the long
|
|
953
|
+
* frame and are left alone.
|
|
954
|
+
*/
|
|
955
|
+
private holdBack = (data: FrameData) => {
|
|
956
|
+
const step = this.options.maxFrameStep;
|
|
957
|
+
const last = this.lastStamp;
|
|
958
|
+
this.lastStamp = data.timestamp;
|
|
959
|
+
if (!step) {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
// The first frame: an animation the render pass started takes its start
|
|
963
|
+
// from motion-dom's clock, which holds one time for a whole task, so a
|
|
964
|
+
// pass late in a long render would date it back to the task's start and
|
|
965
|
+
// its first frame would skip that far. Its start moves to this frame.
|
|
966
|
+
// After that: the time past the cap since the last frame.
|
|
967
|
+
for (const t of this.tracks) {
|
|
968
|
+
for (const c of t.controls ?? []) {
|
|
969
|
+
// the wrapper's resolved animation, read without the getter that
|
|
970
|
+
// flushes pending keyframes. These are motion-dom's private fields
|
|
971
|
+
// (VENDORED.md lists them for a bump to check): past the first frame
|
|
972
|
+
// every control has resolved, so a control without them means the
|
|
973
|
+
// fields moved and the cap no longer reaches the tweens.
|
|
974
|
+
const inner = (c as unknown as { _animation?: object })._animation as
|
|
975
|
+
| { holdTime?: number | null; startTime?: number | null }
|
|
976
|
+
| undefined;
|
|
977
|
+
if (
|
|
978
|
+
last !== undefined &&
|
|
979
|
+
(!inner ||
|
|
980
|
+
(!('animation' in inner) &&
|
|
981
|
+
!('startTime' in inner && 'holdTime' in inner)))
|
|
982
|
+
) {
|
|
983
|
+
warnPrivateFieldsMoved();
|
|
984
|
+
}
|
|
985
|
+
if (
|
|
986
|
+
!inner ||
|
|
987
|
+
'animation' in inner ||
|
|
988
|
+
typeof inner.startTime !== 'number' ||
|
|
989
|
+
inner.holdTime !== null
|
|
990
|
+
) {
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
if (last === undefined) {
|
|
994
|
+
if (data.timestamp - inner.startTime > step) {
|
|
995
|
+
inner.startTime = data.timestamp;
|
|
996
|
+
}
|
|
997
|
+
} else if (data.timestamp - last > step) {
|
|
998
|
+
inner.startTime += data.timestamp - last - step;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
|
|
908
1004
|
private tick = (data: FrameData) => {
|
|
909
1005
|
if (!this.playing || this.cancelled || this.ended) {
|
|
910
1006
|
this.stopTicking();
|
|
911
1007
|
return;
|
|
912
1008
|
}
|
|
913
|
-
const
|
|
1009
|
+
const step = this.options.maxFrameStep;
|
|
1010
|
+
const delta = this.freshClock
|
|
1011
|
+
? 0
|
|
1012
|
+
: step
|
|
1013
|
+
? Math.min(data.delta, step)
|
|
1014
|
+
: data.delta;
|
|
1015
|
+
this.freshClock = false;
|
|
1016
|
+
const next = this.master + delta * this.rate;
|
|
914
1017
|
const gate = this.gates.find(
|
|
915
1018
|
(g) => !g.opened && g.at >= this.master && g.at <= next,
|
|
916
1019
|
);
|
|
@@ -5,6 +5,12 @@ import {
|
|
|
5
5
|
shouldInterruptAfterDeadline,
|
|
6
6
|
} from 'quickjs-emscripten';
|
|
7
7
|
|
|
8
|
+
import {
|
|
9
|
+
describeScriptError,
|
|
10
|
+
REALM_RUNNER_SCRIPT_FILE_NAME,
|
|
11
|
+
wrapRealmRunnerScript,
|
|
12
|
+
} from '@cardstack/runtime-common/realm-runner-script';
|
|
13
|
+
|
|
8
14
|
import type {
|
|
9
15
|
RealmRunnerCallMethod,
|
|
10
16
|
RealmRunnerRequest,
|
|
@@ -106,55 +112,6 @@ const BOOTSTRAP = `
|
|
|
106
112
|
})();
|
|
107
113
|
`;
|
|
108
114
|
|
|
109
|
-
const SCRIPT_FILE_NAME = 'realm-code.js';
|
|
110
|
-
// The script runs inside `(async () => {\n … \n})()`, so its first line is
|
|
111
|
-
// line 2 of what QuickJS evaluates.
|
|
112
|
-
const WRAPPER_LINES = 1;
|
|
113
|
-
|
|
114
|
-
// Turns an error from the script itself into a message the model can act on:
|
|
115
|
-
// the error, the line of its own script it points at with the lines around
|
|
116
|
-
// it, and, for the error that comes up most, what usually causes it. Errors
|
|
117
|
-
// from realm calls and the time-limit interrupt keep their own message.
|
|
118
|
-
function describeScriptError(dumped: unknown, code: string): string {
|
|
119
|
-
let error = (dumped ?? {}) as {
|
|
120
|
-
name?: string;
|
|
121
|
-
message?: string;
|
|
122
|
-
lineNumber?: number;
|
|
123
|
-
};
|
|
124
|
-
let message = error.message ?? String(dumped);
|
|
125
|
-
let name = error.name ?? 'Error';
|
|
126
|
-
if (
|
|
127
|
-
!['SyntaxError', 'TypeError', 'ReferenceError', 'RangeError'].includes(name)
|
|
128
|
-
) {
|
|
129
|
-
return message;
|
|
130
|
-
}
|
|
131
|
-
let lines = code.split('\n');
|
|
132
|
-
let line =
|
|
133
|
-
typeof error.lineNumber === 'number'
|
|
134
|
-
? error.lineNumber - WRAPPER_LINES
|
|
135
|
-
: undefined;
|
|
136
|
-
let parts = [
|
|
137
|
-
`${name} in the script${line ? ` at line ${line}` : ''}: ${message}`,
|
|
138
|
-
];
|
|
139
|
-
if (line && line >= 1 && line <= lines.length) {
|
|
140
|
-
let from = Math.max(1, line - 2);
|
|
141
|
-
let to = Math.min(lines.length, line + 1);
|
|
142
|
-
let excerpt = [];
|
|
143
|
-
for (let n = from; n <= to; n++) {
|
|
144
|
-
excerpt.push(
|
|
145
|
-
`${n === line ? '>' : ' '} ${n} | ${lines[n - 1].slice(0, 200)}`,
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
parts.push(excerpt.join('\n'));
|
|
149
|
-
}
|
|
150
|
-
if (name === 'SyntaxError' && code.includes('`')) {
|
|
151
|
-
parts.push(
|
|
152
|
-
"If a file's content is inside a template literal, a backtick or ${ in that content ends the string or is interpolated. Escape them in the content as \\` and \\${, or build the content without a template literal.",
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
return parts.join('\n');
|
|
156
|
-
}
|
|
157
|
-
|
|
158
115
|
const METHODS = new Set<RealmRunnerCallMethod>([
|
|
159
116
|
'fs.readText',
|
|
160
117
|
'fs.exists',
|
|
@@ -273,8 +230,8 @@ worker.onmessage = async (event: MessageEvent<RealmRunnerRequest>) => {
|
|
|
273
230
|
context.unwrapResult(context.evalCode(BOOTSTRAP)).dispose();
|
|
274
231
|
|
|
275
232
|
let evaluated = context.evalCode(
|
|
276
|
-
|
|
277
|
-
|
|
233
|
+
wrapRealmRunnerScript(request.code),
|
|
234
|
+
REALM_RUNNER_SCRIPT_FILE_NAME,
|
|
278
235
|
);
|
|
279
236
|
if (evaluated.error) {
|
|
280
237
|
let dumped = context.dump(evaluated.error);
|
package/package.json
CHANGED