Menu

#258 feat(tasks): epic grouping, due dates, collapsible sections

closed
nobody
released (243)
2026-06-11
2026-06-11
Anonymous
No

Originally created by: adityaharishch

Summary

  • Epic grouping: tasks list is now sectioned by epic with color-coded headers (deterministic 8-hue palette). Each section is collapsible via chevron toggle.
  • Due date / start date: new migration (035) adds due_date and start_date to pm_tasks. Jira fetches duedate; Linear fetches dueDate + startedAt. Shown in task detail panel; due dates within 3 days turn red.
  • Sticky detail panel: right-side task detail is sticky top-8 on large screens — selecting a task lower in the list no longer requires scrolling back up.
  • Epic label in detail: selected task's detail shows the epic key + title above the task title, in the epic's color.
  • UX cleanup: removed illegible 2px category SegBar from task rows; removed "Y on board" counter from header.

Test plan

  • [ ] Tasks page loads and tasks are grouped under their epic headers
  • [ ] Clicking an epic header collapses/expands its tasks
  • [ ] Selecting a task far down the list keeps the detail panel visible without scrolling
  • [ ] Task detail shows due date when set in Jira (run meridian tasks-sync first)
  • [ ] Due dates within 3 days of today appear in red
  • [ ] Tasks with no epic fall into a "No epic" bucket at the bottom
  • [ ] cargo test passes
  • [ ] npm run build in ui/ passes

🤖 Generated with Claude Code

Related

Tickets: #194
Tickets: #257
Tickets: #262

