mulmoterminal 4.12.0 → 4.13.0
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/README.md +5 -1
- package/bin/cli-args.js +22 -1
- package/bin/mulmoterminal.js +12 -4
- package/common/keymap.ts +70 -9
- package/dist/assets/{AllPackages-CF2GkZ5g-CM3rGYLT.js → AllPackages-CF2GkZ5g-CssQbjOa.js} +1 -1
- package/dist/assets/{BaseConfiguration-CAwZOAki-DTVgM-PG.js → BaseConfiguration-CAwZOAki-Dh0EGNE9.js} +1 -1
- package/dist/assets/{Element-BMI7Nj5w-B9RqTJwZ.js → Element-BMI7Nj5w-CqxLhpQT.js} +1 -1
- package/dist/assets/{Entities-C8p_0jCn-B96BdpL6.js → Entities-C8p_0jCn-DEEX_Xry.js} +1 -1
- package/dist/assets/{FunctionList-YWk54TCy-Dftsdlhu.js → FunctionList-YWk54TCy-D2PM4n-L.js} +1 -1
- package/dist/assets/{InputJax-C5dj-ntL-CSXYU6NO.js → InputJax-C5dj-ntL-Cnbx6KYn.js} +1 -1
- package/dist/assets/{MathItem-YJvQL-XJ-Chiavq2E.js → MathItem-YJvQL-XJ-afY01gTq.js} +1 -1
- package/dist/assets/{MmlFactory-DuXYIgx8-DxABlOoL.js → MmlFactory-DuXYIgx8-DpHfMoIU.js} +1 -1
- package/dist/assets/{Options-oARQeeuT-C2dJZp_J.js → Options-oARQeeuT-CiIYMpYT.js} +1 -1
- package/dist/assets/{OutputJax-DfUZNNCu-CFu_qe01.js → OutputJax-DfUZNNCu-B38K9BaX.js} +1 -1
- package/dist/assets/{PrioritizedList-6lun1Xqd-B1IOIxus.js → PrioritizedList-6lun1Xqd-Dygd27F6.js} +1 -1
- package/dist/assets/{Styles-BPXeoqzi-CENhg2Rx.js → Styles-BPXeoqzi-C58TnqKJ.js} +1 -1
- package/dist/assets/{TeXAtom-LHT3KFWH-DzkJr1zJ.js → TeXAtom-LHT3KFWH-uPYpzamN.js} +1 -1
- package/dist/assets/{abnfDiagram-VCTEODGH-DoYNjdBB-BTUEucbt.js → abnfDiagram-VCTEODGH-DoYNjdBB-Dai05JsH.js} +1 -1
- package/dist/assets/{arc-CqByPKkF-D8g1A1VD.js → arc-CqByPKkF-D5Jg9Zer.js} +1 -1
- package/dist/assets/architecture-7GRP2DOG-CRS69XmP-Bkz68tYo.js +1 -0
- package/dist/assets/{architectureDiagram-5GKGNRK7-BhcT6qS5-CYwxV1T4.js → architectureDiagram-5GKGNRK7-BhcT6qS5-DipIYUA_.js} +1 -1
- package/dist/assets/{assistive-mml-DXJXho4o-DHaLYeaf.js → assistive-mml-DXJXho4o-BRWc7vXC.js} +1 -1
- package/dist/assets/{blockDiagram-I7D4REHJ-1Cng0Q4K-jkjSYlHD.js → blockDiagram-I7D4REHJ-1Cng0Q4K-CmrGn63J.js} +1 -1
- package/dist/assets/{c4Diagram-7LVT6UL2-CAb9c8nc-CVBP7bme.js → c4Diagram-7LVT6UL2-CAb9c8nc-EY5oaGlv.js} +1 -1
- package/dist/assets/channel-BjESmYnj-DCJo3CKJ.js +1 -0
- package/dist/assets/{chunk-2E4U76K2-CMyNbRyJ-CYlEPSYt.js → chunk-2E4U76K2-CMyNbRyJ-BYHoa5DT.js} +1 -1
- package/dist/assets/{chunk-4HAMMTFA-ChwfIAnF-DLsNAJrX.js → chunk-4HAMMTFA-ChwfIAnF-DQUJxetY.js} +1 -1
- package/dist/assets/{chunk-75Z2AOVW-CbPA0Zt_-PBrNY7F4.js → chunk-75Z2AOVW-CbPA0Zt_-BKl9d-co.js} +1 -1
- package/dist/assets/{chunk-CLGD4ZFX-CVw8XHcp-XVRICqz6.js → chunk-CLGD4ZFX-CVw8XHcp-MCMGRlFw.js} +1 -1
- package/dist/assets/{chunk-DU6HZSFF-DuOx2LFX-B0VtwR-4.js → chunk-DU6HZSFF-DuOx2LFX-B86yuhFX.js} +1 -1
- package/dist/assets/{chunk-F27PBJKO-CgBMw9yN-AuWmarZj.js → chunk-F27PBJKO-CgBMw9yN-Bq3zuyO1.js} +1 -1
- package/dist/assets/{chunk-GMAD6QVW-JTqlimUX-DjcFuvKJ.js → chunk-GMAD6QVW-JTqlimUX-BADn_t1m.js} +1 -1
- package/dist/assets/{chunk-GVQU2GXP-D6ccB4DO-z0JsvnGC.js → chunk-GVQU2GXP-D6ccB4DO-D67xQv2L.js} +1 -1
- package/dist/assets/{chunk-IMKFNOWR-ij1DSzMb-Cp2kQ6eT.js → chunk-IMKFNOWR-ij1DSzMb-Cdmwd2WK.js} +1 -1
- package/dist/assets/{chunk-L3NEJ4N5-CUoAQVG2-B_R7YU1U.js → chunk-L3NEJ4N5-CUoAQVG2-Buv2AnXy.js} +1 -1
- package/dist/assets/{chunk-OSK3NFVY-_VNbXQSu-D2nH_Zoh.js → chunk-OSK3NFVY-_VNbXQSu-C5f15YpO.js} +1 -1
- package/dist/assets/{chunk-P2QGCYS3-B8lzOn3Y-CdxuD9Vb.js → chunk-P2QGCYS3-B8lzOn3Y-B3mLNaJD.js} +1 -1
- package/dist/assets/{chunk-POPQ4Y6H-Bh_uOTmp-DV6DnZ5t.js → chunk-POPQ4Y6H-Bh_uOTmp-Dy-RhpSF.js} +1 -1
- package/dist/assets/{chunk-PWAF6VOD-DzjnfYSw-CbNtsCaS.js → chunk-PWAF6VOD-DzjnfYSw-Br7rBQUn.js} +1 -1
- package/dist/assets/{chunk-SHT3W25Y-B8dQLS5w-CLbCDkIa.js → chunk-SHT3W25Y-B8dQLS5w-Bzrea95d.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-B6A-T8Th-C73GGvfo.js → chunk-SVP7TREG-B6A-T8Th-CD7e9pDy.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-C1kwD29l-kqi5-13I.js → chunk-TICWLB2K-C1kwD29l-Cqy2hS-h.js} +1 -1
- package/dist/assets/{chunk-TLUHSLCS-DYzGlCmi-Btk2TYDp.js → chunk-TLUHSLCS-DYzGlCmi-DQFOalYn.js} +2 -2
- package/dist/assets/{chunk-XXDRQBXY-vg0kfC73-C9Og4olA.js → chunk-XXDRQBXY-vg0kfC73-Beoe3hey.js} +1 -1
- package/dist/assets/classDiagram-ZZMXUADV-C7gP-pUH-TqSfWtS1.js +1 -0
- package/dist/assets/classDiagram-v2-VYDZK3BY-CUwV5k1F-TqSfWtS1.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-NuHCtE3P-sgsTiDYm.js → cose-bilkent-JH36ORCC-NuHCtE3P-CAm6fB__.js} +1 -1
- package/dist/assets/cynefin-OW5HDTMX-C4iKivN4-9ujipUYq.js +1 -0
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-CA7sro34-tAE4dFM9.js → cynefinDiagram-5FMLGOSQ-CA7sro34-Dw8nX0TQ.js} +1 -1
- package/dist/assets/{dagre-GXQ25YYZ-DOztjUR_-CfL9WIVN.js → dagre-GXQ25YYZ-DOztjUR_-CLilpzZ6.js} +1 -1
- package/dist/assets/{diagram-S7CK7UJ4-D_nsLfRT-DEQewt62.js → diagram-S7CK7UJ4-D_nsLfRT-AWaKWxdg.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN-DNN-YX9a-Yh8uxMUF.js → diagram-UQ7AKVKN-DNN-YX9a-Cxg2PCC4.js} +1 -1
- package/dist/assets/{diagram-VSXAHHWV--HENzo-l-0GmA-ovB.js → diagram-VSXAHHWV--HENzo-l-Di3J8S1Y.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-m2m9dROj-Cm-nFEG1.js → diagram-VX7I27RA-m2m9dROj-DmZLkzRx.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-D4qpv4-B-BVX8YbHU.js → diagram-Z3DM3KII-D4qpv4-B-BhRu_oHO.js} +1 -1
- package/dist/assets/{dist-CfoBFpi6.js → dist-BA8WR926.js} +1 -1
- package/dist/assets/{dist-C8NBNY_7.js → dist-CSTVKEO6.js} +1 -1
- package/dist/assets/{dist-D7zq5Vj0.js → dist-CVpznzjS.js} +1 -1
- package/dist/assets/{dist-D24DAoP7.js → dist-CgQ_oQmc.js} +1 -1
- package/dist/assets/{dist-DeiyD9Ki-9JRmM4Z8.js → dist-DeiyD9Ki-R86_Zh4z.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-CAbv3sZg-D8QuBFYM.js → ebnfDiagram-PWID7BFC-CAbv3sZg-C6XDy8ND.js} +1 -1
- package/dist/assets/{erDiagram-RLTQ6QDP-a10Ryh0--B1HSzwsq.js → erDiagram-RLTQ6QDP-a10Ryh0--CbOy4ZUP.js} +1 -1
- package/dist/assets/eventmodeling-NTZA5JFV-DhqtNS8R-Dt7SIrGk.js +1 -0
- package/dist/assets/flowDiagram-HODETNUW-CAW_8mwY-CnOc_pLO.js +1 -0
- package/dist/assets/{ganttDiagram-EL5Y4UJY-Cb7leeOC-keCW-kOD.js → ganttDiagram-EL5Y4UJY-Cb7leeOC-weDAGCBD.js} +1 -1
- package/dist/assets/gitGraph-4MIJSDKK-S_Zs6Nxs-BfjtZvJt.js +1 -0
- package/dist/assets/{gitGraphDiagram-WWUBYQGX-BCSbUpXG-DnzApyVv.js → gitGraphDiagram-WWUBYQGX-BCSbUpXG-BomL5zI6.js} +1 -1
- package/dist/assets/{html-DdQiKDqM-B-K3Ykhn.js → html-DdQiKDqM-B6wswbaV.js} +1 -1
- package/dist/assets/index-B-BwWalM.css +1 -0
- package/dist/assets/{index-CeO5tt2R.js → index-Cyyg6dqf.js} +243 -243
- package/dist/assets/info-A6RAGUB7-4sGJh1is-B8w9oPi5.js +1 -0
- package/dist/assets/{infoDiagram-27XIBGKW-BJRaZItN-Vim8p0oz.js → infoDiagram-27XIBGKW-BJRaZItN-DDAXs1r3.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-DHlFe__S-O3s46PZl.js → ishikawaDiagram-5VMMS53U-DHlFe__S-2FoUZyuK.js} +1 -1
- package/dist/assets/{journeyDiagram-3NMN7TZE-BOIo3JM--BUdLRefU.js → journeyDiagram-3NMN7TZE-BOIo3JM--CKVCefHi.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-yrQdG683-tuB3PItC.js → kanban-definition-UXKFOSKX-yrQdG683-DLceahDo.js} +1 -1
- package/dist/assets/{lengths-CvmPfBid-QuWmlAY4.js → lengths-CvmPfBid-Bu4CQkge.js} +1 -1
- package/dist/assets/{line-DUb1TjJS-CIXFDmfK.js → line-DUb1TjJS-Cy4--5UV.js} +1 -1
- package/dist/assets/{linear-xsuGWu5q-B0UqjTq4.js → linear-xsuGWu5q-BtDBK8pF.js} +1 -1
- package/dist/assets/{liteAdaptor-C0FQiOEx-Dd7ejALY.js → liteAdaptor-C0FQiOEx-Bo-QRNEC.js} +1 -1
- package/dist/assets/{marp-eTHdUTCa.js → marp-BNsGmsYi.js} +1 -1
- package/dist/assets/{mathjax-DVEo4UYj-DR6bsbF5.js → mathjax-DVEo4UYj-BDUX9u1X.js} +1 -1
- package/dist/assets/{mermaid-parser.core-fkjeiYO7-UP10Y-9g.js → mermaid-parser.core-fkjeiYO7-Bu045BJA.js} +2 -2
- package/dist/assets/{mermaid.core-CZDx2Cyn-DW_Cfwmb.js → mermaid.core-CZDx2Cyn-W7sMU12C.js} +3 -3
- package/dist/assets/{mindmap-definition-YA3MSWOX-BpKAVyqs-BT2I6xrj.js → mindmap-definition-YA3MSWOX-BpKAVyqs-B8fXczO5.js} +1 -1
- package/dist/assets/{mo-CrlNnGXX-BbSsbziG.js → mo-CrlNnGXX-DvbKdwI8.js} +1 -1
- package/dist/assets/packet-AYTQ26CC-MQQyx8fI-DEraoKKf.js +1 -0
- package/dist/assets/{pegDiagram-XKGWAZYB-C7eG_Ohl-CwOHpMIE.js → pegDiagram-XKGWAZYB-C7eG_Ohl-CL0sh1mk.js} +1 -1
- package/dist/assets/pie-WAS4IAKB-CyaL5i_d-hVe4izaF.js +1 -0
- package/dist/assets/{pieDiagram-E7YTZNPT-5f9wSfDR-BfiHzTle.js → pieDiagram-E7YTZNPT-5f9wSfDR-CLmTFnZr.js} +1 -1
- package/dist/assets/{quadrantDiagram-AXDQQJYC-mWeaOj5S-DmKj1SlO.js → quadrantDiagram-AXDQQJYC-mWeaOj5S-BUPv1MtK.js} +1 -1
- package/dist/assets/radar-RG4KPBEZ-DR-SsHDJ-CUIKMPrd.js +1 -0
- package/dist/assets/railroad-74A4TZTK-C2gqsOGX-Sdqv0l9F.js +1 -0
- package/dist/assets/railroad-abnf-HS5TGJTU-Dpzw6P5u-BWEL2Ih5.js +1 -0
- package/dist/assets/railroad-ebnf-LZEXJU2U-J_BuIrPo-CScwuEKP.js +1 -0
- package/dist/assets/railroad-peg-WCYAUIDC-DzMStOR--CUu-tzEV.js +1 -0
- package/dist/assets/{railroadDiagram-O6MQD6OU-DJskhE7u-wtloi7bZ.js → railroadDiagram-O6MQD6OU-DJskhE7u-B2rIG8vE.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-B5E0bU4h-FfOKAaTo.js → requirementDiagram-BXWQKSXE-B5E0bU4h-BNG0IbS0.js} +1 -1
- package/dist/assets/{sankeyDiagram-P5KCCOFB-DS3AOTai-Ct9wlCuh.js → sankeyDiagram-P5KCCOFB-DS3AOTai-BmF8ArEH.js} +1 -1
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-Bn2L9osv-CYfRdJtF.js → sequenceDiagram-WJ2MYXX4-Bn2L9osv-GsnV2qn4.js} +1 -1
- package/dist/assets/{src-CLC0ddSd-BIuq0-nf.js → src-CLC0ddSd-Dquc25MQ.js} +1 -1
- package/dist/assets/{stateDiagram-D77RDMKH-DbtoW7HL-CDtw4W1S.js → stateDiagram-D77RDMKH-DbtoW7HL-fgglcfel.js} +1 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-CX6RalKY-D118UaDi.js +1 -0
- package/dist/assets/{svg-CaHU_sxA-CszaUDKG.js → svg-CaHU_sxA-BFRh8mf6.js} +1 -1
- package/dist/assets/{swimlanes-42K2YHIH-90h8w_3B-CQvXtcT-.js → swimlanes-42K2YHIH-90h8w_3B-DFNJ2iO4.js} +1 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-49XH_TMr-Bkt0WWk-.js +8 -0
- package/dist/assets/{tex-B1cONMvb-ChHo1cBg.js → tex-B1cONMvb-DVhGZSdj.js} +1 -1
- package/dist/assets/{timeline-definition-24CTP7MA-Bx3KcICO-U9ql-I57.js → timeline-definition-24CTP7MA-Bx3KcICO-CZbcUu5B.js} +1 -1
- package/dist/assets/treeView-Q6P3EWNA-gFs854Sn-D7488VRG.js +1 -0
- package/dist/assets/treemap-WGGIJYW6-DGh_szUA-B-xoM8YR.js +1 -0
- package/dist/assets/{vennDiagram-4TSXK5OY-DSexNe_H-BzdM6vG-.js → vennDiagram-4TSXK5OY-DSexNe_H-CTFwoiwN.js} +1 -1
- package/dist/assets/wardley-WFR3VGLG-BVLYL3gB-CjcN7LrQ.js +1 -0
- package/dist/assets/{wardleyDiagram-VM6X3IG4-DesYYyZW-CFU64ptS.js → wardleyDiagram-VM6X3IG4-DesYYyZW-CW8HWmUS.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-dIt6pHWV-Dp9Unxs1.js → xychartDiagram-S5SC5T6Z-dIt6pHWV-C6EUNlwK.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/server/index.ts +17 -33
- package/server/infra/announce-listening.ts +140 -0
- package/server/infra/loopback-listener.ts +226 -25
- package/server/skills/mulmoterminal-config/SKILL.md +10 -4
- package/server/skills/mulmoterminal-dirs/SKILL.md +1 -1
- package/server/skills/mulmoterminal-header/SKILL.md +33 -0
- package/server/skills/mulmoterminal-keys/SKILL.md +165 -6
- package/server/skills/mulmoterminal-notify/SKILL.md +5 -1
- package/server/skills/mulmoterminal-shared-app/SKILL.md +15 -4
- package/server/skills/mulmoterminal-shared-app/templates/design.md +5 -4
- package/server/skills/mulmoterminal-shared-app/templates/live-poll.md +1 -1
- package/server/skills/mulmoterminal-shared-app/templates/magazine.md +1335 -0
- package/dist/assets/architecture-7GRP2DOG-CRS69XmP-Ujv7TsXX.js +0 -1
- package/dist/assets/channel-BjESmYnj-BFm2Wbs0.js +0 -1
- package/dist/assets/classDiagram-ZZMXUADV-C7gP-pUH-BDpOwtPC.js +0 -1
- package/dist/assets/classDiagram-v2-VYDZK3BY-CUwV5k1F-BDpOwtPC.js +0 -1
- package/dist/assets/cynefin-OW5HDTMX-C4iKivN4-C8N8egm0.js +0 -1
- package/dist/assets/eventmodeling-NTZA5JFV-DhqtNS8R-G2GKHcUd.js +0 -1
- package/dist/assets/flowDiagram-HODETNUW-CAW_8mwY-DUShNtXL.js +0 -1
- package/dist/assets/gitGraph-4MIJSDKK-S_Zs6Nxs-AZ1aHKyD.js +0 -1
- package/dist/assets/index-T6iAhWWD.css +0 -1
- package/dist/assets/info-A6RAGUB7-4sGJh1is-BsYxYGyO.js +0 -1
- package/dist/assets/packet-AYTQ26CC-MQQyx8fI-CgY-ZvU6.js +0 -1
- package/dist/assets/pie-WAS4IAKB-CyaL5i_d-Bfo-rUMV.js +0 -1
- package/dist/assets/radar-RG4KPBEZ-DR-SsHDJ-ciBy6b1Q.js +0 -1
- package/dist/assets/railroad-74A4TZTK-C2gqsOGX-CHNvkyIP.js +0 -1
- package/dist/assets/railroad-abnf-HS5TGJTU-Dpzw6P5u-BRKL6bPY.js +0 -1
- package/dist/assets/railroad-ebnf-LZEXJU2U-J_BuIrPo-CgDH6pLR.js +0 -1
- package/dist/assets/railroad-peg-WCYAUIDC-DzMStOR--CiY5naOb.js +0 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-CX6RalKY-hZ4fjyMZ.js +0 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-49XH_TMr-DzHdUIfM.js +0 -8
- package/dist/assets/treeView-Q6P3EWNA-gFs854Sn-Ce_qY2Wq.js +0 -1
- package/dist/assets/treemap-WGGIJYW6-DGh_szUA-CDNn6UCI.js +0 -1
- package/dist/assets/wardley-WFR3VGLG-BVLYL3gB-CQexd8Yy.js +0 -1
package/README.md
CHANGED
|
@@ -1400,7 +1400,11 @@ Favorited collections get their own toolbar buttons.
|
|
|
1400
1400
|
grid — so you can flip between "see everything" and "focus on one" in a click. While
|
|
1401
1401
|
zoomed, keys you bind walk the enlargement along the on-screen order without reaching for
|
|
1402
1402
|
the mouse — **opt-in, nothing is bound by default**, since any bound key is taken from the
|
|
1403
|
-
terminal underneath.
|
|
1403
|
+
terminal underneath. Press **Set up shortcuts…** in Settings → **Keyboard shortcuts** and an
|
|
1404
|
+
agent writes the `keymap` for you, checking each binding against your existing ones and the traps
|
|
1405
|
+
a browser or a Mac adds; that section also lists every action and the `send` row whether bound or not, including the
|
|
1406
|
+
[keys sent straight to the terminal](https://receptron.github.io/mulmoterminal/guide/en/config.html#keymap-send).
|
|
1407
|
+
To write it by hand, add a `keymap` to `~/.mulmoterminal/config.json` — see the
|
|
1404
1408
|
[guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#keymap)
|
|
1405
1409
|
for the syntax, the action list, and combinations a browser can never bind.
|
|
1406
1410
|
|
package/bin/cli-args.js
CHANGED
|
@@ -191,6 +191,20 @@ export function launcherUrl(reachHost, port) {
|
|
|
191
191
|
* TOP-LEVEL visit to the old origin can see it. A URL that has to be stable is therefore cheaper
|
|
192
192
|
* to keep stable than to migrate.
|
|
193
193
|
*
|
|
194
|
+
* SECOND, INDEPENDENT reason, and the one this comment was missing: Firebase gates
|
|
195
|
+
* `signInWithPopup` on the CALLING PAGE'S ORIGIN, against the authorized-domain list of the
|
|
196
|
+
* project — and RemoteHost's Google sign-in is exactly that call
|
|
197
|
+
* (`src/components/RemoteHostControl.vue`, against `mulmoserver` in `common/firebaseConfig.ts`).
|
|
198
|
+
* Reported on #1900: that project authorizes `localhost` and NOT `127.0.0.1`, checked in the
|
|
199
|
+
* console on 2026-08-28, which is why phone pairing could not sign in on 4.11.0 and can on
|
|
200
|
+
* 4.12.0. Not verified here — this repo cannot read that project's settings.
|
|
201
|
+
*
|
|
202
|
+
* It matters because the two reasons fail INDEPENDENTLY. Someone who solves the storage half —
|
|
203
|
+
* moves the grid layout server-side, say — would read the paragraph above as the whole
|
|
204
|
+
* justification and be free to change this string, and the first thing to break would be a
|
|
205
|
+
* feature nothing in `bin/` mentions. Changing the URL means adding the new loopback address to
|
|
206
|
+
* that authorized-domain list first.
|
|
207
|
+
*
|
|
194
208
|
* Reachability is not the reason it is safe; #1834 is. `server/infra/loopback-listener.ts` makes
|
|
195
209
|
* this server serve `127.0.0.1` in EVERY configuration — as the primary bind, or as a second
|
|
196
210
|
* listener when the operator widens it — because eight local callers write that address as a
|
|
@@ -224,6 +238,13 @@ export function browserUrl(port) {
|
|
|
224
238
|
* IT, and otherwise say plainly that we stepped aside — including where their layout went, since
|
|
225
239
|
* a silent switch here is #1889 all over again for that one user.
|
|
226
240
|
*
|
|
241
|
+
* Stepping aside costs more than the layout, and the note says so: on `http://127.0.0.1:<port>`
|
|
242
|
+
* RemoteHost's Google sign-in is refused as well, because Firebase checks the page's origin
|
|
243
|
+
* against its authorized domains and that project lists `localhost` only (#1900). This branch is
|
|
244
|
+
* a real degradation of the app rather than a cosmetic change of address, and the second failure
|
|
245
|
+
* would otherwise be found only by trying to pair a phone — which is the shape of #1889 again,
|
|
246
|
+
* one feature further along.
|
|
247
|
+
*
|
|
227
248
|
* The probe cannot close the window, only narrow it: `::1` is free when asked, and unless the
|
|
228
249
|
* server binds it (a `::1` or `::` bind), a stranger could still take it afterwards. That is the
|
|
229
250
|
* same probe/bind race `isPortFree` already documents and accepts.
|
|
@@ -236,7 +257,7 @@ export function launchTarget(reachHost, port, localhostIsUnambiguous) {
|
|
|
236
257
|
const checked = launcherUrl(reachHost, port);
|
|
237
258
|
return {
|
|
238
259
|
url: checked,
|
|
239
|
-
note: `Something else is already listening on [::1]:${port}, so the browser is being sent to ${checked} — the address that was checked. If your layout and settings look empty, they are filed under http://localhost:${port}.`,
|
|
260
|
+
note: `Something else is already listening on [::1]:${port}, so the browser is being sent to ${checked} — the address that was checked. If your layout and settings look empty, they are filed under http://localhost:${port}, and the phone's Google sign-in works only there.`,
|
|
240
261
|
};
|
|
241
262
|
}
|
|
242
263
|
const bound = reachHost === V4_LOOPBACK_CLIENTS_DIAL || reachHost === "::1";
|
package/bin/mulmoterminal.js
CHANGED
|
@@ -478,16 +478,23 @@ function runServer(port, probedAddress, localhostIsUnambiguous, noOpen, cwd, onC
|
|
|
478
478
|
// binding — that is #1876's fix and it stays. The URL asks "where does this user's browser
|
|
479
479
|
// keep its state", and the only answer that does not empty the app is the one it has always
|
|
480
480
|
// been given: see browserUrl.
|
|
481
|
-
|
|
481
|
+
// `serverSaysLocalhostIsOurs` is the child's own report and OUTRANKS the probe when present.
|
|
482
|
+
// The probe ran before the child existed, so it cannot see a process that claimed EITHER
|
|
483
|
+
// loopback during the boot — only the child knows how its own binds went (Codex, PR #1903).
|
|
484
|
+
// `undefined` means the child said nothing about it, and then the probe is all there is.
|
|
485
|
+
const beginReady = (reachHost, serverSaysLocalhostIsOurs) => {
|
|
482
486
|
if (readyStarted) return;
|
|
483
487
|
readyStarted = true;
|
|
484
|
-
const
|
|
488
|
+
const localhostIsOurs = localhostIsUnambiguous && serverSaysLocalhostIsOurs !== false;
|
|
489
|
+
const { url, note } = launchTarget(reachHost, port, localhostIsOurs);
|
|
485
490
|
cancelReady = waitUntilReady(port, () => announceReady(url, note, noOpen), { host: reachHost });
|
|
486
491
|
};
|
|
487
492
|
server.on("message", (msg) => {
|
|
488
493
|
if (!isRecordLike(msg) || msg.type !== "listening" || typeof msg.address !== "string") return;
|
|
489
494
|
const reported = launcherReachHost(msg.address);
|
|
490
|
-
|
|
495
|
+
// Absent rather than false when the field is missing, so "an older child said nothing" and
|
|
496
|
+
// "this child could not take it" stay different answers.
|
|
497
|
+
if (reported) beginReady(reported, typeof msg.localhostIsOurs === "boolean" ? msg.localhostIsOurs : undefined);
|
|
491
498
|
});
|
|
492
499
|
// The fallback runs ONLY on an address we can name without asking anyone. Guessing from a
|
|
493
500
|
// NAME is what the child's report exists to replace, and a fallback that guessed anyway just
|
|
@@ -499,7 +506,8 @@ function runServer(port, probedAddress, localhostIsUnambiguous, noOpen, cwd, onC
|
|
|
499
506
|
// into something connectable; BIND_HOST is the last resort and returns null for a name.
|
|
500
507
|
const guessed = probedAddress ? launcherReachHost(probedAddress) : launcherReachHost(BIND_HOST);
|
|
501
508
|
const fallbackReady = setTimeout(() => {
|
|
502
|
-
|
|
509
|
+
// No report, so no v6 answer either — the probe is all this path ever had.
|
|
510
|
+
if (guessed) return beginReady(guessed, undefined);
|
|
503
511
|
if (!readyStarted)
|
|
504
512
|
log(`Started, but ${BIND_HOST} is a name and the server has not reported which address it bound — not guessing. It may still be starting.`);
|
|
505
513
|
}, REPORTED_ADDRESS_GRACE_MS);
|
package/common/keymap.ts
CHANGED
|
@@ -40,6 +40,16 @@ export const isKeymapAction = (value: unknown): value is KeymapAction => typeof
|
|
|
40
40
|
// ^C. A handler that has already swallowed the key cannot change its mind.
|
|
41
41
|
export const TERMINAL_SCOPED_ACTIONS: readonly KeymapAction[] = ["copy", "paste"];
|
|
42
42
|
|
|
43
|
+
// Actions that act ON a terminal and so need one the grid can name. The enlarged cell is the only
|
|
44
|
+
// such state the grid has, so un-zoomed these do nothing rather than guessing which cell was meant.
|
|
45
|
+
// `terminal-new` is exempt (appending needs no subject), and so are `zoom-toggle` / `next-attention`,
|
|
46
|
+
// which pick the cell themselves — that is what makes them the keyboard's way INTO the zoom.
|
|
47
|
+
//
|
|
48
|
+
// Here rather than beside gridShortcutFor because BOTH sides decide from it: the grid dispatches on
|
|
49
|
+
// it, and validateKeymap has to know that these decline the key — the handler returns WITHOUT
|
|
50
|
+
// stopping the event, so a `send` on the same keystroke fires instead (codex on #1906).
|
|
51
|
+
export const NEEDS_A_CURRENT_TERMINAL: readonly KeymapAction[] = ["zoom-next", "zoom-prev", "terminal-new-adjacent", "terminal-close"];
|
|
52
|
+
|
|
43
53
|
// A key that puts BYTES into the focused terminal instead of running an app action (#1005) —
|
|
44
54
|
// Cmd+Right as Ctrl+E for end-of-line, say, or Alt+B for word-back.
|
|
45
55
|
//
|
|
@@ -164,9 +174,17 @@ export function validateKeymap(input: unknown): KeymapProblem[] {
|
|
|
164
174
|
return [{ action: "keymap", binding: input, reason: "`keymap` must be an object of action -> key binding", fatal: true }];
|
|
165
175
|
}
|
|
166
176
|
const entries = Object.entries(input);
|
|
167
|
-
// A claim on one keystroke. `rank` is DISPATCH order, so the winner can be named
|
|
168
|
-
//
|
|
169
|
-
//
|
|
177
|
+
// A claim on one keystroke. `rank` is DISPATCH order, so the winner can be named — with ONE
|
|
178
|
+
// exception, which is why `kind` is carried alongside it.
|
|
179
|
+
//
|
|
180
|
+
// Most actions are claimed by the grid in the capture phase, before the terminal sees the key.
|
|
181
|
+
// `copy` and `paste` are not (TERMINAL_SCOPED_ACTIONS — gridShortcutFor skips them); they are
|
|
182
|
+
// decided inside the terminal by clipboardActionFor, which runs ahead of the send handler. So
|
|
183
|
+
// `paste` still outranks a send.
|
|
184
|
+
//
|
|
185
|
+
// `copy` does NOT, unconditionally: clipboardActionFor returns null when nothing is selected —
|
|
186
|
+
// deliberately, so Ctrl+C stays an interrupt — and the same-key send fires then. Naming `copy`
|
|
187
|
+
// as the winner there tells the user the opposite of what they will see half the time (#1901).
|
|
170
188
|
const bound = new Map<string, Claim[]>();
|
|
171
189
|
const claim = (parsed: KeyBinding, entry: Claim): void => {
|
|
172
190
|
const key = canonicalBinding(parsed); // as PARSED: "Shift+PageUp" and "shift+pageup" are one keystroke
|
|
@@ -182,7 +200,7 @@ export function validateKeymap(input: unknown): KeymapProblem[] {
|
|
|
182
200
|
if (parsed === null) {
|
|
183
201
|
return [{ action, binding, reason: 'unparseable key binding — expected e.g. "PageDown" or "Shift+PageUp"', fatal: true }];
|
|
184
202
|
}
|
|
185
|
-
claim(parsed, { label: action, binding, rank: KEYMAP_ACTIONS.indexOf(action) });
|
|
203
|
+
claim(parsed, { label: action, binding, rank: KEYMAP_ACTIONS.indexOf(action), kind: "action" });
|
|
186
204
|
return [];
|
|
187
205
|
});
|
|
188
206
|
return [...problems, ...duplicateWarnings(bound)];
|
|
@@ -192,6 +210,9 @@ interface Claim {
|
|
|
192
210
|
label: string;
|
|
193
211
|
binding: string;
|
|
194
212
|
rank: number;
|
|
213
|
+
/** Which side of the dispatch this is. `copy` vs a `send` is the one collision whose winner
|
|
214
|
+
* depends on runtime state, and that cannot be read back off `label`. */
|
|
215
|
+
kind: "action" | "send";
|
|
195
216
|
}
|
|
196
217
|
|
|
197
218
|
// Everything wrong with the `send` list, claiming the keystrokes that are well-formed.
|
|
@@ -214,7 +235,7 @@ function sendProblems(input: unknown, claim: (parsed: KeyBinding, entry: Claim)
|
|
|
214
235
|
return [{ action: label, binding: entry.key, reason: "`bytes` is empty — the key would be taken from the terminal and nothing sent", fatal: true }];
|
|
215
236
|
}
|
|
216
237
|
// Ranked after every action, matching who actually wins (see the `bound` comment above).
|
|
217
|
-
claim(parsed, { label, binding: entry.key, rank: KEYMAP_ACTIONS.length + i });
|
|
238
|
+
claim(parsed, { label, binding: entry.key, rank: KEYMAP_ACTIONS.length + i, kind: "send" });
|
|
218
239
|
return [];
|
|
219
240
|
});
|
|
220
241
|
}
|
|
@@ -229,15 +250,55 @@ function duplicateWarnings(bound: Map<string, Claim[]>): KeymapProblem[] {
|
|
|
229
250
|
if (claims.length < 2) return [];
|
|
230
251
|
const [winner, ...losers] = [...claims].sort((a, b) => a.rank - b.rank);
|
|
231
252
|
if (!winner) return []; // unreachable: claims.length >= 2 was checked above
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
253
|
+
const runnerUp = fallthroughWinner(winner, losers);
|
|
254
|
+
return losers.map((loser) => ({
|
|
255
|
+
action: loser.label,
|
|
256
|
+
binding: loser.binding,
|
|
257
|
+
reason: collisionReason(winner, runnerUp, loser),
|
|
236
258
|
fatal: false,
|
|
237
259
|
}));
|
|
238
260
|
});
|
|
239
261
|
}
|
|
240
262
|
|
|
263
|
+
// An action that DECLINES the keystroke in some runtime state, letting it fall through to a `send`
|
|
264
|
+
// binding on the same key. Naming such an action as the sole winner is wrong whenever the user is
|
|
265
|
+
// in the other state, which is the whole of #1901.
|
|
266
|
+
//
|
|
267
|
+
// Two handlers decline, for the same structural reason — each returns WITHOUT stopping the event:
|
|
268
|
+
//
|
|
269
|
+
// - `copy` with no selection (clipboardActionFor), deliberately, so Ctrl+C stays interrupt.
|
|
270
|
+
// - NEEDS_A_CURRENT_TERMINAL with nothing enlarged (gridShortcutFor).
|
|
271
|
+
//
|
|
272
|
+
// `acts` and `otherwise` are the two halves of what the user is told.
|
|
273
|
+
interface StandsAside {
|
|
274
|
+
acts: string;
|
|
275
|
+
otherwise: string;
|
|
276
|
+
}
|
|
277
|
+
const WHILE_ENLARGED: StandsAside = { acts: "only while a terminal is enlarged", otherwise: "when none is" };
|
|
278
|
+
const standsAside = (label: string): StandsAside | null => {
|
|
279
|
+
if (label === "copy") return { acts: "only while text is selected", otherwise: "when nothing is" };
|
|
280
|
+
return NEEDS_A_CURRENT_TERMINAL.some((action) => action === label) ? WHILE_ENLARGED : null;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// The claim that fires when the winner stands aside, or null when the winner takes every state.
|
|
284
|
+
//
|
|
285
|
+
// Only a send can be this one. `actionForKey` returns the lowest-ranked bound action and stops, so
|
|
286
|
+
// a second ACTION on the key is never reached in either state; and `sendBytesFor` takes the FIRST
|
|
287
|
+
// match, so only the first send is. A conditional collision therefore has exactly TWO reachable
|
|
288
|
+
// claims and every later one is unreachable in BOTH states.
|
|
289
|
+
const fallthroughWinner = (winner: Claim, losers: Claim[]): Claim | null =>
|
|
290
|
+
winner.kind === "action" && standsAside(winner.label) !== null ? (losers.find((claim) => claim.kind === "send") ?? null) : null;
|
|
291
|
+
|
|
292
|
+
// What the user will actually see, which is not always a single winner.
|
|
293
|
+
const collisionReason = (winner: Claim, runnerUp: Claim | null, loser: Claim): string => {
|
|
294
|
+
const aside = standsAside(winner.label);
|
|
295
|
+
if (runnerUp === null || aside === null) return `same keystroke as \`${winner.label}\` — only \`${winner.label}\` will fire`;
|
|
296
|
+
if (loser === runnerUp) {
|
|
297
|
+
return `same keystroke as \`${winner.label}\` — \`${winner.label}\` acts ${aside.acts}, and \`${loser.label}\` fires ${aside.otherwise}`;
|
|
298
|
+
}
|
|
299
|
+
return `same keystroke as \`${winner.label}\` and \`${runnerUp.label}\` — \`${winner.label}\` acts ${aside.acts} and \`${runnerUp.label}\` fires ${aside.otherwise}, so \`${loser.label}\` never fires`;
|
|
300
|
+
};
|
|
301
|
+
|
|
241
302
|
// A binding's identity as a keystroke, for spotting two actions that claim the same one.
|
|
242
303
|
const canonicalBinding = (b: KeyBinding): string => `${b.shift ? "S" : ""}${b.alt ? "A" : ""}${b.ctrl ? "C" : ""}${b.meta ? "M" : ""}|${b.key}`;
|
|
243
304
|
|