@elytracms/studio 0.0.71 → 0.0.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -186,12 +186,24 @@ export interface AppliedViewState {
186
186
  * issues for the UI to surface.
187
187
  */
188
188
  export declare function applyViewState(collection: CollectionDef, docs: readonly CmsDocument[], localeConfig: LocaleConfig, state: TableViewState, isPublished?: IsPublished): AppliedViewState;
189
- /** The id of the unnamed built-in "All documents" view. */
189
+ /** The id of the synthesized built-in "All entries" baseline view (EC-444). */
190
190
  export declare const ALL_VIEW_ID = "all";
191
191
  export declare function defaultViewState(collection: CollectionDef): TableViewState;
192
192
  /**
193
- * A config-declared view (EC-246b) — the flat authoring shape a collection
194
- * declares in `cms/` config: `{ name, filters?, sort?, columns?, hidden? }`.
193
+ * Resolve an authored VISIBLE column list (EC-444) into the internal two-axis
194
+ * `{columns, hidden}` table state. The authored list is exactly the columns shown,
195
+ * in order; every OTHER schema field is appended (in schema order) and hidden — the
196
+ * same shape {@link defaultViewState} produces, so {@link resolveColumns} renders
197
+ * precisely the authored list, visible, in that order. An omitted (or empty) list
198
+ * inherits the collection's default lens (all scalars visible, composites hidden).
199
+ * Unknown names in the authored list (a field the schema no longer has) are dropped.
200
+ */
201
+ export declare function resolveConfigColumns(collection: CollectionDef, visible: readonly string[] | undefined): Pick<TableViewState, 'columns' | 'hidden'>;
202
+ /** The reserved `views[]` id whose entry OVERRIDES the synthesized baseline view (EC-444). */
203
+ export declare const DEFAULT_VIEW_OVERRIDE_ID = "default";
204
+ /**
205
+ * A config-declared view (EC-246b/EC-444) — the flat authoring shape a collection
206
+ * declares in `cms/` config: `{ name, initial?, filters?, sort?, columns? }`.
195
207
  * Filters/sort/columns sit at the top level (no nested `state` wrapper). The
196
208
  * studio validates + interprets these; `cms-core` stores them opaquely (it
197
209
  * can't reference the filter vocabulary without a layering cycle).
@@ -199,6 +211,7 @@ export declare function defaultViewState(collection: CollectionDef): TableViewSt
199
211
  export declare const configViewSchema: z.ZodObject<{
200
212
  id: z.ZodOptional<z.ZodString>;
201
213
  name: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>;
214
+ initial: z.ZodOptional<z.ZodBoolean>;
202
215
  default: z.ZodOptional<z.ZodBoolean>;
203
216
  filters: z.ZodOptional<z.ZodArray<z.ZodObject<{
204
217
  field: z.ZodString;
@@ -224,19 +237,19 @@ export declare const configViewSchema: z.ZodObject<{
224
237
  }>;
225
238
  }, z.core.$strip>>>;
226
239
  columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
227
- hidden: z.ZodOptional<z.ZodArray<z.ZodString>>;
228
240
  }, z.core.$strip>;
229
241
  export type ConfigView = z.infer<typeof configViewSchema>;
230
242
  /**
231
243
  * A resolved view: identity + the table state it restores (matches `SavedView`).
232
244
  * `name` stays a {@link LocalizedString} — resolved at the render site (EC-343), like
233
- * `form.label`. `default` marks the initial-view seed (see {@link defaultConfigViewId}).
245
+ * `form.label`. `initial` marks the first-load seed (see {@link defaultConfigViewId}),
246
+ * resolved from the authored `initial ?? default` flag (EC-444).
234
247
  */
235
248
  export interface ResolvedView {
236
249
  id: string;
237
250
  name: LocalizedString;
238
251
  state: TableViewState;
239
- default?: boolean;
252
+ initial?: boolean;
240
253
  }
241
254
  /**
242
255
  * Resolve a collection's config-declared `views` into full {@link ResolvedView}s
@@ -248,26 +261,49 @@ export interface ResolvedView {
248
261
  */
249
262
  export declare function configViewsFor(collection: CollectionDef): ResolvedView[];
250
263
  /**
251
- * The id of the config view flagged `default: true` (EC-343) — the seed for a
252
- * collection's INITIAL active view. When several are flagged (a misconfiguration,
253
- * reported by {@link configViewIssues}) the FIRST in declaration order wins
254
- * deterministically; when none is flagged the caller falls back to the built-in
255
- * "All" lens. `undefined` when the collection declares no default view.
264
+ * Does the collection declare a baseline-override view (a config view whose id is
265
+ * {@link DEFAULT_VIEW_OVERRIDE_ID})? When true the synthesized "All entries" lens is
266
+ * replaced by that entry verbatim (see {@link resolveCollectionViews}), EC-444.
267
+ */
268
+ export declare function hasDefaultViewOverride(collection: CollectionDef): boolean;
269
+ /**
270
+ * The ordered view list a collection surfaces (EC-444): the baseline first, then the
271
+ * config-declared views in declaration order. The baseline is either an authored
272
+ * OVERRIDE (the config view whose id is {@link DEFAULT_VIEW_OVERRIDE_ID}, used
273
+ * VERBATIM and NOT repeated among the trailing views) or, absent one, the
274
+ * `synthesizedBaseline` the caller passes (the studio's translated "All entries"
275
+ * lens). Pure — the store supplies the synthesized baseline (i18n-bound `name`).
276
+ */
277
+ export declare function resolveCollectionViews(collection: CollectionDef, synthesizedBaseline: ResolvedView): ResolvedView[];
278
+ /**
279
+ * The ids of config views that still use the DEPRECATED `default` flag without the
280
+ * new `initial` (EC-444). Drives the one-release deprecation warning (mirrors the
281
+ * duplicate-default channel); empty once every view has migrated to `initial`.
282
+ */
283
+ export declare function deprecatedDefaultFlagViewIds(collection: CollectionDef): string[];
284
+ /**
285
+ * The id of the config view flagged `initial: true` (EC-343/EC-444; `default` read
286
+ * as a deprecated alias) — the seed for a collection's INITIAL active view. When
287
+ * several are flagged (a misconfiguration, reported by {@link configViewIssues}) the
288
+ * FIRST in declaration order wins deterministically; when none is flagged the caller
289
+ * falls back to the baseline lens. `undefined` when the collection flags none.
256
290
  */
257
291
  export declare function defaultConfigViewId(collection: CollectionDef): string | undefined;
258
- /** A config-level problem with a collection's declared views (EC-343). */
292
+ /** A config-level problem with a collection's declared views (EC-343/EC-444). */
259
293
  export interface ConfigViewIssue {
294
+ /** Stable code (kept from EC-343 to avoid catalog churn); reads "duplicate initial". */
260
295
  code: 'duplicate-default';
261
296
  message: string;
262
- /** The offending view ids (all views flagged `default`, in declaration order). */
297
+ /** The offending view ids (all views flagged `initial`, in declaration order). */
263
298
  viewIds: string[];
264
299
  }
265
300
  /**
266
- * Validate a collection's config-declared views (EC-343): at most one may set
267
- * `default: true`. A duplicate is returned as an explicit, structured issue —
268
- * never silently swallowed — so the studio can surface it (the first default still
269
- * wins deterministically for seeding, per {@link defaultConfigViewId}). Pure and
270
- * schema-only; the DOM-free counterpart of the "failures are first-class" contract.
301
+ * Validate a collection's config-declared views (EC-343/EC-444): at most one may set
302
+ * `initial: true` (`default` counted as its deprecated alias). A duplicate is returned
303
+ * as an explicit, structured issue — never silently swallowed — so the studio can
304
+ * surface it (the first still wins deterministically for seeding, per
305
+ * {@link defaultConfigViewId}). Pure and schema-only; the DOM-free counterpart of the
306
+ * "failures are first-class" contract.
271
307
  */
272
308
  export declare function configViewIssues(collection: CollectionDef): ConfigViewIssue[];
273
309
  /** Deep-cloned canonical form (stable key order via the Zod shape). */
@@ -314,7 +314,7 @@ export function applyViewState(collection, docs, localeConfig, state, isPublishe
314
314
  };
315
315
  }
