Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/benchmark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"start": "bun src/main.ts"
},
"dependencies": {
"@profullstack/hqtui": "^0.1.11"
"@profullstack/hqtui": "^0.1.12"
}
}
4 changes: 2 additions & 2 deletions apps/demo/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@profullstack/hqtui-demo",
"version": "0.1.11",
"version": "0.1.12",
"description": "The HQTUI reference dashboard: a btop-grade terminal system monitor. Runs on real system metrics or a deterministic simulation.",
"license": "MIT",
"type": "module",
Expand All @@ -27,7 +27,7 @@
"audit:scroll": "bun scripts/scrollaudit.ts"
},
"dependencies": {
"@profullstack/hqtui": "^0.1.11"
"@profullstack/hqtui": "^0.1.12"
},
"publishConfig": {
"access": "public"
Expand Down
4 changes: 2 additions & 2 deletions apps/demo/scripts/native-parity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ for (const theme of ["dark", "dracula", "nord"]) {
}
}
}
writeFileSync(new URL("ports/rust/demo/parity.json",root),JSON.stringify(cases,null,2)+"\n");
console.log(`Generated ${cases.length} TypeScript dashboard reference cases.`);
writeFileSync(new URL("ports/conformance/fixtures/demo-parity.json",root),JSON.stringify(cases,null,2)+"\n");
console.log(`Generated ${cases.length} shared TypeScript screen reference cases.`);
2 changes: 1 addition & 1 deletion apps/demo/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function parseArgs(argv: string[]): Options {
case "-h":
case "--help": printHelp(); process.exit(0);
case "-v":
case "--version": console.log("hqtui-demo 0.1.11"); process.exit(0);
case "--version": console.log("hqtui-demo 0.1.12"); process.exit(0);
}
}
return options;
Expand Down
9 changes: 6 additions & 3 deletions apps/web/app/docs/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,12 @@ export default async function Docs() {
They work from any directory and never switch branches, reset, or pull in your checkout.
All four dashboard commands now launch ten-screen native demos. Live metrics
currently require Linux; use --sim for generated sample data on other platforms.
Rust screen bodies pass 120 cell-by-cell TypeScript comparisons, but live-data
collection and interaction parity are incomplete. Go, Python and Zig also still
need layout-parity work. Use 1–9 / 0 or Tab to change screens and q to quit.
All four native demos now use the TypeScript reference's ten screen layouts,
including its responsive dashboard, detailed telemetry tabs and widget showcases.
Each port is checked against 120 shared TypeScript reference frames across four
terminal sizes and three themes. Live-data availability still depends on the host
and permissions; full cross-platform collection and interaction parity is not claimed.
Use 1–9 / 0 or Tab to change screens and q to quit.
Headless screenshots work without a TTY. Zig requires version 0.16.
</P>
<P>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export default async function Home() {
High Quality Terminal UI for TypeScript, Rust, Go, Python and Zig
</p>
<Badge variant="secondary" className="mb-5 font-mono text-xs">
v0.1.11 · 5 languages · MIT
v0.1.12 · 5 languages · MIT
</Badge>
<p className="text-balance text-3xl font-bold tracking-tight sm:text-5xl">
Terminal dashboards that
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
"dependencies": {
"@base-ui/react": "1.7.0",
"@profullstack/hqtui": "^0.1.11",
"@profullstack/hqtui": "^0.1.12",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"lucide-react": "1.37.0",
Expand Down
8 changes: 4 additions & 4 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"version": "0.1.0",
"type": "module",
"dependencies": {
"@profullstack/hqtui": "^0.1.11"
"@profullstack/hqtui": "^0.1.12"
}
}
2 changes: 1 addition & 1 deletion packages/hqtui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@profullstack/hqtui",
"version": "0.1.11",
"version": "0.1.12",
"description": "High Quality Terminal UI for TypeScript. btop-grade dashboards with a one-import API, dark by default, zero runtime dependencies.",
"license": "MIT",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion packages/hqtui/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { detectCapabilities } from "./capabilities.ts";
import { themeList, themes } from "./theme.ts";
import { BrailleCanvas } from "./graphics/braille.ts";

const VERSION = "0.1.11";
const VERSION = "0.1.12";

function help(): void {
console.log(`hqtui ${VERSION} — High Quality Terminal UI for TypeScript
Expand Down
2 changes: 1 addition & 1 deletion ports/c/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.20)
project(hqtui_c VERSION 0.1.11 LANGUAGES C)
project(hqtui_c VERSION 0.1.12 LANGUAGES C)
include(GNUInstallDirs)
include(CTest)
option(HQTUI_SANITIZE "Build with address/undefined behavior sanitizers" OFF)
Expand Down
36 changes: 36 additions & 0 deletions ports/conformance/DEMO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Demo visual contract

`fixtures/demo-parity.json` is generated by the real TypeScript screen functions,
not by a native port or by replaying screenshots. Regenerate it from the repo root:

```sh
mise exec bun@1.4.0 -- bun apps/demo/scripts/native-parity.ts
```

The 120 cases cover all ten screens at 80×30, 120×40, 168×46 and 200×60 in dark,
Dracula and Nord. Every native implementation renders the same source sample
using its own library. Tests compare glyphs, foreground/background colors and
attributes. No JavaScript renderer or snapshot playback ships in a native demo.

Rust, Python and Zig require exact cell hashes. Go also checks exact colors and
attributes, allowing at most one Braille dot in the Components gauge: Go's cosine
and the reference runtime differ by one ULP at 2π/3, which straddles a half-pixel.
That tolerance cannot hide spacing, color, text or other-screen differences.

The reference frames check screen bodies, not every possible interaction or live
system sample. Native suites additionally exercise keyboard/overlay priority,
scroll offsets, mouse selection, real-source parsing, real/simulated CLI launches
and terminal restoration. Real missing data must stay missing, never be replaced
with simulated rows. Traffic protocol/direction and HTTP-rate estimates remain
identified as estimates.

Run the complete acceptance checks with the pinned toolchains:

```sh
mise run test:go
mise run test:python
mise run test:zig
mise exec rust@1.97.1 -- cargo test --manifest-path ports/rust/Cargo.toml --example dashboard
mise exec python@3.12.13 -- python apps/demo/scripts/check-native-data.py
mise exec python@3.12.13 -- python apps/demo/scripts/check-native-commands.py
```
2 changes: 1 addition & 1 deletion ports/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.20)
project(hqtui_cpp VERSION 0.1.11 LANGUAGES C CXX)
project(hqtui_cpp VERSION 0.1.12 LANGUAGES C CXX)
include(CTest)
include(GNUInstallDirs)
if(NOT TARGET hqtui::c)
Expand Down
110 changes: 110 additions & 0 deletions ports/go/internal/demo/components.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package demo

import ui "github.com/profullstack/hqtui/ports/go"

func (s *state) scrollHandlers(name string, pane *pane) ui.ScrollHandlers {
return ui.ScrollHandlers{OnFocus: s.action(func() { s.focused[s.screen] = name }), OnScroll: func(d int) {
if !s.overlay() {
s.focused[s.screen] = name
pane.move(d)
}
}, OnSelectRow: func(i int) {
if !s.overlay() {
pane.selected = clamp(pane.offset+i, 0, pane.total-1)
}
}}
}
func processTree() []ui.TreeNode {
node := func(name, cpu, mem string, children ...ui.TreeNode) ui.TreeNode {
return ui.TreeNode{Label: name, Values: []ui.TreeValue{{Text: cpu, Width: 6}, {Text: mem, Width: 6}}, Children: children}
}
return []ui.TreeNode{node("systemd", "1.3", "0.1", node("bash", "0.1", "0.2"), node("bun", "32.8", "4.2", node("bun:worker", "12.4", "1.8"), node("bun:worker", "8.7", "1.3")), node("node", "18.1", "2.1", node("node:worker", "6.1", "0.8")), node("postgres", "6.7", "1.8"))}
}
func (s *state) components(p *ui.Container) {
t := p.Theme()
c, m, n := obj(s.sample["cpu"]), obj(s.sample["memory"]), obj(s.sample["network"])
row(p, ui.Fr(1), 1, func(r *ui.Container) {
r.Column(ui.ColumnOptions{Layout: ui.Layout{Gap: 1}}, func(left *ui.Container) {
left.Panel(ui.PanelOptions{Title: "Buttons & Inputs", Layout: fixed(13)}, func(p *ui.Container) {
row(p, ui.Cells(1), 1, func(r *ui.Container) {
for i, label := range []string{"Primary", "Success", "Warning", "Danger"} {
width := []int{11, 11, 11, 10}[i]
var action func()
if i == 0 {
action = s.action(func() { s.modal = true })
}
r.Button(ui.ButtonOptions{Label: label, Width: width, Variant: []ui.ButtonVariant{ui.ButtonPrimary, ui.ButtonSuccess, ui.ButtonWarning, ui.ButtonDanger}[i]}, action, fixed(width))
}
r.Spacer(ui.Fill())
})
p.Spacer(ui.Cells(1))
row(p, ui.Cells(1), 2, func(r *ui.Container) {
options := []string{"Dark", "Dracula", "Nord", "Tokyo Night"}
r.Select(ui.SelectOptions{Value: options[s.selectIndex%4], Width: 20, Open: s.selectOpen, Options: options, SelectedIndex: s.selectIndex}, s.action(func() { s.selectOpen = !s.selectOpen }), fixed(20))
r.Checkbox(ui.CheckboxOptions{Label: "Toggle", Checked: s.toggle, Variant: ui.CheckboxToggle}, s.action(func() { s.toggle = !s.toggle }), fixed(12))
r.Checkbox(ui.CheckboxOptions{Label: "Checkbox", Checked: s.checked}, s.action(func() { s.checked = !s.checked }), fixed(14))
r.Spacer(ui.Fill())
})
p.Spacer(ui.Cells(1))
p.TextInput(ui.TextInputOptions{Label: "Search", Value: s.input, Placeholder: "type to filter…", Focused: s.editing}, fixed(1))
p.Spacer(ui.Cells(1))
p.Meter(ui.MeterOptions{Label: "Slider", Value: s.slider, Heat: ref(false), Color: &t.Primary})
p.Progress(ui.ProgressOptions{Label: "Progress", Value: 37, Max: ref(120.), ShowCount: true})
})
left.Panel(ui.PanelOptions{Title: "Table Widget"}, func(p *ui.Container) {
rows := []any{}
for _, r := range [][4]string{{"src", "4.2 KB", "dir", "2m ago"}, {"test", "1.1 KB", "dir", "5m ago"}, {"package.json", "1.2 KB", "file", "10m ago"}, {"README.md", "3.4 KB", "file", "1h ago"}, {"bun.lockb", "12 KB", "file", "1h ago"}} {
rows = append(rows, object{"name": r[0], "size": r[1], "type": r[2], "modified": r[3]})
}
s.dataTable(p, "components.files", rows, []dataColumn{dc("name", "Name", 0, 10, &t.Primary, false), dc("size", "Size", 9, 0, nil, true), dc("type", "Type", 6, 0, nil, false), dc("modified", "Modified", 10, 0, &t.Muted, true)}, true, false, true)
})
left.Panel(ui.PanelOptions{Title: "Log Viewer", Layout: fixed(11)}, func(p *ui.Container) {
data := arr(s.sample["logs"])
pane := s.pane("components.logs", len(data))
pane.log = true
entries := []ui.LogEntry{}
for _, v := range data {
d := obj(v)
entries = append(entries, ui.LogEntry{Time: scalar(d["time"]), Level: scalar(d["level"]), Message: scalar(d["message"]), Meta: "{" + scalar(d["meta"]) + "}"})
}
p.Log(ui.LogOptions{Entries: entries, FromEnd: pane.offset, Scrollbar: true}, ui.ScrollHandlers{OnFocus: s.action(func() { s.focused[s.screen] = "components.logs" }), OnScroll: func(d int) {
if !s.overlay() {
s.focused[s.screen] = "components.logs"
pane.offset = clamp(pane.offset-d, 0, len(data)-1)
}
}})
})
})
r.Column(ui.ColumnOptions{Layout: ui.Layout{Gap: 1}}, func(right *ui.Container) {
right.Panel(ui.PanelOptions{Title: "Process Tree", Layout: fixed(13)}, func(p *ui.Container) {
row(p, ui.Cells(1), 0, func(r *ui.Container) {
r.StyledText("Name", ui.TextStyle{Fg: &t.Muted, Bold: true})
r.StyledText("CPU% MEM%", ui.TextStyle{Fg: &t.Muted, Bold: true, Align: ui.AlignRight})
})
pane := s.pane("components.tree", 8)
p.Tree(ui.TreeOptions{Nodes: processTree(), Selected: &pane.selected, Offset: &pane.offset, FollowSelection: true}, s.scrollHandlers("components.tree", pane))
})
right.Panel(ui.PanelOptions{Title: "Sparklines & Gauges", Layout: fixed(12)}, func(p *ui.Container) {
p.Sparkline(ui.SparklineWidgetOptions{Label: "CPU ", Values: values(c["history"]), Text: percent(num(c["total"])), Color: &t.Success})
p.Sparkline(ui.SparklineWidgetOptions{Label: "Mem ", Values: values(m["history"]), Text: percent(num(m["used"]) / num(m["total"])), Color: &t.Warning})
p.Sparkline(ui.SparklineWidgetOptions{Label: "Net ", Values: values(n["downHistory"]), Text: bytes(num(n["downRate"]), 2) + "/s", Color: &t.Primary})
p.Spacer(ui.Cells(1))
row(p, ui.Fr(1), 2, func(r *ui.Container) {
r.Gauge(ui.GaugeOptions{Value: num(c["total"]), Label: percent(num(c["total"]))})
r.Donut(ui.DonutOptions{Segments: []ui.DonutSegment{{Value: num(m["used"]), Color: &t.Primary, Label: "Used"}, {Value: num(m["available"]), Color: &t.Warning, Label: "Free"}}})
})
})
right.Panel(ui.PanelOptions{Title: "Lists & Badges"}, func(p *ui.Container) {
row(p, ui.Cells(1), 1, func(r *ui.Container) {
r.Badge(ui.BadgeOptions{Text: "active", Color: &t.Success}, fixed(10))
r.Badge(ui.BadgeOptions{Text: "idle", Color: &t.Warning, Variant: ui.BadgeSubtle}, fixed(8))
r.Badge(ui.BadgeOptions{Text: "failed", Color: &t.Danger, Variant: ui.BadgeOutline}, fixed(10))
r.Spacer(ui.Fill())
})
p.Spacer(ui.Cells(1))
pane := s.pane("components.list", 4)
p.List(ui.ListOptions{Items: []ui.ListItem{{Label: "apps/demo", Color: &t.Primary}, {Label: "packages/hqtui"}, {Label: "apps/web"}, {Label: "docs"}}, Selected: &pane.selected, Offset: &pane.offset, FollowSelection: true, Bullet: "▸", Scrollbar: true}, s.scrollHandlers("components.list", pane))
})
})
})
}
Loading