create-pmos 0.6.4 → 0.7.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.
@@ -16,18 +16,43 @@
16
16
  <span class="branch" id="branchChip" hidden title="current branch"></span>
17
17
  <div class="right">
18
18
  <button class="ibtn" id="scratchBtn" title="Scratchpad — stage text before the terminal (⌘J)" aria-pressed="false">scratch</button>
19
- <button class="ibtn" id="railBtn" title="Hide/show the library rail" aria-pressed="false">rail</button>
19
+ <!-- The layout control: names the arrangement you are in, and opens the list. Adjusting a
20
+ layout is kept against THAT layout automatically — there is no save, no dirty state and
21
+ no discard (R4). `restore` on a stock entry is the escape hatch that replaces them. -->
22
+ <button class="ibtn lay-chip" id="layBtn" aria-expanded="false" aria-controls="layMenu"
23
+ title="Layout — pick one, then adjust it freely (⌃1–⌃4)"><i aria-hidden="true">▤</i><span id="layName">review</span></button>
20
24
  <button class="ibtn" id="themeBtn" title="Toggle theme">theme</button>
21
25
  </div>
26
+
27
+ <div class="lay-menu" id="layMenu" hidden role="dialog" aria-label="Layouts">
28
+ <div class="lay-cap">layouts</div>
29
+ <div id="layList"></div>
30
+ <form class="lay-new miniform" id="layNewForm" hidden>
31
+ <label class="lbl" for="layNewName">name this layout</label>
32
+ <input id="layNewName" type="text" placeholder="e.g. two agents + prd" autocomplete="off" spellcheck="false" maxlength="40" />
33
+ <div class="row"><button type="submit" class="go">create</button>
34
+ <button type="button" class="quiet" id="layNewCancel">cancel</button></div>
35
+ </form>
36
+ <button class="lay-add" id="layNewBtn">+ new layout from this arrangement</button>
37
+ <p class="lay-foot">Layouts belong to <b id="layScope">this repo</b>. Drag a pane by its ⠿ grip,
38
+ or an edge to resize — whatever you change is kept.</p>
39
+ </div>
22
40
  </header>
23
41
 
24
- <!-- Three columns: the workspace (repos over running agents) · the terminal · the library rail
25
- (library over the document reader). Each rail pane folds away; the rail itself hides. -->
42
+ <!-- The arrangement is COMPUTED from the current layout (lib/layout.js), not hard-coded here:
43
+ the sidebar, the terminal and the reader are grid areas whose columns/rows/areas are written
44
+ by applyLayout(). Each pane carries a ⠿ grip (move) and, where it has an outer edge, a
45
+ resize handle. DOM order is fixed; only the grid decides where a pane lands. -->
26
46
  <div class="body" id="bodyGrid">
27
47
 
28
- <nav class="side" aria-label="Workspaces and running agents">
48
+ <nav class="side" id="side" aria-label="Workspaces and running agents">
49
+ <div class="edge" id="sideEdge" data-edge="side" role="separator" aria-label="Resize the sidebar" tabindex="0"></div>
29
50
  <section class="pane" id="paneWs">
30
- <div class="cap">workspaces <button class="mini" id="addWsBtn" aria-expanded="false" title="Add a repository">+</button></div>
51
+ <div class="cap">
52
+ <span class="grip" id="sideGrip" data-pane="side" role="button" tabindex="0"
53
+ aria-label="Move the sidebar" title="Move this column">⠿</span>
54
+ <button class="fold" id="wsFold" data-fold="ws" aria-expanded="true" title="Hide the workspaces"><span class="chev" aria-hidden="true">▾</span>workspaces</button>
55
+ <button class="mini" id="addWsBtn" aria-expanded="false" title="Add a repository">+</button></div>
31
56
  <div class="pane-body">
32
57
  <div id="workspaces"></div>
33
58
  <form class="miniform" id="addWsForm" hidden>
@@ -44,7 +69,9 @@
44
69
  </section>
45
70
 
46
71
  <section class="pane" id="paneSess">
