From 6f13ba0f6657c2bfc7b54788e0f370267dc2a9fe Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Fri, 30 Jan 2026 19:55:21 -0800 Subject: [PATCH] forge --- apps/code/20260130-000000/package.json | 5 +- .../code/20260130-000000/src/server/index.tsx | 99 ++++++++++++------- apps/todo/20260130-181927/package.json | 1 + .../todo/20260130-181927/src/server/index.tsx | 3 +- apps/versions/20260130-000000/index.tsx | 7 +- apps/versions/20260130-000000/package.json | 1 + src/client/components/AppDetail.tsx | 1 - 7 files changed, 76 insertions(+), 41 deletions(-) diff --git a/apps/code/20260130-000000/package.json b/apps/code/20260130-000000/package.json index d6d21ee..925bab9 100644 --- a/apps/code/20260130-000000/package.json +++ b/apps/code/20260130-000000/package.json @@ -19,8 +19,9 @@ "typescript": "^5.9.2" }, "dependencies": { + "@because/forge": "*", + "@because/howl": "*", "@because/hype": "*", - "@because/toes": "*", - "@because/howl": "*" + "@because/toes": "*" } } diff --git a/apps/code/20260130-000000/src/server/index.tsx b/apps/code/20260130-000000/src/server/index.tsx index e214233..7a9fc7b 100644 --- a/apps/code/20260130-000000/src/server/index.tsx +++ b/apps/code/20260130-000000/src/server/index.tsx @@ -1,5 +1,6 @@ import { Hype } from '@because/hype' -import { theme, define, stylesToCSS, initScript, baseStyles } from '@because/toes/tools' +import { define, stylesToCSS } from '@because/forge' +import { baseStyles, initScript, theme } from '@because/toes/tools' import { readdir, stat } from 'fs/promises' import { readFileSync } from 'fs' import { join, extname, basename } from 'path' @@ -12,29 +13,12 @@ const app = new Hype({ prettyHTML: false }) const Container = define('Container', { fontFamily: theme('fonts-sans'), padding: '20px', + paddingTop: 0, maxWidth: '1200px', margin: '0 auto', color: theme('colors-text'), }) -const Header = define('Header', { - marginBottom: '20px', - paddingBottom: '10px', - borderBottom: `2px solid ${theme('colors-border')}`, -}) - -const Title = define('Title', { - margin: 0, - fontSize: '24px', - fontWeight: 'bold', -}) - -const Subtitle = define('Subtitle', { - color: theme('colors-textMuted'), - fontSize: '18px', - marginTop: '5px', -}) - const FileList = define('FileList', { listStyle: 'none', padding: 0, @@ -114,14 +98,19 @@ const ErrorBox = define('ErrorBox', { margin: '20px 0', }) -const BackLink = define('BackLink', { +const Breadcrumb = define('Breadcrumb', { + display: 'flex', + alignItems: 'center', + gap: '6px', + fontSize: '14px', + marginBottom: '15px', + flexWrap: 'wrap', +}) + +const BreadcrumbLink = define('BreadcrumbLink', { base: 'a', textDecoration: 'none', color: theme('colors-link'), - display: 'inline-flex', - alignItems: 'center', - gap: '5px', - marginBottom: '15px', states: { ':hover': { textDecoration: 'underline', @@ -129,6 +118,15 @@ const BackLink = define('BackLink', { }, }) +const BreadcrumbSeparator = define('BreadcrumbSeparator', { + color: theme('colors-textMuted'), +}) + +const BreadcrumbCurrent = define('BreadcrumbCurrent', { + color: theme('colors-text'), + fontWeight: 500, +}) + const FolderIcon = () => ( @@ -143,12 +141,11 @@ const FileIconSvg = () => ( interface LayoutProps { title: string - subtitle?: string children: Child highlight?: boolean } -function Layout({ title, subtitle, children, highlight }: LayoutProps) { +function Layout({ title, children, highlight }: LayoutProps) { return ( @@ -197,6 +194,43 @@ async function listFiles(appPath: string, subPath: string = '') { }) } +interface BreadcrumbProps { + appName: string + filePath: string + versionParam: string +} + +function PathBreadcrumb({ appName, filePath, versionParam }: BreadcrumbProps) { + const parts = filePath ? filePath.split('/').filter(Boolean) : [] + const crumbs: { name: string; path: string }[] = [] + + let currentPath = '' + for (const part of parts) { + currentPath = currentPath ? `${currentPath}/${part}` : part + crumbs.push({ name: part, path: currentPath }) + } + + return ( + + {crumbs.length > 0 ? ( + {appName} + ) : ( + {appName} + )} + {crumbs.map((crumb, i) => ( + <> + / + {i === crumbs.length - 1 ? ( + {crumb.name} + ) : ( + {crumb.name} + )} + + ))} + + ) +} + function getLanguage(filename: string): string { const ext = extname(filename).toLowerCase() const langMap: Record = { @@ -229,7 +263,6 @@ app.get('/', async c => { } const appPath = join(APPS_DIR, appName, version) - const versionSuffix = version !== 'current' ? ` @ ${version}` : '' try { await stat(appPath) @@ -248,23 +281,21 @@ app.get('/', async c => { fileStats = await stat(fullPath) } catch { return c.html( - + Path "{filePath}" not found ) } - const parentPath = filePath.split('/').slice(0, -1).join('/') const versionParam = version !== 'current' ? `&version=${version}` : '' - const backUrl = `/?app=${appName}${versionParam}${parentPath ? `&file=${parentPath}` : ''}` if (fileStats.isFile()) { const content = readFileSync(fullPath, 'utf-8') const language = getLanguage(basename(fullPath)) return c.html( - - ← Back + + {basename(fullPath)}
{content}
@@ -276,8 +307,8 @@ app.get('/', async c => { const files = await listFiles(appPath, filePath) return c.html( - - {filePath && ← Back} + + {files.map(file => ( diff --git a/apps/todo/20260130-181927/package.json b/apps/todo/20260130-181927/package.json index 4c66e55..f81056d 100644 --- a/apps/todo/20260130-181927/package.json +++ b/apps/todo/20260130-181927/package.json @@ -19,6 +19,7 @@ "typescript": "^5.9.2" }, "dependencies": { + "@because/forge": "*", "@because/hype": "*", "@because/toes": "*" } diff --git a/apps/todo/20260130-181927/src/server/index.tsx b/apps/todo/20260130-181927/src/server/index.tsx index bc025d6..4fb4e50 100644 --- a/apps/todo/20260130-181927/src/server/index.tsx +++ b/apps/todo/20260130-181927/src/server/index.tsx @@ -1,5 +1,6 @@ import { Hype } from '@because/hype' -import { theme, define, stylesToCSS, initScript, baseStyles } from '@because/toes/tools' +import { define, stylesToCSS } from '@because/forge' +import { baseStyles, initScript, theme } from '@because/toes/tools' import { readFileSync, writeFileSync, existsSync } from 'fs' import { join } from 'path' diff --git a/apps/versions/20260130-000000/index.tsx b/apps/versions/20260130-000000/index.tsx index 6f89139..db23e44 100644 --- a/apps/versions/20260130-000000/index.tsx +++ b/apps/versions/20260130-000000/index.tsx @@ -1,5 +1,6 @@ import { Hype } from '@because/hype' -import { theme, define, stylesToCSS, initScript, baseStyles } from '@because/toes/tools' +import { define, stylesToCSS } from '@because/forge' +import { baseStyles, initScript, theme } from '@because/toes/tools' import { readdir, readlink, stat } from 'fs/promises' import { join } from 'path' import type { Child } from 'hono/jsx' @@ -11,6 +12,7 @@ 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'), @@ -110,7 +112,6 @@ function Layout({ title, subtitle, children }: LayoutProps) {
Versions - {subtitle && {subtitle}}
{children}
@@ -129,7 +130,7 @@ async function getVersions(appPath: string): Promise<{ name: string; isCurrent: let currentTarget = '' try { currentTarget = await readlink(join(appPath, 'current')) - } catch {} + } catch { } return entries .filter(e => e.isDirectory() && /^\d{8}-\d{6}$/.test(e.name)) diff --git a/apps/versions/20260130-000000/package.json b/apps/versions/20260130-000000/package.json index b56f2f2..1fc3d1b 100644 --- a/apps/versions/20260130-000000/package.json +++ b/apps/versions/20260130-000000/package.json @@ -19,6 +19,7 @@ "typescript": "^5.9.2" }, "dependencies": { + "@because/forge": "*", "@because/hype": "*", "@because/toes": "*" } diff --git a/src/client/components/AppDetail.tsx b/src/client/components/AppDetail.tsx index 270c03f..86d89fa 100644 --- a/src/client/components/AppDetail.tsx +++ b/src/client/components/AppDetail.tsx @@ -151,7 +151,6 @@ export function AppDetail({ app, render }: { app: App, render: () => void }) { return (
- {toolName} {tool.state !== 'running' && (

Tool is {stateLabels[tool.state].toLowerCase()}