@sascha384/tic 1.35.0 → 2.0.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.
Files changed (96) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/backends/availability.js +4 -2
  3. package/dist/backends/availability.js.map +1 -1
  4. package/dist/backends/factory.d.ts +5 -3
  5. package/dist/backends/factory.js +28 -43
  6. package/dist/backends/factory.js.map +1 -1
  7. package/dist/backends/files/hash.d.ts +1 -0
  8. package/dist/backends/files/hash.js +5 -0
  9. package/dist/backends/files/hash.js.map +1 -0
  10. package/dist/backends/files/index.d.ts +48 -0
  11. package/dist/backends/files/index.js +174 -0
  12. package/dist/backends/files/index.js.map +1 -0
  13. package/dist/backends/files/sync.d.ts +13 -0
  14. package/dist/backends/files/sync.js +69 -0
  15. package/dist/backends/files/sync.js.map +1 -0
  16. package/dist/backends/jira/config.d.ts +1 -1
  17. package/dist/backends/jira/config.js +6 -9
  18. package/dist/backends/jira/config.js.map +1 -1
  19. package/dist/backends/types.d.ts +12 -0
  20. package/dist/backends/types.js +5 -1
  21. package/dist/backends/types.js.map +1 -1
  22. package/dist/cli/commands/config.js +27 -14
  23. package/dist/cli/commands/config.js.map +1 -1
  24. package/dist/cli/commands/init.js +10 -3
  25. package/dist/cli/commands/init.js.map +1 -1
  26. package/dist/cli/commands/mcp.d.ts +4 -4
  27. package/dist/cli/commands/mcp.js +16 -25
  28. package/dist/cli/commands/mcp.js.map +1 -1
  29. package/dist/cli/index.js +16 -19
  30. package/dist/cli/index.js.map +1 -1
  31. package/dist/commands.js +0 -6
  32. package/dist/commands.js.map +1 -1
  33. package/dist/components/Header.js +3 -2
  34. package/dist/components/Header.js.map +1 -1
  35. package/dist/components/OverlayPanel.d.ts +2 -1
  36. package/dist/components/OverlayPanel.js +8 -1
  37. package/dist/components/OverlayPanel.js.map +1 -1
  38. package/dist/components/Settings.js +6 -11
  39. package/dist/components/Settings.js.map +1 -1
  40. package/dist/components/StatusScreen.js +1 -1
  41. package/dist/components/StatusScreen.js.map +1 -1
  42. package/dist/components/WorkItemForm.js +6 -9
  43. package/dist/components/WorkItemForm.js.map +1 -1
  44. package/dist/components/WorkItemList.js +102 -51
  45. package/dist/components/WorkItemList.js.map +1 -1
  46. package/dist/index.js +20 -8
  47. package/dist/index.js.map +1 -1
  48. package/dist/storage/config.d.ts +61 -0
  49. package/dist/storage/config.js +309 -0
  50. package/dist/storage/config.js.map +1 -0
  51. package/dist/storage/db.d.ts +11 -0
  52. package/dist/storage/db.js +34 -0
  53. package/dist/storage/db.js.map +1 -0
  54. package/dist/storage/index.d.ts +73 -0
  55. package/dist/storage/index.js +966 -0
  56. package/dist/storage/index.js.map +1 -0
  57. package/dist/storage/mappers.d.ts +35 -0
  58. package/dist/storage/mappers.js +70 -0
  59. package/dist/storage/mappers.js.map +1 -0
  60. package/dist/storage/schema.d.ts +1844 -0
  61. package/dist/storage/schema.js +197 -0
  62. package/dist/storage/schema.js.map +1 -0
  63. package/dist/storage/syncQueue.d.ts +13 -0
  64. package/dist/storage/syncQueue.js +98 -0
  65. package/dist/storage/syncQueue.js.map +1 -0
  66. package/dist/storage/undo.d.ts +22 -0
  67. package/dist/storage/undo.js +129 -0
  68. package/dist/storage/undo.js.map +1 -0
  69. package/dist/stores/backendDataStore.d.ts +4 -1
  70. package/dist/stores/backendDataStore.js +61 -40
  71. package/dist/stores/backendDataStore.js.map +1 -1
  72. package/dist/stores/configStore.d.ts +3 -1
  73. package/dist/stores/configStore.js +25 -65
  74. package/dist/stores/configStore.js.map +1 -1
  75. package/dist/stores/filterStore.d.ts +2 -0
  76. package/dist/stores/filterStore.js +7 -2
  77. package/dist/stores/filterStore.js.map +1 -1
  78. package/dist/stores/uiStore.d.ts +0 -2
  79. package/dist/stores/uiStore.js.map +1 -1
  80. package/dist/stores/undoStore.d.ts +4 -0
  81. package/dist/stores/undoStore.js +32 -0
  82. package/dist/stores/undoStore.js.map +1 -1
  83. package/dist/sync/SyncManager.d.ts +3 -4
  84. package/dist/sync/SyncManager.js +78 -37
  85. package/dist/sync/SyncManager.js.map +1 -1
  86. package/dist/sync/types.d.ts +10 -1
  87. package/package.json +5 -1
  88. package/dist/backends/local/config.d.ts +0 -38
  89. package/dist/backends/local/config.js +0 -42
  90. package/dist/backends/local/config.js.map +0 -1
  91. package/dist/backends/local/index.d.ts +0 -45
  92. package/dist/backends/local/index.js +0 -291
  93. package/dist/backends/local/index.js.map +0 -1
  94. package/dist/sync/queue.d.ts +0 -12
  95. package/dist/sync/queue.js +0 -56
  96. package/dist/sync/queue.js.map +0 -1
