@hienlh/ppm 0.2.15 → 0.2.17

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.
@@ -1 +1 @@
1
- import{a as e,n as t,r as n,t as r}from"./jsx-runtime-BFALxl05.js";import{t as i}from"./button-CQ5h5gxS.js";import{t as a}from"./trash-2-Dc17nbCE.js";import{a as o,i as s,n as c,o as l,r as u,t as d}from"./dialog-CCBmXo6-.js";import{t as f}from"./utils-D6me7KDg.js";import{t as p}from"./api-client-tgjN9Mx8.js";import{C as m,a as h,b as g,d as _,f as v,v as y,x as b}from"./index-DOHQ7GlD.js";var x=t(`circle`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}]]),S=t(`folder-git-2`,[[`path`,{d:`M18 19a5 5 0 0 1-5-5v8`,key:`sz5oeg`}],[`path`,{d:`M9 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v5`,key:`1w6njk`}],[`circle`,{cx:`13`,cy:`12`,r:`2`,key:`1j92g6`}],[`circle`,{cx:`20`,cy:`19`,r:`2`,key:`1obnsp`}]]),C=t(`pencil`,[[`path`,{d:`M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z`,key:`1a8usu`}],[`path`,{d:`m15 5 4 4`,key:`1mk7zo`}]]),w=e(n(),1),T=r();function E({value:e,onChange:t,onSelect:n,placeholder:r,autoFocus:i}){let[a,o]=(0,w.useState)([]),[s,c]=(0,w.useState)([]),[l,u]=(0,w.useState)(!1),[d,f]=(0,w.useState)(0),[m,_]=(0,w.useState)(!1),v=(0,w.useRef)(null),y=(0,w.useRef)(!1);(0,w.useEffect)(()=>{y.current||(y.current=!0,_(!0),p.get(`/api/projects/suggest-dirs`).then(e=>{o(e),c(e.slice(0,50)),u(e.length>0)}).catch(()=>o([])).finally(()=>_(!1)))},[]),(0,w.useEffect)(()=>{if(a.length===0)return;let t=e.trim().toLowerCase();c(t?a.filter(e=>e.path.toLowerCase().includes(t)||e.name.toLowerCase().includes(t)).slice(0,50):a.slice(0,50)),f(0)},[e,a]),(0,w.useEffect)(()=>{let e=v.current;e&&e.children[d]?.scrollIntoView({block:`nearest`})},[d]);let b=(0,w.useCallback)(e=>{t(e.path),n?.(e),u(!1)},[t,n]),x=(0,w.useCallback)(e=>{if(!(!l||s.length===0))switch(e.key){case`ArrowDown`:e.preventDefault(),f(e=>e<s.length-1?e+1:0);break;case`ArrowUp`:e.preventDefault(),f(e=>e>0?e-1:s.length-1);break;case`Tab`:case`Enter`:s[d]&&(e.preventDefault(),b(s[d]));break;case`Escape`:e.preventDefault(),u(!1);break}},[l,s,d,b]);return(0,T.jsxs)(`div`,{className:`relative`,children:[(0,T.jsxs)(`div`,{className:`relative`,children:[(0,T.jsx)(h,{placeholder:r??`/home/user/my-project`,value:e,onChange:e=>t(e.target.value),onKeyDown:x,onFocus:()=>s.length>0&&u(!0),onBlur:()=>setTimeout(()=>u(!1),200),autoFocus:i}),m&&(0,T.jsx)(g,{className:`absolute right-2 top-1/2 -translate-y-1/2 size-4 text-text-subtle animate-spin`})]}),l&&s.length>0&&(0,T.jsx)(`div`,{className:`absolute z-50 left-0 right-0 top-full mt-1 max-h-48 overflow-y-auto rounded-md border border-border bg-surface shadow-lg`,children:(0,T.jsx)(`div`,{ref:v,className:`py-1`,children:s.map((e,t)=>(0,T.jsxs)(`button`,{type:`button`,className:`flex items-center gap-2 w-full px-3 py-1.5 text-left text-sm transition-colors ${t===d?`bg-primary/10 text-primary`:`hover:bg-surface-hover text-text-primary`}`,onMouseEnter:()=>f(t),onMouseDown:t=>{t.preventDefault(),b(e)},children:[(0,T.jsx)(S,{className:`size-4 text-green-500 shrink-0`}),(0,T.jsxs)(`div`,{className:`min-w-0 flex-1 flex items-baseline gap-2`,children:[(0,T.jsx)(`span`,{className:`font-medium`,children:e.name}),(0,T.jsx)(`span`,{className:`text-xs text-text-subtle truncate`,children:e.path})]})]},e.path))})})]})}function D(){let{projects:e,activeProject:t,setActiveProject:n,fetchProjects:r,loading:g,error:S}=_(),D=v(e=>e.openTab),[O,k]=(0,w.useState)(!1),[A,j]=(0,w.useState)(``),[M,N]=(0,w.useState)(``),[P,F]=(0,w.useState)(``),[I,L]=(0,w.useState)(null),[R,z]=(0,w.useState)(``),[B,V]=(0,w.useState)(``),[H,U]=(0,w.useState)(``),[W,G]=(0,w.useState)(null),[K,q]=(0,w.useState)(``);(0,w.useEffect)(()=>{r()},[r]);function J(e){n(e)}function Y(e){n(e),D({type:`terminal`,title:`Terminal - ${e.name}`,metadata:{projectName:e.name},projectId:e.name,closable:!0})}let X=(0,w.useCallback)(async()=>{if(A.trim()){F(``);try{await p.post(`/api/projects`,{path:A.trim(),name:M.trim()||void 0}),await r(),k(!1),j(``),N(``)}catch(e){F(e instanceof Error?e.message:`Failed to add project`)}}},[A,M,r]);function Z(e,t){t.stopPropagation(),L(e),z(e.name),V(e.path),U(``)}let Q=(0,w.useCallback)(async()=>{if(!(!I||!R.trim())){U(``);try{if(await p.patch(`/api/projects/${encodeURIComponent(I.name)}`,{name:R.trim(),path:B.trim()||void 0}),await r(),t?.name===I.name){let e=_.getState().projects.find(e=>e.name===R.trim());e&&n(e)}L(null)}catch(e){U(e instanceof Error?e.message:`Failed to update project`)}}},[I,R,B,r,t,n]);function $(e,t){t.stopPropagation(),G(e),q(``)}let ee=(0,w.useCallback)(async()=>{if(W){q(``);try{if(await p.del(`/api/projects/${encodeURIComponent(W.name)}`),t?.name===W.name){let t=e.filter(e=>e.name!==W.name);t.length>0&&n(t[0])}await r(),G(null)}catch(e){q(e instanceof Error?e.message:`Failed to delete project`)}}},[W,t,e,r,n]);return S?(0,T.jsx)(`div`,{className:`flex items-center justify-center h-full p-4`,children:(0,T.jsx)(`p`,{className:`text-error text-sm`,children:S})}):(0,T.jsxs)(`div`,{className:`h-full p-4 space-y-4 overflow-auto`,children:[(0,T.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,T.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Projects`}),(0,T.jsxs)(i,{variant:`outline`,size:`sm`,onClick:()=>k(!0),className:`gap-1.5`,children:[(0,T.jsx)(y,{className:`size-4`}),`Add Project`]})]}),g&&(0,T.jsx)(`p`,{className:`text-text-secondary text-sm`,children:`Loading projects...`}),!g&&e.length===0&&(0,T.jsxs)(`div`,{className:`text-center py-8 space-y-2`,children:[(0,T.jsx)(m,{className:`size-10 mx-auto text-text-subtle`}),(0,T.jsx)(`p`,{className:`text-text-secondary text-sm`,children:`No projects registered`}),(0,T.jsxs)(`p`,{className:`text-text-subtle text-xs`,children:[`Click "Add Project" or use `,(0,T.jsx)(`code`,{className:`font-mono bg-surface-elevated px-1 py-0.5 rounded`,children:`ppm projects add <path>`})]})]}),(0,T.jsx)(`div`,{className:`grid gap-3 sm:grid-cols-2 lg:grid-cols-3`,children:e.map(e=>(0,T.jsxs)(`button`,{onClick:()=>J(e),onDoubleClick:()=>Y(e),className:f(`group text-left p-4 rounded-lg border transition-colors relative`,`min-h-[44px]`,t?.name===e.name?`bg-surface border-primary`:`bg-surface border-border hover:border-text-subtle`),children:[(0,T.jsxs)(`div`,{className:`absolute top-2 right-2 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity`,children:[(0,T.jsx)(`button`,{onClick:t=>Z(e,t),className:`p-1.5 rounded-md hover:bg-surface-elevated text-text-subtle hover:text-text-primary transition-colors`,title:`Edit project`,children:(0,T.jsx)(C,{className:`size-3.5`})}),(0,T.jsx)(`button`,{onClick:t=>$(e,t),className:`p-1.5 rounded-md hover:bg-error/10 text-text-subtle hover:text-error transition-colors`,title:`Remove project`,children:(0,T.jsx)(a,{className:`size-3.5`})})]}),(0,T.jsxs)(`div`,{className:`flex items-start gap-3`,children:[(0,T.jsx)(m,{className:`size-5 text-primary shrink-0 mt-0.5`}),(0,T.jsxs)(`div`,{className:`flex-1 min-w-0 space-y-1`,children:[(0,T.jsx)(`p`,{className:`font-medium truncate`,children:e.name}),(0,T.jsx)(`p`,{className:`text-xs text-text-secondary truncate`,children:e.path}),e.branch&&(0,T.jsxs)(`div`,{className:`flex items-center gap-1.5 text-xs text-text-secondary`,children:[(0,T.jsx)(b,{className:`size-3`}),(0,T.jsx)(`span`,{children:e.branch}),e.status&&(0,T.jsx)(x,{className:f(`size-2 fill-current`,e.status===`clean`?`text-success`:`text-warning`)})]})]})]})]},e.name))}),(0,T.jsx)(d,{open:O,onOpenChange:k,children:(0,T.jsxs)(c,{children:[(0,T.jsx)(o,{children:(0,T.jsx)(l,{children:`Add Project`})}),(0,T.jsxs)(`div`,{className:`space-y-3`,children:[(0,T.jsxs)(`div`,{children:[(0,T.jsx)(`label`,{className:`text-sm text-text-secondary`,children:`Path (required)`}),(0,T.jsx)(E,{value:A,onChange:j,onSelect:e=>{M.trim()||N(e.name)},placeholder:`/home/user/my-project`,autoFocus:!0})]}),(0,T.jsxs)(`div`,{children:[(0,T.jsx)(`label`,{className:`text-sm text-text-secondary`,children:`Name (optional)`}),(0,T.jsx)(h,{placeholder:`Auto-detected from folder name`,value:M,onChange:e=>N(e.target.value),onKeyDown:e=>e.key===`Enter`&&X()})]}),P&&(0,T.jsx)(`p`,{className:`text-sm text-error`,children:P})]}),(0,T.jsxs)(s,{children:[(0,T.jsx)(i,{variant:`outline`,onClick:()=>k(!1),children:`Cancel`}),(0,T.jsx)(i,{onClick:X,disabled:!A.trim(),children:`Add`})]})]})}),(0,T.jsx)(d,{open:!!I,onOpenChange:e=>!e&&L(null),children:(0,T.jsxs)(c,{children:[(0,T.jsx)(o,{children:(0,T.jsx)(l,{children:`Edit Project`})}),(0,T.jsxs)(`div`,{className:`space-y-3`,children:[(0,T.jsxs)(`div`,{children:[(0,T.jsx)(`label`,{className:`text-sm text-text-secondary`,children:`Name`}),(0,T.jsx)(h,{value:R,onChange:e=>z(e.target.value),onKeyDown:e=>e.key===`Enter`&&Q(),autoFocus:!0})]}),(0,T.jsxs)(`div`,{children:[(0,T.jsx)(`label`,{className:`text-sm text-text-secondary`,children:`Path`}),(0,T.jsx)(E,{value:B,onChange:V,placeholder:`/home/user/my-project`})]}),H&&(0,T.jsx)(`p`,{className:`text-sm text-error`,children:H})]}),(0,T.jsxs)(s,{children:[(0,T.jsx)(i,{variant:`outline`,onClick:()=>L(null),children:`Cancel`}),(0,T.jsx)(i,{onClick:Q,disabled:!R.trim(),children:`Save`})]})]})}),(0,T.jsx)(d,{open:!!W,onOpenChange:e=>!e&&G(null),children:(0,T.jsxs)(c,{children:[(0,T.jsxs)(o,{children:[(0,T.jsx)(l,{children:`Remove Project`}),(0,T.jsxs)(u,{children:[`Remove `,(0,T.jsx)(`strong`,{children:W?.name}),` from PPM? This only unregisters it — project files on disk are not affected.`]})]}),K&&(0,T.jsx)(`p`,{className:`text-sm text-error`,children:K}),(0,T.jsxs)(s,{children:[(0,T.jsx)(i,{variant:`outline`,onClick:()=>G(null),children:`Cancel`}),(0,T.jsx)(i,{variant:`destructive`,onClick:ee,children:`Remove`})]})]})})]})}export{D as ProjectList};
1
+ import{a as e,n as t,r as n,t as r}from"./jsx-runtime-BFALxl05.js";import{t as i}from"./button-CQ5h5gxS.js";import{t as a}from"./trash-2-Dc17nbCE.js";import{a as o,i as s,n as c,o as l,r as u,t as d}from"./dialog-CCBmXo6-.js";import{t as f}from"./utils-D6me7KDg.js";import{t as p}from"./api-client-tgjN9Mx8.js";import{C as m,a as h,b as g,d as _,f as v,v as y,x as b}from"./index-DBaFu6Af.js";var x=t(`circle`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}]]),S=t(`folder-git-2`,[[`path`,{d:`M18 19a5 5 0 0 1-5-5v8`,key:`sz5oeg`}],[`path`,{d:`M9 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v5`,key:`1w6njk`}],[`circle`,{cx:`13`,cy:`12`,r:`2`,key:`1j92g6`}],[`circle`,{cx:`20`,cy:`19`,r:`2`,key:`1obnsp`}]]),C=t(`pencil`,[[`path`,{d:`M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z`,key:`1a8usu`}],[`path`,{d:`m15 5 4 4`,key:`1mk7zo`}]]),w=e(n(),1),T=r();function E({value:e,onChange:t,onSelect:n,placeholder:r,autoFocus:i}){let[a,o]=(0,w.useState)([]),[s,c]=(0,w.useState)([]),[l,u]=(0,w.useState)(!1),[d,f]=(0,w.useState)(0),[m,_]=(0,w.useState)(!1),v=(0,w.useRef)(null),y=(0,w.useRef)(!1);(0,w.useEffect)(()=>{y.current||(y.current=!0,_(!0),p.get(`/api/projects/suggest-dirs`).then(e=>{o(e),c(e.slice(0,50)),u(e.length>0)}).catch(()=>o([])).finally(()=>_(!1)))},[]),(0,w.useEffect)(()=>{if(a.length===0)return;let t=e.trim().toLowerCase();c(t?a.filter(e=>e.path.toLowerCase().includes(t)||e.name.toLowerCase().includes(t)).slice(0,50):a.slice(0,50)),f(0)},[e,a]),(0,w.useEffect)(()=>{let e=v.current;e&&e.children[d]?.scrollIntoView({block:`nearest`})},[d]);let b=(0,w.useCallback)(e=>{t(e.path),n?.(e),u(!1)},[t,n]),x=(0,w.useCallback)(e=>{if(!(!l||s.length===0))switch(e.key){case`ArrowDown`:e.preventDefault(),f(e=>e<s.length-1?e+1:0);break;case`ArrowUp`:e.preventDefault(),f(e=>e>0?e-1:s.length-1);break;case`Tab`:case`Enter`:s[d]&&(e.preventDefault(),b(s[d]));break;case`Escape`:e.preventDefault(),u(!1);break}},[l,s,d,b]);return(0,T.jsxs)(`div`,{className:`relative`,children:[(0,T.jsxs)(`div`,{className:`relative`,children:[(0,T.jsx)(h,{placeholder:r??`/home/user/my-project`,value:e,onChange:e=>t(e.target.value),onKeyDown:x,onFocus:()=>s.length>0&&u(!0),onBlur:()=>setTimeout(()=>u(!1),200),autoFocus:i}),m&&(0,T.jsx)(g,{className:`absolute right-2 top-1/2 -translate-y-1/2 size-4 text-text-subtle animate-spin`})]}),l&&s.length>0&&(0,T.jsx)(`div`,{className:`absolute z-50 left-0 right-0 top-full mt-1 max-h-48 overflow-y-auto rounded-md border border-border bg-surface shadow-lg`,children:(0,T.jsx)(`div`,{ref:v,className:`py-1`,children:s.map((e,t)=>(0,T.jsxs)(`button`,{type:`button`,className:`flex items-center gap-2 w-full px-3 py-1.5 text-left text-sm transition-colors ${t===d?`bg-primary/10 text-primary`:`hover:bg-surface-hover text-text-primary`}`,onMouseEnter:()=>f(t),onMouseDown:t=>{t.preventDefault(),b(e)},children:[(0,T.jsx)(S,{className:`size-4 text-green-500 shrink-0`}),(0,T.jsxs)(`div`,{className:`min-w-0 flex-1 flex items-baseline gap-2`,children:[(0,T.jsx)(`span`,{className:`font-medium`,children:e.name}),(0,T.jsx)(`span`,{className:`text-xs text-text-subtle truncate`,children:e.path})]})]},e.path))})})]})}function D(){let{projects:e,activeProject:t,setActiveProject:n,fetchProjects:r,loading:g,error:S}=_(),D=v(e=>e.openTab),[O,k]=(0,w.useState)(!1),[A,j]=(0,w.useState)(``),[M,N]=(0,w.useState)(``),[P,F]=(0,w.useState)(``),[I,L]=(0,w.useState)(null),[R,z]=(0,w.useState)(``),[B,V]=(0,w.useState)(``),[H,U]=(0,w.useState)(``),[W,G]=(0,w.useState)(null),[K,q]=(0,w.useState)(``);(0,w.useEffect)(()=>{r()},[r]);function J(e){n(e)}function Y(e){n(e),D({type:`terminal`,title:`Terminal - ${e.name}`,metadata:{projectName:e.name},projectId:e.name,closable:!0})}let X=(0,w.useCallback)(async()=>{if(A.trim()){F(``);try{await p.post(`/api/projects`,{path:A.trim(),name:M.trim()||void 0}),await r(),k(!1),j(``),N(``)}catch(e){F(e instanceof Error?e.message:`Failed to add project`)}}},[A,M,r]);function Z(e,t){t.stopPropagation(),L(e),z(e.name),V(e.path),U(``)}let Q=(0,w.useCallback)(async()=>{if(!(!I||!R.trim())){U(``);try{if(await p.patch(`/api/projects/${encodeURIComponent(I.name)}`,{name:R.trim(),path:B.trim()||void 0}),await r(),t?.name===I.name){let e=_.getState().projects.find(e=>e.name===R.trim());e&&n(e)}L(null)}catch(e){U(e instanceof Error?e.message:`Failed to update project`)}}},[I,R,B,r,t,n]);function $(e,t){t.stopPropagation(),G(e),q(``)}let ee=(0,w.useCallback)(async()=>{if(W){q(``);try{if(await p.del(`/api/projects/${encodeURIComponent(W.name)}`),t?.name===W.name){let t=e.filter(e=>e.name!==W.name);t.length>0&&n(t[0])}await r(),G(null)}catch(e){q(e instanceof Error?e.message:`Failed to delete project`)}}},[W,t,e,r,n]);return S?(0,T.jsx)(`div`,{className:`flex items-center justify-center h-full p-4`,children:(0,T.jsx)(`p`,{className:`text-error text-sm`,children:S})}):(0,T.jsxs)(`div`,{className:`h-full p-4 space-y-4 overflow-auto`,children:[(0,T.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,T.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Projects`}),(0,T.jsxs)(i,{variant:`outline`,size:`sm`,onClick:()=>k(!0),className:`gap-1.5`,children:[(0,T.jsx)(y,{className:`size-4`}),`Add Project`]})]}),g&&(0,T.jsx)(`p`,{className:`text-text-secondary text-sm`,children:`Loading projects...`}),!g&&e.length===0&&(0,T.jsxs)(`div`,{className:`text-center py-8 space-y-2`,children:[(0,T.jsx)(m,{className:`size-10 mx-auto text-text-subtle`}),(0,T.jsx)(`p`,{className:`text-text-secondary text-sm`,children:`No projects registered`}),(0,T.jsxs)(`p`,{className:`text-text-subtle text-xs`,children:[`Click "Add Project" or use `,(0,T.jsx)(`code`,{className:`font-mono bg-surface-elevated px-1 py-0.5 rounded`,children:`ppm projects add <path>`})]})]}),(0,T.jsx)(`div`,{className:`grid gap-3 sm:grid-cols-2 lg:grid-cols-3`,children:e.map(e=>(0,T.jsxs)(`button`,{onClick:()=>J(e),onDoubleClick:()=>Y(e),className:f(`group text-left p-4 rounded-lg border transition-colors relative`,`min-h-[44px]`,t?.name===e.name?`bg-surface border-primary`:`bg-surface border-border hover:border-text-subtle`),children:[(0,T.jsxs)(`div`,{className:`absolute top-2 right-2 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity`,children:[(0,T.jsx)(`button`,{onClick:t=>Z(e,t),className:`p-1.5 rounded-md hover:bg-surface-elevated text-text-subtle hover:text-text-primary transition-colors`,title:`Edit project`,children:(0,T.jsx)(C,{className:`size-3.5`})}),(0,T.jsx)(`button`,{onClick:t=>$(e,t),className:`p-1.5 rounded-md hover:bg-error/10 text-text-subtle hover:text-error transition-colors`,title:`Remove project`,children:(0,T.jsx)(a,{className:`size-3.5`})})]}),(0,T.jsxs)(`div`,{className:`flex items-start gap-3`,children:[(0,T.jsx)(m,{className:`size-5 text-primary shrink-0 mt-0.5`}),(0,T.jsxs)(`div`,{className:`flex-1 min-w-0 space-y-1`,children:[(0,T.jsx)(`p`,{className:`font-medium truncate`,children:e.name}),(0,T.jsx)(`p`,{className:`text-xs text-text-secondary truncate`,children:e.path}),e.branch&&(0,T.jsxs)(`div`,{className:`flex items-center gap-1.5 text-xs text-text-secondary`,children:[(0,T.jsx)(b,{className:`size-3`}),(0,T.jsx)(`span`,{children:e.branch}),e.status&&(0,T.jsx)(x,{className:f(`size-2 fill-current`,e.status===`clean`?`text-success`:`text-warning`)})]})]})]})]},e.name))}),(0,T.jsx)(d,{open:O,onOpenChange:k,children:(0,T.jsxs)(c,{children:[(0,T.jsx)(o,{children:(0,T.jsx)(l,{children:`Add Project`})}),(0,T.jsxs)(`div`,{className:`space-y-3`,children:[(0,T.jsxs)(`div`,{children:[(0,T.jsx)(`label`,{className:`text-sm text-text-secondary`,children:`Path (required)`}),(0,T.jsx)(E,{value:A,onChange:j,onSelect:e=>{M.trim()||N(e.name)},placeholder:`/home/user/my-project`,autoFocus:!0})]}),(0,T.jsxs)(`div`,{children:[(0,T.jsx)(`label`,{className:`text-sm text-text-secondary`,children:`Name (optional)`}),(0,T.jsx)(h,{placeholder:`Auto-detected from folder name`,value:M,onChange:e=>N(e.target.value),onKeyDown:e=>e.key===`Enter`&&X()})]}),P&&(0,T.jsx)(`p`,{className:`text-sm text-error`,children:P})]}),(0,T.jsxs)(s,{children:[(0,T.jsx)(i,{variant:`outline`,onClick:()=>k(!1),children:`Cancel`}),(0,T.jsx)(i,{onClick:X,disabled:!A.trim(),children:`Add`})]})]})}),(0,T.jsx)(d,{open:!!I,onOpenChange:e=>!e&&L(null),children:(0,T.jsxs)(c,{children:[(0,T.jsx)(o,{children:(0,T.jsx)(l,{children:`Edit Project`})}),(0,T.jsxs)(`div`,{className:`space-y-3`,children:[(0,T.jsxs)(`div`,{children:[(0,T.jsx)(`label`,{className:`text-sm text-text-secondary`,children:`Name`}),(0,T.jsx)(h,{value:R,onChange:e=>z(e.target.value),onKeyDown:e=>e.key===`Enter`&&Q(),autoFocus:!0})]}),(0,T.jsxs)(`div`,{children:[(0,T.jsx)(`label`,{className:`text-sm text-text-secondary`,children:`Path`}),(0,T.jsx)(E,{value:B,onChange:V,placeholder:`/home/user/my-project`})]}),H&&(0,T.jsx)(`p`,{className:`text-sm text-error`,children:H})]}),(0,T.jsxs)(s,{children:[(0,T.jsx)(i,{variant:`outline`,onClick:()=>L(null),children:`Cancel`}),(0,T.jsx)(i,{onClick:Q,disabled:!R.trim(),children:`Save`})]})]})}),(0,T.jsx)(d,{open:!!W,onOpenChange:e=>!e&&G(null),children:(0,T.jsxs)(c,{children:[(0,T.jsxs)(o,{children:[(0,T.jsx)(l,{children:`Remove Project`}),(0,T.jsxs)(u,{children:[`Remove `,(0,T.jsx)(`strong`,{children:W?.name}),` from PPM? This only unregisters it — project files on disk are not affected.`]})]}),K&&(0,T.jsx)(`p`,{className:`text-sm text-error`,children:K}),(0,T.jsxs)(s,{children:[(0,T.jsx)(i,{variant:`outline`,onClick:()=>G(null),children:`Cancel`}),(0,T.jsx)(i,{variant:`destructive`,onClick:ee,children:`Remove`})]})]})})]})}export{D as ProjectList};
@@ -0,0 +1 @@
1
+ import{a as e,n as t,r as n,t as r}from"./jsx-runtime-BFALxl05.js";import{_ as i,a,b as o,c as s,d as c,f as l,h as u,i as d,l as f,m as p,n as m,o as h,r as g,s as _,t as v,u as y,x as b}from"./button-CQ5h5gxS.js";import{a as x,i as S,n as C,o as w,r as T,t as E}from"./dist-CYANqO1g.js";import{n as ee,t as D}from"./dist-0XHv8Vwc.js";import{t as O}from"./utils-D6me7KDg.js";import{n as k,t as A}from"./api-client-tgjN9Mx8.js";import{A as j,a as M,h as N,j as P,m as F,t as I}from"./index-DBaFu6Af.js";var L=t(`bell-off`,[[`path`,{d:`M10.268 21a2 2 0 0 0 3.464 0`,key:`vwvbt9`}],[`path`,{d:`M17 17H4a1 1 0 0 1-.74-1.673C4.59 13.956 6 12.499 6 8a6 6 0 0 1 .258-1.742`,key:`178tsu`}],[`path`,{d:`m2 2 20 20`,key:`1ooewy`}],[`path`,{d:`M8.668 3.01A6 6 0 0 1 18 8c0 2.687.77 4.653 1.707 6.05`,key:`1hqiys`}]]),te=t(`bell`,[[`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`}]]),R=t(`chevron-up`,[[`path`,{d:`m18 15-6-6-6 6`,key:`153udz`}]]),z=t(`monitor`,[[`rect`,{width:`20`,height:`14`,x:`2`,y:`3`,rx:`2`,key:`48i651`}],[`line`,{x1:`8`,x2:`16`,y1:`21`,y2:`21`,key:`1svkeh`}],[`line`,{x1:`12`,x2:`12`,y1:`17`,y2:`21`,key:`vw1qmm`}]]),B=t(`moon`,[[`path`,{d:`M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401`,key:`kfwtm`}]]),ne=t(`sun`,[[`circle`,{cx:`12`,cy:`12`,r:`4`,key:`4exip2`}],[`path`,{d:`M12 2v2`,key:`tus03m`}],[`path`,{d:`M12 20v2`,key:`1lh1kg`}],[`path`,{d:`m4.93 4.93 1.41 1.41`,key:`149t6j`}],[`path`,{d:`m17.66 17.66 1.41 1.41`,key:`ptbguv`}],[`path`,{d:`M2 12h2`,key:`1t8f8n`}],[`path`,{d:`M20 12h2`,key:`1q8mjw`}],[`path`,{d:`m6.34 17.66-1.41 1.41`,key:`1m8zz5`}],[`path`,{d:`m19.07 4.93-1.41 1.41`,key:`1shlcs`}]]),V=e(n(),1);function re(e){let t=V.useRef({value:e,previous:e});return V.useMemo(()=>(t.current.value!==e&&(t.current.previous=t.current.value,t.current.value=e),t.current.previous),[e])}var H=r(),ie=`Label`,ae=V.forwardRef((e,t)=>(0,H.jsx)(u.label,{...e,ref:t,onMouseDown:t=>{t.target.closest(`button, input, select, textarea`)||(e.onMouseDown?.(t),!t.defaultPrevented&&t.detail>1&&t.preventDefault())}}));ae.displayName=ie;var oe=ae,se=e(b(),1),ce=[` `,`Enter`,`ArrowUp`,`ArrowDown`],le=[` `,`Enter`],U=`Select`,[W,G,ue]=w(U),[K,de]=p(U,[ue,x]),q=x(),[fe,J]=K(U),[pe,me]=K(U),he=e=>{let{__scopeSelect:t,children:n,open:r,defaultOpen:i,onOpenChange:a,value:o,defaultValue:s,onValueChange:c,dir:l,name:u,autoComplete:d,disabled:p,required:m,form:h}=e,g=q(t),[_,v]=V.useState(null),[b,x]=V.useState(null),[C,w]=V.useState(!1),T=ee(l),[E,D]=y({prop:r,defaultProp:i??!1,onChange:a,caller:U}),[O,k]=y({prop:o,defaultProp:s,onChange:c,caller:U}),A=V.useRef(null),j=_?h||!!_.closest(`form`):!0,[M,N]=V.useState(new Set),P=Array.from(M).map(e=>e.props.value).join(`;`);return(0,H.jsx)(S,{...g,children:(0,H.jsxs)(fe,{required:m,scope:t,trigger:_,onTriggerChange:v,valueNode:b,onValueNodeChange:x,valueNodeHasChildren:C,onValueNodeHasChildrenChange:w,contentId:f(),value:O,onValueChange:k,open:E,onOpenChange:D,dir:T,triggerPointerDownPosRef:A,disabled:p,children:[(0,H.jsx)(W.Provider,{scope:t,children:(0,H.jsx)(pe,{scope:e.__scopeSelect,onNativeOptionAdd:V.useCallback(e=>{N(t=>new Set(t).add(e))},[]),onNativeOptionRemove:V.useCallback(e=>{N(t=>{let n=new Set(t);return n.delete(e),n})},[]),children:n})}),j?(0,H.jsxs)(ot,{"aria-hidden":!0,required:m,tabIndex:-1,name:u,autoComplete:d,value:O,onChange:e=>k(e.target.value),disabled:p,form:h,children:[O===void 0?(0,H.jsx)(`option`,{value:``}):null,Array.from(M)]},P):null]})})};he.displayName=U;var ge=`SelectTrigger`,_e=V.forwardRef((e,t)=>{let{__scopeSelect:n,disabled:r=!1,...i}=e,a=q(n),s=J(ge,n),c=s.disabled||r,d=o(t,s.onTriggerChange),f=G(n),p=V.useRef(`touch`),[m,h,g]=ct(e=>{let t=f().filter(e=>!e.disabled),n=lt(t,e,t.find(e=>e.value===s.value));n!==void 0&&s.onValueChange(n.value)}),_=e=>{c||(s.onOpenChange(!0),g()),e&&(s.triggerPointerDownPosRef.current={x:Math.round(e.pageX),y:Math.round(e.pageY)})};return(0,H.jsx)(E,{asChild:!0,...a,children:(0,H.jsx)(u.button,{type:`button`,role:`combobox`,"aria-controls":s.contentId,"aria-expanded":s.open,"aria-required":s.required,"aria-autocomplete":`none`,dir:s.dir,"data-state":s.open?`open`:`closed`,disabled:c,"data-disabled":c?``:void 0,"data-placeholder":st(s.value)?``:void 0,...i,ref:d,onClick:l(i.onClick,e=>{e.currentTarget.focus(),p.current!==`mouse`&&_(e)}),onPointerDown:l(i.onPointerDown,e=>{p.current=e.pointerType;let t=e.target;t.hasPointerCapture(e.pointerId)&&t.releasePointerCapture(e.pointerId),e.button===0&&e.ctrlKey===!1&&e.pointerType===`mouse`&&(_(e),e.preventDefault())}),onKeyDown:l(i.onKeyDown,e=>{let t=m.current!==``;!(e.ctrlKey||e.altKey||e.metaKey)&&e.key.length===1&&h(e.key),!(t&&e.key===` `)&&ce.includes(e.key)&&(_(),e.preventDefault())})})})});_e.displayName=ge;var ve=`SelectValue`,ye=V.forwardRef((e,t)=>{let{__scopeSelect:n,className:r,style:i,children:a,placeholder:s=``,...l}=e,d=J(ve,n),{onValueNodeHasChildrenChange:f}=d,p=a!==void 0,m=o(t,d.onValueNodeChange);return c(()=>{f(p)},[f,p]),(0,H.jsx)(u.span,{...l,ref:m,style:{pointerEvents:`none`},children:st(d.value)?(0,H.jsx)(H.Fragment,{children:s}):a})});ye.displayName=ve;var be=`SelectIcon`,xe=V.forwardRef((e,t)=>{let{__scopeSelect:n,children:r,...i}=e;return(0,H.jsx)(u.span,{"aria-hidden":!0,...i,ref:t,children:r||`▼`})});xe.displayName=be;var Se=`SelectPortal`,Ce=e=>(0,H.jsx)(a,{asChild:!0,...e});Ce.displayName=Se;var Y=`SelectContent`,we=V.forwardRef((e,t)=>{let n=J(Y,e.__scopeSelect),[r,i]=V.useState();if(c(()=>{i(new DocumentFragment)},[]),!n.open){let t=r;return t?se.createPortal((0,H.jsx)(Te,{scope:e.__scopeSelect,children:(0,H.jsx)(W.Slot,{scope:e.__scopeSelect,children:(0,H.jsx)(`div`,{children:e.children})})}),t):null}return(0,H.jsx)(Oe,{...e,ref:t})});we.displayName=Y;var X=10,[Te,Z]=K(Y),Ee=`SelectContentImpl`,De=i(`SelectContent.RemoveScroll`),Oe=V.forwardRef((e,t)=>{let{__scopeSelect:n,position:r=`item-aligned`,onCloseAutoFocus:i,onEscapeKeyDown:a,onPointerDownOutside:s,side:c,sideOffset:u,align:f,alignOffset:p,arrowPadding:v,collisionBoundary:y,collisionPadding:b,sticky:x,hideWhenDetached:S,avoidCollisions:C,...w}=e,T=J(Y,n),[E,ee]=V.useState(null),[D,O]=V.useState(null),k=o(t,e=>ee(e)),[A,j]=V.useState(null),[M,N]=V.useState(null),P=G(n),[F,I]=V.useState(!1),L=V.useRef(!1);V.useEffect(()=>{if(E)return m(E)},[E]),d();let te=V.useCallback(e=>{let[t,...n]=P().map(e=>e.ref.current),[r]=n.slice(-1),i=document.activeElement;for(let n of e)if(n===i||(n?.scrollIntoView({block:`nearest`}),n===t&&D&&(D.scrollTop=0),n===r&&D&&(D.scrollTop=D.scrollHeight),n?.focus(),document.activeElement!==i))return},[P,D]),R=V.useCallback(()=>te([A,E]),[te,A,E]);V.useEffect(()=>{F&&R()},[F,R]);let{onOpenChange:z,triggerPointerDownPosRef:B}=T;V.useEffect(()=>{if(E){let e={x:0,y:0},t=t=>{e={x:Math.abs(Math.round(t.pageX)-(B.current?.x??0)),y:Math.abs(Math.round(t.pageY)-(B.current?.y??0))}},n=n=>{e.x<=10&&e.y<=10?n.preventDefault():E.contains(n.target)||z(!1),document.removeEventListener(`pointermove`,t),B.current=null};return B.current!==null&&(document.addEventListener(`pointermove`,t),document.addEventListener(`pointerup`,n,{capture:!0,once:!0})),()=>{document.removeEventListener(`pointermove`,t),document.removeEventListener(`pointerup`,n,{capture:!0})}}},[E,z,B]),V.useEffect(()=>{let e=()=>z(!1);return window.addEventListener(`blur`,e),window.addEventListener(`resize`,e),()=>{window.removeEventListener(`blur`,e),window.removeEventListener(`resize`,e)}},[z]);let[ne,re]=ct(e=>{let t=P().filter(e=>!e.disabled),n=lt(t,e,t.find(e=>e.ref.current===document.activeElement));n&&setTimeout(()=>n.ref.current.focus())}),ie=V.useCallback((e,t,n)=>{let r=!L.current&&!n;(T.value!==void 0&&T.value===t||r)&&(j(e),r&&(L.current=!0))},[T.value]),ae=V.useCallback(()=>E?.focus(),[E]),oe=V.useCallback((e,t,n)=>{let r=!L.current&&!n;(T.value!==void 0&&T.value===t||r)&&N(e)},[T.value]),se=r===`popper`?Me:Ae,ce=se===Me?{side:c,sideOffset:u,align:f,alignOffset:p,arrowPadding:v,collisionBoundary:y,collisionPadding:b,sticky:x,hideWhenDetached:S,avoidCollisions:C}:{};return(0,H.jsx)(Te,{scope:n,content:E,viewport:D,onViewportChange:O,itemRefCallback:ie,selectedItem:A,onItemLeave:ae,itemTextRefCallback:oe,focusSelectedItem:R,selectedItemText:M,position:r,isPositioned:F,searchRef:ne,children:(0,H.jsx)(g,{as:De,allowPinchZoom:!0,children:(0,H.jsx)(h,{asChild:!0,trapped:T.open,onMountAutoFocus:e=>{e.preventDefault()},onUnmountAutoFocus:l(i,e=>{T.trigger?.focus({preventScroll:!0}),e.preventDefault()}),children:(0,H.jsx)(_,{asChild:!0,disableOutsidePointerEvents:!0,onEscapeKeyDown:a,onPointerDownOutside:s,onFocusOutside:e=>e.preventDefault(),onDismiss:()=>T.onOpenChange(!1),children:(0,H.jsx)(se,{role:`listbox`,id:T.contentId,"data-state":T.open?`open`:`closed`,dir:T.dir,onContextMenu:e=>e.preventDefault(),...w,...ce,onPlaced:()=>I(!0),ref:k,style:{display:`flex`,flexDirection:`column`,outline:`none`,...w.style},onKeyDown:l(w.onKeyDown,e=>{let t=e.ctrlKey||e.altKey||e.metaKey;if(e.key===`Tab`&&e.preventDefault(),!t&&e.key.length===1&&re(e.key),[`ArrowUp`,`ArrowDown`,`Home`,`End`].includes(e.key)){let t=P().filter(e=>!e.disabled).map(e=>e.ref.current);if([`ArrowUp`,`End`].includes(e.key)&&(t=t.slice().reverse()),[`ArrowUp`,`ArrowDown`].includes(e.key)){let n=e.target,r=t.indexOf(n);t=t.slice(r+1)}setTimeout(()=>te(t)),e.preventDefault()}})})})})})})});Oe.displayName=Ee;var ke=`SelectItemAlignedPosition`,Ae=V.forwardRef((e,t)=>{let{__scopeSelect:n,onPlaced:r,...i}=e,a=J(Y,n),s=Z(Y,n),[l,d]=V.useState(null),[f,p]=V.useState(null),m=o(t,e=>p(e)),h=G(n),g=V.useRef(!1),_=V.useRef(!0),{viewport:v,selectedItem:y,selectedItemText:b,focusSelectedItem:x}=s,S=V.useCallback(()=>{if(a.trigger&&a.valueNode&&l&&f&&v&&y&&b){let e=a.trigger.getBoundingClientRect(),t=f.getBoundingClientRect(),n=a.valueNode.getBoundingClientRect(),i=b.getBoundingClientRect();if(a.dir!==`rtl`){let r=i.left-t.left,a=n.left-r,o=e.left-a,s=e.width+o,c=Math.max(s,t.width),u=window.innerWidth-X,d=D(a,[X,Math.max(X,u-c)]);l.style.minWidth=s+`px`,l.style.left=d+`px`}else{let r=t.right-i.right,a=window.innerWidth-n.right-r,o=window.innerWidth-e.right-a,s=e.width+o,c=Math.max(s,t.width),u=window.innerWidth-X,d=D(a,[X,Math.max(X,u-c)]);l.style.minWidth=s+`px`,l.style.right=d+`px`}let o=h(),s=window.innerHeight-X*2,c=v.scrollHeight,u=window.getComputedStyle(f),d=parseInt(u.borderTopWidth,10),p=parseInt(u.paddingTop,10),m=parseInt(u.borderBottomWidth,10),_=parseInt(u.paddingBottom,10),x=d+p+c+_+m,S=Math.min(y.offsetHeight*5,x),C=window.getComputedStyle(v),w=parseInt(C.paddingTop,10),T=parseInt(C.paddingBottom,10),E=e.top+e.height/2-X,ee=s-E,O=y.offsetHeight/2,k=y.offsetTop+O,A=d+p+k,j=x-A;if(A<=E){let e=o.length>0&&y===o[o.length-1].ref.current;l.style.bottom=`0px`;let t=f.clientHeight-v.offsetTop-v.offsetHeight,n=A+Math.max(ee,O+(e?T:0)+t+m);l.style.height=n+`px`}else{let e=o.length>0&&y===o[0].ref.current;l.style.top=`0px`;let t=Math.max(E,d+v.offsetTop+(e?w:0)+O)+j;l.style.height=t+`px`,v.scrollTop=A-E+v.offsetTop}l.style.margin=`${X}px 0`,l.style.minHeight=S+`px`,l.style.maxHeight=s+`px`,r?.(),requestAnimationFrame(()=>g.current=!0)}},[h,a.trigger,a.valueNode,l,f,v,y,b,a.dir,r]);c(()=>S(),[S]);let[C,w]=V.useState();return c(()=>{f&&w(window.getComputedStyle(f).zIndex)},[f]),(0,H.jsx)(Ne,{scope:n,contentWrapper:l,shouldExpandOnScrollRef:g,onScrollButtonChange:V.useCallback(e=>{e&&_.current===!0&&(S(),x?.(),_.current=!1)},[S,x]),children:(0,H.jsx)(`div`,{ref:d,style:{display:`flex`,flexDirection:`column`,position:`fixed`,zIndex:C},children:(0,H.jsx)(u.div,{...i,ref:m,style:{boxSizing:`border-box`,maxHeight:`100%`,...i.style}})})})});Ae.displayName=ke;var je=`SelectPopperPosition`,Me=V.forwardRef((e,t)=>{let{__scopeSelect:n,align:r=`start`,collisionPadding:i=X,...a}=e,o=q(n);return(0,H.jsx)(T,{...o,...a,ref:t,align:r,collisionPadding:i,style:{boxSizing:`border-box`,...a.style,"--radix-select-content-transform-origin":`var(--radix-popper-transform-origin)`,"--radix-select-content-available-width":`var(--radix-popper-available-width)`,"--radix-select-content-available-height":`var(--radix-popper-available-height)`,"--radix-select-trigger-width":`var(--radix-popper-anchor-width)`,"--radix-select-trigger-height":`var(--radix-popper-anchor-height)`}})});Me.displayName=je;var[Ne,Pe]=K(Y,{}),Fe=`SelectViewport`,Ie=V.forwardRef((e,t)=>{let{__scopeSelect:n,nonce:r,...i}=e,a=Z(Fe,n),s=Pe(Fe,n),c=o(t,a.onViewportChange),d=V.useRef(0);return(0,H.jsxs)(H.Fragment,{children:[(0,H.jsx)(`style`,{dangerouslySetInnerHTML:{__html:`[data-radix-select-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-select-viewport]::-webkit-scrollbar{display:none}`},nonce:r}),(0,H.jsx)(W.Slot,{scope:n,children:(0,H.jsx)(u.div,{"data-radix-select-viewport":``,role:`presentation`,...i,ref:c,style:{position:`relative`,flex:1,overflow:`hidden auto`,...i.style},onScroll:l(i.onScroll,e=>{let t=e.currentTarget,{contentWrapper:n,shouldExpandOnScrollRef:r}=s;if(r?.current&&n){let e=Math.abs(d.current-t.scrollTop);if(e>0){let r=window.innerHeight-X*2,i=parseFloat(n.style.minHeight),a=parseFloat(n.style.height),o=Math.max(i,a);if(o<r){let i=o+e,a=Math.min(r,i),s=i-a;n.style.height=a+`px`,n.style.bottom===`0px`&&(t.scrollTop=s>0?s:0,n.style.justifyContent=`flex-end`)}}}d.current=t.scrollTop})})})]})});Ie.displayName=Fe;var Le=`SelectGroup`,[Re,ze]=K(Le),Be=V.forwardRef((e,t)=>{let{__scopeSelect:n,...r}=e,i=f();return(0,H.jsx)(Re,{scope:n,id:i,children:(0,H.jsx)(u.div,{role:`group`,"aria-labelledby":i,...r,ref:t})})});Be.displayName=Le;var Ve=`SelectLabel`,He=V.forwardRef((e,t)=>{let{__scopeSelect:n,...r}=e,i=ze(Ve,n);return(0,H.jsx)(u.div,{id:i.id,...r,ref:t})});He.displayName=Ve;var Ue=`SelectItem`,[We,Ge]=K(Ue),Ke=V.forwardRef((e,t)=>{let{__scopeSelect:n,value:r,disabled:i=!1,textValue:a,...s}=e,c=J(Ue,n),d=Z(Ue,n),p=c.value===r,[m,h]=V.useState(a??``),[g,_]=V.useState(!1),v=o(t,e=>d.itemRefCallback?.(e,r,i)),y=f(),b=V.useRef(`touch`),x=()=>{i||(c.onValueChange(r),c.onOpenChange(!1))};if(r===``)throw Error(`A <Select.Item /> must have a value prop that is not an empty string. This is because the Select value can be set to an empty string to clear the selection and show the placeholder.`);return(0,H.jsx)(We,{scope:n,value:r,disabled:i,textId:y,isSelected:p,onItemTextChange:V.useCallback(e=>{h(t=>t||(e?.textContent??``).trim())},[]),children:(0,H.jsx)(W.ItemSlot,{scope:n,value:r,disabled:i,textValue:m,children:(0,H.jsx)(u.div,{role:`option`,"aria-labelledby":y,"data-highlighted":g?``:void 0,"aria-selected":p&&g,"data-state":p?`checked`:`unchecked`,"aria-disabled":i||void 0,"data-disabled":i?``:void 0,tabIndex:i?void 0:-1,...s,ref:v,onFocus:l(s.onFocus,()=>_(!0)),onBlur:l(s.onBlur,()=>_(!1)),onClick:l(s.onClick,()=>{b.current!==`mouse`&&x()}),onPointerUp:l(s.onPointerUp,()=>{b.current===`mouse`&&x()}),onPointerDown:l(s.onPointerDown,e=>{b.current=e.pointerType}),onPointerMove:l(s.onPointerMove,e=>{b.current=e.pointerType,i?d.onItemLeave?.():b.current===`mouse`&&e.currentTarget.focus({preventScroll:!0})}),onPointerLeave:l(s.onPointerLeave,e=>{e.currentTarget===document.activeElement&&d.onItemLeave?.()}),onKeyDown:l(s.onKeyDown,e=>{d.searchRef?.current!==``&&e.key===` `||(le.includes(e.key)&&x(),e.key===` `&&e.preventDefault())})})})})});Ke.displayName=Ue;var Q=`SelectItemText`,qe=V.forwardRef((e,t)=>{let{__scopeSelect:n,className:r,style:i,...a}=e,s=J(Q,n),l=Z(Q,n),d=Ge(Q,n),f=me(Q,n),[p,m]=V.useState(null),h=o(t,e=>m(e),d.onItemTextChange,e=>l.itemTextRefCallback?.(e,d.value,d.disabled)),g=p?.textContent,_=V.useMemo(()=>(0,H.jsx)(`option`,{value:d.value,disabled:d.disabled,children:g},d.value),[d.disabled,d.value,g]),{onNativeOptionAdd:v,onNativeOptionRemove:y}=f;return c(()=>(v(_),()=>y(_)),[v,y,_]),(0,H.jsxs)(H.Fragment,{children:[(0,H.jsx)(u.span,{id:d.textId,...a,ref:h}),d.isSelected&&s.valueNode&&!s.valueNodeHasChildren?se.createPortal(a.children,s.valueNode):null]})});qe.displayName=Q;var Je=`SelectItemIndicator`,Ye=V.forwardRef((e,t)=>{let{__scopeSelect:n,...r}=e;return Ge(Je,n).isSelected?(0,H.jsx)(u.span,{"aria-hidden":!0,...r,ref:t}):null});Ye.displayName=Je;var Xe=`SelectScrollUpButton`,Ze=V.forwardRef((e,t)=>{let n=Z(Xe,e.__scopeSelect),r=Pe(Xe,e.__scopeSelect),[i,a]=V.useState(!1),s=o(t,r.onScrollButtonChange);return c(()=>{if(n.viewport&&n.isPositioned){let e=function(){a(t.scrollTop>0)},t=n.viewport;return e(),t.addEventListener(`scroll`,e),()=>t.removeEventListener(`scroll`,e)}},[n.viewport,n.isPositioned]),i?(0,H.jsx)(et,{...e,ref:s,onAutoScroll:()=>{let{viewport:e,selectedItem:t}=n;e&&t&&(e.scrollTop-=t.offsetHeight)}}):null});Ze.displayName=Xe;var Qe=`SelectScrollDownButton`,$e=V.forwardRef((e,t)=>{let n=Z(Qe,e.__scopeSelect),r=Pe(Qe,e.__scopeSelect),[i,a]=V.useState(!1),s=o(t,r.onScrollButtonChange);return c(()=>{if(n.viewport&&n.isPositioned){let e=function(){let e=t.scrollHeight-t.clientHeight;a(Math.ceil(t.scrollTop)<e)},t=n.viewport;return e(),t.addEventListener(`scroll`,e),()=>t.removeEventListener(`scroll`,e)}},[n.viewport,n.isPositioned]),i?(0,H.jsx)(et,{...e,ref:s,onAutoScroll:()=>{let{viewport:e,selectedItem:t}=n;e&&t&&(e.scrollTop+=t.offsetHeight)}}):null});$e.displayName=Qe;var et=V.forwardRef((e,t)=>{let{__scopeSelect:n,onAutoScroll:r,...i}=e,a=Z(`SelectScrollButton`,n),o=V.useRef(null),s=G(n),d=V.useCallback(()=>{o.current!==null&&(window.clearInterval(o.current),o.current=null)},[]);return V.useEffect(()=>()=>d(),[d]),c(()=>{s().find(e=>e.ref.current===document.activeElement)?.ref.current?.scrollIntoView({block:`nearest`})},[s]),(0,H.jsx)(u.div,{"aria-hidden":!0,...i,ref:t,style:{flexShrink:0,...i.style},onPointerDown:l(i.onPointerDown,()=>{o.current===null&&(o.current=window.setInterval(r,50))}),onPointerMove:l(i.onPointerMove,()=>{a.onItemLeave?.(),o.current===null&&(o.current=window.setInterval(r,50))}),onPointerLeave:l(i.onPointerLeave,()=>{d()})})}),tt=`SelectSeparator`,nt=V.forwardRef((e,t)=>{let{__scopeSelect:n,...r}=e;return(0,H.jsx)(u.div,{"aria-hidden":!0,...r,ref:t})});nt.displayName=tt;var rt=`SelectArrow`,it=V.forwardRef((e,t)=>{let{__scopeSelect:n,...r}=e,i=q(n),a=J(rt,n),o=Z(rt,n);return a.open&&o.position===`popper`?(0,H.jsx)(C,{...i,...r,ref:t}):null});it.displayName=rt;var at=`SelectBubbleInput`,ot=V.forwardRef(({__scopeSelect:e,value:t,...n},r)=>{let i=V.useRef(null),a=o(r,i),s=re(t);return V.useEffect(()=>{let e=i.current;if(!e)return;let n=window.HTMLSelectElement.prototype,r=Object.getOwnPropertyDescriptor(n,`value`).set;if(s!==t&&r){let n=new Event(`change`,{bubbles:!0});r.call(e,t),e.dispatchEvent(n)}},[s,t]),(0,H.jsx)(u.select,{...n,style:{...F,...n.style},ref:a,defaultValue:t})});ot.displayName=at;function st(e){return e===``||e===void 0}function ct(e){let t=s(e),n=V.useRef(``),r=V.useRef(0),i=V.useCallback(e=>{let i=n.current+e;t(i),(function e(t){n.current=t,window.clearTimeout(r.current),t!==``&&(r.current=window.setTimeout(()=>e(``),1e3))})(i)},[t]),a=V.useCallback(()=>{n.current=``,window.clearTimeout(r.current)},[]);return V.useEffect(()=>()=>window.clearTimeout(r.current),[]),[n,i,a]}function lt(e,t,n){let r=t.length>1&&Array.from(t).every(e=>e===t[0])?t[0]:t,i=n?e.indexOf(n):-1,a=ut(e,Math.max(i,0));r.length===1&&(a=a.filter(e=>e!==n));let o=a.find(e=>e.textValue.toLowerCase().startsWith(r.toLowerCase()));return o===n?void 0:o}function ut(e,t){return e.map((n,r)=>e[(t+r)%e.length])}var dt=he,ft=_e,pt=ye,mt=xe,ht=Ce,gt=we,_t=Ie,vt=Ke,yt=qe,bt=Ye,xt=Ze,St=$e;function $({className:e,...t}){return(0,H.jsx)(oe,{"data-slot":`label`,className:O(`flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50`,e),...t})}function Ct({...e}){return(0,H.jsx)(dt,{"data-slot":`select`,...e})}function wt({...e}){return(0,H.jsx)(pt,{"data-slot":`select-value`,...e})}function Tt({className:e,size:t=`default`,children:n,...r}){return(0,H.jsxs)(ft,{"data-slot":`select-trigger`,"data-size":t,className:O(`flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground`,e),...r,children:[n,(0,H.jsx)(mt,{asChild:!0,children:(0,H.jsx)(j,{className:`size-4 opacity-50`})})]})}function Et({className:e,children:t,position:n=`item-aligned`,align:r=`center`,...i}){return(0,H.jsx)(ht,{children:(0,H.jsxs)(gt,{"data-slot":`select-content`,className:O(`relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95`,n===`popper`&&`data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1`,e),position:n,align:r,...i,children:[(0,H.jsx)(Ot,{}),(0,H.jsx)(_t,{className:O(`p-1`,n===`popper`&&`h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1`),children:t}),(0,H.jsx)(kt,{})]})})}function Dt({className:e,children:t,...n}){return(0,H.jsxs)(vt,{"data-slot":`select-item`,className:O(`relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2`,e),...n,children:[(0,H.jsx)(`span`,{"data-slot":`select-item-indicator`,className:`absolute right-2 flex size-3.5 items-center justify-center`,children:(0,H.jsx)(bt,{children:(0,H.jsx)(P,{className:`size-4`})})}),(0,H.jsx)(yt,{children:t})]})}function Ot({className:e,...t}){return(0,H.jsx)(xt,{"data-slot":`select-scroll-up-button`,className:O(`flex cursor-default items-center justify-center py-1`,e),...t,children:(0,H.jsx)(R,{className:`size-4`})})}function kt({className:e,...t}){return(0,H.jsx)(St,{"data-slot":`select-scroll-down-button`,className:O(`flex cursor-default items-center justify-center py-1`,e),...t,children:(0,H.jsx)(j,{className:`size-4`})})}function At(){return A.get(`/api/settings/ai`)}function jt(e){return A.put(`/api/settings/ai`,e)}var Mt=[{value:`claude-sonnet-4-6`,label:`Claude Sonnet 4.6`},{value:`claude-opus-4-6`,label:`Claude Opus 4.6`},{value:`claude-haiku-4-5`,label:`Claude Haiku 4.5`}],Nt=[{value:`low`,label:`Low`},{value:`medium`,label:`Medium`},{value:`high`,label:`High`},{value:`max`,label:`Max`}];function Pt(){let[e,t]=(0,V.useState)(null),[n,r]=(0,V.useState)(!1),[i,a]=(0,V.useState)(null),[o,s]=(0,V.useState)(0);(0,V.useEffect)(()=>{At().then(t).catch(e=>a(e.message))},[]);let c=e?.default_provider??`claude`,l=e?.providers[c],u=async(n,i)=>{if(e){r(!0),a(null);try{t(await jt({providers:{[c]:{[n]:i}}})),s(e=>e+1)}catch(e){a(e.message)}finally{r(!1)}}};return e?(0,H.jsxs)(`div`,{className:`space-y-4`,children:[(0,H.jsx)(`h3`,{className:`text-sm font-medium text-text-secondary`,children:`AI Provider`}),(0,H.jsxs)(`div`,{className:`space-y-3`,children:[(0,H.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,H.jsx)($,{htmlFor:`ai-model`,children:`Model`}),(0,H.jsxs)(Ct,{value:l?.model??`claude-sonnet-4-6`,onValueChange:e=>u(`model`,e),children:[(0,H.jsx)(Tt,{id:`ai-model`,className:`w-full`,children:(0,H.jsx)(wt,{})}),(0,H.jsx)(Et,{children:Mt.map(e=>(0,H.jsx)(Dt,{value:e.value,children:e.label},e.value))})]})]}),(0,H.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,H.jsx)($,{htmlFor:`ai-effort`,children:`Effort`}),(0,H.jsxs)(Ct,{value:l?.effort??`high`,onValueChange:e=>u(`effort`,e),children:[(0,H.jsx)(Tt,{id:`ai-effort`,className:`w-full`,children:(0,H.jsx)(wt,{})}),(0,H.jsx)(Et,{children:Nt.map(e=>(0,H.jsx)(Dt,{value:e.value,children:e.label},e.value))})]})]}),(0,H.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,H.jsx)($,{htmlFor:`ai-max-turns`,children:`Max Turns (1-500)`}),(0,H.jsx)(M,{id:`ai-max-turns`,type:`number`,min:1,max:500,defaultValue:l?.max_turns??100,onBlur:e=>{let t=parseInt(e.target.value);isNaN(t)||u(`max_turns`,t)}},`turns-${o}`)]}),(0,H.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,H.jsx)($,{htmlFor:`ai-budget`,children:`Max Budget (USD)`}),(0,H.jsx)(M,{id:`ai-budget`,type:`number`,step:.1,min:.01,max:50,defaultValue:l?.max_budget_usd??``,placeholder:`No limit`,onBlur:e=>{let t=parseFloat(e.target.value);u(`max_budget_usd`,isNaN(t)?void 0:t)}},`budget-${o}`)]}),(0,H.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,H.jsx)($,{htmlFor:`ai-thinking`,children:`Thinking Budget (tokens)`}),(0,H.jsx)(M,{id:`ai-thinking`,type:`number`,min:0,defaultValue:l?.thinking_budget_tokens??``,placeholder:`Disabled`,onBlur:e=>{let t=parseInt(e.target.value);u(`thinking_budget_tokens`,isNaN(t)?void 0:t)}},`thinking-${o}`)]})]}),n&&(0,H.jsx)(`p`,{className:`text-xs text-text-subtle`,children:`Saving...`}),i&&(0,H.jsx)(`p`,{className:`text-xs text-red-500`,children:i})]}):(0,H.jsxs)(`div`,{className:`space-y-3`,children:[(0,H.jsx)(`h3`,{className:`text-sm font-medium text-text-secondary`,children:`AI Provider`}),(0,H.jsx)(`p`,{className:`text-sm text-text-subtle`,children:i?`Error: ${i}`:`Loading...`})]})}function Ft(e){let t=(e+`=`.repeat((4-e.length%4)%4)).replace(/-/g,`+`).replace(/_/g,`/`),n=atob(t);return Uint8Array.from(n,e=>e.charCodeAt(0))}function It(){let[e,t]=(0,V.useState)(`default`),[n,r]=(0,V.useState)(!1),[i,a]=(0,V.useState)(!1);return(0,V.useEffect)(()=>{`Notification`in window&&t(Notification.permission),r(localStorage.getItem(`ppm-push-subscribed`)===`true`)},[]),{permission:e,isSubscribed:n,loading:i,subscribe:(0,V.useCallback)(async()=>{a(!0);try{let e=await Notification.requestPermission();if(t(e),e!==`granted`)return;let n={},i=k();i&&(n.Authorization=`Bearer ${i}`);let a=await(await fetch(`/api/push/vapid-key`,{headers:n})).json();if(!a.ok)throw Error(a.error||`Failed to get VAPID key`);let o=await(await navigator.serviceWorker.ready).pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:Ft(a.data.publicKey).buffer});await fetch(`/api/push/subscribe`,{method:`POST`,headers:{...n,"Content-Type":`application/json`},body:JSON.stringify(o.toJSON())}),r(!0),localStorage.setItem(`ppm-push-subscribed`,`true`)}catch(e){console.error(`[push] Subscribe failed:`,e)}finally{a(!1)}},[]),unsubscribe:(0,V.useCallback)(async()=>{a(!0);try{let e=await(await navigator.serviceWorker.ready).pushManager.getSubscription();if(e){let t={"Content-Type":`application/json`},n=k();n&&(t.Authorization=`Bearer ${n}`),await fetch(`/api/push/subscribe`,{method:`DELETE`,headers:t,body:JSON.stringify({endpoint:e.endpoint})}),await e.unsubscribe()}r(!1),localStorage.removeItem(`ppm-push-subscribed`)}catch(e){console.error(`[push] Unsubscribe failed:`,e)}finally{a(!1)}},[])}}var Lt=[{value:`light`,label:`Light`,icon:ne},{value:`dark`,label:`Dark`,icon:B},{value:`system`,label:`System`,icon:z}],Rt=`PushManager`in window&&`serviceWorker`in navigator,zt=/iPhone|iPad/.test(navigator.userAgent)&&!window.matchMedia(`(display-mode: standalone)`).matches;function Bt(){let{theme:e,setTheme:t}=N(),{permission:n,isSubscribed:r,loading:i,subscribe:a,unsubscribe:o}=It();return(0,H.jsxs)(`div`,{className:`h-full p-4 space-y-6 overflow-auto max-w-lg`,children:[(0,H.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Settings`}),(0,H.jsxs)(`div`,{className:`space-y-3`,children:[(0,H.jsx)(`h3`,{className:`text-sm font-medium text-text-secondary`,children:`Theme`}),(0,H.jsx)(`div`,{className:`flex gap-2`,children:Lt.map(n=>{let r=n.icon;return(0,H.jsxs)(v,{variant:e===n.value?`default`:`outline`,size:`lg`,onClick:()=>t(n.value),className:O(`flex-1 gap-2`,e===n.value&&`ring-2 ring-primary`),children:[(0,H.jsx)(r,{className:`size-4`}),n.label]},n.value)})})]}),(0,H.jsx)(I,{}),(0,H.jsx)(Pt,{}),(0,H.jsx)(I,{}),(0,H.jsxs)(`div`,{className:`space-y-3`,children:[(0,H.jsx)(`h3`,{className:`text-sm font-medium text-text-secondary`,children:`Notifications`}),Rt?(0,H.jsxs)(H.Fragment,{children:[(0,H.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,H.jsxs)(`div`,{className:`flex items-center gap-2`,children:[r?(0,H.jsx)(te,{className:`size-4`}):(0,H.jsx)(L,{className:`size-4`}),(0,H.jsx)(`span`,{className:`text-sm`,children:`Push notifications`})]}),(0,H.jsx)(v,{variant:r?`default`:`outline`,size:`sm`,disabled:i||n===`denied`,onClick:()=>r?o():a(),children:i?`...`:r?`On`:`Off`})]}),n===`denied`&&(0,H.jsx)(`p`,{className:`text-xs text-destructive`,children:`Notifications blocked. Enable in browser settings.`}),zt&&(0,H.jsx)(`p`,{className:`text-xs text-text-subtle`,children:`On iOS, install PPM to Home Screen for push notifications.`})]}):(0,H.jsx)(`p`,{className:`text-sm text-text-subtle`,children:`Push notifications are not supported in this browser.`})]}),(0,H.jsx)(I,{}),(0,H.jsxs)(`div`,{className:`space-y-3`,children:[(0,H.jsx)(`h3`,{className:`text-sm font-medium text-text-secondary`,children:`About`}),(0,H.jsx)(`p`,{className:`text-sm text-text-secondary`,children:`PPM — Personal Project Manager`}),(0,H.jsx)(`p`,{className:`text-xs text-text-subtle`,children:`A mobile-first web IDE for managing your projects.`})]})]})}export{Bt as SettingsTab};
@@ -8,7 +8,7 @@
8
8
  <link rel="preconnect" href="https://fonts.googleapis.com" />
9
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
10
  <link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500;600;700&family=Geist:wght@400;500;600;700&display=swap" rel="stylesheet" />
11
- <script type="module" crossorigin src="/assets/index-DOHQ7GlD.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-DBaFu6Af.js"></script>
12
12
  <link rel="modulepreload" crossorigin href="/assets/jsx-runtime-BFALxl05.js">
13
13
  <link rel="modulepreload" crossorigin href="/assets/utils-D6me7KDg.js">
14
14
  <link rel="modulepreload" crossorigin href="/assets/button-CQ5h5gxS.js">
package/dist/web/sw.js CHANGED
@@ -1 +1 @@
1
- if(!self.define){let s,e={};const l=(l,i)=>(l=new URL(l+".js",i).href,e[l]||new Promise(e=>{if("document"in self){const s=document.createElement("script");s.src=l,s.onload=e,document.head.appendChild(s)}else s=l,importScripts(l),e()}).then(()=>{let s=e[l];if(!s)throw new Error(`Module ${l} didn’t register its module`);return s}));self.define=(i,n)=>{const r=s||("document"in self?document.currentScript.src:"")||location.href;if(e[r])return;let t={};const u=s=>l(s,r),o={module:{uri:r},exports:t,require:u};e[r]=Promise.all(i.map(s=>o[s]||u(s))).then(s=>(n(...s),t))}}define(["./workbox-3e722498"],function(s){"use strict";self.skipWaiting(),s.clientsClaim(),s.precacheAndRoute([{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"index.html",revision:"b9a32029baccfb8cab434879c2000f60"},{url:"icon-512.svg",revision:"a0fb34fc84eb148d51812cd62669f20d"},{url:"icon-192.svg",revision:"a0fb34fc84eb148d51812cd62669f20d"},{url:"assets/x-Bpqyw40Y.js",revision:null},{url:"assets/utils-D6me7KDg.js",revision:null},{url:"assets/trash-2-Dc17nbCE.js",revision:null},{url:"assets/terminal-tab-Cg4Pm_3X.js",revision:null},{url:"assets/terminal-tab-BrP-ENHg.css",revision:null},{url:"assets/settings-tab-Ceuow24i.js",revision:null},{url:"assets/refresh-cw-S6I91MHO.js",revision:null},{url:"assets/react-C32bf_ch.js",revision:null},{url:"assets/project-list-7ReggIMy.js",revision:null},{url:"assets/marked.esm-Cv8mjgnt.js",revision:null},{url:"assets/jsx-runtime-BFALxl05.js",revision:null},{url:"assets/index-Jhl6F2vS.css",revision:null},{url:"assets/index-DOHQ7GlD.js",revision:null},{url:"assets/git-status-panel-B_iCL1Ge.js",revision:null},{url:"assets/git-graph-D4IUX9-7.js",revision:null},{url:"assets/external-link-C6Y-D528.js",revision:null},{url:"assets/dist-CYANqO1g.js",revision:null},{url:"assets/dist-BeHIxUn0.js",revision:null},{url:"assets/dist-0XHv8Vwc.js",revision:null},{url:"assets/diff-viewer-CGIQRv_l.js",revision:null},{url:"assets/dialog-CCBmXo6-.js",revision:null},{url:"assets/copy-D_Q54D-v.js",revision:null},{url:"assets/columns-2-DsiY76NQ.js",revision:null},{url:"assets/code-editor-BauDrXcB.js",revision:null},{url:"assets/chat-tab-Cpj7_-mS.js",revision:null},{url:"assets/button-CQ5h5gxS.js",revision:null},{url:"assets/arrow-up-from-line-DjfWTP75.js",revision:null},{url:"assets/api-client-tgjN9Mx8.js",revision:null},{url:"manifest.webmanifest",revision:"79c8870653c8f419f2e3323085e1f4be"}],{}),s.cleanupOutdatedCaches(),s.registerRoute(new s.NavigationRoute(s.createHandlerBoundToURL("index.html"))),s.registerRoute(/^https?:\/\/.*\/api\//,new s.NetworkOnly,"GET"),s.registerRoute(/^https?:\/\/.*\/ws\//,new s.NetworkOnly,"GET")});
1
+ try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"62550c3ade183e8422ab1b7c5e87d72e","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":null,"url":"assets/x-Bpqyw40Y.js"},{"revision":null,"url":"assets/utils-D6me7KDg.js"},{"revision":null,"url":"assets/trash-2-Dc17nbCE.js"},{"revision":null,"url":"assets/terminal-tab-Cg4Pm_3X.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/settings-tab-C5SlVPjG.js"},{"revision":null,"url":"assets/refresh-cw-S6I91MHO.js"},{"revision":null,"url":"assets/react-C32bf_ch.js"},{"revision":null,"url":"assets/project-list-Ha_JrM9s.js"},{"revision":null,"url":"assets/marked.esm-Cv8mjgnt.js"},{"revision":null,"url":"assets/jsx-runtime-BFALxl05.js"},{"revision":null,"url":"assets/index-Jhl6F2vS.css"},{"revision":null,"url":"assets/index-DBaFu6Af.js"},{"revision":null,"url":"assets/git-status-panel-D50lP9Ru.js"},{"revision":null,"url":"assets/git-graph-BPt8qfBw.js"},{"revision":null,"url":"assets/external-link-C6Y-D528.js"},{"revision":null,"url":"assets/dist-CYANqO1g.js"},{"revision":null,"url":"assets/dist-BeHIxUn0.js"},{"revision":null,"url":"assets/dist-0XHv8Vwc.js"},{"revision":null,"url":"assets/diff-viewer-CX7iJZTM.js"},{"revision":null,"url":"assets/dialog-CCBmXo6-.js"},{"revision":null,"url":"assets/copy-D_Q54D-v.js"},{"revision":null,"url":"assets/columns-2-DsiY76NQ.js"},{"revision":null,"url":"assets/code-editor-CYWA87cs.js"},{"revision":null,"url":"assets/chat-tab-DGDxIbGD.js"},{"revision":null,"url":"assets/button-CQ5h5gxS.js"},{"revision":null,"url":"assets/arrow-up-from-line-DjfWTP75.js"},{"revision":null,"url":"assets/api-client-tgjN9Mx8.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -25,6 +25,7 @@
25
25
  "@types/node": "^25.5.0",
26
26
  "@types/react": "^19.2.14",
27
27
  "@types/react-dom": "^19.2.3",
28
+ "@types/web-push": "^3.6.4",
28
29
  "@vitejs/plugin-react": "^6.0.1",
29
30
  "tailwindcss": "^4.2.1",
30
31
  "vite": "^8.0.0",
@@ -49,7 +50,6 @@
49
50
  "@xterm/addon-fit": "^0.11.0",
50
51
  "@xterm/addon-web-links": "^0.12.0",
51
52
  "@xterm/xterm": "^6.0.0",
52
-
53
53
  "class-variance-authority": "^0.7.1",
54
54
  "clsx": "^2.1.1",
55
55
  "codemirror": "^6.0.2",
@@ -68,6 +68,7 @@
68
68
  "simple-git": "^3.33.0",
69
69
  "sonner": "^2.0.7",
70
70
  "tailwind-merge": "^3.5.0",
71
+ "web-push": "^3.6.7",
71
72
  "zustand": "^5.0.11"
72
73
  }
73
74
  }
@@ -265,8 +265,8 @@ export class ClaudeAgentSdkProvider implements AIProvider {
265
265
  cwd: meta.projectPath,
266
266
  // Use full Claude Code system prompt (coding guidelines, security, response style)
267
267
  systemPrompt: { type: "preset", preset: "claude_code" },
268
- // Load project CLAUDE.md, skills, and hooks from project directory
269
- settingSources: ["project"],
268
+ // Load skills/settings from both user (~/.claude) and project directory
269
+ settingSources: ["user", "project"],
270
270
  // Neutralize Anthropic env vars so SDK uses subscription, not project .env keys.
271
271
  env: {
272
272
  ...process.env,
@@ -5,6 +5,7 @@ import { VERSION } from "../version.ts";
5
5
  import { authMiddleware } from "./middleware/auth.ts";
6
6
  import { projectRoutes } from "./routes/projects.ts";
7
7
  import { settingsRoutes } from "./routes/settings.ts";
8
+ import { pushRoutes } from "./routes/push.ts";
8
9
  import { staticRoutes } from "./routes/static.ts";
9
10
  import { projectScopedRouter } from "./routes/project-scoped.ts";
10
11
  import { terminalWebSocket } from "./ws/terminal.ts";
@@ -97,6 +98,7 @@ app.get("/api/auth/check", (c) => c.json(ok(true)));
97
98
 
98
99
  // API routes
99
100
  app.route("/api/settings", settingsRoutes);
101
+ app.route("/api/push", pushRoutes);
100
102
  app.route("/api/projects", projectRoutes);
101
103
  app.route("/api/project/:projectName", projectScopedRouter);
102
104
 
@@ -0,0 +1,54 @@
1
+ import { Hono } from "hono";
2
+ import { pushService } from "../../services/push-notification.service.ts";
3
+ import { ok, err } from "../../types/api.ts";
4
+
5
+ export const pushRoutes = new Hono();
6
+
7
+ /** GET /push/vapid-key — return VAPID public key for frontend subscription */
8
+ pushRoutes.get("/vapid-key", (c) => {
9
+ try {
10
+ const publicKey = pushService.getVapidPublicKey();
11
+ return c.json(ok({ publicKey }));
12
+ } catch (e) {
13
+ return c.json(err((e as Error).message), 500);
14
+ }
15
+ });
16
+
17
+ /** POST /push/subscribe — save a push subscription */
18
+ pushRoutes.post("/subscribe", async (c) => {
19
+ try {
20
+ const body = await c.req.json<{
21
+ endpoint?: string;
22
+ keys?: { p256dh?: string; auth?: string };
23
+ expirationTime?: number | null;
24
+ }>();
25
+
26
+ if (!body.endpoint || !body.keys?.p256dh || !body.keys?.auth) {
27
+ return c.json(err("Invalid subscription: missing endpoint or keys"), 400);
28
+ }
29
+
30
+ pushService.saveSubscription({
31
+ endpoint: body.endpoint,
32
+ keys: { p256dh: body.keys.p256dh, auth: body.keys.auth },
33
+ expirationTime: body.expirationTime,
34
+ });
35
+
36
+ return c.json(ok(true));
37
+ } catch (e) {
38
+ return c.json(err((e as Error).message), 400);
39
+ }
40
+ });
41
+
42
+ /** DELETE /push/subscribe — remove a push subscription by endpoint */
43
+ pushRoutes.delete("/subscribe", async (c) => {
44
+ try {
45
+ const body = await c.req.json<{ endpoint?: string }>();
46
+ if (!body.endpoint) {
47
+ return c.json(err("Missing endpoint"), 400);
48
+ }
49
+ pushService.removeSubscription(body.endpoint);
50
+ return c.json(ok(true));
51
+ } catch (e) {
52
+ return c.json(err((e as Error).message), 400);
53
+ }
54
+ });
@@ -108,6 +108,13 @@ export const chatWebSocket = {
108
108
  logSessionEvent(sessionId, "ERROR", ev.message ?? JSON.stringify(ev).slice(0, 300));
109
109
  } else if (evType === "done") {
110
110
  logSessionEvent(sessionId, "DONE", `subtype=${ev.resultSubtype ?? "none"} turns=${ev.numTurns ?? "?"}`);
111
+ // Fire-and-forget push notification with project + session title
112
+ import("../../services/push-notification.service.ts").then(({ pushService }) => {
113
+ const project = ws.data.projectName || "Project";
114
+ const session = chatService.getSession(sessionId);
115
+ const sessionTitle = session?.title || `Session ${sessionId.slice(0, 8)}`;
116
+ pushService.notifyAll("Chat completed", `${project} — ${sessionTitle}`).catch(() => {});
117
+ }).catch(() => {});
111
118
  } else {
112
119
  logSessionEvent(sessionId, evType.toUpperCase(), JSON.stringify(ev).slice(0, 200));
113
120
  }
@@ -0,0 +1,118 @@
1
+ import webpush from "web-push";
2
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
3
+ import { resolve, dirname } from "node:path";
4
+ import { homedir } from "node:os";
5
+ import { configService } from "./config.service.ts";
6
+ import type { PushConfig } from "../types/config.ts";
7
+
8
+ const SUBS_PATH = resolve(homedir(), ".ppm", "push-subscriptions.json");
9
+
10
+ interface PushSubscriptionData {
11
+ endpoint: string;
12
+ keys: { p256dh: string; auth: string };
13
+ expirationTime?: number | null;
14
+ }
15
+
16
+ /** Load subscriptions from disk */
17
+ function loadSubscriptions(): PushSubscriptionData[] {
18
+ try {
19
+ if (existsSync(SUBS_PATH)) {
20
+ return JSON.parse(readFileSync(SUBS_PATH, "utf-8"));
21
+ }
22
+ } catch { /* corrupt file — start fresh */ }
23
+ return [];
24
+ }
25
+
26
+ /** Save subscriptions to disk */
27
+ function saveSubscriptions(subs: PushSubscriptionData[]): void {
28
+ const dir = dirname(SUBS_PATH);
29
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
30
+ writeFileSync(SUBS_PATH, JSON.stringify(subs, null, 2), "utf-8");
31
+ }
32
+
33
+ class PushNotificationService {
34
+ private initialized = false;
35
+
36
+ /** Initialize VAPID keys — auto-generate if missing */
37
+ init(): void {
38
+ if (this.initialized) return;
39
+
40
+ let push = configService.get("push") as PushConfig | undefined;
41
+ if (!push?.vapid_public_key || !push?.vapid_private_key) {
42
+ const keys = webpush.generateVAPIDKeys();
43
+ push = {
44
+ vapid_public_key: keys.publicKey,
45
+ vapid_private_key: keys.privateKey,
46
+ vapid_subject: "https://ppm.local",
47
+ };
48
+ configService.set("push", push);
49
+ configService.save();
50
+ console.log("[push] VAPID keys generated and saved to config");
51
+ }
52
+
53
+ webpush.setVapidDetails(
54
+ push.vapid_subject,
55
+ push.vapid_public_key,
56
+ push.vapid_private_key,
57
+ );
58
+ this.initialized = true;
59
+ }
60
+
61
+ /** Get VAPID public key for frontend subscription */
62
+ getVapidPublicKey(): string {
63
+ this.init();
64
+ const push = configService.get("push") as PushConfig | undefined;
65
+ return push?.vapid_public_key ?? "";
66
+ }
67
+
68
+ /** Save a new push subscription */
69
+ saveSubscription(sub: PushSubscriptionData): void {
70
+ const subs = loadSubscriptions();
71
+ // Deduplicate by endpoint
72
+ const filtered = subs.filter((s) => s.endpoint !== sub.endpoint);
73
+ filtered.push(sub);
74
+ saveSubscriptions(filtered);
75
+ }
76
+
77
+ /** Remove a push subscription by endpoint */
78
+ removeSubscription(endpoint: string): void {
79
+ const subs = loadSubscriptions();
80
+ saveSubscriptions(subs.filter((s) => s.endpoint !== endpoint));
81
+ }
82
+
83
+ /** Send push notification to all subscriptions (fire-and-forget) */
84
+ async notifyAll(title: string, body: string): Promise<void> {
85
+ this.init();
86
+ const subs = loadSubscriptions();
87
+ if (subs.length === 0) return;
88
+
89
+ const payload = JSON.stringify({ title, body });
90
+ const expired: string[] = [];
91
+
92
+ await Promise.allSettled(
93
+ subs.map(async (sub) => {
94
+ try {
95
+ await webpush.sendNotification(sub, payload);
96
+ } catch (error: unknown) {
97
+ const statusCode = (error as { statusCode?: number }).statusCode;
98
+ // 404 or 410 = subscription expired/invalid — mark for removal
99
+ if (statusCode === 410 || statusCode === 404) {
100
+ expired.push(sub.endpoint);
101
+ }
102
+ }
103
+ }),
104
+ );
105
+
106
+ // Auto-cleanup expired subscriptions
107
+ if (expired.length > 0) {
108
+ const remaining = loadSubscriptions().filter(
109
+ (s) => !expired.includes(s.endpoint),
110
+ );
111
+ saveSubscriptions(remaining);
112
+ console.log(`[push] Removed ${expired.length} expired subscriptions`);
113
+ }
114
+ }
115
+ }
116
+
117
+ /** Singleton push notification service */
118
+ export const pushService = new PushNotificationService();
@@ -1,3 +1,9 @@
1
+ export interface PushConfig {
2
+ vapid_public_key: string;
3
+ vapid_private_key: string;
4
+ vapid_subject: string;
5
+ }
6
+
1
7
  export interface PpmConfig {
2
8
  device_name: string;
3
9
  port: number;
@@ -5,6 +11,7 @@ export interface PpmConfig {
5
11
  auth: AuthConfig;
6
12
  projects: ProjectConfig[];
7
13
  ai: AIConfig;
14
+ push?: PushConfig;
8
15
  }
9
16
 
10
17
  export interface AuthConfig {
@@ -1,9 +1,10 @@
1
- import { Moon, Sun, Monitor } from "lucide-react";
1
+ import { Moon, Sun, Monitor, Bell, BellOff } from "lucide-react";
2
2
  import { Button } from "@/components/ui/button";
3
3
  import { Separator } from "@/components/ui/separator";
4
4
  import { useSettingsStore, type Theme } from "@/stores/settings-store";
5
5
  import { cn } from "@/lib/utils";
6
6
  import { AISettingsSection } from "./ai-settings-section";
7
+ import { usePushNotification } from "@/hooks/use-push-notification";
7
8
 
8
9
  const THEME_OPTIONS: { value: Theme; label: string; icon: React.ElementType }[] = [
9
10
  { value: "light", label: "Light", icon: Sun },
@@ -11,8 +12,13 @@ const THEME_OPTIONS: { value: Theme; label: string; icon: React.ElementType }[]
11
12
  { value: "system", label: "System", icon: Monitor },
12
13
  ];
13
14
 
15
+ const pushSupported = "PushManager" in window && "serviceWorker" in navigator;
16
+ const isIosNonPwa = /iPhone|iPad/.test(navigator.userAgent) &&
17
+ !window.matchMedia("(display-mode: standalone)").matches;
18
+
14
19
  export function SettingsTab() {
15
20
  const { theme, setTheme } = useSettingsStore();
21
+ const { permission, isSubscribed, loading, subscribe, unsubscribe } = usePushNotification();
16
22
 
17
23
  return (
18
24
  <div className="h-full p-4 space-y-6 overflow-auto max-w-lg">
@@ -48,6 +54,44 @@ export function SettingsTab() {
48
54
 
49
55
  <Separator />
50
56
 
57
+ <div className="space-y-3">
58
+ <h3 className="text-sm font-medium text-text-secondary">Notifications</h3>
59
+ {!pushSupported ? (
60
+ <p className="text-sm text-text-subtle">
61
+ Push notifications are not supported in this browser.
62
+ </p>
63
+ ) : (
64
+ <>
65
+ <div className="flex items-center justify-between">
66
+ <div className="flex items-center gap-2">
67
+ {isSubscribed ? <Bell className="size-4" /> : <BellOff className="size-4" />}
68
+ <span className="text-sm">Push notifications</span>
69
+ </div>
70
+ <Button
71
+ variant={isSubscribed ? "default" : "outline"}
72
+ size="sm"
73
+ disabled={loading || permission === "denied"}
74
+ onClick={() => (isSubscribed ? unsubscribe() : subscribe())}
75
+ >
76
+ {loading ? "..." : isSubscribed ? "On" : "Off"}
77
+ </Button>
78
+ </div>
79
+ {permission === "denied" && (
80
+ <p className="text-xs text-destructive">
81
+ Notifications blocked. Enable in browser settings.
82
+ </p>
83
+ )}
84
+ {isIosNonPwa && (
85
+ <p className="text-xs text-text-subtle">
86
+ On iOS, install PPM to Home Screen for push notifications.
87
+ </p>
88
+ )}
89
+ </>
90
+ )}
91
+ </div>
92
+
93
+ <Separator />
94
+
51
95
  <div className="space-y-3">
52
96
  <h3 className="text-sm font-medium text-text-secondary">About</h3>
53
97
  <p className="text-sm text-text-secondary">
@@ -0,0 +1,96 @@
1
+ import { useState, useEffect, useCallback } from "react";
2
+ import { getAuthToken } from "@/lib/api-client";
3
+
4
+ /** Convert VAPID public key from base64url to Uint8Array for PushManager */
5
+ function urlBase64ToUint8Array(base64String: string): Uint8Array {
6
+ const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
7
+ const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
8
+ const rawData = atob(base64);
9
+ return Uint8Array.from(rawData, (char) => char.charCodeAt(0));
10
+ }
11
+
12
+ export function usePushNotification() {
13
+ const [permission, setPermission] = useState<NotificationPermission>("default");
14
+ const [isSubscribed, setIsSubscribed] = useState(false);
15
+ const [loading, setLoading] = useState(false);
16
+
17
+ // Check current permission and subscription state on mount
18
+ useEffect(() => {
19
+ if ("Notification" in window) {
20
+ setPermission(Notification.permission);
21
+ }
22
+ setIsSubscribed(localStorage.getItem("ppm-push-subscribed") === "true");
23
+ }, []);
24
+
25
+ const subscribe = useCallback(async () => {
26
+ setLoading(true);
27
+ try {
28
+ // 1. Request notification permission
29
+ const perm = await Notification.requestPermission();
30
+ setPermission(perm);
31
+ if (perm !== "granted") return;
32
+
33
+ // 2. Get VAPID public key from server
34
+ const headers: Record<string, string> = {};
35
+ const token = getAuthToken();
36
+ if (token) headers.Authorization = `Bearer ${token}`;
37
+
38
+ const res = await fetch("/api/push/vapid-key", { headers });
39
+ const json = await res.json();
40
+ if (!json.ok) throw new Error(json.error || "Failed to get VAPID key");
41
+
42
+ // 3. Subscribe via PushManager
43
+ const reg = await navigator.serviceWorker.ready;
44
+ const sub = await reg.pushManager.subscribe({
45
+ userVisibleOnly: true,
46
+ applicationServerKey: urlBase64ToUint8Array(json.data.publicKey).buffer as ArrayBuffer,
47
+ });
48
+
49
+ // 4. Send subscription to server
50
+ await fetch("/api/push/subscribe", {
51
+ method: "POST",
52
+ headers: { ...headers, "Content-Type": "application/json" },
53
+ body: JSON.stringify(sub.toJSON()),
54
+ });
55
+
56
+ setIsSubscribed(true);
57
+ localStorage.setItem("ppm-push-subscribed", "true");
58
+ } catch (err) {
59
+ console.error("[push] Subscribe failed:", err);
60
+ } finally {
61
+ setLoading(false);
62
+ }
63
+ }, []);
64
+
65
+ const unsubscribe = useCallback(async () => {
66
+ setLoading(true);
67
+ try {
68
+ const reg = await navigator.serviceWorker.ready;
69
+ const sub = await reg.pushManager.getSubscription();
70
+ if (sub) {
71
+ // Remove from server
72
+ const headers: Record<string, string> = { "Content-Type": "application/json" };
73
+ const token = getAuthToken();
74
+ if (token) headers.Authorization = `Bearer ${token}`;
75
+
76
+ await fetch("/api/push/subscribe", {
77
+ method: "DELETE",
78
+ headers,
79
+ body: JSON.stringify({ endpoint: sub.endpoint }),
80
+ });
81
+
82
+ // Unsubscribe locally
83
+ await sub.unsubscribe();
84
+ }
85
+
86
+ setIsSubscribed(false);
87
+ localStorage.removeItem("ppm-push-subscribed");
88
+ } catch (err) {
89
+ console.error("[push] Unsubscribe failed:", err);
90
+ } finally {
91
+ setLoading(false);
92
+ }
93
+ }, []);
94
+
95
+ return { permission, isSubscribed, loading, subscribe, unsubscribe };
96
+ }