kandown 0.15.1 โ†’ 0.15.2

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/bin/kandown.js CHANGED
@@ -71,11 +71,32 @@ const PKG_ROOT = resolve(__dirname, '..');
71
71
  // ๐Ÿ“– Default localhost range for the zero-config `kandown` web UI server.
72
72
  // ๐Ÿ“– Single source of truth for the daemon port range. Each kandown project
73
73
  // gets its own daemon on the first free port in this range, so multiple
74
- // projects can run in parallel (A=2048, B=2049, C=2050, ...).
74
+ // projects can run in parallel (A=2048, B=2050, C=2051, ...).
75
75
  const START_PORT_RANGE = 2048;
76
76
  const END_PORT_RANGE = 2150;
77
77
  const DAEMON_FILE = 'daemon.json';
78
78
 
79
+ /**
80
+ * ๐Ÿ“– Ports that Chromium/Firefox/Safari refuse to load (net::ERR_UNSAFE_PORT).
81
+ * These are reserved for well-known services (NFS, ssh, smtp, X11, ...).
82
+ * Browsers block navigation to them with no recourse, so a daemon listening
83
+ * on one of these appears dead in the browser even though it serves fine via
84
+ * curl. We MUST skip them when allocating ports. The most common victim in
85
+ * our 2048+ range is 2049 (NFS), which silently broke the 2nd concurrent
86
+ * kandown project. Sourced from Chromium's net/base/port_util.cc restricted
87
+ * ports list (stable, updated rarely).
88
+ */
89
+ const BROWSER_UNSAFE_PORTS = new Set([
90
+ 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77,
91
+ 79, 87, 95, 101, 102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123,
92
+ 135, 139, 143, 179, 389, 427, 465, 512, 513, 514, 515, 526, 530, 531, 532,
93
+ 540, 548, 554, 556, 563, 587, 601, 636, 993, 995, 1720, 1723, 2049, 3659,
94
+ 4045, 5060, 5061, 6000, 6566, 6665, 6666, 6667, 6668, 6669, 6697, 10080,
95
+ ]);
96
+ function isBrowserUnsafePort(port) {
97
+ return BROWSER_UNSAFE_PORTS.has(port);
98
+ }
99
+
79
100
  // ๐Ÿ“– Get current CLI version from package.json at PKG_ROOT
