symbiot 0.55.1 → 0.55.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ui.mjs CHANGED
@@ -28,7 +28,7 @@ body.lq-forced #lq{display:none}
28
28
  .lqd.lq-proj .lt{flex-direction:column;gap:1px;max-width:190px}
29
29
  .lqd.lq-proj .lt .lm{font-weight:500;font-size:11.5px;color:#8A919B;max-width:180px;-webkit-line-clamp:1}
30
30
  body.lq-light .lqd.lq-proj .lt .lm{color:#5A6470}
31
- .lqd.lq-you .lt::before{content:'';flex:none;width:7px;height:7px;border-radius:50%;background:#F2A541;box-shadow:0 0 0 3px rgba(242,165,65,.2)}
31
+ .lqd.lq-you .lt::before,.lqd.lq-lit .lt::before{content:'';flex:none;width:7px;height:7px;border-radius:50%;background:#F2A541;box-shadow:0 0 0 3px rgba(242,165,65,.2)}
32
32
  .lqd .lt span{overflow:hidden;white-space:normal;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12.5px;line-height:1.3;overflow-wrap:anywhere}
33
33
  .lqd .lgo{display:none;margin:4px 10px 10px;min-height:38px;padding:0 16px;border-radius:999px;border:1px solid #FFFFFF;background:linear-gradient(180deg,#FFFFFF 0%,#D2D8E1 44%,#8E97A6 56%,#E6EAF0 100%);color:#06080B;font:700 12.5px var(--sans);cursor:pointer}
34
34
  .lqd.open{z-index:5;border-radius:18px;background:rgba(16,18,21,.92);border-color:rgba(255,255,255,.12);box-shadow:0 24px 50px -20px rgba(0,0,0,.8);text-shadow:none;backdrop-filter:blur(12px)}.lqd.open .lt span{white-space:normal}.lqd.open .lt{max-width:280px}.lqd.open .lgo{display:block}
@@ -53,13 +53,18 @@ body.lq-touch .lqd .lt{min-height:48px}
53
53
  body.lq-keys .lqd:focus{outline:2px solid currentColor;outline-offset:3px}
54
54
  #lqcore{position:absolute;left:0;top:0;padding:4px 8px;border:0;border-radius:12px;background:transparent;color:#F3F0EA;font:600 16px/1.25 var(--sans);letter-spacing:-.01em;text-align:center;cursor:pointer;transition:opacity .6s ease;white-space:normal;text-wrap:balance}
55
55
  body.lq-light #lqcore{color:#151A21}
56
+ /* Pearl's sphere is a mirror, bright over its horizon and dark under it, so its words sit on a soft pearl plate that reads on both */
57
+ body.lq-look-pearl #lqcore.in{padding:10px 18px;border-radius:50%;background:radial-gradient(closest-side,rgba(250,250,248,.9) 0%,rgba(250,250,248,.78) 55%,rgba(250,250,248,0) 100%);text-shadow:0 0 1px #fff,0 0 8px rgba(255,255,255,.9)}
58
+ body.lq-look-pearl.lq-contrast #lqcore.in{background:#FFFFFF;border-radius:16px;text-shadow:none}
56
59
  .lqglass{background:rgba(4,5,8,.78);border:1px solid rgba(220,228,240,.32);backdrop-filter:blur(14px)}
57
60
  body.lq-light .lqglass{background:rgba(255,255,255,.82);border-color:rgba(20,30,45,.2)}
58
- #lqform{position:absolute;left:50%;bottom:20px;transform:translateX(-50%);width:min(640px,calc(100vw - 32px));display:flex;align-items:center;gap:8px;padding:5px 5px 5px 6px;border-radius:20px;z-index:3;color:#ECE9E4}
61
+ #lqform{position:absolute;left:50%;bottom:20px;transform:translateX(-50%);width:min(640px,calc(100vw - 32px));display:flex;align-items:flex-end;gap:8px;padding:5px 5px 5px 6px;border-radius:20px;z-index:3;color:#ECE9E4}
59
62
  #lqform.lqglass{background:rgba(255,255,255,.055);border:1px solid rgba(255,255,255,.12);backdrop-filter:blur(16px)}
60
63
  body.lq-light #lqform{color:#151A21}
61
64
  body.lq-light #lqform.lqglass{background:rgba(255,255,255,.62);border-color:rgba(255,255,255,.9);box-shadow:0 20px 50px -30px rgba(30,45,60,.5)}
62
- #lqask{flex:1;min-width:0;min-height:44px;padding:0 12px;border:0;border-radius:14px;background:transparent;color:inherit;font:400 15.5px var(--sans);outline:none}
65
+ #lqask{flex:1;min-width:0;display:block;box-sizing:border-box;height:44px;min-height:44px;max-height:126px;padding:11px 12px;border:0;border-radius:14px;background:transparent;color:inherit;font:400 15.5px/22px var(--sans);outline:none;resize:none;overflow-y:hidden;overflow-wrap:anywhere;scrollbar-width:thin;scrollbar-color:rgba(236,233,228,.25) transparent}
66
+ #lqask.full{overflow-y:auto}body.lq-light #lqask{scrollbar-color:rgba(21,26,33,.25) transparent}
67
+ #lqask::-webkit-scrollbar{width:6px}#lqask::-webkit-scrollbar-thumb{background:rgba(236,233,228,.25);border-radius:3px}body.lq-light #lqask::-webkit-scrollbar-thumb{background:rgba(21,26,33,.25)}#lqask::-webkit-scrollbar-track{background:transparent}
63
68
  #lqask::placeholder{color:#7A818B}
64
69
  body.lq-light #lqask::placeholder{color:#6A7480}
65
70
  #lqsend{flex:none;width:40px;height:40px;padding:0;display:grid;place-items:center;border-radius:50%;border:0;background:#F3F0EA;color:#0A0B0D;cursor:pointer}
@@ -70,6 +75,25 @@ body.lq-look-pearl #lqsend{background:linear-gradient(180deg,#FFFFFF 0%,#F1F2F5
70
75
  #lqtalk .lqmsg{transition:opacity .4s ease}
71
76
  #lqtalk:hover .lqmsg,#lqtalk.back .lqmsg{opacity:1 !important}
72
77
  #lqtalk::-webkit-scrollbar{display:none}
78
+ /* the chat's own bar, between what was said and the input: Minimize shrinks it back to the
79
+ input, and the Home under it comes back; minimized, one tap on "Chat · n" reopens it, history kept */
80
+ #lqtalkbar{position:absolute;left:50%;bottom:calc(26px + var(--lqfh,56px));transform:translateX(-50%);width:min(640px,calc(100vw - 32px));display:none;align-items:center;gap:10px;z-index:3;pointer-events:none;color:#8A919B;font:500 12px var(--sans)}
81
+ body.lq-chat #lqtalkbar,body.lq-chatmin #lqtalkbar{display:flex}
82
+ body.lq-chat #lqtalk{bottom:calc(64px + var(--lqfh,56px))}
83
+ body.lq-chatmin #lqtalk{display:none}
84
+ #lqtalkbar .lqtb-t{flex:1;min-width:0;padding-left:4px;letter-spacing:.02em}
85
+ #lqtalkbar .lqtb-t::after{content:'';display:inline-block;vertical-align:middle;width:calc(100% - 140px);max-width:420px;height:1px;margin-left:10px;background:linear-gradient(90deg,rgba(236,233,228,.18),rgba(236,233,228,0))}
86
+ #lqtalkbar button{pointer-events:auto;display:inline-flex;align-items:center;gap:6px;min-height:30px;padding:0 12px;border-radius:999px;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.06);backdrop-filter:blur(10px);color:#C9CDD3;font:500 12.5px var(--sans);cursor:pointer}
87
+ #lqtalkbar button:hover{border-color:rgba(255,255,255,.28);color:#ECE9E4}
88
+ #lqtalkbar button:focus-visible{outline:2px solid currentColor;outline-offset:2px}
89
+ body.lq-chatmin #lqtalkbar{justify-content:center}
90
+ body.lq-light #lqtalkbar{color:#5A6470}
91
+ body.lq-light #lqtalkbar .lqtb-t::after{background:linear-gradient(90deg,rgba(21,26,33,.16),rgba(21,26,33,0))}
92
+ body.lq-light #lqtalkbar button{border-color:rgba(21,26,33,.12);background:rgba(255,255,255,.62);color:#2B3644}
93
+ body.lq-light #lqtalkbar button:hover{border-color:rgba(21,26,33,.3);color:#151A21}
94
+ body.lq-look-pearl #lqtalkbar button{background:linear-gradient(180deg,#FFFFFF 0%,#F1F2F5 60%,#E6E9EE 100%);color:#1B1E23;border-color:rgba(21,26,33,.10);box-shadow:0 1px 2px rgba(21,26,33,.10),0 8px 18px -10px rgba(40,52,74,.45)}
95
+ body.lq-touch #lqtalkbar button{min-height:40px}
96
+ body.lq-pooled #lqtalkbar{display:none}
73
97
  .lqmsg{align-self:flex-start;max-width:92%;padding:0;border:0;background:none;color:#ECE9E4;font-size:15px;line-height:1.55;white-space:pre-wrap;overflow-wrap:anywhere;text-shadow:0 1px 12px rgba(0,0,0,.7)}
74
98
  .lqmsg.me{align-self:flex-end;max-width:80%;padding:8px 14px;border-radius:18px;background:rgba(255,255,255,.09);color:#C9CDD3;font-size:14.5px;text-shadow:none}
75
99
  body.lq-light .lqmsg{color:#151A21;text-shadow:0 1px 10px rgba(255,255,255,.8)}
@@ -234,6 +258,39 @@ body.lq-contrast .lqblob .nopt{border:2px solid currentColor}
234
258
  body.lq-touch .lqblob .nopt,body.lq-touch .lqblob .bfx input{min-height:44px}
235
259
  body.lq-talking .lqd.lqblob{opacity:.55}
236
260
  @media (prefers-reduced-motion: reduce){.lqblob.gone{transition:none}}
261
+ /* next up: with nothing waiting on you, a few things that can be done next, each with its gain and one tap */
262
+ #lqnext{display:none;position:absolute;left:0;top:0;box-sizing:border-box;flex-direction:column;gap:6px;padding:10px 12px 12px;border-radius:18px;background:rgba(12,14,17,.84);border:1px solid rgba(220,228,240,.16);backdrop-filter:blur(12px);box-shadow:0 18px 40px -22px rgba(0,0,0,.85);color:#ECE9E4;font:500 13px/1.3 var(--sans);z-index:2;transition:opacity .5s ease}
263
+ #lqnext.on{display:flex}
264
+ #lqnext .nxh{display:flex;align-items:baseline;gap:10px;padding:0 2px}
265
+ #lqnext .nxh span{font:600 15px var(--sans);letter-spacing:-.01em}
266
+ #lqnext .nxh small{flex:1;font:400 12px var(--sans);color:#8A919B}
267
+ #lqnext .nxx{all:unset;box-sizing:border-box;cursor:pointer;width:28px;height:28px;border-radius:50%;display:grid;place-items:center;font-size:18px;color:#8A919B}
268
+ #lqnext .nxx:hover{color:#ECE9E4;background:rgba(255,255,255,.08)}
269
+ #lqnext ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
270
+ #lqnext ol.two{display:grid;grid-template-columns:1fr 1fr}
271
+ #lqnext .nxpg{all:unset;box-sizing:border-box;cursor:pointer;min-height:28px;padding:2px 10px;border-radius:999px;border:1px solid rgba(255,255,255,.16);font:500 12px var(--sans);color:#C9CDD3}
272
+ body.lq-light #lqnext .nxpg{border-color:rgba(20,30,45,.16);color:#2B3644}
273
+ #lqnext .nxpg:focus-visible{outline:2px solid currentColor;outline-offset:2px}
274
+ #lqnext li{display:flex;align-items:center;gap:10px;padding:8px 8px 8px 12px;border-radius:13px;border:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.04);transition:opacity .5s ease,filter .5s ease}
275
+ #lqnext li.gone{opacity:0;filter:blur(6px);pointer-events:none}
276
+ #lqnext .nxt{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
277
+ #lqnext .nxt b{font-weight:600;font-size:13px;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow-wrap:anywhere}
278
+ #lqnext .nxt span{font-size:12px;color:#9AA1AB;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow-wrap:anywhere}
279
+ #lqnext .nxgo{all:unset;box-sizing:border-box;cursor:pointer;flex:none;min-height:32px;padding:6px 12px;border-radius:12px;background:#F3F0EA;color:#0A0B0D;font:600 12.5px var(--sans);white-space:nowrap}
280
+ #lqnext .nxgo:disabled{opacity:.5;cursor:default}
281
+ #lqnext .nxx:focus-visible,#lqnext .nxgo:focus-visible{outline:2px solid currentColor;outline-offset:2px}
282
+ body.lq-light #lqnext{background:rgba(255,255,255,.94);border-color:rgba(20,30,45,.12);box-shadow:0 18px 40px -26px rgba(30,45,60,.5);color:#151A21}
283
+ body.lq-light #lqnext li{background:#FFFFFF;border-color:rgba(20,30,45,.12)}
284
+ body.lq-light #lqnext .nxh small,body.lq-light #lqnext .nxx,body.lq-light #lqnext .nxt span{color:#5A6470}
285
+ body.lq-light #lqnext .nxx:hover{color:#151A21;background:rgba(20,30,45,.06)}
286
+ body.lq-light #lqnext .nxgo{background:#151A21;color:#FFFFFF}
287
+ body.lq-look-pearl #lqnext .nxgo{background:linear-gradient(180deg,#FFFFFF 0%,#F1F2F5 60%,#E6E9EE 100%);color:#1B1E23;box-shadow:inset 0 0 0 1px rgba(0,0,0,.08)}
288
+ body.lq-contrast #lqnext li{border:2px solid currentColor}
289
+ body.lq-touch #lqnext .nxgo,body.lq-touch #lqnext .nxx,body.lq-touch #lqnext .nxpg{min-height:44px;min-width:44px}
290
+ body.lq-talking #lqnext{opacity:.55}
291
+ body.lq-pooled #lqnext,body.lq-work #lqnext{display:none}
292
+ @media (max-width:520px){#lqnext .nxh small{display:none}#lqnext .nxh span{flex:1}}
293
+ @media (prefers-reduced-motion: reduce){#lqnext li{transition:none}}
237
294
  /* what lit an orb, lit up where it opens: the project's asks, its Approve, its held handovers */
238
295
  .lit{scroll-margin-top:72px;position:relative;box-shadow:0 0 0 2px #F2A541,0 0 0 7px rgba(242,165,65,.18)!important;border-radius:16px;animation:lqlit 1.6s ease-out 2}
239
296
  @keyframes lqlit{0%{box-shadow:0 0 0 2px #F2A541,0 0 0 18px rgba(242,165,65,.35)}100%{box-shadow:0 0 0 2px #F2A541,0 0 0 7px rgba(242,165,65,.18)}}
@@ -410,6 +467,13 @@ footer{padding:10px 18px;border-top:1px solid var(--line);display:flex}
410
467
  .pcard .pform input.purl{min-width:0}
411
468
  body.lq-liquid .rcard.pcard,body.lq-liquid.lq-light .rcard.pcard{border-left:3px solid var(--amber)}
412
469
  .tag.cust{background:transparent;border-color:var(--amber);color:var(--amber)}
470
+ /* Marketing: the product an item markets, a quiet word before it; what needs you, ruled amber; the Add row */
471
+ .mkp{display:inline-block;margin-right:8px;padding-right:8px;border-right:1px solid var(--line);font:600 10.5px/1.4 var(--sans);letter-spacing:.09em;text-transform:uppercase;color:var(--green);vertical-align:1px}
472
+ .mkneeds{margin-bottom:14px;padding:4px 0 4px 12px;border-left:2px solid var(--amber)}
473
+ .mkneeds.lit{border-radius:0 16px 16px 0}
474
+ #panel-marketing .pcard.lit{border-radius:11px}
475
+ .mkadd{flex-wrap:wrap;margin:8px 0 6px}.mkadd .mkl{font-size:12px;color:var(--faint)}.mkadd .mkprod{flex:0 0 160px;width:160px}.mkadd .mktext{flex:1;min-width:220px}
476
+ @media (max-width:600px){.mkadd .mkprod{flex:1 1 100%;width:auto}}
413
477
  .rrow{cursor:pointer}.rrow:hover,.rrow:focus-visible{border-color:var(--green)}.rrow.rnew{border-left:3px solid var(--amber)}
414
478
  .out.rdoc{white-space:normal;line-height:1.55;font-size:14px;padding:18px 22px;overflow-wrap:anywhere}
415
479
  .rdoc h2{font-size:21px;margin:4px 0 10px;color:var(--bone)}.rdoc h3{font-size:16px;margin:22px 0 8px;color:var(--bone)}.rdoc h4,.rdoc h5,.rdoc h6{font-size:14px;margin:16px 0 6px;color:var(--bone)}
@@ -544,19 +608,22 @@ body.lq-look-pearl #lmap{background:#EDECE9}
544
608
  #lmlines line.hot{stroke:rgba(255,255,255,.4)}
545
609
  body.lq-light #lmlines line{stroke:rgba(21,26,33,.14)}body.lq-light #lmlines line.hot{stroke:rgba(21,26,33,.45)}
546
610
  #lmov{position:absolute;inset:0;pointer-events:none}
547
- .lml{position:absolute;left:0;top:0;display:flex;flex-direction:column;align-items:center;gap:0;padding:2px 6px;border:0;border-radius:8px;background:transparent;color:#ECE9E4;font:inherit;text-align:center;white-space:nowrap;pointer-events:auto;cursor:pointer;transition:opacity .25s ease;will-change:transform;text-shadow:0 1px 10px rgba(0,0,0,.7)}
611
+ .lml{position:absolute;left:0;top:0;display:flex;flex-direction:column;align-items:center;gap:0;padding:2px 6px;border:0;border-radius:8px;background:transparent;color:#ECE9E4;font:inherit;text-align:center;white-space:nowrap;max-width:158px;pointer-events:auto;cursor:pointer;transition:opacity .25s ease;will-change:transform;text-shadow:0 1px 10px rgba(0,0,0,.7)}
612
+ .lml b,.lml span{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis}
548
613
  .lml b{font:600 13px/1.2 var(--sans)}
549
614
  .lml span{font:400 11.5px/1.3 var(--sans);color:#8A919B}
550
615
  .lml.dormant{opacity:.55}.lml.far{opacity:.3}.lml.near b{color:#F2A541}
551
- .lmme{position:absolute;left:0;top:0;display:flex;align-items:center;gap:7px;pointer-events:none;will-change:transform;font:500 11px var(--sans);letter-spacing:.08em;text-transform:uppercase;color:#ECE9E4}
552
- .lmme i{width:14px;height:14px;border-radius:50%;background:#F3F0EA;box-shadow:0 0 0 4px rgba(243,240,234,.18),0 0 18px rgba(243,240,234,.45)}
553
- body.lq-light .lmme{color:#151A21}body.lq-light .lmme i{background:#151A21;box-shadow:0 0 0 4px rgba(21,26,33,.15)}
616
+ .lml.need span{color:#F2A541}.lml.tiny span{display:none}body.lq-light .lml.need span{color:#A85F0A}
554
617
  .lml:focus-visible{outline:2px solid currentColor;outline-offset:2px}
555
618
  body.lq-light .lml{color:#151A21;text-shadow:0 1px 8px rgba(255,255,255,.8)}body.lq-light .lml span{color:#5A6470}body.lq-light .lml.near b{color:#A85F0A}
556
- .lmc{position:absolute;left:0;top:0;font:500 11px var(--sans);letter-spacing:.12em;text-transform:uppercase;color:#8A919B;white-space:nowrap;pointer-events:none;will-change:transform}
557
- body.lq-light .lmc{color:#5A6470}
619
+ .lmlane{position:absolute;left:0;top:0;display:flex;align-items:baseline;gap:8px;padding:2px 0;border:0;background:transparent;font:500 11px var(--sans);letter-spacing:.12em;text-transform:uppercase;color:#8A919B;white-space:nowrap;pointer-events:auto;cursor:pointer;will-change:transform}
620
+ .lmlane small{font:inherit;letter-spacing:0;color:#5E656F}.lmlane span{letter-spacing:.04em;text-transform:none;font-size:11px;color:#5E656F;opacity:0;transition:opacity .15s ease}.lmlane:hover span,.lmlane:focus-visible span,.lmlane[aria-expanded='false'] span{opacity:1}
621
+ .lmlane:hover{color:#ECE9E4}.lmlane.need{color:#F2A541}.lmlane:focus-visible{outline:2px solid currentColor;outline-offset:3px;border-radius:4px}
622
+ body.lq-light .lmlane{color:#5A6470}body.lq-light .lmlane small,body.lq-light .lmlane span{color:#8A919B}body.lq-light .lmlane:hover{color:#151A21}body.lq-light .lmlane.need{color:#A85F0A}
558
623
  .lmring{position:absolute;left:0;top:0;border-radius:50%;box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.35);pointer-events:none}
559
624
  body.lq-light .lmring{box-shadow:inset 0 0 0 1.5px rgba(21,26,33,.35)}
625
+ .lmring.group{box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.35),0 0 0 4px rgba(255,255,255,.06),0 0 0 8px rgba(255,255,255,.03)}.lmring.need{box-shadow:inset 0 0 0 1.5px rgba(242,165,65,.8)}
626
+ body.lq-light .lmring.group{box-shadow:inset 0 0 0 1.5px rgba(21,26,33,.35),0 0 0 4px rgba(21,26,33,.06),0 0 0 8px rgba(21,26,33,.03)}body.lq-light .lmring.need{box-shadow:inset 0 0 0 1.5px rgba(168,95,10,.8)}
560
627
  .lmtip{position:absolute;left:0;top:0;width:300px;max-width:calc(100% - 20px);padding:12px 14px;border-radius:16px;background:rgba(16,18,21,.94);border:1px solid rgba(255,255,255,.1);box-shadow:0 24px 50px -20px rgba(0,0,0,.8);color:#ECE9E4;opacity:0;pointer-events:none;transition:opacity .15s ease;z-index:3}
561
628
  .lmtip.on{opacity:1}
562
629
  .lmtip b{font:600 13.5px var(--sans)}
@@ -565,8 +632,8 @@ body.lq-light .lmring{box-shadow:inset 0 0 0 1.5px rgba(21,26,33,.35)}
565
632
  .lmtip .lmtn b{font-size:12.5px;color:#F2A541}
566
633
  body.lq-light .lmtip{background:rgba(255,255,255,.96);border-color:rgba(21,26,33,.1);color:#151A21;box-shadow:0 24px 50px -24px rgba(30,45,60,.45)}
567
634
  body.lq-light .lmtip .lmtn{color:#2B3644}body.lq-light .lmtip .lmtn b{color:#A85F0A}
568
- .lmzoom{position:absolute;right:12px;bottom:12px;display:flex;flex-direction:column;gap:6px;z-index:2}
569
- .lmzoom .iconbtn{width:32px;height:32px;font:500 16px var(--sans)}
635
+ .lmzoom{position:absolute;right:12px;top:10px;display:flex;flex-direction:row;gap:6px;z-index:2}
636
+ .lmzoom .iconbtn{width:30px;height:30px;font:500 16px var(--sans)}
570
637
  body.lq-liquid #lmtools{display:flex;flex-direction:column;gap:8px;margin-top:14px}
571
638
  .lmrow{display:flex;flex-wrap:wrap;align-items:center;gap:4px 6px}
572
639
  .lmk{font:500 11px var(--sans);letter-spacing:.1em;text-transform:uppercase;color:var(--faint);margin-right:6px;min-width:110px}
@@ -779,6 +846,7 @@ body.lq-liquid .pshelf .pcard.editing{grid-column:1/-1}
779
846
  .pshelf .psrc summary{font-size:12px;color:var(--faint);cursor:pointer}
780
847
 
781
848
  /* a project's tasks: calm cards, two lines a task (click for all), no repo name inside its own page */
849
+ .wdagents{margin-top:26px;padding-top:16px;border-top:1px solid var(--line)}
782
850
  .tnhow{margin:2px 0 8px}
783
851
  .tnhow > summary{cursor:pointer;list-style:none;display:inline-flex;align-items:center;gap:7px;font:500 12.5px var(--sans);color:var(--faint)}
784
852
  .tnhow > summary::-webkit-details-marker{display:none}
@@ -849,8 +917,8 @@ body.lq-liquid .bcard.blane{position:absolute;left:16px;width:186px;padding:0;bo
849
917
  .blane .bla button:hover{color:var(--bone)}
850
918
  .blane .ble{font-size:11.5px;color:#E5484D;margin-top:2px}
851
919
  .blb{position:absolute;right:16px;width:230px;text-align:right;font-size:12.5px;line-height:1.4;color:var(--faint);transform:translateY(-50%);pointer-events:none}
852
- .bline{position:absolute;height:1px;background:linear-gradient(90deg,transparent,rgba(255,255,255,.1) 6%,rgba(255,255,255,.1) 94%,transparent);pointer-events:none}
853
- body.lq-light .bline{background:linear-gradient(90deg,transparent,rgba(21,26,33,.1) 6%,rgba(21,26,33,.1) 94%,transparent)}
920
+ .blines{position:absolute;left:0;top:0;pointer-events:none;color:#FFFFFF;overflow:visible}
921
+ body.lq-light .blines{color:#151A21}
854
922
  .btick{position:absolute;bottom:12px;font-size:11px;color:var(--faint);transform:translateX(-50%);pointer-events:none}
855
923
  .bnow{position:absolute;top:18px;bottom:30px;width:1px;background:linear-gradient(180deg,transparent,rgba(255,255,255,.25),transparent);pointer-events:none}
856
924
  body.lq-light .bnow{background:linear-gradient(180deg,transparent,rgba(21,26,33,.25),transparent)}
@@ -893,6 +961,7 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
893
961
  <div id="lqrelay" aria-label="Your projects: what their agents did, ask and suggest"></div>
894
962
  <div id="lqgroups"></div>
895
963
  <div id="lqneedt"><span>Needs you</span><small>only you can do these</small></div>
964
+ <div id="lqnext" role="region" aria-label="What can be done next"></div>
896
965
  <div id="lqdrops"></div>
897
966
  <button type="button" id="lqback">&lsaquo; Home</button>
898
967
  <h2 id="lqscene"></h2>
@@ -901,8 +970,9 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
901
970
  <button type="button" id="lqcore" title="talk to Symbiot"></button>
902
971
  <div id="lqmore"></div>
903
972
  <div id="lqtalk" aria-live="polite"></div>
973
+ <div id="lqtalkbar"></div>
904
974
  <div id="lqatts" aria-live="polite"></div>
905
- <form id="lqform" class="lqglass"><label for="lqask" style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)">Talk to Symbiot</label><input id="lqask" autocomplete="off" placeholder="Talk to Symbiot, or tell it what to do (paste a screenshot to show it)"><button type="submit" id="lqsend" aria-label="Send"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 13V3M3.5 7.5 8 3l4.5 4.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></button></form>
975
+ <form id="lqform" class="lqglass"><label for="lqask" style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)">Talk to Symbiot</label><textarea id="lqask" rows="1" autocomplete="off" placeholder="Talk to Symbiot, or tell it what to do (paste a screenshot to show it)"></textarea><button type="submit" id="lqsend" aria-label="Send"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 13V3M3.5 7.5 8 3l4.5 4.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></button></form>
906
976
  </div>
907
977
 
908
978
  <div id="updatebar" class="updatebar"></div>
@@ -916,8 +986,7 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
916
986
  <button class="tab" data-tab="week">Week</button>
917
987
  <button class="tab" data-tab="standup">Standup</button>
918
988
  <button class="tab" data-tab="todo">Todo</button>
919
- <button class="tab" data-tab="tasks">Tasks</button>
920
- <button class="tab" data-tab="agents">Agents</button>
989
+ <button class="tab" data-tab="tasks">Workdesk</button>
921
990
  <button class="tab" data-tab="reports">Reports</button><button class="tab" data-tab="marketing">Marketing</button>
922
991
  <button class="tab" data-tab="settings">Settings</button>
923
992
  </div>
@@ -969,19 +1038,19 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
969
1038
  <div class="rfoot" id="outfoot" style="display:none"></div>
970
1039
  </section>
971
1040
  <section id="panel-tasks" class="hidden">
972
- <details class="tnhow"><summary>How tasks work</summary><div class="note muted">To add a task, tell Symbiot in the bar at the bottom: it files it under the right project. To give tasks to your agent, use <b>Send to repos</b> at the top, beside Close &mdash; filter by tag below to choose which. As the agent finishes each one it lands in <b>Awaiting your review</b>: <b>Approve</b> commits it on a branch and opens a PR, <b>&#8630;</b> sends it back. Ticking a task yourself marks it done (it auto-archives). Click <b><i class=ic-chat></i></b> on a task to ask questions about it.</div></details>
1041
+ <details class="tnhow"><summary>How the Workdesk works</summary><div class="note muted">Everything handed to an agent, in one place: what needs you, work awaiting your review, tasks queued for later, and below them the agents at work, their questions and the handovers between them. To add a task, tell Symbiot in the bar at the bottom: it files it under the right project. To give tasks to your agent, use <b>Send to repos</b> at the top, beside Close &mdash; filter by tag below to choose which. As the agent finishes each one it lands in <b>Awaiting your review</b>: <b>Approve</b> commits it on a branch and opens a PR, <b>&#8630;</b> sends it back. Ticking a task yourself marks it done (it auto-archives). Click <b><i class=ic-chat></i></b> on a task to ask questions about it.</div></details>
973
1042
  <div id="needsbox"></div>
974
1043
  <div id="pushout"></div>
975
1044
  <div id="reviewout"></div>
976
1045
  <div id="reviewlist"></div>
977
1046
  <div id="taskfilter" class="taskfilter"></div>
978
1047
  <div id="tasklist"></div>
979
- </section>
980
- <section id="panel-agents" class="hidden">
981
- <div class="row"><span class="muted">Agents Symbiot has handed work to — live status and output. Questions, options and ideas an agent leaves for you show up on its block, whichever model it runs.</span><button class="iconbtn" id="agentsrefresh" title="Refresh" aria-label="Refresh"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M13 8a5 5 0 1 1-1.5-3.6M13 2.5V5h-2.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></button></div>
1048
+ <div id="panel-agents" class="wdagents">
1049
+ <div class="row"><span class="muted" style="flex:1"><span class="tgroup" style="margin-right:8px">Agents and handovers</span>What each agent is doing, live, and the work handed between them. Questions, options and ideas an agent leaves for you show up on its block, whichever model it runs.</span><button class="iconbtn" id="agentsrefresh" title="Refresh" aria-label="Refresh"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M13 8a5 5 0 1 1-1.5-3.6M13 2.5V5h-2.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></button></div>
982
1050
  <div id="agentsmsg"></div>
983
1051
  <div id="laneslist"></div>
984
1052
  <div id="agentslist"></div>
1053
+ </div>
985
1054
  </section>
986
1055
  <section id="panel-reports" class="hidden">
987
1056
  <div class="row"><span class="muted" style="flex:1">What agents wrote up for you (findings, audits, plans), newest first. They leave each one in a project's .symbiot folder; it shows here as soon as it's written.</span><button class="iconbtn" id="reportsrefresh" title="Refresh" aria-label="Refresh"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M13 8a5 5 0 1 1-1.5-3.6M13 2.5V5h-2.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></button></div>
@@ -989,8 +1058,10 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
989
1058
  <div id="reportview"></div>
990
1059
  </section>
991
1060
  <section id="panel-marketing" class="hidden">
992
- <div class="row"><span class="muted" style="flex:1">What you post about your work, and who answers: your drafts, the replies to them, and the 4-week test of whether posting brings anyone. Symbiot never posts: you do, on LinkedIn.</span></div>
1061
+ <div class="row"><span class="muted" style="flex:1">Marketing for every product you make, in a lane of its own: its agent drafts posts, demo videos, launches and campaigns, each tagged with the product it&#39;s for, and you approve every piece. Symbiot never posts: you do.</span></div>
993
1062
  <div id="mkmsg"></div>
1063
+ <div id="mkneeds"></div>
1064
+ <div id="mktasks"></div>
994
1065
  <div id="boardposts"></div>
995
1066
  <div id="mkreplies"></div>
996
1067
  <div id="mktest"></div>
@@ -1044,6 +1115,7 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
1044
1115
  <input id="agentcmd" type="text" placeholder="e.g. aider --message &quot;{prompt}&quot; --yes · code {dir} · leave blank to just write the file">
1045
1116
  <div class="note muted">Runs in each repo after tasks are written. Use <b>{dir}</b> = repo path, <b>{prompt}</b> = the task instruction. Works with any agent or editor &mdash; it's your command.</div>
1046
1117
  <label class="check swl" style="margin-top:12px"><input type="checkbox" class="sw" id="agenttrust" checked> <span><b>Symbiosis</b> &middot; agents work on their own. They do the work without stopping to ask permission. <b>The membrane</b> still stops pushing to main, publishing, deleting outside their folder, sudo, reading your keys and changing Symbiot's settings. Off: they ask before anything outside their allow list.</span></label>
1118
+ <div class="note muted" id="agentsandbox" style="margin-top:6px"></div>
1047
1119
  <div id="agentpresets" style="margin-top:8px"></div>
1048
1120
  <div class="note muted" id="agentconnectors"></div>
1049
1121
  <div id="grantbox" style="margin-top:10px">
@@ -1114,25 +1186,28 @@ var SELWAIT={};function selWait(key,f){SELWAIT[key]=f;}
1114
1186
  if(document.addEventListener)document.addEventListener('selectionchange',function(){var ks=Object.keys(SELWAIT);if(!ks.length)return;var s=window.getSelection&&window.getSelection();if(s&&!s.isCollapsed)return;var w=SELWAIT;SELWAIT={};ks.forEach(function(k){try{w[k]();}catch(e){}});});
1115
1187
  function whenFree(el,key,f){if(selIn(el)){selWait(key,f);return false;}f();return true;}
1116
1188
  function sameHtml(el,h){if(el._h===h)return true;el.innerHTML=h;el._h=h;return false;}
1117
- var current='map';var mapLoaded=false;var driftLoaded=false;
1189
+ var current='map',WDJUMP=0;var mapLoaded=false;var driftLoaded=false;
1118
1190
  var COLORS={person:'#3DDC97',repo:'#F4F1EA',lang:'#F2A541',tool:'#6bb3ff',agent:'#c58af9',ai:'#5fe3b0',folder:'#b7a98c'};
1119
1191
  function tabs(){return document.querySelectorAll('.tab');}
1120
- function setTab(tab){current=tab;tabs().forEach(function(t){t.classList.toggle('active',t.dataset.tab===tab);});
1121
- var isMap=tab==='map',isSet=tab==='settings',isTasks=tab==='tasks',isDrift=tab==='drift',isAgents=tab==='agents',isBoard=tab==='board',isRun=(tab==='week'||tab==='standup'||tab==='todo');
1192
+ // Opened as Agents (its agents, a run's question): the Workdesk, at its agents, once
1193
+ // the tasks above them and the agents are drawn; not if you've moved, or something's lit.
1194
+ function wdJump(){if(!WDJUMP||current!=='tasks')return;if(Date.now()-WDJUMP>5000){WDJUMP=0;return;}var wa=$('panel-agents');if(document.querySelector('main .lit'))return;if(wa&&wa.scrollIntoView)wa.scrollIntoView({block:'start'});}
1195
+ (function(){var m=document.querySelector('main');if(m&&m.addEventListener)['wheel','pointerdown','keydown','touchstart'].forEach(function(e){m.addEventListener(e,function(){WDJUMP=0;},{passive:true});});})();
1196
+ function setTab(tab){WDJUMP=0;if(tab==='agents'){tab='tasks';WDJUMP=Date.now();}current=tab;tabs().forEach(function(t){t.classList.toggle('active',t.dataset.tab===tab);});
1197
+ var isMap=tab==='map',isSet=tab==='settings',isTasks=tab==='tasks',isDrift=tab==='drift',isBoard=tab==='board',isRun=(tab==='week'||tab==='standup'||tab==='todo');
1122
1198
  $('panel-map').classList.toggle('hidden',!isMap);
1123
1199
  $('panel-board').classList.toggle('hidden',!isBoard);if(isBoard){loadBoard();loadLinks();}
1124
1200
  $('panel-run').classList.toggle('hidden',!isRun);
1125
1201
  $('panel-settings').classList.toggle('hidden',!isSet);if(isSet)loadFirstSteps();
1126
1202
  $('panel-tasks').classList.toggle('hidden',!isTasks);
1127
1203
  $('panel-drift').classList.toggle('hidden',!isDrift);
1128
- $('panel-agents').classList.toggle('hidden',!isAgents);
1129
1204
  $('panel-reports').classList.toggle('hidden',tab!=='reports');if(tab==='reports')loadReports();
1130
1205
  $('panel-marketing').classList.toggle('hidden',tab!=='marketing');if(tab==='marketing')loadMarketing();
1131
1206
  if(isRun){$('what').textContent=tab;wuHead();WUTEXT='';$('out').textContent=WUEMPTY;$('out').classList.add('muted');$('copy').classList.add('hidden');$('outfoot').style.display='none';if(tab==='week')showLatestWeek();}
1132
1207
  if(isMap&&!mapLoaded)loadMap();
1133
1208
  if(isTasks)loadTasks();
1134
1209
  if(isDrift&&!driftLoaded)loadDrift();
1135
- if(isAgents)loadAgents(); else stopAgentsPoll();
1210
+ if(isTasks)loadAgents(); else stopAgentsPoll();
1136
1211
  lqTitle();if(isTasks&&TFILTER.repo)loadParked(lqTitle);}
1137
1212
  // First steps, at the top of Settings, in order: connect an AI, where your work is,
1138
1213
  // your agent, link one site (and whether agent runs can use it), a company folder
@@ -1172,7 +1247,8 @@ if(proj)name=lqProjName(proj);var parked=!!proj&&PARKED.repos.indexOf(proj)>=0;
1172
1247
  if(c)c.textContent=proj?'Projects':'Home';t.innerHTML=esc(name)+(parked?"<span class='lqpk'>parked</span>":'');
1173
1248
  var pk=$('lqpark');if(pk){pk.classList.toggle('hidden',!proj);pk.textContent=parked?'Unpark':'Park this project';pk.title=parked?'let its tasks start agent runs again':'stop its tasks starting agent runs, until you unpark it';pk.setAttribute('data-repo',proj);}
1174
1249
  // the open panel's own Refresh sits beside Close, not under it on the panel's top line
1175
- var tl=$('lqtools');if(tl&&tl.appendChild)['agents','reports'].forEach(function(p){var b=$(p+'refresh'),row=$('panel-'+p);row=row&&row.querySelector('.row');if(!b)return;if(p===current){if(b.parentNode!==tl)tl.appendChild(b);}else if(b.parentNode===tl&&row&&row.appendChild)row.appendChild(b);});
1250
+ var wa=$('panel-agents');if(wa&&wa.classList)wa.classList.toggle('hidden',!!arch); // the Archive is only what's done
1251
+ var tl=$('lqtools');if(tl&&tl.appendChild)['agents','reports'].forEach(function(p){var b=$(p+'refresh'),row=$('panel-'+p);row=row&&row.querySelector('.row');if(!b)return;if(p===current||(p==='agents'&&current==='tasks'&&!arch)){if(b.parentNode!==tl)tl.appendChild(b);}else if(b.parentNode===tl&&row&&row.appendChild)row.appendChild(b);});
1176
1252
  var pt=$('pushtasks');if(pt&&pt.classList)pt.classList.toggle('hidden',current!=='tasks'||!!arch);
1177
1253
  try{document.title=name+' \\u00b7 Symbiot';}catch(e){}}
1178
1254
  function lqPark(repo,on,then,path){api('/api/lanes/park',{repo:repo,path:path||'',on:on}).then(function(r){if(r&&r.error){if(then)then(r.error);return;}loadParked(function(){lqTitle();if(then)then('');if(LQ.scene==='work')lqLoadWork();});});}
@@ -1212,13 +1288,15 @@ var cls=n.type==="person"?"lbl-me":"";s+="<text x='"+n.x.toFixed(1)+"' y='"+(n.y
1212
1288
  document.getElementById("graph").innerHTML=s+"</g>";}
1213
1289
 
1214
1290
  // ---- the Map, in the liquid -------------------------------------------------------
1215
- // Your repos and project folders as droplets in the look, placed by their nearest
1216
- // neighbours (mapknn.mjs: what they're built with, the weeks you work on them, what
1217
- // they're about). The closest merge into one shape, the next are joined by a tendril,
1218
- // the rest by a faint line; each cluster is named over its region; what you haven't
1219
- // touched in a while is smaller and sinks; you sit where your recent work is.
1220
- // Point at a droplet: its neighbours light up and say why. Click: its details.
1221
- var LM={G:null,on:false,items:[],edges:[],clusters:[],view:{k:1,x:0,y:0},hot:null,pan:null,built:false};
1291
+ // Your repos and project folders as droplets in the look, in lanes by kind: what
1292
+ // needs you first (a question or a review waiting), then repos with your commits, the
1293
+ // other repos, folders without git, and the act-* folders agent runs work in, as one
1294
+ // group until you open it. Each lane runs newest first on a grid as wide as the map,
1295
+ // so no two labels meet however many there are, and a lane's name folds it away.
1296
+ // The page scrolls down the lanes; ctrl+scroll (a pinch), + and - zoom. Point at a
1297
+ // droplet: the ones most like it (mapknn.mjs) light up and say why. Click: its details.
1298
+ var LM={G:null,on:false,all:[],items:[],edges:[],lanes:[],shut:{runs:true},view:{k:1,x:0,y:0},hot:null,pan:null,built:false,lw:0,ch:0,cw:172,x0:0};
1299
+ var LMLANES=[['need','Needs you'],['mine','Your repos'],['other','Other repos'],['folders','Folders without git'],['runs','Agent run folders']];
1222
1300
  function lmTilde(p){p=String(p||'');var parts=p.split('/');return (parts[1]==='home'||parts[1]==='Users')&&parts.length>2?'~'+(parts.length>3?'/'+parts.slice(3).join('/'):''):p;}
1223
1301
  function lmName(id){var n=GRAPH&&nodeById(id);return n?n.label:String(id).split('/').pop();}
1224
1302
  function lmNearHtml(id){var k=GRAPH&&GRAPH.knn,ns=k&&k.neighbours&&k.neighbours[id];if(!ns||!ns.length)return '';
@@ -1226,71 +1304,81 @@ return "<div class='k'>Most like it</div><ul class='lmnear'>"+ns.map(function(n)
1226
1304
  function lmNearWire(el){if(el&&el.querySelectorAll)el.querySelectorAll('.lmnear button').forEach(function(b){b.addEventListener('click',function(){selectNode(b.getAttribute('data-id'));});});}
1227
1305
  function lmWeeks(d){var n=GRAPH&&nodeById('repo:'+d.path),w=n&&n.meta&&n.meta.weeks;if(!w||!w.length)return '';var mx=Math.max.apply(null,w.concat([1]));
1228
1306
  return "<div class='k'>Your last 12 weeks</div><div class='lmweeks' role='img' aria-label='"+w.join(', ')+" commits a week'>"+w.map(function(x){return "<i style='height:"+Math.max(2,Math.round(28*x/mx))+"px' title='"+x+" commits'></i>";}).join('')+"</div>";}
1229
- function lmBuild(g){var el=$('lmap');if(!el||!g)return;LM.g=g;var K=g.knn||{neighbours:{},edges:[],clusters:[],vitality:{}};
1230
- var items=g.nodes.filter(function(n){return n.type==='repo'||n.type==='folder';}),mx=1;items.forEach(function(n){var c=(n.meta&&n.meta.commits)||0;if(c>mx)mx=c;});
1231
- var byId={};LM.items=items.map(function(n){var v=K.vitality[n.id]||0,c=(n.meta&&n.meta.commits)||0,fold=n.type==='folder';
1232
- var it={id:n.id,n:n,v:v,fold:fold,r:fold?10:(14+30*Math.sqrt(c/mx))*(0.7+0.3*v),x:0,y:0};byId[n.id]=it;return it;});
1233
- LM.edges=(K.edges||[]).filter(function(e){return byId[e.a]&&byId[e.b];}).map(function(e){return {a:byId[e.a],b:byId[e.b],sim:e.sim};});
1234
- // seed by cluster: each cluster (and each repo on its own) gets its own arc
1235
- var groups=(K.clusters||[]).map(function(c){return c.ids.filter(function(id){return byId[id];}).map(function(id){return byId[id];});}).filter(function(m){return m.length;}),inG={};
1236
- groups.forEach(function(m){m.forEach(function(it){inG[it.id]=1;});});LM.items.forEach(function(it){if(!inG[it.id])groups.push([it]);});
1237
- var tot=0;groups.forEach(function(m){tot+=m.length+0.6;});var acc=-Math.PI/2;
1238
- groups.forEach(function(m){var span=(m.length+0.6)/tot*2*Math.PI,mid=acc+span/2;acc+=span;var R=180+40*m.length;m.forEach(function(it,i){var a=mid+(i-(m.length-1)/2)*Math.min(0.5,span/m.length);it.x=Math.cos(a)*R*(i%2?1.12:0.92);it.y=Math.sin(a)*R*0.8*(i%2?1.12:0.92);});});
1239
- var link={};LM.edges.forEach(function(e){link[e.a.id+'|'+e.b.id]=link[e.b.id+'|'+e.a.id]=e.sim;});
1240
- var L=LM.items;for(var it=0;it<700;it++){
1241
- LM.edges.forEach(function(e){var dx=e.b.x-e.a.x,dy=e.b.y-e.a.y,d=Math.sqrt(dx*dx+dy*dy)||1,rest=e.sim>=0.45?(e.a.r+e.b.r)*1.15:e.a.r+e.b.r+40+150*(1-e.sim),f=(d-rest)*0.05*Math.min(1,e.sim*2.2)/d;e.a.x+=dx*f;e.a.y+=dy*f;e.b.x-=dx*f;e.b.y-=dy*f;});
1242
- for(var i=0;i<L.length;i++){var a=L[i];for(var j=i+1;j<L.length;j++){var b=L[j],dx=b.x-a.x,dy=b.y-a.y,d=Math.sqrt(dx*dx+dy*dy)||1,sm=link[a.id+'|'+b.id],mn=sm>=0.45?(a.r+b.r)*1.08:a.r+b.r+(sm?30:70);if(d<mn){var f=(mn-d)*0.3/d;a.x-=dx*f;a.y-=dy*f;b.x+=dx*f;b.y+=dy*f;}}
1243
- a.x*=0.996;a.y=a.y*0.996+(1-a.v)*0.5;}}
1244
- // you: where your recent work is
1245
- var sx=0,sy=0,sw=0;L.forEach(function(a){var w=a.fold?0:a.v*(1+((a.n.meta&&a.n.meta.commits)||0));sx+=a.x*w;sy+=a.y*w;sw+=w;});
1246
- LM.me={id:'me',x:sw?sx/sw:0,y:sw?sy/sw-10:-10,r:14};for(var q=0;q<200;q++)L.forEach(function(a){var dx=LM.me.x-a.x,dy=LM.me.y-a.y,d=Math.sqrt(dx*dx+dy*dy)||1,mn=a.r+LM.me.r+28;if(d<mn){LM.me.x+=dx/d*(mn-d)*0.5;LM.me.y+=dy/d*(mn-d)*0.5;}});
1247
- LM.clusters=(K.clusters||[]).map(function(c){return {label:c.label,m:c.ids.map(function(id){return byId[id];}).filter(Boolean)};}).filter(function(c){return c.m.length>1;});
1248
- // labels
1249
- var ov=$('lmov');if(ov){ov.innerHTML=L.map(function(a,i){var m=a.n.meta||{},sub=a.fold?'folder, no git':(m.commits||0)+((m.commits||0)===1?' commit':' commits')+(m.last?' &middot; '+lmAgo(m.last):'');return "<button type='button' class='lml"+(a.v<0.25&&!a.fold?' dormant':'')+(a.fold?' fold':'')+"' data-i='"+i+"'><b>"+esc(a.n.label)+"</b><span>"+sub+"</span></button>";}).join('')+
1250
- "<div class='lmme' id='lmme' aria-hidden='true'><i></i><b>you are here</b></div>"+LM.clusters.map(function(c,i){return "<div class='lmc' data-c='"+i+"'>"+esc(c.label)+"</div>";}).join('')+L.filter(function(a){return a.fold;}).map(function(a){return "<i class='lmring' data-id='"+escQ(a.id)+"'></i>";}).join('');
1251
- LM.labs=[].slice.call(ov.querySelectorAll('.lml[data-i]'));LM.cls=[].slice.call(ov.querySelectorAll('.lmc'));LM.rings=[].slice.call(ov.querySelectorAll('.lmring'));
1252
- LM.labs.forEach(function(b){var a=L[+b.getAttribute('data-i')];b.addEventListener('click',function(){selectNode(a.id);});b.addEventListener('pointerenter',function(){lmHot(a);});b.addEventListener('focus',function(){lmHot(a);});b.addEventListener('pointerleave',function(){lmHot(null);});b.addEventListener('blur',function(){lmHot(null);});});}
1307
+ // what needs you (home.mjs workScene): per project, a question or work ready for your
1308
+ // OK; per run folder, the questions its run left you
1309
+ function lmNeeds(){var o={repo:{},path:{}};((LQ.workData&&LQ.workData.projects)||[]).forEach(function(p){var s=p.asks?p.asks+' question'+(p.asks>1?'s':'')+' for you':p.ready?'ready for your OK':'';if(s)o.repo[p.repo]=s;
1310
+ (p.qs||[]).forEach(function(q){if(q.path&&!o.path[q.path])o.path[q.path]=String(q.q||'a question for you');});});return o;}
1311
+ // when it was last worked on: its last commit, or an agent's last session there
1312
+ function lmWhen(m){var t=m&&m.last?Date.parse(m.last)||0:0;((m&&m.agents)||[]).forEach(function(a){if(a.last*1000>t)t=a.last*1000;});return t;}
1313
+ function lmBuild(g){var el=$('lmap');if(!el||!g)return;LM.g=g;LM.knn=g.knn||{neighbours:{},edges:[],clusters:[],vitality:{}};
1314
+ if(!LQ.workData){LQ.workData={};api('/api/work').then(function(w){LQ.workData=w||{};if(LM.g===g)lmBuild(g);}).catch(function(){});}
1315
+ var nodes=g.nodes.filter(function(n){return n.type==='repo'||n.type==='folder';}),mx=1,need=lmNeeds();nodes.forEach(function(n){var c=(n.meta&&n.meta.commits)||0;if(c>mx)mx=c;});
1316
+ LM.all=nodes.map(function(n){var m=n.meta||{},v=LM.knn.vitality[n.id]||0,c=m.commits||0,run=/^act-[0-9a-f]{6,}$/i.test(n.label),fold=n.type==='folder'||run,nd=need.path[m.path]||(run?'':need.repo[n.label]||'');
1317
+ return {id:n.id,n:n,v:v,c:c,fold:fold,run:run,when:lmWhen(m),need:nd,lane:nd?'need':run?'runs':fold?'folders':c?'mine':'other',r:fold?9:(11+15*Math.sqrt(c/mx))*(0.75+0.25*v),x:0,y:0};});
1318
+ lmLayout(true);
1253
1319
  // the rest of what you build with, as a quiet row under the map
1254
- var tl=$('lmtools');if(tl){var langs={};L.forEach(function(a){((a.n.meta&&a.n.meta.langs)||[]).forEach(function(x){langs[x]=(langs[x]||0)+1;});});
1320
+ var L=LM.all,tl=$('lmtools');if(tl){var langs={};L.forEach(function(a){((a.n.meta&&a.n.meta.langs)||[]).forEach(function(x){langs[x]=(langs[x]||0)+1;});});
1255
1321
  var lg=Object.keys(langs).sort(function(x,y){return langs[y]-langs[x];});var tools=g.nodes.filter(function(n){return n.type==='tool'||n.type==='agent'||n.type==='ai';});
1256
1322
  tl.innerHTML=(lg.length?"<div class='lmrow'><span class='lmk'>Languages</span>"+lg.map(function(x){return "<button type='button' class='lmt' data-id='lang:"+escQ(x)+"'>"+esc(x)+"<small>"+langs[x]+"</small></button>";}).join('')+"</div>":"")+
1257
1323
  (tools.length?"<div class='lmrow'><span class='lmk'>You build with</span>"+tools.map(function(n){return "<button type='button' class='lmt' data-id='"+escQ(n.id)+"'>"+esc(n.label)+"</button>";}).join('')+"</div>":"");
1258
1324
  tl.querySelectorAll('.lmt').forEach(function(b){b.addEventListener('click',function(){selectNode(b.getAttribute('data-id'));});});}
1259
- lmFit();if(!LM.built){LM.built=true;lmWire();}lmStart();}
1260
- function lmAgo(day){var t=Date.parse(day);if(!t)return day;var d=Math.round((Date.now()-t)/86400000);return d<=0?'today':d===1?'yesterday':d<14?d+' days ago':d<60?Math.round(d/7)+' weeks ago':Math.round(d/30)+' months ago';}
1261
- function lmFit(){var el=$('lmap'),L=LM.items;if(!el||!L.length)return;var x0=1e9,y0=1e9,x1=-1e9,y1=-1e9;L.concat([LM.me]).forEach(function(a){x0=Math.min(x0,a.x-a.r);y0=Math.min(y0,a.y-a.r);x1=Math.max(x1,a.x+a.r);y1=Math.max(y1,a.y+a.r+30);});
1262
- var w=el.clientWidth||900,h=el.clientHeight||520,k=Math.min(1.6,(w-160)/Math.max(1,x1-x0),(h-150)/Math.max(1,y1-y0));LM.view={k:k,x:w/2-(x0+x1)/2*k,y:h/2-(y0+y1)/2*k+20};}
1325
+ if(!LM.built){LM.built=true;lmWire();}lmStart();}
1326
+ // The lanes on a grid as wide as the map is now: a cell a droplet, its name under it.
1327
+ function lmLayout(fit){var el=$('lmap');if(!el)return;var w=el.clientWidth||900,CW=LM.cw,RH=92,cols=Math.max(1,Math.floor((w-40)/CW)),x0=Math.max(20,(w-cols*CW)/2),y=18,items=[],lanes=[];
1328
+ LMLANES.forEach(function(D){var m=LM.all.filter(function(a){return a.lane===D[0];}).sort(function(a,b){return b.when-a.when||b.c-a.c||(a.n.label<b.n.label?-1:1);});if(!m.length)return;
1329
+ var shut=!!LM.shut[D[0]],show=!shut?m:D[0]==='runs'?[{id:'group:runs',group:true,n:{label:m.length+' agent runs',meta:{}},fold:true,r:17,v:1,c:0,need:'',when:m[0].when,lane:'runs',x:0,y:0}]:[];
1330
+ lanes.push({key:D[0],title:D[1],n:m.length,shut:shut,y:y});y+=30;
1331
+ show.forEach(function(a,i){a.x=x0+CW/2+(i%cols)*CW;a.y=y+30+Math.floor(i/cols)*RH;items.push(a);});y+=Math.ceil(show.length/cols)*RH+16;});
1332
+ LM.items=items;LM.lanes=lanes;LM.ch=y;LM.x0=x0;LM.lw=w;LM.lk=null;if(el.style)el.style.height=Math.max(300,Math.round(y))+'px'; // as tall as its lanes: the page scrolls, not the map
1333
+ if(LM.hot&&items.indexOf(LM.hot)<0)LM.hot=null;
1334
+ var byId={};items.forEach(function(a){byId[a.id]=a;});LM.edges=(LM.knn.edges||[]).filter(function(e){return byId[e.a]&&byId[e.b];}).map(function(e){return {a:byId[e.a],b:byId[e.b],sim:e.sim};});
1335
+ var ov=$('lmov');if(ov){ov.innerHTML=items.map(function(a,i){var m=a.n.meta||{},sub=a.group?'act-* folders &middot; show them':a.need?esc(a.need):a.fold?(a.run?'agent run':'folder')+(a.when?' &middot; '+lmAgo(a.when):', no git'):(a.c?a.c+(a.c===1?' commit':' commits'):'not yours')+(m.last?' &middot; '+lmAgo(m.last):'');
1336
+ return "<button type='button' class='lml"+(a.v<0.25&&!a.fold&&!a.need?' dormant':'')+(a.fold?' fold':'')+(a.need?' need':'')+"' data-i='"+i+"' title='"+escQ(a.n.label)+"'><b>"+esc(a.n.label)+"</b><span>"+sub+"</span></button>";}).join('')+
1337
+ lanes.map(function(L,i){return "<button type='button' class='lmlane"+(L.key==='need'?' need':'')+"' data-l='"+i+"' aria-expanded='"+(L.shut?'false':'true')+"' title='"+(L.shut?'show':'fold')+" this lane'>"+esc(L.title)+"<small>"+L.n+"</small><span>"+(L.shut?'show':'fold')+"</span></button>";}).join('')+
1338
+ items.filter(function(a){return a.fold;}).map(function(a){return "<i class='lmring"+(a.group?' group':'')+(a.need?' need':'')+"' data-id='"+escQ(a.id)+"'></i>";}).join('');
1339
+ LM.labs=[].slice.call(ov.querySelectorAll('.lml[data-i]'));LM.lnb=[].slice.call(ov.querySelectorAll('.lmlane'));LM.rings=[].slice.call(ov.querySelectorAll('.lmring'));
1340
+ LM.labs.forEach(function(b){var a=items[+b.getAttribute('data-i')];b.addEventListener('click',function(){lmPick(a);});b.addEventListener('pointerenter',function(){lmHot(a);});b.addEventListener('focus',function(){lmHot(a);});b.addEventListener('pointerleave',function(){lmHot(null);});b.addEventListener('blur',function(){lmHot(null);});});
1341
+ LM.lnb.forEach(function(b){var L=lanes[+b.getAttribute('data-l')];b.addEventListener('click',function(){LM.shut[L.key]=!L.shut;lmLayout(false);});});}
1342
+ if(fit)lmFit();}
1343
+ // a droplet clicked: its details, or the act-* group opened
1344
+ // what needs you opens where you answer it, like its lane on Home
1345
+ function lmPick(a){if(!a)return;if(a.group){LM.shut.runs=false;lmHot(null);lmLayout(false);return;}if(a.need){lmHot(null);lqFocus({kind:'proj',repo:a.run?'ops':a.n.label,path:a.run?(a.n.meta||{}).path:''});return;}selectNode(a.id);}
1346
+ function lmAgo(day){var t=typeof day==='number'?day:Date.parse(day);if(!t)return day;var d=Math.round((Date.now()-t)/86400000);return d<=0?'today':d===1?'yesterday':d<14?d+' days ago':d<60?Math.round(d/7)+' weeks ago':Math.round(d/30)+' months ago';}
1347
+ function lmFit(){var el=$('lmap');if(!el||!LM.all.length)return;if(el.clientWidth&&Math.abs(el.clientWidth-LM.lw)>4)lmLayout(false);LM.view={k:1,x:0,y:0};}
1263
1348
  function lmHot(a){LM.hot=a;var tip=$('lmtip');var near={};if(a&&GRAPH&&GRAPH.knn)(GRAPH.knn.neighbours[a.id]||[]).forEach(function(n){near[n.id]=n;});
1264
- (LM.labs||[]).forEach(function(b){var it=LM.items[+b.getAttribute('data-i')];if(b.classList){b.classList.toggle('near',!!(a&&near[it.id]));b.classList.toggle('far',!!(a&&it!==a&&!near[it.id]));}});
1265
- if(!tip)return;if(!a){tip.className='lmtip';return;}var m=a.n.meta||{},ns=(GRAPH&&GRAPH.knn&&GRAPH.knn.neighbours[a.id])||[];
1266
- tip.innerHTML="<b>"+esc(a.n.label)+"</b><span class='lmts'>"+(a.fold?'a project folder, not a git repo':(m.commits||0)+((m.commits||0)===1?' commit':' commits')+(m.last?' &middot; last '+lmAgo(m.last):'')+(m.branch?' &middot; on '+esc(m.branch):''))+"</span>"+(ns.length?"<div class='lmtn'>"+ns.map(function(n){return "<div><b>"+esc(lmName(n.id))+"</b> "+esc(n.why.join('; ')||'a little alike')+"</div>";}).join('')+"</div>":"<div class='lmtn'>Nothing else is much like it.</div>");tip.className='lmtip on';}
1349
+ (LM.labs||[]).forEach(function(b){var it=LM.items[+b.getAttribute('data-i')];if(b.classList&&it){b.classList.toggle('near',!!(a&&near[it.id]));b.classList.toggle('far',!!(a&&it!==a&&!near[it.id]));}});
1350
+ if(!tip)return;if(!a){tip.className='lmtip';return;}if(a.group){tip.innerHTML="<b>"+esc(a.n.label)+"</b><span class='lmts'>the act-* folders Symbiot's agent runs work in</span><div class='lmtn'>Click to show them all.</div>";tip.className='lmtip on';return;}
1351
+ var m=a.n.meta||{},ns=(GRAPH&&GRAPH.knn&&GRAPH.knn.neighbours[a.id])||[];
1352
+ tip.innerHTML="<b>"+esc(a.n.label)+"</b><span class='lmts'>"+(a.need?esc(a.need)+' &middot; ':'')+(a.run?'the folder an agent run worked in':a.fold?'a project folder, not a git repo':(m.commits||0)+((m.commits||0)===1?' commit':' commits')+(m.last?' &middot; last '+lmAgo(m.last):'')+(m.branch?' &middot; on '+esc(m.branch):''))+"</span>"+(ns.length?"<div class='lmtn'>"+ns.map(function(n){return "<div><b>"+esc(lmName(n.id))+"</b> "+esc(n.why.join('; ')||'a little alike')+"</div>";}).join('')+"</div>":"<div class='lmtn'>Nothing else is much like it.</div>");tip.className='lmtip on';}
1267
1353
  function lmS(p){return {x:p.x*LM.view.k+LM.view.x,y:p.y*LM.view.k+LM.view.y};}
1268
1354
  function lmWire(){var el=$('lmap');if(!el||!el.addEventListener)return;
1269
- el.addEventListener('wheel',function(e){if(!e)return;e.preventDefault();var r=el.getBoundingClientRect(),mx=e.clientX-r.left,my=e.clientY-r.top,k0=LM.view.k,k=Math.max(0.3,Math.min(4,k0*Math.exp(-e.deltaY*0.0015)));LM.view.x=mx-(mx-LM.view.x)*k/k0;LM.view.y=my-(my-LM.view.y)*k/k0;LM.view.k=k;},{passive:false});
1355
+ el.addEventListener('wheel',function(e){if(!e||!e.ctrlKey)return;e.preventDefault();var r=el.getBoundingClientRect(),mx=e.clientX-r.left,my=e.clientY-r.top,k0=LM.view.k,k=Math.max(0.4,Math.min(3,k0*Math.exp(-e.deltaY*0.004)));LM.view.x=mx-(mx-LM.view.x)*k/k0;LM.view.y=my-(my-LM.view.y)*k/k0;LM.view.k=k;},{passive:false});
1270
1356
  el.addEventListener('pointerdown',function(e){if(e.target&&e.target.closest&&e.target.closest('button,.lmtip'))return;LM.pan={x:e.clientX,y:e.clientY,vx:LM.view.x,vy:LM.view.y,moved:false};if(el.setPointerCapture)el.setPointerCapture(e.pointerId);});
1271
1357
  el.addEventListener('pointermove',function(e){if(LM.pan){var dx=e.clientX-LM.pan.x,dy=e.clientY-LM.pan.y;if(Math.abs(dx)+Math.abs(dy)>3)LM.pan.moved=true;LM.view.x=LM.pan.vx+dx;LM.view.y=LM.pan.vy+dy;return;}
1272
1358
  var r=el.getBoundingClientRect(),mx=e.clientX-r.left,my=e.clientY-r.top,hit=null;LM.items.forEach(function(a){var p=lmS(a);if(!hit&&Math.hypot(mx-p.x,my-p.y)<=a.r*LM.view.k+6)hit=a;});if(hit!==LM.hot&&!(e.target&&e.target.closest&&e.target.closest('.lml')))lmHot(hit);});
1273
- el.addEventListener('pointerup',function(e){var p=LM.pan;LM.pan=null;if(p&&!p.moved&&LM.hot&&!(e.target&&e.target.closest&&e.target.closest('button')))selectNode(LM.hot.id);});
1359
+ el.addEventListener('pointerup',function(e){var p=LM.pan;LM.pan=null;if(p&&!p.moved&&LM.hot&&!(e.target&&e.target.closest&&e.target.closest('button')))lmPick(LM.hot);});
1274
1360
  el.addEventListener('pointerleave',function(){if(!LM.pan)lmHot(null);});
1275
- var zi=$('lmin'),zo=$('lmout'),zf=$('lmfit');function z(f){var w=el.clientWidth/2,h=el.clientHeight/2,k0=LM.view.k,k=Math.max(0.3,Math.min(4,k0*f));LM.view.x=w-(w-LM.view.x)*k/k0;LM.view.y=h-(h-LM.view.y)*k/k0;LM.view.k=k;}
1361
+ var zi=$('lmin'),zo=$('lmout'),zf=$('lmfit');function z(f){var w=el.clientWidth/2,h=el.clientHeight/2,k0=LM.view.k,k=Math.max(0.4,Math.min(3,k0*f));LM.view.x=w-(w-LM.view.x)*k/k0;LM.view.y=h-(h-LM.view.y)*k/k0;LM.view.k=k;}
1276
1362
  if(zi)zi.addEventListener('click',function(){z(1.25);});if(zo)zo.addEventListener('click',function(){z(0.8);});if(zf)zf.addEventListener('click',lmFit);}
1277
1363
  function lmStart(){if(LM.on)return;var raf=typeof window.requestAnimationFrame==='function'?function(f){return window.requestAnimationFrame(f);}:null;if(!raf)return;LM.on=true;
1278
1364
  var loop=function(){var p=$('panel-map'),el=$('lmap');if(!p||!el||!el.offsetParent){LM.on=false;return;}LM.fr=(LM.fr||0)+1;if(LM.fr%glEvery(true)===0)lmFrame();raf(loop);};raf(loop);}
1279
- function lmFrame(){var el=$('lmap'),cv=$('lmc');if(!el||!cv||!LM.items.length)return;if(!LM.G)LM.G=lqGL(cv,64);var w=el.clientWidth,h=el.clientHeight,t=LM.t=(LM.t||0)+0.016,still=LQ.theme&&LQ.theme.still,k=LM.view.k;
1280
- var P=LM.items.map(function(a,i){var p=lmS(a);if(!still){p.x+=Math.sin(t*0.4+i*1.7)*2;p.y+=Math.cos(t*0.35+i*2.3)*2;}return p;}),ME=lmS(LM.me);
1281
- // labels, rings, cluster names, tip
1282
- (LM.labs||[]).forEach(function(b){var i=+b.getAttribute('data-i'),a=LM.items[i],p=P[i];if(!b.style)return;b.style.transform='translate('+Math.round(p.x-(b.offsetWidth||0)/2)+'px,'+Math.round(p.y+a.r*k+4)+'px)';});
1283
- var me=$('lmme');if(me&&me.style)me.style.transform='translate('+Math.round(ME.x-(me.offsetWidth||0)/2)+'px,'+Math.round(ME.y-7)+'px)';
1365
+ function lmFrame(){var el=$('lmap'),cv=$('lmc');if(!el||!cv||!LM.items.length)return;if(!LM.G)LM.G=lqGL(cv,64);var w=el.clientWidth,h=el.clientHeight,t=LM.t=(LM.t||0)+0.016,still=LQ.theme&&LQ.theme.still;
1366
+ if(w&&Math.abs(w-LM.lw)>4)lmLayout(false);
1367
+ // kept on the lanes: no scrolling past the top or the last lane
1368
+ var V=LM.view,k=V.k,lo=Math.min(12,h-LM.ch*k-12);V.y=Math.max(lo,Math.min(12,V.y));V.x=Math.max(Math.min(0,w-LM.lw*k)-40,Math.min(Math.max(0,w-LM.lw*k)+40,V.x));
1369
+ var P=LM.items.map(function(a,i){var p=lmS(a);if(!still){p.x+=Math.sin(t*0.4+i*1.7)*1.5;p.y+=Math.cos(t*0.35+i*2.3)*1.5;}return p;});
1370
+ // a name never wider than its cell, whatever the zoom
1371
+ if(LM.lk!==k){LM.lk=k;(LM.labs||[]).forEach(function(b){if(b.style)b.style.maxWidth=Math.max(44,Math.round(LM.cw*k-14))+'px';if(b.classList)b.classList.toggle('tiny',k<0.75);});}
1372
+ (LM.labs||[]).forEach(function(b){var i=+b.getAttribute('data-i'),a=LM.items[i],p=P[i];if(!b.style||!a)return;b.style.transform='translate('+Math.round(p.x-(b.offsetWidth||0)/2)+'px,'+Math.round(p.y+a.r*k+4)+'px)';});
1373
+ (LM.lnb||[]).forEach(function(b){var L=LM.lanes[+b.getAttribute('data-l')];if(b.style&&L)b.style.transform='translate('+Math.round(LM.x0*k+V.x)+'px,'+Math.round(L.y*k+V.y)+'px)';});
1284
1374
  (LM.rings||[]).forEach(function(rg){var i=-1;LM.items.forEach(function(a,j){if(a.id===rg.getAttribute('data-id'))i=j;});if(i<0||!rg.style)return;var R=Math.round(LM.items[i].r*k);rg.style.width=rg.style.height=(2*R)+'px';rg.style.transform='translate('+Math.round(P[i].x-R)+'px,'+Math.round(P[i].y-R)+'px)';});
1285
- (LM.cls||[]).forEach(function(c){var cl=LM.clusters[+c.getAttribute('data-c')],sx=0,top=1e9;cl.m.forEach(function(a){var i=LM.items.indexOf(a);sx+=P[i].x;top=Math.min(top,P[i].y-a.r*k);});if(c.style)c.style.transform='translate('+Math.round(sx/cl.m.length-(c.offsetWidth||0)/2)+'px,'+Math.round(top-30)+'px)';});
1286
- var tip=$('lmtip');if(tip&&LM.hot){var hi=LM.items.indexOf(LM.hot),hp=P[hi],tw=tip.offsetWidth||280,th=tip.offsetHeight||100,tx=Math.max(10,Math.min(w-tw-10,hp.x+LM.hot.r*k+16)),ty=Math.max(10,Math.min(h-th-10,hp.y-th/2));if(tx<hp.x&&hp.x+LM.hot.r*k+16+tw>w)tx=Math.max(10,hp.x-LM.hot.r*k-16-tw);tip.style.transform='translate('+Math.round(tx)+'px,'+Math.round(ty)+'px)';}
1287
- // faint lines for the weak links
1288
- var sv=$('lmlines');if(sv){var ln='';LM.edges.forEach(function(e){if(e.sim>=0.3)return;var a=P[LM.items.indexOf(e.a)],b=P[LM.items.indexOf(e.b)],hot=LM.hot&&(LM.hot===e.a||LM.hot===e.b);ln+="<line x1='"+a.x.toFixed(1)+"' y1='"+a.y.toFixed(1)+"' x2='"+b.x.toFixed(1)+"' y2='"+b.y.toFixed(1)+"' class='"+(hot?'hot':'')+"'/>";});sv.innerHTML=ln;}
1375
+ var tip=$('lmtip');if(tip&&LM.hot){var hi=LM.items.indexOf(LM.hot),hp=P[hi];if(hp){var tw=tip.offsetWidth||280,th=tip.offsetHeight||100,tx=Math.max(10,Math.min(w-tw-10,hp.x+LM.hot.r*k+16)),ty=Math.max(10,Math.min(h-th-10,hp.y-th/2));if(tx<hp.x&&hp.x+LM.hot.r*k+16+tw>w)tx=Math.max(10,hp.x-LM.hot.r*k-16-tw);tip.style.transform='translate('+Math.round(tx)+'px,'+Math.round(ty)+'px)';}}
1376
+ // what the droplet you point at is most like, as faint lines to them
1377
+ var sv=$('lmlines');if(sv){var ln='';if(LM.hot)LM.edges.forEach(function(e){if(e.a!==LM.hot&&e.b!==LM.hot)return;var a=P[LM.items.indexOf(e.a)],b=P[LM.items.indexOf(e.b)];ln+="<line x1='"+a.x.toFixed(1)+"' y1='"+a.y.toFixed(1)+"' x2='"+b.x.toFixed(1)+"' y2='"+b.y.toFixed(1)+"' class='hot'/>";});sv.innerHTML=ln;}
1289
1378
  var G=LM.G;if(!G)return;var gl=G.gl,d=glDpr(2),W=Math.max(1,Math.floor(w*d)),H=Math.max(1,Math.floor(h*d));if(cv.width!==W||cv.height!==H){cv.width=W;cv.height=H;gl.viewport(0,0,W,H);}
1290
1379
  var o=G.out;for(var z=0;z<o.length;z++)o[z]=0;var n=0,put=function(x,y,r,a){if(n>=64||r<=0)return;o[n*4]=x/w;o[n*4+1]=y/h;o[n*4+2]=r;o[n*4+3]=a||0;n++;};
1291
- LM.items.forEach(function(a,i){if(!a.fold)put(P[i].x,P[i].y,a.r*k*(LM.hot===a?1.08:1),0);});
1292
- // tendrils: a chain of small drops, close enough to always bridge, for the middling links
1293
- LM.edges.forEach(function(e){if(e.sim<0.3||e.a.fold||e.b.fold)return;var A=P[LM.items.indexOf(e.a)],B=P[LM.items.indexOf(e.b)],dx=B.x-A.x,dy=B.y-A.y,D=Math.sqrt(dx*dx+dy*dy)||1,gap=D-(e.a.r+e.b.r)*k;if(gap<=4)return;var m=Math.min(20,Math.ceil(gap/8)),sp=gap/(m+1),rr=Math.max(1.8,sp*0.4);for(var c=1;c<=m;c++){var f=(e.a.r*k+sp*c)/D;put(A.x+dx*f,A.y+dy*f,rr,0);}});
1380
+ // only what's in view: the lanes can hold more than the liquid draws at once
1381
+ LM.items.forEach(function(a,i){var R=a.r*k;if(!a.fold&&P[i].y>-R-20&&P[i].y<h+R+20&&P[i].x>-R-20&&P[i].x<w+R+20)put(P[i].x,P[i].y,R*(LM.hot===a?1.08:1),a.need?1:0);});
1294
1382
  var th=LQ.theme||{},U=G.U;gl.uniform2f(U.uRes,W,H);gl.uniform1f(U.uT,t);gl.uniform1f(U.uDpr,d);gl.uniform4fv(U.uB,o);gl.uniform3f(U.uRip,0,0,-10);gl.uniform1f(U.uStyle,th.look==='glass'?0:th.look==='pearl'?2:1);gl.uniform1f(U.uContrast,th.contrast?1:0);gl.uniform1f(U.uExposure,th.night?0.82:1);gl.drawArrays(gl.TRIANGLES,0,3);}
1295
1383
 
1296
1384
  // The write-ups as a document: # headings, - bullets, **bold**; everything escaped first.
@@ -1384,12 +1472,12 @@ box.querySelectorAll('.fchip[data-dim]').forEach(function(c){c.addEventListener(
1384
1472
  document.getElementById('archtoggle').addEventListener('click',loadArchived);}
1385
1473
  function renderTasks(){var el=document.getElementById('tasklist');TARCH=false;if(el&&el.classList)el.classList.toggle('onerepo',!!TFILTER.repo);renderFilter();renderNeeds();lqTitle();
1386
1474
  var list=ALLTASKS.filter(function(t){return !t.review&&(!TFILTER.type||(t.type||'Features & other')===TFILTER.type)&&(!TFILTER.repo||t.repo===TFILTER.repo);});
1387
- if(!list.length){el.innerHTML="<div class='muted' style='margin-top:12px'>"+(ALLTASKS.length?"Nothing matches this filter.":"No tasks yet. Tick ideas in a repo review, or add one above.")+"</div>";return;}
1475
+ if(!list.length){el.innerHTML="<div class='muted' style='margin-top:12px'>"+(ALLTASKS.length?"Nothing matches this filter.":"No tasks queued. Tell Symbiot one in the bar at the bottom, or tick ideas in a repo review on the Map.")+"</div>";return;}
1388
1476
  var groups={};list.forEach(function(t){var ty=t.type||'Features & other';(groups[ty]=groups[ty]||[]).push(t);});
1389
1477
  var keys=Object.keys(groups).sort(function(a,b){var ia=TORDER.indexOf(a),ib=TORDER.indexOf(b);return (ia<0?99:ia)-(ib<0?99:ib);});
1390
1478
  var h="";keys.forEach(function(ty){h+="<div class='tgroup'>"+esc(ty)+" <span class='tcount'>"+groups[ty].length+"</span></div>";groups[ty].forEach(function(t){h+=taskRow(t,false);});});
1391
1479
  el.innerHTML=h;wireTaskRows(el);}
1392
- function loadTasks(){TARCH=false;api('/api/tasks/sync',{}).then(function(){api('/api/tasks').then(function(list){ALLTASKS=list;renderTasks();});loadPending();});}
1480
+ function loadTasks(){TARCH=false;api('/api/tasks/sync',{}).then(function(){api('/api/tasks').then(function(list){ALLTASKS=list;renderTasks();wdJump();});loadPending();});}
1393
1481
  // While an agent is still working in a repo on the review list, its Approve is
1394
1482
  // greyed out; check again every few seconds, and when it finishes, sync (its last
1395
1483
  // ticks land in review) so Approve unlocks without reopening the tab. The poll
@@ -1568,7 +1656,7 @@ return g('Needs you',needs)+g('At work',run)+(done.length?"<details class='agdon
1568
1656
  function agentsDraw(){api('/api/agents').then(function(list){var el=document.getElementById('agentslist');
1569
1657
  if(!list||!list.length){AGENTLIST=[];el.innerHTML="<div class='muted' style='margin-top:12px'>No agents yet. In <b>Tasks</b>, tick ideas and hit <b>Send to repos</b> (with an agent command set in Settings) &mdash; you'll watch it work here.</div>";stopAgentsPoll();return;}
1570
1658
  var anyRunning=list.some(function(a){return a.status==='running';});
1571
- if(answering()||selIn(el)){stopAgentsPoll();if(anyRunning&&current==='agents')agentsTimer=setTimeout(loadAgents,2000);else selWait('agents',loadAgents);return;} // don't re-render under someone typing an answer, or selecting text to copy
1659
+ if(answering()||selIn(el)){stopAgentsPoll();if(anyRunning&&current==='tasks')agentsTimer=setTimeout(loadAgents,2000);else selWait('agents',loadAgents);return;} // don't re-render under someone typing an answer, or selecting text to copy
1572
1660
  saveDrafts(el);AGENTLIST=list;
1573
1661
  var agentBlock=function(a){var cls=a.status==='running'?'run':(a.status==='done'?'ok':'fail');
1574
1662
  var st=a.status==='running'?('working &middot; '+fmtE(a.elapsed)):(esc(a.status)+' &middot; '+fmtE(a.elapsed)+(a.exitCode!=null?' &middot; exit '+a.exitCode:''));
@@ -1602,8 +1690,9 @@ return "<div class='agent' data-i='"+list.indexOf(a)+"'>"+b+"</div>";};
1602
1690
  el.innerHTML=agentGroups(list,agentBlock);var dn=el.querySelector('.agdone');if(dn)dn.addEventListener('toggle',function(){AGDONEOPEN=dn.open;});
1603
1691
  el.querySelectorAll('.alogout').forEach(function(p){p.scrollTop=p.scrollHeight;});
1604
1692
  restoreDrafts(el);wireAsks(el);wireWaits(el);wireNeeds(el);wireFacts(el);lqLight();
1693
+ wdJump();
1605
1694
  el.querySelectorAll('.apark').forEach(function(btn){btn.addEventListener('click',function(){var g=AGENTLIST[+btn.closest('.agent').getAttribute('data-i')];if(!g)return;btn.disabled=true;lqPark('',btn.getAttribute('data-on')==='1',function(err){btn.disabled=false;$('agentsmsg').innerHTML=err?"<div class='note err'>"+esc(err)+"</div>":'';loadAgents();},g.path);});});
1606
- stopAgentsPoll();if(anyRunning&&current==='agents')agentsTimer=setTimeout(loadAgents,2000);});}
1695
+ stopAgentsPoll();if(anyRunning&&current==='tasks')agentsTimer=setTimeout(loadAgents,2000);});}
1607
1696
  function loadDrift(){var out=document.getElementById('driftout');out.innerHTML="<div class='muted' style='margin-top:12px'>Reading your repos&hellip;</div>";
1608
1697
  var ci=document.getElementById('driftci').checked?'1':'0';var ft=document.getElementById('driftfetch').checked?'1':'0';
1609
1698
  api('/api/drift?ci='+ci+'&fetch='+ft).then(function(d){driftLoaded=true;var repos=d.repos||[];var risky=repos.filter(function(r){return r.flags.some(function(f){return f.level==='warn';});});
@@ -1625,7 +1714,7 @@ var n=(r.written||[]).length;var opens=[];
1625
1714
  var h="<div class='drift pushed'><div class='dh'><span class='dot-c'></span><span class='dn'>Sent to "+n+" repo"+(n===1?"":"s")+"</span><span class='dd'>"+(r.written||[]).map(function(w){return "<span title='"+esc(w.file).replace(/'/g,'&#39;')+"'>&#10003; <b>"+esc(w.name)+"</b> ("+w.count+" task"+(w.count===1?"":"s")+(w.held?", <i class=ic-pause></i> held till its running agent finishes":"")+")</span>";}).join(" &middot; ")+"</span></div>";
1626
1715
  if(r.unresolved&&r.unresolved.length){var names=r.unresolved.map(function(u){return u.name;}).join(", ");var hasNoRepo=r.unresolved.some(function(u){return u.name==='(no repo)';});h+="<div class='dd' style='margin-top:6px'><i class=ic-warn></i> not sent: "+esc(names)+". "+(hasNoRepo?"Tell Symbiot in the bar at the bottom which project it&#39;s for, so it has somewhere to go.":"That repo isn't in the map &mdash; add its folder in Settings.")+"</div>";}
1627
1716
  if(r.handoff&&r.written&&r.written.length){opens=r.written.map(function(w){return api('/api/open',{path:w.path}).then(function(x){if(x&&x.blocked){var d=document.createElement('div');d.className='dd';d.innerHTML="<i class=ic-pause></i> <b>"+esc(w.name)+"</b>: "+esc(x.note)+" <button class='ghost' style='padding:2px 8px;font-size:12px'>Start it anyway</button>";d.querySelector('button').addEventListener('click',function(){this.disabled=true;api('/api/open',{path:w.path,force:true}).then(function(y){d.innerHTML=y&&y.opened?"&#10003; Started an agent in <b>"+esc(w.name)+"</b>.":"<i class=ic-warn></i> <b>"+esc(w.name)+"</b>: "+(y&&y.busy?"an agent is already running there.":"it didn&#39;t start. Check the command in Settings.");});});o.appendChild(d);}if(x&&x.busy){var d=document.createElement('div');d.className='dd';d.innerHTML="<i class=ic-warn></i> <b>"+esc(w.name)+"</b> already has an agent running, so another wasn&#39;t started. "+(x.auto?"Its new tasks are held, and an agent starts on them when it finishes.":"Its new tasks are held until it finishes. After that, an agent starts on them the next time this tab checks the repo, or send again.");o.appendChild(d);}}).catch(function(){});});h+="<div class='dd'><i class=ic-bot></i> Handed "+n+" repo"+(n===1?"":"s")+" to your agent &mdash; opening the <b>Agents</b> tab to watch it work&hellip;</div>";setTimeout(function(){setTab('agents');},500);}
1628
- else{h+="<div class='dd' style='margin-top:8px'>Set an <b>agent command</b> in Settings to auto-run it on send (and watch it in the Agents tab). For now, tell your agent: <b>“Read .symbiot/TASKS.md and implement the unchecked items.”</b></div>";}
1717
+ else{h+="<div class='dd' style='margin-top:8px'>Set an <b>agent command</b> in Settings to auto-run it on send (and watch it on the Workdesk). For now, tell your agent: <b>“Read .symbiot/TASKS.md and implement the unchecked items.”</b></div>";}
1629
1718
  h+="</div>";
1630
1719
  o.innerHTML=h;
1631
1720
  // re-enable only once every handoff has started (or been refused), so a double click can't send twice
@@ -1775,7 +1864,7 @@ if(!items.length){o.innerHTML="<div class='muted' style='margin-top:8px'>Nothing
1775
1864
  o.innerHTML="<div class='out2'><b>"+r.count+"</b> sent in the last 7 days &mdash; what a write-up would see:<br>"+items.map(function(x){return esc(x.date)+" &nbsp;"+esc(x.subject)+" &nbsp;&rarr; "+esc((x.to||[]).join(', ')||'?');}).join("<br>")+"</div>";});}
1776
1865
  function setupLocalUI(){var o=document.getElementById('setupout');o.innerHTML="Setting up a local model&hellip;";
1777
1866
  api('/api/setup-local',{}).then(function(r){if(r.error==='not-installed'){var i=r.install||{};o.innerHTML="Ollama isn't installed. Run this once, then click again:<div class='out2'>"+esc(i.cmd||'')+"</div><div class='muted' style='font-size:11px'>or download: "+esc(i.alt||'https://ollama.com/download')+"</div>";return;}
1778
- o.innerHTML="Pulling <b>"+esc(r.model)+"</b> &mdash; watch it in the <b>Agents</b> tab. Symbiot switches to it automatically when the download finishes.";setTimeout(function(){setTab('agents');},700);}).catch(function(e){o.innerHTML="<span class='err'>Setup failed: "+esc(String((e&&e.message)||e))+"</span>";});}
1867
+ o.innerHTML="Pulling <b>"+esc(r.model)+"</b> &mdash; watch it on the <b>Workdesk</b>. Symbiot switches to it automatically when the download finishes.";setTimeout(function(){setTab('agents');},700);}).catch(function(e){o.innerHTML="<span class='err'>Setup failed: "+esc(String((e&&e.message)||e))+"</span>";});}
1779
1868
  function selectNode(id){sel=id;render();updateBar(id);var n=nodeById(id);api('/api/node?id='+encodeURIComponent(id)).then(showDetail);
1780
1869
  if(n&&n.type==='repo'&&n.meta&&n.meta.path){loadReview(n.label,n.meta.path);}else{hideReview();}}
1781
1870
  function screenToGraph(el,ev){var rc=el.getBoundingClientRect();var mx=(ev.clientX-rc.left)/rc.width*GW;var my=(ev.clientY-rc.top)/rc.height*GH;return {x:(mx-view.x)/view.k,y:(my-view.y)/view.k};}
@@ -1945,7 +2034,7 @@ el.innerHTML="<div class='pshead'><span>Drafts to post</span><small>Symbiot draf
1945
2034
  var pl=el.querySelector('.plink');if(pl)pl.addEventListener('click',function(){pl.disabled=true;api('/api/links/link',{id:'linkedin'}).then(function(r){pl.disabled=false;mkMsg(r&&!r.error?'ok':'err',r&&!r.error?"Opened LinkedIn in Symbiot&#39;s browser. Sign in as you normally do, then close that window.":esc((r&&r.error)||'failed'));});});return;}
1946
2035
  if(p.posts.length){h+="<div class='pshead'><span>Drafts to post</span><small>"+p.posts.length+" waiting on you &middot; Approve copies one and opens LinkedIn, where you post it yourself</small></div><div class='pshelf'>";
1947
2036
  h+=p.posts.map(function(x){var ed=Object.prototype.hasOwnProperty.call(PEDIT,x.id);
1948
- return "<div class='rcard pcard"+(ed?" editing":"")+"' data-id='"+escQ(x.id)+"'><div class='rhead'><b>"+esc(L[x.kind]||x.kind)+"</b><span class='muted'>LinkedIn &middot; drafted "+agoTxt(x.drafted)+(x.edited?" &middot; edited":"")+"</span></div>"
2037
+ return "<div class='rcard pcard"+(ed?" editing":"")+"' data-id='"+escQ(x.id)+"'><div class='rhead'><b>"+mkProd(mkPostProduct(x.id))+esc(L[x.kind]||x.kind)+"</b><span class='muted'>LinkedIn &middot; drafted "+agoTxt(x.drafted)+(x.edited?" &middot; edited":"")+"</span></div>"
1949
2038
  +(ed?"<textarea class='pedit' maxlength='3000'>"+esc(PEDIT[x.id])+"</textarea>":"<div class='ptext'>"+esc(x.text)+"</div><button type='button' class='pmore' aria-expanded='false'>Read it all</button>")
1950
2039
  +(x.show&&!(x.media||[]).length?"<div class='pshow'><span class='muted'>Picture idea:</span> "+esc(x.show)+"</div>":"")
1951
2040
  +pmediaHtml(x)
@@ -2004,7 +2093,38 @@ postsMsg('ok',"&#10003; Added "+r.added+" of your LinkedIn posts: "+r.total+" in
2004
2093
  // can be drafted, or are (home.mjs marketing).
2005
2094
  var MKT=null;
2006
2095
  function mkMsg(cls,html){var o=$('mkmsg');if(o)o.innerHTML=html?"<div class='note "+cls+"'>"+html+"</div>":'';}
2007
- function loadMarketing(){loadPostsUI();api('/api/marketing').then(function(d){if(d&&!d.error){MKT=d;renderMarketing();}});}
2096
+ function loadMarketing(){loadPostsUI();api('/api/marketing').then(function(d){if(d&&!d.error){MKT=d;renderMarketing();renderMkLane();if(POSTS)renderPosts();}});}
2097
+ // The Marketing lane (home.mjs marketingState): what needs you there first (its agent's
2098
+ // questions, its work waiting for your OK, the week's drafts), then its tasks, each tagged
2099
+ // with the product it markets, an Add for a new one, what its agent drafted, and other
2100
+ // lanes' tasks about marketing, which move to it in a click. Opened from its lit orb, what
2101
+ // needs you is lit (MKLIT).
2102
+ var MKLIT=false;
2103
+ function mkProd(p){return p?"<span class='mkp'>"+esc(p)+"</span>":"";}
2104
+ function mkPostProduct(id){var ns=(MKT&&MKT.lane&&MKT.lane.needs)||[];for(var i=0;i<ns.length;i++)if(ns[i].id==='draft:'+id)return ns[i].product||'';return '';}
2105
+ function loadMkTasks(){renderMkLane();}
2106
+ function renderMkLane(){var L=(MKT&&MKT.lane)||null,nd=$('mkneeds'),el=$('mktasks');if(!nd||!el)return;if(!L){nd.innerHTML='';el.innerHTML='';return;}
2107
+ var ns=L.needs||[],lbl={ask:'Answer',approve:'Review',draft:'Show it'},kind={ask:'its agent asks',approve:'waits for your OK',draft:'a draft to post'};
2108
+ nd.innerHTML=ns.length?"<div class='mkneeds"+(MKLIT?" lit":"")+"'><div class='pshead'><span>Needs you &middot; "+ns.length+"</span><small>only you can do these</small></div>"+ns.map(function(n,i){return "<div class='task mkneed' data-i='"+i+"'><span class='t'>"+mkProd(n.product)+esc(n.text)+" <span class='muted' style='font-size:11px'>"+kind[n.kind]+"</span></span><button class='"+(i?'ghost':'act')+" mkact'>"+lbl[n.kind]+"</button></div>";}).join('')+"</div>":'';
2109
+ nd.querySelectorAll('.mkact').forEach(function(b){b.addEventListener('click',function(){var n=ns[+b.closest('.mkneed').getAttribute('data-i')];if(!n)return;
2110
+ if(n.kind==='draft'){var c=document.querySelector("#boardposts .pcard[data-id='"+n.id.slice(6)+"']");if(c){document.querySelectorAll('#panel-marketing .lit').forEach(function(x){if(x!==c)x.classList.remove('lit');});c.classList.add('lit');if(c.scrollIntoView)c.scrollIntoView({block:'center',behavior:'smooth'});}return;}
2111
+ lqFocus({kind:'proj',repo:'marketing'});});});
2112
+ var prods=L.products||[],ln=L.lane||[],els=L.elsewhere||[],fs=L.files||[],st={waiting:'waiting',working:'its agent is at work',review:'done, waits for your OK'};
2113
+ var h="<div class='pshead'><span>Marketing lane</span><small>"+(ln.length?ln.length+" task"+(ln.length>1?"s":"")+(L.working?" &middot; its agent is at work":L.waiting?" &middot; "+L.waiting+" waiting for its agent":""):"Its own agent, for every product: tell it what to make, and say which product it&#39;s for.")+"</small></div>"
2114
+ +"<form class='row mkadd'><label class='mkl' for='mkprod'>Product</label><input id='mkprod' class='mkprod' list='mkprodlist' autocomplete='off' placeholder='Symbiot, Dailify&hellip;' maxlength='40'><datalist id='mkprodlist'>"+prods.map(function(p){return "<option value='"+escQ(p)+"'>";}).join('')+"</datalist><label class='mkl' for='mktext' style='position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)'>Task</label><input id='mktext' class='mktext' autocomplete='off' placeholder='A launch post, a demo video, a campaign&hellip;' maxlength='4000'><button type='submit' class='act'>Add</button></form>"
2115
+ +ln.map(function(t){return "<div class='task' data-id='"+escQ(t.id)+"'><span class='t'>"+mkProd(t.product)+esc(t.text)+" <span class='muted' style='font-size:11px'>"+st[t.status]+"</span></span><button class='ghost mkopen' data-repo='marketing' title='open it on the Workdesk'>Open</button></div>";}).join('')
2116
+ +(L.waiting&&!L.working?"<div class='row'><button class='act mkgo'>Start its agent</button><span class='muted' style='font-size:12px'>it drafts; nothing is posted until you do it</span></div>":"")
2117
+ +(fs.length?"<div class='pshead' style='margin-top:14px'><span>What its agent drafted</span><small>in the lane&#39;s folder, newest first</small></div>"+fs.slice(0,10).map(function(f){return "<div class='task' data-rel='"+escQ(f.rel)+"'><span class='t'>"+mkProd(f.product)+esc(f.name)+" <span class='muted' style='font-size:11px'>"+agoTxt(f.at)+"</span></span><button class='ghost mkfile' title='open it in your editor'>Open</button></div>";}).join(''):"")
2118
+ +(els.length?"<div class='pshead' style='margin-top:14px'><span>About marketing, in other projects</span><small>Move one here and Marketing&#39;s agent takes it, for its product</small></div>"+els.map(function(t){return "<div class='task' data-id='"+escQ(t.id)+"'><span class='t'>"+mkProd(t.product)+esc(t.text)+" <span class='muted' style='font-size:11px'>"+esc(t.name||t.repo||'no project')+"</span></span><button class='ghost mkmove'>Move to Marketing</button><button class='ghost mkopen' data-repo='"+escQ(t.repo||'')+"' title='open its project on the Workdesk'>Open</button></div>";}).join(''):"");
2119
+ var keep=document.activeElement,typed=keep&&keep.closest&&keep.closest('#mktasks .mkadd')?[($('mkprod')||{}).value,($('mktext')||{}).value,keep.id]:null;
2120
+ el.innerHTML=h;if(typed){$('mkprod').value=typed[0]||'';$('mktext').value=typed[1]||'';var f0=$(typed[2]);if(f0&&f0.focus)f0.focus();}
2121
+ var fm=el.querySelector('.mkadd');if(fm)fm.addEventListener('submit',function(ev){ev.preventDefault();var t=$('mktext'),pr=$('mkprod'),bt=fm.querySelector('button');if(!t.value.trim()){t.focus();return;}bt.disabled=true;
2122
+ api('/api/marketing/task',{text:t.value,product:pr.value}).then(function(r){bt.disabled=false;if(!r||r.error){mkMsg('err',esc((r&&r.error)||'failed'));return;}mkMsg('ok',r.duplicate?"It&#39;s on Marketing&#39;s list already.":"Added to Marketing"+(pr.value?" for "+esc(pr.value):"")+". Start its agent when you&#39;re ready.");t.value='';loadMarketing();}).catch(function(e){bt.disabled=false;mkMsg('err',esc(String((e&&e.message)||e)));});});
2123
+ el.querySelectorAll('.mkopen').forEach(function(b){b.addEventListener('click',function(){TFILTER.repo=b.getAttribute('data-repo')||'';TFILTER.type='';lqPool('tasks');if(typeof loadTasks==='function')loadTasks();});});
2124
+ el.querySelectorAll('.mkmove').forEach(function(b){b.addEventListener('click',function(){var id=b.closest('.task').getAttribute('data-id');b.disabled=true;api('/api/marketing/move',{id:id}).then(function(r){if(!r||r.error){b.disabled=false;mkMsg('err',esc((r&&r.error)||'failed'));return;}mkMsg('ok',"Moved to Marketing: its agent takes it from now on.");loadMarketing();});});});
2125
+ el.querySelectorAll('.mkfile').forEach(function(b){b.addEventListener('click',function(){api('/api/marketing/open',{rel:b.closest('.task').getAttribute('data-rel')}).then(function(r){if(r&&r.error)mkMsg('err',esc(r.error));});});});
2126
+ var g=el.querySelector('.mkgo');if(g)g.addEventListener('click',function(){g.disabled=true;api('/api/marketing/go',{}).then(function(r){if(!r||r.error){g.disabled=false;mkMsg('err',esc((r&&r.error)||'It didn&#39;t start.'));return;}mkMsg('ok',r.queued?"Its agent takes them once the run there now finishes.":"Marketing&#39;s agent started. It&#39;s on the Workdesk, and what it drafts waits here for your OK.");loadMarketing();}).catch(function(e){g.disabled=false;mkMsg('err',esc(String((e&&e.message)||e)));});});
2127
+ if(MKLIT){MKLIT=false;var lit=nd.querySelector('.mkneeds');if(lit&&lit.scrollIntoView)lit.scrollIntoView({block:'start'});}}
2008
2128
  var MKDAY=function(s){var d=new Date(s+'T00:00:00');return isNaN(d)?s:d.toLocaleDateString(undefined,{day:'numeric',month:'short'});};
2009
2129
  function renderMarketing(){var d=MKT,r=$('mkreplies'),t=$('mktest');if(!d||!r||!t)return;var rs=d.replies||[];
2010
2130
  r.innerHTML="<div class='pshead'><span>Replies</span><small>"+(!d.linkedin?"Link LinkedIn, and comments on and mentions of your posts show here, marked when they may be a customer.":rs.length?"On LinkedIn in the last 30 days. Draft a reply leaves one in the comment box for you to post.":"None on LinkedIn in the last 30 days. Comments on and mentions of your posts show here, marked when they may be a customer.")+"</small></div>"
@@ -2073,8 +2193,9 @@ $('reportsrefresh').addEventListener('click',function(){REPOPEN='';loadReports()
2073
2193
  // Time runs left to right, one current per feed, "now" on the right. Each message
2074
2194
  // is a bead placed when it arrived: amber and bigger when it needs you, small when
2075
2195
  // read. A bead opens a popover with that message's own actions (the same buttons as
2076
- // the list below, wired by renderBoard's wire); a feed's name carries its Seen,
2077
- // Talk and Check now. Drawn in the look's liquid, with its own canvas.
2196
+ // the list below, wired by renderBoard's wire, each time it's drawn); a feed's name
2197
+ // carries its Seen (its beads leave the stream: what's seen isn't waiting), Talk and
2198
+ // Check now. Drawn in the look's liquid, with its own canvas.
2078
2199
  var BS={G:null,beads:[],hot:null,on:false,b:null},BWIRE=null;
2079
2200
  // A feed's short name: "Inbox - you@x.com - x Mail" reads as Inbox, with the address under it.
2080
2201
  function bsName(c){var parts=String(c.name||'').split(' - '),acct=parts.filter(function(x){return x.indexOf('@')>=0;})[0]||'';return {n:parts[0]||c.name||'',sub:acct};}
@@ -2082,8 +2203,9 @@ function bsGeom(){var el=$('bstream'),w=(el&&el.clientWidth)||900,n=((BS.b&&BS.b
2082
2203
  return {w:w,h:Math.max(300,80+n*118+44),x0:narrow?130:220,x1:w-(narrow?36:280),narrow:narrow,n:n};}
2083
2204
  function bsBuild(b){BS.b=b;var ov=$('bsov'),el=$('bstream');if(!ov||!el)return;var cs=b.cards||[];if(!cs.length){ov.innerHTML='';return;}
2084
2205
  var g=bsGeom();BS.gw=g.w;if(el.style)el.style.height=g.h+'px';var now=Date.now(),H=(b.hours||24)*3600000,laneY=function(i){return 60+(g.h-110)*(i+0.5)/Math.max(1,g.n);};
2085
- var html='';cs.forEach(function(c,i){var y=laneY(i);
2086
- html+="<div class='bline' style='left:"+(g.x0-24)+"px;width:"+(g.x1-g.x0+24)+"px;top:"+Math.round(y)+"px'></div>";
2206
+ // every lane's line in one drawing, the same for each feed: none can come out without its line
2207
+ var html="<svg class='blines' width='"+g.w+"' height='"+g.h+"' aria-hidden='true'><defs><linearGradient id='bslg' gradientUnits='userSpaceOnUse' x1='"+(g.x0-24)+"' x2='"+g.x1+"'><stop offset='0' stop-color='currentColor' stop-opacity='0'/><stop offset='.06' stop-color='currentColor' stop-opacity='.12'/><stop offset='.94' stop-color='currentColor' stop-opacity='.12'/><stop offset='1' stop-color='currentColor' stop-opacity='0'/></linearGradient></defs>"+cs.map(function(c,i){return "<rect class='bline' x='"+(g.x0-24)+"' y='"+Math.round(laneY(i))+"' width='"+(g.x1-g.x0+24)+"' height='1' fill='url(#bslg)'/>";}).join('')+"</svg>";
2208
+ cs.forEach(function(c,i){var y=laneY(i);
2087
2209
  var nm=bsName(c);html+="<div class='bcard blane' data-i='"+i+"' style='top:"+Math.round(y)+"px'><div class='bln' title='"+esc(c.name+(c.url?' \u00b7 '+c.url:''))+"'>"+esc(nm.n)+"</div>"+(nm.sub?"<div class='blsub'>"+esc(nm.sub)+"</div>":"")+"<div class='blc' title='"+(c.checked?"read "+agoTxt(c.checked):"")+"'>"+(c.count?"<b>"+esc(c.label)+"</b>":"quiet")+"</div><div class='bla'>"+(c.count?"<button class='bseen' title='set this feed back to 0: only what comes in after counts'>Seen</button>":"")+"<button class='btalkbtn' title='talk what&#39;s new here over with your AI before you draft a reply'>Talk"+((c.chat||[]).length?" "+Math.ceil(c.chat.length/2):"")+"</button><button class='bcheck' title='read it now'>Check now</button></div>"+(c.error?"<div class='ble'>"+esc(c.error)+"</div>":"")+"</div>";
2088
2210
  if(c.brief&&!g.narrow)html+="<div class='blb' style='top:"+Math.round(y)+"px'>"+esc(c.brief.text.length>150?c.brief.text.slice(0,149)+'…':c.brief.text)+"</div>";});
2089
2211
  var marks=b.hours===24?[18,12,6]:b.hours===72?[48,24]:[120,72,24];
@@ -2092,7 +2214,7 @@ html+="<span class='btick' style='left:"+g.x1+"px'>now</span><div class='bnow' s
2092
2214
  var old={};BS.beads.forEach(function(x){old[x.key]=x;});BS.beads=[];
2093
2215
  cs.forEach(function(c,i){(c.items||[]).forEach(function(n,j){var age=Math.max(0,now-(n.ts||now));if(age>H)return;var key=c.id+':'+(n.id||j),o=old[key];
2094
2216
  BS.beads.push({key:key,i:i,j:j,n:n,need:!!n.need&&!!(n.mail||n.chat),tx:g.x0+(1-age/H)*(g.x1-g.x0),ty:laneY(i),tr:n.need&&(n.mail||n.chat)?13:n.read?6:9,x:o?o.x:g.x1,y:o?o.y:laneY(i),r:o?o.r:0});});
2095
- (c.past||[]).forEach(function(n,j){var age=Math.max(0,now-(n.ts||now));if(age>H)return;var key=c.id+':p:'+(n.id||j),o=old[key];BS.beads.push({key:key,i:i,j:-1-j,n:n,past:true,need:false,tx:g.x0+(1-age/H)*(g.x1-g.x0),ty:laneY(i),tr:4.5,x:o?o.x:g.x1,y:o?o.y:laneY(i),r:o?o.r:0});});});
2217
+ });
2096
2218
  html+=BS.beads.map(function(x,k){return "<button type='button' class='bbead' data-k='"+k+"' aria-label='"+esc(x.n.text||'')+"'></button>";}).join('');
2097
2219
  var byLane={};BS.beads.forEach(function(x){(byLane[x.i]=byLane[x.i]||[]).push(x);});Object.keys(byLane).forEach(function(k){var L=byLane[k].slice().sort(function(a,b){return a.tx-b.tx;}),run=0;for(var q=1;q<L.length;q++){if(L[q].tx-L[q-1].tx<26){run++;L[q].ty+=(run%2?-1:1)*Math.ceil(run/2)*20;}else run=0;}});
2098
2220
  var ml=bsMailLane();BS.waits=[];if(ml>=0)(AWAITS||[]).forEach(function(w,k){if(w.status!=='waiting'&&!(w.reply&&now-w.reply.ts<H))return;var sent=w.sent||now,age=Math.max(0,now-sent),x=g.x0+(1-Math.min(age,H)/H)*(g.x1-g.x0),y=laneY(ml)+(k%2?14:-14);BS.waits.push({w:w,x:x,y:y});
@@ -2101,13 +2223,12 @@ html+="<div class='btip' role='dialog' aria-live='polite'></div>";ov.innerHTML=h
2101
2223
  ov.querySelectorAll('.bwait').forEach(function(b){var i=+b.getAttribute('data-w');b.addEventListener('pointerenter',function(){bsShowWait(i);});b.addEventListener('focus',function(){bsShowWait(i);});b.addEventListener('click',function(){bsShowWait(i);});});
2102
2224
  BS.btns=[];ov.querySelectorAll('.bbead').forEach(function(bt){BS.btns.push(bt);var k=+bt.getAttribute('data-k');bt.addEventListener('pointerenter',function(){bsShow(k);});bt.addEventListener('focus',function(){bsShow(k);});bt.addEventListener('click',function(){bsShow(k);});});
2103
2225
  if(el.addEventListener&&!BS.wired){BS.wired=true;el.addEventListener('pointerleave',function(){bsShow(null);});el.addEventListener('keydown',function(e){if(e&&e.key==='Escape')bsShow(null);});}
2104
- BS.hot=null;bsStart();}
2226
+ if(BWIRE)BWIRE(ov);BS.hot=null;bsStart();}
2105
2227
  function bsShowWait(i){var tip=document.querySelector('#bsov .btip'),x=BS.waits&&BS.waits[i];if(!tip||!x)return;BS.hot=null;BS.hotWait=i;var w=x.w,who=(w.to||[]).join(', ')||'?';
2106
2228
  tip.innerHTML="<div class='bcard'><div class='bi'><div class='btf'>"+(w.status==='waiting'?'Waiting on a reply':'Reply in')+" <span>&middot; "+esc(who)+" &middot; "+(w.status==='waiting'?'sent '+agoTxt(w.sent):agoTxt(w.reply&&w.reply.ts))+"</span></div><div class='btx'>"+esc(w.subject||'')+(w.asked?"<div class='bwask'>Asked for: "+esc(w.asked)+"</div>":"")+(w.handed&&w.status!=='waiting'?"<div class='bwask'>"+(w.handed==='ops'?'An agent':esc(w.handed)+'&#39;s agent')+" has the next step.</div>":"")+(w.error?"<div class='bwask err'>"+esc(w.error)+"</div>":"")+"</div><div class='bta'>"+(w.status==='waiting'?"<button class='quiet bwstop'>Stop waiting</button><span class='bwnote'>Symbiot watches your inbox for it, and hands it on when it's in.</span>":"")+"</div></div></div>";tip.className='btip on';
2107
2229
  var st=tip.querySelector('.bwstop');if(st)st.addEventListener('click',function(){st.disabled=true;api('/api/awaiting/stop',{id:w.id}).then(function(r){if(!r||r.error){st.disabled=false;boardMsg('err',esc((r&&r.error)||'failed'));return;}bsShow(null);loadAwaiting();});});}
2108
2230
  function bsShow(k){var tip=document.querySelector('#bsov .btip');if(!tip)return;BS.hot=k;BS.hotWait=null;if(k==null||!BS.beads[k]){tip.className='btip';return;}
2109
- var x=BS.beads[k],c=BS.b.cards[x.i],n=x.n;if(x.past){tip.innerHTML="<div class='bcard'><div class='bi'><div class='btf'>"+esc(n.name||bsName(c).n)+" <span>&middot; "+esc(bsName(c).n)+" &middot; "+agoTxt(n.ts)+" &middot; seen</span></div><div class='btx' style='margin-bottom:0'>"+esc(n.text||'')+"</div></div></div>";tip.className='btip on';return;}
2110
- tip.innerHTML="<div class='bcard' data-i='"+x.i+"'><div class='bi' data-j='"+x.j+"'><div class='btf'>"+esc(n.name||bsName(c).n)+" <span>&middot; "+esc(bsName(c).n)+" &middot; "+agoTxt(n.ts)+(n.from&&CHATFROM[n.from]?" &middot; "+CHATFROM[n.from]:"")+(n.read?" &middot; read":"")+"</span></div><div class='btx'>"+esc(n.text||'')+"</div><div class='bta'>"+(n.mail||n.chat?"<button class='bdraft'>"+(n.drafted?"Drafted &middot; again":"Draft a reply")+"</button>":"")+(n.href?"<button class='bopen"+(n.mail||n.chat?" quiet":"")+"'>Open</button>":"")+(n.chat&&n.drafted?"<button class='bopenwa quiet'>Open in WhatsApp</button>":"")+"</div></div></div>";
2231
+ var x=BS.beads[k],c=BS.b.cards[x.i],n=x.n;tip.innerHTML="<div class='bcard' data-i='"+x.i+"'><div class='bi' data-j='"+x.j+"'><div class='btf'>"+esc(n.name||bsName(c).n)+" <span>&middot; "+esc(bsName(c).n)+" &middot; "+agoTxt(n.ts)+(n.from&&CHATFROM[n.from]?" &middot; "+CHATFROM[n.from]:"")+(n.read?" &middot; read":"")+"</span></div><div class='btx'>"+esc(n.text||'')+"</div><div class='bta'>"+(n.mail||n.chat?"<button class='bdraft'>"+(n.drafted?"Drafted &middot; again":"Draft a reply")+"</button>":"")+(n.href?"<button class='bopen"+(n.mail||n.chat?" quiet":"")+"'>Open</button>":"")+(n.chat&&n.drafted?"<button class='bopenwa quiet'>Open in WhatsApp</button>":"")+"</div></div></div>";
2111
2232
  tip.className='btip on';if(BWIRE)BWIRE(tip);}
2112
2233
  function bsStart(){if(BS.on)return;var raf=typeof window.requestAnimationFrame==='function'?function(f){return window.requestAnimationFrame(f);}:null;if(!raf)return;BS.on=true;
2113
2234
  var loop=function(){var p=$('panel-board'),el=$('bstream');if(!p||!el||!p.offsetParent||(el.classList&&el.classList.contains('none'))){BS.on=false;return;}BS.fr=(BS.fr||0)+1;if(BS.fr%glEvery(true)===0)bsFrame();raf(loop);};raf(loop);}
@@ -2140,7 +2261,7 @@ return "<div class='bcard"+(c.count?" has":"")+"' data-i='"+i+"'><div class='bh'
2140
2261
  +(c.count>c.items.length?"<div class='bl' style='margin-top:6px'>&hellip;and "+(c.count-c.items.length)+" more</div>":"")
2141
2262
  +"</div>";}).join('');
2142
2263
  var tk=$('boardtalk');if(tk)tk.innerHTML=cs.map(function(c,i){var nt=Math.ceil(((c.chat||[]).length)/2);return BTALK[c.id]?"<div class='bcard' data-i='"+i+"'><div class='bh'><span class='bn'>Talking over "+esc(c.name)+"</span><button class='ghost btalkbtn' style='padding:4px 9px;font-size:12px'>Close</button></div><div class='tchat btalk'><div class='msgs'>"+(nt?chatMsgs(c.chat):TALKHINT)+"</div><div class='row'><input class='talkq' placeholder='Ask about these, or say how to answer one...' style='flex:1'><button class='ghost talksend'>Ask</button><a class='talkclear' title='forget this conversation'>clear</a></div></div></div>":"";}).join('');
2143
- bsBuild(b);
2264
+ BWIRE=wire;bsBuild(b);
2144
2265
  function card(btn){return cs[+btn.closest('.bcard').getAttribute('data-i')];}
2145
2266
  function wire(el){if(!el||!el.querySelectorAll)return;
2146
2267
  el.querySelectorAll('.btalkbtn').forEach(function(btn){btn.addEventListener('click',function(){var c=card(btn);BTALK[c.id]=!BTALK[c.id];renderBoard();var tk2=$('boardtalk'),q=BTALK[c.id]&&tk2&&tk2.querySelector(".bcard[data-i='"+cs.indexOf(c)+"'] .talkq");if(q&&q.focus)q.focus();});});
@@ -2160,15 +2281,20 @@ el.querySelectorAll('.bopenwa').forEach(function(btn){btn.addEventListener('clic
2160
2281
  function item(btn){return card(btn).items[+btn.closest('.bi').getAttribute('data-j')];}
2161
2282
  el.querySelectorAll('.bcheck').forEach(function(btn){btn.addEventListener('click',function(){var c=card(btn);btn.disabled=true;btn.textContent='Reading…';
2162
2283
  api('/api/watch/check',{id:c.id}).then(function(x){if(!x||x.error)boardMsg('err',esc((x&&x.error)||'failed'));else boardMsg(x["new"]&&x["new"].length?'ok':'muted',watchCheckMsg(c,x));loadWatchUI();}).catch(function(e){btn.disabled=false;btn.textContent='Check now';boardMsg('err',esc(String((e&&e.message)||e)));});});});
2163
- el.querySelectorAll('.bseen').forEach(function(btn){btn.addEventListener('click',function(){var c=card(btn);btn.disabled=true;
2164
- api('/api/watch/seen',{id:c.id}).then(function(x){if(!x||x.error){btn.disabled=false;boardMsg('err',esc((x&&x.error)||'failed'));return;}$('boardmsg').innerHTML='';loadBoard();}).catch(function(e){btn.disabled=false;boardMsg('err',esc(String((e&&e.message)||e)));});});});
2284
+ // Seen: its count and blobs go at once, and it's saved (watch.json), so only what comes in after counts
2285
+ el.querySelectorAll('.bseen').forEach(function(btn){btn.addEventListener('click',function(){var c=card(btn);if(!c)return;btn.disabled=true;
2286
+ b.total=Math.max(0,(b.total||0)-(c.count||0));c.count=0;c.items=[];delete c.brief;renderBoard();
2287
+ api('/api/watch/seen',{id:c.id}).then(function(x){if(!x||x.error)boardMsg('err',esc((x&&x.error)||'failed'));else $('boardmsg').innerHTML='';loadBoard();lqLoad(false);}).catch(function(e){boardMsg('err',esc(String((e&&e.message)||e)));loadBoard();});});});
2165
2288
  el.querySelectorAll('.bopen').forEach(function(btn){btn.addEventListener('click',function(){var n=item(btn);if(n&&/^https?:/.test(n.href))window.open(n.href,'_blank','noopener');});});
2166
2289
  el.querySelectorAll('.bdraft').forEach(function(btn){btn.addEventListener('click',function(){var n=item(btn);btn.disabled=true;
2167
2290
  api('/api/watch/draft',{id:n.id}).then(function(x){btn.disabled=false;if(!x||x.error){boardMsg('err',esc((x&&x.error)||'failed'));return;}boardMsg('ok',x.chat?DRAFTING_CHAT:x.social?DRAFTING_SOCIAL:DRAFTING);loadWatchUI();}).catch(function(e){btn.disabled=false;boardMsg('err',esc(String((e&&e.message)||e)));});});});}
2168
- wire(el);wire($('boardtalk'));wire($('bsov'));BWIRE=wire;}
2291
+ wire(el);wire($('boardtalk'));}
2169
2292
  $('boardrefresh').addEventListener('click',loadBoard);
2170
2293
  if(document.addEventListener)document.addEventListener('click',function(ev){var t=ev&&ev.target;if(!t||!t.closest)return;var tx=t.closest('#panel-tasks .task .t');if(!tx||t.closest('a,button,input,textarea,summary'))return;var sel=window.getSelection?String(window.getSelection()):'';if(sel)return;var row=tx.closest('.task');if(row&&row.classList)row.classList.toggle('open');});
2171
- var atr=$('agenttrust');if(atr&&atr.addEventListener){api('/api/agent/trust').then(function(r){if(r)atr.checked=!!r.full;}).catch(function(){});atr.addEventListener('change',function(){api('/api/agent/trust/set',{full:atr.checked}).then(function(r){if(r)atr.checked=!!r.full;});});}
2294
+ // a repo run's sandbox, under Symbiosis: on, or what this computer still needs for it (agents.mjs sandboxState)
2295
+ function sandboxNote(r){var el=$('agentsandbox');if(!el)return;var b=r&&r.sandbox;if(!b||!r.full){el.innerHTML='';return;}
2296
+ el.innerHTML=b.ready&&b.on?"<b>Sandboxed</b> &middot; a repo run&rsquo;s commands also write only in its repo, your folders and the npm and download caches, and can&rsquo;t reach your keys or step out of it. Ops runs, whose job is this computer, keep the membrane alone.":b.unsupported||!b.on?'':"<b>Not sandboxed yet</b> &middot; repo runs would also be held to their repo and your folders, but this computer needs "+b.missing.map(function(m){return '<code>'+esc(m)+'</code>';}).join(' and ')+" first (<code>sudo apt install "+esc(b.missing.map(function(m){return m==='bwrap'?'bubblewrap':m;}).join(' '))+"</code>). Until then they have the membrane.";}
2297
+ var atr=$('agenttrust');if(atr&&atr.addEventListener){api('/api/agent/trust').then(function(r){if(r)atr.checked=!!r.full;sandboxNote(r);}).catch(function(){});atr.addEventListener('change',function(){api('/api/agent/trust/set',{full:atr.checked}).then(function(r){if(r)atr.checked=!!r.full;sandboxNote(r);});});}
2172
2298
  var bw=$('boardwin');if(bw&&bw.querySelectorAll)bw.querySelectorAll('button').forEach(function(b){b.addEventListener('click',function(){BOARDH=+b.getAttribute('data-h')||24;bw.querySelectorAll('button').forEach(function(x){x.setAttribute('aria-checked',x===b?'true':'false');});loadBoard();});});
2173
2299
  // A web page, mapped by itself in the hidden browser (headless.mjs): no capture,
2174
2300
  // no dragging. It takes a few seconds, so the button says so meanwhile.
@@ -2342,10 +2468,10 @@ window.addEventListener('focus',function(){heartbeat(true);}); // re-check for u
2342
2468
 
2343
2469
 
2344
2470
  // ---- the work scene ------------------------------------------------------------------
2345
- // Tasks and agents, in the liquid: what each agent is doing (spheres revolve round
2471
+ // The Workdesk, in the liquid: what each agent is doing (spheres revolve round
2346
2472
  // it while it works), what's done and waiting for your OK, and what's waiting its
2347
2473
  // turn. One Go starts what's waiting. A tap opens the details; nothing else to read.
2348
- function lqWork(){lqRemember();LQ.scene='work';LQ.mode='aware';var sc=$('lqscene');if(sc)sc.textContent=LQ.workBy==='project'?'Projects':'Tasks and agents';var b=document.body;if(b&&b.classList){b.classList.add('lq-work');b.classList.toggle('lq-byproj',LQ.workBy==='project');b.classList.remove('lq-pooled');}lqLoadWork();
2474
+ function lqWork(){lqRemember();LQ.scene='work';LQ.mode='aware';var sc=$('lqscene');if(sc)sc.textContent=LQ.workBy==='project'?'Projects':'Workdesk';var b=document.body;if(b&&b.classList){b.classList.add('lq-work');b.classList.toggle('lq-byproj',LQ.workBy==='project');b.classList.remove('lq-pooled');}lqLoadWork();
2349
2475
  if(LQ.workTimer)clearInterval(LQ.workTimer);LQ.workTimer=setInterval(function(){if(LQ.scene==='work'&&LQ.mode!=='pool')lqLoadWork();},5000);}
2350
2476
  function lqHome(){lqRemember();LQ.scene='home';LQ.relaySig='';var rl=$('lqrelay');if(rl)rl.innerHTML='';var li=$('lqline');if(li)li.innerHTML='';var b=document.body;if(b&&b.classList){b.classList.remove('lq-work');b.classList.remove('lq-byproj');b.classList.remove('lq-pooled');}if(LQ.workTimer){clearInterval(LQ.workTimer);LQ.workTimer=null;}LQ.mode='aware';lqLoad(true);}
2351
2477
 
@@ -2369,7 +2495,7 @@ if(p.summary)h+="<p class='rlsum' title='"+escQ(p.summary)+"'>"+esc(p.summary)+"
2369
2495
  if(q)h+="<div class='lqblob rlask'>"+blobBody({kind:'ask',name:nm,repo:p.repo,q:q.q,options:q.options,path:q.path,fix:q.fix,id:q.id,sub:q.sub,more:Math.max(0,(p.asks||p.qs.length)-1)},false)+"</div>";
2370
2496
  if(p.ready)h+="<button type='button' class='act rlok'>Review and approve</button>";
2371
2497
  if(p.ideas&&p.ideas.length)h+="<div class='rlk'>Extra tasks it suggests</div>"+p.ideas.map(function(d,j){return "<div class='rli' data-j='"+j+"'><span title='"+escQ(d.full)+"'>"+esc(d.text)+"</span><span class='rlia'><button type='button' class='rladd'>+ task</button><button type='button' class='rlskip'>Skip</button></span></div>";}).join('');
2372
- return h+"<div class='rlf'><button type='button' class='rlopen'>Its tasks &rsaquo;</button><button type='button' class='rlag'>Details in Agents &rsaquo;</button></div></section>";}).join('');
2498
+ return h+"<div class='rlf'><button type='button' class='rlopen'>Its tasks &rsaquo;</button><button type='button' class='rlag'>Its agents &rsaquo;</button></div></section>";}).join('');
2373
2499
  el.querySelectorAll('.rly').forEach(function(box){var p=ps[+box.getAttribute('data-i')],q=(p.qs||[])[0],nm=p.name||p.repo;
2374
2500
  var ab=box.querySelector('.rlask');if(ab&&q)wireBlob(ab,{kind:'ask',name:nm,repo:p.repo,q:q.q,options:q.options,path:q.path,fix:q.fix,id:q.id},{done:function(){LQ.relaySig='';lqLoadWork();}});
2375
2501
  var ok=box.querySelector('.rlok');if(ok)ok.addEventListener('click',function(){lqOpen({kind:'proj',repo:p.repo,lit:true});});
@@ -2437,7 +2563,7 @@ LQ.talk.push({me:false,text:t});LQ.talk=LQ.talk.slice(-8);lqTalkShow(false);LQ.r
2437
2563
  // comes out of the core (mass is conserved). Your colours come from the system,
2438
2564
  // live: light or dark, contrast, transparency, forced colours, your accent, night.
2439
2565
  var LQ={drops:[],btns:[],core:{x:0,y:0,cr:0},mode:'aware',last:'',lastAct:Date.now(),talk:[],talking:false,theme:{},ripple:[0.5,0.5,-10],t:0,pointer:null,touch:false,frame:0};
2440
- var LQNAMES={board:'Dashboard',map:'Map',tasks:'Tasks',agents:'Agents',week:'Week',standup:'Standup',todo:'Todo',drift:'Drift',settings:'Settings',reports:'Reports',marketing:'Marketing'};
2566
+ var LQNAMES={board:'Dashboard',map:'Map',tasks:'Workdesk',agents:'Workdesk',week:'Week',standup:'Standup',todo:'Todo',drift:'Drift',settings:'Settings',reports:'Reports',marketing:'Marketing'};
2441
2567
  var LQ_REST=60000,LQ_MAX=11;
2442
2568
  function lqMM(q){try{return !!(window.matchMedia&&window.matchMedia(q).matches);}catch(e){return false;}}
2443
2569
  function lqLookGet(){var l='';try{l=window.localStorage&&window.localStorage.getItem('symbiot-look')||'';}catch(e){}return l==='glass'||l==='pearl'?l:'ferro';}
@@ -2459,7 +2585,7 @@ function lqLoad(commit){if(LQ.scene==='work'){lqLoadWork();return;}var fresh=LQ.
2459
2585
  // (work with an end) up left, Areas (what you keep up with) up right, Resources (to
2460
2586
  // look things up in) down left, the Archive (what's done) sunk low; and what's new on
2461
2587
  // what you watch (Watching) as a zone of its own on the right.
2462
- var PARA_OF={tasks:'p',agents:'p',todo:'p',board:'a',week:'a',standup:'a',marketing:'a',map:'r',drift:'r',settings:'r',reports:'r'};
2588
+ var PARA_OF={tasks:'p',agents:'p',todo:'p',marketing:'p',board:'a',week:'a',standup:'a',map:'r',drift:'r',settings:'r',reports:'r'};
2463
2589
  var PARA_NAME={p:'Projects',a:'Areas',w:'Watching',r:'Resources',x:'Archive'},PARA_SUB={p:'work with an end',a:'what you keep up with',w:'new on what you watch',r:'to look things up in',x:'what’s done'};
2464
2590
  var PARA_ANG={p:-2.55,a:-0.6,w:0.3,r:2.6,x:1.25},PARA_LIVE={p:0.45,a:0.3,w:0.3,r:0.12,x:0};
2465
2591
  function lqPara(d){return d.kind==='you'?'n':d.kind==='archive'?'x':d.kind==='feed'&&d.shape!=='reports'?'w':PARA_OF[d.shape]||'p';}
@@ -2469,11 +2595,28 @@ function lqParaGo(g){lqAct();if(g==='p'){lqWorkBy('project');return;}if(g==='a'|
2469
2595
  // order (questions, then Approves, then first steps); the rest a page on.
2470
2596
  var LQ_BLOBW=250,LQ_NEEDY=128;
2471
2597
  function lqNeeds(h,S){var rank={ask:0,approve:1,setup:2},rk=function(y){return y.urgent?(y.id==='setup:ai'?-3:y.id==='setup:agent'?-2:-1):rank[y.kind]||0;},ys=(h.you||[]).slice().sort(function(a,b){return rk(a)-rk(b)||(a.id<b.id?-1:a.id>b.id?1:0);});
2598
+ var nb=lqNextBox(h,S);if(!ys.length)return {items:[],bottom:nb,x:0,page:0,pages:1,count:0};
2472
2599
  var R=Math.round(24*Math.max(0.85,Math.min(1.1,S.s))),unit=2*R+30+LQ_BLOBW,gap=28,cols=Math.max(1,Math.min(3,Math.floor((S.w-24+gap)/(unit+gap)))),pages=Math.max(1,Math.ceil(ys.length/cols));
2473
2600
  LQ.needPage=(LQ.needPage||0)%pages;var shown=ys.slice(LQ.needPage*cols,LQ.needPage*cols+cols),x0=Math.max(12,(S.w-(shown.length*unit+(shown.length-1)*gap))/2);
2474
2601
  var tall=(shown.some(function(y){return y.kind==='ask'&&(y.options||[]).length;})?172:116)+(shown.some(function(y){return y.fix==='handover';})?16:0);
2475
2602
  var items=shown.map(function(y,i){return {id:y.id,kind:'you',blob:true,item:y,shape:y.shape,title:y.title,sub:y.sub,r:R,tx:x0+i*(unit+gap)+R,ty:LQ_NEEDY+R,ax:0,ay:0};});
2476
2603
  return {items:items,bottom:items.length?LQ_NEEDY+tall+20:0,x:x0,page:LQ.needPage,pages:pages,count:ys.length};}
2604
+ // Next up, where the needs-you band would be: with nothing waiting on you (and only
2605
+ // then), a few things that CAN be done next (home.mjs nextUp), each with its gain
2606
+ // and one tap that starts an agent or makes a task. × puts the box away until what
2607
+ // it suggests changes. Gives the band's bottom (0 when it isn't shown).
2608
+ function lqNextList(h){if(LQ.scene==='work'||(h.you||[]).length)return [];var ns=h.next||[],sig=ns.map(function(s){return s.id;}).join('|'),off='';try{off=(window.localStorage&&window.localStorage.getItem('symbiot-next-off'))||'';}catch(e){}return sig&&off!==sig?ns:[];}
2609
+ function lqNextBox(h,S){var el=$('lqnext'),all=lqNextList(h);if(!el){LQ.nextList=all;return 0;}
2610
+ if(!all.length){LQ.nextList=[];if(el.classList)el.classList.remove('on');sameHtml(el,'');return 0;}
2611
+ // as many as fit without crowding the liquid under it (like the band): two across on a wide screen, a page on for the rest
2612
+ var two=S.w>=1100,per=two?4:S.w<600||S.h<620?1:S.h>=760?3:2,pages=Math.ceil(all.length/per);LQ.nextPage=(LQ.nextPage||0)%pages;var ns=all.slice(LQ.nextPage*per,LQ.nextPage*per+per),w=Math.min(two?1040:600,S.w-24);LQ.nextList=ns;
2613
+ if(!sameHtml(el,"<div class='nxh'><span>What could be done next</span><small>one tap each, if you like</small>"+(pages>1?"<button type='button' class='nxpg'>"+(LQ.nextPage+1)+" of "+pages+" &rsaquo;</button>":"")+"<button type='button' class='nxx' aria-label='put these suggestions away' title='put these away'>&times;</button></div><ol"+(two&&ns.length>1?" class='two'":"")+">"+ns.map(function(s,i){return "<li data-i='"+i+"'><div class='nxt'><b title='"+escQ(s.title)+"'>"+esc(s.title)+"</b><span title='"+escQ(s.gain+' · '+s.time)+"'>"+esc(s.gain)+" &middot; "+esc(s.time)+"</span></div><button type='button' class='nxgo' data-i='"+i+"'>"+esc(s.label)+"</button></li>";}).join('')+"</ol>")){
2614
+ var x=el.querySelector('.nxx');if(x)x.addEventListener('click',function(){var sig=((LQ.home&&LQ.home.next)||[]).map(function(s){return s.id;}).join('|');try{if(window.localStorage)window.localStorage.setItem('symbiot-next-off',sig);}catch(e){}lqAct();lqBuild();});
2615
+ var pg=el.querySelector('.nxpg');if(pg)pg.addEventListener('click',function(){LQ.nextPage=(LQ.nextPage||0)+1;lqAct();lqBuild();});
2616
+ el.querySelectorAll('.nxgo').forEach(function(b){b.addEventListener('click',function(){var s=(LQ.nextList||[])[+b.getAttribute('data-i')],li=b.closest?b.closest('li'):null;if(!s)return;lqAct();b.disabled=true;
2617
+ api('/api/home/next',{id:s.id}).then(function(r){if(!r||r.error){b.disabled=false;lqSaid((r&&r.error)||'It didn’t start. Try again.');return;}lqSaid(r.said||'Started.');if(li&&li.classList)li.classList.add('gone');LQ.fresh=true;setTimeout(function(){lqLoad(false);},650);}).catch(function(e){b.disabled=false;lqSaid(String((e&&e.message)||e));});});});}
2618
+ if(el.classList)el.classList.add('on');if(el.style){el.style.width=w+'px';el.style.transform='translate('+Math.round((S.w-w)/2)+'px,'+(LQ_NEEDY-30)+'px)';}
2619
+ return LQ_NEEDY-30+(el.offsetHeight||(48+Math.ceil(ns.length/(two?2:1))*58))+24;}
2477
2620
  function lqNeedPage(){LQ.needPage=(LQ.needPage||0)+1;lqAct();lqBuild();}
2478
2621
  function lqArchive(){lqPool('tasks');LQ.arch=true;if(typeof loadArchived==='function')loadArchived();}
2479
2622
  function lqOrg(id,k){var h=k*977;id=String(id);for(var i=0;i<id.length;i++)h=(h*31+id.charCodeAt(i))|0;return ((h>>>0)%1000)/1000;}
@@ -2503,16 +2646,22 @@ list.forEach(function(d,i){if(d.kind==='shape')((near||{})[d.shape]||[]).forEach
2503
2646
  else if((d.kind==='you'||d.kind==='feed')&&at[d.shape]!=null)links.push([at[d.shape],i,0.5]);});return links.filter(same);}
2504
2647
  function lqBuild(){var a=LQ.adapt||{},h=LQ.home||{},S=lqSize(),list=[];LQ.bw=S.w;LQ.bh=S.h;
2505
2648
  // the band first: everything else sits under it, round a core centred in what's left
2506
- var need=lqNeeds(h,S),top=need.bottom,cx=S.w/2,cy=need.items.length?Math.max(S.h*0.47,(top+60+S.h-200)/2):S.h*0.47;LQ.cy=cy;LQ.top=top;LQ.need=need;
2649
+ var need=lqNeeds(h,S),top=need.bottom,cx=S.w/2,cy=top?Math.max(S.h*0.47,(top+60+S.h-200)/2):S.h*0.47;LQ.cy=cy;LQ.top=top;LQ.need=need;
2507
2650
  // Marketing only once posts can be drafted, or are (home.mjs): a new user meets what they watch first
2508
- var mk=function(id){return id!=='marketing'||!!h.marketing;};
2651
+ var mk=function(id){return id!=='agents'&&(id!=='marketing'||!!h.marketing);}; // and Agents is the Workdesk (tasks), one orb
2509
2652
  var feeds=(h.feeds||[]).slice(0,2),lay=((a.layout&&a.layout.items)||[]).filter(function(it){return mk(it.id);}),more=((a.layout&&a.layout.more)||[]).filter(mk);
2510
- var vx=Math.max(0.5,Math.min(S.s*1.3,(S.w/2-90)/400)),vy=Math.max(0.35,Math.min(S.s*0.8,(S.h-cy-230)/400,(cy-(top?top+60:160))/400));var rs=Math.min(S.s,(vx+vy)/1.5);var room=Math.max(3,LQ_MAX-feeds.length-1-(need.items.length?2:0));lay.slice(room).forEach(function(it){more.push(it.id);});
2653
+ var vx=Math.max(0.5,Math.min(S.s*1.3,(S.w/2-90)/400)),vy=Math.max(0.35,Math.min(S.s*0.8,(S.h-cy-230)/400,(cy-(top?top+60:160))/400));var rs=Math.min(S.s,(vx+vy)/1.5);var room=Math.max(3,LQ_MAX-feeds.length-1-(top?2:0));
2511
2654
  var repNew=(h.feeds||[]).filter(function(f){return f.id==='feed:reports';})[0],repOut=repNew&&feeds.indexOf(repNew)<0; // new reports, when their own droplet didn't fit
2512
- lay.slice(0,room).forEach(function(it){list.push({id:'shape:'+it.id,kind:'shape',shape:it.id,title:LQNAMES[it.id]||it.id,sub:it.id==='reports'&&repOut?repNew.count+' new':'',r:it.r*rs,tx:cx+Math.cos(it.angle)*it.d*vx,ty:cy+Math.sin(it.angle)*it.d*vy});});
2655
+ // what has something for you is never only under "more": unread reports, and Marketing once it can draft;
2656
+ // each takes the place of the least likely part shown, which goes under "more" instead
2657
+ var vis=lay.slice(0,room);['reports','marketing'].forEach(function(id){if(id==='reports'?!repOut:!h.marketing)return;if(vis.some(function(it){return it.id===id;}))return;var vi=-1;vis.forEach(function(it,i){if(['tasks','reports','marketing'].indexOf(it.id)<0&&(vi<0||it.p<vis[vi].p))vi=i;});if(vi<0)return;var v=vis[vi];vis[vi]={id:id,p:v.p,r:v.r,d:v.d,angle:v.angle};more=more.filter(function(m){return m!==id;});more.push(v.id);});
2658
+ lay.slice(room).forEach(function(it){if(!vis.some(function(x){return x.id===it.id;})&&more.indexOf(it.id)<0)more.push(it.id);});lay=vis;
2659
+ // Marketing's orb is lit (amber, like a lane that asks) when something there needs you, and orbits while its agent works
2660
+ var mkh=h.marketing||{},mkSub=mkh.needs?mkh.needs+(mkh.needs>1?' need you':' needs you'):mkh.working?'its agent is at work':'';
2661
+ lay.slice(0,room).forEach(function(it){var mko=it.id==='marketing';list.push({id:'shape:'+it.id,kind:'shape',shape:it.id,title:LQNAMES[it.id]||it.id,sub:it.id==='reports'&&repOut?repNew.count+' new':mko?mkSub:'',ask:mko&&!!mkh.needs,orbit:mko&&!!mkh.working,r:it.r*rs,tx:cx+Math.cos(it.angle)*it.d*vx,ty:cy+Math.sin(it.angle)*it.d*vy});});
2513
2662
  feeds.forEach(function(f,i){var ang=Math.PI/2+(i-(feeds.length-1)/2)*0.7;var fn=bsName({name:f.title});list.push({id:f.id,kind:'feed',shape:f.shape,title:fn.n+(fn.sub?' · '+fn.sub.split('@').pop():''),sub:f.sub,r:(28+5*Math.min(f.count||1,5))*rs,tx:cx+Math.cos(ang)*300*vx,ty:cy+Math.sin(ang)*300*vy});});
2514
2663
  list.push({id:'archive',kind:'archive',shape:'tasks',title:'Archive',sub:'what’s done: archived tasks',r:26*rs,tx:cx+Math.cos(PARA_ANG.x)*330*vx,ty:cy+Math.sin(PARA_ANG.x)*330*vy});
2515
- list.forEach(function(d){var g=lqPara(d);d.live=PARA_LIVE[g];if(g==='r')d.r*=0.82;if(g==='x')d.r*=0.85;if(d.shape==='agents'&&d.kind==='shape'&&h.working)d.orbit=true;});
2664
+ list.forEach(function(d){var g=lqPara(d);d.live=PARA_LIVE[g];if(g==='r')d.r*=0.82;if(g==='x')d.r*=0.85;if(d.shape==='tasks'&&d.kind==='shape'&&h.working)d.orbit=true;});
2516
2665
  var mr=more.length?[{id:'more',kind:'more',title:'more',sub:more.map(function(m){return LQNAMES[m]||m;}).join(' · '),more:more.slice(),r:24*S.s}]:[],zl=list.concat(mr),zc=lqZones(zl,S,top,92*S.s);
2517
2666
  if(zc){LQ.cy=zc.cy;list=zl;}else{
2518
2667
  lqGrow(list,cx,cy,vx,vy);list.forEach(function(d){d.ax=d.tx;d.ay=d.ty;});var lks=lqLinks(list,a.near);lqSeed(list,lks,cx,cy,vx,vy,lqPara,function(g){return PARA_ANG[g];});lqNear(list,lks,cx,cy,92*rs,S);
@@ -2541,7 +2690,7 @@ lqLabels();}
2541
2690
  // and each part is where it was last time. Sizes still come from how you use them (adapt.mjs), all scaled
2542
2691
  // by one factor when the screen is short, so they stay comparable. null when they
2543
2692
  // can't fit (a narrow or very short screen): the liquid's own layout then.
2544
- var ZONE_ORDER=['tasks','agents','todo','board','week','standup','marketing','map','drift','settings','reports'],ZONE_KIND={shape:0,feed:1,archive:2,more:3};
2693
+ var ZONE_ORDER=['tasks','agents','todo','marketing','board','week','standup','map','drift','settings','reports'],ZONE_KIND={shape:0,feed:1,archive:2,more:3};
2545
2694
  function lqZones(list,S,top,coreR){LQ.zoneT=null;if(S.w<1000||S.h<540)return null;
2546
2695
  var y0=top?top+10:96,y1=S.h-180,mid=(y0+y1)/2,cx=S.w/2,gapC=coreR+36,colW=S.w/2-gapC-20,titleW=150;if(colW-titleW<250||mid-y0<70)return null;
2547
2696
  var cells={p:[cx-gapC-colW,y0,mid],a:[cx+gapC,y0,mid],r:[cx-gapC-colW,mid,y1],w:[cx+gapC,mid,y1]},by={},plan={},k=1;
@@ -2593,7 +2742,7 @@ while(out.indexOf(' ')>=0)out=out.split(' ').join(' ');[' :',' ,',' ;',' .'].f
2593
2742
  function lqLabels(){var el=$('lqdrops');if(!el)return;
2594
2743
  var dropAt=function(b){return LQ.drops[+b.getAttribute('data-i')];};
2595
2744
  var same=sameHtml(el,LQ.drops.map(function(d,i){if(d.blob)return "<div class='lqd lq-you lqblob"+(d.item.urgent?' urgent':'')+"' data-i='"+i+"' role='group' aria-label='"+escQ(String(d.item.name||d.item.title||'')+': '+String(d.item.q||d.item.sub||''))+"'>"+blobBody(d.item,true)+"</div>";
2596
- return "<div class='lqd lq-"+d.kind+(d.parked?' lq-parked':'')+(LQ.openTag===d.id?' open':'')+"' data-i='"+i+"'><button type='button' class='lt' title='"+escQ(String(d.title||'').split('**').join('')+(d.sub?' · '+d.sub:''))+"' aria-expanded='"+(LQ.openTag===d.id)+"'><span>"+esc(lqShort(d.title,42))+"</span>"+(d.kind==='proj'&&d.sub?"<span class='lm'>"+esc(lqShort(d.sub,40))+"</span>":"")+"</button>"+(d.sub?"<small>"+esc(d.sub)+"</small>":"")+"<button type='button' class='lgo'>Open &rsaquo;</button></div>";}).join(''));
2745
+ return "<div class='lqd lq-"+d.kind+(d.parked?' lq-parked':'')+(d.ask&&d.kind==='shape'?' lq-lit':'')+(LQ.openTag===d.id?' open':'')+"' data-i='"+i+"'><button type='button' class='lt' title='"+escQ(String(d.title||'').split('**').join('')+(d.sub?' · '+d.sub:''))+"' aria-expanded='"+(LQ.openTag===d.id)+"'><span>"+esc(lqShort(d.title,42))+"</span>"+(d.kind==='proj'&&d.sub?"<span class='lm'>"+esc(lqShort(d.sub,40))+"</span>":"")+"</button>"+(d.sub?"<small>"+esc(d.sub)+"</small>":"")+"<button type='button' class='lgo'>Open &rsaquo;</button></div>";}).join(''));
2597
2746
  if(!same){LQ.btns=[];LQ.lts=[];el.querySelectorAll('.lqd').forEach(function(b){LQ.btns.push(b);var d=dropAt(b),lt=b.querySelector('.lt'),go=b.querySelector('.lgo');LQ.lts.push(lt);
2598
2747
  if(lt)lt.addEventListener('click',function(ev){lqOpen(dropAt(b),ev);});
2599
2748
  if(go)go.addEventListener('click',function(ev){LQ.openTag=null;lqOpen(dropAt(b),ev);});
@@ -2610,10 +2759,12 @@ function lqCoreText(c,t){if(c&&c._t!==t){c.textContent=t;c._t=t;}}
2610
2759
  // Tapping a sphere (or its name) opens its tag; tapping it again, or elsewhere, closes it.
2611
2760
  function lqTag(id){LQ.openTag=LQ.openTag===id?null:id;lqAct();LQ.btns.forEach(function(b,i){var d=LQ.drops[+b.getAttribute('data-i')],on=!!d&&d.id===LQ.openTag;if(b.classList)b.classList.toggle('open',on);var lt=LQ.lts&&LQ.lts[i];if(lt&&lt.setAttribute)lt.setAttribute('aria-expanded',on?'true':'false');});}
2612
2761
  function lqVia(ev){return LQ.talking?'talk':ev&&ev.pointerType==='touch'?'touch':ev&&ev.detail===0?'key':'click';}
2613
- function lqUse(shape,ev,via){api('/api/adapt/use',{shape:shape,from:LQ.last,via:via||lqVia(ev)});LQ.last=shape;}
2762
+ function lqUse(shape,ev,via){if(shape==='agents')shape='tasks'; // one Workdesk
2763
+ api('/api/adapt/use',{shape:shape,from:LQ.last,via:via||lqVia(ev)});LQ.last=shape;}
2614
2764
  function lqOpen(d,ev){if(!d)return;lqAct();
2615
2765
  if(d.kind==='more'){LQ.moreOpen=!LQ.moreOpen;lqMore(d);return;}
2616
2766
  if(d.kind==='archive'){lqArchive();return;}
2767
+ if(d.kind==='shape'&&d.shape==='marketing'&&d.ask)MKLIT=true;
2617
2768
  if(d.kind==='you'&&d.item){if(d.x!=null){var S1=lqSize();LQ.ripple=[d.x/S1.w,d.y/S1.h,LQ.t];}lqUse(d.shape||'agents',ev);lqFocus(d.item);return;}
2618
2769
  if(d.kind==='proj'){if(d.lit||d.repo==='ops'){lqFocus({kind:'proj',repo:d.repo});return;}TFILTER.repo=d.repo;TFILTER.type='';lqPool('tasks');if(typeof loadTasks==='function')loadTasks();return;}
2619
2770
  // where your files disagree: the checks, lit up in Settings, and these clashes reached you (it dissolves)
@@ -2645,7 +2796,7 @@ function wireBlob(box,y,o){o=o||{};var say=o.say||lqSaid,nm=y.name||y.repo||'the
2645
2796
  var lock=function(on){box.querySelectorAll('button,input').forEach(function(b){b.disabled=on;});};
2646
2797
  var send=function(ans,j){ans=String(ans||'').trim();if(!ans||(!y.path&&!y.fix))return;lock(true);
2647
2798
  (y.fix?api('/api/home/answer',j!=null?{id:y.id,pick:j}:{id:y.id,text:ans}):api('/api/agents/answer',{path:y.path,answers:[{q:y.q,a:ans}],rerun:true})).then(function(r){
2648
- if(!r||r.error){lock(false);say(nm+': '+((r&&r.error)||'the answer didn’t save. Try again on its block in Agents.'),true);return;}
2799
+ if(!r||r.error){lock(false);say(nm+': '+((r&&r.error)||'the answer didn’t save. Try again on its block on the Workdesk.'),true);return;}
2649
2800
  say(y.fix?nm+': '+(r.said||'Done.'):'Sent to '+nm+'. '+(r.yours&&r.yours.length?'Still yours to do: '+r.yours.join(' '):r.rerun?'Its agent is picking it up now.':(r.note||'Saved in its ANSWERS.md.')));
2650
2801
  if(o.sent)o.sent();if(box.classList)box.classList.add('gone');setTimeout(function(){if(o.done)o.done();},650);}).catch(function(e){lock(false);say(nm+': '+String((e&&e.message)||e),true);});};
2651
2802
  box.querySelectorAll('.nopt[data-j]').forEach(function(b){b.addEventListener('click',function(){var j=+b.getAttribute('data-j');send((y.options||[])[j],j);});});
@@ -2685,12 +2836,12 @@ api('/api/lanes').then(function(d){paint(((d&&d.handoffs)||[]).filter(function(x
2685
2836
  var SETFOCUS={ai:'provider',work:'scanroots',agent:'agentcmd',links:'links',company:'knowroots',checks:'knowchecks'};
2686
2837
  function lqLight(){var f=LQ.focus;if(!f||LQ.mode!=='pool')return;var things=[],also=[];
2687
2838
  if(f.kind==='setup'){var a=$(SETFOCUS[f.focus]||''),s=a&&a.closest?a.closest('.sset'):null;if(f.focus==='checks'&&a)things.push(a);else if(s)things.push(s);}
2688
- else if(current==='tasks'){document.querySelectorAll('#needsbox .nitem').forEach(function(e){things.push(e);});document.querySelectorAll('#reviewlist .rcard').forEach(function(e){if(e.getAttribute('data-repo')===f.repo)(things.length?also:things).push(e);});}
2689
- else if(current==='agents'){document.querySelectorAll('#agentslist .agent[data-i]').forEach(function(e){var g=AGENTLIST[+e.getAttribute('data-i')];if(g&&(nQs(g)||g.waiting)&&(f.path?g.path===f.path:/act-[0-9a-f]+$/.test(String(g.path))))things.push(e);});}
2839
+ else if(current==='tasks'&&f.repo&&f.repo!=='ops'){document.querySelectorAll('#needsbox .nitem').forEach(function(e){things.push(e);});document.querySelectorAll('#reviewlist .rcard').forEach(function(e){if(e.getAttribute('data-repo')===f.repo)(things.length?also:things).push(e);});}
2840
+ else if(current==='tasks'){document.querySelectorAll('#agentslist .agent[data-i]').forEach(function(e){var g=AGENTLIST[+e.getAttribute('data-i')];if(g&&(nQs(g)||g.waiting)&&(f.path?g.path===f.path:/act-[0-9a-f]+$/.test(String(g.path))))things.push(e);});}
2690
2841
  things.concat(also).forEach(function(e){if(e.classList)e.classList.add('lit');});
2691
2842
  if(!things.length||f.shown)return;f.shown=true;var e0=things[0];if(e0.scrollIntoView)e0.scrollIntoView({block:'start',behavior:'smooth'});
2692
2843
  if(things.length===1&&e0.querySelector){var c=e0.querySelector('.nopt,.aq input[type=radio],.qother,button.approve,select,input');if(c&&c.focus){try{c.focus({preventScroll:true});}catch(e){c.focus();}}}}
2693
- // The work, by task (Tasks, Agents) or by project (the Projects group): the same scene, a step in the history each.
2844
+ // The work, by task (the Workdesk) or by project (the Projects group): the same scene, a step in the history each.
2694
2845
  function lqWorkBy(by){if(LQ.scene==='work'&&LQ.workBy===by&&LQ.mode!=='pool')return;lqRemember();LQ.back=true;LQ.workBy=by;try{lqWork();}finally{LQ.back=false;}}
2695
2846
  // Back, one step: every move between screens (home, the Tasks scene, a panel, the
2696
2847
  // Archive) notes where you were; a right-click (or the mouse's back button) returns
@@ -2704,22 +2855,34 @@ function lqPool(shape){if(shape==='map'&&LM.items&&LM.items.length)setTimeout(fu
2704
2855
  function lqSink(){lqRemember();LQ.focus=null;var b=document.body;if(b&&b.classList)b.classList.remove('lq-pooled');LQ.mode='aware';lqAct();lqLoad(false);}
2705
2856
  function lqAct(){if(AWAY)return;LQ.lastAct=Date.now();if(LQ.mode==='rest'){LQ.mode='aware';lqLoad(true);}}
2706
2857
  function lqTalkMode(on){LQ.talking=on;var b=document.body;if(b&&b.classList)b.classList.toggle('lq-talking',on);}
2858
+ // Minimize: the chat shrinks back to its input and Home comes back as it was; what was
2859
+ // said stays (LQ.talk), and "Chat · n" above the input opens it again. Esc, the chat's
2860
+ // Minimize, or a click outside it do it; sending a message opens it again.
2861
+ LQ.talkMin=false;
2862
+ function lqTalkBar(){var el=$('lqtalkbar'),n=LQ.talk.length,b=document.body;if(!n)LQ.talkMin=false;if(b&&b.classList){b.classList.toggle('lq-chat',n>0&&!LQ.talkMin);b.classList.toggle('lq-chatmin',n>0&&LQ.talkMin);}if(!el)return;
2863
+ var up="<svg width='12' height='12' viewBox='0 0 12 12' aria-hidden='true'><path d='M2.5 7.5 6 4l3.5 3.5' fill='none' stroke='currentColor' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>",down="<svg width='12' height='12' viewBox='0 0 12 12' aria-hidden='true'><path d='M2.5 4.5 6 8l3.5-3.5' fill='none' stroke='currentColor' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>";
2864
+ var h=!n?'':LQ.talkMin?"<button type='button' class='lqtb-open' aria-expanded='false' aria-controls='lqtalk' title='open the chat again'>"+up+"Chat &middot; "+n+" message"+(n>1?'s':'')+"</button>":"<span class='lqtb-t'>Chat with Symbiot</span><button type='button' class='lqtb-min' aria-expanded='true' aria-controls='lqtalk' title='minimize the chat (Esc): Home comes back, the chat stays'>Minimize"+down+"</button>";
2865
+ if(!sameHtml(el,h)){var bt=el.querySelector('button');if(bt)bt.addEventListener('click',function(){if(LQ.talkMin)lqChatOpen();else lqChatMin();});}}
2866
+ function lqChatMin(){if(!LQ.talk.length||LQ.talkMin)return;LQ.talkMin=true;var i=$('lqask');if(i&&i.blur&&document.activeElement===i)i.blur();lqTalkMode(false);lqTalkBar();lqAct();}
2867
+ function lqChatOpen(){LQ.talkMin=false;lqTalkMode(true);lqTalkShow(false);lqAct();var i=$('lqask');if(i&&i.focus)i.focus();}
2707
2868
  function lqTalkShow(wait){var el=$('lqtalk');if(!el)return;var n=LQ.talk.length;el.innerHTML=LQ.talk.map(function(m,i){var age=n-1-i,op=age<2?1:age===2?0.7:age===3?0.45:0.25;return "<div class='lqmsg"+(m.me?' me':'')+"' style='opacity:"+op+"'>"+(m.imgs&&m.imgs.length?"<span class='lqmimgs'>"+m.imgs.map(function(d){return "<img src='"+d+"' alt='your screenshot'>";}).join('')+"</span>":"")+esc(m.text)+stepsHtml(m.steps)+"</div>";}).join('')+(wait?"<div class='lqmsg thinking'>Thinking: recalling what it knows, reading what's here&hellip;</div>":'');if(el.scrollHeight)el.scrollTop=el.scrollHeight;
2708
2869
  // scrolled up to read back: the whole history shows, clear; back at the bottom it rolls on again
2709
- if(!el.wired&&el.addEventListener){el.wired=true;el.addEventListener('scroll',function(){var up=el.scrollHeight-el.scrollTop-el.clientHeight>24;if(el.classList)el.classList.toggle('back',up);});}}
2870
+ if(!el.wired&&el.addEventListener){el.wired=true;el.addEventListener('scroll',function(){var up=el.scrollHeight-el.scrollTop-el.clientHeight>24;if(el.classList)el.classList.toggle('back',up);});}lqTalkBar();}
2710
2871
  // Screenshots in the talk: paste one (Ctrl+V) or drop it on the bar; it shows as a
2711
2872
  // thumbnail above it, goes with what you say, and the model sees it.
2712
2873
  LQ.atts=[];
2713
2874
  function lqAttsShow(){var el=$('lqatts');if(!el)return;el.innerHTML=LQ.atts.map(function(a,i){return "<span class='lqatt'><img src='"+a.data+"' alt='screenshot "+(i+1)+"'><button type='button' data-i='"+i+"' aria-label='remove screenshot "+(i+1)+"'>&times;</button></span>";}).join('');
2714
2875
  el.querySelectorAll('button').forEach(function(b){b.addEventListener('click',function(){LQ.atts.splice(+b.getAttribute('data-i'),1);lqAttsShow();});});}
2715
2876
  function lqAttach(file){if(!file||['image/png','image/jpeg','image/webp','image/gif'].indexOf(file.type)<0||file.size>8*1024*1024||LQ.atts.length>=4)return;var r=new FileReader();r.onload=function(){LQ.atts.push({data:String(r.result)});lqAttsShow();};r.readAsDataURL(file);}
2716
- function lqSay(){var i=$('lqask');var q=((i&&i.value)||'').trim(),shots=LQ.atts.slice();if(!q&&!shots.length)return;if(!q)q='Take a look at this screenshot.';i.value='';LQ.atts=[];lqAttsShow();lqAct();
2877
+ // The talk box wraps and grows with what's typed, to five lines, then scrolls.
2878
+ function lqGrowAsk(){var i=$('lqask');if(!i||!i.style)return;i.style.height='auto';var h=Math.min(126,Math.max(44,i.scrollHeight||44));i.style.height=h+'px';if(i.classList)i.classList.toggle('full',(i.scrollHeight||0)>126);var f=$('lqform'),b=document.body;if(f&&f.offsetHeight&&b&&b.style&&b.style.setProperty)b.style.setProperty('--lqfh',f.offsetHeight+'px');}
2879
+ function lqSay(){var i=$('lqask');var q=((i&&i.value)||'').trim(),shots=LQ.atts.slice();if(!q&&!shots.length)return;if(!q)q='Take a look at this screenshot.';i.value='';lqGrowAsk();LQ.atts=[];lqAttsShow();lqAct();
2717
2880
  var low=q.toLowerCase().replace(/^(please |can you |could you )/,''),hit='';
2718
2881
  Object.keys(LQNAMES).forEach(function(k){var nm=LQNAMES[k].toLowerCase();if(hit)return;['open ','show ','go to ','take me to '].forEach(function(v){if(low.indexOf(v+nm)===0||low.indexOf(v+'my '+nm)===0)hit=k;});if(low===nm)hit=k;});
2719
2882
  if(hit){lqTalkMode(true);lqUse(hit,null,'talk');lqGo(hit);return;}
2720
2883
  if(/^(go|start|go for it|start them|start it)[.! ]*$/.test(low)){lqTalkMode(true);lqGoWork();return;}
2721
2884
  if(/^(home|back)[.! ]*$/.test(low)&&LQ.scene==='work'){lqHome();return;}
2722
- LQ.talk.push({me:true,text:q,imgs:shots.map(function(a){return a.data;})});LQ.talk=LQ.talk.slice(-8);lqTalkShow(true);
2885
+ LQ.talkMin=false;lqTalkMode(true);LQ.talk.push({me:true,text:q,imgs:shots.map(function(a){return a.data;})});LQ.talk=LQ.talk.slice(-8);lqTalkShow(true);
2723
2886
  api('/api/home/ask',{question:q,images:shots}).then(function(r){LQ.talk.push({me:false,steps:r.steps||[],text:r.error==='not-connected'?'Connect an AI in Settings to talk to me. Say "open settings".':(r.answer||r.error||'(no answer)')});LQ.talk=LQ.talk.slice(-8);lqTalkShow(false);LQ.ripple=[0.5,0.47,LQ.t];lqLoad(false);}).catch(function(e){LQ.talk.push({me:false,text:String((e&&e.message)||e)});lqTalkShow(false);});}
2724
2887
  // The physics: each droplet a critically damped spring to its place (no wobble,
2725
2888
  // no overshoot), pushed off its neighbours and the core where they'd overlap.
@@ -2741,9 +2904,12 @@ var b=LQ.btns[i];if(b&&b.offsetWidth)d.lw=b.offsetWidth;if(b&&b.style){b.style.t
2741
2904
  // off any droplet it would sit on (the "more" droplet, another group's): above it instead
2742
2905
  for(var pass=0;pass<3;pass++)D.forEach(function(d){if(d.blob||lqPara(d)===g&&d.kind!=='more')return;var l=d.x-d.cr-6,r=d.x+d.cr+6,t=d.y-d.cr-6,b=d.y+d.cr+(d.lw?24:6);if(gx<r&&gx+gw>l&&gy<b&&gy+gh>t)gy=t-gh-4;});
2743
2906
  gb.style.transform='translate('+Math.round(gx)+'px,'+Math.round(Math.max(LQ.scene!=='work'&&LQ.top?LQ.top:48,gy))+'px)';}var gshow=!rest&&!pool&&!talk;gb.style.opacity=gshow?'1':'0';gb.style.pointerEvents=gshow?'auto':'none';gb.tabIndex=gshow?0:-1;});
2744
- var ntl=$('lqneedt');if(ntl&&ntl.style)ntl.style.opacity=rest?'0':'1';
2907
+ var ntl=$('lqneedt');if(ntl&&ntl.style)ntl.style.opacity=rest?'0':'1';var nxb=$('lqnext');if(nxb&&nxb.style){nxb.style.opacity=rest?'0':'';nxb.style.pointerEvents=rest?'none':'';}
2745
2908
  (LQ.orbs||[]).forEach(function(o){var d=D[+o.getAttribute('data-i')];if(!d||!o.style)return;var R=Math.round(d.cr+20);o.style.width=o.style.height=(2*R)+'px';o.style.transform='translate('+Math.round(d.x-R)+'px,'+Math.round(d.y-R)+'px)';o.style.opacity=rest||pool?'0':'1';});
2746
- var cb=$('lqcore');if(cb&&cb.style){var inC=LQ.theme&&LQ.theme.look!=='pearl'&&core.cr>60;cb.style.maxWidth=inC?Math.round(core.cr*1.5)+'px':'340px';var cw=cb.offsetWidth||0,chh=cb.offsetHeight||0;cb.style.transform='translate('+Math.round(cx-cw/2)+'px,'+(rest||inC?(inC?Math.round(core.y-chh/2):Math.round(core.y+core.cr+14)):LQ.scene==='work'?112:56)+'px)';var ct=rest?(LQ.coreText||'All handled'):talk||pool?'':(LQ.coreText||'');lqCoreText(cb,ct);cb.style.opacity=ct?'1':'0';cb.style.pointerEvents=ct?'auto':'none';}}
2909
+ var cb=$('lqcore');if(cb&&cb.style){var inC=core.cr>60;if(cb.classList)cb.classList.toggle('in',inC&&!rest);cb.style.maxWidth=inC?Math.round(core.cr*1.5)+'px':'340px';var cw=cb.offsetWidth||0,chh=cb.offsetHeight||0;
2910
+ // too small a core for its words (a phone): under the header, which wraps there; and not at all when the needs band says it already
2911
+ var hd=document.querySelector?document.querySelector('header'):null,below=Math.max(56,((hd&&hd.offsetHeight)||0)+8),said=!inC&&!rest&&LQ.scene!=='work'&&LQ.need&&LQ.need.items&&LQ.need.items.length;
2912
+ cb.style.transform='translate('+Math.round(cx-cw/2)+'px,'+(rest||inC?(inC?Math.round(core.y-chh/2):Math.round(core.y+core.cr+14)):LQ.scene==='work'?112:below)+'px)';var ct=rest?(LQ.coreText||'All handled'):talk||pool||said?'':(LQ.coreText||'');lqCoreText(cb,ct);cb.style.opacity=ct?'1':'0';cb.style.pointerEvents=ct?'auto':'none';}}
2747
2913
  // The liquid: a metaball surface (Σ r²/d² = 1) shaded as chrome, on the GPU.
2748
2914
  var LQ_FS=['precision highp float;',
2749
2915
  'uniform vec2 uRes;uniform float uT;uniform float uDpr;uniform vec4 uB[NB];uniform vec3 uRip;uniform float uStyle;uniform float uContrast;uniform float uExposure;',
@@ -2876,7 +3042,13 @@ var L=$('liquid');if(L&&L.addEventListener){L.addEventListener('click',function(
2876
3042
  var f=$('lqform');if(f&&f.addEventListener)f.addEventListener('submit',function(ev){if(ev&&ev.preventDefault)ev.preventDefault();lqSay();});
2877
3043
  var ia=$('lqask');if(ia&&ia.addEventListener){ia.addEventListener('paste',function(ev){var it=(ev.clipboardData&&ev.clipboardData.items)||[],got=false;for(var k=0;k<it.length;k++){if(it[k].kind==='file'&&String(it[k].type).indexOf('image/')===0){lqAttach(it[k].getAsFile());got=true;}}if(got)ev.preventDefault();});
2878
3044
  var fm=$('lqform');if(fm&&fm.addEventListener){fm.addEventListener('dragover',function(ev){ev.preventDefault();if(fm.classList)fm.classList.add('drop');});fm.addEventListener('dragleave',function(){if(fm.classList)fm.classList.remove('drop');});fm.addEventListener('drop',function(ev){ev.preventDefault();if(fm.classList)fm.classList.remove('drop');var fs=(ev.dataTransfer&&ev.dataTransfer.files)||[];for(var k=0;k<fs.length;k++)lqAttach(fs[k]);});}
2879
- ia.addEventListener('focus',function(){lqTalkMode(true);lqAct();});ia.addEventListener('blur',function(){if(!ia.value&&!LQ.talk.length)lqTalkMode(false);});ia.addEventListener('keydown',function(ev){if(ev&&ev.key==='Escape'){if(LQ.mode==='pool'){lqSink();return;}if(LQ.scene==='work'&&!ia.value){lqHome();return;}ia.value='';LQ.talk=[];lqTalkShow(false);lqTalkMode(false);if(ia.blur)ia.blur();}});}
3045
+ ia.addEventListener('focus',function(){if(!LQ.talkMin)lqTalkMode(true);lqAct();});ia.addEventListener('blur',function(){if(!ia.value&&!LQ.talk.length)lqTalkMode(false);});ia.addEventListener('input',lqGrowAsk);
3046
+ // Enter sends, Shift+Enter is a new line (and Enter mid-composition, an IME's, is the IME's)
3047
+ ia.addEventListener('keydown',function(ev){if(ev&&ev.key==='Enter'&&!ev.shiftKey&&!ev.isComposing&&ev.keyCode!==229){ev.preventDefault();lqSay();return;}});
3048
+ ia.addEventListener('keydown',function(ev){if(ev&&ev.key==='Escape'){if(LQ.mode==='pool'){lqSink();return;}if(LQ.talk.length&&!LQ.talkMin){lqChatMin();return;}if(LQ.scene==='work'&&!ia.value){lqHome();return;}ia.value='';lqGrowAsk();lqTalkMode(false);if(ia.blur)ia.blur();}});}
3049
+ // Esc anywhere on Home, or a click outside the chat (its messages, its bar, the input), minimizes it
3050
+ if(document.addEventListener){document.addEventListener('keydown',function(ev){var t=ev&&ev.target;if(!ev||ev.key!=='Escape'||!LQ.talk.length||LQ.talkMin||LQ.mode==='pool')return;if(t&&t.closest&&t.closest('#lqform,main,input,textarea,select'))return;lqChatMin();});
3051
+ document.addEventListener('pointerdown',function(ev){var t=ev&&ev.target;if(!LQ.talk.length||LQ.talkMin||LQ.mode==='pool')return;if(t&&t.closest&&t.closest('#lqtalk,#lqtalkbar,#lqform,#lqatts'))return;lqChatMin();},true);}
2880
3052
  var co=$('lqcore');if(co&&co.addEventListener)co.addEventListener('click',function(){var i=$('lqask');if(i&&i.focus)i.focus();});
2881
3053
  var sk=$('lqsink');if(sk&&sk.addEventListener)sk.addEventListener('click',lqSink);
2882
3054
  var bk=$('lqback');if(bk&&bk.addEventListener)bk.addEventListener('click',lqHome);