From 78875b559d9765c714c86867e3373154d6666d43 Mon Sep 17 00:00:00 2001 From: himax12 Date: Mon, 2 Mar 2026 02:34:21 +0530 Subject: [PATCH 1/8] feat: add TerminalMarker component for feed phase separators (#7) - New `TerminalMarker` component with typed props and JSDoc - Props: label, timestamp?, variant ('info'|'success'|'warning'|'error'|'neutral') - Theme-aware via CSS custom properties (--term-* variables) - Semantic background tint with color-mix for visual hierarchy (consistent with TerminalBadge) - Responsive: min-w-0 + truncate on timestamp prevents overflow on narrow viewports - Export added to components/terminal.tsx (no breaking changes) - Playground demo added to app/playground/page.tsx --- app/playground/page.tsx | 24 ++++++++- components/terminal-marker.tsx | 92 ++++++++++++++++++++++++++++++++++ components/terminal.tsx | 1 + 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 components/terminal-marker.tsx diff --git a/app/playground/page.tsx b/app/playground/page.tsx index f6a7bde..3003197 100644 --- a/app/playground/page.tsx +++ b/app/playground/page.tsx @@ -1,5 +1,5 @@ import { TerminalApp } from '@/components/terminal-app' -import { Terminal, TerminalCommand, TerminalDiff, TerminalOutput, TerminalSpinner, TerminalBadge, ThemeSwitcher } from '@/components/terminal' +import { Terminal, TerminalCommand, TerminalDiff, TerminalOutput, TerminalSpinner, TerminalBadge, TerminalMarker, ThemeSwitcher } from '@/components/terminal' import { TerminalProgress } from '@/components/terminal-progress' import { LogDemo } from './log-demo' import { PromptDemo } from './prompt-demo' @@ -161,6 +161,28 @@ export default function PlaygroundPage() { +
+

+ TerminalMarker +

+

+ Phase separators for visual boundaries in terminal feeds. +

+ + npm run deploy:full + + ✓ Compiled 42 modules + dist/main.js 124 KB + + ✓ 24 tests passed + coverage: 94% + + → Deploying to production... + ✓ Deployed successfully + + +
+

Typing Animation diff --git a/components/terminal-marker.tsx b/components/terminal-marker.tsx new file mode 100644 index 0000000..bc44621 --- /dev/null +++ b/components/terminal-marker.tsx @@ -0,0 +1,92 @@ +'use client' + +export interface TerminalMarkerProps { + /** Label text displayed as the phase marker. */ + label: string + /** Optional timestamp to display after the label. */ + timestamp?: string + /** Visual style variant (default: 'neutral'). */ + variant?: 'info' | 'success' | 'warning' | 'error' | 'neutral' + /** Additional classes for layout tweaks. */ + className?: string +} + +/** + * Per-variant Tailwind classes for border, background tint, label, and timestamp. + * Background tints use `color-mix` for consistent translucency across themes. + */ +const variantClasses: Record< + NonNullable, + { root: string; label: string; timestamp: string } +> = { + neutral: { + root: 'border-[var(--glass-border)] bg-[rgba(255,255,255,0.03)]', + label: 'text-[var(--term-fg)]', + timestamp: 'text-[var(--term-fg-dim)]', + }, + info: { + root: 'border-[var(--term-blue)]/40 bg-[color-mix(in_oklab,var(--term-blue)_8%,transparent)]', + label: 'text-[var(--term-blue)]', + timestamp: 'text-[var(--term-blue)]/60', + }, + success: { + root: 'border-[var(--term-green)]/40 bg-[color-mix(in_oklab,var(--term-green)_8%,transparent)]', + label: 'text-[var(--term-green)]', + timestamp: 'text-[var(--term-green)]/60', + }, + warning: { + root: 'border-[var(--term-yellow)]/40 bg-[color-mix(in_oklab,var(--term-yellow)_8%,transparent)]', + label: 'text-[var(--term-yellow)]', + timestamp: 'text-[var(--term-yellow)]/60', + }, + error: { + root: 'border-[var(--term-red)]/40 bg-[color-mix(in_oklab,var(--term-red)_8%,transparent)]', + label: 'text-[var(--term-red)]', + timestamp: 'text-[var(--term-red)]/60', + }, +} + +/** + * Displays a terminal-style phase separator for visual boundaries in feeds. + * Used to mark different phases like "Build", "Test", "Deploy" in sequential outputs. + * + * Renders a left-bordered row with a semantic background tint, a bold phase label, + * and an optional timestamp. Fully theme-aware via CSS custom properties and + * naturally responsive at any width. + * + * @param label - Phase label text (e.g., "Build", "Test", "Deploy") + * @param timestamp - Optional timestamp to display after the label + * @param variant - Visual style for semantic coloring (default: 'neutral') + * @param className - Additional CSS classes for layout overrides + * + * @example + * ```tsx + * + * + * + * + * ``` + */ +export function TerminalMarker({ + label, + timestamp, + variant = 'neutral', + className = '', +}: TerminalMarkerProps) { + const cls = variantClasses[variant] + + return ( +
+ + {label} + + {timestamp && ( + {timestamp} + )} +
+ ) +} diff --git a/components/terminal.tsx b/components/terminal.tsx index 56c9e1f..569eb8a 100644 --- a/components/terminal.tsx +++ b/components/terminal.tsx @@ -264,3 +264,4 @@ export { TerminalAutocomplete, useAutocomplete, COMMON_COMMANDS, COMMON_FLAGS, f export { TerminalGhosttyTheme, GhosttyThemePicker } from './terminal-ghostty' export { ThemeSwitcher } from './theme-switcher' export { TerminalBadge } from './terminal-badge' +export { TerminalMarker } from './terminal-marker' From ac8b52c41d2cd81606206e75e7fdb02e20fba296 Mon Sep 17 00:00:00 2001 From: himax12 Date: Mon, 2 Mar 2026 02:42:40 +0530 Subject: [PATCH 2/8] feat: add TerminalLogLine primitive for structured log rows (#8) - New TerminalLogLine component with typed props and JSDoc - Props: message (ReactNode), level, timestamp?, source? - Level badge using 3-char uppercase labels (DBG/INF/WRN/ERR/OK) for consistent monospace alignment - Theme-aware via CSS custom properties; error level colors message text red - Responsive: min-w-0 + wrap-break-word on message prevents overflow - Export added to components/terminal.tsx with re-exported type - Playground demo added with 8 representative log lines --- app/playground/page.tsx | 130 +++++++++++++++++++++---------- components/terminal-log-line.tsx | 116 +++++++++++++++++++++++++++ components/terminal.tsx | 55 +++++++++---- 3 files changed, 242 insertions(+), 59 deletions(-) create mode 100644 components/terminal-log-line.tsx diff --git a/app/playground/page.tsx b/app/playground/page.tsx index 3003197..f1ec138 100644 --- a/app/playground/page.tsx +++ b/app/playground/page.tsx @@ -1,5 +1,15 @@ import { TerminalApp } from '@/components/terminal-app' -import { Terminal, TerminalCommand, TerminalDiff, TerminalOutput, TerminalSpinner, TerminalBadge, TerminalMarker, ThemeSwitcher } from '@/components/terminal' +import { + Terminal, + TerminalCommand, + TerminalDiff, + TerminalOutput, + TerminalSpinner, + TerminalBadge, + TerminalMarker, + TerminalLogLine, + ThemeSwitcher, +} from '@/components/terminal' import { TerminalProgress } from '@/components/terminal-progress' import { LogDemo } from './log-demo' import { PromptDemo } from './prompt-demo' @@ -14,25 +24,19 @@ export default function PlaygroundPage() { return (
-

- Playground -

+

Playground

-

- Terminal App -

+

Terminal App

-

- TerminalPrompt -

+

TerminalPrompt

Interactive command input with history navigation (up / down).

@@ -40,9 +44,7 @@ export default function PlaygroundPage() {
-

- TerminalProgress -

+

TerminalProgress

pnpm install @@ -53,9 +55,7 @@ export default function PlaygroundPage() {
-

- TerminalSpinner -

+

TerminalSpinner

pnpm run build @@ -63,9 +63,7 @@ export default function PlaygroundPage() {
-

- TerminalLog -

+

TerminalLog

Simulated streaming logs with capped history and auto-scroll.

@@ -73,20 +71,18 @@ export default function PlaygroundPage() {
-

- Copy Button -

+

Copy Button

pnpm run build Compiled successfully in 1.2s - Click the copy icon in the header to copy this output. + + Click the copy icon in the header to copy this output. +
-

- TerminalDiff -

+

TerminalDiff

git diff -- src/config.ts Unified @@ -124,9 +120,7 @@ export default function PlaygroundPage() {
-

- TerminalTree -

+

TerminalTree

Expandable tree with custom icon, label, and row render props.

@@ -138,16 +132,14 @@ export default function PlaygroundPage() { Tree Keyboard Navigation

- Arrow keys to navigate, Enter/Space to toggle, ArrowRight to expand/enter, ArrowLeft to collapse/parent. + Arrow keys to navigate, Enter/Space to toggle, ArrowRight to expand/enter, ArrowLeft to + collapse/parent.

-
-

- TerminalBadge -

+

TerminalBadge

pnpm run release @@ -162,9 +154,7 @@ export default function PlaygroundPage() {
-

- TerminalMarker -

+

TerminalMarker

Phase separators for visual boundaries in terminal feeds.

@@ -172,10 +162,10 @@ export default function PlaygroundPage() { npm run deploy:full ✓ Compiled 42 modules - dist/main.js 124 KB + dist/main.js 124 KB ✓ 24 tests passed - coverage: 94% + coverage: 94% → Deploying to production... ✓ Deployed successfully @@ -184,9 +174,65 @@ export default function PlaygroundPage() {
-

- Typing Animation -

+

TerminalLogLine

+

+ Structured log row primitive with level badge, timestamp, source, and message. +

+ + pnpm run start + + + + + + + + + +
+ +
+

Typing Animation

npm run deploy diff --git a/components/terminal-log-line.tsx b/components/terminal-log-line.tsx new file mode 100644 index 0000000..674cf3d --- /dev/null +++ b/components/terminal-log-line.tsx @@ -0,0 +1,116 @@ +'use client' + +import { ReactNode } from 'react' + +export interface TerminalLogLineProps { + /** Primary log message content. */ + message: ReactNode + /** Severity level — controls the label color (default: 'info'). */ + level?: 'debug' | 'info' | 'warn' | 'error' | 'success' + /** Optional timestamp string (e.g. "10:23:45" or ISO). */ + timestamp?: string + /** Optional source / subsystem label (e.g. "server", "db"). */ + source?: string + /** Additional classes for layout overrides. */ + className?: string +} + +/** + * Per-level color tokens. + * + * `badge` — the [LEVEL] indicator pill + * `message` — default foreground for the message (usually fg, dimmed for debug) + */ +const levelClasses: Record< + NonNullable, + { badge: string; message: string } +> = { + debug: { + badge: 'text-[var(--term-fg-dim)] border-[var(--glass-border)]', + message: 'text-[var(--term-fg-dim)]', + }, + info: { + badge: 'text-[var(--term-blue)] border-[var(--term-blue)]/40', + message: 'text-[var(--term-fg)]', + }, + warn: { + badge: 'text-[var(--term-yellow)] border-[var(--term-yellow)]/40', + message: 'text-[var(--term-fg)]', + }, + error: { + badge: 'text-[var(--term-red)] border-[var(--term-red)]/40', + message: 'text-[var(--term-red)]', + }, + success: { + badge: 'text-[var(--term-green)] border-[var(--term-green)]/40', + message: 'text-[var(--term-fg)]', + }, +} + +/** Fixed uppercase display labels so all levels render at the same visual width. */ +const levelLabel: Record, string> = { + debug: 'DBG', + info: 'INF', + warn: 'WRN', + error: 'ERR', + success: 'OK ', +} + +/** + * A single structured log row primitive for terminal-style feeds. + * + * Renders a monospace row with an optional timestamp, a compact level badge, + * an optional source label, and the log message. All color tokens are derived + * from CSS custom properties, making the component fully theme-aware. + * + * Layout (left → right): + * ``` + * [timestamp] [LVL] source message + * ``` + * + * @param message - Log message body (string or any React node) + * @param level - Severity (debug | info | warn | error | success); default 'info' + * @param timestamp - Optional timestamp string shown in dim foreground + * @param source - Optional subsystem / service label shown after the badge + * @param className - Additional CSS classes + * + * @example + * ```tsx + * + * + * + * + * + * ``` + */ +export function TerminalLogLine({ + message, + level = 'info', + timestamp, + source, + className = '', +}: TerminalLogLineProps) { + const cls = levelClasses[level] + + return ( +
+ {/* Timestamp */} + {timestamp && {timestamp}} + + {/* Level badge */} + + {levelLabel[level]} + + + {/* Source */} + {source && {source}} + + {/* Message */} + {message} +
+ ) +} diff --git a/components/terminal.tsx b/components/terminal.tsx index 569eb8a..cafad15 100644 --- a/components/terminal.tsx +++ b/components/terminal.tsx @@ -18,12 +18,12 @@ const TerminalPromptContext = createContext('$') /** * Displays a terminal window with macOS-style chrome and content area. * Renders a terminal emulator UI with title bar, window controls, and monospace content. - * + * * @param children - Terminal content (TerminalCommand, TerminalOutput, TerminalSpinner components) * @param title - Window title shown in the chrome (default: 'Terminal') * @param prompt - Command prompt symbol (default: '$') * @param className - Additional CSS classes to apply to the container - * + * * @example * ```tsx * @@ -32,7 +32,12 @@ const TerminalPromptContext = createContext('$') * * ``` */ -export function Terminal({ children, title = 'Terminal', prompt = '$', className = '' }: TerminalProps) { +export function Terminal({ + children, + title = 'Terminal', + prompt = '$', + className = '', +}: TerminalProps) { const contentRef = useRef(null) const timeoutRef = useRef | null>(null) const [copied, setCopied] = useState(false) @@ -60,7 +65,9 @@ export function Terminal({ children, title = 'Terminal', prompt = '$', className } return ( -
+
{/* Window Chrome */}
@@ -68,9 +75,7 @@ export function Terminal({ children, title = 'Terminal', prompt = '$', className
-
- {title} -
+
{title}
- + {/* Terminal Content */} -
+
{children}
@@ -98,10 +106,10 @@ interface TerminalCommandProps { * Displays a command line in the terminal with a prompt symbol. * Renders text with a leading prompt indicator (typically '$' or '#'). * Inherits prompt from parent Terminal component via context if not specified. - * + * * @param children - The command text to display * @param prompt - The prompt symbol to display before the command (inherited from Terminal if omitted) - * + * * @example * ```tsx * ls -la @@ -134,13 +142,13 @@ interface TerminalOutputProps { * Displays output text with semantic coloring based on message type. * Uses theme colors to indicate success (green), error (red), info (blue), or warning (yellow). * Supports optional typing animation for string children and Prism.js syntax highlighting. - * + * * @param children - The output text to display * @param type - The type of output message (default: 'normal') * @param animate - Enable typing animation (default: false) * @param delay - Milliseconds per character when animating (default: 35) * @param language - Language for syntax highlighting (e.g. 'json', 'typescript') - * + * * @example * ```tsx * Build completed successfully @@ -207,7 +215,11 @@ export function TerminalOutput({
{highlightedHtml ? ( - ) : animate && textContent !== null ? typedText : children} + ) : animate && textContent !== null ? ( + typedText + ) : ( + children + )}
) } @@ -219,9 +231,9 @@ interface TerminalSpinnerProps { /** * Displays an animated spinner with optional text for loading states. * Uses Unicode braille characters for smooth animation. - * + * * @param text - Optional text to display next to the spinner - * + * * @example * ```tsx * @@ -260,8 +272,17 @@ export { TerminalAlert } from './terminal-alert' export { TerminalTabs } from './terminal-tabs' export { TerminalSplit } from './terminal-split' export { TerminalDiff } from './terminal-diff' -export { TerminalAutocomplete, useAutocomplete, COMMON_COMMANDS, COMMON_FLAGS, filterSuggestions, type TerminalAutocompleteProps, type AutocompleteSuggestion } from './terminal-autocomplete' +export { + TerminalAutocomplete, + useAutocomplete, + COMMON_COMMANDS, + COMMON_FLAGS, + filterSuggestions, + type TerminalAutocompleteProps, + type AutocompleteSuggestion, +} from './terminal-autocomplete' export { TerminalGhosttyTheme, GhosttyThemePicker } from './terminal-ghostty' export { ThemeSwitcher } from './theme-switcher' export { TerminalBadge } from './terminal-badge' export { TerminalMarker } from './terminal-marker' +export { TerminalLogLine, type TerminalLogLineProps } from './terminal-log-line' From 905edf9f770258237301205e410bc60b777698d2 Mon Sep 17 00:00:00 2001 From: himax12 Date: Mon, 2 Mar 2026 02:46:08 +0530 Subject: [PATCH 3/8] fix(terminal-log-line): meet all acceptance criteria - Add color-mix background tints to level badges (consistent with TerminalBadge) - Fix badge alignment: use w-[calc(3ch+0.5rem)] + justify-center so all 5 levels (DBG/INF/WRN/ERR/OK) render at identical pixel width in monospace layout - Update levelClasses to Tailwind v4 CSS variable shorthand (text-/border-(--term-*)) - Remove 'OK ' trailing-space hack (HTML collapses whitespace); fixed-width badge container is the correct approach --- components/terminal-log-line.tsx | 42 +++++++++++++++++++------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/components/terminal-log-line.tsx b/components/terminal-log-line.tsx index 674cf3d..c82d751 100644 --- a/components/terminal-log-line.tsx +++ b/components/terminal-log-line.tsx @@ -16,44 +16,52 @@ export interface TerminalLogLineProps { } /** - * Per-level color tokens. + * Per-level color tokens — border, background tint, and text colors. * - * `badge` — the [LEVEL] indicator pill - * `message` — default foreground for the message (usually fg, dimmed for debug) + * `badge` — the [LEVEL] indicator pill (border + bg tint, consistent with TerminalBadge) + * `message` — foreground for the message body (dimmed for debug, red for error) */ const levelClasses: Record< NonNullable, { badge: string; message: string } > = { debug: { - badge: 'text-[var(--term-fg-dim)] border-[var(--glass-border)]', - message: 'text-[var(--term-fg-dim)]', + badge: 'border-(--glass-border) text-(--term-fg-dim) bg-[rgba(255,255,255,0.03)]', + message: 'text-(--term-fg-dim)', }, info: { - badge: 'text-[var(--term-blue)] border-[var(--term-blue)]/40', - message: 'text-[var(--term-fg)]', + badge: + 'border-[var(--term-blue)]/40 text-(--term-blue) bg-[color-mix(in_oklab,var(--term-blue)_10%,transparent)]', + message: 'text-(--term-fg)', }, warn: { - badge: 'text-[var(--term-yellow)] border-[var(--term-yellow)]/40', - message: 'text-[var(--term-fg)]', + badge: + 'border-[var(--term-yellow)]/40 text-(--term-yellow) bg-[color-mix(in_oklab,var(--term-yellow)_10%,transparent)]', + message: 'text-(--term-fg)', }, error: { - badge: 'text-[var(--term-red)] border-[var(--term-red)]/40', - message: 'text-[var(--term-red)]', + badge: + 'border-[var(--term-red)]/40 text-(--term-red) bg-[color-mix(in_oklab,var(--term-red)_10%,transparent)]', + message: 'text-(--term-red)', }, success: { - badge: 'text-[var(--term-green)] border-[var(--term-green)]/40', - message: 'text-[var(--term-fg)]', + badge: + 'border-[var(--term-green)]/40 text-(--term-green) bg-[color-mix(in_oklab,var(--term-green)_10%,transparent)]', + message: 'text-(--term-fg)', }, } -/** Fixed uppercase display labels so all levels render at the same visual width. */ +/** + * 3-character uppercase level labels. + * All entries are exactly 3 chars so the badge pill stays a consistent width + * across levels in a monospace font. + */ const levelLabel: Record, string> = { debug: 'DBG', info: 'INF', warn: 'WRN', error: 'ERR', - success: 'OK ', + success: 'OK', } /** @@ -99,9 +107,9 @@ export function TerminalLogLine({ {/* Timestamp */} {timestamp && {timestamp}} - {/* Level badge */} + {/* Level badge — fixed width keeps columns aligned in monospace feeds */} {levelLabel[level]} From e0b661fdc443e95cd6b88ed8111e16f94ed5cebc Mon Sep 17 00:00:00 2001 From: himax12 Date: Mon, 2 Mar 2026 02:53:00 +0530 Subject: [PATCH 4/8] feat(terminal-log): add structured entries prop, keep lines backward-compat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New LogEntry type { id?, message, level?, timestamp?, source? } - New optional entries prop on TerminalLog — takes precedence over lines - Existing lines?: string[] consumers continue to work with zero changes - Internally reuses TerminalLogLine for structured rendering (level badges, timestamps, source labels, color-mix tints) - maxLines and autoScroll work identically on both paths - Export LogEntry from components/terminal.tsx - Add StructuredLogDemo to playground (streaming entries with all 5 levels) - Use Tailwind v4 CSS variable shorthand in terminal-log.tsx --- app/playground/log-demo.tsx | 61 ++++++++++++++++++- app/playground/page.tsx | 17 +++++- components/terminal-log.tsx | 113 +++++++++++++++++++++++++++++------- components/terminal.tsx | 2 +- 4 files changed, 168 insertions(+), 25 deletions(-) diff --git a/app/playground/log-demo.tsx b/app/playground/log-demo.tsx index 8441c4c..d17002d 100644 --- a/app/playground/log-demo.tsx +++ b/app/playground/log-demo.tsx @@ -1,7 +1,9 @@ 'use client' import { useEffect, useState } from 'react' -import { Terminal, TerminalCommand, TerminalLog } from '@/components/terminal' +import { Terminal, TerminalCommand, TerminalLog, type LogEntry } from '@/components/terminal' + +// ── String-mode demo (unchanged) ───────────────────────────────────────────── const STREAM_LINES = [ '[info] Connecting to build worker...', @@ -38,3 +40,60 @@ export function LogDemo() { ) } + +// ── Structured-mode demo ───────────────────────────────────────────────────── + +const STREAM_ENTRIES: LogEntry[] = [ + { level: 'info', timestamp: '10:23:45', source: 'server', message: 'Worker connected' }, + { level: 'debug', timestamp: '10:23:46', source: 'cache', message: 'HIT packages/react@19.1.0' }, + { level: 'warn', timestamp: '10:23:47', source: 'cache', message: 'MISS @openknots/terminal-ui' }, + { level: 'info', timestamp: '10:23:48', source: 'build', message: 'Compiling 42 modules...' }, + { level: 'success', timestamp: '10:23:49', source: 'build', message: 'Compiled in 1.2s' }, + { level: 'info', timestamp: '10:23:50', source: 'test', message: 'Running 24 unit tests...' }, + { + level: 'error', + timestamp: '10:23:51', + source: 'test', + message: 'FAIL src/utils.test.ts — 1 snapshot mismatch', + }, + { + level: 'info', + timestamp: '10:23:52', + source: 'test', + message: 'Retrying with --updateSnapshot...', + }, + { level: 'success', timestamp: '10:23:53', source: 'test', message: '24 / 24 tests passed' }, + { level: 'success', timestamp: '10:23:54', source: 'deploy', message: 'Published to production' }, +] + +export function StructuredLogDemo() { + const [entries, setEntries] = useState([ + { + id: 'boot-0', + level: 'info', + timestamp: '10:23:44', + source: 'server', + message: 'Starting pipeline...', + }, + ]) + + useEffect(() => { + const timer = window.setInterval(() => { + setEntries((current) => { + const next = STREAM_ENTRIES[current.length % STREAM_ENTRIES.length] + return [...current, { ...next, id: String(current.length) }] + }) + }, 900) + + return () => { + window.clearInterval(timer) + } + }, []) + + return ( + + pnpm run ci + + + ) +} diff --git a/app/playground/page.tsx b/app/playground/page.tsx index f1ec138..096b1de 100644 --- a/app/playground/page.tsx +++ b/app/playground/page.tsx @@ -11,7 +11,7 @@ import { ThemeSwitcher, } from '@/components/terminal' import { TerminalProgress } from '@/components/terminal-progress' -import { LogDemo } from './log-demo' +import { LogDemo, StructuredLogDemo } from './log-demo' import { PromptDemo } from './prompt-demo' import { TreeDemo } from './tree-demo' import { TreeKeyboardDemo } from './tree-keyboard-demo' @@ -63,13 +63,26 @@ export default function PlaygroundPage() {
-

TerminalLog

+

+ TerminalLog — string mode +

Simulated streaming logs with capped history and auto-scroll.

+
+

+ TerminalLog — structured mode +

+

+ Structured entries with level badges, timestamps, and source labels via{' '} + entries prop. +

+ +
+

Copy Button

diff --git a/components/terminal-log.tsx b/components/terminal-log.tsx index e3b8a4a..a5a0233 100644 --- a/components/terminal-log.tsx +++ b/components/terminal-log.tsx @@ -1,10 +1,40 @@ 'use client' import { useEffect, useMemo, useRef } from 'react' +import { TerminalLogLine, type TerminalLogLineProps } from './terminal-log-line' + +// ── Structured entry type ───────────────────────────────────────────────────── + +/** + * A structured log entry for use with the `entries` prop on `TerminalLog`. + * All fields except `message` are optional to keep the type lightweight. + */ +export interface LogEntry { + /** Unique key for React reconciliation. Falls back to index when omitted. */ + id?: string + /** Log message body. */ + message: string + /** Severity level — controls badge color (default: 'info'). */ + level?: TerminalLogLineProps['level'] + /** Optional timestamp string (e.g. "10:23:45"). */ + timestamp?: string + /** Optional source / subsystem label (e.g. "server", "db"). */ + source?: string +} + +// ── Props ───────────────────────────────────────────────────────────────────── export interface TerminalLogProps { - /** Log lines displayed in the terminal stream. */ - lines: string[] + /** + * Plain string lines (backward-compatible path). + * Ignored when `entries` is also provided. + */ + lines?: string[] + /** + * Structured log entries rendered via `TerminalLogLine` with level badges, + * timestamps, and source labels. Takes precedence over `lines`. + */ + entries?: LogEntry[] /** Maximum number of lines rendered (default: 200). */ maxLines?: number /** Auto-scroll to the newest line when new logs arrive (default: false). */ @@ -13,21 +43,38 @@ export interface TerminalLogProps { className?: string } +// ── Component ───────────────────────────────────────────────────────────────── + /** * Displays a terminal-style scrolling log buffer. * - * @param lines - Current log lines to render - * @param maxLines - Maximum number of visible lines kept in view - * @param autoScroll - Whether to stick to the latest line on updates - * @param className - Additional wrapper classes + * Supports two rendering modes: * - * @example + * **String mode** (backward-compatible): * ```tsx * * ``` + * + * **Structured mode** (uses `TerminalLogLine` internally): + * ```tsx + * const entries: LogEntry[] = [ + * { id: '1', level: 'info', timestamp: '10:23:45', source: 'server', message: 'Listening on :3000' }, + * { id: '2', level: 'error', timestamp: '10:23:48', source: 'db', message: 'Connection refused' }, + * ] + * + * ``` + * + * When both props are supplied, `entries` takes precedence. + * + * @param lines - Plain string lines (existing API, fully backward-compatible) + * @param entries - Structured log entries with optional level/timestamp/source + * @param maxLines - Maximum number of visible lines kept in view (default: 200) + * @param autoScroll - Stick to the latest line on updates (default: false) + * @param className - Additional wrapper classes */ export function TerminalLog({ - lines, + lines = [], + entries, maxLines = 200, autoScroll = false, className = '', @@ -35,32 +82,56 @@ export function TerminalLog({ const containerRef = useRef(null) const safeMaxLines = Math.max(1, Math.floor(maxLines)) + // Determine render mode once per render. + const structured = entries !== undefined + + const visibleEntries = useMemo( + () => + structured + ? (entries as LogEntry[]).slice(Math.max(0, (entries as LogEntry[]).length - safeMaxLines)) + : null, + [structured, entries, safeMaxLines] + ) + const visibleLines = useMemo( - () => lines.slice(Math.max(0, lines.length - safeMaxLines)), - [lines, safeMaxLines], + () => (structured ? null : lines.slice(Math.max(0, lines.length - safeMaxLines))), + [structured, lines, safeMaxLines] ) - useEffect(() => { - if (!autoScroll || !containerRef.current) { - return - } + // Scroll sentinel: depend on whichever array is active. + const scrollDep = structured ? visibleEntries : visibleLines + useEffect(() => { + if (!autoScroll || !containerRef.current) return containerRef.current.scrollTop = containerRef.current.scrollHeight - }, [autoScroll, visibleLines]) + }, [autoScroll, scrollDep]) return (
- {visibleLines.map((line, index) => ( -
- {line} -
- ))} + {structured + ? visibleEntries!.map((entry, index) => ( + + )) + : visibleLines!.map((line, index) => ( +
+ {line} +
+ ))}
) } diff --git a/components/terminal.tsx b/components/terminal.tsx index cafad15..9e4529b 100644 --- a/components/terminal.tsx +++ b/components/terminal.tsx @@ -264,7 +264,7 @@ export { TerminalProgress } from './terminal-progress' export { TerminalPrompt } from './terminal-prompt' export { TerminalTree } from './terminal-tree' export type { TreeNode, TreeRenderContext, TerminalTreeProps } from './terminal-tree' -export { TerminalLog } from './terminal-log' +export { TerminalLog, type LogEntry } from './terminal-log' export { TerminalTable, type ColumnAlign } from './terminal-table' export { TerminalBarChart, TerminalSparkline, type BarChartVariant } from './terminal-chart' export { TerminalSelect } from './terminal-select' From 7b91ce217437266a05f13035199b59738be6b63f Mon Sep 17 00:00:00 2001 From: himax12 Date: Mon, 2 Mar 2026 03:17:21 +0530 Subject: [PATCH 5/8] feat: add TerminalFilterBar for level/source/text filtering (#10) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New TerminalFilterBar controlled component with typed props + JSDoc - Level toggle buttons (DBG/INF/WRN/ERR/OK) with active color-mix tints matching TerminalLogLine badge palette; aria-pressed for a11y - Text search input with inline SVG search icon + clear button (X) - Source toggle buttons — row only rendered when sources prop is non-empty - Clear-all pill shown only when any filter is active - All controls are native
+
+

TerminalSearch

+

+ In-feed search with next / prev navigation (Enter / Shift+Enter) and match highlighting. +

+ +
+

Typing Animation

@@ -256,6 +266,15 @@ export default function PlaygroundPage() {
+ +
+

TerminalFilterBar

+

+ Controlled filter bar — level toggles, text search, and source toggles. Pair with{' '} + filterEntries() to apply. +

+ +
) } diff --git a/components/terminal-filter-bar.tsx b/components/terminal-filter-bar.tsx new file mode 100644 index 0000000..6409ee6 --- /dev/null +++ b/components/terminal-filter-bar.tsx @@ -0,0 +1,292 @@ +'use client' + +import { useId } from 'react' +import type { TerminalLogLineProps } from './terminal-log-line' +import type { LogEntry } from './terminal-log' + +// ── Types ───────────────────────────────────────────────────────────────────── + +export type LogLevel = NonNullable + +/** + * The controlled state for `TerminalFilterBar`. + * + * - `levels` — set of active levels. Empty array = all levels visible. + * - `text` — case-insensitive substring filter applied to `message` and `source`. + * - `sources` — set of active sources. Empty array = all sources visible. + */ +export interface FilterBarState { + levels: LogLevel[] + text: string + sources: string[] +} + +/** Returns a `FilterBarState` with all filters cleared (show everything). */ +export function emptyFilterState(): FilterBarState { + return { levels: [], text: '', sources: [] } +} + +export interface TerminalFilterBarProps { + /** Current filter state (controlled). */ + state: FilterBarState + /** Called whenever the user changes any filter value. */ + onChange: (next: FilterBarState) => void + /** + * Available source options to show as toggle buttons. + * When empty or omitted the source row is hidden. + */ + sources?: string[] + /** Additional CSS classes for layout overrides. */ + className?: string +} + +// ── Level metadata ──────────────────────────────────────────────────────────── + +const LEVELS: LogLevel[] = ['debug', 'info', 'warn', 'error', 'success'] + +/** Active (toggled-on) Tailwind classes per level — mirrors TerminalLogLine badge palette. */ +const levelActiveClasses: Record = { + debug: 'border-(--glass-border) text-(--term-fg-dim) bg-[rgba(255,255,255,0.06)]', + info: 'border-[var(--term-blue)]/40 text-(--term-blue) bg-[color-mix(in_oklab,var(--term-blue)_14%,transparent)]', + warn: 'border-[var(--term-yellow)]/40 text-(--term-yellow) bg-[color-mix(in_oklab,var(--term-yellow)_14%,transparent)]', + error: + 'border-[var(--term-red)]/40 text-(--term-red) bg-[color-mix(in_oklab,var(--term-red)_14%,transparent)]', + success: + 'border-[var(--term-green)]/40 text-(--term-green) bg-[color-mix(in_oklab,var(--term-green)_14%,transparent)]', +} + +const levelLabel: Record = { + debug: 'DBG', + info: 'INF', + warn: 'WRN', + error: 'ERR', + success: 'OK', +} + +// ── Utility ─────────────────────────────────────────────────────────────────── + +/** + * Pure filtering helper — apply a `FilterBarState` to a `LogEntry[]`. + * + * Rules: + * - If `state.levels` is empty, all levels pass. + * - If `state.sources` is empty, all sources pass. + * - `state.text` is trimmed and matched case-insensitively against `message` and `source`. + * + * @example + * ```tsx + * const visible = filterEntries(entries, filterState) + * + * ``` + */ +export function filterEntries(entries: LogEntry[], state: FilterBarState): LogEntry[] { + const needle = state.text.trim().toLowerCase() + + return entries.filter((entry) => { + // Level filter + if (state.levels.length > 0) { + const entryLevel: LogLevel = entry.level ?? 'info' + if (!state.levels.includes(entryLevel)) return false + } + + // Source filter + if (state.sources.length > 0) { + if (!entry.source || !state.sources.includes(entry.source)) return false + } + + // Text filter + if (needle) { + const msg = (typeof entry.message === 'string' ? entry.message : '').toLowerCase() + const src = (entry.source ?? '').toLowerCase() + if (!msg.includes(needle) && !src.includes(needle)) return false + } + + return true + }) +} + +// ── Component ───────────────────────────────────────────────────────────────── + +/** + * A compact controlled filter bar for terminal feed views. + * + * Provides three composable filters: + * 1. **Level toggles** — click to include/exclude `debug | info | warn | error | success`. + * No levels selected = all levels shown. + * 2. **Text search** — case-insensitive substring match across `message` and `source`. + * 3. **Source toggles** — only rendered when the `sources` prop is non-empty. + * No sources selected = all sources shown. + * + * Pair with the exported `filterEntries` helper to apply the state: + * ```tsx + * const [filter, setFilter] = useState(emptyFilterState) + * const visible = filterEntries(entries, filter) + * + * + * + * ``` + * + * All controls are keyboard-accessible (native ` + ) + })} + + {/* Separator */} +