create-pmos 0.87.0 → 0.88.0
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/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/guide/layout.md +1 -1
- package/workspace/guide/people.md +7 -5
- package/workspace/guide/today.md +2 -0
- package/workspace/lib/session-origin.js +13 -7
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/components.css +305 -58
- package/workspace/public/pm/dest-people.js +114 -76
- package/workspace/public/pm/dock-sessions.js +3 -2
- package/workspace/public/pm/today-copy.js +3 -1
- package/workspace/public/pm/today-day.js +19 -15
- package/workspace/public/pm/topbar.js +1 -1
- package/workspace/public/vendor/lucide-sprite.js +8 -2
- package/workspace/server.js +1 -1
- package/workspace/third-party/README.md +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.88.0",
|
|
4
4
|
"description": "Scaffold the PMOS portable kit — run the PMOS product-management workflow (initiative → lane-fit spec → contract → build → three gates, with work-type lanes for features, research, maintenance, incidents, and content) entirely inside your own repo, with all state as git-versioned files. Includes `workflow-sweep`, which audits the state every board derives from and separates real defects from work you simply have not done. `create-pmos workspace` also installs + launches the local Terminal Workspace GUI. No database, no network, no telemetry.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
package/payload/KIT-VERSION
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Eval calibration log"
|
|
4
4
|
description: "Append-only record of every PM override of an evaluator verdict."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-07T17:03:38Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Health budgets"
|
|
4
4
|
description: "Closed registry of health-budget anchors (D59), edited only by the PM."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-07T17:03:38Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Discoveries and friction log"
|
|
4
4
|
description: "Append-only record of what runs discovered and where the process rubbed."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-07T17:03:38Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -50,7 +50,7 @@ The dock holds your terminals, documents, conversations, skills and web pages. I
|
|
|
50
50
|
|
|
51
51
|
- Press ⌘J (Ctrl-J), or the button on the rail, to open the dock. Press ⌘J again, or the fold button in the dock, to fold it to the rail.
|
|
52
52
|
- The dock never opens by itself. A tab that opens while the dock is a rail waits in the dock until you open it.
|
|
53
|
-
- The dock lists the sessions of the repository by initiative. A session is in the group of the initiative it is recorded on, or of its `initiative/<id>` branch. Then come **Not linked** (no initiative) and **Automatic** (
|
|
53
|
+
- The dock lists the sessions of the repository by initiative. A session is in the group of the initiative it is recorded on, or of its `initiative/<id>` branch. Then come **Not linked** (no initiative) and **Automatic** (anything you did not start yourself, for example a schedule, the day run, a note's summary or bullets, or a Jira push). **Automatic** is hidden until you press **Show automatic** at the foot of the list (or beside the group select in the strip). A session in front, or one held on a permission question, is always shown. An agent that has finished and reads "needs you" is hidden like the rest. Finished automatic sessions remove themselves a minute after a clean exit, and one with no output for 60 minutes is ended and removed. A failed one stays. The setting is in `state/session-policy.json`: `finish` (true or false) and `idleMinutes` (5 or more, or 0 for off). Then **Open** lists the tabs that are not sessions, for example documents and skills. Each one shows its title, what it is (for example PRD, Document or Web page) and its branch when it is not main. **✕** on an Open row closes the tab only. A terminal keeps running.
|
|
54
54
|
- Each session row starts with a mark for its state: ● working, ◼ quiet, ◆ needs you, ◇ maybe blocked, ○ ended. Under the name, the row shows the state when the session is not working, the type of work when one is set, and the branch when it is not main and not the initiative's own branch. Running sessions come first and ended sessions last. Press the row to bring the session to the front.
|
|
55
55
|
- Press the name of a group to fold it or open it. The number beside it counts its sessions. **Automatic** is folded while none of its sessions is running, because each run leaves an ended session. The group of the session in front always stays open, and the workspace keeps your choice for each group.
|
|
56
56
|
- **⋯** and **✕** show on the row in front, on the row under the pointer and on the row with the keyboard focus. On a touch screen they always show.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
type: Guide
|
|
3
3
|
title: "People: the people you work with in a repository, their interactions, tone, notes and shared work"
|
|
4
|
-
timestamp: 2026-10-
|
|
4
|
+
timestamp: 2026-10-07
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# People
|
|
@@ -37,16 +37,18 @@ A sighting of a new handle adds the person. A sighting with the full name of a p
|
|
|
37
37
|
|
|
38
38
|
## The list
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
People has the same two-card layout as the Agenda. The title says how many people there are. **All**, **Team** and **External** at the top right filter the list. The left card holds the list. Type in **Find a person** to search names, handles and initiatives. **Latest**, **Most** and **Name** sort it by the last interaction, the number of interactions or the name. Each row shows initials, the name, the role and organisation, and the tone as a coloured dot (a ? means you have not confirmed it). Press a person to open their page in the right card. Each card scrolls by itself.
|
|
41
41
|
|
|
42
42
|
## A person's page
|
|
43
43
|
|
|
44
|
+
The top of the page shows the person's name and role, a **Confirm** button when a tone is waiting, and a **⋯** menu. Three tiles under it give the number of interactions, the tone and the shared work.
|
|
45
|
+
|
|
44
46
|
- **Interactions.** Newest first. Each line shows the date, the kind (Mail, Chat, Meeting, 1:1, Jira, Doc, Call or Mention), the line, and where it came from with its pointer and tone.
|
|
45
|
-
- **Tone.** The mix of tones (warm, neutral, cautious, tense) and a proposed overall tone: the most frequent tone of the latest 10 interactions. When two tones are tied, the latest one wins. Press **Confirm** to keep the proposal, **Set another** to choose a different tone, or **Change** to clear your choice. Tone describes the exchanges, not the person.
|
|
47
|
+
- **Tone.** The mix of tones (warm, neutral, cautious, tense) and a proposed overall tone: the most frequent tone of the latest 10 interactions. When two tones are tied, the latest one wins. Press **Confirm** (at the top or here) to keep the proposal, **Set another** to choose a different tone, or **Change** to clear your choice. Tone describes the exchanges, not the person.
|
|
46
48
|
- **Shared work.** The initiatives your interactions name, the plan items the person owns (with **Blocked** when the item is blocked), and the coming meetings that name them when calendar names are on. Nothing here is typed by hand.
|
|
47
|
-
- **Notes.** Type a note and press **Add note
|
|
49
|
+
- **Notes.** Type a note and press the **+** button (**Add note**). Press the **×** next to a note to remove it. Only you can add or remove a note. No command writes one.
|
|
48
50
|
- **Identity and sources.** The person's handles, how many interactions each source gave, where People found them, and the file the person lives in.
|
|
49
|
-
- **Remove from People.** Takes the person out. Later sightings of their handles stay out.
|
|
51
|
+
- **Remove from People.** In the **⋯** menu. Takes the person out. Later sightings of their handles stay out.
|
|
50
52
|
|
|
51
53
|
## Ways in from other places
|
|
52
54
|
|
package/workspace/guide/today.md
CHANGED
|
@@ -16,6 +16,8 @@ Above the cards, Today shows the day in two columns.
|
|
|
16
16
|
|
|
17
17
|
Press **Prepare my day** to start the day. It runs from the brief to the delivery of each agent job, and you do not press anything else until a draft is ready for you. The page [Guide: Today and the proactive engine](proactive-engine.md) describes each step.
|
|
18
18
|
|
|
19
|
+
The bar at the top of the day is one line. It shows the greeting, the date and how many goals are unfinished. While **Prepare my day** runs, it also shows the four steps (Read, Sort, Agents work, You approve) as a small progress bar; press a step to see its detail. When nothing runs, it says when the day was last prepared. **Sources** and **Check now** are icon buttons in the bar.
|
|
20
|
+
|
|
19
21
|
If the morning-brief session stops before it submits a brief, the Read step says so and gives the reason. **Prepare my day** is then ready again, so press it to try once more. If the session says that its folder is not there, open the repository again from its current folder first.
|
|
20
22
|
|
|
21
23
|
Each agent job runs in its own session, so you can open any job and follow or steer it. The workspace types the job's prompt into the session when the agent is ready to read, and presses Enter for you. When you approve or revise a job and its session is still open, the workspace uses that same session again. Prepare again works the same way: when the morning-brief session is still open and has finished its read, or has printed nothing for two minutes, the workspace types the read into that session and runs it again.
|
|
@@ -6,17 +6,22 @@
|
|
|
6
6
|
* be done to them. Pure: the server passes in the rows and the clock, so the rules are testable without a server.
|
|
7
7
|
*
|
|
8
8
|
* Next step, Hand to agent, a skill draft and an alignment are NOT automatic: the PM pressed those, and often talks on.
|
|
9
|
+
* Any other origin is automatic.
|
|
9
10
|
*/
|
|
10
11
|
'use strict';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
// The rule is "if the PM did not trigger it, it is automatic", so the list names what the PM DID trigger and everything
|
|
14
|
+
// else with an origin is automatic. A new background job is then hidden and cleaned up without anyone remembering to list it.
|
|
15
|
+
// Manual: a session opened with no origin, a Next step or Hand to agent press, a skill draft, an alignment, and the Whisper
|
|
16
|
+
// and final-pass setup shells (the PM pressed those and watches them install).
|
|
17
|
+
const MANUAL_EXACT = Object.freeze(['next-step', 'plan-item', 'notes-whisper-setup', 'notes-final-setup']);
|
|
18
|
+
const MANUAL_PREFIX = Object.freeze(['skill-draft:', 'align:']);
|
|
14
19
|
|
|
15
|
-
/** True when the
|
|
20
|
+
/** True when the PM did not trigger a session with this origin: a schedule, the day run, a doc sync, a note's passes, Jira. */
|
|
16
21
|
function isAutomatic(origin) {
|
|
17
22
|
const o = String(origin || '');
|
|
18
23
|
if (!o) return false;
|
|
19
|
-
return
|
|
24
|
+
return !(MANUAL_EXACT.includes(o) || MANUAL_PREFIX.some((p) => o.startsWith(p)));
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
const DEFAULT_POLICY = Object.freeze({ finish: true, idleMinutes: 60 });
|
|
@@ -57,9 +62,10 @@ function decide(s, policy, now) {
|
|
|
57
62
|
return age >= FINISH_GRACE_MS ? 'remove' : null;
|
|
58
63
|
}
|
|
59
64
|
if (!policy.idleMinutes) return null;
|
|
60
|
-
// waiting
|
|
61
|
-
|
|
65
|
+
// A call waiting for permission is a real ask. 'needs' (the agent's turn is over) is NOT: a finished agent always reads
|
|
66
|
+
// 'needs you', so exempting it would keep every finished job for ever. The idle clock still gives a late question time.
|
|
67
|
+
if (s.shown === 'blocked') return null;
|
|
62
68
|
return age >= policy.idleMinutes * 60 * 1000 ? 'end' : null;
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
module.exports = { isAutomatic, normalizePolicy, decide, endedCleanly, DEFAULT_POLICY, FINISH_GRACE_MS,
|
|
71
|
+
module.exports = { isAutomatic, normalizePolicy, decide, endedCleanly, DEFAULT_POLICY, FINISH_GRACE_MS, MANUAL_EXACT, MANUAL_PREFIX };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.88.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pmos-workspace",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.88.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
|
@@ -3406,15 +3406,17 @@
|
|
|
3406
3406
|
flex-wrap: wrap;
|
|
3407
3407
|
align-items: center;
|
|
3408
3408
|
gap: 8px 10px;
|
|
3409
|
-
padding: 8px 12px;
|
|
3409
|
+
padding: 4px 8px 4px 12px;
|
|
3410
|
+
min-height: 40px;
|
|
3410
3411
|
border: 1px solid var(--border);
|
|
3412
|
+
border-radius: 8px;
|
|
3411
3413
|
background: var(--card);
|
|
3412
3414
|
}
|
|
3413
|
-
.td-day__hello { flex: 0
|
|
3415
|
+
.td-day__hello { flex: 0 0 auto; max-width: 100%; min-width: 0; overflow: hidden; text-overflow: ellipsis; font-size: 14px; color: var(--foreground); white-space: nowrap; }
|
|
3414
3416
|
.td-day__dim { color: var(--muted-foreground); }
|
|
3415
3417
|
.td-day__warn { color: var(--warning); }
|
|
3416
3418
|
.td-day__mono { font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 12px; }
|
|
3417
|
-
.td-day__steps { display: flex; flex-wrap:
|
|
3419
|
+
.td-day__steps { display: flex; flex-wrap: nowrap; gap: 3px; list-style: none; margin: 0; padding: 0; }
|
|
3418
3420
|
.td-day__ts { border-radius: 6px; gap: 6px; padding: 0 6px; letter-spacing: 0.04em; color: var(--muted-foreground); }
|
|
3419
3421
|
.td-day__ts[data-step-state="done"], .td-day__ts[data-step-state="now"] { color: var(--foreground); }
|
|
3420
3422
|
.td-day__ts[data-step-state="now"] { background: var(--muted); }
|
|
@@ -3424,7 +3426,11 @@
|
|
|
3424
3426
|
.td-day__ts[data-step-state="now"] .td-day__tsdot { background: var(--foreground); animation: td-day-pulse 1.2s infinite; }
|
|
3425
3427
|
@keyframes td-day-pulse { 50% { opacity: 0.3; } }
|
|
3426
3428
|
@media (prefers-reduced-motion: reduce) { .td-day__ts[data-step-state="now"] .td-day__tsdot { animation: none; } }
|
|
3427
|
-
.td-
|
|
3429
|
+
.td-day__seg { display: block; width: 22px; height: 4px; border-radius: 6px; background: var(--border); }
|
|
3430
|
+
.td-day__ts[data-step-state="done"] .td-day__seg { background: currentColor; }
|
|
3431
|
+
.td-day__ts[data-step-state="now"] .td-day__seg { background: var(--foreground); animation: td-day-pulse 1.2s infinite; }
|
|
3432
|
+
@media (prefers-reduced-motion: reduce) { .td-day__ts[data-step-state="now"] .td-day__seg { animation: none; } }
|
|
3433
|
+
.td-day__status { flex: 1 1 0; min-width: 80px; text-align: right; font-size: 12px; color: var(--muted-foreground); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
3428
3434
|
.td-day__check { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted-foreground); }
|
|
3429
3435
|
.td-day__run { margin: 0; font-size: 12px; line-height: 16px; color: var(--foreground); }
|
|
3430
3436
|
.td-day__run--failed { color: var(--destructive); }
|
|
@@ -5343,6 +5349,9 @@
|
|
|
5343
5349
|
.rp-doc__scroll {
|
|
5344
5350
|
-webkit-overflow-scrolling: touch;
|
|
5345
5351
|
}
|
|
5352
|
+
.rp-people-sec summary {
|
|
5353
|
+
min-height: 44px;
|
|
5354
|
+
}
|
|
5346
5355
|
}
|
|
5347
5356
|
|
|
5348
5357
|
/* Docs (docs-three-way-sync): one line per copy — a status dot, the title over the copy's link, one sentence, one action. */
|
|
@@ -5818,7 +5827,7 @@
|
|
|
5818
5827
|
overflow-wrap: anywhere;
|
|
5819
5828
|
}
|
|
5820
5829
|
|
|
5821
|
-
/* People (people-directory):
|
|
5830
|
+
/* People (people-directory): the PM's people directory — list on left, person's page on right. */
|
|
5822
5831
|
.rp-people {
|
|
5823
5832
|
display: flex;
|
|
5824
5833
|
flex-direction: column;
|
|
@@ -5826,16 +5835,12 @@
|
|
|
5826
5835
|
}
|
|
5827
5836
|
.rp-people-top {
|
|
5828
5837
|
display: flex;
|
|
5829
|
-
flex-wrap: wrap;
|
|
5830
5838
|
align-items: center;
|
|
5839
|
+
justify-content: space-between;
|
|
5831
5840
|
gap: 12px;
|
|
5832
5841
|
}
|
|
5833
5842
|
.rp-people-top > .rp-head__title {
|
|
5834
|
-
margin
|
|
5835
|
-
}
|
|
5836
|
-
.rp-people-find {
|
|
5837
|
-
flex: 1 1 260px;
|
|
5838
|
-
max-width: 420px;
|
|
5843
|
+
margin: 0;
|
|
5839
5844
|
}
|
|
5840
5845
|
.rp-people-body {
|
|
5841
5846
|
display: flex;
|
|
@@ -5843,59 +5848,257 @@
|
|
|
5843
5848
|
gap: 16px;
|
|
5844
5849
|
}
|
|
5845
5850
|
.rp-people-review {
|
|
5846
|
-
|
|
5847
|
-
|
|
5851
|
+
display: flex;
|
|
5852
|
+
flex-direction: column;
|
|
5853
|
+
gap: 8px;
|
|
5854
|
+
}
|
|
5855
|
+
.rp-people-merge {
|
|
5856
|
+
display: flex;
|
|
5857
|
+
align-items: flex-start;
|
|
5858
|
+
gap: 12px;
|
|
5859
|
+
padding: 10px 12px;
|
|
5860
|
+
border-radius: 8px;
|
|
5861
|
+
background: var(--background);
|
|
5862
|
+
box-shadow: 0 0 0 1px var(--border);
|
|
5863
|
+
}
|
|
5864
|
+
.rp-people-merge__dot {
|
|
5865
|
+
width: 6px;
|
|
5866
|
+
height: 6px;
|
|
5867
|
+
border-radius: 50%;
|
|
5868
|
+
background: var(--foreground);
|
|
5869
|
+
flex: none;
|
|
5870
|
+
margin-top: 4px;
|
|
5871
|
+
}
|
|
5872
|
+
.rp-people-merge__text {
|
|
5873
|
+
flex: 1;
|
|
5874
|
+
min-width: 0;
|
|
5875
|
+
}
|
|
5876
|
+
.rp-people-merge__title {
|
|
5877
|
+
margin: 0;
|
|
5878
|
+
font-size: 14px;
|
|
5879
|
+
font-weight: 500;
|
|
5880
|
+
color: var(--foreground);
|
|
5881
|
+
}
|
|
5882
|
+
.rp-people-merge__why {
|
|
5883
|
+
margin: 2px 0 0;
|
|
5884
|
+
font-size: 12px;
|
|
5885
|
+
color: var(--muted-foreground);
|
|
5848
5886
|
}
|
|
5849
5887
|
.rp-people-grid {
|
|
5850
5888
|
display: grid;
|
|
5851
|
-
grid-template-columns: minmax(
|
|
5852
|
-
gap:
|
|
5889
|
+
grid-template-columns: minmax(300px, 2fr) 3fr;
|
|
5890
|
+
gap: 16px;
|
|
5853
5891
|
align-items: start;
|
|
5892
|
+
height: calc(100vh - 220px);
|
|
5893
|
+
min-height: 420px;
|
|
5854
5894
|
}
|
|
5855
|
-
.rp-people-
|
|
5895
|
+
.rp-people-card {
|
|
5856
5896
|
display: flex;
|
|
5857
5897
|
flex-direction: column;
|
|
5858
|
-
gap:
|
|
5859
|
-
|
|
5898
|
+
gap: 10px;
|
|
5899
|
+
padding: 10px 12px;
|
|
5900
|
+
border-radius: 8px;
|
|
5901
|
+
background: var(--card);
|
|
5902
|
+
box-shadow: 0 0 0 1px var(--border);
|
|
5903
|
+
overflow: hidden;
|
|
5860
5904
|
}
|
|
5861
|
-
.rp-people-
|
|
5905
|
+
.rp-people-card__header {
|
|
5862
5906
|
display: flex;
|
|
5863
|
-
flex-wrap: wrap;
|
|
5864
|
-
align-items: center;
|
|
5865
5907
|
justify-content: space-between;
|
|
5866
|
-
|
|
5867
|
-
|
|
5908
|
+
align-items: center;
|
|
5909
|
+
margin: 0;
|
|
5868
5910
|
}
|
|
5869
|
-
.rp-people-
|
|
5870
|
-
|
|
5911
|
+
.rp-people-card__label {
|
|
5912
|
+
display: flex;
|
|
5913
|
+
flex: 1;
|
|
5914
|
+
justify-content: space-between;
|
|
5915
|
+
font: 11px var(--mono);
|
|
5916
|
+
text-transform: uppercase;
|
|
5917
|
+
color: var(--muted-foreground);
|
|
5918
|
+
margin: 0;
|
|
5919
|
+
letter-spacing: 0.04em;
|
|
5871
5920
|
}
|
|
5872
|
-
.rp-people-
|
|
5873
|
-
|
|
5921
|
+
.rp-people-card__count {
|
|
5922
|
+
margin-left: auto;
|
|
5923
|
+
color: var(--muted-foreground);
|
|
5874
5924
|
}
|
|
5875
|
-
.rp-people-
|
|
5876
|
-
|
|
5925
|
+
.rp-people-card__list {
|
|
5926
|
+
display: flex;
|
|
5927
|
+
flex-direction: column;
|
|
5928
|
+
gap: 2px;
|
|
5929
|
+
overflow: auto;
|
|
5930
|
+
min-width: 0;
|
|
5877
5931
|
}
|
|
5878
|
-
.rp-people-
|
|
5932
|
+
.rp-people-card__list [role="list"] {
|
|
5879
5933
|
display: flex;
|
|
5880
5934
|
flex-direction: column;
|
|
5881
|
-
gap:
|
|
5935
|
+
gap: 2px;
|
|
5936
|
+
}
|
|
5937
|
+
.rp-people-item {
|
|
5938
|
+
border-radius: 6px;
|
|
5939
|
+
}
|
|
5940
|
+
.rp-people-item:has([aria-current="true"]) {
|
|
5941
|
+
background: var(--accent);
|
|
5942
|
+
box-shadow: 0 0 0 1px var(--ring);
|
|
5943
|
+
}
|
|
5944
|
+
.rp-people-row__avatar {
|
|
5945
|
+
width: 32px;
|
|
5946
|
+
height: 32px;
|
|
5947
|
+
border-radius: 6px;
|
|
5948
|
+
background: var(--muted);
|
|
5949
|
+
display: grid;
|
|
5950
|
+
place-items: center;
|
|
5951
|
+
font-size: 12px;
|
|
5952
|
+
font-weight: 600;
|
|
5953
|
+
flex: none;
|
|
5954
|
+
color: var(--foreground);
|
|
5955
|
+
}
|
|
5956
|
+
.rp-people-row__text {
|
|
5957
|
+
flex: 1;
|
|
5958
|
+
min-width: 0;
|
|
5959
|
+
}
|
|
5960
|
+
.rp-people-row__name {
|
|
5961
|
+
margin: 0;
|
|
5962
|
+
font-size: 14px;
|
|
5963
|
+
font-weight: 500;
|
|
5964
|
+
white-space: nowrap;
|
|
5965
|
+
overflow: hidden;
|
|
5966
|
+
text-overflow: ellipsis;
|
|
5967
|
+
}
|
|
5968
|
+
.rp-people-row__meta {
|
|
5969
|
+
margin: 2px 0 0;
|
|
5970
|
+
font-size: 12px;
|
|
5971
|
+
color: var(--muted-foreground);
|
|
5972
|
+
white-space: nowrap;
|
|
5973
|
+
overflow: hidden;
|
|
5974
|
+
text-overflow: ellipsis;
|
|
5975
|
+
}
|
|
5976
|
+
.rp-people-row__tone {
|
|
5977
|
+
display: inline-flex;
|
|
5978
|
+
align-items: center;
|
|
5979
|
+
gap: 5px;
|
|
5980
|
+
font-size: 12px;
|
|
5981
|
+
color: var(--muted-foreground);
|
|
5982
|
+
flex: none;
|
|
5983
|
+
}
|
|
5984
|
+
.rp-people-row__tone-dot {
|
|
5985
|
+
width: 8px;
|
|
5986
|
+
height: 8px;
|
|
5987
|
+
border-radius: 50%;
|
|
5988
|
+
background: var(--muted-foreground);
|
|
5989
|
+
flex: none;
|
|
5990
|
+
}
|
|
5991
|
+
.rp-people-row__tone-dot.warm { background: var(--foreground); }
|
|
5992
|
+
.rp-people-row__tone-dot.cautious { background: var(--warning); }
|
|
5993
|
+
.rp-people-row__tone-dot.tense { background: var(--destructive); }
|
|
5994
|
+
.rp-people-row__tone-dot--proposed {
|
|
5995
|
+
background: transparent;
|
|
5996
|
+
box-shadow: inset 0 0 0 1.5px currentColor;
|
|
5997
|
+
}
|
|
5998
|
+
.rp-people-page__header {
|
|
5999
|
+
display: flex;
|
|
6000
|
+
align-items: flex-start;
|
|
6001
|
+
justify-content: space-between;
|
|
6002
|
+
gap: 12px;
|
|
6003
|
+
}
|
|
6004
|
+
.rp-people-page__info {
|
|
6005
|
+
display: flex;
|
|
6006
|
+
align-items: flex-start;
|
|
6007
|
+
gap: 12px;
|
|
5882
6008
|
min-width: 0;
|
|
5883
6009
|
}
|
|
5884
|
-
.rp-people-
|
|
6010
|
+
.rp-people-page__avatar {
|
|
6011
|
+
width: 48px;
|
|
6012
|
+
height: 48px;
|
|
6013
|
+
border-radius: 6px;
|
|
6014
|
+
background: var(--muted);
|
|
6015
|
+
display: grid;
|
|
6016
|
+
place-items: center;
|
|
6017
|
+
font-size: 16px;
|
|
6018
|
+
font-weight: 600;
|
|
6019
|
+
flex: none;
|
|
6020
|
+
color: var(--foreground);
|
|
6021
|
+
}
|
|
6022
|
+
.rp-people-page__text {
|
|
6023
|
+
flex: 1;
|
|
6024
|
+
min-width: 0;
|
|
6025
|
+
}
|
|
6026
|
+
.rp-people-page__name {
|
|
5885
6027
|
margin: 0;
|
|
5886
6028
|
font-size: 20px;
|
|
5887
6029
|
font-weight: 500;
|
|
5888
6030
|
}
|
|
6031
|
+
.rp-people-page__actions {
|
|
6032
|
+
display: flex;
|
|
6033
|
+
align-items: center;
|
|
6034
|
+
gap: 8px;
|
|
6035
|
+
flex: none;
|
|
6036
|
+
}
|
|
6037
|
+
.rp-people-page__tiles {
|
|
6038
|
+
display: grid;
|
|
6039
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
6040
|
+
gap: 6px;
|
|
6041
|
+
}
|
|
6042
|
+
.rp-people-tile {
|
|
6043
|
+
display: grid;
|
|
6044
|
+
gap: 2px;
|
|
6045
|
+
padding: 8px 10px;
|
|
6046
|
+
border-radius: 6px;
|
|
6047
|
+
background: var(--muted);
|
|
6048
|
+
min-width: 0;
|
|
6049
|
+
}
|
|
6050
|
+
.rp-people-tile__label {
|
|
6051
|
+
margin: 0;
|
|
6052
|
+
font: 11px var(--mono);
|
|
6053
|
+
text-transform: uppercase;
|
|
6054
|
+
color: var(--muted-foreground);
|
|
6055
|
+
letter-spacing: 0.04em;
|
|
6056
|
+
}
|
|
6057
|
+
.rp-people-tile b {
|
|
6058
|
+
font-size: 14px;
|
|
6059
|
+
font-weight: 500;
|
|
6060
|
+
white-space: nowrap;
|
|
6061
|
+
overflow: hidden;
|
|
6062
|
+
text-overflow: ellipsis;
|
|
6063
|
+
color: var(--foreground);
|
|
6064
|
+
display: flex;
|
|
6065
|
+
align-items: center;
|
|
6066
|
+
gap: 5px;
|
|
6067
|
+
}
|
|
6068
|
+
.rp-people-tile__dot {
|
|
6069
|
+
display: inline-block;
|
|
6070
|
+
vertical-align: baseline;
|
|
6071
|
+
width: 8px;
|
|
6072
|
+
height: 8px;
|
|
6073
|
+
border-radius: 50%;
|
|
6074
|
+
background: var(--muted-foreground);
|
|
6075
|
+
flex: none;
|
|
6076
|
+
}
|
|
6077
|
+
.rp-people-tile__dot--confirmed { background: currentColor; }
|
|
6078
|
+
.rp-people-tile__dot--proposed {
|
|
6079
|
+
background: transparent;
|
|
6080
|
+
box-shadow: inset 0 0 0 1.5px currentColor;
|
|
6081
|
+
}
|
|
6082
|
+
.rp-people-page__scroll {
|
|
6083
|
+
display: flex;
|
|
6084
|
+
flex-direction: column;
|
|
6085
|
+
gap: 14px;
|
|
6086
|
+
overflow: auto;
|
|
6087
|
+
min-width: 0;
|
|
6088
|
+
}
|
|
5889
6089
|
.rp-people-sec {
|
|
5890
6090
|
display: flex;
|
|
5891
6091
|
flex-direction: column;
|
|
5892
|
-
gap:
|
|
6092
|
+
gap: 6px;
|
|
6093
|
+
padding-top: 12px;
|
|
6094
|
+
border-top: 1px solid var(--border);
|
|
5893
6095
|
}
|
|
5894
6096
|
.rp-people-sec__title {
|
|
5895
6097
|
margin: 0;
|
|
5896
|
-
font
|
|
5897
|
-
|
|
6098
|
+
font: 11px var(--mono);
|
|
6099
|
+
text-transform: uppercase;
|
|
5898
6100
|
color: var(--muted-foreground);
|
|
6101
|
+
letter-spacing: 0.04em;
|
|
5899
6102
|
}
|
|
5900
6103
|
.rp-people-hint {
|
|
5901
6104
|
margin: 0;
|
|
@@ -5915,36 +6118,66 @@
|
|
|
5915
6118
|
.rp-people-list-plain {
|
|
5916
6119
|
display: flex;
|
|
5917
6120
|
flex-direction: column;
|
|
5918
|
-
gap:
|
|
6121
|
+
gap: 6px;
|
|
5919
6122
|
margin: 0;
|
|
5920
6123
|
padding: 0;
|
|
5921
6124
|
list-style: none;
|
|
5922
6125
|
}
|
|
5923
6126
|
.rp-people-int {
|
|
5924
6127
|
display: flex;
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
padding
|
|
5928
|
-
border-bottom: 1px solid var(--border);
|
|
6128
|
+
gap: 10px;
|
|
6129
|
+
align-items: flex-start;
|
|
6130
|
+
padding: 4px 0;
|
|
5929
6131
|
}
|
|
5930
|
-
.rp-people-
|
|
5931
|
-
|
|
5932
|
-
|
|
6132
|
+
.rp-people-int__icon {
|
|
6133
|
+
width: 28px;
|
|
6134
|
+
height: 28px;
|
|
6135
|
+
border-radius: 6px;
|
|
6136
|
+
background: var(--muted);
|
|
6137
|
+
display: grid;
|
|
6138
|
+
place-items: center;
|
|
5933
6139
|
color: var(--muted-foreground);
|
|
6140
|
+
flex: none;
|
|
5934
6141
|
}
|
|
5935
|
-
.rp-people-
|
|
5936
|
-
|
|
6142
|
+
.rp-people-int div {
|
|
6143
|
+
min-width: 0;
|
|
6144
|
+
flex: 1;
|
|
5937
6145
|
}
|
|
5938
6146
|
.rp-people-int__text,
|
|
5939
6147
|
.rp-people-shared,
|
|
5940
6148
|
.rp-people-handle {
|
|
6149
|
+
margin: 0;
|
|
5941
6150
|
font-size: 14px;
|
|
5942
6151
|
}
|
|
6152
|
+
.rp-people-int small {
|
|
6153
|
+
display: block;
|
|
6154
|
+
margin-top: 2px;
|
|
6155
|
+
font-size: 12px;
|
|
6156
|
+
color: var(--muted-foreground);
|
|
6157
|
+
}
|
|
5943
6158
|
.rp-people-shared {
|
|
5944
6159
|
display: flex;
|
|
5945
6160
|
flex-wrap: wrap;
|
|
5946
6161
|
align-items: center;
|
|
5947
|
-
gap:
|
|
6162
|
+
gap: 6px;
|
|
6163
|
+
}
|
|
6164
|
+
.rp-people-chips {
|
|
6165
|
+
display: flex;
|
|
6166
|
+
flex-wrap: wrap;
|
|
6167
|
+
gap: 6px;
|
|
6168
|
+
}
|
|
6169
|
+
.rp-people-chip {
|
|
6170
|
+
display: inline-flex;
|
|
6171
|
+
gap: 6px;
|
|
6172
|
+
align-items: center;
|
|
6173
|
+
height: 26px;
|
|
6174
|
+
padding: 0 10px;
|
|
6175
|
+
border-radius: 6px;
|
|
6176
|
+
background: var(--muted);
|
|
6177
|
+
font-size: 12px;
|
|
6178
|
+
}
|
|
6179
|
+
.rp-people-chip.warn {
|
|
6180
|
+
color: var(--warning);
|
|
5948
6181
|
}
|
|
5949
6182
|
.rp-people-note {
|
|
5950
6183
|
display: flex;
|
|
@@ -5961,29 +6194,43 @@
|
|
|
5961
6194
|
}
|
|
5962
6195
|
.rp-people-noteadd {
|
|
5963
6196
|
display: flex;
|
|
5964
|
-
flex-direction: column;
|
|
5965
6197
|
gap: 8px;
|
|
5966
6198
|
}
|
|
5967
|
-
.rp-people-
|
|
5968
|
-
|
|
5969
|
-
flex-wrap: wrap;
|
|
5970
|
-
gap: 4px;
|
|
5971
|
-
}
|
|
5972
|
-
.rp-people-links:empty {
|
|
5973
|
-
display: none;
|
|
6199
|
+
.rp-people-noteadd > :first-child {
|
|
6200
|
+
flex: 1;
|
|
5974
6201
|
}
|
|
5975
|
-
.rp-people-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
6202
|
+
.rp-people-noteadd textarea {
|
|
6203
|
+
min-height: 34px;
|
|
6204
|
+
height: 34px;
|
|
6205
|
+
resize: none;
|
|
5979
6206
|
}
|
|
5980
6207
|
.rp-people-file {
|
|
5981
6208
|
font-family: var(--mono);
|
|
5982
6209
|
overflow-wrap: anywhere;
|
|
5983
6210
|
}
|
|
6211
|
+
.rp-people-sec summary {
|
|
6212
|
+
list-style: none;
|
|
6213
|
+
cursor: pointer;
|
|
6214
|
+
color: var(--muted-foreground);
|
|
6215
|
+
font-size: 12px;
|
|
6216
|
+
display: flex;
|
|
6217
|
+
gap: 6px;
|
|
6218
|
+
align-items: center;
|
|
6219
|
+
}
|
|
6220
|
+
.rp-people-sec summary::-webkit-details-marker {
|
|
6221
|
+
display: none;
|
|
6222
|
+
}
|
|
6223
|
+
.rp-people-sec summary:focus-visible {
|
|
6224
|
+
outline: 2px solid color-mix(in oklab, var(--ring) 50%, var(--foreground));
|
|
6225
|
+
outline-offset: 2px;
|
|
6226
|
+
}
|
|
5984
6227
|
@media (max-width: 760px) {
|
|
5985
6228
|
.rp-people-grid {
|
|
5986
6229
|
grid-template-columns: 1fr;
|
|
6230
|
+
height: auto;
|
|
6231
|
+
}
|
|
6232
|
+
.rp-people-top {
|
|
6233
|
+
flex-wrap: wrap;
|
|
5987
6234
|
}
|
|
5988
6235
|
}
|
|
5989
6236
|
|
|
@@ -2,24 +2,22 @@
|
|
|
2
2
|
* tone, notes and shared work (initiative people-directory, PRD R5 to R11, proposed D90).
|
|
3
3
|
*
|
|
4
4
|
* A DESTINATION MODULE (registry.js): mountDestination(host, ctx) on every render with the same host, disposeDestination(host).
|
|
5
|
-
* It keeps ONE persistent root per host; the header (title
|
|
6
|
-
*
|
|
5
|
+
* It keeps ONE persistent root per host; the header is built once (h2 title on left, group switch on right), so typing never
|
|
6
|
+
* loses focus. The search field lives in the left card and is built once. Only the body is rebuilt when its own data changed.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* the workspace's own records hold, and agent sessions record what they saw through `pmos people record`. A link from another place opens a person through the tab's slug (people-link.js).
|
|
17
|
-
* In fixture mode it reads nothing.
|
|
8
|
+
* Layout (desktop): header row, "Waiting for you" merge candidates as bordered rows, then two cards side-by-side (minmax(300px,2fr) 3fr,
|
|
9
|
+
* gap 16px, fixed height calc(100vh - 220px) min-height 420px, internal scrolling). Left card: "PEOPLE" label + count, search field,
|
|
10
|
+
* full-width sort tabs, list of people. Right card: person detail with avatar, name, meta, Confirm / ⋯ menu, 3 tiles (Interactions,
|
|
11
|
+
* Tone, Shared work), scrollable sections (Interactions, Shared work, Notes, Identity and sources).
|
|
12
|
+
*
|
|
13
|
+
* What it shows: waiting for you (possible merge candidates), the people list (search, filter, sort), and person's page
|
|
14
|
+
* (who, interactions, tone, shared work, notes, identity). Every press is a page press to POST /api/people/decide.
|
|
15
|
+
* A link from another place opens a person through the tab's slug (people-link.js). In fixture mode it reads nothing.
|
|
18
16
|
*/
|
|
19
17
|
|
|
20
18
|
import { el } from './dom.js';
|
|
21
|
-
import { button, row, field, toast, empty, tabs, badge, menu, ensureComponentsCss } from './primitives.js';
|
|
22
|
-
import { ensureSprite } from './icons.js';
|
|
19
|
+
import { button, iconButton, row, field, toast, empty, tabs, badge, menu, ensureComponentsCss } from './primitives.js';
|
|
20
|
+
import { ensureSprite, icon } from './icons.js';
|
|
23
21
|
import { pmGet, pmPost } from './http.js';
|
|
24
22
|
import { wsOf } from './data.js';
|
|
25
23
|
import { registerCommand, openDestination } from './registry.js';
|
|
@@ -174,22 +172,18 @@ function review(inst) {
|
|
|
174
172
|
const rows = [];
|
|
175
173
|
for (const m of r.merges) {
|
|
176
174
|
const key = m.handle.kind + ':' + m.handle.value;
|
|
177
|
-
rows.push(el('div', { class: 'rp-people-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
],
|
|
187
|
-
})));
|
|
175
|
+
rows.push(el('div', { class: 'rp-people-merge', role: 'listitem', 'data-people-merge': key },
|
|
176
|
+
el('div', { class: 'rp-people-merge__dot' }),
|
|
177
|
+
el('div', { class: 'rp-people-merge__text' },
|
|
178
|
+
el('div', { class: 'rp-people-merge__title' }, COPY.same(m.handle.value, m.personName)),
|
|
179
|
+
el('div', { class: 'rp-people-merge__why' }, m.why)),
|
|
180
|
+
button({ label: COPY.merge, icon: 'git-merge', variant: 'outline', size: 'sm', attrs: { 'data-people-act': 'merge', 'data-people-key': key },
|
|
181
|
+
onClick: () => decide(inst, { act: 'merge', person: m.person, handle: m.handle }, COPY.merged) }),
|
|
182
|
+
button({ label: COPY.keep, variant: 'ghost', size: 'sm', attrs: { 'data-people-act': 'keep-separate', 'data-people-key': key },
|
|
183
|
+
onClick: () => decide(inst, { act: 'keep-separate', a: m.a, b: m.b }, COPY.kept) })));
|
|
188
184
|
}
|
|
189
185
|
if (r.tones.length) rows.push(el('p', { class: 'rp-people-hint', 'data-people-tones': String(r.tones.length) }, COPY.tonesWait(r.tones.length)));
|
|
190
|
-
return el('
|
|
191
|
-
el('h3', { class: 'rp-people-sec__title' }, `${COPY.waiting} · ${r.count}`),
|
|
192
|
-
el('div', { role: 'list' }, rows));
|
|
186
|
+
return el('div', { class: 'rp-people-review', 'data-people-review': '' }, ...rows);
|
|
193
187
|
}
|
|
194
188
|
|
|
195
189
|
/* ── the list ────────────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
@@ -212,11 +206,15 @@ function shownPeople(inst) {
|
|
|
212
206
|
function personLine(inst, p) {
|
|
213
207
|
const open = inst.open === p.id;
|
|
214
208
|
const t = toneOf(p);
|
|
215
|
-
|
|
209
|
+
const toneClass = t === 'tense' || t === 'cautious' || t === 'warm' ? t : '';
|
|
210
|
+
const initials = p.name.split(/\s+/).map((w) => w[0]).join('').toUpperCase();
|
|
211
|
+
return el('div', { class: 'rp-people-item', role: 'listitem', 'data-people-person': p.id },
|
|
216
212
|
row({
|
|
217
213
|
title: p.name,
|
|
218
|
-
meta: [who(p), p.
|
|
219
|
-
|
|
214
|
+
meta: [[who(p), p.external ? COPY.external : null].filter(Boolean).join(' · ')],
|
|
215
|
+
lead: [el('span', { class: 'rp-people-row__avatar' }, initials)],
|
|
216
|
+
trail: t ? [el('span', { class: 'rp-people-row__tone' + (p.tone.confirmed ? '' : ' rp-people-row__tone--proposed'), 'data-people-tone': t },
|
|
217
|
+
el('i', { class: 'rp-people-row__tone-dot ' + toneClass }), p.tone.confirmed ? t : t + '?')] : [],
|
|
220
218
|
onOpen: () => { inst.open = p.id; draw(inst); },
|
|
221
219
|
openLabel: `${p.name}, ${who(p)}`,
|
|
222
220
|
openAttrs: { 'aria-current': open ? 'true' : null, 'data-people-open': p.id },
|
|
@@ -226,20 +224,19 @@ function personLine(inst, p) {
|
|
|
226
224
|
function listPane(inst) {
|
|
227
225
|
const v = inst.view;
|
|
228
226
|
const all = v.people.filter((p) => matches(p, inst.q.trim().toLowerCase()));
|
|
229
|
-
const controls = el('div', { class: 'rp-people-views' },
|
|
230
|
-
tabs({ variant: 'line', value: inst.group, ariaLabel: 'Group', attrs: { 'data-people-group': '' },
|
|
231
|
-
items: [{ id: 'all', label: COPY.all, count: all.length }, { id: 'team', label: COPY.team, count: all.filter((p) => !p.external).length },
|
|
232
|
-
{ id: 'external', label: COPY.external, count: all.filter((p) => p.external).length }],
|
|
233
|
-
onChange: (val) => { inst.group = val; draw(inst); } }),
|
|
234
|
-
tabs({ variant: 'segmented', value: inst.sort, ariaLabel: 'Sort', attrs: { 'data-people-sort': '' },
|
|
235
|
-
items: [{ id: 'latest', label: COPY.latest }, { id: 'most', label: COPY.most }, { id: 'name', label: COPY.name }],
|
|
236
|
-
onChange: (val) => { inst.sort = val; draw(inst); } }));
|
|
237
227
|
const list = shownPeople(inst);
|
|
238
228
|
let rows;
|
|
239
229
|
if (!v.people.length) rows = [el('p', { class: 'rp-section__empty' }, COPY.none)];
|
|
240
230
|
else if (!list.length) rows = [el('p', { class: 'rp-section__empty' }, COPY.noMatch(inst.q.trim()))];
|
|
241
231
|
else rows = [el('div', { role: 'list', 'data-people-list': '' }, list.map((p) => personLine(inst, p)))];
|
|
242
|
-
return el('div', { class: 'rp-people-
|
|
232
|
+
return el('div', { class: 'rp-people-card' },
|
|
233
|
+
el('div', { class: 'rp-people-card__header' },
|
|
234
|
+
el('p', { class: 'rp-people-card__label' }, COPY.title, el('span', { class: 'rp-people-card__count' }, all.length))),
|
|
235
|
+
inst.head.find,
|
|
236
|
+
tabs({ variant: 'segmented', full: true, value: inst.sort, ariaLabel: 'Sort', attrs: { 'data-people-sort': '' },
|
|
237
|
+
items: [{ id: 'latest', label: COPY.latest }, { id: 'most', label: COPY.most }, { id: 'name', label: COPY.name }],
|
|
238
|
+
onChange: (val) => { inst.sort = val; draw(inst); } }),
|
|
239
|
+
el('div', { class: 'rp-people-card__list' }, ...rows));
|
|
243
240
|
}
|
|
244
241
|
|
|
245
242
|
/* ── the person's page ───────────────────────────────────────────────────────────────────────────────────────────── */
|
|
@@ -250,11 +247,16 @@ function section(title, kids, attrs) {
|
|
|
250
247
|
|
|
251
248
|
function interactionsSec(p) {
|
|
252
249
|
if (!p.interactions.length) return section(COPY.interactions, [el('p', { class: 'rp-people-hint' }, COPY.noInteractions)], { 'data-people-sec': 'interactions' });
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
el('span', { class: 'rp-people-
|
|
250
|
+
const icons = { mail: 'mail', live: 'message-square', calendar: 'calendar', jira: 'ticket', gather: 'file-text', plan: 'file-text', notes: 'file-text' };
|
|
251
|
+
return section(`${COPY.interactions} · ${p.count}`, [el('div', { class: 'rp-people-ints' }, p.interactions.slice(0, 50).map((i) => {
|
|
252
|
+
const iconName = icons[i.source] || 'file-text';
|
|
253
|
+
return el('div', { class: 'rp-people-int', 'data-people-int': i.date },
|
|
254
|
+
el('span', { class: 'rp-people-int__icon' }, icon(iconName)),
|
|
255
|
+
el('div', {},
|
|
256
|
+
el('div', { class: 'rp-people-int__text' }, i.text),
|
|
257
|
+
el('small', {}, `${day(i.date)} · ${i.kind}`),
|
|
258
|
+
el('small', { class: 'rp-people-int__src' }, [SOURCE[i.source] || i.source, i.pointer, i.tone].filter(Boolean).join(' · '))));
|
|
259
|
+
}))],
|
|
258
260
|
{ 'data-people-sec': 'interactions' });
|
|
259
261
|
}
|
|
260
262
|
|
|
@@ -267,8 +269,6 @@ function toneSec(inst, p) {
|
|
|
267
269
|
else if (t.proposed) kids.push(el('p', { class: 'rp-people-tone', 'data-people-proposed': t.proposed }, COPY.proposed(t.proposed)));
|
|
268
270
|
if (mix) kids.push(el('p', { class: 'rp-people-hint', 'data-people-mix': '' }, mix));
|
|
269
271
|
const acts = [];
|
|
270
|
-
if (!t.confirmed && t.proposed) acts.push(button({ label: COPY.confirm, variant: 'primary', size: 'sm', attrs: { 'data-people-act': 'tone-confirm' },
|
|
271
|
-
onClick: () => decide(inst, { act: 'tone', person: p.id, tone: t.proposed }, COPY.toneSaved) }));
|
|
272
272
|
const another = button({ label: COPY.setAnother, variant: 'outline', size: 'sm', iconEnd: 'chevron-down', attrs: { 'data-people-act': 'tone-set', 'aria-haspopup': 'menu' },
|
|
273
273
|
onClick: () => menu({ anchor: another, ariaLabel: COPY.setAnother,
|
|
274
274
|
items: TONES.map((x) => ({ label: x, onSelect: () => decide(inst, { act: 'tone', person: p.id, tone: x }, COPY.toneSaved) })) }) });
|
|
@@ -281,25 +281,24 @@ function toneSec(inst, p) {
|
|
|
281
281
|
|
|
282
282
|
function sharedSec(p) {
|
|
283
283
|
const s = p.shared;
|
|
284
|
-
const
|
|
285
|
-
for (const g of s.initiatives)
|
|
284
|
+
const chips = [];
|
|
285
|
+
for (const g of s.initiatives) chips.push(el('span', { class: 'rp-people-chip', 'data-people-shared': 'initiative' }, icon('target'), `${g.id} · ${g.count} interaction${g.count === 1 ? '' : 's'}`));
|
|
286
286
|
for (const it of s.items) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
287
|
+
const chip = el('span', { class: 'rp-people-chip', 'data-people-shared': 'item', 'data-people-item': it.id }, icon(it.blocked ? 'ban' : 'target'), `${COPY.owns} ${it.title || it.id}${it.blocked ? ' · ' + COPY.blocked : ''}`);
|
|
288
|
+
if (it.blocked) chip.classList.add('warn');
|
|
289
|
+
chips.push(chip);
|
|
290
290
|
}
|
|
291
291
|
for (const m of s.meetings) {
|
|
292
292
|
const when = new Date(m.start);
|
|
293
|
-
|
|
294
|
-
`${COPY.meeting} · ${when.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' })} ${when.toTimeString().slice(0, 5)} · ${m.title}`));
|
|
293
|
+
chips.push(el('span', { class: 'rp-people-chip', 'data-people-shared': 'meeting' }, icon('calendar'), `${when.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' })} · ${m.title}`));
|
|
295
294
|
}
|
|
296
|
-
return section(COPY.shared,
|
|
295
|
+
return section(COPY.shared, chips.length ? [el('div', { class: 'rp-people-chips' }, chips)] : [el('p', { class: 'rp-people-hint' }, COPY.noShared)], { 'data-people-sec': 'shared' });
|
|
297
296
|
}
|
|
298
297
|
|
|
299
298
|
function notesSec(inst, p) {
|
|
300
|
-
const list = p.notes.map((n) => el('
|
|
299
|
+
const list = p.notes.map((n) => el('div', { class: 'rp-people-note', 'data-people-note': n.id },
|
|
301
300
|
el('span', { class: 'rp-people-note__text' }, n.text),
|
|
302
|
-
|
|
301
|
+
iconButton({ icon: 'x', ariaLabel: COPY.remove, size: 'sm', attrs: { 'data-people-act': 'note-remove', 'data-people-key': n.id },
|
|
303
302
|
onClick: () => decide(inst, { act: 'note-remove', person: p.id, id: n.id }, COPY.noteRemoved) })));
|
|
304
303
|
const f = field({ label: COPY.addNote, kind: 'textarea', hideLabel: true, placeholder: COPY.notePlaceholder, attrs: { 'data-people-field': 'note' } });
|
|
305
304
|
const save = async () => {
|
|
@@ -310,8 +309,8 @@ function notesSec(inst, p) {
|
|
|
310
309
|
if (r) f.control.value = '';
|
|
311
310
|
};
|
|
312
311
|
return section(COPY.notes, [
|
|
313
|
-
list.length ? el('
|
|
314
|
-
el('div', { class: 'rp-people-noteadd' }, f,
|
|
312
|
+
list.length ? el('div', { class: 'rp-people-list-plain' }, list) : el('p', { class: 'rp-people-hint' }, COPY.noNotes),
|
|
313
|
+
el('div', { class: 'rp-people-noteadd' }, f, iconButton({ icon: 'plus', ariaLabel: COPY.addNote, size: 'sm', attrs: { 'data-people-act': 'note-add' }, onClick: save })),
|
|
315
314
|
], { 'data-people-sec': 'notes' });
|
|
316
315
|
}
|
|
317
316
|
|
|
@@ -319,25 +318,52 @@ function identitySec(inst, p) {
|
|
|
319
318
|
const by = {};
|
|
320
319
|
for (const i of p.interactions) by[i.source] = (by[i.source] || 0) + 1;
|
|
321
320
|
const file = inst.view.file ? String(inst.view.file).split('/').slice(-3).join('/') : 'people.jsonl';
|
|
322
|
-
|
|
323
|
-
el('
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
321
|
+
const details = el('details', { class: 'rp-people-sec', 'data-people-sec': 'identity' },
|
|
322
|
+
el('summary', {},
|
|
323
|
+
icon('chevron-down'),
|
|
324
|
+
COPY.identity),
|
|
325
|
+
el('div', { class: 'rp-people-list-plain' },
|
|
326
|
+
p.handles.map((h) => el('div', { class: 'rp-people-handle', 'data-people-handle': h.kind + ':' + h.value }, handleText(h))),
|
|
327
|
+
Object.entries(by).map(([s, n]) => el('div', { class: 'rp-people-hint' }, `${SOURCE[s] || s}: ${n} interaction${n === 1 ? '' : 's'}`)),
|
|
328
|
+
(p.sources || []).length ? el('div', { class: 'rp-people-hint', 'data-people-sources': p.sources.join(',') }, COPY.foundIn(p.sources.map((s) => SOURCE[s] || s).join(', '))) : null,
|
|
329
|
+
el('div', { class: 'rp-people-hint rp-people-file' }, COPY.lives(file))));
|
|
330
|
+
return details;
|
|
329
331
|
}
|
|
330
332
|
|
|
331
333
|
function personPane(inst) {
|
|
332
334
|
const p = inst.view.people.find((x) => x.id === inst.open);
|
|
333
|
-
if (!p) return el('div', { class: 'rp-people-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
335
|
+
if (!p) return el('div', { class: 'rp-people-card' }, el('p', { class: 'rp-section__empty' }, COPY.pick));
|
|
336
|
+
const t = p.tone;
|
|
337
|
+
const confirmBtn = !t.confirmed && t.proposed ? button({ label: COPY.confirm + ' ' + t.proposed, icon: 'check', variant: 'primary', size: 'sm',
|
|
338
|
+
attrs: { 'data-people-act': 'tone-confirm' },
|
|
339
|
+
onClick: () => decide(inst, { act: 'tone', person: p.id, tone: t.proposed }, COPY.toneSaved) }) : null;
|
|
340
|
+
const moreBtn = iconButton({ icon: 'ellipsis', ariaLabel: 'More', attrs: { 'data-people-act': 'more' },
|
|
341
|
+
onClick: () => menu({ anchor: moreBtn, ariaLabel: 'Actions', items: [
|
|
342
|
+
{ label: COPY.removeFromPeople, icon: 'x', attrs: { 'data-people-act': 'remove' }, onSelect: async () => { const r = await decide(inst, { act: 'remove', person: p.id }, COPY.removed(p.name)); if (r) { inst.open = null; draw(inst); } } }
|
|
343
|
+
] }) });
|
|
344
|
+
return el('article', { class: 'rp-people-card', 'data-people-page': p.id },
|
|
345
|
+
el('div', { class: 'rp-people-page__header' },
|
|
346
|
+
el('div', { class: 'rp-people-page__info' },
|
|
347
|
+
el('span', { class: 'rp-people-page__avatar' }, p.name.split(/\s+/).map((w) => w[0]).join('').toUpperCase()),
|
|
348
|
+
el('div', { class: 'rp-people-page__text' },
|
|
349
|
+
el('h3', { class: 'rp-people-page__name' }, p.name),
|
|
350
|
+
el('p', { class: 'rp-people-hint' }, [who(p), p.external && (p.role || p.org) ? COPY.external : null].filter(Boolean).join(' · ')))),
|
|
351
|
+
el('div', { class: 'rp-people-page__actions' }, confirmBtn, moreBtn)),
|
|
352
|
+
el('div', { class: 'rp-people-page__tiles' },
|
|
353
|
+
el('div', { class: 'rp-people-tile' },
|
|
354
|
+
el('p', { class: 'rp-people-tile__label' }, COPY.interactions),
|
|
355
|
+
el('b', {}, p.count && p.last ? `${p.count} · ${day(p.last)}` : String(p.count || 0))),
|
|
356
|
+
el('div', { class: 'rp-people-tile' },
|
|
357
|
+
el('p', { class: 'rp-people-tile__label' }, COPY.tone),
|
|
358
|
+
el('b', {}, t.confirmed || t.proposed ? [
|
|
359
|
+
el('i', { class: 'rp-people-tile__dot ' + (t.confirmed ? 'rp-people-tile__dot--confirmed' : 'rp-people-tile__dot--proposed') }),
|
|
360
|
+
' ', t.confirmed ? t.confirmed : (t.proposed + '?')
|
|
361
|
+
] : 'None')),
|
|
362
|
+
el('div', { class: 'rp-people-tile' },
|
|
363
|
+
el('p', { class: 'rp-people-tile__label' }, COPY.shared),
|
|
364
|
+
el('b', {}, p.shared.initiatives.length ? `${p.shared.initiatives.length} ${p.shared.initiatives.length === 1 ? 'initiative' : 'initiatives'}` : 'None'))),
|
|
365
|
+
el('div', { class: 'rp-people-page__scroll' },
|
|
366
|
+
interactionsSec(p), toneSec(inst, p), sharedSec(p), notesSec(inst, p), identitySec(inst, p)));
|
|
341
367
|
}
|
|
342
368
|
|
|
343
369
|
/* ── the page ────────────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
@@ -348,7 +374,15 @@ function header(inst) {
|
|
|
348
374
|
const q = find.control;
|
|
349
375
|
q.addEventListener('input', () => { inst.q = q.value; draw(inst); });
|
|
350
376
|
q.addEventListener('keydown', (e) => { if (e.key === 'Escape') { q.resetCommitted(''); inst.q = ''; draw(inst); } });
|
|
351
|
-
|
|
377
|
+
const v = inst.view || { people: [] };
|
|
378
|
+
const all = v.people ? v.people.filter((p) => matches(p, inst.q.trim().toLowerCase())) : [];
|
|
379
|
+
const titleText = v.people && v.people.length ? `${v.people.length} ${v.people.length === 1 ? 'person' : 'people'}` : COPY.title;
|
|
380
|
+
inst.titleEl = el('h2', { class: 'rp-head__title' }, titleText);
|
|
381
|
+
inst.head = el('div', { class: 'rp-people-top' },
|
|
382
|
+
inst.titleEl,
|
|
383
|
+
tabs({ variant: 'segmented', value: inst.group, ariaLabel: 'Group', attrs: { 'data-people-group': '' },
|
|
384
|
+
items: [{ id: 'all', label: COPY.all }, { id: 'team', label: COPY.team }, { id: 'external', label: COPY.external }],
|
|
385
|
+
onChange: (val) => { inst.group = val; draw(inst); } }));
|
|
352
386
|
inst.head.find = find;
|
|
353
387
|
return inst.head;
|
|
354
388
|
}
|
|
@@ -367,5 +401,9 @@ function draw(inst) {
|
|
|
367
401
|
// Focus stays in a note being typed: a redraw of the page would drop it, so it waits until the field is left.
|
|
368
402
|
const typing = document.activeElement && inst.body.contains(document.activeElement) && document.activeElement.matches('textarea');
|
|
369
403
|
if (typing) { inst.last = ''; return; }
|
|
370
|
-
|
|
404
|
+
const n = inst.view.people.length;
|
|
405
|
+
inst.titleEl.textContent = n ? `${n} ${n === 1 ? 'person' : 'people'}` : COPY.title;
|
|
406
|
+
const rev = review(inst);
|
|
407
|
+
const grid = el('div', { class: 'rp-people-grid' }, listPane(inst), personPane(inst));
|
|
408
|
+
inst.body.replaceChildren(...[rev, grid].filter(Boolean));
|
|
371
409
|
}
|
|
@@ -89,9 +89,10 @@ export function groupsOf(repo, { all = false } = {}) {
|
|
|
89
89
|
const out = [...by.keys()].sort((a, b) => rank(a) - rank(b) || a.localeCompare(b))
|
|
90
90
|
.map((i) => ({ key: 'i:' + i, title: titleOf(i), initiative: i, automatic: false, sessions: by.get(i) }));
|
|
91
91
|
if (none.length) out.push({ key: 'none', title: C().notLinked, initiative: null, automatic: false, sessions: none });
|
|
92
|
-
// hidden by default; the session in front is never hidden, and neither is one
|
|
92
|
+
// hidden by default; the session in front is never hidden, and neither is one held on a permission call (a finished agent reads
|
|
93
|
+
// 'needs you' too, so that state alone does not show it)
|
|
93
94
|
const front = frontSessionId();
|
|
94
|
-
const show = all || state.dsShowAuto || sched.some((s) => s.id === front || s.shown === '
|
|
95
|
+
const show = all || state.dsShowAuto || sched.some((s) => s.id === front || s.shown === 'blocked');
|
|
95
96
|
if (sched.length && show) out.push({ key: 'auto', title: C().automatic, initiative: null, automatic: true, sessions: sched });
|
|
96
97
|
return out;
|
|
97
98
|
}
|
|
@@ -104,13 +104,15 @@ export const COPY = {
|
|
|
104
104
|
toggle: (need) => (need ? `Your day · ${need} ${need === 1 ? 'needs' : 'need'} you` : 'Your day: calendar, jobs, goals and mentions'),
|
|
105
105
|
fold: 'Fold your day',
|
|
106
106
|
hello: { morning: 'Good morning.', afternoon: 'Good afternoon.', evening: 'Good evening.' },
|
|
107
|
+
unfinished: (open, all) => (!all ? 'No goals yet' : open ? `${open} unfinished ${open === 1 ? 'goal' : 'goals'}` : 'All goals done'),
|
|
107
108
|
free: (min) => `${Math.floor(min / 60)} h ${min % 60} min free`,
|
|
108
109
|
steps: ['Read', 'Sort', 'Agents work', 'You approve'],
|
|
109
110
|
stepTitle: ['Read your sources', 'Make the jobs', 'Agents do the work', 'You approve'],
|
|
110
111
|
stepHint: ['The session reads your calendar and your mentions.', 'Each job is tied to a goal or a meeting.', 'One session per job, at most 5 at once.', 'Only what sends or decides waits for you.'],
|
|
111
112
|
stepOf: (n) => `Prepare my day · step ${n} of 4`,
|
|
112
113
|
status: {
|
|
113
|
-
idle: '
|
|
114
|
+
idle: 'Not prepared yet',
|
|
115
|
+
last: (at, n) => (at ? `Last prepared ${at}` : 'Prepared today') + (n ? ` · ${n} ${n === 1 ? 'needs' : 'need'} you` : ''),
|
|
114
116
|
reading: 'Reading your calendar and mentions…',
|
|
115
117
|
readFailed: (why) => `The read stopped: ${why}`,
|
|
116
118
|
working: (w, n) => `${w} ${w === 1 ? 'agent' : 'agents'} working · ${n} ${n === 1 ? 'needs' : 'need'} you`,
|
|
@@ -389,34 +389,38 @@ function select(type, id) {
|
|
|
389
389
|
function topBar(ctx) {
|
|
390
390
|
const { m, ws } = ctx;
|
|
391
391
|
const si = stepIndex(m.stage);
|
|
392
|
-
const
|
|
392
|
+
const press = runs.get(ws);
|
|
393
|
+
const busy = !!press && press.state === 'busy';
|
|
394
|
+
const bs = m.run && m.run.brief_session;
|
|
395
|
+
const reading = !!bs && !bs.ended_at && m.stage === 'reading' && !m.readFailed;
|
|
396
|
+
// the four steps show as a small progress bar only while Prepare my day is running; otherwise the bar says when it last ran
|
|
397
|
+
const inProgress = busy || reading || (!!m.run && m.stage === 'working');
|
|
398
|
+
const steps = inProgress ? el('ol', { class: 'td-day__steps', 'aria-label': D.prepare }, ...D.steps.map((t, i) => {
|
|
393
399
|
const st = si < 0 ? 'todo' : i < si || si === 4 ? 'done' : i === si ? 'now' : 'todo';
|
|
394
|
-
return el('li', null, button({ label: [el('span', { class: 'td-
|
|
395
|
-
attrs: { class: 'td-day__ts', 'data-step-state': st, 'data-day-step': String(i), 'data-day-sel': 'step:' + i, 'aria-current': ctx.cur('step', String(i)), title: D.stepHint[i] } }));
|
|
396
|
-
}));
|
|
400
|
+
return el('li', null, button({ label: [el('span', { class: 'td-day__seg', 'aria-hidden': 'true' })], variant: 'ghost', size: 'sm', onClick: () => select('step', i),
|
|
401
|
+
attrs: { class: 'td-day__ts', 'data-step-state': st, 'data-day-step': String(i), 'data-day-sel': 'step:' + i, 'aria-current': ctx.cur('step', String(i)), 'aria-label': t + ': ' + D.stepHint[i], title: t + ': ' + D.stepHint[i] } }));
|
|
402
|
+
})) : null;
|
|
397
403
|
const working = m.tasks.filter((t) => t.status === 'working' || t.status === 'sending').length;
|
|
398
|
-
const
|
|
399
|
-
|
|
404
|
+
const started = m.run && m.run.started_at ? hhmm(m.run.started_at) : '';
|
|
405
|
+
const status = !m.run ? (busy ? D.starting : D.status.idle) : m.readFailed ? D.status.readFailed(m.readFailed.why) : m.stage === 'reading' ? D.status.reading : m.stage === 'working' ? D.status.working(working, m.needs)
|
|
406
|
+
: D.status.last(started, m.needs);
|
|
400
407
|
let check = null;
|
|
401
408
|
if (m.run && m.check) {
|
|
402
409
|
const every = m.settings && m.settings.dayCheck && typeof m.settings.dayCheck.every === 'number' ? m.settings.dayCheck.every : m.check.every;
|
|
403
410
|
const running = !!m.check.running || ui.busy.has('check');
|
|
404
|
-
check = every === 0 ? el('span', { class: 'td-day__check', 'data-day': 'check' }, D.checkOff)
|
|
405
|
-
: el('span', { class: 'td-day__check', 'data-day': 'check'
|
|
406
|
-
|
|
411
|
+
check = every === 0 ? el('span', { class: 'td-day__check', 'data-day': 'check', title: D.checkOff }, D.checkOff)
|
|
412
|
+
: el('span', { class: 'td-day__check', 'data-day': 'check', title: running ? D.checking : D.checked(hhmm(m.check.last), hhmm(m.check.next)) },
|
|
413
|
+
iconButton({ icon: 'refresh-cw', ariaLabel: D.checkNow, size: 'sm', attrs: { 'data-day': 'check-now', disabled: running ? 'true' : null }, onClick: () => checkNow(ws) }));
|
|
407
414
|
}
|
|
408
|
-
const press = runs.get(ws);
|
|
409
|
-
const busy = !!press && press.state === 'busy';
|
|
410
|
-
const bs = m.run && m.run.brief_session;
|
|
411
|
-
const reading = !!bs && !bs.ended_at && m.stage === 'reading' && !m.readFailed;
|
|
412
415
|
const label = busy ? D.starting : reading ? D.stillReading : m.run ? D.again : D.prepare;
|
|
416
|
+
const open = m.allGoals.filter((g) => g.status === 'open').length;
|
|
413
417
|
return el('header', { class: 'td-day__bar' },
|
|
414
|
-
(() => { const when = [dateLabel(m.now),
|
|
418
|
+
(() => { const when = [dateLabel(m.now), D.unfinished(open, m.allGoals.length)].filter(Boolean).join(' · ');
|
|
415
419
|
return el('div', { class: 'td-day__hello', title: greet(m.now) + ' ' + when }, el('b', null, greet(m.now)), ' ', el('span', { class: 'td-day__dim' }, when)); })(),
|
|
416
420
|
steps,
|
|
417
421
|
el('span', { class: 'td-day__status', role: 'status', 'data-day': 'status', title: status }, status),
|
|
418
422
|
check,
|
|
419
|
-
|
|
423
|
+
iconButton({ icon: 'settings', ariaLabel: D.sources, size: 'sm', attrs: { 'data-day': 'sources', 'data-day-sel': 'sources:all', 'aria-current': ctx.cur('sources', 'all') }, onClick: () => select('sources', 'all') }),
|
|
420
424
|
button({ label, variant: 'primary', size: 'sm', disabled: busy || reading, attrs: { 'data-day': 'prepare', 'data-goals': 'prepare-day', title: D.prepareHint, 'aria-busy': busy ? 'true' : 'false' }, onClick: () => prepareDay(ws) }),
|
|
421
425
|
iconButton({ icon: 'chevron-down', ariaLabel: D.fold, size: 'sm', expanded: true, attrs: { 'data-day': 'toggle' }, onClick: () => fold(false) }));
|
|
422
426
|
}
|
|
@@ -37,7 +37,7 @@ export function unsyncedOf(st) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/** The lucide symbol each place wears in the bar (UI comfort, 2026-10-07: a word alone is slow to find; the phone's tab bar already wears these). */
|
|
40
|
-
const NAV_ICON = Object.freeze({ today: 'house', mywork: 'list-checks', initiatives: 'target', board: 'square-kanban', reports: 'chart-column', docs: 'file-text', people: '
|
|
40
|
+
const NAV_ICON = Object.freeze({ today: 'house', mywork: 'list-checks', initiatives: 'target', board: 'square-kanban', reports: 'chart-column', docs: 'file-text', people: 'users', settings: 'settings' });
|
|
41
41
|
|
|
42
42
|
export function createTopbar({ go, openPalette, repoMenu, sync } = {}) {
|
|
43
43
|
const node = el('header', { class: 'fr-bar', 'data-shell': 'top', 'data-fr': 'bar' });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* lucide-sprite.js — GENERATED by workspace/third-party/build-lucide-sprite.js from lucide-static 1.52.0.
|
|
2
|
-
* Do not edit by hand. The icons: grip-vertical, plus, check, square, square-check-big, x, undo-2, flag, bot, chevron-right, chevron-down, ellipsis, circle-alert, history, arrow-up, arrow-down, diamond, settings, search, refresh-cw, chevron-left, sun, moon, arrow-left, arrow-right, globe, columns-2, arrow-up-down, arrow-left-right, panel-top, panel-left, maximize-2, minimize-2, git-branch, clock, bold, italic, code, link, type, heading, list, list-checks, text-quote, file-code, git-compare, cloud, share-2, house, target, square-kanban, file-text, square-terminal, calendar, chart-column.
|
|
2
|
+
* Do not edit by hand. The icons: grip-vertical, plus, check, square, square-check-big, x, undo-2, flag, bot, chevron-right, chevron-down, ellipsis, circle-alert, history, arrow-up, arrow-down, diamond, settings, search, refresh-cw, chevron-left, sun, moon, arrow-left, arrow-right, globe, columns-2, arrow-up-down, arrow-left-right, panel-top, panel-left, maximize-2, minimize-2, git-branch, clock, bold, italic, code, link, type, heading, list, list-checks, text-quote, file-code, git-compare, cloud, share-2, house, target, square-kanban, file-text, square-terminal, calendar, chart-column, users, git-merge, mail, message-square, ticket, ban.
|
|
3
3
|
*
|
|
4
4
|
* ISC License
|
|
5
5
|
*
|
|
@@ -101,5 +101,11 @@ export const SPRITE = '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true
|
|
|
101
101
|
+ '<symbol id="lucide-square-terminal" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m7 11 2-2-2-2" /><path d="M11 13h4" /><rect width="18" height="18" x="3" y="3" rx="2" ry="2" /></symbol>'
|
|
102
102
|
+ '<symbol id="lucide-calendar" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v3" /><path d="M16 2v3" /><rect x="3" y="3" width="18" height="18" rx="2" /><path d="M3 9h18" /></symbol>'
|
|
103
103
|
+ '<symbol id="lucide-chart-column" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v16a2 2 0 0 0 2 2h16" /><path d="M18 17V9" /><path d="M13 17V5" /><path d="M8 17v-3" /></symbol>'
|
|
104
|
+
+ '<symbol id="lucide-users" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /><path d="M16 3.128a4 4 0 0 1 0 7.744" /><path d="M22 21v-2a4 4 0 0 0-3-3.87" /><circle cx="9" cy="7" r="4" /></symbol>'
|
|
105
|
+
+ '<symbol id="lucide-git-merge" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="18" r="3" /><circle cx="6" cy="6" r="3" /><path d="M6 21V9a9 9 0 0 0 9 9" /></symbol>'
|
|
106
|
+
+ '<symbol id="lucide-mail" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" /><rect x="2" y="4" width="20" height="16" rx="2" /></symbol>'
|
|
107
|
+
+ '<symbol id="lucide-message-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z" /></symbol>'
|
|
108
|
+
+ '<symbol id="lucide-ticket" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z" /><path d="M13 5v2" /><path d="M13 17v2" /><path d="M13 11v2" /></symbol>'
|
|
109
|
+
+ '<symbol id="lucide-ban" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M4.929 4.929 19.07 19.071" /></symbol>'
|
|
104
110
|
+ '</svg>';
|
|
105
|
-
export const ICONS = ["grip-vertical","plus","check","square","square-check-big","x","undo-2","flag","bot","chevron-right","chevron-down","ellipsis","circle-alert","history","arrow-up","arrow-down","diamond","settings","search","refresh-cw","chevron-left","sun","moon","arrow-left","arrow-right","globe","columns-2","arrow-up-down","arrow-left-right","panel-top","panel-left","maximize-2","minimize-2","git-branch","clock","bold","italic","code","link","type","heading","list","list-checks","text-quote","file-code","git-compare","cloud","share-2","house","target","square-kanban","file-text","square-terminal","calendar","chart-column"];
|
|
111
|
+
export const ICONS = ["grip-vertical","plus","check","square","square-check-big","x","undo-2","flag","bot","chevron-right","chevron-down","ellipsis","circle-alert","history","arrow-up","arrow-down","diamond","settings","search","refresh-cw","chevron-left","sun","moon","arrow-left","arrow-right","globe","columns-2","arrow-up-down","arrow-left-right","panel-top","panel-left","maximize-2","minimize-2","git-branch","clock","bold","italic","code","link","type","heading","list","list-checks","text-quote","file-code","git-compare","cloud","share-2","house","target","square-kanban","file-text","square-terminal","calendar","chart-column","users","git-merge","mail","message-square","ticket","ban"];
|
package/workspace/server.js
CHANGED
|
@@ -5642,7 +5642,7 @@ async function handleRequest(req, res) {
|
|
|
5642
5642
|
fs.mkdirSync(STATE, { recursive: true });
|
|
5643
5643
|
fs.writeFileSync(SESSION_POLICY_FILE(), JSON.stringify(next, null, 2) + '\n');
|
|
5644
5644
|
}
|
|
5645
|
-
return json(res, { ok: true, policy: readSessionPolicy(),
|
|
5645
|
+
return json(res, { ok: true, policy: readSessionPolicy(), manual_origins: [...sessionOrigin.MANUAL_EXACT, ...sessionOrigin.MANUAL_PREFIX.map((x) => x + '*')] });
|
|
5646
5646
|
}
|
|
5647
5647
|
// End a session and remove it, with an answer. The WebSocket kill/remove messages return nothing,
|
|
5648
5648
|
// so a refusal or a slow exit left the PM clicking a control that seemed to do nothing.
|
|
@@ -12,7 +12,7 @@ The workspace runs with no network (D62). Two third-party browser files are comm
|
|
|
12
12
|
| Package | Version | Licence | Tarball | Tarball sha256 | Shipped file |
|
|
13
13
|
|---|---|---|---|---|---|
|
|
14
14
|
| sortablejs | 1.15.7 | MIT | https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.7.tgz | `66cbfc8fa18860783584fe0c346baf6cbf3399da72d23d21053425ba232abed4` | `public/vendor/Sortable.min.js`: an unmodified copy of `package/Sortable.min.js` (45 478 bytes, sha256 `bf4241bc73fef7f11c59a283a69fe8051cdd31c6d8ff5a2b9ba219e7831fcf76`; npm integrity `sha512-Kk8wLQPlS+yi1ZEf48a4+fzHa4yxjC30M/Sr2AnQu+f/MPwvvX9XjZ6OWejiz8crBsLwSq8GHqaxaET7u6ux0A==`) |
|
|
15
|
-
| lucide-static | 1.52.0 | ISC, plus the Feather MIT notice | https://registry.npmjs.org/lucide-static/-/lucide-static-1.52.0.tgz | `4d2aa079171e4eba4c704844b8b0c9edb6e4cfb2fb8bd86f7b14353994f04a87` | `public/vendor/lucide-sprite.js`: generated by `build-lucide-sprite.js` (
|
|
15
|
+
| lucide-static | 1.52.0 | ISC, plus the Feather MIT notice | https://registry.npmjs.org/lucide-static/-/lucide-static-1.52.0.tgz | `4d2aa079171e4eba4c704844b8b0c9edb6e4cfb2fb8bd86f7b14353994f04a87` | `public/vendor/lucide-sprite.js`: generated by `build-lucide-sprite.js` (21 404 bytes, sha256 `b97ca7eba4a6f6d29614c41d3b050c96df67d77b36a5db5ee175b830d003edfc`). It holds 61 icons: grip-vertical, plus, check, square, square-check-big, x, undo-2, flag, bot, chevron-right, chevron-down, ellipsis, circle-alert, history, arrow-up, arrow-down, diamond, then the eight the frame's chrome draws (Run 3): settings, search, refresh-cw, chevron-left, sun, moon, arrow-left, arrow-right, then globe, columns-2, arrow-up-down and arrow-left-right for the dock strip's web, split and orientation chips (Run 3, C6), then panel-top, panel-left, maximize-2, minimize-2, git-branch and clock for the dock's icon controls and session rows (0.54.2), then bold, italic, code, link, type, heading, list, list-checks, text-quote, file-code, git-compare, cloud and share-2 for the document tab's in-place editor, its Confluence copy and its connections, then house, target, square-kanban, file-text, square-terminal, calendar and chart-column for the phone's tab bar and view switches, then users, git-merge, mail, message-square, ticket and ban for the People place's nav icon, merge button and interaction sources |
|
|
16
16
|
|
|
17
17
|
The licences are in this folder: [sortablejs-1.15.7.LICENSE](sortablejs-1.15.7.LICENSE) and [lucide-static-1.52.0.LICENSE](lucide-static-1.52.0.LICENSE). The sprite also carries the lucide licence text as its first comment.
|
|
18
18
|
|