Tidigar Markdown specification
Try it in the Markdown import tool: edit the example and create an interactive roadmap in your browser.
Status: public pre-release specification, version 1. It defines a Markdown form of a complete Tidigar roadmap that people can read, write and review, and that converts to the JSON project and back without losing anything.
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. The roadmap model, its field rules and limits are defined in the Tidigar Roadmap Interoperability Specification; this document only defines the Markdown form. A document is valid when the project it describes is valid.
Why Markdown
- A roadmap can live next to the text it belongs to, in a repository, and changes show up as readable diffs.
- People and AI agents can write a roadmap by hand with short keys instead of UUIDs, and names instead of IDs.
- Every field of the model has a place, so converting a JSON project to Markdown and back gives the same project, and converting the Markdown a writer produced back and forth gives the same text.
Example
The roadmap below has two dimensions, three activities with dependencies, a milestone, a period, two saved views and an activity description.
---
format: tidigar-markdown
version: 1
projectId: website-relaunch
year: 2027
schedule: calendar-days
---
# Website relaunch
```text
A small plan from content audit to launch.
```
## Dimensions
| Dimension ID | Dimension | Value ID | Value | Color |
| ------------ | --------- | ----------- | ----------- | ------- |
| team | Team | design | Design | #3575e5 |
| | | development | Development | #16a34a |
| status | Status | planned | Planned | #94a3b8 |
| | | done | Done | #22c55e |
## Activities
| ID | Activity | Start | End | Progress | Team | Status | After |
| ------ | -------------- | ---------- | ---------- | -------- | ----------- | ------- | ------ |
| audit | Content audit | 2027-01-04 | 2027-01-15 | 100 | Design | Done | |
| visual | New design | 2027-01-18 | 2027-02-12 | 50 | Design | Planned | audit |
| build | Build and test | 2027-02-15 | 2027-03-26 | 0 | Development | - | visual |
## Milestones
| ID | Milestone | Date | After |
| ------ | --------- | ---------- | ----- |
| launch | Launch | 2027-03-29 | build |
## Periods
| ID | Period | Start | End | Color | Vacation |
| ------ | ------ | ---------- | ---------- | ------- | -------- |
| easter | Easter | 2027-03-25 | 2027-03-29 | #f5c38b | yes |
## Views
### board
| Field | Value |
| ----- | ------------- |
| name | Board by team |
| col | Status |
| row | Team |
### plan
| Field | Value |
| ------------------------ | ------------ |
| name | Plan by team |
| view | timeline |
| group | Team |
| color | Team |
| timelineDependencyArrows | every |
| timelineStart | 2027-01-01 |
| timelineEnd | 2027-04-30 |
## Descriptions
### visual
```text
Two concepts, one review with the client.
```
It describes this project:
{
"manifest": {
"projectId": "2c97e3f7-49a9-5e59-b42f-abb427f7048f",
"formatVersion": 1,
"name": "Website relaunch",
"description": "A small plan from content audit to launch.",
"dimensions": [
{
"id": "c1335d68-c807-57b2-ab1d-f6049c04da2e",
"name": "Team",
"options": [
{
"id": "30e42f43-d994-5baf-8dd1-926427a4aa43",
"name": "Design",
"color": "#3575e5"
},
{
"id": "a7b044b4-6b44-584f-b7f6-e83a977840af",
"name": "Development",
"color": "#16a34a"
}
]
},
{
"id": "fc015fe9-21a9-5e13-99c9-47b57b41e473",
"name": "Status",
"options": [
{
"id": "7e95cfbc-93f3-5db8-a04e-e004103e08a5",
"name": "Planned",
"color": "#94a3b8"
},
{
"id": "48757e42-b696-520f-bae6-77a57b7838c4",
"name": "Done",
"color": "#22c55e"
}
]
}
],
"year": 2027,
"schedule": "calendar-days"
},
"items": [
{
"id": "9e13d792-910c-52ed-b1e2-83452b9ba6c8",
"projectId": "2c97e3f7-49a9-5e59-b42f-abb427f7048f",
"title": "Content audit",
"description": "",
"progress": 100,
"start": "2027-01-04",
"end": "2027-01-15",
"values": {
"c1335d68-c807-57b2-ab1d-f6049c04da2e": "30e42f43-d994-5baf-8dd1-926427a4aa43",
"fc015fe9-21a9-5e13-99c9-47b57b41e473": "48757e42-b696-520f-bae6-77a57b7838c4"
},
"dependsOn": []
},
{
"id": "01d963a6-4d45-51bb-87f5-8f7014560b23",
"projectId": "2c97e3f7-49a9-5e59-b42f-abb427f7048f",
"title": "New design",
"description": "Two concepts, one review with the client.",
"progress": 50,
"start": "2027-01-18",
"end": "2027-02-12",
"values": {
"c1335d68-c807-57b2-ab1d-f6049c04da2e": "30e42f43-d994-5baf-8dd1-926427a4aa43",
"fc015fe9-21a9-5e13-99c9-47b57b41e473": "7e95cfbc-93f3-5db8-a04e-e004103e08a5"
},
"dependsOn": ["9e13d792-910c-52ed-b1e2-83452b9ba6c8"]
},
{
"id": "877d07dd-4ac6-53d7-a4fa-351b630cf19d",
"projectId": "2c97e3f7-49a9-5e59-b42f-abb427f7048f",
"title": "Build and test",
"description": "",
"progress": 0,
"start": "2027-02-15",
"end": "2027-03-26",
"values": {
"c1335d68-c807-57b2-ab1d-f6049c04da2e": "a7b044b4-6b44-584f-b7f6-e83a977840af",
"fc015fe9-21a9-5e13-99c9-47b57b41e473": ""
},
"dependsOn": ["01d963a6-4d45-51bb-87f5-8f7014560b23"]
}
],
"sharedViews": [
{
"id": "9b5cc976-30a0-5573-a829-2a1619bcb91b",
"projectId": "2c97e3f7-49a9-5e59-b42f-abb427f7048f",
"name": "Board by team",
"view": "grid",
"row": "c1335d68-c807-57b2-ab1d-f6049c04da2e",
"col": "fc015fe9-21a9-5e13-99c9-47b57b41e473",
"color": "",
"colorOverrides": {},
"group": "",
"resolution": "month",
"timelineScaleRows": 3,
"timelineShowWeeks": true,
"timelineShowQuarters": true,
"timelineShowToday": false,
"timelineShowPeriodNames": false,
"textMode": "beside",
"filters": {},
"query": "",
"hideEmptyRows": true,
"hideEmptyColumns": true,
"hideEmptyTimelineGroups": true,
"timelineActivityRows": "single",
"hideTimelineLabels": true,
"timelineMilestoneMode": "label",
"timelineWeekDays": "work",
"timelineDependencyArrows": "grouped",
"flowDirection": "auto",
"timelineStart": "2027-01-01",
"timelineEnd": "2027-12-31",
"listColumns": [
"title",
"start",
"end",
"c1335d68-c807-57b2-ab1d-f6049c04da2e",
"fc015fe9-21a9-5e13-99c9-47b57b41e473"
],
"listColumnOrder": [
"title",
"start",
"end",
"c1335d68-c807-57b2-ab1d-f6049c04da2e",
"fc015fe9-21a9-5e13-99c9-47b57b41e473"
]
},
{
"id": "a49042f2-ea81-57c9-9b7d-6ea1abeb2a39",
"projectId": "2c97e3f7-49a9-5e59-b42f-abb427f7048f",
"name": "Plan by team",
"view": "timeline",
"row": "",
"col": "",
"color": "c1335d68-c807-57b2-ab1d-f6049c04da2e",
"colorOverrides": {},
"group": "c1335d68-c807-57b2-ab1d-f6049c04da2e",
"resolution": "month",
"timelineScaleRows": 3,
"timelineShowWeeks": true,
"timelineShowQuarters": true,
"timelineShowToday": false,
"timelineShowPeriodNames": false,
"textMode": "beside",
"filters": {},
"query": "",
"hideEmptyRows": true,
"hideEmptyColumns": true,
"hideEmptyTimelineGroups": true,
"timelineActivityRows": "single",
"hideTimelineLabels": true,
"timelineMilestoneMode": "label",
"timelineWeekDays": "work",
"timelineDependencyArrows": "every",
"flowDirection": "auto",
"timelineStart": "2027-01-01",
"timelineEnd": "2027-04-30",
"listColumns": [
"title",
"start",
"end",
"c1335d68-c807-57b2-ab1d-f6049c04da2e",
"fc015fe9-21a9-5e13-99c9-47b57b41e473"
],
"listColumnOrder": [
"title",
"start",
"end",
"c1335d68-c807-57b2-ab1d-f6049c04da2e",
"fc015fe9-21a9-5e13-99c9-47b57b41e473"
]
}
],
"milestones": [
{
"id": "16eecb5f-205d-5e32-83c4-04410e23eefa",
"projectId": "2c97e3f7-49a9-5e59-b42f-abb427f7048f",
"title": "Launch",
"date": "2027-03-29",
"dependsOn": ["877d07dd-4ac6-53d7-a4fa-351b630cf19d"]
}
],
"periodIndicators": [
{
"id": "49c26a40-ddf7-5d35-80fb-f66a18f338f8",
"projectId": "2c97e3f7-49a9-5e59-b42f-abb427f7048f",
"title": "Easter",
"start": "2027-03-25",
"end": "2027-03-29",
"color": "#f5c38b",
"vacation": true
}
]
}
Document structure
A document is UTF-8 text with lines separated by LF; a CR before an LF is ignored outside text blocks. In this order it holds:
- The front matter.
- The name as a level-one heading,
# Name. - Optionally the project description as a text block.
- Sections, each a level-two heading followed by its content:
## Dimensions,## Activities,## Milestones,## Periods,## Viewsand## Descriptions. A writer emits them in this order and leaves out empty ones; a reader accepts any order. A missing section is an empty collection. A section MUST NOT appear twice.
Blank lines and single-line HTML comments (<!-- … -->) may appear between these parts. Any other content is an error, so a mistake is not silently dropped.
Front matter
The document starts with a line ---, then one key: value per line, then ---.
| Key | Required | Value |
|---|---|---|
format | yes | tidigar-markdown |
version | yes | 1. Readers MUST reject other versions. |
projectId | yes | The project ID, or a key (see IDs and keys) |
year | yes | The project year, four digits |
shareLinkCreatedAt | no | UTC ISO 8601 YYYY-MM-DDTHH:mm:ssZ; omitted when unknown |
schedule | no | calendar-days or work-days; calendar-days when left out |
refPrefix | no | The prefix of reference keys, such as LPP; none when left out |
The model format version is 1, implied by Markdown version 1. Every record of the document belongs to the project, so no record repeats the project ID.
Text
Names, titles and other short text are written in table cells and headings with these escapes, so any text survives:
\\is a backslash.\|is a vertical bar.\nis a line feed,\ra carriage return and\ta tab.\sis a space; a writer uses it for spaces at the start or end.\#is a number sign; a writer uses it for one that ends the name.\u{hex}is any character by its code point; a writer uses it for other control characters.
Any other backslash sequence is an error. A reader trims spaces and tabs at both ends of every cell and list entry, so a writer escapes spaces there with \s and tabs with \t.
A text block holds a description exactly as it is, over any number of lines. It is a fenced code block with the info string text: a fence of at least three backticks, longer than any run of backticks in the text, then the text, then the same fence on its own line. Both fence lines may end with CRLF; the carriage return on a fence line is ignored. Carriage returns inside the text are preserved.
IDs and keys
Every record has an ID column or heading. It holds either a UUID, used as it is, or a key of 1 to 64 letters, digits, _ and -, starting with a letter or digit. A key becomes a name-based version 5 UUID (RFC 9562):
- The project key's namespace is
e9291c6b-93e5-4d37-825b-64a84857a6b6. - A record key's namespace is the project ID.
- The name is the key in UTF-8.
So the same document always gives the same IDs. In the example, the project key website-relaunch becomes 2c97e3f7-49a9-5e59-b42f-abb427f7048f and the activity key audit becomes 9e13d792-910c-52ed-b1e2-83452b9ba6c8. IDs, keys included, MUST be unique across the whole document. A writer emits UUIDs.
References
Cells that point at a dimension or a value hold a reference: the record's ID or key, or its name. A reader first matches IDs and keys, then names; a name MUST belong to exactly one record among the candidates.
A writer uses the name only when nothing else could be meant: it is not empty, is unique, has no spaces at either end, contains none of \ | , = . or control characters, is not a UUID, and is not, in any letter case, -, title, duration, start, end, dependsOn or a fixed column of the Activities table. Otherwise it writes the ID.
In cells that hold a value, an empty cell and - differ: an empty cell leaves the field out, while - sets it to no value ("" in JSON).
Dimensions
One table, with one row per value. A row with a Dimension ID starts a dimension; the rows after it with an empty Dimension ID and Dimension add values to it. A dimension without values is a row with only Dimension ID and Dimension.
| Column | Holds |
|---|---|
Dimension ID | The dimension's ID or key |
Dimension | Its name, as text; may be empty |
Value ID | The value's ID or key |
Value | The value's name, as text; may be empty |
Color | The value's color, #rrggbb |
Activities
One row per activity, in order.
| Column | Required | Holds |
|---|---|---|
ID | yes | The activity's ID or key |
Ref | no | The activity's reference key, as text; none when empty or left out |
Activity | yes | The title, as text |
Start | yes | The first day, YYYY-MM-DD |
End | yes | The last day |
Progress | no | 0, 25, 50, 75 or 100; 0 when empty or left out |
After | no | The activities and fixed milestones it depends on: IDs or keys, separated by commas; an activity may be followed by a share such as (50%) |
Every other column is a dimension: its heading is a reference to the dimension, and its cells are references to values of that dimension, - or empty. A writer emits a column for every dimension, between Progress and After, and emits Ref after ID only when an activity has a reference key; the Milestones table does the same. Dependencies may name any activity of the table and any fixed milestone of the Milestones table.
A dependency on an activity may be followed by (0%), (25%), (50%) or (75%): how much of that activity’s planned duration has passed when this one may start, as in a (50%). Without a share, or with (100%), this activity starts after the other one ends. This is independent of reported Progress. Scheduling counts the chosen share of inclusive days (work days in a work-day roadmap), rounded up; 0% permits the same start. The latest threshold applies to multiple dependencies. Stored dates must not precede the predecessor’s start for a partial share. Fixed milestones cannot have a partial share.
Milestones
| Column | Required | Holds |
|---|---|---|
ID | yes | The milestone's ID or key |
Ref | no | The milestone's reference key, as text; none when empty or left out |
Milestone | yes | The title, as text |
Date | yes | The day, YYYY-MM-DD |
After | no | The activities it depends on, separated by commas |
Fixed | no | yes or no; no when empty or left out |
Source | no | Another plan's milestone as plan ID/milestone ID; none when empty or left out |
A fixed milestone is an event on a set date that activities wait for; its After is empty. A milestone with a Source is an incoming milestone: a copy of the named plan's milestone, and always fixed. A writer emits Source last, and only when a milestone has one.
Periods
| Column | Required | Holds |
|---|---|---|
ID | yes | The period's ID or key |
Period | yes | The title, as text |
Start | yes | The first day |
End | yes | The last day |
Color | yes | #rrggbb |
Vacation | no | yes or no; yes when empty or left out |
Views
Each saved view is a level-three heading with its ID or key, followed by a table with the columns Field and Value, one row per field. name is required; every other field takes the model's default when left out, and a writer leaves out fields that equal their default.
| Field | Value |
|---|---|
name, query | Text |
row, col, group | A dimension reference, assignee for the assignee axis, or - for none |
color, flowGroup, timelineSummarizeBy | A dimension reference, or - for none |
view, resolution, textMode, timelineActivityRows, timelineMilestoneMode, timelineWeekDays, timelineDependencyArrows, flowDirection, flowArrows, flowShapes | The value as in JSON |
presentationStyle | standard, minimal, airy, contrast or terminal |
timelineScaleRows | 1, 2 or 3 |
timelineShowWeeks, timelineShowQuarters, timelineWeekNumbers, timelineShowToday, timelineShowPeriodNames, hideEmptyRows, hideEmptyColumns, hideEmptyTimelineGroups, hideUnconnectedActivities, flowGroupMilestones, hideEmptyFlowGroups, flowHideDates, showAssignee, showRefs, timelineMilestonesBesideWork | yes or no |
timelineStart, timelineEnd | YYYY-MM-DD |
filters | dimension=value entries separated by commas; the value may be - |
colorOverrides | dimension.value=#rrggbb entries separated by commas |
listColumns, listColumnOrder | title, duration, start, end, dependsOn and dimension references, separated by commas; - for none |
The derived field hideTimelineLabels is not written. Views never record which timeline summaries are collapsed; a reader skips a timelineCollapsedSummaries row that an older document may still hold. A dimension whose name is assignee is referenced by its ID. The assignee axis and showAssignee are meaningful only in a plan stored in Tidigar Business; the document never holds assignments or people.
Descriptions
Each activity with a description is a level-three heading with the activity's ID or key, followed by a text block holding the description. An activity without one is left out.
Writing
A writer SHOULD produce exactly what this specification describes, in the order shown, so that its output converts back to the same text: tables padded to their widest cell, UUIDs as IDs, names as references where allowed, default view fields left out, and a final line feed. Alignment and spacing have no meaning to a reader.
Tools that reformat Markdown can change text. A document converted to JSON and back, rather than reformatted, keeps every character.