mulmoterminal 5.0.1 → 5.2.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.
Files changed (139) hide show
  1. package/common/agentTitle.ts +23 -0
  2. package/common/keymap.ts +45 -3
  3. package/dist/assets/{AllPackages-B1YCJRYB-4WQ_EIrl.js → AllPackages-B1YCJRYB-Byv5Y2l8.js} +1 -1
  4. package/dist/assets/{BaseConfiguration-B6s_tKOW-CuXGj6gD.js → BaseConfiguration-B6s_tKOW-D-gjoRXo.js} +1 -1
  5. package/dist/assets/{Element-BMI7Nj5w-Bik3AfgD.js → Element-BMI7Nj5w-pmadKN44.js} +1 -1
  6. package/dist/assets/{Entities-C8p_0jCn-CjiJEF3r.js → Entities-C8p_0jCn-DOze-aLz.js} +1 -1
  7. package/dist/assets/{FunctionList-YWk54TCy-Dq1GQL-M.js → FunctionList-YWk54TCy-ChT_85FS.js} +1 -1
  8. package/dist/assets/{InputJax-C5dj-ntL-DEjnMcSa.js → InputJax-C5dj-ntL-QDnGDgyA.js} +1 -1
  9. package/dist/assets/{MathItem-YJvQL-XJ-pMaMH7Pe.js → MathItem-YJvQL-XJ-C1WmSlqG.js} +1 -1
  10. package/dist/assets/{MmlFactory-DSoKkE7s-BTFYdeyR.js → MmlFactory-DSoKkE7s-BSods9g3.js} +1 -1
  11. package/dist/assets/{Options-oARQeeuT-DN-QpMwi.js → Options-oARQeeuT-BezHG2DV.js} +1 -1
  12. package/dist/assets/{OutputJax-DfUZNNCu-0eNVVspA.js → OutputJax-DfUZNNCu-CiGZyFkj.js} +1 -1
  13. package/dist/assets/{PrioritizedList-6lun1Xqd-B3xDwkAm.js → PrioritizedList-6lun1Xqd-1YFOP2UD.js} +1 -1
  14. package/dist/assets/{Styles-5NmwS-3M-CYH8SIw8.js → Styles-5NmwS-3M-CckuzfVu.js} +1 -1
  15. package/dist/assets/{TeXAtom-B5ni9myz-B2Lg1OeP.js → TeXAtom-B5ni9myz-czpsshhI.js} +1 -1
  16. package/dist/assets/{abnfDiagram-VCTEODGH-BGhi-oVb-rfnIw5mF.js → abnfDiagram-VCTEODGH-BGhi-oVb-CxSD522K.js} +1 -1
  17. package/dist/assets/{arc-CK5ru50N-CpwYDtFh.js → arc-CK5ru50N-DKCl2r1T.js} +1 -1
  18. package/dist/assets/architecture-7GRP2DOG-otSkCkT5-YabtMIbr.js +1 -0
  19. package/dist/assets/{architectureDiagram-5GKGNRK7-ELuPYMlf-jRFBK2xY.js → architectureDiagram-5GKGNRK7-ELuPYMlf-Dopud1bd.js} +1 -1
  20. package/dist/assets/{assistive-mml-CdU3rUpx-CfM4F5jW.js → assistive-mml-CdU3rUpx-CBXDqBb7.js} +1 -1
  21. package/dist/assets/{blockDiagram-I7D4REHJ-BZ7-OGWQ-GPkhdnf1.js → blockDiagram-I7D4REHJ-BZ7-OGWQ-o5hOntDX.js} +1 -1
  22. package/dist/assets/{c4Diagram-7LVT6UL2-DCovPZ_4-DlgnltG1.js → c4Diagram-7LVT6UL2-DCovPZ_4-DmxXKUN2.js} +1 -1
  23. package/dist/assets/channel-Bmu_OOc6-LXxQltDk.js +1 -0
  24. package/dist/assets/{chunk-4HAMMTFA-DqpzLmar-BYwab83k.js → chunk-4HAMMTFA-DqpzLmar-DiA8EWSJ.js} +1 -1
  25. package/dist/assets/{chunk-75Z2AOVW-CT5TYzp5-BRr2sap2.js → chunk-75Z2AOVW-CT5TYzp5-lldrjFxV.js} +1 -1
  26. package/dist/assets/{chunk-DU6HZSFF-BVf-rtdf-BiTD5bH2.js → chunk-DU6HZSFF-BVf-rtdf-CwUL1NFa.js} +1 -1
  27. package/dist/assets/{chunk-F27PBJKO-rP8pUsk3-jZL8tnaO.js → chunk-F27PBJKO-rP8pUsk3-CzNXy7AU.js} +1 -1
  28. package/dist/assets/{chunk-IMKFNOWR-D_IT0r3G-4Na8DCGZ.js → chunk-IMKFNOWR-D_IT0r3G-B9KUwB52.js} +1 -1
  29. package/dist/assets/{chunk-POPQ4Y6H-C5YURnHe-BtvIAZQ9.js → chunk-POPQ4Y6H-C5YURnHe-BSqsqkgY.js} +1 -1
  30. package/dist/assets/{chunk-SHT3W25Y-CfF0J8Qh-CSKY6pei.js → chunk-SHT3W25Y-CfF0J8Qh-5fHXXkh7.js} +1 -1
  31. package/dist/assets/{chunk-SVP7TREG-CTZbRIbX-DXmyUlG6.js → chunk-SVP7TREG-CTZbRIbX-CWrs29Kz.js} +1 -1
  32. package/dist/assets/{chunk-TICWLB2K-DP1lYmZm-2WQGl4am.js → chunk-TICWLB2K-DP1lYmZm-DDcA7wX4.js} +1 -1
  33. package/dist/assets/{chunk-XXDRQBXY-vg0kfC73-BNbpCBp8.js → chunk-XXDRQBXY-vg0kfC73-B9m3UCLq.js} +1 -1
  34. package/dist/assets/classDiagram-ZZMXUADV-BDQOAOBf-BUvThf0-.js +1 -0
  35. package/dist/assets/classDiagram-v2-VYDZK3BY-CwPXtzoF-BUvThf0-.js +1 -0
  36. package/dist/assets/{cose-bilkent-JH36ORCC-qIiD3RGD-glIzrMsu.js → cose-bilkent-JH36ORCC-qIiD3RGD-8iqUd8fN.js} +1 -1
  37. package/dist/assets/cynefin-OW5HDTMX-Xr6IPdZG-BzdrGHCp.js +1 -0
  38. package/dist/assets/{cynefinDiagram-5FMLGOSQ-BZrUCS-v-BP9iYDiL.js → cynefinDiagram-5FMLGOSQ-BZrUCS-v-BjGdpO1b.js} +1 -1
  39. package/dist/assets/{dagre-C2SZ8h4o-Cvn7MMN8.js → dagre-C2SZ8h4o-Bv8J8VUz.js} +1 -1
  40. package/dist/assets/{dagre-GXQ25YYZ-CQcLM4na-CUPIues9.js → dagre-GXQ25YYZ-CQcLM4na-BEUkvakq.js} +1 -1
  41. package/dist/assets/{diagram-S7CK7UJ4-BdmcmUQh-BzmDaJl4.js → diagram-S7CK7UJ4-BdmcmUQh-C17Q-TTS.js} +1 -1
  42. package/dist/assets/{diagram-UQ7AKVKN-BtvmEiYl-C-SDkeQT.js → diagram-UQ7AKVKN-BtvmEiYl-sOGBhdh3.js} +1 -1
  43. package/dist/assets/{diagram-VSXAHHWV-qEG6N0bV-BBEGoVq0.js → diagram-VSXAHHWV-qEG6N0bV-D-F3WtOn.js} +1 -1
  44. package/dist/assets/{diagram-VX7I27RA-BA8is49N-C_RijuTO.js → diagram-VX7I27RA-BA8is49N-DDC2KnwX.js} +1 -1
  45. package/dist/assets/{diagram-Z3DM3KII-XPcU7IOQ-CxOwdwHR.js → diagram-Z3DM3KII-XPcU7IOQ-DnNwyEbY.js} +1 -1
  46. package/dist/assets/{dist-uQlE_JYp.js → dist-B3WvX9Mu.js} +1 -1
  47. package/dist/assets/{dist-BvG2w_9k.js → dist-B_fklXwV.js} +1 -1
  48. package/dist/assets/{dist-DBRVDw9D.js → dist-BfgqBdfY.js} +1 -1
  49. package/dist/assets/{dist-CWF1S7vL.js → dist-CqavRLcS.js} +1 -1
  50. package/dist/assets/{dist-DeiyD9Ki-BDcmanZh.js → dist-DeiyD9Ki-CTmQI5BY.js} +1 -1
  51. package/dist/assets/{ebnfDiagram-PWID7BFC-CTHIHdBS-BGwyAx_X.js → ebnfDiagram-PWID7BFC-CTHIHdBS-BUK0gvW3.js} +1 -1
  52. package/dist/assets/{erDiagram-RLTQ6QDP-DVpWTNZj-CpwnuTPx.js → erDiagram-RLTQ6QDP-DVpWTNZj-WczcFdQ5.js} +1 -1
  53. package/dist/assets/eventmodeling-NTZA5JFV-B7bRrpt_-izWjOGe8.js +1 -0
  54. package/dist/assets/flowDiagram-HODETNUW-r-3T5g30-D06E2Yur.js +1 -0
  55. package/dist/assets/{ganttDiagram-EL5Y4UJY-BfAgk15F-CuhMMDMU.js → ganttDiagram-EL5Y4UJY-BfAgk15F-DTGMCrLj.js} +1 -1
  56. package/dist/assets/gitGraph-4MIJSDKK-BhzSx5wq-HItyG1D5.js +1 -0
  57. package/dist/assets/{gitGraphDiagram-WWUBYQGX-DEVVZ6wt-Bh_VaB4G.js → gitGraphDiagram-WWUBYQGX-DEVVZ6wt-HdaEqyjq.js} +1 -1
  58. package/dist/assets/{graphlib-BYLhHyYO-BCQclP1Y.js → graphlib-BYLhHyYO-DQGQ3Hua.js} +1 -1
  59. package/dist/assets/{html-Z7dGbczU-UIlR1xAp.js → html-Z7dGbczU-CrSYt5SB.js} +1 -1
  60. package/dist/assets/{index-sxsMmFcI.js → index-CFCwHCAC.js} +322 -322
  61. package/dist/assets/index-ZQzSIgvD.css +1 -0
  62. package/dist/assets/info-A6RAGUB7-fR2SUJmJ-CCp3Pve-.js +1 -0
  63. package/dist/assets/{infoDiagram-27XIBGKW-CvHUhAHd-DigY259e.js → infoDiagram-27XIBGKW-CvHUhAHd-Bqc9FCJv.js} +1 -1
  64. package/dist/assets/{ishikawaDiagram-5VMMS53U-CeqyKdwk-ACmU-Jzv.js → ishikawaDiagram-5VMMS53U-CeqyKdwk-BTaokajf.js} +1 -1
  65. package/dist/assets/{journeyDiagram-3NMN7TZE-Jfi7MPDi-YIFwpJyP.js → journeyDiagram-3NMN7TZE-Jfi7MPDi-DG9WVFfp.js} +1 -1
  66. package/dist/assets/{kanban-definition-UXKFOSKX-yOrlojns-0RXVl3u9.js → kanban-definition-UXKFOSKX-yOrlojns-B5-MNvAZ.js} +1 -1
  67. package/dist/assets/{lengths-CvmPfBid-BAfNjGrF.js → lengths-CvmPfBid-Ccc3uBwx.js} +1 -1
  68. package/dist/assets/{linear-jdmEy-qs-Djjlqhox.js → linear-jdmEy-qs-BRxqk9pe.js} +1 -1
  69. package/dist/assets/{liteAdaptor-pH5MXTG0-pAc8cmKr.js → liteAdaptor-pH5MXTG0-D2DVKbJh.js} +1 -1
  70. package/dist/assets/material-symbols-outlined-DhPLOrnb.woff2 +0 -0
  71. package/dist/assets/{mathjax-DVEo4UYj-Cnj1zcS1.js → mathjax-DVEo4UYj-z2lf4I_o.js} +1 -1
  72. package/dist/assets/{mermaid-parser.core-CzBW8Fof-s3bWPn0O.js → mermaid-parser.core-CzBW8Fof-kR4f_D21.js} +2 -2
  73. package/dist/assets/{mermaid.core-Dv4uylek-DqGXjOPu.js → mermaid.core-Dv4uylek-Ddm4KdK9.js} +4 -4
  74. package/dist/assets/{mindmap-definition-YA3MSWOX-DP_2R9Ga-KrOUlEvG.js → mindmap-definition-YA3MSWOX-DP_2R9Ga-BamcFU30.js} +1 -1
  75. package/dist/assets/{mo-Xv_6CC7A-5T19MjVl.js → mo-Xv_6CC7A-C4qQoXD7.js} +1 -1
  76. package/dist/assets/packet-AYTQ26CC-B_oKR9KZ-P1h4UNRU.js +1 -0
  77. package/dist/assets/{pegDiagram-XKGWAZYB-BsBWryhi-DYwgFu60.js → pegDiagram-XKGWAZYB-BsBWryhi-BPOLi_DS.js} +1 -1
  78. package/dist/assets/pie-WAS4IAKB-D1ZXrN1W-BejuFjHy.js +1 -0
  79. package/dist/assets/{pieDiagram-E7YTZNPT-OugkS2pU-CitvFGku.js → pieDiagram-E7YTZNPT-OugkS2pU-BxJPZt-m.js} +1 -1
  80. package/dist/assets/{quadrantDiagram-AXDQQJYC-DXEAAaUE-BJz-5fWN.js → quadrantDiagram-AXDQQJYC-DXEAAaUE-CeLGIG_l.js} +1 -1
  81. package/dist/assets/radar-RG4KPBEZ-BmUgDkiH-Wu_t-Az-.js +1 -0
  82. package/dist/assets/railroad-74A4TZTK-6lEXVJo8-DJe4dT-U.js +1 -0
  83. package/dist/assets/railroad-abnf-HS5TGJTU-BG28Qiz1-M9sfPaQt.js +1 -0
  84. package/dist/assets/railroad-ebnf-LZEXJU2U-DzpHhtTH-C1RD0jlr.js +1 -0
  85. package/dist/assets/railroad-peg-WCYAUIDC-B2CHCTyG-CsDICDaK.js +1 -0
  86. package/dist/assets/{railroadDiagram-O6MQD6OU-BJys8O96-8S8j18MZ.js → railroadDiagram-O6MQD6OU-BJys8O96-CXO-wle8.js} +1 -1
  87. package/dist/assets/{requirementDiagram-BXWQKSXE-BaDgCtM5-D0sLbDHs.js → requirementDiagram-BXWQKSXE-BaDgCtM5-Dt4Cfr6O.js} +1 -1
  88. package/dist/assets/{sankeyDiagram-P5KCCOFB-puZsr_vK-C1OX3N7F.js → sankeyDiagram-P5KCCOFB-puZsr_vK-CMC9fgwz.js} +1 -1
  89. package/dist/assets/{sequenceDiagram-WJ2MYXX4-D1ziEryC-CvoJhcsM.js → sequenceDiagram-WJ2MYXX4-D1ziEryC-DB5eVRRY.js} +1 -1
  90. package/dist/assets/{src-CLC0ddSd-D-EBSy-7.js → src-CLC0ddSd-ByF0JTrb.js} +1 -1
  91. package/dist/assets/{stateDiagram-D77RDMKH-HSEurT6h-DWNnPlrT.js → stateDiagram-D77RDMKH-HSEurT6h-BwSqvh1q.js} +1 -1
  92. package/dist/assets/stateDiagram-v2-MP3YSRHH-DnOa-Otc-DgFYwvfo.js +1 -0
  93. package/dist/assets/{svg-D_RYJvQC-xxS_UOMd.js → svg-D_RYJvQC-BPJwglJJ.js} +1 -1
  94. package/dist/assets/{swimlanes-42K2YHIH-umBsuvsI-BE4MiInp.js → swimlanes-42K2YHIH-umBsuvsI-BAnYSTii.js} +1 -1
  95. package/dist/assets/swimlanesDiagram-VR7AAH4N-ZLUyNmnU-CM3sLR6V.js +8 -0
  96. package/dist/assets/{tex-Dx2UJ3VU-Bvt244KZ.js → tex-Dx2UJ3VU-CCsHHGCD.js} +1 -1
  97. package/dist/assets/{timeline-definition-24CTP7MA-D9plt22Y-DHwEP0-q.js → timeline-definition-24CTP7MA-D9plt22Y-BX4CiOEk.js} +1 -1
  98. package/dist/assets/treeView-Q6P3EWNA-BdvZ0_C5-Ce03QaRO.js +1 -0
  99. package/dist/assets/treemap-WGGIJYW6-CIJLPQlC-sLYXmnbr.js +1 -0
  100. package/dist/assets/{vennDiagram-4TSXK5OY-DZe7wY85-BzqCxOQu.js → vennDiagram-4TSXK5OY-DZe7wY85-BRytU48n.js} +1 -1
  101. package/dist/assets/wardley-WFR3VGLG-CKqtFD8V-CR-W1Hkl.js +1 -0
  102. package/dist/assets/{wardleyDiagram-VM6X3IG4-DK9yytWx-FK9bW_eQ.js → wardleyDiagram-VM6X3IG4-DK9yytWx-DVNY3c-A.js} +1 -1
  103. package/dist/assets/{xychartDiagram-S5SC5T6Z-CLSO4Pc9-BGF6axe4.js → xychartDiagram-S5SC5T6Z-CLSO4Pc9-XnrHm8Bf.js} +1 -1
  104. package/dist/index.html +2 -2
  105. package/package.json +1 -1
  106. package/server/agents/agent-session-title.ts +227 -0
  107. package/server/agents/antigravity-sessions.ts +12 -3
  108. package/server/agents/bounded-cache.ts +30 -0
  109. package/server/agents/codex-sessions.ts +40 -1
  110. package/server/agents/copilot-sessions.ts +21 -0
  111. package/server/agents/cursor-sessions.ts +49 -1
  112. package/server/agents/muse-session.ts +23 -1
  113. package/server/agents/sqlite-read.ts +19 -2
  114. package/server/config/keymap-check.ts +4 -2
  115. package/server/routes/session-routes.ts +65 -16
  116. package/server/skills/mulmoterminal-keys/SKILL.md +19 -2
  117. package/dist/assets/architecture-7GRP2DOG-otSkCkT5-BXYe9Y4x.js +0 -1
  118. package/dist/assets/channel-Bmu_OOc6-Byl1e1Rn.js +0 -1
  119. package/dist/assets/classDiagram-ZZMXUADV-BDQOAOBf-DGA1_qS0.js +0 -1
  120. package/dist/assets/classDiagram-v2-VYDZK3BY-CwPXtzoF-DGA1_qS0.js +0 -1
  121. package/dist/assets/cynefin-OW5HDTMX-Xr6IPdZG-DCyyQmwN.js +0 -1
  122. package/dist/assets/eventmodeling-NTZA5JFV-B7bRrpt_-DWVjLwmJ.js +0 -1
  123. package/dist/assets/flowDiagram-HODETNUW-r-3T5g30-CjAJJOoL.js +0 -1
  124. package/dist/assets/gitGraph-4MIJSDKK-BhzSx5wq-CmU_NC2u.js +0 -1
  125. package/dist/assets/index-DgdW3UkV.css +0 -1
  126. package/dist/assets/info-A6RAGUB7-fR2SUJmJ-Dc6y50d4.js +0 -1
  127. package/dist/assets/material-symbols-outlined-Dp0GkOHZ.woff2 +0 -0
  128. package/dist/assets/packet-AYTQ26CC-B_oKR9KZ-DzcrK2qY.js +0 -1
  129. package/dist/assets/pie-WAS4IAKB-D1ZXrN1W-EXLeDSJc.js +0 -1
  130. package/dist/assets/radar-RG4KPBEZ-BmUgDkiH-CYsT6Q2i.js +0 -1
  131. package/dist/assets/railroad-74A4TZTK-6lEXVJo8-CGKkcPl-.js +0 -1
  132. package/dist/assets/railroad-abnf-HS5TGJTU-BG28Qiz1-CX4pHVjI.js +0 -1
  133. package/dist/assets/railroad-ebnf-LZEXJU2U-DzpHhtTH-DuFCuwxK.js +0 -1
  134. package/dist/assets/railroad-peg-WCYAUIDC-B2CHCTyG-BZCRCTz5.js +0 -1
  135. package/dist/assets/stateDiagram-v2-MP3YSRHH-DnOa-Otc-jwvolGHy.js +0 -1
  136. package/dist/assets/swimlanesDiagram-VR7AAH4N-ZLUyNmnU-DNoFZykH.js +0 -8
  137. package/dist/assets/treeView-Q6P3EWNA-BdvZ0_C5-Cw7gALPI.js +0 -1
  138. package/dist/assets/treemap-WGGIJYW6-CIJLPQlC-BuBwqHqi.js +0 -1
  139. package/dist/assets/wardley-WFR3VGLG-CKqtFD8V-Ds6_n_M_.js +0 -1
