@dylanpiercey/work 0.0.5 → 0.0.7

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 (157) hide show
  1. package/bin/work.mjs +88 -18
  2. package/dist/index.mjs +1218 -820
  3. package/dist/public/assets/{_DwWKKIbx.js → _B-_0cKbG.js} +2 -2
  4. package/dist/public/assets/{_DwWKKIbx.js.map → _B-_0cKbG.js.map} +1 -1
  5. package/dist/public/assets/{_BbQjjMLb2.js → _B1F2GQHq2.js} +2 -2
  6. package/dist/public/assets/{_BbQjjMLb2.js.map → _B1F2GQHq2.js.map} +1 -1
  7. package/dist/public/assets/{_DThwiCYf.js → _B49hrT7F.js} +2 -2
  8. package/dist/public/assets/{_DThwiCYf.js.map → _B49hrT7F.js.map} +1 -1
  9. package/dist/public/assets/{_L0QEXXRu.js → _B4R8SNvy.js} +2 -2
  10. package/dist/public/assets/{_L0QEXXRu.js.map → _B4R8SNvy.js.map} +1 -1
  11. package/dist/public/assets/{_g24DOTM6.js → _B7K5PePe.js} +2 -2
  12. package/dist/public/assets/{_g24DOTM6.js.map → _B7K5PePe.js.map} +1 -1
  13. package/dist/public/assets/{_DsH6sF8_.js → _B8mEgNvE.js} +2 -2
  14. package/dist/public/assets/{_DsH6sF8_.js.map → _B8mEgNvE.js.map} +1 -1
  15. package/dist/public/assets/{_Dp2I7SvR2.js → _BBbcogWL2.js} +2 -2
  16. package/dist/public/assets/{_Dp2I7SvR2.js.map → _BBbcogWL2.js.map} +1 -1
  17. package/dist/public/assets/{_BQnjw6qe.js → _BDxD__Ca.js} +2 -2
  18. package/dist/public/assets/{_BQnjw6qe.js.map → _BDxD__Ca.js.map} +1 -1
  19. package/dist/public/assets/{_BxHj_AmV.js → _BE5Ltp0D.js} +2 -2
  20. package/dist/public/assets/{_BxHj_AmV.js.map → _BE5Ltp0D.js.map} +1 -1
  21. package/dist/public/assets/{_lZmV1DmL.js → _BE8gA6TO.js} +2 -2
  22. package/dist/public/assets/{_lZmV1DmL.js.map → _BE8gA6TO.js.map} +1 -1
  23. package/dist/public/assets/{_f31sxhc9.js → _BEY9qyEj.js} +2 -2
  24. package/dist/public/assets/{_f31sxhc9.js.map → _BEY9qyEj.js.map} +1 -1
  25. package/dist/public/assets/{_3LSnPXE-2.js → _BFPTVBh82.js} +2 -2
  26. package/dist/public/assets/{_3LSnPXE-2.js.map → _BFPTVBh82.js.map} +1 -1
  27. package/dist/public/assets/{_CPt59YK-2.js → _BKNwgsJI2.js} +2 -2
  28. package/dist/public/assets/{_CPt59YK-2.js.map → _BKNwgsJI2.js.map} +1 -1
  29. package/dist/public/assets/{_CGEJqu1l.js → _BLuo0bJF.js} +2 -2
  30. package/dist/public/assets/{_CGEJqu1l.js.map → _BLuo0bJF.js.map} +1 -1
  31. package/dist/public/assets/{_De1qIUYL2.js → _BMbg6Q_32.js} +2 -2
  32. package/dist/public/assets/{_De1qIUYL2.js.map → _BMbg6Q_32.js.map} +1 -1
  33. package/dist/public/assets/{_DFdBRroy.js → _BQqn5S5j.js} +2 -2
  34. package/dist/public/assets/{_DFdBRroy.js.map → _BQqn5S5j.js.map} +1 -1
  35. package/dist/public/assets/{_C_AWyxBB.js → _BTJ_-CIz.js} +2 -2
  36. package/dist/public/assets/{_C_AWyxBB.js.map → _BTJ_-CIz.js.map} +1 -1
  37. package/dist/public/assets/{_DWh6hcFb.js → _BTnhYWUT.js} +2 -2
  38. package/dist/public/assets/{_DWh6hcFb.js.map → _BTnhYWUT.js.map} +1 -1
  39. package/dist/public/assets/{_C55X2_br.js → _B_U5SPso.js} +2 -2
  40. package/dist/public/assets/{_C55X2_br.js.map → _B_U5SPso.js.map} +1 -1
  41. package/dist/public/assets/{_BmLItN7n.js → _BfxQEfor.js} +2 -2
  42. package/dist/public/assets/{_BmLItN7n.js.map → _BfxQEfor.js.map} +1 -1
  43. package/dist/public/assets/{_Cxre_eVs2.js → _Bhn-MKXG2.js} +2 -2
  44. package/dist/public/assets/{_Cxre_eVs2.js.map → _Bhn-MKXG2.js.map} +1 -1
  45. package/dist/public/assets/{_G2euCJ5s2.js → _BkEOzqHA2.js} +2 -2
  46. package/dist/public/assets/{_G2euCJ5s2.js.map → _BkEOzqHA2.js.map} +1 -1
  47. package/dist/public/assets/{_BtEwCzLt.js → _BqeWVi6c.js} +2 -2
  48. package/dist/public/assets/{_BtEwCzLt.js.map → _BqeWVi6c.js.map} +1 -1
  49. package/dist/public/assets/{_D25eFvus.js → _Bx6coq7v.js} +2 -2
  50. package/dist/public/assets/{_D25eFvus.js.map → _Bx6coq7v.js.map} +1 -1
  51. package/dist/public/assets/{_Dj5K_vdY.js → _BxWguH11.js} +2 -2
  52. package/dist/public/assets/{_Dj5K_vdY.js.map → _BxWguH11.js.map} +1 -1
  53. package/dist/public/assets/{_BO52x4nD2.js → _C2TX6Jrr2.js} +2 -2
  54. package/dist/public/assets/{_BO52x4nD2.js.map → _C2TX6Jrr2.js.map} +1 -1
  55. package/dist/public/assets/{_UUjPxQzk.js → _C8Lw1Tmb.js} +2 -2
  56. package/dist/public/assets/{_UUjPxQzk.js.map → _C8Lw1Tmb.js.map} +1 -1
  57. package/dist/public/assets/{_C_RO1bIU.js → _CDtlFxZX.js} +2 -2
  58. package/dist/public/assets/{_C_RO1bIU.js.map → _CDtlFxZX.js.map} +1 -1
  59. package/dist/public/assets/{_AAkZyuUo2.js → _CF3V7ZyO2.js} +2 -2
  60. package/dist/public/assets/{_AAkZyuUo2.js.map → _CF3V7ZyO2.js.map} +1 -1
  61. package/dist/public/assets/{_BMIj0szP.js → _CIUf_Jmq.js} +2 -2
  62. package/dist/public/assets/{_BMIj0szP.js.map → _CIUf_Jmq.js.map} +1 -1
  63. package/dist/public/assets/{_CZjGo4EJ2.js → _CJlF2h3E2.js} +2 -2
  64. package/dist/public/assets/{_CZjGo4EJ2.js.map → _CJlF2h3E2.js.map} +1 -1
  65. package/dist/public/assets/{_DjF3hk4N.js → _CNDjHC1Q.js} +2 -2
  66. package/dist/public/assets/{_DjF3hk4N.js.map → _CNDjHC1Q.js.map} +1 -1
  67. package/dist/public/assets/{_Dg0ufwJw.js → _CTkR0S8t.js} +2 -2
  68. package/dist/public/assets/{_Dg0ufwJw.js.map → _CTkR0S8t.js.map} +1 -1
  69. package/dist/public/assets/{_DWBu1oTd.js → _C_K-IZx7.js} +2 -2
  70. package/dist/public/assets/{_DWBu1oTd.js.map → _C_K-IZx7.js.map} +1 -1
  71. package/dist/public/assets/{_B7ycuWnF.js → _CawcEo7A.js} +2 -2
  72. package/dist/public/assets/{_B7ycuWnF.js.map → _CawcEo7A.js.map} +1 -1
  73. package/dist/public/assets/{_BF4oeZAs2.js → _Ch0uVmAz2.js} +2 -2
  74. package/dist/public/assets/{_BF4oeZAs2.js.map → _Ch0uVmAz2.js.map} +1 -1
  75. package/dist/public/assets/{_BksA_4pq.js → _Ck30GMoL.js} +2 -2
  76. package/dist/public/assets/{_BksA_4pq.js.map → _Ck30GMoL.js.map} +1 -1
  77. package/dist/public/assets/{_LMf8ZNAk.js → _Cpqbvj5d.js} +2 -2
  78. package/dist/public/assets/{_LMf8ZNAk.js.map → _Cpqbvj5d.js.map} +1 -1
  79. package/dist/public/assets/{_MSZeR7tc.js → _CqNVh0h5.js} +2 -2
  80. package/dist/public/assets/{_MSZeR7tc.js.map → _CqNVh0h5.js.map} +1 -1
  81. package/dist/public/assets/{_CZJ6f1W42.js → _Cx_br0wh2.js} +2 -2
  82. package/dist/public/assets/{_CZJ6f1W42.js.map → _Cx_br0wh2.js.map} +1 -1
  83. package/dist/public/assets/{_dbpNK3ti.js → _CzBadWXq.js} +2 -2
  84. package/dist/public/assets/{_dbpNK3ti.js.map → _CzBadWXq.js.map} +1 -1
  85. package/dist/public/assets/{_BKWC43v9.js → _D0Dk2eMz.js} +2 -2
  86. package/dist/public/assets/{_BKWC43v9.js.map → _D0Dk2eMz.js.map} +1 -1
  87. package/dist/public/assets/{_BWobArGx2.js → _D0PyqJyr2.js} +2 -2
  88. package/dist/public/assets/{_BWobArGx2.js.map → _D0PyqJyr2.js.map} +1 -1
  89. package/dist/public/assets/{_Cp2dVuf7.js → _D409hg3n.js} +2 -2
  90. package/dist/public/assets/{_Cp2dVuf7.js.map → _D409hg3n.js.map} +1 -1
  91. package/dist/public/assets/{_DgS0ehtc.js → _D4uduNrb.js} +2 -2
  92. package/dist/public/assets/{_DgS0ehtc.js.map → _D4uduNrb.js.map} +1 -1
  93. package/dist/public/assets/{_DwvTHP0H.js → _DBj-L0Ww.js} +2 -2
  94. package/dist/public/assets/{_DwvTHP0H.js.map → _DBj-L0Ww.js.map} +1 -1
  95. package/dist/public/assets/{_DkAxFIdj2.js → _DC9uMckf2.js} +2 -2
  96. package/dist/public/assets/{_DkAxFIdj2.js.map → _DC9uMckf2.js.map} +1 -1
  97. package/dist/public/assets/{_m6OqtXq5.js → _DHsfzMP_.js} +2 -2
  98. package/dist/public/assets/{_m6OqtXq5.js.map → _DHsfzMP_.js.map} +1 -1
  99. package/dist/public/assets/{_DR7HMLdt.js → _DPDjFNn3.js} +2 -2
  100. package/dist/public/assets/{_DR7HMLdt.js.map → _DPDjFNn3.js.map} +1 -1
  101. package/dist/public/assets/{_CFYld88W.js → _DS1r4Da7.js} +2 -2
  102. package/dist/public/assets/{_CFYld88W.js.map → _DS1r4Da7.js.map} +1 -1
  103. package/dist/public/assets/{_XySmXsU3.js → _DZp-Xvf0.js} +2 -2
  104. package/dist/public/assets/{_XySmXsU3.js.map → _DZp-Xvf0.js.map} +1 -1
  105. package/dist/public/assets/{_C7WwEVpI.js → _DbP8pES9.js} +2 -2
  106. package/dist/public/assets/{_C7WwEVpI.js.map → _DbP8pES9.js.map} +1 -1
  107. package/dist/public/assets/{_B-CqRNtV.js → _DhqQ2yva.js} +2 -2
  108. package/dist/public/assets/{_B-CqRNtV.js.map → _DhqQ2yva.js.map} +1 -1
  109. package/dist/public/assets/{_qGiUFV1r.js → _Din9HEMs.js} +2 -2
  110. package/dist/public/assets/{_qGiUFV1r.js.map → _Din9HEMs.js.map} +1 -1
  111. package/dist/public/assets/{_MSjbYDCb.js → _Dk-i8IAv.js} +2 -2
  112. package/dist/public/assets/{_MSjbYDCb.js.map → _Dk-i8IAv.js.map} +1 -1
  113. package/dist/public/assets/{_CMH9CsQN.js → _DvdWf5o-.js} +2 -2
  114. package/dist/public/assets/{_CMH9CsQN.js.map → _DvdWf5o-.js.map} +1 -1
  115. package/dist/public/assets/{_DaxEJQAG2.js → _DwmbSeOR2.js} +2 -2
  116. package/dist/public/assets/{_DaxEJQAG2.js.map → _DwmbSeOR2.js.map} +1 -1
  117. package/dist/public/assets/{_DdXxywnu.js → _Dz57V0my.js} +2 -2
  118. package/dist/public/assets/{_DdXxywnu.js.map → _Dz57V0my.js.map} +1 -1
  119. package/dist/public/assets/{_DVqaReFM2.js → _JDPB-L9T2.js} +2 -2
  120. package/dist/public/assets/{_DVqaReFM2.js.map → _JDPB-L9T2.js.map} +1 -1
  121. package/dist/public/assets/{_BacHRrKm2.js → _T3Aahgg12.js} +2 -2
  122. package/dist/public/assets/{_BacHRrKm2.js.map → _T3Aahgg12.js.map} +1 -1
  123. package/dist/public/assets/{_sQkp9ieF.js → _Tbp9N0Pq.js} +2 -2
  124. package/dist/public/assets/{_sQkp9ieF.js.map → _Tbp9N0Pq.js.map} +1 -1
  125. package/dist/public/assets/{_CSxCkWdX2.js → _Tqwd1-8Z2.js} +2 -2
  126. package/dist/public/assets/{_CSxCkWdX2.js.map → _Tqwd1-8Z2.js.map} +1 -1
  127. package/dist/public/assets/{_Dc3WoyCf.js → _UPkRAhR-.js} +2 -2
  128. package/dist/public/assets/{_Dc3WoyCf.js.map → _UPkRAhR-.js.map} +1 -1
  129. package/dist/public/assets/{_D4G4BzdU2.js → _WO2um4ce2.js} +2 -2
  130. package/dist/public/assets/{_D4G4BzdU2.js.map → _WO2um4ce2.js.map} +1 -1
  131. package/dist/public/assets/{_DwB4tost2.js → _WXstblxD2.js} +2 -2
  132. package/dist/public/assets/{_DwB4tost2.js.map → _WXstblxD2.js.map} +1 -1
  133. package/dist/public/assets/{_BPAMsQ-Y2.js → _Wq5Lwig62.js} +2 -2
  134. package/dist/public/assets/{_BPAMsQ-Y2.js.map → _Wq5Lwig62.js.map} +1 -1
  135. package/dist/public/assets/{_B7BQvLFA2.js → _YMDMk2XX2.js} +2 -2
  136. package/dist/public/assets/{_B7BQvLFA2.js.map → _YMDMk2XX2.js.map} +1 -1
  137. package/dist/public/assets/{_A4VtPfAb.js → _b9uzSPzx.js} +2 -2
  138. package/dist/public/assets/{_A4VtPfAb.js.map → _b9uzSPzx.js.map} +1 -1
  139. package/dist/public/assets/{_Bjvy806u2.js → _dfWRmwy82.js} +2 -2
  140. package/dist/public/assets/{_Bjvy806u2.js.map → _dfWRmwy82.js.map} +1 -1
  141. package/dist/public/assets/{_2JdtdC0N.js → _ehtlhcCR.js} +2 -2
  142. package/dist/public/assets/{_2JdtdC0N.js.map → _ehtlhcCR.js.map} +1 -1
  143. package/dist/public/assets/{_Fpix3N3i.js → _fZl_Qerv.js} +2 -2
  144. package/dist/public/assets/{_Fpix3N3i.js.map → _fZl_Qerv.js.map} +1 -1
  145. package/dist/public/assets/{_C6fTVhaS.js → _js4WyaBT.js} +2 -2
  146. package/dist/public/assets/{_C6fTVhaS.js.map → _js4WyaBT.js.map} +1 -1
  147. package/dist/public/assets/{_Ck_58QRD.js → _okHqOb61.js} +2 -2
  148. package/dist/public/assets/{_Ck_58QRD.js.map → _okHqOb61.js.map} +1 -1
  149. package/dist/public/assets/{_5XJqee1K2.js → _rMU5-5Oc2.js} +2 -2
  150. package/dist/public/assets/{_5XJqee1K2.js.map → _rMU5-5Oc2.js.map} +1 -1
  151. package/dist/public/assets/{index-BAGH9WA4.js → index-Bx9UtyrE.js} +63 -61
  152. package/dist/public/assets/index-Bx9UtyrE.js.map +1 -0
  153. package/dist/public/assets/{index-KIy0rCx1.css → index-C_YqjXeW.css} +1 -1
  154. package/install.sh +4 -1
  155. package/package.json +1 -1
  156. package/scripts/migrate.mjs +141 -0
  157. package/dist/public/assets/index-BAGH9WA4.js.map +0 -1
package/dist/index.mjs CHANGED
@@ -3481,7 +3481,7 @@ var palette_default = _template("h", (input) => {
3481
3481
  const $input_status__closures = /* @__PURE__ */ new Set();
3482
3482
  const $input_settings_agents__closures = /* @__PURE__ */ new Set();
3483
3483
  let query = "";
3484
- let hi = 0;
3484
+ let hiRaw = 0;
3485
3485
  let showArchived = false;
3486
3486
  const active = input.workspaces.filter((ws) => ws.status === "active");
3487
3487
  const archived = input.workspaces.filter((ws) => ws.status === "archived");
@@ -3517,6 +3517,7 @@ var palette_default = _template("h", (input) => {
3517
3517
  ...commands,
3518
3518
  { kind: "new" }
3519
3519
  ];
3520
+ const hi = Math.min(hiRaw, rows.length - 1);
3520
3521
  const pick = _resume((row) => {
3521
3522
  if (!row) return;
3522
3523
  if (row.kind === "new") input.onNew();
@@ -3543,7 +3544,7 @@ var palette_default = _template("h", (input) => {
3543
3544
  }, $scope0_id, "c", $sg__input_inline__OR__input_workspaces_length, $sg__input_inline__OR__input_workspaces_length, 1, 0, 1);
3544
3545
  _html(`<input${_attr_input_value($scope0_id, "d", query, _resume(function(v) {
3545
3546
  query = v;
3546
- hi = 0;
3547
+ hiRaw = 0;
3547
3548
  }, "h0", $scope0_id))} autofocus placeholder="Jump to workspace…" aria-label="Filter workspaces">${_el_resume($scope0_id, "d")}<ul class=rows>`);
3548
3549
  _for_of(rows, (row, i) => {
3549
3550
  const $scope2_id = _scope_id();
@@ -3566,17 +3567,17 @@ var palette_default = _template("h", (input) => {
3566
3567
  _html(`<span class=row-name><span${_attr_class(["dot", phaseOf$1(st)])}></span>${_el_resume($scope3_id, "a")}<span class=row-name-text>${_escape(row.ws.name)}${_el_resume($scope3_id, "b")}</span>`);
3567
3568
  _if(() => {
3568
3569
  if (row.ws.id === input.selectedId) {
3569
- const $scope15_id = _scope_id();
3570
+ const $scope14_id = _scope_id();
3570
3571
  _html("<span class=\"chip open\">open</span>");
3571
- writeScope($scope15_id, {});
3572
+ writeScope($scope14_id, {});
3572
3573
  return 0;
3573
3574
  }
3574
3575
  }, $scope3_id, "c", 1, 1, 1, 0, 1);
3575
3576
  _if(() => {
3576
3577
  if (row.archived) {
3577
- const $scope16_id = _scope_id();
3578
+ const $scope15_id = _scope_id();
3578
3579
  _html("<span class=chip>archived</span>");
3579
- writeScope($scope16_id, {});
3580
+ writeScope($scope15_id, {});
3580
3581
  return 0;
3581
3582
  }
3582
3583
  }, $scope3_id, "d", 1, 1, 1, 0, 1);
@@ -3589,44 +3590,35 @@ var palette_default = _template("h", (input) => {
3589
3590
  return 0;
3590
3591
  }
3591
3592
  }, $scope3_id, "e", 1, 1, 1, 0, 1);
3592
- _if(() => {
3593
- if (st?.changes) {
3594
- const $scope12_id = _scope_id();
3595
- _html(`<span class="chip stat changes"${_attr("title", `${st.changes} changed file${st.changes === 1 ? "" : "s"}`)}>±<!>${_escape(st.changes)}${_el_resume($scope12_id, "b")}</span>${_el_resume($scope12_id, "a")}`);
3596
- writeScope($scope12_id, {});
3597
- return 0;
3598
- }
3599
- }, $scope3_id, "f", 1, 1, 1, 0, 1);
3600
3593
  _if(() => {
3601
3594
  if (rowPrs.length) {
3602
- const $scope14_id = _scope_id();
3603
- _html(`<span class="chip stat"${_attr("title", `${rowPrs.length} open PR${rowPrs.length === 1 ? "" : "s"}`)}>${_escape(rowPrs.length)}${_el_resume($scope14_id, "b")} PR<!>${rowPrs.length === 1 ? "" : "s"}${_el_resume($scope14_id, "c")}</span>${_el_resume($scope14_id, "a")}`);
3604
- writeScope($scope14_id, {});
3595
+ const $scope13_id = _scope_id();
3596
+ _html(`<span class="chip stat"${_attr("title", `${rowPrs.length} open PR${rowPrs.length === 1 ? "" : "s"}`)}>${_escape(rowPrs.length)}${_el_resume($scope13_id, "b")} PR<!>${rowPrs.length === 1 ? "" : "s"}${_el_resume($scope13_id, "c")}</span>${_el_resume($scope13_id, "a")}`);
3597
+ writeScope($scope13_id, {});
3605
3598
  return 0;
3606
3599
  }
3607
- }, $scope3_id, "g", 1, 1, 1, 0, 1);
3600
+ }, $scope3_id, "f", 1, 1, 1, 0, 1);
3608
3601
  _if(() => {
3609
3602
  if ((st?.ports || []).length) {
3610
- const $scope13_id = _scope_id();
3611
- _html(`<span class="chip stat"${_attr("title", st.ports.map((p) => `:${p.port} ${p.cmd}`).join("\n"))}>:<!>${_escape(st.ports.length)}${_el_resume($scope13_id, "b")}</span>${_el_resume($scope13_id, "a")}`);
3612
- writeScope($scope13_id, {});
3603
+ const $scope12_id = _scope_id();
3604
+ _html(`<span class="chip stat"${_attr("title", st.ports.map((p) => `:${p.port} ${p.cmd}`).join("\n"))}>:<!>${_escape(st.ports.length)}${_el_resume($scope12_id, "b")}</span>${_el_resume($scope12_id, "a")}`);
3605
+ writeScope($scope12_id, {});
3613
3606
  return 0;
3614
3607
  }
3615
- }, $scope3_id, "h", 1, 1, 1, 0, 1);
3608
+ }, $scope3_id, "g", 1, 1, 1, 0, 1);
3616
3609
  _html("</span></span><span class=row-repos>");
3617
3610
  _for_of(st?.repos || row.ws.repos, (repo) => {
3618
- const $scope17_id = _scope_id();
3619
- _html(`<span class=mono>${_escape(repo.worktree)}${_el_resume($scope17_id, "a")}</span>`);
3620
- writeScope($scope17_id, {});
3621
- }, "full_name", $scope3_id, "i", 1, 1, 1, "</span>", 1);
3611
+ const $scope16_id = _scope_id();
3612
+ _html(`<span class=mono>${_escape(repo.worktree)}${_el_resume($scope16_id, "a")}</span>`);
3613
+ writeScope($scope16_id, {});
3614
+ }, "full_name", $scope3_id, "h", 1, 1, 1, "</span>", 1);
3622
3615
  _subscribe($si__input_status && $input_status__closures, _subscribe($si__input_selectedId && $input_selectedId__closures, writeScope($scope3_id, {
3623
- m: st?.agents,
3624
- n: st?.working,
3625
- o: st?.waiting,
3626
- p: st?.changes,
3627
- q: st?.ports,
3628
- r: st?.ports?.length,
3629
- t: rowPrs?.length
3616
+ l: st?.agents,
3617
+ m: st?.working,
3618
+ n: st?.waiting,
3619
+ o: st?.ports,
3620
+ p: st?.ports?.length,
3621
+ r: rowPrs?.length
3630
3622
  })));
3631
3623
  return 2;
3632
3624
  }
@@ -3708,9 +3700,9 @@ var palette_default = _template("h", (input) => {
3708
3700
  }, "h1", $scope6_id), () => {
3709
3701
  _html("<select aria-label=\"Preferred agent\">");
3710
3702
  _for_of(input.settings.agents, (agent) => {
3711
- const $scope18_id = _scope_id();
3712
- _html(`<option${_attr_option_value(agent)}>${_escape(agent)}${_el_resume($scope18_id, "b", $sg__input_settings_agents)}</option>${_el_resume($scope18_id, "a", $sg__input_settings_agents)}`);
3713
- $si__input_settings_agents && writeScope($scope18_id, {});
3703
+ const $scope17_id = _scope_id();
3704
+ _html(`<option${_attr_option_value(agent)}>${_escape(agent)}${_el_resume($scope17_id, "b", $sg__input_settings_agents)}</option>${_el_resume($scope17_id, "a", $sg__input_settings_agents)}`);
3705
+ $si__input_settings_agents && writeScope($scope17_id, {});
3714
3706
  }, (a) => a, $scope6_id, "a", $sg__input_settings_agents, 1, $sg__input_settings_agents, "</select>", 1);
3715
3707
  });
