@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.
Files changed (41) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +1 -1
  3. package/src/executors/submit/executor.js.map +1 -0
  4. package/src/executors/unpublish/executor.js.map +1 -0
  5. package/src/executors/utils/authentication.js.map +1 -0
  6. package/src/executors/utils/irisAppServerSettings.js.map +1 -0
  7. package/src/executors/utils/src/index.js.map +1 -0
  8. package/src/generators/ai-agent-sync/README.md +5 -2
  9. package/src/generators/ai-agent-sync/generator.d.ts +1 -1
  10. package/src/generators/ai-agent-sync/generator.js +29 -4
  11. package/src/generators/ai-agent-sync/generator.js.map +1 -0
  12. package/src/generators/create/generator.js.map +1 -0
  13. package/src/generators/extend/dependencies.js.map +1 -0
  14. package/src/generators/extend/generator.js.map +1 -0
  15. package/src/generators/preset/files/.agents/skills/browser-testing/SKILL.md +193 -0
  16. package/src/generators/preset/files/.agents/skills/create-app/SKILL.md +191 -0
  17. package/src/generators/preset/files/.agents/skills/customfields/SKILL.md +239 -0
  18. package/src/generators/preset/files/.agents/skills/graphql/SKILL.md +147 -0
  19. package/src/generators/preset/files/.agents/skills/graphql-timeseries/SKILL.md +193 -0
  20. package/src/generators/preset/files/.agents/skills/irisx-app-sdk/SKILL.md +116 -0
  21. package/src/generators/preset/files/.agents/skills/react-core-hooks/SKILL.md +215 -0
  22. package/src/generators/preset/files/.agents/skills/tables-and-sorting/SKILL.md +122 -0
  23. package/src/generators/preset/files/.agents/skills/widget-extensions/SKILL.md +245 -0
  24. package/src/generators/preset/files/.cursor/mcp.json +4 -0
  25. package/src/generators/preset/generator.js.map +1 -0
  26. package/src/generators/preset/root-files/AGENTS.md +43 -0
  27. package/src/generators/preset/root-files/CLAUDE.md +17 -0
  28. package/src/index.js.map +1 -0
  29. package/src/utils/ast/astUtils.js.map +1 -0
  30. package/src/utils/fileUpdater.js.map +1 -0
  31. package/src/generators/preset/files/.cursor/commands/create-app.md +0 -226
  32. package/src/generators/preset/files/.cursor/rules/browser-irisx-development.mdc +0 -246
  33. package/src/generators/preset/files/.cursor/rules/graphql-timeseries.md +0 -260
  34. package/src/generators/preset/files/.cursor/rules/irisx-app-sdk-customfields.md +0 -305
  35. package/src/generators/preset/files/.cursor/rules/irisx-app-sdk-graphql.md +0 -30
  36. package/src/generators/preset/files/.cursor/rules/irisx-app-sdk.mdc +0 -82
  37. package/src/generators/preset/files/.cursor/rules/react-core-hooks.md +0 -155
  38. package/src/generators/preset/files/.cursor/rules/rules-index.mdc +0 -10
  39. package/src/generators/preset/files/.cursor/rules/structured-development.mdc +0 -86
  40. package/src/generators/preset/files/.cursor/rules/tables-and-sorting.mdc +0 -126
  41. 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 |
@@ -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 @@
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.
@@ -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