detached 65e3547

fix(config): use messaging bridge for config and fix palette key mapping

- Replace fetch /api/config with webkit messageHandlers bridge to avoid CORS
- Add get_search_shortcuts and get_color_palettes message handlers
- Fix palette token key mapping (background vs Background, surface_variant vs SurfaceVariant)
- Add detailed logging for palette loading and application
- Use proper TypeScript types (unknown instead of any)
- Search shortcuts now work via messaging bridge in both app.ts and omnibox
commit
65e3547fcb408b5ac1bb4f3185c402a9782323bf
author
bamen · · 2025-10-06 18:52 UTC
committer
bamen · · 2025-10-06 16:58 UTC
parent
2cbda56
signature
unsigned
2 files · +129 -68
M gui/src/components/omnibox/messaging.ts +46 -16
@@ -74,25 +74,55 @@ export class OmniboxBridge implements OmniboxMessageBridge {
7474 }
7575
7676 /**
77- * Fetch search shortcuts from backend config
77+ * Fetch search shortcuts from backend via messaging bridge
7878 */
7979 async fetchSearchShortcuts(): Promise<void> {
80- try {
81- const cfg = await fetch("/api/config").then((r) => r.json());
82- const raw = cfg?.search_shortcuts || {};
83- // Normalize to match our SearchShortcut type
84- const normalized: Record<string, SearchShortcut> = {};
85- for (const [key, value] of Object.entries(raw)) {
86- const v = value as Record<string, unknown>;
87- normalized[key] = {
88- url: (v.url ?? v.URL ?? "") as string,
89- description: (v.description ?? v.Description ?? "") as string,
90- };
80+ return new Promise((resolve, reject) => {
81+ // Set up one-time response handler
82+ const originalCallback = (window as any).__dumber_search_shortcuts;
83+ (window as any).__dumber_search_shortcuts = (data: unknown) => {
84+ try {
85+ // Type guard
86+ if (typeof data !== "object" || data === null) {
87+ reject(new Error("Invalid shortcuts data"));
88+ return;
89+ }
90+ const dataObj = data as Record<string, unknown>;
91+ // Normalize to match our SearchShortcut type
92+ const normalized: Record<string, SearchShortcut> = {};
93+ for (const [key, value] of Object.entries(dataObj)) {
94+ const v = value as Record<string, unknown>;
95+ normalized[key] = {
96+ url: (v.url ?? v.URL ?? "") as string,
97+ description: (v.description ?? v.Description ?? "") as string,
98+ };
99+ }
100+ this.setSearchShortcuts(normalized);
101+
102+ // Restore original callback if it existed
103+ if (originalCallback) {
104+ (window as any).__dumber_search_shortcuts = originalCallback;
105+ }
106+
107+ resolve();
108+ } catch (error) {
109+ console.error("Failed to process search shortcuts:", error);
110+ reject(error);
111+ }
112+ };
113+
114+ // Send message to Go backend
115+ const bridge = window.webkit?.messageHandlers?.dumber;
116+ if (bridge && typeof bridge.postMessage === "function") {
117+ bridge.postMessage(
118+ JSON.stringify({
119+ type: "get_search_shortcuts",
120+ }),
121+ );
122+ } else {
123+ reject(new Error("WebKit message handler not available"));
91124 }
92- this.setSearchShortcuts(normalized);
93- } catch (error) {
94- console.error("Failed to fetch search shortcuts:", error);
95- }
125+ });
96126 }
97127
98128 // Suggestions are returned via setSuggestions() when native handler responds
@@ -74,25 +74,55 @@ export class OmniboxBridge implements OmniboxMessageBridge {
74 }74 }
75 75
76 /**76 /**
77- * Fetch search shortcuts from backend config77+ * Fetch search shortcuts from backend via messaging bridge
78 */78 */
79 async fetchSearchShortcuts(): Promise<void> {79 async fetchSearchShortcuts(): Promise<void> {
80- try {80+ return new Promise((resolve, reject) => {
81- const cfg = await fetch("/api/config").then((r) => r.json());81+ // Set up one-time response handler
82- const raw = cfg?.search_shortcuts || {};82+ const originalCallback = (window as any).__dumber_search_shortcuts;
83- // Normalize to match our SearchShortcut type83+ (window as any).__dumber_search_shortcuts = (data: unknown) => {
84- const normalized: Record<string, SearchShortcut> = {};84+ try {
85- for (const [key, value] of Object.entries(raw)) {85+ // Type guard
86- const v = value as Record<string, unknown>;86+ if (typeof data !== "object" || data === null) {
87- normalized[key] = {87+ reject(new Error("Invalid shortcuts data"));
88- url: (v.url ?? v.URL ?? "") as string,88+ return;
89- description: (v.description ?? v.Description ?? "") as string,89+ }
90- };90+ const dataObj = data as Record<string, unknown>;
91+ // Normalize to match our SearchShortcut type
92+ const normalized: Record<string, SearchShortcut> = {};
93+ for (const [key, value] of Object.entries(dataObj)) {
94+ const v = value as Record<string, unknown>;
95+ normalized[key] = {
96+ url: (v.url ?? v.URL ?? "") as string,
97+ description: (v.description ?? v.Description ?? "") as string,
98+ };
99+ }
100+ this.setSearchShortcuts(normalized);
101+
102+ // Restore original callback if it existed
103+ if (originalCallback) {
104+ (window as any).__dumber_search_shortcuts = originalCallback;
105+ }
106+
107+ resolve();
108+ } catch (error) {
109+ console.error("Failed to process search shortcuts:", error);
110+ reject(error);
111+ }
112+ };
113+
114+ // Send message to Go backend
115+ const bridge = window.webkit?.messageHandlers?.dumber;
116+ if (bridge && typeof bridge.postMessage === "function") {
117+ bridge.postMessage(
118+ JSON.stringify({
119+ type: "get_search_shortcuts",
120+ }),
121+ );
122+ } else {
123+ reject(new Error("WebKit message handler not available"));
91 }124 }
92- this.setSearchShortcuts(normalized);125+ });
93- } catch (error) {
94- console.error("Failed to fetch search shortcuts:", error);
95- }
96 }126 }
97 127
98 // Suggestions are returned via setSuggestions() when native handler responds128 // Suggestions are returned via setSuggestions() when native handler responds
M gui/src/injected/main-world.ts +83 -52
@@ -126,98 +126,129 @@ function detectWindowType(features?: string | null): string {
126126
127127 // Request color palettes from Go backend
128128 try {
129+ console.log("[dumber-palette] Initializing color palette loading");
129130 const bridge = window.webkit?.messageHandlers?.dumber;
130- if (bridge && typeof bridge.postMessage === "function") {
131- // Set up callback handler before requesting
132- window.__dumber_color_palettes = (palettes: unknown) => {
133- try {
134- // Type guard to ensure palettes is a valid object
135- if (typeof palettes !== "object" || palettes === null) {
136- console.error("[dumber] Invalid palettes received");
137- return;
138- }
139131
140- window.__dumber_palette = palettes as Record<string, Record<string, string>>;
132+ if (!bridge || typeof bridge.postMessage !== "function") {
133+ console.error("[dumber-palette] ERROR: No webkit message bridge available");
134+ return;
135+ }
136+
137+ console.log("[dumber-palette] WebKit bridge found, setting up handlers");
141138
142- // Normalize palette keys to lowercase
143- const normalized: Record<string, Record<string, string>> = {};
144- if (window.__dumber_palette) {
145- for (const key in window.__dumber_palette) {
146- if (Object.prototype.hasOwnProperty.call(window.__dumber_palette, key)) {
147- normalized[key.toLowerCase()] = window.__dumber_palette[key];
139+ // Set up callback handler before requesting
140+ window.__dumber_color_palettes = (palettes: unknown) => {
141+ console.log("[dumber-palette] Received palettes from backend:", palettes);
142+ try {
143+ // Type guard to ensure palettes is a valid object
144+ if (typeof palettes !== "object" || palettes === null) {
145+ console.error("[dumber-palette] ERROR: Invalid palettes received - type:", typeof palettes);
146+ return;
147+ }
148+
149+ window.__dumber_palette = palettes as Record<string, Record<string, string>>;
150+ console.log("[dumber-palette] Palette keys:", Object.keys(window.__dumber_palette || {}));
151+
152+ // Normalize palette keys to lowercase
153+ const normalized: Record<string, Record<string, string>> = {};
154+ if (window.__dumber_palette) {
155+ for (const key in window.__dumber_palette) {
156+ if (Object.prototype.hasOwnProperty.call(window.__dumber_palette, key)) {
157+ const paletteValue = window.__dumber_palette[key];
158+ if (paletteValue) {
159+ normalized[key.toLowerCase()] = paletteValue;
160+ console.log(`[dumber-palette] Normalized: ${key} -> ${key.toLowerCase()}, colors:`, Object.keys(paletteValue));
148161 }
149162 }
150- window.__dumber_palette = normalized;
151163 }
164+ window.__dumber_palette = normalized;
165+ }
152166
153- // Apply palette now that we have it
154- const initialTheme: "light" | "dark" = document.documentElement.classList.contains("dark") ? "dark" : "light";
155- window.__dumber_initial_theme = initialTheme;
156- if (window.__dumber_applyPalette) {
157- window.__dumber_applyPalette(initialTheme);
158- }
159- } catch (err) {
160- console.error("[dumber] Failed to apply received palette", err);
167+ // Apply palette now that we have it
168+ const initialTheme: "light" | "dark" = document.documentElement.classList.contains("dark") ? "dark" : "light";
169+ console.log(`[dumber-palette] Initial theme detected: ${initialTheme}`);
170+ window.__dumber_initial_theme = initialTheme;
171+
172+ if (window.__dumber_applyPalette) {
173+ console.log("[dumber-palette] Applying palette...");
174+ window.__dumber_applyPalette(initialTheme);
175+ } else {
176+ console.error("[dumber-palette] ERROR: __dumber_applyPalette function not found");
161177 }
162- };
178+ } catch (err) {
179+ console.error("[dumber-palette] ERROR: Failed to apply received palette:", err);
180+ }
181+ };
163182
164- window.__dumber_color_palettes_error = (error: string) => {
165- console.error("[dumber] Failed to get color palettes:", error);
166- };
183+ window.__dumber_color_palettes_error = (error: string) => {
184+ console.error("[dumber-palette] ERROR from backend:", error);
185+ };
167186
168- // Request color palettes
169- bridge.postMessage(JSON.stringify({ type: "get_color_palettes" }));
170- }
187+ // Request color palettes
188+ console.log("[dumber-palette] Sending get_color_palettes request to backend");
189+ bridge.postMessage(JSON.stringify({ type: "get_color_palettes" }));
190+ console.log("[dumber-palette] Request sent, waiting for response...");
171191 } catch (err) {
172- console.error("[dumber] Failed to request color palettes", err);
192+ console.error("[dumber-palette] EXCEPTION requesting color palettes:", err);
173193 }
174194
175195 // Palette application function
176196 window.__dumber_applyPalette = (theme: "light" | "dark") => {
197+ console.log(`[dumber-palette] applyPalette called with theme: ${theme}`);
177198 try {
178199 const palette = window.__dumber_palette?.[theme] || window.__dumber_palette?.["light"];
179200 if (!palette) {
201+ console.error(`[dumber-palette] ERROR: No palette found for theme "${theme}". Available:`, Object.keys(window.__dumber_palette || {}));
180202 return;
181203 }
204+ console.log(`[dumber-palette] Found palette for theme "${theme}":`, palette);
182205
183206 const tokenMap: Record<string, string> = {
184- Background: "--color-browser-bg",
185- Surface: "--color-browser-surface",
186- SurfaceVariant: "--color-browser-surface-variant",
187- Text: "--color-browser-text",
188- Muted: "--color-browser-muted",
189- Accent: "--color-browser-accent",
190- Border: "--color-browser-border",
207+ background: "--color-browser-bg",
208+ surface: "--color-browser-surface",
209+ surface_variant: "--color-browser-surface-variant",
210+ text: "--color-browser-text",
211+ muted: "--color-browser-muted",
212+ accent: "--color-browser-accent",
213+ border: "--color-browser-border",
191214 };
192215
193216 const dynamicMap: Record<string, string> = {
194- Background: "--dynamic-bg",
195- Surface: "--dynamic-surface",
196- SurfaceVariant: "--dynamic-surface-variant",
197- Text: "--dynamic-text",
198- Muted: "--dynamic-muted",
199- Accent: "--dynamic-accent",
200- Border: "--dynamic-border",
217+ background: "--dynamic-bg",
218+ surface: "--dynamic-surface",
219+ surface_variant: "--dynamic-surface-variant",
220+ text: "--dynamic-text",
221+ muted: "--dynamic-muted",
222+ accent: "--dynamic-accent",
223+ border: "--dynamic-border",
201224 };
202225
203226 const root = document.documentElement;
227+ let appliedCount = 0;
204228 for (const key in tokenMap) {
205229 if (Object.prototype.hasOwnProperty.call(tokenMap, key)) {
206230 const colorValue = palette[key];
207- if (colorValue) {
208- root.style.setProperty(tokenMap[key], colorValue);
209- if (Object.prototype.hasOwnProperty.call(dynamicMap, key)) {
210- root.style.setProperty(dynamicMap[key], colorValue);
231+ const tokenVar = tokenMap[key];
232+ const dynamicVar = dynamicMap[key];
233+ if (colorValue && typeof colorValue === "string" && tokenVar) {
234+ root.style.setProperty(tokenVar, colorValue);
235+ if (dynamicVar) {
236+ root.style.setProperty(dynamicVar, colorValue);
211237 }
238+ console.log(`[dumber-palette] Applied ${key}: ${colorValue} -> ${tokenVar}`);
239+ appliedCount++;
240+ } else {
241+ console.warn(`[dumber-palette] Skipped ${key}: colorValue=${colorValue}, tokenVar=${tokenVar}`);
212242 }
213243 }
214244 }
215245
246+ console.log(`[dumber-palette] Applied ${appliedCount} colors successfully`);
216247 document.dispatchEvent(
217248 new CustomEvent("dumber:palette-updated", { detail: { theme } })
218249 );
219250 } catch (err) {
220- console.error("[dumber] Failed to apply palette", err);
251+ console.error("[dumber-palette] EXCEPTION applying palette:", err);
221252 }
222253 };
223254
@@ -126,98 +126,129 @@ function detectWindowType(features?: string | null): string {
126 126
127 // Request color palettes from Go backend127 // Request color palettes from Go backend
128 try {128 try {
129+ console.log("[dumber-palette] Initializing color palette loading");
129 const bridge = window.webkit?.messageHandlers?.dumber;130 const bridge = window.webkit?.messageHandlers?.dumber;
130- if (bridge && typeof bridge.postMessage === "function") {
131- // Set up callback handler before requesting
132- window.__dumber_color_palettes = (palettes: unknown) => {
133- try {
134- // Type guard to ensure palettes is a valid object
135- if (typeof palettes !== "object" || palettes === null) {
136- console.error("[dumber] Invalid palettes received");
137- return;
138- }
139 131
140- window.__dumber_palette = palettes as Record<string, Record<string, string>>;132+ if (!bridge || typeof bridge.postMessage !== "function") {
133+ console.error("[dumber-palette] ERROR: No webkit message bridge available");
134+ return;
135+ }
136+
137+ console.log("[dumber-palette] WebKit bridge found, setting up handlers");
141 138
142- // Normalize palette keys to lowercase139+ // Set up callback handler before requesting
143- const normalized: Record<string, Record<string, string>> = {};140+ window.__dumber_color_palettes = (palettes: unknown) => {
144- if (window.__dumber_palette) {141+ console.log("[dumber-palette] Received palettes from backend:", palettes);
145- for (const key in window.__dumber_palette) {142+ try {
146- if (Object.prototype.hasOwnProperty.call(window.__dumber_palette, key)) {143+ // Type guard to ensure palettes is a valid object
147- normalized[key.toLowerCase()] = window.__dumber_palette[key];144+ if (typeof palettes !== "object" || palettes === null) {
145+ console.error("[dumber-palette] ERROR: Invalid palettes received - type:", typeof palettes);
146+ return;
147+ }
148+
149+ window.__dumber_palette = palettes as Record<string, Record<string, string>>;
150+ console.log("[dumber-palette] Palette keys:", Object.keys(window.__dumber_palette || {}));
151+
152+ // Normalize palette keys to lowercase
153+ const normalized: Record<string, Record<string, string>> = {};
154+ if (window.__dumber_palette) {
155+ for (const key in window.__dumber_palette) {
156+ if (Object.prototype.hasOwnProperty.call(window.__dumber_palette, key)) {
157+ const paletteValue = window.__dumber_palette[key];
158+ if (paletteValue) {
159+ normalized[key.toLowerCase()] = paletteValue;
160+ console.log(`[dumber-palette] Normalized: ${key} -> ${key.toLowerCase()}, colors:`, Object.keys(paletteValue));
148 }161 }
149 }162 }
150- window.__dumber_palette = normalized;
151 }163 }
164+ window.__dumber_palette = normalized;
165+ }
152 166
153- // Apply palette now that we have it167+ // Apply palette now that we have it
154- const initialTheme: "light" | "dark" = document.documentElement.classList.contains("dark") ? "dark" : "light";168+ const initialTheme: "light" | "dark" = document.documentElement.classList.contains("dark") ? "dark" : "light";
155- window.__dumber_initial_theme = initialTheme;169+ console.log(`[dumber-palette] Initial theme detected: ${initialTheme}`);
156- if (window.__dumber_applyPalette) {170+ window.__dumber_initial_theme = initialTheme;
157- window.__dumber_applyPalette(initialTheme);171+
158- }172+ if (window.__dumber_applyPalette) {
159- } catch (err) {173+ console.log("[dumber-palette] Applying palette...");
160- console.error("[dumber] Failed to apply received palette", err);174+ window.__dumber_applyPalette(initialTheme);
175+ } else {
176+ console.error("[dumber-palette] ERROR: __dumber_applyPalette function not found");
161 }177 }
162- };178+ } catch (err) {
179+ console.error("[dumber-palette] ERROR: Failed to apply received palette:", err);
180+ }
181+ };
163 182
164- window.__dumber_color_palettes_error = (error: string) => {183+ window.__dumber_color_palettes_error = (error: string) => {
165- console.error("[dumber] Failed to get color palettes:", error);184+ console.error("[dumber-palette] ERROR from backend:", error);
166- };185+ };
167 186
168- // Request color palettes187+ // Request color palettes
169- bridge.postMessage(JSON.stringify({ type: "get_color_palettes" }));188+ console.log("[dumber-palette] Sending get_color_palettes request to backend");
170- }189+ bridge.postMessage(JSON.stringify({ type: "get_color_palettes" }));
190+ console.log("[dumber-palette] Request sent, waiting for response...");
171 } catch (err) {191 } catch (err) {
172- console.error("[dumber] Failed to request color palettes", err);192+ console.error("[dumber-palette] EXCEPTION requesting color palettes:", err);
173 }193 }
174 194
175 // Palette application function195 // Palette application function
176 window.__dumber_applyPalette = (theme: "light" | "dark") => {196 window.__dumber_applyPalette = (theme: "light" | "dark") => {
197+ console.log(`[dumber-palette] applyPalette called with theme: ${theme}`);
177 try {198 try {
178 const palette = window.__dumber_palette?.[theme] || window.__dumber_palette?.["light"];199 const palette = window.__dumber_palette?.[theme] || window.__dumber_palette?.["light"];
179 if (!palette) {200 if (!palette) {
201+ console.error(`[dumber-palette] ERROR: No palette found for theme "${theme}". Available:`, Object.keys(window.__dumber_palette || {}));
180 return;202 return;
181 }203 }
204+ console.log(`[dumber-palette] Found palette for theme "${theme}":`, palette);
182 205
183 const tokenMap: Record<string, string> = {206 const tokenMap: Record<string, string> = {
184- Background: "--color-browser-bg",207+ background: "--color-browser-bg",
185- Surface: "--color-browser-surface",208+ surface: "--color-browser-surface",
186- SurfaceVariant: "--color-browser-surface-variant",209+ surface_variant: "--color-browser-surface-variant",
187- Text: "--color-browser-text",210+ text: "--color-browser-text",
188- Muted: "--color-browser-muted",211+ muted: "--color-browser-muted",
189- Accent: "--color-browser-accent",212+ accent: "--color-browser-accent",
190- Border: "--color-browser-border",213+ border: "--color-browser-border",
191 };214 };
192 215
193 const dynamicMap: Record<string, string> = {216 const dynamicMap: Record<string, string> = {
194- Background: "--dynamic-bg",217+ background: "--dynamic-bg",
195- Surface: "--dynamic-surface",218+ surface: "--dynamic-surface",
196- SurfaceVariant: "--dynamic-surface-variant",219+ surface_variant: "--dynamic-surface-variant",
197- Text: "--dynamic-text",220+ text: "--dynamic-text",
198- Muted: "--dynamic-muted",221+ muted: "--dynamic-muted",
199- Accent: "--dynamic-accent",222+ accent: "--dynamic-accent",
200- Border: "--dynamic-border",223+ border: "--dynamic-border",
201 };224 };
202 225
203 const root = document.documentElement;226 const root = document.documentElement;
227+ let appliedCount = 0;
204 for (const key in tokenMap) {228 for (const key in tokenMap) {
205 if (Object.prototype.hasOwnProperty.call(tokenMap, key)) {229 if (Object.prototype.hasOwnProperty.call(tokenMap, key)) {
206 const colorValue = palette[key];230 const colorValue = palette[key];
207- if (colorValue) {231+ const tokenVar = tokenMap[key];
208- root.style.setProperty(tokenMap[key], colorValue);232+ const dynamicVar = dynamicMap[key];
209- if (Object.prototype.hasOwnProperty.call(dynamicMap, key)) {233+ if (colorValue && typeof colorValue === "string" && tokenVar) {
210- root.style.setProperty(dynamicMap[key], colorValue);234+ root.style.setProperty(tokenVar, colorValue);
235+ if (dynamicVar) {
236+ root.style.setProperty(dynamicVar, colorValue);
211 }237 }
238+ console.log(`[dumber-palette] Applied ${key}: ${colorValue} -> ${tokenVar}`);
239+ appliedCount++;
240+ } else {
241+ console.warn(`[dumber-palette] Skipped ${key}: colorValue=${colorValue}, tokenVar=${tokenVar}`);
212 }242 }
213 }243 }
214 }244 }
215 245
246+ console.log(`[dumber-palette] Applied ${appliedCount} colors successfully`);
216 document.dispatchEvent(247 document.dispatchEvent(
217 new CustomEvent("dumber:palette-updated", { detail: { theme } })248 new CustomEvent("dumber:palette-updated", { detail: { theme } })
218 );249 );
219 } catch (err) {250 } catch (err) {
220- console.error("[dumber] Failed to apply palette", err);251+ console.error("[dumber-palette] EXCEPTION applying palette:", err);
221 }252 }
222 };253 };
223 254

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