@adcops/autocore-react 3.3.113 → 3.3.116

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 (69) hide show
  1. package/dist/components/index.d.ts +10 -2
  2. package/dist/components/index.d.ts.map +1 -1
  3. package/dist/components/index.js +1 -1
  4. package/dist/components/tis/CycleConfigDialog.d.ts +39 -0
  5. package/dist/components/tis/CycleConfigDialog.d.ts.map +1 -0
  6. package/dist/components/tis/CycleConfigDialog.js +1 -0
  7. package/dist/components/tis/CycleConfigForm.d.ts +22 -0
  8. package/dist/components/tis/CycleConfigForm.d.ts.map +1 -0
  9. package/dist/components/tis/CycleConfigForm.js +1 -0
  10. package/dist/components/tis/CycleStatusStrip.d.ts +10 -0
  11. package/dist/components/tis/CycleStatusStrip.d.ts.map +1 -0
  12. package/dist/components/tis/CycleStatusStrip.js +1 -0
  13. package/dist/components/tis/ResultHistoryTable.d.ts.map +1 -1
  14. package/dist/components/tis/ResultHistoryTable.js +1 -1
  15. package/dist/components/tis/ScienceTable.d.ts +15 -0
  16. package/dist/components/tis/ScienceTable.d.ts.map +1 -1
  17. package/dist/components/tis/ScienceTable.js +1 -1
  18. package/dist/components/tis/TestDataView.d.ts +14 -4
  19. package/dist/components/tis/TestDataView.d.ts.map +1 -1
  20. package/dist/components/tis/TestDataView.js +1 -1
  21. package/dist/components/tis/TestFieldRow.d.ts +151 -0
  22. package/dist/components/tis/TestFieldRow.d.ts.map +1 -0
  23. package/dist/components/tis/TestFieldRow.js +1 -0
  24. package/dist/components/tis/TestSetupForm.d.ts +15 -51
  25. package/dist/components/tis/TestSetupForm.d.ts.map +1 -1
  26. package/dist/components/tis/TestSetupForm.js +1 -1
  27. package/dist/components/tis/TestSetupView.d.ts.map +1 -1
  28. package/dist/components/tis/TestSetupView.js +1 -1
  29. package/dist/components/tis/TisProvider.d.ts +80 -0
  30. package/dist/components/tis/TisProvider.d.ts.map +1 -1
  31. package/dist/components/tis/TisProvider.js +1 -1
  32. package/dist/components/tis-editor/TisConfigEditor.d.ts.map +1 -1
  33. package/dist/components/tis-editor/TisConfigEditor.js +1 -1
  34. package/dist/components/tis-editor/editor/CycleConfigSection.d.ts +19 -0
  35. package/dist/components/tis-editor/editor/CycleConfigSection.d.ts.map +1 -0
  36. package/dist/components/tis-editor/editor/CycleConfigSection.js +1 -0
  37. package/dist/components/tis-editor/editor/FieldArrayEditor.d.ts.map +1 -1
  38. package/dist/components/tis-editor/editor/FieldArrayEditor.js +1 -1
  39. package/dist/components/tis-editor/editor/MethodFormEditor.d.ts.map +1 -1
  40. package/dist/components/tis-editor/editor/MethodFormEditor.js +1 -1
  41. package/dist/components/tis-editor/editor/TestFieldDialog.d.ts +3 -0
  42. package/dist/components/tis-editor/editor/TestFieldDialog.d.ts.map +1 -1
  43. package/dist/components/tis-editor/editor/TestFieldDialog.js +1 -1
  44. package/dist/components/tis-editor/editor/ViewsEditor.d.ts.map +1 -1
  45. package/dist/components/tis-editor/editor/ViewsEditor.js +1 -1
  46. package/dist/components/tis-editor/types.d.ts +28 -1
  47. package/dist/components/tis-editor/types.d.ts.map +1 -1
  48. package/dist/components/tis-editor/validation.d.ts.map +1 -1
  49. package/dist/components/tis-editor/validation.js +1 -1
  50. package/package.json +110 -110
  51. package/src/components/index.ts +14 -0
  52. package/src/components/tis/CycleConfigDialog.tsx +173 -0
  53. package/src/components/tis/CycleConfigForm.tsx +192 -0
  54. package/src/components/tis/CycleStatusStrip.tsx +84 -0
  55. package/src/components/tis/ResultHistoryTable.tsx +141 -1
  56. package/src/components/tis/ScienceTable.tsx +71 -12
  57. package/src/components/tis/TestDataView.tsx +82 -7
  58. package/src/components/tis/TestFieldRow.tsx +297 -0
  59. package/src/components/tis/TestSetupForm.tsx +52 -212
  60. package/src/components/tis/TestSetupView.tsx +60 -3
  61. package/src/components/tis/TisProvider.tsx +191 -0
  62. package/src/components/tis-editor/TisConfigEditor.tsx +1 -0
  63. package/src/components/tis-editor/editor/CycleConfigSection.tsx +93 -0
  64. package/src/components/tis-editor/editor/FieldArrayEditor.tsx +10 -0
  65. package/src/components/tis-editor/editor/MethodFormEditor.tsx +10 -4
  66. package/src/components/tis-editor/editor/TestFieldDialog.tsx +22 -2
  67. package/src/components/tis-editor/editor/ViewsEditor.tsx +3 -0
  68. package/src/components/tis-editor/types.ts +34 -1
  69. package/src/components/tis-editor/validation.ts +20 -2
