mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Fix documentation drift surfaced by the full-docs consistency review
This commit is contained in:
@@ -36,11 +36,12 @@ registry, coordinator, and shell are exhaustively type-checked.
|
||||
// src/app/modals/types.ts
|
||||
export type ModalName =
|
||||
| 'datasets' // Datasets manager (list / detail / new-dataset form)
|
||||
| 'settings' // Appearance, editor, performance, formatting prefs
|
||||
| 'about' // About & Help (shortcuts, privacy)
|
||||
| 'donate' // Donate
|
||||
| 'chartBuilder' // Visual no-JSON chart composition for a dataset
|
||||
| 'extract'; // Extract inline spec data into a new dataset
|
||||
// Settings is NOT a modal — preferences are distributed to per-pane disclosure
|
||||
// popovers (spec §01C/§07; see components/SettingsPopover).
|
||||
|
||||
export type ActiveModal = ModalName | null;
|
||||
```
|
||||
@@ -76,8 +77,8 @@ export interface ModalConfig {
|
||||
init?: (arg?: string) => void;
|
||||
|
||||
/** Serializable snapshot of in-progress edits, used to detect unsaved
|
||||
* changes on close. OMIT for modals that apply immediately (settings,
|
||||
* about, donate) — omission opts out of the discard-confirmation. */
|
||||
* changes on close. OMIT for modals with no in-progress edits to guard
|
||||
* (about, donate) — omission opts out of the discard-confirmation. */
|
||||
getState?: () => Record<string, unknown> | null;
|
||||
|
||||
/** Whether the modal's primary action (Save / Apply) should be blocked
|
||||
@@ -88,16 +89,24 @@ export interface ModalConfig {
|
||||
getError?: () => string | null;
|
||||
|
||||
/** Whether this modal is reflected in the URL hash (back/forward, reload
|
||||
* restore). Datasets and Chart Builder are navigable; Donate is not. */
|
||||
* restore). Datasets and Chart Builder are navigable; About/Donate/Extract
|
||||
* are not (spec §01E). */
|
||||
isUrlNavigable?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
> **Shipped divergence.** The implemented registry (`modals/modal-registry.ts`) **omits
|
||||
> `hasError`/`getError`**: each modal renders its **own action row** inside its body (the
|
||||
> multi-view Datasets manager doesn't fit a single shell-level Save/Cancel), so validity is
|
||||
> each modal's own concern. The shipped `ModalConfig` keeps only `getState` (close-time
|
||||
> unsaved-change detection) plus `init`/`isUrlNavigable`. The generic-footer sketch through
|
||||
> the rest of this section is retained as the simpler pattern for a single-action modal —
|
||||
> treat it as illustrative, not a description of current code.
|
||||
|
||||
### Example entries
|
||||
|
||||
```ts
|
||||
import { DatasetsModal } from '../components/DatasetsModal';
|
||||
import { SettingsModal } from '../components/SettingsModal';
|
||||
import { ChartBuilderModal } from '../components/ChartBuilderModal';
|
||||
import { ExtractModal } from '../components/ExtractModal';
|
||||
import { DonateModal } from '../components/DonateModal';
|
||||
@@ -105,7 +114,6 @@ import { AboutModal } from '../components/AboutModal';
|
||||
import { useDatasetStore } from '../stores/DatasetStore';
|
||||
import { useChartBuilderStore } from '../stores/ChartBuilderStore';
|
||||
import { useExtractStore } from '../stores/ExtractStore';
|
||||
import { useSettingsStore } from '../stores/SettingsStore';
|
||||
|
||||
// Per-modal transient state lives in the relevant feature store; the registry
|
||||
// reads it via `getState()` (Zustand), never through component hooks.
|
||||
@@ -152,22 +160,8 @@ export const MODAL_REGISTRY: Record<ModalName, ModalConfig> = {
|
||||
getError: () => (useExtractStore.getState().name.trim() ? null : 'modals.extract.nameRequired'),
|
||||
},
|
||||
|
||||
// Applies immediately — no getState, so closing never prompts.
|
||||
settings: {
|
||||
name: 'settings',
|
||||
title: 'modals.settings.title',
|
||||
component: SettingsModal,
|
||||
isUrlNavigable: true,
|
||||
init: () => useSettingsStore.getState().loadFromPrefs(),
|
||||
},
|
||||
|
||||
// Pure info modals — no state, no validity, not navigable for donate.
|
||||
about: {
|
||||
name: 'about',
|
||||
title: 'modals.about.title',
|
||||
component: AboutModal,
|
||||
isUrlNavigable: true,
|
||||
},
|
||||
// Pure info modals — no state, no validity, not navigable.
|
||||
about: { name: 'about', title: 'modals.about.title', component: AboutModal },
|
||||
donate: { name: 'donate', title: 'modals.donate.title', component: DonateModal },
|
||||
};
|
||||
```
|
||||
@@ -297,9 +291,9 @@ export function hasUnsavedChanges(): boolean {
|
||||
```
|
||||
|
||||
The snapshot is taken once on open and compared on close. Modals without
|
||||
`getState` (settings, about, donate) snapshot to `null`, so `hasUnsavedChanges`
|
||||
short-circuits and they close instantly — correct, because they either apply
|
||||
immediately or hold nothing to lose.
|
||||
`getState` (about, donate) snapshot to `null`, so `hasUnsavedChanges`
|
||||
short-circuits and they close instantly — correct, because they hold nothing to
|
||||
lose.
|
||||
|
||||
### Validity passthrough
|
||||
|
||||
@@ -512,8 +506,8 @@ entry. Three properties force the split:
|
||||
component; its title/message/labels are supplied at the call site. There's
|
||||
nothing to register.
|
||||
- **Stacks _above_ a feature modal.** The discard-changes prompt must appear over
|
||||
an already-open Datasets/Settings modal — which directly violates the feature
|
||||
layer's "at most one open" rule. So confirmations live on a higher z-layer
|
||||
an already-open feature modal (e.g. Datasets or Chart Builder) — which directly
|
||||
violates the feature layer's "at most one open" rule. So confirmations live on a higher z-layer
|
||||
(`z-index: 1000`, above the future modal shell).
|
||||
- **Not navigable.** A confirmation is never a URL destination or a reload-restore
|
||||
target; it only exists for the duration of one decision.
|
||||
@@ -579,15 +573,16 @@ tool.
|
||||
The coordinator is the join point for navigation:
|
||||
|
||||
- `openModal` calls `syncModalToUrl`; navigable modals write a hash
|
||||
(`#datasets`, `#datasets/dataset-<id>`, `#datasets/dataset-<id>/build`,
|
||||
`#settings`). Non-navigable modals (donate) write nothing.
|
||||
(`#datasets`, `#datasets/dataset-<id>`, `#datasets/dataset-<id>/build`).
|
||||
Non-navigable modals (about, donate, extract) write nothing.
|
||||
- `closeModal` calls `clearModalFromUrl`, returning to the underlying workspace
|
||||
hash.
|
||||
- On load, the URL restorer reads the hash and calls `openModal(name, arg)` to
|
||||
rehydrate the right modal and sub-target.
|
||||
- The global key handler maps `Cmd/Ctrl+K` → `toggleDatasets()`,
|
||||
`Cmd/Ctrl+,` → `openModal('settings')`, and `Escape` → `closeModal()` (the
|
||||
Escape binding is a no-op when `activeModal` is `null`).
|
||||
- The global key handler maps `Cmd/Ctrl+K` → `toggleDatasets()` and `Escape` →
|
||||
`closeModal()` (a no-op when `activeModal` is `null`). `Cmd/Ctrl+,` opens the
|
||||
editor **settings popover**, not a modal (`openSettingsPopover('editor-settings')`;
|
||||
settings are distributed — spec §01C/§07).
|
||||
|
||||
Because all of these call the same coordinator functions, browser
|
||||
Back/Forward, keyboard shortcuts, and in-app triggers stay consistent — they
|
||||
|
||||
Reference in New Issue
Block a user