# Tidigar Markdown specification Try it in the [Markdown import tool](https://tidigar.com/tools/markdown): 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](https://creativecommons.org/licenses/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](https://tidigar.com/agents/docs/file-format.md); 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. ````markdown --- 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: ```json { "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: 1. The front matter. 2. The name as a level-one heading, `# Name`. 3. Optionally the project description as a text block. 4. Sections, each a level-two heading followed by its content: `## Dimensions`, `## Activities`, `## Milestones`, `## Periods`, `## Views` and `## 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](#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. - `\n` is a line feed, `\r` a carriage return and `\t` a tab. - `\s` is 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](https://www.rfc-editor.org/rfc/rfc9562#section-5.5)): - 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.