fix: font initialization and DA1 response filtering

- Use terminal.loadFonts() API for proper font re-measurement after web fonts load
- Add documentation referencing ghostty-web commit feab41f9a8e4491f
- Handle DA1 responses split across socket reads with escape sequence buffering
- Update Makefile push target to explicitly push current tag
This commit is contained in:
GitHub Copilot
2026-01-29 18:54:50 +00:00
parent 31ff91dcc3
commit 19f455d293
4 changed files with 59 additions and 18 deletions
+9 -2
View File
@@ -97,5 +97,12 @@ bump-patch: ## Bump patch version and create git tag
git tag "v$$NEW"; \
echo "Bumped version: $$OLD -> $$NEW (tagged v$$NEW)"
push: ## Push commits and tags to origin
git push origin main --tags
push: ## Push commits and current tag to origin
@TAG=$$(git describe --tags --exact-match 2>/dev/null); \
git push origin main; \
if [ -n "$$TAG" ]; then \
echo "Pushing tag $$TAG..."; \
git push origin "$$TAG"; \
else \
echo "No tag on current commit"; \
fi
+25 -3
View File
@@ -29,9 +29,15 @@ DEFAULT_SCREEN_WIDTH = 132
DEFAULT_SCREEN_HEIGHT = 45
# Pattern to filter out terminal device attribute responses that cause display issues
# These are responses to queries that shouldn't be displayed as text
# These are responses to queries that shouldn't be displayed as text.
# Matches complete DA1/DA2 responses like \x1b[?1;10;0c or \x1b[?64;1;2;...c
DA_RESPONSE_PATTERN = re.compile(rb'\x1b\[\?[\d;]+c')
# Pattern to detect partial DA responses at end of data (incomplete escape sequence)
# Matches: \x1b, \x1b[, \x1b[?, \x1b[?1, \x1b[?1;, \x1b[?1;10, etc.
# These need to be held back until more data arrives to see if they complete
DA_PARTIAL_PATTERN = re.compile(rb'\x1b(?:\[(?:\?[\d;]*)?)?$')
@dataclass(frozen=True)
class DockerExecSpec:
@@ -69,6 +75,8 @@ class DockerExecSession(Session):
self._last_snapshot_counter = 0
self._exec_id: str | None = None
self._pending_output = b""
# Buffer for handling escape sequences split across socket reads
self._escape_buffer = b""
def __repr__(self) -> str:
return (
@@ -302,11 +310,25 @@ class DockerExecSession(Session):
data = await queue.get()
if not data:
break
# Filter out device attribute responses that can cause display issues
# when split across socket reads
# Prepend any buffered partial escape sequence from previous read
if self._escape_buffer:
data = self._escape_buffer + data
self._escape_buffer = b""
# Filter out complete DA1/DA2 responses (e.g., \x1b[?1;10;0c)
data = DA_RESPONSE_PATTERN.sub(b'', data)
if not data:
continue
# Check for partial escape sequence at end that might be a DA response
# Hold it back until we get more data to see if it completes
match = DA_PARTIAL_PATTERN.search(data)
if match:
self._escape_buffer = data[match.start():]
data = data[:match.start()]
if not data:
continue
await self._add_to_replay_buffer(data)
await self._update_screen(data)
if self._connector:
File diff suppressed because one or more lines are too long
+24 -12
View File
@@ -513,19 +513,31 @@ class WebTerminal {
});
// Wait for fonts to load before fitting to ensure correct measurements
//
// FONT INITIALIZATION (ghostty-web):
// -----------------------------------
// The font stack is set in two places:
// 1. At Terminal construction time via ITerminalOptions.fontFamily
// - This sets the initial font for the renderer
// 2. After web fonts load via terminal.loadFonts()
// - This re-measures font metrics and triggers a full re-render
//
// The loadFonts() method (added in ghostty-web commit feab41f9a8e4491f):
// - Calls renderer.remeasureFont() to recalculate cell dimensions
// - Calls handleFontChange() to resize canvas and re-render
//
// DO NOT manually set terminal.options.fontFamily or call renderer methods
// directly - use the public loadFonts() API which handles the full chain.
//
// See: https://github.com/rcarmo/ghostty-web/commit/feab41f9a8e4491f04688a6620974c3f7762a3d9
this.waitForFonts().then(() => {
console.log("[webterm:init] Fonts loaded, reapplying font family and fitting...");
// IMPORTANT: Font updates require BOTH steps to work correctly:
// 1. Set terminal.options.fontFamily - stores the font stack for future reference
// 2. Call renderer.setFontFamily() + remeasureFont() - applies the font and recalculates metrics
// Without step 1, the font stack is lost and defaults are used on re-render.
// Without step 2, the renderer doesn't know about the new fonts.
this.terminal.options.fontFamily = this.fontFamily;
const renderer = (this.terminal as unknown as { renderer?: { setFontFamily: (family: string) => void; remeasureFont: () => void } }).renderer;
if (renderer) {
renderer.setFontFamily(this.fontFamily);
renderer.remeasureFont();
console.log("[webterm:init] Font family updated via renderer");
console.log("[webterm:init] Fonts loaded, triggering font reload...");
// Use the public loadFonts() API which properly handles font re-measurement
// and triggers handleFontChange() internally. This is the correct approach
// per ghostty-web commit feab41f9a8e4491f04688a6620974c3f7762a3d9
if (typeof (this.terminal as unknown as { loadFonts?: () => void }).loadFonts === "function") {
(this.terminal as unknown as { loadFonts: () => void }).loadFonts();
console.log("[webterm:init] terminal.loadFonts() called");
}
this.fit();
console.log("[webterm:init] fit() completed");