316
316
  // --- View state construction, equality, columns ----------------------------------
317
- /** The id of the unnamed built-in "All documents" view. */
317
+ /** The id of the synthesized built-in "All entries" baseline view (EC-444). */
318
318
  export const ALL_VIEW_ID = 'all';
319
319
  /** The default (unnamed) view: no filters, no sort, every field visible. */
320
320
  /**
@@ -326,37 +326,67 @@ export const ALL_VIEW_ID = 'all';
326
326
  */
327
327
  const NON_DEFAULT_COLUMN_TYPES = new Set(['blocks', 'richText', 'object']);
328
328
  /**
329
- * Field names OFF the built-in "All" lens by default: the composite editing surfaces
330
- * (blocks/richText/object) — always noise in a scalar list — PLUS any scalar the
331
- * schema opts out with `form.column: false` (EC-343). Both only affect the default
332
- * columns: the field stays in the picker one toggle away, its form and filterability
333
- * are untouched, and a named view's explicit `columns` list ignores this entirely.
329
+ * Field names OFF the built-in baseline lens by default: exactly the composite
330
+ * editing surfaces (blocks/richText/object) — always noise in a scalar list. The
331
+ * default lens is "all scalars"; there is no per-field column opt-out any more
332
+ * (EC-444 hard-removed `form.column`). A field stays in the picker one toggle away,
333
+ * and a view curates columns by declaring an explicit `columns` list (or the
334
+ * `id: 'default'` baseline override).
334
335
  */
