@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,239 @@
1
+ ---
2
+ name: customfields
3
+ description: Use when storing app data on entities, persisting user input, defining custom fields in manifest, or reading/writing entity-specific data.
4
+ ---
5
+
6
+ # CustomFields
7
+
8
+ ## Overview
9
+
10
+ CustomFields extend the Trackunit data model by storing additional information on entities (assets, accounts, groups, sites). This is the primary mechanism for persisting IrisX app data.
11
+
12
+ ## When to Use
13
+
14
+ - Storing user input or preferences
15
+ - Adding metadata to assets/accounts/groups/sites
16
+ - Persisting app-specific configuration
17
+ - Creating custom KPIs or fleet attributes
18
+
19
+ **Not for:** External databases or storage solutions (use CustomFields instead).
20
+
21
+ ## Defining Custom Fields
22
+
23
+ ### Manifest Configuration
24
+
25
+ ```typescript
26
+ customFieldDefinitions: [
27
+ {
28
+ type: 'STRING',
29
+ entityType: 'ASSET',
30
+ key: 'myCustomField',
31
+ translations: [{
32
+ language: 'en',
33
+ title: 'My Custom Field',
34
+ description: 'Description of what this field stores',
35
+ }],
36
+ uiEditable: true,
37
+ uiVisible: true,
38
+ scopeType: 'ACCOUNT' // or 'ACCOUNT_WRITE_GLOBAL_READ' or 'GLOBAL'
39
+ }
40
+ ]
41
+ ```
42
+
43
+ ### Required Properties
44
+
45
+ | Property | Description |
46
+ |----------|-------------|
47
+ | `type` | Field data type |
48
+ | `entityType` | `ACCOUNT`, `ASSET`, `GROUP`, or `SITE` |
49
+ | `key` | Programmatic identifier (immutable after creation) |
50
+ | `translations` | UI labels and descriptions |
51
+ | `scopeType` | Data access scope |
52
+
53
+ ### Optional Properties
54
+
55
+ | Property | Default | Description |
56
+ |----------|---------|-------------|
57
+ | `uiEditable` | `true` | Can be edited in Manager UI |
58
+ | `uiVisible` | `true` | Visible in Manager UI |
59
+
60
+ ## Supported Field Types
61
+
62
+ | Type | Description | Extra Properties |
63
+ |------|-------------|------------------|
64
+ | `BOOLEAN` | True/false values | - |
65
+ | `DATE` | ISO8601 formatted dates | - |
66
+ | `DROPDOWN` | Predefined option lists | `allValues`, `multiSelect`, `valueReplacements` |
67
+ | `EMAIL` | Email addresses with mailto links | - |
68
+ | `NUMBER` | Numeric values | `minimum`, `maximum`, `isInteger`, `unitSi`, `unitUs` |
69
+ | `PHONE_NUMBER` | E.164 formatted phone numbers | - |
70
+ | `STRING` | Free text | `minimumLength`, `maximumLength`, `pattern` |
71
+ | `STRING_LIST` | Array of strings | - |
72
+ | `WEB_ADDRESS` | URLs as clickable links | - |
73
+ | `JSON` | JSON objects | - |
74
+ | `FILE` | File attachments | - |
75
+ | `MONETARY` | Currency values (ISO 4217) | `currency` |
76
+
77
+ ## Example Field Definitions
78
+
79
+ ### String with Validation
80
+
81
+ ```typescript
82
+ {
83
+ type: 'STRING',
84
+ entityType: 'ASSET',
85
+ key: 'serialNumber',
86
+ translations: [{ language: 'en', title: 'Serial Number', description: 'Asset serial number' }],
87
+ minimumLength: 5,
88
+ maximumLength: 20,
89
+ pattern: '^[A-Z0-9]+$'
90
+ }
91
+ ```
92
+
93
+ ### Number with Units
94
+
95
+ ```typescript
96
+ {
97
+ type: 'NUMBER',
98
+ entityType: 'ASSET',
99
+ key: 'fuelCapacity',
100
+ translations: [{ language: 'en', title: 'Fuel Capacity', description: 'Tank capacity' }],
101
+ minimum: 0,
102
+ maximum: 1000,
103
+ unitSi: 'L',
104
+ unitUs: 'gal'
105
+ }
106
+ ```
107
+
108
+ ### Dropdown with Multiple Selection
109
+
110
+ ```typescript
111
+ {
112
+ type: 'DROPDOWN',
113
+ entityType: 'ASSET',
114
+ key: 'features',
115
+ translations: [{ language: 'en', title: 'Features', description: 'Available features' }],
116
+ allValues: ['GPS', 'Camera', 'Bluetooth', 'WiFi'],
117
+ multiSelect: true
118
+ }
119
+ ```
120
+
121
+ ## Scope Types
122
+
123
+ | Scope | Description |
124
+ |-------|-------------|
125
+ | `ACCOUNT` | Values shared within a single account only |
126
+ | `ACCOUNT_WRITE_GLOBAL_READ` | Account can write, all accounts can read |
127
+ | `GLOBAL` | Values shared between all accounts with entity access |
128
+
129
+ ## Using CustomFields in React
130
+
131
+ ### Required Imports
132
+
133
+ ```typescript
134
+ import {
135
+ useCustomFieldsValueAndDefinition,
136
+ useUpdateCustomFieldValues
137
+ } from '@trackunit/custom-field-api';
138
+ import { CustomField } from '@trackunit/custom-field-components';
139
+ import { useCurrentUserSystemOfMeasurement } from '@trackunit/react-core-hooks';
140
+ ```
141
+
142
+ ### Basic Implementation
143
+
144
+ ```typescript
145
+ import React from 'react';
146
+ import { useForm } from 'react-hook-form';
147
+ import { Card, CardBody, CardHeader, CardFooter, Button, Spinner } from '@trackunit/react-components';
148
+ import { useCustomFieldsValueAndDefinition, useUpdateCustomFieldValues } from '@trackunit/custom-field-api';
149
+ import { CustomField } from '@trackunit/custom-field-components';
150
+ import { useCurrentUserSystemOfMeasurement } from '@trackunit/react-core-hooks';
151
+
152
+ const CustomFieldsForm = ({ assetId }: { assetId: string }) => {
153
+ const { register, handleSubmit, formState, setValue, control } = useForm({
154
+ shouldUnregister: false,
155
+ });
156
+
157
+ const { systemOfMeasurement } = useCurrentUserSystemOfMeasurement();
158
+
159
+ const { fields, loading } = useCustomFieldsValueAndDefinition({
160
+ entityType: "ASSET",
161
+ entityId: assetId,
162
+ systemOfMeasurement: systemOfMeasurement ?? "SI",
163
+ });
164
+
165
+ const { updateCustomFields, inProgress } = useUpdateCustomFieldValues(
166
+ assetId,
167
+ "ASSET",
168
+ fields,
169
+ systemOfMeasurement ?? "SI"
170
+ );
171
+
172
+ if (loading) return <Spinner />;
173
+
174
+ return (
175
+ <Card>
176
+ <CardHeader heading="Custom Fields" />
177
+ <CardBody>
178
+ {fields.map(field => {
179
+ const isEditable = field.valueEditable && (field.definition.uiEditable ?? true);
180
+ return (
181
+ <CustomField
182
+ key={field.definition.key}
183
+ control={control}
184
+ field={field}
185
+ formState={formState}
186
+ isEditable={isEditable}
187
+ register={register}
188
+ setValue={setValue}
189
+ unitPreference={systemOfMeasurement}
190
+ />
191
+ );
192
+ })}
193
+ </CardBody>
194
+ <CardFooter>
195
+ <Button
196
+ loading={inProgress}
197
+ onClick={handleSubmit(updateCustomFields)}
198
+ >
199
+ {inProgress ? "Saving..." : "Save Changes"}
200
+ </Button>
201
+ </CardFooter>
202
+ </Card>
203
+ );
204
+ };
205
+ ```
206
+
207
+ ## Best Practices
208
+
209
+ ### Field Design
210
+ - **Use descriptive keys**: Choose clear, descriptive field keys that won't need changing
211
+ - **Add proper translations**: Always provide meaningful titles and descriptions
212
+ - **Set appropriate constraints**: Use min/max values, length limits, and patterns for validation
213
+
214
+ ### Performance
215
+ - **Batch updates**: Use `useUpdateCustomFieldValues` to update multiple fields at once
216
+ - **Handle loading states**: Always show loading indicators while fetching data
217
+ - **Error handling**: Implement proper error handling for failed updates
218
+
219
+ ### Deployment
220
+ - **Field keys are immutable**: Cannot be changed after creation
221
+ - **Definition updates**: Changing field definitions affects all existing data
222
+ - **Approval process**: Custom field changes require app approval and marketplace submission
223
+
224
+ ## Common Mistakes
225
+
226
+ | Mistake | Fix |
227
+ |---------|-----|
228
+ | Changing field key after creation | Keys are immutable; plan carefully upfront |
229
+ | Missing translations | Always provide titles and descriptions for UI |
230
+ | Using external storage | CustomFields are the recommended persistence mechanism |
231
+ | No loading states | Show `<Spinner />` while fetching data |
232
+ | Skipping error handling | Handle failed updates gracefully |
233
+
234
+ ## References
235
+
236
+ - [Save Data from Your App](https://developers.trackunit.com/docs/save-data-from-your-app)
237
+ - [Defining a Custom Field](https://developers.trackunit.com/docs/defining-a-custom-field)
238
+ - [Using a Custom Field](https://developers.trackunit.com/docs/using-a-custom-field)
239
+ - [Deploying a New Custom Field](https://developers.trackunit.com/docs/deploying-a-new-custom-field)
@@ -0,0 +1,147 @@
1
+ ---
2
+ name: graphql
3
+ description: Use when setting up GraphQL tooling, writing queries/mutations, generating hooks, or fetching data from Trackunit API.
4
+ ---
5
+
6
+ # GraphQL API Integration
7
+
8
+ ## Overview
9
+
10
+ Trackunit exposes a GraphQL API with NX executors for code generation. Use `@trackunit/react-graphql-tools` to generate typed React hooks from `.graphql` files.
11
+
12
+ ## When to Use
13
+
14
+ - Setting up GraphQL in an extension
15
+ - Writing queries or mutations
16
+ - Generating React hooks from `.graphql` files
17
+ - Fetching data from Trackunit API
18
+
19
+ **Not for:** Time series data (use `graphql-timeseries` skill).
20
+
21
+ ## Setup
22
+
23
+ ### Install GraphQL Tools
24
+
25
+ ```bash
26
+ npm install @trackunit/react-graphql-tools
27
+ ```
28
+
29
+ ### Set Up GraphQL Tooling
30
+
31
+ ```bash
32
+ npx nx generate @trackunit/react-graphql-tools:add-graphql --project=[project-name]
33
+ ```
34
+
35
+ Where `[project-name]` is found in the library's `project.json` file (format: `[subdir-name]-[extension-name]` for extensions in subdirectories).
36
+
37
+ ### Generate React Hooks
38
+
39
+ ```bash
40
+ npx nx run [feature-name]-[extension-name]:graphql-hooks
41
+ ```
42
+
43
+ This generates React hooks from `.graphql` files in the `src` folder.
44
+
45
+ ## GraphQL File Structure
46
+
47
+ Create `.graphql` files in `libs/[feature-name]/[extension-name]/src/` directory.
48
+
49
+ ### Query Syntax
50
+
51
+ ```graphql
52
+ query QueryName($variable: Type) {
53
+ fieldName {
54
+ subField
55
+ }
56
+ }
57
+ ```
58
+
59
+ ### Mutation Syntax
60
+
61
+ ```graphql
62
+ mutation MutationName($variable: Type!) {
63
+ mutationField(input: $variable) {
64
+ result
65
+ }
66
+ }
67
+ ```
68
+
69
+ ## React Integration
70
+
71
+ ### Import Generated Hooks
72
+
73
+ ```typescript
74
+ import { QueryNameDocument } from "./generated/graphql-api/graphql";
75
+ ```
76
+
77
+ ### Use Queries
78
+
79
+ ```typescript
80
+ import { useQuery } from '@trackunit/react-graphql-hooks';
81
+
82
+ const { data, loading, error } = useQuery(QueryNameDocument, {
83
+ variables: { /* your variables */ }
84
+ });
85
+ ```
86
+
87
+ ### Use Mutations
88
+
89
+ ```typescript
90
+ import { useMutation } from '@apollo/client';
91
+
92
+ const [mutationName, { data, loading, error }] = useMutation(MutationNameDocument);
93
+ ```
94
+
95
+ ## Common Patterns
96
+
97
+ ### Query with Variables
98
+
99
+ ```typescript
100
+ const { data, loading, error } = useQuery(GetAssetDocument, {
101
+ variables: { assetId: "123" }
102
+ });
103
+
104
+ if (loading) return <Spinner />;
105
+ if (error) return <ErrorMessage error={error} />;
106
+
107
+ return <AssetDisplay asset={data?.asset} />;
108
+ ```
109
+
110
+ ### Mutation with Refetch
111
+
112
+ ```typescript
113
+ import { useMutation } from '@apollo/client';
114
+
115
+ const [updateAsset, { loading }] = useMutation(UpdateAssetDocument, {
116
+ refetchQueries: [{ query: GetAssetDocument, variables: { assetId } }]
117
+ });
118
+
119
+ const handleUpdate = async (input) => {
120
+ await updateAsset({ variables: { input } });
121
+ };
122
+ ```
123
+
124
+ ### Polling
125
+
126
+ ```typescript
127
+ const { data } = useQuery(GetAssetsDocument, {
128
+ pollInterval: 5000, // Poll every 5 seconds
129
+ });
130
+ ```
131
+
132
+ ## References
133
+
134
+ - [Calling Trackunit GraphQL API](https://developers.trackunit.com/docs/graphql-api)
135
+ - [Apollo useQuery documentation](https://www.apollographql.com/docs/react/data/queries)
136
+ - [Apollo useMutation documentation](https://www.apollographql.com/docs/react/data/mutations)
137
+ - [GraphQL Code Generator](https://the-guild.dev/graphql/codegen)
138
+
139
+ ## Common Mistakes
140
+
141
+ | Mistake | Fix |
142
+ |---------|-----|
143
+ | Using generic GraphQL libraries | Use `@trackunit/react-graphql-tools` only |
144
+ | Missing hook generation | Run `nx run [project]:graphql-hooks` after adding `.graphql` files |
145
+ | Wrong project name format | Use `[subdir-name]-[extension-name]` from `project.json` |
146
+ | No loading/error states | Always handle `loading` and `error` from hooks |
147
+ | Importing from wrong path | Import from `./generated/graphql-api/graphql` |
@@ -0,0 +1,193 @@
1
+ ---
2
+ name: graphql-timeseries
3
+ description: Use when querying machine insights, engine metrics, fuel data, operating hours, or building charts with historical time-based data.
4
+ ---
5
+
6
+ # GraphQL Time Series API
7
+
8
+ ## Overview
9
+
10
+ Query time series data through GraphQL using PromQL (Prometheus Query Language). Use `rangeQuery` for time series charts, `instantQuery` for current values.
11
+
12
+ ## When to Use
13
+
14
+ - Querying engine metrics (speed, temperature, fuel rate)
15
+ - Building historical data charts
16
+ - Calculating increases over time periods (daily fuel usage, operating hours)
17
+ - Getting current sensor values
18
+
19
+ **Not for:** Static entity data (use `graphql` skill).
20
+
21
+ ## Query Structure
22
+
23
+ ### Range Query (Time Series Data)
24
+
25
+ ```graphql
26
+ query GetTimeSeriesData($assetId: ID!, $start: DateTime!, $end: DateTime!, $step: Duration!) {
27
+ asset(id: $assetId) {
28
+ timeSeries {
29
+ rangeQuery(query: "metric_name", start: $start, end: $end, step: $step) {
30
+ data {
31
+ ... on TimeSeriesMatrixData {
32
+ result {
33
+ values { timestamp value }
34
+ }
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ ### Instant Query (Single Point in Time)
44
+
45
+ ```graphql
46
+ query GetInstantData($assetId: ID!, $time: DateTime!) {
47
+ asset(id: $assetId) {
48
+ timeSeries {
49
+ instantQuery(query: "metric_name", time: $time) {
50
+ data {
51
+ ... on TimeSeriesVectorData {
52
+ result { value { timestamp value } }
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## Common Metrics
62
+
63
+ ### Machine Insights
64
+
65
+ Not all assets have all insights. Common metrics include:
66
+
67
+ **Engine Metrics:**
68
+ - `machine_insight_engine_speed`
69
+ - `machine_insight_engine_coolant_temperature`
70
+ - `machine_insight_engine_oil_pressure`
71
+ - `machine_insight_engine_oil_temperature`
72
+ - `machine_insight_engine_fuel_rate`
73
+ - `machine_insight_engine_total_fuel_used`
74
+ - `machine_insight_engine_percent_load_at_current_speed`
75
+ - `machine_insight_cumulative_engine_hours`
76
+
77
+ **Fuel & Emissions:**
78
+ - `machine_insight_fuel_level`
79
+ - `machine_insight_fuel_tank_capacity`
80
+ - `machine_insight_fuel_used_last_24`
81
+ - `machine_insight_cumulative_co2_emissions`
82
+
83
+ **Operating Hours:**
84
+ - `machine_insight_cumulative_operating_hours`
85
+ - `machine_insight_cumulative_idle_hours`
86
+ - `machine_insight_cumulative_moving_hours`
87
+ - `machine_insight_cumulative_productive_hours`
88
+
89
+ **Battery & Electric:**
90
+ - `machine_insight_battery_state_of_charge_percent`
91
+ - `machine_insight_battery_state_of_health_percent`
92
+ - `machine_insight_battery_temperature`
93
+ - `machine_insight_battery_current`
94
+ - `machine_insight_battery_potential`
95
+
96
+ **Location & Movement:**
97
+ - `machine_insight_speed`
98
+ - `machine_insight_altitude`
99
+ - `machine_insight_total_vehicle_distance`
100
+
101
+ **Environmental:**
102
+ - `machine_insight_ambient_air_temperature`
103
+ - `machine_insight_barometric_pressure`
104
+
105
+ ## PromQL Examples
106
+
107
+ ### Calculate Daily Increases
108
+
109
+ ```graphql
110
+ query IncreaseOperatingHours($assetId: ID!, $start: DateTime!, $end: DateTime!, $step: Duration!) {
111
+ asset(id: $assetId) {
112
+ timeSeries {
113
+ rangeQuery(
114
+ query: "increase(machine_insight_cumulative_operating_hours[1d])",
115
+ start: $start,
116
+ end: $end,
117
+ step: $step
118
+ ) {
119
+ data {
120
+ ... on TimeSeriesMatrixData {
121
+ result { values { timestamp value } }
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### Filter with Conditions
131
+
132
+ ```graphql
133
+ query HighFuelConsumption($assetId: ID!, $start: DateTime!, $end: DateTime!, $step: Duration!) {
134
+ asset(id: $assetId) {
135
+ timeSeries {
136
+ rangeQuery(
137
+ query: "increase(machine_insight_engine_total_fuel_used[1d]) > 13",
138
+ start: $start,
139
+ end: $end,
140
+ step: $step
141
+ ) {
142
+ data {
143
+ ... on TimeSeriesMatrixData {
144
+ result { values { timestamp value } }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
151
+ ```
152
+
153
+ ### Combine Metrics
154
+
155
+ ```graphql
156
+ query FuelWithHighIdle($assetId: ID!, $start: DateTime!, $end: DateTime!, $step: Duration!) {
157
+ asset(id: $assetId) {
158
+ timeSeries {
159
+ rangeQuery(
160
+ query: "(increase(machine_insight_engine_total_fuel_used[1d]) > 13) and (increase(machine_insight_cumulative_idle_hours[1d]) > 4)",
161
+ start: $start,
162
+ end: $end,
163
+ step: $step
164
+ ) {
165
+ data {
166
+ ... on TimeSeriesMatrixData {
167
+ result { values { timestamp value } }
168
+ }
169
+ }
170
+ }
171
+ }
172
+ }
173
+ }
174
+ ```
175
+
176
+ ## Key Points
177
+
178
+ - **Always use fragments** for both `TimeSeriesMatrixData` and `TimeSeriesVectorData`
179
+ - **Use PromQL functions**: `increase()`, `rate()`, `avg_over_time()`
180
+ - **Filter at query level** with operators: `>`, `<`, `>=`, `<=`, `==`, `!=`
181
+ - **Time windows** use brackets: `[1d]`, `[1h]`, `[15m]`
182
+ - **Step intervals**: Choose based on data granularity needed
183
+ - **Timestamps** are Unix timestamps in seconds
184
+ - **Values** are returned as strings - convert to numbers as needed
185
+
186
+ ## Common Mistakes
187
+
188
+ | Mistake | Fix |
189
+ |---------|-----|
190
+ | Missing fragment for data type | Use `... on TimeSeriesMatrixData` or `TimeSeriesVectorData` |
191
+ | Not handling missing metrics | Not all assets have all insights; handle empty results |
192
+ | Using values as numbers directly | Values are strings; convert with `parseFloat()` |
193
+ | Wrong step interval | Match step to desired chart granularity |
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: irisx-app-sdk
3
+ description: Use when creating apps, adding extensions, configuring CSP/scopes, or needing SDK command reference. Core SDK documentation.
4
+ ---
5
+
6
+ # IrisX App SDK Development
7
+
8
+ ## Overview
9
+
10
+ IrisX Apps are React applications that integrate into Trackunit Manager at defined extension points. Use SDK commands (not generic React/Node.js) for all project operations.
11
+
12
+ ## When to Use
13
+
14
+ - Creating new apps or extensions
15
+ - Configuring security (CSP headers, scopes)
16
+ - Looking up extension types or SDK commands
17
+ - Understanding runtime hooks availability
18
+
19
+ **Not for:** GraphQL setup (use `graphql` skill), browser testing (use `browser-testing` skill).
20
+
21
+ ## Extension Points
22
+
23
+ Extensions are React applications that integrate into specific points in Trackunit Manager:
24
+
25
+ | Extension Type | Location |
26
+ |----------------|----------|
27
+ | `ASSET_HOME_EXTENSION` | New tabs in Assets Home screen |
28
+ | `SITE_HOME_EXTENSION` | New tabs in Site Home screen |
29
+ | `FLEET_EXTENSION` | Menu items in Main Menu |
30
+ | `REPORT_EXTENSION` | New reports in Reports screen |
31
+ | `WIDGET_EXTENSION` | Dashboard widgets |
32
+ | `IRIS_APP_SETTINGS_EXTENSION` | Configuration UI in App library |
33
+ | `ADMIN_EXTENSION` | Admin UI tabs (admin-only) |
34
+ | `CUSTOMER_HOME_EXTENSION` | UI within Customer Home |
35
+ | `ASSET_EVENTS_ACTIONS_EXTENSION` | UI in Asset Home Events |
36
+ | `LIFECYCLE_EXTENSION` | Handles app install/uninstall events |
37
+ | `SERVERLESS_FUNCTION_EXTENSION` | Serverless backend functions |
38
+
39
+ ## Commands
40
+
41
+ ### Create New App
42
+
43
+ ```bash
44
+ npx nx generate @trackunit/iris-app:create [name-of-your-app]
45
+ ```
46
+
47
+ See: [Creating a new app](https://developers.trackunit.com/docs/creating-a-new-app)
48
+
49
+ ### Create New Extension
50
+
51
+ ```bash
52
+ npx nx g @trackunit/iris-app:extend --name=[my-extension-name] --app=[app-name] --directory=[feature] --type=[extension-type]
53
+ ```
54
+
55
+ See: [Creating a new extension](https://developers.trackunit.com/docs/creating-a-new-extension)
56
+
57
+ ### Run App
58
+
59
+ ```bash
60
+ npx nx run [name-of-your-app]:serve
61
+ ```
62
+
63
+ See: [Running the Iris App SDK](https://developers.trackunit.com/docs/running-the-iris-app-sdk)
64
+
65
+ ## Security Configuration
66
+
67
+ IrisX Apps are locked down by default. External API requests and embedded content require proper Content Security Policy (CSP) configuration in the app manifest.
68
+
69
+ ### CSP Headers Example
70
+
71
+ ```typescript
72
+ cspHeader: {
73
+ 'frame-src': ['https://api.example.com', 'https://dashboard.example.com'],
74
+ 'script-src': ['https://api.example.com'],
75
+ 'connect-src': ['https://api.example.com'],
76
+ },
77
+ ```
78
+
79
+ ### Scopes Example
80
+
81
+ ```typescript
82
+ scopes: [
83
+ {scope: "required.scope.1", optional: false},
84
+ {scope: "required.scope.2", optional: true},
85
+ ],
86
+ ```
87
+
88
+ Apps requiring API access need scopes configured in the manifest, and the app must be deployed with those scopes to work locally. Never submit an app automatically - instruct the user to do so.
89
+
90
+ See: [Embedding IrisX Dashboards in Apps](https://developers.trackunit.com/docs/analytics-dashboards-in-apps#troubleshooting)
91
+
92
+ ## Runtime Hooks
93
+
94
+ IrisX Apps have access to runtime hooks from `@trackunit/react-core-hooks`:
95
+
96
+ - `useAssetRuntime()` - Asset information in asset-scoped extensions
97
+ - `useCustomerRuntime()` - Customer data
98
+ - `useEventRuntime()` - Event details
99
+ - `useSiteRuntime()` - Site information
100
+ - Plus navigation, user context, and utility hooks
101
+
102
+ See the `react-core-hooks` skill for full details.
103
+
104
+ ## Quality Assurance
105
+
106
+ Before completing any task: Always use `read_lints` to check for and fix all ESLint and TypeScript errors in modified files.
107
+
108
+ ## Common Mistakes
109
+
110
+ | Mistake | Fix |
111
+ |---------|-----|
112
+ | Using `create-react-app` or `npm init` | Use `nx generate @trackunit/iris-app:create` |
113
+ | Mocking data without asking | Always use real GraphQL API |
114
+ | Missing scopes in manifest | Add required scopes; deploy to enable locally |
115
+ | External API blocked | Add domain to `cspHeader` in manifest |
116
+ | Auto-submitting app | Never auto-submit; instruct user to deploy |