symbiot 0.49.0 → 0.49.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +62 -26
- package/home.mjs +7 -1
- package/package.json +1 -1
- package/ui.mjs +142 -41
- package/watch.mjs +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
What each Symbiot release brought, newest first. When Approve bumps the version, it writes that release here from the tasks it approved, worded as release notes by your connected AI (or in the tasks' own words without one). The app shows the releases newer than yours under "What's new" when it offers an update, and once more after you update.
|
|
4
4
|
|
|
5
|
+
## 0.49.2
|
|
6
|
+
|
|
7
|
+
- **Projects shows your projects.** Click **Projects** on the liquid and each repo with work on it is a sphere: what's going on there under its name (an agent at work, ready for your OK, or how many tasks are waiting), spheres orbiting while an agent works, amber when its work waits for you. Click one to open just that project's tasks; right-click comes back.
|
|
8
|
+
- **The Dashboard, in detail.** A headline that says what's waiting, with when it was last read; a 24 hours / 3 days / 7 days switch instead of the old dropdown; a round refresh; **Close** instead of "Sink back". Feeds go by their short names ("Inbox", with your address under it). What you've already seen stays on the stream as small faint beads, so a quiet day isn't an empty line, and the stream lines up with "now" however wide the window is.
|
|
9
|
+
- **Connections.** The link chips are now a grid by kind of site, each with a bead: solid when linked, amber when it wants you to sign in, hollow when it isn't linked. Link, check and Unlink show when you point at one. It's on the Dashboard as well as in Settings.
|
|
10
|
+
- **Every panel's controls, in the look.** Dropdowns have a dark menu in Ferrofluid instead of the white one, buttons are solid in the look's ink (Pearl keeps its chrome), and the card list drops its emoji and green borders.
|
|
11
|
+
- **README** brought up to date: the looks, P.A.R.A., the stream, Connections, click to open and right-click to go back.
|
|
12
|
+
|
|
13
|
+
## 0.49.1
|
|
14
|
+
|
|
15
|
+
- **Click a sphere, it opens.** A sphere (or its name) opens its screen straight away.
|
|
16
|
+
- **Right-click goes back.** One step at a time: from a panel to the screen you opened it from, from the Tasks scene to home. Your mouse's back button does the same. In a text box, or over text you've selected, right-click still gives the usual menu.
|
|
17
|
+
|
|
5
18
|
## 0.49.0
|
|
6
19
|
|
|
7
20
|
- **Home by P.A.R.A.** The liquid groups everything Symbiot holds four ways: **Projects** (work with an end: Tasks, Agents, Todo, and what's asking you), **Areas** (what you keep up with: the Dashboard, your feeds, Week, Standup), **Resources** (to look things up in: Map, Drift, Settings) and the **Archive** (what's done). Each group has its own part of the screen and its name on the liquid; tap a name to open it. Projects move the most, the Archive is still, and Agents has spheres orbiting it while one works.
|
package/README.md
CHANGED
|
@@ -41,9 +41,9 @@ visual app in your browser — in a clean, chrome-less window if you have
|
|
|
41
41
|
Chrome/Chromium/Edge/Brave (`--app` mode), otherwise a normal tab. No Electron,
|
|
42
42
|
no install — it's the same CLI. Press Ctrl+C (or click Quit) to stop.
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
It opens on [the liquid](#the-liquid-a-home-that-shapes-itself-to-you); each of these parts opens from it:
|
|
45
45
|
|
|
46
|
-
- **Map**
|
|
46
|
+
- **Map** — a live node graph of your work from your local git:
|
|
47
47
|
you at the centre, your repos, the languages and tools they share (so related
|
|
48
48
|
projects cluster), the coding **agents and editors** you have installed, and the
|
|
49
49
|
**AI** powering Symbiot. Project folders that **aren't git repos** show up too
|
|
@@ -55,19 +55,23 @@ Tabs:
|
|
|
55
55
|
**no AI key**; reviews and suggestions use your chosen model. While the scan runs
|
|
56
56
|
the Map shows its progress. Under the graph is the [experimental](#screens-blueprints-for-screen-automation)
|
|
57
57
|
**Screens** view.
|
|
58
|
-
- **Dashboard** — everything you [watch](#screens-blueprints-for-screen-automation)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
what you
|
|
67
|
-
|
|
68
|
-
|
|
58
|
+
- **Dashboard** — everything you [watch](#screens-blueprints-for-screen-automation),
|
|
59
|
+
as a stream: time runs left to right, one current per feed (your inbox, GitHub,
|
|
60
|
+
WhatsApp, any other page), "now" on the right where each feed pools. Every message
|
|
61
|
+
is a bead placed when it arrived; the ones waiting for your reply are bigger and
|
|
62
|
+
amber, and what you've already seen stays as small faint beads. Point at or tap a
|
|
63
|
+
bead to read it and **Draft a reply** or **Open** it. Under each feed's name:
|
|
64
|
+
**Seen** (sets its count back to 0; what it found stays under Watching, and the
|
|
65
|
+
other feeds keep theirs), **Talk** (a chat with your AI about what's new there:
|
|
66
|
+
what needs you, and what to say to whom; agree it there, then **Draft a reply**
|
|
67
|
+
on one, and your agent gets that talk with the message, so it writes what you
|
|
68
|
+
agreed) and **Check now**. The headline says what's waiting; switch between 24
|
|
69
|
+
hours, 3 days and 7 days. Each feed's latest brief sits at the end of its
|
|
70
|
+
current, the cards are still there under **All of it as a list**, and
|
|
71
|
+
**Connections** below shows every site you can [link](#link-your-work).
|
|
72
|
+
`symbiot watch board` prints the same feeds as JSON, and
|
|
69
73
|
`symbiot watch board --line` as one line for a status bar (`2 emails · 1 WhatsApp message`).
|
|
70
|
-
`symbiot watch chat <id> "question"` is
|
|
74
|
+
`symbiot watch chat <id> "question"` is a feed's Talk from a terminal, so an
|
|
71
75
|
agent can go over what's new with you too.
|
|
72
76
|
- **Drift** — the [`symbiot drift`](#whats-out-of-sync-symbiot-drift) report, with
|
|
73
77
|
a "fetch latest" toggle (and an [experimental](#experimental) "check CI").
|
|
@@ -77,7 +81,12 @@ Tabs:
|
|
|
77
81
|
[`symbiot push`](#hand-tasks-to-your-coding-agent-symbiot-push)). What your agent
|
|
78
82
|
finishes lands in **Awaiting your review** (see
|
|
79
83
|
[Review and approve](#review-and-approve-the-agents-work)). A task you tick
|
|
80
|
-
yourself is done and **auto-archives**; the archived view
|
|
84
|
+
yourself is done and **auto-archives**; the archived view (the liquid's
|
|
85
|
+
**Archive**) can restore it. Opening Tasks or Agents from the liquid shows the
|
|
86
|
+
work first: a sphere per task an agent is on, named in plain words, with small
|
|
87
|
+
spheres orbiting it while it works; what's done and waiting for your OK; and what's
|
|
88
|
+
waiting its turn, gathered round the agent in its repo. One **Go** (or saying
|
|
89
|
+
"go") starts everything waiting. Tap a sphere for the details.
|
|
81
90
|
**💬** on any task opens a Q&A thread: ask what it means, how to approach it, or
|
|
82
91
|
(once it's awaiting review) what the agent changed. Answers use your chosen AI,
|
|
83
92
|
grounded in that repo's commits, README, rules and pending diff; the thread is
|
|
@@ -475,13 +484,39 @@ in the app.
|
|
|
475
484
|
|
|
476
485
|
## The liquid: a home that shapes itself to you
|
|
477
486
|
|
|
478
|
-
`symbiot app` opens on one surface of liquid
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
487
|
+
`symbiot app` opens on one surface of liquid. Pick its look at the top, next to the
|
|
488
|
+
name: **Ferrofluid** (glossy black liquid metal, lit like a studio; the default),
|
|
489
|
+
**Glass** (clear droplets that bend the colours behind them) or **Pearl** (silver
|
|
490
|
+
lit like a product photo). Every panel follows the look, and the typeface (Geist)
|
|
491
|
+
ships with Symbiot, so it looks the same offline.
|
|
492
|
+
|
|
493
|
+
Its droplets are what only you can do (an Approve waiting, an agent's question to
|
|
494
|
+
you), what's new on what you watch, and the parts of the app, grouped the
|
|
495
|
+
**P.A.R.A.** way, each group in its own part of the screen with its name over it:
|
|
496
|
+
|
|
497
|
+
- **Projects** (work with an end): Tasks, Agents, Todo and what's asking you. They
|
|
498
|
+
move the most; Agents has spheres orbiting it while an agent works. Click the
|
|
499
|
+
**Projects** name for your projects themselves: a sphere per repo with work on it,
|
|
500
|
+
saying what's going on there, amber when its work waits for your OK; click one for
|
|
501
|
+
just its tasks.
|
|
502
|
+
- **Areas** (what you keep up with): the Dashboard, your feeds, Week and Standup.
|
|
503
|
+
- **Resources** (to look things up in): Map, Drift and Settings.
|
|
504
|
+
- **Archive** (what's done): your archived tasks. It sits still, low on the right.
|
|
505
|
+
|
|
506
|
+
**Click a droplet** and its part opens over the liquid; click a group's name to open
|
|
507
|
+
that group. **Right-click** (or your mouse's back button) goes back one step at a
|
|
508
|
+
time; **Close** or Esc closes a part. In a text box, or over text you've selected,
|
|
509
|
+
right-click still gives the usual menu. Whatever needs you glows **amber**, here and
|
|
510
|
+
on the Dashboard. Talk to it in the bar at the bottom: "open tasks" opens Tasks,
|
|
511
|
+
"go" in the work view starts what's waiting, anything else goes to the same Symbiot
|
|
512
|
+
as every chat. Leave it alone and it rests as one orb. There are no settings for any
|
|
513
|
+
of this: it adapts to you.
|
|
514
|
+
|
|
515
|
+
- **Where things sit: nearest neighbours.** Inside a group, the parts you move
|
|
516
|
+
between most sit together (k-nearest neighbours on the same Markov chain as
|
|
517
|
+
below, counting only links stronger than chance), so the layout has clusters and
|
|
518
|
+
gaps instead of a ring. Each droplet keeps a steady offset of its own, so it
|
|
519
|
+
reads as grown, not ruled, and stays where it was from one visit to the next.
|
|
485
520
|
- **How you work** (`adapt.mjs`). Use decays with a 3-day half-life; what you open
|
|
486
521
|
next comes from a Markov chain (with a Dirichlet prior, so a few moves can't
|
|
487
522
|
swing it) and your hour of day. The more predictable you are (low Shannon
|
|
@@ -503,7 +538,7 @@ Symbiot's config folder, readable by you only.
|
|
|
503
538
|
|
|
504
539
|
## Link your work
|
|
505
540
|
|
|
506
|
-
|
|
541
|
+
**Connections** (on the Dashboard, and in Settings) has one entry per standard work site: Gmail,
|
|
507
542
|
Outlook, Google and Outlook Calendar, GitHub, GitLab, Slack, Microsoft Teams,
|
|
508
543
|
WhatsApp, Jira & Confluence, Linear, Asana, Trello, Google Drive, Notion,
|
|
509
544
|
HubSpot and Salesforce. One click:
|
|
@@ -517,8 +552,9 @@ HubSpot and Salesforce. One click:
|
|
|
517
552
|
in Standup's "Waiting on you", and in **Week**. Someone who doesn't write code
|
|
518
553
|
gets a Week from their linked sites too.
|
|
519
554
|
|
|
520
|
-
|
|
521
|
-
|
|
555
|
+
They're grouped by kind (Mail, Calendar, Code, Chat, Work, Docs, Sales), each with
|
|
556
|
+
a bead that shows where it stands: hollow (not linked), amber (sign in, or signed
|
|
557
|
+
out), solid (linked). Point at one for **Link**, **↻** (check now) and **Unlink**. Unlinking stops
|
|
522
558
|
watching it and stops trusting what the link trusted, never a site you trusted
|
|
523
559
|
yourself.
|
|
524
560
|
|
|
@@ -857,7 +893,7 @@ symbiot watch new --hours 24 # what's new, newest first (JSON)
|
|
|
857
893
|
symbiot watch board # the Dashboard's cards (JSON; .total is the count, for a status bar)
|
|
858
894
|
symbiot watch board --line # the same as one line: "2 emails · 1 WhatsApp message" (empty when nothing's new)
|
|
859
895
|
symbiot watch seen <watch id> # set a card back to 0, like its Seen button
|
|
860
|
-
symbiot watch chat <watch id> "what needs me?" # talk a card over with your AI, like its
|
|
896
|
+
symbiot watch chat <watch id> "what needs me?" # talk a card over with your AI, like its Talk (--clear starts over)
|
|
861
897
|
symbiot watch check # read them all now (JSON)
|
|
862
898
|
symbiot watch draft <id> # Draft a reply to a new email or chat message (see below)
|
|
863
899
|
symbiot watch brief on # your AI says what needs you (off to stop)
|
package/home.mjs
CHANGED
|
@@ -91,7 +91,13 @@ function workScene({ deps = {} } = {}) {
|
|
|
91
91
|
const ready = pending().filter((r) => r.path && !r.running && ((r.tasks || []).length || (r.files || []).length)).map((r) => ({ id: "ready:" + r.repo, repo: r.repo, count: (r.tasks || []).length }));
|
|
92
92
|
const busy = new Set(running.map((r) => r.name)), open = tasks().filter((t) => !t.done && !t.archived && !t.review && t.repo);
|
|
93
93
|
const waiting = open.map((t) => ({ id: "task:" + t.id, text: plain(t.text, 56), repo: t.repo, busy: busy.has(t.repo) }));
|
|
94
|
-
|
|
94
|
+
// Projects: each repo with work on it, what's going on there in one line's worth
|
|
95
|
+
const by = {}, at = (name) => (by[name] = by[name] || { repo: name, waiting: 0, ready: 0, running: null });
|
|
96
|
+
running.forEach((r) => { at(r.name).running = { doing: r.doing, progress: r.progress, ask: r.waiting }; });
|
|
97
|
+
ready.forEach((r) => { at(r.repo).ready = r.count || 1; });
|
|
98
|
+
open.forEach((t) => { at(t.repo).waiting++; });
|
|
99
|
+
const projects = Object.values(by).sort((a, b) => (b.running ? 1 : 0) - (a.running ? 1 : 0) || b.ready - a.ready || b.waiting - a.waiting || (a.repo < b.repo ? -1 : 1));
|
|
100
|
+
return { running, ready, waiting: waiting.slice(0, 12), waitingCount: waiting.length, canGo: waiting.filter((w) => !w.busy).length, projects: projects.slice(0, 10), projectCount: projects.length };
|
|
95
101
|
}
|
|
96
102
|
// Go: everything waiting goes to its repo's agent, the way Send to repos and an
|
|
97
103
|
// agent per repo would: briefs written, an agent started in each (or queued
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "symbiot",
|
|
3
|
-
"version": "0.49.
|
|
3
|
+
"version": "0.49.2",
|
|
4
4
|
"description": "Your week, written from your real work. Reads your local git and writes it up with the AI of your choice — Claude, OpenAI, Gemini, or a local model.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/ui.mjs
CHANGED
|
@@ -25,6 +25,9 @@ body.lq-forced #lq{display:none}
|
|
|
25
25
|
.lqd{position:absolute;left:0;top:0;display:flex;flex-direction:column;align-items:center;border-radius:14px;border:1px solid transparent;background:transparent;color:#ECE9E4;font:600 13.5px/1.25 var(--sans);letter-spacing:-.005em;pointer-events:auto;transition:opacity .5s ease,background-color .2s ease,border-color .2s ease;will-change:transform;text-shadow:0 1px 10px rgba(0,0,0,.6)}
|
|
26
26
|
.lqd .lt .lm{display:block;font-weight:500;font-size:11.5px;color:#8A919B;margin-top:1px}
|
|
27
27
|
.lqd .lt{all:unset;box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:7px;min-height:36px;padding:6px 10px;cursor:pointer;text-align:center;max-width:220px}
|
|
28
|
+
.lqd.lq-proj .lt{flex-direction:column;gap:1px;max-width:260px}
|
|
29
|
+
.lqd.lq-proj .lt .lm{font-weight:500;font-size:12px;color:#8A919B;max-width:250px}
|
|
30
|
+
body.lq-light .lqd.lq-proj .lt .lm{color:#5A6470}
|
|
28
31
|
.lqd.lq-you .lt::before{content:'';flex:none;width:7px;height:7px;border-radius:50%;background:#F2A541;box-shadow:0 0 0 3px rgba(242,165,65,.2)}
|
|
29
32
|
.lqd .lt span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
30
33
|
.lqd .lgo{display:none;margin:4px 10px 10px;min-height:38px;padding:0 16px;border-radius:999px;border:1px solid #FFFFFF;background:linear-gradient(180deg,#FFFFFF 0%,#D2D8E1 44%,#8E97A6 56%,#E6EAF0 100%);color:#06080B;font:700 12.5px var(--sans);cursor:pointer}
|
|
@@ -97,8 +100,11 @@ body.lq-liquid.lq-light.lq-pooled main{background:rgba(246,248,251,.9);box-shado
|
|
|
97
100
|
body.lq-liquid.lq-solid.lq-pooled main{backdrop-filter:none;background:var(--ink)}
|
|
98
101
|
body.lq-liquid .task,body.lq-liquid .agent,body.lq-liquid .rcard,body.lq-liquid .bcard,body.lq-liquid .drift,body.lq-liquid .q,body.lq-liquid .out,body.lq-liquid .tchat{background:linear-gradient(180deg,rgba(255,255,255,.075),rgba(255,255,255,.02));border:1px solid rgba(220,228,240,.16);border-radius:16px;box-shadow:inset 0 1px 0 rgba(255,255,255,.09),0 12px 30px -24px rgba(0,0,0,.9)}
|
|
99
102
|
body.lq-liquid.lq-light .task,body.lq-liquid.lq-light .agent,body.lq-liquid.lq-light .rcard,body.lq-liquid.lq-light .bcard,body.lq-liquid.lq-light .drift,body.lq-liquid.lq-light .q,body.lq-liquid.lq-light .out,body.lq-liquid.lq-light .tchat{background:linear-gradient(180deg,#FFFFFF,#F2F4F8);border-color:rgba(20,30,45,.12);box-shadow:inset 0 1px 0 #FFFFFF,0 12px 28px -24px rgba(20,30,45,.5)}
|
|
100
|
-
body.lq-liquid button.act{background
|
|
101
|
-
body.lq-liquid button.
|
|
103
|
+
body.lq-liquid button.act{background:#F3F0EA;color:#0A0B0D;border:0;border-radius:999px;box-shadow:none;font-weight:600}
|
|
104
|
+
body.lq-liquid button.act:hover{background:#FFFFFF}
|
|
105
|
+
body.lq-liquid.lq-light button.act{background:#151A21;color:#FFFFFF}
|
|
106
|
+
body.lq-liquid.lq-look-pearl button.act,body.lq-look-pearl #lqgo.on,body.lq-liquid.lq-look-pearl .updatebar.show{background:linear-gradient(180deg,#FFFFFF 0%,#D9DDE2 45%,#9AA1AA 55%,#E8EAED 100%);color:#1B1E23;box-shadow:0 2px 6px rgba(0,0,0,.18),inset 0 0 0 1px rgba(0,0,0,.08)}
|
|
107
|
+
body.lq-liquid button.ghost{background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.14);border-radius:999px;color:var(--bone);backdrop-filter:blur(6px)}
|
|
102
108
|
body.lq-liquid.lq-light button.ghost{background:rgba(255,255,255,.7);border-color:rgba(20,30,45,.18)}
|
|
103
109
|
body.lq-liquid input,body.lq-liquid select,body.lq-liquid textarea{border-radius:12px;border:1px solid rgba(220,228,240,.2);background:rgba(255,255,255,.04);color:var(--bone)}
|
|
104
110
|
body.lq-liquid.lq-light input,body.lq-liquid.lq-light select,body.lq-liquid.lq-light textarea{background:#FFFFFF;border-color:rgba(20,30,45,.18)}
|
|
@@ -106,7 +112,8 @@ body.lq-liquid input:focus,body.lq-liquid select:focus,body.lq-liquid textarea:f
|
|
|
106
112
|
body.lq-liquid .tgroup{color:#C9D1DC;letter-spacing:.16em}
|
|
107
113
|
body.lq-liquid.lq-light .tgroup{color:#3B4656}
|
|
108
114
|
body.lq-liquid h2,body.lq-liquid h3{letter-spacing:-.01em}
|
|
109
|
-
body.lq-liquid .fchip.on{background
|
|
115
|
+
body.lq-liquid .fchip.on{background:#F3F0EA;color:#0A0B0D;border-color:#F3F0EA}
|
|
116
|
+
body.lq-liquid.lq-light .fchip.on{background:#151A21;color:#FFFFFF;border-color:#151A21}
|
|
110
117
|
body.lq-contrast.lq-liquid .task,body.lq-contrast.lq-liquid .agent{border:2px solid currentColor}
|
|
111
118
|
|
|
112
119
|
/* the work scene: tasks and agents in the liquid, with spheres revolving round what's at work */
|
|
@@ -128,7 +135,8 @@ body.lq-pooled #lqgroups{display:none}
|
|
|
128
135
|
@keyframes lqorbit{to{transform:rotate(360deg)}}
|
|
129
136
|
@media (prefers-reduced-motion: reduce){.lqorbit span{animation:none}.lqorbit span:nth-child(2){transform:rotate(120deg)}.lqorbit span:nth-child(3){transform:rotate(240deg)}}
|
|
130
137
|
#lqgo,#lqback{display:none}
|
|
131
|
-
body.lq-work #lqgo.on{display:inline-flex;position:absolute;left:50%;bottom:86px;transform:translateX(-50%);z-index:4;align-items:center;gap:8px;min-height:52px;padding:0 26px;border-radius:999px;border:
|
|
138
|
+
body.lq-work #lqgo.on{display:inline-flex;position:absolute;left:50%;bottom:86px;transform:translateX(-50%);z-index:4;align-items:center;gap:8px;min-height:52px;padding:0 26px;border-radius:999px;border:0;background:#F3F0EA;color:#06080B;font:700 16px var(--sans);letter-spacing:.02em;cursor:pointer;box-shadow:0 16px 40px -16px rgba(0,0,0,.75)}
|
|
139
|
+
body.lq-light.lq-work #lqgo.on{background:#151A21;color:#FFFFFF}
|
|
132
140
|
body.lq-work #lqback{display:inline-flex;position:absolute;left:16px;top:52px;z-index:4;align-items:center;min-height:44px;padding:0 16px;border-radius:999px;border:1px solid rgba(220,228,240,.3);background:rgba(4,5,7,.6);backdrop-filter:blur(8px);color:#EEF2F8;font:600 14px var(--sans);cursor:pointer}
|
|
133
141
|
body.lq-light.lq-work #lqback{background:rgba(255,255,255,.8);color:#10151C;border-color:rgba(20,30,45,.2)}
|
|
134
142
|
body.lq-pooled #lqgo,body.lq-pooled #lqback,body.lq-pooled #lqorbits,body.lq-work #lqgroups{display:none}
|
|
@@ -205,7 +213,8 @@ a{color:var(--green);cursor:pointer}.hidden{display:none}
|
|
|
205
213
|
.note{font-size:12px;margin-top:10px}.ok{color:var(--green)}.err{color:var(--amber)}
|
|
206
214
|
.updatebar{display:none;align-items:center;gap:12px;padding:9px 16px;font-size:13px;font-weight:600;background:var(--green);color:var(--ink)}
|
|
207
215
|
.updatebar.show{display:flex}
|
|
208
|
-
body.lq-liquid .updatebar.show{position:fixed;top:10px;left:50%;transform:translateX(-50%);width:max-content;max-width:calc(100vw - 24px);z-index:20;border-radius:999px;border:
|
|
216
|
+
body.lq-liquid .updatebar.show{position:fixed;top:10px;left:50%;transform:translateX(-50%);width:max-content;max-width:calc(100vw - 24px);z-index:20;border-radius:999px;border:0;background:#F3F0EA;color:#06080B;box-shadow:0 14px 40px -14px rgba(0,0,0,.7)}
|
|
217
|
+
body.lq-liquid.lq-light .updatebar.show{background:#151A21;color:#FFFFFF}
|
|
209
218
|
body.lq-liquid .updatebar.show.reconnect{background:var(--amber)}
|
|
210
219
|
body.lq-liquid #appbar.updatebar.show{top:58px}
|
|
211
220
|
.updatebar.reconnect{background:var(--amber)}
|
|
@@ -318,6 +327,63 @@ label.check input{width:auto}
|
|
|
318
327
|
.scrbox span{position:absolute;left:0;top:0;font-size:11px;background:var(--green);color:var(--ink);padding:1px 6px;white-space:nowrap}
|
|
319
328
|
.scrbox.draw{border:2px dashed var(--amber);background:rgba(242,165,65,.14)}
|
|
320
329
|
.board{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;margin-top:12px}
|
|
330
|
+
|
|
331
|
+
/* ---- finer details: the controls every panel shares, in the look ---- */
|
|
332
|
+
.iconbtn{display:inline-grid;place-items:center;width:36px;height:36px;padding:0;border-radius:999px;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.05);color:var(--bone);cursor:pointer;transition:background-color .15s ease}
|
|
333
|
+
.iconbtn:hover{background:rgba(255,255,255,.1)}
|
|
334
|
+
.iconbtn.wide{display:inline-flex;gap:7px;width:auto;padding:0 14px;font:500 13px var(--sans)}
|
|
335
|
+
.iconbtn:focus-visible,.seg button:focus-visible{outline:2px solid currentColor;outline-offset:2px}
|
|
336
|
+
body.lq-light .iconbtn{border-color:rgba(21,26,33,.12);background:rgba(21,26,33,.04)}
|
|
337
|
+
body.lq-light .iconbtn:hover{background:rgba(21,26,33,.08)}
|
|
338
|
+
.seg{display:inline-flex;gap:2px;padding:3px;border-radius:999px;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.1)}
|
|
339
|
+
.seg button{min-height:30px;padding:0 13px;border:0;border-radius:999px;background:transparent;color:var(--faint);font:500 12.5px var(--sans);cursor:pointer}
|
|
340
|
+
.seg button[aria-checked='true']{background:#F3F0EA;color:#0A0B0D}
|
|
341
|
+
body.lq-light .seg{background:rgba(21,26,33,.04);border-color:rgba(21,26,33,.1)}
|
|
342
|
+
body.lq-light .seg button[aria-checked='true']{background:#151A21;color:#FFFFFF}
|
|
343
|
+
body.lq-liquid select{-webkit-appearance:none;appearance:none;padding:8px 34px 8px 12px;border-radius:12px;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.05) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238A919B' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat right 13px center;color:var(--bone);font:500 13px var(--sans);color-scheme:dark;cursor:pointer}
|
|
344
|
+
body.lq-liquid select option{background:#131518;color:#ECE9E4}
|
|
345
|
+
body.lq-liquid.lq-light select{border-color:rgba(21,26,33,.14);background-color:rgba(21,26,33,.04);color-scheme:light}
|
|
346
|
+
body.lq-liquid.lq-light select option{background:#FFFFFF;color:#151A21}
|
|
347
|
+
/* the Dashboard's head */
|
|
348
|
+
.bhead{display:flex;flex-wrap:wrap;align-items:center;gap:12px;margin:2px 0 4px}
|
|
349
|
+
.bsumw{flex:1;min-width:220px}
|
|
350
|
+
.bsum{font:500 16px/1.4 var(--sans);color:var(--bone);text-wrap:balance}
|
|
351
|
+
.bsum b{font-weight:600}
|
|
352
|
+
.bsub{font-size:12.5px;color:var(--faint);margin-top:2px}
|
|
353
|
+
.blane .blsub{font-size:11.5px;color:var(--faint);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-top:1px}
|
|
354
|
+
/* Connections */
|
|
355
|
+
.cxh{display:flex;flex-direction:column;gap:2px;margin:26px 0 12px}
|
|
356
|
+
.cxh span{font:600 15px var(--sans);color:var(--bone)}
|
|
357
|
+
.cxh small{font-size:12.5px;color:var(--faint)}
|
|
358
|
+
.cxg{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:18px 28px}
|
|
359
|
+
.cxc{min-width:0}
|
|
360
|
+
.cxl{font:500 11px var(--sans);letter-spacing:.1em;text-transform:uppercase;color:var(--faint);margin-bottom:4px}
|
|
361
|
+
.lnk.cx{display:flex;align-items:center;gap:0}
|
|
362
|
+
.cx .lbtn{all:unset;box-sizing:border-box;display:flex;align-items:center;gap:9px;flex:1;min-width:0;min-height:32px;padding:4px 8px;margin-left:-8px;border-radius:10px;cursor:pointer}
|
|
363
|
+
.cx .lbtn:hover{background:rgba(255,255,255,.05)}
|
|
364
|
+
.cx .lbtn:focus-visible{outline:2px solid currentColor;outline-offset:1px}
|
|
365
|
+
body.lq-light .cx .lbtn:hover{background:rgba(21,26,33,.05)}
|
|
366
|
+
.cxb{flex:none;width:11px;height:11px;border-radius:50%;box-shadow:inset 0 0 0 1.5px var(--faint);opacity:.6}
|
|
367
|
+
.cx.ok .cxb{opacity:1;box-shadow:0 0 0 1px rgba(255,255,255,.08);background:radial-gradient(circle at 34% 28%,#FFFFFF 0,#C9CFD9 20%,#5B626D 58%,#121418 100%)}
|
|
368
|
+
body.lq-light .cx.ok .cxb{box-shadow:0 1px 2px rgba(0,0,0,.25);background:radial-gradient(circle at 34% 28%,#FFFFFF 0,#E3E6EA 22%,#9BA2AC 55%,#3A3F46 100%)}
|
|
369
|
+
.cx.signin .cxb,.cx.signedout .cxb,.cx.error .cxb{opacity:1;box-shadow:inset 0 0 0 2px var(--amber),0 0 10px rgba(242,165,65,.35)}
|
|
370
|
+
.cxn{font:500 13.5px var(--sans);color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
371
|
+
.cx.ok .cxn{color:var(--bone)}
|
|
372
|
+
.cxs{margin-left:auto;padding-left:8px;font-size:11.5px;color:var(--faint);white-space:nowrap}
|
|
373
|
+
.cx.signin .cxs,.cx.signedout .cxs,.cx.error .cxs{color:var(--amber)}
|
|
374
|
+
.cx.off .cxs{opacity:0;transition:opacity .15s ease}
|
|
375
|
+
.cx.off:hover .cxs,.cx.off .lbtn:focus-visible .cxs,body.lq-touch .cx.off .cxs{opacity:1}
|
|
376
|
+
.cx .lmore,.cx .lrm{display:none;border:0;background:transparent;color:var(--faint);font:500 12px var(--sans);padding:4px 6px;border-radius:8px;cursor:pointer}
|
|
377
|
+
.cx .lmore:hover{color:var(--bone)}.cx .lrm:hover{color:var(--amber)}
|
|
378
|
+
.cx:hover .lmore,.cx:hover .lrm,.cx:focus-within .lmore,.cx:focus-within .lrm,body.lq-touch .cx .lmore,body.lq-touch .cx .lrm{display:inline-flex}
|
|
379
|
+
/* the card list, quiet */
|
|
380
|
+
body.lq-liquid .board .bcard{background:rgba(255,255,255,.035);border:1px solid rgba(255,255,255,.08);border-radius:16px;box-shadow:none}
|
|
381
|
+
body.lq-liquid.lq-light .board .bcard{background:rgba(255,255,255,.6);border-color:rgba(21,26,33,.08)}
|
|
382
|
+
body.lq-liquid .board .bcard.has{border-color:rgba(242,165,65,.35)}
|
|
383
|
+
body.lq-liquid .bcard .bc{font:300 30px/1.1 var(--sans);letter-spacing:-.02em;color:var(--faint)}
|
|
384
|
+
body.lq-liquid .bcard.has .bc{color:var(--amber)}
|
|
385
|
+
body.lq-liquid .bcard .bbrief{border-left:2px solid var(--line);background:transparent;padding:2px 0 2px 12px;color:var(--text)}
|
|
386
|
+
body.lq-liquid #lqsinkrow{margin:-6px -4px 8px}
|
|
321
387
|
/* the Dashboard as a stream: time runs left to right, a current per feed, now on the right */
|
|
322
388
|
.bstream{position:relative;margin-top:10px;border-radius:18px;overflow:hidden;isolation:isolate;background:#08090B;box-shadow:0 0 0 1px rgba(255,255,255,.08)}
|
|
323
389
|
body.lq-light .bstream{background:#EBEEF0;box-shadow:0 0 0 1px rgba(21,26,33,.08)}
|
|
@@ -325,7 +391,7 @@ body.lq-look-pearl .bstream{background:#EDECE9}
|
|
|
325
391
|
.bstream.none{display:none}
|
|
326
392
|
#bsc{position:absolute;inset:0;width:100%;height:100%;display:block}
|
|
327
393
|
#bsov{position:absolute;inset:0}
|
|
328
|
-
body.lq-liquid .bcard.blane{position:absolute;left:16px;width:
|
|
394
|
+
body.lq-liquid .bcard.blane{position:absolute;left:16px;width:186px;padding:0;border:0;background:transparent;box-shadow:none;transform:translateY(-50%)}
|
|
329
395
|
.blane .bln{font:600 14px/1.2 var(--sans);color:var(--bone);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
330
396
|
.blane .blc{font-size:12px;color:var(--faint);margin-top:1px}
|
|
331
397
|
.blane .blc b{color:var(--amber);font-weight:600}
|
|
@@ -400,7 +466,7 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
|
|
|
400
466
|
<button class="tab" data-tab="settings">Settings</button>
|
|
401
467
|
</div>
|
|
402
468
|
<main>
|
|
403
|
-
<div id="lqsinkrow"><button class="
|
|
469
|
+
<div id="lqsinkrow"><button class="iconbtn wide" type="button" id="lqsink" title="close it: it sinks back into the liquid (Esc, or right-click)"><svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true"><path d="M2 2l8 8M10 2l-8 8" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>Close</button></div>
|
|
404
470
|
<section id="panel-map">
|
|
405
471
|
<div class="profile muted" id="profile">Mapping your work…</div>
|
|
406
472
|
<div class="maprow">
|
|
@@ -432,7 +498,7 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
|
|
|
432
498
|
</div>
|
|
433
499
|
</section>
|
|
434
500
|
<section id="panel-board" class="hidden">
|
|
435
|
-
<div class="
|
|
501
|
+
<div class="bhead"><div class="bsumw"><div id="boardsum" class="bsum">Everything you watch, side by side.</div><div id="boardsub" class="bsub"></div></div><div class="seg" id="boardwin" role="radiogroup" aria-label="How far back"><button type="button" role="radio" data-h="24" aria-checked="true">24 hours</button><button type="button" role="radio" data-h="72" aria-checked="false">3 days</button><button type="button" role="radio" data-h="168" aria-checked="false">7 days</button></div><button type="button" class="iconbtn" id="boardrefresh" title="Refresh" aria-label="Refresh"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M13 8a5 5 0 1 1-1.5-3.6M13 2.5V5h-2.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></button></div>
|
|
436
502
|
<div id="boardmsg"></div>
|
|
437
503
|
<div id="bstream" class="bstream"><canvas id="bsc" aria-hidden="true"></canvas><div id="bsov"></div></div>
|
|
438
504
|
<div id="boardtalk"></div>
|
|
@@ -969,20 +1035,20 @@ var pf=$('pforget');if(pf)pf.addEventListener('click',function(){if(typeof confi
|
|
|
969
1035
|
// linked, amber waiting for you to sign in (or signed out), green linked. Shown
|
|
970
1036
|
// in Settings, and on the Dashboard until something is linked.
|
|
971
1037
|
var LINKS=null;
|
|
972
|
-
var LINKWORD={ok:'linked',signin:'sign in, then close
|
|
1038
|
+
var LINKWORD={ok:'linked',signin:'sign in, then close it',signedout:'signed out',error:'needs a fix'};
|
|
973
1039
|
function escQ(t){return esc(t).replace(/'/g,''');}
|
|
974
|
-
|
|
1040
|
+
// Connections: a column per kind of site; each site a bead in the look: solid when
|
|
1041
|
+
// linked, amber when it wants you to sign in, hollow when it isn't linked. Link,
|
|
1042
|
+
// check (↻) and Unlink show on hover (always on touch).
|
|
1043
|
+
function linksHtml(d,board){var h="<div class='cxh'><span>Connections</span><small>"+(d.linked?"What Symbiot watches for you. Link more to see what arrives there, here and in Week and Standup.":"Link your work to see what arrives there, here and in Week and Standup.")+"</small></div>";
|
|
975
1044
|
if(d.error)h+="<div class='note err'>"+esc(d.error)+"</div>";
|
|
976
|
-
d.groups.
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
(on?"<span class='lst'>"+esc(LINKWORD[it.state]||'')+"</span><button class='lmore' title='check it now'>↻</button><button class='lrm' title='unlink: stop watching it and stop trusting it'>×</button>":"")+"</span>";});
|
|
980
|
-
h+="</div>";});return h;}
|
|
1045
|
+
h+="<div class='cxg'>"+d.groups.map(function(g){return "<div class='cxc'><div class='cxl'>"+esc(g)+"</div>"+d.items.filter(function(x){return x.group===g;}).map(function(it){var on=it.state!=='off';
|
|
1046
|
+
return "<div class='lnk cx "+escQ(it.state)+"' data-id='"+escQ(it.id)+"'><button type='button' class='lbtn' title='"+escQ(it.note||(on?'open '+it.url+' to sign in again':'sign in at '+it.url+' and link it'))+"'><span class='cxb' aria-hidden='true'></span><span class='cxn'>"+esc(it.name)+"</span><span class='cxs'>"+(on?esc(LINKWORD[it.state]||''):'Link')+"</span></button>"+
|
|
1047
|
+
(on?"<button type='button' class='lmore' title='check it now' aria-label='Check "+escQ(it.name)+" now'>↻</button><button type='button' class='lrm' title='unlink: stop watching it and stop trusting it'>Unlink</button>":"")+"</div>";}).join('')+"</div>";}).join('')+"</div>";return h;}
|
|
981
1048
|
function linksOut(html){var o=document.getElementById('linksmsg');if(o)o.innerHTML=html;var b=document.getElementById('boardmsg');if(b&&LINKS&&!LINKS.linked)b.innerHTML=html;}
|
|
982
1049
|
function renderLinks(){if(!LINKS)return;['links','boardlinks'].forEach(function(id){var el=document.getElementById(id);if(!el)return;
|
|
983
|
-
if(id==='boardlinks'&&LINKS.linked){el.innerHTML='';return;}
|
|
984
1050
|
el.innerHTML=linksHtml(LINKS,id==='boardlinks');
|
|
985
|
-
el.querySelectorAll('.lnk').forEach(function(sp){var lid=sp.getAttribute('data-id'),name=sp.querySelector('.lbtn').textContent;
|
|
1051
|
+
el.querySelectorAll('.lnk').forEach(function(sp){var lid=sp.getAttribute('data-id'),nm=sp.querySelector('.cxn'),name=nm?nm.textContent:sp.querySelector('.lbtn').textContent;
|
|
986
1052
|
sp.querySelector('.lbtn').addEventListener('click',function(){linksOut("<div class='note muted'>Opening "+esc(name)+"…</div>");
|
|
987
1053
|
api('/api/links/link',{id:lid}).then(function(r){if(!r||r.error){linksOut("<div class='note err'>"+esc(name)+": "+esc((r&&r.error)||'failed')+"</div>");loadLinks();return;}
|
|
988
1054
|
linksOut("<div class='note ok'>Opened "+esc(name)+" in Symbiot's browser. Sign in there as you normally do, then close that window: Symbiot checks it within a minute.</div>");loadLinks();
|
|
@@ -1158,14 +1224,15 @@ api('/api/watch/open-chat',{id:n.id}).then(function(x){b.disabled=false;if(!x||x
|
|
|
1158
1224
|
say('ok',"✓ Opened WhatsApp in Symbiot's browser, in a window of its own. The chat shows your agent's reply in its message box: read it, change it if you like, and send it there. Close the window when you're done, so Watch can read WhatsApp again.");}).catch(function(e){b.disabled=false;say('err',esc(String((e&&e.message)||e)));});}
|
|
1159
1225
|
var DRAFTING="✓ Your agent is drafting a reply to it. It opens the email in your inbox through Screens, writes the reply and leaves it in Drafts for you to read and send: it never presses Send. Follow it, and answer anything it asks, in the Agents tab.";
|
|
1160
1226
|
// ---- Dashboard: a card per page you watch (your inbox, GitHub, WhatsApp…), what's new on each ----
|
|
1161
|
-
var BOARD=null;
|
|
1227
|
+
var BOARD=null;
|
|
1162
1228
|
// a chat's line says who its last message is from (watch.mjs fromOf): only unread ones count as waiting on you
|
|
1163
1229
|
var CHATFROM={them:'unread from them',you:'you sent the last one',unknown:'nothing unread, maybe yours'};
|
|
1164
1230
|
// Talk it over (watch.mjs boardChat): a chat on a card, open by its id, with what's half typed kept across a refresh
|
|
1165
1231
|
var BTALK={},BTALKDRAFT={},BTALKBUSY=0;
|
|
1166
1232
|
var TALKHINT="<div class='muted' style='font-size:12px'>Go over what's new here with your AI: what needs you, and what to say to whom. Then click <b>Draft a reply</b> on one, and your agent writes it the way you agreed here. Your AI is sent what this card lists (for mail: the sender, subject and preview).</div>";
|
|
1167
1233
|
function talking(){var f=document.activeElement;return BTALKBUSY>0||!!(f&&f.closest&&f.closest('.btalk'));}
|
|
1168
|
-
|
|
1234
|
+
var BOARDH=24;
|
|
1235
|
+
function loadBoard(){api('/api/watch/board?hours='+BOARDH).then(function(b){if(b&&b.cards){BOARD=b;if(!talking())renderBoard();}});}
|
|
1169
1236
|
function boardMsg(cls,html){$('boardmsg').innerHTML="<div class='note "+cls+"'>"+html+"</div>";}
|
|
1170
1237
|
|
|
1171
1238
|
// ---- the Dashboard as a stream ----------------------------------------------------
|
|
@@ -1175,38 +1242,41 @@ function boardMsg(cls,html){$('boardmsg').innerHTML="<div class='note "+cls+"'>"
|
|
|
1175
1242
|
// the list below, wired by renderBoard's wire); a feed's name carries its Seen,
|
|
1176
1243
|
// Talk and Check now. Drawn in the look's liquid, with its own canvas.
|
|
1177
1244
|
var BS={G:null,beads:[],hot:null,on:false,b:null},BWIRE=null;
|
|
1245
|
+
// A feed's short name: "Inbox - you@x.com - x Mail" reads as Inbox, with the address under it.
|
|
1246
|
+
function bsName(c){var parts=String(c.name||'').split(' - '),acct=parts.filter(function(x){return x.indexOf('@')>=0;})[0]||'';return {n:parts[0]||c.name||'',sub:acct};}
|
|
1178
1247
|
function bsGeom(){var el=$('bstream'),w=(el&&el.clientWidth)||900,n=((BS.b&&BS.b.cards)||[]).length,narrow=w<720;
|
|
1179
|
-
return {w:w,h:Math.max(
|
|
1248
|
+
return {w:w,h:Math.max(300,80+n*118+44),x0:narrow?130:220,x1:w-(narrow?36:280),narrow:narrow,n:n};}
|
|
1180
1249
|
function bsBuild(b){BS.b=b;var ov=$('bsov'),el=$('bstream');if(!ov||!el)return;var cs=b.cards||[];if(!cs.length){ov.innerHTML='';return;}
|
|
1181
|
-
var g=bsGeom();if(el.style)el.style.height=g.h+'px';var now=Date.now(),H=(b.hours||24)*3600000,laneY=function(i){return 60+(g.h-110)*(i+0.5)/Math.max(1,g.n);};
|
|
1250
|
+
var g=bsGeom();BS.gw=g.w;if(el.style)el.style.height=g.h+'px';var now=Date.now(),H=(b.hours||24)*3600000,laneY=function(i){return 60+(g.h-110)*(i+0.5)/Math.max(1,g.n);};
|
|
1182
1251
|
var html='';cs.forEach(function(c,i){var y=laneY(i);
|
|
1183
|
-
html+="<div class='bline' style='left:"+(g.x0-24)+"px;width:"+(g.x1-g.x0+
|
|
1184
|
-
html+="<div class='bcard blane' data-i='"+i+"' style='top:"+Math.round(y)+"px'><div class='bln' title='"+esc(c.url
|
|
1252
|
+
html+="<div class='bline' style='left:"+(g.x0-24)+"px;width:"+(g.x1-g.x0+24)+"px;top:"+Math.round(y)+"px'></div>";
|
|
1253
|
+
var nm=bsName(c);html+="<div class='bcard blane' data-i='"+i+"' style='top:"+Math.round(y)+"px'><div class='bln' title='"+esc(c.name+(c.url?' \u00b7 '+c.url:''))+"'>"+esc(nm.n)+"</div>"+(nm.sub?"<div class='blsub'>"+esc(nm.sub)+"</div>":"")+"<div class='blc' title='"+(c.checked?"read "+agoTxt(c.checked):"")+"'>"+(c.count?"<b>"+esc(c.label)+"</b>":"quiet")+"</div><div class='bla'>"+(c.count?"<button class='bseen' title='set this feed back to 0: only what comes in after counts'>Seen</button>":"")+"<button class='btalkbtn' title='talk what's new here over with your AI before you draft a reply'>Talk"+((c.chat||[]).length?" "+Math.ceil(c.chat.length/2):"")+"</button><button class='bcheck' title='read it now'>Check now</button></div>"+(c.error?"<div class='ble'>"+esc(c.error)+"</div>":"")+"</div>";
|
|
1185
1254
|
if(c.brief&&!g.narrow)html+="<div class='blb' style='top:"+Math.round(y)+"px'>"+esc(c.brief.text.length>150?c.brief.text.slice(0,149)+'…':c.brief.text)+"</div>";});
|
|
1186
1255
|
var marks=b.hours===24?[18,12,6]:b.hours===72?[48,24]:[120,72,24];
|
|
1187
1256
|
marks.forEach(function(m){html+="<span class='btick' style='left:"+Math.round(g.x0+(1-m/(b.hours||24))*(g.x1-g.x0))+"px'>"+(m<48?m+'h ago':Math.round(m/24)+' days ago')+"</span>";});
|
|
1188
1257
|
html+="<span class='btick' style='left:"+g.x1+"px'>now</span><div class='bnow' style='left:"+g.x1+"px'></div>";
|
|
1189
1258
|
var old={};BS.beads.forEach(function(x){old[x.key]=x;});BS.beads=[];
|
|
1190
1259
|
cs.forEach(function(c,i){(c.items||[]).forEach(function(n,j){var age=Math.max(0,now-(n.ts||now));if(age>H)return;var key=c.id+':'+(n.id||j),o=old[key];
|
|
1191
|
-
BS.beads.push({key:key,i:i,j:j,n:n,need:!!n.need&&!!(n.mail||n.chat),tx:g.x0+(1-age/H)*(g.x1-g.x0),ty:laneY(i),tr:n.need&&(n.mail||n.chat)?13:n.read?6:9,x:o?o.x:g.x1,y:o?o.y:laneY(i),r:o?o.r:0});});
|
|
1260
|
+
BS.beads.push({key:key,i:i,j:j,n:n,need:!!n.need&&!!(n.mail||n.chat),tx:g.x0+(1-age/H)*(g.x1-g.x0),ty:laneY(i),tr:n.need&&(n.mail||n.chat)?13:n.read?6:9,x:o?o.x:g.x1,y:o?o.y:laneY(i),r:o?o.r:0});});
|
|
1261
|
+
(c.past||[]).forEach(function(n,j){var age=Math.max(0,now-(n.ts||now));if(age>H)return;var key=c.id+':p:'+(n.id||j),o=old[key];BS.beads.push({key:key,i:i,j:-1-j,n:n,past:true,need:false,tx:g.x0+(1-age/H)*(g.x1-g.x0),ty:laneY(i),tr:4.5,x:o?o.x:g.x1,y:o?o.y:laneY(i),r:o?o.r:0});});});
|
|
1192
1262
|
html+=BS.beads.map(function(x,k){return "<button type='button' class='bbead' data-k='"+k+"' aria-label='"+esc(x.n.text||'')+"'></button>";}).join('');
|
|
1193
1263
|
html+="<div class='btip' role='dialog' aria-live='polite'></div>";ov.innerHTML=html;
|
|
1194
1264
|
BS.btns=[];ov.querySelectorAll('.bbead').forEach(function(bt){BS.btns.push(bt);var k=+bt.getAttribute('data-k');bt.addEventListener('pointerenter',function(){bsShow(k);});bt.addEventListener('focus',function(){bsShow(k);});bt.addEventListener('click',function(){bsShow(k);});});
|
|
1195
1265
|
if(el.addEventListener&&!BS.wired){BS.wired=true;el.addEventListener('pointerleave',function(){bsShow(null);});el.addEventListener('keydown',function(e){if(e&&e.key==='Escape')bsShow(null);});}
|
|
1196
1266
|
BS.hot=null;bsStart();}
|
|
1197
1267
|
function bsShow(k){var tip=document.querySelector('#bsov .btip');if(!tip)return;BS.hot=k;if(k==null||!BS.beads[k]){tip.className='btip';return;}
|
|
1198
|
-
var x=BS.beads[k],c=BS.b.cards[x.i],n=x.n;
|
|
1199
|
-
tip.innerHTML="<div class='bcard' data-i='"+x.i+"'><div class='bi' data-j='"+x.j+"'><div class='btf'>"+esc(n.name||c.
|
|
1268
|
+
var x=BS.beads[k],c=BS.b.cards[x.i],n=x.n;if(x.past){tip.innerHTML="<div class='bcard'><div class='bi'><div class='btf'>"+esc(n.name||bsName(c).n)+" <span>· "+esc(bsName(c).n)+" · "+agoTxt(n.ts)+" · seen</span></div><div class='btx' style='margin-bottom:0'>"+esc(n.text||'')+"</div></div></div>";tip.className='btip on';return;}
|
|
1269
|
+
tip.innerHTML="<div class='bcard' data-i='"+x.i+"'><div class='bi' data-j='"+x.j+"'><div class='btf'>"+esc(n.name||bsName(c).n)+" <span>· "+esc(bsName(c).n)+" · "+agoTxt(n.ts)+(n.from&&CHATFROM[n.from]?" · "+CHATFROM[n.from]:"")+(n.read?" · read":"")+"</span></div><div class='btx'>"+esc(n.text||'')+"</div><div class='bta'>"+(n.mail||n.chat?"<button class='bdraft'>"+(n.drafted?"Drafted · again":"Draft a reply")+"</button>":"")+(n.href?"<button class='bopen"+(n.mail||n.chat?" quiet":"")+"'>Open</button>":"")+(n.chat&&n.drafted?"<button class='bopenwa quiet'>Open in WhatsApp</button>":"")+"</div></div></div>";
|
|
1200
1270
|
tip.className='btip on';if(BWIRE)BWIRE(tip);}
|
|
1201
1271
|
function bsStart(){if(BS.on)return;var raf=typeof window.requestAnimationFrame==='function'?function(f){return window.requestAnimationFrame(f);}:null;if(!raf)return;BS.on=true;
|
|
1202
1272
|
var loop=function(){var p=$('panel-board'),el=$('bstream');if(!p||!el||!p.offsetParent||(el.classList&&el.classList.contains('none'))){BS.on=false;return;}bsFrame();raf(loop);};raf(loop);}
|
|
1203
|
-
function bsFrame(){var el=$('bstream'),cv=$('bsc');if(!el||!cv)return;if(!BS.G)BS.G=lqGL(cv,40);var g=bsGeom()
|
|
1273
|
+
function bsFrame(){var el=$('bstream'),cv=$('bsc');if(!el||!cv)return;if(!BS.G)BS.G=lqGL(cv,40);var g=bsGeom();if(BS.b&&BS.gw!==g.w){bsBuild(BS.b);g=bsGeom();}var t=LQ.t||0,still=LQ.theme&&LQ.theme.still;
|
|
1204
1274
|
BS.beads.forEach(function(x,k){var hot=BS.hot===k;x.x+=(x.tx-x.x)*0.12;x.y+=((x.ty+(still?0:Math.sin(t*0.8+k*1.3)*2))-x.y)*0.12;x.r+=((x.tr*(hot?1.35:1))-x.r)*0.15;
|
|
1205
1275
|
var bt=BS.btns&&BS.btns[k];if(bt&&bt.style){var R=Math.max(14,x.r+6);bt.style.width=bt.style.height=(2*R)+'px';bt.style.transform='translate('+Math.round(x.x-R)+'px,'+Math.round(x.y-R)+'px)';}});
|
|
1206
1276
|
var tip=document.querySelector('#bsov .btip');if(tip&&BS.hot!=null&&BS.beads[BS.hot]){var x=BS.beads[BS.hot],tw=tip.offsetWidth||300,th=tip.offsetHeight||120,tx=Math.max(12,Math.min(g.w-tw-12,x.x-tw/2)),ty=x.y-x.r-th-14;if(ty<8)ty=x.y+x.r+14;tip.style.transform='translate('+Math.round(tx)+'px,'+Math.round(ty)+'px)';}
|
|
1207
1277
|
var G=BS.G;if(!G)return;var gl=G.gl,d=Math.min(window.devicePixelRatio||1,3),W=Math.max(1,Math.floor(g.w*d)),H=Math.max(1,Math.floor(g.h*d));if(cv.width!==W||cv.height!==H){cv.width=W;cv.height=H;gl.viewport(0,0,W,H);}
|
|
1208
1278
|
var o=G.out;for(var i=0;i<o.length;i++)o[i]=0;var k=0,put=function(px,py,r,a){if(k>=40)return;o[k*4]=px/g.w;o[k*4+1]=py/g.h;o[k*4+2]=Math.max(0,r);o[k*4+3]=a;k++;};
|
|
1209
|
-
(BS.b.cards||[]).forEach(function(c,i){put(g.x1
|
|
1279
|
+
(BS.b.cards||[]).forEach(function(c,i){put(g.x1,60+(g.h-110)*(i+0.5)/Math.max(1,g.n),c.count?9+2*Math.min(c.count,6):5,0);});
|
|
1210
1280
|
BS.beads.forEach(function(x){put(x.x,x.y,x.r,x.need?1:0);});
|
|
1211
1281
|
var th=LQ.theme||{},U=G.U;gl.uniform2f(U.uRes,W,H);gl.uniform1f(U.uT,t);gl.uniform1f(U.uDpr,d);gl.uniform4fv(U.uB,o);gl.uniform3f(U.uRip,0,0,-10);gl.uniform1f(U.uStyle,th.look==='glass'?0:th.look==='pearl'?2:1);gl.uniform1f(U.uContrast,th.contrast?1:0);gl.uniform1f(U.uExposure,th.night?0.82:1);gl.drawArrays(gl.TRIANGLES,0,3);}
|
|
1212
1282
|
function renderBoard(){var b=BOARD,cs=b.cards,el=$('board');
|
|
@@ -1216,10 +1286,11 @@ if(!cs.length){$('boardsum').textContent='Everything you watch, side by side.';
|
|
|
1216
1286
|
el.innerHTML="<div style='grid-column:1/-1'><div class='note muted' style='margin-top:0'>Nothing watched yet. Map your inbox, a chat (web.whatsapp.com) or any page under <b>Screens</b> on the Map tab and click <b>Watch</b> on it, or click <b>Watch GitHub</b> there. Each one gets a card here with what's new on it.</div><div class='row' style='margin-top:8px'><button class='ghost' id='boardgo'>Go to Screens</button></div></div>";
|
|
1217
1287
|
$('boardgo').addEventListener('click',function(){setTab('map');var w=$('watchbox');if(w&&w.scrollIntoView)w.scrollIntoView({block:'center'});});return;}
|
|
1218
1288
|
var when=b.hours===24?'since yesterday':b.hours===72?'in the last 3 days':'in the last 7 days';
|
|
1289
|
+
var lastRead=0;cs.forEach(function(c){if(c.checked>lastRead)lastRead=c.checked;});var sub=$('boardsub');if(sub)sub.textContent=(lastRead?'Read '+agoTxt(lastRead)+' \u00b7 ':'')+cs.length+(cs.length===1?' feed':' feeds');
|
|
1219
1290
|
var waiting=cs.filter(function(c){return c.count;}).map(function(c){return c.label+(c.source==='page'?' on '+c.name:'');});
|
|
1220
1291
|
$('boardsum').innerHTML=waiting.length?"<b style='color:var(--bone)'>Waiting on you "+when+":</b> "+esc(waiting.join(', ')):"Nothing new "+when+" on the "+cs.length+" thing"+(cs.length===1?"":"s")+" you watch.";
|
|
1221
1292
|
el.innerHTML=cs.map(function(c,i){var nt=Math.ceil(((c.chat||[]).length)/2);
|
|
1222
|
-
return "<div class='bcard"+(c.count?" has":"")+"' data-i='"+i+"'><div class='bh'><span
|
|
1293
|
+
return "<div class='bcard"+(c.count?" has":"")+"' data-i='"+i+"'><div class='bh'><span class='bn' title='"+esc(c.url)+"'>"+esc(bsName(c).n)+"</span>"+(c.count?"<button class='ghost bseen' title='set this card back to 0: only what comes in after counts. It stays under Watching, and the other cards keep theirs' style='padding:4px 9px;font-size:12px'>Seen</button>":"")+"<button class='ghost btalkbtn' title='talk what's new here over with your AI before you draft a reply' style='padding:4px 9px;font-size:12px'>Talk"+(nt?" "+nt:"")+"</button><button class='ghost bcheck' title='read it now' style='padding:4px 9px;font-size:12px'>Check now</button></div>"
|
|
1223
1294
|
+"<div class='bc'>"+c.count+"</div><div class='bl'>"+esc(c.count?c.label.replace(/^\\d+ /,''):'nothing new')+" · "+(c.checked?"read "+agoTxt(c.checked):c.last?"tried "+agoTxt(c.last):"first read within a minute")+(c.via==='gh'?" · through gh":"")+"</div>"
|
|
1224
1295
|
+(c.error?"<div class='note err'>"+esc(c.error)+"</div>":"")
|
|
1225
1296
|
+(c.brief?"<div class='bbrief'>"+esc(c.brief.text)+"</div>":"")
|
|
@@ -1253,7 +1324,8 @@ el.querySelectorAll('.bopen').forEach(function(btn){btn.addEventListener('click'
|
|
|
1253
1324
|
el.querySelectorAll('.bdraft').forEach(function(btn){btn.addEventListener('click',function(){var n=item(btn);btn.disabled=true;
|
|
1254
1325
|
api('/api/watch/draft',{id:n.id}).then(function(x){btn.disabled=false;if(!x||x.error){boardMsg('err',esc((x&&x.error)||'failed'));return;}boardMsg('ok',x.chat?DRAFTING_CHAT:DRAFTING);loadWatchUI();}).catch(function(e){btn.disabled=false;boardMsg('err',esc(String((e&&e.message)||e)));});});});}
|
|
1255
1326
|
wire(el);wire($('boardtalk'));wire($('bsov'));BWIRE=wire;}
|
|
1256
|
-
$('boardrefresh').addEventListener('click',loadBoard)
|
|
1327
|
+
$('boardrefresh').addEventListener('click',loadBoard);
|
|
1328
|
+
var bw=$('boardwin');if(bw&&bw.querySelectorAll)bw.querySelectorAll('button').forEach(function(b){b.addEventListener('click',function(){BOARDH=+b.getAttribute('data-h')||24;bw.querySelectorAll('button').forEach(function(x){x.setAttribute('aria-checked',x===b?'true':'false');});loadBoard();});});
|
|
1257
1329
|
// A web page, mapped by itself in the hidden browser (headless.mjs): no capture,
|
|
1258
1330
|
// no dragging. It takes a few seconds, so the button says so meanwhile.
|
|
1259
1331
|
function mapUI(site,b){site=(site||'').trim();if(!site){screenErr('Type a site to map first: gmail, github.com/pulls or a web address.');return;}b.disabled=true;
|
|
@@ -1417,11 +1489,11 @@ window.addEventListener('focus',function(){heartbeat(true);}); // re-check for u
|
|
|
1417
1489
|
// Tasks and agents, in the liquid: what each agent is doing (spheres revolve round
|
|
1418
1490
|
// it while it works), what's done and waiting for your OK, and what's waiting its
|
|
1419
1491
|
// turn. One Go starts what's waiting. A tap opens the details; nothing else to read.
|
|
1420
|
-
function lqWork(){LQ.scene='work';LQ.mode='aware';var b=document.body;if(b&&b.classList){b.classList.add('lq-work');b.classList.remove('lq-pooled');}lqLoadWork();
|
|
1492
|
+
function lqWork(){lqRemember();LQ.scene='work';LQ.mode='aware';var b=document.body;if(b&&b.classList){b.classList.add('lq-work');b.classList.remove('lq-pooled');}lqLoadWork();
|
|
1421
1493
|
if(LQ.workTimer)clearInterval(LQ.workTimer);LQ.workTimer=setInterval(function(){if(LQ.scene==='work'&&LQ.mode!=='pool')lqLoadWork();},5000);}
|
|
1422
|
-
function lqHome(){LQ.scene='home';var b=document.body;if(b&&b.classList){b.classList.remove('lq-work');b.classList.remove('lq-pooled');}if(LQ.workTimer){clearInterval(LQ.workTimer);LQ.workTimer=null;}LQ.mode='aware';lqLoad(true);}
|
|
1494
|
+
function lqHome(){lqRemember();LQ.scene='home';var b=document.body;if(b&&b.classList){b.classList.remove('lq-work');b.classList.remove('lq-pooled');}if(LQ.workTimer){clearInterval(LQ.workTimer);LQ.workTimer=null;}LQ.mode='aware';lqLoad(true);}
|
|
1423
1495
|
function lqLoadWork(){api('/api/work').then(function(w){LQ.workData=w||{};lqBuildWork();}).catch(function(){});}
|
|
1424
|
-
function lqBuildWork(){var w=LQ.workData||{},S=lqSize(),cx=S.w/2,cy=S.h*0.5,list=[];LQ.bw=S.w;LQ.bh=S.h;
|
|
1496
|
+
function lqBuildWork(){if(LQ.workBy==='project'){lqBuildProjects();return;}var w=LQ.workData||{},S=lqSize(),cx=S.w/2,cy=S.h*0.5,list=[];LQ.bw=S.w;LQ.bh=S.h;
|
|
1425
1497
|
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);
|
|
1426
1498
|
var run=(w.running||[]).slice(0,3),ready=(w.ready||[]).slice(0,2),wait=(w.waiting||[]).slice(0,5),extra=(w.waitingCount||0)-wait.length;
|
|
1427
1499
|
run.forEach(function(r,i){var ang=Math.PI+(i+1)*Math.PI/(run.length+1);var pg=r.progress?' · '+r.progress.done+' of '+r.progress.total+' done':'';
|
|
@@ -1437,6 +1509,22 @@ var nr=(w.running||[]).length,nw=w.waitingCount||0,nk=(w.ready||[]).length;
|
|
|
1437
1509
|
LQ.coreText=(nr?nr+' at work':'Nobody at work')+(nw?' · '+nw+' waiting':'')+(nk?' · '+nk+' ready for your OK':'');
|
|
1438
1510
|
var go=$('lqgo');if(go){var n=w.canGo||0;go.textContent=n?'Go \u00b7 start '+n+' task'+(n>1?'s':''):'';if(go.classList)go.classList.toggle('on',n>0);}
|
|
1439
1511
|
lqLabels();}
|
|
1512
|
+
// Projects: a sphere per repo with work on it, sized by how much is going on, its
|
|
1513
|
+
// state under its name; spheres orbit while an agent works, amber when its work waits
|
|
1514
|
+
// for your OK. A click opens that project's tasks.
|
|
1515
|
+
function lqBuildProjects(){var w=LQ.workData||{},S=lqSize(),cx=S.w/2,cy=S.h*0.5,list=[];LQ.bw=S.w;LQ.bh=S.h;
|
|
1516
|
+
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);
|
|
1517
|
+
var ps=(w.projects||[]).slice(0,8);ps.forEach(function(p,i){var ang=-2.2+i*2.399963,d=250+50*(i%3),load=p.waiting+2*p.ready+(p.running?3:0);
|
|
1518
|
+
var sub=p.running?(p.running.ask?'has a question for you':'agent at work: '+String(p.running.doing||'').toLowerCase()):p.ready?'ready for your OK':p.waiting+' task'+(p.waiting>1?'s':'')+' waiting';
|
|
1519
|
+
list.push({id:'proj:'+p.repo,kind:'proj',repo:p.repo,shape:'tasks',title:p.repo,sub:sub,orbit:!!p.running,ask:!!(p.ready||(p.running&&p.running.ask)),r:(30+7*Math.min(6,load))*rs,tx:cx+Math.cos(ang)*d*vx,ty:cy+Math.sin(ang)*d*vy});});
|
|
1520
|
+
|
|
1521
|
+
list.forEach(function(d){d.ax=d.tx;d.ay=d.ty;});lqRelax(list,S,cx,cy,92*rs);
|
|
1522
|
+
var old={};LQ.drops.forEach(function(d){old[d.id]=d;});
|
|
1523
|
+
LQ.drops=list.map(function(d){var o=old[d.id];d.x=o?o.x:cx;d.y=o?o.y:cy;d.vx=o?o.vx:0;d.vy=o?o.vy:0;d.cr=o?o.cr:0;return d;});
|
|
1524
|
+
var n=w.projectCount||list.length,nr=(w.running||[]).length,nw=w.waitingCount||0;
|
|
1525
|
+
LQ.coreText=n?n+' project'+(n>1?'s':'')+(nr?' \u00b7 '+nr+' at work':'')+(nw?' \u00b7 '+nw+' tasks waiting':''):'No projects with work on them yet. Add a task, or tell me what to do.';
|
|
1526
|
+
var go=$('lqgo');if(go){var k=w.canGo||0;go.textContent=k?'Go \u00b7 start '+k+' task'+(k>1?'s':''):'';if(go.classList)go.classList.toggle('on',k>0);}
|
|
1527
|
+
lqLabels();}
|
|
1440
1528
|
function lqGoWork(){var go=$('lqgo');if(go)go.disabled=true;api('/api/work/go',{}).then(function(r){if(go)go.disabled=false;
|
|
1441
1529
|
var t=r&&r.error?r.error:r.started||r.queued?(r.started?'Started '+r.started:'')+(r.started&&r.queued?', ':'')+(r.queued?r.queued+' queued behind work already going':'')+'.':(r&&r.note)||'Nothing waiting to start.';
|
|
1442
1530
|
LQ.talk.push({me:false,text:t});LQ.talk=LQ.talk.slice(-8);lqTalkShow(false);LQ.ripple=[0.5,0.5,LQ.t];lqLoadWork();}).catch(function(){if(go)go.disabled=false;});}
|
|
@@ -1471,8 +1559,8 @@ var PARA_OF={tasks:'p',agents:'p',todo:'p',board:'a',week:'a',standup:'a',map:'r
|
|
|
1471
1559
|
var PARA_NAME={p:'Projects',a:'Areas',r:'Resources',x:'Archive'},PARA_SUB={p:'work with an end',a:'what you keep up with',r:'to look things up in',x:'what’s done'};
|
|
1472
1560
|
var PARA_ANG={p:-2.4,a:-0.65,r:2.45,x:0.8},PARA_LIVE={p:0.45,a:0.3,r:0.12,x:0};
|
|
1473
1561
|
function lqPara(d){return d.kind==='archive'?'x':d.kind==='feed'?'a':PARA_OF[d.shape]||'p';}
|
|
1474
|
-
function lqParaGo(g){lqAct();if(g==='p'){
|
|
1475
|
-
function lqArchive(){lqPool('tasks');if(typeof loadArchived==='function')loadArchived();}
|
|
1562
|
+
function lqParaGo(g){lqAct();if(g==='p'){lqWorkBy('project');return;}if(g==='a'){lqUse('board');lqPool('board');return;}if(g==='r'){lqUse('map');lqPool('map');return;}lqArchive();}
|
|
1563
|
+
function lqArchive(){lqPool('tasks');LQ.arch=true;if(typeof loadArchived==='function')loadArchived();}
|
|
1476
1564
|
function lqOrg(id,k){var h=k*977;id=String(id);for(var i=0;i<id.length;i++)h=(h*31+id.charCodeAt(i))|0;return ((h>>>0)%1000)/1000;}
|
|
1477
1565
|
function lqGrow(list,cx,cy,vx,vy){list.forEach(function(d){var x=(d.tx-cx)/vx,y=(d.ty-cy)/vy,an=Math.atan2(y,x)+(lqOrg(d.id,1)-0.5)*0.7,rr=Math.sqrt(x*x+y*y)*(0.78+0.44*lqOrg(d.id,2));d.tx=cx+Math.cos(an)*rr*vx;d.ty=cy+Math.sin(an)*rr*vy;});}
|
|
1478
1566
|
// Nearest neighbours, in the liquid: each droplet is drawn towards the ones it's
|
|
@@ -1542,9 +1630,9 @@ a.tx=Math.max(a.r+60,Math.min(S.w-a.r-60,a.tx));a.ty=Math.max(140+a.r,Math.min(S
|
|
|
1542
1630
|
var bad=false;for(var i2=0;i2<n&&!bad;i2++)for(var j2=i2+1;j2<n;j2++){var p=L[i2],q=L[j2],ddx=p.tx-q.tx,ddy=p.ty-q.ty;if(Math.sqrt(ddx*ddx+ddy*ddy)<lqSep(p,q,ddx,ddy)-1){bad=true;break;}}
|
|
1543
1631
|
if(!bad)return true;L.forEach(function(x){x.r*=0.9;});coreR*=0.9;}return false;}
|
|
1544
1632
|
function lqLabels(){var el=$('lqdrops');if(!el)return;
|
|
1545
|
-
el.innerHTML=LQ.drops.map(function(d,i){return "<div class='lqd lq-"+d.kind+(LQ.openTag===d.id?' open':'')+"' data-i='"+i+"'><button type='button' class='lt' aria-expanded='"+(LQ.openTag===d.id)+"'><span>"+esc(d.title)+"</span
|
|
1633
|
+
el.innerHTML=LQ.drops.map(function(d,i){return "<div class='lqd lq-"+d.kind+(LQ.openTag===d.id?' open':'')+"' data-i='"+i+"'><button type='button' class='lt' aria-expanded='"+(LQ.openTag===d.id)+"'><span>"+esc(d.title)+"</span>"+(d.kind==='proj'&&d.sub?"<span class='lm'>"+esc(d.sub)+"</span>":"")+"</button>"+(d.sub?"<small>"+esc(d.sub)+"</small>":"")+"<button type='button' class='lgo'>Open ›</button></div>";}).join('');
|
|
1546
1634
|
LQ.btns=[];LQ.lts=[];el.querySelectorAll('.lqd').forEach(function(b){LQ.btns.push(b);var d=LQ.drops[+b.getAttribute('data-i')],lt=b.querySelector('.lt'),go=b.querySelector('.lgo');LQ.lts.push(lt);
|
|
1547
|
-
if(lt)lt.addEventListener('click',function(ev){
|
|
1635
|
+
if(lt)lt.addEventListener('click',function(ev){lqOpen(d,ev);});
|
|
1548
1636
|
if(go)go.addEventListener('click',function(ev){LQ.openTag=null;lqOpen(d,ev);});});
|
|
1549
1637
|
var oe=$('lqorbits');if(oe){oe.innerHTML=LQ.drops.map(function(d,i){return d.kind==='run'||d.orbit?"<div class='lqorbit"+(d.ask?' ask':'')+"' data-i='"+i+"'><span><i></i></span><span><i></i></span><span><i></i></span></div>":'';}).join('');LQ.orbs=[];oe.querySelectorAll('.lqorbit').forEach(function(o){LQ.orbs.push(o);});}
|
|
1550
1638
|
LQ.drops.forEach(function(d){if(d.x===d.tx&&d.y===d.ty)return;if(!LQ.frame){d.x=d.tx;d.y=d.ty;d.cr=d.r;}});lqStep();
|
|
@@ -1557,13 +1645,24 @@ function lqUse(shape,ev,via){api('/api/adapt/use',{shape:shape,from:LQ.last,via:
|
|
|
1557
1645
|
function lqOpen(d,ev){if(!d)return;lqAct();
|
|
1558
1646
|
if(d.kind==='more'){LQ.moreOpen=!LQ.moreOpen;lqMore(d);return;}
|
|
1559
1647
|
if(d.kind==='archive'){lqArchive();return;}
|
|
1648
|
+
if(d.kind==='proj'){TFILTER.repo=d.repo;TFILTER.type='';lqPool('tasks');if(typeof loadTasks==='function')loadTasks();return;}
|
|
1560
1649
|
var S=lqSize();LQ.ripple=[d.x/S.w,d.y/S.h,LQ.t];if(LQ.scene==='work'){lqPool(d.shape||'agents');return;}lqUse(d.shape||'board',ev);lqGo(d.shape||'board');}
|
|
1561
1650
|
function lqMore(d){var el=$('lqmore');if(!el)return;if(!LQ.moreOpen||!d){el.innerHTML='';return;}
|
|
1562
1651
|
el.innerHTML=(d.more||[]).map(function(s){return "<button type='button' class='lqmb lqm' data-s='"+s+"'>"+esc(LQNAMES[s]||s)+"</button>";}).join('');
|
|
1563
1652
|
el.querySelectorAll('.lqm').forEach(function(b){b.addEventListener('click',function(ev){var s=b.getAttribute('data-s');LQ.moreOpen=false;el.innerHTML='';lqUse(s,ev);lqGo(s);});});}
|
|
1564
|
-
function lqGo(shape){if(shape==='tasks'||shape==='agents'){
|
|
1565
|
-
|
|
1566
|
-
function
|
|
1653
|
+
function lqGo(shape){if(shape==='tasks'||shape==='agents'){lqWorkBy('task');return;}lqPool(shape);}
|
|
1654
|
+
// The work, by task (Tasks, Agents) or by project (the Projects group): the same scene, a step in the history each.
|
|
1655
|
+
function lqWorkBy(by){if(LQ.scene==='work'&&LQ.workBy===by&&LQ.mode!=='pool')return;lqRemember();LQ.back=true;LQ.workBy=by;try{lqWork();}finally{LQ.back=false;}}
|
|
1656
|
+
// Back, one step: every move between screens (home, the Tasks scene, a panel, the
|
|
1657
|
+
// Archive) notes where you were; a right-click (or the mouse's back button) returns
|
|
1658
|
+
// there. In a text field or over selected text the browser's own menu stays.
|
|
1659
|
+
function lqState(){return {by:LQ.workBy||'task',scene:LQ.scene==='work'?'work':'home',pool:LQ.mode==='pool'?(LQ.poolShape||null):null,arch:LQ.mode==='pool'&&!!LQ.arch};}
|
|
1660
|
+
function lqRemember(){if(LQ.back)return;LQ.hist=LQ.hist||[];var s=lqState(),top=LQ.hist[LQ.hist.length-1];if(top&&top.scene===s.scene&&top.pool===s.pool&&top.arch===s.arch)return;LQ.hist.push(s);if(LQ.hist.length>40)LQ.hist.shift();}
|
|
1661
|
+
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;}
|
|
1662
|
+
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();
|
|
1663
|
+
if(s.pool){if(s.arch)lqArchive();else lqPool(s.pool);}else if(LQ.mode==='pool')lqSink();}finally{LQ.back=false;}}
|
|
1664
|
+
function lqPool(shape){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.focus)m.focus();}
|
|
1665
|
+
function lqSink(){lqRemember();var b=document.body;if(b&&b.classList)b.classList.remove('lq-pooled');LQ.mode='aware';lqAct();lqLoad(false);}
|
|
1567
1666
|
function lqAct(){LQ.lastAct=Date.now();if(LQ.mode==='rest'){LQ.mode='aware';lqLoad(true);}}
|
|
1568
1667
|
function lqTalkMode(on){LQ.talking=on;var b=document.body;if(b&&b.classList)b.classList.toggle('lq-talking',on);}
|
|
1569
1668
|
function lqTalkShow(wait){var el=$('lqtalk');if(!el)return;el.innerHTML=LQ.talk.map(function(m){return "<div class='lqmsg"+(m.me?' me':'')+"'>"+esc(m.text)+stepsHtml(m.steps)+"</div>";}).join('')+(wait?"<div class='lqmsg thinking'>Thinking: recalling what it knows, reading what's here…</div>":'');if(el.scrollHeight)el.scrollTop=el.scrollHeight;}
|
|
@@ -1660,7 +1759,9 @@ gl.uniform1f(U.uStyle,th.look==='glass'?0:th.look==='pearl'?2:1);gl.uniform1f(U.
|
|
|
1660
1759
|
gl.drawArrays(gl.TRIANGLES,0,3);}
|
|
1661
1760
|
function lqInit(){var bd=document.body;if(!bd||!bd.classList)return;lqTheme();bd.classList.add('lq-liquid');
|
|
1662
1761
|
['(prefers-color-scheme: light)','(prefers-contrast: more)','(forced-colors: active)','(prefers-reduced-transparency: reduce)','(prefers-reduced-motion: reduce)'].forEach(function(q){try{var mq=window.matchMedia&&window.matchMedia(q);if(mq&&mq.addEventListener)mq.addEventListener('change',lqTheme);else if(mq&&mq.addListener)mq.addListener(lqTheme);}catch(e){}});
|
|
1663
|
-
|
|
1762
|
+
if(document.addEventListener){document.addEventListener('contextmenu',function(ev){var b=document.body,t=ev&&ev.target;if(!b||!b.classList||!b.classList.contains('lq-liquid'))return;if(t&&t.closest&&t.closest('input,textarea,select,[contenteditable]'))return;var sel=window.getSelection?String(window.getSelection()):'';if(sel)return;ev.preventDefault();lqBack();});
|
|
1763
|
+
document.addEventListener('mouseup',function(ev){var b=document.body;if(ev&&ev.button===3&&b&&b.classList&&b.classList.contains('lq-liquid')){ev.preventDefault();lqBack();}});}
|
|
1764
|
+
var L=$('liquid');if(L&&L.addEventListener){L.addEventListener('click',function(ev){var t=ev&&ev.target;if(t&&t.closest&&t.closest('button,input,form,.lqd'))return;var x=ev.clientX,y=ev.clientY,hit=null;LQ.drops.forEach(function(d){if(!hit&&Math.hypot(x-d.x,y-d.y)<=d.cr+8)hit=d;});if(hit)lqOpen(hit,ev);else if(LQ.openTag)lqTag(LQ.openTag);});L.addEventListener('pointermove',function(ev){LQ.pointer=[ev.clientX,ev.clientY];if(ev.pointerType==='touch')LQ.touch=true;lqAct();});L.addEventListener('pointerdown',function(ev){if(ev.pointerType==='touch')LQ.touch=true;lqAct();});}
|
|
1664
1765
|
var f=$('lqform');if(f&&f.addEventListener)f.addEventListener('submit',function(ev){if(ev&&ev.preventDefault)ev.preventDefault();lqSay();});
|
|
1665
1766
|
var ia=$('lqask');if(ia&&ia.addEventListener){ia.addEventListener('focus',function(){lqTalkMode(true);lqAct();});ia.addEventListener('blur',function(){if(!ia.value&&!LQ.talk.length)lqTalkMode(false);});ia.addEventListener('keydown',function(ev){if(ev&&ev.key==='Escape'){if(LQ.mode==='pool'){lqSink();return;}if(LQ.scene==='work'&&!ia.value){lqHome();return;}ia.value='';LQ.talk=[];lqTalkShow(false);lqTalkMode(false);if(ia.blur)ia.blur();}});}
|
|
1666
1767
|
var co=$('lqcore');if(co&&co.addEventListener)co.addEventListener('click',function(){var i=$('lqask');if(i&&i.focus)i.focus();});
|
package/watch.mjs
CHANGED
|
@@ -254,8 +254,9 @@ function watchBoard(hours = 24, now = Date.now()) {
|
|
|
254
254
|
const cards = d.watches.map((w) => {
|
|
255
255
|
const recent = d.news.filter((n) => n.watch === w.id && n.ts >= since && unseen(n, w)), k = kindOf(w.url), count = recent.filter((n) => needsYou(n, w)).length;
|
|
256
256
|
const b = d.briefs.find((x) => x.watch === w.id && x.ts >= since && unseen(x, w));
|
|
257
|
+
const past = d.news.filter((n) => n.watch === w.id && n.ts >= since && !unseen(n, w)).slice(0, 12).map((n) => ({ id: n.id, ts: n.ts, name: n.name, text: n.text, seen: true }));
|
|
257
258
|
return { ...view(w), source: sourceOf(w.url), count, label: k ? `${count} ${k[count === 1 ? 0 : 1]}` : `${count} new`,
|
|
258
|
-
items: markNews(recent.slice(0, 8), [w]).map((n) => ({ ...n, need: needsYou(n, w) })), ...(b ? { brief: { text: b.text, ts: b.ts, count: b.count } } : {}), ...(w.chat && w.chat.length ? { chat: w.chat } : {}) };
|
|
259
|
+
items: markNews(recent.slice(0, 8), [w]).map((n) => ({ ...n, need: needsYou(n, w) })), past, ...(b ? { brief: { text: b.text, ts: b.ts, count: b.count } } : {}), ...(w.chat && w.chat.length ? { chat: w.chat } : {}) };
|
|
259
260
|
});
|
|
260
261
|
return { hours, total: cards.reduce((s, c) => s + c.count, 0), cards, brief: briefOn() };
|
|
261
262
|
}
|