3716
3708
  _html("</label>");
@@ -3740,7 +3732,7 @@ var palette_default = _template("h", (input) => {
3740
3732
  z: input.settings?.agents,
3741
3733
  a0: input.onSettings,
3742
3734
  a1: query,
3743
- a2: hi,
3735
+ a2: hiRaw,
3744
3736
  a3: showArchived,
3745
3737
  a4: active,
3746
3738
  a5: archived,
@@ -3749,10 +3741,12 @@ var palette_default = _template("h", (input) => {
3749
3741
  a9: currentWs,
3750
3742
  ab: commands,
3751
3743
  ad: rows,
3752
- af: pick,
3753
- ag: $si__input_selectedId && $input_selectedId__closures,
3754
- ai: $si__input_status && $input_status__closures,
3755
- aj: $si__input_settings_agents && $input_settings_agents__closures
3744
+ af: rows.length,
3745
+ ah: hi,
3746
+ ai: pick,
3747
+ aj: $si__input_selectedId && $input_selectedId__closures,
3748
+ al: $si__input_status && $input_status__closures,
3749
+ am: $si__input_settings_agents && $input_settings_agents__closures
3756
3750
  });
3757
3751
  _resume_branch($scope0_id);
3758
3752
  });
@@ -4647,10 +4641,12 @@ var new_workspace_default = _template("g", (input) => {
4647
4641
  * first render on it); getters return defaults during a server render.
4648
4642
  * Writes update the map immediately and post fire-and-forget. */
4649
4643
  var prefs$1 = /* @__PURE__ */ new Map();
4644
+ var written = /* @__PURE__ */ new Set();
4650
4645
  function get$1(key) {
4651
4646
  return prefs$1.get(key) ?? null;
4652
4647
  }
4653
4648
  function set(key, value) {
4649
+ written.add(key);
4654
4650
  if (value === null) prefs$1.delete(key);
4655
4651
  else prefs$1.set(key, value);
4656
4652
  post(key, value);
@@ -4677,6 +4673,9 @@ function setWorkspaceTab(workspaceId, sessionId) {
4677
4673
  function diffTreeOpen() {
4678
4674
  return get$1("diff-tree") !== "0";
4679
4675
  }
4676
+ function diffUncommitted() {
4677
+ return get$1("diff-uncommitted") === "1";
4678
+ }
4680
4679
  //#endregion
4681
4680
  //#region src/tags/icons/icon-edit.marko
4682
4681
  var icon_edit_default = _template("w", (input) => {
@@ -4732,7 +4731,6 @@ var side_nav_item_default = _template("k", (input) => {
4732
4731
  _if(() => {
4733
4732
  if (ws) {
4734
4733
  const $scope1_id = _scope_id();
4735
- const $if_content__st_changes__closures = /* @__PURE__ */ new Set();
4736
4734
  const $if_content__st_repos__closures = /* @__PURE__ */ new Set();
4737
4735
  const $if_content__st_ports__closures = /* @__PURE__ */ new Set();
4738
4736
  const st = input.status[input.workspaceId];
@@ -4823,7 +4821,7 @@ var side_nav_item_default = _template("k", (input) => {
4823
4821
  })));
4824
4822
  }, "id", $scope3_id, "i", $sg__input_sessions, $sg__input_sessions, $sg__input_sessions, 0, 1);
4825
4823
  _if(() => {
4826
- if ((st?.repos || []).some((r) => (r.prs || []).length) || (st?.ports || []).length || st?.changes) {
4824
+ if ((st?.repos || []).some((r) => (r.prs || []).length) || (st?.ports || []).length) {
4827
4825
  const $scope8_id = _scope_id();
4828
4826
  const $if_content5__repoLabel__closures = /* @__PURE__ */ new Set();
4829
4827
  const repoLabel = _resume((repo) => {
@@ -4833,48 +4831,40 @@ var side_nav_item_default = _template("k", (input) => {
4833
4831
  return repos.some((r) => r.full_name !== repo.full_name && r.full_name.split("/")[1] === name) ? repo.full_name : name;
4834
4832
  }, "k1", $scope8_id);
4835
4833
  _html("<div class=side-chips>");
4836
- _if(() => {
4837
- if (st?.changes) {
4838
- const $scope9_id = _scope_id();
4839
- _html(`<button class="chip changes"${_attr("title", `${st.changes} changed file${st.changes === 1 ? "" : "s"} — review`)}>±${_sep($sg__input_workspaceId__OR__input_status)}${_escape(st.changes)}${_el_resume($scope9_id, "b", $sg__input_workspaceId__OR__input_status)}</button>${_el_resume($scope9_id, "a")}`);
4840
- _script($scope9_id, "k7");
4841
- _subscribe($si__input_workspaceId__OR__input_status && $if_content__st_changes__closures, writeScope($scope9_id, {
4842
- _: _scope_with_id($scope8_id),
4843
- Cj: $si__input_workspaceId__OR__input_status && 1
4844
- }));
4845
- return 0;
4846
- }
4847
- }, $scope8_id, "a", $sg__input_workspaceId__OR__input_status, $sg__input_workspaceId__OR__input_status, $sg__input_workspaceId__OR__input_status, 0, 1);
4848
4834
  _for_of(st?.repos || [], (repo) => {
4849
- const $scope11_id = _scope_id();
4835
+ const $scope9_id = _scope_id();
4850
4836
  _for_of(repo.prs || [], (pr) => {
4851
- const $scope12_id = _scope_id();
4852
- _html(`<a class=${$class$2[(pr.isDraft ? 1 : 0) + (pr.reviewDecision === "CHANGES_REQUESTED" ? 2 : 0)]}${_attr("href", pr.url)} target=_blank rel=noopener${_attr("title", `${repo.worktree} — ${pr.title}${pr.reviewDecision === "APPROVED" ? " (approved)" : pr.reviewDecision === "CHANGES_REQUESTED" ? " (changes requested)" : ""}`)}>${_escape(repoLabel(repo))}${_el_resume($scope12_id, "b", $sg__input_workspaceId__OR__input_status)}#${_sep($sg__input_workspaceId__OR__input_status)}${_escape(pr.number)}${_el_resume($scope12_id, "c", $sg__input_workspaceId__OR__input_status)}${_sep($sg__input_workspaceId__OR__input_status)}${pr.reviewDecision === "APPROVED" ? " ✓" : pr.reviewDecision === "CHANGES_REQUESTED" ? " ✗" : ""}${_el_resume($scope12_id, "d", $sg__input_workspaceId__OR__input_status)}</a>${_el_resume($scope12_id, "a", $sg__input_workspaceId__OR__input_status)}`);
4853
- $si__input_workspaceId__OR__input_status && _subscribe($if_content5__repoLabel__closures, writeScope($scope12_id, { _: _scope_with_id($scope11_id) }));
4854
- }, "number", $scope11_id, "a", $sg__input_workspaceId__OR__input_status, $sg__input_workspaceId__OR__input_status, $sg__input_workspaceId__OR__input_status, 0, 1);
4855
- $si__input_workspaceId__OR__input_status && writeScope($scope11_id, {
4856
- c: repo,
4837
+ const $scope10_id = _scope_id();
4838
+ _html(`<button class=${$class$2[(pr.isDraft ? 1 : 0) + (pr.reviewDecision === "CHANGES_REQUESTED" ? 2 : 0)]}${_attr("title", `${repo.worktree} — ${pr.title}${pr.reviewDecision === "APPROVED" ? " (approved)" : pr.reviewDecision === "CHANGES_REQUESTED" ? " (changes requested)" : ""}`)}>${_escape(repoLabel(repo))}${_el_resume($scope10_id, "b", $sg__input_workspaceId__OR__input_status)}#${_sep($sg__input_workspaceId__OR__input_status)}${_escape(pr.number)}${_el_resume($scope10_id, "c", $sg__input_workspaceId__OR__input_status)}${_sep($sg__input_workspaceId__OR__input_status)}${pr.reviewDecision === "APPROVED" ? " ✓" : pr.reviewDecision === "CHANGES_REQUESTED" ? " ✗" : ""}${_el_resume($scope10_id, "d", $sg__input_workspaceId__OR__input_status)}</button>${_el_resume($scope10_id, "a")}`);
4839
+ _script($scope10_id, "k7");
4840
+ _subscribe($si__input_workspaceId__OR__input_status && $if_content5__repoLabel__closures, writeScope($scope10_id, {
4841
+ l: pr?.number,
4842
+ _: _scope_with_id($scope9_id)
4843
+ }));
4844
+ }, "number", $scope9_id, "a", $sg__input_workspaceId__OR__input_status, $sg__input_workspaceId__OR__input_status, $sg__input_workspaceId__OR__input_status, 0, 1);
4845
+ writeScope($scope9_id, {
4846
+ c: $si__input_workspaceId__OR__input_status && repo,
4857
4847
  e: repo?.worktree,
4858
4848
  _: _scope_with_id($scope8_id)
4859
4849
  });
4860
- }, "full_name", $scope8_id, "b", $sg__input_workspaceId__OR__input_status, $sg__input_workspaceId__OR__input_status, $sg__input_workspaceId__OR__input_status);
4850
+ }, "full_name", $scope8_id, "a", $sg__input_workspaceId__OR__input_status, $sg__input_workspaceId__OR__input_status, $sg__input_workspaceId__OR__input_status);
4861
4851
  _for_of(st?.ports || [], (p) => {
4862
- const $scope10_id = _scope_id();
4863
- _html(`<span class="chip port-chip"${_attr("title", p.cmd)}><button class=port-open${_attr("title", `Open http://${typeof location === "undefined" ? "localhost" : location.hostname}:${p.port}`)}>:${_sep($sg__input_workspaceId__OR__input_status)}${_escape(p.port)}${_el_resume($scope10_id, "c", $sg__input_workspaceId__OR__input_status)}</button>${_el_resume($scope10_id, "b")}<button class=${armedPid === p.pid ? "\"port-kill armed\"" : "port-kill"}${armedPid === p.pid ? _attr("title", `Really kill ${p.cmd}?`) : _attr("title", `Kill ${p.cmd} (pid ${p.pid})`)}>×</button>${_el_resume($scope10_id, "d")}</span>${_el_resume($scope10_id, "a", $sg__input_workspaceId__OR__input_status)}`);
4864
- _script($scope10_id, "k8");
4865
- _subscribe($armedPid__closures, writeScope($scope10_id, {
4852
+ const $scope11_id = _scope_id();
4853
+ _html(`<span class="chip port-chip"${_attr("title", p.cmd)}><button class=port-open${_attr("title", `Open http://${typeof location === "undefined" ? "localhost" : location.hostname}:${p.port}`)}>:${_sep($sg__input_workspaceId__OR__input_status)}${_escape(p.port)}${_el_resume($scope11_id, "c", $sg__input_workspaceId__OR__input_status)}</button>${_el_resume($scope11_id, "b")}<button class=${armedPid === p.pid ? "\"port-kill armed\"" : "port-kill"}${armedPid === p.pid ? _attr("title", `Really kill ${p.cmd}?`) : _attr("title", `Kill ${p.cmd} (pid ${p.pid})`)}>×</button>${_el_resume($scope11_id, "d")}</span>${_el_resume($scope11_id, "a", $sg__input_workspaceId__OR__input_status)}`);
4854
+ _script($scope11_id, "k8");
4855
+ _subscribe($armedPid__closures, writeScope($scope11_id, {
4866
4856
  g: p?.cmd,
4867
4857
  h: p?.port,
4868
4858
  i: p?.pid,
4869
4859
  _: _scope_with_id($scope8_id)
4870
4860
  }));
4871
- }, "port", $scope8_id, "c", $sg__input_workspaceId__OR__input_status, $sg__input_workspaceId__OR__input_status, $sg__input_workspaceId__OR__input_status, 0, 1);
4861
+ }, "port", $scope8_id, "b", $sg__input_workspaceId__OR__input_status, $sg__input_workspaceId__OR__input_status, $sg__input_workspaceId__OR__input_status, 0, 1);
4872
4862
  _html("</div>");
4873
- _subscribe($si__input_workspaceId__OR__input_status && $if_content__st_changes__closures, _subscribe($si__input_workspaceId__OR__input_status && $if_content__st_ports__closures, _subscribe($si__input_workspaceId__OR__input_status && $if_content__st_repos__closures, writeScope($scope8_id, {
4874
- d: $si__input_workspaceId__OR__input_status && repoLabel,
4863
+ _subscribe($si__input_workspaceId__OR__input_status && $if_content__st_ports__closures, _subscribe($si__input_workspaceId__OR__input_status && $if_content__st_repos__closures, writeScope($scope8_id, {
4864
+ c: $si__input_workspaceId__OR__input_status && repoLabel,
4875
4865
  _: _scope_with_id($scope3_id),
4876
- e: $si__input_workspaceId__OR__input_status && $if_content5__repoLabel__closures
4877
- }))));
4866
+ d: $si__input_workspaceId__OR__input_status && $if_content5__repoLabel__closures
4867
+ })));
4878
4868
  return 0;
4879
4869
  }
4880
4870
  }, $scope3_id, "j", $sg__input_workspaceId__OR__input_status, $sg__input_workspaceId__OR__input_status, $sg__input_workspaceId__OR__input_status__OR__input_onReview__OR__input_onNewSession, 0, 1);
@@ -4882,8 +4872,8 @@ var side_nav_item_default = _template("k", (input) => {
4882
4872
  _script($scope3_id, "k9");
4883
4873
  _subscribe($si__input_workspaces__OR__input_workspaceId && $ws_name__closures, _subscribe($si__input_sessions && $input_sessions__closures, _subscribe($si__input_onNewSession && $input_onNewSession__closures, _subscribe($si__input_onReview && $input_onReview__closures, _subscribe($si__input_current && $input_current__closures, writeScope($scope3_id, {
4884
4874
  _: _scope_with_id($scope1_id),
4885
- Ca4: $si__input_current && 1,
4886
- Cad: $si__input_workspaces__OR__input_workspaceId && 1
4875
+ Ca5: $si__input_current && 1,
4876
+ Cae: $si__input_workspaces__OR__input_workspaceId && 1
4887
4877
  }))))));
4888
4878
  return 1;
4889
4879
  }
@@ -4891,12 +4881,10 @@ var side_nav_item_default = _template("k", (input) => {
4891
4881
  writeScope($scope1_id, {
4892
4882
  c: $si__input_workspaceId__OR__input_status__OR__input_rail && st?.repos,
4893
4883
  d: $si__input_workspaceId__OR__input_status__OR__input_rail && st?.ports,
4894
- e: $si__input_workspaceId__OR__input_status__OR__input_rail && st?.changes,
4895
- f: _serialize_if($scope0_reason, 11) && dot,
4884
+ e: _serialize_if($scope0_reason, 11) && dot,
4896
4885
  _: _scope_with_id($scope0_id),
4897
- j: $si__input_workspaceId__OR__input_status && $if_content__st_changes__closures,
4898
- h: $si__input_workspaceId__OR__input_status && $if_content__st_repos__closures,
4899
- i: $si__input_workspaceId__OR__input_status && $if_content__st_ports__closures
4886
+ g: $si__input_workspaceId__OR__input_status && $if_content__st_repos__closures,
4887
+ h: $si__input_workspaceId__OR__input_status && $if_content__st_ports__closures
4900
4888
  });
4901
4889
  return 0;
4902
4890
  }
@@ -4918,21 +4906,22 @@ var side_nav_item_default = _template("k", (input) => {
4918
4906
  r: input.onOpenTab,
4919
4907
  s: input.onRenameSession,
4920
4908
  t: input.onCloseSession,
4921
- u: armedKill,
4922
- v: armedPid,
4923
- x: armBox.timer,
4924
- y: arm,
4925
- a0: $si__input_workspaces__OR__input_workspaceId__OR__input_rail && ws?.name,
4926
- a4: $si__input_current && $input_current__closures,
4927
- a5: $si__input_index && $input_index__closures,
4928
- ad: $si__input_workspaces__OR__input_workspaceId && $ws_name__closures,
4929
- a8: $si__input_agents && $input_agents__closures,
4930
- aa: $si__input_currentTab && $input_currentTab__closures,
4931
- ab: $armedKill__closures,
4932
- ac: $armedPid__closures,
4933
- a6: $si__input_onReview && $input_onReview__closures,
4934
- a7: $si__input_onNewSession && $input_onNewSession__closures,
4935
- a9: $si__input_sessions && $input_sessions__closures
4909
+ u: input.onReviewPr,
4910
+ v: armedKill,
4911
+ w: armedPid,
4912
+ y: armBox.timer,
4913
+ z: arm,
4914
+ a1: $si__input_workspaces__OR__input_workspaceId__OR__input_rail && ws?.name,
4915
+ a5: $si__input_current && $input_current__closures,
4916
+ a6: $si__input_index && $input_index__closures,
4917
+ ae: $si__input_workspaces__OR__input_workspaceId && $ws_name__closures,
4918
+ a9: $si__input_agents && $input_agents__closures,
4919
+ ab: $si__input_currentTab && $input_currentTab__closures,
4920
+ ac: $armedKill__closures,
4921
+ ad: $armedPid__closures,
4922
+ a7: $si__input_onReview && $input_onReview__closures,
4923
+ a8: $si__input_onNewSession && $input_onNewSession__closures,
4924
+ aa: $si__input_sessions && $input_sessions__closures
4936
4925
  });
4937
4926
  _resume_branch($scope0_id);
4938
4927
  });
@@ -5034,7 +5023,7 @@ var $class$1 = [
5034
5023
  "\"side-nav closed open\""
5035
5024
  ];
5036
5025
  var side_nav_default = _template("l", (input) => {
5037
- const $scope0_reason = _scope_reason(), $sg__input_workspace__OR__input_allWorkspaces__OR__input_allStatus = _serialize_guard($scope0_reason, 1), $sg__input_workspace__OR__input_allWorkspaces = _serialize_guard($scope0_reason, 0), $sg__input_workspace__OR__input_allWorkspaces__OR__input_onNewSession = _serialize_guard($scope0_reason, 2), $sg__input_workspace__OR__input_allWorkspaces__OR__input_allStatus__OR__input_sessions = _serialize_guard($scope0_reason, 3), $sg__input_agents = _serialize_guard($scope0_reason, 14), $si__input_workspace = _serialize_if($scope0_reason, 4), $si__input_workspace__OR__input_allWorkspaces = _serialize_if($scope0_reason, 0), $si__input_allStatus = _serialize_if($scope0_reason, 12), $si__input_onSelectWorkspace = _serialize_if($scope0_reason, 13), $si__input_agents = _serialize_if($scope0_reason, 14), $si__input_onNewSession = _serialize_if($scope0_reason, 15), $si__input_sessions = _serialize_if($scope0_reason, 16), $si__input_onRenameSession = _serialize_if($scope0_reason, 17), $si__input_onCloseSession = _serialize_if($scope0_reason, 18);
5026
+ const $scope0_reason = _scope_reason(), $sg__input_workspace__OR__input_allWorkspaces__OR__input_allStatus = _serialize_guard($scope0_reason, 1), $sg__input_workspace__OR__input_allWorkspaces = _serialize_guard($scope0_reason, 0), $sg__input_workspace__OR__input_allWorkspaces__OR__input_onNewSession = _serialize_guard($scope0_reason, 2), $sg__input_workspace__OR__input_allWorkspaces__OR__input_allStatus__OR__input_sessions = _serialize_guard($scope0_reason, 3), $sg__input_agents = _serialize_guard($scope0_reason, 14), $si__input_workspace = _serialize_if($scope0_reason, 4), $si__input_workspace__OR__input_allWorkspaces = _serialize_if($scope0_reason, 0), $si__input_allStatus = _serialize_if($scope0_reason, 12), $si__input_onSelectWorkspace = _serialize_if($scope0_reason, 13), $si__input_agents = _serialize_if($scope0_reason, 14), $si__input_onNewSession = _serialize_if($scope0_reason, 15), $si__input_onReviewOpened = _serialize_if($scope0_reason, 16), $si__input_sessions = _serialize_if($scope0_reason, 17), $si__input_onRenameSession = _serialize_if($scope0_reason, 18), $si__input_onCloseSession = _serialize_if($scope0_reason, 19);
5038
5027
  const $scope0_id = _scope_id();
5039
5028
  const $ws_id__closures = /* @__PURE__ */ new Set();
5040
5029
  const $workspaces__closures = /* @__PURE__ */ new Set();
@@ -5042,6 +5031,7 @@ var side_nav_default = _template("l", (input) => {
5042
5031
  const $input_onSelectWorkspace__closures = /* @__PURE__ */ new Set();
5043
5032
  const $input_agents__closures = /* @__PURE__ */ new Set();
5044
5033
  const $input_onNewSession__closures = /* @__PURE__ */ new Set();
5034
+ const $input_onReviewOpened__closures = /* @__PURE__ */ new Set();
5045
5035
  const $input_sessions__closures = /* @__PURE__ */ new Set();
5046
5036
  const $input_onRenameSession__closures = /* @__PURE__ */ new Set();
5047
5037
  const $input_onCloseSession__closures = /* @__PURE__ */ new Set();
@@ -5075,7 +5065,7 @@ var side_nav_default = _template("l", (input) => {
5075
5065
  if (drawerOpen) {
5076
5066
  const $scope5_id = _scope_id();
5077
5067
  _html(`<div class=drawer-backdrop></div>${_el_resume($scope5_id, "a")}`);
5078
- _script($scope5_id, "l14");
5068
+ _script($scope5_id, "l16");
5079
5069
  writeScope($scope5_id, {});
5080
5070
  return 0;
5081
5071
  }
@@ -5125,12 +5115,25 @@ var side_nav_default = _template("l", (input) => {
5125
5115
  }, "l3", $scope1_id),
5126
5116
  onReview: _resume(function() {
5127
5117
  drawerOpen = false;
5128
- if (wsId === ws.id) view = view === "diff" ? "term" : "diff";
5129
- else {
5118
+ if (wsId === ws.id) {
5119
+ view = view === "diff" ? "term" : "diff";
5120
+ if (view === "diff") input.onReviewOpened?.();
5121
+ } else {
5130
5122
  navIntent$1.requestDiff(wsId);
5131
5123
  input.onSelectWorkspace?.(wsId);
5132
5124
  }
5133
5125
  }, "l4", $scope1_id),
5126
+ onReviewPr: _resume(function(worktree, number) {
5127
+ drawerOpen = false;
5128
+ const pr = `${worktree}/${number}`;
5129
+ if (wsId === ws.id) {
5130
+ view = "diff";
5131
+ input.onReviewOpened?.(pr);
5132
+ } else {
5133
+ navIntent$1.requestDiff(wsId, pr);
5134
+ input.onSelectWorkspace?.(wsId);
5135
+ }
5136
+ }, "l5", $scope1_id),
5134
5137
  sessions: wsId === ws.id && input.sessions ? input.sessions : allStatus[wsId]?.sessions || [],
5135
5138
  currentTab: wsId === ws.id && view === "term" ? tab : null,
5136
5139
  onOpenTab: _resume(function(sessionId) {
@@ -5139,7 +5142,7 @@ var side_nav_default = _template("l", (input) => {
5139
5142
  tab = sessionId;
5140
5143
  view = "term";
5141
5144
  } else input.onOpenSession?.(wsId, sessionId);
5142
- }, "l5", $scope1_id),
5145
+ }, "l6", $scope1_id),
5143
5146
  onRenameSession: input.onRenameSession,
5144
5147
  onCloseSession: input.onCloseSession
5145
5148
  });
@@ -5154,13 +5157,13 @@ var side_nav_default = _template("l", (input) => {
5154
5157
  const $scope2_id = _scope_id();
5155
5158
  let railHover = null;
5156
5159
  const railHoverBox = { timer: 0 };
5157
- const railKeep = _resume(() => clearTimeout(railHoverBox.timer), "l6", $scope2_id);
5160
+ const railKeep = _resume(() => clearTimeout(railHoverBox.timer), "l7", $scope2_id);
5158
5161
  const railHide = _resume(() => {
5159
5162
  clearTimeout(railHoverBox.timer);
5160
5163
  railHoverBox.timer = setTimeout(() => {
5161
5164
  railHover = null;
5162
5165
  }, 150);
5163
- }, "l7", $scope2_id);
5166
+ }, "l8", $scope2_id);
5164
5167
  _html("<div class=side-rail>");
5165
5168
  _for_of(itemIds, (wsId, i) => {
5166
5169
  const $scope3_id = _scope_id();
@@ -5189,10 +5192,10 @@ var side_nav_default = _template("l", (input) => {
5189
5192
  current: wsId === ws.id,
5190
5193
  onSelect: _resume(function() {
5191
5194
  if (wsId !== ws.id) input.onSelectWorkspace?.(wsId);
5192
- }, "l8", $scope3_id)
5195
+ }, "l9", $scope3_id)
5193
5196
  });
5194
5197
  _html(`</span>${_el_resume($scope3_id, "a")}`);
5195
- _script($scope3_id, "l15");
5198
+ _script($scope3_id, "l17");
5196
5199
  _subscribe($si__input_allStatus && $allStatus__closures, _subscribe($si__input_workspace__OR__input_allWorkspaces && $workspaces__closures, _subscribe($si__input_workspace && $ws_id__closures, writeScope($scope3_id, {
5197
5200
  d: wsId,
5198
5201
  _: _scope_with_id($scope2_id),
@@ -5232,24 +5235,37 @@ var side_nav_default = _template("l", (input) => {
5232
5235
  onSelect: _resume(function() {
5233
5236
  railHover = null;
5234
5237
  if (hoverId !== ws.id) input.onSelectWorkspace?.(hoverId);
5235
- }, "l9", $scope4_id),
5238
+ }, "l10", $scope4_id),
5236
5239
  onManage: _resume(function() {
5237
5240
  railHover = null;
5238
5241
  input.onManage?.(hoverId);
5239
- }, "l10", $scope4_id),
5242
+ }, "l11", $scope4_id),
5240
5243
  agents: input.agents,
5241
5244
  onNewSession: _resume(function(agent) {
5242
5245
  railHover = null;
5243
5246
  input.onNewSession(hoverId, agent);
5244
- }, "l11", $scope4_id),
5247
+ }, "l12", $scope4_id),
5245
5248
  onReview: _resume(function() {
5246
5249
  railHover = null;
5247
- if (hoverId === ws.id) view = view === "diff" ? "term" : "diff";
5248
- else {
5250
+ if (hoverId === ws.id) {
5251
+ view = view === "diff" ? "term" : "diff";
5252
+ if (view === "diff") input.onReviewOpened?.();
5253
+ } else {
5249
5254
  navIntent$1.requestDiff(hoverId);
5250
5255
  input.onSelectWorkspace?.(hoverId);
5251
5256
  }
5252
- }, "l12", $scope4_id),
5257
+ }, "l13", $scope4_id),
5258
+ onReviewPr: _resume(function(worktree, number) {
5259
+ railHover = null;
5260
+ const pr = `${worktree}/${number}`;
5261
+ if (hoverId === ws.id) {
5262
+ view = "diff";
5263
+ input.onReviewOpened?.(pr);
5264
+ } else {
5265
+ navIntent$1.requestDiff(hoverId, pr);
5266
+ input.onSelectWorkspace?.(hoverId);
5267
+ }
5268
+ }, "l14", $scope4_id),
5253
5269
  sessions: hoverId === ws.id && input.sessions ? input.sessions : allStatus[hoverId]?.sessions || [],
5254
5270
  currentTab: hoverId === ws.id && view === "term" ? tab : null,
5255
5271
  onOpenTab: _resume(function(sessionId) {
@@ -5258,19 +5274,19 @@ var side_nav_default = _template("l", (input) => {
5258
5274
  tab = sessionId;
5259
5275
  view = "term";
5260
5276
  } else input.onOpenSession?.(hoverId, sessionId);
5261
- }, "l13", $scope4_id),
5277
+ }, "l15", $scope4_id),
5262
5278
  onRenameSession: input.onRenameSession,
5263
5279
  onCloseSession: input.onCloseSession
5264
5280
  });
5265
5281
  _html(`</div>${_el_resume($scope4_id, "a")}`);
5266
- _script($scope4_id, "l16");
5267
- _subscribe($si__input_allStatus && $allStatus__closures, _subscribe($si__input_workspace__OR__input_allWorkspaces && $workspaces__closures, _subscribe($view__closures, _subscribe($tab__closures, _subscribe($si__input_workspace && $ws_id__closures, _subscribe($si__input_onCloseSession && $input_onCloseSession__closures, _subscribe($si__input_onRenameSession && $input_onRenameSession__closures, _subscribe($si__input_sessions && $input_sessions__closures, _subscribe($si__input_onNewSession && $input_onNewSession__closures, _subscribe($si__input_agents && $input_agents__closures, _subscribe($si__input_onSelectWorkspace && $input_onSelectWorkspace__closures, writeScope($scope4_id, {
5282
+ _script($scope4_id, "l18");
5283
+ _subscribe($si__input_allStatus && $allStatus__closures, _subscribe($si__input_workspace__OR__input_allWorkspaces && $workspaces__closures, _subscribe($view__closures, _subscribe($tab__closures, _subscribe($si__input_workspace && $ws_id__closures, _subscribe($si__input_onCloseSession && $input_onCloseSession__closures, _subscribe($si__input_onRenameSession && $input_onRenameSession__closures, _subscribe($si__input_sessions && $input_sessions__closures, _subscribe($si__input_onReviewOpened && $input_onReviewOpened__closures, _subscribe($si__input_onNewSession && $input_onNewSession__closures, _subscribe($si__input_agents && $input_agents__closures, _subscribe($si__input_onSelectWorkspace && $input_onSelectWorkspace__closures, writeScope($scope4_id, {
5268
5284
  c: hoverId,
5269
5285
  b: _existing_scope($childScope3),
5270
- Caz: $si__input_workspace && 1,
5271
- Cb4: $si__input_workspace__OR__input_allWorkspaces && 1,
5272
- Cb5: $si__input_allStatus && 1
5273
- }))))))))))));
5286
+ Cb1: $si__input_workspace && 1,
5287
+ Cb6: $si__input_workspace__OR__input_allWorkspaces && 1,
5288
+ Cb7: $si__input_allStatus && 1
5289
+ })))))))))))));
5274
5290
  return 0;
5275
5291
  }
5276
5292
  }, $scope2_id, "b", 1, 1, 1, 0, 1);
@@ -5306,7 +5322,7 @@ var side_nav_default = _template("l", (input) => {
5306
5322
  _html(`<button class=${notifyOn ? "\"icon-btn notify-btn on\"" : "\"icon-btn notify-btn\""} title=${notifyOn ? "\"Disable agent-waiting notifications\"" : "\"Notify when an agent is waiting\""}>`);
5307
5323
  icon_bell_default({ size: 15 });
5308
5324
  _html(`</button>${_el_resume($scope6_id, "a")}`);
5309
- _script($scope6_id, "l17");
5325
+ _script($scope6_id, "l19");
5310
5326
  writeScope($scope6_id, { _: _scope_with_id($scope0_id) });
5311
5327
  return 0;
5312
5328
  }
@@ -5316,8 +5332,8 @@ var side_nav_default = _template("l", (input) => {
5316
5332
  _html(`</button>${_el_resume($scope0_id, "g")}<button class=icon-btn title="New workspace">`);
5317
5333
  icon_folder_plus_default({ size: 16 });
5318
5334
  _html(`</button>${_el_resume($scope0_id, "i")}</div></aside>${_el_resume($scope0_id, "b")}`);
5319
- _script($scope0_id, "l18");
5320
- _script($scope0_id, "l19");
5335
+ _script($scope0_id, "l20");
5336
+ _script($scope0_id, "l21");
5321
5337
  writeScope($scope0_id, {
5322
5338
  n: _serialize_if($scope0_reason, 6) && input.tab,
5323
5339
  o: _serialize_if($scope0_reason, 5) && input.tabChange,
@@ -5332,61 +5348,111 @@ var side_nav_default = _template("l", (input) => {
5332
5348
  a1: input.onManage,
5333
5349
  a2: input.agents,
5334
5350
  a3: input.onNewSession,
5335
- a4: input.sessions,
5336
- a5: input.onOpenSession,
5337
- a6: input.onRenameSession,
5338
- a7: input.onCloseSession,
5339
- a8: input.onUsage,
5340
- a9: input.onNew,
5341
- aa: _serialize_if($scope0_reason, 11) && ws,
5342
- ac: ws?.id,
5343
- ae: tab,
5344
- ag: view,
5345
- ai: drawerOpen,
5346
- ak: navOpen,
5347
- am: notifyOn,
5348
- ao: toggleNotify,
5349
- ap: itemIds,
5350
- aq: workspaces,
5351
- ar: allStatus,
5352
- af: input.tabChange || void 0,
5353
- ah: input.viewChange || void 0,
5354
- aj: input.drawerOpenChange || void 0,
5355
- az: $si__input_workspace && $ws_id__closures,
5356
- b4: $si__input_workspace__OR__input_allWorkspaces && $workspaces__closures,
5357
- b5: $si__input_allStatus && $allStatus__closures,
5358
- at: $si__input_onSelectWorkspace && $input_onSelectWorkspace__closures,
5359
- au: $si__input_agents && $input_agents__closures,
5360
- av: $si__input_onNewSession && $input_onNewSession__closures,
5361
- aw: $si__input_sessions && $input_sessions__closures,
5362
- ax: $si__input_onRenameSession && $input_onRenameSession__closures,
5363
- ay: $si__input_onCloseSession && $input_onCloseSession__closures,
5364
- b0: $tab__closures,
5365
- b1: $view__closures
5351
+ a4: input.onReviewOpened,
5352
+ a5: input.sessions,
5353
+ a6: input.onOpenSession,
5354
+ a7: input.onRenameSession,
5355
+ a8: input.onCloseSession,
5356
+ a9: input.onUsage,
5357
+ aa: input.onNew,
5358
+ ab: _serialize_if($scope0_reason, 11) && ws,
5359
+ ad: ws?.id,
5360
+ af: tab,
5361
+ ah: view,
5362
+ aj: drawerOpen,
5363
+ al: navOpen,
5364
+ an: notifyOn,
5365
+ ap: toggleNotify,
5366
+ aq: itemIds,
5367
+ ar: workspaces,
5368
+ as: allStatus,
5369
+ ag: input.tabChange || void 0,
5370
+ ai: input.viewChange || void 0,
5371
+ ak: input.drawerOpenChange || void 0,
5372
+ b1: $si__input_workspace && $ws_id__closures,
5373
+ b6: $si__input_workspace__OR__input_allWorkspaces && $workspaces__closures,
5374
+ b7: $si__input_allStatus && $allStatus__closures,
5375
+ au: $si__input_onSelectWorkspace && $input_onSelectWorkspace__closures,
5376
+ av: $si__input_agents && $input_agents__closures,
5377
+ aw: $si__input_onNewSession && $input_onNewSession__closures,
5378
+ ax: $si__input_onReviewOpened && $input_onReviewOpened__closures,
5379
+ ay: $si__input_sessions && $input_sessions__closures,
5380
+ az: $si__input_onRenameSession && $input_onRenameSession__closures,
5381
+ b0: $si__input_onCloseSession && $input_onCloseSession__closures,
5382
+ b2: $tab__closures,
5383
+ b3: $view__closures
5366
5384
  });
5367
5385
  _resume_branch($scope0_id);
5368
5386
  });
5369
5387
  //#endregion
5370
5388
  //#region src/client/review.ts
5371
- /** Render review comments as markdown, quoting the commented lines. */
5389
+ var CONTEXT = 3;
5390
+ /** Render review comments as markdown: per-repo base/head so the agent
5391
+ * can see if it has moved on, plus ±3 lines of numbered patch context
5392
+ * so the snippet is still locatable after a small edit. */
5372
5393
  function commentMarkdown(comments, repos) {
5373
- const byFile = /* @__PURE__ */ new Map();
5374
- for (const c of comments) {
5375
- const key = `${c.worktree}/${c.path}`;
5376
- byFile.set(key, [...byFile.get(key) || [], c]);
5377
- }
5394
+ const byRepo = /* @__PURE__ */ new Map();
5395
+ for (const c of comments) byRepo.set(c.worktree, [...byRepo.get(c.worktree) || [], c]);
5378
5396
  const chunks = ["Review feedback:"];
5379
- for (const [key, list] of byFile) for (const c of list) {
5380
- const file = (repos || []).find((r) => r.worktree === c.worktree)?.files.find((f) => f.path === c.path);
5381
- const source = c.side === "deletions" ? file?.old : file?.new;
5382
- const lines = source ? source.split("\n").slice(c.line_start - 1, c.line_end).join("\n") : null;
5383
- const range = c.line_start === c.line_end ? `L${c.line_start}` : `L${c.line_start}-L${c.line_end}`;
5384
- chunks.push(`\n\`${key}\` ${range}:`);
5385
- if (lines != null) chunks.push("```\n" + lines + "\n```");
5386
- chunks.push(c.text);
5397
+ let n = 0;
5398
+ for (const [worktree, list] of byRepo) {
5399
+ const repo = (repos || []).find((r) => r.worktree === worktree);
5400
+ chunks.push("");
5401
+ chunks.push(repoHeader(worktree, repo));
5402
+ for (const c of list) {
5403
+ n++;
5404
+ const file = repo?.files.find((f) => f.path === c.path);
5405
+ const source = c.side === "deletions" ? file?.old : file?.new;
5406
+ const side = c.side === "deletions" ? "Old" : "New";
5407
+ const range = c.line_start === c.line_end ? `line ${c.line_start}` : `lines ${c.line_start}-${c.line_end}`;
5408
+ chunks.push("");
5409
+ chunks.push(`${n}. **${c.path}** (${side} ${range})`);
5410
+ if (source != null) {
5411
+ const snippet = quoteRange(source, c.line_start, c.line_end, c.side);
5412
+ const fence = markdownFence(snippet);
5413
+ chunks.push("");
5414
+ chunks.push(indent(`${fence}diff\n${snippet}\n${fence}`));
5415
+ }
5416
+ chunks.push("");
5417
+ chunks.push(indent(c.text));
5418
+ }
5387
5419
  }
5388
5420
  return chunks.join("\n");
5389
5421
  }
5422
+ function repoHeader(worktree, repo) {
5423
+ if (!repo?.head) return `${worktree}/`;
5424
+ const head = shortSha(repo.head);
5425
+ const base = shortSha(repo.baseSha);
5426
+ const branch = repo.pr?.headRefName || repo.branch || "HEAD";
5427
+ return `${worktree}/${repo.pr ? ` #${repo.pr.number}` : ""} — \`${branch}\` ${head} vs \`${repo.base}\` ${base}`;
5428
+ }
5429
+ function shortSha(sha, n = 12) {
5430
+ return sha.slice(0, n);
5431
+ }
5432
+ /** ±CONTEXT rows around the commented range, with +/- prefixes and
5433
+ * line numbers — same shape as codiff's review payload. */
5434
+ function quoteRange(source, start, end, side) {
5435
+ const lines = source.split("\n");
5436
+ const from = Math.max(1, start - CONTEXT);
5437
+ const to = Math.min(lines.length, end + CONTEXT);
5438
+ const mark = side === "deletions" ? "-" : "+";
5439
+ const rows = [];
5440
+ for (let i = from; i <= to; i++) {
5441
+ const prefix = i >= start && i <= end ? mark : " ";
5442
+ rows.push(`${prefix}${String(i).padStart(4)} | ${lines[i - 1] ?? ""}`);
5443
+ }
5444
+ return rows.join("\n");
5445
+ }
5446
+ function markdownFence(body) {
5447
+ let ticks = 3;
5448
+ const re = /`+/g;
5449
+ let m;
5450
+ while (m = re.exec(body)) ticks = Math.max(ticks, m[0].length + 1);
5451
+ return "`".repeat(ticks);
5452
+ }
5453
+ function indent(text) {
5454
+ return text.split("\n").map((line) => line ? ` ${line}` : line).join("\n");
5455
+ }
5390
5456
  /** Build hierarchical rows from a sorted file list: a path trie with
5391
5457
  * single-child directory chains compressed into one label (GitHub-style
5392
5458
  * "src/util" segments), each level indented by depth — labels stay short
@@ -5481,77 +5547,11 @@ _template("c", (input) => {
5481
5547
  _resume_branch($scope0_id);
5482
5548
  });
5483
5549
  //#endregion
5484
- //#region src/tags/prompt-dialog.marko
5485
- var prompt_dialog_default = _template("i", (input) => {
5486
- const $scope0_reason = _scope_reason(), $sg__input_label = _serialize_guard($scope0_reason, 2), $sg__input_onCancel = _serialize_guard($scope0_reason, 3), $si__input_submitLabel = _serialize_if($scope0_reason, 4), $si__input_label = _serialize_if($scope0_reason, 2), $si__input_placeholder = _serialize_if($scope0_reason, 5);
5487
- const $scope0_id = _scope_id();
5488
- const $modalview_content__subscribers = /* @__PURE__ */ new Set();
5489
- const $input_submitLabel__closures = /* @__PURE__ */ new Set();
5490
- const $value__closures = /* @__PURE__ */ new Set();
5491
- const $input_label__closures = /* @__PURE__ */ new Set();
5492
- const $input_placeholder__closures = /* @__PURE__ */ new Set();
5493
- let value = input.value || "";
5494
- const submit = _resume(() => {
5495
- const v = value.trim();
5496
- if (v) input.onSubmit(v);
5497
- }, "i1", $scope0_id);
5498
- _set_serialize_reason({
5499
- 0: $sg__input_onCancel,
5500
- 1: $sg__input_label,
5501
- 2: $sg__input_onCancel
5502
- });
5503
- const $childScope = _peek_scope_id();
5504
- modal_view_default({
5505
- label: input.label,
5506
- class: "prompt-dlg",
5507
- onClose: _resume(function() {
5508
- input.onCancel();
5509
- }, "i2", $scope0_id),
5510
- footer: attrTag({ content: _content_resume("i5", () => {
5511
- _scope_reason();
5512
- const $scope1_id = _scope_id();
5513
- _html(`<button class="btn primary"${_attr("disabled", !value.trim())}>${_escape(input.submitLabel || "OK")}${_el_resume($scope1_id, "b", _serialize_guard($scope0_reason, 4))}</button>${_el_resume($scope1_id, "a")}<div class=spacer></div><button class=btn>Cancel</button>${_el_resume($scope1_id, "c")}`);
5514
- _script($scope1_id, "i3");
5515
- _subscribe($value__closures, _subscribe($si__input_submitLabel && $input_submitLabel__closures, writeScope($scope1_id, { _: _scope_with_id($scope0_id) })));
5516
- _resume_branch($scope1_id);
5517
- }, $scope0_id) }),
5518
- content: _content("i6", () => {
5519
- _scope_reason();
5520
- const $scope2_id = _scope_id();
5521
- _html(`<h2 class=prompt-title>${_escape(input.label)}${_el_resume($scope2_id, "a", $sg__input_label)}</h2><input${_attr_input_value($scope2_id, "b", value, _resume((_new_value) => {
5522
- value = _new_value;
5523
- }, "i0", $scope2_id))}${_attr("placeholder", input.placeholder)}>${_el_resume($scope2_id, "b")}`);
5524
- _script($scope2_id, "i4");
5525
- _subscribe($value__closures, _subscribe($si__input_placeholder && $input_placeholder__closures, _subscribe($si__input_label && $input_label__closures, _subscribe($modalview_content__subscribers, writeScope($scope2_id, {
5526
- _: _scope_with_id($scope0_id),
5527
- Cp: 1
5528
- })))));
5529
- _resume_branch($scope2_id);
5530
- })
5531
- });
5532
- _script($scope0_id, "i7");
5533
- writeScope($scope0_id, {
5534
- e: input.onSubmit,
5535
- g: input.onCancel,
5536
- h: input.submitLabel,
5537
- j: value,
5538
- l: submit,
5539
- B2: $modalview_content__subscribers,
5540
- n: $si__input_submitLabel && $input_submitLabel__closures,
5541
- p: $value__closures,
5542
- m: $si__input_label && $input_label__closures,
5543
- o: $si__input_placeholder && $input_placeholder__closures,
5544
- a: _serialize_if($scope0_reason, 0) && _existing_scope($childScope)
5545
- });
5546
- _resume_branch($scope0_id);
5547
- });
5548
- //#endregion
5549
5550
  //#region src/tags/diff-view.marko
5550
5551
  var api$3;
5551
5552
  var copyText;
5552
5553
  var getTermDims;
5553
5554
  var makeFileFilter;
5554
- var CUSTOM = "__custom";
5555
5555
  var diff_view_default = _template("d", (input) => {
5556
5556
  const $scope0_reason = _scope_reason(), $sg__input_onExit = _serialize_guard($scope0_reason, 1), $sg__input_sessions = _serialize_guard($scope0_reason, 0), $si__input_sessions = _serialize_if($scope0_reason, 0), $si__input_sessions_0_title = _serialize_if($scope0_reason, 2), $si__input_sessions_0_agent = _serialize_if($scope0_reason, 3);
5557
5557
  const $scope0_id = _scope_id();
@@ -5580,16 +5580,15 @@ var diff_view_default = _template("d", (input) => {
5580
5580
  let sending = false;
5581
5581
  let sendTarget = null;
5582
5582
  let fileFilter = "";
5583
- let baseRef = "";
5583
+ let uncommitted = diffUncommitted();
5584
5584
  let treeOpen = diffTreeOpen();
5585
- let askBase = false;
5586
5585
  let collapsedDirs = /* @__PURE__ */ new Set();
5587
5586
  let collapsedRepos = /* @__PURE__ */ new Set();
5588
5587
  const toggleRepo = _resume((worktree) => {
5589
5588
  const next = new Set(collapsedRepos);
5590
5589
  if (!next.delete(worktree)) next.add(worktree);
5591
5590
  collapsedRepos = next;
5592
- }, "d4", $scope0_id);
5591
+ }, "d3", $scope0_id);
5593
5592
  const nearBox = {
5594
5593
  io: null,
5595
5594
  cbs: null
@@ -5604,12 +5603,15 @@ var diff_view_default = _template("d", (input) => {
5604
5603
  }, { rootMargin: "600px 0px" });
5605
5604
  nearBox.cbs.set(el, cb);
5606
5605
  nearBox.io.observe(el);
5607
- }, "d5", $scope0_id);
5608
- const load = _resume(async (base = baseRef) => {
5606
+ }, "d4", $scope0_id);
5607
+ const load = _resume(async (dirty = uncommitted) => {
5609
5608
  loading = true;
5610
5609
  error = null;
5611
5610
  try {
5612
- const qs = base ? `?base=${encodeURIComponent(base)}` : "";
5611
+ const q = new URLSearchParams();
5612
+ if (dirty) q.set("uncommitted", "1");
5613
+ if (input.focusPr) q.set("pr", input.focusPr);
5614
+ const qs = q.size ? `?${q}` : "";
5613
5615
  const [diffData, reviewData] = await Promise.all([api$3(`/api/workspaces/${input.workspaceId}/diff${qs}`), api$3(`/api/workspaces/${input.workspaceId}/review`)]);
5614
5616
  repos = diffData;
5615
5617
  comments = reviewData.comments || [];
@@ -5618,23 +5620,23 @@ var diff_view_default = _template("d", (input) => {
5618
5620
  error = String(err.message || err);
5619
5621
  }
5620
5622
  loading = false;
5621
- }, "d6", $scope0_id);
5623
+ }, "d5", $scope0_id);
5622
5624
  const loadReview = _resume(async () => {
5623
5625
  try {
5624
5626
  const reviewData = await api$3(`/api/workspaces/${input.workspaceId}/review`);
5625
5627
  comments = reviewData.comments || [];
5626
5628
  viewed = new Set(reviewData.viewed || []);
5627
5629
  } catch {}
5628
- }, "d7", $scope0_id);
5630
+ }, "d6", $scope0_id);
5629
5631
  const repoList = repos || [];
5630
5632
  const allFiles = repoList.flatMap((r) => r.files.map((f) => ({
5631
5633
  ...f,
5632
- repo: r.worktree,
5634
+ repo: r.id,
5633
5635
  key: `${r.worktree}/${f.path}`
5634
5636
  })));
5635
5637
  const fileMatch = makeFileFilter(fileFilter);
5636
5638
  const shownFiles = allFiles.filter((f) => fileMatch(f.key));
5637
- const filesByRepo = new Map(repoList.map((r) => [r.worktree, shownFiles.filter((f) => f.repo === r.worktree)]));
5639
+ const filesByRepo = new Map(repoList.map((r) => [r.id, shownFiles.filter((f) => f.repo === r.id)]));
5638
5640
  const commentsByFile = (() => {
5639
5641
  const map = /* @__PURE__ */ new Map();
5640
5642
  for (const c of comments) {
@@ -5643,12 +5645,8 @@ var diff_view_default = _template("d", (input) => {
5643
5645
  }
5644
5646
  return map;
5645
5647
  })();
5646
- const defaultBranches = [...new Set(repoList.map((r) => r.default_branch).filter(Boolean))];
5647
5648
  const sendPick = (input.sessions || []).some((s) => s.id === sendTarget) ? sendTarget : input.sessions?.[0]?.id ?? null;
5648
- const setBase = _resume((ref) => {
5649
- baseRef = ref;
5650
- load(ref);
5651
- }, "d8", $scope0_id);
5649
+ const localDiffers = _resume((repo) => !!repo.pr && !!repo.localHead && (repo.localHead !== repo.head || repo.branch !== repo.pr.headRefName), "d7");
5652
5650
  const toggleViewed = _resume((key) => {
5653
5651
  const next = new Set(viewed);
5654
5652
  const isViewed = !next.has(key);
@@ -5666,7 +5664,7 @@ var diff_view_default = _template("d", (input) => {
5666
5664
  }).catch(() => {
5667
5665
  error = "Saving viewed state failed — it may reset on reload.";
5668
5666
  });
5669
- }, "d9", $scope0_id);
5667
+ }, "d8", $scope0_id);
5670
5668
  const saveComment = _resume(async (file, text) => {
5671
5669
  const range = draft;
5672
5670
  draft = null;
@@ -5688,11 +5686,11 @@ var diff_view_default = _template("d", (input) => {
5688
5686
  } catch (err) {
5689
5687
  error = String(err.message || err);
5690
5688
  }
5691
- }, "d10", $scope0_id);
5689
+ }, "d9", $scope0_id);
5692
5690
  const removeComment = _resume(async (id) => {
5693
5691
  comments = comments.filter((c) => c.id !== id);
5694
5692
  await fetch(`/api/review-comments/${id}`, { method: "DELETE" });
5695
- }, "d11", $scope0_id);
5693
+ }, "d10", $scope0_id);
5696
5694
  const clearComments = _resume(async () => {
5697
5695
  comments = [];
5698
5696
  draft = null;
@@ -5701,7 +5699,7 @@ var diff_view_default = _template("d", (input) => {
5701
5699
  headers: { "content-type": "application/json" },
5702
5700
  body: JSON.stringify({ action: "clear" })
5703
5701
  });
5704
- }, "d12", $scope0_id);
5702
+ }, "d11", $scope0_id);
5705
5703
  const copyMarkdown = _resume(async () => {
5706
5704
  if (!await copyText(commentMarkdown(comments, repos))) {
5707
5705
  error = "Copy failed — clipboard needs HTTPS on this browser.";
@@ -5712,7 +5710,7 @@ var diff_view_default = _template("d", (input) => {
5712
5710
  copied = false;
5713
5711
  }, 1500);
5714
5712
  await clearComments();
5715
- }, "d13", $scope0_id);
5713
+ }, "d12", $scope0_id);
5716
5714
  const sendTo = _resume(async (sessionId) => {
5717
5715
  if (!sessionId) return;
5718
5716
  sending = true;
@@ -5732,57 +5730,57 @@ var diff_view_default = _template("d", (input) => {
5732
5730
  error = String(err.message || err);
5733
5731
  sending = false;
5734
5732
  }
5735
- }, "d14", $scope0_id);
5733
+ }, "d13", $scope0_id);
5736
5734
  const jumpTo = _resume((key) => {
5737
5735
  const el = ((scrollEl) => scrollEl())(_el_read_error).querySelector(`details[data-key="${CSS.escape(key)}"]`);
5738
5736
  if (el) {
5739
5737
  el.open = true;
5740
5738
  el.scrollIntoView({ block: "start" });
5741
5739
  }
5742
- }, "d15", $scope0_id);
5740
+ }, "d14", $scope0_id);
5743
5741
  _html(`<div class=diff-view><aside class=${!treeOpen ? "\"tree closed\"" : "tree"}>`);
