create-pmos 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/workspace/README.md +44 -12
- package/workspace/design-parity.js +358 -0
- package/workspace/lib/find-browser.js +60 -0
- package/workspace/lib/four-panel.js +345 -0
- package/workspace/lib/git-info.js +28 -1
- package/workspace/lib/mobile-nav.js +32 -2
- package/workspace/package-lock.json +29 -2
- package/workspace/package.json +13 -5
- package/workspace/public/index.html +79 -409
- package/workspace/public/pm/app.js +242 -0
- package/workspace/public/pm/data.js +877 -0
- package/workspace/public/pm/dom.js +116 -0
- package/workspace/public/pm/fixture.js +979 -0
- package/workspace/public/pm/mobile.css +200 -0
- package/workspace/public/pm/mobile.js +403 -0
- package/workspace/public/pm/modes.js +1076 -0
- package/workspace/public/pm/panel-detail.js +648 -0
- package/workspace/public/pm/panel-initiatives.js +426 -0
- package/workspace/public/pm/panel-repos.js +460 -0
- package/workspace/public/pm/panel-tabs.js +487 -0
- package/workspace/public/pm/state.js +164 -0
- package/workspace/public/pm/tab-doc.js +279 -0
- package/workspace/public/pm/tab-task.js +83 -0
- package/workspace/public/pm/tab-term.js +560 -0
- package/workspace/public/pm/tokens.css +73 -0
- package/workspace/public/vendor/ibm-plex-mono-500.woff2 +0 -0
- package/workspace/server.js +158 -8
- package/workspace/focus.test.js +0 -650
- package/workspace/handoff.test.js +0 -443
- package/workspace/profiles.test.js +0 -362
- package/workspace/public/app.js +0 -5560
- package/workspace/public/styles.css +0 -2258
- package/workspace/repo-scan.test.js +0 -327
|
@@ -1,428 +1,98 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html lang="en"
|
|
2
|
+
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
6
|
-
<title>PMOS
|
|
6
|
+
<title>PMOS workspace</title>
|
|
7
7
|
<!-- The terminal's face is preloaded, not left to be discovered. An @font-face is only fetched when
|
|
8
|
-
something first RENDERS in it, which for Plex Mono is about the same moment
|
|
8
|
+
something first RENDERS in it, which for Plex Mono is about the same moment a tab constructs
|
|
9
9
|
the first xterm — and xterm measures its cell from whatever face is resolved at construction.
|
|
10
10
|
Starting the fetch here, during head parse, means the real metrics are in place before the
|
|
11
|
-
first terminal exists.
|
|
11
|
+
first terminal exists. -->
|
|
12
12
|
<link rel="preload" href="/vendor/ibm-plex-mono-400.woff2" as="font" type="font/woff2" crossorigin />
|
|
13
13
|
<link rel="preload" href="/vendor/ibm-plex-mono-700.woff2" as="font" type="font/woff2" crossorigin />
|
|
14
14
|
<link rel="stylesheet" href="/vendor/xterm.css" />
|
|
15
|
-
<link rel="stylesheet" href="/
|
|
15
|
+
<link rel="stylesheet" href="/pm/tokens.css" />
|
|
16
|
+
<!-- The phone surface's stylesheet. It is a separate file from tokens.css on purpose: tokens.css is
|
|
17
|
+
a verbatim copy of the design prototype's token block and is diffed mechanically against it, so
|
|
18
|
+
a media query added there would read as a defect. Everything below the ≤760px breakpoint lives
|
|
19
|
+
here instead, and above the breakpoint this file declares nothing but `display:none`. -->
|
|
20
|
+
<link rel="stylesheet" href="/pm/mobile.css" />
|
|
21
|
+
<!-- xterm and its addons come straight from node_modules through the local server, as globals, so
|
|
22
|
+
the terminal tab can attach a real PTY. No CDN anywhere: this app renders, and works, offline. -->
|
|
23
|
+
<script src="/vendor/xterm.js"></script>
|
|
24
|
+
<script src="/vendor/addon-fit.js"></script>
|
|
25
|
+
<script src="/vendor/addon-web-links.js"></script>
|
|
26
|
+
<!-- ONE table, TWO consumers (W7). The words and glyphs a session is described with — working,
|
|
27
|
+
quiet, needs you, blocked — live in exactly one file, which the SERVER requires and the browser
|
|
28
|
+
loads here. A second copy would be free to drift, and the sidebar and the server would end up
|
|
29
|
+
holding different opinions about one session. Loaded before the module graph so a panel can
|
|
30
|
+
bind from window.PMOSSessionState rather than re-typing a word. -->
|
|
31
|
+
<script src="/lib/session-state.js"></script>
|
|
32
|
+
<!-- The phone's navigation reducer. DOM-free, loaded as a classic script (it is a UMD that sets
|
|
33
|
+
window.PMOSMobileNav) and required by test.js, so the whole screen domain is walkable in node
|
|
34
|
+
rather than only in a browser. pm/mobile.js reads it from the global; the module graph does not
|
|
35
|
+
import it, because a second copy of the reducer is exactly the drift it was extracted to stop. -->
|
|
36
|
+
<script src="/lib/mobile-nav.js"></script>
|
|
37
|
+
<link rel="preload" href="/vendor/ibm-plex-mono-500.woff2" as="font" type="font/woff2" crossorigin />
|
|
16
38
|
</head>
|
|
17
39
|
<body>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
</
|
|
33
|
-
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<input id="layNewName" type="text" placeholder="e.g. two agents + prd" autocomplete="off" spellcheck="false" maxlength="40" />
|
|
45
|
-
<div class="row"><button type="submit" class="go">create</button>
|
|
46
|
-
<button type="button" class="quiet" id="layNewCancel">cancel</button></div>
|
|
47
|
-
</form>
|
|
48
|
-
<button class="lay-add" id="layNewBtn">+ new layout from this arrangement</button>
|
|
49
|
-
<p class="lay-foot">Layouts belong to <b id="layScope">this repo</b>. Drag a pane by its ⠿ grip,
|
|
50
|
-
or an edge to resize — or use the buttons above. Whatever you change is kept.</p>
|
|
51
|
-
</div>
|
|
40
|
+
<!-- ── the phone surface (≤760px) ────────────────────────────────────────────────────────────────
|
|
41
|
+
Everything else on this page is built by pm/app.js; these two blocks are markup because they
|
|
42
|
+
are the phone's CHROME, and chrome that exists in the document is chrome a screen reader can
|
|
43
|
+
reach in source order — the top bar above the content, the destination nav below it.
|
|
44
|
+
|
|
45
|
+
`hidden` is the SHIPPED state of both. pm/mobile.js removes it only at ≤760px, so above the
|
|
46
|
+
breakpoint this is absent from the accessibility tree rather than merely invisible: a desktop
|
|
47
|
+
PM must not be able to tab into a second navigation system they cannot see. -->
|
|
48
|
+
|
|
49
|
+
<!-- The top bar: back · the screen's name · the ⌘K jump palette · theme. It sits ABOVE .body in
|
|
50
|
+
DOM order because it is the row above the content on screen, and putting it beside the bottom
|
|
51
|
+
nav would read the app backwards. -->
|
|
52
|
+
<div class="mob" id="mobShell" hidden>
|
|
53
|
+
<header class="mob-bar" id="mobBar">
|
|
54
|
+
<button class="mob-back" id="mobBack" type="button" hidden aria-label="Back">‹ back</button>
|
|
55
|
+
<span class="mob-brand" id="mobBrand" aria-hidden="true">◎</span>
|
|
56
|
+
<span class="mob-title" id="mobTitle"></span>
|
|
57
|
+
<!-- The jump palette lives HERE, not in the control bar below. Two reasons, and the second is
|
|
58
|
+
the binding one: it is not a terminal control key (every other button in the bar writes a
|
|
59
|
+
PTY sequence), and that bar is `overflow-x:auto` with seven 44px targets, so at 390px its
|
|
60
|
+
last child sits past the right edge — reachable only by a horizontal scroll nobody would
|
|
61
|
+
guess at. A control the PM cannot see is the hover-only trap in another costume. The
|
|
62
|
+
palette is a jump control for every destination, so it belongs in the bar that is always
|
|
63
|
+
on screen. -->
|
|
64
|
+
<button class="mob-pal" id="mobPalette" type="button" aria-label="Open the jump palette (⌘K)">⌘K</button>
|
|
65
|
+
<button class="mob-theme" id="mobTheme" type="button" aria-label="Toggle theme">◐</button>
|
|
52
66
|
</header>
|
|
67
|
+
</div>
|
|
53
68
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
<button class="mob-theme" id="mobTheme" aria-label="Toggle theme">theme</button>
|
|
70
|
-
</header>
|
|
71
|
-
</div>
|
|
72
|
-
|
|
73
|
-
<!-- The arrangement is COMPUTED from the current layout (lib/layout.js), not hard-coded here:
|
|
74
|
-
the sidebar, the terminal and the reader are grid areas whose columns/rows/areas are written
|
|
75
|
-
by applyLayout(). Each pane carries a ⠿ grip (move) and, where it has an outer edge, a
|
|
76
|
-
resize handle. DOM order is fixed; only the grid decides where a pane lands. -->
|
|
77
|
-
<div class="body" id="bodyGrid">
|
|
78
|
-
|
|
79
|
-
<!-- Collapsed-column rails. Shown INSTEAD of their column, in the same grid area, so the
|
|
80
|
-
track narrows to 36px rather than the column disappearing. Each keeps a real accessible
|
|
81
|
-
name; the rotated text is decoration over it, never the only label. -->
|
|
82
|
-
<button class="colrail" id="col1Rail" hidden aria-label="Show the initiatives column"><span id="col1RailLabel">initiatives ⟩</span></button>
|
|
83
|
-
<button class="colrail" id="col2Rail" hidden aria-label="Show the detail panel"><span id="col2RailLabel">initiative ⟩</span></button>
|
|
84
|
-
|
|
85
|
-
<!-- ONE tree (R1): repositories → their working trees, each labelled by the initiative on its
|
|
86
|
-
branch → that tree's sessions. It replaces the old `workspaces` + `running agents` pair,
|
|
87
|
-
which split a parent from its children across two panes and made the PM correlate a live
|
|
88
|
-
count against a separate list. The fold key stays `ws` so an arrangement saved before this
|
|
89
|
-
change still resolves; `sess` is retired and ignored (R13). -->
|
|
90
|
-
<nav class="side" id="side" aria-label="Repositories, initiatives and running agents">
|
|
91
|
-
<div class="edge" id="sideEdge" data-edge="side" role="separator" aria-label="Resize the sidebar" tabindex="0"></div>
|
|
92
|
-
|
|
93
|
-
<section class="pane" id="paneWs">
|
|
94
|
-
<div class="cap">
|
|
95
|
-
<span class="grip" id="sideGrip" data-pane="side" role="button" tabindex="0"
|
|
96
|
-
aria-label="Move the sidebar" title="Move this column">⠿</span>
|
|
97
|
-
<!-- R8: the pane states its structure. It was called `work` — the least informative word
|
|
98
|
-
available for a tree of repositories, their working trees, and their sessions. -->
|
|
99
|
-
<button class="fold" id="wsFold" data-fold="ws" aria-expanded="true" title="Hide the repositories, their working trees and their sessions"><span class="chev" aria-hidden="true">▾</span>repos & sessions</button>
|
|
100
|
-
<span class="ct" id="needCount"></span>
|
|
101
|
-
<button class="mini" id="addWsBtn" aria-expanded="false" title="Add a repository">+</button></div>
|
|
102
|
-
<div class="pane-body">
|
|
103
|
-
<!-- workspace-focus R1 — a FULL-WIDTH row above the mode menu, not a ninth cell in
|
|
104
|
-
#spMenu: the eighth cell there is the grid-squaring "hide" control and a ninth breaks
|
|
105
|
-
the two-row grid. Focus joins every registered repository, so it sits ABOVE the
|
|
106
|
-
product switcher, which scopes only what is below it. Visible in every mode.
|
|
107
|
-
The badge prints badge.text, never a count — a digit would be a claim about the whole
|
|
108
|
-
world, and the world includes reads that failed. -->
|
|
109
|
-
<button class="focusbtn" id="focusBtn" aria-expanded="false" aria-controls="focusPanel"
|
|
110
|
-
title="What only you can unblock, across every repository">
|
|
111
|
-
<span class="g" aria-hidden="true">◎</span><span class="l">focus</span>
|
|
112
|
-
<span class="ct" id="focusBadge"></span>
|
|
113
|
-
</button>
|
|
114
|
-
<!-- workspace-product-switcher — one button per PRODUCT (a distinct repo_key), not per
|
|
115
|
-
working tree: a product with four trees was four rows of the same name, and the tree
|
|
116
|
-
is a detail of where you stand, not of what you are working on. Buttons WRAP so a
|
|
117
|
-
full product name always shows; a horizontally scrolling strip is not acceptable —
|
|
118
|
-
it hides products off-screen with nothing on screen saying they exist. Product-scoped,
|
|
119
|
-
so app.js hides it outside `product` mode. -->
|
|
120
|
-
<div class="psw" id="prodSwitch" role="group" aria-label="Products" hidden>
|
|
121
|
-
<div class="psw-list" id="pswList"></div>
|
|
122
|
-
<!-- opens the SHIPPED #addWsForm — the same node #addWsBtn opens. Two controls onto one
|
|
123
|
-
form is fine; two forms that both add a repository is not. -->
|
|
124
|
-
<button class="psw-add" id="pswAdd" aria-expanded="false" aria-controls="addWsForm"
|
|
125
|
-
aria-label="Add a repository" title="Add a repository">+</button>
|
|
126
|
-
</div>
|
|
127
|
-
<!-- workspace-pm-spine — the destinations sit ON TOP OF the initiative list they scope,
|
|
128
|
-
not in a separate nav column. Everything here is PRODUCT-SPECIFIC — knowledge and
|
|
129
|
-
scheduled tasks belong to the repository in view, not to the app — so the menu
|
|
130
|
-
lives inside the product's own column, where its scope is unambiguous. -->
|
|
131
|
-
<div class="spmenu" id="spMenu" role="toolbar" aria-label="Views"></div>
|
|
132
|
-
<!-- One search box over everything the column can hold, not a per-mode filter. It
|
|
133
|
-
narrows the current mode's list AND surfaces matches from elsewhere (artifacts,
|
|
134
|
-
sessions) underneath, so a name you half-remember is findable without first
|
|
135
|
-
knowing which mode it lives in. ⌘K focuses it. -->
|
|
136
|
-
<div class="spfind-wrap">
|
|
137
|
-
<div class="spfind">
|
|
138
|
-
<span class="g" aria-hidden="true">⌕</span>
|
|
139
|
-
<input id="spFind" type="search" placeholder="search everything…" aria-label="Search initiatives, artifacts and sessions" autocomplete="off" />
|
|
140
|
-
</div>
|
|
141
|
-
</div>
|
|
142
|
-
<button class="newsess" id="newBtn" aria-expanded="false"><i>+</i> new session — run an agent here</button>
|
|
143
|
-
<form class="miniform" id="newForm" hidden>
|
|
144
|
-
<!-- R5: "here" gets a visible referent. The composer has always acted on the selected
|
|
145
|
-
tree; with every repository on screen at once, nothing said which that was. -->
|
|
146
|
-
<p class="where" id="newWhere"></p>
|
|
147
|
-
<label class="lbl" for="newName">name this session</label>
|
|
148
|
-
<input id="newName" type="text" placeholder="e.g. jira-sync build" aria-label="Session name" autocomplete="off" spellcheck="false" />
|
|
149
|
-
<div class="seg" role="radiogroup" aria-label="Agent to run" id="newAgents">
|
|
150
|
-
<button type="button" data-agent="shell" role="radio" aria-checked="true">shell</button>
|
|
151
|
-
<button type="button" data-agent="claude" role="radio" aria-checked="false">claude</button>
|
|
152
|
-
<button type="button" data-agent="codex" role="radio" aria-checked="false">codex</button>
|
|
153
|
-
</div>
|
|
154
|
-
<!-- workspace-profiles — the chips are written from the descriptors actually read off
|
|
155
|
-
disk. NOTHING is hard-coded here: a picker that renders two chips on a machine with
|
|
156
|
-
no descriptors makes a choice that applies nothing. An empty group renders whatever
|
|
157
|
-
readProfiles said — absent, or a successful read of nothing. -->
|
|
158
|
-
<div class="prof" id="newProfile" role="radiogroup" aria-label="Profile to start this session under"></div>
|
|
159
|
-
<p class="prof-note" id="newProfileNote">Chosen at start and never changed afterwards — a process's
|
|
160
|
-
environment is fixed when it is created. A session started on one profile does not get the MCP
|
|
161
|
-
servers another profile declares.</p>
|
|
162
|
-
<div class="brwrap" id="newBranchWrap" hidden>
|
|
163
|
-
<label class="lbl" for="newBranch">branch <span class="opt">— optional</span></label>
|
|
164
|
-
<select id="newBranch" aria-label="Branch to run this session on"></select>
|
|
165
|
-
<input id="newBranchName" type="text" hidden placeholder="new-branch-name" aria-label="New branch name" autocomplete="off" spellcheck="false" />
|
|
166
|
-
<label class="chk" id="newIsolateWrap" hidden>
|
|
167
|
-
<input type="checkbox" id="newIsolate" checked />
|
|
168
|
-
<span>give it its own folder — leave running sessions alone</span>
|
|
169
|
-
</label>
|
|
170
|
-
<p class="hint" id="newBranchHint"></p>
|
|
171
|
-
</div>
|
|
172
|
-
<p class="hint" id="newNoGit" hidden>no git in this folder — nothing to branch, the session just starts here</p>
|
|
173
|
-
<div class="row">
|
|
174
|
-
<button type="submit" class="go">start session</button>
|
|
175
|
-
<button type="button" class="quiet" id="newCancel">cancel</button>
|
|
176
|
-
</div>
|
|
177
|
-
</form>
|
|
178
|
-
<div id="tree"></div>
|
|
179
|
-
<!-- workspace-focus — a SIBLING of #tree, not a replacement for it: focus is a second
|
|
180
|
-
reading of the same repositories, so leaving the tree in place is what lets the PM
|
|
181
|
-
check the queue against the structure it was derived from. app.js writes six sections
|
|
182
|
-
(#fcIntro #fcObjectives #fcQueue #fcHandoffs #fcDelegated #fcUpkeep) into #focusBody,
|
|
183
|
-
always all six — a section that is absent says nothing, and "nothing waiting on you"
|
|
184
|
-
is a fact worth printing. -->
|
|
185
|
-
<section class="fc" id="focusPanel" hidden aria-label="Focus — what only you can unblock">
|
|
186
|
-
<div class="fc-body" id="focusBody"></div>
|
|
187
|
-
</section>
|
|
188
|
-
<!-- workspace-repo-ingest — the two sources are stated in the affordance, before the
|
|
189
|
-
click, because they do different things: a folder is REGISTERED (read where it lies)
|
|
190
|
-
and a GitHub repo is INGESTED (cloned, scanned, proposed). Both consequence lines are
|
|
191
|
-
always present, one under each option, so the difference is never something you learn
|
|
192
|
-
afterwards from what happened. -->
|
|
193
|
-
<form class="miniform" id="addWsForm" hidden>
|
|
194
|
-
<div class="ing-src seg" role="radiogroup" aria-label="Where this repository comes from" id="addWsSource">
|
|
195
|
-
<button type="button" data-src="local" role="radio" aria-checked="true">local folder</button>
|
|
196
|
-
<button type="button" data-src="github" role="radio" aria-checked="false">GitHub</button>
|
|
197
|
-
</div>
|
|
198
|
-
|
|
199
|
-
<div class="ing-opt" id="addWsLocal">
|
|
200
|
-
<button type="button" class="pick" id="pickFolderBtn">📁 Choose folder…</button>
|
|
201
|
-
<label class="lbl" for="addWsPath">or type a path</label>
|
|
202
|
-
<input id="addWsPath" type="text" placeholder="/path/to/another/repo" aria-label="Path to a repository" autocomplete="off" spellcheck="false" />
|
|
203
|
-
<p class="ing-note" id="addWsLocalNote">Adding a folder registers it. Nothing is imported from a
|
|
204
|
-
bare folder — its own records are read and shown, but no PMOS state is proposed.</p>
|
|
205
|
-
</div>
|
|
206
|
-
|
|
207
|
-
<div class="ing-opt" id="addWsGithub" hidden>
|
|
208
|
-
<label class="lbl" for="addWsRepo">repository</label>
|
|
209
|
-
<input id="addWsRepo" type="text" placeholder="owner/name or https://github.com/owner/name" aria-label="Repository to ingest — owner/name or a GitHub URL" autocomplete="off" spellcheck="false" />
|
|
210
|
-
<p class="ing-note" id="addWsGithubNote">Four stages run: clone · scan (read-only) · review ·
|
|
211
|
-
accept. The scan proposes; nothing is written into the repository until you confirm one item.</p>
|
|
212
|
-
</div>
|
|
213
|
-
|
|
214
|
-
<div class="row">
|
|
215
|
-
<button type="submit" class="go">add</button>
|
|
216
|
-
<button type="button" class="quiet" id="addWsCancel">cancel</button>
|
|
217
|
-
</div>
|
|
218
|
-
<p class="hint">or just run <code>pmos</code> inside that repo</p>
|
|
219
|
-
</form>
|
|
220
|
-
<!-- Worktrees the workspace made are folders the PM has to live with, so the app that
|
|
221
|
-
creates them is the app that clears them. Hidden entirely until there is one. -->
|
|
222
|
-
<details class="wtbox" id="wtBox" hidden>
|
|
223
|
-
<summary id="wtSummary">worktrees</summary>
|
|
224
|
-
<div id="wtList"></div>
|
|
225
|
-
</details>
|
|
226
|
-
</div>
|
|
227
|
-
</section>
|
|
228
|
-
|
|
229
|
-
</nav>
|
|
230
|
-
|
|
231
|
-
<main class="term-col" aria-label="Sessions and terminal">
|
|
232
|
-
<!-- Terminal GROUPS (R6). One group is the shipped behaviour; `split` shows a second beside
|
|
233
|
-
it, each with its own tab strip and its own visible session. The #needs strip is NOT
|
|
234
|
-
duplicated — it belongs to the column, not to a group. -->
|
|
235
|
-
<div class="groups" id="groups" data-split="off">
|
|
236
|
-
<section class="group" id="group0" data-g="0">
|
|
237
|
-
<div class="stabs" role="tablist" aria-label="Open sessions" id="stabs"></div>
|
|
238
|
-
<!-- workspace-profiles R6 — which profile this pane was started under, as WORKSPACE
|
|
239
|
-
chrome between the tab strip and the terminal, never written into the pane. Writing
|
|
240
|
-
into a tmux pane sends input to the agent; anything appended to the ring buffer is
|
|
241
|
-
erased on the next reattach; and a line the workspace injects into scrollback is
|
|
242
|
-
indistinguishable from one the agent printed, which defeats a line whose whole
|
|
243
|
-
purpose is provenance. The sentence is computed server-side and printed verbatim. -->
|
|
244
|
-
<div class="profline" id="profLine0" role="status"></div>
|
|
245
|
-
<div class="termhost" id="termhost">
|
|
246
|
-
<!-- INSIDE termhost (position:relative): the overlay covers only the terminal area,
|
|
247
|
-
never the sidebar/tabs/needs — those must stay usable while a session is exited. -->
|
|
248
|
-
<div class="exited-overlay" id="exited">
|
|
249
|
-
<div class="card">
|
|
250
|
-
<p id="exitedMsg">This session has exited.</p>
|
|
251
|
-
<div class="row">
|
|
252
|
-
<button id="respawnBtn">↻ respawn</button>
|
|
253
|
-
<button id="removeBtn" class="quiet">✕ remove session</button>
|
|
254
|
-
</div>
|
|
255
|
-
</div>
|
|
256
|
-
</div>
|
|
257
|
-
<div class="empty-overlay" id="noSess">
|
|
258
|
-
<div class="card">
|
|
259
|
-
<p>No sessions in <b id="noSessWs">this workspace</b> yet.</p>
|
|
260
|
-
<p class="reason">+ new session runs an agent here — it starts in this repo's root.</p>
|
|
261
|
-
</div>
|
|
262
|
-
</div>
|
|
263
|
-
</div>
|
|
264
|
-
</section>
|
|
265
|
-
<section class="group" id="group1" data-g="1" hidden>
|
|
266
|
-
<div class="stabs" role="tablist" aria-label="Open sessions, second group" id="stabs1"></div>
|
|
267
|
-
<!-- the second group carries its own line: two panes side by side may be two profiles -->
|
|
268
|
-
<div class="profline" id="profLine1" role="status"></div>
|
|
269
|
-
<div class="termhost" id="termhost1">
|
|
270
|
-
<!-- a split with nothing to put in it offers the next step, never an empty black box -->
|
|
271
|
-
<div class="empty-overlay" id="noSess1">
|
|
272
|
-
<div class="card">
|
|
273
|
-
<p>Nothing else to show here yet.</p>
|
|
274
|
-
<p class="reason">Start a second session and it appears in this group.</p>
|
|
275
|
-
<div class="row"><button id="newHere1">+ new session</button>
|
|
276
|
-
<button id="unsplitBtn" class="quiet">✕ close this group</button></div>
|
|
277
|
-
</div>
|
|
278
|
-
</div>
|
|
279
|
-
</div>
|
|
280
|
-
</section>
|
|
281
|
-
</div>
|
|
282
|
-
<div class="needs" id="needs" role="status" style="display:none"></div>
|
|
283
|
-
|
|
284
|
-
<!-- Scratchpad: a collapsible drawer at the BOTTOM of the terminal column. Folded it is a thin
|
|
285
|
-
bar; open it takes a bounded slice below the terminal — it never overlays the tabs, the
|
|
286
|
-
terminal, or the document rail. Persists PER TAB: each session keeps its own staging
|
|
287
|
-
text, and the label below says which tab you are staging for. -->
|
|
288
|
-
|
|
289
|
-
<!-- workspace-viewer-column — the viewer. Documents, images and summoned surfaces are tabs in
|
|
290
|
-
THIS column now, beside the terminals, rather than a pane inside the rail. It is one pane
|
|
291
|
-
spanning the group row rather than one per group: a session belongs to a group because two
|
|
292
|
-
agents genuinely run side by side, and a document has no such property — giving it a group
|
|
293
|
-
would invent an affinity the object does not have. When a viewer tab is active it covers
|
|
294
|
-
the groups; un-selecting it returns both groups exactly as they were. -->
|
|
295
|
-
<section class="viewpane" id="viewPane" hidden aria-label="Document viewer">
|
|
296
|
-
<div class="cap">
|
|
297
|
-
<span class="crumb" id="crumb"></span>
|
|
298
|
-
<button class="mini docrev" id="docEdit" hidden title="Edit this document">edit</button>
|
|
299
|
-
<button class="mini docrev" id="docSave" hidden title="Save (⌘S)">save</button>
|
|
300
|
-
<button class="mini docrev" id="docCancel" hidden title="Discard changes">cancel</button>
|
|
301
|
-
<button class="mini docrev" id="docHistory" hidden title="Changes you have made to this document">history</button>
|
|
302
|
-
<button class="x" id="closeDoc" aria-label="Close document">✕</button>
|
|
303
|
-
</div>
|
|
304
|
-
<div class="pane-body doc-body" id="docBody" tabindex="0"></div>
|
|
305
|
-
</section>
|
|
306
|
-
|
|
307
|
-
<section class="scratch" id="scratch" data-open="false" aria-label="Scratchpad">
|
|
308
|
-
<div class="scratch-head">
|
|
309
|
-
<button type="button" class="scratch-toggle" id="scratchToggle" aria-expanded="false" aria-controls="scratchText">
|
|
310
|
-
<span class="chev" aria-hidden="true">▴</span> scratchpad
|
|
311
|
-
</button>
|
|
312
|
-
<span class="scratch-who" id="scratchWho"></span>
|
|
313
|
-
<span class="scratch-status" id="scratchStatus" role="status"></span>
|
|
314
|
-
<span class="scratch-actions">
|
|
315
|
-
<button type="button" class="mini" id="scratchCopy" title="Copy to clipboard">copy</button>
|
|
316
|
-
<button type="button" class="mini" id="scratchSend" title="Paste into the active terminal (does not press enter)">→ terminal</button>
|
|
317
|
-
</span>
|
|
318
|
-
</div>
|
|
319
|
-
<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>
|
|
320
|
-
</section>
|
|
321
|
-
</main>
|
|
322
|
-
|
|
323
|
-
<aside class="rail" id="rail" aria-label="Library and document">
|
|
324
|
-
<div class="railgrip edge" id="railGrip" data-edge="read" role="separator" aria-label="Resize the reader" tabindex="0"></div>
|
|
325
|
-
<section class="pane lib-pane" id="paneLib">
|
|
326
|
-
<div class="cap">
|
|
327
|
-
<span class="grip" id="readGrip" data-pane="reader" role="button" tabindex="0"
|
|
328
|
-
aria-label="Move the reader" title="Move this pane">⠿</span>
|
|
329
|
-
<button class="fold" id="libFold" aria-expanded="true" title="Hide the initiative panel"><span class="chev" aria-hidden="true">▾</span>initiative</button>
|
|
330
|
-
<!-- R7 — the four standing lenses are gone. What the rail shows follows the SELECTION:
|
|
331
|
-
a repository group gives repo detail (the folder map, its schedules, its history), a
|
|
332
|
-
tree row gives that initiative's dossier. The views below are the sub-views of
|
|
333
|
-
whichever of those two is current, rendered by renderLensTabs(). -->
|
|
334
|
-
<span class="lens" role="tablist" aria-label="Detail view" id="lensTabs"></span>
|
|
335
|
-
<span class="reading" id="libWho" title="the repository these documents come from"></span>
|
|
336
|
-
<button class="mini librefresh" id="libRefresh" title="Rescan the repository for new files & folders" aria-label="Refresh the library">↻</button>
|
|
337
|
-
<button class="mini" id="col2Hide" title="Collapse this column" aria-label="Collapse the detail panel">⟨</button>
|
|
338
|
-
</div>
|
|
339
|
-
<div class="pane-body">
|
|
340
|
-
<input class="libfind" id="libFind" type="search" placeholder="filter the library…" aria-label="Filter the library" autocomplete="off" />
|
|
341
|
-
<div id="library"></div>
|
|
342
|
-
</div>
|
|
343
|
-
</section>
|
|
344
|
-
|
|
345
|
-
</aside>
|
|
346
|
-
|
|
347
|
-
<!-- Drop targets, shown only while a ⠿ grip is being dragged. They are an ACCELERATOR: every
|
|
348
|
-
position they offer is also reachable from the layout menu, so the drag is never the only
|
|
349
|
-
path to an arrangement (R12). -->
|
|
350
|
-
<div class="drop" id="drop" aria-hidden="true">
|
|
351
|
-
<div class="dz" data-z="left">left</div>
|
|
352
|
-
<div class="dz" data-z="right">right</div>
|
|
353
|
-
<div class="dz" data-z="off">hide</div>
|
|
354
|
-
</div>
|
|
355
|
-
</div>
|
|
356
|
-
|
|
357
|
-
<!-- ⌘K jump palette (client-side; no backend) -->
|
|
358
|
-
<div class="palette" id="palette" role="dialog" aria-modal="true" aria-label="Jump" style="display:none">
|
|
359
|
-
<div class="palette-box">
|
|
360
|
-
<input id="palInput" type="text" placeholder="Summon a view (initiatives, roadmap, okr, report) or jump to a session, initiative, document…" aria-label="Summon a view or jump to a workspace, session, initiative or document" autocomplete="off" />
|
|
361
|
-
<div class="palette-list" id="palList" role="listbox"></div>
|
|
362
|
-
</div>
|
|
363
|
-
</div>
|
|
364
|
-
|
|
365
|
-
<!-- in-app folder browser — the stand-in when the machine has no native chooser -->
|
|
366
|
-
<div class="palette" id="fsPicker" role="dialog" aria-modal="true" aria-label="Choose a repository" style="display:none">
|
|
367
|
-
<div class="palette-box">
|
|
368
|
-
<div class="fs-head">
|
|
369
|
-
<button class="fs-up" id="fsUp" aria-label="Parent folder">↑</button>
|
|
370
|
-
<span class="fs-crumb" id="fsCrumb"></span>
|
|
371
|
-
<button class="fs-use" id="fsUse">use this folder</button>
|
|
372
|
-
<button class="x" id="fsClose" aria-label="Cancel">✕</button>
|
|
373
|
-
</div>
|
|
374
|
-
<div class="palette-list" id="fsList" role="listbox"></div>
|
|
375
|
-
</div>
|
|
376
|
-
</div>
|
|
377
|
-
|
|
378
|
-
<!-- Mobile only (≤760px): a terminal control-key accessory bar (phones lack these keys) over the
|
|
379
|
-
destination nav. Hidden on desktop via CSS. The accessory bar keeps its ids and its behaviour
|
|
380
|
-
— it is a deliberate divergence from the prototype, whose terminal screen is a text input over
|
|
381
|
-
sample lines; this one is driving a real PTY, and the control keys are the thing the current
|
|
382
|
-
phone mode does well. No `data-view` is hard-coded: the screen at load is a destination root,
|
|
383
|
-
not the terminal, and app.js sets the attribute from the screen it actually applied. -->
|
|
384
|
-
<div class="mobmenu" id="mobMenu">
|
|
385
|
-
<div class="ctrlbar" id="ctrlBar" role="toolbar" aria-label="Terminal control keys">
|
|
386
|
-
<button type="button" data-seq="esc" aria-label="Escape"><span class="lbl">esc</span></button>
|
|
387
|
-
<button type="button" data-seq="tab" aria-label="Tab"><span class="lbl">tab</span></button>
|
|
388
|
-
<button type="button" id="ctrlKey" aria-pressed="false" aria-label="Control modifier (one-shot)"><span class="lbl">ctrl</span></button>
|
|
389
|
-
<button type="button" data-seq="left" aria-label="Left arrow">←</button>
|
|
390
|
-
<button type="button" data-seq="up" aria-label="Up arrow">↑</button>
|
|
391
|
-
<button type="button" data-seq="down" aria-label="Down arrow">↓</button>
|
|
392
|
-
<button type="button" data-seq="right" aria-label="Right arrow">→</button>
|
|
393
|
-
</div>
|
|
394
|
-
<!-- Five destinations, replacing the three-column switcher. It is a <nav> of buttons carrying
|
|
395
|
-
aria-current, NOT the tablist it used to be: that markup declared role="tablist" over
|
|
396
|
-
role="tab" children with no tabpanel and no aria-controls anywhere — a promise the DOM did
|
|
397
|
-
not keep. Selection here resets a destination to its root; depth is pushed on top and the
|
|
398
|
-
back control in #mobBar pops it. -->
|
|
399
|
-
<nav class="mobnav" id="mobNav" aria-label="Sections">
|
|
400
|
-
<button type="button" class="mobnav-b" data-dest="board" aria-current="page">board</button>
|
|
401
|
-
<button type="button" class="mobnav-b" data-dest="scheduled">scheduled</button>
|
|
402
|
-
<button type="button" class="mobnav-b" data-dest="agents">agents</button>
|
|
403
|
-
<button type="button" class="mobnav-b" data-dest="memory">memory</button>
|
|
404
|
-
<button type="button" class="mobnav-b" data-dest="more">more</button>
|
|
405
|
-
</nav>
|
|
69
|
+
<!-- The bottom block: a terminal control-key accessory bar (terminal screen only — phones have no
|
|
70
|
+
Esc/Tab/Ctrl/arrows) over the five-destination nav. The nav is a <nav> of buttons carrying
|
|
71
|
+
aria-current, NOT a tablist: the retired markup declared role="tablist" over role="tab"
|
|
72
|
+
children with no tabpanel and no aria-controls anywhere, which is a promise the DOM did not
|
|
73
|
+
keep. Selecting a destination resets it to its root; depth is pushed on top of it and #mobBack
|
|
74
|
+
pops one screen. The glyphs are the mobile prototype's own (◧ ◔ ◐ ◒ ☰). -->
|
|
75
|
+
<div class="mobmenu" id="mobMenu" hidden>
|
|
76
|
+
<div class="ctrlbar" id="ctrlBar" role="toolbar" aria-label="Terminal control keys">
|
|
77
|
+
<button type="button" data-seq="esc" aria-label="Escape"><span class="lbl">esc</span></button>
|
|
78
|
+
<button type="button" data-seq="tab" aria-label="Tab"><span class="lbl">tab</span></button>
|
|
79
|
+
<button type="button" id="ctrlKey" aria-pressed="false" aria-label="Control modifier (one-shot)"><span class="lbl">ctrl</span></button>
|
|
80
|
+
<button type="button" data-seq="left" aria-label="Left arrow">←</button>
|
|
81
|
+
<button type="button" data-seq="up" aria-label="Up arrow">↑</button>
|
|
82
|
+
<button type="button" data-seq="down" aria-label="Down arrow">↓</button>
|
|
83
|
+
<button type="button" data-seq="right" aria-label="Right arrow">→</button>
|
|
406
84
|
</div>
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
<span
|
|
410
|
-
<
|
|
411
|
-
|
|
85
|
+
<nav class="mobnav" id="mobNav" aria-label="Sections">
|
|
86
|
+
<button type="button" class="mobnav-b" data-dest="board" aria-current="page"><span class="g" aria-hidden="true">◧</span><span class="l">board</span></button>
|
|
87
|
+
<button type="button" class="mobnav-b" data-dest="scheduled" aria-current="false"><span class="g" aria-hidden="true">◔</span><span class="l">scheduled</span></button>
|
|
88
|
+
<button type="button" class="mobnav-b" data-dest="agents" aria-current="false"><span class="g" aria-hidden="true">◐</span><span class="l">agents</span></button>
|
|
89
|
+
<button type="button" class="mobnav-b" data-dest="memory" aria-current="false"><span class="g" aria-hidden="true">◒</span><span class="l">memory</span></button>
|
|
90
|
+
<button type="button" class="mobnav-b" data-dest="more" aria-current="false"><span class="g" aria-hidden="true">☰</span><span class="l">more</span></button>
|
|
91
|
+
</nav>
|
|
412
92
|
</div>
|
|
413
93
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
<script src="/
|
|
417
|
-
<script src="/lib/preview-kind.js"></script>
|
|
418
|
-
<script src="/lib/layout.js"></script>
|
|
419
|
-
<!-- the phone's navigation reducer: DOM-free, and required by test.js so the whole screen
|
|
420
|
-
domain is walkable in node rather than only in a browser -->
|
|
421
|
-
<script src="/lib/mobile-nav.js"></script>
|
|
422
|
-
<script src="/lib/session-state.js"></script>
|
|
423
|
-
<!-- The editor computes its diff with the SAME module the server saves through, so what you see
|
|
424
|
-
while editing and what the server writes can never disagree. -->
|
|
425
|
-
<script src="/lib/apply-change.js"></script>
|
|
426
|
-
<script src="/app.js"></script>
|
|
94
|
+
<!-- The rest of the surface is built by pm/app.js: the root element carries data-pm-theme and is
|
|
95
|
+
appended here at boot. Nothing is server-rendered, so there is no markup here to keep in sync. -->
|
|
96
|
+
<script type="module" src="/pm/app.js"></script>
|
|
427
97
|
</body>
|
|
428
98
|
</html>
|