@hotmeshio/long-tail 0.10.2 → 0.10.4

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 (397) hide show
  1. package/README.md +1 -0
  2. package/build/api/bot-accounts.d.ts +4 -0
  3. package/build/api/bot-accounts.js +3 -1
  4. package/build/api/escalations/metadata.js +21 -1
  5. package/build/api/escalations/resolve.d.ts +8 -0
  6. package/build/api/escalations/resolve.js +82 -3
  7. package/build/api/roles.d.ts +2 -0
  8. package/build/api/roles.js +4 -0
  9. package/build/bin/ltc.js +10 -1
  10. package/build/index.js +7 -1
  11. package/build/lib/cli/client.d.ts +4 -1
  12. package/build/lib/cli/client.js +4 -2
  13. package/build/lib/db/schemas/023_role_enforce_schema.sql +18 -0
  14. package/build/lib/typed-resolution.d.ts +35 -0
  15. package/build/lib/typed-resolution.js +37 -0
  16. package/build/modules/branding.d.ts +21 -2
  17. package/build/modules/branding.js +22 -1
  18. package/build/modules/config.d.ts +1 -0
  19. package/build/modules/config.js +5 -0
  20. package/build/routes/bot-accounts.js +3 -1
  21. package/build/routes/settings.js +21 -0
  22. package/build/sdk/index.d.ts +4 -0
  23. package/build/sdk/index.js +14 -0
  24. package/build/services/escalation/crud.d.ts +18 -6
  25. package/build/services/escalation/crud.js +18 -2
  26. package/build/services/escalation/index.d.ts +1 -0
  27. package/build/services/escalation/index.js +1 -0
  28. package/build/services/escalation/resolver-validation.d.ts +28 -0
  29. package/build/services/escalation/resolver-validation.js +95 -0
  30. package/build/services/escalation/sql.d.ts +12 -2
  31. package/build/services/escalation/sql.js +22 -2
  32. package/build/services/iam/bots.d.ts +4 -1
  33. package/build/services/iam/bots.js +5 -3
  34. package/build/services/iam/sql.d.ts +2 -2
  35. package/build/services/iam/sql.js +15 -1
  36. package/build/services/role/enforcement-cache.d.ts +21 -0
  37. package/build/services/role/enforcement-cache.js +0 -0
  38. package/build/services/role/index.js +5 -0
  39. package/build/services/role/sql.d.ts +12 -2
  40. package/build/services/role/sql.js +15 -2
  41. package/build/services/role/types.d.ts +9 -0
  42. package/build/shared/form-validation/derive-field-label.d.ts +14 -0
  43. package/build/shared/form-validation/derive-field-label.js +31 -0
  44. package/build/shared/form-validation/field-validator.d.ts +45 -0
  45. package/build/shared/form-validation/field-validator.js +214 -0
  46. package/build/shared/form-validation/index.d.ts +12 -0
  47. package/build/shared/form-validation/index.js +28 -0
  48. package/build/shared/form-validation/validate-resolver-payload.d.ts +41 -0
  49. package/build/shared/form-validation/validate-resolver-payload.js +62 -0
  50. package/build/shared/form-validation/x-lt-bind.d.ts +41 -0
  51. package/build/shared/form-validation/x-lt-bind.js +110 -0
  52. package/build/shared/form-validation/x-lt-help.d.ts +21 -0
  53. package/build/shared/form-validation/x-lt-help.js +91 -0
  54. package/build/shared/form-validation/x-lt-show-if.d.ts +7 -0
  55. package/build/shared/form-validation/x-lt-show-if.js +84 -0
  56. package/build/system/mcp-servers/admin/schemas.d.ts +3 -0
  57. package/build/system/mcp-servers/admin/schemas.js +1 -0
  58. package/build/system/mcp-servers/admin/users.js +3 -1
  59. package/build/system/mcp-servers/human-queue-schemas.d.ts +4 -4
  60. package/build/system/mcp-servers/human-queue-schemas.js +4 -1
  61. package/build/system/mcp-servers/human-queue.js +41 -2
  62. package/build/tsconfig.tsbuildinfo +1 -1
  63. package/build/types/sdk.d.ts +6 -1
  64. package/build/types/startup.d.ts +26 -0
  65. package/build/types/validation.d.ts +43 -0
  66. package/build/types/validation.js +28 -0
  67. package/dashboard/dist/assets/{AdminDashboard-Dy031e9g.js → AdminDashboard-Cma2NGsF.js} +2 -2
  68. package/dashboard/dist/assets/{AdminDashboard-Dy031e9g.js.map → AdminDashboard-Cma2NGsF.js.map} +1 -1
  69. package/dashboard/dist/assets/AgentConfigPage-CbIhM-fa.js +16 -0
  70. package/dashboard/dist/assets/AgentConfigPage-CbIhM-fa.js.map +1 -0
  71. package/dashboard/dist/assets/AgentDetailPage-DuLEKzRT.js +4 -0
  72. package/dashboard/dist/assets/AgentDetailPage-DuLEKzRT.js.map +1 -0
  73. package/dashboard/dist/assets/AgentsPage-DZO9i7Nl.js +2 -0
  74. package/dashboard/dist/assets/AgentsPage-DZO9i7Nl.js.map +1 -0
  75. package/dashboard/dist/assets/AvailableEscalationsPage-DlVoNd8B.js +2 -0
  76. package/dashboard/dist/assets/AvailableEscalationsPage-DlVoNd8B.js.map +1 -0
  77. package/dashboard/dist/assets/{BotPicker-2QEIpc-K.js → BotPicker-CidlGr6f.js} +2 -2
  78. package/dashboard/dist/assets/{BotPicker-2QEIpc-K.js.map → BotPicker-CidlGr6f.js.map} +1 -1
  79. package/dashboard/dist/assets/CapabilitiesPage-BMNNTa0D.js +2 -0
  80. package/dashboard/dist/assets/{CapabilitiesPage-lQJGZ_-t.js.map → CapabilitiesPage-BMNNTa0D.js.map} +1 -1
  81. package/dashboard/dist/assets/{CollapsibleSection-CHWP0WI6.js → CollapsibleSection-W9F9lziT.js} +2 -2
  82. package/dashboard/dist/assets/{CollapsibleSection-CHWP0WI6.js.map → CollapsibleSection-W9F9lziT.js.map} +1 -1
  83. package/dashboard/dist/assets/ConfirmDeleteModal-BUh-Dzba.js +2 -0
  84. package/dashboard/dist/assets/{ConfirmDeleteModal-C0x0HcHX.js.map → ConfirmDeleteModal-BUh-Dzba.js.map} +1 -1
  85. package/dashboard/dist/assets/{CopyableId-uot2mE_g.js → CopyableId-B1mxKfVb.js} +2 -2
  86. package/dashboard/dist/assets/{CopyableId-uot2mE_g.js.map → CopyableId-B1mxKfVb.js.map} +1 -1
  87. package/dashboard/dist/assets/CountdownTimer-lkv_7msa.js +2 -0
  88. package/dashboard/dist/assets/{CountdownTimer-TXsRlTU4.js.map → CountdownTimer-lkv_7msa.js.map} +1 -1
  89. package/dashboard/dist/assets/{CredentialsPage-BKxS-1sA.js → CredentialsPage-DyOgfnPB.js} +2 -2
  90. package/dashboard/dist/assets/CredentialsPage-DyOgfnPB.js.map +1 -0
  91. package/dashboard/dist/assets/{CronLabel-Bd2G0Rap.js → CronLabel-geum95dj.js} +2 -2
  92. package/dashboard/dist/assets/{CronLabel-Bd2G0Rap.js.map → CronLabel-geum95dj.js.map} +1 -1
  93. package/dashboard/dist/assets/CustomDurationPicker-ClufO2ew.js +2 -0
  94. package/dashboard/dist/assets/CustomDurationPicker-ClufO2ew.js.map +1 -0
  95. package/dashboard/dist/assets/DataTable-BTu2AaEl.js +2 -0
  96. package/dashboard/dist/assets/DataTable-BTu2AaEl.js.map +1 -0
  97. package/dashboard/dist/assets/{DropZone-DNDEgMpJ.js → DropZone-C3hOjJtC.js} +2 -2
  98. package/dashboard/dist/assets/{DropZone-DNDEgMpJ.js.map → DropZone-C3hOjJtC.js.map} +1 -1
  99. package/dashboard/dist/assets/{ElapsedCell-g7GO7f8L.js → ElapsedCell-DFOW9T4b.js} +2 -2
  100. package/dashboard/dist/assets/{ElapsedCell-g7GO7f8L.js.map → ElapsedCell-DFOW9T4b.js.map} +1 -1
  101. package/dashboard/dist/assets/EscalationListSchemaPage-_IhqXIQu.js +20 -0
  102. package/dashboard/dist/assets/EscalationListSchemaPage-_IhqXIQu.js.map +1 -0
  103. package/dashboard/dist/assets/EscalationSchemaPage-BCC7iscN.js +9 -0
  104. package/dashboard/dist/assets/EscalationSchemaPage-BCC7iscN.js.map +1 -0
  105. package/dashboard/dist/assets/EscalationsOverview-CvIdOtoP.js +2 -0
  106. package/dashboard/dist/assets/EscalationsOverview-CvIdOtoP.js.map +1 -0
  107. package/dashboard/dist/assets/EventTable-DJWJ7J0m.js +2 -0
  108. package/dashboard/dist/assets/EventTable-DJWJ7J0m.js.map +1 -0
  109. package/dashboard/dist/assets/FilterBar-BR3ztZ8y.js +2 -0
  110. package/dashboard/dist/assets/FilterBar-BR3ztZ8y.js.map +1 -0
  111. package/dashboard/dist/assets/GraphInvokePage-Cwn-8Glr.js +2 -0
  112. package/dashboard/dist/assets/GraphInvokePage-Cwn-8Glr.js.map +1 -0
  113. package/dashboard/dist/assets/HomePage-D8RVntg0.js +2 -0
  114. package/dashboard/dist/assets/HomePage-D8RVntg0.js.map +1 -0
  115. package/dashboard/dist/assets/ListToolbar-DuoEdVtK.js +2 -0
  116. package/dashboard/dist/assets/ListToolbar-DuoEdVtK.js.map +1 -0
  117. package/dashboard/dist/assets/McpOverview-NXKsGiWU.js +2 -0
  118. package/dashboard/dist/assets/McpOverview-NXKsGiWU.js.map +1 -0
  119. package/dashboard/dist/assets/McpQueryDetailPage-BC-iXfIx.js +5 -0
  120. package/dashboard/dist/assets/McpQueryDetailPage-BC-iXfIx.js.map +1 -0
  121. package/dashboard/dist/assets/{McpQueryPage-CsKNXUhb.js → McpQueryPage-BPJmFYOY.js} +2 -2
  122. package/dashboard/dist/assets/{McpQueryPage-CsKNXUhb.js.map → McpQueryPage-BPJmFYOY.js.map} +1 -1
  123. package/dashboard/dist/assets/McpRunDetailPage-B49nUXEl.js +2 -0
  124. package/dashboard/dist/assets/McpRunDetailPage-B49nUXEl.js.map +1 -0
  125. package/dashboard/dist/assets/McpRunsPage-Ce_XIYpP.js +2 -0
  126. package/dashboard/dist/assets/McpRunsPage-Ce_XIYpP.js.map +1 -0
  127. package/dashboard/dist/assets/NamespacePill-LWvZXt_g.js +2 -0
  128. package/dashboard/dist/assets/NamespacePill-LWvZXt_g.js.map +1 -0
  129. package/dashboard/dist/assets/OperationsPage-DI7AQO9f.js +2 -0
  130. package/dashboard/dist/assets/OperationsPage-DI7AQO9f.js.map +1 -0
  131. package/dashboard/dist/assets/OperatorDashboard-DpXjhs-3.js +2 -0
  132. package/dashboard/dist/assets/{OperatorDashboard-_axS17fp.js.map → OperatorDashboard-DpXjhs-3.js.map} +1 -1
  133. package/dashboard/dist/assets/PageHeader-DvEmMMP6.js +2 -0
  134. package/dashboard/dist/assets/PageHeader-DvEmMMP6.js.map +1 -0
  135. package/dashboard/dist/assets/PageHeaderWithStats-X0eAmu-x.js +2 -0
  136. package/dashboard/dist/assets/PageHeaderWithStats-X0eAmu-x.js.map +1 -0
  137. package/dashboard/dist/assets/{PriorityBadge-B4ykBH7f.js → PriorityBadge-CCERr9vQ.js} +2 -2
  138. package/dashboard/dist/assets/PriorityBadge-CCERr9vQ.js.map +1 -0
  139. package/dashboard/dist/assets/ProcessDetailPage-DpYSMRj1.js +2 -0
  140. package/dashboard/dist/assets/ProcessDetailPage-DpYSMRj1.js.map +1 -0
  141. package/dashboard/dist/assets/{ProcessesListPage-CoQEi_Su.js → ProcessesListPage-CQ9tSw7U.js} +2 -2
  142. package/dashboard/dist/assets/{ProcessesListPage-CoQEi_Su.js.map → ProcessesListPage-CQ9tSw7U.js.map} +1 -1
  143. package/dashboard/dist/assets/RoleDetailPage-DKeQKDkW.js +8 -0
  144. package/dashboard/dist/assets/RoleDetailPage-DKeQKDkW.js.map +1 -0
  145. package/dashboard/dist/assets/RolePill-CWhoLwef.js +2 -0
  146. package/dashboard/dist/assets/RolePill-CWhoLwef.js.map +1 -0
  147. package/dashboard/dist/assets/RolesPage-D-6kIact.js +2 -0
  148. package/dashboard/dist/assets/RolesPage-D-6kIact.js.map +1 -0
  149. package/dashboard/dist/assets/RunAsSelector-DzYcpQTm.js +2 -0
  150. package/dashboard/dist/assets/RunAsSelector-DzYcpQTm.js.map +1 -0
  151. package/dashboard/dist/assets/StatCard-DBANWIss.js +2 -0
  152. package/dashboard/dist/assets/StatCard-DBANWIss.js.map +1 -0
  153. package/dashboard/dist/assets/StatusBadge-BbqghFMi.js +2 -0
  154. package/dashboard/dist/assets/StatusBadge-BbqghFMi.js.map +1 -0
  155. package/dashboard/dist/assets/{StickyPagination-Cd83AHd_.js → StickyPagination-BTGfmGV3.js} +2 -2
  156. package/dashboard/dist/assets/{StickyPagination-Cd83AHd_.js.map → StickyPagination-BTGfmGV3.js.map} +1 -1
  157. package/dashboard/dist/assets/StreamMessageDetail-DqRpzA_6.js +2 -0
  158. package/dashboard/dist/assets/StreamMessageDetail-DqRpzA_6.js.map +1 -0
  159. package/dashboard/dist/assets/SwimlaneTimeline-CA106TYo.js +2 -0
  160. package/dashboard/dist/assets/SwimlaneTimeline-CA106TYo.js.map +1 -0
  161. package/dashboard/dist/assets/TagInput-CoR4MzsW.js +2 -0
  162. package/dashboard/dist/assets/TagInput-CoR4MzsW.js.map +1 -0
  163. package/dashboard/dist/assets/{TaskDetailPage-DnjhW7qv.js → TaskDetailPage-BRg-w2Ev.js} +2 -2
  164. package/dashboard/dist/assets/{TaskDetailPage-DnjhW7qv.js.map → TaskDetailPage-BRg-w2Ev.js.map} +1 -1
  165. package/dashboard/dist/assets/TaskQueuePill-Dw3YN8M-.js +2 -0
  166. package/dashboard/dist/assets/TaskQueuePill-Dw3YN8M-.js.map +1 -0
  167. package/dashboard/dist/assets/{TasksListPage-DySfZFgs.js → TasksListPage-kB_j9G8o.js} +2 -2
  168. package/dashboard/dist/assets/{TasksListPage-DySfZFgs.js.map → TasksListPage-kB_j9G8o.js.map} +1 -1
  169. package/dashboard/dist/assets/{TimeAgo-BKOl-7Tt.js → TimeAgo-D7dSIY7-.js} +2 -2
  170. package/dashboard/dist/assets/{TimeAgo-BKOl-7Tt.js.map → TimeAgo-D7dSIY7-.js.map} +1 -1
  171. package/dashboard/dist/assets/TimestampCell-BZe6bx18.js +2 -0
  172. package/dashboard/dist/assets/TimestampCell-BZe6bx18.js.map +1 -0
  173. package/dashboard/dist/assets/ToolPill-xX-buQtY.js +2 -0
  174. package/dashboard/dist/assets/ToolPill-xX-buQtY.js.map +1 -0
  175. package/dashboard/dist/assets/ToolTestPanel-U3NaF_T_.js +2 -0
  176. package/dashboard/dist/assets/ToolTestPanel-U3NaF_T_.js.map +1 -0
  177. package/dashboard/dist/assets/TopicDetailPage-BpALvXP1.js +9 -0
  178. package/dashboard/dist/assets/TopicDetailPage-BpALvXP1.js.map +1 -0
  179. package/dashboard/dist/assets/TopicsPage-Cy3chaca.js +2 -0
  180. package/dashboard/dist/assets/{TopicsPage-5yW63ELl.js.map → TopicsPage-Cy3chaca.js.map} +1 -1
  181. package/dashboard/dist/assets/{UserName-DcXhOSVm.js → UserName-VXM1oHMC.js} +2 -2
  182. package/dashboard/dist/assets/{UserName-DcXhOSVm.js.map → UserName-VXM1oHMC.js.map} +1 -1
  183. package/dashboard/dist/assets/WorkflowExecutionPage-BCq2cR1V.js +2 -0
  184. package/dashboard/dist/assets/WorkflowExecutionPage-BCq2cR1V.js.map +1 -0
  185. package/dashboard/dist/assets/WorkflowPill-CQ1AmhlC.js +2 -0
  186. package/dashboard/dist/assets/WorkflowPill-CQ1AmhlC.js.map +1 -0
  187. package/dashboard/dist/assets/{WorkflowsDashboard-C0Bbrd5M.js → WorkflowsDashboard-BbM69MnB.js} +2 -2
  188. package/dashboard/dist/assets/{WorkflowsDashboard-C0Bbrd5M.js.map → WorkflowsDashboard-BbM69MnB.js.map} +1 -1
  189. package/dashboard/dist/assets/WorkflowsOverview-D5HthpAW.js +2 -0
  190. package/dashboard/dist/assets/WorkflowsOverview-D5HthpAW.js.map +1 -0
  191. package/dashboard/dist/assets/{YamlWorkflowDetailPage-BcbrYhDI.js → YamlWorkflowDetailPage-CDDVMYWi.js} +10 -10
  192. package/dashboard/dist/assets/{YamlWorkflowDetailPage-BcbrYhDI.js.map → YamlWorkflowDetailPage-CDDVMYWi.js.map} +1 -1
  193. package/dashboard/dist/assets/YamlWorkflowsPage-FFjWwydj.js +2 -0
  194. package/dashboard/dist/assets/YamlWorkflowsPage-FFjWwydj.js.map +1 -0
  195. package/dashboard/dist/assets/{agents-CopQ3t5o.js → agents-BwiSNu73.js} +2 -2
  196. package/dashboard/dist/assets/{agents-CopQ3t5o.js.map → agents-BwiSNu73.js.map} +1 -1
  197. package/dashboard/dist/assets/bots-ClgSrxel.js +2 -0
  198. package/dashboard/dist/assets/bots-ClgSrxel.js.map +1 -0
  199. package/dashboard/dist/assets/{capabilities-5JRieYUy.js → capabilities-JUaKsby4.js} +2 -2
  200. package/dashboard/dist/assets/{capabilities-5JRieYUy.js.map → capabilities-JUaKsby4.js.map} +1 -1
  201. package/dashboard/dist/assets/{controlplane-B4rT6tIw.js → controlplane-CuTQ7eUE.js} +2 -2
  202. package/dashboard/dist/assets/{controlplane-B4rT6tIw.js.map → controlplane-CuTQ7eUE.js.map} +1 -1
  203. package/dashboard/dist/assets/escalation-columns-CS0c7cAr.js +2 -0
  204. package/dashboard/dist/assets/escalation-columns-CS0c7cAr.js.map +1 -0
  205. package/dashboard/dist/assets/index-BBA4AxmL.js +2 -0
  206. package/dashboard/dist/assets/index-BBA4AxmL.js.map +1 -0
  207. package/dashboard/dist/assets/index-C-8xXyvr.js +63 -0
  208. package/dashboard/dist/assets/index-C-8xXyvr.js.map +1 -0
  209. package/dashboard/dist/assets/index-C1t0YIXC.js +5 -0
  210. package/dashboard/dist/assets/index-C1t0YIXC.js.map +1 -0
  211. package/dashboard/dist/assets/index-C9o2UNhC.js +2 -0
  212. package/dashboard/dist/assets/index-C9o2UNhC.js.map +1 -0
  213. package/dashboard/dist/assets/index-CBj-vstO.js +2 -0
  214. package/dashboard/dist/assets/{index-Dd1RxGNw.js.map → index-CBj-vstO.js.map} +1 -1
  215. package/dashboard/dist/assets/index-CUguKMXZ.js +2 -0
  216. package/dashboard/dist/assets/index-CUguKMXZ.js.map +1 -0
  217. package/dashboard/dist/assets/index-Cavwd8UU.js +6 -0
  218. package/dashboard/dist/assets/index-Cavwd8UU.js.map +1 -0
  219. package/dashboard/dist/assets/index-CnsPBTXL.js +2 -0
  220. package/dashboard/dist/assets/index-CnsPBTXL.js.map +1 -0
  221. package/dashboard/dist/assets/index-CoPWHo_H.js +2 -0
  222. package/dashboard/dist/assets/index-CoPWHo_H.js.map +1 -0
  223. package/dashboard/dist/assets/index-CwQVaa7x.js +2 -0
  224. package/dashboard/dist/assets/index-CwQVaa7x.js.map +1 -0
  225. package/dashboard/dist/assets/index-DSa-6Ux8.js +2 -0
  226. package/dashboard/dist/assets/index-DSa-6Ux8.js.map +1 -0
  227. package/dashboard/dist/assets/index-DeMs_aBT.css +1 -0
  228. package/dashboard/dist/assets/index-Ikj_NTuy.js +5 -0
  229. package/dashboard/dist/assets/index-Ikj_NTuy.js.map +1 -0
  230. package/dashboard/dist/assets/index-Xd1jcHe7.js +2 -0
  231. package/dashboard/dist/assets/index-Xd1jcHe7.js.map +1 -0
  232. package/dashboard/dist/assets/{knowledge-B9bmoXOe.js → knowledge-BHBt-6kW.js} +2 -2
  233. package/dashboard/dist/assets/{knowledge-B9bmoXOe.js.map → knowledge-BHBt-6kW.js.map} +1 -1
  234. package/dashboard/dist/assets/{mcp-DYMFBOFz.js → mcp-CBcz4BCS.js} +2 -2
  235. package/dashboard/dist/assets/{mcp-DYMFBOFz.js.map → mcp-CBcz4BCS.js.map} +1 -1
  236. package/dashboard/dist/assets/{mcp-query-wloSpyPM.js → mcp-query-C4fKTOYo.js} +2 -2
  237. package/dashboard/dist/assets/{mcp-query-wloSpyPM.js.map → mcp-query-C4fKTOYo.js.map} +1 -1
  238. package/dashboard/dist/assets/{pipelines-DBXX6TEi.js → pipelines-CmAAH3B6.js} +2 -2
  239. package/dashboard/dist/assets/{pipelines-DBXX6TEi.js.map → pipelines-CmAAH3B6.js.map} +1 -1
  240. package/dashboard/dist/assets/role-display-Dvo2lDpU.js +2 -0
  241. package/dashboard/dist/assets/role-display-Dvo2lDpU.js.map +1 -0
  242. package/dashboard/dist/assets/task-queues-7-sHKPsN.js +2 -0
  243. package/dashboard/dist/assets/task-queues-7-sHKPsN.js.map +1 -0
  244. package/dashboard/dist/assets/{tasks-CD3-C7tP.js → tasks-BAEsqcz2.js} +2 -2
  245. package/dashboard/dist/assets/{tasks-CD3-C7tP.js.map → tasks-BAEsqcz2.js.map} +1 -1
  246. package/dashboard/dist/assets/{topics-D8pKi6zA.js → topics-CY0pJ6_o.js} +2 -2
  247. package/dashboard/dist/assets/{topics-D8pKi6zA.js.map → topics-CY0pJ6_o.js.map} +1 -1
  248. package/dashboard/dist/assets/{useEventHooks-DV9pLDOW.js → useEventHooks-BlpwRvHO.js} +2 -2
  249. package/dashboard/dist/assets/{useEventHooks-DV9pLDOW.js.map → useEventHooks-BlpwRvHO.js.map} +1 -1
  250. package/dashboard/dist/assets/{useNamespace-DykYhZY9.js → useNamespace-CoNUlZ6Z.js} +2 -2
  251. package/dashboard/dist/assets/{useNamespace-DykYhZY9.js.map → useNamespace-CoNUlZ6Z.js.map} +1 -1
  252. package/dashboard/dist/assets/{useYamlActivityEvents-By044UAv.js → useYamlActivityEvents-8GUn5Wie.js} +2 -2
  253. package/dashboard/dist/assets/{useYamlActivityEvents-By044UAv.js.map → useYamlActivityEvents-8GUn5Wie.js.map} +1 -1
  254. package/dashboard/dist/assets/{users-mI9goDjj.js → users-uGtDRGxR.js} +2 -2
  255. package/dashboard/dist/assets/{users-mI9goDjj.js.map → users-uGtDRGxR.js.map} +1 -1
  256. package/dashboard/dist/assets/{vendor-icons-ihWGa7_d.js → vendor-icons-DKCAtTJ9.js} +135 -130
  257. package/dashboard/dist/assets/vendor-icons-DKCAtTJ9.js.map +1 -0
  258. package/dashboard/dist/assets/{workflows-DCUilz68.js → workflows-DoX-AtHN.js} +2 -2
  259. package/dashboard/dist/assets/{workflows-DCUilz68.js.map → workflows-DoX-AtHN.js.map} +1 -1
  260. package/dashboard/dist/assets/{yaml-workflows-DWNMP1PT.js → yaml-workflows-DM3jVem2.js} +2 -2
  261. package/dashboard/dist/assets/{yaml-workflows-DWNMP1PT.js.map → yaml-workflows-DM3jVem2.js.map} +1 -1
  262. package/dashboard/dist/index.html +3 -3
  263. package/docs/api/http/escalations.md +15 -1
  264. package/docs/api/http/roles.md +2 -0
  265. package/docs/design-principles.md +234 -0
  266. package/docs/hitl/design.md +228 -0
  267. package/docs/hitl/form.md +21 -1
  268. package/docs/hitl/iframe.md +1 -1
  269. package/docs/hitl/x-lt-display.md +56 -0
  270. package/docs/hitl/x-lt-layout.md +20 -1
  271. package/docs/hitl/x-lt-list-schema.md +8 -1
  272. package/docs/hitl/x-lt-widget.md +23 -0
  273. package/docs/hitl-guide.md +4 -2
  274. package/docs/schema-enforcement.md +149 -0
  275. package/package.json +1 -1
  276. package/dashboard/dist/assets/AgentConfigPage-CqmtYBbC.js +0 -16
  277. package/dashboard/dist/assets/AgentConfigPage-CqmtYBbC.js.map +0 -1
  278. package/dashboard/dist/assets/AgentDetailPage-oYLBrN74.js +0 -4
  279. package/dashboard/dist/assets/AgentDetailPage-oYLBrN74.js.map +0 -1
  280. package/dashboard/dist/assets/AgentsPage-CHczRx5N.js +0 -2
  281. package/dashboard/dist/assets/AgentsPage-CHczRx5N.js.map +0 -1
  282. package/dashboard/dist/assets/AvailableEscalationsPage-DDVuivEE.js +0 -2
  283. package/dashboard/dist/assets/AvailableEscalationsPage-DDVuivEE.js.map +0 -1
  284. package/dashboard/dist/assets/CapabilitiesPage-lQJGZ_-t.js +0 -2
  285. package/dashboard/dist/assets/ConfirmDeleteModal-C0x0HcHX.js +0 -2
  286. package/dashboard/dist/assets/CountdownTimer-TXsRlTU4.js +0 -2
  287. package/dashboard/dist/assets/CredentialsPage-BKxS-1sA.js.map +0 -1
  288. package/dashboard/dist/assets/CustomDurationPicker-j4bBrDJ6.js +0 -2
  289. package/dashboard/dist/assets/CustomDurationPicker-j4bBrDJ6.js.map +0 -1
  290. package/dashboard/dist/assets/DataTable-Byn3oyT9.js +0 -2
  291. package/dashboard/dist/assets/DataTable-Byn3oyT9.js.map +0 -1
  292. package/dashboard/dist/assets/EscalationListSchemaPage-CIJ-OgVC.js +0 -20
  293. package/dashboard/dist/assets/EscalationListSchemaPage-CIJ-OgVC.js.map +0 -1
  294. package/dashboard/dist/assets/EscalationSchemaPage-C7dHgG8e.js +0 -9
  295. package/dashboard/dist/assets/EscalationSchemaPage-C7dHgG8e.js.map +0 -1
  296. package/dashboard/dist/assets/EscalationsOverview-D8NrATg2.js +0 -2
  297. package/dashboard/dist/assets/EscalationsOverview-D8NrATg2.js.map +0 -1
  298. package/dashboard/dist/assets/EventTable-DBV1P2ME.js +0 -2
  299. package/dashboard/dist/assets/EventTable-DBV1P2ME.js.map +0 -1
  300. package/dashboard/dist/assets/FilterBar-B5Vk5tBF.js +0 -2
  301. package/dashboard/dist/assets/FilterBar-B5Vk5tBF.js.map +0 -1
  302. package/dashboard/dist/assets/GraphInvokePage-DbHvggQ_.js +0 -2
  303. package/dashboard/dist/assets/GraphInvokePage-DbHvggQ_.js.map +0 -1
  304. package/dashboard/dist/assets/HomePage-BpexnDO_.js +0 -2
  305. package/dashboard/dist/assets/HomePage-BpexnDO_.js.map +0 -1
  306. package/dashboard/dist/assets/ListToolbar-KC6LGtKP.js +0 -2
  307. package/dashboard/dist/assets/ListToolbar-KC6LGtKP.js.map +0 -1
  308. package/dashboard/dist/assets/McpOverview-BDZC9Hvl.js +0 -2
  309. package/dashboard/dist/assets/McpOverview-BDZC9Hvl.js.map +0 -1
  310. package/dashboard/dist/assets/McpQueryDetailPage-CXhgX1pS.js +0 -5
  311. package/dashboard/dist/assets/McpQueryDetailPage-CXhgX1pS.js.map +0 -1
  312. package/dashboard/dist/assets/McpRunDetailPage-BM3mUSlH.js +0 -2
  313. package/dashboard/dist/assets/McpRunDetailPage-BM3mUSlH.js.map +0 -1
  314. package/dashboard/dist/assets/McpRunsPage-f5hDbpLo.js +0 -2
  315. package/dashboard/dist/assets/McpRunsPage-f5hDbpLo.js.map +0 -1
  316. package/dashboard/dist/assets/NamespacePill-C4u6Go1W.js +0 -2
  317. package/dashboard/dist/assets/NamespacePill-C4u6Go1W.js.map +0 -1
  318. package/dashboard/dist/assets/OperationsPage-D37cZ8Qh.js +0 -2
  319. package/dashboard/dist/assets/OperationsPage-D37cZ8Qh.js.map +0 -1
  320. package/dashboard/dist/assets/OperatorDashboard-_axS17fp.js +0 -2
  321. package/dashboard/dist/assets/PageHeader-Bxa1XFz0.js +0 -2
  322. package/dashboard/dist/assets/PageHeader-Bxa1XFz0.js.map +0 -1
  323. package/dashboard/dist/assets/PageHeaderWithStats-D_sO0MqJ.js +0 -2
  324. package/dashboard/dist/assets/PageHeaderWithStats-D_sO0MqJ.js.map +0 -1
  325. package/dashboard/dist/assets/PriorityBadge-B4ykBH7f.js.map +0 -1
  326. package/dashboard/dist/assets/ProcessDetailPage-D8pAWUXx.js +0 -2
  327. package/dashboard/dist/assets/ProcessDetailPage-D8pAWUXx.js.map +0 -1
  328. package/dashboard/dist/assets/RoleDetailPage-ufbypNNR.js +0 -8
  329. package/dashboard/dist/assets/RoleDetailPage-ufbypNNR.js.map +0 -1
  330. package/dashboard/dist/assets/RolePill-DLwmVMam.js +0 -2
  331. package/dashboard/dist/assets/RolePill-DLwmVMam.js.map +0 -1
  332. package/dashboard/dist/assets/RolesPage-CxpXm4Sw.js +0 -2
  333. package/dashboard/dist/assets/RolesPage-CxpXm4Sw.js.map +0 -1
  334. package/dashboard/dist/assets/RunAsSelector-BebF8Lib.js +0 -2
  335. package/dashboard/dist/assets/RunAsSelector-BebF8Lib.js.map +0 -1
  336. package/dashboard/dist/assets/SlidePanel-BDVDuwIf.js +0 -2
  337. package/dashboard/dist/assets/SlidePanel-BDVDuwIf.js.map +0 -1
  338. package/dashboard/dist/assets/StatCard-DrdlUeQz.js +0 -2
  339. package/dashboard/dist/assets/StatCard-DrdlUeQz.js.map +0 -1
  340. package/dashboard/dist/assets/StatusBadge-CEQMRmSQ.js +0 -2
  341. package/dashboard/dist/assets/StatusBadge-CEQMRmSQ.js.map +0 -1
  342. package/dashboard/dist/assets/StreamMessageDetail-BIXbrZM0.js +0 -2
  343. package/dashboard/dist/assets/StreamMessageDetail-BIXbrZM0.js.map +0 -1
  344. package/dashboard/dist/assets/SwimlaneTimeline-CC5qZjYo.js +0 -2
  345. package/dashboard/dist/assets/SwimlaneTimeline-CC5qZjYo.js.map +0 -1
  346. package/dashboard/dist/assets/TagInput-C2gi47M8.js +0 -2
  347. package/dashboard/dist/assets/TagInput-C2gi47M8.js.map +0 -1
  348. package/dashboard/dist/assets/TaskQueuePill-BPP0Almh.js +0 -2
  349. package/dashboard/dist/assets/TaskQueuePill-BPP0Almh.js.map +0 -1
  350. package/dashboard/dist/assets/TimestampCell-DGOsba-A.js +0 -2
  351. package/dashboard/dist/assets/TimestampCell-DGOsba-A.js.map +0 -1
  352. package/dashboard/dist/assets/ToolPill-D74TkYB-.js +0 -2
  353. package/dashboard/dist/assets/ToolPill-D74TkYB-.js.map +0 -1
  354. package/dashboard/dist/assets/ToolTestPanel-Bez6OyRL.js +0 -2
  355. package/dashboard/dist/assets/ToolTestPanel-Bez6OyRL.js.map +0 -1
  356. package/dashboard/dist/assets/TopicDetailPage-BHccgkU_.js +0 -9
  357. package/dashboard/dist/assets/TopicDetailPage-BHccgkU_.js.map +0 -1
  358. package/dashboard/dist/assets/TopicsPage-5yW63ELl.js +0 -2
  359. package/dashboard/dist/assets/WorkflowExecutionPage-CDDLled5.js +0 -2
  360. package/dashboard/dist/assets/WorkflowExecutionPage-CDDLled5.js.map +0 -1
  361. package/dashboard/dist/assets/WorkflowPill-Fjc2q9U9.js +0 -2
  362. package/dashboard/dist/assets/WorkflowPill-Fjc2q9U9.js.map +0 -1
  363. package/dashboard/dist/assets/WorkflowsOverview-D8MkSMex.js +0 -2
  364. package/dashboard/dist/assets/WorkflowsOverview-D8MkSMex.js.map +0 -1
  365. package/dashboard/dist/assets/YamlWorkflowsPage-9Ki54d-9.js +0 -2
  366. package/dashboard/dist/assets/YamlWorkflowsPage-9Ki54d-9.js.map +0 -1
  367. package/dashboard/dist/assets/bots-CAYJYL9E.js +0 -2
  368. package/dashboard/dist/assets/bots-CAYJYL9E.js.map +0 -1
  369. package/dashboard/dist/assets/escalation-columns-BbS5X61l.js +0 -2
  370. package/dashboard/dist/assets/escalation-columns-BbS5X61l.js.map +0 -1
  371. package/dashboard/dist/assets/index-B68A3v-S.js +0 -2
  372. package/dashboard/dist/assets/index-B68A3v-S.js.map +0 -1
  373. package/dashboard/dist/assets/index-BAi89Xpm.js +0 -2
  374. package/dashboard/dist/assets/index-BAi89Xpm.js.map +0 -1
  375. package/dashboard/dist/assets/index-BJ0jjFsI.js +0 -5
  376. package/dashboard/dist/assets/index-BJ0jjFsI.js.map +0 -1
  377. package/dashboard/dist/assets/index-BOJMUk4F.js +0 -2
  378. package/dashboard/dist/assets/index-BOJMUk4F.js.map +0 -1
  379. package/dashboard/dist/assets/index-BrgYykPV.js +0 -2
  380. package/dashboard/dist/assets/index-BrgYykPV.js.map +0 -1
  381. package/dashboard/dist/assets/index-BxgtIqvl.js +0 -2
  382. package/dashboard/dist/assets/index-BxgtIqvl.js.map +0 -1
  383. package/dashboard/dist/assets/index-C0cHXaX-.css +0 -1
  384. package/dashboard/dist/assets/index-CDHDoHYz.js +0 -6
  385. package/dashboard/dist/assets/index-CDHDoHYz.js.map +0 -1
  386. package/dashboard/dist/assets/index-CUZJHXew.js +0 -2
  387. package/dashboard/dist/assets/index-CUZJHXew.js.map +0 -1
  388. package/dashboard/dist/assets/index-D5n_e59T.js +0 -2
  389. package/dashboard/dist/assets/index-D5n_e59T.js.map +0 -1
  390. package/dashboard/dist/assets/index-DIgiIoou.js +0 -63
  391. package/dashboard/dist/assets/index-DIgiIoou.js.map +0 -1
  392. package/dashboard/dist/assets/index-Dd1RxGNw.js +0 -2
  393. package/dashboard/dist/assets/index-iOb3sGsC.js +0 -5
  394. package/dashboard/dist/assets/index-iOb3sGsC.js.map +0 -1
  395. package/dashboard/dist/assets/index-mcAkQblr.js +0 -2
  396. package/dashboard/dist/assets/index-mcAkQblr.js.map +0 -1
  397. package/dashboard/dist/assets/vendor-icons-ihWGa7_d.js.map +0 -1
