First things, first... What the hack does HSX stand for? I'll say it's 'HTML for Server-Side eXtensions'. :0
But, honestly, I prefer: HTMX Slaps Xtremely :)
SSR-only JSX/TSX renderer for Deno that hides HTMX-style attributes away during
the rendering process, and compiles them to hx-* attributes.
Disclaimer: this was a quick hack in my free time, held together by vibe coding and espresso. I like it a lot, but consider it an early release. I feel it is getting better (a lot)
- SSR-only - No client runtime. Outputs plain HTML.
- HTMX as HTML - Write
get,post,target,swapas native attributes - Type-safe routes - Branded
Route<Path>types with automatic parameter inference - Co-located components -
hsxComponent()bundles route + handler + render - Page guardrails -
hsxPage()enforces semantic, style-free layouts - Branded IDs -
id("name")returnsId<"name">typed as"#name" - Auto HTMX injection -
<script src="/static/htmx.js">injected when needed - No manual hx-* - Throws at render time if you write
hx-getdirectly - Widgets - Define once, serve via SSR or embed as iframes with Declarative Shadow DOM
- Generative UI - AI models select and render widgets via tool calling, streamed to the browser via SSE + HTMX
- Agent-operable - Any
hsxComponentthat declaresdescribe+inputbecomes an AI tool; an agent drives your real endpoints from the same definition that serves humans - HSX Lens - Opt-in dev workbench that maps page samples, components, widgets, targets, and agent tools into a hypermedia manifest
deno add jsr:@srdjan/hsxOr import directly:
import { id, render, route } from "jsr:@srdjan/hsx";HSX is a monorepo with seven packages:
// Core - JSX rendering, type-safe routes, hsxComponent, hsxPage, SSE
import {
Fragment,
hsxComponent,
hsxPage,
id,
render,
renderSSE,
route,
} from "jsr:@srdjan/hsx";
// Styles - ready-to-use CSS with theming support
import { HSX_STYLES_PATH, hsxStyles } from "jsr:@srdjan/hsx-styles";
// Widgets - embeddable widget protocol + SSR/embed adapters + GenUI catalog
import { widgetToHsxComponent } from "jsr:@srdjan/hsx-widgets/ssr";
import { createCatalog, type GenUIWidget } from "jsr:@srdjan/hsx-widgets";
// GenUI - AI-powered generative UI with tool calling
import {
createConversationStore,
createGenUIHandler,
createGenUIRoutes,
} from "jsr:@srdjan/hsx-genui";
import { claudeProvider } from "jsr:@srdjan/hsx-genui/claude";
// Agent - make your app agent-operable (components become AI tools)
import { createAppAgent } from "jsr:@srdjan/hsx-agent";
// MCP - serve those components to external MCP clients (Claude Code, ...)
import { createMcpHandler } from "jsr:@srdjan/hsx-mcp";
// Lens - inspect the app's hypermedia contract during development
import { createHsxLens } from "jsr:@srdjan/hsx-lens";Install individually:
deno add jsr:@srdjan/hsx
deno add jsr:@srdjan/hsx-styles
deno add jsr:@srdjan/hsx-widgets
deno add jsr:@srdjan/hsx-genui
deno add jsr:@srdjan/hsx-agent
deno add jsr:@srdjan/hsx-mcp
deno add jsr:@srdjan/hsx-lensFor smaller bundles, import only what you need:
// Core only - render, route, id, Fragment (smaller bundle)
import { Fragment, id, render, route } from "jsr:@srdjan/hsx/core";
// Components only - hsxComponent, hsxPage
import { hsxComponent, hsxPage } from "jsr:@srdjan/hsx/components";
// Everything (default)
import { hsxComponent, hsxPage, render, route } from "jsr:@srdjan/hsx";Clone and import using workspace package names:
import { hsxComponent, hsxPage, id, render, route } from "@srdjan/hsx";Note: This shows the low-level API using
route(). For most projects, use the hsxComponent pattern below instead.
import { id, render, route } from "@srdjan/hsx";
const routes = {
todos: route("/todos", () => "/todos"),
};
const ids = {
list: id("todo-list"),
};
function Page() {
return (
<html>
<head>
<title>HSX Demo</title>
</head>
<body>
<form post={routes.todos} target={ids.list} swap="outerHTML">
<input name="text" required />
<button type="submit">Add</button>
</form>
<ul id="todo-list">{/* items */}</ul>
</body>
</html>
);
}
Deno.serve(() => render(<Page />));Output HTML:
<html>
<head><title>HSX Demo</title></head>
<body>
<form hx-post="/todos" hx-target="#todo-list" hx-swap="outerHTML">
<input name="text" required>
<button type="submit">Add</button>
</form>
<ul id="todo-list"></ul>
<script src="/static/htmx.js"></script>
</body>
</html>import { hsxComponent } from "jsr:@srdjan/hsx";
export const TodoList = hsxComponent("/todos", {
methods: ["GET", "POST"],
async handler(req) {
if (req.method === "POST") {
const form = await req.formData();
await addTodo(String(form.get("text")));
}
return { todos: await getTodos() }; // must match render props
},
render({ todos }) {
return (
<ul id="todo-list">
{todos.map((t) => <li key={t.id}>{t.text}</li>)}
</ul>
);
},
});
// Use as route in JSX
<form post={TodoList} target="#todo-list" swap="outerHTML" />;
// Use as handler in your server
if (TodoList.match(url.pathname)) return TodoList.handle(req);TypeScript enforces that handler returns the same shape that render expects.
methods defaults to ["GET"]; set fullPage: true when your render function
returns a full document instead of a fragment.
Choose one style: Use either
hsxComponent(recommended) or the low-levelroute()API, but don't mix them in the same project.
hsxPage() wraps a render function that returns a complete HTML document
and validates that:
- The root is
<html>with<head>then<body> - Semantic tags (header/main/section/article/h1-h6/p/ul/ol/li/etc.) have no
classor inlinestyle <style>tags live in<head>; CSS belongs there, not inline- Composition stays within semantic HTML + HSX components
import { hsxComponent, hsxPage } from "jsr:@srdjan/hsx";
const Widget = hsxComponent("/data", {
handler: () => ({ message: "Hi" }),
render: ({ message }) => <p>{message}</p>,
});
const Page = hsxPage(() => (
<html lang="en">
<head>
<title>Guarded Page</title>
<style>{"body { font-family: system-ui; }"}</style>
</head>
<body>
<header>
<h1>Welcome</h1>
</header>
<main>
<section>
<div data-surface="card">
<Widget.Component />
</div>
</section>
</main>
</body>
</html>
));
Deno.serve(() => Page.render());| HSX Attribute | Renders To | Description |
|---|---|---|
get |
hx-get |
HTTP GET request |
post |
hx-post |
HTTP POST request |
put |
hx-put |
HTTP PUT request |
patch |
hx-patch |
HTTP PATCH request |
delete |
hx-delete |
HTTP DELETE request |
target |
hx-target |
Element to update |
swap |
hx-swap |
How to swap content |
trigger |
hx-trigger |
Event that triggers request |
vals |
hx-vals |
Additional values (JSON) |
headers |
hx-headers |
Custom headers (JSON) |
behavior="boost" |
hx-boost="true" |
Enable boost mode (<a> only) |
Supported elements: form, button, a, div, span, section,
article, ul, tbody, tr
Use route() to create type-safe routes with automatic parameter extraction:
const routes = {
users: {
list: route("/users", () => "/users"),
detail: route("/users/:id", (p) => `/users/${p.id}`),
posts: route(
"/users/:userId/posts/:postId",
(p) => `/users/${p.userId}/posts/${p.postId}`,
),
},
};
// In JSX - params are type-checked:
<button get={routes.users.detail} params={{ id: 42 }}>View</button>;
// Renders: <button hx-get="/users/42">View</button>Use id() to create type-safe element references:
const ids = {
list: id("todo-list"), // Type: Id<"todo-list"> = "#todo-list"
count: id("item-count"),
};
// In JSX:
<ul id="todo-list">...</ul>
<button get="/todos" target={ids.list} swap="innerHTML">Refresh</button>
// Renders: <button hx-get="/todos" hx-target="#todo-list" hx-swap="innerHTML">Create reusable wrapper components that pass through HSX attributes for cleaner JSX:
import type { HsxSwap, Urlish } from "jsr:@srdjan/hsx";
function Card(props: {
children: unknown;
title?: string;
get?: Urlish;
trigger?: string;
swap?: HsxSwap;
}) {
return (
<div
data-surface="card"
data-layout="stack"
data-gap="4"
get={props.get}
trigger={props.trigger}
swap={props.swap}
>
{props.title && <h2>{props.title}</h2>}
{props.children}
</div>
);
}
function Subtitle(props: { children: string }) {
return (
<div data-ui="prose">
<p>{props.children}</p>
</div>
);
}Usage:
function Page() {
return (
<main>
<h1>Dashboard</h1>
<Subtitle>Content loads lazily</Subtitle>
<Card
get={routes.stats}
trigger="load"
swap="innerHTML"
title="Statistics"
>
<LoadingSkeleton />
</Card>
<Card title="Team Members">
<UserList />
</Card>
</main>
);
}This pattern keeps your page components clean while maintaining full access to
HSX attributes. See the examples/*/components.tsx files for more examples.
Add this to your deno.json:
JSX intrinsic element types (<div>, <form>, <button>, etc.) are
automatically included via the jsx-runtime import—no additional type
configuration needed.
HSX injects <script src="/static/htmx.js"> when HTMX is used. You must serve
it:
if (url.pathname === "/static/htmx.js") {
const js = await Deno.readTextFile("./vendor/htmx/htmx.js");
return new Response(js, {
headers: { "content-type": "text/javascript; charset=utf-8" },
});
}HSX includes an optional Auras-based CSS module with a single bundled stylesheet:
import { HSX_STYLES_PATH, hsxStyles } from "jsr:@srdjan/hsx-styles";
// Serve the styles
if (url.pathname === HSX_STYLES_PATH) {
return new Response(hsxStyles, {
headers: { "content-type": "text/css; charset=utf-8" },
});
}
// In your page head
<link rel="stylesheet" href={HSX_STYLES_PATH} />;Exports:
hsxStyles- Auras Elements + HSX brand layerHSX_STYLES_PATH- Default path:/static/hsx.css
What you get:
- Layout via data attributes:
data-layout="row|col|stack|cluster|grid|container",data-gap,data-align,data-justify,data-grid-min - Surfaces via attributes:
data-surface="card"anddata-surface="notice" - Button variants via
data-variant="solid|soft|ghost" - Theme controls via
data-theme="dark",data-contrast="more", anddata-motion="reduce"
Customization: Override CSS variables in your page:
<style>
{`:root { --primary: #10b981; --bg: #f0fdf4; --border: #a7f3d0; }`}
</style>;Core tokens include --primary, --primary-hover, --primary-subtle, --bg,
--surface, --surface-raised, --border, --text, --text-muted, spacing
(--space-*), radius (--radius-*), typography (--font-display, --text-*,
--leading-*), shadows (--shadow-*), and layout tokens such as
--container-max.
To force dark mode for a specific page, add data-theme="dark" to <html>.
The @srdjan/hsx-widgets package provides a widget protocol for building
components that work in two contexts: SSR through HSX routes, and embeddable
iframe shells for third-party pages.
A widget is a typed record with validation, styles, rendering, and optional data loading:
import type { Widget } from "jsr:@srdjan/hsx-widgets";
import { fail, ok } from "jsr:@srdjan/hsx-widgets";
export const greetingWidget: Widget<GreetingProps> = {
tag: "hsx-greeting",
props: { validate(raw) {/* ... */} },
styles: `.hsx-greeting { padding: 1rem; }`,
render: (props) => (
<div class="hsx-greeting">
<h2>{props.name}</h2>
</div>
),
load: async (params) => ok({ name: params.name, message: `Hello!` }),
shadow: "open", // Optional: Declarative Shadow DOM
};Use widgetToHsxComponent() to bridge a widget into an HSX route:
import { widgetToHsxComponent } from "jsr:@srdjan/hsx-widgets/ssr";
const GreetingRoute = widgetToHsxComponent(greetingWidget, {
path: "/widgets/greeting/:name",
});
if (GreetingRoute.match(url.pathname)) return GreetingRoute.handle(req);Serve iframe shells with createEmbedHandler(), then embed with a snippet:
<div data-hsx-uri="https://yoursite.com/embed/hsx-greeting?name=World"></div>
<script src="https://yoursite.com/static/hsx/snippet.js"></script>See docs/WIDGETS.md for the full widget guide including Declarative Shadow DOM, style hoisting, and the build pipeline.
The @srdjan/hsx-genui package lets AI models render interactive widgets
directly in a chat interface. The AI selects from a catalog of pre-registered
widgets via tool calling, and rendered HTML streams to the browser via SSE +
HTMX.
A GenUI widget extends the regular Widget protocol with AI metadata - a description, JSON Schema for props, and optional few-shot examples:
import type { GenUIWidget } from "jsr:@srdjan/hsx-widgets";
import { ok } from "jsr:@srdjan/hsx-widgets";
const weatherWidget: GenUIWidget<WeatherProps> = {
tag: "hsx-weather",
description:
"Shows current weather for a city with temperature and conditions",
schema: {
type: "object",
properties: {
city: { type: "string", description: "City name" },
},
required: ["city"],
},
category: "display",
props: {
validate(raw) {
/* ... */ return ok({ city: raw.city });
},
},
styles: `.weather { padding: 1rem; }`,
render: ({ city, temp }) => (
<div class="weather">
<h3>{city}</h3>
<p>{temp}</p>
</div>
),
load: async (params) => {
const data = await fetchWeather(params.city);
return ok(data);
},
};Register widgets in a catalog, then wire up the GenUI handler with an AI provider:
import { createCatalog } from "jsr:@srdjan/hsx-widgets";
import {
createConversationStore,
createGenUIHandler,
createGenUIRoutes,
} from "jsr:@srdjan/hsx-genui";
import { claudeProvider } from "jsr:@srdjan/hsx-genui/claude";
// 1. Register widgets
const catalog = createCatalog([weatherWidget, chartWidget]);
// 2. Create handler with Claude provider
const handler = createGenUIHandler({
catalog,
provider: claudeProvider({ model: "claude-sonnet-4-6" }),
});
// 3. Create routes (page shell + POST endpoint + SSE stream)
const store = createConversationStore();
const { page, send, stream } = createGenUIRoutes({ handler, store });
// 4. Serve
Deno.serve((req) => {
const { pathname } = new URL(req.url);
if (page.match(pathname)) return page.handle(req);
if (send.match(pathname)) return send.handle(req);
if (stream.match(pathname)) return stream.handle(req);
return new Response("Not Found", { status: 404 });
});The user types a message, the AI selects a widget tool, HSX renders it server-side, and the result streams into the page via SSE. No client-side framework needed.
Every catalog includes a built-in hsx-raw tool that lets the AI generate
arbitrary HTML when no pre-registered widget fits. Raw HTML is sanitized via an
allowlist-based sanitizer (disallowed tags, event handlers, and dangerous URI
schemes are stripped) and rendered inside a closed Shadow DOM for style
isolation.
Include AI-readable design constraints in the system prompt:
import { createDesignGuidelines, formatForAI } from "jsr:@srdjan/hsx-widgets";
const guidelines = createDesignGuidelines({
colors: "Use indigo accent. All colors via CSS custom properties.",
});
const handler = createGenUIHandler({
catalog,
provider,
systemPrompt: formatForAI(guidelines),
includeGuidelines: false, // we provided our own
});The core @srdjan/hsx package also exports renderSSE() for building custom
SSE endpoints from any async iterable of JSX:
import { renderSSE } from "jsr:@srdjan/hsx/core";
async function* generateWidgets() {
yield <div>Loading...</div>;
const data = await fetchData();
yield <Chart data={data} />;
}
Deno.serve(() => renderSSE(generateWidgets()));Pairs with HTMX's SSE extension:
<div ext="sse" sseConnect="/stream" sseSwap="message">
{/* widgets appear here as SSE events arrive */}
</div>;The @srdjan/hsx-agent package turns your existing application into an agent
action space. Where GenUI lets an AI render display widgets, this lets an AI
drive your real endpoints: each hsxComponent that declares describe +
input becomes an AI tool from the same definition that serves humans over
HTMX. The agent calls the component's own handle() and reads back the exact
semantic HTML a person would see. One definition, two consumers.
A component is agent-callable only when it declares both describe and
input. Components without them are invisible to the agent - no surprise
mutations.
import { createAppAgent } from "@srdjan/hsx-agent";
import { claudeProvider } from "@srdjan/hsx-genui/claude";
// The same component the human form posts to is now also an AI tool:
const AddTodo = hsxComponent("/todos", {
methods: ["POST"],
describe: "Add a new todo item to the list.",
input: {
schema: {
type: "object",
properties: { text: { type: "string", description: "The todo text." } },
required: ["text"],
},
},
async handler(req) {
const form = await req.formData();
addTodo(String(form.get("text")));
return {};
},
render: () => <TodoListView />,
});
// Humans use it via HTMX (unchanged):
<form post={AddTodo} swap="none" />;
// The agent uses the same definition as a tool:
const agent = createAppAgent({
components: [AddTodo, ToggleTodo, ClearDone],
provider: claudeProvider({ model: "claude-sonnet-4-6" }),
});AppAgent is structurally a GenUI handler ({ handleMessage, tools }), so it
drops straight into the pre-built chat routes:
import { createConversationStore, createGenUIRoutes } from "@srdjan/hsx-genui";
const store = createConversationStore();
const { send, stream } = createGenUIRoutes({
handler: agent,
store,
basePath: "/copilot",
});The agent's tool calls invoke the real components; each rendered fragment is
streamed to the browser and fed back to the model as its observation. In the
todos-copilot example the mutating components render the list with swapOob,
so whether a change comes from the human form or the agent, the canonical
#todo-list updates out-of-band while the agent narrates in the chat. Run it
with ANTHROPIC_API_KEY=... deno task example:todos-copilot.
componentsToTools(components) and toRequest(component, args, origin) are
exported for building custom agent loops on top of the same metadata.
The @srdjan/hsx-mcp package mounts a Model Context Protocol endpoint into your
existing Deno.serve, so external MCP clients - Claude Code, Claude Desktop,
any MCP-capable tool - can discover and operate the same agent-callable
components. The in-app copilot needs a chat UI and an API key; the MCP endpoint
needs neither. Write a component once and it serves humans over HTMX, your
embedded copilot over SSE, and every MCP client over JSON-RPC, all through the
component's own handle().
import { createMcpHandler } from "@srdjan/hsx-mcp";
const mcp = createMcpHandler({
components: todoComponents,
serverName: "todos",
// REQUIRED before exposing beyond localhost:
// bearerToken: Deno.env.get("MCP_TOKEN"),
});
Deno.serve((req) => {
const mcpResponse = mcp.handle(req);
if (mcpResponse) return mcpResponse;
// Your app routes...
return new Response("Not found", { status: 404 });
});Connect from Claude Code:
claude mcp add --transport http todos http://localhost:8000/mcpTool results carry the HTTP status plus the same rendered HTML a human receives,
so the agent observes the app as a user sees it. Pass a lens manifest
(createHsxManifest() output) as manifest to expose the app's full hypermedia
contract as MCP resource hsx://manifest.
The endpoint is stateless Streamable HTTP: POST-only, plain JSON responses, no sessions, no SDK dependency - the protocol subset is hand-rolled like the raw-fetch Claude adapter.
Security: MCP tools mutate real application state. Never mount the endpoint
on a publicly reachable server without authorize or bearerToken.
The @srdjan/hsx-lens package is an opt-in development workbench for the
hypermedia contract your app already declares. It renders explicit page samples,
collects author-time HSX attributes before they become hx-*, mirrors
component/widget/agent metadata, and serves a local manifest plus an HTML
workbench. It does not call component handlers.
import { createHsxLens } from "@srdjan/hsx-lens";
const lens = createHsxLens({
appName: "Todos",
pages: [{ name: "Home", path: "/", render: () => <Page.Component /> }],
components: todoComponents,
});
Deno.serve((req) => {
const lensResponse = lens.handle(req);
if (lensResponse) return lensResponse;
// Your app routes...
return new Response("Not found", { status: 404 });
});By default, the workbench is served at /__hsx and the JSON manifest at
/__hsx/manifest.json. Mount it only in local or trusted development
environments.
Renders JSX to an HTTP Response.
render(<Page />, {
status: 200, // HTTP status code
headers: {}, // Additional response headers
maxDepth: 100, // Max nesting depth (DoS protection)
maxNodes: 50000, // Max node count (DoS protection)
injectHtmx: undefined, // true/false to force, undefined for auto
});Renders JSX to an HTML string.
const html = renderHtml(<Page />, {
maxDepth: 100,
maxNodes: 50000,
injectHtmx: undefined,
});Creates a type-safe route. Path parameters (:param) are automatically
extracted.
const r = route("/users/:id", (p) => `/users/${p.id}`);
// r.path = "/users/:id"
// r.build({ id: 42 }) = "/users/42"Creates a branded element ID with # prefix.
const listId = id("todo-list");
// Type: Id<"todo-list">
// Value: "#todo-list"JSX Fragment for grouping elements without a wrapper.
<Fragment>
<li>One</li>
<li>Two</li>
</Fragment>;Co-locates a route, request handler, and render function.
const Comp = hsxComponent("/items/:id", {
methods: ["GET", "DELETE"], // defaults to ["GET"]
fullPage: false, // default: return fragment Response
status: 200,
headers: { "x-powered-by": "hsx" },
handler: async (_req, params) => ({
item: await getItem(params.id),
}),
render: ({ item }) => <div>{item.name}</div>,
});
// Works anywhere a Route does:
<button delete={Comp} params={{ id: 42 }} target="#row-42" />;Unexpected handler or render errors return a generic 500 response, while
handlers can throw HsxHttpError for intentional boundary failures such as
invalid input.
Run examples with deno task:
| Example | Command | Description |
|---|---|---|
| Todos | deno task example:todos |
Full CRUD with partial updates |
| Active Search | deno task example:active-search |
Live search as you type |
| Lazy Loading | deno task example:lazy-loading |
Deferred content loading |
| Form Validation | deno task example:form-validation |
Server-side validation |
| Polling | deno task example:polling |
Live dashboard with intervals |
| Auras Showcase | deno task example:auras-showcase |
First-impression demo, live scene playground, compact reference |
| Tabs & Modal | deno task example:tabs-modal |
Tab navigation and modals |
| HSX Components | deno task example:hsx-components |
Co-located route + handler + render |
| HSX Page | deno task example:hsx-page |
Semantic full-page with hsxPage guardrails |
| Low-Level API | deno task example:low-level-api |
Manual render/renderHtml without hsxPage/hsxComponent |
| HSX Widget | deno task example:hsx-widget |
Widget SSR route + iframe embed shell |
| Todos Copilot | deno task example:todos-copilot |
AI copilot driving real hsxComponent endpoints |
| Index of examples | examples/README.md |
Quick guide to pick the right example |
For the HSX widget example, build client assets first:
deno task build:hsx-widgets
deno task example:hsx-widget- HTML escaping - All text content and attributes are escaped (XSS prevention)
- Raw text elements -
<script>and<style>children are NOT escaped. Never pass user input. - No manual hx-* - Throws at render time. Use HSX aliases instead.
- DoS protection - Optional
maxDepthandmaxNodeslimits
packages/
hsx/ # Core package (@srdjan/hsx)
mod.ts # Main entry point
jsx-runtime.ts # Minimal JSX runtime (compiler requirement)
render.ts # SSR renderer with HTMX injection
sse.ts # SSE response helper (renderSSE, encodeSSEFrame)
loading.ts # Loading placeholder component
hsx-normalize.ts # HSX to hx-* attribute mapping
hsx-types.ts # Route, Id, HsxSwap, HsxTrigger types
hsx-component.ts # hsxComponent factory (route + handler + render)
hsx-page.ts # hsxPage guardrail for full-page layouts
hsx-styles/ # Styles package (@srdjan/hsx-styles)
mod.ts # Entry point (bundles CSS strings)
hsx-brand.css # HSX brand layer
vendor/ # Vendored Auras foundation
hsx-widgets/ # HSX widgets package (@srdjan/hsx-widgets)
mod.ts # Main entry point
widget.ts # Widget protocol
widget-wrapper.ts # Shared Light DOM / Shadow DOM wrapping
ssr-adapter.ts # Widget -> hsxComponent bridge
styles.ts # Style collection for hsxPage
result.ts # Result<T,E> type utilities
genui-widget.ts # GenUIWidget<P> type (Widget + AI metadata)
catalog.ts # Widget catalog and tool definition generation
raw-widget.ts # Raw HTML escape hatch (Shadow DOM sandbox)
sanitize.ts # Allowlist-based HTML sanitizer
design-guidelines.ts # AI-readable design system constraints
embed/ # Embed helpers (iframe shell + snippet)
build/ # Dual-compile build pipeline (esbuild + Preact)
hsx-genui/ # Generative UI package (@srdjan/hsx-genui)
mod.ts # Main entry point
provider.ts # AIProvider port (Message, ToolCall, StreamEvent)
handler.ts # GenUI handler (AI conversation loop)
conversation.ts # Immutable Conversation + in-memory store
components.tsx # Pre-built chat page + send routes
providers/
claude.ts # Claude/Anthropic adapter (raw fetch + SSE)
hsx-agent/ # Agent-operable apps package (@srdjan/hsx-agent)
mod.ts # Main entry point
app-agent.ts # createAppAgent (AI drives real components)
component-tools.ts # componentsToTools (components -> AI tool defs)
request-build.ts # toRequest (tool-call args -> Request)
types.ts # AgentComponent structural type
hsx-mcp/ # MCP server package (@srdjan/hsx-mcp)
mod.ts # Main entry point
mcp-handler.ts # createMcpHandler (Streamable HTTP endpoint)
protocol.ts # MCP method dispatch (initialize/tools/resources)
jsonrpc.ts # Hand-rolled JSON-RPC 2.0 envelope
types.ts # McpHandlerOptions, McpHandler
hsx-lens/ # Dev workbench package (@srdjan/hsx-lens)
mod.ts # Main entry point
manifest.ts # createHsxManifest (samples -> manifest)
workbench.tsx # createHsxLens (local HTML + JSON routes)
examples/
todos/ # Full todo app example
todos-copilot/ # Todos with an AI copilot driving real endpoints
active-search/ # Search example
lazy-loading/ # Lazy load example
form-validation/ # Form validation example
polling/ # Polling example
tabs-modal/ # Tabs and modal example
hsx-components/ # HSX Component pattern example
hsx-page/ # hsxPage full-page guardrail example
low-level-api/ # Manual render/renderHtml without hsxPage/hsxComponent
hsx-widget/ # HSX widget SSR + embed shell example
vendor/htmx/
htmx.js # Vendored HTMX v4 (alpha)
docs/
EXAMPLES.md # Full examples matrix
USER_GUIDE.md # Comprehensive user guide
HSX_OVERVIEW.md # Architecture overview
HTMX_INTEGRATION.md # HTMX integration details
WIDGETS.md # HSX widget guide and embed workflow
MIT - see LICENSE
{ "imports": { "hsx/jsx-runtime": "jsr:@srdjan/hsx/jsx-runtime" }, "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "hsx" } }