Skip to content

Decide on the strict tsconfig flags wxt 0.21 introduced (currently opted out) #688

Open

Description

@lmorchard

Background

wxt 0.21's generated .wxt/tsconfig.json turns on four compiler options that 0.20.27 did not:

  • verbatimModuleSyntax
  • noUncheckedIndexedAccess
  • noImplicitOverride
  • noFallthroughCasesInSwitch

Because packages/extension/tsconfig.json extends the generated config and maps pilo-core/core and pilo-core/ariaTree to core's sources via paths, the extension typecheck applies these to packages/core/src/** as well as the extension's own code. The wxt 0.21 bump (#687) therefore surfaced 64 errors in files it didn't touch.

In #687 I kept verbatimModuleSyntax on — it's the check that catches the value-import-of-a-type pattern that now hard-fails the rolldown/vite 8 release build ([MISSING_EXPORT] "Logger" is not exported by "../core/src/core.ts") — and fixed the 19 imports it flagged. The other three flags are explicitly disabled in packages/extension/tsconfig.json as a holding position, since adopting them meant 45 more changes inside a dependency bump.

What this issue is (and isn't)

I went through all 45. These are not latent bugs. Almost every one is a provably-safe access that the flag can't narrow:

  • packages/core/src/webAgent.ts:979 — this.messages[i] inside for (let i = this.messages.length - 1; i >= 0; i--)
  • packages/core/src/webAgent.ts:1369 — aiResponse.toolResults[0] guarded by if (aiResponse.toolResults.length > 1)
  • packages/core/src/browser/ariaTree/roleUtils.ts:480-489 — tokens[index] inside while (index < tokens.length)
  • packages/extension/src/background/ExtensionBrowser.ts:990-993 — return tabs[0] guarded by if (!tabs.length || !tabs[0].id) throw
  • 21 of the 45 are container.querySelector(...) / mock.calls[n] destructuring in test files

The two with any teeth are still low-impact:

  • ExtensionBrowser.ts ×5 — const [{ result }] = await browser.scripting.executeScript(...) throws TypeError: Cannot destructure property 'result' of 'undefined' if the array comes back empty (target frame gone). All five sites are already inside try/catch, so the observable difference is a confusing TypeError in the log instead of a clean error.
  • ExtensionBrowser.ts:246 — dataUrl.split(",")[1] passed to Buffer.from; only reachable if captureVisibleTab returns something that isn't a data URL.

packages/core/src/events.ts:547 (noImplicitOverride, TS4114) is the one genuinely correct finding: emit does override EventEmitter.emit and should say so. It's a one-word fix.

So this is a code-hygiene / config-coherence issue, not a correctness one. Filing it so the opt-out doesn't silently become permanent by accident.

Options

  1. Adopt the flags monorepo-wide — turn them on in core, cli, server and extension in one deliberate PR and fix the 45 sites. Coherent, and the flags then actually hold. Cost: a wide mechanical diff, mostly ! and ?. in tests.
  2. Adopt them for the extension only — smaller, but core would then be checked at two different strictness levels depending on which package compiles it, and core's own tsconfig.json wouldn't enforce it, so it would rot straight back.
  3. Keep the opt-out permanently and replace the comment in packages/extension/tsconfig.json with a link to this issue explaining the decision.

My read: (1) if the team wants the strictness, (3) if it doesn't. (2) is the worst of both. There's no urgency either way — nothing here is broken today.

packages/core/src/events.ts:547 is worth doing regardless of which option wins.

Full list

45 errors attributable to the three disabled flags
../core/src/browser/ariaTree/cssTokenizer.ts(101,5)  TS2322: Type 'number | undefined' is not assignable to type 'number'.
../core/src/browser/ariaTree/roleUtils.ts(480,26)  TS2532: Object is possibly 'undefined'.
../core/src/browser/ariaTree/roleUtils.ts(485,9)  TS2532: Object is possibly 'undefined'.
../core/src/browser/ariaTree/roleUtils.ts(489,26)  TS2532: Object is possibly 'undefined'.
../core/src/browser/ariaTree/roleUtils.ts(677,34)  TS2345: Argument of type 'HTMLOptionElement | undefined' is not assignable to parameter of type 'Element'.
../core/src/events.ts(547,3)  TS4114: This member must have an 'override' modifier because it overrides a member in the base class 'EventEmitter<string | symbol, any>'.
../core/src/snapshotCompressor.ts(167,7)  TS2322: Type 'string | undefined' is not assignable to type 'string'.
../core/src/webAgent.ts(1369,24)  TS2532: Object is possibly 'undefined'.
../core/src/webAgent.ts(1384,26)  TS18048: 'toolResult' is possibly 'undefined'.
../core/src/webAgent.ts(1936,24)  TS18048: 'firstToolResult' is possibly 'undefined'.
../core/src/webAgent.ts(979,11)  TS18048: 'msg' is possibly 'undefined'.
../core/src/webAgent.ts(979,48)  TS18048: 'msg' is possibly 'undefined'.
../core/src/webAgent.ts(980,9)  TS2322: Type '{ content: any[]; } | { content: any[]; role: "system"; providerOptions?: SharedV4ProviderOptions | undefined; } | { content: any[]; role: "user"; providerOptions?: SharedV4ProviderOptions | undefined; } | { ...; } | { ...; }' is not assignable to type 'ModelMessage'.
../core/src/webAgent.ts(982,21)  TS18048: 'msg' is possibly 'undefined'.
e2e/fixtures/extension.ts(72,15)  TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
src/background/ExtensionBrowser.ts(127,16)  TS2339: Property 'result' does not exist on type 'InjectionResult | undefined'.
src/background/ExtensionBrowser.ts(169,16)  TS2339: Property 'result' does not exist on type 'InjectionResult | undefined'.
src/background/ExtensionBrowser.ts(246,26)  TS2769: No overload matches this call.
src/background/ExtensionBrowser.ts(589,16)  TS2339: Property 'result' does not exist on type 'InjectionResult | undefined'.
src/background/ExtensionBrowser.ts(690,16)  TS2339: Property 'result' does not exist on type 'InjectionResult | undefined'.
src/background/ExtensionBrowser.ts(951,18)  TS2339: Property 'result' does not exist on type 'InjectionResult | undefined'.
src/background/ExtensionBrowser.ts(990,26)  TS2532: Object is possibly 'undefined'.
src/background/ExtensionBrowser.ts(993,5)  TS2322: Type 'Tab | undefined' is not assignable to type 'Tab'.
src/ui/components/sidepanel/SidePanel.tsx(93,23)  TS2345: Argument of type 'Tab | undefined' is not assignable to parameter of type 'SetStateAction<Tab | null>'.
test/background/configSeed.test.ts(136,20)  TS2532: Object is possibly 'undefined'.
test/background/configSeed.test.ts(240,29)  TS2532: Object is possibly 'undefined'.
test/background/configSeed.test.ts(260,20)  TS2532: Object is possibly 'undefined'.
test/background/configSeed.test.ts(273,20)  TS2532: Object is possibly 'undefined'.
test/background/indicatorControl.test.ts(147,45)  TS18048: 'lastCall' is possibly 'undefined'.
test/background/indicatorControl.test.ts(79,23)  TS18048: 'call' is possibly 'undefined'.
test/components/sidepanel/ChatView.test.tsx(1013,33)  TS2532: Object is possibly 'undefined'.
test/components/sidepanel/ChatView.test.tsx(1052,22)  TS2532: Object is possibly 'undefined'.
test/components/sidepanel/ChatView.test.tsx(1053,23)  TS2532: Object is possibly 'undefined'.
test/components/sidepanel/ChatView.test.tsx(13,10)  TS2532: Object is possibly 'undefined'.
test/components/sidepanel/ChatView.test.tsx(462,9)  TS18048: 'orphanEl' is possibly 'undefined'.
test/components/sidepanel/ChatView.test.tsx(700,35)  TS2532: Object is possibly 'undefined'.
test/components/sidepanel/ChatView.test.tsx(729,35)  TS2532: Object is possibly 'undefined'.
test/components/sidepanel/ChatView.test.tsx(756,35)  TS2532: Object is possibly 'undefined'.
test/components/sidepanel/ChatView.test.tsx(786,35)  TS2532: Object is possibly 'undefined'.
test/components/sidepanel/ChatView.test.tsx(815,35)  TS2532: Object is possibly 'undefined'.
test/components/sidepanel/ChatView.test.tsx(844,35)  TS2532: Object is possibly 'undefined'.
test/components/sidepanel/ChatView.test.tsx(913,33)  TS2532: Object is possibly 'undefined'.
test/components/sidepanel/ChatView.test.tsx(937,33)  TS2532: Object is possibly 'undefined'.
test/components/sidepanel/ChatView.test.tsx(967,33)  TS2532: Object is possibly 'undefined'.
test/components/sidepanel/ChatView.test.tsx(990,33)  TS2532: Object is possibly 'undefined'.

Reproduce by removing the three false overrides from packages/extension/tsconfig.json and running pnpm --filter pilo-extension run typecheck.

Activity

  1. lmorchard commented on Sep 15, 2026

    @lmorchard
    CollaboratorAuthor

    I lean toward option 3, since option 1 sounds like some ocean boiling for unclear benefits.

  2. lmorchard commented on Sep 15, 2026

    @lmorchard
    CollaboratorAuthor

    Measured numbers (supersedes the "~45 findings" above)

    The count in the issue body came from the extension typecheck only, which sees extension code plus whatever of core/src is reachable through the pilo-core/core barrel. That undercounts some areas and double-counts others. Here are real per-flag numbers, deduplicated across packages — cli, server and extension all typecheck core via paths, so raw per-package counts overlap heavily.

    Flag Unique sites
    noFallthroughCasesInSwitch 0
    noImplicitOverride 5 (3 files, all core/src)
    noUncheckedIndexedAccess 55 as CI is configured today, +145 in core/test

    noUncheckedIndexedAccess by area:

    Area Sites
    extension/test 21
    core/src 14
    cli/test 10
    extension/src 9
    extension/e2e 1

    PR #691 takes the first two flags — 0 sites and 5 one-word annotations. That reduces this issue to a single question about noUncheckedIndexedAccess.

    On that remaining question

    31 of the 55 sites are in test files, where the flag buys close to nothing — a test indexing mock.calls[0] fails either way. That leaves 24 production sites, which I've now read in full across two audit passes (the 45 from the original extension run, plus the two only reachable from a core-only typecheck: bidiBrowser.ts:120, guarded by if (treeResult?.contexts?.length > 0), and playwrightBrowser.ts:543, where the index is % total).

    Zero are real bugs. Every one is a provably-safe access the flag can't narrow. The closest to a defect is still the five ExtensionBrowser executeScript destructures, which would throw a confusing TypeError rather than a clean error — and all five are already inside try/catch.

    So the honest case for this flag is forward-looking — it catches future unguarded indexing — not remedial. That's a legitimate reason to adopt it, but it's a different argument than "we have latent bugs," and worth deciding on its own terms.

    Related finding

    packages/core/tsconfig.json excludes **/*.test.ts, so core's 993 tests are not typechecked by any config or CI job. They're clean at baseline (verified), but that's where 145 of the 200 total sites live. Filed separately rather than tangled into this decision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions