@starlove/orbit 1.0.5 → 1.0.6
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/.dockerignore +0 -0
- package/Dockerfile +0 -0
- package/LICENSE +0 -0
- package/README.md +8 -0
- package/SKILL-ORBIT.md +0 -0
- package/build.mjs +0 -0
- package/dist/full/apple-touch-icon.png +0 -0
- package/dist/full/cursors/live_arrow.cur +0 -0
- package/dist/full/cursors/live_link.cur +0 -0
- package/dist/full/cursors/live_move.cur +0 -0
- package/dist/full/cursors/live_select.cur +0 -0
- package/dist/full/cursors/live_unavail.cur +0 -0
- package/dist/full/favicon-32x32.png +0 -0
- package/dist/full/index.html +1 -1
- package/dist/full/js/{app.7e25f59b.js → app.aedc1bfe.js} +1 -1
- package/dist/full/js/app.aedc1bfe.js.map +7 -0
- package/dist/full/orbits-favicon.png +0 -0
- package/dist/full/orbits-logo-bg.png +0 -0
- package/dist/full/orbits-logo.png +0 -0
- package/dist/full/styles.c2e3d20a.css +0 -0
- package/dist/full/version.json +2 -2
- package/docs/AGENT_PROTOCOL.md +0 -0
- package/docs/MCP_SETUP.md +0 -0
- package/docs/SS-Orbit-Dark.png +0 -0
- package/docs/SS-Orbit-Light.png +0 -0
- package/docs/mcp-client.example.json +0 -0
- package/package.json +1 -1
- package/public/apple-touch-icon.png +0 -0
- package/public/cursors/live_arrow.cur +0 -0
- package/public/cursors/live_link.cur +0 -0
- package/public/cursors/live_move.cur +0 -0
- package/public/cursors/live_select.cur +0 -0
- package/public/cursors/live_unavail.cur +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/index.html +0 -0
- package/public/js/api.js +0 -0
- package/public/js/app.js +0 -0
- package/public/js/board-menu.js +0 -0
- package/public/js/config.js +0 -0
- package/public/js/create-card.js +0 -0
- package/public/js/dom.js +0 -0
- package/public/js/drawer.js +0 -0
- package/public/js/format.js +0 -0
- package/public/js/kanban.js +8 -0
- package/public/js/local-backend.js +29 -2
- package/public/js/main.js +0 -0
- package/public/js/modal.js +0 -0
- package/public/js/router.js +0 -0
- package/public/js/search.js +0 -0
- package/public/js/settings-tabs.js +0 -0
- package/public/js/settings.js +0 -0
- package/public/js/sse.js +0 -0
- package/public/js/starscape.js +0 -0
- package/public/js/state.js +0 -0
- package/public/js/ticket-detail.js +0 -0
- package/public/js/toast.js +0 -0
- package/public/js/transport.js +0 -0
- package/public/js/unread.js +0 -0
- package/public/orbits-favicon.png +0 -0
- package/public/orbits-logo-bg.png +0 -0
- package/public/orbits-logo.png +0 -0
- package/public/styles.css +0 -0
- package/src/cli/orbit.js +0 -0
- package/src/core/agent.js +10 -4
- package/src/core/agents-md.js +0 -0
- package/src/core/auth.js +0 -0
- package/src/core/backups.js +0 -0
- package/src/core/board.js +0 -0
- package/src/core/boards.js +0 -0
- package/src/core/bootstrap.js +1 -1
- package/src/core/db.js +0 -0
- package/src/core/delete-board.js +0 -0
- package/src/core/events.js +0 -0
- package/src/core/http.js +0 -0
- package/src/core/paths.js +0 -0
- package/src/core/provision-repo-board.js +0 -0
- package/src/core/queries.js +0 -0
- package/src/core/registry.js +0 -0
- package/src/core/relations.js +0 -0
- package/src/core/router.js +0 -0
- package/src/core/search.js +7 -2
- package/src/core/seed.js +0 -0
- package/src/core/snapshots.js +0 -0
- package/src/core/states.js +0 -0
- package/src/core/system-picker.js +0 -0
- package/src/core/tickets.js +28 -3
- package/src/core/util.js +0 -0
- package/src/mcp-server.js +0 -0
- package/src/server.js +0 -0
- package/dist/full/js/app.7e25f59b.js.map +0 -7
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/dist/full/version.json
CHANGED
package/docs/AGENT_PROTOCOL.md
CHANGED
|
File without changes
|
package/docs/MCP_SETUP.md
CHANGED
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/public/favicon-32x32.png
CHANGED
|
File without changes
|
package/public/index.html
CHANGED
|
File without changes
|
package/public/js/api.js
CHANGED
|
File without changes
|
package/public/js/app.js
CHANGED
|
File without changes
|
package/public/js/board-menu.js
CHANGED
|
File without changes
|
package/public/js/config.js
CHANGED
|
File without changes
|
package/public/js/create-card.js
CHANGED
|
File without changes
|
package/public/js/dom.js
CHANGED
|
File without changes
|
package/public/js/drawer.js
CHANGED
|
File without changes
|
package/public/js/format.js
CHANGED
|
File without changes
|
package/public/js/kanban.js
CHANGED
|
@@ -257,6 +257,14 @@ function renderColumnBody(columnTickets, allTickets) {
|
|
|
257
257
|
continue;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
// Defer children whose epic is in this column — they'll render under the
|
|
261
|
+
// epic when the loop reaches it. Without this skip, a child iterated before
|
|
262
|
+
// its epic would render standalone here, then again inside epic-children
|
|
263
|
+
// (rendered.add happens at the epic, which may come later in updated_at order).
|
|
264
|
+
if (t.parent_ticket_id && epicIdsInColumn.has(t.parent_ticket_id)) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
|
|
260
268
|
if (t.parent_ticket_id && epicById.has(t.parent_ticket_id) && !epicIdsInColumn.has(t.parent_ticket_id)) {
|
|
261
269
|
const epic = epicById.get(t.parent_ticket_id);
|
|
262
270
|
const siblings = childrenByEpicId.get(t.parent_ticket_id) || [];
|
|
@@ -307,7 +307,11 @@ function decorateTickets(snapshot, includeArchived = false) {
|
|
|
307
307
|
const labelById = Object.fromEntries(labels.map((l) => [l.id, l]));
|
|
308
308
|
const stateById = Object.fromEntries(states.map((s) => [s.id, s]));
|
|
309
309
|
const visible = tickets.filter((t) => includeArchived || !t.archived_at);
|
|
310
|
-
const sorted = [...visible].sort((a, b) =>
|
|
310
|
+
const sorted = [...visible].sort((a, b) => {
|
|
311
|
+
const byTime = String(b.updated_at).localeCompare(String(a.updated_at));
|
|
312
|
+
if (byTime !== 0) return byTime;
|
|
313
|
+
return String(a.id).localeCompare(String(b.id));
|
|
314
|
+
});
|
|
311
315
|
return sorted.map((ticket) => {
|
|
312
316
|
const stateRow = stateById[ticket.state_id];
|
|
313
317
|
const tLabels = ticketLabels
|
|
@@ -575,6 +579,17 @@ async function handleStateReorder(boardId, body) {
|
|
|
575
579
|
});
|
|
576
580
|
}
|
|
577
581
|
|
|
582
|
+
// Mirrors src/core/tickets.js#bumpTicketUpdatedAt — rolls child activity up to
|
|
583
|
+
// the parent epic so the column sort (updated_at DESC) keeps the epic group
|
|
584
|
+
// near recent child activity even when only its children changed.
|
|
585
|
+
async function bumpTicketUpdatedAt(stores, ticketId, time) {
|
|
586
|
+
if (!ticketId) return;
|
|
587
|
+
const parent = await reqPromise(stores.tickets.get(ticketId));
|
|
588
|
+
if (!parent) return;
|
|
589
|
+
parent.updated_at = time;
|
|
590
|
+
stores.tickets.put(parent);
|
|
591
|
+
}
|
|
592
|
+
|
|
578
593
|
async function handleTicketCreate(body) {
|
|
579
594
|
const db = await openDb();
|
|
580
595
|
return putAndEvent(db, async (stores) => {
|
|
@@ -610,6 +625,7 @@ async function handleTicketCreate(body) {
|
|
|
610
625
|
stores.ticket_labels.put({ key: tlKey(ticket.id, label.id), ticket_id: ticket.id, label_id: label.id });
|
|
611
626
|
}
|
|
612
627
|
recordEvent(stores, "ticket_created", ticket.id, { title: ticket.title, type: ticket.type });
|
|
628
|
+
await bumpTicketUpdatedAt(stores, ticket.parent_ticket_id, ticket.updated_at);
|
|
613
629
|
return ticket;
|
|
614
630
|
});
|
|
615
631
|
}
|
|
@@ -620,6 +636,7 @@ async function handleTicketPatch(ticketId, body) {
|
|
|
620
636
|
const ticket = await reqPromise(stores.tickets.get(ticketId));
|
|
621
637
|
if (!ticket) throw err(404, "ticket_not_found");
|
|
622
638
|
const oldStateId = ticket.state_id;
|
|
639
|
+
const oldParentId = ticket.parent_ticket_id;
|
|
623
640
|
const SIMPLE = [
|
|
624
641
|
"title",
|
|
625
642
|
"description",
|
|
@@ -663,6 +680,10 @@ async function handleTicketPatch(ticketId, body) {
|
|
|
663
680
|
} else {
|
|
664
681
|
recordEvent(stores, "ticket_updated", ticketId, { fields: Object.keys(body) });
|
|
665
682
|
}
|
|
683
|
+
await bumpTicketUpdatedAt(stores, oldParentId, ticket.updated_at);
|
|
684
|
+
if (ticket.parent_ticket_id && ticket.parent_ticket_id !== oldParentId) {
|
|
685
|
+
await bumpTicketUpdatedAt(stores, ticket.parent_ticket_id, ticket.updated_at);
|
|
686
|
+
}
|
|
666
687
|
return ticket;
|
|
667
688
|
});
|
|
668
689
|
}
|
|
@@ -678,6 +699,7 @@ async function handleTicketDelete(ticketId) {
|
|
|
678
699
|
const cmts = await getAll(stores.comments, ticketId, "ticket_id");
|
|
679
700
|
for (const c of cmts) stores.comments.delete(c.id);
|
|
680
701
|
recordEvent(stores, "ticket_deleted", ticketId, {});
|
|
702
|
+
await bumpTicketUpdatedAt(stores, ticket.parent_ticket_id, nowIso());
|
|
681
703
|
return { ok: true };
|
|
682
704
|
});
|
|
683
705
|
}
|
|
@@ -691,6 +713,7 @@ async function handleTicketArchive(ticketId, archive) {
|
|
|
691
713
|
ticket.updated_at = nowIso();
|
|
692
714
|
stores.tickets.put(ticket);
|
|
693
715
|
recordEvent(stores, archive ? "ticket_archived" : "ticket_restored", ticketId, {});
|
|
716
|
+
await bumpTicketUpdatedAt(stores, ticket.parent_ticket_id, ticket.updated_at);
|
|
694
717
|
return ticket;
|
|
695
718
|
});
|
|
696
719
|
}
|
|
@@ -785,16 +808,20 @@ async function handleCommentCreate(ticketId, body) {
|
|
|
785
808
|
return putAndEvent(db, async (stores) => {
|
|
786
809
|
const ticket = await reqPromise(stores.tickets.get(ticketId));
|
|
787
810
|
if (!ticket) throw err(404, "ticket_not_found");
|
|
811
|
+
const time = nowIso();
|
|
788
812
|
const comment = {
|
|
789
813
|
id: uid(),
|
|
790
814
|
ticket_id: ticketId,
|
|
791
815
|
author: "you",
|
|
792
816
|
kind: body.kind || "note",
|
|
793
817
|
body: body.body || "",
|
|
794
|
-
created_at:
|
|
818
|
+
created_at: time
|
|
795
819
|
};
|
|
796
820
|
stores.comments.put(comment);
|
|
821
|
+
ticket.updated_at = time;
|
|
822
|
+
stores.tickets.put(ticket);
|
|
797
823
|
recordEvent(stores, "comment_created", ticketId, { kind: comment.kind });
|
|
824
|
+
await bumpTicketUpdatedAt(stores, ticket.parent_ticket_id, time);
|
|
798
825
|
return comment;
|
|
799
826
|
});
|
|
800
827
|
}
|
package/public/js/main.js
CHANGED
|
File without changes
|
package/public/js/modal.js
CHANGED
|
File without changes
|
package/public/js/router.js
CHANGED
|
File without changes
|
package/public/js/search.js
CHANGED
|
File without changes
|
|
File without changes
|
package/public/js/settings.js
CHANGED
|
File without changes
|
package/public/js/sse.js
CHANGED
|
File without changes
|
package/public/js/starscape.js
CHANGED
|
File without changes
|
package/public/js/state.js
CHANGED
|
File without changes
|
|
File without changes
|
package/public/js/toast.js
CHANGED
|
File without changes
|
package/public/js/transport.js
CHANGED
|
File without changes
|
package/public/js/unread.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/public/orbits-logo.png
CHANGED
|
File without changes
|
package/public/styles.css
CHANGED
|
File without changes
|
package/src/cli/orbit.js
CHANGED
|
File without changes
|
package/src/core/agent.js
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
requiredString
|
|
25
25
|
} from "./util.js";
|
|
26
26
|
import { boardManual } from "./boards.js";
|
|
27
|
-
import { reindexTicket } from "./tickets.js";
|
|
27
|
+
import { bumpTicketUpdatedAt, reindexTicket } from "./tickets.js";
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Claim the next AI-ready ticket on the resolved board. The router/MCP layer
|
|
@@ -71,14 +71,16 @@ export function claimNext(body, ctx) {
|
|
|
71
71
|
if (!inProgress) throw httpError(400, "missing_in_progress_state");
|
|
72
72
|
|
|
73
73
|
tx(db, () => {
|
|
74
|
+
const time = now();
|
|
74
75
|
db.prepare("UPDATE tickets SET state_id = ?, updated_at = ? WHERE id = ?").run(
|
|
75
76
|
inProgress.id,
|
|
76
|
-
|
|
77
|
+
time,
|
|
77
78
|
candidate.id
|
|
78
79
|
);
|
|
79
80
|
addComment(db, candidate.id, actor.name, "agent_note", "Agent claimed this ticket and is starting work.");
|
|
80
81
|
recordEvent(db, candidate.board_id, "agent_claimed", candidate.id, actor.name, { agent_id: actor.id });
|
|
81
82
|
reindexTicket(db, candidate.id);
|
|
83
|
+
bumpTicketUpdatedAt(db, candidate.parent_ticket_id, time);
|
|
82
84
|
});
|
|
83
85
|
|
|
84
86
|
return {
|
|
@@ -157,7 +159,8 @@ export function checkpointTicket(body, ctx) {
|
|
|
157
159
|
if (!review) throw httpError(400, "missing_review_state");
|
|
158
160
|
const message = requiredString(body.message, "message");
|
|
159
161
|
tx(db, () => {
|
|
160
|
-
|
|
162
|
+
const time = now();
|
|
163
|
+
db.prepare("UPDATE tickets SET state_id = ?, updated_at = ? WHERE id = ?").run(review.id, time, ticket.id);
|
|
161
164
|
addComment(db, ticket.id, actor.name, "checkpoint", message);
|
|
162
165
|
recordEvent(db, ticket.board_id, "checkpoint_requested", ticket.id, actor.name, {
|
|
163
166
|
from: ticket.state_name,
|
|
@@ -167,6 +170,7 @@ export function checkpointTicket(body, ctx) {
|
|
|
167
170
|
actor_id: actor.id
|
|
168
171
|
});
|
|
169
172
|
reindexTicket(db, ticket.id);
|
|
173
|
+
bumpTicketUpdatedAt(db, ticket.parent_ticket_id, time);
|
|
170
174
|
});
|
|
171
175
|
return getContextPack(ticket.id, ctx, 1);
|
|
172
176
|
}
|
|
@@ -190,10 +194,11 @@ export function completeTicket(body, ctx) {
|
|
|
190
194
|
if (body.pr_url) lines.push(`PR: ${body.pr_url}`);
|
|
191
195
|
const updates = body.updates ? appendFieldNote(ticket.implementation_updates, body.updates, actor.name) : ticket.implementation_updates;
|
|
192
196
|
tx(db, () => {
|
|
197
|
+
const time = now();
|
|
193
198
|
addComment(db, ticket.id, actor.name, "completion", lines.join("\n\n"));
|
|
194
199
|
db.prepare(
|
|
195
200
|
"UPDATE tickets SET state_id = ?, implementation_summary = ?, implementation_updates = ?, updated_at = ? WHERE id = ?"
|
|
196
|
-
).run(nextState.id, summary, updates,
|
|
201
|
+
).run(nextState.id, summary, updates, time, ticket.id);
|
|
197
202
|
recordEvent(db, ticket.board_id, "agent_completed", ticket.id, actor.name, {
|
|
198
203
|
from: ticket.state_name,
|
|
199
204
|
next_state: nextState.name,
|
|
@@ -203,6 +208,7 @@ export function completeTicket(body, ctx) {
|
|
|
203
208
|
actor_id: actor.id
|
|
204
209
|
});
|
|
205
210
|
reindexTicket(db, ticket.id);
|
|
211
|
+
bumpTicketUpdatedAt(db, ticket.parent_ticket_id, time);
|
|
206
212
|
});
|
|
207
213
|
return getContextPack(ticket.id, ctx, 1);
|
|
208
214
|
}
|
package/src/core/agents-md.js
CHANGED
|
File without changes
|
package/src/core/auth.js
CHANGED
|
File without changes
|
package/src/core/backups.js
CHANGED
|
File without changes
|
package/src/core/board.js
CHANGED
|
File without changes
|
package/src/core/boards.js
CHANGED
|
File without changes
|
package/src/core/bootstrap.js
CHANGED
|
@@ -105,7 +105,7 @@ export function getBootstrap(actor, requestedBoardId = null) {
|
|
|
105
105
|
JOIN states s ON s.id = t.state_id
|
|
106
106
|
JOIN boards b ON b.id = t.board_id
|
|
107
107
|
WHERE t.archived_at IS NULL
|
|
108
|
-
ORDER BY t.updated_at DESC`
|
|
108
|
+
ORDER BY t.updated_at DESC, t.id ASC`
|
|
109
109
|
)
|
|
110
110
|
.all()
|
|
111
111
|
.filter((ticket) => boardIds.has(ticket.board_id));
|
package/src/core/db.js
CHANGED
|
File without changes
|
package/src/core/delete-board.js
CHANGED
|
File without changes
|
package/src/core/events.js
CHANGED
|
File without changes
|
package/src/core/http.js
CHANGED
|
File without changes
|
package/src/core/paths.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/core/queries.js
CHANGED
|
File without changes
|
package/src/core/registry.js
CHANGED
|
File without changes
|
package/src/core/relations.js
CHANGED
|
File without changes
|
package/src/core/router.js
CHANGED
|
File without changes
|
package/src/core/search.js
CHANGED
|
@@ -44,13 +44,18 @@ export function searchTickets(args, ctx) {
|
|
|
44
44
|
WHERE t.archived_at IS NULL
|
|
45
45
|
AND (t.title LIKE ? OR t.description LIKE ? OR t.ai_plan LIKE ?
|
|
46
46
|
OR t.implementation_summary LIKE ? OR t.implementation_updates LIKE ? OR c.body LIKE ?)
|
|
47
|
-
ORDER BY t.updated_at DESC
|
|
47
|
+
ORDER BY t.updated_at DESC, t.id ASC
|
|
48
48
|
LIMIT ?`
|
|
49
49
|
)
|
|
50
50
|
.all(like, like, like, like, like, like, limit);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
rows.sort((a, b) =>
|
|
53
|
+
rows.sort((a, b) => {
|
|
54
|
+
if (a.rank !== b.rank) return a.rank - b.rank;
|
|
55
|
+
const byTime = String(b.updated_at).localeCompare(String(a.updated_at));
|
|
56
|
+
if (byTime !== 0) return byTime;
|
|
57
|
+
return String(a.id).localeCompare(String(b.id));
|
|
58
|
+
});
|
|
54
59
|
|
|
55
60
|
const seen = new Set();
|
|
56
61
|
const deduped = [];
|
package/src/core/seed.js
CHANGED
|
File without changes
|
package/src/core/snapshots.js
CHANGED
|
File without changes
|
package/src/core/states.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/core/tickets.js
CHANGED
|
@@ -17,6 +17,17 @@ import {
|
|
|
17
17
|
requiredString
|
|
18
18
|
} from "./util.js";
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Bump a ticket's `updated_at` without recording an event or touching other
|
|
22
|
+
* fields. Used to roll child activity up to the parent epic so the column sort
|
|
23
|
+
* (ORDER BY updated_at DESC) keeps the epic group near recent activity even
|
|
24
|
+
* when only its children changed. Pass `null`/undefined to no-op.
|
|
25
|
+
*/
|
|
26
|
+
export function bumpTicketUpdatedAt(db, ticketId, time) {
|
|
27
|
+
if (!ticketId) return;
|
|
28
|
+
db.prepare("UPDATE tickets SET updated_at = ? WHERE id = ?").run(time, ticketId);
|
|
29
|
+
}
|
|
30
|
+
|
|
20
31
|
export function createTicket(body, ctx) {
|
|
21
32
|
const { db, board, actor } = ctx;
|
|
22
33
|
// The router resolves `ctx.board` from path/body/query before we get here.
|
|
@@ -82,6 +93,7 @@ export function createTicket(body, ctx) {
|
|
|
82
93
|
parent_ticket_id: parentTicketId
|
|
83
94
|
});
|
|
84
95
|
reindexTicket(db, ticketId);
|
|
96
|
+
bumpTicketUpdatedAt(db, parentTicketId, time);
|
|
85
97
|
return ticketById(db, ticketId);
|
|
86
98
|
});
|
|
87
99
|
}
|
|
@@ -139,11 +151,12 @@ export function updateTicket(ticketId, body, ctx) {
|
|
|
139
151
|
}
|
|
140
152
|
|
|
141
153
|
return tx(db, () => {
|
|
154
|
+
const time = now();
|
|
142
155
|
if (fields.length > 0) {
|
|
143
156
|
const sets = fields.map((field) => `${field} = ?`);
|
|
144
157
|
const values = fields.map((field) => applied[field]);
|
|
145
158
|
sets.push("updated_at = ?");
|
|
146
|
-
values.push(
|
|
159
|
+
values.push(time, ticketId);
|
|
147
160
|
db.prepare(`UPDATE tickets SET ${sets.join(", ")} WHERE id = ?`).run(...values);
|
|
148
161
|
}
|
|
149
162
|
|
|
@@ -160,7 +173,7 @@ export function updateTicket(ticketId, body, ctx) {
|
|
|
160
173
|
db.prepare("INSERT OR IGNORE INTO ticket_labels (ticket_id, label_id) VALUES (?, ?)").run(ticketId, label.id);
|
|
161
174
|
}
|
|
162
175
|
if (fields.length === 0) {
|
|
163
|
-
db.prepare("UPDATE tickets SET updated_at = ? WHERE id = ?").run(
|
|
176
|
+
db.prepare("UPDATE tickets SET updated_at = ? WHERE id = ?").run(time, ticketId);
|
|
164
177
|
}
|
|
165
178
|
}
|
|
166
179
|
|
|
@@ -191,6 +204,13 @@ export function updateTicket(ticketId, body, ctx) {
|
|
|
191
204
|
}
|
|
192
205
|
}
|
|
193
206
|
reindexTicket(db, ticketId);
|
|
207
|
+
// Roll activity up to the parent epic so the column sort keeps the epic
|
|
208
|
+
// group near recent child activity. On reparent, bump both old and new
|
|
209
|
+
// parent — both views changed (one lost a child, the other gained one).
|
|
210
|
+
bumpTicketUpdatedAt(db, before.parent_ticket_id, time);
|
|
211
|
+
if (after.parent_ticket_id && after.parent_ticket_id !== before.parent_ticket_id) {
|
|
212
|
+
bumpTicketUpdatedAt(db, after.parent_ticket_id, time);
|
|
213
|
+
}
|
|
194
214
|
return after;
|
|
195
215
|
});
|
|
196
216
|
}
|
|
@@ -208,6 +228,7 @@ export function archiveTicket(ticketId, ctx) {
|
|
|
208
228
|
const time = now();
|
|
209
229
|
db.prepare("UPDATE tickets SET archived_at = ?, updated_at = ? WHERE id = ?").run(time, time, ticketId);
|
|
210
230
|
recordEvent(db, ticket.board_id, "ticket_archived", ticketId, actor.name, { title: ticket.title });
|
|
231
|
+
bumpTicketUpdatedAt(db, ticket.parent_ticket_id, time);
|
|
211
232
|
return ticketById(db, ticketId);
|
|
212
233
|
});
|
|
213
234
|
}
|
|
@@ -225,6 +246,7 @@ export function restoreTicket(ticketId, ctx) {
|
|
|
225
246
|
const time = now();
|
|
226
247
|
db.prepare("UPDATE tickets SET archived_at = NULL, updated_at = ? WHERE id = ?").run(time, ticketId);
|
|
227
248
|
recordEvent(db, ticket.board_id, "ticket_restored", ticketId, actor.name, { title: ticket.title });
|
|
249
|
+
bumpTicketUpdatedAt(db, ticket.parent_ticket_id, time);
|
|
228
250
|
return ticketById(db, ticketId);
|
|
229
251
|
});
|
|
230
252
|
}
|
|
@@ -246,6 +268,7 @@ export function deleteTicket(ticketId, ctx) {
|
|
|
246
268
|
});
|
|
247
269
|
db.prepare("DELETE FROM ticket_fts WHERE ticket_id = ?").run(ticketId);
|
|
248
270
|
db.prepare("DELETE FROM tickets WHERE id = ?").run(ticketId);
|
|
271
|
+
bumpTicketUpdatedAt(db, ticket.parent_ticket_id, now());
|
|
249
272
|
return { ok: true, deleted_id: ticketId };
|
|
250
273
|
});
|
|
251
274
|
}
|
|
@@ -262,10 +285,12 @@ export function createComment(ticketId, body, ctx) {
|
|
|
262
285
|
const kind = body.kind || (actor.type === "agent" ? "agent_note" : "human_comment");
|
|
263
286
|
|
|
264
287
|
return tx(db, () => {
|
|
288
|
+
const time = now();
|
|
265
289
|
const comment = addComment(db, ticketId, author, kind, commentBody);
|
|
266
|
-
db.prepare("UPDATE tickets SET updated_at = ? WHERE id = ?").run(
|
|
290
|
+
db.prepare("UPDATE tickets SET updated_at = ? WHERE id = ?").run(time, ticketId);
|
|
267
291
|
recordEvent(db, ticket.board_id, "comment_created", ticketId, actor.name, { kind: comment.kind });
|
|
268
292
|
reindexTicket(db, ticketId);
|
|
293
|
+
bumpTicketUpdatedAt(db, ticket.parent_ticket_id, time);
|
|
269
294
|
return comment;
|
|
270
295
|
});
|
|
271
296
|
}
|
package/src/core/util.js
CHANGED
|
File without changes
|
package/src/mcp-server.js
CHANGED
|
File without changes
|
package/src/server.js
CHANGED
|
File without changes
|