Tidigar Roadmap Interoperability Specification

See the roadmap JSON example for an activity with dates, progress and a dependency in this format.

Status: public pre-release specification. It defines how another application, script or AI agent can create, read and edit Tidigar roadmaps stored as JSON files or .tidigar documents. Calendar events are described in the calendar storage specification.

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, SHOULD NOT and MAY are normative. The canonical Tidigar project is specified field by field, followed by the envelopes and mappings used by each storage backend. JSON field names remain descriptive; only the calendar URL has a separate packed transport, and shareable share links have their own compact encoding in the share link specification. The same project can also be written by hand or reviewed as Markdown, as the Markdown specification describes.

Implementer quick start

  1. Read a project through one of the storage profiles.
  2. Validate and normalize it using every rule in the field tables. Unknown project fields MUST NOT be relied upon: Tidigar drops them when it writes a canonical copy.
  3. Preserve array order. IDs are UUIDs and MUST remain stable when editing an existing record. New IDs SHOULD be cryptographically random UUIDv4 values.
  4. Write the same storage profile unless the user explicitly converts it.
  5. For calendars, follow the calendar storage specification.

A conforming reader rejects unsupported versions and invalid required fields rather than guessing. A conforming writer emits all required fields, valid references and exactly the current versions in the version table. Tidigar is pre-release: there is no compatibility reader or migration for an older incompatible version.

How to read the tables

Normalization is a whitelist: any field not listed here is dropped when a project is loaded or saved. All IDs are UUIDs (versions 1–8), unique across the whole project. Dates are YYYY-MM-DD calendar dates from 1900-01-01 through 2200-12-31, without time or time zone. Colors are #rrggbb.

Project

{ manifest, items, sharedViews, milestones, periodIndicators }
CollectionHoldsLimit
itemsActivities10,000
sharedViewsSaved views99
milestonesMilestones1,000
periodIndicatorsPeriod indicators1,000

Array order is meaningful and preserved in every backend; a calendar keeps it as a rank in each record's event URL.

Manifest

FieldTypeRequiredRuleDefaultCalendarUsed by
projectIdUUIDyesProject identity; every record's projectId must equal it—URL p on every eventIdentity in all storage profiles and links
formatVersionintegeryesone of 1—Manifest NotesRejects other model versions on load
nametextyesmax 100 chars; not blank—Manifest Notes; manifest event titleRoadmap title, library card, calendar name at creation
descriptiontextyesmax 500 chars; may be empty—Manifest NotesRoadmap dialog
dimensionsarray of dimensionyesmax 20 entries—One dimension event eachBoard axes, filters, colors, grouping, List columns
yearintegeryes1900 through 2200—Manifest NotesDefault timeline range of a view without one
scheduleenumnoone of calendar-days, work-days; see Work days"calendar-days"Manifest NotesDate changes, activity dialog, compacting the plan
shareLinkCreatedAtUTC timestampnoISO 8601 YYYY-MM-DDTHH:mm:ssZ, valid date and timeabsentManifest NotesShared version in the library and properties
refPrefixtextno1 to 10 capital letters A–Z and digits, starting with a letter; omitted when emptyabsentManifest NotesReference keys of new records; library card

shareLinkCreatedAt records when the sharing link that created this copy was created. It is preserved when editing or exporting the copy, independently of storage creation and modification times. An older link has no timestamp; readers must not invent one. Sharing the copy again creates a new link timestamp.

refPrefix starts the reference keys an editor gives new activities and milestones.

Dimension

FieldTypeRequiredRuleDefaultCalendarUsed by
idUUIDyes——Dimension Notes; URL iItem values, view axes, filters and columns
nametextyesmax 100 chars; may be empty—Dimension Notes; event titleDimension labels and column headers
optionsarray of optionyesmax 100 entries—Dimension NotesValues an item can take in this dimension

Option

