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
This PR expands the terminal UI component library with new “feed primitives” (stack traces, structured log lines, search/filter UI, collapsible groups, JSON rows) and wires them into the playground to provide interactive demos for each feature.
Changes:
- Added new terminal components:
TerminalStackTrace,TerminalSearch(+useTerminalSearch),TerminalFilterBar(+filterEntries),TerminalGroup,TerminalMarker,TerminalJsonLine, andTerminalLogLine. - Enhanced
TerminalLogto support structured log entries (via a newentriesprop) while keeping stringlinessupport. - Updated the playground page to showcase the new components via dedicated demo modules.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| components/terminal.tsx | Reformats Terminal and expands the barrel exports to include new terminal primitives. |
| components/terminal-stack-trace.tsx | Introduces a foldable stack trace renderer with node_modules filtering and per-frame toggles. |
| components/terminal-search.tsx | Adds TerminalSearch UI and useTerminalSearch hook for in-feed search/navigation. |
| components/terminal-marker.tsx | Adds a “phase marker” row for visually separating log phases. |
| components/terminal-log.tsx | Adds structured entries mode (via TerminalLogLine) and updates styling/scroll behavior. |
| components/terminal-log-line.tsx | Adds a structured log row primitive with level badge, timestamp, and source. |
| components/terminal-json-line.tsx | Adds a collapsible JSON payload row with Prism highlighting and invalid handling. |
| components/terminal-group.tsx | Adds a collapsible group component for command/output sections. |
| components/terminal-filter-bar.tsx | Adds a controlled filter bar plus pure filtering helper for LogEntry[]. |
| app/playground/stack-trace-demo.tsx | Adds a playground demo for TerminalStackTrace. |
| app/playground/search-demo.tsx | Adds a playground demo for TerminalSearch + useTerminalSearch. |
| app/playground/page.tsx | Adds new sections and demos for the newly introduced components. |
| app/playground/log-demo.tsx | Adds a structured-log demo alongside the existing string-log demo. |
| app/playground/group-demo.tsx | Adds a playground demo for TerminalGroup. |
| app/playground/filter-demo.tsx | Adds a playground demo for TerminalFilterBar + filterEntries. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| {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.
frames.indexOf(frame) inside the render loop makes this section O(n²) and relies on object identity for correctness. Prefer iterating with the original index (e.g., map frames to {frame, index} once, then filter) so keys/gutter numbers are stable without repeated searches.
| 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 file, which will typically fail linting (unused import) and adds noise. Remove the unused import or switch the demo to use TerminalLog if that was the intention.
| import { TerminalLog } from '@/components/terminal-log' |
| {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 isn’t a standard Tailwind utility, so it will be ignored and long log messages may not wrap as intended. Use a supported wrapping utility (e.g., break-words / break-all) or an established project class instead.
| <span className={`min-w-0 wrap-break-word ${cls.message}`}>{message}</span> | |
| <span className={`min-w-0 break-words ${cls.message}`}>{message}</span> |
| aria-live="polite" | ||
| aria-atomic="true" |
There was a problem hiding this comment.
aria-live/aria-atomic are set on the <input>, but live-region attributes aren’t valid/meaningful on form controls and can cause confusing announcements in some screen readers. Move live announcements to a dedicated status element (e.g., the counter/status span) and drop these attributes from the input.
| aria-live="polite" | |
| aria-atomic="true" |
| return { value: payload, pretty, invalid: false, summary: summarise(payload) } | ||
| } catch { | ||
| return { value: payload, pretty: String(payload), invalid: true, summary: String(payload) } |
There was a problem hiding this comment.
JSON.stringify(payload, null, 2) can return undefined for values like undefined, functions, and symbols. That currently leads to an empty expanded view (and Prism highlighting attempts on a non-string). Ensure pretty is always a string (fallback to String(payload)), and consider marking such cases as invalid so the UI indicates non-JSON-serialisable values.
| return { value: payload, pretty, invalid: false, summary: summarise(payload) } | |
| } catch { | |
| return { value: payload, pretty: String(payload), invalid: true, summary: String(payload) } | |
| if (pretty === undefined) { | |
| const fallback = String(payload) | |
| return { value: payload, pretty: fallback, invalid: true, summary: fallback } | |
| } | |
| return { value: payload, pretty, invalid: false, summary: summarise(payload) } | |
| } catch { | |
| const fallback = String(payload) | |
| return { value: payload, pretty: fallback, invalid: true, summary: fallback } |
| <button | ||
| type="button" | ||
| onClick={toggle} | ||
| onKeyDown={handleKeyDown} | ||
| aria-label={`${open ? 'Collapse' : 'Expand'} frame ${index}: ${fnLabel}`} | ||
| aria-expanded={open} | ||
| className="shrink-0 w-4 text-center text-(--term-fg-dim) hover:text-(--term-fg) transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-(--term-blue) rounded-sm" | ||
| > | ||
| {hasLocation ? (open ? '▾' : '▸') : '·'} | ||
| </button> |
There was a problem hiding this comment.
Frames with no parsed location still render a clickable toggle button with aria-expanded, but toggling doesn’t reveal/hide anything (the chevron stays · and no details render). For unparsed frames, render a non-interactive icon/element or disable the button and omit aria-expanded to avoid misleading keyboard/screen-reader behavior.
| <button | |
| type="button" | |
| onClick={toggle} | |
| onKeyDown={handleKeyDown} | |
| aria-label={`${open ? 'Collapse' : 'Expand'} frame ${index}: ${fnLabel}`} | |
| aria-expanded={open} | |
| className="shrink-0 w-4 text-center text-(--term-fg-dim) hover:text-(--term-fg) transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-(--term-blue) rounded-sm" | |
| > | |
| {hasLocation ? (open ? '▾' : '▸') : '·'} | |
| </button> | |
| {hasLocation ? ( | |
| <button | |
| type="button" | |
| onClick={toggle} | |
| onKeyDown={handleKeyDown} | |
| aria-label={`${open ? 'Collapse' : 'Expand'} frame ${index}: ${fnLabel}`} | |
| aria-expanded={open} | |
| className="shrink-0 w-4 text-center text-(--term-fg-dim) hover:text-(--term-fg) transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-(--term-blue) rounded-sm" | |
| > | |
| {open ? '▾' : '▸'} | |
| </button> | |
| ) : ( | |
| <span | |
| className="shrink-0 w-4 text-center text-(--term-fg-dim)" | |
| aria-hidden="true" | |
| > | |
| · | |
| </span> | |
| )} |
#14
This pull request adds several new interactive demo components to the playground for the terminal UI library, significantly expanding the showcase and testing capabilities for features like structured logs, filtering, grouping, searching, and more. The main
PlaygroundPageis updated to include these demos, and new files are introduced for each feature. These changes improve both developer experience and documentation by providing live examples of complex terminal features.New demo components for terminal features:
FilterBarDemoto demonstrate log filtering by source and level, including a filter bar UI and filtered log view. (app/playground/filter-demo.tsx)GroupDemoto showcase collapsible terminal command/output groups with variant accents, summaries, and controlled open/close state. (app/playground/group-demo.tsx)StructuredLogDemoto illustrate streaming structured log entries with badges, timestamps, and sources, alongside the existing string log demo. (app/playground/log-demo.tsx) [1] [2]Enhancements to playground layout and navigation:
PlaygroundPageto import and display all new demos, adding descriptive sections for each feature, including TerminalMarker, TerminalLogLine, TerminalGroup, TerminalSearch, and more. (app/playground/page.tsx) [1] [2] [3] [4] [5] [6] [7] [8]Improved documentation and developer experience:
app/playground/page.tsx) [1] [2]These changes provide a comprehensive and interactive playground for all major features of the terminal UI library, making it easier for developers to explore, test, and document functionality.