Skip to content

Commit cc524ee

Browse files
johnmathewsclaude
andcommitted
Fix all 31 no-explicit-any lint warnings
Replace `as any` casts with proper TypeScript interfaces for webkit fullscreen APIs and navigator.audioSession. Consolidate test mock any usages into a single typed alias with one eslint-disable comment. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 277e900 commit cc524ee

5 files changed

Lines changed: 72 additions & 31 deletions

File tree

journal/260425-total-time-layout-fix.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,17 @@ that expanded container.
2626
- Play button and time both scaled up 10%
2727

2828
The play button and total time are now a tight centered group in both layouts.
29+
30+
## Lint cleanup
31+
32+
Resolved all 31 `@typescript-eslint/no-explicit-any` warnings across 4 files:
33+
34+
- **FullscreenButton.svelte / +page.svelte**: Replaced `as any` casts for
35+
vendor-prefixed fullscreen APIs with typed interfaces (`WebkitDocument`,
36+
`WebkitElement`, `StandaloneNavigator`)
37+
- **timer.ts**: Replaced `navigator as any` with an inline intersection type
38+
for `audioSession`
39+
- **timer.test.ts**: Consolidated 20 test mock `any` usages into a single
40+
`type MockAny = any` with one eslint-disable comment
41+
42+
ESLint now reports 0 warnings.