FieldTypeRequiredRuleDefaultCalendarUsed by
idUUIDyes——Dimension NotesItem values, filters, colorOverrides
nametextyesmax 100 chars; may be empty—Dimension NotesOption labels
colorcoloryes#rrggbb—Dimension NotesItem color when the view colors by this dimension

Item (activity)

FieldTypeRequiredRuleDefaultCalendarUsed by
idUUIDyes——URL iIdentity, dependencies, links
projectIdUUIDyesEquals manifest.projectId—URL pOwnership check
titletextyesmax 180 chars; may be empty—Event titleAll views, activity dialog
descriptiontextyesmax 12000 chars; may be empty—Event notesActivity dialog, List
progressintegernoone of 0, 25, 50, 75, 1000URL m.progressProgress bar, List
startdateyesNot after end—All-day event startTimeline, List, dependencies
enddateyesInclusive last day—All-day event end, the day after endTimeline, List, dependencies
valuesobjectnoDimension ID → option ID of that dimension, or ""{}URL m.valuesBoard placement, filters, colors, List
dependsOnarray of IDsnoItems and fixed milestones; no duplicates or cycles; start at least one day after each item's end unless startAfter allows overlap, and not before each fixed milestone's date[]URL m.dependsOnTimeline arrows, date cascades
startAfterobjectnoItem ID → one of 0, 25, 50, 75; IDs outside dependsOn are dropped; such an item's end rule becomes start not before its start; omitted when empty—URL m.startAfterOverlap, timeline arrows, date cascades
reftextnomax 32 chars; omitted when empty; need not be uniqueabsentURL m.refReference key before the title, search

startAfter lets an activity overlap one it depends on. The value is how much of that activity’s planned duration has passed when this one may start, independently of its reported progress: 0 with its start, 50 when half its days have passed. An editor places the activity on the day after that share of days, rounded up and counted in work days when manifest.schedule is work-days, and moves it along when the other activity moves. The stored rule only requires that it does not start before the other activity, so a new vacation period never invalidates a project. An absent entry means 100%: start at least one day after the predecessor ends. The latest threshold applies when there are several dependencies. Fixed milestones cannot appear in startAfter; activities may start on their date. Milestones that depend on activities always wait for those activities to end.

Milestone

FieldTypeRequiredRuleDefaultCalendarUsed by
idUUIDyes——URL iIdentity, links
projectIdUUIDyesEquals manifest.projectId—URL pOwnership check
titletextyesmax 180 chars; not blank—Event titleTimeline, milestone dialog
datedateyesAt least one day after each dependency's end—Timed event 12:00–12:15 local time on that dayTimeline
dependsOnarray of item IDsnoItems only; no duplicates; empty when fixed[]URL m.dependsOnTimeline arrows, date cascades
fixedbooleannoAn event on a set date that items wait forfalseURL m.fixedTimeline, date cascades
reftextnomax 32 chars; omitted when empty; need not be uniqueabsentURL m.refReference key before the title, search
sourceobjectnoprojectId and id, both UUIDs, of another plan's milestone; only when fixedabsentURL m.sourceIncoming milestone, warnings

A milestone is either ordinary or fixed. An ordinary milestone, the default, marks a result: it waits for the items in its dependsOn and its date follows them. A fixed milestone is an event on a set date, such as a decision or a delivery: it waits for nothing, items list it in their own dependsOn, and they may start on its date. Editors draw an ordinary milestone as a filled diamond and a fixed one as an outlined diamond. Editors move a fixed milestone only when the user does, and move the items that wait for it with it.

An incoming milestone is a fixed milestone whose source names a milestone of another plan: that plan's manifest.projectId and the milestone's id. Its title and date are this plan's own copy. An editor compares them with the newest copy of the source plan it has and shows when the source moved or removed the milestone; it changes the date only when the user takes the change in. Removing source makes the milestone this plan's own. A source that names this plan itself is invalid. A milestone that stops being fixed loses its source, and a share link leaves source out, so the milestone opens as an ordinary fixed one.

