Layout & the scroll sequence
Every panel is 32 px tall. Width picks the design problem — and the space rules are opposite per device.
Where content is allowed to live
| Size | Used on | Space rule |
|---|---|---|
| 64x32 | All end products (the V2, and every SCROLL at its narrowest) | Maximize the space. With this few pixels, minimize no-content areas — every pixel should be earning its place. |
| >64x32 (128 / 192 / 384) | SCROLL | Respect a safe zone. Keep content inside roughly x 10–181 on a 192-wide app, with 6–10 px of padding at the left and right edges. |

The #1 scroll rule: design for the sequence
On SCROLL the display is a stream moving right to left. An unknown app plays before yours, another after, and your own pages rotate in between based on the end user's inputs. Scroll hardware comes in threes — Studio 192x32, Pro 384x32, Premier 640x32 — so on the wider panels your app is on the glass at the same time as one or two neighbors.
An app with content at pixel 0 or 191 visually merges with whatever plays around it. Design the app to read as its own unit — edge padding, a contained or boxed trailing zone, or a closed frame — and always preview it in sequence, not alone. Studio's Preview your app in action card does exactly that: your app scrolling between two stock neighbors on every panel size, with a Mark app seams toggle.
The padding is per app, not per page. Pages inside one app can sit closer together, but the app's outer edges need the buffer.
Scroll apps have two placement options: edge padding or centering. Not everything should be centered, but lean on the center of the canvas rather than hard-justifying content against the left and right sides. Reduce wasted space within the safe zone too — a scroll app that parks a small cluster of content in one corner of 192x32 is wasting the panel.
Your own design may run elements to the edge deliberately — some shipped scroll apps draw a thin 2 px accent stripe at the far-left edge as a frame. When an AI is laying out a scroll app on its own, it keeps everything — text, data, pixel art — inside the safe zone.
The one adaptive branch
if c.width >= 128:
# wide layout
else:
# 64-wide layout
The catalog treats 64 as "narrow" and everything else as "wide"; the layout linter
understands exactly this shape. Write different copy for each width, never clipped
copy ("MARKET HOLIDAY" → "HOLIDAY"), and drop elements entirely on 64 rather
than squeezing them. 55 of 59 classic/scroll pairs in the catalog ship a byte-identical
app.star — write the width branch once and both builds come free.
Every app must identify itself
It is not obvious what an app is ~85% of the time just by seeing its data. Because apps rotate in a shared stream, add a splash / intro page to create a break from the preceding app — or, if a splash page isn't warranted, make sure the app carries a pixel-art identity or a title so a viewer can context-switch into its data feed:
- A clock doesn't need a title or splash — it's a clock, we get it.
- Snowfall likely needs a title, but with pixel art of snow falling a splash page isn't required.
- The Minecraft app opens on the Minecraft logo.
Location-based apps always show the location on the image. If the input is a zip code and there's no location API, display the zip itself.
Classic panels (64 / 128): the bands
Vertical bands, measured from the catalog:

- Header y 0–7 — a filled strip with centered
5x7/4x5text, or a4x5eyebrow in dim gray. - Hero y 8–13 —
16x20on wide,10x16on narrow. - Second row y 17–22, footnote y 23–27.
- Margins 2–6 px. A left icon sits at
x = 1, vertically centered; text next to a 24 px icon starts at x 28. One 1 px hairline under the header is the whole grid.
Lists: divide the height evenly (lh = c.height // n, 3 rows on 64, 4 on wide).
Row anatomy is left-label / right-value, and the right side is measured first —
draw the value, compute what's left, then fit / clip the label into it. Nothing in the
API clips; this ordering is what prevents collisions.
SCROLL: the house kit
The modern scroll apps share a page grammar (github-pulse-scroll lines 1–237 in the
catalog is the canonical copy):
- an accent rail wearing the app's state color,
- a chip row — a state-carrying
tab()chip, meta right-aligned, - a content band (y 8–31) split into named zones by
vlines.
"An amber REVIEW chip above a green ALL CAUGHT UP is the page contradicting itself" — compute state first, then dress the chrome in it. List bullets are a 2x2 colored square; reflow columns by item count (2 columns while ≤ 6 items, 3 after).
Spacing rules (hard requirements)
- Nothing draws off the canvas. The renderer clips silently at the borders — a draw one pixel past the edge just loses that column, with no error. Any element near an edge is placed by its measured width (or right-aligned against the edge), never at a hand-picked x, and the worst-case string is rendered and checked at both edges.
- 1 px minimum buffer between text and the next asset — line, polygon, pixel art, anything.
- 1 px minimum between vertically stacked text rows. If two rows can't keep the gap, reduce the font size until they fit — never let rows touch.
- Text on a filled shape is centered both ways. Black text on a pill: the pill is centered vertically and horizontally on the text, high contrast, even padding all around.
More content than fits: three mechanisms, none of them animation
- Pages —
pages: [today, tasks], one function per name; the panel rotates them. 1–3 is typical; the 8-http.getbudget per render is the ceiling. See Pages. - Frames — time-multiplex inside one page:
frame = (ctx.now.unix // 60) % 3withrefresh: 60, plus a hidden_debugframeinput for previewing. - Truncate with an explicit overflow count — show 3, then
"+N MORE"right-aligned in4x5.