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:
@@ -354,14 +354,13 @@ class ClaudeExtension extends ClideExtension {
|
||||
_subs.add(ctx.messages.subscribe(publisher: 'builtin.tickets', channel: 'pick-up').listen(_onTicketPickUp));
|
||||
}
|
||||
|
||||
/// Hand a picked-up ticket to the active Claude session (T-327): the primary
|
||||
/// lead, else the first visible session. A quiet no-op when none is live.
|
||||
/// Hand a picked-up ticket to the active Claude session (T-327/T-339). The
|
||||
/// decision + transition live in [applyTicketPickUp] so they're testable
|
||||
/// without the activation machinery.
|
||||
void _onTicketPickUp(Message m) {
|
||||
final prompt = m.data['prompt'] as String?;
|
||||
if (prompt == null || prompt.isEmpty) return;
|
||||
final target = _orchestrator?.byId('primary') ?? _orchestrator?.visibleSessions.firstOrNull;
|
||||
if (target == null) return;
|
||||
_orchestrator?.injectMessage(target.id, prompt);
|
||||
final ctx = _ctx;
|
||||
if (ctx == null) return;
|
||||
unawaited(applyTicketPickUp(m.data, orchestrator: _orchestrator, ipc: ctx.ipc, messages: ctx.messages));
|
||||
}
|
||||
|
||||
/// Close every session that doesn't belong to the newly-active workspace
|
||||
@@ -472,3 +471,41 @@ class ClaudeExtension extends ClideExtension {
|
||||
return IpcResponse.ok(id: '', data: const {'status': 'shown'});
|
||||
}
|
||||
}
|
||||
|
||||
/// Statuses a pick-up may advance from: a not-yet-started ticket. Picking up a
|
||||
/// ticket that's already `in_progress`/`review`/`done`/`cancelled` injects the
|
||||
/// prompt but leaves the status alone, so a re-pick-up never drags it backwards
|
||||
/// or reopens it (T-339).
|
||||
const _kPickUpStartableStatuses = {'backlog', 'ready'};
|
||||
|
||||
/// Inject a picked-up ticket's prompt into the active session (the `primary`
|
||||
/// lead, else the first visible one) and, on acceptance from a not-yet-started
|
||||
/// ticket, advance it to `in_progress` and publish a `changed` so the sidebar
|
||||
/// refreshes (T-327/T-339). Returns whether a live session accepted the prompt.
|
||||
///
|
||||
/// With no live session there's no injection and no state change — a quiet
|
||||
/// no-op. Kept free of the extension lifecycle so it's directly testable.
|
||||
@visibleForTesting
|
||||
Future<bool> applyTicketPickUp(
|
||||
Map<String, Object?> data, {
|
||||
required ClaudeSessionOrchestrator? orchestrator,
|
||||
required DaemonClient ipc,
|
||||
required MessageBus messages,
|
||||
}) async {
|
||||
final prompt = data['prompt'] as String?;
|
||||
if (prompt == null || prompt.isEmpty) return false;
|
||||
final target = orchestrator?.byId('primary') ?? orchestrator?.visibleSessions.firstOrNull;
|
||||
if (target == null) return false; // no live session → quiet no-op, no state change
|
||||
orchestrator!.injectMessage(target.id, prompt);
|
||||
|
||||
final id = data['id'] as String?;
|
||||
final status = data['status'] as String?;
|
||||
if (id != null && id.isNotEmpty && _kPickUpStartableStatuses.contains(status)) {
|
||||
final resp = await ipc.request('pql.tickets.status', args: {
|
||||
'ids': [id],
|
||||
'status': 'in_progress',
|
||||
});
|
||||
if (resp.ok) messages.publish('builtin.tickets', 'changed', {'id': id});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -328,7 +328,9 @@ class _PickUpAction extends StatelessWidget {
|
||||
unawaited(() async {
|
||||
final resp = await kernel.ipc.request('pql.tickets.show', args: {'id': id, 'withContext': true});
|
||||
if (!resp.ok) return; // a missing ticket / failed fetch is a quiet no-op
|
||||
kernel.messages.publish('builtin.tickets', 'pick-up', {'id': id, 'prompt': pickUpPrompt(resp.data)});
|
||||
// Carry the current status so the receiver can gate the in_progress
|
||||
// transition without a second fetch (T-339).
|
||||
kernel.messages.publish('builtin.tickets', 'pick-up', {'id': id, 'prompt': pickUpPrompt(resp.data), 'status': resp.data['status']});
|
||||
}());
|
||||
},
|
||||
builder: (ctx, hovered, _) => Padding(
|
||||
|
||||
Reference in New Issue
Block a user