mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Snippet naming: content-derived on publish, frozen on explicit rename
This commit is contained in:
@@ -165,6 +165,20 @@ const active = useSnippetStore(selectActiveSnippet);
|
||||
> Rule: if you can compute it, do not store it. Add a new state field only for a
|
||||
> value that is _input_ the app receives, not output it derives.
|
||||
|
||||
### Editing buffers — the sanctioned duplication, and its sync rule
|
||||
|
||||
A text field with debounced auto-save (the metadata panel's Name/Comment, the editor
|
||||
buffer) legitimately mirrors a store fact into local component state: the local copy is
|
||||
the user's in-progress text, the store holds the saved value. This duplication carries an
|
||||
obligation the moment the store fact has **another writer** (publish's content-derived
|
||||
renaming, import, any store-side mutation): the component must **adopt** a store change it
|
||||
didn't make, or its debounced save will write the stale local copy back — silently undoing
|
||||
the other writer. The pattern (see `SnippetLibrary`'s `SnippetMeta`): track the last store
|
||||
value seen in a ref; when the store value changes, adopt it into local state **unless the
|
||||
user has diverged** (local ≠ previous store value) — in-progress typing always wins.
|
||||
Keying the component by entity id handles switching entities; this rule handles the same
|
||||
entity changing underneath.
|
||||
|
||||
---
|
||||
|
||||
## 3. Where State Lives: Central vs. Per-Feature Stores
|
||||
|
||||
@@ -464,7 +464,30 @@ user action — there is no separate coordinator module to call.
|
||||
|
||||
---
|
||||
|
||||
## 7. Where things live
|
||||
## 7. Snippet name provenance — the naming hierarchy
|
||||
|
||||
Snippet names (unlike dataset names) need no uniqueness; what they need is a rule for
|
||||
**who may rewrite them**. Each snippet carries `nameSource` (spec §09A): `'user'` names
|
||||
are frozen — set by an explicit rename (`SnippetStore.renameSnippet`) and never touched
|
||||
by the app again; `'auto'` names are app-picked and keep tracking the spec. On publish,
|
||||
an auto-named snippet is re-named from the now-published content in priority order: the
|
||||
spec's `title` (string, line array, or `{ text }` forms), else a mark + encodings
|
||||
description, else the existing name stands. The derivation dialect is deliberately the
|
||||
same one `generateChartName` uses for builder output, so manually authored and
|
||||
builder-built snippets read alike in the library.
|
||||
|
||||
Flow: `core/snippet.ts` (`deriveSnippetName`, `isAutoNamed`, `isDefaultSnippetName`) →
|
||||
`SnippetStore.publish` (the only rewrite site) / `renameSnippet` (the freeze site) →
|
||||
`SnippetLibrary`'s metadata panel (which must adopt a publish rename — arch 01 §2,
|
||||
editing buffers). Records predating `nameSource` have no provenance; `isAutoNamed`
|
||||
treats them as user-named unless the name is **provably** app-picked — the timestamp
|
||||
default shape, or identical to what `deriveSnippetName` returns for the record's own
|
||||
published spec. The conservative default is deliberate: rewriting a chosen name is worse
|
||||
than failing to track an auto one.
|
||||
|
||||
---
|
||||
|
||||
## 8. Where things live
|
||||
|
||||
| Concern | Location | Pure? | Tested |
|
||||
| ------------------------------------------------------------------------ | -------------------------------- | ------------------- | ----------- |
|
||||
@@ -474,6 +497,7 @@ user action — there is no separate coordinator module to call.
|
||||
| `snippetsReferencingDataset`, `datasetUsageCounts` (reverse-lookup scan) | `src/core/relationships.ts` | yes | unit |
|
||||
| `renameDatasetRefs` → updated count (rename propagation) | `src/app/stores/SnippetStore.ts` | no (mutates stores) | integration |
|
||||
| `dedupeIncomingNames` (datasets + custom themes) | `src/core/import-normalize.ts` | yes | unit |
|
||||
| `deriveSnippetName`, `isAutoNamed` (snippet name provenance) | `src/core/snippet.ts` | yes | unit |
|
||||
|
||||
The dividing line: anything that takes plain data and returns plain data is
|
||||
**core** and unit-tested in isolation; anything that reaches into a Zustand store
|
||||
|
||||
@@ -87,6 +87,7 @@ The library provides the lifecycle operations for snippets. An operation whose o
|
||||
New snippets get a sensible default name, and a tag field exists on each snippet for categorization, though tags are not a primary user surface.
|
||||
|
||||
- A new snippet receives an auto-generated default name based on the current date and time, so it is uniquely identifiable until the user renames it (renaming happens in the metadata panel).
|
||||
- Names follow a **provenance hierarchy**: an **explicitly chosen** name (set via rename in the metadata panel) is frozen — the app never rewrites it. Every **app-picked** name (the timestamp default, a Chart Builder-generated name, or a previously derived one) is a "next best pick" that keeps tracking the spec: on each publish it is re-derived as the spec's `title` when present, else a mark + encodings description (the same dialect the Chart Builder names its output in), else the existing name stands. So the library reads by chart rather than by creation time, until the user takes over a name — at which point their word is final (see _Spec Editor → Publish_, _Data Model → `nameSource`_).
|
||||
- Each snippet stores a list of **tags**. Tags are persisted and carried through duplication; for example, snippets brought in via import are tagged "imported" (see _Import & Export_).
|
||||
- There is no dedicated tag-management UI; tags are stored on the data model but are not surfaced as a primary browsing or editing control.
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ Every snippet carries two versions of its spec: a **published** (stable) version
|
||||
|
||||
- A **Publish** action promotes the current draft to become the published version (the two are made identical).
|
||||
- Publish is also triggered by the keyboard shortcut Cmd/Ctrl+S.
|
||||
- If the snippet's name is **app-picked** (never explicitly renamed by the user — the timestamp default, a builder-generated name, or an earlier derived one), publish re-derives it from the now-published content: the spec's `title` verbatim when present (string, line array, or `{ text }` forms), else a mark + encodings description in the Chart Builder's naming dialect (e.g. "Bar chart of count by Ship Mode"), else the existing name stands. A name the user has set is never rewritten (see _Snippet Library → Naming & Tags_).
|
||||
- The snippet's dataset references track the draft continuously (recomputed on auto-save, extract, and revert), so publish needs no special reference handling — promoting the draft simply carries the already-current references onto the published version (see _Datasets_ for reference linking).
|
||||
- A success toast confirms the snippet was published.
|
||||
- Publish is unavailable when no snippet is active.
|
||||
|
||||
+14
-13
@@ -8,19 +8,20 @@ All data lives entirely in the browser. There is no server, account, or sync. Re
|
||||
|
||||
A **Snippet** is a saved Vega-Lite specification together with its metadata. Snippets are the primary user-authored entity, listed and managed in the _Snippet Library_.
|
||||
|
||||
| Field | Type | Meaning |
|
||||
| ------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| `id` | string | Unique, stable identifier for the snippet. |
|
||||
| `version` | number | Schema version of this record, used for read-time migration (see _Schema versioning_ below). |
|
||||
| `name` | string | Human-readable title shown in the library. |
|
||||
| `created` | ISO-timestamp string | When the snippet was first created. |
|
||||
| `modified` | ISO-timestamp string | When the snippet was last saved. |
|
||||
| `spec` | JSON value | The **published** Vega-Lite spec. May be an object or a string. This is the version rendered and shared by default. |
|
||||
| `draftSpec` | JSON value | The **working draft** Vega-Lite spec being edited. May be an object or a string. |
|
||||
| `comment` | string | Free-form user note about the snippet. |
|
||||
| `tags` | string[] | User-assigned labels for filtering and organization. |
|
||||
| `datasetRefs` | string[] | Names of _Datasets_ referenced by this spec (see relationships below). |
|
||||
| `meta` | object | Free-form, extensible metadata bag for app- or feature-specific data. |
|
||||
| Field | Type | Meaning |
|
||||
| ------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `id` | string | Unique, stable identifier for the snippet. |
|
||||
| `version` | number | Schema version of this record, used for read-time migration (see _Schema versioning_ below). |
|
||||
| `name` | string | Human-readable title shown in the library. |
|
||||
| `nameSource` | `'auto' \| 'user'`? | Name provenance: `auto` names keep tracking the spec on publish; `user` names are frozen (see _Snippet Library → Naming & Tags_). Optional — absent on records predating the field, which are treated as `user` unless the name is provably app-picked: the recognizable timestamp default, or identical to what the app derives from the record's own published spec. |
|
||||
| `created` | ISO-timestamp string | When the snippet was first created. |
|
||||
| `modified` | ISO-timestamp string | When the snippet was last saved. |
|
||||
| `spec` | JSON value | The **published** Vega-Lite spec. May be an object or a string. This is the version rendered and shared by default. |
|
||||
| `draftSpec` | JSON value | The **working draft** Vega-Lite spec being edited. May be an object or a string. |
|
||||
| `comment` | string | Free-form user note about the snippet. |
|
||||
| `tags` | string[] | User-assigned labels for filtering and organization. |
|
||||
| `datasetRefs` | string[] | Names of _Datasets_ referenced by this spec (see relationships below). |
|
||||
| `meta` | object | Free-form, extensible metadata bag for app- or feature-specific data. |
|
||||
|
||||
### Dual spec / draftSpec model
|
||||
|
||||
|
||||
Reference in New Issue
Block a user