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
| Component | Use |
|---|---|
Header, PageIntro, SectionHeading | Stable product hierarchy |
ActionButton, Pressable | Direct human intents |
Card, Badge, MetricCard, EmptyState | Common content/status surfaces |
StatusBar, ScrollButton, ScrollRail | Bounded status and paging |
Keyboard | Shared 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.
Pressablepreserves at least a 40×40 physical-pixel hit target;ActionButtonpreserves 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.