47
- <div class="cap">running agents · <span id="sessCount"></span> <span class="ct" id="needCount"></span></div>
72
+ <div class="cap">
73
+ <button class="fold" id="sessFold" data-fold="sess" aria-expanded="true" title="Hide the running agents"><span class="chev" aria-hidden="true">▾</span>running agents</button>
74
+ · <span id="sessCount"></span> <span class="ct" id="needCount"></span></div>
48
75
  <div class="pane-body">
49
76
  <button class="newsess" id="newBtn" aria-expanded="false"><i>+</i> new session — run an agent here</button>
50
77
  <form class="miniform" id="newForm" hidden>
@@ -72,25 +99,46 @@
72
99
  </nav>
73
100
 
74
101
  <main class="term-col" aria-label="Sessions and terminal">
75
- <div class="stabs" role="tablist" aria-label="Open sessions" id="stabs"></div>
76
- <div class="termhost" id="termhost">
77
- <!-- INSIDE termhost (position:relative): the overlay covers only the terminal area,
78
- never the sidebar/tabs/needs — those must stay usable while a session is exited. -->
79
- <div class="exited-overlay" id="exited">
80
- <div class="card">
81
- <p id="exitedMsg">This session has exited.</p>
82
- <div class="row">
83
- <button id="respawnBtn">↻ respawn</button>
84
- <button id="removeBtn" class="quiet">✕ remove session</button>
102
+ <!-- Terminal GROUPS (R6). One group is the shipped behaviour; `split` shows a second beside
103
+ it, each with its own tab strip and its own visible session. The #needs strip is NOT
104
+ duplicated — it belongs to the column, not to a group. -->
105
+ <div class="groups" id="groups" data-split="off">
106
+ <section class="group" id="group0" data-g="0">
107
+ <div class="stabs" role="tablist" aria-label="Open sessions" id="stabs"></div>
108
+ <div class="termhost" id="termhost">
109
+ <!-- INSIDE termhost (position:relative): the overlay covers only the terminal area,
110
+ never the sidebar/tabs/needs — those must stay usable while a session is exited. -->
111
+ <div class="exited-overlay" id="exited">
112
+ <div class="card">
113
+ <p id="exitedMsg">This session has exited.</p>
114
+ <div class="row">
115
+ <button id="respawnBtn">↻ respawn</button>
116
+ <button id="removeBtn" class="quiet">✕ remove session</button>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ <div class="empty-overlay" id="noSess">
121
+ <div class="card">
122
+ <p>No sessions in <b id="noSessWs">this workspace</b> yet.</p>
123
+ <p class="reason">+ new session runs an agent here — it starts in this repo's root.</p>
124
+ </div>
85
125
  </div>
86
126
  </div>
87
- </div>
88
- <div class="empty-overlay" id="noSess">
89
- <div class="card">
90
- <p>No sessions in <b id="noSessWs">this workspace</b> yet.</p>
91
- <p class="reason">+ new session runs an agent here — it starts in this repo's root.</p>
127
+ </section>
128
+ <section class="group" id="group1" data-g="1" hidden>
129
+ <div class="stabs" role="tablist" aria-label="Open sessions, second group" id="stabs1"></div>
130
+ <div class="termhost" id="termhost1">
131
+ <!-- a split with nothing to put in it offers the next step, never an empty black box -->
132
+ <div class="empty-overlay" id="noSess1">
133
+ <div class="card">
134
+ <p>Nothing else to show here yet.</p>
135
+ <p class="reason">Start a second session and it appears in this group.</p>
136
+ <div class="row"><button id="newHere1">+ new session</button>
137
+ <button id="unsplitBtn" class="quiet">✕ close this group</button></div>
138
+ </div>
139
+ </div>
92
140
  </div>
93
- </div>
141
+ </section>
94
142
  </div>
95
143
  <div class="needs" id="needs" role="status" style="display:none"></div>
96
144
 
@@ -113,9 +161,11 @@
113
161
  </main>
114
162
 
115
163
  <aside class="rail" id="rail" aria-label="Library and document">
