create-pmos 0.6.4 → 0.7.1
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 +68 -0
- package/package.json +1 -1
- package/payload/KIT-VERSION +2 -2
- package/payload/scripts/gate-initiative.sh +4 -1
- package/workspace/lib/apply-change.js +106 -0
- package/workspace/lib/correction-record.js +127 -0
- package/workspace/lib/finding-contract.js +162 -0
- package/workspace/lib/harness-parse.js +5 -1
- package/workspace/lib/layout.js +171 -0
- package/workspace/lib/skill-store.js +28 -9
- package/workspace/lib/tmux-control.js +124 -0
- package/workspace/public/app.js +784 -87
- package/workspace/public/index.html +88 -23
- package/workspace/public/styles.css +214 -19
- package/workspace/server.js +337 -14
- package/workspace/skills/doc-review.skill +114 -0
|
@@ -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
|
-
|
|
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
|
-
<!--
|
|
25
|
-
|
|
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">
|
|
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">
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
<
|
|
80
|
-
<div class="
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
</
|
|
88
|
-
<
|
|
89
|
-
<div class="
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
</
|
|
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
|
|
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
|
-
|
|
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
|
-
/* ──
|
|
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);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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:
|
|
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
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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;
|
|
684
|
-
|
|
685
|
-
.side{
|
|
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,87 @@ 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)}
|
|
989
|
+
|
|
990
|
+
/* the reason tap — required on an edit, and never defaulted. The nudge is inline, not a modal:
|
|
991
|
+
a silent default is exactly the bug this replaced (every edit recorded "incomplete"). */
|
|
992
|
+
.rv-reason{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:10px}
|
|
993
|
+
.rv-rz{padding:0 9px;height:28px;border-radius:999px;border:1px solid var(--border-strong);
|
|
994
|
+
background:var(--chip);font-size:11.5px;color:var(--text-3)}
|
|
995
|
+
.rv-rz:hover{background:var(--card-hover);color:var(--text)}
|
|
996
|
+
/* Selected reads as accent TEXT on a wash, not a filled pill: nothing else on this surface carries
|
|
997
|
+
a solid fill, and no token exists for text-on-accent because no component here needed one. */
|
|
998
|
+
.rv-rz.on{background:var(--accent-wash);border-color:var(--accent);color:var(--accent-text);font-weight:600}
|
|
999
|
+
.rv-nudge{font-size:11.5px;color:var(--warn-text);flex-basis:100%}
|
|
1000
|
+
.rv-note{width:100%;box-sizing:border-box;margin-top:8px;border:1px solid var(--border);
|
|
1001
|
+
border-radius:7px;padding:7px 10px;background:var(--chip);color:var(--text-2);
|
|
1002
|
+
font-family:var(--sans);font-size:12px;outline:none}
|
|
1003
|
+
.rv-note:focus{border-color:var(--accent)}
|
|
1004
|
+
/* the success-signal counters — one quiet line, and an unmeasured number says so */
|
|
1005
|
+
.rv-stats{margin-top:6px;font-family:var(--mono);font-size:10.5px;color:var(--text-dim);line-height:1.7}
|
|
1006
|
+
.rv-stats i{font-style:normal;color:var(--warn-text)}
|
|
1007
|
+
.rv-stats .rv-bad{color:var(--bad);font-weight:600}
|