@@ -0,0 +1,23 @@
1
+ // The cap on what an agent's own store label may be, shipped to the roster.
2
+ //
3
+ // In `common/` because BOTH sides decide from it: the server cuts the title here, and the client has
4
+ // to know that a title of exactly this length may be a TRUNCATION of the prompt beneath it rather
5
+ // than the whole of it. Without that, the roster cannot tell "the summary is the entire prompt, cut
6
+ // by us" from "the summary is a shorter, different sentence that happens to start the same way", and
7
+ // suppressing the second hides a real opening (#2123, Codex round 4).
8
+ export const AGENT_TITLE_MAX = 200;
9
+
10
+ /** What an `agentTitle` IS, which the roster cannot work out from the string itself.
11
+ *
12
+ * Four agents answer with the person's OPENING PROMPT; two answer with a summary the agent wrote
13
+ * for itself. The difference decides whether a title that is exactly `AGENT_TITLE_MAX` long may be
14
+ * treated as our own truncation of the prompt beneath it — for an opening prompt it is, for an
15
+ * agent's own summary it may simply be a long sentence that happens to start the same way.
16
+ *
17
+ * Sent per response rather than derived on the client, so the two cannot drift: the server states
18
+ * what it just read. */
19
+ export type AgentTitleKind = "opening-prompt" | "agent-summary";
20
+
21
+ /** Which kind each agent's store answers with. copilot and muse write their own summary — and are
22
+ * also the two the reader never caches, because they REWRITE it as the conversation goes. */
23
+ export const agentTitleKind = (agent: string): AgentTitleKind => (agent === "copilot" || agent === "muse" ? "agent-summary" : "opening-prompt");
package/common/keymap.ts CHANGED
@@ -16,6 +16,8 @@ export const KEYMAP_ACTIONS = [
16
16
  "zoom-toggle",
17
17
  "zoom-next",
18
18
  "zoom-prev",
19
+ "focus-next",
20
+ "focus-prev",
19
21
  "next-attention",
20
22
  "terminal-new",
21
23
  "terminal-new-here",
@@ -62,6 +64,18 @@ export const NEEDS_A_CURRENT_TERMINAL: readonly KeymapAction[] = [
62
64
  "files-find",
63
65
  ];
64
66
 
67
+ // The mirror of the list above: actions that walk the TILED grid, and so need nothing enlarged.
68
+ // While a cell is, every other cell is either off-screen or parked in the roster, and moving the
69
+ // cursor into one would put it somewhere the user cannot see.
70
+ //
71
+ // This is deliberately NOT one key meaning two things by state. An action has ONE meaning and
72
+ // declines where it has none — exactly as NEEDS_A_CURRENT_TERMINAL does in the other state. Binding
73
+ // one keystroke to `zoom-prev` AND `focus-prev` still resolves to a single action, because
74
+ // `actionForKey` stops at the first bound one, and validateKeymap reports that. Picking the action
75
+ // by state would mean an ordered-candidates resolver plus action-to-action collision reporting; see
76
+ // plans/feat-2106-focus-prev-next.md for why that is a change of its own.
77
+ export const NEEDS_NOTHING_ENLARGED: readonly KeymapAction[] = ["focus-next", "focus-prev"];
78
+
65
79
  // A key that puts BYTES into the focused terminal instead of running an app action (#1005) —
66
80
  // Cmd+Right as Ctrl+E for end-of-line, say, or Alt+B for word-back.
67
81
  //
@@ -80,7 +94,8 @@ export type Keymap = Partial<Record<KeymapAction, string>> & { send?: SendBindin
80
94
 
81
95
  // A parsed binding. `key` is matched against `KeyboardEvent.key` exactly as the browser
82
96
  // reports it, so it is case-sensitive for printable characters ("a" and "A" differ, the
83
- // latter implying Shift).
97
+ // latter implying Shift) — except while Cmd is held on macOS, where the browser reports the
98
+ // UNSHIFTED character and an uppercase letter is unreachable (validateKeymap warns, #2125).
84
99
  export interface KeyBinding {
85
100
  key: string;
86
101
  shift: boolean;
@@ -213,11 +228,35 @@ export function validateKeymap(input: unknown): KeymapProblem[] {
213
228
  return [{ action, binding, reason: 'unparseable key binding — expected e.g. "PageDown" or "Shift+PageUp"', fatal: true }];
214
229
  }
215
230
  claim(parsed, { label: action, binding, rank: KEYMAP_ACTIONS.indexOf(action), kind: "action" });
216
- return [];
231
+ return unshiftedUnderCmdWarnings(action, binding, parsed);
217
232
  });
218
233
  return [...problems, ...duplicateWarnings(bound)];
219
234
  }
