feat(app): GUI backlog — splitter, drag-reorder, fit, persist, modal focus
- LayoutEngine: fix splitter resize (track pointer 1:1 via delta-from-start) and add panel drag-to-reorder using raw pointer events with drop indicators - TerminalView: auto-fit xterm to container via FitAddon + ResizeObserver - App/TopBar: toggleable sidebar; persist sidebar/events collapse in localStorage; bell icon opens the activity log - Wizard: new-workspace modal now grabs focus and handles keyboard - deps: add @xterm/addon-fit Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { useEffect, useRef } from "react";
|
||||
import { Terminal } from "@xterm/xterm";
|
||||
import { WebglAddon } from "@xterm/addon-webgl";
|
||||
import { SearchAddon } from "@xterm/addon-search";
|
||||
import { FitAddon } from "@xterm/addon-fit";
|
||||
import { attachSurface, detachSurface, sendInput, resizeSurface } from "./socketBridge";
|
||||
import { registerSearch, unregisterSearch } from "./searchRegistry";
|
||||
|
||||
@@ -25,6 +26,27 @@ export function TerminalView({ surfaceId }: { surfaceId: string }) {
|
||||
term.loadAddon(search);
|
||||
registerSearch(surfaceId, search);
|
||||
|
||||
const fit = new FitAddon();
|
||||
term.loadAddon(fit);
|
||||
|
||||
// Fit the grid to the container and tell the daemon the new size. Coalesced
|
||||
// through rAF so a burst of resize callbacks yields one resize per frame.
|
||||
let rafId = 0;
|
||||
let lastCols = 0, lastRows = 0;
|
||||
const doFit = () => {
|
||||
rafId = 0;
|
||||
try { fit.fit(); } catch { return; }
|
||||
if (term.cols !== lastCols || term.rows !== lastRows) {
|
||||
lastCols = term.cols;
|
||||
lastRows = term.rows;
|
||||
void resizeSurface(surfaceId, term.cols, term.rows);
|
||||
}
|
||||
};
|
||||
const scheduleFit = () => { if (!rafId) rafId = requestAnimationFrame(doFit); };
|
||||
|
||||
const ro = new ResizeObserver(scheduleFit);
|
||||
ro.observe(ref.current);
|
||||
|
||||
// Input → daemon.
|
||||
const inputDisposable = term.onData((data) => {
|
||||
void sendInput(surfaceId, encoder.encode(data));
|
||||
@@ -38,14 +60,15 @@ export function TerminalView({ surfaceId }: { surfaceId: string }) {
|
||||
}).then((res) => {
|
||||
if (disposed) return;
|
||||
if (res.snapshot) term.write(res.snapshot);
|
||||
if (res.cols && res.rows) {
|
||||
term.resize(res.cols, res.rows);
|
||||
void resizeSurface(surfaceId, res.cols, res.rows);
|
||||
}
|
||||
// Fit to the actual container rather than the daemon's stored geometry,
|
||||
// then push the resulting size back so the PTY reflows to match.
|
||||
scheduleFit();
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposed = true;
|
||||
if (rafId) cancelAnimationFrame(rafId);
|
||||
ro.disconnect();
|
||||
inputDisposable.dispose();
|
||||
void detachSurface(surfaceId);
|
||||
unregisterSearch(surfaceId);
|
||||
|
||||
Reference in New Issue
Block a user