Reference keys

ref is the people-facing key of an activity or a milestone, such as LPP-12, or a number from another system typed or pasted by a person, such as a ticket number. It is free text that a person may change at any time. Keys are not unique: two people may give records the same key before their copies merge, so a reader MUST accept duplicates, and an editor SHOULD point them out rather than refuse them.

An editor that numbers records gives a new activity or milestone the key <refPrefix>-<n>, where n is one more than the highest number any activity or milestone already uses after that prefix, compared without regard to letter case. It does not number existing records. It derives refPrefix once, the first time the project has a name other than the editor's default name for a new roadmap: from two or three words their initials, otherwise the name's first two letters, with letters folded to A–Z and everything else left out, so Launch partner portal gives LPP, Tidigar TI and Öresund OR. A name without such letters gives no prefix. Renaming the project later does not change the prefix; a person may.

Period indicator

FieldTypeRequiredRuleDefaultCalendarUsed by
idUUIDyes——URL iIdentity, links
projectIdUUIDyesEquals manifest.projectId—URL pOwnership check
titletextyesmax 180 chars; not blank—Event titleTimeline band label, period dialog
startdateyesNot after end—All-day event startTimeline band
enddateyesInclusive last day—All-day event end, the day after endTimeline band
colorcoloryes#rrggbb, stored in lowercase—URL m.colorTimeline band color
vacationbooleannoNo work days during the periodtrueURL m.vacationWork days in a work-days roadmap

Work days

A roadmap whose schedule is work-days plans activities in work days: Monday through Friday, except the days of a period whose vacation is true. An activity's duration is the number of work days from start through end, at least 1. Editors keep start on a work day and end on the last work day of that duration, and move the dates when the days off change. Readers must not move stored dates. With calendar-days, every day counts and vacation has no effect.

Saved view

A saved view is a shared, named presentation. Every field is stored in the view's own event Notes; the event title is Tidigar View – <name>.