@@ -268,6 +268,7 @@ Returns all roles with metadata and usage counts.
268
268
  | `metadata_schema` | `object \| null` | JSON Schema declaring the expected shape of `lt_escalations.metadata` for this role. Drives faceted-query key autocomplete and creation-time validation |
269
269
  | `properties` | `object` | Free user-owned bag — icons, colors, tags, etc. No reserved keys |
270
270
  | `ops_visible` | `boolean` | When `true`, the role appears as a station on the `/operations` view |
271
+ | `enforce_schema` | `boolean` | When `true`, every resolve surface validates submitted payloads against this role's form schema and rejects violations with `422` (`code: schema_validation`) — see [Schema Enforcement](../../schema-enforcement.md) |
271
272
  | `parent_role` | `string \| null` | Parent role in the process dependency graph; `null` for root stations |
272
273
  | `sla_minutes` | `number \| null` | Target resolution time in minutes (capacity setting) |
273
274
  | `target_per_hour` | `number \| null` | Intended throughput — items resolved per hour (capacity setting) |
@@ -366,6 +367,7 @@ When the update changes `form_schema` or `metadata_schema`, the new pair is snap
366
367
  | `metadata_schema` | `object \| null` | JSON Schema for `lt_escalations.metadata` |
367
368
  | `properties` | `object \| null` | Free user-owned bag |
