From 028e1d4c3e89155860266f621a16977a81d739cf Mon Sep 17 00:00:00 2001 From: konard Date: Mon, 11 May 2026 14:19:33 +0000 Subject: [PATCH 1/4] Initial commit with task details Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: https://github.com/ProverCoderAI/docker-git/issues/269 --- .gitkeep | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitkeep diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 00000000..b6816271 --- /dev/null +++ b/.gitkeep @@ -0,0 +1 @@ +# .gitkeep file auto-generated at 2026-05-11T14:19:33.306Z for PR creation at branch issue-269-2e25dfa54dec for issue https://github.com/ProverCoderAI/docker-git/issues/269 \ No newline at end of file From 011e2e2687fae1a8afef7fba681282b4e3283430 Mon Sep 17 00:00:00 2001 From: konard Date: Mon, 11 May 2026 14:47:51 +0000 Subject: [PATCH 2/4] fix(web): restore SSH session toolbar after page reload Wire Open browser, Apply, Task manager, and New terminal handlers in the standalone /ssh/session/:id view so the full toolbar appears after reload, matching the dashboard-launched terminal. Closes #269 --- .changeset/restore-ssh-session-toolbar.md | 5 + docs/screenshots/issue-269-after-reload.png | Bin 0 -> 17518 bytes docs/screenshots/issue-269-expected.png | Bin 0 -> 9315 bytes .../src/web/app-terminal-session-handlers.ts | 266 ++++++++++++++++++ .../app/src/web/app-terminal-session-ui.tsx | 171 +++++++++++ packages/app/src/web/app-terminal-session.tsx | 244 +++++++++------- .../app-terminal-session-handlers.test.ts | 55 ++++ 7 files changed, 632 insertions(+), 109 deletions(-) create mode 100644 .changeset/restore-ssh-session-toolbar.md create mode 100644 docs/screenshots/issue-269-after-reload.png create mode 100644 docs/screenshots/issue-269-expected.png create mode 100644 packages/app/src/web/app-terminal-session-handlers.ts create mode 100644 packages/app/src/web/app-terminal-session-ui.tsx create mode 100644 packages/app/tests/docker-git/app-terminal-session-handlers.test.ts diff --git a/.changeset/restore-ssh-session-toolbar.md b/.changeset/restore-ssh-session-toolbar.md new file mode 100644 index 00000000..b6a7b093 --- /dev/null +++ b/.changeset/restore-ssh-session-toolbar.md @@ -0,0 +1,5 @@ +--- +"@prover-coder-ai/docker-git": patch +--- + +Restore the SSH session toolbar after a page reload on `/ssh/session/:id`. The standalone terminal view now wires Open browser, Apply, Task manager, and New terminal handlers in addition to Detach and Kill, matching the dashboard-launched terminal toolbar. diff --git a/docs/screenshots/issue-269-after-reload.png b/docs/screenshots/issue-269-after-reload.png new file mode 100644 index 0000000000000000000000000000000000000000..33ccb19bff2397fb0b66dad7b4765998cd3c2178 GIT binary patch literal 17518 zcmd74cU03$v_FasJtCkYpdesFM4D1WIvf=XRS*!6fb<@uhY%ui1Q7x0orpA%-lc@7 zsB}UP5R%XXgb*N*5Yis!-fOw{{eHhc-gIXG?~xPM#cQ80jr;i}>v%~@jOR5asW`vlKx2d5ugKbF8X z*mHs}`IN#*ajTH|i3Y-=BW!T?Jg!pnYuM(`RtPCyh*x272y$R4W9fYP_a?j38 z_rXX0b4IQv--ZluG%M#^WfKyAO8wvHd_Q$u3<1ORD${qoRL}k8peeL4=EqT zl+sZi0oF!F$TR!)rutv$jVqDz?;8IreLkYQF8DLU!C}Sm#rAj3bzguMxe=bTbN$*- zeSQ6e#jm2%Jgl7#t?mqk(U-oZM_KOAm!|xX+ePP=tckq)f7RlsIV3_=yj-edmt&Z# zE?H~@qhOIHS9VHm!>}McC`r|K>%&>Cu&?xRhYrmxZ2Q2+N3ZHBgiCe2zq&Y9!K)Hw{!)!FD%8i=P`*{+ z_c+JS5I=zDJ0Hr0p~nPbXV2c_W`)e@W-Slo-SSJ!fv75yw?fUfw7BhY%GEKFv1Dy(xQlmp(tu((qmN zLD-l~EXCaIdb14M%4G{*;bOT@Q@(P^CG%?DJV)>KX9R~Kac&BlG7disu0?}vb#5BM?{R zvP+RWqj|YFT>Ws{f#1V?Nc&-@Bv5*N!rh4zN`6DiT-BIM6Gi;4FmO{SV)Sv@564n7 z({<`>|1)35>9;w5^q~0qhrQBe>+;MO2z=0!a}x1TSN!kh9I@}KAIEl?mljO}t*wP` zY5s1h&F3m@?oc!OQF7;xb)NG7$5T{GduPJOQD(_gYBs6}V)_cIQSV72ka^6i7wR9E)?Msnx znN(3S&t<1ob~U%GdX^5i`fWNqcJrMyS66w_qw;~ALCgPw-1O2xsWix>OLj)oIj#9x z9A6J5h;>W`F^HFGmdcOM@Fwn>Zh`K0H=8BhK-wrc%|f{xv@r0(Rq zxVK?+(VuhDmh!M)4=O5aA^A>FXM>m=Zw%#LOLM;f2;FNgF&)zql7R*jj)CkxajIWu z98~80=6&+?-&Vz>la?jhG^l5y#45Ui-$*twS{VBlpeCkr7A@d!we`uyajO|UI2*7? zj&cHS>h+Rv@OQRjz?+`9BbVCHT#=L>SqTndHjC8l#3sMBIeA(NiF0ajNuoHOt2drh zUsQXLQ?r0e!VynPg;Mo!(cux|Kn zGx&u+h4i-G@bfpW_l^hq+)olLv(HkL1~1l>rBzej91C%~oa*t`C}O0anzrr#h8fuX zw(5)<7x*bQsL4MNAj^9Y%E$FuZCT#5Canl9tWbK`d0j0-L(x?6f>#%yXmE4MPft#1 zbgpO#IHmYQ6Pna{mHQ^P2_yXsikf#B&>Phs#QO~uEu1Jlu-j;uGq~WaVQ~{7W~)r5 zQVrgYg0@iwZue2bHS*$BA@|cDmfvg?3c={b8oRmIS=-Jrux=|gn9~HT|My9)!}L6C*Iaqx+-r5Lc1M!G;T;95+H0p2^@a#5_0c^ zikqJIUF<2nTOxGjp9H&RuOzqA;@sl747;pX`{HOkFb&(^v#@sCr|PNVj^IHL|B$Wc z($8L8l*(;=xabY(qL?!($`bE5g?Bdv^(CX=3xopd(4A=Q?ZH)&(-hG_J+XZeQ&yki2boK^scq{eL$ z?kAZ)-y8Y%6sEMal~_iyI&~<;e&2QOvGODcm$!eaTDV|934KD?>1iL(R?>1EpB7$91-0?kLF#Q4V=9J~s#2HZPnS!!tgI;Uo_#1Ks<;5-#DFm@_0;$$8Hj;wx1aA$;)QT5`D2w)`N$( zPX~B!Qkq+xyYCgG6XU5Svz zxD>2lFJ<9ioRaal*u0)|{H(8=8yBcu_ip%zF(5-=!mvKdGj?0j;}gM0P38%%W}b8_ z@s@In22iS~Z-^B9d89lF^BH=P0{kgkcDLPq*Ok8z&Sw#$p|}Kl{^f*fzZGdsOK*_>+LBcVOS;CjF?+hA%ZW z?<_F2xnR!Rz8D>{V!f{`FAKu~{$5woQb8V04(<0(qW)gsjyqtTDhr8pRo9;9R`ED+ zJgl;D=NeMVyX}c8;mD6B#kbO26|dWHg!88vxjcTV2B|mOS5|;&8u4|yPKfgMO;u0= z?1q%o;}WDvYL8P1-!+?!dDPeQFG$R7?`3}ky&}U9h`2XEs4BvTj-}tIG)&&^)u(9p z{#CGo?+c2$B_$C70Cf+g25XjA3>T8$?`Ea~a}*q(RZVdPit@m6N@-XxELTLikSlRY zrcw(aI!}h3AZVpcaP5E=NZwL6yu6L<+e`D;j6dfFlR!DGc*IKI;cCWxwjsj%2O*rU zHsj|NAsLGG3;ndVRdS>(*=dn8C8?Zx++05Ydq+N}epCQS$WUkk4LMi!4w zzvkgOJMb=%+c~&BBhV`_WHUAe&tA~Ys4IGnANkS7t}26$a6hKu1U2tIj!KVXn%|$_ zSRyICYI0=jg^_G*V~n*Ovreqe_gGQ@`JC_8TtRQmc>purO~hQS*3`S+{a+ zIM=JC5Y-xn=?iM_!5zx)88*A;oN3mkP&4!Xw$}g9gKdr)N58GKrcug>*R;HU6)`uM?`gGIg3Xtw|RAFtK;MwX_Nrui&7c+rC!KX}APO zl^OiS;33Huw*55upC?4+B2KGxN1jl&zh$U=LIaxRaHXd%aVGo5PbLp+o~E4nnfVt; zw*O(IezyErG)>A-aQPQV^#AO2ueQ(~hiajua&v<(xFm_V9-82j|Bcy8N?>U=1@}Hy zJN$bL*Hf|I-Sr!+lnz08*Kil-S~U9ykiUxuwEwiZ$7jOMEUBFnLpU}={^-M4F7}K2 zPXoF%khD{t4aS1xX?zMx`~HA^0R#UDAWEyptwzo=%+wlKR;=LNx+lA~tWy1S#XG99 zt*^AN{T}?(yK1x8YWFfThYBI^=xNXL$&iqc{&7?;g%Y^DwDgv!hD!(!Z>L~F1!LEe zH*As?{sjeL9fuA+|4p8EFBDw*JG=Ng7Vbe|)HgWysE}L*aB8+{KzF~=l|N1T(@(ml zfi{1VoFnq^i$4L#{{J%|ko!ILI*$K;HFPB`FRM7%f+x}F!umE^p2Zw%33=NUMq6iC=Kdk&xVH6a1Sq z)%lwg65BKmdT;s<G31;szQ=4L`OeQbqb>0QbUbi+{zh zL10gWx6dF{M-={a8n~YQLpIgg2yEAXS0(pLSK5#`SW;ETi z-u);m7xyfWI{ox_nYM%5PmxEDjXB%?(Wy{*YPzSvpH869>wo{BJVXbfzgxdMbe!?q z)$m+kkHFV3wAR*oyatHWi0qN4n`2&(july$k01JcZGBv|5>Nr|?N=VQ^WqB386W#O z!_PhnpT6K6eSYWz;Yga7ZVgzjBG8`d7OQ1?T{)bbVP?x~stLKXGZfPEL5S z%X+@1%ha(uf>4NS8x8L3ZFJgX01YPMMzk}nT})A5kS&fj+XO%R8kj9*7TA*pN-||@ z+#vZ|?x6d@U6MOl$;2{7OYAx+Y!A*YBh1vj+W0L3V?3?JW7^zsu0=W%0{Ws)UHe(++H-n+-c4lADQQeOJPE)7h4(ODx{DQH^Cm)a$6a}pw(ZVw}+ zn9bCfIrlH&2yC%n&l2h0=B&>XasFziOfZuTpDt_>2^qc+T^yF7&K6vdr1Ia(l1FbP z>h;+o0J(B`Gv-AVZ|XFj4Hq*5j3j|S>H9oSG58BkO)epN5y4VM>4qE0opz5fY_KR3 zzOwm3I`&9%q()p?Ncg9I57G4qRl|5)x$y-`y8i*qYSA8+3nMpccak*Gw3c$k_|(?Q zF>uIbu)4&H`%f#K@uVrhdndR|DNphpg1H$96GAo+M`^5{7utFS`OIqvXjWth2fdme zZ$2DuF6r__yY@_@+>oBWOJuAwrvVPN6_pZg^RkbrF!;98|7?<7~ z7^zB$5awb^$?Fu4vWii;CK>Ul{C)%LalT2|EZXD|^gZc=ED_|UCrd8gp}OF$nZLE< z#rEk#P}r0>y%+k_2v^WZ3E5Xuw6$e$?6C0mCq~`WW&tal^P1S(mnXL(pNPYS-W+=Y zp^zu}EDd(EaNQ}DEimQfR1{M*bo8d-VqPGZV5(3)o)e65 zRgiW>F^*SLxKfK6hNZPWktBGsXkSzf&?d_~;7^$d!jmh!|F}tAxXM?#niEO|Z&A7h zXu79-Vjzt)CYTXmyJbo@|6qMN;Ponclz6Z>-!2H(bho-7v{xQ1b??}Oi?D* zkPha)wbm4w4}VsSqnqzSFM32YtGd1EWZ4W64{|w#_nvcQGVj!kweep`yn&OqRPSK) zH=yP;UP~pHa7v^BRF98Q&CowOzeWv3C0AI@Ho_AQwXGi1kt&c`ZGtJ6)sS^r5 zxxp3P*jXdcj2bE4dhv;M(&d<4$9&b zQu^?IU$YOWCMc6UA!l*oTiJa7clk!s0xJ6;{LS7L0#xlekKPTR&iF({J0Y*dhDh^v zVjFuI*rOVkd$zi-qI)LTtCyO-SqJqsUgn z++Zifa4FrTmCVu-eM&kB-g3{>F8Nl=_nl1X;!|dRE50rzLV;yURu#BpB)GcZCqpacHcC$Er9%GG5>xk z^uVp-^P!);V+;qHhx|f@hXjtS=-PMU8S^|XvX@{Tm9MoCa8R*fG8#?!i zaQG-w_~>TD*s}$<7)Nf}6SEA}uUR5fPd~v+Sb#4&z#Xe$^Kplbr};6STwS@lQh<%9 z`y^(Y->rLbE8Zu6X{L*qbBCceMxB$&lbLg2) zXxrbM_r<=gjVQm@cIehZ$neLNNWd5}4555Xmfk+;eCSb(yWKUpK9njeO zE@loHYiOs4)oS6}{vR7P)6K>`fS24JK>WTn98%y9&IP64XcwEb>>n9JtrWs&}}hh>p64r{dz_j)}>-3bQ2GE zspG_n96M|?(~{KQDPZEz=9WKLg4+-R6?$U~ zY%wfA_-loiROa^BMqpKH`Z`3miiTJPwO$UQRKzco;@ayowOzs{TR%Iv$4_@VeWW#a zPZ&KKr8f*U*foeZ_SWfRW|E`|`?z=rU~eOD1>9TVt5*yAz@pVNo)W4`mwpM7cF+IL zs-Bazt$N(xjkUu+UmO5@$?~s{Pq27sYba&)?;x_T(L)FSO?` zKkIm}`**HVNRA8A`W(>nu?K#rFGEAmFh|u1e}rW}K9u+IU)@SNK{zzmoCtRmj(=zC z)ylD=lsK3a?AoJ<`(xA;eb%>QXD}*;yL6t;>WfiuyF5;A>sU?~rF-DP@A-v(QwLS1 za1vlvRl96?|GlnS-HO@^J27ML+jlr`?L=})oYPxxydPPmFD3LFCfEIA6o^b6E;j5r z490nNJqHSKzYg;pdZdD%@|(}WmQyhHy6(B7wf+14Kun*n6~HZ!DtPq?Hp`pdKT%AW z8=l`*_zUBv8G}urbojpQ$9KsCCsXMZMHHM>u@?H+&no{V(0CFD}PhYoJ{T4wRQ z6`kdCdmWXzQyb;D=BsAfSn9aeU)g>84}^cpFH61o$6q&Al8=}RT(60!8y$M*t?nv( z&_8DW3zAK7r`00+QnajTdMfrfKPK5W!>O6kp|+~lj#}qp$+AB_q1KkJ>Nox1bo-?W zp3f9hJ8n$xG_l3m*IBwC8qB*^&v=l@zDlL8rr`}(7}_fclKgI}1hi|xJ4qY6g|z*} zD!cIz-8K4}+#S|Dx!pqHru-*+?)01ldsz zld}`ioq!?ii#q4Ci1G@BVNr&_Cvy3#^z%fsv(iQ)skD#GojF|$NH8v_?ZxU!4NsE%$_HCX%AC8N3ma{Fxd8oypKhAXaXR~P&O?B zmO{roErd^(1jC)-2+Jnh)JywYlLRwx>6-m&WO$7W#^E23;D%GVCNe&vyjf3g0e@sAs5ilS$3=; zlqFTWWl{4z3;Lje`_(Gwi>$(MDs){-Yo!U;$$t{EhN4#{dQ8paE3Jptc2*KMVVX6z z&3yZ?*V%oK(4-|_vf5?1#XtK1+(&KYPO{caYw(Dv6Nf-)P_v5kCI!YCoLrIC6?ozo z+l&#iv~MP4+z_(oEUl1n?ri_gnI<5HXF1n;wdpgK?3MF0o(*E!W;>_qMg&5K%e=F_Gf2Zh$gDDyOFOTT>oEQ=%w82pwa@1FH^;1e!Y#0Wi6 z47~{ETqSVc+)V+ZwX1=+#T-fsaHPKIgEHd@Wda1+mDpSf3UhENV>6lzaIzD07)0Lv zJ@*Wjbtc1e2+_`&89<9;x0qf=k|TQf@CIr5{Xrx;8P+LE7y7lD54bQ|1m_c%z^?E4 zT2fTC_S5WHIhR*&U{*dm3p;NIu`W`U+iw_CZI8edbq8B+iTpcZY{WcLlbX1?OoPrp zhA?64liw(u}tIZEx57fE!Ey{@!8DBBKY~wr9_UJ%M(1gWbwatzr>819! z%)vOE6SfhWNVEJkk9VSa#ohDm^abi}2t7>2cPDN0-Zg2zYbk554!PEbG7l?v&l?C` zs)x~^wc<&g^|#E~<(!3L$zq--X;Y}kiP~3(yY}!E5CN!o>~lpZaDr!M>Ixq;5dLOj zJ(=mP06$KmS*PKMY54~bi5Bg3=;ck=KVRmq56>bw{*BN1&$JShrM_E_e9d6-Ykg{2 z;Rl&&N71Op4RjK?r1ztFv|0 z)d2WiC|0Uw+A70!&rIMTG29hlccVU}mOARLzp-_gMT#bRtSW>Dyjp&^79Q?A&-r@Fj$Z01zH$_LC92P3l??h%;$?Qy7EaZlW%3kjaUE&+7@Q5 zxjbKPgZE%BE45U>QevERKdtHHy7^$ND{eVP-SS4ZLm5qyoLgj6CX;RLU8ALUw zcn~~jm-^o2*e^S8x=ay_-9Nz^1FoKR22e6ySN|~Ysl7nREnh^!Bk#bi&^&X1Is z2714ZK>QR7j?WX3)4;P|aQ5zhv3Rgm$X^8<&x_9g;W||u{OOj?!6^QRPAycIYVMEK z{=@0z`aa$;_bs`fRORQQ+;_My{(+Sw@^!`k(9cUtGht0jvx9+at3|YTw1@Yl7Ayw? zybu0i8));1s8-pkb{O0wM1_rQqe2H59}3Ji%QF9GHAwJQ)!8*tQoKQ?J5EJydDr7V zH2GDKd7rBC{y*Gv@46_Ar%~B{YbrzA8*VN(ymmIWHb5I_U(|bD-`vsW{_P8YEcV!o zppeGym(7`a4c^jhY)| zO{(g+cpfPP@2|znyj8wYR`0jG*qbX}Y?xEy_oqc+H(nyXqb45-k(Eh1Ollg1Vh)l? zpu7qaED%k7k4($V8oOY>eE+IUS_{y63E_RNuRXJ^apc`ra%L?cKq_N*J!e542P?Jm zw(KtVC0wL-DXJ;AN+*lQhZ?2!Hq?%$Lts-H-_Xack{UH29jl~1MQN&_ivng1VLGQd z=V|6{0?vOl9sB^4mr^i-|Dp<*Z-D9bxuP>091b*KrWhNW!?u*^DzD-=eXL4pAKF@v z(szl1EO9*$C;hTJra4ac3imMK-X3YVKPy0;^88~P3+m`!j2~Ryb}3u+uCK4^347U zz_+u#0h7#Li-kNyi=}7?s2EawMP_d4MXA;u!K|s6YSyU~7{gu=NxZ*6)(PUxKU`llyr6AQ3-w_Dtil zq@@ksA<~!ZLddT%Nn>JqhI*$;bqnWERSsy5E-#n4Mnzj-YO%6NJ*mKTiKT*Inp*z2 z?ssIQ-XLvi&c~j~N31_zX)Uf?e)0T3xtC(n*f)@F-lqHC)|VP8PZwla48Cq` z=G%5Z(i+ll5Z{+%2dX6(y+6AA*O>UjN)xo?8ZOzFqLq@o>SDTinbI;kvTahn+=YZ? z4)2{Mz9$$d+cmpke)bPNo_kU7h4ksHn!$daro4OcfO{*DJAvqlT9bivBgMVBpr9Yh z)g!GjU*8R=*6tFQee3;B*s%l`qdoWbeML3!jWO#!X5f(cO0B6mSg=z;BNX2= zSW~QQkSyNcHvLwzW!+DXltiY$(X*y(4cJn1|a{~5+q3SX5=o7>Q9oU4H@Gx#RBy0lC3)#T0fwrAbqCg z4ZRRJaZ2CFGxeGBv4uWi+HwXIi5bEOXFFs-Ms|cF{SlhP1~o_4mo%@CUQo`|aW8PQ zn58p?-x(3s3GFzsYE9hQG*mfN$L&ki;!oDMrbkBCJTs+np9*RJax!}8V}M2=Pv(8< z^+mOId0LLYoRs<9&2Fg-Q4I;vO)p-K=K|M1AsO|txzjub@N;eQt!Nv$RIG@S`}TV( zGk!NVH_`1=FJ#z5TZWe0mM1vlaqNSN;_(@%t-Q>4w_8pdVB6Ri@`Xp;WZW)#HnS%^ zmL~X8hkTpB4`5r{y-Ck5%V_O|W>9h;Vh@#0w_vuq(M8XaeIGVSYgC6Sxn0K>SQybB z)>Tx?M;GJ2+<`nib9$FOrwSxHM7c>qAgx-CChJi@Ks*)#_rGrLygvh9%4P( zT*E@LoB0*2q{cn1UhwA1~%5wji#2mE@(zm3nGqGfpO@%5|Cc1MlF(Rbc3xL_CdV_5_gO zcr`=)Mn^;0oMAeS2hn?y&XCxhcLyTesN&7JGhvF=(1+s1{i<%U<+rYU81CAvq37dY zsLmC2-8*YEhZ8A~1c=?z$;{r&^whKsD|-WNbA`xOYUwVUXCM|WCmwJ1T&jhi=t0j@ zM3E6!G;}5L!I>6-!lTQQDo&Q)S$7GjG{^F-K-_e(vXhoc0HoqlLvA#qYo>TqvN91S zp~HMoX>D!}Y9eJd?{0;EXHlF9VY#L$N55u%ao?upo0(2bi|Zxe*7CmpTNi(gq-x6FLTJEhUHP> zCvfVs4Z>2MwuL**X`?{1ZnfN0HUPf4VpL=jswSOQ4>e4_ySlS|arwbwwS~lcuWO~P zty|-eys=T#GJz$bvN5Y?4(=y6Ig`ecO0@pYeSt|oZwYY zBVz@nvSH0m0yx2c9*j2Z<9lET849i4i&oIMHD<1N-^gSYoI^3%YD-0c4H?Ou7Vc$Q zYjnvDIsR3Px@=Q&d9n$h8W%%qfrq5DX!TBFhkLv;-Tb26{M^EvQ}0UxZA%a?4Gy(h zw)TX8^{i^I&ogo;f9OL>r2;GqEg{^2+>CGWOTu?zdp2uEvh=)mTXBQ$0uxl{uNuRS zO1iOH&k!X}I;FZ%{?2F!fRoXgX*yXG(pg@qD)fssq&g3?g(>Dg_w{a7#Ao={&^vLx z)Sm4Dw2(h!xFO3^WTe5$JNGiBzDs?5DAkov=)39ePP=YAEK*9i)6<;HsJI=%)x&;b zxy!-v+$CL$j5<4wn4M~JDH7`zY*%yGHf&llyr!TbQ8Nxl6HJwZxj9INRktfYS@8?0 zN}~OkQTyTFa(o@YKQj0jX@!+^9!pQd(~358MLV!jUGRDiAhWN_ zlxEYqupEsG-zj<`00=g_tTEZiHAD^ydG9h@yC~MGwk8l2WaNCzz(we_MOG5)eJuR- za#t5@2lJvRB{xLk;|+i2kAU&HhPo-WePk_{qJX)tYQjv_cR{r={?I#eRZF1>KT5}3 z@P?%MoN8=NCyTz(q86dH6mUgdW1|`-=iypBB9nx&U6~LJTP$x{yTk9LeRvt=Rv21! zxdxZ#A{{kIPRqb%5>mUaWFR`n%Qr`Y8?<0nC+)FOR<<1ykHw>E17dgj8N^cO z!eEhTj7nM0Ne$~5%y;X%5LqWwf_ig2cT}ct(Gt(*a?Pa%X*E(^NB?*PsVL3TElyu- zDM`awq~(WL3NN;vR9a4YfJ-mS<#iTglzhXX-Y1&^Kx2%~mRo3BYH($$ovWsX^YY8F z=@zCJGOl9AOM-78VFea#Ff#2qcc^4&; z^T*dYH%Skeg(n#1xsotO+o`Vcg{2$i;<-)R7YdOR+KW+RG*>~mUvI2VCDHFF? ze|G#S-~(fhz9szL_l?cC>|Q7K8ChC1G`Oa97BARs5N|jW249?s_UkTv)T9YEnM5{u z&6^Zb;)x#%A?B~nfwWsFsTBiN=?DH0O-}T3VKS5MlZ>x z8V$4#X$5{w>VT?oTi*{fs2}$!oL409F&aDxxZdNBtHEpgvjDXwu>qKoaFjl4${Tfl zf=iwh@;620naFnW?1rA$vrAalgTjrg$;*CRW%=C`L3k1`xq+G-*i?k_VI+N>{l1l&L(?T@U#0?TV z+bS0(-;%9@wDJhTV&^4IrPn=z@y5--k&NJJ6k0HyTyWu{wIXSETyp}kZEopViDImO zzAdaxi5Guaao?2}C_}KroCHnhIJDU~I^M(_cCi`W6a(YEfmo_@ZiMbuf^$@z%;TYk zIBzwA6Z^}0aBS!~TeU_4G5t&ub1d zFeIWv?AI1}1RS-a7`y9&i75u&6}-MRZ|6DqwCcgyopjD8t$t42sMHj5ud3$Q`tr(q zq|djPYur@px)j%(qC)M2;(!?eDD{oy=)n&)F>X#E<5W?$pIYNViN_%c6DoIxw%$Y= z9v9nMdl5S{m;2B1q5QglZ9~>qQe}a5h&=hO@Rr&_eU+hW1m$jxgn2GZ5T0ZKHf)Lg zPV*~{3h2qI3)c#B58h7@7Kac_WHVDge$WDF7wed;nAcPzHmv*qs>~gayU(eX9l4XK z+;=~Z`_0Osj*TzTriVzBt~?`}U@s%)z-3%JU!dvUojY(6U*pM0&5F94|NP1O3X2<~ z+1XhE*Cd?RFfBBv62UTwNWq4H5A*IT&T9%GnO{`C3`&B+e;|VMB4^7b?kSayP4_L9^+wFSW+Wf!)6%f~ZeG@M)70jsob&E` zuGsMLXR1Lki*U(GalWxs+`RESP3j@R52X`YrL7d*#SJ)R&h5@<-RKM9OE0ntPbRgH zv`&pdFvz=tV;+@AUCFr}QBJ^WYZt5$TQU39JhipiEKfs#mQxn6DSnL^`F-Ygfqxo3q^N}Kd$&^wr=jJ zN;42$1reJK^&}sld@;DROcY~=`GqCatXZN_1E=TbEDD*m`KvpH0k+OvF0IWZ+jZVe zWkl-*2i^*q(s*n&GWWpJ4eOidQY9qHzf-lA-AlB#=i{>^y2z&7AupjMV%93*i6Qfq zfwvlxmL=mSL{aZ9y8$K!V2}soaJpG9XLwX6|x`z>%6e`WWj*%Ns1~E%XEQx4~z3(2|tG`VDK=TO99-L2`d}-6k#0 zG%A;mX2S2q?DaFgIBDc6G-hr)CO+)kqaM{f+F%)zEy4duG{qEFOVv`CclZ)pM9F+> zahDNvRK5do$618WNo3$NWbOV+$ zT70{`vS_PS1oLIZ$WZj9#M1@A9>|1XBIjL3oDl+F2YsJ(tUz3jkwWe7U3!>i31#T| z8C!^r;0I4}FP$)n**Y>9v@VNB%x%qQ=uk8LGWw7x!0P(_y(? z#cD|UM-$9d6Tq2^M}z4pO7Y#iF_a=nyQyUBLOr?e%ImTDe*hpPv8f}8My zeTim(!VB9?4HQ3by2~8tcC(h;a2=CelCyv-*NKma@Oj$keanZ@jnO(h6lZJr_Wl9R zXfFJn$qNf!>RQ)MaOx^unOu7Q_<>ca8bDFki{>z&4f}!i6&kPQ@)h^Kd8GJ_pOA8J zv2wX9$G{q*4qFg6o{(4K6_{02tb8YY-~x(o2pKJG3^j?fom@O4%sHvpY3|h`Z?nd_ z<_E5%-pDmfY@c$(WQ@q$kbd@AJ=( z;(M7$9@@KszRN0gDB$X1`zy{dCjOy>ckc&F#$$SB34N`P+p?8MEu{Tbko0KiebRN? zjJ*pB)ImOYjemKc~(yNE~QV4w<1WFfMFD2&WfBo@(0Ryp` zxBmqFy1?W2F?M?VWx>eSPV7}`+%vd`(tKgZ8G)`Ak0Fi0`hBVB>=s`eY?b1k@G@Go zFeItO+%m?-@Ja6TB@!zeG2k$yn<>=a^=dISCMc>%;ZaIZ&B*hZd@^Zp#@)p5=t|1N z#@3d*7{*`{kUoUCh!%y87}#sCalZ4nA*Mw-7@4*{A9*B zcpZgi`P}U~_h6x>ZhJmqD~}uLu0(=v(zdT&JwW=~&?o$^QD?-{e*dc>CmQ2+pAaa3 zdBdIV+!U99n?$>t=+hbv-=k0lMP^`$0&c-_w_)M)LMQ&|k*Pe3WvK@WA^^r7pRbLm zw8jLjy^Sr@U2S2`x%riTDPoXX5 z7cni63w(>69k=m8ormWHvjXoY#S0c%HENhP`u59ZUZ`w+J%S&h_=O2i^t7}(SQ`U@ zdVlqv9*vDW`HWD@Ss!Uqjs~x7lzwKZ7#(-CvhVJO`v9t2Yl6LM=R5ghxnGG7Pkvw; zDY^HH8I93O#NI@|$Xd9yzc}Ww&igJcL(q1IK~^sGkn&2(DC|dFta~{j%nm7|#&+JX zb(+`+^{teccMWB33@>62zEf6fCfL-LF`vF9YFNs_8Floy2Tt}qULmvDos%u)$H14{;$s@O{TYFw?<#M_7s z6V5quU>_bg0OOCC|qy(QPpqW3jNk!Z`&h048Zw$34~NtsLi z_#7Z9`Pt$3q%Gk-o%WIYMxSL-F6dO{BM1o_b}IkYu`rb6U8DbW&Lr)WWfhsIS29S0 zc&=R-)$r^KeE+ftqG98Sk;p52j-0+}mkn`g6$MIdHR7eM@O_4xV-gRE${A?Addf+#+vtk-EFj z#YU^9o28y7k+yGNTewusW4TbcnL2t4VPQl$I@)pJ0SIjom)yLz7I73G>zkS;6H{11 z+08b)Yfn0Sl0M|WQMdEHZYAUs^73ZbT~hxIgw$faCQ7yqIMjk-?q*oRl#mX{$<5(+ zK@G)3;g1Ox08k>$!u)b{$gEsEsqw&!%kYZ%=8o9NL|t9#&`RB63a3y6M0=9ec|XZI zjZ6WVyR%YK(wJ(H#d<2BaCemrS)l-YQPr;-9!dj~2q%yck3$})beMKYR+_xrS_)nL z^fo_Xaq_sYD%3CaR3qcXp}c>X0J=Hnt*$sMW@7@Pe!D$-$J2b83vD zNw@ZIK-_%RPV0ds+cXZ_Z#T@d4b~b&UvFyao34ai>&#EC`IxL#xV3eu5x%qeZKGkj z?F`5B+P+l~*L*Cu2k}X>O07#*iD~iblpQD3`oRN-(0kl>LiFck=KdbC)Deb9b1|`C z?}3`j?d9IF>LC}BTG#){%ah3Iidmy6$8xxoCdY)A7u2bX{`08t_S-WzrH<*)o4z%0 za-6?*CpPy3rf2raB@>VX2*zzn* z?y={ek^Gr6|8l0(-_IGDXj*JI(WLqUk%g+P-uqm|&-7e3v$5(?zW3jR#A|f>qyqf$qpYb-~pnuMB(qZ6FK4kg rS1e7WuP3gTOCB~ybq=t?JLlh1op!60XgkGdc3sOQ+ zsgV+@2_-0mmIM??fP}!sbMAX%oFA|MyW{=Xd#<(j+GDTz&9&y5^P4diX8N3Gc+aq~ zuy7h0=vc9^9Jf4be>}x@bU!BQ20aq20ap6=S*nMwtsZqwfV54uSy*ZlIQIWMdDK7c zYv2&T!gB8O?~An;T;{^UBD8C$qiys25oMMu(RKnc{|gv*;bhjKbnxz2g!}mu$8R2& zlayL2zme(UJAtkJVpM54CE3rbY;9*re5hNc=o75uGI^&Z)2hkiRLSeiJW)QI$t1yZ z5!L6fxwC3t=w)+U(JM^ao+C6bw}-4l57$@2O?OB;OdIqMG=q)~Syx$}V=!g|){A|7 znLo`~k7mhI6_V(+|ix-|{Uvp<#Xt-;7q)%}Q z%o_Xy`pNN$n6vkwi8+el1ur0NfH}gJ(m(hY-3k06epTs-!qnf^(&FvKpTl5L5B{-b zV!Ei+e+{dSAA#}h2(6bH@@!L>pO&0*AHy?WtTw!|vbP5jw1FcyoCIutUtVP{+(tuV zMiO})+_VvB3VQ}#D+(T&Y>cAO?Bjy$`h!Gcj*W=C;}$h!ez@S@K>6@u6zdag)Dm!? zkk5WwJ&}{wO7^qf+hkgyx4-YLpy2I&Q`<$Z!nC7q4zl(Mrrb*u4Qx&%m(8hoD&pkmCHdHWlWH zJg(&cK}me08jzwlqB$%SHCFG6g3i9r=z0)s*o#$aZD=@{`}Z%UNY}piTx78t+Z$4K zt^s9RAQ~tQoQ=|$^0a^dMT11X8FMT#>(TA^TL}2575aZMZq83%_KI%Mn9?}vR%tpu zTeY0OA;>!)&763GCE*Xk_I}`v@%Jc4m#5&hou+v3(?ID-2YgU) zk`AkZ>4u9n$(o;j{C_g&Xqo@*OB7c(qm}w!EAC9&uaMRs)&rEhPzltEMgP{e66r$` z=oP5ZN5p~E`iB~+SKb8VB(4xciF+T=aqqo1;#(8^Y4AALneE!S6|tj*4Y6tF7ofxW z?D|!`NoUx1xPY1uWU?>c%U#7E-hypPr?F4!Gc^t^(X8iUV`xD>)4AlwsFuj(PxK`e z@G)cklSYju%HA*QTKChJ_oq$n$j+!}lG&wzY3EwMPVh1R5~LW0pb=VHIl}Ms z^I>hHcJ+eElaAoM?lB!2B`LlHu9$mU^Tv)FSsOaXgxfRV?Nu5M0Ak&#_*hHl!2 zY2mPQGK2CSA%VBgx^#$eO;0faKH29`zmkkyW*-<+)WnS!B5_rlHgg9*HTFEK#z-6W zH4K>}udBWke>J6uSnNbR$)HE62_y~5nYITVeOG*n@8UpQEW&O}El& zOnGp>wjGZqD9CN1=Hz&}i%$4nnK7N~2A!G#7O4-snW%SNb4|H7pPtVZON~4xc4dH? za-~(e==5Sk`;>ZvB-t3krjC!#sb3Yrl)wb`y{nO;Bvro>jg4E-eJ07p`)2d4c1r8? zYnkEndMzd*ur=7o*t+CdhZG7xQvJ)DZ_>D-oxVeCigX45ri4e^?$Qr*U@`b__kK`_ zC~6@Ffr_E`WZ8a~t6F%5OsU$C^X8{;BM1pK*3Io!oVkf%F#7wxMdKWehm~BVo;AtS z$k;~ElS9y5D*iHxGlLe>%~=;(oZ_q2D+f*8*g+>8nUu>xl3Ndj`94c?wL`yE?bYB^ zkj<5-$(9e5Uo3~FEVhtYZMayBb#mmPYNCXcZ?$GA8-veL=n)62Qg=I=@}O>s#jr!L!T-WcAv2oLwWnOTFBid#(b zMcA>i_L8T6bh%+^;!nGqObBIKw*Eja7wXcOA_qh;&7mKWaqn7q5Jt8yfPm^6n|BlJi;q!eM%3?{ z6x(11#&g)tA=Mz&pK@hC{%hI>6|Tye+~*oVoR{BPU@T8G4NPF^Y|Ib5N0H0Q4ei?& zV5FP+4MxJZQag-%v%DpjCjSXPtilIAnWWXt_m%ykY+bMD#q;wdv$S$>+HyhUeyp;I z)dfDLIEa9EPON?6?W+~X{v(OsTr8qK*>x=FEGD0IBpy)pr*EKHWMBRh*4Ni<0;q}C zy5wCsA`*R1$ja1sq22dP+-InD62lyPA)-SAY ze8DX4?` zx4D-pTXVf|?&e=sV#@4rz*pR?-wjWCgE3w(T~rOgbIV+V>;z7ys0#c0wpM?>u99=j zTc-F+dy6x$TqYQOkM4Vei0Zgc38k6?QL>d@YOjM-br|+Fo~M%0BBR&tVSF}h-~QfJ z*vLv~R@z32imO7Yx9Y(^u^>l1WrnaEGbqBg~ay09^C0`qwk)2R3^u2SNtsT zt_w~5Pk6>4C%(q_$7Nhlnau~AO7+;0&Rgj(NpCVkOS$6AckVE!ZR?N^O^sbXN3g9X zZl4CJx(<{SBu(7EXdRF~9FMfbqC!$0QMPatp+OWl=#u&!Y}^@9aS4mB08>@0?5WL+AK-Rz&a$Qb;!Z3DZ>Uf2blY+^pJnzA> z1xJ<4oEVF!qbPa%JZA*Q$%CkpT?Yuyq{f7NJwIjLMwGEqE1~9k@m@yypvJpeZP(0e z&sZ+CO@6t3*H;g;B7{>r4UZ1fI-SBE605(x;t85rSkG`U4f7u}j)*qTqQt%pRSb|{x{`K{a0+-rs=*O^WAP}XWD-Si>nffKsIq~Lb zEhNMoi+LvIUL9%h_WZ{&K80QWx{Y_fu@TLvXVuOdIfD}%-cqXebLhyKO%b`8@PxKp zQOkZQr}L+7Wu_u>w?B2+x#!#&Gb6N`Khhi(tk3b}3v3TNfGp~zB~y8F%d4DU&IqLs zr!JLIZdFC!SWndmaglsjs%uL5B@qJf=90>}|J>;(Ivh!o?oaxtw#}=`a84tM-H~el z;{=j(NHJ>Gy4tH)ME^oYR(X}oY|RW4XSTIYnKMP|&O zF|yjPkss7eS(aG6*j9qY=EnzaNAcC9Zb(hsFk$O91DQ=l$2HkEQ)Fw@E52VzIzRX9 zy)2-D`1`0C#&-HlHIP%;0@TL7z(4Z9;fOk4M7)d3@+(sJ0OST5F_gD~!4KrB%!uG^N1fLmXtdZp`3=IXmShpBpsd>kJ}-RD87w0gKWPlp z&++`6Ohx^PQ@|ym<1Cx&s7LPLYqn#tHF@SsIoehaD&|-!IBzhwoBu5AFIIyFXRyVE zNz?{UHv&C{pbdPycdEAl`mmjgCiL`J7#G){ouE*k_8@!aR8?DOmU^ z^_8!{4;6rAoYSsC$ZiLaE}Le<15It46osb!1p}b{Hy$&kyHf|#f0>h`k-|P8w@UqRqJgp z8-f%f|67WnWjH4D(IB{kS^}K_K<3fVEWxIWQ^aF#&55WFVWuPExitcfya)#{G)rf9 zpax61DMHbl){cIls)qBnsc&gfh2M2;X`|BJT8ln7di*O_Ne@N<-X6u)lmsM96sYQ* z2aWP5_Ac{wFq3Xn4zf~vM#A3@V^nmj8n+HrKQb)nk%l_2Gky&ZKE;j3QLMIY%kiFg zbRD@j0aM^kQ=}I!Q1o3b$idcSdidA)bmF*Gn6+4AX|lW8}@`iU?}c&7F1!hS)D@@A+&^Z)R3?^g5RGDWU-(Alb7F>!oZt z(6f(a%O{x2G>Oqo?rB(fovEHPGw*Bd1})t@p~;wmRl2I~RtbpkDD*D<8}q7$e__7m z0Lo~t{VVr~av^tpPkuVRSWD#UW6+kJ z%q^d;NfVozJ_#ry6q39@Qmu_%Z3=g{@5}~$hn(=oBw*5!ZQ_OC4oA_f%HBo-gB~Nr)Ty66!xh;$+4l6Bx&nB^plPz zWdmMsm18BqcaS>_rX{j3P1E2~cqNGk&nMi81@m~c`oXg!T_}duNZn~+ZrP^|X5T%T z=+#ZTM4)UPQDkxk( z<+B2`FRefmVPZNsw)o|;*y2cs!2Hb_$$SugTkFpJIBQ)^_lB1~%==&aVG8kkGB-E^sZ^ofuHJCj^py9e#;OErRU?58sjc;!a2y>3i& z`(>+*5sE_CM07_M>^w4TGPd6i_f=6ZSO! zT5R&vOWN@Oe{J)7!We*p9|sb|u)r@eg*|d^8?HsaODyz`CZ@y(2WX43>JbXqPoW>$ zEkj5)C)LM-|3?&sbH-i`pISX=L@4H^G*3~BT=Qd;@6W4-Y+vn4Y%Fr`_neNPHm4WI z8K!3c{74coX0x@|J7IFMyigI_mpG~a$^~nB#uOoW*Fev#+SZuOCSaGmVE^~(R zCq}I^T-R!Crl}qmT{AKXZgw$Ij+0 z2hmfftO-G6!%)&}j9k#F$mlIFCfjV}d3gPaS4C*?gX$n{-qaqwP@mB4t2-Cia(VCd z4y40nxDy)twV~}>uI@=3E`QR_xhZ4D>k}~f$7!yY;dFQsW6&?4r0=H?`+%qguOMOP z>5d+YqgZ4yh?zPA^2AafRo|jF33P0`d`Ke>C~46V$_h2vk|2{}OC?zR$rQ z#;TP6)Gnx$>%G|Ddc91|9Af{i^8X=^e}MeE3c&niFRF+G&F&24LTL^fdjI#efILy> z@I9jcn5i-x)*AVD$w8|_8A=a?s|1w1y(-_cUs2N<`=8I1o{b1Llj|_#ip2IwB0&3Z zRy+^r)b_kKCm8QevptG2Hmm=sV~l_S&%Z~>h3`LSNSbAk814TQEVi940|Jwvb`P6( zIBxt6#R#~uq2U`SDlM!dv1N>XA_Twu8yJhmcDeF?4N`juja)yxdFOwt?D1!3Igod^ ziKZf1Szd1FvtNiAD={_h$CBTjN8(Xj`6JyIhZu8l5bVAUYi+n2eS7sJ z*UI59yeDy`SD#pGdni?7Vj0os`~6d!e_OkEb`E5%T#oSx=f5(Nf4xH9w$eKg4a7A_ zltqX7PiEdZnG%!YFl02Mdg!=x2FEmut9-;XKZ&WJVt0wPo7=LJ1D-W#w~X{s3XHsR zXRp3FfH-EZA@bZeoH)fn?s7=svdP-+{C%9w;Zs zQ`)u`n+}(o>i+y37wm3UOJTZ@3jsu0V`IZ_04Dp53NnY5AJ#8{Hs=XU#68B>`b``N zi7N&ac71)xdfa7e$)22ijh}NbPGqVt>2vhp29rM3f!bK~n%S*0>|7e$IuuBia^I55 zCX!=POyC@=Qbd!d_y+gQy=CMdmEdB-$=%c3E{tsOKs&Vi5cu+mLSlHx#&z!bd`81C z!mmeA#qY;oZ~4_GWppL1hY(N9E(R)>lMGuBoO~K4N=%;=+#z#E;6S-v;1?E2GiG_2 z@^rk+(w~@SKR{_B$Pf+>b_P4>ju|0Qh6USs!M(5+>vq^M>`oU9R;nFXyI5?ckQoX- zJc#J}$cAIr-0c+hF1~ixxAO!aUIVQgdB^mgsL2&@#v+M>cuUMEaqgh0iIV&x%f-EO zoo}R>wgEFYQ}cd|ov&8gCZjdzywYP_2TS0uWhNr-$T8YCG=u-@Rq{Nf9;S}+NZVh5 z?L(1fW(C~JY9Z?l#`K!?^^8CVY5pyiyh&5IoUTtZkw-qv>*suz8~mW6CaAf6qhkH2 z=z)M}Ic&e{wgPH?$%zU{;ZX1Vnxlxxna$xj@%lJh&dq2p0VVCI(av%UwvZoRkc$(8 z*``|Wt>w2XzcV&E#H>rr6gM`)LV!5on}!{rp1tMl<@KEkf1-_#gXZCWLft`AhxC<8 z?kq2GpQ1K4@of0Y5Mb#w!}NZq57;;Kt0;o;`_5F;m~ z(UkF2@u(rB0dbC3L40xtnp!ceFvE;->M5=*vap!Dpfc=CJ5ljKKYd?RFO7MF`=lUTZbgCrCpD$K;%JvVbQscvt3Wi7% zR|YQ-^ zspuVWVBg_U&28|=zJO5S4P0wsHTCZ~L2@M1BdXK4A~k!r)i@abj#wXvGLrs!thV!cbLg?3{!x~gR4(T+8yWHdkv z{8tDo;_>Uqf)1>=vSbBH8~W2=GKKaXTgc}!Jz3=jNP#|JEdRV9R8;J0A3VD)Z>ec) zZDy(wC@ixEb4+kV_LJ(}hTBK>o!%2x!94gm45hHJWrV0&5Viz=x_F5G_1a;%%xoY{ z{ylhBFX*t7fxY$~6~s-?Z0ApHo~;D|plv9THVYtE35C-8)nV)nTav1XIArCjV@9suxP-6e%RCUz z6mu8p@OhZ(D-yTw(*Rs|4$ z&C=l@2OENFnr+j>Gd1jHI};3sd*7}4wIG(cF^(0KZt}rsAa{zVv(lFRmnzLf$NAo z*$@l)*q>MZAV04|Z96Qm9hREtkp}>3j<-7jlKX=c5Mvplj$&pn{sa=og5M-J39rfP zlO*6HD*;ul;?M0=>oWKGm9digDT&&VL$^bG$A(Ee?g-BhbKJ?q4Pym&_~G)_Q);<2 zck*eoEU2{vk%ql6JG@3qO-1Ffu;iFv6)lKG%vOW7q_Vk7CqJU0wLI-*wsD2LSq&e(dDN3B>(k4U(%vdk#Z5{uFHM^ literal 0 HcmV?d00001 diff --git a/packages/app/src/web/app-terminal-session-handlers.ts b/packages/app/src/web/app-terminal-session-handlers.ts new file mode 100644 index 00000000..b49e443f --- /dev/null +++ b/packages/app/src/web/app-terminal-session-handlers.ts @@ -0,0 +1,266 @@ +import { Effect } from "effect" +import { type Dispatch, type SetStateAction, useCallback, useState } from "react" + +import { + applyProject, + type ContainerTaskSnapshot, + createProjectTerminalSession, + loadProjectBrowser, + loadProjectTaskLogs, + loadProjectTasks, + projectBrowserCdpUrl, + projectBrowserNoVncUrl, + type ProjectBrowserSession, + stopProjectTask +} from "./api.js" +import { openUrl } from "./open-url.js" +import { terminalSessionRoutePath } from "./terminal.js" + +export type StateMessageUpdater = (message: string | null) => void + +export type ProjectHandlers = { + readonly onApplyProject: (() => void) | undefined + readonly onOpenBrowser: (() => void) | undefined + readonly onOpenTaskManager: (() => void) | undefined + readonly onOpenTerminal: (() => void) | undefined +} + +export type TaskHandlers = { + readonly logs: string + readonly onIncludeDefaultChange: (include: boolean) => void + readonly onLoadLogs: (pid: number) => void + readonly onRefresh: () => void + readonly onStopTask: (pid: number) => void + readonly refreshTasks: (include: boolean) => void + readonly snapshot: ContainerTaskSnapshot | null + readonly taskIncludeDefault: boolean +} + +const confirmApplyProject = (label: string): boolean => { + const dialog = globalThis.confirm + return typeof dialog === "function" + && dialog( + `Apply docker-git config to ${label}? This restarts the container and ends active SSH sessions and in-container browsers.` + ) +} + +const browserStatusMessage = (browser: ProjectBrowserSession): string => { + if (browser.status !== "running") { + return `Browser sidecar is ${browser.status}. Enable Playwright MCP and start the project first.` + } + const noVncUrl = projectBrowserNoVncUrl(browser) + return openUrl(noVncUrl) + ? `Browser opened. CDP endpoint: ${projectBrowserCdpUrl(browser)}.` + : `Browser popup was blocked. Open ${noVncUrl} manually. CDP endpoint: ${projectBrowserCdpUrl(browser)}.` +} + +const runOpenBrowser = (projectId: string, setMessage: StateMessageUpdater): void => { + void Effect.runPromise( + loadProjectBrowser(projectId).pipe( + Effect.tap((browser) => + Effect.sync(() => { + setMessage(browserStatusMessage(browser)) + }) + ), + Effect.catchAll((error) => + Effect.sync(() => { + setMessage(`Failed to open browser: ${error}`) + }) + ), + Effect.asVoid + ) + ) +} + +const runApplyProject = ( + projectId: string, + projectLabel: string, + setMessage: StateMessageUpdater +): void => { + if (!confirmApplyProject(projectLabel)) { + return + } + void Effect.runPromise( + applyProject(projectId).pipe( + Effect.tap((applied) => + Effect.sync(() => { + setMessage(`Applied ${applied.displayName}.`) + }) + ), + Effect.catchAll((error) => + Effect.sync(() => { + setMessage(`Apply failed: ${error}`) + }) + ), + Effect.asVoid + ) + ) +} + +const handleTerminalCreated = (sessionId: string, setMessage: StateMessageUpdater): void => { + const targetUrl = `${globalThis.location.origin}${terminalSessionRoutePath(sessionId)}` + if (!openUrl(targetUrl)) { + setMessage(`New terminal popup was blocked. Open ${targetUrl} manually.`) + } +} + +const runOpenTerminal = (projectKey: string, setMessage: StateMessageUpdater): void => { + void Effect.runPromise( + createProjectTerminalSession(projectKey).pipe( + Effect.tap((created) => + Effect.sync(() => { + handleTerminalCreated(created.session.id, setMessage) + }) + ), + Effect.catchAll((error) => + Effect.sync(() => { + setMessage(`Failed to open new terminal: ${error}`) + }) + ), + Effect.asVoid + ) + ) +} + +export type ProjectActionHandlersArgs = { + readonly onOpenTaskManagerRequest: () => void + readonly projectId: string | undefined + readonly projectKey: string | undefined + readonly projectLabel: string + readonly setMessage: StateMessageUpdater +} + +export const useProjectActionHandlers = ( + { onOpenTaskManagerRequest, projectId, projectKey, projectLabel, setMessage }: ProjectActionHandlersArgs +): ProjectHandlers => ({ + onApplyProject: projectId === undefined ? undefined : () => { + runApplyProject(projectId, projectLabel, setMessage) + }, + onOpenBrowser: projectId === undefined ? undefined : () => { + runOpenBrowser(projectId, setMessage) + }, + onOpenTaskManager: projectId === undefined ? undefined : onOpenTaskManagerRequest, + onOpenTerminal: projectId === undefined || projectKey === undefined + ? undefined + : () => { + runOpenTerminal(projectKey, setMessage) + } +}) + +const runRefreshTasks = ( + projectId: string, + include: boolean, + setSnapshot: Dispatch>, + setMessage: StateMessageUpdater +): void => { + void Effect.runPromise( + loadProjectTasks(projectId, include).pipe( + Effect.tap((next) => + Effect.sync(() => { + setSnapshot(next) + }) + ), + Effect.catchAll((error) => + Effect.sync(() => { + setMessage(`Failed to load tasks: ${error}`) + }) + ), + Effect.asVoid + ) + ) +} + +const runStopTask = ( + projectId: string, + pid: number, + setMessage: StateMessageUpdater, + onAfterStop: () => void +): void => { + void Effect.runPromise( + stopProjectTask(projectId, pid).pipe( + Effect.tap(() => Effect.sync(onAfterStop)), + Effect.catchAll((error) => + Effect.sync(() => { + setMessage(`Failed to stop task ${pid}: ${error}`) + }) + ), + Effect.asVoid + ) + ) +} + +const runLoadLogs = ( + projectId: string, + pid: number, + setLogs: Dispatch>, + setMessage: StateMessageUpdater +): void => { + void Effect.runPromise( + loadProjectTaskLogs(projectId, pid).pipe( + Effect.tap((output) => + Effect.sync(() => { + setLogs(output) + }) + ), + Effect.catchAll((error) => + Effect.sync(() => { + setMessage(`Failed to load logs for ${pid}: ${error}`) + }) + ), + Effect.asVoid + ) + ) +} + +export type TaskManagerHandlersArgs = { + readonly projectId: string | undefined + readonly setMessage: StateMessageUpdater +} + +export const useTaskManagerHandlers = ( + { projectId, setMessage }: TaskManagerHandlersArgs +): TaskHandlers => { + const [snapshot, setSnapshot] = useState(null) + const [logs, setLogs] = useState("") + const [taskIncludeDefault, setTaskIncludeDefault] = useState(false) + + const refreshTasks = useCallback((include: boolean) => { + if (projectId !== undefined) { + runRefreshTasks(projectId, include, setSnapshot, setMessage) + } + }, [projectId, setMessage]) + + const onStopTask = useCallback((pid: number) => { + if (projectId !== undefined) { + runStopTask(projectId, pid, setMessage, () => { + refreshTasks(taskIncludeDefault) + }) + } + }, [projectId, refreshTasks, setMessage, taskIncludeDefault]) + + const onLoadLogs = useCallback((pid: number) => { + if (projectId !== undefined) { + runLoadLogs(projectId, pid, setLogs, setMessage) + } + }, [projectId, setMessage]) + + const onIncludeDefaultChange = useCallback((include: boolean) => { + setTaskIncludeDefault(include) + refreshTasks(include) + }, [refreshTasks]) + + const onRefresh = useCallback(() => { + refreshTasks(taskIncludeDefault) + }, [refreshTasks, taskIncludeDefault]) + + return { + logs, + onIncludeDefaultChange, + onLoadLogs, + onRefresh, + onStopTask, + refreshTasks, + snapshot, + taskIncludeDefault + } +} diff --git a/packages/app/src/web/app-terminal-session-ui.tsx b/packages/app/src/web/app-terminal-session-ui.tsx new file mode 100644 index 00000000..9758ff95 --- /dev/null +++ b/packages/app/src/web/app-terminal-session-ui.tsx @@ -0,0 +1,171 @@ +import type { CSSProperties, JSX } from "react" + +import type { ProjectDetails } from "./api.js" +import type { ProjectHandlers, TaskHandlers } from "./app-terminal-session-handlers.js" +import { Box, Text } from "./elements.js" +import { TaskPanel } from "./panel-tasks.js" +import { TerminalPanel } from "./panel-terminal.js" +import type { ActiveTerminalSession } from "./terminal.js" +import type { ViewportLayout } from "./viewport-layout.js" + +export const terminalOnlyContainerStyle: CSSProperties = { + display: "flex", + flexDirection: "column", + height: "100%", + minHeight: 0, + overflow: "hidden", + padding: "8px", + width: "100%" +} + +const terminalOnlyMessageStyle: CSSProperties = { + background: "#101419", + border: "1px solid #3a4652", + borderRadius: "8px", + color: "#f6d27b", + flexShrink: 0, + marginBottom: "8px", + overflow: "hidden", + padding: "8px", + textOverflow: "ellipsis", + whiteSpace: "nowrap" +} + +const taskManagerBodyStyle: CSSProperties = { + background: "#080a0d", + boxSizing: "border-box", + color: "#d6e5f7", + height: "100%", + overflow: "auto", + padding: "10px" +} + +const taskManagerToolbarStyle: CSSProperties = { + alignItems: "center", + display: "flex", + justifyContent: "flex-end", + marginBottom: "10px" +} + +const taskManagerReturnButtonStyle: CSSProperties = { + background: "#171d24", + border: "1px solid #3a4652", + borderRadius: "8px", + color: "#d6e5f7", + cursor: "pointer", + font: "inherit", + padding: "6px 10px" +} + +export const TerminalOnlyMessage = ( + { message }: { readonly message: string | null } +): JSX.Element | null => message === null ? null :
{message}
+ +type TaskManagerBodyProps = { + readonly onClose: () => void + readonly project: ProjectDetails | null + readonly tasks: TaskHandlers +} + +const TerminalTaskManagerBody = ( + { onClose, project, tasks }: TaskManagerBodyProps +): JSX.Element => ( +
+
+ +
+ +
+) + +export type RenderTaskManagerBodyArgs = { + readonly onCloseTaskManager: () => void + readonly project: ProjectDetails | null + readonly projectId: string | undefined + readonly taskManagerOpen: boolean + readonly tasks: TaskHandlers +} + +export const renderTaskManagerBody = ( + { onCloseTaskManager, project, projectId, taskManagerOpen, tasks }: RenderTaskManagerBodyArgs +): JSX.Element | undefined => + taskManagerOpen && projectId !== undefined + ? + : undefined + +export type TerminalOnlyCallbacks = { + readonly onAttachFailure: () => void + readonly onDetach: () => void + readonly onKill: () => void + readonly onMessage: (message: string) => void +} + +export type TerminalOnlyTerminalPanelArgs = { + readonly bodyContent: JSX.Element | undefined + readonly callbacks: TerminalOnlyCallbacks + readonly handlers: ProjectHandlers + readonly session: ActiveTerminalSession + readonly viewportLayout: ViewportLayout +} + +export const TerminalOnlyTerminalPanel = ( + { bodyContent, callbacks, handlers, session, viewportLayout }: TerminalOnlyTerminalPanelArgs +): JSX.Element => ( + +) + +export const TerminalOnlyClosed = ({ message }: { readonly message: string }): JSX.Element => ( + + + SSH terminal + {message} + + +) + +export const TerminalOnlyLoading = ({ sessionId }: { readonly sessionId: string }): JSX.Element => ( + + + SSH terminal + session: {sessionId} + Attaching terminal... + + +) + +export const TerminalOnlyError = ( + { apiBaseUrl, message }: { readonly apiBaseUrl: string; readonly message: string } +): JSX.Element => ( + + + SSH terminal unavailable + target: {apiBaseUrl} + {message} + + +) diff --git a/packages/app/src/web/app-terminal-session.tsx b/packages/app/src/web/app-terminal-session.tsx index 0d62c2c9..1957926b 100644 --- a/packages/app/src/web/app-terminal-session.tsx +++ b/packages/app/src/web/app-terminal-session.tsx @@ -1,10 +1,29 @@ import { Effect, Match } from "effect" -import { type CSSProperties, type Dispatch, type JSX, type SetStateAction, useEffect, useState } from "react" - -import { deleteTerminalSessionByPath, loadTerminalSessionById, resolveApiBaseUrl } from "./api.js" +import { type Dispatch, type JSX, type SetStateAction, useCallback, useEffect, useState } from "react" + +import { + deleteTerminalSessionByPath, + loadProjectDetails, + loadTerminalSessionById, + type ProjectDetails, + resolveApiBaseUrl +} from "./api.js" import { buildTerminalOnlyActiveSession } from "./app-terminal-session-core.js" -import { Box, Text } from "./elements.js" -import { TerminalPanel } from "./panel-terminal.js" +import { + type ProjectHandlers, + type TaskHandlers, + useProjectActionHandlers, + useTaskManagerHandlers +} from "./app-terminal-session-handlers.js" +import { + renderTaskManagerBody, + TerminalOnlyClosed, + terminalOnlyContainerStyle, + TerminalOnlyError, + TerminalOnlyLoading, + TerminalOnlyMessage, + TerminalOnlyTerminalPanel +} from "./app-terminal-session-ui.js" import type { ActiveTerminalSession } from "./terminal.js" import type { ViewportLayout } from "./viewport-layout.js" @@ -21,29 +40,6 @@ type TerminalOnlyState = type TerminalOnlyStateSetter = Dispatch> -const terminalOnlyContainerStyle: CSSProperties = { - display: "flex", - flexDirection: "column", - height: "100%", - minHeight: 0, - overflow: "hidden", - padding: "8px", - width: "100%" -} - -const terminalOnlyMessageStyle: CSSProperties = { - background: "#101419", - border: "1px solid #3a4652", - borderRadius: "8px", - color: "#f6d27b", - flexShrink: 0, - marginBottom: "8px", - overflow: "hidden", - padding: "8px", - textOverflow: "ellipsis", - whiteSpace: "nowrap" -} - const terminalOnlyLoadingState = (sessionId: string): TerminalOnlyState => ({ _tag: "Loading", sessionId @@ -60,9 +56,7 @@ const terminalOnlyClosedState = (message: string): TerminalOnlyState => ({ message }) -const loadTerminalOnlyState = ( - sessionId: string -): Effect.Effect => +const loadTerminalOnlyState = (sessionId: string): Effect.Effect => loadTerminalSessionById(sessionId).pipe( Effect.match({ onFailure: (message) => terminalOnlyErrorState(message), @@ -78,89 +72,121 @@ const closeTerminalSession = (session: ActiveTerminalSession): void => { void Effect.runPromise(deleteTerminalSessionByPath(session.closePath).pipe(Effect.either, Effect.asVoid)) } -const updateReadyMessage = ( - setState: TerminalOnlyStateSetter, - message: string | null -): void => { - setState((current) => - current._tag === "Ready" - ? { - ...current, - message - } - : current - ) +const updateReadyMessage = (setState: TerminalOnlyStateSetter, message: string | null): void => { + setState((current) => current._tag === "Ready" ? { ...current, message } : current) +} + +const useLoadedProjectDetails = (projectId: string | undefined): ProjectDetails | null => { + const [project, setProject] = useState(null) + useEffect(() => { + if (projectId === undefined) { + return + } + let cancelled = false + void Effect.runPromise( + loadProjectDetails(projectId).pipe( + Effect.tap((details) => + Effect.sync(() => { + if (!cancelled) { + setProject(details) + } + }) + ), + Effect.catchAll(() => Effect.sync(() => {})), + Effect.asVoid + ) + ) + return () => { + cancelled = true + } + }, [projectId]) + return project } -const TerminalOnlyMessage = ({ message }: { readonly message: string | null }): JSX.Element | null => - message === null ? null :
{message}
+type TerminalOnlyReadyArgs = { + readonly session: ActiveTerminalSession + readonly setState: TerminalOnlyStateSetter + readonly state: Extract + readonly viewportLayout: ViewportLayout +} + +const useTerminalOnlyReadyState = ( + session: ActiveTerminalSession, + setState: TerminalOnlyStateSetter +): { + readonly handlers: ProjectHandlers + readonly project: ProjectDetails | null + readonly setMessage: (message: string | null) => void + readonly tasks: TaskHandlers + readonly taskManagerOpen: boolean + readonly setTaskManagerOpen: Dispatch> +} => { + const projectId = session.browserProjectId + const projectKey = session.browserProjectKey + const projectLabel = session.browserProjectName ?? projectId ?? "this project" + const project = useLoadedProjectDetails(projectId) + const [taskManagerOpen, setTaskManagerOpen] = useState(false) + const setMessage = useCallback( + (message: string | null) => { + updateReadyMessage(setState, message) + }, + [setState] + ) + const tasks = useTaskManagerHandlers({ projectId, setMessage }) + const handlers = useProjectActionHandlers({ + onOpenTaskManagerRequest: () => { + setTaskManagerOpen(true) + tasks.refreshTasks(tasks.taskIncludeDefault) + }, + projectId, + projectKey, + projectLabel, + setMessage + }) + return { handlers, project, setMessage, setTaskManagerOpen, taskManagerOpen, tasks } +} const TerminalOnlyReady = ( - { + { session, setState, state, viewportLayout }: TerminalOnlyReadyArgs +): JSX.Element => { + const { handlers, project, setMessage, setTaskManagerOpen, taskManagerOpen, tasks } = useTerminalOnlyReadyState( session, - setState, - state, - viewportLayout - }: { - readonly session: ActiveTerminalSession - readonly setState: TerminalOnlyStateSetter - readonly state: Extract - readonly viewportLayout: ViewportLayout - } -): JSX.Element => ( -
- - { - setState(terminalOnlyErrorState(`Terminal websocket closed before attach: ${session.session.id}.`)) - }} - onDetach={() => { - setState(terminalOnlyClosedState(`Detached SSH terminal: ${session.session.id}.`)) - }} - onKill={() => { - closeTerminalSession(session) - setState(terminalOnlyClosedState(`Killed SSH terminal: ${session.session.id}.`)) - }} - onMessage={(message) => { - updateReadyMessage(setState, message) - }} - session={session} - /> -
-) - -const TerminalOnlyClosed = ({ message }: { readonly message: string }): JSX.Element => ( - - - SSH terminal - {message} - - -) - -const TerminalOnlyLoading = ({ sessionId }: { readonly sessionId: string }): JSX.Element => ( - - - SSH terminal - session: {sessionId} - Attaching terminal... - - -) - -const TerminalOnlyError = ( - { apiBaseUrl, message }: { readonly apiBaseUrl: string; readonly message: string } -): JSX.Element => ( - - - SSH terminal unavailable - target: {apiBaseUrl} - {message} - - -) + setState + ) + const bodyContent = renderTaskManagerBody({ + onCloseTaskManager: () => { + setTaskManagerOpen(false) + }, + project, + projectId: session.browserProjectId, + taskManagerOpen, + tasks + }) + return ( +
+ + { + setState(terminalOnlyErrorState(`Terminal websocket closed before attach: ${session.session.id}.`)) + }, + onDetach: () => { + setState(terminalOnlyClosedState(`Detached SSH terminal: ${session.session.id}.`)) + }, + onKill: () => { + closeTerminalSession(session) + setState(terminalOnlyClosedState(`Killed SSH terminal: ${session.session.id}.`)) + }, + onMessage: setMessage + }} + handlers={handlers} + session={session} + viewportLayout={viewportLayout} + /> +
+ ) +} const renderTerminalOnlyState = ( state: TerminalOnlyState, diff --git a/packages/app/tests/docker-git/app-terminal-session-handlers.test.ts b/packages/app/tests/docker-git/app-terminal-session-handlers.test.ts new file mode 100644 index 00000000..657d9a5e --- /dev/null +++ b/packages/app/tests/docker-git/app-terminal-session-handlers.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from "vitest" + +import { type ProjectHandlers, useProjectActionHandlers } from "../../src/web/app-terminal-session-handlers.js" + +const noopMessage = (_message: string | null): void => {} +const noopOpenTaskManager = (): void => {} + +const buildHandlers = ( + overrides: Partial[0]> = {} +): ProjectHandlers => + useProjectActionHandlers({ + onOpenTaskManagerRequest: noopOpenTaskManager, + projectId: "project-1", + projectKey: "octocat/hello-world", + projectLabel: "octocat/hello-world", + setMessage: noopMessage, + ...overrides + }) + +describe("useProjectActionHandlers", () => { + it("returns all four project action handlers when project context is present", () => { + const handlers = buildHandlers() + expect(typeof handlers.onApplyProject).toBe("function") + expect(typeof handlers.onOpenBrowser).toBe("function") + expect(typeof handlers.onOpenTaskManager).toBe("function") + expect(typeof handlers.onOpenTerminal).toBe("function") + }) + + it("hides all handlers when projectId is missing", () => { + const handlers = buildHandlers({ projectId: undefined }) + expect(handlers.onApplyProject).toBeUndefined() + expect(handlers.onOpenBrowser).toBeUndefined() + expect(handlers.onOpenTaskManager).toBeUndefined() + expect(handlers.onOpenTerminal).toBeUndefined() + }) + + it("hides only onOpenTerminal when projectKey is missing", () => { + const handlers = buildHandlers({ projectKey: undefined }) + expect(typeof handlers.onApplyProject).toBe("function") + expect(typeof handlers.onOpenBrowser).toBe("function") + expect(typeof handlers.onOpenTaskManager).toBe("function") + expect(handlers.onOpenTerminal).toBeUndefined() + }) + + it("wires onOpenTaskManager to the supplied request callback", () => { + let opened = 0 + const handlers = buildHandlers({ + onOpenTaskManagerRequest: () => { + opened += 1 + } + }) + handlers.onOpenTaskManager?.() + expect(opened).toBe(1) + }) +}) From 7b46abcaae3164236efc86ae7d965f045ca0f0a5 Mon Sep 17 00:00:00 2001 From: konard Date: Mon, 11 May 2026 15:01:08 +0000 Subject: [PATCH 3/4] fix(web): use Effect.match instead of catchAll in session handlers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces six tap+catchAll pairs in app-terminal-session-handlers.ts with Effect.match({ onFailure, onSuccess }) to satisfy the Effect-TS lint rule that flags catchAll for discarding typed errors. Behavior is unchanged — same success and failure side-effects, no Effect.asVoid needed since match already returns Effect. --- .../src/web/app-terminal-session-handlers.ts | 98 ++++++++----------- 1 file changed, 42 insertions(+), 56 deletions(-) diff --git a/packages/app/src/web/app-terminal-session-handlers.ts b/packages/app/src/web/app-terminal-session-handlers.ts index b49e443f..33a7245f 100644 --- a/packages/app/src/web/app-terminal-session-handlers.ts +++ b/packages/app/src/web/app-terminal-session-handlers.ts @@ -57,17 +57,14 @@ const browserStatusMessage = (browser: ProjectBrowserSession): string => { const runOpenBrowser = (projectId: string, setMessage: StateMessageUpdater): void => { void Effect.runPromise( loadProjectBrowser(projectId).pipe( - Effect.tap((browser) => - Effect.sync(() => { - setMessage(browserStatusMessage(browser)) - }) - ), - Effect.catchAll((error) => - Effect.sync(() => { + Effect.match({ + onFailure: (error) => { setMessage(`Failed to open browser: ${error}`) - }) - ), - Effect.asVoid + }, + onSuccess: (browser) => { + setMessage(browserStatusMessage(browser)) + } + }) ) ) } @@ -82,17 +79,14 @@ const runApplyProject = ( } void Effect.runPromise( applyProject(projectId).pipe( - Effect.tap((applied) => - Effect.sync(() => { - setMessage(`Applied ${applied.displayName}.`) - }) - ), - Effect.catchAll((error) => - Effect.sync(() => { + Effect.match({ + onFailure: (error) => { setMessage(`Apply failed: ${error}`) - }) - ), - Effect.asVoid + }, + onSuccess: (applied) => { + setMessage(`Applied ${applied.displayName}.`) + } + }) ) ) } @@ -107,17 +101,14 @@ const handleTerminalCreated = (sessionId: string, setMessage: StateMessageUpdate const runOpenTerminal = (projectKey: string, setMessage: StateMessageUpdater): void => { void Effect.runPromise( createProjectTerminalSession(projectKey).pipe( - Effect.tap((created) => - Effect.sync(() => { - handleTerminalCreated(created.session.id, setMessage) - }) - ), - Effect.catchAll((error) => - Effect.sync(() => { + Effect.match({ + onFailure: (error) => { setMessage(`Failed to open new terminal: ${error}`) - }) - ), - Effect.asVoid + }, + onSuccess: (created) => { + handleTerminalCreated(created.session.id, setMessage) + } + }) ) ) } @@ -155,17 +146,14 @@ const runRefreshTasks = ( ): void => { void Effect.runPromise( loadProjectTasks(projectId, include).pipe( - Effect.tap((next) => - Effect.sync(() => { - setSnapshot(next) - }) - ), - Effect.catchAll((error) => - Effect.sync(() => { + Effect.match({ + onFailure: (error) => { setMessage(`Failed to load tasks: ${error}`) - }) - ), - Effect.asVoid + }, + onSuccess: (next) => { + setSnapshot(next) + } + }) ) ) } @@ -178,13 +166,14 @@ const runStopTask = ( ): void => { void Effect.runPromise( stopProjectTask(projectId, pid).pipe( - Effect.tap(() => Effect.sync(onAfterStop)), - Effect.catchAll((error) => - Effect.sync(() => { + Effect.match({ + onFailure: (error) => { setMessage(`Failed to stop task ${pid}: ${error}`) - }) - ), - Effect.asVoid + }, + onSuccess: () => { + onAfterStop() + } + }) ) ) } @@ -197,17 +186,14 @@ const runLoadLogs = ( ): void => { void Effect.runPromise( loadProjectTaskLogs(projectId, pid).pipe( - Effect.tap((output) => - Effect.sync(() => { - setLogs(output) - }) - ), - Effect.catchAll((error) => - Effect.sync(() => { + Effect.match({ + onFailure: (error) => { setMessage(`Failed to load logs for ${pid}: ${error}`) - }) - ), - Effect.asVoid + }, + onSuccess: (output) => { + setLogs(output) + } + }) ) ) } From 3a443411a66706d05f8ceef204a7ea7cc9fc7eca Mon Sep 17 00:00:00 2001 From: konard Date: Mon, 11 May 2026 15:14:59 +0000 Subject: [PATCH 4/4] Revert "Initial commit with task details" This reverts commit 028e1d4c3e89155860266f621a16977a81d739cf. --- .gitkeep | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .gitkeep diff --git a/.gitkeep b/.gitkeep deleted file mode 100644 index b6816271..00000000 --- a/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# .gitkeep file auto-generated at 2026-05-11T14:19:33.306Z for PR creation at branch issue-269-2e25dfa54dec for issue https://github.com/ProverCoderAI/docker-git/issues/269 \ No newline at end of file