@trackunit/iris-app 1.12.9 → 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.
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/executors/submit/executor.js.map +1 -0
- package/src/executors/unpublish/executor.js.map +1 -0
- package/src/executors/utils/authentication.js.map +1 -0
- package/src/executors/utils/irisAppServerSettings.js.map +1 -0
- package/src/executors/utils/src/index.js.map +1 -0
- package/src/generators/ai-agent-sync/README.md +5 -2
- package/src/generators/ai-agent-sync/generator.d.ts +1 -1
- package/src/generators/ai-agent-sync/generator.js +29 -4
- package/src/generators/ai-agent-sync/generator.js.map +1 -0
- package/src/generators/create/generator.js.map +1 -0
- package/src/generators/extend/dependencies.js.map +1 -0
- package/src/generators/extend/generator.js.map +1 -0
- package/src/generators/preset/files/.agents/skills/browser-testing/SKILL.md +193 -0
- package/src/generators/preset/files/.agents/skills/create-app/SKILL.md +191 -0
- package/src/generators/preset/files/.agents/skills/customfields/SKILL.md +239 -0
- package/src/generators/preset/files/.agents/skills/graphql/SKILL.md +147 -0
- package/src/generators/preset/files/.agents/skills/graphql-timeseries/SKILL.md +193 -0
- package/src/generators/preset/files/.agents/skills/irisx-app-sdk/SKILL.md +116 -0
- package/src/generators/preset/files/.agents/skills/react-core-hooks/SKILL.md +215 -0
- package/src/generators/preset/files/.agents/skills/tables-and-sorting/SKILL.md +122 -0
- package/src/generators/preset/files/.agents/skills/widget-extensions/SKILL.md +245 -0
- package/src/generators/preset/files/.cursor/mcp.json +4 -0
- package/src/generators/preset/generator.js.map +1 -0
- package/src/generators/preset/root-files/AGENTS.md +43 -0
- package/src/generators/preset/root-files/CLAUDE.md +17 -0
- package/src/index.js.map +1 -0
- package/src/utils/ast/astUtils.js.map +1 -0
- package/src/utils/fileUpdater.js.map +1 -0
- package/src/generators/preset/files/.cursor/commands/create-app.md +0 -226
- package/src/generators/preset/files/.cursor/rules/browser-irisx-development.mdc +0 -246
- package/src/generators/preset/files/.cursor/rules/graphql-timeseries.md +0 -260
- package/src/generators/preset/files/.cursor/rules/irisx-app-sdk-customfields.md +0 -305
- package/src/generators/preset/files/.cursor/rules/irisx-app-sdk-graphql.md +0 -30
- package/src/generators/preset/files/.cursor/rules/irisx-app-sdk.mdc +0 -82
- package/src/generators/preset/files/.cursor/rules/react-core-hooks.md +0 -155
- package/src/generators/preset/files/.cursor/rules/rules-index.mdc +0 -10
- package/src/generators/preset/files/.cursor/rules/structured-development.mdc +0 -86
- package/src/generators/preset/files/.cursor/rules/tables-and-sorting.mdc +0 -126
- package/src/generators/preset/files/.cursor/rules/widget-extensions.md +0 -323
|
@@ -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 |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../../../../../libs/iris-app-sdk/iris-app/src/generators/preset/generator.ts"],"names":[],"mappings":";;AAmBA,0CA6DC;;AAhFD,uCAQoB;AACpB,+BAA0D;AAC1D,qCAA+C;AAC/C,wDAAwB;AAGxB;;;;GAIG;AACI,KAAK,UAAU,eAAe,CAAC,IAAU,EAAE,OAA8B;IAC9E,kBAAkB;IAClB,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,KAAK,CACR,gBAAgB,EAChB,4LAA4L,CAC7L,CAAC;IACF,IAAI,CAAC,KAAK,CACR,gBAAgB,EAChB,oIAAoI,CACrI,CAAC;IAEF,MAAM,cAAc,GAAG,yBAAyB,CAAC;IAEjD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC;QACjC,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,IAAA,sBAAa,EAAC,IAAI,EAAE,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAE5D,IAAA,mBAAU,EAAC,IAAI,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE;QACvC,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC;YAC3B,KAAK,CAAC,eAAe,GAAG,EAAE,CAAC;QAC7B,CAAC;QACD,KAAK,CAAC,eAAe,CAAC,IAAI,CACxB,wBAAwB,EACxB,+BAA+B,EAC/B,2BAA2B,EAC3B,8BAA8B,CAC/B,CAAC;QACF,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,MAAM,IAAA,kBAAe,EAAC,IAAI,EAAE;QAC7C,GAAG,OAAO;QACV,YAAY,EAAE,oBAAoB;QAClC,UAAU,EAAE,IAAI;KACjB,CAAC,CAAC;IAEH,IAAA,qCAA4B,EAC1B,IAAI,EACJ;QACE,KAAK,EAAE,QAAQ;QACf,WAAW,EAAE,QAAQ;KACtB,EACD,EAAE,CACH,CAAC;IAEF,4CAA4C;IAC5C,IAAA,mBAAU,EAAC,IAAI,EAAE,cAAc,EAAE,CAAC,OAA6C,EAAE,EAAE;QACjF,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACrB,OAAO,CAAC,OAAO,GAAG,EAAE,CAAC;QACvB,CAAC;QACD,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,GAAG,yCAAyC,CAAC;QAChF,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC,CAAC;IAEH,0GAA0G;IAC1G,MAAM,SAAS,GAAG,MAAM,IAAA,0BAAkB,EAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAErD,OAAO,IAAA,yBAAgB,EAAC,UAAU,EAAE,SAAS,CAAC,CAAC;AACjD,CAAC;AAED,kBAAe,eAAe,CAAC;AAE/B,SAAS,qBAAqB,CAAC,IAAU;IACvC,MAAM,MAAM,GAAG,IAAA,mBAAU,EAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACtC,MAAM,CAAC,eAAe,GAAG;QACvB,OAAO,EAAE,MAAM;QACf,OAAO,EAAE,MAAM;KAChB,CAAC;IACF,IAAA,qBAAY,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC7B,CAAC","sourcesContent":["import {\n Tree,\n addDependenciesToPackageJson,\n generateFiles,\n readNxJson,\n runTasksInSerial,\n updateJson,\n updateNxJson,\n} from \"@nx/devkit\";\nimport { initGenerator as jsInitGenerator } from \"@nx/js\";\nimport { reactInitGenerator } from \"@nx/react\";\nimport path from \"path\";\nimport { PresetGeneratorSchema } from \"./schema\";\n\n/**\n * Preset generator for Iris apps workspace.\n * Use with create-nx-workspace to create a new workspace for developing Iris apps.\n * ```npx create-nx-workspace --preset=iris-app```\n */\nexport async function presetGenerator(tree: Tree, options: PresetGeneratorSchema) {\n // setup workspace\n updateWorkspaceLayout(tree);\n tree.write(\n \"apps/readme.md\",\n `# Apps\\n\\nThis folder contains all apps in the workspace.\\n\\nApps are the main entry point for the Iris App SDK.\\nCreate an app by running \\`nx g @trackunit/iris-app:create <app-name>\\`.`\n );\n tree.write(\n \"libs/readme.md\",\n `# Libs\\n\\nThis folder contains all libs in the workspace.\\nCreate a lib by running \\`nx g @trackunit/iris-app:extend <lib-name>\\`.`\n );\n\n const extensionsPath = \".vscode/extensions.json\";\n\n if (!tree.exists(extensionsPath)) {\n tree.write(extensionsPath, \"{}\");\n }\n\n generateFiles(tree, path.join(__dirname, \"files\"), \".\", {});\n\n updateJson(tree, extensionsPath, value => {\n if (!value.recommendations) {\n value.recommendations = [];\n }\n value.recommendations.push(\n \"graphql.vscode-graphql\",\n \"graphql.vscode-graphql-syntax\",\n \"bradlc.vscode-tailwindcss\",\n \"firsttris.vscode-jest-runner\"\n );\n return value;\n });\n\n const jsInitTask = await jsInitGenerator(tree, {\n ...options,\n tsConfigName: \"tsconfig.base.json\",\n skipFormat: true,\n });\n\n addDependenciesToPackageJson(\n tree,\n {\n react: \"19.0.0\",\n \"react-dom\": \"19.0.0\",\n },\n {}\n );\n\n // Add update script for @trackunit packages\n updateJson(tree, \"package.json\", (pkgJson: { scripts?: Record<string, string> }) => {\n if (!pkgJson.scripts) {\n pkgJson.scripts = {};\n }\n pkgJson.scripts[\"update:trackunit\"] = 'npx npm-check-updates \"/@trackunit/\" -u';\n return pkgJson;\n });\n\n // This react generator adds react 19 and react-dom 19 to the package.json if we don't add react 18 above.\n const initReact = await reactInitGenerator(tree, {});\n\n return runTasksInSerial(jsInitTask, initReact);\n}\n\nexport default presetGenerator;\n\nfunction updateWorkspaceLayout(tree: Tree) {\n const nxJson = readNxJson(tree) ?? {};\n nxJson.workspaceLayout = {\n libsDir: \"libs\",\n appsDir: \"apps\",\n };\n updateNxJson(tree, nxJson);\n}\n"]}
|
|
@@ -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.
|
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../libs/iris-app-sdk/iris-app/src/index.ts"],"names":[],"mappings":";;;;AAAA,2DAAiE;AAAxD,6GAAA,gBAAgB,OAAA;AAEzB,2DAA0E;AAAjE,sHAAA,yBAAyB,OAAA;AAElC,+DAAqC;AACrC,8DAAoC","sourcesContent":["export { IrisAppGenerator } from \"./generators/create/generator\";\nexport type { IrisAppGeneratorSchema } from \"./generators/create/schema\";\nexport { IrisAppExtensionGenerator } from \"./generators/extend/generator\";\nexport type { IrisAppExtensionGeneratorSchema } from \"./generators/extend/schema\";\nexport * from \"./utils/ast/astUtils\";\nexport * from \"./utils/fileUpdater\";\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"astUtils.js","sourceRoot":"","sources":["../../../../../../../libs/iris-app-sdk/iris-app/src/utils/ast/astUtils.ts"],"names":[],"mappings":"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","sourcesContent":["import { objectEntries } from \"@trackunit/shared-utils\";\nimport { tsquery } from \"@phenomnomnominal/tsquery\";\nimport * as ts from \"typescript\";\n\n/**\n * This function removes a property with a given name from an object literal\n * provided as a text string.\n *\n * @param {string} fileContent The source code as text\n * @param {string} propertyNames The properties to remove from the object\n */\nexport function removePropertyWithName(fileContent: string, ...propertyNames: Array<string>) {\n const sourceFile = tsquery.ast(fileContent);\n const updatedSourceFile = tsquery.map(sourceFile, \"ObjectLiteralExpression\", node => {\n if (ts.isObjectLiteralExpression(node)) {\n // Filter out the given property\n const updatedProperties = node.properties.filter(property => {\n return !(\n ts.isPropertyAssignment(property) &&\n ts.isIdentifier(property.name) &&\n propertyNames.includes(property.name.text)\n );\n });\n return ts.factory.createObjectLiteralExpression(updatedProperties, true);\n }\n return node;\n });\n return ts.createPrinter().printFile(updatedSourceFile);\n}\n\n/**\n * This function adds a new property with a given name and value to an object literal\n * provided as a text string.\n *\n * @param {string} fileContent - the source code as text\n * @param {string} propertyName - the name of the property to add\n * @param {string | string[]} propertyValue - the value of the property to add\n */\nexport function addPropertyWithName(\n fileContent: string,\n propertyName: string,\n propertyValue: string | Array<string> | Record<string, string>\n) {\n const sourceFile = tsquery.ast(fileContent);\n const updatedSourceFile = tsquery.map(sourceFile, \"ObjectLiteralExpression\", node => {\n if (ts.isObjectLiteralExpression(node)) {\n // Add a new property to the object literal\n let updatedProperties;\n if (typeof propertyValue === \"string\") {\n updatedProperties = [\n ...node.properties,\n ts.factory.createPropertyAssignment(\n ts.factory.createIdentifier(propertyName),\n ts.factory.createStringLiteral(propertyValue)\n ),\n ];\n } else if (Array.isArray(propertyValue)) {\n const arrayLiteral = ts.factory.createArrayLiteralExpression(\n propertyValue.map(value => ts.factory.createStringLiteral(value))\n );\n updatedProperties = [\n ...node.properties,\n ts.factory.createPropertyAssignment(ts.factory.createIdentifier(propertyName), arrayLiteral),\n ];\n } else if (typeof propertyValue === \"object\") {\n updatedProperties = [\n ...node.properties,\n ts.factory.createPropertyAssignment(\n ts.factory.createIdentifier(propertyName),\n ts.factory.createObjectLiteralExpression(createObjectLiteralElementLike(propertyValue))\n ),\n ];\n }\n return ts.factory.createObjectLiteralExpression(updatedProperties, true);\n }\n return node;\n });\n return ts.createPrinter().printFile(updatedSourceFile);\n}\n\n/**\n * This function adds a spread assignment to an object literal.\n *\n * @param {string} fileContent - the source code as text\n * @param {string} variableName - the name of the variable to spread\n */\nexport function addSpreadAssignment(fileContent: string, variableName: string) {\n const sourceFile = tsquery.ast(fileContent);\n \n // First check if the spread already exists\n const existingSpreads = tsquery(sourceFile, \"SpreadAssignment\");\n const hasExistingSpread = existingSpreads.some(spread => {\n if (ts.isSpreadAssignment(spread)) {\n return spread.expression.getText() === variableName;\n }\n return false;\n });\n \n if (hasExistingSpread) {\n return fileContent; // Already has the spread, no changes needed\n }\n \n const updatedSourceFile = tsquery.map(sourceFile, \"ObjectLiteralExpression\", node => {\n if (ts.isObjectLiteralExpression(node)) {\n // Add the spread assignment to the object literal\n const spreadAssignment = ts.factory.createSpreadAssignment(\n ts.factory.createIdentifier(variableName)\n );\n const updatedProperties = [...node.properties, spreadAssignment];\n return ts.factory.createObjectLiteralExpression(updatedProperties, true);\n }\n return node;\n });\n return ts.createPrinter().printFile(updatedSourceFile);\n}\n\n/**\n * This function adds a require statement to a file after existing require statements.\n *\n * @param {string} fileContent - the source code as text\n * @param {string} variableName - the name of the variable to assign to\n * @param {string} modulePath - the module path to require\n */\nexport function addRequireStatement(fileContent: string, variableName: string, modulePath: string) {\n const sourceFile = tsquery.ast(fileContent);\n \n // Check if the require statement already exists\n const existingRequires = tsquery(sourceFile, \"CallExpression\");\n const hasExistingRequire = existingRequires.some(call => {\n if (ts.isCallExpression(call) && \n ts.isIdentifier(call.expression) && \n call.expression.text === \"require\" &&\n call.arguments.length > 0) {\n const firstArg = call.arguments[0];\n if (firstArg && ts.isStringLiteral(firstArg)) {\n return firstArg.text === modulePath;\n }\n }\n return false;\n });\n \n if (hasExistingRequire) {\n return fileContent; // Already has the require, no changes needed\n }\n \n // Find all variable statements with require calls\n const requireStatements = tsquery(sourceFile, \"VariableStatement\")\n .filter(stmt => stmt.getText().includes(\"require(\"));\n \n const requireStatement = `const ${variableName} = require(\"${modulePath}\").default;`;\n \n if (requireStatements.length > 0) {\n // Add after the last require statement\n const lastRequire = requireStatements[requireStatements.length - 1];\n if (lastRequire) {\n const lastRequireEnd = lastRequire.getEnd();\n \n return fileContent.slice(0, lastRequireEnd) + '\\n' + requireStatement + fileContent.slice(lastRequireEnd);\n }\n }\n \n // Add at the beginning if no require statements found\n return requireStatement + '\\n' + fileContent;\n}\n\nfunction createObjectLiteralElementLike(obj: Record<string, string>): Array<ts.ObjectLiteralElementLike> {\n const properties: Array<ts.ObjectLiteralElementLike> = [];\n for (const [key, value] of objectEntries(obj)) {\n properties.push(\n ts.factory.createPropertyAssignment(ts.factory.createStringLiteral(key), ts.factory.createStringLiteral(value))\n );\n }\n return properties;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileUpdater.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app/src/utils/fileUpdater.ts"],"names":[],"mappings":";;;AAEA;;;;;GAKG;AACI,MAAM,gBAAgB,GAAG,CAAC,IAAU,EAAE,IAAY,EAAE,OAAwC,EAAE,EAAE;IACrG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC7C,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IAC5C,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAEpC,IAAI,MAAM,KAAK,WAAW,EAAE,CAAC;QAC3B,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC,CAAC;AAdW,QAAA,gBAAgB,oBAc3B","sourcesContent":["import { Tree } from \"@nx/devkit\";\n\n/**\n *\n * @param tree File system tree\n * @param path Path to source file in the Tree\n * @param updater Function that maps the current file content to a new to be written to the Tree\n */\nexport const updateFileInTree = (tree: Tree, path: string, updater: (fileContent: string) => string) => {\n if (!tree.exists(path)) {\n throw new Error(\"File not found: \" + path);\n }\n\n const fileContent = tree.read(path, \"utf-8\");\n if (!fileContent) {\n throw new Error(\"File is empty: \" + path);\n }\n const result = updater(fileContent);\n\n if (result !== fileContent) {\n tree.write(path, result);\n }\n};\n"]}
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
# Create IrisX App
|
|
2
|
-
|
|
3
|
-
You are helping a user create a new IrisX App with appropriate extensions. Follow this browser-driven iterative development workflow.
|
|
4
|
-
|
|
5
|
-
## Core Principles
|
|
6
|
-
|
|
7
|
-
### Real Data First
|
|
8
|
-
**CRITICAL: Always use Trackunit's GraphQL API for data** - never mock data unless explicitly requested.
|
|
9
|
-
- Use **Trackunit MCP** `introspect-schema` to discover available data
|
|
10
|
-
- Use **Trackunit MCP** `validate-query` to test queries before implementing
|
|
11
|
-
- See @irisx-app-sdk-graphql for implementation patterns
|
|
12
|
-
|
|
13
|
-
### UI Components
|
|
14
|
-
**Use Trackunit Component Library** for consistent UI:
|
|
15
|
-
- Use **Trackunit MCP** `get-story-code` to find components and patterns
|
|
16
|
-
- See @tables-and-sorting, @react-core-hooks for details
|
|
17
|
-
|
|
18
|
-
### Browser Testing
|
|
19
|
-
**Follow browser-first development workflow**:
|
|
20
|
-
- See @browser-irisx-development for complete browser testing procedures
|
|
21
|
-
- Always verify in browser after each code change
|
|
22
|
-
- Check lints first, then reload browser and verify
|
|
23
|
-
|
|
24
|
-
## Development Workflow
|
|
25
|
-
|
|
26
|
-
### Phase 1: Planning
|
|
27
|
-
|
|
28
|
-
**1. Analyze Requirements**
|
|
29
|
-
If not already described, ask: "What functionality do you want to build?"
|
|
30
|
-
|
|
31
|
-
**2. Create Implementation Plan**
|
|
32
|
-
Before any coding, create a detailed plan that **MUST follow this structure**:
|
|
33
|
-
|
|
34
|
-
**Phase 1: Project Setup**
|
|
35
|
-
- Create app with `@trackunit/iris-app:create [app-name]`
|
|
36
|
-
- Create extensions with `@trackunit/iris-app:extend` (list each extension)
|
|
37
|
-
- Start dev server in background
|
|
38
|
-
|
|
39
|
-
**Phase 2: Browser Verification (CRITICAL GATE)**
|
|
40
|
-
- Open browser to https://new.manager.trackunit.com/goto/iris-app-dev
|
|
41
|
-
- Wait for user login (2-4 minutes)
|
|
42
|
-
- Enable "Use Local Apps" toggle (dataTestId="localDevModeSwitch") if not already enabled
|
|
43
|
-
- Navigate to extension location based on type (see @browser-irisx-development):
|
|
44
|
-
- FLEET_EXTENSION: Main menu → App name
|
|
45
|
-
- ASSET_HOME_EXTENSION: Assets → Select asset → Extension tab
|
|
46
|
-
- SITE_HOME_EXTENSION: Sites → Select site → Extension tab
|
|
47
|
-
- WIDGET_EXTENSION: Dashboard → Add Widget → Find widget
|
|
48
|
-
- Take snapshot to verify extension loads
|
|
49
|
-
- Check console for errors
|
|
50
|
-
- **STOP - Must confirm extension working in browser before Phase 3**
|
|
51
|
-
|
|
52
|
-
**Phase 3: Feature Implementation (One at a Time)**
|
|
53
|
-
For EACH feature, list as separate item:
|
|
54
|
-
- Feature 1: [Name]
|
|
55
|
-
- Prerequisites: [GraphQL setup/scopes/packages needed for THIS feature only]
|
|
56
|
-
- Implementation: [What to build]
|
|
57
|
-
- Validation: Check lints (zero tolerance), then browser test
|
|
58
|
-
- Browser test: [What to verify]
|
|
59
|
-
|
|
60
|
-
- Feature 2: [Name]
|
|
61
|
-
- Prerequisites: [Any new tools needed for THIS feature]
|
|
62
|
-
- Implementation: [What to build]
|
|
63
|
-
- Validation: Check lints (zero tolerance), then browser test
|
|
64
|
-
- Browser test: [What to verify]
|
|
65
|
-
|
|
66
|
-
(Continue for all features)
|
|
67
|
-
|
|
68
|
-
**Phase 4: Final Verification**
|
|
69
|
-
- Run lints on all files (zero errors)
|
|
70
|
-
- End-to-end browser testing
|
|
71
|
-
- Documentation completion
|
|
72
|
-
|
|
73
|
-
**Plan Details:**
|
|
74
|
-
- App name: [kebab-case, descriptive]
|
|
75
|
-
- Extension types: [List types from @irisx-app-sdk]
|
|
76
|
-
- Data sources: [GraphQL queries, CustomFields needed]
|
|
77
|
-
- UI components: [Components from Trackunit library]
|
|
78
|
-
- Manifest config: [Scopes, CSP headers if needed]
|
|
79
|
-
|
|
80
|
-
**3. Present Plan to User**
|
|
81
|
-
Show the complete plan with:
|
|
82
|
-
- **Browser flow explicitly documented** in Phase 2 with the URL https://new.manager.trackunit.com/goto/iris-app-dev
|
|
83
|
-
- Navigation steps for the specific extension type(s)
|
|
84
|
-
- Browser-first workflow clearly outlined
|
|
85
|
-
Confirm with user before proceeding.
|
|
86
|
-
|
|
87
|
-
### Phase 2: Project Setup
|
|
88
|
-
|
|
89
|
-
**4. Create App and Extensions ONLY**
|
|
90
|
-
```bash
|
|
91
|
-
npx nx generate @trackunit/iris-app:create [app-name]
|
|
92
|
-
npx nx g @trackunit/iris-app:extend --name=[extension-name] --directory=[feature-name] --type=[EXTENSION_TYPE]
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
**5. Start Development Server Immediately**
|
|
96
|
-
Run in background terminal right after creation:
|
|
97
|
-
```bash
|
|
98
|
-
npx nx run [app-name]:serve
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
**6. Open Browser and Verify Basics Work**
|
|
102
|
-
**CRITICAL: Do this BEFORE any GraphQL setup or UI implementation**
|
|
103
|
-
|
|
104
|
-
Follow the complete browser setup workflow in @browser-irisx-development:
|
|
105
|
-
- Navigate to https://new.manager.trackunit.com/goto/iris-app-dev
|
|
106
|
-
- Wait for user login (be patient, 2-4 minutes)
|
|
107
|
-
- Enable "Use Local Apps" toggle (dataTestId="localDevModeSwitch") if not already enabled
|
|
108
|
-
- Navigate to extension location based on type:
|
|
109
|
-
- FLEET_EXTENSION: Main menu → Look for app name
|
|
110
|
-
- ASSET_HOME_EXTENSION: Assets → Open any asset → Look for extension tab
|
|
111
|
-
- SITE_HOME_EXTENSION: Sites → Open any site → Look for extension tab
|
|
112
|
-
- WIDGET_EXTENSION: Dashboard → Add Widget → Find widget
|
|
113
|
-
- Take snapshot to verify extension loads
|
|
114
|
-
- Check console for errors using browser_console_messages
|
|
115
|
-
|
|
116
|
-
**STOP HERE: Do not proceed to Phase 3 until extension is confirmed working in browser**
|
|
117
|
-
|
|
118
|
-
### Phase 3: Iterative Feature Development
|
|
119
|
-
|
|
120
|
-
**7. Create Documentation**
|
|
121
|
-
Create `/docs/[app-name].md` following @structured-development with the plan and architecture.
|
|
122
|
-
|
|
123
|
-
**8. For Each Feature in Plan (ONE AT A TIME)**
|
|
124
|
-
|
|
125
|
-
For each feature, follow this exact workflow:
|
|
126
|
-
|
|
127
|
-
**a) Setup prerequisites for this feature ONLY**:
|
|
128
|
-
- If feature needs GraphQL: Install GraphQL tools NOW
|
|
129
|
-
```bash
|
|
130
|
-
npm install @trackunit/react-graphql-tools
|
|
131
|
-
npx nx generate @trackunit/react-graphql-tools:add-graphql --project=[project-name]
|
|
132
|
-
```
|
|
133
|
-
- If feature needs specific scopes: Update manifest NOW
|
|
134
|
-
- If feature needs CustomFields: Configure manifest NOW
|
|
135
|
-
- Use Trackunit MCP to discover data/components needed for THIS feature
|
|
136
|
-
|
|
137
|
-
**b) Implement the feature**:
|
|
138
|
-
- Write code changes for ONE feature only
|
|
139
|
-
- Keep changes small and focused (single metric, single UI component, etc.)
|
|
140
|
-
- If building multiple similar items (e.g., 3 KPI cards), do them one at a time
|
|
141
|
-
|
|
142
|
-
**c) Check for lint and TypeScript errors**:
|
|
143
|
-
- Run `read_lints` on the files you just modified
|
|
144
|
-
- Fix ALL errors immediately
|
|
145
|
-
- Zero tolerance: Do not proceed with any linter or TypeScript errors
|
|
146
|
-
- Recheck after fixes to confirm all errors are resolved
|
|
147
|
-
|
|
148
|
-
**d) Reload browser to test**:
|
|
149
|
-
- Follow reload strategy in @browser-irisx-development
|
|
150
|
-
- Check browser console and network tab for errors
|
|
151
|
-
- Verify the feature renders correctly
|
|
152
|
-
- Take screenshot if visual changes
|
|
153
|
-
|
|
154
|
-
**f) Debug if needed**:
|
|
155
|
-
- Read console errors from browser
|
|
156
|
-
- Check network requests for GraphQL/API issues
|
|
157
|
-
- Adjust code and repeat from step c) (check lints, then reload browser) until feature works
|
|
158
|
-
- DO NOT move forward until this feature is working
|
|
159
|
-
|
|
160
|
-
**g) Update documentation**:
|
|
161
|
-
- Mark feature as completed in `/docs/[app-name].md`
|
|
162
|
-
- Document any important findings or gotchas
|
|
163
|
-
|
|
164
|
-
**h) Move to next feature**:
|
|
165
|
-
- Only proceed when current feature is verified working in browser with zero errors
|
|
166
|
-
- Repeat entire process for next feature in plan
|
|
167
|
-
|
|
168
|
-
**9. Continuous Validation Rules**
|
|
169
|
-
Throughout all of Phase 3:
|
|
170
|
-
- After EVERY code change: Check lints first, then reload browser (@browser-irisx-development)
|
|
171
|
-
- Fix ALL lint and TypeScript errors immediately (zero tolerance)
|
|
172
|
-
- Check browser console for errors after each reload
|
|
173
|
-
- Never implement multiple features without full validation between them
|
|
174
|
-
|
|
175
|
-
### Phase 4: Completion
|
|
176
|
-
|
|
177
|
-
**10. Final Verification**
|
|
178
|
-
- Run `read_lints` on all modified files to ensure zero errors
|
|
179
|
-
- Test all features end-to-end in browser (@browser-irisx-development)
|
|
180
|
-
- Verify data loads correctly (real GraphQL data)
|
|
181
|
-
- Take screenshots of working features
|
|
182
|
-
|
|
183
|
-
**11. Summary**
|
|
184
|
-
Inform the user:
|
|
185
|
-
- All features implemented and verified in browser with zero errors
|
|
186
|
-
- No linter or TypeScript errors remaining
|
|
187
|
-
- Documentation at `docs/[app-name].md`
|
|
188
|
-
- App running at local dev server
|
|
189
|
-
- **Important**: User must deploy via Trackunit marketplace (never auto-submit)
|
|
190
|
-
|
|
191
|
-
## Critical Development Rules
|
|
192
|
-
|
|
193
|
-
### Strict Browser-First Workflow
|
|
194
|
-
**Follow this exact order:**
|
|
195
|
-
|
|
196
|
-
1. Create app + extensions
|
|
197
|
-
2. Start dev server immediately
|
|
198
|
-
3. Open browser and verify extension loads (@browser-irisx-development)
|
|
199
|
-
4. **STOP - Do not proceed without browser verification**
|
|
200
|
-
5. Implement ONE feature with prerequisites
|
|
201
|
-
6. Check lints and fix ALL errors (zero tolerance)
|
|
202
|
-
7. Reload browser and verify that ONE feature (@browser-irisx-development)
|
|
203
|
-
8. Repeat steps 5-7 for each remaining feature
|
|
204
|
-
|
|
205
|
-
### Feature Implementation Rules
|
|
206
|
-
- **One feature at a time** - even if building similar components
|
|
207
|
-
- **Prerequisites on-demand** - set up GraphQL/scopes only when needed for current feature
|
|
208
|
-
- **Zero tolerance for errors** - fix all lint and TypeScript errors immediately after code changes
|
|
209
|
-
- **Test completely** - verify working in browser before moving on
|
|
210
|
-
- **Small incremental changes** - easier to debug when issues arise
|
|
211
|
-
- **Continuous validation** - lint check, then browser test after every change
|
|
212
|
-
|
|
213
|
-
### Validation Checklist
|
|
214
|
-
After EVERY code change:
|
|
215
|
-
1. Run `read_lints` on modified files
|
|
216
|
-
2. Fix ALL linter and TypeScript errors (zero tolerance)
|
|
217
|
-
3. Reload browser (see @browser-irisx-development for reload strategy)
|
|
218
|
-
4. Check browser console and network tab for errors
|
|
219
|
-
5. Verify UI renders correctly
|
|
220
|
-
6. Take screenshot if visual changes
|
|
221
|
-
7. Fix any issues before proceeding
|
|
222
|
-
|
|
223
|
-
### Data & UI Development
|
|
224
|
-
- **GraphQL**: Use Trackunit MCP `introspect-schema` and `validate-query`, then implement per @irisx-app-sdk-graphql
|
|
225
|
-
- **Components**: Use Trackunit MCP `get-story-code` to find patterns, implement, verify in browser
|
|
226
|
-
- **Real Data**: Always use Trackunit GraphQL API, never mock data unless explicitly requested
|