@axinom/mosaic-agent-skills 0.0.1
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/README.md +121 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +6 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +15 -0
- package/dist/logger.js.map +1 -0
- package/dist/tools/graphql/index.d.ts +3 -0
- package/dist/tools/graphql/index.d.ts.map +1 -0
- package/dist/tools/graphql/index.js +84 -0
- package/dist/tools/graphql/index.js.map +1 -0
- package/dist/tools/graphql/tools.d.ts +71 -0
- package/dist/tools/graphql/tools.d.ts.map +1 -0
- package/dist/tools/graphql/tools.js +187 -0
- package/dist/tools/graphql/tools.js.map +1 -0
- package/dist/tools/graphql/utils.d.ts +20 -0
- package/dist/tools/graphql/utils.d.ts.map +1 -0
- package/dist/tools/graphql/utils.js +140 -0
- package/dist/tools/graphql/utils.js.map +1 -0
- package/dist/tools/skills/index.d.ts +3 -0
- package/dist/tools/skills/index.d.ts.map +1 -0
- package/dist/tools/skills/index.js +62 -0
- package/dist/tools/skills/index.js.map +1 -0
- package/dist/tools/skills/tools.d.ts +5 -0
- package/dist/tools/skills/tools.d.ts.map +1 -0
- package/dist/tools/skills/tools.js +67 -0
- package/dist/tools/skills/tools.js.map +1 -0
- package/dist/tools/skills/types.d.ts +12 -0
- package/dist/tools/skills/types.d.ts.map +1 -0
- package/dist/tools/skills/types.js +3 -0
- package/dist/tools/skills/types.js.map +1 -0
- package/dist/tools/skills/utils.d.ts +11 -0
- package/dist/tools/skills/utils.d.ts.map +1 -0
- package/dist/tools/skills/utils.js +127 -0
- package/dist/tools/skills/utils.js.map +1 -0
- package/package.json +40 -0
- package/skills/_shared/actions/execution-summary.md +53 -0
- package/skills/_shared/actions/typescript-codegen.md +32 -0
- package/skills/_shared/actions/typescript-validation.md +32 -0
- package/skills/_shared/conventions/field-component-mapping.md +155 -0
- package/skills/_shared/conventions/field-validation-schema.md +77 -0
- package/skills/_shared/discovery/discover-paths.md +52 -0
- package/skills/_shared/discovery/extract-entity-features.md +565 -0
- package/skills/generate-create-station/SKILL.md +93 -0
- package/skills/generate-create-station/refs/finalization/station-registration.md +163 -0
- package/skills/generate-create-station/refs/templates/create-form-station.md +206 -0
- package/skills/generate-create-station/refs/templates/graphql-operations.md +56 -0
- package/skills/generate-details-station/SKILL.md +125 -0
- package/skills/generate-details-station/refs/finalization/explorer-integration.md +242 -0
- package/skills/generate-details-station/refs/finalization/station-registration.md +139 -0
- package/skills/generate-details-station/refs/templates/actions.md +127 -0
- package/skills/generate-details-station/refs/templates/breadcrumb.md +67 -0
- package/skills/generate-details-station/refs/templates/details-form-station.md +589 -0
- package/skills/generate-details-station/refs/templates/details-wrapper.md +54 -0
- package/skills/generate-details-station/refs/templates/form-data-types.md +62 -0
- package/skills/generate-details-station/refs/templates/graphql-operations.md +256 -0
- package/skills/generate-details-station/refs/templates/managed-integrations/image-management-station.md +322 -0
- package/skills/generate-details-station/refs/templates/managed-integrations/video-management-station.md +283 -0
- package/skills/generate-explorer-station/SKILL.md +121 -0
- package/skills/generate-explorer-station/refs/finalization/station-registration.md +145 -0
- package/skills/generate-explorer-station/refs/select-columns-filters.md +63 -0
- package/skills/generate-explorer-station/refs/templates/bulk-edit.md +369 -0
- package/skills/generate-explorer-station/refs/templates/common/bulk-actions.md +64 -0
- package/skills/generate-explorer-station/refs/templates/common/columns.md +131 -0
- package/skills/generate-explorer-station/refs/templates/common/data-provider.md +83 -0
- package/skills/generate-explorer-station/refs/templates/common/filters.md +220 -0
- package/skills/generate-explorer-station/refs/templates/common/inline-actions.md +45 -0
- package/skills/generate-explorer-station/refs/templates/common/types.md +28 -0
- package/skills/generate-explorer-station/refs/templates/graphql-operations.md +235 -0
- package/skills/generate-explorer-station/refs/templates/navigation-explorer-station.md +144 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: video-management-station
|
|
3
|
+
input:
|
|
4
|
+
- EntityFeatures (with capabilities.videos)
|
|
5
|
+
- DiscoveredPaths
|
|
6
|
+
output:
|
|
7
|
+
- path: '{workflowsPath}/src/Stations/{EntityPlural}/{Entity}VideoManagement/'
|
|
8
|
+
description: 'Complete video management station (4 files)'
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Generate Video Management Station
|
|
12
|
+
|
|
13
|
+
Creates station for managing entity videos (main video + trailers).
|
|
14
|
+
|
|
15
|
+
`Conditional` - only generate if `EntityFeatures.capabilities.videos` exists.
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
**Verify capability exists:**
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
if (!EntityFeatures.capabilities.videos) {
|
|
23
|
+
skip this step
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Files to Generate
|
|
28
|
+
|
|
29
|
+
1. `{Entity}VideoManagement.tsx` - Router wrapper
|
|
30
|
+
2. `{Entity}VideoManagementForm.tsx` - Form component
|
|
31
|
+
3. `{Entity}VideoManagement.graphql` - Query
|
|
32
|
+
4. `{Entity}VideoManagementQuickEdit.tsx` - Quick edit wrapper
|
|
33
|
+
|
|
34
|
+
## File 1: Router Wrapper
|
|
35
|
+
|
|
36
|
+
**Path:** `{Entity}VideoManagement/{Entity}VideoManagement.tsx`
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import React from 'react';
|
|
40
|
+
import { useParams } from 'react-router-dom';
|
|
41
|
+
import { {Entity}VideoManagementForm } from './{Entity}VideoManagementForm';
|
|
42
|
+
|
|
43
|
+
interface UrlParams {
|
|
44
|
+
{entityCamel}Id: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const {Entity}VideoManagement: React.FC = () => {
|
|
48
|
+
const { {entityCamel}Id } = useParams<UrlParams>();
|
|
49
|
+
|
|
50
|
+
return <{Entity}VideoManagementForm {entityCamel}Id={Number({entityCamel}Id)} />;
|
|
51
|
+
};
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## File 2: GraphQL Query
|
|
55
|
+
|
|
56
|
+
**Path:** `{Entity}VideoManagement/{Entity}VideoManagement.graphql`
|
|
57
|
+
|
|
58
|
+
```graphql
|
|
59
|
+
query {Entity}Videos($id: {features.idType}!) {
|
|
60
|
+
{features.camelCase}(id: $id) {
|
|
61
|
+
# If mainVideoField exists:
|
|
62
|
+
{features.capabilities.videos.mainVideoField}
|
|
63
|
+
# End if
|
|
64
|
+
# If trailersConnection exists:
|
|
65
|
+
{features.capabilities.videos.trailersConnection} {
|
|
66
|
+
nodes {
|
|
67
|
+
videoId
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
# End if
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## File 3: Form Component
|
|
76
|
+
|
|
77
|
+
**Path:** `{Entity}VideoManagement/{Entity}VideoManagementForm.tsx`
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { ID } from '@axinom/mosaic-managed-workflow-integration';
|
|
81
|
+
import {
|
|
82
|
+
createUpdateGQLFragmentGenerator,
|
|
83
|
+
Details,
|
|
84
|
+
DetailsProps,
|
|
85
|
+
generateArrayMutations,
|
|
86
|
+
} from '@axinom/mosaic-ui';
|
|
87
|
+
import { Field } from 'formik';
|
|
88
|
+
import gql from 'graphql-tag';
|
|
89
|
+
import { ObjectSchemaDefinition } from 'ObjectSchemaDefinition';
|
|
90
|
+
import React, { useCallback, useContext } from 'react';
|
|
91
|
+
import * as Yup from 'yup';
|
|
92
|
+
import { client } from '../../../apolloClient';
|
|
93
|
+
import { ExtensionsContext } from '{discoveredPaths.extensionsContextPath}';
|
|
94
|
+
import {
|
|
95
|
+
Mutation,
|
|
96
|
+
// If trailersConnection exists:
|
|
97
|
+
MutationCreate{features.capabilities.videos.trailersNodeType}Args,
|
|
98
|
+
MutationDelete{features.capabilities.videos.trailersNodeType}Args,
|
|
99
|
+
// End if
|
|
100
|
+
// If mainVideoField exists:
|
|
101
|
+
MutationUpdate{Entity}Args,
|
|
102
|
+
// End if
|
|
103
|
+
use{Entity}VideosQuery,
|
|
104
|
+
} from '../../../generated/graphql';
|
|
105
|
+
|
|
106
|
+
interface FormData {
|
|
107
|
+
// If mainVideoField exists:
|
|
108
|
+
mainVideo: ID[];
|
|
109
|
+
// End if
|
|
110
|
+
// If trailersConnection exists:
|
|
111
|
+
trailerVideos: ID[];
|
|
112
|
+
// End if
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
interface {Entity}VideoManagementFormProps {
|
|
116
|
+
{entityCamel}Id: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const {entityCamel}VideoManagementSchema = Yup.object().shape<
|
|
120
|
+
ObjectSchemaDefinition<FormData>
|
|
121
|
+
>({
|
|
122
|
+
// If mainVideoField exists:
|
|
123
|
+
mainVideo: Yup.array().of(Yup.mixed()).max(1),
|
|
124
|
+
// End if
|
|
125
|
+
// If trailersConnection exists:
|
|
126
|
+
trailerVideos: Yup.array().of(Yup.mixed()),
|
|
127
|
+
// End if
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
export const {Entity}VideoManagementForm: React.FC<
|
|
131
|
+
{Entity}VideoManagementFormProps
|
|
132
|
+
> = ({ {entityCamel}Id }) => {
|
|
133
|
+
const { VideoSelectField } = useContext(ExtensionsContext);
|
|
134
|
+
|
|
135
|
+
const { loading, data, error } = use{Entity}VideosQuery({
|
|
136
|
+
client,
|
|
137
|
+
variables: { id: {entityCamel}Id },
|
|
138
|
+
fetchPolicy: 'no-cache',
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const onSubmit = useCallback(
|
|
142
|
+
async (
|
|
143
|
+
formData: FormData,
|
|
144
|
+
initialData: DetailsProps<FormData>['initialData'],
|
|
145
|
+
): Promise<void> => {
|
|
146
|
+
const generateUpdateGQLFragment =
|
|
147
|
+
createUpdateGQLFragmentGenerator<Mutation>();
|
|
148
|
+
|
|
149
|
+
// If trailersConnection exists:
|
|
150
|
+
const trailerAssignmentMutations = generateArrayMutations({
|
|
151
|
+
current: formData.trailerVideos,
|
|
152
|
+
original: initialData.data?.trailerVideos,
|
|
153
|
+
generateCreateMutation: (videoId) =>
|
|
154
|
+
generateUpdateGQLFragment<MutationCreate{features.capabilities.videos.trailersNodeType}Args>(
|
|
155
|
+
'create{features.capabilities.videos.trailersNodeType}',
|
|
156
|
+
{ input: { {camelTrailersNodeType}: { videoId, {entityCamel}Id } } },
|
|
157
|
+
),
|
|
158
|
+
generateDeleteMutation: (videoId) =>
|
|
159
|
+
generateUpdateGQLFragment<MutationDelete{features.capabilities.videos.trailersNodeType}Args>(
|
|
160
|
+
'delete{features.capabilities.videos.trailersNodeType}',
|
|
161
|
+
{ input: { {entityCamel}Id, videoId } },
|
|
162
|
+
),
|
|
163
|
+
prefix: '{entityCamel}Trailers',
|
|
164
|
+
});
|
|
165
|
+
// End if
|
|
166
|
+
|
|
167
|
+
// If mainVideoField exists:
|
|
168
|
+
const mainVideoUpdateMutation =
|
|
169
|
+
initialData.data?.mainVideo[0] !== formData.mainVideo[0]
|
|
170
|
+
? generateUpdateGQLFragment<MutationUpdate{Entity}Args>('update{Entity}', {
|
|
171
|
+
input: {
|
|
172
|
+
id: {entityCamel}Id,
|
|
173
|
+
patch: { {mainVideoField}: formData.mainVideo[0] ?? null },
|
|
174
|
+
},
|
|
175
|
+
})
|
|
176
|
+
: '';
|
|
177
|
+
// End if
|
|
178
|
+
|
|
179
|
+
const GqlMutationDocument = gql`mutation Update{Entity}Videos {
|
|
180
|
+
// If mainVideoField exists:
|
|
181
|
+
${mainVideoUpdateMutation}
|
|
182
|
+
// End if
|
|
183
|
+
// If trailersConnection exists:
|
|
184
|
+
${trailerAssignmentMutations}
|
|
185
|
+
// End if
|
|
186
|
+
}`;
|
|
187
|
+
|
|
188
|
+
await client.mutate({ mutation: GqlMutationDocument });
|
|
189
|
+
},
|
|
190
|
+
[{entityCamel}Id],
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<Details<FormData>
|
|
195
|
+
defaultTitle="Video Management"
|
|
196
|
+
validationSchema={{entityCamel}VideoManagementSchema}
|
|
197
|
+
initialData={{
|
|
198
|
+
data: {
|
|
199
|
+
// If mainVideoField exists:
|
|
200
|
+
mainVideo: data?.{entityCamel}?.{mainVideoField} ? [data?.{entityCamel}?.{mainVideoField}] : [],
|
|
201
|
+
// End if
|
|
202
|
+
// If trailersConnection exists:
|
|
203
|
+
trailerVideos:
|
|
204
|
+
data?.{entityCamel}?.{trailersConnection}.nodes.map(
|
|
205
|
+
(trailer) => trailer.videoId,
|
|
206
|
+
) ?? [],
|
|
207
|
+
// End if
|
|
208
|
+
},
|
|
209
|
+
loading,
|
|
210
|
+
entityNotFound: data?.{entityCamel} === null,
|
|
211
|
+
error: error?.message,
|
|
212
|
+
}}
|
|
213
|
+
saveData={onSubmit}
|
|
214
|
+
>
|
|
215
|
+
<Form videoSelectField={VideoSelectField} />
|
|
216
|
+
</Details>
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const Form: React.FC<{ videoSelectField: unknown }> = ({
|
|
221
|
+
videoSelectField,
|
|
222
|
+
}) => {
|
|
223
|
+
return (
|
|
224
|
+
<>
|
|
225
|
+
// If mainVideoField exists:
|
|
226
|
+
<Field
|
|
227
|
+
name="mainVideo"
|
|
228
|
+
label="Main Video"
|
|
229
|
+
as={videoSelectField}
|
|
230
|
+
maxItems={1}
|
|
231
|
+
defaultFilterTag="MAIN"
|
|
232
|
+
title="Select Main Video"
|
|
233
|
+
/>
|
|
234
|
+
// End if
|
|
235
|
+
// If trailersConnection exists:
|
|
236
|
+
<Field
|
|
237
|
+
name="trailerVideos"
|
|
238
|
+
label="Trailer Videos"
|
|
239
|
+
as={videoSelectField}
|
|
240
|
+
defaultFilterTag="TRAILER"
|
|
241
|
+
title="Select Trailer Video(s)"
|
|
242
|
+
/>
|
|
243
|
+
// End if
|
|
244
|
+
</>
|
|
245
|
+
);
|
|
246
|
+
};
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## File 4: Quick Edit Wrapper
|
|
250
|
+
|
|
251
|
+
**Path:** `{Entity}VideoManagement/{Entity}VideoManagementQuickEdit.tsx`
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
import { QuickEditContext, QuickEditContextType } from '@axinom/mosaic-ui';
|
|
255
|
+
import React, { useContext } from 'react';
|
|
256
|
+
import { {Entity}Data } from '../{EntityPlural}Explorer/common/{EntityPlural}.types';
|
|
257
|
+
import { {Entity}VideoManagementForm } from './{Entity}VideoManagementForm';
|
|
258
|
+
|
|
259
|
+
export const {Entity}VideoManagementQuickEdit: React.FC = () => {
|
|
260
|
+
const { selectedItem } =
|
|
261
|
+
useContext<QuickEditContextType<{Entity}Data>>(QuickEditContext);
|
|
262
|
+
|
|
263
|
+
return <{Entity}VideoManagementForm {entityCamel}Id={selectedItem.id} />;
|
|
264
|
+
};
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Conditional Generation
|
|
268
|
+
|
|
269
|
+
**Generate fields based on capability detection:**
|
|
270
|
+
|
|
271
|
+
- If `features.capabilities.videos.mainVideoField` exists → include main video
|
|
272
|
+
field
|
|
273
|
+
- If `features.capabilities.videos.trailersConnection` exists → include trailers
|
|
274
|
+
array field
|
|
275
|
+
- Both, one, or neither may be present
|
|
276
|
+
|
|
277
|
+
## Notes
|
|
278
|
+
|
|
279
|
+
- Uses `VideoSelectField` from `ExtensionsContext`
|
|
280
|
+
- Main video uses `maxItems={1}` (single selection)
|
|
281
|
+
- Trailers allows multiple selections
|
|
282
|
+
- Main video updates via `update{Entity}` mutation (patch field)
|
|
283
|
+
- Trailers use array mutations (create/delete)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generate-explorer-station
|
|
3
|
+
description: |
|
|
4
|
+
Generates an Explorer station for a Mosaic entity from scratch using
|
|
5
|
+
components from the `@axinom/mosaic-ui` library and GraphQL schema
|
|
6
|
+
introspection.
|
|
7
|
+
|
|
8
|
+
This skill is SELF-CONTAINED with all conventions, naming patterns, code
|
|
9
|
+
structures, and templates defined in its `references`. The skill's templates
|
|
10
|
+
and conventions are the AUTHORITATIVE source - rely on them as the primary
|
|
11
|
+
guide for all generation decisions.
|
|
12
|
+
|
|
13
|
+
Use the `get_skill_references` to retrieve ALL reference
|
|
14
|
+
files mentioned in the steps below BEFORE starting any code generation.
|
|
15
|
+
allowed-tools:
|
|
16
|
+
- search_schema
|
|
17
|
+
- get_type_definitions
|
|
18
|
+
- get_query_fields
|
|
19
|
+
- get_mutation_fields
|
|
20
|
+
- get_subscription_fields
|
|
21
|
+
- list_skills
|
|
22
|
+
- get_skill
|
|
23
|
+
- get_skill_references
|
|
24
|
+
- execute_skill
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# Generate Explorer Station
|
|
28
|
+
|
|
29
|
+
## Input
|
|
30
|
+
|
|
31
|
+
**Entity name** (PascalCase): e.g., "Movie", "Product", "ServiceAccount"
|
|
32
|
+
|
|
33
|
+
## Output
|
|
34
|
+
|
|
35
|
+
Complete explorer station with:
|
|
36
|
+
|
|
37
|
+
- NavigationExplorer component with data provider, columns, filters
|
|
38
|
+
- Common utility files (types, columns, dataProvider, filters, inlineActions,
|
|
39
|
+
bulkActions)
|
|
40
|
+
- GraphQL operations (fragment, query, filter queries, subscription)
|
|
41
|
+
- Optional bulk edit functionality
|
|
42
|
+
- Station registration (routes, navigation, home tiles)
|
|
43
|
+
|
|
44
|
+
## Steps
|
|
45
|
+
|
|
46
|
+
### 1. Discover Project Paths → `../_shared/discovery/discover-paths.md`
|
|
47
|
+
|
|
48
|
+
Discover: schema path, apollo client, generated types path, workflows path,
|
|
49
|
+
service name
|
|
50
|
+
|
|
51
|
+
### 2. Extract Entity Features → `../_shared/discovery/extract-entity-features.md`
|
|
52
|
+
|
|
53
|
+
Extract `EntityFeatures` from schema using MCP tools (fields, associations,
|
|
54
|
+
capabilities, mutations)
|
|
55
|
+
|
|
56
|
+
### 3. Select Columns & Filters → `refs/select-columns-filters.md`
|
|
57
|
+
|
|
58
|
+
AI recommends columns/filters → present to developer and proceeds with selection
|
|
59
|
+
|
|
60
|
+
### 4. Generate GraphQL Operations → `refs/templates/graphql-operations.md`
|
|
61
|
+
|
|
62
|
+
Generate `.graphql` file with fragment, main query, filter queries, and
|
|
63
|
+
subscription
|
|
64
|
+
|
|
65
|
+
### 5. Run Codegen → `../_shared/actions/typescript-codegen.md`
|
|
66
|
+
|
|
67
|
+
Generate TypeScript types from GraphQL operations
|
|
68
|
+
|
|
69
|
+
### 6. Generate Common Files
|
|
70
|
+
|
|
71
|
+
Generate explorer utility files in order:
|
|
72
|
+
|
|
73
|
+
#### 6.1. Generate Types → `refs/templates/common/types.md`
|
|
74
|
+
|
|
75
|
+
Generate TypeScript type definitions and selection explorer props
|
|
76
|
+
|
|
77
|
+
#### 6.2. Generate Columns → `refs/templates/common/columns.md`
|
|
78
|
+
|
|
79
|
+
Generate column definitions with appropriate renderers (thumbnail, date, enum,
|
|
80
|
+
connection)
|
|
81
|
+
|
|
82
|
+
#### 6.3. Generate Data Provider → `refs/templates/common/data-provider.md`
|
|
83
|
+
|
|
84
|
+
Generate data provider with loadData and optional subscription support
|
|
85
|
+
|
|
86
|
+
#### 6.4. Generate Filters → `refs/templates/common/filters.md`
|
|
87
|
+
|
|
88
|
+
Generate filter options and transformers for PostGraphile filters
|
|
89
|
+
|
|
90
|
+
#### 6.5. Generate Inline Actions → `refs/templates/common/inline-actions.md`
|
|
91
|
+
|
|
92
|
+
Generate inline action menu generator (with TODOs for delete/open details)
|
|
93
|
+
|
|
94
|
+
#### 6.6. Generate Bulk Actions _(conditional)_ → `refs/templates/common/bulk-actions.md`
|
|
95
|
+
|
|
96
|
+
Generate bulk action hooks and actions (only if
|
|
97
|
+
`features.mutations.bulkActions === true`)
|
|
98
|
+
|
|
99
|
+
### 7. Generate Explorer Station → `refs/templates/navigation-explorer-station.md`
|
|
100
|
+
|
|
101
|
+
Generate main `{EntityPlural}.tsx` NavigationExplorer component with TODOs for
|
|
102
|
+
missing features
|
|
103
|
+
|
|
104
|
+
### 8. Generate Bulk Edit _(conditional)_ → `refs/templates/bulk-edit.md`
|
|
105
|
+
|
|
106
|
+
Generate BulkEdit configuration and component (only if
|
|
107
|
+
`features.mutations.bulkEdit === true`)
|
|
108
|
+
|
|
109
|
+
### 9. Validate TypeScript → `../_shared/actions/typescript-validation.md`
|
|
110
|
+
|
|
111
|
+
Run `tsc --noEmit` to verify all generated files compile successfully
|
|
112
|
+
|
|
113
|
+
### 10. Register Station → `refs/finalization/station-registration.md`
|
|
114
|
+
|
|
115
|
+
Create/update registrations.tsx with explorer routes, navigation items, and home
|
|
116
|
+
tiles
|
|
117
|
+
|
|
118
|
+
### 11. Print Summary → `../_shared/actions/execution-summary.md`
|
|
119
|
+
|
|
120
|
+
Display execution summary with generated files, configuration, columns, filters,
|
|
121
|
+
routes, TODOs
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: station-registration
|
|
3
|
+
input:
|
|
4
|
+
- EntityFeatures
|
|
5
|
+
- DiscoveredPaths
|
|
6
|
+
- Generated explorer files
|
|
7
|
+
output:
|
|
8
|
+
- path: '{workflowsPath}/src/Stations/{EntityPlural}/registrations.tsx'
|
|
9
|
+
description: 'Station registration with routes and navigation'
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Register Explorer
|
|
13
|
+
|
|
14
|
+
## 1. Create registrations.tsx
|
|
15
|
+
|
|
16
|
+
**File:** `{workflowsPath}/src/Stations/{EntityPlural}/registrations.tsx`
|
|
17
|
+
|
|
18
|
+
**Discovery:** Icon mapping
|
|
19
|
+
|
|
20
|
+
- Look at entity name to pick best icon from `MediaIconName` enum
|
|
21
|
+
- If no perfect match, pick the best match and add a `TODO`
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { PiletApi } from '@axinom/mosaic-portal';
|
|
25
|
+
import React from 'react';
|
|
26
|
+
import { Extensions, ExtensionsContext } from '{discoveredPaths.extensionsContextPath}';
|
|
27
|
+
import { MediaIconName, MediaIcons } from '../../MediaIcons';
|
|
28
|
+
import { {EntityPlural} } from './{EntityPlural}Explorer/{EntityPlural}';
|
|
29
|
+
|
|
30
|
+
export function register(app: PiletApi, extensions: Extensions): void {
|
|
31
|
+
const {entityCamelPlural}Nav = {
|
|
32
|
+
name: '{entityCamelPlural}',
|
|
33
|
+
path: '/{kebabCasePlural}',
|
|
34
|
+
label: '{EntityPlural}',
|
|
35
|
+
// TODO: Update icon to match entity type
|
|
36
|
+
icon: <MediaIcons icon={MediaIconName.Movie} />,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Home tile
|
|
40
|
+
app.registerTile(
|
|
41
|
+
{
|
|
42
|
+
...{entityCamelPlural}Nav,
|
|
43
|
+
kind: 'home',
|
|
44
|
+
type: 'large',
|
|
45
|
+
},
|
|
46
|
+
false,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Navigation item
|
|
50
|
+
app.registerNavigationItem({
|
|
51
|
+
...{entityCamelPlural}Nav,
|
|
52
|
+
categoryName: 'Content',
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Explorer route
|
|
56
|
+
app.registerPage(
|
|
57
|
+
'/{kebabCasePlural}',
|
|
58
|
+
() => (
|
|
59
|
+
<ExtensionsContext.Provider value={extensions}>
|
|
60
|
+
<{EntityPlural} />
|
|
61
|
+
</ExtensionsContext.Provider>
|
|
62
|
+
),
|
|
63
|
+
{
|
|
64
|
+
breadcrumb: () => '{EntityPlural}',
|
|
65
|
+
permissions: { '{features.service.serviceId}': {features.service.permissions} },
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// If features.mutations.create === true:
|
|
70
|
+
// TODO: Add create route once Create station exists
|
|
71
|
+
// app.registerPage('/{kebabCasePlural}/create', {Entity}Create, {
|
|
72
|
+
// breadcrumb: () => 'New {Entity}',
|
|
73
|
+
// permissions: { '{features.service.serviceId}': {features.service.permissions} },
|
|
74
|
+
// });
|
|
75
|
+
// End if
|
|
76
|
+
|
|
77
|
+
// TODO: Add details route once Details station exists
|
|
78
|
+
// app.registerPage('/{kebabCasePlural}/:{entityCamel}Id', {Entity}Details, {
|
|
79
|
+
// breadcrumb: {Entity}DetailsCrumb,
|
|
80
|
+
// permissions: { '{features.service.serviceId}': {features.service.permissions} },
|
|
81
|
+
// });
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 2. Register in main index
|
|
86
|
+
|
|
87
|
+
**File:** `{workflowsPath}/src/index.tsx`
|
|
88
|
+
|
|
89
|
+
Add import:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { register as register{EntityPlural} } from './Stations/{EntityPlural}/registrations';
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Add registration call in `setup()`:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
register{EntityPlural}(app, extensions);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Example (Product)
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// registrations.tsx
|
|
105
|
+
import { PiletApi } from '@axinom/mosaic-portal';
|
|
106
|
+
import React from 'react';
|
|
107
|
+
import {
|
|
108
|
+
Extensions,
|
|
109
|
+
ExtensionsContext,
|
|
110
|
+
} from '{discoveredPaths.extensionsContextPath}';
|
|
111
|
+
import { MediaIconName, MediaIcons } from '../../MediaIcons';
|
|
112
|
+
import { Products } from './ProductsExplorer/Products';
|
|
113
|
+
|
|
114
|
+
export function register(app: PiletApi, extensions: Extensions): void {
|
|
115
|
+
const productsNav = {
|
|
116
|
+
name: 'products',
|
|
117
|
+
path: '/products',
|
|
118
|
+
label: 'Products',
|
|
119
|
+
// TODO: Update icon to match entity type
|
|
120
|
+
icon: <MediaIcons icon={MediaIconName.Movie} />,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
app.registerTile({ ...productsNav, kind: 'home', type: 'large' }, false);
|
|
124
|
+
|
|
125
|
+
app.registerNavigationItem({ ...productsNav, categoryName: 'Content' });
|
|
126
|
+
|
|
127
|
+
app.registerPage(
|
|
128
|
+
'/products',
|
|
129
|
+
() => (
|
|
130
|
+
<ExtensionsContext.Provider value={extensions}>
|
|
131
|
+
<Products />
|
|
132
|
+
</ExtensionsContext.Provider>
|
|
133
|
+
),
|
|
134
|
+
{
|
|
135
|
+
breadcrumb: () => 'Products',
|
|
136
|
+
permissions: {
|
|
137
|
+
'product-service': ['ADMIN', 'PRODUCTS_EDIT', 'PRODUCTS_VIEW'],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// TODO: Add create route once Create station exists
|
|
143
|
+
// TODO: Add details route once Details station exists
|
|
144
|
+
}
|
|
145
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: select-columns-filters
|
|
3
|
+
input:
|
|
4
|
+
- EntityFeatures
|
|
5
|
+
output:
|
|
6
|
+
- selectedColumns array
|
|
7
|
+
- selectedFilters array
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Select Columns & Filters
|
|
11
|
+
|
|
12
|
+
## Column Selection Logic
|
|
13
|
+
|
|
14
|
+
### Always Include
|
|
15
|
+
|
|
16
|
+
1. **Thumbnail** (if `features.capabilities.images !== undefined`) - first
|
|
17
|
+
column
|
|
18
|
+
2. **Title/Name field** - primary human identifier
|
|
19
|
+
3. **ID** - primary technical identifier
|
|
20
|
+
|
|
21
|
+
### AI Judgment
|
|
22
|
+
|
|
23
|
+
Analyze fields by priority:
|
|
24
|
+
|
|
25
|
+
- **High:** title, name, ID, status fields, tag-like (i.e. `Tags` for easy
|
|
26
|
+
filtering)
|
|
27
|
+
- **Medium:** description fields, M:N associations (i.e. `Category`, `Genre` for
|
|
28
|
+
easy filtering)
|
|
29
|
+
- **Low:** other fields, audit fields
|
|
30
|
+
|
|
31
|
+
### Target
|
|
32
|
+
|
|
33
|
+
Enough columns to distinguish the important metadata of the entity
|
|
34
|
+
|
|
35
|
+
### Order
|
|
36
|
+
|
|
37
|
+
1. Thumbnail (if `features.capabilities.images !== undefined`)
|
|
38
|
+
2. Title field
|
|
39
|
+
3. Important fields
|
|
40
|
+
4. Associations
|
|
41
|
+
5. Date fields
|
|
42
|
+
6. Updated date (last)
|
|
43
|
+
|
|
44
|
+
## Filter Selection Logic
|
|
45
|
+
|
|
46
|
+
### Always Include
|
|
47
|
+
|
|
48
|
+
- **Title field** - text search
|
|
49
|
+
- **ID** - numeric filter
|
|
50
|
+
|
|
51
|
+
### AI Judgment
|
|
52
|
+
|
|
53
|
+
- Identity fields
|
|
54
|
+
- Text fields that users search by
|
|
55
|
+
- Enum fields (status, type, etc.)
|
|
56
|
+
- Date fields (released, created, updated)
|
|
57
|
+
- Tag-Like fields (tags)
|
|
58
|
+
- Associations (genres, categories)
|
|
59
|
+
- Audit fields (createdDate, updatedDate)
|
|
60
|
+
|
|
61
|
+
### Target
|
|
62
|
+
|
|
63
|
+
Enough filters to search by important fields of the entity
|