@sascha384/tic 1.17.0 → 1.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/dist/app.d.ts +2 -34
- package/dist/app.js +8 -68
- package/dist/app.js.map +1 -1
- package/dist/components/Breadcrumbs.d.ts +5 -0
- package/dist/components/Breadcrumbs.js +16 -0
- package/dist/components/Breadcrumbs.js.map +1 -0
- package/dist/components/Header.js +27 -1
- package/dist/components/Header.js.map +1 -1
- package/dist/components/HelpScreen.d.ts +1 -1
- package/dist/components/HelpScreen.js +31 -3
- package/dist/components/HelpScreen.js.map +1 -1
- package/dist/components/IterationPicker.js +6 -6
- package/dist/components/IterationPicker.js.map +1 -1
- package/dist/components/Settings.d.ts +1 -1
- package/dist/components/Settings.js +15 -5
- package/dist/components/Settings.js.map +1 -1
- package/dist/components/StatusScreen.js +32 -4
- package/dist/components/StatusScreen.js.map +1 -1
- package/dist/components/WorkItemForm.js +249 -129
- package/dist/components/WorkItemForm.js.map +1 -1
- package/dist/components/WorkItemList.js +75 -63
- package/dist/components/WorkItemList.js.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/stores/backendDataStore.d.ts +4 -1
- package/dist/stores/backendDataStore.js +14 -5
- package/dist/stores/backendDataStore.js.map +1 -1
- package/dist/stores/formStackStore.d.ts +35 -0
- package/dist/stores/formStackStore.js +65 -0
- package/dist/stores/formStackStore.js.map +1 -0
- package/dist/stores/listViewStore.d.ts +17 -0
- package/dist/stores/listViewStore.js +54 -0
- package/dist/stores/listViewStore.js.map +1 -0
- package/dist/stores/navigationStore.d.ts +29 -0
- package/dist/stores/navigationStore.js +85 -0
- package/dist/stores/navigationStore.js.map +1 -0
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusScreen.js","sourceRoot":"","sources":["../../src/components/StatusScreen.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAC1C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"StatusScreen.js","sourceRoot":"","sources":["../../src/components/StatusScreen.tsx"],"names":[],"mappings":"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"}
|
|
@@ -5,17 +5,47 @@ import TextInput from 'ink-text-input';
|
|
|
5
5
|
import SelectInput from 'ink-select-input';
|
|
6
6
|
import { AutocompleteInput } from './AutocompleteInput.js';
|
|
7
7
|
import { MultiAutocompleteInput } from './MultiAutocompleteInput.js';
|
|
8
|
-
import {
|
|
8
|
+
import { useNavigationStore } from '../stores/navigationStore.js';
|
|
9
|
+
import { formStackStore, useFormStackStore, } from '../stores/formStackStore.js';
|
|
9
10
|
import { SyncQueueStore } from '../sync/queue.js';
|
|
10
11
|
import { useScrollViewport } from '../hooks/useScrollViewport.js';
|
|
11
12
|
import { useBackendDataStore } from '../stores/backendDataStore.js';
|
|
13
|
+
import { useShallow } from 'zustand/shallow';
|
|
12
14
|
import { openInEditor } from '../editor.js';
|
|
13
15
|
import { slugifyTemplateName } from '../backends/local/templates.js';
|
|
14
|
-
import {
|
|
16
|
+
import { Breadcrumbs } from './Breadcrumbs.js';
|
|
15
17
|
const SELECT_FIELDS = ['type', 'status', 'iteration', 'priority'];
|
|
16
18
|
const PRIORITIES = ['low', 'medium', 'high', 'critical'];
|
|
17
19
|
export function WorkItemForm() {
|
|
18
|
-
|
|
20
|
+
// Backend data store - grouped selector with shallow comparison
|
|
21
|
+
const { backend, syncManager, capabilities, statuses, iterations, types, assignees, labels: labelSuggestions, currentIteration, items: allItems, loading: configLoading, } = useBackendDataStore(useShallow((s) => ({
|
|
22
|
+
backend: s.backend,
|
|
23
|
+
syncManager: s.syncManager,
|
|
24
|
+
capabilities: s.capabilities,
|
|
25
|
+
statuses: s.statuses,
|
|
26
|
+
iterations: s.iterations,
|
|
27
|
+
types: s.types,
|
|
28
|
+
assignees: s.assignees,
|
|
29
|
+
labels: s.labels,
|
|
30
|
+
currentIteration: s.currentIteration,
|
|
31
|
+
items: s.items,
|
|
32
|
+
loading: s.loading,
|
|
33
|
+
})));
|
|
34
|
+
// Navigation store - grouped selector with shallow comparison
|
|
35
|
+
const { navigate, navigateToHelp, selectedWorkItemId, activeType, activeTemplate, setActiveTemplate, formMode, setFormMode, editingTemplateSlug, setEditingTemplateSlug, pushWorkItem, popWorkItem, } = useNavigationStore(useShallow((s) => ({
|
|
36
|
+
navigate: s.navigate,
|
|
37
|
+
navigateToHelp: s.navigateToHelp,
|
|
38
|
+
selectedWorkItemId: s.selectedWorkItemId,
|
|
39
|
+
activeType: s.activeType,
|
|
40
|
+
activeTemplate: s.activeTemplate,
|
|
41
|
+
setActiveTemplate: s.setActiveTemplate,
|
|
42
|
+
formMode: s.formMode,
|
|
43
|
+
setFormMode: s.setFormMode,
|
|
44
|
+
editingTemplateSlug: s.editingTemplateSlug,
|
|
45
|
+
setEditingTemplateSlug: s.setEditingTemplateSlug,
|
|
46
|
+
pushWorkItem: s.pushWorkItem,
|
|
47
|
+
popWorkItem: s.popWorkItem,
|
|
48
|
+
})));
|
|
19
49
|
const queueStore = useMemo(() => {
|
|
20
50
|
if (!syncManager)
|
|
21
51
|
return null;
|
|
@@ -33,15 +63,11 @@ export function WorkItemForm() {
|
|
|
33
63
|
syncManager?.pushPending().catch(() => { });
|
|
34
64
|
}
|
|
35
65
|
};
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const labelSuggestions = useBackendDataStore((s) => s.labels);
|
|
42
|
-
const currentIteration = useBackendDataStore((s) => s.currentIteration);
|
|
43
|
-
const allItems = useBackendDataStore((s) => s.items);
|
|
44
|
-
const configLoading = useBackendDataStore((s) => s.loading);
|
|
66
|
+
// Form stack store for draft persistence
|
|
67
|
+
const currentDraft = useFormStackStore((s) => s.currentDraft());
|
|
68
|
+
const showDirtyPrompt = useFormStackStore((s) => s.showDiscardPrompt);
|
|
69
|
+
const storeIsDirty = useFormStackStore((s) => s.isDirty());
|
|
70
|
+
const { push: pushDraft, updateFields, setFocusedField: setStoreFocusedField, setShowDiscardPrompt: setStoreShowDiscardPrompt, } = formStackStore.getState();
|
|
45
71
|
const [existingItem, setExistingItem] = useState(null);
|
|
46
72
|
const [children, setChildren] = useState([]);
|
|
47
73
|
const [dependents, setDependents] = useState([]);
|
|
@@ -56,6 +82,10 @@ export function WorkItemForm() {
|
|
|
56
82
|
setItemLoading(false);
|
|
57
83
|
return;
|
|
58
84
|
}
|
|
85
|
+
if (!backend) {
|
|
86
|
+
setItemLoading(false);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
59
89
|
let cancelled = false;
|
|
60
90
|
setItemLoading(true);
|
|
61
91
|
void (async () => {
|
|
@@ -155,62 +185,91 @@ export function WorkItemForm() {
|
|
|
155
185
|
children,
|
|
156
186
|
dependents,
|
|
157
187
|
]);
|
|
158
|
-
const [title, setTitle] = useState('');
|
|
159
|
-
const [type, setType] = useState(activeType ?? types[0] ?? '');
|
|
160
|
-
const [status, setStatus] = useState(statuses[0] ?? '');
|
|
161
|
-
const [iteration, setIteration] = useState(currentIteration);
|
|
162
|
-
const [priority, setPriority] = useState('medium');
|
|
163
|
-
const [assignee, setAssignee] = useState('');
|
|
164
|
-
const [labels, setLabels] = useState('');
|
|
165
|
-
const [description, setDescription] = useState('');
|
|
166
|
-
const [parentId, setParentId] = useState('');
|
|
167
|
-
const [dependsOn, setDependsOn] = useState('');
|
|
168
|
-
const [newComment, setNewComment] = useState('');
|
|
169
188
|
const [comments, setComments] = useState([]);
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const isDirty =
|
|
185
|
-
|
|
189
|
+
// Derive field values from current draft
|
|
190
|
+
const title = currentDraft?.fields.title ?? '';
|
|
191
|
+
const type = currentDraft?.fields.type ?? activeType ?? types[0] ?? '';
|
|
192
|
+
const status = currentDraft?.fields.status ?? statuses[0] ?? '';
|
|
193
|
+
const iteration = currentDraft?.fields.iteration ?? currentIteration;
|
|
194
|
+
const priority = (currentDraft?.fields.priority ?? 'medium');
|
|
195
|
+
const assignee = currentDraft?.fields.assignee ?? '';
|
|
196
|
+
const labels = currentDraft?.fields.labels ?? '';
|
|
197
|
+
const description = currentDraft?.fields.description ?? '';
|
|
198
|
+
const parentId = currentDraft?.fields.parentId ?? '';
|
|
199
|
+
const dependsOn = currentDraft?.fields.dependsOn ?? '';
|
|
200
|
+
const newComment = currentDraft?.fields.newComment ?? '';
|
|
201
|
+
const focusedField = currentDraft?.focusedField ?? 0;
|
|
202
|
+
// Use store's dirty detection
|
|
203
|
+
const isDirty = storeIsDirty;
|
|
204
|
+
// Field setter wrappers that update the store
|
|
205
|
+
const setTitle = (v) => updateFields({ title: v });
|
|
206
|
+
const setType = (v) => updateFields({ type: v });
|
|
207
|
+
const setStatus = (v) => updateFields({ status: v });
|
|
208
|
+
const setIteration = (v) => updateFields({ iteration: v });
|
|
209
|
+
const setPriority = (v) => updateFields({ priority: v });
|
|
210
|
+
const setAssignee = (v) => updateFields({ assignee: v });
|
|
211
|
+
const setLabels = (v) => updateFields({ labels: v });
|
|
212
|
+
const setDescription = (v) => updateFields({ description: v });
|
|
213
|
+
const setParentId = (v) => updateFields({ parentId: v });
|
|
214
|
+
const setDependsOn = (v) => updateFields({ dependsOn: v });
|
|
215
|
+
const setNewComment = (v) => updateFields({ newComment: v });
|
|
216
|
+
const setFocusedField = (v) => {
|
|
217
|
+
if (typeof v === 'function') {
|
|
218
|
+
const newVal = v(focusedField);
|
|
219
|
+
setStoreFocusedField(newVal);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
setStoreFocusedField(v);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const setShowDirtyPrompt = setStoreShowDiscardPrompt;
|
|
226
|
+
// Initialize form draft when entering form (only if stack is empty)
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
if (formStackStore.getState().stack.length > 0)
|
|
229
|
+
return; // Already has a draft
|
|
230
|
+
const initialFields = {
|
|
231
|
+
title: '',
|
|
232
|
+
type: activeType ?? types[0] ?? '',
|
|
233
|
+
status: statuses[0] ?? '',
|
|
234
|
+
iteration: currentIteration,
|
|
235
|
+
priority: 'medium',
|
|
236
|
+
assignee: '',
|
|
237
|
+
labels: '',
|
|
238
|
+
description: '',
|
|
239
|
+
parentId: '',
|
|
240
|
+
dependsOn: '',
|
|
241
|
+
newComment: '',
|
|
242
|
+
};
|
|
243
|
+
const draft = {
|
|
244
|
+
itemId: selectedWorkItemId,
|
|
245
|
+
itemTitle: selectedWorkItemId ? `#${selectedWorkItemId}` : '(new)',
|
|
246
|
+
fields: initialFields,
|
|
247
|
+
initialSnapshot: { ...initialFields },
|
|
248
|
+
focusedField: 0,
|
|
249
|
+
};
|
|
250
|
+
pushDraft(draft);
|
|
251
|
+
}, []); // Only on mount
|
|
186
252
|
// Sync form fields when the existing item finishes loading
|
|
187
253
|
useEffect(() => {
|
|
188
254
|
if (!existingItem)
|
|
189
255
|
return;
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
setIteration(existingItem.iteration);
|
|
194
|
-
setPriority(existingItem.priority ?? 'medium');
|
|
195
|
-
setAssignee(existingItem.assignee ?? '');
|
|
196
|
-
setLabels(existingItem.labels.join(', '));
|
|
197
|
-
setDescription(existingItem.description ?? '');
|
|
198
|
-
setParentId(existingItem.parent !== null && existingItem.parent !== undefined
|
|
256
|
+
setComments(existingItem.comments ?? []);
|
|
257
|
+
// Build field values
|
|
258
|
+
const parentIdValue = existingItem.parent !== null && existingItem.parent !== undefined
|
|
199
259
|
? (() => {
|
|
200
260
|
const pi = allItems.find((i) => i.id === existingItem.parent);
|
|
201
261
|
return pi
|
|
202
262
|
? `#${existingItem.parent} - ${pi.title}`
|
|
203
263
|
: String(existingItem.parent);
|
|
204
264
|
})()
|
|
205
|
-
: ''
|
|
206
|
-
|
|
265
|
+
: '';
|
|
266
|
+
const dependsOnValue = existingItem.dependsOn
|
|
207
267
|
?.map((depId) => {
|
|
208
268
|
const depItem = allItems.find((i) => i.id === depId);
|
|
209
269
|
return depItem ? `#${depId} - ${depItem.title}` : depId;
|
|
210
270
|
})
|
|
211
|
-
.join(', ') ?? ''
|
|
212
|
-
|
|
213
|
-
setInitialSnapshot(createSnapshot({
|
|
271
|
+
.join(', ') ?? '';
|
|
272
|
+
const newFields = {
|
|
214
273
|
title: existingItem.title,
|
|
215
274
|
type: existingItem.type,
|
|
216
275
|
status: existingItem.status,
|
|
@@ -219,23 +278,25 @@ export function WorkItemForm() {
|
|
|
219
278
|
assignee: existingItem.assignee ?? '',
|
|
220
279
|
labels: existingItem.labels.join(', '),
|
|
221
280
|
description: existingItem.description ?? '',
|
|
222
|
-
parentId:
|
|
223
|
-
|
|
224
|
-
const pi = allItems.find((i) => i.id === existingItem.parent);
|
|
225
|
-
return pi
|
|
226
|
-
? `#${existingItem.parent} - ${pi.title}`
|
|
227
|
-
: String(existingItem.parent);
|
|
228
|
-
})()
|
|
229
|
-
: '',
|
|
230
|
-
dependsOn: existingItem.dependsOn
|
|
231
|
-
?.map((depId) => {
|
|
232
|
-
const depItem = allItems.find((i) => i.id === depId);
|
|
233
|
-
return depItem ? `#${depId} - ${depItem.title}` : depId;
|
|
234
|
-
})
|
|
235
|
-
.join(', ') ?? '',
|
|
281
|
+
parentId: parentIdValue,
|
|
282
|
+
dependsOn: dependsOnValue,
|
|
236
283
|
newComment: '',
|
|
237
|
-
}
|
|
238
|
-
|
|
284
|
+
};
|
|
285
|
+
// Update both fields and initialSnapshot in the store
|
|
286
|
+
formStackStore.setState((state) => {
|
|
287
|
+
if (state.stack.length === 0)
|
|
288
|
+
return state;
|
|
289
|
+
const updated = [...state.stack];
|
|
290
|
+
const current = updated[updated.length - 1];
|
|
291
|
+
updated[updated.length - 1] = {
|
|
292
|
+
...current,
|
|
293
|
+
itemTitle: existingItem.title,
|
|
294
|
+
fields: newFields,
|
|
295
|
+
initialSnapshot: { ...newFields },
|
|
296
|
+
};
|
|
297
|
+
return { stack: updated };
|
|
298
|
+
});
|
|
299
|
+
}, [existingItem, allItems]);
|
|
239
300
|
// Prefill from template (create mode only)
|
|
240
301
|
useEffect(() => {
|
|
241
302
|
if (selectedWorkItemId !== null || !activeTemplate)
|
|
@@ -259,54 +320,15 @@ export function WorkItemForm() {
|
|
|
259
320
|
if (activeTemplate.dependsOn != null)
|
|
260
321
|
setDependsOn(activeTemplate.dependsOn.join(', '));
|
|
261
322
|
}, [activeTemplate, selectedWorkItemId]);
|
|
262
|
-
// Capture initial snapshot for new items once config finishes loading
|
|
263
|
-
useEffect(() => {
|
|
264
|
-
if (selectedWorkItemId !== null ||
|
|
265
|
-
configLoading ||
|
|
266
|
-
initialSnapshot !== null)
|
|
267
|
-
return;
|
|
268
|
-
setInitialSnapshot(createSnapshot({
|
|
269
|
-
title,
|
|
270
|
-
type,
|
|
271
|
-
status,
|
|
272
|
-
iteration,
|
|
273
|
-
priority,
|
|
274
|
-
assignee,
|
|
275
|
-
labels,
|
|
276
|
-
description,
|
|
277
|
-
parentId,
|
|
278
|
-
dependsOn,
|
|
279
|
-
newComment,
|
|
280
|
-
}));
|
|
281
|
-
}, [selectedWorkItemId, configLoading]);
|
|
282
323
|
// Load existing template for editing
|
|
283
324
|
useEffect(() => {
|
|
284
|
-
if (formMode !== 'template' || !editingTemplateSlug)
|
|
325
|
+
if (formMode !== 'template' || !editingTemplateSlug || !backend)
|
|
285
326
|
return;
|
|
286
327
|
let cancelled = false;
|
|
287
328
|
void backend.getTemplate(editingTemplateSlug).then((t) => {
|
|
288
329
|
if (cancelled)
|
|
289
330
|
return;
|
|
290
|
-
|
|
291
|
-
if (t.type != null)
|
|
292
|
-
setType(t.type);
|
|
293
|
-
if (t.status != null)
|
|
294
|
-
setStatus(t.status);
|
|
295
|
-
if (t.priority != null)
|
|
296
|
-
setPriority(t.priority);
|
|
297
|
-
if (t.assignee != null)
|
|
298
|
-
setAssignee(t.assignee);
|
|
299
|
-
if (t.labels != null)
|
|
300
|
-
setLabels(t.labels.join(', '));
|
|
301
|
-
if (t.iteration != null)
|
|
302
|
-
setIteration(t.iteration);
|
|
303
|
-
if (t.description != null)
|
|
304
|
-
setDescription(t.description);
|
|
305
|
-
if (t.parent != null)
|
|
306
|
-
setParentId(String(t.parent));
|
|
307
|
-
if (t.dependsOn != null)
|
|
308
|
-
setDependsOn(t.dependsOn.join(', '));
|
|
309
|
-
setInitialSnapshot(createSnapshot({
|
|
331
|
+
const newFields = {
|
|
310
332
|
title: t.name,
|
|
311
333
|
type: t.type ?? type,
|
|
312
334
|
status: t.status ?? status,
|
|
@@ -318,7 +340,21 @@ export function WorkItemForm() {
|
|
|
318
340
|
parentId: t.parent != null ? String(t.parent) : parentId,
|
|
319
341
|
dependsOn: t.dependsOn != null ? t.dependsOn.join(', ') : dependsOn,
|
|
320
342
|
newComment: '',
|
|
321
|
-
}
|
|
343
|
+
};
|
|
344
|
+
// Update both fields and initialSnapshot in the store
|
|
345
|
+
formStackStore.setState((state) => {
|
|
346
|
+
if (state.stack.length === 0)
|
|
347
|
+
return state;
|
|
348
|
+
const updated = [...state.stack];
|
|
349
|
+
const current = updated[updated.length - 1];
|
|
350
|
+
updated[updated.length - 1] = {
|
|
351
|
+
...current,
|
|
352
|
+
itemTitle: t.name,
|
|
353
|
+
fields: newFields,
|
|
354
|
+
initialSnapshot: { ...newFields },
|
|
355
|
+
};
|
|
356
|
+
return { stack: updated };
|
|
357
|
+
});
|
|
322
358
|
});
|
|
323
359
|
return () => {
|
|
324
360
|
cancelled = true;
|
|
@@ -329,10 +365,8 @@ export function WorkItemForm() {
|
|
|
329
365
|
.filter((item) => item.id !== selectedWorkItemId)
|
|
330
366
|
.map((item) => `#${item.id} - ${item.title}`);
|
|
331
367
|
}, [allItems, selectedWorkItemId]);
|
|
332
|
-
const [focusedField, setFocusedField] = useState(0);
|
|
333
368
|
const [editing, setEditing] = useState(false);
|
|
334
369
|
const [preEditValue, setPreEditValue] = useState('');
|
|
335
|
-
const [showDirtyPrompt, setShowDirtyPrompt] = useState(false);
|
|
336
370
|
const [pendingRelNav, setPendingRelNav] = useState(null);
|
|
337
371
|
const [saving, setSaving] = useState(false);
|
|
338
372
|
useEffect(() => {
|
|
@@ -345,6 +379,8 @@ export function WorkItemForm() {
|
|
|
345
379
|
currentField?.startsWith('rel-child-') ||
|
|
346
380
|
currentField?.startsWith('rel-dependent-');
|
|
347
381
|
async function save() {
|
|
382
|
+
if (!backend)
|
|
383
|
+
return;
|
|
348
384
|
const parsedLabels = labels
|
|
349
385
|
.split(',')
|
|
350
386
|
.map((l) => l.trim())
|
|
@@ -456,19 +492,25 @@ export function WorkItemForm() {
|
|
|
456
492
|
}
|
|
457
493
|
setActiveTemplate(null);
|
|
458
494
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
495
|
+
// Update the initialSnapshot after saving so isDirty becomes false
|
|
496
|
+
formStackStore.setState((state) => {
|
|
497
|
+
if (state.stack.length === 0)
|
|
498
|
+
return state;
|
|
499
|
+
const updated = [...state.stack];
|
|
500
|
+
const current = updated[updated.length - 1];
|
|
501
|
+
updated[updated.length - 1] = {
|
|
502
|
+
...current,
|
|
503
|
+
initialSnapshot: {
|
|
504
|
+
...current.fields,
|
|
505
|
+
newComment: '', // Comment was saved, reset
|
|
506
|
+
},
|
|
507
|
+
fields: {
|
|
508
|
+
...current.fields,
|
|
509
|
+
newComment: '', // Clear after save
|
|
510
|
+
},
|
|
511
|
+
};
|
|
512
|
+
return { stack: updated };
|
|
513
|
+
});
|
|
472
514
|
}
|
|
473
515
|
useInput((_input, key) => {
|
|
474
516
|
// Dirty prompt overlay — capture s/d/esc only
|
|
@@ -477,15 +519,40 @@ export function WorkItemForm() {
|
|
|
477
519
|
void (async () => {
|
|
478
520
|
await save();
|
|
479
521
|
if (pendingRelNav) {
|
|
522
|
+
// Push a new draft for the target item
|
|
523
|
+
const targetItem = allItems.find((i) => i.id === pendingRelNav);
|
|
524
|
+
const defaultFields = {
|
|
525
|
+
title: '',
|
|
526
|
+
type: activeType ?? types[0] ?? '',
|
|
527
|
+
status: statuses[0] ?? '',
|
|
528
|
+
iteration: currentIteration,
|
|
529
|
+
priority: 'medium',
|
|
530
|
+
assignee: '',
|
|
531
|
+
labels: '',
|
|
532
|
+
description: '',
|
|
533
|
+
parentId: '',
|
|
534
|
+
dependsOn: '',
|
|
535
|
+
newComment: '',
|
|
536
|
+
};
|
|
537
|
+
const newDraft = {
|
|
538
|
+
itemId: pendingRelNav,
|
|
539
|
+
itemTitle: targetItem?.title ?? `#${pendingRelNav}`,
|
|
540
|
+
fields: defaultFields,
|
|
541
|
+
initialSnapshot: { ...defaultFields },
|
|
542
|
+
focusedField: 0,
|
|
543
|
+
};
|
|
544
|
+
pushDraft(newDraft);
|
|
480
545
|
pushWorkItem(pendingRelNav);
|
|
481
546
|
setPendingRelNav(null);
|
|
482
547
|
}
|
|
483
548
|
else if (formMode === 'template') {
|
|
549
|
+
formStackStore.getState().pop();
|
|
484
550
|
setFormMode('item');
|
|
485
551
|
setEditingTemplateSlug(null);
|
|
486
552
|
navigate('settings');
|
|
487
553
|
}
|
|
488
554
|
else {
|
|
555
|
+
formStackStore.getState().pop();
|
|
489
556
|
const prev = popWorkItem();
|
|
490
557
|
if (prev === null)
|
|
491
558
|
navigate('list');
|
|
@@ -497,15 +564,41 @@ export function WorkItemForm() {
|
|
|
497
564
|
if (_input === 'd') {
|
|
498
565
|
// Discard: navigate back without saving
|
|
499
566
|
if (pendingRelNav) {
|
|
567
|
+
// Push a new draft for the target item (discarding current)
|
|
568
|
+
formStackStore.getState().pop();
|
|
569
|
+
const targetItem = allItems.find((i) => i.id === pendingRelNav);
|
|
570
|
+
const defaultFields = {
|
|
571
|
+
title: '',
|
|
572
|
+
type: activeType ?? types[0] ?? '',
|
|
573
|
+
status: statuses[0] ?? '',
|
|
574
|
+
iteration: currentIteration,
|
|
575
|
+
priority: 'medium',
|
|
576
|
+
assignee: '',
|
|
577
|
+
labels: '',
|
|
578
|
+
description: '',
|
|
579
|
+
parentId: '',
|
|
580
|
+
dependsOn: '',
|
|
581
|
+
newComment: '',
|
|
582
|
+
};
|
|
583
|
+
const newDraft = {
|
|
584
|
+
itemId: pendingRelNav,
|
|
585
|
+
itemTitle: targetItem?.title ?? `#${pendingRelNav}`,
|
|
586
|
+
fields: defaultFields,
|
|
587
|
+
initialSnapshot: { ...defaultFields },
|
|
588
|
+
focusedField: 0,
|
|
589
|
+
};
|
|
590
|
+
pushDraft(newDraft);
|
|
500
591
|
pushWorkItem(pendingRelNav);
|
|
501
592
|
setPendingRelNav(null);
|
|
502
593
|
}
|
|
503
594
|
else if (formMode === 'template') {
|
|
595
|
+
formStackStore.getState().pop();
|
|
504
596
|
setFormMode('item');
|
|
505
597
|
setEditingTemplateSlug(null);
|
|
506
598
|
navigate('settings');
|
|
507
599
|
}
|
|
508
600
|
else {
|
|
601
|
+
formStackStore.getState().pop();
|
|
509
602
|
const prev = popWorkItem();
|
|
510
603
|
if (prev === null)
|
|
511
604
|
navigate('list');
|
|
@@ -525,6 +618,7 @@ export function WorkItemForm() {
|
|
|
525
618
|
if (key.escape && !editing) {
|
|
526
619
|
if (configLoading || itemLoading || saving) {
|
|
527
620
|
// Allow escape even while loading (no save)
|
|
621
|
+
formStackStore.getState().pop();
|
|
528
622
|
if (formMode === 'template') {
|
|
529
623
|
setFormMode('item');
|
|
530
624
|
setEditingTemplateSlug(null);
|
|
@@ -542,6 +636,7 @@ export function WorkItemForm() {
|
|
|
542
636
|
return;
|
|
543
637
|
}
|
|
544
638
|
// Clean — just go back
|
|
639
|
+
formStackStore.getState().pop();
|
|
545
640
|
if (formMode === 'template') {
|
|
546
641
|
setFormMode('item');
|
|
547
642
|
setEditingTemplateSlug(null);
|
|
@@ -566,6 +661,7 @@ export function WorkItemForm() {
|
|
|
566
661
|
setSaving(true);
|
|
567
662
|
void (async () => {
|
|
568
663
|
await save();
|
|
664
|
+
formStackStore.getState().pop();
|
|
569
665
|
if (formMode === 'template') {
|
|
570
666
|
setFormMode('item');
|
|
571
667
|
setEditingTemplateSlug(null);
|
|
@@ -603,6 +699,29 @@ export function WorkItemForm() {
|
|
|
603
699
|
setShowDirtyPrompt(true);
|
|
604
700
|
}
|
|
605
701
|
else {
|
|
702
|
+
// Create new draft for target item before navigating
|
|
703
|
+
const targetItem = allItems.find((i) => i.id === targetId);
|
|
704
|
+
const defaultFields = {
|
|
705
|
+
title: '',
|
|
706
|
+
type: activeType ?? types[0] ?? '',
|
|
707
|
+
status: statuses[0] ?? '',
|
|
708
|
+
iteration: currentIteration,
|
|
709
|
+
priority: 'medium',
|
|
710
|
+
assignee: '',
|
|
711
|
+
labels: '',
|
|
712
|
+
description: '',
|
|
713
|
+
parentId: '',
|
|
714
|
+
dependsOn: '',
|
|
715
|
+
newComment: '',
|
|
716
|
+
};
|
|
717
|
+
const newDraft = {
|
|
718
|
+
itemId: targetId,
|
|
719
|
+
itemTitle: targetItem?.title ?? `#${targetId}`,
|
|
720
|
+
fields: defaultFields,
|
|
721
|
+
initialSnapshot: { ...defaultFields },
|
|
722
|
+
focusedField: 0,
|
|
723
|
+
};
|
|
724
|
+
pushDraft(newDraft);
|
|
606
725
|
pushWorkItem(targetId);
|
|
607
726
|
}
|
|
608
727
|
}
|
|
@@ -852,8 +971,9 @@ export function WorkItemForm() {
|
|
|
852
971
|
cursor: focusedField,
|
|
853
972
|
chromeLines: 4, // title+margin (2) + help bar margin+text (2)
|
|
854
973
|
});
|
|
855
|
-
|
|
856
|
-
|
|
974
|
+
// Show placeholder when item is still loading
|
|
975
|
+
if (itemLoading) {
|
|
976
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Loading item..." }) }), _jsx(Text, { dimColor: true, children: "esc back ? help" })] }));
|
|
857
977
|
}
|
|
858
978
|
const mode = formMode === 'template'
|
|
859
979
|
? editingTemplateSlug
|
|
@@ -866,7 +986,7 @@ export function WorkItemForm() {
|
|
|
866
986
|
const isFieldVisible = (index) => index >= viewport.start && index < viewport.end;
|
|
867
987
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, color: "cyan", children: [mode, typeLabel ? ` ${typeLabel}` : '', formMode !== 'template' && selectedWorkItemId !== null
|
|
868
988
|
? ` #${selectedWorkItemId}`
|
|
869
|
-
: ''] }) }), fields.map((field, index) => {
|
|
989
|
+
: ''] }) }), _jsx(Breadcrumbs, {}), fields.map((field, index) => {
|
|
870
990
|
if (field === 'rel-parent' ||
|
|
871
991
|
field.startsWith('rel-child-') ||
|
|
872
992
|
field.startsWith('rel-dependent-')) {
|