@trackunit/iris-app 1.12.7 → 1.12.10

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 (29) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/package.json +3 -3
  3. package/src/generators/ai-agent-sync/README.md +5 -2
  4. package/src/generators/ai-agent-sync/generator.d.ts +1 -1
  5. package/src/generators/ai-agent-sync/generator.js +29 -4
  6. package/src/generators/ai-agent-sync/generator.js.map +1 -1
  7. package/src/generators/preset/files/.agents/skills/browser-testing/SKILL.md +193 -0
  8. package/src/generators/preset/files/.agents/skills/create-app/SKILL.md +191 -0
  9. package/src/generators/preset/files/.agents/skills/customfields/SKILL.md +239 -0
  10. package/src/generators/preset/files/.agents/skills/graphql/SKILL.md +147 -0
  11. package/src/generators/preset/files/.agents/skills/graphql-timeseries/SKILL.md +193 -0
  12. package/src/generators/preset/files/.agents/skills/irisx-app-sdk/SKILL.md +116 -0
  13. package/src/generators/preset/files/.agents/skills/react-core-hooks/SKILL.md +215 -0
  14. package/src/generators/preset/files/.agents/skills/tables-and-sorting/SKILL.md +122 -0
  15. package/src/generators/preset/files/.agents/skills/widget-extensions/SKILL.md +245 -0
  16. package/src/generators/preset/files/.cursor/mcp.json +4 -0
  17. package/src/generators/preset/root-files/AGENTS.md +43 -0
  18. package/src/generators/preset/root-files/CLAUDE.md +17 -0
  19. package/src/generators/preset/files/.cursor/commands/create-app.md +0 -226
  20. package/src/generators/preset/files/.cursor/rules/browser-irisx-development.mdc +0 -246
  21. package/src/generators/preset/files/.cursor/rules/graphql-timeseries.md +0 -260
  22. package/src/generators/preset/files/.cursor/rules/irisx-app-sdk-customfields.md +0 -305
  23. package/src/generators/preset/files/.cursor/rules/irisx-app-sdk-graphql.md +0 -30
  24. package/src/generators/preset/files/.cursor/rules/irisx-app-sdk.mdc +0 -82
  25. package/src/generators/preset/files/.cursor/rules/react-core-hooks.md +0 -155
  26. package/src/generators/preset/files/.cursor/rules/rules-index.mdc +0 -10
  27. package/src/generators/preset/files/.cursor/rules/structured-development.mdc +0 -86
  28. package/src/generators/preset/files/.cursor/rules/tables-and-sorting.mdc +0 -126
  29. package/src/generators/preset/files/.cursor/rules/widget-extensions.md +0 -323