5744
- _for_of(repoList.filter((r) => filesByRepo.get(r.worktree)?.length), (repo) => {
5742
+ _for_of(repoList.filter((r) => filesByRepo.get(r.id)?.length), (repo) => {
5745
5743
  const $scope5_id = _scope_id();
5746
- _html(`<div class=tree-repo><button class=${collapsedRepos.has(repo.worktree) ? "\"tree-repo-name mono closed\"" : "\"tree-repo-name mono\""}><span class=tree-chev>${collapsedRepos.has(repo.worktree) ? "▸" : "▾"}${_el_resume($scope5_id, "b")}</span><!>${_escape(repo.worktree)}${_el_resume($scope5_id, "c")}</button>${_el_resume($scope5_id, "a")}`);
5744
+ _html(`<div class=tree-repo><button class=${collapsedRepos.has(repo.id) ? "\"tree-repo-name mono closed\"" : "\"tree-repo-name mono\""}><span class=tree-chev>${collapsedRepos.has(repo.id) ? "▸" : "▾"}${_el_resume($scope5_id, "b")}</span><!>${repo.pr ? `${_escape(`${repo.worktree}`)} #${_escape(`${repo.pr.number}`)}` : _escape(repo.worktree)}${_el_resume($scope5_id, "c")}</button>${_el_resume($scope5_id, "a")}`);
5747
5745
  _if(() => {
5748
- if (!collapsedRepos.has(repo.worktree)) {
5746
+ if (!collapsedRepos.has(repo.id)) {
5749
5747
  const $scope6_id = _scope_id();
5750
5748
  const $if_content4__dirKey__closures = /* @__PURE__ */ new Set();
5751
- const repoRows = treeRows(filesByRepo.get(repo.worktree) || []);
5752
- const dirKey = _resume((path) => `${repo.worktree}/${path}`, "d16", $scope6_id);
5753
- const rowDir = _resume((row) => row.kind === "dir" ? row.path : row.file.path.includes("/") ? row.file.path.slice(0, row.file.path.lastIndexOf("/")) : "", "d17");
5749
+ const repoRows = treeRows(filesByRepo.get(repo.id) || []);
5750
+ const dirKey = _resume((path) => `${repo.id}/${path}`, "d15", $scope6_id);
5751
+ const rowDir = _resume((row) => row.kind === "dir" ? row.path : row.file.path.includes("/") ? row.file.path.slice(0, row.file.path.lastIndexOf("/")) : "", "d16");
5754
5752
  const hiddenByCollapse = _resume((row) => {
5755
5753
  const dir = rowDir(row);
5756
5754
  for (const collapsed of collapsedDirs) {
5757
5755
  const [wt, path] = [collapsed.slice(0, collapsed.indexOf("/")), collapsed.slice(collapsed.indexOf("/") + 1)];
5758
- if (wt !== repo.worktree) continue;
5756
+ if (wt !== repo.id) continue;
5759
5757
  if (row.kind === "dir" && row.path === path) continue;
5760
5758
  if (dir === path || dir.startsWith(`${path}/`)) return true;
5761
5759
  }
5762
5760
  return false;
5763
- }, "d18", $scope6_id);
5761
+ }, "d17", $scope6_id);
5764
5762
  _for_of(repoRows, (row) => {
5765
5763
  const $scope7_id = _scope_id();
5766
5764
  _if(() => {
5767
5765
  if (!hiddenByCollapse(row) && row.kind === "dir") {
5768
5766
  const $scope22_id = _scope_id();
5769
5767
  _html(`<button class=${collapsedDirs.has(dirKey(row.path)) ? "\"tree-dir dim mono closed\"" : "\"tree-dir dim mono\""}${_attr("title", row.path)}${_attr_style(`padding-left:${.35 + row.depth * .75}rem`)}><span class=tree-chev>${collapsedDirs.has(dirKey(row.path)) ? "▸" : "▾"}${_el_resume($scope22_id, "b")}</span>${_escape(row.label)}${_el_resume($scope22_id, "c")}/</button>${_el_resume($scope22_id, "a")}`);
5770
- _script($scope22_id, "d26");
5771
- _subscribe($if_content4__dirKey__closures, _subscribe($collapsedDirs__closures, writeScope($scope22_id, { Cc9: 1 })));
5768
+ _script($scope22_id, "d23");
5769
+ _subscribe($if_content4__dirKey__closures, _subscribe($collapsedDirs__closures, writeScope($scope22_id, { Cc7: 1 })));
5772
5770
  return 0;
5773
5771
  } else if (!hiddenByCollapse(row) && row.kind !== "dir") {
5774
5772
  const $scope8_id = _scope_id();
5775
5773
  _html(`<button class=${viewed.has(row.file.key) ? "\"tree-file mono seen\"" : "\"tree-file mono\""}${_attr("title", row.file.path)}${_attr_style(`padding-left:${.5 + row.depth * .75}rem`)}><span${_attr_class(["status", row.file.status])}>${_escape(row.file.status)}${_el_resume($scope8_id, "c")}</span>${_el_resume($scope8_id, "b")}<span class=tree-file-name>${_escape(row.name)}${_el_resume($scope8_id, "d")}</span>`);
5776
5774
  _if(() => {
5777
5775
  if (commentsByFile.has(row.file.key)) {
5778
- const $scope25_id = _scope_id();
5776
+ const $scope27_id = _scope_id();
5779
5777
  _html("<span class=tree-note>💬</span>");
5780
- writeScope($scope25_id, {});
5778
+ writeScope($scope27_id, {});
5781
5779
  return 0;
5782
5780
  }
5783
5781
  }, $scope8_id, "e", 1, 1, 1, 0, 1);
5784
5782
  _html(`</button>${_el_resume($scope8_id, "a")}`);
5785
- _script($scope8_id, "d27");
5783
+ _script($scope8_id, "d24");
5786
5784
  _subscribe($commentsByFile__closures, _subscribe($viewed__closures, writeScope($scope8_id, {})));
5787
5785
  return 1;
5788
5786
  }
@@ -5808,9 +5806,9 @@ var diff_view_default = _template("d", (input) => {
5808
5806
  }
5809
5807
  }, $scope5_id, "d");
5810
5808
  _html("</div>");
5811
- _script($scope5_id, "d28");
5812
- writeScope($scope5_id, { g: repo?.worktree });
5813
- }, "worktree", $scope0_id, "a", 1, 1, 1, "</aside>", 1);
5809
+ _script($scope5_id, "d25");
5810
+ writeScope($scope5_id, { g: repo?.id });
5811
+ }, "id", $scope0_id, "a", 1, 1, 1, "</aside>", 1);
5814
5812
  _html("<div class=diff-col><div class=diff-head>");
5815
5813
  _if(() => {
5816
5814
  if (input.onExit) {
@@ -5818,7 +5816,7 @@ var diff_view_default = _template("d", (input) => {
5818
5816
  _html("<button class=icon-btn title=\"Back to terminal\">");
5819
5817
  icon_back_default({ size: 14 });
5820
5818
  _html(`</button>${_el_resume($scope1_id, "a")}`);
5821
- _script($scope1_id, "d29");
5819
+ _script($scope1_id, "d26");
5822
5820
  writeScope($scope1_id, { _: _scope_with_id($scope0_id) });
5823
5821
  return 0;
5824
5822
  }
@@ -5832,7 +5830,7 @@ var diff_view_default = _template("d", (input) => {
5832
5830
  if ((input.sessions || []).length === 1) {
5833
5831
  const $scope3_id = _scope_id();
5834
5832
  _html(`<button class="btn primary"${_attr("disabled", sending)}>${sending ? "Sending…" : `Send to ${_escape(`${input.sessions[0].title || input.sessions[0].agent}`)}`}${_el_resume($scope3_id, "b")}</button>${_el_resume($scope3_id, "a")}`);
5835
- _script($scope3_id, "d30");
5833
+ _script($scope3_id, "d27");
5836
5834
  _subscribe($sending__closures, _subscribe($si__input_sessions_0_agent && $input_sessions_0_agent__closures, _subscribe($si__input_sessions_0_title && $input_sessions_0_title__closures, writeScope($scope3_id, { _: _scope_with_id($scope2_id) }))));
5837
5835
  return 0;
5838
5836
  } else if ((input.sessions || []).length) {
@@ -5843,30 +5841,30 @@ var diff_view_default = _template("d", (input) => {
5843
5841
  }, "d0", $scope4_id), () => {
5844
5842
  _html(`<select aria-label="Send comments to session"${_attr("disabled", sending)}>`);
5845
5843
  _for_of(input.sessions, (s) => {
5846
- const $scope26_id = _scope_id();
5847
- _html(`<option${_attr_option_value(s.id)}>${_escape(s.title || s.agent)}${_el_resume($scope26_id, "b", $sg__input_sessions)}</option>${_el_resume($scope26_id, "a", $sg__input_sessions)}`);
5848
- $si__input_sessions && writeScope($scope26_id, {});
5844
+ const $scope28_id = _scope_id();
5845
+ _html(`<option${_attr_option_value(s.id)}>${_escape(s.title || s.agent)}${_el_resume($scope28_id, "b", $sg__input_sessions)}</option>${_el_resume($scope28_id, "a", $sg__input_sessions)}`);
5846
+ $si__input_sessions && writeScope($scope28_id, {});
5849
5847
  }, "id", $scope4_id, "c", $sg__input_sessions, 1, $sg__input_sessions, "</select>", 1);
5850
5848
  });
5851
5849
  _html("</span>");
5852
- _script($scope4_id, "d31");
5850
+ _script($scope4_id, "d28");
5853
5851
  _subscribe($sendTarget__closures, _subscribe($sending__closures, _subscribe($si__input_sessions && $input_sessions__closures, writeScope($scope4_id, {
5854
5852
  _: _scope_with_id($scope2_id),
5855
- Cc5: 1
5853
+ Cc3: 1
5856
5854
  }))));
5857
5855
  return 1;
5858
5856
  }
5859
5857
  }, $scope2_id, "c", $sg__input_sessions, $sg__input_sessions, $sg__input_sessions, 0, 1);
5860
- _script($scope2_id, "d32");
5858
+ _script($scope2_id, "d29");
5861
5859
  writeScope($scope2_id, {});
5862
5860
  return 0;
5863
5861
  }
5864
5862
  }, $scope0_id, "g", 1, 1, 1, "</span>");
5865
5863
  _if(() => {
5866
5864
  if (loading) {
5867
- const $scope27_id = _scope_id();
5865
+ const $scope29_id = _scope_id();
5868
5866
  _html("<span class=\"refresh-chip dim\"><span class=spin></span>Refreshing…</span>");
5869
- writeScope($scope27_id, {});
5867
+ writeScope($scope29_id, {});
5870
5868
  return 0;
5871
5869
  }
5872
5870
  }, $scope0_id, "h", 1, 1, 1, 0, 1);
@@ -5876,50 +5874,49 @@ var diff_view_default = _template("d", (input) => {
5876
5874
  icon_refresh_default({ size: 14 });
5877
5875
  _html(`</button>${_el_resume($scope0_id, "k")}<button class="btn files-btn" title="Jump to file">Files (<!>${_escape(shownFiles.length)}${_el_resume($scope0_id, "n")})</button>${_el_resume($scope0_id, "m")}<input${_attr_input_value($scope0_id, "o", fileFilter, _resume((_new_fileFilter) => {
5878
5876
  fileFilter = _new_fileFilter;
5879
- }, "d1", $scope0_id))} class=file-filter placeholder="Filter files… (globs, !excludes)" aria-label="Filter files">${_el_resume($scope0_id, "o")}`);
5880
- _attr_select_value($scope0_id, "p", baseRef, _resume(function(v) {
5881
- if (v === CUSTOM) askBase = true;
5882
- else setBase(v);
5883
- }, "d2", $scope0_id), () => {
5884
- _html(`<select class=base-pick aria-label="Compare against"><option${_attr_option_value("")}>vs base</option>`);
5885
- _for_of(defaultBranches, (branch) => {
5886
- const $scope28_id = _scope_id();
5887
- _html(`<option${_attr_option_value(branch)}>vs <!>${_escape(branch)}${_el_resume($scope28_id, "b")}</option>${_el_resume($scope28_id, "a")}`);
5888
- writeScope($scope28_id, {});
5889
- }, (b) => b, $scope0_id, "q", 1, 1, 1, 0, 1);
5890
- _if(() => {
5891
- if (baseRef && !defaultBranches.includes(baseRef)) {
5892
- const $scope19_id = _scope_id();
5893
- _html(`<option${_attr_option_value(baseRef)}>vs <!>${_escape(baseRef)}${_el_resume($scope19_id, "b")}</option>${_el_resume($scope19_id, "a")}`);
5894
- writeScope($scope19_id, {});
5895
- return 0;
5896
- }
5897
- }, $scope0_id, "r", 1, 1, 1, 0, 1);
5898
- _html(`<option${_attr_option_value(CUSTOM)}>custom…</option></select>`);
5899
- });
5900
- _html(`${_el_resume($scope0_id, "p")}</div><div class=diff-main>`);
5877
+ }, "d1", $scope0_id))} class=file-filter placeholder="Filter files… (globs, !excludes)" aria-label="Filter files">${_el_resume($scope0_id, "o")}<label class=two-dot title="Include uncommitted and untracked files in the review. Off by default — the review is commit-to-commit."><input${_attr_input_checked($scope0_id, "p", uncommitted)} type=checkbox>${_el_resume($scope0_id, "p")} include uncommitted</label></div><div class=diff-main>`);
5901
5878
  _if(() => {
5902
5879
  if (error) {
5903
5880
  const $scope17_id = _scope_id();
5904
5881
  _html(`<p class=toast-error>${_escape(error)}${_el_resume($scope17_id, "a")} <button>×</button>${_el_resume($scope17_id, "b")}</p>`);
5905
- _script($scope17_id, "d33");
5882
+ _script($scope17_id, "d30");
5906
5883
  writeScope($scope17_id, {});
5907
5884
  return 0;
5908
5885
  }
5909
- }, $scope0_id, "u", 1, 1, 1, 0, 1);
5886
+ }, $scope0_id, "r", 1, 1, 1, 0, 1);
5887
+ _for_of(repoList.filter((r) => r.baseMissing), (repo) => {
5888
+ const $scope19_id = _scope_id();
5889
+ _html(`<p class=toast-error>${_escape(repo.worktree)}${_el_resume($scope19_id, "a")}: base <!>${_escape(repo.base)}${_el_resume($scope19_id, "b")} not found<!>${uncommitted ? " — showing uncommitted changes only" : ""}${_el_resume($scope19_id, "c")}.</p>`);
5890
+ writeScope($scope19_id, {});
5891
+ }, "id", $scope0_id, "s", 1, 1, 1, 0, 1);
5892
+ _for_of(repoList.filter((r) => r.contained && r.files.length), (repo) => {
5893
+ const $scope20_id = _scope_id();
5894
+ _html(`<p class=dim>${_escape(repo.worktree)}${_el_resume($scope20_id, "a")}<!>${repo.pr ? ` #${_escape(`${repo.pr.number}`)}` : ""}${_el_resume($scope20_id, "b")}: already in <!>${_escape(repo.base)}${_el_resume($scope20_id, "c")}<!>${uncommitted ? " — showing uncommitted changes only" : ""}${_el_resume($scope20_id, "d")}.</p>`);
5895
+ writeScope($scope20_id, {});
5896
+ }, "id", $scope0_id, "t", 1, 1, 1, 0, 1);
5897
+ _for_of(repoList.filter((r) => r.githubDrift), (repo) => {
5898
+ const $scope30_id = _scope_id();
5899
+ _html(`<p class=toast-error>${_escape(repo.worktree)}${_el_resume($scope30_id, "a")} #<!>${_escape(repo.pr.number)}${_el_resume($scope30_id, "b")}: origin/<!>${_escape(repo.pr.headRefName)}${_el_resume($scope30_id, "c")} is <!>${_escape(shortSha(repo.head))}${_el_resume($scope30_id, "d")}, GitHub last saw <!>${_escape(shortSha(repo.pr.headRefOid))}${_el_resume($scope30_id, "e")}.</p>`);
5900
+ writeScope($scope30_id, {});
5901
+ }, "id", $scope0_id, "u", 1, 1, 1, 0, 1);
5902
+ _for_of(repoList.filter(localDiffers), (repo) => {
5903
+ const $scope21_id = _scope_id();
5904
+ _html(`<p class=dim>${_escape(repo.worktree)}${_el_resume($scope21_id, "a")} #<!>${_escape(repo.pr.number)}${_el_resume($scope21_id, "b")}: this view is <!>${_escape(shortSha(repo.head))}${_el_resume($scope21_id, "c")} (<!>${_escape(repo.pr.headRefName)}${_el_resume($scope21_id, "d")}). Local is <!>${_escape(shortSha(repo.localHead))}${_el_resume($scope21_id, "e")} (<!>${_escape(repo.branch)}${_el_resume($scope21_id, "f")}<!>${uncommitted ? ", plus uncommitted" : ""}${_el_resume($scope21_id, "g")}).</p>`);
5905
+ writeScope($scope21_id, {});
5906
+ }, "id", $scope0_id, "v", 1, 1, 1, 0, 1);
5910
5907
  _if(() => {
5911
5908
  if (!repos && loading) {
5912
- const $scope29_id = _scope_id();
5909
+ const $scope31_id = _scope_id();
5913
5910
  _html("<div class=diff-skel><p class=dim><span class=spin></span>Loading diff…</p><div class=skel-row></div><div class=skel-row></div><div class=skel-row></div></div>");
5914
- writeScope($scope29_id, {});
5911
+ writeScope($scope31_id, {});
5915
5912
  return 0;
5916
5913
  }
5917
- }, $scope0_id, "v", 1, 1, 1, 0, 1);
5914
+ }, $scope0_id, "w", 1, 1, 1, 0, 1);
5918
5915
  _if(() => {
5919
5916
  if (repos && !allFiles.length) {
5920
- const $scope20_id = _scope_id();
5921
- _html(`<p class="dim center-pad">No changes vs <!>${_escape(baseRef || "base")}${_el_resume($scope20_id, "a")}.</p>`);
5922
- writeScope($scope20_id, {});
5917
+ const $scope25_id = _scope_id();
5918
+ _html(`<p class="dim center-pad">${repoList.some((r) => r.pr) ? "No file changes on these PRs." : "No changes vs base."}${_el_resume($scope25_id, "a")}</p>`);
5919
+ writeScope($scope25_id, {});
5923
5920
  return 0;
5924
5921
  } else if (repos && !shownFiles.length) {
5925
5922
  const $scope18_id = _scope_id();
@@ -5927,29 +5924,109 @@ var diff_view_default = _template("d", (input) => {
5927
5924
  writeScope($scope18_id, {});
5928
5925
  return 1;
5929
5926
  }
5930
- }, $scope0_id, "w", 1, 1, 1, 0, 1);
5927
+ }, $scope0_id, "x", 1, 1, 1, 0, 1);
5931
5928
  _for_of(repoList, (repo) => {
5932
5929
  const $scope9_id = _scope_id();
5933
- const $for_content3__repo_worktree__closures = /* @__PURE__ */ new Set();
5934
- const $for_content3__repo_full_name__closures = /* @__PURE__ */ new Set();
5930
+ const $for_content3__repo_pr_number__closures = /* @__PURE__ */ new Set();
5931
+ const $for_content3__repo_pr_url__closures = /* @__PURE__ */ new Set();
5932
+ const $for_content3__repo_pr_title__closures = /* @__PURE__ */ new Set();
5933
+ const $for_content3__repo_pr_headRefName__closures = /* @__PURE__ */ new Set();
5934
+ const $for_content3__repo_head__closures = /* @__PURE__ */ new Set();
5935
5935
  const $for_content3__repo_base__closures = /* @__PURE__ */ new Set();
5936
+ const $for_content3__repo_baseSha__closures = /* @__PURE__ */ new Set();
5937
+ const $for_content3__repo_full_name__closures = /* @__PURE__ */ new Set();
5938
+ const $for_content3__repo_branch__closures = /* @__PURE__ */ new Set();
5939
+ const $for_content3__repo_localHead__closures = /* @__PURE__ */ new Set();
5940
+ const $for_content3__repo__closures = /* @__PURE__ */ new Set();
5941
+ const $for_content3__repo_id__closures = /* @__PURE__ */ new Set();
5942
+ const $for_content3__repo_pr__closures = /* @__PURE__ */ new Set();
5943
+ const $for_content3__repo_worktree__closures = /* @__PURE__ */ new Set();
5936
5944
  const $for_content3__repoFiles__closures = /* @__PURE__ */ new Set();
5937
- const repoFiles = filesByRepo.get(repo.worktree) || [];
5945
+ const repoFiles = filesByRepo.get(repo.id) || [];
5938
5946
  _if(() => {
5939
5947
  if (repoFiles.length) {
5940
5948
  const $scope10_id = _scope_id();
5941
- _html("<section class=repo-section>");
5949
+ _html(`<section class=repo-section${repo.pr ? _attr("data-pr", `${repo.worktree}/${repo.pr.number}`) : ""}>`);
5942
5950
  _if(() => {
5943
5951
  if (repoList.length > 1) {
5944
5952
  const $scope23_id = _scope_id();
5945
- _html(`<button class=${collapsedRepos.has(repo.worktree) ? "\"repo-sep closed\"" : "repo-sep"}><span class=tree-chev>${collapsedRepos.has(repo.worktree) ? "▸" : "▾"}${_el_resume($scope23_id, "b")}</span><span class=mono>${_escape(repo.worktree)}${_el_resume($scope23_id, "c")}</span><span class=dim>${_escape(repo.full_name)}${_el_resume($scope23_id, "d")}</span><span class=chip>vs <!>${_escape(repo.base)}${_el_resume($scope23_id, "e")}</span></button>${_el_resume($scope23_id, "a")}`);
5946
- _script($scope23_id, "d34");
5947
- _subscribe($for_content3__repo_base__closures, _subscribe($for_content3__repo_full_name__closures, _subscribe($for_content3__repo_worktree__closures, _subscribe($collapsedRepos__closures, writeScope($scope23_id, { Cca: 1 })))));
5953
+ _html(`<button class=${collapsedRepos.has(repo.id) ? "\"repo-sep closed\"" : "repo-sep"}><span class=tree-chev>${collapsedRepos.has(repo.id) ? "▸" : "▾"}${_el_resume($scope23_id, "b")}</span><span class=mono>${_escape(repo.worktree)}${_el_resume($scope23_id, "c")}</span>`);
5954
+ _if(() => {
5955
+ if (repo.pr) {
5956
+ const $scope32_id = _scope_id();
5957
+ _html(`<a class="chip pr"${_attr("href", `${repo.pr.url}/files`)} target=_blank rel=noopener${_attr("title", `${repo.pr.title} — ${repo.pr.headRefName} ${repo.head} vs ${repo.base} ${repo.baseSha}`)}>#<!>${_escape(repo.pr.number)}${_el_resume($scope32_id, "b")}</a>${_el_resume($scope32_id, "a")}<span class=dim>${_escape(repo.pr.title)}${_el_resume($scope32_id, "c")}</span>`);
5958
+ _script($scope32_id, "d31");
5959
+ _subscribe($for_content3__repo_baseSha__closures, _subscribe($for_content3__repo_base__closures, _subscribe($for_content3__repo_head__closures, _subscribe($for_content3__repo_pr_headRefName__closures, _subscribe($for_content3__repo_pr_title__closures, _subscribe($for_content3__repo_pr_url__closures, _subscribe($for_content3__repo_pr_number__closures, writeScope($scope32_id, {
5960
+ Ca1: 2,
5961
+ Ca2: 2,
5962
+ Ca3: 2
5963
+ }))))))));
5964
+ return 0;
5965
+ } else {
5966
+ const $scope33_id = _scope_id();
5967
+ _html(`<span class=dim>${_escape(repo.full_name)}${_el_resume($scope33_id, "a")}</span>`);
5968
+ _subscribe($for_content3__repo_full_name__closures, writeScope($scope33_id, {}));
5969
+ return 1;
5970
+ }
5971
+ }, $scope23_id, "d");
5972
+ _html(`<span class="chip lane"${_attr("title", `${repo.pr ? "PR head" : "checked out"} ${repo.pr ? repo.pr.headRefName : repo.branch} ${repo.head}`)}>on <!>${repo.pr ? _escape(repo.pr.headRefName) : _escape(repo.branch)}${_el_resume($scope23_id, "f")}</span>${_el_resume($scope23_id, "e")}<span class=chip${_attr("title", `${repo.base} ${repo.baseSha}`)}>vs <!>${_escape(repo.base)}${_el_resume($scope23_id, "h")}</span>${_el_resume($scope23_id, "g")}`);
5973
+ _if(() => {
5974
+ if (localDiffers(repo)) {
5975
+ const $scope34_id = _scope_id();
5976
+ _html(`<span class=chip${_attr("title", `checked out ${repo.branch} ${repo.localHead}`)}>local <!>${_escape(repo.branch)}${_el_resume($scope34_id, "b")}</span>${_el_resume($scope34_id, "a")}`);
5977
+ _subscribe($for_content3__repo_localHead__closures, _subscribe($for_content3__repo_branch__closures, writeScope($scope34_id, { Ca5: 2 })));
5978
+ return 0;
5979
+ }
5980
+ }, $scope23_id, "i", 1, 1, 1, 0, 1);
5981
+ _html(`</button>${_el_resume($scope23_id, "a")}`);
5982
+ _script($scope23_id, "d32");
5983
+ _subscribe($for_content3__repo_branch__closures, _subscribe($for_content3__repo_baseSha__closures, _subscribe($for_content3__repo_base__closures, _subscribe($for_content3__repo_head__closures, _subscribe($for_content3__repo_worktree__closures, _subscribe($for_content3__repo_pr__closures, _subscribe($for_content3__repo_id__closures, _subscribe($for_content3__repo__closures, _subscribe($collapsedRepos__closures, writeScope($scope23_id, { Cc8: 1 }))))))))));
5948
5984
  return 0;
5985
+ } else {
5986
+ const $scope26_id = _scope_id();
5987
+ _html("<p class=repo-meta>");
5988
+ _if(() => {
5989
+ if (repo.pr) {
5990
+ const $scope35_id = _scope_id();
5991
+ _html(`<a class="chip pr"${_attr("href", `${repo.pr.url}/files`)} target=_blank rel=noopener${_attr("title", `${repo.pr.title} — ${repo.pr.headRefName} ${repo.head} vs ${repo.base} ${repo.baseSha}`)}>#<!>${_escape(repo.pr.number)}${_el_resume($scope35_id, "b")}</a>${_el_resume($scope35_id, "a")}<span class=dim>${_escape(repo.pr.title)}${_el_resume($scope35_id, "c")}</span>`);
5992
+ _subscribe($for_content3__repo_baseSha__closures, _subscribe($for_content3__repo_base__closures, _subscribe($for_content3__repo_head__closures, _subscribe($for_content3__repo_pr_headRefName__closures, _subscribe($for_content3__repo_pr_title__closures, _subscribe($for_content3__repo_pr_url__closures, _subscribe($for_content3__repo_pr_number__closures, writeScope($scope35_id, {
5993
+ Cx: 1,
5994
+ Cy: 1,
5995
+ Cz: 1,
5996
+ Ca0: 1,
5997
+ Ca1: 3,
5998
+ Ca2: 3,
5999
+ Ca3: 3
6000
+ }))))))));
6001
+ return 0;
6002
+ }
6003
+ }, $scope26_id, "a");
6004
+ _html(`<span class="chip lane"${_attr("title", `${repo.pr ? "PR head" : "checked out"} ${repo.pr ? repo.pr.headRefName : repo.branch} ${repo.head}`)}>on <!>${repo.pr ? _escape(repo.pr.headRefName) : _escape(repo.branch)}${_el_resume($scope26_id, "c")}</span>${_el_resume($scope26_id, "b")}<span class=chip${_attr("title", `${repo.base} ${repo.baseSha}`)}>vs <!>${_escape(repo.base)}${_el_resume($scope26_id, "e")}</span>${_el_resume($scope26_id, "d")}`);
6005
+ _if(() => {
6006
+ if (localDiffers(repo)) {
6007
+ const $scope36_id = _scope_id();
6008
+ _html(`<span class=chip${_attr("title", `checked out ${repo.branch} ${repo.localHead}`)}>local <!>${_escape(repo.branch)}${_el_resume($scope36_id, "b")}</span>${_el_resume($scope36_id, "a")}`);
6009
+ _subscribe($for_content3__repo_localHead__closures, _subscribe($for_content3__repo_branch__closures, writeScope($scope36_id, {
6010
+ Ca5: 3,
6011
+ Ca6: 1
6012
+ })));
6013
+ return 0;
6014
+ }
6015
+ }, $scope26_id, "f", 1, 1, 1, 0, 1);
6016
+ _html("</p>");
6017
+ _subscribe($for_content3__repo_branch__closures, _subscribe($for_content3__repo_baseSha__closures, _subscribe($for_content3__repo_base__closures, _subscribe($for_content3__repo_head__closures, _subscribe($for_content3__repo_pr__closures, _subscribe($for_content3__repo__closures, writeScope($scope26_id, {
6018
+ Ct: 1,
6019
+ Cv: 1,
6020
+ Ca1: 1,
6021
+ Ca2: 1,
6022
+ Ca3: 1,
6023
+ Ca5: 1
6024
+ })))))));
6025
+ return 1;
5949
6026
  }
5950
- }, $scope10_id, "a", 1, 1, 1, 0, 1);
6027
+ }, $scope10_id, "b", 1, 1, 1, 0, 1);
5951
6028
  _if(() => {
5952
- if (!(repoList.length > 1 && collapsedRepos.has(repo.worktree))) {
6029
+ if (!(repoList.length > 1 && collapsedRepos.has(repo.id))) {
5953
6030
  const $scope11_id = _scope_id();
5954
6031
  _html("<div class=files>");
5955
6032
  _for_of(repoFiles, (file) => {
@@ -5962,126 +6039,134 @@ var diff_view_default = _template("d", (input) => {
5962
6039
  _html(`<details${_attr_details_or_dialog_open($scope12_id, "a", expanded)} class=file${_attr("data-key", file.key)}><summary><span${_attr_class(["status", file.status])}>${_escape(file.status)}${_el_resume($scope12_id, "c")}</span>${_el_resume($scope12_id, "b")}<span class=path${_attr("title", file.path)}><bdi>${_escape(file.path)}${_el_resume($scope12_id, "e")}</bdi></span>${_el_resume($scope12_id, "d")}`);
5963
6040
  _if(() => {
5964
6041
  if (fileComments.length) {
5965
- const $scope30_id = _scope_id();
5966
- _html(`<span class=chip>💬 <!>${_escape(fileComments.length)}${_el_resume($scope30_id, "a")}</span>`);
5967
- writeScope($scope30_id, {});
6042
+ const $scope37_id = _scope_id();
6043
+ _html(`<span class=chip>💬 <!>${_escape(fileComments.length)}${_el_resume($scope37_id, "a")}</span>`);
6044
+ writeScope($scope37_id, {});
5968
6045
  return 0;
5969
6046
  }
5970
6047
  }, $scope12_id, "f", 1, 1, 1, 0, 1);
5971
6048
  _html(`<label class=viewed><input${_attr_input_checked($scope12_id, "h", viewed.has(file.key), _resume(function() {
5972
6049
  toggleViewed(file.key);
5973
- }, "d3", $scope12_id))} type=checkbox>${_el_resume($scope12_id, "h")}<span class=dim>viewed</span></label>${_el_resume($scope12_id, "g")}</summary>`);
6050
+ }, "d2", $scope12_id))} type=checkbox>${_el_resume($scope12_id, "h")}<span class=dim>viewed</span></label>${_el_resume($scope12_id, "g")}</summary>`);
5974
6051
  _if(() => {
5975
6052
  if (file.binary) {
5976
- const $scope31_id = _scope_id();
6053
+ const $scope38_id = _scope_id();
5977
6054
  _html("<p class=\"dim diff-pad\">binary file</p>");
5978
- writeScope($scope31_id, {});
6055
+ writeScope($scope38_id, {});
5979
6056
  return 0;
5980
6057
  } else if (file.tooLarge) {
5981
- const $scope32_id = _scope_id();
6058
+ const $scope39_id = _scope_id();
5982
6059
  _html("<p class=\"dim diff-pad\">file too large to display</p>");
5983
- writeScope($scope32_id, {});
6060
+ writeScope($scope39_id, {});
5984
6061
  return 1;
6062
+ } else if (file.old == null && file.new == null) {
6063
+ const $scope40_id = _scope_id();
6064
+ _html("<p class=\"dim diff-pad\">file could not be read</p>");
6065
+ writeScope($scope40_id, {});
6066
+ return 2;
5985
6067
  } else if (expanded) {
5986
6068
  const $scope15_id = _scope_id();
5987
6069
  _if(() => {
5988
6070
  {
5989
6071
  const $scope24_id = _scope_id();
5990
6072
  _html(`<div class=diff-wait></div>${_el_resume($scope24_id, "a")}`);
5991
- _script($scope24_id, "d35");
6073
+ _script($scope24_id, "d33");
5992
6074
  writeScope($scope24_id, {});
5993
6075
  return 0;
5994
6076
  }
5995
6077
  }, $scope15_id, "a", 1, 1, 1, 0, 1);
5996
6078
  writeScope($scope15_id, {});
5997
- return 2;
6079
+ return 3;
5998
6080
  }
5999
6081
  }, $scope12_id, "i");
6000
6082
  _html(`</details>${_el_resume($scope12_id, "a")}`);
6001
- _script($scope12_id, "d36");
6083
+ _script($scope12_id, "d34");
6002
6084
  _subscribe($toggleViewed2__closures, _subscribe($commentsByFile__closures, _subscribe($viewed__closures, writeScope($scope12_id, {
6003
6085
  k: file,
6004
6086
  l: file?.key,
6005
6087
  r: file?.binary,
6006
6088
  s: file?.tooLarge,
6007
- t: fileComments,
6008
- u: fileComments.length,
6009
- v: expanded,
6010
- x: $for_content4__file__closures,
6011
- y: $for_content4__file_key__closures,
6012
- z: $for_content4__fileComments__closures,
6013
- Cc0: 1,
6014
- Ccg: 1
6089
+ t: file?.old,
6090
+ u: file?.new,
6091
+ v: fileComments,
6092
+ w: fileComments.length,
6093
+ x: expanded,
6094
+ z: $for_content4__file__closures,
6095
+ a0: $for_content4__file_key__closures,
6096
+ a1: $for_content4__fileComments__closures,
6097
+ Cby: 1,
6098
+ Ccf: 1
6015
6099
  }))));
6016
6100
  }, "key", $scope11_id, "a", 1, 1, 1, "</div>", 1);
6017
6101
  _subscribe($for_content3__repoFiles__closures, writeScope($scope11_id, {}));
6018
6102
  return 0;
6019
6103
  }
6020
- }, $scope10_id, "b", 1, 1, 1, 0, 1);
6021
- _html("</section>");
6104
+ }, $scope10_id, "c", 1, 1, 1, 0, 1);
6105
+ _html(`</section>${_el_resume($scope10_id, "a")}`);
6022
6106
  _subscribe($repoList_length__closures, _subscribe($collapsedRepos__closures, writeScope($scope10_id, {})));
6023
6107
  return 0;
6024
6108
  }
6025
6109
  }, $scope9_id, "a", 1, 1, 1, 0, 1);
6026
6110
  writeScope($scope9_id, {
6027
- d: repo?.worktree,
6028
- f: repo?.full_name,
6029
- g: repo?.base,
6030
- h: repoFiles,
6031
- j: $for_content3__repo_worktree__closures,
6032
- k: $for_content3__repo_full_name__closures,
6033
- l: $for_content3__repo_base__closures,
6034
- m: $for_content3__repoFiles__closures
6111
+ c: repo,
6112
+ d: repo?.id,
6113
+ f: repo?.pr,
6114
+ g: repo?.worktree,
6115
+ h: repo?.pr?.number,
6116
+ i: repo?.pr?.url,
6117
+ j: repo?.pr?.title,
6118
+ k: repo?.pr?.headRefName,
6119
+ l: repo?.head,
6120
+ m: repo?.base,
6121
+ n: repo?.baseSha,
6122
+ o: repo?.full_name,
6123
+ p: repo?.branch,
6124
+ q: repo?.localHead,
6125
+ r: repoFiles,
6126
+ x: $for_content3__repo_pr_number__closures,
6127
+ y: $for_content3__repo_pr_url__closures,
6128
+ z: $for_content3__repo_pr_title__closures,
6129
+ a0: $for_content3__repo_pr_headRefName__closures,
6130
+ a1: $for_content3__repo_head__closures,
6131
+ a2: $for_content3__repo_base__closures,
6132
+ a3: $for_content3__repo_baseSha__closures,
6133
+ a4: $for_content3__repo_full_name__closures,
6134
+ a5: $for_content3__repo_branch__closures,
6135
+ a6: $for_content3__repo_localHead__closures,
6136
+ t: $for_content3__repo__closures,
6137
+ u: $for_content3__repo_id__closures,
6138
+ v: $for_content3__repo_pr__closures,
6139
+ w: $for_content3__repo_worktree__closures,
6140
+ a7: $for_content3__repoFiles__closures
6035
6141
  });
6036
- }, "worktree", $scope0_id, "x");
6037
- _html(`</div>${_el_resume($scope0_id, "t")}</div>`);
6038
- _if(() => {}, $scope0_id, "y");
6142
+ }, "id", $scope0_id, "y");
6143
+ _html(`</div>${_el_resume($scope0_id, "q")}</div>`);
6144
+ _if(() => {}, $scope0_id, "z");
6039
6145
  _html("</div>");
6040
- _if(() => {
6041
- if (askBase) {
6042
- const $scope21_id = _scope_id();
6043
- _set_serialize_reason(4);
6044
- const $childScope2 = _peek_scope_id();
6045
- prompt_dialog_default({
6046
- label: "Compare against ref",
6047
- placeholder: "branch, tag, or commit",
6048
- value: baseRef,
6049
- submitLabel: "Compare",
6050
- onSubmit: _resume(function(ref) {
6051
- askBase = false;
6052
- setBase(ref);
6053
- }, "d24", $scope21_id),
6054
- onCancel: _resume(function() {
6055
- askBase = false;
6056
- }, "d25", $scope21_id)
6057
- });
6058
- writeScope($scope21_id, { a: _existing_scope($childScope2) });
6059
- return 0;
6060
- }
6061
- }, $scope0_id, "z");
6062
- _script($scope0_id, "d39");
6063
- _script($scope0_id, "d40");
6146
+ _script($scope0_id, "d37");
6147
+ _script($scope0_id, "d38");
6064
6148
  writeScope($scope0_id, {
6065
- a2: input.workspaceId,
6066
- a3: input.active,
6067
- a4: input.sessions,
6068
- a6: input.onSent,
6069
- a7: input.onExit,
6070
- a8: input.sessions?.[0]?.title,
6071
- a9: input.sessions?.[0]?.agent,
6072
- aa: repos,
6073
- ab: comments,
6074
- ad: viewed,
6075
- af: viewed?.size,
6076
- ag: draft,
6077
- ai: error,
6078
- aj: loading,
6079
- al: copied,
6080
- am: sending,
6081
- an: sendTarget,
6082
- ap: fileFilter,
6083
- aq: baseRef,
6084
- as: treeOpen,
6149
+ a2: input.focusPr,
6150
+ a3: input.workspaceId,
6151
+ a4: input.active,
6152
+ a5: input.sessions,
6153
+ a7: input.onSent,
6154
+ a8: input.onExit,
6155
+ a9: input.sessions?.[0]?.title,
6156
+ aa: input.sessions?.[0]?.agent,
6157
+ ab: repos,
6158
+ ac: comments,
6159
+ ae: viewed,
6160
+ ag: viewed?.size,
6161
+ ah: draft,
6162
+ aj: error,
6163
+ ak: loading,
6164
+ am: copied,
6165
+ an: sending,
6166
+ ao: sendTarget,
6167
+ aq: fileFilter,
6168
+ ar: uncommitted,
6169
+ at: treeOpen,
6085
6170
  av: collapsedDirs,
6086
6171
  aw: collapsedRepos,
6087
6172
  ax: toggleRepo,
@@ -6098,31 +6183,30 @@ var diff_view_default = _template("d", (input) => {
6098
6183
  bc: shownFiles,
6099
6184
  bg: filesByRepo,
6100
6185
  bi: commentsByFile,
6101
- bj: defaultBranches,
6102
- bl: sendPick,
6103
- bm: setBase,
6104
- bo: toggleViewed,
6105
- bp: saveComment,
6106
- bq: removeComment,
6107
- br: clearComments,
6108
- bu: copyMarkdown,
6109
- bv: sendTo,
6110
- bw: jumpTo,
6111
- c9: $collapsedDirs__closures,
6112
- c0: $viewed__closures,
6113
- cg: $commentsByFile__closures,
6114
- cf: $filesByRepo__closures,
6115
- by: $si__input_sessions_0_title && $input_sessions_0_title__closures,
6116
- bz: $si__input_sessions_0_agent && $input_sessions_0_agent__closures,
6117
- c5: $sending__closures,
6118
- bx: $si__input_sessions && $input_sessions__closures,
6119
- c6: $sendTarget__closures,
6120
- ca: $collapsedRepos__closures,
6121
- c2: $draft__closures,
6122
- cj: $saveComment2__closures,
6123
- ck: $removeComment2__closures,
6124
- ci: $toggleViewed2__closures,
6125
- cc: $repoList_length__closures
6186
+ bj: sendPick,
6187
+ bk: localDiffers,
6188
+ bm: toggleViewed,
6189
+ bn: saveComment,
6190
+ bo: removeComment,
6191
+ bp: clearComments,
6192
+ bs: copyMarkdown,
6193
+ bt: sendTo,
6194
+ bu: jumpTo,
6195
+ c7: $collapsedDirs__closures,
6196
+ by: $viewed__closures,
6197
+ cf: $commentsByFile__closures,
6198
+ ce: $filesByRepo__closures,
6199
+ bw: $si__input_sessions_0_title && $input_sessions_0_title__closures,
6200
+ bx: $si__input_sessions_0_agent && $input_sessions_0_agent__closures,
6201
+ c3: $sending__closures,
6202
+ bv: $si__input_sessions && $input_sessions__closures,
6203
+ c4: $sendTarget__closures,
6204
+ c8: $collapsedRepos__closures,
6205
+ c0: $draft__closures,
6206
+ ci: $saveComment2__closures,
6207
+ cj: $removeComment2__closures,
6208
+ ch: $toggleViewed2__closures,
6209
+ cb: $repoList_length__closures
6126
6210
  });
6127
6211
  _resume_branch($scope0_id);
6128
6212
  });
@@ -6497,10 +6581,75 @@ var usage_stats_default = _template("n", (input) => {
6497
6581
  _resume_branch($scope0_id);
6498
6582
  });
6499
6583
  //#endregion
6584
+ //#region src/tags/prompt-dialog.marko
6585
+ var prompt_dialog_default = _template("i", (input) => {
6586
+ const $scope0_reason = _scope_reason(), $sg__input_label = _serialize_guard($scope0_reason, 1), $sg__input_onCancel = _serialize_guard($scope0_reason, 2), $si__input_submitLabel = _serialize_if($scope0_reason, 3), $si__input_label = _serialize_if($scope0_reason, 1), $si__input_placeholder = _serialize_if($scope0_reason, 4);
6587
+ const $scope0_id = _scope_id();
6588
+ const $modalview_content__subscribers = /* @__PURE__ */ new Set();
6589
+ const $input_submitLabel__closures = /* @__PURE__ */ new Set();
6590
+ const $value__closures = /* @__PURE__ */ new Set();
6591
+ const $input_label__closures = /* @__PURE__ */ new Set();
6592
+ const $input_placeholder__closures = /* @__PURE__ */ new Set();
6593
+ let value = input.value || "";
6594
+ const submit = _resume(() => {
6595
+ const v = value.trim();
6596
+ if (v) input.onSubmit(v);
6597
+ }, "i1", $scope0_id);
6598
+ _set_serialize_reason({
6599
+ 0: $sg__input_onCancel,
6600
+ 1: $sg__input_label,
6601
+ 2: $sg__input_onCancel
6602
+ });
6603
+ const $childScope = _peek_scope_id();
6604
+ modal_view_default({
6605
+ label: input.label,
6606
+ class: "prompt-dlg",
6607
+ onClose: _resume(function() {
6608
+ input.onCancel();
6609
+ }, "i2", $scope0_id),
6610
+ footer: attrTag({ content: _content_resume("i5", () => {
6611
+ _scope_reason();
6612
+ const $scope1_id = _scope_id();
6613
+ _html(`<button class="btn primary"${_attr("disabled", !value.trim())}>${_escape(input.submitLabel || "OK")}${_el_resume($scope1_id, "b", _serialize_guard($scope0_reason, 3))}</button>${_el_resume($scope1_id, "a")}<div class=spacer></div><button class=btn>Cancel</button>${_el_resume($scope1_id, "c")}`);
6614
+ _script($scope1_id, "i3");
6615
+ _subscribe($value__closures, _subscribe($si__input_submitLabel && $input_submitLabel__closures, writeScope($scope1_id, { _: _scope_with_id($scope0_id) })));
6616
+ _resume_branch($scope1_id);
6617
+ }, $scope0_id) }),
6618
+ content: _content("i6", () => {
6619
+ _scope_reason();
6620
+ const $scope2_id = _scope_id();
6621
+ _html(`<h2 class=prompt-title>${_escape(input.label)}${_el_resume($scope2_id, "a", $sg__input_label)}</h2><input${_attr_input_value($scope2_id, "b", value, _resume((_new_value) => {
6622
+ value = _new_value;
6623
+ }, "i0", $scope2_id))}${_attr("placeholder", input.placeholder)}>${_el_resume($scope2_id, "b")}`);
6624
+ _script($scope2_id, "i4");
6625
+ _subscribe($value__closures, _subscribe($si__input_placeholder && $input_placeholder__closures, _subscribe($si__input_label && $input_label__closures, _subscribe($modalview_content__subscribers, writeScope($scope2_id, {
6626
+ _: _scope_with_id($scope0_id),
6627
+ Cp: 1
6628
+ })))));
6629
+ _resume_branch($scope2_id);
6630
+ })
6631
+ });
6632
+ _script($scope0_id, "i7");
6633
+ writeScope($scope0_id, {
6634
+ e: input.onSubmit,
6635
+ g: input.onCancel,
6636
+ h: input.submitLabel,
6637
+ j: value,
6638
+ l: submit,
6639
+ B2: $modalview_content__subscribers,
6640
+ n: $si__input_submitLabel && $input_submitLabel__closures,
6641
+ p: $value__closures,
6642
+ m: $si__input_label && $input_label__closures,
6643
+ o: $si__input_placeholder && $input_placeholder__closures,
6644
+ a: _serialize_if($scope0_reason, 0) && _existing_scope($childScope)
6645
+ });
6646
+ _resume_branch($scope0_id);
6647
+ });
6648
+ //#endregion
6500
6649
  //#region src/tags/work-view.marko
6501
6650
  var api$1;
6502
6651
  var work_view_default = _template("o", (input) => {
6503
- const $scope0_reason = _scope_reason(), $sg__input_agents = _serialize_guard($scope0_reason, 8), $sg__input_workspace = _serialize_guard($scope0_reason, 2), $si__input_workspace = _serialize_if($scope0_reason, 2), $sg__input_preferred = _serialize_guard($scope0_reason, 5), $si__input_active = _serialize_if($scope0_reason, 4), $si__input_agents = _serialize_if($scope0_reason, 8), $si__input_preferred = _serialize_if($scope0_reason, 5);
6652
+ const $scope0_reason = _scope_reason(), $sg__input_agents = _serialize_guard($scope0_reason, 8), $sg__input_workspace = _serialize_guard($scope0_reason, 2), $si__input_workspace = _serialize_if($scope0_reason, 2), $si__input_active = _serialize_if($scope0_reason, 4), $si__input_agents = _serialize_if($scope0_reason, 8), $si__input_preferred = _serialize_if($scope0_reason, 5);
6504
6653
  const $scope0_id = _scope_id();
6505
6654
  const $input_active__closures = /* @__PURE__ */ new Set();
6506
6655
  const $input_agents__closures = /* @__PURE__ */ new Set();
@@ -6512,12 +6661,15 @@ var work_view_default = _template("o", (input) => {
6512
6661
  const $closeSession2__closures = /* @__PURE__ */ new Set();
6513
6662
  const $newSession2__closures = /* @__PURE__ */ new Set();
6514
6663
  const $input_preferred__closures = /* @__PURE__ */ new Set();
6664
+ const $spawning__closures = /* @__PURE__ */ new Set();
6515
6665
  let sessions = null;
6516
6666
  let tab = null;
6517
6667
  let view = "term";
6668
+ let focusPr = "";
6518
6669
  let drawerOpen = false;
6519
6670
  let usageOpen = false;
6520
6671
  let error = null;
6672
+ let spawning = false;
6521
6673
  let renaming = null;
6522
6674
  const ws = input.workspace;
6523
6675
  const shownSessions = sessions ?? input.allStatus?.[ws.id]?.sessions ?? null;
@@ -6528,6 +6680,7 @@ var work_view_default = _template("o", (input) => {
6528
6680
  return shownSessions?.[0]?.id ?? null;
6529
6681
  })();
6530
6682
  const createSession = _resume(async (agent, select) => {
6683
+ spawning = true;
6531
6684
  try {
6532
6685
  const session = await api$1(`/api/workspaces/${ws.id}/sessions`, {
6533
6686
  method: "POST",
@@ -6542,6 +6695,8 @@ var work_view_default = _template("o", (input) => {
6542
6695
  } catch (err) {
6543
6696
  error = String(err.message || err);
6544
6697
  return null;
6698
+ } finally {
6699
+ spawning = false;
6545
6700
  }
6546
6701
  }, "o0", $scope0_id);
6547
6702
  const loadMem = {
@@ -6627,7 +6782,7 @@ var work_view_default = _template("o", (input) => {
6627
6782
  13: _serialize_guard($scope0_reason, 6),
6628
6783
  14: $sg__input_agents,
6629
6784
  15: 1,
6630
- 16: 1
6785
+ 17: 1
6631
6786
  });
6632
6787
  const $childScope = _peek_scope_id();
6633
6788
  side_nav_default({
@@ -6667,14 +6822,17 @@ var work_view_default = _template("o", (input) => {
6667
6822
  }, "o13", $scope0_id),
6668
6823
  onRenameSession: _resume(function(s) {
6669
6824
  promptRename(s);
6670
- }, "o14", $scope0_id)
6825
+ }, "o14", $scope0_id),
6826
+ onReviewOpened: _resume(function(pr) {
6827
+ focusPr = pr || "";
6828
+ }, "o15", $scope0_id)
6671
6829
  });
6672
6830
  _html("<div class=main-col>");
6673
6831
  _if(() => {
6674
6832
  if (error) {
6675
6833
  const $scope5_id = _scope_id();
6676
6834
  _html(`<p class=toast-error>${_escape(error)}${_el_resume($scope5_id, "a")}<button title=Dismiss>×</button>${_el_resume($scope5_id, "b")}</p>`);
6677
- _script($scope5_id, "o28");
6835
+ _script($scope5_id, "o29");
6678
6836
  writeScope($scope5_id, {});
6679
6837
  return 0;
6680
6838
  }
@@ -6687,13 +6845,14 @@ var work_view_default = _template("o", (input) => {
6687
6845
  workspaceId: ws.id,
6688
6846
  sessions: shownSessions || [],
6689
6847
  active: input.active && view === "diff",
6848
+ focusPr,
6690
6849
  onSent: _resume(function(sessionId) {
6691
6850
  tab = sessionId;
6692
6851
  view = "term";
6693
- }, "o15", $scope0_id),
6852
+ }, "o16", $scope0_id),
6694
6853
  onExit: _resume(function() {
6695
6854
  view = "term";
6696
- }, "o16", $scope0_id)
6855
+ }, "o17", $scope0_id)
6697
6856
  });
6698
6857
  _show_end($scope0_id, "f", $show);
6699
6858
  _html(`<div class=${view === "diff" ? "\"term-area as-bar\"" : "term-area"}>`);
@@ -6732,31 +6891,32 @@ var work_view_default = _template("o", (input) => {
6732
6891
  sessions: shownSessions,
6733
6892
  onNewSession: _resume(function(agent) {
6734
6893
  newSession(ws.id, agent);
6735
- }, "o17", $scope2_id),
6894
+ }, "o18", $scope2_id),
6736
6895
  onOpenTab: _resume(function(id) {
6737
6896
  tab = id;
6738
- }, "o18", $scope2_id),
6897
+ }, "o19", $scope2_id),
6739
6898
  onCloseSession: _resume(function(id) {
6740
6899
  closeSession(id);
6741
- }, "o19", $scope2_id),
6900
+ }, "o20", $scope2_id),
6742
6901
  onRenameSession: _resume(function(id) {
6743
6902
  const target = (shownSessions || []).find((x) => x.id === id);
6744
6903
  if (target) renaming = target;
6745
- }, "o20", $scope2_id),
6904
+ }, "o21", $scope2_id),
6746
6905
  active: input.active && view === "term" && s.id === curTab,
6747
6906
  mode: view,
6748
6907
  onToggleDiff: _resume(function() {
6908
+ focusPr = "";
6749
6909
  view = "diff";
6750
- }, "o21", $scope2_id),
6910
+ }, "o22", $scope2_id),
6751
6911
  onExitReview: _resume(function() {
6752
6912
  view = "term";
6753
- }, "o22", $scope2_id),
6913
+ }, "o23", $scope2_id),
6754
6914
  onOpenNav: _resume(function() {
6755
6915
  drawerOpen = true;
6756
- }, "o23", $scope2_id),
6916
+ }, "o24", $scope2_id),
6757
6917
  onManage: _resume(function() {
6758
6918
  input.onManage?.(ws.id);
6759
- }, "o24", $scope2_id)
6919
+ }, "o25", $scope2_id)
6760
6920
  });
6761
6921
  _show_end($scope2_id, "d", $show2);
6762
6922
  _subscribe($newSession2__closures, _subscribe($closeSession2__closures, _subscribe($curTab__closures, _subscribe($shownSessions__closures, _subscribe($si__input_workspace && $ws_name__closures, _subscribe($si__input_workspace && $ws_id__closures, _subscribe($view__closures, _subscribe($si__input_agents && $input_agents__closures, _subscribe($si__input_active && $input_active__closures, writeScope($scope2_id, {
@@ -6767,9 +6927,9 @@ var work_view_default = _template("o", (input) => {
6767
6927
  _if(() => {
6768
6928
  if (!shownSessions.length) {
6769
6929
  const $scope3_id = _scope_id();
6770
- _html(`<div class=empty-sess><p class=dim>No sessions in this workspace.</p><button class="btn primary">Start ${_sep($sg__input_preferred)}${_escape(input.preferred || "claude")}${_el_resume($scope3_id, "b", $sg__input_preferred)}</button>${_el_resume($scope3_id, "a")}</div>`);
6771
- _script($scope3_id, "o29");
6772
- _subscribe($si__input_preferred && $input_preferred__closures, writeScope($scope3_id, {}));
6930
+ _html(`<div class=empty-sess><p class=dim>No sessions in this workspace.</p><button class="btn primary"${_attr("disabled", spawning)}>${spawning ? "Starting…" : `Start ${_escape(`${input.preferred || "claude"}`)}`}${_el_resume($scope3_id, "b")}</button>${_el_resume($scope3_id, "a")}</div>`);
6931
+ _script($scope3_id, "o30");
6932
+ _subscribe($spawning__closures, _subscribe($si__input_preferred && $input_preferred__closures, writeScope($scope3_id, {})));
6773
6933
  return 0;
6774
6934
  }
6775
6935
  }, $scope1_id, "b", 1, 1, 1, 0, 1);
@@ -6788,7 +6948,7 @@ var work_view_default = _template("o", (input) => {
6788
6948
  const $scope4_id = _scope_id();
6789
6949
  usage_stats_default({ onClose: _resume(function() {
6790
6950
  usageOpen = false;
6791
- }, "o25", $scope4_id) });
6951
+ }, "o26", $scope4_id) });
6792
6952
  writeScope($scope4_id, {});
6793
6953
  return 0;
6794
6954
  }
@@ -6796,7 +6956,6 @@ var work_view_default = _template("o", (input) => {
6796
6956
  _if(() => {
6797
6957
  if (renaming) {
6798
6958
  const $scope6_id = _scope_id();
6799
- _set_serialize_reason(4);
6800
6959
  const $childScope4 = _peek_scope_id();
6801
6960
  prompt_dialog_default({
6802
6961
  label: "Rename session",
@@ -6805,21 +6964,21 @@ var work_view_default = _template("o", (input) => {
6805
6964
  onSubmit: _resume(function(title) {
6806
6965
  renameSession(renaming.id, title);
6807
6966
  renaming = null;
6808
- }, "o26", $scope6_id),
6967
+ }, "o27", $scope6_id),
6809
6968
  onCancel: _resume(function() {
6810
6969
  renaming = null;
6811
- }, "o27", $scope6_id)
6970
+ }, "o28", $scope6_id)
6812
6971
  });
6813
6972
  writeScope($scope6_id, { a: _existing_scope($childScope4) });
6814
6973
  return 0;
6815
6974
  }
6816
6975
  }, $scope0_id, "i");
6817
6976
  _html("</div>");
6818
- _script($scope0_id, "o30");
6819
6977
  _script($scope0_id, "o31");
6820
6978
  _script($scope0_id, "o32");
6821
6979
  _script($scope0_id, "o33");
6822
6980
  _script($scope0_id, "o34");
6981
+ _script($scope0_id, "o35");
6823
6982
  writeScope($scope0_id, {
6824
6983
  m: input.allStatus,
6825
6984
  n: input.active,
@@ -6830,38 +6989,40 @@ var work_view_default = _template("o", (input) => {
6830
6989
  v: sessions,
6831
6990
  w: tab,
6832
6991
  y: view,
6833
- a0: drawerOpen,
6834
- a2: error,
6835
- a4: renaming?.title,
6836
- a5: renaming?.agent,
6837
- a6: renaming?.id,
6838
- a8: ws?.id,
6839
- ad: ws?.status,
6840
- ae: ws?.name,
6841
- af: shownSessions,
6842
- ah: shownSessions?.length,
6843
- ai: curTab,
6844
- ak: createSession,
6845
- an: loadMem.wanted,
6846
- ap: loadMem.run,
6847
- aq: loadBody,
6848
- as: loadSessions,
6849
- au: closeSession,
6850
- av: renameSession,
6851
- aw: newSession,
6852
- ax: promptRename,
6992
+ a1: drawerOpen,
6993
+ a3: error,
6994
+ a4: spawning,
6995
+ a6: renaming?.title,
6996
+ a7: renaming?.agent,
6997
+ a8: renaming?.id,
6998
+ aa: ws?.id,
6999
+ af: ws?.status,
7000
+ ag: ws?.name,
7001
+ ah: shownSessions,
7002
+ aj: shownSessions?.length,
7003
+ ak: curTab,
7004
+ am: createSession,
7005
+ ap: loadMem.wanted,
7006
+ ar: loadMem.run,
7007
+ as: loadBody,
7008
+ au: loadSessions,
7009
+ aw: closeSession,
7010
+ ax: renameSession,
7011
+ ay: newSession,
7012
+ az: promptRename,
6853
7013
  a: _existing_scope($childScope),
6854
7014
  d: _existing_scope($childScope2),
6855
- ay: $si__input_active && $input_active__closures,
6856
- b0: $si__input_agents && $input_agents__closures,
6857
- b1: $view__closures,
6858
- b6: $si__input_workspace && $ws_id__closures,
6859
- b7: $si__input_workspace && $ws_name__closures,
6860
- b8: $shownSessions__closures,
6861
- ba: $curTab__closures,
6862
- bb: $closeSession2__closures,
6863
- bd: $newSession2__closures,
6864
- az: $si__input_preferred && $input_preferred__closures
7015
+ b0: $si__input_active && $input_active__closures,
7016
+ b2: $si__input_agents && $input_agents__closures,
7017
+ b3: $view__closures,
7018
+ b9: $si__input_workspace && $ws_id__closures,
7019
+ ba: $si__input_workspace && $ws_name__closures,
7020
+ bb: $shownSessions__closures,
7021
+ bd: $curTab__closures,
7022
+ be: $closeSession2__closures,
7023
+ bg: $newSession2__closures,
7024
+ b1: $si__input_preferred && $input_preferred__closures,
7025
+ b5: $spawning__closures
6865
7026
  });
6866
7027
  _resume_branch($scope0_id);
6867
7028
  });
@@ -6873,6 +7034,7 @@ var prefs;
6873
7034
  var _page_default = _template("b", (input) => {
6874
7035
  _scope_reason();
6875
7036
  const $scope0_id = _scope_id();
7037
+ const $error__closures = /* @__PURE__ */ new Set();
6876
7038
  const $workspaces__closures = /* @__PURE__ */ new Set();
6877
7039
  const $status__closures = /* @__PURE__ */ new Set();
6878
7040
  const $settings__closures = /* @__PURE__ */ new Set();
@@ -6891,23 +7053,27 @@ var _page_default = _template("b", (input) => {
6891
7053
  let paletteOpen = false;
6892
7054
  let error = null;
6893
7055
  const load = _resume(async () => {
6894
- const [list] = await Promise.all([api("/api/workspaces"), prefs.sync()]);
6895
- workspaces = list;
6896
- if (!loaded) {
6897
- const last = prefs.lastWorkspace();
6898
- if (last && list.some((ws) => ws.id === last)) {
6899
- selectedId = last;
6900
- openIds = [last];
6901
- }
6902
- } else {
6903
- const activeIds = new Set(list.filter((ws) => ws.status === "active").map((ws) => ws.id));
6904
- openIds = openIds.filter((id) => activeIds.has(id));
6905
- if (selectedId && !activeIds.has(selectedId)) {
6906
- selectedId = openIds[openIds.length - 1] || null;
6907
- prefs.setLastWorkspace(selectedId);
7056
+ try {
7057
+ const [list] = await Promise.all([api("/api/workspaces"), prefs.sync()]);
7058
+ workspaces = list;
7059
+ if (!loaded) {
7060
+ const last = prefs.lastWorkspace();
7061
+ if (last && list.some((ws) => ws.id === last)) {
7062
+ selectedId = last;
7063
+ openIds = [last];
7064
+ }
7065
+ } else {
7066
+ const activeIds = new Set(list.filter((ws) => ws.status === "active").map((ws) => ws.id));
7067
+ openIds = openIds.filter((id) => activeIds.has(id));
7068
+ if (selectedId && !activeIds.has(selectedId)) {
7069
+ selectedId = openIds[openIds.length - 1] || null;
7070
+ prefs.setLastWorkspace(selectedId);
7071
+ }
6908
7072
  }
7073
+ loaded = true;
7074
+ } catch (err) {
7075
+ error = `Loading workspaces failed: ${err.message || String(err)}`;
6909
7076
  }
6910
- loaded = true;
6911
7077
  }, "b0", $scope0_id);
6912
7078
  const statusMem = {
6913
7079
  run: void 0,
@@ -6945,7 +7111,7 @@ var _page_default = _template("b", (input) => {
6945
7111
  const showInlinePalette = !creating && !selected && loaded;
6946
7112
  _html("<main>");
6947
7113
  _if(() => {
6948
- if (error) {
7114
+ if (error && loaded) {
6949
7115
  const $scope6_id = _scope_id();
6950
7116
  _html(`<p class=toast-error>${_escape(error)}${_el_resume($scope6_id, "a")} <button>×</button>${_el_resume($scope6_id, "b")}</p>`);
6951
7117
  _script($scope6_id, "b18");
@@ -6956,7 +7122,21 @@ var _page_default = _template("b", (input) => {
6956
7122
  _if(() => {
6957
7123
  if (!loaded) {
6958
7124
  const $scope7_id = _scope_id();
6959
- _html("<div class=\"boot dim\"><span class=spin></span>Loading workspaces…</div>");
7125
+ _html("<div class=\"boot dim\">");
7126
+ _if(() => {
7127
+ if (error) {
7128
+ const $scope8_id = _scope_id();
7129
+ _html(`<p>${_escape(error)}${_el_resume($scope8_id, "a")}</p><button class=btn>Retry</button>${_el_resume($scope8_id, "b")}`);
7130
+ _script($scope8_id, "b19");
7131
+ _subscribe($error__closures, writeScope($scope8_id, {}));
7132
+ return 0;
7133
+ } else {
7134
+ const $scope9_id = _scope_id();
7135
+ _html("<span class=spin></span>Loading workspaces…");
7136
+ writeScope($scope9_id, {});
7137
+ return 1;
7138
+ }
7139
+ }, $scope7_id, "a", 1, 1, 1, "</div>");
6960
7140
  writeScope($scope7_id, {});
6961
7141
  return 0;
6962
7142
  }
@@ -6983,6 +7163,7 @@ var _page_default = _template("b", (input) => {
6983
7163
  openWorkspace(ws.id);
6984
7164
  }, "b5", $scope1_id),
6985
7165
  onNewSession: _resume(function(ws, agent) {
7166
+ paletteOpen = false;
6986
7167
  api(`/api/workspaces/${ws.id}/sessions`, {
6987
7168
  method: "POST",
6988
7169
  headers: { "content-type": "application/json" },
@@ -7091,7 +7272,7 @@ var _page_default = _template("b", (input) => {
7091
7272
  });
7092
7273
  }, (id) => id, $scope0_id, "f");
7093
7274
  _html("</main>");
7094
- _script($scope0_id, "b19");
7275
+ _script($scope0_id, "b20");
7095
7276
  writeScope($scope0_id, {
7096
7277
  g: workspaces,
7097
7278
  h: status,
@@ -7103,21 +7284,22 @@ var _page_default = _template("b", (input) => {
7103
7284
  q: managingId,
7104
7285
  s: paletteOpen,
7105
7286
  t: error,
7106
- u: load,
7107
- w: statusMem.fresh,
7108
- x: statusMem.run,
7109
- z: loadStatus,
7110
- a0: openWorkspace,
7111
- a2: selected,
7112
- a4: managing,
7113
- a5: showInlinePalette,
7114
- a7: $workspaces__closures,
7115
- a8: $status__closures,
7116
- a9: $settings__closures,
7117
- aa: $selectedId__closures,
7118
- ab: $creating__closures,
7119
- ae: $openWorkspace2__closures,
7120
- af: $managing__closures
7287
+ v: load,
7288
+ x: statusMem.fresh,
7289
+ y: statusMem.run,
7290
+ a0: loadStatus,
7291
+ a1: openWorkspace,
7292
+ a3: selected,
7293
+ a5: managing,
7294
+ a6: showInlinePalette,
7295
+ ae: $error__closures,
7296
+ a8: $workspaces__closures,
7297
+ a9: $status__closures,
7298
+ aa: $settings__closures,
7299
+ ab: $selectedId__closures,
7300
+ ac: $creating__closures,
7301
+ af: $openWorkspace2__closures,
7302
+ ag: $managing__closures
7121
7303
  });
7122
7304
  _resume_branch($scope0_id);
7123
7305
  });
@@ -13078,8 +13260,14 @@ function setScrapedPhase(sessionId, phase, blockerText) {
13078
13260
  reasons.set(sessionId, "attention");
13079
13261
  if (blockerText) scrapedSummaries.set(sessionId, blockerText);
13080
13262
  } else scrapedSummaries.delete(sessionId);
13081
- if (scraped.get(sessionId) === phase) return;
13263
+ const stalePin = phase === "working" && phases.get(sessionId) === "waiting" && (reasons.get(sessionId) === "done" || scraped.get(sessionId) === "waiting");
13264
+ if (scraped.get(sessionId) === phase && !stalePin) return;
13082
13265
  applyChange(sessionId, () => {
13266
+ if (stalePin) {
13267
+ phases.delete(sessionId);
13268
+ summaries.delete(sessionId);
13269
+ reasons.delete(sessionId);
13270
+ }
13083
13271
  if (phase) scraped.set(sessionId, phase);
13084
13272
  else scraped.delete(sessionId);
13085
13273
  });
@@ -13726,8 +13914,8 @@ function hydrate(ws) {
13726
13914
  return ws;
13727
13915
  }
13728
13916
  async function createWorkspace(name, repos) {
13917
+ if (!name.trim()) throw new Error("workspace name required");
13729
13918
  const slug = slugify(name);
13730
- if (!slug) throw new Error("workspace name required");
13731
13919
  const id = randomId();
13732
13920
  const dir = path.join(config.workRoot, `${slug}-${id}`);
13733
13921
  const branch = `${config.branchPrefix}-${slug}-${id}`;
@@ -13836,9 +14024,14 @@ async function removeRepo(workspaceId, worktree) {
13836
14024
  ], bare).catch(() => {});
13837
14025
  });
13838
14026
  db.prepare("DELETE FROM repos WHERE workspace_id = ? AND worktree = ?").run(workspaceId, worktree);
14027
+ db.prepare("DELETE FROM review_comments WHERE workspace_id = ? AND worktree = ?").run(workspaceId, worktree);
14028
+ db.prepare("DELETE FROM review_viewed WHERE workspace_id = ? AND key GLOB ? || '/*'").run(workspaceId, worktree);
13839
14029
  const updated = getWorkspace(workspaceId);
13840
- writeAgentsMd(ws.path, ws.branch, updated.repos);
14030
+ try {
14031
+ writeAgentsMd(ws.path, ws.branch, updated.repos);
14032
+ } catch {}
13841
14033
  publish("workspaces.changed");
14034
+ publish("review.changed", { workspaceId });
13842
14035
  return updated;
13843
14036
  }
13844
14037
  async function setWorkspaceStatus(id, status) {
@@ -13968,6 +14161,12 @@ async function ensureBare(fullName) {
13968
14161
  async function bareDefaultBranch(bare) {
13969
14162
  return (await git(["symbolic-ref", "HEAD"], bare)).replace("refs/heads/", "");
13970
14163
  }
14164
+ /** `ref` is this workspace branch or a `-topic` lane of it. Another
14165
+ * workspace's branch can extend this prefix, so callers must also
14166
+ * subtract `otherWorkspaceBranches`. */
14167
+ function ownsBranch(ref, prefix) {
14168
+ return ref === prefix || ref.startsWith(`${prefix}-`);
14169
+ }
13971
14170
  /** Branches of every other workspace — slugs are user-chosen, so one
13972
14171
  * workspace's branch can be a strict prefix of another's; prefix matching
13973
14172
  * (cleanup, PR attribution) must exclude these. */
@@ -13978,16 +14177,18 @@ function otherWorkspaceBranches(id) {
13978
14177
  * minus any ref that actually belongs to another workspace whose branch
13979
14178
  * extends this one's (see otherWorkspaceBranches). */
13980
14179
  async function workspaceBranches(bare, branch, exclude) {
13981
- const out = await git([
14180
+ return (await git([
13982
14181
  "for-each-ref",
13983
14182
  "--format=%(refname:short)",
13984
14183
  "refs/heads/"
13985
- ], bare).catch(() => "");
13986
- const owns = (ref, b) => ref === b || ref.startsWith(`${b}-`);
13987
- return out.split("\n").filter((b) => owns(b, branch) && !exclude.some((x) => owns(b, x)));
14184
+ ], bare).catch(() => "")).split("\n").filter((b) => ownsBranch(b, branch) && !exclude.some((x) => ownsBranch(b, x)));
13988
14185
  }
14186
+ /** Directory/branch-safe prefix for a workspace name. Decomposing first keeps
14187
+ * accented Latin readable (é → e); a name with nothing ASCII left (another
14188
+ * script, emoji) falls back to a generic prefix — the random id is what makes
14189
+ * the name unique, so the slug never has to be rejected. */
13989
14190
  function slugify(name) {
13990
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
14191
+ return name.normalize("NFKD").replace(/\p{M}+/gu, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "ws";
13991
14192
  }
13992
14193
  function randomId() {
13993
14194
  return Math.random().toString(36).slice(2, 8);
@@ -14009,16 +14210,33 @@ function writeAgentsMd(dir, branch, repos) {
14009
14210
  Workspace branch: **\`${branch}\`** — every repo here is checked out on it, and
14010
14211
  it is the required **prefix** for every other branch you create.
14011
14212
 
14012
- - **Branches:** one PR? Stay on \`${branch}\`. Need more than one? Cut a lane per
14013
- PR \`git switch -c ${branch}-<topic> origin/<base>\` — and keep the prefix.
14014
- Tooling finds PRs by that prefix, so a branch named anything else is
14015
- invisible: never invent one.
14213
+ - **Branches:** stay on \`${branch}\` and keep pushing it — one open PR, more
14214
+ commits. After it lands, \`git fetch\` and reset \`${branch}\` to
14215
+ \`origin/<base>\` so the next change reuses this branch. Do not cut a
14216
+ \`${branch}-<topic>\` just to start the next piece of work.
14217
+ A topic lane is only for when \`${branch}\` must stay unmerged (a larger
14218
+ change) and you need a second PR on top of it. Then push every repo's
14219
+ workspace branch first, cut from the parent —
14220
+ \`git switch -c ${branch}-<topic> origin/${branch}\` — and keep the prefix.
14221
+ Only two levels: never \`${branch}-a-b\`. Tooling finds PRs by that prefix,
14222
+ so a branch named anything else is invisible: never invent one.
14016
14223
  - **Repos:**
14017
14224
  ${repos.map((r) => `- \`${r.worktree}/\` — ${r.full_name} (base: \`${r.base_branch}\`)`).join("\n")}
14225
+ - **No \`git stash\`:** every workspace using a repo shares one stash stack, so
14226
+ a pop can take another workspace's work. Commit as you go — uncommitted
14227
+ work isn't part of the review.
14018
14228
  - **Push:** \`git push -u origin HEAD\` in each repo, for review on GitHub.
14019
14229
  Never commit on a base branch.
14230
+ - **Rebase after merge:** when \`${branch}\` is still open and \`origin/<base>\`
14231
+ moves, rebase onto it — a squash-merge does not move the fork point, so the
14232
+ review keeps showing landed work until you do. On a topic lane, rebase onto
14233
+ \`${branch}\` when the parent moves; after the parent merges: \`git fetch
14234
+ --prune\`, rebase onto \`origin/<base>\`, and retarget (\`gh pr edit --base
14235
+ <base>\`).
14020
14236
  - **PRs:** the description covers only what is in the diff — a few sentences on
14021
- what changed and why. No verification logs or file-by-file detail.
14237
+ what changed and why. No verification logs or file-by-file detail. A topic
14238
+ lane's PR is \`gh pr create --base ${branch}\` — GitHub will otherwise target
14239
+ the repo default, and the local review will disagree with the files tab.
14022
14240
  `;
14023
14241
  fs.writeFileSync(path.join(dir, "AGENTS.md"), content);
14024
14242
  const claudeMd = path.join(dir, "CLAUDE.md");
@@ -15385,16 +15603,18 @@ async function scan() {
15385
15603
  "-a",
15386
15604
  "-F",
15387
15605
  "#{session_name} #{pane_title}"
15388
- ]).catch(() => "");
15606
+ ]).catch(() => null);
15607
+ if (out === null) return false;
15389
15608
  for (const line of out.split("\n")) {
15390
15609
  const [name, ...title] = line.split(" ");
15391
15610
  if (name?.startsWith("wk-")) panes.set(name.slice(3), title.join(" "));
15392
15611
  }
15393
- await Promise.all(sessions.map(async (session) => {
15394
- if (!panes.has(session.id)) {
15395
- setScrapedPhase(session.id, void 0);
15396
- return;
15397
- }
15612
+ for (const session of sessions) {
15613
+ if (panes.has(session.id)) continue;
15614
+ clearAgentPhase(session.id);
15615
+ }
15616
+ const live = sessions.filter((session) => panes.has(session.id));
15617
+ await Promise.all(live.map(async (session) => {
15398
15618
  const screen = await tmux([
15399
15619
  "capture-pane",
15400
15620
  "-p",
@@ -15409,7 +15629,7 @@ async function scan() {
15409
15629
  if (result.skip) return;
15410
15630
  setScrapedPhase(session.id, result.state === "working" ? "working" : result.state === "blocked" ? "waiting" : void 0, result.blockerText && blockerSummary(result.blockerText));
15411
15631
  }));
15412
- return true;
15632
+ return live.length > 0;
15413
15633
  }
15414
15634
  /** Human-readable line(s) from a blocker region: the question the agent is
15415
15635
  * asking, with box borders, option lists, and hint bars stripped. */
@@ -15469,6 +15689,7 @@ function swr(ttlMs, load, seed) {
15469
15689
  }
15470
15690
  //#endregion
15471
15691
  //#region src/server/prs.ts
15692
+ var PR_JSON = "number,title,url,headRefName,baseRefName,headRefOid,isDraft,state,reviewDecision";
15472
15693
  var TTL_MS$3 = 12e4;
15473
15694
  /** PRs in every state for a repo, stale-while-revalidate cached. */
15474
15695
  var prs = swr(TTL_MS$3, fetchPrs);
@@ -15499,7 +15720,7 @@ var headPrs = swr(TTL_MS$3, async (key) => {
15499
15720
  "--limit",
15500
15721
  "20",
15501
15722
  "--json",
15502
- "number,title,url,headRefName,isDraft,state,reviewDecision"
15723
+ PR_JSON
15503
15724
  ]).catch(() => "[]");
15504
15725
  return JSON.parse(out);
15505
15726
  });
@@ -15522,7 +15743,7 @@ async function fetchPrs(fullName) {
15522
15743
  "--limit",
15523
15744
  "100",
15524
15745
  "--json",
15525
- "number,title,url,headRefName,isDraft,state,reviewDecision"
15746
+ PR_JSON
15526
15747
  ]).catch(() => "[]");
15527
15748
  return JSON.parse(out);
15528
15749
  }
@@ -15539,247 +15760,54 @@ var lastBatch = 0;
15539
15760
  var hotUntil = 0;
15540
15761
  function notePushActivity() {
15541
15762
  hotUntil = Date.now() + HOT_WINDOW_MS;
15542
- }
15543
- /** Inside the post-push window where PR state is actively flipping. */
15544
- function inHotWindow() {
15545
- return Date.now() < hotUntil;
15546
- }
15547
- async function refreshPrsBatch(fullNames, force = false) {
15548
- const names = [...new Set(fullNames)].filter((n) => n.includes("/"));
15549
- const minMs = force ? 2e3 : Date.now() < hotUntil ? HOT_MIN_MS : BATCH_MIN_MS;
15550
- if (!names.length || Date.now() - lastBatch < minMs) return false;
15551
- const prevBatch = lastBatch;
15552
- lastBatch = Date.now();
15553
- try {
15554
- const out = await gh([
15555
- "api",
15556
- "graphql",
15557
- "-f",
15558
- `query=${"query {" + names.map((fullName, i) => {
15559
- const [owner, name] = fullName.split("/");
15560
- return `r${i}: repository(owner: ${JSON.stringify(owner)}, name: ${JSON.stringify(name)}) { pullRequests(first: 50, orderBy: {field: UPDATED_AT, direction: DESC}) { nodes { number title url headRefName isDraft state reviewDecision } } }`;
15561
- }).join(" ") + "}"}`
15562
- ]);
15563
- const data = JSON.parse(out).data;
15564
- if (!data) throw new Error("graphql response without data");
15565
- names.forEach((fullName, i) => {
15566
- const repo = data[`r${i}`];
15567
- if (!repo) return;
15568
- prs.set(fullName, repo.pullRequests.nodes);
15569
- });
15570
- return true;
15571
- } catch (err) {
15572
- lastBatch = prevBatch;
15573
- throw err;
15574
- }
15575
- }
15576
- /** Confirm a finished (merged/closed) PR so it leaves the manage list. */
15577
- function dismissPr(workspaceId, fullName, number) {
15578
- db.prepare("INSERT OR IGNORE INTO dismissed_prs (workspace_id, full_name, number) VALUES (?, ?, ?)").run(workspaceId, fullName, number);
15579
- }
15580
- /** Drop dismissed PRs from a repo's list — unless one reopened. */
15581
- function withoutDismissed(workspaceId, fullName, list) {
15582
- const dismissed = new Set(all("SELECT number FROM dismissed_prs WHERE workspace_id = ? AND full_name = ?", workspaceId, fullName).map((row) => row.number));
15583
- return list.filter((pr) => pr.state === "OPEN" || !dismissed.has(pr.number));
15584
- }
15585
- //#endregion
15586
- //#region src/server/diff.ts
15587
- var MAX_FILE_BYTES = 5e5;
15588
- /** Diffs compare against remote-tracking refs, which only move when this
15589
- * machine fetches or pushes — a PR merged on GitHub leaves them stale and
15590
- * the diff shows already-merged work until the next fetch. Opening the
15591
- * diff freshens the base refs itself (throttled per repo; each ref
15592
- * fetched independently since one missing ref fails a combined fetch,
15593
- * and capped so a slow remote can't hang the view — stale refs then
15594
- * self-correct on the next open). */
15595
- var FETCH_TTL_MS = 3e4;
15596
- var FETCH_WAIT_MS = 8e3;
15597
- var lastFetch = /* @__PURE__ */ new Map();
15598
- async function freshenBaseRefs(dir, fullName, refs) {
15599
- const now = Date.now();
15600
- if (now - (lastFetch.get(fullName) ?? 0) < FETCH_TTL_MS) return;
15601
- lastFetch.set(fullName, now);
15602
- const fetches = [...new Set(refs)].map((ref) => git([
15603
- "fetch",
15604
- "--quiet",
15605
- "--no-tags",
15606
- "origin",
15607
- ref
15608
- ], dir).catch(() => {}));
15609
- await Promise.race([Promise.all(fetches), new Promise((resolve) => setTimeout(resolve, FETCH_WAIT_MS))]);
15610
- }
15611
- /** The ref the working tree is compared against by default, best first:
15612
- * the workspace branch's remote (a lane worktree then shows its whole
15613
- * delta vs the pushed workspace branch, and a worktree on the workspace
15614
- * branch shows just what isn't pushed), the current branch's own remote,
15615
- * then the configured base branch. An explicit `baseRef` (the UI's
15616
- * "vs …" picker) overrides all. */
15617
- async function resolveBase(dir, wsBranch, baseBranch, baseRef) {
15618
- const candidates = [];
15619
- if (baseRef) candidates.push([`origin/${baseRef}`, baseRef], [baseRef, baseRef]);
15620
- else {
15621
- candidates.push([`origin/${wsBranch}`, `origin/${wsBranch}`]);
15622
- const branch = await git([
15623
- "rev-parse",
15624
- "--abbrev-ref",
15625
- "HEAD"
15626
- ], dir).catch(() => "HEAD");
15627
- if (branch !== "HEAD" && branch !== wsBranch) candidates.push([`origin/${branch}`, `origin/${branch}`]);
15628
- candidates.push([`origin/${baseBranch}`, baseBranch]);
15629
- }
15630
- for (const [ref, label] of candidates) try {
15631
- return {
15632
- mergeBase: await git([
15633
- "merge-base",
15634
- ref,
15635
- "HEAD"
15636
- ], dir),
15637
- label
15638
- };
15639
- } catch {}
15640
- return {
15641
- mergeBase: "HEAD",
15642
- label: baseRef || baseBranch
15643
- };
15644
- }
15645
- /** Changed files per repo: merge-base of a base ref vs the working tree.
15646
- * `baseRef` overrides each repo's default base (see resolveBase). */
15647
- async function workspaceDiff(ws, baseRef) {
15648
- return Promise.all(ws.repos.map(async (repo) => {
15649
- const dir = path.join(ws.path, repo.worktree);
15650
- const branch = await git([
15651
- "rev-parse",
15652
- "--abbrev-ref",
15653
- "HEAD"
15654
- ], dir).catch(() => "HEAD");
15655
- await freshenBaseRefs(dir, repo.full_name, [
15656
- ws.branch,
15657
- ...branch !== "HEAD" ? [branch] : [],
15658
- ...baseRef ? [baseRef] : [repo.base_branch]
15659
- ]);
15660
- const { mergeBase, label } = await resolveBase(dir, ws.branch, repo.base_branch, baseRef);
15661
- const [nameStatus, untracked] = await Promise.all([git([
15662
- "diff",
15663
- "--name-status",
15664
- "-z",
15665
- "--no-renames",
15666
- mergeBase
15667
- ], dir).catch(() => ""), git([
15668
- "ls-files",
15669
- "--others",
15670
- "--exclude-standard",
15671
- "-z"
15672
- ], dir).catch(() => "")]);
15673
- const entries = [];
15674
- const parts = nameStatus.split("\0").filter(Boolean);
15675
- for (let i = 0; i + 1 < parts.length; i += 2) entries.push({
15676
- path: parts[i + 1],
15677
- status: parts[i]
15678
- });
15679
- for (const file of untracked.split("\0").filter(Boolean)) entries.push({
15680
- path: file,
15681
- status: "A"
15682
- });
15683
- const olds = await batchOldContents(dir, mergeBase, entries.filter((e) => e.status !== "A").map((e) => e.path));
15684
- const files = await Promise.all(entries.map((e) => loadFile(dir, e.path, e.status, olds.get(e.path))));
15685
- files.sort((a, b) => a.path.localeCompare(b.path));
15686
- return {
15687
- worktree: repo.worktree,
15688
- full_name: repo.full_name,
15689
- base: label,
15690
- default_branch: repo.default_branch,
15691
- files
15692
- };
15693
- }));
15694
- }
15695
- /** Count of changed files vs the default base's merge-base (incl.
15696
- * untracked) — same notion of "changed" as the diff view's default. */
15697
- async function changedFileCount(dir, wsBranch, baseBranch) {
15698
- const { mergeBase } = await resolveBase(dir, wsBranch, baseBranch);
15699
- if (mergeBase === "HEAD") return 0;
15700
- const [changed, untracked] = await Promise.all([git([
15701
- "diff",
15702
- "--name-only",
15703
- "-z",
15704
- "--no-renames",
15705
- mergeBase
15706
- ], dir).catch(() => ""), git([
15707
- "ls-files",
15708
- "--others",
15709
- "--exclude-standard",
15710
- "-z"
15711
- ], dir).catch(() => "")]);
15712
- return changed.split("\0").filter(Boolean).length + untracked.split("\0").filter(Boolean).length;
15713
- }
15714
- /** Base-side contents for many paths through a single `git cat-file --batch`
15715
- * process (instead of one `git show` per file). Blobs over MAX_FILE_BYTES
15716
- * are skipped and mapped to "tooLarge". */
15717
- async function batchOldContents(dir, mergeBase, paths) {
15718
- const result = /* @__PURE__ */ new Map();
15719
- if (!paths.length) return result;
15720
- await new Promise((resolve) => {
15721
- const proc = spawn("git", ["cat-file", "--batch"], { cwd: dir });
15722
- let buf = Buffer.alloc(0);
15723
- let index = 0;
15724
- let need = -1;
15725
- const parse = () => {
15726
- for (;;) if (need >= 0) {
15727
- if (buf.length < need + 1) return;
15728
- const content = buf.subarray(0, need);
15729
- buf = buf.subarray(need + 1);
15730
- need = -1;
15731
- const p = paths[index++];
15732
- if (content.length > MAX_FILE_BYTES) result.set(p, "tooLarge");
15733
- else result.set(p, content.toString("utf8"));
15734
- } else {
15735
- const nl = buf.indexOf(10);
15736
- if (nl < 0) return;
15737
- const header = buf.subarray(0, nl).toString();
15738
- buf = buf.subarray(nl + 1);
15739
- if (header.endsWith(" missing")) {
15740
- index++;
15741
- continue;
15742
- }
15743
- need = Number(header.split(" ")[2]) || 0;
15744
- }
15745
- };
15746
- proc.stdout.on("data", (chunk) => {
15747
- buf = buf.length ? Buffer.concat([buf, chunk]) : chunk;
15748
- parse();
15749
- });
15750
- proc.on("close", () => resolve());
15751
- proc.on("error", () => resolve());
15752
- proc.stdin.on("error", () => {});
15753
- proc.stdin.end(paths.map((p) => `${mergeBase}:${p}`).join("\n") + "\n");
15754
- });
15755
- return result;
15756
- }
15757
- async function loadFile(dir, filePath, status, old) {
15758
- const result = {
15759
- path: filePath,
15760
- status,
15761
- old: null,
15762
- new: null
15763
- };
15764
- if (old === "tooLarge") result.tooLarge = true;
15765
- else if (old !== void 0) result.old = old;
15766
- if (status !== "D" && !result.tooLarge) try {
15767
- const root = path.resolve(dir);
15768
- const full = path.resolve(dir, filePath);
15769
- if (full !== root && !full.startsWith(root + path.sep)) throw new Error(`path escapes worktree: ${filePath}`);
15770
- if ((await fsp.stat(full)).size > MAX_FILE_BYTES) result.tooLarge = true;
15771
- else result.new = await fsp.readFile(full, "utf8");
15772
- } catch {}
15773
- if (result.old?.includes("\0") || result.new?.includes("\0")) {
15774
- result.binary = true;
15775
- result.old = null;
15776
- result.new = null;
15777
- }
15778
- if (result.tooLarge) {
15779
- result.old = null;
15780
- result.new = null;
15763
+ }
15764
+ /** Inside the post-push window where PR state is actively flipping. */
15765
+ function inHotWindow() {
15766
+ return Date.now() < hotUntil;
15767
+ }
15768
+ async function refreshPrsBatch(fullNames, force = false) {
15769
+ const names = [...new Set(fullNames)].filter((n) => n.includes("/"));
15770
+ const minMs = force ? 2e3 : Date.now() < hotUntil ? HOT_MIN_MS : BATCH_MIN_MS;
15771
+ if (!names.length || Date.now() - lastBatch < minMs) return false;
15772
+ const prevBatch = lastBatch;
15773
+ lastBatch = Date.now();
15774
+ try {
15775
+ const out = await gh([
15776
+ "api",
15777
+ "graphql",
15778
+ "-f",
15779
+ `query=${"query {" + names.map((fullName, i) => {
15780
+ const [owner, name] = fullName.split("/");
15781
+ return `r${i}: repository(owner: ${JSON.stringify(owner)}, name: ${JSON.stringify(name)}) { pullRequests(first: 50, orderBy: {field: UPDATED_AT, direction: DESC}) { nodes { number title url headRefName baseRefName isDraft state reviewDecision headRef { oid } } } }`;
15782
+ }).join(" ") + "}"}`
15783
+ ]).catch((err) => {
15784
+ if (!err.stdout) throw err;
15785
+ return err.stdout;
15786
+ });
15787
+ const data = JSON.parse(out).data;
15788
+ if (!data) throw new Error("graphql response without data");
15789
+ names.forEach((fullName, i) => {
15790
+ const repo = data[`r${i}`];
15791
+ if (!repo) return;
15792
+ prs.set(fullName, repo.pullRequests.nodes.map((n) => ({
15793
+ ...n,
15794
+ headRefOid: n.headRefOid || n.headRef?.oid
15795
+ })));
15796
+ });
15797
+ return true;
15798
+ } catch (err) {
15799
+ lastBatch = prevBatch;
15800
+ throw err;
15781
15801
  }
15782
- return result;
15802
+ }
15803
+ /** Confirm a finished (merged/closed) PR so it leaves the manage list. */
15804
+ function dismissPr(workspaceId, fullName, number) {
15805
+ db.prepare("INSERT OR IGNORE INTO dismissed_prs (workspace_id, full_name, number) VALUES (?, ?, ?)").run(workspaceId, fullName, number);
15806
+ }
15807
+ /** Drop dismissed PRs from a repo's list — unless one reopened. */
15808
+ function withoutDismissed(workspaceId, fullName, list) {
15809
+ const dismissed = new Set(all("SELECT number FROM dismissed_prs WHERE workspace_id = ? AND full_name = ?", workspaceId, fullName).map((row) => row.number));
15810
+ return list.filter((pr) => pr.state === "OPEN" || !dismissed.has(pr.number));
15783
15811
  }
15784
15812
  //#endregion
15785
15813
  //#region src/server/status.ts
@@ -15813,9 +15841,9 @@ function statusForAll() {
15813
15841
  }
15814
15842
  /** Push-based freshness: while any SSE client is connected, recompute the
15815
15843
  * (shared, deduped) snapshot on a slow tick and publish it only when it
15816
- * actually changed — ports appearing, git changes, PR/CI flips reach every
15817
- * client in ~15s instead of waiting out their 60s poll, and idle systems
15818
- * push nothing at all. */
15844
+ * actually changed — ports appearing, PR/CI flips reach every client in
15845
+ * ~15s instead of waiting out their 60s poll, and idle systems push
15846
+ * nothing at all. */
15819
15847
  var WATCH_MS = 15e3;
15820
15848
  /** Right after a local push PR state flips within seconds — tick fast so
15821
15849
  * the badge lands while it still feels immediate. */
@@ -15869,37 +15897,33 @@ async function statusForAllFresh() {
15869
15897
  if (await refreshPrsBatch(activeRepoNames(), true).catch(() => false)) invalidateSnapshot();
15870
15898
  return statusForAll();
15871
15899
  }
15900
+ /** RSS here is the Node + Marko baseline (~80 MB empty, ~115 MB loaded),
15901
+ * not a leak and not worth chasing — young-gen flags do not move it. */
15872
15902
  async function computeStatus() {
15873
15903
  const workspaces = listWorkspaces().filter((ws) => ws.status === "active");
15874
15904
  const paths = new Map(workspaces.map((ws) => [ws.id, ws.path]));
15875
15905
  const [ports, states] = await Promise.all([portsByWorkspace(paths), sessionStates()]);
15876
- const result = {};
15877
15906
  const allBranches = listWorkspaces().map((w) => ({
15878
15907
  id: w.id,
15879
15908
  branch: w.branch
15880
15909
  }));
15881
- await Promise.all(workspaces.map(async (ws) => {
15910
+ const entries = await Promise.all(workspaces.map(async (ws) => {
15882
15911
  const foreign = allBranches.filter((w) => w.id !== ws.id).map((w) => w.branch);
15883
15912
  const statuses = await Promise.all(ws.repos.map((repo) => repoStatus(ws, repo, foreign)));
15884
- result[ws.id] = {
15913
+ return [ws.id, {
15885
15914
  ports: ports.get(ws.id) || [],
15886
- repos: statuses.map((s) => s.status),
15887
- ...sessionCounts(ws.id, states),
15888
- changes: statuses.reduce((sum, s) => sum + s.changed, 0)
15889
- };
15915
+ repos: statuses,
15916
+ ...sessionCounts(ws.id, states)
15917
+ }];
15890
15918
  }));
15891
- return result;
15919
+ return Object.fromEntries(entries);
15892
15920
  }
15893
15921
  async function repoStatus(ws, repo, foreign) {
15894
- const dir = path.join(ws.path, repo.worktree);
15895
- const [repoPrs, changed] = await Promise.all([workspacePrs(repo.full_name, ws.branch, foreign).then((list) => list.filter((pr) => pr.state === "OPEN")), changedFileCount(dir, ws.branch, repo.base_branch)]);
15922
+ const repoPrs = await workspacePrs(repo.full_name, ws.branch, foreign).then((list) => list.filter((pr) => pr.state === "OPEN"));
15896
15923
  return {
15897
- status: {
15898
- full_name: repo.full_name,
15899
- worktree: repo.worktree,
15900
- prs: repoPrs
15901
- },
15902
- changed
15924
+ full_name: repo.full_name,
15925
+ worktree: repo.worktree,
15926
+ prs: repoPrs
15903
15927
  };
15904
15928
  }
15905
15929
  function sessionCounts(workspaceId, states) {
@@ -15945,39 +15969,50 @@ var PADDING = `: ${" ".repeat(2048)}\n\n`;
15945
15969
  /** Heartbeat cadence: keeps intermediaries from idling the stream out and
15946
15970
  * lets the client detect a dead/buffered stream and fall back to polling. */
15947
15971
  var PING_MS = 15e3;
15972
+ var SSE_HEADERS = {
15973
+ "content-type": "text/event-stream",
15974
+ "cache-control": "no-store, no-transform",
15975
+ "x-accel-buffering": "no"
15976
+ };
15948
15977
  //#endregion
15949
15978
  //#region __marko-run__api.events.js
15950
15979
  var getHandler$14 = normalizeHandler(Run.GET((ctx) => {
15980
+ if (ctx.request.method === "HEAD") return new Response(null, { headers: SSE_HEADERS });
15951
15981
  const encoder = new TextEncoder();
15952
- const stream = new ReadableStream({ start(controller) {
15953
- controller.enqueue(encoder.encode(`retry: 3000\n${PADDING}event: hello\ndata: ${JSON.stringify({ boot: BOOT_ID })}\n\n`));
15954
- const ping = setInterval(() => {
15955
- try {
15956
- controller.enqueue(encoder.encode(`event: ping\ndata: 1\n\n`));
15957
- } catch {
15958
- clearInterval(ping);
15959
- }
15960
- }, PING_MS);
15961
- const unsubscribe = subscribe((event) => {
15962
- try {
15963
- controller.enqueue(encoder.encode(`event: ${event.type}\ndata: ${JSON.stringify(event.data ?? null)}\n\n`));
15964
- } catch {
15982
+ let cleanup = () => {};
15983
+ const stream = new ReadableStream({
15984
+ start(controller) {
15985
+ controller.enqueue(encoder.encode(`retry: 3000\n${PADDING}event: hello\ndata: ${JSON.stringify({ boot: BOOT_ID })}\n\n`));
15986
+ const ping = setInterval(() => {
15987
+ try {
15988
+ controller.enqueue(encoder.encode(`event: ping\ndata: 1\n\n`));
15989
+ } catch {
15990
+ clearInterval(ping);
15991
+ }
15992
+ }, PING_MS);
15993
+ const unsubscribe = subscribe((event) => {
15994
+ try {
15995
+ controller.enqueue(encoder.encode(`event: ${event.type}\ndata: ${JSON.stringify(event.data ?? null)}\n\n`));
15996
+ } catch {
15997
+ unsubscribe();
15998
+ }
15999
+ });
16000
+ cleanup = () => {
15965
16001
  unsubscribe();
15966
- }
15967
- });
15968
- ctx.request.signal.addEventListener("abort", () => {
15969
- unsubscribe();
15970
- clearInterval(ping);
15971
- try {
15972
- controller.close();
15973
- } catch {}
15974
- });
15975
- } });
15976
- return new Response(stream, { headers: {
15977
- "content-type": "text/event-stream",
15978
- "cache-control": "no-store, no-transform",
15979
- "x-accel-buffering": "no"
15980
- } });
16002
+ clearInterval(ping);
16003
+ };
16004
+ ctx.request.signal.addEventListener("abort", () => {
16005
+ cleanup();
16006
+ try {
16007
+ controller.close();
16008
+ } catch {}
16009
+ });
16010
+ },
16011
+ cancel() {
16012
+ cleanup();
16013
+ }
16014
+ });
16015
+ return new Response(stream, { headers: SSE_HEADERS });
15981
16016
  }), "GET");
15982
16017
  var get4_options = mergeOptions(mware3, getHandler$14);
15983
16018
  var head4_options = mergeOptions(mware3);
@@ -16133,12 +16168,16 @@ function readDiskCache() {
16133
16168
  }
16134
16169
  }
16135
16170
  async function fetchCatalog() {
16171
+ let partial = false;
16136
16172
  const orgs = (await gh([
16137
16173
  "api",
16138
16174
  "user/orgs",
16139
16175
  "--jq",
16140
16176
  ".[].login"
16141
- ]).catch(() => "")).split("\n").filter(Boolean);
16177
+ ]).catch(() => {
16178
+ partial = true;
16179
+ return "";
16180
+ })).split("\n").filter(Boolean);
16142
16181
  const lists = await Promise.all([void 0, ...orgs].map((owner) => gh([
16143
16182
  "repo",
16144
16183
  "list",
@@ -16147,7 +16186,10 @@ async function fetchCatalog() {
16147
16186
  "500",
16148
16187
  "--json",
16149
16188
  "nameWithOwner,defaultBranchRef,isArchived"
16150
- ]).catch(() => "[]")));
16189
+ ]).catch(() => {
16190
+ partial = true;
16191
+ return "[]";
16192
+ })));
16151
16193
  const byName = /* @__PURE__ */ new Map();
16152
16194
  for (const out of lists) for (const r of JSON.parse(out)) byName.set(r.nameWithOwner, {
16153
16195
  nameWithOwner: r.nameWithOwner,
@@ -16159,6 +16201,10 @@ async function fetchCatalog() {
16159
16201
  fetchedAt: Date.now(),
16160
16202
  repos
16161
16203
  };
16204
+ if (partial) {
16205
+ if (readDiskCache()) throw new Error("gh repo listing failed");
16206
+ return catalog;
16207
+ }
16162
16208
  const tmp = cachePath() + ".tmp";
16163
16209
  fs.writeFileSync(tmp, JSON.stringify(catalog));
16164
16210
  fs.renameSync(tmp, cachePath());
@@ -16478,8 +16524,356 @@ function post20(context) {
16478
16524
  return call(mware3, __mware22, context);
16479
16525
  }
16480
16526
  //#endregion
16527
+ //#region src/server/diff.ts
16528
+ var MAX_FILE_BYTES = 5e5;
16529
+ /** Diffs compare against remote-tracking refs, which only move when this
16530
+ * machine fetches or pushes — a PR merged on GitHub leaves them stale and
16531
+ * the diff shows already-merged work until the next fetch. Opening the
16532
+ * diff freshens the base refs itself (throttled per repo; each ref
16533
+ * fetched independently since one missing ref fails a combined fetch,
16534
+ * and capped so a slow remote can't hang the view — stale refs then
16535
+ * self-correct on the next open). */
16536
+ var FETCH_TTL_MS = 3e4;
16537
+ var FETCH_WAIT_MS = 8e3;
16538
+ var lastFetch = /* @__PURE__ */ new Map();
16539
+ async function freshenBaseRefs(dir, fullName, refs) {
16540
+ if (Date.now() - (lastFetch.get(fullName) ?? 0) < FETCH_TTL_MS) return;
16541
+ const fetches = [...new Set(refs)].map((ref) => git([
16542
+ "fetch",
16543
+ "--quiet",
16544
+ "--no-tags",
16545
+ "origin",
16546
+ ref
16547
+ ], dir).catch((err) => {
16548
+ if (missingRemoteRef(err)) return git([
16549
+ "update-ref",
16550
+ "-d",
16551
+ `refs/remotes/origin/${ref}`
16552
+ ], dir).catch(() => {});
16553
+ }));
16554
+ fetches.push(git([
16555
+ "remote",
16556
+ "prune",
16557
+ "origin"
16558
+ ], dir).catch(() => {}));
16559
+ let timer;
16560
+ await Promise.race([Promise.all(fetches), new Promise((resolve) => {
16561
+ timer = setTimeout(resolve, FETCH_WAIT_MS);
16562
+ })]);
16563
+ clearTimeout(timer);
16564
+ lastFetch.set(fullName, Date.now());
16565
+ }
16566
+ function missingRemoteRef(err) {
16567
+ const msg = err && typeof err === "object" && "stderr" in err ? String(err.stderr) : String(err);
16568
+ return /couldn't find remote ref/.test(msg);
16569
+ }
16570
+ /** The ref the working tree is compared against by default, best first.
16571
+ * Workspace branch / detached / foreign → `origin/<base>`. Our `-topic`
16572
+ * lane → `origin/<ws-branch>` (only two levels; `<ws>-a-b` still parents
16573
+ * to the workspace branch). `foreign` is other workspaces' branches so a
16574
+ * longer prefix is not stolen. An explicit `baseRef` overrides all. */
16575
+ async function resolveBase(dir, wsBranch, baseBranch, headBranch, foreign, baseRef) {
16576
+ const ours = ownsBranch(headBranch, wsBranch) && !foreign.some((x) => ownsBranch(headBranch, x));
16577
+ const candidates = [];
16578
+ if (baseRef) candidates.push([`origin/${baseRef}`, baseRef], [baseRef, baseRef]);
16579
+ else if (headBranch === wsBranch || headBranch === "HEAD" || !ours) {
16580
+ candidates.push([`origin/${baseBranch}`, baseBranch]);
16581
+ candidates.push([`origin/${wsBranch}`, `origin/${wsBranch}`]);
16582
+ } else {
16583
+ candidates.push([`origin/${wsBranch}`, `origin/${wsBranch}`]);
16584
+ candidates.push([`origin/${headBranch}`, `origin/${headBranch}`]);
16585
+ candidates.push([`origin/${baseBranch}`, baseBranch]);
16586
+ }
16587
+ for (const [ref, label] of candidates) try {
16588
+ return {
16589
+ mergeBase: await git([
16590
+ "merge-base",
16591
+ ref,
16592
+ "HEAD"
16593
+ ], dir),
16594
+ ref,
16595
+ label
16596
+ };
16597
+ } catch {}
16598
+ return {
16599
+ mergeBase: "HEAD",
16600
+ ref: "HEAD",
16601
+ label: baseRef || baseBranch,
16602
+ missing: true
16603
+ };
16604
+ }
16605
+ /** True when merging `head` into `base` is a no-op (head's unique work
16606
+ * is already in `base` — the squash-merge case). A conflict is not
16607
+ * containment; the caller falls through to three-dot. */
16608
+ async function treeContainedIn(dir, base, head) {
16609
+ try {
16610
+ return (await git([
16611
+ "merge-tree",
16612
+ "--write-tree",
16613
+ base,
16614
+ head
16615
+ ], dir)).split("\n")[0] === await git(["rev-parse", `${base}^{tree}`], dir);
16616
+ } catch {
16617
+ return false;
16618
+ }
16619
+ }
16620
+ /** Open PRs as GitHub's files tab (remote head vs the PR's `--base`).
16621
+ * Repos with no open PR fall back to the local checkout vs the derived
16622
+ * base. `pr` (`worktree/number`) focuses one PR. `uncommitted` overlays
16623
+ * the working tree on the PR whose head is the current branch. */
16624
+ async function workspaceDiff(ws, opts = {}) {
16625
+ const { baseRef, uncommitted = false, pr: focus } = opts;
16626
+ const foreign = otherWorkspaceBranches(ws.id);
16627
+ return (await Promise.all(ws.repos.map(async (repo) => {
16628
+ const dir = path.join(ws.path, repo.worktree);
16629
+ const branch = await git([
16630
+ "rev-parse",
16631
+ "--abbrev-ref",
16632
+ "HEAD"
16633
+ ], dir).catch(() => "HEAD");
16634
+ const localHead = await git(["rev-parse", "HEAD"], dir).catch(() => "");
16635
+ const open = (await workspacePrs(repo.full_name, ws.branch, foreign)).filter((p) => p.state === "OPEN" && p.baseRefName && p.headRefName && (!focus || focus === `${repo.worktree}/${p.number}`));
16636
+ if (open.length) return Promise.all(open.map((p) => prDiff(dir, repo, p, {
16637
+ uncommitted: uncommitted && branch === p.headRefName,
16638
+ localHead,
16639
+ branch
16640
+ })));
16641
+ if (focus) return [];
16642
+ return [await localDiff(dir, ws, repo, branch, localHead, baseRef, uncommitted)];
16643
+ }))).flat();
16644
+ }
16645
+ async function prDiff(dir, repo, pr, ctx) {
16646
+ await freshenBaseRefs(dir, repo.full_name, [pr.baseRefName, pr.headRefName]);
16647
+ const headRef = `origin/${pr.headRefName}`;
16648
+ const baseRef = `origin/${pr.baseRefName}`;
16649
+ const head = await git(["rev-parse", headRef], dir).catch(() => "") || pr.headRefOid || "";
16650
+ const baseSha = await git(["rev-parse", baseRef], dir).catch(() => "");
16651
+ if (!head || !baseSha) return {
16652
+ id: `${repo.worktree}#${pr.number}`,
16653
+ worktree: repo.worktree,
16654
+ full_name: repo.full_name,
16655
+ base: pr.baseRefName,
16656
+ default_branch: repo.default_branch,
16657
+ branch: ctx.branch,
16658
+ head,
16659
+ baseSha,
16660
+ pr: prMeta(pr),
16661
+ localHead: ctx.localHead,
16662
+ baseMissing: true,
16663
+ files: []
16664
+ };
16665
+ const mergeBase = await git([
16666
+ "merge-base",
16667
+ baseRef,
16668
+ head
16669
+ ], dir).catch(() => "");
16670
+ const contained = await treeContainedIn(dir, baseRef, head);
16671
+ const files = await collectFiles(dir, contained ? head : mergeBase || baseRef, head, ctx.uncommitted);
16672
+ const githubDrift = !!pr.headRefOid && head !== pr.headRefOid;
16673
+ return {
16674
+ id: `${repo.worktree}#${pr.number}`,
16675
+ worktree: repo.worktree,
16676
+ full_name: repo.full_name,
16677
+ base: pr.baseRefName,
16678
+ default_branch: repo.default_branch,
16679
+ branch: ctx.branch,
16680
+ head,
16681
+ baseSha,
16682
+ pr: prMeta(pr),
16683
+ localHead: ctx.localHead,
16684
+ ...githubDrift && { githubDrift: true },
16685
+ ...contained && { contained: true },
16686
+ files
16687
+ };
16688
+ }
16689
+ async function localDiff(dir, ws, repo, branch, localHead, baseRef, uncommitted) {
16690
+ await freshenBaseRefs(dir, repo.full_name, [
16691
+ ws.branch,
16692
+ ...branch !== "HEAD" ? [branch] : [],
16693
+ ...baseRef ? [baseRef] : [repo.base_branch]
16694
+ ]);
16695
+ const { mergeBase, ref, label, missing } = await resolveBase(dir, ws.branch, repo.base_branch, branch, otherWorkspaceBranches(ws.id), baseRef);
16696
+ if (missing && !uncommitted) return {
16697
+ id: repo.worktree,
16698
+ worktree: repo.worktree,
16699
+ full_name: repo.full_name,
16700
+ base: label,
16701
+ default_branch: repo.default_branch,
16702
+ branch,
16703
+ head: localHead,
16704
+ baseSha: localHead,
16705
+ baseMissing: true,
16706
+ files: []
16707
+ };
16708
+ const contained = !missing && await treeContainedIn(dir, ref, "HEAD");
16709
+ const against = contained ? "HEAD" : mergeBase;
16710
+ const baseSha = missing ? localHead : await git(["rev-parse", ref], dir).catch(() => mergeBase);
16711
+ const files = await collectFiles(dir, against, "HEAD", uncommitted);
16712
+ return {
16713
+ id: repo.worktree,
16714
+ worktree: repo.worktree,
16715
+ full_name: repo.full_name,
16716
+ base: label,
16717
+ default_branch: repo.default_branch,
16718
+ branch,
16719
+ head: localHead,
16720
+ baseSha,
16721
+ ...missing && { baseMissing: true },
16722
+ ...contained && { contained: true },
16723
+ files
16724
+ };
16725
+ }
16726
+ function prMeta(pr) {
16727
+ return {
16728
+ number: pr.number,
16729
+ title: pr.title,
16730
+ url: pr.url,
16731
+ headRefName: pr.headRefName,
16732
+ baseRefName: pr.baseRefName,
16733
+ ...pr.headRefOid && { headRefOid: pr.headRefOid }
16734
+ };
16735
+ }
16736
+ async function collectFiles(dir, oldRev, newRev, uncommitted) {
16737
+ const diffArgs = uncommitted ? [
16738
+ "diff",
16739
+ "--name-status",
16740
+ "-z",
16741
+ "--no-renames",
16742
+ oldRev
16743
+ ] : [
16744
+ "diff",
16745
+ "--name-status",
16746
+ "-z",
16747
+ "--no-renames",
16748
+ oldRev,
16749
+ newRev
16750
+ ];
16751
+ const [nameStatus, untracked] = await Promise.all([git(diffArgs, dir).catch(() => ""), uncommitted ? git([
16752
+ "ls-files",
16753
+ "--others",
16754
+ "--exclude-standard",
16755
+ "-z"
16756
+ ], dir).catch(() => "") : Promise.resolve("")]);
16757
+ const entries = [];
16758
+ const parts = nameStatus.split("\0").filter(Boolean);
16759
+ for (let i = 0; i + 1 < parts.length; i += 2) entries.push({
16760
+ path: parts[i + 1],
16761
+ status: parts[i]
16762
+ });
16763
+ for (const file of untracked.split("\0").filter(Boolean)) entries.push({
16764
+ path: file,
16765
+ status: "A"
16766
+ });
16767
+ const olds = await batchContents(dir, oldRev, entries.filter((e) => e.status !== "A").map((e) => e.path));
16768
+ const news = uncommitted ? null : await batchContents(dir, newRev, entries.filter((e) => e.status !== "D").map((e) => e.path));
16769
+ const files = await Promise.all(entries.map((e) => news ? fromBlobs(e.path, e.status, olds.get(e.path), news.get(e.path)) : loadWorktreeFile(dir, e.path, e.status, olds.get(e.path))));
16770
+ files.sort((a, b) => a.path.localeCompare(b.path));
16771
+ return files;
16772
+ }
16773
+ /** Blob contents for many paths through a single `git cat-file --batch`
16774
+ * process (instead of one `git show` per file). Blobs over MAX_FILE_BYTES
16775
+ * are skipped and mapped to "tooLarge". */
16776
+ async function batchContents(dir, rev, paths) {
16777
+ const result = /* @__PURE__ */ new Map();
16778
+ if (!paths.length) return result;
16779
+ await new Promise((resolve) => {
16780
+ const proc = spawn("git", ["cat-file", "--batch"], { cwd: dir });
16781
+ let buf = Buffer.alloc(0);
16782
+ let index = 0;
16783
+ let need = -1;
16784
+ const parse = () => {
16785
+ for (;;) if (need >= 0) {
16786
+ if (buf.length < need + 1) return;
16787
+ const content = buf.subarray(0, need);
16788
+ buf = buf.subarray(need + 1);
16789
+ need = -1;
16790
+ const p = paths[index++];
16791
+ if (content.length > MAX_FILE_BYTES) result.set(p, "tooLarge");
16792
+ else result.set(p, content.toString("utf8"));
16793
+ } else {
16794
+ const nl = buf.indexOf(10);
16795
+ if (nl < 0) return;
16796
+ const header = buf.subarray(0, nl).toString();
16797
+ buf = buf.subarray(nl + 1);
16798
+ if (header.endsWith(" missing")) {
16799
+ index++;
16800
+ continue;
16801
+ }
16802
+ need = Number(header.split(" ")[2]) || 0;
16803
+ }
16804
+ };
16805
+ proc.stdout.on("data", (chunk) => {
16806
+ buf = buf.length ? Buffer.concat([buf, chunk]) : chunk;
16807
+ parse();
16808
+ });
16809
+ proc.on("close", () => resolve());
16810
+ proc.on("error", () => resolve());
16811
+ proc.stdin.on("error", () => {});
16812
+ proc.stdin.end(paths.map((p) => `${rev}:${p}`).join("\n") + "\n");
16813
+ });
16814
+ return result;
16815
+ }
16816
+ function fromBlobs(filePath, status, old, neu) {
16817
+ const result = {
16818
+ path: filePath,
16819
+ status,
16820
+ old: null,
16821
+ new: null
16822
+ };
16823
+ if (old === "tooLarge" || neu === "tooLarge") result.tooLarge = true;
16824
+ else {
16825
+ if (old !== void 0) result.old = old;
16826
+ if (neu !== void 0) result.new = neu;
16827
+ }
16828
+ if (result.old?.includes("\0") || result.new?.includes("\0")) {
16829
+ result.binary = true;
16830
+ result.old = null;
16831
+ result.new = null;
16832
+ }
16833
+ if (result.tooLarge) {
16834
+ result.old = null;
16835
+ result.new = null;
16836
+ }
16837
+ return result;
16838
+ }
16839
+ async function loadWorktreeFile(dir, filePath, status, old) {
16840
+ const result = {
16841
+ path: filePath,
16842
+ status,
16843
+ old: null,
16844
+ new: null
16845
+ };
16846
+ if (old === "tooLarge") result.tooLarge = true;
16847
+ else if (old !== void 0) result.old = old;
16848
+ if (status !== "D" && !result.tooLarge) try {
16849
+ const root = path.resolve(dir);
16850
+ const full = path.resolve(dir, filePath);
16851
+ if (full !== root && !full.startsWith(root + path.sep)) throw new Error(`path escapes worktree: ${filePath}`);
16852
+ if ((await fsp.stat(full)).size > MAX_FILE_BYTES) result.tooLarge = true;
16853
+ else result.new = await fsp.readFile(full, "utf8");
16854
+ } catch {}
16855
+ if (result.old?.includes("\0") || result.new?.includes("\0")) {
16856
+ result.binary = true;
16857
+ result.old = null;
16858
+ result.new = null;
16859
+ }
16860
+ if (result.tooLarge) {
16861
+ result.old = null;
16862
+ result.new = null;
16863
+ }
16864
+ return result;
16865
+ }
16866
+ //#endregion
16481
16867
  //#region __marko-run__api.workspaces.$.diff.js
16482
- var getHandler$4 = normalizeHandler(Run.GET({ search: (value) => ({ base: typeof value.base === "string" && value.base ? value.base : void 0 }) }, async (ctx) => Response.json(await workspaceDiff(ctx.data.workspace, ctx.search.base))), "GET");
16868
+ var getHandler$4 = normalizeHandler(Run.GET({ search: (value) => ({
16869
+ base: typeof value.base === "string" && value.base ? value.base : void 0,
16870
+ uncommitted: value.uncommitted === "1" || value.uncommitted === "true",
16871
+ pr: typeof value.pr === "string" && value.pr ? value.pr : void 0
16872
+ }) }, async (ctx) => Response.json(await workspaceDiff(ctx.data.workspace, {
16873
+ baseRef: ctx.search.base,
16874
+ uncommitted: ctx.search.uncommitted,
16875
+ pr: ctx.search.pr
16876
+ }))), "GET");
16483
16877
  var get21_options = mergeOptions(mware3, mware22, getHandler$4);
16484
16878
  var head21_options = mergeOptions(mware3, mware22);
16485
16879
  function get21(context) {
@@ -20916,6 +21310,7 @@ function attachTermServer(server) {
20916
21310
  if (!wsPath) return ws.close(4004, "unknown workspace");
20917
21311
  const cols = Number(url.searchParams.get("cols")) || 80;
20918
21312
  const rows = Number(url.searchParams.get("rows")) || 24;
21313
+ const connectedVisible = url.searchParams.get("visible") !== "0";
20919
21314
  const proc = pty.spawn("tmux", [
20920
21315
  "-L",
20921
21316
  TMUX_SOCKET,
@@ -20961,10 +21356,11 @@ function attachTermServer(server) {
20961
21356
  flushTimer = setTimeout(flush, Date.now() - lastSend >= FLUSH_MS ? 1 : FLUSH_MS);
20962
21357
  });
20963
21358
  proc.onExit(() => ws.close());
20964
- let visible = true;
20965
- trackViewer(session.id, 1);
21359
+ let visible = connectedVisible;
21360
+ if (visible) trackViewer(session.id, 1);
20966
21361
  let dims = [cols, rows];
20967
21362
  const forceWindowSize = () => {
21363
+ if (!visible) return;
20968
21364
  lastForced.set(session.id, `${dims[0]}x${dims[1]}`);
20969
21365
  tmux([
20970
21366
  "resize-window",
@@ -20986,7 +21382,9 @@ function attachTermServer(server) {
20986
21382
  const resize = parseResize(text);
20987
21383
  if (resize) {
20988
21384
  dims = resize;
20989
- proc.resize(resize[0], resize[1]);
21385
+ try {
21386
+ proc.resize(resize[0], resize[1]);
21387
+ } catch {}
20990
21388
  forceWindowSize();
20991
21389
  }
20992
21390
  const vis = parseVisible(text);
@@ -21072,4 +21470,4 @@ server.listen(port, () => {
21072
21470
  //#endregion
21073
21471
  export {};
21074
21472
 
21075
- ;var __MARKO_MANIFEST__={"esfPH6H":{"block":[2,"stylesheet#/assets/index-KIy0rCx1.css",6,"<link",0," rel=\"stylesheet\" crossorigin href=\"",1,"assets/index-KIy0rCx1.css\"",">"],"defer":[2,"/assets/index-BAGH9WA4.js",6,"<script",0," async type=\"module\" crossorigin src=\"",1,"assets/index-BAGH9WA4.js\"","></script>"]}};
21473
+ ;var __MARKO_MANIFEST__={"esfPH6H":{"block":[2,"stylesheet#/assets/index-C_YqjXeW.css",6,"<link",0," rel=\"stylesheet\" crossorigin href=\"",1,"assets/index-C_YqjXeW.css\"",">"],"defer":[2,"/assets/index-Bx9UtyrE.js",6,"<script",0," async type=\"module\" crossorigin src=\"",1,"assets/index-Bx9UtyrE.js\"","></script>"]}};