@av-pi-studio/web-client 0.0.8

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 (59) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/web/assets/BinaryFallbackViewer-BVnOJSh_.js +2 -0
  3. package/dist/web/assets/BinaryFallbackViewer-BVnOJSh_.js.map +1 -0
  4. package/dist/web/assets/BinaryFallbackViewer-CLiNWU_S.css +1 -0
  5. package/dist/web/assets/ChatPanel-BOR3MYdN.js +3 -0
  6. package/dist/web/assets/ChatPanel-BOR3MYdN.js.map +1 -0
  7. package/dist/web/assets/ChatPanel-DW5U4HRf.css +1 -0
  8. package/dist/web/assets/CodeView-BJb8T6q4.js +2 -0
  9. package/dist/web/assets/CodeView-BJb8T6q4.js.map +1 -0
  10. package/dist/web/assets/CodeView-CLRODhB8.css +1 -0
  11. package/dist/web/assets/DiffView-B4H-yHuX.js +3 -0
  12. package/dist/web/assets/DiffView-B4H-yHuX.js.map +1 -0
  13. package/dist/web/assets/DiffView-CRbeRztO.css +1 -0
  14. package/dist/web/assets/FilePanel-DIInFr3h.css +1 -0
  15. package/dist/web/assets/FilePanel-lERDJldp.js +3 -0
  16. package/dist/web/assets/FilePanel-lERDJldp.js.map +1 -0
  17. package/dist/web/assets/ImageViewer-Cnr0mhRu.js +2 -0
  18. package/dist/web/assets/ImageViewer-Cnr0mhRu.js.map +1 -0
  19. package/dist/web/assets/ImageViewer-ijzMsq8H.css +1 -0
  20. package/dist/web/assets/MarkdownFileViewer-BD5Ri_Fb.css +1 -0
  21. package/dist/web/assets/MarkdownFileViewer-CDHJBxdH.js +2 -0
  22. package/dist/web/assets/MarkdownFileViewer-CDHJBxdH.js.map +1 -0
  23. package/dist/web/assets/TerminalPanel-BQj62G6N.css +1 -0
  24. package/dist/web/assets/TerminalPanel-cnkK_YF5.js +2 -0
  25. package/dist/web/assets/TerminalPanel-cnkK_YF5.js.map +1 -0
  26. package/dist/web/assets/TextViewer-DWYO-J8z.js +2 -0
  27. package/dist/web/assets/TextViewer-DWYO-J8z.js.map +1 -0
  28. package/dist/web/assets/VideoViewer-BBuZ4wou.css +1 -0
  29. package/dist/web/assets/VideoViewer-D2ZnEQoA.js +2 -0
  30. package/dist/web/assets/VideoViewer-D2ZnEQoA.js.map +1 -0
  31. package/dist/web/assets/index-DnTTpAX5.js +3 -0
  32. package/dist/web/assets/index-DnTTpAX5.js.map +1 -0
  33. package/dist/web/assets/index-DrGTLQo0.css +1 -0
  34. package/dist/web/assets/markdown-BQwXm0MF.css +1 -0
  35. package/dist/web/assets/markdown-Biwdm3dP.js +2 -0
  36. package/dist/web/assets/markdown-Biwdm3dP.js.map +1 -0
  37. package/dist/web/assets/use-file-download-BpyWNVxA.js +2 -0
  38. package/dist/web/assets/use-file-download-BpyWNVxA.js.map +1 -0
  39. package/dist/web/assets/vendor-BFXtRv5b.js +83 -0
  40. package/dist/web/assets/vendor-BFXtRv5b.js.map +1 -0
  41. package/dist/web/assets/vendor-dnd-D9kA7FgH.js +6 -0
  42. package/dist/web/assets/vendor-dnd-D9kA7FgH.js.map +1 -0
  43. package/dist/web/assets/vendor-highlight-DaTzDdJW.js +5 -0
  44. package/dist/web/assets/vendor-highlight-DaTzDdJW.js.map +1 -0
  45. package/dist/web/assets/vendor-icons-DUloQbkI.js +182 -0
  46. package/dist/web/assets/vendor-icons-DUloQbkI.js.map +1 -0
  47. package/dist/web/assets/vendor-markdown-CpaoCv-a.js +36 -0
  48. package/dist/web/assets/vendor-markdown-CpaoCv-a.js.map +1 -0
  49. package/dist/web/assets/vendor-overlays-Bo99M0pM.js +2 -0
  50. package/dist/web/assets/vendor-overlays-Bo99M0pM.js.map +1 -0
  51. package/dist/web/assets/vendor-query-CAh-Aqig.js +2 -0
  52. package/dist/web/assets/vendor-query-CAh-Aqig.js.map +1 -0
  53. package/dist/web/assets/vendor-react-BoIQBqDW.js +50 -0
  54. package/dist/web/assets/vendor-react-BoIQBqDW.js.map +1 -0
  55. package/dist/web/assets/vendor-terminal-BhzX2_ts.js +10 -0
  56. package/dist/web/assets/vendor-terminal-BhzX2_ts.js.map +1 -0
  57. package/dist/web/assets/vendor-terminal-DYP7pi_n.css +32 -0
  58. package/dist/web/index.html +21 -0
  59. package/package.json +56 -0