116
- <div class="railgrip" id="railGrip" role="separator" aria-label="Resize the rail" tabindex="0"></div>
164
+ <div class="railgrip edge" id="railGrip" data-edge="read" role="separator" aria-label="Resize the reader" tabindex="0"></div>
117
165
  <section class="pane lib-pane" id="paneLib">
118
166
  <div class="cap">
167
+ <span class="grip" id="readGrip" data-pane="reader" role="button" tabindex="0"
168
+ aria-label="Move the reader" title="Move this pane">⠿</span>
119
169
  <button class="fold" id="libFold" aria-expanded="true" title="Hide the library"><span class="chev" aria-hidden="true">▾</span>library</button>
120
170
  <span class="lens" role="tablist" aria-label="Library view">
121
171
  <button role="tab" data-lens="initiatives" aria-selected="true">initiatives</button>
@@ -142,6 +192,16 @@
142
192
  <div class="pane-body doc-body" id="docBody" tabindex="0"></div>
143
193
  </section>
144
194
  </aside>
195
+
196
+ <!-- Drop targets, shown only while a ⠿ grip is being dragged. They are an ACCELERATOR: every
197
+ position they offer is also reachable from the layout menu, so the drag is never the only
198
+ path to an arrangement (R12). -->
199
+ <div class="drop" id="drop" aria-hidden="true">
200
+ <div class="dz" data-z="left">left</div>
201
+ <div class="dz" data-z="right">right</div>
202
+ <div class="dz" data-z="bottom">bottom</div>
203
+ <div class="dz" data-z="off">hide</div>
204
+ </div>
145
205
  </div>
146
206
 
147
207
  <!-- ⌘K jump palette (client-side; no backend) -->
@@ -195,6 +255,11 @@
195
255
  <script src="/vendor/addon-fit.js"></script>
196
256
  <script src="/vendor/addon-web-links.js"></script>
197
257
  <script src="/lib/preview-kind.js"></script>
258
+ <script src="/lib/layout.js"></script>
259
+ <!-- Both sides of the review must agree about what is applyable and what a change produces, so the
260
+ browser loads the SAME modules the server requires — the client never re-derives either. -->
261
+ <script src="/lib/finding-contract.js"></script>
262
+ <script src="/lib/apply-change.js"></script>
198
263
  <script src="/app.js"></script>
199
264
  </body>
200
265
  </html>
@@ -52,7 +52,10 @@ button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
52
52
  .app{display:grid; grid-template-rows:44px minmax(0,1fr) 28px; height:100vh; height:100dvh}
53
53
 
54
54
  /* ── top bar ── */
