AI Activity Summary
A quiet, one-line rollup of what an agent run did, collapsing to a counted sentence and expanding to the ordered list of steps.
Preview
-
Added the button component
php artisan ui:add buttonDone -
Added the card component
php artisan ui:add cardDone -
Added the avatar component
php artisan ui:add avatarDone -
Built the front-end assets
npm run buildDone - Reviewed dashboard.blade.php Done
- Created ReportCard.php Done
- Left a note about the missing empty state. Done
- Searching the docs for rate limits Done
- Fetching the pricing page Done
- Reading throttle.php Running
<div class="mx-auto w-full max-w-lg space-y-4">
<x-ui.ai.activity-summary
open
:steps="[
['type' => 'command', 'label' => __('Added the button component'), 'detail' => 'php artisan ui:add button', 'status' => 'done'],
['type' => 'command', 'label' => __('Added the card component'), 'detail' => 'php artisan ui:add card', 'status' => 'done'],
['type' => 'command', 'label' => __('Added the avatar component'), 'detail' => 'php artisan ui:add avatar', 'status' => 'done'],
['type' => 'command', 'label' => __('Built the front-end assets'), 'detail' => 'npm run build', 'status' => 'done'],
['type' => 'read', 'label' => __('Reviewed dashboard.blade.php'), 'status' => 'done'],
['type' => 'create', 'label' => __('Created ReportCard.php'), 'status' => 'done'],
['type' => 'note', 'label' => __('Left a note about the missing empty state.'), 'status' => 'done'],
]"
/>
<x-ui.ai.activity-summary
state="running"
:steps="[
['type' => 'search', 'label' => __('Searching the docs for rate limits'), 'status' => 'done'],
['type' => 'fetch', 'label' => __('Fetching the pricing page'), 'status' => 'done'],
['type' => 'read', 'label' => __('Reading throttle.php'), 'status' => 'running'],
]"
/>
</div>
Installation
php artisan ui:add ai-activity-summary
Note
This component ships an Alpine behavior module at
resources/js/ui/ai-activity-summary.js. Import it once from your bundle so it registers on alpine:init:
import './ai-activity-summary.js';
Registry contract
php artisan ui:add ai-activity-summary
writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.
-
resources/views/components/ui/ai/activity-summary.blade.php
Use with AI
A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.
# Brok UI: AI Activity Summary (`ai-activity-summary`)
A quiet, one-line rollup of what an agent run did, collapsing to a counted sentence and expanding to the ordered list of steps.
Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.
## Install
```bash
php artisan ui:add ai-activity-summary
```
## Usage
```blade
<div class="mx-auto w-full max-w-lg space-y-4">
<x-ui.ai.activity-summary
open
:steps="[
['type' => 'command', 'label' => __('Added the button component'), 'detail' => 'php artisan ui:add button', 'status' => 'done'],
['type' => 'command', 'label' => __('Added the card component'), 'detail' => 'php artisan ui:add card', 'status' => 'done'],
['type' => 'command', 'label' => __('Added the avatar component'), 'detail' => 'php artisan ui:add avatar', 'status' => 'done'],
['type' => 'command', 'label' => __('Built the front-end assets'), 'detail' => 'npm run build', 'status' => 'done'],
['type' => 'read', 'label' => __('Reviewed dashboard.blade.php'), 'status' => 'done'],
['type' => 'create', 'label' => __('Created ReportCard.php'), 'status' => 'done'],
['type' => 'note', 'label' => __('Left a note about the missing empty state.'), 'status' => 'done'],
]"
/>
<x-ui.ai.activity-summary
state="running"
:steps="[
['type' => 'search', 'label' => __('Searching the docs for rate limits'), 'status' => 'done'],
['type' => 'fetch', 'label' => __('Fetching the pricing page'), 'status' => 'done'],
['type' => 'read', 'label' => __('Reading throttle.php'), 'status' => 'running'],
]"
/>
</div>
```
## Props
- `steps` (array, default `[]`) — Each entry is [type, label, detail?, status?]. type is one of command, read, edit, create, search, fetch, note, other; status is pending, running, done or error.
- `summary` (mixed|null, default `null`) — Overrides the computed count sentence entirely when given.
- `state` (string, default `done`) — running, done or error. running shimmers the sentence and pulses the glyph; error tints the glyph with the destructive token.
- `open` (bool, default `false`) — Starts the disclosure expanded.
## Use when
- Use to expose AI-specific prompts, states, or reasoning metadata when those details help users supervise or understand an AI workflow.
- Rolling up what an agent run did — commands, file reads, edits, creates — into one quiet line above a markdown answer.
- Letting a reader expand the rollup on demand into the ordered list of individual steps.
## Avoid when
- Do not surface internal AI mechanics when they add confusion or implementation detail without user value.
- Showing a single tool call and its arguments; use ai-function-call for that.
- Showing the model's reasoning; use ai-chain-of-thought for that.
## Anti-patterns
- Decorative AI chrome
- Exposing internal mechanics without user value
## Rules
- Use the `<brok:ai-activity-summary>` tag (or `<x-ui.ai-activity-summary>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.
## Links
- Docs: https://brokui.dev/docs/components/ai-activity-summary
- Registry JSON (files, props, contract): https://brokui.dev/r/open/ai-activity-summary.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
Examples
Long Content
{{-- Long step labels and details wrap inside the list, and the counted
summary truncates on one line instead of pushing the chevron out. --}}
<div class="w-72">
<x-ui.ai.activity-summary
open
:steps="[
['type' => 'command', 'label' => __('Installed every component the dashboard composes, then rebuilt the front-end assets for the preview'), 'detail' => 'php artisan ui:add tray stat chart-legend partition-bar list-panel --no-interaction', 'status' => 'done'],
['type' => 'read', 'label' => __('Reviewed resources/views/pages/dashboard/overview-with-a-very-long-file-name.blade.php'), 'status' => 'done'],
['type' => 'edit', 'label' => __('Edited the spending breakdown so its categories add up to the total expense'), 'status' => 'done'],
['type' => 'create', 'label' => __('Created a report card for the quarterly recurring revenue by region and channel'), 'status' => 'error'],
['type' => 'note', 'label' => __('Left a note about the missing empty state for accounts without transactions'), 'status' => 'done'],
]"
/>
</div>
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| steps | array | [] | Each entry is [type, label, detail?, status?]. type is one of command, read, edit, create, search, fetch, note, other; status is pending, running, done or error. |
| summary | mixed | null | null | Overrides the computed count sentence entirely when given. |
| state | string | done | running, done or error. running shimmers the sentence and pulses the glyph; error tints the glyph with the destructive token. |
| open | bool | false | Starts the disclosure expanded. |
Slots
Default Blade slot only.
Data slots
Stable hooks for CSS overrides and browser tests.
Behavior
- The summary sentence is built from per-type counts in a fixed order (command, read, edit, create, search, fetch, other, note), joined with commas — never a middle-dot separator.
- The line is a disclosure: collapsed it shows a sparkle glyph and the sentence; open it lists every step with its type glyph, label, optional monospace detail and a status dot.
- state "running" wraps the sentence in shimmering-text and pulses the glyph; state "error" tints the glyph with the destructive token.
- Installs a JavaScript behavior module when the registry item includes resources/js/ui files.
- Declares registry capability flags: a11y, interactive, authoredStateFixtures, responsive, rtl, darkMode, localized, reducedMotion.
Guidance
Help users prompt, inspect, or supervise an AI-assisted operation.
Use when
- Use to expose AI-specific prompts, states, or reasoning metadata when those details help users supervise or understand an AI workflow.
- Rolling up what an agent run did — commands, file reads, edits, creates — into one quiet line above a markdown answer.
- Letting a reader expand the rollup on demand into the ordered list of individual steps.
Avoid when
- Do not surface internal AI mechanics when they add confusion or implementation detail without user value.
- Showing a single tool call and its arguments; use ai-function-call for that.
- Showing the model's reasoning; use ai-chain-of-thought for that.
Use instead
- Deterministic conventional workflow
Anti-patterns
- Decorative AI chrome
- Exposing internal mechanics without user value
- Anatomy
- Theming hooks
Accessibility
- Keyboard
- No component-owned keyboard interaction; native element behavior applies.
- Focus
none
- The trigger is a real button with aria-expanded from the collapsible primitive, so the state is announced.
- Each step's status dot carries an sr-only label alongside its colour, so the state is never colour-only.
- Motion on the running glyph, the shimmer and the chevron is disabled under prefers-reduced-motion.
- Semantic HTML and a stable
data-slotattribute for styling and scripting hooks. - Focus-visible rings use the
ringtoken, so keyboard focus is always visible. - Disabled and invalid states are conveyed to assistive tech, not by color alone.
- Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
- Labels go through
__()and layout uses logical properties (ms-*,text-start), so it mirrors underdir="rtl"— flip the preview to RTL to confirm. - Dark mode uses the same semantic tokens under the
darkclass; high contrast follows forced-color system tokens.
Livewire
Livewire can update this component through forwarded wire:* attributes.
Source
The exact, editable file ui:add writes
into your app. Previews render this same code; there are no preview-only components.
@props([
'steps' => [],
// Overrides the computed count sentence entirely when given.
'summary' => null,
// running | done | error — drives the leading glyph and the shimmer.
'state' => 'done',
'open' => false,
])
@php
$steps = is_array($steps) && count($steps)
? $steps
: [
['type' => 'command', 'label' => 'php artisan ui:add button', 'detail' => 'php artisan ui:add button', 'status' => 'done'],
['type' => 'command', 'label' => 'php artisan ui:add card', 'detail' => 'php artisan ui:add card', 'status' => 'done'],
['type' => 'command', 'label' => 'vendor/bin/pint', 'detail' => 'vendor/bin/pint', 'status' => 'done'],
['type' => 'command', 'label' => 'npm run build', 'detail' => 'npm run build', 'status' => 'done'],
['type' => 'read', 'label' => __('Reviewed dashboard.blade.php'), 'status' => 'done'],
['type' => 'create', 'label' => __('Created ReportCard.php'), 'status' => 'done'],
['type' => 'note', 'label' => __('Left a note about the missing empty state.'), 'status' => 'done'],
];
$state = in_array($state, ['running', 'done', 'error'], true) ? $state : 'done';
// Small, fixed vocabulary. An unrecognised type falls back to "other" so a
// caller's typo degrades gracefully instead of throwing.
$typeIcons = [
'command' => '<polyline points="4 17 10 11 4 5"/><line x1="12" x2="20" y1="19" y2="19"/>',
'read' => '<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/>',
'edit' => '<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/><path d="m15 5 4 4"/>',
'create' => '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M12 18v-6"/><path d="M9 15h6"/>',
'search' => '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
'fetch' => '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>',
'note' => '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
'other' => '<circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/>',
];
$statusTones = [
'pending' => ['dot' => 'border border-muted-foreground/50', 'label' => __('Pending')],
'running' => ['dot' => 'bg-info motion-safe:animate-pulse', 'label' => __('Running')],
'done' => ['dot' => 'bg-success', 'label' => __('Done')],
'error' => ['dot' => 'bg-destructive', 'label' => __('Error')],
];
// count per type, in a fixed narration order — "note" trails as a footnote
// rather than a counted action.
$order = ['command', 'read', 'edit', 'create', 'search', 'fetch', 'other', 'note'];
$counts = array_fill_keys($order, 0);
foreach ($steps as $step) {
$type = is_array($step) ? ($step['type'] ?? 'other') : 'other';
$type = array_key_exists($type, $counts) ? $type : 'other';
$counts[$type]++;
}
$phrase = function (string $type, int $n) {
return match ($type) {
'command' => trans_choice('ran :count command|ran :count commands', $n, ['count' => $n]),
'read' => trans_choice('viewed :count file|viewed :count files', $n, ['count' => $n]),
'edit' => trans_choice('edited :count file|edited :count files', $n, ['count' => $n]),
'create' => trans_choice('created :count file|created :count files', $n, ['count' => $n]),
'search' => trans_choice('ran :count search|ran :count searches', $n, ['count' => $n]),
'fetch' => trans_choice('fetched :count page|fetched :count pages', $n, ['count' => $n]),
'note' => trans_choice('wrote :count note|wrote :count notes', $n, ['count' => $n]),
default => trans_choice('took :count other action|took :count other actions', $n, ['count' => $n]),
};
};
$parts = [];
foreach ($order as $type) {
if ($counts[$type] > 0) {
$parts[] = $phrase($type, $counts[$type]);
}
}
// Phrases are lower case so the joined sentence reads naturally; only its
// first letter is raised.
$computed = count($parts) ? \Illuminate\Support\Str::ucfirst(implode(', ', $parts)) : __('No activity yet');
$summaryText = $summary ?? $computed;
@endphp
{{--
AI activity summary — a quiet, one-line rollup of what an agent run did,
meant to sit above a markdown answer rather than inside a card. The line
is a disclosure (the collapsible primitive): collapsed it reads as a
sparkle glyph plus a counted sentence built from `steps` (comma-joined,
never a middle-dot separator); open it lists every step with its type
glyph, label, optional monospace detail and a status dot. `state`
"running" shimmers the sentence and pulses the glyph; "error" tints the
glyph with the destructive token. Status dots carry an sr-only label
alongside the colour so the state isn't colour-only.
--}}
{{-- The collapsible stamps its own data-slot, so the stable hook lives on
this wrapper instead of being passed through (a second data-slot on the
same element would be dropped by the parser). --}}
<div data-slot="ai-activity-summary" data-state="{{ $state }}" {{ $attributes->merge(['class' => 'text-sm']) }}>
<x-ui.collapsible :open="$open">
<x-ui.collapsible.trigger class="-ms-2 w-full gap-2 rounded-md px-2 py-1 text-muted-foreground hover:bg-muted/50 hover:text-foreground focus-visible:ring-inset motion-reduce:transition-none">
<span class="flex min-w-0 flex-1 items-center gap-2">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.75"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
data-slot="ai-activity-summary-glyph"
class="size-4 shrink-0 {{ $state === 'error' ? 'text-destructive' : 'text-muted-foreground' }} {{ $state === 'running' ? 'motion-safe:animate-pulse' : '' }}"
><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/></svg>
@if ($state === 'running')
<x-ui.shimmering-text as="span" class="min-w-0 flex-1 truncate text-start text-sm">{{ $summaryText }}</x-ui.shimmering-text>
@else
<span class="min-w-0 flex-1 truncate text-start">{{ $summaryText }}</span>
@endif
</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 shrink-0 text-muted-foreground transition-transform duration-200 motion-reduce:transition-none" :class="open && 'rotate-180'" aria-hidden="true">
<path d="m6 9 6 6 6-6" />
</svg>
</x-ui.collapsible.trigger>
<x-ui.collapsible.content class="ps-2">
<ol data-slot="ai-activity-summary-list" class="ms-2 mt-1 space-y-2 border-s border-border ps-4">
@foreach ($steps as $step)
@php
$type = is_array($step) ? ($step['type'] ?? 'other') : 'other';
$type = array_key_exists($type, $typeIcons) ? $type : 'other';
$label = is_array($step) ? ($step['label'] ?? '') : '';
$detail = is_array($step) ? ($step['detail'] ?? null) : null;
$status = is_array($step) ? ($step['status'] ?? null) : null;
$tone = $statusTones[$status] ?? null;
@endphp
<li class="flex items-start gap-2 text-sm">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 size-3 shrink-0 text-muted-foreground">{!! $typeIcons[$type] !!}</svg>
<span class="min-w-0 flex-1">
<span class="block break-words text-foreground">{{ $label }}</span>
@if ($detail)
<code class="mt-0.5 block break-all font-mono text-2xs text-muted-foreground">{{ $detail }}</code>
@endif
</span>
@if ($tone)
<span class="mt-1 size-2 shrink-0 rounded-full {{ $tone['dot'] }}" aria-hidden="true"></span>
<span class="sr-only">{{ $tone['label'] }}</span>
@endif
</li>
@endforeach
</ol>
</x-ui.collapsible.content>
</x-ui.collapsible>
</div>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.0.1 - Status
- Stable
- License
-
open - Accessibility reviewed
- No review date recorded
- Last breaking change
- No date recorded
- Deprecation
- Not deprecated
- Contract
-
v6 - Foundation
-
≥ 1.0.0