mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Workspace transfer: custom themes ride the export/import envelope
This commit is contained in:
@@ -286,42 +286,44 @@ reactive code.
|
||||
|
||||
## 5. Import: auto-suffix collisions, then report
|
||||
|
||||
On import we never overwrite an existing dataset. A dataset whose name collides
|
||||
On import we never overwrite an existing record. A record whose name collides
|
||||
is renamed to a unique name via `makeUniqueName`, and **every rename is
|
||||
collected and reported to the user** (toast / summary) so the change is never
|
||||
silent. Crucially, names are reserved _as we go_ — within a single import, two
|
||||
incoming `Sales` datasets become `Sales 2` and `Sales 3`, not two `Sales 2`.
|
||||
The helper is generic over `{ name: string }` because two record kinds key on a
|
||||
unique name: datasets and custom chart themes (only dataset renames need
|
||||
propagation — nothing references a theme by name).
|
||||
|
||||
```ts
|
||||
// src/core/import-normalize.ts
|
||||
|
||||
import { makeUniqueName } from './naming';
|
||||
import type { Dataset } from './dataset';
|
||||
|
||||
export interface DatasetRename {
|
||||
export interface NameRename {
|
||||
from: string;
|
||||
to: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns incoming datasets with collision-free names, plus the renames applied.
|
||||
* `existing` are names already in the library; `incoming` are datasets to add.
|
||||
* Returns incoming records with collision-free names, plus the renames applied.
|
||||
* `existing` are names already in the collection; `incoming` are records to add.
|
||||
*/
|
||||
export function dedupeIncomingDatasetNames(
|
||||
export function dedupeIncomingNames<T extends { name: string }>(
|
||||
existing: ReadonlyArray<string>,
|
||||
incoming: ReadonlyArray<Dataset>,
|
||||
): { datasets: Dataset[]; renames: DatasetRename[] } {
|
||||
incoming: ReadonlyArray<T>,
|
||||
): { records: T[]; renames: NameRename[] } {
|
||||
const reserved = new Set(existing.map((n) => n.toLowerCase()));
|
||||
const renames: DatasetRename[] = [];
|
||||
const renames: NameRename[] = [];
|
||||
|
||||
const datasets = incoming.map((d) => {
|
||||
const unique = makeUniqueName(d.name, reserved);
|
||||
const records = incoming.map((r) => {
|
||||
const unique = makeUniqueName(r.name, reserved);
|
||||
reserved.add(unique.toLowerCase()); // reserve so later imports don't collide
|
||||
if (unique !== d.name) renames.push({ from: d.name, to: unique });
|
||||
return unique === d.name ? d : { ...d, name: unique };
|
||||
if (unique !== r.name) renames.push({ from: r.name, to: unique });
|
||||
return unique === r.name ? r : { ...r, name: unique };
|
||||
});
|
||||
|
||||
return { datasets, renames };
|
||||
return { records, renames };
|
||||
}
|
||||
```
|
||||
|
||||
@@ -471,7 +473,7 @@ user action — there is no separate coordinator module to call.
|
||||
| `renameDatasetInSpec` | `src/core/spec-refs.ts` | yes | unit |
|
||||
| `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 |
|
||||
| `dedupeIncomingDatasetNames` | `src/core/import-normalize.ts` | yes | unit |
|
||||
| `dedupeIncomingNames` (datasets + custom themes) | `src/core/import-normalize.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
|
||||
|
||||
Reference in New Issue
Block a user