- TypeScript 88.4%
- CSS 8.7%
- JavaScript 2.9%
Chrome-verify on the live origin surfaced a 400 on Publish: 'column updated_at is system-managed (stamped from request context) and cannot be set by clients; remove it from the payload'. updateFormMeta and publishForm were sending updated_at in their patchRow payloads. kapable-data stamps updated_at itself. Removed it from both patches (the service bumps it). App-local fix. Re-verified the full publish path against live kapable-data: 'Form published.' with table forms_responses_<formId>, columnCount=2 (DYNAMIC, derived). |
||
|---|---|---|
| app | ||
| public | ||
| .gitignore | ||
| .npmrc | ||
| bun.lock | ||
| DESIGN.md | ||
| package.json | ||
| react-router.config.ts | ||
| README.md | ||
| seed.ts | ||
| tsconfig.json | ||
| vite.config.ts | ||
Forms — Survey / Form Builder
A Kapable v2 starter app (#13 in the Top-20). Build a form from a field palette, watch a live preview take shape, publish it to get a shareable public link, collect responses, and read them as per-field counts plus a raw responses table.
The differentiated capability it demos: on publish, the app dynamically
creates a typed table in kapable-data (forms_responses_<formId>) whose columns
are derived from the fields the user built — short-text/long-text/email →
text, number → float, single-select → text, multi-select → json. The
schema isn't known until the form is composed. See DESIGN.md.
- Routes:
/(forms list) ·/forms/:id/edit(builder + live preview) ·/f/:id(public, unauthenticated fill view) ·/forms/:id/results(dashboard). - Data:
forms_forms,forms_fields(static) + one dynamicforms_responses_<formId>per published form. All via the server-only org keyKAPABLE_ORG_KEY(ansk_live_key for orgkapable-starters) — never in the client bundle. The public fill route is anonymous to end-users but writes server-side through that key. - Gotcha encoded: kapable-data columns are NOT NULL by default — any column
that can be blank is declared
nullable: true(seeapp/lib/forms.server.ts).
Built on the Kapable starter kit (skeleton docs below).
Kapable Starter Kit
The on-brand fork-point for Kapable v2 apps. Every starter/template app forks this repo, then builds its own routes on top.
What you get out of the box:
- React Router 7 SSR + TypeScript, with the
/healthresource route the deploy pipeline gates on (keep it). - Kapable theme system — the canonical
--kp-*token set fromkapable-theme.css,[data-theme="dark|light"]on<html>, FOUC-free first paint (the in-<head>script +kapable-theme.js), and a theme toggle wired towindow.KapableTheme.cycle(). Tailwind v4 is bridged to the same tokens (bg-surface,text-fg,border-line,text-accent, …). @kapable/sdkwired server-side inapp/lib/kapable.server.tswith explicit per-service base URLs (never the bareapi.kapable.aidefault for everything). Session (kses_) read from the request; tokens never reach the client bundle.- Error → toast on every fetch (sonner), a skip-to-content link, a
global
:focus-visiblering, 44px touch targets, and a branded error boundary.
Develop
bun install
bun run dev
Build + run (what the platform does)
bun run build
bun run start # react-router-serve on :3000
Calling the SDK
All SDK calls are server-side (loaders/actions). Always pin the client to the service you're calling — each has its own base URL:
import { kapable } from "~/lib/kapable.server";
export async function loader({ request }) {
const data = kapable("data", { request }); // data.kapable.ai
const { data: rows } = await data.data.listRows("my_table");
return { rows };
}
| Service | Base URL | Notes |
|---|---|---|
| auth, ai, data, comms, board, billing, store, harbor | dedicated subdomain | own Caddy host block |
| wiki, knowledge, secrets, warrant, projects, herald | https://api.kapable.ai |
path-routed gateway (no dedicated subdomain — the subdomain falls through to launchpad) |
api.kapable.ai reaches every service via /v1/* and is the safe universal
choice. Prefer the caller's kses_ session over a bare sk_live_ for writes.
Add a route
- Create
app/routes/<name>.tsx. - Register it in
app/routes.ts— RR7 404s unregistered routes silently.
Theme tokens
Defined in public/kapable-theme.css. Use var(--kp-bg), --kp-surface,
--kp-text, --kp-accent, --kp-border, --kp-danger/success/warn/info, or
the Tailwind aliases. A single data-theme flip recolors everything.