Discussion

  • Anonymous

    Anonymous - 2026-06-11

    Originally posted by: adityaharishch

    Follow-up on LOW [#2] (parent_key ≠ epic key)

    On reflection this is lower-risk than the comment implies. The JQL query fetches type IN (Task, Feature) which excludes Stories — in Next-gen (team-managed) Jira the hierarchy is Epic → Task directly, so parent is the Epic. The 3-level issue only applies to classic Jira projects, where the epic-link custom field is used instead of parent anyway. For the likely setup this is fine as-is. Disregard unless you know you have classic Jira users with Story-layer hierarchies.

     

    Related

    Tickets: #2

  • Anonymous

    Anonymous - 2026-06-11

    Originally posted by: Akarsh-Hegde

    Code Review — feat(tasks): epic grouping, due dates, collapsible sections

    Seven findings across the diff, ranked by severity.


    🔴 Critical — merge blocker

    src/intelligence/providers/linear.rs ~line 214 — Linear INSERT column/value count mismatch

    due_date was added to the column list (14 columns total) but the VALUES clause was not updated — it still has 13 items. SQLite will reject every Linear upsert with a column-count mismatch error. Additionally, .bind(&issue.due_date) is added, giving 11 bind calls for 10 ? placeholders — a second runtime mismatch.

    Fix: add a ? to the VALUES line for due_date:

    VALUES (?, 'linear', ?, ?, ?, '', ?, ?, ?, ?, ?, ?, ?,
            strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
    

    (The Jira upsert in jira.rs handles this correctly — VALUES was updated there. Linear was missed.)


    🟠 Bugs

    ui/components/views/TasksView.tsx ~line 320 — epicColor called with different inputs in list vs. detail

    The list passes epic_key (e.g. "PROJ-42") to epicColor; TaskDetail passes task.epic_title (e.g. "Authentication Refactor"). Since epicColor hashes the string, the same epic gets a different palette color in the detail panel than in the list sidebar. Fix: pass task.epic_key in TaskDetail too.


    ui/components/views/TasksView.tsx ~line 30 — fmtDate defined but never called

    Both {task.due_date} and {task.start_date} are rendered as raw ISO strings (2026-06-15) instead of the intended Jun 15 format. fmtDate() is dead code. Fix: replace {task.due_date} / {task.start_date} with {fmtDate(task.due_date)} / {fmtDate(task.start_date)} in the TaskDetail date section.


    src/migrations/035_pm_tasks_dates.sql line 3 — start_date is always NULL

    The migration adds start_date TEXT and the UI conditionally renders a "Start" section, but neither the Jira nor Linear provider includes start_date in its INSERT column list or binds a value for it. The column, API field, and UI branch are all dead on arrival. Either wire up the provider fields or drop start_date from the migration and UI until a provider supplies it.


    🟡 UX issues

    ui/components/views/TasksView.tsx ~line 50 — collapsedEpics not reset on provider filter change

    Collapsed state bleeds across tabs: collapse an epic in the Jira tab, switch to All — the same epic key is still collapsed. A useEffect resetting collapsedEpics when providerFilter changes would fix this.


    ui/components/views/TasksView.tsx ~line 216 — selected task in a collapsed epic leaves the detail panel and list disconnected

    If the currently-selected task's epic is collapsed, the TaskRow is no longer rendered (!collapsed guard), but sel still resolves to that task via visibleTasks.find(). The detail panel shows the correct task; the list shows no highlighted row. Consider auto-expanding the epic when the selected task is inside it, or auto-selecting the first visible task on collapse.


    ui/components/views/TasksView.tsx ~line 124 — epicOrder section order is non-stable across filter changes

    Epic sections are ordered by first-seen insertion from visibleTasks. Switching providerFilter changes which tasks are visible and therefore which epic appears first — the section layout jumps on every filter change. Fix: sort epicOrder by a stable key (e.g. epic_key alphabetically) after building it.

     
  • Anonymous

    Anonymous - 2026-06-11

    Originally posted by: adityaharishch

    All findings reviewed and confirmed. Exact fixes for each:


    🔴 1 — linear.rs VALUES mismatch — confirmed, must fix

    Column list has 14 items including due_date but VALUES only has 13 (missing the ? for it). Also confirmed: .bind(&issue.due_date) added without a matching placeholder.

    Fix — change line 218:

    // before
    VALUES (?, 'linear', ?, ?, ?, '', ?, ?, ?, ?, ?, ?,
    // after
    VALUES (?, 'linear', ?, ?, ?, '', ?, ?, ?, ?, ?, ?, ?,
    

    (adds one extra ? for due_date before the strftime)


    🟠 2 — epicColor called with epic_title in TaskDetail — confirmed, must fix

    Line 316: const eColor = epicColor(task.epic_title) — but the list sidebar calls epicColor(eKey) where eKey is epic_key. Same epic gets a different color in detail vs. list.

    Fix — line 316:

    // before
    const eColor = epicColor(task.epic_title)
    // after
    const eColor = epicColor(task.epic_key ?? null)
    

    🟠 3 — fmtDate defined but never called — confirmed, must fix

    Lines 390 and 397 render raw ISO strings. fmtDate is dead code.

    Fix — lines 390 and 397:

    // before
    <p ...>{task.start_date}</p>
    <p ...>{task.due_date}</p>
    // after
    <p ...>{fmtDate(task.start_date!)}</p>
    <p ...>{fmtDate(task.due_date!)}</p>
    

    (The outer task.start_date && / task.due_date && guards ensure the value is non-null at that point.)


    🟠 4 — start_date always NULL — confirmed, by design for now

    Migration adds the column; no provider writes it. The {task.start_date && ...} guard means the UI branch never renders in practice — harmless dead code for now. Leaving the column in the migration so a future provider (GitHub issue dates, Linear startedAt) can populate it without another migration. No change needed unless you want to explicitly drop the UI branch until a provider is wired.


    🟡 5–7 — UX issues

    • collapsedEpics not reset on filter change: Real issue. Fix: useEffect(() => setCollapsedEpics(new Set()), [providerFilter])
    • Selected task in collapsed epic: Edge case — detail panel shows correctly, list row just has no highlight. Acceptable for now; auto-expand on selection is a v2 polish item.
    • epicOrder non-stable across filters: Real issue. Fix: after building epicOrder, sort by eKey alphabetically — epicOrder.sort((a, b) => a.key.localeCompare(b.key)) — so section layout is deterministic regardless of filter.
     
  • Anonymous

    Anonymous - 2026-06-11

    Originally posted by: adityaharishch

    All review comments addressed in commit f461d50:

    HIGH — Jira start_date never populated: Removed start_date from the JiraFields struct and the INSERT bind entirely. Accepting that Jira start dates won't be populated rather than requesting a field that doesn't exist on Task/Feature types.

    MEDIUM — isDueSoon misses overdue tasks: Dropped the ms >= 0 guard. Overdue tasks now also render red. Also added T00:00:00 suffix to force local-time parse (fixes UTC-midnight-renders-as-yesterday bug).

    MEDIUM — Epic grouping key collides across providers: tasksByEpic and collapsedEpics now key on epic_key (parent_key). epicColor hashes on epic_key for stable, provider-scoped colors. epicOrder is Array<{ key, title }> so the title display is still correct.

    MEDIUM — Linear startedAt is not a planned start date: Removed startedAt/started_at from Linear entirely (struct field, GraphQL query, INSERT, ON CONFLICT UPDATE). Linear start_date stays NULL.

    LOW — Dates display as raw ISO strings: Added fmtDate(d) helper using toLocaleDateString with month: 'short', day: 'numeric'.

    LOW — parent_key ≠ epic key in 3-level Jira hierarchy: Per Akarsh's follow-up, disregarding — JQL fetches type IN (Task, Feature) which targets Next-gen Jira where the hierarchy is Epic → Task directly.

     
  • Anonymous

    Anonymous - 2026-06-11

    Ticket changed by: Akarsh-Hegde

    • status: open --> closed
     
  • Anonymous

    Anonymous - 2026-06-11

    Originally posted by: adityaharishch

    🎉 This PR is included in version 1.48.0 🎉

    The release is available on:

    Your semantic-release bot 📦🚀

     

Log in to post a comment.