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 .github/workflows/c-cpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- name: Configure
run: cmake -S ports/cpp -B ports/cpp/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=${{ matrix.shared }}
- run: cmake --build ports/cpp/build --config Release --parallel
- run: ctest --test-dir ports/cpp/build -C Release --output-on-failure
- run: ctest --test-dir ports/cpp/build -C Release --output-on-failure --timeout 120
- name: Install without touching system directories
run: cmake --install ports/cpp/build --config Release --prefix "${{ runner.temp }}/hqtui-install"
- name: Build separate installed consumers
Expand Down
25 changes: 21 additions & 4 deletions apps/demo/scripts/check-native-data.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
'python': ['mise', 'exec', 'python@3.12.13', '--', 'python', '-m', 'examples.dashboard'],
'zig': ['mise', 'exec', 'zig@0.16.0', '--', 'zig', 'build', 'run-dashboard', '--'],
}
if os.environ.get('HQTUI_CPP_DEMO'):
COMMANDS['cpp'] = [os.environ['HQTUI_CPP_DEMO']]
if os.environ.get('HQTUI_NATIVE_LANGUAGES'):
selected = os.environ['HQTUI_NATIVE_LANGUAGES'].split(',')
COMMANDS = {language: COMMANDS[language] for language in selected}


def main():
Expand All @@ -33,25 +38,37 @@ def main():
elif name=='journalctl':
if '-u' in sys.argv: print(fixture['ssh'])
elif name=='nvidia-smi': print('Test GPU, 25, 100, 1000, 55, 30')
elif name=='tail': print(fixture['http'])
elif name=='docker': print(json.dumps({'Names':'cpp-fixture-container','Image':'test:local','Status':'Up 1 hour'}))
'''
with tempfile.TemporaryDirectory(prefix='hqtui-native-data-') as directory:
for name in ('who', 'last', 'lastb', 'ss', 'journalctl', 'nvidia-smi'):
utilities = ['who', 'last', 'lastb', 'ss', 'journalctl', 'nvidia-smi']
if 'cpp' in COMMANDS:
utilities += ['tail', 'docker']
for name in utilities:
path = Path(directory) / name
path.write_text(utility)
path.chmod(0o700)
env = {**os.environ, 'PATH': directory + os.pathsep + os.environ['PATH'],
'HQTUI_TEST_TRAFFIC': json.dumps(fixture)}
for language, command in COMMANDS.items():
for screen, expected in [('dashboard', ['Sensors', 'Test GPU', '25%']),
('traffic', ['HTTPS', 'DNS', 'SSH', 'accepted', 'alice']),
('sessions', ['alice', 'eve', 'still', 'failed'])]:
cases = [('dashboard', ['Sensors', 'Test GPU', '25%']),
('traffic', ['HTTPS', 'DNS', 'SSH', 'accepted', 'alice']),
('sessions', ['alice', 'eve', 'still', 'failed'])]
if language == 'cpp':
cases[1][1].extend(['/health', '/missing', '/chat'])
cases.append(('services', ['cpp-fixture-container', 'test:local']))
cases.append(('network', ['127.0.0.1']))
for screen, expected in cases:
result = subprocess.run([*command, '--real', '--snapshot', '--screen', screen,
'--width', '240', '--height', '80'], cwd=ROOT / 'ports' / language,
env=env, capture_output=True, text=True, timeout=120)
assert result.returncode == 0, (language, result.stderr)
for label in expected:
assert label.lower() in result.stdout.lower(), (language, screen, f'missing {label}')
assert 'simulated' not in result.stdout.lower(), (language, screen)
if language == 'cpp':
assert 'token=secret' not in result.stdout, 'HTTP query secret leaked'
print(f'{language} {screen}: fixture utilities reached the real collector and rendered rows', flush=True)


Expand Down
28 changes: 18 additions & 10 deletions apps/demo/scripts/test-updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def git(self, *args):
return subprocess.check_output(["git", *args], cwd=self.repo, stderr=subprocess.DEVNULL, text=True).strip()

def commit(self, label):
(self.repo / "mise.toml").write_text('[tools]\nbun = "1.4.0"\npython = "3.12.13"\nrust = "1.97.1"\ngo = "1.26.0"\nzig = "0.16.0"\n')
(self.repo / "mise.toml").write_text('[tools]\nbun = "1.4.0"\npython = "3.12.13"\nrust = "1.97.1"\ngo = "1.26.0"\nzig = "0.16.0"\ncmake = "4.4.3"\n')
(self.repo / ".gitignore").write_text('__pycache__/\n')
path = self.repo / "ports/python/examples"
path.mkdir(parents=True, exist_ok=True)
Expand All @@ -49,7 +49,7 @@ def commit(self, label):
'import json, os, sys\n'
f'print(json.dumps({{"revision": {label!r}, "args": sys.argv[1:], "cwd": os.getcwd(), "tty": os.isatty(0)}}), flush=True)\n'
'if "--wait" in sys.argv: input()\n')
for language in ("rust", "go", "zig"):
for language in ("rust", "go", "zig", "cpp"):
(self.repo / "ports" / language).mkdir(exist_ok=True)
(self.repo / "ports" / language / "source").write_text(label)
self.git("add", ".")
Expand Down Expand Up @@ -100,7 +100,7 @@ def test_check_only_reports_fetched_commit(self):
self.assertFalse((self.root / "cache/v1/revisions").exists())

def test_invalid_language_and_missing_terminal_fail_before_fetch(self):
for args in (("--system", "cpp"), ("--system", "python")):
for args in (("--system", "c"), ("--system", "python")):
result = self.run_demo(*args)
self.assertNotEqual(result.returncode, 0)
self.assertFalse((self.root / "cache").exists())
Expand All @@ -124,36 +124,44 @@ def stub_compilers(self):
elif tool=="zig":
assert args[0:3]==["build","-Doptimize=ReleaseFast","--prefix"]
output=pathlib.Path(args[3])/"bin/hqtui-demo-zig"
elif tool=="cmake":
if args[0]=="-S":
source=pathlib.Path(args[1]); build=pathlib.Path(args[3])
build.mkdir(parents=True,exist_ok=True)
(build/"source").write_text((source/"source").read_text())
sys.exit(0)
assert args[0]=="--build" and args[2:4]==["--target","hqtui-demo-cpp"]
output=pathlib.Path(args[1])/"hqtui-demo-cpp"
else: raise AssertionError(tool)
label=pathlib.Path("source").read_text()
label=(pathlib.Path(args[1])/"source" if tool=="cmake" else pathlib.Path("source")).read_text()
output.parent.mkdir(parents=True,exist_ok=True)
output.write_text("#!/usr/bin/env python3\\nimport json,sys\\nprint(json.dumps(dict(revision="+repr(label)+",args=sys.argv[1:])))\\n")
output.chmod(0o700)
'''
for name in ("mise", "cargo", "go", "zig"):
for name in ("mise", "cargo", "go", "zig", "cmake"):
path = self.bin / name
path.write_text(code)
path.chmod(0o700)

