Skip to content

Commit 8c710b0

Browse files
authored
Make types and lint green (#1)
* WIP: Biome is happy, but still some types to fix * More typing * Enable lint in CI + clear up some dead code + fix some tests * Try ARM runner for speed * PR feedback * Format * Update e2e tests + remove useless ones * Lint
1 parent f4ceeb0 commit 8c710b0

30 files changed

+1024
-815
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,8 @@ jobs:
2727
- name: Install dependencies
2828
run: pnpm install
2929

30-
# TODO: fix first
31-
# - name: Lint
32-
# run: pnpm lint
30+
- name: Lint
31+
run: pnpm lint
3332

3433
- name: Type check
3534
run: pnpm typecheck
@@ -38,7 +37,7 @@ jobs:
3837
run: pnpm test:ci
3938

4039
- name: Install Playwright browsers
41-
run: pnpm exec playwright install --with-deps
40+
run: pnpm exec playwright install chromium --with-deps
4241

4342
- name: Run E2E tests (skip LLM-dependent tests)
4443
run: pnpm test:e2e

PLAN.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ Roadmap / TODO:
66
- see chat history
77
- share / export individual chats
88
- save complex function call sequence into action scripts
9-
- system prompt with and without tools
9+
- remove disable tool usage setting, not worth trying to support that use case
10+
- customisable system prompt
1011
- tutorial / welcome screen
1112
- handle text selection - add to context with little popup? that could also trigger opening the sidebar?
1213
- maybe also right click context menu element selection for interaction?

biome.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
"linter": {
44
"enabled": true,
55
"rules": {
6-
"recommended": true
6+
"recommended": true,
7+
"correctness": {
8+
"useUniqueElementIds": "warn"
9+
}
710
}
811
},
912
"formatter": {

entrypoints/background.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import browser from 'webextension-polyfill';
22
import { defineBackground } from 'wxt/utils/define-background';
33
import { backgroundLogger } from '~/utils/debug-logger';
44
import { messageHandler } from '~/utils/message-handler';
5+
import type { ExtendedBrowser, MessageFromSidebar } from '~/utils/types';
56

67
/**
78
* Background Script
@@ -13,11 +14,14 @@ export default defineBackground({
1314
main() {
1415
backgroundLogger.debug('Background script starting...');
1516

16-
if ((browser as any).sidePanel) {
17+
const extendedBrowser = browser as ExtendedBrowser;
18+
if (extendedBrowser.sidePanel) {
1719
backgroundLogger.debug('Chrome: Setting up sidePanel');
18-
(browser as any).sidePanel
20+
extendedBrowser.sidePanel
1921
.setPanelBehavior({ openPanelOnActionClick: true })
20-
.catch((error: any) => backgroundLogger.error('Error setting panel behavior', { error }));
22+
.catch((error: unknown) =>
23+
backgroundLogger.error('Error setting panel behavior', { error }),
24+
);
2125
}
2226

2327
if (browser.sidebarAction) {
@@ -34,7 +38,7 @@ export default defineBackground({
3438
backgroundLogger.debug('Setting up message listener...');
3539
browser.runtime.onMessage.addListener((message: unknown, _sender, sendResponse) => {
3640
backgroundLogger.debug('Background script received message', {
37-
messageType: (message as any)?.type,
41+
messageType: (message as MessageFromSidebar)?.type,
3842
});
3943

4044
// Handle async message processing

entrypoints/content.ts

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,32 @@
11
import browser from 'webextension-polyfill';
22
import { defineContentScript } from 'wxt/utils/define-content-script';
33
import { createLLMHelper } from '~/utils/llm-helper';
4+
import type { ContentScriptFunctionRequest } from '~/utils/types';
5+
6+
// Validation functions for tool arguments
7+
function validateStringArg(value: unknown, name: string): string {
8+
if (typeof value !== 'string') {
9+
throw new Error(`${name} must be a string, got ${typeof value}`);
10+
}
11+
return value;
12+
}
13+
14+
function validateOptionalStringArg(value: unknown, name: string): string | undefined {
15+
if (value === undefined || value === null) {
16+
return undefined;
17+
}
18+
if (typeof value !== 'string') {
19+
throw new Error(`${name} must be a string or undefined, got ${typeof value}`);
20+
}
21+
return value;
22+
}
23+
24+
function validateNumberArg(value: unknown, name: string): number {
25+
if (typeof value !== 'number') {
26+
throw new Error(`${name} must be a number, got ${typeof value}`);
27+
}
28+
return value;
29+
}
430

531
export default defineContentScript({
632
matches: ['<all_urls>'],
@@ -9,33 +35,52 @@ export default defineContentScript({
935
const LLMHelper = createLLMHelper();
1036

1137
// Make LLMHelper globally available
12-
(window as any).LLMHelper = LLMHelper;
38+
(window as typeof window & { LLMHelper: ReturnType<typeof createLLMHelper> }).LLMHelper =
39+
LLMHelper;
1340

1441
// Listen for messages from the extension
15-
browser.runtime.onMessage.addListener((request: any, _sender, sendResponse) => {
16-
if (request.type === 'EXECUTE_FUNCTION') {
42+
browser.runtime.onMessage.addListener((request: unknown, _sender, sendResponse) => {
43+
const typedRequest = request as ContentScriptFunctionRequest;
44+
if (typedRequest.type === 'EXECUTE_FUNCTION') {
1745
try {
18-
const functionName = request.function;
19-
const args = request.arguments || {};
46+
const functionName = typedRequest.function;
47+
const args = typedRequest.arguments || {};
2048

2149
if (functionName in LLMHelper) {
2250
// Handle function arguments properly based on function signature
23-
let result;
51+
let result: unknown;
2452
switch (functionName) {
2553
case 'find':
26-
result = LLMHelper.find(args.pattern, args.options);
54+
result = LLMHelper.find(
55+
validateStringArg(args.pattern, 'pattern'),
56+
args.options as
57+
| { limit?: number; type?: string; visible?: boolean; offset?: number }
58+
| undefined,
59+
);
2760
break;
2861
case 'click':
29-
result = LLMHelper.click(args.selector, args.text);
62+
result = LLMHelper.click(
63+
validateStringArg(args.selector, 'selector'),
64+
validateOptionalStringArg(args.text, 'text'),
65+
);
3066
break;
3167
case 'type':
32-
result = LLMHelper.type(args.selector, args.text, args.options);
68+
result = LLMHelper.type(
69+
validateStringArg(args.selector, 'selector'),
70+
validateStringArg(args.text, 'text'),
71+
args.options as
72+
| { clear?: boolean; delay?: number; pressEnter?: boolean }
73+
| undefined,
74+
);
3375
break;
3476
case 'extract':
35-
result = LLMHelper.extract(args.selector, args.property);
77+
result = LLMHelper.extract(
78+
validateStringArg(args.selector, 'selector'),
79+
validateOptionalStringArg(args.property, 'property'),
80+
);
3681
break;
3782
case 'describe':
38-
result = LLMHelper.describe(args.selector);
83+
result = LLMHelper.describe(validateStringArg(args.selector, 'selector'));
3984
break;
4085
case 'summary':
4186
result = LLMHelper.summary();
@@ -55,8 +100,11 @@ export default defineContentScript({
55100
return true; // Keep message channel open for async response
56101
case 'getResponsePage':
57102
// Handle getResponsePage asynchronously
58-
LLMHelper.getResponsePage(args.responseId, args.page)
59-
.then((result: any) => {
103+
LLMHelper.getResponsePage(
104+
validateStringArg(args.responseId, 'responseId'),
105+
validateNumberArg(args.page, 'page'),
106+
)
107+
.then((result: { result: unknown; _meta: unknown }) => {
60108
sendResponse({ success: true, result: result.result, _meta: result._meta });
61109
})
62110
.catch((error: unknown) => {

entrypoints/options/index.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import browser from 'webextension-polyfill';
22
import { DEFAULT_TRUNCATION_LIMIT } from '~/utils/constants';
33
import type { ExtensionSettings, MessageFromSidebar, MessageToSidebar } from '~/utils/types';
4-
import { DEFAULT_PROVIDERS } from '~/utils/types';
4+
import { DEFAULT_PROVIDERS, isExtensionSettings } from '~/utils/types';
55

66
class SettingsManager {
77
private currentSettings: ExtensionSettings | null = null;
@@ -30,9 +30,14 @@ class SettingsManager {
3030
console.debug('Settings response:', JSON.stringify(response));
3131

3232
if (response.type === 'SETTINGS_RESPONSE') {
33-
this.currentSettings = response.payload;
34-
this.populateForm();
35-
console.debug('Settings loaded successfully');
33+
if (isExtensionSettings(response.payload)) {
34+
this.currentSettings = response.payload;
35+
this.populateForm();
36+
console.debug('Settings loaded successfully');
37+
} else {
38+
console.error('Received invalid settings response');
39+
this.showMessage('Error loading settings. Please try refreshing.', 'error');
40+
}
3641
}
3742
} catch (error) {
3843
console.error('Error loading settings:', error);
@@ -109,7 +114,9 @@ class SettingsManager {
109114
// Auto-save for checkboxes
110115
const debugModeCheckbox = document.getElementById('debug-mode') as HTMLInputElement;
111116
const toolsEnabledCheckbox = document.getElementById('tools-enabled') as HTMLInputElement;
112-
const screenshotToolEnabledCheckbox = document.getElementById('screenshot-tool-enabled') as HTMLInputElement;
117+
const screenshotToolEnabledCheckbox = document.getElementById(
118+
'screenshot-tool-enabled',
119+
) as HTMLInputElement;
113120
debugModeCheckbox.addEventListener('change', () => this.autoSave());
114121
toolsEnabledCheckbox.addEventListener('change', () => this.autoSave());
115122
screenshotToolEnabledCheckbox.addEventListener('change', () => this.autoSave());
@@ -137,7 +144,9 @@ class SettingsManager {
137144
const apiKey = (document.getElementById('api-key-input') as HTMLInputElement).value;
138145
const debugMode = (document.getElementById('debug-mode') as HTMLInputElement).checked;
139146
const toolsEnabled = (document.getElementById('tools-enabled') as HTMLInputElement).checked;
140-
const screenshotToolEnabled = (document.getElementById('screenshot-tool-enabled') as HTMLInputElement).checked;
147+
const screenshotToolEnabled = (
148+
document.getElementById('screenshot-tool-enabled') as HTMLInputElement
149+
).checked;
141150
const truncationLimit =
142151
parseInt(
143152
(document.getElementById('truncation-limit-input') as HTMLInputElement).value,
@@ -149,8 +158,13 @@ class SettingsManager {
149158
return;
150159
}
151160

161+
if (!this.currentSettings) {
162+
console.warn('No current settings available for auto-save');
163+
return;
164+
}
165+
152166
const updatedSettings: ExtensionSettings = {
153-
...this.currentSettings!,
167+
...this.currentSettings,
154168
provider: {
155169
name: 'Custom',
156170
endpoint,
@@ -188,9 +202,14 @@ class SettingsManager {
188202
}
189203

190204
try {
205+
if (!this.currentSettings) {
206+
this.showMessage('Settings not loaded. Please refresh and try again.', 'error');
207+
return;
208+
}
209+
191210
// First save the current settings so the background script can test with them
192211
const updatedSettings: ExtensionSettings = {
193-
...this.currentSettings!,
212+
...this.currentSettings,
194213
provider: {
195214
name: (document.getElementById('provider-select') as HTMLInputElement).value || 'Custom',
196215
endpoint,
@@ -210,7 +229,7 @@ class SettingsManager {
210229
// Now test the connection using the background script
211230
const testMessage: MessageFromSidebar = {
212231
type: 'TEST_CONNECTION',
213-
payload: {},
232+
payload: null,
214233
};
215234

216235
const response = (await browser.runtime.sendMessage(testMessage)) as MessageToSidebar;

0 commit comments

Comments
 (0)