Files
ss-tools/specs/034-task-status-center/tasks.md
busya 61f3e6db75 feat: Git manager UI — панель управления Git + HelpTooltip + ReviewToggle
- GitManager: переработан в GitWorkspacePanel с вкладками
- Добавлен GitLifecycleHeader с быстрыми действиями
- RepositoryDashboardGrid: поддержка ReviewToggle, badges, env filter
- HelpTooltip: универсальный компонент подсказок с тестами
- GitManagerModel: доработаны экшены, добавлен isReady, loadDefaultBranch
- Локализация en/ru для Git UI
- tailwind: добавлен animation-delay-200
- ConfirmDialog: a11y-атрибуты для кнопок
2026-07-04 15:01:45 +03:00

29 KiB
Raw Blame History

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-center and verify specs/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, TaskTypeSummary Pydantic schemas to backend/src/models/report.py Extend existing ReportModels region — 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.ts Interfaces from data-model.md §3: TaskType, ReportStatus, ErrorContext, TaskReport, ReportQuery, ReportCollection, ReportDetailView, StatusCounts, TaskTypeSummary, TaskSummary, TaskStatusEvent, TaskCenterFilter, ScreenState @DATA_CONTRACT: each interface references backend/src/models/report.py counterpart via JSDoc @see

  • T005 [P] Add clean_release profile to frontend/src/lib/components/reports/reportTypeProfiles.ts Add entry: clean_release: { key: 'clean_release', label: 'Clean Release', variant: 'success', icon: 'shield-check', fallback: false } RATIONALE: R8 — sync frontend profiles with backend TaskType enum (was missing clean_release)

