Conversation
…en#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
) - 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
- 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
…compat
- 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
…n#10) - 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 <button>/<input> — fully keyboard accessible - Exported pure helper filterEntries(entries, state) for composable use - emptyFilterState() factory for clean initial state - Exports: TerminalFilterBar, filterEntries, emptyFilterState, FilterBarState, TerminalFilterBarProps, LogLevel - FilterBarDemo playground with 14 entries across 5 levels and 4 sources - Entry count indicator: 'N / 14 entries shown'
…n#13) - New TerminalJsonLine component with typed props and JSDoc - Accepts payload: unknown (JS value) or raw JSON string -- serialised/parsed automatically - Collapsed one-line summary: '{ N keys }', '[ N items ]', or truncated primitive - Click (or Enter/Space) toggles expanded Prism-highlighted pretty-print view - Invalid JSON handled safely -- shows INVALID badge + raw value, never throws - Large payloads constrained to max-h-72 scrollable code block - Exported parse logic is pure -- summarise() and parse() are unit-testable - aria-expanded + aria-controls on toggle button for screen reader support - Exported: TerminalJsonLine, TerminalJsonLineProps from terminal.tsx - Playground demo: 5 entries covering object, nested object, raw JSON string, invalid JSON, and a default-expanded payload
There was a problem hiding this comment.
Pull request overview
Adds a set of new terminal UI primitives and interactive playground demos to showcase richer log/terminal experiences (ANSI rendering, structured logs, filtering, grouping, search, JSON payloads, stack traces), and updates the playground page to surface them.
Changes:
- Introduces new terminal components:
TerminalAnsi,TerminalLogLine,TerminalGroup,TerminalSearch(+useTerminalSearch),TerminalFilterBar(+ helpers),TerminalJsonLine,TerminalStackTrace, andTerminalMarker. - Extends
TerminalLogto support structured entries (entries) in addition to string lines. - Refactors
app/playground/page.tsxto add sections/demos for the new components and reorganize headings.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| components/terminal.tsx | Reformats component and expands barrel exports to include new terminal primitives. |
| components/terminal-ansi.tsx | Adds ANSI SGR parsing + safe rendering component (TerminalAnsi / parseAnsi). |
| components/terminal-filter-bar.tsx | Adds controlled filter bar UI + filterEntries/emptyFilterState helpers. |
| components/terminal-group.tsx | Adds collapsible grouping primitive for log sections with variants and ARIA wiring. |
| components/terminal-json-line.tsx | Adds collapsible JSON renderer with Prism highlighting and invalid handling. |
| components/terminal-log.tsx | Adds structured log mode via entries and uses TerminalLogLine for rendering. |
| components/terminal-log-line.tsx | Adds structured log row primitive with level badge/timestamp/source columns. |
| components/terminal-marker.tsx | Adds feed “phase marker” separator component. |
| components/terminal-search.tsx | Adds useTerminalSearch hook and TerminalSearch UI component. |
| components/terminal-stack-trace.tsx | Adds interactive stack trace renderer with filtering/collapse controls. |
| app/playground/page.tsx | Adds new playground sections and imports for the new demos/components. |
| app/playground/ansi-demo.tsx | Adds demo content for ANSI rendering. |
| app/playground/filter-demo.tsx | Adds demo for filter bar + filtered structured logs. |
| app/playground/group-demo.tsx | Adds demo for collapsible grouped log sections (controlled + uncontrolled). |
| app/playground/log-demo.tsx | Adds structured-log streaming demo alongside existing string demo. |
| app/playground/search-demo.tsx | Adds demo for in-feed search with highlighting + navigation. |
| app/playground/stack-trace-demo.tsx | Adds demo for stack trace renderer with sample stacks. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| useTerminalSearch, | ||
| type LogEntry, | ||
| } from '@/components/terminal' | ||
| import { TerminalLog } from '@/components/terminal-log' |
There was a problem hiding this comment.
TerminalLog is imported but never used in this demo, which will trip lint rules like no-unused-vars in many configs. Remove the unused import to keep the file clean.
| import { TerminalLog } from '@/components/terminal-log' |
| {visibleFrames.map((frame, i) => { | ||
| // Preserve original index across the full frame list for gutter numbering | ||
| const originalIndex = frames.indexOf(frame) | ||
| return ( | ||
| <div key={originalIndex} role="listitem"> | ||
| <FrameRow frame={frame} index={originalIndex} /> | ||
| </div> | ||
| ) | ||
| })} | ||
|
|
There was a problem hiding this comment.
originalIndex is computed via frames.indexOf(frame) inside the map, which makes rendering O(n²) and can give surprising results if duplicates ever occur. Use the map index (and adjust for filtering) or carry the original index alongside the frame when building visibleFrames to keep this O(n).
| {visibleFrames.map((frame, i) => { | |
| // Preserve original index across the full frame list for gutter numbering | |
| const originalIndex = frames.indexOf(frame) | |
| return ( | |
| <div key={originalIndex} role="listitem"> | |
| <FrameRow frame={frame} index={originalIndex} /> | |
| </div> | |
| ) | |
| })} | |
| {(() => { | |
| // Compute original indices for all visible frames in a single pass | |
| const visibleOriginalIndices = frames.reduce<number[]>((acc, frame, idx) => { | |
| if (showNodeModules || !frame.isNodeModules) { | |
| acc.push(idx) | |
| } | |
| return acc | |
| }, []) | |
| return visibleFrames.map((frame, i) => { | |
| // Preserve original index across the full frame list for gutter numbering | |
| const originalIndex = visibleOriginalIndices[i] ?? i | |
| return ( | |
| <div key={originalIndex} role="listitem"> | |
| <FrameRow frame={frame} index={originalIndex} /> | |
| </div> | |
| ) | |
| }) | |
| })()} |
| export type { TreeNode, TreeRenderContext, TerminalTreeProps } from './terminal-tree' | ||
| export { TerminalLog } from './terminal-log' | ||
| export { TerminalLog, type LogEntry } from './terminal-log' | ||
| export { TerminalFeed, type TerminalFeedProps, type FeedEntry } from './terminal-feed' |
There was a problem hiding this comment.
The barrel export references ./terminal-feed, but components/terminal-feed.tsx doesn't exist in the repo. This will cause a TypeScript/module-resolution build failure; either add the missing file or remove this export until the component is implemented.
| export { TerminalFeed, type TerminalFeedProps, type FeedEntry } from './terminal-feed' |
| import { TerminalProgress } from '@/components/terminal-progress' | ||
| import { LogDemo } from './log-demo' | ||
| import { LogDemo, StructuredLogDemo } from './log-demo' | ||
| import { FeedDemo } from './feed-demo' |
There was a problem hiding this comment.
FeedDemo is imported from ./feed-demo, but there is no app/playground/feed-demo.tsx in the repository. This import will break the playground page build; add the missing demo file or remove the import/section.
| import { FeedDemo } from './feed-demo' |
| if (p === 0) { Object.assign(next, RESET_STYLE) } | ||
| else if (p === 1) { next.bold = true } | ||
| else if (p === 2) { next.dim = true } | ||
| else if (p === 3) { next.italic = true } | ||
| else if (p === 4) { next.underline = true } | ||
| else if (p === 7) { next.invert = true } | ||
| else if (p === 9) { next.strikethrough = true } | ||
| else if (p === 22) { next.bold = false; next.dim = false } |
There was a problem hiding this comment.
SGR reset code 0 handled inside the parameter loop does not clear fg/bg because RESET_STYLE doesn't include those keys. For sequences like \x1b[0;31m, the background (and possibly foreground) from the prior style can incorrectly persist. Ensure the reset path explicitly clears fg and bg (or include them in RESET_STYLE).
| {source && <span className="shrink-0 text-(--term-fg-dim)">{source}</span>} | ||
|
|
||
| {/* Message */} | ||
| <span className={`min-w-0 wrap-break-word ${cls.message}`}>{message}</span> |
There was a problem hiding this comment.
wrap-break-word is not a standard Tailwind utility and isn't defined anywhere else in the repo, so the message text may not wrap as intended. Use an existing wrapping utility (e.g. break-words/break-all/[overflow-wrap:anywhere]) instead.
| <span className={`min-w-0 wrap-break-word ${cls.message}`}>{message}</span> | |
| <span className={`min-w-0 break-words ${cls.message}`}>{message}</span> |
#15
This pull request adds several new interactive demos to the terminal UI playground, showcasing advanced features such as ANSI color rendering, log filtering, log grouping, and structured log entries. It also refactors the playground page for clarity and better organization, updating section headers and integrating the new demos.
New demos and features:
AnsiDemo, which demonstrates realistic build/test output, ANSI color swatches, styles, 256-color palette, true-color gradients, and background colors using theTerminalAnsicomponent.FilterBarDemoto showcase interactive filtering of log entries by level and source, with a live count of visible entries.GroupDemo, demonstrating collapsible and variant-colored log groups for CI pipeline steps, including controlled open/close state.StructuredLogDemoto illustrate streaming logs as structured entries with badges, timestamps, and sources, complementing the existing string-based log demo. [1] [2]Playground page improvements:
page.tsxto import and display all new demos, refactors section headers for brevity, and adds descriptive text for new features. [1] [2] [3] [4] [5] [6]## Summary