mulmoterminal 2.4.0 → 2.5.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 (134) hide show
  1. package/README.md +21 -3
  2. package/common/prPhase.ts +65 -0
  3. package/common/rateLimits.ts +37 -0
  4. package/common/terminalScrollSpeed.ts +31 -0
  5. package/dist/assets/{abnfDiagram-VRR7QNED-RjiYivmv-CqId6L6N.js → abnfDiagram-VRR7QNED-RjiYivmv-ZKZeabOo.js} +1 -1
  6. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-BlV5di_4.js +1 -0
  7. package/dist/assets/{architectureDiagram-ZJ3FMSHR-3nWA91tG-CWhhLcfM.js → architectureDiagram-ZJ3FMSHR-3nWA91tG-CzywLoPM.js} +1 -1
  8. package/dist/assets/{blockDiagram-677ZJIJ3-BPuAJQRW-89yfYblY.js → blockDiagram-677ZJIJ3-BPuAJQRW-aOYmvIDN.js} +1 -1
  9. package/dist/assets/{c4Diagram-LMCZKHZV-C0LAqQso-Cq70UauP.js → c4Diagram-LMCZKHZV-C0LAqQso-DrRq0Tz1.js} +1 -1
  10. package/dist/assets/channel-7wUqSdoX-DBiTAZ-Z.js +1 -0
  11. package/dist/assets/{chunk-32BRIVSS-BRrYpgtb-Bj5FdJ3A.js → chunk-32BRIVSS-BRrYpgtb-D-VyP-2M.js} +1 -1
  12. package/dist/assets/{chunk-52WLFC77-BJ-ss3Xr-Cf6wft_5.js → chunk-52WLFC77-BJ-ss3Xr-xsQKbX6Y.js} +1 -1
  13. package/dist/assets/{chunk-C7G6YPKG-BZEucKEL-CLA5COUu.js → chunk-C7G6YPKG-BZEucKEL-LmuOyVM4.js} +1 -1
  14. package/dist/assets/{chunk-EX3LRPZG-DLS6FBN1-ChsLH6_y.js → chunk-EX3LRPZG-DLS6FBN1-D_ZX8lXD.js} +1 -1
  15. package/dist/assets/{chunk-FWX5IMBZ-DHLSFw1H-BIvnIbPU.js → chunk-FWX5IMBZ-DHLSFw1H-DABXVNFD.js} +2 -2
  16. package/dist/assets/{chunk-HOUHSVGY-Bhlt8hXJ-Ck-Dxtw-.js → chunk-HOUHSVGY-Bhlt8hXJ-Clcclm_e.js} +1 -1
  17. package/dist/assets/{chunk-ICXQ74PX-DwgHBX_g-CSlWVcWs.js → chunk-ICXQ74PX-DwgHBX_g-DVrDEih_.js} +1 -1
  18. package/dist/assets/{chunk-MOJQB5TN-CMZRaeqt-CYfpp2V7.js → chunk-MOJQB5TN-CMZRaeqt-B6_lajzj.js} +1 -1
  19. package/dist/assets/{chunk-OGEWGWER-8Qy4a8b5-DPIlRee4.js → chunk-OGEWGWER-8Qy4a8b5-BK8v7oHr.js} +1 -1
  20. package/dist/assets/{chunk-PUDLZKDR-DcrWQRYh-J86vFVAN.js → chunk-PUDLZKDR-DcrWQRYh-BpZVxE3I.js} +1 -1
  21. package/dist/assets/{chunk-Q4XR5HBZ-ZXVGkG8Z-D-40tbjk.js → chunk-Q4XR5HBZ-ZXVGkG8Z-BJub83F1.js} +1 -1
  22. package/dist/assets/{chunk-V7JOEXUC-DmGdheTX-7gJmS-F9.js → chunk-V7JOEXUC-DmGdheTX-BwD2Cu1l.js} +1 -1
  23. package/dist/assets/{chunk-VAUOI2AC-CS9QJ4yz-DFjCsQc6.js → chunk-VAUOI2AC-CS9QJ4yz-DYmUgyge.js} +1 -1
  24. package/dist/assets/{chunk-VR4S4FIN-C6a91eNY-Ba059rhB.js → chunk-VR4S4FIN-C6a91eNY-CZWE7I5r.js} +1 -1
  25. package/dist/assets/{chunk-WYO6CB5R-BlzOfotS-CTDodsnw.js → chunk-WYO6CB5R-BlzOfotS-CoeWhGPT.js} +1 -1
  26. package/dist/assets/{chunk-ZGVPDNZ5-BYwxNFTK-CMF9FJ7K.js → chunk-ZGVPDNZ5-BYwxNFTK-DHPaQ6Oa.js} +1 -1
  27. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-DEqD9yXA.js +1 -0
  28. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-DEqD9yXA.js +1 -0
  29. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-rrcpRbiY.js +1 -0
  30. package/dist/assets/{cynefinDiagram-TSTJHNR4-DHA9iPo--BUwfErNr.js → cynefinDiagram-TSTJHNR4-DHA9iPo--BVz-2Ntu.js} +1 -1
  31. package/dist/assets/{dagre-VKFMJZFB--oJKqXBZ-FEoLipSZ.js → dagre-VKFMJZFB--oJKqXBZ-CgKI-Did.js} +1 -1
  32. package/dist/assets/{diagram-FQU43EPY-CcJCB9bG-DwH2nER4.js → diagram-FQU43EPY-CcJCB9bG-DIRlUnKd.js} +1 -1
  33. package/dist/assets/{diagram-G47NLZAW-C_o-WGG1-BuSqgofP.js → diagram-G47NLZAW-C_o-WGG1-DNgB0vZj.js} +1 -1
  34. package/dist/assets/{diagram-NH7WQ7WH-CXJCYvY--CoMsubYQ.js → diagram-NH7WQ7WH-CXJCYvY--DtULNtOi.js} +1 -1
  35. package/dist/assets/{diagram-OA4YK3LP-BMzeJ87A-Ds_49Q4I.js → diagram-OA4YK3LP-BMzeJ87A-Betvk9-o.js} +1 -1
  36. package/dist/assets/{diagram-WEI45ONY-D_93NKqo-DeDTEgI-.js → diagram-WEI45ONY-D_93NKqo-DpjErInC.js} +1 -1
  37. package/dist/assets/{ebnfDiagram-CCIWWBDH-CVai1Ii9-B84fKP1h.js → ebnfDiagram-CCIWWBDH-CVai1Ii9-CkKEjG4D.js} +1 -1
  38. package/dist/assets/{erDiagram-Q63AITRT-CiABnA0s-QGRolPYH.js → erDiagram-Q63AITRT-CiABnA0s-BOytIrTd.js} +1 -1
  39. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-lhGPdKsg.js +1 -0
  40. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-Ce3_LeXQ.js +1 -0
  41. package/dist/assets/{ganttDiagram-NO4QXBWP-DQZvdFo1-BBXcle76.js → ganttDiagram-NO4QXBWP-DQZvdFo1-Bv6Ol2Sx.js} +1 -1
  42. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-DgqDgNXY.js +1 -0
  43. package/dist/assets/{gitGraphDiagram-IHSO6WYX-C2ovBouh-BxGBV6Hj.js → gitGraphDiagram-IHSO6WYX-C2ovBouh-aUQOq1B7.js} +1 -1
  44. package/dist/assets/index-BwvrN1j0.css +1 -0
  45. package/dist/assets/{index-_XRnaxAj.js → index-CbGZuCmY.js} +200 -200
  46. package/dist/assets/info-DKCQHKI2-Dplx5kMp-CbcoQy2P.js +1 -0
  47. package/dist/assets/{infoDiagram-FWYZ7A6U-7UnoB5AP-DCqG4iB9.js → infoDiagram-FWYZ7A6U-7UnoB5AP-D5riRt52.js} +1 -1
  48. package/dist/assets/{ishikawaDiagram-FXEZZL3T-ByUDM_N2-Dkzr63Uo.js → ishikawaDiagram-FXEZZL3T-ByUDM_N2-DUHr3ImH.js} +1 -1
  49. package/dist/assets/{journeyDiagram-5HDEW3XC-c5xIah9o-3K64pBTH.js → journeyDiagram-5HDEW3XC-c5xIah9o-BmYfNX5M.js} +1 -1
  50. package/dist/assets/{kanban-definition-HUTT4EX6-Cnt6loYD-B0U7Mkg9.js → kanban-definition-HUTT4EX6-Cnt6loYD-CYSjS-Kr.js} +1 -1
  51. package/dist/assets/{lib-rFkGOb1O.js → lib-4D_VJiWI.js} +1 -1
  52. package/dist/assets/{line-D7ziSjKi-D25izB30.js → line-D7ziSjKi-BSuJe7JA.js} +1 -1
  53. package/dist/assets/{marp-79PhoGbq.js → marp-aQPALUt4.js} +1 -1
  54. package/dist/assets/{mermaid-parser.core-DxEa8E3F-BzSqp82H.js → mermaid-parser.core-DxEa8E3F-BAMiAMZz.js} +2 -2
  55. package/dist/assets/{mermaid.core-V0OYwIz3-0p62b-zk.js → mermaid.core-V0OYwIz3-0t7TpKVw.js} +3 -3
  56. package/dist/assets/{mindmap-definition-LN4V7U3C-CexN3O6L-BzFX06oy.js → mindmap-definition-LN4V7U3C-CexN3O6L-Y5ILic5l.js} +1 -1
  57. package/dist/assets/packet-7NZHBO7P-CQI3flND-Bzib0qjU.js +1 -0
  58. package/dist/assets/{pegDiagram-2B236MQR-BiM4G0cW-BMRJsJZN.js → pegDiagram-2B236MQR-BiM4G0cW-BcRtbtj2.js} +1 -1
  59. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-sgdov0Km.js +1 -0
  60. package/dist/assets/{pieDiagram-ENE6RG2P-B_fBBS-2-Bl2zNUvh.js → pieDiagram-ENE6RG2P-B_fBBS-2-BoLxHrrD.js} +1 -1
  61. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BigGVxCR-BzQ392_S.js → quadrantDiagram-ABIIQ3AL-BigGVxCR-CaBUCRTr.js} +1 -1
  62. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-HeF-YUqy.js +1 -0
  63. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-CVEaRb6p.js +1 -0
  64. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-Da0o3UJC.js +1 -0
  65. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-d8_8RJsZ.js +1 -0
  66. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-CJVm33Iy.js +1 -0
  67. package/dist/assets/{railroadDiagram-RFXS5EU6--lNIlG64-D-Yn0Dpg.js → railroadDiagram-RFXS5EU6--lNIlG64-BZODWkTp.js} +1 -1
  68. package/dist/assets/{requirementDiagram-TGXJPOKE-D-B0Y5Wu-DIYnBCWZ.js → requirementDiagram-TGXJPOKE-D-B0Y5Wu-CTG1__VO.js} +1 -1
  69. package/dist/assets/{sankeyDiagram-HTMAVEWB-DlutaXDv-Dsr5VJzO.js → sankeyDiagram-HTMAVEWB-DlutaXDv-BHkDstqV.js} +1 -1
  70. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BOLgtggH-mR1rIlJQ.js → sequenceDiagram-DBY2YBRQ-BOLgtggH-B47PY7W5.js} +1 -1
  71. package/dist/assets/{stateDiagram-2N3HPSRC-jf_dXUEU-Cq2A7Udg.js → stateDiagram-2N3HPSRC-jf_dXUEU-4bMMcfLt.js} +1 -1
  72. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-BhlNWKJ_.js +1 -0
  73. package/dist/assets/{swimlanes-5IMT3BWC-CYjtALQH-CtgZ4USf.js → swimlanes-5IMT3BWC-CYjtALQH-Dkkp7Xaf.js} +1 -1
  74. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-BIUkJIiD.js +8 -0
  75. package/dist/assets/{timeline-definition-FHXFAJF6-DYJ4oUm8-CpuMCUFK.js → timeline-definition-FHXFAJF6-DYJ4oUm8-CKcCEyil.js} +1 -1
  76. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-BTmguQlN.js +1 -0
  77. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-C69GNR2Y.js +1 -0
  78. package/dist/assets/{vennDiagram-L72KCM5P-CdKAHoek-B-FSFzf3.js → vennDiagram-L72KCM5P-CdKAHoek-D4FLVcsc.js} +1 -1
  79. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-4mdSyYTy.js +1 -0
  80. package/dist/assets/{wardleyDiagram-EHGQE667-BF6c4_CW-CB01oT0C.js → wardleyDiagram-EHGQE667-BF6c4_CW-CRhp69sj.js} +1 -1
  81. package/dist/assets/{xychartDiagram-FW5EYKEG-CGiKngj7-D1vr6bQ-.js → xychartDiagram-FW5EYKEG-CGiKngj7-CqYw8fCB.js} +1 -1
  82. package/dist/index.html +2 -2
  83. package/package.json +4 -4
  84. package/server/agents/claude-args.ts +10 -1
  85. package/server/agents/codex-rate-limits.spec.ts +74 -0
  86. package/server/agents/codex-rate-limits.ts +94 -0
  87. package/server/agents/codex-rollout.ts +79 -0
  88. package/server/agents/pr-clone-prompt.ts +30 -0
  89. package/server/agents/rate-limit-persist.ts +59 -0
  90. package/server/agents/rate-limit-probe.spec.ts +66 -0
  91. package/server/agents/rate-limit-probe.ts +114 -0
  92. package/server/agents/rate-limit-routes.ts +66 -0
  93. package/server/agents/rate-limit-store.spec.ts +91 -0
  94. package/server/agents/rate-limit-store.ts +73 -0
  95. package/server/agents/statusline.spec.ts +66 -0
  96. package/server/agents/statusline.ts +48 -0
  97. package/server/cli-init.ts +2 -2
  98. package/server/config/app-config.ts +48 -4
  99. package/server/config/config-routes.ts +4 -1
  100. package/server/config/config-schema.ts +1 -1
  101. package/server/config/header-context.ts +9 -8
  102. package/server/git/gitRemote.ts +14 -31
  103. package/server/git/pr-footer.ts +17 -4
  104. package/server/git/prPhase.ts +40 -12
  105. package/server/git/remote-ref.ts +50 -0
  106. package/server/git/repo-root-sync.ts +55 -0
  107. package/server/git/worktree-pr.ts +1 -0
  108. package/server/index.ts +32 -0
  109. package/server/infra/tmux.ts +40 -0
  110. package/server/routes/app-routes.ts +5 -0
  111. package/server/routes/dir-routes.ts +5 -1
  112. package/server/session/spawn-claude.ts +39 -16
  113. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-CfmmIOkB.js +0 -1
  114. package/dist/assets/channel-7wUqSdoX-Bc9EmVWS.js +0 -1
  115. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-Dk_90vZD.js +0 -1
  116. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-Dk_90vZD.js +0 -1
  117. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-B1MlaJhj.js +0 -1
  118. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-C0eB3veQ.js +0 -1
  119. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-BAfA7eXR.js +0 -1
  120. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-CotjI6SA.js +0 -1
  121. package/dist/assets/index-B7LbAi10.css +0 -1
  122. package/dist/assets/info-DKCQHKI2-Dplx5kMp-DKHWzOor.js +0 -1
  123. package/dist/assets/packet-7NZHBO7P-CQI3flND-DyAt_QAV.js +0 -1
  124. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-nlXXCSHg.js +0 -1
  125. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-Dnn5SKpT.js +0 -1
  126. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-DjF0R5Zr.js +0 -1
  127. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-GH7jGfhq.js +0 -1
  128. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-CH8TSRFp.js +0 -1
  129. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-QfsXYBPl.js +0 -1
  130. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-D7ipapeO.js +0 -1
  131. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-D1yGYNNh.js +0 -8
  132. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-nxCnsDvO.js +0 -1
  133. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-7UfnOcZW.js +0 -1
  134. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-DHZIQf1x.js +0 -1
