From c6f4cd826be7d6afbf885e41e29206db61878b22 Mon Sep 17 00:00:00 2001 From: Lex Christopherson Date: Wed, 18 Mar 2026 00:19:30 -0600 Subject: [PATCH] chore(M001-1ya5a3/S01): auto-commit after research-slice --- .gsd/gsd.db-shm | Bin 0 -> 32768 bytes .gsd/gsd.db-wal | Bin 0 -> 160712 bytes .../M001-1ya5a3/slices/S01/S01-RESEARCH.md | 124 ++++++++++++++++++ src/resources/extensions/gsd/doctor.ts | 8 +- .../extensions/gsd/tests/doctor-git.test.ts | 72 +++------- .../gsd/tests/stop-auto-remote.test.ts | 13 +- .../extensions/gsd/tests/worktree-e2e.test.ts | 4 +- 7 files changed, 155 insertions(+), 66 deletions(-) create mode 100644 .gsd/gsd.db-shm create mode 100644 .gsd/gsd.db-wal create mode 100644 .gsd/milestones/M001-1ya5a3/slices/S01/S01-RESEARCH.md diff --git a/.gsd/gsd.db-shm b/.gsd/gsd.db-shm new file mode 100644 index 0000000000000000000000000000000000000000..d8e03167f7b3e87f611e1c3693d0097df8b9004d GIT binary patch literal 32768 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
39g+D$T(uyyR9 zGJWVTSRWE;i$T*ZLL!(m=Ty;&2D&edT)3PY8Ox1cT^zYQK6+{V(uJXmm#_W|%>TZ$ zUtsz0{ITEpKbrUX9psET$VXKODA&+HjQg<~1dxF?_^#RkUaSR=#xA#@J@8VNeL%qs zVJzp)*##|&H*G?}^F&wgOxly8=C1M5dx;&)tyB$Hsr1$A#I)`WplI~aD*#2bDsNJ= z7D_IF&DvRWrgL`Z^57=r649hs)3rrLyrPX|Cf6+6jgw9(7<%bgALFSw24>ys=Nd7X zNw8@Irbx^2CDO$iGwv}IIRc~z;BoVTdWz7bM7o=8RtR21TE!=on4>OYV;IZ2m_5{)q$#^T9w@#Nyg6OVTy+K|Mt6s{Ickk%2?=x85chZh;t zEeFHdW!44+&8$5bfq*^BD&0FF_79>ARXy#G%O0CQ9$uLbOxK3 z07;5Xf}UNt3?==g9<0&tS^sH>XSnNGJgOLPDO@+Xo!Vz|HBJmyK|BvLbQ9tk(%^0r zIy>cqqyR8sA%_eCEk$Qm4R;Ud3XG`P8^xkQK|gjeWZ8*ijTavvQuK5H5E+eK_4@6W zE9T6Aj2AB$_4<)SV=7MZZLNmn3v?N)pB4VhNzfK9VzJY8AexSSeB_$fAG1QD$>1`R zg0K){1wl^w!_XK&VV1rc?^DQR(7#gt)bq#|!Cr9Q>}u$De8RN&xzHckB0L*}PwU09 zeJ>_^5H=p%4+Fn7rks<6RO_9H)f3%B#p#fX*ErP#%5ZgTqP^=hLxIefP(-*2+c2bs zIFi|HC%1Zom>t}>SfRR{2#AveyOYlr*!nuqgdxPhv2rSR%xm17{in&MX7_HJ2RkCFs8ouQ80! z>6b_w`Lbbx{Fu3e+7abb}8Py1jJRJO62+a1gWQklklaGNx8fIO7j&(H6*2z{CCF!TpUvrK(H?*? zOQ<&C>Jkrvn3^`9b}qCNeeRr+HBknQ3A4Zu82U*&=+1ntfFuMog_u|}0dT+ZD$YY7 z$XKID_7rlW$LG$8;LvEi>@}v{MCx$IgyKzuH1UAYf6^j>Y*VRHMAO!sv58D^APxI; z0U8_u$hHF4DwT6$Y+y)O26XeuE;P&ZfLEG4NEIu9lIGkwtcAo4h$ybfCk-rR6vTlX zha_#!+(4Zsc?t%Z_}wte5D>u3z_73qw#hd_aBj@d;>p8ek-vZwhqN$*fnjEQjPqa% zWa93AAd#Ec@?g{;7O+hv@Z33(l4J>;R4IQ#yo<>@WVRbd)ocr~l?#lV4wx^9VX_cO3nB{CekpzWGy!y4OB@wxeU` zt9>7hwVY@;jm(kyoSUDWxG^&^2ZhgkN?klF@LajkZU2&wl)4ueZXlfwWiugR>$LhysZOAYjH+PPqB=FHp2%RO{1(F(JAs?N>Z}V*DdF`d zdB!mkEx2@+xWN#WUeij2!=Oqc>4x%#`mcbVnO$7PzF9VDqaV*{9fWvbgzQ=kG4ZH5 ziijJ?QSf`|a%Kn8Q{&@^vTUlU9P)=v4^$Z~`G1_xB+3cGlf0mtgeuMXzaZe0br2;= zwAa7ek(7oE-xDfa+(6mXH;Du3yRo+6`k-s9m;h}<>Yx*egtmHNoZU@pOUu%b>M!XG zG7vfwA!+GZq#KHYCBs-~G*KhdtVH1mOKDr(a_N*Twqf>07NN;3;45kfPz?~y0MAK% z+pY|8irQBtNnB(eC7F*}%fbdKh9OL4@25{l#Q)1ErkiI5He6pz)r~65D68xFg zgql`VB-2^>5VK%UB+9ce2MdMx^AyNTjO3EaC@GqGg9IJ9K*LrJ2HAo{hbpe$i*}am z2*9|c`@ybZX!1O$3(AZ>%F<>OVNtK8uR@O%!G1v&f^&hvT8JDfwmMmfF3FX}BnFTp zh3dLnk?-HFZNjxEEQ-kx4bL_%B@KyQE0RiA9HbQAlFG2s`Ug;_b7lh2&#H0^-x!lJvl~yG2 z)rqnplO8W}ru2)Dr1jZ;^Ob}V_EB9-Mu!2{a6l^gEB=NYCt*e^N5YYz;UEWV-!FJY`j4b2fY zGhp=d1qKScyI*7&MjXSSvTTZoB*DVuHZO)K4u7C8RT*8+*@MHAhHV?JzVmV1*BWKH z10M^vt+il4HHoZ03(`Ktz*s-JvB%j4bQT%=b2v6BB@YAl>X0T>J zb*K$mDsO+y-@p?A*Je%n7T_%gC-!SQF4CRywPUzS{^DZyPZc3 zWk{lCW)+5h<6Vpf*pw;-WPD*2^(AsAv|>dE$UM7PzVJEh$v9DoawBK*@7RRo{);q0 zbCzn-KYdSHbCHIHdsBWhHf_9eay^mKyrRr&?PdtnlsIj145*>qo{y#Fxs(aPwiAue znD1gFa38?RPHg?ua#rasjm3Nj1D1_FY%Dn0c#IQ+q902~v*>Laujm7le$5~@i2s=J z+wSfps4$;@Woc?YKa;<;n8Xz*P_TW&ErZ>?kTk%zumQ%BVh6q?8!(rdyMbi#Vaa2P zYB)F3EYumQ*2u|#uZN{HP(8UmSs<9QXIo8JSyB=L1*ZvJ#eAZQA{=6!-mw}GOF)Pg z;qBl&gAT^#r!9!v71jRmVclN=S3KO%#5XsNLUrV9m>IG#IIq20HsW{ zC8V=4DMpt4oUwGV)mX@XS_4)IJg6Ut!PK{*+{bc`riQxiWhFyS>{3-Vl!Xh48`3WA zHlCuCdYbWyh-PX;QO0BOAUzc_!tRr*1Q`($TM(%{QI#jgnApS1Buc$nIZ2)&6kds8 zRg!>{3?w;yp858$cK0kghNWx@S5q55-RCCCTg7q3Or=IBRULFf3w0>K&M_TAW+!0P z;dgIri`r)*r}7z67S4%6vLOp}vJU~i4oH*&td&^MT34N_W5l8nwLTX2-QBtaW7zP8 z@z4TV&>3+~PcCgT@w^~?+xV1d6XB`YMT(9K1OppVtOTg#=BUVaMq@jJQ59uQu6`1S zk4_~wJZluDO+Rl6erB6duEvV$_TrZ3jtj^_gW8@0$kn4>S2F!qQM zV(%8`%~^o=z`xeBaIcjcgycl}qcA7)! z%Ro4ZYA)_?iGrh{eBy&--}KN_wKB!?PzV*bM3g8xv0FU;NNrL$7ZoYobk2Ht=!b+S z$2x#PmWts~-%DLgK|*k6F#dKVcWk1>ty-%vlbA|0;&qtP;<%K#;_x0AQxc?1f`bS% zNk~JZigr=hP$Ew9(`YP;?|33>4trSx>{dZ{rl|)iz=Wc$VD)Z>%UX<+S}M5^=*6H}D8of3{#Ln@PoZO6Q#Q!TPEZWl zzKJT{0$U`)Ny`Lu=?xmv;V)JQQ>@z>%~)>GjRpyv>>@fw4c}lWiq1kuyrieAE-%YO z_#2F9MYTHiQ+{_00TEGV4WPcAJ56>76W}#VF_n3ypWqJUWuvzke1V5T(XGO@Ed`s# zwxPS0Rfm+&86`y=D&T(24YxGJCOm6hl$+GVHD$=ep*)r8z_?B02c-4SNsk4d+Xe3SvcNO(kexY4V$}84In~Kmkf_ z!*sE9hrbrn!F~vshG7(1f=2ivt8BN2-F7*P3I-)4BYc;p=xt-9Ry4z+{7)i1GA?td z7tCtea#hAs{7dB9APSHWx&eHe6ch)2P-KQLk~}dFT|dHxSjrA{2|>uF)LMXf~c>`p#~U zPLNxTb!h*btx4lWVYF7dX;a0SFe?xPBoA0W*5SY&m^(z>4M{L3wqZgvfy~vyHZ*MsO$nRB++__xC(T~#7g+zzfAUwqegB{R-wUry zGuw|_3*0|r9j7`D9qRbF=i$~v_(xv?@d}UNUpM|ehJW@)+OvP`@&7{|LtlLIWKXf< z)MF<)ddepoJ)^HAwP{+uXoP7IX^oC zHB8&^ZoibD&ofsN5-UIg#4-%esRi%W()9FBcTZRE*x0e17g8@z`D$$k(qFadMEdgX zj%2Rx)0xjc(bb#F9ord8^(D?pHWfX!niY?5&vm{Tad!?7VcMtMA&i&im&j-A0<+UN&ka<_0xE z4Paej>UMqmxOwg3#P#XCw!iBhuV2HQh;*{0Wuhn3o#@TYPt8oszvjK1e{C?nMTUh< zY*{Q!-j`fUx`Yi%nz)6;P%@2qeBkBBkM=EJ>+BG6MR(yG3YMA6D1R&McJ*(0wEfsl zzCL@rtMAO2&NuHSbio3!B=6omc3e%3HVW=~Q}CR=n4eFN)tgvaoSnLbrx~$GMpr6k z$K);DntEj^pWML0tTmPrXNI1-4wDRiw|y?JBFmiM8(6c`l2l9Dc{lPmCzhrcy^*1- zNn=_OBy+z^%Su{pf7x_AxoDKv<7Avf9Q2ZlNUx^0r~Dn7ZlSp|%bw zO^_L_Q=huqPR8i5qkZ|O_nM5Dl*^;>$K#)yJkr(o+;g3upOWcFOY!vQqnSxbUrEi2 zC12ZFk?OkaLQ0JwnGN_O5yLpZjqvK&7{&qa-l7|tB61fir-`jg^Z+-Sxw5RKO9yxci7zEMWoWKHxaVpcKlr|fU49j>|jvnoM`MJFqFU>B?qv^l8 z<{mrRcm90mYr=eHcY%l7cq)4L4eq;*xY0Vv4snmRO)w5cXss+40P^%N_h!o!xev7?01C0GJhS)a6tB2iVe)OyEef@ zR>6IO0+QXL*)9v)H|rPp`mZ*A|Fc(L`xnd?fHQzUPxX9X{{K$=a&-jImjyx?zF8Z2 zN_mviseSOQ3IwdHBY;o4ji*zh-4^VQjz?GuFfHSRE(3rEm#cA?xjF)p$kh?JIs#^0 zASSd2C<@n-t0Tx=aCHQzf&|vBt0TxnX}COG9f7MONMWG#x6i+Jbp)=CKrr?YoJCyO z)e-PO7wF?zj5$|F(7qJ@y{aRCo&RHu{U4{nZ~X$tKJuYIXy#vikx2mb3-o-q=Sw|* zpMSU?M*&9xM*&9xM*&9xM*&9xM*&9xM*&9xM*&BHcb5XAhu1pe+_1gT_>xlY!v}mK z!S6eC^6)dq6Z9sGhxGt4zQC`X`Rl*&)W7_te`&_aTnCBO$rUQEB%M(MRal+|afhH5>RyybNUW0KCU49J&OO#zA< z$M?33^-}t!sA^FZ^)}^6>A;o1w5__C1fbnPH^d^UQiFyjw=R{suJHYjzU5UnxYelifO6%A@){* z=YMH&CXyr$QZjo;XORL zeb}7ho_!Chwt4K85)pv?TJS2Tz9W+vS+i2+97>g=ob}FHFxJYxU~?s~J`^_w1?_R= zzWqvOd?v<5xe*`oKvbw3Jdqv9W0`8fRCPF33d-`BaHe2oSnB{a)WSiQkw&DxLZ5T3 zbCJqHhElU{s}8jYuCZMXiXg-y_}+}w92X_;(>h=jD6oQ7UdG2wr8Fr3WMZw!hqOVu ztHGN^3NYJD$~=8^F)k^jU|OLZLP=TKukq+K1_Z&l_#bP(I8$0Z-cC-SWilvB5Lw2n z?1#!i91g;|bPIEl$^1$6T7;bxN~Wa%Pq-+f<(mS-l`1~f`gvP*N)S<@B2NB6PqC0i zKq^zl0&+sXP00ldm@C3&-1J%aUS+G+Y_bFs>m!xSWXkRBs);LP`Gr|AF~Vg<9=1JI zL}7|EvFs)$y^H^u1%QaeoH zHJ<_nnj{(?#--)XT!G^rj{|@t8X37DzsKbFMfrV6eqWZ~SLF9q`F)MQM@QthJUe;; zG#@1V|ij>>yR* zSeUB~V_4u>0fM1Pcp?C;;F#0|Hf(_>R08_zh5~pk=%H#i<#c5OIeCXIjx*AoK1aLF zInonx;X8UPCHGZGB&kVPWCwGwa$Jdu?I{Rwj!n5);ht9s-@g1*qHR@hgG?vmTal!V9P_Iqk@3P!uPU>4L4A=T3v|%ARz4 z-pdQln`$bE;I$_Z=)4Dw6gErCo<*FqWd$~{NOVA{iO!(-> z*b<0nk13RIksZstKLCP5^)kiqIm|xZZu_O%@{sq50FZnF8G#OmX&J4GI$Jf{b7r(x z#&MFFLY7koW58K#323(a4*D_%eG&gIwHde^e<;VFlmUhT99aRnB4!}#H02niM!1Fp zrpy#48VpXJqJ0ArLDxrGbs854OAy(`m>ZHvz?>AlgTx}C^eqW=M+&rqmlZStZ$1b5dT++B@e3X^KPsvYCO3PAWMFWZhY8ma9~BiFav98J*;>su8>2c? z4Fxf{J9492EfG}wPTZ`y6t^sUp+#Qw2%i8QHsldqaykKFHDnVNHIaJ*EIo5{7&HJh zb5GAKorhKK&BNB08V8#%@L&DAmwUtd-}$VoBY-Gug-%PK)zgAbWQDCek05se62_fJ zV5AoIz};I`df(e?zf)x@yOl{Lp4x_c?O|Bzu{9YVy}Nt2fr;nNBXH*th&u!RHgQBy zX_UiNxgoha0#`?XvT#>N00g#s88cSghOUl4EL-}$;P!={Y}|)%R*88b&INZKfjf`D zoksw}jG6Lq*}FOdS4Xh$!+#zD(*SH8L7Xpe|DPY7_^1E$A9uSt0+7f3I0`rlI0`rl iI0`rlI0`rlI0`rlI0`rlI12ngDR985fOoKt;Qt4J{7IJp literal 0 HcmV?d00001 diff --git a/.gsd/milestones/M001-1ya5a3/slices/S01/S01-RESEARCH.md b/.gsd/milestones/M001-1ya5a3/slices/S01/S01-RESEARCH.md new file mode 100644 index 000000000..e8e2f0013 --- /dev/null +++ b/.gsd/milestones/M001-1ya5a3/slices/S01/S01-RESEARCH.md @@ -0,0 +1,124 @@ +# S01: Electron Shell + Design System Foundation — Research + +**Date:** 2026-03-18 + +## Summary + +S01 delivers the Electron desktop shell, three-column resizable layout, and the full design system that every subsequent slice builds on. The technology stack is well-understood: electron-vite (v5) for the build pipeline, React 19 + TypeScript in the renderer, Tailwind v4 CSS-first configuration for the design tokens, Radix primitives for accessible UI, react-resizable-panels for the three-column layout, and Phosphor Icons. + +The main risk is getting the project scaffolding right — electron-vite imposes a specific directory structure (`src/main/`, `src/preload/`, `src/renderer/`) and the design system must be defined in Tailwind v4's CSS-first `@theme` block rather than a traditional `tailwind.config.js`. The font loading story (Inter + JetBrains Mono) needs to work in Electron's renderer without external network requests — fonts should be bundled as local assets. There are no novel unknowns; this is a scaffolding + design foundation slice. + +## Recommendation + +Use **electron-vite** as the build tool (not raw Vite + vite-plugin-electron). electron-vite provides a single `electron.vite.config.ts` that configures main, preload, and renderer builds with HMR out of the box. The studio app should live at `studio/` in the repo root and be added to the root `package.json` workspaces array. This gives it access to `@gsd/pi-coding-agent` for RPC types (consumed in S02) while keeping it isolated from the CLI build. + +Use **Tailwind v4** with `@tailwindcss/vite` plugin in the renderer Vite config. Define the full color palette, typography scale, and spacing system in a `@theme` block in the main CSS file — no `tailwind.config.js` needed. This is cleaner and gives us CSS custom properties that Radix components and Monaco can reference. + +Use **react-resizable-panels** (v4.7+) for the three-column layout. It handles the Group/Panel/Separator pattern, supports pixel min/max constraints, collapsible panels, and localStorage persistence via the `useDefaultLayout` hook. + +## Implementation Landscape + +### Key Files to Create + +- `studio/package.json` — `@gsd/studio`, private, depends on electron, electron-vite, react, tailwindcss, @tailwindcss/vite, @radix-ui/*, react-resizable-panels, @phosphor-icons/react, zustand +- `studio/electron.vite.config.ts` — electron-vite config with three builds (main/preload/renderer). Renderer config includes `@tailwindcss/vite` and `@vitejs/plugin-react` +- `studio/src/main/index.ts` — Electron main process: `app.whenReady()`, `BrowserWindow` creation with preload, IPC handler stubs for S02. Window config: frameless/custom title bar or native with `titleBarStyle: 'hiddenInset'` for macOS +- `studio/src/preload/index.ts` — `contextBridge.exposeInMainWorld('studio', { ... })` exposing typed IPC channels. Stubs for `gsd:event`, `gsd:send-command`, `gsd:spawn`, `gsd:status` (wired in S02) +- `studio/src/renderer/index.html` — Minimal HTML entry: `
`, loads `src/main.tsx` +- `studio/src/renderer/src/main.tsx` — React root render, imports global CSS +- `studio/src/renderer/src/App.tsx` — Root component: `` with three panels (sidebar, center, right) +- `studio/src/renderer/src/styles/index.css` — `@import "tailwindcss"` + `@theme { }` block defining the full design system +- `studio/src/renderer/src/components/layout/AppLayout.tsx` — Three-column layout using `react-resizable-panels` Group/Panel/Separator +- `studio/src/renderer/src/components/layout/Sidebar.tsx` — Left panel placeholder (file tree goes here in S06) +- `studio/src/renderer/src/components/layout/CenterPanel.tsx` — Center conversation panel placeholder +- `studio/src/renderer/src/components/layout/RightPanel.tsx` — Right editor/preview panel placeholder +- `studio/src/renderer/src/components/layout/PanelHandle.tsx` — Custom-styled drag handle for Separator (amber accent on hover) +- `studio/src/renderer/src/components/layout/TitleBar.tsx` — Custom title bar with app name, traffic light offset, session controls placeholder +- `studio/src/renderer/src/components/ui/Button.tsx` — Core button primitive (Radix Slot pattern for polymorphism, Tailwind variants) +- `studio/src/renderer/src/components/ui/Text.tsx` — Typography component with preset variants (heading, body, label, code) +- `studio/src/renderer/src/components/ui/Icon.tsx` — Thin wrapper around Phosphor icons with default context (size, weight, color) +- `studio/src/renderer/src/lib/theme/tokens.ts` — TypeScript constants mirroring CSS custom properties for programmatic access (used by Monaco theme in S06, Shiki theme in S03) +- `studio/src/renderer/src/assets/fonts/` — Inter and JetBrains Mono font files (woff2), loaded via `@font-face` in the CSS + +### Design System Specification + +The `@theme` block in `index.css` should define: + +**Colors (CSS custom properties):** +- `--color-bg-primary`: `#0a0a0a` (near-black base) +- `--color-bg-secondary`: `#111111` (panels, cards) +- `--color-bg-tertiary`: `#1a1a1a` (elevated surfaces) +- `--color-bg-hover`: `#222222` (hover states) +- `--color-border`: `#262626` (subtle borders) +- `--color-border-active`: `#333333` (focused borders) +- `--color-text-primary`: `#e5e5e5` (primary text) +- `--color-text-secondary`: `#a3a3a3` (secondary text) +- `--color-text-tertiary`: `#737373` (muted text) +- `--color-accent`: `#d4a04e` (warm amber/gold — the signature color) +- `--color-accent-hover`: `#e0b366` (lighter amber on hover) +- `--color-accent-muted`: `rgba(212, 160, 78, 0.15)` (amber wash for backgrounds) + +**Typography:** +- `--font-sans`: `'Inter', system-ui, sans-serif` +- `--font-mono`: `'JetBrains Mono', ui-monospace, monospace` +- Type scale: 11px, 12px, 13px, 14px, 16px, 20px, 24px, 32px + +**Spacing:** 4px base unit grid + +### Build Order + +1. **Scaffold the project** — `studio/package.json`, `electron.vite.config.ts`, directory structure, add `"studio"` to root workspaces. Run `npm install` from root. +2. **Electron main + preload** — BrowserWindow creation, preload with contextBridge stubs. Verify: `npm run dev -w studio` opens a window. +3. **React renderer + Tailwind** — `index.html`, `main.tsx`, `App.tsx`, CSS with `@import "tailwindcss"` and `@theme` block. Verify: window shows styled content. +4. **Font loading** — Bundle Inter and JetBrains Mono woff2 files, `@font-face` declarations. Verify: fonts render in the window. +5. **Three-column layout** — `AppLayout.tsx` with react-resizable-panels, custom separator handles, panel placeholders. Verify: panels resize, drag handles show amber on hover. +6. **Title bar** — Custom title bar component with macOS traffic light offset. Verify: app looks native. +7. **UI primitives** — Button, Text, Icon components. Verify: rendered in placeholder panels with correct styles. +8. **Phosphor Icons** — IconContext provider with default theme values. Verify: icons render at correct size/weight. + +### Verification Approach + +1. `cd studio && npm run dev` launches the Electron window with no errors +2. The window shows three resizable columns with drag handles +3. Dragging handles resizes panels; handles show amber accent on hover +4. Inter font renders in UI text, JetBrains Mono renders in code-styled elements +5. Phosphor icons render at correct size and weight +6. All placeholder panels show styled placeholder content with correct colors +7. HMR works — editing a React component hot-reloads without restarting +8. `npm run build -w studio` produces a working production build in `studio/out/` + +## Don't Hand-Roll + +| Problem | Existing Solution | Why Use It | +|---------|------------------|------------| +| Electron + Vite build pipeline | `electron-vite` (v5) | Handles main/preload/renderer builds, HMR, and dev server in one config. No need to wire Vite plugins manually. | +| Resizable panel layout | `react-resizable-panels` (v4.7) | Handles drag, keyboard, min/max constraints, localStorage persistence, collapse. Well-tested. | +| Accessible UI primitives | `@radix-ui/*` | Headless, zero-style primitives. Dialog, Tooltip, DropdownMenu, etc. for future slices. S01 only needs the dependency installed. | +| Icon library | `@phosphor-icons/react` (v2.1) | Tree-shakeable, typed, consistent geometric style. `IconContext` for global defaults. | +| CSS framework | `tailwindcss` v4 + `@tailwindcss/vite` | CSS-first config, no JS config file, generates CSS custom properties from `@theme` block. | + +## Constraints + +- **electron-vite directory convention**: Must use `src/main/`, `src/preload/`, `src/renderer/` structure for zero-config. Custom paths require explicit `rollupOptions.input` in each build section. +- **Tailwind v4 has no `tailwind.config.js`**: All theme customization goes in the CSS `@theme` block. This is a new pattern — no JS-side theme object. TypeScript token constants must be manually synced with CSS variables. +- **Fonts must be local**: Electron apps should not depend on Google Fonts CDN. Bundle woff2 files and use `@font-face` with relative paths. +- **Preload script runs in isolated context**: Cannot import renderer modules. Must use `contextBridge.exposeInMainWorld()` to expose IPC channels. TypeScript types can be shared via a `studio/src/shared/` directory. +- **electron-vite v5 requires `@swc/core`**: peer dependency — must be installed. +- **Root workspace**: `studio/` must be added to root `package.json` `"workspaces"` array to access `@gsd/pi-coding-agent` types in S02. + +## Common Pitfalls + +- **Tailwind classes not working in Electron renderer** — The `@tailwindcss/vite` plugin must be added to the `renderer` section of `electron.vite.config.ts`, not the top level. electron-vite has separate Vite configs per process. +- **Context isolation breaks direct IPC** — Cannot use `ipcRenderer` directly in renderer. Must go through `contextBridge` in preload. This is secure but means all IPC channels need explicit exposure. +- **react-resizable-panels API changed in v4** — The library now uses `Group`, `Panel`, `Separator` (not `PanelGroup`, `Panel`, `PanelResizeHandle`). Import names matter. Docs show the v4 API. +- **Font loading flash** — If fonts are loaded asynchronously, there's a brief flash of fallback font. Use `font-display: block` in `@font-face` declarations and preload the font files via `` in `index.html`. +- **macOS title bar** — `titleBarStyle: 'hiddenInset'` gives native traffic lights but overlaps content. Need `CSS: padding-top` or `-webkit-app-region: drag` to create a drag region that doesn't overlap the layout. + +## Skills Discovered + +| Technology | Skill | Status | +|------------|-------|--------| +| Electron | `jezweb/claude-skills@electron-base` (267 installs) | available | +| Electron | `jwynia/agent-skills@electron-best-practices` (112 installs) | available | +| Tailwind v4 | `jezweb/claude-skills@tailwind-v4-shadcn` (2.7K installs) | available (shadcn-oriented, partial relevance) | +| Radix | `yonatangross/orchestkit@radix-primitives` (42 installs) | available | diff --git a/src/resources/extensions/gsd/doctor.ts b/src/resources/extensions/gsd/doctor.ts index 86b8338cb..74f307aa3 100644 --- a/src/resources/extensions/gsd/doctor.ts +++ b/src/resources/extensions/gsd/doctor.ts @@ -345,7 +345,7 @@ export async function selectDoctorScope(basePath: string, requestedScope?: strin return state.registry[0]?.id; } -export async function runGSDDoctor(basePath: string, options?: { fix?: boolean; scope?: string; fixLevel?: "task" | "all" }): Promise { +export async function runGSDDoctor(basePath: string, options?: { fix?: boolean; scope?: string; fixLevel?: "task" | "all"; isolationMode?: "none" | "worktree" | "branch" }): Promise { const issues: DoctorIssue[] = []; const fixesApplied: string[] = []; const fix = options?.fix === true; @@ -386,9 +386,9 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean; } // Git health checks (orphaned worktrees, stale branches, corrupt merge state, tracked runtime files) - const isolationMode: "none" | "worktree" | "branch" = - prefs?.preferences?.git?.isolation === "none" ? "none" : - prefs?.preferences?.git?.isolation === "branch" ? "branch" : "worktree"; + const isolationMode: "none" | "worktree" | "branch" = options?.isolationMode ?? + (prefs?.preferences?.git?.isolation === "none" ? "none" : + prefs?.preferences?.git?.isolation === "branch" ? "branch" : "worktree"); await checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode); // Runtime health checks (crash locks, completed-units, hook state, activity logs, STATE.md, gitignore) diff --git a/src/resources/extensions/gsd/tests/doctor-git.test.ts b/src/resources/extensions/gsd/tests/doctor-git.test.ts index 8e3003dd8..e466d0e36 100644 --- a/src/resources/extensions/gsd/tests/doctor-git.test.ts +++ b/src/resources/extensions/gsd/tests/doctor-git.test.ts @@ -72,20 +72,6 @@ function writePreferencesFile(dir: string, isolation: "none" | "worktree" | "bra writeFileSync(join(gsdDir, "preferences.md"), `---\ngit:\n isolation: "${isolation}"\n---\n`); } -/** - * Write preferences to the test runner's cwd .gsd/preferences.md. - * loadEffectiveGSDPreferences() resolves PROJECT_PREFERENCES_PATH at module - * load time from process.cwd(), so we must write there — not to the temp dir. - */ -const RUNNER_PREFS_PATH = join(process.cwd(), ".gsd", "preferences.md"); -function writeRunnerPreferences(isolation: "none" | "worktree" | "branch"): void { - mkdirSync(join(process.cwd(), ".gsd"), { recursive: true }); - writeFileSync(RUNNER_PREFS_PATH, `---\ngit:\n isolation: "${isolation}"\n---\n`); -} -function removeRunnerPreferences(): void { - try { rmSync(RUNNER_PREFS_PATH); } catch { /* ignore if already gone */ } -} - /** Create a repo with an in-progress milestone. */ function createRepoWithActiveMilestone(): string { const dir = realpathSync(mkdtempSync(join(tmpdir(), "doc-git-test-"))); @@ -146,12 +132,12 @@ async function main(): Promise { mkdirSync(join(dir, ".gsd", "worktrees"), { recursive: true }); run("git worktree add -b milestone/M001 .gsd/worktrees/M001", dir); - const detect = await runGSDDoctor(dir); + const detect = await runGSDDoctor(dir, { isolationMode: "worktree" }); const orphanIssues = detect.issues.filter(i => i.code === "orphaned_auto_worktree"); assertTrue(orphanIssues.length > 0, "detects orphaned worktree"); assertEq(orphanIssues[0]?.unitId, "M001", "orphaned worktree unitId is M001"); - const fixed = await runGSDDoctor(dir, { fix: true }); + const fixed = await runGSDDoctor(dir, { fix: true, isolationMode: "worktree" }); assertTrue(fixed.fixesApplied.some(f => f.includes("removed orphaned worktree")), "fix removes orphaned worktree"); // Verify worktree is gone @@ -174,12 +160,12 @@ async function main(): Promise { // Create a milestone/M001 branch (no worktree) run("git branch milestone/M001", dir); - const detect = await runGSDDoctor(dir); + const detect = await runGSDDoctor(dir, { isolationMode: "worktree" }); const staleIssues = detect.issues.filter(i => i.code === "stale_milestone_branch"); assertTrue(staleIssues.length > 0, "detects stale milestone branch"); assertEq(staleIssues[0]?.unitId, "M001", "stale branch unitId is M001"); - const fixed = await runGSDDoctor(dir, { fix: true }); + const fixed = await runGSDDoctor(dir, { fix: true, isolationMode: "worktree" }); assertTrue(fixed.fixesApplied.some(f => f.includes("deleted stale branch")), "fix deletes stale branch"); // Verify branch is gone @@ -265,7 +251,7 @@ async function main(): Promise { mkdirSync(join(dir, ".gsd", "worktrees"), { recursive: true }); run("git worktree add -b milestone/M001 .gsd/worktrees/M001", dir); - const detect = await runGSDDoctor(dir); + const detect = await runGSDDoctor(dir, { isolationMode: "worktree" }); const orphanIssues = detect.issues.filter(i => i.code === "orphaned_auto_worktree"); assertEq(orphanIssues.length, 0, "active worktree NOT flagged as orphaned"); } @@ -287,15 +273,9 @@ async function main(): Promise { mkdirSync(join(dir, ".gsd", "worktrees"), { recursive: true }); run("git worktree add -b milestone/M001 .gsd/worktrees/M001", dir); - // Write preferences to runner's cwd (where the module resolves project prefs) - writeRunnerPreferences("none"); - try { - const result = await runGSDDoctor(dir); - const orphanIssues = result.issues.filter(i => i.code === "orphaned_auto_worktree"); - assertEq(orphanIssues.length, 0, "none-mode: orphaned worktree NOT detected"); - } finally { - removeRunnerPreferences(); - } + const result = await runGSDDoctor(dir, { isolationMode: "none" }); + const orphanIssues = result.issues.filter(i => i.code === "orphaned_auto_worktree"); + assertEq(orphanIssues.length, 0, "none-mode: orphaned worktree NOT detected"); } } else { console.log("\n=== none-mode skips orphaned worktree (skipped on Windows) ==="); @@ -311,15 +291,9 @@ async function main(): Promise { // Create a milestone/M001 branch (no worktree) run("git branch milestone/M001", dir); - // Write preferences to runner's cwd - writeRunnerPreferences("none"); - try { - const result = await runGSDDoctor(dir); - const staleIssues = result.issues.filter(i => i.code === "stale_milestone_branch"); - assertEq(staleIssues.length, 0, "none-mode: stale branch NOT detected"); - } finally { - removeRunnerPreferences(); - } + const result = await runGSDDoctor(dir, { isolationMode: "none" }); + const staleIssues = result.issues.filter(i => i.code === "stale_milestone_branch"); + assertEq(staleIssues.length, 0, "none-mode: stale branch NOT detected"); } } else { console.log("\n=== none-mode skips stale branch (skipped on Windows) ==="); @@ -335,15 +309,9 @@ async function main(): Promise { const headHash = run("git rev-parse HEAD", dir); writeFileSync(join(dir, ".git", "MERGE_HEAD"), headHash + "\n"); - // Write preferences to runner's cwd - writeRunnerPreferences("none"); - try { - const result = await runGSDDoctor(dir); - const mergeIssues = result.issues.filter(i => i.code === "corrupt_merge_state"); - assertTrue(mergeIssues.length > 0, "none-mode: corrupt merge state IS detected"); - } finally { - removeRunnerPreferences(); - } + const result = await runGSDDoctor(dir, { isolationMode: "none" }); + const mergeIssues = result.issues.filter(i => i.code === "corrupt_merge_state"); + assertTrue(mergeIssues.length > 0, "none-mode: corrupt merge state IS detected"); } // ─── Test 10: none-mode still detects tracked runtime files ──────── @@ -359,15 +327,9 @@ async function main(): Promise { run("git add -f .gsd/activity/test.log", dir); run("git commit -m \"track runtime file\"", dir); - // Write preferences to runner's cwd - writeRunnerPreferences("none"); - try { - const result = await runGSDDoctor(dir); - const trackedIssues = result.issues.filter(i => i.code === "tracked_runtime_files"); - assertTrue(trackedIssues.length > 0, "none-mode: tracked runtime files IS detected"); - } finally { - removeRunnerPreferences(); - } + const result = await runGSDDoctor(dir, { isolationMode: "none" }); + const trackedIssues = result.issues.filter(i => i.code === "tracked_runtime_files"); + assertTrue(trackedIssues.length > 0, "none-mode: tracked runtime files IS detected"); } } finally { diff --git a/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts b/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts index 8a8dd02d7..e10b9020c 100644 --- a/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +++ b/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts @@ -83,11 +83,11 @@ test("stopAutoRemote cleans up stale lock (dead PID) and returns found:false", ( test("stopAutoRemote sends SIGTERM to a live process and returns found:true", async () => { const base = makeTmpBase(); - // Spawn a child process that sleeps, acting as a fake auto-mode session + // Spawn a child process that prints "ready" then sleeps, acting as a fake auto-mode session const child = spawn( process.execPath, - ["-e", "process.on('SIGTERM', () => process.exit(0)); setTimeout(() => process.exit(1), 30000);"], - { stdio: "ignore", detached: false }, + ["-e", "process.on('SIGTERM', () => process.exit(0)); process.stdout.write('ready'); setTimeout(() => process.exit(1), 30000);"], + { stdio: ["ignore", "pipe", "ignore"], detached: false }, ); if (!child.pid) { @@ -95,8 +95,11 @@ test("stopAutoRemote sends SIGTERM to a live process and returns found:true", as } try { - // Wait for child to be ready - await new Promise((resolve) => setTimeout(resolve, 200)); + // Wait for child to signal readiness via stdout + await new Promise((resolve) => { + child.stdout!.once("data", () => resolve()); + setTimeout(resolve, 2000); // fallback timeout + }); // Write lock with child's PID const lockData = { diff --git a/src/resources/extensions/gsd/tests/worktree-e2e.test.ts b/src/resources/extensions/gsd/tests/worktree-e2e.test.ts index b621a43a4..865813e07 100644 --- a/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +++ b/src/resources/extensions/gsd/tests/worktree-e2e.test.ts @@ -209,13 +209,13 @@ _None_ run("git worktree add -b milestone/M001 .gsd/worktrees/M001", repo); // Detect - const detect = await runGSDDoctor(repo); + const detect = await runGSDDoctor(repo, { isolationMode: "worktree" }); const orphanIssues = detect.issues.filter(i => i.code === "orphaned_auto_worktree"); assertTrue(orphanIssues.length > 0, "doctor detects orphaned worktree"); assertEq(orphanIssues[0]?.unitId, "M001", "orphaned worktree unitId is M001"); // Fix - const fixed = await runGSDDoctor(repo, { fix: true }); + const fixed = await runGSDDoctor(repo, { fix: true, isolationMode: "worktree" }); assertTrue( fixed.fixesApplied.some(f => f.includes("removed orphaned worktree")), "doctor fix removes orphaned worktree",