-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
505 lines (459 loc) · 26.2 KB
/
Copy pathindex.html
File metadata and controls
505 lines (459 loc) · 26.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
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
<!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">
<!-- Folding the left panel away. Where the app's own name used to sit:
that word said what this window is to someone already looking at
it, and pressing it did nothing (#118). The control that reaches
the panel below it stands over that panel's column instead, which
is the position it is read from.
Its pair is at the far right of this bar, over the column it
folds, and the two are the same drawing mirrored. Both buttons
carry that drawing; what makes them a mirror rather than a pair of
pictures is one rule in src/styles.css, which turns
`#toggle-participants` over. A second picture for the second panel
would say the two controls do different things (#118, decision 4).
`Ctrl+B` reaches this one and not the one opposite. The key is
the terminal's while the terminal has focus — a session's pane is
the session's (#84) — so the shortcut is not the only way in, and
both buttons stay on the bar whether a panel is open or folded.
`aria-expanded` and a name that does not change with it, the way
`#toggle-diagnostics` below carries the same pair. The state is
the attribute's to say; a label that said it too would be two
answers to one question. -->
<button
id="toggle-history"
class="panel-toggle"
type="button"
aria-expanded="true"
aria-controls="history"
aria-label="トピックの開閉"
title="トピックの開閉(Ctrl+B)"
>
<svg viewBox="0 0 16 16" aria-hidden="true" focusable="false">
<rect x="1.7" y="2.7" width="12.6" height="10.6" rx="2.4" />
<line x1="6.2" y1="2.7" x2="6.2" y2="13.3" />
</svg>
</button>
<!-- 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>
<!-- Folding the right panel away. Last on the bar, so it sits over the
column it reaches — the same reason its pair is first. See that
one for the shape, the mirror and why this one has no shortcut. -->
<button
id="toggle-participants"
class="panel-toggle"
type="button"
aria-expanded="true"
aria-controls="participants"
aria-label="アカウントの開閉"
title="アカウントの開閉"
>
<svg viewBox="0 0 16 16" aria-hidden="true" focusable="false">
<rect x="1.7" y="2.7" width="12.6" height="10.6" rx="2.4" />
<line x1="6.2" y1="2.7" x2="6.2" y2="13.3" />
</svg>
</button>
</header>
<div id="body">
<!-- The topics (src-tauri/src/room_log.rs). Not a strip of past posts:
this column used to hold every line ever said in one flow, and what
Master asked for was a list to pick from (#115). A topic is the
vessel a conversation happens in — picking one puts it back in the
room to the right and the talk continues in it.
It sits opposite the account panel and is its pair in width and
ground, for the reason it was one before: two panels flanking the
room read as two panels, and one of them narrower reads as a
mistake (#48).
新規 is what draws a boundary, and it is the only thing that draws
one. Starting the app opens a new topic too, but the two are
independent: one run may hold several topics, and one topic may
span several runs (#115, decision 1). -->
<aside id="history" aria-label="トピック">
<div class="panel-head">
<h2 class="panel-title">トピック</h2>
</div>
<!-- 新規, at the head of the list rather than beside the heading
(#125, 決定1). It is fixed: it stands outside the <ol> because the
list is redrawn from the index and this is not one of the index's
rows.
It is also what the top of the list used to hold. A launch opens a
topic the index does not carry yet (#115), and the list drew that
topic as a row of its own — a row naming a conversation nobody had
started, which is the thing 新規 says truthfully. Drawn as picked
while the room is in such a topic (#125, AI 判断4), so the place
the room is in is somewhere on this list. -->
<button id="topic-new" type="button" class="new" title="新しいトピックを始める">新規</button>
<!-- Seeded, because the index is read after the frame is up: an empty
list in that gap would say "there are no topics" at the one moment
nothing has been asked yet. -->
<ol id="topic-list" class="topics">
<li class="empty">読み込み中</li>
</ol>
</aside>
<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>
<!-- 誰として喋るか。作業ディレクトリの
`.claude/output-styles/` に置かれた output style の
frontmatter の `name:` を書く。ファイル名ではない。
欄であることに意味がある。起動オプションの文字列の中へ書い
ても同じ行にはなるが、それはアカウントの属性になっていない
——名前と色が起動時宣言から属性へ移った理由と同じである
(#40 / #99)。
空ならこの欄は何も足さず、作業ディレクトリの
`settings.json` が指す既定がそのまま立つ。 -->
<label class="field">
キャラクター
<input
id="dialog-character"
type="text"
spellcheck="false"
placeholder="例: character_Lay(output style の name)"
/>
</label>
<!-- 起動オプション。`{session_id}` を書くと、そこへこのアプリが
決めた UUID が入る。新しいトピックでセッションを始めるときに
使う側であり、`claude` では `--session-id {session_id}` と
書く。どのフラグが id を運ぶかは CLI ごとの問いであるため、
アプリ側は欄を持たず、書かれた場所へ差し込む。
書かなければ id を配らない。それは resume を持たない CLI が
恒常的に置かれる状態であり、失敗ではない——その場合の参加者
は、部屋の読み出し(`read_room_history`)で自分から辿る
(#115)。 -->
<label class="field">
起動オプション
<input
id="dialog-options"
type="text"
spellcheck="false"
placeholder="例: --dangerously-skip-permissions --session-id {session_id}"
/>
</label>
<!-- 再開コマンド。トピックが持っているセッションへ戻る一行であり、
先頭の語が command になる。トピックはアカウントごとに
`{account_id: session_uuid}` を持ち、その id がここへ入る
(#115、決定4B)。
起動コマンドと別の欄なのは、戻り方が起動と同じ呼び出しとは
限らないためである。空なら resume 無しであり、そのアカウント
は開き直したトピックへ新規のセッションとして着席する。席が
一つ戻らなくてもトピックは開く(決定6)。 -->
<label class="field">
再開コマンド
<input
id="dialog-resume"
type="text"
spellcheck="false"
placeholder="例: claude --resume {session_id}"
/>
</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>
<!-- The アプリの終了 confirmation. Closing the window ends every session
running under it, so the close is held open until this is answered
(#85).
Its own `<dialog>`, not a second face of `#end-dialog` above. That
one asks about one account's session and is answered into the
account it was opened on; one element holding both questions would
have to decide which of them it is opening in, and the answer would
have to find its way back to whichever asked. It is the same reason
`#end-dialog` is not a face of the account form.
Not `window.confirm`, for the reason given there (#57), and the hole
is the same one on this operation: the button goes silently dead, or
a default-yes closes the app and every session with it on one click.
取消 holds the focus and Escape closes, which is the same answer: the
window stays and every session keeps running. There is no answer here
that ends the sessions without closing — ending one session is the
row's 終了, and this is the app going away. -->
<dialog id="quit-dialog" aria-labelledby="quit-dialog-title">
<div class="confirm">
<h2 id="quit-dialog-title">アプリの終了</h2>
<p id="quit-dialog-message"></p>
<menu class="actions">
<span class="spacer"></span>
<button id="quit-cancel" type="button" autofocus>取消</button>
<button id="quit-commit" type="button" class="danger">終了</button>
</menu>
</div>
</dialog>
<!-- The トピックの削除 confirmation. Deleting a topic takes the posts and
the way back into the sessions that were in it, and neither is
anywhere else — the log is not in git and nothing copies it (#119).
So the question is asked, and it is asked here rather than on the
button: this one sits in a list whose other click merely opens a
topic.
Its own `<dialog>`, for the reason `#end-dialog` and `#quit-dialog`
are each their own: one element holding three questions would have to
decide which of them it is opening in, and each answer would have to
find its way back to whichever asked.
Two lines, and the second one is not decoration. Deleting a topic
ends the sessions running in it (#119, decision 4), so a dialog that
spoke only about the log would make a stopped session an ambush. It
is hidden when nothing is running, because saying it then would be
saying something untrue.
取消 holds the focus, so a stray Enter or Space answers the safe way.
Escape closes it, which is the same answer. -->
<dialog id="topic-delete-dialog" aria-labelledby="topic-delete-title">
<div class="confirm">
<h2 id="topic-delete-title">トピックの削除</h2>
<p id="topic-delete-message"></p>
<p id="topic-delete-sessions" class="sessions" hidden></p>
<menu class="actions">
<span class="spacer"></span>
<button id="topic-delete-cancel" type="button" autofocus>取消</button>
<button id="topic-delete-commit" type="button" class="danger">削除</button>
</menu>
</div>
</dialog>
</div>
</body>
</html>