FieldTypeRequiredRuleDefaultCalendarUsed by
idUUIDyes——URL iIdentity, links, active saved view
projectIdUUIDyesEquals manifest.projectId—URL pOwnership check
nametextyesmax 100 chars; not blank—View NotesSaved-view menu
viewenumnoone of grid, timeline, list, flow"grid"View NotesMain mode; grid is the Board
rowaxisnoDimension ID, "assignee" or """"View NotesBoard rows
colaxisnoDimension ID, "assignee" or """"View NotesBoard columns
colordimension IDnoDimension ID or """"View NotesColoring in all views and exports
presentationStyleenumnoone of "standard", "minimal", "airy", "contrast", "terminal""standard"View NotesCoordinated appearance in light and dark mode
colorOverridesobjectnoOption ID → #rrggbb; at most 100 kept; unknown options dropped{}View NotesPer-view option colors
groupaxisnoDimension ID, "assignee" or """"View NotesTimeline grouping
resolutionenumnoone of year, quarter, month, week"month"View NotesLegacy scale preference
timelineScaleRowsenumnoone of 1, 2, 33View NotesMaximum timeline scale rows
timelineShowWeeksbooleanno—trueView NotesAllow weeks in the adaptive timeline scale
timelineShowQuartersbooleanno—trueView NotesAllow quarters in the adaptive timeline scale
timelineWeekNumbersbooleanno—trueView NotesWeek numbers; otherwise each week's first date
timelineShowTodaybooleanno—falseView NotesToday marker in the timeline and exported images
timelineShowPeriodNamesbooleanno—falseView NotesPeriod names in the timeline legend and exported images
textModeenumnoone of hide, beside, run-past, fit, under"beside"View NotesTimeline labels, exports
filtersobjectnoDimension ID → option ID or ""; unknown entries dropped{}View NotesFiltering in all views
querytextnomax 200 chars""View NotesSaved search text
hideEmptyRowsbooleanno—trueView NotesBoard
hideEmptyColumnsbooleanno—trueView NotesBoard
hideEmptyTimelineGroupsbooleanno—trueView NotesTimeline
hideUnconnectedActivitiesbooleanno—falseView NotesHide activities without incoming or outgoing dependencies in Flow
flowGroupMilestonesbooleanno—falseView NotesGroup Flow milestones in a separate leading lane
hideEmptyFlowGroupsbooleanno—falseView NotesHide Flow dimension lanes without visible activities
flowHideDatesbooleanno—falseView NotesHide activity and milestone dates in Flow and image exports
showAssigneebooleanno—trueView NotesAssignee faces and List column in Tidigar Business plans
showRefsbooleanno—falseView NotesReference keys before titles in Timeline, List and exports
timelineActivityRowsenumnoone of single, single-label, compactsingle, or single-label when an older hideTimelineLabels: false is presentView NotesTimeline row layout
hideTimelineLabelsbooleanderivedtrue exactly when timelineActivityRows is singleComputedView NotesFallback only; see removal candidates
timelineMilestoneModeenumnoone of hide, marker, label, flag"label"View NotesTimeline milestones
timelineMilestonesBesideWorkbooleanno—falseView NotesTimeline milestones in the row of their latest predecessor
timelineSummarizeBydimension IDnoA manifest dimension ID or """"View NotesTimeline summary rows within groups
timelineWeekDaysenumnoone of work, all"work"View NotesTimeline day scale
timelineDependencyArrowsenumnoone of none, grouped, every"grouped"View NotesTimeline dependency arrows
flowDirectionenumnoone of auto, horizontal, vertical"auto"View NotesFlow direction
flowArrowsenumnoone of curved, right-angle"curved"View NotesFlow dependency arrow shape
flowGroupdimension IDnoA manifest dimension ID or """"View NotesFlow swimlanes
flowShapesenumnoone of capsules, flowchart"capsules"View NotesFlow activity and milestone shapes
timelineStartdatenoNot after timelineEndJanuary 1 of manifest.yearView NotesTimeline range
timelineEnddateno—December 31 of manifest.yearView NotesTimeline range
listColumnsarray of column keysnotitle, duration, start, end, dependsOn or a dimension ID; duplicates and unknown keys droppedtitle, start, end, then every dimension IDView NotesVisible List columns
listColumnOrderarray of column keysnoSame keys as listColumnsSame as listColumnsView NotesList column order, including hidden ones

"assignee" in row, col or group arranges activities by the person each is assigned to, with one group per person and one for unassigned activities. showAssignee shows assignees on activities and as a List column. Both are meaningful only in a plan stored in Tidigar Business, where assignments are kept beside the plan; the project itself never holds assignments or people. Anywhere else an "assignee" axis arranges nothing, as "" does, showAssignee changes nothing, and both values are kept.

duration is the activity's length in days, or in work days with a work calendar. A view whose listColumnOrder does not contain duration shows it before the first of start and end, or after title without them.

dependsOn lists the activity's predecessors by name. A view whose listColumnOrder does not contain dependsOn shows it after the last of duration, start and end, or after title without them.

timelineScaleRows limits the adaptive time header to at most one, two or three rows. timelineShowWeeks and timelineShowQuarters allow the corresponding levels by default. Setting either to false excludes that level while the remaining levels still adapt to available width. Without weeks, three day-level rows use year/month/day. With timelineWeekNumbers set to false, each week is labelled with the date of its first day in the reader's own first weekday: the day alone below a month row, otherwise a short date. As space increases, a single row shows year, quarter, month, week, then day; two rows show year/quarter, year/month, month/week, then month/day; three rows show year/month/week, then month/week/day. Quarters, months, weeks and days become visible at average widths of 40, 34, 28 and 22 pixels respectively. Coarser views use fewer rows. resolution is retained for compatibility; it no longer fixes the displayed time scale. Year labels remain visible while scrolling horizontally. No row shows a single value: when a row would span the whole visible range with one cell, quarters replace a lone year if they divide the range, and otherwise the row is left out and its label moves into the first cell of the row below, so a view may show fewer rows than configured, but always at least one. Without a year row, the top row names the year in its first cell, and in the first cell of each new year, or in the next cell of that year with room for it; a top row of weeks or days also names the month. timelineShowToday shows the Today marker when enabled; it is hidden by default. timelineShowPeriodNames lists the periods in the visible range in the timeline's legend, each with its color and name; by default periods show only as colored bands.

