kaizen-agent 1.23.0 → 1.24.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/cli/notes.test.ts +14 -0
- package/package.json +1 -1
- package/web/board.html +93 -16
- package/web/notes.html +67 -8
package/cli/notes.test.ts
CHANGED
|
@@ -215,6 +215,20 @@ test("the notebook's design tokens are the board's, line for line", () => {
|
|
|
215
215
|
expect(mascot("notes.html")).toBe(mascot("board.html")!);
|
|
216
216
|
});
|
|
217
217
|
|
|
218
|
+
test("the side menu is the board's on the notebook, line for line", () => {
|
|
219
|
+
const between = (f: string, from: string, to: string) => {
|
|
220
|
+
const s = readFileSync(join(root, "web", f), "utf8");
|
|
221
|
+
const at = s.indexOf(from);
|
|
222
|
+
expect(at).toBeGreaterThan(-1);
|
|
223
|
+
return s.slice(at, s.indexOf(to, at) + to.length);
|
|
224
|
+
};
|
|
225
|
+
for (const [from, to] of [["/* side menu:", "/* end side menu */"], ['<nav class="rail"', "</script>"]] as const)
|
|
226
|
+
expect(between("notes.html", from, to)).toBe(between("board.html", from, to));
|
|
227
|
+
const syms = (f: string) => ["board", "note", "link", "gear", "side", "sun", "moon"].map((n) => readFileSync(join(root, "web", f), "utf8").split("\n").find((l) => l.startsWith(`<symbol id="i-${n}"`)));
|
|
228
|
+
expect(syms("notes.html")).toEqual(syms("board.html"));
|
|
229
|
+
expect(syms("board.html").every(Boolean)).toBe(true);
|
|
230
|
+
});
|
|
231
|
+
|
|
218
232
|
test("web/notebook.js was built from the sources as they are (bun run build:web)", () => {
|
|
219
233
|
const first = readFileSync(OUT, "utf8").split("\n", 1)[0]!;
|
|
220
234
|
expect(first).toContain(`kaizen notebook ${sourceHash()} `);
|
package/package.json
CHANGED
package/web/board.html
CHANGED
|
@@ -13,12 +13,38 @@
|
|
|
13
13
|
:root{--r:10px;--r-s:6px;--r-pill:999px;--font:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;--mono:ui-monospace,"JetBrains Mono","Cascadia Code","SF Mono",Menlo,Consolas,monospace;--on-accent:#fff;--hover:color-mix(in srgb,var(--dim) 12%,transparent);--shade:rgb(12 22 47/.1);--shadow-1:0 1px 2px var(--shade);--shadow-2:0 2px 6px var(--shade),0 16px 40px -12px var(--shade)}
|
|
14
14
|
@media(prefers-color-scheme:dark){:root:not([data-theme=light]){--shade:rgb(0 0 0/.45)}}
|
|
15
15
|
:root[data-theme=dark]{--shade:rgb(0 0 0/.45)}
|
|
16
|
-
body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.5 var(--font);-webkit-font-smoothing:antialiased;height:100vh;height:100dvh;overflow:hidden;display:flex
|
|
16
|
+
body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.5 var(--font);-webkit-font-smoothing:antialiased;height:100vh;height:100dvh;overflow:hidden;display:flex}
|
|
17
17
|
button,input,select{font:inherit;color:inherit;background:none;border:0;padding:0}
|
|
18
18
|
button{cursor:pointer}
|
|
19
19
|
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
|
20
20
|
svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;display:block}
|
|
21
21
|
|
|
22
|
+
/* side menu: the same lines in board.html and notes.html (a test holds them equal) */
|
|
23
|
+
.rail{width:220px;flex:none;background:var(--bg-2);border-right:1px solid var(--line);display:flex;flex-direction:column;gap:2px;padding:18px 12px 14px;overflow:hidden auto;transition:width .18s}
|
|
24
|
+
.rail .top{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:0 0 18px 10px}
|
|
25
|
+
.rail .home{display:block;color:inherit;text-decoration:none}
|
|
26
|
+
.rail .top .mascot{display:none;width:36px;height:34px}
|
|
27
|
+
.rail a.it{display:flex;align-items:center;gap:10px;height:34px;padding:0 10px;border-radius:var(--r-s);font-size:13px;font-weight:500;color:var(--dim);text-decoration:none;white-space:nowrap;transition:background .12s,color .12s}
|
|
28
|
+
.rail a.it:hover{background:var(--hover);color:var(--ink)}
|
|
29
|
+
.rail a.it[aria-current=page]{background:color-mix(in srgb,var(--accent) 14%,transparent);color:var(--ink)}
|
|
30
|
+
.rail a.it[aria-current=page] svg{color:var(--accent)}
|
|
31
|
+
.rail .end{flex:1}
|
|
32
|
+
.theme{display:flex;gap:2px;padding:3px;border-radius:var(--r-s);background:var(--hover)}
|
|
33
|
+
.theme button{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;height:28px;border-radius:4px;font-size:12px;font-weight:500;color:var(--dim);transition:background .12s,color .12s}
|
|
34
|
+
.theme button svg{width:15px;height:15px}
|
|
35
|
+
.theme button:hover{color:var(--ink)}
|
|
36
|
+
.theme button[aria-pressed=true]{background:var(--bg-2);color:var(--ink);box-shadow:var(--shadow-1)}
|
|
37
|
+
[data-rail=min] .rail{width:60px;padding-inline:10px;align-items:center}
|
|
38
|
+
[data-rail=min] .rail .top{flex-direction:column;gap:12px;padding:0 0 14px}
|
|
39
|
+
[data-rail=min] .rail .top pre.mark{display:none}
|
|
40
|
+
[data-rail=min] .rail .top .mascot{display:block}
|
|
41
|
+
[data-rail=min] .rail a.it{width:40px;justify-content:center;padding:0}
|
|
42
|
+
[data-rail=min] .rail .lbl{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
|
|
43
|
+
[data-rail=min] .theme{flex-direction:column}
|
|
44
|
+
[data-rail=min] .theme button{width:34px;flex:none}
|
|
45
|
+
@media(max-width:640px){#fold{display:none}}
|
|
46
|
+
/* end side menu */
|
|
47
|
+
.app{flex:1;min-width:0;display:flex;flex-direction:column}
|
|
22
48
|
/* header */
|
|
23
49
|
.hdr{display:flex;align-items:center;gap:16px;padding:14px 28px;border-bottom:1px solid var(--line);background:var(--bg-2);flex-wrap:wrap}
|
|
24
50
|
pre.mark{margin:0;font:700 8px/1 var(--mono)}
|
|
@@ -31,6 +57,7 @@ select.sel,button.sel{cursor:pointer;appearance:none;padding-right:30px;max-widt
|
|
|
31
57
|
.agent svg{width:14px;height:14px}.agent:hover{border-color:var(--dim)}
|
|
32
58
|
.yolo{display:inline-flex;align-items:center;gap:8px;margin-right:auto;font-size:13px;color:var(--ink);cursor:pointer;white-space:nowrap}
|
|
33
59
|
.agent[aria-checked=true]{border-color:var(--accent);color:var(--ink);background:color-mix(in srgb,var(--accent) 14%,var(--bg-2))}
|
|
60
|
+
.hdr h1{margin:0;font-size:17px;font-weight:600}
|
|
34
61
|
.hdr .sel{max-width:min(320px,100%);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left}
|
|
35
62
|
/* project picker: a select-shaped button over a searchable list */
|
|
36
63
|
.combo{position:relative;max-width:100%;display:flex}
|
|
@@ -165,6 +192,13 @@ section h2 .clear{margin-left:auto;min-height:22px;padding:0 8px}
|
|
|
165
192
|
.scrim.on{display:block}
|
|
166
193
|
aside{position:fixed;top:0;right:0;bottom:0;z-index:11;width:520px;max-width:100%;background:var(--bg-2);border-left:1px solid var(--line);box-shadow:var(--shadow-2);display:flex;flex-direction:column;transform:translateX(100%);visibility:hidden;transition:transform .18s,visibility .18s}
|
|
167
194
|
aside.open{transform:none;visibility:visible}
|
|
195
|
+
/* Settings and Sources are pages: the same panel, docked into the app column, its content in a centred column */
|
|
196
|
+
body.paged #board,body.paged .ftr,body.paged .hdr,body.paged .scrim{display:none}
|
|
197
|
+
aside.docked{position:static;flex:1;min-height:0;width:auto;transform:none;visibility:visible;transition:none;box-shadow:none;border:0}
|
|
198
|
+
aside.docked header,aside.docked .body{padding-inline:max(28px,calc((100% - 760px)/2))}
|
|
199
|
+
aside.docked header{border-bottom:0;padding-top:28px;padding-bottom:12px}
|
|
200
|
+
aside.docked header b{font-size:20px}
|
|
201
|
+
aside.docked #pclose{display:none}
|
|
168
202
|
aside header{display:flex;align-items:flex-start;gap:12px;padding:20px 24px 16px;border-bottom:1px solid var(--line)}
|
|
169
203
|
aside header .t{flex:1;min-width:0}
|
|
170
204
|
aside header b{display:block;font-size:17px;font-weight:600;line-height:1.35}
|
|
@@ -285,7 +319,7 @@ aside .foot .more .menu-pop{left:0;right:auto;top:auto;bottom:calc(100% + 4px)}.
|
|
|
285
319
|
.src-add-box{background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r);padding:16px;margin-top:16px;box-shadow:var(--shadow-1)}
|
|
286
320
|
.src-add-box h3{margin:0 0 12px;font-size:13px;font-weight:600;color:var(--ink);display:flex;align-items:center;gap:6px}
|
|
287
321
|
.src-add-box h3 svg{width:16px;height:16px}
|
|
288
|
-
@media(max-width:900px){main{grid-template-columns:1fr;padding-inline:16px;gap:8px}.hdr,.ftr{padding-inline:16px}.more{display:grid}.card:hover .age{display:inline}.toast{left:16px;max-width:calc(100% - 32px)}}
|
|
322
|
+
@media(max-width:900px){main{grid-template-columns:1fr;padding-inline:16px;gap:8px}.hdr,.ftr,aside.docked header,aside.docked .body{padding-inline:16px}.more{display:grid}.card:hover .age{display:inline}.toast{left:16px;max-width:calc(100% - 32px)}}
|
|
289
323
|
@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}
|
|
290
324
|
</style>
|
|
291
325
|
</head>
|
|
@@ -310,6 +344,10 @@ aside .foot .more .menu-pop{left:0;right:auto;top:auto;bottom:calc(100% + 4px)}.
|
|
|
310
344
|
<symbol id="i-doc" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8"/></symbol>
|
|
311
345
|
<symbol id="i-refresh" viewBox="0 0 24 24"><path d="M21 3v5h-5M3 21v-5h5M20.5 13A9 9 0 0 1 5.7 18.3L3 16M3.5 11A9 9 0 0 1 18.3 5.7L21 8"/></symbol>
|
|
312
346
|
<symbol id="i-ext" viewBox="0 0 24 24"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3"/></symbol>
|
|
347
|
+
<symbol id="i-board" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M9 4v16M15 4v16"/></symbol>
|
|
348
|
+
<symbol id="i-side" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M9 4v16"/></symbol>
|
|
349
|
+
<symbol id="i-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></symbol>
|
|
350
|
+
<symbol id="i-moon" viewBox="0 0 24 24"><path d="M20 14.5A8 8 0 1 1 9.5 4a6.5 6.5 0 0 0 10.5 10.5z"/></symbol>
|
|
313
351
|
<symbol id="s-idea" viewBox="0 0 16 16"><circle cx="8" cy="8" r="6" stroke-dasharray="2 2.5"/></symbol>
|
|
314
352
|
<symbol id="s-start" viewBox="0 0 16 16"><circle cx="8" cy="8" r="6" stroke-dasharray="2 2.5"/><path d="M6.5 5.5v5l4-2.5z" fill="currentColor" stroke="none"/></symbol>
|
|
315
353
|
<symbol id="s-run" viewBox="0 0 16 16"><circle cx="8" cy="8" r="6"/><path d="M8 2a6 6 0 0 1 0 12z" fill="currentColor" stroke="none"/></symbol>
|
|
@@ -329,17 +367,40 @@ aside .foot .more .menu-pop{left:0;right:auto;top:auto;bottom:calc(100% + 4px)}.
|
|
|
329
367
|
<symbol id="c-git" viewBox="0 0 16 16"><circle cx="4.5" cy="3.5" r="1.5"/><circle cx="4.5" cy="12.5" r="1.5"/><circle cx="11.5" cy="5.5" r="1.5"/><path d="M4.5 5v6M11.5 7c0 2.5-7 1.5-7 4"/></symbol>
|
|
330
368
|
</defs></svg>
|
|
331
369
|
|
|
332
|
-
<
|
|
333
|
-
<
|
|
370
|
+
<nav class="rail" aria-label="Main">
|
|
371
|
+
<div class="top"><a class="home" href="/" title="Board" aria-label="kaizen board"><pre class="mark"><span>█ █ ▄▀█ █ ▀▀█ █▀▀ █▄ █</span>
|
|
334
372
|
<span>█▀▄ █▀█ █ ▄▀ ██▄ █ ▀█</span>
|
|
335
|
-
<span>█ █ █ █ █ █▄▄ █▄▄ █ █</span></pre>
|
|
373
|
+
<span>█ █ █ █ █ █▄▄ █▄▄ █ █</span></pre><span class="mascot"></span></a><button class="ib" id="fold" aria-label="Collapse menu" title="Collapse menu"><svg><use href="#i-side"/></svg></button></div>
|
|
374
|
+
<a class="it" href="/" data-view="board" title="Board"><svg><use href="#i-board"/></svg><span class="lbl">Board</span></a>
|
|
375
|
+
<a class="it" href="/notebook" data-view="notes" title="Notes"><svg><use href="#i-note"/></svg><span class="lbl">Notes</span></a>
|
|
376
|
+
<a class="it" href="/?view=sources" data-view="sources" title="Request sources"><svg><use href="#i-link"/></svg><span class="lbl">Sources</span></a>
|
|
377
|
+
<a class="it" href="/?view=settings" data-view="settings" title="Settings"><svg><use href="#i-gear"/></svg><span class="lbl">Settings</span></a>
|
|
378
|
+
<span class="end"></span>
|
|
379
|
+
<div class="theme" role="group" aria-label="Theme"><button data-t="light" title="Light theme"><svg><use href="#i-sun"/></svg><span class="lbl">Light</span></button><button data-t="dark" title="Dark theme"><svg><use href="#i-moon"/></svg><span class="lbl">Dark</span></button></div>
|
|
380
|
+
</nav>
|
|
381
|
+
<script>
|
|
382
|
+
// Side menu: theme and collapse, the same lines in board.html and notes.html. Runs before
|
|
383
|
+
// the page draws, so a stored theme or a collapsed menu never flashes the default first.
|
|
384
|
+
(()=>{const r=document.documentElement,wide=matchMedia("(max-width:1100px)"),phone=matchMedia("(max-width:640px)"),os=matchMedia("(prefers-color-scheme: dark)");
|
|
385
|
+
// The toggle's choice is kept per browser; with none, the width decides. A phone always gets icons.
|
|
386
|
+
let fold=null;try{fold=localStorage.getItem("kz-rail")}catch(e){}
|
|
387
|
+
const rail=()=>{const m=phone.matches||(fold?fold==="min":wide.matches),b=document.getElementById("fold");r.dataset.rail=m?"min":"full";b.title=m?"Expand menu":"Collapse menu";b.setAttribute("aria-label",b.title);b.setAttribute("aria-expanded",!m)};
|
|
388
|
+
wide.addEventListener("change",rail);phone.addEventListener("change",rail);rail();
|
|
389
|
+
document.getElementById("fold").onclick=()=>{fold=r.dataset.rail==="min"?"full":"min";try{localStorage.setItem("kz-rail",fold)}catch(e){}rail()};
|
|
390
|
+
// Theme: light, dark, or unset for system, in kz-theme. The switch shows what is on screen.
|
|
391
|
+
const paint=()=>{const d=r.dataset.theme?r.dataset.theme==="dark":os.matches;document.querySelectorAll(".theme button").forEach(b=>b.setAttribute("aria-pressed",(b.dataset.t==="dark")===d))};
|
|
392
|
+
window.setTheme=t=>{if(t==="system")delete r.dataset.theme;else r.dataset.theme=t;try{t==="system"?localStorage.removeItem("kz-theme"):localStorage.setItem("kz-theme",t)}catch(e){}paint();dispatchEvent(new Event("kz-theme"))};
|
|
393
|
+
try{const t=localStorage.getItem("kz-theme");if(t)r.dataset.theme=t}catch(e){}
|
|
394
|
+
os.addEventListener("change",paint);paint();
|
|
395
|
+
document.querySelectorAll(".theme button").forEach(b=>b.onclick=()=>setTheme(b.dataset.t))})();
|
|
396
|
+
</script>
|
|
397
|
+
<div class="app">
|
|
398
|
+
<div class="hdr">
|
|
399
|
+
<h1>Board</h1>
|
|
336
400
|
<div class="combo" id="combo"><button class="sel" id="proj" aria-label="project" aria-haspopup="listbox" aria-expanded="false"></button><div class="menu-pop" id="projpop" hidden><input class="sel" id="projq" aria-label="Search projects" placeholder="Search projects…" spellcheck="false" autocomplete="off"><div id="projs" role="listbox"></div></div></div>
|
|
337
401
|
<div class="combo" id="showc" hidden><button class="sel" id="show" aria-label="Projects shown" aria-haspopup="true" aria-expanded="false"></button><div class="menu-pop" id="showpop" hidden><input class="sel" id="showq" aria-label="Search projects" placeholder="Search projects…" spellcheck="false" autocomplete="off"><div id="shows"></div><button id="showall">Show all</button></div></div>
|
|
338
402
|
<span class="sp"></span>
|
|
339
403
|
<button class="btn pri" id="newtop">New idea</button>
|
|
340
|
-
<a class="ib" href="/notebook" title="Notebook" aria-label="Notebook"><svg><use href="#i-note"/></svg></a>
|
|
341
|
-
<button class="ib" id="sources" title="Request sources" aria-label="Request sources"><svg><use href="#i-link"/></svg></button>
|
|
342
|
-
<button class="ib" id="settings" title="Settings" aria-label="Settings"><svg><use href="#i-gear"/></svg></button>
|
|
343
404
|
</div>
|
|
344
405
|
<main id="board" aria-live="polite"></main>
|
|
345
406
|
|
|
@@ -352,6 +413,7 @@ aside .foot .more .menu-pop{left:0;right:auto;top:auto;bottom:calc(100% + 4px)}.
|
|
|
352
413
|
<div class="body md" id="pbody"></div>
|
|
353
414
|
<div class="foot" id="pfoot"></div>
|
|
354
415
|
</aside>
|
|
416
|
+
</div>
|
|
355
417
|
<div class="toast" id="toast" role="status"></div>
|
|
356
418
|
<div class="help" id="help"><div>
|
|
357
419
|
<p>Optional. Everything here is also a button.</p>
|
|
@@ -660,6 +722,7 @@ const TABS=[["Request","request"],["Notes","notes"],["Plan","plan"],["Backlog","
|
|
|
660
722
|
const tabOf=k=>TABS.findIndex(t=>t[1]===k);
|
|
661
723
|
// keepTab: a link already chose the tab.
|
|
662
724
|
function open(c,keepTab){
|
|
725
|
+
if(view!=="board")go("board",true);
|
|
663
726
|
stopDictation(true); // the panel body is about to be replaced
|
|
664
727
|
fixOff.clear();openKey=key(c);$("panel").classList.add("open");$("scrim").classList.add("on");$("ptitle").textContent=c.kind==="idea"?brief(c.text).head:c.text;render();
|
|
665
728
|
if(c.kind==="idea"){runDoc=null;$("psub").textContent="Idea · "+c.where;$("ptabs").innerHTML="";chat(c,c.notes,c.archived||c.status!=="idea");
|
|
@@ -847,7 +910,7 @@ async function commitPush(c,message,b){b.disabled=true;b.dataset.busy=1;b.textCo
|
|
|
847
910
|
// A refused message comes back for another try, beside the current file list.
|
|
848
911
|
if(!r.ok&&message&&openKey===key(c)&&runDoc.git.files&&runDoc.git.files.length)confirmCommit(c,message)}
|
|
849
912
|
async function toggleArchive(c,archived){const r=await post("/archive",c.kind==="idea"?{dir:c.state,text:c.text,archived}:{dir:c.state,id:c.id,archived});if(r.ok)toast(archived?"Archived":"Restored");close();load()}
|
|
850
|
-
function close(){const k=openKey;stopDictation(true);openKey=null;runDoc=null;$("panel").classList.remove("open");$("scrim").classList.remove("on");render();if(k)cardEl(k)?.focus({preventScroll:true})}
|
|
913
|
+
function close(){if(view!=="board")return;const k=openKey;stopDictation(true);openKey=null;runDoc=null;$("panel").classList.remove("open");$("scrim").classList.remove("on");render();if(k)cardEl(k)?.focus({preventScroll:true})}
|
|
851
914
|
$("pclose").onclick=close;$("scrim").onclick=close;
|
|
852
915
|
// An idea is one inbox line, so a structured request arrives as one wall:
|
|
853
916
|
// "Fix x Problem: … Scope: … Acceptance criteria: (1) … (2) …". Split it, for display
|
|
@@ -932,13 +995,27 @@ async function openSettings(){
|
|
|
932
995
|
// Esc clears a search; with nothing to clear it closes the panel, as it does outside the field.
|
|
933
996
|
$("sq").onkeydown=e=>{if(e.key!=="Escape")return;e.preventDefault();if(setQ){setQ="";e.target.value="";filter()}else close()};
|
|
934
997
|
filter();if(hadFocus){const f=$("sq");f.focus();f.setSelectionRange(f.value.length,f.value.length)}
|
|
935
|
-
const setTheme=t=>{if(t==="system")delete document.documentElement.dataset.theme;else document.documentElement.dataset.theme=t;try{t==="system"?localStorage.removeItem("kz-theme"):localStorage.setItem("kz-theme",t)}catch(e){}};
|
|
936
998
|
$("th").onchange=e=>setTheme(e.target.value);
|
|
937
999
|
$("nt").onclick=()=>{askNotify();setTimeout(openSettings,300)};
|
|
938
1000
|
if(cfg)cfg.rows.forEach(r=>{const el=$("s-"+r.key.replace(/\./g,"-"));const save=async v=>{const f=document.activeElement?.id;const j=await post("/settings",{key:r.key,value:v});if(j.ok)toast("Saved");await openSettings();if(f?.startsWith("s-preset-"))$(f)?.focus()};
|
|
939
1001
|
if(el.tagName==="SELECT"||el.tagName==="INPUT")el.onchange=async()=>{await save(el.value.trim());if(el.tagName==="INPUT")load()};else el.querySelectorAll("[data-v]").forEach(b=>b.onclick=()=>save(b.dataset.v));if(el.matches(".chk"))el.onclick=()=>save(el.dataset.v);
|
|
940
1002
|
// The system's own folder dialog, opened by the board: a page cannot read a real path.
|
|
941
1003
|
if(r.folder)$(el.id+"-pick").onclick=async()=>{const j=await post("/pick-folder",{});if(j.path){await save(j.path);load()}}})}
|
|
1004
|
+
// ---- views: the board, or Settings and Sources as pages in the docked panel, each with its own address
|
|
1005
|
+
let view="board";
|
|
1006
|
+
const markView=v=>document.querySelectorAll(".rail a.it").forEach(a=>a.dataset.view===v?a.setAttribute("aria-current","page"):a.removeAttribute("aria-current"));
|
|
1007
|
+
function go(v,push){if(!["settings","sources"].includes(v))v="board";const was=view;view=v;markView(v);document.body.classList.toggle("paged",v!=="board");
|
|
1008
|
+
if(push&&v!==was)history.pushState(null,"",v==="board"?location.pathname:"?view="+v);
|
|
1009
|
+
const p=$("panel");
|
|
1010
|
+
if(v==="board"){if(was==="board")return;
|
|
1011
|
+
// Undocked without the slide: it was a page, not a drawer leaving.
|
|
1012
|
+
p.style.transition="none";p.classList.remove("open","docked");p.offsetWidth;p.style.transition="";close();return}
|
|
1013
|
+
p.classList.add("docked");if(v==="settings")openSettings();else openSources()}
|
|
1014
|
+
document.querySelectorAll(".rail a[data-view]").forEach(a=>{if(a.dataset.view==="notes")return;a.onclick=e=>{if(e.ctrlKey||e.metaKey||e.shiftKey||e.button)return;e.preventDefault();if(a.dataset.view==="settings")setQ="";go(a.dataset.view,true)}});
|
|
1015
|
+
document.querySelector(".rail .home").onclick=e=>{if(e.ctrlKey||e.metaKey||e.shiftKey||e.button)return;e.preventDefault();go("board",true)};
|
|
1016
|
+
addEventListener("popstate",()=>go(new URLSearchParams(location.search).get("view")));
|
|
1017
|
+
// The menu's theme switch while Settings shows: its Theme row follows.
|
|
1018
|
+
addEventListener("kz-theme",()=>{if(view==="settings")openSettings()});
|
|
942
1019
|
// ---- request sources: links kaizen reads for new ideas. Same panel as settings.
|
|
943
1020
|
const SRC="Request sources";
|
|
944
1021
|
const srcOpen=()=>$("panel").classList.contains("open")&&$("ptitle").textContent===SRC;
|
|
@@ -967,11 +1044,11 @@ async function openSources(){
|
|
|
967
1044
|
const statusHtml=!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>${esc(s.last.note)}</span><span class="hint" style="margin:0">${ago(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: ${esc(s.last.note)}</span><span class="hint" style="margin:0">${ago(s.last.at)}</span>`;
|
|
968
1045
|
return `<div class="src-card" id="src-card-${i}"><div class="src-hd"><div class="src-info"><span class="src-ico" title="${st}">${ico(st==="sheet"?"sheet":st==="doc"?"doc":"link")}</span><span class="src-title" title="${esc(title)}">${esc(title)}</span></div><div class="src-acts"><button class="btn ghost sm" id="src-chk-${i}" data-chk="${i}" aria-label="Check ${esc(title)} now" title="Check this source now">${ico("refresh")} Check</button><button class="btn ghost danger sm" id="src-rm-${i}" data-rm="${i}" aria-label="Remove ${esc(title)}" title="Remove source">${ico("trash")}</button></div></div><div class="src-url"><a href="${esc(s.url)}" target="_blank" rel="noopener noreferrer" title="${esc(s.url)}"><span>${esc(s.url)}</span>${ico("ext")}</a></div><div class="src-status">${statusHtml}</div><div class="src-dest"><span>Ideas go to</span><select class="sel" id="src-mv-${i}" data-mv="${i}" data-k="${esc(s.dir+" "+s.url)}" aria-label="Project of ${esc(title)}">${opts(s.dir,s.project)}</select><button class="btn sm" id="src-mb-${i}" aria-label="Move ${esc(title)} to the chosen project" disabled>Move</button></div></div>`;
|
|
969
1046
|
}).join("")+(d&&d.failed||[]).map(f=>`<p style="color:var(--dim)">The sources of ${esc(f)} could not be read.</p>`).join("");
|
|
970
|
-
const emptyState=`<div class="src-empty"><div class="src-empty-ico">${ico("link")}</div><h4>No request sources yet</h4><p>Connect shared links to gather requests into your Ideas backlog. Nothing runs automatically until you start it.</p><div class="src-empty-grid"><div class="src-empty-item"><strong
|
|
1047
|
+
const emptyState=`<div class="src-empty"><div class="src-empty-ico">${ico("link")}</div><h4>No request sources yet</h4><p>Connect shared links to gather requests into your Ideas backlog. 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></div>`;
|
|
971
1048
|
const addBox=`<div class="src-add-box"><h3>${ico("plus")} Add a source</h3><div style="display:grid;gap:8px"><div><label for="src-url" class="hint" style="display:block;margin-bottom:4px;font-weight:500;color:var(--ink)">Link</label><input class="sel" id="src-url" aria-label="Link" placeholder="https://docs.google.com/spreadsheets/d/… or public URL" spellcheck="false" autocomplete="off" style="width:100%;max-width:none"></div><div style="display:grid;grid-template-columns:1fr 1fr;gap:8px"><div><label for="src-label" class="hint" style="display:block;margin-bottom:4px">Label (optional)</label><input class="sel" id="src-label" aria-label="Label (optional)" placeholder="e.g. Feedback Q4" style="width:100%;max-width:none"></div><div><label for="src-dir" class="hint" style="display:block;margin-bottom:4px">Target project</label><select class="sel" id="src-dir" aria-label="Project this source belongs to" style="width:100%;max-width:none">${dir?"":`<option value="">Choose project…</option>`}${opts(dir)}</select></div></div><div style="display:flex;align-items:center;justify-content:space-between;margin-top:6px"><button class="btn pri" id="src-add">Add source</button><span class="hint" style="font-size:11px;color:var(--dim);margin:0">Public links only. For private docs, run /kaizen gather.</span></div></div></div>`;
|
|
972
1049
|
$("pbody").innerHTML=(d&&d.error?`<p style="color:var(--dim)">${esc(d.error)}</p>`:!d?`<p style="color:var(--dim)">Sources could not be read.</p>`:topBar+(list.length?`<div class="src-list">${cards}</div>`:emptyState))+addBox;
|
|
973
1050
|
$("src-url").value=typed[0];$("src-label").value=typed[1];if(typed[3]!==undefined&&$("src-dir"))$("src-dir").value=typed[3];
|
|
974
|
-
if($("src-to-settings"))$("src-to-settings").onclick=()=>{setQ="sources";
|
|
1051
|
+
if($("src-to-settings"))$("src-to-settings").onclick=()=>{setQ="sources";go("settings",true)};
|
|
975
1052
|
const act=async(body,done)=>{const j=await post("/sources",body);if(j.ok){done(j);load()}if(srcOpen())openSources()};
|
|
976
1053
|
$("pbody").querySelectorAll("[data-mv]").forEach(m=>{const s=list[+m.dataset.mv],b=$("src-mb-"+m.dataset.mv),to=picked.get(m.dataset.k);
|
|
977
1054
|
if(to&&[...m.options].some(o=>o.value===to))m.value=to;
|
|
@@ -1048,8 +1125,6 @@ $("shows").onchange=e=>{const i=e.target.dataset.i,d=SP[i]?.dir;if(!d)return;e.t
|
|
|
1048
1125
|
$("showall").onclick=()=>{shown.clear();setShown()};
|
|
1049
1126
|
document.addEventListener("click",e=>{if(!e.target.closest("#showc")){$("showpop").hidden=true;$("show").setAttribute("aria-expanded",false)}});
|
|
1050
1127
|
$("newtop").onclick=()=>{mode="new";render();$("board").scrollTo(0,0)};
|
|
1051
|
-
$("settings").onclick=()=>{setQ="";openSettings()};$("sources").onclick=openSources;
|
|
1052
|
-
try{const t=localStorage.getItem("kz-theme");if(t)document.documentElement.dataset.theme=t}catch(e){}
|
|
1053
1128
|
$("kbd").onclick=()=>$("help").classList.add("on");$("help").onclick=()=>$("help").classList.remove("on");
|
|
1054
1129
|
document.addEventListener("click",()=>{if(menuKey||clearMenu){menuKey=null;clearMenu=false;render()}});
|
|
1055
1130
|
document.onkeydown=e=>{
|
|
@@ -1057,7 +1132,7 @@ document.onkeydown=e=>{
|
|
|
1057
1132
|
if(e.key==="?"){$("help").classList.toggle("on");return}
|
|
1058
1133
|
if(e.key==="/"&&$("sq")&&$("panel").classList.contains("open")){e.preventDefault();$("sq").focus();return}
|
|
1059
1134
|
if(e.key==="Escape"){$("help").classList.remove("on");if(mode){mode=null;render()}else close();return}
|
|
1060
|
-
if(e.key==="n"){mode="new";render()}
|
|
1135
|
+
if(e.key==="n"){if(view!=="board")go("board",true);mode="new";render()}
|
|
1061
1136
|
if(e.key==="a"){all=!all;keep(all?null:S.dir);load()}
|
|
1062
1137
|
const mv={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];
|
|
1063
1138
|
// In a run's panel: left/right between its tabs, up/down scroll it. Other panels keep the browser's arrows.
|
|
@@ -1077,10 +1152,12 @@ addEventListener("focus",()=>load(true));
|
|
|
1077
1152
|
function listen(){const es=new EventSource("/events");es.onmessage=ev=>{if(ev.data==="changed"){load(true);if(srcOpen()&&!$("src-go")?.disabled&&!$("src-add")?.disabled)openSources()}else if(ev.data==="reload")location.reload();else if(ev.data.startsWith("hello ")){const b=ev.data.slice(6);if(boot&&b!==boot)location.reload();boot=b}};es.onerror=()=>{es.close();setTimeout(listen,3000)}}
|
|
1078
1153
|
// Back to the project picked last time, unless the URL says which view it wants. A
|
|
1079
1154
|
// plain fetch: a project that is gone should fall back to all, not raise a toast.
|
|
1155
|
+
// A page named in the address shows from the first paint; it is filled once the board has loaded.
|
|
1156
|
+
const v0=new URLSearchParams(location.search).get("view");markView(["settings","sources"].includes(v0)?v0:"board");document.body.classList.toggle("paged",["settings","sources"].includes(v0));
|
|
1080
1157
|
(async()=>{let d;try{d=localStorage.getItem("kz-proj")}catch(e){}
|
|
1081
1158
|
if(d&&!urlAll)try{if((await fetch("/cd",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({dir:d})})).ok)all=false;else keep()}catch(e){}
|
|
1082
1159
|
try{const f=JSON.parse(sessionStorage.getItem("kz-form"));if(f)[mode,modeKey]=f}catch(e){}
|
|
1083
|
-
load().then(deep);listen()})();
|
|
1160
|
+
load().then(()=>{go(v0);deep()});listen()})();
|
|
1084
1161
|
</script>
|
|
1085
1162
|
</body>
|
|
1086
1163
|
</html>
|
package/web/notes.html
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
:root{--r:10px;--r-s:6px;--r-pill:999px;--font:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;--mono:ui-monospace,"JetBrains Mono","Cascadia Code","SF Mono",Menlo,Consolas,monospace;--on-accent:#fff;--hover:color-mix(in srgb,var(--dim) 12%,transparent);--shade:rgb(12 22 47/.1);--shadow-1:0 1px 2px var(--shade);--shadow-2:0 2px 6px var(--shade),0 16px 40px -12px var(--shade)}
|
|
15
15
|
@media(prefers-color-scheme:dark){:root:not([data-theme=light]){--shade:rgb(0 0 0/.45)}}
|
|
16
16
|
:root[data-theme=dark]{--shade:rgb(0 0 0/.45)}
|
|
17
|
-
body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.5 var(--font);-webkit-font-smoothing:antialiased;height:100vh;height:100dvh;overflow:hidden;display:flex
|
|
17
|
+
body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.5 var(--font);-webkit-font-smoothing:antialiased;height:100vh;height:100dvh;overflow:hidden;display:flex}
|
|
18
18
|
button,input,select{font:inherit;color:inherit;background:none;border:0;padding:0}
|
|
19
19
|
button{cursor:pointer}
|
|
20
20
|
a{color:inherit;text-decoration:none}
|
|
@@ -22,11 +22,37 @@ a{color:inherit;text-decoration:none}
|
|
|
22
22
|
svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;display:block}
|
|
23
23
|
[hidden]{display:none!important}
|
|
24
24
|
|
|
25
|
-
/*
|
|
25
|
+
/* side menu: the same lines in board.html and notes.html (a test holds them equal) */
|
|
26
|
+
.rail{width:220px;flex:none;background:var(--bg-2);border-right:1px solid var(--line);display:flex;flex-direction:column;gap:2px;padding:18px 12px 14px;overflow:hidden auto;transition:width .18s}
|
|
27
|
+
.rail .top{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:0 0 18px 10px}
|
|
28
|
+
.rail .home{display:block;color:inherit;text-decoration:none}
|
|
29
|
+
.rail .top .mascot{display:none;width:36px;height:34px}
|
|
30
|
+
.rail a.it{display:flex;align-items:center;gap:10px;height:34px;padding:0 10px;border-radius:var(--r-s);font-size:13px;font-weight:500;color:var(--dim);text-decoration:none;white-space:nowrap;transition:background .12s,color .12s}
|
|
31
|
+
.rail a.it:hover{background:var(--hover);color:var(--ink)}
|
|
32
|
+
.rail a.it[aria-current=page]{background:color-mix(in srgb,var(--accent) 14%,transparent);color:var(--ink)}
|
|
33
|
+
.rail a.it[aria-current=page] svg{color:var(--accent)}
|
|
34
|
+
.rail .end{flex:1}
|
|
35
|
+
.theme{display:flex;gap:2px;padding:3px;border-radius:var(--r-s);background:var(--hover)}
|
|
36
|
+
.theme button{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;height:28px;border-radius:4px;font-size:12px;font-weight:500;color:var(--dim);transition:background .12s,color .12s}
|
|
37
|
+
.theme button svg{width:15px;height:15px}
|
|
38
|
+
.theme button:hover{color:var(--ink)}
|
|
39
|
+
.theme button[aria-pressed=true]{background:var(--bg-2);color:var(--ink);box-shadow:var(--shadow-1)}
|
|
40
|
+
[data-rail=min] .rail{width:60px;padding-inline:10px;align-items:center}
|
|
41
|
+
[data-rail=min] .rail .top{flex-direction:column;gap:12px;padding:0 0 14px}
|
|
42
|
+
[data-rail=min] .rail .top pre.mark{display:none}
|
|
43
|
+
[data-rail=min] .rail .top .mascot{display:block}
|
|
44
|
+
[data-rail=min] .rail a.it{width:40px;justify-content:center;padding:0}
|
|
45
|
+
[data-rail=min] .rail .lbl{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
|
|
46
|
+
[data-rail=min] .theme{flex-direction:column}
|
|
47
|
+
[data-rail=min] .theme button{width:34px;flex:none}
|
|
48
|
+
@media(max-width:640px){#fold{display:none}}
|
|
49
|
+
/* end side menu */
|
|
50
|
+
.app{flex:1;min-width:0;display:flex;flex-direction:column}
|
|
51
|
+
/* header: the board's */
|
|
26
52
|
.hdr{display:flex;align-items:center;gap:16px;padding:14px 28px;border-bottom:1px solid var(--line);background:var(--bg-2);flex-wrap:wrap}
|
|
27
53
|
pre.mark{margin:0;font:700 8px/1 var(--mono)}
|
|
28
54
|
pre.mark span:nth-child(1){color:var(--ink-hi)}pre.mark span:nth-child(2){color:color-mix(in srgb,var(--ink-hi),var(--accent))}pre.mark span:nth-child(3){color:var(--accent)}
|
|
29
|
-
.hdr h1{margin:0;font-size:
|
|
55
|
+
.hdr h1{margin:0;font-size:17px;font-weight:600}
|
|
30
56
|
.sel{border:1px solid var(--line);border-radius:var(--r-s);padding:6px 12px;background-color:var(--bg-2);color:var(--ink);font-size:13px}
|
|
31
57
|
select.sel{cursor:pointer;appearance:none;padding-right:30px;max-width:min(320px,100%);text-overflow:ellipsis;background-image:linear-gradient(45deg,transparent 50%,var(--dim) 50%),linear-gradient(135deg,var(--dim) 50%,transparent 50%);background-position:calc(100% - 16px) 50%,calc(100% - 11px) 50%;background-size:5px 5px;background-repeat:no-repeat}
|
|
32
58
|
.sel:hover{border-color:var(--dim)}
|
|
@@ -78,7 +104,7 @@ select.sel{cursor:pointer;appearance:none;padding-right:30px;max-width:min(320px
|
|
|
78
104
|
.main.dealt{position:relative;z-index:6;box-shadow:var(--shadow-2);will-change:transform}
|
|
79
105
|
.wrap{overflow:hidden}
|
|
80
106
|
/* Full screen: the open note alone, header and sidebar gone. */
|
|
81
|
-
.full .hdr,.full .side,.full #side{display:none!important}
|
|
107
|
+
.full .rail,.full .hdr,.full .side,.full #side{display:none!important}
|
|
82
108
|
.full .wrap{grid-template-columns:1fr}
|
|
83
109
|
#title{width:100%;margin:0 0 4px -6px;padding:2px 6px;border:1px solid transparent;border-radius:var(--r-s);font-size:26px;font-weight:600;line-height:1.3;color:var(--ink)}
|
|
84
110
|
#title:hover{border-color:var(--line)}
|
|
@@ -166,6 +192,13 @@ select.sel{cursor:pointer;appearance:none;padding-right:30px;max-width:min(320px
|
|
|
166
192
|
<symbol id="s-wait" viewBox="0 0 16 16"><circle cx="8" cy="8" r="6"/><path d="M8 5v3.5M8 11v.1"/></symbol>
|
|
167
193
|
<symbol id="s-done" viewBox="0 0 16 16"><circle cx="8" cy="8" r="6" fill="currentColor" stroke="none"/><path d="M5.5 8l1.8 1.8L10.8 6" stroke="var(--bg-2)"/></symbol>
|
|
168
194
|
<symbol id="s-aband" viewBox="0 0 16 16"><circle cx="8" cy="8" r="6"/><path d="M5.5 5.5l5 5M10.5 5.5l-5 5"/></symbol>
|
|
195
|
+
<symbol id="i-gear" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></symbol>
|
|
196
|
+
<symbol id="i-link" viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 0 7.1.4l3-3a5 5 0 0 0-7.1-7.1l-1.7 1.7M14 11a5 5 0 0 0-7.1-.4l-3 3a5 5 0 0 0 7.1 7.1l1.7-1.7"/></symbol>
|
|
197
|
+
<symbol id="i-note" viewBox="0 0 24 24"><path d="M6 3h9l4 4v14H6z"/><path d="M14 3v5h5M9 13h6M9 17h6"/></symbol>
|
|
198
|
+
<symbol id="i-board" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M9 4v16M15 4v16"/></symbol>
|
|
199
|
+
<symbol id="i-side" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M9 4v16"/></symbol>
|
|
200
|
+
<symbol id="i-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></symbol>
|
|
201
|
+
<symbol id="i-moon" viewBox="0 0 24 24"><path d="M20 14.5A8 8 0 1 1 9.5 4a6.5 6.5 0 0 0 10.5 10.5z"/></symbol>
|
|
169
202
|
<symbol id="i-trash" viewBox="0 0 24 24"><path d="M3 6h18M8 6V4h8v2M6 6l1 14h10l1-14M10 11v6M14 11v6"/></symbol>
|
|
170
203
|
<symbol id="i-folder" viewBox="0 0 24 24"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></symbol>
|
|
171
204
|
<symbol id="i-chev" viewBox="0 0 24 24"><path d="M9 6l6 6-6 6"/></symbol>
|
|
@@ -176,15 +209,39 @@ select.sel{cursor:pointer;appearance:none;padding-right:30px;max-width:min(320px
|
|
|
176
209
|
<symbol id="i-list" viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h16"/></symbol>
|
|
177
210
|
</defs></svg>
|
|
178
211
|
|
|
212
|
+
<nav class="rail" aria-label="Main">
|
|
213
|
+
<div class="top"><a class="home" href="/" title="Board" aria-label="kaizen board"><pre class="mark"><span>█ █ ▄▀█ █ ▀▀█ █▀▀ █▄ █</span>
|
|
214
|
+
<span>█▀▄ █▀█ █ ▄▀ ██▄ █ ▀█</span>
|
|
215
|
+
<span>█ █ █ █ █ █▄▄ █▄▄ █ █</span></pre><span class="mascot"></span></a><button class="ib" id="fold" aria-label="Collapse menu" title="Collapse menu"><svg><use href="#i-side"/></svg></button></div>
|
|
216
|
+
<a class="it" href="/" data-view="board" title="Board"><svg><use href="#i-board"/></svg><span class="lbl">Board</span></a>
|
|
217
|
+
<a class="it" href="/notebook" data-view="notes" title="Notes"><svg><use href="#i-note"/></svg><span class="lbl">Notes</span></a>
|
|
218
|
+
<a class="it" href="/?view=sources" data-view="sources" title="Request sources"><svg><use href="#i-link"/></svg><span class="lbl">Sources</span></a>
|
|
219
|
+
<a class="it" href="/?view=settings" data-view="settings" title="Settings"><svg><use href="#i-gear"/></svg><span class="lbl">Settings</span></a>
|
|
220
|
+
<span class="end"></span>
|
|
221
|
+
<div class="theme" role="group" aria-label="Theme"><button data-t="light" title="Light theme"><svg><use href="#i-sun"/></svg><span class="lbl">Light</span></button><button data-t="dark" title="Dark theme"><svg><use href="#i-moon"/></svg><span class="lbl">Dark</span></button></div>
|
|
222
|
+
</nav>
|
|
223
|
+
<script>
|
|
224
|
+
// Side menu: theme and collapse, the same lines in board.html and notes.html. Runs before
|
|
225
|
+
// the page draws, so a stored theme or a collapsed menu never flashes the default first.
|
|
226
|
+
(()=>{const r=document.documentElement,wide=matchMedia("(max-width:1100px)"),phone=matchMedia("(max-width:640px)"),os=matchMedia("(prefers-color-scheme: dark)");
|
|
227
|
+
// The toggle's choice is kept per browser; with none, the width decides. A phone always gets icons.
|
|
228
|
+
let fold=null;try{fold=localStorage.getItem("kz-rail")}catch(e){}
|
|
229
|
+
const rail=()=>{const m=phone.matches||(fold?fold==="min":wide.matches),b=document.getElementById("fold");r.dataset.rail=m?"min":"full";b.title=m?"Expand menu":"Collapse menu";b.setAttribute("aria-label",b.title);b.setAttribute("aria-expanded",!m)};
|
|
230
|
+
wide.addEventListener("change",rail);phone.addEventListener("change",rail);rail();
|
|
231
|
+
document.getElementById("fold").onclick=()=>{fold=r.dataset.rail==="min"?"full":"min";try{localStorage.setItem("kz-rail",fold)}catch(e){}rail()};
|
|
232
|
+
// Theme: light, dark, or unset for system, in kz-theme. The switch shows what is on screen.
|
|
233
|
+
const paint=()=>{const d=r.dataset.theme?r.dataset.theme==="dark":os.matches;document.querySelectorAll(".theme button").forEach(b=>b.setAttribute("aria-pressed",(b.dataset.t==="dark")===d))};
|
|
234
|
+
window.setTheme=t=>{if(t==="system")delete r.dataset.theme;else r.dataset.theme=t;try{t==="system"?localStorage.removeItem("kz-theme"):localStorage.setItem("kz-theme",t)}catch(e){}paint();dispatchEvent(new Event("kz-theme"))};
|
|
235
|
+
try{const t=localStorage.getItem("kz-theme");if(t)r.dataset.theme=t}catch(e){}
|
|
236
|
+
os.addEventListener("change",paint);paint();
|
|
237
|
+
document.querySelectorAll(".theme button").forEach(b=>b.onclick=()=>setTheme(b.dataset.t))})();
|
|
238
|
+
</script>
|
|
239
|
+
<div class="app">
|
|
179
240
|
<div class="hdr">
|
|
180
241
|
<button class="ib" id="side" title="Notes list" aria-label="Notes list"><svg><use href="#i-list"/></svg></button>
|
|
181
|
-
<a href="/" title="Back to the board" aria-label="kaizen board"><pre class="mark"><span>█ █ ▄▀█ █ ▀▀█ █▀▀ █▄ █</span>
|
|
182
|
-
<span>█▀▄ █▀█ █ ▄▀ ██▄ █ ▀█</span>
|
|
183
|
-
<span>█ █ █ █ █ █▄▄ █▄▄ █ █</span></pre></a>
|
|
184
242
|
<h1>Notebook</h1>
|
|
185
243
|
<select class="sel" id="proj" aria-label="Project"></select>
|
|
186
244
|
<span class="sp"></span>
|
|
187
|
-
<a class="btn ghost" href="/">Board</a>
|
|
188
245
|
<button class="btn pri" id="new">New note</button>
|
|
189
246
|
</div>
|
|
190
247
|
|
|
@@ -209,7 +266,9 @@ select.sel{cursor:pointer;appearance:none;padding-right:30px;max-width:min(320px
|
|
|
209
266
|
</div>
|
|
210
267
|
</main>
|
|
211
268
|
</div>
|
|
269
|
+
</div>
|
|
212
270
|
<div class="toast" id="toast" role="status"></div>
|
|
271
|
+
<script>document.querySelector('.rail a[data-view="notes"]').setAttribute("aria-current","page")</script>
|
|
213
272
|
<script src="/notebook.js"></script>
|
|
214
273
|
</body>
|
|
215
274
|
</html>
|