From 8a0ca92a1ff99697442a2bf9d548f56dda17051d Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Fri, 24 Apr 2026 10:50:05 +0200 Subject: [PATCH] wire ticket status buttons to pql ticket status Status buttons in the detail view now call pql.tickets.status IPC, publish a changed event to refresh the sidebar list, and reload the detail. Disabled for the current status. IPC handler accepts ids as a list for batch transitions. PqlClient.ticketSetStatus takes List and joins with comma per pql CLI contract. Co-Authored-By: Claude --- lib/builtin/tickets/src/ticket_detail_view.dart | 9 ++++++--- lib/src/daemon/pql_commands.dart | 15 +++++++++++++++ lib/src/pql/client.dart | 6 ++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/builtin/tickets/src/ticket_detail_view.dart b/lib/builtin/tickets/src/ticket_detail_view.dart index d6092b71..83a7cdd9 100644 --- a/lib/builtin/tickets/src/ticket_detail_view.dart +++ b/lib/builtin/tickets/src/ticket_detail_view.dart @@ -153,9 +153,12 @@ class _StatusControls extends StatelessWidget { for (final s in _statuses) ...[ Expanded( child: ClideTappable( - onTap: () async { - await controller.ipc.request('pql.tickets.status', args: {'ids': detail.id, 'status': s}); - await controller.load(detail.id); + onTap: detail.status == s ? null : () async { + final resp = await controller.ipc.request('pql.tickets.status', args: {'ids': [detail.id], 'status': s}); + if (resp.ok) { + controller.messages.publish('builtin.tickets', 'changed', {'id': detail.id}); + await controller.load(detail.id); + } }, builder: (ctx, hovered, _) { final active = detail.status == s; diff --git a/lib/src/daemon/pql_commands.dart b/lib/src/daemon/pql_commands.dart index dc1e34b2..da731669 100644 --- a/lib/src/daemon/pql_commands.dart +++ b/lib/src/daemon/pql_commands.dart @@ -209,6 +209,21 @@ void registerPqlCommands(DaemonDispatcher d, PqlClient pql) { } }); + d.register('pql.tickets.status', (req) async { + final rawIds = req.args['ids']; + final ids = rawIds is List ? rawIds.cast() : rawIds is String ? [rawIds] : []; + final status = req.args['status'] as String?; + if (ids.isEmpty || status == null || status.isEmpty) { + return _userError(req.id, 'pql.tickets.status requires ids and status'); + } + try { + final result = await pql.ticketSetStatus(ids, status); + return IpcResponse.ok(id: req.id, data: {'tickets': result}); + } on PqlException catch (e) { + return _pqlError(req.id, e); + } + }); + d.register('pql.tickets.board', (req) async { try { final board = await pql.ticketBoard( diff --git a/lib/src/pql/client.dart b/lib/src/pql/client.dart index 345d7f35..6f0079a7 100644 --- a/lib/src/pql/client.dart +++ b/lib/src/pql/client.dart @@ -8,6 +8,8 @@ library; import 'dart:convert'; import 'dart:io'; +import 'package:flutter/foundation.dart'; + class PqlException implements Exception { const PqlException(this.message, {this.exitCode = 1, this.stderr = ''}); final String message; @@ -133,6 +135,10 @@ class PqlClient { return _runObject(args); } + Future>> ticketSetStatus(List ids, String status) async { + return _runList(['ticket', 'status', ids.join(','), status]); + } + Future>> ticketBoard({String? team}) async { final args = ['ticket', 'board']; if (team != null) args.addAll(['--team', team]);