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

  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, 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

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 views1,000
milestonesMilestones1,000
periodIndicatorsPeriod indicators1,000

Array order is meaningful and preserved in every backend.

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—Manifest NotesGrid axes, filters, colors, grouping, List columns
yearintegeryes1900 through 2200—Manifest NotesDefault timeline range of a view without one

Dimension

FieldTypeRequiredRuleDefaultCalendarUsed by
idUUIDyes——Manifest NotesItem values, view axes, filters and columns
nametextyesmax 100 chars; may be empty—Manifest NotesDimension labels and column headers
optionsarray of optionyesmax 100 entries—Manifest NotesValues an item can take in this dimension

Option

FieldTypeRequiredRuleDefaultCalendarUsed by
idUUIDyes——Manifest NotesItem values, filters, colorOverrides
nametextyesmax 100 chars; may be empty—Manifest NotesOption labels
colorcoloryes#rrggbb—Manifest 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.valuesGrid placement, filters, colors, List
dependsOnarray of item IDsnoNo duplicates or cycles; start at least one day after each dependency's end[]URL m.dependsOnTimeline arrows, date cascades

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[]URL m.dependsOnTimeline arrows, date cascades

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

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"grid"View NotesMain mode
rowdimension IDnoDimension ID or """"View NotesGrid rows
coldimension IDnoDimension ID or """"View NotesGrid columns
colordimension IDnoDimension ID or """"View NotesColoring in all views and exports
colorOverridesobjectnoOption ID → #rrggbb; at most 100 kept; unknown options dropped{}View NotesPer-view option colors
groupdimension IDnoDimension ID or """"View NotesTimeline grouping
resolutionenumnoone of year, quarter, month, week"month"View NotesTimeline scale
textModeenumnoone of overflow, below, wrap, clip"clip"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—falseView NotesGrid
hideEmptyColumnsbooleanno—trueView NotesGrid
hideEmptyTimelineGroupsbooleanno—trueView NotesTimeline
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, show, label"show"View NotesTimeline milestones
timelineWeekDaysenumnoone of work, all"work"View NotesTimeline day scale
timelineStartdatenoNot after timelineEndJanuary 1 of manifest.yearView NotesTimeline range
timelineEnddateno—December 31 of manifest.yearView NotesTimeline range
listColumnsarray of column keysnotitle, start, end 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

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 v2 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
    },
    "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

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.

RecordEvent titleNotesDate shape
manifestTidigar Project Metadata – <project name>Manifest Notes envelopeAll-day 2000-01-01 through exclusive 2000-01-02
itemItem titleItem description as ordinary textAll-day start through the day after inclusive end
viewTidigar View – <view name>View Notes envelopeAll-day 2000-01-01 through exclusive 2000-01-02
milestoneMilestone titleEmptyTimed 12:00–12:15 local calendar time on date
periodPeriod titleEmptyAll-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.

KeymanifestitemviewmilestoneperiodValue
path/project/item/view/milestone/periodRecord type; the manifest uses /project
vyesyesyesyesyesCalendar representation version, currently 2; independent of formatVersion
pyesyesyesyesyesmanifest.projectId as a 22-character compact UUID
i—yesyesyesyesRecord ID as a 22-character compact UUID
m—yes—yesyesCompact 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:

ByteMeaning
0Compact metadata codec version, currently 0x01
1Record 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.

RecordBytes after the header
itemprogressIndex:u8, valueCount:varuint, sorted repetitions of dimensionUuid:uuid + optionUuid:uuid, dependencyCount:varuint, repetitions of dependencyUuid:uuid
milestonedependencyCount:varuint, repetitions of dependencyUuid:uuid
periodred, 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:

TypemDescriptive fields represented
itemAQECAQAAAAAAAEAAgAAAAAAAAAIAAAAAAABAAIAAAAAAAAADAAprogress: 50, one values entry, empty dependsOn
milestoneAQIBAAAAAAAAQACAAAAAAAAABgone dependsOn UUID
periodAQOap7Ucolor: "#9aa7b5"
ValueMaximum UTF-8/decoded bytes
Complete event URL4096
Decoded m metadata3072
Complete manifest or view Notes16384

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.

SectionPayload keys
manifestmanifest, itemOrder, milestoneOrder, periodOrder, viewOrder
viewview

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

  1. 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.
  2. 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.
  3. Require exactly the IDs and types listed by itemOrder, milestoneOrder, periodOrder and viewOrder. Duplicate IDs, a second manifest, missing records or unexpected owned records are errors.
  4. 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:

  1. Validate the complete updated canonical project.
  2. 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, i or m.
  3. Compare with a fresh provider snapshot to detect concurrent changes.
  4. Create/update/delete only events owned by that validated project. Never delete the calendar or unrelated events.
  5. 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

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