pick-up sets the ticket to in_progress on accept (T-339)

When a ticket is handed to a live Claude pane (T-327), advance it to
in_progress on the receiving side of the bus — gated on acceptance, so a
pick-up with no live session stays a quiet no-op and never mutates state.
Only a not-yet-started ticket (backlog/ready) transitions, so re-picking
up a review/done ticket doesn't drag it backwards. On success it publishes
(builtin.tickets, changed) so the sidebar refreshes.

The handler logic moves into a testable applyTicketPickUp() seam; the
sidebar button now carries the current status in the pick-up payload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 16:18:16 +02:00
co-authored by Claude Opus 4.8
parent dea3e70e49
commit a948bc8a1f
6 changed files with 173 additions and 8 deletions
@@ -3187,3 +3187,4 @@ INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, chang
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3JM0AXK1CTWD720RV1AVZ0', 'status', 'backlog', 'ready', NULL, '2026-06-10 13:54:02', '2026-06-10 13:54:02', '2026-06-10 13:54:02', NULL, '5cb488441033ac5a781d4b7d91dbda6f', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7F2TBJJV1F2KP7XR8', 'status', 'in_progress', 'done', NULL, '2026-06-10 13:55:30', '2026-06-10 13:55:30', '2026-06-10 13:55:30', NULL, 'f618ee8a3a33dabfa822bd0cebae4e03', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3JAXDKZMS0805MMEYB6820', 'status', 'ready', 'done', NULL, '2026-06-10 14:03:10', '2026-06-10 14:03:10', '2026-06-10 14:03:10', NULL, '96365bc3ef7dbf505448be2dad16e4b6', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3JM0AXK1CTWD720RV1AVZ0', 'status', 'ready', 'done', NULL, '2026-06-10 14:14:33', '2026-06-10 14:14:33', '2026-06-10 14:14:33', NULL, 'd028dbbdbad663a682c5ed4d92dc7cc2', 2) ON CONFLICT(hash) DO NOTHING;
+15
View File
@@ -2591,3 +2591,18 @@ Root cause: the sidechain fold keys entirely off the transcript envelope. In tra
Need to confirm whether the prompt envelope is missing isSidechain (likely the spawning prompt is the FIRST user turn of the sidechain and may be emitted by the harness without the flag, or with a parentUuid that points at the spawning turn rather than the Agent tool_use). Check stream_json_session.dart emission too.
Fix direction (TBD after confirming the envelope shape): either (a) ensure the prompt envelope is correctly flagged/parented, or (b) in _sidechainFold associate the first sidechain-adjacent user prompt with its Agent tool_use even when the flag/chain is incomplete. Related: T-263 (prompt folds into the call), T-264 (run nests under the card).', 'done', 'medium', NULL, NULL, NULL, '2026-06-10 13:47:04', '2026-06-10 14:03:10', NULL, 'ee474dfe790eb6e72bd6a7381cd900e2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3JM0AXK1CTWD720RV1AVZ0', 'story', '06FB0TNQM5TWC00GW0P3X02HZW', 'Pick-up sets ticket to in_progress on accept', 'When a ticket is handed to Claude via the sidebar Pick-up button (T-327), also transition the ticket to in_progress — but on the receiving side of the message bus, not in the button.
Today the Pick-up button (`_PickUpAction` in lib/builtin/tickets/src/tickets_view.dart) only fetches the ticket and publishes a (builtin.tickets, pick-up) message carrying {id, prompt}. The Claude pane receives it in `_onTicketPickUp` (lib/builtin/claude/src/extension.dart:359) and injects the prompt into the active session. The state transition should live here, gated on acceptance i.e. only set in_progress once a live session actually accepts the prompt (injectMessage succeeds against a target session), so a pick-up with no live Claude pane stays a quiet no-op and does not mutate state.
Implementation notes:
- In `_onTicketPickUp`, after a successful injectMessage to a target session, call the existing status transition path: IPC `pql.tickets.status` with {ids: [id], status: ''in_progress''} (same call the detail view''s _StatusControls uses).
- The ''id'' is already in the message payload currently only ''prompt'' is read; also read ''id''.
- After a successful transition, publish (builtin.tickets, changed, {id}) so the sidebar/detail refresh, matching ticket_detail_view.dart behaviour.
- Keep it idempotent/safe: if the ticket is already in_progress (or terminal), don''t fight the user consider skipping when current status != open/todo. Verify the configured status vocabulary via `pql ticket statuslist`.
- No live session => no injection => no state change (preserve existing no-op semantics).
Acceptance:
- Picking up a ticket with a live Claude pane injects the prompt AND moves the ticket to in_progress, with the sidebar reflecting the new state.
- Picking up with no live session leaves the ticket untouched.
- Test coverage on _onTicketPickUp for both the accepted and no-session paths.', 'done', 'medium', NULL, NULL, NULL, '2026-06-10 13:48:18', '2026-06-10 14:14:33', NULL, 'd615d07653f4916b50514993935b90da', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);