From fc0d49b5628333a138da9b4afea74d37c4870acd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 04:52:26 +0000 Subject: [PATCH 1/4] Initial plan From 9997053a881c8a7ecc342423b06381216d42d3ba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 05:06:00 +0000 Subject: [PATCH 2/4] feat: match terminal chrome to detected os Agent-Logs-Url: https://github.com/scaryrawr/sl/sessions/9a12051f-666a-405c-a0b2-7daaf1b63772 Co-authored-by: scaryrawr <661373+scaryrawr@users.noreply.github.com> --- .github/images/terminal-default.png | Bin 0 -> 10495 bytes .github/images/terminal-macos.png | Bin 0 -> 10543 bytes apps/page/src/components/terminal.test.ts | 44 +++++++ apps/page/src/components/terminal.tsx | 151 ++++++++++++++++++++-- 4 files changed, 187 insertions(+), 8 deletions(-) create mode 100644 .github/images/terminal-default.png create mode 100644 .github/images/terminal-macos.png create mode 100644 apps/page/src/components/terminal.test.ts diff --git a/.github/images/terminal-default.png b/.github/images/terminal-default.png new file mode 100644 index 0000000000000000000000000000000000000000..eb7f35c46a0c4945bdf92db9890ac710b6ac5e96 GIT binary patch literal 10495 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 literal 0 HcmV?d00001 diff --git a/.github/images/terminal-macos.png b/.github/images/terminal-macos.png new file mode 100644 index 0000000000000000000000000000000000000000..84009b81dc3ca44fff8263979e59825e83d7394b GIT binary patch literal 10543 zcmeHtXIN9|+HTZA_o#r3BGN}tQ9)_a1QKQdQ9(cv>4ZTcC_VI&1Z4(-2#la$fPlyd z(t8aARC-D15L%QHLP?Msl0b4+n7z-L{eAnK^Ihlq&cFRDSKh4kuJyG0e(oo4jJc^1 z|534{AP|WE)=h(ZAkZNy2y{^FZwG-Dot1CGAkYcWErXx#2dA%0`MyYpA>_Vgo|MD+ zCem|`1dHnhp0LuED5|;ltftQyI*e0z9l9<3dgj63s`E{ix!35z4kp9;b#Gf)0B_ z60(N9 zkAo>CY?OJXSa&n7*^ifTmU1ugK2>%|B18`@ZKbpPTC{o679FP1y`8V zlKb8V&1)(8F2&qQeA|#?o>&QYGV+dK9eoV-*o3wq!0ewLmBi&Pp5-!ka>Xpd-apxm zJeer~q6D#UX2>l&*x`|>^>|w)>2P-PmF2@l-3R*8Qom#1OLVJQ)EYaHS)Y;CR$d%q@)s?CEZl%?P z?|klB1~ul{_&|2!mEyZM)`o|>!ho1X>9*LBsnXGNwCIJM$J_t^AZ8Y-we z&YjSx$t-H;ZVuvtP)qVetNzH;i+)VgxRW*Y>yC*DiGdZ7N+|9?rArS}FzNhDs>lS;W((ELCSkOI> zs?Wd09cuESu<7@tOp@H=n~Tqg!~}PCdfNfjFea z1^l{P=H3RZfW=HLn3+#f%(SVXFQ90VO@YheL>=}5w*kJCsxiIOrY1T}Y}nlm%7i>L zybz(HCP-I9BuLk@@|qkFD{fo6!vPvtH|Q424 zpu&Tt<-Gsvb$_FSK<1#2lK-Vm|9$aaUHorv{C9by&xIbd<7@y`=o?43%XlRa?30tc zVuNLz9mKT^voU-g+*wDvM>7zY0R ziX~fBsOHMdABTi+GTTNH?hh9Z@ruI11=g~LjleP#6p~w7!uWzvhM5plUxyPq*Fr}fDNkHpcdzm=IH{( zhm1!P9}!EtNrbsCn7nXsVrF#}*kaBJq0_^XkbtPjg$MZ$kQA1@SDX&s1|TD%*jFW% zBpdt2tkLx%D{szPbcws`@oEY+2}H3`Xe@oYIHqX8{Z!#h51Ro zlN6;2&UIfdFX{GyV7)zi{qj-SLKD4~~F9*K9NMLW$|g zG*P^fZ2Vj|&M&%u((iJv76e1XsSI*|Zdrwub8qf_%iqHnk}^9|EL#y5D%eRsY2OEe z;O2?|n+`*_U3?(FUkRePVe*dCHDFl?Z~BIwK#VAn&xrgofejxs@jTUwAgNe$n-}fF z{WaZ^0N?1TR{g4Lfg)wzMe4ZE;VQzx$#GRbEhUjO6Tc$Oq}Y_6`9aKR?#73hNmMar za!`q}edaPiTdIT1F>;?~x==VO-hvEO>>-Ga?} z^a%(9B$tZmY2RAV5O8I)CJ$4-TjNM*sV2sW`)Omfi9;KqP_RG-_pW{jzg`%R5>vG@ zY{Dx!@C&l+oAJq{6u(vp!nZwm<0M|rcqM6TQ8u$Xh+Q!WPL-}=?-Hd6k^)COZGoP> zZ%9&0cFqkU^~MuJ2)fpD$j=NB-*M7UjJ<9;GCsy=`>2G#aE|zy$7QL|>Qn%J&GU9C zMXJq+GvSxpo5OWxek*LZqIsh=h1c%4us0ls^TOSgRXGbXiE8lctX*Rg{*rjm+#5{HRk{mUACXAw`Q9mS?}KV| zhv~dN!G)C-?MRmkK;oTpKPR0Mver+sR}VOs8}UeV|H}M3Ibg|dPZcX0v7^a@OMsc? zmZ2G)o|xVtB0=Sz?-9_&5&z$lO~}CHmq03*7se=rDWc}X5INhkAK^a$nf2Tf zcO^hG*%E5%;%D88sN3Hg)z$(3u`P2qE=Z6T#4wt?Z)mrHn zX9SZ;yuz%{;5q2;8tFf9t^EatLAb9d3_dBbX3~=L)*RTWdA{`Q0+g%`Q{TM&ku2V)Ta2?^j@|A>;?%B{DnNLM4yb+03UKHPQsn(REW6VSoomSEI2)?S9NZ zMrmC%I(m!gwY9r_9H_?BZAlXHN9cq8aEBI-JGXj)^x;9-Nm}3I(lQi?8Ty`b<^dPOOkP#iL@Ztid1hzyLe@{*1$#aSji zD4E#4J~sa2R;Nc{B5ND18?s_n?Y5ff_YUKs+|#HR1wg6-ZZSMPEtg%9iU*VP2exBO zzDX0ZR0c(t<>22~9^hi4zUz7?9TYIb3zYqXVJ})CDkg|SlLxJA%9bPb41Q2?jN%N80K%c&jHz} zfJ?$;7*USfU^;%?XAldsX-NEzD_E3xl=YG2lbW*ZvZWisuY_^g>9~#<+#Z#ue-7FC z`qrS}AShzKM$2L=9<9%WJ5bhfvQpPkHJ+GH|Er2=RYs2IR=U67r_3=j4x)g)O$+7M zINd_1(`&XPU!c!-W{;0>UlGZPaTYc)f8`9BjOW3q>4NL8z4<9125E%$+HPs^v-Gjs ze*i)2Q}gO}(oH2YV4RHJ+st?m>0)v{D`xT+PkLzqSQ81Gj=OHA{o6?(!`_0tO!)x0 z{~uEM|J=s^zg_GUz}8MND=##ql!c5v)SbOs+*&A3=XtM{ z{IjJOIpg~<_2lWV=*|WuC%*mFPdC9*@y74!&uG5MwH7b5<`vkzGx2W-v>);=(<6oS zKR&Oz4gy&>T(%_61;+FY5kK92&&%Rd`LEe{FbLszuNJ8<6M;AM9^bO>K`Ze@BSdOm4S}i3OSo% z4kdg+Dmm*|cRt_^RknBp_D`DUzvQfDCOt-TqO=h*(;wAlC4TL&jFF73Rit_uv?KmYfC!+CG9h$jpb90dp`&F3 zDbI3bKYrSpPgwK0Dq&Z@hVI(LWA5ydE^i=&3qW*GC<_js`az}siiSqz? zL`YTPktfd{t597%t$9Xw-U*qf5=-Z873_OtV)#g1MOAziA0D;3KeTNEkmot?&y>(Gr20QDj1P)zH#8olSARJ2L;_#kkYj13BHKLzkzA`jG0< z;v2S>42P3zw)*UTblSU^6q#aow+5AolE-eD?2}YaEXS{s)#=YVR(Iuyzr)*MNP9Zr z&y3c{J@{9(znOkTYaF*|Fa>a+@(~SUiDE7j2dOt4&NPyt^CwR@2qjdn9jj2<*nOwY z%S`#UC>G*fg~)`TdXRYBm_(z8=i&CSB}I(|#P1U-=14{9FiQ;795D333#^uLk3pEW zG$Ft*umr&Hg`GRXu$Ewap7sfevsA51AN>z>~8soKu3C$P4vyqjUDJXP;4!q(~tPp@;nzZR_CZwS7t~JU%F@uPs*D2u`>T&Yi#6 z!oT%~jZq($&ew1RG|^ED`Fd|+cj9#E*(Snt^{O9LW#7E?o~$2bJ&CUdQqHb^V;WLl zQ}mE~WmKVu%m4v{ES_PlW0{7V>aq0Cbu@Xn(q>|w@Klh0=}mKntyN}gQx7~02wd|~ z_{D-E+_;@q08C(AD#0;OW+n%nFa(wAIsg&OY$85wxD^z3%G(I}(z%=V8bFLv_^xRf z&eC$5e4L>5a*%L*zSX1-6;AE`Nd>pDe9D+tYJ~go=r<^j(4riCSr+2L!cm>mQ!eW5 zOo@c<><5w3=vo|U?u{jR#GqlMk8`gjpchP=5c1|F8lx#EYY*V@CvGLkQ-4PsV$OV`K2;;}bE&YaOxnuiC)IXfRx?|E(ZC!|yk@5_y-!rBq z2IhPw^Yeh|uH|KxBY2=|{CGTL2p3keSU3lm)^Cv zSHj?T2v5Z8UKRXW?#5fUQ#bG+zMeBXy!pdJ06(Txl3ivG9nihe3cSHG^pY zT|`LTd%rC@zg>+**FGk6>kDao=dkZpL#RL^2^(@MlmK9OpSWY}&W)Cr)a%322ROtT zN>2Ed?xJW}&e{6$0M1=oz(A{;qBVfUQ>T{i3CM12;YhutJ-@5a9 zXZeW?`|;fI8}LO^_cDiN3Fyv4TWxr>+!8G_v%;@nn?EGQvuH0~i&ir#4(-(TU4T5! z#Xb%gc)#2O^zac^*Rm$BDv7W8^#*zkBhSr5xzYsZM<}Bfg)$!(1G`AV+6G|wT{GT9hkhT-a&4mg|Bjkp9~nVkQf z*X?#y)1VO?)4J#*-R}23gmNy;Y2FmtmEYx#&9<+ssB6xN1)TWiroXV4zGMunnzgg zJ3Rcq-_Nq^LcSI)MoWrp3HBrjT}42Pr=2>=f+o!HPs*1 zoGyhg`Ew@6U0(p%7;Eu<3^!r-UFE4Ndt_kUIX!Ed(7ObM@1({aaBMv^5ymS3ex zrfqC6ED%d>h|)!j4fyv6D~e3gj}xo~6z zI^<6fVdAwS+3p&-boVmuqpR}TL6f_YT7gYT+Mz&L---;=Z(%#)yf%EM-RiQz(lct9 zP{kB-Ct-*VFHYR-6_U%=##kN(Q4K%aoRD13!nE3~0pWTpLhCuW6s$)ChV~H|6#&!W zUQH}sYUA}pFa;5+XaYR|6>E1kt(sw|bJv@@n~v3(E5B2<&0{?Tq4#Z?Na%p6_TbVV z{-!*DyJGolQpz`oG3^{oh=#jYG_r6XiE0x}fbOuBkXI8jX5)b8u&)RJj_7%dcGuk& z_-^xzK7h$mYYhpG#J{9jxD2MquhVO4cI2F8Ezy|$3|Ybzk>BR&=N*43=O z$f#1!mDEYA;0OEE!9+(f57doxUSgTktw`|#;}%8A$k(cO00o`$3IlI1P0}o@Cr&D# zQcfiMyA$y5RHsHTRa@W3tGkxa2~CxiDrwpY{^~J2e%B+7xY`%oW0zSA_!Z;_wZN@2 zUvA>ltG*dC?*!T72i8NekAFF*cs9QSc}}1Cbwply_yw$sOtR^B0EqmydaAoMJ$AnI zs@b)dmcvsvmW{es>#}yAS!|^wkyK8lL=!5@l~Y!VdPg8>gFW8q8n1|y^X+aGdCPWy zK#OqZAL^zVQKmfYMmiM7q|otkkM1=nn$ zRT|p6@_CU4q*~%22TpX`jltz9a+{s?%FLV%L#}z za~nqL$8Hm=+_*O@+9s~A^zfhe&l$dD6)=DqciS3G3Po{3ds@OxDTZG%Y&GU8K%l?% z@XiIG`9*N+g%$wOblsP)k@k??RIqH_3qD+KdvB#1kdN2ZQ84hUkg{}@9H0wkk3G8a%{tv2bLSWD3A;5-XO4cB;AyPSu`u{qr~HEg^d zBnwO?hEpzi);9+G-wkpZ6Z4$SR)-CU5q@5r63UE;ok*;y^m1rv&?w2gR(=ELSC`Y* zkBJX21?##zDhaG&q}8d1;UP08MaWh_(j2WkUqRSTKkBnKE=WICy7R@<8>gzMRedA9 zwj-WbdwgO&&lq1LW@z0n)%AIe(cWRW{koGrdkyU_TkP$Y*`@r^*jh6415w!TlF58? z3{ZHl=yMITk67hWO>2`D04e~5l@s~hpGsD5bU8No+{|?CeR@}CleWX5syq8SG`6@e zUo~Y@&fEaVoyxT9Jel#Eo%ouGY3~8uUSLeA_w~uKd5OPKJ%N`nFR#*CXgg5x#~_=B zevh)X66Wv`88Coo%nP4}*6npP;5=giO|E+)#mFik>fHxmw^p&bUaBW(0@nSx~{1iaiZ%wM;?2l1!wm#-X0WH;pmNjFV!0S-DD%D1xl84(ePHtgf zl++j9*#+12@A4k3hp_RI_Euca=s0lM;objSl@{o(lP;Z!B6(s6?$&I<7X41dCtrEPtJs&Tze6|LJ}#!NfjZ z$~3@&S5f6bIqH`^X6tfuWP$nA!GHP3?I~(88YVN+_VZ*N`k^8=fK;IU@KTWj73LMQ z;|RW*#fQov4Dn$5>9*0PhTSuuPgc-AU8X4V^U~$^a;t~Np@w(aObK-Sow@sI+J3i_ z&fiTFbCJ=&+PJ~?&1qM`t*+=$yoh;H3?q&4OoUzY@J$Q%jRn6SQ4g@GakYxh?#1w` zb5R?E+gIH_Dq!Es?7s51V&%U95m$?$lmw&W5iG(>{ zrV8=YIeinB2%zVy#hVMtHC^TUu^r{zPTmVvvUm0Y%K53OD=a_5<9X}&E5mvA>HA+t zF#Eo>W~-oUMEwdBaPl)>WXe}mG!u30!}}WpW6Cv7&pTc9x_&xVExBMizWp8b1rMHA zI*;pmwoMiZEsJ1XQa(=GlacNU_C=HU9phKFk3+&*qO5EAncs(P4EnqJVtM$VIF+It zH0O-l+HaI-7nThfd_=o)HPi)TcnthU=TP7G^JTlg^7VLRtMc=_^{7gm9q6Dy@hXCm z_!2xE)hkHEufLt1avwYvuh@V7c2Q-6Y%T^G5fHL6BR_dP>Cbx>nNbT@>PNe1vs#CO zgak&TL08^V!HJMKb~62k_m{sXD)=XMNrtQ>-meX~Fas3n#qncWb6^y|synP=AO zE`D|wB6kVr2n1y`6%28L1*$`?B)8?EQC$R`;jggmd8k&t2P@86MINULj5^+%CMNIua& ViI`gJSzrZp%h1%I_-ChQ{{!PlFrNSb literal 0 HcmV?d00001 diff --git a/apps/page/src/components/terminal.test.ts b/apps/page/src/components/terminal.test.ts new file mode 100644 index 00000000..dab0cdaa --- /dev/null +++ b/apps/page/src/components/terminal.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, test } from 'bun:test'; + +import { getTerminalWindowChrome } from './terminal'; + +describe('getTerminalWindowChrome', () => { + test('uses macOS chrome when userAgentData reports macOS', () => { + const chrome = getTerminalWindowChrome({ + userAgentData: { + platform: 'macOS' + } + }); + + expect(chrome.buttonPosition).toBe('left'); + expect(chrome.titleBarBackgroundColor).toBe('#ececec'); + expect(chrome.buttons.map((button) => button.backgroundColor)).toEqual(['#ff5f57', '#febc2e', '#28c840']); + }); + + test('falls back to navigator.platform for macOS detection', () => { + const chrome = getTerminalWindowChrome({ + platform: 'MacIntel' + }); + + expect(chrome.buttonPosition).toBe('left'); + }); + + test('keeps non-macOS chrome on the right', () => { + const chrome = getTerminalWindowChrome({ + platform: 'Win32', + userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' + }); + + expect(chrome.buttonPosition).toBe('right'); + expect(chrome.titleBarBackgroundColor).toBe('#333'); + expect(chrome.buttons.map((button) => button.label)).toEqual(['_', '□', '×']); + }); + + test('falls back to the user agent string for macOS detection', () => { + const chrome = getTerminalWindowChrome({ + userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0)' + }); + + expect(chrome.buttonPosition).toBe('left'); + }); +}); diff --git a/apps/page/src/components/terminal.tsx b/apps/page/src/components/terminal.tsx index 1cb7fbdb..b6e03d6e 100644 --- a/apps/page/src/components/terminal.tsx +++ b/apps/page/src/components/terminal.tsx @@ -45,7 +45,7 @@ const styles = { borderRadius: '3px', width: '20px', height: '20px', - cursor: 'pointer' + cursor: 'default' }, terminal: { fontFamily: 'monospace', @@ -65,12 +65,105 @@ type TerminalProps = { backgroundColor?: string; }; +type NavigatorLike = { + platform?: string; + userAgent?: string; + userAgentData?: { + platform?: string; + }; +}; + +type TerminalWindowButton = { + backgroundColor: string; + color: string; + label: string; + borderRadius?: string; + fontSize?: string; + height?: string; + width?: string; +}; + +type TerminalWindowChrome = { + titleBarBackgroundColor: string; + titleBarColor: string; + buttonPosition: 'left' | 'right'; + buttons: readonly TerminalWindowButton[]; +}; + +const DEFAULT_WINDOW_CHROME = { + titleBarBackgroundColor: '#333', + titleBarColor: '#fff', + buttonPosition: 'right', + buttons: [ + { backgroundColor: '#555', color: '#fff', label: '_' }, + { backgroundColor: '#555', color: '#fff', label: '□' }, + { backgroundColor: '#555', color: '#fff', label: '×' } + ] +} as const satisfies TerminalWindowChrome; + +const MACOS_WINDOW_CHROME = { + titleBarBackgroundColor: '#ececec', + titleBarColor: '#444', + buttonPosition: 'left', + buttons: [ + { + backgroundColor: '#ff5f57', + borderRadius: '999px', + color: 'transparent', + fontSize: '0', + height: '12px', + label: '●', + width: '12px' + }, + { + backgroundColor: '#febc2e', + borderRadius: '999px', + color: 'transparent', + fontSize: '0', + height: '12px', + label: '●', + width: '12px' + }, + { + backgroundColor: '#28c840', + borderRadius: '999px', + color: 'transparent', + fontSize: '0', + height: '12px', + label: '●', + width: '12px' + } + ] +} as const satisfies TerminalWindowChrome; + +const isMacOs = (navigatorLike?: NavigatorLike) => { + const userAgentDataPlatform = navigatorLike?.userAgentData?.platform?.toLowerCase(); + if (userAgentDataPlatform?.includes('mac')) { + return true; + } + + const platform = navigatorLike?.platform?.toLowerCase(); + if (platform?.includes('mac')) { + return true; + } + + const userAgent = navigatorLike?.userAgent?.toLowerCase(); + return Boolean(userAgent?.includes('macintosh') || userAgent?.includes('mac os x')); +}; + +export const getTerminalWindowChrome = (navigatorLike?: NavigatorLike): TerminalWindowChrome => + isMacOs(navigatorLike) ? MACOS_WINDOW_CHROME : DEFAULT_WINDOW_CHROME; + const Terminal = ({ title, terminalRef: externalRef, fontColor = '#0f0', backgroundColor = '#000' }: TerminalProps) => { const internalRef = useRef(null); const terminalRef = externalRef ?? internalRef; const dimensionsRef = useRef<{ rows: number; cols: number } | null>(null); const initializedRef = useRef(false); const charSizeRef = useRef<{ width: number; height: number } | null>(null); + const windowChrome = useMemo( + () => getTerminalWindowChrome(typeof navigator === 'undefined' ? undefined : navigator), + [] + ); // Use useLayoutEffect to measure and build rows synchronously before paint useLayoutEffect(() => { @@ -244,16 +337,58 @@ const Terminal = ({ title, terminalRef: externalRef, fontColor = '#0f0', backgro }), [fontColor, backgroundColor] ); + const titleBarStyle = useMemo( + () => ({ + ...styles.titleBar, + backgroundColor: windowChrome.titleBarBackgroundColor, + color: windowChrome.titleBarColor + }), + [windowChrome] + ); + const buttonsStyle = styles.buttons; + const hiddenButtonsStyle = useMemo( + () => ({ + ...buttonsStyle, + visibility: 'hidden' + }), + [buttonsStyle] + ); + const titleStyle = useMemo( + () => ({ + ...styles.title, + color: windowChrome.titleBarColor, + flex: 1, + textAlign: 'center' + }), + [windowChrome] + ); + const renderWindowButtons = (style = buttonsStyle) => ( +
+ {windowChrome.buttons.map((button) => ( + + {button.label} + + ))} +
+ ); return (
-