220
235
 
236
+ // A binding that says one keystroke and waits for another. While Cmd is held, a macOS browser puts
237
+ // the UNSHIFTED character in `KeyboardEvent.key` — Cmd+Shift+P arrives as `"p"` — so a binding
238
+ // written `"Cmd+Shift+P"` waits for a `"P"` that never comes, and nothing downstream can see the
239
+ // difference between that and a shortcut the user has not pressed yet (#2125).
240
+ //
241
+ // Safari and Chrome both do this and w3c/uievents#169 is still open, so it is the platform, not a
242
+ // bug to route around: matching stays case-sensitive (`"a"` and `"A"` are different keystrokes,
243
+ // pinned by the specs) and this only says so out loud.
244
+ //
245
+ // A WARNING, not an error: the deviation is macOS's, so a browser following the spec reports `"P"`
246
+ // and the identical entry is correct there — and the server cannot know which one will connect.
247
+ const UPPERCASE_ASCII_LETTER = /^[A-Z]$/;
248
+ const unshiftedUnderCmdWarnings = (action: string, binding: string, parsed: KeyBinding): KeymapProblem[] =>
249
+ parsed.meta && parsed.shift && UPPERCASE_ASCII_LETTER.test(parsed.key)
250
+ ? [
251
+ {
252
+ action,
253
+ binding,
254
+ reason: `never fires in a macOS browser — with Cmd held it reports the unshifted letter, so write the key lowercase ("${parsed.key.toLowerCase()}")`,
255
+ fatal: false,
256
+ },
257
+ ]
258
+ : [];
259
+
221
260
  interface Claim {
222
261
  label: string;
223
262
  binding: string;
@@ -248,7 +287,7 @@ function sendProblems(input: unknown, claim: (parsed: KeyBinding, entry: Claim)
248
287
  }
249
288
  // Ranked after every action, matching who actually wins (see the `bound` comment above).
250
289
  claim(parsed, { label, binding: entry.key, rank: KEYMAP_ACTIONS.length + i, kind: "send" });
251
- return [];
290
+ return unshiftedUnderCmdWarnings(label, entry.key, parsed);
252
291
  });