55
- .bar{display:flex; align-items:center; gap:10px; padding:0 12px; min-width:0;
55
+ /* position+z-index so the header's own popover (the layout menu) paints ABOVE the body: the two
56
+ are grid siblings, and without this the later-in-DOM body wins the paint order and swallows
57
+ every click aimed at the menu. */
58
+ .bar{display:flex; align-items:center; gap:10px; padding:0 12px; min-width:0; position:relative; z-index:50;
56
59
  border-bottom:1px solid var(--border-3); background:var(--glass); backdrop-filter:blur(8px)}
57
60
  .mark{width:23px;height:23px;display:grid;place-items:center;border:1px solid var(--border-strong);
58
61
  border-radius:7px;background:var(--pill);font-family:var(--mono);font-size:11px;font-weight:700}
@@ -70,16 +73,33 @@ button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
70
73
  .ibtn:hover{border-color:var(--border-strong);color:var(--text-2)}
71
74
  .ibtn[aria-pressed="true"]{border-color:var(--accent);color:var(--accent-text)}
72
75
 
73
- /* ── three columns: workspace · terminal · library rail ── */
76
+ /* ── the arrangement (workspace-layout-system) ──────────────────────────────────────────────
77
+ The template is COMPUTED from the current layout by applyLayout() and written into these three
78
+ custom properties — never as a direct grid declaration. That is deliberate: an inline
79
+ `grid-template-columns` would outrank the ≤760px media query below and take the phone's
80
+ single-column switcher with it. Custom properties lose to a later direct declaration, so the
81
+ two responsive systems stay in charge of their own widths (R9). */
74
82
  .body{display:grid; position:relative;
75
- grid-template-columns:262px minmax(0,1fr) minmax(0,var(--rail-w));
76
- grid-template-rows:minmax(0,1fr); min-height:0}
77
- .body.rail-off{grid-template-columns:262px minmax(0,1fr)}
78
- .body.rail-off .rail{display:none}
79
-
80
- /* ── left column: the workspace — repos on top, running agents below, equal halves ── */
83
+ grid-template-columns:var(--lay-cols, 262px minmax(0,1fr) minmax(0,var(--rail-w)));
84
+ grid-template-rows:var(--lay-rows, minmax(0,1fr));
85
+ grid-template-areas:var(--lay-areas, "side term read");
86
+ min-height:0}
87
+ .side{grid-area:side} .term-col{grid-area:term} .rail{grid-area:read}
88
+ .body[data-side="off"]>.side{display:none}
89
+ .body[data-reader="off"]>.rail{display:none}
90
+ /* docked bottom, the reader is a wide short pane under the terminal, not a column beside it */
91
+ .body[data-reader="bottom"]>.rail{border-left:0;border-top:1px solid var(--border-3)}
92
+ .body[data-reader="bottom"] .railgrip{display:none}
93
+ .body[data-side="right"]>.side{border-right:0;border-left:1px solid var(--border-3)}
94
+ .body[data-reader="left"]>.rail{border-left:0;border-right:1px solid var(--border-3)}
95
+ .body[data-reader="left"] .railgrip{left:auto;right:-3px}
96
+
97
+ /* ── left column: the workspace — repos over running agents. Both panes fold and the column
98
+ resizes, which is the parity the rail always had and this one never did (R11). ── */
81
99
  .side{border-right:1px solid var(--border-3);background:var(--panel);min-height:0;
82
- display:grid;grid-template-rows:1fr 1fr}
100
+ display:flex;flex-direction:column;position:relative}
101
+ .side>.pane{flex:1 1 0}
102
+ .side>.pane[data-fold="off"]{flex:0 0 auto}
83
103
  .side>.pane+.pane{border-top:1px solid var(--border-3)}
84
104
  .pane{display:flex;flex-direction:column;min-height:0;min-width:0}
85
105
  .pane-body{overflow-y:auto;overscroll-behavior:contain;flex:1;min-height:0;padding-bottom:10px}
@@ -100,6 +120,69 @@ button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
100
120
  .cap .fold .chev{font-size:9px;transition:transform .12s}
101
121
  .pane[data-fold="off"] .cap .fold .chev{transform:rotate(-90deg)}
102
122
  @media (prefers-reduced-motion:reduce){.cap .fold .chev{transition:none}}
