@agent-native/core-corpus 0.213.1 → 0.214.0

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.
Files changed (22) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/community/qr/package.json +1 -1
  3. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +15 -1
  4. package/corpus/templates/content/CHANGELOG.md +5 -0
  5. package/corpus/templates/content/actions/_database-view-setup.ts +305 -11
  6. package/corpus/templates/content/actions/_property-utils.ts +3 -0
  7. package/corpus/templates/content/actions/update-content-database-view.ts +18 -2
  8. package/corpus/templates/content/actions/view-screen.ts +12 -0
  9. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +91 -16
  10. package/corpus/templates/content/app/components/editor/database/ListItemLayout.tsx +410 -0
  11. package/corpus/templates/content/app/components/editor/database/ListItemRow.tsx +426 -0
  12. package/corpus/templates/content/app/components/editor/database/ListView.tsx +136 -18
  13. package/corpus/templates/content/app/components/editor/database/navigation-state.ts +3 -0
  14. package/corpus/templates/content/app/components/editor/database/settings.tsx +3 -3
  15. package/corpus/templates/content/app/components/editor/database/view-config.ts +4 -0
  16. package/corpus/templates/content/app/global.css +13 -0
  17. package/corpus/templates/content/app/i18n-data.ts +32 -0
  18. package/corpus/templates/content/changelog/2026-10-19-list-rows-can-be-customized.md +6 -0
  19. package/corpus/templates/content/changelog/2026-10-19-list-views-fit-phone-screens.md +6 -0
  20. package/corpus/templates/content/shared/api.ts +18 -0
  21. package/corpus/templates/content/shared/database-item-layout.ts +591 -0
  22. package/package.json +1 -1
