create-pmos 0.13.0 → 0.15.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.
@@ -82,6 +82,7 @@
82
82
  --success-wash:color-mix(in oklab, var(--success) 10%, transparent);
83
83
  --warning-wash:color-mix(in oklab, var(--warning) 10%, transparent);
84
84
  --info-wash:color-mix(in oklab, var(--info) 10%, transparent);
85
+ --destructive-wash:color-mix(in oklab, var(--destructive) 8%, transparent);
85
86
 
86
87
  --glass:color-mix(in oklab, var(--background) 80%, transparent);
87
88
  --shadow:oklch(0.148 0.004 228.8 / 0.18);
@@ -105,11 +106,22 @@
105
106
  sans scale and mono is reserved for things that ARE code: the terminal, paths, branches,
106
107
  diffs, code spans. ── */
107
108
  --text-2xs:11px; --text-xs:12px; --text-sm:13px; --text-base:14px; --text-lg:16px;
109
+ /* Half-steps the design reference uses between those rungs. They are named for the ROLE they
110
+ play rather than continuing the t-shirt scale, because that is what they are: a fixed set of
111
+ jobs in the navigator and the detail panel, not a general-purpose ladder. Anything outside
112
+ those jobs should reach for a rung above, not add a step here. */
113
+ --text-label:9.5px; /* uppercase field labels — "evals", "sessions", "artifacts" */
114
+ --text-tiny:10px; /* section captions and stage keys */
115
+ --text-meta:10.5px; /* mono ids, counts, pills, badges */
116
+ --text-row:11.5px; /* the second line of a list row */
117
+ --text-body:12.5px; /* panel prose and session names */
118
+ --text-title:15px; /* the initiative name at the head of the detail panel */
119
+ --text-doc:21px; /* an <h1> inside a rendered document */
108
120
  --mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
109
121
  --sans:"Space Grotesk Variable",ui-sans-serif,system-ui,-apple-system,sans-serif;
110
122
 
111
123
  /* layout knobs the JS adjusts (rail width, library/document split) */
112
- --rail-w:400px; --libsplit:44%;
124
+ --rail-w:400px;
113
125
 
114
126
  /* xterm palette tokens — the single source for the JS terminal theme (xtheme()). Dark in BOTH
115
127
  themes by design; the terminal is a terminal, not a themed surface. Concrete hex only (see
@@ -156,6 +168,7 @@
156
168
  --success-wash:color-mix(in oklab, var(--success) 10%, transparent);
157
169
  --warning-wash:color-mix(in oklab, var(--warning) 10%, transparent);
158
170
  --info-wash:color-mix(in oklab, var(--info) 10%, transparent);
171
+ --destructive-wash:color-mix(in oklab, var(--destructive) 12%, transparent);
159
172
 
160
173
  --glass:color-mix(in oklab, var(--background) 78%, transparent);
161
174
  --shadow:oklch(0 0 0 / 0.6);
@@ -180,7 +193,12 @@ button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
180
193
  /* The rows are fixed and the middle row is minmax(0,1fr): without the 0 minimum a tall column
181
194
  (a long needs strip, a long document) grows the row past the viewport and body{overflow:hidden}
182
195
  silently clips the BOTTOM OF THE TERMINAL. Every nested column repeats the min-height:0. */
183
- .app{display:grid; grid-template-rows:44px minmax(0,1fr) 28px; height:100vh; height:100dvh}
196
+ /* The COLUMN track is declared for the same reason the rows are. An implicit `auto` column sizes
197
+ to its widest child's min-content, so any pane that cannot shrink — a long branch chip, a deeply
198
+ indented navigator row — widens the whole shell and the status strip runs off the right edge of
199
+ a phone. minmax(0,…) makes the shell the authority on its own width. */
200
+ .app{display:grid; grid-template-columns:minmax(0,1fr); grid-template-rows:44px minmax(0,1fr) 28px;
201
+ height:100vh; height:100dvh}
184
202
 
185
203
  /* ── top bar ── */
186
204
  /* position+z-index so the header's own popover (the layout menu) paints ABOVE the body: the two
@@ -221,13 +239,33 @@ button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
221
239
  min-height:0}
222
240
  .side{grid-area:side} .term-col{grid-area:term} .rail{grid-area:read}
223
241
  .body[data-side="off"]>.side{display:none}
224
- .body[data-reader="off"]>.rail{display:none}
225
- /* docked bottom, the reader is a wide short pane under the terminal, not a column beside it */
226
- .body[data-reader="bottom"]>.rail{border-left:0;border-top:1px solid var(--border)}
227
- .body[data-reader="bottom"] .railgrip{display:none}
242
+
243
+ /* ── collapsed column rails ────────────────────────────────────────────────────────────────
244
+ Collapsing is NOT the same verb as the layout system's `off`. `off` removes a column's track
245
+ entirely and is reached from the layout menu; collapse keeps the track at 36px and leaves a
246
+ labelled edge you click to get back. The distinction matters because the label is what makes
247
+ the column recoverable without opening a menu — a pane that vanishes with no visible way back
248
+ is the failure the layout engine's hasEscape() exists to prevent, and a collapse control has
249
+ to hold to the same standard. The rail is a real <button>, so it is tabbable and screen
250
+ readers announce it; the rotated text is decoration over an aria-label, never the only name. */
251
+ /* MUST come before the `display:flex` below, and must exist at all: a rule that sets `display`
252
+ silently defeats the UA's `[hidden]{display:none}`, so both rails stayed in the grid as extra
253
+ implicit columns and pushed the app 405px wider than the viewport at 1024px. */
254
+ .colrail[hidden]{display:none}
255
+ .colrail{grid-row:1;background:var(--sidebar);border-right:1px solid var(--border);
256
+ display:flex;justify-content:center;padding-top:14px;min-width:0;
257
+ transition:background-color var(--duration-fast) var(--ease-emphasized)}
258
+ .colrail:hover{background:var(--accent)}
259
+ .colrail>span{writing-mode:vertical-rl;font-size:var(--text-2xs);letter-spacing:.1em;
260
+ color:var(--muted-foreground);font-family:var(--mono);white-space:nowrap}
261
+ #col1Rail{grid-area:side} #col2Rail{grid-area:read}
262
+ .body[data-col1="closed"]>.side,.body[data-col2="closed"]>.rail{display:none}
263
+ /* the resize grips belong to an expanded column; a 36px rail has nothing to resize */
264
+ .body[data-col1="closed"] #sideEdge,.body[data-col2="closed"] .railgrip{display:none}
228
265
  .body[data-side="right"]>.side{border-right:0;border-left:1px solid var(--border)}
229
- .body[data-reader="left"]>.rail{border-left:0;border-right:1px solid var(--border)}
230
- .body[data-reader="left"] .railgrip{left:auto;right:-3px}
266
+ /* the panel sits between the structure column and the terminal, so its own edge is the LEFT one */
267
+ .rail{border-left:0;border-right:1px solid var(--border)}
268
+ .railgrip{left:auto;right:-3px}
231
269
 
232
270
  /* ── left column: the workspace — repos over running agents. Both panes fold and the column
233
271
  resizes, which is the parity the rail always had and this one never did (R11). ── */
@@ -365,6 +403,10 @@ button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
365
403
 
366
404
  /* one line, always: at 13px the full label wraps inside a 262px sidebar and the dashed box grows
367
405
  into a two-line slab. It ellipsizes instead — the + and the first words carry the meaning. */