@@ -25,6 +25,15 @@ export interface TestField {
25
25
  * non-excluded runs sharing this test's sample_id. When set, the field
26
26
  * is NOT supplied by the analysis script — the server computes it. */
27
27
  aggregate?: AggregateSpec;
28
+ /** cycle_fields only: operator-correctable after recording. Measured
29
+ * columns must stay unflagged. cycle_config_fields don't need it —
30
+ * they're annotation and are always correctable. */
31
+ editable?: boolean;
32
+ /** cycle_config_fields only: clear the operator's entry once a row has
33
+ * recorded it, so the next row can't inherit a stale annotation. Pair
34
+ * it with `required` — on its own it turns stale data into silently
35
+ * missing data. */
36
+ autoclear?: boolean;
28
37
  }
29
38
  export type AggFn = 'avg' | 'min' | 'max' | 'stddev' | 'count';
30
39
  export type AggFrom = 'cycle_fields' | 'results';
@@ -93,11 +102,29 @@ export interface TestConfiguration {
93
102
  description?: string;
94
103
  defaults?: Record<string, unknown>;
95
104
  }
105
+ /**
106
+ * Which boundary an operator annotates `cycle_config_fields` at.
107
+ *
108
+ * 'run' one latch per Start — the same values stamp every cycle of the
109
+ * run and clear when it closes. Default; correct for a machine
110
+ * that records many cycles from one Start.
111
+ * 'cycle' one latch per cycle — values clear after each recorded row and
112
+ * the gate re-arms, so every row is annotated separately.
113
+ */
114
+ export type CycleConfigScope = 'run' | 'cycle';
96
115
  export interface TestMethod {
97
116
  label?: string;
98
117
  description?: string;
99
118
  project_fields?: TestField[];
100
119
  config_fields?: TestField[];
120
+ /** Operator-entered per-row annotation, recorded as the first Cycle Data
121
+ * columns after the row index. */
122
+ cycle_config_fields?: TestField[];
123
+ /** Boundary at which cycle_config_fields latch and autoclear. */
124
+ cycle_config_scope?: CycleConfigScope;
125
+ /** Open the Cycle Configuration dialog at the boundary rather than
126
+ * relying on the operator having filled the Cycle tab. */
127
+ prompt_before_cycle?: boolean;
101
128
  cycle_fields?: TestField[];
102
129
  results_fields?: TestField[];
103
130
  raw_data?: RawDataShape | null;
@@ -107,5 +134,5 @@ export interface TestMethod {
107
134
  configurations?: TestConfiguration[];
108
135
  [key: string]: unknown;
109
136
  }
110
- export type FieldArrayKey = 'project_fields' | 'config_fields' | 'cycle_fields' | 'results_fields';
137
+ export type FieldArrayKey = 'project_fields' | 'config_fields' | 'cycle_config_fields' | 'cycle_fields' | 'results_fields';
111
138
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/tis-editor/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/tis-editor/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/components/tis-editor/validation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACG,UAAU,EACxB,MAAM,SAAS,CAAC;AAIjB,MAAM,WAAW,eAAe;IAC5B,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,GAAG,eAAe,EAAE,CAyIjF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAOtG"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/components/tis-editor/validation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACG,UAAU,EACxB,MAAM,SAAS,CAAC;AAMjB,MAAM,WAAW,eAAe;IAC5B,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,GAAG,eAAe,EAAE,CAyJjF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAOtG"}
@@ -1 +1 @@
1
- const FIELD_ARRAY_KEYS=["project_fields","config_fields","cycle_fields","results_fields"];export function validateMethod(e,a){const s=[];for(const t of FIELD_ARRAY_KEYS){const n=a[t]??[],o=new Set;n.forEach((a,n)=>{a.name&&""!==a.name.trim()?o.has(a.name)?s.push({path:`${t}.${n}.name`,message:`${e}.${t}: duplicate field name "${a.name}"`}):o.add(a.name):s.push({path:`${t}.${n}.name`,message:`${e}.${t}: empty field name`}),"number"==typeof a.min&&"number"==typeof a.max&&a.min>a.max&&s.push({path:`${t}.${n}.min`,message:`${e}.${t}.${a.name}: min (${a.min}) is greater than max (${a.max})`})})}const t=new Set(["i32","i64","u32","u64","f32","f64"]),n=new Map((a.cycle_fields??[]).map(e=>[e.name,String(e.type)])),o=new Map((a.results_fields??[]).map(e=>[e.name,String(e.type)]));for(const t of["project_fields","config_fields","cycle_fields"])(a[t]??[]).forEach((a,n)=>{a.aggregate&&s.push({path:`${t}.${n}.aggregate`,message:`${e}.${t}.${a.name}: aggregate is only valid on results_fields`})});(a.results_fields??[]).forEach((a,i)=>{const r=a.aggregate;if(!r||"count"===r.fn)return;const f=r.of?.trim();if(!f)return void s.push({path:`results_fields.${i}.aggregate`,message:`${e}.results_fields.${a.name}: aggregate "${r.fn}" requires a target field (of)`});const m="results"===(r.from??"cycle_fields")?o:n,c="results"===(r.from??"cycle_fields")?"results_fields":"cycle_fields",l=m.get(f);void 0===l?s.push({path:`results_fields.${i}.aggregate`,message:`${e}.results_fields.${a.name}: aggregate "of" references "${f}" which is not a ${c} of this method`}):t.has(l)||s.push({path:`results_fields.${i}.aggregate`,message:`${e}.results_fields.${a.name}: aggregate target "${f}" has non-numeric type "${l}"`})});const i=new Set;for(const e of FIELD_ARRAY_KEYS){(a[e]??[]).forEach(e=>e.name&&i.add(e.name))}const r=new Set(Object.keys(a.raw_data?.columns??{})),f=e=>e.field&&e.field.trim()?{key:e.field,ok:i.has(e.field)}:e.column&&e.column.trim()?{key:e.column,ok:r.has(e.column)}:null,m=a.views??{};for(const[a,t]of Object.entries(m)){if(t?.x){const n=f(t.x);n&&!n.ok&&s.push({path:`views.${a}.x`,message:`${e}.views.${a}: x.field/column "${n.key}" does not match any field or raw_data column`})}(t?.y??[]).forEach((t,n)=>{const o=f(t);o&&!o.ok&&s.push({path:`views.${a}.y.${n}`,message:`${e}.views.${a}.y[${n}]: field/column "${o.key}" does not match any field or raw_data column`})})}const c=a.raw_data;c&&(c.blob_name&&""!==String(c.blob_name).trim()||s.push({path:"raw_data.blob_name",message:`${e}.raw_data: blob_name is empty`}));const l=new Set((a.config_fields??[]).map(e=>e.name).filter(Boolean)),g=a.configurations??[],d=new Set;return g.forEach((a,t)=>{const n="string"==typeof a?.name?a.name.trim():"";n?d.has(n)?s.push({path:`configurations.${t}.name`,message:`${e}.configurations: duplicate configuration name "${n}"`}):d.add(n):s.push({path:`configurations.${t}.name`,message:`${e}.configurations[${t}]: empty configuration name`});const o=a?.defaults??{};for(const a of Object.keys(o))l.has(a)||s.push({path:`configurations.${t}.defaults.${a}`,message:`${e}.configurations.${n||t}: override "${a}" does not match any config_field`})}),s}export function validateMethods(e){const a={};for(const[s,t]of Object.entries(e)){const e=validateMethod(s,t);e.length>0&&(a[s]=e)}return a}
1
+ const FIELD_ARRAY_KEYS=["project_fields","config_fields","cycle_config_fields","cycle_fields","results_fields"];export function validateMethod(e,a){const s=[];for(const n of FIELD_ARRAY_KEYS){const t=a[n]??[],o=new Set;t.forEach((a,t)=>{a.name&&""!==a.name.trim()?o.has(a.name)?s.push({path:`${n}.${t}.name`,message:`${e}.${n}: duplicate field name "${a.name}"`}):o.add(a.name):s.push({path:`${n}.${t}.name`,message:`${e}.${n}: empty field name`}),"number"==typeof a.min&&"number"==typeof a.max&&a.min>a.max&&s.push({path:`${n}.${t}.min`,message:`${e}.${n}.${a.name}: min (${a.min}) is greater than max (${a.max})`})})}const n=new Set(["i32","i64","u32","u64","f32","f64"]),t=new Map((a.cycle_fields??[]).map(e=>[e.name,String(e.type)])),o=new Map((a.results_fields??[]).map(e=>[e.name,String(e.type)]));for(const n of["project_fields","config_fields","cycle_config_fields","cycle_fields"])(a[n]??[]).forEach((a,t)=>{a.aggregate&&s.push({path:`${n}.${t}.aggregate`,message:`${e}.${n}.${a.name}: aggregate is only valid on results_fields`})});(a.results_fields??[]).forEach((a,i)=>{const c=a.aggregate;if(!c||"count"===c.fn)return;const f=c.of?.trim();if(!f)return void s.push({path:`results_fields.${i}.aggregate`,message:`${e}.results_fields.${a.name}: aggregate "${c.fn}" requires a target field (of)`});const r="results"===(c.from??"cycle_fields")?o:t,l="results"===(c.from??"cycle_fields")?"results_fields":"cycle_fields",m=r.get(f);void 0===m?s.push({path:`results_fields.${i}.aggregate`,message:`${e}.results_fields.${a.name}: aggregate "of" references "${f}" which is not a ${l} of this method`}):n.has(m)||s.push({path:`results_fields.${i}.aggregate`,message:`${e}.results_fields.${a.name}: aggregate target "${f}" has non-numeric type "${m}"`})});const i=new Set((a.cycle_fields??[]).map(e=>e.name).filter(Boolean));(a.cycle_config_fields??[]).forEach((a,n)=>{a.name&&i.has(a.name)&&s.push({path:`cycle_config_fields.${n}.name`,message:`${e}: "${a.name}" is declared as both a cycle_config_field and a cycle_field. They share one row namespace; rename one.`})});const c=new Set;for(const e of FIELD_ARRAY_KEYS){(a[e]??[]).forEach(e=>e.name&&c.add(e.name))}const f=new Set(Object.keys(a.raw_data?.columns??{})),r=e=>e.field&&e.field.trim()?{key:e.field,ok:c.has(e.field)}:e.column&&e.column.trim()?{key:e.column,ok:f.has(e.column)}:null,l=a.views??{};for(const[a,n]of Object.entries(l)){if(n?.x){const t=r(n.x);t&&!t.ok&&s.push({path:`views.${a}.x`,message:`${e}.views.${a}: x.field/column "${t.key}" does not match any field or raw_data column`})}(n?.y??[]).forEach((n,t)=>{const o=r(n);o&&!o.ok&&s.push({path:`views.${a}.y.${t}`,message:`${e}.views.${a}.y[${t}]: field/column "${o.key}" does not match any field or raw_data column`})})}const m=a.raw_data;m&&(m.blob_name&&""!==String(m.blob_name).trim()||s.push({path:"raw_data.blob_name",message:`${e}.raw_data: blob_name is empty`}));const d=new Set((a.config_fields??[]).map(e=>e.name).filter(Boolean)),g=a.configurations??[],u=new Set;return g.forEach((a,n)=>{const t="string"==typeof a?.name?a.name.trim():"";t?u.has(t)?s.push({path:`configurations.${n}.name`,message:`${e}.configurations: duplicate configuration name "${t}"`}):u.add(t):s.push({path:`configurations.${n}.name`,message:`${e}.configurations[${n}]: empty configuration name`});const o=a?.defaults??{};for(const a of Object.keys(o))d.has(a)||s.push({path:`configurations.${n}.defaults.${a}`,message:`${e}.configurations.${t||n}: override "${a}" does not match any config_field`})}),s}export function validateMethods(e){const a={};for(const[s,n]of Object.entries(e)){const e=validateMethod(s,n);e.length>0&&(a[s]=e)}return a}
package/package.json CHANGED
@@ -1,118 +1,118 @@
1
1
  {
2
- "name": "@adcops/autocore-react",
3
- "version": "3.3.113",
4
- "description": "A React component library for industrial user interfaces.",
5
- "private": false,
6
- "type": "module",
7
- "scripts": {
8
- "clean": "rimraf ./dist",
9
- "minify": "node ./tools/minify.cjs",
10
- "tsc": "tsc",
11
- "build-themes": "node ./tools/build-themes.cjs",
12
- "generate-docs": "typedoc",
13
- "build": "npm run clean && tsc && node ./tools/copy-distribution-files.cjs && npm run build-themes && npm run minify",
14
- "build:dev": "npm run clean && tsc && node ./tools/copy-distribution-files.cjs",
15
- "dev": "vite",
16
- "playground": "vite",
17
- "playground:build": "vite build",
18
- "prestart": "tsc",
19
- "publish-package": "npm run build && npm version patch && npm publish",
20
- "prepublishOnly": "npm run build",
21
- "release:patch": "npm version patch && npm publish",
22
- "release:minor": "npm version minor && npm publish",
23
- "release:major": "npm version major && npm publish",
24
- "convert-assets": "npx @svgr/cli --typescript --out-dir src/assets/ src/assets/svg/ --jsx-runtime automatic --ignore-existing"
25
- },
26
- "repository": {
27
- "type": "git",
28
- "url": "git+https://adc360.visualstudio.com/AutoCore-React/_git/autocore-react"
29
- },
30
- "keywords": [
31
- "react",
32
- "autocore",
33
- "component-library",
34
- "industrial-ui",
35
- "typescript",
36
- "primereact"
37
- ],
38
- "author": "Automated Design Corp.",
39
- "license": "ISC",
40
- "bugs": {
41
- "url": "https://automateddesign.com"
42
- },
43
- "homepage": "https://automateddesign.com",
44
- "peerDependencies": {
45
- "@types/react": "^18",
46
- "primereact": ">=10.9.7",
47
- "react": "^18",
48
- "react-dom": "^18"
2
+ "name": "@adcops/autocore-react",
3
+ "version": "3.3.116",
4
+ "description": "A React component library for industrial user interfaces.",
5
+ "private": false,
6
+ "type": "module",
7
+ "scripts": {
8
+ "clean": "rimraf ./dist",
9
+ "minify": "node ./tools/minify.cjs",
10
+ "tsc": "tsc",
11
+ "build-themes": "node ./tools/build-themes.cjs",
12
+ "generate-docs": "typedoc",
13
+ "build": "npm run clean && tsc && node ./tools/copy-distribution-files.cjs && npm run build-themes && npm run minify",
14
+ "build:dev": "npm run clean && tsc && node ./tools/copy-distribution-files.cjs",
15
+ "dev": "vite",
16
+ "playground": "vite",
17
+ "playground:build": "vite build",
18
+ "prestart": "tsc",
19
+ "publish-package": "npm run build && npm version patch && npm publish",
20
+ "prepublishOnly": "npm run build",
21
+ "release:patch": "npm version patch && npm publish",
22
+ "release:minor": "npm version minor && npm publish",
23
+ "release:major": "npm version major && npm publish",
24
+ "convert-assets": "npx @svgr/cli --typescript --out-dir src/assets/ src/assets/svg/ --jsx-runtime automatic --ignore-existing"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://adc360.visualstudio.com/AutoCore-React/_git/autocore-react"
29
+ },
30
+ "keywords": [
31
+ "react",
32
+ "autocore",
33
+ "component-library",
34
+ "industrial-ui",
35
+ "typescript",
36
+ "primereact"
37
+ ],
38
+ "author": "Automated Design Corp.",
39
+ "license": "ISC",
40
+ "bugs": {
41
+ "url": "https://automateddesign.com"
42
+ },
43
+ "homepage": "https://automateddesign.com",
44
+ "peerDependencies": {
45
+ "@types/react": "^18",
46
+ "primereact": ">=10.9.7",
47
+ "react": "^18",
48
+ "react-dom": "^18"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "@types/react": {
52
+ "optional": true
53
+ }
54
+ },
55
+ "dependencies": {
56
+ "@monaco-editor/react": "^4.7.0",
57
+ "@tauri-apps/api": "^2.9.1",
58
+ "chart.js": "^4.5.1",
59
+ "chartjs-plugin-annotation": "^3.1.0",
60
+ "chartjs-plugin-zoom": "^2.2.0",
61
+ "clsx": "^2.1.1",
62
+ "numerable": "^0.3.15",
63
+ "react-blockly": "^8.1.2",
64
+ "react-chartjs-2": "^5.3.0",
65
+ "react-number-format": "^5.4.5",
66
+ "react-simple-keyboard": "^3.8.120",
67
+ "react-transition-group": "^4.4.5",
68
+ "sass": "^1.92.1",
69
+ "socket.io-client": "^4.8.1",
70
+ "use-fit-text": "^2.4.0"
71
+ },
72
+ "sideEffects": [
73
+ "**/*.css",
74
+ "**/*.scss"
75
+ ],
76
+ "engines": {
77
+ "node": ">=20.0.0"
78
+ },
79
+ "devDependencies": {
80
+ "@svgr/cli": "^8.1.0",
81
+ "@types/react": "^18.2.15",
82
+ "@types/react-dom": "^18.2.7",
83
+ "@types/react-transition-group": "^4.4.12",
84
+ "@vitejs/plugin-react": "^6.0.2",
85
+ "primereact": "^10.9.7",
86
+ "rimraf": "^6.0.1",
87
+ "terser": "^5.44.0",
88
+ "typedoc": "^0.28.12",
89
+ "typescript": "^5.9.2",
90
+ "vite": "^8.0.13"
91
+ },
92
+ "exports": {
93
+ "./core/*": {
94
+ "import": "./dist/core/*",
95
+ "types": "./dist/core/*.d.ts"
49
96
  },
50
- "peerDependenciesMeta": {
51
- "@types/react": {
52
- "optional": true
53
- }
97
+ "./components/*": {
98
+ "import": "./dist/components/*",
99
+ "types": "./dist/components/*.d.ts"
54
100
  },
55
- "dependencies": {
56
- "@monaco-editor/react": "^4.7.0",
57
- "@tauri-apps/api": "^2.9.1",
58
- "chart.js": "^4.5.1",
59
- "chartjs-plugin-annotation": "^3.1.0",
60
- "chartjs-plugin-zoom": "^2.2.0",
61
- "clsx": "^2.1.1",
62
- "numerable": "^0.3.15",
63
- "react-blockly": "^8.1.2",
64
- "react-chartjs-2": "^5.3.0",
65
- "react-number-format": "^5.4.5",
66
- "react-simple-keyboard": "^3.8.120",
67
- "react-transition-group": "^4.4.5",
68
- "sass": "^1.92.1",
69
- "socket.io-client": "^4.8.1",
70
- "use-fit-text": "^2.4.0"
101
+ "./assets/*": {
102
+ "import": "./dist/assets/*",
103
+ "types": "./dist/assets/*.d.ts"
71
104
  },
72
- "sideEffects": [
73
- "**/*.css",
74
- "**/*.scss"
75
- ],
76
- "engines": {
77
- "node": ">=20.0.0"
105
+ "./hooks/*": {
106
+ "import": "./dist/hooks/*",
107
+ "types": "./dist/hooks/*.d.ts"
78
108
  },
79
- "devDependencies": {
80
- "@svgr/cli": "^8.1.0",
81
- "@types/react": "^18.2.15",
82
- "@types/react-dom": "^18.2.7",
83
- "@types/react-transition-group": "^4.4.12",
84
- "@vitejs/plugin-react": "^6.0.2",
85
- "primereact": "^10.9.7",
86
- "rimraf": "^6.0.1",
87
- "terser": "^5.44.0",
88
- "typedoc": "^0.28.12",
89
- "typescript": "^5.9.2",
90
- "vite": "^8.0.13"
109
+ "./themes/*": {
110
+ "import": "./dist/themes/*",
111
+ "types": "./dist/themes/*.d.ts"
91
112
  },
92
- "exports": {
93
- "./core/*": {
94
- "import": "./dist/core/*",
95
- "types": "./dist/core/*.d.ts"
96
- },
97
- "./components/*": {
98
- "import": "./dist/components/*",
99
- "types": "./dist/components/*.d.ts"
100
- },
101
- "./assets/*": {
102
- "import": "./dist/assets/*",
103
- "types": "./dist/assets/*.d.ts"
104
- },
105
- "./hooks/*": {
106
- "import": "./dist/hooks/*",
107
- "types": "./dist/hooks/*.d.ts"
108
- },
109
- "./themes/*": {
110
- "import": "./dist/themes/*",
111
- "types": "./dist/themes/*.d.ts"
112
- },
113
- "./hub/*": {
114
- "import": "./dist/hub/*",
115
- "types": "./dist/hub/*.d.ts"
116
- }
113
+ "./hub/*": {
114
+ "import": "./dist/hub/*",
115
+ "types": "./dist/hub/*.d.ts"
117
116
  }
117
+ }
118
118
  }
@@ -15,6 +15,7 @@ export {
15
15
  useTisSelection,
16
16
  useTisRuns,
17
17
  useTisRun,
18
+ useTisCycleConfig,
18
19
  } from './tis/TisProvider';
19
20
  export type {
20
21
  TisProviderProps,
@@ -25,6 +26,7 @@ export type {
25
26
  TisRunCacheEntry,
26
27
  SchemaRegistry,
27
28
  TisMethodSchema,
29
+ TisCycleConfigState,
28
30
  } from './tis/TisProvider';
29
31
 
30
32
  export { ProjectSelector } from './tis/ProjectSelector';
@@ -36,6 +38,18 @@ export type { TestSetupFormProps } from './tis/TestSetupForm';
36
38
  export { TestSetupView } from './tis/TestSetupView';
37
39
  export type { TestSetupViewProps } from './tis/TestSetupView';
38
40
 
41
+ export { TestFieldRow } from './tis/TestFieldRow';
42
+ export type { TestFieldRowProps, CycleConfigScope } from './tis/TestFieldRow';
43
+
44
+ export { CycleConfigForm, cycleConfigSummary } from './tis/CycleConfigForm';
45
+ export type { CycleConfigFormProps } from './tis/CycleConfigForm';
46
+
47
+ export { CycleConfigDialog, useCycleConfigGate } from './tis/CycleConfigDialog';
48
+ export type { CycleConfigDialogProps, CycleConfigGate } from './tis/CycleConfigDialog';
49
+
50
+ export { CycleStatusStrip } from './tis/CycleStatusStrip';
51
+ export type { CycleStatusStripProps } from './tis/CycleStatusStrip';
52
+
39
53
  export { ProjectInfoDialog } from './tis/ProjectInfoDialog';
40
54
  export type { ProjectInfoDialogProps, ProjectInfoMode } from './tis/ProjectInfoDialog';
41
55
 
@@ -0,0 +1,173 @@
1
+ /*
2
+ * Copyright (C) 2026 Automated Design Corp. All Rights Reserved.
3
+ *
4
+ * <CycleConfigDialog> — the `prompt_before_cycle` entry dialog.
5
+ *
6
+ * Some methods can't tolerate a mislabelled row: getting the position
7
+ * wrong means re-running the sample. Those declare
8
+ * `prompt_before_cycle: true`, and this dialog puts the entry in front of
9
+ * the operator at the boundary instead of trusting them to have filled
10
+ * the Cycle tab. It opens on the server-computed `prompt_due` edge — a
11
+ * boundary reached with nothing latched — so the scope rules stay in one
12
+ * place (the server) rather than being re-derived here.
13
+ *
14
+ * Two ways to use it:
15
+ *
16
+ * 1. Mounted plainly, it opens by itself whenever a prompt comes due:
17
+ *
18
+ * <CycleConfigDialog />
19
+ *
20
+ * 2. Driven from your Start handler, so the press itself opens it when
21
+ * the annotation is missing and runs the start when it isn't:
22
+ *
23
+ * const gate = useCycleConfigGate();
24
+ * <Button label="Start" onClick={() => gate.run(startTest)} />
25
+ * <CycleConfigDialog controller={gate} />
26
+ *
27
+ * The second form is the one to reach for on a machine whose Start lives
28
+ * in the footer: the press is never silently swallowed — it either starts
29
+ * the test or explains, in a dialog, exactly what is missing.
30
+ */
31
+
32
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
33
+ import { Button } from 'primereact/button';
34
+ import { Dialog } from 'primereact/dialog';
35
+ import { useTis } from './TisProvider';
36
+ import { CycleConfigForm } from './CycleConfigForm';
37
+ import { labelOf } from './TestFieldRow';
38
+
39
+ /** Handle returned by [`useCycleConfigGate`]. */
40
+ export interface CycleConfigGate {
41
+ /** True while the dialog should be open. */
42
+ open: boolean;
43
+ setOpen: (open: boolean) => void;
44
+ /**
45
+ * Gate an action on the annotation being complete. When the entry is
46
+ * ready, `action` runs immediately. When it isn't, the dialog opens
47
+ * instead and `action` is held until the operator completes the fields
48
+ * and confirms — so a Start press is deferred, never dropped.
49
+ */
50
+ run: (action: () => void) => void;
51
+ /** Run the held action and close. Called by the dialog's confirm. */
52
+ confirm: () => void;
53
+ /** Drop the held action and close. */
54
+ cancel: () => void;
55
+ /** True when the gate is blocking (required fields empty). */
56
+ blocked: boolean;
57
+ }
58
+
59
+ /**
60
+ * Wire a Start affordance to the Cycle Configuration gate.
61
+ *
62
+ * Deliberately does NOT return a `disabled` flag for Start. A greyed-out
63
+ * Start in the corner of a touchscreen reads as "the machine is broken"
64
+ * and the operator's next move is a phone call; a press that opens a
65
+ * dialog naming the missing field teaches the workflow in one go.
66
+ */
67
+ export const useCycleConfigGate = (): CycleConfigGate => {
68
+ const tis = useTis();
69
+ const [open, setOpen] = useState(false);
70
+ // Held across the dialog's lifetime rather than kept in state: it is
71
+ // never rendered, and re-rendering on every Start press would reopen
72
+ // the question of whether the action is stale.
73
+ const held = useRef<(() => void) | null>(null);
74
+ const blocked = tis.cycleConfigFields.length > 0 && !tis.cycleConfig.ready;
75
+
76
+ const run = useCallback((action: () => void) => {
77
+ const needsEntry = tis.cycleConfigFields.length > 0
78
+ && (!tis.cycleConfig.ready || tis.cycleConfig.promptDue);
79
+ if (!needsEntry) { action(); return; }
80
+ held.current = action;
81
+ setOpen(true);
82
+ }, [tis.cycleConfigFields.length, tis.cycleConfig.ready, tis.cycleConfig.promptDue]);
83
+
84
+ const confirm = useCallback(() => {
85
+ const action = held.current;
86
+ held.current = null;
87
+ setOpen(false);
88
+ action?.();
89
+ }, []);
90
+
91
+ const cancel = useCallback(() => {
92
+ held.current = null;
93
+ setOpen(false);
94
+ }, []);
95
+
96
+ return { open, setOpen, run, confirm, cancel, blocked };
97
+ };
98
+
99
+ export interface CycleConfigDialogProps {
100
+ /** Gate handle from [`useCycleConfigGate`]. Omit to let the dialog
101
+ * open itself on the server's `prompt_due` edge. */
102
+ controller?: CycleConfigGate;
103
+ /** Label on the confirm button. Default "Continue". Set it to
104
+ * "Start" when the dialog is gating a Start press. */
105
+ confirmLabel?: string;
106
+ }
107
+
108
+ export const CycleConfigDialog: React.FC<CycleConfigDialogProps> = ({
109
+ controller, confirmLabel = 'Continue',
110
+ }) => {
111
+ const tis = useTis();
112
+ const cfg = tis.cycleConfig;
113
+ const fields = tis.cycleConfigFields;
114
+
115
+ const [selfOpen, setSelfOpen] = useState(false);
116
+ const open = controller ? controller.open : selfOpen;
117
+
118
+ // Self-opening mode: follow the server's prompt_due edge. Rising edge
119
+ // only — an operator who dismisses the dialog isn't re-prompted until
120
+ // the next boundary, and the `tis.cycle_ready` gate still stops the
121
+ // machine in the meantime.
122
+ const prevPromptDue = useRef(false);
123
+ useEffect(() => {
124
+ if (controller) return;
125
+ const due = cfg.promptBeforeCycle && cfg.promptDue && fields.length > 0;
126
+ if (due && !prevPromptDue.current) setSelfOpen(true);
127
+ prevPromptDue.current = due;
128
+ }, [controller, cfg.promptBeforeCycle, cfg.promptDue, fields.length]);
129
+
130
+ // With a controller, confirming releases the held action (the Start
131
+ // press that was deferred). Self-opening, there's nothing to release.
132
+ const confirm = () => (controller ? controller.confirm() : setSelfOpen(false));
133
+ const cancel = () => (controller ? controller.cancel() : setSelfOpen(false));
134
+
135
+ const missingLabels = cfg.missing
136
+ .map(n => fields.find(f => f.name === n))
137
+ .filter((f): f is NonNullable<typeof f> => !!f)
138
+ .map(labelOf);
139
+
140
+ return (
141
+ <Dialog
142
+ header="Cycle Configuration"
143
+ visible={open}
144
+ onHide={cancel}
145
+ style={{ width: '34rem', maxWidth: '95vw' }}
146
+ modal
147
+ footer={
148
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '1rem' }}>
149
+ <span style={{ color: 'var(--text-secondary-color)', textAlign: 'left' }}>
150
+ {cfg.ready
151
+ ? 'Recorded on the next cycle.'
152
+ : `Still required: ${missingLabels.join(', ')}`}
153
+ </span>
154
+ <span>
155
+ <Button label="Cancel" text onClick={cancel} />
156
+ <Button
157
+ label={confirmLabel}
158
+ icon="pi pi-check"
159
+ // The only place a disabled control is right:
160
+ // this button *is* the start, and starting with
161
+ // the entry incomplete is what we're preventing.
162
+ // The reason is spelled out beside it.
163
+ disabled={!cfg.ready}
164
+ onClick={confirm}
165
+ />
166
+ </span>
167
+ </div>
168
+ }
169
+ >
170
+ <CycleConfigForm compact />
171
+ </Dialog>
172
+ );
173
+ };