This commit is contained in:
Chris Wanstrath 2026-01-30 19:55:25 -08:00
parent 6f13ba0f66
commit 4cbe5c2566
2 changed files with 165 additions and 0 deletions

85
docs/APPS.md Normal file
View File

@ -0,0 +1,85 @@
# Apps
An app is an HTTP server that runs on its assigned port.
## minimum requirements
```
apps/<name>/
<timestamp>/ # YYYYMMDD-HHMMSS
package.json
index.tsx
current -> <timestamp> # symlink to active version
```
**package.json** must have `scripts.toes`:
```json
{
"name": "my-app",
"module": "index.tsx",
"type": "module",
"private": true,
"scripts": {
"toes": "bun run --watch index.tsx"
},
"toes": {
"icon": "🎨"
},
"dependencies": {
"@because/hype": "*",
"@because/forge": "*"
}
}
```
**index.tsx** must export `app.defaults`:
```tsx
import { Hype } from '@because/hype'
const app = new Hype()
app.get('/', c => c.html(<h1>Hello</h1>))
export default app.defaults
```
## environment
- `PORT` - your assigned port (3001-3100)
- `APPS_DIR` - path to `/apps` directory
## health checks
Toes hits `GET /` every 30 seconds. Return 2xx or get restarted.
3 failures = restart with exponential backoff (1s, 2s, 4s, 8s, 16s, 32s).
## lifecycle
`invalid` -> `stopped` -> `starting` -> `running` -> `stopping`
Apps auto-restart on crash. `bun install` runs before every start.
## cli
```bash
toes new my-app # create from template
toes list # show apps
toes start my-app # start
toes stop my-app # stop
toes restart my-app # restart
toes logs -f my-app # tail logs
toes open my-app # open in browser
```
## making a new app
```bash
toes new my-app --template=spa
```
- `ssr` - server-side rendered (default)
- `spa` - single page app (w/ hono/jsx)
- `bare` - minimal

80
docs/TOOLS.md Normal file
View File

@ -0,0 +1,80 @@
# Tools
A tool is an app that appears as a tab in the dashboard instead of in the sidebar.
Tools know which app is selected and render in an iframe over the content area.
## making an app a tool
Add `toes.tool` to package.json:
```json
{
"toes": {
"tool": true,
"icon": "🔧"
},
"scripts": {
"toes": "bun run --watch index.tsx"
}
}
```
## getting the selected app
Query param `?app=<name>` tells you which app the user selected:
```tsx
app.get('/', c => {
const appName = c.req.query('app')
if (!appName) {
return c.html(<p>No app selected</p>)
}
// do something with appName
})
```
## accessing app files
Always go through the `current` symlink:
```ts
const APPS_DIR = process.env.APPS_DIR ?? '.'
const appPath = join(APPS_DIR, appName, 'current')
```
Not `APPS_DIR/appName` directly.
## talking to the parent
**Navigate to another tool:**
```js
window.parent.postMessage({
type: 'navigate-tool',
tool: 'code',
params: { app: 'my-app', version: '20260130-000000' }
}, '*')
```
**Resize your iframe:**
```js
window.parent.postMessage({
type: 'resize-iframe',
height: 500
}, '*')
```
## iframe behavior
- iframes are cached per tool+app combination
- Never recreated once loaded
- State persists across tab switches
## cli
```bash
toes list --tools # list tools only
toes list --all # list apps and tools
```