diff --git a/apps/code/20260130-000000/src/server/index.tsx b/apps/code/20260130-000000/src/server/index.tsx index 89bd590..6b3deaf 100644 --- a/apps/code/20260130-000000/src/server/index.tsx +++ b/apps/code/20260130-000000/src/server/index.tsx @@ -216,9 +216,12 @@ const fileMemoryScript = ` var version = params.get('version') || 'current'; if (!app) return; var key = 'code-app:' + app + ':' + version + ':file'; - if (file) { - localStorage.setItem(key, file); + if (params.has('file')) { + // Explicit file param (even empty) - save it + if (file) localStorage.setItem(key, file); + else localStorage.removeItem(key); } else { + // No file param - restore saved location var saved = localStorage.getItem(key); if (saved) { var url = '/?app=' + encodeURIComponent(app); @@ -318,7 +321,7 @@ function PathBreadcrumb({ appName, filePath, versionParam }: BreadcrumbProps) { return ( {crumbs.length > 0 ? ( - {appName} + {appName} ) : ( {appName} )} diff --git a/apps/versions/20260130-000000/index.tsx b/apps/versions/20260130-000000/index.tsx index db23e44..4c780ab 100644 --- a/apps/versions/20260130-000000/index.tsx +++ b/apps/versions/20260130-000000/index.tsx @@ -6,6 +6,7 @@ import { join } from 'path' import type { Child } from 'hono/jsx' const APPS_DIR = process.env.APPS_DIR! +const TOES_URL = process.env.TOES_URL! const app = new Hype({ prettyHTML: false }) @@ -181,8 +182,7 @@ app.get('/', async c => { {versions.map(v => ( {formatTimestamp(v.name)} diff --git a/docs/TOOLS.md b/docs/TOOLS.md index ca9b11f..9bc4970 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -45,19 +45,17 @@ const appPath = join(APPS_DIR, appName, 'current') Not `APPS_DIR/appName` directly. -## talking to the parent +## linking to tools -**Navigate to another tool:** +Use `/tool/:name` URLs to link directly to tools with params: -```js -window.parent.postMessage({ - type: 'navigate-tool', - tool: 'code', - params: { app: 'my-app', version: '20260130-000000' } -}, '*') +```html + + View in Code + ``` -**Resize your iframe:** +## resize iframe ```js window.parent.postMessage({ diff --git a/src/client/index.tsx b/src/client/index.tsx index 9b10094..7a1782c 100644 --- a/src/client/index.tsx +++ b/src/client/index.tsx @@ -1,8 +1,8 @@ import { render as renderApp } from 'hono/jsx/dom' import { Dashboard } from './components' -import { apps, selectedApp, selectedTab, setApps, setSelectedApp, setSelectedTab } from './state' +import { apps, selectedApp, selectedTab, setApps, setSelectedApp } from './state' import { initModal } from './components/modal' -import { initToolIframes, updateToolIframes, setNavigateToolHandler, navigateToTool } from './tool-iframes' +import { initToolIframes, updateToolIframes } from './tool-iframes' import { initUpdate } from './update' const render = () => { @@ -19,15 +19,6 @@ initModal(render) initUpdate(render) initToolIframes() -// Handle tool-to-tool navigation via postMessage -setNavigateToolHandler((tool, params) => { - const tools = apps.filter(a => a.tool) - navigateToTool(tool, params, tools, (tab) => { - setSelectedTab(tab) - render() - }) -}) - // Set theme based on system preference const setTheme = () => { const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches diff --git a/src/client/tool-iframes.ts b/src/client/tool-iframes.ts index 96fdea3..39a05ae 100644 --- a/src/client/tool-iframes.ts +++ b/src/client/tool-iframes.ts @@ -12,13 +12,6 @@ let currentTool: string | null = (window as any).__currentTool ?? null // Get the stable container (outside Hono-managed DOM) const getContainer = () => document.getElementById('tool-iframes') -// Callback for tool navigation requests -let onNavigateTool: ((tool: string, params: Record) => void) | null = null - -export function setNavigateToolHandler(handler: (tool: string, params: Record) => void) { - onNavigateTool = handler -} - // Listen for messages from iframes function setupMessageListener() { if ((window as any).__toolIframeMessageListener) return @@ -40,13 +33,6 @@ function setupMessageListener() { } } } - - if (type === 'navigate-tool') { - const { tool, params } = event.data - if (tool && onNavigateTool) { - onNavigateTool(tool, params || {}) - } - } }) } @@ -99,45 +85,6 @@ function buildCacheKey(toolName: string, params: Record): string return parts.join(':') } -// Navigate to a tool with specific params (called from postMessage handler) -export function navigateToTool( - toolName: string, - params: Record, - tools: Array<{ name: string; port?: number; state: string }>, - onSelectTab: (tab: string) => void -) { - const tool = tools.find(t => t.name === toolName) - if (!tool || tool.state !== 'running' || !tool.port) return - - const container = getContainer() - if (!container) return - - const cacheKey = buildCacheKey(toolName, params) - - // Create iframe if needed - let cached = iframes.get(cacheKey) - if (!cached || cached.port !== tool.port) { - const iframe = document.createElement('iframe') - iframe.src = buildToolUrl(tool.port, params) - iframe.dataset.toolName = toolName - iframe.dataset.appName = params.app ?? '' - iframe.style.cssText = `width: 100%; border: none;` - cached = { iframe, port: tool.port } - iframes.set(cacheKey, cached) - container.appendChild(iframe) - } - - // Switch to that tab - onSelectTab(toolName) - - // Force show this specific iframe - currentTool = cacheKey - ;(window as any).__currentTool = cacheKey - for (const [key, { iframe }] of iframes) { - iframe.style.display = key === cacheKey ? 'block' : 'none' - } -} - // Update which iframe is visible based on selected tab and tool state export function updateToolIframes( selectedTab: string, diff --git a/src/server/apps.ts b/src/server/apps.ts index 97618a2..94c1c72 100644 --- a/src/server/apps.ts +++ b/src/server/apps.ts @@ -433,7 +433,7 @@ async function runApp(dir: string, port: number) { const proc = Bun.spawn(['bun', 'run', 'toes'], { cwd, - env: { ...process.env, PORT: String(port), NO_AUTOPORT: 'true', APPS_DIR }, + env: { ...process.env, PORT: String(port), NO_AUTOPORT: 'true', APPS_DIR, TOES_URL: `http://localhost:${process.env.PORT || 3000}` }, stdout: 'pipe', stderr: 'pipe', }) diff --git a/src/server/index.tsx b/src/server/index.tsx index 3da04a4..8c5151a 100644 --- a/src/server/index.tsx +++ b/src/server/index.tsx @@ -1,4 +1,4 @@ -import { initApps } from '$apps' +import { allApps, initApps } from '$apps' import appsRouter from './api/apps' import syncRouter from './api/sync' import { Hype } from '@because/hype' @@ -8,6 +8,18 @@ const app = new Hype({ layout: false }) app.route('/api/apps', appsRouter) app.route('/api/sync', syncRouter) +// Tool URLs: /tool/code?app=todo&file=README.md -> redirect to tool port +app.get('/tool/:tool', c => { + const toolName = c.req.param('tool') + const tool = allApps().find(a => a.tool && a.name === toolName) + if (!tool || tool.state !== 'running' || !tool.port) { + return c.text(`Tool "${toolName}" not found or not running`, 404) + } + const params = new URLSearchParams(c.req.query()).toString() + const url = params ? `http://localhost:${tool.port}?${params}` : `http://localhost:${tool.port}` + return c.redirect(url) +}) + console.log('🐾 Toes!') initApps()