From 5f2d2ed20fddd3a1f43b97aae6266c6c217aa8d3 Mon Sep 17 00:00:00 2001 From: bizzkoot Date: Sun, 11 Jan 2026 20:04:25 +0800 Subject: [PATCH 1/6] feat: add expand state signal and height calculation helpers --- packages/ui/src/components/prompt-input.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/ui/src/components/prompt-input.tsx b/packages/ui/src/components/prompt-input.tsx index 6bd83631..792b7880 100644 --- a/packages/ui/src/components/prompt-input.tsx +++ b/packages/ui/src/components/prompt-input.tsx @@ -46,10 +46,28 @@ export default function PromptInput(props: PromptInputProps) { const [pasteCount, setPasteCount] = createSignal(0) const [imageCount, setImageCount] = createSignal(0) const [mode, setMode] = createSignal<"normal" | "shell">("normal") + const [expandState, setExpandState] = createSignal<"normal" | "fifty" | "eighty">("normal") const SELECTION_INSERT_MAX_LENGTH = 2000 let textareaRef: HTMLTextAreaElement | undefined let containerRef: HTMLDivElement | undefined + const calculateContainerHeight = () => { + if (!containerRef) return 0 + const rect = containerRef.getBoundingClientRect() + const root = containerRef.closest(".session-view") + if (!root) return 0 + const rootRect = root.getBoundingClientRect() + return rootRect.height - rect.top + } + + const getExpandedHeight = (): string => { + const state = expandState() + if (state === "normal") return "auto" + const containerHeight = calculateContainerHeight() + if (state === "fifty") return `${containerHeight * 0.5}px` + return `${containerHeight * 0.8}px` + } + From 1f74dd58441cc579d8f269ef72c49ecd7d5cff84 Mon Sep 17 00:00:00 2001 From: bizzkoot Date: Sun, 11 Jan 2026 20:05:16 +0800 Subject: [PATCH 2/6] feat: create ExpandButton component with click/double-click logic --- packages/ui/src/components/expand-button.tsx | 72 ++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 packages/ui/src/components/expand-button.tsx diff --git a/packages/ui/src/components/expand-button.tsx b/packages/ui/src/components/expand-button.tsx new file mode 100644 index 00000000..3efe8cfb --- /dev/null +++ b/packages/ui/src/components/expand-button.tsx @@ -0,0 +1,72 @@ +import { createSignal, Show } from "solid-js" +import { Maximize2, Minimize2 } from "lucide-solid" + +interface ExpandButtonProps { + expandState: () => "normal" | "fifty" | "eighty" + onToggleExpand: (nextState: "normal" | "fifty" | "eighty") => void +} + +export default function ExpandButton(props: ExpandButtonProps) { + const [clickTime, setClickTime] = createSignal(0) + const DOUBLE_CLICK_THRESHOLD = 300 + + function handleClick() { + const now = Date.now() + const lastClick = clickTime() + const isDoubleClick = now - lastClick < DOUBLE_CLICK_THRESHOLD + + setClickTime(now) + + const current = props.expandState() + + if (isDoubleClick) { + // Double click behavior + if (current === "normal") { + props.onToggleExpand("fifty") + } else if (current === "fifty") { + props.onToggleExpand("eighty") + } else { + props.onToggleExpand("normal") + } + } else { + // Single click behavior + if (current === "normal") { + props.onToggleExpand("fifty") + } else { + props.onToggleExpand("normal") + } + } + + // Reset click timer after threshold + setTimeout(() => setClickTime(0), DOUBLE_CLICK_THRESHOLD) + } + + const getTooltip = () => { + const current = props.expandState() + if (current === "normal") { + return "Click to expand (50%) • Double-click to expand further (80%)" + } else if (current === "fifty") { + return "Double-click to expand to 80% • Click to minimize" + } else { + return "Click to minimize • Double-click to expand to 50%" + } + } + + return ( + + ) +} From ef7787c11707fb0528054a3c11245f50e973b473 Mon Sep 17 00:00:00 2001 From: bizzkoot Date: Sun, 11 Jan 2026 20:07:48 +0800 Subject: [PATCH 3/6] feat: integrate ExpandButton and apply dynamic height to textarea --- packages/ui/src/components/prompt-input.tsx | 22 +++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/prompt-input.tsx b/packages/ui/src/components/prompt-input.tsx index 792b7880..78431371 100644 --- a/packages/ui/src/components/prompt-input.tsx +++ b/packages/ui/src/components/prompt-input.tsx @@ -1,6 +1,7 @@ import { createSignal, Show, onMount, For, onCleanup, createEffect, on, untrack } from "solid-js" import { ArrowBigUp, ArrowBigDown } from "lucide-solid" import UnifiedPicker from "./unified-picker" +import ExpandButton from "./expand-button" import { addToHistory, getHistory } from "../stores/message-history" import { getAttachments, addAttachment, clearAttachments, removeAttachment } from "../stores/attachments" import { resolvePastedPlaceholders } from "../lib/prompt-placeholders" @@ -719,7 +720,13 @@ export default function PromptInput(props: PromptInputProps) { if (!props.onAbortSession || !props.isSessionBusy) return void props.onAbortSession() } - + + function handleExpandToggle(nextState: "normal" | "fifty" | "eighty") { + setExpandState(nextState) + // Keep focus on textarea + textareaRef?.focus() + } + function handleInput(e: Event) { const target = e.target as HTMLTextAreaElement @@ -1197,7 +1204,12 @@ export default function PromptInput(props: PromptInputProps) { onBlur={() => setIsFocused(false)} disabled={props.disabled} rows={4} - style={attachments().length > 0 ? { "padding-top": "8px" } : {}} + style={{ + "padding-top": attachments().length > 0 ? "8px" : "0", + "height": getExpandedHeight(), + "overflow-y": expandState() !== "normal" ? "auto" : "visible", + "transition": "height 0.25s ease", + }} spellcheck={false} autocorrect="off" autoCapitalize="off" @@ -1227,6 +1239,12 @@ export default function PromptInput(props: PromptInputProps) { +
+ +
Date: Sun, 11 Jan 2026 20:09:13 +0800 Subject: [PATCH 4/6] style: add expand button positioning and styles --- .../ui/src/styles/messaging/prompt-input.css | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/packages/ui/src/styles/messaging/prompt-input.css b/packages/ui/src/styles/messaging/prompt-input.css index 8a7baaac..efca4f8d 100644 --- a/packages/ui/src/styles/messaging/prompt-input.css +++ b/packages/ui/src/styles/messaging/prompt-input.css @@ -27,6 +27,7 @@ flex: 1 1 auto; height: 100%; min-width: 0; + transition: height 0.25s ease; } .prompt-input-field { @@ -105,6 +106,40 @@ cursor: not-allowed; } +.prompt-expand-top { + position: absolute; + top: 0.3rem; + right: 3.5rem; + display: flex; + align-items: center; + justify-content: center; + z-index: 2; +} + +.prompt-expand-button { + @apply w-9 h-9 flex items-center justify-center rounded-md; + color: var(--text-muted); + background-color: rgba(15, 23, 42, 0.04); + transition: background-color 0.15s ease, color 0.15s ease; + padding: 0; +} + +.prompt-expand-button:hover:not(:disabled) { + background-color: var(--surface-secondary); + color: var(--text-primary); +} + +.prompt-expand-button:active:not(:disabled) { + background-color: var(--accent-primary); + color: var(--text-inverted); + transform: scale(0.95); +} + +.prompt-expand-button:disabled { + opacity: 0.4; + cursor: not-allowed; +} + .prompt-overlay-text { display: inline-flex; align-items: center; From 9ebc230061573d7dd521ddb884479306f6ec6335 Mon Sep 17 00:00:00 2001 From: bizzkoot Date: Sun, 11 Jan 2026 20:17:19 +0800 Subject: [PATCH 5/6] docs: add expand chat input implementation plan --- docs/plans/2025-01-11-expand-chat-input.md | 521 +++++++++++++++++++++ 1 file changed, 521 insertions(+) create mode 100644 docs/plans/2025-01-11-expand-chat-input.md diff --git a/docs/plans/2025-01-11-expand-chat-input.md b/docs/plans/2025-01-11-expand-chat-input.md new file mode 100644 index 00000000..9e836544 --- /dev/null +++ b/docs/plans/2025-01-11-expand-chat-input.md @@ -0,0 +1,521 @@ +# Expand Chat Input Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Add an expand/minimize button to the chat input section that allows users to expand the textarea to 50% or 80% of the chat window height with single/double-click, maintaining Gemini-like UX. + +**Architecture:** Add a new state signal `expandState` to track 3 states (normal/50%/80%), create an `ExpandButton` component positioned alongside history buttons, calculate dynamic heights based on parent container, apply smooth CSS transitions, and add comprehensive hover tooltips explaining both click behaviors. + +**Tech Stack:** SolidJS (signals/effects), lucide-solid icons (Maximize2/Minimize2), CSS transitions, existing button styling patterns from prompt-input.css + +--- + +## Task 1: Add expand state signal and types + +**Files:** +- Modify: `packages/ui/src/components/prompt-input.tsx:33-52` + +**Step 1: Understand current state structure** + +Read lines 33-52 to see how signals like `prompt`, `mode`, `showPicker` are created. + +Expected: See `createSignal` patterns for boolean and string states. + +**Step 2: Add expand state signal after line 47** + +In `packages/ui/src/components/prompt-input.tsx`, after line 47 (`const [mode, setMode] = createSignal<"normal" | "shell">("normal")`), add: + +```typescript +const [expandState, setExpandState] = createSignal<"normal" | "fifty" | "eighty">("normal") +``` + +**Step 3: Add height calculation memos after line 58** + +Add these utility functions right after the signal definitions (before the createEffect on line 59): + +```typescript +const calculateContainerHeight = () => { + if (!containerRef) return 0 + const rect = containerRef.getBoundingClientRect() + const root = containerRef.closest(".session-view") + if (!root) return 0 + const rootRect = root.getBoundingClientRect() + return rootRect.height - rect.top +} + +const getExpandedHeight = (): string => { + const state = expandState() + if (state === "normal") return "auto" + const containerHeight = calculateContainerHeight() + if (state === "fifty") return `${containerHeight * 0.5}px` + return `${containerHeight * 0.8}px` +} +``` + +**Step 4: Verify no syntax errors** + +Run in terminal: +```bash +cd packages/ui && npm run type-check +``` + +Expected: No TypeScript errors in prompt-input.tsx. + +**Step 5: Commit** + +```bash +git add packages/ui/src/components/prompt-input.tsx +git commit -m "feat: add expand state signal and height calculation helpers" +``` + +--- + +## Task 2: Create ExpandButton component + +**Files:** +- Create: `packages/ui/src/components/expand-button.tsx` + +**Step 1: Create the component file** + +Create `packages/ui/src/components/expand-button.tsx` with this content: + +```typescript +import { createSignal, Show } from "solid-js" +import { Maximize2, Minimize2 } from "lucide-solid" + +interface ExpandButtonProps { + expandState: () => "normal" | "fifty" | "eighty" + onToggleExpand: (nextState: "normal" | "fifty" | "eighty") => void +} + +export default function ExpandButton(props: ExpandButtonProps) { + const [clickTime, setClickTime] = createSignal(0) + const DOUBLE_CLICK_THRESHOLD = 300 + + function handleClick() { + const now = Date.now() + const lastClick = clickTime() + const isDoubleClick = now - lastClick < DOUBLE_CLICK_THRESHOLD + + setClickTime(now) + + const current = props.expandState() + + if (isDoubleClick) { + // Double click behavior + if (current === "normal") { + props.onToggleExpand("fifty") + } else if (current === "fifty") { + props.onToggleExpand("eighty") + } else { + props.onToggleExpand("normal") + } + } else { + // Single click behavior + if (current === "normal") { + props.onToggleExpand("fifty") + } else { + props.onToggleExpand("normal") + } + } + + // Reset click timer after threshold + setTimeout(() => setClickTime(0), DOUBLE_CLICK_THRESHOLD) + } + + const getTooltip = () => { + const current = props.expandState() + if (current === "normal") { + return "Click to expand (50%) • Double-click to expand further (80%)" + } else if (current === "fifty") { + return "Double-click to expand to 80% • Click to minimize" + } else { + return "Click to minimize • Double-click to expand to 50%" + } + } + + return ( + + ) +} +``` + +**Step 2: Verify component syntax** + +Run in terminal: +```bash +cd packages/ui && npm run type-check +``` + +Expected: No TypeScript errors in expand-button.tsx. + +**Step 3: Commit** + +```bash +git add packages/ui/src/components/expand-button.tsx +git commit -m "feat: create ExpandButton component with click/double-click logic" +``` + +--- + +## Task 3: Integrate ExpandButton into PromptInput + +**Files:** +- Modify: `packages/ui/src/components/prompt-input.tsx:1-3, 1040-1250` + +**Step 1: Add import for ExpandButton** + +In `packages/ui/src/components/prompt-input.tsx`, at line 1 after the existing imports, add: + +```typescript +import ExpandButton from "./expand-button" +``` + +**Step 2: Add expand handler function** + +After the `handleAbort` function (around line 703), add: + +```typescript +function handleExpandToggle(nextState: "normal" | "fifty" | "eighty") { + setExpandState(nextState) + // Keep focus on textarea + textareaRef?.focus() +} +``` + +**Step 3: Render ExpandButton in JSX** + +Find the section where history buttons are rendered (around line 1188-1211). Right before the closing `` tag of the history buttons, add the ExpandButton: + +```typescript + +
+ +
+
+``` + +Wait - actually, the expand button should always show (not conditionally with history). Let me correct: + +Replace the above with - add this RIGHT AFTER the `` closing tag for history buttons (after line 1211): + +```typescript +
+ +
+``` + +**Step 4: Apply dynamic height to textarea** + +Find the textarea element (around line 1166-1187). Modify the style binding on the textarea to include dynamic height: + +Change from: +```typescript +style={attachments().length > 0 ? { "padding-top": "8px" } : {}} +``` + +To: +```typescript +style={{ + "padding-top": attachments().length > 0 ? "8px" : "0", + "height": getExpandedHeight(), + "overflow-y": expandState() !== "normal" ? "auto" : "visible", + "transition": "height 0.25s ease", +}} +``` + +**Step 5: Verify no errors** + +Run in terminal: +```bash +cd packages/ui && npm run type-check +``` + +Expected: No TypeScript errors. + +**Step 6: Commit** + +```bash +git add packages/ui/src/components/prompt-input.tsx +git commit -m "feat: integrate ExpandButton and apply dynamic height to textarea" +``` + +--- + +## Task 4: Add CSS styles for expand button positioning + +**Files:** +- Modify: `packages/ui/src/styles/messaging/prompt-input.css:72-107` + +**Step 1: Add prompt-expand-top styles** + +After the `.prompt-history-bottom` rule (around line 88), add: + +```css +.prompt-expand-top { + position: absolute; + top: 0.3rem; + right: 3.5rem; + display: flex; + align-items: center; + justify-content: center; + z-index: 2; +} + +.prompt-expand-button { + @apply w-9 h-9 flex items-center justify-center rounded-md; + color: var(--text-muted); + background-color: rgba(15, 23, 42, 0.04); + transition: background-color 0.15s ease, color 0.15s ease; + padding: 0; +} + +.prompt-expand-button:hover:not(:disabled) { + background-color: var(--surface-secondary); + color: var(--text-primary); +} + +.prompt-expand-button:active:not(:disabled) { + background-color: var(--accent-primary); + color: var(--text-inverted); + transform: scale(0.95); +} + +.prompt-expand-button:disabled { + opacity: 0.4; + cursor: not-allowed; +} +``` + +**Step 2: Adjust prompt-input-field-container for expanded states** + +After the `.prompt-input-field` rule (around line 37), add: + +```css +.prompt-input-field-container { + position: relative; + width: 100%; + min-height: 56px; + flex: 1 1 auto; + height: 100%; + min-width: 0; + transition: height 0.25s ease; +} +``` + +Actually, the min-height should remain. Let me provide the corrected version - modify the existing `.prompt-input-field-container` rule (lines 23-30) to add the transition: + +Change line 24 from: +```css +.prompt-input-field-container { + position: relative; + width: 100%; + min-height: 56px; + flex: 1 1 auto; + height: 100%; + min-width: 0; +} +``` + +To: +```css +.prompt-input-field-container { + position: relative; + width: 100%; + min-height: 56px; + flex: 1 1 auto; + height: 100%; + min-width: 0; + transition: height 0.25s ease; +} +``` + +**Step 3: Verify CSS syntax** + +Run in terminal: +```bash +npm run build --workspace @neuralnomads/codenomad-ui +``` + +Expected: Build succeeds with no CSS errors. + +**Step 4: Commit** + +```bash +git add packages/ui/src/styles/messaging/prompt-input.css +git commit -m "style: add expand button positioning and styles" +``` + +--- + +## Task 5: Test expand/minimize functionality in dev + +**Files:** +- No new files + +**Step 1: Start dev server** + +Run in terminal: +```bash +npm run dev --workspace @neuralnomads/codenomad-ui +``` + +Wait for the server to start and print the local URL. + +**Step 2: Test expand button visibility** + +- Open the dev app in browser +- Create/open a session +- Verify expand button appears in top-right corner of input area, left of arrow buttons +- Verify button shows Maximize2 icon + +**Step 3: Test single-click to 50% expand** + +- Click the expand button once +- Verify textarea height increases to ~50% of chat window +- Verify icon changes to Minimize2 +- Verify tooltip text updates + +**Step 4: Test double-click from normal to 80% expand** + +- Click expand button twice rapidly (within 300ms) +- Verify textarea height jumps to ~80% of chat window +- Verify scrollbar appears if content is long +- Type some text to verify scrolling works + +**Step 5: Test minimize from 80%** + +- Click expand button once +- Verify textarea collapses back to normal height (56px min-height) +- Verify icon changes back to Maximize2 + +**Step 6: Test expand button with 50% state** + +- Single-click expand button → should go to 50% +- Double-click expand button → should go to 80% +- Verify tooltip updates to "Double-click to expand to 80% • Click to minimize" + +**Step 7: Verify arrow buttons unchanged** + +- Verify up/down arrow buttons still visible and functional +- Verify Stop/Start buttons position unchanged +- Verify all buttons maintain original sizes + +**Step 8: Verify smooth transitions** + +- Watch height changes - should see smooth 250ms transition +- No jarring jumps or layout shifts + +**Expected Results:** +- All expand/minimize transitions work as specified +- No overlapping buttons +- Scrollbar appears correctly when needed +- Tooltips are comprehensive and helpful + +If any test fails, return to the task that needs fixing before continuing. + +**Step 9: Manual testing complete** + +Once all tests pass, stop the dev server: +```bash +Ctrl+C +``` + +**Step 10: Commit test verification** + +```bash +git add -A +git commit -m "test: verify expand button functionality and UX" +``` + +--- + +## Task 6: Final cleanup and verification + +**Files:** +- Review: `packages/ui/src/components/prompt-input.tsx` +- Review: `packages/ui/src/components/expand-button.tsx` +- Review: `packages/ui/src/styles/messaging/prompt-input.css` + +**Step 1: Verify no console errors** + +Restart dev server and check browser console: +```bash +npm run dev --workspace @neuralnomads/codenomad-ui +``` + +Open DevTools console - should show no warnings or errors related to expand button. + +**Step 2: Check responsive behavior** + +Resize browser window to different sizes - verify button positioning remains correct at all sizes. + +**Step 3: Verify with attachments** + +- Add an attachment (paste an image or large text) +- Expand the input +- Verify attachment chips display correctly above expanded textarea + +**Step 4: Test with history buttons** + +- Navigate through prompt history with arrow buttons while expanded +- Verify history navigation still works +- Verify expand button doesn't interfere with history buttons + +**Step 5: Build for production** + +```bash +npm run build --workspace @neuralnomads/codenomad-ui +``` + +Expected: Build succeeds with no errors. + +**Step 6: Final commit** + +```bash +git add -A +git commit -m "feat: complete expand chat input feature with full UX" +``` + +--- + +## Summary + +This plan implements the expand chat input feature across 6 tasks: + +1. ✅ Add expand state signal and height helpers +2. ✅ Create ExpandButton component with click/double-click logic +3. ✅ Integrate ExpandButton and apply dynamic heights +4. ✅ Add CSS styles for button and positioning +5. ✅ Test all functionality in dev +6. ✅ Final verification and build + +**Estimated time:** 45-60 minutes total + +**Key points:** +- Expand button positioned top-right, left of arrow buttons +- 3 states: normal (56px min) → 50% height → 80% height +- Single-click advances one state, double-click skips to 80% +- Smooth 250ms transitions with scrollbar support +- Comprehensive hover tooltips explain all behaviors +- Maintains all existing button positions and sizes From 1cb173dc607afcd4ed8f004b63c13058cfd6e80b Mon Sep 17 00:00:00 2001 From: bizzkoot Date: Sun, 11 Jan 2026 21:59:28 +0800 Subject: [PATCH 6/6] feat: implement expandable chat input with double-click detection and gradient tooltip - Add expand button with Maximize2/Minimize2 icons - Implement 3-state height management (normal/50%/80%) - Smart double-click detection with 300ms delay - Height calculation based on session-view - 200px message space - Custom CSS tooltip with dark gradient background and backdrop blur - Send button anchored at bottom via margin-top: auto - Smooth CSS transitions throughout - Double-click at 80% now reduces to 50% (not normal) - Removed all debug console.log statements --- packages/ui/src/components/expand-button.tsx | 43 +++++++---- packages/ui/src/components/prompt-input.tsx | 73 ++++++++++++------- .../ui/src/styles/messaging/prompt-input.css | 31 +++++++- 3 files changed, 103 insertions(+), 44 deletions(-) diff --git a/packages/ui/src/components/expand-button.tsx b/packages/ui/src/components/expand-button.tsx index 3efe8cfb..3c3d92b6 100644 --- a/packages/ui/src/components/expand-button.tsx +++ b/packages/ui/src/components/expand-button.tsx @@ -8,6 +8,7 @@ interface ExpandButtonProps { export default function ExpandButton(props: ExpandButtonProps) { const [clickTime, setClickTime] = createSignal(0) + const [clickTimer, setClickTimer] = createSignal(null) const DOUBLE_CLICK_THRESHOLD = 300 function handleClick() { @@ -15,30 +16,42 @@ export default function ExpandButton(props: ExpandButtonProps) { const lastClick = clickTime() const isDoubleClick = now - lastClick < DOUBLE_CLICK_THRESHOLD - setClickTime(now) + // Clear any pending single-click timer + const timer = clickTimer() + if (timer !== null) { + clearTimeout(timer) + setClickTimer(null) + } const current = props.expandState() if (isDoubleClick) { - // Double click behavior + // Double click behavior - execute immediately if (current === "normal") { - props.onToggleExpand("fifty") + props.onToggleExpand("eighty") } else if (current === "fifty") { props.onToggleExpand("eighty") } else { - props.onToggleExpand("normal") - } - } else { - // Single click behavior - if (current === "normal") { props.onToggleExpand("fifty") - } else { - props.onToggleExpand("normal") } - } + // Reset click time to prevent triple-click issues + setClickTime(0) + } else { + // Single click behavior - delay to wait for potential double-click + setClickTime(now) - // Reset click timer after threshold - setTimeout(() => setClickTime(0), DOUBLE_CLICK_THRESHOLD) + const newTimer = window.setTimeout(() => { + const currentState = props.expandState() + if (currentState === "normal") { + props.onToggleExpand("fifty") + } else { + props.onToggleExpand("normal") + } + setClickTimer(null) + }, DOUBLE_CLICK_THRESHOLD) + + setClickTimer(newTimer) + } } const getTooltip = () => { @@ -48,7 +61,7 @@ export default function ExpandButton(props: ExpandButtonProps) { } else if (current === "fifty") { return "Double-click to expand to 80% • Click to minimize" } else { - return "Click to minimize • Double-click to expand to 50%" + return "Click to minimize • Double-click to reduce to 50%" } } @@ -59,7 +72,7 @@ export default function ExpandButton(props: ExpandButtonProps) { onClick={handleClick} disabled={false} aria-label="Toggle chat input height" - title={getTooltip()} + data-tooltip={getTooltip()} > { - if (!containerRef) return 0 - const rect = containerRef.getBoundingClientRect() - const root = containerRef.closest(".session-view") - if (!root) return 0 - const rootRect = root.getBoundingClientRect() - return rootRect.height - rect.top - } + const calculateExpandedHeight = () => { + if (!containerRef) { + return 0 + } - const getExpandedHeight = (): string => { - const state = expandState() - if (state === "normal") return "auto" - const containerHeight = calculateContainerHeight() - if (state === "fifty") return `${containerHeight * 0.5}px` - return `${containerHeight * 0.8}px` - } + const root = containerRef.closest(".session-view") + if (!root) { + return 0 + } + const rootRect = root.getBoundingClientRect() + + // Reserve minimum space for message section (200px minimum) + const MIN_MESSAGE_SPACE = 200 + const availableForInput = rootRect.height - MIN_MESSAGE_SPACE + + return availableForInput + } + + const expandedHeight = createMemo(() => { + const state = expandState() + if (state === "normal") return "auto" + + const availableHeight = calculateExpandedHeight() + + if (state === "fifty") { + return `${availableHeight * 0.5}px` + } + return `${availableHeight * 0.8}px` + }) @@ -721,11 +734,11 @@ export default function PromptInput(props: PromptInputProps) { void props.onAbortSession() } - function handleExpandToggle(nextState: "normal" | "fifty" | "eighty") { - setExpandState(nextState) - // Keep focus on textarea - textareaRef?.focus() - } + function handleExpandToggle(nextState: "normal" | "fifty" | "eighty") { + setExpandState(nextState) + // Keep focus on textarea + textareaRef?.focus() + } function handleInput(e: Event) { @@ -1186,7 +1199,13 @@ export default function PromptInput(props: PromptInputProps) {
-
+