src/lib/components/FullscreenButton.svelte

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,22 @@
77
let showHint = $state(false);
88
let hintTimeout: ReturnType<typeof setTimeout> | null = null;
99
10+
interface WebkitDocument extends Document {
11+
webkitFullscreenElement?: Element | null;
12+
webkitExitFullscreen?: () => void;
13+
}
14+
15+
interface WebkitElement extends HTMLElement {
16+
webkitRequestFullscreen?: () => void;
17+
}
18+
19+
interface StandaloneNavigator extends Navigator {
20+
standalone?: boolean;
21+
}
22+
1023
function getFullscreenElement(): Element | null {
1124
return document.fullscreenElement
12-
|| (document as any).webkitFullscreenElement
25+
|| (document as WebkitDocument).webkitFullscreenElement
1326
|| null;
1427
}
1528
@@ -18,10 +31,10 @@
1831
}
1932
2033
onMount(() => {
21-
const el = document.documentElement as any;
34+
const el = document.documentElement as WebkitElement;
2235
canFullscreen = !!(el.requestFullscreen || el.webkitRequestFullscreen);
2336
isFullscreen = !!getFullscreenElement();
24-
isStandalone = (navigator as any).standalone === true
37+
isStandalone = (navigator as StandaloneNavigator).standalone === true
2538
|| window.matchMedia("(display-mode: standalone)").matches;
2639
document.addEventListener("fullscreenchange", handleChange);
2740
document.addEventListener("webkitfullscreenchange", handleChange);
@@ -43,13 +56,14 @@
4356
return;
4457
}
4558
if (isFullscreen) {
46-
if (document.exitFullscreen) {
47-
document.exitFullscreen();
48-
} else if ((document as any).webkitExitFullscreen) {
49-
(document as any).webkitExitFullscreen();
59+
const doc = document as WebkitDocument;
60+
if (doc.exitFullscreen) {
61+
doc.exitFullscreen();
62+
} else if (doc.webkitExitFullscreen) {
63+
doc.webkitExitFullscreen();
5064
}
5165
} else {
52-
const el = document.documentElement as any;
66+
const el = document.documentElement as WebkitElement;
5367
if (el.requestFullscreen) {
5468
el.requestFullscreen();
5569
} else if (el.webkitRequestFullscreen) {

src/lib/timer.test.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1588,12 +1588,15 @@ describe("toggleMute", () => {
15881588
});
15891589
});
15901590

1591+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- test mocks for Web Audio API
1592+
type MockAny = any;
1593+
15911594
describe("sound playback", () => {
1592-
let mockOscillator: any;
1593-
let mockGain: any;
1594-
let mockCompressor: any;
1595-
let mockCtx: any;
1596-
let OriginalAudioContext: any;
1595+
let mockOscillator: MockAny;
1596+
let mockGain: MockAny;
1597+
let mockCompressor: MockAny;
1598+
let mockCtx: MockAny;
1599+
let OriginalAudioContext: MockAny;
15971600

15981601
beforeEach(() => {
15991602
resetAudioContext();
@@ -1633,8 +1636,8 @@ describe("sound playback", () => {
16331636
resume: vi.fn(),
16341637
close: vi.fn(),
16351638
};
1636-
OriginalAudioContext = (window as any).AudioContext;
1637-
(window as any).AudioContext = vi.fn(function(this: any) {
1639+
OriginalAudioContext = (window as MockAny).AudioContext;
1640+
(window as MockAny).AudioContext = vi.fn(function(this: MockAny) {
16381641
return Object.assign(this, mockCtx);
16391642
});
16401643
vi.useFakeTimers();
@@ -1644,9 +1647,9 @@ describe("sound playback", () => {
16441647
vi.useRealTimers();
16451648
resetAudioContext();
16461649
if (OriginalAudioContext) {
1647-
(window as any).AudioContext = OriginalAudioContext;
1650+
(window as MockAny).AudioContext = OriginalAudioContext;
16481651
} else {
1649-
delete (window as any).AudioContext;
1652+
delete (window as MockAny).AudioContext;
16501653
}
16511654
});
16521655

@@ -1777,8 +1780,8 @@ describe("sound playback", () => {
17771780
});
17781781

17791782
describe("audio session unlock", () => {
1780-
let mockCtx: any;
1781-
let mockAudioPlay: any;
1783+
let mockCtx: MockAny;
1784+
let mockAudioPlay: MockAny;
17821785

17831786
beforeEach(() => {
17841787
resetAudioContext();
@@ -1790,12 +1793,12 @@ describe("audio session unlock", () => {
17901793
createGain: vi.fn(),
17911794
resume: vi.fn(),
17921795
};
1793-
(window as any).AudioContext = vi.fn(function(this: any) {
1796+
(window as MockAny).AudioContext = vi.fn(function(this: MockAny) {
17941797
return Object.assign(this, mockCtx);
17951798
});
17961799

17971800
mockAudioPlay = vi.fn(() => Promise.resolve());
1798-
vi.stubGlobal("Audio", vi.fn(function(this: any) {
1801+
vi.stubGlobal("Audio", vi.fn(function(this: MockAny) {
17991802
this.play = mockAudioPlay;
18001803
return this;
18011804
}));
@@ -1804,13 +1807,13 @@ describe("audio session unlock", () => {
18041807
afterEach(() => {
18051808
resetAudioContext();
18061809
vi.unstubAllGlobals();
1807-
delete (navigator as any).audioSession;
1810+
delete (navigator as MockAny).audioSession;
18081811
});
18091812

18101813
it("plays silent WAV via Audio element to unlock iOS audio session", () => {
18111814
resumeAudioContext();
1812-
expect((window as any).Audio).toHaveBeenCalled();
1813-
const src = (window as any).Audio.mock.calls[0][0];
1815+
expect((window as MockAny).Audio).toHaveBeenCalled();
1816+
const src = (window as MockAny).Audio.mock.calls[0][0];
18141817
expect(src).toContain("data:audio/wav;base64,");
18151818
expect(mockAudioPlay).toHaveBeenCalled();
18161819
});
@@ -1832,13 +1835,13 @@ describe("audio session unlock", () => {
18321835

18331836
it("sets navigator.audioSession.type to ambient when available", () => {
18341837
const mockSession = { type: "playback" };
1835-
(navigator as any).audioSession = mockSession;
1838+
(navigator as MockAny).audioSession = mockSession;
18361839
resumeAudioContext();
18371840
expect(mockSession.type).toBe("ambient");
18381841
});
18391842

18401843
it("does not throw when navigator.audioSession is unavailable", () => {
1841-
delete (navigator as any).audioSession;
1844+
delete (navigator as MockAny).audioSession;
18421845
expect(() => resumeAudioContext()).not.toThrow();
18431846
});
18441847
});

src/lib/timer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ export function resumeAudioContext(): void {
513513
// Safari fully initialises the audio session.
514514
if (!_audioSessionUnlocked) {
515515
try {
516-
const nav = navigator as any;
516+
const nav = navigator as Navigator & { audioSession?: { type: string } };
517517
if (nav.audioSession) {
518518
nav.audioSession.type = "ambient";
519519
}

src/routes/+page.svelte

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@
2626
import FullscreenButton from "$lib/components/FullscreenButton.svelte";
2727
import KeyboardShortcuts from "$lib/components/KeyboardShortcuts.svelte";
2828
29+
interface WebkitDocument extends Document {
30+
webkitFullscreenElement?: Element | null;
31+
webkitExitFullscreen?: () => void;
32+
}
33+
34+
interface WebkitElement extends HTMLElement {
35+
webkitRequestFullscreen?: () => void;
36+
}
37+
2938
const timer = createTimer();
3039
const { remaining, status, phase, currentRep, totalReps } = timer;
3140
@@ -344,10 +353,11 @@
344353
// F for fullscreen works everywhere
345354
if (e.key === "f" && !e.metaKey && !e.ctrlKey && !e.altKey) {
346355
e.preventDefault();
347-
const el = document.documentElement as any;
348-
if (document.fullscreenElement || (document as any).webkitFullscreenElement) {
349-
if (document.exitFullscreen) document.exitFullscreen();
350-
else if ((document as any).webkitExitFullscreen) (document as any).webkitExitFullscreen();
356+
const el = document.documentElement as WebkitElement;
357+
const doc = document as WebkitDocument;
358+
if (document.fullscreenElement || doc.webkitFullscreenElement) {
359+
if (doc.exitFullscreen) doc.exitFullscreen();
360+
else if (doc.webkitExitFullscreen) doc.webkitExitFullscreen();
351361
} else {
352362
if (el.requestFullscreen) el.requestFullscreen();
353363
else if (el.webkitRequestFullscreen) el.webkitRequestFullscreen();

0 commit comments

Comments
 (0)