Skip to content

Latest commit

 

History

History
122 lines (89 loc) · 5.16 KB

README.md

File metadata and controls

122 lines (89 loc) · 5.16 KB

Patch is a compiled UI framework built for agents. With Patch, agents can inspect an interface and put it into any state directly from the source, without tracing styles through layers of CSS or replaying interactions to reach a particular screen.

  • Local styling. An element's styles are attached directly to it. There are no selectors, classes, or cascade, so nothing elsewhere in the tree can restyle it, and only a small, well-defined set of properties is supported.
  • Observable state. App state can be read and overridden from outside the app, which makes it easy to set up fixtures, reproduce bugs, write tests, and export designs.

Syntax

Markup and reactivity work like Svelte. Use $style for reusable styles and inline objects for one-offs.

<script lang="ts">
  let count = $state(0)

  const button = $style({
    padding: [8, 12],
    borderRadius: 6
  })
</script>

<button
  style={button}
  onclick={() => count++}
>
  Count: {count}
</button>

Inline styles are validated the same way as $style:

<div
  style={{
    width: "fill",
    padding: 12,
    gap: 8
  }}
>
  ...
</div>

Usage

Link the CLI locally:

bun link

Start the dev server:

patch dev app.pat

Build for production:

patch build app.pat

Take a snapshot of the app and copy it to the clipboard as editable layers, ready to paste into Figma:

patch snapshot figma app.pat

Or write it as a standalone HTML file (app.snapshot.html, or a path you pass):

patch snapshot html app.pat

Snapshots render the app from its initial state, without running $effects, so they show exactly that state. You can override any $state value, including the state of one component instance (--state 'Row@22:5[42]/expanded=true'):

patch snapshot html app.pat \
  --state loading=true \
  --state filter=done

An app with several pages is a directory whose src/pages/**/+page.pat files are its routes (src/pages/issues/[id]/+page.pat is /issues/:id). Snapshots pick the page with --url, and patch routes lists every route without running the app:

patch dev examples/issue-tracker
patch snapshot html examples/issue-tracker --url /issues/231
patch routes examples/issue-tracker

Agents can use HTML snapshots to visually verify the code they write: snapshot the screen in the state they just changed, open the file in a browser, and look at it.

Since state can be set from outside, an agent can jump straight to the screen it wants to look at instead of clicking through the app, mocking network requests, or seeding a database to get there. The same mechanism is what fixtures are built on.

Editor

editors/vscode is the VS Code extension. Build it with bun install && bun run package there (package.ts), and install pat.vsix. It:

  • highlights .pat files and shows every compiler diagnostic, type errors included;
  • shows Patch's diagnostics for .pat.ts / .pat.js modules;
  • adds a plugin to VS Code's TypeScript, so in a Patch project (one with .pat files) the editor's TypeScript sees the runes, .pat imports, the 'patch' module and module state exactly as Patch's checker does, and reports runes used in plain .ts files. Outside Patch projects it does nothing.

It runs the language server with the patch that bun link installed, or with the repository it is in. When developing it (the "PAT extension" launch configuration), run bun install there again after changing ts-plugin/: VS Code's TypeScript loads the plugin from node_modules, where it is installed as a copy.

Scope

Patch currently supports apps made of .pat components with:

  • TS/JS in <script>, with type checking
  • Imports from npm packages and local TS/JS modules
  • $state, $derived, and $effect, and module state shared by the whole app in .pat.ts modules (export const workspace = $state({ … }))
  • Components: import Card from './Card.pat', read-only reactive props from $props(), callback props instead of two-way binding, children and snippets ({#snippet}, {@render}), and context (createContext, setContext, getContext from 'patch')
  • {#if}, {:else if}, {:else}, and {#each}, keyed ({#each list as item (item.id)}) or by position
  • <main>, <div>, <button>, <a>, <p>, <span>, <input>, and <textarea>
  • Pages and routing: src/pages routes with [param] segments, page (url, params, route.id) and goto from 'patch', client-side links, browser history, and a not-found page
  • Portals: <portal id="overlay" /> and portal="overlay" render an element elsewhere without changing where it belongs
  • onclick={...}-style event handlers, bind:value on inputs, and bind:this for the underlying DOM element
  • Flex layout with fixed, hug, and fill sizing, and scroll containers (overflow, overflowX, overflowY)
  • Typography, fills, borders, radii, opacity, shadows, and positioning, including elements anchored to another element from bind:this, with collision-aware placement and anchor-relative sizing
  • A Vite dev server with live reload, and production builds to a single standalone HTML file
  • Snapshots of any state as editable Figma layers or standalone HTML

Still WIP.