diff --git a/README.md b/README.md index b9fb4aa..690192b 100644 --- a/README.md +++ b/README.md @@ -1,143 +1,102 @@ # Gridland -Gridland renders [opentui](https://github.com/nicosalm/opentui) React apps directly in the browser using HTML5 ``, bypassing any terminal emulator. Gridland is built on the opentui engine. +Gridland renders [opentui](https://github.com/nicosalm/opentui) React apps directly in the browser using HTML5 ``, bypassing any terminal emulator. -![Screenshot](screenshot.png) +## Quick start -## Architecture - -``` -React JSX -> @opentui/react reconciler -> Renderable tree -> Yoga layout - -> renderSelf() calls buffer.drawText / drawBox / setCell - -> BrowserBuffer (pure JS TypedArrays) stores cell grid - -> CanvasPainter reads buffer -> ctx.fillRect + ctx.fillText +```bash +# Create a new project +npx create-gridland my-app +cd my-app +bun install +bun run dev ``` -The key insight: OpenTUI renderables never call Zig directly. They call `OptimizedBuffer` methods. By replacing the buffer + renderer with pure-JS implementations, the entire renderable/reconciler layer works unchanged. +You can choose between **Vite** and **Next.js** during setup. -## What's implemented +## Add to an existing project -- **BrowserBuffer** — Pure-JS replacement for `OptimizedBuffer` using `Uint32Array`/`Float32Array` typed arrays. Same API: `setCell`, `drawText`, `drawBox`, `fillRect`, scissor rect stack, opacity stack. -- **BrowserTextBuffer / BrowserTextBufferView** — Pure-JS replacements for the Zig-backed text storage and word/char wrapping. -- **CanvasPainter** — Two-pass canvas renderer: background rects, then foreground chars with font style attributes (bold/italic/underline). -- **BrowserRenderer** — Orchestrator running `requestAnimationFrame` loop: lifecycle passes -> Yoga layout -> render commands -> paint. -- **BrowserRenderContext** — Implements the `RenderContext` interface (event emitter, lifecycle pass registry, focus management). -- **Vite plugin** — Custom `resolveId` plugin that intercepts all Zig/FFI/Node.js imports from the opentui source tree and redirects them to browser shims. -- **React integration** — `createBrowserRoot()` wires the @opentui/react reconciler to the browser renderer. +```bash +bun add @gridland/web @gridland/utils +``` -## Getting started +Then wrap your app with the `` component: -```bash -git clone https://github.com//gridland.git -cd gridland -bun setup +```tsx +import { TUI } from "@gridland/web" +import { useKeyboard } from "@gridland/utils" + +function App() { + return ( + + {/* your components */} + + ) +} ``` -This installs all dependencies and builds the packages. +**Vite** — add the plugin to your `vite.config.ts`: -## Running +```ts +import { gridlandWebPlugin } from "@gridland/web/vite-plugin" -```bash -# Dev server -bun run dev -# -> http://localhost:5173 +export default defineConfig({ + plugins: [react(), gridlandWebPlugin()], +}) +``` -# Tests -bun run test +**Next.js** — use the `@gridland/web/next` export and the webpack plugin: -# Production build -bun run build +```ts +// next.config.ts +import { withGridland } from "@gridland/web/next-plugin" +export default withGridland({}) ``` -### AI chat demo (Cloudflare Worker) +```tsx +// component.tsx +"use client" +import { TUI } from "@gridland/web/next" +``` -The docs site AI chat demo connects to a Cloudflare Worker that proxies requests to [OpenRouter](https://openrouter.ai/). The API key never lives in the repo — it's stored as a Cloudflare Workers secret. +## UI components -**Local development:** +Gridland includes a component registry (shadcn-style). Install individual components into your project: ```bash -# 1. Create a local secrets file (gitignored) -echo "OPENROUTER_API_KEY=sk-or-..." > packages/chat-worker/.dev.vars +bunx shadcn@latest add @gridland/spinner +``` -# 2. Start the Worker (localhost:8787) -bun run chat:dev +Available components include Chat, Table, TextInput, SelectInput, Modal, Spinner, StatusBar, and more. -# 3. In another terminal, start the docs site -bun run dev -``` +## Packages -The `.env` at the repo root sets `NEXT_PUBLIC_CHAT_API_URL=http://localhost:8787/chat` so the docs site points at the local Worker. +| Package | Description | +|---------|-------------| +| `@gridland/web` | Core browser runtime — `` component, canvas renderer, Vite/Next.js plugins | +| `@gridland/utils` | Shared hooks and types (`useKeyboard`, `useOnResize`, color utilities) | +| `@gridland/ui` | Component registry (shadcn-style, not installed directly) | +| `@gridland/testing` | Test utilities — `renderTui()`, `Screen` queries, `KeySender` | +| `@gridland/demo` | Demo framework and landing page | +| `@gridland/bun` | Native Bun runtime with FFI for terminal rendering | +| `create-gridland` | Project scaffolding CLI | -**Production deployment:** +## Development (contributors) ```bash -# Set the secret in Cloudflare (one-time, encrypted at rest) -cd packages/chat-worker && npx wrangler secret put OPENROUTER_API_KEY +git clone git@github.com:thoughtfulllc/gridland.git +cd gridland +bun setup -# Deploy the Worker -bun run chat:deploy -``` +# Run the docs site +bun run dev -Then set `NEXT_PUBLIC_CHAT_API_URL` to the deployed Worker URL in your static hosting environment (e.g. Render dashboard). This env var is baked into the static bundle at build time. +# Run tests +bun run test -## Project structure +# Run e2e tests +bun run test:e2e +# Build all packages +bun run build ``` -packages/ - web/ # Core browser runtime (npm: @gridland/web) - src/ - index.ts # Main exports (bundled mode) - core.ts # Core exports (external mode for Vite plugin users) - TUI.tsx # Single React component — THE mounting layer - mount.ts # Imperative mount API: mountGridland(canvas, element) - browser-buffer.ts - browser-text-buffer.ts - browser-text-buffer-view.ts - browser-renderer.ts - browser-render-context.ts - canvas-painter.ts - selection-manager.ts - vite-plugin.ts # Vite plugin for shim resolution - next.ts # Next.js export (thin — just "use client" re-export) - next-plugin.ts # Next.js webpack plugin - utils.ts # SSR-safe utilities - core-shims/ # @opentui/core browser replacements - shims/ # Node.js built-in stubs - __tests__/ # Unit + integration tests - - core/ # Hard-forked opentui engine (private) - - ui/ # UI component library (npm: @gridland/ui) - components/ # Components with tests - - testing/ # Testing utilities (npm: @gridland/testing) - src/ - - utils/ # Portable hooks & utilities (npm: @gridland/utils) - - chat-worker/ # Cloudflare Worker — AI chat proxy - src/index.ts # CORS + streaming via OpenRouter - wrangler.toml # Worker configuration - - bun/ # Native Bun runtime for CLI (npm: @gridland/bun) - demo/ # CLI demo runner (npm: @gridland/demo) - create-gridland/ # Project scaffolder CLI - container/ # Docker sandbox runner - docs/ # Fumadocs documentation site (static export) - -examples/ - vite-example/ # Minimal Vite example - next-example/ # Next.js example - container-demo/ # Docker container demo - -e2e/ # Playwright E2E tests -``` - -## How the Vite plugin works - -The opentui source tree (`packages/core/`) is loaded directly. A custom plugin intercepts imports at resolution time: - -1. **File-level redirects** — Relative imports that resolve to zig-dependent files (buffer, text-buffer, text-buffer-view, syntax-style, renderer, etc.) are redirected to browser shims. -2. **Pattern-based interception** — tree-sitter, hast, and Node.js builtin imports are caught by string matching and redirected to stubs. -3. **Barrel routing** — `@opentui/core` is routed to the real opentui barrel when imported from the react package (to preserve the original module evaluation order), and to our core-shims barrel when imported from our own code. -4. **Circular dep fix** — `Slider.ts`'s import of `../index` is redirected to a minimal deps file to break a barrel-level circular dependency that causes TDZ errors in strict ESM.