@@ -0,0 +1,215 @@
1
+ ---
2
+ name: react-core-hooks
3
+ description: Use when needing asset/site/customer runtime data, user context, navigation, toasts, or confirmation dialogs in IrisX Apps.
4
+ ---
5
+
6
+ # React Core Hooks
7
+
8
+ ## Overview
9
+
10
+ Reference for `@trackunit/react-core-hooks` - runtime context, navigation, user information, and UI interaction hooks for IrisX Apps.
11
+
12
+ ## When to Use
13
+
14
+ - Accessing current asset/site/customer context
15
+ - Getting user information or preferences
16
+ - Navigating within Trackunit Manager
17
+ - Showing toasts or confirmation dialogs
18
+ - Managing widget configuration
19
+
20
+ ## Runtime Hooks
21
+
22
+ ### useAssetRuntime
23
+
24
+ Access asset information in asset-scoped extensions.
25
+
26
+ ```typescript
27
+ const { assetInfo, loading, error } = useAssetRuntime();
28
+ // assetInfo.assetId, assetInfo.name, etc.
29
+ ```
30
+
31
+ ### useCustomerRuntime
32
+
33
+ Access customer information.
34
+
35
+ ```typescript
36
+ const { customerInfo, loading, error } = useCustomerRuntime();
37
+ // customerInfo.customerId, customerInfo.name, etc.
38
+ ```
39
+
40
+ ### useEventRuntime
41
+
42
+ Access event information in event-scoped extensions.
43
+
44
+ ```typescript
45
+ const { eventInfo, loading, error } = useEventRuntime();
46
+ // eventInfo.eventId, eventInfo.type, etc.
47
+ ```
48
+
49
+ ### useSiteRuntime
50
+
51
+ Access site information in site-scoped extensions.
52
+
53
+ ```typescript
54
+ const { siteInfo, loading, error } = useSiteRuntime();
55
+ // siteInfo.siteId, siteInfo.name, etc.
56
+ ```
57
+
58
+ ### useIrisAppName
59
+
60
+ Get current app name.
61
+
62
+ ```typescript
63
+ const { appName, loading, error } = useIrisAppName();
64
+ ```
65
+
66
+ ### useIrisAppId
67
+
68
+ Get current app ID.
69
+
70
+ ```typescript
71
+ const { irisAppId, loading, error } = useIrisAppId();
72
+ ```
73
+
74
+ ## Navigation & User Context
75
+
76
+ ### useNavigateInHost
77
+
78
+ Navigate within Trackunit Manager.
79
+
80
+ ```typescript
81
+ const { gotoAssetHome, gotoSiteHome } = useNavigateInHost();
82
+ gotoAssetHome(assetId);
83
+ gotoSiteHome(siteId);
84
+ ```
85
+
86
+ ### useHasAccessTo
87
+
88
+ Check user access to pages or asset-scoped pages.
89
+
90
+ ```typescript
91
+ const hasAccess = useHasAccessTo({ page: "sites" });
92
+ const hasAssetAccess = useHasAccessTo({ assetId, page: "asset-home" });
93
+ ```
94
+
95
+ ### useCurrentUser
96
+
97
+ Access current user information. Returns flat properties directly.
98
+
99
+ ```typescript
100
+ const { email, name, accountId, userId } = useCurrentUser();
101
+ ```
102
+
103
+ ### useCurrentUserLanguage
104
+
105
+ Get/set user language preference.
106
+
107
+ ```typescript
108
+ const { language, setLanguage } = useCurrentUserLanguage();
109
+ ```
110
+
111
+ ### useCurrentUserTimeZonePreference
112
+
113
+ Get/set user timezone preference.
114
+
115
+ ```typescript
116
+ const { timeZonePreference, setTimeZonePreference } = useCurrentUserTimeZonePreference();
117
+ ```
118
+
119
+ ### useToken
120
+
121
+ Access authentication token.
122
+
123
+ ```typescript
124
+ const { token } = useToken();
125
+ ```
126
+
127
+ ## UI & Interaction
128
+
129
+ ### useToast
130
+
131
+ Show toast notifications.
132
+
133
+ ```typescript
134
+ const { addToast } = useToast();
135
+ addToast({ intent: "success", title: "Operation completed" });
136
+ ```
137
+
138
+ ### useConfirmationDialog
139
+
140
+ Show confirmation dialogs.
141
+
142
+ ```typescript
143
+ const { confirm } = useConfirmationDialog();
144
+ const confirmed = await confirm({
145
+ title: "Confirm Action",
146
+ message: "Are you sure?",
147
+ primaryActionType: "danger",
148
+ primaryActionLabel: "Delete",
149
+ secondaryActionLabel: "Cancel"
150
+ });
151
+ ```
152
+
153
+ ### useModalDialogContext
154
+
155
+ Manage modal dialogs.
156
+
157
+ ```typescript
158
+ const { openModal, closeModal } = useModalDialogContext();
159
+ ```
160
+
161
+ ### useErrorHandler
162
+
163
+ Handle errors consistently.
164
+
165
+ ```typescript
166
+ const { logError } = useErrorHandler();
167
+ logError(new Error("Something went wrong"));
168
+ ```
169
+
170
+ ## Data & State Management
171
+
172
+ ### useAssetSorting
173
+
174
+ Asset sorting functionality.
175
+
176
+ ```typescript
177
+ const { sortingState, setSortBy } = useAssetSorting();
178
+ // sortingState.sortBy, sortingState.order
179
+ ```
180
+
181
+ ### useFilterBarContext
182
+
183
+ Filter bar state management. Provides domain-specific filter values.
184
+
185
+ ```typescript
186
+ const { assetsFilterBarValues, customersFilterBarValues } = useFilterBarContext();
187
+ ```
188
+
189
+ ### useTimeRange
190
+
191
+ Time range context (read-only).
192
+
193
+ ```typescript
194
+ const { timeRange, temporalPeriod } = useTimeRange();
195
+ ```
196
+
197
+ ## Widget Configuration
198
+
199
+ ### useWidgetConfig
200
+
201
+ Access widget configuration, filters, time range, and edit mode state.
202
+
203
+ ```typescript
204
+ const {
205
+ data, setData, dataVersion, loadingData,
206
+ title, setTitle,
207
+ filters, timeRange,
208
+ isEditMode, openEditMode, closeEditMode,
209
+ pollInterval, setLoadingState
210
+ } = useWidgetConfig();
211
+ ```
212
+
213
+ ## Note
214
+
215
+ All hooks require their respective providers to be set up in your app's component tree. IrisX App SDK handles this automatically for extensions.
@@ -0,0 +1,122 @@
1
+ ---
2
+ name: tables-and-sorting
3
+ description: Use when building data tables with filtering, sorting, pagination, or displaying lists of assets, users, or other entities.
4
+ ---
5
+
6
+ # Tables with Filtering & Sorting
7
+
8
+ ## Overview
9
+
10
+ Create interactive data tables using `@trackunit/react-table`, `@trackunit/filters-filter-bar`, and server-side pagination with GraphQL. Always use `useMemo` and `manualSorting: true` for large datasets.
11
+
12
+ ## When to Use
13
+
14
+ - Building asset management interfaces
15
+ - Creating user administration tables
16
+ - Developing dashboard data grids
17
+ - Implementing reporting interfaces with large datasets
18
+
19
+ **Not for:** Simple lists without filtering (use standard components).
20
+
21
+ ## Required Imports
22
+
23
+ ```typescript
24
+ import { Table, useTable } from '@trackunit/react-table';
25
+ import { TextCell, DateTimeCell } from '@trackunit/react-table-base-components';
26
+ import { FilterBar, useFilterBar } from '@trackunit/filters-filter-bar';
27
+ import { useAssetQueryFilters } from '@trackunit/filters-filter-bar-asset-graphql';
28
+ import { usePaginationQuery } from '@trackunit/react-graphql-hooks';
29
+ import { createColumnHelper, SortingState } from '@tanstack/react-table';
30
+ import { useBrandFilter, useModelsFilter } from '@trackunit/filters-asset-filter-definitions';
31
+ ```
32
+
33
+ ## Valid Implementation
34
+
35
+ ```typescript
36
+ type Asset = { id: string; name: string; createdAt: string };
37
+ const columnHelper = createColumnHelper<Asset>();
38
+
39
+ const AssetTable = () => {
40
+ // Setup filters with proper naming
41
+ const filterBar = useFilterBar({
42
+ name: "assetFilters",
43
+ filterBarDefinition: {
44
+ brand: useBrandFilter(),
45
+ model: useModelsFilter()
46
+ }
47
+ });
48
+
49
+ // Convert filter values to GraphQL variables
50
+ const filterVariables = useAssetQueryFilters(filterBar.filterBarConfig.values);
51
+
52
+ // Server-side sorting state
53
+ const [sorting, setSorting] = useState<SortingState>([]);
54
+
55
+ // GraphQL query with filters
56
+ const { data, loading, pagination } = usePaginationQuery(GetAssetsDocument, {
57
+ variables: { first: 50, ...filterVariables },
58
+ pageSize: 50,
59
+ });
60
+
61
+ // Transform data with useMemo
62
+ const assets = useMemo(() =>
63
+ data?.assets.edges.map(edge => ({ ...edge.node, id: edge.node.id })) ?? [],
64
+ [data]
65
+ );
66
+
67
+ // Define columns with proper cell components
68
+ const columns = useMemo(() => [
69
+ columnHelper.accessor('name', {
70
+ cell: ({ row: { original } }) => <TextCell content={original.name} />,
71
+ header: "Asset Name",
72
+ enableSorting: true,
73
+ }),
74
+ columnHelper.accessor('createdAt', {
75
+ cell: ({ row: { original } }) => <DateTimeCell date={new Date(original.createdAt)} />,
76
+ header: "Created",
77
+ enableSorting: true,
78
+ }),
79
+ ], []);
80
+
81
+ // Setup table with manual sorting
82
+ const table = useTable({
83
+ data: assets,
84
+ columns,
85
+ enableSorting: true,
86
+ manualSorting: true,
87
+ state: { sorting },
88
+ onSortingChange: setSorting,
89
+ });
90
+
91
+ return (
92
+ <>
93
+ <FilterBar {...filterBar} />
94
+ <Table
95
+ {...table}
96
+ loading={loading}
97
+ pagination={pagination}
98
+ onRowClick={(row) => navigateToAsset(row.original.id)}
99
+ />
100
+ </>
101
+ );
102
+ };
103
+ ```
104
+
105
+ ## Common Mistakes
106
+
107
+ | Mistake | Fix |
108
+ |---------|-----|
109
+ | Missing `useMemo` for columns/data | Wrap in `useMemo` to prevent re-renders |
110
+ | `manualSorting: false` on large datasets | Use `manualSorting: true` for server-side sorting |
111
+ | Missing filter variables in query | Spread `filterVariables` into GraphQL variables |
112
+ | Using `getValue()` in cells | Use Trackunit cell components (`TextCell`, `DateTimeCell`) |
113
+ | No `onRowClick` handler | Add navigation or detail view on row click |
114
+
115
+ ## Key Requirements
116
+
117
+ - **Always use `useMemo`** for data transformation and column definitions
118
+ - **Enable `manualSorting: true`** for datasets > 1000 items
119
+ - **Integrate filter state** with GraphQL variables using spread operator
120
+ - **Use Trackunit cell components** (`TextCell`, `DateTimeCell`, `PlainDateCell`) for consistency
121
+ - **Implement `onRowClick`** for navigation or detail views
122
+ - **Sync state properly** between filters, sorting, and GraphQL queries
@@ -0,0 +1,245 @@
1
+ ---
2
+ name: widget-extensions
3
+ description: Use when creating dashboard widgets, KPI displays, configurable widgets with edit dialogs, or data visualizations for dashboards.
4
+ ---
5
+
6
+ # Widget Extensions
7
+
8
+ ## Overview
9
+
10
+ Widget extensions are React components that integrate into Trackunit Manager dashboards. Use `useWidgetConfig` for data, filters, and edit mode. Supports KPI, CHART, LIST, MAP, and OTHER types.
11
+
12
+ ## When to Use
13
+
14
+ - Creating dashboard KPIs or metrics
15
+ - Building data visualization widgets
16
+ - Adding configurable widgets with edit dialogs
17
+ - Displaying filtered/time-ranged data in dashboards
18
+
19
+ **Not for:** Full-page extensions (use `FLEET_EXTENSION` or `ASSET_HOME_EXTENSION`).
20
+
21
+ ## Creating Widget Extensions
22
+
23
+ ```bash
24
+ npx nx g @trackunit/iris-app:extend --name=[my-widget-name] --app=[app-name] --directory=[feature] --type=WIDGET_EXTENSION
25
+ ```
26
+
27
+ This creates:
28
+ - `extension-manifest.ts` - Widget configuration and metadata
29
+ - `src/app.tsx` - Main widget component (handles both display and edit mode)
30
+
31
+ ## Widget Manifest
32
+
33
+ ```typescript
34
+ import { WidgetExtensionManifest } from '@trackunit/iris-app-api';
35
+
36
+ const widgetExtensionManifest: WidgetExtensionManifest = {
37
+ id: "my-widget-id",
38
+ type: "WIDGET_EXTENSION",
39
+ sourceRoot: "libs/[feature]/[widget-name]/src",
40
+
41
+ widgetType: "CHART", // CHART | KPI | LIST | MAP | OTHER
42
+
43
+ size: {
44
+ default: { width: 2, height: 2 }, // 1-6 grid units
45
+ allowFullWidth: false,
46
+ },
47
+
48
+ header: {
49
+ name: "My Widget",
50
+ image: { name: "ChartBar" }, // IconByName or IconByPath
51
+ hasEditDialog: true,
52
+ },
53
+
54
+ footer: {
55
+ linkDescription: "View details",
56
+ link: "/my-app/details",
57
+ poweredByImage: { path: "/assets/logo.svg" },
58
+ },
59
+
60
+ preview: {
61
+ description: "Displays chart data for analysis",
62
+ },
63
+
64
+ supportedLocations: ["MY_HOME", "PLAYGROUND"],
65
+
66
+ supportedFilters: {
67
+ TIME_RANGE: { include: ["ALL"] },
68
+ ASSETS: { include: ["assetType", "brands", "models"] },
69
+ CUSTOMERS: { include: ["customerType", "criticality"] },
70
+ SITES: { include: ["siteType", "location"] },
71
+ },
72
+ };
73
+ ```
74
+
75
+ ## Widget Types
76
+
77
+ | Type | Use Case |
78
+ |------|----------|
79
+ | `KPI` | Single metric displays (typically 1x1) |
80
+ | `CHART` | Data visualization (charts, graphs) |
81
+ | `LIST` | Tabular or list-based data |
82
+ | `MAP` | Geographic or spatial data |
83
+ | `OTHER` | Custom or specialized widgets |
84
+
85
+ ## Supported Locations
86
+
87
+ - `MY_HOME` - User's personal dashboard
88
+ - `SITE_HOME` - Site-specific dashboards
89
+ - `PLAYGROUND` - Widget testing playground
90
+
91
+ ## Widget Development
92
+
93
+ ### Basic Widget Component
94
+
95
+ ```typescript
96
+ import { useWidgetConfig } from '@trackunit/react-core-hooks';
97
+
98
+ export const App = () => {
99
+ const {
100
+ data, setData, dataVersion, loadingData,
101
+ title, setTitle,
102
+ filters, timeRange,
103
+ isEditMode, openEditMode, closeEditMode,
104
+ pollInterval, setLoadingState
105
+ } = useWidgetConfig();
106
+
107
+ if (isEditMode) {
108
+ return <EditDialog />;
109
+ }
110
+
111
+ return (
112
+ <WidgetBody>
113
+ <h2>{title}</h2>
114
+ {/* Widget content */}
115
+ </WidgetBody>
116
+ );
117
+ };
118
+ ```
119
+
120
+ ### useWidgetConfig Hook
121
+
122
+ **Data Management:**
123
+ - `data: Record<string, unknown> | null` - Widget configuration data
124
+ - `setData(data, version)` - Update widget data
125
+ - `dataVersion: number | null` - Current data version for migrations
126
+ - `loadingData: boolean` - Whether widget data is loading
127
+
128
+ **Title Management:**
129
+ - `title: string` - Widget title
130
+ - `setTitle(title)` - Update widget title
131
+
132
+ **Context:**
133
+ - `filters` - Applied filters (assets, customers, sites)
134
+ - `timeRange` - Selected time range (startMsInEpoch, endMsInEpoch)
135
+ - `isEditMode: boolean` - Whether widget is in edit mode
136
+
137
+ **Edit Mode:**
138
+ - `openEditMode()` - Enter edit mode
139
+ - `closeEditMode()` - Exit edit mode
140
+
141
+ ### Widget with Edit Dialog
142
+
143
+ ```typescript
144
+ import { useWidgetConfig } from '@trackunit/react-core-hooks';
145
+ import { WidgetEditBody } from '@trackunit/react-widgets';
146
+
147
+ export const App = () => {
148
+ const { isEditMode, data, closeEditMode } = useWidgetConfig();
149
+
150
+ if (isEditMode) {
151
+ return (
152
+ <WidgetEditBody
153
+ onSave={async () => {
154
+ await closeEditMode({ newData: { data: { demo: "yourDataHere"}, dataVersion: 1 }});
155
+ }}
156
+ onCancel={closeEditMode}
157
+ initialData={data}
158
+ />
159
+ );
160
+ }
161
+
162
+ return <MainWidgetContent />;
163
+ };
164
+ ```
165
+
166
+ ## Filter Support
167
+
168
+ ### Using Filters
169
+
170
+ ```typescript
171
+ const { filters, timeRange } = useWidgetConfig();
172
+
173
+ const selectedAssets = filters.assetsFilterBarValues?.assetType;
174
+ const selectedCustomers = filters.customersFilterBarValues?.customerType;
175
+ const selectedSites = filters.sitesFilterBarValues?.siteType;
176
+
177
+ const startTime = timeRange?.startMsInEpoch;
178
+ const endTime = timeRange?.endMsInEpoch;
179
+ ```
180
+
181
+ ## KPI Widget Example
182
+
183
+ ```typescript
184
+ import { useWidgetConfig } from '@trackunit/react-core-hooks';
185
+ import { useQuery } from '@trackunit/react-graphql-hooks';
186
+
187
+ export const App = () => {
188
+ const { filters, timeRange, pollInterval, setLoadingState } = useWidgetConfig();
189
+
190
+ const { data: kpiData, loading } = useQuery(MY_KPI_QUERY, {
191
+ variables: {
192
+ filters: {
193
+ assetIds: filters.assetsFilterBarValues?.assetIds,
194
+ timeRange: {
195
+ start: timeRange?.startMsInEpoch,
196
+ end: timeRange?.endMsInEpoch,
197
+ },
198
+ },
199
+ },
200
+ pollInterval
201
+ });
202
+
203
+ useEffect(() => {
204
+ void setLoadingState({ hasData: !!kpiData, isLoading: loading });
205
+ }, [kpiData, loading, setLoadingState]);
206
+
207
+ return (
208
+ <div className="kpi-widget">
209
+ <div className="kpi-value">{kpiData?.totalCount}</div>
210
+ <div className="kpi-label">Total Assets</div>
211
+ </div>
212
+ );
213
+ };
214
+ ```
215
+
216
+ ## Best Practices
217
+
218
+ ### Widget Design
219
+ 1. **Responsive Layout** - Design for different grid sizes
220
+ 2. **Loading States** - Show loading indicators for async operations
221
+ 3. **Error Handling** - Gracefully handle data loading errors
222
+ 4. **Empty States** - Provide meaningful empty state messages
223
+
224
+ ### Data Management
225
+ 1. **Use Real Data** - Always fetch from Trackunit GraphQL API
226
+ 2. **Respect Filters** - Apply user-selected filters to data queries
227
+ 3. **Efficient Updates** - Only update widget data when necessary
228
+ 4. **Version Control** - Use `dataVersion` for configuration migrations
229
+
230
+ ### Configuration
231
+ 1. **Meaningful Defaults** - Provide sensible default configurations
232
+ 2. **Validation** - Validate user input in edit dialogs
233
+ 3. **Persistence** - Use `setData()` to save configuration changes
234
+ 4. **User Experience** - Keep configuration UI simple and intuitive
235
+
236
+ ## Common Mistakes
237
+
238
+ | Mistake | Fix |
239
+ |---------|-----|
240
+ | Using generic widget libraries | Use Trackunit IrisX App SDK patterns |
241
+ | Not handling `isEditMode` | Check `isEditMode` and render edit dialog |
242
+ | Missing `setLoadingState` call | Call `setLoadingState` to show loading indicators |
243
+ | Ignoring `filters` from context | Apply filters to data queries |
244
+ | Not using `dataVersion` | Increment version when changing data schema |
245
+ | Missing empty/error states | Handle no data and error conditions gracefully |
@@ -1,5 +1,9 @@
1
1
  {
2
2
  "mcpServers": {
3
+ "chrome-devtools": {
4
+ "command": "npx",
5
+ "args": ["-y", "chrome-devtools-mcp@latest"]
6
+ },
3
7
  "nx-mcp": {
4
8
  "command": "npx",
5
9
  "args": ["-y", "nx-mcp@latest"]
@@ -0,0 +1,43 @@
1
+ # IrisX App SDK Workspace
2
+
3
+ This is an IrisX App workspace. Follow these rules for all development.
4
+
5
+ ## Building Real Applications
6
+
7
+ You are building real applications that work with actual Trackunit data.
8
+
9
+ - Never mock data unless explicitly requested by the user
10
+ - Always use Trackunit's GraphQL API to fetch real data
11
+ - If data is not available via GraphQL, ask the user how they want to obtain the data
12
+ - Only use mock data if the user explicitly requests it for testing purposes
13
+
14
+ When building any feature:
15
+ 1. Identify what data you need
16
+ 2. Check if it's available via Trackunit's GraphQL API
17
+ 3. If available, use the GraphQL API
18
+ 4. If not available, ask the user
19
+
20
+ ## IrisX App Commands
21
+
22
+ **ALWAYS use these specific commands** (never generic React/Node.js commands):
23
+
24
+ - **Create new app:** `npx nx generate @trackunit/iris-app:create [name-of-your-app]`
25
+ - **Create new extension:** `npx nx g @trackunit/iris-app:extend --name=[my-extension-name] --app=[app-name] --directory=[feature] --type=[extension-type]`
26
+ - **Run app:** `npx nx run [name-of-your-app]:serve`
27
+
28
+ Available extension types:
29
+ - `ASSET_HOME_EXTENSION` - Tabs in Asset Home screen
30
+ - `SITE_HOME_EXTENSION` - Tabs in Site Home screen
31
+ - `FLEET_EXTENSION` - Menu items in Main Menu
32
+ - `REPORT_EXTENSION` - Reports in Reports screen
33
+ - `WIDGET_EXTENSION` - Dashboard widgets
34
+ - `IRIS_APP_SETTINGS_EXTENSION` - Configuration in App library
35
+ - `ADMIN_EXTENSION` - Admin UI tabs (admin-only)
36
+ - `CUSTOMER_HOME_EXTENSION` - UI within Customer Home
37
+ - `ASSET_EVENTS_ACTIONS_EXTENSION` - UI in Asset Home Events
38
+ - `LIFECYCLE_EXTENSION` - Handles app install/uninstall events
39
+ - `SERVERLESS_FUNCTION_EXTENSION` - Serverless backend functions
40
+
41
+ ## Quality Assurance
42
+
43
+ Before completing any task: Always check for and fix all ESLint and TypeScript errors in modified files.
@@ -0,0 +1,17 @@
1
+ @AGENTS.md
2
+
3
+ # IrisX App Workspace
4
+
5
+ This is an IrisX App project generated by the Trackunit SDK. Follow AGENTS.md for all development rules.
6
+
7
+ ## Skills
8
+
9
+ SDK-specific skills are located in `.agents/skills/`. When a skill is referenced in AGENTS.md, read it from `.agents/skills/[skill-name]/SKILL.md`.
10
+
11
+ If skills from `.agents/skills/` are not appearing in your available skills list, suggest the user symlink them for automatic discovery:
12
+
13
+ ```bash
14
+ ln -s .agents/skills .claude/skills
15
+ ```
16
+
17
+ Note: If the user already has a `.claude/skills` folder, they should symlink individual skills or the contents instead.