@octanejs/opentui 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dominic Gannaway
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 opentui
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # `@octanejs/opentui`
2
+
3
+ An experimental Octane renderer for [OpenTUI](https://github.com/anomalyco/opentui),
4
+ tracking the public binding surface of `@opentui/react@0.5.8` without embedding
5
+ React Reconciler. Octane owns component execution, hooks, context, scheduling,
6
+ errors, refs, and effects; `@opentui/core` continues to own terminal layout,
7
+ input, rendering, and native resources.
8
+
9
+ ## Runtime requirements
10
+
11
+ OpenTUI 0.5.8 requires either Bun 1.3 or newer, or Node.js 26.4 or newer with
12
+ `--experimental-ffi`. This package does not add a JavaScript fallback for
13
+ OpenTUI's native Zig renderer.
14
+
15
+ ```sh
16
+ pnpm add @octanejs/opentui @opentui/core
17
+ ```
18
+
19
+ ## Compiler configuration
20
+
21
+ OpenTUI components live in `*.opentui.tsrx` modules. Add the serializable
22
+ renderer preset to the Octane Vite, Rsbuild, or Rspack compiler configuration:
23
+
24
+ ```ts
25
+ import { defineConfig } from '@octanejs/vite-plugin';
26
+ import { opentuiRenderers } from '@octanejs/opentui/config';
27
+
28
+ export default defineConfig({
29
+ compiler: {
30
+ renderers: opentuiRenderers,
31
+ },
32
+ });
33
+ ```
34
+
35
+ The preset uses OpenTUI host text, supports retained visibility and same-renderer
36
+ portals, and rejects server compilation. Terminal trees have no HTML SSR or
37
+ hydration path.
38
+
39
+ ## Rendering an application
40
+
41
+ ```tsx
42
+ // App.opentui.tsrx
43
+ import { useKeyboard, useTerminalDimensions } from '@octanejs/opentui';
44
+ import { useState } from 'octane';
45
+
46
+ export function App() @{
47
+ const [count, setCount] = useState(0);
48
+ const size = useTerminalDimensions();
49
+
50
+ useKeyboard((event) => {
51
+ if (event.name === 'return') setCount((value) => value + 1);
52
+ });
53
+
54
+ <box style={{ flexDirection: 'column', padding: 1 }}>
55
+ <text>{'Count: ' + count}</text>
56
+ <text>{'Terminal: ' + size.width + 'x' + size.height}</text>
57
+ </box>
58
+ }
59
+ ```
60
+
61
+ ```ts
62
+ import { createCliRenderer } from '@opentui/core';
63
+ import { createRoot } from '@octanejs/opentui';
64
+ import { App } from './App.opentui.tsrx';
65
+
66
+ const renderer = await createCliRenderer();
67
+ const root = createRoot(renderer);
68
+ root.render(App);
69
+ ```
70
+
71
+ `createRoot(renderer).render(Component, props)` follows Octane's programmatic
72
+ root convention. Destroying the `CliRenderer` unmounts the Octane root, and
73
+ `root.unmount()` releases component state, effects, listeners, refs, portals,
74
+ and owned renderables without destroying the caller-owned renderer.
75
+
76
+ ## Surface
77
+
78
+ The built-in intrinsic catalogue matches OpenTUI React 0.5.8: `box`, `text`,
79
+ `code`, `diff`, `markdown`, `input`, `select`, `textarea`, `scrollbox`,
80
+ `ascii-font`, `tab-select`, `line-number`, `image`, `time-to-first-draw`, and
81
+ the text modifiers `span`, `br`, `b`, `strong`, `i`, `em`, `u`, and `a`.
82
+ Register application renderables with `extend()` and augment
83
+ `OpenTUIComponents` for their intrinsic types.
84
+
85
+ The binding exports `useRenderer`, `useKeyboard`, `usePaste`, `useFocus`,
86
+ `useBlur`, `useSelectionHandler`, `useOnResize`, `useTerminalDimensions`, and
87
+ `useTimeline`. OpenTUI callback names and argument lists are preserved: for
88
+ example, select `onChange(index, option)` remains a two-argument OpenTUI event,
89
+ not a DOM synthetic event.
90
+
91
+ `createPortal(children, target)` accepts a borrowed `RootRenderable` created
92
+ from the same `CliRenderer` context. Portal teardown removes only Octane-owned
93
+ children; the target remains caller-owned.
94
+
95
+ The OpenTUI slot system is available as `createOctaneSlotRegistry`, `Slot`, and
96
+ `createSlot`; `createReactSlotRegistry` and the upstream `React*` type names are
97
+ retained as migration aliases. Plugins return Octane universal renderables
98
+ instead of React nodes, and plugin failures are reported to the core registry
99
+ with source `"octane"`.
100
+
101
+ For native integration tests, `@octanejs/opentui/test-utils` exports
102
+ `testRender(Component, props, options)`. The package's focused test command runs
103
+ Vitest with Bun so the OpenTUI FFI-backed test renderer is available.
104
+
105
+ ## Differences from `@opentui/react`
106
+
107
+ - Components are authored in `.opentui.tsrx`; the package does not export
108
+ React's `createElement` or accept React elements.
109
+ - Programmatic roots render an Octane component plus props rather than a React
110
+ node.
111
+ - Refs are ordinary Octane props and can be composed with `ref={[a, b]}`; there
112
+ is no `forwardRef` layer.
113
+ - Octane's compiler-assigned hook slots permit hooks behind conditions and
114
+ after early returns. The exported hooks forward manual slots internally.
115
+ - React DevTools integration and OpenTUI's React runtime-plugin bundling
116
+ subpaths are not included. Compiler setup uses `opentuiRenderers` instead.
117
+ - OpenTUI terminal rendering is client-only; SSR and hydration are unsupported.
118
+
119
+ The compatibility baseline and verified scope are recorded in
120
+ [`status.json`](./status.json).
package/UPSTREAM.md ADDED
@@ -0,0 +1,63 @@
1
+ # OpenTUI React upstream contract
2
+
3
+ ## Pin and source boundary
4
+
5
+ | Field | Value |
6
+ |---|---|
7
+ | Package | `@opentui/react` |
8
+ | Version | `0.5.8` |
9
+ | Canonical commit | `21b002174255ca2236ed3115e2bb2294642f2cf5` |
10
+ | Supported upstream range | exactly `0.5.8` |
11
+ | npm integrity | `sha512-l3N/Kbg5V+OABp60A5iCfEj3WK5pKVUDmUrVPf/xgct9R7Xy2Jbw/0MoSFpgr3odq3CuWprYajnoLCR9f81BRQ==` |
12
+ | License | MIT |
13
+
14
+ The complete `packages/react` subtree from the canonical commit is committed
15
+ byte-for-byte under `upstream/`. Every file verifies offline against its Git
16
+ blob hash in `audit/upstream.lock.json`. The repository-root license is retained
17
+ byte-for-byte as `LICENSE.upstream`; the Octane-authored binding remains under
18
+ the repository MIT license in `LICENSE`.
19
+
20
+ The authored port lives in `src/`. It reuses `@opentui/core@0.5.8` unchanged and
21
+ independently reimplements the React renderer, reconciliation, DevTools
22
+ transport boundary, and WebSocket dependency through Octane's universal host
23
+ driver and existing runtime instrumentation. No `react`, `react-reconciler`,
24
+ `react-devtools-core`, or `ws` source is copied into the binding.
25
+
26
+ ## Export crosswalk
27
+
28
+ | Upstream export group | Octane disposition | Evidence |
29
+ |---|---|---|
30
+ | `createRoot`, `Root`, `flushSync`, `createPortal` | Ported to Octane's universal renderer; roots take a component and props separately and portals target a same-renderer `RootRenderable` | `src/root.ts`, `src/scheduling.ts`, native integration tests |
31
+ | `baseComponents`, `componentCatalogue`, `extend`, `getComponentCatalogue` | Ported with the complete OpenTUI 0.5.8 built-in catalogue and custom renderable extension | `src/components.ts`, config tests |
32
+ | `AppContext`, `useAppContext` | Ported to Octane universal context | `src/context.ts`, native integration tests |
33
+ | `useRenderer`, keyboard, paste, focus, blur, selection, resize, dimensions, and timeline hooks | Ported to Octane hooks with compiler-owned slots and native OpenTUI subscriptions | `src/hooks.ts`, native integration tests |
34
+ | `createReactSlotRegistry`, `Slot`, `createSlot`, and `React*` slot types | Ported over OpenTUI core's slot registry; React-named APIs remain migration aliases for canonical `Octane*` APIs | `src/slot.ts`, native slot tests |
35
+ | `TimeToFirstDraw`, `TimeToFirstDrawProps` | Ported as a universal renderer component | `src/time-to-first-draw.ts`, public source typecheck |
36
+ | component prop types, `RenderableConstructor`, extension types, and `OpenTUIComponents` | Ported with `OctaneNode`, ordinary ref props, and renderer-specific intrinsic types | `src/types.ts`, published-source checks |
37
+ | `testRender` | Adapted to accept an Octane component plus props and execute against `@opentui/core/testing` | `src/test-utils.ts`, native integration tests |
38
+ | `./renderer` | Ported as the renderer/runtime surface plus the OpenTUI host driver | `src/renderer.ts` |
39
+ | `./jsx-runtime`, `./jsx-dev-runtime` | Replaced by the compiler-facing `./intrinsics` and `./intrinsics/jsx-runtime` entries | `src/intrinsics.ts`, config tests |
40
+ | `createElement` | Intentional divergence: Octane component trees are authored in `.opentui.tsrx`; nested runtime element construction is not supported | `README.md`, `docs/differences-from-react.md` |
41
+ | React runtime-plugin support entries | Inapplicable: applications configure the Octane compiler with `opentuiRenderers`; no runtime JSX rewrite is needed | `src/config.ts`, config tests |
42
+ | React Reconciler and React DevTools internals | Reimplemented by Octane's universal host driver and runtime instrumentation; these are not binding public exports | `src/driver.ts`, native lifecycle and identity tests |
43
+
44
+ ## Upstream test-suite disposition
45
+
46
+ The pinned upstream suite contains 56 runtime registrations across seven test
47
+ files. It remains fully visible under `upstream/tests`; fixtures beside those
48
+ suites are preserved but are not registrations.
49
+
50
+ | Upstream area | Disposition |
51
+ |---|---|
52
+ | root teardown and renderer-destroy races | Covered by the native coordinated-cleanup test |
53
+ | image loading, rerender retention, prop reset, cancellation, and abandoned renders | Public image props and host lifecycle are ported; focused image parity remains an explicit follow-up rather than a claimed passing lane |
54
+ | text, layout, prop reset, keyed reconciliation, focus, and intrinsic catalogue behavior | Covered at the binding boundary by native frame, state/prop update, identity, callback, and config tests; the core layout engine itself is reused unchanged |
55
+ | link behavior | Intrinsic and prop surface ported; terminal escape rendering remains owned by unchanged `@opentui/core` |
56
+ | runtime-plugin configuration | Inapplicable because Octane compiles `.opentui.tsrx` ahead of time and exports a serializable renderer preset |
57
+ | slot modes, ordering, failures, context, identity, and teardown | Representative append/fallback, registration, error, identity, and teardown behavior covered by native slot tests |
58
+ | timeline identity | Hook implementation preserves one `Timeline` per component instance; direct adapted case remains an explicit follow-up |
59
+
60
+ The parity manifest is deliberately `recorded-unverified`: the pin and current
61
+ Octane-specific behavioral lanes are machine-checked, but this change does not
62
+ claim that all 56 React-owned upstream registrations have been adapted one for
63
+ one.
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@octanejs/opentui",
3
+ "version": "0.0.1",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "engines": {
8
+ "node": ">=22.22.2"
9
+ },
10
+ "octane": {
11
+ "hookSlots": {
12
+ "manual": [
13
+ "src"
14
+ ]
15
+ }
16
+ },
17
+ "description": "OpenTUI bindings for Octane — terminal components, hooks, and a universal renderer over @opentui/core.",
18
+ "author": {
19
+ "name": "Dominic Gannaway",
20
+ "email": "dg@domgan.com"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/octanejs/octane.git",
28
+ "directory": "packages/opentui"
29
+ },
30
+ "main": "src/index.ts",
31
+ "module": "src/index.ts",
32
+ "types": "src/index.ts",
33
+ "files": [
34
+ "src",
35
+ "LICENSE",
36
+ "LICENSE.upstream",
37
+ "README.md",
38
+ "UPSTREAM.md"
39
+ ],
40
+ "exports": {
41
+ ".": "./src/index.ts",
42
+ "./config": "./src/config.ts",
43
+ "./renderer": "./src/renderer.ts",
44
+ "./intrinsics": "./src/intrinsics.ts",
45
+ "./intrinsics/jsx-runtime": "./src/intrinsics.ts",
46
+ "./test-utils": "./src/test-utils.ts"
47
+ },
48
+ "peerDependencies": {
49
+ "@opentui/core": ">=0.5.8 <0.6.0",
50
+ "octane": "0.1.46"
51
+ },
52
+ "devDependencies": {
53
+ "@opentui/core": "0.5.8",
54
+ "vitest": "^4.1.10",
55
+ "octane": "0.1.46"
56
+ },
57
+ "scripts": {
58
+ "test": "vitest run --root ../.. --project opentui && pnpm test:native",
59
+ "test:native": "NODE_OPTIONS= REACT_PORT_TEST_REPORT_DIR= bun ../../node_modules/vitest/vitest.mjs run --config vitest.native.config.ts",
60
+ "typecheck": "tsrx-tsc --noEmit"
61
+ }
62
+ }
@@ -0,0 +1,63 @@
1
+ import {
2
+ ASCIIFontRenderable,
3
+ BoxRenderable,
4
+ CodeRenderable,
5
+ DiffRenderable,
6
+ ImageRenderable,
7
+ InputRenderable,
8
+ LineNumberRenderable,
9
+ MarkdownRenderable,
10
+ ScrollBoxRenderable,
11
+ SelectRenderable,
12
+ TabSelectRenderable,
13
+ TextareaRenderable,
14
+ TextRenderable,
15
+ TimeToFirstDrawRenderable,
16
+ } from '@opentui/core';
17
+ import type { RenderableConstructor } from './types.js';
18
+ import {
19
+ BoldSpanRenderable,
20
+ ItalicSpanRenderable,
21
+ LineBreakRenderable,
22
+ LinkRenderable,
23
+ SpanRenderable,
24
+ UnderlineSpanRenderable,
25
+ } from './text.js';
26
+
27
+ export const baseComponents = {
28
+ box: BoxRenderable,
29
+ text: TextRenderable,
30
+ code: CodeRenderable,
31
+ diff: DiffRenderable,
32
+ markdown: MarkdownRenderable,
33
+ input: InputRenderable,
34
+ select: SelectRenderable,
35
+ textarea: TextareaRenderable,
36
+ scrollbox: ScrollBoxRenderable,
37
+ 'ascii-font': ASCIIFontRenderable,
38
+ 'tab-select': TabSelectRenderable,
39
+ 'line-number': LineNumberRenderable,
40
+ image: ImageRenderable,
41
+ 'time-to-first-draw': TimeToFirstDrawRenderable,
42
+ span: SpanRenderable,
43
+ br: LineBreakRenderable,
44
+ b: BoldSpanRenderable,
45
+ strong: BoldSpanRenderable,
46
+ i: ItalicSpanRenderable,
47
+ em: ItalicSpanRenderable,
48
+ u: UnderlineSpanRenderable,
49
+ a: LinkRenderable,
50
+ } as const;
51
+
52
+ export type ComponentCatalogue = Record<string, RenderableConstructor>;
53
+
54
+ export const componentCatalogue: ComponentCatalogue = { ...baseComponents };
55
+
56
+ /** Add application-specific OpenTUI renderables to the intrinsic catalogue. */
57
+ export function extend<T extends ComponentCatalogue>(objects: T): void {
58
+ Object.assign(componentCatalogue, objects);
59
+ }
60
+
61
+ export function getComponentCatalogue(): ComponentCatalogue {
62
+ return componentCatalogue;
63
+ }
package/src/config.ts ADDED
@@ -0,0 +1,32 @@
1
+ /** Serializable compiler metadata for OpenTUI-rendered TSRX modules. */
2
+ export const OPENTUI_RENDERER_ID = 'opentui';
3
+
4
+ export const opentuiRenderer = {
5
+ module: '@octanejs/opentui/renderer',
6
+ target: 'universal',
7
+ server: 'unsupported',
8
+ intrinsics: '@octanejs/opentui/intrinsics',
9
+ text: 'host',
10
+ capabilities: ['portal', 'visibility'],
11
+ } as const;
12
+
13
+ export const opentuiRendererRegistry = {
14
+ [OPENTUI_RENDERER_ID]: opentuiRenderer,
15
+ } as const;
16
+
17
+ export const opentuiRendererRules = [
18
+ {
19
+ include: '**/*.opentui.tsrx',
20
+ renderer: OPENTUI_RENDERER_ID,
21
+ },
22
+ ] as const;
23
+
24
+ export const opentuiRenderers = {
25
+ registry: opentuiRendererRegistry,
26
+ rules: opentuiRendererRules,
27
+ } as const;
28
+
29
+ /** Short compatibility name for application config files. */
30
+ export const renderers = opentuiRenderers;
31
+
32
+ export default opentuiRenderers;
package/src/context.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { CliRenderer, KeyHandler } from '@opentui/core';
2
+ import { createContext, useContext } from 'octane/universal';
3
+
4
+ export interface AppContextValue {
5
+ keyHandler: KeyHandler | null;
6
+ renderer: CliRenderer | null;
7
+ }
8
+
9
+ export const AppContext = createContext<AppContextValue>({
10
+ keyHandler: null,
11
+ renderer: null,
12
+ });
13
+
14
+ export function useAppContext(): AppContextValue {
15
+ return useContext(AppContext);
16
+ }