summaryrefslogtreecommitdiffhomepage
path: root/packages/frontend/src/lib/components/ChatInput.svelte
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-02 14:43:16 +0900
committerAdam Malczewski <[email protected]>2026-06-02 14:43:16 +0900
commit3ebcd49c404ed287a97af159ac8adfa63d572849 (patch)
treef8245ed28530a8e96046221eb1d7eca47d508dc8 /packages/frontend/src/lib/components/ChatInput.svelte
parent7c527b4d8a72159954405e720d5bf776802dc0ff (diff)
downloaddispatch-3ebcd49c404ed287a97af159ac8adfa63d572849.tar.gz
dispatch-3ebcd49c404ed287a97af159ac8adfa63d572849.zip
feat(tabs): drag-reorder + double-click rename + per-tab chat draft
- TabBar: HTML5 drag-and-drop to reorder user tabs (subagent tabs untouched); double-click a tab title to rename (Enter/blur confirm, Escape cancel). - Store: add reorderTabs/renameTab/setDraft; per-tab in-memory `draft` and `manualTitle` fields. Manual rename suppresses first-message auto-title. - ChatInput: bind to the active tab's draft so switching tabs saves/restores unsent text instead of clobbering it. - Backend: updateTabPositions() + PATCH /tabs/reorder persist tab order to the existing `position` column; tabs without a stored position fall to the end then get explicit positions on first reorder. - Tests: store reorder/rename/auto-title-guard/draft coverage; core updateTabPositions coverage (FakeDatabase extended with transaction support).
Diffstat (limited to 'packages/frontend/src/lib/components/ChatInput.svelte')
-rw-r--r--packages/frontend/src/lib/components/ChatInput.svelte21
1 files changed, 16 insertions, 5 deletions
diff --git a/packages/frontend/src/lib/components/ChatInput.svelte b/packages/frontend/src/lib/components/ChatInput.svelte
index 0c99078..71eb496 100644
--- a/packages/frontend/src/lib/components/ChatInput.svelte
+++ b/packages/frontend/src/lib/components/ChatInput.svelte
@@ -4,12 +4,17 @@ import { tabStore } from "../tabs.svelte.js";
const MAX_LINES = 7;
let inputEl: HTMLTextAreaElement | undefined;
-let inputValue = $state("");
const agentStatus = $derived(tabStore.activeTab?.agentStatus ?? "idle");
const tabId = $derived(tabStore.activeTab?.id ?? "");
+// The current input text lives on the active tab (in-memory draft), so
+// switching tabs saves the current draft and restores the target tab's text
+// automatically — drafts are never lost or clobbered by tab switching.
+const inputValue = $derived(tabStore.activeTab?.draft ?? "");
$effect(() => {
+ // Re-focus when switching tabs.
+ void tabId;
inputEl?.focus();
});
@@ -29,13 +34,19 @@ function resize() {
el.style.overflowY = el.scrollHeight > maxHeight ? "auto" : "hidden";
}
-// Re-run resize whenever the value changes (covers programmatic clears too).
+// Re-run resize whenever the value changes (covers tab switches and
+// programmatic clears too).
$effect(() => {
// Touch inputValue so this effect tracks it.
void inputValue;
resize();
});
+function handleInput(e: Event) {
+ if (!tabId) return;
+ tabStore.setDraft(tabId, (e.currentTarget as HTMLTextAreaElement).value);
+}
+
function handleKeydown(e: KeyboardEvent) {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
@@ -46,7 +57,7 @@ function handleKeydown(e: KeyboardEvent) {
function submit() {
const text = inputValue.trim();
if (!text) return;
- inputValue = "";
+ if (tabId) tabStore.setDraft(tabId, "");
tabStore.sendMessage(text);
}
</script>
@@ -75,12 +86,12 @@ function submit() {
{/if}
<textarea
bind:this={inputEl}
- bind:value={inputValue}
+ value={inputValue}
rows="1"
placeholder="Type a message..."
class="textarea textarea-ghost flex-1 resize-none leading-normal !min-h-0 h-auto"
onkeydown={handleKeydown}
- oninput={resize}
+ oninput={handleInput}
></textarea>
<button
type="button"