package/README.md CHANGED
@@ -21,6 +21,13 @@ ping to your phone when a task finishes. One `npx` command, no Electron, no conf
21
21
  npx mulmoterminal # starts on http://localhost:34567 and opens your browser
22
22
  ```
23
23
 
24
+ > **Something looks wrong?** Type `/mulmoterminal-bug-report` in any MulmoTerminal session. The
25
+ > bundled skill hears the symptom out, checks your **real** config, schema and version to see
26
+ > whether the behaviour is configuration or by design, searches the existing issues — and only
27
+ > helps you file one if none of that explains it, with the environment collected and secrets
28
+ > masked. Getting you unstuck is the goal; an issue is what is left when the first three steps
29
+ > fail.
30
+
24
31
  ![MulmoTerminal's grid view — four live Claude sessions running side by side, each in its own color-coded project](https://raw.githubusercontent.com/receptron/mulmoterminal/main/docs/guide/images/grid-2x2-live.png)
25
32
 
26
33
  *The grid is a **cockpit for parallel agents** — here, four live Claude sessions, each in its own color-coded project. Every cell's header carries what you need to triage at a glance: **model · context %**, **token counts** (`⇡in ⇣out`), the **git branch / changes** chip, and an AI summary of what the agent is doing. A cell's **border color signals state** — working / done (blue), needs-you (amber — e.g. waiting on a permission), idle — with an attention chime so a stuck cell off-screen still pulls you back. Supervise many; only step in where you're called.*
@@ -455,7 +462,7 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
455
462
 
456
463
  ![The Settings modal — theme, notification sound, PR repos, launch commands, and MCP servers](https://raw.githubusercontent.com/receptron/mulmoterminal/main/docs/guide/images/settings.png)
457
464
 
458
- *Open it from the ⚙ button in the toolbar. Pick a **theme**, set the **terminal font size**, set a custom **attention sound**, list the repos the cross-repo **PRs & Issues** view should aggregate, add **launch commands** for grid cells, and register your own **MCP servers** — no need to hand-edit the config file. Note that **theme and font size are stored per browser** (they're display preferences, so a phone and a desktop keep their own); the rest live in `~/.mulmoterminal/config.json` and are shared by every client.*
465
+ *Open it from the ⚙ button in the toolbar. Pick a **theme**, set the **terminal font size** and **scroll speed**, set a custom **attention sound**, list the repos the cross-repo **PRs & Issues** view should aggregate, add **launch commands** for grid cells, and register your own **MCP servers** — no need to hand-edit the config file. Note that **theme, font size and scroll speed are stored per browser** (they're display preferences, so a phone and a desktop keep their own); the rest live in `~/.mulmoterminal/config.json` and are shared by every client.*
459
466
 
460
467
  | Field | Meaning |
461
468
  | ------------ | ------- |
@@ -468,16 +475,23 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
468
475
  | `quickCommands` | `{ label, text, agents? }` phrases the **phone** offers as chips on a session's terminal view. Tapping one puts `text` in the input box; it is not sent until you press send. `agents` (`"claude"` / `"codex"` / `"shell"`) scopes a chip to session kinds — omit it to offer the chip everywhere. Empty by default. |
469
476
  | `userMcpServers` | `{ id, url }` HTTP MCP servers merged into the **single-view** Claude session's `--mcp-config` (a `localhost` URL is reached over `host.docker.internal` in the Docker sandbox). Takes effect on the next session. |
470
477
  | `buttons` | Header action buttons — see [Header buttons](#header-buttons). Omit to keep the defaults; set to replace them. |
471
- | `chips` | Header info chips. Omit to keep the default set; `[]` hides all built-ins. |
478
+ | `chips` | Header info chips (`dir` / `git` / `work` / `diff` / `ctx` / `usage` / `status` / `tools`, or custom text). Omit to keep the default set; `[]` hides all built-ins. `work` shows which PR / issue the cell is on (`#977 → #966`) and clears itself when the PR merges — see the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#work-chip). |
472
479
  | `pushEnabled` | `true` to send a **Web Push** to your registered devices. Off by default; only sends while the **RemoteHost** channel is connected (see below). The master switch — `pushKinds` picks which moments. |
