History

Re-run session sync after WASM init and merge Ghostty config files

- Bump a terminal epoch when the deferred WASM setup finishes so the session sync effect re-runs against the live terminal even when no session field changed during the load, completing the exited-at-mount handling - Read config.ghostty as an alternate config file name and merge the XDG and macOS Application Support configs in Ghostty's load order instead of stopping at the first existing file

Pixel Perfect committed 4 months ago

Commit 36d1cc61 parent c1caaa32 files · +24 −7Browse files

Unified · Split

2 files
  1. apps/server/src/terminal/ghosttyStyle.ts +18 −6
  2. apps/web/src/components/ThreadTerminalDrawer.tsx +6 −1
apps/server/src/terminal/ghosttyStyle.ts+18−6
@@ -207,13 +207,25 @@ export const loadGhosttyTerminalStyle: Effect.Effect< · 206 unmodified lines
207207 return undefined;
208208 });
209209
210− const configSource = yield* readFirstExisting([
211− path.join(xdgConfigHome, "ghostty", "config"),
212− path.join(home, "Library", "Application Support", "com.mitchellh.ghostty", "config"),
213− ]);
214− if (configSource === undefined) return undefined;
210+ // Ghostty reads the XDG config first and the macOS Application Support
211+ // config after it, so later files override earlier values. It also accepts
212+ // config.ghostty as the file name. Merge every location that exists instead
213+ // of stopping at the first match.
214+ const configDirs = [
215+ path.join(xdgConfigHome, "ghostty"),
216+ path.join(home, "Library", "Application Support", "com.mitchellh.ghostty"),
217+ ];
218+ const configSources: Array<string> = [];
219+ for (const dir of configDirs) {
220+ const source = yield* readFirstExisting([
221+ path.join(dir, "config"),
222+ path.join(dir, "config.ghostty"),
223+ ]);
224+ if (source !== undefined) configSources.push(source);
225+ }
226+ if (configSources.length === 0) return undefined;
215227
216− const config = parseGhosttyConfig(configSource);
228+ const config = parseGhosttyConfig(configSources.join("\n"));
217229
218230 const loadThemeColors = (themeName: string) =>
219231 Effect.gen(function* () {
apps/web/src/components/ThreadTerminalDrawer.tsx+6−1
@@ -434,6 +434,10 @@ export function TerminalViewport({ · 433 unmodified lines
434434 reportFailure: false,
435435 });
436436 const hasHandledExitRef = useRef(false);
437+ // Bumped when the async WASM gate finishes creating a terminal, so the
438+ // session sync effect re-runs against the live terminal even when no
439+ // session field changed while the WASM module was loading.
440+ const [terminalEpoch, setTerminalEpoch] = useState(0);
437441 const selectionPointerRef = useRef<{ x: number; y: number } | null>(null);
438442 const selectionGestureActiveRef = useRef(false);
439443 const selectionActionRequestIdRef = useRef(0);
@@ -549,6 +553,7 @@ export function TerminalViewport({ · 109 unmodified lines
549553 status: "closed",
550554 version: 0,
551555 };
556+ setTerminalEpoch((value) => value + 1);
552557
553558 const clearSelectionAction = () => {
554559 selectionActionRequestIdRef.current += 1;
@@ -970,7 +975,7 @@ export function TerminalViewport({ · 415 unmodified lines
970975 });
971976 }
972977 previousSessionRef.current = current;
973− }, [autoFocus, terminalBuffer, terminalError, terminalStatus, terminalVersion]);
978+ }, [autoFocus, terminalBuffer, terminalEpoch, terminalError, terminalStatus, terminalVersion]);
974979
975980 useEffect(() => {
976981 if (!autoFocus) return;