kandown 0.33.3 → 0.33.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.33.4 — 2026-07-21 — "Daemon Update Recovery"
4
+
5
+ - **Fixed**: **Default `kandown` Launch Reusing Stale Daemons** — running `kandown` now validates the already-running project daemon version before reusing it. If the daemon is missing a version or is running a different CLI version than the current command, Kandown safely stops it and starts a fresh daemon from the active CLI so the browser opens the current web app instead of stale in-memory code.
6
+ - **Fixed**: **Web UI Auto-Update Not Taking Effect Immediately** — successful `/api/update/apply` calls now schedule a detached daemon restart after the HTTP success response is sent. This makes one-click Web UI updates actually switch the local server process to the newly installed package instead of requiring a manual daemon stop/start.
7
+ - **Fixed**: **Auto-Updater False Success Across Multiple Node Prefixes** — global package update attempts now verify the resolved `kandown --version` after each package-manager install command. If `npm`, `pnpm`, `yarn`, or `bun` updates a different global prefix than the executable on PATH, the updater keeps trying fallbacks and no longer reports success while the active command remains stale.
8
+ - **Changed**: **Daemon Health Metadata** — daemon status checks now read and propagate the remote daemon version from `/api/daemon`, keeping CLI-side restart decisions aligned with the process that is actually serving the project.
9
+ - **Changed**: **Update Route Messaging** — the update API success message now explicitly says the daemon is restarting, making the expected short reconnect window clearer for the Web UI.
10
+
3
11
  ## 0.33.3 — 2026-07-21 — "CLI Recovery"
4
12
 
5
13
  - **Fixed**: **Default `kandown` Launch Regression** — restored the pre-split zero-setup startup flow where running `kandown` inside any project automatically initializes `.kandown/` when needed, refreshes the web bundle, starts the per-project daemon, and enters the normal board launch path.
package/bin/kandown.js CHANGED
@@ -16,7 +16,7 @@ import { spawn, execSync } from "child_process";
16
16
  import { homedir } from "os";
17
17
 
18
18
  // src/lib/version.ts
19
- var KANDOWN_VERSION = "0.33.3";
19
+ var KANDOWN_VERSION = "0.33.4";
20
20
 
21
21
  // src/cli/lib/updater.ts
22
22
  import { fileURLToPath } from "url";
@@ -99,6 +99,31 @@ function resolveKandownBin() {
99
99
  }
100
100
  return null;
101
101
  }
