Skip to content

Commit 656da21

Browse files
sarg3ntclaude
andcommitted
feat(console): cluster 1 polish — search, paste guard, shortcut, tab rename
Refs #142 (cluster 1). - xterm-addon-search vendored (@xterm/addon-search 0.15.0). - Search bar markup + CSS: input, prev/next, count, close, Esc to close. Ctrl-F / Cmd-F inside the terminal is intercepted via attachCustomKeyEventHandler so it opens the find bar instead of going to the shell. Each session lazy-loads its own search addon on first use. Result count is rendered live via the addon's onDidChangeResults callback ("3 / 12"). - Large-paste confirm modal: pastes with >= 20 newlines pop a modal with a preview (capped at first 30 lines / 1200 chars). Confirm pipes the text into the active session's PTY via the new ConsoleSession.sendText path; Cancel discards. Document-level paste listener checks that an xterm host is focused before intercepting. Bracketed paste itself rides on the remote shell turning DECSET 2004 on — xterm wraps with ESC[200~/ESC[201~ automatically when asked, so no client-side toggle needed. - Ctrl-Shift-` global shortcut opens a console for the current box (reads the box_id cookie set by switchBox). No-op when no box is pinned — palette is the fallback. - Double-click on a tab label swaps the label into an inline text input; blur/Enter commits, Escape reverts. The rename is per-session (lives on session.label); session.baseLabel still carries the canonical box name for tooltips and #N disambiguation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 334c2e9 commit 656da21

4 files changed

Lines changed: 498 additions & 2 deletions

File tree

gearbox/internal/framework/templates/components/console.templ

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,65 @@ templ ConsoleDrawer() {
116116
</svg>
117117
</button>
118118
</div>
119+
// Search overlay — hidden until Ctrl-F/Cmd-F triggers it.
120+
// Powered by xterm-addon-search; input wired in console.js.
121+
<div id="console-search-bar" class="console-search-bar hidden" role="search">
122+
<input
123+
type="text"
124+
id="console-search-input"
125+
class="console-search-input"
126+
placeholder="Find in terminal…"
127+
aria-label="Find in terminal"
128+
/>
129+
<span id="console-search-count" class="console-search-count" aria-live="polite"></span>
130+
<button type="button"
131+
id="console-search-prev"
132+
class="console-tool-btn"
133+
aria-label="Previous match"
134+
title="Previous match (Shift-Enter)">
135+
<svg class="w-4 h-4" aria-hidden="true" fill="none" stroke="currentColor" viewBox="0 0 24 24">
136+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"></path>
137+
</svg>
138+
</button>
139+
<button type="button"
140+
id="console-search-next"
141+
class="console-tool-btn"
142+
aria-label="Next match"
143+
title="Next match (Enter)">
144+
<svg class="w-4 h-4" aria-hidden="true" fill="none" stroke="currentColor" viewBox="0 0 24 24">
145+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
146+
</svg>
147+
</button>
148+
<button type="button"
149+
id="console-search-close"
150+
class="console-tool-btn"
151+
aria-label="Close search"
152+
title="Close (Esc)">
153+
<svg class="w-4 h-4" aria-hidden="true" fill="none" stroke="currentColor" viewBox="0 0 24 24">
154+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
155+
</svg>
156+
</button>
157+
</div>
119158
// xterm host. Sessions attach their own xterm <div> into this.
120159
// `console-xterm-pad` adds breathing room so cursor/text isn't
121160
// flush against the panel border.
122161
<div class="console-xterm-pad flex-1 overflow-hidden bg-black">
123162
<div id="console-xterm" class="w-full h-full"></div>
124163
</div>
164+
// Large-paste confirmation modal. Triggered by ConsoleManager
165+
// when clipboard content exceeds the paste-confirm threshold
166+
// (default 20 newlines). Buttons wired in console.js.
167+
<div id="console-paste-modal" class="console-paste-modal hidden" role="dialog" aria-modal="true" aria-labelledby="console-paste-modal-title">
168+
<div class="console-paste-modal-panel">
169+
<h3 id="console-paste-modal-title" class="console-paste-modal-title">Paste <span id="console-paste-modal-lines">?</span> lines?</h3>
170+
<p class="console-paste-modal-hint">Large pastes run many commands in sequence. Review the preview before confirming.</p>
171+
<pre id="console-paste-modal-preview" class="console-paste-modal-preview" aria-label="Paste preview"></pre>
172+
<div class="console-paste-modal-actions">
173+
<button type="button" id="console-paste-modal-cancel" class="console-paste-modal-btn console-paste-modal-cancel">Cancel</button>
174+
<button type="button" id="console-paste-modal-confirm" class="console-paste-modal-btn console-paste-modal-confirm">Paste</button>
175+
</div>
176+
</div>
177+
</div>
125178
</div>
126179
}
127180

@@ -133,5 +186,6 @@ templ ConsoleAssets() {
133186
<link rel="stylesheet" href="/static/css/components/console.css"/>
134187
<script src="/static/js/vendor/xterm.min.js" defer></script>
135188
<script src="/static/js/vendor/xterm-addon-fit.min.js" defer></script>
189+
<script src="/static/js/vendor/xterm-addon-search.min.js" defer></script>
136190
<script src="/static/js/console/console.js" defer></script>
137191
}

