Skip to content
57 changes: 57 additions & 0 deletions app/playground/filter-demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use client'

import { useMemo, useState } from 'react'
import {
Terminal,
TerminalCommand,
TerminalLog,
type LogEntry,
} from '@/components/terminal'
import {
TerminalFilterBar,
emptyFilterState,
filterEntries,
type FilterBarState,
} from '@/components/terminal-filter-bar'

const ALL_ENTRIES: LogEntry[] = [
{ id: '1', level: 'info', timestamp: '10:23:44', source: 'server', message: 'Application starting up...' },
{ id: '2', level: 'info', timestamp: '10:23:45', source: 'server', message: 'Listening on :3000' },
{ id: '3', level: 'debug', timestamp: '10:23:45', source: 'cache', message: 'HIT packages/[email protected]' },
{ id: '4', level: 'warn', timestamp: '10:23:46', source: 'cache', message: 'MISS @openknots/terminal-ui — fetching from registry' },
{ id: '5', level: 'info', timestamp: '10:23:47', source: 'build', message: 'Compiling 42 modules...' },
{ id: '6', level: 'debug', timestamp: '10:23:47', source: 'build', message: 'Resolved tsconfig paths in 4ms' },
{ id: '7', level: 'success', timestamp: '10:23:48', source: 'build', message: 'Compiled in 1.2s — 0 errors' },
{ id: '8', level: 'info', timestamp: '10:23:49', source: 'test', message: 'Running 24 unit tests...' },
{ id: '9', level: 'error', timestamp: '10:23:50', source: 'test', message: 'FAIL src/utils.test.ts — 1 snapshot mismatch' },
{ id: '10', level: 'info', timestamp: '10:23:50', source: 'test', message: 'Retrying with --updateSnapshot...' },
{ id: '11', level: 'success', timestamp: '10:23:51', source: 'test', message: '24 / 24 tests passed — coverage 94%' },
{ id: '12', level: 'info', timestamp: '10:23:52', source: 'deploy', message: 'Uploading artifacts to CDN...' },
{ id: '13', level: 'warn', timestamp: '10:23:52', source: 'deploy', message: 'Edge cache warm-up taking longer than 5s' },
{ id: '14', level: 'success', timestamp: '10:23:53', source: 'deploy', message: 'Published to production — https://example.app' },
]

const SOURCES = [...new Set(ALL_ENTRIES.map((e) => e.source!))]

export function FilterBarDemo() {
const [filter, setFilter] = useState<FilterBarState>(emptyFilterState)

const visible = useMemo(() => filterEntries(ALL_ENTRIES, filter), [filter])

return (
<div className="flex flex-col gap-2">
<TerminalFilterBar
state={filter}
onChange={setFilter}
sources={SOURCES}
/>
<Terminal title="pipeline.log">
<TerminalCommand>pnpm run ci</TerminalCommand>
<TerminalLog entries={visible} maxLines={20} />
</Terminal>
<p className="font-mono text-xs text-(--term-fg-dim)">
{visible.length} / {ALL_ENTRIES.length} entries shown
</p>
</div>
)
}
201 changes: 201 additions & 0 deletions app/playground/group-demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
'use client'

import { useState } from 'react'
import { Terminal, TerminalCommand, TerminalGroup, TerminalLogLine } from '@/components/terminal'

// ── Demo ──────────────────────────────────────────────────────────────────────