335
336
  function defaultHiddenColumns(collection) {
336
- return collection.fields
337
- .filter((f) => NON_DEFAULT_COLUMN_TYPES.has(f.type) || f.form?.column === false)
338
- .map((f) => f.name);
337
+ return collection.fields.filter((f) => NON_DEFAULT_COLUMN_TYPES.has(f.type)).map((f) => f.name);
339
338
  }
340
339
  export function defaultViewState(collection) {
341
340
  return {
342
341
  filters: [],
343
342
  sort: null,
344
- // Every field keeps a slot (order + picker), but the composite surfaces and any
345
- // `form.column: false` scalars start hidden so the default lens is the
346
- // collection's chosen scalar columns (EC-246b / EC-343).
343
+ // Every field keeps a slot (order + picker), but the composite surfaces start
344
+ // hidden so the default lens is the collection's scalar columns (EC-246b/EC-444).
347
345
  columns: orderedColumnNames(collection),
348
346
  hidden: defaultHiddenColumns(collection),
349
347
  };
350
348
  }
351
349
  /**
352
- * A config-declared view (EC-246b) — the flat authoring shape a collection
353
- * declares in `cms/` config: `{ name, filters?, sort?, columns?, hidden? }`.
350
+ * Resolve an authored VISIBLE column list (EC-444) into the internal two-axis
351
+ * `{columns, hidden}` table state. The authored list is exactly the columns shown,
352
+ * in order; every OTHER schema field is appended (in schema order) and hidden — the
353
+ * same shape {@link defaultViewState} produces, so {@link resolveColumns} renders
354
+ * precisely the authored list, visible, in that order. An omitted (or empty) list
355
+ * inherits the collection's default lens (all scalars visible, composites hidden).
356
+ * Unknown names in the authored list (a field the schema no longer has) are dropped.
357
+ */
358
+ export function resolveConfigColumns(collection, visible) {
359
+ if (visible === undefined || visible.length === 0) {
360
+ const base = defaultViewState(collection);
361
+ return { columns: base.columns, hidden: base.hidden };
362
+ }
363
+ const schemaNames = orderedColumnNames(collection);
364
+ const schema = new Set(schemaNames);
365
+ const seen = new Set();
366
+ const shown = [];
367
+ for (const name of visible) {
368
+ if (schema.has(name) && !seen.has(name)) {
369
+ shown.push(name);
370
+ seen.add(name);
371
+ }
372
+ }
373
+ const rest = schemaNames.filter((name) => !seen.has(name));
374
+ return { columns: [...shown, ...rest], hidden: rest };
375
+ }
376
+ /** The reserved `views[]` id whose entry OVERRIDES the synthesized baseline view (EC-444). */
377
+ export const DEFAULT_VIEW_OVERRIDE_ID = 'default';
378
+ /**
379
+ * A config-declared view (EC-246b/EC-444) — the flat authoring shape a collection
380
+ * declares in `cms/` config: `{ name, initial?, filters?, sort?, columns? }`.
354
381
  * Filters/sort/columns sit at the top level (no nested `state` wrapper). The
355
382
  * studio validates + interprets these; `cms-core` stores them opaquely (it
356
383
  * can't reference the filter vocabulary without a layering cycle).
357
384
  */