@@ -0,0 +1,197 @@
1
+ import { sqliteTable, text, integer, index, primaryKey, } from 'drizzle-orm/sqlite-core';
2
+ // 1. Work Items
3
+ export const workItems = sqliteTable('work_items', {
4
+ id: text('id').primaryKey(),
5
+ title: text('title').notNull(),
6
+ type: text('type').notNull(),
7
+ status: text('status').notNull(),
8
+ iteration: text('iteration').notNull().default(''),
9
+ priority: text('priority').notNull().default(''),
10
+ assignee: text('assignee').notNull().default(''),
11
+ description: text('description').notNull().default(''),
12
+ parent: text('parent'),
13
+ created: text('created').notNull(),
14
+ updated: text('updated').notNull(),
15
+ deletedAt: text('deleted_at'),
16
+ }, (t) => [
17
+ index('idx_status').on(t.status),
18
+ index('idx_type').on(t.type),
19
+ index('idx_assignee').on(t.assignee),
20
+ index('idx_priority').on(t.priority),
21
+ index('idx_iteration').on(t.iteration),
22
+ index('idx_parent').on(t.parent),
23
+ ]);
24
+ // 2. Work Item Labels (junction)
25
+ export const workItemLabels = sqliteTable('work_item_labels', {
26
+ workItemId: text('work_item_id')
27
+ .notNull()
28
+ .references(() => workItems.id, { onDelete: 'cascade' }),
29
+ label: text('label').notNull(),
30
+ }, (t) => [
31
+ primaryKey({ columns: [t.workItemId, t.label] }),
32
+ index('idx_label').on(t.label),
33
+ ]);
34
+ // 3. Work Item Dependencies (junction)
35
+ export const workItemDeps = sqliteTable('work_item_deps', {
36
+ workItemId: text('work_item_id')
37
+ .notNull()
38
+ .references(() => workItems.id, { onDelete: 'cascade' }),
39
+ dependsOnId: text('depends_on_id')
40
+ .notNull()
41
+ .references(() => workItems.id, { onDelete: 'cascade' }),
42
+ }, (t) => [
43
+ primaryKey({ columns: [t.workItemId, t.dependsOnId] }),
44
+ index('idx_dep_target').on(t.dependsOnId),
45
+ ]);
46
+ // 4. Comments
47
+ export const comments = sqliteTable('comments', {
48
+ id: integer('id').primaryKey({ autoIncrement: true }),
49
+ workItemId: text('work_item_id')
50
+ .notNull()
51
+ .references(() => workItems.id, { onDelete: 'cascade' }),
52
+ author: text('author').notNull().default(''),
53
+ body: text('body').notNull(),
54
+ created: text('created').notNull(),
55
+ }, (t) => [index('idx_comment_item').on(t.workItemId)]);
56
+ // 5. Templates
57
+ export const templates = sqliteTable('templates', {
58
+ slug: text('slug').primaryKey(),
59
+ name: text('name').notNull(),
60
+ type: text('type').notNull().default(''),
61
+ status: text('status').notNull().default(''),
62
+ priority: text('priority').notNull().default(''),
63
+ assignee: text('assignee').notNull().default(''),
64
+ iteration: text('iteration').notNull().default(''),
65
+ parent: text('parent'),
66
+ description: text('description').notNull().default(''),
67
+ });
68
+ // 6. Template Labels (junction)
69
+ export const templateLabels = sqliteTable('template_labels', {
70
+ templateSlug: text('template_slug')
71
+ .notNull()
72
+ .references(() => templates.slug, { onDelete: 'cascade' }),
73
+ label: text('label').notNull(),
74
+ }, (t) => [primaryKey({ columns: [t.templateSlug, t.label] })]);
75
+ // 7. Template Dependencies (junction)
76
+ export const templateDeps = sqliteTable('template_deps', {
77
+ templateSlug: text('template_slug')
78
+ .notNull()
79
+ .references(() => templates.slug, { onDelete: 'cascade' }),
80
+ dependsOnId: text('depends_on_id').notNull(),
81
+ }, (t) => [primaryKey({ columns: [t.templateSlug, t.dependsOnId] })]);
82
+ // 8. Project Configuration (singleton, id=1)
83
+ export const projectConfig = sqliteTable('project_config', {
84
+ id: integer('id').primaryKey().default(1),
85
+ backend: text('backend').notNull().default('none'),
86
+ currentIteration: text('current_iteration').notNull().default(''),
87
+ nextId: integer('next_id').notNull().default(1),
88
+ branchMode: text('branch_mode').notNull().default('branch'),
89
+ branchCommand: text('branch_command').notNull().default(''),
90
+ copyToClipboard: integer('copy_to_clipboard', { mode: 'boolean' })
91
+ .notNull()
92
+ .default(true),
93
+ autoUpdate: integer('auto_update', { mode: 'boolean' })
94
+ .notNull()
95
+ .default(true),
96
+ defaultType: text('default_type').notNull().default('issue'),
97
+ showDetailPanel: integer('show_detail_panel', { mode: 'boolean' })
98
+ .notNull()
99
+ .default(false),
100
+ defaultView: text('default_view').notNull().default(''),
101
+ });
102
+ // 9. Statuses
103
+ export const statuses = sqliteTable('statuses', {
104
+ name: text('name').primaryKey(),
105
+ sortOrder: integer('sort_order').notNull().default(0),
106
+ });
107
+ // 10. Work Item Types
108
+ export const workItemTypes = sqliteTable('work_item_types', {
109
+ name: text('name').primaryKey(),
110
+ sortOrder: integer('sort_order').notNull().default(0),
111
+ });
112
+ // 11. Iterations
113
+ export const iterations = sqliteTable('iterations', {
114
+ name: text('name').primaryKey(),
115
+ sortOrder: integer('sort_order').notNull().default(0),
116
+ });
117
+ // 12. Jira Configuration (singleton, id=1)
118
+ export const jiraConfig = sqliteTable('jira_config', {
119
+ id: integer('id').primaryKey().default(1),
120
+ site: text('site').notNull().default(''),
121
+ project: text('project').notNull().default(''),
122
+ boardId: text('board_id').notNull().default(''),
123
+ });
124
+ // 13. Saved Views
125
+ export const savedViews = sqliteTable('saved_views', {
126
+ name: text('name').primaryKey(),
127
+ });
128
+ // 14. Saved View Filters
129
+ export const savedViewFilters = sqliteTable('saved_view_filters', {
130
+ viewName: text('view_name')
131
+ .notNull()
132
+ .references(() => savedViews.name, { onDelete: 'cascade' }),
133
+ field: text('field').notNull(),
134
+ value: text('value').notNull(),
135
+ }, (t) => [primaryKey({ columns: [t.viewName, t.field, t.value] })]);
136
+ // 15. Saved View Sort Entries
137
+ export const savedViewSortEntries = sqliteTable('saved_view_sort_entries', {
138
+ viewName: text('view_name')
139
+ .notNull()
140
+ .references(() => savedViews.name, { onDelete: 'cascade' }),
141
+ column: text('column').notNull(),
142
+ direction: text('direction').notNull(),
143
+ sortOrder: integer('sort_order').notNull(),
144
+ }, (t) => [primaryKey({ columns: [t.viewName, t.sortOrder] })]);
145
+ // 16. Sync Queue
146
+ export const syncQueue = sqliteTable('sync_queue', {
147
+ id: integer('id').primaryKey({ autoIncrement: true }),
148
+ action: text('action').notNull(),
149
+ itemId: text('item_id').notNull(),
150
+ timestamp: text('timestamp').notNull(),
151
+ commentData: text('comment_data'),
152
+ templateSlug: text('template_slug'),
153
+ }, (t) => [index('idx_queue_item').on(t.itemId, t.action)]);
154
+ // 17. Undo Stack
155
+ export const undoStack = sqliteTable('undo_stack', {
156
+ id: integer('id').primaryKey({ autoIncrement: true }),
157
+ action: text('action').notNull(),
158
+ itemId: text('item_id').notNull(),
159
+ createdAt: text('created_at').notNull(),
160
+ });
161
+ // 18. Undo Item Snapshot (1:1 with undo_stack)
162
+ export const undoItemSnapshot = sqliteTable('undo_item_snapshot', {
163
+ undoId: integer('undo_id')
164
+ .primaryKey()
165
+ .references(() => undoStack.id, { onDelete: 'cascade' }),
166
+ title: text('title').notNull(),
167
+ type: text('type').notNull(),
168
+ status: text('status').notNull(),
169
+ iteration: text('iteration').notNull(),
170
+ priority: text('priority').notNull(),
171
+ assignee: text('assignee').notNull(),
172
+ description: text('description').notNull(),
173
+ parent: text('parent'),
174
+ created: text('created').notNull(),
175
+ updated: text('updated').notNull(),
176
+ });
177
+ // 19. Undo Item Snapshot Labels (junction)
178
+ export const undoItemSnapshotLabels = sqliteTable('undo_item_snapshot_labels', {
179
+ undoId: integer('undo_id')
180
+ .notNull()
181
+ .references(() => undoStack.id, { onDelete: 'cascade' }),
182
+ label: text('label').notNull(),
183
+ }, (t) => [primaryKey({ columns: [t.undoId, t.label] })]);
184
+ // 20. Undo Item Snapshot Dependencies (junction)
185
+ export const undoItemSnapshotDeps = sqliteTable('undo_item_snapshot_deps', {
186
+ undoId: integer('undo_id')
187
+ .notNull()
188
+ .references(() => undoStack.id, { onDelete: 'cascade' }),
189
+ dependsOnId: text('depends_on_id').notNull(),
190
+ }, (t) => [primaryKey({ columns: [t.undoId, t.dependsOnId] })]);
191
+ // 21. File Sync State
192
+ export const fileSyncState = sqliteTable('file_sync_state', {
193
+ itemId: text('item_id').primaryKey(),
194
+ hash: text('hash').notNull(),
195
+ syncedAt: text('synced_at').notNull(),
196
+ });
197
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/storage/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { TicDatabase } from './db.js';
2
+ import type { QueueAction, QueueEntry, SyncQueueData, SyncQueueAdapter } from '../sync/types.js';
3
+ export declare class SyncQueue implements SyncQueueAdapter {
4
+ db: TicDatabase;
5
+ constructor(db: TicDatabase);
6
+ read(): SyncQueueData;
7
+ append(entry: QueueEntry): void;
8
+ remove(itemId: string, action: QueueAction): void;
9
+ removeByIds(itemIds: string[], action: QueueAction): void;
10
+ clear(): void;
11
+ renameItem(oldId: string, newId: string): void;
12
+ claimNext(): QueueEntry | null;
13
+ }
@@ -0,0 +1,98 @@
1
+ import { eq, and, inArray, asc } from 'drizzle-orm';
2
+ import * as s from './schema.js';
3
+ export class SyncQueue {
4
+ db;
5
+ constructor(db) {
6
+ this.db = db;
7
+ }
8
+ read() {
9
+ const rows = this.db
10
+ .select()
11
+ .from(s.syncQueue)
12
+ .orderBy(asc(s.syncQueue.id))
13
+ .all();
14
+ return {
15
+ pending: rows.map((row) => ({
16
+ action: row.action,
17
+ itemId: row.itemId,
18
+ timestamp: row.timestamp,
19
+ ...(row.commentData
20
+ ? {
21
+ commentData: JSON.parse(row.commentData),
22
+ }
23
+ : {}),
24
+ ...(row.templateSlug ? { templateSlug: row.templateSlug } : {}),
25
+ })),
26
+ };
27
+ }
28
+ append(entry) {
29
+ // Deduplicate: delete existing with same itemId + action, then insert
30
+ this.db
31
+ .delete(s.syncQueue)
32
+ .where(and(eq(s.syncQueue.itemId, entry.itemId), eq(s.syncQueue.action, entry.action)))
33
+ .run();
34
+ this.db
35
+ .insert(s.syncQueue)
36
+ .values({
37
+ action: entry.action,
38
+ itemId: entry.itemId,
39
+ timestamp: entry.timestamp,
40
+ commentData: entry.commentData
41
+ ? JSON.stringify(entry.commentData)
42
+ : null,
43
+ templateSlug: entry.templateSlug ?? null,
44
+ })
45
+ .run();
46
+ }
47
+ remove(itemId, action) {
48
+ this.db
49
+ .delete(s.syncQueue)
50
+ .where(and(eq(s.syncQueue.itemId, itemId), eq(s.syncQueue.action, action)))
51
+ .run();
52
+ }
53
+ removeByIds(itemIds, action) {
54
+ if (itemIds.length === 0)
55
+ return;
56
+ this.db
57
+ .delete(s.syncQueue)
58
+ .where(and(inArray(s.syncQueue.itemId, itemIds), eq(s.syncQueue.action, action)))
59
+ .run();
60
+ }
61
+ clear() {
62
+ this.db.delete(s.syncQueue).run();
63
+ }
64
+ renameItem(oldId, newId) {
65
+ this.db
66
+ .update(s.syncQueue)
67
+ .set({ itemId: newId })
68
+ .where(eq(s.syncQueue.itemId, oldId))
69
+ .run();
70
+ }
71
+ claimNext() {
72
+ // Atomic SELECT + DELETE in a transaction
73
+ return this.db.transaction((tx) => {
74
+ const rows = tx
75
+ .select()
76
+ .from(s.syncQueue)
77
+ .orderBy(asc(s.syncQueue.id))
78
+ .limit(1)
79
+ .all();
80
+ if (rows.length === 0)
81
+ return null;
82
+ const row = rows[0];
83
+ tx.delete(s.syncQueue).where(eq(s.syncQueue.id, row.id)).run();
84
+ return {
85
+ action: row.action,
86
+ itemId: row.itemId,
87
+ timestamp: row.timestamp,
88
+ ...(row.commentData
89
+ ? {
90
+ commentData: JSON.parse(row.commentData),
91
+ }
92
+ : {}),
93
+ ...(row.templateSlug ? { templateSlug: row.templateSlug } : {}),
94
+ };
95
+ });
96
+ }
97
+ }
98
+ //# sourceMappingURL=syncQueue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syncQueue.js","sourceRoot":"","sources":["../../src/storage/syncQueue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { TicDatabase } from './db.js';
2
+ import type { UndoEntry } from '../stores/undoStore.js';
3
+ /**
4
+ * Push an undo entry onto the persistent stack.
5
+ * If the stack exceeds MAX_DEPTH, the oldest entry is evicted and returned.
6
+ */
7
+ export declare function pushUndoEntry(db: TicDatabase, entry: UndoEntry): UndoEntry | undefined;
8
+ /**
9
+ * Pop the most recent undo entry from the stack.
10
+ * Returns undefined if the stack is empty.
11
+ */
12
+ export declare function popUndoEntry(db: TicDatabase): UndoEntry | undefined;
13
+ /**
14
+ * Read the entire undo stack without modifying it.
15
+ * Returns entries ordered most-recent first.
16
+ */
17
+ export declare function readUndoStack(db: TicDatabase): UndoEntry[];
18
+ /**
19
+ * Clear the undo stack, returning all entries that were removed.
20
+ * Returns entries ordered most-recent first.
21
+ */
22
+ export declare function clearUndoStack(db: TicDatabase): UndoEntry[];
@@ -0,0 +1,129 @@
1
+ import { eq, desc } from 'drizzle-orm';
2
+ import * as s from './schema.js';
3
+ const MAX_DEPTH = 5;
4
+ function serializeSnapshots(items) {
5
+ return items.map((snap) => ({
6
+ id: snap.id,
7
+ title: snap.title,
8
+ type: snap.type,
9
+ status: snap.status,
10
+ description: snap.description,
11
+ iteration: snap.iteration,
12
+ priority: snap.priority,
13
+ assignee: snap.assignee,
14
+ labels: snap.labels,
15
+ parent: snap.parent,
16
+ dependsOn: snap.dependsOn,
17
+ created: snap.created,
18
+ updated: snap.updated,
19
+ // comments are intentionally omitted from undo snapshots
20
+ }));
21
+ }
22
+ function deserializeSnapshots(snapshots) {
23
+ return snapshots.map((snap) => ({
24
+ ...snap,
25
+ priority: snap.priority,
26
+ comments: [],
27
+ }));
28
+ }
29
+ function reconstructEntry(row) {
30
+ const meta = JSON.parse(row.itemId);
31
+ return {
32
+ type: row.action,
33
+ label: meta.label,
34
+ syncItemIds: meta.syncItemIds,
35
+ syncAction: meta.syncAction,
36
+ createdIds: meta.createdIds,
37
+ itemSnapshots: deserializeSnapshots(meta.itemSnapshots),
38
+ };
39
+ }
40
+ /**
41
+ * Push an undo entry onto the persistent stack.
42
+ * If the stack exceeds MAX_DEPTH, the oldest entry is evicted and returned.
43
+ */
44
+ export function pushUndoEntry(db, entry) {
45
+ const metadata = {
46
+ label: entry.label,
47
+ syncItemIds: entry.syncItemIds,
48
+ syncAction: entry.syncAction,
49
+ createdIds: entry.createdIds,
50
+ itemSnapshots: serializeSnapshots(entry.itemSnapshots),
51
+ };
52
+ let evicted;
53
+ db.transaction((tx) => {
54
+ tx.insert(s.undoStack)
55
+ .values({
56
+ action: entry.type,
57
+ itemId: JSON.stringify(metadata),
58
+ createdAt: new Date().toISOString(),
59
+ })
60
+ .run();
61
+ // Read all rows ordered newest-first to check depth
62
+ const rows = tx
63
+ .select()
64
+ .from(s.undoStack)
65
+ .orderBy(desc(s.undoStack.id))
66
+ .all();
67
+ if (rows.length > MAX_DEPTH) {
68
+ const toEvict = rows.slice(MAX_DEPTH);
69
+ // Reconstruct the oldest evicted entry before deleting
70
+ evicted = reconstructEntry(toEvict[0]);
71
+ for (const row of toEvict) {
72
+ tx.delete(s.undoStack).where(eq(s.undoStack.id, row.id)).run();
73
+ }
74
+ }
75
+ });
76
+ return evicted;
77
+ }
78
+ /**
79
+ * Pop the most recent undo entry from the stack.
80
+ * Returns undefined if the stack is empty.
81
+ */
82
+ export function popUndoEntry(db) {
83
+ let result;
84
+ db.transaction((tx) => {
85
+ const row = tx
86
+ .select()
87
+ .from(s.undoStack)
88
+ .orderBy(desc(s.undoStack.id))
89
+ .limit(1)
90
+ .get();
91
+ if (!row)
92
+ return;
93
+ result = reconstructEntry(row);
94
+ tx.delete(s.undoStack).where(eq(s.undoStack.id, row.id)).run();
95
+ });
96
+ return result;
97
+ }
98
+ /**
99
+ * Read the entire undo stack without modifying it.
100
+ * Returns entries ordered most-recent first.
101
+ */
102
+ export function readUndoStack(db) {
103
+ const rows = db
104
+ .select()
105
+ .from(s.undoStack)
106
+ .orderBy(desc(s.undoStack.id))
107
+ .all();
108
+ return rows.map(reconstructEntry);
109
+ }
110
+ /**
111
+ * Clear the undo stack, returning all entries that were removed.
112
+ * Returns entries ordered most-recent first.
113
+ */
114
+ export function clearUndoStack(db) {
115
+ let entries = [];
116
+ db.transaction((tx) => {
117
+ const rows = tx
118
+ .select()
119
+ .from(s.undoStack)
120
+ .orderBy(desc(s.undoStack.id))
121
+ .all();
122
+ entries = rows.map(reconstructEntry);
123
+ if (rows.length > 0) {
124
+ tx.delete(s.undoStack).run();
125
+ }
126
+ });
127
+ return entries;
128
+ }
129
+ //# sourceMappingURL=undo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"undo.js","sourceRoot":"","sources":["../../src/storage/undo.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import type { Backend, BackendCapabilities } from '../backends/types.js';
2
2
  import type { WorkItem } from '../types.js';
