From aecf4648f81b374ae22af343709bc78314da01a4 Mon Sep 17 00:00:00 2001 From: windshift-agent Date: Sun, 13 Sep 2026 07:42:29 +0000 Subject: [PATCH] fix: enable mobile work item title and description editing (WI-1324) --- .../src/lib/mobile/MobileItemDetail.svelte | 110 ++++++++++++++++-- 1 file changed, 103 insertions(+), 7 deletions(-) diff --git a/frontend/src/lib/mobile/MobileItemDetail.svelte b/frontend/src/lib/mobile/MobileItemDetail.svelte index 59e80f23..3384bd0f 100644 --- a/frontend/src/lib/mobile/MobileItemDetail.svelte +++ b/frontend/src/lib/mobile/MobileItemDetail.svelte @@ -24,7 +24,8 @@ import BasePicker from '../pickers/BasePicker.svelte'; import UserPicker from '../pickers/UserPicker.svelte'; import Avatar from '../components/Avatar.svelte'; - import SafeMarkdown from '../components/SafeMarkdown.svelte'; + import Input from '../components/Input.svelte'; + import ItemDetailDescription from '../features/items/ItemDetailDescription.svelte'; let { itemId } = $props(); @@ -35,6 +36,11 @@ let transitioning = $state(false); let isWatching = $state(false); let watchBusy = $state(false); + let saving = $state(false); + let editingTitle = $state(false); + let editTitle = $state(''); + let editingDescription = $state(false); + let editDescription = $state(''); // Workflow-less personal tasks use the permitted transition endpoint to toggle // globally stable Open/Done IDs, matching desktop and PWA personal views. @@ -91,6 +97,8 @@ const summary = await loadMobileItemDetailSummary(id); if (token !== loadToken) return; item = summary?.item ?? null; + editTitle = item?.title || ''; + editDescription = item?.description || ''; transitions = summary?.transitions?.available_transitions ?? []; isWatching = summary?.watching || false; personalTaskCount = summary?.personal_task_count ?? 0; @@ -167,6 +175,59 @@ } } + async function saveTitle() { + const title = editTitle.trim(); + if (!title || title === item.title) { + editTitle = item.title || ''; + editingTitle = false; + return; + } + saving = true; + try { + const updated = await api.items.update(itemId, { title }); + item = { ...item, ...updated, title }; + editingTitle = false; + } catch (err) { + console.error('Failed to save title:', err); + } finally { + saving = false; + } + } + + function handleTitleKeydown(event) { + if (event.key === 'Enter') { + event.preventDefault(); + saveTitle(); + } else if (event.key === 'Escape') { + editTitle = item.title || ''; + editingTitle = false; + } + } + + function handleSaveField({ field, value }) { + if (field !== 'description') return; + editDescription = value; + saveDescription(); + } + + async function saveDescription() { + saving = true; + try { + const updated = await api.items.update(itemId, { description: editDescription }); + item = { ...item, ...updated, description: editDescription }; + editingDescription = false; + } catch (err) { + console.error('Failed to save description:', err); + } finally { + saving = false; + } + } + + function cancelDescription() { + editDescription = item.description || ''; + editingDescription = false; + } + async function updateAssignee(user) { const assigneeId = user?.id ?? null; if (assigneeId === (item.assignee_id ?? null)) return; @@ -411,7 +472,26 @@
{item.item_type_name}
{/if} -

{item.title}

+ {#if editingTitle} + + {:else} +

+ +

+ {/if} {#if item.due_date || personalTaskCount > 0} @@ -676,6 +769,9 @@ .type { font-size: 0.75rem; color: var(--ds-text-subtle); text-transform: uppercase; letter-spacing: 0.02em; } .title { font-size: 1.25rem; font-weight: var(--font-semibold, 600); color: var(--ds-text); margin: 0 0 1rem; line-height: 1.3; } + .title-button { display: block; width: 100%; padding: 0; border: none; background: transparent; color: inherit; font: inherit; text-align: left; cursor: text; } + .title-button:active { opacity: 0.7; } + .title-input { width: 100%; margin-bottom: 1rem; font-size: 1.25rem; font-weight: var(--font-semibold, 600); } /* Status + assignee picker field rows */ .fields {