POPSULE CAPSULE KIT (spec 1)
https://popsule.app/kit.txt . Full spec with exact types: https://popsule.app/llms-full.txt
A capsule is a tiny personal app for one person, running inside the Popsule app on their phone. You
write it as ONE HTML file. Popsule runs it sealed and offline and gives it native features through
window.popsule. Popsule checks every capsule; breaking a rule below means it is rejected with an error
that says how to fix it.
HOW TO DELIVER
- With the Popsule connection (tools get_kit, plan_capsule, validate, preview, push_capsule): call
validate and preview until there are no errors, then push_capsule. Do not paste the file.
- Without it: reply with ONLY the finished HTML file, in ONE code block, nothing else. The person
shares your reply to Popsule.
1. PLAN FIRST (pick 2 to 4 features)
does: one plain sentence ("Splits a bill between friends, with tip")
remembers: what it saves between opens, or nothing
glance: what a widget should show, or no widget
reminds: when to remind, or never
quick: one thing worth doing without opening ("log a glass"), or none
lifespan: "once" if it does one job and is done
2. THE FILE
- One .html file under 1 MB. Must have a
and the manifest (below) in .
- Inline
- spec: always 1. name: 1 to 40 characters (2 to 4 words). does: one sentence, up to 120 characters.
- capabilities: every feature you use, from the list below. Using one you did not list fails.
- Optional fields: widget, actions, data, shareIn, libraries, resets, dataVersion, lifespan.
A field needs its capability (widget needs "widget", actions needs "actions", data needs "data",
shareIn needs "share-in"), and "actions", "data", "share-in" need their field.
- resets: [{ "kv": "glasses", "to": 0, "every": "day" }] ("day" = midnight, "week" = Monday)
- lifespan: "once" for a capsule that does one job; it must then call popsule.done({ summary }).
4. FEATURES (capabilities)
kv popsule.kv: remember things. get(key), set(key, value), delete(key), keys(),
add(key, number) for counters, push(key, item) for lists. add/push merge across devices.
data popsule.data: shared lists other capsules can use. Built-ins: tasks {text, done, list?, due?},
expenses {amount (whole cents), currency, date, note?, category?}, notes {title?, text},
contacts {name, phone?, email?}, events {title, start, end?}. Manifest:
"data": { "reads": ["tasks"], "writes": ["tasks"] }. Calls: query, add, update, remove, watch.
widget a native home or lock screen widget (section 5).
remind popsule.remind.set({ id, title, body?, at?: "YYYY-MM-DDTHH:MM",
every?: { daily: ["10:00", "16:00"] } or { weekly: { day: "sat", time: "10:00" } },
buttons?: [{ id, text, apply }] }). Max 8 reminders, each at most 6 times a day.
notify popsule.notify(title, body): only while open.
actions quick actions without opening (voice, shortcuts). Manifest:
"actions": [{ "id": "add", "phrase": "Add {item} to Grocery List", "input": { "item": "text" },
"apply": { "kv": "items", "push": { "name": "{item}", "done": false } } },
{ "id": "open", "phrase": "Show Grocery List", "open": true }]
Each needs id, phrase, and either apply or "open": true. Every {name} in phrase must be in input.
share-in accept photos, links, text or files shared from other apps; read popsule.launch.share.
live lock screen while something runs: popsule.live.start({ id, title, until }), update, end.
files popsule.files.pick({ accept }) and popsule.files.save({ name, type, text }).
ai popsule.ai.complete({ prompt }): the only thing that leaves the phone. Only for text the
person typed or shared in. Use only if the request truly needs words written or sorted.
Also: popsule.supports("live") before relying on a feature; popsule.theme is "paper" or "charcoal".
apply (in actions, reminder buttons, widget buttons) is one of:
{ "kv": KEY, "add": NUMBER } | { "kv": KEY, "push": VALUE } | { "kv": KEY, "set": VALUE } |
{ "collection": NAME, "add": { ...record } } {item}, {today}, {now} are filled in.
5. WIDGETS (drawn by Popsule from blocks; never HTML)
"widget": {
"sizes": ["small", "medium", "large", "lock"],
"suggest": { "accent": "data", "surface": "paper", "density": "roomy" },
"blocks": [
{ "type": "title", "text": "Water today" },
{ "type": "value", "bind": { "kv": "glasses" }, "format": "{n} of 8", "accent": true },
{ "type": "button", "id": "plus", "text": "+1", "tap": { "kv": "glasses", "add": 1 } }
]
}
- Blocks: title, value, detail, bar, ring (bind + max), segments (bind + total, max 14), rows
(bind to a list, max 5, "tap": "toggle" flips done), countdown (bind or until), icon, button.
- At most 4 blocks (small and lock show 3). Taps only work on blocks bound to saved data.
- rows: { "type": "rows", "bind": { "kv": "items", "text": "name", "done": "done", "limit": 5 }, "tap": "toggle" }
- accent by meaning: "data" blue (counts of their things), "time" orange (dates, deadlines),
"file" green (one-time jobs), "seal" purple (kept things), "ink". surface: paper, tint, bold.
- While open you may also send a picture of the moment: popsule.widget.update({ blocks: [...] }).
6. TESTS (required if it calculates money, dates, times or amounts)
Put the maths in plain functions and add a test block. Popsule runs it when checking, never for the person.
expect(x).toBe(y) / .toEqual(y) / .toBeCloseTo(y, digits). Work in whole cents for money.
7. GOOD HABITS
- Read saved data defensively: old fields may be missing.
- Every popsule.* call returns a Promise; calls may be made at startup.
- Readable code: clear names, short functions. The person can open and read it in Popsule.
8. SMALLEST COMPLETE EXAMPLE
Water Tracker