358
385
  export const configViewSchema = z.object({
359
- /** Optional stable id; defaults to a slug of `name`. */
386
+ /**
387
+ * Optional stable id; defaults to a slug of `name`. The reserved value
388
+ * {@link DEFAULT_VIEW_OVERRIDE_ID} (`'default'`) marks the baseline-override entry.
389
+ */
360
390
  id: z.string().min(1).optional(),
361
391
  /**
362
392
  * View label; localizable (EC-343): a plain string or a `{ locale }` map, resolved
@@ -364,17 +394,26 @@ export const configViewSchema = z.object({
364
394
  */
365
395
  name: localizedStringSchema,
366
396
  /**
367
- * When true this view seeds the collection's INITIAL active view (EC-343) — the
368
- * curated lens an editor lands on before picking one. At most one per collection;
369
- * a duplicate is a visible validation issue ({@link configViewIssues}), never
370
- * silent. The built-in "All" lens stays available and stays the fallback.
397
+ * When true this view seeds the collection's INITIAL active view (EC-343/EC-444) —
398
+ * the curated lens an editor lands on before picking one. At most one per collection;
399
+ * a duplicate is a visible validation issue ({@link configViewIssues}), never silent.
400
+ * The baseline lens stays available and stays the fallback.
401
+ */
402
+ initial: z.boolean().optional(),
403
+ /**
404
+ * @deprecated EC-444 — renamed to {@link ConfigView.initial}. Still read as an alias
405
+ * for one release (`initial ?? default`) with a console warning; removed next release.
406
+ * Distinct from a field's default VALUE and from the reserved `id: 'default'` override.
371
407
  */
372
408
  default: z.boolean().optional(),
373
409
  filters: z.array(filterClauseSchema).optional(),
374
410
  sort: viewSortSchema.nullable().optional(),
375
- /** Column order; when omitted, the view inherits the collection's default columns. */
411
+ /**
412
+ * The single ordered list of VISIBLE columns (EC-444): what you list is what shows,
413
+ * in that order. Omit to inherit the collection's default columns (all scalars). The
414
+ * two-axis `{columns, hidden}` internal state is derived by {@link resolveConfigColumns}.
415
+ */
376
416
  columns: z.array(z.string().min(1)).optional(),
377
- hidden: z.array(z.string().min(1)).optional(),
378
417
  });
379
418
  /**
380
419
  * Slug used to derive a config view's stable id from its name. A localized name
@@ -401,7 +440,6 @@ export function configViewsFor(collection) {
401
440
  const declared = collection.views;
402
441
  if (!Array.isArray(declared))
403
442
  return [];
404
- const base = defaultViewState(collection);
405
443
  const seen = new Set();
406
444
  const resolved = [];
407
445
  declared.forEach((raw, index) => {
@@ -413,46 +451,100 @@ export function configViewsFor(collection) {
413
451
  if (seen.has(id))
414
452
  return;
415
453
  seen.add(id);
454
+ // EC-444: `initial` supersedes the deprecated `default` alias (one-release window).
455
+ const initial = view.initial ?? view.default;
456
+ const { columns, hidden } = resolveConfigColumns(collection, view.columns);
416
457
  resolved.push({
417
458
  id,
418
459
  name: view.name,
419
- ...(view.default ? { default: true } : {}),
460
+ ...(initial ? { initial: true } : {}),
420
461
  state: normalizeViewState({
421
462
  filters: view.filters ?? [],
422
463
  sort: view.sort ?? null,
423
- columns: view.columns && view.columns.length > 0 ? view.columns : base.columns,
424
- hidden: view.hidden ?? [],
464
+ columns,
465
+ hidden,
425
466
  }),
426
467
  });
427
468
  });
428
469
  return resolved;
429
470
  }
430
471
  /**
431
- * The id of the config view flagged `default: true` (EC-343) — the seed for a
432
- * collection's INITIAL active view. When several are flagged (a misconfiguration,
433
- * reported by {@link configViewIssues}) the FIRST in declaration order wins
434
- * deterministically; when none is flagged the caller falls back to the built-in
435
- * "All" lens. `undefined` when the collection declares no default view.
472
+ * Does the collection declare a baseline-override view (a config view whose id is
473
+ * {@link DEFAULT_VIEW_OVERRIDE_ID})? When true the synthesized "All entries" lens is
474
+ * replaced by that entry verbatim (see {@link resolveCollectionViews}), EC-444.
475
+ */
476
+ export function hasDefaultViewOverride(collection) {
477
+ return configViewsFor(collection).some((view) => view.id === DEFAULT_VIEW_OVERRIDE_ID);
478
+ }
479
+ /**
480
+ * The ordered view list a collection surfaces (EC-444): the baseline first, then the
481
+ * config-declared views in declaration order. The baseline is either an authored
482
+ * OVERRIDE (the config view whose id is {@link DEFAULT_VIEW_OVERRIDE_ID}, used
483
+ * VERBATIM and NOT repeated among the trailing views) or, absent one, the
484
+ * `synthesizedBaseline` the caller passes (the studio's translated "All entries"
485
+ * lens). Pure — the store supplies the synthesized baseline (i18n-bound `name`).
486
+ */
487
+ export function resolveCollectionViews(collection, synthesizedBaseline) {
488
+ const configViews = configViewsFor(collection);
489
+ const overrideIndex = configViews.findIndex((view) => view.id === DEFAULT_VIEW_OVERRIDE_ID);
490
+ if (overrideIndex < 0)
491
+ return [synthesizedBaseline, ...configViews];
492
+ const override = configViews[overrideIndex];
493
+ const rest = configViews.filter((_, index) => index !== overrideIndex);
494
+ return [override, ...rest];
495
+ }
496
+ /**
497
+ * The ids of config views that still use the DEPRECATED `default` flag without the
498
+ * new `initial` (EC-444). Drives the one-release deprecation warning (mirrors the
499
+ * duplicate-default channel); empty once every view has migrated to `initial`.
500
+ */
501
+ export function deprecatedDefaultFlagViewIds(collection) {
502
+ const declared = collection.views;
503
+ if (!Array.isArray(declared))
504
+ return [];
505
+ const seen = new Set();
506
+ const ids = [];
507
+ declared.forEach((raw, index) => {
508
+ const parsed = configViewSchema.safeParse(raw);
509
+ if (!parsed.success)
510
+ return;
511
+ const view = parsed.data;
512
+ const id = view.id ?? configViewId(view.name, index);
513
+ if (seen.has(id))
514
+ return;
515
+ seen.add(id);
516
+ if (view.default !== undefined && view.initial === undefined)
517
+ ids.push(id);
518
+ });
519
+ return ids;
520
+ }
521
+ /**
522
+ * The id of the config view flagged `initial: true` (EC-343/EC-444; `default` read
523
+ * as a deprecated alias) — the seed for a collection's INITIAL active view. When
524
+ * several are flagged (a misconfiguration, reported by {@link configViewIssues}) the
525
+ * FIRST in declaration order wins deterministically; when none is flagged the caller
526
+ * falls back to the baseline lens. `undefined` when the collection flags none.
436
527
  */
