@startsimpli/ui 0.4.40 → 0.4.42
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/package.json +3 -2
- package/src/catalog/__tests__/catalog.test.ts +101 -0
- package/src/catalog/catalog.ts +138 -0
- package/src/catalog/index.ts +48 -0
- package/src/catalog/scaffold.ts +64 -0
- package/src/catalog/types.ts +65 -0
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startsimpli/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.42",
|
|
4
4
|
"description": "Shared UI components package for StartSimpli applications",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.ts",
|
|
9
|
+
"./catalog": "./src/catalog/index.ts",
|
|
9
10
|
"./table": "./src/components/unified-table/index.ts",
|
|
10
11
|
"./gantt": "./src/components/gantt/index.ts",
|
|
11
12
|
"./gantt/styles": "./src/components/gantt/gantt.css",
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
"next": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
35
36
|
"react": "^18.0.0 || ^19.0.0",
|
|
36
37
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
37
|
-
"@startsimpli/api": "0.5.
|
|
38
|
+
"@startsimpli/api": "0.5.41",
|
|
38
39
|
"@startsimpli/auth": "0.4.29"
|
|
39
40
|
},
|
|
40
41
|
"peerDependenciesMeta": {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
UI_CATALOG,
|
|
3
|
+
getCatalogEntry,
|
|
4
|
+
catalogByCategory,
|
|
5
|
+
builtInWidgets,
|
|
6
|
+
renderCatalogForPrompt,
|
|
7
|
+
renderDashboardConfig,
|
|
8
|
+
widgetsNeedingScaffold,
|
|
9
|
+
} from '../index';
|
|
10
|
+
|
|
11
|
+
describe('UI_CATALOG', () => {
|
|
12
|
+
it('has unique ids', () => {
|
|
13
|
+
const ids = UI_CATALOG.map((e) => e.id);
|
|
14
|
+
expect(new Set(ids).size).toBe(ids.length);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('gives every entry the fields the AI builder + scaffolder need', () => {
|
|
18
|
+
for (const e of UI_CATALOG) {
|
|
19
|
+
expect(e.id).toMatch(/^[a-z][a-z0-9-]*$/);
|
|
20
|
+
expect(['widget', 'view', 'component']).toContain(e.category);
|
|
21
|
+
expect(e.title.length).toBeGreaterThan(0);
|
|
22
|
+
expect(e.description.length).toBeGreaterThan(0);
|
|
23
|
+
expect(e.dataNeeds.length).toBeGreaterThan(0);
|
|
24
|
+
expect(e.source.export.length).toBeGreaterThan(0);
|
|
25
|
+
expect(e.source.from.startsWith('@startsimpli/ui')).toBe(true);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('exposes the five template built-in widgets as config-only widgets', () => {
|
|
30
|
+
const builtIns = builtInWidgets().map((e) => e.id).sort();
|
|
31
|
+
expect(builtIns).toEqual(['metrics', 'pipeline', 'quick-create', 'recent', 'type-grid']);
|
|
32
|
+
for (const e of builtInWidgets()) expect(e.category).toBe('widget');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('built-in widgets ship an example config whose kind matches their id', () => {
|
|
36
|
+
for (const e of builtInWidgets()) {
|
|
37
|
+
expect(e.configExample).toBeDefined();
|
|
38
|
+
expect(e.configExample!.kind).toBe(e.id);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('flags heavy blocks so the AI can avoid them by default', () => {
|
|
43
|
+
// The bundle-costly ones (dnd/xlsx/calendar/gantt) must be marked heavy.
|
|
44
|
+
for (const id of ['kanban-board', 'data-table', 'calendar', 'gantt']) {
|
|
45
|
+
expect(getCatalogEntry(id)?.heavy).toBe(true);
|
|
46
|
+
}
|
|
47
|
+
// The lightweight defaults must NOT be heavy.
|
|
48
|
+
for (const id of ['metrics', 'quick-create', 'recent', 'type-grid']) {
|
|
49
|
+
expect(getCatalogEntry(id)?.heavy).toBeFalsy();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('renderCatalogForPrompt lists every block with its heavy/built-in flags', () => {
|
|
54
|
+
const text = renderCatalogForPrompt();
|
|
55
|
+
for (const e of UI_CATALOG) expect(text).toContain(e.id);
|
|
56
|
+
expect(text).toContain('⚠ heavy');
|
|
57
|
+
expect(text).toContain('built-in');
|
|
58
|
+
expect(text).toContain('scaffolded');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('catalogByCategory partitions the catalog', () => {
|
|
62
|
+
const total =
|
|
63
|
+
catalogByCategory('widget').length +
|
|
64
|
+
catalogByCategory('view').length +
|
|
65
|
+
catalogByCategory('component').length;
|
|
66
|
+
expect(total).toBe(UI_CATALOG.length);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('renderDashboardConfig', () => {
|
|
71
|
+
it('renders a valid foundry.dashboard.ts for known widgets', () => {
|
|
72
|
+
const r = renderDashboardConfig([
|
|
73
|
+
{ kind: 'metrics', title: 'Overview' },
|
|
74
|
+
{ kind: 'pipeline', type: 'topic', title: 'Pipeline' },
|
|
75
|
+
{ kind: 'recent', limit: 8 },
|
|
76
|
+
]);
|
|
77
|
+
expect(r.ok).toBe(true);
|
|
78
|
+
expect(r.errors).toEqual([]);
|
|
79
|
+
expect(r.file?.path).toBe('src/foundry.dashboard.ts');
|
|
80
|
+
expect(r.file?.content).toContain('export const dashboard: DashboardConfig = [');
|
|
81
|
+
expect(r.file?.content).toContain('"kind":"metrics"');
|
|
82
|
+
expect(r.file?.content).toContain('"type":"topic"');
|
|
83
|
+
expect(r.file?.content).toContain("import type { DashboardConfig } from '@/components/dashboard/config'");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('rejects an unknown widget kind (never writes a config the app cannot render)', () => {
|
|
87
|
+
const r = renderDashboardConfig([{ kind: 'metrics' }, { kind: 'teleporter' }]);
|
|
88
|
+
expect(r.ok).toBe(false);
|
|
89
|
+
expect(r.file).toBeUndefined();
|
|
90
|
+
expect(r.errors.join(' ')).toContain('teleporter');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('rejects an empty layout', () => {
|
|
94
|
+
expect(renderDashboardConfig([]).ok).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('flags chosen widgets that are not built into the template', () => {
|
|
98
|
+
expect(widgetsNeedingScaffold([{ kind: 'metrics' }, { kind: 'kanban-board' }])).toEqual(['kanban-board']);
|
|
99
|
+
expect(widgetsNeedingScaffold([{ kind: 'metrics' }, { kind: 'recent' }])).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { CatalogEntry } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The curated Foundry app-UI catalog. Order is roughly "reach for these first" →
|
|
5
|
+
* heavier/nicher last. Keep it small and honest: every entry must be genuinely
|
|
6
|
+
* useful in a data-driven tenant app and cheap to explain to a non-technical
|
|
7
|
+
* owner. `builtIn` blocks ship in the template today; the rest are scaffolded in
|
|
8
|
+
* on demand so the base template stays thin.
|
|
9
|
+
*/
|
|
10
|
+
export const UI_CATALOG: CatalogEntry[] = [
|
|
11
|
+
{
|
|
12
|
+
id: 'metrics',
|
|
13
|
+
category: 'widget',
|
|
14
|
+
title: 'Metrics row',
|
|
15
|
+
description:
|
|
16
|
+
'A row of big-number tiles, one per entity type, each linking to its section. The default at-a-glance overview — good on almost every app.',
|
|
17
|
+
dataNeeds: 'Any types (uses record counts).',
|
|
18
|
+
builtIn: true,
|
|
19
|
+
source: { export: 'MetricCard', from: '@startsimpli/ui' },
|
|
20
|
+
configExample: { kind: 'metrics', title: 'Overview' },
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'quick-create',
|
|
24
|
+
category: 'widget',
|
|
25
|
+
title: 'Quick add',
|
|
26
|
+
description:
|
|
27
|
+
'One-click "New <type>" buttons that open the create form in a dialog. The fast path from an empty app to its first records.',
|
|
28
|
+
dataNeeds: 'Any types.',
|
|
29
|
+
builtIn: true,
|
|
30
|
+
source: { export: 'BaseDialog', from: '@startsimpli/ui' },
|
|
31
|
+
configExample: { kind: 'quick-create', title: 'Quick add' },
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'pipeline',
|
|
35
|
+
category: 'widget',
|
|
36
|
+
title: 'Pipeline funnel',
|
|
37
|
+
description:
|
|
38
|
+
'A funnel of one type\'s records across the stages of its status field, with per-stage counts and conversion. Best for a clear forward workflow (e.g. lead → won). Reads oddly for branch statuses like "rejected".',
|
|
39
|
+
dataNeeds: 'A type with a status/enum field whose stages flow forward.',
|
|
40
|
+
builtIn: true,
|
|
41
|
+
source: { export: 'PipelineFunnel', from: '@startsimpli/ui' },
|
|
42
|
+
configExample: { kind: 'pipeline', type: '<typeKey>', title: 'Pipeline' },
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'recent',
|
|
46
|
+
category: 'widget',
|
|
47
|
+
title: 'Recent activity',
|
|
48
|
+
description:
|
|
49
|
+
'A live feed of the most recently created records — across all types or one — with type badges and relative timestamps.',
|
|
50
|
+
dataNeeds: 'Any types (uses created_at).',
|
|
51
|
+
builtIn: true,
|
|
52
|
+
source: { export: 'Card', from: '@startsimpli/ui' },
|
|
53
|
+
configExample: { kind: 'recent', limit: 8, title: 'Recent activity' },
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'type-grid',
|
|
57
|
+
category: 'widget',
|
|
58
|
+
title: 'Section cards',
|
|
59
|
+
description:
|
|
60
|
+
'The classic per-type card grid: count + status breakdown, each card links into its section. A compact index of the whole app.',
|
|
61
|
+
dataNeeds: 'Any types.',
|
|
62
|
+
builtIn: true,
|
|
63
|
+
source: { export: 'Card', from: '@startsimpli/ui' },
|
|
64
|
+
configExample: { kind: 'type-grid', title: 'All sections' },
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'kanban-board',
|
|
68
|
+
category: 'widget',
|
|
69
|
+
title: 'Kanban board preview',
|
|
70
|
+
description:
|
|
71
|
+
'A drag-to-move board that lays a status type\'s records into lanes. Use as a home widget for the primary workflow type, or as the full section for it.',
|
|
72
|
+
dataNeeds: 'A type with a status/enum field.',
|
|
73
|
+
heavy: true,
|
|
74
|
+
source: { export: 'KanbanBoard', from: '@startsimpli/ui' },
|
|
75
|
+
configExample: { kind: 'kanban-board', type: '<typeKey>', title: 'Board' },
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'data-table',
|
|
79
|
+
category: 'view',
|
|
80
|
+
title: 'Data table',
|
|
81
|
+
description:
|
|
82
|
+
'An enterprise table for one type — sortable columns, column picker, server pagination, Excel export. Use for dense list types the owner scans and exports.',
|
|
83
|
+
dataNeeds: 'Any type (renders its fields as columns).',
|
|
84
|
+
heavy: true,
|
|
85
|
+
source: { export: 'UnifiedTable', from: '@startsimpli/ui/table' },
|
|
86
|
+
configExample: { kind: 'data-table', type: '<typeKey>', title: 'Records' },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'calendar',
|
|
90
|
+
category: 'view',
|
|
91
|
+
title: 'Calendar',
|
|
92
|
+
description:
|
|
93
|
+
'A month/week calendar of a type\'s records placed by one of its date fields. Use for anything time-anchored — events, posts, deadlines.',
|
|
94
|
+
dataNeeds: 'A type with a date field.',
|
|
95
|
+
heavy: true,
|
|
96
|
+
source: { export: 'CalendarView', from: '@startsimpli/ui' },
|
|
97
|
+
configExample: { kind: 'calendar', type: '<typeKey>', dateField: '<fieldName>', title: 'Calendar' },
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 'gantt',
|
|
101
|
+
category: 'view',
|
|
102
|
+
title: 'Gantt timeline',
|
|
103
|
+
description:
|
|
104
|
+
'A timeline of records with start/end dates as bars, with board and timeline modes. Use for scheduling/project types spanning ranges.',
|
|
105
|
+
dataNeeds: 'A type with a start-date and an end-date field.',
|
|
106
|
+
heavy: true,
|
|
107
|
+
source: { export: 'GanttChart', from: '@startsimpli/ui/gantt' },
|
|
108
|
+
configExample: { kind: 'gantt', type: '<typeKey>', startField: '<start>', endField: '<end>', title: 'Timeline' },
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: 'metric-trend',
|
|
112
|
+
category: 'widget',
|
|
113
|
+
title: 'Trend metric',
|
|
114
|
+
description:
|
|
115
|
+
'A single metric tile with a sparkline and up/down trend. Use to headline one number that matters (this week\'s new records, a running total).',
|
|
116
|
+
dataNeeds: 'Any type, or a numeric field tracked over time.',
|
|
117
|
+
source: { export: 'MetricCard', from: '@startsimpli/ui' },
|
|
118
|
+
configExample: { kind: 'metric-trend', type: '<typeKey>', title: 'This week' },
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'command-palette',
|
|
122
|
+
category: 'component',
|
|
123
|
+
title: 'Command palette',
|
|
124
|
+
description:
|
|
125
|
+
'A ⌘K global search/jump over the app\'s sections and records. An app-shell add-on, not a home widget — enable it once for the whole app.',
|
|
126
|
+
dataNeeds: 'Any types.',
|
|
127
|
+
source: { export: 'CommandPalette', from: '@startsimpli/ui' },
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'chat',
|
|
131
|
+
category: 'component',
|
|
132
|
+
title: 'Chat thread',
|
|
133
|
+
description:
|
|
134
|
+
'A threaded message view with a composer and attachments. Use only when the app genuinely models conversations (a messages type with a thread). Niche — do not add by default.',
|
|
135
|
+
dataNeeds: 'A messages/thread type.',
|
|
136
|
+
source: { export: 'ChatThread', from: '@startsimpli/ui' },
|
|
137
|
+
},
|
|
138
|
+
];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Foundry app-UI catalog — public surface.
|
|
3
|
+
*
|
|
4
|
+
* Import from `@startsimpli/ui/catalog` (a pure-data subpath; no component code).
|
|
5
|
+
* The AI app builder uses `renderCatalogForPrompt()` to "see" what it can build;
|
|
6
|
+
* `getCatalogEntry`/`UI_CATALOG` back the scaffolder and any component picker UI.
|
|
7
|
+
*/
|
|
8
|
+
export { UI_CATALOG } from './catalog';
|
|
9
|
+
export type { CatalogEntry, CatalogCategory, ScaffoldFile } from './types';
|
|
10
|
+
export {
|
|
11
|
+
renderDashboardConfig,
|
|
12
|
+
widgetsNeedingScaffold,
|
|
13
|
+
type DashboardWidgetConfig,
|
|
14
|
+
type RenderResult,
|
|
15
|
+
} from './scaffold';
|
|
16
|
+
|
|
17
|
+
import { UI_CATALOG } from './catalog';
|
|
18
|
+
import type { CatalogCategory, CatalogEntry } from './types';
|
|
19
|
+
|
|
20
|
+
/** Look up one block by id (the foundry.dashboard.ts `kind` for widgets). */
|
|
21
|
+
export function getCatalogEntry(id: string): CatalogEntry | undefined {
|
|
22
|
+
return UI_CATALOG.find((e) => e.id === id);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** All blocks in a category. */
|
|
26
|
+
export function catalogByCategory(category: CatalogCategory): CatalogEntry[] {
|
|
27
|
+
return UI_CATALOG.filter((e) => e.category === category);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The blocks the base template already ships (config-only, no scaffolding). */
|
|
31
|
+
export function builtInWidgets(): CatalogEntry[] {
|
|
32
|
+
return UI_CATALOG.filter((e) => e.builtIn);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A compact, token-frugal rendering of the catalog for an LLM system prompt: one
|
|
37
|
+
* line per block — id, category, a ⚠ heavy marker, what it needs, and the gist.
|
|
38
|
+
* Deterministic (catalog order) so prompts stay cache-stable.
|
|
39
|
+
*/
|
|
40
|
+
export function renderCatalogForPrompt(): string {
|
|
41
|
+
const line = (e: CatalogEntry) => {
|
|
42
|
+
const flags = [e.builtIn ? 'built-in' : 'scaffolded', e.heavy ? '⚠ heavy' : null]
|
|
43
|
+
.filter(Boolean)
|
|
44
|
+
.join(', ');
|
|
45
|
+
return `- ${e.id} (${e.category}${flags ? `; ${flags}` : ''}) — ${e.title}: ${e.description} Needs: ${e.dataNeeds}`;
|
|
46
|
+
};
|
|
47
|
+
return UI_CATALOG.map(line).join('\n');
|
|
48
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fork-authoring helpers: turn a chosen set of catalog blocks into the concrete
|
|
3
|
+
* files that go into a tenant's forked app repo. The AI app builder calls these
|
|
4
|
+
* to produce a reviewable change (applied as a PR), so the base template stays
|
|
5
|
+
* thin — only what's chosen lands in a fork.
|
|
6
|
+
*
|
|
7
|
+
* Today: `renderDashboardConfig` writes the home layout (foundry.dashboard.ts)
|
|
8
|
+
* from built-in widgets. Per-component scaffolding of heavier blocks (calendar,
|
|
9
|
+
* gantt, …) layers on next, emitting additional ScaffoldFiles.
|
|
10
|
+
*/
|
|
11
|
+
import { UI_CATALOG } from './catalog';
|
|
12
|
+
import type { ScaffoldFile } from './types';
|
|
13
|
+
|
|
14
|
+
/** One home widget: a catalog widget id + its per-widget params (type, title…). */
|
|
15
|
+
export interface DashboardWidgetConfig {
|
|
16
|
+
kind: string;
|
|
17
|
+
[param: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const WIDGET_IDS = new Set(UI_CATALOG.filter((e) => e.category === 'widget').map((e) => e.id));
|
|
21
|
+
|
|
22
|
+
/** Which of the chosen widgets are NOT built into the base template (need scaffolding). */
|
|
23
|
+
export function widgetsNeedingScaffold(widgets: DashboardWidgetConfig[]): string[] {
|
|
24
|
+
const builtIn = new Set(UI_CATALOG.filter((e) => e.builtIn).map((e) => e.id));
|
|
25
|
+
return [...new Set(widgets.map((w) => w.kind))].filter((k) => WIDGET_IDS.has(k) && !builtIn.has(k));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface RenderResult {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
/** Unknown/invalid widget kinds — the AI should fix these before applying. */
|
|
31
|
+
errors: string[];
|
|
32
|
+
file?: ScaffoldFile;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Render `src/foundry.dashboard.ts` for the given home layout. Validates each
|
|
37
|
+
* widget `kind` against the catalog's widget blocks; an unknown kind fails the
|
|
38
|
+
* whole render (so we never write a config referencing a widget the app can't
|
|
39
|
+
* render). Serializes params as a plain literal — valid TS the owner can hand-edit.
|
|
40
|
+
*/
|
|
41
|
+
export function renderDashboardConfig(widgets: DashboardWidgetConfig[]): RenderResult {
|
|
42
|
+
const errors: string[] = [];
|
|
43
|
+
if (widgets.length === 0) errors.push('layout is empty — choose at least one widget');
|
|
44
|
+
for (const w of widgets) {
|
|
45
|
+
if (!w.kind) errors.push('a widget is missing its "kind"');
|
|
46
|
+
else if (!WIDGET_IDS.has(w.kind)) errors.push(`unknown widget kind "${w.kind}"`);
|
|
47
|
+
}
|
|
48
|
+
if (errors.length > 0) return { ok: false, errors };
|
|
49
|
+
|
|
50
|
+
const body = widgets.map((w) => ` ${JSON.stringify(w)},`).join('\n');
|
|
51
|
+
const content = `/**
|
|
52
|
+
* Your app's home, as data. Generated by the Foundry app builder — safe to
|
|
53
|
+
* hand-edit. Each entry is a widget (see components/dashboard for the registry);
|
|
54
|
+
* add/remove/reorder them, or set this to 'auto' to derive a layout from your
|
|
55
|
+
* schema. Widget shapes live in components/dashboard/config.
|
|
56
|
+
*/
|
|
57
|
+
import type { DashboardConfig } from '@/components/dashboard/config';
|
|
58
|
+
|
|
59
|
+
export const dashboard: DashboardConfig = [
|
|
60
|
+
${body}
|
|
61
|
+
];
|
|
62
|
+
`;
|
|
63
|
+
return { ok: true, errors: [], file: { path: 'src/foundry.dashboard.ts', content } };
|
|
64
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Foundry app-UI component catalog (startsim app-builder).
|
|
3
|
+
*
|
|
4
|
+
* A curated, machine-readable index of the @startsimpli/ui building blocks a
|
|
5
|
+
* Foundry tenant app can use on its home / sections — the surface the AI app
|
|
6
|
+
* builder reads to decide what to compose, and the source of truth for how each
|
|
7
|
+
* gets scaffolded into a tenant's forked repo.
|
|
8
|
+
*
|
|
9
|
+
* This module is PURE DATA (no React imports) so it's cheap to pull into an LLM
|
|
10
|
+
* route or a Node scaffolder without dragging component code. The heavy, curated
|
|
11
|
+
* catalog is deliberately SMALL — the ~dozen blocks that make sense in a
|
|
12
|
+
* data-driven tenant app, not the ~300 exports of the kit. The AI is told to
|
|
13
|
+
* reach past it only when asked.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* How a block is used in a tenant app:
|
|
18
|
+
* - `widget`: a home-dashboard block, referenced by `kind` in foundry.dashboard.ts.
|
|
19
|
+
* - `view`: a full section/page for one entity type (board, table, calendar…).
|
|
20
|
+
* - `component`: an app-shell/embeddable primitive (command palette, chat…).
|
|
21
|
+
*/
|
|
22
|
+
export type CatalogCategory = 'widget' | 'view' | 'component';
|
|
23
|
+
|
|
24
|
+
export interface CatalogEntry {
|
|
25
|
+
/** Stable id. For widgets this IS the foundry.dashboard.ts `kind`. */
|
|
26
|
+
id: string;
|
|
27
|
+
category: CatalogCategory;
|
|
28
|
+
/** Human title shown to the owner. */
|
|
29
|
+
title: string;
|
|
30
|
+
/** What it is + when to reach for it — written for the AI builder to reason over. */
|
|
31
|
+
description: string;
|
|
32
|
+
/**
|
|
33
|
+
* The schema shape this block wants, in one plain phrase (e.g. "a type with a
|
|
34
|
+
* status/enum field", "a type with a date field"). The AI only offers a block
|
|
35
|
+
* when the tenant's live schema satisfies this.
|
|
36
|
+
*/
|
|
37
|
+
dataNeeds: string;
|
|
38
|
+
/**
|
|
39
|
+
* Pulls a big transitive dependency (calendar, kanban dnd, xlsx, gantt). The AI
|
|
40
|
+
* is instructed to avoid heavy blocks unless the user explicitly asks — this is
|
|
41
|
+
* how "don't just throw every component in" is enforced at the catalog level.
|
|
42
|
+
*/
|
|
43
|
+
heavy?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Already shipped in the base template's widget registry — configuring it needs
|
|
46
|
+
* only a foundry.dashboard.ts entry (no new files). Absent = must be scaffolded
|
|
47
|
+
* into the fork on demand (keeps the base template thin).
|
|
48
|
+
*/
|
|
49
|
+
builtIn?: boolean;
|
|
50
|
+
/** Where the underlying component comes from, for a scaffolded wrapper's import. */
|
|
51
|
+
source: { export: string; from: string };
|
|
52
|
+
/**
|
|
53
|
+
* An example foundry.dashboard.ts entry for this block, so the AI emits valid
|
|
54
|
+
* config. `kind` matches `id` for widgets.
|
|
55
|
+
*/
|
|
56
|
+
configExample?: Record<string, unknown>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** A block plus the concrete files to add to a fork to enable it (scaffolding). */
|
|
60
|
+
export interface ScaffoldFile {
|
|
61
|
+
/** Repo-relative path within the tenant fork, e.g. src/components/dashboard/calendar-widget.tsx */
|
|
62
|
+
path: string;
|
|
63
|
+
/** Full file contents. */
|
|
64
|
+
content: string;
|
|
65
|
+
}
|