Files
astrolabe/docs/spec/01-application-shell.md
T

136 lines
12 KiB
Markdown

# 01 · Application Shell & Navigation
This section describes the overall workspace structure, the header toolbar, the modal system, keyboard shortcuts, URL-based navigation, transient notifications, and the offline/installable nature of the app. Feature-specific behavior lives in the sections referenced inline.
## A. Workspace Layout
Astrolabe is a single-screen workspace. Below a fixed top header sits a three-pane working area, each pane dedicated to one part of the snippet-editing workflow:
- **Snippet library** (left) — browse, search, select, and manage saved snippets (see _Snippet Library_).
- **Spec editor** (center) — edit the Vega-Lite spec of the selected snippet (see _Spec Editor & Draft/Published Workflow_).
- **Live preview** (right) — render the current spec (see _Live Preview_).
Behavior:
- All three panes are visible by default, laid out side by side in the order library, editor, preview.
- Adjacent panes are separated by a vertical drag handle. The user can drag a handle left/right to resize the two panes it sits between; the rest of the layout is unaffected. When the editor is hidden, the library and preview become adjacent and a single handle between them re-splits the freed space.
- Each pane enforces a minimum width while resizing, so a pane cannot be dragged to nothing.
- Each pane can be individually shown or hidden via a persistent **toggle strip** (a narrow vertical strip of toggle buttons, one per pane, each indicating whether its pane is currently shown). The toggle strip also contains a shortcut button that opens the Datasets manager.
- When a pane is hidden, the remaining visible panes expand to fill the freed space, redistributing proportionally to their remembered widths. When a previously hidden pane is shown again, it returns at its remembered width. The center editor is normally the flex filler with no fixed width, so it remembers the width it had at the moment it was hidden and reclaims that width when shown again; the library and preview then keep the split ratio they were left at while it was hidden.
- Hiding all panes is permitted; the toggle strip remains available to bring panes back.
- Pane widths and per-pane visibility persist locally across sessions and are restored on next load. The app remembers a pane's preferred width even while it is hidden, so re-showing it restores that width rather than an arbitrary one.
## B. Header / Toolbar
A fixed header spans the top of the app.
- **Left side**: the app icon, the app title ("Astrolabe"), and a version badge showing the current app version.
- **Right side**: a row of **icon-only** utility entry points (each with a tooltip and an accessible name), then — set off by a divider — the text-labelled Support button and the theme toggle. Each opens a destination:
| Entry point | Opens |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| Datasets | The Datasets manager modal (see _Datasets_). |
| Import workspace | A file-picker dialog to choose a previously exported file; the chosen file is imported (see _Import & Export_). |
| Export workspace | Immediately produces a downloaded file containing all snippets and datasets (see _Import & Export_). |
| About | The About & Help modal (keyboard shortcuts, about, and privacy information); privacy lives inside. |
| Support | The Support modal — two ways to give back: feedback to the author, or a donation to Ukraine's defense. The one text-labelled, soft-accent button. |
Notes:
- The utilities are icon-only so the header reads as quiet chrome (Carbon UI-shell header: global actions are a right-aligned icon row). The accessible names scope the workspace-level Import/Export ("Export workspace") apart from the preview pane's per-chart "Export" — the two were previously both labelled "Export" at once.
- Import and Export act directly (file dialog / file download); they do not open in-app modals.
- The Datasets, About, and Support entry points each open a modal (see _Modal System_).
- **Settings are not a header entry point.** A design review (see _Settings_) distributed
preferences to the panes they affect — the appearance theme is a header toggle, and the
Editor / Performance / Formatting clusters open from a gear control in their own pane. There
is no central Settings button or modal.
## C. Modal System
The app shows at most one modal at a time. The modal set is: Datasets, About & Help, Support, Chart Builder, Extract-to-Dataset, and Theme Builder. (Settings are deliberately _not_ a modal — they are distributed to per-pane controls; see _Settings_.)
- Opening any modal closes whichever modal was previously open; the two never overlap.
- Every modal can be dismissed by: clicking its close button, pressing **Escape**, or clicking the backdrop outside the modal body. Exception: modals holding in-progress work (the Chart Builder and Theme Builder) ignore backdrop clicks; Escape and the close button still dismiss them.
- Clicking inside the modal body does not dismiss it.
- The Chart Builder, Extract-to-Dataset, and Theme Builder modals are opened from within the Datasets / snippet / preview workflows (see _Chart Builder_, _Datasets_, and _Live Preview_), not from the header.
- Dismissing a modal returns the user to the underlying workspace unchanged.
## D. Keyboard Shortcuts
Shortcuts are platform-aware: the modifier is **Cmd** on Mac and **Ctrl** on other platforms (shown below as Cmd/Ctrl).
| Shortcut | Action |
| -------------------- | ---------------------------------------------------------------------------------- |
| Cmd/Ctrl + Shift + N | Create a new snippet (see _Snippet Library_) |
| Cmd/Ctrl + K | Toggle the Datasets manager open/closed |
| Cmd/Ctrl + S | Publish the current snippet's draft (see _Spec Editor & Draft/Published Workflow_) |
| Cmd/Ctrl + , | Open the Editor settings cluster (see _Settings_) |
| Escape | Close the active modal |
Notes:
- Cmd/Ctrl + K is a toggle: if the Datasets manager is already open it closes it; otherwise it opens it.
- Escape only acts when a modal is open; with no modal open it does nothing.
- Cmd/Ctrl + S publishes the active draft regardless of where focus is — **including
while the editor has focus** — so it behaves as a "save" you reach for mid-edit. The
other shortcuts (new snippet, toggle Datasets, settings) are suppressed while the
user is typing in the editor or an input, so they don't interrupt text entry.
- The shortcut actions override the browser's default behavior for those key combinations.
## E. Navigation & Shareable URL State
The app reflects its current location in the URL hash so that reloading restores the same view and the browser's Back/Forward buttons move between prior states. The user can copy the URL to share or bookmark a specific location.
States and their hash forms:
| State | Hash |
| ------------------------------ | ------------------------------ |
| A selected snippet | `#snippet-<id>` |
| Datasets manager (list) | `#datasets` |
| A specific dataset | `#datasets/dataset-<id>` |
| New-dataset form | `#datasets/new` |
| Chart Builder for a dataset | `#datasets/dataset-<id>/build` |
| Chart Builder, no dataset open | `#build` |
`#build` is the Chart Builder opened with an empty dataset library (its no-datasets state). When datasets exist, an un-targeted builder open immediately lands on one (see _Chart Builder → Opening_), so the URL shows the dataset form instead.
Behavior:
- Selecting a snippet updates the URL to that snippet; reloading reopens that snippet.
- Opening the Datasets manager updates the URL to `#datasets`; opening a specific dataset, the new-dataset form, or the Chart Builder for a dataset updates the URL to the corresponding form above.
- Browser Back/Forward navigate between these states (e.g. closing a modal via Back returns to the previously selected snippet).
- On load, the app reads the hash and restores the corresponding state (selected snippet, Datasets list, a specific dataset, the new-dataset form, or the Chart Builder).
- An empty/absent hash opens the default snippets view with no modal.
**One-shot action links.** Two hash forms are not view states but requests, consumed on load: the app adds a snippet, opens it, then replaces the hash with the created snippet's view — so reloading does not re-add it, and the link never appears in Back/Forward history. Landing at one with an empty library skips the onboarding canvas and lays the workspace out at the same default split leaving the canvas would. Each visit deliberately creates a new copy.
- `#example-<id>` adds the matching gallery example (see _Snippet Library → First-Run & Empty Workspace_), named as in the gallery (same as pressing its **Add**). An unknown id is ignored and the hash degrades to the default view. The landing uses these links (the hero's "Open in Astrolabe") to hand a visitor into the app carrying the chart they were just looking at.
- `#spec-<payload>` carries a spec's own text (base64url-encoded), so any sender — a lesson stage's "Open in Astrolabe", a shared link — can hand a self-contained spec into the app. The snippet's name derives from the spec (its `title`, else a "Mark chart of y by x" phrase, else "Shared spec"), like a pasted spec. A malformed payload is ignored and the hash degrades to the default view.
## F. Toast Notifications
Transient toast messages appear in a corner of the screen to confirm actions or report problems, without interrupting the workflow.
- Four kinds, each visually distinct: **success**, **error**, **warning**, **info**.
- Each toast auto-dismisses after a few seconds, and can also be dismissed manually via its close control.
- Multiple toasts stack rather than replacing one another, and appear/disappear with a brief fade.
Toasts confirm outcomes the user **cannot already see** — a side effect, a disappearance, or a state change with no strong on-screen cue. An action whose result is immediately visible (a newly created snippet opening in the editor; a new dataset shown selected in its detail pane) is confirmed by that visible change, not by an added toast, which would only be noise (Nielsen Norman "aesthetic and minimalist design"; Carbon notification usage; see _docs/architecture/10 → Toast copy_). Assistive-technology users still receive an announcement on the changed region.
Events that raise toasts include:
- Snippet actions: **duplicating** (the copy is easily mistaken for an edit of the original), **deleting** (the snippet disappears), **publishing** a draft, and **reverting** a draft. _Creating_ a snippet opens it in the editor and needs no toast.
- Dataset actions: **deleting**, and **extracting** inline data into a dataset (the dataset is created off-screen while the user is in the editor). _Creating_ a dataset in the Datasets modal is confirmed by the new dataset appearing selected.
- Import/Export: results of an import (success/partial/failure) and confirmation of an export.
- Errors: spec/data validation failures and local-storage capacity warnings.
**Copy Reference** (Datasets) is the exception that proves the rule: a clipboard write is invisible, but the universal pattern confirms it _inline on the control_ ("Copied"), announced politely to assistive tech — a toast per copy would be noise.
## G. Offline & Installable
Astrolabe is local-first and usable without a network connection.
- After the first successful load, the app works fully offline; the interface and previously loaded content remain available with no connection.
- All snippets, datasets, and settings are stored locally and remain accessible offline (see _Data Model_).
- The app is installable as a standalone application from a supporting browser and, once installed, launches in its own window.