@spencerboucher/switchyard 0.9.0 → 0.10.0
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.
- package/README.md +2 -2
- package/dist/switchyard.mjs +7073 -5396
- package/dist/terminal/holder.js +30 -4
- package/package.json +1 -1
- package/web-dist/assets/AutomationView-BsTwLnMp.js +2 -0
- package/web-dist/assets/{AutomationView-D7Syv1En.js.map → AutomationView-BsTwLnMp.js.map} +1 -1
- package/web-dist/assets/Card-CAFxHoHd.js +2 -0
- package/web-dist/assets/Card-CAFxHoHd.js.map +1 -0
- package/web-dist/assets/Checkbox-rDvnQnxc.js +2 -0
- package/web-dist/assets/Checkbox-rDvnQnxc.js.map +1 -0
- package/web-dist/assets/Copy-D8xhl7eJ.js +2 -0
- package/web-dist/assets/{Copy-B6XSAIUd.js.map → Copy-D8xhl7eJ.js.map} +1 -1
- package/web-dist/assets/DangerZone-og55TRug.js +2 -0
- package/web-dist/assets/DangerZone-og55TRug.js.map +1 -0
- package/web-dist/assets/{Field-D-Ty7fKT.js → Field-Dbg_8KTt.js} +2 -2
- package/web-dist/assets/{Field-D-Ty7fKT.js.map → Field-Dbg_8KTt.js.map} +1 -1
- package/web-dist/assets/{InteractionCard-DF1EcSpm.js → InteractionCard-Yu5wgLC3.js} +4 -4
- package/web-dist/assets/{InteractionCard-DF1EcSpm.js.map → InteractionCard-Yu5wgLC3.js.map} +1 -1
- package/web-dist/assets/Menu-euseM8yR.js +2 -0
- package/web-dist/assets/Menu-euseM8yR.js.map +1 -0
- package/web-dist/assets/Overlay-BNPvMjLX.js +2 -0
- package/web-dist/assets/{Overlay-DUQwh6_b.js.map → Overlay-BNPvMjLX.js.map} +1 -1
- package/web-dist/assets/Priority-Ca1B2yFr.js +2 -0
- package/web-dist/assets/{Priority-CHmzfoHg.js.map → Priority-Ca1B2yFr.js.map} +1 -1
- package/web-dist/assets/{SegmentedControl-C_hEVQMB.js → SegmentedControl-D2d-NEr-.js} +2 -2
- package/web-dist/assets/{SegmentedControl-C_hEVQMB.js.map → SegmentedControl-D2d-NEr-.js.map} +1 -1
- package/web-dist/assets/{Select-Cfql4Nwu.js → Select-C9L1HKMm.js} +2 -2
- package/web-dist/assets/{Select-Cfql4Nwu.js.map → Select-C9L1HKMm.js.map} +1 -1
- package/web-dist/assets/StatusChip-BYuVxYS7.js +2 -0
- package/web-dist/assets/StatusChip-BYuVxYS7.js.map +1 -0
- package/web-dist/assets/{Switch-DBfzmqkm.js → Switch-DAcUcSbH.js} +2 -2
- package/web-dist/assets/{Switch-DBfzmqkm.js.map → Switch-DAcUcSbH.js.map} +1 -1
- package/web-dist/assets/{Tabs-BNuL3_Et.js → Tabs-xtagjtA5.js} +2 -2
- package/web-dist/assets/{Tabs-BNuL3_Et.js.map → Tabs-xtagjtA5.js.map} +1 -1
- package/web-dist/assets/{TextArea-CKybrQyn.js → TextArea-CKGHwURx.js} +2 -2
- package/web-dist/assets/{TextArea-CKybrQyn.js.map → TextArea-CKGHwURx.js.map} +1 -1
- package/web-dist/assets/{TextLink-OzzyP2-p.js → TextLink-Bub7sMxr.js} +2 -2
- package/web-dist/assets/{TextLink-OzzyP2-p.js.map → TextLink-Bub7sMxr.js.map} +1 -1
- package/web-dist/assets/attention-CI4dHY81.js +2 -0
- package/web-dist/assets/{attention-V86Gye3I.js.map → attention-CI4dHY81.js.map} +1 -1
- package/web-dist/assets/auth-308hUVgi.js +2 -0
- package/web-dist/assets/auth-308hUVgi.js.map +1 -0
- package/web-dist/assets/automations-BzpsJoa3.js +2 -0
- package/web-dist/assets/{automations-Cw5Gacgm.js.map → automations-BzpsJoa3.js.map} +1 -1
- package/web-dist/assets/bell-DSwa2PAb.js +2 -0
- package/web-dist/assets/bell-DSwa2PAb.js.map +1 -0
- package/web-dist/assets/board-CeJxBVHC.js +10 -0
- package/web-dist/assets/board-CeJxBVHC.js.map +1 -0
- package/web-dist/assets/code-DkNQvMI9.js +12 -0
- package/web-dist/assets/code-DkNQvMI9.js.map +1 -0
- package/web-dist/assets/describe-CVXeUClF.js +5 -0
- package/web-dist/assets/{describe-D6KLGKWa.js.map → describe-CVXeUClF.js.map} +1 -1
- package/web-dist/assets/enroll-jnKygG4L.js +2 -0
- package/web-dist/assets/{enroll-DwT7AcuS.js.map → enroll-jnKygG4L.js.map} +1 -1
- package/web-dist/assets/index-BHGZTS2e.js +16 -0
- package/web-dist/assets/index-BHGZTS2e.js.map +1 -0
- package/web-dist/assets/index-D2ml031z.css +2 -0
- package/web-dist/assets/{more-DQR5uu1c.js → more-DvKHMdBk.js} +2 -2
- package/web-dist/assets/{more-DQR5uu1c.js.map → more-DvKHMdBk.js.map} +1 -1
- package/web-dist/assets/queue-B2Vl0uSh.js +2 -0
- package/web-dist/assets/{queue-DUg1rS8t.js.map → queue-B2Vl0uSh.js.map} +1 -1
- package/web-dist/assets/repo-BDS8RBxq.js +2 -0
- package/web-dist/assets/repo-BDS8RBxq.js.map +1 -0
- package/web-dist/assets/settings-CLt7x9u8.js +8 -0
- package/web-dist/assets/settings-CLt7x9u8.js.map +1 -0
- package/web-dist/assets/states-C773NlHo.js +56 -0
- package/web-dist/assets/states-C773NlHo.js.map +1 -0
- package/web-dist/assets/{terminal-login-S5CAd5xA.js → terminal-login-BK5UjUir.js} +2 -2
- package/web-dist/assets/{terminal-login-S5CAd5xA.js.map → terminal-login-BK5UjUir.js.map} +1 -1
- package/web-dist/assets/useBreakpoint-BphHBrzF.js +2 -0
- package/web-dist/assets/useBreakpoint-BphHBrzF.js.map +1 -0
- package/web-dist/assets/useNow-Cm-tdyod.js +2 -0
- package/web-dist/assets/{useNow-Dj7oEiwn.js.map → useNow-Cm-tdyod.js.map} +1 -1
- package/web-dist/assets/{useResource-CDGSq2At.js → useResource-BBqCoY2q.js} +2 -2
- package/web-dist/assets/{useResource-CDGSq2At.js.map → useResource-BBqCoY2q.js.map} +1 -1
- package/web-dist/index.html +7 -3
- package/web-dist/sw.js +1 -1
- package/web-dist/sw.js.map +1 -1
- package/web-dist/assets/AutomationView-D7Syv1En.js +0 -2
- package/web-dist/assets/Checkbox-D6tRfEeI.js +0 -2
- package/web-dist/assets/Checkbox-D6tRfEeI.js.map +0 -1
- package/web-dist/assets/Copy-B6XSAIUd.js +0 -2
- package/web-dist/assets/DangerZone-DfYlxDC1.js +0 -2
- package/web-dist/assets/DangerZone-DfYlxDC1.js.map +0 -1
- package/web-dist/assets/Menu-BfOszNC4.js +0 -2
- package/web-dist/assets/Menu-BfOszNC4.js.map +0 -1
- package/web-dist/assets/Overlay-DUQwh6_b.js +0 -2
- package/web-dist/assets/Priority-CHmzfoHg.js +0 -2
- package/web-dist/assets/attention-V86Gye3I.js +0 -2
- package/web-dist/assets/automations-Cw5Gacgm.js +0 -2
- package/web-dist/assets/board-B8X7TYYR.js +0 -10
- package/web-dist/assets/board-B8X7TYYR.js.map +0 -1
- package/web-dist/assets/code-BKroYJmR.js +0 -12
- package/web-dist/assets/code-BKroYJmR.js.map +0 -1
- package/web-dist/assets/describe-D6KLGKWa.js +0 -5
- package/web-dist/assets/enroll-DwT7AcuS.js +0 -2
- package/web-dist/assets/index-B3F7WcYf.css +0 -2
- package/web-dist/assets/index-hxvDvCS7.js +0 -12
- package/web-dist/assets/index-hxvDvCS7.js.map +0 -1
- package/web-dist/assets/queue-DUg1rS8t.js +0 -2
- package/web-dist/assets/repo-XzzzEI0d.js +0 -2
- package/web-dist/assets/repo-XzzzEI0d.js.map +0 -1
- package/web-dist/assets/search-C6cn5WOo.js +0 -2
- package/web-dist/assets/search-C6cn5WOo.js.map +0 -1
- package/web-dist/assets/settings-DyUu0tJM.js +0 -8
- package/web-dist/assets/settings-DyUu0tJM.js.map +0 -1
- package/web-dist/assets/states-ZhrxQa3K.js +0 -56
- package/web-dist/assets/states-ZhrxQa3K.js.map +0 -1
- package/web-dist/assets/useNow-Dj7oEiwn.js +0 -2
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{_ as e,m as t,t as n}from"./jsx-runtime-D13aM9HY.js";import{_ as r}from"./states-
|
|
2
|
-
//# sourceMappingURL=TextArea-
|
|
1
|
+
import{_ as e,m as t,t as n}from"./jsx-runtime-D13aM9HY.js";import{_ as r}from"./states-C773NlHo.js";var i=e(t(),1),a=n(),o=r(`rounded-md border border-border bg-surface text-base text-text placeholder:text-subtle md:text-sm`,`outline-none focus:border-accent focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-offset-0 focus-visible:outline-[var(--sy-focus)]`,`aria-[invalid=true]:border-danger aria-[invalid=true]:focus:border-danger`,`disabled:cursor-not-allowed disabled:opacity-55`),s=(0,i.forwardRef)(function({maxHeight:e=`33vh`,minRows:t=1,className:n,value:s,style:c,...l},u){let d=(0,i.useRef)(null),f=(0,i.useCallback)(e=>{d.current=e,typeof u==`function`?u(e):u&&(u.current=e)},[u]);return(0,i.useLayoutEffect)(()=>{let e=d.current;e&&(e.style.height=`auto`,e.style.height=`${e.scrollHeight+(e.offsetHeight-e.clientHeight)}px`)},[s]),(0,a.jsx)(`textarea`,{ref:f,rows:t,value:s,style:{maxHeight:e,...c},className:r(`block w-full resize-none overflow-y-auto px-3 py-2`,o,n),...l})}),c=(0,i.forwardRef)(function({className:e,...t},n){return(0,a.jsx)(`input`,{ref:n,className:r(`tap block h-9 w-full px-3`,o,e),...t})});export{s as n,c as r,o as t};
|
|
2
|
+
//# sourceMappingURL=TextArea-CKGHwURx.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextArea-
|
|
1
|
+
{"version":3,"file":"TextArea-CKGHwURx.js","names":["forwardRef","useRef","useCallback","__toESM","require_react","require_jsx_runtime","cn","import_react","import_jsx_runtime","CONTROL_CLASSES","TextArea","rest","inner","setRef","el","ref","value","minRows","style","className","TextInput"],"sources":["../../src/components/ui/TextArea.tsx"],"sourcesContent":["import {\n forwardRef,\n type TextareaHTMLAttributes,\n useCallback,\n useLayoutEffect,\n useRef,\n} from \"react\";\nimport { cn } from \"../../lib/cn\";\n\n/** Shared look of text controls: border, focus ring, invalid and disabled states. */\nexport const CONTROL_CLASSES = cn(\n \"rounded-md border border-border bg-surface text-base text-text placeholder:text-subtle md:text-sm\",\n \"outline-none focus:border-accent focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-offset-0 focus-visible:outline-[var(--sy-focus)]\",\n \"aria-[invalid=true]:border-danger aria-[invalid=true]:focus:border-danger\",\n \"disabled:cursor-not-allowed disabled:opacity-55\",\n);\n\nexport interface TextAreaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {\n /** Max height before scrolling internally (CSS length; default \"33vh\"). */\n maxHeight?: string;\n /** Minimum rows (default 1). */\n minRows?: number;\n}\n\n/** Auto-growing textarea that scrolls internally past `maxHeight`. */\nexport const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(function TextArea(\n { maxHeight = \"33vh\", minRows = 1, className, value, style, ...rest },\n ref,\n) {\n const inner = useRef<HTMLTextAreaElement | null>(null);\n const setRef = useCallback(\n (el: HTMLTextAreaElement | null) => {\n inner.current = el;\n if (typeof ref === \"function\") ref(el);\n else if (ref) ref.current = el;\n },\n [ref],\n );\n // biome-ignore lint/correctness/useExhaustiveDependencies: re-measure when the value changes\n useLayoutEffect(() => {\n const el = inner.current;\n if (!el) return;\n el.style.height = \"auto\";\n el.style.height = `${el.scrollHeight + (el.offsetHeight - el.clientHeight)}px`;\n }, [value]);\n return (\n <textarea\n ref={setRef}\n rows={minRows}\n value={value}\n style={{ maxHeight, ...style }}\n className={cn(\n \"block w-full resize-none overflow-y-auto px-3 py-2\",\n CONTROL_CLASSES,\n className,\n )}\n {...rest}\n />\n );\n});\n\n/** Single-line input with the same styling. */\nexport const TextInput = forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(\n function TextInput({ className, ...rest }, ref) {\n return (\n <input\n ref={ref}\n className={cn(\"tap block h-9 w-full px-3\", CONTROL_CLASSES, className)}\n {...rest}\n />\n );\n },\n);\n"],"mappings":"A,0HAUaS,EAAkBH,EAC7B,oGACA,8JACA,4EACA,iDACF,EAUaI,GAAA,EAAWV,EAAAA,WAAAA,CAA+C,SACrE,CAAE,YAAY,OAAQ,UAAU,EAAG,YAAW,QAAO,QAAO,GAAGW,GAC/D,EACA,CACA,IAAMC,GAAA,EAAQX,EAAAA,OAAAA,CAAmC,IAAI,EAC/CY,GAAA,EAASX,EAAAA,YAAAA,CACZ,GAAmC,CAClC,EAAM,QAAUY,EACZ,OAAOC,GAAQ,WAAYA,EAAID,CAAE,EAC5BC,IAAK,EAAI,QAAUD,EAC9B,EACA,CAACC,CAAG,CACN,EAQA,OANA,EAAAR,EAAA,gBAAA,KAAsB,CACpB,IAAMO,EAAKF,EAAM,QACZE,IACL,EAAG,MAAM,OAAS,OAClB,EAAG,MAAM,OAAS,GAAGA,EAAG,cAAgBA,EAAG,aAAeA,EAAG,cAAc,IAC7E,EAAG,CAACE,CAAK,CAAC,GAER,EAAAR,EAAA,IAAA,CAAC,WAAD,CACE,IAAKK,EACL,KAAMI,EACC,QACP,MAAO,CAAE,YAAW,GAAGC,CAAM,EAC7B,UAAWZ,EACT,qDACAG,EACAU,CACF,EACA,GAAIR,CACL,CAAA,CAEL,CAAC,EAGYS,GAAA,EAAYpB,EAAAA,WAAAA,CACvB,SAAmB,CAAE,YAAW,GAAGW,GAAQ,EAAK,CAC9C,OACE,EAAAH,EAAA,IAAA,CAAC,QAAD,CACO,MACL,UAAWF,EAAG,4BAA6BG,EAAiBU,CAAS,EACrE,GAAIR,CACL,CAAA,CAEL,CACF,E"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{i as e,t}from"./jsx-runtime-D13aM9HY.js";import{_ as n}from"./states-
|
|
2
|
-
//# sourceMappingURL=TextLink-
|
|
1
|
+
import{i as e,t}from"./jsx-runtime-D13aM9HY.js";import{_ as n}from"./states-C773NlHo.js";var r=t(),i={xs:`text-xs`,sm:`text-sm`,inherit:``};function a(t){let{icon:a,size:o=`inherit`,variant:s,className:c,children:l}=t,u=`to`in t&&t.to!==void 0,d=`href`in t&&t.href!==void 0,f=n(`tap inline-flex min-w-0 items-center gap-1 rounded-sm text-left font-medium text-accent underline-offset-2`,(s??(u||d?`inline`:`action`))===`inline`?`underline`:`hover:underline`,i[o],c),p=(0,r.jsxs)(r.Fragment,{children:[a,l]});if(u){let{icon:n,size:i,variant:a,className:o,children:s,...c}=t;return(0,r.jsx)(e,{...c,className:f,children:p})}if(d){let{icon:e,size:n,variant:i,className:a,children:o,external:s,...c}=t;return(0,r.jsx)(`a`,{...c,className:f,...s?{target:`_blank`,rel:`noreferrer`}:{},children:p})}let{icon:m,size:h,variant:g,className:_,children:v,...y}=t;return(0,r.jsx)(`button`,{type:`button`,...y,className:f,children:p})}export{a as t};
|
|
2
|
+
//# sourceMappingURL=TextLink-Bub7sMxr.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextLink-
|
|
1
|
+
{"version":3,"file":"TextLink-Bub7sMxr.js","names":["_Fragment","Link","cn","import_jsx_runtime","require_jsx_runtime","SIZES","TextLink","props","isLink","isAnchor","style","variant","size","className","content","icon","children","_i","_s","_v","_c","_ch","rest","external"],"sources":["../../src/components/ui/TextLink.tsx"],"sourcesContent":["import type { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from \"react\";\nimport { Link, type LinkProps } from \"react-router\";\nimport { cn } from \"../../lib/cn\";\n\ntype Common = {\n children: ReactNode;\n icon?: ReactNode;\n size?: \"xs\" | \"sm\" | \"inherit\";\n /** \"inline\" underlines (default for links); \"action\" underlines on hover (default for buttons). */\n variant?: \"inline\" | \"action\";\n className?: string;\n};\n\nexport type TextLinkProps =\n | (Common & { to: string } & Omit<LinkProps, \"className\" | \"children\" | \"to\">)\n | (Common & { href: string; external?: boolean } & Omit<\n AnchorHTMLAttributes<HTMLAnchorElement>,\n \"className\" | \"children\" | \"href\"\n >)\n | (Common & { to?: undefined; href?: undefined } & Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n \"className\" | \"children\"\n >);\n\nconst SIZES = { xs: \"text-xs\", sm: \"text-sm\", inherit: \"\" } as const;\n\n/** A link or link-styled button for text. Always inline-flex so `.tap` can size its touch target. */\nexport function TextLink(props: TextLinkProps) {\n const { icon, size = \"inherit\", variant, className, children } = props;\n const isLink = \"to\" in props && props.to !== undefined;\n const isAnchor = \"href\" in props && props.href !== undefined;\n const style = cn(\n \"tap inline-flex min-w-0 items-center gap-1 rounded-sm text-left font-medium text-accent underline-offset-2\",\n (variant ?? (isLink || isAnchor ? \"inline\" : \"action\")) === \"inline\"\n ? \"underline\"\n : \"hover:underline\",\n SIZES[size],\n className,\n );\n const content = (\n <>\n {icon}\n {children}\n </>\n );\n if (isLink) {\n const {\n icon: _i,\n size: _s,\n variant: _v,\n className: _c,\n children: _ch,\n ...rest\n } = props as Common & LinkProps;\n return (\n <Link {...rest} className={style}>\n {content}\n </Link>\n );\n }\n if (isAnchor) {\n const {\n icon: _i,\n size: _s,\n variant: _v,\n className: _c,\n children: _ch,\n external,\n ...rest\n } = props as Common & AnchorHTMLAttributes<HTMLAnchorElement> & { external?: boolean };\n return (\n <a {...rest} className={style} {...(external ? { target: \"_blank\", rel: \"noreferrer\" } : {})}>\n {content}\n </a>\n );\n }\n const {\n icon: _i,\n size: _s,\n variant: _v,\n className: _c,\n children: _ch,\n ...rest\n } = props as Common & ButtonHTMLAttributes<HTMLButtonElement>;\n return (\n <button type=\"button\" {...rest} className={style}>\n {content}\n </button>\n );\n}\n"],"mappings":"A,mGAwBMK,EAAQ,CAAE,GAAI,UAAW,GAAI,UAAW,QAAS,EAAG,EAG1D,SAAgBC,EAAS,EAAsB,CAC7C,GAAM,CAAE,OAAM,OAAO,UAAW,UAAS,YAAW,YAAaC,EAC3DC,EAAS,OAAQD,GAASA,EAAM,KAAO,IAAA,GACvCE,EAAW,SAAUF,GAASA,EAAM,OAAS,IAAA,GAC7CG,EAAQR,EACZ,8GACCS,IAAYH,GAAUC,EAAW,SAAW,aAAe,SACxD,YACA,kBACJJ,EAAMO,GACNC,CACF,EACMC,GACJ,EAAAX,EAAA,KAAA,CAAAH,EAAAA,SAAA,CAAA,SAAA,CACGe,EACAC,CACD,CAAA,CAAA,EAEJ,GAAIR,EAAQ,CACV,GAAM,CACJ,KAAMS,EACN,KAAMC,EACN,QAASC,EACT,UAAWC,EACX,SAAUC,EACV,GAAGC,GACDf,EACJ,OACE,EAAAJ,EAAA,IAAA,CAACF,EAAD,CAAM,GAAIqB,EAAM,UAAWZ,EACxB,SAAAI,CACG,CAAA,CAEV,CACA,GAAIL,EAAU,CACZ,GAAM,CACJ,KAAMQ,EACN,KAAMC,EACN,QAASC,EACT,UAAWC,EACX,SAAUC,EACV,WACA,GAAGC,GACDf,EACJ,OACE,EAAAJ,EAAA,IAAA,CAAC,IAAD,CAAG,GAAImB,EAAM,UAAWZ,EAAO,GAAKa,EAAW,CAAE,OAAQ,SAAU,IAAK,YAAa,EAAI,CAAC,EACvF,SAAAT,CACA,CAAA,CAEP,CACA,GAAM,CACJ,KAAMG,EACN,KAAMC,EACN,QAASC,EACT,UAAWC,EACX,SAAUC,EACV,GAAGC,GACDf,EACJ,OACE,EAAAJ,EAAA,IAAA,CAAC,SAAD,CAAQ,KAAK,SAAS,GAAImB,EAAM,UAAWZ,EACxC,SAAAI,CACK,CAAA,CAEZ,C"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{_ as e,i as t,m as n,n as r,t as i}from"./jsx-runtime-D13aM9HY.js";import{g as a,o,t as s}from"./StatusChip-BYuVxYS7.js";import{b as c,c as l,d as u,f as d,gt as f,h as p,i as m,j as h,n as g,s as _,t as v,u as y,vt as b,yt as x,z as S}from"./states-C773NlHo.js";import{t as C}from"./bell-DSwa2PAb.js";import{i as w,u as T}from"./useBreakpoint-BphHBrzF.js";import{t as E}from"./external-link-3jjTxo92.js";import{n as D,t as O}from"./InteractionCard-Yu5wgLC3.js";import{t as k}from"./server-BqZ-w6M8.js";import{c as A,y as j}from"./index-BHGZTS2e.js";import{t as M}from"./SegmentedControl-D2d-NEr-.js";import{r as N}from"./useResource-BBqCoY2q.js";var P={name:`list-todo`,size:24,node:[[`path`,{d:`M13 5h8`,key:`a7qcls`}],[`path`,{d:`M13 12h8`,key:`h98zly`}],[`path`,{d:`M13 19h8`,key:`c3s6r1`}],[`path`,{d:`m3 17 2 2 4-4`,key:`1jhpwq`}],[`rect`,{x:`3`,y:`4`,width:`6`,height:`6`,rx:`1`,key:`cif1o7`}]]};P.node;var F=r(P),I=e(n(),1),L=[`needs_input`,`failed`,`ready_for_review`,`unsupported_interaction`,`host_unreachable`];function R(e){let t=Object.fromEntries(L.map(e=>[e,[]]));for(let n of e)t[n.reason]?.push(n);return t}function z(e){return e===`dismissed`||e===`history`?e:`open`}function B(e,t){return t===`history`?[]:e.filter(e=>t===`open`?!e.acknowledgedAt:!!e.acknowledgedAt)}function V(e,t,n){return[e?.name,t?.title,n&&n.title!==t?.title?n.title:null].filter(Boolean).join(` · `)||`Switchyard`}function H(e){let t=N(e?`attention-history`:null,e=>f(`/api/attention`,{resolvedOnly:`true`,limit:200},e),{refreshMs:3e4,check:Array.isArray}),n=S(e=>e.attention),r=(0,I.useRef)(!0),{reload:i}=t;return(0,I.useEffect)(()=>{if(r.current){r.current=!1;return}if(!e)return;let t=setTimeout(i,500);return()=>clearTimeout(t)},[n,e,i]),t}var U=i(),W={needs_input:`Questions from Pi waiting for your answer.`,failed:`Runs that failed or were blocked. Open the task to see why.`,ready_for_review:`Finished runs waiting for your review.`,unsupported_interaction:`Questions this client cannot display. Answer them in a terminal attached to the session.`,host_unreachable:`Running work on hosts that stopped responding. Nothing is restarted elsewhere.`};function G(){let e=h(),t=S(e=>e.hydrated),[n,r]=j(`attentionShow`,`open`),i=z(n),a=e=>r(e),o=(0,I.useMemo)(()=>B(e,i),[e,i]),u=(0,I.useMemo)(()=>B(e,`dismissed`).length,[e]),d=(0,I.useMemo)(()=>R(o),[o]),f=H(i===`history`);return(0,U.jsxs)(`div`,{className:`flex w-full min-w-0 flex-col gap-4 px-4 pb-4 safe-pt-4 md:px-6`,children:[(0,U.jsxs)(`header`,{className:`flex min-w-0 flex-wrap items-center gap-3`,children:[(0,U.jsx)(`h1`,{className:`flex-1 text-lg font-semibold`,children:`Attention`}),(0,U.jsx)(M,{label:`Show`,className:`max-sm:w-full max-sm:basis-full`,value:i,onChange:a,options:[{value:`open`,label:`Open`},{value:`dismissed`,label:`Dismissed`},{value:`history`,label:`History`}]})]}),(0,U.jsx)(_,{subject:`attention items`,consequence:`Answers are sent once the connection is back.`,show:t}),i===`history`?(0,U.jsx)(`div`,{"data-testid":`attention-history`,className:`flex min-w-0 flex-col gap-2`,children:!f.data&&f.loading?(0,U.jsx)(l,{label:`Loading history…`}):!f.data&&f.error?(0,U.jsx)(g,{compact:!0,title:`Could not load history`,error:x(f.error),onRetry:f.reload}):!f.data||f.data.length===0?(0,U.jsx)(v,{icon:(0,U.jsx)(c,{className:`size-8`}),title:`No history yet`,description:`Resolved questions, failures and reviews show up here.`}):(0,U.jsxs)(U.Fragment,{children:[(0,U.jsx)(`ul`,{className:`flex min-w-0 flex-col gap-2`,children:f.data.map(e=>(0,U.jsx)(`li`,{className:`min-w-0`,children:(0,U.jsx)(q,{item:e})},e.id))}),f.data.length>=200&&(0,U.jsxs)(`p`,{className:`text-sm text-muted`,children:[`Showing the `,200,` most recent.`]})]})}):t?o.length===0?i===`dismissed`?(0,U.jsx)(v,{icon:(0,U.jsx)(C,{className:`size-8`}),title:`No dismissed items`,description:`Items you dismiss stay here until their question or task is resolved.`,action:{label:`Show open`,onClick:()=>a(`open`)}}):(0,U.jsx)(v,{icon:(0,U.jsx)(C,{className:`size-8`}),title:u>0?`All caught up`:`Nothing needs your attention`,description:u>0?`${u} dismissed ${u===1?`item is`:`items are`} still open.`:`Questions, failures and finished work show up here.`,action:u>0?{label:`Show dismissed`,onClick:()=>a(`dismissed`)}:{label:`Go to Sessions`,to:A.code},secondaryAction:u>0?{label:`Go to Sessions`,to:A.code}:{label:`Go to Board`,to:A.board}}):L.filter(e=>d[e].length>0).map(e=>{let t=s[e],n=t.icon;return(0,U.jsxs)(`section`,{"data-testid":`attention-group-${e}`,"aria-labelledby":`att-${e}`,className:`flex min-w-0 flex-col gap-2`,children:[(0,U.jsxs)(`div`,{children:[(0,U.jsxs)(`h2`,{id:`att-${e}`,className:`flex items-center gap-2 text-base font-semibold`,children:[(0,U.jsx)(n,{"aria-hidden":!0,className:`size-4 text-muted`}),t.label,(0,U.jsxs)(`span`,{className:`text-sm font-normal text-muted`,children:[`(`,d[e].length,`)`]})]}),(0,U.jsx)(`p`,{className:`text-sm text-muted`,children:W[e]})]}),(0,U.jsx)(`ul`,{className:`flex min-w-0 flex-col gap-2`,children:d[e].map(e=>(0,U.jsx)(`li`,{className:`min-w-0`,children:(0,U.jsx)(K,{item:e})},e.id))})]},e)}):(0,U.jsx)(l,{label:`Loading attention…`})]})}function K({item:e}){let n=S(t=>e.projectId?t.projects[e.projectId]:void 0),r=S(t=>e.taskId?t.tasks[e.taskId]:void 0),i=S(t=>e.sessionId?t.sessions[e.sessionId]:void 0),c=S(e=>i?e.hosts[i.hostId]:void 0),l=S(t=>e.interactionId?t.interactions[e.interactionId]:void 0),[f,h]=(0,I.useState)(!1),[_,v]=(0,I.useState)(null),C=async()=>{h(!0),v(null);try{let t=await b(`/api/attention/${encodeURIComponent(e.id)}/acknowledge`),n=t&&typeof t==`object`&&`id`in t?t:{...e,acknowledgedAt:new Date().toISOString()};S.setState(t=>t.attention[e.id]?{attention:{...t.attention,[e.id]:n}}:{}),w.success(`Dismissed. It stays under All until the task or question is resolved.`)}catch(e){v(e)}finally{h(!1)}},j=e.sessionId?e.interactionId?A.interaction(e.sessionId,e.interactionId):A.session(e.sessionId):null,M=!!c&&c.connectivity!==`connected`,N=null;switch(e.reason){case`needs_input`:N=l&&l.state===`pending`?(0,U.jsx)(O,{flush:!0,interaction:l,readOnlyReason:M?`Host ${c?.name} is offline. You can answer once it reconnects.`:i?.readOnly?`This session is read-only.`:null}):l?(0,U.jsx)(O,{flush:!0,interaction:l}):j?(0,U.jsxs)(t,{to:j,className:d(`primary`,`sm`),children:[(0,U.jsx)(E,{"aria-hidden":!0,className:`size-3.5`}),`Answer in session`]}):null;break;case`failed`:N=e.taskId?(0,U.jsxs)(t,{to:A.task(e.taskId),className:d(`primary`,`sm`),children:[(0,U.jsx)(F,{"aria-hidden":!0,className:`size-3.5`}),`Open task`]}):null;break;case`ready_for_review`:N=e.sessionId?(0,U.jsxs)(t,{to:`${A.session(e.sessionId)}?tab=changes`,className:d(`primary`,`sm`),children:[(0,U.jsx)(a,{"aria-hidden":!0,className:`size-3.5`}),`Review changes`]}):e.taskId?(0,U.jsxs)(t,{to:A.task(e.taskId),className:d(`primary`,`sm`),children:[(0,U.jsx)(a,{"aria-hidden":!0,className:`size-3.5`}),`Open review`]}):null;break;case`unsupported_interaction`:N=l?(0,U.jsx)(O,{interaction:l,readOnlyReason:M?`Host ${c?.name} is offline. You can choose once it reconnects.`:i?.readOnly?`This session is read-only.`:null}):(0,U.jsx)(y,{capability:`This dialog`,reason:`An extension opened a terminal-only dialog. In RPC mode Pi gave it no answer, so the extension continued without one.`});break;case`host_unreachable`:N=c?(0,U.jsx)(m,{compact:!0,hostName:c.name,connectivity:c.connectivity===`reconnecting`?`reconnecting`:`unreachable`,lastSeenAt:c.lastSeenAt}):null}let P=V(n,r,i),L=!!l&&(e.reason===`needs_input`||e.reason===`unsupported_interaction`);return e.reason===`needs_input`&&l?.state===`pending`&&D(l.presentation)?(0,U.jsxs)(`article`,{"aria-label":`Plan awaiting approval: ${P}`,className:`flex min-w-0 flex-col gap-2`,children:[(0,U.jsxs)(`div`,{className:`flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-sm text-muted`,children:[(0,U.jsx)(`span`,{className:`min-w-0 basis-full truncate sm:basis-auto sm:flex-1`,children:P}),j&&(0,U.jsxs)(t,{to:j,className:d(`ghost`,`sm`),children:[(0,U.jsx)(E,{"aria-hidden":!0,className:`size-3.5`}),`Open session`]}),(0,U.jsx)(`time`,{dateTime:e.createdAt,className:`shrink-0 text-xs text-subtle sm:ml-auto`,children:p(e.createdAt)}),!e.acknowledgedAt&&(0,U.jsx)(u,{size:`sm`,variant:`ghost`,busy:f,onClick:()=>void C(),children:`Dismiss`})]}),N,_!==null&&(0,U.jsx)(g,{compact:!0,title:`Could not dismiss`,error:x(_),onRetry:()=>void C()})]}):(0,U.jsxs)(`article`,{"aria-label":`${s[e.reason].label}: ${r?.title??i?.title??e.summary}`,className:`flex min-w-0 flex-col gap-2 rounded-lg border border-border bg-surface p-3`,children:[(0,U.jsxs)(`div`,{className:`flex min-w-0 flex-wrap items-center gap-2`,children:[(0,U.jsx)(o,{kind:`attention`,value:e.reason,size:`sm`}),(0,U.jsx)(`span`,{className:`min-w-0 flex-1 truncate text-xs text-muted`,children:P}),(0,U.jsx)(`time`,{dateTime:e.createdAt,className:`shrink-0 text-xs text-subtle`,children:p(e.createdAt)})]}),!L&&(0,U.jsx)(`p`,{className:`min-w-0 text-sm break-words`,children:e.summary}),N&&(0,U.jsx)(`div`,{className:`flex min-w-0 flex-col items-start`,children:N}),(0,U.jsxs)(`div`,{className:`flex min-w-0 flex-wrap items-center gap-2`,children:[e.reason!==`failed`&&e.taskId&&(0,U.jsxs)(t,{to:A.task(e.taskId),className:d(`ghost`,`sm`),children:[(0,U.jsx)(F,{"aria-hidden":!0,className:`size-3.5`}),`Task`]}),j&&(0,U.jsxs)(t,{to:j,className:d(`ghost`,`sm`),children:[(0,U.jsx)(E,{"aria-hidden":!0,className:`size-3.5`}),`Open session`]}),e.reason===`host_unreachable`&&c&&(0,U.jsxs)(t,{to:`${A.settings(`hosts`)}/${encodeURIComponent(c.id)}`,className:d(`ghost`,`sm`),children:[(0,U.jsx)(k,{"aria-hidden":!0,className:`size-3.5`}),`Host details`]}),(0,U.jsx)(`span`,{className:`ml-auto`}),e.acknowledgedAt?(0,U.jsxs)(`span`,{className:`inline-flex items-center gap-1 text-xs text-muted`,children:[(0,U.jsx)(T,{"aria-hidden":!0,className:`size-3.5`}),`Dismissed `,p(e.acknowledgedAt)]}):(0,U.jsx)(u,{size:`sm`,busy:f,onClick:()=>void C(),children:`Dismiss`})]}),_!==null&&(0,U.jsx)(g,{compact:!0,title:`Could not dismiss`,error:x(_),onRetry:()=>void C()})]})}function q({item:e}){let n=S(t=>e.projectId?t.projects[e.projectId]:void 0),r=S(t=>e.taskId?t.tasks[e.taskId]:void 0),i=S(t=>e.sessionId?t.sessions[e.sessionId]:void 0);return(0,U.jsxs)(`article`,{"aria-label":`${s[e.reason].label}: ${r?.title??i?.title??e.summary}`,className:`flex min-w-0 flex-col gap-2 rounded-lg border border-border bg-surface p-3`,children:[(0,U.jsxs)(`div`,{className:`flex min-w-0 flex-wrap items-center gap-2`,children:[(0,U.jsx)(o,{kind:`attention`,value:e.reason,size:`sm`}),(0,U.jsx)(`span`,{className:`min-w-0 flex-1 truncate text-xs text-muted`,children:V(n,r,i)}),e.resolvedAt&&(0,U.jsxs)(`time`,{dateTime:e.resolvedAt,className:`shrink-0 text-xs text-subtle`,children:[`Resolved `,p(e.resolvedAt)]})]}),(0,U.jsx)(`p`,{className:`min-w-0 text-sm break-words`,children:e.summary}),(e.taskId||e.sessionId)&&(0,U.jsxs)(`div`,{className:`flex min-w-0 flex-wrap items-center gap-2`,children:[e.taskId&&(0,U.jsxs)(t,{to:A.task(e.taskId),className:d(`ghost`,`sm`),children:[(0,U.jsx)(F,{"aria-hidden":!0,className:`size-3.5`}),`Task`]}),e.sessionId&&(0,U.jsxs)(t,{to:A.session(e.sessionId),className:d(`ghost`,`sm`),children:[(0,U.jsx)(E,{"aria-hidden":!0,className:`size-3.5`}),`Open session`]})]})]})}export{G as default};
|
|
2
|
+
//# sourceMappingURL=attention-CI4dHY81.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attention-V86Gye3I.js","names":["useRef","useMemo","History","_Fragment","useState","__toESM","Link","require_react","createLucideIcon","require_jsx_runtime","RotateCcwClock","LoadingState","Button","buttonClasses","apiGet","formatRelative","HostUnavailable","useOpenAttention","ErrorState","LastKnownNote","EmptyState","UnsupportedCapability","apiPost","errorMessage","useSyncStore","ExternalLink","isDocumentPresentation","InteractionCard","Server","StatusChip","Eye","CircleCheck","Bell","toast","ROUTES","useFilter","ATTENTION_STATUS","SegmentedControl","useResource","__iconData","ListTodo","import_react","ATTENTION_GROUPS","groupAttention","out","r","a","items","normalizeShow","v","visibleAttention","show","all","attentionMeta","project","task","session","useAttentionHistory","res","enabled","signal","attention","s","first","reload","import_jsx_runtime","GROUP_HINT","AttentionView","hydrated","stored","setStored","setShow","dismissedCount","groups","history","HistoryRow","info","reason","Icon","AttentionRow","item","host","interaction","busy","setBusy","ackError","setAckError","acknowledge","next","sessionHref","hostOffline","action","metaText","showsCard"],"sources":["../../../../node_modules/.pnpm/lucide-react@1.61.0_@types+react@19.3.0_react@19.3.1/node_modules/lucide-react/dist/esm/icons/list-todo.mjs","../../src/features/attention/logic.ts","../../src/features/attention/useAttentionHistory.ts","../../src/features/attention/index.tsx"],"sourcesContent":["/**\n * @license lucide-react v1.61.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconData = {\n name: \"list-todo\",\n size: 24,\n node: [\n [\"path\", { d: \"M13 5h8\", key: \"a7qcls\" }],\n [\"path\", { d: \"M13 12h8\", key: \"h98zly\" }],\n [\"path\", { d: \"M13 19h8\", key: \"c3s6r1\" }],\n [\"path\", { d: \"m3 17 2 2 4-4\", key: \"1jhpwq\" }],\n [\"rect\", { x: \"3\", y: \"4\", width: \"6\", height: \"6\", rx: \"1\", key: \"cif1o7\" }]\n ]\n};\n__iconData.node;\nconst ListTodo = createLucideIcon(__iconData);\n\nexport { __iconData, ListTodo as default };\n//# sourceMappingURL=list-todo.mjs.map\n","import type { AttentionItem, AttentionReason, Project, Session, Task } from \"@switchyard/protocol\";\n\nexport const ATTENTION_GROUPS: AttentionReason[] = [\n \"needs_input\",\n \"failed\",\n \"ready_for_review\",\n \"unsupported_interaction\",\n \"host_unreachable\",\n];\n\n/** Group open attention by reason, keeping the input order within a group. */\nexport function groupAttention(items: AttentionItem[]): Record<AttentionReason, AttentionItem[]> {\n const out = Object.fromEntries(ATTENTION_GROUPS.map((r) => [r, [] as AttentionItem[]])) as Record<\n AttentionReason,\n AttentionItem[]\n >;\n for (const a of items) out[a.reason]?.push(a);\n return out;\n}\n\nexport type AttentionShow = \"open\" | \"dismissed\" | \"history\";\n\n/** Stored prefs from before History: \"all\" (open + dismissed) and unknown values read as \"open\". */\nexport function normalizeShow(v: unknown): AttentionShow {\n return v === \"dismissed\" || v === \"history\" ? v : \"open\";\n}\n\n/** Unacknowledged items for \"open\", acknowledged for \"dismissed\"; [] for \"history\". */\nexport function visibleAttention(all: AttentionItem[], show: AttentionShow): AttentionItem[] {\n if (show === \"history\") return [];\n return all.filter((a) => (show === \"open\" ? !a.acknowledgedAt : !!a.acknowledgedAt));\n}\n\n/** \"project · task · session\" (session only when its title differs from the task's), else \"Switchyard\". */\nexport function attentionMeta(project?: Project, task?: Task, session?: Session): string {\n return (\n [project?.name, task?.title, session && session.title !== task?.title ? session.title : null]\n .filter(Boolean)\n .join(\" · \") || \"Switchyard\"\n );\n}\n","import type { AttentionItem } from \"@switchyard/protocol\";\nimport { useEffect, useRef } from \"react\";\nimport { type Resource, useResource } from \"../../lib/useResource\";\nimport { apiGet } from \"../../sync/api\";\nimport { useSyncStore } from \"../../sync/store\";\n\nexport const HISTORY_LIMIT = 200;\n\n/**\n * Resolved attention items, newest first. The sync store drops resolved items, so they come\n * from the API; refetched when open attention changes (debounced) and every 30 s.\n */\nexport function useAttentionHistory(enabled: boolean): Resource<AttentionItem[]> {\n const res = useResource<AttentionItem[]>(\n enabled ? \"attention-history\" : null,\n (signal) =>\n apiGet<AttentionItem[]>(\n \"/api/attention\",\n { resolvedOnly: \"true\", limit: HISTORY_LIMIT },\n signal,\n ),\n { refreshMs: 30_000, check: Array.isArray },\n );\n const attention = useSyncStore((s) => s.attention);\n const first = useRef(true);\n const { reload } = res;\n // biome-ignore lint/correctness/useExhaustiveDependencies: refetch on attention changes\n useEffect(() => {\n if (first.current) {\n first.current = false;\n return;\n }\n if (!enabled) return;\n const t = setTimeout(reload, 500);\n return () => clearTimeout(t);\n }, [attention, enabled, reload]);\n return res;\n}\n","import type { AttentionItem, AttentionReason } from \"@switchyard/protocol\";\nimport { Bell, CircleCheck, ExternalLink, Eye, History, ListTodo, Server } from \"lucide-react\";\nimport { useMemo, useState } from \"react\";\nimport { Link } from \"react-router\";\nimport { ROUTES } from \"../../app/routes\";\nimport { InteractionCard, isDocumentPresentation } from \"../../components/intent/InteractionCard\";\nimport { Button, buttonClasses } from \"../../components/ui/Button\";\nimport { SegmentedControl } from \"../../components/ui/SegmentedControl\";\nimport { ATTENTION_STATUS, StatusChip } from \"../../components/ui/StatusChip\";\nimport {\n EmptyState,\n ErrorState,\n HostUnavailable,\n LastKnownNote,\n LoadingState,\n UnsupportedCapability,\n} from \"../../components/ui/states\";\nimport { toast } from \"../../components/ui/Toast\";\nimport { formatRelative } from \"../../lib/time\";\nimport { useFilter } from \"../../prefs\";\nimport { apiPost, errorMessage } from \"../../sync/api\";\nimport { useOpenAttention, useSyncStore } from \"../../sync/store\";\nimport {\n ATTENTION_GROUPS,\n type AttentionShow,\n attentionMeta,\n groupAttention,\n normalizeShow,\n visibleAttention,\n} from \"./logic\";\nimport { HISTORY_LIMIT, useAttentionHistory } from \"./useAttentionHistory\";\n\nconst GROUP_HINT: Record<AttentionReason, string> = {\n needs_input: \"Questions from Pi waiting for your answer.\",\n failed: \"Runs that failed or were blocked. Open the task to see why.\",\n ready_for_review: \"Finished runs waiting for your review.\",\n unsupported_interaction:\n \"Questions this client cannot display. Answer them in a terminal attached to the session.\",\n host_unreachable:\n \"Running work on hosts that stopped responding. Nothing is restarted elsewhere.\",\n};\n\n/** /attention: Needs input, Failed, Ready for review (plus unsupported and unreachable). */\nexport default function AttentionView() {\n const all = useOpenAttention();\n const hydrated = useSyncStore((s) => s.hydrated);\n const [stored, setStored] = useFilter<AttentionShow | \"all\">(\"attentionShow\", \"open\");\n const show = normalizeShow(stored);\n const setShow = (v: AttentionShow) => setStored(v);\n const items = useMemo(() => visibleAttention(all, show), [all, show]);\n const dismissedCount = useMemo(() => visibleAttention(all, \"dismissed\").length, [all]);\n const groups = useMemo(() => groupAttention(items), [items]);\n const history = useAttentionHistory(show === \"history\");\n\n return (\n <div className=\"flex w-full min-w-0 flex-col gap-4 px-4 pb-4 safe-pt-4 md:px-6\">\n <header className=\"flex min-w-0 flex-wrap items-center gap-3\">\n <h1 className=\"flex-1 text-lg font-semibold\">Attention</h1>\n <SegmentedControl\n label=\"Show\"\n className=\"max-sm:w-full max-sm:basis-full\"\n value={show}\n onChange={setShow}\n options={[\n { value: \"open\", label: \"Open\" },\n { value: \"dismissed\", label: \"Dismissed\" },\n { value: \"history\", label: \"History\" },\n ]}\n />\n </header>\n <LastKnownNote\n subject=\"attention items\"\n consequence=\"Answers are sent once the connection is back.\"\n show={hydrated}\n />\n {show === \"history\" ? (\n <div data-testid=\"attention-history\" className=\"flex min-w-0 flex-col gap-2\">\n {!history.data && history.loading ? (\n <LoadingState label=\"Loading history…\" />\n ) : !history.data && history.error ? (\n <ErrorState\n compact\n title=\"Could not load history\"\n error={errorMessage(history.error)}\n onRetry={history.reload}\n />\n ) : !history.data || history.data.length === 0 ? (\n <EmptyState\n icon={<History className=\"size-8\" />}\n title=\"No history yet\"\n description=\"Resolved questions, failures and reviews show up here.\"\n />\n ) : (\n <>\n <ul className=\"flex min-w-0 flex-col gap-2\">\n {history.data.map((a) => (\n <li key={a.id} className=\"min-w-0\">\n <HistoryRow item={a} />\n </li>\n ))}\n </ul>\n {history.data.length >= HISTORY_LIMIT && (\n <p className=\"text-sm text-muted\">Showing the {HISTORY_LIMIT} most recent.</p>\n )}\n </>\n )}\n </div>\n ) : !hydrated ? (\n <LoadingState label=\"Loading attention…\" />\n ) : items.length === 0 ? (\n show === \"dismissed\" ? (\n <EmptyState\n icon={<Bell className=\"size-8\" />}\n title=\"No dismissed items\"\n description=\"Items you dismiss stay here until their question or task is resolved.\"\n action={{ label: \"Show open\", onClick: () => setShow(\"open\") }}\n />\n ) : (\n <EmptyState\n icon={<Bell className=\"size-8\" />}\n title={dismissedCount > 0 ? \"All caught up\" : \"Nothing needs your attention\"}\n description={\n dismissedCount > 0\n ? `${dismissedCount} dismissed ${dismissedCount === 1 ? \"item is\" : \"items are\"} still open.`\n : \"Questions, failures and finished work show up here.\"\n }\n action={\n dismissedCount > 0\n ? { label: \"Show dismissed\", onClick: () => setShow(\"dismissed\") }\n : { label: \"Go to Sessions\", to: ROUTES.code }\n }\n secondaryAction={\n dismissedCount > 0\n ? { label: \"Go to Sessions\", to: ROUTES.code }\n : { label: \"Go to Board\", to: ROUTES.board }\n }\n />\n )\n ) : (\n ATTENTION_GROUPS.filter((r) => groups[r].length > 0).map((reason) => {\n const info = ATTENTION_STATUS[reason];\n const Icon = info.icon;\n return (\n <section\n key={reason}\n data-testid={`attention-group-${reason}`}\n aria-labelledby={`att-${reason}`}\n className=\"flex min-w-0 flex-col gap-2\"\n >\n <div>\n <h2\n id={`att-${reason}`}\n className=\"flex items-center gap-2 text-base font-semibold\"\n >\n <Icon aria-hidden className=\"size-4 text-muted\" />\n {info.label}\n <span className=\"text-sm font-normal text-muted\">({groups[reason].length})</span>\n </h2>\n <p className=\"text-sm text-muted\">{GROUP_HINT[reason]}</p>\n </div>\n <ul className=\"flex min-w-0 flex-col gap-2\">\n {groups[reason].map((a) => (\n <li key={a.id} className=\"min-w-0\">\n <AttentionRow item={a} />\n </li>\n ))}\n </ul>\n </section>\n );\n })\n )}\n </div>\n );\n}\n\nfunction AttentionRow({ item }: { item: AttentionItem }) {\n const project = useSyncStore((s) => (item.projectId ? s.projects[item.projectId] : undefined));\n const task = useSyncStore((s) => (item.taskId ? s.tasks[item.taskId] : undefined));\n const session = useSyncStore((s) => (item.sessionId ? s.sessions[item.sessionId] : undefined));\n const host = useSyncStore((s) => (session ? s.hosts[session.hostId] : undefined));\n const interaction = useSyncStore((s) =>\n item.interactionId ? s.interactions[item.interactionId] : undefined,\n );\n const [busy, setBusy] = useState(false);\n const [ackError, setAckError] = useState<unknown>(null);\n\n const acknowledge = async () => {\n setBusy(true);\n setAckError(null);\n try {\n const res = await apiPost<AttentionItem>(\n `/api/attention/${encodeURIComponent(item.id)}/acknowledge`,\n );\n const next =\n res && typeof res === \"object\" && \"id\" in res\n ? res\n : { ...item, acknowledgedAt: new Date().toISOString() };\n useSyncStore.setState((s) =>\n s.attention[item.id] ? { attention: { ...s.attention, [item.id]: next } } : {},\n );\n toast.success(\"Dismissed. It stays under All until the task or question is resolved.\");\n } catch (e) {\n setAckError(e);\n } finally {\n setBusy(false);\n }\n };\n\n const sessionHref = item.sessionId\n ? item.interactionId\n ? ROUTES.interaction(item.sessionId, item.interactionId)\n : ROUTES.session(item.sessionId)\n : null;\n const hostOffline = !!host && host.connectivity !== \"connected\";\n\n let action: React.ReactNode = null;\n switch (item.reason) {\n case \"needs_input\":\n action =\n interaction && interaction.state === \"pending\" ? (\n <InteractionCard\n flush\n interaction={interaction}\n readOnlyReason={\n hostOffline\n ? `Host ${host?.name} is offline. You can answer once it reconnects.`\n : session?.readOnly\n ? \"This session is read-only.\"\n : null\n }\n />\n ) : interaction ? (\n <InteractionCard flush interaction={interaction} />\n ) : sessionHref ? (\n <Link to={sessionHref} className={buttonClasses(\"primary\", \"sm\")}>\n <ExternalLink aria-hidden className=\"size-3.5\" />\n Answer in session\n </Link>\n ) : null;\n break;\n case \"failed\":\n action = item.taskId ? (\n <Link to={ROUTES.task(item.taskId)} className={buttonClasses(\"primary\", \"sm\")}>\n <ListTodo aria-hidden className=\"size-3.5\" />\n Open task\n </Link>\n ) : null;\n break;\n case \"ready_for_review\":\n action = item.sessionId ? (\n <Link\n to={`${ROUTES.session(item.sessionId)}?tab=changes`}\n className={buttonClasses(\"primary\", \"sm\")}\n >\n <Eye aria-hidden className=\"size-3.5\" />\n Review changes\n </Link>\n ) : item.taskId ? (\n <Link to={ROUTES.task(item.taskId)} className={buttonClasses(\"primary\", \"sm\")}>\n <Eye aria-hidden className=\"size-3.5\" />\n Open review\n </Link>\n ) : null;\n break;\n case \"unsupported_interaction\":\n action = interaction ? (\n <InteractionCard\n interaction={interaction}\n readOnlyReason={\n hostOffline\n ? `Host ${host?.name} is offline. You can choose once it reconnects.`\n : session?.readOnly\n ? \"This session is read-only.\"\n : null\n }\n />\n ) : (\n <UnsupportedCapability\n capability=\"This dialog\"\n reason=\"An extension opened a terminal-only dialog. In RPC mode Pi gave it no answer, so the extension continued without one.\"\n />\n );\n break;\n case \"host_unreachable\":\n action = host ? (\n <HostUnavailable\n compact\n hostName={host.name}\n connectivity={host.connectivity === \"reconnecting\" ? \"reconnecting\" : \"unreachable\"}\n lastSeenAt={host.lastSeenAt}\n />\n ) : null;\n break;\n }\n\n const metaText = attentionMeta(project, task, session);\n\n const showsCard =\n !!interaction && (item.reason === \"needs_input\" || item.reason === \"unsupported_interaction\");\n\n // A plan awaiting approval gets the whole width: one meta line, no card around it.\n if (\n item.reason === \"needs_input\" &&\n interaction?.state === \"pending\" &&\n isDocumentPresentation(interaction.presentation)\n ) {\n return (\n <article\n aria-label={`Plan awaiting approval: ${metaText}`}\n className=\"flex min-w-0 flex-col gap-2\"\n >\n <div className=\"flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-sm text-muted\">\n <span className=\"min-w-0 basis-full truncate sm:basis-auto sm:flex-1\">{metaText}</span>\n {sessionHref && (\n <Link to={sessionHref} className={buttonClasses(\"ghost\", \"sm\")}>\n <ExternalLink aria-hidden className=\"size-3.5\" />\n Open session\n </Link>\n )}\n <time dateTime={item.createdAt} className=\"shrink-0 text-xs text-subtle sm:ml-auto\">\n {formatRelative(item.createdAt)}\n </time>\n {!item.acknowledgedAt && (\n <Button size=\"sm\" variant=\"ghost\" busy={busy} onClick={() => void acknowledge()}>\n Dismiss\n </Button>\n )}\n </div>\n {action}\n {ackError !== null && (\n <ErrorState\n compact\n title=\"Could not dismiss\"\n error={errorMessage(ackError)}\n onRetry={() => void acknowledge()}\n />\n )}\n </article>\n );\n }\n\n return (\n <article\n aria-label={`${ATTENTION_STATUS[item.reason].label}: ${task?.title ?? session?.title ?? item.summary}`}\n className=\"flex min-w-0 flex-col gap-2 rounded-lg border border-border bg-surface p-3\"\n >\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n <StatusChip kind=\"attention\" value={item.reason} size=\"sm\" />\n <span className=\"min-w-0 flex-1 truncate text-xs text-muted\">{metaText}</span>\n <time dateTime={item.createdAt} className=\"shrink-0 text-xs text-subtle\">\n {formatRelative(item.createdAt)}\n </time>\n </div>\n {!showsCard && <p className=\"min-w-0 text-sm break-words\">{item.summary}</p>}\n {action && <div className=\"flex min-w-0 flex-col items-start\">{action}</div>}\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n {item.reason !== \"failed\" && item.taskId && (\n <Link to={ROUTES.task(item.taskId)} className={buttonClasses(\"ghost\", \"sm\")}>\n <ListTodo aria-hidden className=\"size-3.5\" />\n Task\n </Link>\n )}\n {sessionHref && (\n <Link to={sessionHref} className={buttonClasses(\"ghost\", \"sm\")}>\n <ExternalLink aria-hidden className=\"size-3.5\" />\n Open session\n </Link>\n )}\n {item.reason === \"host_unreachable\" && host && (\n <Link\n to={`${ROUTES.settings(\"hosts\")}/${encodeURIComponent(host.id)}`}\n className={buttonClasses(\"ghost\", \"sm\")}\n >\n <Server aria-hidden className=\"size-3.5\" />\n Host details\n </Link>\n )}\n <span className=\"ml-auto\" />\n {item.acknowledgedAt ? (\n <span className=\"inline-flex items-center gap-1 text-xs text-muted\">\n <CircleCheck aria-hidden className=\"size-3.5\" />\n Dismissed {formatRelative(item.acknowledgedAt)}\n </span>\n ) : (\n <Button size=\"sm\" busy={busy} onClick={() => void acknowledge()}>\n Dismiss\n </Button>\n )}\n </div>\n {ackError !== null && (\n <ErrorState\n compact\n title=\"Could not dismiss\"\n error={errorMessage(ackError)}\n onRetry={() => void acknowledge()}\n />\n )}\n </article>\n );\n}\n\nfunction HistoryRow({ item }: { item: AttentionItem }) {\n const project = useSyncStore((s) => (item.projectId ? s.projects[item.projectId] : undefined));\n const task = useSyncStore((s) => (item.taskId ? s.tasks[item.taskId] : undefined));\n const session = useSyncStore((s) => (item.sessionId ? s.sessions[item.sessionId] : undefined));\n return (\n <article\n aria-label={`${ATTENTION_STATUS[item.reason].label}: ${task?.title ?? session?.title ?? item.summary}`}\n className=\"flex min-w-0 flex-col gap-2 rounded-lg border border-border bg-surface p-3\"\n >\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n <StatusChip kind=\"attention\" value={item.reason} size=\"sm\" />\n <span className=\"min-w-0 flex-1 truncate text-xs text-muted\">\n {attentionMeta(project, task, session)}\n </span>\n {item.resolvedAt && (\n <time dateTime={item.resolvedAt} className=\"shrink-0 text-xs text-subtle\">\n Resolved {formatRelative(item.resolvedAt)}\n </time>\n )}\n </div>\n <p className=\"min-w-0 text-sm break-words\">{item.summary}</p>\n {(item.taskId || item.sessionId) && (\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n {item.taskId && (\n <Link to={ROUTES.task(item.taskId)} className={buttonClasses(\"ghost\", \"sm\")}>\n <ListTodo aria-hidden className=\"size-3.5\" />\n Task\n </Link>\n )}\n {item.sessionId && (\n <Link to={ROUTES.session(item.sessionId)} className={buttonClasses(\"ghost\", \"sm\")}>\n <ExternalLink aria-hidden className=\"size-3.5\" />\n Open session\n </Link>\n )}\n </div>\n )}\n </article>\n );\n}\n"],"ignoreList":[0],"mappings":"A,oiBASA,IAAMuC,EAAa,CACjB,KAAM,YACN,KAAM,GACN,KAAM,CACJ,CAAC,OAAQ,CAAE,EAAG,UAAW,IAAK,QAAS,CAAC,EACxC,CAAC,OAAQ,CAAE,EAAG,WAAY,IAAK,QAAS,CAAC,EACzC,CAAC,OAAQ,CAAE,EAAG,WAAY,IAAK,QAAS,CAAC,EACzC,CAAC,OAAQ,CAAE,EAAG,gBAAiB,IAAK,QAAS,CAAC,EAC9C,CAAC,OAAQ,CAAE,EAAG,IAAK,EAAG,IAAK,MAAO,IAAK,OAAQ,IAAK,GAAI,IAAK,IAAK,QAAS,CAAC,CAC9E,CACF,EACAA,EAAW,KACX,IAAMC,EAAWhC,EAAiB+B,CAAU,E,WCnB/BG,EAAsC,CACjD,cACA,SACA,mBACA,0BACA,kBACF,EAGA,SAAgBC,EAAe,EAAkE,CAC/F,IAAMC,EAAM,OAAO,YAAYF,EAAiB,IAAK,GAAM,CAACG,EAAG,CAAC,CAAoB,CAAC,CAAC,EAItF,IAAK,IAAMC,KAAKC,EAAOH,EAAIE,EAAE,OAAO,EAAE,KAAKA,CAAC,EAC5C,OAAOF,CACT,CAKA,SAAgBI,EAAc,EAA2B,CACvD,OAAOC,IAAM,aAAeA,IAAM,UAAYA,EAAI,MACpD,CAGA,SAAgBC,EAAiB,EAAsB,EAAsC,CAE3F,OADIC,IAAS,UAAkB,CAAC,EACzBC,EAAI,OAAQ,GAAOD,IAAS,OAAS,CAACL,EAAE,eAAiB,CAAC,CAACA,EAAE,cAAe,CACrF,CAGA,SAAgBO,EAAc,EAAmB,EAAa,EAA2B,CACvF,MACE,CAACC,GAAS,KAAMC,GAAM,MAAOC,GAAWA,EAAQ,QAAUD,GAAM,MAAQC,EAAQ,MAAQ,IAAI,CAAC,CAC1F,OAAO,OAAO,CAAC,CACf,KAAK,KAAK,GAAK,YAEtB,CC5BA,SAAgBC,EAAoB,EAA6C,CAC/E,IAAMC,EAAMpB,EACVqB,EAAU,oBAAsB,KAC/B,GACC7C,EACE,iBACA,CAAE,aAAc,OAAQ,MAAA,GAAqB,EAC7C8C,CACF,EACF,CAAE,UAAW,IAAQ,MAAO,MAAM,OAAQ,CAC5C,EACMC,EAAYrC,EAAc,GAAMsC,EAAE,SAAS,EAC3CC,GAAA,EAAQ/D,EAAAA,OAAAA,CAAO,EAAI,EACnB,CAAE,UAAW0D,EAWnB,OATA,EAAAjB,EAAA,UAAA,KAAgB,CACd,GAAIsB,EAAM,QAAS,CACjB,EAAM,QAAU,GAChB,MACF,CACA,GAAI,CAACJ,EAAS,OACd,IAAM,EAAI,WAAWK,EAAQ,GAAG,EAChC,UAAa,aAAa,CAAC,CAC7B,EAAG,CAACH,EAAWF,EAASK,CAAM,CAAC,EACxBN,CACT,C,UCLMQ,EAA8C,CAClD,YAAa,6CACb,OAAQ,8DACR,iBAAkB,yCAClB,wBACE,2FACF,iBACE,gFACJ,EAGA,SAAwBC,GAAgB,CACtC,IAAMf,EAAMnC,EAAiB,EACvBmD,EAAW5C,EAAc,GAAMsC,EAAE,QAAQ,EACzC,CAACO,EAAQC,GAAanC,EAAiC,gBAAiB,MAAM,EAC9EgB,EAAOH,EAAcqB,CAAM,EAC3BE,EAAW,GAAqBD,EAAUrB,CAAC,EAC3CF,GAAA,EAAQ9C,EAAAA,QAAAA,KAAciD,EAAiBE,EAAKD,CAAI,EAAG,CAACC,EAAKD,CAAI,CAAC,EAC9DqB,GAAA,EAAiBvE,EAAAA,QAAAA,KAAciD,EAAiBE,EAAK,WAAW,CAAC,CAAC,OAAQ,CAACA,CAAG,CAAC,EAC/EqB,GAAA,EAASxE,EAAAA,QAAAA,KAAc0C,EAAeI,CAAK,EAAG,CAACA,CAAK,CAAC,EACrD2B,EAAUjB,EAAoBN,IAAS,SAAS,EAEtD,OACE,EAAAc,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,iEAAf,SAAA,EACE,EAAAA,EAAA,KAAA,CAAC,SAAD,CAAQ,UAAU,4CAAlB,SAAA,EACE,EAAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAU,+BAA+B,SAAA,WAAa,CAAA,GAC1D,EAAAA,EAAA,IAAA,CAAC5B,EAAD,CACE,MAAM,OACN,UAAU,kCACV,MAAOc,EACP,SAAUoB,EACV,QAAS,CACP,CAAE,MAAO,OAAQ,MAAO,MAAO,EAC/B,CAAE,MAAO,YAAa,MAAO,WAAY,EACzC,CAAE,MAAO,UAAW,MAAO,SAAU,CACvC,CACD,CAAA,CACK,C,IACR,EAAAN,EAAA,IAAA,CAAC9C,EAAD,CACE,QAAQ,kBACR,YAAY,gDACZ,KAAMiD,CACP,CAAA,EACAjB,IAAS,WACR,EAAAc,EAAA,IAAA,CAAC,MAAD,CAAK,cAAY,oBAAoB,UAAU,8BAC5C,SAAA,CAACS,EAAQ,MAAQA,EAAQ,SACxB,EAAAT,EAAA,IAAA,CAACtD,EAAD,CAAc,MAAM,kBAAoB,CAAA,EACtC,CAAC+D,EAAQ,MAAQA,EAAQ,OAC3B,EAAAT,EAAA,IAAA,CAAC/C,EAAD,CACE,QAAA,GACA,MAAM,yBACN,MAAOK,EAAamD,EAAQ,KAAK,EACjC,QAASA,EAAQ,MAClB,CAAA,EACC,CAACA,EAAQ,MAAQA,EAAQ,KAAK,SAAW,GAC3C,EAAAT,EAAA,IAAA,CAAC7C,EAAD,CACE,MAAM,EAAA6C,EAAA,IAAA,CAAC/D,EAAD,CAAS,UAAU,QAAU,CAAA,EACnC,MAAM,iBACN,YAAY,wDACb,CAAA,GAED,EAAA+D,EAAA,KAAA,CAAA9D,EAAAA,SAAA,CAAA,SAAA,EACE,EAAA8D,EAAA,IAAA,CAAC,KAAD,CAAI,UAAU,8BACX,SAAAS,EAAQ,KAAK,IAAK,IACjB,EAAAT,EAAA,IAAA,CAAC,KAAD,CAAe,UAAU,UACvB,UAAA,EAAAA,EAAA,IAAA,CAACU,EAAD,CAAY,KAAM7B,CAAI,CAAA,CACpB,EAFKA,EAAE,EAEP,CACL,CACC,CAAA,EACH4B,EAAQ,KAAK,QAAA,MACZ,EAAAT,EAAA,KAAA,CAAC,IAAD,CAAG,UAAU,qBAAb,SAAA,CAAkC,e,IAA2B,eAAgB,CAE/E,CAAA,CAAA,CAAA,CAAA,CAED,CAAA,EACFG,EAEDrB,EAAM,SAAW,EACnBI,IAAS,aACP,EAAAc,EAAA,IAAA,CAAC7C,EAAD,CACE,MAAM,EAAA6C,EAAA,IAAA,CAACjC,EAAD,CAAM,UAAU,QAAU,CAAA,EAChC,MAAM,qBACN,YAAY,wEACZ,OAAQ,CAAE,MAAO,YAAa,YAAeuC,EAAQ,MAAM,CAAE,CAC9D,CAAA,GAED,EAAAN,EAAA,IAAA,CAAC7C,EAAD,CACE,MAAM,EAAA6C,EAAA,IAAA,CAACjC,EAAD,CAAM,UAAU,QAAU,CAAA,EAChC,MAAOwC,EAAiB,EAAI,gBAAkB,+BAC9C,YACEA,EAAiB,EACb,GAAGA,EAAe,aAAaA,IAAmB,EAAI,UAAY,YAAY,cAC9E,sDAEN,OACEA,EAAiB,EACb,CAAE,MAAO,iBAAkB,YAAeD,EAAQ,WAAW,CAAE,EAC/D,CAAE,MAAO,iBAAkB,GAAIrC,EAAO,IAAK,EAEjD,gBACEsC,EAAiB,EACb,CAAE,MAAO,iBAAkB,GAAItC,EAAO,IAAK,EAC3C,CAAE,MAAO,cAAe,GAAIA,EAAO,KAAM,CAEhD,CAAA,EAGHQ,EAAiB,OAAQ,GAAM+B,EAAO5B,EAAE,CAAC,OAAS,CAAC,CAAC,CAAC,IAAK,GAAW,CACnE,IAAM+B,EAAOxC,EAAiByC,GACxBC,EAAOF,EAAK,KAClB,OACE,EAAAX,EAAA,KAAA,CAAC,UAAD,CAEE,cAAa,mBAAmBY,IAChC,kBAAiB,OAAOA,IACxB,UAAU,8BAJZ,SAAA,EAME,EAAAZ,EAAA,KAAA,CAAC,MAAD,CAAA,SAAA,EACE,EAAAA,EAAA,KAAA,CAAC,KAAD,CACE,GAAI,OAAOY,IACX,UAAU,kDAFZ,SAAA,EAIE,EAAAZ,EAAA,IAAA,CAACa,EAAD,CAAM,cAAA,GAAY,UAAU,mBAAqB,CAAA,EAChDF,EAAK,OACN,EAAAX,EAAA,KAAA,CAAC,OAAD,CAAM,UAAU,iCAAhB,SAAA,CAAiD,IAAEQ,EAAOI,EAAO,CAAC,OAAO,GAAO,C,EAC9E,CACJ,CAAA,GAAA,EAAAZ,EAAA,IAAA,CAAC,IAAD,CAAG,UAAU,qBAAsB,SAAAC,EAAWW,EAAW,CAAA,CACtD,CAAA,CAAA,GACL,EAAAZ,EAAA,IAAA,CAAC,KAAD,CAAI,UAAU,8BACX,SAAAQ,EAAOI,EAAO,CAAC,IAAK,IACnB,EAAAZ,EAAA,IAAA,CAAC,KAAD,CAAe,UAAU,UACvB,UAAA,EAAAA,EAAA,IAAA,CAACc,EAAD,CAAc,KAAMjC,CAAI,CAAA,CACtB,EAFKA,EAAE,EAEP,CACL,CACC,CAAA,CACG,CAvBF,EAAA+B,CAuBE,CAEb,CAAC,GA7DD,EAAAZ,EAAA,IAAA,CAACtD,EAAD,CAAc,MAAM,oBAAsB,CAAA,CA+DzC,C,EAET,CAEA,SAASoE,EAAa,CAAE,QAAiC,CACvD,IAAMzB,EAAU9B,EAAc,GAAOwD,EAAK,UAAYlB,EAAE,SAASkB,EAAK,WAAa,IAAA,EAAU,EACvFzB,EAAO/B,EAAc,GAAOwD,EAAK,OAASlB,EAAE,MAAMkB,EAAK,QAAU,IAAA,EAAU,EAC3ExB,EAAUhC,EAAc,GAAOwD,EAAK,UAAYlB,EAAE,SAASkB,EAAK,WAAa,IAAA,EAAU,EACvFC,EAAOzD,EAAc,GAAOgC,EAAUM,EAAE,MAAMN,EAAQ,QAAU,IAAA,EAAU,EAC1E0B,EAAc1D,EAAc,GAChCwD,EAAK,cAAgBlB,EAAE,aAAakB,EAAK,eAAiB,IAAA,EAC5D,EACM,CAACG,EAAMC,IAAA,EAAWhF,EAAAA,SAAAA,CAAS,EAAK,EAChC,CAACiF,EAAUC,IAAA,EAAelF,EAAAA,SAAAA,CAAkB,IAAI,EAEhDmF,EAAc,SAAY,CAC9BH,EAAQ,EAAI,EACZE,EAAY,IAAI,EAChB,GAAI,CACF,IAAM5B,EAAM,MAAMpC,EAChB,kBAAkB,mBAAmB0D,EAAK,EAAE,EAAE,aAChD,EACMQ,EACJ9B,GAAO,OAAOA,GAAQ,UAAY,OAAQA,EACtCA,EACA,CAAE,GAAGsB,EAAM,eAAgB,IAAI,KAAK,CAAA,CAAE,YAAY,CAAE,EAC1DxD,EAAa,SAAU,GACrBsC,EAAE,UAAUkB,EAAK,IAAM,CAAE,UAAW,CAAE,GAAGlB,EAAE,WAAYkB,EAAK,IAAKQ,CAAK,CAAE,EAAI,CAAC,CAC/E,EACAvD,EAAM,QAAQ,uEAAuE,CACvF,OAAS,EAAG,CACVqD,EAAY,CAAC,CACf,QAAU,CACRF,EAAQ,EAAK,CACf,CACF,EAEMK,EAAcT,EAAK,UACrBA,EAAK,cACH9C,EAAO,YAAY8C,EAAK,UAAWA,EAAK,aAAa,EACrD9C,EAAO,QAAQ8C,EAAK,SAAS,EAC/B,KACEU,EAAc,CAAC,CAACT,GAAQA,EAAK,eAAiB,YAEhDU,EAA0B,KAC9B,OAAQX,EAAK,OAAb,CACE,IAAK,cACH,EACEE,GAAeA,EAAY,QAAU,WACnC,EAAAjB,EAAA,IAAA,CAACtC,EAAD,CACE,MAAA,GACa,cACb,eACE+D,EACI,QAAQT,GAAM,KAAK,iDACnBzB,GAAS,SACP,6BACA,IAET,CAAA,EACC0B,GACF,EAAAjB,EAAA,IAAA,CAACtC,EAAD,CAAiB,MAAA,GAAmB,aAAc,CAAA,EAChD8D,GACF,EAAAxB,EAAA,KAAA,CAAC3D,EAAD,CAAM,GAAImF,EAAa,UAAW5E,EAAc,UAAW,IAAI,EAA/D,SAAA,EACE,EAAAoD,EAAA,IAAA,CAACxC,EAAD,CAAc,cAAA,GAAY,UAAU,UAAY,CAAA,EAAC,mBAE7C,CACJ,CAAA,EAAA,KACN,MACF,IAAK,SACH,EAASuD,EAAK,QACZ,EAAAf,EAAA,KAAA,CAAC3D,EAAD,CAAM,GAAI4B,EAAO,KAAK8C,EAAK,MAAM,EAAG,UAAWnE,EAAc,UAAW,IAAI,EAA5E,SAAA,EACE,EAAAoD,EAAA,IAAA,CAACzB,EAAD,CAAU,cAAA,GAAY,UAAU,UAAY,CAAA,EAAC,WAEzC,CACJ,CAAA,EAAA,KACJ,MACF,IAAK,mBACH,EAASwC,EAAK,WACZ,EAAAf,EAAA,KAAA,CAAC3D,EAAD,CACE,GAAI,GAAG4B,EAAO,QAAQ8C,EAAK,SAAS,EAAE,cACtC,UAAWnE,EAAc,UAAW,IAAI,EAF1C,SAAA,EAIE,EAAAoD,EAAA,IAAA,CAACnC,EAAD,CAAK,cAAA,GAAY,UAAU,UAAY,CAAA,EAAC,gBAEpC,CACJ,CAAA,EAAAkD,EAAK,QACP,EAAAf,EAAA,KAAA,CAAC3D,EAAD,CAAM,GAAI4B,EAAO,KAAK8C,EAAK,MAAM,EAAG,UAAWnE,EAAc,UAAW,IAAI,EAA5E,SAAA,EACE,EAAAoD,EAAA,IAAA,CAACnC,EAAD,CAAK,cAAA,GAAY,UAAU,UAAY,CAAA,EAAC,aAEpC,CACJ,CAAA,EAAA,KACJ,MACF,IAAK,0BACH,EAASoD,GACP,EAAAjB,EAAA,IAAA,CAACtC,EAAD,CACe,cACb,eACE+D,EACI,QAAQT,GAAM,KAAK,iDACnBzB,GAAS,SACP,6BACA,IAET,CAAA,GAED,EAAAS,EAAA,IAAA,CAAC5C,EAAD,CACE,WAAW,cACX,OAAO,uHACR,CAAA,EAEH,MACF,IAAK,mBACH,EAAS4D,GACP,EAAAhB,EAAA,IAAA,CAACjD,EAAD,CACE,QAAA,GACA,SAAUiE,EAAK,KACf,aAAcA,EAAK,eAAiB,eAAiB,eAAiB,cACtE,WAAYA,EAAK,UAClB,CAAA,EACC,IAER,CAEA,IAAMW,EAAWvC,EAAcC,EAASC,EAAMC,CAAO,EAE/CqC,EACJ,CAAC,CAACX,IAAgBF,EAAK,SAAW,eAAiBA,EAAK,SAAW,2BA2CrE,OAvCEA,EAAK,SAAW,eAChBE,GAAa,QAAU,WACvBxD,EAAuBwD,EAAY,YAAY,GAG7C,EAAAjB,EAAA,KAAA,CAAC,UAAD,CACE,aAAY,2BAA2B2B,IACvC,UAAU,8BAFZ,SAAA,EAIE,EAAA3B,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,yEAAf,SAAA,EACE,EAAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAU,sDAAuD,SAAA2B,CAAe,CAAA,EACrFH,IACC,EAAAxB,EAAA,KAAA,CAAC3D,EAAD,CAAM,GAAImF,EAAa,UAAW5E,EAAc,QAAS,IAAI,EAA7D,SAAA,EACE,EAAAoD,EAAA,IAAA,CAACxC,EAAD,CAAc,cAAA,GAAY,UAAU,UAAY,CAAA,EAAC,cAE7C,C,IAER,EAAAwC,EAAA,IAAA,CAAC,OAAD,CAAM,SAAUe,EAAK,UAAW,UAAU,0CACvC,SAAAjE,EAAeiE,EAAK,SAAS,CAC1B,CAAA,EACL,CAACA,EAAK,iBACL,EAAAf,EAAA,IAAA,CAACrD,EAAD,CAAQ,KAAK,KAAK,QAAQ,QAAc,OAAM,YAAe,KAAK2E,EAAY,EAAG,SAAA,SAEzE,CAAA,CAEP,C,GACJI,EACAN,IAAa,OACZ,EAAApB,EAAA,IAAA,CAAC/C,EAAD,CACE,QAAA,GACA,MAAM,oBACN,MAAOK,EAAa8D,CAAQ,EAC5B,YAAe,KAAKE,EAAY,CACjC,CAAA,CAEI,C,IAKX,EAAAtB,EAAA,KAAA,CAAC,UAAD,CACE,aAAY,GAAG7B,EAAiB4C,EAAK,OAAO,CAAC,MAAM,IAAIzB,GAAM,OAASC,GAAS,OAASwB,EAAK,UAC7F,UAAU,6EAFZ,SAAA,EAIE,EAAAf,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,4CAAf,SAAA,EACE,EAAAA,EAAA,IAAA,CAACpC,EAAD,CAAY,KAAK,YAAY,MAAOmD,EAAK,OAAQ,KAAK,IAAM,CAAA,GAC5D,EAAAf,EAAA,IAAA,CAAC,OAAD,CAAM,UAAU,6CAA8C,SAAA2B,CAAe,CAAA,GAC7E,EAAA3B,EAAA,IAAA,CAAC,OAAD,CAAM,SAAUe,EAAK,UAAW,UAAU,+BACvC,SAAAjE,EAAeiE,EAAK,SAAS,CAC1B,CAAA,CACH,C,GACJ,CAACa,IAAa,EAAA5B,EAAA,IAAA,CAAC,IAAD,CAAG,UAAU,8BAA+B,SAAAe,EAAK,OAAW,CAAA,EAC1EW,IAAU,EAAA1B,EAAA,IAAA,CAAC,MAAD,CAAK,UAAU,oCAAqC,SAAA0B,CAAY,CAAA,GAC3E,EAAA1B,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,4CAAf,SAAA,CACGe,EAAK,SAAW,UAAYA,EAAK,SAChC,EAAAf,EAAA,KAAA,CAAC3D,EAAD,CAAM,GAAI4B,EAAO,KAAK8C,EAAK,MAAM,EAAG,UAAWnE,EAAc,QAAS,IAAI,EAA1E,SAAA,EACE,EAAAoD,EAAA,IAAA,CAACzB,EAAD,CAAU,cAAA,GAAY,UAAU,UAAY,CAAA,EAAC,MAEzC,C,GAEPiD,IACC,EAAAxB,EAAA,KAAA,CAAC3D,EAAD,CAAM,GAAImF,EAAa,UAAW5E,EAAc,QAAS,IAAI,EAA7D,SAAA,EACE,EAAAoD,EAAA,IAAA,CAACxC,EAAD,CAAc,cAAA,GAAY,UAAU,UAAY,CAAA,EAAC,cAE7C,C,GAEPuD,EAAK,SAAW,oBAAsBC,IACrC,EAAAhB,EAAA,KAAA,CAAC3D,EAAD,CACE,GAAI,GAAG4B,EAAO,SAAS,OAAO,EAAE,GAAG,mBAAmB+C,EAAK,EAAE,IAC7D,UAAWpE,EAAc,QAAS,IAAI,EAFxC,SAAA,EAIE,EAAAoD,EAAA,IAAA,CAACrC,EAAD,CAAQ,cAAA,GAAY,UAAU,UAAY,CAAA,EAAC,cAEvC,C,IAER,EAAAqC,EAAA,IAAA,CAAC,OAAD,CAAM,UAAU,SAAW,CAAA,EAC1Be,EAAK,gBACJ,EAAAf,EAAA,KAAA,CAAC,OAAD,CAAM,UAAU,oDAAhB,SAAA,EACE,EAAAA,EAAA,IAAA,CAAClC,EAAD,CAAa,cAAA,GAAY,UAAU,UAAY,CAAA,EAAC,aACrChB,EAAeiE,EAAK,cAAc,CACzC,CAEN,CAAA,GAAA,EAAAf,EAAA,IAAA,CAACrD,EAAD,CAAQ,KAAK,KAAW,OAAM,YAAe,KAAK2E,EAAY,EAAG,SAAA,SAEzD,CAAA,CAEP,C,GACJF,IAAa,OACZ,EAAApB,EAAA,IAAA,CAAC/C,EAAD,CACE,QAAA,GACA,MAAM,oBACN,MAAOK,EAAa8D,CAAQ,EAC5B,YAAe,KAAKE,EAAY,CACjC,CAAA,CAEI,C,EAEb,CAEA,SAASZ,EAAW,CAAE,QAAiC,CACrD,IAAMrB,EAAU9B,EAAc,GAAOwD,EAAK,UAAYlB,EAAE,SAASkB,EAAK,WAAa,IAAA,EAAU,EACvFzB,EAAO/B,EAAc,GAAOwD,EAAK,OAASlB,EAAE,MAAMkB,EAAK,QAAU,IAAA,EAAU,EAC3ExB,EAAUhC,EAAc,GAAOwD,EAAK,UAAYlB,EAAE,SAASkB,EAAK,WAAa,IAAA,EAAU,EAC7F,OACE,EAAAf,EAAA,KAAA,CAAC,UAAD,CACE,aAAY,GAAG7B,EAAiB4C,EAAK,OAAO,CAAC,MAAM,IAAIzB,GAAM,OAASC,GAAS,OAASwB,EAAK,UAC7F,UAAU,6EAFZ,SAAA,EAIE,EAAAf,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,4CAAf,SAAA,EACE,EAAAA,EAAA,IAAA,CAACpC,EAAD,CAAY,KAAK,YAAY,MAAOmD,EAAK,OAAQ,KAAK,IAAM,CAAA,GAC5D,EAAAf,EAAA,IAAA,CAAC,OAAD,CAAM,UAAU,6CACb,SAAAZ,EAAcC,EAASC,EAAMC,CAAO,CACjC,CAAA,EACLwB,EAAK,aACJ,EAAAf,EAAA,KAAA,CAAC,OAAD,CAAM,SAAUe,EAAK,WAAY,UAAU,+BAA3C,SAAA,CAA0E,YAC9DjE,EAAeiE,EAAK,UAAU,CACpC,C,EAEL,C,IACL,EAAAf,EAAA,IAAA,CAAC,IAAD,CAAG,UAAU,8BAA+B,SAAAe,EAAK,OAAW,CAAA,GAC1DA,EAAK,QAAUA,EAAK,aACpB,EAAAf,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,4CAAf,SAAA,CACGe,EAAK,SACJ,EAAAf,EAAA,KAAA,CAAC3D,EAAD,CAAM,GAAI4B,EAAO,KAAK8C,EAAK,MAAM,EAAG,UAAWnE,EAAc,QAAS,IAAI,EAA1E,SAAA,EACE,EAAAoD,EAAA,IAAA,CAACzB,EAAD,CAAU,cAAA,GAAY,UAAU,UAAY,CAAA,EAAC,MAEzC,CAEP,CAAA,EAAAwC,EAAK,YACJ,EAAAf,EAAA,KAAA,CAAC3D,EAAD,CAAM,GAAI4B,EAAO,QAAQ8C,EAAK,SAAS,EAAG,UAAWnE,EAAc,QAAS,IAAI,EAAhF,SAAA,EACE,EAAAoD,EAAA,IAAA,CAACxC,EAAD,CAAc,cAAA,GAAY,UAAU,UAAY,CAAA,EAAC,cAE7C,CAEL,CAAA,CAAA,C,EAEA,C,EAEb,C"}
|
|
1
|
+
{"version":3,"file":"attention-CI4dHY81.js","names":["useRef","useMemo","History","_Fragment","useState","__toESM","Link","require_react","createLucideIcon","require_jsx_runtime","Eye","ATTENTION_STATUS","RotateCcwClock","LoadingState","Button","buttonClasses","apiGet","formatRelative","HostUnavailable","useOpenAttention","ErrorState","LastKnownNote","EmptyState","UnsupportedCapability","apiPost","errorMessage","useSyncStore","Bell","toast","CircleCheck","ExternalLink","isDocumentPresentation","InteractionCard","Server","ROUTES","useFilter","SegmentedControl","useResource","__iconData","ListTodo","import_react","ATTENTION_GROUPS","groupAttention","out","r","a","items","normalizeShow","v","visibleAttention","show","all","attentionMeta","project","task","session","useAttentionHistory","res","enabled","signal","attention","s","first","reload","import_jsx_runtime","GROUP_HINT","AttentionView","hydrated","stored","setStored","setShow","dismissedCount","groups","history","HistoryRow","info","reason","Icon","AttentionRow","item","host","interaction","busy","setBusy","ackError","setAckError","acknowledge","next","sessionHref","hostOffline","action","metaText","showsCard","StatusChip"],"sources":["../../../../node_modules/.pnpm/lucide-react@1.61.0_@types+react@19.3.0_react@19.3.1/node_modules/lucide-react/dist/esm/icons/list-todo.mjs","../../src/features/attention/logic.ts","../../src/features/attention/useAttentionHistory.ts","../../src/features/attention/index.tsx"],"sourcesContent":["/**\n * @license lucide-react v1.61.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconData = {\n name: \"list-todo\",\n size: 24,\n node: [\n [\"path\", { d: \"M13 5h8\", key: \"a7qcls\" }],\n [\"path\", { d: \"M13 12h8\", key: \"h98zly\" }],\n [\"path\", { d: \"M13 19h8\", key: \"c3s6r1\" }],\n [\"path\", { d: \"m3 17 2 2 4-4\", key: \"1jhpwq\" }],\n [\"rect\", { x: \"3\", y: \"4\", width: \"6\", height: \"6\", rx: \"1\", key: \"cif1o7\" }]\n ]\n};\n__iconData.node;\nconst ListTodo = createLucideIcon(__iconData);\n\nexport { __iconData, ListTodo as default };\n//# sourceMappingURL=list-todo.mjs.map\n","import type { AttentionItem, AttentionReason, Project, Session, Task } from \"@switchyard/protocol\";\n\nexport const ATTENTION_GROUPS: AttentionReason[] = [\n \"needs_input\",\n \"failed\",\n \"ready_for_review\",\n \"unsupported_interaction\",\n \"host_unreachable\",\n];\n\n/** Group open attention by reason, keeping the input order within a group. */\nexport function groupAttention(items: AttentionItem[]): Record<AttentionReason, AttentionItem[]> {\n const out = Object.fromEntries(ATTENTION_GROUPS.map((r) => [r, [] as AttentionItem[]])) as Record<\n AttentionReason,\n AttentionItem[]\n >;\n for (const a of items) out[a.reason]?.push(a);\n return out;\n}\n\nexport type AttentionShow = \"open\" | \"dismissed\" | \"history\";\n\n/** Stored prefs from before History: \"all\" (open + dismissed) and unknown values read as \"open\". */\nexport function normalizeShow(v: unknown): AttentionShow {\n return v === \"dismissed\" || v === \"history\" ? v : \"open\";\n}\n\n/** Unacknowledged items for \"open\", acknowledged for \"dismissed\"; [] for \"history\". */\nexport function visibleAttention(all: AttentionItem[], show: AttentionShow): AttentionItem[] {\n if (show === \"history\") return [];\n return all.filter((a) => (show === \"open\" ? !a.acknowledgedAt : !!a.acknowledgedAt));\n}\n\n/** \"project · task · session\" (session only when its title differs from the task's), else \"Switchyard\". */\nexport function attentionMeta(project?: Project, task?: Task, session?: Session): string {\n return (\n [project?.name, task?.title, session && session.title !== task?.title ? session.title : null]\n .filter(Boolean)\n .join(\" · \") || \"Switchyard\"\n );\n}\n","import type { AttentionItem } from \"@switchyard/protocol\";\nimport { useEffect, useRef } from \"react\";\nimport { type Resource, useResource } from \"../../lib/useResource\";\nimport { apiGet } from \"../../sync/api\";\nimport { useSyncStore } from \"../../sync/store\";\n\nexport const HISTORY_LIMIT = 200;\n\n/**\n * Resolved attention items, newest first. The sync store drops resolved items, so they come\n * from the API; refetched when open attention changes (debounced) and every 30 s.\n */\nexport function useAttentionHistory(enabled: boolean): Resource<AttentionItem[]> {\n const res = useResource<AttentionItem[]>(\n enabled ? \"attention-history\" : null,\n (signal) =>\n apiGet<AttentionItem[]>(\n \"/api/attention\",\n { resolvedOnly: \"true\", limit: HISTORY_LIMIT },\n signal,\n ),\n { refreshMs: 30_000, check: Array.isArray },\n );\n const attention = useSyncStore((s) => s.attention);\n const first = useRef(true);\n const { reload } = res;\n // biome-ignore lint/correctness/useExhaustiveDependencies: refetch on attention changes\n useEffect(() => {\n if (first.current) {\n first.current = false;\n return;\n }\n if (!enabled) return;\n const t = setTimeout(reload, 500);\n return () => clearTimeout(t);\n }, [attention, enabled, reload]);\n return res;\n}\n","import type { AttentionItem, AttentionReason } from \"@switchyard/protocol\";\nimport { Bell, CircleCheck, ExternalLink, Eye, History, ListTodo, Server } from \"lucide-react\";\nimport { useMemo, useState } from \"react\";\nimport { Link } from \"react-router\";\nimport { ROUTES } from \"../../app/routes\";\nimport { InteractionCard, isDocumentPresentation } from \"../../components/intent/InteractionCard\";\nimport { Button, buttonClasses } from \"../../components/ui/Button\";\nimport { SegmentedControl } from \"../../components/ui/SegmentedControl\";\nimport { ATTENTION_STATUS, StatusChip } from \"../../components/ui/StatusChip\";\nimport {\n EmptyState,\n ErrorState,\n HostUnavailable,\n LastKnownNote,\n LoadingState,\n UnsupportedCapability,\n} from \"../../components/ui/states\";\nimport { toast } from \"../../components/ui/Toast\";\nimport { formatRelative } from \"../../lib/time\";\nimport { useFilter } from \"../../prefs\";\nimport { apiPost, errorMessage } from \"../../sync/api\";\nimport { useOpenAttention, useSyncStore } from \"../../sync/store\";\nimport {\n ATTENTION_GROUPS,\n type AttentionShow,\n attentionMeta,\n groupAttention,\n normalizeShow,\n visibleAttention,\n} from \"./logic\";\nimport { HISTORY_LIMIT, useAttentionHistory } from \"./useAttentionHistory\";\n\nconst GROUP_HINT: Record<AttentionReason, string> = {\n needs_input: \"Questions from Pi waiting for your answer.\",\n failed: \"Runs that failed or were blocked. Open the task to see why.\",\n ready_for_review: \"Finished runs waiting for your review.\",\n unsupported_interaction:\n \"Questions this client cannot display. Answer them in a terminal attached to the session.\",\n host_unreachable:\n \"Running work on hosts that stopped responding. Nothing is restarted elsewhere.\",\n};\n\n/** /attention: Needs input, Failed, Ready for review (plus unsupported and unreachable). */\nexport default function AttentionView() {\n const all = useOpenAttention();\n const hydrated = useSyncStore((s) => s.hydrated);\n const [stored, setStored] = useFilter<AttentionShow | \"all\">(\"attentionShow\", \"open\");\n const show = normalizeShow(stored);\n const setShow = (v: AttentionShow) => setStored(v);\n const items = useMemo(() => visibleAttention(all, show), [all, show]);\n const dismissedCount = useMemo(() => visibleAttention(all, \"dismissed\").length, [all]);\n const groups = useMemo(() => groupAttention(items), [items]);\n const history = useAttentionHistory(show === \"history\");\n\n return (\n <div className=\"flex w-full min-w-0 flex-col gap-4 px-4 pb-4 safe-pt-4 md:px-6\">\n <header className=\"flex min-w-0 flex-wrap items-center gap-3\">\n <h1 className=\"flex-1 text-lg font-semibold\">Attention</h1>\n <SegmentedControl\n label=\"Show\"\n className=\"max-sm:w-full max-sm:basis-full\"\n value={show}\n onChange={setShow}\n options={[\n { value: \"open\", label: \"Open\" },\n { value: \"dismissed\", label: \"Dismissed\" },\n { value: \"history\", label: \"History\" },\n ]}\n />\n </header>\n <LastKnownNote\n subject=\"attention items\"\n consequence=\"Answers are sent once the connection is back.\"\n show={hydrated}\n />\n {show === \"history\" ? (\n <div data-testid=\"attention-history\" className=\"flex min-w-0 flex-col gap-2\">\n {!history.data && history.loading ? (\n <LoadingState label=\"Loading history…\" />\n ) : !history.data && history.error ? (\n <ErrorState\n compact\n title=\"Could not load history\"\n error={errorMessage(history.error)}\n onRetry={history.reload}\n />\n ) : !history.data || history.data.length === 0 ? (\n <EmptyState\n icon={<History className=\"size-8\" />}\n title=\"No history yet\"\n description=\"Resolved questions, failures and reviews show up here.\"\n />\n ) : (\n <>\n <ul className=\"flex min-w-0 flex-col gap-2\">\n {history.data.map((a) => (\n <li key={a.id} className=\"min-w-0\">\n <HistoryRow item={a} />\n </li>\n ))}\n </ul>\n {history.data.length >= HISTORY_LIMIT && (\n <p className=\"text-sm text-muted\">Showing the {HISTORY_LIMIT} most recent.</p>\n )}\n </>\n )}\n </div>\n ) : !hydrated ? (\n <LoadingState label=\"Loading attention…\" />\n ) : items.length === 0 ? (\n show === \"dismissed\" ? (\n <EmptyState\n icon={<Bell className=\"size-8\" />}\n title=\"No dismissed items\"\n description=\"Items you dismiss stay here until their question or task is resolved.\"\n action={{ label: \"Show open\", onClick: () => setShow(\"open\") }}\n />\n ) : (\n <EmptyState\n icon={<Bell className=\"size-8\" />}\n title={dismissedCount > 0 ? \"All caught up\" : \"Nothing needs your attention\"}\n description={\n dismissedCount > 0\n ? `${dismissedCount} dismissed ${dismissedCount === 1 ? \"item is\" : \"items are\"} still open.`\n : \"Questions, failures and finished work show up here.\"\n }\n action={\n dismissedCount > 0\n ? { label: \"Show dismissed\", onClick: () => setShow(\"dismissed\") }\n : { label: \"Go to Sessions\", to: ROUTES.code }\n }\n secondaryAction={\n dismissedCount > 0\n ? { label: \"Go to Sessions\", to: ROUTES.code }\n : { label: \"Go to Board\", to: ROUTES.board }\n }\n />\n )\n ) : (\n ATTENTION_GROUPS.filter((r) => groups[r].length > 0).map((reason) => {\n const info = ATTENTION_STATUS[reason];\n const Icon = info.icon;\n return (\n <section\n key={reason}\n data-testid={`attention-group-${reason}`}\n aria-labelledby={`att-${reason}`}\n className=\"flex min-w-0 flex-col gap-2\"\n >\n <div>\n <h2\n id={`att-${reason}`}\n className=\"flex items-center gap-2 text-base font-semibold\"\n >\n <Icon aria-hidden className=\"size-4 text-muted\" />\n {info.label}\n <span className=\"text-sm font-normal text-muted\">({groups[reason].length})</span>\n </h2>\n <p className=\"text-sm text-muted\">{GROUP_HINT[reason]}</p>\n </div>\n <ul className=\"flex min-w-0 flex-col gap-2\">\n {groups[reason].map((a) => (\n <li key={a.id} className=\"min-w-0\">\n <AttentionRow item={a} />\n </li>\n ))}\n </ul>\n </section>\n );\n })\n )}\n </div>\n );\n}\n\nfunction AttentionRow({ item }: { item: AttentionItem }) {\n const project = useSyncStore((s) => (item.projectId ? s.projects[item.projectId] : undefined));\n const task = useSyncStore((s) => (item.taskId ? s.tasks[item.taskId] : undefined));\n const session = useSyncStore((s) => (item.sessionId ? s.sessions[item.sessionId] : undefined));\n const host = useSyncStore((s) => (session ? s.hosts[session.hostId] : undefined));\n const interaction = useSyncStore((s) =>\n item.interactionId ? s.interactions[item.interactionId] : undefined,\n );\n const [busy, setBusy] = useState(false);\n const [ackError, setAckError] = useState<unknown>(null);\n\n const acknowledge = async () => {\n setBusy(true);\n setAckError(null);\n try {\n const res = await apiPost<AttentionItem>(\n `/api/attention/${encodeURIComponent(item.id)}/acknowledge`,\n );\n const next =\n res && typeof res === \"object\" && \"id\" in res\n ? res\n : { ...item, acknowledgedAt: new Date().toISOString() };\n useSyncStore.setState((s) =>\n s.attention[item.id] ? { attention: { ...s.attention, [item.id]: next } } : {},\n );\n toast.success(\"Dismissed. It stays under All until the task or question is resolved.\");\n } catch (e) {\n setAckError(e);\n } finally {\n setBusy(false);\n }\n };\n\n const sessionHref = item.sessionId\n ? item.interactionId\n ? ROUTES.interaction(item.sessionId, item.interactionId)\n : ROUTES.session(item.sessionId)\n : null;\n const hostOffline = !!host && host.connectivity !== \"connected\";\n\n let action: React.ReactNode = null;\n switch (item.reason) {\n case \"needs_input\":\n action =\n interaction && interaction.state === \"pending\" ? (\n <InteractionCard\n flush\n interaction={interaction}\n readOnlyReason={\n hostOffline\n ? `Host ${host?.name} is offline. You can answer once it reconnects.`\n : session?.readOnly\n ? \"This session is read-only.\"\n : null\n }\n />\n ) : interaction ? (\n <InteractionCard flush interaction={interaction} />\n ) : sessionHref ? (\n <Link to={sessionHref} className={buttonClasses(\"primary\", \"sm\")}>\n <ExternalLink aria-hidden className=\"size-3.5\" />\n Answer in session\n </Link>\n ) : null;\n break;\n case \"failed\":\n action = item.taskId ? (\n <Link to={ROUTES.task(item.taskId)} className={buttonClasses(\"primary\", \"sm\")}>\n <ListTodo aria-hidden className=\"size-3.5\" />\n Open task\n </Link>\n ) : null;\n break;\n case \"ready_for_review\":\n action = item.sessionId ? (\n <Link\n to={`${ROUTES.session(item.sessionId)}?tab=changes`}\n className={buttonClasses(\"primary\", \"sm\")}\n >\n <Eye aria-hidden className=\"size-3.5\" />\n Review changes\n </Link>\n ) : item.taskId ? (\n <Link to={ROUTES.task(item.taskId)} className={buttonClasses(\"primary\", \"sm\")}>\n <Eye aria-hidden className=\"size-3.5\" />\n Open review\n </Link>\n ) : null;\n break;\n case \"unsupported_interaction\":\n action = interaction ? (\n <InteractionCard\n interaction={interaction}\n readOnlyReason={\n hostOffline\n ? `Host ${host?.name} is offline. You can choose once it reconnects.`\n : session?.readOnly\n ? \"This session is read-only.\"\n : null\n }\n />\n ) : (\n <UnsupportedCapability\n capability=\"This dialog\"\n reason=\"An extension opened a terminal-only dialog. In RPC mode Pi gave it no answer, so the extension continued without one.\"\n />\n );\n break;\n case \"host_unreachable\":\n action = host ? (\n <HostUnavailable\n compact\n hostName={host.name}\n connectivity={host.connectivity === \"reconnecting\" ? \"reconnecting\" : \"unreachable\"}\n lastSeenAt={host.lastSeenAt}\n />\n ) : null;\n break;\n }\n\n const metaText = attentionMeta(project, task, session);\n\n const showsCard =\n !!interaction && (item.reason === \"needs_input\" || item.reason === \"unsupported_interaction\");\n\n // A plan awaiting approval gets the whole width: one meta line, no card around it.\n if (\n item.reason === \"needs_input\" &&\n interaction?.state === \"pending\" &&\n isDocumentPresentation(interaction.presentation)\n ) {\n return (\n <article\n aria-label={`Plan awaiting approval: ${metaText}`}\n className=\"flex min-w-0 flex-col gap-2\"\n >\n <div className=\"flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-sm text-muted\">\n <span className=\"min-w-0 basis-full truncate sm:basis-auto sm:flex-1\">{metaText}</span>\n {sessionHref && (\n <Link to={sessionHref} className={buttonClasses(\"ghost\", \"sm\")}>\n <ExternalLink aria-hidden className=\"size-3.5\" />\n Open session\n </Link>\n )}\n <time dateTime={item.createdAt} className=\"shrink-0 text-xs text-subtle sm:ml-auto\">\n {formatRelative(item.createdAt)}\n </time>\n {!item.acknowledgedAt && (\n <Button size=\"sm\" variant=\"ghost\" busy={busy} onClick={() => void acknowledge()}>\n Dismiss\n </Button>\n )}\n </div>\n {action}\n {ackError !== null && (\n <ErrorState\n compact\n title=\"Could not dismiss\"\n error={errorMessage(ackError)}\n onRetry={() => void acknowledge()}\n />\n )}\n </article>\n );\n }\n\n return (\n <article\n aria-label={`${ATTENTION_STATUS[item.reason].label}: ${task?.title ?? session?.title ?? item.summary}`}\n className=\"flex min-w-0 flex-col gap-2 rounded-lg border border-border bg-surface p-3\"\n >\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n <StatusChip kind=\"attention\" value={item.reason} size=\"sm\" />\n <span className=\"min-w-0 flex-1 truncate text-xs text-muted\">{metaText}</span>\n <time dateTime={item.createdAt} className=\"shrink-0 text-xs text-subtle\">\n {formatRelative(item.createdAt)}\n </time>\n </div>\n {!showsCard && <p className=\"min-w-0 text-sm break-words\">{item.summary}</p>}\n {action && <div className=\"flex min-w-0 flex-col items-start\">{action}</div>}\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n {item.reason !== \"failed\" && item.taskId && (\n <Link to={ROUTES.task(item.taskId)} className={buttonClasses(\"ghost\", \"sm\")}>\n <ListTodo aria-hidden className=\"size-3.5\" />\n Task\n </Link>\n )}\n {sessionHref && (\n <Link to={sessionHref} className={buttonClasses(\"ghost\", \"sm\")}>\n <ExternalLink aria-hidden className=\"size-3.5\" />\n Open session\n </Link>\n )}\n {item.reason === \"host_unreachable\" && host && (\n <Link\n to={`${ROUTES.settings(\"hosts\")}/${encodeURIComponent(host.id)}`}\n className={buttonClasses(\"ghost\", \"sm\")}\n >\n <Server aria-hidden className=\"size-3.5\" />\n Host details\n </Link>\n )}\n <span className=\"ml-auto\" />\n {item.acknowledgedAt ? (\n <span className=\"inline-flex items-center gap-1 text-xs text-muted\">\n <CircleCheck aria-hidden className=\"size-3.5\" />\n Dismissed {formatRelative(item.acknowledgedAt)}\n </span>\n ) : (\n <Button size=\"sm\" busy={busy} onClick={() => void acknowledge()}>\n Dismiss\n </Button>\n )}\n </div>\n {ackError !== null && (\n <ErrorState\n compact\n title=\"Could not dismiss\"\n error={errorMessage(ackError)}\n onRetry={() => void acknowledge()}\n />\n )}\n </article>\n );\n}\n\nfunction HistoryRow({ item }: { item: AttentionItem }) {\n const project = useSyncStore((s) => (item.projectId ? s.projects[item.projectId] : undefined));\n const task = useSyncStore((s) => (item.taskId ? s.tasks[item.taskId] : undefined));\n const session = useSyncStore((s) => (item.sessionId ? s.sessions[item.sessionId] : undefined));\n return (\n <article\n aria-label={`${ATTENTION_STATUS[item.reason].label}: ${task?.title ?? session?.title ?? item.summary}`}\n className=\"flex min-w-0 flex-col gap-2 rounded-lg border border-border bg-surface p-3\"\n >\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n <StatusChip kind=\"attention\" value={item.reason} size=\"sm\" />\n <span className=\"min-w-0 flex-1 truncate text-xs text-muted\">\n {attentionMeta(project, task, session)}\n </span>\n {item.resolvedAt && (\n <time dateTime={item.resolvedAt} className=\"shrink-0 text-xs text-subtle\">\n Resolved {formatRelative(item.resolvedAt)}\n </time>\n )}\n </div>\n <p className=\"min-w-0 text-sm break-words\">{item.summary}</p>\n {(item.taskId || item.sessionId) && (\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n {item.taskId && (\n <Link to={ROUTES.task(item.taskId)} className={buttonClasses(\"ghost\", \"sm\")}>\n <ListTodo aria-hidden className=\"size-3.5\" />\n Task\n </Link>\n )}\n {item.sessionId && (\n <Link to={ROUTES.session(item.sessionId)} className={buttonClasses(\"ghost\", \"sm\")}>\n <ExternalLink aria-hidden className=\"size-3.5\" />\n Open session\n </Link>\n )}\n </div>\n )}\n </article>\n );\n}\n"],"ignoreList":[0],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{D as e,H as t,St as n,gt as r,pt as i,xt as a}from"./states-C773NlHo.js";import{t as o}from"./presence-05bJD5G3.js";var s=`switchyard`,c=`kv`,l=`sync`,u=`sy:sync-cache`,d=null;function f(){return d||(d=new Promise(e=>{if(typeof indexedDB>`u`)return e(null);try{let t=indexedDB.open(s,1);t.onupgradeneeded=()=>t.result.createObjectStore(c),t.onsuccess=()=>e(t.result),t.onerror=()=>e(null),t.onblocked=()=>e(null)}catch{e(null)}}),d)}function p(e,t){return f().then(n=>new Promise((r,i)=>{if(!n)return i(Error(`no indexeddb`));let a=t(n.transaction(c,e).objectStore(c));a.onsuccess=()=>r(a.result),a.onerror=()=>i(a.error)}))}async function m(){try{let e=await p(`readonly`,e=>e.get(l));if(e)return e}catch{}try{let e=localStorage.getItem(u);return e?JSON.parse(e):null}catch{return null}}async function h(e){try{await p(`readwrite`,t=>t.put(e,l));return}catch{}try{localStorage.setItem(u,JSON.stringify(e))}catch{}}async function g(){try{await p(`readwrite`,e=>e.delete(l))}catch{}try{localStorage.removeItem(u)}catch{}}var _=25e3,v=1e4,y=1e3,b=3e4,x=1500,S=1e4,C=5e3,w=class{url;ws=null;stopped=!0;everLive=!1;attempts=0;retryTimer=null;pingTimer=null;pongTimer=null;persistTimer=null;connectTimer=null;lastFrameAt=0;unsubscribeStore=null;liveListeners=new Set;logoutListeners=new Set;cacheLoaded=null;constructor(e=T()){this.url=e}start(){this.stopped&&(this.stopped=!1,window.addEventListener(`online`,this.onOnline),window.addEventListener(`offline`,this.onOffline),document.addEventListener(`visibilitychange`,this.onVisibility),window.addEventListener(`pageshow`,this.onForeground),window.addEventListener(`focus`,this.onForeground),this.unsubscribeStore=e.subscribe((e,t)=>{e.synced&&e.seq!==t.seq&&this.schedulePersist()}),this.cacheLoaded??=m().then(t=>{t&&!e.getState().synced&&e.getState().hydrateFromCache(t.seq,t.state,t.savedAt)}),this.cacheLoaded.then(()=>this.connect()))}stop(){this.stopped=!0,window.removeEventListener(`online`,this.onOnline),window.removeEventListener(`offline`,this.onOffline),document.removeEventListener(`visibilitychange`,this.onVisibility),window.removeEventListener(`pageshow`,this.onForeground),window.removeEventListener(`focus`,this.onForeground),this.unsubscribeStore?.(),this.unsubscribeStore=null,this.clearTimers();let e=this.ws;this.ws=null,e?.close(1e3,`client stopped`)}send(e){return this.ws?.readyState===WebSocket.OPEN&&(this.ws.send(JSON.stringify(e)),!0)}get isLive(){return e.getState().connection.state===`live`}onLive(e){return this.liveListeners.add(e),()=>this.liveListeners.delete(e)}onLogout(e){return this.logoutListeners.add(e),()=>this.logoutListeners.delete(e)}reconnectNow(){this.stopped||(this.retryTimer&&clearTimeout(this.retryTimer),this.retryTimer=null,!(this.ws&&this.ws.readyState<=WebSocket.OPEN)&&this.connect())}connect(){if(this.stopped)return;if(typeof navigator<`u`&&navigator.onLine===!1){e.getState().setConnection({state:`offline`});return}e.getState().setConnection({state:this.everLive?`reconnecting`:`connecting`});let t;try{t=new WebSocket(this.url)}catch{this.scheduleReconnect();return}this.ws=t,this.connectTimer&&clearTimeout(this.connectTimer);let n=S*Math.min(1+this.attempts,3);this.connectTimer=setTimeout(()=>{this.connectTimer=null,this.ws===t&&this.dropSocket(4e3,`connect timeout`)},n);let r=!1;t.onopen=()=>{if(this.ws!==t)return;r=!0;let n=e.getState().seq;t.send(JSON.stringify({type:`hello`,cursor:n}))},t.onmessage=e=>{if(this.ws!==t)return;this.lastFrameAt=Date.now();let n;try{n=JSON.parse(typeof e.data==`string`?e.data:``)}catch{return}this.handle(n)},t.onclose=e=>{this.ws===t&&(this.ws=null,this.clearTimers(),!this.stopped&&(e.code===4401||e.code===4403||e.code===1008?this.emitLogout(e.reason||`unauthorized`):(!r&&this.attempts%5==0&&this.probeAuth(),this.scheduleReconnect())))},t.onerror=()=>{}}probing=!1;probeAuth(){this.probing||(this.probing=!0,r(`/api/me`).catch(()=>void 0).finally(()=>{this.probing=!1}))}handle(t){let n=e.getState();switch(t.type){case`snapshot`:n.applySnapshot(t.seq,t.state),this.becomeLive();break;case`replay`:n.applyReplay(t.seq,t.events),this.becomeLive();break;case`event`:n.applyEvent(t.event);break;case`stream`:n.applyStream(t.delta);break;case`pong`:this.pongTimer&&clearTimeout(this.pongTimer),this.pongTimer=null,n.markSynced();break;case`force_logout`:this.emitLogout(t.reason)}}becomeLive(){this.connectTimer&&clearTimeout(this.connectTimer),this.connectTimer=null,this.everLive=!0,this.attempts=0,e.getState().setConnection({state:`live`,attempts:0}),this.startPing(),this.schedulePersist();for(let e of this.liveListeners)e()}emitLogout(t){this.stop(),g(),e.getState().reset();for(let e of this.logoutListeners)e(t)}startPing(){this.pingTimer&&clearInterval(this.pingTimer),this.pingTimer=setInterval(()=>{this.send({type:`ping`})&&(this.pongTimer||this.armPongTimeout(v))},_)}armPongTimeout(e){this.pongTimer&&clearTimeout(this.pongTimer),this.pongTimer=setTimeout(()=>{this.pongTimer=null,this.dropSocket(4e3,`pong timeout`)},e)}dropSocket(e,t){let n=this.ws;if(n){this.ws=null,this.clearTimers();try{n.close(e,t)}catch{}this.scheduleReconnect()}}scheduleReconnect(){if(this.stopped)return;if(typeof navigator<`u`&&navigator.onLine===!1){e.getState().setConnection({state:`offline`});return}this.attempts++;let t=Math.min(b,y*2**(this.attempts-1)),n=t/2+t/2*Math.random();e.getState().setConnection({state:this.everLive?`reconnecting`:`connecting`,attempts:this.attempts}),this.retryTimer&&clearTimeout(this.retryTimer),this.retryTimer=setTimeout(()=>{this.retryTimer=null,this.connect()},n)}schedulePersist(){this.persistTimer||=setTimeout(()=>{this.persistTimer=null;let n=e.getState();n.synced&&n.seq!==null&&h({seq:n.seq,state:t(n),savedAt:new Date().toISOString()})},x)}clearTimers(){this.retryTimer&&clearTimeout(this.retryTimer),this.pingTimer&&clearInterval(this.pingTimer),this.pongTimer&&clearTimeout(this.pongTimer),this.connectTimer&&clearTimeout(this.connectTimer),this.connectTimer=null,this.retryTimer=null,this.pingTimer=null,this.pongTimer=null}onOnline=()=>{this.attempts=0,this.reconnectNow()};onVisibility=()=>{document.visibilityState===`visible`&&this.onForeground()};onForeground=()=>{if(!(this.stopped||document.visibilityState===`hidden`)){if(this.isLive){if(Date.now()-this.lastFrameAt<5e3)return;this.send({type:`ping`})&&this.armPongTimeout(C)}else this.attempts=0,this.reconnectNow()}};onOffline=()=>{e.getState().setConnection({state:`offline`}),this.ws?.close(4e3,`offline`)}};function T(){return`${location.protocol===`https:`?`wss:`:`ws:`}//${location.host}/ws/client`}var E=new w(typeof location>`u`?``:T()),D=n()(()=>({status:`checking`,me:null,error:null,signedOutReason:null})),O=`sy:me`,k=`sy:returnTo`,A=null;function j(){E.start(),A??=o(E)}function M(){E.stop(),A?.(),A=null}function N(){return D.getState().me?.device?.id??null}async function P(){D.setState({status:`checking`,error:null});try{let e=await r(`/api/me`);try{localStorage.setItem(O,JSON.stringify(e))}catch{}return D.setState({status:`authenticated`,me:e,signedOutReason:null}),j(),`authenticated`}catch(e){if(e instanceof i&&(e.status===401||e.code===`device_revoked`))return I(e.code===`device_revoked`?`This device was revoked.`:null),`unauthenticated`;let t=F();return t?(D.setState({status:`authenticated`,me:t}),j(),`authenticated`):(D.setState({status:`error`,error:e}),`error`)}}function F(){try{let e=localStorage.getItem(O),t=e?JSON.parse(e):null;return t?.device?.id?t:null}catch{return null}}function I(t){M(),g(),e.getState().reset();try{localStorage.removeItem(O)}catch{}D.setState({status:`unauthenticated`,me:null,signedOutReason:t})}function L(e){if(!(e.startsWith(`/sign-in`)||e.startsWith(`/enroll`)))try{localStorage.setItem(k,e)}catch{}}function R(e){let t=null;try{t=localStorage.getItem(k),localStorage.removeItem(k)}catch{}let n=e??t;return n?.startsWith(`/`)&&!n.startsWith(`//`)&&!n.startsWith(`/sign-in`)&&!n.startsWith(`/enroll`)?n:`/`}var z=!1;function B(){z||(z=!0,a(e=>{D.getState().status===`authenticated`&&I(e===`device_revoked`?`This device was revoked.`:null)}),E.onLogout(e=>{I(e===`unauthorized`?null:`Signed out: ${e}`)}))}export{I as a,E as c,L as i,N as n,R as o,B as r,D as s,P as t};
|
|
2
|
+
//# sourceMappingURL=auth-308hUVgi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-308hUVgi.js","names":["syncStore","toSyncState","create","apiGet","ApiClientError","onUnauthorized","startPresence","DB_NAME","STORE","KEY","LS_KEY","dbPromise","openDb","resolve","req","idb","db","reject","fn","mode","loadCache","v","s","raw","saveCache","value","clearCache","PING_INTERVAL_MS","PONG_TIMEOUT_MS","BACKOFF_BASE_MS","BACKOFF_MAX_MS","PERSIST_DELAY_MS","CONNECT_TIMEOUT_MS","RESUME_PONG_TIMEOUT_MS","SyncClient","defaultWsUrl","url","prev","cached","ws","msg","timeoutMs","opened","cursor","ev","store","reason","ms","code","delay","jittered","syncClient","useAuth","ME_KEY","RETURN_KEY","stopPresence","startSync","stopSync","currentDeviceId","checkAuth","me","signOut","readCachedMe","saveReturnTo","takeReturnTo","saved","candidate","fromQuery","wired","initAuth"],"sources":["../../src/sync/cache.ts","../../src/sync/client.ts","../../src/app/auth.ts"],"sourcesContent":["import type { SyncState } from \"@switchyard/protocol\";\n\n/**\n * Offline cache of the last synced state and its cursor, so the UI shows\n * last-known data while reconnecting and the WS resumes with `hello {cursor}`.\n * IndexedDB when available, localStorage otherwise.\n */\n\nexport interface CachedSync {\n seq: number;\n state: SyncState;\n savedAt: string;\n}\n\nconst DB_NAME = \"switchyard\";\nconst STORE = \"kv\";\nconst KEY = \"sync\";\nconst LS_KEY = \"sy:sync-cache\";\n\nlet dbPromise: Promise<IDBDatabase | null> | null = null;\n\nfunction openDb(): Promise<IDBDatabase | null> {\n if (dbPromise) return dbPromise;\n dbPromise = new Promise((resolve) => {\n if (typeof indexedDB === \"undefined\") return resolve(null);\n try {\n const req = indexedDB.open(DB_NAME, 1);\n req.onupgradeneeded = () => req.result.createObjectStore(STORE);\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => resolve(null);\n req.onblocked = () => resolve(null);\n } catch {\n resolve(null);\n }\n });\n return dbPromise;\n}\n\nfunction idb<T>(mode: IDBTransactionMode, fn: (s: IDBObjectStore) => IDBRequest<T>): Promise<T> {\n return openDb().then(\n (db) =>\n new Promise<T>((resolve, reject) => {\n if (!db) return reject(new Error(\"no indexeddb\"));\n const tx = db.transaction(STORE, mode);\n const req = fn(tx.objectStore(STORE));\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n }),\n );\n}\n\nexport async function loadCache(): Promise<CachedSync | null> {\n try {\n const v = await idb<CachedSync | undefined>(\"readonly\", (s) => s.get(KEY));\n if (v) return v;\n } catch {\n // fall through to localStorage\n }\n try {\n const raw = localStorage.getItem(LS_KEY);\n return raw ? (JSON.parse(raw) as CachedSync) : null;\n } catch {\n return null;\n }\n}\n\nexport async function saveCache(value: CachedSync): Promise<void> {\n try {\n await idb(\"readwrite\", (s) => s.put(value, KEY));\n return;\n } catch {\n // fall through\n }\n try {\n localStorage.setItem(LS_KEY, JSON.stringify(value));\n } catch {\n // quota exceeded: the cache is best effort\n }\n}\n\nexport async function clearCache(): Promise<void> {\n try {\n await idb(\"readwrite\", (s) => s.delete(KEY));\n } catch {\n // ignore\n }\n try {\n localStorage.removeItem(LS_KEY);\n } catch {\n // ignore\n }\n}\n","import type { ClientToServer, ServerToClient } from \"@switchyard/protocol\";\nimport { apiGet } from \"./api\";\nimport { clearCache, loadCache, saveCache } from \"./cache\";\nimport { toSyncState } from \"./reduce\";\nimport { syncStore } from \"./store\";\n\n/**\n * Live sync over `/ws/client`.\n *\n * - On open: `hello {cursor}` with the last applied seq (from memory or the\n * offline cache); the server answers `replay` (events since cursor) or\n * `snapshot` (full SyncState).\n * - Then applies `event` and `stream` frames to the store.\n * - Reconnects with exponential backoff and jitter; `offline` while the\n * browser reports no network.\n * - Probes the link when the page returns to the foreground (a backgrounded phone often\n * holds a dead socket) and gives up on a socket that opens but never syncs.\n * - Persists `{seq, state}` (throttled) so last-known data shows on reload\n * and while reconnecting.\n */\n\nconst PING_INTERVAL_MS = 25_000;\nconst PONG_TIMEOUT_MS = 10_000;\nconst BACKOFF_BASE_MS = 1_000;\nconst BACKOFF_MAX_MS = 30_000;\nconst PERSIST_DELAY_MS = 1_500;\n/** open -> live; scaled by attempts (1x, 2x, 3x) so a large snapshot gets longer on retries. */\nexport const CONNECT_TIMEOUT_MS = 10_000;\nexport const RESUME_PONG_TIMEOUT_MS = 5_000;\n/** Skip the resume ping if a frame arrived this recently. */\nexport const RESUME_FRESH_MS = 5_000;\n\ntype Listener = () => void;\n\nexport class SyncClient {\n private ws: WebSocket | null = null;\n private stopped = true;\n private everLive = false;\n private attempts = 0;\n private retryTimer: ReturnType<typeof setTimeout> | null = null;\n private pingTimer: ReturnType<typeof setInterval> | null = null;\n private pongTimer: ReturnType<typeof setTimeout> | null = null;\n private persistTimer: ReturnType<typeof setTimeout> | null = null;\n private connectTimer: ReturnType<typeof setTimeout> | null = null;\n private lastFrameAt = 0;\n private unsubscribeStore: (() => void) | null = null;\n private liveListeners = new Set<Listener>();\n private logoutListeners = new Set<(reason: string) => void>();\n private cacheLoaded: Promise<void> | null = null;\n\n constructor(private readonly url: string = defaultWsUrl()) {}\n\n /** Load the offline cache into the store and connect. Idempotent. */\n start(): void {\n if (!this.stopped) return;\n this.stopped = false;\n window.addEventListener(\"online\", this.onOnline);\n window.addEventListener(\"offline\", this.onOffline);\n document.addEventListener(\"visibilitychange\", this.onVisibility);\n window.addEventListener(\"pageshow\", this.onForeground);\n window.addEventListener(\"focus\", this.onForeground);\n this.unsubscribeStore = syncStore.subscribe((s, prev) => {\n if (s.synced && s.seq !== prev.seq) this.schedulePersist();\n });\n this.cacheLoaded ??= loadCache().then((cached) => {\n if (cached && !syncStore.getState().synced) {\n syncStore.getState().hydrateFromCache(cached.seq, cached.state, cached.savedAt);\n }\n });\n void this.cacheLoaded.then(() => this.connect());\n }\n\n stop(): void {\n this.stopped = true;\n window.removeEventListener(\"online\", this.onOnline);\n window.removeEventListener(\"offline\", this.onOffline);\n document.removeEventListener(\"visibilitychange\", this.onVisibility);\n window.removeEventListener(\"pageshow\", this.onForeground);\n window.removeEventListener(\"focus\", this.onForeground);\n this.unsubscribeStore?.();\n this.unsubscribeStore = null;\n this.clearTimers();\n const ws = this.ws;\n this.ws = null;\n ws?.close(1000, \"client stopped\");\n }\n\n /** Send a frame when connected. Returns false when it could not be sent. */\n send(msg: ClientToServer): boolean {\n if (this.ws?.readyState !== WebSocket.OPEN) return false;\n this.ws.send(JSON.stringify(msg));\n return true;\n }\n\n get isLive(): boolean {\n return syncStore.getState().connection.state === \"live\";\n }\n\n /** Called each time the connection becomes live (after replay or snapshot). */\n onLive(fn: Listener): () => void {\n this.liveListeners.add(fn);\n return () => this.liveListeners.delete(fn);\n }\n\n /** Called on `force_logout` (device revoked) and on auth failures. */\n onLogout(fn: (reason: string) => void): () => void {\n this.logoutListeners.add(fn);\n return () => this.logoutListeners.delete(fn);\n }\n\n /** Reconnect now (for example from a \"Retry\" button). */\n reconnectNow(): void {\n if (this.stopped) return;\n if (this.retryTimer) clearTimeout(this.retryTimer);\n this.retryTimer = null;\n if (this.ws && this.ws.readyState <= WebSocket.OPEN) return;\n this.connect();\n }\n\n private connect(): void {\n if (this.stopped) return;\n if (typeof navigator !== \"undefined\" && navigator.onLine === false) {\n syncStore.getState().setConnection({ state: \"offline\" });\n return;\n }\n syncStore.getState().setConnection({ state: this.everLive ? \"reconnecting\" : \"connecting\" });\n let ws: WebSocket;\n try {\n ws = new WebSocket(this.url);\n } catch {\n this.scheduleReconnect();\n return;\n }\n this.ws = ws;\n if (this.connectTimer) clearTimeout(this.connectTimer);\n const timeoutMs = CONNECT_TIMEOUT_MS * Math.min(1 + this.attempts, 3);\n this.connectTimer = setTimeout(() => {\n this.connectTimer = null;\n if (this.ws === ws) this.dropSocket(4000, \"connect timeout\");\n }, timeoutMs);\n let opened = false;\n ws.onopen = () => {\n if (this.ws !== ws) return;\n opened = true;\n const cursor = syncStore.getState().seq;\n ws.send(JSON.stringify({ type: \"hello\", cursor } satisfies ClientToServer));\n };\n ws.onmessage = (ev) => {\n if (this.ws !== ws) return;\n this.lastFrameAt = Date.now();\n let msg: ServerToClient;\n try {\n msg = JSON.parse(typeof ev.data === \"string\" ? ev.data : \"\") as ServerToClient;\n } catch {\n return;\n }\n this.handle(msg);\n };\n ws.onclose = (ev) => {\n if (this.ws !== ws) return;\n this.ws = null;\n this.clearTimers();\n if (this.stopped) return;\n // 4401: the coordinator revoked this device (it also sends `force_logout` first).\n // 4403/1008 are treated the same for proxies that map auth failures to close codes.\n if (ev.code === 4401 || ev.code === 4403 || ev.code === 1008) {\n this.emitLogout(ev.reason || \"unauthorized\");\n return;\n }\n // An upgrade rejected with HTTP 401 surfaces only as 1006 before open. Probe\n // GET /api/me: a 401 there signs out through the api() unauthorized listeners.\n if (!opened && this.attempts % 5 === 0) this.probeAuth();\n // 1013 (client too slow) and 1001 (coordinator stopping): reconnect with the cursor.\n this.scheduleReconnect();\n };\n ws.onerror = () => {\n // onclose follows and schedules the reconnect.\n };\n }\n\n private probing = false;\n private probeAuth(): void {\n if (this.probing) return;\n this.probing = true;\n void apiGet(\"/api/me\")\n .catch(() => undefined)\n .finally(() => {\n this.probing = false;\n });\n }\n\n private handle(msg: ServerToClient): void {\n const store = syncStore.getState();\n switch (msg.type) {\n case \"snapshot\":\n store.applySnapshot(msg.seq, msg.state);\n this.becomeLive();\n break;\n case \"replay\":\n store.applyReplay(msg.seq, msg.events);\n this.becomeLive();\n break;\n case \"event\":\n store.applyEvent(msg.event);\n break;\n case \"stream\":\n store.applyStream(msg.delta);\n break;\n case \"pong\":\n if (this.pongTimer) clearTimeout(this.pongTimer);\n this.pongTimer = null;\n store.markSynced();\n break;\n case \"force_logout\":\n this.emitLogout(msg.reason);\n break;\n }\n }\n\n private becomeLive(): void {\n if (this.connectTimer) clearTimeout(this.connectTimer);\n this.connectTimer = null;\n this.everLive = true;\n this.attempts = 0;\n syncStore.getState().setConnection({ state: \"live\", attempts: 0 });\n this.startPing();\n this.schedulePersist();\n for (const fn of this.liveListeners) fn();\n }\n\n private emitLogout(reason: string): void {\n this.stop();\n void clearCache();\n syncStore.getState().reset();\n for (const fn of this.logoutListeners) fn(reason);\n }\n\n private startPing(): void {\n if (this.pingTimer) clearInterval(this.pingTimer);\n this.pingTimer = setInterval(() => {\n if (!this.send({ type: \"ping\" })) return;\n if (!this.pongTimer) this.armPongTimeout(PONG_TIMEOUT_MS);\n }, PING_INTERVAL_MS);\n }\n\n private armPongTimeout(ms: number): void {\n if (this.pongTimer) clearTimeout(this.pongTimer);\n this.pongTimer = setTimeout(() => {\n this.pongTimer = null;\n // No pong: assume a dead socket and reconnect.\n this.dropSocket(4000, \"pong timeout\");\n }, ms);\n }\n\n /** Detach and close a socket we consider dead, then reconnect without waiting for onclose. */\n private dropSocket(code: number, reason: string): void {\n const ws = this.ws;\n if (!ws) return;\n this.ws = null; // its late onclose hits the `this.ws !== ws` guard\n this.clearTimers();\n try {\n ws.close(code, reason);\n } catch {\n // already closing\n }\n this.scheduleReconnect();\n }\n\n private scheduleReconnect(): void {\n if (this.stopped) return;\n if (typeof navigator !== \"undefined\" && navigator.onLine === false) {\n syncStore.getState().setConnection({ state: \"offline\" });\n return;\n }\n this.attempts++;\n const delay = Math.min(BACKOFF_MAX_MS, BACKOFF_BASE_MS * 2 ** (this.attempts - 1));\n const jittered = delay / 2 + Math.random() * (delay / 2);\n syncStore.getState().setConnection({\n state: this.everLive ? \"reconnecting\" : \"connecting\",\n attempts: this.attempts,\n });\n if (this.retryTimer) clearTimeout(this.retryTimer);\n this.retryTimer = setTimeout(() => {\n this.retryTimer = null;\n this.connect();\n }, jittered);\n }\n\n private schedulePersist(): void {\n if (this.persistTimer) return;\n this.persistTimer = setTimeout(() => {\n this.persistTimer = null;\n const s = syncStore.getState();\n if (!s.synced || s.seq === null) return;\n void saveCache({ seq: s.seq, state: toSyncState(s), savedAt: new Date().toISOString() });\n }, PERSIST_DELAY_MS);\n }\n\n private clearTimers(): void {\n if (this.retryTimer) clearTimeout(this.retryTimer);\n if (this.pingTimer) clearInterval(this.pingTimer);\n if (this.pongTimer) clearTimeout(this.pongTimer);\n if (this.connectTimer) clearTimeout(this.connectTimer);\n this.connectTimer = null;\n this.retryTimer = null;\n this.pingTimer = null;\n this.pongTimer = null;\n }\n\n private onOnline = () => {\n this.attempts = 0;\n this.reconnectNow();\n };\n\n private onVisibility = () => {\n if (document.visibilityState === \"visible\") this.onForeground();\n };\n\n /** Back in the foreground (resume, bfcache restore, focus): check the link now. */\n private onForeground = () => {\n if (this.stopped || document.visibilityState === \"hidden\") return;\n if (this.isLive) {\n if (Date.now() - this.lastFrameAt < RESUME_FRESH_MS) return;\n if (this.send({ type: \"ping\" })) this.armPongTimeout(RESUME_PONG_TIMEOUT_MS);\n return;\n }\n this.attempts = 0;\n this.reconnectNow();\n };\n\n private onOffline = () => {\n syncStore.getState().setConnection({ state: \"offline\" });\n this.ws?.close(4000, \"offline\");\n };\n}\n\nexport function defaultWsUrl(): string {\n const proto = location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return `${proto}//${location.host}/ws/client`;\n}\n\n/** The app-wide client instance. */\nexport const syncClient = new SyncClient(typeof location === \"undefined\" ? \"\" : defaultWsUrl());\n","import type { MeResponse } from \"@switchyard/protocol\";\nimport { create } from \"zustand\";\nimport { startPresence } from \"../presence\";\nimport { ApiClientError, apiGet, onUnauthorized } from \"../sync/api\";\nimport { clearCache } from \"../sync/cache\";\nimport { syncClient } from \"../sync/client\";\nimport { syncStore } from \"../sync/store\";\n\n/** Response of GET /api/me (protocol `MeResponse`). */\nexport type Me = MeResponse;\n\nexport type AuthStatus = \"checking\" | \"authenticated\" | \"unauthenticated\" | \"error\";\n\ninterface AuthStore {\n status: AuthStatus;\n me: Me | null;\n error: unknown;\n /** Why the device was signed out (device revoked, force_logout reason). */\n signedOutReason: string | null;\n}\n\nexport const useAuth = create<AuthStore>()(() => ({\n status: \"checking\",\n me: null,\n error: null,\n signedOutReason: null,\n}));\n\nconst ME_KEY = \"sy:me\";\nconst RETURN_KEY = \"sy:returnTo\";\n\nlet stopPresence: (() => void) | null = null;\n\nfunction startSync(): void {\n syncClient.start();\n stopPresence ??= startPresence(syncClient);\n}\n\nfunction stopSync(): void {\n syncClient.stop();\n stopPresence?.();\n stopPresence = null;\n}\n\n/** The current device id, when known. */\nexport function currentDeviceId(): string | null {\n const me = useAuth.getState().me;\n return me?.device?.id ?? null;\n}\n\n/** GET /api/me; starts live sync when authenticated. */\nexport async function checkAuth(): Promise<AuthStatus> {\n useAuth.setState({ status: \"checking\", error: null });\n try {\n const me = await apiGet<Me>(\"/api/me\");\n try {\n localStorage.setItem(ME_KEY, JSON.stringify(me));\n } catch {\n // ignore\n }\n useAuth.setState({ status: \"authenticated\", me, signedOutReason: null });\n startSync();\n return \"authenticated\";\n } catch (e) {\n if (e instanceof ApiClientError && (e.status === 401 || e.code === \"device_revoked\")) {\n signOut(e.code === \"device_revoked\" ? \"This device was revoked.\" : null);\n return \"unauthenticated\";\n }\n // Coordinator unreachable: run from the offline cache if this device enrolled before.\n const cached = readCachedMe();\n if (cached) {\n useAuth.setState({ status: \"authenticated\", me: cached });\n startSync();\n return \"authenticated\";\n }\n useAuth.setState({ status: \"error\", error: e });\n return \"error\";\n }\n}\n\nfunction readCachedMe(): Me | null {\n try {\n const raw = localStorage.getItem(ME_KEY);\n const me = raw ? (JSON.parse(raw) as Partial<Me>) : null;\n // Ignore caches written before the shape was known.\n return me?.device?.id ? (me as Me) : null;\n } catch {\n return null;\n }\n}\n\n/** Forget the local session and show sign-in. */\nexport function signOut(reason: string | null): void {\n stopSync();\n void clearCache();\n syncStore.getState().reset();\n try {\n localStorage.removeItem(ME_KEY);\n } catch {\n // ignore\n }\n useAuth.setState({ status: \"unauthenticated\", me: null, signedOutReason: reason });\n}\n\n/** Remember where to go after sign-in (deep links survive authentication). */\nexport function saveReturnTo(url: string): void {\n if (url.startsWith(\"/sign-in\") || url.startsWith(\"/enroll\")) return;\n try {\n localStorage.setItem(RETURN_KEY, url);\n } catch {\n // ignore\n }\n}\n\n/** Saved deep link (consumed). Only same-origin paths are honored. */\nexport function takeReturnTo(fromQuery: string | null): string {\n let saved: string | null = null;\n try {\n saved = localStorage.getItem(RETURN_KEY);\n localStorage.removeItem(RETURN_KEY);\n } catch {\n // ignore\n }\n const candidate = fromQuery ?? saved;\n if (\n candidate?.startsWith(\"/\") &&\n !candidate.startsWith(\"//\") &&\n !candidate.startsWith(\"/sign-in\") &&\n !candidate.startsWith(\"/enroll\")\n ) {\n return candidate;\n }\n return \"/\";\n}\n\nlet wired = false;\n/** Wire global auth listeners once. */\nexport function initAuth(): void {\n if (wired) return;\n wired = true;\n onUnauthorized((code) => {\n if (useAuth.getState().status === \"authenticated\") {\n signOut(code === \"device_revoked\" ? \"This device was revoked.\" : null);\n }\n });\n syncClient.onLogout((reason) => {\n signOut(reason === \"unauthorized\" ? null : `Signed out: ${reason}`);\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{_ as e,i as t,m as n,n as r,t as i}from"./jsx-runtime-D13aM9HY.js";import{_ as a,c as o,d as s,f as c,gt as l,h as u,n as d,r as f,s as p,t as m,tt as h,z as g}from"./states-C773NlHo.js";import{t as _}from"./calendar-clock-B8zE4JsX.js";import{t as v}from"./chevron-right-evVLS6hq.js";import{c as y,o as b,t as x}from"./useBreakpoint-BphHBrzF.js";import{t as S}from"./hand-UPoHamrn.js";import{t as C}from"./link-2-CaMlUDRG.js";import{t as w}from"./play-CjoDg3ws.js";import{i as T,t as E}from"./useBackTo-BIrCq3W2.js";import{x as D}from"./index-BHGZTS2e.js";import{t as O}from"./Switch-DAcUcSbH.js";import{C as k,D as A,O as j,T as M,_ as N,c as P,k as F,p as I}from"./describe-CVXeUClF.js";var L={name:`webhook`,size:24,node:[[`path`,{d:`M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 0 1 2 17c.01-.7.2-1.4.57-2`,key:`q3hayz`}],[`path`,{d:`m6 17 3.13-5.78c.53-.97.1-2.18-.5-3.1a4 4 0 1 1 6.89-4.06`,key:`1go1hn`}],[`path`,{d:`m12 6 3.13 5.73C15.66 12.7 16.9 13 18 13a4 4 0 0 1 0 8`,key:`qlwsc0`}]]};L.node;var R=r(L),z=e(n(),1),B=i(),V=`/automations/new`,H=e=>`/automations/${encodeURIComponent(e)}`,U={manual:S,once:y,cron:_,webhook:R,dependency:C};function W(){let e=g(e=>e.hydrated),n=A(),r=g(e=>e.projects),i=g(e=>e.hosts),a=g(e=>e.tasks),s=x(),[u,_]=(0,z.useState)({status:`idle`}),v=(0,z.useCallback)(async()=>{_({status:`loading`});try{let e=await l(`/api/automations`),t=Array.isArray(e)?e:e&&typeof e==`object`&&Array.isArray(e.items)?e.items:null,n=t?h.array().safeParse(t):null;if(!n?.success){_({status:`fatal`,error:Error(`The coordinator returned automations in an unexpected format.`)});return}M.getState().putMany(n.data),_({status:`done`})}catch(e){_({status:`error`,error:e})}},[]);(0,z.useEffect)(()=>{e||v()},[e,v]);let y=(0,z.useMemo)(()=>Object.values(n).sort((e,t)=>e.name.localeCompare(t.name)),[n]),b=e||u.status===`done`,S;return S=b?y.length===0?(0,B.jsx)(m,{icon:(0,B.jsx)(D,{className:`size-8`}),title:`No automations yet`,description:`Automations create tasks on a schedule, from a webhook, or when another task is done.`,action:{label:`Create an automation`,to:V,icon:(0,B.jsx)(T,{className:`size-4`})}}):(0,B.jsx)(K,{automations:y,projects:r,hosts:i,tasks:a,table:s===`desktop`}):u.status===`fatal`?(0,B.jsx)(f,{error:u.error}):u.status===`error`?(0,B.jsx)(d,{title:`Could not load automations`,error:u.error,onRetry:()=>void v()}):(0,B.jsx)(o,{label:`Loading automations…`}),(0,B.jsxs)(`div`,{className:`flex w-full min-w-0 flex-col gap-4 px-4 py-4 md:px-6`,children:[(0,B.jsxs)(`header`,{className:`flex flex-wrap items-center justify-between gap-3`,children:[(0,B.jsxs)(`div`,{className:`min-w-0`,children:[(0,B.jsx)(`h1`,{className:`text-lg font-semibold`,children:`Automations`}),(0,B.jsx)(`p`,{className:`text-sm text-muted`,children:`Scheduled, webhook and dependency automations. Routine successes stay in each automation's history.`})]}),(0,B.jsxs)(t,{to:V,state:E,className:c(`primary`),children:[(0,B.jsx)(T,{"aria-hidden":!0,className:`size-4`}),`New automation`]})]}),(0,B.jsx)(p,{subject:`automations`,consequence:`Changes are sent once the coordinator is reachable again.`}),S]})}var G=`grid grid-cols-[minmax(0,2.4fr)_minmax(0,1fr)_5.5rem_minmax(0,1.2fr)_minmax(0,1.4fr)_7.5rem_7rem_auto] items-center gap-3`;function K({automations:e,projects:t,hosts:n,tasks:r,table:i}){let{busy:o,toggle:s}=F(),{busy:c,run:l}=j(),u=(0,z.useCallback)(e=>r[e]?.title,[r]);return(0,B.jsxs)(`div`,{className:`min-w-0 overflow-hidden rounded-lg border border-border bg-surface`,children:[i&&(0,B.jsxs)(`div`,{"aria-hidden":!0,className:a(G,`border-b border-border bg-surface-2 px-4 py-2 text-xs font-medium text-muted`),children:[(0,B.jsx)(`span`,{children:`Automation`}),(0,B.jsx)(`span`,{children:`Project`}),(0,B.jsx)(`span`,{children:`Mode`}),(0,B.jsx)(`span`,{children:`Next occurrence`}),(0,B.jsx)(`span`,{children:`Last outcome`}),(0,B.jsx)(`span`,{children:`Failures`}),(0,B.jsx)(`span`,{children:`Enabled`}),(0,B.jsx)(`span`,{className:`sr-only`,children:`Actions`})]}),(0,B.jsx)(`ul`,{"data-testid":`automations-list`,className:`divide-y divide-border`,children:e.map(e=>{let r=t[e.projectId],a=e.hostId??r?.defaultHostId??null,d=a?n[a]:void 0,f={automation:e,projectName:r?.name??`Unknown project`,host:d,trigger:P(e.trigger,{taskTitle:u}),toggling:!!o[e.id],running:!!c[e.id],onToggle:t=>void s(e,t),onRun:()=>void l(e)};return(0,B.jsx)(`li`,{"data-testid":`automation-row`,"data-automation-id":e.id,children:i?(0,B.jsx)(Z,{...f}):(0,B.jsx)(Q,{...f})},e.id)})})]})}function q({automation:e,trigger:t}){let n=U[e.trigger.kind];return(0,B.jsxs)(`span`,{className:`flex min-w-0 items-start gap-1.5 text-sm text-muted`,children:[(0,B.jsx)(n,{"aria-hidden":!0,className:`mt-0.5 size-3.5 shrink-0`}),(0,B.jsx)(`span`,{className:`min-w-0 break-words`,children:t})]})}function J({automation:e}){if(!e.enabled)return(0,B.jsx)(`span`,{className:`text-sm text-muted`,children:`Disabled`});if(!e.nextAt){let t=e.trigger.kind;return(0,B.jsx)(`span`,{className:`text-sm text-muted`,children:t===`manual`?`On demand`:t===`webhook`?`On webhook`:t===`dependency`?`When the task is done`:`None scheduled`})}return(0,B.jsxs)(`span`,{className:`flex min-w-0 flex-col text-sm`,children:[(0,B.jsx)(`span`,{children:u(e.nextAt)}),(0,B.jsx)(`time`,{dateTime:e.nextAt,className:`text-xs text-muted`,children:I(e.nextAt)})]})}function Y({host:e}){return!e||e.connectivity===`connected`?null:(0,B.jsxs)(`span`,{className:`flex items-center gap-1 text-xs text-danger`,children:[(0,B.jsx)(b,{"aria-hidden":!0,className:`size-3.5 shrink-0`}),(0,B.jsxs)(`span`,{className:`min-w-0 break-words`,children:[`Host `,e.name,` `,e.connectivity===`reconnecting`?`reconnecting`:`unreachable`]})]})}function X({running:e,onRun:t,name:n}){return(0,B.jsx)(s,{size:`sm`,busy:e,icon:(0,B.jsx)(w,{className:`size-3.5`}),onClick:t,"aria-label":`Run ${n} now`,children:`Run now`})}function Z(e){let n=e.automation;return(0,B.jsxs)(`div`,{className:a(G,`px-4 py-3`),children:[(0,B.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-0.5`,children:[(0,B.jsx)(t,{to:H(n.id),state:E,className:`truncate font-medium text-text hover:underline focus-visible:underline`,children:n.name}),(0,B.jsx)(q,{automation:n,trigger:e.trigger})]}),(0,B.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-0.5`,children:[(0,B.jsx)(`span`,{className:`sr-only`,children:`Project: `}),(0,B.jsx)(`span`,{className:`truncate text-sm`,children:e.projectName}),(0,B.jsx)(Y,{host:e.host})]}),(0,B.jsxs)(`span`,{className:`text-sm`,children:[(0,B.jsx)(`span`,{className:`sr-only`,children:`Mode: `}),N(n.mode)]}),(0,B.jsxs)(`div`,{className:`min-w-0`,children:[(0,B.jsx)(`span`,{className:`sr-only`,children:`Next occurrence: `}),(0,B.jsx)(J,{automation:n})]}),(0,B.jsxs)(`div`,{className:`min-w-0 text-sm break-words`,children:[(0,B.jsx)(`span`,{className:`sr-only`,children:`Last outcome: `}),n.lastOutcome??(0,B.jsx)(`span`,{className:`text-muted`,children:`No runs yet`})]}),(0,B.jsx)(k,{count:n.failureCount}),(0,B.jsx)(O,{checked:n.enabled,onChange:e.onToggle,busy:e.toggling,label:`Enable ${n.name}`,onText:`Enabled`,offText:`Disabled`}),(0,B.jsxs)(`div`,{className:`flex items-center gap-1`,children:[(0,B.jsx)(X,{running:e.running,onRun:e.onRun,name:n.name}),(0,B.jsx)(t,{to:H(n.id),state:E,"aria-label":`Edit ${n.name}`,title:`Edit`,className:`tap inline-flex size-9 items-center justify-center rounded-md text-muted hover:bg-surface-2 hover:text-text`,children:(0,B.jsx)(v,{"aria-hidden":!0,className:`size-4`})})]})]})}function Q(e){let n=e.automation;return(0,B.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-2 px-4 py-3`,children:[(0,B.jsxs)(`div`,{className:`flex min-w-0 items-start justify-between gap-2`,children:[(0,B.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-0.5`,children:[(0,B.jsx)(t,{to:H(n.id),state:E,className:`font-medium break-words text-text hover:underline focus-visible:underline`,children:n.name}),(0,B.jsx)(q,{automation:n,trigger:e.trigger})]}),(0,B.jsx)(O,{checked:n.enabled,onChange:e.onToggle,busy:e.toggling,label:`Enable ${n.name}`,onText:`Enabled`,offText:`Disabled`})]}),(0,B.jsxs)(`dl`,{className:`grid min-w-0 grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-1 text-sm`,children:[(0,B.jsx)(`dt`,{className:`text-muted`,children:`Project`}),(0,B.jsxs)(`dd`,{className:`flex min-w-0 flex-col break-words`,children:[e.projectName,(0,B.jsx)(Y,{host:e.host})]}),(0,B.jsx)(`dt`,{className:`text-muted`,children:`Mode`}),(0,B.jsx)(`dd`,{children:N(n.mode)}),(0,B.jsx)(`dt`,{className:`text-muted`,children:`Next`}),(0,B.jsx)(`dd`,{className:`min-w-0`,children:(0,B.jsx)(J,{automation:n})}),(0,B.jsx)(`dt`,{className:`text-muted`,children:`Last outcome`}),(0,B.jsx)(`dd`,{className:`min-w-0 break-words`,children:n.lastOutcome??(0,B.jsx)(`span`,{className:`text-muted`,children:`No runs yet`})}),(0,B.jsx)(`dt`,{className:`text-muted`,children:`Failures`}),(0,B.jsx)(`dd`,{children:(0,B.jsx)(k,{count:n.failureCount})})]}),(0,B.jsxs)(`div`,{className:`flex flex-wrap gap-2`,children:[(0,B.jsx)(X,{running:e.running,onRun:e.onRun,name:n.name}),(0,B.jsxs)(t,{to:H(n.id),state:E,className:c(`ghost`,`sm`),children:[`Edit`,(0,B.jsx)(v,{"aria-hidden":!0,className:`size-3.5`})]})]})]})}export{W as default};
|
|
2
|
+
//# sourceMappingURL=automations-BzpsJoa3.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"automations-Cw5Gacgm.js","names":["useState","useCallback","useMemo","__toESM","Link","require_react","createLucideIcon","require_jsx_runtime","cn","LoadingState","Button","buttonClasses","apiGet","formatRelative","ErrorState","FatalError","LastKnownNote","EmptyState","AutomationView","useSyncStore","CalendarClock","ChevronRight","Hand","Link2","Play","Plus","BACK_STATE","Workflow","WifiOff","Clock","useBreakpoint","Switch","FailureCount","useMergedAutomations","useRunNow","useLocalAutomations","modeLabel","describeTrigger","useToggleEnabled","formatAbsolute","__iconData","Webhook","import_react","import_jsx_runtime","NEW_PATH","editPath","id","TRIGGER_ICONS","AutomationsView","hydrated","s","automations","projects","hosts","tasks","bp","fetchState","setFetchState","load","res","list","parsed","rows","a","b","haveData","body","AutomationRows","TABLE_COLS","toggling","running","taskTitle","table","project","hostId","host","props","toggle","next","run","TableRow","CardRow","TriggerText","Icon","automation","trigger","NextOccurrence","k","HostWarning","RunButton","onRun","name","p"],"sources":["../../../../node_modules/.pnpm/lucide-react@1.61.0_@types+react@19.3.0_react@19.3.1/node_modules/lucide-react/dist/esm/icons/webhook.mjs","../../src/features/automations/index.tsx"],"sourcesContent":["/**\n * @license lucide-react v1.61.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconData = {\n name: \"webhook\",\n size: 24,\n node: [\n [\n \"path\",\n {\n d: \"M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 0 1 2 17c.01-.7.2-1.4.57-2\",\n key: \"q3hayz\"\n }\n ],\n [\"path\", { d: \"m6 17 3.13-5.78c.53-.97.1-2.18-.5-3.1a4 4 0 1 1 6.89-4.06\", key: \"1go1hn\" }],\n [\"path\", { d: \"m12 6 3.13 5.73C15.66 12.7 16.9 13 18 13a4 4 0 0 1 0 8\", key: \"qlwsc0\" }]\n ]\n};\n__iconData.node;\nconst Webhook = createLucideIcon(__iconData);\n\nexport { __iconData, Webhook as default };\n//# sourceMappingURL=webhook.mjs.map\n","import {\n type Automation,\n AutomationView,\n type Host,\n type Project,\n type Task,\n} from \"@switchyard/protocol\";\nimport {\n CalendarClock,\n ChevronRight,\n Clock,\n Hand,\n Link2,\n type LucideIcon,\n Play,\n Plus,\n Webhook,\n WifiOff,\n Workflow,\n} from \"lucide-react\";\nimport { type ReactNode, useCallback, useEffect, useMemo, useState } from \"react\";\nimport { Link } from \"react-router\";\nimport { BACK_STATE } from \"../../app/useBackTo\";\nimport { useBreakpoint } from \"../../app/useBreakpoint\";\nimport { Button, buttonClasses } from \"../../components/ui/Button\";\nimport { Switch } from \"../../components/ui/Switch\";\nimport {\n EmptyState,\n ErrorState,\n FatalError,\n LastKnownNote,\n LoadingState,\n} from \"../../components/ui/states\";\nimport { cn } from \"../../lib/cn\";\nimport { formatRelative } from \"../../lib/time\";\nimport { apiGet } from \"../../sync/api\";\nimport { useSyncStore } from \"../../sync/store\";\nimport {\n FailureCount,\n useLocalAutomations,\n useMergedAutomations,\n useRunNow,\n useToggleEnabled,\n} from \"./controls\";\nimport { describeTrigger, formatAbsolute, modeLabel, type TriggerKind } from \"./describe\";\n\nconst NEW_PATH = \"/automations/new\";\nconst editPath = (id: string) => `/automations/${encodeURIComponent(id)}`;\n\nconst TRIGGER_ICONS: Record<TriggerKind, LucideIcon> = {\n manual: Hand,\n once: Clock,\n cron: CalendarClock,\n webhook: Webhook,\n dependency: Link2,\n};\n\ntype FetchState =\n | { status: \"idle\" | \"loading\" | \"done\" }\n | { status: \"error\"; error: unknown }\n | { status: \"fatal\"; error: unknown };\n\n/** Automations list (spec 15): enabled state, trigger, next occurrence, last outcome, failures. */\nexport default function AutomationsView() {\n const hydrated = useSyncStore((s) => s.hydrated);\n const automations = useMergedAutomations();\n const projects = useSyncStore((s) => s.projects);\n const hosts = useSyncStore((s) => s.hosts);\n const tasks = useSyncStore((s) => s.tasks);\n const bp = useBreakpoint();\n const [fetchState, setFetchState] = useState<FetchState>({ status: \"idle\" });\n\n // REST fallback while the sync snapshot has not arrived (and for retry after a failure).\n const load = useCallback(async () => {\n setFetchState({ status: \"loading\" });\n try {\n const res = await apiGet<unknown>(\"/api/automations\");\n const list = Array.isArray(res)\n ? res\n : res && typeof res === \"object\" && Array.isArray((res as { items?: unknown }).items)\n ? (res as { items: unknown[] }).items\n : null;\n const parsed = list ? AutomationView.array().safeParse(list) : null;\n if (!parsed?.success) {\n setFetchState({\n status: \"fatal\",\n error: new Error(\"The coordinator returned automations in an unexpected format.\"),\n });\n return;\n }\n useLocalAutomations.getState().putMany(parsed.data);\n setFetchState({ status: \"done\" });\n } catch (e) {\n setFetchState({ status: \"error\", error: e });\n }\n }, []);\n\n useEffect(() => {\n if (!hydrated) void load();\n }, [hydrated, load]);\n\n const rows = useMemo(\n () => Object.values(automations).sort((a, b) => a.name.localeCompare(b.name)),\n [automations],\n );\n\n const haveData = hydrated || fetchState.status === \"done\";\n\n let body: ReactNode;\n if (!haveData) {\n if (fetchState.status === \"fatal\") body = <FatalError error={fetchState.error} />;\n else if (fetchState.status === \"error\")\n body = (\n <ErrorState\n title=\"Could not load automations\"\n error={fetchState.error}\n onRetry={() => void load()}\n />\n );\n else body = <LoadingState label=\"Loading automations…\" />;\n } else if (rows.length === 0) {\n body = (\n <EmptyState\n icon={<Workflow className=\"size-8\" />}\n title=\"No automations yet\"\n description=\"Automations create tasks on a schedule, from a webhook, or when another task is done.\"\n action={{ label: \"Create an automation\", to: NEW_PATH, icon: <Plus className=\"size-4\" /> }}\n />\n );\n } else {\n body = (\n <AutomationRows\n automations={rows}\n projects={projects}\n hosts={hosts}\n tasks={tasks}\n table={bp === \"desktop\"}\n />\n );\n }\n\n return (\n <div className=\"flex w-full min-w-0 flex-col gap-4 px-4 py-4 md:px-6\">\n <header className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <h1 className=\"text-lg font-semibold\">Automations</h1>\n <p className=\"text-sm text-muted\">\n Scheduled, webhook and dependency automations. Routine successes stay in each\n automation's history.\n </p>\n </div>\n <Link to={NEW_PATH} state={BACK_STATE} className={buttonClasses(\"primary\")}>\n <Plus aria-hidden className=\"size-4\" />\n New automation\n </Link>\n </header>\n <LastKnownNote\n subject=\"automations\"\n consequence=\"Changes are sent once the coordinator is reachable again.\"\n />\n {body}\n </div>\n );\n}\n\nconst TABLE_COLS =\n \"grid grid-cols-[minmax(0,2.4fr)_minmax(0,1fr)_5.5rem_minmax(0,1.2fr)_minmax(0,1.4fr)_7.5rem_7rem_auto] items-center gap-3\";\n\nfunction AutomationRows({\n automations,\n projects,\n hosts,\n tasks,\n table,\n}: {\n automations: Automation[];\n projects: Record<string, Project>;\n hosts: Record<string, Host>;\n tasks: Record<string, Task>;\n table: boolean;\n}) {\n const { busy: toggling, toggle } = useToggleEnabled();\n const { busy: running, run } = useRunNow();\n const taskTitle = useCallback((id: string) => tasks[id]?.title, [tasks]);\n\n return (\n <div className=\"min-w-0 overflow-hidden rounded-lg border border-border bg-surface\">\n {table && (\n <div\n aria-hidden\n className={cn(\n TABLE_COLS,\n \"border-b border-border bg-surface-2 px-4 py-2 text-xs font-medium text-muted\",\n )}\n >\n <span>Automation</span>\n <span>Project</span>\n <span>Mode</span>\n <span>Next occurrence</span>\n <span>Last outcome</span>\n <span>Failures</span>\n <span>Enabled</span>\n <span className=\"sr-only\">Actions</span>\n </div>\n )}\n <ul data-testid=\"automations-list\" className=\"divide-y divide-border\">\n {automations.map((a) => {\n const project = projects[a.projectId];\n const hostId = a.hostId ?? project?.defaultHostId ?? null;\n const host = hostId ? hosts[hostId] : undefined;\n const props: RowProps = {\n automation: a,\n projectName: project?.name ?? \"Unknown project\",\n host,\n trigger: describeTrigger(a.trigger, { taskTitle }),\n toggling: !!toggling[a.id],\n running: !!running[a.id],\n onToggle: (next) => void toggle(a, next),\n onRun: () => void run(a),\n };\n return (\n <li key={a.id} data-testid=\"automation-row\" data-automation-id={a.id}>\n {table ? <TableRow {...props} /> : <CardRow {...props} />}\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n\ninterface RowProps {\n automation: Automation;\n projectName: string;\n host: Host | undefined;\n trigger: string;\n toggling: boolean;\n running: boolean;\n onToggle: (next: boolean) => void;\n onRun: () => void;\n}\n\nfunction TriggerText({ automation, trigger }: { automation: Automation; trigger: string }) {\n const Icon = TRIGGER_ICONS[automation.trigger.kind];\n return (\n <span className=\"flex min-w-0 items-start gap-1.5 text-sm text-muted\">\n <Icon aria-hidden className=\"mt-0.5 size-3.5 shrink-0\" />\n <span className=\"min-w-0 break-words\">{trigger}</span>\n </span>\n );\n}\n\nfunction NextOccurrence({ automation }: { automation: Automation }) {\n if (!automation.enabled) return <span className=\"text-sm text-muted\">Disabled</span>;\n if (!automation.nextAt) {\n const k = automation.trigger.kind;\n const text =\n k === \"manual\"\n ? \"On demand\"\n : k === \"webhook\"\n ? \"On webhook\"\n : k === \"dependency\"\n ? \"When the task is done\"\n : \"None scheduled\";\n return <span className=\"text-sm text-muted\">{text}</span>;\n }\n return (\n <span className=\"flex min-w-0 flex-col text-sm\">\n <span>{formatRelative(automation.nextAt)}</span>\n <time dateTime={automation.nextAt} className=\"text-xs text-muted\">\n {formatAbsolute(automation.nextAt)}\n </time>\n </span>\n );\n}\n\nfunction HostWarning({ host }: { host: Host | undefined }) {\n if (!host || host.connectivity === \"connected\") return null;\n return (\n <span className=\"flex items-center gap-1 text-xs text-danger\">\n <WifiOff aria-hidden className=\"size-3.5 shrink-0\" />\n <span className=\"min-w-0 break-words\">\n Host {host.name} {host.connectivity === \"reconnecting\" ? \"reconnecting\" : \"unreachable\"}\n </span>\n </span>\n );\n}\n\nfunction RunButton({\n running,\n onRun,\n name,\n}: {\n running: boolean;\n onRun: () => void;\n name: string;\n}) {\n return (\n <Button\n size=\"sm\"\n busy={running}\n icon={<Play className=\"size-3.5\" />}\n onClick={onRun}\n aria-label={`Run ${name} now`}\n >\n Run now\n </Button>\n );\n}\n\nfunction TableRow(p: RowProps) {\n const a = p.automation;\n return (\n <div className={cn(TABLE_COLS, \"px-4 py-3\")}>\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <Link\n to={editPath(a.id)}\n state={BACK_STATE}\n className=\"truncate font-medium text-text hover:underline focus-visible:underline\"\n >\n {a.name}\n </Link>\n <TriggerText automation={a} trigger={p.trigger} />\n </div>\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <span className=\"sr-only\">Project: </span>\n <span className=\"truncate text-sm\">{p.projectName}</span>\n <HostWarning host={p.host} />\n </div>\n <span className=\"text-sm\">\n <span className=\"sr-only\">Mode: </span>\n {modeLabel(a.mode)}\n </span>\n <div className=\"min-w-0\">\n <span className=\"sr-only\">Next occurrence: </span>\n <NextOccurrence automation={a} />\n </div>\n <div className=\"min-w-0 text-sm break-words\">\n <span className=\"sr-only\">Last outcome: </span>\n {a.lastOutcome ?? <span className=\"text-muted\">No runs yet</span>}\n </div>\n <FailureCount count={a.failureCount} />\n <Switch\n checked={a.enabled}\n onChange={p.onToggle}\n busy={p.toggling}\n label={`Enable ${a.name}`}\n onText=\"Enabled\"\n offText=\"Disabled\"\n />\n <div className=\"flex items-center gap-1\">\n <RunButton running={p.running} onRun={p.onRun} name={a.name} />\n <Link\n to={editPath(a.id)}\n state={BACK_STATE}\n aria-label={`Edit ${a.name}`}\n title=\"Edit\"\n className=\"tap inline-flex size-9 items-center justify-center rounded-md text-muted hover:bg-surface-2 hover:text-text\"\n >\n <ChevronRight aria-hidden className=\"size-4\" />\n </Link>\n </div>\n </div>\n );\n}\n\nfunction CardRow(p: RowProps) {\n const a = p.automation;\n return (\n <div className=\"flex min-w-0 flex-col gap-2 px-4 py-3\">\n <div className=\"flex min-w-0 items-start justify-between gap-2\">\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <Link\n to={editPath(a.id)}\n state={BACK_STATE}\n className=\"font-medium break-words text-text hover:underline focus-visible:underline\"\n >\n {a.name}\n </Link>\n <TriggerText automation={a} trigger={p.trigger} />\n </div>\n <Switch\n checked={a.enabled}\n onChange={p.onToggle}\n busy={p.toggling}\n label={`Enable ${a.name}`}\n onText=\"Enabled\"\n offText=\"Disabled\"\n />\n </div>\n <dl className=\"grid min-w-0 grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-1 text-sm\">\n <dt className=\"text-muted\">Project</dt>\n <dd className=\"flex min-w-0 flex-col break-words\">\n {p.projectName}\n <HostWarning host={p.host} />\n </dd>\n <dt className=\"text-muted\">Mode</dt>\n <dd>{modeLabel(a.mode)}</dd>\n <dt className=\"text-muted\">Next</dt>\n <dd className=\"min-w-0\">\n <NextOccurrence automation={a} />\n </dd>\n <dt className=\"text-muted\">Last outcome</dt>\n <dd className=\"min-w-0 break-words\">\n {a.lastOutcome ?? <span className=\"text-muted\">No runs yet</span>}\n </dd>\n <dt className=\"text-muted\">Failures</dt>\n <dd>\n <FailureCount count={a.failureCount} />\n </dd>\n </dl>\n <div className=\"flex flex-wrap gap-2\">\n <RunButton running={p.running} onRun={p.onRun} name={a.name} />\n <Link to={editPath(a.id)} state={BACK_STATE} className={buttonClasses(\"ghost\", \"sm\")}>\n Edit\n <ChevronRight aria-hidden className=\"size-3.5\" />\n </Link>\n </div>\n </div>\n );\n}\n"],"ignoreList":[0],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"automations-BzpsJoa3.js","names":["useState","useCallback","useMemo","__toESM","Link","require_react","createLucideIcon","require_jsx_runtime","cn","LoadingState","Button","buttonClasses","apiGet","formatRelative","ErrorState","FatalError","LastKnownNote","EmptyState","AutomationView","useSyncStore","CalendarClock","ChevronRight","Clock","WifiOff","useBreakpoint","Hand","Link2","Play","Plus","BACK_STATE","Workflow","Switch","FailureCount","useMergedAutomations","useRunNow","useLocalAutomations","modeLabel","describeTrigger","useToggleEnabled","formatAbsolute","__iconData","Webhook","import_react","import_jsx_runtime","NEW_PATH","editPath","id","TRIGGER_ICONS","AutomationsView","hydrated","s","automations","projects","hosts","tasks","bp","fetchState","setFetchState","load","res","list","parsed","rows","a","b","haveData","body","AutomationRows","TABLE_COLS","toggling","running","taskTitle","table","project","hostId","host","props","toggle","next","run","TableRow","CardRow","TriggerText","Icon","automation","trigger","NextOccurrence","k","HostWarning","RunButton","onRun","name","p"],"sources":["../../../../node_modules/.pnpm/lucide-react@1.61.0_@types+react@19.3.0_react@19.3.1/node_modules/lucide-react/dist/esm/icons/webhook.mjs","../../src/features/automations/index.tsx"],"sourcesContent":["/**\n * @license lucide-react v1.61.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconData = {\n name: \"webhook\",\n size: 24,\n node: [\n [\n \"path\",\n {\n d: \"M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 0 1 2 17c.01-.7.2-1.4.57-2\",\n key: \"q3hayz\"\n }\n ],\n [\"path\", { d: \"m6 17 3.13-5.78c.53-.97.1-2.18-.5-3.1a4 4 0 1 1 6.89-4.06\", key: \"1go1hn\" }],\n [\"path\", { d: \"m12 6 3.13 5.73C15.66 12.7 16.9 13 18 13a4 4 0 0 1 0 8\", key: \"qlwsc0\" }]\n ]\n};\n__iconData.node;\nconst Webhook = createLucideIcon(__iconData);\n\nexport { __iconData, Webhook as default };\n//# sourceMappingURL=webhook.mjs.map\n","import {\n type Automation,\n AutomationView,\n type Host,\n type Project,\n type Task,\n} from \"@switchyard/protocol\";\nimport {\n CalendarClock,\n ChevronRight,\n Clock,\n Hand,\n Link2,\n type LucideIcon,\n Play,\n Plus,\n Webhook,\n WifiOff,\n Workflow,\n} from \"lucide-react\";\nimport { type ReactNode, useCallback, useEffect, useMemo, useState } from \"react\";\nimport { Link } from \"react-router\";\nimport { BACK_STATE } from \"../../app/useBackTo\";\nimport { useBreakpoint } from \"../../app/useBreakpoint\";\nimport { Button, buttonClasses } from \"../../components/ui/Button\";\nimport { Switch } from \"../../components/ui/Switch\";\nimport {\n EmptyState,\n ErrorState,\n FatalError,\n LastKnownNote,\n LoadingState,\n} from \"../../components/ui/states\";\nimport { cn } from \"../../lib/cn\";\nimport { formatRelative } from \"../../lib/time\";\nimport { apiGet } from \"../../sync/api\";\nimport { useSyncStore } from \"../../sync/store\";\nimport {\n FailureCount,\n useLocalAutomations,\n useMergedAutomations,\n useRunNow,\n useToggleEnabled,\n} from \"./controls\";\nimport { describeTrigger, formatAbsolute, modeLabel, type TriggerKind } from \"./describe\";\n\nconst NEW_PATH = \"/automations/new\";\nconst editPath = (id: string) => `/automations/${encodeURIComponent(id)}`;\n\nconst TRIGGER_ICONS: Record<TriggerKind, LucideIcon> = {\n manual: Hand,\n once: Clock,\n cron: CalendarClock,\n webhook: Webhook,\n dependency: Link2,\n};\n\ntype FetchState =\n | { status: \"idle\" | \"loading\" | \"done\" }\n | { status: \"error\"; error: unknown }\n | { status: \"fatal\"; error: unknown };\n\n/** Automations list (spec 15): enabled state, trigger, next occurrence, last outcome, failures. */\nexport default function AutomationsView() {\n const hydrated = useSyncStore((s) => s.hydrated);\n const automations = useMergedAutomations();\n const projects = useSyncStore((s) => s.projects);\n const hosts = useSyncStore((s) => s.hosts);\n const tasks = useSyncStore((s) => s.tasks);\n const bp = useBreakpoint();\n const [fetchState, setFetchState] = useState<FetchState>({ status: \"idle\" });\n\n // REST fallback while the sync snapshot has not arrived (and for retry after a failure).\n const load = useCallback(async () => {\n setFetchState({ status: \"loading\" });\n try {\n const res = await apiGet<unknown>(\"/api/automations\");\n const list = Array.isArray(res)\n ? res\n : res && typeof res === \"object\" && Array.isArray((res as { items?: unknown }).items)\n ? (res as { items: unknown[] }).items\n : null;\n const parsed = list ? AutomationView.array().safeParse(list) : null;\n if (!parsed?.success) {\n setFetchState({\n status: \"fatal\",\n error: new Error(\"The coordinator returned automations in an unexpected format.\"),\n });\n return;\n }\n useLocalAutomations.getState().putMany(parsed.data);\n setFetchState({ status: \"done\" });\n } catch (e) {\n setFetchState({ status: \"error\", error: e });\n }\n }, []);\n\n useEffect(() => {\n if (!hydrated) void load();\n }, [hydrated, load]);\n\n const rows = useMemo(\n () => Object.values(automations).sort((a, b) => a.name.localeCompare(b.name)),\n [automations],\n );\n\n const haveData = hydrated || fetchState.status === \"done\";\n\n let body: ReactNode;\n if (!haveData) {\n if (fetchState.status === \"fatal\") body = <FatalError error={fetchState.error} />;\n else if (fetchState.status === \"error\")\n body = (\n <ErrorState\n title=\"Could not load automations\"\n error={fetchState.error}\n onRetry={() => void load()}\n />\n );\n else body = <LoadingState label=\"Loading automations…\" />;\n } else if (rows.length === 0) {\n body = (\n <EmptyState\n icon={<Workflow className=\"size-8\" />}\n title=\"No automations yet\"\n description=\"Automations create tasks on a schedule, from a webhook, or when another task is done.\"\n action={{ label: \"Create an automation\", to: NEW_PATH, icon: <Plus className=\"size-4\" /> }}\n />\n );\n } else {\n body = (\n <AutomationRows\n automations={rows}\n projects={projects}\n hosts={hosts}\n tasks={tasks}\n table={bp === \"desktop\"}\n />\n );\n }\n\n return (\n <div className=\"flex w-full min-w-0 flex-col gap-4 px-4 py-4 md:px-6\">\n <header className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <h1 className=\"text-lg font-semibold\">Automations</h1>\n <p className=\"text-sm text-muted\">\n Scheduled, webhook and dependency automations. Routine successes stay in each\n automation's history.\n </p>\n </div>\n <Link to={NEW_PATH} state={BACK_STATE} className={buttonClasses(\"primary\")}>\n <Plus aria-hidden className=\"size-4\" />\n New automation\n </Link>\n </header>\n <LastKnownNote\n subject=\"automations\"\n consequence=\"Changes are sent once the coordinator is reachable again.\"\n />\n {body}\n </div>\n );\n}\n\nconst TABLE_COLS =\n \"grid grid-cols-[minmax(0,2.4fr)_minmax(0,1fr)_5.5rem_minmax(0,1.2fr)_minmax(0,1.4fr)_7.5rem_7rem_auto] items-center gap-3\";\n\nfunction AutomationRows({\n automations,\n projects,\n hosts,\n tasks,\n table,\n}: {\n automations: Automation[];\n projects: Record<string, Project>;\n hosts: Record<string, Host>;\n tasks: Record<string, Task>;\n table: boolean;\n}) {\n const { busy: toggling, toggle } = useToggleEnabled();\n const { busy: running, run } = useRunNow();\n const taskTitle = useCallback((id: string) => tasks[id]?.title, [tasks]);\n\n return (\n <div className=\"min-w-0 overflow-hidden rounded-lg border border-border bg-surface\">\n {table && (\n <div\n aria-hidden\n className={cn(\n TABLE_COLS,\n \"border-b border-border bg-surface-2 px-4 py-2 text-xs font-medium text-muted\",\n )}\n >\n <span>Automation</span>\n <span>Project</span>\n <span>Mode</span>\n <span>Next occurrence</span>\n <span>Last outcome</span>\n <span>Failures</span>\n <span>Enabled</span>\n <span className=\"sr-only\">Actions</span>\n </div>\n )}\n <ul data-testid=\"automations-list\" className=\"divide-y divide-border\">\n {automations.map((a) => {\n const project = projects[a.projectId];\n const hostId = a.hostId ?? project?.defaultHostId ?? null;\n const host = hostId ? hosts[hostId] : undefined;\n const props: RowProps = {\n automation: a,\n projectName: project?.name ?? \"Unknown project\",\n host,\n trigger: describeTrigger(a.trigger, { taskTitle }),\n toggling: !!toggling[a.id],\n running: !!running[a.id],\n onToggle: (next) => void toggle(a, next),\n onRun: () => void run(a),\n };\n return (\n <li key={a.id} data-testid=\"automation-row\" data-automation-id={a.id}>\n {table ? <TableRow {...props} /> : <CardRow {...props} />}\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n\ninterface RowProps {\n automation: Automation;\n projectName: string;\n host: Host | undefined;\n trigger: string;\n toggling: boolean;\n running: boolean;\n onToggle: (next: boolean) => void;\n onRun: () => void;\n}\n\nfunction TriggerText({ automation, trigger }: { automation: Automation; trigger: string }) {\n const Icon = TRIGGER_ICONS[automation.trigger.kind];\n return (\n <span className=\"flex min-w-0 items-start gap-1.5 text-sm text-muted\">\n <Icon aria-hidden className=\"mt-0.5 size-3.5 shrink-0\" />\n <span className=\"min-w-0 break-words\">{trigger}</span>\n </span>\n );\n}\n\nfunction NextOccurrence({ automation }: { automation: Automation }) {\n if (!automation.enabled) return <span className=\"text-sm text-muted\">Disabled</span>;\n if (!automation.nextAt) {\n const k = automation.trigger.kind;\n const text =\n k === \"manual\"\n ? \"On demand\"\n : k === \"webhook\"\n ? \"On webhook\"\n : k === \"dependency\"\n ? \"When the task is done\"\n : \"None scheduled\";\n return <span className=\"text-sm text-muted\">{text}</span>;\n }\n return (\n <span className=\"flex min-w-0 flex-col text-sm\">\n <span>{formatRelative(automation.nextAt)}</span>\n <time dateTime={automation.nextAt} className=\"text-xs text-muted\">\n {formatAbsolute(automation.nextAt)}\n </time>\n </span>\n );\n}\n\nfunction HostWarning({ host }: { host: Host | undefined }) {\n if (!host || host.connectivity === \"connected\") return null;\n return (\n <span className=\"flex items-center gap-1 text-xs text-danger\">\n <WifiOff aria-hidden className=\"size-3.5 shrink-0\" />\n <span className=\"min-w-0 break-words\">\n Host {host.name} {host.connectivity === \"reconnecting\" ? \"reconnecting\" : \"unreachable\"}\n </span>\n </span>\n );\n}\n\nfunction RunButton({\n running,\n onRun,\n name,\n}: {\n running: boolean;\n onRun: () => void;\n name: string;\n}) {\n return (\n <Button\n size=\"sm\"\n busy={running}\n icon={<Play className=\"size-3.5\" />}\n onClick={onRun}\n aria-label={`Run ${name} now`}\n >\n Run now\n </Button>\n );\n}\n\nfunction TableRow(p: RowProps) {\n const a = p.automation;\n return (\n <div className={cn(TABLE_COLS, \"px-4 py-3\")}>\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <Link\n to={editPath(a.id)}\n state={BACK_STATE}\n className=\"truncate font-medium text-text hover:underline focus-visible:underline\"\n >\n {a.name}\n </Link>\n <TriggerText automation={a} trigger={p.trigger} />\n </div>\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <span className=\"sr-only\">Project: </span>\n <span className=\"truncate text-sm\">{p.projectName}</span>\n <HostWarning host={p.host} />\n </div>\n <span className=\"text-sm\">\n <span className=\"sr-only\">Mode: </span>\n {modeLabel(a.mode)}\n </span>\n <div className=\"min-w-0\">\n <span className=\"sr-only\">Next occurrence: </span>\n <NextOccurrence automation={a} />\n </div>\n <div className=\"min-w-0 text-sm break-words\">\n <span className=\"sr-only\">Last outcome: </span>\n {a.lastOutcome ?? <span className=\"text-muted\">No runs yet</span>}\n </div>\n <FailureCount count={a.failureCount} />\n <Switch\n checked={a.enabled}\n onChange={p.onToggle}\n busy={p.toggling}\n label={`Enable ${a.name}`}\n onText=\"Enabled\"\n offText=\"Disabled\"\n />\n <div className=\"flex items-center gap-1\">\n <RunButton running={p.running} onRun={p.onRun} name={a.name} />\n <Link\n to={editPath(a.id)}\n state={BACK_STATE}\n aria-label={`Edit ${a.name}`}\n title=\"Edit\"\n className=\"tap inline-flex size-9 items-center justify-center rounded-md text-muted hover:bg-surface-2 hover:text-text\"\n >\n <ChevronRight aria-hidden className=\"size-4\" />\n </Link>\n </div>\n </div>\n );\n}\n\nfunction CardRow(p: RowProps) {\n const a = p.automation;\n return (\n <div className=\"flex min-w-0 flex-col gap-2 px-4 py-3\">\n <div className=\"flex min-w-0 items-start justify-between gap-2\">\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <Link\n to={editPath(a.id)}\n state={BACK_STATE}\n className=\"font-medium break-words text-text hover:underline focus-visible:underline\"\n >\n {a.name}\n </Link>\n <TriggerText automation={a} trigger={p.trigger} />\n </div>\n <Switch\n checked={a.enabled}\n onChange={p.onToggle}\n busy={p.toggling}\n label={`Enable ${a.name}`}\n onText=\"Enabled\"\n offText=\"Disabled\"\n />\n </div>\n <dl className=\"grid min-w-0 grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-1 text-sm\">\n <dt className=\"text-muted\">Project</dt>\n <dd className=\"flex min-w-0 flex-col break-words\">\n {p.projectName}\n <HostWarning host={p.host} />\n </dd>\n <dt className=\"text-muted\">Mode</dt>\n <dd>{modeLabel(a.mode)}</dd>\n <dt className=\"text-muted\">Next</dt>\n <dd className=\"min-w-0\">\n <NextOccurrence automation={a} />\n </dd>\n <dt className=\"text-muted\">Last outcome</dt>\n <dd className=\"min-w-0 break-words\">\n {a.lastOutcome ?? <span className=\"text-muted\">No runs yet</span>}\n </dd>\n <dt className=\"text-muted\">Failures</dt>\n <dd>\n <FailureCount count={a.failureCount} />\n </dd>\n </dl>\n <div className=\"flex flex-wrap gap-2\">\n <RunButton running={p.running} onRun={p.onRun} name={a.name} />\n <Link to={editPath(a.id)} state={BACK_STATE} className={buttonClasses(\"ghost\", \"sm\")}>\n Edit\n <ChevronRight aria-hidden className=\"size-3.5\" />\n </Link>\n </div>\n </div>\n );\n}\n"],"ignoreList":[0],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{n as e}from"./jsx-runtime-D13aM9HY.js";var t={name:`bell`,size:24,node:[[`path`,{d:`M10.268 21a2 2 0 0 0 3.464 0`,key:`vwvbt9`}],[`path`,{d:`M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326`,key:`11g9vi`}]]};t.node;var n=e(t);export{n as t};
|
|
2
|
+
//# sourceMappingURL=bell-DSwa2PAb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bell-DSwa2PAb.js","names":["createLucideIcon","__iconData","Bell"],"sources":["../../../../node_modules/.pnpm/lucide-react@1.61.0_@types+react@19.3.0_react@19.3.1/node_modules/lucide-react/dist/esm/icons/bell.mjs"],"sourcesContent":["/**\n * @license lucide-react v1.61.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconData = {\n name: \"bell\",\n size: 24,\n node: [\n [\"path\", { d: \"M10.268 21a2 2 0 0 0 3.464 0\", key: \"vwvbt9\" }],\n [\n \"path\",\n {\n d: \"M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326\",\n key: \"11g9vi\"\n }\n ]\n ]\n};\n__iconData.node;\nconst Bell = createLucideIcon(__iconData);\n\nexport { __iconData, Bell as default };\n//# sourceMappingURL=bell.mjs.map\n"],"ignoreList":[0],"mappings":"A,8CASA,IAAMC,EAAa,CACjB,KAAM,OACN,KAAM,GACN,KAAM,CACJ,CAAC,OAAQ,CAAE,EAAG,+BAAgC,IAAK,QAAS,CAAC,EAC7D,CACE,OACA,CACE,EAAG,gIACH,IAAK,QACP,CACF,CACF,CACF,EACAA,EAAW,KACX,IAAMC,EAAOF,EAAiBC,CAAU,E"}
|