def test_vanilla_and_mise_build_latest_and_reuse_only_same_revision(self):
self.stub_compilers()
for manager in ("--system", "--mise"):
for language in ("rust", "go", "zig"):
for language in ("rust", "go", "zig", "cpp"):
result = self.run_demo(manager, language, "--snapshot", "argument with spaces")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(json.loads(result.stdout)["revision"], "first")
self.assertEqual(json.loads(result.stdout)["args"][-1], "argument with spaces")
log = [json.loads(line) for line in (self.root / "tools.jsonl").read_text().splitlines()]
self.assertEqual(sum(row[0] in ("cargo", "go", "zig") for row in log), 6)
for language in ("rust", "go", "zig"):
self.assertEqual(sum(row[0] in ("cargo", "go", "zig") or row[:2]==["cmake","--build"] for row in log), 8)
for language in ("rust", "go", "zig", "cpp"):
self.assertEqual(self.run_demo("--mise", language, "--snapshot").returncode, 0)
self.commit("second")
for language in ("rust", "go", "zig"):
for language in ("rust", "go", "zig", "cpp"):
result = self.run_demo("--mise", language, "--snapshot")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(json.loads(result.stdout)["revision"], "second")
log = [json.loads(line) for line in (self.root / "tools.jsonl").read_text().splitlines()]
self.assertEqual(sum(row[0] in ("cargo", "go", "zig") for row in log), 9)
self.assertEqual(sum(row[0] in ("cargo", "go", "zig") or row[:2]==["cmake","--build"] for row in log), 12)

