feat(studio): approval payload — explain absence, cap height, accurate label
Some checks failed
Security / Security check (push) Failing after 1s

The approval card only rendered the payload section when a preview
existed, so a step with no 'show:' showed nothing and the reviewer
could not tell why. Now always show the section: a present payload
scrolls inside a height-capped, copyable box; an absent one shows an
explanatory hint (the flow's approval step chooses what to surface via
its 'show:' field). Relabel 'data to be released' -> 'data to review'
(the payload is review context, accurate to its source).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-06-19 02:29:20 +02:00
parent 8a40e8e315
commit 97f3cb8560
6 changed files with 75 additions and 23 deletions

View file

@ -1014,7 +1014,8 @@
"approvalsPillApproved": "freigegeben", "approvalsPillApproved": "freigegeben",
"approvalsPillRejected": "abgelehnt", "approvalsPillRejected": "abgelehnt",
"approvalsPillExpired": "abgelaufen", "approvalsPillExpired": "abgelaufen",
"approvalsPayloadPreview": "DIESE DATEN WERDEN FREIGEGEBEN", "approvalsPayloadPreview": "ZU PRÜFENDE DATEN",
"approvalsNoPayload": "Keine Daten zum Prüfen angehängt. Der system.approval-Schritt des Flows bestimmt über sein \"show:\"-Feld, was angezeigt wird — setze es, um die Daten hinter dieser Entscheidung sichtbar zu machen.",
"approvalsRequestFallback": "Freigabe für diesen Schritt erforderlich", "approvalsRequestFallback": "Freigabe für diesen Schritt erforderlich",
"approvalsFlowStepMeta": "Flow: {flow} · Schritt: {step}", "approvalsFlowStepMeta": "Flow: {flow} · Schritt: {step}",
"approvalsApproveButton": "Freigeben", "approvalsApproveButton": "Freigeben",

View file

@ -1027,7 +1027,8 @@
"approvalsPillApproved": "approved", "approvalsPillApproved": "approved",
"approvalsPillRejected": "rejected", "approvalsPillRejected": "rejected",
"approvalsPillExpired": "expired", "approvalsPillExpired": "expired",
"approvalsPayloadPreview": "DATA TO BE RELEASED", "approvalsPayloadPreview": "DATA TO REVIEW",
"approvalsNoPayload": "No data was attached for review. The flow's approval step chooses what to show via its \"show:\" field — set it to surface the data behind this decision.",
"approvalsRequestFallback": "Approval required for this step", "approvalsRequestFallback": "Approval required for this step",
"approvalsFlowStepMeta": "Flow: {flow} · Step: {step}", "approvalsFlowStepMeta": "Flow: {flow} · Step: {step}",
"@approvalsFlowStepMeta": { "@approvalsFlowStepMeta": {

View file

@ -3065,9 +3065,15 @@ abstract class AppLocalizations {
/// No description provided for @approvalsPayloadPreview. /// No description provided for @approvalsPayloadPreview.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'DATA TO BE RELEASED'** /// **'DATA TO REVIEW'**
String get approvalsPayloadPreview; String get approvalsPayloadPreview;
/// No description provided for @approvalsNoPayload.
///
/// In en, this message translates to:
/// **'No data was attached for review. The flow\'s approval step chooses what to show via its \"show:\" field — set it to surface the data behind this decision.'**
String get approvalsNoPayload;
/// No description provided for @approvalsRequestFallback. /// No description provided for @approvalsRequestFallback.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View file

@ -1767,7 +1767,11 @@ class AppLocalizationsDe extends AppLocalizations {
String get approvalsPillExpired => 'abgelaufen'; String get approvalsPillExpired => 'abgelaufen';
@override @override
String get approvalsPayloadPreview => 'DIESE DATEN WERDEN FREIGEGEBEN'; String get approvalsPayloadPreview => 'ZU PRÜFENDE DATEN';
@override
String get approvalsNoPayload =>
'Keine Daten zum Prüfen angehängt. Der system.approval-Schritt des Flows bestimmt über sein \"show:\"-Feld, was angezeigt wird — setze es, um die Daten hinter dieser Entscheidung sichtbar zu machen.';
@override @override
String get approvalsRequestFallback => String get approvalsRequestFallback =>

View file

@ -1777,7 +1777,11 @@ class AppLocalizationsEn extends AppLocalizations {
String get approvalsPillExpired => 'expired'; String get approvalsPillExpired => 'expired';
@override @override
String get approvalsPayloadPreview => 'DATA TO BE RELEASED'; String get approvalsPayloadPreview => 'DATA TO REVIEW';
@override
String get approvalsNoPayload =>
'No data was attached for review. The flow\'s approval step chooses what to show via its \"show:\" field — set it to surface the data behind this decision.';
@override @override
String get approvalsRequestFallback => 'Approval required for this step'; String get approvalsRequestFallback => 'Approval required for this step';

View file

@ -585,19 +585,44 @@ class _ApprovalCard extends StatelessWidget {
), ),
], ],
), ),
if (approval.payloadPreview != null) ...[ const SizedBox(height: ChainSpace.md),
const SizedBox(height: ChainSpace.md), Padding(
Padding( padding: const EdgeInsets.only(bottom: 4),
padding: const EdgeInsets.only(bottom: 4), child: Text(
child: Text( l.approvalsPayloadPreview,
l.approvalsPayloadPreview, style: theme.textTheme.labelSmall?.copyWith(
style: theme.textTheme.labelSmall?.copyWith( color: theme.colorScheme.onSurfaceVariant,
color: theme.colorScheme.onSurfaceVariant, letterSpacing: 0.6,
letterSpacing: 0.6, fontSize: 10,
fontSize: 10,
),
), ),
), ),
),
if (approval.payloadPreview != null)
Container(
width: double.infinity,
// Cap the height so a large payload scrolls inside the
// card instead of stretching it off-screen; still fully
// readable + copyable (SelectableText).
constraints: const BoxConstraints(maxHeight: 280),
padding: const EdgeInsets.all(ChainSpace.md),
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(ChainRadius.sm),
border: Border.all(color: theme.colorScheme.outlineVariant),
),
child: SingleChildScrollView(
child: SelectableText(
_prettyPreview(approval.payloadPreview!),
style: ChainTheme.mono(
size: 11,
color: theme.colorScheme.onSurface,
),
),
),
)
else
// No `show:` on the approval step explain where the data
// would come from instead of rendering nothing.
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(ChainSpace.md), padding: const EdgeInsets.all(ChainSpace.md),
@ -606,15 +631,26 @@ class _ApprovalCard extends StatelessWidget {
borderRadius: BorderRadius.circular(ChainRadius.sm), borderRadius: BorderRadius.circular(ChainRadius.sm),
border: Border.all(color: theme.colorScheme.outlineVariant), border: Border.all(color: theme.colorScheme.outlineVariant),
), ),
child: SelectableText( child: Row(
_prettyPreview(approval.payloadPreview!), crossAxisAlignment: CrossAxisAlignment.start,
style: ChainTheme.mono( children: [
size: 11, Icon(
color: theme.colorScheme.onSurface, Icons.info_outline,
), size: 14,
color: theme.colorScheme.onSurfaceVariant,
),
const SizedBox(width: ChainSpace.sm),
Expanded(
child: Text(
l.approvalsNoPayload,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
),
],
), ),
), ),
],
const SizedBox(height: ChainSpace.lg), const SizedBox(height: ChainSpace.lg),
Row( Row(
children: [ children: [