406
+ /* `display:flex` beats the UA's `[hidden]{display:none}`, so hiding this from JS silently did
407
+ nothing — the composer stayed on screen in every mode. Any rule that sets `display` on an
408
+ element the code also hides has to opt back into it explicitly. */
409
+ .newsess[hidden]{display:none}
368
410
  .newsess{display:flex;gap:8px;align-items:center;margin:10px 12px 4px;min-height:38px;padding:0 12px;
369
411
  border:1px dashed var(--border);border-radius:var(--radius-md);font-size:var(--text-xs);
370
412
  color:var(--muted-foreground);width:calc(100% - 24px);
@@ -496,6 +538,14 @@ details.hz .kv .src{font-family:var(--mono);font-size:var(--text-2xs);color:var(
496
538
  .term-col{display:flex;flex-direction:column;min-width:0;min-height:0;background:var(--term-bg)}
497
539
  /* Terminal GROUPS (R6): one by default, two side by side when the layout says split. Each group
498
540
  owns a tab strip and a host; the #needs strip and the scratchpad stay with the column. */
541
+ /* Third rule in this file to need an explicit [hidden] because it sets `display` — the UA's
542
+ `[hidden]{display:none}` loses to any author display declaration, so hiding this from JS was
543
+ silently a no-op and the viewer opened UNDER a full-height terminal box. */
544
+ /* When a viewer tab is active the groups box collapses to just its tab strips: the terminals are
545
+ covered, never unmounted, so scrollback and PTYs survive, and the strip that got you here stays
546
+ on screen. A viewer that hides the strip hides its own way back. */
547
+ .groups[data-viewer="on"]{flex:none}
548
+ .groups[data-viewer="on"] .termhost{display:none}
499
549
  .groups{flex:1;min-height:0;min-width:0;display:flex;flex-direction:row}
500
550
  .group{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;background:var(--term-bg)}
501
551
  .group[hidden]{display:none}
@@ -575,28 +625,24 @@ color:var(--muted-foreground);font-size:var(--text-sm);margin-right:4px}
575
625
  .gbtn.primary{border-color:var(--warning)} .needs.accept .gbtn.primary{border-color:var(--info)}
576
626
  .wpath{width:100%;font-size:var(--text-xs);color:var(--muted-foreground);margin-top:2px}
577
627
 
578
- /* ── right rail: the library over the document reader ── */
628
+ /* ── the detail-panel column. It held the library OVER a document reader; documents are tabs in
629
+ the terminal column now (workspace-viewer-column), so this is one pane. ── */
579
630
  .rail{border-left:1px solid var(--input);background:var(--sidebar);position:relative;
580
631
  display:flex;flex-direction:column;min-height:0;min-width:0}
581
632
  /* drag the rail's own edge — a document read at 340px is a column of ten words */
582
633
  .railgrip{position:absolute;left:-4px;top:0;bottom:0;width:8px;z-index:5;cursor:col-resize}
583
634
  .railgrip::after{content:"";position:absolute;inset:0 3px;border-radius:var(--radius-sm);background:transparent}
584
635
  .railgrip:hover::after,.railgrip:focus-visible::after{background:var(--brand)}
585
- .rail .lib-pane{flex:1 1 var(--libsplit);min-height:34px}
586
- .rail .doc-pane{flex:1 1 calc(100% - var(--libsplit));min-height:34px}
587
- .rail .doc-pane[hidden]{display:none}
636
+ .rail .lib-pane{flex:1 1 auto;min-height:34px}
588
637
  .rail .pane[data-fold="off"]{flex:0 0 auto}
589
- .railsplit{flex:none;height:7px;cursor:row-resize;background:var(--muted);
590
- border-block:1px solid var(--border);position:relative}
591
- .railsplit[hidden]{display:none}
592
- .railsplit::after{content:"";position:absolute;inset:2px 42% 2px 42%;border-radius:var(--radius-sm);background:var(--input)}
593
- .railsplit:hover::after,.railsplit:focus-visible::after{background:var(--brand)}
638
+
594
639
 
595
640
  /* library — two lenses over one file set: initiatives (how the PM thinks) / folders (how the
596
641
  repo is laid out). The initiative lens joins a unit of work's scattered artifacts. */
597
642
  /* shadcn Tabs: an inset --muted track, the selected lens lifted onto --background. Four lenses
598
643
  have to survive a narrow rail, so the group is allowed to SHRINK and scroll rather than push
599
644
  the refresh control off the end of the header — which is what a fixed-width group did. */
645
+ .lens[hidden],.libfind[hidden],.reading[hidden],.librefresh[hidden]{display:none}
600
646
  .lens{margin-left:auto;display:inline-flex;gap:2px;border-radius:var(--radius-md);
601
647
  background:var(--muted);padding:2px;min-width:0;overflow-x:auto;scrollbar-width:none}
602
648
  .lens::-webkit-scrollbar{display:none}
@@ -664,19 +710,23 @@ details.tree>summary:hover{color:var(--foreground)}
664
710
  .empty{padding:10px 16px;font-size:var(--text-xs);color:var(--muted-foreground);line-height:1.5}
665
711
  .empty b{color:var(--foreground)}
666
712
 
667
- /* ── the document reader (bottom of the rail) ── */
668
- .doc-pane .cap{padding-right:6px;gap:6px}
713
+ /* ── the viewer: documents, images and summoned surfaces, as tabs in the terminal column ── */
714
+ .viewpane{flex:1;display:flex;flex-direction:column;min-height:0;min-width:0;background:var(--background)}
715
+ .viewpane[hidden]{display:none}
716
+ .viewpane>.cap{flex:none}
717
+
718
+ .viewpane .cap{padding-right:6px;gap:6px}
669
719
  /* the crumb is a FILE PATH — mono earns its place. It yields space to the controls beside it
670
720
  rather than shouldering them off the end of the header. */
671
- .doc-pane .crumb{margin-left:auto;font-family:var(--mono);font-size:var(--text-2xs);
721
+ .viewpane .crumb{margin-left:auto;font-family:var(--mono);font-size:var(--text-2xs);
672
722
  color:var(--muted-foreground);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
673
723
  direction:rtl;text-align:right;max-width:50%;min-width:0;flex:0 1 auto}
674
724
  /* `ghost` icon button, matching the other cap controls instead of being the one outlined box */
675
- .doc-pane .x{flex:none;width:28px;height:28px;display:grid;place-items:center;border-radius:var(--radius-sm);
725
+ .viewpane .x{flex:none;width:28px;height:28px;display:grid;place-items:center;border-radius:var(--radius-sm);
676
726
  border:0;background:transparent;color:var(--muted-foreground);font-size:var(--text-sm);
677
727
  transition:background-color var(--duration-fast) var(--ease-emphasized),
678
728
  color var(--duration-fast) var(--ease-emphasized)}
679
- .doc-pane .x:hover{background:var(--destructive);color:var(--primary-foreground)}
729
+ .viewpane .x:hover{background:var(--destructive);color:var(--primary-foreground)}
680
730
  .doc-body{overflow-y:auto;overscroll-behavior:contain;flex:1;min-height:0;
681
731
  padding:18px 22px 64px;line-height:1.65;font-size:14px;color:var(--foreground)}
682
732
  .doc-body>*{max-width:72ch}
@@ -948,13 +998,11 @@ details.tree>summary:hover{color:var(--foreground)}
948
998
 
949
999
  /* Narrow: the rail floats over the terminal instead of squeezing it below a usable width. */
950
1000
  @media (max-width:1100px){
951
- /* applyLayout() drops the reader's track from the template at this width and sets data-float,
952
- so the grid here is sidebar + terminal and the reader floats over the terminal rather than
953
- squeezing it below a usable width. Docked BOTTOM it keeps its row and does not float. */
954
- .body[data-float="on"]>.rail{position:absolute;top:0;right:0;bottom:0;left:auto;
955
- width:min(420px,86vw);z-index:30;box-shadow:-18px 0 50px -30px var(--shadow)}
956
- .body[data-float="on"][data-reader="left"]>.rail{right:auto;left:0;
957
- box-shadow:18px 0 50px -30px var(--shadow)}/* Split is forced OFF below this width by applyLayout() — two terminal groups inside what is
1001
+ /* applyLayout() drops the panel's track from the template at this width and sets data-float, so
1002
+ the grid here is sidebar + terminal and the panel floats over the terminal rather than
1003
+ squeezing it below a usable width. */
1004
+ .body[data-float="on"]>.rail{position:absolute;top:0;bottom:0;left:0;right:auto;
1005
+ width:min(420px,86vw);z-index:30;box-shadow:18px 0 50px -30px var(--shadow)}/* Split is forced OFF below this width by applyLayout() — two terminal groups inside what is
958
1006
  left of a narrow window is two unusable terminals. The layout menu says so rather than
959
1007
  silently ignoring the choice (R9). */
960
1008
  .lay-narrow{display:block}
@@ -990,41 +1038,110 @@ details.tree>summary:hover{color:var(--foreground)}
990
1038
  .html-pane{min-height:0}
991
1039
  .html-frame{width:100%;min-height:60vh;border:1px solid var(--border);border-radius:var(--radius-md);
992
1040
  background:white} /* a standalone HTML doc assumes a light page; keyword, not hex (token-hygiene) */
993
- /* ── mobile (≤760px): one column at a time + a bottom switcher; the terminal is the priority pane.
1041
+ /* ── mobile (≤760px): ONE screen at a time — a back control at the top, a destination nav at the
1042
+ bottom, and exactly one of the three real column nodes on screen.
1043
+ The columns are never re-parented and never rebuilt; a screen chooses WHICH ONE is visible
1044
+ through `data-mscreen` on #bodyGrid — the same node and the same mechanism the retired
1045
+ `data-mview` used, renamed because the values now name a COLUMN (`side`/`term`/`rail`) rather
1046
+ than a tab. That is what makes a pushed screen, a pop and a destination switch survive without
1047
+ destroying the xterm instance or its PTY socket: the terminal is covered, never unmounted.
1048
+ `.mobtabs` is gone — rule and markup. A bottom destination nav with a leftover three-tab strip
1049
+ underneath it is two navigation systems at once, which is the exact condition the old partition
1050
+ check existed to prevent; keeping it alive as a shim would fail the thing it was protecting.
1051
+ Touch targets ≥44px; the control bar sends the keys a phone keyboard lacks straight to the PTY.
994
1052
  The 3-column desktop layout above is untouched at wide widths — everything here is inside the
995
- ≤760 query (or the .mobmenu, which is display:none until then). Touch targets ≥44px; the control
996
- bar sends the keys a phone keyboard lacks straight to the PTY. ── */
1053
+ ≤760 query, or is a `display:none` above it (.mobmenu, .mob). ── */
997
1054
  .mobmenu{display:none}
1055
+ /* Above the breakpoint the phone bar is ABSENT rather than merely invisible (R12): this removes
1056
+ its box, and app.js leaves the `hidden` attribute on so it is out of the accessibility tree too.
1057
+ Both, not either — a node that is only `hidden` still loses to any author `display` below. */
1058
+ .mob{display:none}
998
1059
  @media (max-width:760px){
999
- /* an extra row above the status strip carries the control bar + switcher */
1000
- .app{grid-template-rows:44px minmax(0,1fr) auto 28px}
1060
+ /* five rows now: top bar · phone bar · content · control bar + destination nav · status strip.
1061
+ `height:100dvh` is load-bearing rather than decorative here — when the soft keyboard opens the
1062
+ visual viewport shrinks and the grid shrinks with it, so the destination nav stays on screen
1063
+ instead of sitting underneath the keys. */
1064
+ .app{grid-template-rows:44px auto minmax(0,1fr) auto 28px;height:100dvh}
1065
+ /* Placed EXPLICITLY rather than left to auto-placement. #mobShell ships `hidden` and only app.js
1066
+ takes the attribute off, so at this width the shell may legitimately be out of flow for a tick
1067
+ — and under auto-placement that shifts .body up into the `auto` row and hands minmax(0,1fr) to
1068
+ the control bar, which collapses the terminal to nothing. Naming the rows makes the phone
1069
+ layout independent of how many of these five children happen to be in flow. */
1070
+ .app>.bar{grid-row:1}
1071
+ .app>.mob{grid-row:2}
1072
+ .app>.body{grid-row:3}
1073
+ .app>.mobmenu{grid-row:4}
1074
+ .app>.strip{grid-row:5}
1001
1075
  /* collapse to a single column; the switcher decides which pane is shown. These are DIRECT
1002
1076
  declarations and the layout writes custom properties, so the phone's arrangement wins here
1003
1077
  whatever layout the desktop is on — the two systems never fight (R9). */
1004
- .body{grid-template-columns:1fr;grid-template-rows:minmax(0,1fr);grid-template-areas:none;position:relative}
1078
+ /* minmax(0,1fr), not 1fr: a bare `1fr` is minmax(AUTO,1fr) and refuses to go below the pane's
1079
+ min-content width, which is what pushed the whole app sideways once the navigator gained a
1080
+ nesting level. The row track already had this; the column was the half that was missing. */
1081
+ .body{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr);grid-template-areas:none;position:relative}
1005
1082
  /* the named areas are gone here, so the panes must stop asking for them — a stale `grid-area`
1006
1083
  places them on implicit tracks and the pane spills over the switcher it is covering */
1007
1084
  .body>.side,.body>.term-col,.body>.rail,.body>.drop{grid-area:auto}
1085
+ /* THE column switch, and the whole of the exclusivity guarantee. Two of the three are
1086
+ `display:none` at any moment, so "exactly one column has a non-zero client rect" is a property
1087
+ of this stylesheet rather than something the reducer has to remember to enforce. A screen's
1088
+ `column` is derived from its `kind` — list/more → side · detail → rail · terminal/doc → term —
1089
+ so every screen the reducer can reach lands on one of these three values and no other.
1090
+ These are DIRECT declarations and the layout engine writes custom properties, so the phone's
1091
+ arrangement wins here whatever layout the desktop is on — the two systems never fight (R9). */
1008
1092
  .body>.side,.body>.term-col,.body>.rail{display:none}
1009
- .body[data-mview="sessions"]>.side{display:flex}
1010
- .body[data-mview="terminal"]>.term-col{display:flex}
1011
- .body[data-mview="library"]>.rail,
1012
- .body[data-reader="off"][data-mview="library"]>.rail{display:flex}
1093
+ .body[data-mscreen="side"]>.side{display:flex}
1094
+ .body[data-mscreen="term"]>.term-col{display:flex}
1095
+ .body[data-mscreen="rail"]>.rail{display:flex}
1013
1096
  /* the rail is a full-width pane here, not the floating overlay the ≤1100 rule makes it */
1014
1097
  .body[data-float="on"]>.rail,.rail{position:static;width:auto;box-shadow:none;z-index:auto;
1015
1098
  border-left:0;border-top:0}
1016
- .railgrip,.railsplit,.side>.edge{display:none} /* edge-drag is a pointer affordance; the switcher owns layout on touch */
1099
+ .railgrip,.side>.edge{display:none} /* edge-drag is a pointer affordance; the switcher owns layout on touch */
1017
1100
  .cap .grip,.lay-chip{display:none} /* and so is the whole layout control (R9) */
1018
1101
  .side>.pane{flex:1 1 0}
1019
1102
  /* one group on a phone, always — the split is a desktop arrangement */
1020
1103
  .groups{flex-direction:column}
1021
1104
  .group+.group{display:none}
1022
1105
 
1023
- /* the mobile menu: a terminal control bar (terminal view only) over the pane switcher */
1024
- /* min-width:0 lets this grid item shrink below its content min-size, so the control bar's
1025
- fixed-width buttons scroll inside it instead of widening the whole page */
1106
+ /* ── the phone's top bar: back · the screen's name · theme ──────────────────────────────────
1107
+ Safe-area insets at BOTH ends. The shipped rule padded only the bottom, which is half the
1108
+ problem: on a notched device a bar pinned to the top of the viewport sits UNDER the notch, so
1109
+ the back control becomes unpressable. `min-height` includes the inset (box-sizing is
1110
+ border-box everywhere here), so the row itself stays a full 44px below it rather than being
1111
+ squeezed into whatever the notch leaves. The horizontal insets matter in landscape, where the
1112
+ rounded corners eat the two ends of the bar. */
1113
+ .mob{display:block}
1114
+ .mob-bar{display:flex;align-items:center;gap:8px;
1115
+ min-height:44px;padding:0 10px; /* the plain fallback: a browser without env() drops the
1116
+ declaration below entirely, and a bar with no padding at
1117
+ all is worse than one that ignores the notch */
1118
+ min-height:calc(44px + env(safe-area-inset-top));
1119
+ padding:env(safe-area-inset-top) max(10px,env(safe-area-inset-right)) 0 max(10px,env(safe-area-inset-left));
1120
+ background:var(--glass);backdrop-filter:blur(8px);border-bottom:1px solid var(--border)}
1121
+ /* back is `hidden` at a destination root and pops exactly one screen otherwise. It is not
1122
+ disabled-and-inert at a root: a control that looks available and does nothing is the thing
1123
+ this workspace refuses to ship, so it is absent instead. */
1124
+ .mob-back{flex:none;min-width:44px;min-height:44px;margin-left:-6px;
1125
+ color:var(--muted-foreground);font-size:var(--text-lg);line-height:1}
1126
+ .mob-back:active{color:var(--foreground)}
1127
+ /* the title ELLIPSIZES rather than pushing the bar sideways — a session name or a document title
1128
+ is arbitrary length and the two controls beside it must stay where the thumb expects them. */
1129
+ .mob-title{flex:1;min-width:0;font-size:var(--text-base);font-weight:600;letter-spacing:-.01em;
1130
+ color:var(--foreground);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1131
+ /* `flex:none` on both trailing controls, so the ellipsizing title above yields to them rather
1132
+ than the other way round — that is what keeps the palette on screen at 390px. */
1133
+ .mob-pal,.mob-theme{flex:none;min-width:44px;min-height:44px;padding:0 8px;
1134
+ color:var(--muted-foreground);font-size:var(--text-xs)}
1135
+ .mob-pal:active,.mob-theme:active{color:var(--foreground)}
1136
+
1137
+ /* the mobile menu: a terminal control bar (terminal screen only) over the destination nav.
1138
+ min-width:0 lets this grid item shrink below its content min-size, so the control bar's
1139
+ fixed-width buttons scroll inside it instead of widening the whole page.
1140
+ The other end of the safe area lives here rather than on the nav, so the control bar gets the
1141
+ insets too — in landscape the rounded corners clip both. */
1026
1142
  .mobmenu{display:block;min-width:0;background:var(--muted);border-top:1px solid var(--border);
1027
- padding-bottom:env(safe-area-inset-bottom)}
1143
+ padding-bottom:env(safe-area-inset-bottom);
1144
+ padding-left:env(safe-area-inset-left);padding-right:env(safe-area-inset-right)}
1028
1145
  .ctrlbar{display:none;min-width:0;gap:5px;padding:6px 8px;overflow-x:auto;-webkit-overflow-scrolling:touch;
1029
1146
  border-bottom:1px solid var(--border)}
1030
1147
  .mobmenu[data-view="terminal"] .ctrlbar{display:flex}
@@ -1034,13 +1151,20 @@ details.tree>summary:hover{color:var(--foreground)}
1034
1151
  .ctrlbar button:active{background:var(--accent)}
1035
1152
  .ctrlbar button[aria-pressed="true"]{border-color:var(--brand);color:var(--brand);background:var(--brand-wash)}
1036
1153
  .ctrlbar .lbl{font-size:var(--text-xs);letter-spacing:.04em}
1037
- .mobtabs{display:flex}
1038
- .mobtabs button{flex:1;min-height:52px;display:flex;flex-direction:column;align-items:center;justify-content:center;
1039
- gap:2px;font-family:var(--mono);font-size:var(--text-xs);letter-spacing:.06em;color:var(--muted-foreground);
1040
- border-top:2px solid transparent;background:var(--muted)}
1041
- .mobtabs button+button{border-left:1px solid var(--border)}
1042
- .mobtabs button[aria-selected="true"]{color:var(--brand);border-top-color:var(--brand);background:var(--accent)}
1043
- @media (prefers-reduced-motion:reduce){.mobtabs button,.ctrlbar button{transition:none}}
1154
+ /* Five destinations. `flex:1 1 0` with `min-width:0` is what lets "scheduled" sit beside four
1155
+ others at 390px: the buttons share the width evenly and a label that still does not fit
1156
+ ellipsizes inside its own button rather than widening the row. No glyph child — the LABEL is
1157
+ the affordance, and a five-way icon row on a phone is a guessing game. */
1158
+ .mobnav{display:flex;min-width:0}
1159
+ .mobnav-b{flex:1 1 0;min-width:0;min-height:52px;padding:0 4px;background:var(--muted);
1160
+ border-top:2px solid transparent;color:var(--muted-foreground);font-size:var(--text-2xs);
1161
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1162
+ .mobnav-b+.mobnav-b{border-left:1px solid var(--border)}
1163
+ /* current is carried on THREE channels — the top bar (a shape), the weight, and the hue — so
1164
+ which destination you are on survives being read without colour. */
1165
+ .mobnav-b[aria-current="page"]{color:var(--brand);border-top-color:var(--brand);
1166
+ background:var(--accent);font-weight:600}
1167
+ @media (prefers-reduced-motion:reduce){.mobnav-b,.ctrlbar button{transition:none}}
1044
1168
 
1045
1169
  /* the header must fit a phone: drop the desktop "workspace" tag and let the status text ellipsize */
1046
1170
  .bar{gap:7px;padding:0 10px}
@@ -1051,6 +1175,18 @@ details.tree>summary:hover{color:var(--foreground)}
1051
1175
  .strip .right{display:none}
1052
1176
  }
1053
1177
 
1178
+ /* R8 — hover-only reveals. `opacity:0` plus a `:hover` rule leaves a touch user with a control
1179
+ that exists, is focusable, and can never be seen: there is no hover to trigger. A coarse pointer
1180
+ gets them at full opacity instead.
1181
+ Keyed on (hover:none) rather than on a width, because a tablet at 900px has no hover either —
1182
+ this is a property of the POINTER, and tying it to the phone breakpoint would leave exactly the
1183
+ devices in between broken. Both `.wsrow .forget` and `.treerow .forget` are listed: the first is
1184
+ the repo row's affordance and the second is the tree row's, and which of the two survives
1185
+ §4.3's deletion of renderWorkspaces is not decidable from this file. */
1186
+ @media (hover:none){
1187
+ .wsrow .forget,.treerow .forget,.sessrow .ren{opacity:1}
1188
+ }
1189
+
1054
1190
  /* ── console surfaces (D64 Track B): needs-you · radar · metrics over file-mode state ────────── */
1055
1191
  /* Honesty by construction: error is unmistakably RED (--bad), empty is CALM (--teal), the loading
1056
1192
  skeleton (.sf-skel, reused) is neither. A broken read can only ever draw .cs-error. */
@@ -1442,3 +1578,681 @@ line-height:1.35;cursor:pointer}
1442
1578
  /* `n/a for this lane` must never look like `missing` — that difference is the whole of R8 */
1443
1579
  .dz-na{font-size:var(--text-2xs);color:var(--muted-foreground);font-style:italic;line-height:1.5;
1444
1580
  padding:2px 0 4px}
1581
+
1582
+ /* ── the PM spine (workspace-pm-spine) ─────────────────────────────────────────────────────────
1583
+ The resident navigator: Objective → Key Result → Initiative, with working trees and sessions
1584
+ nested INSIDE an initiative as attributes of it. Indentation carries the containment, so the
1585
+ levels are distinguished by weight and colour rather than by boxes — a box per level at four
1586
+ levels deep is a wall. Every value here comes from the merged token layer; token-hygiene fails
1587
+ the build on a raw hex. */
1588
+ .sp-obj{margin-bottom:10px}
1589
+ .sp-o{display:flex;align-items:baseline;gap:8px;padding:7px 10px 5px;position:sticky;top:0;z-index:1;
1590
+ background:var(--sidebar);border-bottom:1px solid var(--border)}
1591
+ .sp-o .id{font-family:var(--mono);font-size:var(--text-2xs);font-weight:600;color:var(--foreground);flex:none}
1592
+ .sp-o .nm{font-size:var(--text-xs);color:var(--muted-foreground);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1593
+ .sp-o .ct{margin-left:auto;font-size:var(--text-2xs);color:var(--muted-foreground);background:var(--muted);
1594
+ border-radius:var(--radius-sm);padding:1px 6px;flex:none}
1595
+ /* health budgets are PEERS of objectives (D59), marked by hue, not by demotion */
1596
+ .sp-obj[data-health="1"] .sp-o .id{color:var(--info)}
1597
+ .sp-obj[data-warn="1"] .sp-o .id{color:var(--warning)}
1598
+ .sp-obj[data-plain="1"] .sp-o .id{color:var(--muted-foreground)}
1599
+
1600
+ .sp-kr{margin:0 0 2px}
1601
+ .sp-k{display:flex;align-items:baseline;gap:7px;padding:5px 10px 4px 18px}
1602
+ .sp-k .id{font-family:var(--mono);font-size:var(--text-2xs);color:var(--brand);flex:none}
1603
+ .sp-k .nm{font-size:var(--text-2xs);color:var(--muted-foreground);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1604
+ .sp-k .ct{margin-left:auto;font-size:var(--text-2xs);color:var(--muted-foreground);flex:none}
1605
+
1606
+ /* the initiative — the unit of record, and now the unit you select */
1607
+ .sp-init{margin:0 0 1px}
1608
+ .sp-i{display:block;width:100%;text-align:left;padding:5px 10px 5px 26px;border-left:2px solid transparent;
1609
+ transition:background-color var(--duration-fast) var(--ease-emphasized)}
1610
+ .sp-i:hover{background:var(--accent)}
1611
+ .sp-init[data-current="1"]>.sp-i{background:var(--accent);border-left-color:var(--brand)}
1612
+ .sp-i .r1{display:flex;align-items:baseline;gap:7px;min-width:0}
1613
+ .sp-i .nm{font-family:var(--mono);font-size:var(--text-xs);color:var(--foreground);
1614
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1615
+ .sp-i .lane{font-size:var(--text-2xs);color:var(--muted-foreground);flex:none}
1616
+ /* an initiative with no checkout is a NORMAL state — it reads quiet, never as a fault */
1617
+ .sp-i .noit{margin-left:auto;font-size:var(--text-2xs);color:var(--muted-foreground);flex:none;font-style:italic}
1618
+ .sp-i .onit{margin-left:auto;font-size:var(--text-2xs);color:var(--muted-foreground);flex:none}
1619
+ .sp-i .r2{display:flex;align-items:baseline;gap:8px;margin-top:2px;font-size:var(--text-2xs)}
1620
+ .sp-i .stg{color:var(--muted-foreground)}
1621
+ .sp-i .nx{color:var(--brand)}
1622
+ .sp-i .bl{color:var(--destructive)}
1623
+ .sp-i .drv,.sp-i .rsn{color:var(--muted-foreground);font-style:italic;overflow:hidden;
1624
+ text-overflow:ellipsis;white-space:nowrap}
1625
+ /* trees nested under their initiative: the demotion made visible */
1626
+ .sp-trees{padding-left:10px}
1627
+ .sp-trees:empty{display:none}
1628
+
1629
+ /* R5 — a FAILED spine read is unmistakably red and says why. It is never a green empty tree. */
1630
+ .sp-bad{margin:8px 10px;padding:10px 12px;border:1px solid var(--destructive);border-radius:var(--radius-md);
1631
+ background:var(--destructive-wash);display:flex;flex-direction:column;gap:5px}
1632
+ .sp-bad b{font-size:var(--text-xs);color:var(--destructive)}
1633
+ .sp-bad span{font-size:var(--text-2xs);color:var(--foreground);line-height:1.5}
1634
+ .sp-bad .why{color:var(--muted-foreground)}
1635
+
1636
+ /* ── the initiative's sessions, inside its own panel (workspace-pm-spine) ──────────────────────
1637
+ The work happening ON an initiative belongs beside its ladder, not only under the checkout that
1638
+ happens to carry the branch. Selecting one here is what puts it in the terminal column. */
1639
+ .dz-sess{margin:14px 0 4px}
1640
+ .dz-cap{display:flex;align-items:baseline;gap:7px;font-size:var(--text-xs);font-weight:500;
1641
+ color:var(--muted-foreground);margin-bottom:6px}
1642
+ .dz-cap .ct{font-size:var(--text-2xs);background:var(--muted);border-radius:var(--radius-sm);padding:1px 6px}
1643
+ .dz-s{display:flex;align-items:baseline;gap:8px;width:100%;text-align:left;padding:6px 9px;
1644
+ border-radius:var(--radius-sm);border-left:2px solid transparent;
1645
+ transition:background-color var(--duration-fast) var(--ease-emphasized)}
1646
+ .dz-s:hover{background:var(--accent)}
1647
+ .dz-s[aria-current="true"]{background:var(--accent);border-left-color:var(--brand)}
1648
+ .dz-s .g{flex:none;font-size:var(--text-2xs)}
1649
+ .dz-s[data-state="working"] .g{color:var(--success)}
1650
+ .dz-s[data-state="quiet"] .g{color:var(--warning)}
1651
+ .dz-s[data-state="needs"] .g{color:var(--brand)}
1652
+ .dz-s[data-state="blocked"] .g{color:var(--destructive)}
1653
+ .dz-s[data-state="exited"] .g,.dz-s[data-state="idle"] .g{color:var(--muted-foreground)}
1654
+ .dz-s .nm{font-size:var(--text-sm);color:var(--foreground);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1655
+ .dz-s .st{margin-left:auto;flex:none;font-size:var(--text-2xs);color:var(--muted-foreground);white-space:nowrap}
1656
+ /* an initiative with no tree or no sessions is a NORMAL state and reads as prose, not as an error */
1657
+ .dz-nosess{font-size:var(--text-xs);color:var(--muted-foreground);line-height:1.55;padding:2px 0 4px}
1658
+ .dz-nosess b{font-family:var(--mono);color:var(--foreground);font-weight:400}
1659
+ .dz-start{display:inline-flex;align-items:center;margin-top:6px;min-height:30px;padding:0 11px;
1660
+ border:1px dashed var(--border);border-radius:var(--radius-md);font-size:var(--text-xs);
1661
+ color:var(--muted-foreground);
1662
+ transition:color var(--duration-fast) var(--ease-emphasized),border-color var(--duration-fast) var(--ease-emphasized)}
1663
+ .dz-start:hover{color:var(--foreground);border-color:var(--ring)}
1664
+
1665
+ /* ── the destinations (workspace-pm-spine) ────────────────────────────────────────────────────
1666
+ ON TOP OF the initiative list they scope, not a separate nav column. Everything here is
1667
+ PRODUCT-SPECIFIC — knowledge and scheduled tasks belong to the repository in view — so the menu
1668
+ sits inside the product's own column, where its scope cannot be misread. */
1669
+ .spmenu{display:grid;grid-template-columns:repeat(4,1fr);gap:2px;padding:8px 10px 8px;
1670
+ border-bottom:1px solid var(--border)}
1671
+ /* min-height 44px is the touch target, and it is what makes the two-row grid square up. These
1672
+ are MODES, not links: one is always current, so the pressed state is `aria-pressed`, styled
1673
+ here rather than left to the browser's default (which is nothing on a custom button). */
1674
+ .spm{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;
1675
+ padding:7px 2px 6px;min-height:44px;
1676
+ border-radius:var(--radius-md);color:var(--muted-foreground);
1677
+ transition:background-color var(--duration-fast) var(--ease-emphasized),
1678
+ color var(--duration-fast) var(--ease-emphasized)}
1679
+ .spm:hover{background:var(--accent);color:var(--accent-foreground)}
1680
+ .spm[aria-pressed="true"]{background:var(--accent);color:var(--foreground)}
1681
+ .spm .g{font-size:var(--text-base);line-height:1}
1682
+ .spm .l{font-size:var(--text-tiny);letter-spacing:.01em;line-height:1}
1683
+
1684
+ /* ── column 1: the search box, and the lists each mode renders ─────────────────────────────── */
1685
+ .spfind-wrap{padding:10px 12px 6px}
1686
+ .spfind{display:flex;align-items:center;gap:8px;padding:7px 10px;min-height:34px;
1687
+ border:1px solid var(--input);border-radius:var(--radius-md);background:var(--background);
1688
+ transition:border-color var(--duration-fast) var(--ease-emphasized)}
1689
+ .spfind:focus-within{border-color:var(--ring)}
1690
+ .spfind .g{color:var(--muted-foreground);font-size:var(--text-xs);flex:none}
1691
+ .spfind input{flex:1;min-width:0;font-size:var(--text-body);background:none;border:0;outline:none;
1692
+ color:var(--foreground);font-family:inherit}
1693
+ .spfind input::placeholder{color:var(--muted-foreground)}
1694
+
1695
+ /* One row shape, reused by every non-product mode. They differ in what they say, not in how they
1696
+ are built — which is why a new mode does not need a new set of rules. */
1697
+ .md-note{padding:8px 14px 4px;font-size:var(--text-row);color:var(--muted-foreground);line-height:1.5}
1698
+ .md-cap{padding:7px 14px 4px;font-family:var(--mono);font-size:var(--text-tiny);letter-spacing:.12em;
1699
+ text-transform:uppercase;color:var(--muted-foreground);border-bottom:1px solid var(--border)}
1700
+ .md-row{display:block;width:100%;padding:8px 14px;min-height:44px;
1701
+ border-bottom:1px solid var(--border);border-left:2px solid transparent;
1702
+ transition:background-color var(--duration-fast) var(--ease-emphasized)}
1703
+ .md-row:hover{background:var(--accent)}
1704
+ .md-row[aria-current="true"]{background:var(--accent);border-left-color:var(--brand)}
1705
+ .md-row .r1{display:flex;gap:8px;align-items:baseline;min-width:0}
1706
+ .md-row .nm{font-family:var(--mono);font-size:var(--text-xs);color:var(--foreground);
1707
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1708
+ .md-row .rt{margin-left:auto;flex:none;font-size:var(--text-meta);color:var(--muted-foreground)}
1709
+ .md-row .d{display:block;font-size:var(--text-row);color:var(--muted-foreground);
1710
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1711
+ .md-row .chain{display:block;font-family:var(--mono);font-size:var(--text-tiny);color:var(--info);
1712
+ margin-top:2px;overflow-wrap:anywhere}
1713
+ .md-row .dot{flex:none;font-size:var(--text-tiny)}
1714
+ .md-row[data-live="on"] .dot{color:var(--success)}
1715
+ .md-row[data-live="off"] .dot{color:var(--muted-foreground)}
1716
+ .md-add{display:inline-flex;align-items:center;margin:10px 14px;min-height:30px;padding:0 12px;
1717
+ background:var(--secondary);border:1px solid var(--border);border-radius:var(--radius-md);
1718
+ font-size:var(--text-xs);color:var(--foreground);
1719
+ transition:border-color var(--duration-fast) var(--ease-emphasized)}
1720
+ .md-add:hover{border-color:var(--ring)}
1721
+ /* A mode PMOS keeps no file for says so in full, and never renders a plausible-looking sample.
1722
+ Faking this list would make the workspace lie about what the repository contains, which is the
1723
+ one thing the honest-state contract forbids outright. */
1724
+ .md-none{margin:12px 14px;padding:11px 13px;border:1px dashed var(--input);
1725
+ border-radius:var(--radius-md);font-size:var(--text-xs);line-height:1.6;color:var(--muted-foreground)}
1726
+ .md-none b{color:var(--warning);font-weight:500}
1727
+ .md-none code{font-family:var(--mono);font-size:var(--text-meta);color:var(--foreground)}
1728
+ /* "matches elsewhere" — search results that are not initiatives */
1729
+ .sp-hit{display:block;width:100%;padding:6px 12px;min-height:40px;
1730
+ transition:background-color var(--duration-fast) var(--ease-emphasized)}
1731
+ .sp-hit:hover{background:var(--accent)}
1732
+ .sp-hit .k{display:block;font-family:var(--mono);font-size:var(--text-tiny);color:var(--info)}
1733
+ .sp-hit .l{display:block;font-family:var(--mono);font-size:var(--text-2xs);
1734
+ color:var(--muted-foreground);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1735
+ .sp-nomatch{padding:10px 14px;font-size:var(--text-xs);color:var(--muted-foreground);font-style:italic}
1736
+
1737
+ /* ── column 2 · the detail panel ───────────────────────────────────────────────────────────────
1738
+ The whole point of this column is the ANSWER to "where is this, what is next, what blocks it".
1739
+ So the sizes here are deliberately not uniform: the next-action line is 14px — larger than the
1740
+ stage names, the step descriptions and everything below it — because it is the one line the PM
1741
+ is scanning for. Making every section equal weight is exactly the flaw this redesign fixes.
1742
+ Everything else on the panel is reference material and is typed as such. */
1743
+ .d2{display:flex;flex-direction:column;min-height:0}
1744
+ .d2-head{position:sticky;top:0;z-index:2;background:var(--sidebar);
1745
+ border-bottom:1px solid var(--border);padding:6px 8px 6px 14px;
1746
+ font-size:var(--text-xs);font-weight:500;color:var(--muted-foreground);
1747
+ display:flex;align-items:center;gap:8px}
1748
+ .d2-head .x{margin-left:auto;color:var(--muted-foreground);padding:3px 7px;font-size:var(--text-xs);
1749
+ transition:color var(--duration-fast) var(--ease-emphasized)}
1750
+ .d2-head .x:hover{color:var(--foreground)}
1751
+
1752
+ .d2-title{padding:13px 14px 11px;border-bottom:1px solid var(--border)}
1753
+ .d2-title .nm{font-family:var(--mono);font-size:var(--text-title);font-weight:600;
1754
+ letter-spacing:-.01em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1755
+ .d2-title .tag{font-size:var(--text-xs);color:var(--muted-foreground);margin-top:2px}
1756
+ .d2-pills{display:flex;gap:5px;flex-wrap:wrap;margin-top:8px}
1757
+ .d2-pill{font-family:var(--mono);font-size:var(--text-tiny);padding:2px 6px;
1758
+ background:var(--secondary);border:1px solid var(--border);border-radius:var(--radius-sm);
1759
+ color:var(--muted-foreground)}
1760
+ .d2-pill[data-t="lane"]{color:var(--purple)}
1761
+ .d2-pill[data-t="anchor"]{color:var(--muted-foreground)}
1762
+ .d2-pill[data-t="flow"]{color:var(--info)}
1763
+ .d2-pill[data-t="bad"]{color:var(--destructive)}
1764
+ .d2-pill[data-t="warn"]{color:var(--warning)}
1765
+
1766
+ /* R5 — a failed read is RED and says why. Never a tidy empty state standing in for a broken one. */
1767
+ .d2-bad{margin:12px;padding:11px 13px;border:1px solid var(--destructive);
1768
+ border-radius:var(--radius-md);background:var(--destructive-wash)}
1769
+ .d2-bad .lbl{font-family:var(--mono);font-size:var(--text-tiny);letter-spacing:.14em;
1770
+ text-transform:uppercase;color:var(--destructive);margin-bottom:4px;font-weight:500;display:block}
1771
+ .d2-bad .why{font-size:var(--text-body);line-height:1.55}
1772
+
1773
+ /* the standing answer, when there is no ladder to hang it on */
1774
+ .d2-ans{margin:12px 12px 0;padding:9px 11px;font-size:var(--text-sm);line-height:1.5;
1775
+ border-left:2px solid var(--muted-foreground);background:var(--muted)}
1776
+ .d2-ans[data-tone="warn"]{border-left-color:var(--warning);background:var(--warning-wash)}
1777
+ .d2-ans[data-tone="bad"]{border-left-color:var(--destructive);background:var(--destructive-wash)}
1778
+ .d2-ans[data-tone="info"]{border-left-color:var(--info);background:var(--info-wash)}
1779
+ .d2-ans[data-tone="ok"]{border-left-color:var(--success);background:var(--success-wash)}
1780
+ .d2-noladder{margin:10px 12px 0;padding:9px 11px;border:1px dashed var(--input);
1781
+ border-radius:var(--radius-md);font-size:var(--text-xs);line-height:1.5;color:var(--muted-foreground)}
1782
+
1783
+ /* the "next" tag — one vocabulary, whether it sits on a step or on the standing answer */
1784
+ .d2-next{font-family:var(--mono);font-size:var(--text-tiny);letter-spacing:.14em;
1785
+ text-transform:uppercase;margin-right:8px;font-weight:500;color:var(--muted-foreground)}
1786
+ .d2-ans[data-tone="warn"] .d2-next,.d2-step[data-tone="warn"] .d2-next{color:var(--warning)}
1787
+ .d2-ans[data-tone="bad"] .d2-next,.d2-step[data-tone="bad"] .d2-next{color:var(--destructive)}
1788
+ .d2-ans[data-tone="info"] .d2-next,.d2-step[data-tone="info"] .d2-next{color:var(--info)}
1789
+ .d2-ans[data-tone="ok"] .d2-next,.d2-step[data-tone="ok"] .d2-next{color:var(--success)}
1790
+
1791
+ /* the ladder */
1792
+ .d2-rail{padding:10px 0 6px}
1793
+ .d2-step{display:flex;gap:10px;padding:7px 14px 7px 12px;border-left:2px solid transparent}
1794
+ .d2-step[data-here="1"][data-tone="warn"]{border-left-color:var(--warning);background:var(--warning-wash)}
1795
+ .d2-step[data-here="1"][data-tone="bad"]{border-left-color:var(--destructive);background:var(--destructive-wash)}
1796
+ .d2-step[data-here="1"][data-tone="info"]{border-left-color:var(--info);background:var(--info-wash)}
1797
+ .d2-step[data-here="1"][data-tone="ok"]{border-left-color:var(--success);background:var(--success-wash)}
1798
+ .d2-step .dot{flex:none;width:14px;text-align:center;font-family:var(--mono);
1799
+ font-size:var(--text-2xs);line-height:1.5}
1800
+ .d2-step[data-s="done"] .dot{color:var(--success)}
1801
+ .d2-step[data-s="current"] .dot{color:var(--brand)}
1802
+ .d2-step[data-s="pending"] .dot,.d2-step[data-s="na"] .dot{color:var(--muted-foreground)}
1803
+ .d2-step[data-s="blocked"] .dot{color:var(--destructive)}
1804
+ .d2-step .txt{min-width:0;flex:1}
1805
+ .d2-step .r1{display:flex;gap:9px;align-items:baseline;min-width:0}
1806
+ .d2-step .k{flex:none;font-family:var(--mono);font-size:var(--text-tiny);letter-spacing:.12em;
1807
+ text-transform:uppercase;color:var(--muted-foreground)}
1808
+ .d2-step[data-here="1"] .k,.d2-step[data-s="current"] .k{color:var(--foreground)}
1809
+ .d2-step .t{font-size:var(--text-xs);color:var(--muted-foreground);
1810
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1811
+ /* THE line the panel exists for — larger than anything around it, and allowed to wrap */
1812
+ .d2-act{margin-top:6px;font-size:var(--text-base);line-height:1.45;text-wrap:pretty}
1813
+ .d2-cmd{display:inline-block;margin-top:5px;font-family:var(--mono);font-size:var(--text-meta);
1814
+ padding:4px 8px;min-height:26px;background:var(--background);border:1px solid var(--border);
1815
+ border-radius:var(--radius-md);color:var(--info);max-width:100%;
1816
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom;
1817
+ transition:border-color var(--duration-fast) var(--ease-emphasized)}
1818
+ .d2-cmd:hover{border-color:var(--ring)}
1819
+
1820
+ /* the reference rows below the ladder — a fixed label gutter so they read as one table */
1821
+ .d2-field{display:grid;grid-template-columns:76px minmax(0,1fr);gap:10px;padding:8px 14px;
1822
+ border-bottom:1px solid var(--border)}
1823
+ .d2-field:last-of-type{border-bottom:0}
1824
+ .d2-field>.lbl{font-family:var(--mono);font-size:var(--text-label);letter-spacing:.12em;
1825
+ text-transform:uppercase;color:var(--muted-foreground);padding-top:2px}
1826
+ .d2-field>.val{display:flex;flex-direction:column;gap:2px;min-width:0}
1827
+ .d2-evals{font-size:var(--text-xs);line-height:1.5;text-align:left;color:var(--warning)}
1828
+ .d2-evals[data-v="pass"]{color:var(--success)}
1829
+ .d2-evals[data-v="fail"]{color:var(--destructive)}
1830
+ .d2-evals:hover{text-decoration:underline}
1831
+ .d2-sess{display:flex;gap:8px;align-items:center;padding:5px 6px;margin:0 -6px;min-height:34px;
1832
+ border-left:2px solid transparent;
1833
+ transition:background-color var(--duration-fast) var(--ease-emphasized)}
1834
+ .d2-sess:hover{background:var(--accent)}
1835
+ .d2-sess[aria-current="true"]{border-left-color:var(--brand)}
1836
+ .d2-sess .g{flex:none;font-size:var(--text-tiny)}
1837
+ .d2-sess[data-state="working"] .g,.d2-sess[data-state="working"] .st{color:var(--success)}
1838
+ .d2-sess[data-state="quiet"] .g,.d2-sess[data-state="quiet"] .st{color:var(--warning)}
1839
+ .d2-sess[data-state="needs"] .g,.d2-sess[data-state="needs"] .st{color:var(--brand)}
1840
+ .d2-sess[data-state="blocked"] .g,.d2-sess[data-state="blocked"] .st{color:var(--destructive)}
1841
+ .d2-sess[data-state="exited"] .g,.d2-sess[data-state="exited"] .st{color:var(--muted-foreground)}
1842
+ .d2-sess .nm{font-size:var(--text-body);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1843
+ .d2-sess .st{margin-left:auto;flex:none;font-family:var(--mono);font-size:var(--text-tiny)}
1844
+ .d2-art{display:flex;gap:8px;align-items:center;padding:4px 6px;margin:0 -6px;min-width:0;
1845
+ min-height:30px;transition:background-color var(--duration-fast) var(--ease-emphasized)}
1846
+ .d2-art:hover{background:var(--accent)}
1847
+ .d2-art .role{flex:none;width:52px;font-family:var(--mono);font-size:var(--text-tiny);
1848
+ letter-spacing:.1em;color:var(--info)}
1849
+ .d2-art .path{font-family:var(--mono);font-size:var(--text-tiny);color:var(--muted-foreground);
1850
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1851
+ .d2-quiet{font-size:var(--text-xs);color:var(--muted-foreground);line-height:1.5}
1852
+ .d2-quiet.italic{font-size:var(--text-row);font-style:italic}
1853
+ .d2-btn{align-self:flex-start;margin-top:4px;display:inline-flex;align-items:center;
1854
+ min-height:30px;padding:0 12px;background:var(--secondary);border:1px solid var(--border);
1855
+ border-radius:var(--radius-md);font-size:var(--text-xs);color:var(--foreground);
1856
+ transition:border-color var(--duration-fast) var(--ease-emphasized)}
1857
+ .d2-btn:hover{border-color:var(--ring)}
1858
+ .d2-btn[data-kind="primary"]{background:var(--primary);color:var(--primary-foreground);
1859
+ border-color:transparent;font-weight:500}
1860
+ .d2-btn[data-kind="danger"]{background:none;border-color:var(--destructive);color:var(--destructive)}
1861
+ .d2-btn[data-kind="danger"]:hover{background:var(--destructive-wash)}
1862
+ .d2-acts{display:flex;gap:6px;flex-wrap:wrap;padding:12px 14px;border-bottom:1px solid var(--border)}
1863
+ .d2-acts .d2-btn{margin-top:0}
1864
+ .d2-body{padding:12px 14px;border-bottom:1px solid var(--border);font-size:var(--text-sm);line-height:1.55}
1865
+ .d2-sub{padding:10px 14px 4px;font-family:var(--mono);font-size:var(--text-label);
1866
+ letter-spacing:.12em;text-transform:uppercase;color:var(--muted-foreground)}
1867
+ .d2-list{display:flex;flex-direction:column;gap:2px;padding:4px 14px 10px}
1868
+ .d2-item{display:flex;gap:9px;align-items:center;min-height:34px;padding:0 9px;
1869
+ background:var(--background);border:1px solid var(--border);border-radius:var(--radius-md);
1870
+ transition:border-color var(--duration-fast) var(--ease-emphasized)}
1871
+ .d2-item:hover{border-color:var(--ring)}
1872
+ .d2-item .n{flex:none;font-family:var(--mono);font-size:var(--text-tiny);color:var(--muted-foreground)}
1873
+ .d2-item .k{font-family:var(--mono);font-size:var(--text-row);letter-spacing:.06em}
1874
+ .d2-item .kind{flex:none;width:86px;font-family:var(--mono);font-size:var(--text-label);
1875
+ letter-spacing:.1em;text-transform:uppercase;color:var(--muted-foreground)}
1876
+ .d2-item .id{font-family:var(--mono);font-size:var(--text-row);color:var(--brand)}
1877
+ .d2-item .d{font-size:var(--text-2xs);color:var(--muted-foreground);
1878
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1879
+ /* the panel always ends by naming where it read from — provenance, not decoration */
1880
+ .d2-foot{margin-top:auto;padding:8px 14px;border-top:1px solid var(--border);
1881
+ font-family:var(--mono);font-size:var(--text-tiny);color:var(--muted-foreground);
1882
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1883
+ .d2-empty{flex:1;display:grid;place-items:center;color:var(--muted-foreground);
1884
+ font-size:var(--text-body);padding:20px;text-align:center}
1885
+
1886
+ /* evals on the initiative panel — the Review-Gate record, and the honest absence of one */
1887
+ .dz-ev{margin:14px 0 4px}
1888
+ .dz-e{display:flex;align-items:baseline;gap:9px;padding:5px 0}
1889
+ .dz-e .v{font-size:var(--text-2xs);font-weight:500;border-radius:var(--radius-sm);padding:1px 7px;
1890
+ background:var(--muted);color:var(--muted-foreground)}
1891
+ .dz-e .v[data-v="pass"]{color:var(--success)}
1892
+ .dz-e .v[data-v="fail"]{color:var(--destructive)}
1893
+ .dz-e .sc,.dz-e .co{font-size:var(--text-2xs);color:var(--muted-foreground)}
1894
+ /* a rubric never run is NOT a pass — the sentence says so rather than leaving a green blank */
1895
+ .dz-noev{font-size:var(--text-xs);color:var(--muted-foreground);line-height:1.55}
1896
+ .dz-noev b{color:var(--warning);font-weight:500}
1897
+
1898
+ /* ═════════════════════════════════════════════════════════════════════════════════════════════
1899
+ BUNDLE 2 — focus · product switcher · repo ingest · profiles · handoff links
1900
+ Five feature vocabularies over the four this file already had (.sp-* .md-* .d2-* .cs-*). Each
1901
+ gets one short prefix and otherwise reuses what exists; none of them is a fifth GENERAL
1902
+ vocabulary. Every value goes through the D38 token layer — a literal hex, radius or font size
1903
+ here would be a second source of truth for something the tokens already own.
1904
+ The single rule the whole section is written around: four states, four renderings, never
1905
+ collapsed. A failed read is RED and carries the reader's own words; a successful read of nothing
1906
+ is calm; an absent MECHANISM is stated as an absence, in different words from "empty"; a read
1907
+ still in flight is its own third thing. Where two of those share a style below, that is a bug.
1908
+ ═════════════════════════════════════════════════════════════════════════════════════════════ */
1909
+
1910
+ /* ── the [hidden] companions ──────────────────────────────────────────────────────────────────
1911
+ The UA's `[hidden]{display:none}` loses to ANY author `display` declaration, so a node the code
1912
+ hides stays on screen. This exact bug has shipped three times in this file — its scars are the
1913
+ rules at .colrail, .newsess, .miniform, .group and .cap .mini, each of which was written after
1914
+ the fact. So: every rule below that sets `display` has its companion here, up front, where the
1915
+ set can be read in one go instead of hunted for.
1916
+ The first ten are the set the wiring contract enumerates verbatim. They are at (0,1,1) and the
1917
+ rules they guard are at (0,1,0), so they win whatever the source order — including against the
1918
+ ≤760px overrides at the bottom of this section. */
1919
+ .focusbtn[hidden]{display:none}
1920
+ .fc[hidden]{display:none}
1921
+ .psw[hidden]{display:none}
1922
+ .prof[hidden]{display:none}
1923
+ .profline[hidden]{display:none}
1924
+ .ing-src[hidden]{display:none}
1925
+ .ing-opt[hidden]{display:none}
1926
+ .mob[hidden]{display:none}
1927
+ .mob-back[hidden]{display:none}
1928
+ .d2-links[hidden]{display:none}
1929
+ /* and every other display-setting rule in this section, on the same principle */
1930
+ .fc-body[hidden],.fc-item[hidden],.fc-obj[hidden],.fc-run[hidden],.fc-del[hidden],
1931
+ .fc-budget[hidden],.psw-list[hidden],.psw-add[hidden],.psw-b[hidden],.ing-log[hidden],
1932
+ .ing-row[hidden],.ing-item[hidden],.ing-anchor[hidden],.ing-ev[hidden],.prof-chip[hidden],
1933
+ .d2-link[hidden],.d2-links a[hidden],.d2-notconn[hidden],.d2-inert[hidden],
1934
+ .mob-bar[hidden],.mobnav[hidden]{display:none}
1935
+
1936
+ /* ── focus (workspace-focus) ──────────────────────────────────────────────────────────────────
1937
+ The one surface in column 1 that is NOT scoped by the product switcher — it joins every
1938
+ registered repository — which is why it sits above it. Its whole value is that the list is
1939
+ short, so the states below have to stay distinguishable at a glance:
1940
+ failed read → .cs-error (the existing one; RED, the reader's own message)
1941
+ read of nothing → .fc-none calm, solid, the info hue
1942
+ absent mechanism → .fc-notconn dashed, no hue, different words
1943
+ still deriving → .fc-deriving its own hue and a shimmer, neither of the above
1944
+ .fc-none and .fc-notconn deliberately share NOTHING. An absence rendered as an empty list is
1945
+ the watermelon this bundle exists to prevent. */
1946
+ .focusbtn{display:flex;align-items:center;gap:8px;width:calc(100% - 24px);margin:10px 12px 2px;
1947
+ min-height:30px;padding:6px 10px;border:1px solid var(--border);border-radius:var(--radius-md);
1948
+ background:var(--card);color:var(--foreground);font-size:var(--text-xs);text-align:left;
1949
+ transition:background-color var(--duration-fast) var(--ease-emphasized),
1950
+ border-color var(--duration-fast) var(--ease-emphasized)}
1951
+ .focusbtn:hover{background:var(--accent);border-color:var(--ring)}
1952
+ .focusbtn[aria-expanded="true"]{background:var(--accent);border-color:var(--brand);color:var(--brand)}
1953
+ .focusbtn .g{flex:none;font-size:var(--text-xs);color:var(--brand)}
1954
+ .focusbtn .l{flex:none}
1955
+ /* The badge prints badge.text — a SENTENCE ("4 need you · 1 product still deriving", "needs
1956
+ unreadable"), never a count. So it is styled to hold prose: it takes the remaining width and
1957
+ ellipsizes rather than widening the sidebar, and it is deliberately NOT a pill. A digit in a
1958
+ pill invites reading it as an exact tally of a world that may contain a read which failed. */
1959
+ .focusbtn .ct{margin-left:auto;min-width:0;font-size:var(--text-2xs);color:var(--warning);
1960
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1961
+ .focusbtn .ct[data-bad="1"]{color:var(--destructive)}
1962
+
1963
+ .fc{margin:2px 0 6px;background:var(--card);
1964
+ border-top:1px solid var(--border);border-bottom:1px solid var(--border)}
1965
+ /* its own scroll region, capped: the panel is a second reading of the same repositories and #tree
1966
+ below it is the first, so the panel must never push the structure it was derived from off the
1967
+ bottom of the column. */
1968
+ .fc-body{display:flex;flex-direction:column;max-height:min(58vh,540px);
1969
+ overflow-y:auto;overscroll-behavior:contain}
1970
+ .fc-sec{padding:2px 0 8px}
1971
+ .fc-sec+.fc-sec{border-top:1px solid var(--border)}
1972
+ /* same grammar as .md-cap — the sections of this panel are peers of the mode lists beside them */
1973
+ .fc-cap{padding:7px 14px 4px;font-family:var(--mono);font-size:var(--text-tiny);letter-spacing:.12em;
1974
+ text-transform:uppercase;color:var(--muted-foreground)}
1975
+ /* R3's contract line, first in the panel. It is prose, not a caption: the queue is only worth
1976
+ trusting if the rule that filled it is on the same screen as the rows it excluded. */
1977
+ .fc-intro{padding:8px 14px;font-size:var(--text-row);line-height:1.55;color:var(--muted-foreground)}
1978
+
1979
+ /* a queue row: rank + slug + product on line one, the why on line two, the read on line three.
1980
+ `margin-left:auto` on the product tag and `flex-basis:100%` on the two lines below it are what
1981
+ produce that shape from a flat run of siblings, with no wrapper the renderer has to remember. */
1982
+ .fc-item{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:8px;row-gap:3px;
1983
+ width:100%;min-height:34px;padding:8px 14px 8px 12px;text-align:left;
1984
+ border-top:1px solid var(--border);border-left:2px solid transparent;
1985
+ transition:background-color var(--duration-fast) var(--ease-emphasized)}
1986
+ .fc-item:hover{background:var(--accent)}
1987
+ /* the tier tints the spine and is never the only carrier: the row prints its own `why` and sits
1988
+ under a labelled section. Tier 1 is an integrity failure, so it takes the alarm hue. */
1989
+ .fc-item[data-tier="1"]{border-left-color:var(--destructive)}
1990
+ .fc-item[data-tier="2"]{border-left-color:var(--brand)}
1991
+ .fc-item[data-tier="2b"]{border-left-color:var(--warning)}
1992
+ .fc-item[data-tier="4"]{border-left-color:var(--muted-foreground)}
1993
+ .fc-rank{flex:none;width:16px;font-family:var(--mono);font-size:var(--text-meta);
1994
+ color:var(--muted-foreground)}
1995
+ .fc-nm{min-width:0;font-family:var(--mono);font-size:var(--text-xs);color:var(--foreground);
1996
+ overflow-wrap:anywhere}
1997
+ .fc-prod{margin-left:auto;flex:none;font-size:var(--text-2xs);color:var(--muted-foreground);
1998
+ border:1px solid var(--border);border-radius:var(--radius-sm);padding:1px 6px}
1999
+ .fc-why{flex-basis:100%;padding-left:24px;font-size:var(--text-row);line-height:1.5;
2000
+ color:var(--muted-foreground)}
2001
+ /* NOT decoration, and never dimmed below the `why` above it. This line names the files the row was
2002
+ derived from, which is the only thing that lets the PM check the queue instead of believing it —
2003
+ so it keeps a readable size and the mono face of the paths and timestamps it prints. */
2004
+ .fc-read{flex-basis:100%;padding-left:24px;font-family:var(--mono);font-size:var(--text-tiny);
2005
+ line-height:1.55;color:var(--muted-foreground);overflow-wrap:anywhere}
2006
+
2007
+ /* objectives. Four states, four renderings — and `none` (this product declares no objectives: a
2008
+ read that succeeded) is NOT `not-read` (PMOS never got to it). Collapsing those two is how a
2009
+ product with nothing to show and a product nobody looked at become the same row. */
2010
+ .fc-obj{display:flex;align-items:baseline;gap:8px;width:100%;min-height:30px;padding:5px 14px;
2011
+ text-align:left;font-size:var(--text-xs);line-height:1.5;overflow-wrap:anywhere}
2012
+ .fc-obj[data-state="objective"]{color:var(--foreground)}
2013
+ .fc-obj[data-state="none"]{color:var(--muted-foreground);font-style:italic}
2014
+ .fc-obj[data-state="not-read"]{color:var(--muted-foreground);
2015
+ border-left:2px dashed var(--input);padding-left:12px}
2016
+ .fc-obj[data-state="error"]{color:var(--destructive);background:var(--destructive-wash);
2017
+ border-left:2px solid var(--destructive);padding-left:12px}
2018
+
2019
+ /* an ABSENCE, said as one: PMOS holds no credential, runs no MCP client and has no sync path, so
2020
+ there is nothing here that could be empty. A dashed rule, no hue, no dot, and the module's own
2021
+ words. It must never be mistaken for .fc-none, which is a read that ran and found nothing. */
2022
+ .fc-notconn{margin:6px 14px 4px;padding:9px 11px;border:1px dashed var(--border);
2023
+ border-radius:var(--radius-md);font-size:var(--text-xs);line-height:1.55;
2024
+ color:var(--muted-foreground)}
2025
+ .fc-notconn::before{content:"— "}
2026
+ .fc-notconn b{color:var(--foreground);font-weight:500}
2027
+ /* the CALM zero: the reads worked and nothing is waiting on you. Solid hairline, the info hue this
2028
+ file already spends on a successful empty, and no dashes anywhere. */
2029
+ .fc-none{margin:6px 14px 4px;padding:9px 11px;border:1px solid var(--info);
2030
+ border-radius:var(--radius-md);background:var(--info-wash);
2031
+ font-size:var(--text-xs);line-height:1.55;color:var(--foreground)}
2032
+ .fc-none::before{content:"◦ ";color:var(--info)}
2033
+ /* the THIRD state — a board still being derived. Neither empty nor broken, so it takes neither of
2034
+ their treatments: its own hue, a heavier left edge, and a shimmer that says work is in flight
2035
+ rather than a spinner that says the app is busy. */
2036
+ .fc-deriving{margin:6px 14px 4px;padding:9px 11px;border:1px solid var(--purple);
2037
+ border-left-width:3px;border-radius:var(--radius-md);
2038
+ font-size:var(--text-xs);line-height:1.55;color:var(--foreground);
2039
+ background:linear-gradient(90deg,transparent,color-mix(in oklab,var(--purple) 12%,transparent),transparent);
2040
+ background-size:220% 100%;animation:sfsh 1.8s linear infinite}
2041
+ .fc-deriving::before{content:"◌ ";color:var(--purple)}
2042
+ @media (prefers-reduced-motion:reduce){.fc-deriving{animation:none}}
2043
+
2044
+ /* "12 of 105 scanned · 93 outside the board's in-flight cap" — the sentence that stops a short
2045
+ queue being read as a complete one. */
2046
+ .fc-cov{padding:3px 14px;font-family:var(--mono);font-size:var(--text-tiny);line-height:1.6;
2047
+ color:var(--muted-foreground);overflow-wrap:anywhere}
2048
+ .fc-run{display:flex;align-items:baseline;gap:8px;width:100%;min-height:30px;padding:5px 14px;
2049
+ text-align:left;font-size:var(--text-xs);color:var(--foreground);
2050
+ transition:background-color var(--duration-fast) var(--ease-emphasized)}
2051
+ .fc-run:hover{background:var(--accent)}
2052
+ /* Emitted ONLY for rows the module resolved a skill for. Where starts_state is 'not-resolved' the
2053
+ renderer prints the note and no button at all — an offer PMOS cannot honour is worse than no
2054
+ offer, so there is deliberately no disabled variant of this to reach for. */
2055
+ .fc-del{display:inline-flex;align-items:center;gap:6px;min-height:30px;margin:4px 14px 2px;
2056
+ padding:0 11px;border:1px solid var(--brand);border-radius:var(--radius-md);
2057
+ background:var(--brand-wash);color:var(--brand);font-size:var(--text-xs);
2058
+ transition:background-color var(--duration-fast) var(--ease-emphasized)}
2059
+ .fc-del:hover{background:var(--accent)}
2060
+ .fc-budget{display:flex;align-items:baseline;gap:8px;padding:4px 14px;
2061
+ font-size:var(--text-xs);line-height:1.5;color:var(--foreground)}
2062
+ .fc-budget .n{margin-left:auto;flex:none;font-family:var(--mono);font-size:var(--text-tiny);
2063
+ color:var(--muted-foreground)}
2064
+ /* a cap the registry did not yield is ABSENT, not zero — it is drawn as unavailable, the way
2065
+ details.hz's `.na` already is, and never as a plausible number. */
2066
+ .fc-budget .na{margin-left:auto;flex:none;font-size:var(--text-tiny);font-style:italic;
2067
+ color:var(--muted-foreground)}
2068
+
2069
+ /* ── product switcher (workspace-product-switcher) ────────────────────────────────────────────
2070
+ One button per PRODUCT (a distinct repo_key), never per working tree. The buttons WRAP, and
2071
+ that is the whole mechanism: a horizontally scrolling strip hides products off-screen with
2072
+ nothing on screen saying they exist, which is the tidy-row-of-buttons failure this feature is
2073
+ named after. */
2074
+ .psw{display:flex;align-items:flex-start;gap:6px;padding:6px 12px 4px}
2075
+ .psw-list{display:flex;flex-wrap:wrap;gap:4px;flex:1;min-width:0}
2076
+ .psw-add{flex:none;display:grid;place-items:center;min-width:30px;min-height:30px;
2077
+ border:1px dashed var(--border);border-radius:var(--radius-md);color:var(--muted-foreground);
2078
+ font-size:var(--text-sm);
2079
+ transition:color var(--duration-fast) var(--ease-emphasized),
2080
+ border-color var(--duration-fast) var(--ease-emphasized)}
2081
+ .psw-add:hover{color:var(--foreground);border-color:var(--ring)}
2082
+ .psw-add[aria-expanded="true"]{border-style:solid;border-color:var(--brand);color:var(--brand)}
2083
+ .psw-b{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:6px;row-gap:1px;
2084
+ max-width:100%;min-width:0;min-height:30px;padding:5px 9px;text-align:left;
2085
+ border:1px solid var(--border);border-radius:var(--radius-md);background:var(--card);
2086
+ color:var(--muted-foreground);
2087
+ transition:background-color var(--duration-fast) var(--ease-emphasized),
2088
+ border-color var(--duration-fast) var(--ease-emphasized)}
2089
+ .psw-b:hover{background:var(--accent);border-color:var(--ring)}
2090
+ .psw-b[aria-current="true"]{background:var(--accent);border-color:var(--brand);color:var(--foreground)}
2091
+ /* R2 — the selected product carries BOTH the hue and the ▸ mark. `visibility`, not `display`, so
2092
+ every button keeps the same width and the row does not re-wrap as you switch between them. */
2093
+ .psw-mk{flex:none;font-size:var(--text-2xs);color:var(--brand);visibility:hidden}
2094
+ .psw-b[aria-current="true"] .psw-mk{visibility:visible}
2095
+ /* NEVER `text-overflow:ellipsis`, `white-space:nowrap` or a `max-width` here. The browser suite
2096
+ asserts scrollWidth <= clientWidth on every label at the narrowest sidebar, and a product name
2097
+ truncated to "workspace-…" is a product you cannot tell from its sibling. It wraps instead. */
2098
+ .psw-nm{min-width:0;font-family:var(--mono);font-size:var(--text-xs);color:var(--foreground);
2099
+ overflow-wrap:anywhere}
2100
+ .psw-b[aria-current="true"] .psw-nm{font-weight:600}
2101
+ /* EXACTLY four states and no fifth. All four are declared even where two look alike, because the
2102
+ set is the contract: `reading…` is not `no initiative records`, and neither of them is `0`.
2103
+ `error` is the only one drawn as a fault — and the button stays clickable under it: an
2104
+ unreadable board is not an unreachable product. */
2105
+ .psw-ct{flex:none;font-size:var(--text-2xs);color:var(--muted-foreground)}
2106
+ .psw-ct[data-state="reading"]{color:var(--muted-foreground);font-style:italic}
2107
+ .psw-ct[data-state="empty"]{color:var(--muted-foreground)}
2108
+ .psw-ct[data-state="count"]{color:var(--muted-foreground)}
2109
+ .psw-ct[data-state="error"]{color:var(--destructive);font-weight:500}
2110
+ .psw-ct[data-state="error"]::before{content:"! ";font-family:var(--mono)}
2111
+ .psw-live{flex:none;font-size:var(--text-2xs);color:var(--success)}
2112
+
2113
+ /* ── repo ingest (workspace-repo-ingest) ──────────────────────────────────────────────────────
2114
+ The source choice states its consequence BEFORE the click — a folder is REGISTERED where it
2115
+ lies, a GitHub repo is cloned, scanned and PROPOSED — so both notes sit on screen at once, one
2116
+ under each option. `.ing-src` sets no `display` of its own (it wears `.seg`), but it still needs
2117
+ its `[hidden]` companion above, because `.seg` is what sets display:flex on it. */
2118
+ .ing-src{margin-bottom:2px}
2119
+ .ing-opt{display:flex;flex-direction:column;gap:8px}
2120
+ .ing-note{font-size:var(--text-xs);line-height:1.55;color:var(--muted-foreground)}
2121
+ /* the scan's own log, printed as it came. `error` is RED and carries the reader's message;
2122
+ `warn` is amber; `info` is the ordinary case and is not tinted at all — tinting everything is
2123
+ how nothing stands out. Each level also carries a MARK, so the level survives without colour. */
2124
+ .ing-log{display:flex;flex-direction:column;margin:8px 0 4px;overflow:hidden;
2125
+ border:1px solid var(--border);border-radius:var(--radius-md);background:var(--card)}
2126
+ .ing-row{display:flex;gap:8px;align-items:baseline;padding:5px 10px;
2127
+ font-family:var(--mono);font-size:var(--text-tiny);line-height:1.55;
2128
+ color:var(--muted-foreground);overflow-wrap:anywhere}
2129
+ .ing-row+.ing-row{border-top:1px solid var(--border)}
2130
+ .ing-row::before{flex:none;width:9px;text-align:center}
2131
+ .ing-row[data-level="info"]::before{content:"·"}
2132
+ .ing-row[data-level="warn"]::before{content:"!"}
2133
+ .ing-row[data-level="error"]::before{content:"✕"}
2134
+ .ing-row[data-level="warn"]{color:var(--warning)}
2135
+ .ing-row[data-level="error"]{color:var(--destructive);background:var(--destructive-wash)}
2136
+ /* one proposed record, confirmed or dismissed ON ITS OWN. There is no `.ing-confirm-all` and there
2137
+ must never be one: an anchor written across a dozen records nobody read is a dozen pieces of
2138
+ work anchored by nobody, every gate green. The tedium IS the gate. */
2139
+ .ing-item{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:8px;row-gap:4px;
2140
+ padding:9px 14px;border-top:1px solid var(--border)}
2141
+ .ing-item .pth{flex-basis:100%;font-family:var(--mono);font-size:var(--text-tiny);
2142
+ color:var(--muted-foreground);overflow-wrap:anywhere}
2143
+ /* a MARK and a WORD, never a hue on its own — the accessible name is written by the renderer.
2144
+ `guessed` is dashed and amber because it is a claim PMOS made up, and it must not look like the
2145
+ `declared` value beside it, which is a claim the repository made. */
2146
+ .ing-anchor{display:inline-flex;align-items:baseline;gap:5px;flex:none;padding:1px 6px;
2147
+ font-family:var(--mono);font-size:var(--text-tiny);color:var(--muted-foreground);
2148
+ border:1px solid var(--border);border-radius:var(--radius-sm)}
2149
+ .ing-anchor[data-a="declared"]{color:var(--foreground)}
2150
+ .ing-anchor[data-a="guessed"]{color:var(--warning);border-color:var(--warning);border-style:dashed}
2151
+ .ing-anchor[data-a="none"]{border-style:dashed}
2152
+ /* the same shape as .fc-read, for the same reason: this is the evidence the guess rests on, and a
2153
+ guess whose evidence you cannot see is indistinguishable from a fact. */
2154
+ .ing-ev{display:block;flex-basis:100%;font-family:var(--mono);font-size:var(--text-tiny);
2155
+ line-height:1.55;color:var(--muted-foreground);overflow-wrap:anywhere}
2156
+ .ing-bad{margin:8px 0;padding:9px 11px;border:1px solid var(--destructive);
2157
+ border-radius:var(--radius-md);background:var(--destructive-wash);
2158
+ font-size:var(--text-xs);line-height:1.55;color:var(--foreground)}
2159
+ .ing-bad b{color:var(--destructive);font-weight:500}
2160
+ /* how old the scan is. A proposal reviewed against a repository that has moved is the mismatch
2161
+ applyChange refuses, so the age is on screen before you confirm rather than after. */
2162
+ .ing-stamp{font-family:var(--mono);font-size:var(--text-tiny);color:var(--muted-foreground)}
2163
+
2164
+ /* ── profiles (workspace-profiles) ────────────────────────────────────────────────────────────
2165
+ NOTHING in this vocabulary may render a dot, a tick, or a colour that reads as "connected".
2166
+ PMOS has no credential store, no MCP client and no reachability check; a descriptor DECLARES mcp
2167
+ servers, and a declaration is a string a human typed into a JSON file. A status light over it
2168
+ would be a fabrication, whatever the file says.
2169
+ The chips are exactly the descriptors that were read — never a hard-coded pair. */
2170
+ .prof{display:flex;flex-wrap:wrap;gap:4px}
2171
+ /* the existing `.seg [role=radio]` look, at the size a wrapping group needs */
2172
+ .prof-chip{display:inline-flex;align-items:baseline;gap:6px;max-width:100%;min-width:0;
2173
+ min-height:30px;padding:5px 9px;text-align:left;
2174
+ border:1px solid var(--border);border-radius:var(--radius-md);background:var(--secondary);
2175
+ color:var(--muted-foreground);font-size:var(--text-xs);
2176
+ transition:background-color var(--duration-fast) var(--ease-emphasized),
2177
+ border-color var(--duration-fast) var(--ease-emphasized)}
2178
+ .prof-chip:hover{color:var(--foreground);border-color:var(--ring)}
2179
+ .prof-chip[aria-checked="true"]{background:var(--background);color:var(--foreground);
2180
+ border-color:var(--brand);font-weight:500;box-shadow:0 1px 2px var(--shadow)}
2181
+ /* the declared mcp names sit at the SAME weight as the account string beside them. They are two
2182
+ equally ordinary facts read out of one file, and typographic emphasis on the mcp list would read
2183
+ as a claim about reachability that nothing in this repository can make. */
2184
+ .prof-mcp{font-family:var(--mono);font-size:var(--text-tiny);font-weight:400;
2185
+ color:var(--muted-foreground);overflow-wrap:anywhere}
2186
+ .prof-note{font-size:var(--text-xs);line-height:1.55;color:var(--muted-foreground)}
2187
+ /* a descriptor that could not be used is RED and says why — and it is NOT offered as a choice,
2188
+ while every good chip beside it still is. */
2189
+ .prof-bad{margin-top:4px;padding:8px 10px;border:1px solid var(--destructive);
2190
+ border-radius:var(--radius-md);background:var(--destructive-wash);
2191
+ font-size:var(--text-xs);line-height:1.55;color:var(--foreground)}
2192
+ .prof-bad b{color:var(--destructive);font-weight:500}
2193
+ /* R6 — which profile a pane was started under, as WORKSPACE chrome between the tab strip and the
2194
+ terminal. It is never written into the pane: input written to a tmux pane goes to the AGENT, a
2195
+ ring-buffer append is erased on the next reattach, and a line injected into scrollback is
2196
+ indistinguishable from one the agent printed — which defeats a line whose entire purpose is
2197
+ provenance. It continues the `.stabs` band (same --muted surface) so it reads as chrome and not
2198
+ as the terminal's first row. The sentence is computed server-side and printed verbatim. */
2199
+ .profline{display:block;flex:none;padding:4px 10px;background:var(--muted);
2200
+ border-bottom:1px solid var(--border);
2201
+ font-family:var(--mono);font-size:var(--text-tiny);line-height:1.5;
2202
+ color:var(--muted-foreground);overflow-wrap:anywhere}
2203
+ /* collapses only when there is literally no pane to describe. `profile: not recorded` is a
2204
+ sentence and stays on screen; this is not a way to hide one. */
2205
+ .profline:empty{display:none}
2206
+
2207
+ /* ── handoff links (workspace-jira-handoff) ───────────────────────────────────────────────────
2208
+ `links.connected` is false on every code path in the module, so there is no connected variant
2209
+ to write here and none is coming. The one thing that must not happen on this surface is a
2210
+ declared string borrowing the authority of a reading. */
2211
+ .d2-links{display:flex;flex-direction:column;gap:2px;padding:4px 14px 10px}
2212
+ .d2-link{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:8px;row-gap:2px;
2213
+ min-height:30px;margin:0 -3px;padding:6px 9px;
2214
+ background:var(--background);border:1px solid var(--border);border-radius:var(--radius-md)}
2215
+ .d2-links a{min-width:0;color:var(--brand);text-decoration:underline;text-underline-offset:2px;
2216
+ overflow-wrap:anywhere}
2217
+ /* MUST NOT be styled like .d2-pill. In this panel the pill grammar — the filled --secondary box
2218
+ with its hairline border — means "PMOS worked this out". A typed epic key is a string a human
2219
+ wrote into frontmatter. Lending it the derived marker's box is exactly how a declaration
2220
+ silently borrows the authority of a reading, and it is this feature's named FAIL. So: plain mono
2221
+ text. No background, no border, no padding, no box of any kind. */
2222
+ .d2-kind{flex:none;padding:0;background:none;border:0;white-space:nowrap;
2223
+ font-family:var(--mono);font-size:var(--text-tiny);letter-spacing:.06em;
2224
+ color:var(--muted-foreground)}
2225
+ /* VISIBLE TEXT inside every row — never a `title` tooltip, never one banner at the top of the
2226
+ section standing in for all of them, never colour alone. It is deliberately not shrunk below the
2227
+ row's own type and not dimmed further: judging this line repetitive and demoting it is the exact
2228
+ move that lets a declared string read as a live connection. */
2229
+ .d2-notconn{display:block;flex-basis:100%;font-size:var(--text-row);line-height:1.5;
2230
+ color:var(--muted-foreground)}
2231
+ .d2-notconn::before{content:"— "}
2232
+ /* a reference that is NOT a url — no href was constructed, because PMOS never builds a URL it was
2233
+ not given. It reads as text you can copy, never as a link you can follow: no link hue, no solid
2234
+ underline, and the copy control is the only thing in it that is pressable. */
2235
+ .d2-inert{display:inline-flex;align-items:baseline;gap:7px;max-width:100%;min-width:0;
2236
+ font-family:var(--mono);font-size:var(--text-tiny);color:var(--foreground);
2237
+ border-bottom:1px dashed var(--input);overflow-wrap:anywhere}
2238
+ .d2-inert button{flex:none;min-height:30px;padding:0 7px;font-size:var(--text-2xs);
2239
+ color:var(--muted-foreground);
2240
+ transition:color var(--duration-fast) var(--ease-emphasized)}
2241
+ .d2-inert button:hover{color:var(--brand)}
2242
+ /* the build/review annotation on a ladder step. An ANNOTATION, not a verdict: it never takes the
2243
+ step's tone hue, never the .d2-next label grammar, and nothing about it feeds `data-s` — a
2244
+ declared field may add context to a derived verdict, it may never become one. */
2245
+ .d2-anno{font-family:var(--mono);font-size:var(--text-tiny);letter-spacing:.06em;
2246
+ color:var(--muted-foreground)}
2247
+
2248
+ /* ── touch targets for everything above ───────────────────────────────────────────────────────
2249
+ ≥30px with a pointer (declared inline above) and ≥44px on a phone. This block is LAST in the
2250
+ file on purpose: the per-component rules it raises are at the same specificity, so source order
2251
+ is what decides, and a ≤760px block written higher up would silently lose to them. */
2252
+ @media (max-width:760px){
2253
+ .focusbtn,.fc-item,.fc-obj,.fc-run,.fc-del,.psw-b,.psw-add,.prof-chip,
2254
+ .ing-src button,.ing-item button,.d2-inert button{min-height:44px}
2255
+ .d2-links a{display:inline-flex;align-items:center;min-height:44px}
2256
+ /* the panel has the whole screen's width here, so it can afford more of its height */
2257
+ .fc-body{max-height:min(70vh,640px)}
2258
+ }