@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.
- package/convex/cms.ts +8 -0
- package/convex/delivery.ts +12 -2
- package/convex/write-limits.ts +62 -0
- package/dist/components/cms/cms-store.js +43 -16
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +54 -18
- package/dist/lib/cms-views/view-model.js +132 -40
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +1 -1
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.js +1 -1
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/package.json +2 -2
|
@@ -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
|
|
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
|
-
*
|
|
194
|
-
*
|
|
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`. `
|
|
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
|
-
|
|
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
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
|
|
255
|
-
|
|
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 `
|
|
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
|
-
* `
|
|
268
|
-
* never silently swallowed — so the studio can
|
|
269
|
-
* wins deterministically for seeding, per
|
|
270
|
-
* schema-only; the DOM-free counterpart of the
|
|
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
|
|
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
|
|
330
|
-
* (blocks/richText/object) — always noise in a scalar list
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
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
|
|
345
|
-
//
|
|
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
|
-
*
|
|
353
|
-
*
|
|
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
|
-
/**
|
|
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) —
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
...(
|
|
460
|
+
...(initial ? { initial: true } : {}),
|
|
420
461
|
state: normalizeViewState({
|
|
421
462
|
filters: view.filters ?? [],
|
|
422
463
|
sort: view.sort ?? null,
|
|
423
|
-
columns
|
|
424
|
-
hidden
|
|
464
|
+
columns,
|
|
465
|
+
hidden,
|
|
425
466
|
}),
|
|
426
467
|
});
|
|
427
468
|
});
|
|
428
469
|
return resolved;
|
|
429
470
|
}
|
|
430
471
|
/**
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
|
|
435
|
-
|
|
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.
|
|
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
|
-
* `
|
|
443
|
-
* never silently swallowed — so the studio can
|
|
444
|
-
* wins deterministically for seeding, per
|
|
445
|
-
* schema-only; the DOM-free counterpart of the
|
|
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
|
|
449
|
-
if (
|
|
540
|
+
const initials = configViewsFor(collection).filter((view) => view.initial);
|
|
541
|
+
if (initials.length <= 1)
|
|
450
542
|
return [];
|
|
451
|
-
const viewIds =
|
|
543
|
+
const viewIds = initials.map((view) => view.id);
|
|
452
544
|
return [
|
|
453
545
|
{
|
|
454
546
|
code: 'duplicate-default',
|
|
455
|
-
message: `Collection "${collection.id}" declares ${
|
|
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
|
];
|