Skip to content

fix: rename let app in firebaseConfig.js to stop shadowing window.app in inline event handlers#4

Open
DDancingDeath wants to merge 1 commit into
mainfrom
fix/firebaseconfig-app-shadowing
Open

fix: rename let app in firebaseConfig.js to stop shadowing window.app in inline event handlers#4
DDancingDeath wants to merge 1 commit into
mainfrom
fix/firebaseconfig-app-shadowing

Conversation

@DDancingDeath
Copy link
Copy Markdown
Owner

Summary

Renames let app -> let firebaseApp in www/firebaseConfig.js to stop a hidden global-lexical-scope shadow that breaks inline event handlers like onclick="app.foo()".

How it was found

Built a Playwright interaction-driver in the staging clone repo (DDancingDeath/AadhatManagementApp-staging) that clicks every interactive button on every tab — not just renders pages. Within minutes it surfaced that the entire AI Assistant tab in the staging clone was throwing Cannot read properties of undefined (reading 'sendFromInput') on every Send-button click.

Existing JSDOM tests passed because they only string-matched HTML; they never invoked any handler. So the bug was invisible from automated tests, invisible from DevTools (window.app looked perfect), and looked fine to a casual visual smoke check (the chat tab visibly rendered).

Root cause

firebaseConfig.js is loaded as a classic <script> (not a module). In that context, a top-level let app creates a binding in the realm''s global lexical environment. That environment lives "above" window in the scope chain that inline event handlers see. So onclick="app.X()" resolves app to this Firebase App instance — even though window.app.X exists and works fine when called from the console or from module code.

Result: any onclick="app.foo()" button silently throws and does nothing.

What this PR changes

File Change
www/firebaseConfig.js Rename let app -> let firebaseApp (4 lines). Added a 16-line warning comment above the declaration so the trap is documented inline for future maintainers.
www/js/__tests__/firebase-app-shadow.test.js New. Two pinned regression tests: (1) bans let|const|var app as a top-level binding in firebaseConfig.js; (2) sanity-checks the result of firebase.initializeApp(...) is bound to a different name (firebaseApp etc.).

What this PR does NOT change

  • No HTML template was touched. Prod''s navigation.html already uses window.app.nav.X() form for every inline handler, so nothing in the running app currently resolves against the shadowed binding. This rename is a pure no-op for current behaviour. The fix is defence-in-depth — when the AI Assistant module (separate WALK-1 follow-up) lands in prod, the fix will already be in place.
  • No public API. window.app continues to be set by main.js exactly as before. The exports of firebaseConfig.js (auth, db, etc.) are unchanged.
  • No build, deploy, or service-worker change.

Testing

  • npx jest105 passed / 105 (was 103; +2 new regression tests).
  • Manual: applied identical fix to staging, deployed to https://aadhat-staging.web.app, ran Playwright interaction-driver. AI Assistant Send button — broken for months in staging by this exact bug — now sends and receives real replies sourced from live Firestore data.

Why a narrow PR

The chat module itself doesn''t exist in prod yet (tracked as WALK-1 — "AI Assistant non-functional"). Porting the staging chat module is a much larger follow-up. This PR ships only the SDK-level shadowing fix + its regression test, so the future chat port doesn''t also have to debug a years-old SDK trap.

Same bug class to watch out for

Any classic <script> that does let foo = ... or const foo = ... at top level is creating a global lexical binding that isn''t a window.foo property but DOES shadow window.foo for inline event handlers. Other repo-wide identifiers worth checking: db, auth, firebase. (None are currently used in inline handlers, but worth grepping if more inline handlers get added.)

…pp in inline event handlers

Found via Playwright interaction-driver in the staging clone repo
(DDancingDeath/AadhatManagementApp-staging). Full RCA: STG-WALK-5 in
that repo's docs/REVIEW_ISSUES.md.

## The bug

firebaseConfig.js declared `let app = firebase.initializeApp(...)` at
the top level of a classic <script>. In that context, `let app`
creates a binding in the **global lexical environment** that lives
"above" `window` in the scope chain inline event handlers see. So
`onclick="app.X()"` in any HTML template resolves `app` to the
**Firebase App instance** (which has no `.X` method) — even though
`window.app.X` is defined and works fine from console / module code.

The shadow is invisible from DevTools (`window.app` looks correct) so
the bug is silent: every inline `onclick="app.foo()"` button throws
`Cannot read properties of undefined (reading 'foo')` and does
nothing.

In the staging repo this took out the entire AI Assistant feature
(every quick-question button + the Send button). Once the AI
Assistant lands in prod (separate work), the same shadow would break
it instantly.

## The fix

1. Rename `let app` -> `let firebaseApp` in firebaseConfig.js (4 lines
   touched). 16-line warning comment added above the declaration so
   future maintainers don't accidentally rename it back.
2. New `www/js/__tests__/firebase-app-shadow.test.js` Jest test that
   FAILS if any future commit reintroduces `let|const|var app` as a
   top-level binding in firebaseConfig.js. The test also asserts the
   Firebase init result is bound to a name other than `app`.

## What does NOT change

- No HTML template was modified. All existing inline handlers in prod
  already use `window.app.nav.X()` form (see navigation.html), so
  this rename is a pure no-op for the running app — nothing was
  resolving against the shadowed binding.
- No public API. `window.app` continues to be set by main.js exactly
  as before. The `auth`, `db`, `firebase` exports of firebaseConfig.js
  are unchanged.
- No build, deploy, or service worker change.

## Testing

- `npx jest`: 105/105 passing (was 103; +2 new regression tests)
- Manual: opened the deployed staging URL with the same fix applied,
  every existing inline `window.app.nav.showTab(...)` continues to
  work; AI Assistant Send button — broken in staging by this exact
  bug — now sends and receives real replies.

## Why this is a separate PR (not bundled with the chat fix)

The chat module itself doesn't exist in prod (WALK-1: AI Assistant
non-functional). Porting the staging chat module is a much larger
follow-up. This PR ships ONLY the firebaseConfig.js shadowing fix +
its regression test, so the future chat port doesn't have to also
debug a years-old SDK shadowing trap.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants