Browse documentation

Start here

OverviewGetting startedThe mental model

Use the runtime

Run the simulatorPi Agent and workspaceInstall and manage AppsESP32-P4 reference targetESP32-S3 supported target

Build Apps

App developer guideBuild your first AppApp source and packageData and migrationsActions and ToolsView and interactionNetworking and native servicesApp resourcesSchedulesPackage and updateTesting and debugging

Understand the runtime

Runtime flowGuests and lifecycleLayers and ownershipHarness boundary

Security

Trust and capabilitiesData isolationLifecycle and recovery

Reference

App manifestPocketPi APIView APICLI referenceLimits and compatibility

Examples

Exa App walkthroughRobinhood App walkthrough

Project

Current boundariesValidation status

View and interaction

A View is fixed JavaScript source shipped by the App release. It projects durable Data into a retained PocketJS node tree and turns human input into Action or narrow navigation events.

Keep presentation state small

const model = View.state({
  items: [],
  offset: 0,
  status: "READY",
});

Use View state for the current screen, selection, pagination cursor, loading label and other ephemeral presentation choices. Anything that must survive Guest eviction or restart belongs in App Data.

Bind durable state through a Projection

const itemsProjection = PocketPi.projection.many(
  "SELECT id, title FROM items ORDER BY id DESC LIMIT $limit",
  () => ({ "$limit": 20 }),
  (rows) => model.update({ items: rows }),
);

projection.one applies one row or null.projection.many applies an array. Keep SQL bounded with limits and only select columns needed by the current surface.

Mount one render function

function render() {
  const state = model.get();
  return View.Screen({ children: [
    View.Header({
      title: "ITEMS",
      onBack: () => PocketPi.navigate("pi-agent"),
    }),
    View.Column({
      style: { grow: 1, padding: 24, gap: 12 },
      children: state.items.map((item) => View.Card({
        style: { padding: 20 },
        children: View.Text({ text: item.title }),
      })),
    }),
  ] });
}

View.mount(render);

Reading a state value during render tracks it. Updating that state marks the View dirty, and the next View tick reconciles changed node properties/text while retaining compatible nodes.

Use explicit flow

A container with multiple normal-flow children must use View.Row,View.Column or an explicit direction. This avoids a hidden layout default. Use absolute positioning only for a deliberate overlay.

View.Row({
  style: { height: 84, gap: 12, align: "center" },
  children: [left, right],
})

Read the host viewport, not the board name

View.viewport
// { width, height, orientation, scale, layoutWidth, layoutHeight }

const LANDSCAPE = View.viewport.orientation === "landscape";

const content = LANDSCAPE
  ? View.Row({ style: { grow: 1, gap: 12 }, children: [primary, aside] })
  : View.Column({ style: { grow: 1, gap: 24 }, children: [primary, aside] });

The P4 host reports 720×1280. The S3 host rotates its 800×480 physical panel and reports 480×800. The simulator can exercise 720×1280, 800×480 and 480×800. The View SDK chooses a 720×1280 portrait or 800×480 landscape reference canvas and derives one continuous geometry scale. App numeric style values are design units and must not be multiplied byView.viewport.scale again.

Branch on orientation only when the composition itself should change, such as a portrait stack becoming landscape columns. Use scale only to reduce bounded repeated content. Do not branch on ESP32-P4, ESP32-S3 or a board profile.

Send events, not side effects

View.Pressable({
  onPress: () => PocketPi.action("select", { id: item.id }),
  children: View.Text({ text: item.title }),
})

View.Pressable participates in native hit testing and pressed feedback. A handler returns an Action/navigation event; it should not wait for HTTP, call a provider, block the frame or write business state directly.

Shared Pi Design components

ComponentUse
Header, PageIntro, SectionHeadingStable product hierarchy
ActionButton, PressableDirect human intents
Card, Badge, MetricCard, EmptyStateCommon content/status surfaces
StatusBar, ScrollButton, ScrollRailBounded status and paging
KeyboardShared on-device key layout; App still owns input meaning

Keep domain components such as portfolio charts, search-history rows and account selectors inside the App until their semantics are genuinely reusable. Inventory: docs/pocket-pi-design-system.md.

Design for the device

  • The logical viewport is host-provided: currently 720×1280 on P4 and 480×800 on S3 after rotation.
  • Pressable preserves at least a 40×40 physical-pixel hit target; ActionButton preserves at least 48 physical pixels of height.
  • Use bounded visible rows and explicit UP/DN paging rather than unbounded node lists.
  • Use the shared baked font slots; there is no runtime font loader.
  • Test pointer-down feedback, release and action routing in the simulator and physical touch.
  • Do not show unstable CPU/PSRAM/FPS telemetry as product UI.