# Corporate AI chat final fix report Date: 2026-07-26 ## Fixed finding The browser now enforces the same request budget as the chat route before it mutates conversation state or starts a request: - New messages are accepted through 32,000 characters and rejected at 32,001. - The composer prevents submission, retains the complete invalid draft, and exposes an accessible validation error instead of silently truncating input. - The hook independently rejects over-limit calls, so non-composer callers cannot create an invalid or retryable local state. - Completed history is reduced to the newest deterministic context suffix that fits at most 100 messages, 32,000 characters per message, and 128,000 total characters. - A retry is unavailable unless the immediate latest completed user message is valid and remains the tail of the sanitized request context; retrying also rechecks the current payload before transport. - Server validation consumes the shared limit constants, preventing client and server budgets from drifting. ## TDD evidence The focused RED run failed for the intended missing behaviors: - 32,001 characters still called `authFetch`. - 51 completed turns plus a new message sent 103 messages. - The composer exposed no over-limit error and left Send enabled. - The request-window module did not yet exist. - Persisted invalid history had no non-retryable state. - A native-limit paste silently truncated the draft, and a whitespace-only latest user message could be skipped to retry an older prompt. After implementation and independent review fixes, the focused suite passed 52/52 tests. Boundary coverage includes 32,000 accepted, 32,001 rejected, exactly 100 messages, trimming above 100 messages, exactly 128,000 total characters, trimming above 128,000, and an oversized historical-message boundary. ## Verification | Check | Result | | --- | --- | | Focused tests | Passed: 4 files, 52 tests. | | `npm test` | Passed: 11 files, 95 tests. | | `npm run lint` | Passed: exit 0. | | `npx tsc --noEmit` | Passed: exit 0. | | `npm run build` | Passed: compiled, type-checked, and generated 7/7 static pages. The first sandboxed attempt failed only because Turbopack could not bind its internal port; the approved retry outside the sandbox passed. | ## Residual review findings These findings were intentionally left unchanged because they are independent of request-budget enforcement, require a separate product/interaction choice, or overlap the user's pre-existing dirty files: - Explicit Geist typography is absent. Restoring it affects global typography; `app/globals.css` already has user-owned unstaged changes. - The custom model menu does not dismiss on Tab/focus leave. This is a separate focus-management change and needs its own interaction tests. - A model selected before the first message is not persisted. This requires a new user-scoped preference/storage contract. - Streaming always scrolls to the bottom. A follow-mode policy needs explicit user-intent detection and dedicated scroll tests. - The light radial body gradient is not neutralized in dark mode. `app/globals.css` is user-owned and unstaged. - The OG card copy/model visual remains mismatched. Image generation was not called again, as explicitly prohibited. - The CSP test does not explicitly assert production absence of `unsafe-eval`. This is independent configuration-test coverage. - `vite-tsconfig-paths` remains declared but unused. `package.json` and `package-lock.json` contain user-owned unstaged changes and were not touched. ## Scope protection Only the chat request-limit implementation, its tests, and this report are staged for the final fix commit. Pre-existing changes to shadcn setup, package metadata/lockfile, utilities, and global CSS remain unstaged.