368
369
  | `ops_visible` | `boolean` | Include in the `/operations` view |
370
+ | `enforce_schema` | `boolean` | Turn server-side resolver schema validation on/off |
369
371
  | `parent_role` | `string \| null` | Parent in the process dependency graph |
370
372
  | `sla_minutes` | `number \| null` | SLA target in minutes |
371
373
  | `target_per_hour` | `number \| null` | Throughput target (items per hour) |
@@ -0,0 +1,234 @@
1
+ # Design Principles
2
+
3
+ The design system behind the dashboard and every generated x-lt-* form. One
4
+ token vocabulary, one value discipline, one form doctrine — the built-in pages
5
+ and the forms customers author render from the same rules.
6
+
7
+ ---
8
+
9
+ ## 1. Tokens are the system
10
+
11
+ Every color, size, radius, and spacing knob flows through a `--lt-*` CSS
12
+ variable declared in `dashboard/src/styles/globals.css`. Nothing paints a raw
13
+ hex. A registered stylesheet (`branding.customCss` / `branding.themes` in
14
+ `start()`) can therefore restyle the entire product — including full dark
15
+ themes — with zero source changes. Midnight is the standing proof: if a color
16
+ escapes the token system, it glows in the dark.
17
+
18
+ Deliberate exceptions, each carrying a stated constraint in code: the
19
+ signature pad (the exported PNG is a document — ink on white), customer
20
+ iframes (their HTML assumes a white canvas), OAuth brand marks, theme-picker
21
+ swatches, and overlay scrims.
22
+
23
+ ## 2. Color — the value discipline
24
+
25
+ Chroma is spent, not spread.
26
+
27
+ - **Surfaces are near-neutral.** Hover fills, section bands, and borders
28
+ carry only a whisper of the theme hue. Chroma never lands on a large
29
+ rectangle — a colored slab makes every real accent invisible.
30
+ - **One elevation logic, both polarities.** The page carries the base wash
31
+ (a light theme tint by day, the deep shade at night); sections and sheets
32
+ are distinct surfaces on it (white paper by day, the deeper well at
33
+ night); fields are wells that step AWAY from their surface (darker than
34
+ white by day, lighter than the shade at night). Light is Midnight
35
+ inverted — never a flat white page with white sections.
36
+ - **The accent appears in small doses**: links, buttons, checks, pills,
37
+ section labels, focus states. Because it is scarce, it reads as "act here."
38
+ - **Dark themes are the same principle, inverted** — near-black neutral
39
+ surfaces, the identical accent discipline. Midnight is not a different
40
+ design; it is the same design at night.
41
+ - **Status is information, not decoration.** Text-safe status tokens
42
+ (≥4.5:1) for words; brighter `-graphic` variants for dots, bars, and
43
+ charts (≥3:1).
44
+ - **The focus fill is the one ambient accent moment** — entering a field
45
+ earns the tint.
46
+
47
+ ## 3. Text — black speaks, color acts
48
+
49
+ - One neutral near-black ramp shared by every light theme: `#171717` body,
50
+ `#373737` labels, `#646464` helper, `#8C8C8C` low-emphasis meta. Labels are
51
+ never theme-colored — a tinted label is indistinguishable from a link.
52
+ If it's colored, it must be clickable.
53
+ - **11px floor.** No informative text below `text-2xs`. Quaternary tone is
54
+ reserved for large or decorative text (≥3:1).
55
+ - **Headings**: near-black (`#333333`), small caps, weight 300, generous
56
+ sizes — `heading-1` (2.125rem) / `heading-2` (1.5rem) / `heading-3`
57
+ (1.25rem). The size carries hierarchy; the light weight keeps it elegant.
58
+ Mono identifiers (role keys, workflow types, topics) stay code-styled,
59
+ never small-caps.
60
+
61
+ ## 4. Fields and controls
62
+
63
+ - **One recipe** (`.field/.input/.select/.textarea`): white body, light
64
+ neutral border, 3px radius (`--lt-radius-field`), border deepens to accent
65
+ and fill takes the theme tint on focus. Every field-like control uses the
66
+ recipe or the tokens — no bespoke field styling.
67
+ - **Width follows content, capped by the measure:**
68
+ - Generated forms hold a readable measure (`max-w-form`, 56rem). A wide
69
+ monitor gets margin, never a 2000px input.
70
+ - Selects size to their content, floored at 16rem for presence — a
71
+ one-word choice never stretches across the page. The floor yields to
72
+ the cell (`min(16rem, 100%)`): CSS lets min-width beat max-width, so
73
+ a bare floor would overflow a narrow column.
74
+ - Number and date inputs hold a hand-sized width (12rem; datetimes
75
+ 16rem) — fixed-length values never stretch to the measure.
76
+ - Text, textarea, email, and url fill their cell — prose-length content
77
+ deserves the measure.
78
+ - Widgets follow the same split. Prose surfaces (code editor, markdown,
79
+ rich text, uploads) fill the cell. The signature pad holds its natural
80
+ document proportion (25rem, the 400×150 export) — the PNG is the
81
+ artifact, and stretching the pad stretches the document.
82
+ - **Decisions are never checkboxes.** A decision is an enum opening on an
83
+ explicit disabled **Choose…** placeholder. The user picks; nothing is an
84
+ implicit first option, and there is no way back to unchosen.
85
+ - Checkboxes exist only inside checklists — confirmations of work, not
86
+ choices.
87
+ - **Item length and selection mode determine list geometry.**
88
+ Sentence-length require-all *rituals* read top-down, one per row — the
89
+ vertical stack is deliberate. Short pick-any *tags* (reasons, categories)
90
+ flow horizontally as selectable chips: thumb-sized pills that wrap,
91
+ selection in solid accent. Never stack six two-word items into six
92
+ full-width rows — endless y with an empty x is a dead layout.
93
+ - **Required is marked, everywhere.** Every required input — text, select,
94
+ number, upload, checkbox, or checkbox group — carries the red asterisk at
95
+ its label. A require-all checklist is required by definition and marked
96
+ like the rest. Required or optional is never a guess.
97
+
98
+ ## 5. Layout — the canonical shell
99
+
100
+ ```
101
+ ┌──────────────── Header (full width) ────────────────┐
102
+ │ left nav │ main viewport │ right panel │
103
+ └──────────────── EventFeed (full width) ─────────────┘
104
+ ```
105
+
106
+ - Header and event-stream footer span the full width. The left nav and the
107
+ global right SlidePanel are flex siblings of the main viewport — content
108
+ narrows when either opens; nothing overlays it. Pages fill the right panel
109
+ via `useShellPanel()`. The DocsDrawer is the full-screen overlay for
110
+ markdown.
111
+ - iPad (768–1366) is the floor target: page padding clamps down, the sidebar
112
+ auto-collapses below 1024, nothing depends on hover alone.
113
+ - Structure comes from typography, whitespace, and divider lines — never
114
+ rounded-bordered cards, never gradient fades. Sections sit on a barely
115
+ sunken band with an accent left rule.
116
+
117
+ ## 6. The form doctrine
118
+
119
+ The reference implementation is `examples/workflows/acme-stations` — mimic it.
120
+
121
+ 1. **Facts first, as a dictionary.** Read-only work-item facts render as a dense
122
+ label/value spec sheet (`x-lt-display: "dictionary"`), not form rows.
123
+ 2. **One explicit decision** gates the form. Until it is made the page shows
124
+ facts, the decision, nothing else — even sign-off waits.
125
+ 3. **Linear reveals.** Each outcome fades in exactly the section it needs
126
+ (`x-lt-showIf` value matches). The enter animation makes the
127
+ cause-and-effect visible in time: click Reject, watch the report arrive.
128
+ 4. **The left/right law.** Left and Right always render side by side, Left
129
+ first — as dictionary pairs (two-column dictionaries fill row by row, so
130
+ consecutive items share a row) and as inputs (`x-lt-column-group` pairs
131
+ them in one cell, the 2×2).
132
+ 5. **Checklists**: standard confirmations may arrive pre-checked
133
+ (formDefaults) — the standard is the default and the resolver unchecks
134
+ what isn't true. The work item's own custom work arrives unchecked; those
135
+ clicks are the record. `x-lt-require-all` guards completion.
136
+ 6. **Every input carries a `title` and one instructional `description`
137
+ line — no input goes without.** The anatomy is fixed and identical for
138
+ every input kind: label, instruction, control, in that order. The
139
+ anatomy is a declared column, never an accident of width — the label is
140
+ a block, so a narrow control (a number, a short select) can never ride
141
+ sideways beside it when the instruction line is absent. Inputs that
142
+ share a row carry instructions of similar length so their controls align
143
+ across the row; a bare input beside an instructed one is a consistency
144
+ defect, in both content and alignment. The WHY — reference tables,
145
+ consequences, vocabulary — lives in `x-lt-help` beside the form.
146
+ 7. **Sign-off last.** A short audit note with a live counter.
147
+ 8. Hidden conditional fields still submit their defaults; resolver contracts
148
+ treat empty as absent.
149
+
150
+ ## 7. Copy — the economist tone
151
+
152
+ The escalation `description` is the detail page's title: a short noun
153
+ phrase naming the artifact — "Final QA — ACME-1042 · wgt-8127" —
154
+ never a sentence, never an instruction, never a status report.
155
+
156
+ Say what to do. Speak to the actor, in the imperative, always: "Pick Reject
157
+ to file a report" — never "Reject files a report." The system is never the
158
+ subject of an instruction, and passive voice never appears. One line per
159
+ instruction; consequences ride the same sentence ("Resolve — the widget moves
160
+ on to assembly"). Reference tables over paragraphs. Never preachy, never
161
+ motivational, never "you're the first human — do well." State what a thing is
162
+ and does, never what it lacks.
163
+
164
+ ## 8. Motion
165
+
166
+ Motion explains causality; it never decorates. Reveals fade in tied to the
167
+ choice that caused them (200–300ms enter). Errors shake once and settle.
168
+ Panels slide as flex siblings so content reflows rather than being covered.
169
+ Nothing loops, nothing floats.
170
+
171
+ ## 9. Accessibility floors
172
+
173
+ - Body and label text ≥4.5:1; large/meta text and graphics ≥3:1.
174
+ - Every input is label-associated; errors and helpers ride
175
+ `aria-describedby`; the errors panel click-focuses the field
176
+ (`data-field-key` is a contract).
177
+ - Disabled forms use `inert` — out of the tab order, not just grayed.
178
+ - `color-scheme` follows the theme so native controls match the surface.
179
+
180
+ ## 10. Responsive — geometry follows the container
181
+
182
+ 1. **Geometry follows the container, not the viewport.** Side panels, the
183
+ facet drawer, and the nav rail narrow containers below any viewport
184
+ breakpoint — a `md:`/`lg:` variant on a content grid is a defect.
185
+ Viewport variants are lawful only in the shell frame (header, nav rail),
186
+ where the viewport is the container.
187
+ 2. **Thresholds are named container tokens** (tailwind `containers`):
188
+ `@dict-inline` 22rem · `@grp-cols` 26rem · `@form-cols` 34rem ·
189
+ `@filters` 72rem · `@dict-pairs` 38rem · `@table` 48rem. Components use
190
+ the names, never raw rem values. A fold threshold is sized for the
191
+ HEAVIEST content its component carries — escaping to the condensed
192
+ format early beats ever letting a bar wrap.
193
+ 3. **The dictionary reflow ladder** — the poster child. The same
194
+ label/value pairs render as: two pairs per row at `@dict-pairs`; one
195
+ label|value pair per row at `@dict-inline`; label stacked over value
196
+ below. Pairing order never changes across geometries.
197
+ 4. **A table row IS a dictionary.** Below `@table`, tables fold into dense
198
+ console-style cards — identity columns become the title line, the rest
199
+ fold into label/value pairs on the dictionary ladder. **Tables never
200
+ scroll horizontally** — master lists use `layout="fixed"` so a table
201
+ physically cannot outgrow its container. Fixed columns cut both ways:
202
+ **cell content never bleeds into a neighbor** — every cell clips
203
+ (`overflow-hidden` on the `td`) and every text-bearing pill or label
204
+ truncates with an ellipsis (`max-w-full` + `truncate`), carrying the
205
+ full value in `title` for hover reveal.
206
+ 5. **The column budget, disclosed both ways.** The floor set — identity,
207
+ owner, urgency, age — always renders. Enrichment columns (workflow,
208
+ metadata facets) return only when the table's container has room
209
+ (`showFrom: '@split' | '@wall'`); below that they fold into card pairs.
210
+ The list is a jumping-off spot: the metadata cell carries the refine
211
+ icons — filter within the role, search across roles, shift+click to AND
212
+ facets — the ELK-style drill the whole product surfaces.
213
+ 6. **Refine icons are always visible enough to tap** — half-opacity at
214
+ rest, full on hover. Hover-only affordances do not exist on the floor.
215
+ 7. **Disclosure order**: drop priority-3 columns, fold priority-2 into
216
+ pairs, keep priority-1 always.
217
+ 8. **Touch parity**: every hover-only affordance has a tap equivalent —
218
+ the iPad floor has no hover.
219
+ 9. **The header diet ladder** (below lg): mark-only logo, icon+count links,
220
+ secondary actions fold into the user menu, the nav rail becomes a
221
+ drawer behind a menu button.
222
+ 10. **Sticky elements are never containers** — `container-type` breaks
223
+ `position: sticky` on the same element; wrap the geometry element
224
+ instead.
225
+
226
+ ## 11. Never
227
+
228
+ Raw hex in components. Chroma on large surfaces. Theme-colored labels.
229
+ Text below 11px. Decisions as checkboxes. Implicit select defaults.
230
+ Full-width one-word inputs. Tag lists stacked as rows. Unmarked required
231
+ fields. Inputs without instruction lines. Passive voice or system-voice
232
+ copy. Horizontal scroll. Viewport breakpoints on content grids. Cards and
233
+ gradient fades. "Back to X" links. Polling. Preachy copy. Customer names —
234
+ the showcase brand is always Acme.
@@ -0,0 +1,228 @@
1
+ # Design
2
+
3
+ Long-tail ships one design system. The built-in pages and every form generated
4
+ from your schemas render from the same rules, so a form you author with plain
5
+ JSON Schema and `x-lt-*` tokens arrives already styled, responsive, accessible,
6
+ and themed. This page explains the principles in the terms that matter when you
7
+ author schemas and copy, the rules the renderer applies to errors, and how a
8
+ deployment plugs its own stylesheet in through static config.
9
+
10
+ The full internal doctrine lives in
11
+ [`docs/design-principles.md`](../design-principles.md); the reference form
12
+ implementation is `examples/workflows/acme-stations`.
13
+
14
+ ---
15
+
16
+ ## The principles, from the author's seat
17
+
18
+ ### Tokens carry everything
19
+
20
+ Every color, size, radius, and spacing value in the product flows through a
21
+ `--lt-*` CSS variable. Your schemas never mention style — they declare
22
+ structure (`title`, `description`, sections, `x-lt-*` tokens) and the renderer
23
+ supplies the design. This is what makes theming total: a registered stylesheet
24
+ restyles the dashboard and your generated forms in one stroke, including full
25
+ dark themes.
26
+
27
+ ### Color means "act here"
28
+
29
+ Surfaces stay near-neutral with a whisper of the theme hue; the saturated
30
+ accent appears only in small doses — links, buttons, checks, focus states.
31
+ Because color is scarce, anything colored reads as actionable. The same rule
32
+ governs your content: labels and copy render near-black, status renders through
33
+ the status tokens, and the accent marks the interactive. A form that "adds
34
+ color" through its copy fights the system; a form that relies on structure gets
35
+ the emphasis for free.
36
+
37
+ ### Text: black speaks, color acts
38
+
39
+ Labels resolve from your schema (`title` first, then title-cased keys — see
40
+ [form.md](form.md)) and render in the neutral near-black ramp. Headings are
41
+ small-caps, light-weight, generously sized; section names you declare in
42
+ `x-lt-section-options` render in that voice. Nothing informative renders below
43
+ 11px.
44
+
45
+ ### Fields behave by declaration
46
+
47
+ - **Width follows content.** Forms hold a readable measure; selects size to
48
+ their options; numbers stay hand-sized; prose fields fill their cell. You
49
+ never set widths.
50
+ - **Decisions are enums.** A decision field renders as a select opening on an
51
+ explicit disabled **Choose…** placeholder — the resolver picks; nothing is
52
+ an implicit default. Model decisions as `enum`, never as booleans.
53
+ - **Checkboxes are confirmations of work**, and they live in checklists.
54
+ Sentence-length require-all rituals render one per row; short pick-any tags
55
+ (reasons, categories) flow as selectable chips. The renderer chooses from
56
+ your item lengths and `x-lt-require-all`; override with `x-lt-variant`.
57
+ - **Required is marked everywhere.** Every field in `required` — and every
58
+ require-all checklist — carries the red asterisk. Declare `required`
59
+ honestly and the form communicates it uniformly.
60
+
61
+ ### The form doctrine
62
+
63
+ The shape that makes a resolver fast:
64
+
65
+ 1. **Facts first, as a dictionary** — read-only work-item facts render as a dense
66
+ spec sheet (`x-lt-display: "dictionary"`), not as form rows.
67
+ 2. **One explicit decision gates the form** — until the outcome is chosen,
68
+ the page shows facts and the decision, nothing else.
69
+ 3. **Linear reveals** — each outcome fades in exactly the section it needs
70
+ (`x-lt-showIf`), so cause and effect are visible in time.
71
+ 4. **Pairs render side by side** — `x-lt-column-group` keeps Left and Right
72
+ together; two-column dictionaries fill row by row.
73
+ 5. **Sign-off last** — a short audit note closes the form.
74
+
75
+ ### Responsive comes free
76
+
77
+ Geometry follows the container, not the viewport. Your dictionary reflows from
78
+ paired columns to stacked labels as space narrows; tables fold into dense
79
+ console cards instead of ever scrolling horizontally; the form grid drops to
80
+ one column when a side panel narrows it. Authors declare structure once; every
81
+ width from an iPad to a wall monitor is handled.
82
+
83
+ ---
84
+
85
+ ## Error display
86
+
87
+ Errors follow fixed rules; your schema and copy determine only their content.
88
+
89
+ **At the field.** The first failing check on a visible field renders inline:
90
+ the control's border turns to the status-error token, the field shakes once and
91
+ settles, and the message appears beneath the control. Format guards fire on
92
+ blur; everything else fires on submit. The message rides `aria-describedby`,
93
+ so screen readers announce it with the field.
94
+
95
+ **The errors panel.** A blocked submit opens the right-side panel with the
96
+ full list — one row per issue, showing the field's label (derived exactly as
97
+ the form derives it, so `title` pays off here too) and the message. Clicking a
98
+ row scrolls to and focuses the field. The panel is `aria-live`; the count
99
+ ("3 issues to resolve") updates as fixes land.
100
+
101
+ **Server enforcement, same surfaces.** Roles with schema enforcement enabled
102
+ validate the submission server-side and reject invalid payloads with a
103
+ canonical `422` carrying per-field errors. The form maps those onto the same
104
+ inline and panel surfaces — a resolver sees one error language whether the
105
+ check ran in the browser or on the server.
106
+
107
+ **Hidden fields are exempt.** Fields hidden by `x-lt-showIf` at submission
108
+ time are skipped by required validation — an unchosen branch never blocks the
109
+ chosen one.
110
+
111
+ **Error copy is instruction.** An error message states the fix, in the
112
+ imperative, in one line: "Enter a quantity between 1 and 4." It names what to
113
+ do next, never narrates what went wrong at length, and never speaks as the
114
+ system ("invalid input detected"). When you supply custom validation messages,
115
+ write them as the direct instruction the resolver should follow.
116
+
117
+ ---
118
+
119
+ ## Recommendations — write instructions that work
120
+
121
+ The renderer guarantees the anatomy: label, one instruction line, control, in
122
+ that order, for every input. Your job is the words. The tone is a sparse
123
+ economist: say what to do, to the actor, and stop.
124
+
125
+ | Write | In place of |
126
+ |-------|-------------|
127
+ | Pick Reject to file a report | Reject files a report |
128
+ | Confirm each custom item on the widget | Items should be confirmed |
129
+ | Enter the count from the work ticket | Quantity field |
130
+ | Resolve — the widget moves on to final QA | When resolved, the widget will be moved on |
131
+
132
+ - **Every input carries a `title` and one `description` line.** The
133
+ description is the instruction: imperative, actor-addressed, one line.
134
+ Inputs sharing a row should carry instructions of similar length so their
135
+ controls align.
136
+ - **Consequences ride the same sentence.** "Pick Complete to send the widget
137
+ to final QA" tells the actor what to do and what happens, in one
138
+ breath. A second sentence is almost always the start of a paragraph nobody
139
+ reads.
140
+ - **The escalation `description` is a title, not a sentence.** It names the
141
+ artifact — "Addons — ACME-1042 · wgt-8127" — because it becomes the detail
142
+ page's heading and the list's summary line.
143
+ - **The WHY lives in `x-lt-help`.** Reference tables, vocabulary, and
144
+ decision consequences belong in the side panel, in markdown, where the
145
+ resolver can consult them without the form carrying paragraphs. Keep it
146
+ factual; skip the motivation.
147
+ - **Let defaults do the routine work.** Standard confirmations arrive
148
+ pre-checked via `formDefaults` — the resolver unchecks what isn't true.
149
+ The work item's own custom items arrive unchecked; those clicks are the record.
150
+
151
+ ---
152
+
153
+ ## Plugging in a stylesheet
154
+
155
+ A deployment takes control of the design system through the `branding` block
156
+ of the static start config. `customCss` is appended to the dashboard
157
+ stylesheet; `themes` registers full themes that join the header theme picker
158
+ alongside the built-ins. Both are served at `GET /api/settings/custom.css` and
159
+ load before first paint.
160
+
161
+ ```typescript
162
+ import { start } from 'long-tail';
163
+
164
+ await start({
165
+ // ...database, workers, roles...
166
+ branding: {
167
+ appName: 'AcmeAdmin',
168
+
169
+ // Targeted overrides — adjust tokens globally.
170
+ customCss: `
171
+ :root {
172
+ --lt-radius-field: 6px;
173
+ --lt-measure-form: 48rem;
174
+ }
175
+ `,
176
+
177
+ // A full registered theme. Author every --lt-* token under the
178
+ // [data-theme] selector; the id becomes the data-theme attribute and
179
+ // the theme appears in the header picker with its swatch.
180
+ themes: [
181
+ {
182
+ id: 'acme-slate',
183
+ label: 'Acme Slate',
184
+ swatch: '#334155',
185
+ dark: true,
186
+ css: `
187
+ [data-theme='acme-slate'] {
188
+ color-scheme: dark;
189
+ --lt-color-scheme: dark;
190
+
191
+ --lt-surface: 15 23 42; /* page wash */
192
+ --lt-surface-raised: 30 41 59; /* header, sheets */
193
+ --lt-surface-sunken: 10 16 30; /* section bands */
194
+ --lt-surface-hover: 40 53 74;
195
+ --lt-surface-border: 51 65 85;
196
+
197
+ --lt-field-bg: 34 46 66; /* fields step lighter than the page */
198
+ --lt-field-border: 71 85 105;
199
+ --lt-field-focus: 41 56 81;
200
+
201
+ --lt-accent: 125 170 255;
202
+ --lt-accent-hover: 155 190 255;
203
+ --lt-heading: 170 200 255;
204
+
205
+ --lt-text-primary: 233 238 248;
206
+ --lt-text-secondary: 185 197 219;
207
+ --lt-text-tertiary: 140 155 182;
208
+ --lt-text-quaternary: 105 120 148;
209
+ }
210
+ `,
211
+ },
212
+ ],
213
+ },
214
+ });
215
+ ```
216
+
217
+ Values are RGB triplets (`15 23 42`, not `#0F172A`) so the system can apply
218
+ alpha through `rgb(var(--lt-*) / <alpha>)`. The bundled Midnight theme
219
+ (`examples/themes/midnight.ts`) is a complete worked example covering every
220
+ token family — surfaces, fields, accent, text ramp, and the status family —
221
+ and is registered through this exact mechanism; deployment themes are
222
+ first-class in the same way.
223
+
224
+ Keep the elevation logic when authoring a theme: the page carries the wash,
225
+ sections are distinct surfaces on it, and fields step away from their surface
226
+ — darker than the sheets in a light theme, lighter than the shade in a dark
227
+ one. A theme that holds those relationships inherits the whole system's
228
+ legibility; the tokens make it a matter of picking six surface values.
package/docs/hitl/form.md CHANGED
@@ -4,6 +4,26 @@ The dashboard renders forms automatically from JSON Schema. No frontend code nee
4
4
 
