Skip to content

Commit 4bb9b90

Browse files
Add auto-growing prompt composer with capped scroll
1 parent f7e018b commit 4bb9b90

5 files changed

Lines changed: 45 additions & 4 deletions

File tree

Buffaly.CodexEmbedded.Web/wwwroot/app.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ const VS_SELECTION_MAX_PROMPT_CHARS = 4000;
123123
const BUILD_FIX_POLL_INTERVAL_MS = 2000;
124124
const BUILD_FIX_MAX_CHARS = 12000;
125125
const OPENAI_KEY_STATUS_CACHE_MS = 15000;
126+
const PROMPT_INPUT_MAX_HEIGHT_DESKTOP_PX = 360;
127+
const PROMPT_INPUT_MAX_HEIGHT_MOBILE_PX = 192;
126128
const UI_AUDIT_OUTGOING_TYPES = new Set([
127129
"session_create",
128130
"session_attach",
@@ -1382,6 +1384,33 @@ function rememberPromptDraftForState(state) {
13821384
persistPromptDraftState();
13831385
}
13841386

1387+
function getPromptInputMaxHeightPx() {
1388+
return isMobileViewport() ? PROMPT_INPUT_MAX_HEIGHT_MOBILE_PX : PROMPT_INPUT_MAX_HEIGHT_DESKTOP_PX;
1389+
}
1390+
1391+
function refreshPromptInputHeight(options = {}) {
1392+
if (!promptInput) {
1393+
return;
1394+
}
1395+
1396+
const reset = options.reset === true;
1397+
const maxHeightPx = getPromptInputMaxHeightPx();
1398+
promptInput.style.maxHeight = `${maxHeightPx}px`;
1399+
1400+
if (reset) {
1401+
promptInput.style.height = "";
1402+
promptInput.style.overflowY = "hidden";
1403+
return;
1404+
}
1405+
1406+
promptInput.style.height = "auto";
1407+
const scrollHeight = Math.max(0, promptInput.scrollHeight || 0);
1408+
if (scrollHeight > 0) {
1409+
promptInput.style.height = `${Math.min(scrollHeight, maxHeightPx)}px`;
1410+
}
1411+
promptInput.style.overflowY = scrollHeight > maxHeightPx ? "auto" : "hidden";
1412+
}
1413+
13851414
function clearCurrentPromptDraft() {
13861415
const key = getCurrentPromptDraftKey();
13871416
rememberPromptDraftForKey(key, "");
@@ -1408,6 +1437,7 @@ function restorePromptDraftForActiveSession(options = {}) {
14081437
if (promptInput.value !== normalized) {
14091438
promptInput.value = normalized;
14101439
}
1440+
refreshPromptInputHeight({ reset: normalized.length === 0 });
14111441

14121442
let nextImages = promptDraftImagesByKey.get(key);
14131443
if ((!Array.isArray(nextImages) || nextImages.length === 0)
@@ -5077,6 +5107,7 @@ function removeQueuedPrompt(sessionId, queueItemId) {
50775107

50785108
function restoreQueuedPromptForEditing(text, images = []) {
50795109
promptInput.value = String(text || "");
5110+
refreshPromptInputHeight({ reset: promptInput.value.length === 0 });
50805111

50815112
pendingComposerImages = normalizePromptDraftImages(images, { assignIds: true });
50825113
renderComposerImages();
@@ -8193,6 +8224,7 @@ promptInput.addEventListener("paste", async (event) => {
81938224
});
81948225

81958226
promptInput.addEventListener("input", () => {
8227+
refreshPromptInputHeight({ reset: promptInput.value.length === 0 });
81968228
rememberPromptDraftForState(getActiveSessionState());
81978229
refreshVsSelectionSnapshot().catch(() => {});
81988230
});
@@ -8386,6 +8418,7 @@ async function queueCurrentComposerPrompt() {
83868418
}
83878419

83888420
promptInput.value = "";
8421+
refreshPromptInputHeight({ reset: true });
83898422
clearCurrentPromptDraft();
83908423
clearComposerImages();
83918424
resetPlanModeNextTurn();
@@ -8423,6 +8456,7 @@ promptForm.addEventListener("submit", async (event) => {
84238456
if (prompt.localeCompare("cancel", undefined, { sensitivity: "accent" }) === 0) {
84248457
cancelPendingToolUserInput();
84258458
promptInput.value = "";
8459+
refreshPromptInputHeight({ reset: true });
84268460
clearCurrentPromptDraft();
84278461
clearComposerImages();
84288462
return;
@@ -8438,6 +8472,7 @@ promptForm.addEventListener("submit", async (event) => {
84388472

84398473
if (!pendingBuildFixClip && images.length === 0 && await tryHandleSlashCommand(prompt)) {
84408474
promptInput.value = "";
8475+
refreshPromptInputHeight({ reset: true });
84418476
clearCurrentPromptDraft();
84428477
return;
84438478
}
@@ -8522,6 +8557,7 @@ promptForm.addEventListener("submit", async (event) => {
85228557
}
85238558

85248559
promptInput.value = "";
8560+
refreshPromptInputHeight({ reset: true });
85258561
clearCurrentPromptDraft();
85268562
clearComposerImages();
85278563
resetPlanModeNextTurn();
@@ -8560,6 +8596,7 @@ promptInput.addEventListener("keydown", (event) => {
85608596

85618597
event.preventDefault();
85628598
promptInput.value = lastSent;
8599+
refreshPromptInputHeight({ reset: false });
85638600
promptInput.selectionStart = promptInput.selectionEnd = promptInput.value.length;
85648601
rememberPromptDraftForState(getActiveSessionState());
85658602
return;
@@ -8707,6 +8744,7 @@ window.addEventListener("resize", () => {
87078744
if (!isMobileViewport()) {
87088745
setMobileProjectsOpen(false);
87098746
}
8747+
refreshPromptInputHeight({ reset: promptInput.value.length === 0 });
87108748
updateMobileProjectsButton();
87118749
updateConversationMetaVisibility();
87128750
updateScrollToBottomButton();
@@ -8757,6 +8795,7 @@ document.addEventListener("visibilitychange", () => {
87578795
});
87588796

87598797
applySavedUiSettings();
8798+
refreshPromptInputHeight({ reset: promptInput.value.length === 0 });
87608799
renderComposerImages();
87618800
renderVsSelectionIndicator();
87628801
startVsSelectionPolling();

Buffaly.CodexEmbedded.Web/wwwroot/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<link rel="icon" type="image/x-icon" href="images/favicon.ico">
88
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
99
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
10-
<link rel="stylesheet" href="site.css?v=20260307-sidebar-collapse-header1">
10+
<link rel="stylesheet" href="site.css?v=20260307-prompt-autogrow1">
1111
</head>
1212
<body>
1313
<div class="layout">
@@ -446,7 +446,7 @@
446446
<script src="sessionTimeline.js?v=20260227-turnanchor2"></script>
447447
<script src="scribe.js?v=20260303-openai-key-check1"></script>
448448
<script src="app.shared.js?v=20260228-cleanup1"></script>
449-
<script src="app.js?v=20260304-turn-retry1"></script>
449+
<script src="app.js?v=20260307-prompt-autogrow1"></script>
450450
<script src="app.diffs.js?v=20260304-ui-audit-reason2"></script>
451451
</body>
452452
</html>

Buffaly.CodexEmbedded.Web/wwwroot/site.css

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2084,9 +2084,11 @@ body {
20842084
border: 1px solid #d1d5db;
20852085
border-radius: 18px;
20862086
min-height: 7.2rem;
2087+
max-height: 22.5rem;
20872088
background: #ffffff;
20882089
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.05);
2089-
resize: vertical;
2090+
resize: none;
2091+
overflow-y: hidden;
20902092
font: inherit;
20912093
line-height: 1.35;
20922094
}
@@ -5322,7 +5324,7 @@ html[data-theme="dark"] .server-row-action-btn:hover {
53225324
.prompt-form textarea {
53235325
width: 100%;
53245326
min-height: 5.2rem;
5325-
max-height: 10rem;
5327+
max-height: 12rem;
53265328
padding: 0.5rem 0.62rem;
53275329
}
53285330

0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)