473
480
  | `pushKinds` | Which moments push: `"finished"` (a turn ended, ✅) and/or `"waiting"` (the agent stopped to ask — a permission prompt or a question, ❓, **once per prompt**). Omit to keep both; `[]` for none. A kind added in a later version stays off until you tick it. |
474
481
  | `worklogEnabled` | `true` to run the built-in **dev worklog** batch (see below). Off by default (each run spawns an LLM session, so it costs tokens). |
475
482
  | `worklogIntervalHours` | Worklog cadence in hours (default `6`, clamped to `1`–`168`). |
476
483
  | `terminalSubmit` | Which bytes Claude reads as **submit** vs **newline**: `"cr"` (default — Enter submits, Shift+Enter makes a newline) or `"esc-cr"` (for a Claude Code rebound the other way). Applies to the keyboard **and** the phone remote-view submit, for **Claude sessions only** (shell/codex keep plain Enter). See the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#terminal-submit). |
477
484
  | `copyOnSelect` | `true` puts a **mouse selection on the clipboard the moment it settles**, with no key pressed (the PuTTY / iTerm2 behaviour). **Off by default** — it changes the clipboard when you may only have meant to highlight something. No Settings UI: edit the file and reload the tab. Composes with the `copy` keymap action rather than replacing it. Over plain `http://` the browser gives a page no clipboard access, so a fallback asks xterm to copy instead; see the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#copy-on-select). |
