detached 382faa5

feat(gui): add global shadow dom host utility for component isolation

commit
382faa5b845d3447ef530528b5d5fa0511a1ad5d
author
bamen · · 2025-09-16 08:13 UTC
parent
3ef9418
signature
unsigned
1 file · +74 -0
A gui/src/injected/modules/shadowHost.ts +74 -0
@@ -0,0 +1,74 @@
1+/**
2+ * Global Shadow DOM Host Utility
3+ *
4+ * Ensures a single, shared ShadowRoot is created and reused for all injected UI components
5+ * (omnibox, toast, etc.), with a minimal CSS reset applied once.
6+ */
7+
8+// Track shadow-root initialization to avoid duplicate reset injection
9+const shadowResetApplied = new WeakSet<ShadowRoot>();
10+
11+export const GLOBAL_SHADOW_HOST_ID = 'dumber-ui-root';
12+
13+/**
14+ * Ensure and return the global ShadowRoot used by injected UI.
15+ */
16+export function getGlobalShadowRoot(): ShadowRoot {
17+ // Create or find the host container
18+ let host = document.getElementById(GLOBAL_SHADOW_HOST_ID) as HTMLElement | null;
19+ if (!host) {
20+ host = document.createElement('div');
21+ host.id = GLOBAL_SHADOW_HOST_ID;
22+ host.style.cssText = `
23+ position: fixed !important;
24+ top: 0 !important;
25+ left: 0 !important;
26+ right: 0 !important;
27+ bottom: 0 !important;
28+ z-index: 2147483647 !important;
29+ pointer-events: none !important;
30+ margin: 0 !important;
31+ padding: 0 !important;
32+ border: none !important;
33+ background: none !important;
34+ isolation: isolate !important;
35+ contain: layout style !important;
36+ `;
37+ document.documentElement.appendChild(host);
38+ }
39+
40+ const shadowRoot = host.shadowRoot ?? host.attachShadow({ mode: 'open' });
41+
42+ // Inject a minimal reset and base styles into the shadow root once
43+ if (!shadowResetApplied.has(shadowRoot)) {
44+ const resetStyle = document.createElement('style');
45+ resetStyle.textContent = `
46+ :host { all: initial; }
47+ *, *::before, *::after { box-sizing: border-box; }
48+ :host { font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue', Arial, sans-serif; }
49+ `;
50+ shadowRoot.appendChild(resetStyle);
51+ shadowResetApplied.add(shadowRoot);
52+ }
53+
54+ return shadowRoot;
55+}
56+
57+/**
58+ * Ensure and return a dedicated mount element inside the global ShadowRoot.
59+ * Use a unique id per feature (e.g., "dumber-omnibox", "dumber-toast").
60+ */
61+export function ensureShadowMount(mountId: string): HTMLElement {
62+ const root = getGlobalShadowRoot();
63+ let mount = root.getElementById?.(mountId) as HTMLElement | null;
64+ if (!mount) {
65+ mount = document.createElement('div');
66+ mount.id = mountId;
67+ mount.style.cssText = `
68+ position: relative;
69+ pointer-events: none; /* components inside control their own pointer events */
70+ `;
71+ root.appendChild(mount);
72+ }
73+ return mount;
74+}
@@ -0,0 +1,74 @@
1+/**
2+ * Global Shadow DOM Host Utility
3+ *
4+ * Ensures a single, shared ShadowRoot is created and reused for all injected UI components
5+ * (omnibox, toast, etc.), with a minimal CSS reset applied once.
6+ */
7+
8+// Track shadow-root initialization to avoid duplicate reset injection
9+const shadowResetApplied = new WeakSet<ShadowRoot>();
10+
11+export const GLOBAL_SHADOW_HOST_ID = 'dumber-ui-root';
12+
13+/**
14+ * Ensure and return the global ShadowRoot used by injected UI.
15+ */
16+export function getGlobalShadowRoot(): ShadowRoot {
17+ // Create or find the host container
18+ let host = document.getElementById(GLOBAL_SHADOW_HOST_ID) as HTMLElement | null;
19+ if (!host) {
20+ host = document.createElement('div');
21+ host.id = GLOBAL_SHADOW_HOST_ID;
22+ host.style.cssText = `
23+ position: fixed !important;
24+ top: 0 !important;
25+ left: 0 !important;
26+ right: 0 !important;
27+ bottom: 0 !important;
28+ z-index: 2147483647 !important;
29+ pointer-events: none !important;
30+ margin: 0 !important;
31+ padding: 0 !important;
32+ border: none !important;
33+ background: none !important;
34+ isolation: isolate !important;
35+ contain: layout style !important;
36+ `;
37+ document.documentElement.appendChild(host);
38+ }
39+
40+ const shadowRoot = host.shadowRoot ?? host.attachShadow({ mode: 'open' });
41+
42+ // Inject a minimal reset and base styles into the shadow root once
43+ if (!shadowResetApplied.has(shadowRoot)) {
44+ const resetStyle = document.createElement('style');
45+ resetStyle.textContent = `
46+ :host { all: initial; }
47+ *, *::before, *::after { box-sizing: border-box; }
48+ :host { font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue', Arial, sans-serif; }
49+ `;
50+ shadowRoot.appendChild(resetStyle);
51+ shadowResetApplied.add(shadowRoot);
52+ }
53+
54+ return shadowRoot;
55+}
56+
57+/**
58+ * Ensure and return a dedicated mount element inside the global ShadowRoot.
59+ * Use a unique id per feature (e.g., "dumber-omnibox", "dumber-toast").
60+ */
61+export function ensureShadowMount(mountId: string): HTMLElement {
62+ const root = getGlobalShadowRoot();
63+ let mount = root.getElementById?.(mountId) as HTMLElement | null;
64+ if (!mount) {
65+ mount = document.createElement('div');
66+ mount.id = mountId;
67+ mount.style.cssText = `
68+ position: relative;
69+ pointer-events: none; /* components inside control their own pointer events */
70+ `;
71+ root.appendChild(mount);
72+ }
73+ return mount;
74+}

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