refactor(chat): publish composer geometry, not a bare measurement - #29608
Draft
chrisnojima wants to merge 1 commit into
Draft
refactor(chat): publish composer geometry, not a bare measurement#29608chrisnojima wants to merge 1 commit into
chrisnojima wants to merge 1 commit into
Conversation
MaxInputAreaContext published a single number, so five modules each re-derived the composer's geometry from it plus their own insets and keyboard animation. The sticky offset existed twice byte-identically, the `- inputAreaHeight - 15` term twice in one file, and the 0.35 panel ratio twice across two files with different clamping. Re-syncing those copies is what the last two suggestion-popup fixes were doing. computeComposerBox now owns every derivation and every constant. The model ships as two contexts, split on whether a value comes from layout: the box changes identity when the conversation is measured, while the bottom inset, the sticky offset and the keyboard shared values do not. The message list reads only the latter, so measuring the box no longer re-renders it. Keep that seam — merging the two costs a list render on every mount and rotation, and there is a test that fails if you do. The keyboard-driven parts stay animated: they are worklet helpers taking the shared value's current frame, never React state. Behaviour is unchanged. Where two call sites disagreed today they still do: the command-markdown panel keeps its unclamped 35% and its 250pt pre-layout backstop, while the suggestion list keeps its [120, 240] clamp, its leftover-reserve cap and its 0. The suggestion popup is portaled to a host outside the router, so the conversation's contexts cannot reach it; it keeps its local hooks and its height clamp stays inert exactly as before, now documented. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rccpV5nLxxC5opF5xzrz7
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The refactor is cohesive, removes duplicated arithmetic safely, preserves documented behavioral differences, and adds targeted tests to lock in the geometry and render invariants.
Pull request overview
Refactors the mobile chat composer sizing logic to publish a structured “composer geometry” model (instead of a single raw measurement), centralizing all derivations/constants and splitting the published values across two contexts to avoid unnecessary message-list re-renders on layout measurement/rotation.
Changes:
- Introduces
composer-geometryas the single source of truth for composer box sizing, panel heights, and keyboard/inset-derived helpers. - Replaces the old
MaxInputAreaContextwithComposerAnchorContext(stable, non-layout-derived) andComposerBoxContext(layout-derived sizes). - Adds focused unit/component tests covering geometry arithmetic and the “anchor doesn’t re-render on measurement” invariant.
File summaries
| File | Description |
|---|---|
| shared/chat/conversation/normal/index.tsx | Provides the new anchor/box contexts and uses computeComposerBox for measured layout-derived sizing. |
| shared/chat/conversation/list-area/index.tsx | Switches list keyboard/inset math to read from ComposerAnchorContext and uses geometry helpers. |
| shared/chat/conversation/input-area/suggestors/index.tsx | Uses ComposerBoxContext/geometry helpers for suggestion area sizing while preserving portal fallback behavior. |
| shared/chat/conversation/input-area/normal/max-input-area-context.tsx | Removes the now-obsolete raw measurement context. |
| shared/chat/conversation/input-area/normal/input.tsx | Uses ComposerBoxContext + expandedInputMaxHeight for expanded input and suggestion list sizing. |
| shared/chat/conversation/composer-viewport-context.tsx | Adds the two new contexts and defines their non-conversation fallbacks. |
| shared/chat/conversation/composer-viewport-context.test.tsx | Adds render-probe tests validating context split and render behavior. |
| shared/chat/conversation/composer-geometry.ts | Adds centralized geometry model/constants and worklet-safe helpers. |
| shared/chat/conversation/composer-geometry.test.ts | Adds unit tests locking in the shipped arithmetic and edge cases. |
| shared/chat/conversation/command-markdown.tsx | Switches command-markdown max-height to the new geometry-derived value. |
Review details
- Files reviewed: 10/10 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
input-area/normal/max-input-area-context.tsxwas three lines — its entire interface wasReact.createContext(0).It published a raw measurement where callers needed a geometry model, so five modules re-derived that model from the integer plus their own inset and keyboard hooks:
{closed: -insets.bottom, opened: 0}appeared byte-identical innormal/index.tsx:147andsuggestors/index.tsx:457, the second with a 4-line comment explaining it must mirror the first.maxInputArea * 0.35appeared independently ininput.tsx:1263andcommand-markdown.tsx:20.- inputAreaHeight - 15appeared twice ~35 lines apart ininput.tsx, with15a bare literal both times.Two shipped fixes three weeks apart edited the same file to re-sync these derivations. Zero tests covered any of the arithmetic.
Change
One pure
computeComposerBox()owns every derivation and every constant, now named:composerPanelHeightRatio,expandedInputTopGap,commandMarkdownFallbackMaxHeight,singleLineHeight,threeLineHeight.The context is split along a stable/layout seam, so consumers only re-render for what they read:
ComposerAnchorContext—{bottomInset, stickyOffset, keyboardHeight, keyboardProgress}. Nothing layout-derived, so its identity survives every measurement.ListAreareads this and only this.ComposerBoxContext— the measured box and sizes derived from it. Read only by the composer's own panels.The invariant is stated on the type: "Do not add layout-derived fields." Keyboard values stay reanimated shared values — nothing became React state.
maxInputAreaand0.35now have zero occurrences outside the geometry module.Two disagreeing derivations, both preserved
commandMarkdownMaxHeightis unclamped with a 250pt backstop; the suggestion list is clamped to[120, 240]and reserve-capped with a 0 fallback. At h=200 these give 70 vs 16. Preserved as-is rather than silently unified.Dormant code, deliberately left dormant
MobileSuggestionArea's height clamp has never executed —@gorhom/portalrenders it at a host outside the router, so the provider never reaches it and the height has always beenundefined. Preserved exactly; switching it on is a real behaviour change.emptyViewportreproduces master's out-of-provider fallbacks exactly (250 / 0 / 36 / 78).Validation
lint:allclean —0 bailed out, 0 whole-props deps, tsc clean both projects.jest --runInBand— 232 suites / 2257 tests (baseline 230 / 2236).New
composer-geometry.test.ts(18 tests) andcomposer-viewport-context.test.tsxwith render-counting probes proving measuring the box does not re-render the list.