gearbox/static/css/components/console.css

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,20 @@ body.console-dock-open {
203203
cursor: pointer;
204204
padding: 0;
205205
}
206+
207+
/* Inline rename input — sized to match the original label so the tab
208+
* doesn't jump width on edit. */
209+
.console-tab-rename {
210+
background: rgb(2, 6, 23);
211+
color: rgb(226, 232, 240);
212+
border: 1px solid rgb(52, 211, 153);
213+
border-radius: 2px;
214+
font: inherit;
215+
font-size: 12px;
216+
padding: 0 4px;
217+
min-width: 80px;
218+
max-width: 200px;
219+
}
206220
.console-tab-close {
207221
background: none;
208222
border: none;
@@ -231,6 +245,127 @@ body.console-dock-open {
231245
display: none;
232246
}
233247

248+
/* ---------- search overlay ---------- */
249+
250+
/* Floats below the header in drawer/dock; absolute so it doesn't push
251+
* the xterm host. Width matches the panel content area. */
252+
.console-search-bar {
253+
display: flex;
254+
align-items: center;
255+
gap: 4px;
256+
padding: 4px 8px;
257+
background: rgb(15, 23, 42);
258+
border-bottom: 1px solid rgb(51, 65, 85);
259+
}
260+
.console-search-input {
261+
flex: 1 1 auto;
262+
min-width: 0;
263+
background: rgb(2, 6, 23);
264+
color: rgb(226, 232, 240);
265+
border: 1px solid rgb(51, 65, 85);
266+
border-radius: 4px;
267+
padding: 4px 8px;
268+
font-size: 12px;
269+
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
270+
}
271+
.console-search-input:focus {
272+
outline: none;
273+
border-color: rgb(52, 211, 153);
274+
}
275+
.console-search-count {
276+
font-size: 11px;
277+
color: rgb(148, 163, 184);
278+
min-width: 50px;
279+
text-align: right;
280+
font-variant-numeric: tabular-nums;
281+
}
282+
283+
/* ---------- paste-confirm modal ---------- */
284+
285+
.console-paste-modal {
286+
position: absolute;
287+
inset: 0;
288+
z-index: 5;
289+
background: rgba(0, 0, 0, 0.7);
290+
display: flex;
291+
align-items: center;
292+
justify-content: center;
293+
padding: 24px;
294+
}
295+
.console-paste-modal.hidden {
296+
display: none;
297+
}
298+
.console-paste-modal-panel {
299+
background: rgb(15, 23, 42);
300+
border: 1px solid rgb(51, 65, 85);
301+
border-radius: 6px;
302+
max-width: 560px;
303+
width: 100%;
304+
padding: 16px;
305+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
306+
}
307+
.console-paste-modal-title {
308+
font-size: 14px;
309+
font-weight: 600;
310+
color: rgb(226, 232, 240);
311+
margin: 0 0 4px 0;
312+
}
313+
.console-paste-modal-hint {
314+
font-size: 12px;
315+
color: rgb(148, 163, 184);
316+
margin: 0 0 8px 0;
317+
}
318+
.console-paste-modal-preview {
319+
background: rgb(2, 6, 23);
320+
color: rgb(203, 213, 225);
321+
border: 1px solid rgb(51, 65, 85);
322+
border-radius: 4px;
323+
padding: 8px;
324+
font-size: 11px;
325+
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
326+
max-height: 200px;
327+
overflow: auto;
328+
white-space: pre-wrap;
329+
word-break: break-all;
330+
margin: 0 0 12px 0;
331+
}
332+
.console-paste-modal-actions {
333+
display: flex;
334+
justify-content: flex-end;
335+
gap: 8px;
336+
}
337+
.console-paste-modal-btn {
338+
padding: 6px 12px;
339+
border-radius: 4px;
340+
font-size: 12px;
341+
font-weight: 500;
342+
border: 1px solid rgb(51, 65, 85);
343+
cursor: pointer;
344+
}
345+
.console-paste-modal-cancel {
346+
background: rgb(30, 41, 59);
347+
color: rgb(226, 232, 240);
348+
}
349+
.console-paste-modal-cancel:hover {
350+
background: rgb(51, 65, 85);
351+
}
352+
.console-paste-modal-confirm {
353+
background: rgb(52, 211, 153);
354+
color: rgb(2, 6, 23);
355+
border-color: rgb(52, 211, 153);
356+
}
357+
.console-paste-modal-confirm:hover {
358+
background: rgb(110, 231, 183);
359+
}
360+
361+
/* xterm-addon-search highlight tweaks for our dark theme */
362+
.xterm .xterm-decoration.console-search-active {
363+
background: rgba(250, 204, 21, 0.6);
364+
}
365+
.xterm .xterm-decoration.console-search-other {
366+
background: rgba(250, 204, 21, 0.25);
367+
}
368+
234369
/* ---------- xterm padding ---------- */
235370

236371
/* xterm.js paints right up to its container's edges by default. The

0 commit comments

Comments
 (0)