kandown 0.17.2 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -91,8 +91,72 @@ The web daemon stays alive after you quit the TUI so the browser keeps working.
91
91
  | `kandown daemon start` | Start/reconnect this project's web daemon |
92
92
  | `kandown daemon stop` | Stop this project's web daemon |
93
93
  | `kandown update` | Update `kandown.html` to latest |
94
+ | `kandown list` \| `show` \| `create` \| `move` \| `assign` \| `commit` | One-shot task commands (see below) |
95
+ | `kandown tasks` | Full help for the one-shot task commands |
96
+ | `kandown work` | **For AI agents:** print the agent rules + a live board digest (see below) |
94
97
  | `kandown help` | CLI help |
95
98
 
99
+ ### One-shot task commands (scriptable, agent-friendly)
100
+
101
+ Top-level, non-interactive commands for scripting, CI, and AI agents — no wrapper prefix, just `kandown <command>`. Every command auto-inits `.kandown/` on first use, same as the interactive CLI.
102
+
103
+ | Command | Description |
104
+ |---|---|
105
+ | `kandown list` | List tasks — `[-s status] [-a assignee] [-t tag] [-p priority] [--archived] [--json]` |
106
+ | `kandown show <id>` | Print a task file's raw content |
107
+ | `kandown create "title"` | Create a task — `[-p priority] [-a assignee] [-t tag ...] [--to status] [--id custom-id] [--json]` |
108
+ | `kandown move <id> <status>` | Move a task — `<status>` is a column name or `"archived"` |
109
+ | `kandown assign <id> [name]` | Assign a task (omit name to unassign) |
110
+ | `kandown commit [-m "message"]` | `git add tasks/ .kandown/kandown.json` + `git commit` |
111
+
112
+ ```bash
113
+ kandown list --json | jq '.[] | select(.priority=="P1")'
114
+ kandown create "Refactor auth middleware" -p P1 -t backend
115
+ kandown move t42 Done
116
+ kandown assign t42 alice
117
+ kandown commit -m "tasks: add auth refactor"
118
+ ```
119
+
120
+ **Output contract:** stdout carries data only (task ids, JSON, tables) — everything decorative (`✓ Created…`, warnings, errors) goes to stderr. This keeps `ID=$(kandown create "...")` and `kandown list --json | jq ...` clean and composable. Exit code `0` on success, non-zero on error — safe to check in scripts.
121
+
122
+ **No update checks:** the task commands and `kandown daemon` never touch the npm registry, so they stay fast and fully offline-capable — ideal for CI and for AI agents driving the board directly.
123
+
124
+ ### `kandown work` — the agent entrypoint
125
+
126
+ `kandown init` no longer copies a block of rules into your `AGENTS.md`/`CLAUDE.md` — it appends a single line instead:
127
+
128
+ > This project uses **kandown** for task management. **Always run `kandown work` when starting a new task** — it prints the current rules and board state, kept in sync with the installed CLI version.
129
+
130
+ Running `kandown work` prints, as plain markdown on stdout:
131
+
132
+ 1. **The agent rules** — always fresh, served straight from the installed CLI version instead of a copy that goes stale the moment the package updates.
133
+ 2. **Global instructions** (optional) — `~/.kandown/instructions.md`, applied to every kandown project on this machine (personal conventions, team-wide rules).
134
+ 3. **Project instructions** (optional) — `.kandown/instructions.md`, this project only (stack quirks, "always use pnpm", commit message language, etc).
135
+ 4. **A live board digest** — column counts, tasks per column with blocked-by annotations, and a computed **"next actionable task"** (closest to done, unblocked, highest priority) — so the agent gets its context in the same call.
136
+
137
+ This removes the drift problem of a rules block frozen into every project's `AGENTS.md` at init time, keeps the injected footprint to one line, and lets you layer instructions globally or per project without touching the agent file at all.
138
+
139
+ ---
140
+
141
+ ## Environment variables
142
+
143
+ | Variable | Effect |
144
+ |---|---|
145
+ | `KANDOWN_NO_UPDATE=1` | Disable the background auto-update check entirely (recommended for CI) |
146
+ | `KANDOWN_DEBUG=1` | Print full stack traces on unexpected errors instead of a one-line summary |
147
+ | `KANDOWN_AGENT_HOOK_URL` | POST endpoint that receives tasks sent via the "Send to Agent" button / TUI `g` key |
148
+ | `KANDOWN_AGENT_HOOK_LABEL` | Custom label for the agent hook button (default: `Agent`) |
149
+ | `KANDOWN_AGENT_HOOK_HEADERS` | JSON object of extra HTTP headers to send with the agent hook request |
150
+
151
+ Interactive runs of `kandown` check npm for updates at most once every 24 hours (never for the task commands or `daemon`, never when stdout isn't a terminal) and auto-install silently when one is found.
152
+
153
+ ---
154
+
155
+ ## Security notes
156
+
157
+ - The local web daemon binds to `127.0.0.1` only and issues a random per-project API token on startup (stored in the gitignored `.kandown/daemon.json`, injected into the served page). Every API route except the read-only `GET /api/daemon` identity check requires it — a stray browser tab on another site can't read or write your tasks through it.
158
+ - `.kandown/daemon.json` and `.kandown/daemon.lock` are runtime-only files; `kandown init` adds them to `.kandown/.gitignore` automatically.
159
+
96
160
  ---
97
161
 
98
162
  ## Features
package/bin/kandown.js CHANGED
@@ -50,6 +50,7 @@ globalThis.require = createRequire(import.meta.url);
50
50
  import { fileURLToPath } from 'node:url';
51
51
  import { createServer } from 'node:http';
52
52
  import { dirname, join, resolve } from 'node:path';
53
+ import { homedir } from 'node:os';
53
54
  import {
54
55
  existsSync,
55
56
  mkdirSync,
@@ -383,7 +384,7 @@ function atomicWriteFileSync(path, content) {
383
384
  }
384
385
 
385
386
  // 📖 Output contract (M2): stdout carries DATA ONLY (ids, JSON, tables, help)
386
- // so `$(kandown shell create ...)` and `| jq` pipelines stay clean. Every
387
+ // so `$(kandown create ...)` and `| jq` pipelines stay clean. Every
387
388
  // decoration — status lines, warnings, errors, progress — goes to stderr.
388
389
  /** Data output → stdout. Use ONLY for content the command was asked to produce. */
389
390
  const out = (msg) => console.log(msg);
@@ -457,7 +458,14 @@ ${c.bold}Commands:${c.reset}
457
458
  ${c.cyan}settings${c.reset} Open the settings TUI
458
459
  ${c.cyan}daemon${c.reset} Manage the per-project web daemon (start|stop|status)
459
460
  ${c.cyan}update${c.reset} Update kandown.html to the latest version
460
- ${c.cyan}shell${c.reset} Run shellable task commands (list/show/create/move/assign/commit)
461
+ ${c.cyan}list${c.reset} List tasks ${c.dim}(-s status, -a assignee, -t tag, -p priority, --json)${c.reset}
462
+ ${c.cyan}show${c.reset} Print a task's raw file content
463
+ ${c.cyan}create${c.reset} Create a task ${c.dim}(-p priority, -a assignee, -t tag, --to status)${c.reset}
464
+ ${c.cyan}move${c.reset} Move a task to a column (or "archived")
465
+ ${c.cyan}assign${c.reset} Assign / unassign a task
466
+ ${c.cyan}commit${c.reset} git add + commit the task files
467
+ ${c.cyan}tasks${c.reset} Full help for the commands above
468
+ ${c.cyan}work${c.reset} ${c.bold}For AI agents:${c.reset} print the agent rules + live board digest
461
469
  ${c.cyan}help${c.reset} Show this help
462
470
 
463
471
  ${c.bold}Options:${c.reset}
@@ -471,9 +479,10 @@ ${c.bold}Examples:${c.reset}
471
479
  ${c.dim}$${c.reset} npx kandown init
472
480
  ${c.dim}$${c.reset} npx kandown init --path docs/kanban
473
481
  ${c.dim}$${c.reset} npx kandown init --no-agents
474
- ${c.dim}$${c.reset} npx kandown shell list --json
475
- ${c.dim}$${c.reset} npx kandown shell create "Refactor auth" -p P1
476
- ${c.dim}$${c.reset} npx kandown shell commit -m "tasks: refactor auth"
482
+ ${c.dim}$${c.reset} npx kandown work ${c.dim}# what an AI agent should run first${c.reset}
483
+ ${c.dim}$${c.reset} npx kandown list --json
484
+ ${c.dim}$${c.reset} npx kandown create "Refactor auth" -p P1
485
+ ${c.dim}$${c.reset} npx kandown commit -m "tasks: refactor auth"
477
486
  `);
478
487
  }
479
488
 
@@ -548,12 +557,7 @@ function appendAgentReference(cwd, agentsFile, kandownPath) {
548
557
  ${marker}
549
558
  ## Task management
550
559
 
551
- **IMPORTANT:** Before touching any task files, you MUST read \`${kandownPath}/AGENT_KANDOWN.md\`.
552
-
553
- This project uses a file-based kanban:
554
- - **Tasks live in \`./tasks/t-xxx.md\`** at the project root — each task file owns its status
555
- - **Config lives in \`${kandownPath}/kandown.json\`** (columns, appearance, agent settings)
556
- - **Completion workflow:** set task frontmatter \`status: Done\` + write the completion report
560
+ This project uses **kandown** for task management. **Always run \`kandown work\` when starting a new task** — it prints the current rules and board state, kept in sync with the installed CLI version. (Tasks live in \`./tasks/*.md\`; if you can't run the CLI, read \`${kandownPath}/AGENT_KANDOWN.md\` instead.)
557
561
  `;
558
562
 
559
563
  try {
@@ -574,12 +578,7 @@ function createAgentsFileIfMissing(cwd, kandownPath) {
574
578
  <!-- kandown:agent-ref -->
575
579
  ## Task management
576
580
 
577
- **IMPORTANT:** Before touching any task files, you MUST read \`AGENT_KANDOWN.md\`.
578
-
579
- This project uses a file-based kandown:
580
- - **Tasks live in \`./tasks/t-xxx.md\`** at the project root — each task file owns its status
581
- - **Config lives in \`${kandownPath}/kandown.json\`** (columns, appearance, agent settings)
582
- - **Completion workflow:** set task frontmatter \`status: Done\` + write the completion report
581
+ This project uses **kandown** for task management. **Always run \`kandown work\` when starting a new task** — it prints the current rules and board state, kept in sync with the installed CLI version. (Tasks live in \`./tasks/*.md\`; if you can't run the CLI, read \`${kandownPath}/AGENT_KANDOWN.md\` instead.)
583
582
  `;
584
583
  writeFileSync(agentsPath, content, 'utf8');
585
584
  return true;
@@ -883,9 +882,9 @@ function cmdUpdate(rawArgs) {
883
882
  success(`Updated ${args.path}/kandown.html`);
884
883
  }
885
884
 
886
- /* ═════════════ Shellable task commands ═════════════ */
885
+ /* ═════════════ One-shot task commands ═════════════ */
887
886
  /**
888
- * 📖 Self-contained minimal YAML frontmatter parser/writer for the CLI shell
887
+ * 📖 Self-contained minimal YAML frontmatter parser/writer for the one-shot task
889
888
  * commands. The web app uses a richer schema (parseSimpleYaml) in the
890
889
  * browser; the CLI keeps its own because it can't import the browser bundle
891
890
  * and these commands only need to round-trip a known small set of scalar
@@ -1028,7 +1027,7 @@ function nextTaskId(kandownDir) {
1028
1027
  return 't' + (maxN + 1);
1029
1028
  }
1030
1029
 
1031
- function shellPad(str, len) {
1030
+ function padCell(str, len) {
1032
1031
  const s = String(str);
1033
1032
  // 📖 Only truncate when the string is strictly LONGER than the column —
1034
1033
  // an exact fit must render as-is (`>=` chopped the last char of e.g. the
@@ -1037,8 +1036,8 @@ function shellPad(str, len) {
1037
1036
  return s + ' '.repeat(len - s.length);
1038
1037
  }
1039
1038
 
1040
- function shellParseArgs(argv) {
1041
- // 📖 Minimal flag parser for the shell subcommands. Stops at the first
1039
+ function taskParseArgs(argv) {
1040
+ // 📖 Minimal flag parser for the task commands. Stops at the first
1042
1041
  // positional arg so `kandown create "Some title with -dash"` works.
1043
1042
  const out = { positional: [], flags: {} };
1044
1043
  for (let i = 0; i < argv.length; i++) {
@@ -1058,9 +1057,9 @@ function shellParseArgs(argv) {
1058
1057
  return out;
1059
1058
  }
1060
1059
 
1061
- function shellResolveStatus(config, status) {
1060
+ function resolveStatusArg(config, status) {
1062
1061
  // 📖 Status can be a configured column name OR the reserved `archived`
1063
- // sentinel. Match is case-insensitive to keep shell usage forgiving.
1062
+ // sentinel. Match is case-insensitive to stay forgiving in scripts.
1064
1063
  if (!status) return null;
1065
1064
  const lower = status.toLowerCase();
1066
1065
  if (lower === 'archived') return 'archived';
@@ -1071,11 +1070,11 @@ function shellResolveStatus(config, status) {
1071
1070
  return null;
1072
1071
  }
1073
1072
 
1074
- function shellList(rawArgs) {
1073
+ function cmdList(rawArgs) {
1075
1074
  const { kandownDir } = ensureKandownDir(rawArgs);
1076
- const args = shellParseArgs(rawArgs);
1075
+ const args = taskParseArgs(rawArgs);
1077
1076
  const config = readKandownConfig(kandownDir);
1078
- const statusFilter = args.flags.status ? shellResolveStatus(config, args.flags.status) : null;
1077
+ const statusFilter = args.flags.status ? resolveStatusArg(config, args.flags.status) : null;
1079
1078
  if (args.flags.status && !statusFilter) {
1080
1079
  err(`Unknown status: ${args.flags.status}`);
1081
1080
  process.exit(1);
@@ -1111,28 +1110,28 @@ function shellList(rawArgs) {
1111
1110
 
1112
1111
  if (rows.length === 0) {
1113
1112
  // 📖 Placeholder goes to stderr — an empty result on stdout stays empty
1114
- // so `[ -z "$(kandown shell list ...)" ]` style checks behave.
1113
+ // so `[ -z "$(kandown list ...)" ]` style checks behave.
1115
1114
  log(c.dim + '(no tasks)' + c.reset);
1116
1115
  return;
1117
1116
  }
1118
1117
 
1119
1118
  const idW = Math.max(2, ...rows.map(r => r.id.length));
1120
- out(`${c.dim}${shellPad('ID', idW)} ${shellPad('STATUS', 14)} ${shellPad('PRI', 4)} ${shellPad('ASSIGNEE', 12)} TITLE${c.reset}`);
1119
+ out(`${c.dim}${padCell('ID', idW)} ${padCell('STATUS', 14)} ${padCell('PRI', 4)} ${padCell('ASSIGNEE', 12)} TITLE${c.reset}`);
1121
1120
  for (const r of rows) {
1122
1121
  const status = (r.fm.status || 'Backlog') + (r.fm.archived === true || r.fm.archived === 'true' ? ' (archived)' : '');
1123
1122
  const pri = r.fm.priority || '';
1124
1123
  const assignee = r.fm.assignee || '';
1125
1124
  const title = (r.fm.title || '(untitled)').replace(/\n/g, ' ');
1126
- out(`${shellPad(r.id, idW)} ${shellPad(status, 14)} ${shellPad(pri, 4)} ${shellPad(assignee, 12)} ${title}`);
1125
+ out(`${padCell(r.id, idW)} ${padCell(status, 14)} ${padCell(pri, 4)} ${padCell(assignee, 12)} ${title}`);
1127
1126
  }
1128
1127
  }
1129
1128
 
1130
- function shellShow(rawArgs) {
1129
+ function cmdShow(rawArgs) {
1131
1130
  const { kandownDir } = ensureKandownDir(rawArgs);
1132
- const args = shellParseArgs(rawArgs);
1131
+ const args = taskParseArgs(rawArgs);
1133
1132
  const id = args.positional[0];
1134
1133
  if (!id) {
1135
- err('Usage: kandown shell show <id>');
1134
+ err('Usage: kandown show <id>');
1136
1135
  process.exit(1);
1137
1136
  }
1138
1137
  const path = findTaskFile(kandownDir, id);
@@ -1149,7 +1148,7 @@ function shellShow(rawArgs) {
1149
1148
  * resolved when it lives in the terminal column OR is archived. Unknown
1150
1149
  * ids and self-references never block.
1151
1150
  */
1152
- function shellTaskIsResolved(kandownDir, id, terminalLower) {
1151
+ function depIsResolved(kandownDir, id, terminalLower) {
1153
1152
  if (!/^[a-zA-Z0-9_-]+$/.test(id)) return true; // unknown / invalid → don't block
1154
1153
  const path = findTaskFile(kandownDir, id);
1155
1154
  if (!path) return true; // unknown id → don't block
@@ -1158,17 +1157,17 @@ function shellTaskIsResolved(kandownDir, id, terminalLower) {
1158
1157
  return isArch || (parsed.frontmatter.status || '').toLowerCase() === terminalLower;
1159
1158
  }
1160
1159
 
1161
- function shellCreate(rawArgs) {
1160
+ function cmdCreate(rawArgs) {
1162
1161
  const { kandownDir } = ensureKandownDir(rawArgs);
1163
- const args = shellParseArgs(rawArgs);
1162
+ const args = taskParseArgs(rawArgs);
1164
1163
  const title = args.positional.join(' ').trim();
1165
1164
  if (!title) {
1166
- err('Usage: kandown shell create "title" [-p priority] [-a assignee] [-t tag] [--to status]');
1165
+ err('Usage: kandown create "title" [-p priority] [-a assignee] [-t tag] [--to status]');
1167
1166
  process.exit(1);
1168
1167
  }
1169
1168
  const config = readKandownConfig(kandownDir);
1170
1169
  const defaultStatus = (config && config.board && config.board.columns && config.board.columns[0]) || 'Backlog';
1171
- const targetStatus = args.flags.to ? shellResolveStatus(config, args.flags.to) : defaultStatus;
1170
+ const targetStatus = args.flags.to ? resolveStatusArg(config, args.flags.to) : defaultStatus;
1172
1171
  if (args.flags.to && !targetStatus) {
1173
1172
  err(`Unknown status: ${args.flags.to}`);
1174
1173
  process.exit(1);
@@ -1207,17 +1206,17 @@ function shellCreate(rawArgs) {
1207
1206
  }
1208
1207
  }
1209
1208
 
1210
- function shellMove(rawArgs) {
1209
+ function cmdMove(rawArgs) {
1211
1210
  const { kandownDir } = ensureKandownDir(rawArgs);
1212
- const args = shellParseArgs(rawArgs);
1211
+ const args = taskParseArgs(rawArgs);
1213
1212
  const [id, rawStatus] = args.positional;
1214
1213
  const targetStatus = rawStatus || args.flags.to;
1215
1214
  if (!id || !targetStatus) {
1216
- err('Usage: kandown shell move <id> <status>');
1215
+ err('Usage: kandown move <id> <status>');
1217
1216
  process.exit(1);
1218
1217
  }
1219
1218
  const config = readKandownConfig(kandownDir);
1220
- const resolved = shellResolveStatus(config, targetStatus);
1219
+ const resolved = resolveStatusArg(config, targetStatus);
1221
1220
  if (!resolved) {
1222
1221
  err(`Unknown status: ${targetStatus}`);
1223
1222
  process.exit(1);
@@ -1238,7 +1237,7 @@ function shellMove(rawArgs) {
1238
1237
  const blocked = [];
1239
1238
  for (const dep of deps) {
1240
1239
  if (typeof dep !== 'string' || !dep.trim() || dep === id) continue;
1241
- if (!shellTaskIsResolved(kandownDir, dep, terminalLower)) blocked.push(dep);
1240
+ if (!depIsResolved(kandownDir, dep, terminalLower)) blocked.push(dep);
1242
1241
  }
1243
1242
  if (blocked.length > 0) {
1244
1243
  const list = blocked.length === 1 ? blocked[0] : `${blocked.slice(0, -1).join(', ')} and ${blocked[blocked.length - 1]}`;
@@ -1267,12 +1266,12 @@ function shellMove(rawArgs) {
1267
1266
  log(`${c.green}✓${c.reset} ${c.bold}${id}${c.reset} → ${resolved}`);
1268
1267
  }
1269
1268
 
1270
- function shellAssign(rawArgs) {
1269
+ function cmdAssign(rawArgs) {
1271
1270
  const { kandownDir } = ensureKandownDir(rawArgs);
1272
- const args = shellParseArgs(rawArgs);
1271
+ const args = taskParseArgs(rawArgs);
1273
1272
  const [id, name] = args.positional;
1274
1273
  if (!id) {
1275
- err('Usage: kandown shell assign <id> [name] (omit name to unassign)');
1274
+ err('Usage: kandown assign <id> [name] (omit name to unassign)');
1276
1275
  process.exit(1);
1277
1276
  }
1278
1277
  const path = findTaskFile(kandownDir, id);
@@ -1287,9 +1286,9 @@ function shellAssign(rawArgs) {
1287
1286
  log(`${c.green}✓${c.reset} ${c.bold}${id}${c.reset} → ${name ? c.cyan + name : c.dim + '(unassigned)'}${c.reset}`);
1288
1287
  }
1289
1288
 
1290
- function shellCommit(rawArgs) {
1289
+ function cmdCommit(rawArgs) {
1291
1290
  const { kandownDir } = ensureKandownDir(rawArgs);
1292
- const args = shellParseArgs(rawArgs);
1291
+ const args = taskParseArgs(rawArgs);
1293
1292
  const projectRoot = getProjectRoot(kandownDir);
1294
1293
  // 📖 Refuse to commit if not inside a git repo — silently failing here would
1295
1294
  // let the user believe they versioned their tasks when they didn't.
@@ -1335,34 +1334,16 @@ function shellCommit(rawArgs) {
1335
1334
  }
1336
1335
  }
1337
1336
 
1338
- function cmdShell(subcmd, rawArgs) {
1339
- switch (subcmd) {
1340
- case 'list':
1341
- case 'ls':
1342
- shellList(rawArgs);
1343
- return;
1344
- case 'show':
1345
- shellShow(rawArgs);
1346
- return;
1347
- case 'create':
1348
- case 'new':
1349
- shellCreate(rawArgs);
1350
- return;
1351
- case 'move':
1352
- shellMove(rawArgs);
1353
- return;
1354
- case 'assign':
1355
- shellAssign(rawArgs);
1356
- return;
1357
- case 'commit':
1358
- shellCommit(rawArgs);
1359
- return;
1360
- case undefined:
1361
- case 'help':
1362
- case '--help':
1363
- case '-h':
1364
- out(`
1365
- ${c.bold}kandown shell${c.reset} ${c.dim}· task commands (one-shot, scriptable)${c.reset}
1337
+ /**
1338
+ * 📖 Prints the one-shot task-command cheatsheet. Reachable as `kandown tasks`
1339
+ * (or `kandown tasks help`) — the commands themselves (list/show/create/move/
1340
+ * assign/commit) are top-level, not nested under a "shell" prefix, since
1341
+ * they're the most basic operations of the product and scripts/agents should
1342
+ * never need to remember a wrapper word to reach them.
1343
+ */
1344
+ function printTaskCommandsHelp() {
1345
+ out(`
1346
+ ${c.bold}kandown${c.reset} ${c.dim}· one-shot task commands (scriptable, agent-friendly)${c.reset}
1366
1347
 
1367
1348
  ${c.bold}Commands:${c.reset}
1368
1349
  ${c.cyan}list${c.reset} [-s status] [-a assignee] [-t tag] [-p priority] [--archived] [--json]
@@ -1373,18 +1354,151 @@ ${c.bold}Commands:${c.reset}
1373
1354
  ${c.cyan}commit${c.reset} [-m "message"] (git add tasks/ + .kandown/kandown.json + git commit)
1374
1355
 
1375
1356
  ${c.bold}Examples:${c.reset}
1376
- ${c.dim}$${c.reset} kandown shell list --json | jq '.[] | select(.priority=="P1")'
1377
- ${c.dim}$${c.reset} kandown shell create "Refactor auth middleware" -p P1 -t backend
1378
- ${c.dim}$${c.reset} kandown shell move t42 Done
1379
- ${c.dim}$${c.reset} kandown shell assign t42 alice
1380
- ${c.dim}$${c.reset} kandown shell commit -m "tasks: add auth refactor"
1357
+ ${c.dim}$${c.reset} kandown list --json | jq '.[] | select(.priority=="P1")'
1358
+ ${c.dim}$${c.reset} kandown create "Refactor auth middleware" -p P1 -t backend
1359
+ ${c.dim}$${c.reset} kandown move t42 Done
1360
+ ${c.dim}$${c.reset} kandown assign t42 alice
1361
+ ${c.dim}$${c.reset} kandown commit -m "tasks: add auth refactor"
1381
1362
  `);
1382
- return;
1383
- default:
1384
- err(`Unknown shell command: ${subcmd}`);
1385
- log(` Run ${c.cyan}kandown shell help${c.reset} for the list.`);
1386
- process.exit(1);
1363
+ }
1364
+
1365
+ /* ═════════════ kandown work — agent entrypoint ═════════════ */
1366
+ /**
1367
+ * 📖 `kandown work` replaces the "paste the rules into AGENTS.md/CLAUDE.md"
1368
+ * pattern: instead of a stale copy embedded in the project's agent file at
1369
+ * init time, the CLI serves the rules fresh on every invocation — always in
1370
+ * sync with the installed version — plus a live digest of the board so the
1371
+ * agent gets its marching orders and its context in one call.
1372
+ *
1373
+ * Layering (base → global → project), each optional except the base:
1374
+ * 1. Base rules — templates/AGENT_KANDOWN.md, shipped with the package.
1375
+ * 2. Global — ~/.kandown/instructions.md, applies to every project
1376
+ * on this machine (team conventions, personal style).
1377
+ * 3. Project — .kandown/instructions.md, this project only.
1378
+ * Later layers are appended, not merged — the agent reads all of them.
1379
+ */
1380
+
1381
+ const PRIORITY_RANK = { P1: 0, P2: 1, P3: 2, P4: 3 };
1382
+ function priorityRank(p) {
1383
+ return Object.prototype.hasOwnProperty.call(PRIORITY_RANK, p) ? PRIORITY_RANK[p] : 4;
1384
+ }
1385
+
1386
+ /**
1387
+ * 📖 Builds the "Current board" section: column counts, tasks per column
1388
+ * (with blocked-by annotations), and a "Next actionable task" pick — the
1389
+ * task in the most-advanced non-terminal column (closest to done, so
1390
+ * in-flight work finishes before new work starts) with no unresolved
1391
+ * `depends_on`, tie-broken by priority. Mirrors the same gate logic used by
1392
+ * `move`/the TUI/the web store.
1393
+ */
1394
+ function buildBoardDigest(kandownDir) {
1395
+ const config = readKandownConfig(kandownDir);
1396
+ const columns = (config && config.board && Array.isArray(config.board.columns) && config.board.columns.length > 0)
1397
+ ? config.board.columns
1398
+ : ['Backlog', 'Todo', 'In Progress', 'Review', 'Done'];
1399
+ const terminalCol = columns[columns.length - 1];
1400
+ const terminalLower = terminalCol.toLowerCase();
1401
+
1402
+ const rows = [];
1403
+ for (const id of listAllTaskIds(kandownDir)) {
1404
+ const path = findTaskFile(kandownDir, id);
1405
+ if (!path) continue;
1406
+ let content;
1407
+ try { content = readFileSync(path, 'utf8'); } catch { continue; }
1408
+ const parsed = parseFrontmatter(content);
1409
+ const fm = parsed.frontmatter;
1410
+ const isArchived = fm.archived === true || fm.archived === 'true' || path.includes('/archive/');
1411
+ if (isArchived) continue;
1412
+ rows.push({ id, fm });
1413
+ }
1414
+
1415
+ if (rows.length === 0) {
1416
+ return `## Current board\n\n(no tasks yet)`;
1387
1417
  }
1418
+
1419
+ const resolved = new Map();
1420
+ for (const r of rows) {
1421
+ const isArch = r.fm.archived === true || r.fm.archived === 'true';
1422
+ resolved.set(r.id, isArch || (r.fm.status || '').toLowerCase() === terminalLower);
1423
+ }
1424
+
1425
+ const byColumn = new Map(columns.map(col => [col, []]));
1426
+ for (const r of rows) {
1427
+ const status = r.fm.status || columns[0];
1428
+ const col = columns.find(c => c.toLowerCase() === String(status).toLowerCase()) || columns[0];
1429
+ const deps = Array.isArray(r.fm.depends_on) ? r.fm.depends_on : [];
1430
+ const blocked = deps.filter(d => typeof d === 'string' && d.trim() && d !== r.id && !resolved.get(d));
1431
+ // 📖 `col` always resolves to one of `columns` (falls back to columns[0]),
1432
+ // which the Map was seeded with above — the entry always exists.
1433
+ byColumn.get(col).push({ ...r, blocked });
1434
+ }
1435
+
1436
+ const lines = ['## Current board', ''];
1437
+ lines.push(`**Columns:** ${columns.map(col => `${col} (${(byColumn.get(col) || []).length})`).join(' · ')}`);
1438
+
1439
+ for (const col of columns) {
1440
+ const tasks = (byColumn.get(col) || []).sort((a, b) => priorityRank(a.fm.priority) - priorityRank(b.fm.priority));
1441
+ if (tasks.length === 0) continue;
1442
+ lines.push('', `### ${col}`);
1443
+ for (const t of tasks) {
1444
+ const pri = t.fm.priority ? `[${t.fm.priority}] ` : '';
1445
+ const assignee = t.fm.assignee ? ` (@${t.fm.assignee})` : '';
1446
+ const blockedStr = t.blocked.length > 0 ? ` ⛔ blocked by ${t.blocked.join(', ')}` : '';
1447
+ lines.push(`- ${t.id} ${pri}${t.fm.title || '(untitled)'}${assignee}${blockedStr}`);
1448
+ }
1449
+ }
1450
+
1451
+ const actionable = rows.filter(r => {
1452
+ const status = r.fm.status || columns[0];
1453
+ if (status.toLowerCase() === terminalLower) return false;
1454
+ const deps = Array.isArray(r.fm.depends_on) ? r.fm.depends_on : [];
1455
+ return !deps.some(d => typeof d === 'string' && d.trim() && d !== r.id && !resolved.get(d));
1456
+ });
1457
+ const next = actionable.sort((a, b) => {
1458
+ const idxA = columns.findIndex(col => col.toLowerCase() === (a.fm.status || columns[0]).toLowerCase());
1459
+ const idxB = columns.findIndex(col => col.toLowerCase() === (b.fm.status || columns[0]).toLowerCase());
1460
+ if (idxA !== idxB) return idxB - idxA; // most-advanced non-terminal column first
1461
+ return priorityRank(a.fm.priority) - priorityRank(b.fm.priority);
1462
+ })[0];
1463
+
1464
+ lines.push('', '### Next actionable task');
1465
+ lines.push(next
1466
+ ? `→ **${next.id}** — ${next.fm.title || '(untitled)'} (${next.fm.priority || 'no priority'}, ${next.fm.status || columns[0]})`
1467
+ : 'None — every task is done, archived, or blocked.');
1468
+
1469
+ return lines.join('\n');
1470
+ }
1471
+
1472
+ async function cmdWork(rawArgs) {
1473
+ const { kandownDir } = ensureKandownDir(rawArgs);
1474
+
1475
+ let baseRules = '';
1476
+ try {
1477
+ baseRules = readFileSync(join(PKG_ROOT, 'templates', 'AGENT_KANDOWN.md'), 'utf8').trim();
1478
+ } catch (e) {
1479
+ warn(`Could not read base rules (${e.message})`);
1480
+ }
1481
+
1482
+ let globalInstructions = '';
1483
+ const globalPath = join(homedir(), '.kandown', 'instructions.md');
1484
+ if (existsSync(globalPath)) {
1485
+ try { globalInstructions = readFileSync(globalPath, 'utf8').trim(); }
1486
+ catch (e) { warn(`Could not read global instructions (${e.message})`); }
1487
+ }
1488
+
1489
+ let projectInstructions = '';
1490
+ const projectPath = join(kandownDir, 'instructions.md');
1491
+ if (existsSync(projectPath)) {
1492
+ try { projectInstructions = readFileSync(projectPath, 'utf8').trim(); }
1493
+ catch (e) { warn(`Could not read project instructions (${e.message})`); }
1494
+ }
1495
+
1496
+ const sections = [baseRules];
1497
+ if (globalInstructions) sections.push(`## Global instructions\n\n${globalInstructions}`);
1498
+ if (projectInstructions) sections.push(`## Project-specific instructions\n\n${projectInstructions}`);
1499
+ sections.push(buildBoardDigest(kandownDir));
1500
+
1501
+ out(sections.filter(Boolean).join('\n\n---\n\n'));
1388
1502
  }
1389
1503
 
1390
1504
  function parsePort(value) {
@@ -2503,11 +2617,13 @@ if (cmd === '--version' || cmd === '-v') {
2503
2617
  const skipUpdate = process.argv.slice(2).includes('--no-update-check');
2504
2618
 
2505
2619
  // 📖 Update policy (M1): the check only runs for INTERACTIVE commands.
2506
- // `shell` (scripts/agents) and `daemon` (spawned children, status probes)
2507
- // must never pay a network round-trip nor risk a mid-pipeline respawn.
2508
- // Inside checkForUpdate there are further guards: 24h throttle, TTY-only,
2509
- // and the KANDOWN_NO_UPDATE=1 opt-out.
2510
- const SCRIPTED_COMMANDS = new Set(['shell', 'daemon']);
2620
+ // The one-shot task commands (scripts/agents) and `daemon` (spawned
2621
+ // children, status probes) must never pay a network round-trip nor risk a
2622
+ // mid-pipeline respawn. Inside checkForUpdate there are further guards: 24h
2623
+ // throttle, TTY-only, and the KANDOWN_NO_UPDATE=1 opt-out.
2624
+ const SCRIPTED_COMMANDS = new Set([
2625
+ 'list', 'ls', 'show', 'create', 'new', 'move', 'assign', 'commit', 'tasks', 'work', 'daemon',
2626
+ ]);
2511
2627
  if (!skipUpdate && !SCRIPTED_COMMANDS.has(cmd)) await checkForUpdate(process.argv);
2512
2628
 
2513
2629
  switch (cmd) {
@@ -2532,13 +2648,47 @@ switch (cmd) {
2532
2648
  cmdUpdate(rest);
2533
2649
  break;
2534
2650
 
2535
- case 'shell': {
2536
- // 📖 Two-level command: `kandown shell <subcmd> [...args]`. Pull the
2537
- // first non-flag arg as the subcommand and forward the rest.
2538
- const [shellSubcmd, ...shellRest] = rest;
2539
- cmdShell(shellSubcmd, shellRest);
2651
+ // 📖 One-shot task commands — top-level, no "shell" wrapper. These are the
2652
+ // most basic operations of the product (list/read/create/move/assign a
2653
+ // task, commit the board to git); nesting them under a prefix only added
2654
+ // friction for scripts and AI agents, which is exactly who these are for.
2655
+ case 'list':
2656
+ case 'ls':
2657
+ cmdList(rest);
2658
+ break;
2659
+
2660
+ case 'show':
2661
+ cmdShow(rest);
2662
+ break;
2663
+
2664
+ case 'create':
2665
+ case 'new':
2666
+ cmdCreate(rest);
2667
+ break;
2668
+
2669
+ case 'move':
2670
+ cmdMove(rest);
2671
+ break;
2672
+
2673
+ case 'assign':
2674
+ cmdAssign(rest);
2675
+ break;
2676
+
2677
+ case 'commit':
2678
+ cmdCommit(rest);
2679
+ break;
2680
+
2681
+ case 'tasks':
2682
+ // 📖 `kandown tasks` — cheatsheet for the commands above (`list help`
2683
+ // etc. would collide with real usage, so the index lives on its own verb).
2684
+ printTaskCommandsHelp();
2685
+ break;
2686
+
2687
+ case 'work':
2688
+ // 📖 `kandown work` — the agent entrypoint (rules + live board digest).
2689
+ // See cmdWork's doc comment for the full rationale.
2690
+ await cmdWork(rest);
2540
2691
  break;
2541
- }
2542
2692
 
2543
2693
  case 'help':
2544
2694
  case '--help':
package/dist/index.html CHANGED
@@ -113,7 +113,7 @@ Error generating stack: `+y.message+`
113
113
 
114
114
  ## Subtasks
115
115
 
116
- `}}async function Uz(t,e){try{return await(await(await(await t.getDirectoryHandle("archive",{create:!1})).getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}}async function Joe(t,e){try{return await(await t.getDirectoryHandle("archive",{create:!1})).getFileHandle(`${e}.md`),!0}catch{return!1}}async function Vz(t,e){try{return await t.getDirectoryHandle("archive",{create:e})}catch{return null}}async function ece(t){if(qt())return eS();const e=new Set;for await(const n of t.values())n.kind==="file"&&n.name.endsWith(".md")&&e.add(n.name.slice(0,-3));try{const n=await t.getDirectoryHandle("archive",{create:!1});for await(const a of n.values())a.kind==="file"&&a.name.endsWith(".md")&&e.add(a.name.slice(0,-3))}catch{}return[...e].sort((n,a)=>n.localeCompare(a,void 0,{numeric:!0}))}async function Es(t,e){if(qt())try{const i=await tS(e);return Rg(i)}catch{return vj(e)}const a=await(async i=>{try{return await(await(await i.getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}})(t)??await Uz(t,e);return a!==null?Rg(a):vj(e)}async function tce(t,e){if(qt())try{const a=await tS(e);return{ok:!0,task:Rg(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 Uz(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:Rg(n)}}catch(a){return{ok:!1,reason:"corrupted",error:a}}}async function iu(t,e,n,a){const i=Q4(n,a);if(qt())return Voe(e,i);try{const o=await(await(await Joe(t,e)?await Vz(t,!0):t).getFileHandle(`${e}.md`,{create:!0})).createWritable();try{await o.write(i)}finally{await o.close()}}catch(r){throw tk(r,`${e}.md`)}}async function yj(t,e){if(qt())return Zoe(e);try{await t.removeEntry(`${e}.md`)}catch{}try{await(await t.getDirectoryHandle("archive",{create:!1})).removeEntry(`${e}.md`)}catch{}}async function nce(t,e){await hs(`/api/tasks/${encodeURIComponent(t)}/archive`,{method:"POST",body:e,headers:{"Content-Type":"text/plain"}})}async function ace(t,e){await hs(`/api/tasks/${encodeURIComponent(t)}/unarchive`,{method:"POST",body:e,headers:{"Content-Type":"text/plain"}})}async function ice(t,e,n,a){const i=Q4(n,a);if(qt())return nce(e,i);try{const o=await(await(await Vz(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 tk(r,`archive/${e}.md`)}}async function rce(t,e,n,a){const i=Q4(n,a);if(qt())return ace(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 tk(r,`${e}.md`)}}const sce="kanban-md",oce=1,ap="recentProjects";function nS(){return new Promise((t,e)=>{const n=indexedDB.open(sce,oce);n.onerror=()=>e(n.error),n.onsuccess=()=>t(n.result),n.onupgradeneeded=()=>{const a=n.result;a.objectStoreNames.contains(ap)||a.createObjectStore(ap,{keyPath:"id"})}})}async function T2(t){const e=await nS();return new Promise((n,a)=>{const i=e.transaction(ap,"readwrite");i.objectStore(ap).put(t),i.oncomplete=()=>n(),i.onerror=()=>a(i.error)})}async function jy(){const t=await nS();return new Promise((e,n)=>{const i=t.transaction(ap,"readonly").objectStore(ap).getAll();i.onsuccess=()=>{const r=i.result||[];r.sort((s,o)=>o.lastOpened-s.lastOpened),e(r.slice(0,10))},i.onerror=()=>n(i.error)})}async function cce(t){const e=await nS();return new Promise((n,a)=>{const i=e.transaction(ap,"readwrite");i.objectStore(ap).delete(t),i.oncomplete=()=>n(),i.onerror=()=>a(i.error)})}async function _j(t,e=!0){const n={mode:e?"readwrite":"read"};try{return await t.queryPermission(n)==="granted"||await t.requestPermission(n)==="granted"}catch(a){return console.warn("[FS] verifyPermission: handle is no longer valid:",a),!1}}function Zz(t=Ci){const e=t.board.columns;return e[e.length-1]??"Done"}function pce(t,e=Ci){return t===Zz(e)||t.toLowerCase()==="archived"}class lce{dirHandle=null;tasksDirHandle=null;intervalId=null;configHash=null;taskHashes=new Map;knownTaskIds=new Set;listeners=new Map;debounceTimers=new Map;debounceDelay=150;consecutiveErrors=0;maxConsecutiveErrors=5;disabled=!1;start(e,n){this.dirHandle=e,this.tasksDirHandle=n,this.consecutiveErrors=0,this.disabled=!1,this.initHashes(),this.intervalId=setInterval(()=>void this.tick(),300)}stop(){this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null),this.configHash=null,this.taskHashes.clear(),this.knownTaskIds.clear(),this.listeners.clear(),this.debounceTimers.forEach(e=>clearTimeout(e)),this.debounceTimers.clear(),this.consecutiveErrors=0,this.disabled=!1}isDisabled(){return this.disabled}on(e,n){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(n),()=>{this.listeners.get(e)?.delete(n)}}getKnownTaskIds(){return Array.from(this.knownTaskIds)}async initHashes(){if(!(!this.dirHandle||!this.tasksDirHandle))try{const e=await kj(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 kj(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 wj(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 wj(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 kj(t){try{return await(await(await t.getFileHandle("kandown.json")).getFile()).text()}catch{return null}}async function wj(t,e){try{return await(await(await t.getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}}const al=new lce,dce={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 xj=null;function Gz(){return"Notification"in window?Notification.permission:"unsupported"}async function uce(){return"Notification"in window?await Notification.requestPermission():"unsupported"}function P2({title:t,body:e,config:n}){if(n.notifications.sound&&mce(n.notifications.soundId),!(!n.notifications.browser||Gz()!=="granted"))try{new Notification(t,{body:e})}catch{}}function mce(t){const e=dce[t],n=window.AudioContext??window.webkitAudioContext;if(!(!n||e.length===0))try{xj??=new n;const a=xj;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 Mf(t,e={}){const{maxAttempts:n=3,baseDelayMs:a=500,retryableCheck:i=Boe}=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 fce(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 gce(){const t=await eS();return await Promise.all(t.map(async n=>{const{frontmatter:a,body:i}=await qz(n),r={...a,id:a.id||n,status:a.status||"Backlog"},{subtasks:s,bodyWithoutSubtasks:o}=Ds(i);return{id:n,frontmatter:r,body:o,subtasks:s}}))}async function hce(t){const e=await ece(t),n=await Promise.all(e.map(async r=>{const s=await tce(t,r);return{id:r,result:s}})),a=[],i=[];for(const{id:r,result:s}of n)if(s.ok){const o={...s.task.frontmatter,id:s.task.frontmatter.id||r,status:s.task.frontmatter.status||"Backlog"},{subtasks:c,bodyWithoutSubtasks:p}=Ds(s.task.body);a.push({id:r,frontmatter:o,body:p,subtasks:c})}else{if(s.reason==="not-found")continue;i.push(r)}return{tasks:a,failedIds:i}}async function Aj(t,e,n){const a=[];for(const s of e){const o=s.name;s.tasks.forEach((c,p)=>{const d=(async()=>{const{frontmatter:m,body:f}=await Es(t,c.id);await iu(t,c.id,{...m,id:c.id,status:o,order:p},f)})();a.push({id:c.id,promise:d})})}const i=await Promise.allSettled(a.map(s=>s.promise)),r=[];return i.forEach((s,o)=>{s.status==="rejected"&&r.push(a[o].id)}),{failedIds:r}}function rl(t){zoe(t.ui.theme,t.ui.skin,t.ui.font,t.ui.background)}function v3(t){return{title:t.frontmatter.title||t.id,status:t.frontmatter.status||"Backlog",body:t.body,subtasks:t.subtasks}}function M2(t){fu.clear(),t.forEach(e=>{fu.set(e.id,v3(e))})}function bce(t){const e=t.split(/[\\/]+/).filter(Boolean),n=e[e.length-1];return n===".kandown"?e[e.length-2]??"Project":n??"Project"}function vce(t,e){return e.reduce((n,a,i)=>{const r=t[i]?.done??!1;return n+(a.done&&!r?1:0)},0)}function yce(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 _ce=0;const fu=new Map,Lf=new Map;let L2=null;const ge=Poe((t,e)=>({isOpen:!1,loading:!1,dirHandle:null,projectName:null,tasksDirHandle:null,boardTitle:"Project Kanban",columns:[],archivedTasks:[],showArchives:!1,showMetadata:!0,taskContents:new Map,searchMatches:new Map,viewMode:localStorage.getItem("kandown:view")||"board",density:localStorage.getItem("kandown:density")||"comfortable",filters:{search:"",priority:null,tag:null,assignee:null,ownerType:null},commandOpen:!1,cheatsheetOpen:!1,drawerTaskId:null,drawerData:null,currentPage:"board",config:Ci,recentProjects:[],toasts:[],agentHook:null,isReloading:!1,lastReloadError:null,failedTaskIds:[],watcherError:null,hasUnsavedDrawerEdits:!1,lastSaveError:null,drawerRecoveryData:new Map,drawerBaseVersion:null,conflictState:null,showConflictModal:!1,openFolder:async()=>{if(!Hz()){const c=navigator.userAgent||"this browser";e().toast(new Hoe(c).message,"error",8e3);return}let n;try{n=await Yoe()}catch(c){c instanceof J4?e().toast("Permission denied — please grant access to the project folder","error"):e().toast("Failed to open folder: "+c.message,"error");return}if(!n)return;const{projectHandle:a,kandownHandle:i,tasksHandle:r}=n,s=a.name;t({dirHandle:i,tasksDirHandle:r,projectName:s}),window.history.pushState({},"",`?p=${encodeURIComponent(s)}`);const o=qt()?E2():null;try{await T2({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 jy();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 _j(n.handle,!0)){let r=!1;try{await Ly(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 cce(n.id);const s=await jy();t({recentProjects:s})}catch{}return}e().toast("Permission denied — please grant access to the folder","error");return}try{const r=await Ly(n.handle),s=await b3(n.handle),o=n.handle.name;t({dirHandle:r,tasksDirHandle:s,projectName:o}),window.history.pushState({},"",`?p=${encodeURIComponent(o)}`);try{await T2({...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=E2();if(!n)throw new Error("No server root");await bj();const a=bce(n),i=await Bz();rl(i);const r=await eS(),s=await Promise.all(r.map(async f=>{const{frontmatter:h,body:b}=await qz(f),k={...h,id:h.id||f,status:h.status||"Backlog"},{subtasks:w,bodyWithoutSubtasks:A}=Ds(b);return{id:f,frontmatter:k,body:A,subtasks:w}}));M2(s);const o=s.map(f=>({frontmatter:f.frontmatter,body:qd(f.body,f.subtasks)})),c=S2(o,i.board.columns),p=$2(o),d=c.reduce((f,h)=>f+h.tasks.length,0),m=new Map;if(d<=10)for(const f of s)m.set(f.frontmatter.id,{frontmatter:f.frontmatter,subtasks:f.subtasks,body:f.body});t({loading:!1,isOpen:!0,config:i,columns:c,archivedTasks:p,boardTitle:"Project Kanban",projectName:a,taskContents:m,searchMatches:new Map}),window.history.pushState({},"",`?p=${encodeURIComponent(a)}`),e().setupWatcher(),e().refreshAgentHook()}catch{t({loading:!1,isOpen:!1}),e().toast("Impossible de charger le projet. Relancez `kandown`.","error")}},tryAutoOpenServerProject:async()=>{if(!qt())return;const n=E2();if(!n)return;await bj();const a=await jy(),i=a.find(p=>p.kandownDir===n);if(!i){await e().openServerProject();return}if(!await _j(i.handle,!0)){await e().openServerProject();return}const s=await Ly(i.handle),o=await b3(i.handle),c=i.handle.name;t({dirHandle:s,tasksDirHandle:o,projectName:c,recentProjects:a,isOpen:!0}),window.history.pushState({},"",`?p=${encodeURIComponent(c)}`),await T2({...i,lastOpened:Date.now()}),await e().loadConfig(),await e().reloadBoard(),e().setupWatcher()},loadConfig:async()=>{const{dirHandle:n}=e();if(!(!n&&!qt()))try{const a=await Xoe(n);if(a.ok){t({config:a.config}),rl(a.config);return}if(a.reason==="corrupted"){if(a.rawContent&&n)try{const r=await(await n.getFileHandle("kandown.json.backup",{create:!0})).createWritable();try{await r.write(a.rawContent)}finally{await r.close()}}catch{}e().toast("kandown.json is corrupted — using default settings. A backup was saved as kandown.json.backup.","warning",1e4)}t({config:Ci}),rl(Ci)}catch{t({config:Ci}),rl(Ci)}},updateConfig:async n=>{const{dirHandle:a,config:i}=e();if(!a&&!qt())return;const r=n(i);t({config:r}),rl(r);try{await Qoe(a,r)}catch(s){const o=s;o instanceof $o?e().toast("Disk is full — settings were not saved.","error",8e3):e().toast("Failed to save config: "+o.message,"error")}},reloadBoard:async()=>{const{tasksDirHandle:n,config:a}=e();t({isReloading:!0,lastReloadError:null});try{if(qt()){const i=await gce();M2(i);const r=i.map(d=>({frontmatter:d.frontmatter,body:qd(d.body,d.subtasks)})),s=S2(r,a.board.columns),o=$2(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 hce(n);M2(i);const s=i.map(m=>({frontmatter:m.frontmatter,body:qd(m.body,m.subtasks)})),o=S2(s,a.board.columns),c=$2(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=qt();if(!d&&!e().tasksDirHandle)return;const m=s.find(E=>E.name===a),f=s.find(E=>E.name===i);if(!m||!f)return;const h=m.tasks.findIndex(E=>E.id===n);if(h===-1)return;const b=m.tasks[h];if(!b)return;if(pce(i,o)){const E=new Map,T=Zz(o).toLowerCase();for(const D of s)for(const F of D.tasks){const H=F.frontmatter&&(F.frontmatter.archived===!0||F.frontmatter.archived==="true");E.set(F.id,{exists:!0,resolved:H||F.id===n||D.name.toLowerCase()===T})}const L=[];for(const D of b.dependsOn){if(typeof D!="string"||!D.trim()||D===n)continue;const F=E.get(D);(!F||!F.resolved)&&L.push(D)}if(L.length>0){const D=L.length===1?L[0]:`${L.slice(0,-1).join(", ")} and ${L[L.length-1]}`;e().toast(`Cannot move ${n} to ${i}: blocked by ${D}`,"error");return}}const k=s.map(E=>({...E,tasks:[...E.tasks]})),w=k.find(E=>E.name===a),A=k.find(E=>E.name===i),[S]=w.tasks.splice(h,1);/done|termin|closed|complet/i.test(i)?S.checked=!0:S.checked=!1,r!==void 0?A.tasks.splice(r,0,S):A.tasks.push(S),t({columns:k});try{const{tasksDirHandle:E}=e();if(!E&&!d)return;const T=a===i?k.filter(D=>D.name===i):k.filter(D=>D.name===a||D.name===i),{failedIds:L}=await Mf(()=>Aj(E??null,T,o.board.columns),{maxAttempts:3});if(L.length>0){const D=L.length===1?`Could not save move for ${L[0]}`:`${L.length} tasks could not be moved`;e().toast(D,"warning",8e3),await e().reloadBoard()}}catch(E){const T=E;T instanceof $o?e().toast("Disk is full — move was not saved. Free up space and try again.","error",8e3):e().toast("Failed to save: "+T.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&&!qt())return;const d=r.map(h=>({...h,tasks:[...h.tasks]})),m=d.find(h=>h.name===n);if(!m)return;const[f]=m.tasks.splice(a,1);m.tasks.splice(i,0,f),t({columns:d});try{const{tasksDirHandle:h}=e(),b=qt();if(!h&&!b)return;const{failedIds:k}=await Mf(()=>Aj(h??null,[m],o.board.columns),{maxAttempts:3});k.length>0&&(e().toast(`Could not save reorder for ${k.length} task(s)`,"warning",8e3),await e().reloadBoard())}catch(h){const b=h;b instanceof $o?e().toast("Disk is full — reorder was not saved.","error",8e3):e().toast("Failed to save: "+b.message,"error"),t({columns:r,taskContents:c,searchMatches:p})}},addColumn:async n=>{const a=n.trim();if(!a)return;const{config:i}=e();i.board.columns.some(r=>r.toLowerCase()===a.toLowerCase())||(await e().updateConfig(r=>({...r,board:{...r.board,columns:[...r.board.columns,a]}})),await e().reloadBoard())},renameColumn:async(n,a)=>{const i=a.trim(),{columns:r,tasksDirHandle:s,config:o}=e();if(!s||!i||i.toLowerCase()===n.toLowerCase())return;if(r.some(d=>d.name.toLowerCase()===i.toLowerCase())){e().toast("Column already exists","error");return}const c=r,p=r.map(d=>d.name===n?{...d,name:i}:d);t({columns:p});try{const d=c.find(m=>m.name===n);if(d){const f=(await Promise.allSettled(d.tasks.map(async(h,b)=>{const{frontmatter:k,body:w}=await Es(s,h.id);await iu(s,h.id,{...k,id:h.id,status:i,order:b},w)}))).filter(h=>h.status==="rejected").length;f>0&&e().toast(`${f} task(s) could not be renamed`,"warning",8e3)}await e().updateConfig(m=>{const f={...m.board.columnColors??{}},h=f[n.toLowerCase()];h&&(f[i.toLowerCase()]=h,delete f[n.toLowerCase()]);const b=m.board.columns.some(k=>k.toLowerCase()===n.toLowerCase())?m.board.columns:[...m.board.columns,n];return{...m,board:{...m.board,columns:b.map(k=>k.toLowerCase()===n.toLowerCase()?i:k),columnColors:f}}}),await e().reloadBoard()}catch(d){e().toast("Failed to rename column: "+d.message,"error"),t({columns:c})}},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&&!qt())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&&!qt())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=>yj(i,p.id)))).filter(p=>p.status==="rejected").length;c>0&&e().toast(`${c} task(s) could not be deleted`,"warning",8e3),await e().updateConfig(p=>{const d={...p.board.columnColors??{}};return delete d[n.toLowerCase()],{...p,board:{...p.board,columns:p.board.columns.filter(m=>m.toLowerCase()!==n.toLowerCase()),columnColors:d}}}),await e().reloadBoard(),e().toast("Column deleted")}catch(o){e().toast("Failed to delete column: "+o.message,"error"),t({columns:s})}},createTask:async n=>{const{columns:a,tasksDirHandle:i,config:r,taskContents:s,searchMatches:o}=e();if(!i&&!qt()||!a.length)return null;const c=n||r.board.columns[0]||a[0].name,p=fce(a),d=a.find(w=>w.name===c)?.tasks.length??0,m={id:p,title:"",checked:!1,dependsOn:[],tags:[],assignee:null,priority:r.fields.priority?r.board.defaultPriority:null,ownerType:r.fields.ownerType?r.board.defaultOwnerType:"",progress:null,frontmatter:{}},f=a.map(w=>w.name===c?{...w,tasks:[...w.tasks,m]}:w),h=new Map(s),b={id:p,title:"",status:c,order:d,priority:r.fields.priority?r.board.defaultPriority:"",tags:[],assignee:"",created:new Date().toISOString().slice(0,10),ownerType:r.fields.ownerType?r.board.defaultOwnerType:"",tools:""},k="";h.set(p,{frontmatter:b,subtasks:[],body:k}),t({columns:f,taskContents:h});try{const w=i||null;return await Mf(()=>iu(w,p,b,k),{maxAttempts:3}),e().toast(`Created ${p.replace(/^t/,"")}`),await e().openDrawer(p),p}catch(w){const A=w;return A instanceof $o?e().toast("Disk is full — task was not created.","error",8e3):e().toast("Failed to create: "+A.message,"error"),t({columns:a,taskContents:s,searchMatches:o}),null}},deleteTask:async n=>{const{columns:a,tasksDirHandle:i,taskContents:r,searchMatches:s}=e();if(!i&&!qt())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 yj(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&&!qt()))try{const{frontmatter:i,body:r}=await Es(a||null,n);await ice(a||null,n,{...i,archived:!0},r),e().drawerTaskId===n&&e().closeDrawer(),await e().reloadBoard(),e().toast("Archived")}catch(i){const r=i;r instanceof $o?e().toast("Disk is full — task was not archived.","error",8e3):e().toast("Failed to archive: "+r.message,"error")}},unarchiveTask:async n=>{const{tasksDirHandle:a}=e();if(!(!a&&!qt()))try{const{frontmatter:i,body:r}=await Es(a||null,n),s={...i};delete s.archived,await rce(a||null,n,s,r),await e().reloadBoard(),e().toast("Restored")}catch(i){const r=i;r instanceof $o?e().toast("Disk is full — task was not restored.","error",8e3):e().toast("Failed to restore: "+r.message,"error")}},setShowArchives:n=>t({showArchives:n}),setShowMetadata:n=>t({showMetadata:n}),openDrawer:async n=>{const{tasksDirHandle:a}=e();if(!(!a&&!qt()))try{const{frontmatter:i,body:r}=await Es(a,n),{subtasks:s,bodyWithoutSubtasks:o}=Ds(r),c={frontmatter:i,subtasks:s,body:o,savedAt:Date.now()},p=e().drawerRecoveryData.get(n),d=p?{frontmatter:p.frontmatter,subtasks:p.subtasks,body:p.body}:{frontmatter:i,subtasks:s,body:o},m=new Map(e().drawerRecoveryData);m.delete(n),t({drawerTaskId:n,drawerData:d,drawerBaseVersion:c,conflictState:null,showConflictModal:!1,hasUnsavedDrawerEdits:!!p,lastSaveError:null,drawerRecoveryData:m}),p&&e().toast("Restored your unsaved edits for this task","info")}catch(i){e().toast("Failed to open: "+i.message,"error")}},closeDrawer:()=>t({drawerTaskId:null,drawerData:null,drawerBaseVersion:null,conflictState:null,showConflictModal:!1,hasUnsavedDrawerEdits:!1,lastSaveError:null}),markDrawerDirty:()=>t({hasUnsavedDrawerEdits:!0}),forceCloseDrawer:()=>{const{drawerTaskId:n,drawerData:a}=e();if(n&&a){const i=new Map(e().drawerRecoveryData);i.set(n,{frontmatter:a.frontmatter,subtasks:a.subtasks,body:a.body}),t({drawerRecoveryData:i})}e().closeDrawer()},updateDrawerData:n=>{const{drawerData:a}=e();a&&t({drawerData:n(a)})},saveDrawer:async()=>{const{drawerTaskId:n,drawerData:a,tasksDirHandle:i,taskContents:r}=e();if(!n||!a)return;const s=qd(a.body,a.subtasks),o={...a.frontmatter,id:n};try{await Mf(()=>iu(i||null,n,o,s),{maxAttempts:3}),e().toast("Saved");const c=new Map(e().drawerRecoveryData);c.delete(n),t({drawerTaskId:null,drawerData:null,hasUnsavedDrawerEdits:!1,lastSaveError:null,drawerRecoveryData:c});const p=new Map(r);p.set(n,{frontmatter:o,subtasks:a.subtasks,body:a.body}),t({taskContents:p}),await e().reloadBoard()}catch(c){const p=c,d=p instanceof $o?"Disk is full — your edits are kept. Free up space and retry.":"Failed to save: "+p.message;e().toast(d,"error",8e3),t({lastSaveError:d})}},saveDrawerMetadata:async()=>{const{drawerTaskId:n,drawerData:a,tasksDirHandle:i,taskContents:r}=e();if(!(!n||!a))try{const s=qd(a.body,a.subtasks),o={...a.frontmatter,id:n};await Mf(()=>iu(i||null,n,o,s),{maxAttempts:3});const c=new Map(r);c.set(n,{frontmatter:o,subtasks:a.subtasks,body:a.body}),t({taskContents:c,hasUnsavedDrawerEdits:!1,lastSaveError:null}),await e().reloadBoard()}catch(s){const o=s,c=o instanceof $o?"Disk is full — your edits are kept.":"Failed to save: "+o.message;t({hasUnsavedDrawerEdits:!0,lastSaveError:c})}},setViewMode:n=>{localStorage.setItem("kandown:view",n),t({viewMode:n})},setDensity:n=>{localStorage.setItem("kandown:density",n),t({density:n})},setFilter:(n,a)=>{if(t(i=>({filters:{...i.filters,[n]:a}})),n==="search"){const{columns:i,tasksDirHandle:r,taskContents:s}=e(),o=a,c=i.flatMap(p=>p.tasks.map(d=>d.id));if(r){const p=c.filter(d=>!s.has(d));p.length>0?e().loadTaskContents(p).then(()=>{e().computeSearchMatches(o)}):e().computeSearchMatches(o)}}},clearFilters:()=>t({filters:{search:"",priority:null,tag:null,assignee:null,ownerType:null},searchMatches:new Map}),setCommandOpen:n=>t({commandOpen:n}),setCheatsheetOpen:n=>t({cheatsheetOpen:n}),refreshAgentHook:async()=>{if(!qt()){t({agentHook:null});return}const n=await Goe();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 Woe(n);if(i===null){e().toast("Could not reach the daemon","error");return}i.ok?e().toast(`Sent to ${a.label}`):e().toast(i.error||"Agent hook failed","error")},setCurrentPage:n=>t({currentPage:n}),loadTaskContents:async n=>{const{tasksDirHandle:a}=e();if(!a)return;const i=new Map(e().taskContents);await Promise.all(n.map(async r=>{if(!i.has(r))try{const{frontmatter:s,body:o}=await Es(a,r),{subtasks:c,bodyWithoutSubtasks:p}=Ds(o);i.set(r,{frontmatter:s,subtasks:c,body:p})}catch{}})),t({taskContents:i})},computeSearchMatches:n=>{if(!n.trim()){t({searchMatches:new Map});return}const{taskContents:a}=e(),i=new Map,r=n.toLowerCase();for(const[s,o]of a){const c=zz(o,r);c.length>0&&i.set(s,c)}t({searchMatches:i})},toast:(n,a="success",i)=>{const r=++_ce,s=i??(a==="error"||a==="warning"?6e3:2500);t(o=>({toasts:[...o.toasts,{id:r,message:n,type:a}]})),setTimeout(()=>{t(o=>({toasts:o.toasts.filter(c=>c.id!==r)}))},s)},dismissToast:n=>t(a=>({toasts:a.toasts.filter(i=>i.id!==n)})),resolveConflict:async n=>{const{conflictState:a,drawerData:i,tasksDirHandle:r,drawerTaskId:s,drawerBaseVersion:o}=e();if(!(!a||!r||!s))if(n==="reload"){const{frontmatter:c,body:p}=await Es(r,s),{subtasks:d,bodyWithoutSubtasks:m}=Ds(p);t({drawerData:{frontmatter:c,subtasks:d,body:m},drawerBaseVersion:{frontmatter:c,subtasks:d,body:m,savedAt:Date.now()},conflictState:null,showConflictModal:!1}),e().toast("Reloaded from disk")}else if(n==="overwrite"){if(i&&s&&o){const c=qd(i.body,i.subtasks),p={...i.frontmatter,id:s};try{await iu(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(qt()){L2&&clearInterval(L2),L2=setInterval(()=>{e().reloadBoard()},2e3);return}const{dirHandle:n,tasksDirHandle:a}=e();if(!n||!a)return;al.stop(),Lf.forEach(s=>clearTimeout(s)),Lf.clear(),al.start(n,a);const i=(s,o)=>{const c=Lf.get(s);c&&clearTimeout(c);const p=Math.max(2e3,e().config.notifications.editDebounceMs),d=setTimeout(()=>{Lf.delete(s);const m=e().config;m.notifications.taskEdits&&P2({title:"Task edited",body:`${o} changed on disk.`,config:m})},p);Lf.set(s,d)},r=async s=>{const{tasksDirHandle:o,config:c}=e();if(!o)return;let p,d;try{({frontmatter:p,body:d}=await Es(o,s))}catch(A){console.warn(`[Watcher] notifyTaskChange: failed to read ${s}:`,A);return}const{subtasks:m,bodyWithoutSubtasks:f}=Ds(d),h={id:s,frontmatter:{...p,id:p.id||s,status:p.status||"Backlog"},body:f,subtasks:m},b=v3(h),k=fu.get(s);if(!k){fu.set(s,b);return}c.notifications.statusChanges&&k.status!==b.status&&P2({title:"Task status changed",body:`${b.title}: ${k.status} → ${b.status}`,config:c});const w=vce(k.subtasks,b.subtasks);c.notifications.subtaskCompletions&&w>0&&P2({title:"Subtask completed",body:w===1?`${b.title}: 1 subtask completed.`:`${b.title}: ${w} subtasks completed.`,config:c}),yce(k,b)&&i(s,b.title),fu.set(s,b)};al.on("configChanged",()=>{try{e().loadConfig(),e().toast("Settings updated externally","info")}catch(s){console.error("[Watcher] configChanged handler error:",s)}}),al.on("taskChanged",async s=>{try{const{drawerTaskId:o,drawerBaseVersion:c,tasksDirHandle:p}=e();if(await r(s),o===s&&c&&p){let d,m;try{({frontmatter:d,body:m}=await Es(p,s))}catch(E){console.warn(`[Watcher] taskChanged: failed to re-read ${s}:`,E);return}const{subtasks:f,bodyWithoutSubtasks:h}=Ds(m),b=c,k=JSON.stringify(b.frontmatter)!==JSON.stringify(d),w=b.body!==h,A=JSON.stringify(b.subtasks)!==JSON.stringify(f);if(!k&&!w&&!A)return;let S="none";k&&(w||A)?S="full":k?S="metadata-only":(w||A)&&(S="body-only"),t({conflictState:{taskId:s,type:S,local:b,remote:{frontmatter:d,body:h,subtasks:f}},showConflictModal:S==="full"})}else await e().reloadBoard()}catch(o){console.error(`[Watcher] taskChanged handler error for ${s}:`,o)}}),al.on("newTaskDetected",async s=>{try{const{tasksDirHandle:o}=e();if(o){let c,p;try{({frontmatter:c,body:p}=await Es(o,s))}catch(f){console.warn(`[Watcher] newTaskDetected: failed to read ${s}:`,f),e().toast(`New task ${s} detected but could not be loaded`,"warning");return}const{subtasks:d,bodyWithoutSubtasks:m}=Ds(p);fu.set(s,v3({id:s,frontmatter:{...c,id:c.id||s,status:c.status||"Backlog"},body:m,subtasks:d}))}await e().reloadBoard()}catch(o){console.error(`[Watcher] newTaskDetected handler error for ${s}:`,o)}}),al.on("watcherError",s=>{t({watcherError:s}),e().toast(s,"warning",1e4)})},restartWatcher:()=>{const{dirHandle:n,tasksDirHandle:a}=e();!n||!a||(t({watcherError:null}),e().setupWatcher(),e().toast("File watcher restarted"))}}));jy().then(t=>{ge.setState({recentProjects:t})}).catch(t=>{console.warn("[Store] Failed to hydrate recent projects:",t)});rl(Ci);window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{rl(ge.getState().config)});const kce=Object.freeze(Object.defineProperty({__proto__:null,useStore:ge},Symbol.toStringTag,{value:"Module"}));function wce(){const t=ge(o=>o.config),e=ge(o=>o.updateConfig),[n,a]=$.useState(!1);if($.useEffect(()=>{a(!0)},[]),!n)return null;const i=t.ui.theme==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":t.ui.theme,r=i==="dark",s=()=>{const o=r?"light":"dark";e(c=>({...c,ui:{...c.ui,theme:o}}))};return _.jsx(at.button,{onClick:s,className:g3("relative inline-flex items-center rounded-full transition-all duration-300","border border-black/[0.08] dark:border-white/[0.12]","bg-black/[0.05] dark:bg-white/[0.08]","h-8 w-[52px] px-1"),title:r?"Switch to light mode":"Switch to dark mode",children:_.jsx(at.div,{className:g3("inline-flex items-center justify-center rounded-full shadow-md",r?"bg-[#1e293b] border border-white/[0.15]":"bg-black border border-black/20","h-[26px] w-[26px]"),animate:{x:r?22:0},transition:{type:"spring",stiffness:500,damping:30},children:_.jsx(at.div,{initial:{rotate:-90,scale:.5,opacity:0},animate:{rotate:0,scale:1,opacity:1},transition:{duration:.25,ease:"easeOut"},children:r?_.jsx(Zn.Moon,{size:13,className:"text-sky-300"}):_.jsx(Zn.Sun,{size:13,className:"text-amber-400"})},i)})})}function xce(t){const e=Nse(t,{stiffness:180,damping:22,mass:.8});return $.useEffect(()=>{e.set(t)},[t,e]),_z(e,a=>Math.round(a).toString())}const y3="0.17.2",Ace=({className:t})=>_.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 150 150",className:t,fill:"currentColor",children:[_.jsx("path",{d:"m57.6 64.6 0.1-0.1v-31.3c-0.1-3.5-2.7-5.6-5.7-5.6h-16.3v59.6c0.2-1.3 0.9-2.6 1.7-3.2l20.2-19.4z"}),_.jsx("path",{d:"m87.6 43.8c-3.4 0.1-7.1 1.3-9.9 3.7l-38.5 38.7c-2.1 2.1-3.4 4.8-3.5 7.5v26.7l77.5-76.4v-0.2h-25.6z"}),_.jsx("path",{d:"m108.1 96.4-6 5-22.4-20.8-14.6 14.2 21.1 21.4-5 4.1c-0.6 0.5-0.3 0.9 0.2 0.9h27.6c0.4 0 0.7-0.4 0.7-0.6v-24.8c0-0.7-1.1-0.3-1.6 0.3v0.3z"})]});function Cce(){const{t}=Cn(),e=ge(R=>R.dirHandle),n=ge(R=>R.isOpen),a=ge(R=>R.projectName),i=ge(R=>R.archivedTasks.length),r=ge(R=>R.showArchives),s=ge(R=>R.setShowArchives),o=ge(R=>R.columns),c=ge(R=>R.openFolder),p=ge(R=>R.reloadBoard),d=ge(R=>R.createTask),m=ge(R=>R.setCommandOpen),f=ge(R=>R.viewMode),h=ge(R=>R.setViewMode),b=ge(R=>R.density),k=ge(R=>R.setDensity),w=ge(R=>R.setCurrentPage),A=ge(R=>R.recentProjects),S=ge(R=>R.openRecentProject),E=ge(R=>R.filters),T=ge(R=>R.setFilter),L=ge(R=>R.clearFilters),D=ge(R=>R.config.fields),F=ge(R=>R.lastReloadError),H=ge(R=>R.watcherError),O=ge(R=>R.restartWatcher),[j,K]=$.useState(!1),[Q,ee]=$.useState(!0),U=$.useRef(null),J=$.useRef(null),W=o.reduce((R,Z)=>R+Z.tasks.length,0),V=xce(W),B=[];D.priority&&E.priority&&B.push({type:"priority",label:E.priority,value:E.priority}),D.tags&&E.tag&&B.push({type:"tag",label:"#"+E.tag,value:E.tag}),D.assignee&&E.assignee&&B.push({type:"assignee",label:"@"+E.assignee,value:E.assignee});const ne=[{label:t("filterBar.ownerAll"),value:""},{label:t("filterBar.ownerHuman"),value:"human"},{label:t("filterBar.ownerAI"),value:"ai"}],I=B.length>0||E.search||D.ownerType&&E.ownerType;return $.useEffect(()=>{if(!j)return;const R=Z=>{U.current&&!U.current.contains(Z.target)&&K(!1)};return document.addEventListener("mousedown",R),()=>document.removeEventListener("mousedown",R)},[j]),$.useEffect(()=>{const R=setTimeout(()=>ee(!1),5e3);return()=>clearTimeout(R)},[]),$.useEffect(()=>{document.title=a?`${a} · Kandown`:"Kandown"},[a]),_.jsxs(_.Fragment,{children:[(F||H)&&_.jsxs("div",{className:"flex items-center gap-2 px-5 py-1.5 bg-amber-500/10 border-b border-amber-500/30 text-[12px] text-amber-700 dark:text-amber-300",children:[_.jsx("span",{className:"flex-1 truncate",children:H?`⚠ ${H}`:`⚠ ${F}`}),H&&_.jsx("button",{type:"button",onClick:O,className:"px-2 py-0.5 rounded bg-amber-500/20 hover:bg-amber-500/30 text-[11.5px] font-medium",children:"Restart watcher"}),_.jsx("button",{type:"button",onClick:p,className:"px-2 py-0.5 rounded bg-amber-500/20 hover:bg-amber-500/30 text-[11.5px] font-medium",children:"Reload"})]}),_.jsxs("header",{className:"flex items-center justify-between px-5 h-[64px] border-b border-border bg-card/80 backdrop-blur-xl relative z-10",children:[_.jsxs("div",{className:"flex items-center gap-4 min-w-0",children:[_.jsx("div",{className:"flex items-center gap-2.5 flex-shrink-0",children:_.jsxs("button",{onClick:()=>window.history.pushState({},"",window.location.pathname),className:"flex items-center gap-2 cursor-pointer",children:[_.jsx(Ace,{className:"w-[34px] h-[34px] dark:text-white text-black"}),_.jsx(Sr,{mode:"wait",initial:!1,children:Q?_.jsxs(at.span,{className:"flex items-center gap-2",initial:{opacity:0},animate:{opacity:1},exit:{opacity:0},transition:{duration:.45},children:[_.jsx("span",{className:"text-[15px] font-semibold tracking-tight text-fg",children:"kandown"}),_.jsxs("span",{className:"inline-flex items-center h-5 px-1.5 text-[10.5px] font-semibold text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-500/15 rounded-md",children:["v",y3]})]},"boot"):a?_.jsx(at.span,{className:"text-[15px] font-semibold tracking-tight text-fg truncate max-w-[240px]",initial:{opacity:0},animate:{opacity:1},exit:{opacity:0},transition:{duration:.45},children:a},"project"):null})]})}),e&&_.jsxs(_.Fragment,{children:[_.jsx("div",{className:"w-px h-[20px] bg-black/[0.08] dark:bg-white/[0.08] flex-shrink-0"}),_.jsxs("div",{className:"flex items-center gap-2 px-3 h-9 bg-secondary/60 border border-border rounded-xl min-w-[200px] max-w-[280px] focus-within:border-border-focus focus-within:bg-secondary transition-all",children:[_.jsx(Zn.Search,{size:14,className:"text-fg-muted/60 flex-shrink-0"}),_.jsx("input",{ref:J,type:"text",placeholder:t("filterBar.searchPlaceholder"),value:E.search,onChange:R=>T("search",R.target.value),className:"bg-transparent border-none outline-none text-fg text-[13px] w-full placeholder:text-fg-muted/60"}),E.search?_.jsx("button",{onClick:()=>T("search",""),className:"text-fg-muted/60 hover:text-fg flex-shrink-0",children:_.jsx(Zn.X,{size:14})}):_.jsx("kbd",{className:"inline-flex items-center h-5 px-1.5 text-[10px] font-medium text-fg-muted/50 bg-black/[0.04] dark:bg-white/[0.08] rounded border border-black/[0.06] dark:border-white/[0.1]",children:"⌘K"})]}),_.jsxs("div",{className:"flex items-center gap-1.5 flex-wrap overflow-hidden",children:[_.jsx(Sr,{children:B.map(R=>_.jsxs(at.button,{initial:{opacity:0,scale:.9},animate:{opacity:1,scale:1},exit:{opacity:0,scale:.9},transition:{duration:.15},onClick:()=>T(R.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:[R.label,_.jsx(Zn.X,{size:10,className:"text-fg-muted/60"})]},R.type+R.value))}),D.ownerType&&_.jsx("div",{className:"flex items-center h-6 border border-black/[0.06] dark:border-white/[0.1] rounded-lg overflow-hidden",children:ne.map(R=>_.jsx("button",{onClick:()=>T("ownerType",R.value),className:`h-full px-2.5 text-[12px] transition-colors ${E.ownerType===R.value?"bg-black/[0.06] dark:bg-white/[0.12] text-fg":"text-fg-muted/70 hover:text-fg"}`,children:R.label},R.value))}),I&&_.jsx("button",{onClick:L,className:"text-[12px] text-fg-muted/60 hover:text-fg transition-colors",children:t("filterBar.clearAll")})]})]}),e&&_.jsxs(_.Fragment,{children:[_.jsx("div",{className:"w-px h-[20px] bg-black/[0.08] dark:bg-white/[0.08] flex-shrink-0"}),_.jsxs("div",{className:"relative flex-shrink-0",ref:U,children:[_.jsxs("button",{onClick:()=>K(R=>!R),className:"flex items-center gap-1.5 px-2.5 py-1.5 text-[13px] text-fg-muted hover:text-fg hover:bg-black/[0.04] dark:hover:bg-white/[0.06] rounded-lg transition-colors border border-transparent hover:border-black/[0.06] dark:hover:border-white/[0.1]",children:[_.jsx(Zn.Folder,{size:13,className:"text-fg-muted/70"}),_.jsxs("span",{className:"font-medium",children:[".",e.name]}),_.jsx(Zn.ChevronDown,{size:11,className:"opacity-50"})]}),_.jsx(Sr,{children:j&&_.jsx(at.div,{initial:{opacity:0,y:-4,scale:.98},animate:{opacity:1,y:0,scale:1},exit:{opacity:0,y:-4,scale:.98},transition:{duration:.12},className:"absolute top-full left-0 mt-2 min-w-[240px] glass rounded-xl shadow-[0_16px_48px_rgba(0,0,0,0.5)] overflow-hidden z-50",children:_.jsxs("div",{className:"py-1.5",children:[A.length>0&&_.jsxs(_.Fragment,{children:[_.jsx("div",{className:"px-3 py-1.5 text-[11px] font-semibold uppercase tracking-wider text-fg-muted/60",children:t("header.recentProjects")}),A.map(R=>_.jsxs("button",{onClick:()=>{K(!1),S(R)},className:"w-full flex items-center gap-2 px-3 py-2 text-[13.5px] text-left hover:bg-black/[0.04] dark:hover:bg-white/[0.06] transition-colors",children:[_.jsx(Zn.Folder,{size:12,className:"text-fg-muted/60"}),_.jsx("span",{className:"truncate",children:R.name}),R.id===e.name&&_.jsx(Zn.Check,{size:12,className:"ml-auto text-emerald-500"})]},R.id)),_.jsx("div",{className:"h-px bg-black/[0.06] dark:bg-white/[0.08] my-1.5 mx-2"})]}),_.jsxs("button",{onClick:()=>{K(!1),c()},className:"w-full flex items-center gap-2 px-3 py-2 text-[13.5px] text-left hover:bg-black/[0.04] dark:hover:bg-white/[0.06] transition-colors",children:[_.jsx(Zn.Plus,{size:12,className:"text-fg-muted/60"}),_.jsx("span",{children:t("header.openFolder...")})]})]})})})]})]})]}),_.jsx("div",{className:"flex items-center gap-2 flex-shrink-0",children:n||e?_.jsxs(_.Fragment,{children:[_.jsxs("div",{className:"flex items-center gap-2 mr-2 text-[12.5px] text-fg-muted/70",children:[_.jsx("span",{className:"inline-block w-1.5 h-1.5 rounded-full bg-emerald-500"}),_.jsx(at.span,{className:"tabular-nums font-medium",children:V}),_.jsx("span",{children:t("header.tasks")})]}),_.jsxs("div",{className:"flex items-center bg-black/[0.04] dark:bg-white/[0.06] border border-black/[0.06] dark:border-white/[0.1] rounded-xl p-0.5 h-10",children:[_.jsx("button",{onClick:()=>h("board"),className:`w-9 h-9 inline-flex items-center justify-center rounded-lg transition-all ${f==="board"?"bg-card text-fg shadow-sm":"text-fg-muted/70 hover:text-fg"}`,title:t("common.board"),children:_.jsx(Zn.LayoutBoard,{size:18})}),_.jsx("button",{onClick:()=>h("list"),className:`w-9 h-9 inline-flex items-center justify-center rounded-lg transition-all ${f==="list"?"bg-card text-fg shadow-sm":"text-fg-muted/70 hover:text-fg"}`,title:t("common.list"),children:_.jsx(Zn.LayoutList,{size:18})})]}),_.jsx(na,{variant:"icon",icon:"Archive",onClick:()=>s(!r),title:r?t("header.backToBoard"):`${t("header.archives")} (${i})`,className:r?"text-accent":""}),_.jsx(na,{variant:"icon",icon:"Density",onClick:()=>k(b==="compact"?"comfortable":"compact"),title:`Density: ${b}`}),_.jsx(na,{variant:"icon",icon:"Settings",onClick:()=>w("settings"),title:t("common.settings")}),_.jsx(wce,{}),_.jsx("div",{className:"w-px h-5 bg-black/[0.08] dark:bg-white/[0.08] mx-1"}),_.jsx(na,{variant:"secondary",icon:"Search",label:t("common.search"),shortcut:"⌘K",onClick:()=>m(!0),title:"Command palette (⌘K)"}),_.jsx(na,{variant:"icon",icon:"Refresh",onClick:p,title:`${t("common.reload")} (R)`}),_.jsx(na,{variant:"primary",icon:"Plus",label:t("common.newTask"),shortcut:"N",onClick:()=>d()})]}):_.jsx(na,{variant:"primary",label:t("common.openFolder"),onClick:c})})]})]})}/**
116
+ `}}async function Uz(t,e){try{return await(await(await(await t.getDirectoryHandle("archive",{create:!1})).getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}}async function Joe(t,e){try{return await(await t.getDirectoryHandle("archive",{create:!1})).getFileHandle(`${e}.md`),!0}catch{return!1}}async function Vz(t,e){try{return await t.getDirectoryHandle("archive",{create:e})}catch{return null}}async function ece(t){if(qt())return eS();const e=new Set;for await(const n of t.values())n.kind==="file"&&n.name.endsWith(".md")&&e.add(n.name.slice(0,-3));try{const n=await t.getDirectoryHandle("archive",{create:!1});for await(const a of n.values())a.kind==="file"&&a.name.endsWith(".md")&&e.add(a.name.slice(0,-3))}catch{}return[...e].sort((n,a)=>n.localeCompare(a,void 0,{numeric:!0}))}async function Es(t,e){if(qt())try{const i=await tS(e);return Rg(i)}catch{return vj(e)}const a=await(async i=>{try{return await(await(await i.getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}})(t)??await Uz(t,e);return a!==null?Rg(a):vj(e)}async function tce(t,e){if(qt())try{const a=await tS(e);return{ok:!0,task:Rg(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 Uz(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:Rg(n)}}catch(a){return{ok:!1,reason:"corrupted",error:a}}}async function iu(t,e,n,a){const i=Q4(n,a);if(qt())return Voe(e,i);try{const o=await(await(await Joe(t,e)?await Vz(t,!0):t).getFileHandle(`${e}.md`,{create:!0})).createWritable();try{await o.write(i)}finally{await o.close()}}catch(r){throw tk(r,`${e}.md`)}}async function yj(t,e){if(qt())return Zoe(e);try{await t.removeEntry(`${e}.md`)}catch{}try{await(await t.getDirectoryHandle("archive",{create:!1})).removeEntry(`${e}.md`)}catch{}}async function nce(t,e){await hs(`/api/tasks/${encodeURIComponent(t)}/archive`,{method:"POST",body:e,headers:{"Content-Type":"text/plain"}})}async function ace(t,e){await hs(`/api/tasks/${encodeURIComponent(t)}/unarchive`,{method:"POST",body:e,headers:{"Content-Type":"text/plain"}})}async function ice(t,e,n,a){const i=Q4(n,a);if(qt())return nce(e,i);try{const o=await(await(await Vz(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 tk(r,`archive/${e}.md`)}}async function rce(t,e,n,a){const i=Q4(n,a);if(qt())return ace(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 tk(r,`${e}.md`)}}const sce="kanban-md",oce=1,ap="recentProjects";function nS(){return new Promise((t,e)=>{const n=indexedDB.open(sce,oce);n.onerror=()=>e(n.error),n.onsuccess=()=>t(n.result),n.onupgradeneeded=()=>{const a=n.result;a.objectStoreNames.contains(ap)||a.createObjectStore(ap,{keyPath:"id"})}})}async function T2(t){const e=await nS();return new Promise((n,a)=>{const i=e.transaction(ap,"readwrite");i.objectStore(ap).put(t),i.oncomplete=()=>n(),i.onerror=()=>a(i.error)})}async function jy(){const t=await nS();return new Promise((e,n)=>{const i=t.transaction(ap,"readonly").objectStore(ap).getAll();i.onsuccess=()=>{const r=i.result||[];r.sort((s,o)=>o.lastOpened-s.lastOpened),e(r.slice(0,10))},i.onerror=()=>n(i.error)})}async function cce(t){const e=await nS();return new Promise((n,a)=>{const i=e.transaction(ap,"readwrite");i.objectStore(ap).delete(t),i.oncomplete=()=>n(),i.onerror=()=>a(i.error)})}async function _j(t,e=!0){const n={mode:e?"readwrite":"read"};try{return await t.queryPermission(n)==="granted"||await t.requestPermission(n)==="granted"}catch(a){return console.warn("[FS] verifyPermission: handle is no longer valid:",a),!1}}function Zz(t=Ci){const e=t.board.columns;return e[e.length-1]??"Done"}function pce(t,e=Ci){return t===Zz(e)||t.toLowerCase()==="archived"}class lce{dirHandle=null;tasksDirHandle=null;intervalId=null;configHash=null;taskHashes=new Map;knownTaskIds=new Set;listeners=new Map;debounceTimers=new Map;debounceDelay=150;consecutiveErrors=0;maxConsecutiveErrors=5;disabled=!1;start(e,n){this.dirHandle=e,this.tasksDirHandle=n,this.consecutiveErrors=0,this.disabled=!1,this.initHashes(),this.intervalId=setInterval(()=>void this.tick(),300)}stop(){this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null),this.configHash=null,this.taskHashes.clear(),this.knownTaskIds.clear(),this.listeners.clear(),this.debounceTimers.forEach(e=>clearTimeout(e)),this.debounceTimers.clear(),this.consecutiveErrors=0,this.disabled=!1}isDisabled(){return this.disabled}on(e,n){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(n),()=>{this.listeners.get(e)?.delete(n)}}getKnownTaskIds(){return Array.from(this.knownTaskIds)}async initHashes(){if(!(!this.dirHandle||!this.tasksDirHandle))try{const e=await kj(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 kj(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 wj(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 wj(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 kj(t){try{return await(await(await t.getFileHandle("kandown.json")).getFile()).text()}catch{return null}}async function wj(t,e){try{return await(await(await t.getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}}const al=new lce,dce={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 xj=null;function Gz(){return"Notification"in window?Notification.permission:"unsupported"}async function uce(){return"Notification"in window?await Notification.requestPermission():"unsupported"}function P2({title:t,body:e,config:n}){if(n.notifications.sound&&mce(n.notifications.soundId),!(!n.notifications.browser||Gz()!=="granted"))try{new Notification(t,{body:e})}catch{}}function mce(t){const e=dce[t],n=window.AudioContext??window.webkitAudioContext;if(!(!n||e.length===0))try{xj??=new n;const a=xj;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 Mf(t,e={}){const{maxAttempts:n=3,baseDelayMs:a=500,retryableCheck:i=Boe}=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 fce(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 gce(){const t=await eS();return await Promise.all(t.map(async n=>{const{frontmatter:a,body:i}=await qz(n),r={...a,id:a.id||n,status:a.status||"Backlog"},{subtasks:s,bodyWithoutSubtasks:o}=Ds(i);return{id:n,frontmatter:r,body:o,subtasks:s}}))}async function hce(t){const e=await ece(t),n=await Promise.all(e.map(async r=>{const s=await tce(t,r);return{id:r,result:s}})),a=[],i=[];for(const{id:r,result:s}of n)if(s.ok){const o={...s.task.frontmatter,id:s.task.frontmatter.id||r,status:s.task.frontmatter.status||"Backlog"},{subtasks:c,bodyWithoutSubtasks:p}=Ds(s.task.body);a.push({id:r,frontmatter:o,body:p,subtasks:c})}else{if(s.reason==="not-found")continue;i.push(r)}return{tasks:a,failedIds:i}}async function Aj(t,e,n){const a=[];for(const s of e){const o=s.name;s.tasks.forEach((c,p)=>{const d=(async()=>{const{frontmatter:m,body:f}=await Es(t,c.id);await iu(t,c.id,{...m,id:c.id,status:o,order:p},f)})();a.push({id:c.id,promise:d})})}const i=await Promise.allSettled(a.map(s=>s.promise)),r=[];return i.forEach((s,o)=>{s.status==="rejected"&&r.push(a[o].id)}),{failedIds:r}}function rl(t){zoe(t.ui.theme,t.ui.skin,t.ui.font,t.ui.background)}function v3(t){return{title:t.frontmatter.title||t.id,status:t.frontmatter.status||"Backlog",body:t.body,subtasks:t.subtasks}}function M2(t){fu.clear(),t.forEach(e=>{fu.set(e.id,v3(e))})}function bce(t){const e=t.split(/[\\/]+/).filter(Boolean),n=e[e.length-1];return n===".kandown"?e[e.length-2]??"Project":n??"Project"}function vce(t,e){return e.reduce((n,a,i)=>{const r=t[i]?.done??!1;return n+(a.done&&!r?1:0)},0)}function yce(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 _ce=0;const fu=new Map,Lf=new Map;let L2=null;const ge=Poe((t,e)=>({isOpen:!1,loading:!1,dirHandle:null,projectName:null,tasksDirHandle:null,boardTitle:"Project Kanban",columns:[],archivedTasks:[],showArchives:!1,showMetadata:!0,taskContents:new Map,searchMatches:new Map,viewMode:localStorage.getItem("kandown:view")||"board",density:localStorage.getItem("kandown:density")||"comfortable",filters:{search:"",priority:null,tag:null,assignee:null,ownerType:null},commandOpen:!1,cheatsheetOpen:!1,drawerTaskId:null,drawerData:null,currentPage:"board",config:Ci,recentProjects:[],toasts:[],agentHook:null,isReloading:!1,lastReloadError:null,failedTaskIds:[],watcherError:null,hasUnsavedDrawerEdits:!1,lastSaveError:null,drawerRecoveryData:new Map,drawerBaseVersion:null,conflictState:null,showConflictModal:!1,openFolder:async()=>{if(!Hz()){const c=navigator.userAgent||"this browser";e().toast(new Hoe(c).message,"error",8e3);return}let n;try{n=await Yoe()}catch(c){c instanceof J4?e().toast("Permission denied — please grant access to the project folder","error"):e().toast("Failed to open folder: "+c.message,"error");return}if(!n)return;const{projectHandle:a,kandownHandle:i,tasksHandle:r}=n,s=a.name;t({dirHandle:i,tasksDirHandle:r,projectName:s}),window.history.pushState({},"",`?p=${encodeURIComponent(s)}`);const o=qt()?E2():null;try{await T2({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 jy();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 _j(n.handle,!0)){let r=!1;try{await Ly(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 cce(n.id);const s=await jy();t({recentProjects:s})}catch{}return}e().toast("Permission denied — please grant access to the folder","error");return}try{const r=await Ly(n.handle),s=await b3(n.handle),o=n.handle.name;t({dirHandle:r,tasksDirHandle:s,projectName:o}),window.history.pushState({},"",`?p=${encodeURIComponent(o)}`);try{await T2({...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=E2();if(!n)throw new Error("No server root");await bj();const a=bce(n),i=await Bz();rl(i);const r=await eS(),s=await Promise.all(r.map(async f=>{const{frontmatter:h,body:b}=await qz(f),k={...h,id:h.id||f,status:h.status||"Backlog"},{subtasks:w,bodyWithoutSubtasks:A}=Ds(b);return{id:f,frontmatter:k,body:A,subtasks:w}}));M2(s);const o=s.map(f=>({frontmatter:f.frontmatter,body:qd(f.body,f.subtasks)})),c=S2(o,i.board.columns),p=$2(o),d=c.reduce((f,h)=>f+h.tasks.length,0),m=new Map;if(d<=10)for(const f of s)m.set(f.frontmatter.id,{frontmatter:f.frontmatter,subtasks:f.subtasks,body:f.body});t({loading:!1,isOpen:!0,config:i,columns:c,archivedTasks:p,boardTitle:"Project Kanban",projectName:a,taskContents:m,searchMatches:new Map}),window.history.pushState({},"",`?p=${encodeURIComponent(a)}`),e().setupWatcher(),e().refreshAgentHook()}catch{t({loading:!1,isOpen:!1}),e().toast("Impossible de charger le projet. Relancez `kandown`.","error")}},tryAutoOpenServerProject:async()=>{if(!qt())return;const n=E2();if(!n)return;await bj();const a=await jy(),i=a.find(p=>p.kandownDir===n);if(!i){await e().openServerProject();return}if(!await _j(i.handle,!0)){await e().openServerProject();return}const s=await Ly(i.handle),o=await b3(i.handle),c=i.handle.name;t({dirHandle:s,tasksDirHandle:o,projectName:c,recentProjects:a,isOpen:!0}),window.history.pushState({},"",`?p=${encodeURIComponent(c)}`),await T2({...i,lastOpened:Date.now()}),await e().loadConfig(),await e().reloadBoard(),e().setupWatcher()},loadConfig:async()=>{const{dirHandle:n}=e();if(!(!n&&!qt()))try{const a=await Xoe(n);if(a.ok){t({config:a.config}),rl(a.config);return}if(a.reason==="corrupted"){if(a.rawContent&&n)try{const r=await(await n.getFileHandle("kandown.json.backup",{create:!0})).createWritable();try{await r.write(a.rawContent)}finally{await r.close()}}catch{}e().toast("kandown.json is corrupted — using default settings. A backup was saved as kandown.json.backup.","warning",1e4)}t({config:Ci}),rl(Ci)}catch{t({config:Ci}),rl(Ci)}},updateConfig:async n=>{const{dirHandle:a,config:i}=e();if(!a&&!qt())return;const r=n(i);t({config:r}),rl(r);try{await Qoe(a,r)}catch(s){const o=s;o instanceof $o?e().toast("Disk is full — settings were not saved.","error",8e3):e().toast("Failed to save config: "+o.message,"error")}},reloadBoard:async()=>{const{tasksDirHandle:n,config:a}=e();t({isReloading:!0,lastReloadError:null});try{if(qt()){const i=await gce();M2(i);const r=i.map(d=>({frontmatter:d.frontmatter,body:qd(d.body,d.subtasks)})),s=S2(r,a.board.columns),o=$2(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 hce(n);M2(i);const s=i.map(m=>({frontmatter:m.frontmatter,body:qd(m.body,m.subtasks)})),o=S2(s,a.board.columns),c=$2(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=qt();if(!d&&!e().tasksDirHandle)return;const m=s.find(E=>E.name===a),f=s.find(E=>E.name===i);if(!m||!f)return;const h=m.tasks.findIndex(E=>E.id===n);if(h===-1)return;const b=m.tasks[h];if(!b)return;if(pce(i,o)){const E=new Map,T=Zz(o).toLowerCase();for(const D of s)for(const F of D.tasks){const H=F.frontmatter&&(F.frontmatter.archived===!0||F.frontmatter.archived==="true");E.set(F.id,{exists:!0,resolved:H||F.id===n||D.name.toLowerCase()===T})}const L=[];for(const D of b.dependsOn){if(typeof D!="string"||!D.trim()||D===n)continue;const F=E.get(D);(!F||!F.resolved)&&L.push(D)}if(L.length>0){const D=L.length===1?L[0]:`${L.slice(0,-1).join(", ")} and ${L[L.length-1]}`;e().toast(`Cannot move ${n} to ${i}: blocked by ${D}`,"error");return}}const k=s.map(E=>({...E,tasks:[...E.tasks]})),w=k.find(E=>E.name===a),A=k.find(E=>E.name===i),[S]=w.tasks.splice(h,1);/done|termin|closed|complet/i.test(i)?S.checked=!0:S.checked=!1,r!==void 0?A.tasks.splice(r,0,S):A.tasks.push(S),t({columns:k});try{const{tasksDirHandle:E}=e();if(!E&&!d)return;const T=a===i?k.filter(D=>D.name===i):k.filter(D=>D.name===a||D.name===i),{failedIds:L}=await Mf(()=>Aj(E??null,T,o.board.columns),{maxAttempts:3});if(L.length>0){const D=L.length===1?`Could not save move for ${L[0]}`:`${L.length} tasks could not be moved`;e().toast(D,"warning",8e3),await e().reloadBoard()}}catch(E){const T=E;T instanceof $o?e().toast("Disk is full — move was not saved. Free up space and try again.","error",8e3):e().toast("Failed to save: "+T.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&&!qt())return;const d=r.map(h=>({...h,tasks:[...h.tasks]})),m=d.find(h=>h.name===n);if(!m)return;const[f]=m.tasks.splice(a,1);m.tasks.splice(i,0,f),t({columns:d});try{const{tasksDirHandle:h}=e(),b=qt();if(!h&&!b)return;const{failedIds:k}=await Mf(()=>Aj(h??null,[m],o.board.columns),{maxAttempts:3});k.length>0&&(e().toast(`Could not save reorder for ${k.length} task(s)`,"warning",8e3),await e().reloadBoard())}catch(h){const b=h;b instanceof $o?e().toast("Disk is full — reorder was not saved.","error",8e3):e().toast("Failed to save: "+b.message,"error"),t({columns:r,taskContents:c,searchMatches:p})}},addColumn:async n=>{const a=n.trim();if(!a)return;const{config:i}=e();i.board.columns.some(r=>r.toLowerCase()===a.toLowerCase())||(await e().updateConfig(r=>({...r,board:{...r.board,columns:[...r.board.columns,a]}})),await e().reloadBoard())},renameColumn:async(n,a)=>{const i=a.trim(),{columns:r,tasksDirHandle:s,config:o}=e();if(!s||!i||i.toLowerCase()===n.toLowerCase())return;if(r.some(d=>d.name.toLowerCase()===i.toLowerCase())){e().toast("Column already exists","error");return}const c=r,p=r.map(d=>d.name===n?{...d,name:i}:d);t({columns:p});try{const d=c.find(m=>m.name===n);if(d){const f=(await Promise.allSettled(d.tasks.map(async(h,b)=>{const{frontmatter:k,body:w}=await Es(s,h.id);await iu(s,h.id,{...k,id:h.id,status:i,order:b},w)}))).filter(h=>h.status==="rejected").length;f>0&&e().toast(`${f} task(s) could not be renamed`,"warning",8e3)}await e().updateConfig(m=>{const f={...m.board.columnColors??{}},h=f[n.toLowerCase()];h&&(f[i.toLowerCase()]=h,delete f[n.toLowerCase()]);const b=m.board.columns.some(k=>k.toLowerCase()===n.toLowerCase())?m.board.columns:[...m.board.columns,n];return{...m,board:{...m.board,columns:b.map(k=>k.toLowerCase()===n.toLowerCase()?i:k),columnColors:f}}}),await e().reloadBoard()}catch(d){e().toast("Failed to rename column: "+d.message,"error"),t({columns:c})}},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&&!qt())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&&!qt())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=>yj(i,p.id)))).filter(p=>p.status==="rejected").length;c>0&&e().toast(`${c} task(s) could not be deleted`,"warning",8e3),await e().updateConfig(p=>{const d={...p.board.columnColors??{}};return delete d[n.toLowerCase()],{...p,board:{...p.board,columns:p.board.columns.filter(m=>m.toLowerCase()!==n.toLowerCase()),columnColors:d}}}),await e().reloadBoard(),e().toast("Column deleted")}catch(o){e().toast("Failed to delete column: "+o.message,"error"),t({columns:s})}},createTask:async n=>{const{columns:a,tasksDirHandle:i,config:r,taskContents:s,searchMatches:o}=e();if(!i&&!qt()||!a.length)return null;const c=n||r.board.columns[0]||a[0].name,p=fce(a),d=a.find(w=>w.name===c)?.tasks.length??0,m={id:p,title:"",checked:!1,dependsOn:[],tags:[],assignee:null,priority:r.fields.priority?r.board.defaultPriority:null,ownerType:r.fields.ownerType?r.board.defaultOwnerType:"",progress:null,frontmatter:{}},f=a.map(w=>w.name===c?{...w,tasks:[...w.tasks,m]}:w),h=new Map(s),b={id:p,title:"",status:c,order:d,priority:r.fields.priority?r.board.defaultPriority:"",tags:[],assignee:"",created:new Date().toISOString().slice(0,10),ownerType:r.fields.ownerType?r.board.defaultOwnerType:"",tools:""},k="";h.set(p,{frontmatter:b,subtasks:[],body:k}),t({columns:f,taskContents:h});try{const w=i||null;return await Mf(()=>iu(w,p,b,k),{maxAttempts:3}),e().toast(`Created ${p.replace(/^t/,"")}`),await e().openDrawer(p),p}catch(w){const A=w;return A instanceof $o?e().toast("Disk is full — task was not created.","error",8e3):e().toast("Failed to create: "+A.message,"error"),t({columns:a,taskContents:s,searchMatches:o}),null}},deleteTask:async n=>{const{columns:a,tasksDirHandle:i,taskContents:r,searchMatches:s}=e();if(!i&&!qt())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 yj(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&&!qt()))try{const{frontmatter:i,body:r}=await Es(a||null,n);await ice(a||null,n,{...i,archived:!0},r),e().drawerTaskId===n&&e().closeDrawer(),await e().reloadBoard(),e().toast("Archived")}catch(i){const r=i;r instanceof $o?e().toast("Disk is full — task was not archived.","error",8e3):e().toast("Failed to archive: "+r.message,"error")}},unarchiveTask:async n=>{const{tasksDirHandle:a}=e();if(!(!a&&!qt()))try{const{frontmatter:i,body:r}=await Es(a||null,n),s={...i};delete s.archived,await rce(a||null,n,s,r),await e().reloadBoard(),e().toast("Restored")}catch(i){const r=i;r instanceof $o?e().toast("Disk is full — task was not restored.","error",8e3):e().toast("Failed to restore: "+r.message,"error")}},setShowArchives:n=>t({showArchives:n}),setShowMetadata:n=>t({showMetadata:n}),openDrawer:async n=>{const{tasksDirHandle:a}=e();if(!(!a&&!qt()))try{const{frontmatter:i,body:r}=await Es(a,n),{subtasks:s,bodyWithoutSubtasks:o}=Ds(r),c={frontmatter:i,subtasks:s,body:o,savedAt:Date.now()},p=e().drawerRecoveryData.get(n),d=p?{frontmatter:p.frontmatter,subtasks:p.subtasks,body:p.body}:{frontmatter:i,subtasks:s,body:o},m=new Map(e().drawerRecoveryData);m.delete(n),t({drawerTaskId:n,drawerData:d,drawerBaseVersion:c,conflictState:null,showConflictModal:!1,hasUnsavedDrawerEdits:!!p,lastSaveError:null,drawerRecoveryData:m}),p&&e().toast("Restored your unsaved edits for this task","info")}catch(i){e().toast("Failed to open: "+i.message,"error")}},closeDrawer:()=>t({drawerTaskId:null,drawerData:null,drawerBaseVersion:null,conflictState:null,showConflictModal:!1,hasUnsavedDrawerEdits:!1,lastSaveError:null}),markDrawerDirty:()=>t({hasUnsavedDrawerEdits:!0}),forceCloseDrawer:()=>{const{drawerTaskId:n,drawerData:a}=e();if(n&&a){const i=new Map(e().drawerRecoveryData);i.set(n,{frontmatter:a.frontmatter,subtasks:a.subtasks,body:a.body}),t({drawerRecoveryData:i})}e().closeDrawer()},updateDrawerData:n=>{const{drawerData:a}=e();a&&t({drawerData:n(a)})},saveDrawer:async()=>{const{drawerTaskId:n,drawerData:a,tasksDirHandle:i,taskContents:r}=e();if(!n||!a)return;const s=qd(a.body,a.subtasks),o={...a.frontmatter,id:n};try{await Mf(()=>iu(i||null,n,o,s),{maxAttempts:3}),e().toast("Saved");const c=new Map(e().drawerRecoveryData);c.delete(n),t({drawerTaskId:null,drawerData:null,hasUnsavedDrawerEdits:!1,lastSaveError:null,drawerRecoveryData:c});const p=new Map(r);p.set(n,{frontmatter:o,subtasks:a.subtasks,body:a.body}),t({taskContents:p}),await e().reloadBoard()}catch(c){const p=c,d=p instanceof $o?"Disk is full — your edits are kept. Free up space and retry.":"Failed to save: "+p.message;e().toast(d,"error",8e3),t({lastSaveError:d})}},saveDrawerMetadata:async()=>{const{drawerTaskId:n,drawerData:a,tasksDirHandle:i,taskContents:r}=e();if(!(!n||!a))try{const s=qd(a.body,a.subtasks),o={...a.frontmatter,id:n};await Mf(()=>iu(i||null,n,o,s),{maxAttempts:3});const c=new Map(r);c.set(n,{frontmatter:o,subtasks:a.subtasks,body:a.body}),t({taskContents:c,hasUnsavedDrawerEdits:!1,lastSaveError:null}),await e().reloadBoard()}catch(s){const o=s,c=o instanceof $o?"Disk is full — your edits are kept.":"Failed to save: "+o.message;t({hasUnsavedDrawerEdits:!0,lastSaveError:c})}},setViewMode:n=>{localStorage.setItem("kandown:view",n),t({viewMode:n})},setDensity:n=>{localStorage.setItem("kandown:density",n),t({density:n})},setFilter:(n,a)=>{if(t(i=>({filters:{...i.filters,[n]:a}})),n==="search"){const{columns:i,tasksDirHandle:r,taskContents:s}=e(),o=a,c=i.flatMap(p=>p.tasks.map(d=>d.id));if(r){const p=c.filter(d=>!s.has(d));p.length>0?e().loadTaskContents(p).then(()=>{e().computeSearchMatches(o)}):e().computeSearchMatches(o)}}},clearFilters:()=>t({filters:{search:"",priority:null,tag:null,assignee:null,ownerType:null},searchMatches:new Map}),setCommandOpen:n=>t({commandOpen:n}),setCheatsheetOpen:n=>t({cheatsheetOpen:n}),refreshAgentHook:async()=>{if(!qt()){t({agentHook:null});return}const n=await Goe();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 Woe(n);if(i===null){e().toast("Could not reach the daemon","error");return}i.ok?e().toast(`Sent to ${a.label}`):e().toast(i.error||"Agent hook failed","error")},setCurrentPage:n=>t({currentPage:n}),loadTaskContents:async n=>{const{tasksDirHandle:a}=e();if(!a)return;const i=new Map(e().taskContents);await Promise.all(n.map(async r=>{if(!i.has(r))try{const{frontmatter:s,body:o}=await Es(a,r),{subtasks:c,bodyWithoutSubtasks:p}=Ds(o);i.set(r,{frontmatter:s,subtasks:c,body:p})}catch{}})),t({taskContents:i})},computeSearchMatches:n=>{if(!n.trim()){t({searchMatches:new Map});return}const{taskContents:a}=e(),i=new Map,r=n.toLowerCase();for(const[s,o]of a){const c=zz(o,r);c.length>0&&i.set(s,c)}t({searchMatches:i})},toast:(n,a="success",i)=>{const r=++_ce,s=i??(a==="error"||a==="warning"?6e3:2500);t(o=>({toasts:[...o.toasts,{id:r,message:n,type:a}]})),setTimeout(()=>{t(o=>({toasts:o.toasts.filter(c=>c.id!==r)}))},s)},dismissToast:n=>t(a=>({toasts:a.toasts.filter(i=>i.id!==n)})),resolveConflict:async n=>{const{conflictState:a,drawerData:i,tasksDirHandle:r,drawerTaskId:s,drawerBaseVersion:o}=e();if(!(!a||!r||!s))if(n==="reload"){const{frontmatter:c,body:p}=await Es(r,s),{subtasks:d,bodyWithoutSubtasks:m}=Ds(p);t({drawerData:{frontmatter:c,subtasks:d,body:m},drawerBaseVersion:{frontmatter:c,subtasks:d,body:m,savedAt:Date.now()},conflictState:null,showConflictModal:!1}),e().toast("Reloaded from disk")}else if(n==="overwrite"){if(i&&s&&o){const c=qd(i.body,i.subtasks),p={...i.frontmatter,id:s};try{await iu(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(qt()){L2&&clearInterval(L2),L2=setInterval(()=>{e().reloadBoard()},2e3);return}const{dirHandle:n,tasksDirHandle:a}=e();if(!n||!a)return;al.stop(),Lf.forEach(s=>clearTimeout(s)),Lf.clear(),al.start(n,a);const i=(s,o)=>{const c=Lf.get(s);c&&clearTimeout(c);const p=Math.max(2e3,e().config.notifications.editDebounceMs),d=setTimeout(()=>{Lf.delete(s);const m=e().config;m.notifications.taskEdits&&P2({title:"Task edited",body:`${o} changed on disk.`,config:m})},p);Lf.set(s,d)},r=async s=>{const{tasksDirHandle:o,config:c}=e();if(!o)return;let p,d;try{({frontmatter:p,body:d}=await Es(o,s))}catch(A){console.warn(`[Watcher] notifyTaskChange: failed to read ${s}:`,A);return}const{subtasks:m,bodyWithoutSubtasks:f}=Ds(d),h={id:s,frontmatter:{...p,id:p.id||s,status:p.status||"Backlog"},body:f,subtasks:m},b=v3(h),k=fu.get(s);if(!k){fu.set(s,b);return}c.notifications.statusChanges&&k.status!==b.status&&P2({title:"Task status changed",body:`${b.title}: ${k.status} → ${b.status}`,config:c});const w=vce(k.subtasks,b.subtasks);c.notifications.subtaskCompletions&&w>0&&P2({title:"Subtask completed",body:w===1?`${b.title}: 1 subtask completed.`:`${b.title}: ${w} subtasks completed.`,config:c}),yce(k,b)&&i(s,b.title),fu.set(s,b)};al.on("configChanged",()=>{try{e().loadConfig(),e().toast("Settings updated externally","info")}catch(s){console.error("[Watcher] configChanged handler error:",s)}}),al.on("taskChanged",async s=>{try{const{drawerTaskId:o,drawerBaseVersion:c,tasksDirHandle:p}=e();if(await r(s),o===s&&c&&p){let d,m;try{({frontmatter:d,body:m}=await Es(p,s))}catch(E){console.warn(`[Watcher] taskChanged: failed to re-read ${s}:`,E);return}const{subtasks:f,bodyWithoutSubtasks:h}=Ds(m),b=c,k=JSON.stringify(b.frontmatter)!==JSON.stringify(d),w=b.body!==h,A=JSON.stringify(b.subtasks)!==JSON.stringify(f);if(!k&&!w&&!A)return;let S="none";k&&(w||A)?S="full":k?S="metadata-only":(w||A)&&(S="body-only"),t({conflictState:{taskId:s,type:S,local:b,remote:{frontmatter:d,body:h,subtasks:f}},showConflictModal:S==="full"})}else await e().reloadBoard()}catch(o){console.error(`[Watcher] taskChanged handler error for ${s}:`,o)}}),al.on("newTaskDetected",async s=>{try{const{tasksDirHandle:o}=e();if(o){let c,p;try{({frontmatter:c,body:p}=await Es(o,s))}catch(f){console.warn(`[Watcher] newTaskDetected: failed to read ${s}:`,f),e().toast(`New task ${s} detected but could not be loaded`,"warning");return}const{subtasks:d,bodyWithoutSubtasks:m}=Ds(p);fu.set(s,v3({id:s,frontmatter:{...c,id:c.id||s,status:c.status||"Backlog"},body:m,subtasks:d}))}await e().reloadBoard()}catch(o){console.error(`[Watcher] newTaskDetected handler error for ${s}:`,o)}}),al.on("watcherError",s=>{t({watcherError:s}),e().toast(s,"warning",1e4)})},restartWatcher:()=>{const{dirHandle:n,tasksDirHandle:a}=e();!n||!a||(t({watcherError:null}),e().setupWatcher(),e().toast("File watcher restarted"))}}));jy().then(t=>{ge.setState({recentProjects:t})}).catch(t=>{console.warn("[Store] Failed to hydrate recent projects:",t)});rl(Ci);window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{rl(ge.getState().config)});const kce=Object.freeze(Object.defineProperty({__proto__:null,useStore:ge},Symbol.toStringTag,{value:"Module"}));function wce(){const t=ge(o=>o.config),e=ge(o=>o.updateConfig),[n,a]=$.useState(!1);if($.useEffect(()=>{a(!0)},[]),!n)return null;const i=t.ui.theme==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":t.ui.theme,r=i==="dark",s=()=>{const o=r?"light":"dark";e(c=>({...c,ui:{...c.ui,theme:o}}))};return _.jsx(at.button,{onClick:s,className:g3("relative inline-flex items-center rounded-full transition-all duration-300","border border-black/[0.08] dark:border-white/[0.12]","bg-black/[0.05] dark:bg-white/[0.08]","h-8 w-[52px] px-1"),title:r?"Switch to light mode":"Switch to dark mode",children:_.jsx(at.div,{className:g3("inline-flex items-center justify-center rounded-full shadow-md",r?"bg-[#1e293b] border border-white/[0.15]":"bg-black border border-black/20","h-[26px] w-[26px]"),animate:{x:r?22:0},transition:{type:"spring",stiffness:500,damping:30},children:_.jsx(at.div,{initial:{rotate:-90,scale:.5,opacity:0},animate:{rotate:0,scale:1,opacity:1},transition:{duration:.25,ease:"easeOut"},children:r?_.jsx(Zn.Moon,{size:13,className:"text-sky-300"}):_.jsx(Zn.Sun,{size:13,className:"text-amber-400"})},i)})})}function xce(t){const e=Nse(t,{stiffness:180,damping:22,mass:.8});return $.useEffect(()=>{e.set(t)},[t,e]),_z(e,a=>Math.round(a).toString())}const y3="0.18.0",Ace=({className:t})=>_.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 150 150",className:t,fill:"currentColor",children:[_.jsx("path",{d:"m57.6 64.6 0.1-0.1v-31.3c-0.1-3.5-2.7-5.6-5.7-5.6h-16.3v59.6c0.2-1.3 0.9-2.6 1.7-3.2l20.2-19.4z"}),_.jsx("path",{d:"m87.6 43.8c-3.4 0.1-7.1 1.3-9.9 3.7l-38.5 38.7c-2.1 2.1-3.4 4.8-3.5 7.5v26.7l77.5-76.4v-0.2h-25.6z"}),_.jsx("path",{d:"m108.1 96.4-6 5-22.4-20.8-14.6 14.2 21.1 21.4-5 4.1c-0.6 0.5-0.3 0.9 0.2 0.9h27.6c0.4 0 0.7-0.4 0.7-0.6v-24.8c0-0.7-1.1-0.3-1.6 0.3v0.3z"})]});function Cce(){const{t}=Cn(),e=ge(R=>R.dirHandle),n=ge(R=>R.isOpen),a=ge(R=>R.projectName),i=ge(R=>R.archivedTasks.length),r=ge(R=>R.showArchives),s=ge(R=>R.setShowArchives),o=ge(R=>R.columns),c=ge(R=>R.openFolder),p=ge(R=>R.reloadBoard),d=ge(R=>R.createTask),m=ge(R=>R.setCommandOpen),f=ge(R=>R.viewMode),h=ge(R=>R.setViewMode),b=ge(R=>R.density),k=ge(R=>R.setDensity),w=ge(R=>R.setCurrentPage),A=ge(R=>R.recentProjects),S=ge(R=>R.openRecentProject),E=ge(R=>R.filters),T=ge(R=>R.setFilter),L=ge(R=>R.clearFilters),D=ge(R=>R.config.fields),F=ge(R=>R.lastReloadError),H=ge(R=>R.watcherError),O=ge(R=>R.restartWatcher),[j,K]=$.useState(!1),[Q,ee]=$.useState(!0),U=$.useRef(null),J=$.useRef(null),W=o.reduce((R,Z)=>R+Z.tasks.length,0),V=xce(W),B=[];D.priority&&E.priority&&B.push({type:"priority",label:E.priority,value:E.priority}),D.tags&&E.tag&&B.push({type:"tag",label:"#"+E.tag,value:E.tag}),D.assignee&&E.assignee&&B.push({type:"assignee",label:"@"+E.assignee,value:E.assignee});const ne=[{label:t("filterBar.ownerAll"),value:""},{label:t("filterBar.ownerHuman"),value:"human"},{label:t("filterBar.ownerAI"),value:"ai"}],I=B.length>0||E.search||D.ownerType&&E.ownerType;return $.useEffect(()=>{if(!j)return;const R=Z=>{U.current&&!U.current.contains(Z.target)&&K(!1)};return document.addEventListener("mousedown",R),()=>document.removeEventListener("mousedown",R)},[j]),$.useEffect(()=>{const R=setTimeout(()=>ee(!1),5e3);return()=>clearTimeout(R)},[]),$.useEffect(()=>{document.title=a?`${a} · Kandown`:"Kandown"},[a]),_.jsxs(_.Fragment,{children:[(F||H)&&_.jsxs("div",{className:"flex items-center gap-2 px-5 py-1.5 bg-amber-500/10 border-b border-amber-500/30 text-[12px] text-amber-700 dark:text-amber-300",children:[_.jsx("span",{className:"flex-1 truncate",children:H?`⚠ ${H}`:`⚠ ${F}`}),H&&_.jsx("button",{type:"button",onClick:O,className:"px-2 py-0.5 rounded bg-amber-500/20 hover:bg-amber-500/30 text-[11.5px] font-medium",children:"Restart watcher"}),_.jsx("button",{type:"button",onClick:p,className:"px-2 py-0.5 rounded bg-amber-500/20 hover:bg-amber-500/30 text-[11.5px] font-medium",children:"Reload"})]}),_.jsxs("header",{className:"flex items-center justify-between px-5 h-[64px] border-b border-border bg-card/80 backdrop-blur-xl relative z-10",children:[_.jsxs("div",{className:"flex items-center gap-4 min-w-0",children:[_.jsx("div",{className:"flex items-center gap-2.5 flex-shrink-0",children:_.jsxs("button",{onClick:()=>window.history.pushState({},"",window.location.pathname),className:"flex items-center gap-2 cursor-pointer",children:[_.jsx(Ace,{className:"w-[34px] h-[34px] dark:text-white text-black"}),_.jsx(Sr,{mode:"wait",initial:!1,children:Q?_.jsxs(at.span,{className:"flex items-center gap-2",initial:{opacity:0},animate:{opacity:1},exit:{opacity:0},transition:{duration:.45},children:[_.jsx("span",{className:"text-[15px] font-semibold tracking-tight text-fg",children:"kandown"}),_.jsxs("span",{className:"inline-flex items-center h-5 px-1.5 text-[10.5px] font-semibold text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-500/15 rounded-md",children:["v",y3]})]},"boot"):a?_.jsx(at.span,{className:"text-[15px] font-semibold tracking-tight text-fg truncate max-w-[240px]",initial:{opacity:0},animate:{opacity:1},exit:{opacity:0},transition:{duration:.45},children:a},"project"):null})]})}),e&&_.jsxs(_.Fragment,{children:[_.jsx("div",{className:"w-px h-[20px] bg-black/[0.08] dark:bg-white/[0.08] flex-shrink-0"}),_.jsxs("div",{className:"flex items-center gap-2 px-3 h-9 bg-secondary/60 border border-border rounded-xl min-w-[200px] max-w-[280px] focus-within:border-border-focus focus-within:bg-secondary transition-all",children:[_.jsx(Zn.Search,{size:14,className:"text-fg-muted/60 flex-shrink-0"}),_.jsx("input",{ref:J,type:"text",placeholder:t("filterBar.searchPlaceholder"),value:E.search,onChange:R=>T("search",R.target.value),className:"bg-transparent border-none outline-none text-fg text-[13px] w-full placeholder:text-fg-muted/60"}),E.search?_.jsx("button",{onClick:()=>T("search",""),className:"text-fg-muted/60 hover:text-fg flex-shrink-0",children:_.jsx(Zn.X,{size:14})}):_.jsx("kbd",{className:"inline-flex items-center h-5 px-1.5 text-[10px] font-medium text-fg-muted/50 bg-black/[0.04] dark:bg-white/[0.08] rounded border border-black/[0.06] dark:border-white/[0.1]",children:"⌘K"})]}),_.jsxs("div",{className:"flex items-center gap-1.5 flex-wrap overflow-hidden",children:[_.jsx(Sr,{children:B.map(R=>_.jsxs(at.button,{initial:{opacity:0,scale:.9},animate:{opacity:1,scale:1},exit:{opacity:0,scale:.9},transition:{duration:.15},onClick:()=>T(R.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:[R.label,_.jsx(Zn.X,{size:10,className:"text-fg-muted/60"})]},R.type+R.value))}),D.ownerType&&_.jsx("div",{className:"flex items-center h-6 border border-black/[0.06] dark:border-white/[0.1] rounded-lg overflow-hidden",children:ne.map(R=>_.jsx("button",{onClick:()=>T("ownerType",R.value),className:`h-full px-2.5 text-[12px] transition-colors ${E.ownerType===R.value?"bg-black/[0.06] dark:bg-white/[0.12] text-fg":"text-fg-muted/70 hover:text-fg"}`,children:R.label},R.value))}),I&&_.jsx("button",{onClick:L,className:"text-[12px] text-fg-muted/60 hover:text-fg transition-colors",children:t("filterBar.clearAll")})]})]}),e&&_.jsxs(_.Fragment,{children:[_.jsx("div",{className:"w-px h-[20px] bg-black/[0.08] dark:bg-white/[0.08] flex-shrink-0"}),_.jsxs("div",{className:"relative flex-shrink-0",ref:U,children:[_.jsxs("button",{onClick:()=>K(R=>!R),className:"flex items-center gap-1.5 px-2.5 py-1.5 text-[13px] text-fg-muted hover:text-fg hover:bg-black/[0.04] dark:hover:bg-white/[0.06] rounded-lg transition-colors border border-transparent hover:border-black/[0.06] dark:hover:border-white/[0.1]",children:[_.jsx(Zn.Folder,{size:13,className:"text-fg-muted/70"}),_.jsxs("span",{className:"font-medium",children:[".",e.name]}),_.jsx(Zn.ChevronDown,{size:11,className:"opacity-50"})]}),_.jsx(Sr,{children:j&&_.jsx(at.div,{initial:{opacity:0,y:-4,scale:.98},animate:{opacity:1,y:0,scale:1},exit:{opacity:0,y:-4,scale:.98},transition:{duration:.12},className:"absolute top-full left-0 mt-2 min-w-[240px] glass rounded-xl shadow-[0_16px_48px_rgba(0,0,0,0.5)] overflow-hidden z-50",children:_.jsxs("div",{className:"py-1.5",children:[A.length>0&&_.jsxs(_.Fragment,{children:[_.jsx("div",{className:"px-3 py-1.5 text-[11px] font-semibold uppercase tracking-wider text-fg-muted/60",children:t("header.recentProjects")}),A.map(R=>_.jsxs("button",{onClick:()=>{K(!1),S(R)},className:"w-full flex items-center gap-2 px-3 py-2 text-[13.5px] text-left hover:bg-black/[0.04] dark:hover:bg-white/[0.06] transition-colors",children:[_.jsx(Zn.Folder,{size:12,className:"text-fg-muted/60"}),_.jsx("span",{className:"truncate",children:R.name}),R.id===e.name&&_.jsx(Zn.Check,{size:12,className:"ml-auto text-emerald-500"})]},R.id)),_.jsx("div",{className:"h-px bg-black/[0.06] dark:bg-white/[0.08] my-1.5 mx-2"})]}),_.jsxs("button",{onClick:()=>{K(!1),c()},className:"w-full flex items-center gap-2 px-3 py-2 text-[13.5px] text-left hover:bg-black/[0.04] dark:hover:bg-white/[0.06] transition-colors",children:[_.jsx(Zn.Plus,{size:12,className:"text-fg-muted/60"}),_.jsx("span",{children:t("header.openFolder...")})]})]})})})]})]})]}),_.jsx("div",{className:"flex items-center gap-2 flex-shrink-0",children:n||e?_.jsxs(_.Fragment,{children:[_.jsxs("div",{className:"flex items-center gap-2 mr-2 text-[12.5px] text-fg-muted/70",children:[_.jsx("span",{className:"inline-block w-1.5 h-1.5 rounded-full bg-emerald-500"}),_.jsx(at.span,{className:"tabular-nums font-medium",children:V}),_.jsx("span",{children:t("header.tasks")})]}),_.jsxs("div",{className:"flex items-center bg-black/[0.04] dark:bg-white/[0.06] border border-black/[0.06] dark:border-white/[0.1] rounded-xl p-0.5 h-10",children:[_.jsx("button",{onClick:()=>h("board"),className:`w-9 h-9 inline-flex items-center justify-center rounded-lg transition-all ${f==="board"?"bg-card text-fg shadow-sm":"text-fg-muted/70 hover:text-fg"}`,title:t("common.board"),children:_.jsx(Zn.LayoutBoard,{size:18})}),_.jsx("button",{onClick:()=>h("list"),className:`w-9 h-9 inline-flex items-center justify-center rounded-lg transition-all ${f==="list"?"bg-card text-fg shadow-sm":"text-fg-muted/70 hover:text-fg"}`,title:t("common.list"),children:_.jsx(Zn.LayoutList,{size:18})})]}),_.jsx(na,{variant:"icon",icon:"Archive",onClick:()=>s(!r),title:r?t("header.backToBoard"):`${t("header.archives")} (${i})`,className:r?"text-accent":""}),_.jsx(na,{variant:"icon",icon:"Density",onClick:()=>k(b==="compact"?"comfortable":"compact"),title:`Density: ${b}`}),_.jsx(na,{variant:"icon",icon:"Settings",onClick:()=>w("settings"),title:t("common.settings")}),_.jsx(wce,{}),_.jsx("div",{className:"w-px h-5 bg-black/[0.08] dark:bg-white/[0.08] mx-1"}),_.jsx(na,{variant:"secondary",icon:"Search",label:t("common.search"),shortcut:"⌘K",onClick:()=>m(!0),title:"Command palette (⌘K)"}),_.jsx(na,{variant:"icon",icon:"Refresh",onClick:p,title:`${t("common.reload")} (R)`}),_.jsx(na,{variant:"primary",icon:"Plus",label:t("common.newTask"),shortcut:"N",onClick:()=>d()})]}):_.jsx(na,{variant:"primary",label:t("common.openFolder"),onClick:c})})]})]})}/**
117
117
  * @license @tabler/icons-react v3.41.1 - MIT
118
118
  *
119
119
  * This source code is licensed under the MIT license.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kandown",
3
- "version": "0.17.2",
3
+ "version": "0.18.0",
4
4
  "description": "File-based Kanban engine backed by Markdown. Zero backend, AI-agent friendly.",
5
5
  "type": "module",
6
6
  "bin": {