Shell#

A dashboard frame with exactly one job: own the chrome, and tell the body what is left.

Import#

TSX
import { Shell } from '@sigx/terminal';

Usage#

The body is a scoped slot receiving { width, height } — the content box inside the frame:

TSX
import { component, defineApp, Shell, DataTable } from '@sigx/terminal';

const App = component(() => {
    return () => (
        <Shell
            title="sigx actors"
            version="0.1.0"
            tabs={TABS}
            activeTab={tab.value}
            status={STATUS}
        >
            {(pane) => (
                <DataTable width={pane.width} height={pane.height} columns={COLS} rows={rows.value} />
            )}
        </Shell>
    );
});

That slot is the whole point of the component. getTerminalSize() reports the terminal; what a body needs is that minus whatever chrome the frame drew — which is a private detail of the frame's own layout. Without it, every caller hardcodes a guess (rows - 12, rows - 13, - 4) that goes wrong the moment the frame changes shape.

A budget, not a reservation#

pane is the maximum a body may emit, not an enforced box.

The renderer's layout is content-driven end to end — there is no clipping primitive and no way to measure an opaque child — so <Shell> cannot hold the box on the body's behalf. A body that emits fewer rows leaves the frame short; one that emits more pushes the footer off the bottom. (The renderer clamps a fullscreen frame to the viewport, so the damage stops there rather than shearing the screen.)

Comply by fitting content to the pane, which fitLines does in one call:

TSX
{(pane) => <text>{fitLines(visible, pane).join('\n')}</text>}

What it deliberately does not do#

No state, no key handling, no lifecycle. Tab switching, a command palette, a view stack, single-key shortcuts, exit ordering and the non-TTY fallback are each a few lines in the app, and every app wants to vary them — bundling them is what turns a frame into a cage.

activeTab is a plain value rather than a Model for the same reason: nothing here ever writes it. It marks the current chip; the app owns the signal and switches the body itself.

Props#

PropTypeNotes
titlestringframe title
versionstringshown beside the title
subtitlestringsecondary line
header'boxed' | 'gradient' | 'plain'header style
tabsTabOption<string>[]the tab strip
activeTabstringwhich chip is marked current — read-only to the frame
body'boxed' | 'plain'body frame style
bodyTitlestringtitle on the body frame
statusStatusItem[]the status line
hintsKeyHint[]the key-hint footer

Slot#

SlotScopeNotes
defaultShellPane{ width, height }the content box inside the frame

See also#

  • DataTable — takes width/height directly.
  • Card — a titled panel, without the frame arithmetic.