- GitManager: переработан в GitWorkspacePanel с вкладками - Добавлен GitLifecycleHeader с быстрыми действиями - RepositoryDashboardGrid: поддержка ReviewToggle, badges, env filter - HelpTooltip: универсальный компонент подсказок с тестами - GitManagerModel: доработаны экшены, добавлен isReady, loadDefaultBranch - Локализация en/ru для Git UI - tailwind: добавлен animation-delay-200 - ConfirmDialog: a11y-атрибуты для кнопок
29 KiB
Tasks: Task Status Center
Input: Design documents from /specs/034-task-status-center/
Prerequisites: plan.md ✅, spec.md ✅, ux_reference.md ✅, research.md ✅, data-model.md ✅, contracts/modules.md ✅
Tests: All C4 contracts require tests verifying @PRE/@POST/@INVARIANT. Rejected-path regression tests for RBAC filtering.
Organization: Tasks grouped by user story — each story independently implementable and testable.
Phase 1: Setup (Shared Infrastructure)
Purpose: Verify branch, environment, and prerequisites.
-
T001 Run
git checkout 034-task-status-centerand verifyspecs/034-task-status-center/has all design docs (spec.md, plan.md, research.md, data-model.md, contracts/modules.md, quickstart.md, ux_reference.md) -
T002 Run
cd backend && source .venv/bin/activate && python -m pytest backend/tests/services/reports/ -v— verify existing reports tests pass before any changes
Phase 2: Foundational (Blocking Prerequisites)
Purpose: Core infrastructure that ALL user stories depend on — types, RBAC filter, API client, type profiles.
⚠️ CRITICAL: No user story work until this phase passes verification.
Data Layer (backend + frontend — parallel)
-
T003 [P] Add
TaskSummary,StatusCounts,TaskTypeSummaryPydantic schemas tobackend/src/models/report.pyExtend existingReportModelsregion — add schemas from data-model.md §2.1 @POST:TaskSummary(by_type: list[TaskTypeSummary], total_tasks: int, active_tasks: int)with all counts ≥0 -
T004 [P] Create TypeScript DTOs in
frontend/src/types/reports.tsInterfaces from data-model.md §3:TaskType,ReportStatus,ErrorContext,TaskReport,ReportQuery,ReportCollection,ReportDetailView,StatusCounts,TaskTypeSummary,TaskSummary,TaskStatusEvent,TaskCenterFilter,ScreenState@DATA_CONTRACT: each interface referencesbackend/src/models/report.pycounterpart via JSDoc@see -
T005 [P] Add
clean_releaseprofile tofrontend/src/lib/components/reports/reportTypeProfiles.tsAdd entry:clean_release: { key: 'clean_release', label: 'Clean Release', variant: 'success', icon: 'shield-check', fallback: false }RATIONALE: R8 — sync frontend profiles with backendTaskTypeenum (was missingclean_release)
RBAC & API Client (sequential after T003/T004)
-
T006 Implement
_filter_tasks_by_rbac()inbackend/src/services/reports/report_service.py@PRE: current_user is authenticated User, tasks is list of in-memory Task objects @POST: returns tasks filtered by role — admin→all, analyst→own+system(user_id=None), viewer→own only RATIONALE: extracted as pure function for testability (R4), no side effects, no service dependencies REJECTED: inline filtering in list_reports — mixes authorization with query logic REJECTED: filtering at TaskGraph level — graph is data structure, not auth boundary @TEST_EDGE: admin→all_tasks_returned, analyst→own_plus_system, viewer→only_own, no_matching→empty_list -
T007 [P] Add
getReportsSummary()and type existing functions infrontend/src/lib/api/reports.tsNew:export async function getReportsSummary(): Promise<TaskSummary>→GET /api/reports/summaryviaapi.fetchApiEnhance:getReports()returnsPromise<ReportCollection>,getReportDetail()returnsPromise<ReportDetailView>(replace generic<T>) @DATA_CONTRACT: ReportQuery → ReportCollection, (none) → TaskSummary, reportId → ReportDetailView import types from$types/reports -
T008 [P] Add
"created_at"to allowedsort_byvalues inbackend/src/models/report.pyReportQuery._validate_sort_byvalidator Change allowed set from{"updated_at", "status", "task_type"}to{"updated_at", "created_at", "status", "task_type"} -
T008a [P] Add
ReportsSettingsPydantic schema tobackend/src/models/report.pySchema:class ReportsSettings(BaseModel): disabled_task_types: list[TaskType] = Field(default_factory=list)@POST:ReportsSettings(disabled_task_types: list[TaskType])— validates task types against enum -
T008b Implement
GET /api/settings/reportsandPUT /api/settings/reportsendpoints inbackend/src/api/routes/settings.py(orreports.py) GET: returns{ disabled_task_types: [...] }— any authenticated user withtasks:READPUT: accepts{ disabled_task_types: [...] }— admin only (settings:WRITEpermission), validates types againstTaskTypeenum @PRE: GET — authenticated user; PUT — admin role withsettings:WRITE@POST: GET →ReportsSettings; PUT → updatedReportsSettings@TEST_EDGE: get_returns_defaults→empty_list, put_by_admin→saved, put_by_non_admin→403, put_invalid_type→422 RATIONALE: UX group visibility feature — admin can disable task types globally for all users -
T008c [P] Add
getReportsSettings()tofrontend/src/lib/api/reports.tsNew:export async function getReportsSettings(): Promise<ReportsSettings>→GET /api/settings/reportsNew:export async function updateReportsSettings(settings: ReportsSettings): Promise<ReportsSettings>→PUT /api/settings/reportsimportReportsSettingsfrom$types/reports
Foundational Verification
- T009 Verify foundational phase — run
cd backend && python -m ruff check .,cd frontend && npm run lint,cd backend && source .venv/bin/activate && python -m pytest backend/tests/ -k "reports" -vAll existing tests must pass. New schemas must not break existing code.
Checkpoint: Types, schemas, RBAC filter, and API client ready — user stories can now begin.
Phase 3: User Story 1 — Сводный обзор всех задач (Priority: P1) 🎯 MVP
Goal: На странице /reports появляется сводная панель над списком отчётов с агрегированными счётчиками по статусам (pending, running, awaiting_input, success, failed) для каждого типа задач, обновляемая в реальном времени через WebSocket.
Independent Test: Открыть /reports → сводная панель с карточками тип×статус, счётчики корректны, при изменении статуса задачи через API счётчики обновляются без перезагрузки страницы.
Tests for User Story 1 (write FIRST, expect FAIL)
-
T010 [P] [US1] Write L1 model test for
TaskCenterModelinvariants infrontend/src/lib/models/__tests__/TaskCenterModel.test.ts@TEST_INVARIANT: active tasks sorted before completed, visibleSummary excludes disabledTypes AND not in visibleTypes @TEST_EDGE: empty_tasks→summary_shows_zeros, ws_disconnect→screenState='disconnected', ws_reconnect→screenState='ready' @TEST_EDGE: toggleTypeVisibility→localStorage_updated, admin_disabled_type→hidden_from_visibleSummary Use vitest, no DOM render (~27ms target). MockfetchApiandWebSocket. -
T011 [P] [US1] Write contract test for
Reports.SummaryService.get_summary()inbackend/tests/services/reports/test_report_service.py@TEST_EDGE: admin_role→all_tasks_counted, analyst_role→own_plus_system_counted, viewer_role→only_own_counted @TEST_EDGE: empty_tasks→TaskSummary(total_tasks=0, active_tasks=0, by_type=[]) @TEST_CONTRACT:TaskSummaryinvariants — all counts ≥0, total == sum of all status counts across all types
Backend Implementation for User Story 1
-
T012 [US1] Implement
ReportsService.get_summary()method inbackend/src/services/reports/report_service.py@PRE: task_manager initialized, current_user authenticated with tasks:READ @POST: TaskSummary with counts filtered by user role — aggregateget_all_tasks()→ normalize → group by (task_type × status) → TaskTypeSummary per type @SIDE_EFFECT: None (read-only aggregation) RATIONALE: RBAC filtering in service layer (R4), separate method for independent caching (R1) REJECTED: combining into list_reports header, WebSocket-only delivery for initial load Use_filter_tasks_by_rbac(tasks, current_user)from T006, thenresolve_task_type(task.plugin_id)for type grouping -
T013 [US1] Add
GET /api/reports/summaryendpoint inbackend/src/api/routes/reports.py@PRE: current_user authenticated viaDepends(get_current_user)@POST: returnsTaskSummary— summary counts reflect RBAC (admin→all, analyst→own+system, viewer→own) RATIONALE: separate endpoint from list_reports (R1) — lightweight, independently cacheable REJECTED: combining summary into list_reports header, WebSocket-only summary delivery Route:@router.get("/summary", response_model=TaskSummary), injecttask_manager+clean_release_repodependencies -
T014 [US1] Enhance
ReportsService.list_reports()inbackend/src/services/reports/report_service.pywith RBAC row-level filtering @PRE: current_user passed to service method @POST: ReportCollection.items filtered by user role (same rules as summary) Addcurrent_userparameter. Call_filter_tasks_by_rbac()in_load_normalized_reports()before normalization. RATIONALE: TSC-FR-009 requires RBAC on both summary AND list — not just gate, but row-level visibility REJECTED: separate admin/user list endpoints — single endpoint with role-based filtering avoids route duplication
Frontend Model (core — all stories depend on it)
- T015 [US1] Implement
TaskCenterModelinfrontend/src/lib/models/TaskCenterModel.svelte.ts@STATE:tasks,summary,filters,screenState,wsConnected,selectedTaskId,page,pageSize,disabledTypes(admin-disabled, from backend),visibleTypes(operator preference, from localStorage),lastUpdated@ACTION:loadInitialData(initialFilters?)— parallel fetch summary + list + settings, then connect WS @ACTION:connectWebSocket()— subscribe togetTaskEventsWsUrl(), buffer events, 100ms debounce flush @ACTION:disconnectWebSocket(),destroy()— cleanup WS, timers @ACTION:toggleTypeVisibility(type)— toggle type invisibleTypes, persist to localStorage @INVARIANT: active tasks (PENDING, RUNNING, AWAITING_INPUT) sorted before completed tasks in$derived filteredTasks@INVARIANT:visibleSummaryexcludes types indisabledTypes(admin) AND not invisibleTypes(operator) @INVARIANT: DECOMPOSITION GATE: 400 lines, 40 methods (ADR-0010) @SIDE_EFFECT: connects to/ws/task-eventsWebSocket, callsReportsApi, updates browser URL viahistory.replaceState(), reads/writeslocalStorageforvisibleTypes@POST: all public state-mutating methods trigger$derivedrecomputation @POST: WS lifecycle managed: connect onloadInitialData, disconnect ondestroyRATIONALE: single model (R3) — 3 stories share the same task dataset; ~400 lines, at ADR-0010 gate REJECTED: submodels from day one, global store (page-scoped state), polling-based refresh Private:_eventBuffer,_flushTimeout,_reconnectAttempt(0),_reconnectTimer,_scheduleReconnect()with exponential backoff 1s→2s→4s→8s→max 30s
Frontend Components (US1)
-
T016 [US1] Implement
SummaryPanelinfrontend/src/lib/components/reports/SummaryPanel.svelte@UX_STATE:loading→skeleton grid (4 cards with pulse animation),ready→grid of clickable cards (2-6 cols responsive),empty→all cards show zero counts,disconnected→cards visible with reduced opacity + "disconnected" badge @UX_FEEDBACK: count change → CSStransition: all 200mson count numbers, card click → callsonFilterByTypeAndStatus(taskType, status)@UX_RECOVERY: disconnected → auto-reconnect indicator, manual reconnect viaonReconnectprop Props:summary: TaskSummary|null,screenState: ScreenState,onFilterByTypeAndStatus: (TaskType, ReportStatus)=>voidUses<Card>from$lib/ui/Card.svelte(existing). Colors per ux_reference.md: running→tokenprimary, pending→muted, success→success, failed→destructive, awaiting_input→warning. @POST: clicking card emitsonFilterByTypeAndStatus(taskType, status)— will be wired to model in Phase 4 (US2) -
T017 [US1] Add WebSocket connection indicator to
frontend/src/routes/reports/+page.svelte(inline in page header) Green dot●whenm.wsConnected===true, yellow pulsing◉when reconnecting, red○when disconnected Uses semantic tokens:bg-success/bg-warning/bg-destructivein aw-3 h-3 rounded-fullspan Shows tooltip on hover: "Подключено / Переподключение / Соединение потеряно"
Frontend Page (US1 — thin render layer)
-
T018 [US1] Create
frontend/src/routes/reports/+page.tsload function Parseurl.searchParamsinto initialTaskCenterFilter:task_types(comma-sep→array),statuses(comma-sep→array),search,time_range,sort_by,sort_orderReturn{ initialFilters }for the page component RATIONALE: R5 — filter state survives page refresh via URL query params -
T019 [US1] Rewrite
frontend/src/routes/reports/+page.svelteas thin render layer Layout:<PageHeader title="Центр статусов">+ refresh<Button variant="ghost">(callsm.refreshSummary()) Below:<SummaryPanel>, existing<ReportCard>-based list (placeholder for US2-enhanced<TaskList>),<ConnectionIndicator>Imports and instantiatesTaskCenterModel, callsm.loadInitialData(data.initialFilters)inonMount, cleans up via$effectreturn Model instance:const m = new TaskCenterModel()RATIONALE: thin render layer per ADR-0006 model-first pattern — all state/logic in Model Uses<PageHeader>from$lib/ui/PageHeader.svelte(existing),<Button variant="ghost">from$lib/ui/Button.svelte(existing) Disconnected banner:<div class="bg-warning-light border border-warning-ring text-warning p-3 rounded-lg mb-4">with text "Соединение потеряно. Попытка переподключения..." +<Button variant="secondary">Подключиться сейчас</Button>
Verification for User Story 1
-
T020 [US1] Write L2 UX test for
SummaryPanelinfrontend/src/lib/components/reports/__tests__/SummaryPanel.test.ts@UX_TEST: render_with_summary→cards_visible, click_card→calls_onFilterByTypeAndStatus, loading_state→skeleton_visible, zero_counts→cards_show_0, disconnected_state→opacity_reduced -
T021 [US1] Run US1 verification —
cd backend && source .venv/bin/activate && python -m pytest backend/tests/services/reports/ backend/tests/api/routes/test_reports.py -v,cd frontend && npm run test -- TaskCenterModel SummaryPanel,cd frontend && npm run lintConfirm: summary endpoint returns correct counts, RBAC filtering works per role, model invariants hold, SummaryPanel renders skeletons/empty/ready states -
T022 [US1] Semantic audit — manually verify against
ux_reference.md§3 states: -loading→ full-page skeleton (pulse animation) -empty→ icon + "Нет активных задач" + navigation hints (inbox icon from<Icon name="inbox">) -disconnected→ data visible with red indicator + reconnect banner -error→ error banner + "Повторить загрузку" button -idle→ summary cards + list, green connection indicator
Checkpoint: Summary dashboard functional — open /reports, see aggregated counts, watch them update via WebSocket.
Phase 4: User Story 2 — Детальный список задач с фильтрацией (Priority: P2)
Goal: Оператор фильтрует список задач по типу, статусу, тексту и времени; кликает по карточке сводки → список фильтруется по тип×статус; активные задачи всегда сверху.
Independent Test: На /reports кликнуть по карточке сводки «Миграция / Выполняется (3)» → список показывает только выполняющиеся миграции, остальные скрыты.
Frontend Implementation for User Story 2
-
T023 [US2] Extend
TaskCenterModelinfrontend/src/lib/models/TaskCenterModel.svelte.ts— add filter logic + URL sync @ACTION:applyFilter(partial: Partial<TaskCenterFilter>)— merges partial intofilters, resetspageto 1, calls_syncFiltersToUrl()@ACTION:clearFilters()— resetsfiltersto defaults, calls_syncFiltersToUrl()Private_syncFiltersToUrl(): serializes non-default filters to URL query params viahistory.replaceState(null, '', url), debounced 300msfilteredTasks$derived: applytask_types[],statuses[],search(match on summary + task_id + source_ref),time_range(1h/24h/7d/30d/all → compute time_from from now) RATIONALE: R5 — URL query params survive refresh,replaceStateavoids back-button pollution @TEST_EDGE: apply_type_filter→only_matching_types, clearFilters→all_tasks_restored, search_empty_string→no_filtering -
T024 [US2] Implement
FilterBarinfrontend/src/lib/components/reports/FilterBar.svelte@UX_STATE:default→all filters empty, showing all tasks;filtered→active filter badges visible with "✕" clear button @UX_FEEDBACK: filter change → immediate visual indicator on active filter badges (badge with count), empty result → "Нет задач, соответствующих фильтрам" + "Сбросить фильтры" button Contains: task type<Select multiple>(from$lib/ui/Select.svelteexisting), status<Select multiple>, text<Input type="search">(from$lib/ui/Input.svelteexisting), time range<Select>, sort_by<Select>, sort_order toggle button Props:filters: TaskCenterFilter,onApplyFilter(partial),onClearFilters()Uses semantic tokens:bg-surface-card border border-border text-textfor the bar container -
T025 [US2] Enhance
TaskListinfrontend/src/lib/components/reports/TaskList.svelte(modify existing) @UX_STATE:loading→10 skeleton rows,empty→<EmptyState>with "Нет задач" (inbox icon + links "Запустить перевод/миграцию/бекап"),ready→task rows with pagination @UX_STATE:filtered_empty→"Нет задач, соответствующих фильтрам" + "Сбросить фильтры" button @UX_FEEDBACK: status change → animated badge transition (transition: background-color 300ms), task completion → brief row highlight (green flash SUCCESS, red flash FAILED), new task → slide-in from top Each row shows: type icon (viagetReportTypeProfile), task_id/summary, status<span>badge (inline Tailwind:rounded-full px-2.5 py-0.5 text-xs font-mediumwith colors: success→bg-success-light text-success, failed→bg-destructive-light text-destructive, running→bg-primary/10 text-primary, pending→bg-surface-muted text-text-muted, awaiting_input→bg-warning-light text-warning), relative time ("2 мин назад"), duration, source_ref display Pagination: prev/next buttons + "Page X of Y" display. Uses<Button variant="secondary">from$lib/uiSort: active tasks (PENDING, RUNNING, AWAITING_INPUT) always sorted before completed Props:tasks: TaskReport[],screenState: ScreenState,selectedTaskId: string|null,page: number,onSelectTask(taskId),onPageChange(n)Uses<EmptyState>from$lib/ui/EmptyState.svelte(existing) -
T026 [US2] Wire
SummaryPanelclick → filter in+page.svelteUpdate<SummaryPanel onFilterByTypeAndStatus={...}>callback to callm.applyFilter({ task_types: [type], statuses: [status] })Integrate<FilterBar>and enhanced<TaskList>into page layout below<SummaryPanel>Page structure:PageHeader → SummaryPanel → FilterBar → TaskList
Verification for User Story 2
-
T027 [US2] Write L2 UX test for
FilterBarinfrontend/src/lib/components/reports/__tests__/FilterBar.test.ts@UX_TEST: select_type→calls_onApplyFilter_with_task_types, select_status→calls_onApplyFilter_with_statuses, type_search→calls_onApplyFilter_with_search, click_clear→calls_onClearFilters, active_badge→visible_with_count -
T028 [US2] Run US2 verification —
cd frontend && npm run test -- FilterBar TaskList TaskCenterModel,cd frontend && npm run lintConfirm: type filter shows only matching tasks, status filter works, search matches summary/task_id, time range filters correctly, active tasks always sorted first, pagination controls work, clear filters restores full list
Checkpoint: Full filtering working — summary cards are clickable filters, multi-select dropdowns work, search and time range functional.
Phase 5: User Story 3 — Быстрый переход к логам и результатам задачи (Priority: P3)
Goal: Клик по задаче открывает Task Drawer с логами; hover показывает тултип с деталями; задачи AWAITING_INPUT отображаются с индикатором и кнопкой «Ответить».
Independent Test: На /reports кликнуть по FAILED задаче → открывается Task Drawer с логами, скролл к последней ошибке.
Implementation for User Story 3
-
T029 [US3] Implement
TaskCenterModel.selectTask()infrontend/src/lib/models/TaskCenterModel.svelte.ts— integrate with existing TaskDrawer @ACTION:selectTask(taskId)— setsselectedTaskId, callstaskDrawerStore.openDrawerForTask(taskId)from$lib/stores/taskDrawer.svelte.ts(existing) @SIDE_EFFECT: opens Task Drawer (existing component) which connects to/ws/logs/{taskId}for live log streaming Import:import { openDrawerForTask } from '$lib/stores/taskDrawer.svelte.ts' -
T030 [US3] Enhance
TaskListrow template infrontend/src/lib/components/reports/TaskList.svelte— add hover tooltip + click handler + awaiting_input indicator Click:onclick={() => onSelectTask(task.report_id)}— calls model'sselectTaskwhich opens TaskDrawer Hover tooltip:<div class="absolute z-10 bg-surface-card border border-border rounded-lg shadow-lg p-3 text-sm">showing task_type label, started_at, duration, key param (extract fromsource_refordetails). Shown onmouseenter, hidden onmouseleave. Position: above row. AWAITING_INPUT indicator: task with statusin_progress+ haserror_context.code === 'AWAITING_INPUT'→ show yellow<span class="bg-warning-light text-warning rounded-full px-2 py-0.5 text-xs">Ожидает ввода</span>+ small<Button variant="secondary" size="sm">Ответить</Button>that opens TaskDrawer with input form FAILED task visual: destructive left border ring viaborder-l-4 border-destructive-ringUses<Button>from$lib/ui(existing),<Icon>from$lib/ui(existing) -
T031 [US3] Ensure Task Drawer scrolls to last error for FAILED tasks In
TaskListclick handler: when task status isfailed, passscrollTo: 'last_error'hint toopenDrawerForTaskRATIONALE: spec US3 acceptance criterion #1 — "курсор прокручивается к последней ошибке" If TaskDrawer doesn't support this yet, addscrollToHintparam toopenDrawerForTask()intaskDrawer.svelte.ts
Verification for User Story 3
-
T032 [US3] Write L2 UX test for TaskList drill-down in
frontend/src/lib/components/reports/__tests__/TaskList.test.ts(extend existing) @UX_TEST: click_failed_task→opens_task_drawer, hover_row→shows_tooltip, awaiting_input_task→shows_indicator_and_button, failed_task→has_destructive_left_border -
T033 [US3] Run US3 verification —
cd frontend && npm run test -- TaskList TaskCenterModel, manual smoke: click task → TaskDrawer opens, hover → tooltip appears
Checkpoint: Full drill-down functional — click any task to open logs, hover for details, awaiting_input tasks interactive.
Phase 6: Polish & Cross-Cutting Concerns
Purpose: Regression defense, lint/build gates, semantic index health.
Regression Tests
-
T034 [P] Write rejected-path regression test for RBAC filtering in
backend/tests/services/reports/test_report_service.py@TEST_EDGE: viewer_sees_only_own_tasks→other_users_tasks_excluded, analyst_sees_own_and_system→admin_tasks_excluded, no_role_user→empty_list @TEST_EDGE: task_with_null_user_id→analyst_can_see, task_with_null_user_id→viewer_cannot_see Verify that REJECTED patterns (inline filtering, graph-level filtering) are NOT implemented — test checks_filter_tasks_by_rbac()is used -
T035 [P] Write WebSocket reconnect regression test in
frontend/src/lib/models/__tests__/TaskCenterModel.test.ts@TEST_EDGE: ws_close_abnormal→screenState='reconnecting', ws_reconnect_success→screenState='ready', ws_max_retries→screenState='disconnected', ws_close_normal→screenState='disconnected' Verify exponential backoff: 1s, 2s, 4s, 8s, max 30s (mock timers) -
T036 Run full backend tests —
cd backend && source .venv/bin/activate && python -m pytest -vAll tests must pass. No regressions in non-reports modules. -
T037 Run full frontend tests —
cd frontend && npm run testAll tests pass. No regressions in unrelated components.
Lint & Build Gates
- T038 Run
cd backend && python -m ruff check .— zero new violations - T039 Run
cd frontend && npm run lint— zero new violations - T040 Run
cd frontend && npm run build— production build succeeds (static adapter, SPA mode)
Admin Settings UI
- T040a Add "Отчёты" section to settings page
frontend/src/routes/settings/+page.svelte(or sub-route) Admin-only section with checkboxes for eachTaskType— controlsdisabled_task_typesglobally UsesgetReportsSettings()+updateReportsSettings()from T008c @UX_STATE: loading→spinner, loaded→checkboxes, saving→button spinner, error→toast Save button hidden for non-admins (checksettings:WRITEpermission) RATIONALE: UX group visibility feature — admin can disable task types for all users
Semantic Health
-
T041 [P] Attention compliance audit: verify contracts pass ATTN_1 (first-line density), ATTN_2 (hierarchical IDs
Reports.*,TaskCenter*), ATTN_3 (@SEMANTICSkeyword consistency — all contracts usetask-status-center), ATTN_4 (each contract ≤150 lines, each module ≤400 lines) -
T042 [P] Semantic index rebuild: run
axiom_semantic_index rebuild rebuild_mode="full"— confirm 0 parse warnings from feature files -
T043 [P] Orphan audit: run
axiom_semantic_context workspace_health— confirm no new orphan contracts introduced
Dependencies & Execution Order
Phase Dependencies
Phase 1 (Setup) ──► Phase 2 (Foundational) ──┬──► Phase 3 (US1) ──► Phase 4 (US2) ──► Phase 5 (US3)
│
└──► Phase 6 (Polish — after all stories)
Within User Story Dependencies
- US1: T010,T011 (tests) → T012,T013,T014 (backend) ∥ T015 (model) → T016,T017 (components) → T018,T019 (page) → T020,T021,T022 (verify)
- US2: T023 (model enhance) ∥ T024 (FilterBar) ∥ T025 (TaskList) → T026 (wire) → T027,T028 (verify)
- US3: T029 (model) ∥ T030 (TaskList enhance) ∥ T031 (Drawer scroll) → T032,T033 (verify)
Parallel Opportunities
| Phase | Can run in parallel |
|---|---|
| Phase 2 | T003 ∥ T004 ∥ T005 (different files, different stacks) |
| US1 tests | T010 ∥ T011 (different stacks) |
| US1 components | T016 ∥ T017 (different files) |
| US2 components | T023 ∥ T024 ∥ T025 (different files) |
| US3 | T029 ∥ T030 ∥ T031 (different concerns) |
| Polish | T034 ∥ T035 (different stacks), T041 ∥ T042 ∥ T043 |
MVP Delivery
- Complete Phase 1 + Phase 2
- Complete Phase 3 (US1)
- STOP and VALIDATE: Summary dashboard works with real-time WebSocket updates
- Deploy/demo if ready
Implementation Strategy
Incremental Delivery
| Delivery | Scope | Value |
|---|---|---|
| MVP | US1 | Summary dashboard with real-time counts |
| +1 | +US2 | Full filtering, clickable summary cards, search |
| +2 | +US3 | Task Drawer integration, tooltips, awaiting_input |
Notes
[P]— parallelizable (different files, no shared state)[USx]— user story tag for traceability- C4+ tasks have inlined contract constraints (
@PRE,@POST,@TEST_EDGE,RATIONALE/REJECTED) - Model-first frontend: types → model → L1 test → components → L2 UX test
- Never use
writable()— all new state uses$statein.svelte.ts - Never use raw Tailwind colors — use semantic tokens (
bg-surface-card,text-text,bg-destructive-light, etc.) - After every file write, verify anchor pairs with
read_outlineper anti-corruption protocol