-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
302 lines (275 loc) · 15.2 KB
/
Copy pathindex.html
File metadata and controls
302 lines (275 loc) · 15.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Pullcept</title>
<script type="module" src="/src/main.ts" defer></script>
<link rel="stylesheet" href="/src/styles.css" />
</head>
<body>
<div id="app">
<header id="titlebar">
<span class="title">Pullcept</span>
<!-- The roster used to be echoed here as one line. It is the
participant panel's list now: two renderings of one roster is one
surface too many, and the panel is always on screen. -->
<span class="spacer"></span>
<!-- The person at this screen used to declare their name and colour
here, every session, into fields that saved as they lost focus.
They are an account now (#59), so both live where every other
account's do: in the dialog their row in the participant list
opens. Two surfaces editing one pair is the split this issue is
closing, in miniature. -->
<!-- The conversation's text size. The keyboard is the main way in
(Ctrl + = / - / 0); this is here because of what the default is.
The default is deliberately the size that was called too large, so
what an untouched screen shows is the thing being complained
about, and a control reachable only by keys would leave that
fixable only by whoever already knew the keys.
It reaches the conversation's words, wherever they are: the room,
and the textarea they are typed into (#81). The panel, the
diagnostics pane, the terminal and the composer's own controls
keep their own sizes — see the `#room` block in src/styles.css for
why the scope holds. -->
<select
id="room-font-size"
aria-label="会話の文字サイズ"
title="会話の文字サイズ(Ctrl + = / - / 0)"
></select>
<button id="toggle-diagnostics" type="button" aria-expanded="false">端末</button>
</header>
<div id="body">
<div id="conversation">
<main id="room" aria-live="polite"></main>
<!-- Diagnostics, not the conversation surface. The room shows what
was said; this shows the CLI that was launched, as it actually
renders. Nothing here is read as a message: the room's lines come
from the channel and from say_to_room, never from this output.
Only the terminal is left here. The values that are read rather
than watched — the socket, the session, how it was launched —
moved to the participant panel, where they are on screen without
opening anything. -->
<!-- One frame, holding a terminal per running account. All but the
selected one are hidden rather than closed, so a session keeps
running and keeps filling its own scrollback while another is
being watched (#57).
A window, since #68: it carries a header of its own and the
rounding is on this element rather than on the terminal inside
it, so the frame that is folded and unfolded is one shape. -->
<aside id="diagnostics" hidden>
<!-- The window's header. Its name, then three things in the order
they are reached for: which terminal is on the glass, how large
it is drawn, and folding the whole pane away.
No fill of its own. It is the same ground as the rest of the
window, and what says it is a header is where it sits and what
is in it (Master, 実機, 2026-08-24).
The tabs and the participant rows are two renderings of one
selection, and they move together — either one puts the same
terminal on the glass. That is the duplication #59 removed from
the roster, chosen deliberately here (#68): a tab strip answers
"which terminals are open" at the pane being looked at, which
the rows answer only by being read alongside it.
✕ folds the pane; it does not end anything. The session under
the terminal keeps running and its tab comes back with the
pane. Ending a session is 終了 on the row, and it stays there —
one click from a control that merely changes what is showing is
how a slip ends a session mid-answer (#57 / #71). -->
<div id="terminal-head">
<!-- The window's name, at the left where a window's name goes. The
tabs follow it, so the strip reads as this window's tabs; it
is also what the header says about itself when nothing is
running and there are no tabs to show. Same word as the title
bar's button, which is the control that opens this. -->
<span class="pane-title">端末</span>
<!-- `group`, not `tablist`. A tab strip that carries a close
control on some of its tabs is two controls per tab, and the
tablist pattern has one; the rows in the panel already mark
the same selection with `aria-pressed`, and matching them is
worth more than a role whose keyboard contract this does not
implement. -->
<div id="terminal-tabs" role="group" aria-label="端末のタブ"></div>
<!-- The terminal's own size, and only the terminal's. It is one of
three independent size axes — the conversation (#60), this,
and the whole UI (#66) — and none of the three is expressed
relative to another. This one moves xterm's `fontSize`, which
is what the CLI's columns and rows are computed from, so a
change here re-fits the pane and tells the session. -->
<select
id="terminal-font-size"
aria-label="端末の文字サイズ"
title="端末の文字サイズ"
></select>
<button id="diagnostics-close" type="button" aria-label="端末を閉じる" title="端末を閉じる">
✕
</button>
</div>
<div id="terminal" aria-label="セッションの端末"></div>
</aside>
<footer id="composer">
<!-- The addressee is optional. Leaving it on 全体 is a room-wide
utterance; who answers one of those is the participants'
judgment, not the room's. The room delivers every utterance to
everyone either way, and anyone in the roster can be named —
people and sessions alike. -->
<label class="to">
宛先
<select id="to-select">
<option value="">全体</option>
</select>
</label>
<textarea
id="input"
rows="2"
placeholder="部屋に発言する(Enter で送信 / Shift+Enter で改行)"
></textarea>
<button id="send" type="button">送信</button>
</footer>
<div id="status" role="status"></div>
</div>
<!-- Who is here, and what the session under a row was launched from.
The values were all reachable before this panel existed; what was
missing was a place that shows them without being opened.
**One list.** The room's roster and this app's accounts, joined on
the account id the room now carries (#59). It was two lists — a
roster and a terminal list — because a `Participant` had no account
id and the two halves could only have been matched by name, which
#40 and #53 ruled out (#57). With the id on the wire the two
answers are one row: who is here, and what can be done with them.
Grouped by the kind declared when the account was made. An account
that is not running is still someone, so it is listed dimmed rather
than left out (#53); it is still not offered as an addressee,
because a name that cannot be reached is not one worth naming.
The row carries the session's whole lifecycle. 開始 was on a row of
its own above the conversation until #62, which put the two ends of
one thing on two surfaces; 終了 had already moved here (#57). The
row is two fixed columns after the name — the lifecycle, then 編集 —
so neither moves when a session starts or ends. -->
<aside id="participants" aria-label="アカウント">
<div class="panel-head">
<h2 class="panel-title">アカウント</h2>
<!-- Making an account belongs beside the list an account appears
in, not on the row that launches one. It opens an empty form
and creates nothing until that form is decided (#59). -->
<button id="account-new" type="button" title="アカウントを追加">+</button>
</div>
<!-- Seeded, because the roster only arrives once the room answers:
a blank strip there would read as "nobody" before anyone has
been asked. -->
<ul id="roster" class="roster">
<li class="empty">参加者なし</li>
</ul>
<!-- What the terminal on the glass was launched from. The values
follow the row that is selected above, so they answer for the
pane being looked at rather than for whichever session started
last. 部屋ソケット is the exception and is the room's own. -->
<dl class="facts">
<dt>部屋ソケット</dt>
<dd id="socket-state">確認中</dd>
<dt>セッション</dt>
<dd id="session-state">未起動</dd>
<dt>接続方法</dt>
<dd id="session-transport">—</dd>
<dt>起動コマンド</dt>
<dd id="session-command">—</dd>
<dt>作業ディレクトリ</dt>
<dd id="session-dir" class="path">—</dd>
<dt>開始時刻</dt>
<dd id="session-started">—</dd>
<dt>ウィンドウ</dt>
<dd id="session-window">—</dd>
</dl>
</aside>
</div>
<!-- The account form. In this webview rather than an OS window: it is a
step inside one act, and a second window is a thing to find, move and
lose behind the app.
It holds a draft. Nothing here reaches the account list until 決定 —
the fields used to save as they lost focus, so + created an account
at the moment it was pressed and there was no moment of deciding and
no way back (#59). 取消 leaves nothing behind, for a new account and
for an edit alike. -->
<dialog id="account-dialog" aria-labelledby="account-dialog-title">
<form id="account-form" method="dialog">
<h2 id="account-dialog-title">アカウント</h2>
<label class="field">
名前
<input id="dialog-name" type="text" spellcheck="false" />
</label>
<!-- Declared here, and only here. The room sees what kind of
connection someone arrived on, which says nothing about whether
they are a person — a human joining from another client arrives
the same way a session does (#59). -->
<label class="field">
種別
<select id="dialog-kind">
<option value="user">user(人間)</option>
<option value="ai">AI(セッション)</option>
</select>
</label>
<label class="field">
色
<select id="dialog-hue"></select>
</label>
<!-- How to start a session. Shown for an AI account only: a person is
not launched, and there is no command under them to write options
for. -->
<div id="dialog-launch" class="launch">
<label class="field">
作業ディレクトリ
<input id="dialog-cwd" type="text" spellcheck="false" />
</label>
<label class="field">
起動オプション
<input
id="dialog-options"
type="text"
spellcheck="false"
placeholder="例: --dangerously-skip-permissions"
/>
</label>
<!-- What will actually run. The app merges its own channel entry
into whatever is typed above, so the line written here is not
the line that launches; showing the result is cheaper than
explaining the merge. It sits beside the field it explains. -->
<span id="dialog-preview" class="preview"></span>
</div>
<p id="dialog-error" class="error" role="alert"></p>
<menu class="actions">
<!-- Two clicks: this one cannot be taken back and it sits beside
two that can. Not `window.confirm`, whose two failure
directions are both wrong here (#57). 終了 in the list asked
the same question in this same shape until #71 moved it into a
dialog of its own; whether this one follows is #72. -->
<button id="dialog-delete" type="button" class="delete">削除</button>
<span class="spacer"></span>
<button id="dialog-cancel" type="button">取消</button>
<button id="dialog-commit" type="submit" value="commit">決定</button>
</menu>
</form>
</dialog>
<!-- The 終了 confirmation. One click on the row opens this, and the
question is answered here rather than on the button itself (#71).
Its own `<dialog>`, not the account form above. That one is a form
for editing an account; a single element holding both would have to
decide which of its two faces to open in every time it opens.
In the webview, and not `window.confirm`: a host that answers
nothing fails in one of two directions and both are wrong here — the
button goes silently dead, or a default-yes ends the session on one
click (#57). A `<dialog>` on this side has neither failure.
取消 holds the focus, so a stray Enter or Space on an opened dialog
answers the safe way. Escape closes it, which is the same answer. -->
<dialog id="end-dialog" aria-labelledby="end-dialog-title">
<div class="confirm">
<h2 id="end-dialog-title">セッションの終了</h2>
<p id="end-dialog-message"></p>
<menu class="actions">
<span class="spacer"></span>
<button id="end-cancel" type="button" autofocus>取消</button>
<button id="end-commit" type="button" class="danger">終了</button>
</menu>
</div>
</dialog>
</div>
</body>
</html>