backlog-mcp 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/schema.d.ts CHANGED
@@ -1,16 +1,25 @@
1
1
  export declare function isValidTaskId(id: unknown): id is string;
2
2
  export declare function parseTaskId(id: string): number | null;
3
- export declare function formatTaskId(num: number): string;
3
+ export declare function formatTaskId(num: number, type?: 'task' | 'epic'): string;
4
4
  export declare function nextTaskId(existingTasks: ReadonlyArray<{
5
5
  id: string;
6
- }>): string;
6
+ }>, type?: 'task' | 'epic'): string;
7
7
  export declare const STATUSES: readonly ["open", "in_progress", "blocked", "done", "cancelled"];
8
8
  export type Status = (typeof STATUSES)[number];
9
+ export declare const TASK_TYPES: readonly ["task", "epic"];
10
+ export type TaskType = (typeof TASK_TYPES)[number];
11
+ export interface Reference {
12
+ url: string;
13
+ title?: string;
14
+ }
9
15
  export interface Task {
10
16
  id: string;
11
17
  title: string;
12
18
  description?: string;
13
19
  status: Status;
20
+ type?: TaskType;
21
+ epic_id?: string;
22
+ references?: Reference[];
14
23
  created_at: string;
15
24
  updated_at: string;
16
25
  blocked_reason?: string;
@@ -20,6 +29,9 @@ export interface CreateTaskInput {
20
29
  id?: string;
21
30
  title: string;
22
31
  description?: string;
32
+ type?: TaskType;
33
+ epic_id?: string;
34
+ references?: Reference[];
23
35
  }
24
36
  export declare function createTask(input: CreateTaskInput, existingTasks?: ReadonlyArray<{
25
37
  id: string;
package/dist/schema.js CHANGED
@@ -2,41 +2,55 @@
2
2
  // Task ID
3
3
  // ============================================================================
4
4
  const TASK_ID_PATTERN = /^TASK-(\d{4,})$/;
5
+ const EPIC_ID_PATTERN = /^EPIC-(\d{4,})$/;
5
6
  export function isValidTaskId(id) {
6
- return typeof id === 'string' && TASK_ID_PATTERN.test(id);
7
+ return typeof id === 'string' && (TASK_ID_PATTERN.test(id) || EPIC_ID_PATTERN.test(id));
7
8
  }
8
9
  export function parseTaskId(id) {
9
- const match = TASK_ID_PATTERN.exec(id);
10
+ const match = TASK_ID_PATTERN.exec(id) || EPIC_ID_PATTERN.exec(id);
10
11
  if (!match?.[1])
11
12
  return null;
12
13
  return parseInt(match[1], 10);
13
14
  }
14
- export function formatTaskId(num) {
15
- return `TASK-${num.toString().padStart(4, '0')}`;
15
+ export function formatTaskId(num, type) {
16
+ const prefix = type === 'epic' ? 'EPIC' : 'TASK';
17
+ return `${prefix}-${num.toString().padStart(4, '0')}`;
16
18
  }
17
- export function nextTaskId(existingTasks) {
19
+ export function nextTaskId(existingTasks, type) {
20
+ const pattern = type === 'epic' ? EPIC_ID_PATTERN : TASK_ID_PATTERN;
18
21
  let maxNum = 0;
19
22
  for (const task of existingTasks) {
20
- const num = parseTaskId(task.id);
21
- if (num !== null && num > maxNum) {
22
- maxNum = num;
23
+ const match = pattern.exec(task.id);
24
+ if (match?.[1]) {
25
+ const num = parseInt(match[1], 10);
26
+ if (num > maxNum)
27
+ maxNum = num;
23
28
  }
24
29
  }
25
- return formatTaskId(maxNum + 1);
30
+ return formatTaskId(maxNum + 1, type);
26
31
  }
27
32
  // ============================================================================
28
33
  // Status
29
34
  // ============================================================================
30
35
  export const STATUSES = ['open', 'in_progress', 'blocked', 'done', 'cancelled'];
36
+ export const TASK_TYPES = ['task', 'epic'];
31
37
  export function createTask(input, existingTasks = []) {
32
38
  const now = new Date().toISOString();
33
- return {
34
- id: input.id ?? nextTaskId(existingTasks),
39
+ const task = {
40
+ id: input.id ?? nextTaskId(existingTasks, input.type),
35
41
  title: input.title,
36
- description: input.description,
37
42
  status: 'open',
38
43
  created_at: now,
39
44
  updated_at: now,
40
45
  };
46
+ if (input.description)
47
+ task.description = input.description;
48
+ if (input.type)
49
+ task.type = input.type;
50
+ if (input.epic_id)
51
+ task.epic_id = input.epic_id;
52
+ if (input.references?.length)
53
+ task.references = input.references;
54
+ return task;
41
55
  }
42
56
  //# sourceMappingURL=schema.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,UAAU;AACV,+EAA+E;AAE/E,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAE1C,MAAM,UAAU,aAAa,CAAC,EAAW;IACvC,OAAO,OAAO,EAAE,KAAK,QAAQ,IAAI,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7B,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,QAAQ,GAAG,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,aAA4C;IACrE,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjC,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,GAAG,MAAM,EAAE,CAAC;YACjC,MAAM,GAAG,GAAG,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,+EAA+E;AAC/E,SAAS;AACT,+EAA+E;AAE/E,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,CAAU,CAAC;AA4BzF,MAAM,UAAU,UAAU,CACxB,KAAsB,EACtB,gBAA+C,EAAE;IAEjD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,OAAO;QACL,EAAE,EAAE,KAAK,CAAC,EAAE,IAAI,UAAU,CAAC,aAAa,CAAC;QACzC,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,MAAM,EAAE,MAAM;QACd,UAAU,EAAE,GAAG;QACf,UAAU,EAAE,GAAG;KAChB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
package/dist/server.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import 'dotenv/config';
2
+ export {};
package/dist/server.js CHANGED
@@ -1,12 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import 'dotenv/config';
2
+ try {
3
+ await import('dotenv/config');
4
+ }
5
+ catch { }
3
6
  import { readFileSync } from 'node:fs';
4
7
  import { dirname, join } from 'node:path';
5
8
  import { fileURLToPath } from 'node:url';
6
9
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
10
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
11
  import { z } from 'zod';
9
- import { createTask, STATUSES } from './schema.js';
12
+ import { createTask, STATUSES, TASK_TYPES } from './schema.js';
10
13
  import { storage } from './backlog.js';
11
14
  import { startViewer } from './viewer.js';
12
15
  // Read version from package.json
@@ -26,15 +29,21 @@ server.registerTool('backlog_list', {
26
29
  description: 'List tasks from backlog. Shows open/in_progress/blocked by default. Use status=["done"] to see completed tasks.',
27
30
  inputSchema: {
28
31
  status: z.array(z.enum(STATUSES)).optional().describe('Filter: open, in_progress, blocked, done, cancelled. Default: open, in_progress, blocked'),
32
+ type: z.enum(TASK_TYPES).optional().describe('Filter by type: task, epic, or omit for all'),
33
+ epic_id: z.string().optional().describe('Filter tasks belonging to a specific epic'),
29
34
  counts: z.boolean().optional().describe('Return counts per status instead of task list'),
30
35
  limit: z.number().optional().describe('Max tasks to return. Default: 20'),
31
36
  },
32
- }, async ({ status, counts, limit }) => {
33
- const tasks = storage.list({ status, limit });
37
+ }, async ({ status, type, epic_id, counts, limit }) => {
38
+ let tasks = storage.list({ status, limit });
39
+ if (type)
40
+ tasks = tasks.filter(t => (t.type ?? 'task') === type);
41
+ if (epic_id)
42
+ tasks = tasks.filter(t => t.epic_id === epic_id);
34
43
  if (counts) {
35
44
  return { content: [{ type: 'text', text: JSON.stringify(storage.counts(), null, 2) }] };
36
45
  }
37
- const list = tasks.map((t) => ({ id: t.id, title: t.title, status: t.status }));
46
+ const list = tasks.map((t) => ({ id: t.id, title: t.title, status: t.status, type: t.type ?? 'task', epic_id: t.epic_id }));
38
47
  return { content: [{ type: 'text', text: JSON.stringify(list, null, 2) }] };
39
48
  });
40
49
  server.registerTool('backlog_get', {
@@ -55,9 +64,12 @@ server.registerTool('backlog_create', {
55
64
  inputSchema: {
56
65
  title: z.string().describe('Task title'),
57
66
  description: z.string().optional().describe('Task description in markdown'),
67
+ type: z.enum(TASK_TYPES).optional().describe('Type: task (default) or epic'),
68
+ epic_id: z.string().optional().describe('Parent epic ID to link this task to'),
69
+ references: z.array(z.object({ url: z.string(), title: z.string().optional() })).optional().describe('Reference links with optional titles'),
58
70
  },
59
- }, async ({ title, description }) => {
60
- const task = createTask({ title, description }, storage.list());
71
+ }, async ({ title, description, type, epic_id, references }) => {
72
+ const task = createTask({ title, description, type, epic_id, references }, storage.list());
61
73
  storage.add(task);
62
74
  return { content: [{ type: 'text', text: `Created ${task.id}` }] };
63
75
  });
@@ -68,20 +80,32 @@ server.registerTool('backlog_update', {
68
80
  title: z.string().optional().describe('New title'),
69
81
  description: z.string().optional().describe('New description'),
70
82
  status: z.enum(STATUSES).optional().describe('New status'),
83
+ epic_id: z.string().nullable().optional().describe('Parent epic ID (null to unlink)'),
84
+ references: z.array(z.object({ url: z.string(), title: z.string().optional() })).optional().describe('Reference links with optional titles'),
71
85
  blocked_reason: z.string().optional().describe('Reason if status is blocked'),
72
86
  evidence: z.array(z.string()).optional().describe('Proof of completion when marking done - links to PRs, docs, or notes'),
73
87
  },
74
- }, async ({ id, title, description, status, blocked_reason, evidence }) => {
88
+ }, async ({ id, title, description, status, epic_id, references, blocked_reason, evidence }) => {
75
89
  const task = storage.get(id);
76
90
  if (!task) {
77
91
  return { content: [{ type: 'text', text: `Not found: ${id}` }], isError: true };
78
92
  }
79
- const updates = { title, description, status, blocked_reason, evidence };
80
- const updated = {
81
- ...task,
82
- ...Object.fromEntries(Object.entries(updates).filter(([_, v]) => v !== undefined)),
83
- updated_at: new Date().toISOString(),
84
- };
93
+ const updates = {};
94
+ if (title !== undefined)
95
+ updates.title = title;
96
+ if (description !== undefined)
97
+ updates.description = description;
98
+ if (status !== undefined)
99
+ updates.status = status;
100
+ if (epic_id !== undefined)
101
+ updates.epic_id = epic_id ?? undefined;
102
+ if (references !== undefined)
103
+ updates.references = references;
104
+ if (blocked_reason !== undefined)
105
+ updates.blocked_reason = blocked_reason;
106
+ if (evidence !== undefined)
107
+ updates.evidence = evidence;
108
+ const updated = { ...task, ...updates, updated_at: new Date().toISOString() };
85
109
  storage.save(updated);
86
110
  return { content: [{ type: 'text', text: `Updated ${id}` }] };
87
111
  });
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AAEA,IAAI,CAAC;IAAC,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;AAAC,CAAC;AAAC,MAAM,CAAC,CAAA,CAAC;AAE/C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAa,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,iCAAiC;AACjC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AAErF,eAAe;AACf,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,MAAM,CAAC;AACvD,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAEtB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,aAAa;IACnB,OAAO,EAAE,GAAG,CAAC,OAAO;CACrB,CAAC,CAAC;AAEH,+EAA+E;AAC/E,QAAQ;AACR,+EAA+E;AAE/E,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;IACE,WAAW,EAAE,iHAAiH;IAC9H,WAAW,EAAE;QACX,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0FAA0F,CAAC;QACjJ,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;QAC3F,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2CAA2C,CAAC;QACpF,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;QACxF,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;KAC1E;CACF,EACD,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE;IACjD,IAAI,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5C,IAAI,IAAI;QAAE,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;IACjE,IAAI,OAAO;QAAE,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC;IAC9D,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IACnG,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC5H,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACvF,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,YAAY,CACjB,aAAa,EACb;IACE,WAAW,EAAE,uEAAuE;IACpF,WAAW,EAAE;QACX,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,kDAAkD,CAAC;KAC5G;CACF,EACD,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;IACf,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACvF,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,cAAc,GAAG,EAAE,CAAC,CAAC;IACtF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,CAAC;AACrF,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,YAAY,CACjB,gBAAgB,EAChB;IACE,WAAW,EAAE,mCAAmC;IAChD,WAAW,EAAE;QACX,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC;QACxC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,8BAA8B,CAAC;QAC3E,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,8BAA8B,CAAC;QAC5E,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;QAC9E,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;KAC7I;CACF,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE;IAC1D,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3F,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,WAAW,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;AAC9E,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,YAAY,CACjB,gBAAgB,EAChB;IACE,WAAW,EAAE,0BAA0B;IACvC,WAAW,EAAE;QACX,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QAC5C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC;QAClD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAC9D,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC1D,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;QACrF,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;QAC5I,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6BAA6B,CAAC;QAC7E,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sEAAsE,CAAC;KAC1H;CACF,EACD,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,EAAE,EAAE;IAC1F,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC7B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3F,CAAC;IACD,MAAM,OAAO,GAAkB,EAAE,CAAC;IAClC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;IAC/C,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,CAAC,WAAW,GAAG,WAAW,CAAC;IACjE,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;IAClD,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,CAAC,OAAO,GAAG,OAAO,IAAI,SAAS,CAAC;IAClE,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,CAAC,UAAU,GAAG,UAAU,CAAC;IAC9D,IAAI,cAAc,KAAK,SAAS;QAAE,OAAO,CAAC,cAAc,GAAG,cAAc,CAAC;IAC1E,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACxD,MAAM,OAAO,GAAS,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;IACpF,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;AACzE,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,YAAY,CACjB,gBAAgB,EAChB;IACE,WAAW,EAAE,6CAA6C;IAC1D,WAAW,EAAE;QACX,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC;KAC7C;CACF,EACD,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;IACf,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACnC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3F,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;AACzE,CAAC,CACF,CAAC;AAEF,+EAA+E;AAC/E,OAAO;AACP,+EAA+E;AAE/E,KAAK,UAAU,IAAI;IACjB,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,MAAM,CAAC,CAAC;IACvE,WAAW,CAAC,UAAU,CAAC,CAAC;IAExB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { epicIcon, taskIcon } from '../icons/index.js';
2
+ export class TaskBadge extends HTMLElement {
3
+ connectedCallback() {
4
+ this.render();
5
+ }
6
+ static get observedAttributes() {
7
+ return ['task-id', 'type'];
8
+ }
9
+ attributeChangedCallback() {
10
+ this.render();
11
+ }
12
+ render() {
13
+ const id = this.getAttribute('task-id') || '';
14
+ const type = this.getAttribute('type') || 'task';
15
+ const icon = type === 'epic' ? epicIcon : taskIcon;
16
+ this.className = `task-badge type-${type}`;
17
+ this.innerHTML = `<span class="task-badge-icon">${icon}</span><span class="task-badge-id">${id}</span>`;
18
+ }
19
+ }
20
+ customElements.define('task-badge', TaskBadge);
@@ -21,7 +21,8 @@ export class TaskDetail extends HTMLElement {
21
21
  const task = await fetchTask(taskId);
22
22
  const headerHtml = `
23
23
  <div class="task-header-left">
24
- <button class="btn-outline task-id-btn" onclick="navigator.clipboard.writeText('${task.id}')" title="Copy ID">${task.id} ${copyIcon}</button>
24
+ ${task.epic_id ? `<button class="btn-outline epic-id-btn" onclick="navigator.clipboard.writeText('${task.epic_id}')" title="Copy Epic ID"><task-badge task-id="${task.epic_id}" type="epic"></task-badge> ${copyIcon}</button>` : ''}
25
+ <button class="btn-outline task-id-btn" onclick="navigator.clipboard.writeText('${task.id}')" title="Copy ID"><task-badge task-id="${task.id}" type="${task.type || 'task'}"></task-badge> ${copyIcon}</button>
25
26
  <span class="status-badge status-${task.status || 'open'}">${(task.status || 'open').replace('_', ' ')}</span>
26
27
  ${task.filePath ? `
27
28
  <div class="task-meta-path">
@@ -38,10 +39,21 @@ export class TaskDetail extends HTMLElement {
38
39
  const metaHtml = `
39
40
  <div class="task-meta-card">
40
41
  <h1 class="task-meta-title">${task.title || ''}</h1>
41
- <div class="task-meta-dates">
42
+ <div class="task-meta-row">
42
43
  <span>Created: ${task.created_at ? new Date(task.created_at).toLocaleDateString() : ''}</span>
43
44
  <span>Updated: ${task.updated_at ? new Date(task.updated_at).toLocaleDateString() : ''}</span>
45
+ ${task.epic_id ? `<span class="task-meta-epic"><span class="task-meta-epic-label">Epic:</span><a href="#" class="epic-link" data-epic-id="${task.epic_id}"><task-badge task-id="${task.epic_id}" type="epic"></task-badge></a>${task.epicTitle ? `<span class="epic-title">${task.epicTitle}</span>` : ''}</span>` : ''}
44
46
  </div>
47
+ ${task.references?.length ? `
48
+ <div class="task-meta-evidence">
49
+ <div class="task-meta-evidence-label">References:</div>
50
+ <ul>${task.references.map((r) => {
51
+ const url = typeof r === 'string' ? r : r.url;
52
+ const title = typeof r === 'string' ? r : (r.title || r.url);
53
+ return `<li><a href="${url}" target="_blank" rel="noopener">${title}</a></li>`;
54
+ }).join('')}</ul>
55
+ </div>
56
+ ` : ''}
45
57
  ${task.evidence?.length ? `
46
58
  <div class="task-meta-evidence">
47
59
  <div class="task-meta-evidence-label">Evidence:</div>
@@ -58,6 +70,18 @@ export class TaskDetail extends HTMLElement {
58
70
  article.appendChild(mdBlock);
59
71
  this.innerHTML = '';
60
72
  this.appendChild(article);
73
+ // Bind epic link click to navigate
74
+ const epicLink = this.querySelector('.epic-link');
75
+ if (epicLink) {
76
+ epicLink.addEventListener('click', (e) => {
77
+ e.preventDefault();
78
+ const epicId = epicLink.dataset.epicId;
79
+ if (epicId) {
80
+ this.loadTask(epicId);
81
+ document.dispatchEvent(new CustomEvent('task-selected', { detail: { taskId: epicId } }));
82
+ }
83
+ });
84
+ }
61
85
  // Bind copy raw button (in pane header)
62
86
  const copyRawBtn = paneHeader?.querySelector('.copy-raw');
63
87
  if (copyRawBtn && task.raw) {
@@ -3,31 +3,57 @@ const FILTERS = [
3
3
  { key: 'completed', label: 'Completed' },
4
4
  { key: 'all', label: 'All' },
5
5
  ];
6
+ const TYPE_FILTERS = [
7
+ { key: 'task', label: 'Tasks' },
8
+ { key: 'epic', label: 'Epics' },
9
+ { key: 'all', label: 'All' },
10
+ ];
6
11
  export class TaskFilterBar extends HTMLElement {
7
12
  currentFilter = 'active';
13
+ currentType = 'all';
8
14
  connectedCallback() {
9
15
  this.render();
10
16
  this.attachListeners();
11
17
  }
12
18
  render() {
13
- const buttons = FILTERS.map(f => `<button class="filter-btn ${this.currentFilter === f.key ? 'active' : ''}" data-filter="${f.key}">${f.label}</button>`).join('');
14
- this.innerHTML = `<div class="filter-bar">${buttons}</div>`;
19
+ const statusButtons = FILTERS.map(f => `<button class="filter-btn ${this.currentFilter === f.key ? 'active' : ''}" data-filter="${f.key}">${f.label}</button>`).join('');
20
+ const typeButtons = TYPE_FILTERS.map(f => `<button class="filter-btn ${this.currentType === f.key ? 'active' : ''}" data-type="${f.key}">${f.label}</button>`).join('');
21
+ this.innerHTML = `
22
+ <div class="filter-bar"><span class="filter-label">Status</span>${statusButtons}</div>
23
+ <div class="filter-bar type-filter"><span class="filter-label">Type</span>${typeButtons}</div>
24
+ `;
15
25
  }
16
26
  attachListeners() {
17
- this.querySelectorAll('.filter-btn').forEach(btn => {
27
+ this.querySelectorAll('[data-filter]').forEach(btn => {
18
28
  btn.addEventListener('click', (e) => {
19
29
  const filter = e.target.dataset.filter;
20
30
  if (filter)
21
31
  this.setFilter(filter);
22
32
  });
23
33
  });
34
+ this.querySelectorAll('[data-type]').forEach(btn => {
35
+ btn.addEventListener('click', (e) => {
36
+ const type = e.target.dataset.type;
37
+ if (type)
38
+ this.setType(type);
39
+ });
40
+ });
24
41
  }
25
42
  setFilter(filter) {
43
+ document.dispatchEvent(new CustomEvent('filter-change', { detail: { filter, type: this.currentType } }));
44
+ }
45
+ setType(type) {
46
+ document.dispatchEvent(new CustomEvent('filter-change', { detail: { filter: this.currentFilter, type } }));
47
+ }
48
+ setState(filter, type) {
26
49
  this.currentFilter = filter;
27
- this.querySelectorAll('.filter-btn').forEach(btn => {
50
+ this.currentType = type;
51
+ this.querySelectorAll('[data-filter]').forEach(btn => {
28
52
  btn.classList.toggle('active', btn.dataset.filter === filter);
29
53
  });
30
- document.dispatchEvent(new CustomEvent('filter-change', { detail: { filter } }));
54
+ this.querySelectorAll('[data-type]').forEach(btn => {
55
+ btn.classList.toggle('active', btn.dataset.type === type);
56
+ });
31
57
  }
32
58
  }
33
59
  customElements.define('task-filter-bar', TaskFilterBar);
@@ -1,3 +1,4 @@
1
+ import { pinIcon } from '../icons/index.js';
1
2
  export class TaskItem extends HTMLElement {
2
3
  connectedCallback() {
3
4
  this.render();
@@ -7,39 +8,50 @@ export class TaskItem extends HTMLElement {
7
8
  const id = this.dataset.id || '';
8
9
  const title = this.dataset.title || '';
9
10
  const status = this.dataset.status || 'open';
11
+ const type = this.dataset.type || 'task';
12
+ const isChild = this.dataset.child === 'true';
13
+ const isPinned = this.hasAttribute('pinned');
10
14
  const isSelected = this.hasAttribute('selected');
11
- this.className = `task-item ${isSelected ? 'selected' : ''}`;
15
+ this.className = `task-item-wrapper ${isPinned ? 'pinned' : ''} ${isChild ? 'child' : ''}`;
12
16
  this.innerHTML = `
13
- <div class="task-item-header">
14
- <span class="task-id">${id}</span>
17
+ <div class="task-item ${isSelected ? 'selected' : ''} type-${type}">
18
+ <task-badge task-id="${id}" type="${type}"></task-badge>
15
19
  <span class="task-title">${title}</span>
16
20
  <span class="status-badge status-${status}">${status.replace('_', ' ')}</span>
17
21
  </div>
22
+ ${type === 'epic' ? `<button class="pin-btn ${isPinned ? 'pinned' : ''}" title="${isPinned ? 'Unpin' : 'Pin to filter'}">${pinIcon}</button>` : ''}
18
23
  `;
19
24
  }
20
25
  attachListeners() {
21
- this.addEventListener('click', () => {
26
+ const taskItem = this.querySelector('.task-item');
27
+ taskItem?.addEventListener('click', () => {
22
28
  const taskId = this.dataset.id;
23
29
  if (!taskId)
24
30
  return;
25
- // Update selection in list
26
- document.querySelectorAll('task-item').forEach(item => {
27
- const htmlItem = item;
28
- item.classList.toggle('selected', htmlItem.dataset.id === taskId);
31
+ document.querySelectorAll('task-item .task-item').forEach(item => {
32
+ item.classList.toggle('selected', item.closest('task-item')?.dataset.id === taskId);
29
33
  });
30
- // Notify detail pane
31
34
  const detailPane = document.querySelector('task-detail');
32
35
  if (detailPane) {
33
36
  detailPane.loadTask(taskId);
34
37
  }
35
- // Emit event for URL state
36
38
  document.dispatchEvent(new CustomEvent('task-selected', { detail: { taskId } }));
37
- // Update task list's selected state
38
39
  const taskList = document.querySelector('task-list');
39
40
  if (taskList) {
40
41
  taskList.setSelected(taskId);
41
42
  }
42
43
  });
44
+ const pinBtn = this.querySelector('.pin-btn');
45
+ if (pinBtn) {
46
+ pinBtn.addEventListener('click', (e) => {
47
+ e.stopPropagation();
48
+ const epicId = this.dataset.id;
49
+ if (!epicId)
50
+ return;
51
+ const isPinned = this.hasAttribute('pinned');
52
+ document.dispatchEvent(new CustomEvent('epic-pin', { detail: { epicId: isPinned ? null : epicId } }));
53
+ });
54
+ }
43
55
  }
44
56
  }
45
57
  customElements.define('task-item', TaskItem);
@@ -4,26 +4,48 @@ function escapeAttr(text) {
4
4
  }
5
5
  export class TaskList extends HTMLElement {
6
6
  currentFilter = 'active';
7
+ currentType = 'all';
8
+ pinnedEpicId = null;
7
9
  selectedTaskId = null;
8
10
  connectedCallback() {
9
- // Get initial selection from URL
10
11
  const params = new URLSearchParams(window.location.search);
11
12
  this.selectedTaskId = params.get('task');
13
+ this.pinnedEpicId = params.get('epic');
12
14
  this.loadTasks();
13
15
  setInterval(() => this.loadTasks(), 5000);
14
- // Listen for filter changes
15
16
  document.addEventListener('filter-change', ((e) => {
16
17
  this.currentFilter = e.detail.filter;
18
+ this.currentType = e.detail.type ?? 'all';
17
19
  this.loadTasks();
18
20
  }));
19
- // Listen for task selection
20
21
  document.addEventListener('task-selected', ((e) => {
21
22
  this.setSelected(e.detail.taskId);
22
23
  }));
24
+ document.addEventListener('epic-pin', ((e) => {
25
+ this.pinnedEpicId = e.detail.epicId;
26
+ this.loadTasks();
27
+ }));
28
+ }
29
+ setState(filter, type, epicId, taskId) {
30
+ this.currentFilter = filter;
31
+ this.currentType = type;
32
+ this.pinnedEpicId = epicId;
33
+ this.selectedTaskId = taskId;
34
+ this.loadTasks();
23
35
  }
24
36
  async loadTasks() {
25
37
  try {
26
- const tasks = await fetchTasks(this.currentFilter);
38
+ let tasks = await fetchTasks(this.currentFilter);
39
+ // Type filter
40
+ if (this.currentType !== 'all') {
41
+ tasks = tasks.filter(t => (t.type ?? 'task') === this.currentType);
42
+ }
43
+ // Epic pin filter
44
+ if (this.pinnedEpicId) {
45
+ const pinnedEpic = tasks.find(t => t.id === this.pinnedEpicId);
46
+ const children = tasks.filter(t => t.epic_id === this.pinnedEpicId);
47
+ tasks = pinnedEpic ? [pinnedEpic, ...children] : children;
48
+ }
27
49
  this.render(tasks);
28
50
  }
29
51
  catch (error) {
@@ -40,14 +62,29 @@ export class TaskList extends HTMLElement {
40
62
  `;
41
63
  return;
42
64
  }
65
+ // Group: epics first with their children, then orphan tasks
66
+ const epics = tasks.filter(t => (t.type ?? 'task') === 'epic');
67
+ const childTasks = tasks.filter(t => t.epic_id && epics.some(e => e.id === t.epic_id));
68
+ const orphanTasks = tasks.filter(t => (t.type ?? 'task') === 'task' && !childTasks.includes(t));
69
+ const grouped = [];
70
+ for (const epic of epics) {
71
+ grouped.push(epic);
72
+ for (const child of childTasks.filter(t => t.epic_id === epic.id)) {
73
+ grouped.push({ ...child, isChild: true });
74
+ }
75
+ }
76
+ grouped.push(...orphanTasks);
43
77
  this.innerHTML = `
44
78
  <div class="task-list">
45
- ${tasks.map(task => `
79
+ ${grouped.map(task => `
46
80
  <task-item
47
81
  data-id="${task.id}"
48
82
  data-title="${escapeAttr(task.title)}"
49
83
  data-status="${task.status}"
84
+ data-type="${task.type ?? 'task'}"
85
+ ${task.isChild ? 'data-child="true"' : ''}
50
86
  ${this.selectedTaskId === task.id ? 'selected' : ''}
87
+ ${this.pinnedEpicId === task.id ? 'pinned' : ''}
51
88
  ></task-item>
52
89
  `).join('')}
53
90
  </div>
@@ -1 +1,4 @@
1
1
  export const copyIcon = `<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"/><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z"/></svg>`;
2
+ export const pinIcon = `<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><path d="M4.456.734a1.75 1.75 0 012.826.504l.613 1.327a3.08 3.08 0 002.084 1.707l2.454.584c1.332.317 1.8 1.972.832 2.94L11.06 10l3.72 3.72a.75.75 0 11-1.06 1.06L10 11.06l-2.204 2.205c-.968.968-2.623.5-2.94-.832l-.584-2.454a3.08 3.08 0 00-1.707-2.084l-1.327-.613a1.75 1.75 0 01-.504-2.826L4.456.734z"/></svg>`;
3
+ export const epicIcon = `<svg viewBox="0 0 16 16" width="14" height="14" fill="none" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="epicGrad" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#f0b429"/><stop offset="100%" stop-color="#ff6b2d"/></linearGradient></defs><polygon points="8,1 14,4.5 14,11.5 8,15 2,11.5 2,4.5" stroke="url(#epicGrad)" stroke-width="1.5" fill="none"/><circle cx="8" cy="8" r="2" fill="url(#epicGrad)"/></svg>`;
4
+ export const taskIcon = `<svg viewBox="0 0 16 16" width="14" height="14" fill="none" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="taskGrad" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#00d4ff"/><stop offset="100%" stop-color="#7b2dff"/></linearGradient></defs><polygon points="8,1 15,8 8,15 1,8" stroke="url(#taskGrad)" stroke-width="1.5" fill="none"/></svg>`;
@@ -2,30 +2,28 @@ import './components/task-filter-bar.js';
2
2
  import './components/task-list.js';
3
3
  import './components/task-item.js';
4
4
  import './components/task-detail.js';
5
- // URL state management
6
- const params = new URLSearchParams(window.location.search);
7
- const initialFilter = params.get('filter') || 'active';
8
- const initialTask = params.get('task');
9
- // Set initial filter
10
- document.addEventListener('DOMContentLoaded', () => {
5
+ import './components/task-badge.js';
6
+ import { urlState } from './utils/url-state.js';
7
+ // Subscribe components to URL state changes - single source of truth
8
+ urlState.subscribe((state) => {
11
9
  const filterBar = document.querySelector('task-filter-bar');
12
- if (filterBar?.setFilter)
13
- filterBar.setFilter(initialFilter);
14
- if (initialTask) {
10
+ filterBar?.setState?.(state.filter, state.type);
11
+ const taskList = document.querySelector('task-list');
12
+ taskList?.setState?.(state.filter, state.type, state.epic, state.task);
13
+ if (state.task) {
15
14
  const detail = document.querySelector('task-detail');
16
- if (detail?.loadTask)
17
- detail.loadTask(initialTask);
15
+ detail?.loadTask?.(state.task);
18
16
  }
19
17
  });
20
- // Update URL on filter change
18
+ // Initialize on load
19
+ document.addEventListener('DOMContentLoaded', () => urlState.init());
20
+ // Component events -> URL updates
21
21
  document.addEventListener('filter-change', ((e) => {
22
- const url = new URL(window.location.href);
23
- url.searchParams.set('filter', e.detail.filter);
24
- history.replaceState(null, '', url);
22
+ urlState.set({ filter: e.detail.filter, type: e.detail.type });
25
23
  }));
26
- // Update URL on task selection
27
24
  document.addEventListener('task-selected', ((e) => {
28
- const url = new URL(window.location.href);
29
- url.searchParams.set('task', e.detail.taskId);
30
- history.replaceState(null, '', url);
25
+ urlState.set({ task: e.detail.taskId });
26
+ }));
27
+ document.addEventListener('epic-pin', ((e) => {
28
+ urlState.set({ epic: e.detail.epicId });
31
29
  }));
@@ -0,0 +1,42 @@
1
+ class UrlState {
2
+ listeners = [];
3
+ pushing = false;
4
+ constructor() {
5
+ window.addEventListener('popstate', () => this.notify());
6
+ }
7
+ get() {
8
+ const params = new URLSearchParams(window.location.search);
9
+ return {
10
+ filter: params.get('filter') || 'active',
11
+ type: params.get('type') || 'all',
12
+ task: params.get('task'),
13
+ epic: params.get('epic'),
14
+ };
15
+ }
16
+ set(updates) {
17
+ if (this.pushing)
18
+ return;
19
+ this.pushing = true;
20
+ const url = new URL(window.location.href);
21
+ for (const [key, value] of Object.entries(updates)) {
22
+ if (value)
23
+ url.searchParams.set(key, value);
24
+ else
25
+ url.searchParams.delete(key);
26
+ }
27
+ history.pushState(null, '', url);
28
+ this.notify();
29
+ this.pushing = false;
30
+ }
31
+ subscribe(listener) {
32
+ this.listeners.push(listener);
33
+ }
34
+ notify() {
35
+ const state = this.get();
36
+ this.listeners.forEach(fn => fn(state));
37
+ }
38
+ init() {
39
+ this.notify();
40
+ }
41
+ }
42
+ export const urlState = new UrlState();
package/dist/viewer.js CHANGED
@@ -87,8 +87,10 @@ export async function startViewer(port = 3030) {
87
87
  }
88
88
  const filePath = storage.getFilePath(taskId);
89
89
  const raw = storage.getMarkdown(taskId);
90
+ const epic = task.epic_id ? storage.get(task.epic_id) : null;
91
+ const epicTitle = epic?.title;
90
92
  res.writeHead(200, { 'Content-Type': 'application/json' });
91
- res.end(JSON.stringify({ ...task, filePath, raw }));
93
+ res.end(JSON.stringify({ ...task, filePath, raw, epicTitle }));
92
94
  return;
93
95
  }
94
96
  // GET /open/:id - open file in default editor
@@ -1 +1 @@
1
- {"version":3,"file":"viewer.js","sourceRoot":"","sources":["../src/viewer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"viewer.js","sourceRoot":"","sources":["../src/viewer.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backlog-mcp",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Minimal task backlog MCP server for Claude and AI agents",
5
5
  "keywords": [
6
6
  "mcp",
@@ -44,7 +44,7 @@
44
44
  "start": "node dist/server.js",
45
45
  "test": "vitest run",
46
46
  "test:watch": "vitest",
47
- "dev": "tsc -p viewer/tsconfig.json && concurrently \"tsx watch src/server.ts\" \"tsc -p viewer/tsconfig.json --watch --preserveWatchOutput\""
47
+ "dev": "lsof -ti:3030 | xargs kill -9 2>/dev/null; tsc -p viewer/tsconfig.json && concurrently \"tsx watch src/server.ts\" \"tsc -p viewer/tsconfig.json --watch --preserveWatchOutput\""
48
48
  },
49
49
  "engines": {
50
50
  "node": ">=18.0.0"
package/viewer/styles.css CHANGED
@@ -72,10 +72,24 @@
72
72
  /* Filter Bar */
73
73
  .filter-bar {
74
74
  display: flex;
75
+ align-items: center;
75
76
  gap: 8px;
76
77
  flex-wrap: wrap;
77
78
  }
78
79
 
80
+ .filter-bar.type-filter {
81
+ margin-top: 8px;
82
+ padding-top: 8px;
83
+ border-top: 1px solid #3e3e42;
84
+ }
85
+
86
+ .filter-label {
87
+ font-size: 11px;
88
+ color: #888;
89
+ text-transform: uppercase;
90
+ min-width: 40px;
91
+ }
92
+
79
93
  .filter-btn {
80
94
  padding: 6px 12px;
81
95
  border: 1px solid #3e3e42;
@@ -101,16 +115,60 @@
101
115
  .task-list {
102
116
  display: flex;
103
117
  flex-direction: column;
104
- gap: 8px;
118
+ gap: 6px;
119
+ }
120
+
121
+ .task-item-wrapper {
122
+ display: flex;
123
+ align-items: stretch;
124
+ gap: 4px;
125
+ min-width: 0;
126
+ }
127
+
128
+ .task-item-wrapper.child {
129
+ margin-left: 16px;
130
+ padding-left: 12px;
131
+ position: relative;
132
+ }
133
+
134
+ .task-item-wrapper.child::before {
135
+ content: '';
136
+ position: absolute;
137
+ left: 0;
138
+ top: 50%;
139
+ width: 8px;
140
+ height: 1px;
141
+ background: #3e3e42;
142
+ }
143
+
144
+ .task-item-wrapper.child::after {
145
+ content: '';
146
+ position: absolute;
147
+ left: 0;
148
+ top: -6px;
149
+ bottom: 50%;
150
+ width: 1px;
151
+ background: #3e3e42;
152
+ }
153
+
154
+ .task-item-wrapper.pinned .task-item {
155
+ border-color: #f0b429;
156
+ background: #3d3522;
105
157
  }
106
158
 
107
159
  .task-item {
160
+ flex: 1;
161
+ display: flex;
162
+ align-items: center;
163
+ gap: 8px;
108
164
  background: #252526;
109
165
  border: 1px solid #3e3e42;
110
166
  border-radius: 6px;
111
167
  padding: 12px;
112
168
  cursor: pointer;
113
169
  transition: all 0.2s;
170
+ min-width: 0;
171
+ overflow: hidden;
114
172
  }
115
173
 
116
174
  .task-item:hover {
@@ -123,11 +181,28 @@
123
181
  border-color: #007acc;
124
182
  }
125
183
 
126
- .task-item-header {
184
+ .task-item-wrapper > .pin-btn {
185
+ background: #252526;
186
+ border: 1px solid #3e3e42;
187
+ border-radius: 6px;
188
+ padding: 0 8px;
189
+ cursor: pointer;
190
+ color: #888;
127
191
  display: flex;
128
- justify-content: space-between;
129
192
  align-items: center;
130
- gap: 8px;
193
+ transition: all 0.2s;
194
+ }
195
+
196
+ .task-item-wrapper > .pin-btn:hover {
197
+ background: #2d2d30;
198
+ border-color: #f0b429;
199
+ color: #f0b429;
200
+ }
201
+
202
+ .task-item-wrapper > .pin-btn.pinned {
203
+ background: #3d3522;
204
+ border-color: #f0b429;
205
+ color: #f0b429;
131
206
  }
132
207
 
133
208
  .task-id {
@@ -139,6 +214,7 @@
139
214
  .task-title {
140
215
  font-size: 13px;
141
216
  flex: 1;
217
+ min-width: 0;
142
218
  white-space: nowrap;
143
219
  overflow: hidden;
144
220
  text-overflow: ellipsis;
@@ -150,6 +226,7 @@
150
226
  border-radius: 10px;
151
227
  font-weight: 500;
152
228
  text-transform: uppercase;
229
+ flex-shrink: 0;
153
230
  }
154
231
 
155
232
  .status-open { background: #1f6feb; color: white; }
@@ -158,6 +235,49 @@
158
235
  .status-done { background: #1a7f37; color: white; }
159
236
  .status-cancelled { background: #57606a; color: white; }
160
237
 
238
+ /* Task Badge */
239
+ .task-badge {
240
+ display: inline-flex;
241
+ align-items: center;
242
+ gap: 4px;
243
+ font-family: monospace;
244
+ font-size: inherit;
245
+ color: inherit;
246
+ }
247
+
248
+ .task-badge-icon {
249
+ display: inline-flex;
250
+ }
251
+
252
+ .task-badge-id {
253
+ font-weight: 600;
254
+ }
255
+
256
+ .task-item .task-badge {
257
+ color: #888;
258
+ }
259
+
260
+ .type-icon {
261
+ margin-right: 6px;
262
+ display: inline-flex;
263
+ vertical-align: middle;
264
+ }
265
+
266
+ .type-icon.type-epic {
267
+ color: #f0b429;
268
+ }
269
+
270
+ .type-icon.type-task {
271
+ color: #3b82f6;
272
+ }
273
+
274
+ .epic-icon-inline {
275
+ display: inline-flex;
276
+ vertical-align: middle;
277
+ margin-right: 4px;
278
+ color: #f0b429;
279
+ }
280
+
161
281
  /* Task Detail */
162
282
  .task-detail-header {
163
283
  margin-bottom: 20px;
@@ -310,11 +430,33 @@
310
430
  margin: 0 0 8px 0;
311
431
  font-weight: 600;
312
432
  }
313
- .task-meta-dates {
433
+ .task-meta-row {
314
434
  font-size: 12px;
315
435
  color: #8b949e;
316
436
  display: flex;
437
+ align-items: center;
317
438
  gap: 16px;
439
+ flex-wrap: wrap;
440
+ }
441
+ .task-meta-epic {
442
+ display: inline-flex;
443
+ align-items: center;
444
+ gap: 4px;
445
+ }
446
+ .task-meta-epic-label {
447
+ margin-right: 0;
448
+ }
449
+ .epic-link {
450
+ color: #f0b429;
451
+ text-decoration: none;
452
+ display: inline-flex;
453
+ align-items: center;
454
+ }
455
+ .epic-link:hover {
456
+ text-decoration: underline;
457
+ }
458
+ .epic-title {
459
+ color: #d4d4d4;
318
460
  }
319
461
  .task-meta-path {
320
462
  display: flex;
@@ -430,7 +572,7 @@ task-detail .markdown-body {
430
572
  }
431
573
 
432
574
 
433
- .task-id-btn {
575
+ .task-id-btn, .epic-id-btn {
434
576
  font-family: monospace;
435
577
  font-weight: 600;
436
578
  }