478
- | `prWorkdirFooter` | Ends the body of a PR **⧉ Open PR** creates with `work in <clone>` — the directory name of the clone the work happened in, so a PR says which of several side-by-side checkouts produced it. **On by default**; set `false` to opt out — read from the file per PR, so no restart is needed (there is no Settings control for it). Only applied to PRs this app creates (pressing the button again on an existing PR never re-appends). |
485
+ | `prWorkdirFooter` | Ends a PR body with `work in <clone>` — the directory name of the clone the work happened in, so a PR says which of several side-by-side checkouts produced it. Applies to **both** paths that open PRs here: **⧉ Open PR** appends it to the PR it creates, and every Claude session is told to end the bodies it writes with the same line (the name is resolved by the server, so a session inside a managed worktree still names the main checkout). **On by default**; set `false` to opt out — read per PR and per session spawn, so no restart is needed (there is no Settings control for it). Appending is idempotent: an existing PR never gets a second copy. |
479
486
  | `fontFamily` | The **terminal font** every session renders in — a CSS font-family stack, e.g. `"'Cica', 'MS Gothic', monospace"`. No Settings UI: edit the file, then **restart** (this config is read once at startup). Unset uses the built-in stack (JetBrains Mono / Fira Code / Menlo / Consolas, then CJK faces for Japanese, Korean and Chinese). Unlike the per-browser font **size**, this is one value for the whole host — it names fonts, and which fonts exist is a property of the machine. A directory can override it. See the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#font-family). |
