experiments in a post-browser web
1/**
2 * Help Docs Extension Background Script
3 *
4 * Spawns a fullscreen transparent overlay window with animated
5 * documentation hints that appear from screen edges.
6 *
7 * Runs in isolated extension process (peek://ext/helpdocs/background.html)
8 */
9
10const api = window.app;
11const debug = api.debug;
12
13console.log('[ext:helpdocs] background init');
14
15const OVERLAY_ADDRESS = 'peek://ext/helpdocs/overlay.html';
16const STORAGE_KEY = 'helpdocs_enabled';
17
18let enabled = false;
19let overlayWindowId = null;
20let appFocused = true;
21
22/**
23 * Load enabled state
24 */
25const loadState = async () => {
26 try {
27 const stored = localStorage.getItem(STORAGE_KEY);
28 enabled = stored === 'true'; // Default disabled
29 console.log('[ext:helpdocs] Loaded state from localStorage - enabled:', enabled);
30 } catch (err) {
31 console.log('[ext:helpdocs] Failed to load state from localStorage:', err);
32 }
33 return enabled;
34};
35
36/**
37 * Save enabled state
38 */
39const saveState = (value) => {
40 enabled = value;
41 try {
42 localStorage.setItem(STORAGE_KEY, String(value));
43 console.log('[ext:helpdocs] Saved state to localStorage - enabled:', value);
44 } catch (err) {
45 console.error('[ext:helpdocs] Failed to save state to localStorage:', err);
46 }
47};
48
49/**
50 * Open the overlay window (fullscreen, transparent, click-through)
51 */
52const openOverlay = async () => {
53 if (overlayWindowId) {
54 const exists = await api.window.exists(overlayWindowId);
55 if (exists.success && exists.data) {
56 await api.window.show(overlayWindowId);
57 return;
58 }
59 overlayWindowId = null;
60 }
61
62 const screenW = window.screen.width;
63 const screenH = window.screen.height;
64
65 const params = {
66 key: OVERLAY_ADDRESS,
67 width: screenW,
68 height: screenH,
69 x: 0,
70 y: 0,
71 transparent: true,
72 alwaysOnTop: true,
73 skipTaskbar: true,
74 focusable: false,
75 resizable: false,
76 frame: false,
77 hasShadow: false,
78 escapeMode: 'ignore',
79 title: 'Help Docs',
80 centerOnParent: false,
81 show: true
82 };
83
84 try {
85 const result = await api.window.open(OVERLAY_ADDRESS, params);
86 if (result.success) {
87 overlayWindowId = result.id;
88 console.log('[ext:helpdocs] Overlay opened:', overlayWindowId);
89
90 // Make the window click-through but still receive mouse move events
91 // forward: true means mouse moves are still forwarded to the renderer
92 setTimeout(async () => {
93 try {
94 await api.window.setIgnoreMouseEvents(overlayWindowId, true, true);
95 console.log('[ext:helpdocs] Set click-through with forward');
96 } catch (err) {
97 console.error('[ext:helpdocs] Failed to set click-through:', err);
98 }
99 }, 500);
100 } else {
101 console.error('[ext:helpdocs] Failed to open overlay:', result.error);
102 }
103 } catch (error) {
104 console.error('[ext:helpdocs] Error opening overlay:', error);
105 }
106};
107
108const showOverlay = async () => {
109 if (overlayWindowId) {
110 const exists = await api.window.exists(overlayWindowId);
111 if (exists.success && exists.data) {
112 await api.window.show(overlayWindowId);
113 }
114 }
115};
116
117const hideOverlay = async () => {
118 if (overlayWindowId) {
119 const exists = await api.window.exists(overlayWindowId);
120 if (exists.success && exists.data) {
121 await api.window.hide(overlayWindowId);
122 }
123 }
124};
125
126const closeOverlay = async () => {
127 if (overlayWindowId) {
128 await api.window.close(overlayWindowId);
129 overlayWindowId = null;
130 console.log('[ext:helpdocs] Overlay closed');
131 }
132};
133
134const toggle = async () => {
135 const newState = !enabled;
136 saveState(newState);
137 if (newState) {
138 await openOverlay();
139 return { output: 'Help docs enabled', mimeType: 'text/plain' };
140 } else {
141 await closeOverlay();
142 return { output: 'Help docs disabled', mimeType: 'text/plain' };
143 }
144};
145
146// Commands
147const commandDefinitions = [
148 {
149 name: 'help docs',
150 description: 'Toggle help documentation overlay',
151 execute: async () => {
152 console.log('[ext:helpdocs] Toggle command executed');
153 return await toggle();
154 }
155 }
156];
157
158let registeredCommands = [];
159
160const initCommands = () => {
161 commandDefinitions.forEach(cmd => {
162 api.commands.register(cmd);
163 registeredCommands.push(cmd.name);
164 });
165 console.log('[ext:helpdocs] Registered commands:', registeredCommands);
166};
167
168const uninitCommands = () => {
169 registeredCommands.forEach(name => {
170 api.commands.unregister(name);
171 });
172 registeredCommands = [];
173};
174
175const init = async () => {
176 console.log('[ext:helpdocs] init');
177
178 await loadState();
179 initCommands();
180
181 // Track app focus to show/hide overlay
182 api.subscribe('app:focus-changed', async (msg) => {
183 appFocused = !!msg.focused;
184 if (enabled && overlayWindowId) {
185 if (appFocused) {
186 await showOverlay();
187 } else {
188 await hideOverlay();
189 }
190 }
191 }, api.scopes.GLOBAL);
192
193 if (enabled) {
194 await openOverlay();
195 }
196
197 console.log('[ext:helpdocs] Initialized, enabled:', enabled);
198};
199
200const uninit = () => {
201 console.log('[ext:helpdocs] uninit');
202 uninitCommands();
203 closeOverlay();
204};
205
206export default {
207 id: 'helpdocs',
208 labels: {
209 name: 'Help docs'
210 },
211 init,
212 uninit
213};