5
5
  ---
6
6
 
7
+ ## Field Labels
8
+
9
+ A field's label resolves in order:
10
+
11
+ 1. The `title` keyword — the author's explicit label, always wins.
12
+ 2. Snake/kebab keys title-cased: `left_quantity` → `Left Quantity`. All-caps tokens inside a key keep their casing (`po_number` → `Po Number`, `PO_number` → `PO Number`).
13
+ 3. Single-token keys pass through unchanged: `PO`, `SKU`, `LEFTQUANTITY`.
14
+
15
+ Declare `title` on every field whose key isn't already a readable label — it is the difference between `LEFTQUANTITY` and `Left Quantity` everywhere the field faces a human: the form, the dictionary display, and the errors panel.
16
+
17
+ ```json
18
+ {
19
+ "properties": {
20
+ "LEFTQUANTITY": { "type": "number", "title": "Left Quantity" }
21
+ }
22
+ }
23
+ ```
24
+
25
+ ---
26
+
7
27
  ## Field Types
8
28
 
9
29
  | JSON Type | Renders As |
@@ -46,7 +66,7 @@ Use `format` on any `string` field to get a specialized input:
46
66
 
47
67
  ## Required Fields
48
68
 
49
- Fields listed in `required` show a red asterisk and block submission when empty:
69
+ Every required input text, select, number, upload, or checklist group — shows a red asterisk at its label and blocks submission when empty. A checklist with `x-lt-require-all` is required by definition and carries the asterisk too. Required or optional is never a guess.
50
70
 
