Skip to main content
5 min read

Data, inputs & naming

Data: cadence and constraints

  • API data is constrained before it is drawn. Never display a returned string with a loose edge — everything that comes back from an API goes through the measure / fit / clip path so it cannot run off the canvas. No exceptions for "it's always short in practice."
  • refresh follows the data, not the device: 60 s for clocks, 300 for transit and markets, 900–1800 for weather, 3600 for daily feeds.
  • ttl_seconds on http.get tracks how fast each hop moves: 86400 for geocoding and grid lookups, down to 60 only for genuinely live feeds. Keep refresh and the main fetch's ttl in sync, and say so in a comment.
  • Two-hop location is standard: zip → api.zippopotam.us/us/<zip> (ttl 86400) → lat/lon → the real feed. Send a User-Agent to NWS.
  • Budget: max 8 http.get calls per render — size the page count to it.

See HTTP requests for the mechanics.

Inputs

  • Free text only for genuinely free text. If the value could be a picklist, it is one: dropdowns and option fields defined in the GDN source beat free text, because free text is where community error comes from. Free text is right when the value is displayed verbatim or passed to an API (a zip, a station ID, a key); a backend input that selects behavior is a dropdown.
  • Every logic path an input combination allows must work. "Zip code OR station ID" means the app handles either one being absent — or a dropdown first asks which the user is providing, and the free text follows. No combination of filled / blank inputs may break the render.
  • Every input is visibly used. If an app takes multiple inputs and shows one page, the page uses them all — or the app makes a page per input (never a random subset of the inputs' pages). Studio flags a declared input the code never reads as not used in code.
  • Input keys are letters + digits only (apikey, never api_key); API keys must be app_input_type: api-key (encrypted) with a folder README.md saying where to get one. Defaults containing : are truncated; date pickers arrive as "2026-08-14T01" — parse digits. Optional blank-default inputs are read as ctx.inputs.get("key", fallback). Reuse the shared 52-zone timezone dropdown and the zip input where they fit.

Every control, with a picture: User input types.

Naming, category & submission

  • App names are descriptive of what the app actually is, and mutually exclusive — against every app in all of Glance, not just GDN. "Custom Sports Schedule" that is really a Scottish football schedule is named Scottish Football Schedule.
  • No "SCROLL" or "LED" in app names. Apps are potentially shared across the whole platform, so the device name doesn't belong in the app name. (Existing classic / scroll pairs use a " (Scroll)" suffix in name: to tell the builds apart — leave those as they are. This rule is about an app's own title: don't name a new app after the device it runs on.)
  • Every app lands in a real category — or a new category is requested. Other is a catch-all, not a good landing place.
gdn: 1
id: market-hours # lowercase-hyphen, equals the folder name
name: Market Hours # descriptive, unique across Glance, no device names
category: Finance # a real category - Other is a last resort
width: 64 # 64 / 128 / 192 / 384 - height always 32
refresh: 300
pages: [bell] # one def per name; 1-3 typical

Every field: manifest.yaml.

Comment culture

Non-obvious layout numbers carry a comment naming the collision or misread they fix, with the offending string and its measured width:

# 'TROP STORM' is 107px at 10x16, so it started at x=79 and the two
# drew through each other for 41px.

The best apps open with a # DESIGN. paragraph stating the visual thesis — which, under these guidelines, is your design brief — before any code. Write both.

Next