v0.10.0 0f4e9eb

app.ts RawHistoryFolder

1import type { HistoryEntry, SearchShortcut } from "../types/generated.js";
2
3export class AppService {
4  private history: HistoryEntry[] = [];
5  private shortcuts: Record<string, SearchShortcut> = {};
6
7  constructor() {}
8
9  async initialize(): Promise<void> {
10    try {
11      await Promise.all([this.loadHistory(), this.loadShortcuts()]);
12    } catch (error) {
13      console.error("Failed to initialize app:", error);
14    }
15  }
16
17  async loadHistory(): Promise<void> {
18    try {
19      // Use message bridge instead of fetch
20      return new Promise((resolve, reject) => {
21        // Set up response handler
22        window.__dumber_history_recent = (data: HistoryEntry[]) => {
23          this.history = Array.isArray(data) ? data : [];
24          resolve();
25        };
26
27        window.__dumber_history_error = (error: string) => {
28          console.error("Failed to load history:", error);
29          reject(new Error(error));
30        };
31
32        // Send message to Go backend
33        const bridge = window.webkit?.messageHandlers?.dumber;
34        if (bridge && typeof bridge.postMessage === "function") {
35          bridge.postMessage(
36            JSON.stringify({
37              type: "history_recent",
38              limit: 50,
39              offset: 0,
40            }),
41          );
42        } else {
43          reject(new Error("WebKit message handler not available"));
44        }
45      });
46    } catch (error) {
47      console.error("Failed to load history:", error);
48      // Mock history for development/fallback
49      this.history = [
50        {
51          id: 1,
52          url: "https://github.com/wailsapp/wails",
53          title: "Wails Framework",
54          visit_count: 3,
55          last_visited: new Date().toISOString(),
56          created_at: new Date().toISOString(),
57          favicon_url: null,
58        },
59        {
60          id: 2,
61          url: "https://go.dev",
62          title: "The Go Programming Language",
63          visit_count: 2,
64          last_visited: new Date().toISOString(),
65          created_at: new Date().toISOString(),
66          favicon_url: null,
67        },
68        {
69          id: 3,
70          url: "https://developer.mozilla.org",
71          title: "MDN Web Docs",
72          visit_count: 1,
73          last_visited: new Date().toISOString(),
74          created_at: new Date().toISOString(),
75          favicon_url: null,
76        },
77      ];
78    }
79  }
80
81  async loadShortcuts(): Promise<void> {
82    try {
83      const cfg = await fetch("/api/config").then((r) => r.json());
84      const raw = cfg?.search_shortcuts || {};
85      // Normalize field casing from backend (supports URL/Description and url/description)
86      const normalized: Record<string, SearchShortcut> = {};
87      for (const [key, value] of Object.entries(raw)) {
88        const v = value as Record<string, unknown>;
89        normalized[key] = {
90          id: 0,
91          shortcut: key,
92          url_template: (v.url ?? v.URL ?? "") as string,
93          description: (v.description ?? v.Description ?? "") as string,
94          created_at: null,
95        };
96      }
97      this.shortcuts = normalized;
98    } catch (error) {
99      console.error("Failed to load shortcuts:", error);
100      // Mock shortcuts for development/fallback
101      this.shortcuts = {
102        g: {
103          id: 0,
104          shortcut: "g",
105          url_template: "https://google.com/search?q={query}",
106          description: "Google Search",
107          created_at: null,
108        },
109        gh: {
110          id: 0,
111          shortcut: "gh",
112          url_template: "https://github.com/search?q={query}",
113          description: "GitHub Search",
114          created_at: null,
115        },
116        so: {
117          id: 0,
118          shortcut: "so",
119          url_template: "https://stackoverflow.com/search?q={query}",
120          description: "Stack Overflow",
121          created_at: null,
122        },
123        w: {
124          id: 0,
125          shortcut: "w",
126          url_template:
127            "https://en.wikipedia.org/wiki/Special:Search?search={query}",
128          description: "Wikipedia",
129          created_at: null,
130        },
131      };
132    }
133  }
134
135  getHistory(): HistoryEntry[] {
136    return this.history;
137  }
138
139  getShortcuts(): Record<string, SearchShortcut> {
140    return this.shortcuts;
141  }
142
143  async copyToClipboard(text: string): Promise<void> {
144    try {
145      await navigator.clipboard.writeText(text);
146      console.log("Copied to clipboard:", text);
147    } catch (err) {
148      console.error("Failed to copy:", err);
149    }
150  }
151}

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