123
+ /* the move affordance every relocatable pane carries. Keyboard reaches every position it
124
+ offers through the layout menu, so this is an accelerator and never the only path (R12). */
125
+ .cap .grip{width:22px;min-height:26px;display:grid;place-items:center;border-radius:6px;flex:none;
126
+ color:var(--text-dim);cursor:grab;letter-spacing:-2px;margin-left:-4px;touch-action:none}
127
+ .cap .grip:hover{background:var(--card-hover);color:var(--accent-text)}
128
+ .cap .grip:active{cursor:grabbing}
129
+
130
+ /* a column's outer edge, dragged to resize. The rail's original .railgrip is one of these. */
131
+ .edge{position:absolute;top:0;bottom:0;width:7px;z-index:20;cursor:col-resize;touch-action:none}
132
+ .edge:hover,.edge.on{background:linear-gradient(90deg,transparent,var(--glow),transparent)}
133
+ .side>.edge{right:-3px}
134
+ .body[data-side="right"]>.side>.edge{right:auto;left:-3px}
135
+
136
+ /* drop targets — visible only while a grip is being dragged */
137
+ .drop{position:absolute;inset:0;z-index:40;display:none;grid-area:1/1/-1/-1}
138
+ .body.dragging>.drop{display:block}
139
+ .dz{position:absolute;border:1px dashed var(--border-strong);border-radius:12px;
140
+ background:var(--accent-wash);display:grid;place-items:center;font-family:var(--mono);
141
+ font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--text-muted)}
142
+ .dz.hot{border-style:solid;border-color:var(--accent);color:var(--accent-text);
143
+ background:var(--pill);box-shadow:0 0 0 1px var(--glow)}
144
+ .dz[data-z="left"]{left:8px;top:8px;bottom:8px;width:22%}
145
+ .dz[data-z="right"]{right:8px;top:8px;bottom:8px;width:22%}
146
+ .dz[data-z="bottom"]{left:26%;right:26%;bottom:8px;height:30%}
147
+ .dz[data-z="off"]{left:26%;right:26%;top:8px;height:20%}
148
+ .dz.no{display:none}
149
+ .laghost{position:fixed;z-index:70;pointer-events:none;padding:5px 10px;border-radius:8px;
150
+ background:var(--pill);border:1px solid var(--accent);color:var(--accent-text);
151
+ font-family:var(--mono);font-size:10px}
152
+
153
+ /* ── the layout control ── */
154
+ .ibtn.lay-chip{gap:7px;display:inline-flex;align-items:center;padding:0 11px}
155
+ .ibtn.lay-chip i{font-style:normal;color:var(--text-dim);font-size:11px}
156
+ .ibtn.lay-chip[aria-expanded="true"]{border-color:var(--accent);color:var(--accent-text);background:var(--pill)}
157
+ .lay-menu{position:absolute;top:40px;right:12px;z-index:60;width:272px;background:var(--panel);
158
+ border:1px solid var(--border-strong);border-radius:12px;padding:8px;
159
+ box-shadow:0 22px 60px -26px var(--shadow)}
160
+ .lay-cap{font-family:var(--mono);font-size:8.5px;letter-spacing:.15em;text-transform:uppercase;
161
+ color:var(--text-dim);margin:2px 5px 6px}
162
+ .lay-row{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px;border-radius:9px;
163
+ font-family:var(--mono);font-size:11px;color:var(--text-3);text-align:left;min-height:38px}
164
+ .lay-row:hover{background:var(--card-hover);color:var(--text-2)}
165
+ .lay-row[aria-current="true"]{background:var(--pill);color:var(--accent-text);
166
+ box-shadow:inset 0 0 0 1px var(--accent)}
167
+ .lay-row .nm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
168
+ .lay-row .kbd{font-size:8.5px;color:var(--text-dim);border:1px solid var(--border);
169
+ border-radius:4px;padding:0 3px;flex:none}
170
+ /* a wireframe of the arrangement — the name alone does not say what you get */
171
+ .lay-mini{width:28px;height:18px;border:1px solid var(--border-strong);border-radius:3px;
172
+ display:flex;gap:1px;padding:1px;flex:none;background:var(--bg)}
173
+ .lay-mini span{background:var(--border-strong);border-radius:1px}
174
+ .lay-row[aria-current="true"] .lay-mini span{background:var(--accent)}
175
+ .lay-act{display:flex;align-items:center;justify-content:center;min-width:26px;min-height:26px;
176
+ border-radius:6px;color:var(--text-dim);font-size:11px;flex:none}
177
+ .lay-act:hover{background:var(--chip);color:var(--accent-text)}
178
+ .lay-act.del:hover{color:var(--bad)}
179
+ .lay-add{display:block;width:100%;margin-top:4px;padding:9px;border:1px dashed var(--border-strong);
180
+ border-radius:10px;font-family:var(--mono);font-size:10.5px;color:var(--text-muted)}
181
+ .lay-add:hover{color:var(--text-2);border-color:var(--accent)}
182
+ .lay-new{padding:6px 5px 2px}
183
+ .lay-foot{font-family:var(--mono);font-size:9.5px;color:var(--text-dim);line-height:1.55;
184
+ margin:9px 5px 2px;padding-top:8px;border-top:1px solid var(--border-3)}
185
+ .lay-foot b{color:var(--text-muted);font-weight:400}
103
186
 
104
187
  /* workspaces — one server, many repos; the row carries the switch + a forget affordance */
