Formát plánu Sestavy
Otevřený a jednoduchý formát pro jednotlivé tréninky i vícetýdenní programy. Napište ho ručně, vyexportujte z tabulky nebo ho vygenerujte vlastními nástroji.
Přehled
Plán je seznam částí (například Rozcvička, Hlavní část, Zklidnění). Každá část obsahuje položky a běží v jednom ze čtyř režimů. Program navíc přidává týdny a dny, a každý den má vlastní části.
- Normální (výchozí): položky popořadě,
roundskrát.bell: truezazvoní na gong na začátku každé časované položky i odpočinku awarnupozorní tolik sekund před koncem časované položky. - Superset: položky se sériemi a opakováními, prováděné série po sérii (A1, A2, pak znovu A1), s
restposlední položky mezi koly. - AMRAP: co nejvíc kol seznamu za
capsekund; sportovec si kola počítá sám. - Na čas: seznam jednou, co nejrychleji, s koncem po
capsekundách.
Položka je jednoho z pěti druhů, podle svých klíčů:
- Časovaná položka: nastavte
timev sekundách.sides: 2ji spustí jednou na každou stranu. - Silová položka: nastavte
setsareps. Sportovec po každé sérii klepne na Hotovo a pak běží časovač odpočinku.repsaloadmohou uvádět hodnotu pro každou sérii zvlášť ([12, 10, 8]);tempose zobrazí jako pokyn. - Vis: repeatery na hangboardu:
hangsekund,repeatsvisů v sérii soffsekundami mezi nimi,setsssetRestmezi nimi,preppřed první sérií agripaedge. - Odpočinek: položka pouze s
restv sekundách. - Položka seznamu: řádek seznamu AMRAP nebo Na čas:
exerciseareps, číslo nebo text jako400 m.
Soubory
Specifikace formátu je v angličtině, aby odpovídala souborům, které aplikace čte.
An open JSON format for structured training plans: intervals/HIIT, strength (sets × reps), mobility, hangboard repeaters, supersets, AMRAP and For Time blocks, and bell rounds. One parser, one review screen: every import route (file, link, QR, Describe a workout) converts into this format, and so will the Garmin Connect and TrainingPeaks routes planned after launch.
- Extension:
.sestava· UTIapp.sestava.plan(conforms topublic.json) - MIME:
application/vnd.sestava.plan+json - Encoding: UTF-8
- Schema:
schemas/plan.schema.json(JSON Schema 2020-12) - CSV variant for spreadsheet coaches:
schemas/plan-csv.md - Versioning:
VERSIONING.md· Changes:CHANGELOG.md
Document
{
"format": "sestava.plan/1",
"name": "Strength Foundations",
"author": "Coach Novak",
"id": "Sf3nR8wQ2cLx6YtB",
"version": 2,
"category": "strength",
"note": "Log every top set.",
"weeks": [ { "days": [ { "name": "Full Body A", "blocks": [ … ] } ] } ]
}
| Field | Type | Required | Notes |
|---|---|---|---|
format | string | yes | sestava.plan/1 or sestava.plan/1.<minor> |
name | string | yes | non-blank |
author | string or null | no | trimmed; blank is treated as absent |
id | string of 1–64 A–Z a–z 0–9 _ - | with version | stable plan identity; Studio generates it once and never changes it |
version | integer 1–9007199254740991 | with id | raised when a plan that was already shared is shared again after edits |
category | one of the values under category | no | cover colour in the app and on the athlete page |
note | string or null, at most 500 characters | no | a short note from the coach, shown to athletes |
progression | object | no | reserved, see progression; the default for every strength item |
weeks | array of week | one of | a program |
blocks | array of block | one of | a single workout; exactly one of weeks / blocks |
id and version are both present or both absent; a writer must not emit one without the
other. version stops at 2^53 − 1 so a JavaScript reader holds every value exactly. Length limits count Unicode code points as written, before trimming (what JSON
Schema’s maxLength counts), so a note of 500 emoji is allowed.
A single workout given as top-level blocks is read as one week with one day named after
the plan. Writers emit blocks again for such plans. A single workout is a plan with one
week and one day named after the plan; a plan with one week and one day whose name differs
from the plan is a one-workout program, not a single workout.
category
| Value | Label |
|---|---|
strength | Strength |
hiit | HIIT |
mobility | Mobility |
core | Core |
cardio | Cardio |
recovery | Recovery |
climbing | Climbing |
yoga | Yoga |
boxing | Boxing |
The label is what the review row Category: {Label} and Studio’s category chips show.
Writers emit only these values (the JSON Schema enum is strict). The set may grow in a minor, so readers are lenient: an unknown string is treated as absent and the review adds
the warning Unknown category “{raw}”; the plan opens without one; the plan is written back
without category. "category": null and a non-string value are invalid.
week
| Field | Type | Required |
|---|---|---|
days | array of day, min 1 | yes |
day
| Field | Type | Required |
|---|---|---|
name | string, non-blank | yes |
blocks | array of block, min 1 | yes |
block
| Field | Type | Required | Notes |
|---|---|---|---|
section | string, non-blank | yes | shown as the kicker, e.g. Warm-up, HIIT |
mode | normal, superset, amrap or fortime | no | how the block runs; default normal |
cap | integer 1–86400 | in amrap and fortime only, required there | seconds |
bell | boolean | no, normal only | rounds start and end on a bell; default false |
warn | integer 1–86400 | no, only with bell: true | a knock this many seconds before the end of each timed item; shorter than every timed item’s time |
rounds | integer 1–1000 | no, normal only | repeats the items; default 1 |
items | array of item, min 1 | yes | at least 2 in a superset |
| Mode | Items it may hold | How it runs |
|---|---|---|
normal | timed, strength, rest, hang | the items in order, rounds times |
superset | strength only, at least two, all with the same sets | set by set: A1, A2, … then the last item’s rest, except after the last set |
amrap | block exercises | as many rounds of the list as fit in cap; the athlete counts rounds |
fortime | block exercises | the list once, as fast as possible; stops at cap |
A superset’s letter is its position among the day’s superset blocks (A … Z, then AA, AB …);
each reps step is tagged with the letter and the item’s 1-based position (“A2”). rest is
allowed only on a superset’s last item.
The cap label is {s} s under a minute, {m} min for whole minutes and {m}:{ss} min
otherwise (30 s, 6 min, 1:30 min).
item — one kind, decided by keys
In a normal or superset block the kinds are tried in this order: hang makes a hang item;
otherwise time a timed item; otherwise sets a strength item; otherwise a lone rest (no
exercise) a rest item. An item with both time and sets and no hang, or with exercise
but none of hang, time and sets, is invalid. In an amrap or fortime block every item
is a block exercise.
Timed (time): counts down and advances automatically.
{ "exercise": "Side Plank", "time": 30, "sides": 2, "cue": "Hips high", "load": "bodyweight" }
Strength (sets + reps): the athlete taps Done Set; then rest seconds of rest start.
{ "exercise": "Bench Press", "sets": 3, "reps": [12, 10, 8], "rest": 90, "tempo": "3-1-1-0", "load": ["60 kg", "65 kg", "70 kg"] }
Rest (rest only):
{ "rest": 20 }
Hang (hang + repeats): hangboard intervals.
{ "exercise": "Half Crimp Repeaters", "prep": 5, "hang": 7, "off": 3, "repeats": 6, "sets": 5, "setRest": 180, "grip": "Half crimp", "edge": "20 mm", "load": "+5 kg" }
Block exercise (every item of an amrap or fortime block): one line of the work list.
{ "exercise": "Run", "reps": "400 m" }
Allowed keys per kind
✓ required, ○ optional (default in brackets), — not allowed, ignored: writers must not emit it, readers ignore it.
| Key | timed | strength | rest | hang | block exercise | Type |
|---|---|---|---|---|---|---|
exercise | ✓ | ✓ | — | ✓ | ✓ | non-blank string; compared case- and whitespace-insensitively |
time | ✓ | — | — | — | — | integer 1–86400 (seconds) |
sets | — | ✓ | — | ○ (1) | — | integer 1–10000 |
reps | — | ✓ | — | — | ✓ | strength: integer 1–10000 or a list of them; block exercise: integer 1–10000 or non-blank string |
rest | — | ○ | ✓ | — | — | integer 1–86400 (seconds) |
hang | — | — | — | ✓ | — | integer 1–86400; one hang |
repeats | — | — | — | ✓ | — | integer 1–1000; hangs per set |
off | — | — | — | ○ (0) | — | integer 0–86400; between hangs |
setRest | — | — | — | ○ (0) | — | integer 0–86400; between sets |
prep | — | — | — | ○ (0) | — | integer 0–86400; before the first set |
grip | — | — | — | ○ | — | string or null (“Half crimp”) |
edge | — | — | — | ○ | — | string or null (“20 mm”) |
sides | ○ (1) | ○ (1) | — | — | — | 1 or 2 |
tempo | — | ○ | — | — | — | string or null (“3-1-1-0”), shown as “Tempo 3-1-1-0” |
cue | ○ | ○ | — | ○ | ○ | string or null; shown hint (not spoken) |
load | ○ | ○ (single or list) | — | ○ | ○ (single) | non-blank string or null, e.g. 16 kg, bodyweight |
image | ○ | ○ | — | ○ | — | string or null; absolute http(s) URL, an invalid value is a review warning |
progression | ignored | ○ | ignored | ignored | ignored | object, reserved; see progression |
- A key marked — is an error at that key (
not allowed on a timed item; in anamraporfortimeblocknot allowed in an AMRAP block/not allowed in a For Time block), never ignored. “Unknown keys are ignored” holds only for keys the format does not define, and forprogression, the one defined key a reader ignores (it is never marked —). Block keys on an item and item keys on a block are unknown there and ignored. A key counts as present when the object has it, even with the valuenull. grip,edge,tempo,cue,loadandimagemay benull(absent).sides,hang,repeats,off,setRest,prep, a hang item’ssets, a block exercise’srepsand the block keysmode,cap,bellandwarnrejectnull.repsandloadon a strength item are one value, or a list with exactly one value per set (the step for set n takes the nth value). List elements follow the single-value rules and may not benull. Lists are kept as written:[8, 8, 8]stays a list.- A block exercise’s
repsstring is trimmed; if only the digits 0–9 remain it is that whole number (" 10 ","10"and10are the same plan, and"0"or"10001"are errors). Anything else is text shown as written ("400 m","10/side"); readers never parse it.
progression
Reserved in 1.0 (#408); its semantics come with progression rules (#407). No 1.0 reader applies it.
"progression": { "rule": "double", "step": 1, "range": [8, 12], "ladder": "push-up", "stepBackAfter": 2, "deloadEvery": 6 }
It may appear on the document, where it is the default for every strength item of the plan, and on a strength item, where it overrides the document’s block for that item. Nowhere else: on a timed, rest or hang item and on a block exercise the schema forbids it, so writers must not emit it there, and readers ignore it; on a block, week or day it is an unknown key. A later minor may allow it in more places.
| Field | Type | Reserved for (semantics in #407) |
|---|---|---|
rule | non-blank string | the rule kind, such as double (double progression) |
step | integer 1–10000 | reps added per set when the rule advances |
range | array of exactly 2 integers 1–10000 | the reps range [low, high]; reaching high advances the ladder |
ladder | non-blank string | a ladder id from the Format library |
stepBackAfter | integer 1–1000 | failed sessions before a step back |
deloadEvery | integer 1–1000 | weeks between deloads |
Every field is optional and the object may be empty. Only the six names are reserved; their
types are provisional and may widen in a minor. Unknown keys inside progression are
ignored, as everywhere.
- Readers that do not apply it ignore it: its presence, its contents and its validity change
nothing about reading, review or execution. A plan with
progressionreads, reviews and runs exactly as the same plan without it. - Readers that apply it never reject a plan over it: an invalid or misplaced
progressionis a review warning, and the item runs as written. - The canonical form leaves
progressionout until a writer models it. - The key never changes how a plan is executed: progression proposes edits the athlete confirms, and the plan that runs is always the plan as stored.
Error order
Readers report the first error in this order, so every implementation names the same one:
- Block keys:
section,mode, thencap,bell,warn,roundsin that order, each checked for the block’s mode (capmissing inamrap/fortime, or present elsewhere;belloutsidenormalor not a boolean;warnwithoutbell: true;roundsoutsidenormal). - The items, in order. For each item: its kind; then misplaced keys in the global order
exercise, time, sides, cue, load, image, sets, reps, rest, tempo, prep, hang, off, repeats, setRest, grip, edge; then missing required keys; then values in the kind’s canonical key order. - Rules across items. Superset: every item’s
setsequals the first item’s; thenrestonly on the last item; then strength items only; then at least two items. Bell block:warnshorter than every timed item’stime.
| Rule | Error |
|---|---|
mode not one of the four words | Invalid "mode" (circuit) at blocks[0].mode: must be one of normal, superset, amrap, fortime |
cap missing in amrap / fortime | Missing "cap" at blocks[0] |
cap in normal / superset | … cap: allowed only in AMRAP and For Time blocks |
bell not a boolean / outside normal | … bell: expected true or false / … bell: allowed only in normal blocks |
warn without bell: true | … warn: allowed only with bell |
warn ≥ a timed item’s time | Invalid "warn" (10) at blocks[0].warn: must be shorter than every timed item in the block |
rounds outside normal | … rounds: not allowed in a Superset block (an AMRAP block, a For Time block) |
superset sets differ | Invalid "sets" (4) at blocks[0].items[1].sets: must equal the first item's sets (3) |
superset rest not on the last item | … rest: allowed only on the last item of a Superset block |
| non-strength item in a superset | Invalid "items" at blocks[0].items[1]: a Superset block holds strength items only |
| superset with one item | Invalid "items" (1) at blocks[0].items: a Superset block needs at least two items |
| list of another length | Invalid "reps" (2) at blocks[0].items[0].reps: must list one value per set (3) |
sides not 1 or 2 | … sides: must be 1 or 2 |
Rules
- Unknown keys are ignored, so a
sestava.plan/1.xdocument written by a newer app still opens on an older one (with a review warning). Keys the format defines are never ignored, exceptprogression(see progression): one a kind or mode does not allow is an error (see “Allowed keys per kind”). - Duplicate JSON keys are undefined:
Foundation’sJSONDecoderkeeps the first occurrence, where some other parsers keep the last. Do not rely on either behaviour; writers must not emit duplicate keys. - Writers emit the canonical form: keys in this order (document
format,name,author,id,version,category,note,weeks,blocks; blocksection, mode, cap, bell, warn, rounds, items; timedexercise, time, sides, cue, load, image; strengthexercise, sets, reps, rest, sides, tempo, cue, load, image; restrest; hangexercise, prep, hang, off, repeats, sets, setRest, grip, edge, cue, load, image; block exerciseexercise, reps, cue, load), absent optional keys and keys equal to their default left out (mode: "normal",bell: false,rounds: 1,sides: 1, a hang’ssets: 1, zeroprep,offandsetRest), lists written as arrays, a block exercise’s whole-numberrepswritten as a number ("010"is written10), two-space indent exactly as JavaScript’sJSON.stringify(plan, null, 2), and a trailing newline. The same plan always gives the same bytes, so the same plan always gives the same link. - Empty
weeks,days,blocksoritemsare invalid: a plan must have something to do. - Text values are trimmed:
name(plan and day),author,section,exercise,cue,load,image,note,tempo,grip,edgeand a block exercise’s textreps. A blankauthor,cue,note,tempo,griporedgeis treated as absent; a blankname,section,exercise,loadorrepsis invalid.load,author,note,cue,image,tempo,gripandedgemay benull;id,versionandcategorymay not. - Trimming removes exactly the Unicode
White_Spacecharacters, which is Foundation’swhitespacesAndNewlines: U+0009–U+000D, U+0020, U+0085, U+00A0, U+1680, U+2000–U+200A, U+2028, U+2029, U+202F, U+205F and U+3000. U+FEFF (zero-width no-break space, the byte order mark) is not whitespace and is kept, even at the edge of a value. Other implementations must trim with this explicit set rather than their language’s default trim (JavaScript’sString.prototype.trimremoves U+FEFF and keeps U+0085, for example). Thenormalizationvector pins this. - A trimmed
imageis valid only as an absolutehttporhttpsURL (case-insensitive scheme) with a non-empty host, including an IPv6 literal host in brackets ([::1], not validated as IPv6 beyond the brackets themselves); anything else — no scheme, no host, a space or other character breaking the host — is kept exactly as written and is a review warning, never an error. A valid URL’s path, query and fragment are percent-encoded for two cases only: a literal space (as%20) and any non-ASCII character (as its UTF-8 bytes, each%XXuppercase); the scheme, host, port, userinfo and anything already percent-encoded are kept exactly as written. This is Foundation’sURL(string:)behaviour for these two cases, confirmed identical on macOS and theswift:6.4Linux container that CI uses, so it is the format’s rule rather than a Foundation quirk; other differences between Foundation’s parser and a strict RFC 3986 reading (for examplehttps:///x, or a userinfo containing@) are out of scope and not pinned. Theimage-url-normalizationvector pins a space, a non-ASCII path and an IPv6 host. - Readers reject a day that would flatten to more than 5000 steps; the cap is enforced by
readers, not the JSON Schema, because a flattened step count is derived from
roundsand each item’s kind (and, for strength items, whetherrestis present) and cannot be expressed as a JSON Schema constraint on any single field. The schema also cannot express equalsetsin a superset,restonly on a superset’s last item, a list length equal tosets,warnshorter than the block’s timed items, or a block exercise’s digit-stringrepsbounds; readers enforce those.
Link identity
Re-importing a plan updates the stored plan it matches instead of creating a duplicate. The match is decided by the first rule that applies:
id. A plan that carriesidis identified by it, whatever route it came by (link, QR code or file). Importing it with a higherversionreplaces the stored plan and keeps its history and progress; the app says “A newer version is available”. The same or a lowerversionopens the stored plan unchanged.- Fragment payload. A plan without
idopened from a fragment link (https://sestava.app/p#<payload>) is identified by the SHA-256 of the decoded payload bytes (the plan’s UTF-8 JSON after base64url decoding and, unless the payload is marked~, inflating), so two different plans never share an identity. - Canonical link. Otherwise a link’s identity is its canonical form (lowercase scheme and
host, no default port, no fragment, no
utm_*/fbclid/gclidquery items, no trailing slash). Re-importing a link or QR code in a different but canonically equivalent form (e.g. a tracking query added, or a trailing slash) updates the same plan. Path and query keep their percent-encoding, so/p/7K%2FQ2and/p/7K/Q2(or?v=a%26band?v=a&b) are different links. User info (user:pass@) is dropped. Remaining query items keep their order and the case of their names, so?a=1&b=2and?b=2&a=1are different links.
A file import without id is always a new plan. The core computes the rule as
PlanIdentity.
Execution semantics (what a player does with a day)
Flatten the day into steps, block by block:
| Step | Produced by |
|---|---|
| timed | a timed item: one step, or a left then a right step of the full time with sides: 2 |
| reps | a strength item: one per set, or a left then a right step per set with sides: 2; the step for set n carries the nth reps and load of a list |
| rest | a rest item; a strength item’s rest after each set in a normal block (after the pair with sides: 2); a superset’s last-item rest between rounds only; a hang item’s setRest between sets |
| prep | a hang item with prep > 0: once per round, before its first set |
| hang | a hang item: repeats per set |
| off | a hang item with off > 0: between the hangs of a set, none after the last |
| amrap | an amrap block: one step of cap seconds named AMRAP {cap label} carrying the list |
| fortime | a fortime block: one step with a cap-second limit named For Time carrying the list |
A normal block with rounds repeats its items that many times. A superset runs, for set s in
1…sets, each item’s reps step in order (left then right with sides: 2), then the last item’s
rest unless s is the last set. A hang item makes, per round,
(prep > 0 ? 1 : 0) + sets × repeats + (off > 0 ? sets × (repeats − 1) : 0) + (setRest > 0 ? sets − 1 : 0)
steps.
The athlete counts AMRAP rounds ("+1 Round"); reaching the cap ends the block with the rounds counted. A For Time block ends when the athlete taps Done (the time taken, pauses excluded) or at the cap (“capped”). Skipping or ending either block before its cap records it as skipped; once the cap has passed, even with no tick yet (a Live Activity action while suspended), it records the cap result instead. Results are keyed by the block’s 0-based position in its day.
In a bell block the timed and rest steps ring a bell instead of their tones, and with warn a knock sounds that many seconds before the end of each timed step.
Estimated duration (the estimate convention, shared by the app and Studio): timed, rest, prep,
hang and off steps count their seconds, amrap and fortime steps their cap, and each reps step
counts 45 seconds (both sides count; DurationEstimator.repsStepEstimate in the core). A plan’s estimate is the sum over all of its days.
Examples: examples/. full-body-a.sestava flattens to 25 steps and
estimates 1065 seconds; hangboard-repeaters 61 steps and 1070 s, gym-mix 10 and 1110 s,
boxing-rounds 9 and 900 s, evening-yoga-flow 14 and 885 s.
Conformance vectors
vectors/ holds one file per example (Format/examples/*.sestava) and per valid test fixture, written by the
core’s ConformanceVectorTests. It also covers reader-only fixtures: documents the JSON Schema
rejects (so writers must not emit them) that a reader still opens, such as an unknown
category. Any other implementation of the format (Studio’s TypeScript
module) must reproduce everything but source from source:
{
"source": { "…": "the input document, object keys sorted" },
"canonical": { "…": "the canonical form (see Rules), keys in schema order" },
"steps": 16,
"durationSeconds": 240,
"checks": [ { "severity": "ok", "message": "Format sestava.plan/1 is valid" } ]
}
steps and durationSeconds follow “Execution semantics” summed over all days.
vectors/invalid/ holds one error vector per invalid test fixture that
parses as JSON. An implementation must report the same first error for source:
{
"source": { "…": "the fixture, object keys sorted" },
"error": {
"path": "blocks[0].items[0].rest",
"message": "Invalid \"rest\" at blocks[0].items[0].rest: allowed only on the last item of a Superset block"
}
}
error.path is the error’s location ($ for errors without one, such as a missing format);
error.message is the core’s PlanDecodingError.errorDescription, stable per case. checks
are the review rows, computed with no bundled exercise images. Each file is exactly
JSON.stringify(vector, null, 2) plus a newline. CI fails when a committed vector is stale;
regenerate with
SESTAVA_UPDATE_VECTORS=1 swift test --package-path Packages/SestavaCore --filter 'ConformanceVectorTests|ErrorVectorTests'
(the filter is a regular expression; both suites write vectors).
CSV z tabulky
Naplánujte trénink v tabulce a vyexportujte ho jako CSV. Buňky, které se nepoužijí, nechte prázdné.
Specifikace formátu je v angličtině, aby odpovídala souborům, které aplikace čte.
One row per item. Header row required; columns may appear in any order, header names are trimmed, and extra columns are ignored.
- Delimiter:
,,;or tab, sniffed from the header line (the first non-blank line): the candidate occurring most often in the raw header line wins; among candidates tied for most,,beats;and;beats tab; no candidate at all means,. Quotes are not considered while sniffing, so header names must not contain a delimiter. - Quoting: fields containing the delimiter, quotes or line breaks are quoted with
", and a quote inside a quoted field is doubled (""). Only a quote at the start of a field opens a quoted field; a quote anywhere else is a literal character, soHold 5" off floorimports as written. A quoted field may span lines; the error line of a record is the physical line where it starts. Field values are trimmed of surrounding whitespace and line breaks, so a quoted"S\n"reads asSand a value holding only a line break is blank. - Lines: a UTF-8 BOM is ignored; CRLF, LF and lone CR all end a line. Blank lines, and lines whose fields are all blank, are ignored.
week,day,day_name,section,rounds,exercise,time,sets,reps,load,rest,cue
These twelve columns are required. The session-format columns are optional: a sheet without one reads as if every cell in it were blank, so older sheets import unchanged.
mode,cap,bell,warn,hang,repeats,off,set_rest,prep,grip,edge,sides,tempo
| Column | Rule |
|---|---|
week, day | positive integers; empty means 1. They are positions, not labels: weeks are ordered by number and days by number within a week, whatever order the rows come in. Week numbers must be 1…N without gaps, and so must the day numbers within each week. All rows of a day must be contiguous; weeks may be interleaved (week 2’s rows between week 1’s days) as long as each day’s rows are contiguous. |
day_name | taken from the first row of a day; empty means Day <day>, except in a sheet with exactly one day, where that day takes the plan name so the sheet imports as a single workout. day_name must be the same on every row of a day: a later row may leave it blank (it is ignored) or repeat the first row’s name, but a different non-blank name is an error; when the first row leaves it blank, every later row of the day must too. |
section | trimmed; must not be blank. Consecutive rows with the same section form one block |
rounds | section-level; empty means 1, maximum 1000; must be blank in a superset, AMRAP or For Time section |
mode | section-level; blank means normal; otherwise normal, superset, amrap or fortime (lowercase, as in JSON) |
cap | section-level; seconds 1–86400; required in an AMRAP or For Time section, not allowed elsewhere |
bell | section-level; yes, no, true, false, 1 or 0, any case; blank means no bell; normal sections only |
warn | section-level; seconds 1–86400; only with bell; shorter than every timed row’s time in the section |
time, rest, hang | seconds, 1–86400 |
off, set_rest, prep | seconds, 0–86400; blank means 0 |
sets | 1–10000 (on a hang row, blank means 1) |
repeats | 1–1000 |
reps | strength rows: split at /, each part 1–10000; more than one part is a list with one value per set (12/10/8). AMRAP and For Time rows: the whole cell, never split; only digits read as a number (1–10000), anything else as text (10/side, 400 m) |
load | strength rows: split at / only when the part count equals sets (60 kg/65 kg/70 kg on a 3-set row is a list; 20/14 kg or RPE 7/8 on a 3-set row stay one load); a list part must not be blank. Other rows: one load, never split |
sides | 1 or 2; blank means 1 |
grip, edge, tempo, cue | text; blank means absent |
Section-level columns (rounds, mode, cap, bell, warn) are set by the first row of a section; later rows leave them blank or repeat the same value, and a different value is an
error naming the line. A column the section’s mode never allows (bell and warn outside a normal section, cap outside AMRAP and For Time, rounds outside normal) is an error on every
row, not only the first: a redundant bell,no on a later row of an AMRAP section errors exactly
as bell,yes would, it does not import silently.
Rows are classified by their non-blank cells, as JSON items are by their keys. In an AMRAP or
For Time section every row is a block exercise (exercise + reps, optional load and
cue). Elsewhere hang makes a hang row (exercise + hang + repeats), then time a timed
row (exercise + time), then sets a strength row (exercise + sets + reps), then a lone rest a rest row. A non-blank cell in a column the row’s kind does not allow is an error
naming the line (sets is not allowed on a timed row, time is not allowed in an AMRAP section), checked in the JSON key order. A superset section holds strength rows only, all with
the first row’s sets, rest only on its last row, and at least two rows.
Any other combination is an error naming the line number. A gap in the week or day numbers is an error naming the line of the last data row; a day whose rows are split by another day’s rows is an error naming the first row that returns to it. The plan name is not in the CSV; importers use the file name. A day whose rows flatten to more than 5000 steps is rejected, naming the line of the day’s last row.
Sdílení se sportovci
- Pošlete soubor přes AirDrop, zprávy nebo e-mail. Otevře se přímo v Sestavě.
- Umístěte soubor na svůj web a sdílejte odkaz. Otevření na iPhonu plán rovnou importuje.
- Z odkazu udělejte QR kód na nástěnku tělocvičny nebo do tištěného materiálu.
Kontrola
Sestava zkontroluje každý plán před importem a řekne sportovci, co a kde je špatně. Zprávy uvádějí klíč a jeho místo v souboru, například:
Položka v blocks[1].items[0] musí mít právě jeden z klíčů „time“, „sets“, „hang“ nebo „rest“Klíč „tempo“ v blocks[0].items[0].tempo není povolen u časované položky(klíč, který formát definuje, u druhu položky, která ho nepřijímá, je chyba, nikdy se neignoruje; jedinou výjimkou je vyhrazený klíčprogression, který se ignoruje)Klíč „rest“ v blocks[0].items[0].rest je povolen jen u poslední položky části SupersetCSV, řádek 9: hodnota ve sloupci rounds musí být stejná jako na prvním řádku části- „Neznámá kategorie
{raw}; plán se otevře bez kategorie.“ (upozornění, ne chyba, když je kategorie neznámá)
Aplikace tyto zprávy dosud zobrazuje v angličtině; česká verze aplikace ještě nevyšla.
Verze
format is sestava.plan/<major> or sestava.plan/<major>.<minor>. Missing minor is 0.
- Same major: compatible. Readers accept any minor of a major they know.
- Newer minor than the reader knows: read it, ignore unknown keys, show a review warning.
- Newer major: refuse with a clear error.
- Older major (once a major 2 exists): readers migrate explicitly.
A minor bump adds optional keys that do not change how a plan is executed. A major bump may
change or remove anything. Any key that changes how a plan is executed is a major change once
readers ship: an older reader would ignore it and run the plan differently.
Closed value sets (category) grow in minors; readers treat an unknown value as absent and warn.
The current version is sestava.plan/1.0.
Bounds are part of 1.0; raising a bound is a minor change, lowering one is major.
1.0 is unreleased; bounds, strictness rules and keys added before release are part of 1.0.
Historie změn
1.0 — 2026-09-24
Initial version: programs (weeks/days/blocks/items) and single workouts (top-level blocks); timed, strength and rest items; rounds; cue, load and image; CSV variant.
Tightened before release: a timed item with rest is invalid (JSON and CSV), and in CSV a rounds value on a later row of a section must match the first row (blank inherits).
Bounds: time and rest are 1–86400 seconds, rounds is 1–1000, sets and reps are
1–10000; a day that flattens to more than 5000 steps is rejected. cue and section are
trimmed (a blank cue is absent, a blank section is invalid); load, author, cue and
image may be null. In CSV, a day’s day_name must match across all of its rows, and a blank section is an error naming the line (as in JSON). A sheet with exactly one day whose
first row leaves day_name blank is a single workout: the day takes the plan name. When a day’s first row leaves day_name blank, its later rows must leave it blank too. In CSV, field
values are trimmed of surrounding whitespace and line breaks.
CSV reading: the delimiter is ,, ; or tab, sniffed from the header line (the first
non-blank line; ties go to ,, then ;, then tab); only a quote at the start of a field
opens a quoted field, elsewhere it is literal; header names are trimmed; quoted fields may
span lines; CRLF, LF and lone CR all end a line; blank lines are ignored. week and day are
positions: weeks and days are ordered by number, numbered 1…N without gaps, and a day’s rows
must be contiguous.
Plan identity and presentation, added before release: optional top-level id (1–64 characters
from A–Z a–z 0–9 _ -) and version (integer 1 to 2^53 − 1), present together or not at all;
category (strength, hiit, mobility, core, cardio, recovery, climbing, yoga or
boxing; writers emit only these, readers treat an unknown string as absent and the review
warns, null is invalid); note (string or null, at most 500 code points as
written, trimmed, blank is absent). Writers emit the canonical form: schema key order,
two-space indent, trailing newline. Link identity is now id first, then the SHA-256 of a /p#… link’s decoded payload, then the canonical URL. The duration estimate counts a reps step
as 45 seconds (was 40).
Normalisation, settled before release: author is trimmed like every other text field and a blank author is treated as absent. Trimming removes exactly the Unicode White_Space
characters (U+0085 and U+00A0 included); U+FEFF is not whitespace and is kept.
Session formats, added before release: a fourth item kind hang (hang, repeats, optional
prep, off, setRest, sets, grip, edge, cue, load, image); block mode
(normal, superset, amrap, fortime) with cap (seconds, required in amrap and
fortime), bell and warn (normal blocks only); the block exercise, the item shape of
amrap and fortime blocks (exercise, reps as a whole number or text, cue, load);
sides (1 or 2) on timed and strength items; tempo on strength items; list-valued reps
and load on strength items (one value per set). A key the format defines but a kind or mode
does not allow is now an error at that key. The canonical order and default omission cover the
new keys. CSV gains the optional columns mode, cap, bell, warn, hang, repeats,
off, set_rest, prep, grip, edge, sides and tempo, and reps on strength rows
splits at /. The duration estimate counts prep, hang and off steps at their seconds and
amrap and fortime steps at their cap. Error vectors (vectors/invalid/) pin the first error
for every invalid fixture.
Names, settled before release (#16, 2026-10-05): the format id is sestava.plan/1 (the earlier
working name setlist.plan/1 was never shipped and is not read), the file is name.sestava
(one extension, so iOS types it as a Sestava plan), the UTI is app.sestava.plan conforming to
public.json, the MIME type is application/vnd.sestava.plan+json, and the schema’s $id is the
URL the site serves it at.
Progression, reserved before release (#408): an optional progression object on the document
and on strength items (rule, step, range, ladder, stepBackAfter, deloadEvery;
semantics in #407). Readers that do not apply progression ignore it; readers that do never
reject a plan over it (a review warning). The canonical form leaves it out until a writer
models it.