Skip to content
Brok UI

Loading…

No results

AI Activity Summary

Open source

A quiet, one-line rollup of what an agent run did, collapsing to a counted sentence and expanding to the ordered list of steps.

Version
v1.0.1
Stability
stable
License
MIT
Related
AI Function Call
AI Chain of Thought
AI Task Progress
Collapsible

Preview

  1. Added the button component php artisan ui:add button Done
  2. Added the card component php artisan ui:add card Done
  3. Added the avatar component php artisan ui:add avatar Done
  4. Built the front-end assets npm run build Done
  5. Reviewed dashboard.blade.php Done
  6. Created ReportCard.php Done
  7. Left a note about the missing empty state. Done
  1. Searching the docs for rate limits Done
  2. Fetching the pricing page Done
  3. Reading throttle.php Running
previews.components.ai-activity-summary.default.blade.php 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>

Installation

terminal
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:

resources/js/ui/index.js JS
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.

  • blade resources/views/components/ui/ai/activity-summary.blade.php
Registry dependencies
collapsible shimmering-text
Packages
composer: jml/brok:^0.2

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.

ai-activity-summary.md
# 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.blade.php Blade
{{-- 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

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
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.

ai-activity-summary ai-activity-summary-glyph ai-activity-summary-list

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

AI-assisted interaction

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
root ai-activity-summary-list
Theming hooks
Composes semantic collapsible, shimmering-text, muted-foreground, destructive and success status tokens.

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
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-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, 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 under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Safe

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.

resources/views/components/ui/ai/activity-summary.blade.php Blade
@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