Files
astrolabe/docs/spec/08-import-export.md
T

118 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 08 · Import & Export
Astrolabe lets a user back up or transfer their entire workspace as a single JSON file, and bring data back in by importing such a file. These two whole-workspace actions are triggered from header controls labelled **Import** and **Export**. Import always merges with existing data; it never replaces what is already stored.
Separately, a single chart can be exported on its own — its spec or its rendered image — from the Live Preview pane (see _Per-chart export_ below). That is distinct from the workspace Export: it gets _one_ chart out, not a backup of the library.
## Export
Export produces one downloadable JSON file containing every snippet (see _Snippet Library_), every dataset (see _Datasets_), and every custom chart theme (see _Live Preview → Chart theme_), wrapped in an envelope carrying format metadata.
- **Trigger**: the **Export** header control runs the export immediately (no intermediate dialog).
- **Contents**: all snippets, datasets, and custom chart themes currently stored, plus envelope metadata.
- **Empty workspace**: if there are no snippets, the user is informed ("No snippets to export") and no file is downloaded — even if datasets or themes exist.
- **Filename**: `astrolabe-project-YYYY-MM-DD.json`, where the date is today's date (export day).
- **Feedback**: on success a toast reports the counts, e.g. "Exported 4 snippets, 2 datasets and 1 theme" (the dataset and theme clauses are omitted when their counts are zero; singular/plural wording adapts to the counts).
### Export envelope shape
The downloaded file is a single JSON object: an envelope with a format `version`, an export timestamp, an exporter tag, and the data arrays.
```json
{
"version": "1.0",
"exportedAt": "2026-06-03T12:00:00.000Z",
"exportedBy": "Astrolabe",
"snippets": [
/* full snippet objects (see Data Model) */
],
"datasets": [
/* full dataset objects (see Data Model) */
],
"themes": [
/* full custom chart theme objects (see Data Model) */
]
}
```
- `version` — export format version (currently `"1.0"`).
- `exportedAt` — ISO 8601 timestamp of the export.
- `exportedBy` — fixed identifier `"Astrolabe"`.
- `snippets` / `datasets` / `themes` — arrays of complete records as defined in _Data Model_, each including its record `version` field. (This is the per-record schema version, not the envelope `version` above.) `themes` is additive: exports always write it, and importers treat it as optional, so pre-theme envelopes remain valid `"1.0"` files.
## Per-chart export
A single chart can be exported on its own, separately from the whole-workspace Export above. The affordance is an **Export** control in the **Live Preview pane header** — placed there because exporting an image needs the chart that is currently rendered, and "export this chart" reads naturally beside the chart you are looking at. It is a disclosure that reveals four actions in two groups:
**Spec** (always available when a snippet is open):
- **Copy spec** — copies the currently-shown spec (draft or published, matching the editor's view) to the clipboard as JSON. Confirmed by a success toast, since a clipboard write is otherwise invisible.
- **Download JSON** — downloads the currently-shown spec as a `.vl.json` file.
- **Referenced data** (shown only when the spec references one or more saved datasets) — a choice between **Inline** (default) and **Keep refs**. _Inline_ replaces each `{ data: { name } }` reference with the dataset's actual values so the exported spec renders standalone (outside Astrolabe), leaving the authored sizing untouched; _Keep refs_ exports the reference as written (which only resolves inside Astrolabe). It applies to both spec actions. If inlining is requested but a referenced dataset is missing from the library, the export is declined with a clear error.
**Image** (available only when a chart is currently rendered — the actions are disabled, with an explanatory line, while the preview is empty or showing an error):
- **Download PNG** — a rasterized image of the chart as shown.
- **Download SVG** — a vector image of the chart as shown.
- **Resolution** (PNG) — `1×` / `2×` / `3×`, default `1×`. These are multipliers **of the display's pixel density**, so `1×` already matches on-screen crispness on a high-DPI (Retina) display; higher values produce larger images for print or zoom. (SVG is resolution-independent and ignores this.)
- **Background** — `Theme` (default) / `White` / `None`. The chart itself renders on a transparent background (so on screen it shows the pane colour); export therefore fills it: _Theme_ matches the active theme's background, _White_ is always white, _None_ keeps it transparent. Applies to both PNG and SVG.
Every format exports _what is on screen_: the same spec the editor shows and the same chart the preview renders (current fit mode included). With a _Theme_ background the exported image reflects the active light/dark theme.
- **Filename**: derived from the snippet's name, made filesystem-safe (whitespace and illegal characters normalized; letters of any script preserved), with the format as the extension — e.g. `sales-by-region.png`, `sales-by-region.vl.json`. A name with nothing usable falls back to `chart`. No date or `astrolabe-` prefix (unlike the workspace export) — the user is exporting one named chart and wants its name on the file.
- **Feedback**: a success toast naming the saved file (or confirming the copy); a clear error if the clipboard is blocked or the chart is not ready to rasterize.
- **Availability**: the Export control is disabled when no snippet is open. The image actions additionally require a live rendered chart.
## Import
Import lets the user pick a JSON file from their device; its contents are normalized, merged into the current workspace, and saved.
- **Trigger**: the **Import** header control opens a file picker restricted to JSON files. After a file is chosen (or the picker cancelled) the control is ready to be used again immediately.
### Accepted inputs
The importer recognizes several shapes so that both Astrolabe exports and looser snippet files work:
- **Astrolabe export envelope** — an object with a `version` and a `snippets` array; optional `datasets` and `themes` arrays are imported too.
- **Bare array of snippets** — a top-level JSON array is treated as a list of snippets (no datasets or themes).
- **Single snippet object** — any other object is treated as one snippet.
- **Older / foreign snippet shapes** — snippets that do not match the current model are normalized onto it:
- Alternative field names are mapped: `content` → spec, `draft` → draft spec, `createdAt` → creation timestamp.
- Missing timestamps are generated at import time (creation and modification set to now, or derived from the source timestamp when present).
- Missing identifiers, names, comments, tags, dataset references, metadata, and record `version` are filled with defaults (a missing `version` is treated as the earliest shape and migrated up on read — see _Data Model_).
- Such normalized imports are tagged `"imported"` so the user can find them.
A snippet is treated as already in current Astrolabe format when it carries an ISO-style creation timestamp; in that case its existing fields (id, name, timestamps, spec, draft spec, comment, tags, dataset references, metadata) are preserved as-is, with sensible fallbacks for any missing field.
### Merge behavior
- Imported snippets are **appended** to the existing library; nothing is overwritten or removed.
- **ID collisions** (an incoming snippet whose id already exists) are resolved by assigning the incoming snippet a fresh unique id; the original snippet keeps its id.
- Datasets and custom themes are imported **before** snippets so that snippet dataset references can resolve.
- Imported custom themes always receive fresh ids from the theme library; an envelope's theme ids never displace existing records.
### Name conflicts (datasets and themes)
When an imported dataset's or custom theme's name already exists in the library, it is auto-renamed to a unique name rather than overwriting the existing one (see _Datasets_).
- A numeric suffix is appended to the original name; further suffixes are added until the name is unique.
- The renamed records are reported to the user via a warning toast listing each `original -> new` rename.
- A dataset rename is propagated into the imported snippets that reference it; theme renames need no propagation (nothing references a theme by name).
- If a single dataset fails to import, it is skipped and the rest of the import continues.
### Storage limit handling
Snippet storage has an approximate 5 MB budget (see _Snippet Library_ storage monitor).
- If the incoming snippets would push total snippet storage over the budget, the user is warned about the overage amount, but the app still attempts to save the import.
- If the save ultimately fails because the storage quota is exceeded, the user is told to delete some snippets and try again, and no partial snippet import is committed.
- The storage check applies to snippets; datasets are stored separately and saved during the dataset phase above.
### Feedback
- **Success**: a toast reports how many snippets (and datasets and themes, when any) were imported, e.g. "Imported 4 snippets, 2 datasets and 1 theme".
- **Renames**: when datasets or themes were renamed, the success message is shown as a warning toast that also lists the renames.
- **Empty file**: if no snippets are found in the file, the user is informed ("No snippets found in file") and nothing is imported — even if the file carries datasets or themes.
- **Quota failure**: a clear error advising the user to delete snippets and retry.
- **Invalid file**: a non-JSON or unparseable file produces a clear error ("Failed to import. Please check that the file is valid JSON."); an unreadable file produces a read error. In all error cases the existing workspace is left unchanged.