fix(chat): keep status-bar clearance in sync so bottom-right banners can't cover the input

The desktop chat input reserves space for Obsidian's bottom-right status bar
via the `--copilot-status-bar-clearance` variable, but `ChatViewLayout` only
re-measured the overlap on `css-change`. When a core or plugin status-bar item
appears or grows at runtime (update-available notice, sync status, ...) the bar
can get taller without a `css-change` event, so the stale clearance let the
banner overlap the chat input and its send button.

Attach a ResizeObserver to the status bar so any size change re-runs the
measurement. This stays generalizable: it measures the bar's geometry rather
than hardcoding any specific banner or plugin, and rebinds across theme reloads
that swap the element. Mobile is unaffected (the whole mechanism early-returns
on mobile).

Closes #99

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Logan Yang 2026-06-07 14:56:09 -07:00
parent 7261171638
commit 56c19b3aae
No known key found for this signature in database

View file

@ -12,6 +12,8 @@ const CSS_CHANGE_DEBOUNCE_MS = 600;
export class ChatViewLayout {
private debounceTimer: number | null = null;
private cssChangeRef: ReturnType<Workspace["on"]> | null = null;
private statusBarResizeObserver: ResizeObserver | null = null;
private observedStatusBar: HTMLElement | null = null;
constructor(
private containerEl: HTMLElement,
@ -32,6 +34,9 @@ export class ChatViewLayout {
this.workspace.offref(this.cssChangeRef);
this.cssChangeRef = null;
}
this.statusBarResizeObserver?.disconnect();
this.statusBarResizeObserver = null;
this.observedStatusBar = null;
}
/**
@ -43,6 +48,14 @@ export class ChatViewLayout {
* reflow so nothing flickers. Themes that already position content above
* the status bar (no overlap) get 0 clearance automatically. Auto-hide
* themes (opacity: 0) also get 0 since the bar is transparent.
*
* The desktop status bar lives in the bottom-right and hosts core and
* plugin items (update-available notice, sync status, word count, ...).
* When one of those appears or grows -- making the bar taller, e.g. items
* wrap to a second line -- the overlap changes without a `css-change`
* event, so a ResizeObserver on the bar keeps the clearance in sync and
* the chat input from being covered. Measuring the bar's geometry stays
* generalizable: no specific banner or plugin is hardcoded.
*/
private setupStatusBarClearance(): void {
if (Platform.isMobile) return;
@ -53,6 +66,8 @@ export class ChatViewLayout {
const viewContent = this.containerEl.querySelector<HTMLElement>(".view-content");
if (!statusBar || !viewContent) return;
this.observeStatusBar(statusBar, syncClearance);
// Zero out clearance and force reflow to measure natural overlap.
// Remove any inline override left from a prior run so the CSS default
// (0px) applies and the resulting rect reflects the natural overlap.
@ -81,4 +96,17 @@ export class ChatViewLayout {
this.debounceTimer = window.setTimeout(syncClearance, CSS_CHANGE_DEBOUNCE_MS);
});
}
/**
* Keep a single ResizeObserver pointed at the current status bar element so
* runtime size changes re-trigger a measurement. Rebinds when the element is
* replaced (e.g. after a theme reload swaps the DOM); a no-op otherwise.
*/
private observeStatusBar(statusBar: HTMLElement, onResize: () => void): void {
if (this.observedStatusBar === statusBar) return;
this.statusBarResizeObserver?.disconnect();
this.statusBarResizeObserver = new ResizeObserver(() => onResize());
this.statusBarResizeObserver.observe(statusBar);
this.observedStatusBar = statusBar;
}
}