Skip to content

Commit 105dc98

Browse files
committed
fix(relations): filter epic ↔ task belongs_to edges from Relations panel
The epic↔task association already has dedicated UI on both sides: the task sidebar shows an Epic field with its own selector, and the epic page lists its tasks separately. Surfacing the auto-managed belongs_to edge in the Relations panel just duplicated the same information with a non-clickable raw label. Extended enrichRelations to drop edges where kind='belongs_to' and the endpoints are (epics, tasks) in either direction, alongside the existing tag-edge filter. Added a regression test that creates a linkTaskToEpic association and asserts the belongs_to edge is hidden from both the task's and the epic's Relations panels.
1 parent 35ef8b7 commit 105dc98

2 files changed

Lines changed: 43 additions & 7 deletions

File tree

src/lib/store-manager.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -544,11 +544,16 @@ export class StoreManager {
544544
* relative to a queried entity: targetGraph, targetId, title, direction.
545545
* Titles are batch-resolved per graph (one SQL query per distinct graph).
546546
*
547-
* Auto-managed `kind: 'tagged'` edges from the `tags` graph are filtered out:
548-
* tags already render in their own sidebar section, and surfacing them in
549-
* the Relations panel just clutters it with one row per tag. The raw edges
550-
* remain available via MCP notes_list_links / tasks_list_links / etc. for
551-
* LLM agents that want the full graph.
547+
* Auto-managed edges are filtered out before enrichment:
548+
*
549+
* - `kind: 'tagged'` from/to the `tags` graph — tags already render in
550+
* their own sidebar section, and one row per tag in Relations is noise.
551+
* - `kind: 'belongs_to'` between `epics` and `tasks` — the epic↔task link
552+
* has dedicated UI (Epic field in the task sidebar, task list on the epic
553+
* page), so duplicating it in Relations is noise.
554+
*
555+
* The raw edges remain available via MCP notes_list_links /
556+
* tasks_list_links / etc. for LLM agents that want the full graph.
552557
*/
553558
enrichRelations(
554559
entityGraph: GraphName,
@@ -561,8 +566,13 @@ export class StoreManager {
561566
title: string;
562567
direction: 'out' | 'in';
563568
}> {
564-
// Drop auto-tagged edges before enrichment.
565-
const userEdges = edges.filter(e => !(e.kind === 'tagged' && (e.fromGraph === 'tags' || e.toGraph === 'tags')));
569+
// Drop auto-managed edges before enrichment.
570+
const isTagEdge = (e: Edge) => e.kind === 'tagged' && (e.fromGraph === 'tags' || e.toGraph === 'tags');
571+
const isEpicTaskLink = (e: Edge) =>
572+
e.kind === 'belongs_to' &&
573+
((e.fromGraph === 'epics' && e.toGraph === 'tasks') ||
574+
(e.fromGraph === 'tasks' && e.toGraph === 'epics'));
575+
const userEdges = edges.filter(e => !isTagEdge(e) && !isEpicTaskLink(e));
566576
// Group target ids by their graph so we can batch-resolve titles.
567577
const idsByGraph = new Map<GraphName, number[]>();
568578
const view = userEdges.map(e => {

src/tests/store/store-manager.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,32 @@ describe('StoreManager', () => {
571571
expect(skillRel?.title).toBe('S title');
572572
});
573573

574+
it('filters out epic ↔ task belongs_to edges (have their own UI surface)', async () => {
575+
// linkTaskToEpic creates an epic→task edge with kind='belongs_to'.
576+
// The epic is already shown in the task sidebar (Epic field) and the
577+
// epic page lists its tasks separately, so this edge would just clutter
578+
// the Relations panel on either side.
579+
const epic = await manager.createEpic({ title: 'Big Epic', description: '' });
580+
const task = await manager.createTask({ title: 'Some task', description: '' });
581+
manager.linkTaskToEpic(epic.id, task.id);
582+
583+
// From the task's perspective: belongs_to should NOT appear.
584+
const taskEdges = [
585+
...manager.findOutgoingEdges('tasks', task.id),
586+
...manager.findIncomingEdges('tasks', task.id),
587+
];
588+
const taskRels = manager.enrichRelations('tasks', task.id, taskEdges);
589+
expect(taskRels.find(r => r.kind === 'belongs_to')).toBeUndefined();
590+
591+
// From the epic's perspective: belongs_to should NOT appear either.
592+
const epicEdges = [
593+
...manager.findOutgoingEdges('epics', epic.id),
594+
...manager.findIncomingEdges('epics', epic.id),
595+
];
596+
const epicRels = manager.enrichRelations('epics', epic.id, epicEdges);
597+
expect(epicRels.find(r => r.kind === 'belongs_to')).toBeUndefined();
598+
});
599+
574600
it('filters out auto-tagged edges so the Relations panel stays clean', async () => {
575601
// Notes with tags get auto-created `tags → knowledge` edges (kind: 'tagged').
576602
// Tags already render in their own sidebar section; the Relations panel

0 commit comments

Comments
 (0)