← v15 index

Schema components · design system

14 input shapes · 10 output shapes · schema-first · single source of truth
v15.3

Principle

01

JSON Schema (via OpenAPI) is the single source of truth. Every Floom app is described by a spec; every input and output renderer is derived from that spec. We never invent parallel type unions.

Source
OpenAPI 3.1 + JSON Schema
Request: parameters[].schema and requestBody.content.*.schema. Response: responses.*.content.*.schema.
Discriminator
Pure functions
pickInputShape(schema) and pickOutputShape(schema). Deterministic. Never throws. Ships in packages/renderer.
Hints
x-floom-* vendor extensions
Override shape (x-floom-input-shape), language (x-floom-language), UX mode (x-floom-shape), size caps (x-floom-max-size).

Derivation

02 · 14 input rules · 10 output rules

Precedence walks top-to-bottom. First match wins. Final catch-all is always text (input) / object (output) so a spec can never refuse to render.

Input shapes
ShapeSchema fragment
(explicit)x-floom-input-shape: <shape> — always wins
csvtype: string, format: binary, contentMediaType: text/csv
imagetype: string, format: binary, contentMediaType: image/*
filetype: string, format: binary
multifiletype: array, items.format: binary
datetype: string, format: date
datetimetype: string, format: date-time
urltype: string, format: uri
enumtype: string, enum: [...]
jsontype: string, contentMediaType: application/json
codetype: string, x-floom-language: <lang>
textareatype: string, maxLength > 200 or x-floom-multiline: true
texttype: string (catch-all)
numbertype: number or type: integer
booleantype: boolean
Output shapes
ShapeSchema fragment
(explicit)x-floom-output-shape: <shape> — always wins
pdftype: string, contentMediaType: application/pdf
imagetype: string, contentMediaType: image/*
audiotype: string, contentMediaType: audio/*
tabletype: array, items.type: object
markdowntype: string, contentMediaType: text/markdown
codetype: string, x-floom-language: <lang>
streamx-floom-stream: true or Transfer-Encoding: chunked
texttype: string (catch-all)
objecttype: object (catch-all)
errorstate === "output-error" (any shape)

Input shapes · 14

03 · empty / filled / error

Every input component accepts { spec, value, onChange, label, error, disabled }. All three states must be designed — empty is the default, filled is the happy path, error is how users recover.

text
Single-line plain text. Default catch-all for type: string.
type: string title: City description: Any city name
empty
City
Any city name
filled
City *
error
City *
City is required
textarea
Multi-line text. Triggered by maxLength > 200 or x-floom-multiline.
type: string maxLength: 2000 x-floom-multiline: true
empty
Prompt
Up to 2,000 characters
filled
Prompt
82 / 2000
error
Prompt *
Minimum 10 characters
code
Syntax-highlighted editor. Language from x-floom-language.
type: string x-floom-language: python x-floom-multiline: true
empty
Script
# write your script…
filled
Script
def fetch(url): return requests.get(url).text print(fetch("https://api.example.com"))
error
Script
# SyntaxError: invalid syntax def fetch(url return requests.get(url)
Line 2: missing closing parenthesis
url
URL with live metadata preview. Triggered by format: uri.
type: string format: uri title: Source URL
empty
Source URL
filled (with preview)
Source URL
OpenPaper launches its public research agent
openpaper.ai · Mar 2026 · 4 min read
error
Source URL
Not a valid URL
number
Numeric input. Range slider if both min + max set.
type: integer minimum: 1 maximum: 10 default: 3
empty (no bounds)
Count
filled (with range)
Results per page
3
1 – 10
error
Results per page
Must be between 1 and 10
enum
Select for ≤10 options, searchable combobox for >10.
type: string enum: [A4, Letter, Legal] default: A4
empty (small)
Paper size
filled (small)
Paper size
combobox (11+)
Country
Portugal
Puerto Rico
boolean
Toggle switch. Never a raw checkbox.
type: boolean default: false description: Include metadata
off
on
Adds EXIF + source URL to the output
disabled
Requires pro plan
date
Native date picker. datetime uses datetime-local.
type: string format: date title: Departure
empty
Departure
filled
Departure
datetime variant
Arrival window
file
Drag-drop zone. MIME filter from contentMediaType. Size cap from x-floom-max-size.
type: string format: binary contentMediaType: text/html x-floom-max-size: 2MB
empty
HTML file
Drop an HTML file here
or click to browse · max 2 MB
filled
HTML file
invoice-q3-2026.html
text/html · 42 KB
error
HTML file
File too large
invoice.html · 3.4 MB · limit 2 MB
image
Like file + thumbnail preview + paste-to-upload.
type: string format: binary contentMediaType: image/*
empty
Source image
Drop an image, paste, or browse
PNG · JPG · WEBP
filled
Source image
IMG
cover-v3.png
1920×1080 · 412 KB
error
Source image
Unsupported format
slide.heic — only PNG / JPG / WEBP
csv
Upload + client-side parse. Header toggle. 10-row preview.
type: string format: binary contentMediaType: text/csv
empty
Rows
Drop a CSV
up to 50,000 rows
filled
Rows
customers-q3.csv · 4,812 rows · 620 KB
emailplanmrr
ana@floom.devpro99
ben@flyfast.ioteam499
carla@openpaper.aipro99
error
Rows
Parse failed at line 17
Unclosed quote · column 2
multifile
List + drag-drop + per-file progress. Reorderable.
type: array items: type: string format: binary
empty
Pages
Drop up to 20 files
uploading
Pages
page-01.pdf
OK
page-02.pdf
62%
error
Pages
page-03.pdf
Upload failed · network
json
JSON editor with schema validation. For structured blobs.
type: string contentMediaType: application/json description: Additional config
empty
Config
// { }
filled
Config
{ "model": "claude-sonnet", "temperature": 0.2, "stream": true }
error
Config
{ "model": "claude-sonnet", "temperature": }
Unexpected token at line 3

Output shapes · 10

04 · loading / success / error

Already implemented in packages/renderer/src/outputs/. This matrix is the contract that apps/web/OutputPanel must honor when wired (issue #41).

text
Plain string. Catch-all for type: string.
type: string
loading
Generating…
success
The cheapest option is Lisbon → Tokyo via Doha, departing June 14, return June 28, €487 on Qatar Airways.
error
Generation failed
UpstreamTimeout: 504 from flyfast-api after 30s
markdown
Rendered headings, lists, code, links. Default for prose outputs.
type: string contentMediaType: text/markdown
loading
Writing summary…
success

Flight options

  • QR 204 Lisbon → Doha → Tokyo — €487
  • TK 1760 via Istanbul — €534
error
Could not render markdown
SyntaxError: unclosed code fence at line 12
code
Syntax-highlighted block. Language from x-floom-language.
type: string x-floom-language: python
loading
Compiling…
success
from floom import app def run(event): return {"ok": True}
error
Syntax error in generated code
unexpected EOF at line 4
table
Array of objects. Columns from item schema.
type: array items: type: object properties: email: { type: string } mrr: { type: number }
loading
Fetching rows…
success
emailmrr
ana@floom.dev99
ben@flyfast.io499
carla@openpaper.ai99
error
Empty result set
0 rows returned — query had no matches
object
Structured JSON. Collapsible keys. Catch-all for type: object.
type: object properties: price: { type: number } currency: { type: string } carrier: { type: string }
loading
Parsing…
success
{ "price": 487, "currency": "EUR", "carrier": "QR" }
error
Schema mismatch
expected price of type number, got string
image
Inline preview + download. Transparent backgrounds checkered.
type: string contentMediaType: image/png format: binary
loading
Rendering…
success
COVER · 1920×1080
cover-v3.png · 412 KBDownload
error
Image generation failed
nanobanana: safety_filter_triggered
pdf
First-page preview + paginate + download. No external viewer.
type: string contentMediaType: application/pdf format: binary
loading
Building PDF…
success
invoice-q3.pdf · 1 / 4 · 112 KBDownload
error
Could not render PDF
html-to-pdf: unsupported CSS filter: blur()
audio
Waveform + play/pause + download. Inline, no popups.
type: string contentMediaType: audio/mpeg format: binary
loading
Synthesizing voice…
success
0:00 · 1:42
error
Voice synthesis failed
elevenlabs: voice_not_found
stream
Live token stream. Blinking cursor at tail.
x-floom-stream: true type: string
connecting
Opening stream…
streaming
Thinking about your query… Found 3 candidate flights under €500. Checking schedule for June 14
error
Stream closed unexpectedly
econnreset after 14s · 412 tokens
error
Pseudo-shape, always shown when state === "output-error".
// not a schema — a render state state: "output-error" error: { message, code?, retry? }
recoverable
Upstream timeout
504 from flyfast-api after 30s
fatal
Quota exceeded
flyfast: 1,000 searches / day limit reached
Retries disabled · upgrade plan
unauthenticated
Missing credential
requires FLYFAST_API_KEY — add it in Secrets
Open secrets →

Render state machine

05

Every Floom run moves through three states. The same component tree is mounted across all three; only the rendered children differ. No conditional remounts, so refinement / retry feel like the same conversation.

state · 01

input-available

Schema resolved, composer / form rendered, nothing has run yet. This is every app's landing state for signed-in users.

state · 02a

output-available

Run succeeded. data is set, schema is the response schema, renderer picks an OutputShape. Refinement composer stays visible for conversational apps.

state · 02b

output-error

Run failed. Error block with message + code + retry action. data is null. Input card stays filled so the user can iterate without re-typing.

Composition

06

<SchemaInput> per field. Composition rules are deterministic and based on field count + shape adjacency. No layout manager; these three patterns cover 95% of apps.

Vertical stack · default
City
Departure
Return
Two-column · wide viewport
From
To
Depart
Return
Inline · short + boolean
Count
3
Include metadata

Prompt mode vs form mode

07 · controlled by x-floom-shape

The same schema can render in two modes. prompt uses a textarea composer + Claude interprets free-text into fields (flyfast, openpaper). form uses <SchemaInput> directly with a Run button (html-to-pdf, resize-image). Default is auto: if the op has a single textarea-shaped field, treat as prompt; else form.

x-floom-shape: prompt flyfast

One big textarea. Claude extracts destination, dates, budget from prose.

Thread · last 2
· Lisbon to Tokyo mid-June under €500 · 1m ago
· Madrid to Bali in September · yesterday

x-floom-shape: form html-to-pdf

Schema form, Run button. No composer. Run-log history, not threads.

HTML file
invoice-q3.html
42 KB
Paper size
Orientation
Run log · last 2
· invoice-q3.html → invoice-q3.pdf · 1.1s · 112 KB
· spec.html → spec.pdf · 0.8s · 48 KB · yesterday

Mobile · 375px

08

Inputs stack 100% width. Tables overflow horizontally. Image outputs fill card width. No hover-only states.

floom.dev/p/html-to-pdf
HTML file
invoice.html
42 KB
Paper size
floom.dev/p/flyfast

Best match

QR 204 Lisbon → Doha → Tokyo
June 14 · €487 · Qatar Airways

Implementation
Contract + discriminator: packages/renderer/src/contract/index.ts
Input components: packages/renderer/src/inputs/ (issue #40)
Output components: packages/renderer/src/outputs/ (exists)
Web-app wiring: #41 + #42
Vendor extensions: spec/protocol.md (issue #43)