480
487
 
488
+ Every MulmoTerminal on the machine shares this one file, so an older build could save over a key a
489
+ newer one wrote. It doesn't: a **top-level key this version doesn't recognise is written back
490
+ untouched**, which is what makes running two versions side by side — or downgrading for a while —
491
+ safe. A mistyped key survives on the same rule, which is deliberate: a line you can still see is
492
+ easier to debug than one that silently vanished. See the
493
+ [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#unknown-keys).
494
+
481
495
  #### Header buttons
482
496
 
483
497
  Each terminal header shows configurable **action buttons**. Omitting `buttons` (globally or per-dir)
@@ -958,6 +972,10 @@ Favorited collections get their own toolbar buttons.
958
972
  **Option** is treated as Meta so Claude's Alt-key bindings work. If your Claude Code is
959
973
  rebound so Enter and Shift+Enter behave backwards, flip them with
960
974
  [`terminalSubmit`](https://receptron.github.io/mulmoterminal/guide/en/config.html#terminal-submit).
975
+ - **Scroll speed** — one wheel notch or trackpad swipe moves the terminal the same distance
976
+ whether you're reading a shell's scrollback or a full-screen app like Claude Code. If a
977
+ two-finger scroll on a Mac trackpad flies past what you were reading, turn **terminal scroll
978
+ speed** down in Settings (0.25×–3×, per browser — it's a property of the pointing device).
961
979
  - **No accidental page zoom** — `Ctrl`+wheel and a trackpad pinch would rescale the whole
962
980
  page and drag the layout and the terminal's fit along with it, so both are ignored.
963
981
  Keyboard zoom (`Cmd`/`Ctrl` `+` / `-`) still works when you mean it, and a phone's finger
@@ -0,0 +1,65 @@
1
+ // What a cell is working on, as it travels between the two sides: the branch's PR, its phase in
2
+ // the review loop, and the issue that PR closes. Shared because BOTH sides decide from it — the
3
+ // server derives it from `gh`, the client decides what the header chip shows — and it used to be
4
+ // two declarations with a "keep them in sync" comment on one of them (src/components/rosterPhase.ts).
5
+
6
+ // Ordered roughly along the lifecycle so the client can pick a colour/label per phase.
7
+ // `none` = no PR for this branch yet (still local work); `ready` = open, CI green, no
8
+ // changes requested — i.e. waiting to merge.
9
+ export type PrPhase = "none" | "draft" | "ci-failing" | "changes-requested" | "ci-running" | "ready" | "merged" | "closed";
10
+
11
+ export const PR_PHASES: readonly PrPhase[] = ["none", "draft", "ci-failing", "changes-requested", "ci-running", "ready", "merged", "closed"];
12
+
13
+ export const isPrPhase = (v: unknown): v is PrPhase => typeof v === "string" && (PR_PHASES as readonly string[]).includes(v);
14
+
15
+ // The /api/pr-phase response. `phase` and `prUrl` predate the rest and the roster reads them;
16
+ // the numbers are what lets a cell say WHICH work it is on rather than just how far along it is.
17
+ export interface WorkItem {
18
+ phase: PrPhase;
19
+ pr: number | null;
20
+ prUrl: string | null;
21
+ issue: number | null;
22
+ issueUrl: string | null;
23
+ }
24
+
25
+ export const EMPTY_WORK_ITEM: Readonly<WorkItem> = { phase: "none", pr: null, prUrl: null, issue: null, issueUrl: null };
26
+
27
+ // GitHub's closing keywords, as GitHub itself matches them: any of these, then optional
28
+ // whitespace/colon, then #N. Anything else in the body is a mention, not a link — a PR that
29
+ // says "related to #12" is not working on #12.
30
+ // One character class for the separator, not `\s*:?\s+`: two adjacent whitespace quantifiers
31
+ // backtrack super-linearly on a long body, and a PR description is user input.
32
+ //
33
+ // `[1-9]\d*` rather than `\d+`: there is no issue #0, and `#0123` is not issue 123 — both are
34
+ // typos, and a typo that renders as a link to somebody else's issue is worse than no chip.
35
+ const CLOSING_KEYWORD = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)[:\s]+#([1-9]\d*)/i;
36
+
37
+ // Digits from a body or a branch name are unbounded, and `Number("9".repeat(20))` is 1e20 — which
38
+ // would render in the chip as "#1e+20" and link nowhere. Anything past the safe-integer range is
39
+ // not an issue number (found by Codex review).
40
+ function toIssueNumber(digits: string): number | null {
41
+ const n = Number(digits);
42
+ return Number.isSafeInteger(n) && n > 0 ? n : null;
43
+ }
44
+
45
+ // The issue a PR body says it closes, or null. Deliberately blind to the full-URL form
46
+ // (`Fixes https://github.com/o/r/issues/12`): that can name ANOTHER repository, and a number
47
+ // shown next to this cell's PR has to belong to the same repo to be clickable and true.
48
+ export function issueRefFromPrBody(body: string | null | undefined): number | null {
49
+ const found = typeof body === "string" ? CLOSING_KEYWORD.exec(body) : null;
50
+ return found ? toIssueNumber(found[1]) : null;
51
+ }
52
+
53
+ // A branch named after its issue the way this repo names them: `fix/966-preserve-unknown-keys`.
54
+ // Requires a type prefix and a hyphen after the digits, so `chore/dep-updates-20260728` is not
55
+ // read as issue #20260728.
56
+ const BRANCH_ISSUE = /^[a-z][a-z-]*\/([1-9]\d*)-/;
57
+
58
+ // A CANDIDATE, never an answer: `release/2026-07-28-hotfix` yields 2026 here, and no pattern can
59
+ // tell that apart from a branch for issue #2026 — the year and the number have the same shape.
60
+ // The caller must confirm the issue exists before showing it, or a cell claims to be working on
61
+ // somebody else's issue. Named for the doubt so a call site can't forget it.
62
+ export function issueCandidateFromBranch(branch: string | null | undefined): number | null {
63
+ const found = typeof branch === "string" ? BRANCH_ISSUE.exec(branch) : null;
64
+ return found ? toIssueNumber(found[1]) : null;
65
+ }
@@ -0,0 +1,37 @@
1
+ // The rate-limit wire shape, and the one parser for it (#387).
2
+ //
3
+ // Here rather than on either side because BOTH decide from it: the server reads it back out of its
4
+ // own cache file, and the browser reads it off `/api/rate-limits`. Written twice it was written
5
+ // twice differently — jscpd caught the copies before they had a chance to drift.
6
+ //
7
+ // Everything is optional on purpose. A window can be absent because the agent is not installed,
8
+ // because the plan is API-key billed, because no session has answered yet, or because upstream
9
+ // dropped the field (anthropics/claude-code#40094). None of those are zero, and a gauge reading
10
+ // 0% when the truth is 83% is the worst thing this data can do.
11
+
12
+ export interface RateLimitWindow {
13
+ usedPercentage: number; // 0-100, fractional
14
+ resetsAt_sec: number | null; // Unix epoch seconds
15
+ }
16
+
17
+ export interface RateLimits {
18
+ fiveHour: RateLimitWindow | null;
19
+ sevenDay: RateLimitWindow | null;
20
+ }
21
+
22
+ const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null;
23
+ const finiteNumber = (value: unknown): number | null => (typeof value === "number" && Number.isFinite(value) ? value : null);
24
+
25
+ export function parseRateLimitWindow(raw: unknown): RateLimitWindow | null {
26
+ if (!isRecord(raw)) return null;
27
+ const used = finiteNumber(raw.usedPercentage);
28
+ return used === null ? null : { usedPercentage: used, resetsAt_sec: finiteNumber(raw.resetsAt_sec) };
29
+ }
30
+
31
+ /** Null when neither window survives, so a caller can tell "nothing to show" from "0% used". */
32
+ export function parseRateLimits(raw: unknown): RateLimits | null {
33
+ if (!isRecord(raw)) return null;
34
+ const fiveHour = parseRateLimitWindow(raw.fiveHour);
35
+ const sevenDay = parseRateLimitWindow(raw.sevenDay);
36
+ return fiveHour || sevenDay ? { fiveHour, sevenDay } : null;
37
+ }
@@ -0,0 +1,31 @@
1
+ // How far one wheel/trackpad gesture scrolls the terminal, as a multiplier on the xterm
2
+ // default (1 = xterm's own speed). Shared because both sides decide from the same numbers:
3
+ // the Settings stepper bounds its own buttons, and the client re-validates what it reads back
4
+ // out of localStorage.
5
+ //
6
+ // It drives BOTH scrolling paths, which are different code: xterm's `scrollSensitivity` for
7
+ // the normal buffer's scrollback, and the wheel-report accumulator in ./mouseReports for a
8
+ // full-screen TUI in the alternate buffer (Claude Code). One setting, because to the person
9
+ // scrolling they are the same gesture.
10
+ export const TERMINAL_SCROLL_SPEED_DEFAULT = 1;
11
+ // A macOS trackpad emits a wheel event every few pixels, so the useful range sits below 1 —
12
+ // 0.25 is "a swipe moves a paragraph", still fast enough to cross a long transcript. Above 3
13
+ // a single flick outruns the renderer and lands nowhere readable, which is the complaint this
14
+ // setting exists to answer.
15
+ export const TERMINAL_SCROLL_SPEED_MIN = 0.25;
16
+ export const TERMINAL_SCROLL_SPEED_MAX = 3;
17
+ export const TERMINAL_SCROLL_SPEED_STEP = 0.25;
18
+
19
+ // Clamp rather than reject an out-of-range number, for the same reason the font size does:
20
+ // honouring the direction the user asked for reads as working, where snapping back to the
21
+ // default reads as ignored. Non-numeric input means "nothing set here" and stays null so the
22
+ // caller can fall back. Values are snapped to the step so the label stays readable (0.75, not
23
+ // 0.7499999) and so the stepper can't drift off its own grid.
24
+ export function normalizeScrollSpeed(input: unknown): number | null {
25
+ if (typeof input !== "number" || !Number.isFinite(input)) return null;
26
+ const clamped = Math.min(TERMINAL_SCROLL_SPEED_MAX, Math.max(TERMINAL_SCROLL_SPEED_MIN, input));
27
+ const snapped = Math.round(clamped / TERMINAL_SCROLL_SPEED_STEP) * TERMINAL_SCROLL_SPEED_STEP;
28
+ // The multiply above reintroduces binary-float noise (0.30000000000000004); two decimals is
29
+ // more than the step needs and leaves the value safe to compare and to render.
30
+ return Math.round(snapped * 100) / 100;
31
+ }
@@ -1 +1 @@
1
- import{n as e}from"./chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js";import{f as t}from"./src-Brzfja-q-ch1VcTiG.js";import{_ as n,g as r}from"./mermaid-parser.core-DxEa8E3F-BzSqp82H.js";import"./chunk-WYO6CB5R-BlzOfotS-CTDodsnw.js";import"./chunk-VAUOI2AC-CS9QJ4yz-DFjCsQc6.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-CYfpp2V7.js";import{t as s}from"./chunk-JWPE2WC7-BQ3zXr2k-DSd3Ct95.js";var c=r().RailroadAbnf.parser.LangiumParser,l=e(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=e(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=e(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=e(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=e(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=e(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=e(e=>{s(e,a),e.title&&a.setTitle(e.title),e.rules.map(e=>a.addRule(m(e)))},`populateDb`),g={parser:{parse:e(e=>{a.clear(),t.debug(`[ABNF Parser] Starting Langium parse`);let r=c.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new n(r);let i=r.value;t.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),t.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:a}},db:a,renderer:o,styles:i};export{g as diagram};
1
+ import{n as e}from"./chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js";import{f as t}from"./src-Brzfja-q-ch1VcTiG.js";import{_ as n,g as r}from"./mermaid-parser.core-DxEa8E3F-BAMiAMZz.js";import"./chunk-WYO6CB5R-BlzOfotS-CoeWhGPT.js";import"./chunk-VAUOI2AC-CS9QJ4yz-DYmUgyge.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-B6_lajzj.js";import{t as s}from"./chunk-JWPE2WC7-BQ3zXr2k-DSd3Ct95.js";var c=r().RailroadAbnf.parser.LangiumParser,l=e(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=e(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=e(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=e(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=e(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=e(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=e(e=>{s(e,a),e.title&&a.setTitle(e.title),e.rules.map(e=>a.addRule(m(e)))},`populateDb`),g={parser:{parse:e(e=>{a.clear(),t.debug(`[ABNF Parser] Starting Langium parse`);let r=c.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new n(r);let i=r.value;t.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),t.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:a}},db:a,renderer:o,styles:i};export{g as diagram};
@@ -0,0 +1 @@
1
+ import{f as e}from"./mermaid-parser.core-DxEa8E3F-BAMiAMZz.js";export{e as createArchitectureServices};