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.