From d5a85c4ed0e552e1594dd0dccbf647b1c4ada406 Mon Sep 17 00:00:00 2001 From: "cubecraft-agents[bot]" <3458173+cubecraft-agents[bot]@users.noreply.github.com> Date: Sun, 26 Apr 2026 12:54:25 +0000 Subject: [PATCH 1/4] CUB-47: Implement Tactical Dark Mode CSS Variables --- frontend/src/styles.scss | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index af13a84..a581a28 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -48,6 +48,17 @@ html { // These are NOT part of the M3 tonal palette; they are semantic overrides. // --------------------------------------------------------------------------- :root { + // --- Tactical Dark Mode color palette (CUB-47) --- + --color-surface: #0F172A; + --color-surface-light: #1E293B; + --color-primary: #38BDF8; + --color-secondary: #2DD4BF; + --color-accent: #A78BFA; + --color-danger: #F87171; + --color-text-primary: #FFFFFF; + --color-text-secondary: #94A3B8; + --color-border: #334155; + // --- Status colors --- --status-active: #38BDF8; --status-idle: #2DD4BF; @@ -90,7 +101,7 @@ html { // Global Body Styles // --------------------------------------------------------------------------- body { - background-color: var(--cc-background); + background-color: var(--color-surface); color: var(--cc-on-surface); font-family: 'Inter', 'Roboto', sans-serif; margin: 0; From fb88eab4d11d57601848e0aeaa17e6e444232078 Mon Sep 17 00:00:00 2001 From: "cubecraft-agents[bot]" <3458173+cubecraft-agents[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 10:07:07 +0000 Subject: [PATCH 2/4] feat(CUB-55): add SendStatusUpdate method to AgentStatusHub --- backend/ControlCenter/Hubs/AgentStatusHub.cs | 27 +++++++ .../Hubs/Models/AgentStatusModels.cs | 74 +++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/backend/ControlCenter/Hubs/AgentStatusHub.cs b/backend/ControlCenter/Hubs/AgentStatusHub.cs index f0d4497..c790b29 100644 --- a/backend/ControlCenter/Hubs/AgentStatusHub.cs +++ b/backend/ControlCenter/Hubs/AgentStatusHub.cs @@ -37,6 +37,33 @@ public class AgentStatusHub : Hub _logger = logger; } + /// + /// Broadcasts an agent status update to all connected clients. + /// + /// + /// Any connected client (or server-side caller) can invoke this method + /// to push a status update to every subscriber. The DTO is converted to + /// an record and relayed through the + /// callback. + /// + /// + /// The agent status update DTO to broadcast. + public async Task SendStatusUpdate(AgentStatusUpdateDto update) + { + _logger.LogInformation( + "Broadcasting status update for agent {AgentId}: {Status}", + update.AgentId, update.Status); + + var agentUpdate = update.ToUpdate(); + + // Broadcast to all connected clients + await Clients.All.AgentStatusChanged(agentUpdate); + + // Also push to the specific agent's group + var agentGroup = AgentGroupName(update.AgentId); + await Clients.Group(agentGroup).AgentStatusChanged(agentUpdate); + } + /// /// Adds the calling connection to the fleet group. /// Once joined, the client will receive all agent status changes diff --git a/backend/ControlCenter/Hubs/Models/AgentStatusModels.cs b/backend/ControlCenter/Hubs/Models/AgentStatusModels.cs index 3c9c97d..3edb603 100644 --- a/backend/ControlCenter/Hubs/Models/AgentStatusModels.cs +++ b/backend/ControlCenter/Hubs/Models/AgentStatusModels.cs @@ -72,6 +72,80 @@ public record TaskProgressUpdate( string? Elapsed ); +/// +/// Data transfer object for broadcasting agent status updates +/// to all connected SignalR clients via the hub's SendStatusUpdate method. +/// +/// This DTO provides a mutable, serialization-friendly alternative to +/// for callers that construct updates +/// from external data sources (e.g., HTTP API payloads). +/// +public class AgentStatusUpdateDto +{ + /// + /// Agent identifier, e.g. "otto", "dex", "rex". + /// + public string AgentId { get; set; } = string.Empty; + + /// + /// Human-readable display name, e.g. "Otto", "Dex". + /// + public string DisplayName { get; set; } = string.Empty; + + /// + /// Role description, e.g. "Orchestrator Agent", "Backend Specialist". + /// + public string Role { get; set; } = string.Empty; + + /// + /// Current operational status of the agent as lowercase string: + /// "active", "idle", "thinking", "error". + /// + public string Status { get; set; } = string.Empty; + + /// + /// Description of the agent's current task, if any. + /// + public string? CurrentTask { get; set; } + + /// + /// Full session key, e.g. "agent:otto:telegram:direct:8787451565". + /// + public string SessionKey { get; set; } = string.Empty; + + /// + /// Communication channel, e.g. "telegram", "discord", "slack". + /// + public string Channel { get; set; } = string.Empty; + + /// + /// ISO 8601 timestamp of the agent's last activity. + /// + public string LastActivity { get; set; } = string.Empty; + + /// + /// Error message when the agent status is "error". + /// + public string? ErrorMessage { get; set; } + + /// + /// Converts this DTO to an immutable record + /// for use with the typed SignalR client interface. + /// + /// An with equivalent field values. + public AgentStatusUpdate ToUpdate() => new( + AgentId, + DisplayName, + Role, + Status, + CurrentTask, + SessionKey, + Channel, + LastActivity, + ErrorMessage + ); +} + /// /// Snapshot of an agent's full card data, sent on initial connection /// or when the fleet state is requested. From c8ca182af0a3e2ccc2409d2733c953d7daf6b443 Mon Sep 17 00:00:00 2001 From: "cubecraft-agents[bot]" <3458173+cubecraft-agents[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 12:40:29 +0000 Subject: [PATCH 3/4] =?UTF-8?q?CUB-52:=20responsive=20hub=20grid=20CSS=20?= =?UTF-8?q?=E2=80=94=20extract=20styles=20to=20SCSS,=20add=202-col=20deskt?= =?UTF-8?q?op=20/=201-col=20mobile=20breakpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/app/pages/hub/hub-page.component.scss | 28 +++++++++++++++++++ .../src/app/pages/hub/hub-page.component.ts | 13 +-------- 2 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 frontend/src/app/pages/hub/hub-page.component.scss diff --git a/frontend/src/app/pages/hub/hub-page.component.scss b/frontend/src/app/pages/hub/hub-page.component.scss new file mode 100644 index 0000000..1b2c65f --- /dev/null +++ b/frontend/src/app/pages/hub/hub-page.component.scss @@ -0,0 +1,28 @@ +// ============================================================================ +// Hub Page — Responsive AgentCard Grid +// Desktop (≥1024px): 2×2 grid +// Mobile (<1024px): single-column stack +// ============================================================================ + +.hub-page { + display: grid; + grid-template-columns: 1fr; + gap: 16px; + padding: var(--cc-section-padding, 16px); + min-height: 400px; + overflow-x: hidden; +} + +.hub-page__placeholder { + color: var(--cc-on-surface-variant); + font-size: 16px; + text-align: center; + padding: 24px 0; +} + +// Desktop / kiosk breakpoint — 2-column grid +@media (min-width: 1024px) { + .hub-page { + grid-template-columns: repeat(2, 1fr); + } +} \ No newline at end of file diff --git a/frontend/src/app/pages/hub/hub-page.component.ts b/frontend/src/app/pages/hub/hub-page.component.ts index 7819be4..1749b94 100644 --- a/frontend/src/app/pages/hub/hub-page.component.ts +++ b/frontend/src/app/pages/hub/hub-page.component.ts @@ -9,18 +9,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';

Command Hub — Fleet status grid will render here

`, - styles: [` - .hub-page { - display: flex; - align-items: center; - justify-content: center; - min-height: 400px; - } - .hub-page__placeholder { - color: var(--cc-on-surface-variant); - font-size: 16px; - } - `], + styleUrl: './hub-page.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class HubPageComponent {} \ No newline at end of file From 5375d117922e0c40b6d0203418e2fd5f431fe268 Mon Sep 17 00:00:00 2001 From: rex-bot Date: Mon, 27 Apr 2026 14:17:04 +0000 Subject: [PATCH 4/4] CUB-48: Agent Status Badge component with pulse animations --- .../agent-status-badge.component.html | 8 + .../agent-status-badge.component.scss | 146 ++++++++++++++++++ .../agent-status-badge.component.ts | 54 +++++++ .../components/agent-status-badge/index.ts | 1 + frontend/src/app/components/index.ts | 1 + 5 files changed, 210 insertions(+) create mode 100644 frontend/src/app/components/agent-status-badge/agent-status-badge.component.html create mode 100644 frontend/src/app/components/agent-status-badge/agent-status-badge.component.scss create mode 100644 frontend/src/app/components/agent-status-badge/agent-status-badge.component.ts create mode 100644 frontend/src/app/components/agent-status-badge/index.ts create mode 100644 frontend/src/app/components/index.ts diff --git a/frontend/src/app/components/agent-status-badge/agent-status-badge.component.html b/frontend/src/app/components/agent-status-badge/agent-status-badge.component.html new file mode 100644 index 0000000..e88ea86 --- /dev/null +++ b/frontend/src/app/components/agent-status-badge/agent-status-badge.component.html @@ -0,0 +1,8 @@ + + + {{ displayLabel }} + \ No newline at end of file diff --git a/frontend/src/app/components/agent-status-badge/agent-status-badge.component.scss b/frontend/src/app/components/agent-status-badge/agent-status-badge.component.scss new file mode 100644 index 0000000..331d6f9 --- /dev/null +++ b/frontend/src/app/components/agent-status-badge/agent-status-badge.component.scss @@ -0,0 +1,146 @@ +// ============================================================================ +// Agent Status Badge — per spec Section 7.3 +// Colored pill with dot indicator and optional pulse animation. +// ============================================================================ + +$badge-height: 24px; +$dot-size: 8px; +$border-radius: 12px; +$font-size: 12px; +$font-weight: 500; +$padding-x: 8px; +$gap: 6px; + +@use 'sass:color'; + +// Status color palette +$color-active: #22c55e; // green-500 +$color-idle: #9ca3af; // gray-400 +$color-thinking: #3b82f6; // blue-500 +$color-error: #ef4444; // red-500 +$color-offline: #9ca3af; // gray-400 + +// Background tints (12% opacity for soft pill background) +$bg-active: rgba($color-active, 0.12); +$bg-idle: rgba($color-idle, 0.12); +$bg-thinking: rgba($color-thinking, 0.12); +$bg-error: rgba($color-error, 0.12); +$bg-offline: rgba($color-offline, 0.12); + +// --------------------------------------------------------------------------- +// Base pill +// --------------------------------------------------------------------------- +.badge { + display: inline-flex; + align-items: center; + height: $badge-height; + padding: 0 $padding-x; + border-radius: $border-radius; + gap: $gap; + font-size: $font-size; + font-weight: $font-weight; + line-height: 1; + white-space: nowrap; + user-select: none; +} + +// --------------------------------------------------------------------------- +// Dot indicator +// --------------------------------------------------------------------------- +.badge__dot { + width: $dot-size; + height: $dot-size; + border-radius: 50%; + flex-shrink: 0; +} + +// --------------------------------------------------------------------------- +// Label text +// --------------------------------------------------------------------------- +.badge__label { + line-height: 1; +} + +// --------------------------------------------------------------------------- +// Status color variants +// --------------------------------------------------------------------------- +.badge--active { + background: $bg-active; + color: color.adjust($color-active, $lightness: -10%); + + .badge__dot { + background: $color-active; + } +} + +.badge--idle { + background: $bg-idle; + color: color.adjust($color-idle, $lightness: -15%); + + .badge__dot { + background: $color-idle; + } +} + +.badge--thinking { + background: $bg-thinking; + color: color.adjust($color-thinking, $lightness: -10%); + + .badge__dot { + background: $color-thinking; + } +} + +.badge--error { + background: $bg-error; + color: color.adjust($color-error, $lightness: -10%); + + .badge__dot { + background: $color-error; + } +} + +.badge--offline { + background: $bg-offline; + color: color.adjust($color-offline, $lightness: -15%); + + .badge__dot { + background: $color-offline; + } +} + +// --------------------------------------------------------------------------- +// Pulse animation — applied when status is active, thinking, or error +// --------------------------------------------------------------------------- +.badge--pulse { + .badge__dot { + animation: pulse-dot 2s ease-in-out infinite; + } +} + +// Active: 2s pulse +.badge--active.badge--pulse .badge__dot { + animation-duration: 2s; +} + +// Thinking: 3s pulse +.badge--thinking.badge--pulse .badge__dot { + animation-duration: 3s; +} + +// Error: 0.8s pulse (fast, urgent) +.badge--error.badge--pulse .badge__dot { + animation-duration: 0.8s; +} + +@keyframes pulse-dot { + 0%, + 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.4; + transform: scale(1.5); + } +} \ No newline at end of file diff --git a/frontend/src/app/components/agent-status-badge/agent-status-badge.component.ts b/frontend/src/app/components/agent-status-badge/agent-status-badge.component.ts new file mode 100644 index 0000000..a246d63 --- /dev/null +++ b/frontend/src/app/components/agent-status-badge/agent-status-badge.component.ts @@ -0,0 +1,54 @@ +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; +import { AgentStatus } from '../../models/agent.model'; + +/** + * Agent Status Badge component. + * Displays a colored pill with a pulse animation indicating the agent's current status. + * Per spec Section 7.3: Agent Card Component Interface — status indicator. + * + * Color mapping: + * - Active → green + * - Idle → gray + * - Thinking → blue + * - Error → red + * - Offline → gray (no pulse) + * + * Pulse animations: + * - Active → 2s + * - Error → 0.8s + * - Thinking → 3s + * - Idle / Offline → no pulse + */ +@Component({ + selector: 'app-agent-status-badge', + standalone: true, + imports: [], + templateUrl: './agent-status-badge.component.html', + styleUrl: './agent-status-badge.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AgentStatusBadgeComponent { + /** Current agent status — binds to the AgentStatus type from the model. */ + readonly status = input.required(); + + /** Label text shown inside the badge. Defaults to title-cased status. */ + readonly label = input(); + + get displayLabel(): string { + return this.label() ?? this.titleCase(this.status()); + } + + /** CSS class driven by the current status value. */ + get statusClass(): string { + return `badge--${this.status()}`; + } + + /** Whether the pulse animation should be active for the current status. */ + get hasPulse(): boolean { + return this.status() === 'active' || this.status() === 'thinking' || this.status() === 'error'; + } + + private titleCase(value: string): string { + return value.charAt(0).toUpperCase() + value.slice(1); + } +} \ No newline at end of file diff --git a/frontend/src/app/components/agent-status-badge/index.ts b/frontend/src/app/components/agent-status-badge/index.ts new file mode 100644 index 0000000..e531d31 --- /dev/null +++ b/frontend/src/app/components/agent-status-badge/index.ts @@ -0,0 +1 @@ +export { AgentStatusBadgeComponent } from './agent-status-badge.component'; \ No newline at end of file diff --git a/frontend/src/app/components/index.ts b/frontend/src/app/components/index.ts new file mode 100644 index 0000000..09c8fb9 --- /dev/null +++ b/frontend/src/app/components/index.ts @@ -0,0 +1 @@ +export { AgentStatusBadgeComponent } from './agent-status-badge/agent-status-badge.component'; \ No newline at end of file