symbiot 0.54.2 → 0.54.4
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 +22 -0
- package/README.md +49 -10
- package/adapt.mjs +1 -1
- package/agents.mjs +62 -6
- package/checks.mjs +16 -1
- package/handback.mjs +16 -6
- package/handover.mjs +41 -2
- package/headless.mjs +55 -3
- package/home.mjs +123 -14
- package/index.mjs +41 -6
- package/lanes.mjs +132 -5
- package/package.json +2 -2
- package/post.mjs +310 -41
- package/sandbox.mjs +1 -1
- package/server.mjs +36 -8
- package/ui.mjs +192 -66
- package/watch.mjs +5 -2
package/ui.mjs
CHANGED
|
@@ -101,6 +101,9 @@ body.lq-liquid.lq-light.lq-pooled #lqsinkrow{background:rgba(246,248,251,.94);bo
|
|
|
101
101
|
#lqtitle{margin:0;font:650 24px/1.15 var(--sans);letter-spacing:-.02em;color:var(--bone);overflow-wrap:anywhere}
|
|
102
102
|
#lqtitle .lqpk{margin-left:10px;font:600 11.5px var(--sans);letter-spacing:.06em;text-transform:uppercase;color:var(--amber);vertical-align:middle}
|
|
103
103
|
#lqpark{align-self:center;padding:5px 12px;font-size:12.5px}
|
|
104
|
+
/* what the open panel does to itself (its Refresh) sits beside Close */
|
|
105
|
+
.lqacts,#lqtools{display:flex;align-items:center;gap:8px;flex:0 0 auto}#lqtools:empty,#lqtools:not(:has(>:not(.hidden))){display:none}
|
|
106
|
+
#pushtasks{padding:7px 14px;font-size:13px}
|
|
104
107
|
#lqscene{display:none}
|
|
105
108
|
body.lq-work:not(.lq-pooled) #lqscene{display:block;position:absolute;left:50%;top:50px;transform:translateX(-50%);z-index:3;margin:0;font:650 24px/1.15 var(--sans);letter-spacing:-.02em;color:#F3F0EA;text-shadow:0 1px 12px rgba(0,0,0,.6);pointer-events:none;white-space:nowrap}
|
|
106
109
|
body.lq-light.lq-work #lqscene{color:#151A21;text-shadow:0 1px 10px rgba(255,255,255,.8)}
|
|
@@ -140,7 +143,7 @@ body.lq-liquid.lq-look-pearl button.act,body.lq-look-pearl #lqgo.on,body.lq-liqu
|
|
|
140
143
|
body.lq-liquid button.ghost{background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.14);border-radius:999px;color:var(--bone);backdrop-filter:blur(6px)}
|
|
141
144
|
body.lq-liquid.lq-light button.ghost{background:rgba(255,255,255,.7);border-color:rgba(20,30,45,.18)}
|
|
142
145
|
body.lq-liquid input,body.lq-liquid select,body.lq-liquid textarea{border-radius:12px;border:1px solid rgba(220,228,240,.2);background:rgba(255,255,255,.04);color:var(--bone)}
|
|
143
|
-
body.lq-liquid.lq-light input,body.lq-liquid.lq-light select,body.lq-liquid.lq-light textarea{background:#FFFFFF;border-color:rgba(20,30,45,.18)}
|
|
146
|
+
body.lq-liquid.lq-light input,body.lq-liquid.lq-light select,body.lq-liquid.lq-light textarea{background-color:#FFFFFF;border-color:rgba(20,30,45,.18)}
|
|
144
147
|
body.lq-liquid input:focus,body.lq-liquid select:focus,body.lq-liquid textarea:focus{outline:none;border-color:rgba(255,255,255,.7);box-shadow:0 0 0 3px rgba(200,210,224,.18)}
|
|
145
148
|
body.lq-liquid .tgroup{color:#C9D1DC;letter-spacing:.16em}
|
|
146
149
|
body.lq-liquid.lq-light .tgroup{color:#3B4656}
|
|
@@ -216,6 +219,7 @@ body.lq-pooled #lqneedt,body.lq-work #lqneedt{display:none}
|
|
|
216
219
|
.lqblob .bfx button{padding:0 12px;border-radius:12px;background:#F3F0EA;color:#0A0B0D;font-weight:600}
|
|
217
220
|
.lqblob .bfx.hidden{display:none}
|
|
218
221
|
.lqblob .bmore{font-size:11.5px;color:#8A919B}
|
|
222
|
+
.lqblob .bfor{font-size:11.5px;line-height:1.3;color:#8A919B;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
|
|
219
223
|
.lqblob .nopt:focus-visible,.lqblob .bfree:focus-visible,.lqblob .bfx button:focus-visible{outline:2px solid currentColor;outline-offset:2px}
|
|
220
224
|
.lqblob.gone{opacity:0!important;filter:blur(8px);transition:opacity .6s ease,filter .6s ease;pointer-events:none!important}
|
|
221
225
|
body.lq-light .lqd.lqblob{background:rgba(255,255,255,.94);border-color:rgba(154,82,0,.32);box-shadow:0 18px 40px -26px rgba(30,45,60,.5)}
|
|
@@ -224,7 +228,7 @@ body.lq-light .lqblob .nopt{background:#FFFFFF;border-color:rgba(20,30,45,.16);c
|
|
|
224
228
|
body.lq-light .lqblob .nopt.rec{background:#151A21;color:#FFFFFF;border-color:#151A21}
|
|
225
229
|
body.lq-light .lqblob .bfx input{background:#FFFFFF;border-color:rgba(20,30,45,.18)}
|
|
226
230
|
body.lq-light .lqblob .bfx button{background:#151A21;color:#FFFFFF}
|
|
227
|
-
body.lq-light .lqblob .bfree,body.lq-light .lqd.lqblob .lt i,body.lq-light .lqblob .bmore{color:#5A6470}
|
|
231
|
+
body.lq-light .lqblob .bfree,body.lq-light .lqd.lqblob .lt i,body.lq-light .lqblob .bmore,body.lq-light .lqblob .bfor{color:#5A6470}
|
|
228
232
|
body.lq-look-pearl .lqblob .nopt.rec,body.lq-look-pearl .lqblob .bfx button{background:linear-gradient(180deg,#FFFFFF 0%,#F1F2F5 60%,#E6E9EE 100%);color:#1B1E23;border-color:rgba(0,0,0,.08)}
|
|
229
233
|
body.lq-contrast .lqblob .nopt{border:2px solid currentColor}
|
|
230
234
|
body.lq-touch .lqblob .nopt,body.lq-touch .lqblob .bfx input{min-height:44px}
|
|
@@ -247,6 +251,7 @@ body.lq-talking .lqd.lqblob{opacity:.55}
|
|
|
247
251
|
#firststeps .fst span{font-size:12.5px;color:var(--faint);overflow-wrap:anywhere}
|
|
248
252
|
#firststeps li button{flex:none;padding:5px 12px;font-size:12.5px}
|
|
249
253
|
#needsbox:empty{display:none}
|
|
254
|
+
#pushout .pushed{margin-top:6px;padding:10px 14px}#pushout .pushed .dh{flex-wrap:wrap;row-gap:2px}#pushout .pushed .dd{font-size:12.5px;line-height:1.5}#pushout>.dd{margin-top:4px}
|
|
250
255
|
#needsbox{margin:4px 0 12px}
|
|
251
256
|
#needsbox .nitem{display:flex;flex-direction:column;align-items:stretch;gap:6px;padding:12px 14px;margin-top:8px}
|
|
252
257
|
#needsbox .nitem .nopt{max-width:720px}
|
|
@@ -396,6 +401,13 @@ footer{padding:10px 18px;border-top:1px solid var(--line);display:flex}
|
|
|
396
401
|
.pcard textarea.pedit:focus{outline:none;border-color:var(--green)}
|
|
397
402
|
.pcard .psrc{margin-top:8px;font-size:12px;color:var(--faint)}.pcard .psrc summary{cursor:pointer}.pcard .psrc div{margin-top:3px;overflow-wrap:anywhere}
|
|
398
403
|
.pcard .pchars{font-size:11px;color:var(--faint);margin-left:auto}
|
|
404
|
+
.pcard .pshow{margin-top:8px;font-size:12.5px;color:var(--text)}
|
|
405
|
+
.pcard .pmedia{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}
|
|
406
|
+
.pcard .pthumb{position:relative;border:1px solid var(--line);border-radius:9px;overflow:hidden;background:var(--ink);line-height:0}
|
|
407
|
+
.pcard .pthumb img,.pcard .pthumb video{display:block;max-height:150px;max-width:260px;width:auto;height:auto}
|
|
408
|
+
.pcard .pthumb .pmdel{position:absolute;top:4px;right:4px;padding:0 7px;min-height:0;line-height:20px;font-size:15px;border-radius:999px;background:var(--ink);opacity:.85}
|
|
409
|
+
.pcard .pthumb .pmdel:hover,.pcard .pthumb .pmdel:focus-visible{opacity:1}
|
|
410
|
+
.pcard .pform input.purl{min-width:0}
|
|
399
411
|
body.lq-liquid .rcard.pcard,body.lq-liquid.lq-light .rcard.pcard{border-left:3px solid var(--amber)}
|
|
400
412
|
.tag.cust{background:transparent;border-color:var(--amber);color:var(--amber)}
|
|
401
413
|
.rrow{cursor:pointer}.rrow:hover,.rrow:focus-visible{border-color:var(--green)}.rrow.rnew{border-left:3px solid var(--amber)}
|
|
@@ -500,6 +512,8 @@ body.lq-light .cx .lbtn:hover{background:rgba(21,26,33,.05)}
|
|
|
500
512
|
body.lq-light .cx.ok .cxb{box-shadow:0 1px 2px rgba(0,0,0,.25);background:radial-gradient(circle at 34% 28%,#FFFFFF 0,#E3E6EA 22%,#9BA2AC 55%,#3A3F46 100%)}
|
|
501
513
|
.cx.signin .cxb,.cx.signedout .cxb,.cx.error .cxb{opacity:1;box-shadow:inset 0 0 0 2px var(--amber),0 0 10px rgba(242,165,65,.35)}
|
|
502
514
|
.cxn{font:500 13.5px var(--sans);color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
515
|
+
.cxa{flex:none;font-size:12px;color:var(--faint);opacity:.55}.cxa.ready{color:var(--bone);opacity:1}
|
|
516
|
+
.cxlg{display:flex;flex-wrap:wrap;align-items:center;gap:4px 6px}.cxh .cxlg .cxlgf{display:inline-flex;align-items:center;gap:6px;margin-left:8px;font:inherit;color:inherit}
|
|
503
517
|
.cx.ok .cxn{color:var(--bone)}
|
|
504
518
|
.cxs{margin-left:auto;padding-left:8px;font-size:11.5px;color:var(--faint);white-space:nowrap}
|
|
505
519
|
.cx.signin .cxs,.cx.signedout .cxs,.cx.error .cxs{color:var(--amber)}
|
|
@@ -682,7 +696,8 @@ body.lq-liquid .sset label.check{display:flex;align-items:center;gap:10px;margin
|
|
|
682
696
|
body.lq-liquid .sset .note{font-size:12.5px;max-width:70ch}
|
|
683
697
|
body.lq-liquid .sset .cxh{margin-top:0}
|
|
684
698
|
/* the Close bar carries the panel's own ground, so what scrolls under it stays clean */
|
|
685
|
-
|
|
699
|
+
/* top: the panel's padding (main's 16px), so it sticks at the very top edge: scrolled content no longer shows in a strip above it */
|
|
700
|
+
body.lq-liquid.lq-pooled #lqsinkrow{top:-16px;margin:-14px -14px 10px;padding:10px 10px 8px;background:linear-gradient(180deg,rgba(6,8,11,.97) 70%,rgba(6,8,11,0));border-radius:22px 22px 0 0}
|
|
686
701
|
body.lq-liquid.lq-light.lq-pooled #lqsinkrow{background:linear-gradient(180deg,rgba(246,248,251,.98) 70%,rgba(246,248,251,0))}
|
|
687
702
|
|
|
688
703
|
/* Agents: needs you, at work, finished (folded) */
|
|
@@ -743,6 +758,11 @@ body.lq-light .bwthread{border-top-color:rgba(184,106,12,.45)}
|
|
|
743
758
|
.pshead{display:flex;flex-direction:column;gap:2px;margin:24px 0 10px}
|
|
744
759
|
.pshead span{font:600 15px var(--sans);color:var(--bone)}
|
|
745
760
|
.pshead small{font-size:12.5px;color:var(--faint)}
|
|
761
|
+
/* Marketing's 4-week test: a row a week; the week you're in marked, the ones to come faint */
|
|
762
|
+
.mkwrap{overflow-x:auto}.mktab{width:100%;border-collapse:collapse;font-size:13px}
|
|
763
|
+
.mktab th{font:500 11px var(--sans);letter-spacing:.08em;text-transform:uppercase;color:var(--faint);text-align:left;padding:6px 10px 8px 0;border-bottom:1px solid var(--line);white-space:nowrap}
|
|
764
|
+
.mktab td{padding:9px 10px 9px 0;border-bottom:1px solid var(--line);color:var(--text);vertical-align:baseline}.mktab td b{color:var(--bone)}
|
|
765
|
+
.mktab td.n{font-variant-numeric:tabular-nums}.mktab tr[aria-current] td{color:var(--bone)}.mktab tr[aria-current] td:first-child{box-shadow:inset 2px 0 0 var(--amber);padding-left:8px}
|
|
746
766
|
.pshelf{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px;align-items:start}
|
|
747
767
|
body.lq-liquid .pshelf .pcard{position:relative;display:flex;flex-direction:column;gap:10px;margin:0;padding:16px 18px;border-radius:20px;border:1px solid rgba(255,255,255,.08);border-left:1px solid rgba(255,255,255,.08);background:rgba(255,255,255,.035)}
|
|
748
768
|
body.lq-liquid.lq-light .pshelf .pcard{background:rgba(255,255,255,.75);border-color:rgba(21,26,33,.08)}
|
|
@@ -769,6 +789,9 @@ body.lq-liquid #panel-tasks .task.open .t{display:block;-webkit-line-clamp:unset
|
|
|
769
789
|
#tasklist.onerepo .task .rp,.rcard .task .rp{display:none}
|
|
770
790
|
body.lq-liquid .rcard{border:1px solid rgba(242,165,65,.3);border-radius:20px;padding:16px 18px 14px;background:rgba(255,255,255,.035);box-shadow:none;margin-top:10px}
|
|
771
791
|
body.lq-liquid.lq-light .rcard{background:rgba(255,255,255,.75);border-color:rgba(154,82,0,.25)}
|
|
792
|
+
/* a long card's Approve row sticks to the panel's bottom edge (bottom: main's padding) while the card is in view, so it's never scrolled out of reach */
|
|
793
|
+
body.lq-liquid .rcard>.ract{position:sticky;bottom:-16px;z-index:1;margin:8px -18px 0;padding:12px 18px 10px;background:linear-gradient(180deg,rgba(9,11,15,0),rgba(9,11,15,.97) 14px)}
|
|
794
|
+
body.lq-liquid.lq-light .rcard>.ract{background:linear-gradient(180deg,rgba(248,249,251,0),rgba(248,249,251,.96) 14px)}
|
|
772
795
|
body.lq-liquid .rcard .rhead b{font:600 16px var(--sans);letter-spacing:-.01em}
|
|
773
796
|
body.lq-liquid .rcard .task{margin-top:6px;padding:9px 12px;border-radius:12px}
|
|
774
797
|
.rwork{display:flex;align-items:center;gap:9px;margin:10px 0 2px;font-size:13px;color:var(--text)}
|
|
@@ -845,7 +868,7 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
|
|
|
845
868
|
.bcard .tchat{border-top:1px solid var(--line);border-radius:9px;margin-top:8px}
|
|
846
869
|
.bcard .bbrief{white-space:pre-line;margin:8px 0;padding:8px 10px;border-left:3px solid var(--green);background:var(--ink3);border-radius:0 8px 8px 0;font-size:13px;color:var(--bone)}
|
|
847
870
|
@media(max-width:760px){.maprow{flex-direction:column}.detail{width:auto;max-height:none}}
|
|
848
|
-
@media(max-width:600px){#
|
|
871
|
+
@media(max-width:600px){#screenname,#pagesite{flex:1 1 100%!important}.tabs{overflow-x:auto;scrollbar-width:none;padding:0 8px;gap:0}.tabs::-webkit-scrollbar{display:none}.tab{flex:none;padding:9px 11px}header{padding:12px 14px 8px}main{padding:12px}}
|
|
849
872
|
</style></head><body>
|
|
850
873
|
<div id="liquid" aria-label="Symbiot">
|
|
851
874
|
<canvas id="lq" aria-hidden="true"></canvas>
|
|
@@ -879,11 +902,11 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
|
|
|
879
902
|
<button class="tab" data-tab="todo">Todo</button>
|
|
880
903
|
<button class="tab" data-tab="tasks">Tasks</button>
|
|
881
904
|
<button class="tab" data-tab="agents">Agents</button>
|
|
882
|
-
<button class="tab" data-tab="reports">Reports</button>
|
|
905
|
+
<button class="tab" data-tab="reports">Reports</button><button class="tab" data-tab="marketing">Marketing</button>
|
|
883
906
|
<button class="tab" data-tab="settings">Settings</button>
|
|
884
907
|
</div>
|
|
885
908
|
<main>
|
|
886
|
-
<div id="lqsinkrow"><div class="lqwhere"><span id="lqcrumb" class="lqcrumb">Home</span><h2 id="lqtitle" aria-live="polite"></h2><button type="button" class="ghost hidden" id="lqpark"></button></div><button class="iconbtn wide" type="button" id="lqsink" title="close it: it sinks back into the liquid (Esc, or right-click)"><svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true"><path d="M2 2l8 8M10 2l-8 8" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>Close</button></div>
|
|
909
|
+
<div id="lqsinkrow"><div class="lqwhere"><span id="lqcrumb" class="lqcrumb">Home</span><h2 id="lqtitle" aria-live="polite"></h2><button type="button" class="ghost hidden" id="lqpark"></button></div><div class="lqacts"><span id="lqtools"><button class="ghost hidden" type="button" id="pushtasks" title="Write .symbiot/TASKS.md into each repo for your coding agent">Send to repos</button></span><button class="iconbtn wide" type="button" id="lqsink" title="close it: it sinks back into the liquid (Esc, or right-click)"><svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true"><path d="M2 2l8 8M10 2l-8 8" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>Close</button></div></div>
|
|
887
910
|
<section id="panel-map">
|
|
888
911
|
<div class="profile muted" id="profile">Mapping your work…</div>
|
|
889
912
|
<div class="maprow">
|
|
@@ -921,7 +944,6 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
|
|
|
921
944
|
<div id="bstream" class="bstream"><canvas id="bsc" aria-hidden="true"></canvas><div id="bsov"></div></div>
|
|
922
945
|
<div id="boardtalk"></div>
|
|
923
946
|
<div id="boardawait"></div>
|
|
924
|
-
<div id="boardposts"></div>
|
|
925
947
|
<details id="blist" class="blist"><summary>All of it as a list</summary><div id="board" class="board"></div></details>
|
|
926
948
|
<div id="boardlinks"></div>
|
|
927
949
|
</section>
|
|
@@ -931,8 +953,7 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
|
|
|
931
953
|
<div class="rfoot" id="outfoot" style="display:none"></div>
|
|
932
954
|
</section>
|
|
933
955
|
<section id="panel-tasks" class="hidden">
|
|
934
|
-
<
|
|
935
|
-
<details class="tnhow"><summary>How tasks work</summary><div class="note muted">To give tasks to your agent, use <b>Send to repos</b> — 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>
|
|
956
|
+
<details class="tnhow"><summary>How tasks work</summary><div class="note muted">To add a task, tell Symbiot in the bar at the bottom: it files it under the right project. To give tasks to your agent, use <b>Send to repos</b> at the top, beside Close — 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>
|
|
936
957
|
<div id="needsbox"></div>
|
|
937
958
|
<div id="pushout"></div>
|
|
938
959
|
<div id="reviewout"></div>
|
|
@@ -941,7 +962,7 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
|
|
|
941
962
|
<div id="tasklist"></div>
|
|
942
963
|
</section>
|
|
943
964
|
<section id="panel-agents" class="hidden">
|
|
944
|
-
<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"
|
|
965
|
+
<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>
|
|
945
966
|
<div id="agentsmsg"></div>
|
|
946
967
|
<div id="laneslist"></div>
|
|
947
968
|
<div id="agentslist"></div>
|
|
@@ -951,6 +972,13 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
|
|
|
951
972
|
<div id="reportslist"></div>
|
|
952
973
|
<div id="reportview"></div>
|
|
953
974
|
</section>
|
|
975
|
+
<section id="panel-marketing" class="hidden">
|
|
976
|
+
<div class="row"><span class="muted" style="flex:1">What you post about your work, and who answers: your drafts, the replies to them, and the 4-week test of whether posting brings anyone. Symbiot never posts: you do, on LinkedIn.</span></div>
|
|
977
|
+
<div id="mkmsg"></div>
|
|
978
|
+
<div id="boardposts"></div>
|
|
979
|
+
<div id="mkreplies"></div>
|
|
980
|
+
<div id="mktest"></div>
|
|
981
|
+
</section>
|
|
954
982
|
<section id="panel-drift" class="hidden">
|
|
955
983
|
<div class="row"><span class="muted">What's out of sync, stuck or at risk across your repos — local git facts.</span>
|
|
956
984
|
<label class="muted swl" style="margin-left:auto"><input type="checkbox" class="sw" id="driftfetch"> Fetch latest</label>
|
|
@@ -968,7 +996,7 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
|
|
|
968
996
|
<option value="gemini">Gemini (Google)</option>
|
|
969
997
|
<option value="ollama">Local model (Ollama) - free, no key</option>
|
|
970
998
|
</select>
|
|
971
|
-
<div class="note muted">The Map
|
|
999
|
+
<div class="note muted">The Map needs no key. For the write-ups, a hosted model needs an API key - or run <b>Ollama</b> locally for a free, private option (nothing leaves your machine).</div>
|
|
972
1000
|
<div id="keyWrap"><label>API key</label><input id="key" type="password" placeholder="paste your key">
|
|
973
1001
|
<div class="note"><a id="getkey">Where do I get a key?</a></div></div>
|
|
974
1002
|
<div id="baseWrap" class="hidden"><label>Ollama URL</label><input id="baseUrl" type="text" value="http://localhost:11434"></div>
|
|
@@ -1083,9 +1111,10 @@ $('panel-tasks').classList.toggle('hidden',!isTasks);
|
|
|
1083
1111
|
$('panel-drift').classList.toggle('hidden',!isDrift);
|
|
1084
1112
|
$('panel-agents').classList.toggle('hidden',!isAgents);
|
|
1085
1113
|
$('panel-reports').classList.toggle('hidden',tab!=='reports');if(tab==='reports')loadReports();
|
|
1114
|
+
$('panel-marketing').classList.toggle('hidden',tab!=='marketing');if(tab==='marketing')loadMarketing();
|
|
1086
1115
|
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();}
|
|
1087
1116
|
if(isMap&&!mapLoaded)loadMap();
|
|
1088
|
-
if(isTasks)
|
|
1117
|
+
if(isTasks)loadTasks();
|
|
1089
1118
|
if(isDrift&&!driftLoaded)loadDrift();
|
|
1090
1119
|
if(isAgents)loadAgents(); else stopAgentsPoll();
|
|
1091
1120
|
lqTitle();if(isTasks&&TFILTER.repo)loadParked(lqTitle);}
|
|
@@ -1106,6 +1135,9 @@ var fsTimer=null;(function(){var ps=$('panel-settings');if(ps&&ps.addEventListen
|
|
|
1106
1135
|
var tabPicked=false;tabs().forEach(function(t){t.addEventListener('click',function(){tabPicked=true;setTab(t.dataset.tab);});});
|
|
1107
1136
|
// Watching a page (your inbox, GitHub)? The app opens on the Dashboard instead of
|
|
1108
1137
|
// the Map, unless you've picked a tab already. The Map still loads behind it.
|
|
1138
|
+
// A link to a screen (#reports, #board…) opens on it: how symbiot post's picture of
|
|
1139
|
+
// "the new Reports view" shows that view, not home.
|
|
1140
|
+
function lqDeep(){var h=String(location.hash||'').slice(1);if(!h||!LQNAMES[h])return;setTimeout(function(){tabPicked=true;lqPool(h);},400);}
|
|
1109
1141
|
function firstTab(){api('/api/watch/board').then(function(b){if(b&&b.cards&&b.cards.length&&!tabPicked&¤t==='map')setTab('board');}).catch(function(){});}
|
|
1110
1142
|
// The AI in use isn't shown while it works (it's noise); only when none is
|
|
1111
1143
|
// connected, as an urgent line that opens its part of Settings. Home has it out
|
|
@@ -1123,6 +1155,9 @@ var tf=typeof TFILTER!=='undefined'&&TFILTER||{},arch=typeof TARCH!=='undefined'
|
|
|
1123
1155
|
if(proj)name=lqProjName(proj);var parked=!!proj&&PARKED.repos.indexOf(proj)>=0;
|
|
1124
1156
|
if(c)c.textContent=proj?'Projects':'Home';t.innerHTML=esc(name)+(parked?"<span class='lqpk'>parked</span>":'');
|
|
1125
1157
|
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);}
|
|
1158
|
+
// the open panel's own Refresh sits beside Close, not under it on the panel's top line
|
|
1159
|
+
var tl=$('lqtools');if(tl&&tl.appendChild)['agents','reports'].forEach(function(p){var b=$(p+'refresh'),row=$('panel-'+p);row=row&&row.querySelector('.row');if(!b)return;if(p===current){if(b.parentNode!==tl)tl.appendChild(b);}else if(b.parentNode===tl&&row&&row.appendChild)row.appendChild(b);});
|
|
1160
|
+
var pt=$('pushtasks');if(pt&&pt.classList)pt.classList.toggle('hidden',current!=='tasks'||!!arch);
|
|
1126
1161
|
try{document.title=name+' \\u00b7 Symbiot';}catch(e){}}
|
|
1127
1162
|
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();});});}
|
|
1128
1163
|
function loadParked(then){api('/api/lanes/parked').then(function(p){PARKED={repos:(p&&p.repos)||[],paths:(p&&p.paths)||[]};if(then)then();}).catch(function(){if(then)then();});}
|
|
@@ -1369,7 +1404,7 @@ else if(ur)h+="<div class='muted' style='margin:6px 0'><span class='err'>Unrelea
|
|
|
1369
1404
|
// The version is already released (its v* tag exists, or npm has it) and these changes keep it:
|
|
1370
1405
|
// Approve bumps it in the PR too, a patch unless you pick otherwise.
|
|
1371
1406
|
var bo=r.bumpOffer;if(bo&&r.files.length)h+="<div class='row' style='margin:6px 0;font-size:12px'><span class='muted'>Version</span><select class='bumpsel' title='bump the version in package.json (and the lockfile) in this PR' style='flex:0 0 auto;width:auto'><option value='patch'>bump to "+esc(bo.patch)+" (patch)</option><option value='minor'>bump to "+esc(bo.minor)+" (minor)</option><option value=''>keep "+esc(bo.version)+"</option></select></div>";
|
|
1372
|
-
h+="<div class='row'>"+(pq&&r.path?"<button class='act answerq' data-path='"+esc(r.path).replace(/'/g,''')+"'>Answer its question"+(pq>1?"s":"")+"</button>":"")+"<button class='"+(pd?'ghost':'act')+" approve'"+(pd?" data-partly='1'":"")+(r.path&&!r.running?"":" disabled")+(r.running?" title='the agent is still editing this repo'>"+(r.untasked?"Approve changes without a task":"Approve")+" · agent still working":r.untasked?" data-untasked='1' title='commit on a branch, push and open a PR, without a task'>Approve changes without a task → PR":" title='commit on a branch, push, open a PR, then archive'>Approve → "+(r.files.length?"PR":"archive"))+"</button>"+(r.files.length?"<button class='ghost showdiff'>Show diff</button>":"")+"<label title='Queue GitHub auto-merge so this PR lands once its CI checks pass. Needs Allow auto-merge on the repo.' style='margin-left:auto;font-size:12px;color:var(--faint);display:flex;align-items:center;gap:6px'><input type='checkbox' class='amtoggle' style='width:auto'"+(r.autoMerge?" checked":"")+"> auto-merge on green CI</label></div><div class='rdiff hidden'></div></div>";});
|
|
1407
|
+
h+="<div class='row ract'>"+(pq&&r.path?"<button class='act answerq' data-path='"+esc(r.path).replace(/'/g,''')+"'>Answer its question"+(pq>1?"s":"")+"</button>":"")+"<button class='"+(pd?'ghost':'act')+" approve'"+(pd?" data-partly='1'":"")+(r.path&&!r.running?"":" disabled")+(r.running?" title='the agent is still editing this repo'>"+(r.untasked?"Approve changes without a task":"Approve")+" · agent still working":r.untasked?" data-untasked='1' title='commit on a branch, push and open a PR, without a task'>Approve changes without a task → PR":" title='commit on a branch, push, open a PR, then archive'>Approve → "+(r.files.length?"PR":"archive"))+"</button>"+(r.files.length?"<button class='ghost showdiff'>Show diff</button>":"")+"<label title='Queue GitHub auto-merge so this PR lands once its CI checks pass. Needs Allow auto-merge on the repo.' style='margin-left:auto;font-size:12px;color:var(--faint);display:flex;align-items:center;gap:6px'><input type='checkbox' class='amtoggle' style='width:auto'"+(r.autoMerge?" checked":"")+"> auto-merge on green CI</label></div><div class='rdiff hidden'></div></div>";});
|
|
1373
1408
|
el.innerHTML=h;
|
|
1374
1409
|
el.querySelectorAll('.rcard').forEach(function(card){var repo=card.getAttribute('data-repo');
|
|
1375
1410
|
card.querySelectorAll('.task').forEach(function(row){wireAsk(row,row.getAttribute('data-id'));});
|
|
@@ -1448,6 +1483,13 @@ api('/api/agents/skip',{path:a.path,text:s.text}).then(function(r){if(!r||r.erro
|
|
|
1448
1483
|
// A step of yours an answer picked (👤 You): the next run waits for it (agents.mjs waitingFor)
|
|
1449
1484
|
function waitHtml(a){var w=a.waiting;if(!w)return '';var fs=(w.files||[]).map(function(f){return "<code>"+esc(f)+"</code>";}).join(" or ");if(w.cmd)fs=(fs?fs+" or ":"")+"the agent command in Settings → Handoff";
|
|
1450
1485
|
return "<div class='aq'><h4><i class=ic-pause></i> Waiting on your step</h4><div class='qt'><i class=ic-person></i> "+esc(w.step)+"</div><div class='qc'>"+(fs?"Your agent "+(w.rerun?"starts by itself":"can start")+" once "+fs+" changes"+(w.rerun?", while Symbiot runs":"")+". Done it some other way?":"Your agent waits for it, so it doesn't stop on the same questions again.")+" Start it now once it's done.</div><div class='row'><button class='act wstart' data-id='"+esc(a.id)+"'>Start it now</button></div></div>";}
|
|
1486
|
+
// A run that ended waiting on you (agents.mjs needsOf): your OK for what it would send or
|
|
1487
|
+
// change, or a step only you can do, with what to check first. Answered as on Home.
|
|
1488
|
+
function needsHtml(a){var n=a.needs;if(!n||n.waiting)return '';var ok=n.kind==='approve';
|
|
1489
|
+
return "<div class='aq'><h4><i class=ic-person></i> "+(ok?"Waits for your OK":"Needs you")+"</h4><div class='qt'>"+esc(n.what)+"</div>"+(n.check?"<div class='qc'><b>Check first:</b> "+esc(n.check)+"</div>":"")
|
|
1490
|
+
+"<div class='row'><button class='act ngo' data-id='"+esc(a.id)+"' title='"+(ok?"its agent goes ahead with it":"its agent checks it worked and carries on")+"'>"+(ok?"Go ahead":"Done it")+"</button><button class='ghost nskip' data-id='"+esc(a.id)+"' title='it stops asking'>Skip</button></div></div>";}
|
|
1491
|
+
function wireNeeds(el){el.querySelectorAll('.ngo,.nskip').forEach(function(btn){btn.addEventListener('click',function(){var a=agentById(btn.getAttribute('data-id'));if(!a)return;btn.disabled=true;var msg=document.getElementById('agentsmsg');
|
|
1492
|
+
api('/api/home/answer',{id:'needs:'+a.id,pick:btn.classList.contains('ngo')?0:1}).then(function(r){msg.innerHTML=r&&!r.error?"<div class='note ok'>"+esc(a.name)+": "+esc(r.said||'Done.')+"</div>":"<div class='note err'>"+esc((r&&r.error)||'failed')+"</div>";loadAgents();}).catch(function(e){btn.disabled=false;msg.innerHTML="<div class='note err'>"+esc(String((e&&e.message)||e))+"</div>";});});});}
|
|
1451
1493
|
function wireWaits(el){el.querySelectorAll('.wstart').forEach(function(btn){btn.addEventListener('click',function(){var a=agentById(btn.getAttribute('data-id'));if(!a)return;btn.disabled=true;
|
|
1452
1494
|
api('/api/open',{path:a.path,force:true}).then(function(x){var msg=document.getElementById('agentsmsg');msg.innerHTML=x&&x.opened?"<div class='note ok'>✓ Started your agent in <b>"+esc(a.name)+"</b>.</div>":"<div class='note err'>"+esc(a.name)+": "+(x&&x.busy?"an agent is already running there.":"it didn't start. Check the command in Settings.")+"</div>";loadAgents();}).catch(function(e){btn.disabled=false;document.getElementById('agentsmsg').innerHTML="<div class='note err'>"+esc(String((e&&e.message)||e))+"</div>";});});});}
|
|
1453
1495
|
// What a run left for Symbiot's memory (.symbiot/REMEMBER.json, handback.mjs): nothing goes in until Remember
|
|
@@ -1504,7 +1546,7 @@ function loadAgents(){loadLanes();loadParked(agentsDraw);}
|
|
|
1504
1546
|
// Agents, in order: what needs you (a question, a step of yours), what's at work, and
|
|
1505
1547
|
// what's finished, folded away (open stays open across refreshes).
|
|
1506
1548
|
var AGDONEOPEN=false;
|
|
1507
|
-
function agentGroups(list,fn){var needs=[],run=[],done=[];list.forEach(function(a){if(nQs(a)||a.waiting)needs.push(a);else if(a.status==='running')run.push(a);else done.push(a);});
|
|
1549
|
+
function agentGroups(list,fn){var needs=[],run=[],done=[];list.forEach(function(a){if(nQs(a)||a.waiting||a.needs)needs.push(a);else if(a.status==='running')run.push(a);else done.push(a);});
|
|
1508
1550
|
var g=function(t,xs){return xs.length?"<div class='tgroup agg'>"+t+" <span class='tcount'>"+xs.length+"</span></div>"+xs.map(fn).join(''):'';};
|
|
1509
1551
|
return g('Needs you',needs)+g('At work',run)+(done.length?"<details class='agdone'"+(AGDONEOPEN?' open':'')+"><summary class='tgroup agg'>Finished <span class='tcount'>"+done.length+"</span></summary>"+done.map(fn).join('')+"</details>":'');}
|
|
1510
1552
|
function agentsDraw(){api('/api/agents').then(function(list){var el=document.getElementById('agentslist');
|
|
@@ -1518,6 +1560,7 @@ if(a.fromHeld)st+=" · started on the tasks held for the last run";
|
|
|
1518
1560
|
if(a.earlier)st+=" · "+(a.status==='running'?"started outside this window":"ran before Symbiot last started");
|
|
1519
1561
|
if(a.waiting)st+=" · <span style='color:var(--amber)'>waiting on your step</span>";
|
|
1520
1562
|
if(nQs(a))st+=" · <span style='color:var(--amber)'>needs your answers</span>";
|
|
1563
|
+
if(a.needs&&!a.needs.waiting)st+=" · <span style='color:var(--amber)'>"+(a.needs.kind==='approve'?"waits for your OK":"needs you")+"</span>";
|
|
1521
1564
|
if(a.remember&&a.remember.length)st+=" · <span style='color:var(--amber)'>found "+a.remember.length+" thing"+(a.remember.length>1?"s":"")+" to remember</span>";
|
|
1522
1565
|
if(a.held!=null){var hn=a.held.length,ht=a.held.map(function(t){return "• "+esc(t).replace(/'/g,''');}).join(' ');
|
|
1523
1566
|
ht+=(hn?' ':'')+(a.status==='running'?"Sent while this agent was running. They wait in .symbiot/TASKS.next.md, replace TASKS.md when it finishes (keeping its ticks), and an agent starts on them then.":"They wait in .symbiot/TASKS.next.md for the agent running in this folder to finish. After that, an agent starts on them the next time the Tasks tab checks this repo, or send again.");
|
|
@@ -1525,7 +1568,7 @@ st+=" · <span style='color:var(--amber);cursor:help' title='"+ht+"'><i cl
|
|
|
1525
1568
|
// a repo's run can be parked from here in one click: its tasks start no runs until it's unparked
|
|
1526
1569
|
var pk=!/\\/drafts\\/act-[^/]*$/.test(String(a.path||''))&&a.path,parked=pk&&PARKED.paths.indexOf(a.path)>=0;
|
|
1527
1570
|
if(parked)st+=" · <span style='color:var(--amber)'>parked: no new runs</span>";
|
|
1528
|
-
var b="<div class='dh'><span class='orb "+cls+"'></span><span class='dn'>"+esc(String(a.name||'').split('**').join(''))+"</span><span class='dd'>"+st+"</span>"+(pk?"<button type='button' class='ghost apark' data-on='"+(parked?'0':'1')+"' title='"+(parked?"let its tasks start agent runs again":"stop its tasks starting agent runs, until you unpark it")+"'>"+(parked?'Unpark':'Park')+"</button>":"")+"</div>";
|
|
1571
|
+
var b="<div class='dh'><span class='orb "+cls+"'></span><span class='dn'>"+esc(String(a.name||'').split('**').join('').replace(/^(Agent:\s*)What['’]s needed:\s*/i,'$1'))+"</span><span class='dd'>"+st+"</span>"+(pk?"<button type='button' class='ghost apark' data-on='"+(parked?'0':'1')+"' title='"+(parked?"let its tasks start agent runs again":"stop its tasks starting agent runs, until you unpark it")+"'>"+(parked?'Unpark':'Park')+"</button>":"")+"</div>";
|
|
1529
1572
|
b+=workHtml(a);
|
|
1530
1573
|
var ch=a.changed;
|
|
1531
1574
|
if(ch&&(ch.dirty||ch.stat||(ch.commits&&ch.commits.length))){
|
|
@@ -1537,12 +1580,12 @@ if(ch&&(ch.dirty||ch.stat||(ch.commits&&ch.commits.length))){
|
|
|
1537
1580
|
}else if(a.status==='done'){
|
|
1538
1581
|
b+="<div class='changed muted'>No file changes detected (the agent may have only planned or asked).</div>";
|
|
1539
1582
|
}
|
|
1540
|
-
b+=waitHtml(a)+askHtml(a)+factsHtml(a);
|
|
1583
|
+
b+=waitHtml(a)+needsHtml(a)+askHtml(a)+factsHtml(a);
|
|
1541
1584
|
b+=a.work?("<details class='wkraw'><summary>what it said, in full</summary><pre class='alogout'>"+esc((a.tail&&a.tail.trim())||'(nothing yet)')+"</pre></details>"):("<pre class='alogout'>"+esc((a.tail&&a.tail.trim())||'(waiting for output…)')+"</pre>");
|
|
1542
1585
|
return "<div class='agent' data-i='"+list.indexOf(a)+"'>"+b+"</div>";};
|
|
1543
1586
|
el.innerHTML=agentGroups(list,agentBlock);var dn=el.querySelector('.agdone');if(dn)dn.addEventListener('toggle',function(){AGDONEOPEN=dn.open;});
|
|
1544
1587
|
el.querySelectorAll('.alogout').forEach(function(p){p.scrollTop=p.scrollHeight;});
|
|
1545
|
-
restoreDrafts(el);wireAsks(el);wireWaits(el);wireFacts(el);lqLight();
|
|
1588
|
+
restoreDrafts(el);wireAsks(el);wireWaits(el);wireNeeds(el);wireFacts(el);lqLight();
|
|
1546
1589
|
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);});});
|
|
1547
1590
|
stopAgentsPoll();if(anyRunning&¤t==='agents')agentsTimer=setTimeout(loadAgents,2000);});}
|
|
1548
1591
|
function loadDrift(){var out=document.getElementById('driftout');out.innerHTML="<div class='muted' style='margin-top:12px'>Reading your repos…</div>";
|
|
@@ -1557,16 +1600,15 @@ var clean=repos.filter(function(r){return !r.flags.length;}).map(function(r){ret
|
|
|
1557
1600
|
if(clean.length)h+="<div class='muted' style='margin-top:10px'>clean: "+esc(clean.join(", "))+"</div>";
|
|
1558
1601
|
if(!repos.length)h+="<div class='muted'>No repos found under "+esc(d.base||"your home folder")+".</div>";
|
|
1559
1602
|
out.innerHTML=h;});}
|
|
1560
|
-
function fillTaskRepos(){var sel=document.getElementById('newtaskrepo');if(!sel||!GRAPH)return;var cur=sel.value;var names=GRAPH.nodes.filter(function(n){return n.type==='repo'||n.type==='folder';}).map(function(n){return n.label;}).sort();sel.innerHTML="<option value=''>repo…</option>"+names.map(function(n){return "<option value='"+esc(n)+"'"+(n===cur?" selected":"")+">"+esc(n)+"</option>";}).join("");}
|
|
1561
|
-
function addTaskUI(){var i=document.getElementById('newtask');var v=(i.value||'').trim();if(!v)return;var repo=(document.getElementById('newtaskrepo')||{}).value||'';api('/api/tasks/add',{text:v,repo:repo}).then(function(){i.value='';TFILTER={type:'',repo:''};loadTasks();});}
|
|
1562
1603
|
function pushTasksUI(){var o=document.getElementById('pushout');var btn=document.getElementById('pushtasks');btn.disabled=true;
|
|
1563
1604
|
o.innerHTML="<div class='muted' style='margin-top:10px'><span class='dot-c' style='background:var(--amber)'></span> Writing .symbiot/TASKS.md into your repos…</div>";
|
|
1564
1605
|
api('/api/tasks/push',{type:TFILTER.type||'',repo:TFILTER.repo||''}).then(function(r){
|
|
1565
|
-
if(r.empty){btn.disabled=false;o.innerHTML="<div class='muted' style='margin-top:10px'>No open tasks to send.
|
|
1566
|
-
var n=(r.written||[]).length;var opens=[];
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
if(r.
|
|
1606
|
+
if(r.empty){btn.disabled=false;o.innerHTML="<div class='muted' style='margin-top:10px'>No open tasks to send. Tell Symbiot what to do in the bar at the bottom, or tick ideas in a repo review.</div>";return;}
|
|
1607
|
+
var n=(r.written||[]).length;var opens=[];
|
|
1608
|
+
// one line, its paths on hover: the review cards below it stay in view
|
|
1609
|
+
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>";
|
|
1610
|
+
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>";}
|
|
1611
|
+
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);}
|
|
1570
1612
|
else{h+="<div class='dd' style='margin-top:8px'>Set an <b>agent command</b> in Settings to auto-run it on send (and watch it in the Agents tab). For now, tell your agent: <b>“Read .symbiot/TASKS.md and implement the unchecked items.”</b></div>";}
|
|
1571
1613
|
h+="</div>";
|
|
1572
1614
|
o.innerHTML=h;
|
|
@@ -1660,10 +1702,17 @@ function escQ(t){return esc(t).replace(/'/g,''');}
|
|
|
1660
1702
|
// Connections: a column per kind of site; each site a bead in the look: solid when
|
|
1661
1703
|
// linked, amber when it wants you to sign in, hollow when it isn't linked. Link,
|
|
1662
1704
|
// check (↻) and Unlink show on hover (always on touch).
|
|
1663
|
-
|
|
1705
|
+
// The plug marks the sites your agent's runs can use too, before you link them: only
|
|
1706
|
+
// those Claude has a connector for, and only with Claude Code as your agent (agents.mjs
|
|
1707
|
+
// linkReach). Faint until you've connected it in claude.ai.
|
|
1708
|
+
function reachOf(d,id){var r=d.reach,s=r&&r.claude&&r.sites&&r.sites[id];return s||null;}
|
|
1709
|
+
function linksHtml(d,board){var anyR=d.items.some(function(it){return reachOf(d,it.id);});
|
|
1710
|
+
var h="<div class='cxh'><span>Connections</span><small>"+(d.linked?"What Symbiot watches for you. Link more to see what arrives there, here and in Week and Standup.":"Link your work to see what arrives there, here and in Week and Standup.")+"</small>"
|
|
1711
|
+
+(anyR?"<small class='cxlg'><i class='ic-plug cxa ready' aria-hidden='true'></i> your agent's runs can use it too, through Claude's connector for it <span class='cxlgf'><i class='ic-plug cxa' aria-hidden='true'></i> faint: once you connect it in claude.ai (Settings → Connectors)</span></small>":"")+"</div>";
|
|
1664
1712
|
if(d.error)h+="<div class='note err'>"+esc(d.error)+"</div>";
|
|
1665
1713
|
h+="<div class='cxg'>"+d.groups.map(function(g){return "<div class='cxc'><div class='cxl'>"+esc(g)+"</div>"+d.items.filter(function(x){return x.group===g;}).map(function(it){var on=it.state!=='off';
|
|
1666
|
-
|
|
1714
|
+
var rc=reachOf(d,it.id),rt=rc?(rc.ready?" Your agent's runs can use it too, through Claude's "+rc.connector.replace(/^claude\.ai\s+/i,'')+" connector.":" Your agent's runs can use it too once you connect "+rc.name+" in claude.ai → Settings → Connectors (only you can sign in there)."):'';
|
|
1715
|
+
return "<div class='lnk cx "+escQ(it.state)+"' data-id='"+escQ(it.id)+"'><button type='button' class='lbtn' title='"+escQ(String(it.note||(on?'open '+it.url+' to sign in again':'sign in at '+it.url+' and link it')).replace(/\.?\s*$/,rt?'.':'')+rt)+"'><span class='cxb' aria-hidden='true'></span><span class='cxn'>"+esc(it.name)+"</span>"+(rc?"<i class='ic-plug cxa"+(rc.ready?" ready":"")+"' role='img' aria-label='"+escQ(rc.ready?"agent runs can use it too":"agent runs can use it once connected in claude.ai")+"'></i>":"")+"<span class='cxs'>"+(on?esc(LINKWORD[it.state]||''):'Link')+"</span></button>"+
|
|
1667
1716
|
(on?"<button type='button' class='lmore' title='check it now' aria-label='Check "+escQ(it.name)+" now'>↻</button><button type='button' class='lrm' title='unlink: stop watching it and stop trusting it'>Unlink</button>":"")+"</div>";}).join('')+"</div>";}).join('')+"</div>";return h;}
|
|
1668
1717
|
function linksOut(html){var o=document.getElementById('linksmsg');if(o)o.innerHTML=html;var b=document.getElementById('boardmsg');if(b&&LINKS&&!LINKS.linked)b.innerHTML=html;}
|
|
1669
1718
|
function renderLinks(){if(!LINKS)return;['links','boardlinks'].forEach(function(id){var el=document.getElementById(id);if(!el)return;
|
|
@@ -1730,7 +1779,7 @@ function scanLine(s){return 'Mapping your work… '+s.phase+(s.total?' '+s.done+
|
|
|
1730
1779
|
function loadMap(){var p=document.getElementById("profile");p.textContent="Mapping your work...";document.getElementById("graph").innerHTML="";sel=null;hideDetail();hideReview();var done=false;
|
|
1731
1780
|
function poll(){if(done)return;api('/api/scan').then(function(s){if(done)return;if(s&&s.active&&s.phase)p.textContent=scanLine(s);setTimeout(poll,600);}).catch(function(){});}
|
|
1732
1781
|
setTimeout(poll,400);
|
|
1733
|
-
api("/api/map").then(function(g){done=true;mapLoaded=true;if(!g.nodes||!g.nodes.length){p.textContent="No git repositories found under your home folder.";return;}GRAPH=g;
|
|
1782
|
+
api("/api/map").then(function(g){done=true;mapLoaded=true;if(!g.nodes||!g.nodes.length){p.textContent="No git repositories found under your home folder.";return;}GRAPH=g;layout(g.nodes,g.edges);lmBuild(g);view={k:1,x:0,y:0};p.innerHTML=profileLine(g)+(g.stats&&g.stats.partial?" · <span class='err'>partial — the scan hit its time limit</span>":"");var af=$('allowfiles');if(af)af.addEventListener('click',function(){SymbiotAndroid.storage();});var ut=$('usetermux');if(ut)ut.addEventListener('click',function(){SymbiotAndroid.openTermux();});render();});}
|
|
1734
1783
|
// The Android app calls this when you come back having allowed file access, so
|
|
1735
1784
|
// the map rescans instead of staying empty.
|
|
1736
1785
|
window.symbiotStorageGranted=function(){if(current==='map')loadMap();else mapLoaded=false;};
|
|
@@ -1851,25 +1900,44 @@ var CUSTWHY={pricing:'asks about pricing',install:'asks how to install it',team:
|
|
|
1851
1900
|
function custTag(n){return n.customer?" <span class='tag cust' title='"+escQ(CUSTWHY[n.customer]||'')+"'>maybe a customer</span>":"";}
|
|
1852
1901
|
// ---- Posts (post.mjs): the week's real work as drafts waiting on you: Approve, Edit, Skip. Symbiot never posts:
|
|
1853
1902
|
// Approve copies the post and opens LinkedIn's share box, where you paste it and post it yourself ----
|
|
1854
|
-
var POSTS=null,PEDIT={},PBUSY=0,PMSG='';
|
|
1903
|
+
var POSTS=null,PEDIT={},PBUSY=0,PMSG='',PFORM={},PSEEN='';
|
|
1855
1904
|
// what the last action said: kept across a re-render (the list refreshes right after an Approve)
|
|
1856
1905
|
function postsMsg(cls,html){PMSG=html?"<div class='note "+cls+"'>"+html+"</div>":'';var o=$('postsmsg');if(o){o.innerHTML=PMSG;wireShare();}}
|
|
1857
|
-
var PSHARE='';function wireShare(){var sh=document.querySelector('#postsmsg .pshare');if(sh&&PSHARE)sh.addEventListener('click',function(){window.open(PSHARE,'_blank','noopener');});
|
|
1858
|
-
|
|
1906
|
+
var PSHARE='',PFOLDER='';function wireShare(){var sh=document.querySelector('#postsmsg .pshare');if(sh&&PSHARE)sh.addEventListener('click',function(){window.open(PSHARE,'_blank','noopener');});
|
|
1907
|
+
var fo=document.querySelector('#postsmsg .pfolder');if(fo&&PFOLDER)fo.addEventListener('click',function(){api('/api/posts/media/folder',{id:PFOLDER}).then(function(r){if(r&&r.error)postsMsg('err',esc(r.error));});});}
|
|
1908
|
+
// unchanged since the last look: left as it is, so a playing video or a half-typed page stays
|
|
1909
|
+
function loadPostsUI(){api('/api/posts').then(function(p){if(!p||!p.posts)return;POSTS=p;var f=document.activeElement,k=JSON.stringify(p);if(PBUSY||(f&&f.closest&&f.closest('#boardposts')&&/^(TEXTAREA|INPUT)$/.test(f.tagName)))return;if(k===PSEEN&&$('boardposts')&&$('boardposts').innerHTML)return;PSEEN=k;renderPosts();});}
|
|
1910
|
+
// a draft's pictures and video, each with a way off it, and the page form (From a page) when it's open
|
|
1911
|
+
function pmediaSrc(x,a){return '/api/posts/media?post='+encodeURIComponent(x.id)+'&m='+encodeURIComponent(a.id)+'&t='+encodeURIComponent(T);}
|
|
1912
|
+
function pmediaHtml(x){var m=x.media||[],h='',pf=PFORM[x.id],c=POSTS.clip||{min:3,max:30};
|
|
1913
|
+
if(m.length)h+="<div class='pmedia'>"+m.map(function(a){var src=pmediaSrc(x,a),what=a.from==='page'?'picture of '+(a.url||a.name):a.from==='clip'?'clip of '+(a.url||a.name):(a.name||a.kind);
|
|
1914
|
+
return "<div class='pthumb' data-m='"+escQ(a.id)+"' title='"+escQ(what)+"'>"+(a.kind==='video'?"<video src='"+escQ(src)+"' controls muted playsinline preload='metadata'></video>":"<img src='"+escQ(src)+"' alt='"+escQ(what)+"'>")
|
|
1915
|
+
+"<button class='ghost pmdel' title='take it off this draft' aria-label='Remove "+escQ(a.kind)+"'>×</button></div>";}).join('')+"</div>";
|
|
1916
|
+
if(pf)h+="<div class='row pform'><input class='purl' placeholder='a page: localhost:3000, your site, github.com/you' value='"+escQ(pf.url||'')+"' style='flex:1'>"
|
|
1917
|
+
+"<button class='ghost ppic' title='the page as it looks now, sharp enough for a feed'>Take a picture</button>"
|
|
1918
|
+
+"<select class='psecs' title='how long the clip runs' style='flex:0 0 auto;width:auto'>"+[5,8,15,30].filter(function(s){return s>=c.min&&s<=c.max;}).map(function(s){return "<option value='"+s+"'"+(s===(pf.secs||8)?" selected":"")+">"+s+" s</option>";}).join('')+"</select>"
|
|
1919
|
+
+"<button class='ghost pclip'"+(POSTS.canClip?" title='records the page, scrolling slowly down it, as an MP4'":" disabled title='recording a clip needs ffmpeg, which isn't installed'")+">Record a clip</button>"
|
|
1920
|
+
+"<button class='ghost pfclose'>Cancel</button></div>";
|
|
1921
|
+
return h;}
|
|
1859
1922
|
// copy in the page (the click on Approve), else select it for Ctrl+C; gives whether it copied
|
|
1860
1923
|
function copyPost(text,done){function fallback(){var t=document.createElement('textarea');t.value=text;t.style.position='fixed';t.style.opacity='0';document.body.appendChild(t);t.select();var ok=false;try{ok=document.execCommand('copy');}catch(e){}t.remove();done(ok);}
|
|
1861
1924
|
if(navigator.clipboard&&navigator.clipboard.writeText)navigator.clipboard.writeText(text).then(function(){done(true);},fallback);else fallback();}
|
|
1862
1925
|
function renderPosts(){var p=POSTS,el=$('boardposts');if(!el||!p)return;var L=p.labels||{},h='';
|
|
1863
1926
|
// it can't draft yet (no AI, or neither LinkedIn linked nor examples in voice.md): one line, not a block a new user meets first
|
|
1864
|
-
if(!p.posts.length&&p.canDraft===false){var need=[];if(!p.connected)need.push('an AI is connected (Settings)');if(!p.linkedin&&!p.voice.count)need.push('LinkedIn is linked
|
|
1865
|
-
el.innerHTML="<div class='
|
|
1927
|
+
if(!p.posts.length&&p.canDraft===false){var need=[];if(!p.connected)need.push('an AI is connected (Settings)');if(!p.linkedin&&!p.voice.count)need.push('LinkedIn is linked, or <code>'+esc(p.voice.file)+'</code> has a few of your posts');
|
|
1928
|
+
el.innerHTML="<div class='pshead'><span>Drafts to post</span><small>Symbiot drafts your week's LinkedIn posts from your git, in your voice, once "+need.join(', and ')+".</small></div>"+(!p.linkedin?"<div class='row'><button class='ghost plink' title='sign in to LinkedIn once in Symbiot's browser: it reads your posts for your voice, and replies to them show here'>Link LinkedIn</button></div>":"");
|
|
1929
|
+
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;}
|
|
1866
1930
|
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'>";
|
|
1867
1931
|
h+=p.posts.map(function(x){var ed=Object.prototype.hasOwnProperty.call(PEDIT,x.id);
|
|
1868
1932
|
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>"
|
|
1869
1933
|
+(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>")
|
|
1934
|
+
+(x.show&&!(x.media||[]).length?"<div class='pshow'><span class='muted'>Picture idea:</span> "+esc(x.show)+"</div>":"")
|
|
1935
|
+
+pmediaHtml(x)
|
|
1870
1936
|
+(x.sources&&x.sources.length?"<details class='psrc'><summary>From git: "+x.sources.length+" thing"+(x.sources.length===1?"":"s")+" it says</summary>"+x.sources.map(function(s){return "<div>"+esc(s)+"</div>";}).join('')+"</details>":"")
|
|
1871
1937
|
+"<div class='row'>"+(ed?"<button class='act psave'>Save</button><button class='ghost pcancel'>Cancel</button><span class='pchars'>"+String(PEDIT[x.id]).length+" / 3000</span>"
|
|
1872
|
-
:"<button class='act papprove' title='copies it to your clipboard and opens LinkedIn's share box, where you paste it and post it yourself. Symbiot never posts it, and doesn't schedule it'>Approve</button><button class='ghost pedit-btn' title='change its words here'>Edit</button><button class='ghost pskip' title='drop this draft (it stays in the log)'>Skip</button>"
|
|
1938
|
+
:"<button class='act papprove' title='copies it to your clipboard and opens LinkedIn's share box, where you paste it and post it yourself. Symbiot never posts it, and doesn't schedule it'>Approve</button><button class='ghost pedit-btn' title='change its words here'>Edit</button><button class='ghost pskip' title='drop this draft (it stays in the log)'>Skip</button>"
|
|
1939
|
+
+"<button class='ghost pmadd' title='a picture or video of yours: PNG, JPG, GIF, MP4, MOV or WebM'>Add picture or video</button>"+(PFORM[x.id]?"":"<button class='ghost pmfrom' title='a picture, or a short clip, of a web page (your app on localhost too)'>From a page</button>")
|
|
1940
|
+
+"<input type='file' class='pmfile hidden' accept='image/png,image/jpeg,image/gif,video/mp4,video/quicktime,video/webm'>")+"</div></div>";}).join('')+"</div>";}
|
|
1873
1941
|
else h+="<div class='row' style='margin-top:10px'><span class='muted' style='flex:1'>"+(p.voice.count?"Posts: draft this week's 3 LinkedIn posts from your git, in your voice ("+p.voice.count+" example"+(p.voice.count===1?"":"s")+"). Nothing is posted until you approve one, and then you post it yourself."
|
|
1874
1942
|
:"Posts: Symbiot drafts LinkedIn posts from your week's git in your own voice, once it has examples of your posts. Link LinkedIn (below) and click <b>Fill from LinkedIn</b>, or paste 5–10 of your posts into <code>"+esc(p.voice.file)+"</code>, a line of --- between each.")+"</span>"
|
|
1875
1943
|
+"<button class='ghost pvoice' title='reads your recent posts on LinkedIn in Symbiot's signed-in browser into voice.md, which you can read over'>Fill from LinkedIn</button>"
|
|
@@ -1879,8 +1947,28 @@ el.querySelectorAll('.pmore').forEach(function(b){b.addEventListener('click',fun
|
|
|
1879
1947
|
function post(btn){var id=btn.closest('.pcard').getAttribute('data-id');return p.posts.filter(function(x){return x.id===id;})[0];}
|
|
1880
1948
|
el.querySelectorAll('.papprove').forEach(function(btn){btn.addEventListener('click',function(){var x=post(btn);if(!x)return;btn.disabled=true;postsMsg('','');
|
|
1881
1949
|
api('/api/posts/approve',{id:x.id}).then(function(r){if(!r||r.error){btn.disabled=false;postsMsg('err',esc((r&&r.error)||'failed'));return;}
|
|
1882
|
-
copyPost(r.post.text,function(ok){PSHARE=r.share;
|
|
1883
|
-
|
|
1950
|
+
copyPost(r.post.text,function(ok){PSHARE=r.share;PFOLDER=r.files&&r.files.length?r.post.id:'';
|
|
1951
|
+
var n=(r.post.media||[]).length,vid=(r.post.media||[]).some(function(a){return a.kind==='video';}),what=vid?'its video':n===1?'its picture':'its '+n+' pictures';
|
|
1952
|
+
postsMsg('ok',(ok?"✓ Approved and copied to your clipboard. ":"✓ Approved. Your browser didn't let Symbiot copy it: select it below and copy it (Ctrl+C). ")+"<button class='ghost pshare'>Open LinkedIn's share box</button> and paste it there (Ctrl+V)"
|
|
1953
|
+
+(n?", then add "+what+" with LinkedIn's "+(vid?"video":"photo")+" button: <button class='ghost pfolder' title='"+escQ(r.folder||'')+"'>Open "+(n===1?"its":"their")+" folder</button>. Post it yourself.":", then post it yourself.")+" Symbiot doesn't post or schedule it."+(ok?"":"<div style='user-select:all;white-space:pre-wrap;color:var(--bone);margin-top:6px'>"+esc(r.post.text)+"</div>"));loadPostsUI();});}).catch(function(e){btn.disabled=false;postsMsg('err',esc(String((e&&e.message)||e)));});});});
|
|
1954
|
+
// pictures and video: yours (a file), or a picture or clip of a page; each comes off with its ×
|
|
1955
|
+
function pmediaDone(btn,label){return function(r){PBUSY=Math.max(0,PBUSY-1);if(btn){btn.disabled=false;if(label)btn.textContent=label;}if(!r||r.error){postsMsg('err',esc((r&&r.error)||'failed'));return;}
|
|
1956
|
+
if(r.media&&r.post)delete PFORM[r.post.id];var f=document.activeElement;if(f&&f.blur&&f.closest&&f.closest('#boardposts'))f.blur();postsMsg('','');loadPostsUI();};}
|
|
1957
|
+
function pfail(btn,label){return function(e){PBUSY=Math.max(0,PBUSY-1);if(btn){btn.disabled=false;if(label)btn.textContent=label;}postsMsg('err',esc(String((e&&e.message)||e)));};}
|
|
1958
|
+
el.querySelectorAll('.pmadd').forEach(function(btn){var inp=btn.parentNode.querySelector('.pmfile');btn.addEventListener('click',function(){if(inp)inp.click();});
|
|
1959
|
+
if(inp)inp.addEventListener('change',function(){var x=post(btn),f=inp.files&&inp.files[0];if(!x||!f)return;btn.disabled=true;btn.textContent='Adding…';PBUSY++;postsMsg('','');
|
|
1960
|
+
fetch('/api/posts/media/add?id='+encodeURIComponent(x.id)+'&name='+encodeURIComponent(f.name),{method:'POST',headers:{'x-symbiot-token':T,'content-type':'application/octet-stream'},body:f}).then(function(r){return r.json();})
|
|
1961
|
+
.then(pmediaDone(btn,'Add picture or video'),pfail(btn,'Add picture or video'));});});
|
|
1962
|
+
el.querySelectorAll('.pmfrom').forEach(function(btn){btn.addEventListener('click',function(){var x=post(btn);if(!x)return;PFORM[x.id]={url:p.lastUrl||'',secs:8};renderPosts();var i=el.querySelector(".pcard[data-id='"+x.id+"'] .purl");if(i){i.focus();i.select();}});});
|
|
1963
|
+
el.querySelectorAll('.pform').forEach(function(row){var x=post(row),i=row.querySelector('.purl'),s=row.querySelector('.psecs');if(!x)return;
|
|
1964
|
+
i.addEventListener('input',function(){PFORM[x.id].url=i.value;});if(s)s.addEventListener('change',function(){PFORM[x.id].secs=Number(s.value)||8;});
|
|
1965
|
+
i.addEventListener('keydown',function(e){if(e.key==='Enter'){e.preventDefault();row.querySelector('.ppic').click();}if(e.key==='Escape'){delete PFORM[x.id];renderPosts();}});
|
|
1966
|
+
row.querySelector('.pfclose').addEventListener('click',function(){delete PFORM[x.id];renderPosts();});
|
|
1967
|
+
function shoot(btn,clip){if(!i.value.trim()){i.focus();return;}var label=btn.textContent,secs=s?Number(s.value)||8:8;btn.disabled=true;btn.textContent=clip?'Recording '+secs+' s…':'Taking it…';PBUSY++;postsMsg('','');
|
|
1968
|
+
api('/api/posts/media/page',{id:x.id,url:i.value.trim(),clip:clip,seconds:secs}).then(pmediaDone(btn,label),pfail(btn,label));}
|
|
1969
|
+
row.querySelector('.ppic').addEventListener('click',function(){shoot(this,false);});row.querySelector('.pclip').addEventListener('click',function(){shoot(this,true);});});
|
|
1970
|
+
el.querySelectorAll('.pmdel').forEach(function(btn){btn.addEventListener('click',function(){var x=post(btn),m=btn.closest('.pthumb').getAttribute('data-m');if(!x)return;btn.disabled=true;PBUSY++;
|
|
1971
|
+
api('/api/posts/media/remove',{id:x.id,m:m}).then(pmediaDone(null,''),pfail(null,''));});});
|
|
1884
1972
|
el.querySelectorAll('.pedit-btn').forEach(function(btn){btn.addEventListener('click',function(){var x=post(btn);if(!x)return;PEDIT[x.id]=x.text;renderPosts();var t=el.querySelector(".pcard[data-id='"+x.id+"'] textarea");if(t)t.focus();});});
|
|
1885
1973
|
el.querySelectorAll('textarea.pedit').forEach(function(t){t.addEventListener('input',function(){var id=t.closest('.pcard').getAttribute('data-id');PEDIT[id]=t.value;var c=t.closest('.pcard').querySelector('.pchars');if(c)c.textContent=t.value.length+' / 3000';});});
|
|
1886
1974
|
el.querySelectorAll('.pcancel').forEach(function(btn){btn.addEventListener('click',function(){var x=post(btn);if(x)delete PEDIT[x.id];renderPosts();});});
|
|
@@ -1891,10 +1979,33 @@ el.querySelectorAll('.pskip').forEach(function(btn){btn.addEventListener('click'
|
|
|
1891
1979
|
api('/api/posts/skip',{id:x.id}).then(function(r){if(!r||r.error){btn.disabled=false;postsMsg('err',esc((r&&r.error)||'failed'));return;}loadPostsUI();}).catch(function(e){btn.disabled=false;postsMsg('err',esc(String((e&&e.message)||e)));});});});
|
|
1892
1980
|
var dr=el.querySelector('.pdraft');if(dr)dr.addEventListener('click',function(){dr.disabled=true;postsMsg('','');dr.textContent='Drafting…';PBUSY++;
|
|
1893
1981
|
api('/api/posts/draft',{}).then(function(r){PBUSY=Math.max(0,PBUSY-1);dr.disabled=false;dr.textContent="Draft this week's posts";if(!r||r.error){postsMsg('err',esc((r&&r.error)||'failed'));return;}
|
|
1894
|
-
POSTS.posts=r.posts;renderPosts();if(r.dropped&&r.dropped.length)
|
|
1982
|
+
POSTS.posts=r.posts;renderPosts();var said=[];if(r.retried)said.push("The first try kept none (each claimed what git doesn't show), so I tried again.");if(r.pictures)said.push((r.pictures===1?"One draft names a screen of an app you run here: its picture's on it already.":r.pictures+" drafts name a screen of an app you run here: their pictures are on them already.")+" Keep or remove it.");if(r.dropped&&r.dropped.length)said.push("Dropped "+r.dropped.length+": "+r.dropped.map(function(d){return (L[d.kind]||d.kind)+(d.cited?" (it claimed "+d.unsupported.join(', ')+", which git doesn't show)":" (it cited nothing from git)");}).join('; '));if(said.length)postsMsg('muted',esc(said.join(' ')));loadPostsUI();}).catch(function(e){PBUSY=Math.max(0,PBUSY-1);dr.disabled=false;dr.textContent="Draft this week's posts";postsMsg('err',esc(String((e&&e.message)||e)));});});
|
|
1895
1983
|
var vb=el.querySelector('.pvoice');if(vb)vb.addEventListener('click',function(){vb.disabled=true;postsMsg('','');vb.textContent='Reading LinkedIn…';
|
|
1896
1984
|
api('/api/posts/voice',{confirmed:true}).then(function(r){vb.disabled=false;vb.textContent='Fill from LinkedIn';if(!r||r.error){postsMsg('err',esc((r&&r.error)||'failed'));return;}
|
|
1897
1985
|
postsMsg('ok',"✓ Added "+r.added+" of your LinkedIn posts: "+r.total+" in <code>"+esc(r.file)+"</code>. Read them over, and delete any that don't sound like you.");loadPostsUI();}).catch(function(e){vb.disabled=false;vb.textContent='Fill from LinkedIn';postsMsg('err',esc(String((e&&e.message)||e)));});});}
|
|
1986
|
+
// ---- Marketing: what you post about your work (post.mjs), who answers, and the 4-week test ----
|
|
1987
|
+
// Off the Dashboard, which shows only what you watch. Its orb shows on Home once posts
|
|
1988
|
+
// can be drafted, or are (home.mjs marketing).
|
|
1989
|
+
var MKT=null;
|
|
1990
|
+
function mkMsg(cls,html){var o=$('mkmsg');if(o)o.innerHTML=html?"<div class='note "+cls+"'>"+html+"</div>":'';}
|
|
1991
|
+
function loadMarketing(){loadPostsUI();api('/api/marketing').then(function(d){if(d&&!d.error){MKT=d;renderMarketing();}});}
|
|
1992
|
+
var MKDAY=function(s){var d=new Date(s+'T00:00:00');return isNaN(d)?s:d.toLocaleDateString(undefined,{day:'numeric',month:'short'});};
|
|
1993
|
+
function renderMarketing(){var d=MKT,r=$('mkreplies'),t=$('mktest');if(!d||!r||!t)return;var rs=d.replies||[];
|
|
1994
|
+
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>"
|
|
1995
|
+
+rs.map(function(n){return "<div class='task' data-id='"+escQ(n.id)+"'><span class='t'>"+esc(n.text)+custTag(n)+" <span class='muted' style='font-size:11px'>"+agoTxt(n.ts)+"</span></span><button class='ghost mkdraft' title='your agent drafts a reply in your voice and leaves it in the comment box, never posted'>Draft a reply</button></div>";}).join('');
|
|
1996
|
+
r.querySelectorAll('.mkdraft').forEach(function(b){b.addEventListener('click',function(){var id=b.closest('.task').getAttribute('data-id');b.disabled=true;
|
|
1997
|
+
api('/api/watch/draft',{id:id}).then(function(x){b.disabled=false;mkMsg(x&&!x.error?'ok':'err',x&&!x.error?DRAFTING_SOCIAL:esc((x&&x.error)||'failed'));}).catch(function(e){b.disabled=false;mkMsg('err',esc(String((e&&e.message)||e)));});});});
|
|
1998
|
+
var T4=d.test;if(!T4){t.innerHTML='';return;}
|
|
1999
|
+
var pk=(T4.packages||[]).join(', '),now=T4.week,over=T4.rows.every(function(x){return x.over;});
|
|
2000
|
+
var cell=function(x,v,extra){return x.started?"<td class='n'>"+v+(extra?" <span class='muted'>("+extra+")</span>":"")+"</td>":"<td class='n muted'>–</td>";};
|
|
2001
|
+
var h="<div class='pshead'><span>The 4-week test</span><small>"+(over?"Done: "+MKDAY(T4.start)+" to "+MKDAY(T4.end)+".":now?"Week "+now+" of 4 ("+MKDAY(T4.start)+" to "+MKDAY(T4.end)+").":"Starts "+MKDAY(T4.start)+", runs to "+MKDAY(T4.end)+".")+" Does posting bring anyone? Real replies, installs or pricing questions say it does.</small></div>"
|
|
2002
|
+
+"<div class='mkwrap'><table class='mktab'><thead><tr><th>Week</th><th>Posts published</th><th>Replies</th><th title='LinkedIn's own count: Who viewed your profile'>Profile visits</th><th"+(pk?" title='npm downloads of "+escQ(pk)+"'":"")+">Installs</th><th>Pricing questions</th></tr></thead><tbody>"
|
|
2003
|
+
+T4.rows.map(function(x){return "<tr"+(x.started?"":" class='later'")+(x.started&&!x.over?" aria-current='true'":"")+"><td><b>"+x.week+"</b> <span class='muted'>"+MKDAY(x.from)+"–"+MKDAY(x.to)+"</span></td>"
|
|
2004
|
+
+cell(x,x.published,x.media?x.media+" with a picture":"")+cell(x,x.replies,x.customers?x.customers+" maybe a customer":"")
|
|
2005
|
+
+(x.started?"<td class='n muted' title='LinkedIn's own count: its analytics on each post, and Who viewed your profile'>on LinkedIn</td>":"<td class='n muted'>–</td>")
|
|
2006
|
+
+cell(x,x.installs==null?"–":Number(x.installs).toLocaleString())+cell(x,x.pricing)+"</tr>";}).join('')+"</tbody></table></div>"
|
|
2007
|
+
+"<div class='muted' style='font-size:12px;margin-top:6px'>"+(pk?"Installs: npm downloads of "+esc(pk)+". Releases, mirrors and CI download too, so trust replies and pricing questions more. ":"")+"Replies and pricing questions come from LinkedIn's notifications, which Symbiot watches once LinkedIn is linked.</div>";
|
|
2008
|
+
t.innerHTML=h;}
|
|
1898
2009
|
// ---- Dashboard: a card per page you watch (your inbox, GitHub, WhatsApp…), what's new on each ----
|
|
1899
2010
|
var BOARD=null;
|
|
1900
2011
|
// a chat's line says who its last message is from (watch.mjs fromOf): only unread ones count as waiting on you
|
|
@@ -1904,7 +2015,7 @@ var BTALK={},BTALKDRAFT={},BTALKBUSY=0;
|
|
|
1904
2015
|
var TALKHINT="<div class='muted' style='font-size:12px'>Go over what's new here with your AI: what needs you, and what to say to whom. Then click <b>Draft a reply</b> on one, and your agent writes it the way you agreed here. Your AI is sent what this card lists (for mail: the sender, subject and preview).</div>";
|
|
1905
2016
|
function talking(){var f=document.activeElement;return BTALKBUSY>0||!!(f&&f.closest&&f.closest('.btalk'));}
|
|
1906
2017
|
var BOARDH=24;
|
|
1907
|
-
function loadBoard(){api('/api/watch/board?hours='+BOARDH).then(function(b){if(b&&b.cards){BOARD=b;if(!talking())renderBoard();}});loadAwaiting();
|
|
2018
|
+
function loadBoard(){api('/api/watch/board?hours='+BOARDH).then(function(b){if(b&&b.cards){BOARD=b;if(!talking())renderBoard();}});loadAwaiting();}
|
|
1908
2019
|
function boardMsg(cls,html){$('boardmsg').innerHTML="<div class='note "+cls+"'>"+html+"</div>";}
|
|
1909
2020
|
|
|
1910
2021
|
// Replies an agent's email waits on (handback.mjs): Symbiot watches your inbox for them, and hands each on when it's in
|
|
@@ -1916,8 +2027,11 @@ var AWAITS=[];
|
|
|
1916
2027
|
function bsWaitWord(){var n=(AWAITS||[]).filter(function(w){return w.status==='waiting';}).length;return n?' · waiting on '+n+(n===1?' reply':' replies'):'';}
|
|
1917
2028
|
function bsMailLane(){var cs=(BS.b&&BS.b.cards)||[];for(var i=0;i<cs.length;i++)if(cs[i].source==='mail')return i;return -1;}
|
|
1918
2029
|
function loadAwaiting(){api('/api/awaiting').then(function(d){var el=$('boardawait');if(!el||!d)return;var ws=d.waits||[];AWAITS=ws;if(BS.b)bsBuild(BS.b);
|
|
1919
|
-
if(!ws.length
|
|
1920
|
-
|
|
2030
|
+
if(!ws.length){el.innerHTML='';return;}var open=ws.filter(function(w){return w.status==='waiting';}).length,s=d.sight;
|
|
2031
|
+
// nothing can see the reply arrive: no inbox watched, or the one watched is signed out
|
|
2032
|
+
var blind=s&&!s.sees?"<div class='note err'>"+((s.signedOut||[]).length?esc(s.signedOut[0])+" is signed out, so a reply would go unseen. Under Screens, type its address and click Sign in, sign in once and close the window.":"Symbiot can't see your inbox, so a reply would go unseen. Watch your inbox (map it under Screens, then Watch), or turn on Email in Settings.")+"</div>":"";
|
|
2033
|
+
if(bsMailLane()>=0){el.innerHTML=blind;return;}
|
|
2034
|
+
el.innerHTML="<div class='tgroup' style='margin-top:14px'>Waiting on replies <span class='tcount'>"+open+"</span></div><div class='muted' style='font-size:12px;margin-bottom:6px'>Emails your agents sent that need an answer. Symbiot looks for each reply in your inbox itself, and when one is in, the agent that does the next step starts on it. You don't have to say they replied.</div>"+blind+
|
|
1921
2035
|
ws.map(function(w){var who=(w.to||[]).join(', ')||'?',st=w.status==='waiting'?'waiting since '+agoTxt(w.sent):(AWAITWORD[w.status]||w.status)+(w.reply?' '+agoTxt(w.reply.ts):'')+(w.handed?' → '+(w.handed==='ops'?'an agent':esc(w.handed)+''s agent')+' has the next step':'');
|
|
1922
2036
|
return "<div class='task'><span class='t'><b>"+esc(who)+"</b>: "+esc(w.subject)+(w.asked?"<div class='muted' style='font-size:12px'>asked for: "+esc(w.asked)+"</div>":"")+(w.error?"<div class='note err'>"+esc(w.error)+"</div>":"")+"</span><span class='rp'"+(w.status==='waiting'?" style='color:var(--amber)'":"")+">"+st+"</span>"+(w.status==='waiting'?"<button class='ghost astop' data-id='"+esc(w.id)+"' title='stop looking for this reply' style='padding:3px 9px;font-size:12px'>Stop waiting</button>":"")+"</div>";}).join('');
|
|
1923
2037
|
el.querySelectorAll('.astop').forEach(function(btn){btn.addEventListener('click',function(){btn.disabled=true;api('/api/awaiting/stop',{id:btn.getAttribute('data-id')}).then(function(r){if(!r||r.error){btn.disabled=false;boardMsg('err',esc((r&&r.error)||'failed'));return;}loadAwaiting();});});});});}
|
|
@@ -2124,7 +2238,6 @@ document.getElementById('addtrusted').addEventListener('click',addTrustedUI);
|
|
|
2124
2238
|
document.getElementById('newtrusted').addEventListener('keydown',function(e){if(e.key==='Enter')addTrustedUI();});
|
|
2125
2239
|
document.getElementById('driftrun').addEventListener('click',function(){driftLoaded=false;loadDrift();});
|
|
2126
2240
|
document.getElementById('agentsrefresh').addEventListener('click',loadAgents);
|
|
2127
|
-
document.getElementById('addtask').addEventListener('click',addTaskUI);
|
|
2128
2241
|
document.getElementById('pushtasks').addEventListener('click',pushTasksUI);
|
|
2129
2242
|
document.getElementById('agentcmd').addEventListener('change',saveAgent);
|
|
2130
2243
|
document.getElementById('granttoolbtn').addEventListener('click',function(){grant('tool');});
|
|
@@ -2136,7 +2249,6 @@ document.getElementById('mailaddrs').addEventListener('change',saveMailAddrs);
|
|
|
2136
2249
|
document.getElementById('addmail').addEventListener('click',addMailUI);
|
|
2137
2250
|
document.getElementById('newmail').addEventListener('keydown',function(e){if(e.key==='Enter')addMailUI();});
|
|
2138
2251
|
document.getElementById('mailpreview').addEventListener('click',previewMail);
|
|
2139
|
-
document.getElementById('newtask').addEventListener('keydown',function(e){if(e.key==='Enter')addTaskUI();});
|
|
2140
2252
|
document.getElementById('capture').addEventListener('click',captureUI);
|
|
2141
2253
|
document.getElementById('screenload').addEventListener('click',pickImageUI);
|
|
2142
2254
|
document.getElementById('mappage').addEventListener('click',function(){mapUI($('pagesite').value,$('mappage'));});
|
|
@@ -2229,12 +2341,12 @@ if(!ps.length){el.innerHTML='';return;}
|
|
|
2229
2341
|
el.innerHTML=ps.map(function(p,i){var nm=p.name||p.repo,q=(p.qs||[])[0],st=p.parked?'parked':(p.qs&&p.qs.length)?'asks you':p.ready?'ready for your OK':p.state||'';
|
|
2230
2342
|
var h="<section class='rly"+((p.qs&&p.qs.length)||p.ready?' lit':'')+"' data-i='"+i+"'><div class='rlh'><b>"+esc(nm)+"</b>"+(st?"<span class='rls'>"+esc(st)+"</span>":"")+"</div>";
|
|
2231
2343
|
if(p.summary)h+="<p class='rlsum' title='"+escQ(p.summary)+"'>"+esc(p.summary)+"</p>";
|
|
2232
|
-
if(q)h+="<div class='lqblob rlask'>"+blobBody({kind:'ask',name:nm,repo:p.repo,q:q.q,options:q.options,path:q.path,more:Math.max(0,(p.asks||p.qs.length)-1)},false)+"</div>";
|
|
2344
|
+
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>";
|
|
2233
2345
|
if(p.ready)h+="<button type='button' class='act rlok'>Review and approve</button>";
|
|
2234
2346
|
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('');
|
|
2235
2347
|
return h+"<div class='rlf'><button type='button' class='rlopen'>Its tasks ›</button><button type='button' class='rlag'>Details in Agents ›</button></div></section>";}).join('');
|
|
2236
2348
|
el.querySelectorAll('.rly').forEach(function(box){var p=ps[+box.getAttribute('data-i')],q=(p.qs||[])[0],nm=p.name||p.repo;
|
|
2237
|
-
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},{done:function(){LQ.relaySig='';lqLoadWork();}});
|
|
2349
|
+
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();}});
|
|
2238
2350
|
var ok=box.querySelector('.rlok');if(ok)ok.addEventListener('click',function(){lqOpen({kind:'proj',repo:p.repo,lit:true});});
|
|
2239
2351
|
box.querySelector('.rlopen').addEventListener('click',function(){lqOpen({kind:'proj',repo:p.repo});});
|
|
2240
2352
|
box.querySelector('.rlag').addEventListener('click',function(){lqPool('agents');});
|
|
@@ -2284,7 +2396,7 @@ list.forEach(function(d){d.ax=d.tx;d.ay=d.ty;});lqRelax(list,S,cx,cy,92*S.s);
|
|
|
2284
2396
|
var old={};LQ.drops.forEach(function(d){old[d.id]=d;});
|
|
2285
2397
|
LQ.drops=list.map(function(d){var o=old[d.id];d.x=o?o.x:cx;d.y=o?o.y:cy;d.vx=o?o.vx:0;d.vy=o?o.vy:0;d.cr=o?o.cr:0;return d;});
|
|
2286
2398
|
var n=w.projectCount||list.length,nr=(w.running||[]).length,nw=w.waitingCount||0;
|
|
2287
|
-
LQ.coreText=n?n+' project'+(n>1?'s':'')+(nr?' \u00b7 '+nr+' at work':'')+(nw?' \u00b7 '+nw+' tasks waiting':''):'No projects with work on them yet.
|
|
2399
|
+
LQ.coreText=n?n+' project'+(n>1?'s':'')+(nr?' \u00b7 '+nr+' at work':'')+(nw?' \u00b7 '+nw+' tasks waiting':''):'No projects with work on them yet. Tell me what to do.';
|
|
2288
2400
|
var go=$('lqgo');if(go){var k=w.canGo||0;go.textContent=k?'Go \u00b7 start '+k+' task'+(k>1?'s':''):'';if(go.classList)go.classList.toggle('on',k>0);}
|
|
2289
2401
|
lqLabels();}
|
|
2290
2402
|
function lqGoWork(){var go=$('lqgo');if(go)go.disabled=true;api('/api/work/go',{}).then(function(r){if(go)go.disabled=false;
|
|
@@ -2300,7 +2412,7 @@ LQ.talk.push({me:false,text:t});LQ.talk=LQ.talk.slice(-8);lqTalkShow(false);LQ.r
|
|
|
2300
2412
|
// comes out of the core (mass is conserved). Your colours come from the system,
|
|
2301
2413
|
// live: light or dark, contrast, transparency, forced colours, your accent, night.
|
|
2302
2414
|
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};
|
|
2303
|
-
var LQNAMES={board:'Dashboard',map:'Map',tasks:'Tasks',agents:'Agents',week:'Week',standup:'Standup',todo:'Todo',drift:'Drift',settings:'Settings',reports:'Reports'};
|
|
2415
|
+
var LQNAMES={board:'Dashboard',map:'Map',tasks:'Tasks',agents:'Agents',week:'Week',standup:'Standup',todo:'Todo',drift:'Drift',settings:'Settings',reports:'Reports',marketing:'Marketing'};
|
|
2304
2416
|
var LQ_REST=60000,LQ_MAX=11;
|
|
2305
2417
|
function lqMM(q){try{return !!(window.matchMedia&&window.matchMedia(q).matches);}catch(e){return false;}}
|
|
2306
2418
|
function lqLookGet(){var l='';try{l=window.localStorage&&window.localStorage.getItem('symbiot-look')||'';}catch(e){}return l==='glass'||l==='pearl'?l:'ferro';}
|
|
@@ -2322,7 +2434,7 @@ function lqLoad(commit){if(LQ.scene==='work'){lqLoadWork();return;}var fresh=LQ.
|
|
|
2322
2434
|
// (work with an end) up left, Areas (what you keep up with) up right, Resources (to
|
|
2323
2435
|
// look things up in) down left, the Archive (what's done) sunk low; and what's new on
|
|
2324
2436
|
// what you watch (Watching) as a zone of its own on the right.
|
|
2325
|
-
var PARA_OF={tasks:'p',agents:'p',todo:'p',board:'a',week:'a',standup:'a',map:'r',drift:'r',settings:'r',reports:'r'};
|
|
2437
|
+
var PARA_OF={tasks:'p',agents:'p',todo:'p',board:'a',week:'a',standup:'a',marketing:'a',map:'r',drift:'r',settings:'r',reports:'r'};
|
|
2326
2438
|
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'};
|
|
2327
2439
|
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};
|
|
2328
2440
|
function lqPara(d){return d.kind==='you'?'n':d.kind==='archive'?'x':d.kind==='feed'&&d.shape!=='reports'?'w':PARA_OF[d.shape]||'p';}
|
|
@@ -2334,7 +2446,7 @@ var LQ_BLOBW=250,LQ_NEEDY=128;
|
|
|
2334
2446
|
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);});
|
|
2335
2447
|
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));
|
|
2336
2448
|
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);
|
|
2337
|
-
var tall=shown.some(function(y){return y.kind==='ask'&&(y.options||[]).length;})?172:116;
|
|
2449
|
+
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);
|
|
2338
2450
|
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};});
|
|
2339
2451
|
return {items:items,bottom:items.length?LQ_NEEDY+tall+20:0,x:x0,page:LQ.needPage,pages:pages,count:ys.length};}
|
|
2340
2452
|
function lqNeedPage(){LQ.needPage=(LQ.needPage||0)+1;lqAct();lqBuild();}
|
|
@@ -2367,7 +2479,9 @@ else if((d.kind==='you'||d.kind==='feed')&&at[d.shape]!=null)links.push([at[d.sh
|
|
|
2367
2479
|
function lqBuild(){var a=LQ.adapt||{},h=LQ.home||{},S=lqSize(),list=[];LQ.bw=S.w;LQ.bh=S.h;
|
|
2368
2480
|
// the band first: everything else sits under it, round a core centred in what's left
|
|
2369
2481
|
var need=lqNeeds(h,S),top=need.bottom,cx=S.w/2,cy=need.items.length?Math.max(S.h*0.47,(top+60+S.h-200)/2):S.h*0.47;LQ.cy=cy;LQ.top=top;LQ.need=need;
|
|
2370
|
-
|
|
2482
|
+
// Marketing only once posts can be drafted, or are (home.mjs): a new user meets what they watch first
|
|
2483
|
+
var mk=function(id){return id!=='marketing'||!!h.marketing;};
|
|
2484
|
+
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);
|
|
2371
2485
|
var vx=Math.max(0.5,Math.min(S.s*1.3,(S.w/2-90)/400)),vy=Math.max(0.35,Math.min(S.s*0.8,(S.h-cy-230)/400,(cy-(top?top+60:160))/400));var rs=Math.min(S.s,(vx+vy)/1.5);var room=Math.max(3,LQ_MAX-feeds.length-1-(need.items.length?2:0));lay.slice(room).forEach(function(it){more.push(it.id);});
|
|
2372
2486
|
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
|
|
2373
2487
|
lay.slice(0,room).forEach(function(it){list.push({id:'shape:'+it.id,kind:'shape',shape:it.id,title:LQNAMES[it.id]||it.id,sub:it.id==='reports'&&repOut?repNew.count+' new':'',r:it.r*rs,tx:cx+Math.cos(it.angle)*it.d*vx,ty:cy+Math.sin(it.angle)*it.d*vy});});
|
|
@@ -2395,12 +2509,14 @@ LQ.talkWeight=(a.modes&&a.modes.talkWeight)||0.35;
|
|
|
2395
2509
|
lqLabels();}
|
|
2396
2510
|
// Home in zones, on a screen wide enough for them: the core in the middle of what's
|
|
2397
2511
|
// under the band; Projects then Resources to its left, Areas then Watching (with the
|
|
2398
|
-
// Archive and "more" at its end) to its right; each a row with its name at its
|
|
2399
|
-
//
|
|
2400
|
-
//
|
|
2512
|
+
// Archive and "more" at its end) to its right; each a row with its name at its outer
|
|
2513
|
+
// end, so the droplets, which flow out of the core, never cross a name on their way
|
|
2514
|
+
// (on the right, names next to the core had the Dashboard orb sitting on "what you
|
|
2515
|
+
// keep up with" until it landed). Fixed places in a fixed order, so nothing overlaps
|
|
2516
|
+
// and each part is where it was last time. Sizes still come from how you use them (adapt.mjs), all scaled
|
|
2401
2517
|
// by one factor when the screen is short, so they stay comparable. null when they
|
|
2402
2518
|
// can't fit (a narrow or very short screen): the liquid's own layout then.
|
|
2403
|
-
var ZONE_ORDER=['tasks','agents','todo','board','week','standup','map','drift','settings','reports'],ZONE_KIND={shape:0,feed:1,archive:2,more:3};
|
|
2519
|
+
var ZONE_ORDER=['tasks','agents','todo','board','week','standup','marketing','map','drift','settings','reports'],ZONE_KIND={shape:0,feed:1,archive:2,more:3};
|
|
2404
2520
|
function lqZones(list,S,top,coreR){LQ.zoneT=null;if(S.w<1000||S.h<540)return null;
|
|
2405
2521
|
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;
|
|
2406
2522
|
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;
|
|
@@ -2414,8 +2530,8 @@ var R=0;m.forEach(function(d){R=Math.max(R,d.r);});var av=colW-titleW,fitW=funct
|
|
|
2414
2530
|
if(fitW(per)<0.6&&m.length>1)per=Math.ceil(m.length/2);var rows=Math.ceil(m.length/per);
|
|
2415
2531
|
k=Math.min(k,fitW(per),((c[2]-c[1])/rows-46)/(2*R));plan[g]={m:m,per:per,rows:rows,stepX:av/per};});
|
|
2416
2532
|
if(k<0.55)return null;k=Math.min(1,k);LQ.zoneT={};
|
|
2417
|
-
Object.keys(plan).forEach(function(g){var c=cells[g],p=plan[g],rowH=(c[2]-c[1])/p.rows,x0=c[0]+titleW+(colW-titleW)/2;
|
|
2418
|
-
LQ.zoneT[g]=[c[0]+titleW/2,c[1]+rowH/2-13];
|
|
2533
|
+
Object.keys(plan).forEach(function(g){var c=cells[g],p=plan[g],rowH=(c[2]-c[1])/p.rows,right=g==='a'||g==='w',x0=c[0]+(right?0:titleW)+(colW-titleW)/2;
|
|
2534
|
+
LQ.zoneT[g]=[right?c[0]+colW-titleW/2:c[0]+titleW/2,c[1]+rowH/2-13];
|
|
2419
2535
|
p.m.forEach(function(d,i){d.r*=k;var row=Math.floor(i/p.per),n=Math.min(p.per,p.m.length-row*p.per);d.tx=x0+(i%p.per-(n-1)/2)*p.stepX;d.ty=c[1]+row*rowH+rowH/2-13;});});
|
|
2420
2536
|
return {cy:mid};}
|
|
2421
2537
|
// Force-directed layout, solved once per change: push overlapping targets apart
|
|
@@ -2475,6 +2591,8 @@ if(d.kind==='more'){LQ.moreOpen=!LQ.moreOpen;lqMore(d);return;}
|
|
|
2475
2591
|
if(d.kind==='archive'){lqArchive();return;}
|
|
2476
2592
|
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;}
|
|
2477
2593
|
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;}
|
|
2594
|
+
// where your files disagree: the checks, lit up in Settings, and these clashes reached you (it dissolves)
|
|
2595
|
+
if(d.id==='feed:clash'){api('/api/knowledge/checks/seen',{}).catch(function(){});if(LQ.home)LQ.home.feeds=(LQ.home.feeds||[]).filter(function(f){return f.id!=='feed:clash';});lqUse('settings',ev);loadChecks();lqFocus({kind:'setup',focus:'checks'});return;}
|
|
2478
2596
|
var S=lqSize();LQ.ripple=[d.x/S.w,d.y/S.h,LQ.t];if(LQ.scene==='work'){lqPool(d.shape||'agents');return;}lqUse(d.shape||'board',ev);lqGo(d.shape||'board');}
|
|
2479
2597
|
function lqMore(d){var el=$('lqmore');if(!el)return;if(!LQ.moreOpen||!d){el.innerHTML='';return;}
|
|
2480
2598
|
if(d.lanes){el.innerHTML=d.lanes.map(function(p,i){return "<button type='button' class='lqmb lqm' data-i='"+i+"'>"+(p.lit?"<span style='color:#F2A541'>●</span> ":"")+esc(p.name)+"</button>";}).join('');
|
|
@@ -2489,8 +2607,9 @@ function lqGo(shape){if(shape==='tasks'||shape==='agents'){lqWorkBy('task');retu
|
|
|
2489
2607
|
function blobOpt(o){return String(o||'').replace(/[ ]*[(]recommended[)][ ]*$/i,'');}
|
|
2490
2608
|
function blobBody(y,home){var h='',nm=y.name||y.repo||(y.kind==='setup'?y.title:'')||'';
|
|
2491
2609
|
h+=home?"<button type='button' class='lt' title='open "+escQ(nm)+", with this lit up'><span>"+esc(lqShort(nm,30))+"</span><i>open ›</i></button>":"<div class='nhead'><b>"+esc(nm)+(y.kind==='ask'?" asks":"")+"</b>"+(y.more?"<span class='muted'>+"+y.more+" more on its block in Agents</span>":"")+"</div>";
|
|
2492
|
-
if(y.kind!=='ask')return h+"<div class='bq'>"+esc(y.kind==='approve'?String(y.sub||'').replace(/ · only you decide$/,''):String(y.sub||'').charAt(0).toUpperCase()+String(y.sub||'').slice(1)+'.')+"</div><div class='bo'><button type='button' class='nopt rec ngo'><span class='bt'>"+(y.kind==='approve'?'Review and approve':y.id==='setup:inbox'?'Show me your inbox':y.id==='setup:ai'?'Connect an AI':y.urgent?'Reconnect':'Open Settings')+"</span></button></div>";
|
|
2493
|
-
var os=y.options||[];h+="<div class='bq' title='"+escQ(y.q||'')+"'>"+esc(y.q||y.sub||'')+"</div>";
|
|
2610
|
+
if(y.kind!=='ask')return h+"<div class='bq'>"+esc(y.kind==='approve'?String(y.sub||'').replace(/ · only you decide$/,''):String(y.sub||'').charAt(0).toUpperCase()+String(y.sub||'').slice(1)+'.')+"</div><div class='bo'><button type='button' class='nopt rec ngo'><span class='bt'>"+(y.kind==='approve'?'Review and approve':y.id==='setup:inbox'?(y.signin?'Sign in again':'Show me your inbox'):y.id==='setup:ai'?'Connect an AI':y.pick?'Use '+y.pick.name:y.urgent?'Reconnect':'Open Settings')+"</span></button></div>";
|
|
2611
|
+
var os=y.options||[];h+="<div class='bq' title='"+escQ((y.q||'')+(y.why?' ('+y.why+')':''))+"'>"+esc(y.q||y.sub||'')+"</div>";
|
|
2612
|
+
if(y.fix==='handover'&&y.sub)h+="<div class='bfor' title='"+escQ(y.sub)+"'>for: "+esc(y.sub)+"</div>";
|
|
2494
2613
|
if(os.length)h+="<div class='bo'>"+os.map(function(o,j){return "<button type='button' class='nopt"+(j===0&&/[(]recommended[)]/i.test(o)?' rec':'')+"' data-j='"+j+"' title='"+escQ(o)+"'><span class='bt'>"+whoHtml(blobOpt(o))+"</span></button>";}).join('')+"</div><button type='button' class='bfree' aria-expanded='false'>or answer in your own words</button>";
|
|
2495
2614
|
h+="<form class='bfx"+(os.length?' hidden':'')+"'><input placeholder='your answer' aria-label='your answer to "+escQ(nm)+"'><button type='submit'>Send</button></form>";
|
|
2496
2615
|
if(home&&y.more)h+="<span class='bmore'>+"+y.more+" more question"+(y.more>1?'s':'')+" after this one</span>";
|
|
@@ -2499,22 +2618,26 @@ return h;}
|
|
|
2499
2618
|
// continue): saved to its ANSWERS.md, and its agent picks it up. Then it dissolves.
|
|
2500
2619
|
function wireBlob(box,y,o){o=o||{};var say=o.say||lqSaid,nm=y.name||y.repo||'the agent';
|
|
2501
2620
|
var lock=function(on){box.querySelectorAll('button,input').forEach(function(b){b.disabled=on;});};
|
|
2502
|
-
var send=function(ans){ans=String(ans||'').trim();if(!ans
|
|
2503
|
-
api('/api/agents/answer',{path:y.path,answers:[{q:y.q,a:ans}],rerun:true}).then(function(r){
|
|
2621
|
+
var send=function(ans,j){ans=String(ans||'').trim();if(!ans||(!y.path&&!y.fix))return;lock(true);
|
|
2622
|
+
(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){
|
|
2504
2623
|
if(!r||r.error){lock(false);say(nm+': '+((r&&r.error)||'the answer didn’t save. Try again on its block in Agents.'),true);return;}
|
|
2505
|
-
say('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.')));
|
|
2624
|
+
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.')));
|
|
2506
2625
|
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);});};
|
|
2507
|
-
box.querySelectorAll('.nopt[data-j]').forEach(function(b){b.addEventListener('click',function(){send((y.options||[])[
|
|
2626
|
+
box.querySelectorAll('.nopt[data-j]').forEach(function(b){b.addEventListener('click',function(){var j=+b.getAttribute('data-j');send((y.options||[])[j],j);});});
|
|
2508
2627
|
var fr=box.querySelector('.bfree'),fx=box.querySelector('.bfx');
|
|
2509
2628
|
if(fr&&fx)fr.addEventListener('click',function(){var shut=fx.classList.toggle('hidden');fr.setAttribute('aria-expanded',shut?'false':'true');var i=fx.querySelector('input');if(!shut&&i&&i.focus)i.focus();});
|
|
2510
2629
|
if(fx)fx.addEventListener('submit',function(ev){if(ev&&ev.preventDefault)ev.preventDefault();var i=fx.querySelector('input');send(i&&i.value);});
|
|
2511
|
-
var go=box.querySelector('.ngo');if(go)go.addEventListener('click',function(){lqFocus(y);
|
|
2630
|
+
var go=box.querySelector('.ngo');if(go)go.addEventListener('click',function(){if(!y.pick){lqFocus(y);return;}
|
|
2631
|
+
// Pick your agent: the one Symbiot found, in one click; Settings has the others
|
|
2632
|
+
lock(true);api('/api/agentcmd',{cmd:y.pick.tmpl}).then(function(r){if(!r||r.error){lock(false);say((r&&r.error)||'It didn’t save. Pick it in Settings.',true);return;}
|
|
2633
|
+
say('Your agent is '+y.pick.name+' now: Send to repos and Go hand your tasks to it.');if(box.classList)box.classList.add('gone');LQ.fresh=true;setTimeout(function(){if(o.done)o.done();else lqLoad(false);},650);}).catch(function(e){lock(false);say(String((e&&e.message)||e),true);});});}
|
|
2512
2634
|
function lqSaid(t){LQ.talk.push({me:false,text:t});LQ.talk=LQ.talk.slice(-8);lqTalkShow(false);}
|
|
2513
2635
|
// A lit orb opens its project with what lit it lit up and in view (lqLight): a
|
|
2514
2636
|
// repo's questions and its Approve, under "Needs you here" at the top of its
|
|
2515
2637
|
// tasks, with its held handovers; an ops run's question on its block in Agents;
|
|
2516
2638
|
// a first step on its part of Settings.
|
|
2517
2639
|
function lqFocus(y){lqAct();y=y||{};LQ.focus={kind:y.kind||'',repo:y.repo||'',path:y.path||'',focus:y.focus||'',shown:false};
|
|
2640
|
+
document.querySelectorAll('main .lit').forEach(function(x){x.classList.remove('lit');}); // only what this orb lit stays lit
|
|
2518
2641
|
if(y.kind==='setup'){lqPool('settings');setTimeout(lqLight,300);return;}
|
|
2519
2642
|
if(!y.repo||y.repo==='ops'){lqPool('agents');return;}
|
|
2520
2643
|
TFILTER.repo=y.repo;TFILTER.type='';lqPool('tasks');
|
|
@@ -2534,9 +2657,9 @@ lqLight();};
|
|
|
2534
2657
|
api('/api/lanes').then(function(d){paint(((d&&d.handoffs)||[]).filter(function(x){return x.status==='held'&&(x.to===repo||x.from===repo);}));}).catch(function(){paint([]);});}
|
|
2535
2658
|
// What lit the orb, lit up where it opened (.lit): the first in view, and with only
|
|
2536
2659
|
// one, its first answer focused. Again after each re-render, but scrolled to once.
|
|
2537
|
-
var SETFOCUS={ai:'provider',work:'scanroots',agent:'agentcmd',links:'links',company:'knowroots'};
|
|
2660
|
+
var SETFOCUS={ai:'provider',work:'scanroots',agent:'agentcmd',links:'links',company:'knowroots',checks:'knowchecks'};
|
|
2538
2661
|
function lqLight(){var f=LQ.focus;if(!f||LQ.mode!=='pool')return;var things=[],also=[];
|
|
2539
|
-
if(f.kind==='setup'){var a=$(SETFOCUS[f.focus]||''),s=a&&a.closest?a.closest('.sset'):null;if(s)things.push(s);}
|
|
2662
|
+
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);}
|
|
2540
2663
|
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);});}
|
|
2541
2664
|
else if(current==='agents'){document.querySelectorAll('#agentslist .agent[data-i]').forEach(function(e){var g=AGENTLIST[+e.getAttribute('data-i')];if(g&&(nQs(g)||g.waiting)&&(f.path?g.path===f.path:/act-[0-9a-f]+$/.test(String(g.path))))things.push(e);});}
|
|
2542
2665
|
things.concat(also).forEach(function(e){if(e.classList)e.classList.add('lit');});
|
|
@@ -2588,10 +2711,13 @@ if(still){d.x=tx;d.y=ty;d.vx=0;d.vy=0;}else{d.vx+=ax;d.vy+=ay;d.x+=d.vx;d.y+=d.v
|
|
|
2588
2711
|
if(!rest&&!pool){d.x=Math.max(d.cr+12,Math.min(S.w-d.cr-12,d.x));d.y=Math.max((d.blob?60:140)+d.cr,Math.min(S.h-150-d.cr-50,d.y));}
|
|
2589
2712
|
d.cr+=(tr-d.cr)*(still?1:0.06);
|
|
2590
2713
|
var b=LQ.btns[i];if(b&&b.offsetWidth)d.lw=b.offsetWidth;if(b&&b.style){b.style.transform=d.blob?'translate('+Math.round(d.x+d.r+30)+'px,'+Math.round(d.y-d.r-4)+'px)':'translate('+Math.round(d.x-(b.offsetWidth||0)/2)+'px,'+Math.round(d.y+d.cr+(d.kind==='run'?28:4))+'px)';var show=!rest&&!pool;b.style.opacity=show?'1':'0';b.style.pointerEvents=show?'auto':'none';var lt=LQ.lts&&LQ.lts[i];if(lt)lt.tabIndex=show?0:-1;}}
|
|
2591
|
-
(LQ.gbtns||[]).forEach(function(gb){var g=gb.getAttribute('data-g'),top=1e9,sx=0,nn=0;D.forEach(function(d){if(d.kind!=='more'&&lqPara(d)===g){top=Math.min(top,d.y-d.cr);sx+=d.x;nn++;}});if(!nn||!gb.style)return;var gw=gb.offsetWidth||0,gh=gb.offsetHeight||0,zt=LQ.scene!=='work'&&LQ.zoneT&&LQ.zoneT[g];if(zt)gb.style.transform='translate('+Math.round(zt[0]-gw/2)+'px,'+Math.round(zt[1]-gh/2)+'px)';else
|
|
2714
|
+
(LQ.gbtns||[]).forEach(function(gb){var g=gb.getAttribute('data-g'),top=1e9,sx=0,nn=0;D.forEach(function(d){if(d.kind!=='more'&&lqPara(d)===g){top=Math.min(top,d.y-d.cr);sx+=d.x;nn++;}});if(!nn||!gb.style)return;var gw=gb.offsetWidth||0,gh=gb.offsetHeight||0,zt=LQ.scene!=='work'&&LQ.zoneT&&LQ.zoneT[g];if(zt)gb.style.transform='translate('+Math.round(zt[0]-gw/2)+'px,'+Math.round(zt[1]-gh/2)+'px)';else{var gx=Math.max(12,Math.min(lqSize().w-gw-12,sx/nn-gw/2)),gy=top-gh-30;
|
|
2715
|
+
// off any droplet it would sit on (the "more" droplet, another group's): above it instead
|
|
2716
|
+
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;});
|
|
2717
|
+
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;});
|
|
2592
2718
|
var ntl=$('lqneedt');if(ntl&&ntl.style)ntl.style.opacity=rest?'0':'1';
|
|
2593
2719
|
(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';});
|
|
2594
|
-
var cb=$('lqcore');if(cb&&cb.style){var inC=LQ.theme&&LQ.theme.look!=='pearl'&&core.cr>60;cb.style.maxWidth=inC?Math.round(core.cr*1.5)+'px':'340px';var cw=cb.offsetWidth||0,chh=cb.offsetHeight||0;cb.style.transform='translate('+Math.round(cx-cw/2)+'px,'+(rest||inC?(inC?Math.round(core.y-chh/2):Math.round(core.y+core.cr+14)):56)+'px)';var ct=rest?(LQ.coreText||'All handled'):talk||pool?'':(LQ.coreText||'');lqCoreText(cb,ct);cb.style.opacity=ct?'1':'0';cb.style.pointerEvents=ct?'auto':'none';}}
|
|
2720
|
+
var cb=$('lqcore');if(cb&&cb.style){var inC=LQ.theme&&LQ.theme.look!=='pearl'&&core.cr>60;cb.style.maxWidth=inC?Math.round(core.cr*1.5)+'px':'340px';var cw=cb.offsetWidth||0,chh=cb.offsetHeight||0;cb.style.transform='translate('+Math.round(cx-cw/2)+'px,'+(rest||inC?(inC?Math.round(core.y-chh/2):Math.round(core.y+core.cr+14)):LQ.scene==='work'?112:56)+'px)';var ct=rest?(LQ.coreText||'All handled'):talk||pool?'':(LQ.coreText||'');lqCoreText(cb,ct);cb.style.opacity=ct?'1':'0';cb.style.pointerEvents=ct?'auto':'none';}}
|
|
2595
2721
|
// The liquid: a metaball surface (Σ r²/d² = 1) shaded as chrome, on the GPU.
|
|
2596
2722
|
var LQ_FS=['precision highp float;',
|
|
2597
2723
|
'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;',
|
|
@@ -2696,5 +2822,5 @@ var G=lqGL();if(G){try{lqStep();lqDraw(G);}catch(e){}}if(typeof window.requestAn
|
|
|
2696
2822
|
function frame(){if(!document.hidden){LQ.frame++;if(LQ.mode!=='pool'||LQ.frame%3===0)lqStep();var every=(GLSOFT||GLSTILL)?glEvery(LQ.mode==='aware'):LQ.mode==='pool'?6:LQ.mode==='rest'?2:1;if(G&&LQ.frame%every===0)lqDraw(G);}raf(frame);} // moves every frame; drawn less at rest, behind a panel, without a GPU
|
|
2697
2823
|
raf(frame);}
|
|
2698
2824
|
|
|
2699
|
-
initGraphEvents();syncP();refresh();loadMap();firstTab();lqInit();loadWhatsNew();loadAgentCfg();loadScanRoots();loadKnowledge();loadPhone();loadLinks();loadMind();loadTrusted();loadMail();loadScreensUI();loadMonitorsUI();loadDesktop();loadWatchUI();setInterval(function(){whenFree(document.querySelector('main'),'watch',loadWatchUI);},60000);loadPhoneLink();
|
|
2825
|
+
initGraphEvents();syncP();refresh();loadMap();firstTab();lqInit();lqDeep();loadWhatsNew();loadAgentCfg();loadScanRoots();loadKnowledge();loadPhone();loadLinks();loadMind();loadTrusted();loadMail();loadScreensUI();loadMonitorsUI();loadDesktop();loadWatchUI();setInterval(function(){whenFree(document.querySelector('main'),'watch',loadWatchUI);},60000);loadPhoneLink();
|
|
2700
2826
|
</script></body></html>`;
|