@unittest.skipUnless(os.name == "posix", "requires a controlling PTY")
def test_pipe_launcher_reattaches_keyboard_and_releases_build_lock(self):
Expand Down
26 changes: 17 additions & 9 deletions apps/web/app/docs/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { CLONE, LANGUAGES, PORTS } from "@/lib/languages";
export const dynamic = "force-dynamic";

export const metadata = { title: "Docs" };
const TYPESCRIPT = LANGUAGES.find(({ id }) => id === "typescript")!;

const SECTIONS = [
{ id: "languages", label: "Choose a language" },
Expand Down Expand Up @@ -64,9 +65,9 @@ export default async function Docs() {
<main className="min-w-0 flex-1">
<h1 className="text-4xl font-bold tracking-tight">Documentation</h1>
<P>
HQTUI builds terminal applications in TypeScript, Rust, Go, Python and Zig.
All five implementations provide differential rendering, Braille graphics,
truecolor, widgets and headless testing.
HQTUI builds terminal applications in TypeScript, Rust, Go, Python and Zig,
with a new native C++ ten-screen demo over the shared C rendering core.
The C++ library API remains experimental.
</P>

<H2 id="languages">Choose a language</H2>
Expand All @@ -76,19 +77,26 @@ export default async function Docs() {
))}
</div>
<P>
Rust, Go, Python and Zig are native ports with no JavaScript runtime requirement.
Rust, Go, Python, Zig and C++ demos need no JavaScript runtime.
Both the vanilla and mise commands below fetch latest main before running.
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
All five native dashboard commands launch ten-screen demos. Live metrics
currently require Linux; use --sim for generated sample data on other platforms.
All four native demos now use the TypeScript reference&apos;s ten screen layouts,
All five native demos use the TypeScript reference&apos;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>
C++ requires a C++17 compiler (GCC or Clang) and CMake 3.20+.
Its mise command supplies pinned CMake; you still need your platform&apos;s
C/C++ build tools. The interactive C++ terminal supports Linux/macOS;
live collection and exact 120-frame parity are currently tested on Linux.
The C-only demo and other mise language integrations are not ready yet.
</P>
<P>
Linux sensor panels now collect available hwmon temperatures, fans, voltage,
current and power, plus CPU clocks (including the /proc/cpuinfo fallback for VMs),
Expand Down Expand Up @@ -127,16 +135,16 @@ export default async function Docs() {
</div>
<P>For development only, you can still clone the monorepo and use its local commands; those do not auto-update:</P>
<CommandBlock className="mt-4" command={CLONE} label="Developer checkout (optional)" />
<P>In an updated checkout, mise run demo:rust also updates before running (likewise demo:go, demo:python, demo:zig and demo:typescript). Use demo-local:rust and the other demo-local tasks to work on your local edits without updating.</P>
<P>In an updated checkout, mise run demo:cpp updates before running (likewise demo:rust, demo:go, demo:python, demo:zig and demo:typescript). Use demo-local:cpp and the other demo-local tasks to work on your local edits without updating.</P>
<P>
<Link href="/blog/native-rust-go-python-zig" className="text-[#5fff87] underline underline-offset-4">Read how the ports share a conformance corpus</Link>.
The API guide below describes the TypeScript reference implementation.
</P>

<H2 id="install">Install TypeScript</H2>
<P>Try the full ten-screen demo with simulated data, without creating an app:</P>
<CommandBlock className="mt-3" command={LANGUAGES[0].interactiveDemo} label="TypeScript demo" />
<CommandBlock className="mt-3" command={LANGUAGES[0].miseDemo} label="TypeScript · mise" />
<CommandBlock className="mt-3" command={TYPESCRIPT.interactiveDemo} label="TypeScript demo" />
<CommandBlock className="mt-3" command={TYPESCRIPT.miseDemo} label="TypeScript · mise" />
<P>Omit <code>--sim</code> to use real system metrics. To build your own app, install the library:</P>
<Code className="mt-4" code={`bun add @profullstack/hqtui # Bun is the default runtime
npm add @profullstack/hqtui # Node 22.6+ works unchanged`} />
Expand Down
17 changes: 9 additions & 8 deletions apps/web/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const DASHBOARD = `app.render(({ ui }) => {
});`;

/** Fetch current main into a private cache, build, then run. */
const DEMO = LANGUAGES[0].demo;
const DEMO = LANGUAGES.find(({ id }) => id === "typescript")!.demo;

const TESTING = `import { renderToScreen } from "@profullstack/hqtui";

Expand Down Expand Up @@ -144,12 +144,12 @@ export default async function Home() {
priority
className="mx-auto mb-6 w-[22rem] max-w-full sm:w-[30rem]"
/>
<h1 className="sr-only">HQTUI — High Quality Terminal UI for TypeScript, Rust, Go, Python and Zig</h1>
<h1 className="sr-only">HQTUI — High Quality Terminal UI for TypeScript, Rust, Go, Python, Zig and C++</h1>
<p className="mb-6 text-balance text-lg text-white/70">
High Quality Terminal UI for TypeScript, Rust, Go, Python and Zig
High Quality Terminal UI for TypeScript, Rust, Go, Python, Zig and C++
</p>
<Badge variant="secondary" className="mb-5 font-mono text-xs">
v0.1.12 · 5 languages · MIT
v0.1.12 · 6 language demos · MIT
</Badge>
<p className="text-balance text-3xl font-bold tracking-tight sm:text-5xl">
Terminal dashboards that
Expand Down Expand Up @@ -227,17 +227,18 @@ export default async function Home() {
</section>

<section id="languages" className="mx-auto max-w-7xl scroll-mt-14 px-4 pt-20 sm:px-6">
<h2 className="text-3xl font-bold tracking-tight">One terminal UI, five languages</h2>
<h2 className="text-3xl font-bold tracking-tight">One terminal UI, six language demos</h2>
<p className="mt-3 max-w-3xl text-white/60">
Build in TypeScript or use a native Rust, Go, Python or Zig implementation.
Each port includes the app loop, widgets, themes, input handling and a headless renderer.
The four native ports need no JavaScript runtime and use only their standard libraries.
C++ now has a native ten-screen demo over the shared C rendering core, with an experimental library API.
The native demos need no JavaScript runtime.
</p>
<div className="mt-6 max-w-2xl">
<p className="text-sm text-white/50">
Every command checks latest main, builds in a private cache, and runs it.
No manual clone or pull. Vanilla uses your installed toolchain; mise uses
the pinned toolchain. Git and curl are required; the first build takes longer.
the pinned toolchain. C++ uses pinned CMake with your installed GCC/Clang.
Git and curl are required; the first build takes longer.
</p>
</div>

Expand Down
13 changes: 12 additions & 1 deletion apps/web/lib/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,22 @@ export type Language = {
export const CLONE = "git clone https://github.com/profullstack/hqtui";
export const LAUNCHER = "https://hqtui.com/demo.sh";
export function latestDemo(language: string, mise = false): string {
if (!["typescript", "rust", "go", "python", "zig"].includes(language)) throw new Error("Unsupported demo language");
if (!["typescript", "rust", "go", "python", "zig", "cpp"].includes(language)) throw new Error("Unsupported demo language");
return `curl -fsSL ${LAUNCHER} | sh -s -- --${mise ? "mise" : "system"} ${language}`;
}

export const LANGUAGES: readonly Language[] = [
{
name: "C++",
id: "cpp",
description: "Native C++17 demo over the shared C renderer. Experimental library API; requires GCC/Clang and CMake.",
href: "/docs#cpp",
demo: latestDemo("cpp"),
snapshotDemo: `${latestDemo("cpp")} --snapshot`,
interactiveDemo: latestDemo("cpp"),
miseDemo: latestDemo("cpp", true),
native: true,
},
{
name: "TypeScript",
id: "typescript",
Expand Down
Loading