From 12becd1f4ba4a22796fce1f6a515151f9cee17ee Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Fri, 22 May 2026 23:17:00 +0000 Subject: [PATCH 1/3] fix(chat): rename _inflightStateLimits() to _getInflightStateLimits() to fix v0.51.117 collision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #2771. v0.51.117 (PR #2766) introduced a top-level function _inflightStateLimits() in static/ui.js that collided with the window._inflightStateLimits config object set in static/boot.js. Because top-level function declarations in classic (non-module) scripts attach to window, boot.js's assignment overwrote the function reference, and every later _inflightStateLimits() call threw TypeError. _compactInflightState() runs on every send(), so no new chat session could be created — v0.51.117 is effectively unusable. Reported by @jahilldev, with multiple users (@isma3iloiso, @theDanielJLewis, @JHVenn) confirming the bug or reverting to v0.51.116. Fix: rename the function to _getInflightStateLimits() — the window-attached config key stays under its original name (unchanged for any downstream code that reads it). Updates all 4 call sites in static/ui.js. Tests: - Update tests/test_inflight_storage_quota.py — the existing test asserted 'function _inflightStateLimits()' in UI_JS as a positive presence check, which certified the bug. Now asserts the renamed function name is present AND the old colliding name is absent AND no stale call sites remain. - Add tests/test_window_function_collision.py — generalized regression that scans every static JS file for top-level function declarations whose name also appears as the target of 'window.X = {...}' or 'window.X = '. This is the exact shape that broke #2715 (_pinnedSessionsLimit in v0.51.106) and #2771. Test fails with a precise diagnostic naming the file and symbol if the bug class returns. Confirmed test FAILS on current master (unfixed) and PASSES on this branch. Verified end-to-end against the live browser before commit: - typeof window._inflightStateLimits === 'object' (config preserved) - typeof window._getInflightStateLimits === 'function' - _getInflightStateLimits() returns the limits object - saveInflightState() persists to localStorage without throwing Full pytest suite: 6308 passed, 6 skipped, 3 xpassed, 8 subtests passed. Opus advisor: SHIP. --- CHANGELOG.md | 4 + static/ui.js | 8 +- tests/test_inflight_storage_quota.py | 13 +- tests/test_window_function_collision.py | 151 ++++++++++++++++++++++++ 4 files changed, 170 insertions(+), 6 deletions(-) create mode 100644 tests/test_window_function_collision.py diff --git a/CHANGELOG.md b/CHANGELOG.md index ea880f72..f465c492 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## [Unreleased] +### Fixed + +- **PR TBD** — fix(chat): rename `_inflightStateLimits()` in `static/ui.js` to `_getInflightStateLimits()` so it no longer collides with the `window._inflightStateLimits` config object set in `static/boot.js`. Closes #2771. The v0.51.117 in-flight-recovery quota fix (#2766) declared a top-level helper with the same name as a window-attached config object; because top-level `function foo(){…}` declarations in classic (non-module) scripts attach to `window`, boot.js's `window._inflightStateLimits = {…}` assignment overwrote the function reference before any session could send. Every new chat broke on first `send()` with `TypeError: _inflightStateLimits is not a function`, leaving v0.51.117 effectively un-usable. Renamed the function only (the public-ish window key is unchanged) and updated all 4 call sites. **New regression test `tests/test_window_function_collision.py` scans every static JS file for top-level `function NAME()` declarations whose name is also the target of `window.NAME = {…}` / `= `, the exact shape that broke #2715 (`_pinnedSessionsLimit` in v0.51.106) and #2771 (`_inflightStateLimits` in v0.51.117). The test fails loudly with a precise file:name diagnostic if the bug class returns. Verified end-to-end against the live browser before merge: `_getInflightStateLimits()` returns the limits object and `saveInflightState()` persists to localStorage without throwing. + ## [v0.51.117] — 2026-05-22 — Release CO (stage-pr2766 — 1-PR — in-flight recovery storage quota-safe) ### Fixed diff --git a/static/ui.js b/static/ui.js index 8e123c3f..3737fff8 100644 --- a/static/ui.js +++ b/static/ui.js @@ -4081,7 +4081,7 @@ function _boundedInflightInt(value, fallback, min, max){ if(!Number.isFinite(n)) return fallback; return Math.max(min, Math.min(max, n)); } -function _inflightStateLimits(){ +function _getInflightStateLimits(){ const configured=(typeof window!=='undefined'&&window._inflightStateLimits&&typeof window._inflightStateLimits==='object')?window._inflightStateLimits:{}; return { maxSessions:_boundedInflightInt(configured.maxSessions, INFLIGHT_STATE_DEFAULT_LIMITS.maxSessions, 1, 25), @@ -4110,7 +4110,7 @@ function _isStorageQuotaError(err){ ); } function _truncateInflightValue(value, maxChars){ - const limits=_inflightStateLimits(); + const limits=_getInflightStateLimits(); const stringLimit=_boundedInflightInt(maxChars, limits.stringChars, 1000, 500000); if(typeof value==='string'){ if(value.length<=stringLimit) return value; @@ -4125,7 +4125,7 @@ function _truncateInflightValue(value, maxChars){ return value; } function _compactInflightState(state){ - const limits=_inflightStateLimits(); + const limits=_getInflightStateLimits(); const messages=Array.isArray(state.messages)?state.messages.slice(-limits.messages):[]; const toolCalls=Array.isArray(state.toolCalls)?state.toolCalls.slice(-limits.toolCalls):[]; return _truncateInflightValue({ @@ -4136,7 +4136,7 @@ function _compactInflightState(state){ }, limits.stringChars); } function _writeInflightStateMap(all){ - const limits=_inflightStateLimits(); + const limits=_getInflightStateLimits(); const entries=Object.entries(all||{}) .sort((a,b)=>Number(b[1]&&b[1].updated_at||0)-Number(a[1]&&a[1].updated_at||0)) .slice(0,limits.maxSessions); diff --git a/tests/test_inflight_storage_quota.py b/tests/test_inflight_storage_quota.py index 615863c7..deba33d0 100644 --- a/tests/test_inflight_storage_quota.py +++ b/tests/test_inflight_storage_quota.py @@ -28,7 +28,7 @@ def test_inflight_state_is_compacted_before_localstorage_write(): compact_body = _function_body(UI_JS, "_compactInflightState") assert "const entry={..._compactInflightState(state),updated_at:Date.now()};" in save_body - assert "const limits=_inflightStateLimits();" in compact_body + assert "const limits=_getInflightStateLimits();" in compact_body assert ".slice(-limits.messages)" in compact_body assert ".slice(-limits.toolCalls)" in compact_body assert "limits.jsonChars" in UI_JS @@ -49,7 +49,16 @@ def test_inflight_state_limits_are_configurable_from_settings(): assert "window._inflightStateLimits={" in BOOT_JS assert "maxSessions:parseInt(s.inflight_state_max_sessions||8,10)||8" in BOOT_JS assert "messages:parseInt(s.inflight_state_max_messages||24,10)||24" in BOOT_JS - assert "function _inflightStateLimits()" in UI_JS + # The reader function MUST use a different name than the window-attached + # config object — top-level `function foo(){}` in non-module scripts + # attaches to `window`, so a collision causes boot.js to overwrite the + # function with the config object and every later call throws + # `_inflightStateLimits is not a function`. See #2771. + assert "function _getInflightStateLimits()" in UI_JS + assert "function _inflightStateLimits()" not in UI_JS, ( + "Function name must not collide with window._inflightStateLimits " + "config object (#2771)." + ) assert "window._inflightStateLimits" in UI_JS assert "INFLIGHT_STATE_MAX_SESSIONS = 3" not in UI_JS assert "INFLIGHT_STATE_MAX_MESSAGES = 8" not in UI_JS diff --git a/tests/test_window_function_collision.py b/tests/test_window_function_collision.py new file mode 100644 index 00000000..4c5dfaac --- /dev/null +++ b/tests/test_window_function_collision.py @@ -0,0 +1,151 @@ +"""Regression coverage for the function-name × window-attached-config collision class. + +This test guards against a specific failure mode that has caused two +brick-class regressions (v0.51.106 #2715 `_pinnedSessionsLimit`, v0.51.117 +#2771 `_inflightStateLimits`): + + - Some module declares `function foo(){...}` at top level. Since the + WebUI ships classic (non-module) scripts via `