Tidigar embed story specification
Watch the animated story example to see timed changes play on an embedded roadmap, alongside its story script.
Status: public pre-release specification, version 1. It defines how a page that embeds a Tidigar roadmap can play a short story on it: timed changes to the plan, with captions, that the reader sees animate in the embedded views. A tutorial can show a plan changing step by step, and a product page can show what a dependency does.
License: published by Hamacher Software AB under CC BY 4.0. Anyone may implement the formats it describes, in any software, without asking.
The keywords MUST, MUST NOT, SHOULD and MAY are normative. Roadmaps, their records and fields are defined in the Tidigar Roadmap Interoperability Specification; embeds open a share link.
Embedding a story
A story is a script block next to an ordinary embed. Its type is text/tidigar-story, so the browser never runs it, and data-for names the ids of the iframes it plays in. The embed script delivers it.
<iframe
id="vacation"
src="https://embed.tidigar.com/#s=1.…"
title="Vacation plan"
width="100%"
height="480"
style="border:0"
></iframe>
<script type="text/tidigar-story" data-for="vacation">
0:01 caption("The plan counts only weekdays")
0:04 add("period", {"title": "Summer vacation", "start": "2026-07-06", "end": "2026-08-02"})
caption("Work that spans the vacation moves past it")
0:08 applyView("Timeline by team")
0:10 compactRoadmap()
0:14 restore()
caption()
0:16 loop()
</script>
<script async src="https://embed.tidigar.com/embed.js"></script>
- The embed URL is a plain share link. Everything that is set up front belongs in it: the plan, whether it is planned in calendar or work days, its saved views and the view it opens in. A story only does what the reader should see happen.
data-forlists one or more iframe ids, separated by spaces. The first frame plays the story and shows its captions. Every other frame runs only the setup calls (shiftToToday) and follows the first, since frames of one roadmap on a page stay in step: they show the same plan and the same selected activity. A frame not named by any story shows its plan as it is.- Without the embed script, or where a platform strips scripts, the frame shows the plan without the story.
- Nothing is saved. The story changes the frame's plan in memory only, and Open in Tidigar opens the plan as the reader sees it.
Screens
A Tidigar Business plan's screen can carry a story that the plan's editors wrote for it, in this syntax. It plays wherever the screen is shown (https://embed.tidigar.com/#screen=…): in a full window, as on a wall screen, and in a frame on a page, as a story block would, and every rule on this page applies. A story block on the publishing page names the frame as usual and wins over the screen's story. When the plan changes while a story plays, the screen takes the new version when the story starts again.
Syntax
A story is plain text, one call per line:
[m:ss[.s]] name(arguments)
- The timecode is optional and counts from the start of the story: minutes, then seconds with at most one decimal (
0:03,1:02.5). A line without a timecode runs at the same moment as the line above it, and the first lines without one at0:00. Timecodes MUST NOT go backwards. nameis one of the primitives below. Nothing else is ever run: a story cannot run code, load anything or insert HTML.- The arguments are JSON values separated by commas, read as a JSON array. Text is in double quotes; dates are
"YYYY-MM-DD". - Blank lines and lines starting with
#are ignored. - A story MUST NOT be longer than 65,536 characters or hold more than 500 calls.
All calls at one moment change the plan together, and the views animate them as one change: bars glide, cards move, arrows draw. With reduced motion the changes appear at once.
The calls at 0:00 that change the plan set it up instead: every frame of the story runs them at once, without animation, before the reader sees the plan, as it runs shiftToToday. A story can so open on an earlier state of the plan than the one stored, and play forward from there. caption and focus at 0:00 show as the story starts.
Primitives
| Call | Does |
|---|---|
changeDates("Title", {"start", "end"}) | Moves an activity's start, end or both, as a drag does; what waits for it follows. A date is absolute or relative to the activity's own: "+3d", "-2d". |
update("Title", {…}) | Sets fields of an activity (start, end, progress, title, description, and dimension values by name: "Status": "Done", or null to clear), a milestone (date, title) or a period (start, end, title, color, vacation). |
add("kind", {…}) | Adds a record of a kind, "activity", "milestone" or "period", with a title and its dates (start and end, or date for a milestone), and the same optional fields as update. A period is a vacation unless "vacation": false. |
remove("Title") | Removes an activity, milestone or period. Nothing waits for a removed activity any more. |
connectActivities("Child", "Parent", 50) | The child, an activity or a milestone, now waits for the parent activity and moves after it if needed. The optional share, 0, 25, 50, 75 or 100 (the default), is how much of the parent’s planned duration passes first, independent of reported progress: with 50 an activity may start halfway through another activity. A milestone, and anything waiting for a milestone, waits for the end. |
changeDependency("Child", "Parent", 50) | Changes how much of the parent passes before the child may start, as above; 100 waits for its end again. The child moves to the new start if needed. |
removeDependency("Child", "Parent") | The child no longer waits for the parent. |
compactRoadmap() | Every activity that waits for another starts as soon as it can, and milestones follow what they wait for. |
applyView("Saved view name") | Switches the playing frame to one of the plan's saved views. |
caption("Text") | Shows a caption at the bottom of the playing frame until the next caption; caption() clears it. |
focus("Title") | Selects an activity or milestone, as a tap does, in every frame of the roadmap on the page: its chain stands out, and the flow straightens the critical line through it. focus() selects nothing. |
hide("Title") | Takes an activity, milestone or period out of the plan until show, as if it were not there yet: nothing waits for it meanwhile, and calls that name it are skipped. |
show("Title") | Puts a hidden record back where it stood, with its fields as they were. It waits again for what it waited for, and what waited for it waits again, wherever both are shown; what waits moves after it if needed. |
today("2026-03-14") | Shows the today line on that date, as on the day of a status meeting, even where the view hides it; the plan does not move. today() returns the line to the real day and the view's own setting. |
shiftToToday("Title", 2) | Setup: moves the whole plan so that today is two days into that activity or at that milestone, without animation. Every frame of the story runs it at once. |
restore() | Returns the plan, the view, the hidden records and the today line to how they were when the story started, after its setup, with nothing selected. |
loop() | Starts the story over from 0:00. A story without it stops after its last call. |
Records are named by their title, exactly as the reader sees it. Every change works as the same edit in Tidigar does: what waits for a moved activity follows, milestones keep their place after what they wait for, and in a plan planned in work days, adding or moving a vacation moves the work around it.
A plan's history
A story can show how a plan changed over time, such as the versions of a schedule shown at a series of status meetings. The plan holds the latest version, so it stays the plan people work in, and the story opens on the first version and plays the changes up to the latest:
# Setup at 0:00: the plan as it was at the first meeting.
0:00 hide("Pilot")
hide("Go-live")
update("Design", {"end": "2026-02-27", "progress": 25})
update("Build", {"start": "2026-03-02", "end": "2026-04-24"})
today("2026-02-02")
caption("2 February: the plan at kickoff")
0:04 update("Design", {"end": "2026-03-13", "progress": 75})
changeDates("Build", {"start": "2026-03-16", "end": "2026-05-15"})
show("Pilot")
today("2026-03-02")
caption("2 March: design takes two weeks more, and a pilot is added")
0:08 update("Design", {"progress": 100})
update("Build", {"end": "2026-05-29"})
show("Go-live")
today("2026-04-06")
caption("6 April: the stored plan")
0:13 loop()
- Each meeting is one moment: its date in
today, its changes, and a caption that says what changed. Four to six seconds per meeting reads well. - Records that came later are hidden at
0:00and shown at the meeting where they appeared; records that were dropped are removed at that meeting and, if the stored plan no longer has them, added at0:00and removed later. - A renamed record carries its latest title in the plan: the setup sets the earlier title with
update, and the meeting where it changed sets the new one. - The last moment brings the plan to the stored version, so the story ends where the plan stands.
Errors
A story never stops at a bad call. A line that cannot be read, and a call that names a missing or ambiguous title, an unknown field, a dimension value that does not exist or a change the roadmap rules forbid, such as a dependency cycle, is skipped with a warning in the browser console that gives its line number. The plan stays as it was before that call, and the story goes on.
The reader stays in control
- The first touch, key or wheel in any frame of the page stops every story on it; a small button in the playing frame replays the story from the start.
- A reader who prefers reduced motion sees the button instead of a playing story.
- A story pauses when all the frames named by its
data-forare outside the viewport, or the containing page is hidden. If any named frame is visible, the story continues in every view, including the hidden ones, keeping them synchronized. Returning to the page or a named frame resumes from the paused time, without restarting or rushing through missed steps. Viewport pausing requires a browser that supports intersection observation. - While a story plays its frame only grows, so a change of view does not make the page jump.