detached 0e1c397

fix(gui): track workspace focus events for pane shortcuts

commit
0e1c397d0f3ac5ae104a4cdf66e7b29197b5c93a
author
bamen · · 2025-10-12 08:30 UTC
parent
05647bb
signature
unsigned
1 file · +35 -1
M gui/src/injected/modules/workspace.ts +35 -1
@@ -143,6 +143,8 @@ class WorkspaceController implements WorkspaceRuntime {
143143 private paneModeTimer: ReturnType<typeof setTimeout> | null = null;
144144 private instanceId: string;
145145 private lastActiveCheckTime = 0;
146+ private workspaceHasFocus = true;
147+ private focusEventReceived = false;
146148
147149 constructor() {
148150 // Generate unique instance ID for this webview
@@ -150,6 +152,27 @@ class WorkspaceController implements WorkspaceRuntime {
150152 console.log(
151153 `[workspace] Created WorkspaceController instance: ${this.instanceId}`,
152154 );
155+
156+ document.addEventListener(
157+ "dumber:workspace-focus",
158+ (event: Event) => {
159+ const detail = (event as CustomEvent).detail ?? {};
160+ this.focusEventReceived = true;
161+
162+ if (typeof detail?.active === "boolean") {
163+ this.workspaceHasFocus = detail.active;
164+ } else {
165+ this.workspaceHasFocus = detail !== false;
166+ }
167+
168+ if (!this.workspaceHasFocus) {
169+ console.log(
170+ `[workspace] Instance ${this.instanceId} marked inactive via focus event`,
171+ );
172+ }
173+ },
174+ { passive: true },
175+ );
153176 }
154177
155178 getInstanceId(): string {
@@ -160,10 +183,19 @@ class WorkspaceController implements WorkspaceRuntime {
160183 // Cache active check for 50ms to avoid excessive checks
161184 const now = Date.now();
162185 if (now - this.lastActiveCheckTime < 50) {
163- return true; // Assume active within debounce window
186+ return this.workspaceHasFocus;
164187 }
165188 this.lastActiveCheckTime = now;
166189
190+ if (this.focusEventReceived) {
191+ if (!this.workspaceHasFocus) {
192+ console.log(
193+ `[workspace] Instance ${this.instanceId} not active (focus event state)`,
194+ );
195+ }
196+ return this.workspaceHasFocus;
197+ }
198+
167199 // Check if this webview has focus by attempting to query document.hasFocus()
168200 // In a multi-pane environment, only the focused webview should handle pane mode
169201 try {
@@ -171,6 +203,8 @@ class WorkspaceController implements WorkspaceRuntime {
171203 const isVisible = document.visibilityState === "visible";
172204 const isActive = hasFocus && isVisible;
173205
206+ this.workspaceHasFocus = isActive;
207+
174208 if (!isActive) {
175209 console.log(
176210 `[workspace] Instance ${this.instanceId} not active: focus=${hasFocus} visible=${isVisible}`,
@@ -143,6 +143,8 @@ class WorkspaceController implements WorkspaceRuntime {
143 private paneModeTimer: ReturnType<typeof setTimeout> | null = null;143 private paneModeTimer: ReturnType<typeof setTimeout> | null = null;
144 private instanceId: string;144 private instanceId: string;
145 private lastActiveCheckTime = 0;145 private lastActiveCheckTime = 0;
146+ private workspaceHasFocus = true;
147+ private focusEventReceived = false;
146 148
147 constructor() {149 constructor() {
148 // Generate unique instance ID for this webview150 // Generate unique instance ID for this webview
@@ -150,6 +152,27 @@ class WorkspaceController implements WorkspaceRuntime {
150 console.log(152 console.log(
151 `[workspace] Created WorkspaceController instance: ${this.instanceId}`,153 `[workspace] Created WorkspaceController instance: ${this.instanceId}`,
152 );154 );
155+
156+ document.addEventListener(
157+ "dumber:workspace-focus",
158+ (event: Event) => {
159+ const detail = (event as CustomEvent).detail ?? {};
160+ this.focusEventReceived = true;
161+
162+ if (typeof detail?.active === "boolean") {
163+ this.workspaceHasFocus = detail.active;
164+ } else {
165+ this.workspaceHasFocus = detail !== false;
166+ }
167+
168+ if (!this.workspaceHasFocus) {
169+ console.log(
170+ `[workspace] Instance ${this.instanceId} marked inactive via focus event`,
171+ );
172+ }
173+ },
174+ { passive: true },
175+ );
153 }176 }
154 177
155 getInstanceId(): string {178 getInstanceId(): string {
@@ -160,10 +183,19 @@ class WorkspaceController implements WorkspaceRuntime {
160 // Cache active check for 50ms to avoid excessive checks183 // Cache active check for 50ms to avoid excessive checks
161 const now = Date.now();184 const now = Date.now();
162 if (now - this.lastActiveCheckTime < 50) {185 if (now - this.lastActiveCheckTime < 50) {
163- return true; // Assume active within debounce window186+ return this.workspaceHasFocus;
164 }187 }
165 this.lastActiveCheckTime = now;188 this.lastActiveCheckTime = now;
166 189
190+ if (this.focusEventReceived) {
191+ if (!this.workspaceHasFocus) {
192+ console.log(
193+ `[workspace] Instance ${this.instanceId} not active (focus event state)`,
194+ );
195+ }
196+ return this.workspaceHasFocus;
197+ }
198+
167 // Check if this webview has focus by attempting to query document.hasFocus()199 // Check if this webview has focus by attempting to query document.hasFocus()
168 // In a multi-pane environment, only the focused webview should handle pane mode200 // In a multi-pane environment, only the focused webview should handle pane mode
169 try {201 try {
@@ -171,6 +203,8 @@ class WorkspaceController implements WorkspaceRuntime {
171 const isVisible = document.visibilityState === "visible";203 const isVisible = document.visibilityState === "visible";
172 const isActive = hasFocus && isVisible;204 const isActive = hasFocus && isVisible;
173 205
206+ this.workspaceHasFocus = isActive;
207+
174 if (!isActive) {208 if (!isActive) {
175 console.log(209 console.log(
176 `[workspace] Instance ${this.instanceId} not active: focus=${hasFocus} visible=${isVisible}`,210 `[workspace] Instance ${this.instanceId} not active: focus=${hasFocus} visible=${isVisible}`,

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close