diff --git a/lib/builtin/decisions/src/decision_detail_view.dart b/lib/builtin/decisions/src/decision_detail_view.dart index 13d00e5c..62ec350f 100644 --- a/lib/builtin/decisions/src/decision_detail_view.dart +++ b/lib/builtin/decisions/src/decision_detail_view.dart @@ -33,7 +33,7 @@ class _DecisionDetailViewState extends State { Future _load(String id) async { setState(() => _loading = true); final kernel = ClideKernel.of(context); - final resp = await kernel.ipc.request('pql.decisions.show', args: {'id': id}); + final resp = await kernel.ipc.request('pql.decisions.read', args: {'id': id}); if (!mounted) return; setState(() { _loading = false; @@ -57,6 +57,8 @@ class _DecisionDetailViewState extends State { final domain = d['domain'] as String?; final status = d['status'] as String?; final date = d['date'] as String?; + final body = d['body'] as String?; + final refs = (d['refs'] as List?)?.cast>() ?? const []; final typeColor = typeColors.forType(type); return SingleChildScrollView( @@ -104,12 +106,63 @@ class _DecisionDetailViewState extends State { ], ), ), + if (body != null && body.isNotEmpty) ...[ + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: tokens.panelBackground, + borderRadius: BorderRadius.circular(4), + border: Border.all(color: tokens.panelBorder), + ), + child: ClideText(body, fontSize: 13, fontFamily: clideMonoFamily), + ), + ], + if (refs.isNotEmpty) ...[ + const SizedBox(height: 16), + ClideText('CROSS-REFERENCES', fontSize: 11, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily), + const SizedBox(height: 6), + for (final ref in refs) _RefCard(ref: ref, tokens: tokens), + ], ], ), ); } } +class _RefCard extends StatelessWidget { + const _RefCard({required this.ref, required this.tokens}); + final Map ref; + final SurfaceTokens tokens; + + @override + Widget build(BuildContext context) { + final targetId = (ref['target_id'] ?? ref['source_id']) as String? ?? ''; + final refType = ref['ref_type'] as String? ?? ''; + return Padding( + padding: const EdgeInsets.only(bottom: 4), + child: ClideTappable( + onTap: () => ClideKernel.of(context).messages.publish('builtin.decisions', 'selection', {'id': targetId}), + builder: (ctx, hovered, _) => Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + decoration: BoxDecoration( + color: hovered ? tokens.sidebarItemHover : tokens.panelBackground, + borderRadius: BorderRadius.circular(4), + border: Border.all(color: hovered ? tokens.panelActiveBorder : tokens.panelBorder), + ), + child: Row( + children: [ + ClideText(targetId, fontSize: 11, color: tokens.globalFocus, fontFamily: clideMonoFamily), + const SizedBox(width: 8), + ClideText(refType, fontSize: 11, color: tokens.globalTextMuted), + ], + ), + ), + ), + ); + } +} + class _StatusBadge extends StatelessWidget { const _StatusBadge({required this.status, required this.tokens}); final String status; diff --git a/lib/src/daemon/pql_commands.dart b/lib/src/daemon/pql_commands.dart index 6980ad95..21288743 100644 --- a/lib/src/daemon/pql_commands.dart +++ b/lib/src/daemon/pql_commands.dart @@ -125,6 +125,19 @@ void registerPqlCommands(DaemonDispatcher d, PqlClient pql) { } }); + d.register('pql.decisions.read', (req) async { + final id = req.args['id'] as String?; + if (id == null || id.isEmpty) { + return _userError(req.id, 'pql.decisions.read requires an id'); + } + try { + final result = await pql.decisionRead(id); + return IpcResponse.ok(id: req.id, data: result); + } on PqlException catch (e) { + return _pqlError(req.id, e); + } + }); + d.register('pql.decisions.show', (req) async { final id = req.args['id'] as String?; if (id == null || id.isEmpty) { diff --git a/lib/src/pql/client.dart b/lib/src/pql/client.dart index 1036fc45..6770a3e7 100644 --- a/lib/src/pql/client.dart +++ b/lib/src/pql/client.dart @@ -94,6 +94,10 @@ class PqlClient { return _runObject(args); } + Future> decisionRead(String id) async { + return _runObject(['decisions', 'read', id]); + } + Future>> decisionCoverage() async { return _runList(['decisions', 'coverage']); }