@@ -0,0 +1 @@
1
+ {"root":["../src/app.tsx","../src/css-modules.d.ts","../src/main.tsx","../src/brand/brand-logo.ts","../src/brand/config.ts","../src/brand/index.ts","../src/brand/theme-injection.ts","../src/components/primitives/Avatar.tsx","../src/components/primitives/Button.tsx","../src/components/primitives/Checkbox.tsx","../src/components/primitives/Dialog.tsx","../src/components/primitives/Divider.tsx","../src/components/primitives/Icon.tsx","../src/components/primitives/ResizeHandle.tsx","../src/components/primitives/ScreenTitle.tsx","../src/components/primitives/ScrollArea.tsx","../src/components/primitives/Select.tsx","../src/components/primitives/Shortcut.tsx","../src/components/primitives/Spinner.tsx","../src/components/primitives/StatusBadge.tsx","../src/components/primitives/StatusDot.tsx","../src/components/primitives/Surface.tsx","../src/components/primitives/Switch.tsx","../src/components/primitives/TextInput.tsx","../src/components/primitives/helpers.ts","../src/components/primitives/index.ts","../src/components/primitives/useHover.ts","../src/features/chat/Attachments.tsx","../src/features/chat/ChatPanel.tsx","../src/features/chat/Composer.tsx","../src/features/chat/Timeline.tsx","../src/features/chat/rows/AssistantRow.tsx","../src/features/chat/rows/ErrorRow.tsx","../src/features/chat/rows/ReasoningRow.tsx","../src/features/chat/rows/SystemRow.tsx","../src/features/chat/rows/ToolCard.tsx","../src/features/chat/rows/UserRow.tsx","../src/features/connection/ConnectionStatus.tsx","../src/features/connection/Toolbar.tsx","../src/features/files/BinaryFallbackViewer.tsx","../src/features/files/CodeView.tsx","../src/features/files/DiffView.tsx","../src/features/files/FileExplorer.tsx","../src/features/files/FilePanel.tsx","../src/features/files/ImageViewer.tsx","../src/features/files/MarkdownFileViewer.tsx","../src/features/files/RightSidebar.tsx","../src/features/files/TextViewer.tsx","../src/features/files/VideoViewer.tsx","../src/features/files/viewer-registry.ts","../src/features/git/ChangesPanel.tsx","../src/features/sessions/SessionContextMenu.tsx","../src/features/sessions/SessionItem.tsx","../src/features/sessions/SessionList.tsx","../src/features/sessions/WorkspaceGroupHeader.tsx","../src/features/sessions/open-workspace.ts","../src/features/sessions/status-map.ts","../src/features/sessions/workspace-grouping.ts","../src/features/terminal/TerminalPanel.tsx","../src/features/terminal/TerminalsPanel.tsx","../src/features/workspace/TabPanelHost.tsx","../src/features/workspace/TabStrip.tsx","../src/features/workspace/panel-registry.ts","../src/features/workspace-picker/OpenWorkspaceDialog.tsx","../src/hooks/agent-stream-events.ts","../src/hooks/use-agent-stream.ts","../src/hooks/use-checkout-status.ts","../src/hooks/use-connection.ts","../src/hooks/use-explorer.ts","../src/hooks/use-file-diff.ts","../src/hooks/use-file-download.ts","../src/hooks/use-file-read.ts","../src/hooks/use-home-dir.ts","../src/hooks/use-session-restore.ts","../src/hooks/use-shortcuts.ts","../src/hooks/use-terminals.ts","../src/lib/promise-with-resolvers.d.ts","../src/lib/connection/connection-store.ts","../src/lib/connection/files-changed.ts","../src/lib/connection/query-client.ts","../src/lib/connection/rpc-keys.ts","../src/lib/protocol/events.ts","../src/platform/breakpoints.ts","../src/providers/AppProviders.tsx","../src/providers/kv-store.ts","../src/routes/WorkspacePage.tsx","../src/stores/explorer-store.test.ts","../src/stores/explorer-store.ts","../src/stores/git-store.ts","../src/stores/session-store.ts","../src/stores/tab-store.ts","../src/stores/terminal-store.ts","../src/stores/ui-store.ts","../src/theme/ThemeBoundary.tsx","../src/theme/appearance-store.ts","../src/theme/color-utils.ts","../src/theme/colors.ts","../src/theme/css-bridge.ts","../src/theme/index.ts","../src/theme/palette.ts","../src/theme/theme.ts","../src/theme/tokens.ts","../src/theme/variants.ts","../src/timeline/highlight.ts","../src/timeline/markdown.tsx","../src/timeline/reducer.test.ts","../src/timeline/reducer.ts","../src/timeline/row-model.ts","../src/timeline/tool-mapping.test.ts","../src/timeline/tool-mapping.ts","../src/ui/avatar.ts","../src/ui/button.ts","../src/ui/combobox.ts","../src/ui/index.ts","../src/ui/screen-header.ts","../src/ui/shortcut.ts","../src/ui/status-badge.ts","../src/ui/status-dot.ts","../src/ui/toast.ts"],"version":"5.9.3"}
@@ -0,0 +1,2 @@
1
+ import{r as l,j as e}from"./vendor-react-BoIQBqDW.js";import{S as m,B as a}from"./index-DnTTpAX5.js";import{u as d}from"./use-file-download-BpyWNVxA.js";import{t as c}from"./vendor-icons-DUloQbkI.js";import"./vendor-query-CAh-Aqig.js";import"./vendor-BFXtRv5b.js";import"./vendor-markdown-CpaoCv-a.js";import"./vendor-highlight-DaTzDdJW.js";import"./vendor-overlays-Bo99M0pM.js";import"./vendor-dnd-D9kA7FgH.js";const p="_wrap_renwa_1",f="_name_renwa_12",t={wrap:p,name:f};function N({path:s}){const[i,n]=l.useState(!1),r=d(s,i),o=s.split("/").pop()||s;return e.jsxs("div",{className:t.wrap,children:[e.jsx(c,{size:32}),e.jsx("div",{className:t.name,children:o}),e.jsx("div",{children:"No preview available for this file type."}),r.isError&&e.jsxs("div",{children:["Error: ",r.error instanceof Error?r.error.message:"download failed"]}),i&&r.isLoading?e.jsx(m,{size:"sm"}):r.data?e.jsx("a",{href:r.data.objectUrl,download:r.data.fileName||o,children:e.jsx(a,{size:"sm",children:"Save file"})}):e.jsx(a,{size:"sm",onClick:()=>n(!0),children:"Download"})]})}export{N as BinaryFallbackViewer};
2
+ //# sourceMappingURL=BinaryFallbackViewer-BVnOJSh_.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BinaryFallbackViewer-BVnOJSh_.js","sources":["../../../src/features/files/BinaryFallbackViewer.tsx"],"sourcesContent":["/**\n * BinaryFallbackViewer — the registry's catch-all for binary files with no dedicated viewer\n * (archives, executables, unrecognized blobs). No preview is attempted eagerly; a manual\n * \"Download\" action fetches the bytes on demand via `useFileDownload` and saves them locally\n * (POC_TO_APP_PLAN_UI.md §4.5 follow-up: modular file preview).\n */\n\nimport { useState } from \"react\";\nimport { FileQuestion } from \"lucide-react\";\nimport { Button } from \"@pi-studio-ui/components/primitives/Button.js\";\nimport { Spinner } from \"@pi-studio-ui/components/primitives/Spinner.js\";\nimport { useFileDownload } from \"@pi-studio-ui/hooks/use-file-download.js\";\nimport type { ViewerProps } from \"./viewer-registry.js\";\nimport styles from \"./BinaryFallbackViewer.module.css\";\n\nexport function BinaryFallbackViewer({ path }: ViewerProps) {\n const [requested, setRequested] = useState(false);\n const query = useFileDownload(path, requested);\n const name = path.split(\"/\").pop() || path;\n\n return (\n <div className={styles.wrap}>\n <FileQuestion size={32} />\n <div className={styles.name}>{name}</div>\n <div>No preview available for this file type.</div>\n {query.isError && (\n <div>Error: {query.error instanceof Error ? query.error.message : \"download failed\"}</div>\n )}\n {requested && query.isLoading ? (\n <Spinner size=\"sm\" />\n ) : query.data ? (\n <a href={query.data.objectUrl} download={query.data.fileName || name}>\n <Button size=\"sm\">Save file</Button>\n </a>\n ) : (\n <Button size=\"sm\" onClick={() => setRequested(true)}>\n Download\n </Button>\n )}\n </div>\n );\n}\n"],"names":["BinaryFallbackViewer","path","requested","setRequested","useState","query","useFileDownload","name","jsxs","styles","jsx","FileQuestion","Spinner","Button"],"mappings":"ydAeO,SAASA,EAAqB,CAAE,KAAAC,GAAqB,CAC1D,KAAM,CAACC,EAAWC,CAAY,EAAIC,EAAAA,SAAS,EAAK,EAC1CC,EAAQC,EAAgBL,EAAMC,CAAS,EACvCK,EAAON,EAAK,MAAM,GAAG,EAAE,OAASA,EAEtC,OACEO,EAAAA,KAAC,MAAA,CAAI,UAAWC,EAAO,KACrB,SAAA,CAAAC,EAAAA,IAACC,EAAA,CAAa,KAAM,EAAA,CAAI,EACxBD,EAAAA,IAAC,MAAA,CAAI,UAAWD,EAAO,KAAO,SAAAF,EAAK,EACnCG,EAAAA,IAAC,OAAI,SAAA,0CAAA,CAAwC,EAC5CL,EAAM,SACLG,EAAAA,KAAC,MAAA,CAAI,SAAA,CAAA,UAAQH,EAAM,iBAAiB,MAAQA,EAAM,MAAM,QAAU,iBAAA,EAAkB,EAErFH,GAAaG,EAAM,UAClBK,EAAAA,IAACE,EAAA,CAAQ,KAAK,IAAA,CAAK,EACjBP,EAAM,WACP,IAAA,CAAE,KAAMA,EAAM,KAAK,UAAW,SAAUA,EAAM,KAAK,UAAYE,EAC9D,SAAAG,MAACG,EAAA,CAAO,KAAK,KAAK,qBAAS,EAC7B,EAEAH,EAAAA,IAACG,EAAA,CAAO,KAAK,KAAK,QAAS,IAAMV,EAAa,EAAI,EAAG,SAAA,UAAA,CAErD,CAAA,EAEJ,CAEJ"}
@@ -0,0 +1 @@
1
+ ._wrap_renwa_1{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;height:100%;min-height:0;color:var(--pi-color-foregroundMuted, #9aa0a6);font-size:12px}._name_renwa_12{color:var(--pi-color-foreground, #fafafa);font-size:13px;font-weight:600}
@@ -0,0 +1,3 @@
1
+ import{j as t,r as p}from"./vendor-react-BoIQBqDW.js";import{b as F,c as J}from"./vendor-query-CAh-Aqig.js";import{t as P,u as f,a as H,D as Z,T as ee,B as N}from"./index-DnTTpAX5.js";import{k as te,l as ne,m as se,n as oe,o as ae,p as re,T as L,C as ie,q as le,r as ce,s as ue}from"./vendor-icons-DUloQbkI.js";import{M}from"./markdown-Biwdm3dP.js";import{C as I}from"./vendor-BFXtRv5b.js";import{D as de}from"./DiffView-B4H-yHuX.js";import"./vendor-overlays-Bo99M0pM.js";import"./vendor-dnd-D9kA7FgH.js";import"./vendor-highlight-DaTzDdJW.js";import"./vendor-markdown-CpaoCv-a.js";const me="_badge_jtokc_3",pe={badge:me};function ge(e){switch(e){case"success":return{token:"statusSuccess"};case"error":return{token:"statusDanger"};case"muted":return{token:"foregroundMuted"}}}function he({label:e,variant:n,className:s}){const{token:i}=ge(n),r=`var(--pi-color-${i})`;return t.jsx("span",{className:`${pe.badge}${s?` ${s}`:""}`,style:{backgroundColor:`color-mix(in srgb, ${r} 18%, transparent)`,borderColor:`color-mix(in srgb, ${r} 45%, transparent)`,color:r},children:e})}const _e={shell:L,read:re,edit:ae,write:oe,search:se,fetch:ne,task:te};function fe(e){return _e[e.kind]??L}function xe(e){return e.kind}function ve(e){switch(e.kind){case"read":case"write":case"edit":return e.path??null;default:return null}}function be(e){return e.kind==="write"||e.kind==="edit"||e.kind==="shell"}function ye(e){const n=[];if(e.kind==="edit"&&e.diff)n.push({kind:"diff",patch:e.diff});else{const s=P(e);s&&n.push({kind:"code",text:s})}return e.output&&n.push({kind:"code",text:e.output}),n}function je(e){const n=P(e);if(!n)return"";switch(e.kind){case"read":case"write":case"edit":{const s=n.split("/");return s[s.length-1]||n}case"shell":return n.split(`
2
+ `,1)[0]??n;default:return n}}const ke=500;let S;function we(e,n,s,i){clearTimeout(S),S=setTimeout(()=>{S=void 0,e.invalidateQueries({queryKey:["file"]}),e.invalidateQueries({queryKey:["explorer"]}),n&&s&&n.connection.request("checkout_refresh_request",{cwd:s}).catch(()=>{})},ke)}function O({sessionId:e,event:n,client:s,queryClient:i}){const r=f.getState();switch(r.applyStreamEvent(e,n),n.kind){case"turn_started":r.setStatus(e,"running");break;case"turn_completed":r.setStatus(e,"idle");break;case"turn_failed":r.setStatus(e,"error");break;case"turn_canceled":r.setStatus(e,"idle");break;case"tool_call":{if(n.status==="completed"&&be(n.tool)){const l=r.sessions[e],c=(l==null?void 0:l.cwd)||null;we(i,s,c,ve(n.tool))}break}}}function Ne(e){const n=H(r=>r.client),s=f(r=>{var l;return((l=r.sessions[e])==null?void 0:l.agentId)??null}),i=F();p.useEffect(()=>!n||!s?void 0:n.agent(s).timeline.subscribe(l=>{O({sessionId:e,event:l,client:n,queryClient:i})}),[n,s,e,i])}const Ie="_row_19zvb_3",Se="_who_19zvb_13",ze="_user_19zvb_24",Ce="_userImages_19zvb_31",De="_userImage_19zvb_31",Te="_imageDialog_19zvb_47",$e="_imageDialogPreview_19zvb_51",Re="_assistant_19zvb_60",Ae="_streaming_19zvb_67",Be="_streamingText_19zvb_71",Ee="_cursor_19zvb_76",Fe="_blink_19zvb_1",Pe="_reasoning_19zvb_91",He="_tool_19zvb_101",Le="_toolHeader_19zvb_111",Me="_toolChevron_19zvb_129",Oe="_toolChevronOpen_19zvb_135",Ke="_toolInlineDetail_19zvb_139",Ue="_toolBody_19zvb_149",qe="_toolCode_19zvb_155",We="_toolIcon_19zvb_166",Ve="_running_19zvb_172",Ge="_pulse_19zvb_1",Qe="_completed_19zvb_177",Xe="_error_19zvb_181",Ye="_toolLabel_19zvb_191",Je="_toolBadge_19zvb_196",Ze="_system_19zvb_209",o={row:Ie,who:Se,user:ze,userImages:Ce,userImage:De,imageDialog:Te,imageDialogPreview:$e,assistant:Re,streaming:Ae,streamingText:Be,cursor:Ee,blink:Fe,reasoning:Pe,tool:He,toolHeader:Le,toolChevron:Me,toolChevronOpen:Oe,toolInlineDetail:Ke,toolBody:Ue,toolCode:qe,toolIcon:We,running:Ve,pulse:Ge,completed:Qe,error:Xe,toolLabel:Ye,toolBadge:Je,system:Ze};function et({row:e}){return t.jsxs("div",{className:`${o.row} ${o.assistant}${e.streaming?` ${o.streaming}`:""}`,children:[t.jsx("span",{className:o.who,children:"assistant"}),e.streaming?t.jsxs("span",{className:o.streamingText,children:[e.text,t.jsx("span",{className:o.cursor,children:"▍"})]}):t.jsx(M,{text:e.text})]})}function tt({row:e}){return t.jsxs("div",{className:`${o.row} ${o.reasoning}`,children:[t.jsx("span",{className:o.who,children:"thinking"}),e.streaming?t.jsxs("span",{className:o.streamingText,children:[e.text,t.jsx("span",{className:o.cursor,children:"▍"})]}):t.jsx(M,{text:e.text})]})}const nt={running:"muted",completed:"success",error:"error"};function st({row:e}){const[n,s]=p.useState(!1),i=fe(e.tool),r=je(e.tool),l=ye(e.tool),c=l.length>0;return t.jsxs("div",{className:I(o.row,o.tool),children:[t.jsxs("button",{type:"button",className:o.toolHeader,onClick:()=>c&&s(d=>!d),"aria-expanded":c?n:void 0,disabled:!c,children:[c&&t.jsx(ie,{size:12,className:I(o.toolChevron,n&&o.toolChevronOpen)}),t.jsx("span",{className:I(o.toolIcon,o[e.status]),children:t.jsx(i,{size:14})}),t.jsx("span",{className:o.toolLabel,children:xe(e.tool)}),r&&t.jsx("span",{className:o.toolInlineDetail,title:r,children:r}),t.jsx(he,{className:o.toolBadge,label:e.status,variant:nt[e.status]})]}),n&&c&&t.jsx("div",{className:o.toolBody,children:l.map((d,u)=>d.kind==="diff"?t.jsx(de,{patch:d.patch},u):t.jsx("pre",{className:o.toolCode,children:d.text},u))})]})}function ot({row:e}){const[n,s]=p.useState(null);return t.jsxs("div",{className:`${o.row} ${o.user}`,children:[t.jsx("span",{className:o.who,children:"you"}),e.text,e.images&&e.images.length>0&&t.jsx("div",{className:o.userImages,children:e.images.map((i,r)=>{const l=`data:${i.mimeType??"image/png"};base64,${i.data??""}`;return t.jsx("img",{className:o.userImage,src:l,alt:"",role:"button",tabIndex:0,onClick:()=>s(l),onKeyDown:c=>{(c.key==="Enter"||c.key===" ")&&s(l)}},r)})}),t.jsx(Z,{open:n!==null,onOpenChange:i=>{i||s(null)},title:"Image",width:"auto",bare:!0,className:o.imageDialog,children:n&&t.jsx("img",{className:o.imageDialogPreview,src:n,alt:""})})]})}function at({row:e}){return t.jsxs("div",{className:`${o.row} ${o.error}`,children:[t.jsx("span",{className:o.who,children:"error"}),e.text]})}function rt({row:e}){return t.jsx("div",{className:`${o.row} ${o.system}`,children:e.text})}const it="_viewport_1byko_3",lt="_inner_1byko_10",ct="_rowWrap_1byko_15",ut="_empty_1byko_25",dt="_working_1byko_34",mt="_workingDots_1byko_43",h={viewport:it,inner:lt,rowWrap:ct,empty:ut,working:dt,workingDots:mt},pt=40;function gt(e){switch(e.kind){case"user":return t.jsx(ot,{row:e});case"assistant":return t.jsx(et,{row:e});case"reasoning":return t.jsx(tt,{row:e});case"tool":return t.jsx(st,{row:e});case"error":return t.jsx(at,{row:e});case"system":return t.jsx(rt,{row:e})}}function ht({session:e}){const n=e.timeline.rows,s=e.status==="running",i=p.useRef(null),r=p.useRef(!0),l=p.useRef(n.length),c=J({count:n.length,getScrollElement:()=>i.current,estimateSize:()=>48,overscan:8,getItemKey:u=>{var m;return((m=n[u])==null?void 0:m.id)??u}});function d(){const u=i.current;if(!u)return;const m=u.scrollHeight-u.scrollTop-u.clientHeight;r.current=m<pt}return p.useEffect(()=>{const u=n.length>l.current;l.current=n.length,u&&r.current&&c.scrollToIndex(n.length-1,{align:"end"})},[n.length,c]),n.length===0?t.jsx("div",{className:h.viewport,ref:i,children:s?t.jsxs("div",{className:h.working,role:"status","aria-live":"polite",children:[t.jsxs("span",{className:h.workingDots,"aria-hidden":"true",children:[t.jsx("span",{}),t.jsx("span",{}),t.jsx("span",{})]}),"Agent is working…"]}):t.jsx("div",{className:h.empty,children:"No messages yet — say something to start."})}):t.jsxs("div",{className:h.viewport,ref:i,onScroll:d,children:[t.jsx("div",{className:h.inner,style:{height:c.getTotalSize()},children:c.getVirtualItems().map(u=>{const m=n[u.index];return m?t.jsx("div",{ref:c.measureElement,"data-index":u.index,className:h.rowWrap,style:{transform:`translateY(${u.start}px)`},children:gt(m)},u.key):null})}),s&&t.jsxs("div",{className:h.working,role:"status","aria-live":"polite",children:[t.jsxs("span",{className:h.workingDots,"aria-hidden":"true",children:[t.jsx("span",{}),t.jsx("span",{}),t.jsx("span",{})]}),"Agent is working…"]})]})}const _t="_strip_5sm81_4",ft="_thumb_5sm81_11",xt="_remove_5sm81_28",z={strip:_t,thumb:ft,remove:xt};function vt({images:e,onRemove:n}){return e.length===0?null:t.jsx("div",{className:z.strip,children:e.map((s,i)=>t.jsxs("div",{className:z.thumb,children:[t.jsx("img",{src:s.thumbUrl,alt:""}),t.jsx("button",{type:"button",className:z.remove,title:"Remove",onClick:()=>n(i),children:"×"})]},i))})}function bt(e){const{promise:n,resolve:s,reject:i}=Promise.withResolvers(),r=new FileReader;return r.onload=()=>{const l=r.result;if(typeof l!="string"){i(new Error("Unexpected FileReader result"));return}const c=l.indexOf(","),d=c>=0?l.slice(c+1):l;s({mimeType:e.type,data:d,thumbUrl:l})},r.onerror=()=>i(r.error??new Error("Failed to read file")),r.readAsDataURL(e),n}const yt="_composer_1ll3p_1",jt="_inputArea_1ll3p_13",kt="_textarea_1ll3p_21",wt="_attachBtn_1ll3p_28",Nt="_hiddenFileInput_1ll3p_32",It="_actions_1ll3p_36",x={composer:yt,inputArea:jt,textarea:kt,attachBtn:wt,hiddenFileInput:Nt,actions:It},St=160;function E(e){e.style.height="auto",e.style.height=`${Math.min(e.scrollHeight,St)}px`}function zt(e){return typeof e!="object"||e===null||!("type"in e)||e.type!=="agent_stream"?!1:"agentId"in e&&typeof e.agentId=="string"&&"event"in e}function Ct({sessionId:e}){const n=H(a=>a.client),s=f(a=>a.sessions[e]),i=f(a=>a.bindAgent),r=f(a=>a.addUserMessage),l=f(a=>a.setCwd),c=F(),[d,u]=p.useState(""),[m,j]=p.useState([]),[D,T]=p.useState(!1),k=p.useRef(null),$=p.useRef(null),K=(s==null?void 0:s.status)==="running",U=!!n&&!D&&(d.trim().length>0||m.length>0);async function R(a){if(a.type.startsWith("image/"))try{const g=await bt(a);j(_=>[..._,g])}catch{}}function q(a){for(const g of a.target.files??[])R(g);a.target.value=""}function W(a){for(const g of a.clipboardData.items)if(g.type.startsWith("image/")){a.preventDefault();const _=g.getAsFile();_&&R(_)}}function V(a){j(g=>g.filter((_,b)=>b!==a))}function G(a){u(a.target.value),E(a.target)}function Q(a){a.key==="Enter"&&!a.shiftKey&&(a.preventDefault(),A())}async function A(){if(!n||!s||D)return;const a=d.trim();if(!a&&m.length===0)return;u(""),k.current&&E(k.current);const g=m;j([]),r(e);const _=a||"Describe this image",b=g.length>0?g.map(v=>({mimeType:v.mimeType,data:v.data})):void 0;T(!0);try{if(s.agentId)await n.agent(s.agentId).send(_,{images:b});else{const v=s.cwd||"~";l(e,v);let w=null;const Y=n.connection.onSessionMessage(y=>{zt(y)&&(w===null&&(w=y.agentId),y.agentId===w&&O({sessionId:e,event:y.event,client:n,queryClient:c}))});let B;try{B=await n.createAgent({config:{provider:"pi",cwd:v},initialPrompt:_,images:b,labels:{}})}finally{Y()}i(e,B.agentId)}}catch{}finally{T(!1)}}function X(){s!=null&&s.agentId&&(n==null||n.agent(s.agentId).interrupt())}return t.jsxs("div",{className:x.composer,children:[t.jsxs("div",{className:x.inputArea,children:[t.jsx(ee,{ref:k,className:x.textarea,rows:1,value:d,placeholder:"Ask anything… (Enter to send, Shift+Enter for newline)",onChange:G,onKeyDown:Q,onPaste:W}),t.jsx(vt,{images:m,onRemove:V})]}),t.jsx(N,{className:x.attachBtn,variant:"ghost",size:"md",iconOnly:!0,title:"Attach image",onClick:()=>{var a;return(a=$.current)==null?void 0:a.click()},children:t.jsx(le,{size:16})}),t.jsx("input",{ref:$,className:x.hiddenFileInput,type:"file",accept:"image/*",multiple:!0,onChange:q}),t.jsxs("div",{className:x.actions,children:[t.jsx(N,{size:"sm",disabled:!U,onClick:()=>void A(),leftIcon:t.jsx(ce,{size:14}),children:"Send"}),K&&t.jsx(N,{size:"sm",variant:"destructive",onClick:X,leftIcon:t.jsx(ue,{size:14}),children:"Stop"})]})]})}const Dt="_panel_12s9l_1",Tt="_empty_12s9l_8",C={panel:Dt,empty:Tt};function Kt({tab:e}){const{sessionId:n}=e.data,s=f(i=>i.sessions[n]);return Ne(n),s?t.jsxs("div",{className:C.panel,children:[t.jsx(ht,{session:s}),t.jsx(Ct,{sessionId:s.id})]}):t.jsx("div",{className:C.panel,children:t.jsx("div",{className:C.empty,children:"Session not found."})})}export{Kt as ChatPanel};
3
+ //# sourceMappingURL=ChatPanel-BOR3MYdN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatPanel-BOR3MYdN.js","sources":["../../../src/ui/status-badge.ts","../../../src/components/primitives/StatusBadge.tsx","../../../src/timeline/tool-mapping.ts","../../../src/lib/connection/files-changed.ts","../../../src/hooks/agent-stream-events.ts","../../../src/hooks/use-agent-stream.ts","../../../src/features/chat/rows/AssistantRow.tsx","../../../src/features/chat/rows/ReasoningRow.tsx","../../../src/features/chat/rows/ToolCard.tsx","../../../src/features/chat/rows/UserRow.tsx","../../../src/features/chat/rows/ErrorRow.tsx","../../../src/features/chat/rows/SystemRow.tsx","../../../src/features/chat/Timeline.tsx","../../../src/features/chat/Attachments.tsx","../../../src/features/chat/Composer.tsx","../../../src/features/chat/ChatPanel.tsx"],"sourcesContent":["// StatusBadge + Alert variant contracts.\n// ui-components.md § Surfaces / badges / chips / avatars\n\nexport type StatusBadgeVariant = \"success\" | \"error\" | \"muted\";\n\n// Single semantic token per variant — text color AND a translucent tint of the SAME token as\n// background (matches `ChangesPanel.module.css`'s `.added`/`.modified`/`.deleted` convention).\n// Never split bg/border/text across different token families (e.g. `success` vs `statusSuccess`)\n// — they can diverge in the theme (dark mode's `success` aliases the blue accent, not green),\n// producing a badge with mismatched border/background colors.\nexport type BadgeTokens = {\n token: string;\n};\n\nexport function statusBadgeTokens(variant: StatusBadgeVariant): BadgeTokens {\n switch (variant) {\n case \"success\":\n return { token: \"statusSuccess\" };\n case \"error\":\n return { token: \"statusDanger\" };\n case \"muted\":\n return { token: \"foregroundMuted\" };\n }\n}\n\nexport type AlertVariant = \"default\" | \"info\" | \"success\" | \"warning\" | \"error\";\n\nexport type AlertIconInfo = {\n /** Lucide icon name string for the alert variant. */\n icon: string;\n /** Theme token key for the icon/border/title accent color. */\n accentToken: string;\n};\n\nexport function alertIconInfo(variant: AlertVariant): AlertIconInfo {\n switch (variant) {\n case \"default\":\n return { icon: \"info\", accentToken: \"foregroundMuted\" };\n case \"info\":\n return { icon: \"info\", accentToken: \"accent\" };\n case \"success\":\n return { icon: \"check-circle\", accentToken: \"success\" };\n case \"warning\":\n return { icon: \"triangle-alert\", accentToken: \"statusWarning\" };\n case \"error\":\n return { icon: \"circle-x\", accentToken: \"destructive\" };\n }\n}\n\n// AttachmentPill remove-button visibility — mirrors hover-to-show pattern.\nexport function attachmentPillRemoveVisible(\n isNativeCtx: boolean,\n isCompact: boolean,\n hovered: boolean,\n): boolean {\n return isNativeCtx || isCompact || hovered;\n}\n","/**\n * StatusBadge — small rounded pill with variant coloring.\n * ui-components.md § Surfaces / badges / chips / avatars\n */\n\nimport { type ReactNode } from \"react\";\nimport styles from \"./StatusBadge.module.css\";\nimport { statusBadgeTokens, type StatusBadgeVariant } from \"@pi-studio-ui/ui/status-badge.js\";\n\nexport interface StatusBadgeProps {\n label: ReactNode;\n variant: StatusBadgeVariant;\n className?: string;\n}\n\nexport function StatusBadge({ label, variant, className }: StatusBadgeProps) {\n const { token } = statusBadgeTokens(variant);\n const color = `var(--pi-color-${token})`;\n return (\n <span\n className={`${styles.badge}${className ? ` ${className}` : \"\"}`}\n style={{\n backgroundColor: `color-mix(in srgb, ${color} 18%, transparent)`,\n borderColor: `color-mix(in srgb, ${color} 45%, transparent)`,\n color,\n }}\n >\n {label}\n </span>\n );\n}\n","/**\n * tool.kind → display label + icon. Scoped to the protocol's `ToolCallDetail` union\n * (shell/read/edit/write/search/fetch/task) — POC_TO_APP_PLAN_UI.md §3 `timeline/tool-mapping.ts`.\n */\n\nimport type { ToolCallDetail } from \"@av-pi-studio/protocol\";\nimport {\n Terminal,\n Eye,\n Pencil,\n FilePlus,\n Search,\n Globe,\n ListTree,\n type LucideIcon,\n} from \"lucide-react\";\nimport { toolDetailText } from \"./row-model.js\";\n\nconst ICON_BY_KIND: Record<ToolCallDetail[\"kind\"], LucideIcon> = {\n shell: Terminal,\n read: Eye,\n edit: Pencil,\n write: FilePlus,\n search: Search,\n fetch: Globe,\n task: ListTree,\n};\n\nexport function toolIcon(tool: ToolCallDetail): LucideIcon {\n return ICON_BY_KIND[tool.kind] ?? Terminal;\n}\n\n/** Human label for a tool card header, e.g. \"shell\", \"edit\". */\nexport function toolLabel(tool: ToolCallDetail): string {\n return tool.kind;\n}\n\n/** One-line detail summary for a tool card (POC: `t.command || t.path || t.query || …`). */\nexport function toolSummary(tool: ToolCallDetail): string {\n return toolDetailText(tool);\n}\n\n/** File path a tool touched, if any — used to target `filesChanged` invalidation (§4.5). */\nexport function toolFilePath(tool: ToolCallDetail): string | null {\n switch (tool.kind) {\n case \"read\":\n case \"write\":\n case \"edit\":\n return tool.path ?? null;\n default:\n return null;\n }\n}\n\n/** True for tool kinds whose completion should invalidate open file/diff/explorer/git queries. */\nexport function toolMutatesFiles(tool: ToolCallDetail): boolean {\n return tool.kind === \"write\" || tool.kind === \"edit\" || tool.kind === \"shell\";\n}\n\n/**\n * Expandable detail body for a tool card, as an ordered list of sections. `diff` renders as a\n * `<DiffView>`; `code` renders in a monospace block. Order: input detail (command/path/query/…)\n * first, then tool output (stdout, file content, etc.) if present. Empty array = header-only card\n * with nothing worth expanding.\n */\nexport type ToolBodySection = { kind: \"diff\"; patch: string } | { kind: \"code\"; text: string };\n\nexport function toolBody(tool: ToolCallDetail): ToolBodySection[] {\n const sections: ToolBodySection[] = [];\n if (tool.kind === \"edit\" && tool.diff) sections.push({ kind: \"diff\", patch: tool.diff });\n else {\n const text = toolDetailText(tool);\n if (text) sections.push({ kind: \"code\", text });\n }\n if (tool.output) sections.push({ kind: \"code\", text: tool.output });\n return sections;\n}\n\n/** Concise inline label shown in the collapsed header (basename for paths, first line for commands). */\nexport function toolInlineDetail(tool: ToolCallDetail): string {\n const text = toolDetailText(tool);\n if (!text) return \"\";\n switch (tool.kind) {\n case \"read\":\n case \"write\":\n case \"edit\": {\n const parts = text.split(\"/\");\n return parts[parts.length - 1] || text;\n }\n case \"shell\": {\n const firstLine = text.split(\"\\n\", 1)[0] ?? text;\n return firstLine;\n }\n default:\n return text;\n }\n}\n","/**\n * `filesChanged` signal — invalidates the file/diff/explorer query caches after a completed\n * write/edit/shell tool call (POC `scheduleFileRefresh`/`reloadOpenFileTabs`, chat.html\n * ~line 1070-1092, POC_TO_APP_PLAN_UI.md §4.3/§4.5/§5). Debounced ~500ms to coalesce a burst of\n * tool completions (POC used `setTimeout(...,600)`/`400`).\n *\n * The daemon does NOT push a `checkout_status_update` automatically after a git-affecting tool\n * completion, so this module also fires an explicit `checkout_refresh_request` for the active\n * cwd — `ChangesPanel`/`FileExplorer` then pick up the resulting push/refetch reactively, since\n * they're already subscribed (`use-checkout-status.ts`) or query-cache-driven (`use-explorer.ts`).\n *\n * NOTE (follow-up, cross-slice): this module only *exports* the invalidation function. The actual\n * call site — wiring this into the timeline reducer's `tool_call` handling on `status:\"completed\"`\n * for `toolMutatesFiles(tool) === true` — belongs to the chat/session-store slice (built in\n * parallel) or the top-level app wiring; do not import/call it from `timeline/reducer.ts` here,\n * that file is a shared contract file owned across slices.\n */\n\nimport type { QueryClient } from \"@tanstack/react-query\";\nimport type { PiStudioClient } from \"@av-pi-studio/client\";\n\nconst DEBOUNCE_MS = 500;\nlet pendingTimer: ReturnType<typeof setTimeout> | undefined;\n\n/**\n * Invalidate file/diff/explorer TanStack Query caches and refresh git status for `cwd`.\n * Debounced: repeated calls within `DEBOUNCE_MS` coalesce into a single invalidation pass.\n *\n * `toolFilePath` is accepted for future path-scoped invalidation (POC `reloadOpenFileTabs`\n * matched by path suffix) but the current pass invalidates the whole `file`/`explorer` query\n * families — TanStack Query dedupes refetches for inactive queries, so this is cheap and correct\n * even when the path is unknown.\n */\nexport function invalidateAfterToolCompletion(\n queryClient: QueryClient,\n client: PiStudioClient | null,\n cwd: string | null,\n _toolFilePath: string | null,\n): void {\n clearTimeout(pendingTimer);\n pendingTimer = setTimeout(() => {\n pendingTimer = undefined;\n void queryClient.invalidateQueries({ queryKey: [\"file\"] });\n void queryClient.invalidateQueries({ queryKey: [\"explorer\"] });\n if (client && cwd) {\n void client.connection.request(\"checkout_refresh_request\", { cwd }).catch(() => {});\n }\n }, DEBOUNCE_MS);\n}\n","/**\n * Shared `AgentStreamEvent` → store-update logic, used both by the live subscription\n * (`use-agent-stream.ts`) and by the live first-turn apply in `Composer.tsx` (see its comment for\n * why: `create_agent_request` blocks until the whole first turn completes, so the timeline is fed\n * from the raw broadcast as events arrive rather than from a not-yet-attached subscription).\n */\n\nimport type { PiStudioClient } from \"@av-pi-studio/client\";\nimport type { AgentStreamEvent } from \"@av-pi-studio/protocol\";\nimport type { QueryClient } from \"@tanstack/react-query\";\nimport { useSessionStore } from \"@pi-studio-ui/stores/session-store.js\";\nimport { toolMutatesFiles, toolFilePath } from \"@pi-studio-ui/timeline/tool-mapping.js\";\nimport { invalidateAfterToolCompletion } from \"@pi-studio-ui/lib/connection/files-changed.js\";\n\nexport interface ApplyAgentStreamEventArgs {\n sessionId: string;\n event: AgentStreamEvent;\n client: PiStudioClient;\n queryClient: QueryClient;\n}\n\nexport function applyAgentStreamEvent({\n sessionId,\n event,\n client,\n queryClient,\n}: ApplyAgentStreamEventArgs): void {\n const sessionStore = useSessionStore.getState();\n sessionStore.applyStreamEvent(sessionId, event);\n switch (event.kind) {\n case \"turn_started\":\n sessionStore.setStatus(sessionId, \"running\");\n break;\n case \"turn_completed\":\n sessionStore.setStatus(sessionId, \"idle\");\n break;\n case \"turn_failed\":\n sessionStore.setStatus(sessionId, \"error\");\n break;\n case \"turn_canceled\":\n sessionStore.setStatus(sessionId, \"idle\");\n break;\n case \"tool_call\": {\n if (event.status === \"completed\" && toolMutatesFiles(event.tool)) {\n const session = sessionStore.sessions[sessionId];\n const cwd = session?.cwd || null;\n invalidateAfterToolCompletion(queryClient, client, cwd, toolFilePath(event.tool));\n }\n break;\n }\n default:\n break;\n }\n}\n","/**\n * Live timeline subscription for a chat session. Subscribes to\n * `client.agent(agentId).timeline.subscribe` whenever both the connection's `client` and the\n * session's `agentId` are available, routing every event through `applyAgentStreamEvent`\n * (POC_TO_APP_PLAN_UI.md §4.3). Also mirrors the POC's `handleAgentStream` turn-lifecycle →\n * `session.status` transitions so the Composer's Stop button and the session-list status dot\n * stay live.\n *\n * NOTE: this only covers events that arrive *after* the subscription attaches, which requires a\n * bound `agentId`. The daemon runs the whole first turn of a brand-new agent *before*\n * `create_agent_request` resolves, so `Composer.handleSend` applies that first turn live from the\n * raw broadcast (latching the agent id from the first `agent_stream` frame it sees) and only\n * calls `bindAgent` once the RPC resolves — after the turn is already applied. This hook therefore\n * attaches only in time for *follow-up* turns, which is exactly what avoids double-applying the\n * first turn (the reducer is not event-id-idempotent).\n */\n\nimport { useEffect } from \"react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { useSessionStore } from \"@pi-studio-ui/stores/session-store.js\";\nimport { applyAgentStreamEvent } from \"./agent-stream-events.js\";\n\nexport function useAgentStream(sessionId: string): void {\n const client = useConnectionStore((s) => s.client);\n const agentId = useSessionStore((s) => s.sessions[sessionId]?.agentId ?? null);\n const queryClient = useQueryClient();\n\n useEffect(() => {\n if (!client || !agentId) return;\n\n const unsubscribe = client.agent(agentId).timeline.subscribe((event) => {\n applyAgentStreamEvent({ sessionId, event, client, queryClient });\n });\n\n return unsubscribe;\n }, [client, agentId, sessionId, queryClient]);\n}\n","/**\n * Assistant message row (POC `.msg.assistant`, POC_TO_APP_PLAN_UI.md §4.3). While streaming,\n * renders plain text with a blinking cursor — re-parsing markdown on every token would be\n * wasteful (§6 Performance strategy). Once `turn_completed` finalizes the row (`streaming:\n * false`), it renders through `<Markdown>`.\n */\n\nimport type { AssistantRow as AssistantRowModel } from \"@pi-studio-ui/timeline/row-model.js\";\nimport { Markdown } from \"@pi-studio-ui/timeline/markdown.js\";\nimport styles from \"./rows.module.css\";\n\nexport interface AssistantRowProps {\n row: AssistantRowModel;\n}\n\nexport function AssistantRow({ row }: AssistantRowProps) {\n return (\n <div className={`${styles.row} ${styles.assistant}${row.streaming ? ` ${styles.streaming}` : \"\"}`}>\n <span className={styles.who}>assistant</span>\n {row.streaming ? (\n <span className={styles.streamingText}>\n {row.text}\n <span className={styles.cursor}>▍</span>\n </span>\n ) : (\n <Markdown text={row.text} />\n )}\n </div>\n );\n}\n","/**\n * Reasoning row — dimmer/italic styling, visually distinct from assistant replies (POC\n * `.msg.reasoning`, POC_TO_APP_PLAN_UI.md §4.3). Streams as plain text with a cursor, same\n * rationale as `AssistantRow`.\n */\n\nimport type { ReasoningRow as ReasoningRowModel } from \"@pi-studio-ui/timeline/row-model.js\";\nimport { Markdown } from \"@pi-studio-ui/timeline/markdown.js\";\nimport styles from \"./rows.module.css\";\n\nexport interface ReasoningRowProps {\n row: ReasoningRowModel;\n}\n\nexport function ReasoningRow({ row }: ReasoningRowProps) {\n return (\n <div className={`${styles.row} ${styles.reasoning}`}>\n <span className={styles.who}>thinking</span>\n {row.streaming ? (\n <span className={styles.streamingText}>\n {row.text}\n <span className={styles.cursor}>▍</span>\n </span>\n ) : (\n <Markdown text={row.text} />\n )}\n </div>\n );\n}\n","/**\n * Tool call card — icon + label + inline detail + status badge header, with an expandable body\n * (POC `.msg.tool`, POC_TO_APP_PLAN_UI.md §4.3). Collapsed by default; clicking the header reveals\n * the full detail as an ordered list of sections: input detail (`edit` diffs render through the\n * shared `<DiffView>`; shell command/path/query/url/task description render in a monospace block)\n * followed by tool output (stdout, file content, etc.), when present. Cards with no expandable\n * sections (`toolBody` → `[]`) show only the header. The virtualized timeline re-measures on\n * expand via `measureElement`, so height changes need no special handling here.\n */\n\nimport { useState } from \"react\";\nimport { ChevronRight } from \"lucide-react\";\nimport { clsx } from \"clsx\";\nimport { StatusBadge } from \"@pi-studio-ui/components/primitives/StatusBadge.js\";\nimport { DiffView } from \"@pi-studio-ui/features/files/DiffView.js\";\nimport { toolIcon, toolLabel, toolInlineDetail, toolBody } from \"@pi-studio-ui/timeline/tool-mapping.js\";\nimport type { ToolRow } from \"@pi-studio-ui/timeline/row-model.js\";\nimport styles from \"./rows.module.css\";\n\nexport interface ToolCardProps {\n row: ToolRow;\n}\n\nconst BADGE_VARIANT = {\n running: \"muted\",\n completed: \"success\",\n error: \"error\",\n} as const;\n\nexport function ToolCard({ row }: ToolCardProps) {\n const [expanded, setExpanded] = useState(false);\n const Icon = toolIcon(row.tool);\n const inlineDetail = toolInlineDetail(row.tool);\n const body = toolBody(row.tool);\n const hasBody = body.length > 0;\n\n return (\n <div className={clsx(styles.row, styles.tool)}>\n <button\n type=\"button\"\n className={styles.toolHeader}\n onClick={() => hasBody && setExpanded((v) => !v)}\n aria-expanded={hasBody ? expanded : undefined}\n disabled={!hasBody}\n >\n {hasBody && (\n <ChevronRight\n size={12}\n className={clsx(styles.toolChevron, expanded && styles.toolChevronOpen)}\n />\n )}\n <span className={clsx(styles.toolIcon, styles[row.status])}>\n <Icon size={14} />\n </span>\n <span className={styles.toolLabel}>{toolLabel(row.tool)}</span>\n {inlineDetail && (\n <span className={styles.toolInlineDetail} title={inlineDetail}>\n {inlineDetail}\n </span>\n )}\n <StatusBadge className={styles.toolBadge} label={row.status} variant={BADGE_VARIANT[row.status]} />\n </button>\n {expanded && hasBody && (\n <div className={styles.toolBody}>\n {body.map((section, i) =>\n section.kind === \"diff\" ? (\n <DiffView key={i} patch={section.patch} />\n ) : (\n <pre key={i} className={styles.toolCode}>\n {section.text}\n </pre>\n ),\n )}\n </div>\n )}\n </div>\n );\n}\n","/**\n * User message row — plain text + optional image thumbnails (POC `.msg.user`,\n * POC_TO_APP_PLAN_UI.md §4.3/§4.4). Images render at up to 200×150, matching the POC's inline\n * `userBubble.appendChild(img)` styling. Clicking a thumbnail opens it full-size in a `Dialog`.\n */\n\nimport { useState } from \"react\";\nimport { Dialog } from \"@pi-studio-ui/components/primitives/Dialog.js\";\nimport type { UserRow as UserRowModel } from \"@pi-studio-ui/timeline/row-model.js\";\nimport styles from \"./rows.module.css\";\n\nexport interface UserRowProps {\n row: UserRowModel;\n}\n\nexport function UserRow({ row }: UserRowProps) {\n const [openedSrc, setOpenedSrc] = useState<string | null>(null);\n\n return (\n <div className={`${styles.row} ${styles.user}`}>\n <span className={styles.who}>you</span>\n {row.text}\n {row.images && row.images.length > 0 && (\n <div className={styles.userImages}>\n {row.images.map((img, i) => {\n const src = `data:${img.mimeType ?? \"image/png\"};base64,${img.data ?? \"\"}`;\n return (\n <img\n key={i}\n className={styles.userImage}\n src={src}\n alt=\"\"\n role=\"button\"\n tabIndex={0}\n onClick={() => setOpenedSrc(src)}\n onKeyDown={(ev) => {\n if (ev.key === \"Enter\" || ev.key === \" \") setOpenedSrc(src);\n }}\n />\n );\n })}\n </div>\n )}\n <Dialog\n open={openedSrc !== null}\n onOpenChange={(open) => {\n if (!open) setOpenedSrc(null);\n }}\n title=\"Image\"\n width=\"auto\"\n bare\n className={styles.imageDialog}\n >\n {openedSrc && <img className={styles.imageDialogPreview} src={openedSrc} alt=\"\" />}\n </Dialog>\n </div>\n );\n}\n","/**\n * Error row — red-tinted block (POC `.msg.err`, POC_TO_APP_PLAN_UI.md §4.3), used for\n * `turn_failed`/`error` stream events.\n */\n\nimport type { ErrorRow as ErrorRowModel } from \"@pi-studio-ui/timeline/row-model.js\";\nimport styles from \"./rows.module.css\";\n\nexport interface ErrorRowProps {\n row: ErrorRowModel;\n}\n\nexport function ErrorRow({ row }: ErrorRowProps) {\n return (\n <div className={`${styles.row} ${styles.error}`}>\n <span className={styles.who}>error</span>\n {row.text}\n </div>\n );\n}\n","/**\n * System row — muted/italic styling (POC's `turn_canceled` \"(canceled)\" reasoning-styled bubble,\n * POC_TO_APP_PLAN_UI.md §4.3), used for canceled-turn markers and other system notices.\n */\n\nimport type { SystemRow as SystemRowModel } from \"@pi-studio-ui/timeline/row-model.js\";\nimport styles from \"./rows.module.css\";\n\nexport interface SystemRowProps {\n row: SystemRowModel;\n}\n\nexport function SystemRow({ row }: SystemRowProps) {\n return <div className={`${styles.row} ${styles.system}`}>{row.text}</div>;\n}\n","/**\n * Virtualized timeline viewport — TanStack Virtual over `session.timeline.rows` with\n * variable-size rows (POC `.chat-area`, POC_TO_APP_PLAN_UI.md §4.3/§6). Auto-scrolls to the\n * bottom on new rows unless the user has scrolled up more than 40px from the bottom; re-sticks\n * whenever the row count grows from a user's own send (POC's forced `scrollTop = scrollHeight`,\n * fixed to respect manual scroll-up).\n */\n\nimport { useEffect, useRef } from \"react\";\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\nimport type { SessionEntry } from \"@pi-studio-ui/stores/session-store.js\";\nimport type { TimelineRow } from \"@pi-studio-ui/timeline/row-model.js\";\nimport { AssistantRow } from \"./rows/AssistantRow.js\";\nimport { ReasoningRow } from \"./rows/ReasoningRow.js\";\nimport { ToolCard } from \"./rows/ToolCard.js\";\nimport { UserRow } from \"./rows/UserRow.js\";\nimport { ErrorRow } from \"./rows/ErrorRow.js\";\nimport { SystemRow } from \"./rows/SystemRow.js\";\nimport styles from \"./Timeline.module.css\";\n\nexport interface TimelineProps {\n session: SessionEntry;\n}\n\nconst STICK_THRESHOLD_PX = 40;\n\nfunction renderRow(row: TimelineRow) {\n switch (row.kind) {\n case \"user\":\n return <UserRow row={row} />;\n case \"assistant\":\n return <AssistantRow row={row} />;\n case \"reasoning\":\n return <ReasoningRow row={row} />;\n case \"tool\":\n return <ToolCard row={row} />;\n case \"error\":\n return <ErrorRow row={row} />;\n case \"system\":\n return <SystemRow row={row} />;\n }\n}\n\nexport function Timeline({ session }: TimelineProps) {\n const rows = session.timeline.rows;\n const running = session.status === \"running\";\n const scrollRef = useRef<HTMLDivElement>(null);\n const stickToBottomRef = useRef(true);\n const prevRowCountRef = useRef(rows.length);\n\n const virtualizer = useVirtualizer({\n count: rows.length,\n getScrollElement: () => scrollRef.current,\n estimateSize: () => 48,\n overscan: 8,\n getItemKey: (index) => rows[index]?.id ?? index,\n });\n\n function handleScroll(): void {\n const el = scrollRef.current;\n if (!el) return;\n const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;\n stickToBottomRef.current = distanceFromBottom < STICK_THRESHOLD_PX;\n }\n\n useEffect(() => {\n const grew = rows.length > prevRowCountRef.current;\n prevRowCountRef.current = rows.length;\n if (!grew) return;\n if (!stickToBottomRef.current) return;\n virtualizer.scrollToIndex(rows.length - 1, { align: \"end\" });\n }, [rows.length, virtualizer]);\n\n if (rows.length === 0) {\n return (\n <div className={styles.viewport} ref={scrollRef}>\n {running ? (\n <div className={styles.working} role=\"status\" aria-live=\"polite\">\n <span className={styles.workingDots} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n Agent is working…\n </div>\n ) : (\n <div className={styles.empty}>No messages yet — say something to start.</div>\n )}\n </div>\n );\n }\n\n return (\n <div className={styles.viewport} ref={scrollRef} onScroll={handleScroll}>\n <div className={styles.inner} style={{ height: virtualizer.getTotalSize() }}>\n {virtualizer.getVirtualItems().map((virtualRow) => {\n const row = rows[virtualRow.index];\n if (!row) return null;\n return (\n <div\n key={virtualRow.key}\n ref={virtualizer.measureElement}\n data-index={virtualRow.index}\n className={styles.rowWrap}\n style={{ transform: `translateY(${virtualRow.start}px)` }}\n >\n {renderRow(row)}\n </div>\n );\n })}\n </div>\n {running && (\n <div className={styles.working} role=\"status\" aria-live=\"polite\">\n <span className={styles.workingDots} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n Agent is working…\n </div>\n )}\n </div>\n );\n}\n","/**\n * Attachment preview strip — presentational thumbnail row with remove buttons (POC\n * `renderAttachments`, POC_TO_APP_PLAN_UI.md §4.4). Pending-image state lives in `Composer`;\n * this component only renders `images` and forwards `onRemove(index)`.\n */\n\nimport styles from \"./Attachments.module.css\";\n\nexport interface PendingImage {\n mimeType: string;\n /** Base64 payload, no `data:...;base64,` prefix — sent to the daemon as-is. */\n data: string;\n /** Full data URL, for the thumbnail `<img src>`. */\n thumbUrl: string;\n}\n\nexport interface AttachmentsProps {\n images: PendingImage[];\n onRemove: (index: number) => void;\n}\n\nexport function Attachments({ images, onRemove }: AttachmentsProps) {\n if (images.length === 0) return null;\n\n return (\n <div className={styles.strip}>\n {images.map((img, i) => (\n <div key={i} className={styles.thumb}>\n <img src={img.thumbUrl} alt=\"\" />\n <button\n type=\"button\"\n className={styles.remove}\n title=\"Remove\"\n onClick={() => onRemove(i)}\n >\n ×\n </button>\n </div>\n ))}\n </div>\n );\n}\n\n/** `FileReader.readAsDataURL` wrapped as a promise (POC `addImageFile`). */\nexport function readImageFile(file: File): Promise<PendingImage> {\n const { promise, resolve, reject } = Promise.withResolvers<PendingImage>();\n const reader = new FileReader();\n reader.onload = () => {\n const result = reader.result;\n if (typeof result !== \"string\") {\n reject(new Error(\"Unexpected FileReader result\"));\n return;\n }\n const commaIndex = result.indexOf(\",\");\n const data = commaIndex >= 0 ? result.slice(commaIndex + 1) : result;\n resolve({ mimeType: file.type, data, thumbUrl: result });\n };\n reader.onerror = () => reject(reader.error ?? new Error(\"Failed to read file\"));\n reader.readAsDataURL(file);\n return promise;\n}\n","/**\n * Composer — autosizing textarea + attach/send/stop (POC `initChatPanel`/`send()`,\n * POC_TO_APP_PLAN_UI.md §4.4). Send routing: no `agentId` yet → `client.createAgent(...)` then\n * `sessionStore.bindAgent` on success (the race-free binding point `useAgentStream` reacts to);\n * else → `client.agent(agentId).send(...)`. Stop → `client.agent(agentId).interrupt()`.\n *\n * IMPORTANT: `create_agent_request` (and `send_agent_prompt`) block server-side until the whole\n * turn finishes, broadcasting every `agent_stream` event for that turn *before* the RPC resolves.\n * A brand-new agent has no live subscription yet at that point — `useAgentStream` only attaches\n * once `bindAgent` sets `agentId`, which happens *after* `createAgent()` resolves. So for the\n * first turn `handleSend` subscribes to the raw broadcast directly and applies each `agent_stream`\n * event *as it arrives*, latching the new agent's id from the first such event (agent_update\n * frames are top-level and never reach `onSessionMessage`; agent_stream frames are session-wrapped\n * and do). So the user message and streamed response appear live instead of all at once when the\n * RPC returns.\n * `bindAgent` is deferred until the RPC resolves so `useAgentStream` attaches only for follow-up\n * turns and never re-applies the first turn (the reducer is not event-id-idempotent).\n */\n\nimport { useRef, useState, type ChangeEvent, type ClipboardEvent, type KeyboardEvent } from \"react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { Paperclip, Send, Square } from \"lucide-react\";\nimport type { AgentStreamEvent } from \"@av-pi-studio/protocol\";\nimport { Button } from \"@pi-studio-ui/components/primitives/Button.js\";\nimport { TextArea } from \"@pi-studio-ui/components/primitives/TextInput.js\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { useSessionStore } from \"@pi-studio-ui/stores/session-store.js\";\nimport { applyAgentStreamEvent } from \"@pi-studio-ui/hooks/agent-stream-events.js\";\nimport { Attachments, readImageFile, type PendingImage } from \"./Attachments.js\";\nimport styles from \"./Composer.module.css\";\n\nconst MAX_TEXTAREA_HEIGHT = 160;\n\nexport interface ComposerProps {\n sessionId: string;\n}\n\nfunction autoResize(el: HTMLTextAreaElement): void {\n el.style.height = \"auto\";\n el.style.height = `${Math.min(el.scrollHeight, MAX_TEXTAREA_HEIGHT)}px`;\n}\n\ninterface AgentStreamMessage {\n type: \"agent_stream\";\n agentId: string;\n event: AgentStreamEvent;\n}\n\nfunction isAgentStreamMessage(msg: unknown): msg is AgentStreamMessage {\n if (typeof msg !== \"object\" || msg === null) return false;\n if (!(\"type\" in msg) || msg.type !== \"agent_stream\") return false;\n return \"agentId\" in msg && typeof msg.agentId === \"string\" && \"event\" in msg;\n}\n\nexport function Composer({ sessionId }: ComposerProps) {\n const client = useConnectionStore((s) => s.client);\n const session = useSessionStore((s) => s.sessions[sessionId]);\n const bindAgent = useSessionStore((s) => s.bindAgent);\n const addUserMessage = useSessionStore((s) => s.addUserMessage);\n const setCwd = useSessionStore((s) => s.setCwd);\n const queryClient = useQueryClient();\n\n const [text, setText] = useState(\"\");\n const [images, setImages] = useState<PendingImage[]>([]);\n const [sending, setSending] = useState(false);\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n const running = session?.status === \"running\";\n const canSend = Boolean(client) && !sending && (text.trim().length > 0 || images.length > 0);\n\n async function addImageFile(file: File): Promise<void> {\n if (!file.type.startsWith(\"image/\")) return;\n try {\n const image = await readImageFile(file);\n setImages((prev) => [...prev, image]);\n } catch {\n // Best-effort attachment read — ignore unreadable files (POC has no error UI here either).\n }\n }\n\n function handleFileInputChange(ev: ChangeEvent<HTMLInputElement>): void {\n for (const file of ev.target.files ?? []) void addImageFile(file);\n ev.target.value = \"\";\n }\n\n function handlePaste(ev: ClipboardEvent<HTMLTextAreaElement>): void {\n for (const item of ev.clipboardData.items) {\n if (item.type.startsWith(\"image/\")) {\n ev.preventDefault();\n const file = item.getAsFile();\n if (file) void addImageFile(file);\n }\n }\n }\n\n function handleRemoveImage(index: number): void {\n setImages((prev) => prev.filter((_, i) => i !== index));\n }\n\n function handleTextareaChange(ev: ChangeEvent<HTMLTextAreaElement>): void {\n setText(ev.target.value);\n autoResize(ev.target);\n }\n\n function handleKeyDown(ev: KeyboardEvent<HTMLTextAreaElement>): void {\n if (ev.key === \"Enter\" && !ev.shiftKey) {\n ev.preventDefault();\n void handleSend();\n }\n }\n\n async function handleSend(): Promise<void> {\n if (!client || !session || sending) return;\n const trimmed = text.trim();\n if (!trimmed && images.length === 0) return;\n\n setText(\"\");\n if (textareaRef.current) autoResize(textareaRef.current);\n const sentImages = images;\n setImages([]);\n\n // No optimistic local echo: the daemon canonically emits exactly one `user_message`\n // stream event per prompt (packages/server/src/agent/agent-service.ts), broadcast to this\n // session's timeline subscription — the reducer renders it from there. Echoing locally too\n // would double the row; unlike the POC (whose `handleAgentStream` switch has no\n // `user_message` case and so never rendered the server's broadcast), this store renders\n // every event kind uniformly, so the server is the single source of truth here.\n addUserMessage(sessionId);\n const prompt = trimmed || \"Describe this image\";\n const rpcImages =\n sentImages.length > 0\n ? sentImages.map((img) => ({ mimeType: img.mimeType, data: img.data }))\n : undefined;\n\n setSending(true);\n try {\n if (!session.agentId) {\n const cwd = session.cwd || \"~\";\n setCwd(sessionId, cwd);\n // The daemon runs the entire first turn *before* `createAgent()` resolves, broadcasting\n // every `agent_stream` event for it in the meantime (agent-service.ts `runTurn`). If we\n // waited for the RPC to learn our agentId the timeline would stay empty (and show no\n // running indicator) until the whole turn finished — the reported bug. Instead we watch\n // the live broadcast during the call and apply events as they arrive.\n //\n // We latch the agent id from the *first `agent_stream` event* we see, not from\n // `agent_update{status:\"initializing\"}`: `agent_update` is broadcast as a bare top-level\n // frame that the client's frame router drops (only `session`-wrapped frames reach\n // `onSessionMessage`), whereas every `agent_stream` frame IS `session`-wrapped and carries\n // its `agentId`. The daemon creates exactly one agent per `createAgent`, so the first\n // stream event on this socket after we subscribe belongs to our new agent.\n //\n // `bindAgent` is deferred until *after* the RPC resolves: binding attaches\n // `useAgentStream`, and since the reducer isn't event-id-idempotent, a mid-turn attach\n // would double every remaining row. Binding post-resolve means this handler is the sole\n // applier for the first turn and `useAgentStream` only ever sees follow-up turns.\n let liveAgentId: string | null = null;\n const unsubscribeLive = client.connection.onSessionMessage((msg) => {\n if (!isAgentStreamMessage(msg)) return;\n if (liveAgentId === null) liveAgentId = msg.agentId;\n if (msg.agentId !== liveAgentId) return;\n applyAgentStreamEvent({ sessionId, event: msg.event, client, queryClient });\n });\n\n let result: { agentId: string };\n try {\n result = await client.createAgent({\n config: { provider: \"pi\", cwd },\n initialPrompt: prompt,\n images: rpcImages,\n labels: {},\n });\n } finally {\n unsubscribeLive();\n }\n\n bindAgent(sessionId, result.agentId);\n } else {\n await client.agent(session.agentId).send(prompt, { images: rpcImages });\n }\n } catch {\n // RPC failures surface via agent_stream `error`/`turn_failed` events (POC swallows the\n // rejection the same way; the stream is the source of truth for user-visible failure).\n } finally {\n setSending(false);\n }\n }\n\n function handleStop(): void {\n if (!session?.agentId) return;\n void client?.agent(session.agentId).interrupt();\n }\n\n return (\n <div className={styles.composer}>\n <div className={styles.inputArea}>\n <TextArea\n ref={textareaRef}\n className={styles.textarea}\n rows={1}\n value={text}\n placeholder=\"Ask anything… (Enter to send, Shift+Enter for newline)\"\n onChange={handleTextareaChange}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n />\n <Attachments images={images} onRemove={handleRemoveImage} />\n </div>\n <Button\n className={styles.attachBtn}\n variant=\"ghost\"\n size=\"md\"\n iconOnly\n title=\"Attach image\"\n onClick={() => fileInputRef.current?.click()}\n >\n <Paperclip size={16} />\n </Button>\n <input\n ref={fileInputRef}\n className={styles.hiddenFileInput}\n type=\"file\"\n accept=\"image/*\"\n multiple\n onChange={handleFileInputChange}\n />\n <div className={styles.actions}>\n <Button size=\"sm\" disabled={!canSend} onClick={() => void handleSend()} leftIcon={<Send size={14} />}>\n Send\n </Button>\n {running && (\n <Button size=\"sm\" variant=\"destructive\" onClick={handleStop} leftIcon={<Square size={14} />}>\n Stop\n </Button>\n )}\n </div>\n </div>\n );\n}\n","/**\n * ChatPanel — the tab content mounted by `panel-registry.ts` for `kind: \"chat\"` tabs. Looks up\n * the session by `tab.data.sessionId`, keeps its timeline live via `useAgentStream`, and renders\n * the virtualized `Timeline` + `Composer` (POC_TO_APP_PLAN_UI.md §4.3/§4.4).\n */\n\nimport { useAgentStream } from \"@pi-studio-ui/hooks/use-agent-stream.js\";\nimport { useSessionStore } from \"@pi-studio-ui/stores/session-store.js\";\nimport type { Tab, ChatTabData } from \"@pi-studio-ui/stores/tab-store.js\";\nimport { Timeline } from \"./Timeline.js\";\nimport { Composer } from \"./Composer.js\";\nimport styles from \"./ChatPanel.module.css\";\n\nexport interface ChatPanelProps {\n tab: Tab;\n}\n\nexport function ChatPanel({ tab }: ChatPanelProps) {\n // `panel-registry.ts` only ever mounts this component for `tab.kind === \"chat\"`, whose `data`\n // is always `ChatTabData` — narrowed here since `Tab[\"data\"]` is a union at the type level.\n const { sessionId } = tab.data as ChatTabData;\n const session = useSessionStore((s) => s.sessions[sessionId]);\n\n useAgentStream(sessionId);\n\n if (!session) {\n return (\n <div className={styles.panel}>\n <div className={styles.empty}>Session not found.</div>\n </div>\n );\n }\n\n return (\n <div className={styles.panel}>\n <Timeline session={session} />\n <Composer sessionId={session.id} />\n </div>\n );\n}\n"],"names":["statusBadgeTokens","variant","StatusBadge","label","className","token","color","jsx","styles","ICON_BY_KIND","Terminal","Eye","Pencil","FilePlus","Search","Globe","ListTree","toolIcon","tool","toolLabel","toolFilePath","toolMutatesFiles","toolBody","sections","text","toolDetailText","toolInlineDetail","parts","DEBOUNCE_MS","pendingTimer","invalidateAfterToolCompletion","queryClient","client","cwd","_toolFilePath","applyAgentStreamEvent","sessionId","event","sessionStore","useSessionStore","session","useAgentStream","useConnectionStore","s","agentId","_a","useQueryClient","useEffect","AssistantRow","row","jsxs","Markdown","ReasoningRow","BADGE_VARIANT","ToolCard","expanded","setExpanded","useState","Icon","inlineDetail","body","hasBody","clsx","v","ChevronRight","section","i","DiffView","UserRow","openedSrc","setOpenedSrc","img","src","ev","Dialog","open","ErrorRow","SystemRow","STICK_THRESHOLD_PX","renderRow","Timeline","rows","running","scrollRef","useRef","stickToBottomRef","prevRowCountRef","virtualizer","useVirtualizer","index","handleScroll","el","distanceFromBottom","grew","virtualRow","Attachments","images","onRemove","readImageFile","file","promise","resolve","reject","reader","result","commaIndex","data","MAX_TEXTAREA_HEIGHT","autoResize","isAgentStreamMessage","msg","Composer","bindAgent","addUserMessage","setCwd","setText","setImages","sending","setSending","textareaRef","fileInputRef","canSend","addImageFile","image","prev","handleFileInputChange","handlePaste","item","handleRemoveImage","handleTextareaChange","handleKeyDown","handleSend","trimmed","sentImages","prompt","rpcImages","liveAgentId","unsubscribeLive","handleStop","TextArea","Button","Paperclip","Send","Square","ChatPanel","tab"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ ._badge_jtokc_3{display:inline-flex;align-items:center;justify-content:center;padding:1px 6px;border-radius:9999px;border:1px solid;font-size:var(--pi-font-size-xs, 12px);font-family:var(--pi-font-ui, system-ui, sans-serif);font-weight:500;white-space:nowrap;line-height:1.5}._row_19zvb_3{padding:8px 12px;border-radius:var(--pi-borderRadius-md, 6px);max-width:85%;white-space:pre-wrap;word-break:break-word;line-height:1.5;font-size:13px}._who_19zvb_13{display:block;font-size:10px;color:var(--pi-color-foregroundMuted, #7a8394);margin-bottom:3px;text-transform:uppercase;letter-spacing:.03em}._user_19zvb_24{background:var(--pi-color-accentMuted, #1e3a5f);color:var(--pi-color-foreground, #dbe9ff);align-self:flex-end;border-bottom-right-radius:2px}._userImages_19zvb_31{display:flex;gap:6px;flex-wrap:wrap;margin-top:6px}._userImage_19zvb_31{max-width:200px;max-height:150px;border-radius:4px;display:block;object-fit:cover;cursor:pointer}._imageDialog_19zvb_47{max-width:90vw}._imageDialogPreview_19zvb_51{display:block;max-width:90vw;max-height:80vh;object-fit:contain}._assistant_19zvb_60{background:var(--pi-color-surface, #1c1f26);color:var(--pi-color-foreground, #d6dae0);align-self:flex-start;border-bottom-left-radius:2px}._assistant_19zvb_60._streaming_19zvb_67{border-left:2px solid var(--pi-color-accent, #2e5cb8)}._streamingText_19zvb_71{white-space:pre-wrap;word-break:break-word}._cursor_19zvb_76{display:inline-block;margin-left:1px;animation:_blink_19zvb_1 1s step-start infinite;color:var(--pi-color-accentBright, #a2b4d7)}@keyframes _blink_19zvb_1{50%{opacity:0}}._reasoning_19zvb_91{background:var(--pi-color-background, #0f1115);color:var(--pi-color-foregroundMuted, #7a8394);font-style:italic;font-size:12px;align-self:flex-start}._tool_19zvb_101{background:var(--pi-color-surface, #1c1f26);border:1px solid var(--pi-color-border, #2c313b);align-self:flex-start;font-size:12px;max-width:90%;padding:0;overflow:hidden}._toolHeader_19zvb_111{display:flex;align-items:center;gap:6px;width:100%;padding:8px 12px;background:none;border:none;color:inherit;font:inherit;text-align:left;cursor:pointer}._toolHeader_19zvb_111:disabled{cursor:default}._toolChevron_19zvb_129{flex-shrink:0;color:var(--pi-color-foregroundMuted, #7a8394);transition:transform .12s ease}._toolChevronOpen_19zvb_135{transform:rotate(90deg)}._toolInlineDetail_19zvb_139{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--pi-color-foregroundMuted, #9aa0a6);font-family:var(--pi-font-mono, ui-monospace, monospace);min-width:0;flex:1}._toolBody_19zvb_149{border-top:1px solid var(--pi-color-border, #2c313b);max-height:420px;overflow:auto}._toolCode_19zvb_155{margin:0;padding:8px 12px;font-family:var(--pi-font-mono, ui-monospace, monospace);font-size:12px;line-height:1.5;white-space:pre-wrap;word-break:break-word;color:var(--pi-color-foreground, #dbe3f0)}._toolIcon_19zvb_166{display:flex;align-items:center;flex-shrink:0}._toolIcon_19zvb_166._running_19zvb_172{color:var(--pi-color-accentBright, #a2b4d7);animation:_pulse_19zvb_1 1.2s ease-in-out infinite}._toolIcon_19zvb_166._completed_19zvb_177{color:var(--pi-color-statusSuccess, #27ae60)}._toolIcon_19zvb_166._error_19zvb_181{color:var(--pi-color-statusDanger, #c0392b)}@keyframes _pulse_19zvb_1{50%{opacity:.5}}._toolLabel_19zvb_191{font-weight:600;text-transform:capitalize}._toolBadge_19zvb_196{margin-left:auto}._error_19zvb_181{background:var(--pi-color-destructive, #3a1515);color:var(--pi-color-destructiveForeground, #ff8a80);align-self:flex-start}._system_19zvb_209{background:transparent;color:var(--pi-color-foregroundMuted, #7a8394);font-style:italic;align-self:flex-start;padding:4px 12px}._viewport_1byko_3{flex:1 1 auto;overflow-y:auto;min-height:0;padding:16px 20px}._inner_1byko_10{position:relative;width:100%}._rowWrap_1byko_15{position:absolute;top:0;left:0;width:100%;display:flex;flex-direction:column;padding-bottom:4px}._empty_1byko_25{display:flex;align-items:center;justify-content:center;height:100%;color:var(--pi-color-foregroundMuted, #7a8394);font-size:13px}._working_1byko_34{display:flex;align-items:center;gap:8px;padding:8px 2px 4px;color:var(--pi-color-foregroundMuted, #7a8394);font-size:13px}._workingDots_1byko_43{display:inline-flex;gap:4px}._workingDots_1byko_43 span{width:6px;height:6px;border-radius:50%;background:var(--pi-color-accentBright, #a2b4d7);animation:_workingBounce_1byko_1 1.2s ease-in-out infinite}._workingDots_1byko_43 span:nth-child(2){animation-delay:.16s}._workingDots_1byko_43 span:nth-child(3){animation-delay:.32s}@keyframes _workingBounce_1byko_1{0%,80%,to{opacity:.3;transform:translateY(0)}40%{opacity:1;transform:translateY(-3px)}}._strip_5sm81_4{display:flex;gap:6px;flex-wrap:wrap;padding:6px 0 0}._thumb_5sm81_11{position:relative;width:48px;height:48px;border-radius:4px;overflow:hidden;border:1px solid var(--pi-color-border, #2c313b);flex-shrink:0}._thumb_5sm81_11 img{width:100%;height:100%;object-fit:cover;display:block}._remove_5sm81_28{position:absolute;top:-2px;right:-2px;width:16px;height:16px;background:var(--pi-color-statusDanger, #c0392b);color:#fff;border:none;border-radius:50%;font-size:10px;line-height:16px;text-align:center;cursor:pointer;padding:0}._composer_1ll3p_1{padding:10px 16px;border-top:1px solid var(--pi-color-border, #2c313b);background:var(--pi-color-surface, #14161a);display:flex;gap:8px;align-items:center;flex-shrink:0}._inputArea_1ll3p_13{flex:1;display:flex;flex-direction:column;gap:0;min-width:0}._textarea_1ll3p_21{resize:none;min-height:38px;max-height:160px;overflow-y:auto}._attachBtn_1ll3p_28{flex-shrink:0}._hiddenFileInput_1ll3p_32{display:none}._actions_1ll3p_36{display:flex;flex-direction:column;gap:4px;flex-shrink:0}._panel_12s9l_1{display:flex;flex-direction:column;height:100%;min-height:0}._empty_12s9l_8{display:flex;align-items:center;justify-content:center;height:100%;color:var(--pi-color-foregroundMuted, #7a8394);font-size:13px}
@@ -0,0 +1,2 @@
1
+ import{u as l}from"./vendor-query-CAh-Aqig.js";import{a as f,r as p}from"./index-DnTTpAX5.js";import{j as i,r as u}from"./vendor-react-BoIQBqDW.js";import{N as d,O as m,P as g,Q as h,S as w,T as x}from"./vendor-BFXtRv5b.js";function q(r,n=!0){const t=f(e=>e.client);return l({queryKey:p.fileRead(r),queryFn:async()=>{if(!t)throw new Error("not connected");const e=await t.connection.request("file_read_request",{path:r});if(!e.ok)throw new Error(e.error??"file read failed");return{content:e.content??"",size:e.size??0}},enabled:!!t&&!!r&&n})}const y="_wrap_17s3u_1",E={wrap:y},c=new Map;function C(r){const[n,t]=u.useState(null);return u.useEffect(()=>{const e=w.matchFilename(x,r);if(!e){t(null);return}const s=c.get(e.name);if(s){t(s);return}let a=!1;return t(null),e.load().then(o=>{c.set(e.name,o),a||t(o)}),()=>{a=!0}},[r]),n?[n]:[]}function v({path:r,content:n}){const t=C(r),e=[d.lineWrapping,m(),...t];return i.jsx("div",{className:E.wrap,children:i.jsx(g,{value:n,theme:h,extensions:e,editable:!1,readOnly:!0,basicSetup:{highlightActiveLine:!1,foldGutter:!1,searchKeymap:!1},height:"100%"})})}export{v as C,q as u};
2
+ //# sourceMappingURL=CodeView-BJb8T6q4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CodeView-BJb8T6q4.js","sources":["../../../src/hooks/use-file-read.ts","../../../src/features/files/CodeView.tsx"],"sourcesContent":["/**\n * `use-file-read` — TanStack Query wrapper over `file_read_request` (POC `loadFileContent`,\n * POC_TO_APP_PLAN_UI.md §4.5 / §3 `hooks/use-file-read.ts`).\n */\n\nimport { useQuery } from \"@tanstack/react-query\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { rpcKeys } from \"@pi-studio-ui/lib/connection/rpc-keys.js\";\n\nexport interface FileReadResult {\n content: string;\n size: number;\n}\n\ninterface FileReadRpcResponse {\n ok: boolean;\n content?: string;\n size?: number;\n error?: string;\n}\n\nexport function useFileRead(path: string, enabled = true) {\n const client = useConnectionStore((s) => s.client);\n\n return useQuery({\n queryKey: rpcKeys.fileRead(path),\n queryFn: async (): Promise<FileReadResult> => {\n if (!client) throw new Error(\"not connected\");\n const response = await client.connection.request<FileReadRpcResponse>(\"file_read_request\", {\n path,\n });\n if (!response.ok) throw new Error(response.error ?? \"file read failed\");\n return { content: response.content ?? \"\", size: response.size ?? 0 };\n },\n enabled: Boolean(client) && Boolean(path) && enabled,\n });\n}\n","/**\n * CodeView — highlighted, read-only file body with a line-number gutter (POC `showFile`/\n * `reloadFileInPanel`, chat.html ~line 812-828, POC_TO_APP_PLAN_UI.md §4.5). Built on CodeMirror 6\n * (`@uiw/react-codemirror`) rather than a hand-rolled `<pre>` + separate gutter `<pre>`: the two\n * previously drifted out of sync on scroll (independent scroll containers, no shared scroll\n * anchor) whenever content was long enough to scroll or lines wrapped. CodeMirror owns a single\n * scrollable viewport with gutter and content painted from the same layout pass, so this class of\n * bug cannot recur.\n */\n\nimport { useEffect, useState } from \"react\";\nimport CodeMirror from \"@uiw/react-codemirror\";\nimport type { Extension } from \"@codemirror/state\";\nimport { EditorView } from \"@codemirror/view\";\nimport { languages } from \"@codemirror/language-data\";\nimport { LanguageDescription, type LanguageSupport, foldGutter } from \"@codemirror/language\";\nimport { githubDark } from \"@uiw/codemirror-theme-github\";\nimport styles from \"./CodeView.module.css\";\n\nexport interface CodeViewProps {\n path: string;\n content: string;\n}\n\n// `LanguageDescription.support` is populated lazily by `.load()`, which dynamically imports the\n// grammar the first time a given language is used and resolves instantly (cached) after that.\nconst supportCache = new Map<string, LanguageSupport>();\n\nfunction useLanguageExtension(path: string): Extension[] {\n const [support, setSupport] = useState<LanguageSupport | null>(null);\n\n useEffect(() => {\n const desc = LanguageDescription.matchFilename(languages, path);\n if (!desc) {\n setSupport(null);\n return;\n }\n const cached = supportCache.get(desc.name);\n if (cached) {\n setSupport(cached);\n return;\n }\n let cancelled = false;\n setSupport(null);\n void desc.load().then((loaded) => {\n supportCache.set(desc.name, loaded);\n if (!cancelled) setSupport(loaded);\n });\n return () => {\n cancelled = true;\n };\n }, [path]);\n\n return support ? [support] : [];\n}\n\nexport function CodeView({ path, content }: CodeViewProps) {\n const languageExt = useLanguageExtension(path);\n const extensions: Extension[] = [EditorView.lineWrapping, foldGutter(), ...languageExt];\n\n return (\n <div className={styles.wrap}>\n <CodeMirror\n value={content}\n theme={githubDark}\n extensions={extensions}\n editable={false}\n readOnly\n basicSetup={{\n highlightActiveLine: false,\n foldGutter: false,\n searchKeymap: false,\n }}\n height=\"100%\"\n />\n </div>\n );\n}\n"],"names":["useFileRead","path","enabled","client","useConnectionStore","s","useQuery","rpcKeys","response","supportCache","useLanguageExtension","support","setSupport","useState","useEffect","desc","LanguageDescription","languages","cached","cancelled","loaded","CodeView","content","languageExt","extensions","EditorView","foldGutter","jsx","styles","CodeMirror","githubDark"],"mappings":"gOAqBO,SAASA,EAAYC,EAAcC,EAAU,GAAM,CACxD,MAAMC,EAASC,EAAoBC,GAAMA,EAAE,MAAM,EAEjD,OAAOC,EAAS,CACd,SAAUC,EAAQ,SAASN,CAAI,EAC/B,QAAS,SAAqC,CAC5C,GAAI,CAACE,EAAQ,MAAM,IAAI,MAAM,eAAe,EAC5C,MAAMK,EAAW,MAAML,EAAO,WAAW,QAA6B,oBAAqB,CACzF,KAAAF,CAAA,CACD,EACD,GAAI,CAACO,EAAS,GAAI,MAAM,IAAI,MAAMA,EAAS,OAAS,kBAAkB,EACtE,MAAO,CAAE,QAASA,EAAS,SAAW,GAAI,KAAMA,EAAS,MAAQ,CAAA,CACnE,EACA,QAAS,EAAQL,GAAW,EAAQF,GAASC,CAAA,CAC9C,CACH,oCCVMO,MAAmB,IAEzB,SAASC,EAAqBT,EAA2B,CACvD,KAAM,CAACU,EAASC,CAAU,EAAIC,EAAAA,SAAiC,IAAI,EAEnEC,OAAAA,EAAAA,UAAU,IAAM,CACd,MAAMC,EAAOC,EAAoB,cAAcC,EAAWhB,CAAI,EAC9D,GAAI,CAACc,EAAM,CACTH,EAAW,IAAI,EACf,MACF,CACA,MAAMM,EAAST,EAAa,IAAIM,EAAK,IAAI,EACzC,GAAIG,EAAQ,CACVN,EAAWM,CAAM,EACjB,MACF,CACA,IAAIC,EAAY,GAChB,OAAAP,EAAW,IAAI,EACVG,EAAK,KAAA,EAAO,KAAMK,GAAW,CAChCX,EAAa,IAAIM,EAAK,KAAMK,CAAM,EAC7BD,GAAWP,EAAWQ,CAAM,CACnC,CAAC,EACM,IAAM,CACXD,EAAY,EACd,CACF,EAAG,CAAClB,CAAI,CAAC,EAEFU,EAAU,CAACA,CAAO,EAAI,CAAA,CAC/B,CAEO,SAASU,EAAS,CAAE,KAAApB,EAAM,QAAAqB,GAA0B,CACzD,MAAMC,EAAcb,EAAqBT,CAAI,EACvCuB,EAA0B,CAACC,EAAW,aAAcC,EAAA,EAAc,GAAGH,CAAW,EAEtF,OACEI,EAAAA,IAAC,MAAA,CAAI,UAAWC,EAAO,KACrB,SAAAD,EAAAA,IAACE,EAAA,CACC,MAAOP,EACP,MAAOQ,EACP,WAAAN,EACA,SAAU,GACV,SAAQ,GACR,WAAY,CACV,oBAAqB,GACrB,WAAY,GACZ,aAAc,EAAA,EAEhB,OAAO,MAAA,CAAA,EAEX,CAEJ"}
@@ -0,0 +1 @@
1
+ ._wrap_17s3u_1{height:100%;min-height:0;overflow:hidden}._wrap_17s3u_1 .cm-theme,._wrap_17s3u_1 .cm-editor{height:100%}
@@ -0,0 +1,3 @@
1
+ import{j as d}from"./vendor-react-BoIQBqDW.js";import{C as f}from"./vendor-BFXtRv5b.js";const h="_diffView_1s170_1",u="_hunkHeader_1s170_7",_="_line_1s170_14",x="_add_1s170_19",m="_del_1s170_23",p="_ctx_1s170_27",k="_ln_1s170_30",i={diffView:h,hunkHeader:u,line:_,add:x,del:m,ctx:p,ln:k},N=/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;function W(a){const r=a.split(`
2
+ `),e=[];let s=0,n=0;for(const t of r){if(t.startsWith("diff --git")){e.push({kind:"hunk-header",text:t});continue}if(!(t.startsWith("index ")||t.startsWith("---")||t.startsWith("+++"))){if(t.startsWith("@@")){const c=N.exec(t);if(c){const l=c[1],o=c[2];s=(l?parseInt(l,10):1)-1,n=(o?parseInt(o,10):1)-1}e.push({kind:"hunk-header",text:t});continue}t.startsWith("+")?(n++,e.push({kind:"add",text:t,lineNumber:n})):t.startsWith("-")?(s++,e.push({kind:"del",text:t,lineNumber:s})):(s++,n++,e.push({kind:"ctx",text:t,lineNumber:n}))}}return e}function w({patch:a}){const r=W(a);return d.jsx("div",{className:i.diffView,children:r.map((e,s)=>e.kind==="hunk-header"?d.jsx("div",{className:i.hunkHeader,children:e.text},s):d.jsxs("div",{className:f(i.line,i[e.kind]),children:[d.jsx("span",{className:i.ln,children:e.lineNumber??""}),e.text]},s))})}export{w as D};
3
+ //# sourceMappingURL=DiffView-B4H-yHuX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DiffView-B4H-yHuX.js","sources":["../../../src/features/files/DiffView.tsx"],"sourcesContent":["/**\n * DiffView — parsed unified-diff renderer (POC `renderDiffHtml`, chat.html ~line 959-971,\n * POC_TO_APP_PLAN_UI.md §4.5). Ported into a typed `parseDiff` + a React component — no\n * `dangerouslySetInnerHTML`/`innerHTML`, unlike the POC.\n */\n\nimport { clsx } from \"clsx\";\nimport styles from \"./DiffView.module.css\";\n\nexport type DiffRowKind = \"hunk-header\" | \"add\" | \"del\" | \"ctx\";\n\nexport interface DiffRow {\n kind: DiffRowKind;\n text: string;\n lineNumber?: number;\n}\n\nconst HUNK_HEADER_RE = /^@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/;\n\n/** Parse a unified-diff patch string into typed rows (POC `renderDiffHtml`, ported verbatim). */\nexport function parseDiff(patch: string): DiffRow[] {\n const lines = patch.split(\"\\n\");\n const rows: DiffRow[] = [];\n let oldLine = 0;\n let newLine = 0;\n\n for (const line of lines) {\n if (line.startsWith(\"diff --git\")) {\n rows.push({ kind: \"hunk-header\", text: line });\n continue;\n }\n if (line.startsWith(\"index \") || line.startsWith(\"---\") || line.startsWith(\"+++\")) {\n continue;\n }\n if (line.startsWith(\"@@\")) {\n const m = HUNK_HEADER_RE.exec(line);\n if (m) {\n const oldStart = m[1];\n const newStart = m[2];\n oldLine = (oldStart ? parseInt(oldStart, 10) : 1) - 1;\n newLine = (newStart ? parseInt(newStart, 10) : 1) - 1;\n }\n rows.push({ kind: \"hunk-header\", text: line });\n continue;\n }\n if (line.startsWith(\"+\")) {\n newLine++;\n rows.push({ kind: \"add\", text: line, lineNumber: newLine });\n } else if (line.startsWith(\"-\")) {\n oldLine++;\n rows.push({ kind: \"del\", text: line, lineNumber: oldLine });\n } else {\n oldLine++;\n newLine++;\n rows.push({ kind: \"ctx\", text: line, lineNumber: newLine });\n }\n }\n\n return rows;\n}\n\nexport interface DiffViewProps {\n patch: string;\n}\n\nexport function DiffView({ patch }: DiffViewProps) {\n const rows = parseDiff(patch);\n\n return (\n <div className={styles.diffView}>\n {rows.map((row, i) =>\n row.kind === \"hunk-header\" ? (\n <div key={i} className={styles.hunkHeader}>\n {row.text}\n </div>\n ) : (\n <div key={i} className={clsx(styles.line, styles[row.kind])}>\n <span className={styles.ln}>{row.lineNumber ?? \"\"}</span>\n {row.text}\n </div>\n ),\n )}\n </div>\n );\n}\n"],"names":["HUNK_HEADER_RE","parseDiff","patch","lines","rows","oldLine","newLine","line","m","oldStart","newStart","DiffView","jsx","styles","row","i","jsxs","clsx"],"mappings":"gSAiBMA,EAAiB,0CAGhB,SAASC,EAAUC,EAA0B,CAClD,MAAMC,EAAQD,EAAM,MAAM;AAAA,CAAI,EACxBE,EAAkB,CAAA,EACxB,IAAIC,EAAU,EACVC,EAAU,EAEd,UAAWC,KAAQJ,EAAO,CACxB,GAAII,EAAK,WAAW,YAAY,EAAG,CACjCH,EAAK,KAAK,CAAE,KAAM,cAAe,KAAMG,EAAM,EAC7C,QACF,CACA,GAAI,EAAAA,EAAK,WAAW,QAAQ,GAAKA,EAAK,WAAW,KAAK,GAAKA,EAAK,WAAW,KAAK,GAGhF,IAAIA,EAAK,WAAW,IAAI,EAAG,CACzB,MAAMC,EAAIR,EAAe,KAAKO,CAAI,EAClC,GAAIC,EAAG,CACL,MAAMC,EAAWD,EAAE,CAAC,EACdE,EAAWF,EAAE,CAAC,EACpBH,GAAWI,EAAW,SAASA,EAAU,EAAE,EAAI,GAAK,EACpDH,GAAWI,EAAW,SAASA,EAAU,EAAE,EAAI,GAAK,CACtD,CACAN,EAAK,KAAK,CAAE,KAAM,cAAe,KAAMG,EAAM,EAC7C,QACF,CACIA,EAAK,WAAW,GAAG,GACrBD,IACAF,EAAK,KAAK,CAAE,KAAM,MAAO,KAAMG,EAAM,WAAYD,EAAS,GACjDC,EAAK,WAAW,GAAG,GAC5BF,IACAD,EAAK,KAAK,CAAE,KAAM,MAAO,KAAMG,EAAM,WAAYF,EAAS,IAE1DA,IACAC,IACAF,EAAK,KAAK,CAAE,KAAM,MAAO,KAAMG,EAAM,WAAYD,EAAS,GAE9D,CAEA,OAAOF,CACT,CAMO,SAASO,EAAS,CAAE,MAAAT,GAAwB,CACjD,MAAME,EAAOH,EAAUC,CAAK,EAE5B,OACEU,EAAAA,IAAC,MAAA,CAAI,UAAWC,EAAO,SACpB,SAAAT,EAAK,IAAI,CAACU,EAAKC,IACdD,EAAI,OAAS,cACXF,EAAAA,IAAC,MAAA,CAAY,UAAWC,EAAO,WAC5B,SAAAC,EAAI,MADGC,CAEV,EAEAC,EAAAA,KAAC,MAAA,CAAY,UAAWC,EAAKJ,EAAO,KAAMA,EAAOC,EAAI,IAAI,CAAC,EACxD,SAAA,CAAAF,MAAC,QAAK,UAAWC,EAAO,GAAK,SAAAC,EAAI,YAAc,GAAG,EACjDA,EAAI,IAAA,CAAA,EAFGC,CAGV,CAAA,EAGN,CAEJ"}
@@ -0,0 +1 @@
1
+ ._diffView_1s170_1{font-size:12px;line-height:1.6;font-family:var(--pi-font-mono, ui-monospace, monospace);overflow-x:auto}._hunkHeader_1s170_7{background:var(--pi-color-surfaceSunken, #111);color:var(--pi-color-accentBright, #a2b4d7);padding:4px 14px;font-size:11px;border-bottom:1px solid var(--pi-color-border, #353b46)}._line_1s170_14{padding:0 14px;white-space:pre-wrap;word-break:break-word}._add_1s170_19{background:color-mix(in srgb,var(--pi-color-diffAddition, #4ade80) 15%,transparent);color:var(--pi-color-diffAddition, #4ade80)}._del_1s170_23{background:color-mix(in srgb,var(--pi-color-diffDeletion, #ff8a80) 15%,transparent);color:var(--pi-color-diffDeletion, #ff8a80)}._ctx_1s170_27{color:var(--pi-color-foregroundMuted, #9aa0a6)}._ln_1s170_30{display:inline-block;width:50px;text-align:right;padding-right:10px;color:var(--pi-color-foregroundMuted, #9aa0a6);-webkit-user-select:none;user-select:none;font-size:11px}
@@ -0,0 +1 @@
1
+ ._panel_i61bw_1{display:flex;flex-direction:column;height:100%;min-height:0}._header_i61bw_7{display:flex;align-items:center;gap:8px;padding:8px 14px;background:var(--pi-color-surface, #16181a);border-bottom:1px solid var(--pi-color-border, #353b46);font-size:12px;flex-shrink:0}._path_i61bw_17{color:var(--pi-color-accentBright, #a2b4d7);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}._size_i61bw_24{color:var(--pi-color-foregroundMuted, #9aa0a6);font-size:11px;flex-shrink:0}._viewToggle_i61bw_29{display:flex;gap:2px;flex-shrink:0}._body_i61bw_34{flex:1;overflow:auto;min-height:0}._emptyState_i61bw_39{display:flex;align-items:center;justify-content:center;gap:8px;color:var(--pi-color-foregroundMuted, #9aa0a6);font-size:12px;padding:20px}._diffEmpty_i61bw_48{padding:30px;text-align:center;color:var(--pi-color-foregroundMuted, #9aa0a6);font-size:12px}
@@ -0,0 +1,3 @@
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/TextViewer-DWYO-J8z.js","assets/vendor-react-BoIQBqDW.js","assets/index-DnTTpAX5.js","assets/vendor-query-CAh-Aqig.js","assets/vendor-BFXtRv5b.js","assets/vendor-markdown-CpaoCv-a.js","assets/vendor-highlight-DaTzDdJW.js","assets/vendor-icons-DUloQbkI.js","assets/vendor-overlays-Bo99M0pM.js","assets/vendor-dnd-D9kA7FgH.js","assets/index-DrGTLQo0.css","assets/CodeView-BJb8T6q4.js","assets/CodeView-CLRODhB8.css","assets/DiffView-B4H-yHuX.js","assets/DiffView-CRbeRztO.css","assets/MarkdownFileViewer-CDHJBxdH.js","assets/markdown-Biwdm3dP.js","assets/markdown-BQwXm0MF.css","assets/MarkdownFileViewer-BD5Ri_Fb.css","assets/ImageViewer-Cnr0mhRu.js","assets/use-file-download-BpyWNVxA.js","assets/ImageViewer-ijzMsq8H.css","assets/VideoViewer-D2ZnEQoA.js","assets/VideoViewer-BBuZ4wou.css","assets/BinaryFallbackViewer-BVnOJSh_.js","assets/BinaryFallbackViewer-CLiNWU_S.css"])))=>i.map(i=>d[i]);
2
+ import{r as d,j as i}from"./vendor-react-BoIQBqDW.js";import{C as h}from"./vendor-BFXtRv5b.js";import{a as b,r as v,B as _,S as p}from"./index-DnTTpAX5.js";import{u as x}from"./vendor-query-CAh-Aqig.js";import{D as E}from"./DiffView-B4H-yHuX.js";import{_ as f}from"./vendor-highlight-DaTzDdJW.js";function V(e,a,t,r=!0){const o=b(n=>n.client);return x({queryKey:v.fileDiff(e,a,t),queryFn:async()=>{if(!o)throw new Error("not connected");return{patch:(await o.connection.request("file_diff_request",{path:e,cwd:a,staged:t})).patch??""}},enabled:!!o&&!!e&&!!a&&r})}const j=d.lazy(()=>f(()=>import("./TextViewer-DWYO-J8z.js"),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14])).then(e=>({default:e.TextViewer}))),T=d.lazy(()=>f(()=>import("./MarkdownFileViewer-CDHJBxdH.js"),__vite__mapDeps([15,1,2,3,4,5,6,7,8,9,10,11,12,16,17,13,14,18])).then(e=>({default:e.MarkdownFileViewer}))),k=d.lazy(()=>f(()=>import("./ImageViewer-Cnr0mhRu.js"),__vite__mapDeps([19,1,2,3,4,5,6,7,8,9,10,20,13,14,21])).then(e=>({default:e.ImageViewer}))),z=d.lazy(()=>f(()=>import("./VideoViewer-D2ZnEQoA.js"),__vite__mapDeps([22,1,2,3,4,5,6,7,8,9,10,20,13,14,23])).then(e=>({default:e.VideoViewer}))),D=d.lazy(()=>f(()=>import("./BinaryFallbackViewer-BVnOJSh_.js"),__vite__mapDeps([24,1,2,3,4,5,6,7,8,9,10,20,25])).then(e=>({default:e.BinaryFallbackViewer}))),I={text:j,markdown:T,image:k,video:z,binary:D},S={md:"markdown",markdown:"markdown",mdx:"markdown",png:"image",jpg:"image",jpeg:"image",gif:"image",webp:"image",svg:"image",bmp:"image",ico:"image",avif:"image",mp4:"video",webm:"video",mov:"video",m4v:"video",ogv:"video",pdf:"binary",zip:"binary",gz:"binary",tar:"binary",exe:"binary",wasm:"binary",woff:"binary",woff2:"binary",ttf:"binary",otf:"binary"},N=[["image/","image"],["video/","video"]];function F(e){const a=e.split("/").pop()??e,t=a.lastIndexOf(".");return t>0?a.slice(t+1).toLowerCase():""}function O(e,a={}){const t=S[F(e)];if(t)return t;const r=a.mimeHint??"";for(const[o,n]of N)if(r.startsWith(o))return n;return a.isBinary?"binary":"text"}const P="_panel_i61bw_1",R="_header_i61bw_7",B="_path_i61bw_17",L="_size_i61bw_24",M="_viewToggle_i61bw_29",A="_body_i61bw_34",C="_emptyState_i61bw_39",K="_diffEmpty_i61bw_48",s={panel:P,header:R,path:B,size:L,viewToggle:M,body:A,emptyState:C,diffEmpty:K};function u(e){return"path"in e}function q(e){return"staged"in e}function W({tab:e}){var m;const a=e.kind==="diff",t=e.data,r=u(t)?t.path:"",o=u(t)&&q(t)?t.staged:!1,[n,l]=d.useState(a?"diff":"file"),w=e.workspaceCwd||"~",c=V(r,w,o,n==="diff"),y=O(r),g=I[y];return i.jsxs("div",{className:s.panel,children:[i.jsxs("div",{className:s.header,children:[i.jsx("span",{className:s.path,children:r}),i.jsx("span",{className:s.size,children:n==="diff"?o?"staged":"unstaged":null}),i.jsxs("div",{className:s.viewToggle,children:[i.jsx(_,{size:"xs",variant:n==="file"?"default":"ghost",onClick:()=>l("file"),children:"File"}),i.jsx(_,{size:"xs",variant:n==="diff"?"default":"ghost",onClick:()=>l("diff"),children:"Diff"})]})]}),i.jsx("div",{className:h(s.body,"file-body"),children:n==="file"?i.jsx(d.Suspense,{fallback:i.jsxs("div",{className:s.emptyState,children:[i.jsx(p,{size:"sm"})," Loading..."]}),children:i.jsx(g,{path:r})}):c.isLoading?i.jsxs("div",{className:s.emptyState,children:[i.jsx(p,{size:"sm"})," Loading diff..."]}):c.isError?i.jsxs("div",{className:s.emptyState,children:["Error: ",c.error instanceof Error?c.error.message:"unknown error"]}):(m=c.data)!=null&&m.patch?i.jsx(E,{patch:c.data.patch}):i.jsx("div",{className:s.diffEmpty,children:"No changes (file matches HEAD)"})})]})}const J=Object.freeze(Object.defineProperty({__proto__:null,FilePanel:W},Symbol.toStringTag,{value:"Module"}));export{J as F,s as p};
3
+ //# sourceMappingURL=FilePanel-lERDJldp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"mappings":"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","names":["useFileDiff","path","cwd","staged","enabled","client","useConnectionStore","s","useQuery","rpcKeys","TextViewer","lazy","__vitePreload","m","MarkdownFileViewer","ImageViewer","VideoViewer","BinaryFallbackViewer","VIEWER_BY_KIND","EXT_TO_VIEWER","MIME_PREFIX_TO_VIEWER","extOf","name","dot","detectViewerKind","opts","byExt","mime","prefix","kind","hasPath","data","isDiffTabData","FilePanel","tab","isDiffTab","viewMode","setViewMode","useState","diffQuery","viewerKind","Viewer","jsxs","styles","jsx","Button","clsx","Suspense","Spinner","_a","DiffView"],"ignoreList":[],"sources":["../../../src/hooks/use-file-diff.ts","../../../src/features/files/viewer-registry.ts","../../../src/features/files/FilePanel.tsx"],"sourcesContent":["/**\n * `use-file-diff` — TanStack Query wrapper over `file_diff_request` (POC `loadDiffContent`,\n * POC_TO_APP_PLAN_UI.md §4.5 / §3 `hooks/use-file-diff.ts`).\n */\n\nimport { useQuery } from \"@tanstack/react-query\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { rpcKeys } from \"@pi-studio-ui/lib/connection/rpc-keys.js\";\n\nexport interface FileDiffResult {\n patch: string;\n}\n\ninterface FileDiffRpcResponse {\n patch?: string;\n}\n\nexport function useFileDiff(path: string, cwd: string, staged: boolean, enabled = true) {\n const client = useConnectionStore((s) => s.client);\n\n return useQuery({\n queryKey: rpcKeys.fileDiff(path, cwd, staged),\n queryFn: async (): Promise<FileDiffResult> => {\n if (!client) throw new Error(\"not connected\");\n const response = await client.connection.request<FileDiffRpcResponse>(\"file_diff_request\", {\n path,\n cwd,\n staged,\n });\n return { patch: response.patch ?? \"\" };\n },\n enabled: Boolean(client) && Boolean(path) && Boolean(cwd) && enabled,\n });\n}\n","/**\n * File-viewer registry — maps a detected file kind to the viewer component that renders it\n * (POC_TO_APP_PLAN_UI.md §4.5 follow-up: modular file preview). `FilePanel` detects the kind from\n * `path` (extension) and the explorer's binary `mimeHint` (for extension-less/ambiguous files),\n * then looks up the viewer here — adding a new file type is one entry in `EXT_TO_VIEWER`/\n * `MIME_PREFIX_TO_VIEWER` plus one entry in `VIEWER_BY_KIND`, no changes to `FilePanel` itself.\n */\n\nimport { lazy, type ComponentType } from \"react\";\n\nexport type ViewerKind = \"text\" | \"markdown\" | \"image\" | \"video\" | \"binary\";\n\nexport interface ViewerProps {\n /** Explorer-relative or absolute path, as opened from the file tree/diff panel. */\n path: string;\n}\n\nconst TextViewer = lazy(() => import(\"./TextViewer.js\").then((m) => ({ default: m.TextViewer })));\nconst MarkdownFileViewer = lazy(() =>\n import(\"./MarkdownFileViewer.js\").then((m) => ({ default: m.MarkdownFileViewer })),\n);\nconst ImageViewer = lazy(() => import(\"./ImageViewer.js\").then((m) => ({ default: m.ImageViewer })));\nconst VideoViewer = lazy(() => import(\"./VideoViewer.js\").then((m) => ({ default: m.VideoViewer })));\nconst BinaryFallbackViewer = lazy(() =>\n import(\"./BinaryFallbackViewer.js\").then((m) => ({ default: m.BinaryFallbackViewer })),\n);\n\n/** One component per `ViewerKind`. Register a new file type here — nowhere else. */\nexport const VIEWER_BY_KIND: Record<ViewerKind, ComponentType<ViewerProps>> = {\n text: TextViewer,\n markdown: MarkdownFileViewer,\n image: ImageViewer,\n video: VideoViewer,\n binary: BinaryFallbackViewer,\n};\n\nconst EXT_TO_VIEWER: Record<string, ViewerKind> = {\n md: \"markdown\",\n markdown: \"markdown\",\n mdx: \"markdown\",\n\n png: \"image\",\n jpg: \"image\",\n jpeg: \"image\",\n gif: \"image\",\n webp: \"image\",\n svg: \"image\",\n bmp: \"image\",\n ico: \"image\",\n avif: \"image\",\n\n mp4: \"video\",\n webm: \"video\",\n mov: \"video\",\n m4v: \"video\",\n ogv: \"video\",\n\n // No dedicated preview yet — show the binary fallback (name + manual download) instead of\n // dumping raw/garbled bytes into the text viewer.\n pdf: \"binary\",\n zip: \"binary\",\n gz: \"binary\",\n tar: \"binary\",\n exe: \"binary\",\n wasm: \"binary\",\n woff: \"binary\",\n woff2: \"binary\",\n ttf: \"binary\",\n otf: \"binary\",\n};\n\nconst MIME_PREFIX_TO_VIEWER: Array<[prefix: string, kind: ViewerKind]> = [\n [\"image/\", \"image\"],\n [\"video/\", \"video\"],\n];\n\nfunction extOf(path: string): string {\n const name = path.split(\"/\").pop() ?? path;\n const dot = name.lastIndexOf(\".\");\n return dot > 0 ? name.slice(dot + 1).toLowerCase() : \"\";\n}\n\n/**\n * Detects the viewer kind for a file, preferring the extension (fast, always available) and\n * falling back to a server-supplied MIME hint (extension-less files, e.g. binary blobs the\n * explorer sniffed) — `isBinary` further distinguishes an unrecognized binary from unrecognized\n * text, so unknown-but-textual files (`.env`, `.gitignore`) still fall through to `TextViewer`.\n */\nexport function detectViewerKind(\n path: string,\n opts: { mimeHint?: string; isBinary?: boolean } = {},\n): ViewerKind {\n const byExt = EXT_TO_VIEWER[extOf(path)];\n if (byExt) return byExt;\n\n const mime = opts.mimeHint ?? \"\";\n for (const [prefix, kind] of MIME_PREFIX_TO_VIEWER) {\n if (mime.startsWith(prefix)) return kind;\n }\n\n if (opts.isBinary) return \"binary\";\n return \"text\";\n}\n","/**\n * FilePanel — the file/diff view toggle panel (POC `buildFilePanel`/`buildDiffPanel` +\n * `loadFileContent`/`loadDiffContent`, chat.html ~line 800-900, POC_TO_APP_PLAN_UI.md §4.5).\n * `panel-registry.ts` mounts this component for BOTH `kind:\"file\"` and `kind:\"diff\"` tabs — a\n * `kind:\"file\"` tab defaults to the File view, a `kind:\"diff\"` tab defaults to the Diff view.\n * Both views stay toggleable within the same panel via local `viewMode` state (not tab-store\n * state), matching the POC's per-panel `showFile`/`showDiff` toggle.\n *\n * The File view dispatches through the modular viewer registry (`viewer-registry.ts`):\n * `detectViewerKind` picks a viewer from the path's extension, and `VIEWER_BY_KIND` resolves the\n * (lazily-loaded) component — text/markdown/image/video today, more by adding one registry entry.\n */\n\nimport { Suspense, useState } from \"react\";\nimport { clsx } from \"clsx\";\nimport { Button } from \"@pi-studio-ui/components/primitives/Button.js\";\nimport { Spinner } from \"@pi-studio-ui/components/primitives/Spinner.js\";\nimport { useFileDiff } from \"@pi-studio-ui/hooks/use-file-diff.js\";\nimport type { Tab, FileTabData, DiffTabData } from \"@pi-studio-ui/stores/tab-store.js\";\nimport { DiffView } from \"./DiffView.js\";\nimport { detectViewerKind, VIEWER_BY_KIND } from \"./viewer-registry.js\";\nimport styles from \"./FilePanel.module.css\";\n\ntype ViewMode = \"file\" | \"diff\";\n\nfunction hasPath(data: Tab[\"data\"]): data is FileTabData | DiffTabData {\n return \"path\" in data;\n}\n\nfunction isDiffTabData(data: FileTabData | DiffTabData): data is DiffTabData {\n return \"staged\" in data;\n}\n\nexport interface FilePanelProps {\n tab: Tab;\n}\n\nexport function FilePanel({ tab }: FilePanelProps) {\n const isDiffTab = tab.kind === \"diff\";\n const data = tab.data;\n const path = hasPath(data) ? data.path : \"\";\n const staged = hasPath(data) && isDiffTabData(data) ? data.staged : false;\n\n const [viewMode, setViewMode] = useState<ViewMode>(isDiffTab ? \"diff\" : \"file\");\n const cwd = tab.workspaceCwd || \"~\";\n\n const diffQuery = useFileDiff(path, cwd, staged, viewMode === \"diff\");\n\n const viewerKind = detectViewerKind(path);\n const Viewer = VIEWER_BY_KIND[viewerKind];\n\n return (\n <div className={styles.panel}>\n <div className={styles.header}>\n <span className={styles.path}>{path}</span>\n <span className={styles.size}>{viewMode === \"diff\" ? (staged ? \"staged\" : \"unstaged\") : null}</span>\n <div className={styles.viewToggle}>\n <Button\n size=\"xs\"\n variant={viewMode === \"file\" ? \"default\" : \"ghost\"}\n onClick={() => setViewMode(\"file\")}\n >\n File\n </Button>\n <Button\n size=\"xs\"\n variant={viewMode === \"diff\" ? \"default\" : \"ghost\"}\n onClick={() => setViewMode(\"diff\")}\n >\n Diff\n </Button>\n </div>\n </div>\n <div className={clsx(styles.body, \"file-body\")}>\n {viewMode === \"file\" ? (\n <Suspense fallback={<div className={styles.emptyState}><Spinner size=\"sm\" /> Loading...</div>}>\n <Viewer path={path} />\n </Suspense>\n ) : diffQuery.isLoading ? (\n <div className={styles.emptyState}>\n <Spinner size=\"sm\" /> Loading diff...\n </div>\n ) : diffQuery.isError ? (\n <div className={styles.emptyState}>\n Error: {diffQuery.error instanceof Error ? diffQuery.error.message : \"unknown error\"}\n </div>\n ) : diffQuery.data?.patch ? (\n <DiffView patch={diffQuery.data.patch} />\n ) : (\n <div className={styles.diffEmpty}>No changes (file matches HEAD)</div>\n )}\n </div>\n </div>\n );\n}\n"],"file":"assets/FilePanel-lERDJldp.js"}
@@ -0,0 +1,2 @@
1
+ import{j as e}from"./vendor-react-BoIQBqDW.js";import{S as t}from"./index-DnTTpAX5.js";import{u as a}from"./use-file-download-BpyWNVxA.js";import{p as s}from"./FilePanel-lERDJldp.js";import"./vendor-query-CAh-Aqig.js";import"./vendor-BFXtRv5b.js";import"./vendor-markdown-CpaoCv-a.js";import"./vendor-highlight-DaTzDdJW.js";import"./vendor-icons-DUloQbkI.js";import"./vendor-overlays-Bo99M0pM.js";import"./vendor-dnd-D9kA7FgH.js";import"./DiffView-B4H-yHuX.js";const m="_wrap_1al28_1",n="_image_1al28_14",o={wrap:m,image:n};function S({path:i}){const r=a(i);return r.isLoading?e.jsxs("div",{className:s.emptyState,children:[e.jsx(t,{size:"sm"})," Loading image..."]}):r.isError?e.jsxs("div",{className:s.emptyState,children:["Error: ",r.error instanceof Error?r.error.message:"unknown error"]}):r.data?e.jsx("div",{className:o.wrap,children:e.jsx("img",{className:o.image,src:r.data.objectUrl,alt:i.split("/").pop()||i})}):null}export{S as ImageViewer};
2
+ //# sourceMappingURL=ImageViewer-Cnr0mhRu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageViewer-Cnr0mhRu.js","sources":["../../../src/features/files/ImageViewer.tsx"],"sourcesContent":["/**\n * ImageViewer — renders a binary image file by downloading it over the file-transfer binary\n * frames (`useFileDownload`) and displaying the resulting object URL (POC_TO_APP_PLAN_UI.md §4.5\n * follow-up: modular file preview). A checkerboard background shows through transparent PNGs.\n */\n\nimport { Spinner } from \"@pi-studio-ui/components/primitives/Spinner.js\";\nimport { useFileDownload } from \"@pi-studio-ui/hooks/use-file-download.js\";\nimport type { ViewerProps } from \"./viewer-registry.js\";\nimport panelStyles from \"./FilePanel.module.css\";\nimport styles from \"./ImageViewer.module.css\";\n\nexport function ImageViewer({ path }: ViewerProps) {\n const query = useFileDownload(path);\n\n if (query.isLoading) {\n return (\n <div className={panelStyles.emptyState}>\n <Spinner size=\"sm\" /> Loading image...\n </div>\n );\n }\n if (query.isError) {\n return (\n <div className={panelStyles.emptyState}>\n Error: {query.error instanceof Error ? query.error.message : \"unknown error\"}\n </div>\n );\n }\n if (!query.data) return null;\n\n return (\n <div className={styles.wrap}>\n <img className={styles.image} src={query.data.objectUrl} alt={path.split(\"/\").pop() || path} />\n </div>\n );\n}\n"],"names":["ImageViewer","path","query","useFileDownload","jsxs","panelStyles","jsx","Spinner","styles"],"mappings":"4gBAYO,SAASA,EAAY,CAAE,KAAAC,GAAqB,CACjD,MAAMC,EAAQC,EAAgBF,CAAI,EAElC,OAAIC,EAAM,UAENE,EAAAA,KAAC,MAAA,CAAI,UAAWC,EAAY,WAC1B,SAAA,CAAAC,EAAAA,IAACC,EAAA,CAAQ,KAAK,IAAA,CAAK,EAAE,mBAAA,EACvB,EAGAL,EAAM,QAENE,EAAAA,KAAC,MAAA,CAAI,UAAWC,EAAY,WAAY,SAAA,CAAA,UAC9BH,EAAM,iBAAiB,MAAQA,EAAM,MAAM,QAAU,eAAA,EAC/D,EAGCA,EAAM,KAGTI,MAAC,OAAI,UAAWE,EAAO,KACrB,SAAAF,MAAC,MAAA,CAAI,UAAWE,EAAO,MAAO,IAAKN,EAAM,KAAK,UAAW,IAAKD,EAAK,MAAM,GAAG,EAAE,IAAA,GAASA,CAAA,CAAM,CAAA,CAC/F,EALsB,IAO1B"}
@@ -0,0 +1 @@
1
+ ._wrap_1al28_1{display:flex;align-items:center;justify-content:center;height:100%;min-height:0;overflow:auto;padding:20px;background:repeating-conic-gradient(var(--pi-color-surfaceSunken, #111) 0% 25%,var(--pi-color-surface, #16181a) 0% 50%) 0 0 / 20px 20px}._image_1al28_14{max-width:100%;max-height:100%;object-fit:contain}
@@ -0,0 +1 @@
1
+ ._wrap_khhx0_1{height:100%;min-height:0;overflow:auto}._toolbar_khhx0_6{position:sticky;top:0;z-index:1;display:flex;justify-content:flex-end;gap:2px;padding:6px 10px;background:var(--pi-color-surface, #16181a);border-bottom:1px solid var(--pi-color-border, #353b46)}._rendered_khhx0_17{padding:16px 20px;max-width:860px}
@@ -0,0 +1,2 @@
1
+ import{r as d,j as r}from"./vendor-react-BoIQBqDW.js";import{S as c,B as a}from"./index-DnTTpAX5.js";import{u as m,C as p}from"./CodeView-BJb8T6q4.js";import{M as l}from"./markdown-Biwdm3dP.js";import{p as n}from"./FilePanel-lERDJldp.js";import"./vendor-query-CAh-Aqig.js";import"./vendor-BFXtRv5b.js";import"./vendor-markdown-CpaoCv-a.js";import"./vendor-highlight-DaTzDdJW.js";import"./vendor-icons-DUloQbkI.js";import"./vendor-overlays-Bo99M0pM.js";import"./vendor-dnd-D9kA7FgH.js";import"./DiffView-B4H-yHuX.js";const x="_wrap_khhx0_1",u="_toolbar_khhx0_6",h="_rendered_khhx0_17",o={wrap:x,toolbar:u,rendered:h};function M({path:s}){const e=m(s),[t,i]=d.useState("preview");return e.isLoading?r.jsxs("div",{className:n.emptyState,children:[r.jsx(c,{size:"sm"})," Loading..."]}):e.isError?r.jsxs("div",{className:n.emptyState,children:["Error: ",e.error instanceof Error?e.error.message:"unknown error"]}):e.data?r.jsxs("div",{className:o.wrap,children:[r.jsxs("div",{className:o.toolbar,children:[r.jsx(a,{size:"xs",variant:t==="preview"?"default":"ghost",onClick:()=>i("preview"),children:"Preview"}),r.jsx(a,{size:"xs",variant:t==="source"?"default":"ghost",onClick:()=>i("source"),children:"Source"})]}),t==="preview"?r.jsx("div",{className:o.rendered,children:r.jsx(l,{text:e.data.content})}):r.jsx(p,{path:s,content:e.data.content})]}):null}export{M as MarkdownFileViewer};
2
+ //# sourceMappingURL=MarkdownFileViewer-CDHJBxdH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MarkdownFileViewer-CDHJBxdH.js","sources":["../../../src/features/files/MarkdownFileViewer.tsx"],"sourcesContent":["/**\n * MarkdownFileViewer — renders a `.md`/`.mdx` file's content through the app's sanitized\n * Markdown renderer (`timeline/markdown.tsx`, react-markdown + remark-gfm — no\n * `dangerouslySetInnerHTML` of user content), with a Preview/Source toggle since raw markdown is\n * sometimes what you actually want to read (POC_TO_APP_PLAN_UI.md §4.5 follow-up: modular file\n * preview).\n */\n\nimport { useState } from \"react\";\nimport { Button } from \"@pi-studio-ui/components/primitives/Button.js\";\nimport { Spinner } from \"@pi-studio-ui/components/primitives/Spinner.js\";\nimport { useFileRead } from \"@pi-studio-ui/hooks/use-file-read.js\";\nimport { Markdown } from \"@pi-studio-ui/timeline/markdown.js\";\nimport { CodeView } from \"./CodeView.js\";\nimport type { ViewerProps } from \"./viewer-registry.js\";\nimport panelStyles from \"./FilePanel.module.css\";\nimport styles from \"./MarkdownFileViewer.module.css\";\n\ntype Mode = \"preview\" | \"source\";\n\nexport function MarkdownFileViewer({ path }: ViewerProps) {\n const query = useFileRead(path);\n const [mode, setMode] = useState<Mode>(\"preview\");\n\n if (query.isLoading) {\n return (\n <div className={panelStyles.emptyState}>\n <Spinner size=\"sm\" /> Loading...\n </div>\n );\n }\n if (query.isError) {\n return (\n <div className={panelStyles.emptyState}>\n Error: {query.error instanceof Error ? query.error.message : \"unknown error\"}\n </div>\n );\n }\n if (!query.data) return null;\n\n return (\n <div className={styles.wrap}>\n <div className={styles.toolbar}>\n <Button size=\"xs\" variant={mode === \"preview\" ? \"default\" : \"ghost\"} onClick={() => setMode(\"preview\")}>\n Preview\n </Button>\n <Button size=\"xs\" variant={mode === \"source\" ? \"default\" : \"ghost\"} onClick={() => setMode(\"source\")}>\n Source\n </Button>\n </div>\n {mode === \"preview\" ? (\n <div className={styles.rendered}>\n <Markdown text={query.data.content} />\n </div>\n ) : (\n <CodeView path={path} content={query.data.content} />\n )}\n </div>\n );\n}\n"],"names":["MarkdownFileViewer","path","query","useFileRead","mode","setMode","useState","jsxs","panelStyles","jsx","Spinner","styles","Button","Markdown","CodeView"],"mappings":"wmBAoBO,SAASA,EAAmB,CAAE,KAAAC,GAAqB,CACxD,MAAMC,EAAQC,EAAYF,CAAI,EACxB,CAACG,EAAMC,CAAO,EAAIC,EAAAA,SAAe,SAAS,EAEhD,OAAIJ,EAAM,UAENK,EAAAA,KAAC,MAAA,CAAI,UAAWC,EAAY,WAC1B,SAAA,CAAAC,EAAAA,IAACC,EAAA,CAAQ,KAAK,IAAA,CAAK,EAAE,aAAA,EACvB,EAGAR,EAAM,QAENK,EAAAA,KAAC,MAAA,CAAI,UAAWC,EAAY,WAAY,SAAA,CAAA,UAC9BN,EAAM,iBAAiB,MAAQA,EAAM,MAAM,QAAU,eAAA,EAC/D,EAGCA,EAAM,KAGTK,EAAAA,KAAC,MAAA,CAAI,UAAWI,EAAO,KACrB,SAAA,CAAAJ,EAAAA,KAAC,MAAA,CAAI,UAAWI,EAAO,QACrB,SAAA,CAAAF,EAAAA,IAACG,EAAA,CAAO,KAAK,KAAK,QAASR,IAAS,UAAY,UAAY,QAAS,QAAS,IAAMC,EAAQ,SAAS,EAAG,SAAA,UAExG,EACAI,EAAAA,IAACG,EAAA,CAAO,KAAK,KAAK,QAASR,IAAS,SAAW,UAAY,QAAS,QAAS,IAAMC,EAAQ,QAAQ,EAAG,SAAA,QAAA,CAEtG,CAAA,EACF,EACCD,IAAS,UACRK,MAAC,MAAA,CAAI,UAAWE,EAAO,SACrB,eAACE,EAAA,CAAS,KAAMX,EAAM,KAAK,OAAA,CAAS,EACtC,EAEAO,EAAAA,IAACK,GAAS,KAAAb,EAAY,QAASC,EAAM,KAAK,OAAA,CAAS,CAAA,EAEvD,EAnBsB,IAqB1B"}
@@ -0,0 +1 @@
1
+ ._wrap_b74ul_1{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;height:100%;width:100%;overflow:hidden;background:var(--pi-terminal-bg, #181b1a)}._terminal_b74ul_11{flex:1 1 auto;min-height:0;width:100%;overflow:hidden;padding:6px 8px}._status_b74ul_18{display:flex;align-items:center;justify-content:center;gap:8px;height:100%;color:var(--pi-color-foregroundMuted, #9aa0a6);font-size:13px}
@@ -0,0 +1,2 @@
1
+ var A=Object.defineProperty;var I=(n,e,t)=>e in n?A(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var g=(n,e,t)=>I(n,typeof e!="symbol"?e+"":e,t);import{r as a,j as l}from"./vendor-react-BoIQBqDW.js";import{x as B,a as F}from"./vendor-terminal-BhzX2_ts.js";import{e as E,a as N,b as T,S as D}from"./index-DnTTpAX5.js";import"./vendor-BFXtRv5b.js";import"./vendor-query-CAh-Aqig.js";import"./vendor-icons-DUloQbkI.js";import"./vendor-overlays-Bo99M0pM.js";import"./vendor-dnd-D9kA7FgH.js";import"./vendor-highlight-DaTzDdJW.js";import"./vendor-markdown-CpaoCv-a.js";class O{constructor(e){g(this,"subscribers",new Map);g(this,"detach",null);this.daemon=e}start(){this.detach||(this.detach=this.daemon.onTerminalFrame(e=>this.dispatch(e)))}stop(){var e;(e=this.detach)==null||e.call(this),this.detach=null}subscribeSlot(e,t){return this.subscribers.set(e,t),()=>{this.subscribers.get(e)===t&&this.subscribers.delete(e)}}hasSlot(e){return this.subscribers.has(e)}sendInput(e,t){this.daemon.sendBinary(E({opcode:"Input",slot:e,data:t}))}sendResize(e,t,f){this.daemon.sendBinary(E({opcode:"Resize",slot:e,rows:t,cols:f}))}dispatch(e){var f,p,u;const t=this.subscribers.get(e.slot);if(t)switch(e.opcode){case"Output":(f=t.onOutput)==null||f.call(t,e.data);return;case"Snapshot":(p=t.onSnapshot)==null||p.call(t,e.data);return;case"Restore":(u=t.onRestore)==null||u.call(t,e.data);return;default:return}}}const C="_wrap_b74ul_1",P="_terminal_b74ul_11",W="_status_b74ul_18",d={wrap:C,terminal:P,status:W},q=new WeakMap;function G(n){let e=q.get(n);return e||(e=new O(n),e.start(),q.set(n,e)),e}const H={background:"#181b1a",foreground:"#fafafa",cursor:"#a2b4d7",cursorAccent:"#181b1a",selectionBackground:"rgba(255,255,255,0.18)",black:"#18181b",red:"#ef4444",green:"#22c55e",yellow:"#f59e0b",blue:"#3b82f6",magenta:"#a855f7",cyan:"#14b8a6",white:"#d4d4d8",brightBlack:"#52525b",brightRed:"#f87171",brightGreen:"#4ade80",brightYellow:"#fbbf24",brightBlue:"#60a5fa",brightMagenta:"#c084fc",brightCyan:"#2dd4bf",brightWhite:"#fafafa"};function te({tab:n}){const e=n.data,t=N(r=>r.client),p=T(r=>r.activeTabId)===n.id,u=a.useRef(null),w=a.useRef(null),h=a.useRef(null),b=a.useRef(e.slot),S=a.useRef(t);S.current=t;const[c,k]=a.useState(e.slot),[_,v]=a.useState(null),m=a.useRef(!1),R=a.useRef(!1);return a.useEffect(()=>{if(m.current=!0,!t||b.current!==null||R.current)return()=>{m.current=!1};R.current=!0;const r=e.cwd||"~";return t.connection.request("create_terminal_request",{workspaceId:"",cwd:r}).then(s=>{const i=s.terminal.slot;if(!m.current){t.connection.request("kill_terminal_request",{slot:i}).catch(()=>{});return}b.current=i,k(i),T.getState().updateData(n.id,{slot:i})}).catch(s=>{m.current&&v(s instanceof Error?s.message:String(s))}).finally(()=>{R.current=!1}),()=>{m.current=!1}},[t,n.id]),a.useEffect(()=>()=>{var s;const r=b.current;r!==null&&((s=S.current)==null||s.connection.request("kill_terminal_request",{slot:r}).catch(()=>{}))},[]),a.useEffect(()=>{if(!t||c===null||!u.current)return;const r=new B.Terminal({cursorBlink:!0,fontFamily:"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",fontSize:13,scrollback:5e3,theme:H,allowProposedApi:!0}),s=new F.FitAddon;r.loadAddon(s),r.open(u.current),s.fit(),w.current=r,h.current=s;const i=G(t.connection),j=i.subscribeSlot(c,{onOutput:o=>r.write(o),onSnapshot:o=>{r.clear(),r.write(o)},onRestore:o=>r.write(o)});t.connection.request("subscribe_terminal_request",{slot:c}).catch(o=>{v(o instanceof Error?o.message:String(o))});const y=r.onData(o=>{i.sendInput(c,new TextEncoder().encode(o))}),z=r.onResize(({cols:o,rows:M})=>{i.sendResize(c,M,o)}),x=new ResizeObserver(()=>{s.fit()});return x.observe(u.current),()=>{x.disconnect(),y.dispose(),z.dispose(),j(),t.connection.request("unsubscribe_terminal_request",{slot:c}).catch(()=>{}),r.dispose(),w.current=null,h.current=null}},[t,c]),a.useEffect(()=>{var r;p&&((r=h.current)==null||r.fit())},[p]),_?l.jsx("div",{className:d.wrap,children:l.jsxs("div",{className:d.status,children:["Terminal error: ",_]})}):c===null?l.jsx("div",{className:d.wrap,children:l.jsxs("div",{className:d.status,children:[l.jsx(D,{size:"sm"})," Starting terminal…"]})}):l.jsx("div",{className:d.wrap,children:l.jsx("div",{ref:u,className:d.terminal})})}export{te as TerminalPanel};
2
+ //# sourceMappingURL=TerminalPanel-cnkK_YF5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TerminalPanel-cnkK_YF5.js","sources":["../../../../client/src/terminal-stream-router.ts","../../../src/features/terminal/TerminalPanel.tsx"],"sourcesContent":["import { encodeTerminalFrame, type TerminalFrame } from \"@av-pi-studio/protocol\";\n\nimport type { DaemonClient } from \"./daemon-client.js\";\n\n/**\n * Client-side demux of binary terminal frames to per-slot subscribers, plus outbound input/resize\n * encoding (architecture/client-app-runtime.md § Router; features/terminals.md § Binary stream\n * protocol).\n *\n * Inbound `Output`/`Snapshot`/`Restore` frames are dispatched to the subscriber registered for that\n * `slot`. Outbound `Input`/`Resize` are encoded with the right opcode + slot and sent on the data\n * path.\n */\n\nexport interface TerminalSlotSubscriber {\n /** Live terminal output bytes. */\n onOutput?: (data: Uint8Array) => void;\n /** Full-screen snapshot bytes (sent on (re)subscribe). */\n onSnapshot?: (data: Uint8Array) => void;\n /** Restore snapshot bytes (reflowable/mode-gated). */\n onRestore?: (data: Uint8Array) => void;\n}\n\nexport class TerminalStreamRouter {\n private readonly subscribers = new Map<number, TerminalSlotSubscriber>();\n private detach: (() => void) | null = null;\n\n constructor(private readonly daemon: DaemonClient) {}\n\n /** Begin routing inbound terminal frames. Idempotent. */\n start(): void {\n if (this.detach) return;\n this.detach = this.daemon.onTerminalFrame((frame) => this.dispatch(frame));\n }\n\n /** Stop routing inbound frames (subscribers retained). */\n stop(): void {\n this.detach?.();\n this.detach = null;\n }\n\n /** Register (or replace) the subscriber for a slot. Returns an unsubscribe fn. */\n subscribeSlot(slot: number, subscriber: TerminalSlotSubscriber): () => void {\n this.subscribers.set(slot, subscriber);\n return () => {\n if (this.subscribers.get(slot) === subscriber) this.subscribers.delete(slot);\n };\n }\n\n /** True iff a subscriber is registered for the slot. */\n hasSlot(slot: number): boolean {\n return this.subscribers.has(slot);\n }\n\n // ─── Outbound ─────────────────────────────────────────────────────────────\n\n /** Send raw input bytes to a slot's PTY (opcode `Input = 0x02`). */\n sendInput(slot: number, data: Uint8Array): void {\n this.daemon.sendBinary(encodeTerminalFrame({ opcode: \"Input\", slot, data }));\n }\n\n /** Send a resize (opcode `Resize = 0x03`, JSON `{ rows, cols }` payload). */\n sendResize(slot: number, rows: number, cols: number): void {\n this.daemon.sendBinary(encodeTerminalFrame({ opcode: \"Resize\", slot, rows, cols }));\n }\n\n // ─── Inbound dispatch ───────────────────────────────────────────────────────\n\n private dispatch(frame: TerminalFrame): void {\n const subscriber = this.subscribers.get(frame.slot);\n if (!subscriber) return; // no subscriber for this slot — drop\n switch (frame.opcode) {\n case \"Output\":\n subscriber.onOutput?.(frame.data);\n return;\n case \"Snapshot\":\n subscriber.onSnapshot?.(frame.data);\n return;\n case \"Restore\":\n subscriber.onRestore?.(frame.data);\n return;\n default:\n // Input/Resize are outbound-only; ignore if echoed back.\n return;\n }\n }\n}\n","/**\n * TerminalPanel — @xterm/xterm mount + binary-frame streaming via `TerminalStreamRouter`\n * (POC `initTerminalPanel`, POC_TO_APP_PLAN_UI.md §4.6). Strict upgrade over the POC's 800ms\n * `capture_terminal_request` poll: the daemon pushes `Output`/`Snapshot`/`Restore` binary frames\n * directly over the one shared `DaemonClient` connection, demuxed by slot.\n *\n * Slot lifecycle: created once via `create_terminal_request`, then persisted onto the tab's\n * `TerminalTabData.slot` via `useTabStore.getState().updateData` so switching away and back to\n * this tab (kept mounted-but-hidden by `TabPanelHost`) never recreates the terminal. `TabPanelHost`\n * only unmounts a tab's panel when the tab leaves the store's `tabs[]` (i.e. real tab close, never\n * a tab switch) — this component's true-unmount effect below relies on exactly that invariant to\n * send `kill_terminal_request`, terminating the PTY server-side instead of leaking it forever.\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { Terminal } from \"@xterm/xterm\";\nimport { FitAddon } from \"@xterm/addon-fit\";\nimport \"@xterm/xterm/css/xterm.css\";\nimport type { DaemonClient } from \"@av-pi-studio/client\";\nimport { TerminalStreamRouter } from \"@av-pi-studio/client\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { useTabStore } from \"@pi-studio-ui/stores/tab-store.js\";\nimport type { Tab, TerminalTabData } from \"@pi-studio-ui/stores/tab-store.js\";\nimport { Spinner } from \"@pi-studio-ui/components/primitives/Spinner.js\";\nimport styles from \"./TerminalPanel.module.css\";\n\nexport interface TerminalPanelProps {\n tab: Tab;\n}\n\ninterface CreateTerminalResponse {\n terminal: { slot: number };\n}\n\n// One TerminalStreamRouter per daemon connection — multiple terminal tabs share it rather than\n// each opening its own frame demuxer over the same socket.\nconst routerByDaemon = new WeakMap<DaemonClient, TerminalStreamRouter>();\n\nfunction routerFor(daemon: DaemonClient): TerminalStreamRouter {\n let router = routerByDaemon.get(daemon);\n if (!router) {\n router = new TerminalStreamRouter(daemon);\n router.start();\n routerByDaemon.set(daemon, router);\n }\n return router;\n}\n\n/** Dark palette matching the app's github-dark-ish default theme (theme/variants.ts \"dark\"). */\nconst TERMINAL_THEME = {\n background: \"#181b1a\",\n foreground: \"#fafafa\",\n cursor: \"#a2b4d7\",\n cursorAccent: \"#181b1a\",\n selectionBackground: \"rgba(255,255,255,0.18)\",\n black: \"#18181b\",\n red: \"#ef4444\",\n green: \"#22c55e\",\n yellow: \"#f59e0b\",\n blue: \"#3b82f6\",\n magenta: \"#a855f7\",\n cyan: \"#14b8a6\",\n white: \"#d4d4d8\",\n brightBlack: \"#52525b\",\n brightRed: \"#f87171\",\n brightGreen: \"#4ade80\",\n brightYellow: \"#fbbf24\",\n brightBlue: \"#60a5fa\",\n brightMagenta: \"#c084fc\",\n brightCyan: \"#2dd4bf\",\n brightWhite: \"#fafafa\",\n};\n\nexport function TerminalPanel({ tab }: TerminalPanelProps) {\n const data = tab.data as TerminalTabData;\n const client = useConnectionStore((s) => s.client);\n const activeTabId = useTabStore((s) => s.activeTabId);\n const isActive = activeTabId === tab.id;\n\n const containerRef = useRef<HTMLDivElement | null>(null);\n const terminalRef = useRef<Terminal | null>(null);\n const fitAddonRef = useRef<FitAddon | null>(null);\n const slotRef = useRef<number | null>(data.slot);\n // Mirrors `use-checkout-status.ts`'s convention: kept in sync every render so the unmount-only\n // kill effect below always sends the CURRENT client, never a stale mount-time closure (e.g.\n // after a reconnect swaps in a new `PiStudioClient` instance).\n const clientRef = useRef(client);\n clientRef.current = client;\n\n const [slot, setSlot] = useState<number | null>(data.slot);\n const [error, setError] = useState<string | null>(null);\n\n // ─── Slot lifecycle: create once, persist onto the tab so re-opening reuses it ─────────────\n // React StrictMode double-invokes effects in dev: mount → cleanup → remount, synchronously,\n // on the SAME component instance (refs/state persist across all three phases — this is not\n // three separate mounts). Two things must hold across that: (1) the request fires exactly\n // once, and (2) whether to APPLY the eventual response is decided by whether the component is\n // mounted at RESPONSE time, not by a flag captured at REQUEST time.\n //\n // `requestStartedRef` gives (1): set the instant the request fires and never reset, so the\n // phantom-mount's cleanup-then-remount sees it's already in flight and never fires a second\n // `create_terminal_request` (this is what previously spawned two real PTYs from one Ctrl+T).\n //\n // `isMountedRef` gives (2): flipped true at the START of every effect invocation and false in\n // every cleanup, so it always reflects the LATEST phase. StrictMode's remount happens\n // synchronously, before the request's promise can possibly settle, so by response time\n // `isMountedRef.current` is back to `true` for a StrictMode phantom (correctly applies the\n // slot) — but stays `false` for a genuine fast real close (correctly kills the orphaned PTY\n // instead of leaking it or, as the previous buggy version did, killing a terminal that was\n // never actually torn down).\n const isMountedRef = useRef(false);\n const requestStartedRef = useRef(false);\n useEffect(() => {\n isMountedRef.current = true;\n if (!client || slotRef.current !== null || requestStartedRef.current) {\n return () => {\n isMountedRef.current = false;\n };\n }\n requestStartedRef.current = true;\n\n const cwd = data.cwd || \"~\";\n\n void client.connection\n .request<CreateTerminalResponse>(\"create_terminal_request\", { workspaceId: \"\", cwd })\n .then((res) => {\n const created = res.terminal.slot;\n if (!isMountedRef.current) {\n // A real close happened with no remount after it — kill the PTY that finished\n // spawning after the tab was already gone, instead of leaking it.\n void client.connection.request(\"kill_terminal_request\", { slot: created }).catch(() => {});\n return;\n }\n slotRef.current = created;\n setSlot(created);\n useTabStore.getState().updateData(tab.id, { slot: created });\n })\n .catch((err: unknown) => {\n if (!isMountedRef.current) return;\n setError(err instanceof Error ? err.message : String(err));\n })\n .finally(() => {\n // Reset only after the promise settles — by then StrictMode's synchronous\n // mount→cleanup→remount window has long passed, so this can never reopen the\n // double-fire race. It DOES allow a legitimate retry (e.g. `client` changed because of\n // a reconnect after the first attempt failed) instead of leaving the tab stuck forever.\n requestStartedRef.current = false;\n });\n\n return () => {\n isMountedRef.current = false;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [client, tab.id]);\n\n // ─── True unmount only: kill the PTY server-side ───────────────────────────────────────────\n // Runs its cleanup exactly once, when this component itself unmounts (real tab close, per\n // `TabPanelHost`'s \"hidden but alive\" model — a tab switch never unmounts). An empty deps array\n // means React never re-runs the effect body itself; only the cleanup fires, and only on\n // unmount, so this never races the slot-creation effect above or double-kills on a client\n // change. Without this, every closed terminal tab leaked its PTY process forever.\n useEffect(() => {\n return () => {\n const currentSlot = slotRef.current;\n if (currentSlot === null) return;\n void clientRef.current?.connection\n .request(\"kill_terminal_request\", { slot: currentSlot })\n .catch(() => {});\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // ─── xterm mount (once slot + container are ready) ─────────────────────────────────────────\n useEffect(() => {\n if (!client || slot === null || !containerRef.current) return;\n\n const terminal = new Terminal({\n cursorBlink: true,\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',\n fontSize: 13,\n scrollback: 5000,\n theme: TERMINAL_THEME,\n allowProposedApi: true,\n });\n const fitAddon = new FitAddon();\n terminal.loadAddon(fitAddon);\n terminal.open(containerRef.current);\n fitAddon.fit();\n\n terminalRef.current = terminal;\n fitAddonRef.current = fitAddon;\n\n const router = routerFor(client.connection);\n const unsubscribeSlot = router.subscribeSlot(slot, {\n onOutput: (chunk) => terminal.write(chunk),\n onSnapshot: (chunk) => {\n terminal.clear();\n terminal.write(chunk);\n },\n onRestore: (chunk) => terminal.write(chunk),\n });\n\n void client.connection.request(\"subscribe_terminal_request\", { slot }).catch((err: unknown) => {\n setError(err instanceof Error ? err.message : String(err));\n });\n\n const dataDisposable = terminal.onData((chunk) => {\n router.sendInput(slot, new TextEncoder().encode(chunk));\n });\n const resizeDisposable = terminal.onResize(({ cols, rows }) => {\n router.sendResize(slot, rows, cols);\n });\n\n const resizeObserver = new ResizeObserver(() => {\n fitAddon.fit();\n });\n resizeObserver.observe(containerRef.current);\n\n return () => {\n resizeObserver.disconnect();\n dataDisposable.dispose();\n resizeDisposable.dispose();\n unsubscribeSlot();\n void client.connection.request(\"unsubscribe_terminal_request\", { slot }).catch(() => {});\n terminal.dispose();\n terminalRef.current = null;\n fitAddonRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [client, slot]);\n\n // ─── Re-fit whenever this tab becomes the active (visible) one ────────────────────────────\n useEffect(() => {\n if (!isActive) return;\n fitAddonRef.current?.fit();\n }, [isActive]);\n\n if (error) {\n return (\n <div className={styles.wrap}>\n <div className={styles.status}>Terminal error: {error}</div>\n </div>\n );\n }\n\n if (slot === null) {\n return (\n <div className={styles.wrap}>\n <div className={styles.status}>\n <Spinner size=\"sm\" /> Starting terminal…\n </div>\n </div>\n );\n }\n\n return (\n <div className={styles.wrap}>\n <div ref={containerRef} className={styles.terminal} />\n </div>\n );\n}\n"],"names":["TerminalStreamRouter","daemon","__publicField","frame","_a","slot","subscriber","data","encodeTerminalFrame","rows","cols","_b","_c","routerByDaemon","routerFor","router","TERMINAL_THEME","TerminalPanel","tab","client","useConnectionStore","s","isActive","useTabStore","containerRef","useRef","terminalRef","fitAddonRef","slotRef","clientRef","setSlot","useState","error","setError","isMountedRef","requestStartedRef","useEffect","cwd","res","created","err","currentSlot","terminal","Terminal","fitAddon","FitAddon","unsubscribeSlot","chunk","dataDisposable","resizeDisposable","resizeObserver","jsx","styles","jsxs","Spinner"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{j as e}from"./vendor-react-BoIQBqDW.js";import{S as i}from"./index-DnTTpAX5.js";import{u as s,C as n}from"./CodeView-BJb8T6q4.js";import{p as o}from"./FilePanel-lERDJldp.js";import"./vendor-query-CAh-Aqig.js";import"./vendor-BFXtRv5b.js";import"./vendor-markdown-CpaoCv-a.js";import"./vendor-highlight-DaTzDdJW.js";import"./vendor-icons-DUloQbkI.js";import"./vendor-overlays-Bo99M0pM.js";import"./vendor-dnd-D9kA7FgH.js";import"./DiffView-B4H-yHuX.js";function E({path:t}){const r=s(t);return r.isLoading?e.jsxs("div",{className:o.emptyState,children:[e.jsx(i,{size:"sm"})," Loading..."]}):r.isError?e.jsxs("div",{className:o.emptyState,children:["Error: ",r.error instanceof Error?r.error.message:"unknown error"]}):r.data?e.jsx(n,{path:t,content:r.data.content}):null}export{E as TextViewer};
2
+ //# sourceMappingURL=TextViewer-DWYO-J8z.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TextViewer-DWYO-J8z.js","sources":["../../../src/features/files/TextViewer.tsx"],"sourcesContent":["/**\n * TextViewer — the default viewer for source/text files: fetches the UTF-8 preview via\n * `useFileRead` and renders it through `CodeView` (line gutter + Shiki highlighting). Registered\n * in `viewer-registry.ts` as the fallback for any file not claimed by a more specific viewer.\n */\n\nimport { Spinner } from \"@pi-studio-ui/components/primitives/Spinner.js\";\nimport { useFileRead } from \"@pi-studio-ui/hooks/use-file-read.js\";\nimport { CodeView } from \"./CodeView.js\";\nimport type { ViewerProps } from \"./viewer-registry.js\";\nimport styles from \"./FilePanel.module.css\";\n\nexport function TextViewer({ path }: ViewerProps) {\n const query = useFileRead(path);\n\n if (query.isLoading) {\n return (\n <div className={styles.emptyState}>\n <Spinner size=\"sm\" /> Loading...\n </div>\n );\n }\n if (query.isError) {\n return (\n <div className={styles.emptyState}>\n Error: {query.error instanceof Error ? query.error.message : \"unknown error\"}\n </div>\n );\n }\n if (!query.data) return null;\n return <CodeView path={path} content={query.data.content} />;\n}\n"],"names":["TextViewer","path","query","useFileRead","jsxs","styles","jsx","Spinner","CodeView"],"mappings":"2cAYO,SAASA,EAAW,CAAE,KAAAC,GAAqB,CAChD,MAAMC,EAAQC,EAAYF,CAAI,EAE9B,OAAIC,EAAM,UAENE,EAAAA,KAAC,MAAA,CAAI,UAAWC,EAAO,WACrB,SAAA,CAAAC,EAAAA,IAACC,EAAA,CAAQ,KAAK,IAAA,CAAK,EAAE,aAAA,EACvB,EAGAL,EAAM,QAENE,EAAAA,KAAC,MAAA,CAAI,UAAWC,EAAO,WAAY,SAAA,CAAA,UACzBH,EAAM,iBAAiB,MAAQA,EAAM,MAAM,QAAU,eAAA,EAC/D,EAGCA,EAAM,WACHM,EAAA,CAAS,KAAAP,EAAY,QAASC,EAAM,KAAK,QAAS,EADlC,IAE1B"}
@@ -0,0 +1 @@
1
+ ._wrap_1c9y4_1{display:flex;align-items:center;justify-content:center;height:100%;min-height:0;padding:20px;background:var(--pi-color-surfaceSunken, #111)}._video_1c9y4_10{max-width:100%;max-height:100%}
@@ -0,0 +1,2 @@
1
+ import{j as o}from"./vendor-react-BoIQBqDW.js";import{S as t}from"./index-DnTTpAX5.js";import{u as a}from"./use-file-download-BpyWNVxA.js";import{p as e}from"./FilePanel-lERDJldp.js";import"./vendor-query-CAh-Aqig.js";import"./vendor-BFXtRv5b.js";import"./vendor-markdown-CpaoCv-a.js";import"./vendor-highlight-DaTzDdJW.js";import"./vendor-icons-DUloQbkI.js";import"./vendor-overlays-Bo99M0pM.js";import"./vendor-dnd-D9kA7FgH.js";import"./DiffView-B4H-yHuX.js";const n="_wrap_1c9y4_1",m="_video_1c9y4_10",i={wrap:n,video:m};function S({path:s}){const r=a(s);return r.isLoading?o.jsxs("div",{className:e.emptyState,children:[o.jsx(t,{size:"sm"})," Loading video..."]}):r.isError?o.jsxs("div",{className:e.emptyState,children:["Error: ",r.error instanceof Error?r.error.message:"unknown error"]}):r.data?o.jsx("div",{className:i.wrap,children:o.jsx("video",{className:i.video,src:r.data.objectUrl,controls:!0})}):null}export{S as VideoViewer};
2
+ //# sourceMappingURL=VideoViewer-D2ZnEQoA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VideoViewer-D2ZnEQoA.js","sources":["../../../src/features/files/VideoViewer.tsx"],"sourcesContent":["/**\n * VideoViewer — renders a binary video file via the file-transfer binary download\n * (`useFileDownload`) into a native `<video>` element (POC_TO_APP_PLAN_UI.md §4.5 follow-up:\n * modular file preview).\n */\n\nimport { Spinner } from \"@pi-studio-ui/components/primitives/Spinner.js\";\nimport { useFileDownload } from \"@pi-studio-ui/hooks/use-file-download.js\";\nimport type { ViewerProps } from \"./viewer-registry.js\";\nimport panelStyles from \"./FilePanel.module.css\";\nimport styles from \"./VideoViewer.module.css\";\n\nexport function VideoViewer({ path }: ViewerProps) {\n const query = useFileDownload(path);\n\n if (query.isLoading) {\n return (\n <div className={panelStyles.emptyState}>\n <Spinner size=\"sm\" /> Loading video...\n </div>\n );\n }\n if (query.isError) {\n return (\n <div className={panelStyles.emptyState}>\n Error: {query.error instanceof Error ? query.error.message : \"unknown error\"}\n </div>\n );\n }\n if (!query.data) return null;\n\n return (\n <div className={styles.wrap}>\n {/* eslint-disable-next-line jsx-a11y/media-has-caption -- arbitrary local file, no caption track available */}\n <video className={styles.video} src={query.data.objectUrl} controls />\n </div>\n );\n}\n"],"names":["VideoViewer","path","query","useFileDownload","jsxs","panelStyles","jsx","Spinner","styles"],"mappings":"4gBAYO,SAASA,EAAY,CAAE,KAAAC,GAAqB,CACjD,MAAMC,EAAQC,EAAgBF,CAAI,EAElC,OAAIC,EAAM,UAENE,EAAAA,KAAC,MAAA,CAAI,UAAWC,EAAY,WAC1B,SAAA,CAAAC,EAAAA,IAACC,EAAA,CAAQ,KAAK,IAAA,CAAK,EAAE,mBAAA,EACvB,EAGAL,EAAM,QAENE,EAAAA,KAAC,MAAA,CAAI,UAAWC,EAAY,WAAY,SAAA,CAAA,UAC9BH,EAAM,iBAAiB,MAAQA,EAAM,MAAM,QAAU,eAAA,EAC/D,EAGCA,EAAM,WAGR,MAAA,CAAI,UAAWM,EAAO,KAErB,eAAC,QAAA,CAAM,UAAWA,EAAO,MAAO,IAAKN,EAAM,KAAK,UAAW,SAAQ,GAAC,EACtE,EANsB,IAQ1B"}