3
- import type { SyncStatus } from '../sync/types.js';
3
+ import type { SyncQueueAdapter, SyncStatus } from '../sync/types.js';
4
4
  import type { SyncManager } from '../sync/SyncManager.js';
5
5
  export declare const defaultCapabilities: BackendCapabilities;
6
6
  export interface BackendDataStoreState {
@@ -18,8 +18,11 @@ export interface BackendDataStoreState {
18
18
  syncStatus: SyncStatus | null;
19
19
  backend: Backend | null;
20
20
  syncManager: SyncManager | null;
21
+ queue: SyncQueueAdapter | null;
21
22
  init(cwd: string): void;
22
23
  refresh(): Promise<void>;
24
+ reloadItem(id: string): Promise<void>;
25
+ removeItem(id: string): void;
23
26
  setSyncStatus(status: SyncStatus): void;
24
27
  destroy(): void;
25
28
  }
@@ -1,6 +1,7 @@
1
1
  import { createStore } from 'zustand/vanilla';
2
2
  import { useStore } from 'zustand';
3
3
  import { configStore } from './configStore.js';
4
+ import { undoStore } from './undoStore.js';
4
5
  export const defaultCapabilities = {
5
6
  relationships: false,
6
7
  customTypes: false,
@@ -31,45 +32,25 @@ export const defaultCapabilities = {
31
32
  let currentBackend = null;
32
33
  let initGeneration = 0;
33
34
  async function createBackendAndSync(cwd) {
34
- const { LocalBackend } = await import('../backends/local/index.js');
35
- const backendType = configStore.getState().config.backend ?? 'local';
36
- const local = await LocalBackend.create(cwd, {
37
- tempIds: backendType !== 'local',
38
- });
39
- if (backendType === 'local') {
40
- return { backend: local, syncManager: null };
35
+ const { Storage } = await import('../storage/index.js');
36
+ const primary = Storage.create(cwd);
37
+ // Set up stores with SQLite backing
38
+ configStore.getState().setDatabase(primary.getDatabase());
39
+ undoStore.getState().setDatabase(primary.getDatabase());
40
+ // Re-read config from DB (may have been loaded with defaults before DB was available)
41
+ await configStore.getState().init(cwd);
42
+ const config = configStore.getState().config;
43
+ const { createRemoteBackend } = await import('../backends/factory.js');
44
+ const remote = await createRemoteBackend(cwd, config.backend ?? 'none');
45
+ let syncManager = null;
46
+ let queue = null;
47
+ if (remote) {
48
+ const { SyncManager: SM } = await import('../sync/SyncManager.js');
49
+ const { SyncQueue } = await import('../storage/syncQueue.js');
50
+ queue = new SyncQueue(primary.getDatabase());
51
+ syncManager = new SM(primary, remote, queue);
41
52
  }
42
- // Dynamic import of remote backend this is the expensive part
43
- let remote;
44
- switch (backendType) {
45
- case 'github': {
46
- const { GitHubBackend } = await import('../backends/github/index.js');
47
- remote = new GitHubBackend(cwd);
48
- break;
49
- }
50
- case 'gitlab': {
51
- const { GitLabBackend } = await import('../backends/gitlab/index.js');
52
- remote = new GitLabBackend(cwd);
53
- break;
54
- }
55
- case 'azure': {
56
- const { AzureDevOpsBackend } = await import('../backends/ado/index.js');
57
- remote = new AzureDevOpsBackend(cwd);
58
- break;
59
- }
60
- case 'jira': {
61
- const { JiraBackend } = await import('../backends/jira/index.js');
62
- remote = await JiraBackend.create(cwd);
63
- break;
64
- }
65
- default:
66
- throw new Error(`Unknown backend "${backendType}"`);
67
- }
68
- const { SyncQueueStore } = await import('../sync/queue.js');
69
- const { SyncManager } = await import('../sync/SyncManager.js');
70
- const queueStore = new SyncQueueStore(cwd);
71
- const syncManager = new SyncManager(local, remote, queueStore);
72
- return { backend: local, syncManager };
53
+ return { backend: primary, syncManager, queue };
73
54
  }
74
55
  export const backendDataStore = createStore((set, get) => ({
75
56
  items: [],
@@ -86,16 +67,17 @@ export const backendDataStore = createStore((set, get) => ({
86
67
  syncStatus: null,
87
68
  backend: null,
88
69
  syncManager: null,
70
+ queue: null,
89
71
  init(cwd) {
90
72
  get().destroy();
91
73
  const generation = ++initGeneration;
92
74
  set({ loading: true });
93
75
  void createBackendAndSync(cwd)
94
- .then(({ backend, syncManager }) => {
76
+ .then(({ backend, syncManager, queue }) => {
95
77
  if (generation !== initGeneration)
96
78
  return;
97
79
  currentBackend = backend;
98
- set({ backend, syncManager });
80
+ set({ backend, syncManager, queue });
99
81
  if (syncManager) {
100
82
  syncManager.onStatusChange((status) => {
101
83
  if (generation !== initGeneration)
@@ -154,11 +136,49 @@ export const backendDataStore = createStore((set, get) => ({
154
136
  set({ error: e instanceof Error ? e.message : String(e) });
155
137
  }
156
138
  },
139
+ async reloadItem(id) {
140
+ if (!currentBackend)
141
+ return;
142
+ try {
143
+ const item = await currentBackend.getWorkItem(id);
144
+ set((state) => {
145
+ const idx = state.items.findIndex((i) => i.id === id);
146
+ const items = [...state.items];
147
+ if (idx >= 0) {
148
+ items[idx] = item;
149
+ }
150
+ else {
151
+ items.push(item);
152
+ }
153
+ return { items };
154
+ });
155
+ // Refresh dynamic lists that may have changed
156
+ const [assignees, labels] = await Promise.all([
157
+ currentBackend.getAssignees().catch(() => []),
158
+ currentBackend.getLabels().catch(() => []),
159
+ ]);
160
+ set({ assignees, labels });
161
+ }
162
+ catch (e) {
163
+ set({ error: e instanceof Error ? e.message : String(e) });
164
+ }
165
+ },
166
+ removeItem(id) {
167
+ set((state) => ({
168
+ items: state.items.filter((i) => i.id !== id),
169
+ }));
170
+ },
157
171
  setSyncStatus(status) {
158
172
  set({ syncStatus: status });
159
173
  },
160
174
  destroy() {
161
175
  ++initGeneration;
176
+ // Null out store DB references before closing the connection
177
+ undoStore.getState().destroy();
178
+ configStore.getState().setDatabase(null);
179
+ if (currentBackend && 'destroy' in currentBackend) {
180
+ currentBackend.destroy();
181
+ }
162
182
  currentBackend = null;
163
183
  set({
164
184
  items: [],
@@ -175,6 +195,7 @@ export const backendDataStore = createStore((set, get) => ({
175
195
  syncStatus: null,
176
196
  backend: null,
177
197
  syncManager: null,
198
+ queue: null,
178
199
  });
179
200
  },
180
201
  }));