timelineDependencyArrows chooses which dependency arrows a timeline draws: none draws no arrow at rest: an arrow appears only while one of its ends is pointed at, focused, dragged, selected or open for editing; grouped also draws the arrows between activities in the same timeline group, and treats every other arrow, including every arrow into a milestone, as none does; every draws every arrow. Without grouping all activities share one group. Printed and exported images draw only the arrows that are always visible.

hideEmptyFlowGroups defaults to false. When enabled, dimension lanes without visible activities are hidden in Flow, including lanes made empty by filters. Milestone lanes remain independent of this choice.

flowGroupMilestones defaults to false: Flow milestones are placed near their dependent activities without dimension membership. When true, milestones use a separate lane, leftmost in vertical Flow and topmost in horizontal Flow.

hideUnconnectedActivities hides activities with no incoming or outgoing dependencies in Flow. Connections to milestones or activities outside the view still count. It affects the view and its exported images.

flowDirection sets which way a flow view reads. horizontal runs each chain of dependencies from left to right and stacks separate chains below each other; vertical runs each chain from top to bottom and places separate chains side by side. auto reads vertically where the view is taller than it is wide, and otherwise horizontally. Exported and printed images read the way the view is shown.

flowArrows chooses curved dependency arrows or arrows with right-angle (90-degree) bends. flowGroup optionally groups activities into swimlanes in dimension option order. Unset values use a No value lane. Milestones sit near their dependency activities, or in a separate leading lane when flowGroupMilestones is enabled. Empty option lanes remain visible unless hideEmptyFlowGroups is enabled. Grouping does not assign or change values.

flowShapes chooses capsules for every node, or flowchart with diamonds for activities with more than one distinct incoming dependency, rectangles for other activities, and capsule milestones retaining their milestone marker. Classification includes dependencies hidden by view filters. These independent choices affect Flow, exported and printed images. Milestone capsules represent milestones, not process start/end or decisions.

textMode sets how an activity's title appears at its bar: hide omits it, beside places it outside the bar whenever the full title fits after its end or before its start. If neither side has room, a title that fits stays inside; longer titles use the side with more room and truncate. run-past lets text run past the bar, fit shows only what fits, and under shows one line below the bar. The former wrapped-below choice reads as under.

timelineMilestoneMode shows milestones as a diamond (marker), a diamond with its title (label), a small flag on a thin pole with its title beside it (flag), or not at all (hide). With timelineMilestonesBesideWork, a milestone that is not fixed and waits for shown activities sits in the row of the one that ends last instead of the milestone group; other milestones keep the milestone rows, whose heading is left out when grouping is disabled or when there are no separate milestones. Under timelineDependencyArrows: "grouped" such a milestone counts as part of its row's group.

timelineSummarizeBy adds a summary row for each of its dimension's values within every timeline group, holding the group's activities with that value; activities without a value follow the summaries. A summary spans from the earliest start to the latest end of its shown activities and is computed, not stored. Summarizing by the group dimension gives each group one summary. A view never records which summaries are collapsed: collapsing a summary is each reader's own choice, outside the project. A reader ignores a timelineCollapsedSummaries field that older data may still hold.

Some values were renamed when their default changed. A reader treats the older timelineMilestoneMode value show as label, every older timelineDependencyArrows value (all, groups, off and activities) as grouped, and every older textMode value (clip, below, wrap and overflow) as beside; a milestone diamond without its title is now marker.

