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/CHANGELOG.md +12 -0
- package/README.md +128 -47
- package/agents.mjs +136 -6
- package/guard.mjs +6 -2
- package/handback.mjs +2 -2
- package/handover.mjs +14 -3
- package/home.mjs +183 -33
- package/lanes.mjs +6 -6
- package/marketing.mjs +130 -0
- package/mind.mjs +9 -9
- package/package.json +4 -3
- package/reports.mjs +3 -3
- package/scan.mjs +9 -2
- package/server.mjs +22 -11
- package/tasks.mjs +14 -10
- package/ui.mjs +297 -125
- package/watch.mjs +5 -5
- package/writeups.mjs +3 -3
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:
|
|
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:
|
|
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
|
-
.
|
|
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
|
-
.
|
|
557
|
-
|
|
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;
|
|
569
|
-
.lmzoom .iconbtn{width:
|
|
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
|
-
.
|
|
853
|
-
body.lq-light .
|
|
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">‹ 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><
|
|
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">
|
|
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
|
|
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 — 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>↶</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
|
-
|
|
980
|
-
<
|
|
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">
|
|
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'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 "{prompt}" --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 — 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> · 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
|
-
|
|
1121
|
-
|
|
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(
|
|
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
|
|
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'&¤t==='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,
|
|
1216
|
-
//
|
|
1217
|
-
//
|
|
1218
|
-
//
|
|
1219
|
-
//
|
|
1220
|
-
//
|
|
1221
|
-
|
|
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
|
-
|
|
1230
|
-
|
|
1231
|
-
var
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
var
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
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?' · '+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
|
-
|
|
1260
|
-
|
|
1261
|
-
function
|
|
1262
|
-
var
|
|
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 · show them':a.need?esc(a.need):a.fold?(a.run?'agent run':'folder')+(a.when?' · '+lmAgo(a.when):', no git'):(a.c?a.c+(a.c===1?' commit':' commits'):'not yours')+(m.last?' · '+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;}
|
|
1266
|
-
|
|
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)+' · ':'')+(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?' · last '+lmAgo(m.last):'')+(m.branch?' · 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.
|
|
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')))
|
|
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.
|
|
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
|
|
1280
|
-
|
|
1281
|
-
//
|
|
1282
|
-
|
|
1283
|
-
var
|
|
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.
|
|
1286
|
-
|
|
1287
|
-
|
|
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
|
-
|
|
1292
|
-
|
|
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
|
|
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) — 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&¤t==='
|
|
1659
|
+
if(answering()||selIn(el)){stopAgentsPoll();if(anyRunning&¤t==='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 · '+fmtE(a.elapsed)):(esc(a.status)+' · '+fmtE(a.elapsed)+(a.exitCode!=null?' · 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&¤t==='
|
|
1695
|
+
stopAgentsPoll();if(anyRunning&¤t==='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…</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,''')+"'>✓ <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(" · ")+"</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's for, so it has somewhere to go.":"That repo isn't in the map — 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?"✓ 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'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'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 — opening the <b>Agents</b> tab to watch it work…</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
|
|
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 — what a write-up would see:<br>"+items.map(function(x){return esc(x.date)+" "+esc(x.subject)+" → "+esc((x.to||[]).join(', ')||'?');}).join("<br>")+"</div>";});}
|
|
1776
1865
|
function setupLocalUI(){var o=document.getElementById('setupout');o.innerHTML="Setting up a local model…";
|
|
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> — watch it
|
|
1867
|
+
o.innerHTML="Pulling <b>"+esc(r.model)+"</b> — 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'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 · 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 · drafted "+agoTxt(x.drafted)+(x.edited?" · 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 · drafted "+agoTxt(x.drafted)+(x.edited?" · 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',"✓ 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 · "+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?" · its agent is at work":L.waiting?" · "+L.waiting+" waiting for its agent":""):"Its own agent, for every product: tell it what to make, and say which product it'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…' 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…' 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'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'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's on Marketing's list already.":"Added to Marketing"+(pr.value?" for "+esc(pr.value):"")+". Start its agent when you'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't start.'));return;}mkMsg('ok',r.queued?"Its agent takes them once the run there now finishes.":"Marketing's agent started. It'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
|
|
2077
|
-
//
|
|
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
|
-
|
|
2086
|
-
html
|
|
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'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
|
-
|
|
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>· "+esc(who)+" · "+(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)+''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;
|
|
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>· "+esc(bsName(c).n)+" · "+agoTxt(n.ts)+(n.from&&CHATFROM[n.from]?" · "+CHATFROM[n.from]:"")+(n.read?" · read":"")+"</span></div><div class='btx'>"+esc(n.text||'')+"</div><div class='bta'>"+(n.mail||n.chat?"<button class='bdraft'>"+(n.drafted?"Drafted · 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>· "+esc(bsName(c).n)+" · "+agoTxt(n.ts)+(n.from&&CHATFROM[n.from]?" · "+CHATFROM[n.from]:"")+(n.read?" · read":"")+"</span></div><div class='btx'>"+esc(n.text||'')+"</div><div class='bta'>"+(n.mail||n.chat?"<button class='bdraft'>"+(n.drafted?"Drafted · 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'>…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
|
-
|
|
2164
|
-
|
|
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'));
|
|
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
|
-
|
|
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> · a repo run’s commands also write only in its repo, your folders and the npm and download caches, and can’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> · 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
|
-
//
|
|
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':'
|
|
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 ›</button><button type='button' class='rlag'>
|
|
2498
|
+
return h+"<div class='rlf'><button type='button' class='rlopen'>Its tasks ›</button><button type='button' class='rlag'>Its agents ›</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:'
|
|
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',
|
|
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+" ›</button>":"")+"<button type='button' class='nxx' aria-label='put these suggestions away' title='put these away'>×</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)+" · "+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=
|
|
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-(
|
|
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
|
-
|
|
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==='
|
|
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','
|
|
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 ›</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 ›</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&<.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){
|
|
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
|
|
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==='
|
|
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 (
|
|
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 · "+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…</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)+"'>×</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
|
-
|
|
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=
|
|
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('
|
|
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);
|