253
292
  }
254
293
 
@@ -280,6 +319,7 @@ function duplicateWarnings(bound: Map<string, Claim[]>): KeymapProblem[] {
280
319
  //
281
320
  // - `copy` with no selection (clipboardActionFor), deliberately, so Ctrl+C stays interrupt.
282
321
  // - NEEDS_A_CURRENT_TERMINAL with nothing enlarged (gridShortcutFor).
322
+ // - NEEDS_NOTHING_ENLARGED with something enlarged (gridShortcutFor), the mirror of it.
283
323
  //
284
324
  // `acts` and `otherwise` are the two halves of what the user is told.
285
325
  interface StandsAside {
@@ -287,8 +327,10 @@ interface StandsAside {
287
327
  otherwise: string;
288
328
  }
289
329
  const WHILE_ENLARGED: StandsAside = { acts: "only while a terminal is enlarged", otherwise: "when none is" };
330
+ const WHILE_NOT_ENLARGED: StandsAside = { acts: "only while no terminal is enlarged", otherwise: "when one is" };
290
331
  const standsAside = (label: string): StandsAside | null => {
291
332
  if (label === "copy") return { acts: "only while text is selected", otherwise: "when nothing is" };
333
+ if (NEEDS_NOTHING_ENLARGED.some((action) => action === label)) return WHILE_NOT_ENLARGED;
292
334
  return NEEDS_A_CURRENT_TERMINAL.some((action) => action === label) ? WHILE_ENLARGED : null;
293
335
  };
294
336