# Roughflow spec for coding agents Roughflow is a mobile-first, low-fidelity wireframing app. A person roughs out screens with a small set of shapes, tags each shape's semantic role, wires up navigation between screens, and exports this document: a structured spec describing frames, elements, and flow. It is handed to you (Claude Code, Codex, or any other coding agent) as a build spec. Your job is to read it and scaffold the real UI it describes. This file is both a human-readable explainer and the schema reference the exported YAML's `# schema:` header links to. ## What you're looking at Every export starts with a two-line comment header: # Roughflow spec v1 # schema: https://roughflow.com/llms.txt If the export is scoped to a single frame, a third comment line follows: # scope: frame (, ) After the header, the document is plain YAML (hand-emitted, not run through a generic YAML library: expect fixed field order and flow-style `{...}` maps for small objects like `size`, `frame`, and `repeat`). ## Top-level keys - `rough`: string. The name of the Roughflow document (a "Rough") this was exported from. - `exported`: string. ISO-8601 timestamp of the export. - `scope`: `full` or `frame`. `full` exports every frame in the Rough; `frame` exports exactly one frame plus only the flow edges whose source element lives in it (see the `# scope:` comment line above for which one). - `frames`: list of frame objects (see below). - `flow`: list of navigation edges, or `[]` if there are none. ## Frame fields Each entry under `frames:` is: - `id`: string, stable identifier. - `name`: string, user-given frame name (e.g. "Home", "Settings"). - `device`: one of `phone` | `tablet` | `desktop` | `custom`. This is the frame's preset, not a literal device model, so treat it as a rough size class, not a pixel-exact target. - `size`: `{w, h}` in logical pixels. Preset sizes: phone 384x848, tablet 768x1024, desktop 1440x896; `custom` frames carry their own explicit size. - `elements`: list of element objects (see below), or `[]` if the frame is empty. Order matters (see "Z-order" below). ## Element fields Each entry under a frame's `elements:` (or a group/repeater's `children:`) is: - `id`: string, stable identifier. - `type`: one of `rect` | `circle` | `text` | `line` | `image` | `group`. This is the *drawn primitive* (what shape it is), not its semantic role. `group` is a plain wrapper around `children` with no paint of its own, used both for a hand-grouped cluster of primitives and, combined with `repeat` below, as a list/grid repeater's template container. - `role`: optional. One of the closed set: `button`, `input`, `heading`, `text`, `image`, `icon`, `list`, `grid`, `group`, `nav-bar`, `tab-bar`, `frame`, `custom`. This is the *semantic meaning* a human assigned in the layers view, independent of `type` (e.g. a `rect` tagged `role: icon` is a square standing in for an icon; the annotation, not the pixels, is the intent). Absent `role` means untagged: geometry only, no assigned meaning yet. - `custom_role`: present only when `role: custom`. Freeform text the user typed for a case the closed enum doesn't cover. - `confidence`: present only when `role: custom`, always `low`. Signals to you that this role is a human's free-text escape hatch, not a vetted enum value, so treat it as a hint, not a hard contract. - `name`: optional string. A user-given label for the element, when set. - `text`: optional string, only ever present on `type: text` elements, and only when the user actually typed content. A `type: text` element with NO `text` field is a greeked/placeholder text block (a placeholder bar was drawn, no real copy yet): treat it as "role + geometry only," not a blank string to render. - `frame`: `{x, y, w, h}` in logical pixels, quantized to a 16px grid. **Coordinates are relative, not absolute** (see "Coordinate system" below). - `children`: optional list, recursively shaped like this same element list. Present on `group`-type elements that contain nested elements (a hand-made group, or a repeater's template container; see `repeat` below). - `repeat`: optional. `{cols, rows, gap, gap_cross}`, all in logical pixels except `cols`/`rows` which are counts. Present when this element is a list/grid repeater: its (single) child under `children` is the template item, repeated `cols` × `rows` times with `gap` (primary axis spacing) and `gap_cross` (cross-axis spacing). **Always this 4-field shape, even when one axis is 1**: a vertical list is `{cols: 1, rows: N, ...}`, a horizontal list is `{cols: N, rows: 1, ...}`, there is no separate "list" shape. Roughflow's model has no independent list primitive: list and grid are the same repeat container, just with a different axis count. ## Coordinate system - Grid: everything is placed on a 16px logical-pixel grid (both frame element positions/sizes and repeat gaps are quantized to multiples of 16). - A **top-level** element's `frame.{x,y}` is relative to its containing frame's own top-left origin (i.e. `(0,0)` is the frame's top-left corner, not the canvas/scene origin). - A **nested** element's `frame.{x,y}` (anything under a `children:` list) is relative to its immediate parent group/repeater's top-left corner, not the outer frame. Nest arithmetic accordingly when reconstructing absolute layout: walk the tree accumulating each level's `{x,y}` offset. ## Z-order Within a frame's `elements` list (and within any `children` list), **order is z-order, bottom-first**: the first element in the list paints first / sits at the back; later entries paint on top. When you scaffold overlapping elements (e.g. a button on top of a background rect), preserve this order in your generated component tree (mount order or explicit stacking). ## Flow entries Each entry under `flow:` is one navigation edge: - from: {element: , frame: } to: transition: slide This is **element-anchored** navigation, not frame-to-frame: `from.element` is the specific element a user tapped/interacted with to trigger navigation (e.g. a button, a nav-bar item, a list-item template), `from.frame` is the frame that element currently lives in, and `to` is the destination frame's `id`. When scaffolding, wire this as an onPress/onClick navigation call on that specific element/component, not a generic screen-level link. - `transition`: optional. One of `slide` | `fade` | `pop`, defaults to `slide` when absent. The suggested screen-transition animation for this navigation, set by the user in the flow layer. Map it to whatever your target platform's equivalent is (e.g. a push/slide transition, a crossfade, or a modal-style pop), a hint for feel, not a hard contract. ## Scalars and quoting String values are printed bare when safe, and double-quoted (with `\` and `"` escaped) when they contain YAML flow-indicator characters (`: # { } [ ] , & * ! | > ' " % @` \`), would otherwise parse as a number, bool, or null, or have leading/trailing whitespace. Standard YAML string parsing handles both cases correctly, so no custom parser is required. Numbers print as bare integers when whole (`128`, never `128.0`); Roughflow's own geometry is always grid-quantized, so you should only see a decimal if the file was hand-edited outside the app. ## How to turn this into a real app 1. **One frame ≈ one screen.** Scaffold one screen/route/component per frame, named after `frame.name`, sized to roughly `frame.size` as a starting layout (phone/tablet/desktop as a breakpoint hint, not a hard pixel lock: this is a rough spec, not a mockup). 2. **`role` drives the component you generate**, not `type`. A `rect` tagged `role: button` becomes a real button component; a `rect` tagged `role: icon` becomes an icon slot (use `name`/`custom_role` as a hint for *which* icon, if given; there is no icon asset reference, only intent). Untagged elements (no `role`) are literally just shapes: lay them out but don't over-infer meaning. 3. **`repeat` → a list/grid component.** Build the single child under `children` as one reusable template/item component, then render it in a loop/`ListView`/CSS grid driven by `cols`/`rows`/`gap`/`gap_cross`; don't hand-copy the template N times. 4. **Plain `group` (no `repeat`) → a layout container**, e.g. a `
`/ `Column`/`Row`/`Stack` wrapping its `children` at their relative offsets, with no repetition semantics, just grouping. 5. **`flow` → navigation wiring.** For each edge, attach a navigation action (route push, `Navigator.push`, ``, etc.) from `from.element`'s generated component to `to`'s generated screen. 6. **`role: custom` / `confidence: low`** elements are the roughest signal in the document: use `custom_role`'s text as a naming/intent hint, but don't assume it maps cleanly onto a known component pattern the way the closed enum roles do. 7. Geometry (`frame: {x,y,w,h}`) is a rough starting layout, not a pixel contract, since this is deliberately a low-fidelity tool (see the roughflow.com landing page for the thesis). Prefer semantic, responsive layout over literally reproducing every coordinate. ## Source of truth This document is generated from `lib/export/yaml_exporter.dart` in the Roughflow app. If anything here ever seems to disagree with an actual export you're holding, trust the file in front of you: this page describes the shape, not a specific instance.