Full-Stack Reference
jac-client Reference
Section titled “jac-client Reference”Complete reference for jac-client, the full-stack web development plugin for Jac.
Installation
Section titled “Installation”pip install jac-clientProject Setup
Section titled “Project Setup”Create New Project
Section titled “Create New Project”jac create myapp --use clientcd myappProject Structure
Section titled “Project Structure”myapp/├── jac.toml # Project configuration├── main.jac # Entry point with app() function├── components/ # Reusable components│ └── Button.tsx # TypeScript components supported└── styles/ # CSS files └── main.cssClient Blocks
Section titled “Client Blocks”Use cl { } to define client-side (React) code:
cl { def:pub app() -> JsxElement { return <div> <h1>Hello, World!</h1> </div>; }}Export Requirement
Section titled “Export Requirement”The entry app() function must be exported with :pub:
cl { def:pub app() -> JsxElement { # :pub required return <App />; }}Components
Section titled “Components”Function Components
Section titled “Function Components”cl { def:pub Button(props: dict) -> JsxElement { return <button className={props.get("className", "")} onClick={props.get("onClick")} > {props.children} </button>; }}Using Props
Section titled “Using Props”cl { def:pub Card(props: dict) -> JsxElement { return <div className="card"> <h2>{props["title"]}</h2> <p>{props["description"]}</p> {props.children} </div>; }}Composition
Section titled “Composition”cl { def:pub app() -> JsxElement { return <div> <Card title="Welcome" description="Hello!"> <Button onClick={lambda -> None { print("clicked"); }}> Click Me </Button> </Card> </div>; }}Reactive State
Section titled “Reactive State”The has Keyword
Section titled “The has Keyword”Inside cl { } blocks, has creates reactive state:
cl { def:pub Counter() -> JsxElement { has count: int = 0; # Compiles to useState(0)
return <div> <p>Count: {count}</p> <button onClick={lambda -> None { count = count + 1; }}> Increment </button> </div>; }}How It Works
Section titled “How It Works”| Jac Syntax | React Equivalent |
|---|---|
has count: int = 0 | const [count, setCount] = useState(0) |
count = count + 1 | setCount(count + 1) |
Complex State
Section titled “Complex State”cl { def:pub Form() -> JsxElement { has name: str = ""; has items: list = []; has data: dict = {"key": "value"};
# Create new references for lists/objects def add_item(item: str) -> None { items = items + [item]; # Concatenate to new list }
return <div>Form</div>; }}React Hooks
Section titled “React Hooks”useEffect (Automatic)
Section titled “useEffect (Automatic)”Similar to how has variables automatically generate useState, the can with entry and can with exit syntax automatically generates useEffect hooks:
| Jac Syntax | React Equivalent |
|---|---|
can with entry { ... } | useEffect(() => { ... }, []) |
async can with entry { ... } | useEffect(() => { (async () => { ... })(); }, []) |
can with exit { ... } | useEffect(() => { return () => { ... }; }, []) |
can with [dep] entry { ... } | useEffect(() => { ... }, [dep]) |
can with (a, b) entry { ... } | useEffect(() => { ... }, [a, b]) |
cl { def:pub DataLoader() -> JsxElement { has data: list = []; has loading: bool = True;
# Run once on mount (async with IIFE wrapping) async can with entry { data = await fetch_data(); loading = False; }
# Cleanup on unmount can with exit { cleanup_subscriptions(); }
return <div>...</div>; }
def:pub UserProfile(userId: str) -> JsxElement { has user: dict = {};
# Re-run when userId changes (dependency array) async can with [userId] entry { user = await fetch_user(userId); }
# Multiple dependencies using tuple syntax async can with (userId, refresh) entry { user = await fetch_user(userId); }
return <div>{user.name}</div>; }}useEffect (Manual)
Section titled “useEffect (Manual)”You can also use useEffect manually by importing it from React:
cl { import from react { useEffect }
def:pub DataLoader() -> JsxElement { has data: list = []; has loading: bool = True;
# Run once on mount useEffect(lambda -> None { fetch_data(); }, []);
# Run when dependency changes useEffect(lambda -> None { refresh_data(); }, [some_dep]);
return <div>...</div>; }}useContext
Section titled “useContext”cl { import from react { createContext, useContext }
glob AppContext = createContext(None);
def:pub AppProvider(props: dict) -> JsxElement { has theme: str = "light";
return <AppContext.Provider value={{"theme": theme}}> {props.children} </AppContext.Provider>; }
def:pub ThemedComponent() -> JsxElement { ctx = useContext(AppContext); return <div className={ctx.theme}>Content</div>; }}Backend Integration
Section titled “Backend Integration”Calling Walkers from Client
Section titled “Calling Walkers from Client”Use native Jac spawn syntax to call walkers from client code. First, import your walkers with sv import, then spawn them:
# Import walkers from backendsv import from ...main { get_tasks, create_task }
cl { def:pub TaskList() -> JsxElement { has tasks: list = []; has loading: bool = True;
# Fetch data on component mount async can with entry { result = root spawn get_tasks(); if result.reports and result.reports.length > 0 { tasks = result.reports[0]; } loading = False; }
if loading { return <p>Loading...</p>; }
return <ul> {[<li key={task["id"]}>{task["title"]}</li> for task in tasks]} </ul>; }}Walker Response
Section titled “Walker Response”The spawn call returns a result object:
| Property | Type | Description |
|---|---|---|
result.reports | list | Data reported by walker via report |
result.status | int | HTTP status code |
Mutations (Create, Update, Delete)
Section titled “Mutations (Create, Update, Delete)”sv import from ...main { create_task, toggle_task, delete_task }
cl { def:pub TaskForm() -> JsxElement { has title: str = ""; has tasks: list = [];
async def handleSubmit() -> None { if not title.trim() { return; } result = root spawn create_task(title=title.trim()); if result.reports { tasks = tasks.concat([result.reports[0]]); } title = ""; }
async def handleToggle(taskId: str) -> None { taskId spawn toggle_task(); # Update local state }
return <form onSubmit={lambda e: any -> None { e.preventDefault(); handleSubmit(); }}> <input value={title} onChange={lambda e: any -> None { title = e.target.value; }} /> <button type="submit">Add Task</button> </form>; }}Routing
Section titled “Routing”File-Based Routing (Recommended)
Section titled “File-Based Routing (Recommended)”jac-client supports file-based routing using a pages/ directory:
myapp/├── main.jac└── pages/ ├── index.jac # / ├── about.jac # /about ├── users/ │ ├── index.jac # /users │ └── [id].jac # /users/:id (dynamic route) └── (auth)/ # Route group (parentheses) ├── layout.jac # Shared layout for auth routes ├── login.jac # /login └── signup.jac # /signupEach page file exports a page function:
# pages/about.jaccl { def:pub page() -> any { return <div> <h1>About Us</h1> </div>; }}Manual Routes
Section titled “Manual Routes”For manual routing, import components from @jac/runtime:
cl import from "@jac/runtime" { Router, Routes, Route, Link }
cl { def:pub app() -> JsxElement { return <Router> <nav> <Link to="/">Home</Link> <Link to="/about">About</Link> </nav>
<Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </Router>; }}URL Parameters
Section titled “URL Parameters”cl import from "@jac/runtime" { useParams }
cl { def:pub UserProfile() -> JsxElement { params = useParams(); user_id = params["id"];
return <div>User: {user_id}</div>; }
# Route: /user/:id}Programmatic Navigation
Section titled “Programmatic Navigation”cl import from "@jac/runtime" { useNavigate }
cl { def:pub LoginForm() -> JsxElement { navigate = useNavigate();
async def handle_login() -> None { success = await do_login(); if success { navigate("/dashboard"); } }
return <button onClick={lambda -> None { handle_login(); }}> Login </button>; }}Nested Routes with Outlet
Section titled “Nested Routes with Outlet”cl import from "@jac/runtime" { Outlet }
cl { def:pub DashboardLayout() -> JsxElement { # Child routes render where Outlet is placed return <div> <Sidebar /> <main> <Outlet /> </main> </div>; }}Authentication
Section titled “Authentication”jac-client provides built-in authentication functions via @jac/runtime.
Available Functions
Section titled “Available Functions”| Function | Returns | Description |
|---|---|---|
jacLogin(username, password) | bool | Login user, returns True on success |
jacSignup(username, password) | dict | Register user, returns {success: bool, error?: str} |
jacLogout() | void | Clear auth token |
jacIsLoggedIn() | bool | Check if user is authenticated |
jacLogin
Section titled “jacLogin”cl import from "@jac/runtime" { jacLogin, useNavigate }
cl { def:pub LoginForm() -> any { has username: str = ""; has password: str = ""; has error: str = "";
navigate = useNavigate();
async def handleLogin(e: any) -> None { e.preventDefault(); # jacLogin returns bool (True = success, False = failure) success = await jacLogin(username, password); if success { navigate("/dashboard"); } else { error = "Invalid credentials"; } }
return <form onSubmit={handleLogin}>...</form>; }}jacSignup
Section titled “jacSignup”cl import from "@jac/runtime" { jacSignup }
cl { async def handleSignup() -> None { # jacSignup returns dict with success key result = await jacSignup(username, password); if result["success"] { # User registered and logged in navigate("/dashboard"); } else { error = result["error"] or "Signup failed"; } }}jacLogout / jacIsLoggedIn
Section titled “jacLogout / jacIsLoggedIn”cl import from "@jac/runtime" { jacLogout, jacIsLoggedIn }
cl { def:pub NavBar() -> any { isLoggedIn = jacIsLoggedIn();
def handleLogout() -> None { jacLogout(); # Redirect to login }
return <nav> {isLoggedIn and ( <button onClick={lambda -> None { handleLogout(); }}>Logout</button> ) or ( <a href="/login">Login</a> )} </nav>; }}AuthGuard for Protected Routes
Section titled “AuthGuard for Protected Routes”Use AuthGuard to protect routes in file-based routing:
cl import from "@jac/runtime" { AuthGuard, Outlet }
# pages/(auth)/layout.jaccl { def:pub layout() -> any { return <AuthGuard redirect="/login"> <Outlet /> </AuthGuard>; }}Styling
Section titled “Styling”Inline Styles
Section titled “Inline Styles”cl { def:pub StyledComponent() -> JsxElement { return <div style={{"color": "blue", "padding": "10px"}}> Styled content </div>; }}CSS Classes
Section titled “CSS Classes”cl { def:pub Card() -> JsxElement { return <div className="card card-primary"> Content </div>; }}CSS Files
Section titled “CSS Files”/* styles/main.css */.card { padding: 1rem; border-radius: 8px;}cl { import "./styles/main.css";}TypeScript Integration
Section titled “TypeScript Integration”TypeScript/TSX files are automatically supported:
// components/Button.tsximport React from 'react';
interface ButtonProps { label: string; onClick: () => void;}
export const Button: React.FC<ButtonProps> = ({ label, onClick }) => { return <button onClick={onClick}>{label}</button>;};cl { import from "./components/Button" { Button }
def:pub app() -> JsxElement { return <Button label="Click" onClick={lambda -> None { }} />; }}Configuration
Section titled “Configuration”jac.toml
Section titled “jac.toml”[project]name = "myapp"version = "0.1.0"
[serve]base_route_app = "app" # Serve at /cl_route_prefix = "/cl" # Client route prefix
[plugins.client]enabled = true
[plugins.client.configs.tailwind]# Generates tailwind.config.jscontent = ["./src/**/*.{jac,tsx,jsx}"]CLI Commands
Section titled “CLI Commands”Quick Reference
Section titled “Quick Reference”| Command | Description |
|---|---|
jac create myapp --use client | Create new full-stack project |
jac start | Start dev server |
jac start --dev | Dev server with HMR |
jac start --client pwa | Start PWA (builds then serves) |
jac start --client desktop | Start desktop app in dev mode |
jac build | Build for production (web) |
jac build --client desktop | Build desktop app |
jac build --client pwa | Build PWA with offline support |
jac setup desktop | One-time desktop target setup (Tauri) |
jac setup pwa | One-time PWA setup (icons directory) |
jac add --npm <pkg> | Add npm package |
jac remove --npm <pkg> | Remove npm package |
jac build
Section titled “jac build”Build a Jac application for a specific target.
jac build [filename] [--client TARGET] [-p PLATFORM]| Option | Description | Default |
|---|---|---|
filename | Path to .jac file | main.jac |
--client | Build target (web, desktop, pwa) | web |
-p, --platform | Desktop platform (windows, macos, linux, all) | Current platform |
Examples:
# Build web target (default)jac build
# Build specific filejac build main.jac
# Build PWA with offline supportjac build --client pwa
# Build desktop app for current platformjac build --client desktop
# Build for a specific platformjac build --client desktop --platform windows
# Build for all platformsjac build --client desktop --platform alljac setup
Section titled “jac setup”One-time initialization for a build target.
jac setup <target>| Option | Description |
|---|---|
target | Target to setup (desktop, pwa) |
Examples:
# Setup desktop target (creates src-tauri/ directory)jac setup desktop
# Setup PWA target (creates pwa_icons/ directory)jac setup pwaExtended Core Commands
Section titled “Extended Core Commands”jac-client extends several core commands:
| Command | Added Option | Description |
|---|---|---|
jac create | --use client | Create full-stack project template |
jac create | --skip | Skip npm package installation |
jac start | --client <target> | Client build target for dev server |
jac add | --npm | Add npm (client-side) dependency |
jac remove | --npm | Remove npm (client-side) dependency |
Multi-Target Architecture
Section titled “Multi-Target Architecture”jac-client supports building for multiple deployment targets from a single codebase.
| Target | Command | Output | Setup Required |
|---|---|---|---|
| Web (default) | jac build | .jac/client/dist/ | No |
| Desktop (Tauri) | jac build --client desktop | Native installers | Yes |
| PWA | jac build --client pwa | Installable web app | No |
Web Target (Default)
Section titled “Web Target (Default)”Standard browser deployment using Vite:
jac build # Build for webjac start --dev # Dev server with HMROutput: .jac/client/dist/ with index.html, bundled JS, and CSS.
Desktop Target (Tauri)
Section titled “Desktop Target (Tauri)”Native desktop applications using Tauri. Creates installers for Windows, macOS, and Linux.
Prerequisites:
- Rust/Cargo: rustup.rs
- Build tools (platform-specific)
Setup & Build:
# 1. One-time setup (creates src-tauri/ directory)jac setup desktop
# 2. Development with hot reloadjac start main.jac --client desktop --dev
# 3. Build installer for current platformjac build --client desktop
# 4. Build for specific platformjac build --client desktop --platform windowsjac build --client desktop --platform macosjac build --client desktop --platform linuxOutput: Installers in src-tauri/target/release/bundle/:
- Windows:
.exeinstaller - macOS:
.dmgor.appbundle - Linux:
.AppImage,.deb, or.rpm
Configuration: Edit src-tauri/tauri.conf.json to customize window size, title, and app metadata.
PWA Target
Section titled “PWA Target”Progressive Web App with offline support, installability, and native-like experience.
Features:
- Offline support via Service Worker
- Installable on devices
- Auto-generated
manifest.json - Automatic icon generation (with Pillow)
Setup & Build:
# Optional: One-time setup (creates pwa_icons/ directory)jac setup pwa
# Build PWA (includes manifest + service worker)jac build --client pwa
# Development (service worker disabled for better DX)jac start --client pwa --dev
# Production (builds PWA then serves)jac start --client pwaOutput: Web bundle + manifest.json + sw.js (service worker)
Configuration in jac.toml:
[plugins.client.pwa]theme_color = "#000000"background_color = "#ffffff"cache_name = "my-app-cache-v1"
[plugins.client.pwa.manifest]name = "My App"short_name = "App"description = "My awesome Jac app"Custom Icons: Add pwa-192x192.png and pwa-512x512.png to pwa_icons/ directory.
Development Server
Section titled “Development Server”Start Server
Section titled “Start Server”# Basicjac start main.jac
# With hot module replacementjac start main.jac --dev
# HMR without client bundling (API only)jac start main.jac --dev --no-client
# Dev server for desktop targetjac start main.jac --client desktopAPI Proxy
Section titled “API Proxy”In dev mode, API routes are automatically proxied:
/walker/*→ Backend/function/*→ Backend/user/*→ Backend
Event Handlers
Section titled “Event Handlers”cl { def:pub Form() -> JsxElement { has value: str = "";
return <div> <input value={value} onChange={lambda e: any -> None { value = e.target.value; }} onKeyPress={lambda e: any -> None { if e.key == "Enter" { submit(); } }} /> <button onClick={lambda -> None { submit(); }}> Submit </button> </div>; }}Conditional Rendering
Section titled “Conditional Rendering”cl { def:pub ConditionalComponent() -> JsxElement { has show: bool = False; has items: list = [];
if show { content = <p>Visible</p>; } else { content = <p>Hidden</p>; } return <div> {content}
{show and <p>Only when true</p>}
{[<li key={item["id"]}>{item["name"]}</li> for item in items]} </div>; }}Error Handling
Section titled “Error Handling”JacClientErrorBoundary
Section titled “JacClientErrorBoundary”JacClientErrorBoundary is a specialized error boundary component that catches rendering errors in your component tree, logs them, and displays a fallback UI, preventing the entire app from crashing when a descendant component fails.
Quick Start
Section titled “Quick Start”Import and wrap JacClientErrorBoundary around any subtree where you want to catch render-time errors:
cl import from "@jac/runtime" { JacClientErrorBoundary }
cl { def:pub app() -> any { return <JacClientErrorBoundary fallback={<div>Oops! Something went wrong.</div>}> <MainAppComponents /> </JacClientErrorBoundary>; }}Built-in Wrapping
Section titled “Built-in Wrapping”By default, jac-client internally wraps your entire application with JacClientErrorBoundary. This means:
- You don’t need to manually wrap your root app component
- Errors in any component are caught and handled gracefully
- The app continues to run and displays a fallback UI instead of crashing
| Prop | Type | Description |
|---|---|---|
fallback | JsxElement | Custom fallback UI to show on error |
FallbackComponent | Component | Show default fallback UI with error |
children | JsxElement | Components to protect |
Example with Custom Fallback
Section titled “Example with Custom Fallback”cl { def:pub App() -> any { return <JacClientErrorBoundary fallback={<div className="error">Component failed to load</div>}> <ExpensiveWidget /> </JacClientErrorBoundary>; }}Nested Boundaries
Section titled “Nested Boundaries”You can nest multiple error boundaries for fine-grained error isolation:
cl { def:pub App() -> any { return <JacClientErrorBoundary fallback={<div>App error</div>}> <Header /> <JacClientErrorBoundary fallback={<div>Content error</div>}> <MainContent /> </JacClientErrorBoundary> <Footer /> </JacClientErrorBoundary>; }}If MainContent throws an error, only that boundary’s fallback is shown, while Header and Footer continue rendering normally.
Use Cases
Section titled “Use Cases”- Isolate Failure-Prone Widgets: Protect sections that fetch data, embed third-party code, or are unstable
- Per-Page Protection: Wrap top-level pages/routes to prevent one error from failing the whole app
- Micro-Frontend Boundaries: Nest boundaries around embeddables for fault isolation