Code-Based Apps
Build applications with TypeScript/TSX files
Overview
Section titled “Overview”Bifrost applications are React + Tailwind applications that run inside the Bifrost platform. Apps are built with TypeScript/TSX files organized into pages, layouts, and components.
Everything your app needs is available via a single import:
import { Button, Card, useState, useWorkflowQuery } from "bifrost";External npm packages (declared in app.yaml) use standard imports:
import dayjs from "dayjs";import { LineChart, Line } from "recharts";File Structure
Section titled “File Structure”app.yaml # Metadata (name, description, dependencies)_layout.tsx # Root layout wrapper (required)_providers.tsx # Optional context providersstyles.css # Custom CSS (dark mode via .dark selector)pages/ index.tsx # Home page (/) about.tsx # About page (/about) clients/ index.tsx # Clients list (/clients) [id].tsx # Client detail (/clients/:id)components/ MyComponent.tsx # Shared componentsmodules/ utils.ts # Utility modulesFile Path Conventions
Section titled “File Path Conventions”- Root files:
_layout.tsx(required),_providers.tsx(optional),styles.css(optional),app.yaml - Pages:
pages/*.tsx- automatically become routes - Components:
components/*.tsx- reusable UI - Modules:
modules/*.ts- utilities and helpers - Dynamic routes:
[param].tsxsyntax (e.g.,[id].tsx)
Imports
Section titled “Imports”Everything comes from a single "bifrost" import — React hooks, UI components, Bifrost hooks, icons, and utilities:
import { useState, useEffect, useMemo, useCallback, useRef, useContext, Button, Card, CardHeader, CardTitle, CardContent, useWorkflowQuery, useUser, useNavigate, Settings, ChevronRight, cn, toast, format} from "bifrost";Workflow Hooks
Section titled “Workflow Hooks”The primary way to interact with backend workflows.
useWorkflowQuery(workflowId, params?, options?)
Section titled “useWorkflowQuery(workflowId, params?, options?)”Auto-executes on mount. Use for loading data.
// Load data on mountconst { data, isLoading } = useWorkflowQuery("workflow-uuid", { limit: 10 });
// Conditional loading -- only runs when id is truthyconst { data } = useWorkflowQuery("workflow-uuid", { id }, { enabled: !!id });| Property | Type | Description |
|---|---|---|
data | T | null | Result data |
isLoading | boolean | True while executing |
isError | boolean | True if failed |
error | string | null | Error message |
refetch | () => Promise<T> | Re-execute |
logs | StreamingLog[] | Real-time logs |
Options: { enabled?: boolean } — set false to defer execution.
useWorkflowMutation(workflowId)
Section titled “useWorkflowMutation(workflowId)”Manual execution via execute(). Use for user-triggered actions.
const { execute, isLoading } = useWorkflowMutation("workflow-uuid");const result = await execute({ name: "New Item" });| Property | Type | Description |
|---|---|---|
execute | (params?) => Promise<T> | Run workflow |
isLoading | boolean | True while executing |
data | T | null | Last result |
error | string | null | Error message |
reset | () => void | Reset state |
Other Hooks
Section titled “Other Hooks”| Hook | Description |
|---|---|
useUser() | Current authenticated user { id, email, name } |
useAppState(key, initialValue) | Persistent cross-page state |
useParams() | URL path parameters |
useSearchParams() | Query string parameters |
useNavigate() | Programmatic navigation: navigate("/path") |
useLocation() | Current location object |
React Hooks
Section titled “React Hooks”Standard React hooks are available from the "bifrost" import:
useState, useEffect, useMemo, useCallback, useRef, useContext
Routing
Section titled “Routing”Outlet- Renders child routes in layoutsLink- Navigation links
Pre-included Components
Section titled “Pre-included Components”These are available from "bifrost" without installation. They are standard shadcn/ui components — use them exactly as documented in the shadcn/ui docs.
Layout: Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent
Forms: Button, Input, Label, Textarea, Checkbox, Switch, Select (+ SelectTrigger, SelectContent, SelectItem, SelectGroup, SelectLabel, SelectValue, SelectSeparator), RadioGroup, RadioGroupItem, Combobox, MultiCombobox, TagsInput
Display: Badge, Avatar (+ AvatarImage, AvatarFallback), Alert (+ AlertTitle, AlertDescription), Skeleton, Progress
Navigation: Tabs (+ TabsList, TabsTrigger, TabsContent), Pagination (+ PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious)
Feedback: Dialog (+ DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger), AlertDialog (+ sub-components), Tooltip (+ TooltipContent, TooltipProvider, TooltipTrigger), Popover (+ PopoverContent, PopoverTrigger, PopoverAnchor)
Data: Table (+ TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption)
Date: CalendarPicker, DateRangePicker
Icons: All lucide-react icons (e.g., Settings, ChevronRight, Search, Plus, Trash2, Users, Mail)
Utilities: cn(...) (Tailwind class merging), toast(message) (Sonner notifications), format(date, pattern) (date-fns)
Custom Components
Section titled “Custom Components”Need a component not listed above? Build it in components/. shadcn/ui components are just TSX files — recreate any shadcn component, customize it, or build entirely new ones from scratch with React and Tailwind.
For example, to add a Sheet component, create components/Sheet.tsx using Radix primitives and Tailwind — the same pattern shadcn/ui uses. Or build a rich text editor, a kanban board, a color picker — anything you can build in React.
Custom CSS
Section titled “Custom CSS”Add a styles.css file to your app root for custom styles:
/* CSS variables for theming */:root { --app-primary: oklch(0.5 0.18 260); --app-surface: #fffef9;}
/* Dark mode -- inherits from platform toggle */.dark { --app-primary: oklch(0.7 0.15 260); --app-surface: #1e1e22;}
/* Custom classes */.paper-bg { background-color: var(--app-surface); background-image: repeating-linear-gradient( transparent, transparent 1.7rem, rgba(0,0,0,0.06) 1.7rem, rgba(0,0,0,0.06) 1.75rem );}Use in components: <div className="paper-bg rounded-lg">. Tailwind classes and custom CSS classes can be mixed freely.
External Dependencies
Section titled “External Dependencies”Declare npm packages in app.yaml:
name: My Dashboarddescription: Analytics dashboarddependencies: recharts: "2.12" dayjs: "1.11"Max 20 packages. Loaded at runtime from esm.sh CDN.
Runtime Environment
Section titled “Runtime Environment”Apps run inside the Bifrost shell (not in an iframe). Browser globals (window, document, fetch, ResizeObserver, MutationObserver, etc.) are accessible — use them directly when needed. External npm packages that depend on DOM APIs (rich text editors, drag-and-drop libraries, charting with DOM measurement) work normally.
Cannot use:
- ES dynamic
import()— all dependencies must be declared inapp.yaml - Node.js APIs (
fs,path,process, etc.)
Use useWorkflowQuery/useWorkflowMutation for calling backend workflows. Use fetch directly for external HTTP calls that don’t need backend logic.
Layout Pattern
Section titled “Layout Pattern”The root _layout.tsx is required and must use <Outlet /> for routing.
Your app renders in a fixed-height container. The platform does not scroll the page for you — if a page needs scrolling, add overflow-auto to the element that should scroll.
import { Outlet } from "bifrost";
// _layout.tsx - CORRECTexport default function RootLayout() { return ( <div className="h-full bg-background overflow-hidden"> <Outlet /> </div> );}With Navigation
Section titled “With Navigation”import { useLocation, useNavigate, cn, LayoutDashboard, Building2, Outlet} from "bifrost";
const navItems = [ { path: "/", label: "Dashboard", icon: LayoutDashboard }, { path: "/customers", label: "Customers", icon: Building2 },];
export default function Layout() { const location = useLocation(); const nav = useNavigate();
return ( <div className="flex h-full bg-background overflow-hidden"> <aside className="w-64 border-r bg-card flex flex-col shrink-0"> <nav className="flex-1 p-2"> {navItems.map((item) => { const Icon = item.icon; const isActive = location.pathname === item.path;
return ( <button key={item.path} onClick={() => nav(item.path)} className={cn( "w-full flex items-center gap-3 px-3 py-2 rounded-md text-sm", isActive ? "bg-primary text-primary-foreground" : "hover:bg-muted" )} > <Icon className="h-4 w-4" /> {item.label} </button> ); })} </nav> </aside>
<main className="flex-1 min-w-0 min-h-0 flex flex-col p-6 overflow-hidden"> <Outlet /> </main> </div> );}Complete Example
Section titled “Complete Example”A full page with workflow integration, loading states, and actions.
import { useState, useEffect, useWorkflowQuery, useWorkflowMutation, Card, CardHeader, CardTitle, CardContent, Table, TableHeader, TableBody, TableRow, TableHead, TableCell, Button, Badge, Skeleton, Building2, Loader2, RefreshCw, X, cn} from "bifrost";
interface Customer { id: string; name: string; email: string; status: "active" | "inactive";}
export default function CustomersPage() { const { data, isLoading, refetch } = useWorkflowQuery("ef8cf1f2-b451-47f4-aee8-336f7cb21d33"); const deleteMutation = useWorkflowMutation("abc12345-delete-workflow-id"); const [deleting, setDeleting] = useState<string | null>(null);
const customers: Customer[] = data?.customers || [];
const handleDelete = async (id: string) => { setDeleting(id); try { await deleteMutation.execute({ customer_id: id }); refetch(); } finally { setDeleting(null); } };
if (isLoading && customers.length === 0) { return ( <div className="p-6"> <Skeleton className="h-9 w-48 mb-4" /> <Skeleton className="h-64 w-full" /> </div> ); }
return ( <div className="flex flex-col h-full p-6 overflow-hidden"> {/* Header */} <div className="flex items-center justify-between mb-6 shrink-0"> <div> <h1 className="text-3xl font-bold">Customers</h1> <p className="text-muted-foreground">Manage your customer list</p> </div> <Button variant="outline" onClick={() => refetch()} disabled={isLoading} > {isLoading ? ( <Loader2 className="w-4 h-4 mr-2 animate-spin" /> ) : ( <RefreshCw className="w-4 h-4 mr-2" /> )} Refresh </Button> </div>
{/* Table */} <Card className="flex flex-col min-h-0 flex-1"> <CardHeader className="shrink-0"> <CardTitle className="flex items-center gap-2"> <Building2 className="w-5 h-5" /> Customer List </CardTitle> </CardHeader> <CardContent className="flex-1 min-h-0 overflow-auto"> {customers.length === 0 ? ( <div className="flex flex-col items-center py-12"> <Building2 className="w-12 h-12 text-muted-foreground mb-4" /> <p className="text-muted-foreground">No customers found</p> </div> ) : ( <Table> <TableHeader> <TableRow> <TableHead>Name</TableHead> <TableHead>Email</TableHead> <TableHead>Status</TableHead> <TableHead className="text-right">Actions</TableHead> </TableRow> </TableHeader> <TableBody> {customers.map((customer) => ( <TableRow key={customer.id}> <TableCell className="font-medium">{customer.name}</TableCell> <TableCell>{customer.email}</TableCell> <TableCell> <Badge variant={customer.status === "active" ? "default" : "secondary"} > {customer.status} </Badge> </TableCell> <TableCell className="text-right"> <Button variant="ghost" size="sm" onClick={() => handleDelete(customer.id)} disabled={deleting === customer.id} > {deleting === customer.id ? ( <Loader2 className="w-4 h-4 animate-spin" /> ) : ( <X className="w-4 h-4" /> )} </Button> </TableCell> </TableRow> ))} </TableBody> </Table> )} </CardContent> </Card> </div> );}Building Apps via MCP
Section titled “Building Apps via MCP”Use these MCP tools to build apps:
create_app- Create app metadata (name, slug, description)list_workflows- Get workflow IDs you’ll needreplace_content- Create/update files (entity_type:app_file)get_content- Read existing filespublish_app- Publish when ready (only when user requests)
Workflow
Section titled “Workflow”1. create_app (name, slug)2. list_workflows → get IDs3. replace_content → _layout.tsx4. replace_content → pages/index.tsx5. Preview at /apps/{slug}6. publish_app (when user requests)