102
+ async function readInstalledKandownVersion(targetVersion) {
103
+ const localVersion = getCurrentVersion();
104
+ const bin = resolveKandownBin();
105
+ if (!bin) return localVersion;
106
+ return await new Promise((resolveVersion) => {
107
+ const child = spawn(bin, ["--version"], {
108
+ timeout: 5e3,
109
+ stdio: ["pipe", "pipe", "pipe"],
110
+ env: { ...process.env, KANDOWN_NO_UPDATE: "1" },
111
+ detached: false
112
+ });
113
+ let stdout = "";
114
+ child.stdout.on("data", (d) => {
115
+ stdout += d;
116
+ });
117
+ child.stderr.on("data", () => {
118
+ });
119
+ child.on("error", () => resolveVersion(localVersion));
120
+ child.on("close", (code) => {
121
+ if (code !== 0) return resolveVersion(localVersion);
122
+ const match = stdout.trim().match(/v?(\d+\.\d+\.\d+(?:-[\w.-]+)?)/);
123
+ resolveVersion(match ? match[1] : localVersion);
124
+ });
125
+ });
126
+ }
102
127
  function updateCheckedRecently() {
103
128
  try {
104
129
  if (!existsSync(UPDATE_CHECK_CACHE)) return false;
@@ -115,6 +140,10 @@ function rememberUpdateCheck() {
115
140
  } catch {
116
141
  }
117
142
  }
143
+ function requestedSemver(packageSpec) {
144
+ const match = packageSpec.match(/@(\d+\.\d+\.\d+(?:-[\w.-]+)?)$/);
145
+ return match ? match[1] : null;
146
+ }
118
147
  async function performGlobalPackageUpdate(packageSpec) {
119
148
  const cleanEnv = { ...process.env };
120
149
  for (const k of Object.keys(cleanEnv)) {
@@ -122,6 +151,12 @@ async function performGlobalPackageUpdate(packageSpec) {
122
151
  delete cleanEnv[k];
123
152
  }
124
153
  }
154
+ const targetVersion = requestedSemver(packageSpec);
155
+ const verifyInstalledVersion = async () => {
156
+ if (!targetVersion) return true;
157
+ const installedVersion = await readInstalledKandownVersion(targetVersion);
158
+ return semverGt(installedVersion, targetVersion) >= 0;
159
+ };
125
160
  const tryPkgCmd = (cmd, args) => {
126
161
  return new Promise((res) => {
127
162
  const child = spawn(cmd, args, {
@@ -138,22 +173,26 @@ async function performGlobalPackageUpdate(packageSpec) {
138
173
  child.on("close", (code) => res(code === 0));
139
174
  });
140
175
  };
176
+ const tryPkgCmdAndVerify = async (cmd, args) => {
177
+ if (!await tryPkgCmd(cmd, args)) return false;
178
+ return verifyInstalledVersion();
179
+ };
141
180
  const currentBin = resolveKandownBin() || "";
142
181
  const currentBinDir = currentBin ? dirname(currentBin) : null;
143
182
  const siblingNpm = currentBinDir ? join(currentBinDir, "npm") : null;
144
183
  const siblingPnpm = currentBinDir ? join(currentBinDir, "pnpm") : null;
145
184
  const isPnpmInstall = currentBin.includes("pnpm");
146
- if (siblingPnpm && existsSync(siblingPnpm) && await tryPkgCmd(siblingPnpm, ["add", "-g", packageSpec])) return true;
147
- if (siblingNpm && existsSync(siblingNpm) && await tryPkgCmd(siblingNpm, ["install", "-g", packageSpec, "--force"])) return true;
185
+ if (siblingPnpm && existsSync(siblingPnpm) && await tryPkgCmdAndVerify(siblingPnpm, ["add", "-g", packageSpec])) return true;
186
+ if (siblingNpm && existsSync(siblingNpm) && await tryPkgCmdAndVerify(siblingNpm, ["install", "-g", packageSpec, "--force"])) return true;
148
187
  if (isPnpmInstall) {
149
- if (await tryPkgCmd("pnpm", ["add", "-g", packageSpec])) return true;
150
- if (await tryPkgCmd("npm", ["install", "-g", packageSpec, "--force"])) return true;
188
+ if (await tryPkgCmdAndVerify("pnpm", ["add", "-g", packageSpec])) return true;
189
+ if (await tryPkgCmdAndVerify("npm", ["install", "-g", packageSpec, "--force"])) return true;
151
190
  } else {
152
- if (await tryPkgCmd("npm", ["install", "-g", packageSpec, "--force"])) return true;
153
- if (await tryPkgCmd("pnpm", ["add", "-g", packageSpec])) return true;
191
+ if (await tryPkgCmdAndVerify("npm", ["install", "-g", packageSpec, "--force"])) return true;
192
+ if (await tryPkgCmdAndVerify("pnpm", ["add", "-g", packageSpec])) return true;
154
193
  }
155
- if (await tryPkgCmd("yarn", ["global", "add", packageSpec])) return true;
156
- return await tryPkgCmd("bun", ["add", "-g", packageSpec]);
194
+ if (await tryPkgCmdAndVerify("yarn", ["global", "add", packageSpec])) return true;
195
+ return await tryPkgCmdAndVerify("bun", ["add", "-g", packageSpec]);
157
196
  }
158
197
  async function checkForUpdate(argv = process.argv) {
159
198
  if (process.env.KANDOWN_NO_UPDATE === "1") return;
@@ -802,9 +841,10 @@ function isProcessAlive(pid) {
802
841
  }
803
842
  function parseRemoteDaemonInfo(value) {
804
843
  if (!isRecord(value)) return null;
805
- const { ok, pid, kandownDir } = value;
844
+ const { ok, pid, kandownDir, version } = value;
806
845
  if (ok !== true || typeof pid !== "number" || !Number.isInteger(pid) || typeof kandownDir !== "string") return null;
807
- return { ok, pid, kandownDir };
846
+ if (version !== null && typeof version !== "string" && version !== void 0) return null;
847
+ return { ok, pid, kandownDir, version: typeof version === "string" ? version : null };
808
848
  }
809
849
  async function fetchDaemonInfo(port) {
810
850
  try {
@@ -846,7 +886,7 @@ async function getDaemonStatus(kandownDir) {
846
886
  removeDaemonMetadata(kandownDir);
847
887
  return { running: false, metadata: null };
848
888
  }
849
- return { running: true, metadata };
889
+ return { running: true, metadata: { ...metadata, version: remote.version ?? metadata.version } };
850
890
  }
851
891
  async function waitForDaemon(kandownDir, timeoutMs = 8e3) {
852
892
  const started = Date.now();
@@ -861,7 +901,10 @@ async function waitForDaemon(kandownDir, timeoutMs = 8e3) {
861
901
  }
862
902
  async function startProjectDaemon(kandownDir, preferredPort) {
863
903
  const current = await getDaemonStatus(kandownDir);
864
- if (current.running) return current;
904
+ if (current.running) {
905
+ if (current.metadata?.version === getCurrentVersion()) return current;
906
+ await stopProjectDaemon(kandownDir);
907
+ }
865
908
  const cliPath = process.argv[1];
866
909
  if (!cliPath) throw new Error("Cannot locate kandown CLI entrypoint");
867
910
  const args = [
@@ -984,6 +1027,42 @@ function syncProjectKandownHtml(kandownDir) {
984
1027
  }
985
1028
  return false;
986
1029
  }
1030
+ function readDaemonPort(kandownDir) {
1031
+ try {
1032
+ const raw = JSON.parse(readFileSync5(join5(kandownDir, "daemon.json"), "utf8"));
1033
+ return typeof raw.port === "number" && Number.isInteger(raw.port) ? raw.port : null;
1034
+ } catch {
1035
+ return null;
1036
+ }
1037
+ }
1038
+ function restartDaemonAfterUpdateResponse(res, kandownDir) {
1039
+ const cliPath = process.argv[1];
1040
+ if (!cliPath) return;
1041
+ const args = ["--no-update-check", "daemon", "run", "--path", kandownDir];
1042
+ const port = readDaemonPort(kandownDir);
1043
+ if (port !== null) args.push("--port", String(port));
1044
+ const launcher = `
1045
+ const { spawn } = require('node:child_process');
1046
+ const [nodeBin, cliPath, ...cliArgs] = process.argv.slice(1);
1047
+ setTimeout(() => {
1048
+ const child = spawn(nodeBin, [cliPath, ...cliArgs], {
1049
+ detached: true,
1050
+ stdio: 'ignore',
1051
+ env: { ...process.env, KANDOWN_DAEMON: '1' },
1052
+ });
1053
+ child.unref();
1054
+ }, 350);
1055
+ `;
1056
+ res.on("finish", () => {
1057
+ const child = spawn3(process.execPath, ["-e", launcher, process.execPath, cliPath, ...args], {
1058
+ detached: true,
1059
+ stdio: "ignore",
1060
+ env: { ...process.env, KANDOWN_DAEMON: "1" }
1061
+ });
1062
+ child.unref();
1063
+ setTimeout(() => process.exit(0), 50).unref();
1064
+ });
1065
+ }
987
1066
  async function handleApi(req, res, url, kandownDir) {
988
1067
  const path = url.pathname;
989
1068
  const method = req.method || "GET";
@@ -1050,7 +1129,8 @@ async function handleApi(req, res, url, kandownDir) {
1050
1129
  const ok = await performGlobalPackageUpdate(`kandown@${targetVersion}`);
1051
1130
  syncProjectKandownHtml(kandownDir);
1052
1131
  if (ok) {
1053
- writeJson(res, 200, { ok: true, version: targetVersion, message: "Update installed successfully" });
1132
+ restartDaemonAfterUpdateResponse(res, kandownDir);
1133
+ writeJson(res, 200, { ok: true, version: targetVersion, message: "Update installed successfully; daemon is restarting" });
1054
1134
  broadcastSseEvent({ type: "update", version: targetVersion });
1055
1135
  } else {
1056
1136
  writeJson(res, 500, { ok: false, message: "Global package installation failed" });
package/bin/tui.js CHANGED
@@ -907,21 +907,21 @@ var require_react_development = __commonJS({
907
907
  );
908
908
  actScopeDepth = prevActScopeDepth;
909
909
  }
910
- function recursivelyFlushAsyncActWork(returnValue, resolve3, reject) {
910
+ function recursivelyFlushAsyncActWork(returnValue, resolve4, reject) {
911
911
  var queue = ReactSharedInternals.actQueue;
912
912
  if (null !== queue)
913
913
  if (0 !== queue.length)
914
914
  try {
915
915
  flushActQueue(queue);
916
916
  enqueueTask(function() {
917
- return recursivelyFlushAsyncActWork(returnValue, resolve3, reject);
917
+ return recursivelyFlushAsyncActWork(returnValue, resolve4, reject);
918
918
  });
919
919
  return;
920
920
  } catch (error) {
921
921
  ReactSharedInternals.thrownErrors.push(error);
922
922
  }
923
923
  else ReactSharedInternals.actQueue = null;
924
- 0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve3(returnValue);
924
+ 0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve4(returnValue);
925
925
  }
926
926
  function flushActQueue(queue) {
927
927
  if (!isFlushing) {
@@ -1108,7 +1108,7 @@ var require_react_development = __commonJS({
1108
1108
  ));
1109
1109
  });
1110
1110
  return {
1111
- then: function(resolve3, reject) {
1111
+ then: function(resolve4, reject) {
1112
1112
  didAwaitActCall = true;
1113
1113
  thenable.then(
1114
1114
  function(returnValue) {
@@ -1118,7 +1118,7 @@ var require_react_development = __commonJS({
1118
1118
  flushActQueue(queue), enqueueTask(function() {
1119
1119
  return recursivelyFlushAsyncActWork(
1120
1120
  returnValue,
1121
- resolve3,
1121
+ resolve4,
1122
1122
  reject
1123
1123
  );
1124
1124
  });
@@ -1132,7 +1132,7 @@ var require_react_development = __commonJS({
1132
1132
  ReactSharedInternals.thrownErrors.length = 0;
1133
1133
  reject(_thrownError);
1134
1134
  }
1135
- } else resolve3(returnValue);
1135
+ } else resolve4(returnValue);
1136
1136
  },
1137
1137
  function(error) {
1138
1138
  popActScope(prevActQueue, prevActScopeDepth);
@@ -1154,15 +1154,15 @@ var require_react_development = __commonJS({
1154
1154
  if (0 < ReactSharedInternals.thrownErrors.length)
1155
1155
  throw callback = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, callback;
1156
1156
  return {
1157
- then: function(resolve3, reject) {
1157
+ then: function(resolve4, reject) {
1158
1158
  didAwaitActCall = true;
1159
1159
  0 === prevActScopeDepth ? (ReactSharedInternals.actQueue = queue, enqueueTask(function() {
1160
1160
  return recursivelyFlushAsyncActWork(
1161
1161
  returnValue$jscomp$0,
1162
- resolve3,
1162
+ resolve4,
1163
1163
  reject
1164
1164
  );
1165
- })) : resolve3(returnValue$jscomp$0);
1165
+ })) : resolve4(returnValue$jscomp$0);
1166
1166
  }
1167
1167
  };
1168
1168
  };
@@ -3048,8 +3048,8 @@ var require_react_reconciler_production = __commonJS({
3048
3048
  currentEntangledActionThenable = {
3049
3049
  status: "pending",
3050
3050
  value: void 0,
3051
- then: function(resolve3) {
3052
- entangledListeners.push(resolve3);
3051
+ then: function(resolve4) {
3052
+ entangledListeners.push(resolve4);
3053
3053
  }
3054
3054
  };
3055
3055
  }
@@ -3072,8 +3072,8 @@ var require_react_reconciler_production = __commonJS({
3072
3072
  status: "pending",
3073
3073
  value: null,
3074
3074
  reason: null,
3075
- then: function(resolve3) {
3076
- listeners.push(resolve3);
3075
+ then: function(resolve4) {
3076
+ listeners.push(resolve4);
3077
3077
  }
3078
3078
  };
3079
3079
  thenable.then(
@@ -12672,8 +12672,8 @@ var require_react_reconciler_development = __commonJS({
12672
12672
  currentEntangledActionThenable = {
12673
12673
  status: "pending",
12674
12674
  value: void 0,
12675
- then: function(resolve3) {
12676
- entangledListeners.push(resolve3);
12675
+ then: function(resolve4) {
12676
+ entangledListeners.push(resolve4);
12677
12677
  }
12678
12678
  };
12679
12679
  }
@@ -12696,8 +12696,8 @@ var require_react_reconciler_development = __commonJS({
12696
12696
  status: "pending",
12697
12697
  value: null,
12698
12698
  reason: null,
12699
- then: function(resolve3) {
12700
- listeners.push(resolve3);
12699
+ then: function(resolve4) {
12700
+ listeners.push(resolve4);
12701
12701
  }
12702
12702
  };
12703
12703
  thenable.then(
@@ -44418,22 +44418,22 @@ var init_devtools = __esm({
44418
44418
  init_devtools_window_polyfill();
44419
44419
  init_wrapper();
44420
44420
  import_react_devtools_core = __toESM(require_backend(), 1);
44421
- isDevToolsReachable = async () => new Promise((resolve3) => {
44421
+ isDevToolsReachable = async () => new Promise((resolve4) => {
44422
44422
  const socket = new wrapper_default("ws://localhost:8097");
44423
44423
  const timeout = setTimeout(() => {
44424
44424
  socket.terminate();
44425
- resolve3(false);
44425
+ resolve4(false);
44426
44426
  }, 2e3);
44427
44427
  timeout.unref();
44428
44428
  socket.on("open", () => {
44429
44429
  clearTimeout(timeout);
44430
44430
  socket.terminate();
44431
- resolve3(true);
44431
+ resolve4(true);
44432
44432
  });
44433
44433
  socket.on("error", () => {
44434
44434
  clearTimeout(timeout);
44435
44435
  socket.terminate();
44436
- resolve3(false);
44436
+ resolve4(false);
44437
44437
  });
44438
44438
  });
44439
44439
  if (await isDevToolsReachable()) {
@@ -52683,8 +52683,8 @@ var kittyModifiers = {
52683
52683
  var noop = () => {
52684
52684
  };
52685
52685
  var textEncoder = new TextEncoder();
52686
- var yieldImmediate = async () => new Promise((resolve3) => {
52687
- setImmediate(resolve3);
52686
+ var yieldImmediate = async () => new Promise((resolve4) => {
52687
+ setImmediate(resolve4);
52688
52688
  });
52689
52689
  var kittyQueryEscapeByte = 27;
52690
52690
  var kittyQueryOpenBracketByte = 91;
@@ -52888,8 +52888,8 @@ var Ink = class {
52888
52888
  };
52889
52889
  }
52890
52890
  this.initKittyKeyboard();
52891
- this.exitPromise = new Promise((resolve3, reject) => {
52892
- this.resolveExitPromise = resolve3;
52891
+ this.exitPromise = new Promise((resolve4, reject) => {
52892
+ this.resolveExitPromise = resolve4;
52893
52893
  this.rejectExitPromise = reject;
52894
52894
  });
52895
52895
  void this.exitPromise.catch(noop);
@@ -53182,9 +53182,9 @@ var Ink = class {
53182
53182
  settleThrottle(this.throttledOnRender, canWriteToStdout);
53183
53183
  settleThrottle(this.throttledLog, canWriteToStdout);
53184
53184
  if (canWriteToStdout && hasWritableState) {
53185
- await new Promise((resolve3) => {
53185
+ await new Promise((resolve4) => {
53186
53186
  this.options.stdout.write("", () => {
53187
- resolve3();
53187
+ resolve4();
53188
53188
  });
53189
53189
  });
53190
53190
  return;
@@ -53251,8 +53251,8 @@ var Ink = class {
53251
53251
  async awaitNextRender() {
53252
53252
  if (!this.nextRenderCommit) {
53253
53253
  let resolveRender;
53254
- const promise = new Promise((resolve3) => {
53255
- resolveRender = resolve3;
53254
+ const promise = new Promise((resolve4) => {
53255
+ resolveRender = resolve4;
53256
53256
  });
53257
53257
  this.nextRenderCommit = { promise, resolve: resolveRender };
53258
53258
  }
@@ -53984,7 +53984,7 @@ var import_react32 = __toESM(require_react(), 1);
53984
53984
  var import_react33 = __toESM(require_react(), 1);
53985
53985
 
53986
53986
  // src/cli/tui.tsx
53987
- import { fileURLToPath as fileURLToPath2 } from "url";
53987
+ import { fileURLToPath as fileURLToPath3 } from "url";
53988
53988
 
53989
53989
  // src/cli/app.tsx
53990
53990
  var import_react38 = __toESM(require_react(), 1);
@@ -54434,7 +54434,7 @@ function ValueDisplay({ setting, value, focused }) {
54434
54434
  // src/cli/screens/board.tsx
54435
54435
  var import_react37 = __toESM(require_react(), 1);
54436
54436
  import { spawnSync } from "child_process";
54437
- import { join as join8 } from "path";
54437
+ import { join as join9 } from "path";
54438
54438
 
54439
54439
  // src/cli/lib/board-reader.ts
54440
54440
  import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3, mkdirSync, unlinkSync as unlinkSync2 } from "fs";
@@ -54923,12 +54923,56 @@ function archiveTaskInBoard(kandownDir, taskId) {
54923
54923
  }
54924
54924
 
54925
54925
  // src/cli/lib/daemon.ts
54926
- import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync as unlinkSync3 } from "fs";
54927
- import { dirname as dirname2, join as join3 } from "path";
54928
- import { execFileSync as execFileSync3, spawn } from "child_process";
54926
+ import { existsSync as existsSync5, readFileSync as readFileSync5, unlinkSync as unlinkSync4 } from "fs";
54927
+ import { dirname as dirname3, join as join4 } from "path";
54928
+ import { execFileSync as execFileSync3, spawn as spawn2 } from "child_process";
54929
54929
  import { createConnection } from "net";
54930
+
54931
+ // src/cli/lib/updater.ts
54932
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2, unlinkSync as unlinkSync3, statSync, mkdirSync as mkdirSync2 } from "fs";
54933
+ import { join as join3, resolve } from "path";
54934
+ import { spawn, execSync } from "child_process";
54935
+ import { homedir as homedir2 } from "os";
54936
+
54937
+ // src/lib/version.ts
54938
+ var KANDOWN_VERSION = "0.33.4";
54939
+
54940
+ // src/cli/lib/updater.ts
54941
+ import { fileURLToPath as fileURLToPath2 } from "url";
54942
+ import { dirname as dirname2 } from "path";
54943
+ function getPackageRoot() {
54944
+ try {
54945
+ const currentFile = fileURLToPath2(import.meta.url);
54946
+ if (currentFile.includes("/bin/")) {
54947
+ return resolve(dirname2(currentFile), "..");
54948
+ }
54949
+ return resolve(dirname2(currentFile), "../../..");
54950
+ } catch {
54951
+ return process.cwd();
54952
+ }
54953
+ }
54954
+ var PKG_ROOT2 = getPackageRoot();
54955
+ var CACHE_DIR = join3(homedir2(), ".kandown");
54956
+ var UPDATE_CHECK_CACHE = join3(CACHE_DIR, ".update-check.json");
54957
+ var UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
54958
+ function getCurrentVersion() {
54959
+ if (KANDOWN_VERSION && KANDOWN_VERSION !== "0.0.0-dev") {
54960
+ return KANDOWN_VERSION;
54961
+ }
54962
+ try {
54963
+ const pkgPath = resolve(import.meta.url ? new URL("../../..", import.meta.url).pathname : process.cwd(), "package.json");
54964
+ if (existsSync4(pkgPath)) {
54965
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
54966
+ if (pkg.version) return pkg.version;
54967
+ }
54968
+ } catch {
54969
+ }
54970
+ return KANDOWN_VERSION || "0.32.1";
54971
+ }
54972
+
54973
+ // src/cli/lib/daemon.ts
54930
54974
  function metadataPath(kandownDir) {
54931
- return join3(kandownDir, "daemon.json");
54975
+ return join4(kandownDir, "daemon.json");
54932
54976
  }
54933
54977
  function isRecord(value) {
54934
54978
  return typeof value === "object" && value !== null;
@@ -54946,9 +54990,9 @@ function parseMetadata(value) {
54946
54990
  }
54947
54991
  function readDaemonMetadata(kandownDir) {
54948
54992
  const path = metadataPath(kandownDir);
54949
- if (!existsSync4(path)) return null;
54993
+ if (!existsSync5(path)) return null;
54950
54994
  try {
54951
- return parseMetadata(JSON.parse(readFileSync4(path, "utf8")));
54995
+ return parseMetadata(JSON.parse(readFileSync5(path, "utf8")));
54952
54996
  } catch {
54953
54997
  return null;
54954
54998
  }
@@ -54956,7 +55000,7 @@ function readDaemonMetadata(kandownDir) {
54956
55000
  function removeDaemonMetadata(kandownDir) {
54957
55001
  try {
54958
55002
  const path = metadataPath(kandownDir);
54959
- if (existsSync4(path)) unlinkSync3(path);
55003
+ if (existsSync5(path)) unlinkSync4(path);
54960
55004
  } catch {
54961
55005
  }
54962
55006
  }
@@ -54970,9 +55014,10 @@ function isProcessAlive(pid) {
54970
55014
  }
54971
55015
  function parseRemoteDaemonInfo(value) {
54972
55016
  if (!isRecord(value)) return null;
54973
- const { ok, pid, kandownDir } = value;
55017
+ const { ok, pid, kandownDir, version } = value;
54974
55018
  if (ok !== true || typeof pid !== "number" || !Number.isInteger(pid) || typeof kandownDir !== "string") return null;
54975
- return { ok, pid, kandownDir };
55019
+ if (version !== null && typeof version !== "string" && version !== void 0) return null;
55020
+ return { ok, pid, kandownDir, version: typeof version === "string" ? version : null };
54976
55021
  }
54977
55022
  async function fetchDaemonInfo(port) {
54978
55023
  try {
@@ -54986,16 +55031,16 @@ async function fetchDaemonInfo(port) {
54986
55031
  }
54987
55032
  }
54988
55033
  function isPortListening(port, timeoutMs = 400) {
54989
- return new Promise((resolve3) => {
55034
+ return new Promise((resolve4) => {
54990
55035
  const socket = createConnection({ port, host: "127.0.0.1" }, () => {
54991
55036
  socket.destroy();
54992
- resolve3(true);
55037
+ resolve4(true);
54993
55038
  });
54994
- socket.on("error", () => resolve3(false));
55039
+ socket.on("error", () => resolve4(false));
54995
55040
  socket.setTimeout(timeoutMs);
54996
55041
  socket.on("timeout", () => {
54997
55042
  socket.destroy();
54998
- resolve3(false);
55043
+ resolve4(false);
54999
55044
  });
55000
55045
  });
55001
55046
  }
@@ -55014,7 +55059,7 @@ async function getDaemonStatus(kandownDir) {
55014
55059
  removeDaemonMetadata(kandownDir);
55015
55060
  return { running: false, metadata: null };
55016
55061
  }
55017
- return { running: true, metadata };
55062
+ return { running: true, metadata: { ...metadata, version: remote.version ?? metadata.version } };
55018
55063
  }
55019
55064
  async function waitForDaemon(kandownDir, timeoutMs = 8e3) {
55020
55065
  const started = Date.now();
@@ -55023,13 +55068,16 @@ async function waitForDaemon(kandownDir, timeoutMs = 8e3) {
55023
55068
  if (metadata && isProcessAlive(metadata.pid) && await isPortListening(metadata.port)) {
55024
55069
  return { running: true, metadata };
55025
55070
  }
55026
- await new Promise((resolve3) => setTimeout(resolve3, 120));
55071
+ await new Promise((resolve4) => setTimeout(resolve4, 120));
55027
55072
  }
55028
55073
  return { running: false, metadata: null };
55029
55074
  }
55030
55075
  async function startProjectDaemon(kandownDir, preferredPort) {
55031
55076
  const current = await getDaemonStatus(kandownDir);
55032
- if (current.running) return current;
55077
+ if (current.running) {
55078
+ if (current.metadata?.version === getCurrentVersion()) return current;
55079
+ await stopProjectDaemon(kandownDir);
55080
+ }
55033
55081
  const cliPath = process.argv[1];
55034
55082
  if (!cliPath) throw new Error("Cannot locate kandown CLI entrypoint");
55035
55083
  const args = [
@@ -55043,8 +55091,8 @@ async function startProjectDaemon(kandownDir, preferredPort) {
55043
55091
  if (typeof preferredPort === "number" && Number.isInteger(preferredPort)) {
55044
55092
  args.push("--port", String(preferredPort));
55045
55093
  }
55046
- const child = spawn(process.execPath, args, {
55047
- cwd: dirname2(kandownDir),
55094
+ const child = spawn2(process.execPath, args, {
55095
+ cwd: dirname3(kandownDir),
55048
55096
  detached: true,
55049
55097
  stdio: "ignore",
55050
55098
  env: { ...process.env, KANDOWN_DAEMON: "1" }
@@ -55083,7 +55131,7 @@ async function stopProjectDaemon(kandownDir) {
55083
55131
  }
55084
55132
  const started = Date.now();
55085
55133
  while (Date.now() - started < 2500 && isProcessAlive(pid)) {
55086
- await new Promise((resolve3) => setTimeout(resolve3, 100));
55134
+ await new Promise((resolve4) => setTimeout(resolve4, 100));
55087
55135
  }
55088
55136
  if (isProcessAlive(pid)) {
55089
55137
  try {
@@ -55096,9 +55144,9 @@ async function stopProjectDaemon(kandownDir) {
55096
55144
  }
55097
55145
 
55098
55146
  // src/cli/lib/file-watcher.ts
55099
- import { createReadStream, statSync } from "fs";
55147
+ import { createReadStream, statSync as statSync2 } from "fs";
55100
55148
  import { createHash } from "crypto";
55101
- import { join as join6 } from "path";
55149
+ import { join as join7 } from "path";
55102
55150
 
55103
55151
  // node_modules/.pnpm/chokidar@4.0.3/node_modules/chokidar/esm/index.js
55104
55152
  import { stat as statcb } from "fs";
@@ -55829,9 +55877,9 @@ var NodeFsHandler = class {
55829
55877
  if (this.fsw.closed) {
55830
55878
  return;
55831
55879
  }
55832
- const dirname5 = sysPath.dirname(file);
55880
+ const dirname6 = sysPath.dirname(file);
55833
55881
  const basename3 = sysPath.basename(file);
55834
- const parent = this.fsw._getWatchedDir(dirname5);
55882
+ const parent = this.fsw._getWatchedDir(dirname6);
55835
55883
  let prevStats = stats;
55836
55884
  if (parent.has(basename3))
55837
55885
  return;
@@ -55858,7 +55906,7 @@ var NodeFsHandler = class {
55858
55906
  prevStats = newStats2;
55859
55907
  }
55860
55908
  } catch (error) {
55861
- this.fsw._remove(dirname5, basename3);
55909
+ this.fsw._remove(dirname6, basename3);
55862
55910
  }
55863
55911
  } else if (parent.has(basename3)) {
55864
55912
  const at = newStats.atimeMs;
@@ -55954,7 +56002,7 @@ var NodeFsHandler = class {
55954
56002
  this._addToNodeFs(path, initialAdd, wh, depth + 1);
55955
56003
  }
55956
56004
  }).on(EV.ERROR, this._boundHandleError);
55957
- return new Promise((resolve3, reject) => {
56005
+ return new Promise((resolve4, reject) => {
55958
56006
  if (!stream)
55959
56007
  return reject();
55960
56008
  stream.once(STR_END, () => {
@@ -55963,7 +56011,7 @@ var NodeFsHandler = class {
55963
56011
  return;
55964
56012
  }
55965
56013
  const wasThrottled = throttler ? throttler.clear() : false;
55966
- resolve3(void 0);
56014
+ resolve4(void 0);
55967
56015
  previous.getChildren().filter((item) => {
55968
56016
  return item !== directory && !current.has(item);
55969
56017
  }).forEach((item) => {
@@ -56792,11 +56840,11 @@ function watch(paths, options = {}) {
56792
56840
 
56793
56841
  // src/cli/lib/file-watcher.ts
56794
56842
  function hashFile(filePath) {
56795
- return new Promise((resolve3, reject) => {
56843
+ return new Promise((resolve4, reject) => {
56796
56844
  const hash = createHash("sha256");
56797
56845
  const stream = createReadStream(filePath);
56798
56846
  stream.on("data", (chunk) => hash.update(chunk));
56799
- stream.on("end", () => resolve3(hash.digest("hex")));
56847
+ stream.on("end", () => resolve4(hash.digest("hex")));
56800
56848
  stream.on("error", reject);
56801
56849
  });
56802
56850
  }
@@ -56824,17 +56872,17 @@ var FileWatcher = class {
56824
56872
  */
56825
56873
  start(kandownDir) {
56826
56874
  const tasksDir = getTasksDir(kandownDir);
56827
- const configPath = join6(kandownDir, "kandown.json");
56875
+ const configPath = join7(kandownDir, "kandown.json");
56828
56876
  const existingIds = listTaskIds(kandownDir);
56829
56877
  for (const id of existingIds) {
56830
56878
  this.knownTaskIds.add(id);
56831
56879
  try {
56832
- const filePath = join6(tasksDir, `${id}.md`);
56880
+ const filePath = join7(tasksDir, `${id}.md`);
56833
56881
  this.taskHashes.set(id, hashFileSync(filePath));
56834
56882
  } catch {
56835
56883
  }
56836
56884
  }
56837
- this.watcher = watch([join6(tasksDir, "*.md"), configPath], {
56885
+ this.watcher = watch([join7(tasksDir, "*.md"), configPath], {
56838
56886
  ignoreInitial: true,
56839
56887
  awaitWriteFinish: { stabilityThreshold: 25, pollInterval: 25 },
56840
56888
  alwaysStat: true
@@ -56883,7 +56931,7 @@ var FileWatcher = class {
56883
56931
  handleFsEvent(event, filePath, kandownDir) {
56884
56932
  if (this.stopped) return;
56885
56933
  const tasksDir = getTasksDir(kandownDir);
56886
- const configPath = join6(kandownDir, "kandown.json");
56934
+ const configPath = join7(kandownDir, "kandown.json");
56887
56935
  if (filePath === configPath) {
56888
56936
  const key = `config:${event}`;
56889
56937
  const existing = this.debounceTimers.get(key);
@@ -56936,15 +56984,15 @@ var FileWatcher = class {
56936
56984
  async pollHashes(kandownDir) {
56937
56985
  if (this.stopped) return;
56938
56986
  const tasksDir = getTasksDir(kandownDir);
56939
- const configPath = join6(kandownDir, "kandown.json");
56987
+ const configPath = join7(kandownDir, "kandown.json");
56940
56988
  try {
56941
56989
  const newHash = hashFileSync(configPath);
56942
56990
  } catch {
56943
56991
  }
56944
56992
  for (const taskId of this.knownTaskIds) {
56945
- const filePath = join6(tasksDir, `${taskId}.md`);
56993
+ const filePath = join7(tasksDir, `${taskId}.md`);
56946
56994
  try {
56947
- statSync(filePath);
56995
+ statSync2(filePath);
56948
56996
  const newHash = await hashFile(filePath);
56949
56997
  const oldHash = this.taskHashes.get(taskId);
56950
56998
  if (oldHash !== void 0 && newHash !== oldHash) {
@@ -56959,7 +57007,7 @@ var FileWatcher = class {
56959
57007
  const currentIds = listTaskIds(kandownDir);
56960
57008
  for (const id of currentIds) {
56961
57009
  if (!this.knownTaskIds.has(id)) {
56962
- const filePath = join6(tasksDir, `${id}.md`);
57010
+ const filePath = join7(tasksDir, `${id}.md`);
56963
57011
  try {
56964
57012
  const newHash = await hashFile(filePath);
56965
57013
  this.knownTaskIds.add(id);
@@ -57134,9 +57182,9 @@ function buildPrompt(agentDoc, taskContent, taskId, kandownDir) {
57134
57182
  }
57135
57183
 
57136
57184
  // src/cli/lib/launcher.ts
57137
- import { execSync, spawn as spawn2 } from "child_process";
57138
- import { writeFileSync as writeFileSync2 } from "fs";
57139
- import { join as join7 } from "path";
57185
+ import { execSync as execSync2, spawn as spawn3 } from "child_process";
57186
+ import { writeFileSync as writeFileSync3 } from "fs";
57187
+ import { join as join8 } from "path";
57140
57188
  import { tmpdir } from "os";
57141
57189
  function isInTmux() {
57142
57190
  return !!process.env.TMUX;
@@ -57169,9 +57217,9 @@ function launchAgent(opts) {
57169
57217
  if (!taskMoved) {
57170
57218
  throw new Error(`Could not move task ${taskId} to In Progress \u2014 task file missing or unwritable.`);
57171
57219
  }
57172
- const contextFile = join7(tmpdir(), `kandown-${taskId}-context.md`);
57220
+ const contextFile = join8(tmpdir(), `kandown-${taskId}-context.md`);
57173
57221
  try {
57174
- writeFileSync2(contextFile, `${systemPrompt}
57222
+ writeFileSync3(contextFile, `${systemPrompt}
57175
57223
 
57176
57224
  ---
57177
57225
 
@@ -57193,7 +57241,7 @@ ${taskPrompt}`, "utf8");
57193
57241
  `KANDOWN_DIR=${shellescape(kandownDir)}`
57194
57242
  ].join(" ");
57195
57243
  try {
57196
- execSync(`tmux split-window -h -p 50 ${shellescape(`env ${envPrefix} ${shellCmd}`)}`, {
57244
+ execSync2(`tmux split-window -h -p 50 ${shellescape(`env ${envPrefix} ${shellCmd}`)}`, {
57197
57245
  stdio: "inherit"
57198
57246
  });
57199
57247
  } catch (e) {
@@ -57203,7 +57251,7 @@ ${taskPrompt}`, "utf8");
57203
57251
  } else {
57204
57252
  try {
57205
57253
  onBeforeExec?.();
57206
- const child = spawn2(binary, args, {
57254
+ const child = spawn3(binary, args, {
57207
57255
  stdio: "inherit",
57208
57256
  env: {
57209
57257
  ...process.env,
@@ -58361,7 +58409,7 @@ function Board({ kandownDir, version }) {
58361
58409
  if (input === "e") {
58362
58410
  const task = getFocusedTask();
58363
58411
  if (task) {
58364
- const taskPath = join8(getTasksDir(kandownDir), `${task.id}.md`);
58412
+ const taskPath = join9(getTasksDir(kandownDir), `${task.id}.md`);
58365
58413
  const editor = process.env.EDITOR || "nano";
58366
58414
  try {
58367
58415
  spawnSync(editor, [taskPath], { stdio: "inherit" });
@@ -58784,7 +58832,7 @@ async function run(screen, kandownDir, version) {
58784
58832
  });
58785
58833
  await instance.waitUntilExit();
58786
58834
  }
58787
- if (process.argv[1] === fileURLToPath2(import.meta.url)) {
58835
+ if (process.argv[1] === fileURLToPath3(import.meta.url)) {
58788
58836
  const screen = process.argv[2] || "board";
58789
58837
  const kandownDir = process.argv[3];
58790
58838
  const version = process.argv[4];
package/dist/index.html CHANGED
@@ -201,7 +201,7 @@ Error generating stack: `+_.message+`
201
201
 
202
202
  ## Subtasks
203
203
 
204
- `}}async function bH(t,e){try{return await(await(await(await t.getDirectoryHandle("archive",{create:!1})).getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}}async function ope(t,e){try{return await(await t.getDirectoryHandle("archive",{create:!1})).getFileHandle(`${e}.md`),!0}catch{return!1}}async function vH(t,e){try{return await t.getDirectoryHandle("archive",{create:e})}catch{return null}}async function cpe(t){if(Lt())return fS();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 Ps(t,e){if(Lt())try{const i=await gS(e);return Fg(i)}catch{return FL(e)}const a=await(async i=>{try{return await(await(await i.getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}})(t)??await bH(t,e);return a!==null?Fg(a):FL(e)}async function ppe(t,e){if(Lt())try{const a=await gS(e);return{ok:!0,task:Fg(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 bH(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:Fg(n)}}catch(a){return{ok:!1,reason:"corrupted",error:a}}}async function pu(t,e,n,a){const i=dS(n,a);if(Lt())return Xce(e,i);try{const o=await(await(await ope(t,e)?await vH(t,!0):t).getFileHandle(`${e}.md`,{create:!0})).createWritable();try{await o.write(i)}finally{await o.close()}}catch(r){throw Lh(r,`${e}.md`)}}async function zL(t,e){if(Lt())return Qce(e);try{await t.removeEntry(`${e}.md`)}catch{}try{await(await t.getDirectoryHandle("archive",{create:!1})).removeEntry(`${e}.md`)}catch{}}async function lpe(t,e){await gi(`/api/tasks/${encodeURIComponent(t)}/archive`,{method:"POST",body:e,headers:{"Content-Type":"text/plain"}})}async function dpe(t,e){await gi(`/api/tasks/${encodeURIComponent(t)}/unarchive`,{method:"POST",body:e,headers:{"Content-Type":"text/plain"}})}async function upe(t,e,n,a){const i=dS(n,a);if(Lt())return lpe(e,i);try{const o=await(await(await vH(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 Lh(r,`archive/${e}.md`)}}async function mpe(t,e,n,a){const i=dS(n,a);if(Lt())return dpe(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 Lh(r,`${e}.md`)}}const fpe="kanban-md",gpe=1,sp="recentProjects";function hS(){return new Promise((t,e)=>{const n=indexedDB.open(fpe,gpe);n.onerror=()=>e(n.error),n.onsuccess=()=>t(n.result),n.onupgradeneeded=()=>{const a=n.result;a.objectStoreNames.contains(sp)||a.createObjectStore(sp,{keyPath:"id"})}})}async function H2(t){const e=await hS();return new Promise((n,a)=>{const i=e.transaction(sp,"readwrite");i.objectStore(sp).put(t),i.oncomplete=()=>n(),i.onerror=()=>a(i.error)})}async function Fy(){const t=await hS();return new Promise((e,n)=>{const i=t.transaction(sp,"readonly").objectStore(sp).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 hpe(t){const e=await hS();return new Promise((n,a)=>{const i=e.transaction(sp,"readwrite");i.objectStore(sp).delete(t),i.oncomplete=()=>n(),i.onerror=()=>a(i.error)})}async function HL(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}}async function bpe(){try{return await(await gi("/api/update/check")).json()}catch{return null}}async function yH(){try{return await(await gi("/api/update/apply",{method:"POST"})).json()}catch{return null}}function _H(t=Ei){const e=t.board.columns;return e[e.length-1]??"Done"}function vpe(t,e=Ei){return t===_H(e)||t.toLowerCase()==="archived"}class ype{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;eventSource=null;start(e,n){this.dirHandle=e,this.tasksDirHandle=n,this.consecutiveErrors=0,this.disabled=!1,Lt()&&this.startServerSse(),e&&n&&(this.initHashes(),this.intervalId=setInterval(()=>void this.tick(),300))}startServerSse(){if(typeof window>"u"||!Lt()||this.eventSource)return;const e=window.__KANDOWN_TOKEN__,n=e?`/api/events?token=${encodeURIComponent(e)}`:"/api/events";try{this.eventSource=new EventSource(n),this.eventSource.onmessage=a=>{try{const i=JSON.parse(a.data);i.type==="change"&&(i.taskId?this.emit("taskChanged",i.taskId):(this.emit("configChanged"),this.emit("taskChanged","")))}catch{}}}catch(a){console.warn("[Watcher] EventSource init failed:",a)}}stop(){this.eventSource&&(this.eventSource.close(),this.eventSource=null),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 BL(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 BL(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 UL(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 UL(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 BL(t){try{return await(await(await t.getFileHandle("kandown.json")).getFile()).text()}catch{return null}}async function UL(t,e){try{return await(await(await t.getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}}const Lo=new ype,_pe={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 qL=null;function kH(){return"Notification"in window?Notification.permission:"unsupported"}async function kpe(){return"Notification"in window?await Notification.requestPermission():"unsupported"}function B2({title:t,body:e,config:n}){if(n.notifications.webhookUrl&&fetch(n.notifications.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({event:"kandown_notification",title:t,body:e,timestamp:new Date().toISOString()})}).catch(()=>{}),n.notifications.sound&&wpe(n.notifications.soundId),!(!n.notifications.browser||kH()!=="granted"))try{new Notification(t,{body:e})}catch{}}function wpe(t){const e=_pe[t],n=window.AudioContext??window.webkitAudioContext;if(!(!n||e.length===0))try{qL??=new n;const a=qL;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 jf(t,e={}){const{maxAttempts:n=3,baseDelayMs:a=500,retryableCheck:i=Zce}=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 xpe(t){const e=t.toLowerCase(),n=new Date;if(e==="today")return n.toISOString().split("T")[0];if(e==="tomorrow"){const i=new Date(n);return i.setDate(i.getDate()+1),i.toISOString().split("T")[0]}const a=["sunday","monday","tuesday","wednesday","thursday","friday","saturday"];if(a.includes(e)){const i=a.indexOf(e),r=new Date(n);let s=i-r.getDay();return s<=0&&(s+=7),r.setDate(r.getDate()+s),r.toISOString().split("T")[0]}return t}function Ape(t){let e=t.trim(),n;const a=[];let i,r;const s=[];return e=e.replace(/(?:^|\s)p([1-4])(?:\s|$)/i,(c,p)=>(n=`P${p}`," ")),e=e.replace(/(?:^|\s)#([a-zA-Z0-9_-]+)/g,(c,p)=>(a.push(p.toLowerCase())," ")),e=e.replace(/(?:^|\s)@([a-zA-Z0-9_-]+)/g,(c,p)=>(i=p," ")),e=e.replace(/(?:^|\s)due:([^\s]+)/i,(c,p)=>(r=xpe(p)," ")),e=e.replace(/(?:^|\s)\+([a-zA-Z0-9_-]+)/g,(c,p)=>(s.push(p)," ")),{title:e.replace(/\s+/g," ").trim()||t,...n?{priority:n}:{},...a.length>0?{tags:a}:{},...i?{assignee:i}:{},...r?{due:r}:{},...s.length>0?{depends_on:s}:{}}}const Cpe=/^[A-Za-z0-9_-]+$/;function U2(t){if(!t)return null;const e=t.trim().replace(/^\/+|\/+$/g,"").replace(/^#/,"");return!e||e.includes("/")||e.includes(".")||!Cpe.test(e)?null:/^\d+$/.test(e)?`t${e}`:e}function Npe(t){const e=new URLSearchParams(t.search).get("p");if(!e)return null;const[n]=e.split("/").filter(Boolean);return n||null}function zy(t){const e=new URLSearchParams(t.search),n=U2(e.get("task"));if(n)return n;const a=e.get("p");if(a){const[,...r]=a.split("/").filter(Boolean),s=U2(r.join("/"));if(s)return s}return U2(decodeURIComponent(t.pathname))}function T3(t){return t?`/?p=${encodeURIComponent(t)}`:"/"}function bS(t,e){const n=t.replace(/^t(?=\d+$)/,""),a=`/${encodeURIComponent(n)}`;return e?`${a}?p=${encodeURIComponent(e)}`:a}function Hy(t,e=!1){typeof window>"u"||`${window.location.pathname}${window.location.search}`===t||(e?window.history.replaceState({},"",t):window.history.pushState({},"",t),window.dispatchEvent(new Event("kandown:urlchange")))}function Iv(t){typeof window<"u"&&zy(window.location)||Hy(T3(t))}function Spe(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 $pe(){const t=await fS();return await Promise.all(t.map(async n=>{const{frontmatter:a,body:i}=await hH(n),r={...a,id:a.id||n,status:a.status||"Backlog"},{subtasks:s,bodyWithoutSubtasks:o}=Rs(i);return{id:n,frontmatter:r,body:o,subtasks:s}}))}async function Epe(t){const e=await cpe(t),n=await Promise.all(e.map(async r=>{const s=await ppe(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}=Rs(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 VL(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 Ps(t,c.id);await pu(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 pl(t){mH(t.ui.theme,t.ui.skin,t.ui.font,t.ui.background)}function P3(t){return{title:t.frontmatter.title||t.id,status:t.frontmatter.status||"Backlog",body:t.body,subtasks:t.subtasks}}function q2(t){yu.clear(),t.forEach(e=>{yu.set(e.id,P3(e))})}function Tpe(t){const e=t.split(/[\\/]+/).filter(Boolean),n=e[e.length-1];return n===".kandown"?e[e.length-2]??"Project":n??"Project"}function Ppe(t,e){return e.reduce((n,a,i)=>{const r=t[i]?.done??!1;return n+(a.done&&!r?1:0)},0)}function Mpe(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 Lpe=0;const yu=new Map,Df=new Map;let V2=null;const se=Lce((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:Ei,recentProjects:[],toasts:[],agentHook:null,isReloading:!1,lastReloadError:null,failedTaskIds:[],watcherError:null,hasUnsavedDrawerEdits:!1,lastSaveError:null,drawerRecoveryData:new Map,selectedTaskIds:[],toggleTaskSelection:n=>{t(a=>({selectedTaskIds:a.selectedTaskIds.includes(n)?a.selectedTaskIds.filter(s=>s!==n):[...a.selectedTaskIds,n]}))},clearTaskSelection:()=>t({selectedTaskIds:[]}),bulkMoveTasks:async n=>{const{selectedTaskIds:a,columns:i,moveTask:r}=e();for(const s of a){let o="Backlog";for(const c of i)if(c.tasks.some(p=>p.id===s)){o=c.name;break}await r(s,o,n)}t({selectedTaskIds:[]})},bulkDeleteTasks:async()=>{const{selectedTaskIds:n,deleteTask:a}=e();for(const i of n)await a(i);t({selectedTaskIds:[]})},drawerBaseVersion:null,conflictState:null,showConflictModal:!1,openFolder:async()=>{if(!fH()){const c=navigator.userAgent||"this browser";e().toast(new Vce(c).message,"error",8e3);return}let n;try{n=await npe()}catch(c){c instanceof mS?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}),Iv(s);const o=Lt()?z2():null;try{await H2({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 Fy();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 HL(n.handle,!0)){let r=!1;try{await Oy(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 hpe(n.id);const s=await Fy();t({recentProjects:s})}catch{}return}e().toast("Permission denied — please grant access to the folder","error");return}try{const r=await Oy(n.handle),s=await E3(n.handle),o=n.handle.name;t({dirHandle:r,tasksDirHandle:s,projectName:o}),Iv(o);try{await H2({...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=z2();if(!n)throw new Error("No server root");await OL();const a=Tpe(n),i=await gH();pl(i);const r=await fS(),s=await Promise.all(r.map(async f=>{const{frontmatter:h,body:v}=await hH(f),k={...h,id:h.id||f,status:h.status||"Backlog"},{subtasks:w,bodyWithoutSubtasks:A}=Rs(v);return{id:f,frontmatter:k,body:A,subtasks:w}}));q2(s);const o=s.map(f=>({frontmatter:f.frontmatter,body:Wd(f.body,f.subtasks)})),c=R2(o,i.board.columns),p=O2(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}),Iv(a),e().setupWatcher(),e().refreshAgentHook()}catch{t({loading:!1,isOpen:!1}),e().toast("Impossible de charger le projet. Relancez `kandown`.","error")}},tryAutoOpenServerProject:async()=>{if(!Lt())return;const n=z2();if(!n)return;await OL();const a=await Fy(),i=a.find(p=>p.kandownDir===n);if(!i){await e().openServerProject();return}if(!await HL(i.handle,!0)){await e().openServerProject();return}const s=await Oy(i.handle),o=await E3(i.handle),c=i.handle.name;t({dirHandle:s,tasksDirHandle:o,projectName:c,recentProjects:a,isOpen:!0}),Iv(c),await H2({...i,lastOpened:Date.now()}),await e().loadConfig(),await e().reloadBoard(),e().setupWatcher()},loadConfig:async()=>{const{dirHandle:n}=e();if(!(!n&&!Lt()))try{const a=await ape(n);if(a.ok){t({config:a.config}),pl(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:Ei}),pl(Ei)}catch{t({config:Ei}),pl(Ei)}},updateConfig:async n=>{const{dirHandle:a,config:i}=e();if(!a&&!Lt())return;const r=n(i);t({config:r}),pl(r);try{await ipe(a,r)}catch(s){const o=s;o instanceof To?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(Lt()){const i=await $pe();q2(i);const r=i.map(d=>({frontmatter:d.frontmatter,body:Wd(d.body,d.subtasks)})),s=R2(r,a.board.columns),o=O2(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 Epe(n);q2(i);const s=i.map(m=>({frontmatter:m.frontmatter,body:Wd(m.body,m.subtasks)})),o=R2(s,a.board.columns),c=O2(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=Lt();if(!d&&!e().tasksDirHandle)return;const m=s.find(T=>T.name===a),f=s.find(T=>T.name===i);if(!m||!f)return;const h=m.tasks.findIndex(T=>T.id===n);if(h===-1)return;const v=m.tasks[h];if(!v)return;if(vpe(i,o)){const T=new Map,M=_H(o).toLowerCase();for(const D of s)for(const I of D.tasks){const H=I.frontmatter&&(I.frontmatter.archived===!0||I.frontmatter.archived==="true");T.set(I.id,{exists:!0,resolved:H||I.id===n||D.name.toLowerCase()===M})}const j=[];for(const D of v.dependsOn){if(typeof D!="string"||!D.trim()||D===n)continue;const I=T.get(D);(!I||!I.resolved)&&j.push(D)}if(j.length>0){const D=j.length===1?j[0]:`${j.slice(0,-1).join(", ")} and ${j[j.length-1]}`;e().toast(`Cannot move ${n} to ${i}: blocked by ${D}`,"error");return}}const k=s.map(T=>({...T,tasks:[...T.tasks]})),w=k.find(T=>T.name===a),A=k.find(T=>T.name===i),[$]=w.tasks.splice(h,1);/done|termin|closed|complet/i.test(i)?$.checked=!0:$.checked=!1,r!==void 0?A.tasks.splice(r,0,$):A.tasks.push($),t({columns:k});try{const{tasksDirHandle:T}=e();if(!T&&!d)return;const M=a===i?k.filter(D=>D.name===i):k.filter(D=>D.name===a||D.name===i),{failedIds:j}=await jf(()=>VL(T??null,M,o.board.columns),{maxAttempts:3});if(j.length>0){const D=j.length===1?`Could not save move for ${j[0]}`:`${j.length} tasks could not be moved`;e().toast(D,"warning",8e3),await e().reloadBoard()}}catch(T){const M=T;M instanceof To?e().toast("Disk is full — move was not saved. Free up space and try again.","error",8e3):e().toast("Failed to save: "+M.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&&!Lt())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(),v=Lt();if(!h&&!v)return;const{failedIds:k}=await jf(()=>VL(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 v=h;v instanceof To?e().toast("Disk is full — reorder was not saved.","error",8e3):e().toast("Failed to save: "+v.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,v)=>{const{frontmatter:k,body:w}=await Ps(s,h.id);await pu(s,h.id,{...k,id:h.id,status:i,order:v},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 v=m.board.columns.some(k=>k.toLowerCase()===n.toLowerCase())?m.board.columns:[...m.board.columns,n];return{...m,board:{...m.board,columns:v.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})}},reorderColumns:async(n,a)=>{const{config:i,columns:r,tasksDirHandle:s}=e();if(n<0||a<0||n>=r.length||a>=r.length||n===a||!s&&!Lt())return;const o=Array.from(r),[c]=o.splice(n,1);o.splice(a,0,c),t({columns:o});const p=o.map(d=>d.name);try{await e().updateConfig(d=>({...d,board:{...d.board,columns:p}})),await e().reloadBoard()}catch(d){e().toast("Failed to reorder columns: "+d.message,"error"),t({columns:r})}},deleteColumn:async n=>{const{columns:a,tasksDirHandle:i}=e();if(!i&&!Lt())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=>zL(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,a)=>{const{columns:i,tasksDirHandle:r,config:s,taskContents:o,searchMatches:c}=e();if(!r&&!Lt()||!i.length)return null;const p=n||s.board.columns[0]||i[0].name,d=Spe(i),m=i.find($=>$.name===p)?.tasks.length??0,f=a?Ape(a):null,h={id:d,title:f?.title||"",checked:!1,dependsOn:f?.depends_on||[],tags:f?.tags||[],assignee:f?.assignee||null,priority:f?.priority||(s.fields.priority?s.board.defaultPriority:null),ownerType:s.fields.ownerType?s.board.defaultOwnerType:"",progress:null,frontmatter:{}},v=i.map($=>$.name===p?{...$,tasks:[...$.tasks,h]}:$),k=new Map(o),w={id:d,title:f?.title||"",status:p,order:m,priority:f?.priority||(s.fields.priority?s.board.defaultPriority:""),tags:f?.tags||[],assignee:f?.assignee||"",due:f?.due||"",depends_on:f?.depends_on||[],created:new Date().toISOString().slice(0,10),ownerType:s.fields.ownerType?s.board.defaultOwnerType:"",tools:""},A="";k.set(d,{frontmatter:w,subtasks:[],body:A}),t({columns:v,taskContents:k});try{const $=r||null;return await jf(()=>pu($,d,w,A),{maxAttempts:3}),e().toast(`Created ${d.replace(/^t/,"")}`),await e().openDrawer(d),d}catch($){const T=$;return T instanceof To?e().toast("Disk is full — task was not created.","error",8e3):e().toast("Failed to create: "+T.message,"error"),t({columns:i,taskContents:o,searchMatches:c}),null}},deleteTask:async n=>{const{columns:a,tasksDirHandle:i,taskContents:r,searchMatches:s}=e();if(!i&&!Lt())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 zL(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&&!Lt()))try{const{frontmatter:i,body:r}=await Ps(a||null,n);await upe(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 To?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&&!Lt()))try{const{frontmatter:i,body:r}=await Ps(a||null,n),s={...i};delete s.archived,await mpe(a||null,n,s,r),await e().reloadBoard(),e().toast("Restored")}catch(i){const r=i;r instanceof To?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,a={})=>{const{tasksDirHandle:i,projectName:r}=e();if(!(!i&&!Lt()))try{const{frontmatter:s,body:o}=await Ps(i,n),{subtasks:c,bodyWithoutSubtasks:p}=Rs(o),d={frontmatter:s,subtasks:c,body:p,savedAt:Date.now()},m=e().drawerRecoveryData.get(n),f=m?{frontmatter:m.frontmatter,subtasks:m.subtasks,body:m.body}:{frontmatter:s,subtasks:c,body:p},h=new Map(e().drawerRecoveryData);h.delete(n),a.syncUrl!==!1&&Hy(bS(n,r),a.replace),t({drawerTaskId:n,drawerData:f,drawerBaseVersion:d,conflictState:null,showConflictModal:!1,hasUnsavedDrawerEdits:!!m,lastSaveError:null,drawerRecoveryData:h}),m&&e().toast("Restored your unsaved edits for this task","info")}catch(s){e().toast("Failed to open: "+s.message,"error")}},closeDrawer:(n={})=>{n.syncUrl!==!1&&Hy(T3(e().projectName),n.replace),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=Wd(a.body,a.subtasks),o={...a.frontmatter,id:n};try{await jf(()=>pu(i||null,n,o,s),{maxAttempts:3}),e().toast("Saved");const c=new Map(e().drawerRecoveryData);c.delete(n),Hy(T3(e().projectName)),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 To?"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=Wd(a.body,a.subtasks),o={...a.frontmatter,id:n};await jf(()=>pu(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 To?"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(!Lt()){t({agentHook:null});return}const n=await Jce();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 epe(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 Ps(a,r),{subtasks:c,bodyWithoutSubtasks:p}=Rs(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=lH(o,r);c.length>0&&i.set(s,c)}t({searchMatches:i})},toast:(n,a="success",i)=>{const r=++Lpe,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 Ps(r,s),{subtasks:d,bodyWithoutSubtasks:m}=Rs(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=Wd(i.body,i.subtasks),p={...i.frontmatter,id:s};try{await pu(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(Lt()){V2&&clearInterval(V2),V2=setInterval(()=>{e().reloadBoard()},2e3);return}const{dirHandle:n,tasksDirHandle:a}=e();if(!n||!a)return;Lo.stop(),Df.forEach(s=>clearTimeout(s)),Df.clear(),Lo.start(n,a);const i=(s,o)=>{const c=Df.get(s);c&&clearTimeout(c);const p=Math.max(2e3,e().config.notifications.editDebounceMs),d=setTimeout(()=>{Df.delete(s);const m=e().config;m.notifications.taskEdits&&B2({title:"Task edited",body:`${o} changed on disk.`,config:m})},p);Df.set(s,d)},r=async s=>{const{tasksDirHandle:o,config:c}=e();if(!o)return;let p,d;try{({frontmatter:p,body:d}=await Ps(o,s))}catch(A){console.warn(`[Watcher] notifyTaskChange: failed to read ${s}:`,A);return}const{subtasks:m,bodyWithoutSubtasks:f}=Rs(d),h={id:s,frontmatter:{...p,id:p.id||s,status:p.status||"Backlog"},body:f,subtasks:m},v=P3(h),k=yu.get(s);if(!k){yu.set(s,v);return}c.notifications.statusChanges&&k.status!==v.status&&B2({title:"Task status changed",body:`${v.title}: ${k.status} → ${v.status}`,config:c});const w=Ppe(k.subtasks,v.subtasks);c.notifications.subtaskCompletions&&w>0&&B2({title:"Subtask completed",body:w===1?`${v.title}: 1 subtask completed.`:`${v.title}: ${w} subtasks completed.`,config:c}),Mpe(k,v)&&i(s,v.title),yu.set(s,v)};Lo.on("configChanged",()=>{try{e().loadConfig(),e().toast("Settings updated externally","info")}catch(s){console.error("[Watcher] configChanged handler error:",s)}}),Lo.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 Ps(p,s))}catch(T){console.warn(`[Watcher] taskChanged: failed to re-read ${s}:`,T);return}const{subtasks:f,bodyWithoutSubtasks:h}=Rs(m),v=c,k=JSON.stringify(v.frontmatter)!==JSON.stringify(d),w=v.body!==h,A=JSON.stringify(v.subtasks)!==JSON.stringify(f);if(!k&&!w&&!A)return;let $="none";k&&(w||A)?$="full":k?$="metadata-only":(w||A)&&($="body-only"),t({conflictState:{taskId:s,type:$,local:v,remote:{frontmatter:d,body:h,subtasks:f}},showConflictModal:$==="full"})}else await e().reloadBoard()}catch(o){console.error(`[Watcher] taskChanged handler error for ${s}:`,o)}}),Lo.on("newTaskDetected",async s=>{try{const{tasksDirHandle:o}=e();if(o){let c,p;try{({frontmatter:c,body:p}=await Ps(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}=Rs(p);yu.set(s,P3({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)}}),Lo.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"))}}));Fy().then(t=>{se.setState({recentProjects:t})}).catch(t=>{console.warn("[Store] Failed to hydrate recent projects:",t)});pl(Ei);window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{pl(se.getState().config)});const jpe=Object.freeze(Object.defineProperty({__proto__:null,useStore:se},Symbol.toStringTag,{value:"Module"}));function Dpe({icon:t,value:e,label:n,isActive:a,onClick:i,layoutId:r}){return b.jsxs("button",{type:"button",className:T1("relative flex size-8 cursor-default items-center justify-center rounded-full transition-all [&_svg]:size-4",a?"text-zinc-950 dark:text-zinc-50":"text-zinc-400 hover:text-zinc-950 dark:text-zinc-500 dark:hover:text-zinc-50"),role:"radio","aria-checked":a,"aria-label":`Switch to ${n} theme`,onClick:()=>i(e),children:[t,a&&b.jsx(nt.div,{layoutId:r,transition:{type:"spring",bounce:.3,duration:.6},className:"absolute inset-0 rounded-full border border-zinc-200 dark:border-zinc-700"})]})}const Ipe=[{icon:b.jsx(yce,{}),value:"auto",label:"system"},{icon:b.jsx(Sce,{}),value:"light",label:"light"},{icon:b.jsx(kce,{}),value:"dark",label:"dark"}];function Rpe({value:t,onChange:e,className:n}){const a=se(d=>d.config.ui.theme),i=se(d=>d.updateConfig),r=`theme-option-${S.useId()}`,[s,o]=S.useState(!1);S.useEffect(()=>{o(!0)},[]);const c=t??a,p=e??(d=>{i(m=>({...m,ui:{...m.ui,theme:d}}))});return s?b.jsx(nt.div,{initial:{opacity:0},animate:{opacity:1},transition:{duration:.3},className:T1("inline-flex items-center overflow-hidden rounded-full bg-white ring-1 ring-zinc-200 ring-inset dark:bg-zinc-950 dark:ring-zinc-700",n),role:"radiogroup","aria-label":"Theme mode",children:Ipe.map(d=>b.jsx(Dpe,{icon:d.icon,value:d.value,label:d.label,isActive:c===d.value,onClick:p,layoutId:r},d.value))},String(s)):b.jsx("div",{className:T1("flex h-8 w-24",n)})}function Ope(t){const e=soe(t,{stiffness:180,damping:22,mass:.8});return S.useEffect(()=>{e.set(t)},[t,e]),Uz(e,a=>Math.round(a).toString())}const M3="0.33.3";function wH({className:t="w-6 h-6"}){return b.jsxs("svg",{viewBox:"0 0 150 150",className:t,version:"1.1",xmlns:"http://www.w3.org/2000/svg",xmlnsXlink:"http://www.w3.org/1999/xlink",style:{fillRule:"evenodd",clipRule:"evenodd",strokeLinejoin:"round",strokeMiterlimit:2},children:[b.jsx("path",{d:"M148,21.594L148,128.406C148,139.22 139.22,148 128.406,148L21.594,148C10.78,148 2,139.22 2,128.406L2,21.594C2,10.78 10.78,2 21.594,2C139.22,2 148,10.78 148,21.594Z",style:{fill:"url(#_KandownLinear1)"}}),b.jsxs("g",{transform:"matrix(0.823551,0,0,0.823551,12.830775,11.282318)",children:[b.jsx("g",{transform:"matrix(1.620982,0,0,1.566239,-41.718998,-41.628205)",children:b.jsx("path",{d:"M56.491,62.601C57.2,61.897 57.602,60.925 57.606,59.908C57.627,53.936 57.7,33.2 57.7,33.2C57.6,29.7 55,27.6 52,27.6L42.075,27.6C38.544,27.6 35.673,30.547 35.644,34.201C35.528,48.996 35.327,83.484 35.956,83.013C35.956,83.013 52.072,66.994 56.491,62.601Z",style:{fill:"rgb(252,255,239)",fillRule:"nonzero"}})}),b.jsx("g",{transform:"matrix(1.566239,0,0,1.566239,-39.774573,-41.628205)",children:b.jsx("path",{d:"M87.6,43.8C84.2,43.9 80.5,45.1 77.7,47.5L39.2,86.2C37.1,88.3 35.8,91 35.7,93.7L35.7,116.574C35.7,117.219 36.088,117.802 36.683,118.051C37.278,118.3 37.965,118.167 38.425,117.714C50.467,105.842 98.836,58.16 110.608,46.555C111.047,46.122 111.185,45.468 110.956,44.894C110.728,44.321 110.178,43.94 109.561,43.928C106.663,43.871 103.089,43.8 103.089,43.8L87.6,43.8Z",style:{fill:"rgb(241,255,184)",fillRule:"nonzero"}})}),b.jsx("g",{transform:"matrix(0.766318,0.766329,-1.302872,1.302853,170.442469,-47.541879)",children:b.jsx("path",{d:"M38.038,101.45C35.808,101.45 34,100.387 34,99.075C34,95.326 34,88.102 34,84.58C34,84.042 34.364,83.525 35.011,83.145C35.658,82.764 36.536,82.55 37.452,82.55C46.372,82.55 69.5,82.55 69.5,82.55C69.5,82.55 69.5,77.52 69.5,74.389C69.5,73.947 69.936,73.545 70.617,73.359C71.298,73.173 72.101,73.236 72.675,73.521C80.028,77.17 98.075,86.125 104.279,89.204C104.832,89.479 105.168,89.876 105.206,90.301C105.245,90.727 104.982,91.143 104.481,91.45C98.972,94.832 83.072,104.591 75.013,109.539C74.195,110.041 72.946,110.206 71.841,109.959C70.737,109.712 69.99,109.101 69.946,108.406C69.744,105.262 69.5,101.45 69.5,101.45L38.038,101.45Z",style:{fill:"rgb(136,225,56)"}})})]}),b.jsx("defs",{children:b.jsxs("linearGradient",{id:"_KandownLinear1",x1:"0",y1:"0",x2:"1",y2:"0",gradientUnits:"userSpaceOnUse",gradientTransform:"matrix(62,131,-131,62,9,8)",children:[b.jsx("stop",{offset:"0",style:{stopColor:"rgb(12,29,23)",stopOpacity:1}}),b.jsx("stop",{offset:"1",style:{stopColor:"rgb(24,41,35)",stopOpacity:1}})]})})]})}function Fpe(){const{t}=Tn(),e=se(B=>B.dirHandle),n=se(B=>B.isOpen),a=se(B=>B.projectName),i=se(B=>B.closeDrawer),r=se(B=>B.drawerTaskId),s=se(B=>B.saveDrawer),o=se(B=>B.archivedTasks.length),c=se(B=>B.showArchives),p=se(B=>B.setShowArchives),d=se(B=>B.columns),m=se(B=>B.openFolder),f=se(B=>B.reloadBoard),h=se(B=>B.createTask),v=se(B=>B.setCommandOpen),k=se(B=>B.viewMode),w=se(B=>B.setViewMode),A=se(B=>B.density),$=se(B=>B.setDensity),T=se(B=>B.setCurrentPage),M=se(B=>B.recentProjects),j=se(B=>B.openRecentProject),D=se(B=>B.filters),I=se(B=>B.setFilter),H=se(B=>B.clearFilters),R=se(B=>B.config.fields),L=se(B=>B.lastReloadError),G=se(B=>B.watcherError),Y=se(B=>B.restartWatcher),[ne,U]=S.useState(!1),[F,V]=S.useState(!0),Q=S.useRef(null),J=S.useRef(null),oe=d.reduce((B,ae)=>B+ae.tasks.length,0),O=Ope(oe),Z=[];R.priority&&D.priority&&Z.push({type:"priority",label:D.priority,value:D.priority}),R.tags&&D.tag&&Z.push({type:"tag",label:"#"+D.tag,value:D.tag}),R.assignee&&D.assignee&&Z.push({type:"assignee",label:"@"+D.assignee,value:D.assignee});const K=[{label:t("filterBar.ownerAll"),value:""},{label:t("filterBar.ownerHuman"),value:"human"},{label:t("filterBar.ownerAI"),value:"ai"}],E=Z.length>0||D.search||R.ownerType&&D.ownerType;return S.useEffect(()=>{if(!ne)return;const B=ae=>{Q.current&&!Q.current.contains(ae.target)&&U(!1)};return document.addEventListener("mousedown",B),()=>document.removeEventListener("mousedown",B)},[ne]),S.useEffect(()=>{const B=setTimeout(()=>V(!1),5e3);return()=>clearTimeout(B)},[]),S.useEffect(()=>{document.title=a?`${a} · Kandown`:"Kandown"},[a]),b.jsxs(b.Fragment,{children:[(L||G)&&b.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:[b.jsx("span",{className:"flex-1 truncate",children:G?`⚠ ${G}`:`⚠ ${L}`}),G&&b.jsx("button",{type:"button",onClick:Y,className:"px-2 py-0.5 rounded bg-amber-500/20 hover:bg-amber-500/30 text-[11.5px] font-medium",children:"Restart watcher"}),b.jsx("button",{type:"button",onClick:f,className:"px-2 py-0.5 rounded bg-amber-500/20 hover:bg-amber-500/30 text-[11.5px] font-medium",children:"Reload"})]}),b.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:[b.jsxs("div",{className:"flex items-center gap-4 min-w-0",children:[b.jsxs("div",{className:"flex items-center gap-2.5 flex-shrink-0",children:[b.jsxs("button",{onClick:()=>{r?s():i({replace:!0})},className:"flex items-center gap-2 cursor-pointer",children:[b.jsx(wH,{className:"w-[34px] h-[34px] dark:text-white text-black"}),b.jsx(Wa,{mode:"wait",initial:!1,children:F?b.jsxs(nt.span,{className:"flex items-center gap-2",initial:{opacity:0},animate:{opacity:1},exit:{opacity:0},transition:{duration:.45},children:[b.jsx("span",{className:"text-[15px] font-semibold tracking-tight text-fg",children:"kandown"}),b.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",M3]})]},"boot"):a?b.jsx(nt.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})]}),r&&b.jsxs("button",{type:"button",onClick:()=>{s()},className:"hidden md:inline-flex items-center gap-2 rounded-xl border border-border bg-bg px-3 py-2 text-[13px] font-semibold text-fg shadow-sm transition-colors hover:border-border-strong hover:bg-bg-2",title:t("taskWorkspace.backToCards"),children:[b.jsx(Wn.ArrowLeft,{size:15}),b.jsx("span",{children:t("common.back")})]})]}),e&&b.jsxs(b.Fragment,{children:[b.jsx("div",{className:"w-px h-[20px] bg-black/[0.08] dark:bg-white/[0.08] flex-shrink-0"}),b.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:[b.jsx(Wn.Search,{size:14,className:"text-fg-muted/60 flex-shrink-0"}),b.jsx("input",{ref:J,type:"text",placeholder:t("filterBar.searchPlaceholder"),value:D.search,onChange:B=>I("search",B.target.value),className:"bg-transparent border-none outline-none text-fg text-[13px] w-full placeholder:text-fg-muted/60"}),D.search?b.jsx("button",{onClick:()=>I("search",""),className:"text-fg-muted/60 hover:text-fg flex-shrink-0",children:b.jsx(Wn.X,{size:14})}):b.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"})]}),b.jsxs("div",{className:"flex items-center gap-1.5 flex-wrap overflow-hidden",children:[b.jsx(Wa,{children:Z.map(B=>b.jsxs(nt.button,{initial:{opacity:0,scale:.9},animate:{opacity:1,scale:1},exit:{opacity:0,scale:.9},transition:{duration:.15},onClick:()=>I(B.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:[B.label,b.jsx(Wn.X,{size:10,className:"text-fg-muted/60"})]},B.type+B.value))}),R.ownerType&&b.jsx("div",{className:"flex items-center h-6 border border-black/[0.06] dark:border-white/[0.1] rounded-lg overflow-hidden",children:K.map(B=>b.jsx("button",{onClick:()=>I("ownerType",B.value),className:`h-full px-2.5 text-[12px] transition-colors ${D.ownerType===B.value?"bg-black/[0.06] dark:bg-white/[0.12] text-fg":"text-fg-muted/70 hover:text-fg"}`,children:B.label},B.value))}),E&&b.jsx("button",{onClick:H,className:"text-[12px] text-fg-muted/60 hover:text-fg transition-colors",children:t("filterBar.clearAll")})]})]}),e&&b.jsxs(b.Fragment,{children:[b.jsx("div",{className:"w-px h-[20px] bg-black/[0.08] dark:bg-white/[0.08] flex-shrink-0"}),b.jsxs("div",{className:"relative flex-shrink-0",ref:Q,children:[b.jsxs("button",{onClick:()=>U(B=>!B),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:[b.jsx(Wn.Folder,{size:13,className:"text-fg-muted/70"}),b.jsxs("span",{className:"font-medium",children:[".",e.name]}),b.jsx(Wn.ChevronDown,{size:11,className:"opacity-50"})]}),b.jsx(Wa,{children:ne&&b.jsx(nt.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:b.jsxs("div",{className:"py-1.5",children:[M.length>0&&b.jsxs(b.Fragment,{children:[b.jsx("div",{className:"px-3 py-1.5 text-[11px] font-semibold uppercase tracking-wider text-fg-muted/60",children:t("header.recentProjects")}),M.map(B=>b.jsxs("button",{onClick:()=>{U(!1),j(B)},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:[b.jsx(Wn.Folder,{size:12,className:"text-fg-muted/60"}),b.jsx("span",{className:"truncate",children:B.name}),B.id===e.name&&b.jsx(Wn.Check,{size:12,className:"ml-auto text-emerald-500"})]},B.id)),b.jsx("div",{className:"h-px bg-black/[0.06] dark:bg-white/[0.08] my-1.5 mx-2"})]}),b.jsxs("button",{onClick:()=>{U(!1),m()},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:[b.jsx(Wn.Plus,{size:12,className:"text-fg-muted/60"}),b.jsx("span",{children:t("header.openFolder...")})]})]})})})]})]})]}),b.jsx("div",{className:"flex items-center gap-2 flex-shrink-0",children:n||e?b.jsxs(b.Fragment,{children:[b.jsxs("div",{className:"flex items-center gap-2 mr-2 text-[12.5px] text-fg-muted/70",children:[b.jsx("span",{className:"inline-block w-1.5 h-1.5 rounded-full bg-emerald-500"}),b.jsx(nt.span,{className:"tabular-nums font-medium",children:O}),b.jsx("span",{children:t("header.tasks")})]}),b.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:[b.jsx("button",{onClick:()=>w("board"),className:`w-9 h-9 inline-flex items-center justify-center rounded-lg transition-all ${k==="board"?"bg-card text-fg shadow-sm":"text-fg-muted/70 hover:text-fg"}`,title:t("common.board"),children:b.jsx(Wn.LayoutBoard,{size:18})}),b.jsx("button",{onClick:()=>w("list"),className:`w-9 h-9 inline-flex items-center justify-center rounded-lg transition-all ${k==="list"?"bg-card text-fg shadow-sm":"text-fg-muted/70 hover:text-fg"}`,title:t("common.list"),children:b.jsx(Wn.LayoutList,{size:18})})]}),b.jsx(an,{variant:"icon",icon:"Archive",onClick:()=>p(!c),title:c?t("header.backToBoard"):`${t("header.archives")} (${o})`,className:c?"text-accent":""}),b.jsx(an,{variant:"icon",icon:"Density",onClick:()=>$(A==="compact"?"comfortable":"compact"),title:`Density: ${A}`}),b.jsx(an,{variant:"icon",icon:"Settings",onClick:()=>T("settings"),title:t("common.settings")}),b.jsx(Rpe,{}),b.jsx("div",{className:"w-px h-5 bg-black/[0.08] dark:bg-white/[0.08] mx-1"}),b.jsx(an,{variant:"secondary",icon:"Search",label:t("common.search"),shortcut:"⌘K",onClick:()=>v(!0),title:"Command palette (⌘K)"}),b.jsx(an,{variant:"icon",icon:"Refresh",onClick:f,title:`${t("common.reload")} (R)`}),b.jsx(an,{variant:"primary",icon:"Plus",label:t("common.newTask"),shortcut:"N",onClick:()=>h()})]}):b.jsx(an,{variant:"primary",label:t("common.openFolder"),onClick:m})})]})]})}/**
204
+ `}}async function bH(t,e){try{return await(await(await(await t.getDirectoryHandle("archive",{create:!1})).getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}}async function ope(t,e){try{return await(await t.getDirectoryHandle("archive",{create:!1})).getFileHandle(`${e}.md`),!0}catch{return!1}}async function vH(t,e){try{return await t.getDirectoryHandle("archive",{create:e})}catch{return null}}async function cpe(t){if(Lt())return fS();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 Ps(t,e){if(Lt())try{const i=await gS(e);return Fg(i)}catch{return FL(e)}const a=await(async i=>{try{return await(await(await i.getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}})(t)??await bH(t,e);return a!==null?Fg(a):FL(e)}async function ppe(t,e){if(Lt())try{const a=await gS(e);return{ok:!0,task:Fg(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 bH(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:Fg(n)}}catch(a){return{ok:!1,reason:"corrupted",error:a}}}async function pu(t,e,n,a){const i=dS(n,a);if(Lt())return Xce(e,i);try{const o=await(await(await ope(t,e)?await vH(t,!0):t).getFileHandle(`${e}.md`,{create:!0})).createWritable();try{await o.write(i)}finally{await o.close()}}catch(r){throw Lh(r,`${e}.md`)}}async function zL(t,e){if(Lt())return Qce(e);try{await t.removeEntry(`${e}.md`)}catch{}try{await(await t.getDirectoryHandle("archive",{create:!1})).removeEntry(`${e}.md`)}catch{}}async function lpe(t,e){await gi(`/api/tasks/${encodeURIComponent(t)}/archive`,{method:"POST",body:e,headers:{"Content-Type":"text/plain"}})}async function dpe(t,e){await gi(`/api/tasks/${encodeURIComponent(t)}/unarchive`,{method:"POST",body:e,headers:{"Content-Type":"text/plain"}})}async function upe(t,e,n,a){const i=dS(n,a);if(Lt())return lpe(e,i);try{const o=await(await(await vH(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 Lh(r,`archive/${e}.md`)}}async function mpe(t,e,n,a){const i=dS(n,a);if(Lt())return dpe(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 Lh(r,`${e}.md`)}}const fpe="kanban-md",gpe=1,sp="recentProjects";function hS(){return new Promise((t,e)=>{const n=indexedDB.open(fpe,gpe);n.onerror=()=>e(n.error),n.onsuccess=()=>t(n.result),n.onupgradeneeded=()=>{const a=n.result;a.objectStoreNames.contains(sp)||a.createObjectStore(sp,{keyPath:"id"})}})}async function H2(t){const e=await hS();return new Promise((n,a)=>{const i=e.transaction(sp,"readwrite");i.objectStore(sp).put(t),i.oncomplete=()=>n(),i.onerror=()=>a(i.error)})}async function Fy(){const t=await hS();return new Promise((e,n)=>{const i=t.transaction(sp,"readonly").objectStore(sp).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 hpe(t){const e=await hS();return new Promise((n,a)=>{const i=e.transaction(sp,"readwrite");i.objectStore(sp).delete(t),i.oncomplete=()=>n(),i.onerror=()=>a(i.error)})}async function HL(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}}async function bpe(){try{return await(await gi("/api/update/check")).json()}catch{return null}}async function yH(){try{return await(await gi("/api/update/apply",{method:"POST"})).json()}catch{return null}}function _H(t=Ei){const e=t.board.columns;return e[e.length-1]??"Done"}function vpe(t,e=Ei){return t===_H(e)||t.toLowerCase()==="archived"}class ype{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;eventSource=null;start(e,n){this.dirHandle=e,this.tasksDirHandle=n,this.consecutiveErrors=0,this.disabled=!1,Lt()&&this.startServerSse(),e&&n&&(this.initHashes(),this.intervalId=setInterval(()=>void this.tick(),300))}startServerSse(){if(typeof window>"u"||!Lt()||this.eventSource)return;const e=window.__KANDOWN_TOKEN__,n=e?`/api/events?token=${encodeURIComponent(e)}`:"/api/events";try{this.eventSource=new EventSource(n),this.eventSource.onmessage=a=>{try{const i=JSON.parse(a.data);i.type==="change"&&(i.taskId?this.emit("taskChanged",i.taskId):(this.emit("configChanged"),this.emit("taskChanged","")))}catch{}}}catch(a){console.warn("[Watcher] EventSource init failed:",a)}}stop(){this.eventSource&&(this.eventSource.close(),this.eventSource=null),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 BL(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 BL(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 UL(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 UL(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 BL(t){try{return await(await(await t.getFileHandle("kandown.json")).getFile()).text()}catch{return null}}async function UL(t,e){try{return await(await(await t.getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}}const Lo=new ype,_pe={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 qL=null;function kH(){return"Notification"in window?Notification.permission:"unsupported"}async function kpe(){return"Notification"in window?await Notification.requestPermission():"unsupported"}function B2({title:t,body:e,config:n}){if(n.notifications.webhookUrl&&fetch(n.notifications.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({event:"kandown_notification",title:t,body:e,timestamp:new Date().toISOString()})}).catch(()=>{}),n.notifications.sound&&wpe(n.notifications.soundId),!(!n.notifications.browser||kH()!=="granted"))try{new Notification(t,{body:e})}catch{}}function wpe(t){const e=_pe[t],n=window.AudioContext??window.webkitAudioContext;if(!(!n||e.length===0))try{qL??=new n;const a=qL;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 jf(t,e={}){const{maxAttempts:n=3,baseDelayMs:a=500,retryableCheck:i=Zce}=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 xpe(t){const e=t.toLowerCase(),n=new Date;if(e==="today")return n.toISOString().split("T")[0];if(e==="tomorrow"){const i=new Date(n);return i.setDate(i.getDate()+1),i.toISOString().split("T")[0]}const a=["sunday","monday","tuesday","wednesday","thursday","friday","saturday"];if(a.includes(e)){const i=a.indexOf(e),r=new Date(n);let s=i-r.getDay();return s<=0&&(s+=7),r.setDate(r.getDate()+s),r.toISOString().split("T")[0]}return t}function Ape(t){let e=t.trim(),n;const a=[];let i,r;const s=[];return e=e.replace(/(?:^|\s)p([1-4])(?:\s|$)/i,(c,p)=>(n=`P${p}`," ")),e=e.replace(/(?:^|\s)#([a-zA-Z0-9_-]+)/g,(c,p)=>(a.push(p.toLowerCase())," ")),e=e.replace(/(?:^|\s)@([a-zA-Z0-9_-]+)/g,(c,p)=>(i=p," ")),e=e.replace(/(?:^|\s)due:([^\s]+)/i,(c,p)=>(r=xpe(p)," ")),e=e.replace(/(?:^|\s)\+([a-zA-Z0-9_-]+)/g,(c,p)=>(s.push(p)," ")),{title:e.replace(/\s+/g," ").trim()||t,...n?{priority:n}:{},...a.length>0?{tags:a}:{},...i?{assignee:i}:{},...r?{due:r}:{},...s.length>0?{depends_on:s}:{}}}const Cpe=/^[A-Za-z0-9_-]+$/;function U2(t){if(!t)return null;const e=t.trim().replace(/^\/+|\/+$/g,"").replace(/^#/,"");return!e||e.includes("/")||e.includes(".")||!Cpe.test(e)?null:/^\d+$/.test(e)?`t${e}`:e}function Npe(t){const e=new URLSearchParams(t.search).get("p");if(!e)return null;const[n]=e.split("/").filter(Boolean);return n||null}function zy(t){const e=new URLSearchParams(t.search),n=U2(e.get("task"));if(n)return n;const a=e.get("p");if(a){const[,...r]=a.split("/").filter(Boolean),s=U2(r.join("/"));if(s)return s}return U2(decodeURIComponent(t.pathname))}function T3(t){return t?`/?p=${encodeURIComponent(t)}`:"/"}function bS(t,e){const n=t.replace(/^t(?=\d+$)/,""),a=`/${encodeURIComponent(n)}`;return e?`${a}?p=${encodeURIComponent(e)}`:a}function Hy(t,e=!1){typeof window>"u"||`${window.location.pathname}${window.location.search}`===t||(e?window.history.replaceState({},"",t):window.history.pushState({},"",t),window.dispatchEvent(new Event("kandown:urlchange")))}function Iv(t){typeof window<"u"&&zy(window.location)||Hy(T3(t))}function Spe(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 $pe(){const t=await fS();return await Promise.all(t.map(async n=>{const{frontmatter:a,body:i}=await hH(n),r={...a,id:a.id||n,status:a.status||"Backlog"},{subtasks:s,bodyWithoutSubtasks:o}=Rs(i);return{id:n,frontmatter:r,body:o,subtasks:s}}))}async function Epe(t){const e=await cpe(t),n=await Promise.all(e.map(async r=>{const s=await ppe(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}=Rs(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 VL(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 Ps(t,c.id);await pu(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 pl(t){mH(t.ui.theme,t.ui.skin,t.ui.font,t.ui.background)}function P3(t){return{title:t.frontmatter.title||t.id,status:t.frontmatter.status||"Backlog",body:t.body,subtasks:t.subtasks}}function q2(t){yu.clear(),t.forEach(e=>{yu.set(e.id,P3(e))})}function Tpe(t){const e=t.split(/[\\/]+/).filter(Boolean),n=e[e.length-1];return n===".kandown"?e[e.length-2]??"Project":n??"Project"}function Ppe(t,e){return e.reduce((n,a,i)=>{const r=t[i]?.done??!1;return n+(a.done&&!r?1:0)},0)}function Mpe(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 Lpe=0;const yu=new Map,Df=new Map;let V2=null;const se=Lce((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:Ei,recentProjects:[],toasts:[],agentHook:null,isReloading:!1,lastReloadError:null,failedTaskIds:[],watcherError:null,hasUnsavedDrawerEdits:!1,lastSaveError:null,drawerRecoveryData:new Map,selectedTaskIds:[],toggleTaskSelection:n=>{t(a=>({selectedTaskIds:a.selectedTaskIds.includes(n)?a.selectedTaskIds.filter(s=>s!==n):[...a.selectedTaskIds,n]}))},clearTaskSelection:()=>t({selectedTaskIds:[]}),bulkMoveTasks:async n=>{const{selectedTaskIds:a,columns:i,moveTask:r}=e();for(const s of a){let o="Backlog";for(const c of i)if(c.tasks.some(p=>p.id===s)){o=c.name;break}await r(s,o,n)}t({selectedTaskIds:[]})},bulkDeleteTasks:async()=>{const{selectedTaskIds:n,deleteTask:a}=e();for(const i of n)await a(i);t({selectedTaskIds:[]})},drawerBaseVersion:null,conflictState:null,showConflictModal:!1,openFolder:async()=>{if(!fH()){const c=navigator.userAgent||"this browser";e().toast(new Vce(c).message,"error",8e3);return}let n;try{n=await npe()}catch(c){c instanceof mS?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}),Iv(s);const o=Lt()?z2():null;try{await H2({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 Fy();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 HL(n.handle,!0)){let r=!1;try{await Oy(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 hpe(n.id);const s=await Fy();t({recentProjects:s})}catch{}return}e().toast("Permission denied — please grant access to the folder","error");return}try{const r=await Oy(n.handle),s=await E3(n.handle),o=n.handle.name;t({dirHandle:r,tasksDirHandle:s,projectName:o}),Iv(o);try{await H2({...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=z2();if(!n)throw new Error("No server root");await OL();const a=Tpe(n),i=await gH();pl(i);const r=await fS(),s=await Promise.all(r.map(async f=>{const{frontmatter:h,body:v}=await hH(f),k={...h,id:h.id||f,status:h.status||"Backlog"},{subtasks:w,bodyWithoutSubtasks:A}=Rs(v);return{id:f,frontmatter:k,body:A,subtasks:w}}));q2(s);const o=s.map(f=>({frontmatter:f.frontmatter,body:Wd(f.body,f.subtasks)})),c=R2(o,i.board.columns),p=O2(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}),Iv(a),e().setupWatcher(),e().refreshAgentHook()}catch{t({loading:!1,isOpen:!1}),e().toast("Impossible de charger le projet. Relancez `kandown`.","error")}},tryAutoOpenServerProject:async()=>{if(!Lt())return;const n=z2();if(!n)return;await OL();const a=await Fy(),i=a.find(p=>p.kandownDir===n);if(!i){await e().openServerProject();return}if(!await HL(i.handle,!0)){await e().openServerProject();return}const s=await Oy(i.handle),o=await E3(i.handle),c=i.handle.name;t({dirHandle:s,tasksDirHandle:o,projectName:c,recentProjects:a,isOpen:!0}),Iv(c),await H2({...i,lastOpened:Date.now()}),await e().loadConfig(),await e().reloadBoard(),e().setupWatcher()},loadConfig:async()=>{const{dirHandle:n}=e();if(!(!n&&!Lt()))try{const a=await ape(n);if(a.ok){t({config:a.config}),pl(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:Ei}),pl(Ei)}catch{t({config:Ei}),pl(Ei)}},updateConfig:async n=>{const{dirHandle:a,config:i}=e();if(!a&&!Lt())return;const r=n(i);t({config:r}),pl(r);try{await ipe(a,r)}catch(s){const o=s;o instanceof To?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(Lt()){const i=await $pe();q2(i);const r=i.map(d=>({frontmatter:d.frontmatter,body:Wd(d.body,d.subtasks)})),s=R2(r,a.board.columns),o=O2(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 Epe(n);q2(i);const s=i.map(m=>({frontmatter:m.frontmatter,body:Wd(m.body,m.subtasks)})),o=R2(s,a.board.columns),c=O2(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=Lt();if(!d&&!e().tasksDirHandle)return;const m=s.find(T=>T.name===a),f=s.find(T=>T.name===i);if(!m||!f)return;const h=m.tasks.findIndex(T=>T.id===n);if(h===-1)return;const v=m.tasks[h];if(!v)return;if(vpe(i,o)){const T=new Map,M=_H(o).toLowerCase();for(const D of s)for(const I of D.tasks){const H=I.frontmatter&&(I.frontmatter.archived===!0||I.frontmatter.archived==="true");T.set(I.id,{exists:!0,resolved:H||I.id===n||D.name.toLowerCase()===M})}const j=[];for(const D of v.dependsOn){if(typeof D!="string"||!D.trim()||D===n)continue;const I=T.get(D);(!I||!I.resolved)&&j.push(D)}if(j.length>0){const D=j.length===1?j[0]:`${j.slice(0,-1).join(", ")} and ${j[j.length-1]}`;e().toast(`Cannot move ${n} to ${i}: blocked by ${D}`,"error");return}}const k=s.map(T=>({...T,tasks:[...T.tasks]})),w=k.find(T=>T.name===a),A=k.find(T=>T.name===i),[$]=w.tasks.splice(h,1);/done|termin|closed|complet/i.test(i)?$.checked=!0:$.checked=!1,r!==void 0?A.tasks.splice(r,0,$):A.tasks.push($),t({columns:k});try{const{tasksDirHandle:T}=e();if(!T&&!d)return;const M=a===i?k.filter(D=>D.name===i):k.filter(D=>D.name===a||D.name===i),{failedIds:j}=await jf(()=>VL(T??null,M,o.board.columns),{maxAttempts:3});if(j.length>0){const D=j.length===1?`Could not save move for ${j[0]}`:`${j.length} tasks could not be moved`;e().toast(D,"warning",8e3),await e().reloadBoard()}}catch(T){const M=T;M instanceof To?e().toast("Disk is full — move was not saved. Free up space and try again.","error",8e3):e().toast("Failed to save: "+M.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&&!Lt())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(),v=Lt();if(!h&&!v)return;const{failedIds:k}=await jf(()=>VL(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 v=h;v instanceof To?e().toast("Disk is full — reorder was not saved.","error",8e3):e().toast("Failed to save: "+v.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,v)=>{const{frontmatter:k,body:w}=await Ps(s,h.id);await pu(s,h.id,{...k,id:h.id,status:i,order:v},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 v=m.board.columns.some(k=>k.toLowerCase()===n.toLowerCase())?m.board.columns:[...m.board.columns,n];return{...m,board:{...m.board,columns:v.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})}},reorderColumns:async(n,a)=>{const{config:i,columns:r,tasksDirHandle:s}=e();if(n<0||a<0||n>=r.length||a>=r.length||n===a||!s&&!Lt())return;const o=Array.from(r),[c]=o.splice(n,1);o.splice(a,0,c),t({columns:o});const p=o.map(d=>d.name);try{await e().updateConfig(d=>({...d,board:{...d.board,columns:p}})),await e().reloadBoard()}catch(d){e().toast("Failed to reorder columns: "+d.message,"error"),t({columns:r})}},deleteColumn:async n=>{const{columns:a,tasksDirHandle:i}=e();if(!i&&!Lt())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=>zL(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,a)=>{const{columns:i,tasksDirHandle:r,config:s,taskContents:o,searchMatches:c}=e();if(!r&&!Lt()||!i.length)return null;const p=n||s.board.columns[0]||i[0].name,d=Spe(i),m=i.find($=>$.name===p)?.tasks.length??0,f=a?Ape(a):null,h={id:d,title:f?.title||"",checked:!1,dependsOn:f?.depends_on||[],tags:f?.tags||[],assignee:f?.assignee||null,priority:f?.priority||(s.fields.priority?s.board.defaultPriority:null),ownerType:s.fields.ownerType?s.board.defaultOwnerType:"",progress:null,frontmatter:{}},v=i.map($=>$.name===p?{...$,tasks:[...$.tasks,h]}:$),k=new Map(o),w={id:d,title:f?.title||"",status:p,order:m,priority:f?.priority||(s.fields.priority?s.board.defaultPriority:""),tags:f?.tags||[],assignee:f?.assignee||"",due:f?.due||"",depends_on:f?.depends_on||[],created:new Date().toISOString().slice(0,10),ownerType:s.fields.ownerType?s.board.defaultOwnerType:"",tools:""},A="";k.set(d,{frontmatter:w,subtasks:[],body:A}),t({columns:v,taskContents:k});try{const $=r||null;return await jf(()=>pu($,d,w,A),{maxAttempts:3}),e().toast(`Created ${d.replace(/^t/,"")}`),await e().openDrawer(d),d}catch($){const T=$;return T instanceof To?e().toast("Disk is full — task was not created.","error",8e3):e().toast("Failed to create: "+T.message,"error"),t({columns:i,taskContents:o,searchMatches:c}),null}},deleteTask:async n=>{const{columns:a,tasksDirHandle:i,taskContents:r,searchMatches:s}=e();if(!i&&!Lt())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 zL(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&&!Lt()))try{const{frontmatter:i,body:r}=await Ps(a||null,n);await upe(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 To?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&&!Lt()))try{const{frontmatter:i,body:r}=await Ps(a||null,n),s={...i};delete s.archived,await mpe(a||null,n,s,r),await e().reloadBoard(),e().toast("Restored")}catch(i){const r=i;r instanceof To?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,a={})=>{const{tasksDirHandle:i,projectName:r}=e();if(!(!i&&!Lt()))try{const{frontmatter:s,body:o}=await Ps(i,n),{subtasks:c,bodyWithoutSubtasks:p}=Rs(o),d={frontmatter:s,subtasks:c,body:p,savedAt:Date.now()},m=e().drawerRecoveryData.get(n),f=m?{frontmatter:m.frontmatter,subtasks:m.subtasks,body:m.body}:{frontmatter:s,subtasks:c,body:p},h=new Map(e().drawerRecoveryData);h.delete(n),a.syncUrl!==!1&&Hy(bS(n,r),a.replace),t({drawerTaskId:n,drawerData:f,drawerBaseVersion:d,conflictState:null,showConflictModal:!1,hasUnsavedDrawerEdits:!!m,lastSaveError:null,drawerRecoveryData:h}),m&&e().toast("Restored your unsaved edits for this task","info")}catch(s){e().toast("Failed to open: "+s.message,"error")}},closeDrawer:(n={})=>{n.syncUrl!==!1&&Hy(T3(e().projectName),n.replace),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=Wd(a.body,a.subtasks),o={...a.frontmatter,id:n};try{await jf(()=>pu(i||null,n,o,s),{maxAttempts:3}),e().toast("Saved");const c=new Map(e().drawerRecoveryData);c.delete(n),Hy(T3(e().projectName)),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 To?"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=Wd(a.body,a.subtasks),o={...a.frontmatter,id:n};await jf(()=>pu(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 To?"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(!Lt()){t({agentHook:null});return}const n=await Jce();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 epe(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 Ps(a,r),{subtasks:c,bodyWithoutSubtasks:p}=Rs(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=lH(o,r);c.length>0&&i.set(s,c)}t({searchMatches:i})},toast:(n,a="success",i)=>{const r=++Lpe,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 Ps(r,s),{subtasks:d,bodyWithoutSubtasks:m}=Rs(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=Wd(i.body,i.subtasks),p={...i.frontmatter,id:s};try{await pu(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(Lt()){V2&&clearInterval(V2),V2=setInterval(()=>{e().reloadBoard()},2e3);return}const{dirHandle:n,tasksDirHandle:a}=e();if(!n||!a)return;Lo.stop(),Df.forEach(s=>clearTimeout(s)),Df.clear(),Lo.start(n,a);const i=(s,o)=>{const c=Df.get(s);c&&clearTimeout(c);const p=Math.max(2e3,e().config.notifications.editDebounceMs),d=setTimeout(()=>{Df.delete(s);const m=e().config;m.notifications.taskEdits&&B2({title:"Task edited",body:`${o} changed on disk.`,config:m})},p);Df.set(s,d)},r=async s=>{const{tasksDirHandle:o,config:c}=e();if(!o)return;let p,d;try{({frontmatter:p,body:d}=await Ps(o,s))}catch(A){console.warn(`[Watcher] notifyTaskChange: failed to read ${s}:`,A);return}const{subtasks:m,bodyWithoutSubtasks:f}=Rs(d),h={id:s,frontmatter:{...p,id:p.id||s,status:p.status||"Backlog"},body:f,subtasks:m},v=P3(h),k=yu.get(s);if(!k){yu.set(s,v);return}c.notifications.statusChanges&&k.status!==v.status&&B2({title:"Task status changed",body:`${v.title}: ${k.status} → ${v.status}`,config:c});const w=Ppe(k.subtasks,v.subtasks);c.notifications.subtaskCompletions&&w>0&&B2({title:"Subtask completed",body:w===1?`${v.title}: 1 subtask completed.`:`${v.title}: ${w} subtasks completed.`,config:c}),Mpe(k,v)&&i(s,v.title),yu.set(s,v)};Lo.on("configChanged",()=>{try{e().loadConfig(),e().toast("Settings updated externally","info")}catch(s){console.error("[Watcher] configChanged handler error:",s)}}),Lo.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 Ps(p,s))}catch(T){console.warn(`[Watcher] taskChanged: failed to re-read ${s}:`,T);return}const{subtasks:f,bodyWithoutSubtasks:h}=Rs(m),v=c,k=JSON.stringify(v.frontmatter)!==JSON.stringify(d),w=v.body!==h,A=JSON.stringify(v.subtasks)!==JSON.stringify(f);if(!k&&!w&&!A)return;let $="none";k&&(w||A)?$="full":k?$="metadata-only":(w||A)&&($="body-only"),t({conflictState:{taskId:s,type:$,local:v,remote:{frontmatter:d,body:h,subtasks:f}},showConflictModal:$==="full"})}else await e().reloadBoard()}catch(o){console.error(`[Watcher] taskChanged handler error for ${s}:`,o)}}),Lo.on("newTaskDetected",async s=>{try{const{tasksDirHandle:o}=e();if(o){let c,p;try{({frontmatter:c,body:p}=await Ps(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}=Rs(p);yu.set(s,P3({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)}}),Lo.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"))}}));Fy().then(t=>{se.setState({recentProjects:t})}).catch(t=>{console.warn("[Store] Failed to hydrate recent projects:",t)});pl(Ei);window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{pl(se.getState().config)});const jpe=Object.freeze(Object.defineProperty({__proto__:null,useStore:se},Symbol.toStringTag,{value:"Module"}));function Dpe({icon:t,value:e,label:n,isActive:a,onClick:i,layoutId:r}){return b.jsxs("button",{type:"button",className:T1("relative flex size-8 cursor-default items-center justify-center rounded-full transition-all [&_svg]:size-4",a?"text-zinc-950 dark:text-zinc-50":"text-zinc-400 hover:text-zinc-950 dark:text-zinc-500 dark:hover:text-zinc-50"),role:"radio","aria-checked":a,"aria-label":`Switch to ${n} theme`,onClick:()=>i(e),children:[t,a&&b.jsx(nt.div,{layoutId:r,transition:{type:"spring",bounce:.3,duration:.6},className:"absolute inset-0 rounded-full border border-zinc-200 dark:border-zinc-700"})]})}const Ipe=[{icon:b.jsx(yce,{}),value:"auto",label:"system"},{icon:b.jsx(Sce,{}),value:"light",label:"light"},{icon:b.jsx(kce,{}),value:"dark",label:"dark"}];function Rpe({value:t,onChange:e,className:n}){const a=se(d=>d.config.ui.theme),i=se(d=>d.updateConfig),r=`theme-option-${S.useId()}`,[s,o]=S.useState(!1);S.useEffect(()=>{o(!0)},[]);const c=t??a,p=e??(d=>{i(m=>({...m,ui:{...m.ui,theme:d}}))});return s?b.jsx(nt.div,{initial:{opacity:0},animate:{opacity:1},transition:{duration:.3},className:T1("inline-flex items-center overflow-hidden rounded-full bg-white ring-1 ring-zinc-200 ring-inset dark:bg-zinc-950 dark:ring-zinc-700",n),role:"radiogroup","aria-label":"Theme mode",children:Ipe.map(d=>b.jsx(Dpe,{icon:d.icon,value:d.value,label:d.label,isActive:c===d.value,onClick:p,layoutId:r},d.value))},String(s)):b.jsx("div",{className:T1("flex h-8 w-24",n)})}function Ope(t){const e=soe(t,{stiffness:180,damping:22,mass:.8});return S.useEffect(()=>{e.set(t)},[t,e]),Uz(e,a=>Math.round(a).toString())}const M3="0.33.4";function wH({className:t="w-6 h-6"}){return b.jsxs("svg",{viewBox:"0 0 150 150",className:t,version:"1.1",xmlns:"http://www.w3.org/2000/svg",xmlnsXlink:"http://www.w3.org/1999/xlink",style:{fillRule:"evenodd",clipRule:"evenodd",strokeLinejoin:"round",strokeMiterlimit:2},children:[b.jsx("path",{d:"M148,21.594L148,128.406C148,139.22 139.22,148 128.406,148L21.594,148C10.78,148 2,139.22 2,128.406L2,21.594C2,10.78 10.78,2 21.594,2C139.22,2 148,10.78 148,21.594Z",style:{fill:"url(#_KandownLinear1)"}}),b.jsxs("g",{transform:"matrix(0.823551,0,0,0.823551,12.830775,11.282318)",children:[b.jsx("g",{transform:"matrix(1.620982,0,0,1.566239,-41.718998,-41.628205)",children:b.jsx("path",{d:"M56.491,62.601C57.2,61.897 57.602,60.925 57.606,59.908C57.627,53.936 57.7,33.2 57.7,33.2C57.6,29.7 55,27.6 52,27.6L42.075,27.6C38.544,27.6 35.673,30.547 35.644,34.201C35.528,48.996 35.327,83.484 35.956,83.013C35.956,83.013 52.072,66.994 56.491,62.601Z",style:{fill:"rgb(252,255,239)",fillRule:"nonzero"}})}),b.jsx("g",{transform:"matrix(1.566239,0,0,1.566239,-39.774573,-41.628205)",children:b.jsx("path",{d:"M87.6,43.8C84.2,43.9 80.5,45.1 77.7,47.5L39.2,86.2C37.1,88.3 35.8,91 35.7,93.7L35.7,116.574C35.7,117.219 36.088,117.802 36.683,118.051C37.278,118.3 37.965,118.167 38.425,117.714C50.467,105.842 98.836,58.16 110.608,46.555C111.047,46.122 111.185,45.468 110.956,44.894C110.728,44.321 110.178,43.94 109.561,43.928C106.663,43.871 103.089,43.8 103.089,43.8L87.6,43.8Z",style:{fill:"rgb(241,255,184)",fillRule:"nonzero"}})}),b.jsx("g",{transform:"matrix(0.766318,0.766329,-1.302872,1.302853,170.442469,-47.541879)",children:b.jsx("path",{d:"M38.038,101.45C35.808,101.45 34,100.387 34,99.075C34,95.326 34,88.102 34,84.58C34,84.042 34.364,83.525 35.011,83.145C35.658,82.764 36.536,82.55 37.452,82.55C46.372,82.55 69.5,82.55 69.5,82.55C69.5,82.55 69.5,77.52 69.5,74.389C69.5,73.947 69.936,73.545 70.617,73.359C71.298,73.173 72.101,73.236 72.675,73.521C80.028,77.17 98.075,86.125 104.279,89.204C104.832,89.479 105.168,89.876 105.206,90.301C105.245,90.727 104.982,91.143 104.481,91.45C98.972,94.832 83.072,104.591 75.013,109.539C74.195,110.041 72.946,110.206 71.841,109.959C70.737,109.712 69.99,109.101 69.946,108.406C69.744,105.262 69.5,101.45 69.5,101.45L38.038,101.45Z",style:{fill:"rgb(136,225,56)"}})})]}),b.jsx("defs",{children:b.jsxs("linearGradient",{id:"_KandownLinear1",x1:"0",y1:"0",x2:"1",y2:"0",gradientUnits:"userSpaceOnUse",gradientTransform:"matrix(62,131,-131,62,9,8)",children:[b.jsx("stop",{offset:"0",style:{stopColor:"rgb(12,29,23)",stopOpacity:1}}),b.jsx("stop",{offset:"1",style:{stopColor:"rgb(24,41,35)",stopOpacity:1}})]})})]})}function Fpe(){const{t}=Tn(),e=se(B=>B.dirHandle),n=se(B=>B.isOpen),a=se(B=>B.projectName),i=se(B=>B.closeDrawer),r=se(B=>B.drawerTaskId),s=se(B=>B.saveDrawer),o=se(B=>B.archivedTasks.length),c=se(B=>B.showArchives),p=se(B=>B.setShowArchives),d=se(B=>B.columns),m=se(B=>B.openFolder),f=se(B=>B.reloadBoard),h=se(B=>B.createTask),v=se(B=>B.setCommandOpen),k=se(B=>B.viewMode),w=se(B=>B.setViewMode),A=se(B=>B.density),$=se(B=>B.setDensity),T=se(B=>B.setCurrentPage),M=se(B=>B.recentProjects),j=se(B=>B.openRecentProject),D=se(B=>B.filters),I=se(B=>B.setFilter),H=se(B=>B.clearFilters),R=se(B=>B.config.fields),L=se(B=>B.lastReloadError),G=se(B=>B.watcherError),Y=se(B=>B.restartWatcher),[ne,U]=S.useState(!1),[F,V]=S.useState(!0),Q=S.useRef(null),J=S.useRef(null),oe=d.reduce((B,ae)=>B+ae.tasks.length,0),O=Ope(oe),Z=[];R.priority&&D.priority&&Z.push({type:"priority",label:D.priority,value:D.priority}),R.tags&&D.tag&&Z.push({type:"tag",label:"#"+D.tag,value:D.tag}),R.assignee&&D.assignee&&Z.push({type:"assignee",label:"@"+D.assignee,value:D.assignee});const K=[{label:t("filterBar.ownerAll"),value:""},{label:t("filterBar.ownerHuman"),value:"human"},{label:t("filterBar.ownerAI"),value:"ai"}],E=Z.length>0||D.search||R.ownerType&&D.ownerType;return S.useEffect(()=>{if(!ne)return;const B=ae=>{Q.current&&!Q.current.contains(ae.target)&&U(!1)};return document.addEventListener("mousedown",B),()=>document.removeEventListener("mousedown",B)},[ne]),S.useEffect(()=>{const B=setTimeout(()=>V(!1),5e3);return()=>clearTimeout(B)},[]),S.useEffect(()=>{document.title=a?`${a} · Kandown`:"Kandown"},[a]),b.jsxs(b.Fragment,{children:[(L||G)&&b.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:[b.jsx("span",{className:"flex-1 truncate",children:G?`⚠ ${G}`:`⚠ ${L}`}),G&&b.jsx("button",{type:"button",onClick:Y,className:"px-2 py-0.5 rounded bg-amber-500/20 hover:bg-amber-500/30 text-[11.5px] font-medium",children:"Restart watcher"}),b.jsx("button",{type:"button",onClick:f,className:"px-2 py-0.5 rounded bg-amber-500/20 hover:bg-amber-500/30 text-[11.5px] font-medium",children:"Reload"})]}),b.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:[b.jsxs("div",{className:"flex items-center gap-4 min-w-0",children:[b.jsxs("div",{className:"flex items-center gap-2.5 flex-shrink-0",children:[b.jsxs("button",{onClick:()=>{r?s():i({replace:!0})},className:"flex items-center gap-2 cursor-pointer",children:[b.jsx(wH,{className:"w-[34px] h-[34px] dark:text-white text-black"}),b.jsx(Wa,{mode:"wait",initial:!1,children:F?b.jsxs(nt.span,{className:"flex items-center gap-2",initial:{opacity:0},animate:{opacity:1},exit:{opacity:0},transition:{duration:.45},children:[b.jsx("span",{className:"text-[15px] font-semibold tracking-tight text-fg",children:"kandown"}),b.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",M3]})]},"boot"):a?b.jsx(nt.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})]}),r&&b.jsxs("button",{type:"button",onClick:()=>{s()},className:"hidden md:inline-flex items-center gap-2 rounded-xl border border-border bg-bg px-3 py-2 text-[13px] font-semibold text-fg shadow-sm transition-colors hover:border-border-strong hover:bg-bg-2",title:t("taskWorkspace.backToCards"),children:[b.jsx(Wn.ArrowLeft,{size:15}),b.jsx("span",{children:t("common.back")})]})]}),e&&b.jsxs(b.Fragment,{children:[b.jsx("div",{className:"w-px h-[20px] bg-black/[0.08] dark:bg-white/[0.08] flex-shrink-0"}),b.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:[b.jsx(Wn.Search,{size:14,className:"text-fg-muted/60 flex-shrink-0"}),b.jsx("input",{ref:J,type:"text",placeholder:t("filterBar.searchPlaceholder"),value:D.search,onChange:B=>I("search",B.target.value),className:"bg-transparent border-none outline-none text-fg text-[13px] w-full placeholder:text-fg-muted/60"}),D.search?b.jsx("button",{onClick:()=>I("search",""),className:"text-fg-muted/60 hover:text-fg flex-shrink-0",children:b.jsx(Wn.X,{size:14})}):b.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"})]}),b.jsxs("div",{className:"flex items-center gap-1.5 flex-wrap overflow-hidden",children:[b.jsx(Wa,{children:Z.map(B=>b.jsxs(nt.button,{initial:{opacity:0,scale:.9},animate:{opacity:1,scale:1},exit:{opacity:0,scale:.9},transition:{duration:.15},onClick:()=>I(B.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:[B.label,b.jsx(Wn.X,{size:10,className:"text-fg-muted/60"})]},B.type+B.value))}),R.ownerType&&b.jsx("div",{className:"flex items-center h-6 border border-black/[0.06] dark:border-white/[0.1] rounded-lg overflow-hidden",children:K.map(B=>b.jsx("button",{onClick:()=>I("ownerType",B.value),className:`h-full px-2.5 text-[12px] transition-colors ${D.ownerType===B.value?"bg-black/[0.06] dark:bg-white/[0.12] text-fg":"text-fg-muted/70 hover:text-fg"}`,children:B.label},B.value))}),E&&b.jsx("button",{onClick:H,className:"text-[12px] text-fg-muted/60 hover:text-fg transition-colors",children:t("filterBar.clearAll")})]})]}),e&&b.jsxs(b.Fragment,{children:[b.jsx("div",{className:"w-px h-[20px] bg-black/[0.08] dark:bg-white/[0.08] flex-shrink-0"}),b.jsxs("div",{className:"relative flex-shrink-0",ref:Q,children:[b.jsxs("button",{onClick:()=>U(B=>!B),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:[b.jsx(Wn.Folder,{size:13,className:"text-fg-muted/70"}),b.jsxs("span",{className:"font-medium",children:[".",e.name]}),b.jsx(Wn.ChevronDown,{size:11,className:"opacity-50"})]}),b.jsx(Wa,{children:ne&&b.jsx(nt.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:b.jsxs("div",{className:"py-1.5",children:[M.length>0&&b.jsxs(b.Fragment,{children:[b.jsx("div",{className:"px-3 py-1.5 text-[11px] font-semibold uppercase tracking-wider text-fg-muted/60",children:t("header.recentProjects")}),M.map(B=>b.jsxs("button",{onClick:()=>{U(!1),j(B)},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:[b.jsx(Wn.Folder,{size:12,className:"text-fg-muted/60"}),b.jsx("span",{className:"truncate",children:B.name}),B.id===e.name&&b.jsx(Wn.Check,{size:12,className:"ml-auto text-emerald-500"})]},B.id)),b.jsx("div",{className:"h-px bg-black/[0.06] dark:bg-white/[0.08] my-1.5 mx-2"})]}),b.jsxs("button",{onClick:()=>{U(!1),m()},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:[b.jsx(Wn.Plus,{size:12,className:"text-fg-muted/60"}),b.jsx("span",{children:t("header.openFolder...")})]})]})})})]})]})]}),b.jsx("div",{className:"flex items-center gap-2 flex-shrink-0",children:n||e?b.jsxs(b.Fragment,{children:[b.jsxs("div",{className:"flex items-center gap-2 mr-2 text-[12.5px] text-fg-muted/70",children:[b.jsx("span",{className:"inline-block w-1.5 h-1.5 rounded-full bg-emerald-500"}),b.jsx(nt.span,{className:"tabular-nums font-medium",children:O}),b.jsx("span",{children:t("header.tasks")})]}),b.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:[b.jsx("button",{onClick:()=>w("board"),className:`w-9 h-9 inline-flex items-center justify-center rounded-lg transition-all ${k==="board"?"bg-card text-fg shadow-sm":"text-fg-muted/70 hover:text-fg"}`,title:t("common.board"),children:b.jsx(Wn.LayoutBoard,{size:18})}),b.jsx("button",{onClick:()=>w("list"),className:`w-9 h-9 inline-flex items-center justify-center rounded-lg transition-all ${k==="list"?"bg-card text-fg shadow-sm":"text-fg-muted/70 hover:text-fg"}`,title:t("common.list"),children:b.jsx(Wn.LayoutList,{size:18})})]}),b.jsx(an,{variant:"icon",icon:"Archive",onClick:()=>p(!c),title:c?t("header.backToBoard"):`${t("header.archives")} (${o})`,className:c?"text-accent":""}),b.jsx(an,{variant:"icon",icon:"Density",onClick:()=>$(A==="compact"?"comfortable":"compact"),title:`Density: ${A}`}),b.jsx(an,{variant:"icon",icon:"Settings",onClick:()=>T("settings"),title:t("common.settings")}),b.jsx(Rpe,{}),b.jsx("div",{className:"w-px h-5 bg-black/[0.08] dark:bg-white/[0.08] mx-1"}),b.jsx(an,{variant:"secondary",icon:"Search",label:t("common.search"),shortcut:"⌘K",onClick:()=>v(!0),title:"Command palette (⌘K)"}),b.jsx(an,{variant:"icon",icon:"Refresh",onClick:f,title:`${t("common.reload")} (R)`}),b.jsx(an,{variant:"primary",icon:"Plus",label:t("common.newTask"),shortcut:"N",onClick:()=>h()})]}):b.jsx(an,{variant:"primary",label:t("common.openFolder"),onClick:m})})]})]})}/**
205
205
  * @license @tabler/icons-react v3.41.1 - MIT
206
206
  *
207
207
  * 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.33.3",
3
+ "version": "0.33.4",
4
4
  "description": "File-based Kanban engine backed by Markdown. Zero backend, AI-agent friendly.",
5
5
  "type": "module",
6
6
  "bin": {