RBAC & API Client (sequential after T003/T004)

  • T006 Implement _filter_tasks_by_rbac() in backend/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 in frontend/src/lib/api/reports.ts New: export async function getReportsSummary(): Promise<TaskSummary>GET /api/reports/summary via api.fetchApi Enhance: getReports() returns Promise<ReportCollection>, getReportDetail() returns Promise<ReportDetailView> (replace generic <T>) @DATA_CONTRACT: ReportQuery → ReportCollection, (none) → TaskSummary, reportId → ReportDetailView import types from $types/reports

  • T008 [P] Add "created_at" to allowed sort_by values in backend/src/models/report.py ReportQuery._validate_sort_by validator Change allowed set from {"updated_at", "status", "task_type"} to {"updated_at", "created_at", "status", "task_type"}

  • T008a [P] Add ReportsSettings Pydantic schema to backend/src/models/report.py Schema: 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/reports and PUT /api/settings/reports endpoints in backend/src/api/routes/settings.py (or reports.py) GET: returns { disabled_task_types: [...] } — any authenticated user with tasks:READ PUT: accepts { disabled_task_types: [...] } — admin only (settings:WRITE permission), validates types against TaskType enum @PRE: GET — authenticated user; PUT — admin role with settings:WRITE @POST: GET → ReportsSettings; PUT → updated ReportsSettings @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() to frontend/src/lib/api/reports.ts New: export async function getReportsSettings(): Promise<ReportsSettings>GET /api/settings/reports New: export async function updateReportsSettings(settings: ReportsSettings): Promise<ReportsSettings>PUT /api/settings/reports import ReportsSettings from $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" -v All 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 TaskCenterModel invariants in frontend/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). Mock fetchApi and WebSocket.

  • T011 [P] [US1] Write contract test for Reports.SummaryService.get_summary() in backend/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: TaskSummary invariants — 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 in backend/src/services/reports/report_service.py @PRE: task_manager initialized, current_user authenticated with tasks:READ @POST: TaskSummary with counts filtered by user role — aggregate get_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, then resolve_task_type(task.plugin_id) for type grouping

  • T013 [US1] Add GET /api/reports/summary endpoint in backend/src/api/routes/reports.py @PRE: current_user authenticated via Depends(get_current_user) @POST: returns TaskSummary — 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), inject task_manager + clean_release_repo dependencies

  • T014 [US1] Enhance ReportsService.list_reports() in backend/src/services/reports/report_service.py with RBAC row-level filtering @PRE: current_user passed to service method @POST: ReportCollection.items filtered by user role (same rules as summary) Add current_user parameter. 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 TaskCenterModel in frontend/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 to getTaskEventsWsUrl(), buffer events, 100ms debounce flush @ACTION: disconnectWebSocket(), destroy() — cleanup WS, timers @ACTION: toggleTypeVisibility(type) — toggle type in visibleTypes, persist to localStorage @INVARIANT: active tasks (PENDING, RUNNING, AWAITING_INPUT) sorted before completed tasks in $derived filteredTasks @INVARIANT: visibleSummary excludes types in disabledTypes (admin) AND not in visibleTypes (operator) @INVARIANT: DECOMPOSITION GATE: 400 lines, 40 methods (ADR-0010) @SIDE_EFFECT: connects to /ws/task-events WebSocket, calls ReportsApi, updates browser URL via history.replaceState(), reads/writes localStorage for visibleTypes @POST: all public state-mutating methods trigger $derived recomputation @POST: WS lifecycle managed: connect on loadInitialData, disconnect on destroy RATIONALE: 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 SummaryPanel in frontend/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 → CSS transition: all 200ms on count numbers, card click → calls onFilterByTypeAndStatus(taskType, status) @UX_RECOVERY: disconnected → auto-reconnect indicator, manual reconnect via onReconnect prop Props: summary: TaskSummary|null, screenState: ScreenState, onFilterByTypeAndStatus: (TaskType, ReportStatus)=>void Uses <Card> from $lib/ui/Card.svelte (existing). Colors per ux_reference.md: running→token primary, pending→muted, success→success, failed→destructive, awaiting_input→warning. @POST: clicking card emits onFilterByTypeAndStatus(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 when m.wsConnected===true, yellow pulsing when reconnecting, red when disconnected Uses semantic tokens: bg-success / bg-warning / bg-destructive in a w-3 h-3 rounded-full span Shows tooltip on hover: "Подключено / Переподключение / Соединение потеряно"

Frontend Page (US1 — thin render layer)

  • T018 [US1] Create frontend/src/routes/reports/+page.ts load function Parse url.searchParams into initial TaskCenterFilter: task_types (comma-sep→array), statuses (comma-sep→array), search, time_range, sort_by, sort_order Return { initialFilters } for the page component RATIONALE: R5 — filter state survives page refresh via URL query params

  • T019 [US1] Rewrite frontend/src/routes/reports/+page.svelte as thin render layer Layout: <PageHeader title="Центр статусов"> + refresh <Button variant="ghost"> (calls m.refreshSummary()) Below: <SummaryPanel>, existing <ReportCard>-based list (placeholder for US2-enhanced <TaskList>), <ConnectionIndicator> Imports and instantiates TaskCenterModel, calls m.loadInitialData(data.initialFilters) in onMount, cleans up via $effect return 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 SummaryPanel in frontend/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 lint Confirm: 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 TaskCenterModel in frontend/src/lib/models/TaskCenterModel.svelte.ts — add filter logic + URL sync @ACTION: applyFilter(partial: Partial<TaskCenterFilter>) — merges partial into filters, resets page to 1, calls _syncFiltersToUrl() @ACTION: clearFilters() — resets filters to defaults, calls _syncFiltersToUrl() Private _syncFiltersToUrl(): serializes non-default filters to URL query params via history.replaceState(null, '', url), debounced 300ms filteredTasks $derived: apply task_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, replaceState avoids back-button pollution @TEST_EDGE: apply_type_filter→only_matching_types, clearFilters→all_tasks_restored, search_empty_string→no_filtering

  • T024 [US2] Implement FilterBar in frontend/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.svelte existing), status <Select multiple>, text <Input type="search"> (from $lib/ui/Input.svelte existing), 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-text for the bar container

  • T025 [US2] Enhance TaskList in frontend/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 (via getReportTypeProfile), task_id/summary, status <span> badge (inline Tailwind: rounded-full px-2.5 py-0.5 text-xs font-medium with 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/ui Sort: 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 SummaryPanel click → filter in +page.svelte Update <SummaryPanel onFilterByTypeAndStatus={...}> callback to call m.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 FilterBar in frontend/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 lint Confirm: 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() in frontend/src/lib/models/TaskCenterModel.svelte.ts — integrate with existing TaskDrawer @ACTION: selectTask(taskId) — sets selectedTaskId, calls taskDrawerStore.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 TaskList row template in frontend/src/lib/components/reports/TaskList.svelte — add hover tooltip + click handler + awaiting_input indicator Click: onclick={() => onSelectTask(task.report_id)} — calls model's selectTask which 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 from source_ref or details). Shown on mouseenter, hidden on mouseleave. Position: above row. AWAITING_INPUT indicator: task with status in_progress + has error_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 via border-l-4 border-destructive-ring Uses <Button> from $lib/ui (existing), <Icon> from $lib/ui (existing)

  • T031 [US3] Ensure Task Drawer scrolls to last error for FAILED tasks In TaskList click handler: when task status is failed, pass scrollTo: 'last_error' hint to openDrawerForTask RATIONALE: spec US3 acceptance criterion #1 — "курсор прокручивается к последней ошибке" If TaskDrawer doesn't support this yet, add scrollToHint param to openDrawerForTask() in taskDrawer.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 -v All tests must pass. No regressions in non-reports modules.

  • T037 Run full frontend tests — cd frontend && npm run test All 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 each TaskType — controls disabled_task_types globally Uses getReportsSettings() + updateReportsSettings() from T008c @UX_STATE: loading→spinner, loaded→checkboxes, saving→button spinner, error→toast Save button hidden for non-admins (check settings:WRITE permission) 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 (@SEMANTICS keyword consistency — all contracts use task-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

  1. Complete Phase 1 + Phase 2
  2. Complete Phase 3 (US1)
  3. STOP and VALIDATE: Summary dashboard works with real-time WebSocket updates
  4. 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 $state in .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_outline per anti-corruption protocol