51
71
  ```json
52
72
  {
@@ -54,7 +54,7 @@ const WORKBENCH_FORM_SCHEMA = {
54
54
  // Workflow side — the wait is a normal conditionLT; the iframe submits the payload
55
55
  const design = await conditionLT<{ stl_url: string }>(signalId, {
56
56
  role: 'cad-designer',
57
- description: 'Design the orthotic insole for this order.',
57
+ description: 'Design the widget for this order.',
58
58
  escalation_payload: JSON.stringify({ workbenchId, companyId }),
59
59
  });
60
60
  if (design) {
@@ -0,0 +1,56 @@
1
+ # Dictionary Display (`x-lt-display`)
2
+
3
+ Read-only facts — order numbers, quantities, product names — render as a dense definition list when dictionary display is requested: labels beside values in a compact grid, a run of consecutive facts merged into one list. This is the display for reference data the resolver reads but never edits.
4
+
5
+ `x-lt-display: "dictionary"` applies at three levels; the nearest wins:
6
+
7
+ | Level | Placement | Applies to |
8
+ |-------|-----------|-----------|
9
+ | Field | on the property | that field |
10
+ | Section | `x-lt-section-options` at the schema root | read-only fields in that section |
11
+ | Schema | at the schema root | every read-only field |
12
+
13
+ Only read-only fields (`readOnly: true`) render as dictionary rows. Editable fields keep their inputs, and content widgets (`markdown`, `attachment`, `image`) keep their block rendering regardless of display settings.
14
+
15
+ ```json
16
+ {
17
+ "x-lt-layout": "two-column",
18
+ "x-lt-display": "dictionary",
19
+ "properties": {
20
+ "po": { "type": "string", "title": "PO", "readOnly": true, "x-lt-section": "The Order" },
21
+ "order_id": { "type": "string", "title": "Order ID", "readOnly": true, "x-lt-section": "The Order" },
22
+ "left_quantity": { "type": "number", "title": "Left Qty", "readOnly": true, "x-lt-section": "The Order" },
23
+ "right_quantity": { "type": "number", "title": "Right Qty","readOnly": true, "x-lt-section": "The Order" },
24
+ "approved": { "type": "boolean", "x-lt-section": "The Decision" }
25
+ }
26
+ }
27
+ ```
28
+
29
+ The four order facts render as one two-column dictionary under "The Order"; `approved` stays a checkbox.
30
+
31
+ ## Section Options (`x-lt-section-options`)
32
+
33
+ Per-section display settings live at the schema root, keyed by section name:
34
+
35
+ ```json
36
+ {
37
+ "x-lt-section-options": {
38
+ "The Order": { "display": "dictionary", "columns": 2 }
39
+ }
40
+ }
41
+ ```
42
+
43
+ | Option | Values | Meaning |
44
+ |--------|--------|---------|
45
+ | `display` | `"dictionary"` | Read-only fields in the section render as dictionary rows |
46
+ | `columns` | `1` \| `2` | Dictionary column count; defaults to 2 in two-column layouts, 1 otherwise |
47
+
48
+ Two-column dictionaries fill **row by row**: consecutive items share a row, so ordering controls pairing — declare `left_quantity` and `right_quantity` together and they render side by side, Left first.
49
+
50
+ ## Value formatting
51
+
52
+ Strings and numbers render as-is. Booleans render as Yes / No. Objects and arrays render as compact JSON. Empty values (`null`, `""`) render as an em dash.
53
+
54
+ Labels resolve the same way as form labels: the `title` keyword when present, otherwise the field key with snake/kebab separators title-cased. Declare `title` on every fact — `"PO"`, `"Order ID"` — for labels that read like product copy rather than column names.
55
+
56
+ Presentation tokens (`x-lt-display`, `x-lt-section-options`, `x-lt-column-group`) never affect validation or the submitted payload; a schema renders identically strict with or without them.
@@ -58,6 +58,25 @@ Sections are ordered by the first field that carries the section name (respects
58
58
 
59
59
  ---
60
60
 
61
+ ## Nested Column Groups (`x-lt-column-group`)
62
+
63
+ In a two-column layout, consecutive fields sharing an `x-lt-column-group` name render inside a single grid cell as their own two-column pair — a 2×2 arrangement. One nesting level is supported; the token is ignored when the schema layout is not `two-column`.
64
+
65
+ ```json
66
+ {
67
+ "x-lt-layout": "two-column",
68
+ "properties": {
69
+ "left_quantity": { "type": "number", "title": "Left Qty", "x-lt-column-group": "quantities" },
70
+ "right_quantity": { "type": "number", "title": "Right Qty", "x-lt-column-group": "quantities" },
71
+ "notes": { "type": "string", "format": "textarea" }
72
+ }
73
+ }
74
+ ```
75
+
76
+ The two quantities share one cell of the outer grid (each taking half of it) while `notes` occupies a normal cell beside them.
77
+
78
+ ---
79
+
61
80
  ## Payload Binding (`x-lt-bind`)
62
81
 
63
82
  The form is flat; the payload the workflow consumes rarely is. A field may declare `x-lt-bind` — the path its value occupies in the resolver payload (dot keys, optional `[n]` indices). The dashboard maps the flat form through the binds on submit, and reverse-maps `envelope.formDefaults` through them to prefill. A field with no bind lands at its own name at the payload root.
@@ -89,7 +108,7 @@ Only the form is versioned on the role — the payload shape is the workflow's o
89
108
 
90
109
  ## Help Panel (`x-lt-help`)
91
110
 
92
- Schema-level `x-lt-help` carries the form's guidance — checklists, tier tables, callouts, links — as markdown. The dashboard renders it in the side panel beside the form, so the form itself stays a clean title-and-fields surface while the SOP sits one glance to the right. The help text versions with the form: it lives in the same `form_schema` snapshot in `lt_role_schemas`.
111
+ Schema-level `x-lt-help` carries the form's guidance — checklists, tier tables, callouts, links — as markdown. The dashboard renders it in the side panel beside the form, so the form itself stays a clean title-and-fields surface while the SOP sits one glance to the right. When a schema carries `x-lt-help` (or `x-lt-context`), a help icon renders beside the form title; it opens the Instructions view of the side panel. The help text versions with the form: it lives in the same `form_schema` snapshot in `lt_role_schemas`.
93
112
 
94
113
  ```json
95
114
  {
@@ -98,6 +98,13 @@ Use when the rows describe **entities** (machines, stations) rather than a queue
98
98
  }
99
99
  ```
100
100
 
101
- The card's `state` renders as a status chip (a stable hue per token — commonly a subtype or a metadata state facet). The grid wraps to the viewport (wall-screen friendly). Clicking a card opens the entity's history: the table view filtered to that facet value (`x-lt-group-by` should therefore be a `metadata.*` path). `x-lt-help` renders above the board as in `facet-table`. In the digital-twin pattern — each machine advertising one live pending row — the board is exact by construction; for wider scopes it groups the fetched page, with standard pagination beyond it.
101
+ The card's `state` renders as a status chip (a stable hue per token — commonly a subtype or a metadata state facet). The grid wraps to the viewport (wall-screen friendly). `x-lt-help` renders above the board as in `facet-table`. In the digital-twin pattern — each machine advertising one live pending row — the board is exact by construction; for wider scopes it groups the fetched page, with standard pagination beyond it.
102
+
103
+ Cards are fully interactive (`x-lt-group-by` should be a `metadata.*` path so the facet affordances apply):
104
+
105
+ - **Click** opens the group's latest row in the detail view.
106
+ - **⇧ click** adds the entity's facet to the live filter set — additive, so shift-clicking narrows the board in place and the facet chips row shows each condition with its own remove.
107
+ - **Hovering the card footer** reveals the entity's history as the filtered **table** or **timeline** view (the same view modes the queue offers everywhere else).
108
+ - **Fields bound to a pure `{{metadata.KEY}}` token** carry the same hover pair the table's metadata cells offer: **filter** (narrows within the current role; ⇧ click adds to the current filters) and **search** (the facet across all roles).
102
109
 
103
110
  The reference: `examples/seed-fleet-sim.ts` — one advert per machine, a `format: "age"` "Since" field, and role default pins (see [pinned-views.md](pinned-views.md)).
@@ -144,6 +144,29 @@ The field type must be `"object"`. The submitted value is `Record<string, boolea
144
144
  - `"envelope"` — for item definitions that are render data only (no query cost). The workflow puts them in `conditionLT`'s `envelope` parameter.
145
145
  - `"metadata"` — only when items need to be GIN-indexed and searchable as facets. Adds index cost.
146
146
 
147
+ ### Geometry (`x-lt-variant`)
148
+
149
+ Item length and selection mode decide the checklist's shape:
150
+
151
+ | Variant | Shape | The shape for |
152
+ |---------|-------|---------------|
153
+ | `"rows"` | One item per line | Rituals — sentence-length confirmations, read top-down and ticked deliberately |
154
+ | `"chips"` | A wrapping cloud of selectable pills | Tags — short pick-any items (reject reasons, categories); selection shows in solid accent |
155
+
156
+ Set `"x-lt-variant"` explicitly, or let the renderer decide: `x-lt-require-all` always renders rows; otherwise chips when every label is 28 characters or fewer. The submitted value is identical in both shapes.
157
+
158
+ ```json
159
+ {
160
+ "rejectReasons": {
161
+ "type": "object",
162
+ "description": "Check every reason that applies",
163
+ "x-lt-widget": "checklist",
164
+ "x-lt-source": "envelope.reject_reason_items",
165
+ "x-lt-variant": "chips"
166
+ }
167
+ }
168
+ ```
169
+
147
170
  A checklist enforces one of three completion levels:
148
171
 
149
172
  | Level | Declaration | Blocks submission until |