Skip to content

Feat/terminal ansi#26

Open
himax12 wants to merge 10 commits intoclawgreen:mainfrom
himax12:feat/terminal-ansi
Open

Feat/terminal ansi#26
himax12 wants to merge 10 commits intoclawgreen:mainfrom
himax12:feat/terminal-ansi

Conversation

@himax12
Copy link
Copy Markdown

@himax12 himax12 commented Mar 1, 2026

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

  • ANSI color and style demo: Introduces AnsiDemo, which demonstrates realistic build/test output, ANSI color swatches, styles, 256-color palette, true-color gradients, and background colors using the TerminalAnsi component.
  • Log filtering demo: Adds FilterBarDemo to showcase interactive filtering of log entries by level and source, with a live count of visible entries.
  • Log grouping demo: Implements GroupDemo, demonstrating collapsible and variant-colored log groups for CI pipeline steps, including controlled open/close state.
  • Structured log streaming demo: Adds StructuredLogDemo to illustrate streaming logs as structured entries with badges, timestamps, and sources, complementing the existing string-based log demo. [1] [2]

Playground page improvements:

  • Integration and organization: Updates page.tsx to 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

himax12 added 10 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:14
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

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, and TerminalMarker.
  • Extends TerminalLog to support structured entries (entries) in addition to string lines.
  • Refactors app/playground/page.tsx to 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'
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 demo, which will trip lint rules like no-unused-vars in many configs. Remove the unused import to keep the file clean.

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

Copilot uses AI. Check for mistakes.
Comment on lines +300 to +309
{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.

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).

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

Copilot uses AI. Check for mistakes.
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'
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.

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.

Suggested change
export { TerminalFeed, type TerminalFeedProps, type FeedEntry } from './terminal-feed'

Copilot uses AI. Check for mistakes.
import { TerminalProgress } from '@/components/terminal-progress'
import { LogDemo } from './log-demo'
import { LogDemo, StructuredLogDemo } from './log-demo'
import { FeedDemo } from './feed-demo'
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.

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.

Suggested change
import { FeedDemo } from './feed-demo'

Copilot uses AI. Check for mistakes.
Comment on lines +122 to +129
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 }
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.

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).

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 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.

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.
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