Skip to content

Feat/terminal stack trace#25

Open
himax12 wants to merge 9 commits intoclawgreen:mainfrom
himax12:feat/terminal-stack-trace
Open

Feat/terminal stack trace#25
himax12 wants to merge 9 commits intoclawgreen:mainfrom
himax12:feat/terminal-stack-trace

Conversation

@himax12
Copy link
Copy Markdown

@himax12 himax12 commented Mar 1, 2026

#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 PlaygroundPage is 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:

  • Added FilterBarDemo to demonstrate log filtering by source and level, including a filter bar UI and filtered log view. (app/playground/filter-demo.tsx)
  • Added GroupDemo to showcase collapsible terminal command/output groups with variant accents, summaries, and controlled open/close state. (app/playground/group-demo.tsx)
  • Added StructuredLogDemo to 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:

  • Updated PlaygroundPage to 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:

  • Each new section in the playground includes concise descriptions, usage notes, and live interactive examples to help developers understand and test terminal features in isolation. (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.

himax12 added 9 commits March 2, 2026 02:34
…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
Copilot AI review requested due to automatic review settings March 1, 2026 22:07
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, and TerminalLogLine.
  • Enhanced TerminalLog to support structured log entries (via a new entries prop) while keeping string lines support.
  • 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.

Comment on lines +300 to +306
{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>
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
useTerminalSearch,
type LogEntry,
} from '@/components/terminal'
import { TerminalLog } from '@/components/terminal-log'
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
import { TerminalLog } from '@/components/terminal-log'

Copilot uses AI. Check for mistakes.
{source && <span className="shrink-0 text-(--term-fg-dim)">{source}</span>}

{/* Message */}
<span className={`min-w-0 wrap-break-word ${cls.message}`}>{message}</span>
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
<span className={`min-w-0 wrap-break-word ${cls.message}`}>{message}</span>
<span className={`min-w-0 break-words ${cls.message}`}>{message}</span>

Copilot uses AI. Check for mistakes.
Comment on lines +229 to +230
aria-live="polite"
aria-atomic="true"
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
aria-live="polite"
aria-atomic="true"

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +60
return { value: payload, pretty, invalid: false, summary: summarise(payload) }
} catch {
return { value: payload, pretty: String(payload), invalid: true, summary: String(payload) }
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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 }

Copilot uses AI. Check for mistakes.
Comment on lines +152 to +161
<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>
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
<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>
)}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants