@flamingo-stack/openframe-frontend-core 0.0.740 → 0.0.742

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 (206) hide show
  1. package/dist/build/export-map.json +1 -1
  2. package/dist/{chunk-BV5WZVOP.cjs → chunk-32Q7K5OP.cjs} +254 -254
  3. package/dist/{chunk-ZLTCQKTD.cjs → chunk-3LW43GIJ.cjs} +6 -6
  4. package/dist/{chunk-VA7OMDZO.cjs → chunk-4FWURR3W.cjs} +50 -50
  5. package/dist/{chunk-EKGL5K5K.cjs → chunk-4LDU32OB.cjs} +17 -17
  6. package/dist/{chunk-VC4IV3AY.cjs → chunk-4PKORVOX.cjs} +2804 -954
  7. package/dist/{chunk-3ZIQOYNX.cjs → chunk-54EA4RLX.cjs} +8 -8
  8. package/dist/{chunk-YXKB5WRE.cjs → chunk-5MH6BRPY.cjs} +36 -36
  9. package/dist/{chunk-PQ2M7P2U.cjs → chunk-5TCIRWHL.cjs} +9 -9
  10. package/dist/{chunk-WFPCCBOM.cjs → chunk-7AM3AFM4.cjs} +10 -10
  11. package/dist/{chunk-7ZFCR7UY.cjs → chunk-ABLUMGCI.cjs} +3 -3
  12. package/dist/{chunk-PX7FY655.cjs → chunk-ASMNNSUQ.cjs} +3 -3
  13. package/dist/{chunk-HF7Z2E4W.cjs → chunk-BI3XBCQX.cjs} +2 -2
  14. package/dist/{chunk-SA7LDEVJ.cjs → chunk-CSJO7ZGN.cjs} +3 -3
  15. package/dist/{chunk-VOX4E6AX.cjs → chunk-DGFDBPO7.cjs} +7 -5
  16. package/dist/{chunk-KHXZFLIN.cjs → chunk-DKOLQPE7.cjs} +8 -2
  17. package/dist/{chunk-BDA5F6RM.cjs → chunk-DNFSMVXJ.cjs} +16 -16
  18. package/dist/{chunk-B3XBJCOD.cjs → chunk-H3UA7UY5.cjs} +7 -7
  19. package/dist/{chunk-2Y5Y4FLM.cjs → chunk-HDBY2URA.cjs} +64 -64
  20. package/dist/{chunk-UW3YXDC4.cjs → chunk-HIRVNBX5.cjs} +20 -20
  21. package/dist/{chunk-LLAC2UZ3.cjs → chunk-ISJXKKYW.cjs} +28 -28
  22. package/dist/{chunk-FICJS5OA.cjs → chunk-KTPR66EE.cjs} +4 -4
  23. package/dist/{chunk-7UIQPMBJ.cjs → chunk-L34TKFTS.cjs} +21 -164
  24. package/dist/{chunk-AAFW4STC.cjs → chunk-LU7TD6QS.cjs} +5 -5
  25. package/dist/{chunk-3HPB4H4C.cjs → chunk-NTGIQP7O.cjs} +7 -7
  26. package/dist/{chunk-WBANGORJ.cjs → chunk-OBODY7F3.cjs} +3 -3
  27. package/dist/{chunk-GABDE3FM.cjs → chunk-OWGLRE74.cjs} +19 -19
  28. package/dist/{chunk-CWRVTQUO.cjs → chunk-PS6T65KA.cjs} +101 -101
  29. package/dist/{chunk-IFS4AH77.cjs → chunk-QYLIHZKZ.cjs} +10 -10
  30. package/dist/{chunk-GVTDVS5U.cjs → chunk-R34JD6NP.cjs} +3 -3
  31. package/dist/{chunk-VU4CKUJW.cjs → chunk-R6SCSV2I.cjs} +103 -103
  32. package/dist/{chunk-II4QMH26.cjs → chunk-STEJZPIO.cjs} +3 -3
  33. package/dist/{chunk-4RLGLB45.cjs → chunk-T433HSL6.cjs} +47 -47
  34. package/dist/{chunk-DSVNFRY5.cjs → chunk-U2O5YQRP.cjs} +38 -38
  35. package/dist/{chunk-O67V3HJR.cjs → chunk-URVIPSAJ.cjs} +24 -24
  36. package/dist/{chunk-HYBMADYO.cjs → chunk-UWHCRIQQ.cjs} +22 -22
  37. package/dist/{chunk-GSWQZX23.cjs → chunk-UY6A6TEY.cjs} +2 -2
  38. package/dist/{chunk-D5KEWWLW.cjs → chunk-UZLRSFGG.cjs} +32 -32
  39. package/dist/{chunk-DUVOQHQI.cjs → chunk-W2RM764D.cjs} +7 -7
  40. package/dist/{chunk-NTJE6SM6.cjs → chunk-WI2OTGKC.cjs} +2 -2
  41. package/dist/{chunk-SFY4UYPY.cjs → chunk-WIHHA54J.cjs} +44 -44
  42. package/dist/{chunk-YRE3O65E.cjs → chunk-XAA5EZ5T.cjs} +12 -12
  43. package/dist/{chunk-KLI4OSRA.cjs → chunk-YQGDXH3M.cjs} +2 -2
  44. package/dist/{chunk-SWR22DOZ.cjs → chunk-Z2DQYKZA.cjs} +89 -89
  45. package/dist/{chunk-5SVOGLNK.cjs → chunk-ZIFYSLIM.cjs} +6 -6
  46. package/dist/{cloud-tenants-PVXDI5MU.cjs → cloud-tenants-6ICAL3HO.cjs} +34 -34
  47. package/dist/components/case-studies/index.cjs +37 -37
  48. package/dist/components/chat/index.cjs +17 -17
  49. package/dist/components/contact/index.cjs +25 -25
  50. package/dist/components/docs/index.cjs +28 -28
  51. package/dist/components/embeds/index.cjs +25 -25
  52. package/dist/components/faq/index.cjs +26 -26
  53. package/dist/components/features/capability-explorer.d.ts +9 -2
  54. package/dist/components/features/capability-explorer.d.ts.map +1 -1
  55. package/dist/components/features/capability-explorer.js +113 -6
  56. package/dist/components/features/index.cjs +162 -24
  57. package/dist/components/features/index.d.ts +1 -0
  58. package/dist/components/features/index.d.ts.map +1 -1
  59. package/dist/components/features/index.js +1 -0
  60. package/dist/components/features/scripts/execution-cells.d.ts +65 -0
  61. package/dist/components/features/scripts/execution-cells.d.ts.map +1 -0
  62. package/dist/components/features/scripts/execution-cells.js +75 -0
  63. package/dist/components/features/scripts/execution-status.d.ts +48 -0
  64. package/dist/components/features/scripts/execution-status.d.ts.map +1 -0
  65. package/dist/components/features/scripts/execution-status.js +15 -0
  66. package/dist/components/features/scripts/index.d.ts +20 -0
  67. package/dist/components/features/scripts/index.d.ts.map +1 -0
  68. package/dist/components/features/scripts/index.js +59 -0
  69. package/dist/components/features/scripts/schedule-form/index.d.ts +18 -0
  70. package/dist/components/features/scripts/schedule-form/index.d.ts.map +1 -0
  71. package/dist/components/features/scripts/schedule-form/index.js +84 -0
  72. package/dist/components/features/scripts/schedule-form/schedule-enums.d.ts +24 -0
  73. package/dist/components/features/scripts/schedule-form/schedule-enums.d.ts.map +1 -0
  74. package/dist/components/features/scripts/schedule-form/schedule-enums.js +18 -0
  75. package/dist/components/features/scripts/schedule-form/schedule-form-fields.d.ts +57 -0
  76. package/dist/components/features/scripts/schedule-form/schedule-form-fields.d.ts.map +1 -0
  77. package/dist/components/features/scripts/schedule-form/schedule-form-fields.js +169 -0
  78. package/dist/components/features/scripts/schedule-form/schedule-form-types.d.ts +138 -0
  79. package/dist/components/features/scripts/schedule-form/schedule-form-types.d.ts.map +1 -0
  80. package/dist/components/features/scripts/schedule-form/schedule-form-types.js +72 -0
  81. package/dist/components/features/scripts/schedule-form/schedule-interval-input.d.ts +54 -0
  82. package/dist/components/features/scripts/schedule-form/schedule-interval-input.d.ts.map +1 -0
  83. package/dist/components/features/scripts/schedule-form/schedule-interval-input.js +60 -0
  84. package/dist/components/features/scripts/schedule-form/schedule-offline-fields.d.ts +29 -0
  85. package/dist/components/features/scripts/schedule-form/schedule-offline-fields.d.ts.map +1 -0
  86. package/dist/components/features/scripts/schedule-form/schedule-offline-fields.js +124 -0
  87. package/dist/components/features/scripts/schedule-form/schedule-script-picker-card.d.ts +52 -0
  88. package/dist/components/features/scripts/schedule-form/schedule-script-picker-card.d.ts.map +1 -0
  89. package/dist/components/features/scripts/schedule-form/schedule-script-picker-card.js +244 -0
  90. package/dist/components/features/scripts/schedule-form/schedule-script-search-field.d.ts +35 -0
  91. package/dist/components/features/scripts/schedule-form/schedule-script-search-field.d.ts.map +1 -0
  92. package/dist/components/features/scripts/schedule-form/schedule-script-search-field.js +58 -0
  93. package/dist/components/features/scripts/schedule-form/schedule-scripts-field.d.ts +25 -0
  94. package/dist/components/features/scripts/schedule-form/schedule-scripts-field.d.ts.map +1 -0
  95. package/dist/components/features/scripts/schedule-form/schedule-scripts-field.js +66 -0
  96. package/dist/components/features/scripts/schedule-form/schedule-timing-fields.d.ts +77 -0
  97. package/dist/components/features/scripts/schedule-form/schedule-timing-fields.d.ts.map +1 -0
  98. package/dist/components/features/scripts/schedule-form/schedule-timing-fields.js +262 -0
  99. package/dist/components/features/scripts/schedule-form/schedule-timing.d.ts +288 -0
  100. package/dist/components/features/scripts/schedule-form/schedule-timing.d.ts.map +1 -0
  101. package/dist/components/features/scripts/schedule-form/schedule-timing.js +165 -0
  102. package/dist/components/features/scripts/script-cells.d.ts +11 -0
  103. package/dist/components/features/scripts/script-cells.d.ts.map +1 -0
  104. package/dist/components/features/scripts/script-cells.js +19 -0
  105. package/dist/components/features/scripts/script-executions-table.d.ts +58 -0
  106. package/dist/components/features/scripts/script-executions-table.d.ts.map +1 -0
  107. package/dist/components/features/scripts/script-executions-table.js +88 -0
  108. package/dist/components/features/scripts/script-schedules-table.d.ts +38 -0
  109. package/dist/components/features/scripts/script-schedules-table.d.ts.map +1 -0
  110. package/dist/components/features/scripts/script-schedules-table.js +71 -0
  111. package/dist/components/features/scripts/script-shell-badge.d.ts +9 -0
  112. package/dist/components/features/scripts/script-shell-badge.d.ts.map +1 -0
  113. package/dist/components/features/scripts/script-shell-badge.js +15 -0
  114. package/dist/components/features/scripts/scripts-table-columns.d.ts +162 -0
  115. package/dist/components/features/scripts/scripts-table-columns.d.ts.map +1 -0
  116. package/dist/components/features/scripts/scripts-table-columns.js +69 -0
  117. package/dist/components/features/scripts/scripts-table-shared.d.ts +19 -0
  118. package/dist/components/features/scripts/scripts-table-shared.d.ts.map +1 -0
  119. package/dist/components/features/scripts/scripts-table-shared.js +35 -0
  120. package/dist/components/features/scripts/scripts-table.d.ts +40 -0
  121. package/dist/components/features/scripts/scripts-table.d.ts.map +1 -0
  122. package/dist/components/features/scripts/scripts-table.js +84 -0
  123. package/dist/components/features/scripts/scripts-tabs.d.ts +5 -0
  124. package/dist/components/features/scripts/scripts-tabs.d.ts.map +1 -0
  125. package/dist/components/features/scripts/scripts-tabs.js +10 -0
  126. package/dist/components/features/scripts/shell-types.d.ts +13 -0
  127. package/dist/components/features/scripts/shell-types.d.ts.map +1 -0
  128. package/dist/components/features/scripts/shell-types.js +21 -0
  129. package/dist/components/help-center-pages/index.cjs +144 -144
  130. package/dist/components/index.cjs +224 -86
  131. package/dist/components/meeting-scheduler/index.cjs +74 -74
  132. package/dist/components/navigation/index.cjs +27 -27
  133. package/dist/components/onboarding-guides/index.cjs +27 -27
  134. package/dist/components/product-demo/fixtures/scripts.d.ts +34 -0
  135. package/dist/components/product-demo/fixtures/scripts.d.ts.map +1 -0
  136. package/dist/components/product-demo/fixtures/scripts.js +123 -0
  137. package/dist/components/product-demo/fixtures/shared.d.ts +8 -0
  138. package/dist/components/product-demo/fixtures/shared.d.ts.map +1 -1
  139. package/dist/components/product-demo/fixtures/shared.js +7 -1
  140. package/dist/components/product-demo/fixtures/software-update.d.ts.map +1 -1
  141. package/dist/components/product-demo/fixtures/software-update.js +1 -5
  142. package/dist/components/product-demo/index.cjs +16 -13
  143. package/dist/components/product-demo/product-screen.d.ts.map +1 -1
  144. package/dist/components/product-demo/product-screen.js +2 -1
  145. package/dist/components/product-demo/registry.d.ts.map +1 -1
  146. package/dist/components/product-demo/registry.js +2 -1
  147. package/dist/components/product-demo/screen-keys.cjs +2 -1
  148. package/dist/components/product-demo/screen-keys.d.ts +1 -0
  149. package/dist/components/product-demo/screen-keys.d.ts.map +1 -1
  150. package/dist/components/product-demo/screen-keys.js +2 -1
  151. package/dist/components/product-demo/screens/scripts.d.ts +11 -0
  152. package/dist/components/product-demo/screens/scripts.d.ts.map +1 -0
  153. package/dist/components/product-demo/screens/scripts.js +130 -0
  154. package/dist/components/related-content/index.cjs +12 -12
  155. package/dist/components/tickets/index.cjs +30 -30
  156. package/dist/components/ui/index.cjs +24 -24
  157. package/dist/{devices-TWEA7XOR.cjs → devices-OFP2IBKN.cjs} +30 -30
  158. package/dist/{embeddable-chat-6JKKFQEB.cjs → embeddable-chat-4C22MGNC.cjs} +92 -92
  159. package/dist/hooks/index.cjs +10 -10
  160. package/dist/index.cjs +169 -31
  161. package/dist/{knowledge-4GQDBNQB.cjs → knowledge-KKBCSNUA.cjs} +29 -29
  162. package/dist/{logs-EVU6GJHZ.cjs → logs-X2GRNO57.cjs} +46 -46
  163. package/dist/{policies-OZUEA5YS.cjs → policies-35BXKB6P.cjs} +31 -31
  164. package/dist/{remote-session-DNFZ5OLY.cjs → remote-session-N4PJB5FW.cjs} +15 -15
  165. package/dist/scripts-KQYVQOLU.cjs +2112 -0
  166. package/dist/{software-update-BLIVYOY2.cjs → software-update-ED33WSKT.cjs} +36 -40
  167. package/dist/{tickets-board-CKZRB6YV.cjs → tickets-board-O753S4ZB.cjs} +31 -31
  168. package/dist/utils/embed-authed-fetch.d.ts +33 -0
  169. package/dist/utils/embed-authed-fetch.d.ts.map +1 -1
  170. package/dist/utils/embed-authed-fetch.js +3 -1
  171. package/dist/utils/index.cjs +3 -1
  172. package/dist/{video-file-player-ZH2ILAPO.cjs → video-file-player-QZ3YOLHO.cjs} +2 -2
  173. package/package.json +1 -1
  174. package/src/components/features/capability-explorer.tsx +134 -7
  175. package/src/components/features/index.ts +1 -0
  176. package/src/components/features/scripts/execution-cells.tsx +149 -0
  177. package/src/components/features/scripts/execution-status.ts +42 -0
  178. package/src/components/features/scripts/index.ts +42 -0
  179. package/src/components/features/scripts/schedule-form/index.ts +59 -0
  180. package/src/components/features/scripts/schedule-form/schedule-enums.ts +26 -0
  181. package/src/components/features/scripts/schedule-form/schedule-form-fields.tsx +249 -0
  182. package/src/components/features/scripts/schedule-form/schedule-form-types.ts +189 -0
  183. package/src/components/features/scripts/schedule-form/schedule-interval-input.tsx +121 -0
  184. package/src/components/features/scripts/schedule-form/schedule-offline-fields.tsx +218 -0
  185. package/src/components/features/scripts/schedule-form/schedule-script-picker-card.tsx +321 -0
  186. package/src/components/features/scripts/schedule-form/schedule-script-search-field.tsx +115 -0
  187. package/src/components/features/scripts/schedule-form/schedule-scripts-field.tsx +97 -0
  188. package/src/components/features/scripts/schedule-form/schedule-timing-fields.tsx +482 -0
  189. package/src/components/features/scripts/schedule-form/schedule-timing.ts +484 -0
  190. package/src/components/features/scripts/script-cells.tsx +29 -0
  191. package/src/components/features/scripts/script-executions-table.tsx +145 -0
  192. package/src/components/features/scripts/script-schedules-table.tsx +113 -0
  193. package/src/components/features/scripts/script-shell-badge.tsx +25 -0
  194. package/src/components/features/scripts/scripts-table-columns.ts +95 -0
  195. package/src/components/features/scripts/scripts-table-shared.tsx +43 -0
  196. package/src/components/features/scripts/scripts-table.tsx +124 -0
  197. package/src/components/features/scripts/scripts-tabs.ts +11 -0
  198. package/src/components/features/scripts/shell-types.ts +26 -0
  199. package/src/components/product-demo/fixtures/scripts.ts +186 -0
  200. package/src/components/product-demo/fixtures/shared.ts +7 -0
  201. package/src/components/product-demo/fixtures/software-update.ts +1 -7
  202. package/src/components/product-demo/product-screen.tsx +3 -1
  203. package/src/components/product-demo/registry.ts +1 -0
  204. package/src/components/product-demo/screen-keys.ts +1 -0
  205. package/src/components/product-demo/screens/scripts.tsx +192 -0
  206. package/src/utils/embed-authed-fetch.ts +42 -1
@@ -21,13 +21,15 @@ export const PRODUCT_SCREEN_COMPACT_HEIGHT = 300;
21
21
  * host's readable width: the device list's customer column breaks a name
22
22
  * mid-word below it, and the logs table beside its open drawer has no room
23
23
  * for the message; the software form is laid out wide so the package, the
24
- * schedule and the picked devices all fit the frame's height. A frame never
24
+ * schedule and the picked devices all fit the frame's height; the scripts
25
+ * screen puts a schedule's name, its timing and its repeat on one row. A frame never
25
26
  * lays such a screen out narrower.
26
27
  */
27
28
  const MIN_LAYOUT_WIDTH: Partial<Record<ProductScreenKey, number>> = {
28
29
  devices: 1120,
29
30
  logs: 1120,
30
31
  'software-update': 1400,
32
+ scripts: 1400,
31
33
  };
32
34
 
33
35
  /**
@@ -17,4 +17,5 @@ export const PRODUCT_SCREEN_LOADERS: Record<ProductScreenKey, ProductScreenLoade
17
17
  'tickets-board': () => import('./screens/tickets-board'),
18
18
  logs: () => import('./screens/logs'),
19
19
  knowledge: () => import('./screens/knowledge'),
20
+ scripts: () => import('./screens/scripts'),
20
21
  };
@@ -15,6 +15,7 @@ export const PRODUCT_SCREEN_LABELS = {
15
15
  'tickets-board': 'Ticket board',
16
16
  logs: 'Logs with a log open',
17
17
  knowledge: 'Knowledge base',
18
+ scripts: 'Script library and schedules',
18
19
  } as const;
19
20
 
20
21
  export type ProductScreenKey = keyof typeof PRODUCT_SCREEN_LABELS;
@@ -0,0 +1,192 @@
1
+ 'use client';
2
+
3
+ import { useMemo } from 'react';
4
+ import {
5
+ EXECUTION_COLUMNS,
6
+ ScriptExecutionResult,
7
+ ScriptExecutionsTable,
8
+ type ScriptExecutionTableRow,
9
+ ScriptShellBadge,
10
+ type ScriptsTableColumnLayout,
11
+ } from '../../features/scripts';
12
+ import { TimerIcon } from '../../icons-v2-generated/date-and-time/timer-icon';
13
+ import { TruncateText } from '../../ui/truncate-text';
14
+ import { useProductDemoCast } from '../cast';
15
+ import { buildScriptsFixture, type DemoScript } from '../fixtures/scripts';
16
+ import type { ProductScreenViewProps } from '../types';
17
+
18
+ /**
19
+ * The picture has nowhere to open a run and no row menu, and the Result
20
+ * column would repeat the output opened under its row.
21
+ */
22
+ const WIDE_HIDDEN_COLUMNS = ['result', 'open'];
23
+
24
+ /**
25
+ * A card's width hides the device column (the product's page drops it below
26
+ * `lg`), and the device is what the narrow picture is about: here it takes
27
+ * the room, with the status beside it.
28
+ */
29
+ const COMPACT_COLUMNS: readonly ScriptsTableColumnLayout[] = [
30
+ { ...EXECUTION_COLUMNS.machineId, width: 'flex-1 min-w-0', hideAt: undefined },
31
+ EXECUTION_COLUMNS.status,
32
+ ];
33
+ const COMPACT_ROWS = 4;
34
+
35
+ type TokenKind = 'comment' | 'keyword' | 'string' | 'variable' | 'command' | 'parameter' | 'number' | 'plain';
36
+
37
+ /**
38
+ * PowerShell token colours: the product editor's `ods-dark` highlight style
39
+ * (`script-editor-view.tsx`) in ODS tokens. Keywords yellow, strings and
40
+ * variables cyan, commands green, numbers amber, parameters grey, comments
41
+ * muted.
42
+ */
43
+ const TOKEN_CLASS: Record<TokenKind, string> = {
44
+ comment: 'italic text-ods-text-tertiary',
45
+ keyword: 'text-ods-warning',
46
+ string: 'text-ods-flamingo-cyan',
47
+ variable: 'text-ods-flamingo-cyan',
48
+ command: 'text-ods-success',
49
+ parameter: 'text-ods-text-secondary',
50
+ number: 'text-ods-open-yellow',
51
+ plain: 'text-ods-text-primary',
52
+ };
53
+
54
+ /** One group per kind, in {@link TOKEN_KINDS} order; a comment or a string swallows what it contains. */
55
+ const TOKEN_PATTERN =
56
+ /(#.*$)|("(?:[^"\\]|\\.)*"|'[^']*')|(\$[\w:]+)|\b(foreach|in|if|else|exit|return|param)\b|\b([A-Z][a-z]+-[A-Z][A-Za-z]+)\b|(-[A-Za-z]+)\b|\b(\d+(?:GB|MB|KB)?)\b/g;
57
+ const TOKEN_KINDS: readonly TokenKind[] = [
58
+ 'comment',
59
+ 'string',
60
+ 'variable',
61
+ 'keyword',
62
+ 'command',
63
+ 'parameter',
64
+ 'number',
65
+ ];
66
+
67
+ interface Token {
68
+ text: string;
69
+ kind: TokenKind;
70
+ }
71
+
72
+ /** One line of PowerShell as coloured runs. Only the picture's own script is read, so the rules are few. */
73
+ function tokenize(line: string): Token[] {
74
+ const tokens: Token[] = [];
75
+ let last = 0;
76
+ for (const match of line.matchAll(TOKEN_PATTERN)) {
77
+ const start = match.index ?? 0;
78
+ if (start > last) tokens.push({ text: line.slice(last, start), kind: 'plain' });
79
+ const group = match.slice(1).findIndex(Boolean);
80
+ tokens.push({ text: match[0], kind: TOKEN_KINDS[group] ?? 'plain' });
81
+ last = start + match[0].length;
82
+ }
83
+ if (last < line.length) tokens.push({ text: line.slice(last), kind: 'plain' });
84
+ return tokens;
85
+ }
86
+
87
+ /**
88
+ * The script as the product's details page shows it: its name and shell over
89
+ * the editor's read-only view. The product's editor is CodeMirror, which the
90
+ * library does not carry, so this draws the editor's geometry (a 46px number
91
+ * gutter, the code 32px past it, 22px rows of 14px mono) in its colours. The
92
+ * header adds the schedule that runs the script and who wrote and approved
93
+ * it: the two things the conversation beside the picture settled.
94
+ */
95
+ function ScriptCodePicture({ script }: { script: DemoScript }) {
96
+ // Keyed by line number: the script never changes under the picture.
97
+ const lines = useMemo(
98
+ () => script.code.split('\n').map((line, index) => ({ number: index + 1, tokens: tokenize(line) })),
99
+ [script.code],
100
+ );
101
+ const { schedule, provenance } = script;
102
+ return (
103
+ <section
104
+ aria-label={script.name}
105
+ className="flex h-full min-w-0 flex-col overflow-hidden rounded-[8px] border border-ods-border bg-ods-card"
106
+ >
107
+ <div className="flex flex-col gap-[var(--spacing-system-xxs)] border-b border-ods-border p-[var(--spacing-system-m)]">
108
+ <div className="flex min-w-0 items-center justify-between gap-[var(--spacing-system-m)]">
109
+ <TruncateText variant="h4">{script.name}</TruncateText>
110
+ <span className="flex shrink-0 items-center gap-[var(--spacing-system-xxs)] text-ods-text-primary text-h6">
111
+ <TimerIcon className="size-4 text-ods-text-secondary" />
112
+ {`${schedule.repeat} ${schedule.time} · ${schedule.deviceCount} devices`}
113
+ </span>
114
+ </div>
115
+ <div className="flex min-w-0 items-center justify-between gap-[var(--spacing-system-m)]">
116
+ <TruncateText variant="h6" tone="secondary">
117
+ {`Written by ${provenance.author} · approved by ${provenance.approver}`}
118
+ </TruncateText>
119
+ <ScriptShellBadge
120
+ value={script.shellType}
121
+ className="shrink-0 text-ods-text-secondary text-h6"
122
+ iconClassName="size-4"
123
+ />
124
+ </div>
125
+ </div>
126
+ <pre className="m-0 min-h-0 flex-1 overflow-hidden bg-ods-bg py-3 font-mono text-[14px] leading-[22px]">
127
+ {lines.map(line => (
128
+ <div key={line.number} className="flex">
129
+ <span className="w-[46px] shrink-0 select-none pr-[4px] text-right text-ods-text-tertiary">
130
+ {line.number}
131
+ </span>
132
+ <code className="whitespace-pre pl-[32px]">
133
+ {line.tokens.map((token, at) => (
134
+ <span key={`${at}-${token.text}`} className={TOKEN_CLASS[token.kind]}>
135
+ {token.text}
136
+ </span>
137
+ ))}
138
+ </code>
139
+ </div>
140
+ ))}
141
+ </pre>
142
+ </section>
143
+ );
144
+ }
145
+
146
+ /**
147
+ * Where the page's Mingo conversation leaves off: the script it wrote, on the
148
+ * left, and the first night its schedule ran, on the right, as the product's
149
+ * Execution History (a run still going, one failed, one finished with its
150
+ * output open under it: what it cleaned and what it freed). The schedule form
151
+ * and the library are the conversation's part, so the picture leaves them
152
+ * out. The narrow rendering is the run alone: which devices, and how each went.
153
+ */
154
+ export default function ScriptsScreen({ compact = false }: ProductScreenViewProps) {
155
+ const cast = useProductDemoCast();
156
+ const fixture = useMemo(() => buildScriptsFixture(cast), [cast]);
157
+
158
+ if (compact) {
159
+ return (
160
+ // Four runs of the card's row height fill the frame; the padding is what is left round them.
161
+ <div className="h-full overflow-hidden bg-ods-bg p-[var(--spacing-system-xsf)]">
162
+ <ScriptExecutionsTable
163
+ rows={fixture.executions.slice(0, COMPACT_ROWS)}
164
+ columns={COMPACT_COLUMNS}
165
+ showHeader={false}
166
+ compact
167
+ />
168
+ </div>
169
+ );
170
+ }
171
+
172
+ const renderOutput = (row: ScriptExecutionTableRow) =>
173
+ row.id === fixture.openExecutionId ? <ScriptExecutionResult result={row.result} empty="—" /> : null;
174
+
175
+ return (
176
+ // The frame's tighter padding and no header row: the output opened under a run needs the height.
177
+ <div className="flex h-full gap-[var(--spacing-system-m)] overflow-hidden bg-ods-bg p-[var(--spacing-system-m)]">
178
+ <div className="w-[39%] shrink-0">
179
+ <ScriptCodePicture script={fixture.script} />
180
+ </div>
181
+ <div className="min-w-0 flex-1 overflow-hidden">
182
+ <ScriptExecutionsTable
183
+ rows={fixture.executions}
184
+ hideColumns={WIDE_HIDDEN_COLUMNS}
185
+ renderSubRow={renderOutput}
186
+ showHeader={false}
187
+ compact
188
+ />
189
+ </div>
190
+ </div>
191
+ );
192
+ }
@@ -86,6 +86,34 @@ export interface EmbedAuthAdapter {
86
86
  * host's own auth layer, not to this fetch wrapper.
87
87
  */
88
88
  refresh?: () => Promise<boolean>;
89
+ /**
90
+ * Optional guard, run before every request goes out — the first attempt
91
+ * and the retry after a `refresh`. Throw — or reject — to refuse the
92
+ * request: `embedAuthedFetch` rejects with that error and nothing reaches
93
+ * the network.
94
+ *
95
+ * For a host-wide state that makes every request pointless. openframe's
96
+ * mobile force-update is the case it exists for: once the gateway has
97
+ * refused this client build, every later request is refused locally, so no
98
+ * surface's retry or reconnect loop keeps hitting the gateway. Runs inside
99
+ * the returned promise (after the synchronous same-origin guard), so
100
+ * callers see a rejection, never a synchronous throw.
101
+ */
102
+ beforeRequest?: () => void | Promise<void>;
103
+ /**
104
+ * Optional response hook, run on every response (the post-refresh retry's
105
+ * too) BEFORE the 401 self-heal. Throw — or reject — to fail the request
106
+ * with that error: `embedAuthedFetch` rejects with it, and neither
107
+ * `refresh` nor a retry runs. Return to let the response continue through
108
+ * the wrapper unchanged.
109
+ *
110
+ * For a status the host handles app-wide rather than per surface — e.g.
111
+ * openframe's gateway answering `426 Upgrade Required` to a too-old mobile
112
+ * build, which swaps the whole app for an "update required" screen and
113
+ * must never be retried or read as an auth failure. Read the body from
114
+ * `response.clone()`: once the hook returns, the caller reads the original.
115
+ */
116
+ onResponse?: (response: Response) => void | Promise<void>;
89
117
  /**
90
118
  * Exact extra origins (`scheme://host[:port]`, as produced by `URL.origin`)
91
119
  * the same-origin guard additionally accepts. The guard normally rejects
@@ -253,6 +281,11 @@ export function needsBearerAssetFetch(url: string): boolean {
253
281
  * no longer need a host-side `window.fetch` monkey-patch to survive an
254
282
  * expired access token mid-chat. With no adapter (or no `refresh`), the
255
283
  * 401 passes straight through unchanged.
284
+ *
285
+ * **Host hooks:** an adapter's `beforeRequest` may refuse a request before it
286
+ * is sent, and its `onResponse` sees every response ahead of the 401
287
+ * self-heal and may fail the request instead (see their docs). Without them
288
+ * the wrapper behaves exactly as described above.
256
289
  */
257
290
  export function embedAuthedFetch(url: string, init: RequestInit = {}): Promise<Response> {
258
291
  // Same-origin guard runs SYNCHRONOUSLY (not awaited inside the async
@@ -354,6 +387,11 @@ async function fetchWithRefresh(
354
387
  baseHeaders: Record<string, string>,
355
388
  isRetry: boolean,
356
389
  ): Promise<Response> {
390
+ const adapter = getRegisteredAuthAdapter();
391
+ // Per attempt, so a retry after `refresh` is refused too when the host's
392
+ // state changed while the refresh was in flight.
393
+ await adapter?.beforeRequest?.();
394
+
357
395
  // Re-run the merge each attempt: `applyProxyAuth` reads the latest stored
358
396
  // proxy creds and `getHeaders()` reads the latest bearer, so a retry after
359
397
  // refresh carries the rotated token rather than the stale one. `{...baseHeaders}`
@@ -365,7 +403,6 @@ async function fetchWithRefresh(
365
403
  // is the host's explicit "this is my auth model" override, intentionally
366
404
  // last-writer-wins. When no adapter is registered, this is a zero-cost
367
405
  // no-op (object spread of `{}`).
368
- const adapter = getRegisteredAuthAdapter();
369
406
  if (adapter?.getHeaders) {
370
407
  // Filter `undefined` values — the adapter type allows them so consumers
371
408
  // don't have to narrow `{ Authorization: token ? '…' : undefined }`-shaped
@@ -387,6 +424,10 @@ async function fetchWithRefresh(
387
424
  credentials,
388
425
  });
389
426
 
427
+ // Ahead of the 401 self-heal: a status the host claims by throwing must
428
+ // never reach `refresh` or a retry.
429
+ await adapter?.onResponse?.(response);
430
+
390
431
  // 401 self-heal: refresh the token once and retry. Only when an adapter
391
432
  // opted into `refresh`, and only on the first attempt — a 401 on the
392
433
  // retry means the fresh token is also unauthorized, so surface it.