Skip to main content

An app surface is the part your user sees. It should read named data, render every state, and send named requests. It should not import package storage details.

A founder adds a Customers screen. The screen needs a list, a loading state, an empty state, and one action: archive a customer.
Read

The surface asks for a stable input name and renders loading, error, empty, and success states.

Render

The user sees concrete product data: customers, messages, tasks, rooms, invoices, or whatever your product owns.

Act

The surface sends a small request to a target instead of mutating local files by hand.

Surface contract

PieceSurface ownsSurface does not own
ReadsWhich data name it needs and how to render each stateStorage layout or package admission
ActionsButton intent, payload shape, pending/error/success UITarget execution details
LayoutLocal component structure inside the surfaceGlobal navigation and device/account placement
CopyUser-facing words for the product momentPrivate setup details

Minimal shape

import { sendRequestToBitfieldTarget, useBitfieldData } from '@bitfield/runtime-kit';

export function CustomersSurface() {
  const customers = useBitfieldData('customers.list');

  if (customers.loading) return <p>Loading customers...</p>;
  if (customers.error) return <p>Customers could not load.</p>;
  if (!customers.data?.length) return <p>No customers yet.</p>;

  return (
    <ul>
      {customers.data.map((customer) => (
        <li key={customer.id}>
          <span>{customer.name}</span>
          <button
            type="button"
            onClick={() =>
              sendRequestToBitfieldTarget('customers.archive', { customerId: customer.id })
            }
          >
            Archive
          </button>
        </li>
      ))}
    </ul>
  );
}
The screen should render a real product list, survive loading and error states, and send one named product action without importing Bitfield storage or runtime setup.

What not to do

// Bad shape: the screen is trying to become the package loader.
const rawPackageFile = await fetch('/some-package-file.json');
const rows = JSON.parse(await rawPackageFile.text());
The surface should not parse package material. Package admission and named data reads belong in Runtime Kit, not app components.

Common failures

SymptomCauseFix
The component crashes before data arrivesIt renders only the success pathAdd loading, error, empty, and success states
The screen imports or parses package file namesThe surface is bypassing named data readsRead through useBitfieldData(...)
Every button sends a vague commandThe action reply shape is not product-shapedUse a named target like customers.archive
The UI cannot show success or failureThe request result is ignoredRender pending, success, and failure feedback

Verify

CheckPasses when
Loading pathThe screen shows a visible loading state before data arrives
Error pathThe screen shows a visible error state when the read fails
Empty pathThe screen explains what the user sees when no records exist
Success pathThe screen renders product data, not storage vocabulary
Request pathThe button calls a named target with a small payload

Next

Use Package authoring to create the package file that can feed this surface. Use Runtime Kit API for the exact public function contracts.
Last modified on May 10, 2026