From 387f746f8cd6d0e240b6372337616ed152238731 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Tue, 10 Mar 2026 16:53:05 -0700 Subject: [PATCH] ok --- .gitignore | 4 + CLAUDE.md | 327 ++++------------------------------------- README.md | 66 +++++++++ bin/.gitkeep | 0 bun.lock | 35 +++++ docs/PLAN.md | 141 ++++++++++++++++++ index.tsx | 1 - package.json | 13 +- scripts/postinstall.sh | 17 +++ src/pages/index.tsx | 1 - src/server.ts | 159 ++++++++++++++++++++ src/server/index.tsx | 5 - 12 files changed, 456 insertions(+), 313 deletions(-) create mode 100644 README.md create mode 100644 bin/.gitkeep create mode 100644 bun.lock create mode 100644 docs/PLAN.md delete mode 100644 index.tsx create mode 100755 scripts/postinstall.sh delete mode 100644 src/pages/index.tsx create mode 100644 src/server.ts delete mode 100644 src/server/index.tsx diff --git a/.gitignore b/.gitignore index 74c83bb..9122461 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# pre-built go binary (per-platform) +bin/tronbyt-server* +!bin/.gitkeep + # dependencies (bun install) node_modules diff --git a/CLAUDE.md b/CLAUDE.md index 84d6dfb..22a060e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,307 +1,38 @@ -# Toes - Guide to Writing Apps +# Tronbyt Toes App -Toes manages and runs web apps, each on its own port. +This is a [toes](/Users/corey/code/toes) app. See the [toes CLAUDE.md](/Users/corey/code/toes/CLAUDE.md) for the framework docs. -Apps are server-rendered TypeScript using **Hype** (wraps Hono) and **Forge** (CSS-in-JS). +Wraps the Tronbyt Go server (self-hosted Tidbyt replacement) as a toes-managed subprocess. Bun proxies all HTTP and WebSocket traffic to the Go binary over a unix socket. -Runtime is **Bun**. +## How It Works -## Required Components - -Every toes app/tool must have: - -1. **`.npmrc`** pointing to `registry=https://npm.nose.space` (the private registry for `@because/*` packages) -2. **`package.json`** with a `scripts.toes` entry (this is how toes discovers and runs apps) -3. **HTTP `GET /ok`** returning 200 (health check endpoint — toes polls this every 30s and restarts unresponsive apps) - -## App vs Tool - -An **app** shows in the sidebar and opens in its own browser tab. - -A **tool** renders as a tab inside the dashboard (in an iframe). It receives `?app=` to know the selected app. The only code difference is `"toes": { "tool": true }` in package.json and some extra imports from `@because/toes`. - -## Required Files - -Every app needs `.npmrc`, `tsconfig.json`, `package.json`, and `index.tsx`. - -**.npmrc** -- always this exact content: ``` -registry=https://npm.nose.space +Tidbyt device → tronbyt.toes.local → toes → Bun (PORT) → Go binary (unix socket) ``` -**tsconfig.json** -- use exactly, do not improvise: -```json -{ - "compilerOptions": { - "lib": ["ESNext"], - "target": "ESNext", - "module": "Preserve", - "moduleDetection": "force", - "jsx": "react-jsx", - "jsxImportSource": "hono/jsx", - "allowJs": true, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true, - "noImplicitOverride": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false, - "baseUrl": ".", - "paths": { - "$*": ["src/server/*"], - "#*": ["src/client/*"], - "@*": ["src/shared/*"] - } - } -} +- `src/server/index.tsx` — spawns Go binary, proxies HTTP + WebSocket, health checks +- `bin/` — pre-built Go binary (gitignored, per-platform) +- No UI of its own — Go server serves its own web dashboard + +## Setup + +1. Download the binary for your platform from https://github.com/tronbyt/server/releases +2. Place it in `bin/` (e.g. `bin/tronbyt-server-darwin-arm64`) +3. `chmod +x bin/tronbyt-server-*` +4. On macOS: System Settings → Privacy & Security → Allow Anyway + +## Env Vars + +Toes provides `PORT`, `DATA_DIR`, `APPS_DIR`, `TOES_URL`, `APP_URL`. + +Tronbyt-specific vars (set via toes env config): +- `PRODUCTION` — `false` skips firmware downloads (default) +- `SINGLE_USER_AUTO_LOGIN` — `true` for home network (default) +- `SYSTEM_APPS_AUTO_REFRESH` — `true` to keep community apps updated (default) + +## Device Config + +Set the Tidbyt Image URL to: ``` - -**package.json** for an app: -```json -{ - "name": "my-app", - "private": true, - "module": "index.tsx", - "type": "module", - "scripts": { "toes": "bun run --watch index.tsx" }, - "toes": { "icon": "🖥️" }, - "dependencies": { - "@because/forge": "*", - "@because/hype": "*" - }, - "devDependencies": { "@types/bun": "latest" } -} +http://tronbyt.toes.local//next ``` - -For a **tool**, add `@because/toes` to dependencies and set `"tool": true` (or a string for a custom tab label like `"tool": ".env"`): -```json -{ - "toes": { "icon": "🔧", "tool": true }, - "dependencies": { - "@because/forge": "*", - "@because/hype": "*", - "@because/toes": "*" - } -} -``` - -## Hype - -Hype wraps Hono. It adds `app.defaults` (the Bun server export), `app.sse()` for server-sent events, and `Hype.router()` for sub-routers. Everything else is standard Hono. - -```tsx -import { Hype } from '@because/hype' -const app = new Hype() - -app.get('/', c => c.html(

