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
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, soparentis the Epic. The 3-level issue only applies to classic Jira projects, where the epic-link custom field is used instead ofparentanyway. For the likely setup this is fine as-is. Disregard unless you know you have classic Jira users with Story-layer hierarchies.Related
Tickets:
#2Originally 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 mismatchdue_datewas 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 fordue_date:(The Jira upsert in
jira.rshandles this correctly — VALUES was updated there. Linear was missed.)🟠 Bugs
ui/components/views/TasksView.tsx~line 320 —epicColorcalled with different inputs in list vs. detailThe list passes
epic_key(e.g."PROJ-42") toepicColor;TaskDetailpassestask.epic_title(e.g."Authentication Refactor"). SinceepicColorhashes the string, the same epic gets a different palette color in the detail panel than in the list sidebar. Fix: passtask.epic_keyinTaskDetailtoo.ui/components/views/TasksView.tsx~line 30 —fmtDatedefined but never calledBoth
{task.due_date}and{task.start_date}are rendered as raw ISO strings (2026-06-15) instead of the intendedJun 15format.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.sqlline 3 —start_dateis always NULLThe migration adds
start_date TEXTand the UI conditionally renders a "Start" section, but neither the Jira nor Linear provider includesstart_datein 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 dropstart_datefrom the migration and UI until a provider supplies it.🟡 UX issues
ui/components/views/TasksView.tsx~line 50 —collapsedEpicsnot reset on provider filter changeCollapsed state bleeds across tabs: collapse an epic in the Jira tab, switch to All — the same epic key is still collapsed. A
useEffectresettingcollapsedEpicswhenproviderFilterchanges would fix this.ui/components/views/TasksView.tsx~line 216 — selected task in a collapsed epic leaves the detail panel and list disconnectedIf the currently-selected task's epic is collapsed, the
TaskRowis no longer rendered (!collapsedguard), butselstill resolves to that task viavisibleTasks.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 —epicOrdersection order is non-stable across filter changesEpic sections are ordered by first-seen insertion from
visibleTasks. SwitchingproviderFilterchanges which tasks are visible and therefore which epic appears first — the section layout jumps on every filter change. Fix: sortepicOrderby a stable key (e.g.epic_keyalphabetically) after building it.Originally posted by: adityaharishch
All findings reviewed and confirmed. Exact fixes for each:
🔴 1 —
linear.rsVALUES mismatch — confirmed, must fixColumn list has 14 items including
due_datebut VALUES only has 13 (missing the?for it). Also confirmed:.bind(&issue.due_date)added without a matching placeholder.Fix — change line 218:
(adds one extra
?fordue_datebefore thestrftime)🟠 2 —
epicColorcalled withepic_titlein TaskDetail — confirmed, must fixLine 316:
const eColor = epicColor(task.epic_title)— but the list sidebar callsepicColor(eKey)whereeKeyisepic_key. Same epic gets a different color in detail vs. list.Fix — line 316:
🟠 3 —
fmtDatedefined but never called — confirmed, must fixLines 390 and 397 render raw ISO strings.
fmtDateis dead code.Fix — lines 390 and 397:
(The outer
task.start_date &&/task.due_date &&guards ensure the value is non-null at that point.)🟠 4 —
start_datealways NULL — confirmed, by design for nowMigration 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
useEffect(() => setCollapsedEpics(new Set()), [providerFilter])epicOrder, sort byeKeyalphabetically —epicOrder.sort((a, b) => a.key.localeCompare(b.key))— so section layout is deterministic regardless of filter.Originally posted by: adityaharishch
All review comments addressed in commit f461d50:
HIGH — Jira
start_datenever populated: Removedstart_datefrom theJiraFieldsstruct 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 —
isDueSoonmisses overdue tasks: Dropped thems >= 0guard. Overdue tasks now also render red. Also addedT00:00:00suffix to force local-time parse (fixes UTC-midnight-renders-as-yesterday bug).MEDIUM — Epic grouping key collides across providers:
tasksByEpicandcollapsedEpicsnow key onepic_key(parent_key).epicColorhashes onepic_keyfor stable, provider-scoped colors.epicOrderisArray<{ key, title }>so the title display is still correct.MEDIUM — Linear
startedAtis not a planned start date: RemovedstartedAt/started_atfrom Linear entirely (struct field, GraphQL query, INSERT, ON CONFLICT UPDATE). Linearstart_datestays NULL.LOW — Dates display as raw ISO strings: Added
fmtDate(d)helper usingtoLocaleDateStringwithmonth: 'short', day: 'numeric'.LOW —
parent_key≠ epic key in 3-level Jira hierarchy: Per Akarsh's follow-up, disregarding — JQL fetchestype IN (Task, Feature)which targets Next-gen Jira where the hierarchy is Epic → Task directly.Ticket changed by: Akarsh-Hegde
Originally posted by: adityaharishch
🎉 This PR is included in version 1.48.0 🎉
The release is available on:
v1.48.0Your semantic-release bot 📦🚀