Tidigar Roadmap Interoperability Specification
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, .tidigar documents or calendar events.
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.
Implementer quick start
- Read a project through one of the storage profiles.
- 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.
- Preserve array order. IDs are UUIDs and MUST remain stable when editing an existing record. New IDs SHOULD be cryptographically random UUIDv4 values.
- Write the same storage profile unless the user explicitly converts it.
- For calendars, treat the validated event URL as identity. Never infer ownership from a calendar name, event title, duration or all-day status.
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
- Required:
yesmeans a record without the field is rejected.nomeans a missing field gets the default.derivedmeans the value is computed from other fields and any stored value is replaced. - Rule: a constraint a valid value MUST meet, such as
max N chars,max N entriesorone of …. - Default: a JSON value in backticks is used when the field is missing; a sentence describes a computed default.
- Calendar: where the field lives in a calendar event.
URL m.<key>means inside the compact metadata of the event URL (see Calendar URL). - Used by: where Tidigar shows or uses the field.
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 }
| Collection | Holds | Limit |
|---|---|---|
items | Activities | 10,000 |
sharedViews | Saved views | 1,000 |
milestones | Milestones | 1,000 |
periodIndicators | Period indicators | 1,000 |
Array order is meaningful and preserved in every backend.
Manifest
| Field | Type | Required | Rule | Default | Calendar | Used by |
|---|---|---|---|---|---|---|
projectId | UUID | yes | Project identity; every record's projectId must equal it | — | URL p on every event | Identity in all storage profiles and links |
formatVersion | integer | yes | one of 1 | — | Manifest Notes | Rejects other model versions on load |
name | text | yes | max 100 chars; not blank | — | Manifest Notes; manifest event title | Roadmap title, library card, calendar name at creation |
description | text | yes | max 500 chars; may be empty | — | Manifest Notes | Roadmap dialog |
dimensions | array of dimension | yes | max 20 entries | — | Manifest Notes | Grid axes, filters, colors, grouping, List columns |
year | integer | yes | 1900 through 2200 | — | Manifest Notes | Default timeline range of a view without one |
Dimension
| Field | Type | Required | Rule | Default | Calendar | Used by |
|---|---|---|---|---|---|---|
id | UUID | yes | — | — | Manifest Notes | Item values, view axes, filters and columns |
name | text | yes | max 100 chars; may be empty | — | Manifest Notes | Dimension labels and column headers |
options | array of option | yes | max 100 entries | — | Manifest Notes | Values an item can take in this dimension |
Option
| Field | Type | Required | Rule | Default | Calendar | Used by |
|---|---|---|---|---|---|---|
id | UUID | yes | — | — | Manifest Notes | Item values, filters, colorOverrides |
name | text | yes | max 100 chars; may be empty | — | Manifest Notes | Option labels |
color | color | yes | #rrggbb | — | Manifest Notes | Item color when the view colors by this dimension |
Item (activity)
| Field | Type | Required | Rule | Default | Calendar | Used by |
|---|---|---|---|---|---|---|
id | UUID | yes | — | — | URL i | Identity, dependencies, links |
projectId | UUID | yes | Equals manifest.projectId | — | URL p | Ownership check |
title | text | yes | max 180 chars; may be empty | — | Event title | All views, activity dialog |
description | text | yes | max 12000 chars; may be empty | — | Event notes | Activity dialog, List |
progress | integer | no | one of 0, 25, 50, 75, 100 | 0 | URL m.progress | Progress bar, List |
start | date | yes | Not after end | — | All-day event start | Timeline, List, dependencies |
end | date | yes | Inclusive last day | — | All-day event end, the day after end | Timeline, List, dependencies |
values | object | no | Dimension ID → option ID of that dimension, or "" | {} | URL m.values | Grid placement, filters, colors, List |
dependsOn | array of item IDs | no | No duplicates or cycles; start at least one day after each dependency's end | [] | URL m.dependsOn | Timeline arrows, date cascades |
Milestone
| Field | Type | Required | Rule | Default | Calendar | Used by |
|---|---|---|---|---|---|---|
id | UUID | yes | — | — | URL i | Identity, links |
projectId | UUID | yes | Equals manifest.projectId | — | URL p | Ownership check |
title | text | yes | max 180 chars; not blank | — | Event title | Timeline, milestone dialog |
date | date | yes | At least one day after each dependency's end | — | Timed event 12:00–12:15 local time on that day | Timeline |
dependsOn | array of item IDs | no | Items only; no duplicates | [] | URL m.dependsOn | Timeline arrows, date cascades |
Period indicator
| Field | Type | Required | Rule | Default | Calendar | Used by |
|---|---|---|---|---|---|---|
id | UUID | yes | — | — | URL i | Identity, links |
projectId | UUID | yes | Equals manifest.projectId | — | URL p | Ownership check |
title | text | yes | max 180 chars; not blank | — | Event title | Timeline band label, period dialog |
start | date | yes | Not after end | — | All-day event start | Timeline band |
end | date | yes | Inclusive last day | — | All-day event end, the day after end | Timeline band |
color | color | yes | #rrggbb, stored in lowercase | — | URL m.color | Timeline band color |
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>.
| Field | Type | Required | Rule | Default | Calendar | Used by |
|---|---|---|---|---|---|---|
id | UUID | yes | — | — | URL i | Identity, links, active saved view |
projectId | UUID | yes | Equals manifest.projectId | — | URL p | Ownership check |
name | text | yes | max 100 chars; not blank | — | View Notes | Saved-view menu |
view | enum | no | one of grid, timeline, list | "grid" | View Notes | Main mode |
row | dimension ID | no | Dimension ID or "" | "" | View Notes | Grid rows |
col | dimension ID | no | Dimension ID or "" | "" | View Notes | Grid columns |
color | dimension ID | no | Dimension ID or "" | "" | View Notes | Coloring in all views and exports |
colorOverrides | object | no | Option ID → #rrggbb; at most 100 kept; unknown options dropped | {} | View Notes | Per-view option colors |
group | dimension ID | no | Dimension ID or "" | "" | View Notes | Timeline grouping |
resolution | enum | no | one of year, quarter, month, week | "month" | View Notes | Timeline scale |
textMode | enum | no | one of overflow, below, wrap, clip | "clip" | View Notes | Timeline labels, exports |
filters | object | no | Dimension ID → option ID or ""; unknown entries dropped | {} | View Notes | Filtering in all views |
query | text | no | max 200 chars | "" | View Notes | Saved search text |
hideEmptyRows | boolean | no | — | false | View Notes | Grid |
hideEmptyColumns | boolean | no | — | true | View Notes | Grid |
hideEmptyTimelineGroups | boolean | no | — | true | View Notes | Timeline |
timelineActivityRows | enum | no | one of single, single-label, compact | single, or single-label when an older hideTimelineLabels: false is present | View Notes | Timeline row layout |
hideTimelineLabels | boolean | derived | true exactly when timelineActivityRows is single | Computed | View Notes | Fallback only; see removal candidates |
timelineMilestoneMode | enum | no | one of hide, show, label | "show" | View Notes | Timeline milestones |
timelineWeekDays | enum | no | one of work, all | "work" | View Notes | Timeline day scale |
timelineStart | date | no | Not after timelineEnd | January 1 of manifest.year | View Notes | Timeline range |
timelineEnd | date | no | — | December 31 of manifest.year | View Notes | Timeline range |
listColumns | array of column keys | no | title, start, end or a dimension ID; duplicates and unknown keys dropped | title, start, end, then every dimension ID | View Notes | Visible List columns |
listColumnOrder | array of column keys | no | Same keys as listColumns | Same as listColumns | View Notes | List column order, including hidden ones |
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.
| Profile | Suggested extension | Root discriminator | Contents |
|---|---|---|---|
| Project exchange file | .json | format: "tidigar-project" | Exactly one canonical project in project |
| Multi-project browser backup | .json | format: "tidigar-backup" | Canonical projects in projects |
| Native document | .tidigar | format: "tidigar-document", version: 1 | One project exchange value in roadmap, plus document metadata |
| Calendar | — | Valid v2 HTTPS URL on every owned event | One 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
},
"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.
| Field | Type | Required for writers | Rule |
|---|---|---|---|
format | text | yes | Exactly "tidigar-document" |
version | integer | yes | Exactly 1 |
id | UUID | yes | Document identity; independent of manifest.projectId |
title | text | yes | Library label; the canonical roadmap name remains roadmap.project.manifest.name |
createdAt | text | yes | ISO 8601 timestamp; legacy readers fall back to updatedAt |
updatedAt | text | yes | ISO 8601 timestamp |
roadmap | object | yes | A valid tidigar-project exchange value |
thumbnail | text | no | Standard padded Base64 bytes of an optional PNG preview |
Readers MUST use roadmap as the roadmap authority. title, createdAt, updatedAt and thumbnail are document-library metadata and MUST NOT overwrite canonical project fields. 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 calendar-backed roadmap consists of exactly one manifest event plus one event for every item, shared view, milestone and period indicator. The calendar MAY contain unrelated events; a conforming implementation MUST leave them untouched. Every owned event is non-recurring and carries a validated identity URL.
| Record | Event title | Notes | Date shape |
|---|---|---|---|
| manifest | Tidigar Project Metadata – <project name> | Manifest Notes envelope | All-day 2000-01-01 through exclusive 2000-01-02 |
| item | Item title | Item description as ordinary text | All-day start through the day after inclusive end |
| view | Tidigar View – <view name> | View Notes envelope | All-day 2000-01-01 through exclusive 2000-01-02 |
| milestone | Milestone title | Empty | Timed 12:00–12:15 local calendar time on date |
| period | Period title | Empty | All-day start through the day after inclusive end |
Calendar record type comes only from the URL path. Items and periods MUST be all-day; milestones MUST be timed; manifest and view records MUST use the fixed metadata day. No Tidigar record may recur. An external move within a milestone's calendar day does not change its canonical date; a later Tidigar write restores the fixed 12:00–12:15 window. Calendar dates MUST be calculated as calendar days rather than 86,400-second durations.
Calendar URL
Every Tidigar event stores its identity in EKEvent.url:
https://open.tidigar.com/<path>?v=2&p=<projectId22>[&i=<recordId22>][&m=<metadata>]
The origin has no user, port or fragment. Query order does not matter. The validated path is the only record-type discriminator. Duplicate known keys, an unknown v, a missing required key, a non-canonical UUID or metadata on a record type that does not own metadata are errors. Unknown keys are ignored and never taken as a newer version.
| Key | manifest | item | view | milestone | period | Value |
|---|---|---|---|---|---|---|
| path | /project | /item | /view | /milestone | /period | Record type; the manifest uses /project |
v | yes | yes | yes | yes | yes | Calendar representation version, currently 2; independent of formatVersion |
p | yes | yes | yes | yes | yes | manifest.projectId as a 22-character compact UUID |
i | — | yes | yes | yes | yes | Record ID as a 22-character compact UUID |
m | — | yes | — | yes | yes | Compact metadata, below |
p and i encode the UUID's 16 bytes directly as unpadded base64url. They do not shorten, hash or otherwise replace the canonical UUID; all 128 bits survive.
m is an unpadded base64url representation of deterministic, type-specific binary data. Canonical project JSON and exported JSON retain the descriptive field names in the tables above. The binary transport has this common header:
| Byte | Meaning |
|---|---|
0 | Compact metadata codec version, currently 0x01 |
1 | Record tag: item 0x01, milestone 0x02, period 0x03 |
Integers named count use canonical unsigned LEB128: the shortest encoding is required. uuid is the UUID's 16 bytes in the same order as its 32 hexadecimal digits. A decoder rejects truncation, trailing data, a wrong record tag, non-minimal counts, duplicate item dimension IDs and non-canonical base64url.
| Record | Bytes after the header |
|---|---|
| item | progressIndex:u8, valueCount:varuint, sorted repetitions of dimensionUuid:uuid + optionUuid:uuid, dependencyCount:varuint, repetitions of dependencyUuid:uuid |
| milestone | dependencyCount:varuint, repetitions of dependencyUuid:uuid |
| period | red, green and blue as three bytes |
progressIndex is 0 through 4, representing 0, 25, 50, 75 and 100. Item values sort by dimension UUID bytes; dependency array order remains canonical project order. Title, dates and description are never duplicated in m. Examples:
| Type | m | Descriptive fields represented |
|---|---|---|
| item | AQECAQAAAAAAAEAAgAAAAAAAAAIAAAAAAABAAIAAAAAAAAADAA | progress: 50, one values entry, empty dependsOn |
| milestone | AQIBAAAAAAAAQACAAAAAAAAABg | one dependsOn UUID |
| period | AQOap7U | color: "#9aa7b5" |
| Value | Maximum UTF-8/decoded bytes |
|---|---|
| Complete event URL | 4096 |
Decoded m metadata | 3072 |
| Complete manifest or view Notes | 16384 |
Exceeding a limit fails the save rather than truncating.
Notes envelopes
The manifest and view events carry their data in Notes, inside a versioned envelope (format: "tidigar-calendar-notes", version: 1) between delimiter lines. The complete UTF-8 Notes value has this form, with one line feed (LF) at each shown line break and no final line feed:
Tidigar calendar metadata.
--- TIDIGAR-METADATA-V1 ---
{"format":"tidigar-calendar-notes","version":1,"section":"<manifest-or-view>","payload":{...}}
--- END TIDIGAR-METADATA-V1 ---
JSON object key order is insignificant. Writers SHOULD use compact JSON to leave maximum provider headroom. Complete Notes are limited to 16,384 bytes.
| Section | Payload keys |
|---|---|
manifest | manifest, itemOrder, milestoneOrder, periodOrder, viewOrder |
view | view |
The order lists hold the IDs of every item, milestone, period indicator and view in project order, which is how a missing or unexpected event is detected.
Reading a calendar roadmap
- Find the single manifest event by the fixed metadata day and validate its URL, Notes envelope and manifest payload. A name match is not ownership.
- Scan the complete supported model date domain, 1900 through 2200, for events in that same calendar. Select only events whose validated URL has the same project UUID and a supported path/version.
- Require exactly the IDs and types listed by
itemOrder,milestoneOrder,periodOrderandviewOrder. Duplicate IDs, a second manifest, missing records or unexpected owned records are errors. - Map event fields and packed metadata into descriptive canonical records, restore the arrays in manifest order and validate the whole project.
Scanning only a visible date range is not a complete read. Calendar provider event identifiers are provider-local and MUST NOT enter the canonical project or be used as stable Tidigar identity.
Creating or editing a calendar roadmap
Creation is safe only in a newly created, verified calendar. Editing an existing calendar is safe only after a complete valid Tidigar project has been read from it. A writer MUST:
- Validate the complete updated canonical project.
- Build the full desired event set using the mapping above, v2 URLs and Notes envelopes. Preserve unknown URL query items only when they do not duplicate
v,p,iorm. - Compare with a fresh provider snapshot to detect concurrent changes.
- Create/update/delete only events owned by that validated project. Never delete the calendar or unrelated events.
- Read back provider results before acknowledging the save. Truncation or an incomplete record set is an error, never a partial success.
The three compact m examples above are sufficient to verify byte ordering and encoding independently.
Versions
| Version | Where | Current | Changes when |
|---|---|---|---|
formatVersion | manifest | 1 | The canonical model changes incompatibly |
v | Calendar event URL | 2 | The URL or m layout changes |
version | Calendar Notes envelope | 1 | The Notes envelope or its payload keys change |
version | Native .tidigar document | 1 | The document envelope changes |