kaizen-agent 1.36.0 → 1.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/cli/install.test.ts +60 -0
- package/cli/install.ts +74 -24
- package/package.json +1 -1
- package/web/board/cards.ts +1 -1
- package/web/board.html +1 -1
- package/web/board.js +10 -10
- package/web/notebook.js +1 -1
- package/web/term.js +1 -1
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ curl -fsSL https://bun.sh/install | bash && ~/.bun/bin/bunx kaizen-agent
|
|
|
48
48
|
|
|
49
49
|
Already have Bun? Run `bunx kaizen-agent`.
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
kaizen needs an AI coding agent such as [Claude Code](https://docs.claude.com/en/docs/claude-code). If none is installed, the installer lets you pick one, runs its official installer after you say yes, and then carries on. Git is needed for the board's commit and push.
|
|
52
52
|
|
|
53
53
|
### Windows
|
|
54
54
|
|
|
@@ -58,7 +58,7 @@ Windows support is still in beta. In PowerShell:
|
|
|
58
58
|
irm bun.sh/install.ps1 | iex; & "$env:USERPROFILE\.bun\bin\bunx.exe" kaizen-agent
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
Already have Bun? Run `bunx kaizen-agent`. As on macOS and Linux
|
|
61
|
+
Already have Bun? Run `bunx kaizen-agent`. As on macOS and Linux, the installer offers to install an AI coding agent when none is found; Git is only needed for the board's commit and push.
|
|
62
62
|
|
|
63
63
|
## Use the board
|
|
64
64
|
|
package/cli/install.test.ts
CHANGED
|
@@ -159,3 +159,63 @@ it("init's gitignore keeps run state and personal notes out, the docs vault in",
|
|
|
159
159
|
expect(ignored(".kaizen/notes/Project/Archive/Old.md")).toBe(false);
|
|
160
160
|
} finally { rmSync(proj, { recursive: true, force: true }); }
|
|
161
161
|
});
|
|
162
|
+
|
|
163
|
+
// A box with no agent: the picker installs the chosen one with its official command and
|
|
164
|
+
// the kaizen install carries on in the same run. `curl` is a fake that hands back a
|
|
165
|
+
// script dropping a stub `claude` in ~/.local/bin, as the real installer does; nothing
|
|
166
|
+
// reaches the network.
|
|
167
|
+
const noAgentBox = () => {
|
|
168
|
+
const root = mkdtempSync(join(tmpdir(), "kz-noagent-"));
|
|
169
|
+
const home = join(root, "home"), pkg = join(root, "pkg"), bin = join(root, "bin");
|
|
170
|
+
mkdirSync(home); mkdirSync(bin);
|
|
171
|
+
const src = dirname(dirname(import.meta.path));
|
|
172
|
+
for (const d of ["cli", "skills", "agents", "commands", "web", "package.json"]) {
|
|
173
|
+
cpSync(join(src, d), join(pkg, d), { recursive: true, filter: (p) => !p.includes("node_modules") });
|
|
174
|
+
}
|
|
175
|
+
for (const t of ["sh", "bash", "mkdir", "chmod", "cat"]) { const w = Bun.which(t); if (w) symlinkSync(w, join(bin, t)); }
|
|
176
|
+
symlinkSync(process.execPath, join(bin, "bun"));
|
|
177
|
+
writeFileSync(join(bin, "curl"), `#!/bin/sh\necho "curl $*" >> "$HOME/curl.log"\nprintf 'mkdir -p "$HOME/.local/bin"\\nprintf "#!/bin/sh\\\\nexit 0\\\\n" > "$HOME/.local/bin/claude"\\nchmod +x "$HOME/.local/bin/claude"\\n'\n`, { mode: 0o755 });
|
|
178
|
+
const env = { PATH: bin, HOME: home, USERPROFILE: home, TMPDIR: root, TERM: "xterm" };
|
|
179
|
+
return { root, home, pkg, env };
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
it.skipIf(typeof (Bun as any).Terminal !== "function")("no agent: the picked agent is installed after a yes, and the kaizen install goes on", async () => {
|
|
183
|
+
const { root, home, pkg, env } = noAgentBox();
|
|
184
|
+
let out = "";
|
|
185
|
+
const answered = new Set<string>();
|
|
186
|
+
const proc = Bun.spawn([process.execPath, join(pkg, "cli", "install.ts"), "--global"], {
|
|
187
|
+
cwd: home, env,
|
|
188
|
+
terminal: { cols: 160, rows: 40, data: (_t: unknown, d: Uint8Array) => { out += new TextDecoder().decode(d); } },
|
|
189
|
+
} as any);
|
|
190
|
+
const answer = (prompt: string, keys: string) => {
|
|
191
|
+
if (!answered.has(prompt) && out.includes(prompt)) { answered.add(prompt); (proc as any).terminal.write(keys); }
|
|
192
|
+
};
|
|
193
|
+
const until = Date.now() + 30_000;
|
|
194
|
+
try {
|
|
195
|
+
while (Date.now() < until && !out.includes("to install, or")) {
|
|
196
|
+
answer("Which one would you like to install?", "\r");
|
|
197
|
+
answer("Install Claude Code now?", "\r");
|
|
198
|
+
answer("Start Claude Code now to sign in?", "\x1b[B\r");
|
|
199
|
+
await Bun.sleep(100);
|
|
200
|
+
}
|
|
201
|
+
expect(out).toContain("curl -fsSL https://claude.ai/install.sh | bash");
|
|
202
|
+
expect(readFileSync(join(home, "curl.log"), "utf8")).toContain("https://claude.ai/install.sh");
|
|
203
|
+
expect(out).toContain("installed Claude Code");
|
|
204
|
+
expect(existsSync(join(home, ".local", "bin", "claude"))).toBe(true);
|
|
205
|
+
expect(out).toContain("to install, or"); // the kaizen install went on, no rerun
|
|
206
|
+
} finally {
|
|
207
|
+
proc.kill();
|
|
208
|
+
await proc.exited;
|
|
209
|
+
rmSync(root, { recursive: true, force: true });
|
|
210
|
+
}
|
|
211
|
+
}, 40_000);
|
|
212
|
+
|
|
213
|
+
it("no agent, no terminal: the list is printed and nothing is installed", () => {
|
|
214
|
+
const { root, home, pkg, env } = noAgentBox();
|
|
215
|
+
const p = Bun.spawnSync([process.execPath, join(pkg, "cli", "install.ts")], { cwd: home, env });
|
|
216
|
+
const ran = existsSync(join(home, "curl.log"));
|
|
217
|
+
rmSync(root, { recursive: true, force: true });
|
|
218
|
+
expect(p.exitCode).toBe(0);
|
|
219
|
+
expect(p.stdout.toString()).toContain("Install one, then run this again.");
|
|
220
|
+
expect(ran).toBe(false);
|
|
221
|
+
});
|
package/cli/install.ts
CHANGED
|
@@ -54,25 +54,30 @@ const SKILLS = ["kaizen", "kaizen-help", "story-deck", "story-video"];
|
|
|
54
54
|
// commands/ are Claude Code's own formats and go nowhere else.
|
|
55
55
|
const AGENTS = [
|
|
56
56
|
{ name: "Claude Code", dir: ".claude", cmd: "claude", full: true,
|
|
57
|
-
docs: "https://docs.claude.com/en/docs/claude-code"
|
|
57
|
+
docs: "https://docs.claude.com/en/docs/claude-code",
|
|
58
|
+
install: { posix: "curl -fsSL https://claude.ai/install.sh | bash", win32: "irm https://claude.ai/install.ps1 | iex" } },
|
|
58
59
|
{ name: "Codex", dir: ".codex", cmd: "codex", full: false,
|
|
59
|
-
docs: "https://github.com/openai/codex"
|
|
60
|
+
docs: "https://github.com/openai/codex",
|
|
61
|
+
install: { posix: "npm install -g @openai/codex", win32: "npm install -g @openai/codex" } },
|
|
60
62
|
{ name: "Antigravity", dir: ".agents", cmd: "agy", full: false,
|
|
61
63
|
docs: "https://antigravity.google" },
|
|
62
64
|
{ name: "OpenCode", dir: ".opencode", cmd: "opencode", full: false,
|
|
63
|
-
docs: "https://opencode.ai"
|
|
65
|
+
docs: "https://opencode.ai",
|
|
66
|
+
install: { posix: "curl -fsSL https://opencode.ai/install | bash", win32: "npm install -g opencode-ai" } },
|
|
64
67
|
{ name: "Cursor", dir: ".cursor", cmd: "cursor", full: false,
|
|
65
68
|
docs: "https://cursor.com" },
|
|
66
69
|
{ name: "Gemini CLI", dir: ".gemini", cmd: "gemini", full: false,
|
|
67
|
-
docs: "https://github.com/google-gemini/gemini-cli"
|
|
68
|
-
|
|
70
|
+
docs: "https://github.com/google-gemini/gemini-cli",
|
|
71
|
+
install: { posix: "npm install -g @google/gemini-cli", win32: "npm install -g @google/gemini-cli" } },
|
|
72
|
+
] as { name: string; dir: string; cmd: string; full: boolean; docs: string; install?: { posix: string; win32: string } }[];
|
|
69
73
|
|
|
70
74
|
// An agent counts as present if it has a directory or a command. The directory
|
|
71
75
|
// alone misses someone who installed the agent and has not run it yet; the command
|
|
72
76
|
// alone misses one that is not on this shell's PATH.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
);
|
|
77
|
+
// Recomputed after kaizen installs an agent, so the install carries on in the same run.
|
|
78
|
+
// PATH passed explicitly: Bun.which does not see later edits to process.env.PATH.
|
|
79
|
+
const detect = () => new Set(AGENTS.filter((a) => Bun.which(a.cmd, { PATH: process.env.PATH })).map((a) => a.dir));
|
|
80
|
+
let onPath = detect();
|
|
76
81
|
const found = (r: string) =>
|
|
77
82
|
AGENTS.filter((a) => existsSync(join(r, a.dir)) || onPath.has(a.dir));
|
|
78
83
|
|
|
@@ -308,38 +313,83 @@ function removeLink(p: string) {
|
|
|
308
313
|
// nothing, so send them to the agent first rather than laying out files no reader
|
|
309
314
|
// will ever load.
|
|
310
315
|
if (!found(home).length && !args.has("--force")) {
|
|
311
|
-
await noAgent();
|
|
312
|
-
process.exit(0);
|
|
316
|
+
if (!(await noAgent())) process.exit(0);
|
|
313
317
|
}
|
|
314
318
|
|
|
315
|
-
|
|
319
|
+
// Returns true once an agent is installed and found, so the kaizen install goes on.
|
|
320
|
+
async function noAgent(): Promise<boolean> {
|
|
316
321
|
console.log(` ${c.bold("No AI coding agent found on this machine.")}\n`);
|
|
317
322
|
console.log(` ${c.dim("kaizen is a workflow that an agent runs — it plans, you approve, it builds,")}`);
|
|
318
323
|
console.log(` ${c.dim("and a second agent reviews. So an agent has to be installed first.")}\n`);
|
|
319
324
|
|
|
320
|
-
|
|
325
|
+
const list = () => {
|
|
321
326
|
for (const a of AGENTS) console.log(` ${a.name.padEnd(14)} ${a.docs}`);
|
|
322
327
|
console.log(`\n ${c.dim("Install one, then run this again.")}`);
|
|
323
|
-
|
|
324
|
-
}
|
|
328
|
+
};
|
|
329
|
+
if (!interactive) { list(); return false; }
|
|
325
330
|
|
|
326
331
|
const pick = await select("Which one would you like to install?", [
|
|
327
332
|
...AGENTS.map((a) => ({ label: a.name, hint: a.docs, value: a as (typeof AGENTS)[number] | null })),
|
|
328
333
|
{ label: "None of these — just show me the list", hint: "", value: null },
|
|
329
334
|
]);
|
|
335
|
+
if (!pick) { list(); return false; }
|
|
336
|
+
|
|
337
|
+
const docs = async (why = "") => {
|
|
338
|
+
if (why) console.log(` ${why}`);
|
|
339
|
+
console.log(` ${c.bold(pick.name)}`);
|
|
340
|
+
console.log(` ${c.cyan(pick.docs)}\n`);
|
|
341
|
+
const opened = await openUrl(pick.docs);
|
|
342
|
+
console.log(opened
|
|
343
|
+
? ` ${c.dim("Opened in your browser. Install it, then run")} ${c.cyan("kaizen")} ${c.dim("again.")}`
|
|
344
|
+
: ` ${c.dim("Open that page to install it, then run")} ${c.cyan("kaizen")} ${c.dim("again.")}`);
|
|
345
|
+
return false;
|
|
346
|
+
};
|
|
330
347
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
348
|
+
const win = process.platform === "win32";
|
|
349
|
+
const cmd = pick.install?.[win ? "win32" : "posix"];
|
|
350
|
+
if (!cmd) return docs();
|
|
351
|
+
if (cmd.startsWith("npm ") && !Bun.which("npm")) return docs(c.dim(`${pick.name} installs with npm, which needs Node.js: https://nodejs.org`));
|
|
352
|
+
|
|
353
|
+
console.log(`\n ${c.dim("Official installer:")} ${c.cyan(cmd)}`);
|
|
354
|
+
if (!(await select(`Install ${pick.name} now?`, [
|
|
355
|
+
{ label: "Yes", hint: "run the command above", value: true },
|
|
356
|
+
{ label: "No", hint: "open the docs page instead", value: false },
|
|
357
|
+
]))) return docs();
|
|
358
|
+
|
|
359
|
+
// ponytail: sh/powershell straight from the vendor's one-liner; no sudo, a failure falls back to the docs.
|
|
360
|
+
const code = Bun.spawnSync(win ? ["powershell", "-NoProfile", "-Command", cmd] : ["sh", "-c", cmd],
|
|
361
|
+
{ stdio: ["inherit", "inherit", "inherit"] }).exitCode;
|
|
362
|
+
addInstallerBins();
|
|
363
|
+
onPath = detect();
|
|
364
|
+
const bin = Bun.which(pick.cmd, { PATH: process.env.PATH });
|
|
365
|
+
if (code !== 0 || !bin) {
|
|
366
|
+
return docs(code !== 0
|
|
367
|
+
? `${c.bold("The install did not finish")} ${c.dim(`(exit ${code}).`)}`
|
|
368
|
+
: `${c.bold(`${pick.name} installed, but`)} ${c.cyan(pick.cmd)} ${c.bold("is not on PATH yet.")} ${c.dim("Open a new terminal and run kaizen again.")}`);
|
|
335
369
|
}
|
|
370
|
+
step(`installed ${pick.name}`);
|
|
371
|
+
|
|
372
|
+
if (await select(`Start ${pick.name} now to sign in?`, [
|
|
373
|
+
{ label: "Yes", hint: "exit the agent once you are signed in, and kaizen carries on", value: true },
|
|
374
|
+
{ label: "Later", hint: `run ${pick.cmd} yourself before your first run`, value: false },
|
|
375
|
+
])) Bun.spawnSync([bin], { stdio: ["inherit", "inherit", "inherit"] });
|
|
376
|
+
console.log();
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
336
379
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
380
|
+
// The vendors' installers drop the binary in a folder the running shell may not have
|
|
381
|
+
// on PATH yet; add the usual ones so Bun.which sees the new agent.
|
|
382
|
+
function addInstallerBins() {
|
|
383
|
+
const dirs = [join(home, ".local", "bin"), join(home, ".opencode", "bin")];
|
|
384
|
+
if (Bun.which("npm")) {
|
|
385
|
+
try {
|
|
386
|
+
const prefix = Bun.spawnSync(["npm", "prefix", "-g"]).stdout.toString().trim();
|
|
387
|
+
if (prefix) dirs.push(process.platform === "win32" ? prefix : join(prefix, "bin"));
|
|
388
|
+
} catch {}
|
|
389
|
+
}
|
|
390
|
+
const sep = process.platform === "win32" ? ";" : ":";
|
|
391
|
+
const have = (process.env.PATH ?? "").split(sep);
|
|
392
|
+
process.env.PATH = [...dirs.filter((d) => existsSync(d) && !have.includes(d)), ...have].join(sep);
|
|
343
393
|
}
|
|
344
394
|
|
|
345
395
|
// Best effort: a headless box or a locked-down desktop has no opener, and the URL
|
package/package.json
CHANGED
package/web/board/cards.ts
CHANGED
|
@@ -116,7 +116,7 @@ function card(c) {
|
|
|
116
116
|
? `<ul>${kids
|
|
117
117
|
.map((x, i) => {
|
|
118
118
|
const s = ICON[x.status] || "idea";
|
|
119
|
-
return `<li data-i="${i}" tabindex="0" role="button" title="Open"><span class="st d-${s}"><svg><use href="#s-${s}"/></svg></span><span class="kids-s">${esc(WORD[x.status] || x.status)}</span><span class="kids-n">${esc(x.kind === "idea" ? brief(x.text).head : x.
|
|
119
|
+
return `<li data-i="${i}" tabindex="0" role="button" title="Open"><span class="st d-${s}"><svg><use href="#s-${s}"/></svg></span><span class="kids-s">${esc(WORD[x.status] || x.status)}</span><span class="kids-n" title="${esc(x.title || x.text || x.id)}">${esc(x.kind === "idea" ? brief(x.text).head : (x.text || x.id || "").replace(/-/g, " "))}</span></li>`;
|
|
120
120
|
})
|
|
121
121
|
.join("")}</ul>`
|
|
122
122
|
: "");
|
package/web/board.html
CHANGED
|
@@ -180,7 +180,7 @@ section h2 .clear{margin-left:auto;min-height:22px;padding:0 8px}
|
|
|
180
180
|
.chip.ok svg{color:var(--ok)}
|
|
181
181
|
.chip.run{color:var(--ink)}.chip.run i{background:var(--run)}
|
|
182
182
|
.chip.link,.pst.link{color:var(--accent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--accent) 40%,transparent)}.pst.link{padding-left:9px;background:color-mix(in srgb,var(--accent) 10%,transparent)}
|
|
183
|
-
.kids{margin-top:6px;padding-top:6px;border-top:1px solid color-mix(in srgb,var(--line) 70%,transparent)}.kids-t{all:unset;cursor:pointer;display:flex;align-items:center;gap:6px;width:100%;font-size:12px;color:var(--dim)}.kids-t:focus-visible,.kids li:focus-visible{outline:2px solid var(--accent);border-radius:4px}.kids-w{width:7px;height:7px;border-radius:50%;background:var(--warn);margin-left:auto}.kids ul{list-style:none;margin:4px 0 0;padding:0;display:flex;flex-direction:column;gap:1px}.kids li{display:grid;grid-template-columns:16px auto 1fr;gap:8px;align-items:
|
|
183
|
+
.kids{margin-top:6px;padding-top:6px;border-top:1px solid color-mix(in srgb,var(--line) 70%,transparent)}.kids-t{all:unset;cursor:pointer;display:flex;align-items:center;gap:6px;width:100%;font-size:12px;color:var(--dim)}.kids-t:focus-visible,.kids li:focus-visible{outline:2px solid var(--accent);border-radius:4px}.kids-w{width:7px;height:7px;border-radius:50%;background:var(--warn);margin-left:auto}.kids ul{list-style:none;margin:4px 0 0;padding:0;display:flex;flex-direction:column;gap:1px}.kids li{display:grid;grid-template-columns:16px auto 1fr;gap:8px;align-items:start;font-size:12px;padding:3px 4px;border-radius:6px;cursor:pointer;min-width:0}.kids li:hover{background:var(--hover)}.kids-s{font-family:var(--mono);font-size:11px;color:var(--dim);white-space:nowrap}.kids-n{overflow-wrap:anywhere;line-height:1.35;color:var(--ink)}.kids .st{width:14px;height:14px}
|
|
184
184
|
.st.d-idea{color:var(--dim)}.st.d-start{color:var(--run)}.st.d-run{color:var(--run)}.st.d-stall{color:var(--dim)}.st.d-wait{color:var(--warn)}.st.d-done{color:var(--ok)}.st.d-aband{color:var(--dim)}
|
|
185
185
|
.d-run{color:var(--run)}.d-wait{color:var(--warn)}.d-done{color:var(--ok)}
|
|
186
186
|
.pulse{display:inline-block;width:7px;height:7px;border-radius:50%;background:currentColor;vertical-align:middle}
|
package/web/board.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* kaizen board
|
|
1
|
+
/* kaizen board fff0c686caeb0a21 -- generated by bun run build:web from web/board/main.ts; do not edit */
|
|
2
2
|
(()=>{var a={S:{project:null,projects:[],cards:[],now:Date.now(),all:!1},all:new URLSearchParams(location.search).get("all")!=="0",dir:null,openKey:null,mode:null,modeKey:null,menuKey:null,tab:"request",lastTab:"request",runDoc:null,clearMenu:!1,wantTerm:null,setQ:"",pageGen:0,PL:[],shown:new Set((()=>{try{return JSON.parse(localStorage.getItem("kz-show"))||[]}catch(e){return[]}})())};var t=(e)=>document.getElementById(e),p=(e)=>String(e).replace(/[&<>"]/g,(n)=>({"&":"&","<":"<",">":">",'"':"""})[n]),L=(e)=>`<svg><use href="#i-${e}"/></svg>`;var pn;function q(e,n){let o=t("toast");if(o.textContent=e,o.classList.toggle("rep",!!n),n){let r=document.createElement("a");r.href="/issue?"+new URLSearchParams(n),r.target="_blank",r.rel="noopener",r.textContent="Report",o.append(" ",r)}o.classList.add("on"),clearTimeout(pn),pn=setTimeout(()=>o.classList.remove("on"),n?8000:4000)}function me(){try{if(typeof Notification<"u"&&Notification.permission==="default")Notification.requestPermission()}catch(e){}}function fn(e){try{if(typeof Notification<"u"&&Notification.permission==="granted")new Notification("kaizen",{body:e,icon:"/logo.png"})}catch(n){}}var cs="https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.3.0",hn={tiny:"onnx-community/whisper-tiny",base:"onnx-community/whisper-base",small:"onnx-community/whisper-small","large-v3-turbo":"onnx-community/whisper-large-v3-turbo"},Et=null,ds=`import{pipeline}from"${cs}";let asr=null;
|
|
3
3
|
onmessage=async e=>{const m=e.data;try{
|
|
4
4
|
if(m.type==="load"){const prog=p=>{if(p.status==="progress")postMessage({type:"progress",file:p.file,pct:p.progress||0})};
|
|
@@ -8,26 +8,26 @@ onmessage=async e=>{const m=e.data;try{
|
|
|
8
8
|
catch(e){if(gpu)postMessage({type:"fallback",why:String(e&&e.message||e)});asr=await pipeline("automatic-speech-recognition",m.model,{...cpu,progress_callback:prog})}
|
|
9
9
|
postMessage({type:"ready"})}
|
|
10
10
|
if(m.type==="run"){const r=await asr(m.audio,{chunk_length_s:30,return_timestamps:false});postMessage({type:"text",sid:m.sid,seq:m.seq,text:(r.text||"").trim()})}
|
|
11
|
-
}catch(err){postMessage({type:"error",sid:m.sid,seq:m.seq,error:String(err&&err.message||err)})}}`,us='class T extends AudioWorkletProcessor{process(i){const c=i[0]&&i[0][0];if(c)this.port.postMessage(c.slice(0));return true}}registerProcessor("kz-tap",T)',ps=!!(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&window.AudioWorkletNode&&window.WebAssembly&&window.Worker),ge=null,Se=null,ut=()=>{},ce=null,Pe=!1,pt=new Set;async function fs(){let e="small";try{e=((await fetch("/settings").then((o)=>o.json())).rows.find((o)=>o.key==="dictation.model")||{}).value||e}catch(n){}if(!hn[e])e="small";if(Se&&Et!==e){try{ge.terminate()}catch(n){}ge=null,Se=null,pt.clear(),ce=null}if(Se)return Se;return Et=e,Se=new Promise(async(n,o)=>{ge=new Worker(URL.createObjectURL(new Blob([ds],{type:"text/javascript"})),{type:"module"});let r={};ge.onmessage=(c)=>{let m=c.data;if(m.type==="progress"){r[m.file]=m.pct;let b=Object.values(r);ut(Math.round(b.reduce((y,f)=>y+f,0)/b.length))}else if(m.type==="ready")n();else if(m.type==="fallback")q("GPU could not run the model, using the CPU: "+m.why);else if(m.type==="error"&&m.seq==null)o(Error(m.error));for(let b of pt)b.onmsg(m)},ge.onerror=(c)=>o(Error(c.message||"worker failed"));let l=null;try{l=navigator.gpu&&await navigator.gpu.requestAdapter()}catch(c){}if(!l)q("No WebGPU: dictation will run on the CPU and be slower");ge.postMessage({type:"load",model:hn[Et],webgpu:!!l,f16:!!(l&&l.features.has("shader-f16"))})}),Se.catch(()=>{Se=null;try{ge.terminate()}catch(n){}ge=null}),Se}function de(e){if(ce&&(e||!ce.field.isConnected))ce.stop()}function Je(e){if(!ps)return null;let n=document.createElement("button");n.className="ib sm mic",n.title="Dictate",n.setAttribute("aria-label","Dictate"),n.type="button";let o=()=>{n.innerHTML=L("mic"),n.classList.remove("on","busy")};o();let r=e[0];return e.forEach((l)=>l.addEventListener("focus",()=>r=l)),n.onclick=async(l)=>{if(l.preventDefault(),l.stopPropagation(),ce){let M=ce.btn===n;if(ce.stop(),M)return}if(Pe)return;Pe=!0,n.classList.add("busy"),ut=(M)=>{n.innerHTML=`<i>${M}%</i>`};try{await fs()}catch(M){o(),ut=()=>{},Pe=!1,q("Dictation model could not load: "+M.message);return}if(ut=()=>{},n.innerHTML=L("mic"),!n.isConnected){o(),Pe=!1;return}let c;try{c=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,noiseSuppression:!0,echoCancellation:!0}})}catch(M){o(),Pe=!1,q("Microphone not available: "+(M.name==="NotAllowedError"?"permission denied":M.message));return}if(ce)ce.stop();let m=new AudioContext({sampleRate:16000});await m.audioWorklet.addModule(URL.createObjectURL(new Blob([us],{type:"text/javascript"})));let b=m.createMediaStreamSource(c),y=new AudioWorkletNode(m,"kz-tap");b.connect(y);let f=r,u=f.selectionStart??f.value.length,
|
|
12
|
-
`),o=n.findIndex((y)=>/^#+\s*(\d+\.\s*)?open questions/i.test(y.trim()));if(o<0)return[];let r=/^#+/.exec(n[o].trim())[0].length,l=[],c=null,m=!1;for(let y of n.slice(o+1)){let f=y.trim(),u=/^#+(?=\s)/.exec(f);if(u&&u[0].length<=r)break;if(!u&&f&&!/^```/.test(f)&&!/^none\.?$/i.test(f))m=!0;let
|
|
13
|
-
`:"")+u.map(([h,d],v)=>
|
|
11
|
+
}catch(err){postMessage({type:"error",sid:m.sid,seq:m.seq,error:String(err&&err.message||err)})}}`,us='class T extends AudioWorkletProcessor{process(i){const c=i[0]&&i[0][0];if(c)this.port.postMessage(c.slice(0));return true}}registerProcessor("kz-tap",T)',ps=!!(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&window.AudioWorkletNode&&window.WebAssembly&&window.Worker),ge=null,Se=null,ut=()=>{},ce=null,Pe=!1,pt=new Set;async function fs(){let e="small";try{e=((await fetch("/settings").then((o)=>o.json())).rows.find((o)=>o.key==="dictation.model")||{}).value||e}catch(n){}if(!hn[e])e="small";if(Se&&Et!==e){try{ge.terminate()}catch(n){}ge=null,Se=null,pt.clear(),ce=null}if(Se)return Se;return Et=e,Se=new Promise(async(n,o)=>{ge=new Worker(URL.createObjectURL(new Blob([ds],{type:"text/javascript"})),{type:"module"});let r={};ge.onmessage=(c)=>{let m=c.data;if(m.type==="progress"){r[m.file]=m.pct;let b=Object.values(r);ut(Math.round(b.reduce((y,f)=>y+f,0)/b.length))}else if(m.type==="ready")n();else if(m.type==="fallback")q("GPU could not run the model, using the CPU: "+m.why);else if(m.type==="error"&&m.seq==null)o(Error(m.error));for(let b of pt)b.onmsg(m)},ge.onerror=(c)=>o(Error(c.message||"worker failed"));let l=null;try{l=navigator.gpu&&await navigator.gpu.requestAdapter()}catch(c){}if(!l)q("No WebGPU: dictation will run on the CPU and be slower");ge.postMessage({type:"load",model:hn[Et],webgpu:!!l,f16:!!(l&&l.features.has("shader-f16"))})}),Se.catch(()=>{Se=null;try{ge.terminate()}catch(n){}ge=null}),Se}function de(e){if(ce&&(e||!ce.field.isConnected))ce.stop()}function Je(e){if(!ps)return null;let n=document.createElement("button");n.className="ib sm mic",n.title="Dictate",n.setAttribute("aria-label","Dictate"),n.type="button";let o=()=>{n.innerHTML=L("mic"),n.classList.remove("on","busy")};o();let r=e[0];return e.forEach((l)=>l.addEventListener("focus",()=>r=l)),n.onclick=async(l)=>{if(l.preventDefault(),l.stopPropagation(),ce){let M=ce.btn===n;if(ce.stop(),M)return}if(Pe)return;Pe=!0,n.classList.add("busy"),ut=(M)=>{n.innerHTML=`<i>${M}%</i>`};try{await fs()}catch(M){o(),ut=()=>{},Pe=!1,q("Dictation model could not load: "+M.message);return}if(ut=()=>{},n.innerHTML=L("mic"),!n.isConnected){o(),Pe=!1;return}let c;try{c=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,noiseSuppression:!0,echoCancellation:!0}})}catch(M){o(),Pe=!1,q("Microphone not available: "+(M.name==="NotAllowedError"?"permission denied":M.message));return}if(ce)ce.stop();let m=new AudioContext({sampleRate:16000});await m.audioWorklet.addModule(URL.createObjectURL(new Blob([us],{type:"text/javascript"})));let b=m.createMediaStreamSource(c),y=new AudioWorkletNode(m,"kz-tap");b.connect(y);let f=r,u=f.selectionStart??f.value.length,w=document.createElement("span");w.className="vu",w.setAttribute("aria-hidden","true"),w.innerHTML=[0.5,0.9,1.3,0.8,0.45].map((M)=>`<i style="--w:${M}"></i>`).join("");let h=[],d=0,v=0,x=0,k=0,E=0,P=new Map,C=!1,H=16000,F=6*H,Z=0.8*H,z=1*H,I=(M)=>{if(!M||!f.isConnected)return;let G=f.value;if(document.activeElement===f)u=f.selectionStart;u=Math.min(u,G.length);let J=G.slice(0,u),oe=J&&!/\s$/.test(J)?" ":"";f.value=J+oe+M+G.slice(u),u=J.length+oe.length+M.length,f.selectionStart=f.selectionEnd=u,f.dispatchEvent(new Event("input",{bubbles:!0}))},ie=()=>{if(!d||v<z){h=[],d=0,v=0,x=0;return}let M=new Float32Array(d),G=0;for(let J of h)M.set(J,G),G+=J.length;h=[],d=0,v=0,x=0,P.set(k,null),ge.postMessage({type:"run",sid:R,seq:k,audio:M},[M.buffer]),k++,n.classList.add("busy")},W=()=>{while(P.has(E)&&P.get(E)!==null)I(P.get(E)),P.delete(E),E++;if(!P.size){if(n.classList.remove("busy"),C)o(),pt.delete(xe)}};y.port.onmessage=(M)=>{let G=M.data;h.push(G),d+=G.length;let J=0;for(let fe=0;fe<G.length;fe++)J+=G[fe]*G[fe];let oe=Math.sqrt(J/G.length);if(oe>0.01)v+=G.length,x=0;else x+=G.length;{let fe=Math.min(1,oe*8).toFixed(2);f.style.setProperty("--lvl",fe),w.style.setProperty("--lvl",fe)}if(d>=F||v>=z&&x>=Z)ie()};let R=Math.random().toString(36).slice(2),xe={btn:n,field:f,onmsg:(M)=>{if(M.sid!==R)return;if(M.type==="text")P.set(M.seq,M.text),W();else if(M.type==="error"&&M.seq!=null)P.set(M.seq,""),q("Dictation failed: "+M.error),W()},stop:()=>{if(C)return;C=!0,n.classList.remove("on"),f.classList.remove("dictating"),f.style.removeProperty("--lvl"),n.innerHTML=L("mic");try{y.port.onmessage=null,b.disconnect(),y.disconnect(),c.getTracks().forEach((M)=>M.stop()),m.close()}catch(M){}ce=null,ie(),W()}};pt.add(xe),ce=xe,Pe=!1,n.classList.remove("busy"),n.classList.add("on"),f.classList.add("dictating"),n.replaceChildren(w),f.focus()},n}var mn="kz-d:";function Re(e,n){try{let o=sessionStorage.getItem("kz-d:"+n);if(o!=null)e.value=o}catch(o){}e.addEventListener("input",()=>{try{sessionStorage.setItem("kz-d:"+n,e.value)}catch(o){}})}function gn(e){try{Object.keys(sessionStorage).forEach((n)=>{if(n.startsWith("kz-d:"+e))sessionStorage.removeItem(n)})}catch(n){}}var bn=86400000,yn=(e,n)=>{let o=n-e;return o<3600000?Math.round(o/60000)+"m":o<86400000?Math.round(o/3600000)+"h":Math.round(o/86400000)+"d"},Mt={claude:"Claude Code",codex:"Codex",agy:"Antigravity",opencode:"OpenCode",gemini:"Gemini",cursor:"Cursor",yolo:"yolo",unrechecked:"fix not rechecked"},vn={claude:1,codex:1,agy:1,opencode:1,gemini:1,cursor:1,full:1,lite:1},kn=["Ideas","Planning","Building","Review","Done"],qe={idea:"idea",starting:"start",running:"run",stalled:"stall",waiting:"wait",done:"done",abandoned:"aband"},hs={"approvals.plan":"plan needs your approval","approvals.review":"review needs your approval"},Xe=(e)=>hs[e]||e,Ke={idea:"idea",starting:"starting",running:"running",stalled:"stalled",waiting:"waiting on you",done:"done",abandoned:"abandoned"};function wn(e,n){let o=e&&e.match(new RegExp(`^##\\s+${n}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$(?![\\s\\S]))`,"m"));return o?o[1].trim():null}var ft=(e)=>wn(e,"Preview"),ht=(e,n)=>e.replace(/\]\((attachments\/[^)]+)\)/g,(o,r)=>`](/file?dir=${encodeURIComponent(n.state)}&p=${encodeURIComponent((n.kind==="run"?`runs/${n.id}/`:"")+r)})`),$n=(e)=>(wn(e,"Commit")||"").replace(/^```\w*\n|\n```$/g,"").trim();function xn(e){let n=(e||"").split(`
|
|
12
|
+
`),o=n.findIndex((y)=>/^#+\s*(\d+\.\s*)?open questions/i.test(y.trim()));if(o<0)return[];let r=/^#+/.exec(n[o].trim())[0].length,l=[],c=null,m=!1;for(let y of n.slice(o+1)){let f=y.trim(),u=/^#+(?=\s)/.exec(f);if(u&&u[0].length<=r)break;if(!u&&f&&!/^```/.test(f)&&!/^none\.?$/i.test(f))m=!0;let w=/^\**Q\d*\s*[:.)]\**\s*(.+)$/.exec(f);if(w){l.push(c={q:w[1].trim(),multi:/multi-?select/i.test(w[1]),options:[]});continue}let h=/^[-*]\s+(.+)$/.exec(f);if(h&&c){let d=h[1].trim();c.options.push({text:d,rec:/\(recommended\)/i.test(d),label:d.replace(/\s*\(recommended\)/i,"").split(/\s+[—–-]\s+|:\s/)[0].trim()})}else if(c&&c.options.length)c=null}let b=l.filter((y)=>y.options.length);return b.length||!m?b:null}function Le(e){let n=/(^|[.!?)]\s+|\s)([A-Z][\w-]*(?: [a-z][\w-]*){0,3}):\s+/g,o=[],r;while(r=n.exec(e))if(!/^\s+$/.test(r[1])||!o.length&&!r[2].includes(" "))o.push([r.index+r[1].length,r[2],n.lastIndex]);if(o.length<2)return{head:e,parts:[]};let l=o.map(([,c,m],b)=>{let y=e.slice(m,b+1<o.length?o[b+1][0]:e.length).trim(),f=y.split(/\s*\((\d+)\)\s+/);if(f.length<5)return{label:c,body:y};let u=[];for(let h=1;h<f.length;h+=2)u.push([f[h],f[h+1]]);let w=u.every(([h],d)=>+h===d+1);return{label:c,body:(f[0]?f[0]+`
|
|
13
|
+
`:"")+u.map(([h,d],v)=>w?`${v+1}. ${d}`:`- (${h}) ${d}`).join(`
|
|
14
14
|
`)}});return{head:e.slice(0,o[0][0]).trim()||`${l[0].label}: ${e.slice(o[0][2],o[1][0]).trim()}`,parts:l}}function Ce(e,n){let o=e.split(`
|
|
15
15
|
`),r=[],l=0,c=null,m=(y)=>p(y).replace(/`([^`]+)`/g,"<code>$1</code>").replace(/\*\*([^*]+)\*\*/g,"<b>$1</b>").replace(/!\[([^\]]*)\]\(([^)\s]+)\)/g,'<a href="$2" target="_blank"><img src="$2" alt="$1"></a>').replace(/\[([^\]]+)\]\(([^)\s]+)\)/g,'<a href="$2" target="_blank">$1</a>'),b=()=>{if(c)r.push(`</${c}>`),c=null};while(l<o.length){let y=o[l];if(/^```/.test(y)){b();let h=[];l++;while(l<o.length&&!/^```/.test(o[l]))h.push(o[l++]);r.push(`<pre>${p(h.join(`
|
|
16
|
-
`))}</pre>`),l++;continue}let f=/^(#{1,6})\s+(.*)$/.exec(y);if(f){b(),r.push(`<h${Math.min(f[1].length,3)}>${m(f[2])}</h${Math.min(f[1].length,3)}>`),l++;continue}if(/^---+$/.test(y.trim())){b(),r.push("<hr>"),l++;continue}if(/^\|/.test(y)){b();let h=[];while(l<o.length&&/^\|/.test(o[l]))h.push(o[l++]);let d=(v)=>v.replace(/^\||\|$/g,"").split("|").map((
|
|
17
|
-
${o?Cn(o.rows).map(([d,v])=>`<h3 class="sg">${p(d)}</h3><div class="sgrp">${v.map((
|
|
16
|
+
`))}</pre>`),l++;continue}let f=/^(#{1,6})\s+(.*)$/.exec(y);if(f){b(),r.push(`<h${Math.min(f[1].length,3)}>${m(f[2])}</h${Math.min(f[1].length,3)}>`),l++;continue}if(/^---+$/.test(y.trim())){b(),r.push("<hr>"),l++;continue}if(/^\|/.test(y)){b();let h=[];while(l<o.length&&/^\|/.test(o[l]))h.push(o[l++]);let d=(v)=>v.replace(/^\||\|$/g,"").split("|").map((x)=>m(x.trim()));r.push("<table>"+h.filter((v)=>!/^\|\s*-/.test(v)).map((v,x)=>`<tr>${d(v).map((k)=>x?`<td>${k}</td>`:`<th>${k}</th>`).join("")}</tr>`).join("")+"</table>");continue}let u=/^\s*(?:[-*]|\d+\.)\s+(.*)$/.exec(y);if(u){let h=/^\s*\d+\./.test(y)?"ol":"ul";if(c!==h)b(),r.push(`<${h}>`),c=h;let d=n&&/^(open|done|rejected):\s*(.*)$/.exec(u[1]);r.push(d?`<li class="tk${d[1]==="rejected"?" rej":""}"><label><input type="checkbox" tabindex="-1" aria-disabled="true" onclick="return false"${d[1]==="done"?" checked":""} aria-label="${d[1]}"><span>${m(d[2])}</span></label></li>`:`<li>${m(u[1])}</li>`),l++;continue}if(!y.trim()){b(),l++;continue}b();let w=[y];l++;while(l<o.length&&o[l].trim()&&!/^(#|```|\||\s*[-*]\s|\s*\d+\.\s|---)/.test(o[l]))w.push(o[l++]);r.push(`<p>${m(w.join(" "))}</p>`)}return b(),r.join("")}function Sn(e,n){let o=[...e.matchAll(/^\s*-\s*(todo|doing|done):\s*(.*)$/gm)].map((m)=>[m[1],m[2]]);if(!o.length)return"";let r=o.filter((m)=>m[0]==="done").length,l=Math.round(r/o.length*100),c={done:"✓",doing:'<span class="pulse"></span>',todo:""};return`<div class="prog"><div class="ph"><b>${r} of ${o.length} done</b><span>${l}%</span></div><div class="pbar"><i style="width:${l}%"></i></div><ol>${o.map(([m,b],y)=>`<li class="p-${m}${n&&n[y]&&n[y]!==m?" chg":""}"><span class="pi">${c[m]}</span><span>${p(b).replace(/`([^`]+)`/g,"<code>$1</code>")}</span></li>`).join("")}</ol></div>`}var Ne=null,Dt=null;function qn(e){if(Dt=e,!e.update)return!1;Ne=e;let n=document.querySelector('.rail a[data-view="settings"]');return n.classList.add("upd"),n.title=`Settings: kaizen ${e.latest} is available`,!0}function Pt(){if(!Ne){let l=document.createElement("div");l.className="updbar quiet",l.innerHTML=`<span id="upd-msg" aria-live="polite">${Dt?`You are running kaizen ${p(Dt.version)}.`:"kaizen"}</span><button class="btn" id="upd-chk">Check for updates</button>`,t("pbody").prepend(l);let c=t("upd-chk");c.onclick=async()=>{c.disabled=!0,c.textContent="Checking…";let m=await fetch("/version?fresh=1").then((b)=>b.json()).catch(()=>null);if(!l.isConnected)return;if(c.disabled=!1,c.textContent="Check for updates",m&&qn(m)){l.remove(),Pt(),t("upd-go").focus();return}t("upd-msg").textContent=!m||!m.latest?"Could not reach npm. Try again later.":`kaizen ${m.version} is up to date.`};return}let e=document.createElement("div");e.className="updbar";let n=(Ne.notes||[]).map((l)=>`<li><b>${p(l.v)}</b> ${p(l.text)}</li>`).join(""),o=n?`<span class="updnew"><button class="btn" aria-describedby="upd-notes">What's new</button><span class="updpop" id="upd-notes" role="tooltip"><ul>${n}</ul><a href="https://github.com/hfadhlullah/kaizen/releases" target="_blank" rel="noopener">All release notes</a></span></span>`:"";e.innerHTML=`<span>kaizen <b>${p(Ne.latest)}</b> is available. You are running ${p(Ne.version)}.</span><span class="updact">${o}<button class="btn pri" id="upd-go">Update kaizen</button></span>`,t("pbody").prepend(e);let r=t("upd-go");r.onclick=async()=>{if(!confirm(`Update kaizen to ${Ne.latest}? The board restarts, and agents running inside cards stop.`))return;if(r.disabled=!0,r.textContent="Updating…",!(await A("/upgrade",{})).ok){r.disabled=!1,r.textContent="Update kaizen";return}r.textContent="Restarting…",setTimeout(()=>location.reload(),20000)}}function Ln(){fetch("/version").then((e)=>e.json()).then((e)=>{if(qn(e)&&Q==="settings")be()}).catch(()=>{})}var Cn=(e)=>{let n=new Map;for(let o of e){if(!n.has(o.group))n.set(o.group,[]);n.get(o.group).push(o)}return[...n]};async function be(){if(Q!=="settings")return;let e=++a.pageGen;if(de(!0),!t("panel").classList.contains("open")||t("ptitle").textContent!=="Settings")t("pbody").innerHTML='<p class="snone">Loading…</p>',t("psub").textContent="";ye(),a.openKey=null,a.runDoc=null,t("pterm").hidden=!0,t("panel").classList.add("open"),t("scrim").classList.add("on"),t("ptitle").textContent="Settings",t("ptabs").innerHTML="",t("pfoot").innerHTML="";let n=document.documentElement.dataset.theme||"system",o=await fetch("/settings").then((d)=>d.json()).catch(()=>null),r=await fetch("/google").then((d)=>d.json()).catch(()=>({}));if(e!==a.pageGen)return;t("psub").textContent="Theme applies to this browser. Everything else is saved to your kaizen settings.",t("psub").title=o?o.file:"";let l=(d,v,x)=>{if(d==="s-preset")return`<span class="bar" id="${d}" role="group" aria-label="Preset">${v.filter((k)=>k!=="custom").map((k)=>`<button id="${d}-${k}" data-v="${k}" class="${k===x?"on":""}" aria-pressed="${k===x}">${k}</button>`).join("")}</span>`;if(v.length===2&&v.includes("true")&&v.includes("false")){let k=x==="true";return`<button class="chk${k?" on":""}" id="${d}" data-v="${k?"false":"true"}" role="switch" aria-checked="${k}" title="${k?"On":"Off"}"></button>`}if(v.every((k)=>/^\d+$/.test(k)))return`<span class="bar" id="${d}" role="group">${v.map((k)=>`<button data-v="${k}" class="${k===x?"on":""}" aria-pressed="${k===x}">${k}</button>`).join("")}</span>`;return`<select class="sel" id="${d}">${v.map((k)=>`<option value="${p(k)}"${k===x?" selected":""}>${k===""?"—":p(k)}</option>`).join("")}</select>`},c=(d,v,x,k,E)=>`<div class="srow" data-q="${p(E.toLowerCase())}"><label for="${k}">${p(d)}</label>${v}<p class="hint">${x}</p></div>`,m=document.activeElement?.id==="sq",b=typeof Notification>"u"?null:Notification.permission,y="A desktop notification when a run starts waiting on you."+(b?b==="denied"?" Blocked in this browser's site settings.":"":" Not supported in this browser."),f=[...o?Cn(o.rows).map(([d])=>d):[],"Google","Browser"];t("pbody").innerHTML=`<div class="sq"><input class="sel" id="sq" type="search" placeholder="Search settings" aria-label="Search settings" spellcheck="false" autocomplete="off" value="${p(a.setQ)}"><div class="sjump" role="group" aria-label="Jump to section">${f.map((d)=>`<button data-g="${p(d)}">${p(d)}</button>`).join("")}</div></div>
|
|
17
|
+
${o?Cn(o.rows).map(([d,v])=>`<h3 class="sg">${p(d)}</h3><div class="sgrp">${v.map((x)=>{let k="s-"+x.key.replace(/\./g,"-");return c(x.label,x.text?x.folder?`<span class="fpick"><input class="sel" id="${k}" value="${p(x.value)}" placeholder="not set" spellcheck="false"><button class="btn" id="${k}-pick" type="button">Browse…</button></span>`:`<input class="sel" id="${k}" value="${p(x.value)}" placeholder="not set" spellcheck="false">`:l(k,x.values,x.value),p(x.help),k,[x.label,x.help,x.key,d,...x.values].join(" "))}).join("")}</div>`).join(""):'<p class="snone">Settings could not be read. Run <code>kaizen settings</code>.</p>'}
|
|
18
18
|
<h3 class="sg">Google</h3><div class="sgrp">${c("OAuth client id",`<input class="sel" id="gg-id" placeholder="${r.hasClient?"saved; type to replace":"from Google Cloud, Desktop app"}" spellcheck="false" autocomplete="off">`,"Kaizen writes Done into a sheet through your own Google client: create an OAuth client of type Desktop app in Google Cloud and enable the Sheets API. Nothing is shared with other installs.","gg-id","google oauth client id sheets write back")}
|
|
19
19
|
${c("OAuth client secret",`<span class="fpick"><input class="sel" id="gg-secret" type="password" placeholder="${r.hasClient?"saved; type to replace":""}" spellcheck="false" autocomplete="off"><button class="btn" id="gg-save" type="button">Save</button></span>`,"Kept in ~/.kaizen/google.json, readable only by you. Saving a new client signs Google out.","gg-secret","google oauth client secret")}
|
|
20
20
|
${c("Google account",`<button class="btn${r.connected?"":" pri"}" id="gg-conn" type="button"${r.hasClient||r.connected?"":" disabled"}>${r.connected?"Disconnect":"Connect"}</button>`,r.connected?"Connected. Sources with Write back on put Done in the sheet row once their run is done.":"Connect after saving the client. Needed only for Write back on sheet sources.","gg-conn","google account connect sheets write back")}</div>
|
|
21
21
|
<h3 class="sg">Browser</h3><div class="sgrp">${c("Theme",l("th",["system","light","dark"],n),"Light or dark for this browser only; system follows your device.","th","theme light dark system browser")}
|
|
22
22
|
${c("Notifications",`<button class="chk${b==="granted"?" on":""}" id="nt" role="switch" aria-checked="${b==="granted"}"${b&&b!=="denied"?"":" disabled"}></button>`,y,"nt","notifications "+y+" browser")}</div>
|
|
23
|
-
<p class="snone" id="snone" hidden></p>`,Pt();let u=[...t("pbody").querySelectorAll(".sg")],k=[...t("pbody").querySelectorAll(".sjump button")],h=()=>{let d=a.setQ.trim().toLowerCase(),v=!1;u.forEach((w,x)=>{let E=!1;for(let P of w.nextElementSibling.children)P.hidden=!!d&&!P.dataset.q.includes(d),E||=!P.hidden;if(w.hidden=!E,k[x])k[x].hidden=!E;v||=E}),t("snone").hidden=v,t("snone").textContent=`No setting matches “${a.setQ.trim()}”.`};if(k.forEach((d,v)=>d.onclick=()=>{let w=t("pbody");w.scrollTo({top:w.scrollTop+u[v].getBoundingClientRect().top-w.querySelector(".sq").getBoundingClientRect().bottom-8,behavior:"smooth"})}),t("sq").oninput=(d)=>{a.setQ=d.target.value,h()},t("sq").onkeydown=(d)=>{if(d.key!=="Escape")return;if(d.preventDefault(),a.setQ)a.setQ="",d.target.value="",h();else X()},h(),m){let d=t("sq");d.focus(),d.setSelectionRange(d.value.length,d.value.length)}if(t("gg-save").onclick=async()=>{let d=await A("/google",{op:"client",id:t("gg-id").value,secret:t("gg-secret").value});if(d.ok)q("Saved");else q(d.error||"Enter both the client id and the secret");be()},t("gg-conn").onclick=async()=>{if(r.connected)return await A("/google",{op:"disconnect"}),be();let d=window.open("","_blank"),v=await A("/google",{op:"connect"});if(v.ok&&d)d.location=v.url;else d?.close(),q(v.error||"Could not start Google sign-in")},t("th").onchange=(d)=>setTheme(d.target.value),t("nt").onclick=()=>{me(),setTimeout(be,300)},o)o.rows.forEach((d)=>{let v=t("s-"+d.key.replace(/\./g,"-")),w=async(x)=>{let E=document.activeElement?.id;if((await A("/settings",{key:d.key,value:x})).ok)q("Saved");if(await be(),E?.startsWith("s-preset-"))t(E)?.focus()};if(v.tagName==="SELECT"||v.tagName==="INPUT")v.onchange=async()=>{if(await w(v.value.trim()),v.tagName==="INPUT")N()};else v.querySelectorAll("[data-v]").forEach((x)=>x.onclick=()=>w(x.dataset.v));if(v.matches(".chk"))v.onclick=()=>w(v.dataset.v);if(d.folder)t(v.id+"-pick").onclick=async()=>{let x=await A("/pick-folder",{});if(x.path)await w(x.path),N()}})}var Q="board",Rt=(e)=>{document.querySelectorAll(".rail a.it").forEach((n)=>n.dataset.view===e?n.setAttribute("aria-current","page"):n.removeAttribute("aria-current")),document.querySelector(".hdr h1").textContent=e==="sources"?"Icebox":"Board"};function re(e,n){if(a.pageGen++,e==="icebox")e="sources";if(!["settings","sources"].includes(e))e="board";let o=Q;if(Q=e,Rt(e),document.body.classList.toggle("paged",e!=="board"),document.body.classList.toggle("ice",e==="sources"),n&&e!==o)history.pushState(null,"",e==="board"?location.pathname:"?view="+(e==="sources"?"icebox":e));let r=t("panel");if(e==="board"){if(o==="board")return;r.style.transition="none",r.classList.remove("open","docked"),r.offsetWidth,r.style.transition="",X();return}if(r.classList.add("docked"),e==="settings")be();else K()}function jn(){document.querySelectorAll(".rail a[data-view]").forEach((e)=>{if(e.dataset.view==="notes")return;e.onclick=(n)=>{if(n.ctrlKey||n.metaKey||n.shiftKey||n.button)return;if(n.preventDefault(),e.dataset.view==="settings")a.setQ="";re(e.dataset.view,!0)}}),document.querySelector(".rail .home").onclick=(e)=>{if(e.ctrlKey||e.metaKey||e.shiftKey||e.button)return;e.preventDefault(),re("board",!0)},addEventListener("popstate",()=>re(new URLSearchParams(location.search).get("view"))),addEventListener("kz-theme",()=>{if(Q==="settings")be()})}var Tn="Icebox",ne=null,tt={},Kt=(e)=>{if(!tt[e])try{tt[e]=localStorage.getItem("kz-reply-style:"+e)}catch(n){}return tt[e]||"casual"},ms={casual:"Casual",fun:"Fun",helpful:"Helpful",promo:"Promo"},se=null,ve=null,le=null,ue="",ee="",He="link",Ze=null,Ie=!1,mt=!1,gt=null,_=new Set,et={x:"X",tiktok:"TikTok",instagram:"Instagram",threads:"Threads"},B=()=>t("panel").classList.contains("open")&&t("ptitle").textContent===Tn,Te=(e)=>{let n=Math.round((Date.now()-Date.parse(e))/60000);return n<1?"just now":n<60?n+" min ago":n<1440?Math.round(n/60)+" h ago":Math.round(n/1440)+" d ago"};function An(e,n){let o=t(e);if(!o)return;let r=o.querySelector("button.sel"),l=o.querySelector(".menu-pop"),c=l.querySelector("input"),m=l.querySelector(".pick-list"),b=(f)=>{if(l.hidden=!f,r.setAttribute("aria-expanded",f),f)c.value="",y(),c.focus()},y=()=>{let f=c.value.trim().toLowerCase(),u=null,k=!1;for(let h of m.children)if(h.classList.contains("grp")){if(u)u.hidden=!k;u=h,k=!1}else{let d=!f||h.dataset.q.includes(f)||u&&u.dataset.g.includes(f);h.hidden=!d,k||=d}if(u)u.hidden=!k};r.onclick=()=>b(l.hidden),c.oninput=y,c.onkeydown=(f)=>{if(f.key==="Escape")f.stopPropagation(),b(!1),r.focus();else if(f.key==="Enter")f.preventDefault(),m.querySelector("button:not([hidden])")?.click()},m.onclick=(f)=>{let u=f.target.closest("button[data-v]");if(!u)return;b(!1),n(u.dataset.v)}}async function K(){if(Q!=="sources")return;let e=++a.pageGen;de(!0);let n=B()?[t("src-url")?.value||"",t("src-label")?.value||"",document.activeElement?.id,t("src-dir")?.value,t("src-cat")?.value||"",t("src-plat")?.value,t("src-q")?.value||""]:["",""],o=new Map(B()?[...t("pbody").querySelectorAll("[data-mv]")].map((s)=>[s.dataset.k,s.value]):[]),r=B()&&document.activeElement?.closest?.(".ice-row"),l=r?[r.dataset.k,document.activeElement.id.replace(/-\d+$/,"")]:null,c=B()&&t("ice-title")?[t("ice-title").dataset.k,t("ice-title").value,t("ice-details").value]:null,m=new Map(B()?[...t("pbody").querySelectorAll("[data-ct]")].map((s)=>[s.dataset.k,s.value]):[]);if(!B())t("pbody").innerHTML='<p class="snone">Loading…</p>';ye(),a.openKey=null,a.runDoc=null,t("pterm").hidden=!0,t("panel").classList.add("open"),t("scrim").classList.add("on"),t("ptitle").textContent=Tn,t("ptabs").innerHTML="",t("pfoot").innerHTML="",t("psub").textContent="",t("psub").title="";let b=a.all?null:a.S.dir,y=b?"?dir="+encodeURIComponent(b):"",f=await fetch("/sources"+y).then((s)=>s.json()).catch(()=>null);if(e!==a.pageGen||!B())return;let u=(s)=>{try{return new URL(s).hostname}catch{return s}},k=(s,i,g)=>{if(et[g])return`<span class="fav">${L("b-"+g)}</span>`;let S;try{let O=new URL(s);if(/^https?:$/.test(O.protocol))S=O.origin}catch{}let j=!S?"":s.includes("docs.google.com/spreadsheets")?"https://ssl.gstatic.com/docs/spreadsheets/favicon3.ico":s.includes("docs.google.com/document")?"https://ssl.gstatic.com/docs/documents/images/kix-favicon7.ico":S+"/favicon.ico";return`<span class="fav">${j?`<img src="${p(j)}" alt="" loading="lazy" referrerpolicy="no-referrer" onerror="this.remove()">`:""}${L(i)}</span>`},h=(s)=>et[s?.social]||(s?u(s.url).replace(/^www\./,""):"")||"Other",d=(s)=>{if(s.social)return"social";let i=s.url;if(i.includes("docs.google.com/spreadsheets")||i.endsWith(".csv")||i.endsWith(".tsv"))return"sheet";if(i.includes("docs.google.com/document"))return"doc";return"link"},v=f&&f.sources||[],w=[...f&&f.icebox||[],...f&&f.dismissed||[]],x=(f&&f.icebox||[]).length,E=f&&f.gatherer||{},P=E.use==="chatgpt"&&E.connected,C=(s,i)=>(s&&!a.S.projects.some((g)=>g.dir===s)?`<option value="${p(s)}" title="${p(i||s)}" selected>${p(ke(i||s))}</option>`:"")+a.S.projects.map((g)=>`<option value="${p(g.dir)}" title="${p(g.label)}"${g.dir===s?" selected":""}>${p(ke(g.label))}</option>`).join(""),H=v.map((s,i)=>{let g=d(s),S=s.label||u(s.url),j=s.social&&(!s.last||!s.last.ok&&/gathered by your agent/i.test(s.last.note))?`<span class="src-badge dim">${L("at")}${P?"Gathered with ChatGPT":"Gathered by your agent"}</span>`:!s.last?'<span class="src-badge dim">Not gathered yet</span>':s.last.ok?`<span class="src-badge ok"><svg viewBox="0 0 16 16"><path d="M3.5 8.5l3 3 6-6" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>${p(s.last.note)}</span><span class="hint" style="margin:0">${Te(s.last.at)}</span>`:`<span class="src-badge warn"><svg viewBox="0 0 16 16"><circle cx="8" cy="8" r="6" stroke="currentColor" fill="none" stroke-width="1.8"/><path d="M8 5v3.5M8 11v.1" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round"/></svg>Failed: ${p(s.last.note)}</span><span class="hint" style="margin:0">${Te(s.last.at)}</span>`,O=g==="sheet"?"sheet":g==="doc"?"doc":g==="social"?"at":"link",Y=s.url.replace(/^https?:\/\/(www\.)?/,"");return`<div class="src-card" id="src-card-${i}"><div class="src-hd"><span class="src-ico" title="${g==="social"?p(et[s.social]||"social"):g}">${k(s.url,O,s.social)}</span><div class="src-info"><span class="src-title" title="${p(S)}">${p(S)}</span><a class="src-url" href="${p(s.url)}" target="_blank" rel="noopener noreferrer" title="${p(s.url)}"><span>${p(Y)}</span>${L("ext")}</a></div><div class="src-acts">${s.social?`<button class="ib sm" id="src-ag-${i}" data-ag="${i}" aria-label="Gather ${p(S)} with your agent" title="${P?"Gather with your ChatGPT plan":"Gather: opens your agent on /kaizen gather"}">${L("refresh")}</button>`:`<button class="ib sm" id="src-chk-${i}" data-chk="${i}" aria-label="Check ${p(S)} now" title="Check now">${L("refresh")}</button>`}<button class="ib sm danger" id="src-rm-${i}" data-rm="${i}" aria-label="Remove ${p(S)}" title="Remove source">${L("trash")}</button></div></div><div class="src-status">${j}</div><div class="src-fields"><label class="src-f" title="Category"><span>Category</span><input class="sel" id="src-ct-${i}" data-ct="${i}" data-k="${p(s.dir+" "+s.url)}" value="${p(s.category||"")}" placeholder="${p(h(s))}" maxlength="40" aria-label="Category of ${p(S)}"><button class="btn sm pri" id="src-cs-${i}" aria-label="Save the category of ${p(S)}" disabled>Save</button></label><label class="src-f" title="Project"><span>Project</span>${Oe(s.dir,s.project||"")}<select class="sel" id="src-mv-${i}" data-mv="${i}" data-k="${p(s.dir+" "+s.url)}" aria-label="Project of ${p(S)}">${C(s.dir,s.project)}</select><button class="btn sm pri" id="src-mb-${i}" aria-label="Move ${p(S)} to the chosen project" disabled>Move</button></label>${g==="sheet"||/github\.com/.test(s.url)?`<label class="src-wb"><input type="checkbox" id="src-wb-${i}" data-wb="${i}"${s.writeback?" checked":""}> ${g==="sheet"?"Mark the row Done":"Comment and close the issue"} when its run is done</label>`:""}</div></div>`}).join(""),F=(f&&f.failed||[]).map((s)=>`<p style="color:var(--dim)">The sources of ${p(s)} could not be read.</p>`).join(""),Z=`<div class="src-empty"><div class="src-empty-ico">${L("link")}</div><h4>No request sources yet</h4><p>Connect shared links to gather requests into the icebox, where you choose which become ideas. Nothing runs automatically until you start it.</p><div class="src-empty-grid"><div class="src-empty-item"><strong>Google Sheets</strong>Public sheets with idea or task rows. Rows marked Done are skipped.</div><div class="src-empty-item"><strong>Google Docs</strong>Shared public docs with items or notes written line by line.</div><div class="src-empty-item"><strong>Public links</strong>TSV, CSV, or raw text links containing requests.</div><div class="src-empty-item"><strong>Social media</strong>A keyword, #hashtag or @account on X, TikTok, Instagram or Threads, read by your agent.</div></div></div>`,z=He==="social",I=!v.length||Ie||[n[0],n[1],n[4],n[6]].some(Boolean),ie=`<div class="src-add" id="src-addbox"${I?"":" hidden"}><div class="hd"><h4>Add a source</h4><div class="src-kind" role="group" aria-label="Kind of source"><button id="src-k-link" data-kind="link" aria-pressed="${!z}">Link</button><button id="src-k-social" data-kind="social" aria-pressed="${z}">Social</button></div></div><label for="src-url"${z?" hidden":""}><span>Link</span><input class="sel" id="src-url" placeholder="https://docs.google.com/spreadsheets/d/… or any public link" spellcheck="false" autocomplete="off"></label><div class="two plat"${z?"":" hidden"}><label for="src-plat"><span>Platform</span><select class="sel" id="src-plat">${Object.entries(et).map(([s,i])=>`<option value="${s}">${i}</option>`).join("")}</select></label><label for="src-q"><span>Follow</span><input class="sel" id="src-q" placeholder="keyword, #hashtag or @account" maxlength="100" spellcheck="false" autocomplete="off"></label></div><div class="two"><label for="src-label"><span>Label <i>optional</i></span><input class="sel" id="src-label" placeholder="${z?"e.g. Mentions on X":"e.g. Feedback Q4"}"></label><label for="src-cat"><span>Category <i>optional</i></span><input class="sel" id="src-cat" placeholder="${z?"e.g. Social":"e.g. Bugs"}" maxlength="40"></label></div><label for="src-dir"><span>Project</span><select class="sel" id="src-dir">${b?"":'<option value="">Choose project…</option>'}${C(b)}</select></label><div class="acts"><span class="hint">${z?P?"Your ChatGPT plan reads public posts when you press Gather on the source.":"Kaizen signs in nowhere. Your agent reads public posts when you press Gather on the source.":"Public links only. For private docs, run /kaizen gather."}</span><button class="btn pri" id="src-add">Add source</button></div></div>`,W=(s)=>{let i=v.find((g)=>g.dir===s.dir&&g.url===s.source);return i?i.label||u(i.url):u(s.source)},R=(s)=>s.dir+" "+s.id,xe=(s)=>s.dir+" "+s.source,M=(s)=>v.find((i)=>i.dir===s.dir&&i.url===s.source),G=(s)=>s.status==="dismissed"?"Dismissed":s.status==="done"?"Done":s.category||M(s)?.category||h(M(s)||{url:s.source}),J=(s,i)=>{let g=new Map;return s.forEach((S)=>{let j=i(S);g.set(j,{it:S,n:(g.get(j)?.n||0)+1})}),g},oe=b?new Map:J(w,(s)=>s.dir);if(!b){for(let s of a.shown)if(!oe.has(s)){let i=Ee().find((g)=>g.dir===s);if(i)oe.set(s,{it:{dir:s,project:i.label},n:0})}}let fe=(s)=>b||!a.shown.size||a.shown.has(s.dir),tn=w.filter(fe),_e=J(tn,G);if(ee&&!_e.has(ee))ee="";let Vn=tn.filter((s)=>!ee||G(s)===ee),Be=J(Vn,xe);if(ue&&!Be.has(ue))ue="";let Jn=new Set(w.map(R));if(se&&!Jn.has(se))se=null;if(ve!==se)ve=null;let Ye=new Map;w.forEach((s,i)=>{if(!fe(s)||(ee?G(s)!==ee:s.status==="dismissed"||s.status==="done")||ue&&xe(s)!==ue)return;let g=G(s);Ye.has(g)||Ye.set(g,[]),Ye.get(g).push(i)});let U=[...Ye.values()].flat();{let s=new Set(U.map((i)=>R(w[i])));[..._].forEach((i)=>s.has(i)||_.delete(i))}if(!U.some((s)=>R(w[s])===le))le=U.length?R(w[U[0]]):null;let Qe=U.filter((s)=>_.has(R(w[s]))).length,Xn=oe.size>1,nn=(s,i,g,S)=>`<div class="combo ice-combo" id="${s}"><button class="sel" aria-label="${i}" aria-haspopup="listbox" aria-expanded="false">${g}</button><div class="menu-pop" hidden><input class="sel" aria-label="Search ${i.toLowerCase()}" placeholder="Search…" spellcheck="false" autocomplete="off"><div class="pick-list" role="listbox">${S}</div></div></div>`,at=(s,i,g,S,j="")=>`<button type="button" role="option" data-v="${p(s)}" data-q="${p(S.toLowerCase())}" class="${i?"on":""}${j}" aria-selected="${i}">${g}</button>`,it=new Map;for(let[s,{it:i}]of Be){let g=h(M(i)||{url:i.source});it.has(g)||it.set(g,[]),it.get(g).push([s,i])}let Ct=(s)=>k(s.source,"link",M(s)?.social),jt=ue&&[...Be].find(([s])=>s===ue)?.[1].it,Zn=(_e.size>1||_e.has("Dismissed")||_e.has("Done")?nn("ice-fc","Category",`<span>${p(ee||"All categories")}</span>`,at("",!ee,"<span>All categories</span>","all categories")+[..._e].map(([s,{n:i}])=>at(s,ee===s,`<span>${p(s)}</span><i>${i}</i>`,s)).join("")):"")+(Be.size>1?nn("ice-fs","Source",jt?`${Ct(jt)}<span>${p(W(jt))}</span>`:"<span>All sources</span>",at("",!ue,"<span>All sources</span>","all sources")+[...it].map(([s,i])=>`<div class="grp" data-g="${p(s.toLowerCase())}">${Ct(i[0][1])}${p(s)}</div>`+i.map(([g,S])=>at(g,ue===g,`${Ct(S)}<span>${p(W(S).toLowerCase().startsWith(s.toLowerCase()+":")?W(S).slice(s.length+1).trim():W(S))}</span><i>${Be.get(g).n}</i>`,s+" "+W(S)," sub")).join("")).join("")):""),es=`<div class="ice-bar">${U.length?`<input type="checkbox" id="ice-all" aria-label="Select every request shown"${Qe&&Qe===U.length?" checked":""}>`:""}<span class="count">${ee==="Dismissed"||ee==="Done"?`${U.length} ${ee.toLowerCase()}`:`${U.length<x?`${U.length} of ${x}`:x} waiting`}</span>${Zn}<span class="end"></span><button class="btn sm" id="src-go"${v.length?"":" disabled"}>${L("refresh")} Gather all</button><button class="btn ghost sm" id="src-tog" aria-expanded="${Ze??!v.length}" aria-controls="src-sec">${L("link")} Sources · ${v.length}</button></div>`,sn=_.size?`<div class="ice-bulk" role="toolbar" aria-label="Selected requests"><span>${_.size} selected</span><span class="end"></span><button class="btn sm" id="ice-bp" title="Each becomes its own idea, titled as gathered (p)">${L("plus")} Promote</button><button class="btn ghost sm" id="ice-bd" title="Dismiss (d)">${L("x")} Dismiss</button><button class="btn ghost sm" id="ice-bc" title="Clear selection (Esc)">Clear</button></div>`:"",ts=(s)=>`<div class="ice-form"><label for="ice-title" class="hint" style="margin:0">Idea title</label><input class="sel" id="ice-title" data-k="${p(R(s))}" maxlength="200" value="${p(s.text)}" aria-label="Idea title"><label for="ice-details" class="hint" style="margin:0">Details</label><textarea class="sel" id="ice-details" rows="3" placeholder="What, for whom, why" aria-label="Details"></textarea><div class="ice-form-acts"><span class="hint">Source goes into the notes.</span><button class="btn ghost sm" id="ice-cancel">Cancel</button><button class="btn pri sm" id="ice-ok">Promote</button></div></div>`,ns=(s)=>{if(!s.post)return"";if(s.replied)return`<span class="hint" style="margin:0">You replied to this post ${Te(s.replied)}.</span>`;let i=ne&&ne.k===R(s)?ne:null,g=(S)=>`<select class="sel" id="ice-rst" aria-label="Reply style" title="Promo names kaizen; Helpful may mention it when it fits; Casual and Fun never do"${S?" disabled":""}>${Object.entries(ms).map(([j,O])=>`<option value="${j}"${j===Kt(s.dir)?" selected":""}>${O}</option>`).join("")}</select>`;if(!i)return`<div class="ice-form-acts"><span class="hint">You review it before it posts.</span>${E.connected?g(!1):""}<button class="btn sm" id="ice-rp">Reply</button></div>`;if(i.wait)return'<div class="ice-form-acts"><span class="hint">Drafting…</span></div>';return`<div class="ice-form"><label for="ice-reply" class="hint" style="margin:0">Your reply${i.note?` · ${p(i.note)}`:""}</label><textarea class="sel" id="ice-reply" rows="3" maxlength="2000" aria-label="Your reply"${i.busy||i.again?" disabled":""}>${p(i.text)}</textarea><span class="hint" id="ice-rn" style="margin:0">${i.again?"Writing another…":`${i.text.length} chars`}</span><div class="ice-form-acts"><span class="end"></span>${E.connected?`${g(i.busy||i.again)}<button class="btn ghost sm" id="ice-rg"${i.busy||i.again?" disabled":""}>Regenerate</button>`:""}<button class="btn ghost sm" id="ice-rc"${i.busy?" disabled":""}>Cancel</button><button class="btn pri sm" id="ice-rs"${i.busy||i.again||!i.text.trim()?" disabled":""}>${i.busy?"Sending…":"Send reply"}</button></div></div>`},ss=(s)=>{let i=w[s],g=R(i),S=se===g;return`<div class="ice-row${le===g?" cur":""}${_.has(g)?" sel":""}" data-i="${s}" data-k="${p(g)}"><input type="checkbox" id="ice-ck-${s}" data-ck="${s}"${_.has(g)?" checked":""} aria-label="Select: ${p(i.text)}"><span class="ice-src" title="${p(W(i))}">${k(i.source,"link",v.find((j)=>j.dir===i.dir&&j.url===i.source)?.social)}</span><button class="ice-t" id="ice-o-${s}" data-o="${s}" aria-expanded="${S}" title="${p(i.text)}">${p(i.text)}</button><span class="ice-meta">${Xn?`<span class="nm" title="${p(i.project||"")}">${Oe(i.dir,i.project||"")}${p(ke(i.project||""))}</span>`:""}${i.status==="done"&&i.written?`<span title="Written back ${p(Te(i.written))}">written back</span>`:""}${i.status==="done"&&i.writeError?`<span title="${p(i.writeError)}">write-back failed</span>`:""}${i.status==="promoted"?`<span title="Promoted to Ideas: ${p(i.idea||"")}">promoted</span>`:""}${i.replied?`<span title="Replied ${p(Te(i.replied))}">replied</span>`:""}<span class="ice-age">${Te(i.at)}</span><span class="ice-ra">${i.status==="promoted"||i.status==="done"?"":`<button class="btn ghost sm" id="ice-pr-${s}" data-pr="${s}" title="Promote to Ideas (p)" aria-label="Promote to Ideas: ${p(i.text)}">${L("plus")}</button>`}${i.status==="promoted"||i.status==="done"?"":i.status==="dismissed"?`<button class="btn ghost sm" id="ice-rs-${s}" data-rs="${s}" title="Restore to waiting" aria-label="Restore: ${p(i.text)}">Restore</button>`:`<button class="btn ghost sm" id="ice-ds-${s}" data-ds="${s}" title="Dismiss (d)" aria-label="Dismiss: ${p(i.text)}">${L("x")}</button>`}</span></span></div>`+(S?`<div class="ice-open">${i.notes?`<div class="ice-notes">${p(i.notes)}</div>`:""}${(i.images||[]).filter((j)=>/^https:\/\//.test(j)).length?`<details class="ice-imgs"><summary>Show images (${i.images.filter((j)=>/^https:\/\//.test(j)).length}) — loads them from the source's site</summary>${i.images.filter((j)=>/^https:\/\//.test(j)).map((j,O)=>{let Y=`/ice-image?dir=${encodeURIComponent(i.dir)}&id=${encodeURIComponent(i.id)}&i=${O}`;return`<a href="${p(Y)}" target="_blank" rel="noopener noreferrer"><img data-src="${p(Y)}" alt="" referrerpolicy="no-referrer"></a>`}).join("")}</details>`:""}<span class="hint" style="margin:0">From ${p(W(i))} · ${Te(i.at)}${i.post?` · <a href="${p(i.post)}" target="_blank" rel="noopener">open post</a>`:""}</span>${ns(i)}${ve===g?ts(i):""}</div>`:"")},os=x&&!U.length?`<div class="ice-zero"><h4>Nothing matches</h4><p>${x} waiting outside these filters.</p><p style="margin-top:10px"><button class="btn sm" id="ice-unf">Clear filters</button></p></div>`:U.length?`<div class="ice-list">${[...Ye].map(([s,i])=>`<div class="ice-cat">${p(s)}<i>${i.length}</i></div>${i.map(ss).join("")}`).join("")}</div>`:'<div class="ice-zero"><h4>Inbox zero</h4><p>Nothing waiting. Gather all checks your sources for new requests; /kaizen discover in your agent sorts what comes in.</p></div>',rt=Ze??!v.length,At=rt&&!t("src-sec")?.classList.contains("open"),as=`<div class="src-scrim${rt&&!At?" on":""}" id="src-scrim"></div><aside class="src-sec${rt&&!At?" open":""}" id="src-sec" aria-label="Sources"${rt?"":" inert"}><header><div class="t"><b>Sources</b><div class="src-chips"><a id="src-to-settings" class="schip" role="button" tabindex="0" title="${f&&f.every&&f.every!=="off"?`Checked every ${p(f.every)}`:"Auto-check off"}: change in Settings">${L("clock")}${f&&f.every&&f.every!=="off"?p(f.every):"Off"}</a>${E.connected?`<span class="schip on" title="ChatGPT${E.email?": "+p(E.email):""}">${L("ai")}ChatGPT<a id="cg-out" role="button" tabindex="0" aria-label="Disconnect ChatGPT" title="Disconnect">${L("x")}</a></span>`:`<a id="cg-in" class="schip" role="button" tabindex="0" title="Connect ChatGPT">${L("ai")}ChatGPT</a>`}${(f.google||{}).connected?`<span class="schip on" title="Google connected">${L("google")}Google<a id="gg-out" role="button" tabindex="0" aria-label="Disconnect Google" title="Disconnect">${L("x")}</a></span>`:`<a id="gg-in" class="schip" role="button" tabindex="0" title="Connect Google">${L("google")}Google</a>`}</div></div><div class="src-hda">${v.length?`<button class="btn sm" id="src-new" aria-expanded="${I}" aria-controls="src-addbox">${L("plus")} Add</button>`:""}<button class="ib" id="src-x" aria-label="Close sources" title="Close">${L("close")}</button></div></header><div class="body">${ie}${v.length?`<div class="src-list">${H}</div>`:Z}</div></aside>`,is=E.use==="ask"&&!E.connected?'<div class="cg-onb"><b>Gather social posts with your ChatGPT plan</b><p>Connect ChatGPT Plus or Pro and Gather reads X, TikTok, Instagram and Threads sources right here, counted against your plan, with no agent to open. Skip it and Gather keeps using your agent. You can change this in Settings.</p><div class="acts"><button class="btn ghost" id="cg-skip">Skip for now</button><button class="btn pri" id="cg-go">Continue with ChatGPT</button></div></div>':"",rs=t("src-sec")?.querySelector(".body")?.scrollTop??0;if(t("pbody").innerHTML=f&&f.error?`<p style="color:var(--dim)">${p(f.error)}</p>`:!f?'<p style="color:var(--dim)">The icebox could not be read.</p>':is+es+sn+F+os+as,!f||f.error)return;let on=t("src-sec")?.querySelector(".body");if(on)on.scrollTop=rs;if(c&&c[0]===se&&t("ice-title"))t("ice-title").value=c[1],t("ice-details").value=c[2];if(t("src-add")){if(n[5])t("src-plat").value=n[5];if(t("src-q").value=n[6]||"",t("src-url").value=n[0],t("src-label").value=n[1],t("src-cat").value=n[4]||"",n[3]!==void 0&&t("src-dir"))t("src-dir").value=n[3]}if(t("src-to-settings"))t("src-to-settings").onclick=()=>{a.setQ="sources",re("settings",!0)};let ls=async()=>{let s=window.open("","_blank"),i=await A("/chatgpt",{op:"connect"});if(i.ok&&s)s.location=i.url;else if(s?.close(),i.ok)location.href=i.url};for(let s of["cg-go","cg-in"])if(t(s))t(s).onclick=ls;if(t("gg-in"))t("gg-in").onclick=async()=>{if(!(f.google||{}).hasClient){q("Add your Google client in Settings first"),a.setQ="google",re("settings",!0);return}let s=window.open("","_blank"),i=await A("/google",{op:"connect"});if(i.ok&&s)s.location=i.url;else if(s?.close(),i.ok)location.href=i.url;else q(i.error||"Could not start Google sign-in")};if(t("gg-out"))t("gg-out").onclick=async()=>{if(!confirm("Disconnect Google? Done requests stop being written to sheets."))return;if((await A("/google",{op:"disconnect"})).ok)q("Google disconnected"),K()};if(t("cg-skip"))t("cg-skip").onclick=async()=>{if((await A("/chatgpt",{op:"skip"})).ok)q("Gather keeps using your agent"),K()};if(t("cg-out"))t("cg-out").onclick=async()=>{if(!confirm("Disconnect ChatGPT? Gather goes back to your agent."))return;if((await A("/chatgpt",{op:"disconnect"})).ok)q("ChatGPT disconnected"),K()};if(At)t("src-sec").offsetWidth,t("src-sec").classList.add("open"),t("src-scrim").classList.add("on");let lt=()=>{Ze=!1,t("src-sec").classList.remove("open"),t("src-scrim").classList.remove("on"),t("src-sec").inert=!0,t("src-tog").setAttribute("aria-expanded","false"),t("src-tog").focus()};if(t("src-x").onclick=t("src-scrim").onclick=lt,t("src-new"))t("src-new").onclick=()=>{let s=t("src-addbox");if(Ie=s.hidden,s.hidden=!Ie,t("src-new").setAttribute("aria-expanded",Ie),Ie)t(He==="social"?"src-q":"src-url").focus()};if(t("src-sec").onkeydown=(s)=>{if(s.key==="Escape")s.stopPropagation(),lt()},t("src-tog").onclick=()=>{if(t("src-sec").classList.contains("open"))return lt();Ze=!0,K().then(()=>Ze&&t("src-url")?.focus())},An("ice-fs",(s)=>{ue=s,K()}),An("ice-fc",(s)=>{ee=s,K()}),t("ice-unf"))t("ice-unf").onclick=()=>{if(ee=ue="",!b)a.shown.clear();bt(),K()};t("pbody").querySelectorAll("[data-kind]").forEach((s)=>s.onclick=()=>{if(He===s.dataset.kind)return;He=s.dataset.kind,K().then(()=>t(He==="social"?"src-q":"src-url")?.focus())});let Ae=async(s,i)=>{let g=await A("/sources",s);if(g.ok)i(g),N();if(B())K()};t("pbody").querySelectorAll("[data-mv]").forEach((s)=>{let i=v[+s.dataset.mv],g=t("src-mb-"+s.dataset.mv),S=o.get(s.dataset.k);if(S&&[...s.options].some((j)=>j.value===S))s.value=S;(s.onchange=()=>{g.disabled=s.value===i.dir})(),g.onclick=()=>Ae({op:"move",dir:i.dir,to:s.value,url:i.url},()=>q(`Source moved. Requests already gathered stay in ${i.project||"the old project"}.`))}),t("pbody").querySelectorAll("[data-wb]").forEach((s)=>{let i=v[+s.dataset.wb];s.onchange=()=>{if(s.checked&&i.social===void 0&&/docs\.google\.com/.test(i.url)&&!(f.google||{}).connected){alert("Connect Google first (Sources header), so kaizen can write to the sheet."),s.checked=!1;return}if(s.checked&&!confirm("When a promoted request's run is done, kaizen will write that back to this source. A closed issue or a changed cell is visible to others. Turn on?")){s.checked=!1;return}Ae({op:"writeback",dir:i.dir,url:i.url,on:s.checked},()=>q(s.checked?"Done requests will be written back":"Write back off"))}}),t("pbody").querySelectorAll("[data-ct]").forEach((s)=>{let i=v[+s.dataset.ct],g=t("src-cs-"+s.dataset.ct),S=m.get(s.dataset.k);if(S!==void 0)s.value=S;(s.oninput=()=>{g.disabled=s.value.trim()===(i.category||"")})(),s.onkeydown=(j)=>{if(j.key==="Enter"&&!g.disabled)g.click()},g.onclick=()=>{g.disabled=!0,Ae({op:"category",dir:i.dir,url:i.url,category:s.value.trim()},()=>q(s.value.trim()?`New requests from ${i.label||u(i.url)} go under ${s.value.trim()}`:"Category cleared"))}});let an=()=>{if(!document.activeElement||document.activeElement===document.body)t("pbody").querySelector(".ice-row.cur .ice-t")?.focus()},rn=(s)=>{let i=U.findIndex((j)=>R(w[j])===le);if(i<0||!s.has(le))return;let g=(j)=>!s.has(R(w[j])),S=U.slice(i+1).find(g)??U.slice(0,i).reverse().find(g);le=S===void 0?null:R(w[S])},Tt=(s,i,g,S={})=>(async()=>{if(rn(new Set([R(i)])),(await A("/icebox",{op:s,dir:i.dir,ids:[i.id],...S})).ok){if(s==="promote"&&se===R(i))se=ve=null;q(g),N()}if(B())K().then(an)})(),ln=async(s)=>{let i=w.filter((S)=>_.has(R(S)));if(!i.length)return;rn(new Set(i.map(R))),mt=!0,t("ice-bp").disabled=t("ice-bd").disabled=!0;let g=0;try{if(s==="dismiss")for(let S of new Set(i.map((j)=>j.dir))){let j=i.filter((O)=>O.dir===S).map((O)=>O.id);if((await A("/icebox",{op:s,dir:S,ids:j})).ok)g+=j.length}else for(let S of i)if((await A("/icebox",{op:s,dir:S.dir,ids:[S.id]})).ok)g++}finally{mt=!1}if(_.clear(),g)q(`${g} ${s==="promote"?"promoted to Ideas":"dismissed"}${g<i.length?`, ${i.length-g} not`:""}`);if(N(),B())K().then(an)};if(sn)t("ice-bp").onclick=()=>ln("promote"),t("ice-bd").onclick=()=>ln("dismiss"),t("ice-bc").onclick=()=>{_.clear(),K()};if(t("ice-all"))t("ice-all").onchange=()=>{let s=U.map((i)=>R(w[i]));if(Qe===U.length)s.forEach((i)=>_.delete(i));else s.forEach((i)=>_.add(i));K()};if(t("ice-all"))t("ice-all").indeterminate=Qe>0&&Qe<U.length;t("pbody").querySelectorAll("[data-ck]").forEach((s)=>s.onchange=()=>{let i=R(w[+s.dataset.ck]);le=i,s.checked?_.add(i):_.delete(i),K()});let cn=(s)=>{let i=R(w[s]);le=i,se=se===i?null:i,ve=null,K().then(()=>t("ice-o-"+s)?.focus())};t("pbody").querySelectorAll(".ice-row").forEach((s)=>s.onclick=(i)=>{if(!i.target.closest("input,button"))cn(+s.dataset.i)}),t("pbody").querySelectorAll("[data-o]").forEach((s)=>s.onclick=()=>cn(+s.dataset.o)),t("pbody").querySelectorAll("details.ice-imgs").forEach((s)=>s.ontoggle=()=>s.open&&s.querySelectorAll("img[data-src]").forEach((i)=>i.src=i.dataset.src)),t("pbody").querySelectorAll("[data-pr]").forEach((s)=>s.onclick=()=>{le=se=ve=R(w[+s.dataset.pr]),K().then(()=>{t("ice-title")?.focus(),t("ice-title")?.select()})}),t("pbody").querySelectorAll("[data-ds]").forEach((s)=>s.onclick=()=>{s.disabled=!0,Tt("dismiss",w[+s.dataset.ds],"Dismissed")}),t("pbody").querySelectorAll("[data-rs]").forEach((s)=>s.onclick=()=>{s.disabled=!0,Tt("restore",w[+s.dataset.rs],"Restored")});let Ve=w.find((s)=>R(s)===se);if(t("ice-rp"))t("ice-rp").onclick=async()=>{let s=Ve,i=R(s);ne={k:i,text:"",wait:!0},K();let g=await A("/reply",{op:"draft",dir:s.dir,id:s.id,style:Kt(s.dir)});if(!ne||ne.k!==i)return;ne={k:i,text:g.text||"",note:g.ok?g.note||"":"",wait:!1},K().then(()=>t("ice-reply")?.focus())};if(t("ice-rst"))t("ice-rst").onchange=(s)=>{let i=Ve.dir;tt[i]=s.target.value;try{localStorage.setItem("kz-reply-style:"+i,tt[i])}catch(g){}};if(t("ice-rg"))t("ice-rg").onclick=async()=>{let s=Ve,i=ne;i.again=!0,K();let g=await A("/reply",{op:"draft",dir:s.dir,id:s.id,style:Kt(s.dir),avoid:i.text});if(ne!==i)return;if(i.again=!1,g.text)i.text=g.text;i.note=g.ok?g.note||"":"",K().then(()=>t("ice-reply")?.focus())};if(t("ice-reply")){let s=t("ice-reply");s.oninput=()=>{ne.text=s.value,t("ice-rn").textContent=`${s.value.length} chars`,t("ice-rs").disabled=!s.value.trim()},s.onkeydown=(i)=>{if(i.key==="Escape")i.stopPropagation(),t("ice-rc").click()},t("ice-rc").onclick=()=>{ne=null,K()},t("ice-rs").onclick=async()=>{let i=Ve,g=ne;g.busy=!0,K();let S=await A("/reply",{op:"send",dir:i.dir,id:i.id,text:g.text});if(S.ok)ne=null,q("Reply posted");else if(S.sent)ne=null;else g.busy=!1;if(N(),B())K()}}if(t("ice-ok")){let s=()=>{let i=t("ice-title").value.trim();if(!i){t("ice-title").focus();return}t("ice-ok").disabled=!0,Tt("promote",Ve,"Promoted to Ideas",{text:i,details:t("ice-details").value})};t("ice-ok").onclick=s,t("ice-cancel").onclick=()=>{ve=null,K()},t("ice-title").onkeydown=(i)=>{if(i.key==="Enter")s();if(i.key==="Escape")t("ice-cancel").click()},t("ice-details").onkeydown=(i)=>{if(i.key==="Enter"&&(i.ctrlKey||i.metaKey))s();if(i.key==="Escape")t("ice-cancel").click()}}gt=(s)=>{if(s.altKey||s.ctrlKey||s.metaKey)return!1;let i=s.key,g=U.findIndex((O)=>R(w[O])===le),S=U[g];if(i==="Escape"){if(t("src-sec")?.classList.contains("open"))return lt(),!0;if(se){if(ve)ve=null;else se=null;return K(),!0}if(_.size)return _.clear(),K(),!0;return!1}if(g<0)return!1;if(i==="j"||i==="k"){s.preventDefault();let O=U[Math.max(0,Math.min(U.length-1,g+(i==="j"?1:-1)))];return le=R(w[O]),t("pbody").querySelectorAll(".ice-row").forEach((Y)=>Y.classList.toggle("cur",+Y.dataset.i===O)),t("pbody").querySelector(`.ice-row[data-i="${O}"]`)?.scrollIntoView({block:"nearest"}),!0}let j=i==="o"?t("ice-o-"+S):i==="x"?t("ice-ck-"+S):i==="p"?_.size?t("ice-bp"):t("ice-pr-"+S):i==="d"?_.size?t("ice-bd"):t("ice-ds-"+S):null;if(!j||j.disabled)return!1;return s.preventDefault(),j.click(),!0},t("pbody").querySelectorAll("[data-chk]").forEach((s)=>{let i=v[+s.dataset.chk];s.onclick=()=>{s.disabled=!0,s.innerHTML=L("refresh")+" Checking…",Ae({op:"gather",dir:i.dir,url:i.url},(g)=>{let S=g.results&&g.results[0];q(S?S.ok?`${i.label||u(i.url)}: ${S.note}${S.added?" in the icebox":""}`:`Failed: ${S.note}`:"Checked")})}}),t("pbody").querySelectorAll("[data-ag]").forEach((s)=>{let i=v[+s.dataset.ag];s.onclick=()=>{if(s.disabled=!0,P)s.innerHTML=L("refresh")+" Gathering…";Ae({op:"agent",dir:i.dir,url:i.url},(g)=>{let S=g.results&&g.results[0];q(g.via==="chatgpt"?S&&S.ok?`${i.label||u(i.url)}: ${S.note}${S.added?" in the icebox":""}`:`Failed: ${S?S.note:"no answer"}`:"/kaizen gather opened in your terminal: "+je(g.agent))})}}),t("pbody").querySelectorAll("[data-rm]").forEach((s)=>{let i=v[+s.dataset.rm];s.onclick=()=>{if(!confirm(`Remove ${i.label||u(i.url)}? What it gathered stays; added again later, it may bring back ones you deleted or dismissed.`))return;Ae({op:"remove",dir:v[+s.dataset.rm].dir,url:v[+s.dataset.rm].url},()=>q("Source removed"))}});let dn=w.findIndex((s)=>R(s)===le);if(l){let s=w.findIndex((i)=>R(i)===l[0]);(s>=0?t(l[1]+"-"+s):dn>=0?t("ice-o-"+dn):null)?.focus()}let ct=!l&&n[2]&&t(n[2]);if(ct)(ct.disabled?t(ct.id.replace("src-mb-","src-mv-").replace("src-cs-","src-ct-")):ct)?.focus();if(t("src-add"))t("src-add").onclick=()=>{let s=He==="social",i=t("src-url").value.trim(),g=t("src-q").value.trim(),S=t("src-plat").value;if(s?!g:!i){t(s?"src-q":"src-url").focus();return}let j=t("src-dir").value;if(!j){t("src-dir").focus();return}t("src-add").disabled=!0,t("src-add").textContent="Adding…",(async()=>{let O=t("src-label").value.trim()||(s?`${et[S]}: ${g}`:""),Y=t("src-cat").value.trim(),he=await A("/sources",s?{op:"add",dir:j,platform:S,query:g,label:O,category:Y}:{op:"add",dir:j,url:i,label:O,category:Y});if(he.ok&&s)t("src-q").value=t("src-label").value=t("src-cat").value="",q("Source added. Click Gather on it to have your agent read its posts."),N();else if(he.ok){t("src-url").value=t("src-label").value=t("src-cat").value="";let un=await A("/sources",{op:"gather",dir:j,url:i}),dt=un.results&&un.results[0];q(dt?dt.ok?`Source added: ${dt.note} in the icebox`:`Source added, but: ${dt.note}`:"Source added"),N()}else q(he.error||"Could not add source");if(he.ok)Ie=!1;if(B())K()})()},t("src-url").onkeydown=t("src-q").onkeydown=t("src-label").onkeydown=t("src-cat").onkeydown=(s)=>{if(s.key==="Enter")t("src-add").click()};if(t("src-go"))t("src-go").onclick=()=>{t("src-go").disabled=!0,t("src-go").innerHTML=L("refresh")+" Gathering…",Ae({op:"gather",dir:b||void 0},(s)=>{let i=s.results||[],g=(Y)=>v.some((he)=>he.url===Y.url&&he.social),S=i.reduce((Y,he)=>Y+he.added,0),j=i.filter((Y)=>!Y.ok&&!g(Y)).length,O=i.filter(g).length;q((S?`${S} new in the icebox`:"Nothing new")+(j?`, ${j} source${j===1?"":"s"} could not be read`:"")+(O?`; ${O} social for /kaizen gather`:""))})}}var Nt=new URLSearchParams(location.search).has("all");function ze(e){if(Nt)return;try{e?localStorage.setItem("kz-proj",e):localStorage.removeItem("kz-proj")}catch(n){}}var ke=(e)=>e.split(/[\\/]/).pop()||e,Oe=(e,n="")=>{let o=`<span class="plogo" aria-hidden="true">${p((ke(n)||"?").charAt(0).toUpperCase())}</span>`;return a.S?.projects?.some((r)=>r.dir===e&&r.logo)?`<img class="plogo" src="/project-logo?dir=${encodeURIComponent(e)}" alt="" loading="lazy" onerror="this.outerHTML=this.dataset.f" data-f="${p(o)}">`:o},vt=(e,n)=>`${Oe(e,n)}<span>${p(ke(n))}</span>`;function wt(){let e=t("projq").value.trim().toLowerCase(),n=0;t("projs").innerHTML=a.PL.map((r,l)=>r[0].toLowerCase().includes(e)?(r[2]&&!n++?'<div class="grp">In your projects folder</div>':"")+`<button role="option" data-i="${l}" title="${p(r[0])}">${r[1]==="all"?`<span class="plogo" aria-hidden="true">${L("board")}</span><span>${p(r[0])}</span>`:r[1]==="__find"?`<span class="plogo" aria-hidden="true">${L("plus")}</span><span>${p(r[0])}</span>`:vt(r[1],r[0])}</button>`:"").join("")||'<div class="grp">No match</div>';let o=t("projs").querySelector("button");if(o)o.classList.add("on")}function yt(e){if(t("projpop").hidden=!e,t("proj").setAttribute("aria-expanded",e),e)t("projq").value="",wt(),t("projq").focus()}async function gs(e){if(e==="__find"){q("Searching…");let n=await A("/find",{});if(n.ok)q(n.added?`Found ${n.added} new project${n.added===1?"":"s"}`:"No new projects found")}else if(e==="all")a.all=!0,ze();else if((await A("/cd",{dir:e})).ok)a.all=!1,a.dir=e,ze(e);if(await N(),Q==="sources")K()}var Me=null;function En(e){let n=bs(),o=e.querySelector("#newdir"),r=e.querySelector("#ndc .menu-pop"),l=r.querySelector("input"),c=e.querySelector("#nds");if(!n.some((f)=>f.dir===Me))Me=n[0]?.dir??null;let m=()=>{let f=n.find((u)=>u.dir===Me);o.innerHTML=f?vt(f.dir,f.label):"Choose project"},b=()=>{let f=l.value.trim().toLowerCase();c.innerHTML=n.filter((u)=>u.label.toLowerCase().includes(f)).map((u)=>`<button type="button" role="option" data-d="${p(u.dir)}" title="${p(u.label)}" class="${u.dir===Me?"on":""}">${vt(u.dir,u.label)}</button>`).join("")||'<div class="grp">No match</div>',c.querySelectorAll("button").forEach((u)=>u.onclick=()=>{Me=u.dataset.d,m(),y(!1),e.querySelector("#newidea").focus()})},y=(f)=>{if(r.hidden=!f,o.setAttribute("aria-expanded",f),f)l.value="",b(),l.focus()};o.onclick=(f)=>{f.preventDefault(),y(r.hidden)},l.oninput=b,l.onkeydown=(f)=>{if(f.key==="Escape")f.stopPropagation(),y(!1);if(f.key==="Enter")f.preventDefault(),c.querySelector("button")?.click()},m()}function bs(){let e=Ee(),n=e.filter((o)=>a.shown.has(o.dir));return n.length?n:e}function Ee(){let e=[...a.S.projects];for(let n of a.S.cards)if(!e.some((o)=>o.dir===n.state))e.push({dir:n.state,label:n.where});return e}var Ge=[];function kt(){let e=t("showq").value.trim().toLowerCase();Ge=Ee(),t("shows").innerHTML=Ge.map((o,r)=>o.label.toLowerCase().includes(e)?`<button type="button" role="menuitemcheckbox" data-i="${r}" aria-checked="${a.shown.has(o.dir)}" title="${p(o.label)}">${vt(o.dir,o.label)}<svg class="ck"><use href="#i-check"/></svg></button>`:"").join("")||'<div class="grp">No match</div>';let n=Ge.filter((o)=>a.shown.has(o.dir)).length;t("showcount").textContent=n?`${n} of ${Ge.length} shown`:`All ${Ge.length} shown`}function bt(){try{localStorage.setItem("kz-show",JSON.stringify([...a.shown]))}catch(e){}if(D(),B())K()}function Mn(){t("proj").onclick=()=>yt(t("projpop").hidden),t("projq").oninput=wt,t("projq").onkeydown=(e)=>{let n=[...t("projs").querySelectorAll("button")],o=n.findIndex((r)=>r.classList.contains("on"));if(e.key==="Escape")yt(!1),t("proj").focus();else if(e.key==="Enter"){if(n[o])n[o].click()}else if((e.key==="ArrowDown"||e.key==="ArrowUp")&&n[o]){e.preventDefault();let r=n[(o+(e.key==="ArrowDown"?1:n.length-1))%n.length];n[o].classList.remove("on"),r.classList.add("on"),r.scrollIntoView({block:"nearest"})}},t("projs").onclick=(e)=>{let n=e.target.closest("button");if(n)yt(!1),gs(a.PL[n.dataset.i][1])},document.addEventListener("click",(e)=>{if(!e.target.closest("#combo"))yt(!1)}),t("show").onclick=()=>{let e=t("showpop").hidden;if(t("showpop").hidden=!e,t("show").setAttribute("aria-expanded",e),e)t("showq").value="",kt(),t("showq").focus()},t("showq").oninput=kt,t("showq").onkeydown=(e)=>{if(e.key==="Escape")t("showpop").hidden=!0,t("show").setAttribute("aria-expanded",!1),t("show").focus()},t("shows").onclick=(e)=>{let n=e.target.closest("button[data-i]"),o=n?.dataset.i,r=Ge[o]?.dir;if(!r)return;a.shown.has(r)?a.shown.delete(r):a.shown.add(r),bt(),t("shows").querySelector(`[data-i="${o}"]`)?.focus()},t("showall").onclick=()=>{a.shown.clear(),bt()},document.addEventListener("click",(e)=>{if(!e.target.closest("#showc"))t("showpop").hidden=!0,t("show").setAttribute("aria-expanded",!1)}),t("newtop").onclick=()=>{a.mode="new",D(),t("board").scrollTo(0,0)}}var Ue=null,Ht=6,$t=new Set,nt=(()=>{try{return JSON.parse(localStorage.getItem("kz-order"))||{}}catch(e){return{}}})(),ae=!1,st=!1,pe=new Set,T=(e)=>e._k||(e.kind==="idea"?"idea:"+e.state+":"+e.text:"run:"+e.state+":"+e.id),ys=(e)=>e.kind==="run"?a.S.cards.filter((n)=>n.parent===e.id&&n.state===e.state):[],vs=(e)=>!!e.parent&&a.S.cards.some((n)=>n.kind==="run"&&n.id===e.parent&&n.state===e.state&&!n.archived&&n.column!==4),It=new Set;function ks(e){let n=T(e),o=document.createElement("div");o.className="card"+(n===a.openKey?" open":"")+(n===a.menuKey?" menu":"")+(e.archived?" archived":""),o.tabIndex=0,o.dataset.k=n,o.setAttribute("role","button");let r=qe[e.status]||"idea",l=e.kind==="idea"?"idea":e.text.replace(/-/g," "),c=e.status==="waiting"?Xe(e.awaiting)||"needs you":e.status||"idea",m=[],b=[],y=(h,d,v="")=>`<span class="mk${v}" role="img" title="${p(d)}" aria-label="${p(d)}"><svg><use href="#${h}"/></svg></span>`;if(a.all){let h=e.where.split("/").filter(Boolean).pop()||e.where;m.push(`<span class="chip proj" title="${p(e.where)}">${p(h)}</span>`)}for(let h of e.tags||[])if(vn[h])b.push(y("c-"+h,h==="full"?"Full run: separate reviewer":h==="lite"?"Lite run: one session":Mt[h]||h,h==="full"||h==="lite"?" accent":""));else if(h==="yolo")b.push('<span class="mk warn" title="Started in Yolo: the agent runs commands without asking">yolo</span>');else m.push(`<span class="chip">${p(Mt[h]||h)}</span>`);if(e.pty&&e.pty.alive)b.push(y("i-term","The agent is running in this card: open it, terminal button at the top"," run"));let f=e.notes&&/^\[[^\]]*\] (?:Gathered from|Promoted from the icebox:) (.+?)\. Outside text/.exec(e.notes);if(e.parent)m.push(`<span class="chip link" title="Filed by ${p(e.parent)}">${p(e.parent.replace(/^\d{4}-\d\d-\d\d-/,"").replace(/-/g," "))}</span>`);if(e.branch)m.push(`<span class="chip" title="Built on its own branch, ${p(e.branch)}">branch</span>`);if(f)m.push(`<span class="chip" title="From ${p(f[1])}">gathered</span>`);else if(e.notes)m.push('<span class="chip">notes</span>');if(e.git)b.push(y("c-git",e.git==="pushed"?"Committed & pushed":"Not committed & pushed yet",e.git==="pushed"?" ok":" warn"));if(e.open)b.push(y("i-bug",`${e.open} open finding${e.open===1?"":"s"} to fix`," warn"));let u=st&&!ae&&e.column===4;o.innerHTML=(u?`<input type="checkbox" class="pick" ${pe.has(n)?"checked":""} aria-label="Select">`:"")+`<div class="c-id"><span class="st d-${r}" role="img" title="${p(c)}" aria-label="${p(c)}"><svg><use href="#s-${r}"/></svg></span><span class="idt">${p(l)}</span><span class="age">${e.kind==="run"?yn(e.moved||a.S.now,a.S.now):""}</span><button class="ib sm more" data-a="menu" title="Actions" aria-label="Actions" aria-haspopup="menu" aria-expanded="${n===a.menuKey}">${L("more")}</button></div>
|
|
23
|
+
<p class="snone" id="snone" hidden></p>`,Pt();let u=[...t("pbody").querySelectorAll(".sg")],w=[...t("pbody").querySelectorAll(".sjump button")],h=()=>{let d=a.setQ.trim().toLowerCase(),v=!1;u.forEach((x,k)=>{let E=!1;for(let P of x.nextElementSibling.children)P.hidden=!!d&&!P.dataset.q.includes(d),E||=!P.hidden;if(x.hidden=!E,w[k])w[k].hidden=!E;v||=E}),t("snone").hidden=v,t("snone").textContent=`No setting matches “${a.setQ.trim()}”.`};if(w.forEach((d,v)=>d.onclick=()=>{let x=t("pbody");x.scrollTo({top:x.scrollTop+u[v].getBoundingClientRect().top-x.querySelector(".sq").getBoundingClientRect().bottom-8,behavior:"smooth"})}),t("sq").oninput=(d)=>{a.setQ=d.target.value,h()},t("sq").onkeydown=(d)=>{if(d.key!=="Escape")return;if(d.preventDefault(),a.setQ)a.setQ="",d.target.value="",h();else X()},h(),m){let d=t("sq");d.focus(),d.setSelectionRange(d.value.length,d.value.length)}if(t("gg-save").onclick=async()=>{let d=await A("/google",{op:"client",id:t("gg-id").value,secret:t("gg-secret").value});if(d.ok)q("Saved");else q(d.error||"Enter both the client id and the secret");be()},t("gg-conn").onclick=async()=>{if(r.connected)return await A("/google",{op:"disconnect"}),be();let d=window.open("","_blank"),v=await A("/google",{op:"connect"});if(v.ok&&d)d.location=v.url;else d?.close(),q(v.error||"Could not start Google sign-in")},t("th").onchange=(d)=>setTheme(d.target.value),t("nt").onclick=()=>{me(),setTimeout(be,300)},o)o.rows.forEach((d)=>{let v=t("s-"+d.key.replace(/\./g,"-")),x=async(k)=>{let E=document.activeElement?.id;if((await A("/settings",{key:d.key,value:k})).ok)q("Saved");if(await be(),E?.startsWith("s-preset-"))t(E)?.focus()};if(v.tagName==="SELECT"||v.tagName==="INPUT")v.onchange=async()=>{if(await x(v.value.trim()),v.tagName==="INPUT")N()};else v.querySelectorAll("[data-v]").forEach((k)=>k.onclick=()=>x(k.dataset.v));if(v.matches(".chk"))v.onclick=()=>x(v.dataset.v);if(d.folder)t(v.id+"-pick").onclick=async()=>{let k=await A("/pick-folder",{});if(k.path)await x(k.path),N()}})}var Q="board",Rt=(e)=>{document.querySelectorAll(".rail a.it").forEach((n)=>n.dataset.view===e?n.setAttribute("aria-current","page"):n.removeAttribute("aria-current")),document.querySelector(".hdr h1").textContent=e==="sources"?"Icebox":"Board"};function re(e,n){if(a.pageGen++,e==="icebox")e="sources";if(!["settings","sources"].includes(e))e="board";let o=Q;if(Q=e,Rt(e),document.body.classList.toggle("paged",e!=="board"),document.body.classList.toggle("ice",e==="sources"),n&&e!==o)history.pushState(null,"",e==="board"?location.pathname:"?view="+(e==="sources"?"icebox":e));let r=t("panel");if(e==="board"){if(o==="board")return;r.style.transition="none",r.classList.remove("open","docked"),r.offsetWidth,r.style.transition="",X();return}if(r.classList.add("docked"),e==="settings")be();else K()}function jn(){document.querySelectorAll(".rail a[data-view]").forEach((e)=>{if(e.dataset.view==="notes")return;e.onclick=(n)=>{if(n.ctrlKey||n.metaKey||n.shiftKey||n.button)return;if(n.preventDefault(),e.dataset.view==="settings")a.setQ="";re(e.dataset.view,!0)}}),document.querySelector(".rail .home").onclick=(e)=>{if(e.ctrlKey||e.metaKey||e.shiftKey||e.button)return;e.preventDefault(),re("board",!0)},addEventListener("popstate",()=>re(new URLSearchParams(location.search).get("view"))),addEventListener("kz-theme",()=>{if(Q==="settings")be()})}var Tn="Icebox",ne=null,tt={},Kt=(e)=>{if(!tt[e])try{tt[e]=localStorage.getItem("kz-reply-style:"+e)}catch(n){}return tt[e]||"casual"},ms={casual:"Casual",fun:"Fun",helpful:"Helpful",promo:"Promo"},se=null,ve=null,le=null,ue="",ee="",He="link",Ze=null,Ie=!1,mt=!1,gt=null,_=new Set,et={x:"X",tiktok:"TikTok",instagram:"Instagram",threads:"Threads"},B=()=>t("panel").classList.contains("open")&&t("ptitle").textContent===Tn,Te=(e)=>{let n=Math.round((Date.now()-Date.parse(e))/60000);return n<1?"just now":n<60?n+" min ago":n<1440?Math.round(n/60)+" h ago":Math.round(n/1440)+" d ago"};function An(e,n){let o=t(e);if(!o)return;let r=o.querySelector("button.sel"),l=o.querySelector(".menu-pop"),c=l.querySelector("input"),m=l.querySelector(".pick-list"),b=(f)=>{if(l.hidden=!f,r.setAttribute("aria-expanded",f),f)c.value="",y(),c.focus()},y=()=>{let f=c.value.trim().toLowerCase(),u=null,w=!1;for(let h of m.children)if(h.classList.contains("grp")){if(u)u.hidden=!w;u=h,w=!1}else{let d=!f||h.dataset.q.includes(f)||u&&u.dataset.g.includes(f);h.hidden=!d,w||=d}if(u)u.hidden=!w};r.onclick=()=>b(l.hidden),c.oninput=y,c.onkeydown=(f)=>{if(f.key==="Escape")f.stopPropagation(),b(!1),r.focus();else if(f.key==="Enter")f.preventDefault(),m.querySelector("button:not([hidden])")?.click()},m.onclick=(f)=>{let u=f.target.closest("button[data-v]");if(!u)return;b(!1),n(u.dataset.v)}}async function K(){if(Q!=="sources")return;let e=++a.pageGen;de(!0);let n=B()?[t("src-url")?.value||"",t("src-label")?.value||"",document.activeElement?.id,t("src-dir")?.value,t("src-cat")?.value||"",t("src-plat")?.value,t("src-q")?.value||""]:["",""],o=new Map(B()?[...t("pbody").querySelectorAll("[data-mv]")].map((s)=>[s.dataset.k,s.value]):[]),r=B()&&document.activeElement?.closest?.(".ice-row"),l=r?[r.dataset.k,document.activeElement.id.replace(/-\d+$/,"")]:null,c=B()&&t("ice-title")?[t("ice-title").dataset.k,t("ice-title").value,t("ice-details").value]:null,m=new Map(B()?[...t("pbody").querySelectorAll("[data-ct]")].map((s)=>[s.dataset.k,s.value]):[]);if(!B())t("pbody").innerHTML='<p class="snone">Loading…</p>';ye(),a.openKey=null,a.runDoc=null,t("pterm").hidden=!0,t("panel").classList.add("open"),t("scrim").classList.add("on"),t("ptitle").textContent=Tn,t("ptabs").innerHTML="",t("pfoot").innerHTML="",t("psub").textContent="",t("psub").title="";let b=a.all?null:a.S.dir,y=b?"?dir="+encodeURIComponent(b):"",f=await fetch("/sources"+y).then((s)=>s.json()).catch(()=>null);if(e!==a.pageGen||!B())return;let u=(s)=>{try{return new URL(s).hostname}catch{return s}},w=(s,i,g)=>{if(et[g])return`<span class="fav">${L("b-"+g)}</span>`;let S;try{let O=new URL(s);if(/^https?:$/.test(O.protocol))S=O.origin}catch{}let j=!S?"":s.includes("docs.google.com/spreadsheets")?"https://ssl.gstatic.com/docs/spreadsheets/favicon3.ico":s.includes("docs.google.com/document")?"https://ssl.gstatic.com/docs/documents/images/kix-favicon7.ico":S+"/favicon.ico";return`<span class="fav">${j?`<img src="${p(j)}" alt="" loading="lazy" referrerpolicy="no-referrer" onerror="this.remove()">`:""}${L(i)}</span>`},h=(s)=>et[s?.social]||(s?u(s.url).replace(/^www\./,""):"")||"Other",d=(s)=>{if(s.social)return"social";let i=s.url;if(i.includes("docs.google.com/spreadsheets")||i.endsWith(".csv")||i.endsWith(".tsv"))return"sheet";if(i.includes("docs.google.com/document"))return"doc";return"link"},v=f&&f.sources||[],x=[...f&&f.icebox||[],...f&&f.dismissed||[]],k=(f&&f.icebox||[]).length,E=f&&f.gatherer||{},P=E.use==="chatgpt"&&E.connected,C=(s,i)=>(s&&!a.S.projects.some((g)=>g.dir===s)?`<option value="${p(s)}" title="${p(i||s)}" selected>${p(ke(i||s))}</option>`:"")+a.S.projects.map((g)=>`<option value="${p(g.dir)}" title="${p(g.label)}"${g.dir===s?" selected":""}>${p(ke(g.label))}</option>`).join(""),H=v.map((s,i)=>{let g=d(s),S=s.label||u(s.url),j=s.social&&(!s.last||!s.last.ok&&/gathered by your agent/i.test(s.last.note))?`<span class="src-badge dim">${L("at")}${P?"Gathered with ChatGPT":"Gathered by your agent"}</span>`:!s.last?'<span class="src-badge dim">Not gathered yet</span>':s.last.ok?`<span class="src-badge ok"><svg viewBox="0 0 16 16"><path d="M3.5 8.5l3 3 6-6" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>${p(s.last.note)}</span><span class="hint" style="margin:0">${Te(s.last.at)}</span>`:`<span class="src-badge warn"><svg viewBox="0 0 16 16"><circle cx="8" cy="8" r="6" stroke="currentColor" fill="none" stroke-width="1.8"/><path d="M8 5v3.5M8 11v.1" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round"/></svg>Failed: ${p(s.last.note)}</span><span class="hint" style="margin:0">${Te(s.last.at)}</span>`,O=g==="sheet"?"sheet":g==="doc"?"doc":g==="social"?"at":"link",Y=s.url.replace(/^https?:\/\/(www\.)?/,"");return`<div class="src-card" id="src-card-${i}"><div class="src-hd"><span class="src-ico" title="${g==="social"?p(et[s.social]||"social"):g}">${w(s.url,O,s.social)}</span><div class="src-info"><span class="src-title" title="${p(S)}">${p(S)}</span><a class="src-url" href="${p(s.url)}" target="_blank" rel="noopener noreferrer" title="${p(s.url)}"><span>${p(Y)}</span>${L("ext")}</a></div><div class="src-acts">${s.social?`<button class="ib sm" id="src-ag-${i}" data-ag="${i}" aria-label="Gather ${p(S)} with your agent" title="${P?"Gather with your ChatGPT plan":"Gather: opens your agent on /kaizen gather"}">${L("refresh")}</button>`:`<button class="ib sm" id="src-chk-${i}" data-chk="${i}" aria-label="Check ${p(S)} now" title="Check now">${L("refresh")}</button>`}<button class="ib sm danger" id="src-rm-${i}" data-rm="${i}" aria-label="Remove ${p(S)}" title="Remove source">${L("trash")}</button></div></div><div class="src-status">${j}</div><div class="src-fields"><label class="src-f" title="Category"><span>Category</span><input class="sel" id="src-ct-${i}" data-ct="${i}" data-k="${p(s.dir+" "+s.url)}" value="${p(s.category||"")}" placeholder="${p(h(s))}" maxlength="40" aria-label="Category of ${p(S)}"><button class="btn sm pri" id="src-cs-${i}" aria-label="Save the category of ${p(S)}" disabled>Save</button></label><label class="src-f" title="Project"><span>Project</span>${Oe(s.dir,s.project||"")}<select class="sel" id="src-mv-${i}" data-mv="${i}" data-k="${p(s.dir+" "+s.url)}" aria-label="Project of ${p(S)}">${C(s.dir,s.project)}</select><button class="btn sm pri" id="src-mb-${i}" aria-label="Move ${p(S)} to the chosen project" disabled>Move</button></label>${g==="sheet"||/github\.com/.test(s.url)?`<label class="src-wb"><input type="checkbox" id="src-wb-${i}" data-wb="${i}"${s.writeback?" checked":""}> ${g==="sheet"?"Mark the row Done":"Comment and close the issue"} when its run is done</label>`:""}</div></div>`}).join(""),F=(f&&f.failed||[]).map((s)=>`<p style="color:var(--dim)">The sources of ${p(s)} could not be read.</p>`).join(""),Z=`<div class="src-empty"><div class="src-empty-ico">${L("link")}</div><h4>No request sources yet</h4><p>Connect shared links to gather requests into the icebox, where you choose which become ideas. Nothing runs automatically until you start it.</p><div class="src-empty-grid"><div class="src-empty-item"><strong>Google Sheets</strong>Public sheets with idea or task rows. Rows marked Done are skipped.</div><div class="src-empty-item"><strong>Google Docs</strong>Shared public docs with items or notes written line by line.</div><div class="src-empty-item"><strong>Public links</strong>TSV, CSV, or raw text links containing requests.</div><div class="src-empty-item"><strong>Social media</strong>A keyword, #hashtag or @account on X, TikTok, Instagram or Threads, read by your agent.</div></div></div>`,z=He==="social",I=!v.length||Ie||[n[0],n[1],n[4],n[6]].some(Boolean),ie=`<div class="src-add" id="src-addbox"${I?"":" hidden"}><div class="hd"><h4>Add a source</h4><div class="src-kind" role="group" aria-label="Kind of source"><button id="src-k-link" data-kind="link" aria-pressed="${!z}">Link</button><button id="src-k-social" data-kind="social" aria-pressed="${z}">Social</button></div></div><label for="src-url"${z?" hidden":""}><span>Link</span><input class="sel" id="src-url" placeholder="https://docs.google.com/spreadsheets/d/… or any public link" spellcheck="false" autocomplete="off"></label><div class="two plat"${z?"":" hidden"}><label for="src-plat"><span>Platform</span><select class="sel" id="src-plat">${Object.entries(et).map(([s,i])=>`<option value="${s}">${i}</option>`).join("")}</select></label><label for="src-q"><span>Follow</span><input class="sel" id="src-q" placeholder="keyword, #hashtag or @account" maxlength="100" spellcheck="false" autocomplete="off"></label></div><div class="two"><label for="src-label"><span>Label <i>optional</i></span><input class="sel" id="src-label" placeholder="${z?"e.g. Mentions on X":"e.g. Feedback Q4"}"></label><label for="src-cat"><span>Category <i>optional</i></span><input class="sel" id="src-cat" placeholder="${z?"e.g. Social":"e.g. Bugs"}" maxlength="40"></label></div><label for="src-dir"><span>Project</span><select class="sel" id="src-dir">${b?"":'<option value="">Choose project…</option>'}${C(b)}</select></label><div class="acts"><span class="hint">${z?P?"Your ChatGPT plan reads public posts when you press Gather on the source.":"Kaizen signs in nowhere. Your agent reads public posts when you press Gather on the source.":"Public links only. For private docs, run /kaizen gather."}</span><button class="btn pri" id="src-add">Add source</button></div></div>`,W=(s)=>{let i=v.find((g)=>g.dir===s.dir&&g.url===s.source);return i?i.label||u(i.url):u(s.source)},R=(s)=>s.dir+" "+s.id,xe=(s)=>s.dir+" "+s.source,M=(s)=>v.find((i)=>i.dir===s.dir&&i.url===s.source),G=(s)=>s.status==="dismissed"?"Dismissed":s.status==="done"?"Done":s.category||M(s)?.category||h(M(s)||{url:s.source}),J=(s,i)=>{let g=new Map;return s.forEach((S)=>{let j=i(S);g.set(j,{it:S,n:(g.get(j)?.n||0)+1})}),g},oe=b?new Map:J(x,(s)=>s.dir);if(!b){for(let s of a.shown)if(!oe.has(s)){let i=Ee().find((g)=>g.dir===s);if(i)oe.set(s,{it:{dir:s,project:i.label},n:0})}}let fe=(s)=>b||!a.shown.size||a.shown.has(s.dir),tn=x.filter(fe),_e=J(tn,G);if(ee&&!_e.has(ee))ee="";let Vn=tn.filter((s)=>!ee||G(s)===ee),Be=J(Vn,xe);if(ue&&!Be.has(ue))ue="";let Jn=new Set(x.map(R));if(se&&!Jn.has(se))se=null;if(ve!==se)ve=null;let Ye=new Map;x.forEach((s,i)=>{if(!fe(s)||(ee?G(s)!==ee:s.status==="dismissed"||s.status==="done")||ue&&xe(s)!==ue)return;let g=G(s);Ye.has(g)||Ye.set(g,[]),Ye.get(g).push(i)});let U=[...Ye.values()].flat();{let s=new Set(U.map((i)=>R(x[i])));[..._].forEach((i)=>s.has(i)||_.delete(i))}if(!U.some((s)=>R(x[s])===le))le=U.length?R(x[U[0]]):null;let Qe=U.filter((s)=>_.has(R(x[s]))).length,Xn=oe.size>1,nn=(s,i,g,S)=>`<div class="combo ice-combo" id="${s}"><button class="sel" aria-label="${i}" aria-haspopup="listbox" aria-expanded="false">${g}</button><div class="menu-pop" hidden><input class="sel" aria-label="Search ${i.toLowerCase()}" placeholder="Search…" spellcheck="false" autocomplete="off"><div class="pick-list" role="listbox">${S}</div></div></div>`,at=(s,i,g,S,j="")=>`<button type="button" role="option" data-v="${p(s)}" data-q="${p(S.toLowerCase())}" class="${i?"on":""}${j}" aria-selected="${i}">${g}</button>`,it=new Map;for(let[s,{it:i}]of Be){let g=h(M(i)||{url:i.source});it.has(g)||it.set(g,[]),it.get(g).push([s,i])}let Ct=(s)=>w(s.source,"link",M(s)?.social),jt=ue&&[...Be].find(([s])=>s===ue)?.[1].it,Zn=(_e.size>1||_e.has("Dismissed")||_e.has("Done")?nn("ice-fc","Category",`<span>${p(ee||"All categories")}</span>`,at("",!ee,"<span>All categories</span>","all categories")+[..._e].map(([s,{n:i}])=>at(s,ee===s,`<span>${p(s)}</span><i>${i}</i>`,s)).join("")):"")+(Be.size>1?nn("ice-fs","Source",jt?`${Ct(jt)}<span>${p(W(jt))}</span>`:"<span>All sources</span>",at("",!ue,"<span>All sources</span>","all sources")+[...it].map(([s,i])=>`<div class="grp" data-g="${p(s.toLowerCase())}">${Ct(i[0][1])}${p(s)}</div>`+i.map(([g,S])=>at(g,ue===g,`${Ct(S)}<span>${p(W(S).toLowerCase().startsWith(s.toLowerCase()+":")?W(S).slice(s.length+1).trim():W(S))}</span><i>${Be.get(g).n}</i>`,s+" "+W(S)," sub")).join("")).join("")):""),es=`<div class="ice-bar">${U.length?`<input type="checkbox" id="ice-all" aria-label="Select every request shown"${Qe&&Qe===U.length?" checked":""}>`:""}<span class="count">${ee==="Dismissed"||ee==="Done"?`${U.length} ${ee.toLowerCase()}`:`${U.length<k?`${U.length} of ${k}`:k} waiting`}</span>${Zn}<span class="end"></span><button class="btn sm" id="src-go"${v.length?"":" disabled"}>${L("refresh")} Gather all</button><button class="btn ghost sm" id="src-tog" aria-expanded="${Ze??!v.length}" aria-controls="src-sec">${L("link")} Sources · ${v.length}</button></div>`,sn=_.size?`<div class="ice-bulk" role="toolbar" aria-label="Selected requests"><span>${_.size} selected</span><span class="end"></span><button class="btn sm" id="ice-bp" title="Each becomes its own idea, titled as gathered (p)">${L("plus")} Promote</button><button class="btn ghost sm" id="ice-bd" title="Dismiss (d)">${L("x")} Dismiss</button><button class="btn ghost sm" id="ice-bc" title="Clear selection (Esc)">Clear</button></div>`:"",ts=(s)=>`<div class="ice-form"><label for="ice-title" class="hint" style="margin:0">Idea title</label><input class="sel" id="ice-title" data-k="${p(R(s))}" maxlength="200" value="${p(s.text)}" aria-label="Idea title"><label for="ice-details" class="hint" style="margin:0">Details</label><textarea class="sel" id="ice-details" rows="3" placeholder="What, for whom, why" aria-label="Details"></textarea><div class="ice-form-acts"><span class="hint">Source goes into the notes.</span><button class="btn ghost sm" id="ice-cancel">Cancel</button><button class="btn pri sm" id="ice-ok">Promote</button></div></div>`,ns=(s)=>{if(!s.post)return"";if(s.replied)return`<span class="hint" style="margin:0">You replied to this post ${Te(s.replied)}.</span>`;let i=ne&&ne.k===R(s)?ne:null,g=(S)=>`<select class="sel" id="ice-rst" aria-label="Reply style" title="Promo names kaizen; Helpful may mention it when it fits; Casual and Fun never do"${S?" disabled":""}>${Object.entries(ms).map(([j,O])=>`<option value="${j}"${j===Kt(s.dir)?" selected":""}>${O}</option>`).join("")}</select>`;if(!i)return`<div class="ice-form-acts"><span class="hint">You review it before it posts.</span>${E.connected?g(!1):""}<button class="btn sm" id="ice-rp">Reply</button></div>`;if(i.wait)return'<div class="ice-form-acts"><span class="hint">Drafting…</span></div>';return`<div class="ice-form"><label for="ice-reply" class="hint" style="margin:0">Your reply${i.note?` · ${p(i.note)}`:""}</label><textarea class="sel" id="ice-reply" rows="3" maxlength="2000" aria-label="Your reply"${i.busy||i.again?" disabled":""}>${p(i.text)}</textarea><span class="hint" id="ice-rn" style="margin:0">${i.again?"Writing another…":`${i.text.length} chars`}</span><div class="ice-form-acts"><span class="end"></span>${E.connected?`${g(i.busy||i.again)}<button class="btn ghost sm" id="ice-rg"${i.busy||i.again?" disabled":""}>Regenerate</button>`:""}<button class="btn ghost sm" id="ice-rc"${i.busy?" disabled":""}>Cancel</button><button class="btn pri sm" id="ice-rs"${i.busy||i.again||!i.text.trim()?" disabled":""}>${i.busy?"Sending…":"Send reply"}</button></div></div>`},ss=(s)=>{let i=x[s],g=R(i),S=se===g;return`<div class="ice-row${le===g?" cur":""}${_.has(g)?" sel":""}" data-i="${s}" data-k="${p(g)}"><input type="checkbox" id="ice-ck-${s}" data-ck="${s}"${_.has(g)?" checked":""} aria-label="Select: ${p(i.text)}"><span class="ice-src" title="${p(W(i))}">${w(i.source,"link",v.find((j)=>j.dir===i.dir&&j.url===i.source)?.social)}</span><button class="ice-t" id="ice-o-${s}" data-o="${s}" aria-expanded="${S}" title="${p(i.text)}">${p(i.text)}</button><span class="ice-meta">${Xn?`<span class="nm" title="${p(i.project||"")}">${Oe(i.dir,i.project||"")}${p(ke(i.project||""))}</span>`:""}${i.status==="done"&&i.written?`<span title="Written back ${p(Te(i.written))}">written back</span>`:""}${i.status==="done"&&i.writeError?`<span title="${p(i.writeError)}">write-back failed</span>`:""}${i.status==="promoted"?`<span title="Promoted to Ideas: ${p(i.idea||"")}">promoted</span>`:""}${i.replied?`<span title="Replied ${p(Te(i.replied))}">replied</span>`:""}<span class="ice-age">${Te(i.at)}</span><span class="ice-ra">${i.status==="promoted"||i.status==="done"?"":`<button class="btn ghost sm" id="ice-pr-${s}" data-pr="${s}" title="Promote to Ideas (p)" aria-label="Promote to Ideas: ${p(i.text)}">${L("plus")}</button>`}${i.status==="promoted"||i.status==="done"?"":i.status==="dismissed"?`<button class="btn ghost sm" id="ice-rs-${s}" data-rs="${s}" title="Restore to waiting" aria-label="Restore: ${p(i.text)}">Restore</button>`:`<button class="btn ghost sm" id="ice-ds-${s}" data-ds="${s}" title="Dismiss (d)" aria-label="Dismiss: ${p(i.text)}">${L("x")}</button>`}</span></span></div>`+(S?`<div class="ice-open">${i.notes?`<div class="ice-notes">${p(i.notes)}</div>`:""}${(i.images||[]).filter((j)=>/^https:\/\//.test(j)).length?`<details class="ice-imgs"><summary>Show images (${i.images.filter((j)=>/^https:\/\//.test(j)).length}) — loads them from the source's site</summary>${i.images.filter((j)=>/^https:\/\//.test(j)).map((j,O)=>{let Y=`/ice-image?dir=${encodeURIComponent(i.dir)}&id=${encodeURIComponent(i.id)}&i=${O}`;return`<a href="${p(Y)}" target="_blank" rel="noopener noreferrer"><img data-src="${p(Y)}" alt="" referrerpolicy="no-referrer"></a>`}).join("")}</details>`:""}<span class="hint" style="margin:0">From ${p(W(i))} · ${Te(i.at)}${i.post?` · <a href="${p(i.post)}" target="_blank" rel="noopener">open post</a>`:""}</span>${ns(i)}${ve===g?ts(i):""}</div>`:"")},os=k&&!U.length?`<div class="ice-zero"><h4>Nothing matches</h4><p>${k} waiting outside these filters.</p><p style="margin-top:10px"><button class="btn sm" id="ice-unf">Clear filters</button></p></div>`:U.length?`<div class="ice-list">${[...Ye].map(([s,i])=>`<div class="ice-cat">${p(s)}<i>${i.length}</i></div>${i.map(ss).join("")}`).join("")}</div>`:'<div class="ice-zero"><h4>Inbox zero</h4><p>Nothing waiting. Gather all checks your sources for new requests; /kaizen discover in your agent sorts what comes in.</p></div>',rt=Ze??!v.length,At=rt&&!t("src-sec")?.classList.contains("open"),as=`<div class="src-scrim${rt&&!At?" on":""}" id="src-scrim"></div><aside class="src-sec${rt&&!At?" open":""}" id="src-sec" aria-label="Sources"${rt?"":" inert"}><header><div class="t"><b>Sources</b><div class="src-chips"><a id="src-to-settings" class="schip" role="button" tabindex="0" title="${f&&f.every&&f.every!=="off"?`Checked every ${p(f.every)}`:"Auto-check off"}: change in Settings">${L("clock")}${f&&f.every&&f.every!=="off"?p(f.every):"Off"}</a>${E.connected?`<span class="schip on" title="ChatGPT${E.email?": "+p(E.email):""}">${L("ai")}ChatGPT<a id="cg-out" role="button" tabindex="0" aria-label="Disconnect ChatGPT" title="Disconnect">${L("x")}</a></span>`:`<a id="cg-in" class="schip" role="button" tabindex="0" title="Connect ChatGPT">${L("ai")}ChatGPT</a>`}${(f.google||{}).connected?`<span class="schip on" title="Google connected">${L("google")}Google<a id="gg-out" role="button" tabindex="0" aria-label="Disconnect Google" title="Disconnect">${L("x")}</a></span>`:`<a id="gg-in" class="schip" role="button" tabindex="0" title="Connect Google">${L("google")}Google</a>`}</div></div><div class="src-hda">${v.length?`<button class="btn sm" id="src-new" aria-expanded="${I}" aria-controls="src-addbox">${L("plus")} Add</button>`:""}<button class="ib" id="src-x" aria-label="Close sources" title="Close">${L("close")}</button></div></header><div class="body">${ie}${v.length?`<div class="src-list">${H}</div>`:Z}</div></aside>`,is=E.use==="ask"&&!E.connected?'<div class="cg-onb"><b>Gather social posts with your ChatGPT plan</b><p>Connect ChatGPT Plus or Pro and Gather reads X, TikTok, Instagram and Threads sources right here, counted against your plan, with no agent to open. Skip it and Gather keeps using your agent. You can change this in Settings.</p><div class="acts"><button class="btn ghost" id="cg-skip">Skip for now</button><button class="btn pri" id="cg-go">Continue with ChatGPT</button></div></div>':"",rs=t("src-sec")?.querySelector(".body")?.scrollTop??0;if(t("pbody").innerHTML=f&&f.error?`<p style="color:var(--dim)">${p(f.error)}</p>`:!f?'<p style="color:var(--dim)">The icebox could not be read.</p>':is+es+sn+F+os+as,!f||f.error)return;let on=t("src-sec")?.querySelector(".body");if(on)on.scrollTop=rs;if(c&&c[0]===se&&t("ice-title"))t("ice-title").value=c[1],t("ice-details").value=c[2];if(t("src-add")){if(n[5])t("src-plat").value=n[5];if(t("src-q").value=n[6]||"",t("src-url").value=n[0],t("src-label").value=n[1],t("src-cat").value=n[4]||"",n[3]!==void 0&&t("src-dir"))t("src-dir").value=n[3]}if(t("src-to-settings"))t("src-to-settings").onclick=()=>{a.setQ="sources",re("settings",!0)};let ls=async()=>{let s=window.open("","_blank"),i=await A("/chatgpt",{op:"connect"});if(i.ok&&s)s.location=i.url;else if(s?.close(),i.ok)location.href=i.url};for(let s of["cg-go","cg-in"])if(t(s))t(s).onclick=ls;if(t("gg-in"))t("gg-in").onclick=async()=>{if(!(f.google||{}).hasClient){q("Add your Google client in Settings first"),a.setQ="google",re("settings",!0);return}let s=window.open("","_blank"),i=await A("/google",{op:"connect"});if(i.ok&&s)s.location=i.url;else if(s?.close(),i.ok)location.href=i.url;else q(i.error||"Could not start Google sign-in")};if(t("gg-out"))t("gg-out").onclick=async()=>{if(!confirm("Disconnect Google? Done requests stop being written to sheets."))return;if((await A("/google",{op:"disconnect"})).ok)q("Google disconnected"),K()};if(t("cg-skip"))t("cg-skip").onclick=async()=>{if((await A("/chatgpt",{op:"skip"})).ok)q("Gather keeps using your agent"),K()};if(t("cg-out"))t("cg-out").onclick=async()=>{if(!confirm("Disconnect ChatGPT? Gather goes back to your agent."))return;if((await A("/chatgpt",{op:"disconnect"})).ok)q("ChatGPT disconnected"),K()};if(At)t("src-sec").offsetWidth,t("src-sec").classList.add("open"),t("src-scrim").classList.add("on");let lt=()=>{Ze=!1,t("src-sec").classList.remove("open"),t("src-scrim").classList.remove("on"),t("src-sec").inert=!0,t("src-tog").setAttribute("aria-expanded","false"),t("src-tog").focus()};if(t("src-x").onclick=t("src-scrim").onclick=lt,t("src-new"))t("src-new").onclick=()=>{let s=t("src-addbox");if(Ie=s.hidden,s.hidden=!Ie,t("src-new").setAttribute("aria-expanded",Ie),Ie)t(He==="social"?"src-q":"src-url").focus()};if(t("src-sec").onkeydown=(s)=>{if(s.key==="Escape")s.stopPropagation(),lt()},t("src-tog").onclick=()=>{if(t("src-sec").classList.contains("open"))return lt();Ze=!0,K().then(()=>Ze&&t("src-url")?.focus())},An("ice-fs",(s)=>{ue=s,K()}),An("ice-fc",(s)=>{ee=s,K()}),t("ice-unf"))t("ice-unf").onclick=()=>{if(ee=ue="",!b)a.shown.clear();bt(),K()};t("pbody").querySelectorAll("[data-kind]").forEach((s)=>s.onclick=()=>{if(He===s.dataset.kind)return;He=s.dataset.kind,K().then(()=>t(He==="social"?"src-q":"src-url")?.focus())});let Ae=async(s,i)=>{let g=await A("/sources",s);if(g.ok)i(g),N();if(B())K()};t("pbody").querySelectorAll("[data-mv]").forEach((s)=>{let i=v[+s.dataset.mv],g=t("src-mb-"+s.dataset.mv),S=o.get(s.dataset.k);if(S&&[...s.options].some((j)=>j.value===S))s.value=S;(s.onchange=()=>{g.disabled=s.value===i.dir})(),g.onclick=()=>Ae({op:"move",dir:i.dir,to:s.value,url:i.url},()=>q(`Source moved. Requests already gathered stay in ${i.project||"the old project"}.`))}),t("pbody").querySelectorAll("[data-wb]").forEach((s)=>{let i=v[+s.dataset.wb];s.onchange=()=>{if(s.checked&&i.social===void 0&&/docs\.google\.com/.test(i.url)&&!(f.google||{}).connected){alert("Connect Google first (Sources header), so kaizen can write to the sheet."),s.checked=!1;return}if(s.checked&&!confirm("When a promoted request's run is done, kaizen will write that back to this source. A closed issue or a changed cell is visible to others. Turn on?")){s.checked=!1;return}Ae({op:"writeback",dir:i.dir,url:i.url,on:s.checked},()=>q(s.checked?"Done requests will be written back":"Write back off"))}}),t("pbody").querySelectorAll("[data-ct]").forEach((s)=>{let i=v[+s.dataset.ct],g=t("src-cs-"+s.dataset.ct),S=m.get(s.dataset.k);if(S!==void 0)s.value=S;(s.oninput=()=>{g.disabled=s.value.trim()===(i.category||"")})(),s.onkeydown=(j)=>{if(j.key==="Enter"&&!g.disabled)g.click()},g.onclick=()=>{g.disabled=!0,Ae({op:"category",dir:i.dir,url:i.url,category:s.value.trim()},()=>q(s.value.trim()?`New requests from ${i.label||u(i.url)} go under ${s.value.trim()}`:"Category cleared"))}});let an=()=>{if(!document.activeElement||document.activeElement===document.body)t("pbody").querySelector(".ice-row.cur .ice-t")?.focus()},rn=(s)=>{let i=U.findIndex((j)=>R(x[j])===le);if(i<0||!s.has(le))return;let g=(j)=>!s.has(R(x[j])),S=U.slice(i+1).find(g)??U.slice(0,i).reverse().find(g);le=S===void 0?null:R(x[S])},Tt=(s,i,g,S={})=>(async()=>{if(rn(new Set([R(i)])),(await A("/icebox",{op:s,dir:i.dir,ids:[i.id],...S})).ok){if(s==="promote"&&se===R(i))se=ve=null;q(g),N()}if(B())K().then(an)})(),ln=async(s)=>{let i=x.filter((S)=>_.has(R(S)));if(!i.length)return;rn(new Set(i.map(R))),mt=!0,t("ice-bp").disabled=t("ice-bd").disabled=!0;let g=0;try{if(s==="dismiss")for(let S of new Set(i.map((j)=>j.dir))){let j=i.filter((O)=>O.dir===S).map((O)=>O.id);if((await A("/icebox",{op:s,dir:S,ids:j})).ok)g+=j.length}else for(let S of i)if((await A("/icebox",{op:s,dir:S.dir,ids:[S.id]})).ok)g++}finally{mt=!1}if(_.clear(),g)q(`${g} ${s==="promote"?"promoted to Ideas":"dismissed"}${g<i.length?`, ${i.length-g} not`:""}`);if(N(),B())K().then(an)};if(sn)t("ice-bp").onclick=()=>ln("promote"),t("ice-bd").onclick=()=>ln("dismiss"),t("ice-bc").onclick=()=>{_.clear(),K()};if(t("ice-all"))t("ice-all").onchange=()=>{let s=U.map((i)=>R(x[i]));if(Qe===U.length)s.forEach((i)=>_.delete(i));else s.forEach((i)=>_.add(i));K()};if(t("ice-all"))t("ice-all").indeterminate=Qe>0&&Qe<U.length;t("pbody").querySelectorAll("[data-ck]").forEach((s)=>s.onchange=()=>{let i=R(x[+s.dataset.ck]);le=i,s.checked?_.add(i):_.delete(i),K()});let cn=(s)=>{let i=R(x[s]);le=i,se=se===i?null:i,ve=null,K().then(()=>t("ice-o-"+s)?.focus())};t("pbody").querySelectorAll(".ice-row").forEach((s)=>s.onclick=(i)=>{if(!i.target.closest("input,button"))cn(+s.dataset.i)}),t("pbody").querySelectorAll("[data-o]").forEach((s)=>s.onclick=()=>cn(+s.dataset.o)),t("pbody").querySelectorAll("details.ice-imgs").forEach((s)=>s.ontoggle=()=>s.open&&s.querySelectorAll("img[data-src]").forEach((i)=>i.src=i.dataset.src)),t("pbody").querySelectorAll("[data-pr]").forEach((s)=>s.onclick=()=>{le=se=ve=R(x[+s.dataset.pr]),K().then(()=>{t("ice-title")?.focus(),t("ice-title")?.select()})}),t("pbody").querySelectorAll("[data-ds]").forEach((s)=>s.onclick=()=>{s.disabled=!0,Tt("dismiss",x[+s.dataset.ds],"Dismissed")}),t("pbody").querySelectorAll("[data-rs]").forEach((s)=>s.onclick=()=>{s.disabled=!0,Tt("restore",x[+s.dataset.rs],"Restored")});let Ve=x.find((s)=>R(s)===se);if(t("ice-rp"))t("ice-rp").onclick=async()=>{let s=Ve,i=R(s);ne={k:i,text:"",wait:!0},K();let g=await A("/reply",{op:"draft",dir:s.dir,id:s.id,style:Kt(s.dir)});if(!ne||ne.k!==i)return;ne={k:i,text:g.text||"",note:g.ok?g.note||"":"",wait:!1},K().then(()=>t("ice-reply")?.focus())};if(t("ice-rst"))t("ice-rst").onchange=(s)=>{let i=Ve.dir;tt[i]=s.target.value;try{localStorage.setItem("kz-reply-style:"+i,tt[i])}catch(g){}};if(t("ice-rg"))t("ice-rg").onclick=async()=>{let s=Ve,i=ne;i.again=!0,K();let g=await A("/reply",{op:"draft",dir:s.dir,id:s.id,style:Kt(s.dir),avoid:i.text});if(ne!==i)return;if(i.again=!1,g.text)i.text=g.text;i.note=g.ok?g.note||"":"",K().then(()=>t("ice-reply")?.focus())};if(t("ice-reply")){let s=t("ice-reply");s.oninput=()=>{ne.text=s.value,t("ice-rn").textContent=`${s.value.length} chars`,t("ice-rs").disabled=!s.value.trim()},s.onkeydown=(i)=>{if(i.key==="Escape")i.stopPropagation(),t("ice-rc").click()},t("ice-rc").onclick=()=>{ne=null,K()},t("ice-rs").onclick=async()=>{let i=Ve,g=ne;g.busy=!0,K();let S=await A("/reply",{op:"send",dir:i.dir,id:i.id,text:g.text});if(S.ok)ne=null,q("Reply posted");else if(S.sent)ne=null;else g.busy=!1;if(N(),B())K()}}if(t("ice-ok")){let s=()=>{let i=t("ice-title").value.trim();if(!i){t("ice-title").focus();return}t("ice-ok").disabled=!0,Tt("promote",Ve,"Promoted to Ideas",{text:i,details:t("ice-details").value})};t("ice-ok").onclick=s,t("ice-cancel").onclick=()=>{ve=null,K()},t("ice-title").onkeydown=(i)=>{if(i.key==="Enter")s();if(i.key==="Escape")t("ice-cancel").click()},t("ice-details").onkeydown=(i)=>{if(i.key==="Enter"&&(i.ctrlKey||i.metaKey))s();if(i.key==="Escape")t("ice-cancel").click()}}gt=(s)=>{if(s.altKey||s.ctrlKey||s.metaKey)return!1;let i=s.key,g=U.findIndex((O)=>R(x[O])===le),S=U[g];if(i==="Escape"){if(t("src-sec")?.classList.contains("open"))return lt(),!0;if(se){if(ve)ve=null;else se=null;return K(),!0}if(_.size)return _.clear(),K(),!0;return!1}if(g<0)return!1;if(i==="j"||i==="k"){s.preventDefault();let O=U[Math.max(0,Math.min(U.length-1,g+(i==="j"?1:-1)))];return le=R(x[O]),t("pbody").querySelectorAll(".ice-row").forEach((Y)=>Y.classList.toggle("cur",+Y.dataset.i===O)),t("pbody").querySelector(`.ice-row[data-i="${O}"]`)?.scrollIntoView({block:"nearest"}),!0}let j=i==="o"?t("ice-o-"+S):i==="x"?t("ice-ck-"+S):i==="p"?_.size?t("ice-bp"):t("ice-pr-"+S):i==="d"?_.size?t("ice-bd"):t("ice-ds-"+S):null;if(!j||j.disabled)return!1;return s.preventDefault(),j.click(),!0},t("pbody").querySelectorAll("[data-chk]").forEach((s)=>{let i=v[+s.dataset.chk];s.onclick=()=>{s.disabled=!0,s.innerHTML=L("refresh")+" Checking…",Ae({op:"gather",dir:i.dir,url:i.url},(g)=>{let S=g.results&&g.results[0];q(S?S.ok?`${i.label||u(i.url)}: ${S.note}${S.added?" in the icebox":""}`:`Failed: ${S.note}`:"Checked")})}}),t("pbody").querySelectorAll("[data-ag]").forEach((s)=>{let i=v[+s.dataset.ag];s.onclick=()=>{if(s.disabled=!0,P)s.innerHTML=L("refresh")+" Gathering…";Ae({op:"agent",dir:i.dir,url:i.url},(g)=>{let S=g.results&&g.results[0];q(g.via==="chatgpt"?S&&S.ok?`${i.label||u(i.url)}: ${S.note}${S.added?" in the icebox":""}`:`Failed: ${S?S.note:"no answer"}`:"/kaizen gather opened in your terminal: "+je(g.agent))})}}),t("pbody").querySelectorAll("[data-rm]").forEach((s)=>{let i=v[+s.dataset.rm];s.onclick=()=>{if(!confirm(`Remove ${i.label||u(i.url)}? What it gathered stays; added again later, it may bring back ones you deleted or dismissed.`))return;Ae({op:"remove",dir:v[+s.dataset.rm].dir,url:v[+s.dataset.rm].url},()=>q("Source removed"))}});let dn=x.findIndex((s)=>R(s)===le);if(l){let s=x.findIndex((i)=>R(i)===l[0]);(s>=0?t(l[1]+"-"+s):dn>=0?t("ice-o-"+dn):null)?.focus()}let ct=!l&&n[2]&&t(n[2]);if(ct)(ct.disabled?t(ct.id.replace("src-mb-","src-mv-").replace("src-cs-","src-ct-")):ct)?.focus();if(t("src-add"))t("src-add").onclick=()=>{let s=He==="social",i=t("src-url").value.trim(),g=t("src-q").value.trim(),S=t("src-plat").value;if(s?!g:!i){t(s?"src-q":"src-url").focus();return}let j=t("src-dir").value;if(!j){t("src-dir").focus();return}t("src-add").disabled=!0,t("src-add").textContent="Adding…",(async()=>{let O=t("src-label").value.trim()||(s?`${et[S]}: ${g}`:""),Y=t("src-cat").value.trim(),he=await A("/sources",s?{op:"add",dir:j,platform:S,query:g,label:O,category:Y}:{op:"add",dir:j,url:i,label:O,category:Y});if(he.ok&&s)t("src-q").value=t("src-label").value=t("src-cat").value="",q("Source added. Click Gather on it to have your agent read its posts."),N();else if(he.ok){t("src-url").value=t("src-label").value=t("src-cat").value="";let un=await A("/sources",{op:"gather",dir:j,url:i}),dt=un.results&&un.results[0];q(dt?dt.ok?`Source added: ${dt.note} in the icebox`:`Source added, but: ${dt.note}`:"Source added"),N()}else q(he.error||"Could not add source");if(he.ok)Ie=!1;if(B())K()})()},t("src-url").onkeydown=t("src-q").onkeydown=t("src-label").onkeydown=t("src-cat").onkeydown=(s)=>{if(s.key==="Enter")t("src-add").click()};if(t("src-go"))t("src-go").onclick=()=>{t("src-go").disabled=!0,t("src-go").innerHTML=L("refresh")+" Gathering…",Ae({op:"gather",dir:b||void 0},(s)=>{let i=s.results||[],g=(Y)=>v.some((he)=>he.url===Y.url&&he.social),S=i.reduce((Y,he)=>Y+he.added,0),j=i.filter((Y)=>!Y.ok&&!g(Y)).length,O=i.filter(g).length;q((S?`${S} new in the icebox`:"Nothing new")+(j?`, ${j} source${j===1?"":"s"} could not be read`:"")+(O?`; ${O} social for /kaizen gather`:""))})}}var Nt=new URLSearchParams(location.search).has("all");function ze(e){if(Nt)return;try{e?localStorage.setItem("kz-proj",e):localStorage.removeItem("kz-proj")}catch(n){}}var ke=(e)=>e.split(/[\\/]/).pop()||e,Oe=(e,n="")=>{let o=`<span class="plogo" aria-hidden="true">${p((ke(n)||"?").charAt(0).toUpperCase())}</span>`;return a.S?.projects?.some((r)=>r.dir===e&&r.logo)?`<img class="plogo" src="/project-logo?dir=${encodeURIComponent(e)}" alt="" loading="lazy" onerror="this.outerHTML=this.dataset.f" data-f="${p(o)}">`:o},vt=(e,n)=>`${Oe(e,n)}<span>${p(ke(n))}</span>`;function wt(){let e=t("projq").value.trim().toLowerCase(),n=0;t("projs").innerHTML=a.PL.map((r,l)=>r[0].toLowerCase().includes(e)?(r[2]&&!n++?'<div class="grp">In your projects folder</div>':"")+`<button role="option" data-i="${l}" title="${p(r[0])}">${r[1]==="all"?`<span class="plogo" aria-hidden="true">${L("board")}</span><span>${p(r[0])}</span>`:r[1]==="__find"?`<span class="plogo" aria-hidden="true">${L("plus")}</span><span>${p(r[0])}</span>`:vt(r[1],r[0])}</button>`:"").join("")||'<div class="grp">No match</div>';let o=t("projs").querySelector("button");if(o)o.classList.add("on")}function yt(e){if(t("projpop").hidden=!e,t("proj").setAttribute("aria-expanded",e),e)t("projq").value="",wt(),t("projq").focus()}async function gs(e){if(e==="__find"){q("Searching…");let n=await A("/find",{});if(n.ok)q(n.added?`Found ${n.added} new project${n.added===1?"":"s"}`:"No new projects found")}else if(e==="all")a.all=!0,ze();else if((await A("/cd",{dir:e})).ok)a.all=!1,a.dir=e,ze(e);if(await N(),Q==="sources")K()}var Me=null;function En(e){let n=bs(),o=e.querySelector("#newdir"),r=e.querySelector("#ndc .menu-pop"),l=r.querySelector("input"),c=e.querySelector("#nds");if(!n.some((f)=>f.dir===Me))Me=n[0]?.dir??null;let m=()=>{let f=n.find((u)=>u.dir===Me);o.innerHTML=f?vt(f.dir,f.label):"Choose project"},b=()=>{let f=l.value.trim().toLowerCase();c.innerHTML=n.filter((u)=>u.label.toLowerCase().includes(f)).map((u)=>`<button type="button" role="option" data-d="${p(u.dir)}" title="${p(u.label)}" class="${u.dir===Me?"on":""}">${vt(u.dir,u.label)}</button>`).join("")||'<div class="grp">No match</div>',c.querySelectorAll("button").forEach((u)=>u.onclick=()=>{Me=u.dataset.d,m(),y(!1),e.querySelector("#newidea").focus()})},y=(f)=>{if(r.hidden=!f,o.setAttribute("aria-expanded",f),f)l.value="",b(),l.focus()};o.onclick=(f)=>{f.preventDefault(),y(r.hidden)},l.oninput=b,l.onkeydown=(f)=>{if(f.key==="Escape")f.stopPropagation(),y(!1);if(f.key==="Enter")f.preventDefault(),c.querySelector("button")?.click()},m()}function bs(){let e=Ee(),n=e.filter((o)=>a.shown.has(o.dir));return n.length?n:e}function Ee(){let e=[...a.S.projects];for(let n of a.S.cards)if(!e.some((o)=>o.dir===n.state))e.push({dir:n.state,label:n.where});return e}var Ge=[];function kt(){let e=t("showq").value.trim().toLowerCase();Ge=Ee(),t("shows").innerHTML=Ge.map((o,r)=>o.label.toLowerCase().includes(e)?`<button type="button" role="menuitemcheckbox" data-i="${r}" aria-checked="${a.shown.has(o.dir)}" title="${p(o.label)}">${vt(o.dir,o.label)}<svg class="ck"><use href="#i-check"/></svg></button>`:"").join("")||'<div class="grp">No match</div>';let n=Ge.filter((o)=>a.shown.has(o.dir)).length;t("showcount").textContent=n?`${n} of ${Ge.length} shown`:`All ${Ge.length} shown`}function bt(){try{localStorage.setItem("kz-show",JSON.stringify([...a.shown]))}catch(e){}if(D(),B())K()}function Mn(){t("proj").onclick=()=>yt(t("projpop").hidden),t("projq").oninput=wt,t("projq").onkeydown=(e)=>{let n=[...t("projs").querySelectorAll("button")],o=n.findIndex((r)=>r.classList.contains("on"));if(e.key==="Escape")yt(!1),t("proj").focus();else if(e.key==="Enter"){if(n[o])n[o].click()}else if((e.key==="ArrowDown"||e.key==="ArrowUp")&&n[o]){e.preventDefault();let r=n[(o+(e.key==="ArrowDown"?1:n.length-1))%n.length];n[o].classList.remove("on"),r.classList.add("on"),r.scrollIntoView({block:"nearest"})}},t("projs").onclick=(e)=>{let n=e.target.closest("button");if(n)yt(!1),gs(a.PL[n.dataset.i][1])},document.addEventListener("click",(e)=>{if(!e.target.closest("#combo"))yt(!1)}),t("show").onclick=()=>{let e=t("showpop").hidden;if(t("showpop").hidden=!e,t("show").setAttribute("aria-expanded",e),e)t("showq").value="",kt(),t("showq").focus()},t("showq").oninput=kt,t("showq").onkeydown=(e)=>{if(e.key==="Escape")t("showpop").hidden=!0,t("show").setAttribute("aria-expanded",!1),t("show").focus()},t("shows").onclick=(e)=>{let n=e.target.closest("button[data-i]"),o=n?.dataset.i,r=Ge[o]?.dir;if(!r)return;a.shown.has(r)?a.shown.delete(r):a.shown.add(r),bt(),t("shows").querySelector(`[data-i="${o}"]`)?.focus()},t("showall").onclick=()=>{a.shown.clear(),bt()},document.addEventListener("click",(e)=>{if(!e.target.closest("#showc"))t("showpop").hidden=!0,t("show").setAttribute("aria-expanded",!1)}),t("newtop").onclick=()=>{a.mode="new",D(),t("board").scrollTo(0,0)}}var Ue=null,Ht=6,$t=new Set,nt=(()=>{try{return JSON.parse(localStorage.getItem("kz-order"))||{}}catch(e){return{}}})(),ae=!1,st=!1,pe=new Set,T=(e)=>e._k||(e.kind==="idea"?"idea:"+e.state+":"+e.text:"run:"+e.state+":"+e.id),ys=(e)=>e.kind==="run"?a.S.cards.filter((n)=>n.parent===e.id&&n.state===e.state):[],vs=(e)=>!!e.parent&&a.S.cards.some((n)=>n.kind==="run"&&n.id===e.parent&&n.state===e.state&&!n.archived&&n.column!==4),It=new Set;function ks(e){let n=T(e),o=document.createElement("div");o.className="card"+(n===a.openKey?" open":"")+(n===a.menuKey?" menu":"")+(e.archived?" archived":""),o.tabIndex=0,o.dataset.k=n,o.setAttribute("role","button");let r=qe[e.status]||"idea",l=e.kind==="idea"?"idea":e.text.replace(/-/g," "),c=e.status==="waiting"?Xe(e.awaiting)||"needs you":e.status||"idea",m=[],b=[],y=(h,d,v="")=>`<span class="mk${v}" role="img" title="${p(d)}" aria-label="${p(d)}"><svg><use href="#${h}"/></svg></span>`;if(a.all){let h=e.where.split("/").filter(Boolean).pop()||e.where;m.push(`<span class="chip proj" title="${p(e.where)}">${p(h)}</span>`)}for(let h of e.tags||[])if(vn[h])b.push(y("c-"+h,h==="full"?"Full run: separate reviewer":h==="lite"?"Lite run: one session":Mt[h]||h,h==="full"||h==="lite"?" accent":""));else if(h==="yolo")b.push('<span class="mk warn" title="Started in Yolo: the agent runs commands without asking">yolo</span>');else m.push(`<span class="chip">${p(Mt[h]||h)}</span>`);if(e.pty&&e.pty.alive)b.push(y("i-term","The agent is running in this card: open it, terminal button at the top"," run"));let f=e.notes&&/^\[[^\]]*\] (?:Gathered from|Promoted from the icebox:) (.+?)\. Outside text/.exec(e.notes);if(e.parent)m.push(`<span class="chip link" title="Filed by ${p(e.parent)}">${p(e.parent.replace(/^\d{4}-\d\d-\d\d-/,"").replace(/-/g," "))}</span>`);if(e.branch)m.push(`<span class="chip" title="Built on its own branch, ${p(e.branch)}">branch</span>`);if(f)m.push(`<span class="chip" title="From ${p(f[1])}">gathered</span>`);else if(e.notes)m.push('<span class="chip">notes</span>');if(e.git)b.push(y("c-git",e.git==="pushed"?"Committed & pushed":"Not committed & pushed yet",e.git==="pushed"?" ok":" warn"));if(e.open)b.push(y("i-bug",`${e.open} open finding${e.open===1?"":"s"} to fix`," warn"));let u=st&&!ae&&e.column===4;o.innerHTML=(u?`<input type="checkbox" class="pick" ${pe.has(n)?"checked":""} aria-label="Select">`:"")+`<div class="c-id"><span class="st d-${r}" role="img" title="${p(c)}" aria-label="${p(c)}"><svg><use href="#s-${r}"/></svg></span><span class="idt">${p(l)}</span><span class="age">${e.kind==="run"?yn(e.moved||a.S.now,a.S.now):""}</span><button class="ib sm more" data-a="menu" title="Actions" aria-label="Actions" aria-haspopup="menu" aria-expanded="${n===a.menuKey}">${L("more")}</button></div>
|
|
24
24
|
<div class="c-title"${e.kind==="idea"&&Le(e.text).parts.length?` title="${p(e.text)}"`:""}>${p(e.kind==="idea"?Le(e.text).head:e.title||e.id)}</div>
|
|
25
|
-
${m.length||b.length?`<div class="c-foot"><span class="c-chips">${m.join("")}</span><span class="c-marks">${b.join("")}</span></div>`:""}`;let k=ys(e);if(k.length){let h=It.has(n),d=k.filter((x)=>x.status==="done").length,v=k.some((x)=>x.status==="waiting"),w=document.createElement("div");w.className="kids",w.innerHTML=`<button class="kids-t" aria-expanded="${h}">${h?"▾":"▸"} ${k.length} ${k.length===1?"child":"children"}${d?` · ${d} done`:""}${v?'<span class="kids-w" title="A child is waiting on you"></span>':""}</button>`+(h?`<ul>${k.map((x,E)=>{let P=qe[x.status]||"idea";return`<li data-i="${E}" tabindex="0" role="button" title="Open"><span class="st d-${P}"><svg><use href="#s-${P}"/></svg></span><span class="kids-s">${p(Ke[x.status]||x.status)}</span><span class="kids-n">${p(x.kind==="idea"?Le(x.text).head:x.title||x.id)}</span></li>`}).join("")}</ul>`:""),w.querySelector(".kids-t").onclick=(x)=>{x.stopPropagation(),h?It.delete(n):It.add(n),D()},w.querySelectorAll("li").forEach((x)=>{let E=(P)=>{P.stopPropagation(),we(k[+x.dataset.i])};x.onclick=E,x.onkeydown=(P)=>(P.key==="Enter"||P.key===" ")&&(P.preventDefault(),E(P))}),o.appendChild(w)}if(n===a.menuKey){let h=[];if(ae){if(h.push(["restore","Restore",L("restore")]),h.push(["open","Open",L("open")]),e.kind==="idea")h.push(null,["delete","Delete",L("trash"),"danger"])}else if(e.kind==="idea"&&e.status==="idea")h.push(["run","Run",L("play")],["edit","Edit",L("pen")],...a.S.projects.some((d)=>d.dir!==e.state)?[["move","Move to project",L("ext")]]:[],["archive","Archive",L("archive")],null,["reject","Reject",L("x")],["delete","Delete",L("trash"),"danger"]);else if(e.kind==="idea")h.push(["open","Open",L("open")],["run","Run again",L("play")],["archive","Archive",L("archive")],null,["delete","Remove",L("trash"),"danger"]);else{let d=e.status==="waiting"?e.awaiting==="approvals.plan"?"Review plan":e.awaiting==="findings"?"See findings":e.awaiting==="approvals.review"?"Review work":"Open":"Open";if(h.push(["open",d,L("open")]),h.push(["archive","Archive",L("archive")]),e.status==="waiting"||e.status==="running"||e.status==="stalled")h.push(null,["reject","Abandon",L("x"),"danger"])}o.innerHTML+=`<div class="menu-pop" role="menu">${h.map((d)=>d?`<button role="menuitem" data-a="${d[0]}" class="${d[3]||""}">${d[2]}${d[1]}</button>`:"<hr>").join("")}</div>`}if(o.querySelectorAll("[data-a]").forEach((h)=>h.onclick=async(d)=>{d.stopPropagation();let v=h.dataset.a,w=a.menuKey;if(a.menuKey=null,v==="menu"){a.menuKey=w===n?null:n,D();return}if(v==="open"){we(e);return}if(v==="archive"||v==="restore"){xt(e,v==="archive");return}a.mode=v,a.modeKey=n,D()}),u)return o.classList.toggle("picked",pe.has(n)),o.onclick=()=>{pe.has(n)?pe.delete(n):pe.add(n),D()},o;return o.onclick=()=>{if(a.menuKey){a.menuKey=null,D();return}we(e)},o.onkeydown=(h)=>{if(h.key==="Enter"||h.key===" ")h.preventDefault(),we(e)},o}function ws(e){let n=document.createElement("li");n.className="inl";let o=e.state;if(a.mode==="edit")n.innerHTML=`<input value="${p(e.text)}"><textarea placeholder="Notes: links, paths, background">${p(e.notes||"")}</textarea><div class="q dim" id="clong" hidden>The first line is over 72 characters; logs will cut it short.</div><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Save</button></div>`,n.querySelector("[data-a=s]").onclick=async()=>{let l=n.querySelector("input").value.trim(),c=n.querySelector("textarea").value.trim();if(a.mode=null,l&&(l!==e.text||c!==(e.notes||"")))await A("/inbox",{op:"edit",dir:o,text:e.text,next:l,notes:c});N()};if(a.mode==="reject"){let l=e.kind==="idea";n.innerHTML=`<div class="q">${l?"Reject this idea?":"Abandon this run?"} The reason is recorded.</div><input placeholder="Reason"><div class="row"><button class="btn ghost" data-a="c">Keep it</button><button class="btn warn" data-a="s">${l?"Reject":"Abandon"}</button></div>`,n.querySelector("[data-a=s]").onclick=async()=>{let c=n.querySelector("input").value.trim();if(!c){q("A reason is required");return}if(a.mode=null,(l?await A("/inbox",{op:"reject",dir:o,text:e.text,reason:c}):await A("/abort",{dir:o,id:e.id,why:c})).ok)q(l?"Rejected":"Abandoned, reason recorded");N()}}if(a.mode==="delete")n.innerHTML=`<div class="q">Delete "${p(e.text)}"?</div><div class="q dim">Not recorded anywhere.</div><div class="row"><button class="btn ghost" data-a="c">Keep it</button><button class="btn danger" data-a="s">Delete</button></div>`,n.querySelector("[data-a=s]").onclick=async()=>{if(a.mode=null,(await A("/inbox",{op:"delete",dir:o,text:e.text})).ok)q("Deleted");N()};if(a.mode==="move"){let l=a.S.projects.filter((c)=>c.dir!==o);n.innerHTML=`<div class="q">Move "${p(e.text)}" to</div><select class="sel" aria-label="Project">${l.map((c)=>`<option value="${p(c.dir)}" title="${p(c.label)}">${p(ke(c.label))}</option>`).join("")}</select><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Move</button></div>`,n.querySelector("[data-a=s]").onclick=async()=>{let c=n.querySelector("select"),m=c.value,b=c.selectedOptions[0]?.textContent;if(a.mode=null,(await A("/inbox",{op:"move",dir:o,to:m,text:e.text})).ok)q(`Moved to ${b}`);N()}}if(a.mode==="run")n.innerHTML=`<div class="q">Run "${p(e.text)}"</div><div class="q dim">Opens your agent with this request in a terminal.</div><div class="q dim">Full plans, builds, and checks with a separate reviewer. Lite does it in one session.</div><div id="run-pick" style="display:grid;gap:6px"><div id="run-agent" class="agents" role="radiogroup" aria-label="Agent"></div></div><div class="run-actions"><label class="yolo" id="run-yolo" title="Yolo: the agent runs every command and edit without asking" hidden><button type="button" class="chk" role="switch" aria-checked="false"></button>Yolo</label><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn" data-a="l">Lite</button><button class="btn pri" data-a="s">Full run</button></div></div>`,n.querySelector("[data-a=s]").onclick=()=>Dn(e,"full"),n.querySelector("[data-a=l]").onclick=()=>Dn(e,"lite"),$s(n,e);if(n.querySelector("[data-a=c]").onclick=()=>{a.mode=null,D()},n.querySelectorAll("input,textarea").forEach((l,c)=>Re(l,"f:"+a.mode+":"+a.modeKey+":"+c)),a.mode!=="run"){let l=Je([...n.querySelectorAll("input,textarea")]);if(l)n.querySelector(".row").prepend(l)}let r=n.querySelector("input");if(r)r.onkeydown=(l)=>{if(l.key==="Enter")n.querySelector("[data-a=s]").click();if(l.key==="Escape")a.mode=null,D()},setTimeout(()=>r.focus());return n}var te=null,je=(e)=>e.name+(e.note?". "+e.note:"");async function $s(e,n){let o=T(n),r=e.querySelector("#run-agent"),l=await fetch("/agents?dir="+encodeURIComponent(n.state)).then((y)=>y.json()).catch(()=>null);if(!l||!l.agents||!l.agents.length){e.querySelector("#run-pick").hidden=!0;return}if(l.agents.some((y)=>y.cmd==="agy"))l.agents=l.agents.filter((y)=>y.cmd!=="gemini");if(!te||te.k!==o)te={k:o,agent:l.agents.some((y)=>y.cmd===l.default)?l.default:l.agents[0].cmd};r.innerHTML=l.agents.map((y)=>`<button type="button" class="agent" role="radio" aria-checked="${y.cmd===te.agent}" data-cmd="${p(y.cmd)}"><svg><use href="#c-${p(y.cmd)}"/></svg>${p(y.name)}</button>`).join("");let c=e.querySelector("#run-yolo"),m=c.querySelector(".chk"),b=()=>{let y=!!l.agents.find((u)=>u.cmd===te.agent)?.yolo,f=!!te.yolo;c.hidden=!y,m.classList.toggle("on",f),m.setAttribute("aria-checked",f)};r.querySelectorAll(".agent").forEach((y)=>y.onclick=()=>{te.agent=y.dataset.cmd,r.querySelectorAll(".agent").forEach((f)=>f.setAttribute("aria-checked",f===y)),b()}),m.onclick=()=>{te.yolo=!te.yolo,b()},b(),r.onkeydown=m.onkeydown=(y)=>{if(y.key==="Escape")a.mode=null,D()}}async function Dn(e,n){a.mode=null,me();let o=te&&te.k===T(e)?te:null;te=null;let r=!!(o&&o.yolo&&!t("run-yolo")?.hidden),l=await A("/run",{dir:e.state,text:e.text,kind:n,agent:o?o.agent:void 0,yolo:r});if(l.ok){if(q((n==="full"?"Full":"Lite")+(r?" yolo":"")+" run "+De(l)+": "+je(l.agent)),l.pty)a.wantTerm=T(e)}N()}function D(){if(a.mode&&a.mode!=="new"&&!a.S.cards.some((u)=>T(u)===a.modeKey))a.mode=null;if(a.mode!=="run"&&te)te.yolo=!1;if(!a.mode)gn("f:");try{sessionStorage.setItem("kz-form",JSON.stringify(["new","edit","reject"].includes(a.mode)?[a.mode,a.modeKey]:null))}catch(u){}let e=Ot(),n=a.all&&a.shown.size&&Ee().some((u)=>a.shown.has(u.dir)),o=a.S.cards.filter((u)=>u.archived===ae&&(!n||a.shown.has(u.state))),r=t("board");r.innerHTML="";let l=a.S.cards.filter((u)=>u.archived).length,c=(u)=>o.filter((k)=>k.status===u).length,m=[];for(let u of["waiting","running","stalled","starting","idea","done","abandoned"]){let k=c(u);if(!k&&u!=="running")continue;let h=u==="running"?'<span class="d-run"><span class="pulse"></span></span>':`<span class="st d-${qe[u]}"><svg><use href="#s-${qe[u]}"/></svg></span>`;m.push(`<button class="${u==="waiting"?"attn ":""}${Ue===u?"on":""}" data-f="${u}" title="Show only ${Ke[u]}">${h}${k} ${Ke[u]}${u==="idea"&&k!==1?"s":""}</button>`)}m.push(`<button class="${ae?"on":""}" data-f="__archive" title="${o.filter((u)=>u.kind==="idea").length} ideas · ${o.filter((u)=>u.kind==="run").length} runs on the board">${ae?"Back to board":"Archive "+l}</button>`),t("strip").innerHTML=m.join(""),t("strip").querySelectorAll("button").forEach((u)=>u.onclick=()=>{if(u.dataset.f==="__archive"){ae=!ae,Ue=null,D();return}Ue=Ue===u.dataset.f?null:u.dataset.f,D()}),a.PL=[["All projects","all"],...a.S.projects.map((u)=>[u.label,u.dir]),...a.S.fresh.map((u)=>[u.label,u.dir,1]),["Find projects on this PC…","__find"]];let b=a.all||!a.S.dir?"All projects":(a.PL.find((u)=>u[1]===a.S.dir)||["All projects"])[0];if(t("proj").innerHTML=a.all||!a.S.dir?`<span class="plogo" aria-hidden="true">${L("board")}</span><span>${p(b)}</span>`:`${Oe(a.S.dir,b)}<span>${p(ke(b))}</span>`,t("proj").title=b,!t("projpop").hidden)wt();t("showc").hidden=!a.all;let y=Ee(),f=y.filter((u)=>a.shown.has(u.dir)).length;if(t("show").textContent=f?`Showing ${f} of ${y.length}`:"Showing all",!t("showpop").hidden)kt();if(!o.length&&a.mode!=="new"){if(r.innerHTML=ae?'<div class="empty"><div class="mascot"></div><b>Nothing archived</b></div>':'<div class="empty"><div class="mascot"></div><b>Nothing here yet</b><p>Add an idea and kaizen will plan it, build it, and check the result.</p><button class="btn pri" id="newempty">New idea</button></div>',!ae)t("newempty").onclick=()=>{a.mode="new",D()}}if(kn.forEach((u,k)=>{let h=document.createElement("section"),d=o.filter((C)=>C.column===k&&(!Ue||C.status===Ue)&&(ae||!vs(C)));if(k<4){let C=(F)=>F.kind==="run"?F.moved||0:a.S.cards.indexOf(F),H=nt[k]==="old";d.sort((F,Z)=>H?C(F)-C(Z):C(Z)-C(F))}let v=k===4&&!ae&&d.length,w=(C)=>d.filter((H)=>a.S.now-(H.moved||a.S.now)>C*bn),x=`<h2>${u}<i>${d.length||""}</i>${k<4&&d.length>1?`<button class="btn sm ghost ord" title="Sort order">${nt[k]==="old"?"Oldest":"Newest"}</button>`:""}${k===0&&!ae?`<button class="ib sm" title="New idea" aria-label="New idea">${L("plus")}</button>`:""}`;if(v&&st)x+=`<span class="pickbar"><button class="btn sm ghost" data-p="cancel">Cancel</button><button class="btn sm pri" data-p="go" ${pe.size?"":"disabled"}>Archive ${pe.size||""}</button></span>`;else if(v)x+='<button class="btn sm ghost clear" title="Archive finished runs">Clear ▾</button>';if(v&&a.clearMenu)x+=`<div class="menu-pop clear-pop"><button data-c="7">Older than a week <i>${w(7).length}</i></button><button data-c="3">Older than 3 days <i>${w(3).length}</i></button><button data-c="0">Everything <i>${d.length}</i></button><hr><button data-c="pick">Pick which…</button></div>`;h.innerHTML=x+"</h2>";{let C=h.querySelector(".ord");if(C)C.onclick=(H)=>{H.stopPropagation(),nt[k]=nt[k]==="old"?"new":"old";try{localStorage.setItem("kz-order",JSON.stringify(nt))}catch(F){}D()}}if(k===0&&!ae)h.querySelector(".ib").onclick=()=>{a.mode="new",D()};if(v){let C=h.querySelector(".clear");if(C)C.onclick=(H)=>{H.stopPropagation(),a.clearMenu=!a.clearMenu,D()};h.querySelectorAll("[data-c]").forEach((H)=>H.onclick=async(F)=>{F.stopPropagation(),a.clearMenu=!1;let Z=H.dataset.c;if(Z==="pick"){st=!0,pe.clear(),D();return}let z=w(+Z);if(!z.length){q("Nothing that old"),D();return}for(let I of z)await A("/archive",{dir:I.state,id:I.id});q("Archived "+z.length),N()}),h.querySelectorAll("[data-p]").forEach((H)=>H.onclick=async(F)=>{if(F.stopPropagation(),H.dataset.p==="cancel"){st=!1,pe.clear(),D();return}let Z=d.filter((z)=>pe.has(T(z)));st=!1,pe.clear();for(let z of Z)await A("/archive",{dir:z.state,id:z.id});q("Archived "+Z.length),N()})}let E=document.createElement("ul");if(k===0&&a.mode==="new"){let C=document.createElement("li");C.className="inl",C.innerHTML=`${a.all?'<div class="combo" id="ndc"><button class="sel" id="newdir" aria-label="Project for this idea" aria-haspopup="listbox" aria-expanded="false"></button><div class="menu-pop" hidden><input class="sel" aria-label="Search projects" placeholder="Search projects…" spellcheck="false" autocomplete="off"><div id="nds" role="listbox"></div></div></div>':""}<input id="newidea" placeholder="What would you like done?"><textarea id="newnotes" placeholder="Notes: links, paths, background (optional)"></textarea><div class="att" id="newatt"></div><input type="file" id="newfile" multiple hidden><div class="row"><button class="ib" id="newpick" aria-label="Attach files" title="Attach files">${L("clip")}</button><button class="btn ghost" id="nc">Cancel</button><button class="btn pri" id="ns">Add</button></div>`,E.appendChild(C);let H=C.querySelector("#newidea"),F=[];Re(H,"f:new:0"),Re(C.querySelector("textarea"),"f:new:1");let Z=async()=>{let I=H.value.trim();if(!I)return;let ie=C.querySelector("textarea").value.trim();a.mode=null;let W=a.all?Me:null;if((await A("/inbox",W?{op:"add",dir:W,text:I,notes:ie}:{op:"add",text:I,notes:ie})).ok){if(q("Idea added"),F.length){let xe=await Promise.all(F.map((G)=>new Promise((J)=>{let oe=new FileReader;oe.onload=()=>J({name:G.name,data:oe.result}),oe.readAsDataURL(G)})));if(!(await A("/note",{dir:W,for:I,text:"",files:xe})).ok)q("Idea added, files not attached")}}N()},z=(I)=>{F.push(I),C.querySelector("#newatt").textContent=F.map((ie)=>ie.name).join(" · ")};C.querySelector("#newpick").onclick=()=>C.querySelector("#newfile").click(),C.querySelector("#newfile").onchange=(I)=>{[...I.target.files].forEach(z),I.target.value=""};for(let I of[H,C.querySelector("textarea")])I.onpaste=(ie)=>{for(let W of ie.clipboardData.items)if(W.kind==="file"){let R=W.getAsFile();z(new File([R],R.name==="image.png"?`paste-${Date.now()}.png`:R.name,{type:R.type})),ie.preventDefault()}};if(a.all)En(C);C.querySelector("#ns").onclick=Z,C.querySelector("#nc").onclick=()=>{a.mode=null,D()};{let I=Je([H,C.querySelector("textarea")]);if(I)C.querySelector(".row").prepend(I)}H.onkeydown=(I)=>{if(I.key==="Enter")Z();if(I.key==="Escape")a.mode=null,D()},setTimeout(()=>H.focus())}let P=$t.has(k)||d.length<=Ht+1?d:d.slice(0,Ht);if(P.forEach((C)=>{if(a.modeKey===T(C)&&a.mode&&a.mode!=="new"){E.appendChild(ws(C));return}let H=document.createElement("li");H.appendChild(ks(C)),E.appendChild(H)}),P.length<d.length){let C=document.createElement("li");C.innerHTML=`<button class="more-btn">Show ${d.length-P.length} more</button>`,C.firstChild.onclick=()=>{$t.add(k),D()},E.appendChild(C)}else if($t.has(k)&&d.length>Ht+1){let C=document.createElement("li");C.innerHTML='<button class="more-btn">Show less</button>',C.firstChild.onclick=()=>{$t.delete(k),D()},E.appendChild(C)}h.appendChild(E),r.appendChild(h)}),de(!1),e&&document.activeElement===document.body)Gt(e)?.focus({preventScroll:!0})}var Ot=()=>document.activeElement?.closest?.("#board .card")?.dataset.k,Gt=(e)=>e&&t("board").querySelector('.card[data-k="'+CSS.escape(e)+'"]');function Pn(e,n){let o=[...t("board").querySelectorAll("section")].map((b)=>[...b.querySelectorAll(".card")]).filter((b)=>b.length);if(!o.length)return;let r=document.activeElement.closest(".card"),l=o.findIndex((b)=>b.includes(r)),c=0;if(l<0)l=0;else if(c=o[l].indexOf(r),n)c=Math.max(0,Math.min(o[l].length-1,c+n));else l=Math.max(0,Math.min(o.length-1,l+e)),c=Math.min(c,o[l].length-1);let m=o[l][c];m.focus({preventScroll:!0}),m.scrollIntoView({block:"nearest",inline:"nearest"})}var Rn=()=>a.dir?{"x-kz-dir":a.dir}:{};async function N(e){let n;try{n=await fetch("/state"+(a.all?"?all=1":""),{headers:Rn()})}catch{q(Kn);return}let o=a.openKey&&a.S.cards.find((r)=>T(r)===a.openKey);a.S=await n.json();{let r=new Map;for(let l of a.S.cards){let c=T(l),m=r.get(c)||0;r.set(c,m+1),l._k=m?c+"#"+m:c}}for(let r of a.S.cards){if(r.kind!=="run"||r.archived)continue;if(due(T(r),r.notice,r.status==="done",a.S.now))fn(r.notice)}if(!(e&&a.mode&&t("board").querySelector(".inl input")))D();if(a.wantTerm&&!a.openKey&&!a.mode){let r=a.S.cards.find((l)=>T(l)===a.wantTerm&&l.pty);if(r){a.wantTerm=null,we(r);return}}if(a.openKey){let r=a.S.cards.find((c)=>T(c)===a.openKey),l=t("pbody").querySelector(".notes textarea");if(l&&document.activeElement===l)return;if(!r){if(o&&o.kind==="idea")xs(o);return}if(a.runDoc)V(r,!0,!0);else if(r.kind==="idea")zt(r)}}async function xs(e){let n=T(e),o=(l)=>l.toLowerCase().replace(/\s+/g," ").trim(),r=o(e.text);for(let l of a.S.cards.filter((c)=>c.kind==="run"&&c.state===e.state).sort((c,m)=>(m.moved??0)-(c.moved??0)).slice(0,5)){let c=await fetch(`/run/${encodeURIComponent(l.id)}?dir=${encodeURIComponent(l.state)}`).then((m)=>m.json()).catch(()=>null);if(a.openKey!==n)return;if(o(c?.request||"").includes(r)){we(l);return}}if(a.openKey===n)X()}var Kn="The board server is not running — start it with kaizen web (or the shortcut), then reload.";async function A(e,n){let o;try{o=await fetch(e,{method:"POST",headers:{"content-type":"application/json",...Rn()},body:JSON.stringify(n)})}catch{return q(Kn),{ok:!1}}let r=await o.json().catch(()=>({}));if(!o.ok){let l=r.error||r.why||"That did not work";if(q(l,{title:l.slice(0,80),what:`POST ${e} failed (HTTP ${o.status}): ${l}`}),r.manual)console.log("run manually:",r.manual)}return r}var St=null,qt=null,Nn=null,De=(e)=>e.pty?"running in its card":"opened in your terminal",Ss=()=>Nn||=new Promise((e,n)=>{let o=document.createElement("link");o.rel="stylesheet",o.href="/term.css",document.head.append(o);let r=document.createElement("script");r.src="/term.js",r.onload=()=>e(window.kzTerm),r.onerror=()=>{Nn=null,n()},document.head.append(r)});function ye(){if(qt)qt();qt=null,St=null,t("panel").classList.remove("wide")}async function Ut(e){let n=e.pty.key;if(St===n&&t("pbody").querySelector(".term"))return;ye(),St=n,t("panel").classList.add("wide"),t("pbody").innerHTML='<div class="term" aria-label="Agent terminal"></div>';let o;try{o=await Ss()}catch{t("pbody").innerHTML='<p style="color:var(--dim)">The terminal could not load.</p>';return}if(St!==n)return;qt=o(t("pbody").querySelector(".term"),n,()=>N(!0))}var Hn=(e)=>e.status==="stalled"||!!(e.pty&&!e.pty.alive);async function In(e,n){n.disabled=!0,me();let o=await A("/resume",{dir:e.state,id:e.id});if(!o.ok){n.disabled=!1;return}if(q("Resumed, "+De(o)+": "+je(o.agent)),o.pty)a.wantTerm=T(e);if(a.openKey===T(e))t("pfoot").innerHTML="",V(e,!0)}var Ft={"approvals.plan":"The plan is ready. Read it in the Plan tab, then approve or send it back.","approvals.review":"The work is ready to check. Approve to finish it, or revise with what to change.",findings:"The review has open findings. Approve as-is to accept them and finish, or fix selected findings first."},Wt=new Set,_t=(e)=>T(e)+":"+e.awaiting+":"+e.moved;async function Lt(e,n,o,r){n.disabled=!0,me();let l=await A("/decide",{dir:e.state,id:e.id,awaiting:e.awaiting,...o===void 0?{}:{why:o},...r?{answers:r}:{}});if(!l.ok){n.disabled=!1;return}if(Wt.add(_t(e)),q((o===void 0?"Approved":"Sent back")+", "+De(l)+": "+je(l.agent)),l.pty)a.wantTerm=T(e);if(a.openKey===T(e))t("pfoot").innerHTML="",V(e,!0)}function On(e){let n=t("pfoot"),o=e.awaiting==="approvals.plan";n.innerHTML=`<div class="inl" style="flex:1"><div class="q">${o?"What should the plan do differently?":"What should change in the work?"}</div><div class="q dim">Opens your agent on this run with your comment.${o?" It revises the plan and comes back here for approval.":" It changes the work, rechecks it and comes back here."}</div><textarea aria-label="Revise comment"></textarea><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Send back</button></div></div>`;let r=n.querySelector("textarea");r.focus(),n.querySelector("[data-a=c]").onclick=()=>{n.innerHTML="",V(e,!0)},n.querySelector("[data-a=s]").onclick=(l)=>{let c=r.value.trim();if(!c){q("Say what should change"),r.focus();return}Lt(e,l.target,c)}}function Gn(e,n){let o=t("pfoot");o.innerHTML=`<div class="inl qa" style="flex:1"><div class="q">The plan asks ${n.length===1?"one question":n.length+" questions"}.</div><div class="q dim">Your answers go to your agent with the approval. It records them and builds the plan.</div>${n.map((r,l)=>`<fieldset><legend>${p(r.q)}</legend>${r.options.map((c,m)=>`<label><input type="${r.multi?"checkbox":"radio"}" name="q${l}" value="${m}"${c.rec||!r.multi&&!m&&!r.options.some((b)=>b.rec)?" checked":""}> ${p(c.text)}</label>`).join("")}<input type="text" data-n="${l}" placeholder="Note (optional)" aria-label="Note: ${p(r.q)}"></fieldset>`).join("")}<div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Approve with these answers</button></div></div>`,o.querySelector("[data-a=c]").onclick=()=>{o.innerHTML="",V(e,!0)},o.querySelector("[data-a=s]").onclick=(r)=>Lt(e,r.target,void 0,n.map((l,c)=>{let m=[...o.querySelectorAll(`[name=q${c}]:checked`)].map((y)=>`"${l.options[+y.value].label}"`).join(", ")||"none of the options",b=o.querySelector(`[data-n="${c}"]`).value.trim();return`${l.q} — ${m}${b?"; "+b:""}`}))}function qs(e){let n=[];for(let o of(e||"").split(`
|
|
25
|
+
${m.length||b.length?`<div class="c-foot"><span class="c-chips">${m.join("")}</span><span class="c-marks">${b.join("")}</span></div>`:""}`;let w=ys(e);if(w.length){let h=It.has(n),d=w.filter((k)=>k.status==="done").length,v=w.some((k)=>k.status==="waiting"),x=document.createElement("div");x.className="kids",x.innerHTML=`<button class="kids-t" aria-expanded="${h}">${h?"▾":"▸"} ${w.length} ${w.length===1?"child":"children"}${d?` · ${d} done`:""}${v?'<span class="kids-w" title="A child is waiting on you"></span>':""}</button>`+(h?`<ul>${w.map((k,E)=>{let P=qe[k.status]||"idea";return`<li data-i="${E}" tabindex="0" role="button" title="Open"><span class="st d-${P}"><svg><use href="#s-${P}"/></svg></span><span class="kids-s">${p(Ke[k.status]||k.status)}</span><span class="kids-n" title="${p(k.title||k.text||k.id)}">${p(k.kind==="idea"?Le(k.text).head:(k.text||k.id||"").replace(/-/g," "))}</span></li>`}).join("")}</ul>`:""),x.querySelector(".kids-t").onclick=(k)=>{k.stopPropagation(),h?It.delete(n):It.add(n),D()},x.querySelectorAll("li").forEach((k)=>{let E=(P)=>{P.stopPropagation(),we(w[+k.dataset.i])};k.onclick=E,k.onkeydown=(P)=>(P.key==="Enter"||P.key===" ")&&(P.preventDefault(),E(P))}),o.appendChild(x)}if(n===a.menuKey){let h=[];if(ae){if(h.push(["restore","Restore",L("restore")]),h.push(["open","Open",L("open")]),e.kind==="idea")h.push(null,["delete","Delete",L("trash"),"danger"])}else if(e.kind==="idea"&&e.status==="idea")h.push(["run","Run",L("play")],["edit","Edit",L("pen")],...a.S.projects.some((d)=>d.dir!==e.state)?[["move","Move to project",L("ext")]]:[],["archive","Archive",L("archive")],null,["reject","Reject",L("x")],["delete","Delete",L("trash"),"danger"]);else if(e.kind==="idea")h.push(["open","Open",L("open")],["run","Run again",L("play")],["archive","Archive",L("archive")],null,["delete","Remove",L("trash"),"danger"]);else{let d=e.status==="waiting"?e.awaiting==="approvals.plan"?"Review plan":e.awaiting==="findings"?"See findings":e.awaiting==="approvals.review"?"Review work":"Open":"Open";if(h.push(["open",d,L("open")]),h.push(["archive","Archive",L("archive")]),e.status==="waiting"||e.status==="running"||e.status==="stalled")h.push(null,["reject","Abandon",L("x"),"danger"])}o.innerHTML+=`<div class="menu-pop" role="menu">${h.map((d)=>d?`<button role="menuitem" data-a="${d[0]}" class="${d[3]||""}">${d[2]}${d[1]}</button>`:"<hr>").join("")}</div>`}if(o.querySelectorAll("[data-a]").forEach((h)=>h.onclick=async(d)=>{d.stopPropagation();let v=h.dataset.a,x=a.menuKey;if(a.menuKey=null,v==="menu"){a.menuKey=x===n?null:n,D();return}if(v==="open"){we(e);return}if(v==="archive"||v==="restore"){xt(e,v==="archive");return}a.mode=v,a.modeKey=n,D()}),u)return o.classList.toggle("picked",pe.has(n)),o.onclick=()=>{pe.has(n)?pe.delete(n):pe.add(n),D()},o;return o.onclick=()=>{if(a.menuKey){a.menuKey=null,D();return}we(e)},o.onkeydown=(h)=>{if(h.key==="Enter"||h.key===" ")h.preventDefault(),we(e)},o}function ws(e){let n=document.createElement("li");n.className="inl";let o=e.state;if(a.mode==="edit")n.innerHTML=`<input value="${p(e.text)}"><textarea placeholder="Notes: links, paths, background">${p(e.notes||"")}</textarea><div class="q dim" id="clong" hidden>The first line is over 72 characters; logs will cut it short.</div><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Save</button></div>`,n.querySelector("[data-a=s]").onclick=async()=>{let l=n.querySelector("input").value.trim(),c=n.querySelector("textarea").value.trim();if(a.mode=null,l&&(l!==e.text||c!==(e.notes||"")))await A("/inbox",{op:"edit",dir:o,text:e.text,next:l,notes:c});N()};if(a.mode==="reject"){let l=e.kind==="idea";n.innerHTML=`<div class="q">${l?"Reject this idea?":"Abandon this run?"} The reason is recorded.</div><input placeholder="Reason"><div class="row"><button class="btn ghost" data-a="c">Keep it</button><button class="btn warn" data-a="s">${l?"Reject":"Abandon"}</button></div>`,n.querySelector("[data-a=s]").onclick=async()=>{let c=n.querySelector("input").value.trim();if(!c){q("A reason is required");return}if(a.mode=null,(l?await A("/inbox",{op:"reject",dir:o,text:e.text,reason:c}):await A("/abort",{dir:o,id:e.id,why:c})).ok)q(l?"Rejected":"Abandoned, reason recorded");N()}}if(a.mode==="delete")n.innerHTML=`<div class="q">Delete "${p(e.text)}"?</div><div class="q dim">Not recorded anywhere.</div><div class="row"><button class="btn ghost" data-a="c">Keep it</button><button class="btn danger" data-a="s">Delete</button></div>`,n.querySelector("[data-a=s]").onclick=async()=>{if(a.mode=null,(await A("/inbox",{op:"delete",dir:o,text:e.text})).ok)q("Deleted");N()};if(a.mode==="move"){let l=a.S.projects.filter((c)=>c.dir!==o);n.innerHTML=`<div class="q">Move "${p(e.text)}" to</div><select class="sel" aria-label="Project">${l.map((c)=>`<option value="${p(c.dir)}" title="${p(c.label)}">${p(ke(c.label))}</option>`).join("")}</select><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Move</button></div>`,n.querySelector("[data-a=s]").onclick=async()=>{let c=n.querySelector("select"),m=c.value,b=c.selectedOptions[0]?.textContent;if(a.mode=null,(await A("/inbox",{op:"move",dir:o,to:m,text:e.text})).ok)q(`Moved to ${b}`);N()}}if(a.mode==="run")n.innerHTML=`<div class="q">Run "${p(e.text)}"</div><div class="q dim">Opens your agent with this request in a terminal.</div><div class="q dim">Full plans, builds, and checks with a separate reviewer. Lite does it in one session.</div><div id="run-pick" style="display:grid;gap:6px"><div id="run-agent" class="agents" role="radiogroup" aria-label="Agent"></div></div><div class="run-actions"><label class="yolo" id="run-yolo" title="Yolo: the agent runs every command and edit without asking" hidden><button type="button" class="chk" role="switch" aria-checked="false"></button>Yolo</label><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn" data-a="l">Lite</button><button class="btn pri" data-a="s">Full run</button></div></div>`,n.querySelector("[data-a=s]").onclick=()=>Dn(e,"full"),n.querySelector("[data-a=l]").onclick=()=>Dn(e,"lite"),$s(n,e);if(n.querySelector("[data-a=c]").onclick=()=>{a.mode=null,D()},n.querySelectorAll("input,textarea").forEach((l,c)=>Re(l,"f:"+a.mode+":"+a.modeKey+":"+c)),a.mode!=="run"){let l=Je([...n.querySelectorAll("input,textarea")]);if(l)n.querySelector(".row").prepend(l)}let r=n.querySelector("input");if(r)r.onkeydown=(l)=>{if(l.key==="Enter")n.querySelector("[data-a=s]").click();if(l.key==="Escape")a.mode=null,D()},setTimeout(()=>r.focus());return n}var te=null,je=(e)=>e.name+(e.note?". "+e.note:"");async function $s(e,n){let o=T(n),r=e.querySelector("#run-agent"),l=await fetch("/agents?dir="+encodeURIComponent(n.state)).then((y)=>y.json()).catch(()=>null);if(!l||!l.agents||!l.agents.length){e.querySelector("#run-pick").hidden=!0;return}if(l.agents.some((y)=>y.cmd==="agy"))l.agents=l.agents.filter((y)=>y.cmd!=="gemini");if(!te||te.k!==o)te={k:o,agent:l.agents.some((y)=>y.cmd===l.default)?l.default:l.agents[0].cmd};r.innerHTML=l.agents.map((y)=>`<button type="button" class="agent" role="radio" aria-checked="${y.cmd===te.agent}" data-cmd="${p(y.cmd)}"><svg><use href="#c-${p(y.cmd)}"/></svg>${p(y.name)}</button>`).join("");let c=e.querySelector("#run-yolo"),m=c.querySelector(".chk"),b=()=>{let y=!!l.agents.find((u)=>u.cmd===te.agent)?.yolo,f=!!te.yolo;c.hidden=!y,m.classList.toggle("on",f),m.setAttribute("aria-checked",f)};r.querySelectorAll(".agent").forEach((y)=>y.onclick=()=>{te.agent=y.dataset.cmd,r.querySelectorAll(".agent").forEach((f)=>f.setAttribute("aria-checked",f===y)),b()}),m.onclick=()=>{te.yolo=!te.yolo,b()},b(),r.onkeydown=m.onkeydown=(y)=>{if(y.key==="Escape")a.mode=null,D()}}async function Dn(e,n){a.mode=null,me();let o=te&&te.k===T(e)?te:null;te=null;let r=!!(o&&o.yolo&&!t("run-yolo")?.hidden),l=await A("/run",{dir:e.state,text:e.text,kind:n,agent:o?o.agent:void 0,yolo:r});if(l.ok){if(q((n==="full"?"Full":"Lite")+(r?" yolo":"")+" run "+De(l)+": "+je(l.agent)),l.pty)a.wantTerm=T(e)}N()}function D(){if(a.mode&&a.mode!=="new"&&!a.S.cards.some((u)=>T(u)===a.modeKey))a.mode=null;if(a.mode!=="run"&&te)te.yolo=!1;if(!a.mode)gn("f:");try{sessionStorage.setItem("kz-form",JSON.stringify(["new","edit","reject"].includes(a.mode)?[a.mode,a.modeKey]:null))}catch(u){}let e=Ot(),n=a.all&&a.shown.size&&Ee().some((u)=>a.shown.has(u.dir)),o=a.S.cards.filter((u)=>u.archived===ae&&(!n||a.shown.has(u.state))),r=t("board");r.innerHTML="";let l=a.S.cards.filter((u)=>u.archived).length,c=(u)=>o.filter((w)=>w.status===u).length,m=[];for(let u of["waiting","running","stalled","starting","idea","done","abandoned"]){let w=c(u);if(!w&&u!=="running")continue;let h=u==="running"?'<span class="d-run"><span class="pulse"></span></span>':`<span class="st d-${qe[u]}"><svg><use href="#s-${qe[u]}"/></svg></span>`;m.push(`<button class="${u==="waiting"?"attn ":""}${Ue===u?"on":""}" data-f="${u}" title="Show only ${Ke[u]}">${h}${w} ${Ke[u]}${u==="idea"&&w!==1?"s":""}</button>`)}m.push(`<button class="${ae?"on":""}" data-f="__archive" title="${o.filter((u)=>u.kind==="idea").length} ideas · ${o.filter((u)=>u.kind==="run").length} runs on the board">${ae?"Back to board":"Archive "+l}</button>`),t("strip").innerHTML=m.join(""),t("strip").querySelectorAll("button").forEach((u)=>u.onclick=()=>{if(u.dataset.f==="__archive"){ae=!ae,Ue=null,D();return}Ue=Ue===u.dataset.f?null:u.dataset.f,D()}),a.PL=[["All projects","all"],...a.S.projects.map((u)=>[u.label,u.dir]),...a.S.fresh.map((u)=>[u.label,u.dir,1]),["Find projects on this PC…","__find"]];let b=a.all||!a.S.dir?"All projects":(a.PL.find((u)=>u[1]===a.S.dir)||["All projects"])[0];if(t("proj").innerHTML=a.all||!a.S.dir?`<span class="plogo" aria-hidden="true">${L("board")}</span><span>${p(b)}</span>`:`${Oe(a.S.dir,b)}<span>${p(ke(b))}</span>`,t("proj").title=b,!t("projpop").hidden)wt();t("showc").hidden=!a.all;let y=Ee(),f=y.filter((u)=>a.shown.has(u.dir)).length;if(t("show").textContent=f?`Showing ${f} of ${y.length}`:"Showing all",!t("showpop").hidden)kt();if(!o.length&&a.mode!=="new"){if(r.innerHTML=ae?'<div class="empty"><div class="mascot"></div><b>Nothing archived</b></div>':'<div class="empty"><div class="mascot"></div><b>Nothing here yet</b><p>Add an idea and kaizen will plan it, build it, and check the result.</p><button class="btn pri" id="newempty">New idea</button></div>',!ae)t("newempty").onclick=()=>{a.mode="new",D()}}if(kn.forEach((u,w)=>{let h=document.createElement("section"),d=o.filter((C)=>C.column===w&&(!Ue||C.status===Ue)&&(ae||!vs(C)));if(w<4){let C=(F)=>F.kind==="run"?F.moved||0:a.S.cards.indexOf(F),H=nt[w]==="old";d.sort((F,Z)=>H?C(F)-C(Z):C(Z)-C(F))}let v=w===4&&!ae&&d.length,x=(C)=>d.filter((H)=>a.S.now-(H.moved||a.S.now)>C*bn),k=`<h2>${u}<i>${d.length||""}</i>${w<4&&d.length>1?`<button class="btn sm ghost ord" title="Sort order">${nt[w]==="old"?"Oldest":"Newest"}</button>`:""}${w===0&&!ae?`<button class="ib sm" title="New idea" aria-label="New idea">${L("plus")}</button>`:""}`;if(v&&st)k+=`<span class="pickbar"><button class="btn sm ghost" data-p="cancel">Cancel</button><button class="btn sm pri" data-p="go" ${pe.size?"":"disabled"}>Archive ${pe.size||""}</button></span>`;else if(v)k+='<button class="btn sm ghost clear" title="Archive finished runs">Clear ▾</button>';if(v&&a.clearMenu)k+=`<div class="menu-pop clear-pop"><button data-c="7">Older than a week <i>${x(7).length}</i></button><button data-c="3">Older than 3 days <i>${x(3).length}</i></button><button data-c="0">Everything <i>${d.length}</i></button><hr><button data-c="pick">Pick which…</button></div>`;h.innerHTML=k+"</h2>";{let C=h.querySelector(".ord");if(C)C.onclick=(H)=>{H.stopPropagation(),nt[w]=nt[w]==="old"?"new":"old";try{localStorage.setItem("kz-order",JSON.stringify(nt))}catch(F){}D()}}if(w===0&&!ae)h.querySelector(".ib").onclick=()=>{a.mode="new",D()};if(v){let C=h.querySelector(".clear");if(C)C.onclick=(H)=>{H.stopPropagation(),a.clearMenu=!a.clearMenu,D()};h.querySelectorAll("[data-c]").forEach((H)=>H.onclick=async(F)=>{F.stopPropagation(),a.clearMenu=!1;let Z=H.dataset.c;if(Z==="pick"){st=!0,pe.clear(),D();return}let z=x(+Z);if(!z.length){q("Nothing that old"),D();return}for(let I of z)await A("/archive",{dir:I.state,id:I.id});q("Archived "+z.length),N()}),h.querySelectorAll("[data-p]").forEach((H)=>H.onclick=async(F)=>{if(F.stopPropagation(),H.dataset.p==="cancel"){st=!1,pe.clear(),D();return}let Z=d.filter((z)=>pe.has(T(z)));st=!1,pe.clear();for(let z of Z)await A("/archive",{dir:z.state,id:z.id});q("Archived "+Z.length),N()})}let E=document.createElement("ul");if(w===0&&a.mode==="new"){let C=document.createElement("li");C.className="inl",C.innerHTML=`${a.all?'<div class="combo" id="ndc"><button class="sel" id="newdir" aria-label="Project for this idea" aria-haspopup="listbox" aria-expanded="false"></button><div class="menu-pop" hidden><input class="sel" aria-label="Search projects" placeholder="Search projects…" spellcheck="false" autocomplete="off"><div id="nds" role="listbox"></div></div></div>':""}<input id="newidea" placeholder="What would you like done?"><textarea id="newnotes" placeholder="Notes: links, paths, background (optional)"></textarea><div class="att" id="newatt"></div><input type="file" id="newfile" multiple hidden><div class="row"><button class="ib" id="newpick" aria-label="Attach files" title="Attach files">${L("clip")}</button><button class="btn ghost" id="nc">Cancel</button><button class="btn pri" id="ns">Add</button></div>`,E.appendChild(C);let H=C.querySelector("#newidea"),F=[];Re(H,"f:new:0"),Re(C.querySelector("textarea"),"f:new:1");let Z=async()=>{let I=H.value.trim();if(!I)return;let ie=C.querySelector("textarea").value.trim();a.mode=null;let W=a.all?Me:null;if((await A("/inbox",W?{op:"add",dir:W,text:I,notes:ie}:{op:"add",text:I,notes:ie})).ok){if(q("Idea added"),F.length){let xe=await Promise.all(F.map((G)=>new Promise((J)=>{let oe=new FileReader;oe.onload=()=>J({name:G.name,data:oe.result}),oe.readAsDataURL(G)})));if(!(await A("/note",{dir:W,for:I,text:"",files:xe})).ok)q("Idea added, files not attached")}}N()},z=(I)=>{F.push(I),C.querySelector("#newatt").textContent=F.map((ie)=>ie.name).join(" · ")};C.querySelector("#newpick").onclick=()=>C.querySelector("#newfile").click(),C.querySelector("#newfile").onchange=(I)=>{[...I.target.files].forEach(z),I.target.value=""};for(let I of[H,C.querySelector("textarea")])I.onpaste=(ie)=>{for(let W of ie.clipboardData.items)if(W.kind==="file"){let R=W.getAsFile();z(new File([R],R.name==="image.png"?`paste-${Date.now()}.png`:R.name,{type:R.type})),ie.preventDefault()}};if(a.all)En(C);C.querySelector("#ns").onclick=Z,C.querySelector("#nc").onclick=()=>{a.mode=null,D()};{let I=Je([H,C.querySelector("textarea")]);if(I)C.querySelector(".row").prepend(I)}H.onkeydown=(I)=>{if(I.key==="Enter")Z();if(I.key==="Escape")a.mode=null,D()},setTimeout(()=>H.focus())}let P=$t.has(w)||d.length<=Ht+1?d:d.slice(0,Ht);if(P.forEach((C)=>{if(a.modeKey===T(C)&&a.mode&&a.mode!=="new"){E.appendChild(ws(C));return}let H=document.createElement("li");H.appendChild(ks(C)),E.appendChild(H)}),P.length<d.length){let C=document.createElement("li");C.innerHTML=`<button class="more-btn">Show ${d.length-P.length} more</button>`,C.firstChild.onclick=()=>{$t.add(w),D()},E.appendChild(C)}else if($t.has(w)&&d.length>Ht+1){let C=document.createElement("li");C.innerHTML='<button class="more-btn">Show less</button>',C.firstChild.onclick=()=>{$t.delete(w),D()},E.appendChild(C)}h.appendChild(E),r.appendChild(h)}),de(!1),e&&document.activeElement===document.body)Gt(e)?.focus({preventScroll:!0})}var Ot=()=>document.activeElement?.closest?.("#board .card")?.dataset.k,Gt=(e)=>e&&t("board").querySelector('.card[data-k="'+CSS.escape(e)+'"]');function Pn(e,n){let o=[...t("board").querySelectorAll("section")].map((b)=>[...b.querySelectorAll(".card")]).filter((b)=>b.length);if(!o.length)return;let r=document.activeElement.closest(".card"),l=o.findIndex((b)=>b.includes(r)),c=0;if(l<0)l=0;else if(c=o[l].indexOf(r),n)c=Math.max(0,Math.min(o[l].length-1,c+n));else l=Math.max(0,Math.min(o.length-1,l+e)),c=Math.min(c,o[l].length-1);let m=o[l][c];m.focus({preventScroll:!0}),m.scrollIntoView({block:"nearest",inline:"nearest"})}var Rn=()=>a.dir?{"x-kz-dir":a.dir}:{};async function N(e){let n;try{n=await fetch("/state"+(a.all?"?all=1":""),{headers:Rn()})}catch{q(Kn);return}let o=a.openKey&&a.S.cards.find((r)=>T(r)===a.openKey);a.S=await n.json();{let r=new Map;for(let l of a.S.cards){let c=T(l),m=r.get(c)||0;r.set(c,m+1),l._k=m?c+"#"+m:c}}for(let r of a.S.cards){if(r.kind!=="run"||r.archived)continue;if(due(T(r),r.notice,r.status==="done",a.S.now))fn(r.notice)}if(!(e&&a.mode&&t("board").querySelector(".inl input")))D();if(a.wantTerm&&!a.openKey&&!a.mode){let r=a.S.cards.find((l)=>T(l)===a.wantTerm&&l.pty);if(r){a.wantTerm=null,we(r);return}}if(a.openKey){let r=a.S.cards.find((c)=>T(c)===a.openKey),l=t("pbody").querySelector(".notes textarea");if(l&&document.activeElement===l)return;if(!r){if(o&&o.kind==="idea")xs(o);return}if(a.runDoc)V(r,!0,!0);else if(r.kind==="idea")zt(r)}}async function xs(e){let n=T(e),o=(l)=>l.toLowerCase().replace(/\s+/g," ").trim(),r=o(e.text);for(let l of a.S.cards.filter((c)=>c.kind==="run"&&c.state===e.state).sort((c,m)=>(m.moved??0)-(c.moved??0)).slice(0,5)){let c=await fetch(`/run/${encodeURIComponent(l.id)}?dir=${encodeURIComponent(l.state)}`).then((m)=>m.json()).catch(()=>null);if(a.openKey!==n)return;if(o(c?.request||"").includes(r)){we(l);return}}if(a.openKey===n)X()}var Kn="The board server is not running — start it with kaizen web (or the shortcut), then reload.";async function A(e,n){let o;try{o=await fetch(e,{method:"POST",headers:{"content-type":"application/json",...Rn()},body:JSON.stringify(n)})}catch{return q(Kn),{ok:!1}}let r=await o.json().catch(()=>({}));if(!o.ok){let l=r.error||r.why||"That did not work";if(q(l,{title:l.slice(0,80),what:`POST ${e} failed (HTTP ${o.status}): ${l}`}),r.manual)console.log("run manually:",r.manual)}return r}var St=null,qt=null,Nn=null,De=(e)=>e.pty?"running in its card":"opened in your terminal",Ss=()=>Nn||=new Promise((e,n)=>{let o=document.createElement("link");o.rel="stylesheet",o.href="/term.css",document.head.append(o);let r=document.createElement("script");r.src="/term.js",r.onload=()=>e(window.kzTerm),r.onerror=()=>{Nn=null,n()},document.head.append(r)});function ye(){if(qt)qt();qt=null,St=null,t("panel").classList.remove("wide")}async function Ut(e){let n=e.pty.key;if(St===n&&t("pbody").querySelector(".term"))return;ye(),St=n,t("panel").classList.add("wide"),t("pbody").innerHTML='<div class="term" aria-label="Agent terminal"></div>';let o;try{o=await Ss()}catch{t("pbody").innerHTML='<p style="color:var(--dim)">The terminal could not load.</p>';return}if(St!==n)return;qt=o(t("pbody").querySelector(".term"),n,()=>N(!0))}var Hn=(e)=>e.status==="stalled"||!!(e.pty&&!e.pty.alive);async function In(e,n){n.disabled=!0,me();let o=await A("/resume",{dir:e.state,id:e.id});if(!o.ok){n.disabled=!1;return}if(q("Resumed, "+De(o)+": "+je(o.agent)),o.pty)a.wantTerm=T(e);if(a.openKey===T(e))t("pfoot").innerHTML="",V(e,!0)}var Ft={"approvals.plan":"The plan is ready. Read it in the Plan tab, then approve or send it back.","approvals.review":"The work is ready to check. Approve to finish it, or revise with what to change.",findings:"The review has open findings. Approve as-is to accept them and finish, or fix selected findings first."},Wt=new Set,_t=(e)=>T(e)+":"+e.awaiting+":"+e.moved;async function Lt(e,n,o,r){n.disabled=!0,me();let l=await A("/decide",{dir:e.state,id:e.id,awaiting:e.awaiting,...o===void 0?{}:{why:o},...r?{answers:r}:{}});if(!l.ok){n.disabled=!1;return}if(Wt.add(_t(e)),q((o===void 0?"Approved":"Sent back")+", "+De(l)+": "+je(l.agent)),l.pty)a.wantTerm=T(e);if(a.openKey===T(e))t("pfoot").innerHTML="",V(e,!0)}function On(e){let n=t("pfoot"),o=e.awaiting==="approvals.plan";n.innerHTML=`<div class="inl" style="flex:1"><div class="q">${o?"What should the plan do differently?":"What should change in the work?"}</div><div class="q dim">Opens your agent on this run with your comment.${o?" It revises the plan and comes back here for approval.":" It changes the work, rechecks it and comes back here."}</div><textarea aria-label="Revise comment"></textarea><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Send back</button></div></div>`;let r=n.querySelector("textarea");r.focus(),n.querySelector("[data-a=c]").onclick=()=>{n.innerHTML="",V(e,!0)},n.querySelector("[data-a=s]").onclick=(l)=>{let c=r.value.trim();if(!c){q("Say what should change"),r.focus();return}Lt(e,l.target,c)}}function Gn(e,n){let o=t("pfoot");o.innerHTML=`<div class="inl qa" style="flex:1"><div class="q">The plan asks ${n.length===1?"one question":n.length+" questions"}.</div><div class="q dim">Your answers go to your agent with the approval. It records them and builds the plan.</div>${n.map((r,l)=>`<fieldset><legend>${p(r.q)}</legend>${r.options.map((c,m)=>`<label><input type="${r.multi?"checkbox":"radio"}" name="q${l}" value="${m}"${c.rec||!r.multi&&!m&&!r.options.some((b)=>b.rec)?" checked":""}> ${p(c.text)}</label>`).join("")}<input type="text" data-n="${l}" placeholder="Note (optional)" aria-label="Note: ${p(r.q)}"></fieldset>`).join("")}<div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Approve with these answers</button></div></div>`,o.querySelector("[data-a=c]").onclick=()=>{o.innerHTML="",V(e,!0)},o.querySelector("[data-a=s]").onclick=(r)=>Lt(e,r.target,void 0,n.map((l,c)=>{let m=[...o.querySelectorAll(`[name=q${c}]:checked`)].map((y)=>`"${l.options[+y.value].label}"`).join(", ")||"none of the options",b=o.querySelector(`[data-n="${c}"]`).value.trim();return`${l.q} — ${m}${b?"; "+b:""}`}))}function qs(e){let n=[];for(let o of(e||"").split(`
|
|
26
26
|
`)){let r=/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2})\]\s?(.*)$/.exec(o);if(r){n.push({at:r[1],text:r[2]});continue}let l=n[n.length-1];if(l)l.text+=`
|
|
27
27
|
`+o.replace(/^\s{2}/,"");else if(o.trim())n.push({at:"",text:o})}return n.map((o)=>({...o,text:o.text.trim()})).filter((o)=>o.text)}var Bt=null;function Yt(e,n,o){let r=(h)=>ht(h,e),l=qs(n),c=e.kind==="idea"?Le(e.text):{parts:[]},m=(c.parts.length?`<section class="desc">${Ce(c.parts.map((h)=>`### ${h.label}
|
|
28
28
|
${h.body}`).join(`
|
|
29
29
|
|
|
30
|
-
`))}</section>`:"")+`<div class="chat">${l.map((h)=>`<div class="msg">${h.at?`<time>${p(h.at)}</time>`:""}${Ce(r(h.text))}</div>`).join("")||'<p style="color:var(--dim)">No notes yet.</p>'}</div>`;if(!o)m+='<div class="notes"><textarea placeholder="Add a note. Paste a screenshot, or attach files."></textarea><div class="att" id="natt"></div><div class="tools"><input type="file" id="nfile" multiple hidden><button class="btn ghost" id="npick">Attach</button><span>Enter sends, Shift+Enter newline</span><button class="btn pri" id="nsend">Send</button></div></div>';if(t("pbody").innerHTML=m,o)return;let b=t("pbody").querySelector("textarea"),y=[];if(Re(b,"n:"+T(e)),Bt===T(e))Bt=null,b.focus();{let h=Je([b]);if(h)t("npick").before(h)}let f=()=>{t("natt").textContent=y.map((h)=>h.name).join(" · ")},u=(h)=>{y.push(h),f()};t("npick").onclick=()=>t("nfile").click(),t("nfile").onchange=(h)=>{[...h.target.files].forEach(u),h.target.value=""},b.onpaste=(h)=>{for(let d of h.clipboardData.items)if(d.kind==="file"){let v=d.getAsFile();u(new File([v],v.name==="image.png"?`paste-${Date.now()}.png`:v.name,{type:v.type})),h.preventDefault()}};let
|
|
30
|
+
`))}</section>`:"")+`<div class="chat">${l.map((h)=>`<div class="msg">${h.at?`<time>${p(h.at)}</time>`:""}${Ce(r(h.text))}</div>`).join("")||'<p style="color:var(--dim)">No notes yet.</p>'}</div>`;if(!o)m+='<div class="notes"><textarea placeholder="Add a note. Paste a screenshot, or attach files."></textarea><div class="att" id="natt"></div><div class="tools"><input type="file" id="nfile" multiple hidden><button class="btn ghost" id="npick">Attach</button><span>Enter sends, Shift+Enter newline</span><button class="btn pri" id="nsend">Send</button></div></div>';if(t("pbody").innerHTML=m,o)return;let b=t("pbody").querySelector("textarea"),y=[];if(Re(b,"n:"+T(e)),Bt===T(e))Bt=null,b.focus();{let h=Je([b]);if(h)t("npick").before(h)}let f=()=>{t("natt").textContent=y.map((h)=>h.name).join(" · ")},u=(h)=>{y.push(h),f()};t("npick").onclick=()=>t("nfile").click(),t("nfile").onchange=(h)=>{[...h.target.files].forEach(u),h.target.value=""},b.onpaste=(h)=>{for(let d of h.clipboardData.items)if(d.kind==="file"){let v=d.getAsFile();u(new File([v],v.name==="image.png"?`paste-${Date.now()}.png`:v.name,{type:v.type})),h.preventDefault()}};let w=async()=>{de(!0);let h=b.value.trim();if(!h&&!y.length)return;let d=await Promise.all(y.map((k)=>new Promise((E)=>{let P=new FileReader;P.onload=()=>E({name:k.name,data:P.result}),P.readAsDataURL(k)}))),v=e.kind==="run"?{dir:e.state,id:e.id,text:h,files:d}:{dir:e.state,for:e.text,text:h,files:d};if((await A("/note",v)).ok){y.length=0,b.value="";try{sessionStorage.removeItem(mn+"n:"+T(e))}catch(k){}q("Noted"),Bt=T(e),b.blur(),N()}};t("nsend").onclick=w,b.onkeydown=(h)=>{if(h.key==="Enter"&&!h.shiftKey)h.preventDefault(),w()},t("pbody").scrollTop=0}var zn="kz-sent",$e=new Set,Qt=new Set((()=>{try{return JSON.parse(sessionStorage.getItem(zn))||[]}catch(e){return[]}})());function Un(e){let n=(u)=>e.id+":"+u,o=(u)=>Qt.has(n(u.n)),r=a.runDoc.findings||[],l=r.filter((u)=>!u.done&&!o(u));if(e.archived||!r.some((u)=>!u.done)||!(e.status==="done"||e.awaiting==="findings"||e.awaiting==="approvals.review"))return;let c=document.createElement("div");if(c.className="inl fix",c.innerHTML=`<div class="q">Fix findings</div><div class="q dim">Opens your agent in this project, told to fix the ticked findings and recheck them.</div>${e.status==="waiting"?'<div class="q dim">A session is already waiting on this run in a terminal; this opens another one.</div>':""}`+r.map((u)=>{let w=u.done?" (closed)":o(u)?" (sent)":"";return`<label class="${u.done?"off done":w?"off":""}"><input type="checkbox" data-n="${u.n}" ${w?"checked disabled":$e.has(n(u.n))?"":"checked"}><span>${u.n}. ${Ce(u.text).replace(/^<p>|<\/p>$/g,"")}${w}</span></label>`}).join("")+(l.length?'<div class="row"><button class="btn ghost" data-a="all"></button><button class="btn pri" data-a="s"></button></div>':""),!l.length)return t("pbody").appendChild(c);let m=c.querySelector("[data-a=s]"),b=c.querySelector("[data-a=all]"),y=()=>l.filter((u)=>!$e.has(n(u.n))).map((u)=>u.n),f=()=>{let u=y().length;m.textContent=u===l.length?"Fix all "+u:"Fix "+u+" selected",m.disabled=!u,b.textContent=u===l.length?"Select none":"Select all",c.querySelectorAll("input:not(:disabled)").forEach((w)=>w.checked=!$e.has(n(w.dataset.n)))};c.onchange=(u)=>{let w=n(u.target.dataset.n);u.target.checked?$e.delete(w):$e.add(w),f()},b.onclick=()=>{let u=y().length===l.length;l.forEach((w)=>u?$e.add(n(w.n)):$e.delete(n(w.n))),f()},m.onclick=async()=>{let u=y();m.disabled=!0,me();let w=await A("/fix",{dir:e.state,id:e.id,nums:u});if(w.ok){if(q("Fixing "+u.length+" finding"+(u.length===1?"":"s")+", "+De(w)+": "+je(w.agent)),w.pty)a.wantTerm=T(e);u.forEach((h)=>Qt.add(n(h)));try{sessionStorage.setItem(zn,JSON.stringify([...Qt]))}catch(h){}V(e,!0)}else f()},f(),t("pbody").appendChild(c)}function Vt(e){let n=a.runDoc.git,o=a.runDoc.commit;if(e.archived||!n||!n.repo||!(e.status==="done"||e.awaiting==="approvals.review"))return"";if(o&&o.pushed)return`<button class="btn" disabled title="${p(o.sha)}">Committed & pushed</button>`+(n.files.length?'<button class="btn" data-a="commit">Commit new changes</button>':n.ahead>0?'<button class="btn" data-a="push">Push</button>':"");if(n.files.length)return'<button class="btn pri" data-a="commit">Commit & push</button>';if(n.ahead>0)return'<button class="btn pri" data-a="push">Push</button>';return o||a.runDoc.impl?'<button class="btn" disabled>Committed & pushed</button>':""}function Jt(e,n){let o=a.runDoc.git,r=t("pfoot");r.innerHTML=`<div class="inl" style="flex:1"><div class="q">Commit ${o.files.length} changed file${o.files.length===1?"":"s"} on ${p(o.branch||"this branch")} and push to ${p(o.remote)}?</div><div class="q dim">Only the files this run's report names.${o.others?` ${o.others} other changed file${o.others===1?" is":"s are"} left alone.`:""}</div><pre class="files">${p(o.files.join(`
|
|
31
31
|
`))}</pre>${o.shared&&o.shared.length?`<div class="q">These files also hold other runs' changes, and a file is committed whole:</div><pre class="files">${p(o.shared.join(`
|
|
32
32
|
`))}</pre>`:""}<textarea aria-label="Commit message" placeholder="feat(scope): what changed">${p(n||$n(a.runDoc.impl)||e.title||e.text)}</textarea><div class="q dim" id="clong" hidden>The first line is over 72 characters; logs will cut it short.</div><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Commit & push</button></div></div>`;let l=r.querySelector("textarea"),c=r.querySelector("[data-a=s]");l.focus();let m=()=>{l.style.height="auto",l.style.height=Math.min(l.scrollHeight+2,innerHeight/2)+"px",t("clong").hidden=l.value.split(`
|
|
33
|
-
`)[0].length<=72};l.oninput=m,m(),r.querySelector("[data-a=c]").onclick=()=>{r.innerHTML="",V(e,!0)},c.onclick=()=>{if(!l.value.trim()){q("A commit message is required");return}Xt(e,l.value,c)},l.onkeydown=(b)=>{if(b.key==="Enter"&&!b.shiftKey)b.preventDefault(),c.click()}}async function Xt(e,n,o){o.disabled=!0,o.dataset.busy=1,o.textContent="Pushing…";let r=await A("/commit",{dir:e.state,id:e.id,message:n,files:a.runDoc.git.files});if(r.ok)q("Committed & pushed "+r.sha);if(t("pfoot").innerHTML="",a.openKey!==T(e))return;if(await V(e,!1),!r.ok&&n&&a.openKey===T(e)&&a.runDoc.git.files&&a.runDoc.git.files.length)Jt(e,n)}var Fn=[["Request","request"],["Notes","notes"],["Plan","plan"],["Backlog","backlog"],["Work","impl"],["Review","review"],["Preview","preview"]],Zt=(e)=>e.charAt(0).toUpperCase()+e.slice(1),Ls=(e)=>/\s/.test(e)?e:Zt(e.replace(/^\d{4}-\d{2}-\d{2}-/,"").split(/[-_]/).map((n)=>/^(ui|ux|api|cli|ai|ci|id|url|pr|tui|ios|pwa)$/i.test(n)?n.toUpperCase():n).join(" "));function we(e,n){if(Q!=="board")re("board",!0);if(de(!0),$e.clear(),a.openKey=T(e),t("panel").classList.add("open"),t("scrim").classList.add("on"),t("ptitle").textContent=e.kind==="idea"?Le(e.text).head:Ls(e.title||e.text),t("ptitle").title=e.kind==="idea"?"":e.id||"",D(),t("pterm").hidden=!0,e.kind==="idea"){a.runDoc=null,t("psub").innerHTML=`<span class="pst"><span class="st d-idea"><svg><use href="#s-idea"/></svg></span>Idea</span><span class="pst dim">${p(e.where.split(/[\\/]/).pop()||e.where)}</span>`,t("ptabs").innerHTML="",zt(e);let o=t("pfoot");o.innerHTML=e.archived?'<button class="btn pri" data-a="restore">Restore</button>':e.status==="idea"?'<button class="btn ghost danger" data-a="delete">Delete</button><button class="btn ghost" data-a="archive">Archive</button><span class="dec"><button class="btn" data-a="reject">Reject</button><button class="btn" data-a="edit">Edit</button><button class="btn pri" data-a="run">Run</button></span>':`<span class="note">${e.pty?"Starting: the agent runs below.":"Starting: a run was launched for this idea. No terminal with your agent? Run it again."}</span><span class="dec"><button class="btn" data-a="run">Run again</button></span>`,o.querySelectorAll("[data-a]").forEach((r)=>r.onclick=()=>{let l=r.dataset.a;if(l==="archive"||l==="restore"){xt(e,l==="archive");return}let c=T(e);X(),a.mode=l,a.modeKey=c,D()});return}if(!n)a.lastTab=a.tab=e.status==="abandoned"?"plan":{1:"plan",2:"backlog",3:"review",4:"preview"}[e.column]||"request";if(!n&&e.pty&&e.pty.alive&&e.column>=1&&e.column<=3)a.tab="term";V(e,!1)}var Fe=null;function Cs(e){let n=e.column===2&&a.runDoc.progress,o=Fe&&Fe.id===e.id?Fe:null,r=n?Sn(n,o&&o.st):"";if(!r)return;Fe={id:e.id,st:[...n.matchAll(/^\s*-\s*(todo|doing|done):/gm)].map((c)=>c[1]),w:+/width:(\d+)%/.exec(r)[1]},t("pbody").innerHTML=r+(a.runDoc.backlog?"<h3>Backlog</h3>"+Ce(a.runDoc.backlog,!0):"");let l=t("pbody").querySelector(".pbar i");if(o&&o.w!==Fe.w)l.style.width=o.w+"%",l.offsetWidth,l.style.width=Fe.w+"%"}function zt(e){if(e.pty)Ut(e);else ye(),Yt(e,e.notes,e.archived||e.status!=="idea")}async function V(e,n,o){if(!n||!a.runDoc||a.runDoc.id!==e.id||o){let d=await fetch(`/run/${encodeURIComponent(e.id)}?dir=${encodeURIComponent(e.state)}`).then((v)=>v.json());if(n&&t("pfoot").querySelector(".inl,[data-busy],.more[open]"))return;a.runDoc=d}if(a.openKey!==T(e))return;e=a.S.cards.find((d)=>T(d)===a.openKey)||e;let r=t("pbody").querySelector(".notes textarea");if(n&&r&&document.activeElement===r)return;t("psub").innerHTML=`<span class="pst"><span class="st d-${qe[e.status]||"idea"}"><svg><use href="#s-${qe[e.status]||"idea"}"/></svg></span>${p(Zt(Ke[e.status]||e.status))}</span>`+(e.awaiting?`<span class="pst dim">${p(Zt(Xe(e.awaiting)))}</span>`:"")+(e.parent?`<span class="pst link" title="Filed by this run">${p(e.parent)}</span>`:"");let l=Fn.filter((d)=>d[1]==="request"||d[1]==="notes"||(d[1]==="preview"?ft(a.runDoc.impl):a.runDoc[d[1]]||d[1]==="backlog"&&e.column===2&&a.runDoc.progress));if(e.pty&&a.wantTerm===T(e))a.wantTerm=null,a.tab="term";if(a.tab==="term"&&!e.pty)a.tab=a.lastTab;if(a.tab!=="term"&&!l.some((d)=>d[1]===a.tab))a.tab=[...l].reverse().find((d)=>d[1]!=="notes")[1];if(a.tab!=="term")a.lastTab=a.tab;t("panel").classList.toggle("wide",a.tab==="term");let c=t("pterm");c.hidden=!e.pty,c.classList.toggle("on",a.tab==="term"),c.setAttribute("aria-pressed",a.tab==="term"),c.title=c.ariaLabel=a.tab==="term"?"Close terminal":"Terminal",c.onclick=()=>{a.tab=a.tab==="term"?a.lastTab:"term",V(e,!0)},t("ptabs").innerHTML=l.map((d)=>`<button class="${d[1]===a.tab?"on":""}" data-k="${d[1]}">${d[0]}</button>`).join(""),[...t("ptabs").children].forEach((d)=>d.onclick=()=>{a.tab=d.dataset.k,V(e,!0)});let m=a.tab,b=m==="preview"?ft(a.runDoc.impl)&&ht(ft(a.runDoc.impl),e):a.runDoc[m];if(m!=="term")ye();if(m==="term")Ut(e);else if(m==="notes"){let d=e.archived||e.status==="done"||e.status==="abandoned";Yt(e,b,d)}else t("pbody").innerHTML=b?Ce(b,m==="backlog"):`<p style="color:var(--dim)">No ${{request:"request",plan:"plan",impl:"work report",preview:"preview",review:"review",backlog:"backlog"}[m]} yet.</p>`;if(m==="backlog")Cs(e);if(m==="review")Un(e);if(m==="notes"&&a.runDoc.linkedFrom&&a.runDoc.linkedFrom.length)t("pbody").insertAdjacentHTML("afterbegin",`<h3>Linked from notes</h3><ul class="lnk">${a.runDoc.linkedFrom.map((d)=>`<li><a href="/notebook?dir=${encodeURIComponent(e.state)}&note=${encodeURIComponent(d)}">${p(d)}</a></li>`).join("")}</ul>`);if(We&&!n)if(We.k===T(e))js(We);else We=null;let y=t("pfoot");if(n&&y.querySelector(".inl,[data-busy],.more[open]"))return;let f=Vt(e)+(e.archived?'<button class="btn pri" data-a="restore">Restore</button>':'<button class="btn ghost" data-a="archive">Archive</button>'),u=e.status==="waiting"&&e.awaiting==="approvals.plan"?xn(a.runDoc.plan):[],k=u||[],h=e.pty&&!e.pty.alive?'<button class="btn" data-a="resume">Resume session</button>':"";if(e.archived)y.innerHTML=f;else if((e.status==="waiting"||e.awaiting==="findings")&&Ft[e.awaiting]){let d=Wt.has(_t(e)),v=!u?" It has open questions the board cannot show: answer them in the terminal running this run, or approve and your agent asks them.":k.length?` It asks ${k.length===1?"one question":k.length+" questions"}: Approve opens ${k.length===1?"it":"them"}.`:"",w=Vt(e).replace(/class="btn( pri)?"/g,"")+'<button data-a="archive">Archive</button><hr><button data-a="ab" class="danger">Abandon</button>';y.innerHTML=`<span class="note">${d?"Sent to your agent.":Ft[e.awaiting]+v+" Answering here opens a new agent session."}</span><details class="more"><summary class="btn ghost">More</summary><div class="menu-pop">${w}</div></details>${h}<span class="dec"><button class="btn" data-a="revise"${d?" disabled":""}>Revise</button><button class="btn pri" data-a="approve"${d?" disabled":""}>${e.awaiting==="findings"?"Approve as-is":"Approve"}</button></span>`}else if(e.status==="waiting")y.innerHTML=`<span class="note">Waiting on you: ${p(Xe(e.awaiting))}. Answer in the ${e.pty&&e.pty.alive?"terminal (button at the top)":"terminal running this run"}.</span>${f}<button class="btn ghost" data-a="ab">Abandon</button>${h}`;else if(e.status==="done")y.innerHTML=f+h;else if(e.status==="abandoned")y.innerHTML=f;else y.innerHTML=`<span class="note">${e.pty&&!e.pty.alive?"Its agent ended before the stage finished. Resume continues it here.":e.status==="stalled"?"No progress for a while. Resume it, or abandon it.":e.pty?"":"Running in your agent."}</span>${f}<button class="btn ghost" data-a="ab">Abandon run</button>${Hn(e)?'<button class="btn pri" data-a="resume">Resume</button>':""}`;y.querySelectorAll("[data-a]").forEach((d)=>d.onclick=()=>{let v=d.dataset.a;if(v==="archive"||v==="restore"){xt(e,v==="archive");return}if(v==="push"){Xt(e,"",d);return}if(v==="commit"){Jt(e);return}if(v==="approve"){if(k.length)Gn(e,k);else Lt(e,d);return}if(v==="revise"){On(e);return}if(v==="resume"){In(e,d);return}X(),a.mode="reject",a.modeKey=T(e),D()})}var We=null;function Wn(){let e=new URLSearchParams(location.search),n=e.get("dir"),o=e.get("run"),r=e.get("idea");if(!o&&r===null)return;history.replaceState(null,"",location.pathname+"?all=1");let l=a.S.cards.find((y)=>y.state===n&&(o?y.kind==="run"&&y.id===o:y.kind==="idea"&&y.text===r));if(!l){q(o?"That run is not on this board":"That idea is not on this board");return}let c=/^(finding|backlog)-(\d+)$/.exec(e.get("item")||""),m=e.get("tab")||(c?c[1]==="finding"?"review":"backlog":""),b=Fn.find((y)=>y[1]===(m==="work"?"impl":m));if(b)a.tab=b[1];We=c?{type:c[1],n:+c[2],k:T(l)}:null,we(l,!!b)}function js(e){We=null;let n=(m)=>String(m).replace(/!?\[([^\]]*)\]\([^)\s]+\)/g,"$1").replace(/[`*]/g,"").replace(/\s+/g," ").trim(),o=[...(a.runDoc.backlog||"").matchAll(/^\s*-\s*(?:open|done|rejected):\s*(.*)$/gm)],r=e.type==="finding"?(a.runDoc.findings||[]).find((m)=>m.n===e.n)?.text:o[e.n-1]?.[1];if(!r){q("That item is not in this run");return}let l=n(r).slice(0,60),c=[...t("pbody").querySelectorAll("li,p")].find((m)=>!m.closest(".prog")&&n(m.textContent).includes(l));if(!c)return;c.scrollIntoView({block:"center"}),c.classList.add("hit"),setTimeout(()=>c.classList.remove("hit"),2400)}async function xt(e,n){if((await A("/archive",e.kind==="idea"?{dir:e.state,text:e.text,archived:n}:{dir:e.state,id:e.id,archived:n})).ok)q(n?"Archived":"Restored");X(),N()}function X(){if(Q!=="board")return;let e=a.openKey;if(de(!0),ye(),a.openKey=null,a.runDoc=null,t("panel").classList.remove("open"),t("scrim").classList.remove("on"),D(),e)Gt(e)?.focus({preventScroll:!0})}function _n(){document.addEventListener("click",(e)=>{for(let n of document.querySelectorAll(".more[open]"))if(!n.contains(e.target))n.open=!1}),t("pclose").onclick=X,t("scrim").onclick=X}function Bn(){t("kbd").onclick=()=>t("help").classList.add("on"),t("help").onclick=()=>t("help").classList.remove("on"),document.addEventListener("click",()=>{if(a.menuKey||a.clearMenu)a.menuKey=null,a.clearMenu=!1,D()}),document.onkeydown=(e)=>{if(Q==="sources"&>&&B()&&!t("help").classList.contains("on")&&!e.target.matches("input:not([type=checkbox]),select,textarea,[contenteditable]")&&!e.target.closest("#src-sec")&>(e))return;if(e.target.matches("input,select,textarea,[contenteditable]"))return;if(e.key==="?"){t("help").classList.toggle("on");return}if(e.key==="/"&&t("sq")&&t("panel").classList.contains("open")){e.preventDefault(),t("sq").focus();return}if(e.key==="Escape"){if(t("help").classList.remove("on"),a.mode)a.mode=null,D();else X();return}if(e.key==="n"){if(Q!=="board")re("board",!0);a.mode="new",D()}if(e.key==="a")a.all=!a.all,ze(a.all?null:a.S.dir),N();let n={ArrowUp:[0,-1],k:[0,-1],ArrowDown:[0,1],j:[0,1],ArrowLeft:[-1,0],h:[-1,0],ArrowRight:[1,0],l:[1,0]}[e.key],o=n&&a.openKey&&!e.altKey&&!e.ctrlKey&&!e.metaKey&&a.S.cards.find((c)=>T(c)===a.openKey&&c.kind==="run");if(o&&t("ptabs").children.length){if(e.preventDefault(),n[0]){let c=[...t("ptabs").children].map((y)=>y.dataset.k).concat(t("pterm").hidden?[]:["term"]),m=c.indexOf(a.tab),b=c[m<0?n[0]>0?0:c.length-1:Math.max(0,Math.min(c.length-1,m+n[0]))];if(b!==a.tab)a.tab=b,V(o,!0)}else t("pbody").scrollBy({top:n[1]*40});return}if(n&&!t("panel").classList.contains("open")&&!e.altKey&&!e.ctrlKey&&!e.metaKey&&(e.target===document.body||e.target.matches("#board .card"))){e.preventDefault(),Pn(...n);return}let r=a.openKey||Ot(),l=r&&a.S.cards.find((c)=>T(c)===r);if(!l)return;if(e.key==="e"&&l.kind==="idea")X(),a.mode="edit",a.modeKey=T(l),D();if(e.key==="x"&&!["done","abandoned","starting"].includes(l.status))X(),a.mode="reject",a.modeKey=T(l),D();if(e.key==="r"&&l.kind==="idea")X(),a.mode="run",a.modeKey=T(l),D()}}var en=null;function Yn(){let e=new EventSource("/events");e.onmessage=(n)=>{if(n.data==="changed"){if(N(!0),B()&&!mt&&!t("src-go")?.disabled&&!t("src-add")?.disabled)K()}else if(n.data==="reload")location.reload();else if(n.data.startsWith("hello ")){let o=n.data.slice(6);if(en&&o!==en)location.reload();en=o}},e.onerror=()=>{e.close(),setTimeout(Yn,3000)}}var ot=((e)=>e==="icebox"?"sources":e)(new URLSearchParams(location.search).get("view"));function Qn(){addEventListener("focus",()=>N(!0)),Rt(["settings","sources"].includes(ot)?ot:"board"),document.body.classList.toggle("paged",["settings","sources"].includes(ot)),document.body.classList.toggle("ice",ot==="sources"),(async()=>{let e;try{e=localStorage.getItem("kz-proj")}catch(n){}if(e&&!Nt)try{if((await fetch("/cd",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({dir:e})})).ok)a.all=!1,a.dir=e;else ze()}catch(n){}try{let n=JSON.parse(sessionStorage.getItem("kz-form"));if(n)[a.mode,a.modeKey]=n}catch(n){}N().then(()=>{re(ot),Wn()}),Yn()})()}_n();jn();Mn();Ln();Bn();Qn();document.addEventListener("pointerdown",(e)=>{let n=e.target;for(let o of document.querySelectorAll(".menu-pop:not([hidden])")){let r=o.closest(".combo,details")||o.parentElement;if(!r||r.contains(n))continue;if(r instanceof HTMLDetailsElement)r.open=!1;else if(r.classList.contains("combo"))o.hidden=!0,r.querySelector("[aria-expanded]")?.setAttribute("aria-expanded","false");else o.remove(),r.classList.remove("menu"),a.menuKey=null,a.clearMenu=!1}},!0);})();
|
|
33
|
+
`)[0].length<=72};l.oninput=m,m(),r.querySelector("[data-a=c]").onclick=()=>{r.innerHTML="",V(e,!0)},c.onclick=()=>{if(!l.value.trim()){q("A commit message is required");return}Xt(e,l.value,c)},l.onkeydown=(b)=>{if(b.key==="Enter"&&!b.shiftKey)b.preventDefault(),c.click()}}async function Xt(e,n,o){o.disabled=!0,o.dataset.busy=1,o.textContent="Pushing…";let r=await A("/commit",{dir:e.state,id:e.id,message:n,files:a.runDoc.git.files});if(r.ok)q("Committed & pushed "+r.sha);if(t("pfoot").innerHTML="",a.openKey!==T(e))return;if(await V(e,!1),!r.ok&&n&&a.openKey===T(e)&&a.runDoc.git.files&&a.runDoc.git.files.length)Jt(e,n)}var Fn=[["Request","request"],["Notes","notes"],["Plan","plan"],["Backlog","backlog"],["Work","impl"],["Review","review"],["Preview","preview"]],Zt=(e)=>e.charAt(0).toUpperCase()+e.slice(1),Ls=(e)=>/\s/.test(e)?e:Zt(e.replace(/^\d{4}-\d{2}-\d{2}-/,"").split(/[-_]/).map((n)=>/^(ui|ux|api|cli|ai|ci|id|url|pr|tui|ios|pwa)$/i.test(n)?n.toUpperCase():n).join(" "));function we(e,n){if(Q!=="board")re("board",!0);if(de(!0),$e.clear(),a.openKey=T(e),t("panel").classList.add("open"),t("scrim").classList.add("on"),t("ptitle").textContent=e.kind==="idea"?Le(e.text).head:Ls(e.title||e.text),t("ptitle").title=e.kind==="idea"?"":e.id||"",D(),t("pterm").hidden=!0,e.kind==="idea"){a.runDoc=null,t("psub").innerHTML=`<span class="pst"><span class="st d-idea"><svg><use href="#s-idea"/></svg></span>Idea</span><span class="pst dim">${p(e.where.split(/[\\/]/).pop()||e.where)}</span>`,t("ptabs").innerHTML="",zt(e);let o=t("pfoot");o.innerHTML=e.archived?'<button class="btn pri" data-a="restore">Restore</button>':e.status==="idea"?'<button class="btn ghost danger" data-a="delete">Delete</button><button class="btn ghost" data-a="archive">Archive</button><span class="dec"><button class="btn" data-a="reject">Reject</button><button class="btn" data-a="edit">Edit</button><button class="btn pri" data-a="run">Run</button></span>':`<span class="note">${e.pty?"Starting: the agent runs below.":"Starting: a run was launched for this idea. No terminal with your agent? Run it again."}</span><span class="dec"><button class="btn" data-a="run">Run again</button></span>`,o.querySelectorAll("[data-a]").forEach((r)=>r.onclick=()=>{let l=r.dataset.a;if(l==="archive"||l==="restore"){xt(e,l==="archive");return}let c=T(e);X(),a.mode=l,a.modeKey=c,D()});return}if(!n)a.lastTab=a.tab=e.status==="abandoned"?"plan":{1:"plan",2:"backlog",3:"review",4:"preview"}[e.column]||"request";if(!n&&e.pty&&e.pty.alive&&e.column>=1&&e.column<=3)a.tab="term";V(e,!1)}var Fe=null;function Cs(e){let n=e.column===2&&a.runDoc.progress,o=Fe&&Fe.id===e.id?Fe:null,r=n?Sn(n,o&&o.st):"";if(!r)return;Fe={id:e.id,st:[...n.matchAll(/^\s*-\s*(todo|doing|done):/gm)].map((c)=>c[1]),w:+/width:(\d+)%/.exec(r)[1]},t("pbody").innerHTML=r+(a.runDoc.backlog?"<h3>Backlog</h3>"+Ce(a.runDoc.backlog,!0):"");let l=t("pbody").querySelector(".pbar i");if(o&&o.w!==Fe.w)l.style.width=o.w+"%",l.offsetWidth,l.style.width=Fe.w+"%"}function zt(e){if(e.pty)Ut(e);else ye(),Yt(e,e.notes,e.archived||e.status!=="idea")}async function V(e,n,o){if(!n||!a.runDoc||a.runDoc.id!==e.id||o){let d=await fetch(`/run/${encodeURIComponent(e.id)}?dir=${encodeURIComponent(e.state)}`).then((v)=>v.json());if(n&&t("pfoot").querySelector(".inl,[data-busy],.more[open]"))return;a.runDoc=d}if(a.openKey!==T(e))return;e=a.S.cards.find((d)=>T(d)===a.openKey)||e;let r=t("pbody").querySelector(".notes textarea");if(n&&r&&document.activeElement===r)return;t("psub").innerHTML=`<span class="pst"><span class="st d-${qe[e.status]||"idea"}"><svg><use href="#s-${qe[e.status]||"idea"}"/></svg></span>${p(Zt(Ke[e.status]||e.status))}</span>`+(e.awaiting?`<span class="pst dim">${p(Zt(Xe(e.awaiting)))}</span>`:"")+(e.parent?`<span class="pst link" title="Filed by this run">${p(e.parent)}</span>`:"");let l=Fn.filter((d)=>d[1]==="request"||d[1]==="notes"||(d[1]==="preview"?ft(a.runDoc.impl):a.runDoc[d[1]]||d[1]==="backlog"&&e.column===2&&a.runDoc.progress));if(e.pty&&a.wantTerm===T(e))a.wantTerm=null,a.tab="term";if(a.tab==="term"&&!e.pty)a.tab=a.lastTab;if(a.tab!=="term"&&!l.some((d)=>d[1]===a.tab))a.tab=[...l].reverse().find((d)=>d[1]!=="notes")[1];if(a.tab!=="term")a.lastTab=a.tab;t("panel").classList.toggle("wide",a.tab==="term");let c=t("pterm");c.hidden=!e.pty,c.classList.toggle("on",a.tab==="term"),c.setAttribute("aria-pressed",a.tab==="term"),c.title=c.ariaLabel=a.tab==="term"?"Close terminal":"Terminal",c.onclick=()=>{a.tab=a.tab==="term"?a.lastTab:"term",V(e,!0)},t("ptabs").innerHTML=l.map((d)=>`<button class="${d[1]===a.tab?"on":""}" data-k="${d[1]}">${d[0]}</button>`).join(""),[...t("ptabs").children].forEach((d)=>d.onclick=()=>{a.tab=d.dataset.k,V(e,!0)});let m=a.tab,b=m==="preview"?ft(a.runDoc.impl)&&ht(ft(a.runDoc.impl),e):a.runDoc[m];if(m!=="term")ye();if(m==="term")Ut(e);else if(m==="notes"){let d=e.archived||e.status==="done"||e.status==="abandoned";Yt(e,b,d)}else t("pbody").innerHTML=b?Ce(b,m==="backlog"):`<p style="color:var(--dim)">No ${{request:"request",plan:"plan",impl:"work report",preview:"preview",review:"review",backlog:"backlog"}[m]} yet.</p>`;if(m==="backlog")Cs(e);if(m==="review")Un(e);if(m==="notes"&&a.runDoc.linkedFrom&&a.runDoc.linkedFrom.length)t("pbody").insertAdjacentHTML("afterbegin",`<h3>Linked from notes</h3><ul class="lnk">${a.runDoc.linkedFrom.map((d)=>`<li><a href="/notebook?dir=${encodeURIComponent(e.state)}&note=${encodeURIComponent(d)}">${p(d)}</a></li>`).join("")}</ul>`);if(We&&!n)if(We.k===T(e))js(We);else We=null;let y=t("pfoot");if(n&&y.querySelector(".inl,[data-busy],.more[open]"))return;let f=Vt(e)+(e.archived?'<button class="btn pri" data-a="restore">Restore</button>':'<button class="btn ghost" data-a="archive">Archive</button>'),u=e.status==="waiting"&&e.awaiting==="approvals.plan"?xn(a.runDoc.plan):[],w=u||[],h=e.pty&&!e.pty.alive?'<button class="btn" data-a="resume">Resume session</button>':"";if(e.archived)y.innerHTML=f;else if((e.status==="waiting"||e.awaiting==="findings")&&Ft[e.awaiting]){let d=Wt.has(_t(e)),v=!u?" It has open questions the board cannot show: answer them in the terminal running this run, or approve and your agent asks them.":w.length?` It asks ${w.length===1?"one question":w.length+" questions"}: Approve opens ${w.length===1?"it":"them"}.`:"",x=Vt(e).replace(/class="btn( pri)?"/g,"")+'<button data-a="archive">Archive</button><hr><button data-a="ab" class="danger">Abandon</button>';y.innerHTML=`<span class="note">${d?"Sent to your agent.":Ft[e.awaiting]+v+" Answering here opens a new agent session."}</span><details class="more"><summary class="btn ghost">More</summary><div class="menu-pop">${x}</div></details>${h}<span class="dec"><button class="btn" data-a="revise"${d?" disabled":""}>Revise</button><button class="btn pri" data-a="approve"${d?" disabled":""}>${e.awaiting==="findings"?"Approve as-is":"Approve"}</button></span>`}else if(e.status==="waiting")y.innerHTML=`<span class="note">Waiting on you: ${p(Xe(e.awaiting))}. Answer in the ${e.pty&&e.pty.alive?"terminal (button at the top)":"terminal running this run"}.</span>${f}<button class="btn ghost" data-a="ab">Abandon</button>${h}`;else if(e.status==="done")y.innerHTML=f+h;else if(e.status==="abandoned")y.innerHTML=f;else y.innerHTML=`<span class="note">${e.pty&&!e.pty.alive?"Its agent ended before the stage finished. Resume continues it here.":e.status==="stalled"?"No progress for a while. Resume it, or abandon it.":e.pty?"":"Running in your agent."}</span>${f}<button class="btn ghost" data-a="ab">Abandon run</button>${Hn(e)?'<button class="btn pri" data-a="resume">Resume</button>':""}`;y.querySelectorAll("[data-a]").forEach((d)=>d.onclick=()=>{let v=d.dataset.a;if(v==="archive"||v==="restore"){xt(e,v==="archive");return}if(v==="push"){Xt(e,"",d);return}if(v==="commit"){Jt(e);return}if(v==="approve"){if(w.length)Gn(e,w);else Lt(e,d);return}if(v==="revise"){On(e);return}if(v==="resume"){In(e,d);return}X(),a.mode="reject",a.modeKey=T(e),D()})}var We=null;function Wn(){let e=new URLSearchParams(location.search),n=e.get("dir"),o=e.get("run"),r=e.get("idea");if(!o&&r===null)return;history.replaceState(null,"",location.pathname+"?all=1");let l=a.S.cards.find((y)=>y.state===n&&(o?y.kind==="run"&&y.id===o:y.kind==="idea"&&y.text===r));if(!l){q(o?"That run is not on this board":"That idea is not on this board");return}let c=/^(finding|backlog)-(\d+)$/.exec(e.get("item")||""),m=e.get("tab")||(c?c[1]==="finding"?"review":"backlog":""),b=Fn.find((y)=>y[1]===(m==="work"?"impl":m));if(b)a.tab=b[1];We=c?{type:c[1],n:+c[2],k:T(l)}:null,we(l,!!b)}function js(e){We=null;let n=(m)=>String(m).replace(/!?\[([^\]]*)\]\([^)\s]+\)/g,"$1").replace(/[`*]/g,"").replace(/\s+/g," ").trim(),o=[...(a.runDoc.backlog||"").matchAll(/^\s*-\s*(?:open|done|rejected):\s*(.*)$/gm)],r=e.type==="finding"?(a.runDoc.findings||[]).find((m)=>m.n===e.n)?.text:o[e.n-1]?.[1];if(!r){q("That item is not in this run");return}let l=n(r).slice(0,60),c=[...t("pbody").querySelectorAll("li,p")].find((m)=>!m.closest(".prog")&&n(m.textContent).includes(l));if(!c)return;c.scrollIntoView({block:"center"}),c.classList.add("hit"),setTimeout(()=>c.classList.remove("hit"),2400)}async function xt(e,n){if((await A("/archive",e.kind==="idea"?{dir:e.state,text:e.text,archived:n}:{dir:e.state,id:e.id,archived:n})).ok)q(n?"Archived":"Restored");X(),N()}function X(){if(Q!=="board")return;let e=a.openKey;if(de(!0),ye(),a.openKey=null,a.runDoc=null,t("panel").classList.remove("open"),t("scrim").classList.remove("on"),D(),e)Gt(e)?.focus({preventScroll:!0})}function _n(){document.addEventListener("click",(e)=>{for(let n of document.querySelectorAll(".more[open]"))if(!n.contains(e.target))n.open=!1}),t("pclose").onclick=X,t("scrim").onclick=X}function Bn(){t("kbd").onclick=()=>t("help").classList.add("on"),t("help").onclick=()=>t("help").classList.remove("on"),document.addEventListener("click",()=>{if(a.menuKey||a.clearMenu)a.menuKey=null,a.clearMenu=!1,D()}),document.onkeydown=(e)=>{if(Q==="sources"&>&&B()&&!t("help").classList.contains("on")&&!e.target.matches("input:not([type=checkbox]),select,textarea,[contenteditable]")&&!e.target.closest("#src-sec")&>(e))return;if(e.target.matches("input,select,textarea,[contenteditable]"))return;if(e.key==="?"){t("help").classList.toggle("on");return}if(e.key==="/"&&t("sq")&&t("panel").classList.contains("open")){e.preventDefault(),t("sq").focus();return}if(e.key==="Escape"){if(t("help").classList.remove("on"),a.mode)a.mode=null,D();else X();return}if(e.key==="n"){if(Q!=="board")re("board",!0);a.mode="new",D()}if(e.key==="a")a.all=!a.all,ze(a.all?null:a.S.dir),N();let n={ArrowUp:[0,-1],k:[0,-1],ArrowDown:[0,1],j:[0,1],ArrowLeft:[-1,0],h:[-1,0],ArrowRight:[1,0],l:[1,0]}[e.key],o=n&&a.openKey&&!e.altKey&&!e.ctrlKey&&!e.metaKey&&a.S.cards.find((c)=>T(c)===a.openKey&&c.kind==="run");if(o&&t("ptabs").children.length){if(e.preventDefault(),n[0]){let c=[...t("ptabs").children].map((y)=>y.dataset.k).concat(t("pterm").hidden?[]:["term"]),m=c.indexOf(a.tab),b=c[m<0?n[0]>0?0:c.length-1:Math.max(0,Math.min(c.length-1,m+n[0]))];if(b!==a.tab)a.tab=b,V(o,!0)}else t("pbody").scrollBy({top:n[1]*40});return}if(n&&!t("panel").classList.contains("open")&&!e.altKey&&!e.ctrlKey&&!e.metaKey&&(e.target===document.body||e.target.matches("#board .card"))){e.preventDefault(),Pn(...n);return}let r=a.openKey||Ot(),l=r&&a.S.cards.find((c)=>T(c)===r);if(!l)return;if(e.key==="e"&&l.kind==="idea")X(),a.mode="edit",a.modeKey=T(l),D();if(e.key==="x"&&!["done","abandoned","starting"].includes(l.status))X(),a.mode="reject",a.modeKey=T(l),D();if(e.key==="r"&&l.kind==="idea")X(),a.mode="run",a.modeKey=T(l),D()}}var en=null;function Yn(){let e=new EventSource("/events");e.onmessage=(n)=>{if(n.data==="changed"){if(N(!0),B()&&!mt&&!t("src-go")?.disabled&&!t("src-add")?.disabled)K()}else if(n.data==="reload")location.reload();else if(n.data.startsWith("hello ")){let o=n.data.slice(6);if(en&&o!==en)location.reload();en=o}},e.onerror=()=>{e.close(),setTimeout(Yn,3000)}}var ot=((e)=>e==="icebox"?"sources":e)(new URLSearchParams(location.search).get("view"));function Qn(){addEventListener("focus",()=>N(!0)),Rt(["settings","sources"].includes(ot)?ot:"board"),document.body.classList.toggle("paged",["settings","sources"].includes(ot)),document.body.classList.toggle("ice",ot==="sources"),(async()=>{let e;try{e=localStorage.getItem("kz-proj")}catch(n){}if(e&&!Nt)try{if((await fetch("/cd",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({dir:e})})).ok)a.all=!1,a.dir=e;else ze()}catch(n){}try{let n=JSON.parse(sessionStorage.getItem("kz-form"));if(n)[a.mode,a.modeKey]=n}catch(n){}N().then(()=>{re(ot),Wn()}),Yn()})()}_n();jn();Mn();Ln();Bn();Qn();document.addEventListener("pointerdown",(e)=>{let n=e.target;for(let o of document.querySelectorAll(".menu-pop:not([hidden])")){let r=o.closest(".combo,details")||o.parentElement;if(!r||r.contains(n))continue;if(r instanceof HTMLDetailsElement)r.open=!1;else if(r.classList.contains("combo"))o.hidden=!0,r.querySelector("[aria-expanded]")?.setAttribute("aria-expanded","false");else o.remove(),r.classList.remove("menu"),a.menuKey=null,a.clearMenu=!1}},!0);})();
|
package/web/notebook.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* kaizen notebook
|
|
1
|
+
/* kaizen notebook fff0c686caeb0a21 -- generated by bun run build:web from web/notebook.ts; do not edit */
|
|
2
2
|
var Ss=[],ua=[];(()=>{let e="lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,1n,9,16,o,,x,1i,3,,i,,7,a,2,t,3,1k,,,7,2,2,2,3,9,,a,2,q,,2,3,1k,,,5,4,2,2,3,3,,u,2,3,,b,3,1k,,,8,,3,,3,k,2,m,6,,3,1k,,,7,2,2,2,3,7,3,a,2,u,,1n,5,3,3,,4,9,,14,5,1j,,,7,,3,,4,7,2,b,2,t,3,1k,,,7,,3,,4,7,2,b,2,f,,c,4,1j,2,,7,,3,,4,9,,a,2,t,3,1y,,4,6,,,,8,i,2,1p,,,8,c,8,2q,,,a,b,7,21,2,r,,,,,,4,2,1d,k,,2,5,b,,10,9,,2u,b,,6,n,4,4,3,g,4,d,,,3,6,,f,,jj,3,qa,4,s,3,t,2,u,2,1s,w,9,,19,3,,,39,2,y,,3a,c,4,c,63,5,1l,a,,,,,2,o,2,,1c,1a,2,c,k,5,1b,h,12,9,c,3,u,d,1k,e,1c,k,48,3,,l,4,,6,,2,3,5i,1s,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,n,5,4,,2b,2,1e,i,q,i,d,,12,8,p,d,18,4,1b,e,10,,1v,e,c,,8,2,1a,,1f,,,3,2,2,5,2,,,15,5,5,2,6k,8,,2,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,1t,5,8t,2,25,6,1y,b,1d,4,3e,3,1h,f,15,,2,2,a,4,19,b,7,,1p,3,10,e,g,2,18,,c,3,1c,e,8,4,,2,2k,c,6,,2,,4d,c,l,4,1j,2,,7,2,2,2,3,9,,a,2,2,7,3,5,1v,9,,,2,,,4,,5,,,e,2,2a,i,n,,29,k,6j,7,2,9,r,2,2a,h,2y,d,2t,3,2,a,74,f,6t,6,,2,2,4,,,,2,3x,7,2,7,3,,s,a,14,7,,4,8,,9,b,1a,g,5i,8,5j,8,,8,2a,m,,e,3e,6,3,,,2,,7,,,1u,5,,2,,5,9n,4,9,2,,,1c,7,3,5,n,,44l,,6,f,8ug,i,1xc,5,1n,7,t4,,,1j,7,4,29,,b,2,f57,2,3mp,1a,2,n,f2,5,3,6,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,2s,,4g,7,af,,1p,4,e4,4,72,2,6r,,2,,7,2,5,,d6,7,31,7,240,5".split(",").map((t)=>t?parseInt(t,36):1);for(let t=0,i=0;t<e.length;t++)(t%2?ua:Ss).push(i=i+e[t])})();function op(e){if(e<768)return!1;for(let t=0,i=Ss.length;;){let n=t+i>>1;if(e<Ss[n])i=n;else if(e>=ua[n])t=n+1;else return!0;if(t==i)return!1}}function ha(e){return e>=127462&&e<=127487}var fa=8205;function da(e,t,i=!0,n=!0){return(i?Oa:lp)(e,t,n)}function Oa(e,t,i){if(t==e.length)return t;if(t&&pa(e.charCodeAt(t))&&ma(e.charCodeAt(t-1)))t--;let n=bs(e,t);t+=ca(n);while(t<e.length){let r=bs(e,t);if(n==fa||r==fa||i&&op(r))t+=ca(r),n=r;else if(ha(r)){let s=0,o=t-2;while(o>=0&&ha(bs(e,o)))s++,o-=2;if(s%2==0)break;else t+=2}else break}return t}function lp(e,t,i){while(t>1){let n=Oa(e,t-2,i);if(n<t)return n;t--}return 0}function bs(e,t){let i=e.charCodeAt(t);if(!ma(i)||t+1==e.length)return i;let n=e.charCodeAt(t+1);if(!pa(n))return i;return(i-55296<<10)+(n-56320)+65536}function pa(e){return e>=56320&&e<57344}function ma(e){return e>=55296&&e<56320}function ca(e){return e<65536?1:2}class _{lineAt(e){if(e<0||e>this.length)throw RangeError(`Invalid position ${e} in document of length ${this.length}`);return this.lineInner(e,!1,1,0)}line(e){if(e<1||e>this.lines)throw RangeError(`Invalid line number ${e} in ${this.lines}-line document`);return this.lineInner(e,!0,1,0)}replace(e,t,i){[e,t]=vi(this,e,t);let n=[];if(this.decompose(0,e,n,2),i.length)i.decompose(0,i.length,n,3);return this.decompose(t,this.length,n,1),ft.from(n,this.length-(t-e)+i.length)}append(e){return this.replace(this.length,this.length,e)}slice(e,t=this.length){[e,t]=vi(this,e,t);let i=[];return this.decompose(e,t,i,0),ft.from(i,t-e)}eq(e){if(e==this)return!0;if(e.length!=this.length||e.lines!=this.lines)return!1;let t=this.scanIdentical(e,1),i=this.length-this.scanIdentical(e,-1),n=new wi(this),r=new wi(e);for(let s=t,o=t;;){if(n.next(s),r.next(s),s=0,n.lineBreak!=r.lineBreak||n.done!=r.done||n.value!=r.value)return!1;if(o+=n.value.length,n.done||o>=i)return!0}}iter(e=1){return new wi(this,e)}iterRange(e,t=this.length){return new Ts(this,e,t)}iterLines(e,t){let i;if(e==null)i=this.iter();else{if(t==null)t=this.lines+1;let n=this.line(e).from;i=this.iterRange(n,Math.max(n,t==this.lines+1?this.length:t<=1?0:this.line(t-1).to))}return new As(i)}toString(){return this.sliceString(0)}toJSON(){let e=[];return this.flatten(e),e}constructor(){}static of(e){if(e.length==0)throw RangeError("A document must have at least one line");if(e.length==1&&!e[0])return _.empty;return e.length<=32?new oe(e):ft.from(oe.split(e,[]))}}class oe extends _{constructor(e,t=ap(e)){super();this.text=e,this.length=t}get lines(){return this.text.length}get children(){return null}lineInner(e,t,i,n){for(let r=0;;r++){let s=this.text[r],o=n+s.length;if((t?i:o)>=e)return new ka(n,o,i,s);n=o+1,i++}}decompose(e,t,i,n){let r=e<=0&&t>=this.length?this:new oe(ga(this.text,e,t),Math.min(t,this.length)-Math.max(0,e));if(n&1){let s=i.pop(),o=cr(r.text,s.text.slice(),0,r.length);if(o.length<=32)i.push(new oe(o,s.length+r.length));else{let l=o.length>>1;i.push(new oe(o.slice(0,l)),new oe(o.slice(l)))}}else i.push(r)}replace(e,t,i){if(!(i instanceof oe))return super.replace(e,t,i);[e,t]=vi(this,e,t);let n=cr(this.text,cr(i.text,ga(this.text,0,e)),t),r=this.length+i.length-(t-e);if(n.length<=32)return new oe(n,r);return ft.from(oe.split(n,[]),r)}sliceString(e,t=this.length,i=`
|
|
3
3
|
`){[e,t]=vi(this,e,t);let n="";for(let r=0,s=0;r<=t&&s<this.text.length;s++){let o=this.text[s],l=r+o.length;if(r>e&&s)n+=i;if(e<l&&t>r)n+=o.slice(Math.max(0,e-r),t-r);r=l+1}return n}flatten(e){for(let t of this.text)e.push(t)}scanIdentical(){return 0}static split(e,t){let i=[],n=-1;for(let r of e)if(i.push(r),n+=r.length+1,i.length==32)t.push(new oe(i,n)),i=[],n=-1;if(n>-1)t.push(new oe(i,n));return t}}class ft extends _{constructor(e,t){super();this.children=e,this.length=t,this.lines=0;for(let i of e)this.lines+=i.lines}lineInner(e,t,i,n){for(let r=0;;r++){let s=this.children[r],o=n+s.length,l=i+s.lines-1;if((t?l:o)>=e)return s.lineInner(e,t,i,n);n=o+1,i=l+1}}decompose(e,t,i,n){for(let r=0,s=0;s<=t&&r<this.children.length;r++){let o=this.children[r],l=s+o.length;if(e<=l&&t>=s){let a=n&((s<=e?1:0)|(l>=t?2:0));if(s>=e&&l<=t&&!a)i.push(o);else o.decompose(e-s,t-s,i,a)}s=l+1}}replace(e,t,i){if([e,t]=vi(this,e,t),i.lines<this.lines)for(let n=0,r=0;n<this.children.length;n++){let s=this.children[n],o=r+s.length;if(e>=r&&t<=o){let l=s.replace(e-r,t-r,i),a=this.lines-s.lines+l.lines;if(l.lines<a>>4&&l.lines>a>>6){let h=this.children.slice();return h[n]=l,new ft(h,this.length-(t-e)+i.length)}return super.replace(r,o,l)}r=o+1}return super.replace(e,t,i)}sliceString(e,t=this.length,i=`
|
|
4
4
|
`){[e,t]=vi(this,e,t);let n="";for(let r=0,s=0;r<this.children.length&&s<=t;r++){let o=this.children[r],l=s+o.length;if(s>e&&r)n+=i;if(e<l&&t>s)n+=o.sliceString(e-s,t-s,i);s=l+1}return n}flatten(e){for(let t of this.children)t.flatten(e)}scanIdentical(e,t){if(!(e instanceof ft))return 0;let i=0,[n,r,s,o]=t>0?[0,0,this.children.length,e.children.length]:[this.children.length-1,e.children.length-1,-1,-1];for(;;n+=t,r+=t){if(n==s||r==o)return i;let l=this.children[n],a=e.children[r];if(l!=a)return i+l.scanIdentical(a,t);i+=l.length+1}}static from(e,t=e.reduce((i,n)=>i+n.length+1,-1)){let i=0;for(let u of e)i+=u.lines;if(i<32){let u=[];for(let d of e)d.flatten(u);return new oe(u,t)}let n=Math.max(32,i>>5),r=n<<1,s=n>>1,o=[],l=0,a=-1,h=[];function f(u){let d;if(u.lines>r&&u instanceof ft)for(let O of u.children)f(O);else if(u.lines>s&&(l>s||!l))c(),o.push(u);else if(u instanceof oe&&l&&(d=h[h.length-1])instanceof oe&&u.lines+d.lines<=32)l+=u.lines,a+=u.length+1,h[h.length-1]=new oe(d.text.concat(u.text),d.length+1+u.length);else{if(l+u.lines>n)c();l+=u.lines,a+=u.length+1,h.push(u)}}function c(){if(l==0)return;o.push(h.length==1?h[0]:ft.from(h,a)),a=-1,l=h.length=0}for(let u of e)f(u);return c(),o.length==1?o[0]:new ft(o,t)}}_.empty=new oe([""],0);function ap(e){let t=-1;for(let i of e)t+=i.length+1;return t}function cr(e,t,i=0,n=1e9){for(let r=0,s=0,o=!0;s<e.length&&r<=n;s++){let l=e[s],a=r+l.length;if(a>=i){if(a>n)l=l.slice(0,n-r);if(r<i)l=l.slice(i-r);if(o)t[t.length-1]+=l,o=!1;else t.push(l)}r=a+1}return t}function ga(e,t,i){return cr(e,[""],t,i)}class wi{constructor(e,t=1){this.dir=t,this.done=!1,this.lineBreak=!1,this.value="",this.nodes=[e],this.offsets=[t>0?1:(e instanceof oe?e.text.length:e.children.length)<<1]}nextInner(e,t){this.done=this.lineBreak=!1;for(;;){let i=this.nodes.length-1,n=this.nodes[i],r=this.offsets[i],s=r>>1,o=n instanceof oe?n.text.length:n.children.length;if(s==(t>0?o:0)){if(i==0)return this.done=!0,this.value="",this;if(t>0)this.offsets[i-1]++;this.nodes.pop(),this.offsets.pop()}else if((r&1)==(t>0?0:1)){if(this.offsets[i]+=t,e==0)return this.lineBreak=!0,this.value=`
|
package/web/term.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* kaizen terminal
|
|
1
|
+
/* kaizen terminal fff0c686caeb0a21 -- generated by bun run build:web from web/term.ts; do not edit */
|
|
2
2
|
(()=>{var{defineProperty:fr,getOwnPropertyDescriptor:tn}=Object,sn=(e,t)=>{for(var i in t)fr(e,i,{get:t[i],enumerable:!0})},X=(e,t,i,s)=>{for(var r=s>1?void 0:s?tn(t,i):t,n=e.length-1,o;n>=0;n--)(o=e[n])&&(r=(s?o(t,i,r):o(r))||r);return s&&r&&fr(t,i,r),r},C=(e,t)=>(i,s)=>t(i,s,e),ds="Terminal input",xi={get:()=>ds,set:(e)=>ds=e},_s="Too much output to announce, navigate to rows manually to read",Ei={get:()=>_s,set:(e)=>_s=e};function rn(e){return e.replace(/\r?\n/g,"\r")}function nn(e,t){return t?"\x1B[200~"+e+"\x1B[201~":e}function on(e,t){e.clipboardData&&e.clipboardData.setData("text/plain",t.selectionText),e.preventDefault()}function an(e,t,i,s){if(e.stopPropagation(),e.clipboardData){let r=e.clipboardData.getData("text/plain");pr(r,t,i,s)}}function pr(e,t,i,s){e=rn(e),e=nn(e,i.decPrivateModes.bracketedPasteMode&&s.rawOptions.ignoreBracketedPasteMode!==!0),i.triggerDataEvent(e,!0),t.value=""}function gr(e,t,i){let s=i.getBoundingClientRect(),r=e.clientX-s.left-10,n=e.clientY-s.top-10;t.style.width="20px",t.style.height="20px",t.style.left=`${r}px`,t.style.top=`${n}px`,t.style.zIndex="1000",t.focus()}function us(e,t,i,s,r){gr(e,t,i),r&&s.rightClickSelect(e),t.value=s.selectionText,t.select()}function qe(e){return e>65535?(e-=65536,String.fromCharCode((e>>10)+55296)+String.fromCharCode(e%1024+56320)):String.fromCharCode(e)}function ii(e,t=0,i=e.length){let s="";for(let r=t;r<i;++r){let n=e[r];n>65535?(n-=65536,s+=String.fromCharCode((n>>10)+55296)+String.fromCharCode(n%1024+56320)):s+=String.fromCharCode(n)}return s}var hn=class{constructor(){this._interim=0}clear(){this._interim=0}decode(e,t){let i=e.length;if(!i)return 0;let s=0,r=0;if(this._interim){let n=e.charCodeAt(r++);56320<=n&&n<=57343?t[s++]=(this._interim-55296)*1024+n-56320+65536:(t[s++]=this._interim,t[s++]=n),this._interim=0}for(let n=r;n<i;++n){let o=e.charCodeAt(n);if(55296<=o&&o<=56319){if(++n>=i)return this._interim=o,s;let a=e.charCodeAt(n);56320<=a&&a<=57343?t[s++]=(o-55296)*1024+a-56320+65536:(t[s++]=o,t[s++]=a);continue}o!==65279&&(t[s++]=o)}return s}},ln=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(e,t){let i=e.length;if(!i)return 0;let s=0,r,n,o,a,l=0,h=0;if(this.interim[0]){let _=!1,f=this.interim[0];f&=(f&224)===192?31:(f&240)===224?15:7;let u=0,p;for(;(p=this.interim[++u]&63)&&u<4;)f<<=6,f|=p;let y=(this.interim[0]&224)===192?2:(this.interim[0]&240)===224?3:4,b=y-u;for(;h<b;){if(h>=i)return 0;if(p=e[h++],(p&192)!==128){h--,_=!0;break}else this.interim[u++]=p,f<<=6,f|=p&63}_||(y===2?f<128?h--:t[s++]=f:y===3?f<2048||f>=55296&&f<=57343||f===65279||(t[s++]=f):f<65536||f>1114111||(t[s++]=f)),this.interim.fill(0)}let d=i-4,c=h;for(;c<i;){for(;c<d&&!((r=e[c])&128)&&!((n=e[c+1])&128)&&!((o=e[c+2])&128)&&!((a=e[c+3])&128);)t[s++]=r,t[s++]=n,t[s++]=o,t[s++]=a,c+=4;if(r=e[c++],r<128)t[s++]=r;else if((r&224)===192){if(c>=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(l=(r&31)<<6|n&63,l<128){c--;continue}t[s++]=l}else if((r&240)===224){if(c>=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,s;if(o=e[c++],(o&192)!==128){c--;continue}if(l=(r&15)<<12|(n&63)<<6|o&63,l<2048||l>=55296&&l<=57343||l===65279)continue;t[s++]=l}else if((r&248)===240){if(c>=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,s;if(o=e[c++],(o&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,this.interim[2]=o,s;if(a=e[c++],(a&192)!==128){c--;continue}if(l=(r&7)<<18|(n&63)<<12|(o&63)<<6|a&63,l<65536||l>1114111)continue;t[s++]=l}}return s}},vr="",Ve=" ",Rt=class e{constructor(){this.fg=0,this.bg=0,this.extended=new Yt}static toColorRGB(t){return[t>>>16&255,t>>>8&255,t&255]}static fromColorRGB(t){return(t[0]&255)<<16|(t[1]&255)<<8|t[2]&255}clone(){let t=new e;return t.fg=this.fg,t.bg=this.bg,t.extended=this.extended.clone(),t}isInverse(){return this.fg&67108864}isBold(){return this.fg&134217728}isUnderline(){return this.hasExtendedAttrs()&&this.extended.underlineStyle!==0?1:this.fg&268435456}isBlink(){return this.fg&536870912}isInvisible(){return this.fg&1073741824}isItalic(){return this.bg&67108864}isDim(){return this.bg&134217728}isStrikethrough(){return this.fg&2147483648}isProtected(){return this.bg&536870912}isOverline(){return this.bg&1073741824}getFgColorMode(){return this.fg&50331648}getBgColorMode(){return this.bg&50331648}isFgRGB(){return(this.fg&50331648)===50331648}isBgRGB(){return(this.bg&50331648)===50331648}isFgPalette(){return(this.fg&50331648)===16777216||(this.fg&50331648)===33554432}isBgPalette(){return(this.bg&50331648)===16777216||(this.bg&50331648)===33554432}isFgDefault(){return(this.fg&50331648)===0}isBgDefault(){return(this.bg&50331648)===0}isAttributeDefault(){return this.fg===0&&this.bg===0}getFgColor(){switch(this.fg&50331648){case 16777216:case 33554432:return this.fg&255;case 50331648:return this.fg&16777215;default:return-1}}getBgColor(){switch(this.bg&50331648){case 16777216:case 33554432:return this.bg&255;case 50331648:return this.bg&16777215;default:return-1}}hasExtendedAttrs(){return this.bg&268435456}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(this.bg&268435456&&~this.extended.underlineColor)switch(this.extended.underlineColor&50331648){case 16777216:case 33554432:return this.extended.underlineColor&255;case 50331648:return this.extended.underlineColor&16777215;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return this.bg&268435456&&~this.extended.underlineColor?this.extended.underlineColor&50331648:this.getFgColorMode()}isUnderlineColorRGB(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===50331648:this.isFgRGB()}isUnderlineColorPalette(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===16777216||(this.extended.underlineColor&50331648)===33554432:this.isFgPalette()}isUnderlineColorDefault(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===0:this.isFgDefault()}getUnderlineStyle(){return this.fg&268435456?this.bg&268435456?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}},Yt=class e{constructor(t=0,i=0){this._ext=0,this._urlId=0,this._ext=t,this._urlId=i}get ext(){return this._urlId?this._ext&-469762049|this.underlineStyle<<26:this._ext}set ext(t){this._ext=t}get underlineStyle(){return this._urlId?5:(this._ext&469762048)>>26}set underlineStyle(t){this._ext&=-469762049,this._ext|=t<<26&469762048}get underlineColor(){return this._ext&67108863}set underlineColor(t){this._ext&=-67108864,this._ext|=t&67108863}get urlId(){return this._urlId}set urlId(t){this._urlId=t}get underlineVariantOffset(){let t=(this._ext&3758096384)>>29;return t<0?t^4294967288:t}set underlineVariantOffset(t){this._ext&=536870911,this._ext|=t<<29&3758096384}clone(){return new e(this._ext,this._urlId)}isEmpty(){return this.underlineStyle===0&&this._urlId===0}},ye=class e extends Rt{constructor(){super(...arguments);this.content=0,this.fg=0,this.bg=0,this.extended=new Yt,this.combinedData=""}static fromCharData(t){let i=new e;return i.setFromCharData(t),i}isCombined(){return this.content&2097152}getWidth(){return this.content>>22}getChars(){return this.content&2097152?this.combinedData:this.content&2097151?qe(this.content&2097151):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):this.content&2097151}setFromCharData(t){this.fg=t[0],this.bg=0;let i=!1;if(t[1].length>2)i=!0;else if(t[1].length===2){let s=t[1].charCodeAt(0);if(55296<=s&&s<=56319){let r=t[1].charCodeAt(1);56320<=r&&r<=57343?this.content=(s-55296)*1024+r-56320+65536|t[2]<<22:i=!0}else i=!0}else this.content=t[1].charCodeAt(0)|t[2]<<22;i&&(this.combinedData=t[1],this.content=2097152|t[2]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}},fs="di$target",Di="di$dependencies",li=new Map;function cn(e){return e[Di]||[]}function oe(e){if(li.has(e))return li.get(e);let t=function(i,s,r){if(arguments.length!==3)throw Error("@IServiceName-decorator can only be used to decorate a parameter");dn(t,i,r)};return t._id=e,li.set(e,t),t}function dn(e,t,i){t[fs]===t?t[Di].push({id:e,index:i}):(t[Di]=[{id:e,index:i}],t[fs]=t)}var ue=oe("BufferService"),mr=oe("CoreMouseService"),it=oe("CoreService"),_n=oe("CharsetService"),ts=oe("InstantiationService"),Sr=oe("LogService"),fe=oe("OptionsService"),wr=oe("OscLinkService"),un=oe("UnicodeService"),Lt=oe("DecorationService"),Bi=class{constructor(e,t,i){this._bufferService=e,this._optionsService=t,this._oscLinkService=i}provideLinks(e,t){let i=this._bufferService.buffer.lines.get(e-1);if(!i){t(void 0);return}let s=[],r=this._optionsService.rawOptions.linkHandler,n=new ye,o=i.getTrimmedLength(),a=-1,l=-1,h=!1;for(let d=0;d<o;d++)if(!(l===-1&&!i.hasContent(d))){if(i.loadCell(d,n),n.hasExtendedAttrs()&&n.extended.urlId)if(l===-1){l=d,a=n.extended.urlId;continue}else h=n.extended.urlId!==a;else l!==-1&&(h=!0);if(h||l!==-1&&d===o-1){let c=this._oscLinkService.getLinkData(a)?.uri;if(c){let _={start:{x:l+1,y:e},end:{x:d+(!h&&d===o-1?1:0),y:e}},f=!1;if(!r?.allowNonHttpProtocols)try{let u=new URL(c);["http:","https:"].includes(u.protocol)||(f=!0)}catch{f=!0}f||s.push({text:c,range:_,activate:(u,p)=>r?r.activate(u,p,_):fn(u,p),hover:(u,p)=>r?.hover?.(u,p,_),leave:(u,p)=>r?.leave?.(u,p,_)})}h=!1,n.hasExtendedAttrs()&&n.extended.urlId?(l=d,a=n.extended.urlId):(l=-1,a=-1)}}t(s)}};Bi=X([C(0,ue),C(1,fe),C(2,wr)],Bi);function fn(e,t){if(confirm(`Do you want to navigate to ${t}?
|
|
3
3
|
|
|
4
4
|
WARNING: This link could potentially be dangerous`)){let i=window.open();if(i){try{i.opener=null}catch{}i.location.href=t}else console.warn("Opening link blocked as opener could not be cleared")}}var si=oe("CharSizeService"),Oe=oe("CoreBrowserService"),is=oe("MouseService"),Ie=oe("RenderService"),pn=oe("SelectionService"),br=oe("CharacterJoinerService"),ot=oe("ThemeService"),yr=oe("LinkProviderService"),gn=class{constructor(){this.listeners=[],this.unexpectedErrorHandler=function(e){setTimeout(()=>{throw e.stack?ps.isErrorNoTelemetry(e)?new ps(e.message+`
|