Skip to content

Commit bf30a06

Browse files
authored
Merge pull request #253 from Windshiftapp/agent-runs/run-142
2 parents 9ebdbae + aecf464 commit bf30a06

1 file changed

Lines changed: 103 additions & 7 deletions

File tree

frontend/src/lib/mobile/MobileItemDetail.svelte

Lines changed: 103 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
import BasePicker from '../pickers/BasePicker.svelte';
2525
import UserPicker from '../pickers/UserPicker.svelte';
2626
import Avatar from '../components/Avatar.svelte';
27-
import SafeMarkdown from '../components/SafeMarkdown.svelte';
27+
import Input from '../components/Input.svelte';
28+
import ItemDetailDescription from '../features/items/ItemDetailDescription.svelte';
2829
2930
let { itemId } = $props();
3031
@@ -35,6 +36,11 @@
3536
let transitioning = $state(false);
3637
let isWatching = $state(false);
3738
let watchBusy = $state(false);
39+
let saving = $state(false);
40+
let editingTitle = $state(false);
41+
let editTitle = $state('');
42+
let editingDescription = $state(false);
43+
let editDescription = $state('');
3844
3945
// Workflow-less personal tasks use the permitted transition endpoint to toggle
4046
// globally stable Open/Done IDs, matching desktop and PWA personal views.
@@ -91,6 +97,8 @@
9197
const summary = await loadMobileItemDetailSummary(id);
9298
if (token !== loadToken) return;
9399
item = summary?.item ?? null;
100+
editTitle = item?.title || '';
101+
editDescription = item?.description || '';
94102
transitions = summary?.transitions?.available_transitions ?? [];
95103
isWatching = summary?.watching || false;
96104
personalTaskCount = summary?.personal_task_count ?? 0;
@@ -167,6 +175,59 @@
167175
}
168176
}
169177
178+
async function saveTitle() {
179+
const title = editTitle.trim();
180+
if (!title || title === item.title) {
181+
editTitle = item.title || '';
182+
editingTitle = false;
183+
return;
184+
}
185+
saving = true;
186+
try {
187+
const updated = await api.items.update(itemId, { title });
188+
item = { ...item, ...updated, title };
189+
editingTitle = false;
190+
} catch (err) {
191+
console.error('Failed to save title:', err);
192+
} finally {
193+
saving = false;
194+
}
195+
}
196+
197+
function handleTitleKeydown(event) {
198+
if (event.key === 'Enter') {
199+
event.preventDefault();
200+
saveTitle();
201+
} else if (event.key === 'Escape') {
202+
editTitle = item.title || '';
203+
editingTitle = false;
204+
}
205+
}
206+
207+
function handleSaveField({ field, value }) {
208+
if (field !== 'description') return;
209+
editDescription = value;
210+
saveDescription();
211+
}
212+
213+
async function saveDescription() {
214+
saving = true;
215+
try {
216+
const updated = await api.items.update(itemId, { description: editDescription });
217+
item = { ...item, ...updated, description: editDescription };
218+
editingDescription = false;
219+
} catch (err) {
220+
console.error('Failed to save description:', err);
221+
} finally {
222+
saving = false;
223+
}
224+
}
225+
226+
function cancelDescription() {
227+
editDescription = item.description || '';
228+
editingDescription = false;
229+
}
230+
170231
async function updateAssignee(user) {
171232
const assigneeId = user?.id ?? null;
172233
if (assigneeId === (item.assignee_id ?? null)) return;
@@ -411,7 +472,26 @@
411472
<div class="status-line"><span class="type">{item.item_type_name}</span></div>
412473
{/if}
413474
414-
<h1 class="title" data-testid="detail-title">{item.title}</h1>
475+
{#if editingTitle}
476+
<Input
477+
bind:value={editTitle}
478+
class="title-input"
479+
aria-label="Edit title"
480+
dataTestid="mobile-title-editor"
481+
onblur={saveTitle}
482+
onkeydown={handleTitleKeydown}
483+
disabled={saving}
484+
/>
485+
{:else}
486+
<h1 class="title" data-testid="detail-title">
487+
<button
488+
class="title-button"
489+
type="button"
490+
onclick={() => { editTitle = item.title || ''; editingTitle = true; }}
491+
aria-label="Edit title"
492+
>{item.title}</button>
493+
</h1>
494+
{/if}
415495
416496
<!-- Status + assignee pickers. Status options come from the workflow's
417497
available-transitions endpoint (not hardcoded), so custom workflows
@@ -492,11 +572,24 @@
492572
</UserPicker>
493573
</div>
494574
495-
{#if item.description}
496-
<div class="html-content desc" data-testid="detail-description">
497-
<SafeMarkdown html={item.description_html} source={item.description} />
498-
</div>
499-
{/if}
575+
<div class="desc" data-testid="detail-description">
576+
<ItemDetailDescription
577+
{item}
578+
bind:editingDescription
579+
bind:editDescription
580+
{saving}
581+
availableSubIssueTypes={[]}
582+
showLinkButton={false}
583+
showDiagramButton={false}
584+
showAIActions={false}
585+
onsavefield={handleSaveField}
586+
oncanceledit={cancelDescription}
587+
onstartEditingDescription={() => {
588+
editDescription = item.description || '';
589+
editingDescription = true;
590+
}}
591+
/>
592+
</div>
500593
501594
<!-- Meta -->
502595
{#if item.due_date || personalTaskCount > 0}
@@ -676,6 +769,9 @@
676769
.type { font-size: 0.75rem; color: var(--ds-text-subtle); text-transform: uppercase; letter-spacing: 0.02em; }
677770
678771
.title { font-size: 1.25rem; font-weight: var(--font-semibold, 600); color: var(--ds-text); margin: 0 0 1rem; line-height: 1.3; }
772+
.title-button { display: block; width: 100%; padding: 0; border: none; background: transparent; color: inherit; font: inherit; text-align: left; cursor: text; }
773+
.title-button:active { opacity: 0.7; }
774+
.title-input { width: 100%; margin-bottom: 1rem; font-size: 1.25rem; font-weight: var(--font-semibold, 600); }
679775
680776
/* Status + assignee picker field rows */
681777
.fields {

0 commit comments

Comments
 (0)