105
188
  .wsrow{display:flex;align-items:stretch}
@@ -213,6 +296,14 @@ details.hz .kv .src{font-family:var(--mono);font-size:8.5px;color:var(--text-dim
213
296
 
214
297
  /* ── terminal column ── */
215
298
  .term-col{display:flex;flex-direction:column;min-width:0;min-height:0;background:var(--term-bg)}
299
+ /* Terminal GROUPS (R6): one by default, two side by side when the layout says split. Each group
300
+ owns a tab strip and a host; the #needs strip and the scratchpad stay with the column. */
301
+ .groups{flex:1;min-height:0;min-width:0;display:flex;flex-direction:row}
302
+ .group{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;background:var(--term-bg)}
303
+ .group[hidden]{display:none}
304
+ .group+.group{border-left:1px solid var(--border-3)}
305
+ .group[data-focus="true"] .stabs{box-shadow:inset 0 2px 0 var(--accent)}
306
+ .groups[data-split="off"] .group[data-focus="true"] .stabs{box-shadow:none}
216
307
  .stabs{display:flex;gap:2px;padding:6px 8px 0;background:var(--panel-head);flex:none;
217
308
  border-bottom:1px solid var(--border-3);overflow-x:auto;min-height:44px;align-items:flex-end}
218
309
  /* tab = wrap of two SIBLING buttons (select + close) — valid interactive structure */
@@ -631,10 +722,20 @@ details.tree>summary:hover{color:var(--text-2)}
631
722
 
632
723
  /* Narrow: the rail floats over the terminal instead of squeezing it below a usable width. */
633
724
  @media (max-width:1100px){
634
- .body{grid-template-columns:222px minmax(0,1fr)}
635
- .rail{position:absolute;top:0;right:0;bottom:0;width:min(420px,86vw);z-index:30;
636
- box-shadow:-18px 0 50px -30px var(--shadow)}
725
+ /* applyLayout() drops the reader's track from the template at this width and sets data-float,
726
+ so the grid here is sidebar + terminal and the reader floats over the terminal rather than
727
+ squeezing it below a usable width. Docked BOTTOM it keeps its row and does not float. */
728
+ .body[data-float="on"]>.rail{position:absolute;top:0;right:0;bottom:0;left:auto;
729
+ width:min(420px,86vw);z-index:30;box-shadow:-18px 0 50px -30px var(--shadow)}
730
+ .body[data-float="on"][data-reader="left"]>.rail{right:auto;left:0;
731
+ box-shadow:18px 0 50px -30px var(--shadow)}
732
+ /* Split is forced OFF below this width by applyLayout() — two terminal groups inside what is
733
+ left of a narrow window is two unusable terminals. The layout menu says so rather than
734
+ silently ignoring the choice (R9). */
735
+ .lay-narrow{display:block}
637
736
  }
737
+ .lay-narrow{display:none;font-family:var(--mono);font-size:9.5px;color:var(--warn-text);
738
+ line-height:1.5;margin:6px 5px 0}
638
739
 
639
740
  /* history lens — episodic recall over this workspace's own session scrollback */
640
741
  .hi-hint{padding:14px;color:var(--text-dim);font-size:11px;line-height:1.6}
@@ -672,17 +773,27 @@ details.tree>summary:hover{color:var(--text-2)}
672
773
  @media (max-width:760px){
673
774
  /* an extra row above the status strip carries the control bar + switcher */
674
775
  .app{grid-template-rows:44px minmax(0,1fr) auto 28px}
675
- /* collapse to a single column; the switcher decides which pane is shown */
676
- .body{grid-template-columns:1fr;position:relative}
776
+ /* collapse to a single column; the switcher decides which pane is shown. These are DIRECT
777
+ declarations and the layout writes custom properties, so the phone's arrangement wins here
778
+ whatever layout the desktop is on — the two systems never fight (R9). */
779
+ .body{grid-template-columns:1fr;grid-template-rows:minmax(0,1fr);grid-template-areas:none;position:relative}
780
+ /* the named areas are gone here, so the panes must stop asking for them — a stale `grid-area`
781
+ places them on implicit tracks and the pane spills over the switcher it is covering */
782
+ .body>.side,.body>.term-col,.body>.rail,.body>.drop{grid-area:auto}
677
783
  .body>.side,.body>.term-col,.body>.rail{display:none}
678
- .body[data-mview="sessions"]>.side{display:grid}
784
+ .body[data-mview="sessions"]>.side{display:flex}
679
785
  .body[data-mview="terminal"]>.term-col{display:flex}
680
786
  .body[data-mview="library"]>.rail,
681
- .body.rail-off[data-mview="library"]>.rail{display:flex}
787
+ .body[data-reader="off"][data-mview="library"]>.rail{display:flex}
682
788
  /* the rail is a full-width pane here, not the floating overlay the ≤1100 rule makes it */
683
- .rail{position:static;width:auto;box-shadow:none;z-index:auto;border-left:0}
684
- .railgrip,.railsplit{display:none} /* edge-drag is a pointer affordance; the switcher owns layout on touch */
685
- .side{grid-template-rows:1fr 1fr}
789
+ .body[data-float="on"]>.rail,.rail{position:static;width:auto;box-shadow:none;z-index:auto;
790
+ border-left:0;border-top:0}
791
+ .railgrip,.railsplit,.side>.edge{display:none} /* edge-drag is a pointer affordance; the switcher owns layout on touch */
792
+ .cap .grip,.lay-chip{display:none} /* and so is the whole layout control (R9) */
793
+ .side>.pane{flex:1 1 0}
794
+ /* one group on a phone, always — the split is a desktop arrangement */
795
+ .groups{flex-direction:column}
796
+ .group+.group{display:none}
686
797
 
687
798
  /* the mobile menu: a terminal control bar (terminal view only) over the pane switcher */
688
799
  /* min-width:0 lets this grid item shrink below its content min-size, so the control bar's
@@ -810,3 +921,68 @@ details.tree>summary:hover{color:var(--text-2)}
810
921
  border:0;padding:10px 12px;background:var(--term-bg);color:var(--text-2);
811
922
  font-family:var(--mono);font-size:12px;line-height:1.5;outline:none}
812
923
  .scratch[data-open="true"] .scratch-text{display:block}
924
+
925
+ /* ── the summoned review (workspace-review-and-edit) ──────────────────────────────────────────
926
+ Deliberately the quietest surface in the app. The PM's disposition on the prototype was "too much
927
+ guardrails and ceremony — guardrails is ok, just make it less obvious", so every guard here is
928
+ structural and none of them announces itself: no cards, no severity hues, no taxonomy headers.
929
+ A withheld finding is marked only by a hollow dot and the ABSENCE of an Apply button. */
930
+ /* The rail is the surface's real home (~400-520px), not the full-width page the prototype was
931
+ judged on — so the rhythm is tuned for the column it actually lives in, and the max-width only
932
+ bites once the PM drags the rail wide. */
933
+ .rv{max-width:660px;padding:16px 18px 36px}
934
+ .rv-lede{font-size:15px;line-height:1.45;letter-spacing:-.01em;color:var(--text);margin-bottom:5px}
935
+ .rv-sub{font-size:11.5px;line-height:1.55;color:var(--text-muted);margin-bottom:18px;
936
+ word-break:break-word;overflow-wrap:anywhere}
937
+ .rv-sub .mono{font-size:10.5px}
938
+ .rv-empty{padding:24px 22px;max-width:560px}
939
+ .rv-empty p{font-size:14px;line-height:1.6;color:var(--text-2);margin-bottom:8px}
940
+ .rv-empty .rv-dim{font-size:12.5px;color:var(--text-muted)}
941
+ .rv-errs{margin:10px 0 0 18px;font-family:var(--mono);font-size:11px;color:var(--text-3);line-height:1.7}
942
+
943
+ .rv-f{position:relative;padding:14px 0 14px 18px;border-top:1px solid var(--border-3);cursor:pointer}
944
+ .rv-f:first-of-type{border-top:0}
945
+ .rv-f::before{content:"";position:absolute;left:0;top:20px;width:5px;height:5px;border-radius:50%;
946
+ background:var(--text-dim)}
947
+ .rv-f.on::before{background:var(--accent)}
948
+ /* hollow = nothing to apply. The shape carries it, so meaning is never in hue alone. */
949
+ .rv-f.rv-quiet::before{background:transparent;box-shadow:inset 0 0 0 1px var(--text-dim)}
950
+ .rv-f.rv-done{opacity:.55}
951
+ .rv-f.rv-done .rv-h{text-decoration:line-through;text-decoration-color:var(--text-dim)}
952
+ .rv-h{font-size:13.5px;line-height:1.5;color:var(--text-2);letter-spacing:-.005em}
953
+ .rv-f:hover .rv-h,.rv-f.on .rv-h{color:var(--text)}
954
+ .rv-src{margin-top:4px;font-family:var(--mono);font-size:10.5px;color:var(--text-dim)}
955
+ .rv-d{display:none;padding-top:11px}
956
+ .rv-f.on .rv-d,.rv-f[data-editing="1"] .rv-d{display:block}
957
+ .rv-d p{font-size:12.5px;line-height:1.7;color:var(--text-3);margin-bottom:10px}
958
+ .rv-why{padding-left:11px;box-shadow:inset 2px 0 0 var(--border-strong);color:var(--text-muted)}
959
+ .rv-acts{display:flex;gap:15px;align-items:center;flex-wrap:wrap;margin-top:12px}
960
+ .rv-acts button{font-size:12.5px;color:var(--accent-text);padding:3px 0;min-height:30px}
961
+ .rv-acts button:hover{text-decoration:underline}
962
+ .rv-acts button.q{color:var(--text-dim)}
963
+ .rv-only{font-size:12px;color:var(--text-dim)}
964
+ .rv-tail{margin-top:30px;padding-top:14px;border-top:1px solid var(--border-3);
965
+ font-size:11.5px;line-height:1.7;color:var(--text-dim)}
966
+ .rv-tail b{color:var(--text-muted);font-weight:500}
967
+ .rv-say{margin-bottom:14px;padding:9px 12px;border-radius:9px;border:1px solid var(--bad);
968
+ background:rgba(242,101,95,.08);font-size:12px;line-height:1.55;color:var(--text-2)}
969
+
970
+ /* the in-place editor — opens under the finding, never a modal or a mode switch */
971
+ .rv-ed:empty{display:none}
972
+ .rv-ed{padding-top:12px}
973
+ .rv-edfile{display:flex;align-items:baseline;gap:10px;font-size:10.5px;color:var(--text-dim);margin-bottom:8px}
974
+ .rv-n{margin-left:auto;font-family:var(--mono)}
975
+ .rv-n b{color:var(--ok-text);font-weight:600}
976
+ .rv-n i{color:var(--bad);font-style:normal;font-weight:600}
977
+ .rv-code{font-family:var(--mono);font-size:11.5px;line-height:1.8;
978
+ border-top:1px solid var(--border-3);border-bottom:1px solid var(--border-3);padding:6px 0}
979
+ .rv-l{display:grid;grid-template-columns:3px 1fr;align-items:start}
980
+ .rv-l .g{align-self:stretch;border-radius:2px;background:transparent}
981
+ .rv-l .t{padding:0 4px 0 11px;white-space:pre-wrap;word-break:break-word;color:var(--text-3)}
982
+ .rv-l.was .g{background:rgba(242,101,95,.5)}
983
+ .rv-l.was .t{color:var(--text-dim);text-decoration:line-through;text-decoration-color:rgba(242,101,95,.55)}
984
+ .rv-ta{width:100%;box-sizing:border-box;margin-top:8px;resize:vertical;
985
+ border:1px solid var(--border);border-left:3px solid var(--ok);border-radius:0 7px 7px 0;
986
+ padding:8px 11px;background:rgba(72,213,151,.06);color:var(--text-2);
987
+ font-family:var(--mono);font-size:11.5px;line-height:1.8;outline:none}
988
+ .rv-ta:focus{border-color:var(--accent);border-left-color:var(--ok)}