create-pmos 0.80.0 → 0.82.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/scripts/workflow-state.js +2 -2
- package/payload/state/discoveries.md +1 -1
- package/workspace/guide/layout.md +3 -1
- package/workspace/lib/engine/workflow-state.js +2 -2
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/app.js +3 -1
- package/workspace/public/pm/components.css +63 -56
- package/workspace/public/pm/dest-today.js +3 -2
- package/workspace/public/pm/dom.js +60 -0
- package/workspace/public/pm/roadmap.js +2 -2
- package/workspace/public/pm/topbar.js +4 -1
- package/workspace/run-tests.js +5 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.82.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-07T15:34:34Z"}
|
|
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-07T15:34:34Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -1104,8 +1104,8 @@ function computeWorkflowDerived(id, opts) {
|
|
|
1104
1104
|
steps.push(step('accept', 'accept', 'blocked', `blocked by: ${blockers.join(', ')}`,
|
|
1105
1105
|
'the Acceptance Gate is the PM\'s (D12) — but it does not open over an unfinished ladder'));
|
|
1106
1106
|
} else {
|
|
1107
|
-
steps.push(step('accept', 'accept', 'pending', '
|
|
1108
|
-
'
|
|
1107
|
+
steps.push(step('accept', 'accept', 'pending', 'no acceptance record needed (D94)',
|
|
1108
|
+
'merge once the required checks are green and the PM has not returned the run'));
|
|
1109
1109
|
}
|
|
1110
1110
|
}
|
|
1111
1111
|
|
|
@@ -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-07T15:34:34Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -11,11 +11,13 @@ On a desktop screen the workspace is one frame. It has one top bar, the place on
|
|
|
11
11
|
|
|
12
12
|
## The top bar
|
|
13
13
|
|
|
14
|
+
Corners are rounded a little: 6 pixels on buttons and fields, 8 pixels on cards and sheets. Buttons and tabs use normal capital letters. Only the small headings above a list stay in capitals. The page does not scroll. Only the place on screen scrolls, and the top bar stays where it is. The tab you are on is marked with a straight line under it.
|
|
15
|
+
|
|
14
16
|
The top bar has these parts, from left to right:
|
|
15
17
|
|
|
16
18
|
- **Repository ▾** names the current repository. Press it to choose another repository, or **All repositories** to see the work of every repository at once. All repositories replaces the old Portfolio mode.
|
|
17
19
|
- **Branches and sessions** is in ⌘K. It opens a sheet with every branch of the repository (main first) and how many sessions run in each. Press a branch to see its sessions, open one, end one, or start a session there. **+ branch** makes a branch, and **+ session** starts one with a branch, an agent and a profile. With All repositories, the sheet lists every repository.
|
|
18
|
-
- **Agenda, Initiatives, Board, Reports, Docs and People** are the
|
|
20
|
+
- **Agenda, My work, Initiatives, Board, Reports, Docs and People** are the places. Each has an icon before its name, the same icon as on the phone. Press one to show it. The Agenda count shows how many items need you. Docs shows whether each document is the same as its Google Docs and Confluence copy. People shows the people you work with in the repository ([People](people.md)).
|
|
19
21
|
- **Search or capture ⌘K** opens the command palette. Type to find an initiative, a repository, an open tab or a command.
|
|
20
22
|
- **Sync** shows only when the current repository is not in sync with its remote. Press it to fetch, bring in the work of the other machine, record your changes and push. If the repository has not fetched in the last 10 minutes, Sync also shows, because a push from another machine is not visible until a fetch. When the repository fetched recently and is in sync, Sync is not shown. When two machines add lines to the same ledger (a `.jsonl` file under `pmos/` or `planning/`), Sync keeps the lines from both machines. If Sync stops, the message names the step that stopped. If both machines changed the same lines of another file, Sync changes nothing and names the files. Then choose **Push this machine’s version** or **Pull the other machine’s version**. Sync keeps that version of those files, keeps all other changes from both machines, and finishes. It never overwrites the remote history.
|
|
21
23
|
- **Sync on its own.** If you forget to press Sync, the workspace syncs for you. It syncs each repository once when the workspace starts. It also syncs a repository that has records not yet pushed when no record changed for 5 minutes. In a document repository it syncs every changed file on any branch. In a code repository it syncs only on the default branch (for example `main`), and only when every commit not yet pushed changes records and no code. If you close the tab while records are not yet pushed, the browser asks you first. To turn automatic sync off, start the workspace with `PMOS_AUTO_SYNC=0`.
|
|
@@ -1104,8 +1104,8 @@ function computeWorkflowDerived(id, opts) {
|
|
|
1104
1104
|
steps.push(step('accept', 'accept', 'blocked', `blocked by: ${blockers.join(', ')}`,
|
|
1105
1105
|
'the Acceptance Gate is the PM\'s (D12) — but it does not open over an unfinished ladder'));
|
|
1106
1106
|
} else {
|
|
1107
|
-
steps.push(step('accept', 'accept', 'pending', '
|
|
1108
|
-
'
|
|
1107
|
+
steps.push(step('accept', 'accept', 'pending', 'no acceptance record needed (D94)',
|
|
1108
|
+
'merge once the required checks are green and the PM has not returned the run'));
|
|
1109
1109
|
}
|
|
1110
1110
|
}
|
|
1111
1111
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.82.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.82.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* the shell still comes up.
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
-
import { el, clear, setChildren } from './dom.js';
|
|
33
|
+
import { el, clear, setChildren, keepScroll } from './dom.js';
|
|
34
34
|
import { state, setState, subscribe, flush, FIXTURE, LEGACY_LAYOUT_FLAG } from './state.js';
|
|
35
35
|
/* The phone surface (≤760px). It owns its own chrome and decides which single panel is visible;
|
|
36
36
|
* the shell's only part in it is to keep all four hosts mounted below the breakpoint and to hand
|
|
@@ -563,6 +563,8 @@ let frDetail = null;
|
|
|
563
563
|
const hostMain = el('div', { class: 'fr-main', 'data-fr': 'main' });
|
|
564
564
|
const destHosts = {};
|
|
565
565
|
for (const id of FRAME_DESTS) destHosts[id] = el('div', { class: 'fr-dest', 'data-fr-host': id, hidden: true });
|
|
566
|
+
// each place keeps its reading position when it redraws through a short state (dom.js keepScroll)
|
|
567
|
+
for (const id of FRAME_DESTS) keepScroll(destHosts[id]);
|
|
566
568
|
const frEnd = el('span', { class: 'fr-end', 'aria-hidden': 'true' });
|
|
567
569
|
|
|
568
570
|
function setupFrame() {
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* primitives `.pmc-*` — written only by primitives.js and icons.js (R28-9)
|
|
6
6
|
* layouts `.bd-*` — the Breakdown's own arrangement of those primitives
|
|
7
7
|
* Every selector starts with `.pmc-` or `.bd-`. Colours are tokens.css variables only; there is no hex and no oklch here.
|
|
8
|
-
* Radius
|
|
8
|
+
* Radius (R27 as amended by the PM 2026-10-07): 6px on controls (buttons, inputs, selects, tabs, segmented controls,
|
|
9
|
+
* badges, menu items, checkboxes), 8px on cards, sheets, dialogs, menus, tiles and panels; a dot (50% or a pill) stays round.
|
|
10
|
+
* Buttons, tabs and segmented controls use sentence case; small non-interactive eyebrows may stay uppercase at 0.04em. Hover rules sit inside (hover: hover). Under a coarse pointer every
|
|
9
11
|
* control is at least 44px and every row and card at least 48px.
|
|
10
12
|
*/
|
|
11
13
|
|
|
@@ -53,15 +55,13 @@
|
|
|
53
55
|
min-height: 32px;
|
|
54
56
|
padding: 0 14px;
|
|
55
57
|
border: 1px solid var(--border);
|
|
56
|
-
border-radius:
|
|
58
|
+
border-radius: 6px;
|
|
57
59
|
background: transparent;
|
|
58
60
|
color: var(--foreground);
|
|
59
61
|
font-family: var(--sans);
|
|
60
62
|
font-size: 12px;
|
|
61
63
|
line-height: 1;
|
|
62
|
-
font-weight:
|
|
63
|
-
letter-spacing: 0.1em;
|
|
64
|
-
text-transform: uppercase;
|
|
64
|
+
font-weight: 500;
|
|
65
65
|
white-space: nowrap;
|
|
66
66
|
cursor: pointer;
|
|
67
67
|
user-select: none;
|
|
@@ -128,6 +128,7 @@
|
|
|
128
128
|
display: none;
|
|
129
129
|
}
|
|
130
130
|
.pmc-field {
|
|
131
|
+
position: relative;
|
|
131
132
|
display: flex;
|
|
132
133
|
flex-direction: column;
|
|
133
134
|
gap: 2px;
|
|
@@ -141,6 +142,8 @@
|
|
|
141
142
|
}
|
|
142
143
|
.pmc-field__label--hidden {
|
|
143
144
|
position: absolute;
|
|
145
|
+
top: 0;
|
|
146
|
+
left: 0;
|
|
144
147
|
width: 1px;
|
|
145
148
|
height: 1px;
|
|
146
149
|
overflow: hidden;
|
|
@@ -158,7 +161,7 @@
|
|
|
158
161
|
padding: 0;
|
|
159
162
|
border: 0;
|
|
160
163
|
border-bottom: 1px solid var(--input);
|
|
161
|
-
border-radius:
|
|
164
|
+
border-radius: 6px;
|
|
162
165
|
background: transparent;
|
|
163
166
|
color: var(--foreground);
|
|
164
167
|
font-family: var(--sans);
|
|
@@ -218,7 +221,7 @@
|
|
|
218
221
|
height: 16px;
|
|
219
222
|
margin: 0;
|
|
220
223
|
border: 1px solid var(--muted-foreground);
|
|
221
|
-
border-radius:
|
|
224
|
+
border-radius: 6px;
|
|
222
225
|
background: transparent;
|
|
223
226
|
cursor: pointer;
|
|
224
227
|
}
|
|
@@ -249,7 +252,7 @@
|
|
|
249
252
|
font-size: 11px;
|
|
250
253
|
line-height: 16px;
|
|
251
254
|
font-weight: 600;
|
|
252
|
-
letter-spacing: 0.
|
|
255
|
+
letter-spacing: 0.04em;
|
|
253
256
|
text-transform: uppercase;
|
|
254
257
|
white-space: nowrap;
|
|
255
258
|
color: var(--muted-foreground);
|
|
@@ -452,7 +455,7 @@
|
|
|
452
455
|
min-width: 0;
|
|
453
456
|
min-height: 36px;
|
|
454
457
|
padding: 10px 12px;
|
|
455
|
-
border-radius:
|
|
458
|
+
border-radius: 8px;
|
|
456
459
|
background: var(--card);
|
|
457
460
|
color: var(--foreground);
|
|
458
461
|
box-shadow: 0 0 0 1px color-mix(in oklab, var(--foreground) 9%, transparent),
|
|
@@ -553,14 +556,14 @@
|
|
|
553
556
|
min-height: 26px;
|
|
554
557
|
padding: 0 12px;
|
|
555
558
|
border: 1px solid transparent;
|
|
556
|
-
border-radius:
|
|
559
|
+
border-radius: 6px;
|
|
557
560
|
background: transparent;
|
|
558
561
|
color: color-mix(in oklab, var(--foreground) 68%, transparent);
|
|
559
562
|
font-family: var(--sans);
|
|
560
563
|
font-size: 12px;
|
|
561
564
|
line-height: 1;
|
|
562
565
|
font-weight: 600;
|
|
563
|
-
letter-spacing: 0.
|
|
566
|
+
letter-spacing: 0.04em;
|
|
564
567
|
text-transform: uppercase;
|
|
565
568
|
white-space: nowrap;
|
|
566
569
|
cursor: pointer;
|
|
@@ -599,6 +602,7 @@
|
|
|
599
602
|
}
|
|
600
603
|
.pmc-tabs--line .pmc-tabs__tab[aria-selected="true"] {
|
|
601
604
|
background: transparent;
|
|
605
|
+
border-radius: 0;
|
|
602
606
|
box-shadow: inset 0 -2px 0 var(--foreground);
|
|
603
607
|
}
|
|
604
608
|
|
|
@@ -615,7 +619,7 @@
|
|
|
615
619
|
overflow: auto;
|
|
616
620
|
padding: 4px;
|
|
617
621
|
border: 0;
|
|
618
|
-
border-radius:
|
|
622
|
+
border-radius: 8px;
|
|
619
623
|
background: var(--card);
|
|
620
624
|
color: var(--foreground);
|
|
621
625
|
font-family: var(--sans);
|
|
@@ -633,7 +637,7 @@
|
|
|
633
637
|
min-height: 32px;
|
|
634
638
|
padding: 0 12px;
|
|
635
639
|
border: 0;
|
|
636
|
-
border-radius:
|
|
640
|
+
border-radius: 6px;
|
|
637
641
|
background: transparent;
|
|
638
642
|
color: var(--foreground);
|
|
639
643
|
font: inherit;
|
|
@@ -663,7 +667,7 @@
|
|
|
663
667
|
margin: 0;
|
|
664
668
|
padding: 0;
|
|
665
669
|
border: 0;
|
|
666
|
-
border-radius:
|
|
670
|
+
border-radius: 8px;
|
|
667
671
|
background: var(--card);
|
|
668
672
|
color: var(--foreground);
|
|
669
673
|
font-family: var(--sans);
|
|
@@ -794,7 +798,7 @@
|
|
|
794
798
|
min-height: 44px;
|
|
795
799
|
padding: 4px 4px 4px 16px;
|
|
796
800
|
border: 0;
|
|
797
|
-
border-radius:
|
|
801
|
+
border-radius: 8px;
|
|
798
802
|
background: var(--card);
|
|
799
803
|
color: var(--foreground);
|
|
800
804
|
font-family: var(--sans);
|
|
@@ -936,7 +940,7 @@
|
|
|
936
940
|
margin: 0;
|
|
937
941
|
padding: 0;
|
|
938
942
|
border: 0;
|
|
939
|
-
border-radius:
|
|
943
|
+
border-radius: 6px;
|
|
940
944
|
background: transparent;
|
|
941
945
|
color: inherit;
|
|
942
946
|
transform: translateY(-50%);
|
|
@@ -1084,7 +1088,7 @@
|
|
|
1084
1088
|
margin-right: 10px;
|
|
1085
1089
|
font-size: 12px;
|
|
1086
1090
|
font-weight: 600;
|
|
1087
|
-
letter-spacing: 0.
|
|
1091
|
+
letter-spacing: 0.04em;
|
|
1088
1092
|
text-transform: uppercase;
|
|
1089
1093
|
}
|
|
1090
1094
|
.bd-entry__meta {
|
|
@@ -1200,7 +1204,7 @@
|
|
|
1200
1204
|
font-size: 11px;
|
|
1201
1205
|
line-height: 16px;
|
|
1202
1206
|
font-weight: 600;
|
|
1203
|
-
letter-spacing: 0.
|
|
1207
|
+
letter-spacing: 0.04em;
|
|
1204
1208
|
text-transform: uppercase;
|
|
1205
1209
|
color: var(--muted-foreground);
|
|
1206
1210
|
}
|
|
@@ -1287,7 +1291,7 @@
|
|
|
1287
1291
|
font-size: 12px;
|
|
1288
1292
|
line-height: 16px;
|
|
1289
1293
|
font-weight: 600;
|
|
1290
|
-
letter-spacing: 0.
|
|
1294
|
+
letter-spacing: 0.04em;
|
|
1291
1295
|
text-transform: uppercase;
|
|
1292
1296
|
}
|
|
1293
1297
|
.bd-col__n {
|
|
@@ -1545,10 +1549,9 @@
|
|
|
1545
1549
|
.in-head__title {
|
|
1546
1550
|
flex: 1 1 auto;
|
|
1547
1551
|
margin: 0;
|
|
1548
|
-
font-size:
|
|
1549
|
-
line-height:
|
|
1550
|
-
font-weight:
|
|
1551
|
-
letter-spacing: -0.01em;
|
|
1552
|
+
font-size: 20px;
|
|
1553
|
+
line-height: 28px;
|
|
1554
|
+
font-weight: 500;
|
|
1552
1555
|
}
|
|
1553
1556
|
.in-body {
|
|
1554
1557
|
display: flex;
|
|
@@ -1645,7 +1648,7 @@
|
|
|
1645
1648
|
font-size: 12px;
|
|
1646
1649
|
line-height: 16px;
|
|
1647
1650
|
font-weight: 600;
|
|
1648
|
-
letter-spacing: 0.
|
|
1651
|
+
letter-spacing: 0.04em;
|
|
1649
1652
|
text-transform: uppercase;
|
|
1650
1653
|
font-variant-numeric: tabular-nums;
|
|
1651
1654
|
}
|
|
@@ -1670,7 +1673,7 @@
|
|
|
1670
1673
|
font-size: 12px;
|
|
1671
1674
|
line-height: 16px;
|
|
1672
1675
|
font-weight: 600;
|
|
1673
|
-
letter-spacing: 0.
|
|
1676
|
+
letter-spacing: 0.04em;
|
|
1674
1677
|
text-transform: uppercase;
|
|
1675
1678
|
}
|
|
1676
1679
|
.in-col__count {
|
|
@@ -1910,7 +1913,7 @@
|
|
|
1910
1913
|
font-size: 12px;
|
|
1911
1914
|
line-height: 16px;
|
|
1912
1915
|
font-weight: 600;
|
|
1913
|
-
letter-spacing: 0.
|
|
1916
|
+
letter-spacing: 0.04em;
|
|
1914
1917
|
text-transform: uppercase;
|
|
1915
1918
|
}
|
|
1916
1919
|
.in-starters {
|
|
@@ -2133,7 +2136,7 @@
|
|
|
2133
2136
|
margin: 0;
|
|
2134
2137
|
font-size: 12px;
|
|
2135
2138
|
font-weight: 600;
|
|
2136
|
-
letter-spacing: 0.
|
|
2139
|
+
letter-spacing: 0.04em;
|
|
2137
2140
|
text-transform: uppercase;
|
|
2138
2141
|
color: var(--muted-foreground);
|
|
2139
2142
|
}
|
|
@@ -2222,6 +2225,7 @@
|
|
|
2222
2225
|
}
|
|
2223
2226
|
.fr-link[aria-current="page"] {
|
|
2224
2227
|
color: var(--foreground);
|
|
2228
|
+
border-radius: 0;
|
|
2225
2229
|
box-shadow: inset 0 -2px 0 var(--foreground);
|
|
2226
2230
|
}
|
|
2227
2231
|
.fr-mark {
|
|
@@ -2266,6 +2270,8 @@
|
|
|
2266
2270
|
min-width: 0;
|
|
2267
2271
|
min-height: 0;
|
|
2268
2272
|
overflow-y: auto;
|
|
2273
|
+
/* the destination is the one thing that scrolls: a wheel at its end does not move the page and its bars */
|
|
2274
|
+
overscroll-behavior: contain;
|
|
2269
2275
|
}
|
|
2270
2276
|
.fr-dest[hidden] {
|
|
2271
2277
|
display: none;
|
|
@@ -2476,7 +2482,7 @@
|
|
|
2476
2482
|
color: var(--muted-foreground);
|
|
2477
2483
|
font-size: 11px;
|
|
2478
2484
|
font-weight: 600;
|
|
2479
|
-
letter-spacing: 0.
|
|
2485
|
+
letter-spacing: 0.04em;
|
|
2480
2486
|
text-transform: uppercase;
|
|
2481
2487
|
text-overflow: ellipsis;
|
|
2482
2488
|
white-space: nowrap;
|
|
@@ -2765,7 +2771,7 @@
|
|
|
2765
2771
|
color: var(--muted-foreground);
|
|
2766
2772
|
font-size: 11px;
|
|
2767
2773
|
font-weight: 600;
|
|
2768
|
-
letter-spacing: 0.
|
|
2774
|
+
letter-spacing: 0.04em;
|
|
2769
2775
|
text-transform: uppercase;
|
|
2770
2776
|
list-style: none;
|
|
2771
2777
|
cursor: pointer;
|
|
@@ -3007,7 +3013,7 @@
|
|
|
3007
3013
|
color: var(--muted-foreground);
|
|
3008
3014
|
font-size: 11px;
|
|
3009
3015
|
font-weight: 600;
|
|
3010
|
-
letter-spacing: 0.
|
|
3016
|
+
letter-spacing: 0.04em;
|
|
3011
3017
|
text-transform: uppercase;
|
|
3012
3018
|
}
|
|
3013
3019
|
.fr-rail__graph {
|
|
@@ -3089,7 +3095,7 @@
|
|
|
3089
3095
|
color: var(--muted-foreground);
|
|
3090
3096
|
font-size: 11px;
|
|
3091
3097
|
font-weight: 600;
|
|
3092
|
-
letter-spacing: 0.
|
|
3098
|
+
letter-spacing: 0.04em;
|
|
3093
3099
|
text-transform: uppercase;
|
|
3094
3100
|
}
|
|
3095
3101
|
.fr-rail__finding[data-status="stale"] .fr-rail__finding-s,
|
|
@@ -3245,7 +3251,7 @@
|
|
|
3245
3251
|
.td-day__warn { color: var(--warning); }
|
|
3246
3252
|
.td-day__mono { font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 12px; }
|
|
3247
3253
|
.td-day__steps { display: flex; flex-wrap: wrap; gap: 2px; list-style: none; margin: 0; padding: 0; }
|
|
3248
|
-
.td-day__ts { gap: 6px; padding: 0 6px; letter-spacing: 0.04em; color: var(--muted-foreground); }
|
|
3254
|
+
.td-day__ts { border-radius: 6px; gap: 6px; padding: 0 6px; letter-spacing: 0.04em; color: var(--muted-foreground); }
|
|
3249
3255
|
.td-day__ts[data-step-state="done"], .td-day__ts[data-step-state="now"] { color: var(--foreground); }
|
|
3250
3256
|
.td-day__ts[data-step-state="now"] { background: var(--muted); }
|
|
3251
3257
|
.td-day__ts[aria-current="true"] { border-color: var(--ring); }
|
|
@@ -3260,14 +3266,15 @@
|
|
|
3260
3266
|
.td-day__run--failed { color: var(--destructive); }
|
|
3261
3267
|
.td-day__note { margin: 0; font-size: 12px; line-height: 16px; color: var(--muted-foreground); }
|
|
3262
3268
|
|
|
3263
|
-
.td-day__cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: 12px; align-items:
|
|
3264
|
-
.td-day__left { display: grid; gap: 12px; min-width: 0; }
|
|
3269
|
+
.td-day__cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: 12px; align-items: stretch; }
|
|
3270
|
+
.td-day__left { display: grid; gap: 12px; min-width: 0; align-content: start; }
|
|
3265
3271
|
.td-day__panel, .td-day__detail {
|
|
3266
3272
|
display: grid;
|
|
3267
3273
|
gap: 8px;
|
|
3268
3274
|
min-width: 0;
|
|
3269
3275
|
padding: 10px 12px;
|
|
3270
3276
|
border: 1px solid var(--border);
|
|
3277
|
+
border-radius: 8px;
|
|
3271
3278
|
background: var(--card);
|
|
3272
3279
|
}
|
|
3273
3280
|
.td-day__detail { position: sticky; top: 8px; max-height: calc(100vh - 16px); overflow: auto; align-content: start; }
|
|
@@ -3282,7 +3289,7 @@
|
|
|
3282
3289
|
margin: 0;
|
|
3283
3290
|
font-size: 12px;
|
|
3284
3291
|
font-weight: 600;
|
|
3285
|
-
letter-spacing: 0.
|
|
3292
|
+
letter-spacing: 0.04em;
|
|
3286
3293
|
text-transform: uppercase;
|
|
3287
3294
|
color: var(--muted-foreground);
|
|
3288
3295
|
}
|
|
@@ -3295,7 +3302,7 @@
|
|
|
3295
3302
|
.td-day__lead { min-width: 44px; font: 12px var(--mono); color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
|
|
3296
3303
|
.td-day__chips { display: inline-flex; gap: 4px; }
|
|
3297
3304
|
.td-day__chips { align-items: center; flex-wrap: wrap; justify-content: flex-end; }
|
|
3298
|
-
.td-day__repo { font: inherit; font-size: 12px; color: var(--foreground); background: var(--background); border: 1px solid var(--border); border-radius:
|
|
3305
|
+
.td-day__repo { font: inherit; font-size: 12px; color: var(--foreground); background: var(--background); border: 1px solid var(--border); border-radius: 6px; padding: 2px 6px; max-width: 160px; }
|
|
3299
3306
|
.td-day__repo:focus-visible { outline: 2px solid color-mix(in oklab, var(--ring) 50%, var(--foreground)); outline-offset: 2px; }
|
|
3300
3307
|
.td-day__nowrow { display: flex; align-items: center; gap: 8px; padding: 0 8px; font: 11px var(--mono); color: var(--destructive); }
|
|
3301
3308
|
.td-day__nowrow::after { content: ""; flex: 1; border-top: 2px solid var(--destructive); }
|
|
@@ -3306,7 +3313,7 @@
|
|
|
3306
3313
|
|
|
3307
3314
|
/* the detail */
|
|
3308
3315
|
.td-day__dhead { display: grid; gap: 4px; }
|
|
3309
|
-
.td-day__kind { font: 11px var(--mono); letter-spacing: 0.
|
|
3316
|
+
.td-day__kind { font: 11px var(--mono); letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted-foreground); }
|
|
3310
3317
|
.td-day__dhead h3 { margin: 0; font-size: 16px; line-height: 24px; color: var(--foreground); }
|
|
3311
3318
|
.td-day__detail p { margin: 0; font-size: 14px; line-height: 20px; color: var(--foreground); }
|
|
3312
3319
|
.td-day__detail p.td-day__note { font-size: 12px; line-height: 16px; color: var(--muted-foreground); }
|
|
@@ -3317,11 +3324,10 @@
|
|
|
3317
3324
|
height: 28px;
|
|
3318
3325
|
padding: 0 10px;
|
|
3319
3326
|
border: 1px solid var(--border);
|
|
3327
|
+
border-radius: 6px;
|
|
3320
3328
|
color: var(--foreground);
|
|
3321
3329
|
font-size: 12px;
|
|
3322
|
-
font-weight:
|
|
3323
|
-
letter-spacing: 0.1em;
|
|
3324
|
-
text-transform: uppercase;
|
|
3330
|
+
font-weight: 500;
|
|
3325
3331
|
text-decoration: none;
|
|
3326
3332
|
white-space: nowrap;
|
|
3327
3333
|
}
|
|
@@ -3329,7 +3335,7 @@
|
|
|
3329
3335
|
@media (pointer: coarse) { .td-day__a { min-height: 44px; } }
|
|
3330
3336
|
.td-day__phase, .td-day__rel { display: grid; gap: 4px; }
|
|
3331
3337
|
.td-day__phase h4, .td-day__rel h4, .td-day__notes h4, .td-day__draftbox h4, .td-day__more h4 {
|
|
3332
|
-
margin: 0; font-size: 11px; letter-spacing: 0.
|
|
3338
|
+
margin: 0; font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted-foreground);
|
|
3333
3339
|
}
|
|
3334
3340
|
.td-day__chk { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; font-size: 14px; color: var(--foreground); }
|
|
3335
3341
|
.td-day__chk li { display: grid; grid-template-columns: 16px minmax(0, 1fr); gap: 6px; }
|
|
@@ -3351,7 +3357,7 @@
|
|
|
3351
3357
|
.td-day__draft { margin: 0; max-height: 220px; overflow: auto; padding: 8px 10px; white-space: pre-wrap; font: 12px/1.5 var(--mono); background: var(--muted); color: var(--foreground); }
|
|
3352
3358
|
.td-day__revise { display: grid; gap: 6px; }
|
|
3353
3359
|
/* act and prepare (initiative today-act-prepare): a verb first, one sentence, the steps, one button that fits, the marks */
|
|
3354
|
-
.td-day__verb { display: inline-block; min-width: 58px; padding: 2px 5px; border: 1px solid currentColor; font: 600 10px/1.3 var(--mono); letter-spacing: 0.
|
|
3360
|
+
.td-day__verb { display: inline-block; min-width: 58px; padding: 2px 5px; border: 1px solid currentColor; font: 600 10px/1.3 var(--mono); letter-spacing: 0.04em; text-transform: uppercase; text-align: center; color: var(--foreground); white-space: nowrap; }
|
|
3355
3361
|
.td-day__verb[data-verb="decide"], .td-day__verb[data-verb="prepare"] { color: var(--warning); }
|
|
3356
3362
|
.td-day__acth { margin: 0; font-size: 16px; font-weight: 600; line-height: 1.3; text-wrap: balance; }
|
|
3357
3363
|
.td-day__todo { margin: 0; padding: 7px 10px; border-left: 3px solid var(--foreground); background: var(--muted); font-size: 14px; line-height: 1.45; color: var(--foreground); }
|
|
@@ -3459,7 +3465,7 @@
|
|
|
3459
3465
|
gap: 8px;
|
|
3460
3466
|
min-width: 0;
|
|
3461
3467
|
padding: 10px 12px;
|
|
3462
|
-
border-radius:
|
|
3468
|
+
border-radius: 8px;
|
|
3463
3469
|
background: var(--card);
|
|
3464
3470
|
color: var(--foreground);
|
|
3465
3471
|
box-shadow: 0 0 0 1px var(--border);
|
|
@@ -3553,7 +3559,7 @@
|
|
|
3553
3559
|
}
|
|
3554
3560
|
.td-health {
|
|
3555
3561
|
padding: 8px 12px;
|
|
3556
|
-
border-radius:
|
|
3562
|
+
border-radius: 8px;
|
|
3557
3563
|
background: var(--muted);
|
|
3558
3564
|
box-shadow: inset 2px 0 0 var(--warning);
|
|
3559
3565
|
font-size: 12px;
|
|
@@ -4011,13 +4017,13 @@
|
|
|
4011
4017
|
margin: 16px 16px 0;
|
|
4012
4018
|
padding: 12px;
|
|
4013
4019
|
border: 1px solid var(--border);
|
|
4014
|
-
border-radius:
|
|
4020
|
+
border-radius: 8px;
|
|
4015
4021
|
}
|
|
4016
4022
|
.bo-releases__head {
|
|
4017
4023
|
font-size: 12px;
|
|
4018
4024
|
line-height: 16px;
|
|
4019
4025
|
font-weight: 600;
|
|
4020
|
-
letter-spacing: 0.
|
|
4026
|
+
letter-spacing: 0.04em;
|
|
4021
4027
|
text-transform: uppercase;
|
|
4022
4028
|
}
|
|
4023
4029
|
.bo-releases__note {
|
|
@@ -4036,7 +4042,7 @@
|
|
|
4036
4042
|
.bo-rel--today {
|
|
4037
4043
|
padding: 10px 12px;
|
|
4038
4044
|
border: 1px solid var(--border);
|
|
4039
|
-
border-radius:
|
|
4045
|
+
border-radius: 8px;
|
|
4040
4046
|
background: var(--card);
|
|
4041
4047
|
}
|
|
4042
4048
|
.bo-rel__main {
|
|
@@ -4119,7 +4125,7 @@
|
|
|
4119
4125
|
font-size: 11px;
|
|
4120
4126
|
line-height: 16px;
|
|
4121
4127
|
font-weight: 500;
|
|
4122
|
-
letter-spacing: 0.
|
|
4128
|
+
letter-spacing: 0.04em;
|
|
4123
4129
|
text-transform: uppercase;
|
|
4124
4130
|
color: var(--muted-foreground);
|
|
4125
4131
|
}
|
|
@@ -4266,7 +4272,7 @@
|
|
|
4266
4272
|
font-family: var(--mono);
|
|
4267
4273
|
font-size: 11px;
|
|
4268
4274
|
font-weight: 500;
|
|
4269
|
-
letter-spacing: 0.
|
|
4275
|
+
letter-spacing: 0.04em;
|
|
4270
4276
|
text-transform: uppercase;
|
|
4271
4277
|
color: var(--muted-foreground);
|
|
4272
4278
|
}
|
|
@@ -5883,6 +5889,8 @@
|
|
|
5883
5889
|
}
|
|
5884
5890
|
.nt-sr {
|
|
5885
5891
|
position: absolute;
|
|
5892
|
+
top: 0;
|
|
5893
|
+
left: 0;
|
|
5886
5894
|
width: 1px;
|
|
5887
5895
|
height: 1px;
|
|
5888
5896
|
overflow: hidden;
|
|
@@ -5925,7 +5933,7 @@
|
|
|
5925
5933
|
margin: 0;
|
|
5926
5934
|
font-size: 11px;
|
|
5927
5935
|
font-weight: 500;
|
|
5928
|
-
letter-spacing: .
|
|
5936
|
+
letter-spacing: 0.04em;
|
|
5929
5937
|
text-transform: uppercase;
|
|
5930
5938
|
color: var(--muted-foreground);
|
|
5931
5939
|
}
|
|
@@ -6200,7 +6208,7 @@
|
|
|
6200
6208
|
gap: 2px;
|
|
6201
6209
|
padding: 8px 12px;
|
|
6202
6210
|
border: 1px solid var(--border);
|
|
6203
|
-
border-radius:
|
|
6211
|
+
border-radius: 8px;
|
|
6204
6212
|
background: var(--background);
|
|
6205
6213
|
color: var(--foreground);
|
|
6206
6214
|
font: inherit;
|
|
@@ -6585,10 +6593,9 @@
|
|
|
6585
6593
|
color: var(--foreground);
|
|
6586
6594
|
}
|
|
6587
6595
|
|
|
6588
|
-
/* R27
|
|
6589
|
-
|
|
6590
|
-
.
|
|
6591
|
-
|
|
6592
|
-
}
|
|
6596
|
+
/* R27 as amended by the PM 2026-10-07: the dock follows the same radii as every other surface (6px controls, 8px
|
|
6597
|
+
containers). The tab strip and the scratchpad are drawn with inline radii (workspace-cockpit-nav); the inline radii
|
|
6598
|
+
(8px tab strip, 6px controls) stay. A tab's colour dot is a dot and keeps its circle. */
|
|
6599
|
+
|
|
6593
6600
|
|
|
6594
6601
|
/* -- end of the layout sections: add nothing below this line -- */
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* The legacy Today view (view-today.js) calls mountToday on its own host, so both surfaces draw the one list.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { el } from './dom.js';
|
|
15
|
+
import { el, withAnchor } from './dom.js';
|
|
16
16
|
import { empty, tabs, button, toast, ensureComponentsCss } from './primitives.js';
|
|
17
17
|
import { registerTodaySource, todayCards, todayCount, openInitiative } from './registry.js';
|
|
18
18
|
import { repoTuples } from './panel-repos.js';
|
|
@@ -200,7 +200,8 @@ export function mountToday(host, ctx) {
|
|
|
200
200
|
if (out.sig === r.sig) return;
|
|
201
201
|
const k = focusKey(r.root);
|
|
202
202
|
r.sig = out.sig;
|
|
203
|
-
|
|
203
|
+
// a card added or removed above the reader must not move what they are reading (dom.js withAnchor)
|
|
204
|
+
withAnchor(host, r.root, 'data-today-id', () => r.root.replaceChildren(...out.children.filter(Boolean)));
|
|
204
205
|
refocus(r.root, k);
|
|
205
206
|
}
|
|
206
207
|
|
|
@@ -114,3 +114,63 @@ export function setChildren(parent, kids) {
|
|
|
114
114
|
});
|
|
115
115
|
return parent;
|
|
116
116
|
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Keep the reader's place across a rebuild (UI comfort, 2026-10-07: "when I scroll down into a section it pages up").
|
|
120
|
+
* Before `rebuild()` runs, the first element carrying `attr` that is still on screen in `scroller` is remembered with its
|
|
121
|
+
* offset from the scroller's top; afterwards the scroller is moved so that same element sits at that same offset. A
|
|
122
|
+
* card added or removed above the reader therefore no longer pushes what they are reading off the screen. With the
|
|
123
|
+
* scroller at its top, or no such element, it only runs `rebuild()`.
|
|
124
|
+
*/
|
|
125
|
+
export function withAnchor(scroller, root, attr, rebuild) {
|
|
126
|
+
let key = null;
|
|
127
|
+
let off = 0;
|
|
128
|
+
if (scroller && scroller.scrollTop > 0 && root) {
|
|
129
|
+
const top = scroller.getBoundingClientRect().top;
|
|
130
|
+
for (const n of root.querySelectorAll('[' + attr + ']')) {
|
|
131
|
+
const r = n.getBoundingClientRect();
|
|
132
|
+
if (r.height && r.bottom > top) { key = n.getAttribute(attr); off = r.top - top; break; }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
rebuild();
|
|
136
|
+
if (key == null) return;
|
|
137
|
+
const again = [...root.querySelectorAll('[' + attr + ']')].find((n) => n.getAttribute(attr) === key);
|
|
138
|
+
if (!again) return;
|
|
139
|
+
const drift = again.getBoundingClientRect().top - scroller.getBoundingClientRect().top - off;
|
|
140
|
+
if (Math.abs(drift) >= 1) scroller.scrollTop += drift;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Give a scroller its reading position back when a rebuild took it away (UI comfort, 2026-10-07). A destination that
|
|
145
|
+
* redraws through a short state — a "Reading…" line, an emptied list — collapses the scroller, the browser clamps the
|
|
146
|
+
* offset towards the top, and when the content returns the reader is left at the top. This watches the scroller's
|
|
147
|
+
* subtree: when a change leaves the offset ABOVE where the reader last was and the content has shrunk below it, that
|
|
148
|
+
* place is held, and the next change that makes the content tall enough again returns to it. Anything the reader does
|
|
149
|
+
* (wheel, touch, a key, a click) drops what is held, so their own scrolling always wins. Returns its undo.
|
|
150
|
+
*/
|
|
151
|
+
export function keepScroll(scroller) {
|
|
152
|
+
let top = scroller.scrollTop;
|
|
153
|
+
let held = null;
|
|
154
|
+
let until = 0;
|
|
155
|
+
const drop = () => { held = null; };
|
|
156
|
+
const onScroll = () => { if (held == null) top = scroller.scrollTop; };
|
|
157
|
+
const intents = ['wheel', 'touchstart', 'keydown', 'pointerdown'];
|
|
158
|
+
for (const ev of intents) scroller.addEventListener(ev, drop, { passive: true, capture: true });
|
|
159
|
+
scroller.addEventListener('scroll', onScroll, { passive: true });
|
|
160
|
+
const mo = new MutationObserver(() => {
|
|
161
|
+
if (scroller.hidden || !scroller.isConnected) return;
|
|
162
|
+
const max = scroller.scrollHeight - scroller.clientHeight;
|
|
163
|
+
if (held == null) {
|
|
164
|
+
if (top > 0 && scroller.scrollTop < top - 1 && max < top) { held = top; until = Date.now() + 5000; }
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (Date.now() > until) { held = null; top = scroller.scrollTop; return; }
|
|
168
|
+
if (max >= held - 1) { scroller.scrollTop = held; top = held; held = null; }
|
|
169
|
+
});
|
|
170
|
+
mo.observe(scroller, { childList: true, subtree: true, characterData: true });
|
|
171
|
+
return () => {
|
|
172
|
+
mo.disconnect();
|
|
173
|
+
for (const ev of intents) scroller.removeEventListener(ev, drop, { capture: true });
|
|
174
|
+
scroller.removeEventListener('scroll', onScroll);
|
|
175
|
+
};
|
|
176
|
+
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* env = { repo, ws, openInitiative(repo, slug), onData?(answer) }
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import { el } from './dom.js';
|
|
23
|
+
import { el, withAnchor } from './dom.js';
|
|
24
24
|
import { card, iconButton, dragHandle, button, menu, empty, toast, SEL } from './primitives.js';
|
|
25
25
|
import { watchSig } from './sig-watch.js';
|
|
26
26
|
import { loadSortable, baseOptions, dragSet } from './bd-drag.js';
|
|
@@ -211,7 +211,7 @@ export function mountRoadmap(host, env) {
|
|
|
211
211
|
const kids = [headLine(), el('p', { class: 'in-note in-hint', 'data-in-hint': '' }, COPY.menuHintCard)];
|
|
212
212
|
if (data.flags && data.flags.length) kids.push(el('p', { class: 'in-note', 'data-in-flags': '' }, COPY.flags(data.flags.length)));
|
|
213
213
|
kids.push(el('div', { class: 'in-cols', 'data-in-cols': '' }, HORIZONS.map(column)));
|
|
214
|
-
root.replaceChildren(...kids);
|
|
214
|
+
withAnchor(root.closest('.fr-dest'), root, 'data-in-card', () => root.replaceChildren(...kids));
|
|
215
215
|
if (refocus) { const t = root.querySelector('[data-in-open="' + CSS.escape(refocus) + '"]'); if (t) t.focus({ preventScroll: true }); }
|
|
216
216
|
bindDrag();
|
|
217
217
|
}
|
|
@@ -36,6 +36,9 @@ export function unsyncedOf(st) {
|
|
|
36
36
|
&& ((st.behind | 0) > 0 || (st.ahead | 0) > 0 || (st.dirty_records | 0) > 0 || st.remote_stale === true);
|
|
37
37
|
}
|
|
38
38
|
|
|
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: 'share-2', settings: 'settings' });
|
|
41
|
+
|
|
39
42
|
export function createTopbar({ go, openPalette, repoMenu, sync } = {}) {
|
|
40
43
|
const node = el('header', { class: 'fr-bar', 'data-shell': 'top', 'data-fr': 'bar' });
|
|
41
44
|
|
|
@@ -48,7 +51,7 @@ export function createTopbar({ go, openPalette, repoMenu, sync } = {}) {
|
|
|
48
51
|
const links = {};
|
|
49
52
|
for (const id of [...BAR_DESTS, 'settings']) {
|
|
50
53
|
links[id] = button({
|
|
51
|
-
label: COPY.nav[id], variant: 'ghost', icon: id
|
|
54
|
+
label: COPY.nav[id], variant: 'ghost', icon: NAV_ICON[id],
|
|
52
55
|
attrs: { class: 'fr-link', 'data-fr-dest': id, 'aria-current': 'false' },
|
|
53
56
|
onClick: () => go && go(id),
|
|
54
57
|
});
|
package/workspace/run-tests.js
CHANGED
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
* file in exactly one shard), and --check fails when a suite on disk is in no list, so a new suite
|
|
19
19
|
* that nobody wired in is a red build rather than a suite that runs nowhere.
|
|
20
20
|
*
|
|
21
|
-
* --changed
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* --changed narrows the group to the suites a diff against REF (default origin/main) can reach, and
|
|
22
|
+
* runs the whole group whenever it cannot tell. The local inner loop uses it, and so does Workspace
|
|
23
|
+
* CI for the browser group on a PR (ci-browser-after-merge): every browser suite still runs after the
|
|
24
|
+
* merge on main, and kit-publish waits for that run. See changedSuites() for the rule.
|
|
25
25
|
*
|
|
26
26
|
* Output: a suite's own output is buffered and printed whole when it finishes, so lines from two
|
|
27
27
|
* suites never interleave. A passing suite prints its last lines; --verbose prints all of it.
|
|
@@ -256,7 +256,7 @@ async function main() {
|
|
|
256
256
|
if (changed) {
|
|
257
257
|
const ref = changed === true ? 'origin/main' : changed;
|
|
258
258
|
const pick = changedSuites(list, ref);
|
|
259
|
-
console.log(`run-tests: --changed against ${ref}: ${pick.why}. ${pick.suites.length} of ${list.length} suites run here;
|
|
259
|
+
console.log(`run-tests: --changed against ${ref}: ${pick.why}. ${pick.suites.length} of ${list.length} suites run here; every suite runs on main after the merge.`);
|
|
260
260
|
list = pick.suites;
|
|
261
261
|
if (!list.length) return;
|
|
262
262
|
}
|