symbiot 0.63.6 → 0.63.8
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 +41 -0
- package/README.md +39 -5
- package/agents.mjs +52 -11
- package/browser.html +21 -3
- package/capture.mjs +761 -0
- package/demo.mjs +171 -0
- package/guard.mjs +23 -5
- package/handback.mjs +22 -8
- package/handover.mjs +6 -2
- package/headless.mjs +281 -80
- package/home.mjs +23 -41
- package/index.mjs +65 -11
- package/lanes.mjs +99 -9
- package/links.mjs +52 -2
- package/marketing.mjs +40 -7
- package/mdns.mjs +6 -4
- package/package.json +6 -3
- package/phone.mjs +79 -12
- package/plain.mjs +48 -0
- package/reports.mjs +15 -1
- package/server.mjs +89 -42
- package/tray.mjs +44 -4
- package/ui.mjs +317 -47
package/ui.mjs
CHANGED
|
@@ -125,6 +125,8 @@ body.lq-light #lqlook{background:rgba(21,26,33,.05);border-color:rgba(21,26,33,.
|
|
|
125
125
|
body.lq-light #lqlook button{color:#5A6470}
|
|
126
126
|
body.lq-light #lqlook button[aria-checked='true']{background:#151A21;color:#FFFFFF}
|
|
127
127
|
body.lq-touch #lqlook button{min-height:40px}
|
|
128
|
+
/* a phone's width: the version and the three looks on one line, none cut off. The name and the status line went past the screen's edge there (the status, wrapped, made the header 116px tall), and what the status says is on Home */
|
|
129
|
+
@media (max-width:520px){body.lq-liquid header .brand,body.lq-liquid header .status{display:none}body.lq-liquid header .ver{white-space:nowrap;padding:2px 6px}body.lq-liquid #lqlook{margin-left:6px}#lqlook button{padding:0 8px;font-size:12px}}
|
|
128
130
|
body.lq-liquid header .brand{letter-spacing:.38em;font-weight:500;text-transform:uppercase}
|
|
129
131
|
body.lq-liquid .tabs{display:none}
|
|
130
132
|
body.lq-liquid main{display:none}
|
|
@@ -269,6 +271,12 @@ body.lq-pooled #lqneedt,body.lq-work #lqneedt{display:none}
|
|
|
269
271
|
.dm-chat{background:#D9FDD3;color:#111B21;margin-left:16px;border-top-right-radius:4px}.dm-chat .dmto{color:#1F7A4C}
|
|
270
272
|
.dm-mail{background:#FFFFFF;color:#202124;border:1px solid #DADCE0}.dm-mail .dmto{color:#5F6368}
|
|
271
273
|
.lqblob .bfor{font-size:11.5px;line-height:1.3;color:#8A919B;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
|
|
274
|
+
/* the work a question asks about is done already (lanes.mjs doneSince); a click's own feedback: the button pressed, Sending…, or what went wrong */
|
|
275
|
+
.lqblob .bdone{font-size:12px;line-height:1.35;color:#7FD8A6;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow-wrap:anywhere}
|
|
276
|
+
.lqblob .bst{font-size:12px;line-height:1.35;color:#8A919B}.lqblob .bst:empty{display:none}.lqblob .bst.err{color:#F2A541}
|
|
277
|
+
.lqblob .nopt.pressed,.lqblob .bfx button.pressed{opacity:1;box-shadow:inset 0 0 0 2px currentColor}
|
|
278
|
+
.lqblob.sending .nopt:not(.pressed),.lqblob.sending .bfx button:not(.pressed),.lqblob.sending .bfree{opacity:.45;cursor:default}
|
|
279
|
+
.lqblob.sending .nopt:hover{border-color:rgba(255,255,255,.16)}
|
|
272
280
|
.lqblob .nopt:focus-visible,.lqblob .bfree:focus-visible,.lqblob .bfx button:focus-visible{outline:2px solid currentColor;outline-offset:2px}
|
|
273
281
|
.lqblob.gone{opacity:0!important;filter:blur(8px);transition:opacity .6s ease,filter .6s ease;pointer-events:none!important}
|
|
274
282
|
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)}
|
|
@@ -277,7 +285,8 @@ body.lq-light .lqblob .nopt{background:#FFFFFF;border-color:rgba(20,30,45,.16);c
|
|
|
277
285
|
body.lq-light .lqblob .nopt.rec{background:#151A21;color:#FFFFFF;border-color:#151A21}
|
|
278
286
|
body.lq-light .lqblob .bfx input{background:#FFFFFF;border-color:rgba(20,30,45,.18)}
|
|
279
287
|
body.lq-light .lqblob .bfx button{background:#151A21;color:#FFFFFF}
|
|
280
|
-
body.lq-light .lqblob .bfree,body.lq-light .lqd.lqblob .lt i,body.lq-light .lqblob .bmore,body.lq-light .lqblob .bfor{color:#5A6470}
|
|
288
|
+
body.lq-light .lqblob .bfree,body.lq-light .lqd.lqblob .lt i,body.lq-light .lqblob .bmore,body.lq-light .lqblob .bfor,body.lq-light .lqblob .bst{color:#5A6470}
|
|
289
|
+
body.lq-light .lqblob .bdone{color:#1F7A4C}body.lq-light .lqblob .bst.err{color:#9A5200}
|
|
281
290
|
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)}
|
|
282
291
|
body.lq-contrast .lqblob .nopt{border:2px solid currentColor}
|
|
283
292
|
body.lq-touch .lqblob .nopt,body.lq-touch .lqblob .bfx input{min-height:44px}
|
|
@@ -511,8 +520,48 @@ body.lq-liquid .mkcap{border-radius:18px;border-color:rgba(255,255,255,.08);back
|
|
|
511
520
|
.mkbox:hover{box-shadow:0 0 0 2px rgba(255,255,255,.55)}.mkbox:focus-visible{outline:2px solid var(--bone);outline-offset:1px}
|
|
512
521
|
.mkcapt{font-size:13px;line-height:1.45;color:var(--text);overflow-wrap:anywhere}.mkcapm{font-size:11.5px;color:var(--faint)}
|
|
513
522
|
.mkuse{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.mkuse button{padding:4px 10px;font-size:12px}#mktray .mkuse select{width:auto;max-width:100%;min-height:0;padding:4px 30px 4px 10px;border-radius:999px;font-size:12px;background-position:right 11px center}
|
|
523
|
+
/* capturing a product (capture.mjs), a capture to look over first, and a blur box you draw */
|
|
524
|
+
.mkcaprow{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:4px 0 14px}.mkcaprow select,.mkcaprow input{width:auto;min-width:0;min-height:0;padding:6px 12px;border-radius:999px;font-size:13px}.mkcaprow input{flex:1 1 220px}.mkcaprow select{padding-right:30px;background-position:right 11px center}
|
|
525
|
+
.mkcapst{flex-basis:100%;font-size:12.5px;line-height:1.45;color:var(--faint)}.mkcapst:empty{display:none}.mkcapst.err{color:var(--amber)}.mkcapst.ok{color:var(--green)}
|
|
526
|
+
.mkreview{display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:8px 10px;border-radius:10px;border:1px solid var(--amber);font-size:12.5px;line-height:1.45;color:var(--text)}.mkreview span{flex:1 1 200px}.mkreview button{padding:4px 10px;font-size:12px}
|
|
527
|
+
.mkcapst2{font-size:12px;color:var(--faint)}.mkcapst2:empty{display:none}.mkcapst2.err{color:var(--amber)}
|
|
528
|
+
.mkdrawpad{display:none;position:absolute;inset:0;cursor:crosshair;touch-action:none;z-index:2}.mkcap.drawing .mkdrawpad{display:block}.mkcap.drawing .mkshot{outline:2px dashed var(--bone);outline-offset:2px}
|
|
529
|
+
.mkdrawn{position:absolute;border:1.5px solid var(--bone);background:rgba(10,12,14,.35);border-radius:3px;pointer-events:none}
|
|
530
|
+
.mkchipx{all:unset;cursor:pointer;margin-left:2px;padding:0 6px;border-radius:999px;font:600 12px/1.5 var(--sans);color:var(--faint)}.mkchipx:hover,.mkchipx:focus-visible{color:var(--bone);outline:2px solid var(--bone);outline-offset:1px}
|
|
514
531
|
.mkchips{display:flex;flex-wrap:wrap;gap:5px}.mkchip{all:unset;box-sizing:border-box;cursor:pointer;padding:2px 9px;border-radius:999px;border:1px dashed var(--amber);color:var(--text);font:500 11.5px/1.5 var(--sans)}.mkchip.on{border:1px solid var(--green);color:var(--green)}.mkchip:hover,.mkchip:focus-visible{outline:2px solid var(--bone);outline-offset:1px}.mkshot .mkbox.hl{box-shadow:0 0 0 2px var(--bone)}
|
|
515
532
|
.mkpv{margin:6px 0 14px}.mkpv .mkpvact{margin-top:8px}.mkpv .pchars{font-size:11px;color:var(--faint)}
|
|
533
|
+
/* a draft opened large (mkBig): its pictures and video near full size, its text in reading type, where it goes; Approve, Edit and Skip on it */
|
|
534
|
+
#mktasks .task[data-rel],#mkneeds .mkneed[data-rel],#boardposts .pcard{cursor:pointer}.mkpv .lipost,.mkpv .rdpost{cursor:zoom-in}
|
|
535
|
+
.mkbig{position:fixed;inset:0;z-index:950;display:flex;align-items:center;justify-content:center;padding:28px;background:rgba(3,4,6,.62);backdrop-filter:blur(6px)}
|
|
536
|
+
.mkbigin{position:relative;display:flex;flex-direction:column;width:min(1240px,100%);max-height:100%;border-radius:22px;background:rgba(10,12,15,.94);border:1px solid rgba(255,255,255,.12);box-shadow:0 0 0 1px rgba(214,222,234,.1),0 40px 90px -30px rgba(0,0,0,.85),inset 0 1px 0 rgba(255,255,255,.06);backdrop-filter:blur(20px) saturate(1.25);color:var(--text);overflow:hidden}
|
|
537
|
+
body.lq-light .mkbig{background:rgba(40,44,52,.38)}body.lq-light .mkbigin{background:rgba(250,250,251,.97);border-color:rgba(21,26,33,.1);box-shadow:0 40px 90px -30px rgba(20,24,30,.45)}
|
|
538
|
+
body.lq-solid .mkbig,body.lq-solid .mkbigin{backdrop-filter:none}
|
|
539
|
+
.mkbighead{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:16px 18px 14px 22px;border-bottom:1px solid var(--line)}
|
|
540
|
+
.mkbighead h2{flex:1 1 260px;margin:0;font:650 19px/1.3 var(--sans);color:var(--bone);letter-spacing:-.01em}
|
|
541
|
+
.mkbigto{display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:999px;border:1px solid var(--line);font-size:12.5px;color:var(--text);white-space:nowrap}.mkbigto b{color:var(--bone);font-weight:600}
|
|
542
|
+
.mkbigx{all:unset;box-sizing:border-box;cursor:pointer;width:38px;height:38px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--line);color:var(--bone);font-size:22px;line-height:1}.mkbigx:hover,.mkbigx:focus-visible{outline:2px solid var(--bone);outline-offset:1px}
|
|
543
|
+
.mkbigbody{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(320px,1fr);gap:24px;padding:20px 22px;overflow:auto;min-height:0}
|
|
544
|
+
.mkbigbody.textonly{grid-template-columns:minmax(0,760px);justify-content:center}
|
|
545
|
+
@media(max-width:920px){.mkbig{padding:10px}.mkbigbody{grid-template-columns:1fr}}
|
|
546
|
+
.mkbigmedia{display:flex;flex-direction:column;gap:10px;min-width:0}
|
|
547
|
+
.mkbigmain{position:relative;border-radius:14px;overflow:hidden;background:rgba(0,0,0,.35);line-height:0;border:1px solid var(--line)}body.lq-light .mkbigmain{background:#eceae6}
|
|
548
|
+
.mkbigmain img,.mkbigmain video{display:block;width:100%;max-height:68vh;object-fit:contain}.mkbigmain img{cursor:zoom-in}
|
|
549
|
+
.mkbigthumbs{display:flex;gap:8px;flex-wrap:wrap}.mkbigthumbs button{all:unset;cursor:pointer;box-sizing:border-box;width:84px;height:56px;border-radius:9px;overflow:hidden;border:2px solid transparent;opacity:.7;background:rgba(0,0,0,.3)}.mkbigthumbs button.on{border-color:var(--bone);opacity:1}.mkbigthumbs button:hover,.mkbigthumbs button:focus-visible{opacity:1;outline:2px solid var(--bone);outline-offset:1px}.mkbigthumbs img,.mkbigthumbs video{width:100%;height:100%;object-fit:cover;pointer-events:none}
|
|
550
|
+
.mkbignone{padding:18px;border-radius:14px;border:1px dashed var(--amber);font-size:14px;line-height:1.5;color:var(--text)}
|
|
551
|
+
.mkbigtext{min-width:0}.mkbigtitle{margin:0 0 10px;font:650 18px/1.4 var(--sans);color:var(--bone)}.mkbigtitle small{display:block;font:500 12px var(--sans);color:var(--faint);margin-bottom:3px;letter-spacing:.02em}
|
|
552
|
+
.mkbigbodyt{white-space:pre-wrap;overflow-wrap:anywhere;font-size:16px;line-height:1.62;color:var(--bone)}.mkbig .lihash{color:#71b7fb}body.lq-light .mkbig .lihash{color:#0a66c2}
|
|
553
|
+
.mkbigbody textarea{display:block;width:100%;min-height:46vh;box-sizing:border-box;background:var(--ink);border:1px solid var(--line);border-radius:12px;padding:12px 14px;color:var(--bone);font:16px/1.6 var(--sans);resize:vertical}.mkbigbody textarea:focus{outline:none;border-color:var(--green)}
|
|
554
|
+
.mkbigmeta{margin-top:12px;font-size:12.5px;color:var(--faint)}.mkbig .linotes{margin-top:12px;font-size:13.5px}
|
|
555
|
+
.mkbigfoot{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:12px 22px 16px;border-top:1px solid var(--line)}.mkbigfoot .mkbigsay{flex:1 1 220px;font-size:13px;color:var(--faint);line-height:1.45}.mkbigfoot .mkbigsay.err{color:var(--amber)}.mkbigfoot .mkbigsay.ok{color:var(--green)}
|
|
556
|
+
/* one picture alone: arrows, or a swipe, between a post's pictures and video */
|
|
557
|
+
.mkzoom{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:56px 72px;background:rgba(0,0,0,.9);touch-action:pan-y}
|
|
558
|
+
.mkzoom img,.mkzoom video{max-width:100%;max-height:100%;border-radius:8px;object-fit:contain;user-select:none}
|
|
559
|
+
.mkzoom .mkzx,.mkzoom .mkzp,.mkzoom .mkzn{all:unset;box-sizing:border-box;position:absolute;cursor:pointer;width:46px;height:46px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.22);color:#F3F0EA;font-size:24px;line-height:1}
|
|
560
|
+
.mkzoom .mkzx:hover,.mkzoom .mkzp:hover,.mkzoom .mkzn:hover,.mkzoom button:focus-visible{background:rgba(255,255,255,.2);outline:2px solid #F3F0EA;outline-offset:1px}
|
|
561
|
+
.mkzoom .mkzx{top:14px;right:16px}.mkzoom .mkzp{left:14px;top:50%;margin-top:-23px}.mkzoom .mkzn{right:14px;top:50%;margin-top:-23px}
|
|
562
|
+
.mkzoom .mkzc{position:absolute;left:0;right:0;bottom:14px;text-align:center;color:#C9CDD3;font:500 13px var(--sans)}
|
|
563
|
+
@media(max-width:700px){.mkzoom{padding:56px 8px}.mkzoom .mkzp,.mkzoom .mkzn{top:auto;bottom:8px;margin:0}}
|
|
564
|
+
.mkbigb{padding:4px 10px;font-size:12px}.mkshot img{cursor:zoom-in}.mkcap.drawing .mkshot img{cursor:crosshair}
|
|
516
565
|
.lipost{max-width:552px;background:#fff;color:#191919;border:1px solid #e0dfdc;border-radius:10px;padding:12px 16px 4px;font:14px/1.43 -apple-system,system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif}
|
|
517
566
|
.liauth{display:flex;gap:8px;align-items:flex-start;margin-bottom:10px}
|
|
518
567
|
.liav{flex:0 0 48px;width:48px;height:48px;border-radius:50%;object-fit:cover;background:#0a66c2;color:#fff;display:flex;align-items:center;justify-content:center;font-weight:600;font-size:17px}
|
|
@@ -522,6 +571,15 @@ body.lq-liquid .mkcap{border-radius:18px;border-color:rgba(255,255,255,.08);back
|
|
|
522
571
|
.limore{background:none;border:0;padding:0 0 0 2px;min-height:0;color:rgba(0,0,0,.6);font:inherit;cursor:pointer}.limore:hover{color:#0a66c2;text-decoration:underline}
|
|
523
572
|
.limedia{display:block;width:calc(100% + 32px);margin:0 -16px 4px;max-height:560px;object-fit:contain;background:#f3f2ef}
|
|
524
573
|
.libar{display:flex;justify-content:space-around;border-top:1px solid #e0dfdc;padding:6px 0;color:rgba(0,0,0,.6);font-weight:600;font-size:13px}
|
|
574
|
+
/* a Reddit draft as Reddit shows it: its subreddit and who posts it, its title, its flair, the body */
|
|
575
|
+
.rdpost{max-width:640px;background:#fff;color:#1c1c1c;border:1px solid #d4d7d9;border-radius:10px;padding:10px 16px 4px;font:14px/1.5 -apple-system,system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif}
|
|
576
|
+
.rdhead{display:flex;align-items:center;flex-wrap:wrap;gap:6px;font-size:12px;color:#576f76}.rdhead b{color:#1c1c1c;font-weight:700}
|
|
577
|
+
.rdic{flex:none;width:24px;height:24px;border-radius:50%;background:#ff4500;color:#fff;display:inline-flex;align-items:center;justify-content:center;font:700 11px/1 -apple-system,system-ui,sans-serif}
|
|
578
|
+
.rdtitle{margin:8px 0 6px;font-weight:600;font-size:18px;line-height:1.35;color:#1c1c1c;overflow-wrap:anywhere}
|
|
579
|
+
.rdflair{display:inline-block;margin:0 0 8px;padding:1px 9px;border-radius:999px;background:#e5ebee;color:#1c1c1c;font-size:12px;font-weight:600}
|
|
580
|
+
.rdbody{white-space:pre-wrap;overflow-wrap:anywhere;margin:4px 0 8px}.rdbody code{background:#eef1f3;padding:0 4px;border-radius:4px;font:12.5px ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
581
|
+
.rdmedia{display:block;width:100%;max-height:560px;object-fit:contain;background:#0e1113;border-radius:12px;margin:6px 0 8px}
|
|
582
|
+
.rdbar{display:flex;gap:18px;border-top:1px solid #e5ebee;padding:6px 0;color:#576f76;font-weight:600;font-size:12.5px}
|
|
525
583
|
.linotes{margin-top:8px;font-size:12.5px;color:var(--text)}.linotes summary{cursor:pointer;color:var(--faint)}.linotest{white-space:pre-wrap;overflow-wrap:anywhere;margin:6px 0;color:var(--text)}
|
|
526
584
|
.mkneeds{margin-bottom:14px;padding:4px 0 4px 12px;border-left:2px solid var(--amber)}
|
|
527
585
|
.mkneeds.lit{border-radius:0 16px 16px 0}
|
|
@@ -536,6 +594,12 @@ body.lq-liquid .mkcap{border-radius:18px;border-color:rgba(255,255,255,.08);back
|
|
|
536
594
|
.rdoc code{font-size:12.5px;background:var(--ink3);border:1px solid var(--line);border-radius:5px;padding:0 4px}.rdoc pre{background:var(--ink3);border:1px solid var(--line);border-radius:9px;padding:10px 12px;overflow:auto}.rdoc pre code{background:none;border:0;padding:0}
|
|
537
595
|
.rdoc pre.prose{font-family:inherit;white-space:pre-wrap;overflow-wrap:anywhere;overflow:visible}.rdoc pre.prose code{font-family:inherit;font-size:14px;line-height:1.5}
|
|
538
596
|
.rdoc img.rimg{display:block;max-width:min(100%,560px);height:auto;margin:10px 0;border:1px solid var(--line);border-radius:9px;cursor:zoom-in}.rdoc .rimgmiss{display:inline-block;margin:6px 0;padding:6px 10px;border:1px dashed var(--amber);border-radius:8px;color:var(--faint);font-size:13px}
|
|
597
|
+
/* an open report's way back, kept in view under the page's header while the report scrolls (top: repStick) */
|
|
598
|
+
.rbackbar{position:sticky;top:var(--rbtop,0px);z-index:3;display:flex;align-items:center;gap:10px;margin:10px -10px 4px;padding:8px 10px;border-radius:14px;background:rgba(6,8,11,.9);backdrop-filter:blur(14px);box-shadow:0 1px 0 rgba(220,228,240,.12)}
|
|
599
|
+
.rbackbar button{flex:none}.rbackbar .rbpath{flex:1;min-width:0;font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;direction:rtl;text-align:left}
|
|
600
|
+
body.lq-light .rbackbar{background:rgba(246,248,251,.95);box-shadow:0 1px 0 rgba(20,30,45,.1)}body.lq-solid .rbackbar{backdrop-filter:none;background:var(--ink)}
|
|
601
|
+
@media(max-width:520px){.rbackbar .rbpath{display:none}}
|
|
602
|
+
body.lq-liquid.lq-pooled:has(#panel-reports:not(.hidden) #rbackbar) #lqsinkrow{background:rgba(6,8,11,.97)}body.lq-liquid.lq-light.lq-pooled:has(#panel-reports:not(.hidden) #rbackbar) #lqsinkrow{background:rgba(246,248,251,.98)}
|
|
539
603
|
.rzoom{position:fixed;inset:0;z-index:1000;background:rgba(0,0,0,.82);display:flex;align-items:center;justify-content:center;cursor:zoom-out;padding:24px}.rzoom img{max-width:100%;max-height:100%;border-radius:8px}
|
|
540
604
|
.rdoc blockquote{margin:8px 0;padding:2px 12px;border-left:3px solid var(--line);color:var(--faint)}.rdoc a{color:var(--green)}
|
|
541
605
|
.rdoc .rtable{overflow-x:auto;margin:10px 0}.rdoc table{border-collapse:collapse;font-size:13px;min-width:100%}.rdoc th,.rdoc td{border:1px solid var(--line);padding:6px 8px;text-align:left;vertical-align:top}.rdoc th{background:var(--ink3);color:var(--bone);font-weight:600}
|
|
@@ -981,6 +1045,27 @@ body.lq-light #lqaway .aws b{color:#9A5200}
|
|
|
981
1045
|
.pcq .opts button{text-align:left;white-space:normal;line-height:1.4;height:auto;padding:8px 12px}
|
|
982
1046
|
.pcq form{display:flex;gap:8px;margin-top:8px}.pcq form input{flex:1;min-width:0}
|
|
983
1047
|
.pcq ul{margin:6px 0 8px;padding-left:18px;font-size:13px;line-height:1.5;color:var(--text)}
|
|
1048
|
+
/* Home on a phone paired with your computer (lqPhone): a plain list to act on, no liquid. What needs you and the tasks first, the rest of Symbiot one tap away in a row along the top */
|
|
1049
|
+
#phhome{display:none}
|
|
1050
|
+
body.lq-phone:not(.lq-work):not(.lq-pooled) #phhome{display:block;position:absolute;left:0;right:0;top:64px;bottom:84px;z-index:3;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:none;padding:0 16px 24px;color:var(--text);font-family:var(--sans)}
|
|
1051
|
+
#phhome::-webkit-scrollbar{display:none}
|
|
1052
|
+
body.lq-phone:not(.lq-work):not(.lq-pooled) :is(#lq,#lqdrops,#lqgroups,#lqneedt,#lqnext,#lqcore,#lqorbits,#lqrelay,#lqmore,#lqline,#lqgo){display:none}
|
|
1053
|
+
body.lq-phone:not(.lq-work):not(.lq-pooled) #liquid{background:var(--ink)}
|
|
1054
|
+
#phnav{position:sticky;top:0;z-index:2;display:flex;gap:6px;overflow-x:auto;scrollbar-width:none;margin:0 -16px;padding:8px 16px 10px;background:var(--ink)}
|
|
1055
|
+
#phnav::-webkit-scrollbar{display:none}
|
|
1056
|
+
#phnav button{flex:none;display:inline-flex;align-items:center;gap:6px;min-height:34px;padding:6px 13px;border-radius:999px;border:1px solid var(--line);background:var(--ink2);color:var(--text);font:500 13px/1 var(--sans);cursor:pointer}
|
|
1057
|
+
#phnav button b{font-weight:600;color:var(--amber)}
|
|
1058
|
+
#phnav button.lit{border-color:var(--amber)}
|
|
1059
|
+
#phhome #pcline{font-size:12.5px;line-height:1.45;color:var(--faint);margin:4px 0 2px}
|
|
1060
|
+
#phhome #pcline b{color:var(--amber)}
|
|
1061
|
+
#phhome .pshead{margin:14px 0 8px}
|
|
1062
|
+
#phhome .mkneeds{margin-top:10px}
|
|
1063
|
+
#phhome .pcadd input{flex:1 1 100%}#phhome .pcadd select{flex:1 1 0}
|
|
1064
|
+
#phyou{margin:10px 0 0}
|
|
1065
|
+
#phyou .phyi{display:flex;flex-direction:column;align-items:flex-start;gap:2px;width:100%;margin-top:6px;padding:10px 12px;border:1px solid var(--line);border-radius:12px;background:var(--ink2);color:var(--text);font:inherit;text-align:left;cursor:pointer}
|
|
1066
|
+
#phyou .phyi b{font-weight:600;font-size:13.5px}
|
|
1067
|
+
#phyou .phyi span{font-size:12.5px;line-height:1.4;color:var(--faint)}
|
|
1068
|
+
#phyou .phyi.urgent{border-color:var(--amber)}
|
|
984
1069
|
.pcadd{display:flex;gap:8px;margin:8px 0 2px;flex-wrap:wrap}.pcadd input{flex:1;min-width:150px}.pcadd select{flex:0 1 160px;min-width:0}
|
|
985
1070
|
.pcfound{display:flex;flex-direction:column;gap:6px;margin-top:8px}
|
|
986
1071
|
body.lq-hline:not(.lq-work):not(.lq-pooled):not(.lq-chat):not(.lq-onb) #lqline{display:block;position:absolute;left:50%;top:auto;bottom:100px;transform:translateX(-50%);z-index:3;max-width:calc(100% - 32px);text-align:center;font:400 13.5px/1.4 var(--sans);color:#A1A8B1;text-shadow:0 1px 10px rgba(0,0,0,.7)}
|
|
@@ -1086,6 +1171,7 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
|
|
|
1086
1171
|
<canvas id="lq" aria-hidden="true"></canvas>
|
|
1087
1172
|
<div id="lqorbits"></div>
|
|
1088
1173
|
<div id="lqline" aria-live="polite"></div>
|
|
1174
|
+
<div id="phhome" role="region" aria-label="Home"><nav id="phnav" aria-label="The rest of Symbiot"></nav><div id="phpc"></div></div>
|
|
1089
1175
|
<div id="lqrelay" aria-label="Your projects: what their agents did, ask and suggest"></div>
|
|
1090
1176
|
<div id="lqgroups"></div>
|
|
1091
1177
|
<div id="lqneedt"><span>Needs you</span><small>only you can do these</small></div>
|
|
@@ -1804,8 +1890,8 @@ function relHtml(rl){return "<div class='qrel"+(rl.waiting?" wait":"")+"'>Needs
|
|
|
1804
1890
|
function askHtml(a){var k=a.ask;if(!k)return '';var qs=k.questions||[],ss=k.suggestions||[];if(!qs.length&&!ss.length)return '';
|
|
1805
1891
|
var h="<div class='aq' data-id='"+esc(a.id)+"'>";
|
|
1806
1892
|
if(qs.length){h+="<h4><i class=ic-ask></i> "+qs.length+" question"+(qs.length>1?"s":"")+" for you</h4>";
|
|
1807
|
-
qs.forEach(function(q,i){var rl=q.release;h+="<div class='q' data-i='"+i+"'><div class='qt'>"+esc(q.q)+"</div>"+(q.context?"<div class='qc'>"+esc(q.context)+"</div>":"")+(rl?relHtml(rl):"");
|
|
1808
|
-
(q.options||[]).forEach(function(o,j){var off=rl&&rl.waiting&&DONEOPT.test(o);h+="<label class='opt"+(off?" off":"")+"'><input type='radio' name='q_"+esc(a.id)+"_"+i+"' value='"+j+"'"+(off?" disabled":"")+"><span>"+whoHtml(o)+(off?" <i>(once "+esc(rl.name+" "+rl.needs)+" is installed)</i>":"")+"</span></label>";});
|
|
1893
|
+
qs.forEach(function(q,i){var rl=q.release;h+="<div class='q' data-i='"+i+"'><div class='qt'>"+esc(q.shown||q.q)+"</div>"+(q.context?"<div class='qc'>"+esc(q.context)+"</div>":"")+(rl?relHtml(rl):"");
|
|
1894
|
+
(q.options||[]).forEach(function(o,j){var off=rl&&rl.waiting&&DONEOPT.test(o);h+="<label class='opt"+(off?" off":"")+"'><input type='radio' name='q_"+esc(a.id)+"_"+i+"' value='"+j+"'"+(off?" disabled":"")+"><span>"+whoHtml((q.labels&&q.labels[j])||o)+(off?" <i>(once "+esc(rl.name+" "+rl.needs)+" is installed)</i>":"")+"</span></label>";});
|
|
1809
1895
|
h+="<input class='qother' placeholder='"+((q.options&&q.options.length)?"or answer in your own words":"your answer")+"'></div>";});
|
|
1810
1896
|
h+="<div class='row'><button class='act qsend' title='save the answers and hand the repo back to your agent'>Send answers & continue</button><button class='ghost qsave' title='save the answers for the next run'>Save only</button></div>";}
|
|
1811
1897
|
if(ss.length){var shown=IDEASOPEN[a.path]?ss.length:(k.ideasShown||ss.length);h+="<h4><i class=ic-idea></i> Ideas from the agent</h4>";ss.forEach(function(s,i){if(i>=shown)return;h+="<div class='idea'><span style='flex:1'>"+esc(s.text)+(s.other?" <span class='tag' title='this idea is for another project, so + task adds it to that one'>for "+esc(s.repo)+"</span>":"")+"</span>"+(s.added?"<span class='tag'>in Tasks</span>":"<button class='ghost qidea' data-i='"+i+"' style='padding:3px 9px;font-size:12px'>+ task</button><button class='ghost qskip' data-i='"+i+"' title='turn this idea down: the next one moves up, and the agent is told not to suggest it again' style='padding:3px 9px;font-size:12px'>Skip</button>")+"</div>";});
|
|
@@ -1860,6 +1946,11 @@ api('/api/agents/remember',skip?{path:a.path,skip:true}:{path:a.path,only:only})
|
|
|
1860
1946
|
if(!r||r.error){box.querySelectorAll('button').forEach(function(b){b.disabled=false;});msg.innerHTML="<div class='note err'>"+esc((r&&r.error)||'failed')+"</div>";return;}
|
|
1861
1947
|
msg.innerHTML=skip?"<div class='note ok'>Skipped what <b>"+esc(a.name)+"</b> found: nothing went into memory.</div>":"<div class='note ok'>✓ Remembered "+r.remembered+" new fact"+(r.remembered===1?"":"s")+" from <b>"+esc(r.title||a.name)+"</b>"+(r.remembered<r.of?" (the rest were known already)":"")+".</div>";loadAgents();});}
|
|
1862
1948
|
var k=box.querySelector('.rkeep'),s=box.querySelector('.rskip');if(k)k.addEventListener('click',function(){send(false);});if(s)s.addEventListener('click',function(){send(true);});});}
|
|
1949
|
+
// What this run handed to another lane (lanes.mjs handingOf): whether that lane has it yet, and if not, why; once it has, how far its run has got
|
|
1950
|
+
// How far the run on a handover has got: its tasks ticked, and what it's doing now
|
|
1951
|
+
function handProg(x){var b=[];if(x.progress)b.push(x.progress.done+' of '+x.progress.total+' ticked');if(x.doing)b.push(x.doing);return b.join(' \u00b7 ');}
|
|
1952
|
+
function handHtml(a){var hs=a.handing;if(!hs||!hs.length)return '';
|
|
1953
|
+
return "<div class='aq hand'><h4>Handed over</h4>"+hs.map(function(x){var pg=handProg(x);return "<div class='qc'><b>"+esc(x.lane)+"</b>: "+esc(x.text)+"<br><span"+(x.gone?" class='muted'":" style='color:var(--amber)'")+">"+esc(x.why)+"</span>"+(pg?"<br><span class='muted'>"+esc(pg)+"</span>":"")+"</div>";}).join('')+"</div>";}
|
|
1863
1954
|
function answering(){var f=document.activeElement;return !!(f&&f.closest&&f.closest('.aq'));}
|
|
1864
1955
|
// Handovers (lanes.mjs): what one lane's agent handed to another, and where it stands.
|
|
1865
1956
|
var LANEWORD={started:'working on it',held:'queued: that lane is busy',blocked:"didn't start",done:'done, reported back',error:"couldn't hand over"};
|
|
@@ -1902,9 +1993,10 @@ function loadAgents(){loadLanes();loadParked(agentsDraw);phoneApprovedNote();}
|
|
|
1902
1993
|
function phoneApprovedNote(){var el=$('phoneappr');if(!el)return;var ap=(LQ.home&&LQ.home.phoneApproved)||[];
|
|
1903
1994
|
el.innerHTML=ap.map(function(a){return "<div class='note "+(a.status==='failed'?'err':'ok')+"'>"+(a.status==='failed'?"Approving <b>"+esc(a.repo)+"</b> from "+esc(a.phone)+" failed: "+esc(a.error||''):(a.status==='approving'?"Approving ":"✓ Approved ")+"<b>"+esc(a.repo)+"</b> from "+esc(a.phone)+", "+agoTxt(a.at)+(a.pr?" · <a href='"+escQ(a.pr)+"' target='_blank' rel='noopener'>its PR</a>":""))+"</div>";}).join('');}
|
|
1904
1995
|
// Agents, in order: what needs you (a question, a step of yours), what's at work, and
|
|
1905
|
-
// what's finished, folded away (open stays open across refreshes).
|
|
1996
|
+
// what's finished, folded away (open stays open across refreshes). A run whose handover
|
|
1997
|
+
// hasn't gone to its lane yet stays out with what's at work, not folded away.
|
|
1906
1998
|
var AGDONEOPEN=false;
|
|
1907
|
-
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);});
|
|
1999
|
+
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'||(a.handing||[]).some(function(x){return !x.gone;}))run.push(a);else done.push(a);});
|
|
1908
2000
|
var g=function(t,xs){return xs.length?"<div class='tgroup agg'>"+t+" <span class='tcount'>"+xs.length+"</span></div>"+xs.map(fn).join(''):'';};
|
|
1909
2001
|
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>":'');}
|
|
1910
2002
|
function agentsDraw(){api('/api/agents').then(function(list){var el=document.getElementById('agentslist');
|
|
@@ -1919,6 +2011,7 @@ if(a.earlier)st+=" · "+(a.status==='running'?"started outside this window
|
|
|
1919
2011
|
if(a.waiting)st+=" · <span style='color:var(--amber)'>waiting on your step</span>";
|
|
1920
2012
|
if(nQs(a))st+=" · <span style='color:var(--amber)'>needs your answers</span>";
|
|
1921
2013
|
if(a.needs&&!a.needs.waiting)st+=" · <span style='color:var(--amber)'>"+(a.needs.kind==='approve'?"waits for your OK":"needs you")+"</span>";
|
|
2014
|
+
var hw=(a.handing||[]).filter(function(x){return !x.gone;}).length;if(hw)st+=" · <span style='color:var(--amber)'>"+(hw>1?hw+" handovers":"a handover")+" not sent yet</span>";
|
|
1922
2015
|
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>";
|
|
1923
2016
|
if(a.held!=null){var hn=a.held.length,ht=a.held.map(function(t){return "• "+esc(t).replace(/'/g,''');}).join(' ');
|
|
1924
2017
|
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.");
|
|
@@ -1938,7 +2031,7 @@ if(ch&&(ch.dirty||ch.stat||(ch.commits&&ch.commits.length))){
|
|
|
1938
2031
|
}else if(a.status==='done'){
|
|
1939
2032
|
b+="<div class='changed muted'>No file changes detected (the agent may have only planned or asked).</div>";
|
|
1940
2033
|
}
|
|
1941
|
-
b+=waitHtml(a)+needsHtml(a)+askHtml(a)+factsHtml(a);
|
|
2034
|
+
b+=waitHtml(a)+needsHtml(a)+askHtml(a)+handHtml(a)+factsHtml(a);
|
|
1942
2035
|
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>");
|
|
1943
2036
|
return "<div class='agent' data-i='"+list.indexOf(a)+"'>"+b+"</div>";};
|
|
1944
2037
|
el.innerHTML=agentGroups(list,agentBlock);var dn=el.querySelector('.agdone');if(dn)dn.addEventListener('toggle',function(){AGDONEOPEN=dn.open;});
|
|
@@ -2040,7 +2133,7 @@ function phoneSeen(p){return p.old?"<span class='err'>paired before the link was
|
|
|
2040
2133
|
function renderPhoneLink(d,msg){if(!d||!d.role)return;var box=$('phonelink'),h='';if(!box)return;
|
|
2041
2134
|
if(d.role==='phone'){
|
|
2042
2135
|
if(d.paired&&!d.old){h+="<div class='task'><span class='t'>Paired with <b>"+esc(d.name)+"</b> <span class='muted' style='font-size:12px'>"+esc(d.url||'')+(d.last?" · asked "+agoTxt(d.last)+(d.via==='relay'?" through the relay":""):"")+"</span>"+(d.error?"<br><span class='err' style='font-size:12px'>"+esc(d.error)+"</span>":"")+"</span><button class='ghost' id='pcheck' title='ask your computer now'>Check now</button><button class='rm' id='pforget' title='forget it: its copy here goes too'>×</button></div>";
|
|
2043
|
-
h+="<div class='note muted'>Its tasks, what needs you and its agents’ questions are on Home, in <b>On "+esc(d.name)+"</b>: tick, add, answer and approve from there. While Symbiot runs on this phone it asks every 2 minutes (and as soon as this phone’s network changes), and
|
|
2136
|
+
h+="<div class='note muted'>Its tasks, what needs you and its agents’ questions are on Home, in <b>On "+esc(d.name)+"</b>: tick, add, answer and approve from there. While Symbiot runs on this phone it asks every 2 minutes (and as soon as this phone’s network changes), and tells you, with a notification, what Watch found there and what newly needs you: an agent’s question, work waiting for your Approve.</div>";}
|
|
2044
2137
|
else{if(d.old)h+="<div class='note err'>"+esc(d.error||'')+"</div>";
|
|
2045
2138
|
h+="<div class='note muted' style='margin-top:2px'>"+(window.SymbiotAndroid?"<b>Scan the code your computer shows:</b> in Symbiot on your computer, open Settings → <b>Your phone</b> and switch it on. Point this phone’s camera at the QR, and tap its link. Or type its address and code here:":"In Symbiot on your computer, open Settings → <b>Your phone</b> and switch it on: it shows its address and a 6-digit code. Type both here.")+"</div>"+
|
|
2046
2139
|
"<div class='row'><input id='paddr' placeholder='your computer’s address, e.g. 192.168.8.50:7392' style='flex:1'><input id='pcode' placeholder='code' inputmode='numeric' style='flex:0 0 90px'><button class='act' id='ppair'>Pair</button></div>"+
|
|
@@ -2082,12 +2175,13 @@ var h='',W="<span class='pcwait'> waiting to send</span>";
|
|
|
2082
2175
|
(c.notes||[]).forEach(function(n){h+="<div class='task' data-note='"+escQ(n.id)+"'><span class='t err'>"+esc(n.error)+(n.what?" <span class='muted'>("+esc(n.what)+")</span>":"")+"</span><button class='ghost pcnote'>OK</button></div>";});
|
|
2083
2176
|
var asks=c.asks||[],aps=c.approves||[],needs=c.needs||[];
|
|
2084
2177
|
if(asks.length||aps.length||needs.length){h+="<div class='mkneeds lit'><div class='pshead'><span>Needs you · "+(asks.length+aps.length+needs.length)+"</span><small>on "+esc(d.name)+"</small></div>";
|
|
2085
|
-
asks.forEach(function(a,i){var q=(a.questions||[])[0]||{};h+="<div class='pcq' data-ask='"+i+"'><div class='qn'>"+esc(a.name)+" asks"+((a.questions||[]).length>1?" · 1 of "+a.questions.length:"")+"</div><div class='qq'>"+esc(q.q||'')+"</div>"+
|
|
2086
|
-
(a.waiting?"<div class='note' style='margin:0'>You answered: "+esc((a.answered||[])[0]||'')+W+"</div>":"<div class='opts'>"+(q.options||[]).map(function(o,j){return "<button class='"+(j?'ghost':'act')+" pcopt' data-j='"+j+"'>"+esc(o)+"</button>";}).join('')+"</div><form class='pcown'><input placeholder='Or say it in your own words' maxlength='2000'><button class='ghost' type='submit'>Send</button></form>")+"</div>";});
|
|
2178
|
+
asks.forEach(function(a,i){var q=(a.questions||[])[0]||{};h+="<div class='pcq' data-ask='"+i+"'><div class='qn'>"+esc(a.name)+" asks"+((a.questions||[]).length>1?" · 1 of "+a.questions.length:"")+"</div><div class='qq'>"+esc(q.shown||q.q||'')+"</div>"+
|
|
2179
|
+
(a.waiting?"<div class='note' style='margin:0'>You answered: "+esc((a.answered||[])[0]||'')+W+"</div>":"<div class='opts'>"+(q.options||[]).map(function(o,j){return "<button class='"+(j?'ghost':'act')+" pcopt' data-j='"+j+"'>"+esc((q.labels&&q.labels[j])||o)+"</button>";}).join('')+"</div><form class='pcown'><input placeholder='Or say it in your own words' maxlength='2000'><button class='ghost' type='submit'>Send</button></form>")+"</div>";});
|
|
2087
2180
|
aps.forEach(function(a,i){h+="<div class='pcq' data-ap='"+i+"'><div class='qn'>"+esc(a.repo)+" · waiting for your Approve</div><div class='qq'>"+(a.tasks.length?a.tasks.length+" task"+(a.tasks.length>1?"s":"")+" done":"Changes without a task")+" · <span class='muted'>"+esc(a.stat||(a.files+" file"+(a.files===1?"":"s")+" changed"))+"</span></div>"+(a.tasks.length?"<ul>"+a.tasks.map(function(t){return "<li>"+esc(t.text)+"</li>";}).join('')+"</ul>":"")+
|
|
2088
2181
|
(a.waiting?"<div class='note' style='margin:0'>Approved here"+W+"</div>":"<div class='row' style='margin:0'><button class='act pcapprove'>Approve</button><span class='muted' style='font-size:12px'>exactly this: if the work changes before your computer gets it, it isn’t approved</span></div>")+"</div>";});
|
|
2089
2182
|
needs.forEach(function(n){h+="<div class='task'><span class='t'>"+esc(n.title)+" <span class='muted' style='font-size:12px'>"+esc(n.sub)+" · on your computer</span></span></div>";});
|
|
2090
2183
|
h+="</div>";}
|
|
2184
|
+
if(LQ.phone)h+="<div class='phyouslot'></div>"; // on a phone's Home: what this phone needs, after what the computer does
|
|
2091
2185
|
(c.recent||[]).slice(-3).forEach(function(r){h+="<div class='note "+(r.status==='failed'?'err':'ok')+"'>"+(r.status==='approving'?"Approving "+esc(r.repo)+" (from "+esc(r.phone)+")…":r.status==='failed'?"Approving "+esc(r.repo)+" from "+esc(r.phone)+" failed: "+esc(r.error||''):"✓ "+esc(r.repo)+" approved from "+esc(r.phone)+(r.pr?" · <a href='"+escQ(r.pr)+"' target='_blank' rel='noopener'>its PR</a>":""))+"</div>";});
|
|
2092
2186
|
var open=(c.tasks||[]).filter(function(t){return !t.done;}),done=(c.tasks||[]).filter(function(t){return t.done;}),repos=[];(c.tasks||[]).forEach(function(t){if(t.repo&&repos.indexOf(t.repo)<0)repos.push(t.repo);});
|
|
2093
2187
|
h+="<div class='pshead' style='margin-top:14px'><span>Tasks · "+open.length+" open</span><small>on "+esc(d.name)+"</small></div>";
|
|
@@ -2095,7 +2189,8 @@ h+="<form class='pcadd'><input id='pctask' placeholder='Add a task' maxlength='4
|
|
|
2095
2189
|
h+=open.concat(done.slice(0,8)).map(function(t){return "<div class='task"+(t.done?" done":"")+"' data-id='"+escQ(t.id)+"'><input type='checkbox' class='pctick'"+(t.done?" checked":"")+(String(t.id).indexOf('q:')===0?" disabled":"")+" aria-label='done'><span class='t'>"+esc(t.text)+(t.repo?" <span class='rp'>"+esc(t.repo)+"</span>":"")+(t.review?" <span class='muted' style='font-size:11px'>done by its agent, waits for your Approve</span>":"")+(t.waiting?W:"")+"</span></div>";}).join('');
|
|
2096
2190
|
if((c.watch||[]).length)h+="<div class='pshead' style='margin-top:14px'><span>What Watch found</span><small>on "+esc(d.name)+", today</small></div>"+c.watch.map(function(w){return "<div class='task'><span class='t'><b>"+esc(w.name)+"</b> <span class='muted' style='font-size:12px'>"+esc(w.label)+"</span>"+(w.items||[]).map(function(i){return "<br><span style='font-size:13px'>"+esc(i.text)+"</span>";}).join('')+"</span></div>";}).join('');
|
|
2097
2191
|
var typed=$('pctask')?[$('pctask').value,$('pcrepo').value,document.activeElement&&document.activeElement.id]:null;
|
|
2098
|
-
|
|
2192
|
+
var py=$('phyou');if(py&&el.contains(py))el.parentNode.appendChild(py);
|
|
2193
|
+
el.innerHTML=h;var pys=el.querySelector('.phyouslot');if(pys&&py)pys.parentNode.replaceChild(py,pys);if(typed){$('pctask').value=typed[0]||'';$('pcrepo').value=typed[1]||'';if(typed[2]==='pctask')$('pctask').focus();}
|
|
2099
2194
|
el.querySelectorAll('.pcnote').forEach(function(b){b.addEventListener('click',function(){api('/api/phone/note',{id:b.closest('.task').getAttribute('data-note')}).then(function(x){PCV=x;renderComputer();});});});
|
|
2100
2195
|
el.querySelectorAll('.pcq[data-ask]').forEach(function(box){var a=asks[+box.getAttribute('data-ask')],q=(a.questions||[])[0]||{};if(a.waiting)return;
|
|
2101
2196
|
var send=function(t){if(!t)return;pcDo('agent.answer',{path:a.path,answers:[{q:q.q,a:t}]});};
|
|
@@ -2369,6 +2464,7 @@ else h+="<div class='row' style='margin-top:10px'><span class='muted' style='fle
|
|
|
2369
2464
|
+"<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>"
|
|
2370
2465
|
+"<button class='ghost pdraft'"+(p.voice.count?" title='your AI drafts 3 posts from your last 7 days of commits, release tags and changelog; each must cite what git shows'":" disabled title='add example posts first'")+">Draft this week's posts</button></div>";
|
|
2371
2466
|
el.innerHTML=h+"<div id='postsmsg'>"+PMSG+"</div>";
|
|
2467
|
+
el.querySelectorAll('.pcard').forEach(function(c){if(c.classList.contains('editing'))return;c.title='open it large';c.addEventListener('click',function(ev){if(ev.target.closest('button,a,input,select,textarea,video,label,summary,details'))return;mkBig({post:c.getAttribute('data-id')},c);});});
|
|
2372
2468
|
el.querySelectorAll('.pmore').forEach(function(b){b.addEventListener('click',function(){var c=b.closest('.pcard'),on=c.classList.toggle('open');b.setAttribute('aria-expanded',on?'true':'false');b.textContent=on?'Show less':'Read it all';});});
|
|
2373
2469
|
function post(btn){var id=btn.closest('.pcard').getAttribute('data-id');return p.posts.filter(function(x){return x.id===id;})[0];}
|
|
2374
2470
|
el.querySelectorAll('.papprove').forEach(function(btn){btn.addEventListener('click',function(){var x=post(btn);if(!x)return;btn.disabled=true;postsMsg('','');
|
|
@@ -2429,8 +2525,15 @@ function loadMkTasks(){renderMkLane();}
|
|
|
2429
2525
|
var MKPV='';
|
|
2430
2526
|
function mkTags(t){return String(t||'').split(/(#[A-Za-z0-9_]+)/g).map(function(x,i){return i%2?"<span class='lihash'>"+esc(x)+"</span>":esc(x);}).join('');}
|
|
2431
2527
|
function mkMediaSrc(rel){return '/api/marketing/media?rel='+encodeURIComponent(rel)+'&t='+encodeURIComponent(T);}
|
|
2432
|
-
|
|
2433
|
-
var
|
|
2528
|
+
// Reddit's markdown, as far as a preview needs it: its text as written, a span in backticks as code.
|
|
2529
|
+
function mkRdText(t){var bt=String.fromCharCode(96);return String(t||'').split(bt).map(function(x,i,all){return i%2&&i<all.length-1?"<code>"+esc(x)+"</code>":esc((i%2?bt:'')+x);}).join('');}
|
|
2530
|
+
function mkRedditHtml(p){var a=p.author||{},who=p.handle||(a.name?'you ('+a.name+')':'you');
|
|
2531
|
+
return "<div class='rdpost' aria-label='Reddit preview'><div class='rdhead'><span class='rdic' aria-hidden='true'>r/</span><b>"+esc(p.where||'Reddit')+"</b><span>· "+(p.when?"scheduled for "+esc(p.when):"now")+" · posted by "+esc(who)+"</span></div>"
|
|
2532
|
+
+"<div class='rdtitle'>"+esc(p.title||'')+"</div>"+(p.flair?"<span class='rdflair'>"+esc(p.flair)+"</span>":"")
|
|
2533
|
+
+(p.media||[]).map(function(m){return m.kind==='video'?"<video class='rdmedia' src='"+escQ(mkMediaSrc(m.rel))+"' controls muted playsinline preload='metadata'></video>":"<img class='rdmedia' src='"+escQ(mkMediaSrc(m.rel))+"' alt='"+escQ(m.name)+"'>";}).join('')
|
|
2534
|
+
+"<div class='rdbody'>"+mkRdText(p.body)+"</div><div class='rdbar'><span>⇧ Vote ⇩</span><span>Comments</span><span>Award</span><span>Share</span></div></div>";}
|
|
2535
|
+
function mkPreviewHtml(p){var a=p.author||{},plat=p.platform==='linkedin'?'LinkedIn':(p.platform||'post'),reddit=/reddit/i.test(p.platform||'');
|
|
2536
|
+
var h=reddit?mkRedditHtml(p):"<div class='lipost' aria-label='"+escQ(plat)+" preview'><div class='liauth'>"+(a.avatar?"<img class='liav' src='"+escQ(mkMediaSrc('avatar'))+"' alt=''>":"<span class='liav'>"+esc(a.initials||'')+"</span>")
|
|
2434
2537
|
+"<div class='liwho'><b>"+esc(a.name||'')+"</b>"+(a.headline?"<small>"+esc(a.headline)+"</small>":"")+"<small>"+(p.when?"Scheduled for "+esc(p.when):"Now")+" · 🌐</small></div></div>"
|
|
2435
2538
|
+"<div class='litext'>"+(p.cut?mkTags(p.shown)+"…<button type='button' class='limore'>see more</button>":mkTags(p.body))+"</div>"
|
|
2436
2539
|
+(p.media||[]).map(function(m){return m.kind==='video'?"<video class='limedia' src='"+escQ(mkMediaSrc(m.rel))+"' controls muted playsinline preload='metadata'></video>":"<img class='limedia' src='"+escQ(mkMediaSrc(m.rel))+"' alt='"+escQ(m.name)+"'>";}).join('')
|
|
@@ -2441,23 +2544,88 @@ var done=p.status==='posted'?"Posted"+(on?" on "+esc(on):"")+(p.posted?", "+esc(
|
|
|
2441
2544
|
:p.status==='superseded'?"Superseded"+((p.by||[]).length?" by "+p.by.map(function(b){return "<code>"+esc(b)+"</code>";}).join(', '):"")+": it won't be posted."
|
|
2442
2545
|
:p.status==='approved'?"Approved "+agoTxt(p.statusAt)+": its agent "+(p.when?"schedules":"posts")+" it through Symbiot's browser, with this text.":p.status==='skipped'?"Skipped "+agoTxt(p.statusAt)+": it won't be posted.":"";
|
|
2443
2546
|
h+="<div class='row mkpvact'>"+(fin||p.status==='approved'?"":"<button class='act mkok'>Approve"+(p.when?", schedule it":", post it")+"</button>")+(fin||p.status==='skipped'?"":"<button class='ghost mkskip'>Skip</button>")
|
|
2444
|
-
+"<span class='muted' style='font-size:12px;flex:1'>"+(done||(p.when?"Its agent schedules it for "+esc(p.when)+" once you approve.":"Its agent posts it once you approve."))+"</span><span class='pchars'>"+p.chars+" / 3000</span></div>";
|
|
2547
|
+
+"<span class='muted' style='font-size:12px;flex:1'>"+(done||(p.when?"Its agent schedules it for "+esc(p.when)+" once you approve.":"Its agent posts it once you approve."))+"</span><span class='pchars'>"+(reddit?"title "+String(p.title||'').length+" / 300 · "+p.chars+" / 40000":p.chars+" / 3000")+"</span></div>";
|
|
2445
2548
|
h+="<details class='linotes'><summary>Its notes</summary>"+(p.notes?"<div class='linotest'>"+esc(p.notes)+"</div>":"<div class='muted'>None.</div>")+"<button type='button' class='ghost mkfile' title='open the draft in your editor'>Edit the file</button></details>";
|
|
2446
2549
|
return h;}
|
|
2447
2550
|
function mkPreview(box,rel){if(!box.innerHTML)box.innerHTML="<div class='muted'>Loading…</div>";
|
|
2448
2551
|
api('/api/marketing/draft?rel='+encodeURIComponent(rel)).then(function(p){if(MKPV!==rel)return;if(!p||p.error){box.innerHTML="<div class='note err'>"+esc((p&&p.error)||'failed')+"</div>";return;}
|
|
2449
2552
|
box.innerHTML=mkPreviewHtml(p);
|
|
2553
|
+
var lp=box.querySelector('.lipost,.rdpost');if(lp){lp.title='open it large';lp.addEventListener('click',function(ev){if(ev.target.closest('button,a,video'))return;mkBig({rel:rel},lp);});}
|
|
2450
2554
|
var mo=box.querySelector('.limore');if(mo)mo.addEventListener('click',function(){box.querySelector('.litext').innerHTML=mkTags(p.body);});
|
|
2451
2555
|
var fl=box.querySelector('.mkfile');if(fl)fl.addEventListener('click',function(){api('/api/marketing/open',{rel:rel}).then(function(r){if(r&&r.error)mkMsg('err',esc(r.error));});});
|
|
2452
2556
|
box.querySelectorAll('.mkok,.mkskip').forEach(function(b){b.addEventListener('click',function(){var skip=b.classList.contains('mkskip');b.disabled=true;
|
|
2453
2557
|
api('/api/marketing/draft/answer',{rel:rel,skip:skip}).then(function(r){if(!r||r.error){b.disabled=false;mkMsg('err',esc((r&&r.error)||'failed'));return;}mkMsg('ok',esc(r.said||'Done.'));loadMarketing();}).catch(function(e){b.disabled=false;mkMsg('err',esc(String((e&&e.message)||e)));});});});}).catch(function(e){box.innerHTML="<div class='note err'>"+esc(String((e&&e.message)||e))+"</div>";});}
|
|
2558
|
+
// A draft opened large (MKBIG): its pictures and video near full size (a click: one alone,
|
|
2559
|
+
// arrows or a swipe between them), its whole text in reading type, which product it's for
|
|
2560
|
+
// and where it goes (LinkedIn, r/ClaudeAI), and Approve, Edit and Skip on it, so nothing
|
|
2561
|
+
// needs zooming and nothing needs closing first. Esc, its X, or a click outside close it.
|
|
2562
|
+
// A lane draft ({rel}: marketing.mjs draftPreview) or a post on the board ({post}: POSTS).
|
|
2563
|
+
var MKBIG=null,MKZOOM=null;
|
|
2564
|
+
var MKPLAT={linkedin:'LinkedIn',reddit:'Reddit',x:'X',twitter:'X',instagram:'Instagram',facebook:'Facebook',threads:'Threads',mastodon:'Mastodon',bluesky:'Bluesky',youtube:'YouTube',tiktok:'TikTok'};
|
|
2565
|
+
function mkPlat(p){p=String(p||'').toLowerCase();return MKPLAT[p]||(p?p.charAt(0).toUpperCase()+p.slice(1):'');}
|
|
2566
|
+
function mkBigClose(){var o=$('mkbig');if(o)o.remove();var by=MKBIG&&MKBIG.by;MKBIG=null;if(by&&by.isConnected&&by.focus){try{by.focus({preventScroll:true});}catch(x){}}}
|
|
2567
|
+
function mkBig(what,by){MKBIG={rel:what.rel||'',post:what.post||'',by:by||document.activeElement,edit:null,say:'',cls:''};var o=$('mkbig');if(!o){o=document.createElement('div');o.id='mkbig';o.className='mkbig';o.setAttribute('role','dialog');o.setAttribute('aria-modal','true');o.setAttribute('aria-labelledby','mkbigt');document.body.appendChild(o);
|
|
2568
|
+
o.addEventListener('pointerdown',function(ev){o.downOut=ev.target===o;});o.addEventListener('click',function(ev){if(ev.target===o&&o.downOut)mkBigClose();});}
|
|
2569
|
+
o.innerHTML="<div class='mkbigin'><div class='mkbighead'><h2 id='mkbigt'>Loading…</h2><button type='button' class='mkbigx' aria-label='Close' title='Close (Esc)'>×</button></div></div>";o.querySelector('.mkbigx').addEventListener('click',mkBigClose);o.querySelector('.mkbigx').focus();
|
|
2570
|
+
mkBigLoad();}
|
|
2571
|
+
function mkBigLoad(){if(!MKBIG)return;var m=MKBIG;
|
|
2572
|
+
if(m.post){var x=((POSTS&&POSTS.posts)||[]).filter(function(p){return p.id===m.post;})[0];if(!x){mkBigRender({error:"That draft isn't waiting any more."});return;}
|
|
2573
|
+
mkBigRender({kind:'post',x:x,product:mkPostProduct(x.id),platform:'linkedin',title:((POSTS.labels||{})[x.kind])||x.kind,body:x.text,media:(x.media||[]).map(function(a){return {src:pmediaSrc(x,a),kind:a.kind==='video'?'video':'image',name:a.name||a.kind};}),chars:String(x.text||'').length,limit:3000,meta:"Drafted "+agoTxt(x.drafted)+(x.edited?", edited since":"")});return;}
|
|
2574
|
+
api('/api/marketing/draft?rel='+encodeURIComponent(m.rel)).then(function(p){if(MKBIG!==m)return;if(!p||p.error){mkBigRender({error:esc((p&&p.error)||'failed')});return;}
|
|
2575
|
+
mkBigRender({kind:'lane',p:p,product:p.product,platform:p.platform,where:p.where,flair:p.flair,handle:p.handle||'',when:p.when,title:p.title,body:p.body,media:(p.media||[]).map(function(a){return {src:mkMediaSrc(a.rel),kind:a.kind,name:a.name};}),missing:p.missing||[],chars:p.chars,limit:/reddit/i.test(p.platform||'')?40000:3000,notes:p.notes,status:p.status,final:p.status==='posted'||p.status==='superseded'});}).catch(function(e){mkBigRender({error:esc(String((e&&e.message)||e))});});}
|
|
2576
|
+
function mkBigRender(d){var o=$('mkbig');if(!o||!MKBIG)return;var m=MKBIG;m.d=d;var inn=o.querySelector('.mkbigin');
|
|
2577
|
+
if(d.error){inn.innerHTML="<div class='mkbighead'><h2 id='mkbigt'>Draft</h2><button type='button' class='mkbigx' aria-label='Close' title='Close (Esc)'>×</button></div><div class='mkbigbody textonly'><div class='note err'>"+d.error+"</div></div>";inn.querySelector('.mkbigx').addEventListener('click',mkBigClose);return;}
|
|
2578
|
+
var reddit=/reddit/i.test(d.platform||''),plat=mkPlat(d.platform)||'LinkedIn',to=reddit?esc(d.where||'Reddit'):d.where?plat+' · '+esc(d.where):plat;
|
|
2579
|
+
var head="<div class='mkbighead'>"+mkProd(d.product)+"<h2 id='mkbigt'>"+esc(reddit?(d.product?d.product+' on Reddit':'A Reddit post'):d.title||'Draft')+"</h2><span class='mkbigto' title='where it goes'>to <b>"+to+"</b>"+(d.flair?" · flair "+esc(d.flair):"")+(d.handle?" · as "+esc(d.handle):"")+(d.when?" · "+esc(d.when):"")+"</span><button type='button' class='mkbigx' aria-label='Close' title='Close (Esc)'>×</button></div>";
|
|
2580
|
+
var ms=d.media||[],i=Math.min(m.at||0,Math.max(0,ms.length-1));m.at=i;
|
|
2581
|
+
var media=ms.length?"<div class='mkbigmedia'><div class='mkbigmain'>"+(ms[i].kind==='video'?"<video src='"+escQ(ms[i].src)+"' controls playsinline preload='metadata'></video>":"<img src='"+escQ(ms[i].src)+"' alt='"+escQ(ms[i].name||'')+"' title='see it alone (arrows go through them)' tabindex='0'>")+"</div>"
|
|
2582
|
+
+(ms.length>1?"<div class='mkbigthumbs' role='tablist' aria-label='Its pictures and video'>"+ms.map(function(a,k){return "<button type='button' role='tab' data-k='"+k+"' aria-selected='"+(k===i)+"' class='"+(k===i?'on':'')+"' title='"+escQ(a.name||'')+"'>"+(a.kind==='video'?"<video src='"+escQ(a.src)+"' muted preload='metadata'></video>":"<img src='"+escQ(a.src)+"' alt=''>")+"</button>";}).join('')+"</div>":"")
|
|
2583
|
+
+((d.missing||[]).length?"<div class='mkbignone'>It also names "+d.missing.map(function(x){return "<code>"+esc(x)+"</code>";}).join(', ')+", which isn't next to it yet.</div>":"")+"</div>"
|
|
2584
|
+
:"";
|
|
2585
|
+
var editing=m.edit!=null,text="<div class='mkbigtext'>"+(!ms.length&&(d.missing||[]).length?"<div class='mkbignone' style='margin-bottom:16px'>Its picture or video, <code>"+d.missing.map(esc).join('</code>, <code>')+"</code>, isn't next to it yet, so it'd go out without it.</div>":"")+(reddit&&d.title?"<p class='mkbigtitle'><small>Its title</small>"+esc(d.title)+"</p>":"")
|
|
2586
|
+
+(editing?"<label for='mkbigedit' style='position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)'>Its text</label><textarea id='mkbigedit' maxlength='"+(d.limit||3000)+"'>"+esc(m.edit)+"</textarea>":"<div class='mkbigbodyt'>"+(reddit?mkRdText(d.body):mkTags(d.body))+"</div>")
|
|
2587
|
+
+"<div class='mkbigmeta'>"+(editing?String(m.edit).length:d.chars)+" / "+(d.limit||3000)+" characters"+(d.meta?" · "+esc(d.meta):"")+"</div>"
|
|
2588
|
+
+(d.kind==='lane'?"<details class='linotes'><summary>Its notes</summary>"+(d.notes?"<div class='linotest'>"+esc(d.notes)+"</div>":"<div class='muted'>None.</div>")+"</details>":(d.x&&d.x.sources&&d.x.sources.length?"<details class='linotes'><summary>From git: "+d.x.sources.length+" thing"+(d.x.sources.length===1?"":"s")+" it says</summary>"+d.x.sources.map(function(s){return "<div class='linotest'>"+esc(s)+"</div>";}).join('')+"</details>":""))+"</div>";
|
|
2589
|
+
var st=d.status,done=d.kind==='lane'?(d.final?(st==='posted'?"Posted: it isn't approved or posted again.":"Superseded: it won't be posted."):st==='approved'?"Approved: its agent "+(d.when?"schedules":"posts")+" it through Symbiot's browser, with this text.":st==='skipped'?"Skipped: it won't be posted.":""):"";
|
|
2590
|
+
var act=d.final?"":editing?"<button class='act mkbsave'>Save</button><button class='ghost mkbcancel'>Cancel</button>"
|
|
2591
|
+
:(st==='approved'?"":"<button class='act mkbok'>Approve"+(d.kind==='lane'?(d.when?", schedule it":", post it"):"")+"</button>")+(d.kind==='lane'?"<button class='ghost mkbfile' title='open the draft in your editor'>Edit the file</button>":"<button class='ghost mkbedit' title='change its words here'>Edit</button>")+(st==='skipped'?"":"<button class='ghost mkbskip' title='drop this draft: it won't be posted'>Skip</button>");
|
|
2592
|
+
var say=m.say||done||(d.kind==='lane'?(d.when?"Its agent schedules it for "+esc(d.when)+" once you approve.":"Its agent posts it once you approve."):"Approve it and Marketing's agent posts it on LinkedIn, as it reads here.");
|
|
2593
|
+
inn.innerHTML=head+"<div class='mkbigbody"+(media?"":" textonly")+"'>"+media+text+"</div><div class='mkbigfoot'>"+act+"<span class='mkbigsay "+(m.cls||'')+"' role='status'>"+say+"</span></div>";
|
|
2594
|
+
inn.querySelector('.mkbigx').addEventListener('click',mkBigClose);
|
|
2595
|
+
var mi=inn.querySelector('.mkbigmain img');if(mi){mi.addEventListener('click',function(){mkZoom(ms,m.at,mi);});mi.addEventListener('keydown',function(e){if(e.key==='Enter'||e.key===' '){e.preventDefault();mkZoom(ms,m.at,mi);}});}
|
|
2596
|
+
inn.querySelectorAll('.mkbigthumbs button').forEach(function(b){b.addEventListener('click',function(){m.at=+b.getAttribute('data-k');mkBigRender(d);var t=$('mkbig')&&$('mkbig').querySelector(".mkbigthumbs button[data-k='"+m.at+"']");if(t)t.focus();});});
|
|
2597
|
+
var said=function(cls,t){m.cls=cls;m.say=t;var e=inn.querySelector('.mkbigsay');if(e){e.className='mkbigsay '+cls;e.innerHTML=t;}};
|
|
2598
|
+
var busy=function(b){inn.querySelectorAll('.mkbigfoot button').forEach(function(x){x.disabled=!!b;});};
|
|
2599
|
+
inn.querySelectorAll('.mkbok,.mkbskip').forEach(function(b){b.addEventListener('click',function(){var skip=b.classList.contains('mkbskip');busy(true);said('',skip?'Skipping it…':'Approving it…');
|
|
2600
|
+
if(d.kind==='post'){var cb=document.querySelector("#boardposts .pcard[data-id='"+m.post+"'] "+(skip?'.pskip':'.papprove'));if(!cb){busy(false);said('err',"Couldn't find it on the page any more.");return;}cb.click();said('ok',skip?"Skipped: it won't be posted.":"Approved: Marketing's agent posts it on LinkedIn, as it reads here.");inn.querySelectorAll('.mkbok,.mkbskip,.mkbedit').forEach(function(x){x.remove();});return;}
|
|
2601
|
+
api('/api/marketing/draft/answer',{rel:m.rel,skip:skip}).then(function(r){busy(false);if(!r||r.error){said('err',esc((r&&r.error)||'failed'));return;}m.say=esc(r.said||'Done.');m.cls='ok';loadMarketing();mkBigLoad();}).catch(function(e){busy(false);said('err',esc(String((e&&e.message)||e)));});});});
|
|
2602
|
+
var fb=inn.querySelector('.mkbfile');if(fb)fb.addEventListener('click',function(){api('/api/marketing/open',{rel:m.rel}).then(function(r){said(r&&r.error?'err':'ok',r&&r.error?esc(r.error):"Opened in your editor. Save it there, and this shows the change when you open it again.");});});
|
|
2603
|
+
var eb=inn.querySelector('.mkbedit');if(eb)eb.addEventListener('click',function(){m.edit=d.body||'';m.say='';mkBigRender(d);var t=$('mkbigedit');if(t)t.focus();});
|
|
2604
|
+
var ta=inn.querySelector('#mkbigedit');if(ta)ta.addEventListener('input',function(){m.edit=ta.value;var mt=inn.querySelector('.mkbigmeta');if(mt)mt.firstChild.nodeValue=ta.value.length+" / "+(d.limit||3000)+" characters";});
|
|
2605
|
+
var cc=inn.querySelector('.mkbcancel');if(cc)cc.addEventListener('click',function(){m.edit=null;mkBigRender(d);});
|
|
2606
|
+
var sv=inn.querySelector('.mkbsave');if(sv)sv.addEventListener('click',function(){PEDIT[m.post]=m.edit;renderPosts();var cb=document.querySelector("#boardposts .pcard[data-id='"+m.post+"'] .psave");if(!cb){said('err',"Couldn't find it on the page any more.");return;}cb.click();m.edit=null;m.say="Saved.";m.cls='ok';setTimeout(mkBigLoad,900);});}
|
|
2607
|
+
// One picture (or video) alone, as big as the window: arrows, the buttons or a swipe go
|
|
2608
|
+
// through the rest of them; Esc, its X or a click beside it close it.
|
|
2609
|
+
function mkZoom(list,i,by){if(!list||!list.length)return;mkZoomClose();var z=document.createElement('div');z.className='mkzoom';z.setAttribute('role','dialog');z.setAttribute('aria-modal','true');z.setAttribute('aria-label','Picture');document.body.appendChild(z);MKZOOM={list:list,i:Math.max(0,Math.min(i||0,list.length-1)),el:z,by:by||document.activeElement};
|
|
2610
|
+
var sx=null;z.addEventListener('pointerdown',function(e){sx=e.clientX;z.downOut=e.target===z;});z.addEventListener('pointerup',function(e){if(sx==null)return;var dx=e.clientX-sx;sx=null;if(Math.abs(dx)>50&&MKZOOM&&MKZOOM.list.length>1){mkZoomGo(dx<0?1:-1);z.swiped=true;}});
|
|
2611
|
+
z.addEventListener('click',function(e){if(z.swiped){z.swiped=false;return;}if(e.target===z&&z.downOut)mkZoomClose();});mkZoomShow();}
|
|
2612
|
+
function mkZoomShow(){var Z=MKZOOM;if(!Z)return;var a=Z.list[Z.i],n=Z.list.length,z=Z.el;
|
|
2613
|
+
z.innerHTML=(a.kind==='video'?"<video src='"+escQ(a.src)+"' controls autoplay playsinline></video>":"<img src='"+escQ(a.src)+"' alt='"+escQ(a.name||'')+"' draggable='false'>")+"<button type='button' class='mkzx' aria-label='Close' title='Close (Esc)'>×</button>"
|
|
2614
|
+
+(n>1?"<button type='button' class='mkzp' aria-label='Previous' title='Previous (←)'>‹</button><button type='button' class='mkzn' aria-label='Next' title='Next (→)'>›</button>":"")+"<div class='mkzc'>"+(n>1?(Z.i+1)+" of "+n+(a.name?" · ":""):"")+esc(a.name||'')+"</div>";
|
|
2615
|
+
z.querySelector('.mkzx').addEventListener('click',mkZoomClose);var p=z.querySelector('.mkzp'),q=z.querySelector('.mkzn');if(p)p.addEventListener('click',function(){mkZoomGo(-1);});if(q)q.addEventListener('click',function(){mkZoomGo(1);});(q||z.querySelector('.mkzx')).focus();}
|
|
2616
|
+
function mkZoomGo(d){var Z=MKZOOM;if(!Z)return;Z.i=(Z.i+d+Z.list.length)%Z.list.length;mkZoomShow();if(MKBIG&&MKBIG.d&&Z.by&&Z.by.closest&&Z.by.closest('#mkbig'))MKBIG.at=Z.i;}
|
|
2617
|
+
function mkZoomClose(){var Z=MKZOOM;MKZOOM=null;if(!Z)return;Z.el.remove();if(MKBIG&&MKBIG.d&&Z.by&&Z.by.closest&&Z.by.closest('#mkbig')){mkBigRender(MKBIG.d);var mi=$('mkbig')&&$('mkbig').querySelector('.mkbigmain img');if(mi)mi.focus();}else if(Z.by&&Z.by.isConnected&&Z.by.focus){try{Z.by.focus({preventScroll:true});}catch(x){}}}
|
|
2618
|
+
// Esc closes the picture first, then the draft; arrows go through the pictures
|
|
2619
|
+
if(document.addEventListener)document.addEventListener('keydown',function(e){if(MKZOOM){if(e.key==='Escape'){e.preventDefault();e.stopPropagation();mkZoomClose();}else if(e.key==='ArrowRight'||e.key==='ArrowLeft'){e.preventDefault();mkZoomGo(e.key==='ArrowRight'?1:-1);}return;}
|
|
2620
|
+
if(MKBIG){if(e.key==='Escape'){e.preventDefault();e.stopPropagation();mkBigClose();return;}var d=MKBIG.d,t=e.target;if(d&&(d.media||[]).length>1&&(e.key==='ArrowRight'||e.key==='ArrowLeft')&&!(t&&t.closest&&t.closest('textarea,input,select,video'))){e.preventDefault();MKBIG.at=((MKBIG.at||0)+(e.key==='ArrowRight'?1:-1)+d.media.length)%d.media.length;mkBigRender(d);}}},true);
|
|
2454
2621
|
function renderMkLane(){var L=(MKT&&MKT.lane)||null,nd=$('mkneeds'),el=$('mktasks');if(!nd||!el)return;if(!L){nd.innerHTML='';el.innerHTML='';return;}
|
|
2455
2622
|
var ns=L.needs||[],lbl={ask:'Answer',approve:'Review',draft:'Show it',post:'Preview'},kind={ask:'its agent asks',approve:'waits for your OK',draft:'a draft to post',post:'a post to approve'};
|
|
2456
|
-
nd.innerHTML=ns.length?"<div class='mkneeds"+(MKLIT?" lit":"")+"'><div class='pshead'><span>Needs you · "+ns.length+"</span><small>only you can do these</small></div>"+ns.map(function(n,i){return "<div class='task mkneed' data-i='"+i+"'><span class='t'>"+mkProd(n.product)+esc(n.text)+" <span class='muted' style='font-size:11px'>"+kind[n.kind]+"</span></span><button class='"+(i?'ghost':'act')+" mkact'>"+lbl[n.kind]+"</button></div>";}).join('')+"</div>":'';
|
|
2623
|
+
nd.innerHTML=ns.length?"<div class='mkneeds"+(MKLIT?" lit":"")+"'><div class='pshead'><span>Needs you · "+ns.length+"</span><small>only you can do these</small></div>"+ns.map(function(n,i){return "<div class='task mkneed' data-i='"+i+"'"+(n.kind==='post'?" data-rel='"+escQ(n.rel)+"' title='open it large'":"")+"><span class='t'>"+mkProd(n.product)+esc(n.text)+" <span class='muted' style='font-size:11px'>"+kind[n.kind]+"</span></span><button class='"+(i?'ghost':'act')+" mkact'>"+lbl[n.kind]+"</button></div>";}).join('')+"</div>":'';
|
|
2457
2624
|
nd.querySelectorAll('.mkact').forEach(function(b){b.addEventListener('click',function(){var n=ns[+b.closest('.mkneed').getAttribute('data-i')];if(!n)return;
|
|
2458
|
-
if(n.kind==='post'){
|
|
2625
|
+
if(n.kind==='post'){mkBig({rel:n.rel},b);return;}
|
|
2459
2626
|
if(n.kind==='draft'){var c=document.querySelector("#boardposts .pcard[data-id='"+n.id.slice(6)+"']");if(c){document.querySelectorAll('#panel-marketing .lit').forEach(function(x){if(x!==c)x.classList.remove('lit');});c.classList.add('lit');if(c.scrollIntoView)c.scrollIntoView({block:'center',behavior:'smooth'});}return;}
|
|
2460
2627
|
lqFocus({kind:'proj',repo:'marketing'});});});
|
|
2628
|
+
nd.querySelectorAll('.mkneed[data-rel]').forEach(function(r){r.addEventListener('click',function(ev){if(ev.target.closest('button,a,input,select,textarea'))return;mkBig({rel:r.getAttribute('data-rel')},r);});});
|
|
2461
2629
|
var prods=L.products||[],ln=L.lane||[],els=L.elsewhere||[],fs=L.files||[],st={waiting:'waiting',working:'its agent is at work',review:'done, waits for your OK'};
|
|
2462
2630
|
var h="<div class='pshead'><span>Marketing lane</span><small>"+(ln.length?ln.length+" task"+(ln.length>1?"s":"")+(L.working?" · its agent is at work":L.waiting?" · "+L.waiting+" waiting for its agent":""):"Its own agent, for every product: tell it what to make, and say which product it's for.")+"</small></div>"
|
|
2463
2631
|
+"<form class='row mkadd'><label class='mkl' for='mkprod'>Product</label><input id='mkprod' class='mkprod' list='mkprodlist' autocomplete='off' placeholder='Symbiot, Dailify…' maxlength='40'><datalist id='mkprodlist'>"+prods.map(function(p){return "<option value='"+escQ(p)+"'>";}).join('')+"</datalist><label class='mkl' for='mktext' style='position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)'>Task</label><input id='mktext' class='mktext' autocomplete='off' placeholder='A launch post, a demo video, a campaign…' maxlength='4000'><button type='submit' class='act'>Add</button></form>"
|
|
@@ -2472,6 +2640,7 @@ api('/api/marketing/task',{text:t.value,product:pr.value}).then(function(r){bt.d
|
|
|
2472
2640
|
el.querySelectorAll('.mkopen').forEach(function(b){b.addEventListener('click',function(){TFILTER.repo=b.getAttribute('data-repo')||'';TFILTER.type='';lqPool('tasks');if(typeof loadTasks==='function')loadTasks();});});
|
|
2473
2641
|
el.querySelectorAll('.mkmove').forEach(function(b){b.addEventListener('click',function(){var id=b.closest('.task').getAttribute('data-id');b.disabled=true;api('/api/marketing/move',{id:id}).then(function(r){if(!r||r.error){b.disabled=false;mkMsg('err',esc((r&&r.error)||'failed'));return;}mkMsg('ok',"Moved to Marketing: its agent takes it from now on.");loadMarketing();});});});
|
|
2474
2642
|
el.querySelectorAll('.mkprev').forEach(function(b){b.addEventListener('click',function(){var rel=b.closest('.task').getAttribute('data-rel');MKPV=MKPV===rel?'':rel;renderMkLane();});});
|
|
2643
|
+
el.querySelectorAll('.task[data-rel]').forEach(function(r){r.title='open it large';r.addEventListener('click',function(ev){if(ev.target.closest('button,a,input,select,textarea'))return;mkBig({rel:r.getAttribute('data-rel')},r);});});
|
|
2475
2644
|
if(MKPV){var pv=null;el.querySelectorAll('.mkpv').forEach(function(x){if(x.getAttribute('data-rel')===MKPV)pv=x;});if(pv)mkPreview(pv,MKPV);else MKPV='';}
|
|
2476
2645
|
var g=el.querySelector('.mkgo');if(g)g.addEventListener('click',function(){g.disabled=true;api('/api/marketing/go',{}).then(function(r){if(!r||r.error){g.disabled=false;mkMsg('err',esc((r&&r.error)||'It didn't start.'));return;}mkMsg('ok',r.queued?"Its agent takes them once the run there now finishes.":"Marketing's agent started. It's on the Workdesk, and what it drafts waits here for your OK.");loadMarketing();}).catch(function(e){g.disabled=false;mkMsg('err',esc(String((e&&e.message)||e)));});});
|
|
2477
2646
|
if(MKLIT){MKLIT=false;var lit=nd.querySelector('.mkneeds');if(lit&&lit.scrollIntoView)lit.scrollIntoView({block:'start'});}}
|
|
@@ -2481,11 +2650,17 @@ if(MKLIT){MKLIT=false;var lit=nd.querySelector('.mkneeds');if(lit&&lit.scrollInt
|
|
|
2481
2650
|
// once) and the capture is blurred again. Use puts it on a post (its media: line) in one
|
|
2482
2651
|
// click: the posts it was taken for each have a button, any other is in the list.
|
|
2483
2652
|
var MKTRAY=null;
|
|
2484
|
-
function loadMkTray(){api('/api/marketing/tray').then(function(d){if(d&&!d.error){MKTRAY=d.trays||[];renderMkTray();}});}
|
|
2653
|
+
function loadMkTray(){api('/api/marketing/tray').then(function(d){if(d&&!d.error){MKTRAY=d.trays||[];MKPRODUCTS=d.products||[];renderMkTray();}});}
|
|
2485
2654
|
function mkTraySrc(rel,at){return '/api/marketing/tray/media?rel='+encodeURIComponent(rel)+'&v='+Math.round(at||0)+'&t='+encodeURIComponent(T);}
|
|
2486
2655
|
// a capture's boxes, one a place: a clip has the same box in each moment it shows there
|
|
2487
2656
|
function mkBoxes(c){var g={},out=[];(c.regions||[]).forEach(function(r){var k=[r.x,r.y,r.w,r.h].join();if(!g[k]){g[k]={x:r.x,y:r.y,w:r.w,h:r.h,kind:r.kind,ids:[],on:0};out.push(g[k]);}g[k].ids.push(r.id);if(r.on)g[k].on++;});out.forEach(function(b){b.all=b.on===b.ids.length;});return out;}
|
|
2488
2657
|
var MKKIND={still:'Picture',clip:'Clip',reel:'Reel'};
|
|
2658
|
+
// Capture a product: Symbiot takes its key screens itself (capture.mjs), blurred before they reach the tray
|
|
2659
|
+
var MKPRODUCTS=[],MKCAPBUSY=false,MKCAP={sel:0,url:'',cls:'',say:''}; // kept through a redraw
|
|
2660
|
+
function mkCapRow(){
|
|
2661
|
+
return "<div class='pshead'><span>Capture a product</span><small>Symbiot opens it in a browser of its own, takes its key screens as pictures and short clips, and blurs anything private before it reaches the tray: names, emails, numbers, keys, what's typed in, chats, faces and pictures. No demo video needed.</small></div>"
|
|
2662
|
+
+"<div class='mkcaprow'>"+(MKPRODUCTS.length?"<select id='mkcapp' aria-label='Product to capture'>"+MKPRODUCTS.map(function(p,i){return "<option value='"+i+"'"+(i===MKCAP.sel?" selected":"")+">"+esc(p.name)+(p.tray?" (has a tray)":"")+"</option>";}).join('')+"</select>":"<input id='mkcapname' placeholder='Its name' aria-label='Product to capture' autocomplete='off' value='"+escQ(MKCAP.name||'')+"' style='flex:0 1 160px'>")
|
|
2663
|
+
+"<input id='mkcapurl' placeholder='Its address, if not running here' title='Its address, if it isnplaceholder='Its address, if it isn't running here (http://localhost:3000)' aria-label='Its address'#39;t running here: http://localhost:3000, or where itplaceholder='Its address, if it isn't running here (http://localhost:3000)' aria-label='Its address'#39;s deployed' aria-label='Its address' autocomplete='off' value='"+escQ(MKCAP.url)+"'><button type='button' class='act' id='mkcapgo'"+(MKCAPBUSY?" disabled":"")+">"+(MKCAPBUSY?"Capturing…":"Capture")+"</button><div class='mkcapst"+(MKCAP.cls?" "+MKCAP.cls:"")+"' id='mkcapst' role='status'>"+esc(MKCAP.say)+"</div></div>";}
|
|
2489
2664
|
function renderMkTray(){var el=$('mktray');if(!el)return;var ts=MKTRAY||[];
|
|
2490
2665
|
var h=ts.map(function(t,ti){var open=(t.drafts||[]).filter(function(d){return d.status!=='posted'&&d.status!=='superseded';});
|
|
2491
2666
|
return "<div class='pshead'><span>Pick tray · "+esc(t.product)+"</span><small>"+t.items.length+" capture"+(t.items.length===1?"":"s")+", blurred where "+(t.items.length===1?"it shows":"they show")+" something private. Click a box to blur it or show it; Use puts one on a post.</small></div><div class='mktray'>"
|
|
@@ -2493,9 +2668,14 @@ return "<div class='pshead'><span>Pick tray · "+esc(t.product)+"</span><s
|
|
|
2493
2668
|
var shot=c.show?(c.showKind==='video'?"<video src='"+escQ(mkTraySrc(c.show,c.at))+"' muted loop playsinline controls preload='metadata'></video>":"<img src='"+escQ(mkTraySrc(c.show,c.at))+"' alt='"+escQ(c.caption||c.name)+"' loading='lazy'>"):"<div class='muted' style='padding:20px;line-height:1.4'>Not rendered yet.</div>";
|
|
2494
2669
|
shot+=bx.map(function(b,bi){var p=function(v){return Math.round(v*10000)/100+'%';};return "<button type='button' class='mkbox"+(b.all?" on":"")+"' data-b='"+bi+"' aria-pressed='"+b.all+"' style='left:"+p(b.x)+";top:"+p(b.y)+";width:"+p(b.w)+";height:"+p(b.h)+"' title='"+escQ(b.kind+(b.all?": blurred. Click to show it":": shown. Click to blur it"))+"' aria-label='"+escQ(b.kind+(b.all?", blurred":", shown"))+"'></button>";}).join('');
|
|
2495
2670
|
var fr=(c.for||[]).map(function(f){return t.folder+'/'+f;}),mine=open.filter(function(d){return fr.indexOf(d.rel)>=0;}),rest=open.filter(function(d){return fr.indexOf(d.rel)<0;});
|
|
2496
|
-
var
|
|
2671
|
+
var look=c.review?"<div class='mkreview'><span>"+esc(c.review)+"</span><button type='button' class='ghost mklooked'>Looked it over</button></div>":"";
|
|
2672
|
+
var use=c.review?"":c.use?mine.map(function(d){var di=t.drafts.indexOf(d);return (d.media||[]).indexOf(c.use)>=0?"<span class='mkst approved' title='its picture or video now'>on "+esc(d.name)+"</span>":"<button type='button' class='ghost mkuseb' data-d='"+di+"'>Use for "+esc(d.name)+"</button>";}).join('')
|
|
2497
2673
|
+(rest.length?"<select class='mkusel' aria-label='Use it for another post'><option value=''>"+(mine.length?"Another post":"Use for a post")+"…</option>"+rest.map(function(d){return "<option value='"+t.drafts.indexOf(d)+"'"+((d.media||[]).indexOf(c.use)>=0?" disabled":"")+">"+esc(d.name)+((d.media||[]).indexOf(c.use)>=0?" (on it)":"")+"</option>";}).join('')+"</select>":""):"";
|
|
2498
|
-
|
|
2674
|
+
var canDraw=c.blur&&c.kind!=='reel'&&c.show,big=c.show?"<button type='button' class='ghost mkbigb' title='see it large: arrows go through the tray'>Open large</button>":"";
|
|
2675
|
+
return "<div class='mkcap' data-t='"+ti+"' data-c='"+ci+"'><div class='mkshot'>"+shot+(canDraw?"<div class='mkdrawpad' aria-hidden='true'></div>":"")+"</div><div class='mkcapt'>"+esc(c.caption||c.name)+"</div><div class='mkcapm'>"+esc(MKKIND[c.kind]||c.kind)+(c.screen?" · "+esc(c.screen):"")+(bx.length?" · "+n+" of "+bx.length+" box"+(bx.length===1?"":"es")+" blurred":c.kind==='reel'?" · its clips' blur":" · nothing to blur")+"</div>"+(bx.length?"<div class='mkchips'>"+bx.map(function(b,bi){var hand=(c.regions||[]).filter(function(r){return r.hand&&[r.x,r.y,r.w,r.h].join()===[b.x,b.y,b.w,b.h].join();})[0];
|
|
2676
|
+
return "<span style='display:inline-flex;align-items:center'><button type='button' class='mkchip"+(b.all?" on":"")+"' data-b='"+bi+"' aria-pressed='"+b.all+"' title='"+escQ(b.all?"blurred: click to show it":"shown: click to blur it")+"'>"+esc(b.kind)+(b.all?" · blurred":" · shown")+"</button>"+(hand?"<button type='button' class='mkchipx' data-id='"+escQ(String(hand.id))+"' title='Remove this box you drew' aria-label='Remove this box you drew'>×</button>":"")+"</span>";}).join('')+"</div>":"")
|
|
2677
|
+
+look+(use?"<div class='mkuse'>"+use+"</div>":"")+(canDraw||big?"<div class='mkuse'>"+big+(canDraw?"<button type='button' class='ghost mkdraw' aria-pressed='false' title='Drag across the capture to blur that part too'>Draw a blur box</button><span class='mkcapst2' role='status'></span>":"")+"</div>":"")+"</div>";}).join('')+"</div>";}).join('');
|
|
2678
|
+
h=mkCapRow()+h;
|
|
2499
2679
|
if(sameHtml(el,h))return;
|
|
2500
2680
|
var at=function(x){var c=x.closest('.mkcap');return {t:ts[+c.getAttribute('data-t')],c:ts[+c.getAttribute('data-t')].items[+c.getAttribute('data-c')],el:c};};
|
|
2501
2681
|
el.querySelectorAll('.mkchip').forEach(function(c){var box=function(){return c.closest('.mkcap').querySelector(".mkbox[data-b='"+c.getAttribute('data-b')+"']");};c.addEventListener('mouseenter',function(){var x=box();if(x)x.classList.add('hl');});c.addEventListener('mouseleave',function(){var x=box();if(x)x.classList.remove('hl');});});
|
|
@@ -2505,7 +2685,37 @@ mkMsg(r.rendered?'ok':'err',r.rendered?(g.all?"Shown: that box isn't blurred
|
|
|
2505
2685
|
var useIt=function(x,d){var a=at(x),dr=a.t.drafts[d];if(!dr)return;x.disabled=true;
|
|
2506
2686
|
api('/api/marketing/tray/use',{rel:dr.rel,file:a.c.use}).then(function(r){x.disabled=false;if(!r||r.error){mkMsg('err',esc((r&&r.error)||'failed'));return;}mkMsg('ok',"“"+esc(dr.name)+"” now shows "+esc(a.c.caption||a.c.name)+"."+(r.reopened?" It was approved with another, so it asks for your OK again.":""));loadMarketing();}).catch(function(e){x.disabled=false;mkMsg('err',esc(String((e&&e.message)||e)));});};
|
|
2507
2687
|
el.querySelectorAll('.mkuseb').forEach(function(b){b.addEventListener('click',function(){useIt(b,+b.getAttribute('data-d'));});});
|
|
2508
|
-
|
|
2688
|
+
// a capture large, alone: a click on its picture (not on a box), or Open large; arrows go through its tray
|
|
2689
|
+
var zoomAt=function(x){var a=at(x),shown=a.t.items.filter(function(c){return c.show;});mkZoom(shown.map(function(c){return {src:mkTraySrc(c.show,c.at),kind:c.showKind==='video'?'video':'image',name:c.caption||c.name};}),Math.max(0,shown.indexOf(a.c)),x);};
|
|
2690
|
+
el.querySelectorAll('.mkshot img').forEach(function(im){im.addEventListener('click',function(){if(!im.closest('.mkcap').classList.contains('drawing'))zoomAt(im);});});
|
|
2691
|
+
el.querySelectorAll('.mkbigb').forEach(function(b){b.addEventListener('click',function(){zoomAt(b);});});
|
|
2692
|
+
el.querySelectorAll('.mkusel').forEach(function(s){s.addEventListener('change',function(){if(s.value!=='')useIt(s,+s.value);});});
|
|
2693
|
+
// looked over: then it can go on a post
|
|
2694
|
+
el.querySelectorAll('.mklooked').forEach(function(b){b.addEventListener('click',function(){var a=at(b);b.disabled=true;
|
|
2695
|
+
api('/api/marketing/tray/reviewed',{tray:a.t.rel,name:a.c.name}).then(function(r){b.disabled=false;if(!r||r.error){mkMsg('err',esc((r&&r.error)||'failed'));return;}loadMkTray();}).catch(function(){b.disabled=false;});});});
|
|
2696
|
+
// a box you drew, removed
|
|
2697
|
+
el.querySelectorAll('.mkchipx').forEach(function(b){b.addEventListener('click',function(){var a=at(b);if(a.el.classList.contains('busy'))return;a.el.classList.add('busy');
|
|
2698
|
+
api('/api/marketing/tray/box',{tray:a.t.rel,name:a.c.name,remove:b.getAttribute('data-id')}).then(function(r){a.el.classList.remove('busy');if(!r||r.error){mkMsg('err',esc((r&&r.error)||'failed'));return;}loadMkTray();}).catch(function(){a.el.classList.remove('busy');});});});
|
|
2699
|
+
// Draw a blur box: drag across the capture; the box is blurred in it (a clip: all the way through)
|
|
2700
|
+
el.querySelectorAll('.mkdraw').forEach(function(b){b.addEventListener('click',function(){var card=b.closest('.mkcap'),on=!card.classList.contains('drawing');card.classList.toggle('drawing',on);b.setAttribute('aria-pressed',on?'true':'false');b.textContent=on?'Cancel drawing':'Draw a blur box';
|
|
2701
|
+
var st=card.querySelector('.mkcapst2');if(st){st.className='mkcapst2';st.textContent=on?'Drag across what to blur.':'';}});});
|
|
2702
|
+
el.querySelectorAll('.mkdrawpad').forEach(function(pad){var card=pad.closest('.mkcap'),shot=card.querySelector('.mkshot'),st=card.querySelector('.mkcapst2'),start=null,box=null;
|
|
2703
|
+
var pt=function(e){var r=shot.getBoundingClientRect();return {x:Math.min(1,Math.max(0,(e.clientX-r.left)/r.width)),y:Math.min(1,Math.max(0,(e.clientY-r.top)/r.height))};};
|
|
2704
|
+
var place=function(a,c){var x=Math.min(a.x,c.x),y=Math.min(a.y,c.y),w=Math.abs(a.x-c.x),hh=Math.abs(a.y-c.y);box.style.left=(x*100)+'%';box.style.top=(y*100)+'%';box.style.width=(w*100)+'%';box.style.height=(hh*100)+'%';return {x:x,y:y,w:w,h:hh};};
|
|
2705
|
+
pad.addEventListener('pointerdown',function(e){e.preventDefault();start=pt(e);box=document.createElement('div');box.className='mkdrawn';shot.appendChild(box);place(start,start);try{pad.setPointerCapture(e.pointerId);}catch(x){}});
|
|
2706
|
+
pad.addEventListener('pointermove',function(e){if(start)place(start,pt(e));});
|
|
2707
|
+
pad.addEventListener('pointerup',function(e){if(!start)return;var bx=place(start,pt(e));start=null;var a=at(pad);
|
|
2708
|
+
if(bx.w<0.01||bx.h<0.01){box.remove();if(st){st.className='mkcapst2 err';st.textContent='Drag across what to blur: that was a click.';}return;}
|
|
2709
|
+
card.classList.add('busy');if(st){st.className='mkcapst2';st.textContent='Blurring it…';}
|
|
2710
|
+
api('/api/marketing/tray/box',{tray:a.t.rel,name:a.c.name,box:bx}).then(function(r){card.classList.remove('busy');if(!r||r.error){box.remove();if(st){st.className='mkcapst2 err';st.textContent=(r&&r.error)||'failed';}return;}
|
|
2711
|
+
mkMsg(r.rendered?'ok':'err',r.rendered?"Blurred: the box you drew is blurred in it now.":"Saved, but it isn't blurred in it yet: "+esc(r.note||'it couldn't be blurred'));card.classList.remove('drawing');loadMkTray();}).catch(function(x){card.classList.remove('busy');box.remove();if(st){st.className='mkcapst2 err';st.textContent=String((x&&x.message)||x);}});});});
|
|
2712
|
+
// Capture: one product at a time, a minute or two; what it says shows under the button
|
|
2713
|
+
var cs=$('mkcapp'),cu=$('mkcapurl'),cn=$('mkcapname');if(cs)cs.addEventListener('change',function(){MKCAP.sel=+cs.value;});if(cn)cn.addEventListener('input',function(){MKCAP.name=cn.value;});if(cu)cu.addEventListener('input',function(){MKCAP.url=cu.value;});
|
|
2714
|
+
var say=function(cls,t){MKCAP.cls=cls;MKCAP.say=t;var st=$('mkcapst');if(st){st.className='mkcapst'+(cls?' '+cls:'');st.textContent=t;}};
|
|
2715
|
+
var go=$('mkcapgo');if(go)go.addEventListener('click',function(){var p=cs?MKPRODUCTS[+cs.value]:{name:(cn&&cn.value||'').trim()};if(MKCAPBUSY)return;if(!p||!p.name){say('err','Say which product: its name.');return;}MKCAPBUSY=true;go.disabled=true;go.textContent='Capturing…';say('','Capturing '+p.name+': its key screens, a picture each and a few short clips, blurred before they reach the tray. A minute or two.');
|
|
2716
|
+
api('/api/marketing/capture',{product:p.name,url:($('mkcapurl').value||'').trim()}).then(function(r){MKCAPBUSY=false;var g=$('mkcapgo');if(g){g.disabled=false;g.textContent='Capture';}
|
|
2717
|
+
if(!r||r.error){say('err',(r&&r.error)||'It didn’t capture.');return;}
|
|
2718
|
+
say('ok',r.note+(r.skipped||[]).map(function(x){return x.clip?' No clip of '+x.path+': '+x.why+'.':' Skipped '+x.path+': '+x.why+'.';}).join('')+(r.seed?' It has demo data ('+r.seed+'): capture with that loaded, not real data.':''));loadMkTray();}).catch(function(x){MKCAPBUSY=false;var g=$('mkcapgo');if(g){g.disabled=false;g.textContent='Capture';}say('err',String((x&&x.message)||x));});});}
|
|
2509
2719
|
var MKDAY=function(s){var d=new Date(s+'T00:00:00');return isNaN(d)?s:d.toLocaleDateString(undefined,{day:'numeric',month:'short'});};
|
|
2510
2720
|
function renderMarketing(){var d=MKT,r=$('mkreplies'),t=$('mktest');if(!d||!r||!t)return;var rs=d.replies||[];
|
|
2511
2721
|
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>"
|
|
@@ -2562,14 +2772,20 @@ if(!REPS.length){el.innerHTML="<div class='muted' style='margin-top:12px'>No rep
|
|
|
2562
2772
|
var n=REPS.filter(function(r){return r.new;}).length;
|
|
2563
2773
|
el.innerHTML="<div class='tgroup' style='display:flex;align-items:center;gap:8px"+(n?";color:var(--amber)":"")+"'>"+(n?n+' unread':'All read')+" <span class='tcount'>of "+REPS.length+"</span>"+(n?"<button class='ghost' id='reportsseen' style='margin-left:auto;padding:3px 9px;font-size:12px;letter-spacing:0;text-transform:none'>Mark all read</button>":"")+"</div>"+
|
|
2564
2774
|
REPS.map(function(r,i){var where=!r.run||r.run===r.lane?esc(r.lane):r.run.indexOf(r.lane+':')===0?esc(r.run):esc(r.lane)+' · '+esc(r.run);
|
|
2565
|
-
return "<div class='task rrow"+(r.new?" rnew":"")+"' data-i='"+i+"' tabindex='0' role='button' title='"+escQ(r.file)+"'><span class='t'><b>"+esc(r.title)+"</b><div class='muted' style='font-size:12px'>"+(r.new?"<span style='color:var(--amber)'>unread</span> · ":"")+where+" · "+esc(r.name)+" · "+kb(r.size)+(r.running?" · <span style='color:var(--amber)'>its agent is still writing</span>":"")+"</div></span><span class='rp'>"+agoTxt(r.mtime)+"</span></div>";}).join('');
|
|
2775
|
+
return "<div class='task rrow"+(r.new?" rnew":"")+"' data-i='"+i+"' tabindex='0' role='button' title='"+escQ(r.file)+"'><span class='t'><b>"+esc(r.title)+"</b><div class='muted' style='font-size:12px'>"+(r.new?"<span style='color:var(--amber)'>unread</span> · ":"")+where+" · "+esc(r.name)+" · "+kb(r.size)+(r.computer?" · from "+esc(r.computer):"")+(r.running?" · <span style='color:var(--amber)'>its agent is still writing</span>":"")+"</div></span><span class='rp'>"+agoTxt(r.mtime)+"</span></div>";}).join('');
|
|
2566
2776
|
el.querySelectorAll('.rrow').forEach(function(row){var go=function(){openReport(REPS[+row.getAttribute('data-i')]);};row.addEventListener('click',go);row.addEventListener('keydown',function(ev){if(ev.key==='Enter'||ev.key===' '){ev.preventDefault();go();}});});
|
|
2567
2777
|
var s=$('reportsseen');if(s)s.addEventListener('click',function(){s.disabled=true;api('/api/reports/seen',{}).then(function(){loadReports();lqLoad(false);});});}
|
|
2568
|
-
|
|
2569
|
-
|
|
2778
|
+
// The way back sits at the top of the report itself (#reportview), so it stays in view, under
|
|
2779
|
+
// the page's header, however long the report: on a long one the list was a long scroll up away.
|
|
2780
|
+
function openReport(r){if(!r)return;REPOPEN=r.id;var w=$('reportview');$('reportslist').innerHTML='';
|
|
2781
|
+
w.innerHTML="<div class='rbackbar' id='rbackbar'><button class='ghost' id='reportback'>← All reports</button><span class='muted rbpath' title='"+escQ(r.file)+"'><bdi>"+esc(r.file)+"</bdi></span></div><div id='reportbody'><div class='out rdoc muted'>Opening…</div></div>";var v=$('reportbody');
|
|
2782
|
+
$('reportback').addEventListener('click',function(){loadReports();renderReports();});repStick();
|
|
2570
2783
|
api('/api/reports/read?id='+encodeURIComponent(r.id)).then(function(d){if(REPOPEN!==r.id)return;if(!d||d.error){v.innerHTML="<div class='note err'>"+esc((d&&d.error)||'failed')+"</div>";return;}
|
|
2571
|
-
v.innerHTML="<div class='out rdoc'>"+d.html+"</div>"+repActsHtml(d);repImgs(v);repActsWire(v,d);var m=document.querySelector('main');if(m&&m.scrollTo)m.scrollTo(0,0);lqLoad(false);}).catch(function(e){v.innerHTML="<div class='note err'>"+esc(String((e&&e.message)||e))+"</div>";});}
|
|
2784
|
+
v.innerHTML="<div class='out rdoc'>"+d.html+"</div>"+(d.remote?"":repActsHtml(d));repImgs(v);if(!d.remote)repActsWire(v,d);repStick();var m=document.querySelector('main');if(m&&m.scrollTo)m.scrollTo(0,0);lqLoad(false);}).catch(function(e){v.innerHTML="<div class='note err'>"+esc(String((e&&e.message)||e))+"</div>";});}
|
|
2572
2785
|
$('reportsrefresh').addEventListener('click',function(){REPOPEN='';loadReports();});
|
|
2786
|
+
// where the way back sticks: just under the page's own sticky header (#lqsinkrow), whatever its height
|
|
2787
|
+
function repStick(){var b=$('rbackbar');if(!b)return;var s=$('lqsinkrow'),cs=s&&getComputedStyle(s),top=cs&&cs.position==='sticky'&&cs.display!=='none'?(parseFloat(cs.top)||0)+s.offsetHeight-1:0;b.style.setProperty('--rbtop',top+'px');}
|
|
2788
|
+
if(window.addEventListener)window.addEventListener('resize',repStick);if(window.ResizeObserver&&$('lqsinkrow'))new ResizeObserver(repStick).observe($('lqsinkrow'));
|
|
2573
2789
|
// Its end: 2-4 ideas drawn from it, ticked straight onto the Workdesk in its lane, and a box
|
|
2574
2790
|
// to ask about it here (home.mjs reportIdeasAdd, reportAsk); a draft gets Approve / Reject.
|
|
2575
2791
|
// A report's images: their src carries the token; one that won't load says so; a click enlarges it.
|
|
@@ -2804,7 +3020,7 @@ document.getElementById('weeklyhour').addEventListener('change',saveWeekly);
|
|
|
2804
3020
|
document.getElementById('weeklynow').addEventListener('click',weeklyNow);
|
|
2805
3021
|
document.getElementById('autostart').addEventListener('change',saveAutostart);
|
|
2806
3022
|
(function(){var h='';for(var i=0;i<24;i++)h+="<option value='"+i+"'>"+(i<10?'0':'')+i+":00</option>";document.getElementById('weeklyhour').innerHTML=h;})();
|
|
2807
|
-
var SRV_STARTED=null,srvDown=false,updBusy=false;
|
|
3023
|
+
var SRV_STARTED=null,srvDown=false,updBusy=false,SRV_UPD=null; // SRV_UPD: the app that was asked to update
|
|
2808
3024
|
function ubar(){return document.getElementById('updatebar');}
|
|
2809
3025
|
// One Symbiot window, not a pile: each window has an id, says so the first time it
|
|
2810
3026
|
// asks, and the server remembers the newest. An older one closes itself, unless it's
|
|
@@ -2825,16 +3041,22 @@ function heartbeat(fresh){
|
|
|
2825
3041
|
else if(p.started!==SRV_STARTED){location.reload();return;}
|
|
2826
3042
|
if(srvDown){location.reload();return;}
|
|
2827
3043
|
if(updBusy||selIn(b))return; // not under text you're selecting in it
|
|
3044
|
+
// An update in flight, from any window: where it is, and no button (a second
|
|
3045
|
+
// click started a second npm install over the first, and they collided).
|
|
3046
|
+
if(p.updating){var w=p.updating,m=Math.max(0,Math.floor((Date.now()-(w.since||Date.now()))/60000));b.className='updatebar show';
|
|
3047
|
+
b.innerHTML=w.retrying?"Updating to "+esc(w.target)+" once npm has its download (a few minutes after a release): looking every "+Math.round((w.every||30000)/1000)+" seconds, for up to "+Math.round((w.wait||1200000)/60000)+" minutes"+(m?" ("+m+" so far)":"")+". Nothing to do.":"Updating to "+esc(w.target)+"… Symbiot restarts by itself when it’s in, and this page reloads.";return;}
|
|
3048
|
+
// The last one didn't go through: what npm said, and Try again (not a terminal).
|
|
3049
|
+
var f=p.updateFailed;
|
|
3050
|
+
if(f&&p.latest&&p.newer&&f.target===p.latest){b.className='updatebar reconnect show';
|
|
3051
|
+
b.innerHTML="The update to "+esc(f.target)+" didn’t go through: "+esc(String(f.error||'npm stopped').replace(/[.]$/,''))+". You’re still on "+esc(p.version)+". <button id='doupd'>Try again</button>";
|
|
3052
|
+
var ab=document.getElementById('doupd');if(ab)ab.onclick=doUpdate;return;}
|
|
2828
3053
|
// Loop guard: if we already tried to update to this version and we're still
|
|
2829
3054
|
// not on it after the restart, the install isn't advancing — stop offering
|
|
2830
3055
|
// the button (which just loops) and tell them to update manually.
|
|
2831
3056
|
var tried=null;try{tried=localStorage.getItem('symbiot_update_tried');}catch(e){}
|
|
2832
3057
|
if(tried){
|
|
2833
3058
|
if(p.version!==tried){try{localStorage.removeItem('symbiot_update_tried');}catch(e){} } // advanced → success, clear
|
|
2834
|
-
else if(p.
|
|
2835
|
-
b.className='updatebar show';b.innerHTML="Updating to "+esc(p.retrying.target)+": npm is still publishing it, so Symbiot tries again every 30 seconds (try "+p.retrying.attempt+" of "+(p.retrying.of||20)+"). Nothing to do.";return;
|
|
2836
|
-
}
|
|
2837
|
-
else if(p.latest&&p.newer){ // still on the same version → the install didn't advance
|
|
3059
|
+
else if(p.latest&&p.newer&&p.started!==SRV_UPD){ // restarted, still on the same version → the install didn't advance
|
|
2838
3060
|
b.className='updatebar reconnect show';
|
|
2839
3061
|
b.innerHTML="Auto-update to "+esc(p.latest)+" didn't take (still on "+esc(p.version)+"). Run <code>npm install -g symbiot@latest</code> in a terminal, then restart.";
|
|
2840
3062
|
return;
|
|
@@ -2864,7 +3086,9 @@ el.className='whatsnew show';el.innerHTML="<div class='row'><b style='flex:1;col
|
|
|
2864
3086
|
+cs.map(function(c){return "<div style='margin-top:8px'><b>"+esc(c.version)+"</b>"+(c.date?" <span class='muted'>"+esc(c.date)+"</span>":"")+"<ul style='margin:4px 0 0 18px;padding:0'>"+c.items.slice(0,12).map(function(x){return "<li>"+md(x)+"</li>";}).join('')+(c.items.length>12?"<li class='muted'>…and "+(c.items.length-12)+" more</li>":"")+"</ul></div>";}).join('');
|
|
2865
3087
|
$('wnok').onclick=function(){el.className='whatsnew';if(after)api('/api/whatsnew/seen',{});};}
|
|
2866
3088
|
function loadWhatsNew(){api('/api/whatsnew').then(function(r){if(r&&r.changes&&r.changes.length)showNews(r,"What's new in Symbiot "+r.version,true);});}
|
|
2867
|
-
function doUpdate(){updBusy=true;try{localStorage.setItem('symbiot_update_tried',document.getElementById('ver').textContent.replace(/^v/,''));}catch(e){}var b=ubar();b.className='updatebar show';b.textContent='Updating & restarting… this page will reload itself when it is back.';
|
|
3089
|
+
function doUpdate(){updBusy=true;SRV_UPD=SRV_STARTED;try{localStorage.setItem('symbiot_update_tried',document.getElementById('ver').textContent.replace(/^v/,''));}catch(e){}var b=ubar();b.className='updatebar show';b.textContent='Updating & restarting… this page will reload itself when it is back.';
|
|
3090
|
+
// still waiting for npm's download (or already under way): the heartbeat shows where it is
|
|
3091
|
+
api('/api/update',{}).then(function(r){if(r&&(r.waiting||r.already||r.error)){updBusy=false;if(r.error){b.className='updatebar reconnect show';b.textContent=r.error;}}});}
|
|
2868
3092
|
setInterval(heartbeat,4000);heartbeat(true);
|
|
2869
3093
|
window.addEventListener('focus',function(){heartbeat(true);}); // re-check for updates when you come back to the window
|
|
2870
3094
|
|
|
@@ -2885,21 +3109,22 @@ function lqHome(){lqRemember();LQ.scene='home';LQ.relaySig='';var rl=$('lqrelay'
|
|
|
2885
3109
|
function lqLineText(w){var nr=(w.running||[]).length,nq=0,ni=0,nk=(w.ready||[]).length,nw=w.waitingCount||0;(w.projects||[]).forEach(function(p){nq+=p.asks||0;ni+=(p.ideas||[]).length;});
|
|
2886
3110
|
var bits=[];if(nq)bits.push('<b>'+nq+' question'+(nq>1?'s':'')+' for you</b>');if(nk)bits.push('<b>'+nk+' ready for your OK</b>');if(nr)bits.push(nr+' at work');if(nw)bits.push(nw+' waiting');if(ni)bits.push(ni+' suggested task'+(ni>1?'s':''));
|
|
2887
3111
|
return bits.length?bits.join(' · '):'All quiet: nothing at work, nothing asking you.';}
|
|
2888
|
-
function lqRelayList(w){return lqSortLanes((w.projects||[]).filter(function(p){return p.summary||(p.qs&&p.qs.length)||p.ready||(p.ideas&&p.ideas.length);}).slice());}
|
|
3112
|
+
function lqRelayList(w){return lqSortLanes((w.projects||[]).filter(function(p){return p.summary||(p.qs&&p.qs.length)||p.ready||(p.ideas&&p.ideas.length)||(p.handing&&p.handing.length);}).slice());}
|
|
2889
3113
|
function lqRelayOn(){var S=lqSize();return LQ.scene==='work'&&S.w>=900&&lqRelayList(LQ.workData||{}).length>0;}
|
|
2890
3114
|
function lqRelay(){var el=$('lqrelay'),ln=$('lqline'),w=LQ.workData||{};if(ln)ln.innerHTML=LQ.scene==='work'?lqLineText(w):'';if(!el)return;
|
|
2891
|
-
var ps=lqRelayList(w),sig=JSON.stringify(ps.map(function(p){return [p.repo,p.summary,p.state,p.ready,(p.qs||[]).map(function(q){return q.q;}),(p.ideas||[]).map(function(i){return i.full;}),p.parked];}));
|
|
3115
|
+
var ps=lqRelayList(w),sig=JSON.stringify(ps.map(function(p){return [p.repo,p.summary,p.state,p.ready,(p.qs||[]).map(function(q){return q.q;}),(p.ideas||[]).map(function(i){return i.full;}),p.parked,(p.handing||[]).map(function(x){return x.why+handProg(x);})];}));
|
|
2892
3116
|
if(el.contains&&document.activeElement&&el.contains(document.activeElement)&&el.innerHTML)return;if(sig===LQ.relaySig&&el.innerHTML)return;LQ.relaySig=sig;
|
|
2893
3117
|
if(!ps.length){el.innerHTML='';return;}
|
|
2894
3118
|
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||'';
|
|
2895
3119
|
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>";
|
|
2896
3120
|
if(p.summary)h+="<p class='rlsum' title='"+escQ(p.summary)+"'>"+esc(p.summary)+"</p>";
|
|
2897
|
-
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,draft:q.draft,signInTo:q.signInTo,more:Math.max(0,(p.asks||p.qs.length)-1)},false)+"</div>";
|
|
3121
|
+
if(q)h+="<div class='lqblob rlask'>"+blobBody({kind:'ask',name:nm,repo:p.repo,q:q.q,raw:q.raw,labels:q.labels,options:q.options,path:q.path,fix:q.fix,id:q.id,sub:q.sub,draft:q.draft,signInTo:q.signInTo,more:Math.max(0,(p.asks||p.qs.length)-1)},false)+"</div>";
|
|
2898
3122
|
if(p.ready)h+="<button type='button' class='act rlok'>Review and approve</button>";
|
|
3123
|
+
if(p.handing&&p.handing.length)h+="<div class='rlk'>Handed over</div>"+p.handing.map(function(x){var pg=handProg(x);return "<p class='rlsum rlhand' title='"+escQ(x.text)+"'><b>"+esc(x.lane)+"</b>: "+esc(x.why)+(pg?" \u00b7 "+esc(pg):"")+"</p>";}).join('');
|
|
2899
3124
|
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('');
|
|
2900
3125
|
return h+"<div class='rlf'><button type='button' class='rlopen'>Its tasks ›</button><button type='button' class='rlag'>Its agents ›</button></div></section>";}).join('');
|
|
2901
3126
|
el.querySelectorAll('.rly').forEach(function(box){var p=ps[+box.getAttribute('data-i')],q=(p.qs||[])[0],nm=p.name||p.repo;
|
|
2902
|
-
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();}});
|
|
3127
|
+
var ab=box.querySelector('.rlask');if(ab&&q)wireBlob(ab,{kind:'ask',name:nm,repo:p.repo,q:q.q,raw:q.raw,labels:q.labels,options:q.options,path:q.path,fix:q.fix,id:q.id},{done:function(){LQ.relaySig='';lqLoadWork();}});
|
|
2903
3128
|
var ok=box.querySelector('.rlok');if(ok)ok.addEventListener('click',function(){lqOpen({kind:'proj',repo:p.repo,lit:true});});
|
|
2904
3129
|
box.querySelector('.rlopen').addEventListener('click',function(){lqOpen({kind:'proj',repo:p.repo});});
|
|
2905
3130
|
box.querySelector('.rlag').addEventListener('click',function(){lqPool('agents');});
|
|
@@ -2941,7 +3166,7 @@ function lqBuildProjects(){var w=LQ.workData||{},S=lqSize(),cx=S.w/2,cy=S.h*0.5,
|
|
|
2941
3166
|
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-160)/400)),rs=Math.min(S.s,(vx+vy)/1.5);
|
|
2942
3167
|
var all=lqSortLanes((w.projects||[]).slice()),cap=S.w<700?5:7,ps=all.slice(0,cap),rest=all.slice(cap),slots=Math.max(3,ps.length+(rest.length?1:0));
|
|
2943
3168
|
ps.forEach(function(p,i){var ang=Math.PI+i*2*Math.PI/slots,load=p.waiting+2*p.ready+(p.running?3:0)+2*Math.min(2,p.asks||0);
|
|
2944
|
-
var sub=p.asks?p.asks+' question'+(p.asks>1?'s':'')+' for you':p.ready?'ready for your OK':p.runs>1?p.runs+' agents at work':p.running?(p.running.ask?'has a question for you':p.running.quiet||p.running.looping?runWarn(p.running).replace(/ · $/,''):p.running.line?String(p.running.line)+(p.running.eta?' · '+p.running.eta:''):'agent at work: '+String(p.running.doing||'').toLowerCase()):p.waiting+' task'+(p.waiting>1?'s':'')+' waiting';if(p.parked)sub='parked \\u00b7 '+sub;
|
|
3169
|
+
var sub=p.asks?p.asks+' question'+(p.asks>1?'s':'')+' for you':p.ready?'ready for your OK':p.runs>1?p.runs+' agents at work':p.running?(p.running.ask?'has a question for you':p.running.quiet||p.running.looping?runWarn(p.running).replace(/ · $/,''):p.running.line?String(p.running.line)+(p.running.eta?' · '+p.running.eta:''):'agent at work: '+String(p.running.doing||'').toLowerCase()):!p.waiting&&p.handing&&p.handing.length?(p.handing.some(function(x){return !x.gone;})?'a handover not sent yet':'handed over to '+p.handing[0].lane):p.waiting+' task'+(p.waiting>1?'s':'')+' waiting';if(p.parked)sub='parked \\u00b7 '+sub;
|
|
2945
3170
|
list.push({id:'proj:'+p.repo,kind:'proj',repo:p.repo,shape:'tasks',title:p.name||p.repo,sub:sub,orbit:!!p.running,ask:!!p.lit,lit:!!p.lit,parked:!!p.parked,r:(30+7*Math.min(6,load))*rs,tx:cx+Math.cos(ang)*260*vx,ty:cy+Math.sin(ang)*260*vy});});
|
|
2946
3171
|
if(rest.length){var am=Math.PI+ps.length*2*Math.PI/slots;list.push({id:'moreproj',kind:'more',title:'+'+rest.length+' more',sub:'quieter: '+rest.map(function(p){return p.name||p.repo;}).join(' · '),lanes:rest.map(function(p){return {repo:p.repo,name:p.name||p.repo,lit:!!p.lit};}),r:24*rs,tx:cx+Math.cos(am)*260*vx,ty:cy+Math.sin(am)*260*vy});}
|
|
2947
3172
|
|
|
@@ -2996,7 +3221,7 @@ function lqParaGo(g){lqAct();if(g==='p'){lqWorkBy('project');return;}if(g==='a'|
|
|
|
2996
3221
|
// project, the question, its two answers), as many across as fit, in a steady
|
|
2997
3222
|
// order (questions, then Approves, then first steps); the rest a page on.
|
|
2998
3223
|
var LQ_BLOBW=250,LQ_NEEDY=128;
|
|
2999
|
-
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||[]).
|
|
3224
|
+
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||[]).filter(function(y){return !blobSent(y);}).sort(function(a,b){return rk(a)-rk(b)||(a.id<b.id?-1:a.id>b.id?1:0);});
|
|
3000
3225
|
var nb=lqNextBox(h,S);if(!ys.length)return {items:[],bottom:nb,x:0,page:0,pages:1,count:0};
|
|
3001
3226
|
var R=Math.round(24*Math.max(0.85,Math.min(1.1,S.s))),unit=3*R+22+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));
|
|
3002
3227
|
LQ.needPage=(LQ.needPage||0)%pages;
|
|
@@ -3077,7 +3302,40 @@ if(pc&&pc.away)t="<b>"+esc(pc.name)+" is out of reach.</b> Showing what it sent
|
|
|
3077
3302
|
else if(pc&&pc.old)t="<b>Pair this phone with "+esc(pc.name)+" again:</b> the link between them is sealed now.";
|
|
3078
3303
|
else if(ap)t=ap.status==='failed'?"<b>Approving "+esc(ap.repo)+" from "+esc(ap.phone)+" failed:</b> "+esc(ap.error||''):(ap.status==='approving'?"Approving ":"Approved ")+esc(ap.repo)+" from "+esc(ap.phone)+" at "+esc(pcTime(ap.at))+".";
|
|
3079
3304
|
if(b&&b.classList)b.classList.toggle('lq-hline',!!t);if(el&&LQ.scene!=='work'&&el.innerHTML!==t)el.innerHTML=t;}
|
|
3305
|
+
// Home on a phone paired with your computer: what you act on there, read at a glance.
|
|
3306
|
+
// Not the liquid (its groups, orbs and the computer's work behind a droplet: "the UI on
|
|
3307
|
+
// the phone is bloated", 2026-10-12), but a plain list: how fresh the computer's copy
|
|
3308
|
+
// is, what needs you there (its agents' questions, its Approves) answered in place,
|
|
3309
|
+
// then its tasks, to tick or add (the computer's panel, renderComputer, moved here).
|
|
3310
|
+
// What this phone itself needs comes after the computer's (renderComputer puts it there): its questions and Approves as
|
|
3311
|
+
// they are, its setup together in one, as Settings has it. The rest of Symbiot is a
|
|
3312
|
+
// row along the top, one tap each. false when this isn't a paired phone: the liquid.
|
|
3313
|
+
var PH_NAV=['settings','reports','board','map','week','standup','marketing']; // not the Workdesk: the computer's tasks are this list, and the phone's own was empty
|
|
3314
|
+
function lqPhone(h){var on=!!(h&&h.computer),b=document.body,el=$('phhome'),slot=$('phpc'),pc=$('panel-computer');LQ.phone=on;
|
|
3315
|
+
if(b&&b.classList)b.classList.toggle('lq-phone',on);
|
|
3316
|
+
// the computer's view lives on Home while this is a paired phone, and in its panel otherwise
|
|
3317
|
+
if(slot&&pc){['pcline','pcmsg','pcbody'].forEach(function(id){var n=$(id),to=on?slot:pc;if(n&&n.parentNode!==to)to.appendChild(n);});
|
|
3318
|
+
if(!$('phyou')){var py=document.createElement('div');py.id='phyou';slot.appendChild(py);}}
|
|
3319
|
+
if(!on||!el)return false;
|
|
3320
|
+
// under the header, however tall it is (taller for a finger)
|
|
3321
|
+
var hd=document.querySelector('header'),hb=hd&&hd.getBoundingClientRect?Math.round(hd.getBoundingClientRect().bottom):0;if(hb>0&&el.style)el.style.top=hb+'px';
|
|
3322
|
+
var rep=((h.feeds||[]).filter(function(f){return f.id==='feed:reports';})[0]||{}).count||0,mk=h.marketing||null;
|
|
3323
|
+
var nav=$('phnav');if(nav&&!sameHtml(nav,PH_NAV.filter(function(s){return s!=='marketing'||mk;}).map(function(s){var n=s==='reports'?rep:s==='marketing'?(mk.needs||0):0;return "<button type='button' data-s='"+s+"'"+(n?" class='lit'":"")+">"+esc(LQNAMES[s]||s)+(n?" <b>"+n+"</b>":"")+"</button>";}).join('')))
|
|
3324
|
+
nav.querySelectorAll('button').forEach(function(x){x.addEventListener('click',function(ev){var s=x.getAttribute('data-s');lqAct();if(s==='marketing'&&mk&&mk.needs)MKLIT=true;lqUse(s,ev);lqGo(s);});});
|
|
3325
|
+
LQ.drops=[];LQ.need=null;LQ.coreText='';lqLabels();
|
|
3326
|
+
phYou(h);
|
|
3327
|
+
// fresh from the computer now and then, never while you're typing an answer or a task here
|
|
3328
|
+
var fa=document.activeElement,typing=fa&&el.contains&&el.contains(fa)&&/^(INPUT|TEXTAREA|SELECT)$/.test(fa.tagName||'');
|
|
3329
|
+
if(!typing&&(!PCV||Date.now()-(LQ.pcAt||0)>20000)){LQ.pcAt=Date.now();loadComputer();}
|
|
3330
|
+
return true;}
|
|
3331
|
+
function phYou(h){var el=$('phyou');if(!el)return;var ys=(h&&h.you)||[],setup=ys.filter(function(y){return y.kind==='setup';}),rest=ys.filter(function(y){return y.kind!=='setup';}),rows=rest.slice();
|
|
3332
|
+
if(setup.length)rows.push({kind:'setup',id:'phone:setup',urgent:setup.some(function(y){return y.urgent;}),shape:'settings',focus:setup[0].focus,title:setup.length>1?setup.length+' things to set up':setup[0].title,sub:setup.length>1?setup.map(function(y){return y.title;}).join(' · '):setup[0].sub});
|
|
3333
|
+
if(!sameHtml(el,rows.length?"<div class='pshead'><span>Needs you on this phone · "+rows.length+"</span></div>"+rows.map(function(y,i){return "<button type='button' class='phyi"+(y.urgent?' urgent':'')+"' data-i='"+i+"'><b>"+esc(y.title)+"</b><span>"+esc(y.sub||'')+"</span></button>";}).join(''):''))
|
|
3334
|
+
el.querySelectorAll('.phyi').forEach(function(x){x.addEventListener('click',function(ev){var y=rows[+x.getAttribute('data-i')];if(y)lqOpen({kind:'you',item:y,shape:y.shape},ev);});});}
|
|
3080
3335
|
function lqBuild(){var a=LQ.adapt||{},h=LQ.home||{},S=lqSize(),list=[];LQ.bw=S.w;LQ.bh=S.h;
|
|
3336
|
+
// the talk box's hint in full where it fits; on a phone it was cut off mid-word
|
|
3337
|
+
var ask=$('lqask'),nar=S.w<520;if(ask&&LQ.askNarrow!==nar){LQ.askNarrow=nar;ask.setAttribute('placeholder',nar?'Tell Symbiot what to do':'Talk to Symbiot, or tell it what to do (paste a screenshot to show it)');}
|
|
3338
|
+
if(lqPhone(h))return;
|
|
3081
3339
|
// the band first: everything else sits under it, round a core centred in what's left
|
|
3082
3340
|
var need=lqNeeds(h,S),band=lqRunBand(h,S,need.bottom),top=band.bottom||need.bottom,cx=S.w/2,cy=top?Math.max(S.h*0.47,(top+60+S.h-200)/2):S.h*0.47;LQ.cy=cy;LQ.top=top;LQ.need=need;
|
|
3083
3341
|
// Marketing only once posts can be drafted, or are (home.mjs): a new user meets what they watch first
|
|
@@ -3230,7 +3488,7 @@ function blobOpt(o){return String(o||'').replace(/[ ]*[(]recommended[)][ ]*$/i,'
|
|
|
3230
3488
|
// week-old one is dropped. One whose question went (answered elsewhere) is said in
|
|
3231
3489
|
// Home's talk with what you wrote (bdOrphans), never dropped silently.
|
|
3232
3490
|
var BDKEY='symbiot-blobdrafts',BDKEEP=7*86400000;
|
|
3233
|
-
function bdKey(y){return y.path&&
|
|
3491
|
+
function bdKey(y){var q=y.raw||y.q;return y.path&&q?String(y.path)+'|'+String(q):String(y.id||'')+'|'+String(q||y.sub||'');} // the same question on Home, the Workdesk and a project
|
|
3234
3492
|
function bdAll(){try{var a=JSON.parse(localStorage.getItem(BDKEY)||'{}');return a&&typeof a==='object'?a:{};}catch(e){return {};}}
|
|
3235
3493
|
function bdGet(y){var d=bdAll()[bdKey(y)];return d&&d.text?d:null;}
|
|
3236
3494
|
function bdSet(y,text,s,e,src){var a=bdAll(),k=typeof y==='string'?y:bdKey(y),now=Date.now();if(text)a[k]={text:String(text),at:now,s:s,e:e,name:y.name||y.repo||'',q:y.q||'',src:src||'blob'};else delete a[k];
|
|
@@ -3243,28 +3501,40 @@ h+=home?"<button type='button' class='lt' title='open "+escQ(nm)+", with this li
|
|
|
3243
3501
|
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>";
|
|
3244
3502
|
var os=y.options||[];if(y.draft)h+=draftHtml(y.draft);h+="<div class='bq' title='"+escQ((y.q||'')+(y.why?' ('+y.why+')':''))+"'>"+esc(y.q||y.sub||'')+"</div>";
|
|
3245
3503
|
if(y.fix==='handover'&&y.sub)h+="<div class='bfor' title='"+escQ(y.sub)+"'>for: "+esc(y.sub)+"</div>";
|
|
3504
|
+
if(y.done)h+="<div class='bdone' title='"+escQ(y.done.text)+"'>"+esc(y.done.lane)+" has done this already: "+esc(y.done.text)+"</div>";
|
|
3246
3505
|
if(y.signInTo)h+="<div class='bo'><button type='button' class='nopt bsign' title='opens "+escQ(y.signInTo.name)+" in the Symbiot Browser: sign in there, then click Done'><span class='bt'>Sign in to "+esc(y.signInTo.name)+" ›</span></button></div>";
|
|
3247
3506
|
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((y.labels&&y.labels[j])||o)+"'><span class='bt'>"+whoHtml(blobOpt((y.labels&&y.labels[j])||o))+"</span></button>";}).join('')+"</div><button type='button' class='bfree' aria-expanded='false'>"+(y.draft?'Change it':'or answer in your own words')+"</button>";
|
|
3248
3507
|
h+="<form class='bfx"+(os.length?' hidden':'')+"'><input placeholder='"+(y.draft?'what to change':'your answer')+"' aria-label='"+(y.draft?'what to change in the reply':'your answer to '+escQ(nm))+"'><button type='submit'>"+(y.draft?'Redraft':'Send')+"</button></form>";
|
|
3249
3508
|
if(home&&y.more)h+="<span class='bmore'>+"+y.more+" more question"+(y.more>1?'s':'')+" after this one</span>";
|
|
3250
|
-
return h;}
|
|
3509
|
+
return h+"<div class='bst' role='status' aria-live='polite'></div>";}
|
|
3251
3510
|
// Answering in a blob is answering on the agent's block in Agents (Send answers &
|
|
3252
3511
|
// continue): saved to its ANSWERS.md, and its agent picks it up. Then it dissolves.
|
|
3253
|
-
|
|
3512
|
+
// A click shows at once: the button pressed, Sending… on the card, then the card goes, or
|
|
3513
|
+
// says on itself what went wrong (a word in Home's talk alone went unseen: the Allow
|
|
3514
|
+
// pressed again and again on 2026-10-12). One answer per question at a time (BLOBBUSY),
|
|
3515
|
+
// through a redraw too, and a card answered stays away until Home has heard (BLOBSENT).
|
|
3516
|
+
var BLOBBUSY={},BLOBSENT={},BLOBSENT_FOR=120000;
|
|
3517
|
+
function blobSent(y){var t=BLOBSENT[bdKey(y)];return !!t&&Date.now()-t<BLOBSENT_FOR;}
|
|
3518
|
+
function wireBlob(box,y,o){o=o||{};var say=o.say||lqSaid,nm=y.name||y.repo||'the agent',key=bdKey(y);
|
|
3254
3519
|
var lock=function(on){box.querySelectorAll('button,input').forEach(function(b){b.disabled=on;});};
|
|
3255
|
-
var
|
|
3256
|
-
(
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3520
|
+
var st=function(t,bad){var e=box.querySelector('.bst');if(!e)return;e.textContent=t||'';if(e.classList)e.classList.toggle('err',!!bad);if(o.grew)o.grew();};
|
|
3521
|
+
var busy=function(on,b){if(box.classList)box.classList.toggle('sending',on);if(box.setAttribute)box.setAttribute('aria-busy',on?'true':'false');box.querySelectorAll('.pressed').forEach(function(x){x.classList.remove('pressed');});if(on&&b&&b.classList)b.classList.add('pressed');lock(on);};
|
|
3522
|
+
var send=function(ans,j,b){ans=String(ans||'').trim();if(!ans||(!y.path&&!y.fix)||BLOBBUSY[key])return;BLOBBUSY[key]=1;busy(true,b);st('Sending…');
|
|
3523
|
+
var fail=function(t){delete BLOBBUSY[key];busy(false);st(t,true);say(nm+': '+t,true);};
|
|
3524
|
+
(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.raw||y.q,a:ans}],rerun:true})).then(function(r){
|
|
3525
|
+
if(!r||r.error){fail((r&&r.error)||'the answer didn’t save. Try again on its block on the Workdesk.');return;}
|
|
3526
|
+
delete BLOBBUSY[key];BLOBSENT[key]=Date.now();st(r.already?'Answered already.':'Sent.');
|
|
3527
|
+
say(y.fix?nm+': '+(r.said||'Done.'):r.already?nm+': '+r.note:'Sent to '+(r.lane||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.')));
|
|
3528
|
+
bdSet(y,'');if(o.sent)o.sent();if(box.classList)box.classList.add('gone');setTimeout(function(){if(o.done)o.done();},650);}).catch(function(e){fail(String((e&&e.message)||e));});};
|
|
3529
|
+
if(BLOBBUSY[key]){busy(true);st('Sending…');} // drawn again while its answer is on its way
|
|
3530
|
+
box.querySelectorAll('.nopt[data-j]').forEach(function(b){b.addEventListener('click',function(){var j=+b.getAttribute('data-j');send((y.options||[])[j],j,b);});});
|
|
3261
3531
|
var bs=box.querySelector('.bsign');if(bs)bs.addEventListener('click',function(){bs.disabled=true;openSignIn(y.signInTo,function(t,ok){bs.disabled=false;say(t,!ok);});});
|
|
3262
3532
|
var fr=box.querySelector('.bfree'),fx=box.querySelector('.bfx'),fi=fx&&fx.querySelector('input');
|
|
3263
3533
|
if(fi){var dr=bdGet(y);if(dr&&!fi.value){fi.value=dr.text;if(fx.classList)fx.classList.remove('hidden');if(fr)fr.setAttribute('aria-expanded','true');try{if(dr.s!=null)fi.setSelectionRange(dr.s,dr.e!=null?dr.e:dr.s);}catch(e){}}
|
|
3264
3534
|
var keep=function(){bdSet(y,fi.value,fi.selectionStart,fi.selectionEnd);};['input','keyup','mouseup','select'].forEach(function(t){fi.addEventListener(t,keep);});}
|
|
3265
3535
|
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();if(o.grew)o.grew();});
|
|
3266
3536
|
if(o.grew&&fi&&fx&&!fx.classList.contains('hidden'))setTimeout(o.grew,0); // a draft brought back opened it
|
|
3267
|
-
if(fx)fx.addEventListener('submit',function(ev){if(ev&&ev.preventDefault)ev.preventDefault();var i=fx.querySelector('input');
|
|
3537
|
+
if(fx)fx.addEventListener('submit',function(ev){if(ev&&ev.preventDefault)ev.preventDefault();var i=fx.querySelector('input');if(!i||!String(i.value||'').trim()){st('Type your answer first.',true);if(i&&i.focus)i.focus();return;}send(i.value,null,fx.querySelector('button'));});
|
|
3268
3538
|
var go=box.querySelector('.ngo');if(go)go.addEventListener('click',function(){if(!y.pick){lqFocus(y);return;}
|
|
3269
3539
|
// Pick your agent: the one Symbiot found, in one click; Settings has the others
|
|
3270
3540
|
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;}
|
|
@@ -3315,7 +3585,7 @@ function lqRemember(){if(LQ.back)return;LQ.hist=LQ.hist||[];var s=lqState(),top=
|
|
|
3315
3585
|
function lqBack(){var h=LQ.hist||[],s=h.pop(),cur=lqState();if(!s){if(cur.pool)s={scene:cur.scene,pool:null};else if(cur.scene==='work')s={scene:'home',pool:null};else return;}
|
|
3316
3586
|
LQ.back=true;try{if(s.scene==='work'&&(cur.scene!=='work'||cur.by!==s.by)){LQ.workBy=s.by||'task';lqWork();}else if(s.scene==='home'&&cur.scene==='work')lqHome();
|
|
3317
3587
|
if(s.pool){if(s.arch)lqArchive();else lqPool(s.pool);}else if(LQ.mode==='pool')lqSink();}finally{LQ.back=false;}}
|
|
3318
|
-
function lqPool(shape){lqWhy(null);if(shape==='map'&&LM.items&&LM.items.length)setTimeout(function(){lmFit();lmStart();},60);lqRemember();LQ.poolShape=shape;LQ.arch=false;tabPicked=true;setTab(shape);var b=document.body;if(b&&b.classList)b.classList.add('lq-pooled');LQ.mode='pool';var m=document.querySelector('main');if(m){m.scrollTop=0;if(m.focus)m.focus();}}
|
|
3588
|
+
function lqPool(shape){if(shape==='computer'&&LQ.phone){lqSink();return;}lqWhy(null);if(shape==='map'&&LM.items&&LM.items.length)setTimeout(function(){lmFit();lmStart();},60);lqRemember();LQ.poolShape=shape;LQ.arch=false;tabPicked=true;setTab(shape);var b=document.body;if(b&&b.classList)b.classList.add('lq-pooled');LQ.mode='pool';var m=document.querySelector('main');if(m){m.scrollTop=0;if(m.focus)m.focus();}}
|
|
3319
3589
|
function lqSink(){lqWhy(null);lqRemember();LQ.focus=null;var b=document.body;if(b&&b.classList)b.classList.remove('lq-pooled');LQ.mode='aware';lqAct();lqLoad(false);}
|
|
3320
3590
|
function lqAct(){if(AWAY)return;LQ.lastAct=Date.now();if(LQ.mode==='rest'){LQ.mode='aware';lqLoad(true);}}
|
|
3321
3591
|
function lqTalkMode(on){LQ.talking=on;var b=document.body;if(b&&b.classList)b.classList.toggle('lq-talking',on);}
|
|
@@ -3722,7 +3992,7 @@ lqLoad(true);
|
|
|
3722
3992
|
// the only clocks: rest when left alone, fresh data now and then, the hour (night)
|
|
3723
3993
|
setInterval(function(){if(LQ.mode==='aware'&&!LQ.talking&&Date.now()-LQ.lastAct>LQ_REST)LQ.mode='rest';if(LQ.mode!=='pool')lqLoad(false);var h=new Date().getHours(),n=h>=22||h<6;if(n!==LQ.theme.night)lqTheme();},30000);
|
|
3724
3994
|
var G=lqGL();if(G){try{lqStep();lqDraw(G);}catch(e){}}if(typeof window.requestAnimationFrame!=='function')return;var raf=function(f){return window.requestAnimationFrame(f);};
|
|
3725
|
-
function frame(){if(!document.hidden){LQ.frame++;if(LQ.mode!=='pool'||LQ.frame%3===0)lqStep();var every=glLight()?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
|
|
3995
|
+
function frame(){if(!document.hidden){LQ.frame++;if(LQ.mode!=='pool'||LQ.frame%3===0)lqStep();var every=glLight()?glEvery(LQ.mode==='aware'):LQ.mode==='pool'?6:LQ.mode==='rest'?2:1;if(G&&LQ.frame%every===0&&!(LQ.phone&&LQ.scene!=='work'&&LQ.mode!=='pool'))lqDraw(G);}raf(frame);} // moves every frame; drawn less at rest, behind a panel, without a GPU, with Low GPU or in a window not in use; not at all under a phone's Home list
|
|
3726
3996
|
raf(frame);}
|
|
3727
3997
|
|
|
3728
3998
|
initGraphEvents();syncP();refresh();firstTab();lqInit();onbLoad();var sbo=$('sbopen');if(sbo)sbo.addEventListener('click',function(){sbOpen('');});var rr=$('rerunsetup');if(rr)rr.addEventListener('click',function(){api('/api/onboarding/restart',{}).then(function(){lqSink();onbLoad();});});awayInit();lqDeep();loadWhatsNew();loadAgentCfg();loadScanRoots();loadKnowledge();loadPhone();loadLinks();loadMind();loadTrusted();loadMail();loadScreensUI();loadMonitorsUI();loadDesktop();loadWatchUI();setInterval(function(){whenFree(document.querySelector('main'),'watch',loadWatchUI);},60000);loadPhoneLink();
|