PocketFactory

Describe the app you need.
It runs here minutes later.

This is a workshop for the small applications a team actually runs on — a form to collect requests, a shared register, a stock list, a dashboard. You say what you want in your own words to an AI assistant, and it builds the whole thing: the screens people use, the database behind them, and who is allowed to see what.

Then it stays up. Each app gets an address of its own the moment it is built — nothing to install, no server to rent, no deploying — and everyone reaches all of them with one account.

Need a change? Ask for it the same way. The assistant can read the app's own errors, so it can usually fix what it got wrong without anyone going to look.

How it works The architecture, what an agent can do, and what apps are built from

How it fits together

The master is a control plane. It holds the registry and the user pool, answers MCP calls, and supervises the fleet — but it never sits in an app's request path.

master /mcp · /_/ · /auth registry · user pool · supervisor todo :43000 own db · hooks · dashboard notes :43001 own db · hooks · dashboard shop :43002 own db · hooks · dashboard
Each app is a separate operating-system process, because generated apps run their own server-side JavaScript. That code gets its own address space, and stays killable and restartable on its own.

What an agent can do

Ten tools over one MCP endpoint at /mcp.

upsert_app

Create or update an app in one call — its React sources, its server-side hooks and its database schema together.

get_app

Read an app back: its sources and the schema it declared, in the same shape upsert_app takes.

list_apps

Every app with its URL, state and port.

get_logs

Browser console, server logs, process output and lifecycle events for one app — enough to diagnose a page without opening it.

restore_version

Puts an app back to a version it held earlier — the sources it stored and the schema they were written against. It rolls forward: what the old version held becomes the next one, so a restore can itself be restored. The schema becomes exactly what that version declared, so it asks before dropping anything and names what would go.

start_app · stop_app

Lifecycle. Data is untouched by a stop.

secrets

An app's vault: the credentials its server-side code needs and its source must not carry. Write only — a secret goes in and is never read back out, not by this tool and not by any other. Hooks read it as $secrets.get("NAME"); nothing reaches the browser, and nothing reaches across apps.

rename_app

Moves an app to a new slug — its subdomain, its directory and the name every other tool calls it by. The database, the owners, the stored versions and the history come along; every link to the old address stops resolving, so it asks.

archive_app

Stops an app and sets it aside. The directory is renamed rather than removed and the slug is freed, so the data survives and the name can be reused.

delete_app

Removes the process, the registry entry and the whole directory. Irreversible, so it asks.

owners

Who may reach an app, at one of two levels: owner may change it, user may only open it. Whoever creates one owns it; owners bring in whoever else they need. Factory administrators reach every app without being listed.

branding

One look for every app at once: colours, type, shape and a logo. Setting the factory's own is a factory administrator's call; an app's is its builder's.

One call

Interface, schema and backend logic arrive together. Sources are TSX — the page, the bundle and the stylesheet are generated, never sent.

// upsert_app
  {
    "slug": "todo",
    "sources": [
      { "path": "index.tsx",    "content": "import { BrandProvider, BrandShell } from '@pf/brand'…" },
      { "path": "TaskList.tsx", "content": "export function TaskList() {…}" }
    ],
    "hooks": [{ "path": "main.pb.js", "content": "routerAdd(…)" }],
    "schema": [{
      "name": "tasks",
      "access": "owner",
      "fields": [
        { "name": "title", "type": "text", "required": true },
        { "name": "done",  "type": "bool" }
      ]
    }]
  }

One look

A brand is an intention, not a stylesheet. One colour becomes the ten shades Mantine wants, derived on the same curve as its own palettes; everything else is chosen from a closed vocabulary. There is no CSS to write and no theme object to assemble.

// branding
  { "action": "apply",
    "brand": { "name": "Acme", "palette": { "primary": "#1e3a5f" } } }

That reaches every app immediately. Colours, type, radius and density arrive through a stylesheet each app serves — no rebuild, no restart, including apps built before the brand existed. The logo, the header and the component defaults come from @pf/brand, which an app imports, so they arrive with its next build. The sign-in page and this one wear it too.

What it compiles against

An app sends .tsx, .ts and .css, and imports from a fixed set. esbuild runs inside the master, against dependencies compiled into the binary — there is no npm and no network at build time, in the image or anywhere else.

react

19.2. JSX is automatic, so an app never imports React itself — and there is exactly one React on the page, whatever it pulls in.

@mantine/core · hooks

9.6. The component set, and the hooks that go with it. The stylesheet is already linked, so an app never has to ship CSS to look finished.

@mantine/form

useForm, for anything with inputs.

@mantine/dates · dayjs

Date inputs and calendars, with every dayjs plugin and the en and fr locales.

@mantine/charts

Charts, on recharts underneath — which is not importable on its own, so an app cannot end up with two charting libraries.

@mantine/notifications

Toasts.

@mantine/nprogress

The loading bar across the top of the page.

wouter

Routing in a few KiB: Route, Link, Switch, useLocation.

@phosphor-icons/react

1512 icons, of which an app pays only for the ones it draws.

@pf/brand

Not vendored but generated, per build, from this factory's brand: the theme, the logo, and the shell an app is built around.

Two ways to ship a dependency

Most of that set is served prebuilt and imported at runtime. The icon set is not, and the difference is what an app actually uses of what it imports.

assets/vendor react · mantine · wouter · dayjs import map the browser fetched once, then cached through every later upsert assets/bundled the icon set, as source esbuild the app's own bundle only the icons it imports, about 3 KiB each
A prebuilt bundle is the better deal when an app uses most of what it imports: it is fetched once and survives every later upsert untouched. It is a bad deal for 1512 independent icons of which an app names five — prebuilt, that is 4.9 MB to draw one gear. So the icon set is handed to the app's own build as source instead, and esbuild keeps what the app actually referenced.

What that buys you

The properties that fall out of building it this way.

Apps that stand alone

An app is a directory. Copy it elsewhere, run the binary against it, and it serves — with the master switched off.

Full-stack, not just forms

Apps ship their own PocketBase JS hooks, so they can expose real endpoints and react to their own data.

One sign-in, every app

Accounts live on the master — password, Google, Entra or any OIDC. Apps inherit it without a line of change, and every front is behind that sign-in.

Apps that report themselves

Every page's console, uncaught errors and failed requests are captured automatically and land in the app's own log, ready for get_logs.

A failed build changes nothing

Nothing is written until the compile succeeds, so a broken edit leaves the app serving its last working version — and the caller gets file, line and column.

Schema with a history

Each change writes a migration snapshot into the app's pb_migrations. Diffable, replayable, worth committing.

Private by default, public on request

Nothing of an app reaches an anonymous browser unless it asks to be public. Then the gate steps aside and the app's own access rules decide what a stranger sees — and its header offers a way in rather than demanding one.

Apps that belong to someone

Whoever creates an app owns it, and decides who else may open or change it. An app nobody shared with you does not appear in your listing, does not open in your browser, and answers none of your calls — factory administrators excepted.

One identity across the fleet

Apps read their colours and type from a stylesheet the factory writes, so a rebrand reaches every one of them at once — no rebuild, and nothing to remember for the app built next week.

Isolation by construction

Separate process, separate database, separate origin. One app cannot read another's data or its storage.