Personal view state

The current mode, selection, search, unsaved view changes, scroll and zoom are personal and device-local. They are never part of the project, exports, backups, documents or calendars, and this specification defines no schema for it.

Storage profiles

All JSON is UTF-8. Object key order and insignificant whitespace have no meaning; array order is significant. A file extension helps users and tools but does not replace the format discriminator.

ProfileSuggested extensionRoot discriminatorContents
Project exchange file.jsonformat: "tidigar-project"Exactly one canonical project in project
Multi-project browser backup.jsonformat: "tidigar-backup"Canonical projects in projects
Native document.tidigarformat: "tidigar-document", version: 1One project exchange value in roadmap, plus document metadata
Calendar—Valid v3 HTTPS URL on every owned eventOne event per project record

Data a browser or app keeps for itself, such as browser storage or personal view state, is not an interchange format and is outside every project payload.

Project exchange file

The root object has exactly this semantic shape:

{
  "format": "tidigar-project",
  "project": {
    "manifest": {},
    "items": [],
    "sharedViews": [],
    "milestones": [],
    "periodIndicators": []
  }
}

project MUST satisfy every canonical-model rule below. Tidigar readers ignore unknown root keys, but interoperable writers SHOULD emit only format and project. The following is a complete minimal file, not pseudocode:

{
  "format": "tidigar-project",
  "project": {
    "manifest": {
      "projectId": "00000000-0000-4000-8000-000000000001",
      "formatVersion": 1,
      "name": "Example roadmap",
      "description": "",
      "dimensions": [],
      "year": 2026,
      "schedule": "calendar-days"
    },
    "items": [],
    "sharedViews": [],
    "milestones": [],
    "periodIndicators": []
  }
}

Multi-project backup

The root object is { "format": "tidigar-backup", "projects": [...] }. Every entry in projects is a canonical project object, without its own tidigar-project wrapper. Order is preserved. This profile is for whole-browser backup and restore; use a project exchange file when sharing one roadmap.

Native .tidigar document

A .tidigar file is JSON and can be processed without Apple frameworks. Its roadmap value is the complete project exchange object above, not only the inner canonical project.

FieldTypeRequired for writersRule
formattextyesExactly "tidigar-document"
versionintegeryesExactly 1
idUUIDyesDocument identity; independent of manifest.projectId
titletextyesLibrary label; the canonical roadmap name remains roadmap.project.manifest.name
createdAttextyesISO 8601 timestamp; legacy readers fall back to updatedAt
updatedAttextyesISO 8601 timestamp
roadmapobjectyesA valid tidigar-project exchange value
thumbnailtextnoStandard padded Base64 bytes of an optional PNG preview in a light color scheme
thumbnailDarktextnoThe same preview in a dark color scheme; only beside thumbnail

Readers MUST use roadmap as the roadmap authority. title, createdAt, updatedAt, thumbnail and thumbnailDark are document-library metadata and MUST NOT overwrite canonical project fields. A reader showing a dark library uses thumbnailDark and falls back to thumbnail when it is missing. Tidigar currently accepts a missing title as "Roadmap", an invalid/missing timestamp as an old timestamp, and an invalid thumbnail as no thumbnail; interoperable writers MUST NOT depend on those recovery defaults.

To edit a file-backed roadmap: decode the applicable envelope, validate the complete canonical project, apply changes while retaining stable IDs and array order, validate again, then atomically replace the file. Writers SHOULD retain the native document id when updating an existing .tidigar file.

Calendar storage profile

A roadmap can also be stored as events in a calendar, one event per record. The calendar storage specification defines the events, their identity URLs and the rules for reading and writing them.

Versions

VersionWhereCurrentChanges when
formatVersionmanifest1The canonical model changes incompatibly
vCalendar event URL3The URL or m layout changes
versionCalendar Notes envelope2The Notes envelope or its payload keys change
versionNative .tidigar document1The document envelope changes