/**
* Playground demo for TerminalGroup.
*
* Shows several groups in a single terminal window:
* - A success group (collapsed by default) — install step
* - A warn group (open) — test step with a warning
* - An error group (open) — build step with failures
* - An info group (open) — controlled open/close via external button
*/
export function GroupDemo() {
// Controlled group example
const [deployOpen, setDeployOpen] = useState(true)

return (
<Terminal title="ci-pipeline.log">
<TerminalCommand>pnpm run ci</TerminalCommand>

{/* ── Install step: collapsed by default ── */}
<div className="px-3 pt-2">
<TerminalGroup
title="install"
summary="42 packages resolved from cache"
countLabel="0.8s"
variant="success"
defaultOpen={false}
>
<TerminalLogLine
level="debug"
timestamp="10:23:44"
source="npm"
message="Resolving dependency tree…"
/>
<TerminalLogLine
level="debug"
timestamp="10:23:44"
source="npm"
message="HIT [email protected]"
/>
<TerminalLogLine
level="debug"
timestamp="10:23:44"
source="npm"
message="HIT [email protected]"
/>
<TerminalLogLine
level="success"
timestamp="10:23:44"
source="npm"
message="42 packages installed from cache"
/>
</TerminalGroup>
</div>

{/* ── Lint step: info, collapsed by default ── */}
<div className="px-3 pt-1">
<TerminalGroup
title="lint"
summary="ruff check — no issues"
countLabel="0.2s"
variant="info"
defaultOpen={false}
>
<TerminalLogLine
level="info"
timestamp="10:23:45"
source="ruff"
message="Checking 38 files…"
/>
<TerminalLogLine
level="success"
timestamp="10:23:45"
source="ruff"
message="All checks passed"
/>
</TerminalGroup>
</div>

{/* ── Test step: warn, open ── */}
<div className="px-3 pt-1">
<TerminalGroup
title="test"
summary="23/24 passed · 1 snapshot mismatch"
countLabel="4.1s"
variant="warn"
defaultOpen
>
<TerminalLogLine
level="info"
timestamp="10:23:46"
source="jest"
message="Running 24 test suites…"
/>
<TerminalLogLine
level="success"
timestamp="10:23:49"
source="jest"
message="23 tests passed"
/>
<TerminalLogLine
level="warn"
timestamp="10:23:49"
source="jest"
message="FAIL src/utils.test.ts — 1 snapshot out of date"
/>
<TerminalLogLine
level="info"
timestamp="10:23:49"
source="jest"
message="Run with --updateSnapshot to fix"
/>
</TerminalGroup>
</div>

{/* ── Build step: error, open ── */}
<div className="px-3 pt-1">
<TerminalGroup
title="build"
summary="TypeScript compilation failed"
countLabel="3 errors"
variant="error"
defaultOpen
>
<TerminalLogLine
level="info"
timestamp="10:23:50"
source="tsc"
message="Compiling 42 modules…"
/>
<TerminalLogLine
level="error"
timestamp="10:23:51"
source="tsc"
message="src/api/handler.ts:14 — Property 'data' does not exist on type 'Response'"
/>
<TerminalLogLine
level="error"
timestamp="10:23:51"
source="tsc"
message="src/api/handler.ts:22 — Argument of type 'string' is not assignable to 'number'"
/>
<TerminalLogLine
level="error"
timestamp="10:23:51"
source="tsc"
message="src/utils/retry.ts:8 — Cannot find module '../types' or its type declarations"
/>
</TerminalGroup>
</div>

{/* ── Deploy step: controlled ── */}
<div className="px-3 pt-1 pb-3">
<TerminalGroup
title="deploy"
summary="Production rollout"
countLabel="2.3s"
variant="info"
open={deployOpen}
onOpenChange={setDeployOpen}
>
<TerminalLogLine
level="info"
timestamp="10:23:52"
source="k8s"
message="Rolling update → deployment/api-server"
/>
<TerminalLogLine
level="info"
timestamp="10:23:53"
source="k8s"
message="4/4 pods healthy"
/>
<TerminalLogLine
level="success"
timestamp="10:23:54"
source="k8s"
message="Deployment complete. Live at https://api.example.com"
/>
</TerminalGroup>

{/* External controlled toggle */}
<div className="mt-2 pl-1">
<button
type="button"
onClick={() => setDeployOpen((v) => !v)}
className="font-mono text-xs text-(--term-fg-dim) underline underline-offset-2
hover:text-(--term-fg) transition-colors"
>
{deployOpen ? '▲ collapse deploy (controlled)' : '▼ expand deploy (controlled)'}
</button>
</div>
</div>
</Terminal>
)
}
61 changes: 60 additions & 1 deletion app/playground/log-demo.tsx
Original file line number Diff line number Diff line change
@@ -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...',
Expand Down Expand Up @@ -38,3 +40,60 @@ export function LogDemo() {
</Terminal>
)
}

// ── 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/[email protected]' },
{ 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<LogEntry[]>([
{
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) }]
})
Comment on lines +81 to +85
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.

In StructuredLogDemo, the entries state grows without bound (you keep appending every 900ms and never trim). Even though TerminalLog only renders the last maxLines, the backing state will keep accumulating and can cause memory/perf issues if the playground stays open. Consider capping entries in the state update (e.g., keep only the last N entries).

Copilot uses AI. Check for mistakes.
}, 900)

return () => {
window.clearInterval(timer)
}
}, [])

return (
<Terminal title="pipeline.log">
<TerminalCommand>pnpm run ci</TerminalCommand>
<TerminalLog entries={entries} maxLines={8} autoScroll />
</Terminal>
)
}
Loading
Loading