437
528
  export function defaultConfigViewId(collection) {
438
- return configViewsFor(collection).find((view) => view.default)?.id;
529
+ return configViewsFor(collection).find((view) => view.initial)?.id;
439
530
  }
440
531
  /**
441
- * Validate a collection's config-declared views (EC-343): at most one may set
442
- * `default: true`. A duplicate is returned as an explicit, structured issue —
443
- * never silently swallowed — so the studio can surface it (the first default still
444
- * wins deterministically for seeding, per {@link defaultConfigViewId}). Pure and
445
- * schema-only; the DOM-free counterpart of the "failures are first-class" contract.
532
+ * Validate a collection's config-declared views (EC-343/EC-444): at most one may set
533
+ * `initial: true` (`default` counted as its deprecated alias). A duplicate is returned
534
+ * as an explicit, structured issue — never silently swallowed — so the studio can
535
+ * surface it (the first still wins deterministically for seeding, per
536
+ * {@link defaultConfigViewId}). Pure and schema-only; the DOM-free counterpart of the
537
+ * "failures are first-class" contract.
446
538
  */
447
539
  export function configViewIssues(collection) {
448
- const defaults = configViewsFor(collection).filter((view) => view.default);
449
- if (defaults.length <= 1)
540
+ const initials = configViewsFor(collection).filter((view) => view.initial);
541
+ if (initials.length <= 1)
450
542
  return [];
451
- const viewIds = defaults.map((view) => view.id);
543
+ const viewIds = initials.map((view) => view.id);
452
544
  return [
453
545
  {
454
546
  code: 'duplicate-default',
455
- message: `Collection "${collection.id}" declares ${defaults.length} default views (${viewIds.join(', ')}); at most one view may set \`default: true\`. Seeding "${viewIds[0]}".`,
547
+ message: `Collection "${collection.id}" declares ${initials.length} initial views (${viewIds.join(', ')}); at most one view may set \`initial: true\`. Seeding "${viewIds[0]}".`,
456
548
  viewIds,
457
549
  },
458
550
  ];