feat: Parent-Child TaskFlow — Delegated durch sichtbare Child-Tasks ersetzt

- Delegated State aus Board, Entities, DTOs, Frontend-Spalten und Tests entfernt
- Parent-Tasks bleiben InProgress waehrend delegierter Agentenarbeit
- Child-Tasks laufen sichtbar mit normalen States und parentTaskId
- Doku: README, Phase 3, Changelog, Controller-Kommentare angepasst
- openclaw-task-board-flow.md als Referenzdoku hinzugefuegt
- 73/73 Backend-Tests gruen, Frontend-Build gruen
This commit is contained in:
2026-06-21 21:30:52 +02:00
parent b89289989a
commit ac131f7f53
15 changed files with 464 additions and 133 deletions
+29 -8
View File
@@ -92,7 +92,6 @@ function statusLabel(state: string): string {
const map: Record<string, string> = {
'Backlog': 'Offen',
'In progress': 'In Bearbeitung',
'Delegated': 'Delegiert',
'Review': 'Review',
'Blocked': 'Blockiert',
'Done': 'Erledigt',
@@ -105,7 +104,6 @@ function statusClass(state: string): string {
if (s === 'done') return 'is-done'
if (s === 'blocked') return 'is-blocked'
if (s === 'review') return 'is-review'
if (s === 'delegated') return 'is-delegated'
if (s === 'in progress') return 'is-progress'
return 'is-backlog'
}
@@ -148,8 +146,27 @@ function relativeTime(date?: string | null): string {
return `vor ${days} d`
}
function progressHint(taskLike: Pick<TaskDto, 'lastActivityMessage' | 'expectedFrom'>): string {
return taskLike.lastActivityMessage?.trim() || (taskLike.expectedFrom ? `Wartet auf ${taskLike.expectedFrom}` : 'Noch kein relevanter Progress-Status')
function childStatusSummary(taskId: string): string {
const childItems = children.value.filter(child => child.parentTaskId === taskId)
if (!childItems.length) return ''
const counts = {
inProgress: childItems.filter(child => child.state === 'In progress').length,
review: childItems.filter(child => child.state === 'Review').length,
blocked: childItems.filter(child => child.state === 'Blocked').length,
done: childItems.filter(child => child.state === 'Done').length,
}
const parts = [] as string[]
if (counts.inProgress) parts.push(`${counts.inProgress} in Arbeit`)
if (counts.review) parts.push(`${counts.review} im Review`)
if (counts.blocked) parts.push(`${counts.blocked} blockiert`)
if (counts.done) parts.push(`${counts.done} erledigt`)
return parts.length ? `Child-Tasks: ${parts.join(' · ')}` : `Child-Tasks: ${childItems.length}`
}
function progressHint(taskLike: Pick<TaskDto, 'id' | 'lastActivityMessage' | 'expectedFrom'>): string {
return taskLike.lastActivityMessage?.trim() || childStatusSummary(taskLike.id) || (taskLike.expectedFrom ? `Wartet auf ${taskLike.expectedFrom}` : 'Noch kein relevanter Progress-Status')
}
/* ── API calls ───────────────────────────────── */
@@ -362,6 +379,7 @@ function handleKeydown(e: KeyboardEvent) {
<span class="meta-chip">{{ task.priority }} Priorität</span>
<span v-if="task.isAgentTask" class="meta-chip">🤖 Agent-Task</span>
<span v-if="task.expectedFrom" class="meta-chip"> Erwartet: {{ task.expectedFrom }}</span>
<span v-if="task.parentTaskId" class="meta-chip"> Sichtbare Child-Task</span>
</div>
</div>
@@ -390,7 +408,7 @@ function handleKeydown(e: KeyboardEvent) {
</span>
</div>
<div v-if="task.isAgentTask" class="progress-banner">
<div v-if="task.isAgentTask || childStatusSummary(task.id)" class="progress-banner">
<strong>Letzter Fortschritt:</strong> {{ progressHint(task) }}
</div>
@@ -424,7 +442,7 @@ function handleKeydown(e: KeyboardEvent) {
<option value="Medium">Medium</option>
<option value="Low">Low</option>
</select>
<input v-model="subtaskAssign" class="galaxy-input narrow" placeholder="Zuständig (bao, iris…)" />
<input v-model="subtaskAssign" class="galaxy-input narrow" placeholder="Zuständig (bao, iris, researcher…)" />
<button class="btn-primary btn-sm" @click="createSubtask" :disabled="creatingSubtask">
{{ creatingSubtask ? 'Erstelle…' : 'Anlegen' }}
</button>
@@ -455,6 +473,8 @@ function handleKeydown(e: KeyboardEvent) {
>
<option value="Backlog">Offen</option>
<option value="In progress">In Arbeit</option>
<option value="Review">Review</option>
<option value="Blocked">Blockiert</option>
<option value="Done">Erledigt</option>
</select>
<button
@@ -520,7 +540,6 @@ function handleKeydown(e: KeyboardEvent) {
<select v-model="form.state" class="galaxy-input galaxy-select" :disabled="!canChangeState">
<option value="Backlog">Offen</option>
<option value="In progress">In Bearbeitung</option>
<option value="Delegated">Delegiert</option>
<option value="Review">Review</option>
<option value="Blocked">Blockiert</option>
<option value="Done">Erledigt</option>
@@ -543,6 +562,8 @@ function handleKeydown(e: KeyboardEvent) {
<option value="programmer">🛠 Programmer</option>
<option value="reviewer">🔎 Reviewer</option>
<option value="architekt">🏛 Architekt</option>
<option value="researcher">🔬 Researcher</option>
<option value="executor"> Executor</option>
</select>
</label>
<label class="sidebar-field">
@@ -559,6 +580,7 @@ function handleKeydown(e: KeyboardEvent) {
<div><dt>Erstellt</dt><dd>{{ formatDate(task.createdAt) }}</dd></div>
<div><dt>Geändert</dt><dd>{{ formatDate(task.updatedAt, true) }}</dd></div>
<div v-if="task.isAgentTask"><dt>Letzter Status</dt><dd>{{ relativeTime(task.lastActivityAt ?? task.updatedAt) }}</dd></div>
<div v-if="task.parentTaskId"><dt>Task-Typ</dt><dd>Sichtbare Child-Task</dd></div>
</dl>
</section>
@@ -678,7 +700,6 @@ function handleKeydown(e: KeyboardEvent) {
.state-badge.is-backlog { color: #fde68a; background: rgba(251,191,36,.12); border-color: rgba(251,191,36,.25); }
.state-badge.is-progress { color: #86efac; background: rgba(34,197,94,.12); border-color: rgba(34,197,94,.25); }
.state-badge.is-delegated { color: #d8b4fe; background: rgba(168,85,247,.12); border-color: rgba(168,85,247,.25); }
.state-badge.is-review { color: #fdba74; background: rgba(249,115,22,.12); border-color: rgba(249,115,22,.25); }
.state-badge.is-blocked { color: #fda4af; background: rgba(244,63,94,.12); border-color: rgba(244,63,94,.25); }
.state-badge.is-done { color: #86efac; background: rgba(34,197,94,.12); border-color: rgba(34,197,94,.25); }