80
101
  function getCurrentVersion() {
81
102
  try {
@@ -1902,6 +1923,11 @@ async function listenOnAvailablePort(kandownDir, preferredPort) {
1902
1923
 
1903
1924
  // If user specified a specific port, only try that one
1904
1925
  if (isSinglePort) {
1926
+ if (isBrowserUnsafePort(port)) {
1927
+ err(`Port ${c.bold}${port}${c.reset} is reserved for a well-known service and ${c.bold}blocked by all browsers${c.reset} (net::ERR_UNSAFE_PORT).`);
1928
+ log(` Pick another port with ${c.cyan}--port${c.reset}.`);
1929
+ process.exit(1);
1930
+ }
1905
1931
  const server = createServeServer(kandownDir);
1906
1932
  try {
1907
1933
  await listen(server, port);
@@ -1917,6 +1943,10 @@ async function listenOnAvailablePort(kandownDir, preferredPort) {
1917
1943
 
1918
1944
  // ๐Ÿ“– Scan range โ€” try ports until one works
1919
1945
  for (let p = START_PORT_RANGE; p <= END_PORT_RANGE; p++) {
1946
+ // ๐Ÿ“– Skip browser-blocked ports (e.g. 2049 = NFS). A daemon here would
1947
+ // start fine and answer curl, but the browser refuses with
1948
+ // net::ERR_UNSAFE_PORT, so the web UI looks dead. Move to the next port.
1949
+ if (isBrowserUnsafePort(p)) continue;
1920
1950
  // Skip port if occupied by a stale kandown from a DIFFERENT project
1921
1951
  const stale = detectStaleKandown(p, kandownDir);
1922
1952
  if (stale && !stale.reason) {
package/dist/index.html CHANGED
@@ -113,7 +113,7 @@ Error generating stack: `+y.message+`
113
113
 
114
114
  ## Subtasks
115
115
 
116
- `}}async function Hz(t,e){try{return await(await(await(await t.getDirectoryHandle("archive",{create:!1})).getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}}async function Yoe(t,e){try{return await(await t.getDirectoryHandle("archive",{create:!1})).getFileHandle(`${e}.md`),!0}catch{return!1}}async function Bz(t,e){try{return await t.getDirectoryHandle("archive",{create:e})}catch{return null}}async function Xoe(t){if(Kt())return Q4();const e=new Set;for await(const n of t.values())n.kind==="file"&&n.name.endsWith(".md")&&e.add(n.name.slice(0,-3));try{const n=await t.getDirectoryHandle("archive",{create:!1});for await(const a of n.values())a.kind==="file"&&a.name.endsWith(".md")&&e.add(a.name.slice(0,-3))}catch{}return[...e].sort((n,a)=>n.localeCompare(a,void 0,{numeric:!0}))}async function Es(t,e){if(Kt())try{const i=await J4(e);return Ig(i)}catch{return hj(e)}const a=await(async i=>{try{return await(await(await i.getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}})(t)??await Hz(t,e);return a!==null?Ig(a):hj(e)}async function Qoe(t,e){if(Kt())try{const a=await J4(e);return{ok:!0,task:Ig(a)}}catch(a){return a.name==="NotFoundError"?{ok:!1,reason:"not-found"}:{ok:!1,reason:"unknown",error:a}}let n=null;try{n=await(await(await t.getFileHandle(`${e}.md`)).getFile()).text()}catch{}if(n===null)try{n=await Hz(t,e)}catch(a){return{ok:!1,reason:"unknown",error:a}}if(n===null)return{ok:!1,reason:"not-found"};if(n.trim()==="")return{ok:!1,reason:"corrupted",error:new Error(`Task file ${e}.md is empty`)};try{return{ok:!0,task:Ig(n)}}catch(a){return{ok:!1,reason:"corrupted",error:a}}}async function au(t,e,n,a){const i=Y4(n,a);if(Kt())return Boe(e,i);try{const o=await(await(await Yoe(t,e)?await Bz(t,!0):t).getFileHandle(`${e}.md`,{create:!0})).createWritable();try{await o.write(i)}finally{await o.close()}}catch(r){throw ek(r,`${e}.md`)}}async function bj(t,e){if(Kt())return qoe(e);try{await t.removeEntry(`${e}.md`)}catch{}try{await(await t.getDirectoryHandle("archive",{create:!1})).removeEntry(`${e}.md`)}catch{}}async function Joe(t,e){await hs(`/api/tasks/${encodeURIComponent(t)}/archive`,{method:"POST",body:e,headers:{"Content-Type":"text/plain"}})}async function ece(t,e){await hs(`/api/tasks/${encodeURIComponent(t)}/unarchive`,{method:"POST",body:e,headers:{"Content-Type":"text/plain"}})}async function tce(t,e,n,a){const i=Y4(n,a);if(Kt())return Joe(e,i);try{const o=await(await(await Bz(t,!0)).getFileHandle(`${e}.md`,{create:!0})).createWritable();try{await o.write(i)}finally{await o.close()}try{await t.removeEntry(`${e}.md`)}catch{}}catch(r){throw ek(r,`archive/${e}.md`)}}async function nce(t,e,n,a){const i=Y4(n,a);if(Kt())return ece(e,i);try{const s=await(await t.getFileHandle(`${e}.md`,{create:!0})).createWritable();try{await s.write(i)}finally{await s.close()}try{await(await t.getDirectoryHandle("archive",{create:!1})).removeEntry(`${e}.md`)}catch{}}catch(r){throw ek(r,`${e}.md`)}}const ace="kanban-md",ice=1,ap="recentProjects";function eS(){return new Promise((t,e)=>{const n=indexedDB.open(ace,ice);n.onerror=()=>e(n.error),n.onsuccess=()=>t(n.result),n.onupgradeneeded=()=>{const a=n.result;a.objectStoreNames.contains(ap)||a.createObjectStore(ap,{keyPath:"id"})}})}async function E2(t){const e=await eS();return new Promise((n,a)=>{const i=e.transaction(ap,"readwrite");i.objectStore(ap).put(t),i.oncomplete=()=>n(),i.onerror=()=>a(i.error)})}async function Ly(){const t=await eS();return new Promise((e,n)=>{const i=t.transaction(ap,"readonly").objectStore(ap).getAll();i.onsuccess=()=>{const r=i.result||[];r.sort((s,o)=>o.lastOpened-s.lastOpened),e(r.slice(0,10))},i.onerror=()=>n(i.error)})}async function rce(t){const e=await eS();return new Promise((n,a)=>{const i=e.transaction(ap,"readwrite");i.objectStore(ap).delete(t),i.oncomplete=()=>n(),i.onerror=()=>a(i.error)})}async function vj(t,e=!0){const n={mode:e?"readwrite":"read"};try{return await t.queryPermission(n)==="granted"||await t.requestPermission(n)==="granted"}catch(a){return console.warn("[FS] verifyPermission: handle is no longer valid:",a),!1}}function qz(t=Ci){const e=t.board.columns;return e[e.length-1]??"Done"}function sce(t,e=Ci){return t===qz(e)||t.toLowerCase()==="archived"}class oce{dirHandle=null;tasksDirHandle=null;intervalId=null;configHash=null;taskHashes=new Map;knownTaskIds=new Set;listeners=new Map;debounceTimers=new Map;debounceDelay=150;consecutiveErrors=0;maxConsecutiveErrors=5;disabled=!1;start(e,n){this.dirHandle=e,this.tasksDirHandle=n,this.consecutiveErrors=0,this.disabled=!1,this.initHashes(),this.intervalId=setInterval(()=>void this.tick(),300)}stop(){this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null),this.configHash=null,this.taskHashes.clear(),this.knownTaskIds.clear(),this.listeners.clear(),this.debounceTimers.forEach(e=>clearTimeout(e)),this.debounceTimers.clear(),this.consecutiveErrors=0,this.disabled=!1}isDisabled(){return this.disabled}on(e,n){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(n),()=>{this.listeners.get(e)?.delete(n)}}getKnownTaskIds(){return Array.from(this.knownTaskIds)}async initHashes(){if(!(!this.dirHandle||!this.tasksDirHandle))try{const e=await yj(this.dirHandle);e!==null&&(this.configHash=await this.hash(e)),await this.syncTaskDir(!1)}catch(e){console.warn("[Watcher] initHashes error:",e)}}async tick(){if(!(!this.dirHandle||!this.tasksDirHandle)&&!this.disabled)try{const e=await yj(this.dirHandle);if(e!==null){const n=await this.hash(e);this.configHash!==null&&n!==this.configHash&&(this.configHash=n,this.debouncedEmit("configChanged"))}for(const n of[...this.knownTaskIds])try{const a=await _j(this.tasksDirHandle,n);if(a!==null){const i=await this.hash(a),r=this.taskHashes.get(n);r!==void 0&&i!==r&&(this.taskHashes.set(n,i),this.debouncedEmit("taskChanged",n))}}catch(a){console.warn(`[Watcher] Error reading task ${n}:`,a)}await this.syncTaskDir(!0),this.consecutiveErrors=0}catch(e){this.consecutiveErrors++,console.error(`[Watcher] Tick failed (${this.consecutiveErrors}/${this.maxConsecutiveErrors}):`,e),this.consecutiveErrors>=this.maxConsecutiveErrors&&this.disable(`File watcher stopped after ${this.maxConsecutiveErrors} consecutive errors: ${e.message}`)}}disable(e){this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null),this.disabled=!0,this.emit("watcherError",e)}debouncedEmit(e,...n){const a=e+JSON.stringify(n),i=this.debounceTimers.get(a);i&&clearTimeout(i);const r=setTimeout(()=>{this.debounceTimers.delete(a),this.emit(e,...n)},this.debounceDelay);this.debounceTimers.set(a,r)}async syncTaskDir(e){if(!this.tasksDirHandle)return;let n;try{n=this.tasksDirHandle.values()}catch(a){throw a}for await(const a of n){if(a.kind!=="file"||!a.name.endsWith(".md"))continue;const i=a.name.replace(".md","");if(!this.knownTaskIds.has(i))try{this.knownTaskIds.add(i);const r=await _j(this.tasksDirHandle,i);r!==null&&(this.taskHashes.set(i,await this.hash(r)),e&&this.debouncedEmit("newTaskDetected",i))}catch(r){console.warn(`[Watcher] syncTaskDir: failed to read ${i}:`,r)}}}async hash(e){const a=new TextEncoder().encode(e),i=await crypto.subtle.digest("SHA-256",a);return Array.from(new Uint8Array(i)).map(s=>s.toString(16).padStart(2,"0")).join("")}emit(e,...n){this.listeners.get(e)?.forEach(i=>{if(e==="configChanged"){i();return}if(e==="taskChanged"){const[s]=n;i(s);return}if(e==="newTaskDetected"){const[s]=n;i(s);return}const[r]=n;i(r)})}}async function yj(t){try{return await(await(await t.getFileHandle("kandown.json")).getFile()).text()}catch{return null}}async function _j(t,e){try{return await(await(await t.getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}}const al=new oce,cce={soft:[{frequency:520,durationMs:90,delayMs:0,type:"sine"},{frequency:720,durationMs:120,delayMs:95,type:"sine"}],chime:[{frequency:660,durationMs:120,delayMs:0,type:"triangle"},{frequency:880,durationMs:160,delayMs:125,type:"triangle"}],ping:[{frequency:920,durationMs:110,delayMs:0,type:"sine"}],pop:[{frequency:260,durationMs:55,delayMs:0,type:"square"},{frequency:520,durationMs:80,delayMs:60,type:"square"}]};let kj=null;function Uz(){return"Notification"in window?Notification.permission:"unsupported"}async function pce(){return"Notification"in window?await Notification.requestPermission():"unsupported"}function P2({title:t,body:e,config:n}){if(n.notifications.sound&&lce(n.notifications.soundId),!(!n.notifications.browser||Uz()!=="granted"))try{new Notification(t,{body:e})}catch{}}function lce(t){const e=cce[t],n=window.AudioContext??window.webkitAudioContext;if(!(!n||e.length===0))try{kj??=new n;const a=kj;a.state==="suspended"&&a.resume();const i=a.currentTime+.01;e.forEach(r=>{const s=a.createOscillator(),o=a.createGain(),c=i+r.delayMs/1e3,p=c+r.durationMs/1e3;s.type=r.type??"sine",s.frequency.setValueAtTime(r.frequency,c),o.gain.setValueAtTime(1e-4,c),o.gain.exponentialRampToValueAtTime(.08,c+.01),o.gain.exponentialRampToValueAtTime(1e-4,p),s.connect(o),o.connect(a.destination),s.start(c),s.stop(p+.02)})}catch{}}async function Tf(t,e={}){const{maxAttempts:n=3,baseDelayMs:a=500,retryableCheck:i=Foe}=e;let r;for(let s=1;s<=n;s++)try{return await t()}catch(o){if(r=o,!i(o))throw o;if(s<n){const c=a*Math.pow(2,s-1);await new Promise(p=>setTimeout(p,c))}}throw r}function dce(t){let e=-1;for(const n of t)for(const a of n.tasks){const i=a.id.match(/^t(\d+)$/);if(i){const r=parseInt(i[1],10);r>e&&(e=r)}}return"t"+(e+1)}async function uce(){const t=await Q4();return await Promise.all(t.map(async n=>{const{frontmatter:a,body:i}=await zz(n),r={...a,id:a.id||n,status:a.status||"Backlog"},{subtasks:s,bodyWithoutSubtasks:o}=Ds(i);return{id:n,frontmatter:r,body:o,subtasks:s}}))}async function mce(t){const e=await Xoe(t),n=await Promise.all(e.map(async r=>{const s=await Qoe(t,r);return{id:r,result:s}})),a=[],i=[];for(const{id:r,result:s}of n)if(s.ok){const o={...s.task.frontmatter,id:s.task.frontmatter.id||r,status:s.task.frontmatter.status||"Backlog"},{subtasks:c,bodyWithoutSubtasks:p}=Ds(s.task.body);a.push({id:r,frontmatter:o,body:p,subtasks:c})}else{if(s.reason==="not-found")continue;i.push(r)}return{tasks:a,failedIds:i}}async function wj(t,e,n){const a=[];for(const s of e){const o=s.name;s.tasks.forEach((c,p)=>{const d=(async()=>{const{frontmatter:m,body:f}=await Es(t,c.id);await au(t,c.id,{...m,id:c.id,status:o,order:p},f)})();a.push({id:c.id,promise:d})})}const i=await Promise.allSettled(a.map(s=>s.promise)),r=[];return i.forEach((s,o)=>{s.status==="rejected"&&r.push(a[o].id)}),{failedIds:r}}function rl(t){Roe(t.ui.theme,t.ui.skin,t.ui.font,t.ui.background)}function h3(t){return{title:t.frontmatter.title||t.id,status:t.frontmatter.status||"Backlog",body:t.body,subtasks:t.subtasks}}function T2(t){mu.clear(),t.forEach(e=>{mu.set(e.id,h3(e))})}function fce(t){const e=t.split(/[\\/]+/).filter(Boolean),n=e[e.length-1];return n===".kandown"?e[e.length-2]??"Project":n??"Project"}function gce(t,e){return e.reduce((n,a,i)=>{const r=t[i]?.done??!1;return n+(a.done&&!r?1:0)},0)}function hce(t,e){const n=t.subtasks.map(i=>({text:i.text,description:i.description??"",report:i.report??""})),a=e.subtasks.map(i=>({text:i.text,description:i.description??"",report:i.report??""}));return t.title!==e.title||t.body!==e.body||JSON.stringify(n)!==JSON.stringify(a)}let bce=0;const mu=new Map,Mf=new Map;let M2=null;const ue=$oe((t,e)=>({isOpen:!1,loading:!1,dirHandle:null,projectName:null,tasksDirHandle:null,boardTitle:"Project Kanban",columns:[],archivedTasks:[],showArchives:!1,showMetadata:!0,taskContents:new Map,searchMatches:new Map,viewMode:localStorage.getItem("kandown:view")||"board",density:localStorage.getItem("kandown:density")||"comfortable",filters:{search:"",priority:null,tag:null,assignee:null,ownerType:null},commandOpen:!1,cheatsheetOpen:!1,drawerTaskId:null,drawerData:null,currentPage:"board",config:Ci,recentProjects:[],toasts:[],agentHook:null,isReloading:!1,lastReloadError:null,failedTaskIds:[],watcherError:null,hasUnsavedDrawerEdits:!1,lastSaveError:null,drawerRecoveryData:new Map,drawerBaseVersion:null,conflictState:null,showConflictModal:!1,openFolder:async()=>{if(!Oz()){const c=navigator.userAgent||"this browser";e().toast(new Ooe(c).message,"error",8e3);return}let n;try{n=await Goe()}catch(c){c instanceof X4?e().toast("Permission denied โ€” please grant access to the project folder","error"):e().toast("Failed to open folder: "+c.message,"error");return}if(!n)return;const{projectHandle:a,kandownHandle:i,tasksHandle:r}=n,s=a.name;t({dirHandle:i,tasksDirHandle:r,projectName:s}),window.history.pushState({},"",`?p=${encodeURIComponent(s)}`);const o=Kt()?$2():null;try{await E2({id:a.name,name:a.name,handle:a,lastOpened:Date.now(),...o?{kandownDir:o}:{}})}catch(c){console.warn("[Store] Failed to save recent project:",c)}await e().loadConfig(),await e().reloadBoard();try{const c=await Ly();t({recentProjects:c})}catch(c){console.warn("[Store] Failed to load recent projects:",c)}e().setupWatcher()},openRecentProject:async n=>{const a={dirHandle:e().dirHandle,tasksDirHandle:e().tasksDirHandle,projectName:e().projectName};if(!await vj(n.handle,!0)){let r=!1;try{await My(n.handle),r=!0}catch{r=!1}if(!r){e().toast(`"${n.name}" is no longer accessible. Removed from recent projects.`,"warning",8e3);try{await rce(n.id);const s=await Ly();t({recentProjects:s})}catch{}return}e().toast("Permission denied โ€” please grant access to the folder","error");return}try{const r=await My(n.handle),s=await g3(n.handle),o=n.handle.name;t({dirHandle:r,tasksDirHandle:s,projectName:o}),window.history.pushState({},"",`?p=${encodeURIComponent(o)}`);try{await E2({...n,lastOpened:Date.now()})}catch(c){console.warn("[Store] Failed to update recent project:",c)}await e().loadConfig(),await e().reloadBoard(),e().setupWatcher()}catch(r){t(a),e().toast(`Failed to open project: ${r.message}`,"error")}},openServerProject:async()=>{t({loading:!0});try{const n=$2();if(!n)throw new Error("No server root");await gj();const a=fce(n),i=await Fz();rl(i);const r=await Q4(),s=await Promise.all(r.map(async f=>{const{frontmatter:h,body:b}=await zz(f),k={...h,id:h.id||f,status:h.status||"Backlog"},{subtasks:w,bodyWithoutSubtasks:A}=Ds(b);return{id:f,frontmatter:k,body:A,subtasks:w}}));T2(s);const o=s.map(f=>({frontmatter:f.frontmatter,body:qd(f.body,f.subtasks)})),c=N2(o,i.board.columns),p=S2(o),d=c.reduce((f,h)=>f+h.tasks.length,0),m=new Map;if(d<=10)for(const f of s)m.set(f.frontmatter.id,{frontmatter:f.frontmatter,subtasks:f.subtasks,body:f.body});t({loading:!1,isOpen:!0,config:i,columns:c,archivedTasks:p,boardTitle:"Project Kanban",projectName:a,taskContents:m,searchMatches:new Map}),window.history.pushState({},"",`?p=${encodeURIComponent(a)}`),e().setupWatcher(),e().refreshAgentHook()}catch{t({loading:!1,isOpen:!1}),e().toast("Impossible de charger le projet. Relancez `kandown`.","error")}},tryAutoOpenServerProject:async()=>{if(!Kt())return;const n=$2();if(!n)return;await gj();const a=await Ly(),i=a.find(p=>p.kandownDir===n);if(!i){await e().openServerProject();return}if(!await vj(i.handle,!0)){await e().openServerProject();return}const s=await My(i.handle),o=await g3(i.handle),c=i.handle.name;t({dirHandle:s,tasksDirHandle:o,projectName:c,recentProjects:a,isOpen:!0}),window.history.pushState({},"",`?p=${encodeURIComponent(c)}`),await E2({...i,lastOpened:Date.now()}),await e().loadConfig(),await e().reloadBoard(),e().setupWatcher()},loadConfig:async()=>{const{dirHandle:n}=e();if(!(!n&&!Kt()))try{const a=await Woe(n);if(a.ok){t({config:a.config}),rl(a.config);return}if(a.reason==="corrupted"){if(a.rawContent&&n)try{const r=await(await n.getFileHandle("kandown.json.backup",{create:!0})).createWritable();try{await r.write(a.rawContent)}finally{await r.close()}}catch{}e().toast("kandown.json is corrupted โ€” using default settings. A backup was saved as kandown.json.backup.","warning",1e4)}t({config:Ci}),rl(Ci)}catch{t({config:Ci}),rl(Ci)}},updateConfig:async n=>{const{dirHandle:a,config:i}=e();if(!a&&!Kt())return;const r=n(i);t({config:r}),rl(r);try{await Koe(a,r)}catch(s){const o=s;o instanceof $o?e().toast("Disk is full โ€” settings were not saved.","error",8e3):e().toast("Failed to save config: "+o.message,"error")}},reloadBoard:async()=>{const{tasksDirHandle:n,config:a}=e();t({isReloading:!0,lastReloadError:null});try{if(Kt()){const i=await uce();T2(i);const r=i.map(d=>({frontmatter:d.frontmatter,body:qd(d.body,d.subtasks)})),s=N2(r,a.board.columns),o=S2(r);t({boardTitle:"Project Kanban",columns:s,archivedTasks:o});const c=s.reduce((d,m)=>d+m.tasks.length,0),p=new Map;if(c<=10)for(const d of i)p.set(d.frontmatter.id,{frontmatter:d.frontmatter,subtasks:d.subtasks,body:d.body});t({taskContents:p,searchMatches:new Map,failedTaskIds:[],isReloading:!1})}else if(n){const{tasks:i,failedIds:r}=await mce(n);T2(i);const s=i.map(m=>({frontmatter:m.frontmatter,body:qd(m.body,m.subtasks)})),o=N2(s,a.board.columns),c=S2(s);t({boardTitle:"Project Kanban",columns:o,archivedTasks:c});const p=o.reduce((m,f)=>m+f.tasks.length,0),d=new Map;if(p<=10)for(const m of i)d.set(m.frontmatter.id,{frontmatter:m.frontmatter,subtasks:m.subtasks,body:m.body});if(r.length>0){const m=r.length===1?`Task ${r[0]} could not be loaded`:`${r.length} tasks could not be loaded`;e().toast(m,"warning",8e3)}t({taskContents:d,searchMatches:new Map,failedTaskIds:r,isReloading:!1})}else t({isReloading:!1})}catch(i){const r=i.message||String(i);t({isReloading:!1,lastReloadError:`Failed to reload board: ${r}`}),e().toast(`Board reload failed โ€” showing last loaded state (${r})`,"warning",8e3)}},moveTask:async(n,a,i,r)=>{const{columns:s,config:o,taskContents:c,searchMatches:p}=e(),d=Kt();if(!d&&!e().tasksDirHandle)return;const m=s.find(E=>E.name===a),f=s.find(E=>E.name===i);if(!m||!f)return;const h=m.tasks.findIndex(E=>E.id===n);if(h===-1)return;const b=m.tasks[h];if(!b)return;if(sce(i,o)){const E=new Map,P=qz(o).toLowerCase();for(const j of s)for(const z of j.tasks){const H=z.frontmatter&&(z.frontmatter.archived===!0||z.frontmatter.archived==="true");E.set(z.id,{exists:!0,resolved:H||z.id===n||j.name.toLowerCase()===P})}const M=[];for(const j of b.dependsOn){if(typeof j!="string"||!j.trim()||j===n)continue;const z=E.get(j);(!z||!z.resolved)&&M.push(j)}if(M.length>0){const j=M.length===1?M[0]:`${M.slice(0,-1).join(", ")} and ${M[M.length-1]}`;e().toast(`Cannot move ${n} to ${i}: blocked by ${j}`,"error");return}}const k=s.map(E=>({...E,tasks:[...E.tasks]})),w=k.find(E=>E.name===a),A=k.find(E=>E.name===i),[S]=w.tasks.splice(h,1);/done|termin|closed|complet/i.test(i)?S.checked=!0:S.checked=!1,r!==void 0?A.tasks.splice(r,0,S):A.tasks.push(S),t({columns:k});try{const{tasksDirHandle:E}=e();if(!E&&!d)return;const P=a===i?k.filter(j=>j.name===i):k.filter(j=>j.name===a||j.name===i),{failedIds:M}=await Tf(()=>wj(E??null,P,o.board.columns),{maxAttempts:3});if(M.length>0){const j=M.length===1?`Could not save move for ${M[0]}`:`${M.length} tasks could not be moved`;e().toast(j,"warning",8e3),await e().reloadBoard()}}catch(E){const P=E;P instanceof $o?e().toast("Disk is full โ€” move was not saved. Free up space and try again.","error",8e3):e().toast("Failed to save: "+P.message,"error"),t({columns:s,taskContents:c,searchMatches:p})}},reorderInColumn:async(n,a,i)=>{const{columns:r,tasksDirHandle:s,config:o,taskContents:c,searchMatches:p}=e();if(!s&&!Kt())return;const d=r.map(h=>({...h,tasks:[...h.tasks]})),m=d.find(h=>h.name===n);if(!m)return;const[f]=m.tasks.splice(a,1);m.tasks.splice(i,0,f),t({columns:d});try{const{tasksDirHandle:h}=e(),b=Kt();if(!h&&!b)return;const{failedIds:k}=await Tf(()=>wj(h??null,[m],o.board.columns),{maxAttempts:3});k.length>0&&(e().toast(`Could not save reorder for ${k.length} task(s)`,"warning",8e3),await e().reloadBoard())}catch(h){const b=h;b instanceof $o?e().toast("Disk is full โ€” reorder was not saved.","error",8e3):e().toast("Failed to save: "+b.message,"error"),t({columns:r,taskContents:c,searchMatches:p})}},addColumn:async n=>{const a=n.trim();if(!a)return;const{config:i}=e();i.board.columns.some(r=>r.toLowerCase()===a.toLowerCase())||(await e().updateConfig(r=>({...r,board:{...r.board,columns:[...r.board.columns,a]}})),await e().reloadBoard())},renameColumn:async(n,a)=>{const i=a.trim(),{columns:r,tasksDirHandle:s,config:o}=e();if(!s||!i||i.toLowerCase()===n.toLowerCase())return;if(r.some(d=>d.name.toLowerCase()===i.toLowerCase())){e().toast("Column already exists","error");return}const c=r,p=r.map(d=>d.name===n?{...d,name:i}:d);t({columns:p});try{const d=c.find(m=>m.name===n);if(d){const f=(await Promise.allSettled(d.tasks.map(async(h,b)=>{const{frontmatter:k,body:w}=await Es(s,h.id);await au(s,h.id,{...k,id:h.id,status:i,order:b},w)}))).filter(h=>h.status==="rejected").length;f>0&&e().toast(`${f} task(s) could not be renamed`,"warning",8e3)}await e().updateConfig(m=>{const f={...m.board.columnColors??{}},h=f[n.toLowerCase()];h&&(f[i.toLowerCase()]=h,delete f[n.toLowerCase()]);const b=m.board.columns.some(k=>k.toLowerCase()===n.toLowerCase())?m.board.columns:[...m.board.columns,n];return{...m,board:{...m.board,columns:b.map(k=>k.toLowerCase()===n.toLowerCase()?i:k),columnColors:f}}}),await e().reloadBoard()}catch(d){e().toast("Failed to rename column: "+d.message,"error"),t({columns:c})}},deleteColumn:async n=>{const{columns:a,tasksDirHandle:i}=e();if(!i&&!Kt())return;const r=a.find(o=>o.name===n);if(!r)return;const s=a;t({columns:a.filter(o=>o.name!==n)});try{const c=(await Promise.allSettled(r.tasks.map(p=>bj(i,p.id)))).filter(p=>p.status==="rejected").length;c>0&&e().toast(`${c} task(s) could not be deleted`,"warning",8e3),await e().updateConfig(p=>{const d={...p.board.columnColors??{}};return delete d[n.toLowerCase()],{...p,board:{...p.board,columns:p.board.columns.filter(m=>m.toLowerCase()!==n.toLowerCase()),columnColors:d}}}),await e().reloadBoard(),e().toast("Column deleted")}catch(o){e().toast("Failed to delete column: "+o.message,"error"),t({columns:s})}},createTask:async n=>{const{columns:a,tasksDirHandle:i,config:r,taskContents:s,searchMatches:o}=e();if(!i&&!Kt()||!a.length)return null;const c=n||r.board.columns[0]||a[0].name,p=dce(a),d=a.find(w=>w.name===c)?.tasks.length??0,m={id:p,title:"",checked:!1,dependsOn:[],tags:[],assignee:null,priority:r.fields.priority?r.board.defaultPriority:null,ownerType:r.fields.ownerType?r.board.defaultOwnerType:"",progress:null,frontmatter:{}},f=a.map(w=>w.name===c?{...w,tasks:[...w.tasks,m]}:w),h=new Map(s),b={id:p,title:"",status:c,order:d,priority:r.fields.priority?r.board.defaultPriority:"",tags:[],assignee:"",created:new Date().toISOString().slice(0,10),ownerType:r.fields.ownerType?r.board.defaultOwnerType:"",tools:""},k="";h.set(p,{frontmatter:b,subtasks:[],body:k}),t({columns:f,taskContents:h});try{const w=i||null;return await Tf(()=>au(w,p,b,k),{maxAttempts:3}),e().toast(`Created ${p.replace(/^t/,"")}`),await e().openDrawer(p),p}catch(w){const A=w;return A instanceof $o?e().toast("Disk is full โ€” task was not created.","error",8e3):e().toast("Failed to create: "+A.message,"error"),t({columns:a,taskContents:s,searchMatches:o}),null}},deleteTask:async n=>{const{columns:a,tasksDirHandle:i,taskContents:r,searchMatches:s}=e();if(!i&&!Kt())return;const o=a.map(d=>({...d,tasks:d.tasks.filter(m=>m.id!==n)}));t({columns:o});const c=new Map(r);c.delete(n);const p=new Map(s);p.delete(n),t({taskContents:c,searchMatches:p});try{await bj(i||null,n),e().toast("Deleted")}catch(d){const m=d;e().toast("Failed to delete: "+m.message,"error"),t({columns:a,taskContents:r,searchMatches:s})}},archiveTask:async n=>{const{tasksDirHandle:a}=e();if(!(!a&&!Kt()))try{const{frontmatter:i,body:r}=await Es(a||null,n);await tce(a||null,n,{...i,archived:!0},r),e().drawerTaskId===n&&e().closeDrawer(),await e().reloadBoard(),e().toast("Archived")}catch(i){const r=i;r instanceof $o?e().toast("Disk is full โ€” task was not archived.","error",8e3):e().toast("Failed to archive: "+r.message,"error")}},unarchiveTask:async n=>{const{tasksDirHandle:a}=e();if(!(!a&&!Kt()))try{const{frontmatter:i,body:r}=await Es(a||null,n),s={...i};delete s.archived,await nce(a||null,n,s,r),await e().reloadBoard(),e().toast("Restored")}catch(i){const r=i;r instanceof $o?e().toast("Disk is full โ€” task was not restored.","error",8e3):e().toast("Failed to restore: "+r.message,"error")}},setShowArchives:n=>t({showArchives:n}),setShowMetadata:n=>t({showMetadata:n}),openDrawer:async n=>{const{tasksDirHandle:a}=e();if(!(!a&&!Kt()))try{const{frontmatter:i,body:r}=await Es(a,n),{subtasks:s,bodyWithoutSubtasks:o}=Ds(r),c={frontmatter:i,subtasks:s,body:o,savedAt:Date.now()},p=e().drawerRecoveryData.get(n),d=p?{frontmatter:p.frontmatter,subtasks:p.subtasks,body:p.body}:{frontmatter:i,subtasks:s,body:o},m=new Map(e().drawerRecoveryData);m.delete(n),t({drawerTaskId:n,drawerData:d,drawerBaseVersion:c,conflictState:null,showConflictModal:!1,hasUnsavedDrawerEdits:!!p,lastSaveError:null,drawerRecoveryData:m}),p&&e().toast("Restored your unsaved edits for this task","info")}catch(i){e().toast("Failed to open: "+i.message,"error")}},closeDrawer:()=>t({drawerTaskId:null,drawerData:null,drawerBaseVersion:null,conflictState:null,showConflictModal:!1,hasUnsavedDrawerEdits:!1,lastSaveError:null}),markDrawerDirty:()=>t({hasUnsavedDrawerEdits:!0}),forceCloseDrawer:()=>{const{drawerTaskId:n,drawerData:a}=e();if(n&&a){const i=new Map(e().drawerRecoveryData);i.set(n,{frontmatter:a.frontmatter,subtasks:a.subtasks,body:a.body}),t({drawerRecoveryData:i})}e().closeDrawer()},updateDrawerData:n=>{const{drawerData:a}=e();a&&t({drawerData:n(a)})},saveDrawer:async()=>{const{drawerTaskId:n,drawerData:a,tasksDirHandle:i,taskContents:r}=e();if(!n||!a)return;const s=qd(a.body,a.subtasks),o={...a.frontmatter,id:n};try{await Tf(()=>au(i||null,n,o,s),{maxAttempts:3}),e().toast("Saved");const c=new Map(e().drawerRecoveryData);c.delete(n),t({drawerTaskId:null,drawerData:null,hasUnsavedDrawerEdits:!1,lastSaveError:null,drawerRecoveryData:c});const p=new Map(r);p.set(n,{frontmatter:o,subtasks:a.subtasks,body:a.body}),t({taskContents:p}),await e().reloadBoard()}catch(c){const p=c,d=p instanceof $o?"Disk is full โ€” your edits are kept. Free up space and retry.":"Failed to save: "+p.message;e().toast(d,"error",8e3),t({lastSaveError:d})}},saveDrawerMetadata:async()=>{const{drawerTaskId:n,drawerData:a,tasksDirHandle:i,taskContents:r}=e();if(!(!n||!a))try{const s=qd(a.body,a.subtasks),o={...a.frontmatter,id:n};await Tf(()=>au(i||null,n,o,s),{maxAttempts:3});const c=new Map(r);c.set(n,{frontmatter:o,subtasks:a.subtasks,body:a.body}),t({taskContents:c,hasUnsavedDrawerEdits:!1,lastSaveError:null}),await e().reloadBoard()}catch(s){const o=s,c=o instanceof $o?"Disk is full โ€” your edits are kept.":"Failed to save: "+o.message;t({hasUnsavedDrawerEdits:!0,lastSaveError:c})}},setViewMode:n=>{localStorage.setItem("kandown:view",n),t({viewMode:n})},setDensity:n=>{localStorage.setItem("kandown:density",n),t({density:n})},setFilter:(n,a)=>{if(t(i=>({filters:{...i.filters,[n]:a}})),n==="search"){const{columns:i,tasksDirHandle:r,taskContents:s}=e(),o=a,c=i.flatMap(p=>p.tasks.map(d=>d.id));if(r){const p=c.filter(d=>!s.has(d));p.length>0?e().loadTaskContents(p).then(()=>{e().computeSearchMatches(o)}):e().computeSearchMatches(o)}}},clearFilters:()=>t({filters:{search:"",priority:null,tag:null,assignee:null,ownerType:null},searchMatches:new Map}),setCommandOpen:n=>t({commandOpen:n}),setCheatsheetOpen:n=>t({cheatsheetOpen:n}),refreshAgentHook:async()=>{if(!Kt()){t({agentHook:null});return}const n=await Uoe();t({agentHook:n?.agentHook??null})},sendTaskToAgent:async n=>{const a=e().agentHook;if(!a){e().toast("Agent hook not configured","error");return}e().toast(`Sending to ${a.label}โ€ฆ`);const i=await Voe(n);if(i===null){e().toast("Could not reach the daemon","error");return}i.ok?e().toast(`Sent to ${a.label}`):e().toast(i.error||"Agent hook failed","error")},setCurrentPage:n=>t({currentPage:n}),loadTaskContents:async n=>{const{tasksDirHandle:a}=e();if(!a)return;const i=new Map(e().taskContents);await Promise.all(n.map(async r=>{if(!i.has(r))try{const{frontmatter:s,body:o}=await Es(a,r),{subtasks:c,bodyWithoutSubtasks:p}=Ds(o);i.set(r,{frontmatter:s,subtasks:c,body:p})}catch{}})),t({taskContents:i})},computeSearchMatches:n=>{if(!n.trim()){t({searchMatches:new Map});return}const{taskContents:a}=e(),i=new Map,r=n.toLowerCase();for(const[s,o]of a){const c=Rz(o,r);c.length>0&&i.set(s,c)}t({searchMatches:i})},toast:(n,a="success",i)=>{const r=++bce,s=i??(a==="error"||a==="warning"?6e3:2500);t(o=>({toasts:[...o.toasts,{id:r,message:n,type:a}]})),setTimeout(()=>{t(o=>({toasts:o.toasts.filter(c=>c.id!==r)}))},s)},dismissToast:n=>t(a=>({toasts:a.toasts.filter(i=>i.id!==n)})),resolveConflict:async n=>{const{conflictState:a,drawerData:i,tasksDirHandle:r,drawerTaskId:s,drawerBaseVersion:o}=e();if(!(!a||!r||!s))if(n==="reload"){const{frontmatter:c,body:p}=await Es(r,s),{subtasks:d,bodyWithoutSubtasks:m}=Ds(p);t({drawerData:{frontmatter:c,subtasks:d,body:m},drawerBaseVersion:{frontmatter:c,subtasks:d,body:m,savedAt:Date.now()},conflictState:null,showConflictModal:!1}),e().toast("Reloaded from disk")}else if(n==="overwrite"){if(i&&s&&o){const c=qd(i.body,i.subtasks),p={...i.frontmatter,id:s};try{await au(r,s,p,c),t({drawerBaseVersion:{...i,savedAt:Date.now()},conflictState:null,showConflictModal:!1}),e().toast("Overwritten remote changes")}catch(d){e().toast("Failed to overwrite: "+d.message,"error")}}}else t({conflictState:null,showConflictModal:!1})},setupWatcher:()=>{if(Kt()){M2&&clearInterval(M2),M2=setInterval(()=>{e().reloadBoard()},2e3);return}const{dirHandle:n,tasksDirHandle:a}=e();if(!n||!a)return;al.stop(),Mf.forEach(s=>clearTimeout(s)),Mf.clear(),al.start(n,a);const i=(s,o)=>{const c=Mf.get(s);c&&clearTimeout(c);const p=Math.max(2e3,e().config.notifications.editDebounceMs),d=setTimeout(()=>{Mf.delete(s);const m=e().config;m.notifications.taskEdits&&P2({title:"Task edited",body:`${o} changed on disk.`,config:m})},p);Mf.set(s,d)},r=async s=>{const{tasksDirHandle:o,config:c}=e();if(!o)return;let p,d;try{({frontmatter:p,body:d}=await Es(o,s))}catch(A){console.warn(`[Watcher] notifyTaskChange: failed to read ${s}:`,A);return}const{subtasks:m,bodyWithoutSubtasks:f}=Ds(d),h={id:s,frontmatter:{...p,id:p.id||s,status:p.status||"Backlog"},body:f,subtasks:m},b=h3(h),k=mu.get(s);if(!k){mu.set(s,b);return}c.notifications.statusChanges&&k.status!==b.status&&P2({title:"Task status changed",body:`${b.title}: ${k.status} โ†’ ${b.status}`,config:c});const w=gce(k.subtasks,b.subtasks);c.notifications.subtaskCompletions&&w>0&&P2({title:"Subtask completed",body:w===1?`${b.title}: 1 subtask completed.`:`${b.title}: ${w} subtasks completed.`,config:c}),hce(k,b)&&i(s,b.title),mu.set(s,b)};al.on("configChanged",()=>{try{e().loadConfig(),e().toast("Settings updated externally","info")}catch(s){console.error("[Watcher] configChanged handler error:",s)}}),al.on("taskChanged",async s=>{try{const{drawerTaskId:o,drawerBaseVersion:c,tasksDirHandle:p}=e();if(await r(s),o===s&&c&&p){let d,m;try{({frontmatter:d,body:m}=await Es(p,s))}catch(E){console.warn(`[Watcher] taskChanged: failed to re-read ${s}:`,E);return}const{subtasks:f,bodyWithoutSubtasks:h}=Ds(m),b=c,k=JSON.stringify(b.frontmatter)!==JSON.stringify(d),w=b.body!==h,A=JSON.stringify(b.subtasks)!==JSON.stringify(f);if(!k&&!w&&!A)return;let S="none";k&&(w||A)?S="full":k?S="metadata-only":(w||A)&&(S="body-only"),t({conflictState:{taskId:s,type:S,local:b,remote:{frontmatter:d,body:h,subtasks:f}},showConflictModal:S==="full"})}else await e().reloadBoard()}catch(o){console.error(`[Watcher] taskChanged handler error for ${s}:`,o)}}),al.on("newTaskDetected",async s=>{try{const{tasksDirHandle:o}=e();if(o){let c,p;try{({frontmatter:c,body:p}=await Es(o,s))}catch(f){console.warn(`[Watcher] newTaskDetected: failed to read ${s}:`,f),e().toast(`New task ${s} detected but could not be loaded`,"warning");return}const{subtasks:d,bodyWithoutSubtasks:m}=Ds(p);mu.set(s,h3({id:s,frontmatter:{...c,id:c.id||s,status:c.status||"Backlog"},body:m,subtasks:d}))}await e().reloadBoard()}catch(o){console.error(`[Watcher] newTaskDetected handler error for ${s}:`,o)}}),al.on("watcherError",s=>{t({watcherError:s}),e().toast(s,"warning",1e4)})},restartWatcher:()=>{const{dirHandle:n,tasksDirHandle:a}=e();!n||!a||(t({watcherError:null}),e().setupWatcher(),e().toast("File watcher restarted"))}}));Ly().then(t=>{ue.setState({recentProjects:t})}).catch(t=>{console.warn("[Store] Failed to hydrate recent projects:",t)});rl(Ci);window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{rl(ue.getState().config)});const vce=Object.freeze(Object.defineProperty({__proto__:null,useStore:ue},Symbol.toStringTag,{value:"Module"}));function yce(){const t=ue(o=>o.config),e=ue(o=>o.updateConfig),[n,a]=$.useState(!1);if($.useEffect(()=>{a(!0)},[]),!n)return null;const i=t.ui.theme==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":t.ui.theme,r=i==="dark",s=()=>{const o=r?"light":"dark";e(c=>({...c,ui:{...c.ui,theme:o}}))};return _.jsx(lt.button,{onClick:s,className:m3("relative inline-flex items-center rounded-full transition-all duration-300","border border-black/[0.08] dark:border-white/[0.12]","bg-black/[0.05] dark:bg-white/[0.08]","h-8 w-[52px] px-1"),title:r?"Switch to light mode":"Switch to dark mode",children:_.jsx(lt.div,{className:m3("inline-flex items-center justify-center rounded-full shadow-md",r?"bg-[#1e293b] border border-white/[0.15]":"bg-black border border-black/20","h-[26px] w-[26px]"),animate:{x:r?22:0},transition:{type:"spring",stiffness:500,damping:30},children:_.jsx(lt.div,{initial:{rotate:-90,scale:.5,opacity:0},animate:{rotate:0,scale:1,opacity:1},transition:{duration:.25,ease:"easeOut"},children:r?_.jsx(Zn.Moon,{size:13,className:"text-sky-300"}):_.jsx(Zn.Sun,{size:13,className:"text-amber-400"})},i)})})}function _ce(t){const e=xse(t,{stiffness:180,damping:22,mass:.8});return $.useEffect(()=>{e.set(t)},[t,e]),bz(e,a=>Math.round(a).toString())}const b3="0.15.1",kce=({className:t})=>_.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 150 150",className:t,fill:"currentColor",children:[_.jsx("path",{d:"m57.6 64.6 0.1-0.1v-31.3c-0.1-3.5-2.7-5.6-5.7-5.6h-16.3v59.6c0.2-1.3 0.9-2.6 1.7-3.2l20.2-19.4z"}),_.jsx("path",{d:"m87.6 43.8c-3.4 0.1-7.1 1.3-9.9 3.7l-38.5 38.7c-2.1 2.1-3.4 4.8-3.5 7.5v26.7l77.5-76.4v-0.2h-25.6z"}),_.jsx("path",{d:"m108.1 96.4-6 5-22.4-20.8-14.6 14.2 21.1 21.4-5 4.1c-0.6 0.5-0.3 0.9 0.2 0.9h27.6c0.4 0 0.7-0.4 0.7-0.6v-24.8c0-0.7-1.1-0.3-1.6 0.3v0.3z"})]});function wce(){const{t}=Cn(),e=ue(I=>I.dirHandle),n=ue(I=>I.isOpen),a=ue(I=>I.projectName),i=ue(I=>I.archivedTasks.length),r=ue(I=>I.showArchives),s=ue(I=>I.setShowArchives),o=ue(I=>I.columns),c=ue(I=>I.openFolder),p=ue(I=>I.reloadBoard),d=ue(I=>I.createTask),m=ue(I=>I.setCommandOpen),f=ue(I=>I.viewMode),h=ue(I=>I.setViewMode),b=ue(I=>I.density),k=ue(I=>I.setDensity),w=ue(I=>I.setCurrentPage),A=ue(I=>I.recentProjects),S=ue(I=>I.openRecentProject),E=ue(I=>I.filters),P=ue(I=>I.setFilter),M=ue(I=>I.clearFilters),j=ue(I=>I.config.fields),z=ue(I=>I.lastReloadError),H=ue(I=>I.watcherError),O=ue(I=>I.restartWatcher),[L,W]=$.useState(!1),[X,te]=$.useState(!0),Z=$.useRef(null),Q=$.useRef(null),q=o.reduce((I,J)=>I+J.tasks.length,0),V=_ce(q),B=[];j.priority&&E.priority&&B.push({type:"priority",label:E.priority,value:E.priority}),j.tags&&E.tag&&B.push({type:"tag",label:"#"+E.tag,value:E.tag}),j.assignee&&E.assignee&&B.push({type:"assignee",label:"@"+E.assignee,value:E.assignee});const ne=[{label:t("filterBar.ownerAll"),value:""},{label:t("filterBar.ownerHuman"),value:"human"},{label:t("filterBar.ownerAI"),value:"ai"}],D=B.length>0||E.search||j.ownerType&&E.ownerType;return $.useEffect(()=>{if(!L)return;const I=J=>{Z.current&&!Z.current.contains(J.target)&&W(!1)};return document.addEventListener("mousedown",I),()=>document.removeEventListener("mousedown",I)},[L]),$.useEffect(()=>{const I=setTimeout(()=>te(!1),5e3);return()=>clearTimeout(I)},[]),$.useEffect(()=>{document.title=a?`${a} ยท Kandown`:"Kandown"},[a]),_.jsxs(_.Fragment,{children:[(z||H)&&_.jsxs("div",{className:"flex items-center gap-2 px-5 py-1.5 bg-amber-500/10 border-b border-amber-500/30 text-[12px] text-amber-700 dark:text-amber-300",children:[_.jsx("span",{className:"flex-1 truncate",children:H?`โš  ${H}`:`โš  ${z}`}),H&&_.jsx("button",{type:"button",onClick:O,className:"px-2 py-0.5 rounded bg-amber-500/20 hover:bg-amber-500/30 text-[11.5px] font-medium",children:"Restart watcher"}),_.jsx("button",{type:"button",onClick:p,className:"px-2 py-0.5 rounded bg-amber-500/20 hover:bg-amber-500/30 text-[11.5px] font-medium",children:"Reload"})]}),_.jsxs("header",{className:"flex items-center justify-between px-5 h-[64px] border-b border-border bg-card/80 backdrop-blur-xl relative z-10",children:[_.jsxs("div",{className:"flex items-center gap-4 min-w-0",children:[_.jsx("div",{className:"flex items-center gap-2.5 flex-shrink-0",children:_.jsxs("button",{onClick:()=>window.history.pushState({},"",window.location.pathname),className:"flex items-center gap-2 cursor-pointer",children:[_.jsx(kce,{className:"w-[34px] h-[34px] dark:text-white text-black"}),_.jsx(Sr,{mode:"wait",initial:!1,children:X?_.jsxs(lt.span,{className:"flex items-center gap-2",initial:{opacity:0},animate:{opacity:1},exit:{opacity:0},transition:{duration:.45},children:[_.jsx("span",{className:"text-[15px] font-semibold tracking-tight text-fg",children:"kandown"}),_.jsxs("span",{className:"inline-flex items-center h-5 px-1.5 text-[10.5px] font-semibold text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-500/15 rounded-md",children:["v",b3]})]},"boot"):a?_.jsx(lt.span,{className:"text-[15px] font-semibold tracking-tight text-fg truncate max-w-[240px]",initial:{opacity:0},animate:{opacity:1},exit:{opacity:0},transition:{duration:.45},children:a},"project"):null})]})}),e&&_.jsxs(_.Fragment,{children:[_.jsx("div",{className:"w-px h-[20px] bg-black/[0.08] dark:bg-white/[0.08] flex-shrink-0"}),_.jsxs("div",{className:"flex items-center gap-2 px-3 h-9 bg-secondary/60 border border-border rounded-xl min-w-[200px] max-w-[280px] focus-within:border-border-focus focus-within:bg-secondary transition-all",children:[_.jsx(Zn.Search,{size:14,className:"text-fg-muted/60 flex-shrink-0"}),_.jsx("input",{ref:Q,type:"text",placeholder:t("filterBar.searchPlaceholder"),value:E.search,onChange:I=>P("search",I.target.value),className:"bg-transparent border-none outline-none text-fg text-[13px] w-full placeholder:text-fg-muted/60"}),E.search?_.jsx("button",{onClick:()=>P("search",""),className:"text-fg-muted/60 hover:text-fg flex-shrink-0",children:_.jsx(Zn.X,{size:14})}):_.jsx("kbd",{className:"inline-flex items-center h-5 px-1.5 text-[10px] font-medium text-fg-muted/50 bg-black/[0.04] dark:bg-white/[0.08] rounded border border-black/[0.06] dark:border-white/[0.1]",children:"โŒ˜K"})]}),_.jsxs("div",{className:"flex items-center gap-1.5 flex-wrap overflow-hidden",children:[_.jsx(Sr,{children:B.map(I=>_.jsxs(lt.button,{initial:{opacity:0,scale:.9},animate:{opacity:1,scale:1},exit:{opacity:0,scale:.9},transition:{duration:.15},onClick:()=>P(I.type,null),className:"inline-flex items-center gap-1 h-6 px-2.5 text-[12px] text-fg bg-black/[0.05] dark:bg-white/[0.1] border border-black/[0.08] dark:border-white/[0.12] rounded-lg hover:bg-black/[0.08] dark:hover:bg-white/[0.15] transition-colors",children:[I.label,_.jsx(Zn.X,{size:10,className:"text-fg-muted/60"})]},I.type+I.value))}),j.ownerType&&_.jsx("div",{className:"flex items-center h-6 border border-black/[0.06] dark:border-white/[0.1] rounded-lg overflow-hidden",children:ne.map(I=>_.jsx("button",{onClick:()=>P("ownerType",I.value),className:`h-full px-2.5 text-[12px] transition-colors ${E.ownerType===I.value?"bg-black/[0.06] dark:bg-white/[0.12] text-fg":"text-fg-muted/70 hover:text-fg"}`,children:I.label},I.value))}),D&&_.jsx("button",{onClick:M,className:"text-[12px] text-fg-muted/60 hover:text-fg transition-colors",children:t("filterBar.clearAll")})]})]}),e&&_.jsxs(_.Fragment,{children:[_.jsx("div",{className:"w-px h-[20px] bg-black/[0.08] dark:bg-white/[0.08] flex-shrink-0"}),_.jsxs("div",{className:"relative flex-shrink-0",ref:Z,children:[_.jsxs("button",{onClick:()=>W(I=>!I),className:"flex items-center gap-1.5 px-2.5 py-1.5 text-[13px] text-fg-muted hover:text-fg hover:bg-black/[0.04] dark:hover:bg-white/[0.06] rounded-lg transition-colors border border-transparent hover:border-black/[0.06] dark:hover:border-white/[0.1]",children:[_.jsx(Zn.Folder,{size:13,className:"text-fg-muted/70"}),_.jsxs("span",{className:"font-medium",children:[".",e.name]}),_.jsx(Zn.ChevronDown,{size:11,className:"opacity-50"})]}),_.jsx(Sr,{children:L&&_.jsx(lt.div,{initial:{opacity:0,y:-4,scale:.98},animate:{opacity:1,y:0,scale:1},exit:{opacity:0,y:-4,scale:.98},transition:{duration:.12},className:"absolute top-full left-0 mt-2 min-w-[240px] glass rounded-xl shadow-[0_16px_48px_rgba(0,0,0,0.5)] overflow-hidden z-50",children:_.jsxs("div",{className:"py-1.5",children:[A.length>0&&_.jsxs(_.Fragment,{children:[_.jsx("div",{className:"px-3 py-1.5 text-[11px] font-semibold uppercase tracking-wider text-fg-muted/60",children:t("header.recentProjects")}),A.map(I=>_.jsxs("button",{onClick:()=>{W(!1),S(I)},className:"w-full flex items-center gap-2 px-3 py-2 text-[13.5px] text-left hover:bg-black/[0.04] dark:hover:bg-white/[0.06] transition-colors",children:[_.jsx(Zn.Folder,{size:12,className:"text-fg-muted/60"}),_.jsx("span",{className:"truncate",children:I.name}),I.id===e.name&&_.jsx(Zn.Check,{size:12,className:"ml-auto text-emerald-500"})]},I.id)),_.jsx("div",{className:"h-px bg-black/[0.06] dark:bg-white/[0.08] my-1.5 mx-2"})]}),_.jsxs("button",{onClick:()=>{W(!1),c()},className:"w-full flex items-center gap-2 px-3 py-2 text-[13.5px] text-left hover:bg-black/[0.04] dark:hover:bg-white/[0.06] transition-colors",children:[_.jsx(Zn.Plus,{size:12,className:"text-fg-muted/60"}),_.jsx("span",{children:t("header.openFolder...")})]})]})})})]})]})]}),_.jsx("div",{className:"flex items-center gap-2 flex-shrink-0",children:n||e?_.jsxs(_.Fragment,{children:[_.jsxs("div",{className:"flex items-center gap-2 mr-2 text-[12.5px] text-fg-muted/70",children:[_.jsx("span",{className:"inline-block w-1.5 h-1.5 rounded-full bg-emerald-500"}),_.jsx(lt.span,{className:"tabular-nums font-medium",children:V}),_.jsx("span",{children:t("header.tasks")})]}),_.jsxs("div",{className:"flex items-center bg-black/[0.04] dark:bg-white/[0.06] border border-black/[0.06] dark:border-white/[0.1] rounded-xl p-0.5 h-10",children:[_.jsx("button",{onClick:()=>h("board"),className:`w-9 h-9 inline-flex items-center justify-center rounded-lg transition-all ${f==="board"?"bg-card text-fg shadow-sm":"text-fg-muted/70 hover:text-fg"}`,title:t("common.board"),children:_.jsx(Zn.LayoutBoard,{size:18})}),_.jsx("button",{onClick:()=>h("list"),className:`w-9 h-9 inline-flex items-center justify-center rounded-lg transition-all ${f==="list"?"bg-card text-fg shadow-sm":"text-fg-muted/70 hover:text-fg"}`,title:t("common.list"),children:_.jsx(Zn.LayoutList,{size:18})})]}),_.jsx(na,{variant:"icon",icon:"Archive",onClick:()=>s(!r),title:r?t("header.backToBoard"):`${t("header.archives")} (${i})`,className:r?"text-accent":""}),_.jsx(na,{variant:"icon",icon:"Density",onClick:()=>k(b==="compact"?"comfortable":"compact"),title:`Density: ${b}`}),_.jsx(na,{variant:"icon",icon:"Settings",onClick:()=>w("settings"),title:t("common.settings")}),_.jsx(yce,{}),_.jsx("div",{className:"w-px h-5 bg-black/[0.08] dark:bg-white/[0.08] mx-1"}),_.jsx(na,{variant:"secondary",icon:"Search",label:t("common.search"),shortcut:"โŒ˜K",onClick:()=>m(!0),title:"Command palette (โŒ˜K)"}),_.jsx(na,{variant:"icon",icon:"Refresh",onClick:p,title:`${t("common.reload")} (R)`}),_.jsx(na,{variant:"primary",icon:"Plus",label:t("common.newTask"),shortcut:"N",onClick:()=>d()})]}):_.jsx(na,{variant:"primary",label:t("common.openFolder"),onClick:c})})]})]})}/**
116
+ `}}async function Hz(t,e){try{return await(await(await(await t.getDirectoryHandle("archive",{create:!1})).getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}}async function Yoe(t,e){try{return await(await t.getDirectoryHandle("archive",{create:!1})).getFileHandle(`${e}.md`),!0}catch{return!1}}async function Bz(t,e){try{return await t.getDirectoryHandle("archive",{create:e})}catch{return null}}async function Xoe(t){if(Kt())return Q4();const e=new Set;for await(const n of t.values())n.kind==="file"&&n.name.endsWith(".md")&&e.add(n.name.slice(0,-3));try{const n=await t.getDirectoryHandle("archive",{create:!1});for await(const a of n.values())a.kind==="file"&&a.name.endsWith(".md")&&e.add(a.name.slice(0,-3))}catch{}return[...e].sort((n,a)=>n.localeCompare(a,void 0,{numeric:!0}))}async function Es(t,e){if(Kt())try{const i=await J4(e);return Ig(i)}catch{return hj(e)}const a=await(async i=>{try{return await(await(await i.getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}})(t)??await Hz(t,e);return a!==null?Ig(a):hj(e)}async function Qoe(t,e){if(Kt())try{const a=await J4(e);return{ok:!0,task:Ig(a)}}catch(a){return a.name==="NotFoundError"?{ok:!1,reason:"not-found"}:{ok:!1,reason:"unknown",error:a}}let n=null;try{n=await(await(await t.getFileHandle(`${e}.md`)).getFile()).text()}catch{}if(n===null)try{n=await Hz(t,e)}catch(a){return{ok:!1,reason:"unknown",error:a}}if(n===null)return{ok:!1,reason:"not-found"};if(n.trim()==="")return{ok:!1,reason:"corrupted",error:new Error(`Task file ${e}.md is empty`)};try{return{ok:!0,task:Ig(n)}}catch(a){return{ok:!1,reason:"corrupted",error:a}}}async function au(t,e,n,a){const i=Y4(n,a);if(Kt())return Boe(e,i);try{const o=await(await(await Yoe(t,e)?await Bz(t,!0):t).getFileHandle(`${e}.md`,{create:!0})).createWritable();try{await o.write(i)}finally{await o.close()}}catch(r){throw ek(r,`${e}.md`)}}async function bj(t,e){if(Kt())return qoe(e);try{await t.removeEntry(`${e}.md`)}catch{}try{await(await t.getDirectoryHandle("archive",{create:!1})).removeEntry(`${e}.md`)}catch{}}async function Joe(t,e){await hs(`/api/tasks/${encodeURIComponent(t)}/archive`,{method:"POST",body:e,headers:{"Content-Type":"text/plain"}})}async function ece(t,e){await hs(`/api/tasks/${encodeURIComponent(t)}/unarchive`,{method:"POST",body:e,headers:{"Content-Type":"text/plain"}})}async function tce(t,e,n,a){const i=Y4(n,a);if(Kt())return Joe(e,i);try{const o=await(await(await Bz(t,!0)).getFileHandle(`${e}.md`,{create:!0})).createWritable();try{await o.write(i)}finally{await o.close()}try{await t.removeEntry(`${e}.md`)}catch{}}catch(r){throw ek(r,`archive/${e}.md`)}}async function nce(t,e,n,a){const i=Y4(n,a);if(Kt())return ece(e,i);try{const s=await(await t.getFileHandle(`${e}.md`,{create:!0})).createWritable();try{await s.write(i)}finally{await s.close()}try{await(await t.getDirectoryHandle("archive",{create:!1})).removeEntry(`${e}.md`)}catch{}}catch(r){throw ek(r,`${e}.md`)}}const ace="kanban-md",ice=1,ap="recentProjects";function eS(){return new Promise((t,e)=>{const n=indexedDB.open(ace,ice);n.onerror=()=>e(n.error),n.onsuccess=()=>t(n.result),n.onupgradeneeded=()=>{const a=n.result;a.objectStoreNames.contains(ap)||a.createObjectStore(ap,{keyPath:"id"})}})}async function E2(t){const e=await eS();return new Promise((n,a)=>{const i=e.transaction(ap,"readwrite");i.objectStore(ap).put(t),i.oncomplete=()=>n(),i.onerror=()=>a(i.error)})}async function Ly(){const t=await eS();return new Promise((e,n)=>{const i=t.transaction(ap,"readonly").objectStore(ap).getAll();i.onsuccess=()=>{const r=i.result||[];r.sort((s,o)=>o.lastOpened-s.lastOpened),e(r.slice(0,10))},i.onerror=()=>n(i.error)})}async function rce(t){const e=await eS();return new Promise((n,a)=>{const i=e.transaction(ap,"readwrite");i.objectStore(ap).delete(t),i.oncomplete=()=>n(),i.onerror=()=>a(i.error)})}async function vj(t,e=!0){const n={mode:e?"readwrite":"read"};try{return await t.queryPermission(n)==="granted"||await t.requestPermission(n)==="granted"}catch(a){return console.warn("[FS] verifyPermission: handle is no longer valid:",a),!1}}function qz(t=Ci){const e=t.board.columns;return e[e.length-1]??"Done"}function sce(t,e=Ci){return t===qz(e)||t.toLowerCase()==="archived"}class oce{dirHandle=null;tasksDirHandle=null;intervalId=null;configHash=null;taskHashes=new Map;knownTaskIds=new Set;listeners=new Map;debounceTimers=new Map;debounceDelay=150;consecutiveErrors=0;maxConsecutiveErrors=5;disabled=!1;start(e,n){this.dirHandle=e,this.tasksDirHandle=n,this.consecutiveErrors=0,this.disabled=!1,this.initHashes(),this.intervalId=setInterval(()=>void this.tick(),300)}stop(){this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null),this.configHash=null,this.taskHashes.clear(),this.knownTaskIds.clear(),this.listeners.clear(),this.debounceTimers.forEach(e=>clearTimeout(e)),this.debounceTimers.clear(),this.consecutiveErrors=0,this.disabled=!1}isDisabled(){return this.disabled}on(e,n){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(n),()=>{this.listeners.get(e)?.delete(n)}}getKnownTaskIds(){return Array.from(this.knownTaskIds)}async initHashes(){if(!(!this.dirHandle||!this.tasksDirHandle))try{const e=await yj(this.dirHandle);e!==null&&(this.configHash=await this.hash(e)),await this.syncTaskDir(!1)}catch(e){console.warn("[Watcher] initHashes error:",e)}}async tick(){if(!(!this.dirHandle||!this.tasksDirHandle)&&!this.disabled)try{const e=await yj(this.dirHandle);if(e!==null){const n=await this.hash(e);this.configHash!==null&&n!==this.configHash&&(this.configHash=n,this.debouncedEmit("configChanged"))}for(const n of[...this.knownTaskIds])try{const a=await _j(this.tasksDirHandle,n);if(a!==null){const i=await this.hash(a),r=this.taskHashes.get(n);r!==void 0&&i!==r&&(this.taskHashes.set(n,i),this.debouncedEmit("taskChanged",n))}}catch(a){console.warn(`[Watcher] Error reading task ${n}:`,a)}await this.syncTaskDir(!0),this.consecutiveErrors=0}catch(e){this.consecutiveErrors++,console.error(`[Watcher] Tick failed (${this.consecutiveErrors}/${this.maxConsecutiveErrors}):`,e),this.consecutiveErrors>=this.maxConsecutiveErrors&&this.disable(`File watcher stopped after ${this.maxConsecutiveErrors} consecutive errors: ${e.message}`)}}disable(e){this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null),this.disabled=!0,this.emit("watcherError",e)}debouncedEmit(e,...n){const a=e+JSON.stringify(n),i=this.debounceTimers.get(a);i&&clearTimeout(i);const r=setTimeout(()=>{this.debounceTimers.delete(a),this.emit(e,...n)},this.debounceDelay);this.debounceTimers.set(a,r)}async syncTaskDir(e){if(!this.tasksDirHandle)return;let n;try{n=this.tasksDirHandle.values()}catch(a){throw a}for await(const a of n){if(a.kind!=="file"||!a.name.endsWith(".md"))continue;const i=a.name.replace(".md","");if(!this.knownTaskIds.has(i))try{this.knownTaskIds.add(i);const r=await _j(this.tasksDirHandle,i);r!==null&&(this.taskHashes.set(i,await this.hash(r)),e&&this.debouncedEmit("newTaskDetected",i))}catch(r){console.warn(`[Watcher] syncTaskDir: failed to read ${i}:`,r)}}}async hash(e){const a=new TextEncoder().encode(e),i=await crypto.subtle.digest("SHA-256",a);return Array.from(new Uint8Array(i)).map(s=>s.toString(16).padStart(2,"0")).join("")}emit(e,...n){this.listeners.get(e)?.forEach(i=>{if(e==="configChanged"){i();return}if(e==="taskChanged"){const[s]=n;i(s);return}if(e==="newTaskDetected"){const[s]=n;i(s);return}const[r]=n;i(r)})}}async function yj(t){try{return await(await(await t.getFileHandle("kandown.json")).getFile()).text()}catch{return null}}async function _j(t,e){try{return await(await(await t.getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}}const al=new oce,cce={soft:[{frequency:520,durationMs:90,delayMs:0,type:"sine"},{frequency:720,durationMs:120,delayMs:95,type:"sine"}],chime:[{frequency:660,durationMs:120,delayMs:0,type:"triangle"},{frequency:880,durationMs:160,delayMs:125,type:"triangle"}],ping:[{frequency:920,durationMs:110,delayMs:0,type:"sine"}],pop:[{frequency:260,durationMs:55,delayMs:0,type:"square"},{frequency:520,durationMs:80,delayMs:60,type:"square"}]};let kj=null;function Uz(){return"Notification"in window?Notification.permission:"unsupported"}async function pce(){return"Notification"in window?await Notification.requestPermission():"unsupported"}function P2({title:t,body:e,config:n}){if(n.notifications.sound&&lce(n.notifications.soundId),!(!n.notifications.browser||Uz()!=="granted"))try{new Notification(t,{body:e})}catch{}}function lce(t){const e=cce[t],n=window.AudioContext??window.webkitAudioContext;if(!(!n||e.length===0))try{kj??=new n;const a=kj;a.state==="suspended"&&a.resume();const i=a.currentTime+.01;e.forEach(r=>{const s=a.createOscillator(),o=a.createGain(),c=i+r.delayMs/1e3,p=c+r.durationMs/1e3;s.type=r.type??"sine",s.frequency.setValueAtTime(r.frequency,c),o.gain.setValueAtTime(1e-4,c),o.gain.exponentialRampToValueAtTime(.08,c+.01),o.gain.exponentialRampToValueAtTime(1e-4,p),s.connect(o),o.connect(a.destination),s.start(c),s.stop(p+.02)})}catch{}}async function Tf(t,e={}){const{maxAttempts:n=3,baseDelayMs:a=500,retryableCheck:i=Foe}=e;let r;for(let s=1;s<=n;s++)try{return await t()}catch(o){if(r=o,!i(o))throw o;if(s<n){const c=a*Math.pow(2,s-1);await new Promise(p=>setTimeout(p,c))}}throw r}function dce(t){let e=-1;for(const n of t)for(const a of n.tasks){const i=a.id.match(/^t(\d+)$/);if(i){const r=parseInt(i[1],10);r>e&&(e=r)}}return"t"+(e+1)}async function uce(){const t=await Q4();return await Promise.all(t.map(async n=>{const{frontmatter:a,body:i}=await zz(n),r={...a,id:a.id||n,status:a.status||"Backlog"},{subtasks:s,bodyWithoutSubtasks:o}=Ds(i);return{id:n,frontmatter:r,body:o,subtasks:s}}))}async function mce(t){const e=await Xoe(t),n=await Promise.all(e.map(async r=>{const s=await Qoe(t,r);return{id:r,result:s}})),a=[],i=[];for(const{id:r,result:s}of n)if(s.ok){const o={...s.task.frontmatter,id:s.task.frontmatter.id||r,status:s.task.frontmatter.status||"Backlog"},{subtasks:c,bodyWithoutSubtasks:p}=Ds(s.task.body);a.push({id:r,frontmatter:o,body:p,subtasks:c})}else{if(s.reason==="not-found")continue;i.push(r)}return{tasks:a,failedIds:i}}async function wj(t,e,n){const a=[];for(const s of e){const o=s.name;s.tasks.forEach((c,p)=>{const d=(async()=>{const{frontmatter:m,body:f}=await Es(t,c.id);await au(t,c.id,{...m,id:c.id,status:o,order:p},f)})();a.push({id:c.id,promise:d})})}const i=await Promise.allSettled(a.map(s=>s.promise)),r=[];return i.forEach((s,o)=>{s.status==="rejected"&&r.push(a[o].id)}),{failedIds:r}}function rl(t){Roe(t.ui.theme,t.ui.skin,t.ui.font,t.ui.background)}function h3(t){return{title:t.frontmatter.title||t.id,status:t.frontmatter.status||"Backlog",body:t.body,subtasks:t.subtasks}}function T2(t){mu.clear(),t.forEach(e=>{mu.set(e.id,h3(e))})}function fce(t){const e=t.split(/[\\/]+/).filter(Boolean),n=e[e.length-1];return n===".kandown"?e[e.length-2]??"Project":n??"Project"}function gce(t,e){return e.reduce((n,a,i)=>{const r=t[i]?.done??!1;return n+(a.done&&!r?1:0)},0)}function hce(t,e){const n=t.subtasks.map(i=>({text:i.text,description:i.description??"",report:i.report??""})),a=e.subtasks.map(i=>({text:i.text,description:i.description??"",report:i.report??""}));return t.title!==e.title||t.body!==e.body||JSON.stringify(n)!==JSON.stringify(a)}let bce=0;const mu=new Map,Mf=new Map;let M2=null;const ue=$oe((t,e)=>({isOpen:!1,loading:!1,dirHandle:null,projectName:null,tasksDirHandle:null,boardTitle:"Project Kanban",columns:[],archivedTasks:[],showArchives:!1,showMetadata:!0,taskContents:new Map,searchMatches:new Map,viewMode:localStorage.getItem("kandown:view")||"board",density:localStorage.getItem("kandown:density")||"comfortable",filters:{search:"",priority:null,tag:null,assignee:null,ownerType:null},commandOpen:!1,cheatsheetOpen:!1,drawerTaskId:null,drawerData:null,currentPage:"board",config:Ci,recentProjects:[],toasts:[],agentHook:null,isReloading:!1,lastReloadError:null,failedTaskIds:[],watcherError:null,hasUnsavedDrawerEdits:!1,lastSaveError:null,drawerRecoveryData:new Map,drawerBaseVersion:null,conflictState:null,showConflictModal:!1,openFolder:async()=>{if(!Oz()){const c=navigator.userAgent||"this browser";e().toast(new Ooe(c).message,"error",8e3);return}let n;try{n=await Goe()}catch(c){c instanceof X4?e().toast("Permission denied โ€” please grant access to the project folder","error"):e().toast("Failed to open folder: "+c.message,"error");return}if(!n)return;const{projectHandle:a,kandownHandle:i,tasksHandle:r}=n,s=a.name;t({dirHandle:i,tasksDirHandle:r,projectName:s}),window.history.pushState({},"",`?p=${encodeURIComponent(s)}`);const o=Kt()?$2():null;try{await E2({id:a.name,name:a.name,handle:a,lastOpened:Date.now(),...o?{kandownDir:o}:{}})}catch(c){console.warn("[Store] Failed to save recent project:",c)}await e().loadConfig(),await e().reloadBoard();try{const c=await Ly();t({recentProjects:c})}catch(c){console.warn("[Store] Failed to load recent projects:",c)}e().setupWatcher()},openRecentProject:async n=>{const a={dirHandle:e().dirHandle,tasksDirHandle:e().tasksDirHandle,projectName:e().projectName};if(!await vj(n.handle,!0)){let r=!1;try{await My(n.handle),r=!0}catch{r=!1}if(!r){e().toast(`"${n.name}" is no longer accessible. Removed from recent projects.`,"warning",8e3);try{await rce(n.id);const s=await Ly();t({recentProjects:s})}catch{}return}e().toast("Permission denied โ€” please grant access to the folder","error");return}try{const r=await My(n.handle),s=await g3(n.handle),o=n.handle.name;t({dirHandle:r,tasksDirHandle:s,projectName:o}),window.history.pushState({},"",`?p=${encodeURIComponent(o)}`);try{await E2({...n,lastOpened:Date.now()})}catch(c){console.warn("[Store] Failed to update recent project:",c)}await e().loadConfig(),await e().reloadBoard(),e().setupWatcher()}catch(r){t(a),e().toast(`Failed to open project: ${r.message}`,"error")}},openServerProject:async()=>{t({loading:!0});try{const n=$2();if(!n)throw new Error("No server root");await gj();const a=fce(n),i=await Fz();rl(i);const r=await Q4(),s=await Promise.all(r.map(async f=>{const{frontmatter:h,body:b}=await zz(f),k={...h,id:h.id||f,status:h.status||"Backlog"},{subtasks:w,bodyWithoutSubtasks:A}=Ds(b);return{id:f,frontmatter:k,body:A,subtasks:w}}));T2(s);const o=s.map(f=>({frontmatter:f.frontmatter,body:qd(f.body,f.subtasks)})),c=N2(o,i.board.columns),p=S2(o),d=c.reduce((f,h)=>f+h.tasks.length,0),m=new Map;if(d<=10)for(const f of s)m.set(f.frontmatter.id,{frontmatter:f.frontmatter,subtasks:f.subtasks,body:f.body});t({loading:!1,isOpen:!0,config:i,columns:c,archivedTasks:p,boardTitle:"Project Kanban",projectName:a,taskContents:m,searchMatches:new Map}),window.history.pushState({},"",`?p=${encodeURIComponent(a)}`),e().setupWatcher(),e().refreshAgentHook()}catch{t({loading:!1,isOpen:!1}),e().toast("Impossible de charger le projet. Relancez `kandown`.","error")}},tryAutoOpenServerProject:async()=>{if(!Kt())return;const n=$2();if(!n)return;await gj();const a=await Ly(),i=a.find(p=>p.kandownDir===n);if(!i){await e().openServerProject();return}if(!await vj(i.handle,!0)){await e().openServerProject();return}const s=await My(i.handle),o=await g3(i.handle),c=i.handle.name;t({dirHandle:s,tasksDirHandle:o,projectName:c,recentProjects:a,isOpen:!0}),window.history.pushState({},"",`?p=${encodeURIComponent(c)}`),await E2({...i,lastOpened:Date.now()}),await e().loadConfig(),await e().reloadBoard(),e().setupWatcher()},loadConfig:async()=>{const{dirHandle:n}=e();if(!(!n&&!Kt()))try{const a=await Woe(n);if(a.ok){t({config:a.config}),rl(a.config);return}if(a.reason==="corrupted"){if(a.rawContent&&n)try{const r=await(await n.getFileHandle("kandown.json.backup",{create:!0})).createWritable();try{await r.write(a.rawContent)}finally{await r.close()}}catch{}e().toast("kandown.json is corrupted โ€” using default settings. A backup was saved as kandown.json.backup.","warning",1e4)}t({config:Ci}),rl(Ci)}catch{t({config:Ci}),rl(Ci)}},updateConfig:async n=>{const{dirHandle:a,config:i}=e();if(!a&&!Kt())return;const r=n(i);t({config:r}),rl(r);try{await Koe(a,r)}catch(s){const o=s;o instanceof $o?e().toast("Disk is full โ€” settings were not saved.","error",8e3):e().toast("Failed to save config: "+o.message,"error")}},reloadBoard:async()=>{const{tasksDirHandle:n,config:a}=e();t({isReloading:!0,lastReloadError:null});try{if(Kt()){const i=await uce();T2(i);const r=i.map(d=>({frontmatter:d.frontmatter,body:qd(d.body,d.subtasks)})),s=N2(r,a.board.columns),o=S2(r);t({boardTitle:"Project Kanban",columns:s,archivedTasks:o});const c=s.reduce((d,m)=>d+m.tasks.length,0),p=new Map;if(c<=10)for(const d of i)p.set(d.frontmatter.id,{frontmatter:d.frontmatter,subtasks:d.subtasks,body:d.body});t({taskContents:p,searchMatches:new Map,failedTaskIds:[],isReloading:!1})}else if(n){const{tasks:i,failedIds:r}=await mce(n);T2(i);const s=i.map(m=>({frontmatter:m.frontmatter,body:qd(m.body,m.subtasks)})),o=N2(s,a.board.columns),c=S2(s);t({boardTitle:"Project Kanban",columns:o,archivedTasks:c});const p=o.reduce((m,f)=>m+f.tasks.length,0),d=new Map;if(p<=10)for(const m of i)d.set(m.frontmatter.id,{frontmatter:m.frontmatter,subtasks:m.subtasks,body:m.body});if(r.length>0){const m=r.length===1?`Task ${r[0]} could not be loaded`:`${r.length} tasks could not be loaded`;e().toast(m,"warning",8e3)}t({taskContents:d,searchMatches:new Map,failedTaskIds:r,isReloading:!1})}else t({isReloading:!1})}catch(i){const r=i.message||String(i);t({isReloading:!1,lastReloadError:`Failed to reload board: ${r}`}),e().toast(`Board reload failed โ€” showing last loaded state (${r})`,"warning",8e3)}},moveTask:async(n,a,i,r)=>{const{columns:s,config:o,taskContents:c,searchMatches:p}=e(),d=Kt();if(!d&&!e().tasksDirHandle)return;const m=s.find(E=>E.name===a),f=s.find(E=>E.name===i);if(!m||!f)return;const h=m.tasks.findIndex(E=>E.id===n);if(h===-1)return;const b=m.tasks[h];if(!b)return;if(sce(i,o)){const E=new Map,P=qz(o).toLowerCase();for(const j of s)for(const z of j.tasks){const H=z.frontmatter&&(z.frontmatter.archived===!0||z.frontmatter.archived==="true");E.set(z.id,{exists:!0,resolved:H||z.id===n||j.name.toLowerCase()===P})}const M=[];for(const j of b.dependsOn){if(typeof j!="string"||!j.trim()||j===n)continue;const z=E.get(j);(!z||!z.resolved)&&M.push(j)}if(M.length>0){const j=M.length===1?M[0]:`${M.slice(0,-1).join(", ")} and ${M[M.length-1]}`;e().toast(`Cannot move ${n} to ${i}: blocked by ${j}`,"error");return}}const k=s.map(E=>({...E,tasks:[...E.tasks]})),w=k.find(E=>E.name===a),A=k.find(E=>E.name===i),[S]=w.tasks.splice(h,1);/done|termin|closed|complet/i.test(i)?S.checked=!0:S.checked=!1,r!==void 0?A.tasks.splice(r,0,S):A.tasks.push(S),t({columns:k});try{const{tasksDirHandle:E}=e();if(!E&&!d)return;const P=a===i?k.filter(j=>j.name===i):k.filter(j=>j.name===a||j.name===i),{failedIds:M}=await Tf(()=>wj(E??null,P,o.board.columns),{maxAttempts:3});if(M.length>0){const j=M.length===1?`Could not save move for ${M[0]}`:`${M.length} tasks could not be moved`;e().toast(j,"warning",8e3),await e().reloadBoard()}}catch(E){const P=E;P instanceof $o?e().toast("Disk is full โ€” move was not saved. Free up space and try again.","error",8e3):e().toast("Failed to save: "+P.message,"error"),t({columns:s,taskContents:c,searchMatches:p})}},reorderInColumn:async(n,a,i)=>{const{columns:r,tasksDirHandle:s,config:o,taskContents:c,searchMatches:p}=e();if(!s&&!Kt())return;const d=r.map(h=>({...h,tasks:[...h.tasks]})),m=d.find(h=>h.name===n);if(!m)return;const[f]=m.tasks.splice(a,1);m.tasks.splice(i,0,f),t({columns:d});try{const{tasksDirHandle:h}=e(),b=Kt();if(!h&&!b)return;const{failedIds:k}=await Tf(()=>wj(h??null,[m],o.board.columns),{maxAttempts:3});k.length>0&&(e().toast(`Could not save reorder for ${k.length} task(s)`,"warning",8e3),await e().reloadBoard())}catch(h){const b=h;b instanceof $o?e().toast("Disk is full โ€” reorder was not saved.","error",8e3):e().toast("Failed to save: "+b.message,"error"),t({columns:r,taskContents:c,searchMatches:p})}},addColumn:async n=>{const a=n.trim();if(!a)return;const{config:i}=e();i.board.columns.some(r=>r.toLowerCase()===a.toLowerCase())||(await e().updateConfig(r=>({...r,board:{...r.board,columns:[...r.board.columns,a]}})),await e().reloadBoard())},renameColumn:async(n,a)=>{const i=a.trim(),{columns:r,tasksDirHandle:s,config:o}=e();if(!s||!i||i.toLowerCase()===n.toLowerCase())return;if(r.some(d=>d.name.toLowerCase()===i.toLowerCase())){e().toast("Column already exists","error");return}const c=r,p=r.map(d=>d.name===n?{...d,name:i}:d);t({columns:p});try{const d=c.find(m=>m.name===n);if(d){const f=(await Promise.allSettled(d.tasks.map(async(h,b)=>{const{frontmatter:k,body:w}=await Es(s,h.id);await au(s,h.id,{...k,id:h.id,status:i,order:b},w)}))).filter(h=>h.status==="rejected").length;f>0&&e().toast(`${f} task(s) could not be renamed`,"warning",8e3)}await e().updateConfig(m=>{const f={...m.board.columnColors??{}},h=f[n.toLowerCase()];h&&(f[i.toLowerCase()]=h,delete f[n.toLowerCase()]);const b=m.board.columns.some(k=>k.toLowerCase()===n.toLowerCase())?m.board.columns:[...m.board.columns,n];return{...m,board:{...m.board,columns:b.map(k=>k.toLowerCase()===n.toLowerCase()?i:k),columnColors:f}}}),await e().reloadBoard()}catch(d){e().toast("Failed to rename column: "+d.message,"error"),t({columns:c})}},deleteColumn:async n=>{const{columns:a,tasksDirHandle:i}=e();if(!i&&!Kt())return;const r=a.find(o=>o.name===n);if(!r)return;const s=a;t({columns:a.filter(o=>o.name!==n)});try{const c=(await Promise.allSettled(r.tasks.map(p=>bj(i,p.id)))).filter(p=>p.status==="rejected").length;c>0&&e().toast(`${c} task(s) could not be deleted`,"warning",8e3),await e().updateConfig(p=>{const d={...p.board.columnColors??{}};return delete d[n.toLowerCase()],{...p,board:{...p.board,columns:p.board.columns.filter(m=>m.toLowerCase()!==n.toLowerCase()),columnColors:d}}}),await e().reloadBoard(),e().toast("Column deleted")}catch(o){e().toast("Failed to delete column: "+o.message,"error"),t({columns:s})}},createTask:async n=>{const{columns:a,tasksDirHandle:i,config:r,taskContents:s,searchMatches:o}=e();if(!i&&!Kt()||!a.length)return null;const c=n||r.board.columns[0]||a[0].name,p=dce(a),d=a.find(w=>w.name===c)?.tasks.length??0,m={id:p,title:"",checked:!1,dependsOn:[],tags:[],assignee:null,priority:r.fields.priority?r.board.defaultPriority:null,ownerType:r.fields.ownerType?r.board.defaultOwnerType:"",progress:null,frontmatter:{}},f=a.map(w=>w.name===c?{...w,tasks:[...w.tasks,m]}:w),h=new Map(s),b={id:p,title:"",status:c,order:d,priority:r.fields.priority?r.board.defaultPriority:"",tags:[],assignee:"",created:new Date().toISOString().slice(0,10),ownerType:r.fields.ownerType?r.board.defaultOwnerType:"",tools:""},k="";h.set(p,{frontmatter:b,subtasks:[],body:k}),t({columns:f,taskContents:h});try{const w=i||null;return await Tf(()=>au(w,p,b,k),{maxAttempts:3}),e().toast(`Created ${p.replace(/^t/,"")}`),await e().openDrawer(p),p}catch(w){const A=w;return A instanceof $o?e().toast("Disk is full โ€” task was not created.","error",8e3):e().toast("Failed to create: "+A.message,"error"),t({columns:a,taskContents:s,searchMatches:o}),null}},deleteTask:async n=>{const{columns:a,tasksDirHandle:i,taskContents:r,searchMatches:s}=e();if(!i&&!Kt())return;const o=a.map(d=>({...d,tasks:d.tasks.filter(m=>m.id!==n)}));t({columns:o});const c=new Map(r);c.delete(n);const p=new Map(s);p.delete(n),t({taskContents:c,searchMatches:p});try{await bj(i||null,n),e().toast("Deleted")}catch(d){const m=d;e().toast("Failed to delete: "+m.message,"error"),t({columns:a,taskContents:r,searchMatches:s})}},archiveTask:async n=>{const{tasksDirHandle:a}=e();if(!(!a&&!Kt()))try{const{frontmatter:i,body:r}=await Es(a||null,n);await tce(a||null,n,{...i,archived:!0},r),e().drawerTaskId===n&&e().closeDrawer(),await e().reloadBoard(),e().toast("Archived")}catch(i){const r=i;r instanceof $o?e().toast("Disk is full โ€” task was not archived.","error",8e3):e().toast("Failed to archive: "+r.message,"error")}},unarchiveTask:async n=>{const{tasksDirHandle:a}=e();if(!(!a&&!Kt()))try{const{frontmatter:i,body:r}=await Es(a||null,n),s={...i};delete s.archived,await nce(a||null,n,s,r),await e().reloadBoard(),e().toast("Restored")}catch(i){const r=i;r instanceof $o?e().toast("Disk is full โ€” task was not restored.","error",8e3):e().toast("Failed to restore: "+r.message,"error")}},setShowArchives:n=>t({showArchives:n}),setShowMetadata:n=>t({showMetadata:n}),openDrawer:async n=>{const{tasksDirHandle:a}=e();if(!(!a&&!Kt()))try{const{frontmatter:i,body:r}=await Es(a,n),{subtasks:s,bodyWithoutSubtasks:o}=Ds(r),c={frontmatter:i,subtasks:s,body:o,savedAt:Date.now()},p=e().drawerRecoveryData.get(n),d=p?{frontmatter:p.frontmatter,subtasks:p.subtasks,body:p.body}:{frontmatter:i,subtasks:s,body:o},m=new Map(e().drawerRecoveryData);m.delete(n),t({drawerTaskId:n,drawerData:d,drawerBaseVersion:c,conflictState:null,showConflictModal:!1,hasUnsavedDrawerEdits:!!p,lastSaveError:null,drawerRecoveryData:m}),p&&e().toast("Restored your unsaved edits for this task","info")}catch(i){e().toast("Failed to open: "+i.message,"error")}},closeDrawer:()=>t({drawerTaskId:null,drawerData:null,drawerBaseVersion:null,conflictState:null,showConflictModal:!1,hasUnsavedDrawerEdits:!1,lastSaveError:null}),markDrawerDirty:()=>t({hasUnsavedDrawerEdits:!0}),forceCloseDrawer:()=>{const{drawerTaskId:n,drawerData:a}=e();if(n&&a){const i=new Map(e().drawerRecoveryData);i.set(n,{frontmatter:a.frontmatter,subtasks:a.subtasks,body:a.body}),t({drawerRecoveryData:i})}e().closeDrawer()},updateDrawerData:n=>{const{drawerData:a}=e();a&&t({drawerData:n(a)})},saveDrawer:async()=>{const{drawerTaskId:n,drawerData:a,tasksDirHandle:i,taskContents:r}=e();if(!n||!a)return;const s=qd(a.body,a.subtasks),o={...a.frontmatter,id:n};try{await Tf(()=>au(i||null,n,o,s),{maxAttempts:3}),e().toast("Saved");const c=new Map(e().drawerRecoveryData);c.delete(n),t({drawerTaskId:null,drawerData:null,hasUnsavedDrawerEdits:!1,lastSaveError:null,drawerRecoveryData:c});const p=new Map(r);p.set(n,{frontmatter:o,subtasks:a.subtasks,body:a.body}),t({taskContents:p}),await e().reloadBoard()}catch(c){const p=c,d=p instanceof $o?"Disk is full โ€” your edits are kept. Free up space and retry.":"Failed to save: "+p.message;e().toast(d,"error",8e3),t({lastSaveError:d})}},saveDrawerMetadata:async()=>{const{drawerTaskId:n,drawerData:a,tasksDirHandle:i,taskContents:r}=e();if(!(!n||!a))try{const s=qd(a.body,a.subtasks),o={...a.frontmatter,id:n};await Tf(()=>au(i||null,n,o,s),{maxAttempts:3});const c=new Map(r);c.set(n,{frontmatter:o,subtasks:a.subtasks,body:a.body}),t({taskContents:c,hasUnsavedDrawerEdits:!1,lastSaveError:null}),await e().reloadBoard()}catch(s){const o=s,c=o instanceof $o?"Disk is full โ€” your edits are kept.":"Failed to save: "+o.message;t({hasUnsavedDrawerEdits:!0,lastSaveError:c})}},setViewMode:n=>{localStorage.setItem("kandown:view",n),t({viewMode:n})},setDensity:n=>{localStorage.setItem("kandown:density",n),t({density:n})},setFilter:(n,a)=>{if(t(i=>({filters:{...i.filters,[n]:a}})),n==="search"){const{columns:i,tasksDirHandle:r,taskContents:s}=e(),o=a,c=i.flatMap(p=>p.tasks.map(d=>d.id));if(r){const p=c.filter(d=>!s.has(d));p.length>0?e().loadTaskContents(p).then(()=>{e().computeSearchMatches(o)}):e().computeSearchMatches(o)}}},clearFilters:()=>t({filters:{search:"",priority:null,tag:null,assignee:null,ownerType:null},searchMatches:new Map}),setCommandOpen:n=>t({commandOpen:n}),setCheatsheetOpen:n=>t({cheatsheetOpen:n}),refreshAgentHook:async()=>{if(!Kt()){t({agentHook:null});return}const n=await Uoe();t({agentHook:n?.agentHook??null})},sendTaskToAgent:async n=>{const a=e().agentHook;if(!a){e().toast("Agent hook not configured","error");return}e().toast(`Sending to ${a.label}โ€ฆ`);const i=await Voe(n);if(i===null){e().toast("Could not reach the daemon","error");return}i.ok?e().toast(`Sent to ${a.label}`):e().toast(i.error||"Agent hook failed","error")},setCurrentPage:n=>t({currentPage:n}),loadTaskContents:async n=>{const{tasksDirHandle:a}=e();if(!a)return;const i=new Map(e().taskContents);await Promise.all(n.map(async r=>{if(!i.has(r))try{const{frontmatter:s,body:o}=await Es(a,r),{subtasks:c,bodyWithoutSubtasks:p}=Ds(o);i.set(r,{frontmatter:s,subtasks:c,body:p})}catch{}})),t({taskContents:i})},computeSearchMatches:n=>{if(!n.trim()){t({searchMatches:new Map});return}const{taskContents:a}=e(),i=new Map,r=n.toLowerCase();for(const[s,o]of a){const c=Rz(o,r);c.length>0&&i.set(s,c)}t({searchMatches:i})},toast:(n,a="success",i)=>{const r=++bce,s=i??(a==="error"||a==="warning"?6e3:2500);t(o=>({toasts:[...o.toasts,{id:r,message:n,type:a}]})),setTimeout(()=>{t(o=>({toasts:o.toasts.filter(c=>c.id!==r)}))},s)},dismissToast:n=>t(a=>({toasts:a.toasts.filter(i=>i.id!==n)})),resolveConflict:async n=>{const{conflictState:a,drawerData:i,tasksDirHandle:r,drawerTaskId:s,drawerBaseVersion:o}=e();if(!(!a||!r||!s))if(n==="reload"){const{frontmatter:c,body:p}=await Es(r,s),{subtasks:d,bodyWithoutSubtasks:m}=Ds(p);t({drawerData:{frontmatter:c,subtasks:d,body:m},drawerBaseVersion:{frontmatter:c,subtasks:d,body:m,savedAt:Date.now()},conflictState:null,showConflictModal:!1}),e().toast("Reloaded from disk")}else if(n==="overwrite"){if(i&&s&&o){const c=qd(i.body,i.subtasks),p={...i.frontmatter,id:s};try{await au(r,s,p,c),t({drawerBaseVersion:{...i,savedAt:Date.now()},conflictState:null,showConflictModal:!1}),e().toast("Overwritten remote changes")}catch(d){e().toast("Failed to overwrite: "+d.message,"error")}}}else t({conflictState:null,showConflictModal:!1})},setupWatcher:()=>{if(Kt()){M2&&clearInterval(M2),M2=setInterval(()=>{e().reloadBoard()},2e3);return}const{dirHandle:n,tasksDirHandle:a}=e();if(!n||!a)return;al.stop(),Mf.forEach(s=>clearTimeout(s)),Mf.clear(),al.start(n,a);const i=(s,o)=>{const c=Mf.get(s);c&&clearTimeout(c);const p=Math.max(2e3,e().config.notifications.editDebounceMs),d=setTimeout(()=>{Mf.delete(s);const m=e().config;m.notifications.taskEdits&&P2({title:"Task edited",body:`${o} changed on disk.`,config:m})},p);Mf.set(s,d)},r=async s=>{const{tasksDirHandle:o,config:c}=e();if(!o)return;let p,d;try{({frontmatter:p,body:d}=await Es(o,s))}catch(A){console.warn(`[Watcher] notifyTaskChange: failed to read ${s}:`,A);return}const{subtasks:m,bodyWithoutSubtasks:f}=Ds(d),h={id:s,frontmatter:{...p,id:p.id||s,status:p.status||"Backlog"},body:f,subtasks:m},b=h3(h),k=mu.get(s);if(!k){mu.set(s,b);return}c.notifications.statusChanges&&k.status!==b.status&&P2({title:"Task status changed",body:`${b.title}: ${k.status} โ†’ ${b.status}`,config:c});const w=gce(k.subtasks,b.subtasks);c.notifications.subtaskCompletions&&w>0&&P2({title:"Subtask completed",body:w===1?`${b.title}: 1 subtask completed.`:`${b.title}: ${w} subtasks completed.`,config:c}),hce(k,b)&&i(s,b.title),mu.set(s,b)};al.on("configChanged",()=>{try{e().loadConfig(),e().toast("Settings updated externally","info")}catch(s){console.error("[Watcher] configChanged handler error:",s)}}),al.on("taskChanged",async s=>{try{const{drawerTaskId:o,drawerBaseVersion:c,tasksDirHandle:p}=e();if(await r(s),o===s&&c&&p){let d,m;try{({frontmatter:d,body:m}=await Es(p,s))}catch(E){console.warn(`[Watcher] taskChanged: failed to re-read ${s}:`,E);return}const{subtasks:f,bodyWithoutSubtasks:h}=Ds(m),b=c,k=JSON.stringify(b.frontmatter)!==JSON.stringify(d),w=b.body!==h,A=JSON.stringify(b.subtasks)!==JSON.stringify(f);if(!k&&!w&&!A)return;let S="none";k&&(w||A)?S="full":k?S="metadata-only":(w||A)&&(S="body-only"),t({conflictState:{taskId:s,type:S,local:b,remote:{frontmatter:d,body:h,subtasks:f}},showConflictModal:S==="full"})}else await e().reloadBoard()}catch(o){console.error(`[Watcher] taskChanged handler error for ${s}:`,o)}}),al.on("newTaskDetected",async s=>{try{const{tasksDirHandle:o}=e();if(o){let c,p;try{({frontmatter:c,body:p}=await Es(o,s))}catch(f){console.warn(`[Watcher] newTaskDetected: failed to read ${s}:`,f),e().toast(`New task ${s} detected but could not be loaded`,"warning");return}const{subtasks:d,bodyWithoutSubtasks:m}=Ds(p);mu.set(s,h3({id:s,frontmatter:{...c,id:c.id||s,status:c.status||"Backlog"},body:m,subtasks:d}))}await e().reloadBoard()}catch(o){console.error(`[Watcher] newTaskDetected handler error for ${s}:`,o)}}),al.on("watcherError",s=>{t({watcherError:s}),e().toast(s,"warning",1e4)})},restartWatcher:()=>{const{dirHandle:n,tasksDirHandle:a}=e();!n||!a||(t({watcherError:null}),e().setupWatcher(),e().toast("File watcher restarted"))}}));Ly().then(t=>{ue.setState({recentProjects:t})}).catch(t=>{console.warn("[Store] Failed to hydrate recent projects:",t)});rl(Ci);window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{rl(ue.getState().config)});const vce=Object.freeze(Object.defineProperty({__proto__:null,useStore:ue},Symbol.toStringTag,{value:"Module"}));function yce(){const t=ue(o=>o.config),e=ue(o=>o.updateConfig),[n,a]=$.useState(!1);if($.useEffect(()=>{a(!0)},[]),!n)return null;const i=t.ui.theme==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":t.ui.theme,r=i==="dark",s=()=>{const o=r?"light":"dark";e(c=>({...c,ui:{...c.ui,theme:o}}))};return _.jsx(lt.button,{onClick:s,className:m3("relative inline-flex items-center rounded-full transition-all duration-300","border border-black/[0.08] dark:border-white/[0.12]","bg-black/[0.05] dark:bg-white/[0.08]","h-8 w-[52px] px-1"),title:r?"Switch to light mode":"Switch to dark mode",children:_.jsx(lt.div,{className:m3("inline-flex items-center justify-center rounded-full shadow-md",r?"bg-[#1e293b] border border-white/[0.15]":"bg-black border border-black/20","h-[26px] w-[26px]"),animate:{x:r?22:0},transition:{type:"spring",stiffness:500,damping:30},children:_.jsx(lt.div,{initial:{rotate:-90,scale:.5,opacity:0},animate:{rotate:0,scale:1,opacity:1},transition:{duration:.25,ease:"easeOut"},children:r?_.jsx(Zn.Moon,{size:13,className:"text-sky-300"}):_.jsx(Zn.Sun,{size:13,className:"text-amber-400"})},i)})})}function _ce(t){const e=xse(t,{stiffness:180,damping:22,mass:.8});return $.useEffect(()=>{e.set(t)},[t,e]),bz(e,a=>Math.round(a).toString())}const b3="0.15.2",kce=({className:t})=>_.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 150 150",className:t,fill:"currentColor",children:[_.jsx("path",{d:"m57.6 64.6 0.1-0.1v-31.3c-0.1-3.5-2.7-5.6-5.7-5.6h-16.3v59.6c0.2-1.3 0.9-2.6 1.7-3.2l20.2-19.4z"}),_.jsx("path",{d:"m87.6 43.8c-3.4 0.1-7.1 1.3-9.9 3.7l-38.5 38.7c-2.1 2.1-3.4 4.8-3.5 7.5v26.7l77.5-76.4v-0.2h-25.6z"}),_.jsx("path",{d:"m108.1 96.4-6 5-22.4-20.8-14.6 14.2 21.1 21.4-5 4.1c-0.6 0.5-0.3 0.9 0.2 0.9h27.6c0.4 0 0.7-0.4 0.7-0.6v-24.8c0-0.7-1.1-0.3-1.6 0.3v0.3z"})]});function wce(){const{t}=Cn(),e=ue(I=>I.dirHandle),n=ue(I=>I.isOpen),a=ue(I=>I.projectName),i=ue(I=>I.archivedTasks.length),r=ue(I=>I.showArchives),s=ue(I=>I.setShowArchives),o=ue(I=>I.columns),c=ue(I=>I.openFolder),p=ue(I=>I.reloadBoard),d=ue(I=>I.createTask),m=ue(I=>I.setCommandOpen),f=ue(I=>I.viewMode),h=ue(I=>I.setViewMode),b=ue(I=>I.density),k=ue(I=>I.setDensity),w=ue(I=>I.setCurrentPage),A=ue(I=>I.recentProjects),S=ue(I=>I.openRecentProject),E=ue(I=>I.filters),P=ue(I=>I.setFilter),M=ue(I=>I.clearFilters),j=ue(I=>I.config.fields),z=ue(I=>I.lastReloadError),H=ue(I=>I.watcherError),O=ue(I=>I.restartWatcher),[L,W]=$.useState(!1),[X,te]=$.useState(!0),Z=$.useRef(null),Q=$.useRef(null),q=o.reduce((I,J)=>I+J.tasks.length,0),V=_ce(q),B=[];j.priority&&E.priority&&B.push({type:"priority",label:E.priority,value:E.priority}),j.tags&&E.tag&&B.push({type:"tag",label:"#"+E.tag,value:E.tag}),j.assignee&&E.assignee&&B.push({type:"assignee",label:"@"+E.assignee,value:E.assignee});const ne=[{label:t("filterBar.ownerAll"),value:""},{label:t("filterBar.ownerHuman"),value:"human"},{label:t("filterBar.ownerAI"),value:"ai"}],D=B.length>0||E.search||j.ownerType&&E.ownerType;return $.useEffect(()=>{if(!L)return;const I=J=>{Z.current&&!Z.current.contains(J.target)&&W(!1)};return document.addEventListener("mousedown",I),()=>document.removeEventListener("mousedown",I)},[L]),$.useEffect(()=>{const I=setTimeout(()=>te(!1),5e3);return()=>clearTimeout(I)},[]),$.useEffect(()=>{document.title=a?`${a} ยท Kandown`:"Kandown"},[a]),_.jsxs(_.Fragment,{children:[(z||H)&&_.jsxs("div",{className:"flex items-center gap-2 px-5 py-1.5 bg-amber-500/10 border-b border-amber-500/30 text-[12px] text-amber-700 dark:text-amber-300",children:[_.jsx("span",{className:"flex-1 truncate",children:H?`โš  ${H}`:`โš  ${z}`}),H&&_.jsx("button",{type:"button",onClick:O,className:"px-2 py-0.5 rounded bg-amber-500/20 hover:bg-amber-500/30 text-[11.5px] font-medium",children:"Restart watcher"}),_.jsx("button",{type:"button",onClick:p,className:"px-2 py-0.5 rounded bg-amber-500/20 hover:bg-amber-500/30 text-[11.5px] font-medium",children:"Reload"})]}),_.jsxs("header",{className:"flex items-center justify-between px-5 h-[64px] border-b border-border bg-card/80 backdrop-blur-xl relative z-10",children:[_.jsxs("div",{className:"flex items-center gap-4 min-w-0",children:[_.jsx("div",{className:"flex items-center gap-2.5 flex-shrink-0",children:_.jsxs("button",{onClick:()=>window.history.pushState({},"",window.location.pathname),className:"flex items-center gap-2 cursor-pointer",children:[_.jsx(kce,{className:"w-[34px] h-[34px] dark:text-white text-black"}),_.jsx(Sr,{mode:"wait",initial:!1,children:X?_.jsxs(lt.span,{className:"flex items-center gap-2",initial:{opacity:0},animate:{opacity:1},exit:{opacity:0},transition:{duration:.45},children:[_.jsx("span",{className:"text-[15px] font-semibold tracking-tight text-fg",children:"kandown"}),_.jsxs("span",{className:"inline-flex items-center h-5 px-1.5 text-[10.5px] font-semibold text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-500/15 rounded-md",children:["v",b3]})]},"boot"):a?_.jsx(lt.span,{className:"text-[15px] font-semibold tracking-tight text-fg truncate max-w-[240px]",initial:{opacity:0},animate:{opacity:1},exit:{opacity:0},transition:{duration:.45},children:a},"project"):null})]})}),e&&_.jsxs(_.Fragment,{children:[_.jsx("div",{className:"w-px h-[20px] bg-black/[0.08] dark:bg-white/[0.08] flex-shrink-0"}),_.jsxs("div",{className:"flex items-center gap-2 px-3 h-9 bg-secondary/60 border border-border rounded-xl min-w-[200px] max-w-[280px] focus-within:border-border-focus focus-within:bg-secondary transition-all",children:[_.jsx(Zn.Search,{size:14,className:"text-fg-muted/60 flex-shrink-0"}),_.jsx("input",{ref:Q,type:"text",placeholder:t("filterBar.searchPlaceholder"),value:E.search,onChange:I=>P("search",I.target.value),className:"bg-transparent border-none outline-none text-fg text-[13px] w-full placeholder:text-fg-muted/60"}),E.search?_.jsx("button",{onClick:()=>P("search",""),className:"text-fg-muted/60 hover:text-fg flex-shrink-0",children:_.jsx(Zn.X,{size:14})}):_.jsx("kbd",{className:"inline-flex items-center h-5 px-1.5 text-[10px] font-medium text-fg-muted/50 bg-black/[0.04] dark:bg-white/[0.08] rounded border border-black/[0.06] dark:border-white/[0.1]",children:"โŒ˜K"})]}),_.jsxs("div",{className:"flex items-center gap-1.5 flex-wrap overflow-hidden",children:[_.jsx(Sr,{children:B.map(I=>_.jsxs(lt.button,{initial:{opacity:0,scale:.9},animate:{opacity:1,scale:1},exit:{opacity:0,scale:.9},transition:{duration:.15},onClick:()=>P(I.type,null),className:"inline-flex items-center gap-1 h-6 px-2.5 text-[12px] text-fg bg-black/[0.05] dark:bg-white/[0.1] border border-black/[0.08] dark:border-white/[0.12] rounded-lg hover:bg-black/[0.08] dark:hover:bg-white/[0.15] transition-colors",children:[I.label,_.jsx(Zn.X,{size:10,className:"text-fg-muted/60"})]},I.type+I.value))}),j.ownerType&&_.jsx("div",{className:"flex items-center h-6 border border-black/[0.06] dark:border-white/[0.1] rounded-lg overflow-hidden",children:ne.map(I=>_.jsx("button",{onClick:()=>P("ownerType",I.value),className:`h-full px-2.5 text-[12px] transition-colors ${E.ownerType===I.value?"bg-black/[0.06] dark:bg-white/[0.12] text-fg":"text-fg-muted/70 hover:text-fg"}`,children:I.label},I.value))}),D&&_.jsx("button",{onClick:M,className:"text-[12px] text-fg-muted/60 hover:text-fg transition-colors",children:t("filterBar.clearAll")})]})]}),e&&_.jsxs(_.Fragment,{children:[_.jsx("div",{className:"w-px h-[20px] bg-black/[0.08] dark:bg-white/[0.08] flex-shrink-0"}),_.jsxs("div",{className:"relative flex-shrink-0",ref:Z,children:[_.jsxs("button",{onClick:()=>W(I=>!I),className:"flex items-center gap-1.5 px-2.5 py-1.5 text-[13px] text-fg-muted hover:text-fg hover:bg-black/[0.04] dark:hover:bg-white/[0.06] rounded-lg transition-colors border border-transparent hover:border-black/[0.06] dark:hover:border-white/[0.1]",children:[_.jsx(Zn.Folder,{size:13,className:"text-fg-muted/70"}),_.jsxs("span",{className:"font-medium",children:[".",e.name]}),_.jsx(Zn.ChevronDown,{size:11,className:"opacity-50"})]}),_.jsx(Sr,{children:L&&_.jsx(lt.div,{initial:{opacity:0,y:-4,scale:.98},animate:{opacity:1,y:0,scale:1},exit:{opacity:0,y:-4,scale:.98},transition:{duration:.12},className:"absolute top-full left-0 mt-2 min-w-[240px] glass rounded-xl shadow-[0_16px_48px_rgba(0,0,0,0.5)] overflow-hidden z-50",children:_.jsxs("div",{className:"py-1.5",children:[A.length>0&&_.jsxs(_.Fragment,{children:[_.jsx("div",{className:"px-3 py-1.5 text-[11px] font-semibold uppercase tracking-wider text-fg-muted/60",children:t("header.recentProjects")}),A.map(I=>_.jsxs("button",{onClick:()=>{W(!1),S(I)},className:"w-full flex items-center gap-2 px-3 py-2 text-[13.5px] text-left hover:bg-black/[0.04] dark:hover:bg-white/[0.06] transition-colors",children:[_.jsx(Zn.Folder,{size:12,className:"text-fg-muted/60"}),_.jsx("span",{className:"truncate",children:I.name}),I.id===e.name&&_.jsx(Zn.Check,{size:12,className:"ml-auto text-emerald-500"})]},I.id)),_.jsx("div",{className:"h-px bg-black/[0.06] dark:bg-white/[0.08] my-1.5 mx-2"})]}),_.jsxs("button",{onClick:()=>{W(!1),c()},className:"w-full flex items-center gap-2 px-3 py-2 text-[13.5px] text-left hover:bg-black/[0.04] dark:hover:bg-white/[0.06] transition-colors",children:[_.jsx(Zn.Plus,{size:12,className:"text-fg-muted/60"}),_.jsx("span",{children:t("header.openFolder...")})]})]})})})]})]})]}),_.jsx("div",{className:"flex items-center gap-2 flex-shrink-0",children:n||e?_.jsxs(_.Fragment,{children:[_.jsxs("div",{className:"flex items-center gap-2 mr-2 text-[12.5px] text-fg-muted/70",children:[_.jsx("span",{className:"inline-block w-1.5 h-1.5 rounded-full bg-emerald-500"}),_.jsx(lt.span,{className:"tabular-nums font-medium",children:V}),_.jsx("span",{children:t("header.tasks")})]}),_.jsxs("div",{className:"flex items-center bg-black/[0.04] dark:bg-white/[0.06] border border-black/[0.06] dark:border-white/[0.1] rounded-xl p-0.5 h-10",children:[_.jsx("button",{onClick:()=>h("board"),className:`w-9 h-9 inline-flex items-center justify-center rounded-lg transition-all ${f==="board"?"bg-card text-fg shadow-sm":"text-fg-muted/70 hover:text-fg"}`,title:t("common.board"),children:_.jsx(Zn.LayoutBoard,{size:18})}),_.jsx("button",{onClick:()=>h("list"),className:`w-9 h-9 inline-flex items-center justify-center rounded-lg transition-all ${f==="list"?"bg-card text-fg shadow-sm":"text-fg-muted/70 hover:text-fg"}`,title:t("common.list"),children:_.jsx(Zn.LayoutList,{size:18})})]}),_.jsx(na,{variant:"icon",icon:"Archive",onClick:()=>s(!r),title:r?t("header.backToBoard"):`${t("header.archives")} (${i})`,className:r?"text-accent":""}),_.jsx(na,{variant:"icon",icon:"Density",onClick:()=>k(b==="compact"?"comfortable":"compact"),title:`Density: ${b}`}),_.jsx(na,{variant:"icon",icon:"Settings",onClick:()=>w("settings"),title:t("common.settings")}),_.jsx(yce,{}),_.jsx("div",{className:"w-px h-5 bg-black/[0.08] dark:bg-white/[0.08] mx-1"}),_.jsx(na,{variant:"secondary",icon:"Search",label:t("common.search"),shortcut:"โŒ˜K",onClick:()=>m(!0),title:"Command palette (โŒ˜K)"}),_.jsx(na,{variant:"icon",icon:"Refresh",onClick:p,title:`${t("common.reload")} (R)`}),_.jsx(na,{variant:"primary",icon:"Plus",label:t("common.newTask"),shortcut:"N",onClick:()=>d()})]}):_.jsx(na,{variant:"primary",label:t("common.openFolder"),onClick:c})})]})]})}/**
117
117
  * @license @tabler/icons-react v3.41.1 - MIT
118
118
  *
119
119
  * This source code is licensed under the MIT license.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kandown",
3
- "version": "0.15.1",
3
+ "version": "0.15.2",
4
4
  "description": "File-based Kanban engine backed by Markdown. Zero backend, AI-agent friendly.",
5
5
  "type": "module",
6
6
  "bin": {