The .shira file format
.shiraUTF-8 JSON (RFC 8259)application/jsonsingle JSON object1What a .shira file is
A .shira file is one JSON object that fully describes a roadmap:
its timeline window, its display settings, its rows (categories), its activity bars
and its milestone markers. There is no binary container, no compression and no
wrapper envelope — the file's entire content is the object described below.
The same object is the unit of exchange everywhere in Shira. Save to file writes
it, pretty-printed, to <roadmap title>.shira, and Open file accepts
any file whose content parses into this shape. Everything a roadmap needs in order to be
drawn is in the file; nothing is inferred from anywhere else.
It is strict JSON: no comments, no trailing commas, no NaN or
Infinity, no single-quoted strings. Property order is irrelevant.
Properties that are not part of the format are ignored when the file is read.
The format stores plan data plus presentation intent — never computed geometry. Pixel positions, row heights and stacking are worked out when the roadmap is drawn, so the same file renders correctly at any zoom, in any timescale, and in every export.
The shortest valid file
Every required field, nothing else:
{
"title": "Q3 Launch Plan",
"description": "",
"canvasStartDate": "2026-06-01",
"canvasEndDate": "2026-12-31",
"activities": [
{
"id": "a1b2c3d4",
"title": "Beta programme",
"startDate": "2026-07-01",
"endDate": "2026-09-30",
"line": 1
}
]
} That file opens, renders one uncategorized bar on a month timescale, and is a legitimate roadmap. Everything else on this page is optional refinement.
The root object
| Field | Type | Presence | Meaning & default |
|---|---|---|---|
title | string | required | Roadmap name. Shown in the canvas header and used as the export filename. |
description | string | required | Reserved for future use. Write "". |
canvasStartDate | string · YYYY-MM-DD | required | First day of the visible timeline window. |
canvasEndDate | string · YYYY-MM-DD | required | Last day of the window, inclusive. Must be on or after the start, and the window must span at least 28 days. |
activities | Activity[] | required | The activity bars. May be an empty array. |
milestones | Milestone[] | optional | Diamond markers. Default []. |
categoriesConfig | {title, color?}[] | optional | Row order. Default []. |
timescale | "year" | "half" | "quarter" | "month" | "week" | "day" | optional | Granularity of the column header and grid lines. Default "month". May be adjusted when the file is opened. |
displayDensity | "compact" | "condensed" | "comfortable" | optional | Row height preset. Default "compact". |
textTruncation | "clip" | "overflow" | optional | What a label does when it is wider than its bar: "clip" cuts it at the bar edge, "overflow" lets it spill past. Default "clip". |
categoryView | "show" | "hide" | optional | "hide" collapses the left-hand category label column, giving the grid its full width. Default "show". |
todayMilestone | boolean | optional | Draw the vertical "today" line across the canvas. Default true. Only a real boolean false disables it. |
The activity object
An activity is one horizontal bar: a titled span of time on a row.
| Field | Type | Presence | Meaning |
|---|---|---|---|
id | string | required | Stable identity, unique across the whole file. Any unique non-empty string is valid. |
title | string | required | Label drawn on the bar. |
startDate | string · YYYY-MM-DD | required | First day of the activity. |
endDate | string · YYYY-MM-DD | required | Last day, inclusive — the bar is drawn through the end of that day. |
line | number | required | 1-based row index within its category. |
category | string | optional | Name of the row group. Omit the key entirely to place the activity in the uncategorized group. |
color | string · #RRGGBB | optional | Bar fill. Default #93C5FD. |
A single-day activity
Set startDate equal to endDate and the bar occupies exactly that day.
For a point in time that should read as an event rather than a span, prefer a milestone.
Activities outside the timeline window
An activity whose dates fall partly or wholly outside the canvas window is still valid and is still loaded. It renders clipped at the canvas edge, and an activity entirely outside the window is simply not visible. Narrowing the window is a view operation — it never destroys plan data.
The milestone object
A milestone is a dated diamond marker with a label — a moment, not a span.
| Field | Type | Presence | Meaning |
|---|---|---|---|
id | string | required | Unique identity, same rules as an activity id. |
title | string | required | Label drawn beside the marker. Must not be empty or whitespace-only — an empty title rejects the whole file. |
date | string · YYYY-MM-DD | required | The day the marker sits on. Must parse as a date, or the file is rejected. |
category | string | optional | Which category's milestone row it belongs to. Omit for the uncategorized group. |
Every category gets one reserved milestone row above its activity lines. Milestones are not positioned by hand: when several fall close enough together that their labels would collide, they are packed into stacked lanes automatically. A file therefore never needs to encode vertical milestone placement, and there is no field for it.
A milestone may reference a category that no activity uses. That category still gets a row — a milestone-only row is a normal, supported shape.
Categories, rows and lines
Which rows exist, and in what order
The set of rows comes from the items, not from categoriesConfig. Every distinct
category value appearing on an activity or a milestone becomes a row.
categoriesConfig then only decides order:
- Entries in
categoriesConfig, in array order — but only those actually used by at least one activity or milestone. A configured category with no items is silently dropped. - Then any remaining used categories that
categoriesConfigdid not mention, in the order they were first encountered.
So: to control row order, list every category in categoriesConfig in the order you want,
and make sure each one carries at least one item. To leave ordering to the data, omit
categoriesConfig entirely.
The color property on a categoriesConfig entry is accepted but is not
applied to rendering today. Treat it as reserved.
An item with no category key belongs to the uncategorized group, which renders
as a row with an empty label. Do not write "category": "" to express this —
an empty string is a distinct, non-canonical value, and items written that way can behave
inconsistently once edited. Omit the key.
line: stacking within a category
line is a 1-based integer that is scoped per category — line 1 in "Sales" and
line 1 in "Marketing" are different rows. Within one category:
- Two activities on the same line render side by side on one row. That is the point of the field: put sequential, non-overlapping work on one line to keep the roadmap compact.
- Two activities on the same line whose date ranges overlap will be drawn on top of each other. Nothing repacks them when the file is opened — assigning lines so that same-line activities never overlap is the generator's job.
- Line numbers should be contiguous from 1. A gap (1, 2, 4) renders as an empty row where 3 would be.
- Never emit fractional lines such as
2.5.
Assigning lines: the greedy rule
Within each category, walk the activities and place each on the lowest-numbered line where it
collides with nothing already placed. Two items collide when
a.startDate ≤ b.endDate and b.startDate ≤ a.endDate. This is optimal in
row count and keeps the roadmap as short as it can be.
Dates and rules
Date handling
- Every date is a calendar day in
YYYY-MM-DDform, interpreted as UTC midnight. No other format is supported — no timestamps, no offsets, noDD/MM/YYYY. - End dates are inclusive, both for the canvas window and for activities.
- Because dates are UTC-anchored, a file renders identically for viewers in every timezone. Emit each date as a literal string: building it from a local-midnight date object and serialising with
toISOString()shifts every date one day earlier for anyone west of UTC.
Rules that reject a file
These are checked when a file is opened. If any fails, the roadmap is refused as a whole — it is not partially loaded.
| Rule | Detail |
|---|---|
| Well-formed JSON | A parse error is reported as an unreadable file. |
canvasEndDate ≥ canvasStartDate | An inverted window is rejected. |
| Window spans ≥ 28 days | Measured inclusively (end − start + 1 day). Shorter windows are rejected. |
Every milestone has a non-empty title | Whitespace-only counts as empty. |
Every milestone date parses | An unparseable date rejects the file. |
Activity dates are not checked: a reversed activity (endDate before
startDate) loads and renders as a zero-or-negative-width bar. Duplicate ids,
overlapping same-line activities and non-contiguous line numbers are likewise accepted and
simply render badly. Validate these in your generator — the checklist below covers them.
Timescale adjustment
The requested timescale is checked against the window length, because fine
granularities are unreadable across long spans. Two adjustments happen silently:
| Requested | Condition | Result |
|---|---|---|
"day" | window longer than 45 days | becomes "month" |
"week" | window longer than 100 days | becomes "month" |
| any other | — | used as written |
So a multi-year roadmap should request "month", "quarter",
"half" or "year"; asking for "day" will not be honoured.
Colour
An activity's color is a hex string in #RRGGBB form. Omitting it yields
the default #93C5FD. Shira ships a curated palette of fourteen mid-light swatches
held in one lightness/chroma band, so bars read as a designed set rather than a random mix.
Generators should draw from it, typically one hue per category.
Bar labels are drawn in a dark ink, so very dark fills reduce legibility. Staying inside the palette avoids the problem entirely.
A complete example
Three ordered categories, activities stacked on two lines within a category, one uncategorized activity, and milestones — including one attached to a category that carries no bars of its own.
{
"title": "New Business Development Plan 2026-2027",
"description": "",
"canvasStartDate": "2026-06-01",
"canvasEndDate": "2027-12-31",
"timescale": "month",
"displayDensity": "compact",
"textTruncation": "clip",
"categoryView": "show",
"todayMilestone": true,
"activities": [
{
"id": "bd01mkta",
"title": "Market & Competitor Analysis",
"startDate": "2026-06-01",
"endDate": "2026-08-31",
"category": "Research & Strategy",
"color": "#93C5FD",
"line": 1
},
{
"id": "bd03case",
"title": "Business Case & Financial Model",
"startDate": "2026-09-01",
"endDate": "2026-11-30",
"category": "Research & Strategy",
"color": "#A5B4FC",
"line": 1
},
{
"id": "bd02disc",
"title": "Customer Discovery Interviews",
"startDate": "2026-07-01",
"endDate": "2026-09-30",
"category": "Research & Strategy",
"color": "#93C5FD",
"line": 2
},
{
"id": "bd11play",
"title": "Sales Playbook & Pricing",
"startDate": "2027-01-01",
"endDate": "2027-03-31",
"category": "Sales & Revenue",
"color": "#FDBA74",
"line": 1
},
{
"id": "bd99misc",
"title": "Board reporting cadence",
"startDate": "2026-06-01",
"endDate": "2027-12-31",
"color": "#CBD5E1",
"line": 1
}
],
"milestones": [
{
"id": "ms01gono",
"title": "Go / No-Go Decision",
"date": "2026-11-30",
"category": "Research & Strategy"
},
{
"id": "ms03glau",
"title": "General Availability Launch",
"date": "2027-05-01",
"category": "Offering & Pilots"
}
],
"categoriesConfig": [
{ "title": "Research & Strategy" },
{ "title": "Offering & Pilots" },
{ "title": "Sales & Revenue" }
]
} JSON Schema
Draft 2020-12. Rules that cannot be expressed in the shape of the document — the 28-day minimum window, the timescale adjustment, per-category line packing — are listed above and in the checklist below.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://shiraroadmap.com/schema/shira-1.json",
"title": "Shira roadmap (.shira)",
"type": "object",
"required": ["title", "description", "canvasStartDate", "canvasEndDate", "activities"],
"properties": {
"title": { "type": "string" },
"description": { "type": "string" },
"canvasStartDate": { "$ref": "#/$defs/calendarDate" },
"canvasEndDate": { "$ref": "#/$defs/calendarDate" },
"timescale": {
"enum": ["year", "half", "quarter", "month", "week", "day"],
"default": "month"
},
"displayDensity": { "enum": ["compact", "condensed", "comfortable"], "default": "compact" },
"textTruncation": { "enum": ["clip", "overflow"], "default": "clip" },
"categoryView": { "enum": ["show", "hide"], "default": "show" },
"todayMilestone": { "type": "boolean", "default": true },
"activities": { "type": "array", "items": { "$ref": "#/$defs/activity" } },
"milestones": { "type": "array", "items": { "$ref": "#/$defs/milestone" }, "default": [] },
"categoriesConfig": {
"type": "array",
"items": {
"type": "object",
"required": ["title"],
"properties": {
"title": { "type": "string" },
"color": { "$ref": "#/$defs/hexColor" }
}
},
"default": []
}
},
"$defs": {
"calendarDate": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
"hexColor": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" },
"activity": {
"type": "object",
"required": ["id", "title", "startDate", "endDate", "line"],
"properties": {
"id": { "type": "string", "minLength": 1 },
"title": { "type": "string" },
"startDate": { "$ref": "#/$defs/calendarDate" },
"endDate": { "$ref": "#/$defs/calendarDate" },
"line": { "type": "integer", "minimum": 1 },
"category": { "type": "string", "minLength": 1 },
"color": { "$ref": "#/$defs/hexColor" }
}
},
"milestone": {
"type": "object",
"required": ["id", "title", "date"],
"properties": {
"id": { "type": "string", "minLength": 1 },
"title": { "type": "string", "minLength": 1 },
"date": { "$ref": "#/$defs/calendarDate" },
"category": { "type": "string", "minLength": 1 }
}
}
}
}
The schema forbids "category": "" on purpose. An empty string is not how the
format spells "uncategorized" — omitting the key is.
Generator checklist
For code or an AI agent emitting a .shira file. Everything here is either enforced
when the file is opened, or silently produces a broken-looking roadmap.
Must hold, or the file is rejected
- The document is strict, parseable JSON — no comments, no trailing commas.
title,description,canvasStartDate,canvasEndDateandactivitiesare all present.canvasEndDateis on or aftercanvasStartDate, and the window is at least 28 days long.- Every milestone has a non-empty
titleand a parseabledate.
Must hold, or the roadmap renders wrong
- Every
idis unique across all activities and milestones. - Every activity's
endDateis on or after itsstartDate. - All dates are
YYYY-MM-DDstrings emitted literally — never derived from a local-timezone date object. - Within each category,
linevalues run contiguously from 1, and no two activities sharing a line have overlapping date ranges. - No fractional
linevalues. - Uncategorized items omit the
categorykey rather than setting it to"". - Every
categoriesConfigentry is used by at least one item, otherwise it is dropped and the ordering you intended is lost. colorvalues are#RRGGBB, ideally from the palette above.
Worth doing
- Choose a
timescalethe window can actually carry:"month"or coarser beyond about three months. - Give the canvas window a little air on each side of the earliest and latest item.
- Use one colour per category so the roadmap reads as grouped work.
- List categories in
categoriesConfigin a deliberate order — most strategic at the top usually reads best. - Prefer a milestone over a one-day activity for decision points, launches and deadlines.
This page describes the .shira format as implemented by the Shira roadmap
application. Fields marked reserved are part of the stored document but carry no rendering
behaviour today; they may gain meaning in a later revision, so a generator should write the
documented placeholder values rather than inventing its own.