@@ -0,0 +1,591 @@
1
+ import type {
2
+ ContentDatabaseItemLayout,
3
+ ContentDatabaseItemLeadingKind,
4
+ ContentDatabaseItemTitleWeight,
5
+ } from "./api";
6
+ import {
7
+ isPrimaryBlocksField,
8
+ type DocumentPropertyOptions,
9
+ type DocumentPropertyType,
10
+ type DocumentPropertyVisibility,
11
+ } from "./properties";
12
+
13
+ export const CONTENT_DATABASE_ITEM_LEADING_KINDS = [
14
+ "none",
15
+ "page_icon",
16
+ "checkbox",
17
+ "status",
18
+ ] as const satisfies readonly ContentDatabaseItemLeadingKind[];
19
+
20
+ export const CONTENT_DATABASE_ITEM_TITLE_WEIGHTS = [
21
+ "regular",
22
+ "medium",
23
+ "bold",
24
+ ] as const satisfies readonly ContentDatabaseItemTitleWeight[];
25
+
26
+ export const CONTENT_DATABASE_ITEM_LINE2_LINES = [1, 2, 3] as const;
27
+
28
+ /** Most properties line 3 or the trailing slot may show. */
29
+ export const MAX_ITEM_LAYOUT_META_PROPERTIES = 6;
30
+
31
+ export const CONTENT_DATABASE_ITEM_LAYOUT_SLOTS = [
32
+ "leading",
33
+ "title",
34
+ "line2",
35
+ "line3",
36
+ "trailing",
37
+ ] as const;
38
+
39
+ export type ContentDatabaseItemLayoutSlot =
40
+ (typeof CONTENT_DATABASE_ITEM_LAYOUT_SLOTS)[number];
41
+
42
+ export const CONTENT_DATABASE_ITEM_LAYOUT_PRESET_IDS = [
43
+ "default",
44
+ "checklist",
45
+ "reminders",
46
+ "tracker",
47
+ "digest",
48
+ ] as const;
49
+
50
+ export type ContentDatabaseItemLayoutPresetId =
51
+ (typeof CONTENT_DATABASE_ITEM_LAYOUT_PRESET_IDS)[number];
52
+
53
+ export interface ContentDatabaseItemLayoutPropertyLike {
54
+ definition: {
55
+ id: string;
56
+ type: DocumentPropertyType;
57
+ options?: DocumentPropertyOptions | null;
58
+ visibility?: DocumentPropertyVisibility;
59
+ };
60
+ }
61
+
62
+ /** Always-hidden properties stay out of rows, as they do in every view. */
63
+ function showableProperties<P extends ContentDatabaseItemLayoutPropertyLike>(
64
+ properties: readonly P[],
65
+ ): P[] {
66
+ return properties.filter(
67
+ (property) => property.definition.visibility !== "always_hide",
68
+ );
69
+ }
70
+
71
+ const LEADING_PROPERTY_TYPES: Partial<
72
+ Record<ContentDatabaseItemLeadingKind, DocumentPropertyType>
73
+ > = {
74
+ checkbox: "checkbox",
75
+ status: "status",
76
+ };
77
+
78
+ const LINE2_PROPERTY_TYPES: readonly DocumentPropertyType[] = [
79
+ "text",
80
+ "url",
81
+ "blocks",
82
+ ];
83
+
84
+ /** Properties a leading checkbox or status can show. */
85
+ export function itemLayoutLeadingCandidates<
86
+ P extends ContentDatabaseItemLayoutPropertyLike,
87
+ >(properties: readonly P[], kind: ContentDatabaseItemLeadingKind): P[] {
88
+ const type = LEADING_PROPERTY_TYPES[kind];
89
+ if (!type) return [];
90
+ return showableProperties(properties).filter(
91
+ (property) => property.definition.type === type,
92
+ );
93
+ }
94
+
95
+ /** Text-like properties line 2 can show. The page body arrives separately. */
96
+ export function itemLayoutLine2Candidates<
97
+ P extends ContentDatabaseItemLayoutPropertyLike,
98
+ >(properties: readonly P[]): P[] {
99
+ return showableProperties(properties).filter(
100
+ (property) =>
101
+ LINE2_PROPERTY_TYPES.includes(property.definition.type) &&
102
+ !(
103
+ property.definition.type === "blocks" &&
104
+ isPrimaryBlocksField(property.definition.options ?? {})
105
+ ),
106
+ );
107
+ }
108
+
109
+ /** Properties line 3 and the trailing slot can show as short values. */
110
+ export function itemLayoutMetaCandidates<
111
+ P extends ContentDatabaseItemLayoutPropertyLike,
112
+ >(properties: readonly P[]): P[] {
113
+ return showableProperties(properties).filter(
114
+ (property) => property.definition.type !== "blocks",
115
+ );
116
+ }
117
+
118
+ function propertyIdOrNull(value: unknown) {
119
+ if (typeof value !== "string") return null;
120
+ const id = value.trim();
121
+ return id ? id : null;
122
+ }
123
+
124
+ function propertyIdList(value: unknown) {
125
+ if (!Array.isArray(value)) return [];
126
+ const seen = new Set<string>();
127
+ const ids: string[] = [];
128
+ for (const item of value) {
129
+ const id = propertyIdOrNull(item);
130
+ if (!id || seen.has(id)) continue;
131
+ seen.add(id);
132
+ ids.push(id);
133
+ if (ids.length === MAX_ITEM_LAYOUT_META_PROPERTIES) break;
134
+ }
135
+ return ids;
136
+ }
137
+
138
+ function record(value: unknown): Record<string, unknown> {
139
+ return value && typeof value === "object" && !Array.isArray(value)
140
+ ? (value as Record<string, unknown>)
141
+ : {};
142
+ }
143
+
144
+ /**
145
+ * Reads a saved item layout, filling anything missing. Null means the view
146
+ * keeps its default rows.
147
+ */
148
+ export function normalizeContentDatabaseItemLayout(
149
+ value: unknown,
150
+ ): ContentDatabaseItemLayout | null {
151
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
152
+ const raw = value as Record<string, unknown>;
153
+ const leading = record(raw.leading);
154
+ const line2 = record(raw.line2);
155
+ const kind = CONTENT_DATABASE_ITEM_LEADING_KINDS.includes(
156
+ leading.kind as ContentDatabaseItemLeadingKind,
157
+ )
158
+ ? (leading.kind as ContentDatabaseItemLeadingKind)
159
+ : "page_icon";
160
+ const lines = CONTENT_DATABASE_ITEM_LINE2_LINES.includes(
161
+ line2.lines as 1 | 2 | 3,
162
+ )
163
+ ? (line2.lines as 1 | 2 | 3)
164
+ : 1;
165
+ return {
166
+ leading: {
167
+ kind,
168
+ propertyId: LEADING_PROPERTY_TYPES[kind]
169
+ ? propertyIdOrNull(leading.propertyId)
170
+ : null,
171
+ },
172
+ titleWeight: CONTENT_DATABASE_ITEM_TITLE_WEIGHTS.includes(
173
+ raw.titleWeight as ContentDatabaseItemTitleWeight,
174
+ )
175
+ ? (raw.titleWeight as ContentDatabaseItemTitleWeight)
176
+ : "medium",
177
+ line2: { propertyId: propertyIdOrNull(line2.propertyId), lines },
178
+ line3: propertyIdList(raw.line3),
179
+ trailing: propertyIdList(raw.trailing),
180
+ };
181
+ }
182
+
183
+ /**
184
+ * The `itemLayout` field of a normalized view. An explicit null is kept so a
185
+ * reset to default rows overrides the layout saved on the server; a missing
186
+ * or unreadable value is left out so saves that don't know about layouts
187
+ * keep the saved one.
188
+ */
189
+ export function contentDatabaseItemLayoutField(value: unknown): {
190
+ itemLayout?: ContentDatabaseItemLayout | null;
191
+ } {
192
+ if (value === null) return { itemLayout: null };
193
+ const itemLayout = normalizeContentDatabaseItemLayout(value);
194
+ return itemLayout ? { itemLayout } : {};
195
+ }
196
+
197
+ /** Carries the saved layout into a full-view save that left it out. */
198
+ export function withSavedContentDatabaseItemLayout<
199
+ T extends { id: string; itemLayout?: ContentDatabaseItemLayout | null },
200
+ >(
201
+ view: T,
202
+ savedViews: readonly {
203
+ id: string;
204
+ itemLayout?: ContentDatabaseItemLayout | null;
205
+ }[],
206
+ ): T {
207
+ if (view.itemLayout !== undefined) return view;
208
+ const saved = savedViews.find((candidate) => candidate.id === view.id);
209
+ return saved?.itemLayout === undefined
210
+ ? view
211
+ : { ...view, itemLayout: saved.itemLayout };
212
+ }
213
+
214
+ export interface ResolvedContentDatabaseItemLayout<P> {
215
+ leading: { kind: ContentDatabaseItemLeadingKind; property: P | null };
216
+ titleWeight: ContentDatabaseItemTitleWeight;
217
+ line2: { property: P | null; lines: 1 | 2 | 3 };
218
+ line3: P[];
219
+ trailing: P[];
220
+ }
221
+
222
+ /**
223
+ * Matches a layout's property IDs to the collection's current properties.
224
+ * Missing or mismatched properties drop out, and a checkbox or status slot
225
+ * without a usable property falls back to the page icon.
226
+ */
227
+ export function resolveContentDatabaseItemLayout<
228
+ P extends ContentDatabaseItemLayoutPropertyLike,
229
+ >(
230
+ layout: ContentDatabaseItemLayout,
231
+ properties: readonly P[],
232
+ ): ResolvedContentDatabaseItemLayout<P> {
233
+ const byId = new Map(
234
+ showableProperties(properties).map((property) => [
235
+ property.definition.id,
236
+ property,
237
+ ]),
238
+ );
239
+ const leadingCandidates = itemLayoutLeadingCandidates(
240
+ properties,
241
+ layout.leading.kind,
242
+ );
243
+ const leadingProperty = LEADING_PROPERTY_TYPES[layout.leading.kind]
244
+ ? layout.leading.propertyId
245
+ ? (leadingCandidates.find(
246
+ (property) => property.definition.id === layout.leading.propertyId,
247
+ ) ?? null)
248
+ : (leadingCandidates[0] ?? null)
249
+ : null;
250
+ const line2Candidates = new Set(itemLayoutLine2Candidates(properties));
251
+ const line2Property = layout.line2.propertyId
252
+ ? (byId.get(layout.line2.propertyId) ?? null)
253
+ : null;
254
+ const meta = (ids: readonly string[]) =>
255
+ ids.flatMap((id) => {
256
+ const property = byId.get(id);
257
+ return property && property.definition.type !== "blocks"
258
+ ? [property]
259
+ : [];
260
+ });
261
+ return {
262
+ leading: {
263
+ kind:
264
+ LEADING_PROPERTY_TYPES[layout.leading.kind] && !leadingProperty
265
+ ? "page_icon"
266
+ : layout.leading.kind,
267
+ property: leadingProperty,
268
+ },
269
+ titleWeight: layout.titleWeight,
270
+ line2: {
271
+ property:
272
+ line2Property && line2Candidates.has(line2Property)
273
+ ? line2Property
274
+ : null,
275
+ lines: layout.line2.lines,
276
+ },
277
+ line3: meta(layout.line3),
278
+ trailing: meta(layout.trailing),
279
+ };
280
+ }
281
+
282
+ /**
283
+ * Lists what is wrong with a layout's property references, for callers that
284
+ * should reject a bad layout instead of rendering around it.
285
+ */
286
+ export function contentDatabaseItemLayoutReferenceErrors(
287
+ layout: ContentDatabaseItemLayout,
288
+ properties: readonly ContentDatabaseItemLayoutPropertyLike[],
289
+ ): string[] {
290
+ const errors: string[] = [];
291
+ const byId = new Map(
292
+ showableProperties(properties).map((property) => [
293
+ property.definition.id,
294
+ property,
295
+ ]),
296
+ );
297
+ const leadingType = LEADING_PROPERTY_TYPES[layout.leading.kind];
298
+ if (leadingType) {
299
+ const property = layout.leading.propertyId
300
+ ? byId.get(layout.leading.propertyId)
301
+ : itemLayoutLeadingCandidates(properties, layout.leading.kind)[0];
302
+ if (!property) {
303
+ errors.push(
304
+ layout.leading.propertyId
305
+ ? `leading.propertyId references unavailable property "${layout.leading.propertyId}".`
306
+ : `A ${layout.leading.kind} leading slot needs a ${leadingType} property.`,
307
+ );
308
+ } else if (property.definition.type !== leadingType) {
309
+ errors.push(
310
+ `leading.propertyId must reference a ${leadingType} property for a ${layout.leading.kind} leading slot.`,
311
+ );
312
+ }
313
+ }
314
+ if (layout.line2.propertyId) {
315
+ const property = byId.get(layout.line2.propertyId);
316
+ if (!property) {
317
+ errors.push(
318
+ `line2.propertyId references unavailable property "${layout.line2.propertyId}".`,
319
+ );
320
+ } else if (!itemLayoutLine2Candidates([property]).length) {
321
+ errors.push(
322
+ "line2.propertyId must reference a text, URL, or non-body Blocks property.",
323
+ );
324
+ }
325
+ }
326
+ for (const slot of ["line3", "trailing"] as const) {
327
+ for (const id of layout[slot]) {
328
+ const property = byId.get(id);
329
+ if (!property) {
330
+ errors.push(`${slot} references unavailable property "${id}".`);
331
+ } else if (property.definition.type === "blocks") {
332
+ errors.push(`${slot} cannot show the Blocks property "${id}".`);
333
+ }
334
+ }
335
+ }
336
+ return errors;
337
+ }
338
+
339
+ /**
340
+ * Drops slot references the rows can't show (deleted, always-hidden, or the
341
+ * wrong type), so a stale saved slot never blocks a later edit.
342
+ */
343
+ export function pruneContentDatabaseItemLayout(
344
+ layout: ContentDatabaseItemLayout,
345
+ properties: readonly ContentDatabaseItemLayoutPropertyLike[],
346
+ ): ContentDatabaseItemLayout {
347
+ const resolved = resolveContentDatabaseItemLayout(layout, properties);
348
+ const ids = (list: readonly ContentDatabaseItemLayoutPropertyLike[]) =>
349
+ list.map((property) => property.definition.id);
350
+ return {
351
+ leading: {
352
+ kind: resolved.leading.kind,
353
+ propertyId: resolved.leading.property ? layout.leading.propertyId : null,
354
+ },
355
+ titleWeight: layout.titleWeight,
356
+ line2: {
357
+ propertyId: resolved.line2.property?.definition.id ?? null,
358
+ lines: layout.line2.lines,
359
+ },
360
+ line3: ids(resolved.line3),
361
+ trailing: ids(resolved.trailing),
362
+ };
363
+ }
364
+
365
+ /**
366
+ * Drops slot references to properties that no longer exist. References to
367
+ * hidden or retyped properties stay saved, because those changes can be
368
+ * undone; the rows skip them meanwhile.
369
+ */
370
+ export function dropDeletedContentDatabaseItemLayoutReferences(
371
+ layout: ContentDatabaseItemLayout,
372
+ properties: readonly ContentDatabaseItemLayoutPropertyLike[],
373
+ ): ContentDatabaseItemLayout {
374
+ const exists = new Set(properties.map((property) => property.definition.id));
375
+ const kept = (id: string | null) => (id && exists.has(id) ? id : null);
376
+ return {
377
+ leading:
378
+ layout.leading.propertyId && !kept(layout.leading.propertyId)
379
+ ? { kind: "page_icon", propertyId: null }
380
+ : layout.leading,
381
+ titleWeight: layout.titleWeight,
382
+ line2: {
383
+ propertyId: kept(layout.line2.propertyId),
384
+ lines: layout.line2.lines,
385
+ },
386
+ line3: layout.line3.filter((id) => exists.has(id)),
387
+ trailing: layout.trailing.filter((id) => exists.has(id)),
388
+ };
389
+ }
390
+
391
+ /**
392
+ * Line 3 or End after someone picks a property in the slot's menu, or
393
+ * Nothing (null). Saved properties the menu doesn't offer, such as ones
394
+ * hidden in this view, stay for when they're shown again; they give up their
395
+ * place only when the slot is full.
396
+ */
397
+ export function toggleContentDatabaseItemLayoutMetaProperty(
398
+ savedIds: readonly string[],
399
+ offeredIds: readonly string[],
400
+ id: string | null,
401
+ ): string[] {
402
+ const offered = new Set(offeredIds);
403
+ const next =
404
+ id === null
405
+ ? savedIds.filter((saved) => !offered.has(saved))
406
+ : savedIds.includes(id)
407
+ ? savedIds.filter((saved) => saved !== id)
408
+ : [...savedIds, id];
409
+ for (
410
+ let index = next.length - 1;
411
+ index >= 0 && next.length > MAX_ITEM_LAYOUT_META_PROPERTIES;
412
+ index -= 1
413
+ ) {
414
+ if (!offered.has(next[index])) next.splice(index, 1);
415
+ }
416
+ return next;
417
+ }
418
+
419
+ /**
420
+ * The layout a full-view save stores. References the view already saved
421
+ * stay, hidden or retyped or not; a new reference the rows can't show (the
422
+ * wrong type, or hidden) is dropped, as is any reference to a deleted
423
+ * property. A full save never fails over a slot.
424
+ */
425
+ export function savableContentDatabaseItemLayout(
426
+ layout: ContentDatabaseItemLayout,
427
+ saved: ContentDatabaseItemLayout | null,
428
+ properties: readonly ContentDatabaseItemLayoutPropertyLike[],
429
+ hiddenPropertyIds: readonly string[] = [],
430
+ ): ContentDatabaseItemLayout {
431
+ const hidden = new Set(hiddenPropertyIds);
432
+ const showable = pruneContentDatabaseItemLayout(
433
+ layout,
434
+ properties.filter((property) => !hidden.has(property.definition.id)),
435
+ );
436
+ const savedLeading =
437
+ saved?.leading.kind === layout.leading.kind &&
438
+ saved.leading.propertyId === layout.leading.propertyId;
439
+ const meta = (slot: "line3" | "trailing") =>
440
+ layout[slot].filter(
441
+ (id) => saved?.[slot].includes(id) || showable[slot].includes(id),
442
+ );
443
+ return dropDeletedContentDatabaseItemLayoutReferences(
444
+ {
445
+ leading: savedLeading ? layout.leading : showable.leading,
446
+ titleWeight: layout.titleWeight,
447
+ line2: {
448
+ propertyId:
449
+ saved?.line2.propertyId === layout.line2.propertyId
450
+ ? layout.line2.propertyId
451
+ : showable.line2.propertyId,
452
+ lines: layout.line2.lines,
453
+ },
454
+ line3: meta("line3"),
455
+ trailing: meta("trailing"),
456
+ },
457
+ properties,
458
+ );
459
+ }
460
+
461
+ /**
462
+ * The layout someone starts editing from on a view with no saved layout. It
463
+ * matches the default rows: page icon, title, and the first four properties.
464
+ * Line 2 is the only slot that shows a Blocks field, so one of those goes
465
+ * there. The page body itself has no slot yet.
466
+ */
467
+ export function startingContentDatabaseItemLayout(
468
+ visibleProperties: readonly ContentDatabaseItemLayoutPropertyLike[],
469
+ ): ContentDatabaseItemLayout {
470
+ const shown = visibleProperties.slice(0, 4);
471
+ const blocks = itemLayoutLine2Candidates(shown).find(
472
+ (property) => property.definition.type === "blocks",
473
+ );
474
+ return {
475
+ leading: { kind: "page_icon", propertyId: null },
476
+ titleWeight: "medium",
477
+ line2: { propertyId: blocks?.definition.id ?? null, lines: 1 },
478
+ line3: [],
479
+ trailing: itemLayoutMetaCandidates(shown).map(
480
+ (property) => property.definition.id,
481
+ ),
482
+ };
483
+ }
484
+
485
+ export interface ContentDatabaseItemLayoutPreset {
486
+ id: ContentDatabaseItemLayoutPresetId;
487
+ /** Null for Default, which keeps the view's default rows. */
488
+ layout: ContentDatabaseItemLayout | null;
489
+ /** The property type the preset needs but the collection lacks. */
490
+ missingPropertyType: "checkbox" | "status" | null;
491
+ }
492
+
493
+ function firstOfType<P extends ContentDatabaseItemLayoutPropertyLike>(
494
+ properties: readonly P[],
495
+ ...types: DocumentPropertyType[]
496
+ ) {
497
+ for (const type of types) {
498
+ const match = properties.find(
499
+ (property) => property.definition.type === type,
500
+ );
501
+ if (match) return match.definition.id;
502
+ }
503
+ return null;
504
+ }
505
+
506
+ /** Named starting points for a list's rows, built from the collection's properties. */
507
+ export function contentDatabaseItemLayoutPresets(
508
+ properties: readonly ContentDatabaseItemLayoutPropertyLike[],
509
+ ): ContentDatabaseItemLayoutPreset[] {
510
+ const shown = showableProperties(properties);
511
+ const checkbox = firstOfType(shown, "checkbox");
512
+ const status = firstOfType(shown, "status");
513
+ const text = itemLayoutLine2Candidates(shown)[0]?.definition.id ?? null;
514
+ const date = firstOfType(shown, "date");
515
+ const person = firstOfType(shown, "person");
516
+ const tag = firstOfType(shown, "multi_select", "select");
517
+ const ids = (...list: Array<string | null>) =>
518
+ list.filter((id): id is string => !!id);
519
+
520
+ return [
521
+ { id: "default", layout: null, missingPropertyType: null },
522
+ {
523
+ id: "checklist",
524
+ missingPropertyType: checkbox ? null : "checkbox",
525
+ layout: {
526
+ leading: { kind: "checkbox", propertyId: checkbox },
527
+ titleWeight: "bold",
528
+ line2: { propertyId: text, lines: 2 },
529
+ line3: [],
530
+ trailing: [],
531
+ },
532
+ },
533
+ {
534
+ id: "reminders",
535
+ missingPropertyType: checkbox ? null : "checkbox",
536
+ layout: {
537
+ leading: { kind: "checkbox", propertyId: checkbox },
538
+ titleWeight: "medium",
539
+ line2: { propertyId: text, lines: 1 },
540
+ line3: ids(date, person, tag),
541
+ trailing: [],
542
+ },
543
+ },
544
+ {
545
+ id: "tracker",
546
+ missingPropertyType: status ? null : "status",
547
+ layout: {
548
+ leading: { kind: "status", propertyId: status },
549
+ titleWeight: "regular",
550
+ line2: { propertyId: null, lines: 1 },
551
+ line3: [],
552
+ trailing: ids(tag, person, date),
553
+ },
554
+ },
555
+ {
556
+ id: "digest",
557
+ missingPropertyType: null,
558
+ layout: {
559
+ leading: { kind: "page_icon", propertyId: null },
560
+ titleWeight: "bold",
561
+ line2: { propertyId: text, lines: 3 },
562
+ line3: ids(status, date, tag),
563
+ trailing: [],
564
+ },
565
+ },
566
+ ];
567
+ }
568
+
569
+ function sameLayout(
570
+ a: ContentDatabaseItemLayout | null,
571
+ b: ContentDatabaseItemLayout | null,
572
+ ) {
573
+ return (
574
+ JSON.stringify(normalizeContentDatabaseItemLayout(a)) ===
575
+ JSON.stringify(normalizeContentDatabaseItemLayout(b))
576
+ );
577
+ }
578
+
579
+ /** The preset a layout currently matches, or null for a custom layout. */
580
+ export function matchingContentDatabaseItemLayoutPreset(
581
+ layout: ContentDatabaseItemLayout | null | undefined,
582
+ properties: readonly ContentDatabaseItemLayoutPropertyLike[],
583
+ ): ContentDatabaseItemLayoutPresetId | null {
584
+ const current = layout ?? null;
585
+ return (
586
+ contentDatabaseItemLayoutPresets(properties).find(
587
+ (preset) =>
588
+ !preset.missingPropertyType && sameLayout(preset.layout, current),
589
+ )?.id ?? null
590
+ );
591
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core-corpus",
3
- "version": "0.213.1",
3
+ "version": "0.214.0",
4
4
  "description": "Opt-in source corpus for Agent-Native Core source-search.",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {