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.
- package/README.md +39 -0
- package/package.json +1 -1
- package/payload/KIT-VERSION +2 -2
- package/workspace/focus.test.js +650 -0
- package/workspace/handoff.test.js +443 -0
- package/workspace/lib/console-state.js +48 -1
- package/workspace/lib/drive-guard.js +98 -0
- package/workspace/lib/drive-plan.js +97 -0
- package/workspace/lib/engine/workflow-state.js +13 -3
- package/workspace/lib/focus.js +967 -0
- package/workspace/lib/handoff.js +280 -0
- package/workspace/lib/layout.js +68 -46
- package/workspace/lib/mobile-nav.js +127 -0
- package/workspace/lib/profiles.js +285 -0
- package/workspace/lib/repo-scan.js +469 -0
- package/workspace/package.json +4 -2
- package/workspace/profiles.test.js +362 -0
- package/workspace/public/app.js +2318 -208
- package/workspace/public/index.html +158 -28
- package/workspace/public/styles.css +865 -51
- package/workspace/repo-scan.test.js +327 -0
- package/workspace/server.js +914 -23
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
|
|
225
|
-
/*
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
230
|
-
.
|
|
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
|
-
/* ──
|
|
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
|
|
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
|
-
|
|
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
|
|
668
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
|
952
|
-
|
|
953
|
-
squeezing it below a usable width.
|
|
954
|
-
.body[data-float="on"]>.rail{position:absolute;top:0;
|
|
955
|
-
width:min(420px,86vw);z-index:30;box-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):
|
|
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
|
|
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
|
-
/*
|
|
1000
|
-
|
|
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
|
-
|
|
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-
|
|
1010
|
-
.body[data-
|
|
1011
|
-
.body[data-
|
|
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,.
|
|
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
|
|
1024
|
-
|
|
1025
|
-
|
|
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
|
-
.
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
.
|
|
1042
|
-
.
|
|
1043
|
-
|
|
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
|
+
}
|