create-pmos 0.6.3 → 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.
@@ -13,19 +13,46 @@
13
13
  <header class="bar">
14
14
  <span class="mark">P</span><b>PMOS</b><span class="sub">workspace</span>
15
15
  <span class="conn" id="conn">connecting…</span>
16
+ <span class="branch" id="branchChip" hidden title="current branch"></span>
16
17
  <div class="right">
17
- <button class="ibtn" id="railBtn" title="Hide/show the library rail" aria-pressed="false">rail</button>
18
+ <button class="ibtn" id="scratchBtn" title="Scratchpad — stage text before the terminal (⌘J)" aria-pressed="false">scratch</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>
18
24
  <button class="ibtn" id="themeBtn" title="Toggle theme">theme</button>
19
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>
20
40
  </header>
21
41
 
22
- <!-- Three columns: the workspace (repos over running agents) · the terminal · the library rail
23
- (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. -->
24
46
  <div class="body" id="bodyGrid">
25
47
 
26
- <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>
27
50
  <section class="pane" id="paneWs">
28
- <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>
29
56
  <div class="pane-body">
30
57
  <div id="workspaces"></div>
31
58
  <form class="miniform" id="addWsForm" hidden>
@@ -42,7 +69,9 @@
42
69
  </section>
43
70
 
44
71
  <section class="pane" id="paneSess">
45
- <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>
46
75
  <div class="pane-body">
47
76
  <button class="newsess" id="newBtn" aria-expanded="false"><i>+</i> new session — run an agent here</button>
48
77
  <form class="miniform" id="newForm" hidden>
@@ -53,6 +82,12 @@
53
82
  <button type="button" data-agent="claude" role="radio" aria-checked="false">claude</button>
54
83
  <button type="button" data-agent="codex" role="radio" aria-checked="false">codex</button>
55
84
  </div>
85
+ <div class="brwrap" id="newBranchWrap" hidden>
86
+ <label class="lbl" for="newBranch">branch</label>
87
+ <select id="newBranch" aria-label="Branch to run this session on"></select>
88
+ <input id="newBranchName" type="text" hidden placeholder="new-branch-name" aria-label="New branch name" autocomplete="off" spellcheck="false" />
89
+ <p class="hint warn">the branch is shared by every session in this repo — switching or creating one moves them all</p>
90
+ </div>
56
91
  <div class="row">
57
92
  <button type="submit" class="go">start session</button>
58
93
  <button type="button" class="quiet" id="newCancel">cancel</button>
@@ -64,33 +99,73 @@
64
99
  </nav>
65
100
 
66
101
  <main class="term-col" aria-label="Sessions and terminal">
67
- <div class="stabs" role="tablist" aria-label="Open sessions" id="stabs"></div>
68
- <div class="termhost" id="termhost">
69
- <!-- INSIDE termhost (position:relative): the overlay covers only the terminal area,
70
- never the sidebar/tabs/needs — those must stay usable while a session is exited. -->
71
- <div class="exited-overlay" id="exited">
72
- <div class="card">
73
- <p id="exitedMsg">This session has exited.</p>
74
- <div class="row">
75
- <button id="respawnBtn">↻ respawn</button>
76
- <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>
77
125
  </div>
78
126
  </div>
79
- </div>
80
- <div class="empty-overlay" id="noSess">
81
- <div class="card">
82
- <p>No sessions in <b id="noSessWs">this workspace</b> yet.</p>
83
- <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>
84
140
  </div>
85
- </div>
141
+ </section>
86
142
  </div>
87
143
  <div class="needs" id="needs" role="status" style="display:none"></div>
144
+
145
+ <!-- Scratchpad: a collapsible drawer at the BOTTOM of the terminal column. Folded it is a thin
146
+ bar; open it takes a bounded slice below the terminal — it never overlays the tabs, the
147
+ terminal, or the document rail. Persists per workspace. -->
148
+ <section class="scratch" id="scratch" data-open="false" aria-label="Scratchpad">
149
+ <div class="scratch-head">
150
+ <button type="button" class="scratch-toggle" id="scratchToggle" aria-expanded="false" aria-controls="scratchText">
151
+ <span class="chev" aria-hidden="true">▴</span> scratchpad
152
+ </button>
153
+ <span class="scratch-status" id="scratchStatus" role="status"></span>
154
+ <span class="scratch-actions">
155
+ <button type="button" class="mini" id="scratchCopy" title="Copy to clipboard">copy</button>
156
+ <button type="button" class="mini" id="scratchSend" title="Paste into the active terminal (does not press enter)">→ terminal</button>
157
+ </span>
158
+ </div>
159
+ <textarea id="scratchText" class="scratch-text" placeholder="Stage commands or notes here, then copy or send them to the terminal…" spellcheck="false" autocomplete="off" aria-label="Scratchpad text"></textarea>
160
+ </section>
88
161
  </main>
89
162
 
90
163
  <aside class="rail" id="rail" aria-label="Library and document">
91
- <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>
92
165
  <section class="pane lib-pane" id="paneLib">
93
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>
94
169
  <button class="fold" id="libFold" aria-expanded="true" title="Hide the library"><span class="chev" aria-hidden="true">▾</span>library</button>
95
170
  <span class="lens" role="tablist" aria-label="Library view">
96
171
  <button role="tab" data-lens="initiatives" aria-selected="true">initiatives</button>
@@ -117,6 +192,16 @@
117
192
  <div class="pane-body doc-body" id="docBody" tabindex="0"></div>
118
193
  </section>
119
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>
120
205
  </div>
121
206
 
122
207
  <!-- ⌘K jump palette (client-side; no backend) -->
@@ -170,6 +255,11 @@
170
255
  <script src="/vendor/addon-fit.js"></script>
171
256
  <script src="/vendor/addon-web-links.js"></script>
172
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>
173
263
  <script src="/app.js"></script>
174
264
  </body>
175
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
@@ -773,3 +884,105 @@ details.tree>summary:hover{color:var(--text-2)}
773
884
  .cs-perrow .bar{flex:1;height:6px;border-radius:4px;background:var(--chip);overflow:hidden}
774
885
  .cs-perrow .bar i{display:block;height:100%;background:var(--accent);border-radius:4px}
775
886
  .cs-perrow .v{font-family:var(--mono);font-size:10px;color:var(--text-dim);flex:none;width:28px;text-align:right}
887
+
888
+ /* ── branch awareness (workspace-session-controls) ── */
889
+ .bar .branch{font-family:var(--mono);font-size:10px;letter-spacing:.04em;color:var(--text-3);
890
+ padding:3px 8px;border:1px solid var(--border);border-radius:7px;background:var(--chip);
891
+ white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:26ch}
892
+ .bar .branch[hidden]{display:none}
893
+ .bar .branch.dirty{color:var(--warn-text);border-color:var(--warn)}
894
+
895
+ /* new-session branch picker — matches the miniform vocabulary */
896
+ .brwrap{display:flex;flex-direction:column;gap:6px}
897
+ .brwrap[hidden]{display:none}
898
+ .brwrap select{min-height:38px;padding:0 8px;border:1px solid var(--border-strong);border-radius:8px;
899
+ background:var(--chip);color:var(--text-2);font-family:var(--mono);font-size:11px}
900
+ .brwrap select:focus{border-color:var(--accent)}
901
+ .brwrap input[hidden]{display:none}
902
+ .miniform .hint.warn{color:var(--warn-text)}
903
+
904
+ /* ── scratchpad drawer: bottom of the terminal column, collapsible, never overlays anything ── */
905
+ .scratch{flex:none;border-top:1px solid var(--border-3);background:var(--panel);display:flex;flex-direction:column;min-height:0}
906
+ .scratch-head{display:flex;align-items:center;gap:8px;padding:5px 10px;min-height:34px;
907
+ background:var(--panel-head);border-bottom:1px solid transparent}
908
+ .scratch[data-open="true"] .scratch-head{border-bottom-color:var(--border-3)}
909
+ .scratch-toggle{display:inline-flex;align-items:center;gap:7px;border:0;background:none;
910
+ font-family:var(--mono);font-size:8.5px;letter-spacing:.15em;text-transform:uppercase;color:var(--text-dim);cursor:pointer}
911
+ .scratch-toggle:hover{color:var(--text-2)}
912
+ .scratch-toggle .chev{display:inline-block;transition:transform .15s;font-size:9px}
913
+ .scratch[data-open="true"] .scratch-toggle .chev{transform:rotate(180deg)}
914
+ .scratch-status{font-family:var(--mono);font-size:9.5px;color:var(--text-dim);letter-spacing:.04em}
915
+ .scratch-actions{margin-left:auto;display:flex;gap:6px}
916
+ .scratch-actions .mini{min-height:28px;padding:0 10px;border:1px solid var(--border);border-radius:7px;
917
+ background:var(--chip);color:var(--accent-text);font-family:var(--mono);font-size:10px;letter-spacing:0;cursor:pointer}
918
+ .scratch-actions .mini:hover{border-color:var(--accent)}
919
+ .scratch-actions .mini:disabled{color:var(--text-dim);border-color:var(--border-3);cursor:not-allowed;opacity:.6}
920
+ .scratch-text{display:none;width:100%;box-sizing:border-box;resize:vertical;height:22vh;min-height:90px;max-height:45vh;
921
+ border:0;padding:10px 12px;background:var(--term-bg);color:var(--text-2);
922
+ font-family:var(--mono);font-size:12px;line-height:1.5;outline:none}
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)}