Hello

)) -app.get('/ok', c => c.text('ok')) // Health check -- required - -export default app.defaults -``` - -Constructor options: `prettyHTML` (default true, tools should set false), `layout` (default true), `logging` (default true). - -**SSE** -- the one non-Hono addition: -```tsx -app.sse('/stream', (send, c) => { - send({ hello: 'world' }) - const interval = setInterval(() => send({ time: Date.now() }), 1000) - return () => clearInterval(interval) // cleanup on disconnect -}) -``` - -**Sub-routers:** -```tsx -const api = Hype.router() -api.get('/items', c => c.json([])) -app.route('/api', api) // mounts at /api/items -``` - -## Forge - -Forge creates styled JSX components via `define()`. Properties use camelCase CSS. Numbers auto-convert to `px` (except `flex`, `opacity`, `zIndex`, `fontWeight`). - -```tsx -import { define, stylesToCSS } from '@because/forge' - -const Box = define('Box', { - padding: 20, - borderRadius: '6px', -}) -// content renders
content
-``` - -**`base`** -- set the HTML element (default `div`): -```tsx -const Button = define('Button', { base: 'button', padding: '8px 16px' }) -const Link = define('Link', { base: 'a', textDecoration: 'none' }) -``` - -**`states`** -- pseudo-classes: -```tsx -const Item = define('Item', { - padding: 12, - states: { - ':hover': { backgroundColor: '#eee' }, - ':last-child': { borderBottom: 'none' }, - }, -}) -``` - -**`selectors`** -- nested CSS (`&` = the component): -```tsx -const List = define('List', { - selectors: { - '& > li:last-child': { borderBottom: 'none' }, - }, -}) -``` - -**`variants`** -- conditional styles via props: -```tsx -const Button = define('Button', { - base: 'button', - variants: { - variant: { - primary: { backgroundColor: '#2563eb', color: 'white' }, - danger: { backgroundColor: '#dc2626', color: 'white' }, - }, - }, -}) -// -``` - -**Serving CSS** -- apps serve `stylesToCSS()` from a route. Tools prepend `baseStyles`: -```tsx -app.get('/styles.css', c => - c.text(baseStyles + stylesToCSS(), 200, { 'Content-Type': 'text/css; charset=utf-8' }) -) -``` - -## Theme Tokens - -Tools import `theme` from `@because/toes/tools`. It returns CSS variables that resolve per light/dark mode. - -```tsx -import { baseStyles, ToolScript, theme } from '@because/toes/tools' - -const Container = define('Container', { - color: theme('colors-text'), - border: `1px solid ${theme('colors-border')}`, -}) -``` - -Available tokens: - -| Token | Use for | -|-------|---------| -| `colors-bg`, `colors-bgSubtle`, `colors-bgElement`, `colors-bgHover` | Backgrounds | -| `colors-text`, `colors-textMuted`, `colors-textFaint` | Text | -| `colors-border` | Borders | -| `colors-link` | Links | -| `colors-primary`, `colors-primaryText` | Primary actions | -| `colors-error`, `colors-dangerBorder`, `colors-dangerText` | Errors/danger | -| `colors-success`, `colors-successBg` | Success states | -| `colors-statusRunning`, `colors-statusStopped` | Status indicators | -| `fonts-sans`, `fonts-mono` | Font stacks | -| `spacing-xs` (4), `spacing-sm` (8), `spacing-md` (12), `spacing-lg` (16), `spacing-xl` (24) | Spacing (px) | -| `radius-md` (6px) | Border radius | - -## Writing a Tool - -Tools need three extra things vs apps: - -1. `` in `` (handles dark mode + iframe height communication) -2. `baseStyles` prepended to CSS output -3. Handle the `?app=` query param - -```tsx -import { Hype } from '@because/hype' -import { define, stylesToCSS } from '@because/forge' -import { baseStyles, ToolScript, theme } from '@because/toes/tools' -import type { Child } from 'hono/jsx' - -const APPS_DIR = process.env.APPS_DIR! -const app = new Hype({ prettyHTML: false }) - -const Container = define('Container', { - fontFamily: theme('fonts-sans'), - padding: '20px', - paddingTop: 0, - maxWidth: '800px', - margin: '0 auto', - color: theme('colors-text'), -}) - -function Layout({ title, children }: { title: string; children: Child }) { - return ( - - - - - {title} - - - - - {children} - - - ) -} - -app.get('/ok', c => c.text('ok')) -app.get('/styles.css', c => - c.text(baseStyles + stylesToCSS(), 200, { 'Content-Type': 'text/css; charset=utf-8' }) -) - -app.get('/', async c => { - const appName = c.req.query('app') - if (!appName) return c.html(

No app selected

) - // ... tool logic using join(APPS_DIR, appName, 'current') for file paths - return c.html(...) -}) - -export default app.defaults -``` - -**Environment variables** available to tools: `APPS_DIR`, `TOES_URL` (base URL of Toes server), `PORT`, `TOES_DIR`. - -**Accessing app files:** always use `join(APPS_DIR, appName, 'current')`. - -**Calling the Toes API:** `fetch(\`${TOES_URL}/api/apps\`)`, `fetch(\`${TOES_URL}/api/apps/${name}\`)`. - -**Linking between tools:** `View Code`. - -## Patterns - -**Fire-and-forget with polling** -- for long-running ops, don't await in POST. Use `` to poll while running. - -**Inline client JS** -- use `