Skip to content

[fix] Normalize legacy persisted tasks#30

Closed
arrokh wants to merge 1 commit into
tintinweb:masterfrom
arrokh:tasks/fix/legacy-task-normalization
Closed

[fix] Normalize legacy persisted tasks#30
arrokh wants to merge 1 commit into
tintinweb:masterfrom
arrokh:tasks/fix/legacy-task-normalization

Conversation

@arrokh

@arrokh arrokh commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Issue Details

Pi could exit from an uncaught exception while rendering the task widget:

TypeError: Cannot read properties of undefined (reading length)
at TaskWidget.renderWidget (.../src/ui/task-widget.ts:188:55)

The failing path was a persisted legacy task record that did not include newer task fields such as blockedBy, blocks, or metadata. TaskWidget and other consumers expect TaskStore to return fully-shaped Task objects, so reading task.blockedBy.length could throw before the UI could recover.

Purpose

Normalize legacy persisted task data at the TaskStore load boundary so every consumer receives canonical task objects. This preserves existing widget/tool behavior while making old task files compatible with the current task schema.

Summary

  • Added load-time normalization for file-backed tasks in TaskStore.
  • Default missing metadata to {} and missing dependency arrays to [].
  • Preserve existing timestamps and nextId behavior for persisted task files.
  • Added regression coverage at both the store boundary and widget render path.

Issue → Solution Flow

flowchart TD
    A[Legacy persisted task file] --> B[TaskStore.load]
    B --> C{Missing newer fields?\nblockedBy / blocks / metadata}
    C -->|Before| D[Raw partial task returned]
    D --> E[TaskWidget.renderWidget]
    E --> F[task.blockedBy.length]
    F --> G[Uncaught TypeError\npi exits]

    C -->|After| H[normalizeTask fills defaults]
    H --> I[Canonical Task object]
    I --> J[Widget and tools read arrays safely]
    J --> K[Task list renders without throwing]
Loading

Validation

  • Reproduced the failing widget regression before the fix.
  • npm test -- --run test/task-store.test.ts test/task-widget.test.ts — 80 tests passed.
  • npm run typecheck — passed.
  • npm run lint — passed.
  • npm run build — passed.
  • npm run prepublishOnly — passed (lint, typecheck, test, build; 170 tests).
  • Node smoke check confirmed a legacy task is normalized and rendered by the widget without throwing.
  • Confirmed installed package source/dist copy matches the TaskStore fix for immediate local use.

@arrokh
arrokh marked this pull request as ready for review July 1, 2026 01:55
@arrokh arrokh changed the title [tasks][fix] Normalize legacy persisted tasks [fix] Normalize legacy persisted tasks Jul 1, 2026
@arrokh arrokh closed this by deleting the head repository Jul 14, 2026
tintinweb added a commit that referenced this pull request Jul 22, 2026
Task files written before the blocking feature have no blockedBy/blocks/
metadata. TaskStore.load() deserialized them as-is, so consumers reading
those fields unguarded (e.g. task.blockedBy.length in the widget) threw a
TypeError. Because the widget renders on a pi-tui timer, the throw was
uncaught and killed the whole pi process on the first render after upgrade.

- Normalize every record at the load() boundary (normalizeTask): default
  metadata/blocks/blockedBy, with type guards for hand-edited files. This is
  the single fix point — the same unguarded accesses exist across the store
  and index mutation paths, not just the widget.
- Wrap the widget render in try/catch returning a safe fallback, so a render
  error can never again escape to the TUI timer and crash the host.

Fixes #33. Normalization approach based on #30 (thanks @arrokh); reported by
@eSaadster.

Co-authored-by: Noor Octavian (Alvin) Anwar <nooroctaviananwar@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant