lcp 0.1.0

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 (1676) hide show
  1. checksums.yaml +7 -0
  2. data/.claude/skills/lcp-custom-field/SKILL.md +205 -0
  3. data/.claude/skills/lcp-getting-started/SKILL.md +332 -0
  4. data/.claude/skills/lcp-host-binding/SKILL.md +287 -0
  5. data/.claude/skills/lcp-model/SKILL.md +185 -0
  6. data/.claude/skills/lcp-permissions/SKILL.md +176 -0
  7. data/.claude/skills/lcp-presenter/SKILL.md +194 -0
  8. data/.claude/skills/lcp-workflow/SKILL.md +281 -0
  9. data/CHANGELOG.md +69 -0
  10. data/MIT-LICENSE +20 -0
  11. data/README.md +28 -0
  12. data/Rakefile +17 -0
  13. data/app/assets/javascripts/lcp_ruby/application.js +58 -0
  14. data/app/assets/javascripts/lcp_ruby/controllers/advanced_filter_controller.js +1521 -0
  15. data/app/assets/javascripts/lcp_ruby/controllers/approval_actions_controller.js +24 -0
  16. data/app/assets/javascripts/lcp_ruby/controllers/array_input_controller.js +156 -0
  17. data/app/assets/javascripts/lcp_ruby/controllers/batch_select_controller.js +405 -0
  18. data/app/assets/javascripts/lcp_ruby/controllers/cascading_selects_controller.js +436 -0
  19. data/app/assets/javascripts/lcp_ruby/controllers/char_counter_controller.js +28 -0
  20. data/app/assets/javascripts/lcp_ruby/controllers/clipboard_controller.js +62 -0
  21. data/app/assets/javascripts/lcp_ruby/controllers/conditional_rendering_controller.js +178 -0
  22. data/app/assets/javascripts/lcp_ruby/controllers/confirm_dialog_controller.js +131 -0
  23. data/app/assets/javascripts/lcp_ruby/controllers/custom_fields_manage_controller.js +80 -0
  24. data/app/assets/javascripts/lcp_ruby/controllers/dialog_controller.js +421 -0
  25. data/app/assets/javascripts/lcp_ruby/controllers/dialog_nested_controller.js +182 -0
  26. data/app/assets/javascripts/lcp_ruby/controllers/direct_upload_controller.js +100 -0
  27. data/app/assets/javascripts/lcp_ruby/controllers/drawer_controller.js +205 -0
  28. data/app/assets/javascripts/lcp_ruby/controllers/dropdown_controller.js +160 -0
  29. data/app/assets/javascripts/lcp_ruby/controllers/export_dialog_controller.js +15 -0
  30. data/app/assets/javascripts/lcp_ruby/controllers/field_picker_controller.js +290 -0
  31. data/app/assets/javascripts/lcp_ruby/controllers/file_upload_controller.js +135 -0
  32. data/app/assets/javascripts/lcp_ruby/controllers/filter_auto_submit_controller.js +46 -0
  33. data/app/assets/javascripts/lcp_ruby/controllers/filter_dirty_controller.js +94 -0
  34. data/app/assets/javascripts/lcp_ruby/controllers/form_actions_controller.js +68 -0
  35. data/app/assets/javascripts/lcp_ruby/controllers/form_handling_controller.js +67 -0
  36. data/app/assets/javascripts/lcp_ruby/controllers/import_dialog_controller.js +142 -0
  37. data/app/assets/javascripts/lcp_ruby/controllers/import_mapper_controller.js +322 -0
  38. data/app/assets/javascripts/lcp_ruby/controllers/index_sortable_controller.js +388 -0
  39. data/app/assets/javascripts/lcp_ruby/controllers/inline_create_controller.js +137 -0
  40. data/app/assets/javascripts/lcp_ruby/controllers/kanban_board_controller.js +290 -0
  41. data/app/assets/javascripts/lcp_ruby/controllers/menu_controller.js +19 -0
  42. data/app/assets/javascripts/lcp_ruby/controllers/nested_forms_controller.js +261 -0
  43. data/app/assets/javascripts/lcp_ruby/controllers/responsive_sidebar_controller.js +161 -0
  44. data/app/assets/javascripts/lcp_ruby/controllers/responsive_top_nav_controller.js +394 -0
  45. data/app/assets/javascripts/lcp_ruby/controllers/saved_filters_controller.js +129 -0
  46. data/app/assets/javascripts/lcp_ruby/controllers/search_controller.js +82 -0
  47. data/app/assets/javascripts/lcp_ruby/controllers/selection_controller.js +33 -0
  48. data/app/assets/javascripts/lcp_ruby/controllers/sidebar_controller.js +83 -0
  49. data/app/assets/javascripts/lcp_ruby/controllers/sidebar_toggle_controller.js +66 -0
  50. data/app/assets/javascripts/lcp_ruby/controllers/slider_controller.js +26 -0
  51. data/app/assets/javascripts/lcp_ruby/controllers/submenu_controller.js +55 -0
  52. data/app/assets/javascripts/lcp_ruby/controllers/tom_select_controller.js +168 -0
  53. data/app/assets/javascripts/lcp_ruby/controllers/tree_index_controller.js +106 -0
  54. data/app/assets/javascripts/lcp_ruby/controllers/tree_reparent_controller.js +182 -0
  55. data/app/assets/javascripts/lcp_ruby/controllers/tree_select_controller.js +119 -0
  56. data/app/assets/javascripts/lcp_ruby/controllers/ui_components_controller.js +135 -0
  57. data/app/assets/javascripts/lcp_ruby/controllers/zone_tabs_controller.js +66 -0
  58. data/app/assets/javascripts/lcp_ruby/dev_toolbar.js +494 -0
  59. data/app/assets/javascripts/lcp_ruby/i18n.js.erb +29 -0
  60. data/app/assets/javascripts/lcp_ruby/lucide_init.js +24 -0
  61. data/app/assets/javascripts/lcp_ruby/stimulus_bootstrap.js +7 -0
  62. data/app/assets/javascripts/lcp_ruby/utils.js +119 -0
  63. data/app/assets/javascripts/lcp_ruby/xhr_fetch.js +411 -0
  64. data/app/assets/stylesheets/lcp_ruby/application.css +1940 -0
  65. data/app/assets/stylesheets/lcp_ruby/dev_toolbar.css +355 -0
  66. data/app/controllers/concerns/lcp_ruby/dialog_rendering.rb +167 -0
  67. data/app/controllers/concerns/lcp_ruby/form_action_execution.rb +249 -0
  68. data/app/controllers/concerns/lcp_ruby/page_authorization.rb +105 -0
  69. data/app/controllers/concerns/lcp_ruby/zone_resolution.rb +199 -0
  70. data/app/controllers/lcp_ruby/actions_controller.rb +481 -0
  71. data/app/controllers/lcp_ruby/application_controller.rb +59 -0
  72. data/app/controllers/lcp_ruby/approval_tasks_controller.rb +98 -0
  73. data/app/controllers/lcp_ruby/auth/base_controller.rb +58 -0
  74. data/app/controllers/lcp_ruby/auth/callbacks_controller.rb +103 -0
  75. data/app/controllers/lcp_ruby/auth/passwords_controller.rb +15 -0
  76. data/app/controllers/lcp_ruby/auth/registrations_controller.rb +37 -0
  77. data/app/controllers/lcp_ruby/auth/sessions_controller.rb +112 -0
  78. data/app/controllers/lcp_ruby/custom_fields_controller.rb +370 -0
  79. data/app/controllers/lcp_ruby/dev_toolbar_controller.rb +197 -0
  80. data/app/controllers/lcp_ruby/dialogs_controller.rb +199 -0
  81. data/app/controllers/lcp_ruby/health_controller.rb +23 -0
  82. data/app/controllers/lcp_ruby/impersonation_controller.rb +32 -0
  83. data/app/controllers/lcp_ruby/landing_controller.rb +49 -0
  84. data/app/controllers/lcp_ruby/metrics_controller.rb +17 -0
  85. data/app/controllers/lcp_ruby/resources_controller.rb +2218 -0
  86. data/app/controllers/lcp_ruby/saved_filters_controller.rb +221 -0
  87. data/app/helpers/lcp_ruby/condition_helper.rb +87 -0
  88. data/app/helpers/lcp_ruby/dashboard_helper.rb +45 -0
  89. data/app/helpers/lcp_ruby/dev_toolbar_helper.rb +17 -0
  90. data/app/helpers/lcp_ruby/dialog_helper.rb +27 -0
  91. data/app/helpers/lcp_ruby/display/card_helper.rb +317 -0
  92. data/app/helpers/lcp_ruby/display_helper.rb +63 -0
  93. data/app/helpers/lcp_ruby/display_template_helper.rb +100 -0
  94. data/app/helpers/lcp_ruby/form_helper.rb +1020 -0
  95. data/app/helpers/lcp_ruby/grouped_query_helper.rb +107 -0
  96. data/app/helpers/lcp_ruby/i18n_payload_helper.rb +197 -0
  97. data/app/helpers/lcp_ruby/layout_helper.rb +832 -0
  98. data/app/helpers/lcp_ruby/link_through_helper.rb +29 -0
  99. data/app/helpers/lcp_ruby/oidc_button_helper.rb +59 -0
  100. data/app/helpers/lcp_ruby/search_helper.rb +31 -0
  101. data/app/helpers/lcp_ruby/tree_helper.rb +140 -0
  102. data/app/helpers/lcp_ruby/view_slot_helper.rb +33 -0
  103. data/app/models/lcp_ruby/user.rb +61 -0
  104. data/app/views/layouts/lcp_ruby/application.html.erb +168 -0
  105. data/app/views/layouts/lcp_ruby/auth.html.erb +170 -0
  106. data/app/views/lcp_ruby/auth/callbacks/failure.html.erb +18 -0
  107. data/app/views/lcp_ruby/auth/mailer/reset_password_instructions.html.erb +9 -0
  108. data/app/views/lcp_ruby/auth/passwords/edit.html.erb +34 -0
  109. data/app/views/lcp_ruby/auth/passwords/new.html.erb +24 -0
  110. data/app/views/lcp_ruby/auth/registrations/edit.html.erb +48 -0
  111. data/app/views/lcp_ruby/auth/registrations/new.html.erb +46 -0
  112. data/app/views/lcp_ruby/auth/sessions/new.html.erb +55 -0
  113. data/app/views/lcp_ruby/auth/shared/_links.html.erb +13 -0
  114. data/app/views/lcp_ruby/custom_fields/_manage_row.html.erb +82 -0
  115. data/app/views/lcp_ruby/custom_fields/manage.html.erb +47 -0
  116. data/app/views/lcp_ruby/dialogs/_dialog_composite_frame.html.erb +87 -0
  117. data/app/views/lcp_ruby/dialogs/_dialog_frame.html.erb +60 -0
  118. data/app/views/lcp_ruby/dialogs/_dialog_show_frame.html.erb +14 -0
  119. data/app/views/lcp_ruby/dialogs/_show_secret.html.erb +10 -0
  120. data/app/views/lcp_ruby/dialogs/_success.html.erb +1 -0
  121. data/app/views/lcp_ruby/errors/not_found.html.erb +5 -0
  122. data/app/views/lcp_ruby/menu_renderers/_user_menu.html.erb +51 -0
  123. data/app/views/lcp_ruby/menu_renderers/_user_menu_panel.html.erb +32 -0
  124. data/app/views/lcp_ruby/navigation/_auto_top.html.erb +16 -0
  125. data/app/views/lcp_ruby/navigation/_both_sidebar.html.erb +1 -0
  126. data/app/views/lcp_ruby/navigation/_both_top.html.erb +1 -0
  127. data/app/views/lcp_ruby/navigation/_impersonation_banner.html.erb +14 -0
  128. data/app/views/lcp_ruby/navigation/_item_content.html.erb +23 -0
  129. data/app/views/lcp_ruby/navigation/_link_or_button.html.erb +51 -0
  130. data/app/views/lcp_ruby/navigation/_mobile_header.html.erb +23 -0
  131. data/app/views/lcp_ruby/navigation/_panel_item.html.erb +32 -0
  132. data/app/views/lcp_ruby/navigation/_sidebar.html.erb +38 -0
  133. data/app/views/lcp_ruby/navigation/_sidebar_item.html.erb +38 -0
  134. data/app/views/lcp_ruby/navigation/_sidebar_toggle.html.erb +32 -0
  135. data/app/views/lcp_ruby/navigation/_top.html.erb +26 -0
  136. data/app/views/lcp_ruby/navigation/_top_item.html.erb +112 -0
  137. data/app/views/lcp_ruby/navigation/_widget_item.html.erb +14 -0
  138. data/app/views/lcp_ruby/resources/_action_button.html.erb +138 -0
  139. data/app/views/lcp_ruby/resources/_advanced_filter.html.erb +55 -0
  140. data/app/views/lcp_ruby/resources/_api_status_banner.html.erb +11 -0
  141. data/app/views/lcp_ruby/resources/_association_list.html.erb +132 -0
  142. data/app/views/lcp_ruby/resources/_audit_history.html.erb +66 -0
  143. data/app/views/lcp_ruby/resources/_batch_toolbar.html.erb +54 -0
  144. data/app/views/lcp_ruby/resources/_filter_form.html.erb +57 -0
  145. data/app/views/lcp_ruby/resources/_form.html.erb +73 -0
  146. data/app/views/lcp_ruby/resources/_form_action_button.html.erb +20 -0
  147. data/app/views/lcp_ruby/resources/_form_action_dropdown_item.html.erb +17 -0
  148. data/app/views/lcp_ruby/resources/_form_actions.html.erb +38 -0
  149. data/app/views/lcp_ruby/resources/_form_section.html.erb +235 -0
  150. data/app/views/lcp_ruby/resources/_grid_page.html.erb +17 -0
  151. data/app/views/lcp_ruby/resources/_grouped_index.html.erb +61 -0
  152. data/app/views/lcp_ruby/resources/_inline_create_form.html.erb +50 -0
  153. data/app/views/lcp_ruby/resources/_json_items_list.html.erb +77 -0
  154. data/app/views/lcp_ruby/resources/_json_nested_fields.html.erb +30 -0
  155. data/app/views/lcp_ruby/resources/_kanban_card.html.erb +31 -0
  156. data/app/views/lcp_ruby/resources/_kanban_card_body.html.erb +17 -0
  157. data/app/views/lcp_ruby/resources/_kanban_column.html.erb +33 -0
  158. data/app/views/lcp_ruby/resources/_kanban_column_header.html.erb +10 -0
  159. data/app/views/lcp_ruby/resources/_kanban_index.html.erb +17 -0
  160. data/app/views/lcp_ruby/resources/_nested_field_cell.html.erb +33 -0
  161. data/app/views/lcp_ruby/resources/_nested_fields.html.erb +26 -0
  162. data/app/views/lcp_ruby/resources/_nested_row_content.html.erb +65 -0
  163. data/app/views/lcp_ruby/resources/_scope_filters.html.erb +71 -0
  164. data/app/views/lcp_ruby/resources/_semantic_index_page.html.erb +162 -0
  165. data/app/views/lcp_ruby/resources/_semantic_page.html.erb +121 -0
  166. data/app/views/lcp_ruby/resources/_show_sections.html.erb +128 -0
  167. data/app/views/lcp_ruby/resources/_table_index.html.erb +169 -0
  168. data/app/views/lcp_ruby/resources/_tile_card.html.erb +26 -0
  169. data/app/views/lcp_ruby/resources/_tile_card_body.html.erb +19 -0
  170. data/app/views/lcp_ruby/resources/_tiles_index.html.erb +15 -0
  171. data/app/views/lcp_ruby/resources/_transition_button.html.erb +33 -0
  172. data/app/views/lcp_ruby/resources/_tree_index.html.erb +61 -0
  173. data/app/views/lcp_ruby/resources/_view_switcher.html.erb +24 -0
  174. data/app/views/lcp_ruby/resources/edit.html.erb +9 -0
  175. data/app/views/lcp_ruby/resources/index.html.erb +80 -0
  176. data/app/views/lcp_ruby/resources/new.html.erb +9 -0
  177. data/app/views/lcp_ruby/resources/show.html.erb +37 -0
  178. data/app/views/lcp_ruby/shared/_breadcrumbs.html.erb +15 -0
  179. data/app/views/lcp_ruby/shared/_custom_partial_error.html.erb +3 -0
  180. data/app/views/lcp_ruby/shared/_flash_messages.html.erb +19 -0
  181. data/app/views/lcp_ruby/slots/index/_advanced_filter.html.erb +3 -0
  182. data/app/views/lcp_ruby/slots/index/_collection_actions.html.erb +50 -0
  183. data/app/views/lcp_ruby/slots/index/_manage_all.html.erb +4 -0
  184. data/app/views/lcp_ruby/slots/index/_pagination_footer.html.erb +71 -0
  185. data/app/views/lcp_ruby/slots/index/_predefined_filters.html.erb +8 -0
  186. data/app/views/lcp_ruby/slots/index/_saved_filters.html.erb +87 -0
  187. data/app/views/lcp_ruby/slots/index/_search.html.erb +52 -0
  188. data/app/views/lcp_ruby/slots/index/_search_parameter.html.erb +72 -0
  189. data/app/views/lcp_ruby/slots/index/_sort_dropdown.html.erb +21 -0
  190. data/app/views/lcp_ruby/slots/index/_summary_bar.html.erb +22 -0
  191. data/app/views/lcp_ruby/slots/index/_view_switcher.html.erb +1 -0
  192. data/app/views/lcp_ruby/slots/show/_approval_status.html.erb +8 -0
  193. data/app/views/lcp_ruby/slots/show/_back_to_list.html.erb +1 -0
  194. data/app/views/lcp_ruby/slots/show/_copy_url.html.erb +5 -0
  195. data/app/views/lcp_ruby/slots/show/_single_actions.html.erb +4 -0
  196. data/app/views/lcp_ruby/slots/show/_view_switcher.html.erb +1 -0
  197. data/app/views/lcp_ruby/widgets/_approval_status.html.erb +150 -0
  198. data/app/views/lcp_ruby/widgets/_chart.html.erb +36 -0
  199. data/app/views/lcp_ruby/widgets/_embed.html.erb +13 -0
  200. data/app/views/lcp_ruby/widgets/_kpi_card.html.erb +29 -0
  201. data/app/views/lcp_ruby/widgets/_list.html.erb +17 -0
  202. data/app/views/lcp_ruby/widgets/_presenter_zone.html.erb +84 -0
  203. data/app/views/lcp_ruby/widgets/_record_show_zone.html.erb +11 -0
  204. data/app/views/lcp_ruby/widgets/_text.html.erb +3 -0
  205. data/app/views/lcp_ruby/widgets/_workflow_graph.html.erb +32 -0
  206. data/app/views/lcp_ruby/zones/_custom_zone.html.erb +16 -0
  207. data/app/views/lcp_ruby/zones/_error.html.erb +5 -0
  208. data/app/views/lcp_ruby/zones/_zone_frame.html.erb +25 -0
  209. data/app/views/lcp_ruby/zones/_zone_search.html.erb +20 -0
  210. data/config/locales/cs.yml +859 -0
  211. data/config/locales/en.yml +731 -0
  212. data/config/routes.rb +119 -0
  213. data/docs/README.md +225 -0
  214. data/docs/architecture.md +212 -0
  215. data/docs/feature-catalog.md +763 -0
  216. data/docs/feature-catalog.yml +20911 -0
  217. data/docs/getting-started.md +1187 -0
  218. data/docs/guides/action-buttons.md +537 -0
  219. data/docs/guides/adding-locale.md +353 -0
  220. data/docs/guides/api-backed-models.md +478 -0
  221. data/docs/guides/attachments.md +399 -0
  222. data/docs/guides/auditing.md +333 -0
  223. data/docs/guides/batch-actions.md +342 -0
  224. data/docs/guides/composite-pages.md +1290 -0
  225. data/docs/guides/computed-fields.md +350 -0
  226. data/docs/guides/conditional-rendering.md +832 -0
  227. data/docs/guides/custom-actions.md +238 -0
  228. data/docs/guides/custom-fields.md +439 -0
  229. data/docs/guides/custom-renderers.md +234 -0
  230. data/docs/guides/custom-types.md +274 -0
  231. data/docs/guides/dashboards.md +504 -0
  232. data/docs/guides/debugging/README.md +38 -0
  233. data/docs/guides/debugging/controllers.md +147 -0
  234. data/docs/guides/debugging/data.md +165 -0
  235. data/docs/guides/debugging/metadata.md +143 -0
  236. data/docs/guides/debugging/models.md +126 -0
  237. data/docs/guides/debugging/permissions.md +147 -0
  238. data/docs/guides/debugging/presenters.md +151 -0
  239. data/docs/guides/developer-tools.md +418 -0
  240. data/docs/guides/dialogs.md +392 -0
  241. data/docs/guides/display-types.md +1430 -0
  242. data/docs/guides/eager-loading.md +230 -0
  243. data/docs/guides/event-handlers.md +135 -0
  244. data/docs/guides/export.md +305 -0
  245. data/docs/guides/extensibility.md +761 -0
  246. data/docs/guides/groups.md +220 -0
  247. data/docs/guides/hierarchical-authorization.md +427 -0
  248. data/docs/guides/host-application.md +556 -0
  249. data/docs/guides/host-controller-integration.md +473 -0
  250. data/docs/guides/impersonation.md +83 -0
  251. data/docs/guides/import.md +165 -0
  252. data/docs/guides/inherited-permissions.md +459 -0
  253. data/docs/guides/menu.md +373 -0
  254. data/docs/guides/monitoring.md +254 -0
  255. data/docs/guides/oidc-setup.md +399 -0
  256. data/docs/guides/permission-source.md +205 -0
  257. data/docs/guides/permissions.md +364 -0
  258. data/docs/guides/presenters.md +2324 -0
  259. data/docs/guides/record-aliases.md +303 -0
  260. data/docs/guides/rendering-extension-points.md +280 -0
  261. data/docs/guides/role-source.md +288 -0
  262. data/docs/guides/selectbox.md +516 -0
  263. data/docs/guides/sequences.md +291 -0
  264. data/docs/guides/soft-delete.md +460 -0
  265. data/docs/guides/theming.md +129 -0
  266. data/docs/guides/tiles.md +383 -0
  267. data/docs/guides/tree-structures.md +297 -0
  268. data/docs/guides/userstamps.md +288 -0
  269. data/docs/guides/view-groups.md +259 -0
  270. data/docs/guides/view-slots.md +352 -0
  271. data/docs/guides/virtual-columns.md +810 -0
  272. data/docs/guides/workflow.md +692 -0
  273. data/docs/reference/api-backed-models.md +404 -0
  274. data/docs/reference/api-tokens.md +128 -0
  275. data/docs/reference/auditing.md +277 -0
  276. data/docs/reference/boot_lifecycle.md +188 -0
  277. data/docs/reference/cascading_selects.md +189 -0
  278. data/docs/reference/condition-operators.md +445 -0
  279. data/docs/reference/custom-fields.md +483 -0
  280. data/docs/reference/dialogs.md +286 -0
  281. data/docs/reference/doctor.md +168 -0
  282. data/docs/reference/dynamic-references.md +95 -0
  283. data/docs/reference/eager-loading.md +192 -0
  284. data/docs/reference/engine-configuration.md +989 -0
  285. data/docs/reference/export.md +309 -0
  286. data/docs/reference/forms.md +68 -0
  287. data/docs/reference/groups.md +176 -0
  288. data/docs/reference/host-controller-integration.md +342 -0
  289. data/docs/reference/i18n.md +497 -0
  290. data/docs/reference/i18n_check.md +351 -0
  291. data/docs/reference/import.md +260 -0
  292. data/docs/reference/invariant_check.md +216 -0
  293. data/docs/reference/menu.md +985 -0
  294. data/docs/reference/model-dsl.md +1157 -0
  295. data/docs/reference/models.md +2972 -0
  296. data/docs/reference/monitoring.md +222 -0
  297. data/docs/reference/oidc-bearer.md +269 -0
  298. data/docs/reference/oidc.md +407 -0
  299. data/docs/reference/page_filters.md +328 -0
  300. data/docs/reference/pages.md +1375 -0
  301. data/docs/reference/permission-source.md +185 -0
  302. data/docs/reference/permissions.md +715 -0
  303. data/docs/reference/presenter-dsl.md +1719 -0
  304. data/docs/reference/presenters.md +3627 -0
  305. data/docs/reference/role-source.md +227 -0
  306. data/docs/reference/theme-variables.md +139 -0
  307. data/docs/reference/tree-structures.md +374 -0
  308. data/docs/reference/types.md +470 -0
  309. data/docs/reference/view-groups.md +347 -0
  310. data/docs/reference/view-slots.md +228 -0
  311. data/docs/reference/virtual_forms.md +196 -0
  312. data/docs/reference/workflow-approvals.md +387 -0
  313. data/docs/reference/workflow.md +651 -0
  314. data/examples/crm/Gemfile +9 -0
  315. data/examples/crm/Gemfile.lock +417 -0
  316. data/examples/crm/Rakefile +2 -0
  317. data/examples/crm/app/actions/activity/complete.rb +20 -0
  318. data/examples/crm/app/actions/deal/close_won.rb +20 -0
  319. data/examples/crm/app/assets/config/manifest.js +3 -0
  320. data/examples/crm/app/controllers/application_controller.rb +9 -0
  321. data/examples/crm/app/event_handlers/deal/on_stage_change.rb +17 -0
  322. data/examples/crm/app/lcp_services/computed/weighted_deal_value.rb +13 -0
  323. data/examples/crm/app/lcp_services/data_providers/active_contacts_count.rb +14 -0
  324. data/examples/crm/app/lcp_services/data_providers/open_deals_count.rb +14 -0
  325. data/examples/crm/app/lcp_services/data_providers/pending_activities_count.rb +15 -0
  326. data/examples/crm/app/lcp_services/data_providers/pipeline_value.rb +27 -0
  327. data/examples/crm/app/lcp_services/data_providers/won_deals_count.rb +14 -0
  328. data/examples/crm/app/lcp_services/defaults/thirty_days_out.rb +11 -0
  329. data/examples/crm/app/lcp_services/transforms/titlecase.rb +11 -0
  330. data/examples/crm/app/lcp_services/validators/deal_credit_limit.rb +19 -0
  331. data/examples/crm/app/lcp_services/validators/deal_documents_required.rb +17 -0
  332. data/examples/crm/app/renderers/conditional_badge.rb +39 -0
  333. data/examples/crm/bin/rails +4 -0
  334. data/examples/crm/bin/rake +4 -0
  335. data/examples/crm/config/application.rb +31 -0
  336. data/examples/crm/config/boot.rb +2 -0
  337. data/examples/crm/config/database.yml +12 -0
  338. data/examples/crm/config/environment.rb +2 -0
  339. data/examples/crm/config/initializers/lcp_ruby.rb +45 -0
  340. data/examples/crm/config/lcp_ruby/menu.yml +53 -0
  341. data/examples/crm/config/lcp_ruby/models/activity.rb +43 -0
  342. data/examples/crm/config/lcp_ruby/models/city.rb +19 -0
  343. data/examples/crm/config/lcp_ruby/models/company.rb +65 -0
  344. data/examples/crm/config/lcp_ruby/models/contact.rb +62 -0
  345. data/examples/crm/config/lcp_ruby/models/country.rb +22 -0
  346. data/examples/crm/config/lcp_ruby/models/custom_field_definition.rb +60 -0
  347. data/examples/crm/config/lcp_ruby/models/deal.rb +85 -0
  348. data/examples/crm/config/lcp_ruby/models/deal_category.rb +15 -0
  349. data/examples/crm/config/lcp_ruby/models/gapfree_sequence.rb +17 -0
  350. data/examples/crm/config/lcp_ruby/models/region.rb +15 -0
  351. data/examples/crm/config/lcp_ruby/models/saved_filter.rb +50 -0
  352. data/examples/crm/config/lcp_ruby/pages/activity_quick_log.yml +19 -0
  353. data/examples/crm/config/lcp_ruby/pages/company_detail.yml +127 -0
  354. data/examples/crm/config/lcp_ruby/pages/deals_overview.yml +65 -0
  355. data/examples/crm/config/lcp_ruby/permissions/activity.yml +40 -0
  356. data/examples/crm/config/lcp_ruby/permissions/custom_field_definition.yml +16 -0
  357. data/examples/crm/config/lcp_ruby/permissions/deal.yml +54 -0
  358. data/examples/crm/config/lcp_ruby/permissions/default.yml +16 -0
  359. data/examples/crm/config/lcp_ruby/permissions/gapfree_sequence.yml +6 -0
  360. data/examples/crm/config/lcp_ruby/permissions/saved_filter.yml +27 -0
  361. data/examples/crm/config/lcp_ruby/presenters/activity.rb +140 -0
  362. data/examples/crm/config/lcp_ruby/presenters/activity_quick_form.rb +36 -0
  363. data/examples/crm/config/lcp_ruby/presenters/activity_short.rb +16 -0
  364. data/examples/crm/config/lcp_ruby/presenters/activity_tiles.rb +35 -0
  365. data/examples/crm/config/lcp_ruby/presenters/city.rb +53 -0
  366. data/examples/crm/config/lcp_ruby/presenters/company.rb +147 -0
  367. data/examples/crm/config/lcp_ruby/presenters/company_activities_zone.rb +26 -0
  368. data/examples/crm/config/lcp_ruby/presenters/company_archive.rb +37 -0
  369. data/examples/crm/config/lcp_ruby/presenters/company_contacts_zone.rb +22 -0
  370. data/examples/crm/config/lcp_ruby/presenters/company_deals_zone.rb +36 -0
  371. data/examples/crm/config/lcp_ruby/presenters/company_short.rb +21 -0
  372. data/examples/crm/config/lcp_ruby/presenters/company_show_zone.rb +38 -0
  373. data/examples/crm/config/lcp_ruby/presenters/company_sidebar.rb +15 -0
  374. data/examples/crm/config/lcp_ruby/presenters/company_tiles.rb +35 -0
  375. data/examples/crm/config/lcp_ruby/presenters/contact.rb +120 -0
  376. data/examples/crm/config/lcp_ruby/presenters/contact_quick_form.rb +17 -0
  377. data/examples/crm/config/lcp_ruby/presenters/contact_short.rb +20 -0
  378. data/examples/crm/config/lcp_ruby/presenters/contact_tiles.rb +34 -0
  379. data/examples/crm/config/lcp_ruby/presenters/country.rb +44 -0
  380. data/examples/crm/config/lcp_ruby/presenters/custom_fields.rb +124 -0
  381. data/examples/crm/config/lcp_ruby/presenters/deal.rb +181 -0
  382. data/examples/crm/config/lcp_ruby/presenters/deal_category.rb +46 -0
  383. data/examples/crm/config/lcp_ruby/presenters/deal_overview.rb +6 -0
  384. data/examples/crm/config/lcp_ruby/presenters/deal_pipeline.rb +18 -0
  385. data/examples/crm/config/lcp_ruby/presenters/deal_short.rb +25 -0
  386. data/examples/crm/config/lcp_ruby/presenters/deal_tiles.rb +48 -0
  387. data/examples/crm/config/lcp_ruby/presenters/region.rb +42 -0
  388. data/examples/crm/config/lcp_ruby/presenters/save_filter_dialog.rb +17 -0
  389. data/examples/crm/config/lcp_ruby/presenters/saved_filters.rb +93 -0
  390. data/examples/crm/config/lcp_ruby/views/activities.yml +16 -0
  391. data/examples/crm/config/lcp_ruby/views/cities.yml +10 -0
  392. data/examples/crm/config/lcp_ruby/views/companies.yml +14 -0
  393. data/examples/crm/config/lcp_ruby/views/contacts.yml +16 -0
  394. data/examples/crm/config/lcp_ruby/views/countries.yml +8 -0
  395. data/examples/crm/config/lcp_ruby/views/custom_fields.rb +9 -0
  396. data/examples/crm/config/lcp_ruby/views/deal_categories.yml +8 -0
  397. data/examples/crm/config/lcp_ruby/views/deals.yml +19 -0
  398. data/examples/crm/config/lcp_ruby/views/pipeline.yml +10 -0
  399. data/examples/crm/config/lcp_ruby/views/regions.yml +10 -0
  400. data/examples/crm/config/lcp_ruby/views/saved_filters.yml +7 -0
  401. data/examples/crm/config/locales/cs.yml +338 -0
  402. data/examples/crm/config/locales/en.yml +353 -0
  403. data/examples/crm/config/routes.rb +4 -0
  404. data/examples/crm/config/storage.yml +3 -0
  405. data/examples/crm/config.ru +2 -0
  406. data/examples/crm/db/migrate/20260219104942_create_active_storage_tables.active_storage.rb +57 -0
  407. data/examples/crm/db/schema.rb +245 -0
  408. data/examples/crm/db/seeds.rb +1111 -0
  409. data/examples/crm/erd.md +163 -0
  410. data/examples/hr/Gemfile +9 -0
  411. data/examples/hr/Gemfile.lock +419 -0
  412. data/examples/hr/Rakefile +6 -0
  413. data/examples/hr/app/actions/asset/assign_asset.rb +20 -0
  414. data/examples/hr/app/actions/asset/return_asset.rb +20 -0
  415. data/examples/hr/app/actions/candidate/advance.rb +32 -0
  416. data/examples/hr/app/actions/candidate/hire.rb +20 -0
  417. data/examples/hr/app/actions/candidate/reject_candidate.rb +20 -0
  418. data/examples/hr/app/actions/expense_claim/approve.rb +21 -0
  419. data/examples/hr/app/actions/expense_claim/reject.rb +21 -0
  420. data/examples/hr/app/actions/expense_claim/submit.rb +20 -0
  421. data/examples/hr/app/actions/interview/complete_interview.rb +20 -0
  422. data/examples/hr/app/actions/leave_request/approve.rb +21 -0
  423. data/examples/hr/app/actions/leave_request/cancel.rb +20 -0
  424. data/examples/hr/app/actions/leave_request/reject.rb +21 -0
  425. data/examples/hr/app/assets/config/manifest.js +1 -0
  426. data/examples/hr/app/assets/stylesheets/application.css +10 -0
  427. data/examples/hr/app/condition_services/is_own_org_unit.rb +14 -0
  428. data/examples/hr/app/condition_services/is_own_record.rb +20 -0
  429. data/examples/hr/app/controllers/application_controller.rb +15 -0
  430. data/examples/hr/app/event_handlers/asset_assignment/on_create.rb +24 -0
  431. data/examples/hr/app/event_handlers/candidate/on_status_change.rb +18 -0
  432. data/examples/hr/app/event_handlers/leave_request/on_status_change.rb +45 -0
  433. data/examples/hr/app/helpers/application_helper.rb +2 -0
  434. data/examples/hr/app/javascript/application.js +1 -0
  435. data/examples/hr/app/jobs/application_job.rb +7 -0
  436. data/examples/hr/app/lcp_services/computed/employee_tenure.rb +28 -0
  437. data/examples/hr/app/lcp_services/computed/leave_remaining.rb +13 -0
  438. data/examples/hr/app/lcp_services/data_providers/headcount_text.rb +14 -0
  439. data/examples/hr/app/lcp_services/data_providers/open_positions_count.rb +14 -0
  440. data/examples/hr/app/lcp_services/data_providers/pending_expenses_count.rb +14 -0
  441. data/examples/hr/app/lcp_services/data_providers/pending_leaves_count.rb +14 -0
  442. data/examples/hr/app/lcp_services/defaults/current_year.rb +11 -0
  443. data/examples/hr/app/lcp_services/transforms/titlecase.rb +11 -0
  444. data/examples/hr/app/lcp_services/validators/expense_receipt_required.rb +17 -0
  445. data/examples/hr/app/lcp_services/validators/leave_balance_check.rb +37 -0
  446. data/examples/hr/app/models/application_record.rb +3 -0
  447. data/examples/hr/app/views/layouts/application.html.erb +29 -0
  448. data/examples/hr/app/views/pwa/manifest.json.erb +22 -0
  449. data/examples/hr/app/views/pwa/service-worker.js +26 -0
  450. data/examples/hr/bin/brakeman +7 -0
  451. data/examples/hr/bin/bundler-audit +6 -0
  452. data/examples/hr/bin/ci +6 -0
  453. data/examples/hr/bin/dev +2 -0
  454. data/examples/hr/bin/docker-entrypoint +8 -0
  455. data/examples/hr/bin/importmap +4 -0
  456. data/examples/hr/bin/jobs +6 -0
  457. data/examples/hr/bin/kamal +27 -0
  458. data/examples/hr/bin/rails +4 -0
  459. data/examples/hr/bin/rake +4 -0
  460. data/examples/hr/bin/rubocop +8 -0
  461. data/examples/hr/bin/setup +35 -0
  462. data/examples/hr/bin/thrust +5 -0
  463. data/examples/hr/config/application.rb +31 -0
  464. data/examples/hr/config/boot.rb +2 -0
  465. data/examples/hr/config/bundler-audit.yml +5 -0
  466. data/examples/hr/config/cache.yml +16 -0
  467. data/examples/hr/config/ci.rb +20 -0
  468. data/examples/hr/config/credentials.yml.enc +1 -0
  469. data/examples/hr/config/database.yml +36 -0
  470. data/examples/hr/config/deploy.yml +119 -0
  471. data/examples/hr/config/environment.rb +5 -0
  472. data/examples/hr/config/environments/development.rb +66 -0
  473. data/examples/hr/config/environments/production.rb +74 -0
  474. data/examples/hr/config/environments/test.rb +45 -0
  475. data/examples/hr/config/importmap.rb +3 -0
  476. data/examples/hr/config/initializers/assets.rb +7 -0
  477. data/examples/hr/config/initializers/content_security_policy.rb +29 -0
  478. data/examples/hr/config/initializers/filter_parameter_logging.rb +8 -0
  479. data/examples/hr/config/initializers/inflections.rb +16 -0
  480. data/examples/hr/config/initializers/lcp_ruby.rb +18 -0
  481. data/examples/hr/config/lcp_ruby/menu.yml +86 -0
  482. data/examples/hr/config/lcp_ruby/models/announcement.rb +33 -0
  483. data/examples/hr/config/lcp_ruby/models/asset.rb +73 -0
  484. data/examples/hr/config/lcp_ruby/models/asset_assignment.rb +39 -0
  485. data/examples/hr/config/lcp_ruby/models/audit_log.yml +57 -0
  486. data/examples/hr/config/lcp_ruby/models/candidate.rb +68 -0
  487. data/examples/hr/config/lcp_ruby/models/custom_field_definition.rb +60 -0
  488. data/examples/hr/config/lcp_ruby/models/dashboard.rb +17 -0
  489. data/examples/hr/config/lcp_ruby/models/document.rb +38 -0
  490. data/examples/hr/config/lcp_ruby/models/employee.rb +128 -0
  491. data/examples/hr/config/lcp_ruby/models/employee_skill.rb +29 -0
  492. data/examples/hr/config/lcp_ruby/models/expense_claim.rb +70 -0
  493. data/examples/hr/config/lcp_ruby/models/goal.rb +48 -0
  494. data/examples/hr/config/lcp_ruby/models/group.rb +37 -0
  495. data/examples/hr/config/lcp_ruby/models/group_membership.rb +26 -0
  496. data/examples/hr/config/lcp_ruby/models/interview.rb +54 -0
  497. data/examples/hr/config/lcp_ruby/models/job_posting.rb +67 -0
  498. data/examples/hr/config/lcp_ruby/models/leave_balance.rb +31 -0
  499. data/examples/hr/config/lcp_ruby/models/leave_request.rb +52 -0
  500. data/examples/hr/config/lcp_ruby/models/leave_type.rb +35 -0
  501. data/examples/hr/config/lcp_ruby/models/organization_unit.rb +42 -0
  502. data/examples/hr/config/lcp_ruby/models/performance_review.rb +59 -0
  503. data/examples/hr/config/lcp_ruby/models/position.rb +43 -0
  504. data/examples/hr/config/lcp_ruby/models/skill.rb +27 -0
  505. data/examples/hr/config/lcp_ruby/models/training_course.rb +53 -0
  506. data/examples/hr/config/lcp_ruby/models/training_enrollment.rb +35 -0
  507. data/examples/hr/config/lcp_ruby/pages/dashboard.yml +101 -0
  508. data/examples/hr/config/lcp_ruby/permissions/announcement.yml +25 -0
  509. data/examples/hr/config/lcp_ruby/permissions/asset.yml +35 -0
  510. data/examples/hr/config/lcp_ruby/permissions/audit_log.yml +28 -0
  511. data/examples/hr/config/lcp_ruby/permissions/candidate.yml +25 -0
  512. data/examples/hr/config/lcp_ruby/permissions/custom_field_definition.yml +28 -0
  513. data/examples/hr/config/lcp_ruby/permissions/dashboard.yml +25 -0
  514. data/examples/hr/config/lcp_ruby/permissions/default.yml +25 -0
  515. data/examples/hr/config/lcp_ruby/permissions/document.yml +37 -0
  516. data/examples/hr/config/lcp_ruby/permissions/employee.yml +55 -0
  517. data/examples/hr/config/lcp_ruby/permissions/expense_claim.yml +45 -0
  518. data/examples/hr/config/lcp_ruby/permissions/group.yml +27 -0
  519. data/examples/hr/config/lcp_ruby/permissions/job_posting.yml +34 -0
  520. data/examples/hr/config/lcp_ruby/permissions/leave_request.yml +45 -0
  521. data/examples/hr/config/lcp_ruby/permissions/performance_review.yml +42 -0
  522. data/examples/hr/config/lcp_ruby/presenters/announcement.rb +47 -0
  523. data/examples/hr/config/lcp_ruby/presenters/asset.rb +69 -0
  524. data/examples/hr/config/lcp_ruby/presenters/asset_assignment.rb +47 -0
  525. data/examples/hr/config/lcp_ruby/presenters/audit_logs.yml +43 -0
  526. data/examples/hr/config/lcp_ruby/presenters/candidate.rb +71 -0
  527. data/examples/hr/config/lcp_ruby/presenters/custom_fields.rb +124 -0
  528. data/examples/hr/config/lcp_ruby/presenters/dashboard.rb +37 -0
  529. data/examples/hr/config/lcp_ruby/presenters/document.rb +46 -0
  530. data/examples/hr/config/lcp_ruby/presenters/employee.rb +167 -0
  531. data/examples/hr/config/lcp_ruby/presenters/employee_archive.rb +38 -0
  532. data/examples/hr/config/lcp_ruby/presenters/employee_directory.rb +27 -0
  533. data/examples/hr/config/lcp_ruby/presenters/employee_skill.rb +57 -0
  534. data/examples/hr/config/lcp_ruby/presenters/expense_claim.rb +76 -0
  535. data/examples/hr/config/lcp_ruby/presenters/goal.rb +60 -0
  536. data/examples/hr/config/lcp_ruby/presenters/group.rb +48 -0
  537. data/examples/hr/config/lcp_ruby/presenters/interview.rb +59 -0
  538. data/examples/hr/config/lcp_ruby/presenters/job_posting.rb +73 -0
  539. data/examples/hr/config/lcp_ruby/presenters/leave_balance.rb +50 -0
  540. data/examples/hr/config/lcp_ruby/presenters/leave_request.rb +89 -0
  541. data/examples/hr/config/lcp_ruby/presenters/leave_type.rb +52 -0
  542. data/examples/hr/config/lcp_ruby/presenters/organization_unit.rb +56 -0
  543. data/examples/hr/config/lcp_ruby/presenters/performance_review.rb +87 -0
  544. data/examples/hr/config/lcp_ruby/presenters/position.rb +54 -0
  545. data/examples/hr/config/lcp_ruby/presenters/skill.rb +45 -0
  546. data/examples/hr/config/lcp_ruby/presenters/training_course.rb +61 -0
  547. data/examples/hr/config/lcp_ruby/presenters/training_enrollment.rb +49 -0
  548. data/examples/hr/config/lcp_ruby/views/announcements.yml +8 -0
  549. data/examples/hr/config/lcp_ruby/views/asset_assignments.yml +10 -0
  550. data/examples/hr/config/lcp_ruby/views/assets.yml +8 -0
  551. data/examples/hr/config/lcp_ruby/views/audit_logs.yml +8 -0
  552. data/examples/hr/config/lcp_ruby/views/candidates.yml +8 -0
  553. data/examples/hr/config/lcp_ruby/views/custom_fields.yml +8 -0
  554. data/examples/hr/config/lcp_ruby/views/dashboard.yml +6 -0
  555. data/examples/hr/config/lcp_ruby/views/documents.yml +10 -0
  556. data/examples/hr/config/lcp_ruby/views/employee_skills.yml +10 -0
  557. data/examples/hr/config/lcp_ruby/views/employees.yml +14 -0
  558. data/examples/hr/config/lcp_ruby/views/expense_claims.yml +10 -0
  559. data/examples/hr/config/lcp_ruby/views/goals.yml +10 -0
  560. data/examples/hr/config/lcp_ruby/views/groups.yml +8 -0
  561. data/examples/hr/config/lcp_ruby/views/interviews.yml +8 -0
  562. data/examples/hr/config/lcp_ruby/views/job_postings.yml +6 -0
  563. data/examples/hr/config/lcp_ruby/views/leave_balances.yml +10 -0
  564. data/examples/hr/config/lcp_ruby/views/leave_requests.yml +10 -0
  565. data/examples/hr/config/lcp_ruby/views/leave_types.yml +8 -0
  566. data/examples/hr/config/lcp_ruby/views/organization_units.yml +8 -0
  567. data/examples/hr/config/lcp_ruby/views/performance_reviews.yml +10 -0
  568. data/examples/hr/config/lcp_ruby/views/positions.yml +8 -0
  569. data/examples/hr/config/lcp_ruby/views/skills.yml +8 -0
  570. data/examples/hr/config/lcp_ruby/views/training_courses.yml +8 -0
  571. data/examples/hr/config/lcp_ruby/views/training_enrollments.yml +10 -0
  572. data/examples/hr/config/locales/cs.yml +496 -0
  573. data/examples/hr/config/locales/en.yml +740 -0
  574. data/examples/hr/config/locales/sk.yml +496 -0
  575. data/examples/hr/config/puma.rb +42 -0
  576. data/examples/hr/config/queue.yml +18 -0
  577. data/examples/hr/config/recurring.yml +15 -0
  578. data/examples/hr/config/routes.rb +4 -0
  579. data/examples/hr/config/storage.yml +27 -0
  580. data/examples/hr/config.ru +6 -0
  581. data/examples/hr/db/cache_schema.rb +12 -0
  582. data/examples/hr/db/migrate/20260303202825_create_active_storage_tables.active_storage.rb +57 -0
  583. data/examples/hr/db/queue_schema.rb +129 -0
  584. data/examples/hr/db/schema.rb +588 -0
  585. data/examples/hr/db/seeds.rb +932 -0
  586. data/examples/hr/erd.md +396 -0
  587. data/examples/hr/public/400.html +135 -0
  588. data/examples/hr/public/404.html +135 -0
  589. data/examples/hr/public/406-unsupported-browser.html +135 -0
  590. data/examples/hr/public/422.html +135 -0
  591. data/examples/hr/public/500.html +135 -0
  592. data/examples/hr/public/icon.svg +3 -0
  593. data/examples/hr/public/robots.txt +1 -0
  594. data/examples/showcase/Gemfile +15 -0
  595. data/examples/showcase/Gemfile.lock +425 -0
  596. data/examples/showcase/Rakefile +2 -0
  597. data/examples/showcase/app/actions/showcase_batch_task/assign_batch.rb +20 -0
  598. data/examples/showcase/app/actions/showcase_batch_task/close_task.rb +21 -0
  599. data/examples/showcase/app/actions/showcase_condition/approve.rb +20 -0
  600. data/examples/showcase/app/actions/showcase_permission/lock.rb +20 -0
  601. data/examples/showcase/app/assets/config/manifest.js +3 -0
  602. data/examples/showcase/app/assets/stylesheets/application.css +27 -0
  603. data/examples/showcase/app/condition_services/budget_threshold.rb +16 -0
  604. data/examples/showcase/app/condition_services/overdue_check.rb +11 -0
  605. data/examples/showcase/app/controllers/application_controller.rb +2 -0
  606. data/examples/showcase/app/controllers/docs_controller.rb +48 -0
  607. data/examples/showcase/app/controllers/host_inventory_items_managed_controller.rb +90 -0
  608. data/examples/showcase/app/controllers/host_inventory_items_report_controller.rb +39 -0
  609. data/examples/showcase/app/controllers/host_inventory_items_wizard_controller.rb +50 -0
  610. data/examples/showcase/app/data_providers/showcase_amount_provider.rb +52 -0
  611. data/examples/showcase/app/data_providers/weather_station_provider.rb +140 -0
  612. data/examples/showcase/app/event_handlers/showcase_model/on_status_change.rb +17 -0
  613. data/examples/showcase/app/event_handlers/showcase_permission/on_status_change.rb +17 -0
  614. data/examples/showcase/app/event_handlers/showcase_workflow/log_review_exit.rb +16 -0
  615. data/examples/showcase/app/event_handlers/showcase_workflow/notify_reviewers.rb +15 -0
  616. data/examples/showcase/app/event_handlers/showcase_workflow/request_submitted.rb +15 -0
  617. data/examples/showcase/app/lcp_metrics/showcase_metrics.rb +31 -0
  618. data/examples/showcase/app/lcp_services/computed/showcase_score.rb +18 -0
  619. data/examples/showcase/app/lcp_services/computed/showcase_total.rb +13 -0
  620. data/examples/showcase/app/lcp_services/defaults/one_week_from_now.rb +11 -0
  621. data/examples/showcase/app/lcp_services/menu_items/recent_announcements.rb +36 -0
  622. data/examples/showcase/app/lcp_services/virtual_columns/project_health.rb +26 -0
  623. data/examples/showcase/app/model_extensions/lcp_error_log_extension.rb +12 -0
  624. data/examples/showcase/app/models/host_inventory_item.rb +20 -0
  625. data/examples/showcase/app/models/platform_profile.rb +27 -0
  626. data/examples/showcase/app/models/platform_user.rb +5 -0
  627. data/examples/showcase/app/views/docs/show.html.erb +19 -0
  628. data/examples/showcase/app/views/host_inventory_items_report/index.html.erb +61 -0
  629. data/examples/showcase/app/views/host_inventory_items_report/show.html.erb +39 -0
  630. data/examples/showcase/app/views/layouts/docs.html.erb +46 -0
  631. data/examples/showcase/app/views/showcase/menu_renderers/_status_pill.html.erb +17 -0
  632. data/examples/showcase/app/views/showcase_custom/_activity_timeline.html.erb +37 -0
  633. data/examples/showcase/app/views/showcase_custom/_card_index.html.erb +74 -0
  634. data/examples/showcase/app/views/showcase_custom/_detail_show.html.erb +115 -0
  635. data/examples/showcase/app/views/showcase_custom/_location_map.html.erb +29 -0
  636. data/examples/showcase/app/views/showcase_custom/_location_view.html.erb +34 -0
  637. data/examples/showcase/app/views/showcase_custom/_quick_stats.html.erb +50 -0
  638. data/examples/showcase/app/views/showcase_custom/_stats_sidebar.html.erb +42 -0
  639. data/examples/showcase/app/views/showcase_custom/_tags_editor.html.erb +48 -0
  640. data/examples/showcase/bin/rails +4 -0
  641. data/examples/showcase/bin/rake +4 -0
  642. data/examples/showcase/config/application.rb +37 -0
  643. data/examples/showcase/config/boot.rb +2 -0
  644. data/examples/showcase/config/database.yml +12 -0
  645. data/examples/showcase/config/environment.rb +2 -0
  646. data/examples/showcase/config/initializers/devise.rb +5 -0
  647. data/examples/showcase/config/initializers/lcp_ruby.rb +175 -0
  648. data/examples/showcase/config/lcp_ruby/auth.yml +60 -0
  649. data/examples/showcase/config/lcp_ruby/jobs/data_import.yml +7 -0
  650. data/examples/showcase/config/lcp_ruby/jobs/showcase_cleanup.yml +11 -0
  651. data/examples/showcase/config/lcp_ruby/jobs/showcase_event_triggered.yml +11 -0
  652. data/examples/showcase/config/lcp_ruby/jobs/showcase_multi_step.yml +9 -0
  653. data/examples/showcase/config/lcp_ruby/jobs/showcase_webhook.yml +13 -0
  654. data/examples/showcase/config/lcp_ruby/menu.yml +249 -0
  655. data/examples/showcase/config/lcp_ruby/models/_base_document.rb +26 -0
  656. data/examples/showcase/config/lcp_ruby/models/_categorized_document.rb +19 -0
  657. data/examples/showcase/config/lcp_ruby/models/_contactable.rb +20 -0
  658. data/examples/showcase/config/lcp_ruby/models/api_token.rb +23 -0
  659. data/examples/showcase/config/lcp_ruby/models/article.rb +49 -0
  660. data/examples/showcase/config/lcp_ruby/models/article_tag.rb +10 -0
  661. data/examples/showcase/config/lcp_ruby/models/author.rb +16 -0
  662. data/examples/showcase/config/lcp_ruby/models/batch_operation.yml +79 -0
  663. data/examples/showcase/config/lcp_ruby/models/batch_operation_item.yml +45 -0
  664. data/examples/showcase/config/lcp_ruby/models/category.rb +23 -0
  665. data/examples/showcase/config/lcp_ruby/models/comment.rb +21 -0
  666. data/examples/showcase/config/lcp_ruby/models/custom_field_definition.rb +59 -0
  667. data/examples/showcase/config/lcp_ruby/models/department.rb +32 -0
  668. data/examples/showcase/config/lcp_ruby/models/employee.rb +52 -0
  669. data/examples/showcase/config/lcp_ruby/models/employee_emergency_contact.rb +24 -0
  670. data/examples/showcase/config/lcp_ruby/models/employee_profile.rb +18 -0
  671. data/examples/showcase/config/lcp_ruby/models/employee_skill.rb +10 -0
  672. data/examples/showcase/config/lcp_ruby/models/export_log.yml +27 -0
  673. data/examples/showcase/config/lcp_ruby/models/export_profile.yml +37 -0
  674. data/examples/showcase/config/lcp_ruby/models/feature.rb +76 -0
  675. data/examples/showcase/config/lcp_ruby/models/gapfree_sequence.rb +17 -0
  676. data/examples/showcase/config/lcp_ruby/models/group.rb +26 -0
  677. data/examples/showcase/config/lcp_ruby/models/group_membership.rb +21 -0
  678. data/examples/showcase/config/lcp_ruby/models/group_role_mapping.rb +14 -0
  679. data/examples/showcase/config/lcp_ruby/models/host_inventory_item.yml +111 -0
  680. data/examples/showcase/config/lcp_ruby/models/import_profile.rb +30 -0
  681. data/examples/showcase/config/lcp_ruby/models/import_row.rb +32 -0
  682. data/examples/showcase/config/lcp_ruby/models/ingredient_def.rb +11 -0
  683. data/examples/showcase/config/lcp_ruby/models/lcp_error_log.yml +61 -0
  684. data/examples/showcase/config/lcp_ruby/models/page_config.rb +19 -0
  685. data/examples/showcase/config/lcp_ruby/models/permission_config.rb +21 -0
  686. data/examples/showcase/config/lcp_ruby/models/pipeline.rb +15 -0
  687. data/examples/showcase/config/lcp_ruby/models/pipeline_stage.rb +17 -0
  688. data/examples/showcase/config/lcp_ruby/models/platform_profile.rb +104 -0
  689. data/examples/showcase/config/lcp_ruby/models/profile_setting.rb +17 -0
  690. data/examples/showcase/config/lcp_ruby/models/profile_tag.rb +19 -0
  691. data/examples/showcase/config/lcp_ruby/models/project.rb +23 -0
  692. data/examples/showcase/config/lcp_ruby/models/role.rb +24 -0
  693. data/examples/showcase/config/lcp_ruby/models/saved_filter.rb +50 -0
  694. data/examples/showcase/config/lcp_ruby/models/showcase_aggregate.rb +93 -0
  695. data/examples/showcase/config/lcp_ruby/models/showcase_aggregate_company.rb +14 -0
  696. data/examples/showcase/config/lcp_ruby/models/showcase_aggregate_item.rb +25 -0
  697. data/examples/showcase/config/lcp_ruby/models/showcase_announcement.rb +20 -0
  698. data/examples/showcase/config/lcp_ruby/models/showcase_array.rb +45 -0
  699. data/examples/showcase/config/lcp_ruby/models/showcase_attachment.rb +49 -0
  700. data/examples/showcase/config/lcp_ruby/models/showcase_audit_log.yml +44 -0
  701. data/examples/showcase/config/lcp_ruby/models/showcase_audited_record.rb +20 -0
  702. data/examples/showcase/config/lcp_ruby/models/showcase_batch_task.rb +27 -0
  703. data/examples/showcase/config/lcp_ruby/models/showcase_business_unit.rb +26 -0
  704. data/examples/showcase/config/lcp_ruby/models/showcase_condition.rb +24 -0
  705. data/examples/showcase/config/lcp_ruby/models/showcase_condition_category.rb +16 -0
  706. data/examples/showcase/config/lcp_ruby/models/showcase_condition_task.rb +16 -0
  707. data/examples/showcase/config/lcp_ruby/models/showcase_condition_threshold.rb +13 -0
  708. data/examples/showcase/config/lcp_ruby/models/showcase_contact.rb +22 -0
  709. data/examples/showcase/config/lcp_ruby/models/showcase_custom_render.rb +38 -0
  710. data/examples/showcase/config/lcp_ruby/models/showcase_delete_reason.rb +11 -0
  711. data/examples/showcase/config/lcp_ruby/models/showcase_division.rb +31 -0
  712. data/examples/showcase/config/lcp_ruby/models/showcase_extensibility.rb +25 -0
  713. data/examples/showcase/config/lcp_ruby/models/showcase_field.rb +53 -0
  714. data/examples/showcase/config/lcp_ruby/models/showcase_form.rb +23 -0
  715. data/examples/showcase/config/lcp_ruby/models/showcase_form_action.rb +38 -0
  716. data/examples/showcase/config/lcp_ruby/models/showcase_grade.rb +18 -0
  717. data/examples/showcase/config/lcp_ruby/models/showcase_hr_employee.rb +90 -0
  718. data/examples/showcase/config/lcp_ruby/models/showcase_item_class.rb +31 -0
  719. data/examples/showcase/config/lcp_ruby/models/showcase_job_execution.rb +49 -0
  720. data/examples/showcase/config/lcp_ruby/models/showcase_memo.rb +23 -0
  721. data/examples/showcase/config/lcp_ruby/models/showcase_model.rb +74 -0
  722. data/examples/showcase/config/lcp_ruby/models/showcase_organization.rb +31 -0
  723. data/examples/showcase/config/lcp_ruby/models/showcase_permission.rb +30 -0
  724. data/examples/showcase/config/lcp_ruby/models/showcase_person.rb +23 -0
  725. data/examples/showcase/config/lcp_ruby/models/showcase_positioning.rb +19 -0
  726. data/examples/showcase/config/lcp_ruby/models/showcase_quick_note.rb +13 -0
  727. data/examples/showcase/config/lcp_ruby/models/showcase_recipe.rb +17 -0
  728. data/examples/showcase/config/lcp_ruby/models/showcase_report.rb +32 -0
  729. data/examples/showcase/config/lcp_ruby/models/showcase_school_class.rb +17 -0
  730. data/examples/showcase/config/lcp_ruby/models/showcase_search.rb +84 -0
  731. data/examples/showcase/config/lcp_ruby/models/showcase_sequence.rb +46 -0
  732. data/examples/showcase/config/lcp_ruby/models/showcase_soft_delete.rb +30 -0
  733. data/examples/showcase/config/lcp_ruby/models/showcase_soft_delete_item.rb +21 -0
  734. data/examples/showcase/config/lcp_ruby/models/showcase_student.rb +17 -0
  735. data/examples/showcase/config/lcp_ruby/models/showcase_type_default.rb +49 -0
  736. data/examples/showcase/config/lcp_ruby/models/showcase_userstamps.rb +26 -0
  737. data/examples/showcase/config/lcp_ruby/models/showcase_virtual_field.rb +68 -0
  738. data/examples/showcase/config/lcp_ruby/models/showcase_workflow.rb +54 -0
  739. data/examples/showcase/config/lcp_ruby/models/skill.rb +22 -0
  740. data/examples/showcase/config/lcp_ruby/models/tag.rb +16 -0
  741. data/examples/showcase/config/lcp_ruby/models/user.rb +54 -0
  742. data/examples/showcase/config/lcp_ruby/models/weather_station.rb +20 -0
  743. data/examples/showcase/config/lcp_ruby/models/workflow_approval_request.yml +77 -0
  744. data/examples/showcase/config/lcp_ruby/models/workflow_approval_step.yml +56 -0
  745. data/examples/showcase/config/lcp_ruby/models/workflow_approval_task.yml +51 -0
  746. data/examples/showcase/config/lcp_ruby/models/workflow_audit_log.yml +74 -0
  747. data/examples/showcase/config/lcp_ruby/pages/article_detail.yml +32 -0
  748. data/examples/showcase/config/lcp_ruby/pages/author_detail.yml +27 -0
  749. data/examples/showcase/config/lcp_ruby/pages/category_detail.yml +24 -0
  750. data/examples/showcase/config/lcp_ruby/pages/chart_showcase.yml +151 -0
  751. data/examples/showcase/config/lcp_ruby/pages/department_detail.yml +55 -0
  752. data/examples/showcase/config/lcp_ruby/pages/department_explorer.yml +60 -0
  753. data/examples/showcase/config/lcp_ruby/pages/employee_overview.yml +106 -0
  754. data/examples/showcase/config/lcp_ruby/pages/employee_transfer_dialog.yml +24 -0
  755. data/examples/showcase/config/lcp_ruby/pages/hr_turnover_dashboard.yml +288 -0
  756. data/examples/showcase/config/lcp_ruby/pages/main_dashboard.yml +142 -0
  757. data/examples/showcase/config/lcp_ruby/pages/monitoring_dashboard.yml +58 -0
  758. data/examples/showcase/config/lcp_ruby/pages/pipeline_detail.yml +16 -0
  759. data/examples/showcase/config/lcp_ruby/pages/showcase_custom_zones.yml +39 -0
  760. data/examples/showcase/config/lcp_ruby/pages/showcase_form_action_dialog.yml +11 -0
  761. data/examples/showcase/config/lcp_ruby/pages/workflow_request_detail.yml +34 -0
  762. data/examples/showcase/config/lcp_ruby/permissions/api_token.yml +26 -0
  763. data/examples/showcase/config/lcp_ruby/permissions/batch_operation.yml +36 -0
  764. data/examples/showcase/config/lcp_ruby/permissions/custom_field_definition.yml +15 -0
  765. data/examples/showcase/config/lcp_ruby/permissions/default.yml +28 -0
  766. data/examples/showcase/config/lcp_ruby/permissions/export_log.yml +23 -0
  767. data/examples/showcase/config/lcp_ruby/permissions/export_profile.yml +25 -0
  768. data/examples/showcase/config/lcp_ruby/permissions/gapfree_sequence.yml +6 -0
  769. data/examples/showcase/config/lcp_ruby/permissions/group.yml +20 -0
  770. data/examples/showcase/config/lcp_ruby/permissions/group_membership.yml +20 -0
  771. data/examples/showcase/config/lcp_ruby/permissions/group_role_mapping.yml +20 -0
  772. data/examples/showcase/config/lcp_ruby/permissions/host_inventory_item.yml +34 -0
  773. data/examples/showcase/config/lcp_ruby/permissions/import_profile.yml +29 -0
  774. data/examples/showcase/config/lcp_ruby/permissions/import_row.yml +17 -0
  775. data/examples/showcase/config/lcp_ruby/permissions/lcp_error_log.yml +8 -0
  776. data/examples/showcase/config/lcp_ruby/permissions/page_config.yml +15 -0
  777. data/examples/showcase/config/lcp_ruby/permissions/permission_config.yml +15 -0
  778. data/examples/showcase/config/lcp_ruby/permissions/platform_profile.yml +23 -0
  779. data/examples/showcase/config/lcp_ruby/permissions/profile_setting.yml +23 -0
  780. data/examples/showcase/config/lcp_ruby/permissions/profile_tag.yml +23 -0
  781. data/examples/showcase/config/lcp_ruby/permissions/role.yml +20 -0
  782. data/examples/showcase/config/lcp_ruby/permissions/saved_filter.yml +39 -0
  783. data/examples/showcase/config/lcp_ruby/permissions/showcase_announcement.yml +22 -0
  784. data/examples/showcase/config/lcp_ruby/permissions/showcase_audit_log.yml +15 -0
  785. data/examples/showcase/config/lcp_ruby/permissions/showcase_audited_record.yml +15 -0
  786. data/examples/showcase/config/lcp_ruby/permissions/showcase_batch_task.yml +31 -0
  787. data/examples/showcase/config/lcp_ruby/permissions/showcase_condition.yml +54 -0
  788. data/examples/showcase/config/lcp_ruby/permissions/showcase_contact.yml +21 -0
  789. data/examples/showcase/config/lcp_ruby/permissions/showcase_custom_render.yml +21 -0
  790. data/examples/showcase/config/lcp_ruby/permissions/showcase_delete_reason.yml +12 -0
  791. data/examples/showcase/config/lcp_ruby/permissions/showcase_form_action.yml +26 -0
  792. data/examples/showcase/config/lcp_ruby/permissions/showcase_grade.yml +33 -0
  793. data/examples/showcase/config/lcp_ruby/permissions/showcase_job_execution.yml +23 -0
  794. data/examples/showcase/config/lcp_ruby/permissions/showcase_permission.yml +47 -0
  795. data/examples/showcase/config/lcp_ruby/permissions/showcase_quick_note.yml +12 -0
  796. data/examples/showcase/config/lcp_ruby/permissions/showcase_school_class.yml +37 -0
  797. data/examples/showcase/config/lcp_ruby/permissions/showcase_student.yml +43 -0
  798. data/examples/showcase/config/lcp_ruby/permissions/showcase_workflow.yml +34 -0
  799. data/examples/showcase/config/lcp_ruby/permissions/workflow_approval_request.yml +18 -0
  800. data/examples/showcase/config/lcp_ruby/permissions/workflow_approval_step.yml +18 -0
  801. data/examples/showcase/config/lcp_ruby/permissions/workflow_approval_task.yml +18 -0
  802. data/examples/showcase/config/lcp_ruby/permissions/workflow_audit_log.yml +19 -0
  803. data/examples/showcase/config/lcp_ruby/presenters/announcements.rb +59 -0
  804. data/examples/showcase/config/lcp_ruby/presenters/approval_requests.rb +69 -0
  805. data/examples/showcase/config/lcp_ruby/presenters/approval_steps.rb +59 -0
  806. data/examples/showcase/config/lcp_ruby/presenters/approval_tasks.rb +65 -0
  807. data/examples/showcase/config/lcp_ruby/presenters/article_comments_zone.rb +12 -0
  808. data/examples/showcase/config/lcp_ruby/presenters/article_related_zone.rb +15 -0
  809. data/examples/showcase/config/lcp_ruby/presenters/articles.rb +149 -0
  810. data/examples/showcase/config/lcp_ruby/presenters/articles_tiles.rb +30 -0
  811. data/examples/showcase/config/lcp_ruby/presenters/author_articles_zone.rb +12 -0
  812. data/examples/showcase/config/lcp_ruby/presenters/author_show_zone.rb +12 -0
  813. data/examples/showcase/config/lcp_ruby/presenters/authors.rb +39 -0
  814. data/examples/showcase/config/lcp_ruby/presenters/batch_operation_items.yml +45 -0
  815. data/examples/showcase/config/lcp_ruby/presenters/batch_operations.yml +66 -0
  816. data/examples/showcase/config/lcp_ruby/presenters/categories.rb +50 -0
  817. data/examples/showcase/config/lcp_ruby/presenters/category_articles_zone.rb +20 -0
  818. data/examples/showcase/config/lcp_ruby/presenters/category_children_zone.rb +10 -0
  819. data/examples/showcase/config/lcp_ruby/presenters/comment_quick_add_dialog.rb +15 -0
  820. data/examples/showcase/config/lcp_ruby/presenters/custom_fields.rb +124 -0
  821. data/examples/showcase/config/lcp_ruby/presenters/dashboard_employees.rb +16 -0
  822. data/examples/showcase/config/lcp_ruby/presenters/delete_reason_dialog.rb +13 -0
  823. data/examples/showcase/config/lcp_ruby/presenters/departments.rb +55 -0
  824. data/examples/showcase/config/lcp_ruby/presenters/dept_add_employee_dialog.rb +19 -0
  825. data/examples/showcase/config/lcp_ruby/presenters/dept_children_zone.rb +10 -0
  826. data/examples/showcase/config/lcp_ruby/presenters/dept_detail_zone.rb +14 -0
  827. data/examples/showcase/config/lcp_ruby/presenters/dept_employees_zone.rb +16 -0
  828. data/examples/showcase/config/lcp_ruby/presenters/dept_list_selection_zone.rb +19 -0
  829. data/examples/showcase/config/lcp_ruby/presenters/employee_overview_index_zone.rb +36 -0
  830. data/examples/showcase/config/lcp_ruby/presenters/employee_quick_add_dialog.rb +14 -0
  831. data/examples/showcase/config/lcp_ruby/presenters/employee_show_zone.rb +20 -0
  832. data/examples/showcase/config/lcp_ruby/presenters/employee_transfer_form_zone.rb +19 -0
  833. data/examples/showcase/config/lcp_ruby/presenters/employees.rb +165 -0
  834. data/examples/showcase/config/lcp_ruby/presenters/employees_tiles.rb +33 -0
  835. data/examples/showcase/config/lcp_ruby/presenters/export_logs.yml +58 -0
  836. data/examples/showcase/config/lcp_ruby/presenters/export_profiles.yml +70 -0
  837. data/examples/showcase/config/lcp_ruby/presenters/extensibility_quick_edit_dialog.rb +13 -0
  838. data/examples/showcase/config/lcp_ruby/presenters/feature_kanban.rb +30 -0
  839. data/examples/showcase/config/lcp_ruby/presenters/features_card.rb +179 -0
  840. data/examples/showcase/config/lcp_ruby/presenters/features_hub.rb +44 -0
  841. data/examples/showcase/config/lcp_ruby/presenters/features_table.rb +37 -0
  842. data/examples/showcase/config/lcp_ruby/presenters/features_tiles.rb +48 -0
  843. data/examples/showcase/config/lcp_ruby/presenters/group_memberships.rb +54 -0
  844. data/examples/showcase/config/lcp_ruby/presenters/group_role_mappings.rb +48 -0
  845. data/examples/showcase/config/lcp_ruby/presenters/groups.rb +74 -0
  846. data/examples/showcase/config/lcp_ruby/presenters/host_inventory_items.rb +99 -0
  847. data/examples/showcase/config/lcp_ruby/presenters/host_inventory_items_managed.rb +84 -0
  848. data/examples/showcase/config/lcp_ruby/presenters/host_inventory_items_report.rb +40 -0
  849. data/examples/showcase/config/lcp_ruby/presenters/host_inventory_items_wizard.rb +76 -0
  850. data/examples/showcase/config/lcp_ruby/presenters/import_profiles.rb +61 -0
  851. data/examples/showcase/config/lcp_ruby/presenters/import_rows.rb +39 -0
  852. data/examples/showcase/config/lcp_ruby/presenters/lcp_error_logs.yml +46 -0
  853. data/examples/showcase/config/lcp_ruby/presenters/my_api_tokens.rb +35 -0
  854. data/examples/showcase/config/lcp_ruby/presenters/my_api_tokens_create_dialog.rb +27 -0
  855. data/examples/showcase/config/lcp_ruby/presenters/my_employee_profile.rb +24 -0
  856. data/examples/showcase/config/lcp_ruby/presenters/my_settings.rb +50 -0
  857. data/examples/showcase/config/lcp_ruby/presenters/page_configs.rb +56 -0
  858. data/examples/showcase/config/lcp_ruby/presenters/permission_configs.rb +58 -0
  859. data/examples/showcase/config/lcp_ruby/presenters/pipeline_edit_zone.rb +11 -0
  860. data/examples/showcase/config/lcp_ruby/presenters/pipeline_stages.rb +51 -0
  861. data/examples/showcase/config/lcp_ruby/presenters/pipeline_stages_zone.rb +11 -0
  862. data/examples/showcase/config/lcp_ruby/presenters/pipelines.rb +40 -0
  863. data/examples/showcase/config/lcp_ruby/presenters/platform_profiles.rb +90 -0
  864. data/examples/showcase/config/lcp_ruby/presenters/projects.rb +58 -0
  865. data/examples/showcase/config/lcp_ruby/presenters/quick_note_dialog.rb +14 -0
  866. data/examples/showcase/config/lcp_ruby/presenters/roles.rb +60 -0
  867. data/examples/showcase/config/lcp_ruby/presenters/save_filter_dialog.rb +17 -0
  868. data/examples/showcase/config/lcp_ruby/presenters/saved_filters.rb +94 -0
  869. data/examples/showcase/config/lcp_ruby/presenters/showcase_aggregate_items.rb +61 -0
  870. data/examples/showcase/config/lcp_ruby/presenters/showcase_aggregates.rb +115 -0
  871. data/examples/showcase/config/lcp_ruby/presenters/showcase_aggregates_tiles.rb +44 -0
  872. data/examples/showcase/config/lcp_ruby/presenters/showcase_amount_kanban.rb +31 -0
  873. data/examples/showcase/config/lcp_ruby/presenters/showcase_arrays.rb +140 -0
  874. data/examples/showcase/config/lcp_ruby/presenters/showcase_attachments.rb +66 -0
  875. data/examples/showcase/config/lcp_ruby/presenters/showcase_audit_logs.rb +46 -0
  876. data/examples/showcase/config/lcp_ruby/presenters/showcase_audited_records.rb +58 -0
  877. data/examples/showcase/config/lcp_ruby/presenters/showcase_batch_tasks.rb +97 -0
  878. data/examples/showcase/config/lcp_ruby/presenters/showcase_batch_tasks_archive.rb +52 -0
  879. data/examples/showcase/config/lcp_ruby/presenters/showcase_business_units.rb +43 -0
  880. data/examples/showcase/config/lcp_ruby/presenters/showcase_conditions.rb +195 -0
  881. data/examples/showcase/config/lcp_ruby/presenters/showcase_contacts.rb +82 -0
  882. data/examples/showcase/config/lcp_ruby/presenters/showcase_custom_render_with.rb +50 -0
  883. data/examples/showcase/config/lcp_ruby/presenters/showcase_custom_sections.rb +88 -0
  884. data/examples/showcase/config/lcp_ruby/presenters/showcase_custom_zones_main.rb +75 -0
  885. data/examples/showcase/config/lcp_ruby/presenters/showcase_divisions.rb +53 -0
  886. data/examples/showcase/config/lcp_ruby/presenters/showcase_extensibility.rb +48 -0
  887. data/examples/showcase/config/lcp_ruby/presenters/showcase_fields_card.rb +34 -0
  888. data/examples/showcase/config/lcp_ruby/presenters/showcase_fields_table.rb +128 -0
  889. data/examples/showcase/config/lcp_ruby/presenters/showcase_fields_tiles.rb +42 -0
  890. data/examples/showcase/config/lcp_ruby/presenters/showcase_form_action_dialog.rb +27 -0
  891. data/examples/showcase/config/lcp_ruby/presenters/showcase_form_actions.rb +132 -0
  892. data/examples/showcase/config/lcp_ruby/presenters/showcase_form_actions_overflow.rb +76 -0
  893. data/examples/showcase/config/lcp_ruby/presenters/showcase_forms.rb +110 -0
  894. data/examples/showcase/config/lcp_ruby/presenters/showcase_grades.rb +38 -0
  895. data/examples/showcase/config/lcp_ruby/presenters/showcase_hr_employees.rb +84 -0
  896. data/examples/showcase/config/lcp_ruby/presenters/showcase_item_classes.rb +120 -0
  897. data/examples/showcase/config/lcp_ruby/presenters/showcase_job_executions.rb +150 -0
  898. data/examples/showcase/config/lcp_ruby/presenters/showcase_memos.rb +96 -0
  899. data/examples/showcase/config/lcp_ruby/presenters/showcase_models.rb +112 -0
  900. data/examples/showcase/config/lcp_ruby/presenters/showcase_organizations.rb +106 -0
  901. data/examples/showcase/config/lcp_ruby/presenters/showcase_people.rb +97 -0
  902. data/examples/showcase/config/lcp_ruby/presenters/showcase_permissions.rb +84 -0
  903. data/examples/showcase/config/lcp_ruby/presenters/showcase_positioning.rb +61 -0
  904. data/examples/showcase/config/lcp_ruby/presenters/showcase_recipes.rb +105 -0
  905. data/examples/showcase/config/lcp_ruby/presenters/showcase_recipes_raw.rb +32 -0
  906. data/examples/showcase/config/lcp_ruby/presenters/showcase_reports.rb +109 -0
  907. data/examples/showcase/config/lcp_ruby/presenters/showcase_school_classes.rb +34 -0
  908. data/examples/showcase/config/lcp_ruby/presenters/showcase_searches.rb +226 -0
  909. data/examples/showcase/config/lcp_ruby/presenters/showcase_sequences.rb +67 -0
  910. data/examples/showcase/config/lcp_ruby/presenters/showcase_soft_delete.rb +88 -0
  911. data/examples/showcase/config/lcp_ruby/presenters/showcase_soft_delete_archive.rb +52 -0
  912. data/examples/showcase/config/lcp_ruby/presenters/showcase_soft_delete_items.rb +47 -0
  913. data/examples/showcase/config/lcp_ruby/presenters/showcase_students.rb +33 -0
  914. data/examples/showcase/config/lcp_ruby/presenters/showcase_type_defaults.rb +92 -0
  915. data/examples/showcase/config/lcp_ruby/presenters/showcase_userstamps.rb +76 -0
  916. data/examples/showcase/config/lcp_ruby/presenters/showcase_virtual_fields.rb +104 -0
  917. data/examples/showcase/config/lcp_ruby/presenters/showcase_workflow_admin.rb +24 -0
  918. data/examples/showcase/config/lcp_ruby/presenters/showcase_workflow_kanban.rb +36 -0
  919. data/examples/showcase/config/lcp_ruby/presenters/showcase_workflows.rb +103 -0
  920. data/examples/showcase/config/lcp_ruby/presenters/tags.rb +35 -0
  921. data/examples/showcase/config/lcp_ruby/presenters/users.rb +61 -0
  922. data/examples/showcase/config/lcp_ruby/presenters/weather_stations.rb +60 -0
  923. data/examples/showcase/config/lcp_ruby/presenters/workflow_audit_logs.rb +76 -0
  924. data/examples/showcase/config/lcp_ruby/presenters/workflow_request_audit_zone.rb +28 -0
  925. data/examples/showcase/config/lcp_ruby/theme.yml +2 -0
  926. data/examples/showcase/config/lcp_ruby/types/currency.yml +15 -0
  927. data/examples/showcase/config/lcp_ruby/types/percentage.yml +16 -0
  928. data/examples/showcase/config/lcp_ruby/types/rating.yml +20 -0
  929. data/examples/showcase/config/lcp_ruby/views/announcements.yml +7 -0
  930. data/examples/showcase/config/lcp_ruby/views/approval_requests.yml +7 -0
  931. data/examples/showcase/config/lcp_ruby/views/approval_steps.yml +7 -0
  932. data/examples/showcase/config/lcp_ruby/views/approval_tasks.yml +7 -0
  933. data/examples/showcase/config/lcp_ruby/views/articles.yml +12 -0
  934. data/examples/showcase/config/lcp_ruby/views/authors.yml +7 -0
  935. data/examples/showcase/config/lcp_ruby/views/batch_operation_items.yml +6 -0
  936. data/examples/showcase/config/lcp_ruby/views/batch_operations.yml +10 -0
  937. data/examples/showcase/config/lcp_ruby/views/categories.yml +9 -0
  938. data/examples/showcase/config/lcp_ruby/views/custom_fields.rb +8 -0
  939. data/examples/showcase/config/lcp_ruby/views/dashboard.yml +10 -0
  940. data/examples/showcase/config/lcp_ruby/views/department_explorer.yml +7 -0
  941. data/examples/showcase/config/lcp_ruby/views/departments.yml +9 -0
  942. data/examples/showcase/config/lcp_ruby/views/employee_overview.yml +7 -0
  943. data/examples/showcase/config/lcp_ruby/views/employees.yml +12 -0
  944. data/examples/showcase/config/lcp_ruby/views/export_logs.yml +7 -0
  945. data/examples/showcase/config/lcp_ruby/views/export_profiles.yml +7 -0
  946. data/examples/showcase/config/lcp_ruby/views/features.yml +19 -0
  947. data/examples/showcase/config/lcp_ruby/views/group_memberships.yml +7 -0
  948. data/examples/showcase/config/lcp_ruby/views/group_role_mappings.yml +7 -0
  949. data/examples/showcase/config/lcp_ruby/views/groups.yml +7 -0
  950. data/examples/showcase/config/lcp_ruby/views/host_inventory_items.yml +16 -0
  951. data/examples/showcase/config/lcp_ruby/views/hr_turnover_dashboard.yml +7 -0
  952. data/examples/showcase/config/lcp_ruby/views/import_profiles.yml +7 -0
  953. data/examples/showcase/config/lcp_ruby/views/import_rows.yml +7 -0
  954. data/examples/showcase/config/lcp_ruby/views/lcp_error_logs.yml +10 -0
  955. data/examples/showcase/config/lcp_ruby/views/monitoring.yml +7 -0
  956. data/examples/showcase/config/lcp_ruby/views/my_api_tokens.rb +9 -0
  957. data/examples/showcase/config/lcp_ruby/views/page_configs.yml +11 -0
  958. data/examples/showcase/config/lcp_ruby/views/permission_configs.yml +11 -0
  959. data/examples/showcase/config/lcp_ruby/views/pipeline_stages.yml +7 -0
  960. data/examples/showcase/config/lcp_ruby/views/pipelines.yml +7 -0
  961. data/examples/showcase/config/lcp_ruby/views/platform_profiles.yml +7 -0
  962. data/examples/showcase/config/lcp_ruby/views/projects.yml +9 -0
  963. data/examples/showcase/config/lcp_ruby/views/roles.yml +7 -0
  964. data/examples/showcase/config/lcp_ruby/views/saved_filters.yml +7 -0
  965. data/examples/showcase/config/lcp_ruby/views/showcase_aggregate_items.yml +7 -0
  966. data/examples/showcase/config/lcp_ruby/views/showcase_aggregates.yml +10 -0
  967. data/examples/showcase/config/lcp_ruby/views/showcase_arrays.yml +7 -0
  968. data/examples/showcase/config/lcp_ruby/views/showcase_attachments.yml +7 -0
  969. data/examples/showcase/config/lcp_ruby/views/showcase_audit_logs.yml +11 -0
  970. data/examples/showcase/config/lcp_ruby/views/showcase_audited_records.yml +10 -0
  971. data/examples/showcase/config/lcp_ruby/views/showcase_batch_tasks.yml +10 -0
  972. data/examples/showcase/config/lcp_ruby/views/showcase_business_units.yml +7 -0
  973. data/examples/showcase/config/lcp_ruby/views/showcase_conditions.yml +7 -0
  974. data/examples/showcase/config/lcp_ruby/views/showcase_contacts.yml +7 -0
  975. data/examples/showcase/config/lcp_ruby/views/showcase_custom_render_with.yml +7 -0
  976. data/examples/showcase/config/lcp_ruby/views/showcase_custom_sections.yml +7 -0
  977. data/examples/showcase/config/lcp_ruby/views/showcase_custom_zones.yml +7 -0
  978. data/examples/showcase/config/lcp_ruby/views/showcase_divisions.yml +9 -0
  979. data/examples/showcase/config/lcp_ruby/views/showcase_extensibility.yml +7 -0
  980. data/examples/showcase/config/lcp_ruby/views/showcase_fields.yml +13 -0
  981. data/examples/showcase/config/lcp_ruby/views/showcase_form_actions.yml +10 -0
  982. data/examples/showcase/config/lcp_ruby/views/showcase_forms.yml +7 -0
  983. data/examples/showcase/config/lcp_ruby/views/showcase_grades.yml +9 -0
  984. data/examples/showcase/config/lcp_ruby/views/showcase_hr_employees.yml +9 -0
  985. data/examples/showcase/config/lcp_ruby/views/showcase_item_classes.yml +7 -0
  986. data/examples/showcase/config/lcp_ruby/views/showcase_job_executions.yml +9 -0
  987. data/examples/showcase/config/lcp_ruby/views/showcase_memos.yml +7 -0
  988. data/examples/showcase/config/lcp_ruby/views/showcase_models.yml +7 -0
  989. data/examples/showcase/config/lcp_ruby/views/showcase_organizations.yml +7 -0
  990. data/examples/showcase/config/lcp_ruby/views/showcase_people.yml +7 -0
  991. data/examples/showcase/config/lcp_ruby/views/showcase_permissions.yml +7 -0
  992. data/examples/showcase/config/lcp_ruby/views/showcase_positioning.yml +7 -0
  993. data/examples/showcase/config/lcp_ruby/views/showcase_recipes.yml +10 -0
  994. data/examples/showcase/config/lcp_ruby/views/showcase_reports.yml +7 -0
  995. data/examples/showcase/config/lcp_ruby/views/showcase_school_classes.yml +7 -0
  996. data/examples/showcase/config/lcp_ruby/views/showcase_searches.yml +7 -0
  997. data/examples/showcase/config/lcp_ruby/views/showcase_sequences.yml +7 -0
  998. data/examples/showcase/config/lcp_ruby/views/showcase_soft_delete.yml +10 -0
  999. data/examples/showcase/config/lcp_ruby/views/showcase_soft_delete_items.yml +8 -0
  1000. data/examples/showcase/config/lcp_ruby/views/showcase_students.yml +9 -0
  1001. data/examples/showcase/config/lcp_ruby/views/showcase_userstamps.yml +7 -0
  1002. data/examples/showcase/config/lcp_ruby/views/showcase_virtual_fields.yml +7 -0
  1003. data/examples/showcase/config/lcp_ruby/views/showcase_workflows.yml +16 -0
  1004. data/examples/showcase/config/lcp_ruby/views/tags.yml +7 -0
  1005. data/examples/showcase/config/lcp_ruby/views/users.yml +7 -0
  1006. data/examples/showcase/config/lcp_ruby/views/weather_stations.yml +7 -0
  1007. data/examples/showcase/config/lcp_ruby/views/workflow_audit_logs.yml +9 -0
  1008. data/examples/showcase/config/lcp_ruby/workflows/showcase_form_action.rb +48 -0
  1009. data/examples/showcase/config/lcp_ruby/workflows/showcase_workflow.rb +199 -0
  1010. data/examples/showcase/config/locales/cs.yml +2818 -0
  1011. data/examples/showcase/config/locales/en.yml +67 -0
  1012. data/examples/showcase/config/locales/lcp_ruby/api_tokens.cs.yml +46 -0
  1013. data/examples/showcase/config/locales/lcp_ruby/api_tokens.en.yml +49 -0
  1014. data/examples/showcase/config/routes.rb +21 -0
  1015. data/examples/showcase/config/storage.yml +3 -0
  1016. data/examples/showcase/config.ru +2 -0
  1017. data/examples/showcase/db/migrate/20260219124321_create_active_storage_tables.active_storage.rb +57 -0
  1018. data/examples/showcase/db/migrate/20260220072723_create_lcp_ruby_users.rb +42 -0
  1019. data/examples/showcase/db/migrate/20260406120000_create_host_inventory_items.rb +32 -0
  1020. data/examples/showcase/db/migrate/20260428134542_add_oidc_columns_to_lcp_ruby_users.rb +13 -0
  1021. data/examples/showcase/db/migrate/20260502120000_create_platform_profiles.rb +19 -0
  1022. data/examples/showcase/db/schema.rb +1222 -0
  1023. data/examples/showcase/db/seeds.rb +3085 -0
  1024. data/examples/showcase/erd.md +567 -0
  1025. data/examples/showcase/test/fixtures/import_articles.csv +4 -0
  1026. data/examples/showcase/test/fixtures/import_employees.csv +4 -0
  1027. data/examples/todo/Gemfile +8 -0
  1028. data/examples/todo/Gemfile.lock +415 -0
  1029. data/examples/todo/Rakefile +2 -0
  1030. data/examples/todo/app/assets/config/manifest.js +1 -0
  1031. data/examples/todo/app/controllers/application_controller.rb +6 -0
  1032. data/examples/todo/app/lcp_services/defaults/one_week_from_now.rb +11 -0
  1033. data/examples/todo/bin/rails +4 -0
  1034. data/examples/todo/bin/rake +4 -0
  1035. data/examples/todo/config/application.rb +31 -0
  1036. data/examples/todo/config/boot.rb +2 -0
  1037. data/examples/todo/config/database.yml +12 -0
  1038. data/examples/todo/config/environment.rb +2 -0
  1039. data/examples/todo/config/lcp_ruby/models/todo_item.yml +67 -0
  1040. data/examples/todo/config/lcp_ruby/models/todo_list.yml +49 -0
  1041. data/examples/todo/config/lcp_ruby/permissions/default.yml +10 -0
  1042. data/examples/todo/config/lcp_ruby/permissions/todo_item.yml +21 -0
  1043. data/examples/todo/config/lcp_ruby/presenters/todo_item.yml +75 -0
  1044. data/examples/todo/config/lcp_ruby/presenters/todo_list.yml +68 -0
  1045. data/examples/todo/config/lcp_ruby/views/todo_items.yml +11 -0
  1046. data/examples/todo/config/lcp_ruby/views/todo_lists.yml +9 -0
  1047. data/examples/todo/config/routes.rb +4 -0
  1048. data/examples/todo/config/storage.yml +3 -0
  1049. data/examples/todo/config.ru +2 -0
  1050. data/examples/todo/db/migrate/20260219103416_create_active_storage_tables.active_storage.rb +57 -0
  1051. data/examples/todo/db/schema.rb +63 -0
  1052. data/examples/todo/db/seeds.rb +24 -0
  1053. data/examples/todo/erd.md +21 -0
  1054. data/exe/lcp +33 -0
  1055. data/lib/generators/lcp_ruby/agent_setup_generator.rb +102 -0
  1056. data/lib/generators/lcp_ruby/api_tokens_generator.rb +102 -0
  1057. data/lib/generators/lcp_ruby/auditing_generator.rb +54 -0
  1058. data/lib/generators/lcp_ruby/background_jobs_generator.rb +61 -0
  1059. data/lib/generators/lcp_ruby/batch_operations_generator.rb +62 -0
  1060. data/lib/generators/lcp_ruby/claude_skills_generator.rb +47 -0
  1061. data/lib/generators/lcp_ruby/custom_fields_generator.rb +54 -0
  1062. data/lib/generators/lcp_ruby/dsl_to_yaml.rb +72 -0
  1063. data/lib/generators/lcp_ruby/entity/color_palette.rb +22 -0
  1064. data/lib/generators/lcp_ruby/entity/field_descriptor.rb +24 -0
  1065. data/lib/generators/lcp_ruby/entity/field_token_parser.rb +101 -0
  1066. data/lib/generators/lcp_ruby/entity/role_discovery.rb +45 -0
  1067. data/lib/generators/lcp_ruby/entity_generator.rb +1104 -0
  1068. data/lib/generators/lcp_ruby/export_generator.rb +71 -0
  1069. data/lib/generators/lcp_ruby/format_support.rb +56 -0
  1070. data/lib/generators/lcp_ruby/gapfree_sequences_generator.rb +64 -0
  1071. data/lib/generators/lcp_ruby/groups_generator.rb +94 -0
  1072. data/lib/generators/lcp_ruby/host_controller_generator.rb +202 -0
  1073. data/lib/generators/lcp_ruby/import_generator.rb +96 -0
  1074. data/lib/generators/lcp_ruby/install_auth_generator.rb +432 -0
  1075. data/lib/generators/lcp_ruby/install_generator.rb +319 -0
  1076. data/lib/generators/lcp_ruby/monitoring_generator.rb +58 -0
  1077. data/lib/generators/lcp_ruby/oidc_role_mappings_generator.rb +60 -0
  1078. data/lib/generators/lcp_ruby/pages_generator.rb +66 -0
  1079. data/lib/generators/lcp_ruby/permission_source_generator.rb +66 -0
  1080. data/lib/generators/lcp_ruby/role_model_generator.rb +73 -0
  1081. data/lib/generators/lcp_ruby/saved_filters_generator.rb +62 -0
  1082. data/lib/generators/lcp_ruby/templates/add_oidc_columns_to_lcp_ruby_users.rb.erb +13 -0
  1083. data/lib/generators/lcp_ruby/templates/agent_setup/agents_md.md +3 -0
  1084. data/lib/generators/lcp_ruby/templates/agent_setup/claude_md.md +12 -0
  1085. data/lib/generators/lcp_ruby/templates/api_tokens/create_dialog.rb +31 -0
  1086. data/lib/generators/lcp_ruby/templates/api_tokens/locales.en.yml +43 -0
  1087. data/lib/generators/lcp_ruby/templates/api_tokens/model.rb +23 -0
  1088. data/lib/generators/lcp_ruby/templates/api_tokens/permissions.yml +30 -0
  1089. data/lib/generators/lcp_ruby/templates/api_tokens/presenter.rb +38 -0
  1090. data/lib/generators/lcp_ruby/templates/api_tokens/view_group.rb +13 -0
  1091. data/lib/generators/lcp_ruby/templates/auditing/model.rb +34 -0
  1092. data/lib/generators/lcp_ruby/templates/auditing/permissions.yml +19 -0
  1093. data/lib/generators/lcp_ruby/templates/auditing/presenter.rb +41 -0
  1094. data/lib/generators/lcp_ruby/templates/auditing/view_group.rb +10 -0
  1095. data/lib/generators/lcp_ruby/templates/background_jobs/model.rb +59 -0
  1096. data/lib/generators/lcp_ruby/templates/background_jobs/permissions.yml +22 -0
  1097. data/lib/generators/lcp_ruby/templates/background_jobs/presenter.rb +82 -0
  1098. data/lib/generators/lcp_ruby/templates/background_jobs/view_group.rb +9 -0
  1099. data/lib/generators/lcp_ruby/templates/batch_operations/item_model.rb +28 -0
  1100. data/lib/generators/lcp_ruby/templates/batch_operations/item_presenter.rb +43 -0
  1101. data/lib/generators/lcp_ruby/templates/batch_operations/model.rb +42 -0
  1102. data/lib/generators/lcp_ruby/templates/batch_operations/permissions.yml +44 -0
  1103. data/lib/generators/lcp_ruby/templates/batch_operations/presenter.rb +61 -0
  1104. data/lib/generators/lcp_ruby/templates/batch_operations/view_group.rb +9 -0
  1105. data/lib/generators/lcp_ruby/templates/create_lcp_ruby_users.rb.erb +50 -0
  1106. data/lib/generators/lcp_ruby/templates/custom_fields/model.rb +60 -0
  1107. data/lib/generators/lcp_ruby/templates/custom_fields/permissions.yml +19 -0
  1108. data/lib/generators/lcp_ruby/templates/custom_fields/presenter.rb +128 -0
  1109. data/lib/generators/lcp_ruby/templates/custom_fields/view_group.rb +9 -0
  1110. data/lib/generators/lcp_ruby/templates/entity/model.rb +42 -0
  1111. data/lib/generators/lcp_ruby/templates/entity/permissions.yml +20 -0
  1112. data/lib/generators/lcp_ruby/templates/entity/presenter.rb +55 -0
  1113. data/lib/generators/lcp_ruby/templates/entity/view_group.rb +8 -0
  1114. data/lib/generators/lcp_ruby/templates/export/export_log_model.rb +30 -0
  1115. data/lib/generators/lcp_ruby/templates/export/export_log_permissions.yml +24 -0
  1116. data/lib/generators/lcp_ruby/templates/export/export_logs_presenter.rb +51 -0
  1117. data/lib/generators/lcp_ruby/templates/export/export_profile_model.rb +28 -0
  1118. data/lib/generators/lcp_ruby/templates/export/export_profile_permissions.yml +26 -0
  1119. data/lib/generators/lcp_ruby/templates/export/export_profiles_presenter.rb +59 -0
  1120. data/lib/generators/lcp_ruby/templates/gapfree_sequences/model.rb +18 -0
  1121. data/lib/generators/lcp_ruby/templates/gapfree_sequences/permissions.yml +19 -0
  1122. data/lib/generators/lcp_ruby/templates/gapfree_sequences/presenter.rb +51 -0
  1123. data/lib/generators/lcp_ruby/templates/gapfree_sequences/view_group.rb +9 -0
  1124. data/lib/generators/lcp_ruby/templates/groups/group_membership_model.rb +17 -0
  1125. data/lib/generators/lcp_ruby/templates/groups/group_model.rb +28 -0
  1126. data/lib/generators/lcp_ruby/templates/groups/group_permissions.yml +19 -0
  1127. data/lib/generators/lcp_ruby/templates/groups/group_presenter.rb +53 -0
  1128. data/lib/generators/lcp_ruby/templates/groups/group_role_mapping_model.rb +15 -0
  1129. data/lib/generators/lcp_ruby/templates/groups/group_view_group.rb +9 -0
  1130. data/lib/generators/lcp_ruby/templates/host_controller/controller.rb.erb +131 -0
  1131. data/lib/generators/lcp_ruby/templates/import/data_import_job.yml +7 -0
  1132. data/lib/generators/lcp_ruby/templates/import/import_profile_model.rb +30 -0
  1133. data/lib/generators/lcp_ruby/templates/import/import_profile_permissions.yml +29 -0
  1134. data/lib/generators/lcp_ruby/templates/import/import_profiles_presenter.rb +57 -0
  1135. data/lib/generators/lcp_ruby/templates/import/import_row_model.rb +32 -0
  1136. data/lib/generators/lcp_ruby/templates/import/import_row_permissions.yml +11 -0
  1137. data/lib/generators/lcp_ruby/templates/import/import_rows_presenter.rb +35 -0
  1138. data/lib/generators/lcp_ruby/templates/install/default_permissions.yml +26 -0
  1139. data/lib/generators/lcp_ruby/templates/install/menu.yml.tt +71 -0
  1140. data/lib/generators/lcp_ruby/templates/install/model.rb +20 -0
  1141. data/lib/generators/lcp_ruby/templates/install/permissions.yml +25 -0
  1142. data/lib/generators/lcp_ruby/templates/install/presenter.rb +44 -0
  1143. data/lib/generators/lcp_ruby/templates/install/view_group.rb +10 -0
  1144. data/lib/generators/lcp_ruby/templates/install_auth/oidc/entra.yml.erb +46 -0
  1145. data/lib/generators/lcp_ruby/templates/install_auth/oidc/generic.yml.erb +55 -0
  1146. data/lib/generators/lcp_ruby/templates/install_auth/oidc/google.yml.erb +42 -0
  1147. data/lib/generators/lcp_ruby/templates/install_auth/oidc/keycloak.yml.erb +45 -0
  1148. data/lib/generators/lcp_ruby/templates/install_auth/oidc/okta.yml.erb +45 -0
  1149. data/lib/generators/lcp_ruby/templates/install_auth/user.rb +36 -0
  1150. data/lib/generators/lcp_ruby/templates/monitoring/model.rb +34 -0
  1151. data/lib/generators/lcp_ruby/templates/monitoring/model_extension.rb +12 -0
  1152. data/lib/generators/lcp_ruby/templates/monitoring/page.yml +49 -0
  1153. data/lib/generators/lcp_ruby/templates/monitoring/permissions.yml +8 -0
  1154. data/lib/generators/lcp_ruby/templates/monitoring/presenter.rb +44 -0
  1155. data/lib/generators/lcp_ruby/templates/oidc_role_mappings/locales.en.yml +15 -0
  1156. data/lib/generators/lcp_ruby/templates/oidc_role_mappings/model.rb +32 -0
  1157. data/lib/generators/lcp_ruby/templates/oidc_role_mappings/permissions.yml +21 -0
  1158. data/lib/generators/lcp_ruby/templates/oidc_role_mappings/presenter.rb +41 -0
  1159. data/lib/generators/lcp_ruby/templates/pages/model.rb +19 -0
  1160. data/lib/generators/lcp_ruby/templates/pages/permissions.yml +19 -0
  1161. data/lib/generators/lcp_ruby/templates/pages/presenter.rb +51 -0
  1162. data/lib/generators/lcp_ruby/templates/pages/view_group.rb +10 -0
  1163. data/lib/generators/lcp_ruby/templates/permission_source/model.rb +21 -0
  1164. data/lib/generators/lcp_ruby/templates/permission_source/permissions.yml +19 -0
  1165. data/lib/generators/lcp_ruby/templates/permission_source/presenter.rb +51 -0
  1166. data/lib/generators/lcp_ruby/templates/permission_source/view_group.rb +10 -0
  1167. data/lib/generators/lcp_ruby/templates/role_model/model.rb +24 -0
  1168. data/lib/generators/lcp_ruby/templates/role_model/permissions.yml +19 -0
  1169. data/lib/generators/lcp_ruby/templates/role_model/presenter.rb +62 -0
  1170. data/lib/generators/lcp_ruby/templates/role_model/view_group.rb +10 -0
  1171. data/lib/generators/lcp_ruby/templates/saved_filters/model.rb +51 -0
  1172. data/lib/generators/lcp_ruby/templates/saved_filters/permissions.yml +44 -0
  1173. data/lib/generators/lcp_ruby/templates/saved_filters/presenter.rb +96 -0
  1174. data/lib/generators/lcp_ruby/templates/saved_filters/save_dialog_presenter.rb +19 -0
  1175. data/lib/generators/lcp_ruby/templates/workflow_approvals/request_model.rb +50 -0
  1176. data/lib/generators/lcp_ruby/templates/workflow_approvals/request_permissions.yml +10 -0
  1177. data/lib/generators/lcp_ruby/templates/workflow_approvals/request_presenter.rb +44 -0
  1178. data/lib/generators/lcp_ruby/templates/workflow_approvals/request_view_group.rb +10 -0
  1179. data/lib/generators/lcp_ruby/templates/workflow_approvals/step_model.rb +36 -0
  1180. data/lib/generators/lcp_ruby/templates/workflow_approvals/step_permissions.yml +10 -0
  1181. data/lib/generators/lcp_ruby/templates/workflow_approvals/task_model.rb +34 -0
  1182. data/lib/generators/lcp_ruby/templates/workflow_approvals/task_permissions.yml +10 -0
  1183. data/lib/generators/lcp_ruby/templates/workflow_approvals/task_presenter.rb +39 -0
  1184. data/lib/generators/lcp_ruby/templates/workflow_approvals/task_view_group.rb +10 -0
  1185. data/lib/generators/lcp_ruby/templates/workflow_audit_log/model.rb +48 -0
  1186. data/lib/generators/lcp_ruby/templates/workflow_audit_log/permissions.yml +10 -0
  1187. data/lib/generators/lcp_ruby/templates/workflow_audit_log/presenter.rb +44 -0
  1188. data/lib/generators/lcp_ruby/templates/workflow_audit_log/view_group.rb +10 -0
  1189. data/lib/generators/lcp_ruby/templates/workflow_definition/model.rb +43 -0
  1190. data/lib/generators/lcp_ruby/templates/workflow_definition/permissions.yml +19 -0
  1191. data/lib/generators/lcp_ruby/templates/workflow_definition/presenter.rb +70 -0
  1192. data/lib/generators/lcp_ruby/templates/workflow_definition/view_group.rb +10 -0
  1193. data/lib/generators/lcp_ruby/workflow_approvals_generator.rb +93 -0
  1194. data/lib/generators/lcp_ruby/workflow_audit_log_generator.rb +54 -0
  1195. data/lib/generators/lcp_ruby/workflow_definition_generator.rb +77 -0
  1196. data/lib/lcp.rb +6 -0
  1197. data/lib/lcp_ruby/actions/action_executor.rb +66 -0
  1198. data/lib/lcp_ruby/actions/action_registry.rb +48 -0
  1199. data/lib/lcp_ruby/actions/api_tokens/revoke.rb +13 -0
  1200. data/lib/lcp_ruby/actions/base_action.rb +79 -0
  1201. data/lib/lcp_ruby/actions/form_action_pipeline.rb +138 -0
  1202. data/lib/lcp_ruby/aggregates/query_builder.rb +6 -0
  1203. data/lib/lcp_ruby/api_tokens/model_extension.rb +41 -0
  1204. data/lib/lcp_ruby/api_tokens/resolver_registry.rb +53 -0
  1205. data/lib/lcp_ruby/api_tokens/token_generator.rb +27 -0
  1206. data/lib/lcp_ruby/api_tokens/verifier.rb +38 -0
  1207. data/lib/lcp_ruby/app_template.rb +181 -0
  1208. data/lib/lcp_ruby/array_query.rb +120 -0
  1209. data/lib/lcp_ruby/asset_copier.rb +62 -0
  1210. data/lib/lcp_ruby/association_fk_type.rb +191 -0
  1211. data/lib/lcp_ruby/association_join_column.rb +28 -0
  1212. data/lib/lcp_ruby/association_options_builder.rb +231 -0
  1213. data/lib/lcp_ruby/auditing/audit_writer.rb +258 -0
  1214. data/lib/lcp_ruby/auditing/contract_validator.rb +95 -0
  1215. data/lib/lcp_ruby/auditing/registry.rb +29 -0
  1216. data/lib/lcp_ruby/auditing/setup.rb +49 -0
  1217. data/lib/lcp_ruby/authentication/audit_subscriber.rb +51 -0
  1218. data/lib/lcp_ruby/authentication/bearer_jwt_verifier.rb +139 -0
  1219. data/lib/lcp_ruby/authentication/devise_setup.rb +47 -0
  1220. data/lib/lcp_ruby/authentication/errors.rb +27 -0
  1221. data/lib/lcp_ruby/authentication/http_fetcher.rb +36 -0
  1222. data/lib/lcp_ruby/authentication/jwks_cache.rb +91 -0
  1223. data/lib/lcp_ruby/authentication/oidc_bearer_resolver.rb +84 -0
  1224. data/lib/lcp_ruby/authentication/omniauth_builder.rb +147 -0
  1225. data/lib/lcp_ruby/authentication/provider.rb +108 -0
  1226. data/lib/lcp_ruby/authentication/provider_registry.rb +227 -0
  1227. data/lib/lcp_ruby/authentication/role_mapper.rb +94 -0
  1228. data/lib/lcp_ruby/authentication/test_support.rb +257 -0
  1229. data/lib/lcp_ruby/authentication/user_resolver.rb +169 -0
  1230. data/lib/lcp_ruby/authentication.rb +40 -0
  1231. data/lib/lcp_ruby/authorization/association_lookup.rb +56 -0
  1232. data/lib/lcp_ruby/authorization/authorization_error.rb +12 -0
  1233. data/lib/lcp_ruby/authorization/cache.rb +89 -0
  1234. data/lib/lcp_ruby/authorization/codes.rb +17 -0
  1235. data/lib/lcp_ruby/authorization/impersonated_user.rb +29 -0
  1236. data/lib/lcp_ruby/authorization/includes_hint.rb +110 -0
  1237. data/lib/lcp_ruby/authorization/inherited_parent_validator.rb +142 -0
  1238. data/lib/lcp_ruby/authorization/invariant_check/configuration.rb +132 -0
  1239. data/lib/lcp_ruby/authorization/invariant_error.rb +15 -0
  1240. data/lib/lcp_ruby/authorization/misconfigured_page_error.rb +30 -0
  1241. data/lib/lcp_ruby/authorization/page_gate.rb +57 -0
  1242. data/lib/lcp_ruby/authorization/permission_evaluator.rb +343 -0
  1243. data/lib/lcp_ruby/authorization/policy_factory.rb +91 -0
  1244. data/lib/lcp_ruby/authorization/runtime_invariant_validator.rb +421 -0
  1245. data/lib/lcp_ruby/authorization/scope_builder.rb +227 -0
  1246. data/lib/lcp_ruby/authorization/scope_resolver.rb +28 -0
  1247. data/lib/lcp_ruby/authorized_controller.rb +44 -0
  1248. data/lib/lcp_ruby/background_jobs/base_handler.rb +113 -0
  1249. data/lib/lcp_ruby/background_jobs/change_handler.rb +17 -0
  1250. data/lib/lcp_ruby/background_jobs/contract.rb +16 -0
  1251. data/lib/lcp_ruby/background_jobs/contract_validator.rb +112 -0
  1252. data/lib/lcp_ruby/background_jobs/declarative/base_action.rb +11 -0
  1253. data/lib/lcp_ruby/background_jobs/declarative/call_webhook_action.rb +174 -0
  1254. data/lib/lcp_ruby/background_jobs/declarative/fire_event_action.rb +24 -0
  1255. data/lib/lcp_ruby/background_jobs/declarative/registry.rb +34 -0
  1256. data/lib/lcp_ruby/background_jobs/declarative/run_scope_action.rb +98 -0
  1257. data/lib/lcp_ruby/background_jobs/declarative/send_notification_action.rb +13 -0
  1258. data/lib/lcp_ruby/background_jobs/definition.rb +134 -0
  1259. data/lib/lcp_ruby/background_jobs/enqueue.rb +83 -0
  1260. data/lib/lcp_ruby/background_jobs/errors.rb +9 -0
  1261. data/lib/lcp_ruby/background_jobs/executor_job.rb +111 -0
  1262. data/lib/lcp_ruby/background_jobs/handler_factory.rb +46 -0
  1263. data/lib/lcp_ruby/background_jobs/host_source.rb +33 -0
  1264. data/lib/lcp_ruby/background_jobs/model_source.rb +93 -0
  1265. data/lib/lcp_ruby/background_jobs/registry.rb +81 -0
  1266. data/lib/lcp_ruby/background_jobs/resolver.rb +29 -0
  1267. data/lib/lcp_ruby/background_jobs/schedule_adapter.rb +14 -0
  1268. data/lib/lcp_ruby/background_jobs/setup.rb +145 -0
  1269. data/lib/lcp_ruby/background_jobs/static_source.rb +19 -0
  1270. data/lib/lcp_ruby/background_jobs/steps_executor.rb +52 -0
  1271. data/lib/lcp_ruby/background_jobs/triggers/event_trigger.rb +97 -0
  1272. data/lib/lcp_ruby/background_jobs/triggers/trigger_installer.rb +20 -0
  1273. data/lib/lcp_ruby/background_jobs/unique_key_builder.rb +31 -0
  1274. data/lib/lcp_ruby/batch_actions/base_service.rb +70 -0
  1275. data/lib/lcp_ruby/batch_actions/batch_action_handler.rb +200 -0
  1276. data/lib/lcp_ruby/batch_actions/custom_action_dispatcher.rb +133 -0
  1277. data/lib/lcp_ruby/batch_actions/destroy_service.rb +37 -0
  1278. data/lib/lcp_ruby/batch_actions/permanently_destroy_service.rb +33 -0
  1279. data/lib/lcp_ruby/batch_actions/restore_service.rb +33 -0
  1280. data/lib/lcp_ruby/batch_actions.rb +5 -0
  1281. data/lib/lcp_ruby/bulk_updater.rb +25 -0
  1282. data/lib/lcp_ruby/cli/docs_command.rb +29 -0
  1283. data/lib/lcp_ruby/cli/examples_command.rb +29 -0
  1284. data/lib/lcp_ruby/cli/new_command.rb +509 -0
  1285. data/lib/lcp_ruby/cli/run_command.rb +155 -0
  1286. data/lib/lcp_ruby/cli/skills_command.rb +54 -0
  1287. data/lib/lcp_ruby/cli.rb +74 -0
  1288. data/lib/lcp_ruby/condition_evaluator.rb +366 -0
  1289. data/lib/lcp_ruby/condition_service_registry.rb +58 -0
  1290. data/lib/lcp_ruby/condition_services/current_user_role.rb +28 -0
  1291. data/lib/lcp_ruby/condition_services/feature_flag.rb +63 -0
  1292. data/lib/lcp_ruby/condition_services/impersonating.rb +24 -0
  1293. data/lib/lcp_ruby/conditions/validator.rb +35 -0
  1294. data/lib/lcp_ruby/configuration.rb +431 -0
  1295. data/lib/lcp_ruby/controller/authentication.rb +118 -0
  1296. data/lib/lcp_ruby/controller/authorization.rb +198 -0
  1297. data/lib/lcp_ruby/controller/bearer_authentication.rb +76 -0
  1298. data/lib/lcp_ruby/controller/crud_helpers.rb +233 -0
  1299. data/lib/lcp_ruby/controller/error_handling.rb +94 -0
  1300. data/lib/lcp_ruby/controller/impersonation.rb +70 -0
  1301. data/lib/lcp_ruby/controller/locale_binding.rb +62 -0
  1302. data/lib/lcp_ruby/controller/path_helpers.rb +125 -0
  1303. data/lib/lcp_ruby/controller/presenter_setup.rb +89 -0
  1304. data/lib/lcp_ruby/controller/search.rb +321 -0
  1305. data/lib/lcp_ruby/controller/view_helpers.rb +105 -0
  1306. data/lib/lcp_ruby/current.rb +13 -0
  1307. data/lib/lcp_ruby/custom_fields/applicator.rb +194 -0
  1308. data/lib/lcp_ruby/custom_fields/contract_validator.rb +77 -0
  1309. data/lib/lcp_ruby/custom_fields/definition_change_handler.rb +21 -0
  1310. data/lib/lcp_ruby/custom_fields/query.rb +112 -0
  1311. data/lib/lcp_ruby/custom_fields/registry.rb +70 -0
  1312. data/lib/lcp_ruby/custom_fields/setup.rb +58 -0
  1313. data/lib/lcp_ruby/custom_fields/utils.rb +40 -0
  1314. data/lib/lcp_ruby/custom_fields.rb +9 -0
  1315. data/lib/lcp_ruby/data_source/api_error_placeholder.rb +47 -0
  1316. data/lib/lcp_ruby/data_source/api_filter_translator.rb +72 -0
  1317. data/lib/lcp_ruby/data_source/api_model_concern.rb +131 -0
  1318. data/lib/lcp_ruby/data_source/api_preloader.rb +44 -0
  1319. data/lib/lcp_ruby/data_source/base.rb +85 -0
  1320. data/lib/lcp_ruby/data_source/cached_wrapper.rb +107 -0
  1321. data/lib/lcp_ruby/data_source/host.rb +71 -0
  1322. data/lib/lcp_ruby/data_source/registry.rb +39 -0
  1323. data/lib/lcp_ruby/data_source/resilient_wrapper.rb +67 -0
  1324. data/lib/lcp_ruby/data_source/rest_json.rb +247 -0
  1325. data/lib/lcp_ruby/data_source/setup.rb +57 -0
  1326. data/lib/lcp_ruby/dev_toolbar.rb +8 -0
  1327. data/lib/lcp_ruby/display/base_renderer.rb +21 -0
  1328. data/lib/lcp_ruby/display/count_badge.rb +11 -0
  1329. data/lib/lcp_ruby/display/icon_badge.rb +17 -0
  1330. data/lib/lcp_ruby/display/renderer_registry.rb +138 -0
  1331. data/lib/lcp_ruby/display/renderers/attachment_link.rb +26 -0
  1332. data/lib/lcp_ruby/display/renderers/attachment_list.rb +32 -0
  1333. data/lib/lcp_ruby/display/renderers/attachment_preview.rb +26 -0
  1334. data/lib/lcp_ruby/display/renderers/avatar.rb +14 -0
  1335. data/lib/lcp_ruby/display/renderers/badge.rb +43 -0
  1336. data/lib/lcp_ruby/display/renderers/boolean_icon.rb +54 -0
  1337. data/lib/lcp_ruby/display/renderers/code.rb +39 -0
  1338. data/lib/lcp_ruby/display/renderers/collection.rb +34 -0
  1339. data/lib/lcp_ruby/display/renderers/color_swatch.rb +25 -0
  1340. data/lib/lcp_ruby/display/renderers/concerns/attachment_helpers.rb +73 -0
  1341. data/lib/lcp_ruby/display/renderers/concerns/workflow_helpers.rb +35 -0
  1342. data/lib/lcp_ruby/display/renderers/copy_code.rb +33 -0
  1343. data/lib/lcp_ruby/display/renderers/currency.rb +14 -0
  1344. data/lib/lcp_ruby/display/renderers/date.rb +17 -0
  1345. data/lib/lcp_ruby/display/renderers/datetime.rb +17 -0
  1346. data/lib/lcp_ruby/display/renderers/email_link.rb +15 -0
  1347. data/lib/lcp_ruby/display/renderers/file_size.rb +11 -0
  1348. data/lib/lcp_ruby/display/renderers/heading.rb +11 -0
  1349. data/lib/lcp_ruby/display/renderers/image.rb +17 -0
  1350. data/lib/lcp_ruby/display/renderers/internal_link.rb +23 -0
  1351. data/lib/lcp_ruby/display/renderers/link.rb +15 -0
  1352. data/lib/lcp_ruby/display/renderers/link_list.rb +90 -0
  1353. data/lib/lcp_ruby/display/renderers/markdown.rb +33 -0
  1354. data/lib/lcp_ruby/display/renderers/number.rb +14 -0
  1355. data/lib/lcp_ruby/display/renderers/percentage.rb +12 -0
  1356. data/lib/lcp_ruby/display/renderers/phone_link.rb +15 -0
  1357. data/lib/lcp_ruby/display/renderers/progress_bar.rb +15 -0
  1358. data/lib/lcp_ruby/display/renderers/rating.rb +15 -0
  1359. data/lib/lcp_ruby/display/renderers/record_link.rb +101 -0
  1360. data/lib/lcp_ruby/display/renderers/relative_date.rb +15 -0
  1361. data/lib/lcp_ruby/display/renderers/rich_text.rb +15 -0
  1362. data/lib/lcp_ruby/display/renderers/text.rb +12 -0
  1363. data/lib/lcp_ruby/display/renderers/truncate.rb +17 -0
  1364. data/lib/lcp_ruby/display/renderers/url_link.rb +22 -0
  1365. data/lib/lcp_ruby/display/renderers/workflow_badge.rb +37 -0
  1366. data/lib/lcp_ruby/display/renderers/workflow_timeline.rb +173 -0
  1367. data/lib/lcp_ruby/display/renderers.rb +3 -0
  1368. data/lib/lcp_ruby/display/text_badge.rb +15 -0
  1369. data/lib/lcp_ruby/dsl/condition_builder.rb +190 -0
  1370. data/lib/lcp_ruby/dsl/dsl_loader.rb +365 -0
  1371. data/lib/lcp_ruby/dsl/field_builder.rb +35 -0
  1372. data/lib/lcp_ruby/dsl/job_builder.rb +92 -0
  1373. data/lib/lcp_ruby/dsl/model_builder.rb +544 -0
  1374. data/lib/lcp_ruby/dsl/presenter_builder.rb +1272 -0
  1375. data/lib/lcp_ruby/dsl/source_location_capture.rb +52 -0
  1376. data/lib/lcp_ruby/dsl/type_builder.rb +88 -0
  1377. data/lib/lcp_ruby/dsl/view_group_builder.rb +92 -0
  1378. data/lib/lcp_ruby/dsl/workflow_builder.rb +319 -0
  1379. data/lib/lcp_ruby/dynamic.rb +7 -0
  1380. data/lib/lcp_ruby/dynamic_references/resolver.rb +154 -0
  1381. data/lib/lcp_ruby/dynamic_references/validator.rb +92 -0
  1382. data/lib/lcp_ruby/embed_providers/base.rb +18 -0
  1383. data/lib/lcp_ruby/embed_providers/grafana.rb +38 -0
  1384. data/lib/lcp_ruby/embed_providers/metabase.rb +37 -0
  1385. data/lib/lcp_ruby/engine.rb +680 -0
  1386. data/lib/lcp_ruby/events/async_handler_job.rb +21 -0
  1387. data/lib/lcp_ruby/events/dispatcher.rb +52 -0
  1388. data/lib/lcp_ruby/events/handler_base.rb +51 -0
  1389. data/lib/lcp_ruby/events/handler_registry.rb +49 -0
  1390. data/lib/lcp_ruby/export/data_generator.rb +158 -0
  1391. data/lib/lcp_ruby/export/export_handler.rb +315 -0
  1392. data/lib/lcp_ruby/export/field_tree_builder.rb +219 -0
  1393. data/lib/lcp_ruby/export/setup.rb +94 -0
  1394. data/lib/lcp_ruby/export/value_formatter.rb +223 -0
  1395. data/lib/lcp_ruby/export.rb +9 -0
  1396. data/lib/lcp_ruby/gem_paths.rb +51 -0
  1397. data/lib/lcp_ruby/generators/entity_menu_writer.rb +258 -0
  1398. data/lib/lcp_ruby/generators/feature_registry.rb +208 -0
  1399. data/lib/lcp_ruby/generators/prerequisites.rb +90 -0
  1400. data/lib/lcp_ruby/grouped_query/builder.rb +206 -0
  1401. data/lib/lcp_ruby/grouped_query/result_wrapper.rb +72 -0
  1402. data/lib/lcp_ruby/grouped_query/row.rb +31 -0
  1403. data/lib/lcp_ruby/groups/change_handler.rb +18 -0
  1404. data/lib/lcp_ruby/groups/contract.rb +42 -0
  1405. data/lib/lcp_ruby/groups/contract_validator.rb +110 -0
  1406. data/lib/lcp_ruby/groups/host_loader.rb +54 -0
  1407. data/lib/lcp_ruby/groups/model_loader.rb +186 -0
  1408. data/lib/lcp_ruby/groups/registry.rb +113 -0
  1409. data/lib/lcp_ruby/groups/setup.rb +129 -0
  1410. data/lib/lcp_ruby/groups/yaml_loader.rb +97 -0
  1411. data/lib/lcp_ruby/hash_utils.rb +42 -0
  1412. data/lib/lcp_ruby/i18n_check/configuration.rb +104 -0
  1413. data/lib/lcp_ruby/i18n_check/heuristics.rb +26 -0
  1414. data/lib/lcp_ruby/i18n_check/key_deriver.rb +136 -0
  1415. data/lib/lcp_ruby/i18n_check/offense.rb +29 -0
  1416. data/lib/lcp_ruby/i18n_check/registry_walker.rb +621 -0
  1417. data/lib/lcp_ruby/i18n_check/reporter.rb +96 -0
  1418. data/lib/lcp_ruby/i18n_check/runner.rb +46 -0
  1419. data/lib/lcp_ruby/i18n_check.rb +15 -0
  1420. data/lib/lcp_ruby/i18n_lint.rb +145 -0
  1421. data/lib/lcp_ruby/import/auto_mapper.rb +98 -0
  1422. data/lib/lcp_ruby/import/field_tree_builder.rb +178 -0
  1423. data/lib/lcp_ruby/import/file_parser.rb +223 -0
  1424. data/lib/lcp_ruby/import/import_dialog_handler.rb +410 -0
  1425. data/lib/lcp_ruby/import/import_job_handler.rb +224 -0
  1426. data/lib/lcp_ruby/import/row_processor.rb +281 -0
  1427. data/lib/lcp_ruby/import/setup.rb +277 -0
  1428. data/lib/lcp_ruby/import/value_coercer.rb +143 -0
  1429. data/lib/lcp_ruby/import.rb +14 -0
  1430. data/lib/lcp_ruby/json_item_wrapper.rb +152 -0
  1431. data/lib/lcp_ruby/kanban/board.rb +28 -0
  1432. data/lib/lcp_ruby/kanban/column.rb +28 -0
  1433. data/lib/lcp_ruby/kanban/default_provider.rb +376 -0
  1434. data/lib/lcp_ruby/kanban/host_provider.rb +94 -0
  1435. data/lib/lcp_ruby/kanban/move_result.rb +52 -0
  1436. data/lib/lcp_ruby/kanban/provider_test_harness.rb +54 -0
  1437. data/lib/lcp_ruby/kanban/swimlane.rb +21 -0
  1438. data/lib/lcp_ruby/menu.rb +46 -0
  1439. data/lib/lcp_ruby/metadata/aggregate_definition.rb +6 -0
  1440. data/lib/lcp_ruby/metadata/association_definition.rb +196 -0
  1441. data/lib/lcp_ruby/metadata/auth_validator.rb +222 -0
  1442. data/lib/lcp_ruby/metadata/configuration_validator.rb +7958 -0
  1443. data/lib/lcp_ruby/metadata/contract_result.rb +9 -0
  1444. data/lib/lcp_ruby/metadata/display_template_definition.rb +77 -0
  1445. data/lib/lcp_ruby/metadata/enum_label_resolver.rb +27 -0
  1446. data/lib/lcp_ruby/metadata/erd_generator.rb +274 -0
  1447. data/lib/lcp_ruby/metadata/event_definition.rb +55 -0
  1448. data/lib/lcp_ruby/metadata/field_definition.rb +267 -0
  1449. data/lib/lcp_ruby/metadata/group_definition.rb +31 -0
  1450. data/lib/lcp_ruby/metadata/i18n_label.rb +29 -0
  1451. data/lib/lcp_ruby/metadata/loader.rb +916 -0
  1452. data/lib/lcp_ruby/metadata/menu_definition.rb +116 -0
  1453. data/lib/lcp_ruby/metadata/menu_item.rb +792 -0
  1454. data/lib/lcp_ruby/metadata/menu_item_resolver.rb +105 -0
  1455. data/lib/lcp_ruby/metadata/model_definition.rb +612 -0
  1456. data/lib/lcp_ruby/metadata/model_hash_merger.rb +88 -0
  1457. data/lib/lcp_ruby/metadata/model_inheritance_resolver.rb +165 -0
  1458. data/lib/lcp_ruby/metadata/page_definition.rb +245 -0
  1459. data/lib/lcp_ruby/metadata/path_template.rb +231 -0
  1460. data/lib/lcp_ruby/metadata/permission_definition.rb +237 -0
  1461. data/lib/lcp_ruby/metadata/permission_merger.rb +81 -0
  1462. data/lib/lcp_ruby/metadata/presenter_definition.rb +689 -0
  1463. data/lib/lcp_ruby/metadata/reserved_names.rb +79 -0
  1464. data/lib/lcp_ruby/metadata/responsive_policy.rb +95 -0
  1465. data/lib/lcp_ruby/metadata/schema_validator.rb +172 -0
  1466. data/lib/lcp_ruby/metadata/validation_definition.rb +69 -0
  1467. data/lib/lcp_ruby/metadata/view_group_definition.rb +208 -0
  1468. data/lib/lcp_ruby/metadata/virtual_column_definition.rb +154 -0
  1469. data/lib/lcp_ruby/metadata/zone_definition.rb +423 -0
  1470. data/lib/lcp_ruby/metrics/collector.rb +123 -0
  1471. data/lib/lcp_ruby/metrics/collector_registry.rb +57 -0
  1472. data/lib/lcp_ruby/metrics/error_recorder.rb +70 -0
  1473. data/lib/lcp_ruby/metrics/fingerprint.rb +33 -0
  1474. data/lib/lcp_ruby/metrics/json_query.rb +47 -0
  1475. data/lib/lcp_ruby/metrics/metric_definitions.rb +105 -0
  1476. data/lib/lcp_ruby/metrics/prometheus_check.rb +9 -0
  1477. data/lib/lcp_ruby/metrics/rate_limiter.rb +99 -0
  1478. data/lib/lcp_ruby/metrics/setup.rb +71 -0
  1479. data/lib/lcp_ruby/metrics/subscriber.rb +126 -0
  1480. data/lib/lcp_ruby/model_factory/aggregate_applicator.rb +14 -0
  1481. data/lib/lcp_ruby/model_factory/api_association_applicator.rb +119 -0
  1482. data/lib/lcp_ruby/model_factory/api_builder.rb +85 -0
  1483. data/lib/lcp_ruby/model_factory/array_type.rb +78 -0
  1484. data/lib/lcp_ruby/model_factory/array_type_applicator.rb +33 -0
  1485. data/lib/lcp_ruby/model_factory/association_applicator.rb +201 -0
  1486. data/lib/lcp_ruby/model_factory/attachment_applicator.rb +160 -0
  1487. data/lib/lcp_ruby/model_factory/auditing_applicator.rb +72 -0
  1488. data/lib/lcp_ruby/model_factory/builder.rb +235 -0
  1489. data/lib/lcp_ruby/model_factory/callback_applicator.rb +63 -0
  1490. data/lib/lcp_ruby/model_factory/computed_applicator.rb +55 -0
  1491. data/lib/lcp_ruby/model_factory/default_applicator.rb +85 -0
  1492. data/lib/lcp_ruby/model_factory/enum_applicator.rb +24 -0
  1493. data/lib/lcp_ruby/model_factory/inherited_parent_validator_applicator.rb +39 -0
  1494. data/lib/lcp_ruby/model_factory/label_method_builder.rb +82 -0
  1495. data/lib/lcp_ruby/model_factory/managed_tracking.rb +71 -0
  1496. data/lib/lcp_ruby/model_factory/positioning_applicator.rb +23 -0
  1497. data/lib/lcp_ruby/model_factory/ransack_applicator.rb +66 -0
  1498. data/lib/lcp_ruby/model_factory/registry.rb +33 -0
  1499. data/lib/lcp_ruby/model_factory/schema_manager.rb +655 -0
  1500. data/lib/lcp_ruby/model_factory/scope_applicator.rb +87 -0
  1501. data/lib/lcp_ruby/model_factory/sequence_applicator.rb +173 -0
  1502. data/lib/lcp_ruby/model_factory/service_accessor_applicator.rb +40 -0
  1503. data/lib/lcp_ruby/model_factory/soft_delete_applicator.rb +141 -0
  1504. data/lib/lcp_ruby/model_factory/transform_applicator.rb +51 -0
  1505. data/lib/lcp_ruby/model_factory/tree_applicator.rb +239 -0
  1506. data/lib/lcp_ruby/model_factory/userstamps_applicator.rb +73 -0
  1507. data/lib/lcp_ruby/model_factory/validation_applicator.rb +319 -0
  1508. data/lib/lcp_ruby/model_factory/virtual_column_applicator.rb +79 -0
  1509. data/lib/lcp_ruby/model_factory/workflow_applicator.rb +141 -0
  1510. data/lib/lcp_ruby/pages/change_handler.rb +15 -0
  1511. data/lib/lcp_ruby/pages/contract_validator.rb +74 -0
  1512. data/lib/lcp_ruby/pages/definition_validator.rb +42 -0
  1513. data/lib/lcp_ruby/pages/filter_form.rb +200 -0
  1514. data/lib/lcp_ruby/pages/filter_form_validator.rb +636 -0
  1515. data/lib/lcp_ruby/pages/registry.rb +133 -0
  1516. data/lib/lcp_ruby/pages/resolver.rb +32 -0
  1517. data/lib/lcp_ruby/pages/scope_context_resolver.rb +37 -0
  1518. data/lib/lcp_ruby/pages/scope_filter_set.rb +57 -0
  1519. data/lib/lcp_ruby/pages/setup.rb +46 -0
  1520. data/lib/lcp_ruby/path_utils.rb +12 -0
  1521. data/lib/lcp_ruby/permissions/change_handler.rb +22 -0
  1522. data/lib/lcp_ruby/permissions/contract_validator.rb +74 -0
  1523. data/lib/lcp_ruby/permissions/definition_validator.rb +119 -0
  1524. data/lib/lcp_ruby/permissions/registry.rb +135 -0
  1525. data/lib/lcp_ruby/permissions/setup.rb +51 -0
  1526. data/lib/lcp_ruby/permissions/source_resolver.rb +56 -0
  1527. data/lib/lcp_ruby/presenter/action_set.rb +236 -0
  1528. data/lib/lcp_ruby/presenter/breadcrumb_builder.rb +183 -0
  1529. data/lib/lcp_ruby/presenter/breadcrumb_path_helper.rb +17 -0
  1530. data/lib/lcp_ruby/presenter/column_set.rb +268 -0
  1531. data/lib/lcp_ruby/presenter/enrichment.rb +136 -0
  1532. data/lib/lcp_ruby/presenter/field_value_resolver.rb +237 -0
  1533. data/lib/lcp_ruby/presenter/includes_resolver/association_dependency.rb +59 -0
  1534. data/lib/lcp_ruby/presenter/includes_resolver/dependency_collector.rb +394 -0
  1535. data/lib/lcp_ruby/presenter/includes_resolver/loading_strategy.rb +70 -0
  1536. data/lib/lcp_ruby/presenter/includes_resolver/strategy_resolver.rb +123 -0
  1537. data/lib/lcp_ruby/presenter/includes_resolver.rb +42 -0
  1538. data/lib/lcp_ruby/presenter/layout_builder.rb +467 -0
  1539. data/lib/lcp_ruby/presenter/link_resolver.rb +65 -0
  1540. data/lib/lcp_ruby/presenter/metadata_lookup.rb +28 -0
  1541. data/lib/lcp_ruby/presenter/resolver.rb +25 -0
  1542. data/lib/lcp_ruby/record_aliases/metadata_checker.rb +213 -0
  1543. data/lib/lcp_ruby/record_aliases/setup.rb +212 -0
  1544. data/lib/lcp_ruby/reserved_route_segments.rb +37 -0
  1545. data/lib/lcp_ruby/roles/change_handler.rb +11 -0
  1546. data/lib/lcp_ruby/roles/contract_validator.rb +67 -0
  1547. data/lib/lcp_ruby/roles/registry.rb +89 -0
  1548. data/lib/lcp_ruby/roles/setup.rb +50 -0
  1549. data/lib/lcp_ruby/routing/presenter_routes.rb +104 -0
  1550. data/lib/lcp_ruby/saved_filters/change_handler.rb +13 -0
  1551. data/lib/lcp_ruby/saved_filters/contract_validator.rb +85 -0
  1552. data/lib/lcp_ruby/saved_filters/registry.rb +36 -0
  1553. data/lib/lcp_ruby/saved_filters/resolver.rb +108 -0
  1554. data/lib/lcp_ruby/saved_filters/setup.rb +42 -0
  1555. data/lib/lcp_ruby/saved_filters/stale_field_validator.rb +84 -0
  1556. data/lib/lcp_ruby/schemas/auth.json +208 -0
  1557. data/lib/lcp_ruby/schemas/menu.json +338 -0
  1558. data/lib/lcp_ruby/schemas/model.json +1161 -0
  1559. data/lib/lcp_ruby/schemas/page.json +877 -0
  1560. data/lib/lcp_ruby/schemas/permission.json +454 -0
  1561. data/lib/lcp_ruby/schemas/presenter.json +2274 -0
  1562. data/lib/lcp_ruby/schemas/theme.json +62 -0
  1563. data/lib/lcp_ruby/schemas/type.json +146 -0
  1564. data/lib/lcp_ruby/schemas/view_group.json +163 -0
  1565. data/lib/lcp_ruby/search/custom_field_filter.rb +171 -0
  1566. data/lib/lcp_ruby/search/custom_filter_interceptor.rb +40 -0
  1567. data/lib/lcp_ruby/search/filter_metadata_builder.rb +409 -0
  1568. data/lib/lcp_ruby/search/filter_param_builder.rb +177 -0
  1569. data/lib/lcp_ruby/search/operator_registry.rb +79 -0
  1570. data/lib/lcp_ruby/search/param_sanitizer.rb +25 -0
  1571. data/lib/lcp_ruby/search/parameter_definition.rb +187 -0
  1572. data/lib/lcp_ruby/search/parameterized_scope_applicator.rb +129 -0
  1573. data/lib/lcp_ruby/search/query_builder.rb +143 -0
  1574. data/lib/lcp_ruby/search/query_language_parser.rb +549 -0
  1575. data/lib/lcp_ruby/search/query_language_serializer.rb +193 -0
  1576. data/lib/lcp_ruby/search/quick_search.rb +162 -0
  1577. data/lib/lcp_ruby/search/relative_date_expander.rb +57 -0
  1578. data/lib/lcp_ruby/search_result.rb +70 -0
  1579. data/lib/lcp_ruby/sequences/sequence_manager.rb +51 -0
  1580. data/lib/lcp_ruby/services/accessors/json_field.rb +23 -0
  1581. data/lib/lcp_ruby/services/built_in_accessors.rb +17 -0
  1582. data/lib/lcp_ruby/services/built_in_defaults.rb +22 -0
  1583. data/lib/lcp_ruby/services/built_in_transforms.rb +20 -0
  1584. data/lib/lcp_ruby/services/checker.rb +133 -0
  1585. data/lib/lcp_ruby/services/registry.rb +83 -0
  1586. data/lib/lcp_ruby/skills_installer.rb +73 -0
  1587. data/lib/lcp_ruby/sort/enum_sort_order.rb +38 -0
  1588. data/lib/lcp_ruby/tasks/destroy_order_resolver.rb +57 -0
  1589. data/lib/lcp_ruby/tasks/doctor.rb +294 -0
  1590. data/lib/lcp_ruby/tasks/permission_resolve_formatter.rb +245 -0
  1591. data/lib/lcp_ruby/types/built_in_types.rb +157 -0
  1592. data/lib/lcp_ruby/types/transforms/base_transform.rb +11 -0
  1593. data/lib/lcp_ruby/types/transforms/downcase.rb +11 -0
  1594. data/lib/lcp_ruby/types/transforms/normalize_phone.rb +19 -0
  1595. data/lib/lcp_ruby/types/transforms/normalize_url.rb +16 -0
  1596. data/lib/lcp_ruby/types/transforms/strip.rb +11 -0
  1597. data/lib/lcp_ruby/types/type_definition.rb +112 -0
  1598. data/lib/lcp_ruby/types/type_registry.rb +75 -0
  1599. data/lib/lcp_ruby/url_safety.rb +97 -0
  1600. data/lib/lcp_ruby/user_snapshot.rb +15 -0
  1601. data/lib/lcp_ruby/version.rb +3 -0
  1602. data/lib/lcp_ruby/view_slots/registry.rb +71 -0
  1603. data/lib/lcp_ruby/view_slots/slot_component.rb +22 -0
  1604. data/lib/lcp_ruby/view_slots/slot_context.rb +20 -0
  1605. data/lib/lcp_ruby/virtual_columns/builder.rb +234 -0
  1606. data/lib/lcp_ruby/virtual_columns/collector.rb +186 -0
  1607. data/lib/lcp_ruby/virtual_columns.rb +4 -0
  1608. data/lib/lcp_ruby/virtual_fields/synthetic_marker.rb +17 -0
  1609. data/lib/lcp_ruby/virtual_fields/types/array_of.rb +49 -0
  1610. data/lib/lcp_ruby/virtual_fields/virtual_field.rb +107 -0
  1611. data/lib/lcp_ruby/virtual_fields/virtual_form.rb +144 -0
  1612. data/lib/lcp_ruby/widgets/chart_palette.rb +25 -0
  1613. data/lib/lcp_ruby/widgets/chartkick_check.rb +9 -0
  1614. data/lib/lcp_ruby/widgets/data_resolver.rb +676 -0
  1615. data/lib/lcp_ruby/widgets/date_grouper.rb +54 -0
  1616. data/lib/lcp_ruby/widgets/presenter_zone_resolver.rb +170 -0
  1617. data/lib/lcp_ruby/widgets/record_source_resolver.rb +56 -0
  1618. data/lib/lcp_ruby/widgets/scope_applicator.rb +187 -0
  1619. data/lib/lcp_ruby/workflow/approval/activation_handler.rb +39 -0
  1620. data/lib/lcp_ruby/workflow/approval/approval_definition.rb +117 -0
  1621. data/lib/lcp_ruby/workflow/approval/approver_resolver.rb +98 -0
  1622. data/lib/lcp_ruby/workflow/approval/cleanup_handler.rb +37 -0
  1623. data/lib/lcp_ruby/workflow/approval/contract_validator.rb +96 -0
  1624. data/lib/lcp_ruby/workflow/approval/data_builder.rb +53 -0
  1625. data/lib/lcp_ruby/workflow/approval/discard_handler.rb +51 -0
  1626. data/lib/lcp_ruby/workflow/approval/engine.rb +314 -0
  1627. data/lib/lcp_ruby/workflow/approval/registry.rb +40 -0
  1628. data/lib/lcp_ruby/workflow/approval/resolution_handler.rb +103 -0
  1629. data/lib/lcp_ruby/workflow/approval/setup.rb +138 -0
  1630. data/lib/lcp_ruby/workflow/approval/step_definition.rb +52 -0
  1631. data/lib/lcp_ruby/workflow/approval/step_evaluator.rb +163 -0
  1632. data/lib/lcp_ruby/workflow/approval/system_evaluator.rb +29 -0
  1633. data/lib/lcp_ruby/workflow/approval/task_manager.rb +202 -0
  1634. data/lib/lcp_ruby/workflow/audit_contract_validator.rb +64 -0
  1635. data/lib/lcp_ruby/workflow/audit_registry.rb +24 -0
  1636. data/lib/lcp_ruby/workflow/audit_writer.rb +51 -0
  1637. data/lib/lcp_ruby/workflow/change_handler.rb +14 -0
  1638. data/lib/lcp_ruby/workflow/contract.rb +21 -0
  1639. data/lib/lcp_ruby/workflow/contract_validator.rb +44 -0
  1640. data/lib/lcp_ruby/workflow/errors.rb +12 -0
  1641. data/lib/lcp_ruby/workflow/host_source.rb +19 -0
  1642. data/lib/lcp_ruby/workflow/mermaid_builder.rb +217 -0
  1643. data/lib/lcp_ruby/workflow/model_source.rb +79 -0
  1644. data/lib/lcp_ruby/workflow/registry.rb +113 -0
  1645. data/lib/lcp_ruby/workflow/resolver.rb +32 -0
  1646. data/lib/lcp_ruby/workflow/setup.rb +135 -0
  1647. data/lib/lcp_ruby/workflow/state_definition.rb +59 -0
  1648. data/lib/lcp_ruby/workflow/state_machine.rb +78 -0
  1649. data/lib/lcp_ruby/workflow/static_source.rb +20 -0
  1650. data/lib/lcp_ruby/workflow/transition_action_builder.rb +46 -0
  1651. data/lib/lcp_ruby/workflow/transition_definition.rb +70 -0
  1652. data/lib/lcp_ruby/workflow/transition_executor.rb +140 -0
  1653. data/lib/lcp_ruby/workflow/transition_label_resolver.rb +21 -0
  1654. data/lib/lcp_ruby/workflow/transition_result.rb +20 -0
  1655. data/lib/lcp_ruby/workflow/value_resolver.rb +58 -0
  1656. data/lib/lcp_ruby/workflow/workflow_definition.rb +195 -0
  1657. data/lib/lcp_ruby.rb +764 -0
  1658. data/lib/rubocop/cop/lcp_ruby/no_hardcoded_i18n_string.rb +249 -0
  1659. data/lib/tasks/lcp_ruby.rake +432 -0
  1660. data/lib/tasks/lcp_ruby_assets.rake +37 -0
  1661. data/lib/tasks/lcp_ruby_auth.rake +49 -0
  1662. data/lib/tasks/lcp_ruby_db.rake +76 -0
  1663. data/lib/tasks/lcp_ruby_doctor.rake +20 -0
  1664. data/lib/tasks/lcp_ruby_feature_catalog.rake +61 -0
  1665. data/lib/tasks/lcp_ruby_gapfree_sequences.rake +39 -0
  1666. data/lib/tasks/lcp_ruby_i18n_check.rake +23 -0
  1667. data/lib/tasks/lcp_ruby_i18n_lint.rake +20 -0
  1668. data/lib/tasks/lcp_ruby_invariant_check.rake +72 -0
  1669. data/vendor/assets/javascripts/lcp_ruby/activestorage.min.js +866 -0
  1670. data/vendor/assets/javascripts/lcp_ruby/highlight.min.js +1244 -0
  1671. data/vendor/assets/javascripts/lcp_ruby/lucide.min.js +12 -0
  1672. data/vendor/assets/javascripts/lcp_ruby/stimulus.umd.js +2588 -0
  1673. data/vendor/assets/javascripts/lcp_ruby/tom-select.complete.min.js +444 -0
  1674. data/vendor/assets/stylesheets/lcp_ruby/highlight-github.min.css +12 -0
  1675. data/vendor/assets/stylesheets/lcp_ruby/tom-select.css +412 -0
  1676. metadata +1950 -0
@@ -0,0 +1,3085 @@
1
+ puts "Seeding showcase data..."
2
+
3
+ # Clear existing data so seeds are re-runnable (children before parents)
4
+ # Also reset auto-increment counters so IDs start from 1 (demo_path links use hardcoded IDs)
5
+ connection = ActiveRecord::Base.connection
6
+ %w[
7
+ showcase_grade showcase_student showcase_school_class
8
+ saved_filter feature pipeline_stage pipeline showcase_batch_task showcase_item_class showcase_recipe showcase_positioning showcase_userstamps
9
+ showcase_sequence gapfree_sequence
10
+ showcase_job_execution
11
+ workflow_approval_task workflow_approval_step workflow_approval_request
12
+ workflow_audit_log showcase_workflow
13
+ showcase_audit_log showcase_audited_record page_config
14
+ showcase_custom_render
15
+ showcase_person showcase_organization showcase_contact
16
+ showcase_memo showcase_report
17
+ showcase_soft_delete_item showcase_soft_delete showcase_aggregate_item showcase_aggregate showcase_aggregate_company
18
+ showcase_virtual_field showcase_extensibility permission_config role showcase_permission
19
+ showcase_attachment custom_field_definition employee_skill project showcase_search showcase_array
20
+ showcase_hr_employee showcase_division showcase_business_unit
21
+ showcase_announcement
22
+ employee skill department showcase_form comment article_tag tag article
23
+ author category showcase_model showcase_field
24
+ group_role_mapping group_membership group
25
+ showcase_condition_task showcase_condition_threshold showcase_condition showcase_condition_category
26
+ lcp_error_log
27
+ export_profile export_log
28
+ ].each do |model_name|
29
+ next unless LcpRuby.registry.registered?(model_name)
30
+ model = LcpRuby.registry.model_for(model_name)
31
+ model.delete_all
32
+ connection.execute("DELETE FROM sqlite_sequence WHERE name='#{model.table_name}'") if connection.adapter_name == "SQLite"
33
+ end
34
+ puts " Cleared existing seed data"
35
+
36
+ # Create default admin user (independent of authentication mode — showcase
37
+ # always provides a local admin login for demos)
38
+ if defined?(LcpRuby::User)
39
+ LcpRuby::User.find_or_create_by!(email: "admin@example.com") do |u|
40
+ u.name = "Admin User"
41
+ u.password = "password123"
42
+ u.password_confirmation = "password123"
43
+ u.lcp_role = [ "admin" ]
44
+ u.profile_data = {
45
+ theme: "dark",
46
+ locale: "en",
47
+ timezone: "America/New_York",
48
+ density: "comfortable",
49
+ border_radius: "rounded"
50
+ }
51
+ end
52
+ puts " Created admin user: admin@example.com / password123"
53
+ end
54
+
55
+ # ── Inherits-from cascade vignette ───────────────────────────────────────
56
+ # Placed early so it survives even if a later (unrelated) seed block fails.
57
+ # School registry: school_class → student → grade. Permission YAML files
58
+ # under config/lcp_ruby/permissions/showcase_{school_class,student,grade}.yml
59
+ # demonstrate the `inherits_from:` declarative cascade — see
60
+ # docs/guides/inherited-permissions.md for the walkthrough.
61
+ #
62
+ # Seed graph (teacher_id values are hypothetical user IDs — replace with
63
+ # real user IDs in your local seed if you want to demo role-switching):
64
+ #
65
+ # class_alpha (teacher_id=100) class_beta (teacher_id=200)
66
+ # ├── alice (guardian_id=300) ├── carol (guardian_id=999)
67
+ # │ ├── grade(maths,5) │ └── grade(maths,3)
68
+ # │ └── grade(english,4)
69
+ # └── bob (guardian_id=999)
70
+ # └── grade(maths,5)
71
+ #
72
+ # What the cascade produces under different roles:
73
+ # - teacher (id=100): sees class_alpha, alice, bob, alice's & bob's grades
74
+ # - teacher (id=200): sees class_beta, carol, carol's grade
75
+ # - parent (id=300): sees ONLY alice and her two grades (guardian scope)
76
+ # - admin / viewer: sees everything
77
+ SchoolClassModel = LcpRuby.registry.model_for("showcase_school_class")
78
+ StudentModel = LcpRuby.registry.model_for("showcase_student")
79
+ GradeModel = LcpRuby.registry.model_for("showcase_grade")
80
+
81
+ class_alpha = SchoolClassModel.create!(name: "Class Alpha", year: 2026, teacher_id: 100)
82
+ class_beta = SchoolClassModel.create!(name: "Class Beta", year: 2026, teacher_id: 200)
83
+
84
+ alice = StudentModel.create!(name: "Alice", guardian_id: 300, school_class_id: class_alpha.id)
85
+ bob = StudentModel.create!(name: "Bob", guardian_id: 999, school_class_id: class_alpha.id)
86
+ carol = StudentModel.create!(name: "Carol", guardian_id: 999, school_class_id: class_beta.id)
87
+
88
+ GradeModel.create!(subject: "maths", value: 5, recorded_on: Date.today - 7, student_id: alice.id)
89
+ GradeModel.create!(subject: "english", value: 4, recorded_on: Date.today - 14, student_id: alice.id)
90
+ GradeModel.create!(subject: "maths", value: 5, recorded_on: Date.today - 7, student_id: bob.id)
91
+ GradeModel.create!(subject: "maths", value: 3, recorded_on: Date.today - 7, student_id: carol.id)
92
+ puts " Created school registry vignette (2 classes, 3 students, 4 grades)"
93
+
94
+ # Phase 1: Field Types
95
+ FieldModel = LcpRuby.registry.model_for("showcase_field")
96
+
97
+ [
98
+ {
99
+ title: "Product Launch Campaign",
100
+ description: "A comprehensive marketing campaign for our new product line featuring advanced targeting and multi-channel distribution.",
101
+ count: 42,
102
+ rating_value: 4.5,
103
+ price: 1299.99,
104
+ is_active: true,
105
+ start_date: Date.today + 7,
106
+ event_time: Time.current + 3.days,
107
+ status: "active",
108
+ priority: "high",
109
+ metadata: { category: "marketing", tags: [ "launch", "q1" ] }.to_json,
110
+ external_id: SecureRandom.uuid,
111
+ email: "campaign@example.com",
112
+ phone: "+1 (555) 123-4567",
113
+ website: "https://example.com/campaigns",
114
+ brand_color: "#3498db"
115
+ },
116
+ {
117
+ title: "Q4 Budget Review",
118
+ description: "Annual budget review for Q4 fiscal year.",
119
+ count: 156,
120
+ rating_value: 3.0,
121
+ price: 50000.00,
122
+ is_active: true,
123
+ start_date: Date.today - 30,
124
+ event_time: Time.current - 2.weeks,
125
+ status: "active",
126
+ priority: "critical",
127
+ metadata: { department: "finance" }.to_json,
128
+ external_id: SecureRandom.uuid,
129
+ email: "finance@example.com",
130
+ phone: "+44 20 7946 0958",
131
+ website: "https://finance.example.com",
132
+ brand_color: "#e74c3c"
133
+ },
134
+ {
135
+ title: "Draft Proposal",
136
+ description: nil,
137
+ count: 0,
138
+ rating_value: nil,
139
+ price: nil,
140
+ is_active: false,
141
+ start_date: nil,
142
+ event_time: nil,
143
+ status: "draft",
144
+ priority: "low",
145
+ metadata: nil,
146
+ external_id: nil,
147
+ email: nil,
148
+ phone: nil,
149
+ website: nil,
150
+ brand_color: nil
151
+ },
152
+ {
153
+ title: "Archived Project Alpha",
154
+ description: "This project was completed and archived last quarter.",
155
+ count: 999,
156
+ rating_value: 5.0,
157
+ price: 100.50,
158
+ is_active: false,
159
+ start_date: Date.today - 90,
160
+ event_time: Time.current - 3.months,
161
+ status: "archived",
162
+ priority: "medium",
163
+ metadata: { archived_by: "admin", reason: "completed" }.to_json,
164
+ external_id: SecureRandom.uuid,
165
+ email: "alpha@example.com",
166
+ phone: "555-0000",
167
+ website: "http://alpha.example.com",
168
+ brand_color: "#2ecc71"
169
+ },
170
+ {
171
+ title: "Deleted Record Example",
172
+ description: "This demonstrates a deleted status record that still appears in lists.",
173
+ count: -1,
174
+ rating_value: 0.5,
175
+ price: 0.01,
176
+ is_active: false,
177
+ start_date: Date.today - 365,
178
+ event_time: Time.current - 1.year,
179
+ status: "deleted",
180
+ priority: "low",
181
+ metadata: { deleted_at: Time.current.iso8601 }.to_json,
182
+ external_id: SecureRandom.uuid,
183
+ email: "deleted@example.com",
184
+ phone: nil,
185
+ website: nil,
186
+ brand_color: "#95a5a6"
187
+ },
188
+ {
189
+ title: "International Event",
190
+ description: "Multi-language conference with speakers from 20 countries. Features include live translation, virtual attendance, and networking sessions.",
191
+ count: 2500,
192
+ rating_value: 4.8,
193
+ price: 75000.00,
194
+ is_active: true,
195
+ start_date: Date.today + 60,
196
+ event_time: Time.current + 2.months,
197
+ status: "active",
198
+ priority: "critical",
199
+ metadata: { countries: 20, languages: [ "en", "de", "fr", "es", "ja" ] }.to_json,
200
+ external_id: SecureRandom.uuid,
201
+ email: "events@globalconf.example.com",
202
+ phone: "+49 30 12345678",
203
+ website: "https://globalconf.example.com",
204
+ brand_color: "#9b59b6"
205
+ },
206
+ {
207
+ title: "Weekly Status Report",
208
+ description: "Standard weekly update.",
209
+ count: 52,
210
+ rating_value: 2.0,
211
+ price: 0.00,
212
+ is_active: true,
213
+ start_date: Date.today,
214
+ event_time: Time.current,
215
+ status: "active",
216
+ priority: "medium",
217
+ metadata: { frequency: "weekly" }.to_json,
218
+ external_id: SecureRandom.uuid,
219
+ email: "reports@example.com",
220
+ phone: "+1-800-555-0199",
221
+ website: "https://reports.example.com/weekly",
222
+ brand_color: "#f39c12"
223
+ },
224
+ {
225
+ title: "High-Priority Security Audit",
226
+ description: "Mandatory security compliance audit required by regulatory framework. All systems must pass penetration testing.",
227
+ count: 7,
228
+ rating_value: 1.0,
229
+ price: 25000.00,
230
+ is_active: true,
231
+ start_date: Date.today + 14,
232
+ event_time: Time.current + 2.weeks,
233
+ status: "draft",
234
+ priority: "critical",
235
+ metadata: { compliance: "SOC2", systems: [ "api", "web", "mobile" ] }.to_json,
236
+ external_id: SecureRandom.uuid,
237
+ email: "security@example.com",
238
+ phone: "+1 555 987 6543",
239
+ website: "https://security.example.com",
240
+ brand_color: "#c0392b"
241
+ },
242
+ {
243
+ title: "Team Building Activity",
244
+ description: "Outdoor team building event for the engineering department.",
245
+ count: 35,
246
+ rating_value: 3.5,
247
+ price: 1500.00,
248
+ is_active: true,
249
+ start_date: Date.today + 21,
250
+ event_time: Time.current + 3.weeks,
251
+ status: "active",
252
+ priority: "low",
253
+ metadata: nil,
254
+ external_id: SecureRandom.uuid,
255
+ email: "hr@example.com",
256
+ phone: "+1 (555) 456-7890",
257
+ website: nil,
258
+ brand_color: "#1abc9c"
259
+ },
260
+ {
261
+ title: "API v2 Migration",
262
+ description: "Migrate all services from API v1 to v2. This involves updating authentication, rate limiting, and response formats.",
263
+ count: 128,
264
+ rating_value: 4.0,
265
+ price: 0.00,
266
+ is_active: true,
267
+ start_date: Date.today - 7,
268
+ event_time: Time.current - 1.week,
269
+ status: "active",
270
+ priority: "high",
271
+ metadata: { version: "2.0", breaking_changes: true, affected_services: 12 }.to_json,
272
+ external_id: SecureRandom.uuid,
273
+ email: "api-team@example.com",
274
+ phone: nil,
275
+ website: "https://api.example.com/v2/docs",
276
+ brand_color: "#2c3e50"
277
+ }
278
+ ].each do |attrs|
279
+ FieldModel.create!(attrs)
280
+ end
281
+
282
+ puts " Created #{FieldModel.count} showcase_field records"
283
+
284
+ # Phase 2: Model Features
285
+ ModelModel = LcpRuby.registry.model_for("showcase_model")
286
+
287
+ [
288
+ { name: "Alpha Project", code: "alpha-001", status: "active", amount: 5000.00, max_value: 100, min_value: 10, email: "alpha@example.com", phone: "+1 555 111 2222", website: "https://alpha.example.com", tags_json: { priority: "high" }.to_json },
289
+ { name: "Beta Release", code: "beta-release", status: "active", amount: 12500.50, max_value: 200, min_value: 50, email: "beta@example.com", tags_json: { version: "2.0" }.to_json },
290
+ { name: "Draft Spec", code: "draft-spec", status: "draft", amount: nil, max_value: 50, min_value: 0, tags_json: nil },
291
+ { name: "Completed Task", code: "done-task", status: "completed", amount: 1000.00, max_value: 10, min_value: 1, email: "done@example.com", website: "https://done.example.com" },
292
+ { name: "Cancelled Order", code: "cancelled-123", status: "cancelled", amount: 750.00, max_value: 500, min_value: 100, phone: "+44 20 1234 5678" },
293
+ { name: " Whitespace Test ", code: " UPPER_case ", status: "draft", amount: nil, max_value: 999, min_value: 1 },
294
+ { name: "Edge Case Zero", code: "zero-case", status: "active", amount: 0.00, max_value: 0, min_value: -10 },
295
+ { name: "Large Values", code: "large-vals", status: "active", amount: 99999999.99, max_value: 9999, min_value: 1, email: "large@example.com", website: "http://large.example.com" }
296
+ ].each do |attrs|
297
+ ModelModel.create!(attrs)
298
+ end
299
+
300
+ puts " Created #{ModelModel.count} showcase_model records"
301
+
302
+ # Phase 3: Associations & Nested Forms
303
+ AuthorModel = LcpRuby.registry.model_for("author")
304
+ CategoryModel = LcpRuby.registry.model_for("category")
305
+ TagModel = LcpRuby.registry.model_for("tag")
306
+ ArticleModel = LcpRuby.registry.model_for("article")
307
+ CommentModel = LcpRuby.registry.model_for("comment")
308
+ ArticleTagModel = LcpRuby.registry.model_for("article_tag")
309
+
310
+ # Authors
311
+ authors = [
312
+ AuthorModel.create!(name: "Alice Chen", email: "alice@example.com", bio: "Senior technical writer with 10 years of experience."),
313
+ AuthorModel.create!(name: "Bob Martinez", email: "bob@example.com", bio: "Staff engineer and occasional blogger."),
314
+ AuthorModel.create!(name: "Carol Williams", email: "carol@example.com", bio: "Product manager passionate about documentation."),
315
+ AuthorModel.create!(name: "David Kim", email: "david@example.com", bio: "Open source contributor and educator."),
316
+ AuthorModel.create!(name: "Eva Novak", email: "eva@example.com", bio: "DevOps engineer and automation enthusiast.")
317
+ ]
318
+ puts " Created #{authors.size} authors"
319
+
320
+ # Categories (4 levels)
321
+ tech = CategoryModel.create!(name: "Technology", description: "All technology related articles.")
322
+ web = CategoryModel.create!(name: "Web Development", description: "Frontend and backend web technologies.", parent_id: tech.id)
323
+ mobile = CategoryModel.create!(name: "Mobile Development", description: "iOS and Android development.", parent_id: tech.id)
324
+ frontend = CategoryModel.create!(name: "Frontend", description: "React, Vue, Angular and more.", parent_id: web.id)
325
+ backend = CategoryModel.create!(name: "Backend", description: "APIs, databases, and server-side.", parent_id: web.id)
326
+ react_eco = CategoryModel.create!(name: "React Ecosystem", description: "React, Next.js, Remix, and related libraries.", parent_id: frontend.id)
327
+ css_styling = CategoryModel.create!(name: "CSS & Styling", description: "CSS frameworks, Tailwind, PostCSS, and design systems.", parent_id: frontend.id)
328
+
329
+ business = CategoryModel.create!(name: "Business", description: "Business strategy and management.")
330
+ startup = CategoryModel.create!(name: "Startups", description: "Startup ecosystem and entrepreneurship.", parent_id: business.id)
331
+ enterprise = CategoryModel.create!(name: "Enterprise", description: "Large-scale business solutions.", parent_id: business.id)
332
+
333
+ science = CategoryModel.create!(name: "Science", description: "Scientific discoveries and research.")
334
+ puts " Created #{CategoryModel.count} categories"
335
+
336
+ # Tags
337
+ tag_names = [
338
+ { name: "Ruby", color: "#cc342d" },
339
+ { name: "Rails", color: "#cc0000" },
340
+ { name: "JavaScript", color: "#f7df1e" },
341
+ { name: "TypeScript", color: "#3178c6" },
342
+ { name: "React", color: "#61dafb" },
343
+ { name: "Vue", color: "#42b883" },
344
+ { name: "Python", color: "#3776ab" },
345
+ { name: "Docker", color: "#2496ed" },
346
+ { name: "AWS", color: "#ff9900" },
347
+ { name: "DevOps", color: "#0db7ed" },
348
+ { name: "Testing", color: "#8bc34a" },
349
+ { name: "Performance", color: "#ff5722" },
350
+ { name: "Security", color: "#f44336" },
351
+ { name: "Tutorial", color: "#9c27b0" },
352
+ { name: "Best Practices", color: "#607d8b" }
353
+ ]
354
+ tags = tag_names.map { |t| TagModel.create!(t) }
355
+ puts " Created #{tags.size} tags"
356
+
357
+ # Articles with comments and tags
358
+ articles_data = [
359
+ { title: "Getting Started with Ruby on Rails", slug: "getting-started-with-ruby-on-rails", body: "A comprehensive guide to building your first Rails application.", status: "published", category: frontend, author: authors[0], tags: [ 0, 1, 13 ], comments: [ [ "Great article!", "Reader1" ], [ "Very helpful", "Reader2" ] ] },
360
+ { title: "Advanced React Patterns", slug: "advanced-react-patterns", body: "Exploring compound components, render props, and hooks.", status: "published", category: frontend, author: authors[1], tags: [ 2, 4, 14 ], comments: [ [ "Love the hooks examples", "DevFan" ] ] },
361
+ { title: "Building REST APIs with Rails", slug: "building-rest-apis-with-rails", body: "Best practices for designing RESTful APIs.", status: "published", category: backend, author: authors[0], tags: [ 0, 1, 14 ], comments: [ [ "What about GraphQL?", "APIUser" ], [ "Solid patterns", "BackendDev" ], [ "More examples please", "Newbie" ] ] },
362
+ { title: "Docker for Development", slug: "docker-for-development", body: "Setting up Docker for local development environments.", status: "published", category: backend, author: authors[4], tags: [ 7, 9 ], comments: [] },
363
+ { title: "Mobile-First Design Principles", slug: "mobile-first-design-principles", body: "Why mobile-first approach matters for modern applications.", status: "published", category: mobile, author: authors[2], tags: [ 14 ], comments: [ [ "Responsive > Mobile-first", "WebDev" ] ] },
364
+ { title: "TypeScript Migration Guide", slug: "typescript-migration-guide", body: "Step by step guide to migrating JavaScript to TypeScript.", status: "draft", category: frontend, author: authors[1], tags: [ 2, 3 ], comments: [] },
365
+ { title: "AWS Lambda Best Practices", slug: "aws-lambda-best-practices", body: "Optimizing serverless functions for production.", status: "published", category: backend, author: authors[4], tags: [ 8, 9, 11 ], comments: [ [ "Cold starts are still an issue", "CloudUser" ] ] },
366
+ { title: "Vue 3 Composition API", slug: "vue-3-composition-api", body: "Understanding the new composition API in Vue 3.", status: "published", category: frontend, author: authors[3], tags: [ 2, 5 ], comments: [ [ "Finally!", "VueFan" ], [ "Great comparison with Options API", "Dev123" ] ] },
367
+ { title: "Security Best Practices for Web Apps", slug: "security-best-practices-for-web-apps", body: "OWASP top 10 and how to protect your application.", status: "draft", category: web, author: authors[4], tags: [ 12, 14 ], comments: [] },
368
+ { title: "Python for Data Science", slug: "python-for-data-science", body: "Introduction to pandas, numpy, and matplotlib.", status: "published", category: science, author: authors[3], tags: [ 6, 13 ], comments: [ [ "Can you cover scikit-learn?", "DataNerd" ] ] },
369
+ { title: "Startup Metrics That Matter", slug: "startup-metrics-that-matter", body: "Key performance indicators for early-stage startups.", status: "published", category: startup, author: authors[2], tags: [ 14 ], comments: [] },
370
+ { title: "Enterprise Architecture Patterns", slug: "enterprise-architecture-patterns", body: "Scaling applications for enterprise use.", status: "draft", category: enterprise, author: authors[0], tags: [ 14, 11 ], comments: [] },
371
+ { title: "Testing Rails Applications", slug: "testing-rails-applications", body: "RSpec, Capybara, and factory_bot patterns.", status: "published", category: backend, author: authors[0], tags: [ 0, 1, 10 ], comments: [ [ "What about minitest?", "Tester" ], [ "Factory bot is essential", "QAEngineer" ] ] },
372
+ { title: "Performance Optimization in React", slug: "performance-optimization-in-react", body: "Memoization, code splitting, and lazy loading.", status: "published", category: frontend, author: authors[1], tags: [ 2, 4, 11 ], comments: [ [ "useMemo vs useCallback?", "ReactDev" ] ] },
373
+ { title: "DevOps Culture and Practices", slug: "devops-culture-and-practices", body: "Building a DevOps culture in your organization.", status: "archived", category: tech, author: authors[4], tags: [ 7, 8, 9 ], comments: [] },
374
+ { title: "Server Components in Next.js", slug: "server-components-in-nextjs", body: "Understanding React Server Components and their impact on data fetching and rendering strategies.", status: "published", category: react_eco, author: authors[1], tags: [ 2, 4, 11 ], comments: [ [ "Game changer for SSR!", "NextFan" ] ] },
375
+ { title: "State Management with Zustand", slug: "state-management-with-zustand", body: "A lightweight alternative to Redux for React state management.", status: "published", category: react_eco, author: authors[3], tags: [ 2, 4, 14 ], comments: [] },
376
+ { title: "Tailwind CSS Best Practices", slug: "tailwind-css-best-practices", body: "Utility-first CSS patterns, custom themes, and component extraction strategies.", status: "published", category: css_styling, author: authors[2], tags: [ 14 ], comments: [ [ "Finally moved from SCSS to Tailwind", "CSSFan" ] ] }
377
+ ]
378
+
379
+ articles_data.each do |data|
380
+ article = ArticleModel.create!(
381
+ title: data[:title],
382
+ slug: data[:slug],
383
+ body: data[:body],
384
+ status: data[:status],
385
+ word_count: data[:body].split.size,
386
+ category_id: data[:category].id,
387
+ author_id: data[:author].id
388
+ )
389
+ data[:tags].each { |tag_idx| ArticleTagModel.create!(article_id: article.id, tag_id: tags[tag_idx].id) }
390
+ data[:comments].each_with_index do |(body, author_name), idx|
391
+ CommentModel.create!(article_id: article.id, body: body, author_name: author_name, position: idx + 1)
392
+ end
393
+ end
394
+
395
+ puts " Created #{ArticleModel.count} articles, #{CommentModel.count} comments, #{ArticleTagModel.count} article-tag links"
396
+
397
+ # Extra comments on first article for pagination demo (>10 to exceed per_page 10 on article_comments_zone)
398
+ first_article = ArticleModel.first
399
+ (1..13).each do |i|
400
+ CommentModel.create!(
401
+ article_id: first_article.id,
402
+ body: "Discussion point #{i}: Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
403
+ author_name: "Reader#{i + 2}",
404
+ position: i + 2
405
+ )
406
+ end
407
+ puts " Added #{13} extra comments to '#{first_article.title}' for pagination demo (#{CommentModel.where(article_id: first_article.id).count} total)"
408
+
409
+ # Phase 4: Form Features
410
+ FormModel = LcpRuby.registry.model_for("showcase_form")
411
+
412
+ [
413
+ { name: "Simple Form", form_type: "simple", priority: 25, satisfaction: 3, is_premium: false, reason: nil },
414
+ { name: "Advanced Config", form_type: "advanced", priority: 75, satisfaction: 4, is_premium: true, reason: "VIP customer", advanced_field_1: "Custom value", advanced_field_2: "Extra data" },
415
+ { name: "Special Request", form_type: "special", priority: 100, satisfaction: 5, is_premium: true, reason: "Partnership", rejection_reason: "Pending approval" },
416
+ { name: "Basic Entry", form_type: "simple", priority: 50, satisfaction: 2, is_premium: false },
417
+ { name: "Premium Advanced", form_type: "advanced", priority: 90, satisfaction: 5, is_premium: true, reason: "Enterprise tier", advanced_field_1: "Enterprise", config_data: { feature_flags: [ "beta", "api_v2" ] }.to_json }
418
+ ].each do |attrs|
419
+ FormModel.create!(attrs)
420
+ end
421
+ puts " Created #{FormModel.count} showcase_form records"
422
+
423
+ # Phase 5: Selects
424
+ DeptModel = LcpRuby.registry.model_for("department")
425
+ SkillModel = LcpRuby.registry.model_for("skill")
426
+ EmpModel = LcpRuby.registry.model_for("employee")
427
+ EmpSkillModel = LcpRuby.registry.model_for("employee_skill")
428
+ ProjModel = LcpRuby.registry.model_for("project")
429
+
430
+ # Departments (3 levels)
431
+ eng = DeptModel.create!(name: "Engineering", code: "eng")
432
+ fe = DeptModel.create!(name: "Frontend", code: "eng-fe", parent_id: eng.id)
433
+ be = DeptModel.create!(name: "Backend", code: "eng-be", parent_id: eng.id)
434
+ devops = DeptModel.create!(name: "DevOps", code: "eng-devops", parent_id: eng.id)
435
+ react_team = DeptModel.create!(name: "React Team", code: "eng-fe-react", parent_id: fe.id)
436
+ api_team = DeptModel.create!(name: "API Team", code: "eng-be-api", parent_id: be.id)
437
+
438
+ design = DeptModel.create!(name: "Design", code: "design")
439
+ ux = DeptModel.create!(name: "UX Design", code: "design-ux", parent_id: design.id)
440
+ ui = DeptModel.create!(name: "UI Design", code: "design-ui", parent_id: design.id)
441
+
442
+ mgmt = DeptModel.create!(name: "Management", code: "mgmt")
443
+ puts " Created #{DeptModel.count} departments"
444
+
445
+ # Skills
446
+ skills = [
447
+ { name: "Ruby", category: "technical" },
448
+ { name: "JavaScript", category: "technical" },
449
+ { name: "Python", category: "technical" },
450
+ { name: "React", category: "technical" },
451
+ { name: "Docker", category: "technical" },
452
+ { name: "AWS", category: "technical" },
453
+ { name: "Communication", category: "soft" },
454
+ { name: "Leadership", category: "management" },
455
+ { name: "Project Management", category: "management" },
456
+ { name: "English", category: "language" },
457
+ { name: "German", category: "language" },
458
+ { name: "Testing", category: "technical" },
459
+ { name: "CSS", category: "technical" },
460
+ { name: "SQL", category: "technical" },
461
+ { name: "Teamwork", category: "soft" }
462
+ ].map { |s| SkillModel.create!(s) }
463
+ puts " Created #{skills.size} skills"
464
+
465
+ # Employees
466
+ employees = [
467
+ EmpModel.create!(name: "Jane Smith", email: "jane@example.com", role: "manager", status: "active", department_id: eng.id),
468
+ EmpModel.create!(name: "John Doe", email: "john@example.com", role: "developer", status: "active", department_id: fe.id),
469
+ EmpModel.create!(name: "Alice Brown", email: "alice.b@example.com", role: "developer", status: "active", department_id: be.id),
470
+ EmpModel.create!(name: "Bob Wilson", email: "bob.w@example.com", role: "developer", status: "active", department_id: react_team.id),
471
+ EmpModel.create!(name: "Carol Davis", email: "carol@example.com", role: "designer", status: "active", department_id: ux.id),
472
+ EmpModel.create!(name: "Dan Lee", email: "dan@example.com", role: "admin", status: "active", department_id: mgmt.id),
473
+ EmpModel.create!(name: "Eva Green", email: "eva.g@example.com", role: "developer", status: "on_leave", department_id: devops.id),
474
+ EmpModel.create!(name: "Frank White", email: "frank@example.com", role: "intern", status: "active", department_id: api_team.id),
475
+ EmpModel.create!(name: "Grace Chen", email: "grace@example.com", role: "developer", status: "active", department_id: be.id),
476
+ EmpModel.create!(name: "Henry Kim", email: "henry@example.com", role: "designer", status: "active", department_id: ui.id),
477
+ EmpModel.create!(name: "Iris Taylor", email: "iris@example.com", role: "developer", status: "terminated", department_id: fe.id),
478
+ EmpModel.create!(name: "Jack Brown", email: "jack@example.com", role: "developer", status: "archived", department_id: devops.id)
479
+ ]
480
+ # Set mentors
481
+ employees[1].update!(mentor_id: employees[0].id)
482
+ employees[3].update!(mentor_id: employees[1].id)
483
+ employees[7].update!(mentor_id: employees[2].id)
484
+
485
+ # Link Dan Lee (admin-role employee) to the admin user so
486
+ # /showcase/my-employee-profile/me resolves for the admin login.
487
+ if LcpRuby.configuration.authentication == :built_in
488
+ admin_user = LcpRuby::User.find_by(email: "admin@example.com")
489
+ employees[5].update!(user_id: admin_user.id) if admin_user
490
+ end
491
+
492
+ puts " Created #{employees.size} employees"
493
+
494
+ # Employee skills
495
+ [ [ 0, [ 0, 6, 7, 8, 9 ] ], [ 1, [ 1, 3, 12, 9 ] ], [ 2, [ 0, 1, 13, 11 ] ], [ 3, [ 1, 3, 9 ] ], [ 4, [ 12, 6, 14 ] ], [ 5, [ 7, 8, 6, 9 ] ], [ 6, [ 4, 5, 0 ] ], [ 7, [ 0, 13, 11 ] ], [ 8, [ 0, 2, 13 ] ], [ 9, [ 12, 6 ] ] ].each do |emp_idx, skill_idxs|
496
+ skill_idxs.each { |si| EmpSkillModel.create!(employee_id: employees[emp_idx].id, skill_id: skills[si].id) }
497
+ end
498
+ puts " Created #{EmpSkillModel.count} employee-skill links"
499
+
500
+ # Employee profiles (has_one)
501
+ if LcpRuby.registry.registered?("employee_profile")
502
+ ProfileModel = LcpRuby.registry.model_for("employee_profile")
503
+ [
504
+ { employee_id: employees[0].id, hire_date: "2019-03-15", salary: 95_000, date_of_birth: "1985-06-20", national_id: "850620/1234", bank_account: "CZ6508000000192000145399", phone: "+420-602-123-456", address: "Vinohradska 12, Prague 2" },
505
+ { employee_id: employees[1].id, hire_date: "2020-07-01", salary: 82_000, date_of_birth: "1990-11-05", national_id: "901105/5678", bank_account: "CZ6508000000192000145400", phone: "+420-603-234-567", address: "Karlova 8, Prague 1" },
506
+ { employee_id: employees[2].id, hire_date: "2021-01-10", salary: 88_000, date_of_birth: "1988-02-14", national_id: "880214/9012", phone: "+420-604-345-678" },
507
+ { employee_id: employees[3].id, hire_date: "2022-04-01", salary: 75_000, date_of_birth: "1995-08-30" },
508
+ { employee_id: employees[4].id, hire_date: "2020-09-15", salary: 78_000, date_of_birth: "1992-12-01", national_id: "921201/3456", phone: "+420-605-456-789", address: "Nerudova 3, Prague 1" },
509
+ { employee_id: employees[5].id, hire_date: "2018-01-02", salary: 120_000, date_of_birth: "1980-04-10", national_id: "800410/7890", bank_account: "CZ6508000000192000145401", phone: "+420-601-567-890", address: "Celetna 5, Prague 1", notes: "Company founder" },
510
+ { employee_id: employees[6].id, hire_date: "2021-06-01", salary: 90_000, date_of_birth: "1993-07-22" },
511
+ { employee_id: employees[8].id, hire_date: "2022-11-15", salary: 85_000, date_of_birth: "1991-09-18", national_id: "910918/2345", phone: "+420-607-789-012" }
512
+ ].each { |attrs| ProfileModel.create!(attrs) }
513
+ puts " Created #{ProfileModel.count} employee profiles"
514
+ end
515
+
516
+ # Employee emergency contacts (has_one)
517
+ if LcpRuby.registry.registered?("employee_emergency_contact")
518
+ EmergencyModel = LcpRuby.registry.model_for("employee_emergency_contact")
519
+ [
520
+ { employee_id: employees[0].id, contact_name: "Peter Smith", relationship: "spouse", phone: "+420-608-111-222", email: "peter.smith@email.cz" },
521
+ { employee_id: employees[1].id, contact_name: "Mary Doe", relationship: "parent", phone: "+420-609-222-333" },
522
+ { employee_id: employees[2].id, contact_name: "Tom Brown", relationship: "sibling", phone: "+420-610-333-444", email: "tom.b@email.cz" },
523
+ { employee_id: employees[4].id, contact_name: "Lisa Davis", relationship: "spouse", phone: "+420-611-444-555" },
524
+ { employee_id: employees[5].id, contact_name: "Sarah Lee", relationship: "spouse", phone: "+420-612-555-666", email: "sarah.lee@email.cz" },
525
+ { employee_id: employees[8].id, contact_name: "James Chen", relationship: "parent", phone: "+420-613-666-777" }
526
+ ].each { |attrs| EmergencyModel.create!(attrs) }
527
+ puts " Created #{EmergencyModel.count} employee emergency contacts"
528
+ end
529
+
530
+ # Projects
531
+ [
532
+ { name: "Website Redesign", status: "active", department_id: fe.id, lead_id: employees[1].id },
533
+ { name: "API v3", status: "active", department_id: be.id, lead_id: employees[2].id },
534
+ { name: "Cloud Migration", status: "active", department_id: devops.id, lead_id: employees[6].id },
535
+ { name: "Brand Refresh", status: "completed", department_id: design.id, lead_id: employees[4].id },
536
+ { name: "Internal Tools", status: "active", department_id: eng.id, lead_id: employees[0].id }
537
+ ].each { |p| ProjModel.create!(p) }
538
+ puts " Created #{ProjModel.count} projects"
539
+
540
+ # Phase 5b: Custom Fields
541
+ # Create custom field definitions on employees (all types) and projects (practical subset)
542
+ CfdModel = LcpRuby.registry.model_for("custom_field_definition")
543
+
544
+ # -- Employee custom fields: one of every type for showcase --
545
+ employee_cfds = [
546
+ # String: nickname with length constraints
547
+ {
548
+ target_model: "employee", field_name: "nickname", custom_type: "string",
549
+ label: "Nickname", section: "Personal Info", position: 0,
550
+ active: true, required: false, placeholder: "e.g., Johnny",
551
+ hint: "Short display name used in casual contexts",
552
+ min_length: 2, max_length: 30,
553
+ show_in_table: true, sortable: true, searchable: true
554
+ },
555
+ # Text: bio with longer content
556
+ {
557
+ target_model: "employee", field_name: "bio", custom_type: "text",
558
+ label: "Biography", section: "Personal Info", position: 1,
559
+ active: true, required: false, placeholder: "Brief biography...",
560
+ max_length: 1000, show_in_table: false, show_in_form: true, show_in_show: true
561
+ },
562
+ # Integer: years of experience with min/max
563
+ {
564
+ target_model: "employee", field_name: "years_experience", custom_type: "integer",
565
+ label: "Years of Experience", section: "Professional", position: 0,
566
+ active: true, required: false, default_value: "0",
567
+ hint: "Total years of professional experience",
568
+ min_value: 0, max_value: 50,
569
+ show_in_table: true, sortable: true
570
+ },
571
+ # Float: performance score
572
+ {
573
+ target_model: "employee", field_name: "performance_score", custom_type: "float",
574
+ label: "Performance Score", section: "Professional", position: 1,
575
+ active: true, required: false,
576
+ min_value: 0.0, max_value: 10.0,
577
+ show_in_table: true, sortable: true
578
+ },
579
+ # Decimal: hourly rate with precision
580
+ {
581
+ target_model: "employee", field_name: "hourly_rate", custom_type: "decimal",
582
+ label: "Hourly Rate (USD)", section: "Compensation", position: 0,
583
+ active: true, required: false,
584
+ hint: "Base hourly rate before taxes and deductions",
585
+ min_value: 0, precision: 2,
586
+ show_in_table: true, sortable: true
587
+ },
588
+ # Boolean: remote worker flag
589
+ {
590
+ target_model: "employee", field_name: "remote_worker", custom_type: "boolean",
591
+ label: "Remote Worker", section: "Work Arrangement", position: 0,
592
+ active: true, required: false, default_value: "false",
593
+ show_in_table: true
594
+ },
595
+ # Date: start date
596
+ {
597
+ target_model: "employee", field_name: "start_date", custom_type: "date",
598
+ label: "Start Date", section: "Employment", position: 0,
599
+ active: true, required: false,
600
+ show_in_table: true, sortable: true
601
+ },
602
+ # Datetime: last review
603
+ {
604
+ target_model: "employee", field_name: "last_review_at", custom_type: "datetime",
605
+ label: "Last Performance Review", section: "Employment", position: 1,
606
+ active: true, required: false,
607
+ show_in_table: false, show_in_show: true
608
+ },
609
+ # Enum: t-shirt size with custom values
610
+ {
611
+ target_model: "employee", field_name: "tshirt_size", custom_type: "enum",
612
+ label: "T-Shirt Size", section: "Personal Info", position: 2,
613
+ active: true, required: false, default_value: "M",
614
+ hint: "For company swag orders",
615
+ enum_values: [
616
+ { "value" => "XS", "label" => "Extra Small" },
617
+ { "value" => "S", "label" => "Small" },
618
+ { "value" => "M", "label" => "Medium" },
619
+ { "value" => "L", "label" => "Large" },
620
+ { "value" => "XL", "label" => "Extra Large" },
621
+ { "value" => "XXL", "label" => "XXL" }
622
+ ],
623
+ show_in_table: true
624
+ },
625
+ # Inactive field: demonstrates that inactive fields are hidden
626
+ {
627
+ target_model: "employee", field_name: "legacy_id", custom_type: "string",
628
+ label: "Legacy System ID", section: "System", position: 99,
629
+ active: false, required: false,
630
+ show_in_table: false, show_in_form: false, show_in_show: false
631
+ }
632
+ ]
633
+
634
+ employee_cfds.each { |attrs| CfdModel.create!(attrs) }
635
+ puts " Created #{employee_cfds.size} employee custom field definitions"
636
+
637
+ # -- Project custom fields: practical business fields --
638
+ project_cfds = [
639
+ # Enum: priority
640
+ {
641
+ target_model: "project", field_name: "priority", custom_type: "enum",
642
+ label: "Priority", section: "Project Details", position: 0,
643
+ active: true, required: true,
644
+ hint: "Determines task queue ordering",
645
+ enum_values: %w[low medium high critical],
646
+ default_value: "medium",
647
+ show_in_table: true, sortable: true
648
+ },
649
+ # Decimal: budget
650
+ {
651
+ target_model: "project", field_name: "budget", custom_type: "decimal",
652
+ label: "Budget (USD)", section: "Financials", position: 0,
653
+ active: true, required: false,
654
+ hint: "Approved budget in USD, excluding contingency",
655
+ min_value: 0, precision: 2,
656
+ show_in_table: true, sortable: true
657
+ },
658
+ # Date: deadline
659
+ {
660
+ target_model: "project", field_name: "deadline", custom_type: "date",
661
+ label: "Deadline", section: "Project Details", position: 1,
662
+ active: true, required: false,
663
+ show_in_table: true, sortable: true
664
+ },
665
+ # Integer: team size
666
+ {
667
+ target_model: "project", field_name: "team_size", custom_type: "integer",
668
+ label: "Team Size", section: "Project Details", position: 2,
669
+ active: true, required: false,
670
+ min_value: 1, max_value: 500,
671
+ show_in_table: true
672
+ },
673
+ # Text: notes
674
+ {
675
+ target_model: "project", field_name: "notes", custom_type: "text",
676
+ label: "Project Notes", section: "Documentation", position: 0,
677
+ active: true, required: false, placeholder: "Additional project notes...",
678
+ searchable: true
679
+ },
680
+ # Boolean: is_public
681
+ {
682
+ target_model: "project", field_name: "is_public", custom_type: "boolean",
683
+ label: "Public Project", section: "Visibility", position: 0,
684
+ active: true, required: false, default_value: "false",
685
+ show_in_table: true
686
+ },
687
+ # String: client name with search
688
+ {
689
+ target_model: "project", field_name: "client_name", custom_type: "string",
690
+ label: "Client Name", section: "Financials", position: 1,
691
+ active: true, required: false,
692
+ max_length: 100, searchable: true,
693
+ show_in_table: true
694
+ }
695
+ ]
696
+
697
+ project_cfds.each { |attrs| CfdModel.create!(attrs) }
698
+ puts " Created #{project_cfds.size} project custom field definitions"
699
+
700
+ # Force-refresh custom field accessors after creating definitions
701
+ LcpRuby::CustomFields::Registry.reload!
702
+ employee_model_class = LcpRuby.registry.model_for("employee")
703
+ project_model_class = LcpRuby.registry.model_for("project")
704
+ employee_model_class.apply_custom_field_accessors!
705
+ project_model_class.apply_custom_field_accessors!
706
+
707
+ # Fill some employees with custom field data.
708
+ # Order matches the `employees` array created in Phase 5 above.
709
+ emp_custom_data = [
710
+ # employees[0]: Jane Smith (manager)
711
+ { nickname: "JaneS", bio: "Engineering lead with a passion for clean architecture.", years_experience: 12,
712
+ performance_score: 9.2, hourly_rate: 85.00, remote_worker: false,
713
+ start_date: "2018-03-15", last_review_at: "2025-12-01 10:00:00", tshirt_size: "M" },
714
+ # employees[1]: John Doe (developer)
715
+ { nickname: "JD", years_experience: 5, performance_score: 7.8, hourly_rate: 55.00,
716
+ remote_worker: true, start_date: "2021-06-01", tshirt_size: "L" },
717
+ # employees[2]: Alice Brown
718
+ { nickname: "Ali", years_experience: 8, performance_score: 8.5, hourly_rate: 70.00,
719
+ remote_worker: false, start_date: "2019-09-20", tshirt_size: "S" },
720
+ # employees[3]: Bob Wilson
721
+ { years_experience: 3, performance_score: 6.5, hourly_rate: 45.00,
722
+ remote_worker: true, start_date: "2023-01-10", tshirt_size: "XL" },
723
+ # employees[4]: Carol Davis (designer)
724
+ { nickname: "Cee", years_experience: 6, performance_score: 8.0, hourly_rate: 60.00,
725
+ remote_worker: false, start_date: "2020-04-01", tshirt_size: "XS" },
726
+ # employees[5]: Dan Lee (admin)
727
+ { nickname: "DanTheMan", bio: "Operations and people management.", years_experience: 15,
728
+ performance_score: 8.8, hourly_rate: 95.00, remote_worker: false,
729
+ start_date: "2015-01-01", last_review_at: "2025-11-15 14:30:00", tshirt_size: "L" }
730
+ ]
731
+
732
+ employees.first(emp_custom_data.size).each_with_index do |emp, idx|
733
+ emp.assign_attributes(emp_custom_data[idx])
734
+ emp.save!
735
+ end
736
+ puts " Filled custom field data for #{emp_custom_data.size} employees"
737
+
738
+ # Fill projects with custom field data.
739
+ # ProjModel defined in Phase 5 above. Order matches creation order.
740
+ proj_records = ProjModel.all.to_a
741
+ proj_custom_data = [
742
+ # proj_records[0]: Website Redesign
743
+ { priority: "high", budget: 50000.00, deadline: (Date.today + 90).to_s, team_size: 8,
744
+ notes: "Major redesign of the public-facing website.", is_public: true, client_name: "Internal" },
745
+ # proj_records[1]: API v3
746
+ { priority: "critical", budget: 120000.00, deadline: (Date.today + 180).to_s, team_size: 12,
747
+ notes: "Full API rewrite with GraphQL support.", is_public: false },
748
+ # proj_records[2]: Cloud Migration
749
+ { priority: "high", budget: 200000.00, deadline: (Date.today + 365).to_s, team_size: 6,
750
+ is_public: false, client_name: "Ops Team" },
751
+ # proj_records[3]: Brand Refresh
752
+ { priority: "low", budget: 15000.00, team_size: 3, is_public: true, client_name: "Marketing" },
753
+ # proj_records[4]: Internal Tools
754
+ { priority: "medium", budget: 30000.00, deadline: (Date.today + 60).to_s, team_size: 4,
755
+ notes: "Build internal dashboards and admin tools.", is_public: false }
756
+ ]
757
+
758
+ proj_records.first(proj_custom_data.size).each_with_index do |proj, idx|
759
+ proj.assign_attributes(proj_custom_data[idx])
760
+ proj.save!
761
+ end
762
+ puts " Filled custom field data for #{proj_custom_data.size} projects"
763
+
764
+ # Phase 6: Attachments (just create empty records — files need manual upload)
765
+ AttachModel = LcpRuby.registry.model_for("showcase_attachment")
766
+ AttachModel.create!(title: "Sample Record (upload files via edit)")
767
+ puts " Created #{AttachModel.count} showcase_attachment records"
768
+
769
+ # Phase 7: Permissions
770
+ PermModel = LcpRuby.registry.model_for("showcase_permission")
771
+
772
+ [
773
+ { title: "Open Task", status: "open", owner_id: 1, priority: "medium", confidential: false, public_notes: "Anyone can see this.", internal_notes: "Admin-only notes." },
774
+ { title: "In Progress Item", status: "in_progress", owner_id: 1, assignee_id: 2, priority: "high", confidential: false, public_notes: "Being worked on." },
775
+ { title: "Locked Record", status: "locked", owner_id: 1, priority: "critical", confidential: true, public_notes: "This record is locked.", internal_notes: "Only admin can edit." },
776
+ { title: "Archived Entry", status: "archived", owner_id: 2, priority: "low", confidential: false, public_notes: "Historical record.", internal_notes: "Cannot be destroyed." },
777
+ { title: "Confidential Report", status: "open", owner_id: 1, priority: "high", confidential: true, internal_notes: "Top secret information.", public_notes: "Classified." }
778
+ ].each { |attrs| PermModel.create!(attrs) }
779
+ puts " Created #{PermModel.count} showcase_permission records"
780
+
781
+ # Phase 8: Roles
782
+ RoleModel = LcpRuby.registry.model_for("role")
783
+
784
+ [
785
+ { name: "admin", label: "Administrator", description: "Full system access. Can manage all records, roles, and settings.", active: true, position: 0 },
786
+ { name: "editor", label: "Editor", description: "Can create and edit records but cannot delete or manage roles.", active: true, position: 10 },
787
+ { name: "viewer", label: "Viewer", description: "Read-only access to all public data.", active: true, position: 20 },
788
+ { name: "owner", label: "Owner", description: "Full access scoped to own records only.", active: true, position: 30 },
789
+ # Used by the inherits_from cascade vignette (school registry).
790
+ { name: "teacher", label: "Teacher", description: "School-registry teacher: sees own classes (teacher_id = current_user.id) and inherits visibility down to students and grades.", active: true, position: 40 },
791
+ { name: "parent", label: "Parent", description: "School-registry guardian: sees own children (guardian_id = current_user.id) and inherits visibility to their grades.", active: true, position: 41 },
792
+ { name: "deprecated_role", label: "Deprecated Role", description: "This role is no longer active. Kept for audit history.", active: false, position: 99 }
793
+ ].each { |attrs| RoleModel.create!(attrs) }
794
+ puts " Created #{RoleModel.count} role records"
795
+
796
+ # Phase 8b: Permission Configs (DB-backed permissions)
797
+ PermConfigModel = LcpRuby.registry.model_for("permission_config")
798
+
799
+ [
800
+ {
801
+ target_model: "article",
802
+ definition: {
803
+ roles: {
804
+ admin: {
805
+ crud: %w[index show create update destroy],
806
+ fields: { readable: "all", writable: "all" },
807
+ actions: "all",
808
+ scope: "all",
809
+ presenters: "all"
810
+ },
811
+ editor: {
812
+ crud: %w[index show create update],
813
+ fields: { readable: "all", writable: %w[title body category_id author_id published featured] },
814
+ actions: { allowed: [] },
815
+ scope: "all",
816
+ presenters: "all"
817
+ },
818
+ viewer: {
819
+ crud: %w[index show],
820
+ fields: { readable: %w[title body category_id author_id published], writable: [] },
821
+ actions: { allowed: [] },
822
+ scope: "all",
823
+ presenters: "all"
824
+ }
825
+ },
826
+ default_role: "viewer"
827
+ }.to_json,
828
+ active: true,
829
+ notes: "DB-backed permissions for articles. Overrides the YAML _default. Editors can publish but not delete."
830
+ },
831
+ {
832
+ target_model: "department",
833
+ definition: {
834
+ roles: {
835
+ admin: {
836
+ crud: %w[index show create update destroy],
837
+ fields: { readable: "all", writable: "all" },
838
+ actions: "all",
839
+ scope: "all",
840
+ presenters: "all"
841
+ },
842
+ editor: {
843
+ crud: %w[index show update],
844
+ fields: { readable: "all", writable: %w[name description] },
845
+ actions: { allowed: [] },
846
+ scope: "all"
847
+ },
848
+ viewer: {
849
+ crud: %w[index show],
850
+ fields: { readable: "all", writable: [] },
851
+ actions: { allowed: [] },
852
+ scope: "all"
853
+ }
854
+ },
855
+ default_role: "viewer"
856
+ }.to_json,
857
+ active: true,
858
+ notes: "DB-backed permissions for departments. Editors can update but not create or delete."
859
+ },
860
+ {
861
+ target_model: "employee",
862
+ definition: {
863
+ roles: {
864
+ admin: {
865
+ crud: %w[index show create update destroy],
866
+ fields: { readable: "all", writable: "all" },
867
+ actions: "all",
868
+ scope: "all",
869
+ presenters: "all"
870
+ },
871
+ editor: {
872
+ crud: %w[index show create update],
873
+ fields: { readable: "all", writable: %w[name email role status department_id mentor_id nickname bio years_experience] },
874
+ actions: { allowed: [] },
875
+ scope: "all",
876
+ presenters: "all"
877
+ },
878
+ viewer: {
879
+ crud: %w[index show],
880
+ fields: { readable: %w[name email role status department_id nickname bio years_experience tshirt_size], writable: [] },
881
+ actions: { allowed: [] },
882
+ scope: "all",
883
+ presenters: "all"
884
+ }
885
+ },
886
+ default_role: "viewer",
887
+ field_overrides: {
888
+ hourly_rate: { readable_by: %w[admin], writable_by: %w[admin] },
889
+ performance_score: { readable_by: %w[admin editor], writable_by: %w[admin] }
890
+ }
891
+ }.to_json,
892
+ active: true,
893
+ notes: "Per-field custom field permissions demo. Viewers see basic custom fields but not salary/performance. field_overrides restrict hourly_rate to admin only."
894
+ },
895
+ {
896
+ target_model: "user",
897
+ definition: {
898
+ roles: {
899
+ admin: {
900
+ crud: %w[index show update],
901
+ fields: { readable: "all", writable: "all" },
902
+ scope: "all",
903
+ presenters: "all"
904
+ },
905
+ editor: {
906
+ crud: %w[show update],
907
+ fields: {
908
+ readable: %w[name email theme locale timezone density border_radius],
909
+ writable: %w[name email theme locale timezone density border_radius]
910
+ },
911
+ scope: { type: "field_match", field: "id", value: "current_user.id" },
912
+ presenters: %w[my_settings]
913
+ },
914
+ viewer: {
915
+ crud: %w[show update],
916
+ fields: {
917
+ readable: %w[name email theme locale timezone density border_radius],
918
+ writable: %w[name email theme locale timezone density border_radius]
919
+ },
920
+ scope: { type: "field_match", field: "id", value: "current_user.id" },
921
+ presenters: %w[my_settings]
922
+ }
923
+ },
924
+ default_role: "viewer"
925
+ }.to_json,
926
+ active: true,
927
+ notes: "Self-service user profile — non-admin roles can only see/edit their own record via the my_settings presenter. Users presenter remains admin-only."
928
+ },
929
+ {
930
+ target_model: "inactive_demo",
931
+ definition: {
932
+ roles: {
933
+ admin: {
934
+ crud: %w[index show create update destroy],
935
+ fields: { readable: "all", writable: "all" },
936
+ actions: "all",
937
+ scope: "all"
938
+ }
939
+ },
940
+ default_role: "admin"
941
+ }.to_json,
942
+ active: false,
943
+ notes: "Inactive permission config — ignored by the resolver. Demonstrates that inactive records don't affect authorization."
944
+ }
945
+ ].each { |attrs| PermConfigModel.create!(attrs) }
946
+ puts " Created #{PermConfigModel.count} permission config records"
947
+
948
+ # Phase 8c: Groups
949
+ GroupModel = LcpRuby.registry.model_for("group")
950
+ MembershipModel = LcpRuby.registry.model_for("group_membership")
951
+ RoleMappingModel = LcpRuby.registry.model_for("group_role_mapping")
952
+
953
+ engineering = GroupModel.create!(
954
+ name: "engineering", label: "Engineering Team",
955
+ description: "Software engineers and technical staff responsible for product development.",
956
+ source: "manual", active: true
957
+ )
958
+ design = GroupModel.create!(
959
+ name: "design", label: "Design Team",
960
+ description: "UX/UI designers and product design professionals.",
961
+ source: "manual", active: true
962
+ )
963
+ management = GroupModel.create!(
964
+ name: "management", label: "Management",
965
+ description: "Department heads and project managers with administrative access.",
966
+ source: "manual", active: true
967
+ )
968
+ contractors = GroupModel.create!(
969
+ name: "contractors", label: "External Contractors",
970
+ description: "External contractors with limited read-only access.",
971
+ external_id: "CN=Contractors,OU=External,DC=corp,DC=com",
972
+ source: "api", active: true
973
+ )
974
+ legacy_team = GroupModel.create!(
975
+ name: "legacy_team", label: "Legacy Team (Inactive)",
976
+ description: "Former team that has been dissolved. Kept for audit purposes.",
977
+ external_id: "CN=LegacyTeam,OU=Archived,DC=corp,DC=com",
978
+ source: "ldap", active: false
979
+ )
980
+
981
+ # Memberships — link employee user IDs to groups
982
+ [
983
+ { group: engineering, user_id: 1, source: "manual" },
984
+ { group: engineering, user_id: 2, source: "manual" },
985
+ { group: engineering, user_id: 3, source: "ldap" },
986
+ { group: design, user_id: 4, source: "manual" },
987
+ { group: design, user_id: 5, source: "manual" },
988
+ { group: design, user_id: 2, source: "manual" },
989
+ { group: management, user_id: 6, source: "manual" },
990
+ { group: management, user_id: 7, source: "manual" },
991
+ { group: contractors, user_id: 8, source: "api" },
992
+ { group: contractors, user_id: 9, source: "api" },
993
+ { group: contractors, user_id: 10, source: "api" },
994
+ { group: legacy_team, user_id: 11, source: "ldap" },
995
+ { group: legacy_team, user_id: 12, source: "ldap" },
996
+ { group: engineering, user_id: 6, source: "manual" }
997
+ ].each do |attrs|
998
+ MembershipModel.create!(attrs)
999
+ rescue ActiveRecord::RecordInvalid
1000
+ nil
1001
+ end
1002
+
1003
+ # Role mappings — map groups to authorization roles
1004
+ [
1005
+ { group: engineering, role_name: "editor" },
1006
+ { group: engineering, role_name: "viewer" },
1007
+ { group: design, role_name: "editor" },
1008
+ { group: management, role_name: "admin" },
1009
+ { group: contractors, role_name: "viewer" }
1010
+ ].each do |attrs|
1011
+ RoleMappingModel.create!(attrs)
1012
+ rescue ActiveRecord::RecordInvalid
1013
+ nil
1014
+ end
1015
+
1016
+ puts " Created #{GroupModel.count} groups, #{MembershipModel.count} memberships, #{RoleMappingModel.count} role mappings"
1017
+
1018
+ # Phase 9: Extensibility (was Phase 8)
1019
+ ExtModel = LcpRuby.registry.model_for("showcase_extensibility")
1020
+
1021
+ [
1022
+ { name: "US Dollar Account", currency: "USD", amount: 10000.00 },
1023
+ { name: "Euro Account", currency: "EUR", amount: 8500.50 },
1024
+ { name: "British Pound Reserve", currency: "GBP", amount: 25000.00 },
1025
+ { name: "Japanese Yen Fund", currency: "JPY", amount: 1500000.00 },
1026
+ { name: "No Currency Set", currency: nil, amount: 500.00 }
1027
+ ].each { |attrs| ExtModel.create!(attrs) }
1028
+ puts " Created #{ExtModel.count} showcase_extensibility records"
1029
+
1030
+ # Phase 10: Virtual Fields
1031
+ VirtualModel = LcpRuby.registry.model_for("showcase_virtual_field")
1032
+
1033
+ [
1034
+ {
1035
+ name: "Premium Widget",
1036
+ properties: { color: "blue", priority: 4, unit_price: 29.99, featured: true,
1037
+ category: "electronics", warehouse: "WEST-07", release_date: "2025-06-15",
1038
+ sku_code: "ELEC-PW-001", city: "San Francisco", country: "USA" }
1039
+ },
1040
+ {
1041
+ name: "Basic Gadget",
1042
+ properties: { color: "gray", priority: 2, unit_price: 9.99, featured: false,
1043
+ category: "electronics", warehouse: "MAIN-01", release_date: "2025-01-10",
1044
+ sku_code: "ELEC-BG-042", city: "Berlin", country: "Germany" }
1045
+ },
1046
+ {
1047
+ name: "Limited Edition Box",
1048
+ properties: { color: "gold", priority: 5, unit_price: 149.00, featured: true,
1049
+ category: "furniture", warehouse: "EAST-03", release_date: "2025-12-01",
1050
+ sku_code: "FURN-LE-100", city: "Tokyo", country: "Japan" }
1051
+ },
1052
+ {
1053
+ name: "Clearance Item",
1054
+ properties: { color: "red", priority: 1, unit_price: 2.50, featured: false,
1055
+ category: "clothing", sku_code: "CLTH-CI-999" }
1056
+ },
1057
+ {
1058
+ name: "New Arrival",
1059
+ properties: { color: "green", priority: 3, unit_price: 45.00, featured: true,
1060
+ category: "food", warehouse: "MAIN-01", release_date: "2026-02-01",
1061
+ sku_code: "FOOD-NA-007" }
1062
+ },
1063
+ {
1064
+ name: "Empty Properties Test",
1065
+ properties: {}
1066
+ }
1067
+ ].each { |attrs| VirtualModel.create!(attrs) }
1068
+ puts " Created #{VirtualModel.count} showcase_virtual_field records"
1069
+
1070
+ # Phase 11: Positioning
1071
+ PriorityModel = LcpRuby.registry.model_for("showcase_positioning")
1072
+
1073
+ [
1074
+ { name: "Design database schema", description: "Create ERD and define table structures", status: "done", priority: "high" },
1075
+ { name: "Implement authentication", description: "Add JWT-based auth flow", status: "done", priority: "critical" },
1076
+ { name: "Build REST API endpoints", description: "Create CRUD endpoints for all resources", status: "in_progress", priority: "high" },
1077
+ { name: "Write integration tests", description: "End-to-end API testing", status: "in_progress", priority: "medium" },
1078
+ { name: "Set up CI/CD pipeline", description: "GitHub Actions for automated deploy", status: "todo", priority: "medium" },
1079
+ { name: "Add monitoring & alerts", description: "Datadog integration for error tracking", status: "todo", priority: "low" },
1080
+ { name: "Performance optimization", description: "Query optimization and caching", status: "todo", priority: "medium" },
1081
+ { name: "Write user documentation", description: "API docs and user guide", status: "todo", priority: "low" }
1082
+ ].each { |attrs| PriorityModel.create!(attrs) }
1083
+ puts " Created #{PriorityModel.count} showcase_positioning records"
1084
+
1085
+ PipelineModel = LcpRuby.registry.model_for("pipeline")
1086
+ StageModel = LcpRuby.registry.model_for("pipeline_stage")
1087
+
1088
+ sales = PipelineModel.create!(name: "Sales Pipeline", description: "Standard B2B sales process")
1089
+ hiring = PipelineModel.create!(name: "Hiring Pipeline", description: "Recruitment workflow")
1090
+ support = PipelineModel.create!(name: "Support Pipeline", description: "Customer support ticket flow")
1091
+
1092
+ [
1093
+ { name: "Lead", color: "#3498db" },
1094
+ { name: "Qualified", color: "#2ecc71" },
1095
+ { name: "Proposal", color: "#f39c12" },
1096
+ { name: "Negotiation", color: "#e67e22" },
1097
+ { name: "Closed Won", color: "#27ae60" },
1098
+ { name: "Closed Lost", color: "#e74c3c" }
1099
+ ].each { |attrs| StageModel.create!(attrs.merge(pipeline: sales)) }
1100
+
1101
+ [
1102
+ { name: "Application", color: "#9b59b6" },
1103
+ { name: "Phone Screen", color: "#3498db" },
1104
+ { name: "Technical Interview", color: "#2ecc71" },
1105
+ { name: "Final Interview", color: "#f39c12" },
1106
+ { name: "Offer", color: "#27ae60" }
1107
+ ].each { |attrs| StageModel.create!(attrs.merge(pipeline: hiring)) }
1108
+
1109
+ [
1110
+ { name: "New", color: "#e74c3c" },
1111
+ { name: "In Progress", color: "#f39c12" },
1112
+ { name: "Waiting on Customer", color: "#95a5a6" },
1113
+ { name: "Resolved", color: "#27ae60" }
1114
+ ].each { |attrs| StageModel.create!(attrs.merge(pipeline: support)) }
1115
+
1116
+ puts " Created #{PipelineModel.count} pipelines with #{StageModel.count} stages"
1117
+
1118
+ # Phase 12: Recipes (JSON Field Nested Editing)
1119
+ RecipeModel = LcpRuby.registry.model_for("showcase_recipe")
1120
+
1121
+ [
1122
+ {
1123
+ title: "Spaghetti Carbonara",
1124
+ cuisine: "italian",
1125
+ servings: 4,
1126
+ steps: [
1127
+ { instruction: "Bring a large pot of salted water to boil", duration_minutes: 10 },
1128
+ { instruction: "Cook spaghetti according to package directions", duration_minutes: 12 },
1129
+ { instruction: "Fry guanciale in a large skillet until crispy", duration_minutes: 8 },
1130
+ { instruction: "Whisk eggs, pecorino, and black pepper in a bowl", duration_minutes: 2 },
1131
+ { instruction: "Toss hot pasta with guanciale, then stir in egg mixture off heat", duration_minutes: 3 }
1132
+ ],
1133
+ ingredients: [
1134
+ { name: "Spaghetti", quantity: "400", unit: "g", notes: nil, optional: false },
1135
+ { name: "Guanciale", quantity: "200", unit: "g", notes: "Pancetta works as a substitute", optional: false },
1136
+ { name: "Egg yolks", quantity: "6", unit: "pcs", notes: nil, optional: false },
1137
+ { name: "Pecorino Romano", quantity: "100", unit: "g", notes: "Freshly grated", optional: false },
1138
+ { name: "Black pepper", quantity: "2", unit: "tsp", notes: "Freshly cracked", optional: false },
1139
+ { name: "Parmesan", quantity: "50", unit: "g", notes: "For extra richness", optional: true }
1140
+ ]
1141
+ },
1142
+ {
1143
+ title: "Chicken Tikka Masala",
1144
+ cuisine: "indian",
1145
+ servings: 6,
1146
+ steps: [
1147
+ { instruction: "Marinate chicken in yogurt and spices for at least 1 hour", duration_minutes: 60 },
1148
+ { instruction: "Grill or broil chicken until charred", duration_minutes: 15 },
1149
+ { instruction: "Saute onions, garlic, and ginger until golden", duration_minutes: 10 },
1150
+ { instruction: "Add tomato puree and spices, simmer for 15 minutes", duration_minutes: 15 },
1151
+ { instruction: "Add cream and grilled chicken, simmer until heated through", duration_minutes: 10 },
1152
+ { instruction: "Garnish with cilantro and serve with naan", duration_minutes: 2 }
1153
+ ],
1154
+ ingredients: [
1155
+ { name: "Chicken thighs", quantity: "800", unit: "g", notes: "Boneless, skinless", optional: false },
1156
+ { name: "Yogurt", quantity: "200", unit: "ml", notes: "Plain, full-fat", optional: false },
1157
+ { name: "Garam masala", quantity: "2", unit: "tbsp", notes: nil, optional: false },
1158
+ { name: "Tomato puree", quantity: "400", unit: "ml", notes: "Canned crushed tomatoes", optional: false },
1159
+ { name: "Heavy cream", quantity: "200", unit: "ml", notes: nil, optional: false },
1160
+ { name: "Onion", quantity: "2", unit: "pcs", notes: "Finely diced", optional: false },
1161
+ { name: "Garlic", quantity: "4", unit: "pcs", notes: "Cloves, minced", optional: false },
1162
+ { name: "Fresh cilantro", quantity: "1", unit: "tbsp", notes: "Chopped, for garnish", optional: true }
1163
+ ]
1164
+ },
1165
+ {
1166
+ title: "Miso Ramen",
1167
+ cuisine: "japanese",
1168
+ servings: 2,
1169
+ steps: [
1170
+ { instruction: "Prepare dashi broth from kombu and bonito flakes", duration_minutes: 20 },
1171
+ { instruction: "Dissolve miso paste into the warm broth", duration_minutes: 3 },
1172
+ { instruction: "Cook ramen noodles according to package", duration_minutes: 4 },
1173
+ { instruction: "Prepare toppings: soft-boil eggs, slice chashu, chop scallions", duration_minutes: 15 },
1174
+ { instruction: "Assemble bowls: noodles, broth, then arrange toppings", duration_minutes: 3 }
1175
+ ],
1176
+ ingredients: [
1177
+ { name: "Ramen noodles", quantity: "200", unit: "g", notes: "Fresh preferred", optional: false },
1178
+ { name: "White miso paste", quantity: "3", unit: "tbsp", notes: nil, optional: false },
1179
+ { name: "Dashi stock", quantity: "800", unit: "ml", notes: nil, optional: false },
1180
+ { name: "Chashu pork", quantity: "150", unit: "g", notes: "Sliced", optional: false },
1181
+ { name: "Soft-boiled eggs", quantity: "2", unit: "pcs", notes: "Marinated in soy sauce overnight", optional: true },
1182
+ { name: "Scallions", quantity: "2", unit: "pcs", notes: "Thinly sliced", optional: false },
1183
+ { name: "Nori sheets", quantity: "2", unit: "pcs", notes: nil, optional: true }
1184
+ ]
1185
+ }
1186
+ ].each { |attrs| RecipeModel.create!(attrs) }
1187
+
1188
+ puts " Created #{RecipeModel.count} showcase_recipe records"
1189
+
1190
+ # Phase 14: Advanced Search Showcase
1191
+ SearchModel = LcpRuby.registry.model_for("showcase_search")
1192
+
1193
+ # Grab existing departments, categories, and authors for associations
1194
+ all_depts = DeptModel.all.to_a
1195
+ all_cats = CategoryModel.all.to_a
1196
+ all_authors = AuthorModel.all.to_a
1197
+
1198
+ [
1199
+ {
1200
+ title: "Widget Pro X100",
1201
+ description: "High-end widget with advanced features and premium build quality.",
1202
+ quantity: 250, rating: 4.7, price: 299.99,
1203
+ published: true,
1204
+ status: "published", priority: "high",
1205
+ release_date: Date.today - 30,
1206
+ last_reviewed_at: Time.current - 2.days,
1207
+ tracking_id: SecureRandom.uuid,
1208
+ contact_email: "sales@widgets.example.com",
1209
+ contact_phone: "+1 555 100 2000",
1210
+ source_url: "https://widgets.example.com/pro-x100",
1211
+ department_id: all_depts.find { |d| d.name == "Engineering" }&.id,
1212
+ category_id: all_cats.find { |c| c.name == "Technology" }&.id,
1213
+ author_id: all_authors.first&.id
1214
+ },
1215
+ {
1216
+ title: "Budget Gadget Basic",
1217
+ description: "Affordable everyday gadget for casual users.",
1218
+ quantity: 1200, rating: 3.2, price: 19.99,
1219
+ published: true,
1220
+ status: "published", priority: "low",
1221
+ release_date: Date.today - 90,
1222
+ last_reviewed_at: Time.current - 15.days,
1223
+ tracking_id: SecureRandom.uuid,
1224
+ contact_email: "info@gadgets.example.com",
1225
+ source_url: "https://gadgets.example.com/basic",
1226
+ department_id: all_depts.find { |d| d.name == "Frontend" }&.id,
1227
+ category_id: all_cats.find { |c| c.name == "Web Development" }&.id,
1228
+ author_id: all_authors.second&.id
1229
+ },
1230
+ {
1231
+ title: "Enterprise Server Rack",
1232
+ description: "42U server rack with integrated cooling and cable management.",
1233
+ quantity: 15, rating: 4.9, price: 4500.00,
1234
+ published: true,
1235
+ status: "approved", priority: "critical",
1236
+ release_date: Date.today - 7,
1237
+ last_reviewed_at: Time.current - 1.hour,
1238
+ tracking_id: SecureRandom.uuid,
1239
+ contact_email: "enterprise@racks.example.com",
1240
+ contact_phone: "+44 20 7946 0958",
1241
+ department_id: all_depts.find { |d| d.name == "Backend" }&.id,
1242
+ category_id: all_cats.find { |c| c.name == "Enterprise" }&.id,
1243
+ author_id: all_authors.third&.id
1244
+ },
1245
+ {
1246
+ title: "Cloud Monitor Dashboard",
1247
+ description: "Real-time monitoring dashboard for cloud infrastructure.",
1248
+ quantity: 0, rating: 4.1, price: 0.00,
1249
+ published: false,
1250
+ status: "draft", priority: "medium",
1251
+ release_date: Date.today + 30,
1252
+ tracking_id: SecureRandom.uuid,
1253
+ contact_email: "dev@cloud.example.com",
1254
+ department_id: all_depts.find { |d| d.name == "DevOps" }&.id,
1255
+ category_id: all_cats.find { |c| c.name == "Backend" }&.id,
1256
+ author_id: all_authors.fourth&.id
1257
+ },
1258
+ {
1259
+ title: "Mobile SDK v3",
1260
+ description: "Cross-platform mobile SDK with native performance.",
1261
+ quantity: nil, rating: nil, price: 149.00,
1262
+ published: false,
1263
+ status: "review", priority: "high",
1264
+ release_date: Date.today + 14,
1265
+ last_reviewed_at: Time.current - 3.days,
1266
+ tracking_id: SecureRandom.uuid,
1267
+ contact_phone: "+49 30 1234 5678",
1268
+ source_url: "https://sdk.example.com/v3",
1269
+ department_id: all_depts.find { |d| d.name == "React Team" }&.id,
1270
+ category_id: all_cats.find { |c| c.name == "Frontend" }&.id,
1271
+ author_id: all_authors.first&.id
1272
+ },
1273
+ {
1274
+ title: "Security Audit Tool",
1275
+ description: "Automated security scanning and vulnerability reporting.",
1276
+ quantity: 50, rating: 4.5, price: 899.00,
1277
+ published: true,
1278
+ status: "published", priority: "critical",
1279
+ release_date: Date.today - 60,
1280
+ last_reviewed_at: Time.current - 5.days,
1281
+ tracking_id: SecureRandom.uuid,
1282
+ contact_email: "security@tools.example.com",
1283
+ department_id: all_depts.find { |d| d.name == "API Team" }&.id,
1284
+ category_id: all_cats.find { |c| c.name == "Backend" }&.id,
1285
+ author_id: all_authors.last&.id
1286
+ },
1287
+ {
1288
+ title: "Design System Components",
1289
+ description: "Reusable UI component library with accessibility built in.",
1290
+ quantity: 87, rating: 4.3, price: 0.00,
1291
+ published: true,
1292
+ status: "published", priority: "medium",
1293
+ release_date: Date.today - 120,
1294
+ last_reviewed_at: Time.current - 30.days,
1295
+ department_id: all_depts.find { |d| d.name == "UX Design" }&.id,
1296
+ category_id: all_cats.find { |c| c.name == "CSS & Styling" }&.id,
1297
+ author_id: all_authors.second&.id
1298
+ },
1299
+ {
1300
+ title: "Legacy Data Migrator",
1301
+ description: nil,
1302
+ quantity: 3, rating: 2.1, price: 50.00,
1303
+ published: false,
1304
+ status: "archived", priority: "low",
1305
+ release_date: Date.today - 365,
1306
+ tracking_id: SecureRandom.uuid,
1307
+ department_id: all_depts.find { |d| d.name == "Management" }&.id,
1308
+ category_id: all_cats.find { |c| c.name == "Business" }&.id,
1309
+ author_id: all_authors.third&.id
1310
+ },
1311
+ {
1312
+ title: "AI Code Assistant",
1313
+ description: "Machine learning powered code completion and review.",
1314
+ quantity: 0, rating: nil, price: nil,
1315
+ published: false,
1316
+ status: "draft", priority: "high",
1317
+ release_date: nil,
1318
+ tracking_id: nil,
1319
+ contact_email: "ai@assistant.example.com",
1320
+ department_id: all_depts.find { |d| d.name == "Engineering" }&.id,
1321
+ category_id: all_cats.find { |c| c.name == "React Ecosystem" }&.id,
1322
+ author_id: all_authors.fourth&.id
1323
+ },
1324
+ {
1325
+ title: "Analytics Pipeline",
1326
+ description: "Real-time event processing and reporting pipeline with SQL interface.",
1327
+ quantity: 500, rating: 3.8, price: 1200.00,
1328
+ published: true,
1329
+ status: "approved", priority: "medium",
1330
+ release_date: Date.today - 14,
1331
+ last_reviewed_at: Time.current,
1332
+ tracking_id: SecureRandom.uuid,
1333
+ contact_email: "data@pipeline.example.com",
1334
+ contact_phone: "+1 555 999 8888",
1335
+ source_url: "https://pipeline.example.com",
1336
+ department_id: all_depts.find { |d| d.name == "Backend" }&.id,
1337
+ category_id: all_cats.find { |c| c.name == "Science" }&.id,
1338
+ author_id: all_authors.last&.id
1339
+ },
1340
+ {
1341
+ title: "Notification Service",
1342
+ description: "Multi-channel notification delivery: email, SMS, push, and webhooks.",
1343
+ quantity: 10000, rating: 4.6, price: 75.00,
1344
+ published: true,
1345
+ status: "published", priority: "high",
1346
+ release_date: Date.today - 45,
1347
+ last_reviewed_at: Time.current - 10.days,
1348
+ tracking_id: SecureRandom.uuid,
1349
+ contact_email: "notify@service.example.com",
1350
+ contact_phone: "+1 555 777 6666",
1351
+ department_id: all_depts.find { |d| d.name == "Engineering" }&.id,
1352
+ category_id: all_cats.find { |c| c.name == "Technology" }&.id,
1353
+ author_id: all_authors.first&.id
1354
+ },
1355
+ {
1356
+ title: "Form Builder Pro",
1357
+ description: "Drag-and-drop form builder with conditional logic and validation rules.",
1358
+ quantity: 42, rating: 3.9, price: 199.00,
1359
+ published: false,
1360
+ status: "review", priority: "medium",
1361
+ release_date: Date.today + 7,
1362
+ last_reviewed_at: Time.current - 1.day,
1363
+ tracking_id: SecureRandom.uuid,
1364
+ source_url: "https://formbuilder.example.com/pro",
1365
+ department_id: all_depts.find { |d| d.name == "Frontend" }&.id,
1366
+ category_id: all_cats.find { |c| c.name == "Startups" }&.id,
1367
+ author_id: all_authors.second&.id
1368
+ }
1369
+ ].each do |attrs|
1370
+ SearchModel.create!(attrs)
1371
+ end
1372
+ puts " Created #{SearchModel.count} showcase_search records"
1373
+
1374
+ # Phase 15: Saved Filters
1375
+ if LcpRuby.registry.registered?("saved_filter")
1376
+ SavedFilterModel = LcpRuby.registry.model_for("saved_filter")
1377
+
1378
+ # --- Saved filters for showcase-search (inline display) ---
1379
+ [
1380
+ {
1381
+ name: "Published & Expensive",
1382
+ description: "Items that are published with price >= 100",
1383
+ target_presenter: "showcase_searches",
1384
+ condition_tree: {
1385
+ logic: "and",
1386
+ conditions: [
1387
+ { field: "published", operator: "true" },
1388
+ { field: "price", operator: "gteq", value: "100" }
1389
+ ]
1390
+ },
1391
+ ql_text: "published is true and price >= 100",
1392
+ visibility: "personal",
1393
+ pinned: true,
1394
+ default_filter: true,
1395
+ owner_id: 1,
1396
+ position: 1,
1397
+ icon: "dollar-sign",
1398
+ color: "green"
1399
+ },
1400
+ {
1401
+ name: "Critical Priority",
1402
+ description: "Items with high or critical priority",
1403
+ target_presenter: "showcase_searches",
1404
+ condition_tree: {
1405
+ logic: "and",
1406
+ conditions: [
1407
+ { field: "priority", operator: "in", value: %w[high critical] }
1408
+ ]
1409
+ },
1410
+ ql_text: "priority in ['high', 'critical']",
1411
+ visibility: "personal",
1412
+ pinned: true,
1413
+ owner_id: 1,
1414
+ position: 2,
1415
+ icon: "alert-triangle",
1416
+ color: "red"
1417
+ },
1418
+ {
1419
+ name: "Recent Drafts",
1420
+ description: "Draft items created in the last 30 days",
1421
+ target_presenter: "showcase_searches",
1422
+ condition_tree: {
1423
+ logic: "and",
1424
+ conditions: [
1425
+ { field: "status", operator: "eq", value: "draft" },
1426
+ { field: "created_at", operator: "last_n_days", value: "30" }
1427
+ ]
1428
+ },
1429
+ ql_text: "status = 'draft' and created_at last_n_days 30",
1430
+ visibility: "personal",
1431
+ owner_id: 1,
1432
+ position: 3,
1433
+ icon: "edit",
1434
+ color: "gray"
1435
+ },
1436
+ {
1437
+ name: "All Published",
1438
+ description: "All published items (visible to everyone)",
1439
+ target_presenter: "showcase_searches",
1440
+ condition_tree: {
1441
+ logic: "and",
1442
+ conditions: [
1443
+ { field: "status", operator: "eq", value: "published" }
1444
+ ]
1445
+ },
1446
+ ql_text: "status = 'published'",
1447
+ visibility: "global",
1448
+ pinned: true,
1449
+ owner_id: 1,
1450
+ position: 4,
1451
+ icon: "globe",
1452
+ color: "green"
1453
+ },
1454
+ {
1455
+ name: "Admin: In Review",
1456
+ description: "Items currently in review (admin role filter)",
1457
+ target_presenter: "showcase_searches",
1458
+ condition_tree: {
1459
+ logic: "and",
1460
+ conditions: [
1461
+ { field: "status", operator: "eq", value: "review" }
1462
+ ]
1463
+ },
1464
+ ql_text: "status = 'review'",
1465
+ visibility: "role",
1466
+ target_role: "admin",
1467
+ owner_id: 1,
1468
+ position: 5,
1469
+ icon: "shield",
1470
+ color: "orange"
1471
+ },
1472
+ {
1473
+ name: "Engineering: High-Value Items",
1474
+ description: "Expensive items for engineering team review",
1475
+ target_presenter: "showcase_searches",
1476
+ condition_tree: {
1477
+ logic: "and",
1478
+ conditions: [
1479
+ { field: "price", operator: "gteq", value: "200" },
1480
+ { field: "status", operator: "not_eq", value: "archived" }
1481
+ ]
1482
+ },
1483
+ ql_text: "price >= 200 and status != 'archived'",
1484
+ visibility: "group",
1485
+ target_group: "engineering",
1486
+ pinned: true,
1487
+ owner_id: 1,
1488
+ position: 6,
1489
+ icon: "cpu",
1490
+ color: "purple"
1491
+ },
1492
+ # --- Saved filters for articles (dropdown display) ---
1493
+ {
1494
+ name: "Published Articles",
1495
+ description: "All articles with published status",
1496
+ target_presenter: "articles",
1497
+ condition_tree: {
1498
+ logic: "and",
1499
+ conditions: [
1500
+ { field: "status", operator: "eq", value: "published" }
1501
+ ]
1502
+ },
1503
+ ql_text: "status = 'published'",
1504
+ visibility: "global",
1505
+ pinned: true,
1506
+ owner_id: 1,
1507
+ position: 1,
1508
+ icon: "check-circle",
1509
+ color: "green"
1510
+ },
1511
+ {
1512
+ name: "My Draft Articles",
1513
+ description: "Draft articles (personal filter)",
1514
+ target_presenter: "articles",
1515
+ condition_tree: {
1516
+ logic: "and",
1517
+ conditions: [
1518
+ { field: "status", operator: "eq", value: "draft" }
1519
+ ]
1520
+ },
1521
+ ql_text: "status = 'draft'",
1522
+ visibility: "personal",
1523
+ owner_id: 1,
1524
+ position: 2,
1525
+ icon: "file"
1526
+ },
1527
+ {
1528
+ name: "Long Articles",
1529
+ description: "Articles with more than 500 words",
1530
+ target_presenter: "articles",
1531
+ condition_tree: {
1532
+ logic: "and",
1533
+ conditions: [
1534
+ { field: "word_count", operator: "gteq", value: "500" }
1535
+ ]
1536
+ },
1537
+ ql_text: "word_count >= 500",
1538
+ visibility: "personal",
1539
+ pinned: true,
1540
+ owner_id: 1,
1541
+ position: 3,
1542
+ icon: "book"
1543
+ }
1544
+ ].each { |attrs| SavedFilterModel.create!(attrs) }
1545
+
1546
+ puts " Created #{SavedFilterModel.count} saved filters"
1547
+ end
1548
+
1549
+ # Phase: Array Fields Showcase
1550
+ ArrayModel = LcpRuby.registry.model_for("showcase_array")
1551
+
1552
+ [
1553
+ {
1554
+ title: "Web Framework Comparison",
1555
+ description: "Comparing popular web frameworks across multiple dimensions.",
1556
+ tags: %w[ruby rails javascript react comparison],
1557
+ categories: %w[frontend backend],
1558
+ scores: [ 5, 4, 3, 5, 4 ],
1559
+ measurements: [ 98.5, 72.3, 88.1 ],
1560
+ default_labels: %w[important review],
1561
+ record_type: "advanced",
1562
+ featured: true
1563
+ },
1564
+ {
1565
+ title: "DevOps Pipeline Setup",
1566
+ description: "Setting up CI/CD pipeline with automated testing and deployment.",
1567
+ tags: %w[devops ci cd docker kubernetes urgent],
1568
+ categories: %w[devops backend],
1569
+ scores: [ 5, 5, 4 ],
1570
+ measurements: [ 150.0, 200.5 ],
1571
+ default_labels: %w[important],
1572
+ record_type: "special",
1573
+ featured: true
1574
+ },
1575
+ {
1576
+ title: "Quick Start Guide",
1577
+ description: "A simple getting started tutorial for beginners.",
1578
+ tags: %w[tutorial beginner],
1579
+ categories: %w[frontend],
1580
+ scores: [ 3, 4 ],
1581
+ measurements: [],
1582
+ default_labels: %w[important review],
1583
+ record_type: "basic",
1584
+ featured: false
1585
+ },
1586
+ {
1587
+ title: "Performance Optimization",
1588
+ description: "Database query optimization and caching strategies.",
1589
+ tags: %w[performance database caching optimization],
1590
+ categories: %w[backend devops],
1591
+ scores: [ 5, 4, 5, 3, 2 ],
1592
+ measurements: [ 12.5, 8.3, 45.7, 3.2 ],
1593
+ default_labels: [],
1594
+ record_type: "advanced",
1595
+ featured: false
1596
+ },
1597
+ {
1598
+ title: "Empty Arrays Demo",
1599
+ description: "Record with no array values to demonstrate empty state rendering.",
1600
+ tags: [],
1601
+ categories: [],
1602
+ scores: [],
1603
+ measurements: [],
1604
+ default_labels: %w[important review],
1605
+ record_type: "basic",
1606
+ featured: false
1607
+ },
1608
+ {
1609
+ title: "Design System Components",
1610
+ description: "Building a component library with design tokens and variants.",
1611
+ tags: %w[design components ui ux],
1612
+ categories: %w[design frontend],
1613
+ scores: [ 4, 4, 5 ],
1614
+ measurements: [ 16.0, 24.0, 32.0, 48.0 ],
1615
+ default_labels: %w[review],
1616
+ record_type: "special",
1617
+ featured: true
1618
+ }
1619
+ ].each { |attrs| ArrayModel.create!(attrs) }
1620
+
1621
+ puts " Created #{ArrayModel.count} array demo records"
1622
+
1623
+ # Phase 13: Feature Catalog
1624
+ # Resolve the catalog from the gem's bundled docs (LcpRuby ships
1625
+ # feature-catalog.yml). Works whether showcase runs in this checkout or is
1626
+ # materialized elsewhere via `lcp run showcase` — `GemPaths.docs` follows the
1627
+ # loaded gem, not a repo-relative path.
1628
+ FeatureModel = LcpRuby.registry.model_for("feature")
1629
+ feature_catalog_path = File.join(LcpRuby::GemPaths.docs, "feature-catalog.yml")
1630
+ feature_catalog = YAML.load_file(feature_catalog_path)
1631
+ features = Array(feature_catalog["features"])
1632
+
1633
+ # Drop catalog keys not mapped on the Feature model so new meta-keys
1634
+ # (e.g. `generator:` on generator-bundled features) don't crash the seed.
1635
+ known_attrs = FeatureModel.column_names
1636
+ features.each do |attrs|
1637
+ FeatureModel.create!(attrs.slice(*known_attrs))
1638
+ end
1639
+ puts " Created #{FeatureModel.count} feature catalog entries from docs/feature-catalog.yml"
1640
+
1641
+ # Phase: Userstamps Showcase
1642
+ UserstampsModel = LcpRuby.registry.model_for("showcase_userstamps")
1643
+
1644
+ # Simulate different users creating/updating documents
1645
+ admin_user = if LcpRuby.configuration.authentication == :built_in
1646
+ LcpRuby::User.find_by(email: "admin@example.com")
1647
+ end
1648
+
1649
+ LcpRuby::Current.user = admin_user
1650
+
1651
+ [
1652
+ { title: "Architecture Decision Record: Microservices", content: "After evaluating monolith vs microservices, we decided to adopt a modular monolith.", status: "published", priority: "high" },
1653
+ { title: "API Versioning Strategy", content: "Use URL path versioning (v1, v2) for public APIs and header versioning for internal.", status: "published", priority: "normal" },
1654
+ { title: "Database Migration Guidelines", content: "All migrations must be reversible. No data migrations in schema migrations.", status: "review", priority: "high" },
1655
+ { title: "Frontend Component Library", content: "Evaluate Radix UI, shadcn/ui, and Headless UI for the design system.", status: "draft", priority: "normal" },
1656
+ { title: "Deployment Runbook: Production", content: "Step-by-step guide for production deployments including rollback procedures.", status: "published", priority: "high" },
1657
+ { title: "Code Review Checklist", content: "Security, performance, testing, documentation, and naming conventions.", status: "review", priority: "normal" },
1658
+ { title: "Incident Response Plan", content: "Escalation paths, communication templates, and post-mortem process.", status: "draft", priority: "low" },
1659
+ { title: "Technical Debt Register", content: "Tracked items: legacy auth module, N+1 in reports, missing indexes.", status: "archived", priority: "low" }
1660
+ ].each { |attrs| UserstampsModel.create!(attrs) }
1661
+
1662
+ LcpRuby::Current.user = nil
1663
+
1664
+ puts " Created #{UserstampsModel.count} tracked documents (userstamps showcase)"
1665
+
1666
+ # Phase: Sequences Showcase
1667
+ SequenceModel = LcpRuby.registry.model_for("showcase_sequence")
1668
+
1669
+ # Create records across categories to demonstrate all sequence types
1670
+ [
1671
+ { title: "Server outage report", category: "support", description: "Production server went down at 3am" },
1672
+ { title: "New feature request", category: "engineering", description: "Add dark mode support" },
1673
+ { title: "Q1 invoice", category: "billing", description: "Quarterly billing for Enterprise plan" },
1674
+ { title: "API rate limit issue", category: "support", description: "Customer hitting rate limits" },
1675
+ { title: "Database migration plan", category: "engineering", description: "Migrate from MySQL to PostgreSQL" },
1676
+ { title: "Annual subscription renewal", category: "billing", description: "Renewal for 2026" },
1677
+ { title: "General inquiry", category: "general", description: "Prospective customer questions" },
1678
+ { title: "CI/CD pipeline redesign", category: "engineering", description: "Move to GitHub Actions" },
1679
+ { title: "Password reset not working", category: "support", description: "Users reporting 500 errors" },
1680
+ { title: "Credit note CN-2025-001", category: "billing", description: "Refund for overpayment" },
1681
+ { title: "Security audit findings", category: "general", description: "Penetration test results review" },
1682
+ { title: "Mobile app crash on login", category: "support", description: "iOS 18 compatibility issue" }
1683
+ ].each { |attrs| SequenceModel.create!(attrs) }
1684
+
1685
+ puts " Created #{SequenceModel.count} sequenced records (sequences showcase)"
1686
+
1687
+ # Phase: Soft Delete Showcase
1688
+ # Phase: Aggregates
1689
+ AggCompanyModel = LcpRuby.registry.model_for("showcase_aggregate_company")
1690
+ AggProjectModel = LcpRuby.registry.model_for("showcase_aggregate")
1691
+ AggTaskModel = LcpRuby.registry.model_for("showcase_aggregate_item")
1692
+
1693
+ agg_companies = [
1694
+ { name: "Acme Corp", country: "USA" },
1695
+ { name: "Globex Inc", country: "Germany" },
1696
+ { name: "Initech", country: "Japan" }
1697
+ ].map { |attrs| AggCompanyModel.create!(attrs) }
1698
+
1699
+ agg_projects = [
1700
+ { name: "Platform Redesign", description: "Complete UI/UX overhaul with new design system", status: "active", budget: 150_000, company_id: agg_companies[0].id },
1701
+ { name: "Mobile App v2", description: "Native mobile app rewrite in Swift/Kotlin", status: "active", budget: 200_000, company_id: agg_companies[0].id },
1702
+ { name: "Data Pipeline", description: "Real-time data ingestion and processing pipeline", status: "planning", budget: 80_000, company_id: agg_companies[1].id },
1703
+ { name: "API Gateway", description: "Centralized API gateway with rate limiting and auth", status: "completed", budget: 60_000, company_id: agg_companies[1].id },
1704
+ { name: "Legacy Migration", description: "Migrate legacy PHP codebase to Rails", status: "archived", budget: 120_000, company_id: agg_companies[2].id }
1705
+ ].map { |attrs| AggProjectModel.create!(attrs) }
1706
+
1707
+ # Tasks for Platform Redesign (project 0) — 8 tasks, 3 done, 3 assignees
1708
+ [
1709
+ { title: "Design system tokens", status: "done", hours: 40, cost: 4000, priority_score: 9, assignee: "Alice", due_date: "2025-01-15" },
1710
+ { title: "Component library", status: "done", hours: 80, cost: 8000, priority_score: 9, assignee: "Alice", due_date: "2025-02-01" },
1711
+ { title: "Navigation redesign", status: "done", hours: 24, cost: 2400, priority_score: 7, assignee: "Bob", due_date: "2025-02-15" },
1712
+ { title: "Dashboard layout", status: "in_progress", hours: 32, cost: 3200, priority_score: 8, assignee: "Bob", due_date: "2025-03-01" },
1713
+ { title: "Form components", status: "in_progress", hours: 48, cost: 4800, priority_score: 6, assignee: "Carol", due_date: "2025-03-15" },
1714
+ { title: "Accessibility audit", status: "todo", hours: 16, cost: 1600, priority_score: 5, assignee: "Alice", due_date: "2025-04-01" },
1715
+ { title: "Performance testing", status: "todo", hours: 20, cost: 2000, priority_score: 4, assignee: "Bob", due_date: "2025-04-15" },
1716
+ { title: "Documentation", status: "todo", hours: 12, cost: 1200, priority_score: 3, assignee: "Carol", due_date: "2025-05-01" }
1717
+ ].each { |attrs| AggTaskModel.create!(attrs.merge(showcase_aggregate_id: agg_projects[0].id)) }
1718
+
1719
+ # Tasks for Mobile App v2 (project 1) — 6 tasks, 1 done, 2 assignees
1720
+ [
1721
+ { title: "App architecture", status: "done", hours: 24, cost: 3600, priority_score: 10, assignee: "Dan", due_date: "2025-01-20" },
1722
+ { title: "Auth module", status: "in_progress", hours: 40, cost: 6000, priority_score: 8, assignee: "Eve", due_date: "2025-02-10" },
1723
+ { title: "Offline sync", status: "in_progress", hours: 60, cost: 9000, priority_score: 7, assignee: "Dan", due_date: "2025-03-01" },
1724
+ { title: "Push notifications", status: "todo", hours: 20, cost: 3000, priority_score: 5, assignee: "Eve", due_date: "2025-03-20" },
1725
+ { title: "App store submission", status: "todo", hours: 8, cost: 1200, priority_score: 3, assignee: "Dan", due_date: "2025-04-15" },
1726
+ { title: "Beta testing", status: "todo", hours: 16, cost: 2400, priority_score: 4, assignee: "Eve", due_date: "2025-04-01" }
1727
+ ].each { |attrs| AggTaskModel.create!(attrs.merge(showcase_aggregate_id: agg_projects[1].id)) }
1728
+
1729
+ # Tasks for Data Pipeline (project 2) — 4 tasks, 0 done, 2 assignees
1730
+ [
1731
+ { title: "Schema design", status: "todo", hours: 16, cost: 2400, priority_score: 8, assignee: "Frank", due_date: "2025-03-01" },
1732
+ { title: "Kafka setup", status: "todo", hours: 24, cost: 3600, priority_score: 7, assignee: "Grace", due_date: "2025-03-15" },
1733
+ { title: "Stream processors", status: "todo", hours: 40, cost: 6000, priority_score: 6, assignee: "Frank", due_date: "2025-04-01" },
1734
+ { title: "Monitoring dashboard", status: "todo", hours: 12, cost: 1800, priority_score: 4, assignee: "Grace", due_date: "2025-04-15" }
1735
+ ].each { |attrs| AggTaskModel.create!(attrs.merge(showcase_aggregate_id: agg_projects[2].id)) }
1736
+
1737
+ # Tasks for API Gateway (project 3) — 5 tasks, all done, 1 assignee
1738
+ [
1739
+ { title: "Gateway framework", status: "done", hours: 32, cost: 3200, priority_score: 9, assignee: "Hank", due_date: "2024-10-01" },
1740
+ { title: "Rate limiter", status: "done", hours: 16, cost: 1600, priority_score: 8, assignee: "Hank", due_date: "2024-10-15" },
1741
+ { title: "Auth middleware", status: "done", hours: 24, cost: 2400, priority_score: 8, assignee: "Hank", due_date: "2024-11-01" },
1742
+ { title: "Load testing", status: "done", hours: 12, cost: 1200, priority_score: 5, assignee: "Hank", due_date: "2024-11-15" },
1743
+ { title: "Production deploy", status: "done", hours: 8, cost: 800, priority_score: 7, assignee: "Hank", due_date: "2024-12-01" }
1744
+ ].each { |attrs| AggTaskModel.create!(attrs.merge(showcase_aggregate_id: agg_projects[3].id)) }
1745
+
1746
+ # Tasks for Legacy Migration (project 4) — 3 tasks, 2 done, 2 assignees
1747
+ [
1748
+ { title: "Code audit", status: "done", hours: 40, cost: 4000, priority_score: 7, assignee: "Ivy", due_date: "2024-06-01" },
1749
+ { title: "Data migration scripts", status: "done", hours: 60, cost: 6000, priority_score: 8, assignee: "Jack", due_date: "2024-07-15" },
1750
+ { title: "Final cutover", status: "cancelled", hours: 16, cost: 1600, priority_score: 9, assignee: "Ivy", due_date: "2024-08-01" }
1751
+ ].each { |attrs| AggTaskModel.create!(attrs.merge(showcase_aggregate_id: agg_projects[4].id)) }
1752
+
1753
+ puts " Created #{AggCompanyModel.count} companies, #{AggProjectModel.count} aggregate projects with #{AggTaskModel.count} tasks"
1754
+
1755
+ SoftDeleteModel = LcpRuby.registry.model_for("showcase_soft_delete")
1756
+ SoftDeleteItemModel = LcpRuby.registry.model_for("showcase_soft_delete_item")
1757
+
1758
+ LcpRuby::Current.user = admin_user
1759
+
1760
+ docs = [
1761
+ { title: "Q1 Planning Document", content: "Strategic objectives and OKRs for Q1. Focus areas: platform stability, developer experience, onboarding.", status: "active", priority: "high" },
1762
+ { title: "Release Notes v2.4", content: "New features: soft delete, userstamps, cascade discard. Bug fixes: permission cache, scope builder.", status: "active", priority: "normal" },
1763
+ { title: "Security Audit Findings", content: "Penetration test results and remediation plan. All critical findings addressed.", status: "active", priority: "high" },
1764
+ { title: "Legacy API Migration Plan", content: "Timeline for deprecating v1 endpoints and migrating consumers to v2.", status: "draft", priority: "normal" },
1765
+ { title: "Team Retrospective Notes", content: "What went well: deployment automation. What to improve: test coverage for edge cases.", status: "draft", priority: "low" },
1766
+ { title: "Outdated Design Spec", content: "Initial wireframes for the dashboard. Superseded by the revised spec.", status: "archived", priority: "low" }
1767
+ ].map { |attrs| SoftDeleteModel.create!(attrs) }
1768
+
1769
+ # Add child items to some documents
1770
+ items_data = {
1771
+ 0 => [
1772
+ { name: "Define OKRs", notes: "Align with company goals" },
1773
+ { name: "Assign team leads", notes: "One lead per initiative" },
1774
+ { name: "Set milestone dates", notes: "Monthly checkpoints" }
1775
+ ],
1776
+ 1 => [
1777
+ { name: "Write changelog", notes: "User-facing summary" },
1778
+ { name: "Update docs", notes: "Reference guides and examples" }
1779
+ ],
1780
+ 2 => [
1781
+ { name: "Fix XSS in search", notes: "Input sanitization added" },
1782
+ { name: "Patch SQL injection", notes: "Parameterized queries" },
1783
+ { name: "Enable CSP headers", notes: "Report-only mode first" },
1784
+ { name: "Rotate API keys", notes: "All environments" }
1785
+ ],
1786
+ 3 => [
1787
+ { name: "Inventory v1 consumers", notes: "Check analytics for active users" },
1788
+ { name: "Build compatibility layer", notes: "Translate v1 requests to v2" }
1789
+ ]
1790
+ }
1791
+
1792
+ items_data.each do |doc_index, items|
1793
+ items.each do |item_attrs|
1794
+ SoftDeleteItemModel.create!(item_attrs.merge(showcase_soft_delete_id: docs[doc_index].id))
1795
+ end
1796
+ end
1797
+
1798
+ # Discard 2 documents to pre-populate the archive
1799
+ docs[4].discard! # Team Retrospective Notes
1800
+ docs[5].discard! # Outdated Design Spec
1801
+
1802
+ LcpRuby::Current.user = nil
1803
+
1804
+ puts " Created #{SoftDeleteModel.kept.count} active + #{SoftDeleteModel.discarded.count} archived soft delete documents with #{SoftDeleteItemModel.count} items"
1805
+
1806
+ # Phase: Batch Actions Showcase
1807
+ BatchTaskModel = LcpRuby.registry.model_for("showcase_batch_task")
1808
+
1809
+ LcpRuby::Current.user = admin_user
1810
+
1811
+ batch_tasks = [
1812
+ # Open tasks
1813
+ { title: "Review Q1 budget proposal", status: "open", priority: "high", assignee: "Alice", due_date: "2026-04-15", estimated_hours: 4.0 },
1814
+ { title: "Update onboarding documentation", status: "open", priority: "normal", assignee: "Bob", due_date: "2026-04-20", estimated_hours: 8.0 },
1815
+ { title: "Fix broken CI pipeline", status: "open", priority: "critical", assignee: "Carol", due_date: "2026-03-25", estimated_hours: 2.0 },
1816
+ { title: "Prepare sprint demo", status: "open", priority: "normal", assignee: "Alice", due_date: "2026-04-01", estimated_hours: 3.0 },
1817
+ { title: "Audit third-party dependencies", status: "open", priority: "high", assignee: "Dan", due_date: "2026-04-10", estimated_hours: 6.0 },
1818
+ { title: "Design new landing page", status: "open", priority: "normal", assignee: "Eve", due_date: "2026-05-01", estimated_hours: 16.0 },
1819
+ { title: "Set up staging environment", status: "open", priority: "high", assignee: "Frank", due_date: "2026-03-28", estimated_hours: 5.0 },
1820
+ { title: "Write API rate limiting spec", status: "open", priority: "normal", assignee: "Grace", due_date: "2026-04-25", estimated_hours: 4.0 },
1821
+ # In progress
1822
+ { title: "Implement user dashboard", status: "in_progress", priority: "high", assignee: "Alice", due_date: "2026-04-05", estimated_hours: 24.0 },
1823
+ { title: "Migrate legacy database", status: "in_progress", priority: "critical", assignee: "Bob", due_date: "2026-04-01", estimated_hours: 40.0 },
1824
+ { title: "Refactor authentication module", status: "in_progress", priority: "normal", assignee: "Carol", due_date: "2026-04-15", estimated_hours: 16.0 },
1825
+ { title: "Add search autocomplete", status: "in_progress", priority: "normal", assignee: "Dan", due_date: "2026-04-20", estimated_hours: 12.0 },
1826
+ # In review
1827
+ { title: "Performance optimization report", status: "review", priority: "high", assignee: "Eve", due_date: "2026-03-30", estimated_hours: 8.0 },
1828
+ { title: "Security patch deployment", status: "review", priority: "critical", assignee: "Frank", due_date: "2026-03-22", estimated_hours: 3.0 },
1829
+ { title: "Update privacy policy page", status: "review", priority: "low", assignee: "Grace", due_date: "2026-04-30", estimated_hours: 2.0 },
1830
+ # Closed (cannot be batch-deleted by editor role — demonstrates per-record skip)
1831
+ { title: "Initial project setup", status: "closed", priority: "normal", assignee: "Alice", due_date: "2026-01-15", estimated_hours: 4.0 },
1832
+ { title: "Database schema design", status: "closed", priority: "high", assignee: "Bob", due_date: "2026-01-20", estimated_hours: 12.0 },
1833
+ { title: "CI/CD pipeline setup", status: "closed", priority: "high", assignee: "Carol", due_date: "2026-02-01", estimated_hours: 8.0 },
1834
+ { title: "Code review guidelines", status: "closed", priority: "normal", assignee: "Dan", due_date: "2026-02-10", estimated_hours: 3.0 },
1835
+ { title: "Release v1.0", status: "closed", priority: "critical", assignee: "Eve", due_date: "2026-03-01", estimated_hours: 6.0 },
1836
+ # Extra records for cross-page selection (per_page: 15, so these push to page 2)
1837
+ { title: "Plan team offsite", status: "open", priority: "low", assignee: "Frank", due_date: "2026-06-01", estimated_hours: 2.0 },
1838
+ { title: "Benchmark new ORM version", status: "open", priority: "normal", assignee: "Grace", due_date: "2026-05-15", estimated_hours: 4.0 },
1839
+ { title: "Write integration test suite", status: "open", priority: "high", assignee: "Alice", due_date: "2026-04-30", estimated_hours: 20.0 },
1840
+ { title: "Configure CDN caching rules", status: "open", priority: "normal", assignee: "Bob", due_date: "2026-05-10", estimated_hours: 3.0 },
1841
+ { title: "Evaluate monitoring tools", status: "open", priority: "normal", assignee: "Carol", due_date: "2026-05-20", estimated_hours: 6.0 }
1842
+ ].map { |attrs| BatchTaskModel.create!(attrs) }
1843
+
1844
+ # Discard a few to pre-populate the archive view
1845
+ batch_tasks[20].discard! # Plan team offsite
1846
+ batch_tasks[21].discard! # Benchmark new ORM version
1847
+
1848
+ LcpRuby::Current.user = nil
1849
+
1850
+ puts " Created #{BatchTaskModel.kept.count} active + #{BatchTaskModel.discarded.count} archived batch tasks"
1851
+
1852
+ # Phase 14: Row Styling (item_classes) Demo
1853
+ ItemClassModel = LcpRuby.registry.model_for("showcase_item_class")
1854
+
1855
+ item_class_records = [
1856
+ # USE CASE 1: cancelled + eq → muted + strikethrough
1857
+ { name: "Cancelled project", status: "cancelled", priority: "low", score: 30, amount: 500.00, code: "PROJ-001", email: "alice@example.com", notes: "Was deprioritized.", due_date: 2.months.ago.to_date },
1858
+
1859
+ # USE CASE 2: completed + eq → success (green)
1860
+ { name: "Completed delivery", status: "completed", priority: "medium", score: 85, amount: 2500.00, code: "DEL-042", email: "bob@example.com", notes: "Shipped on time.", due_date: 1.week.ago.to_date },
1861
+
1862
+ # USE CASE 3: critical priority + eq → danger (red)
1863
+ { name: "Server outage fix", status: "active", priority: "critical", score: 95, amount: 0.00, code: "INC-911", email: "ops@example.com", notes: "P1 incident.", due_date: Date.current },
1864
+
1865
+ # USE CASE 4: on_hold + eq → warning (yellow)
1866
+ { name: "Pending approval", status: "on_hold", priority: "medium", score: 60, amount: 1200.00, code: "REQ-007", email: nil, notes: "Waiting for budget sign-off.", due_date: 1.month.from_now.to_date },
1867
+
1868
+ # USE CASE 5: high priority + eq → bold
1869
+ { name: "Urgent feature request", status: "active", priority: "high", score: 75, amount: 8000.00, code: "FEAT-100", email: "pm@example.com", notes: "Board-level priority.", due_date: 2.weeks.from_now.to_date },
1870
+
1871
+ # USE CASE 6: score > 90 → info (blue) — also critical → danger (accumulation demo)
1872
+ { name: "Top performer record", status: "active", priority: "medium", score: 98, amount: 15000.00, code: "PERF-001", email: "star@example.com", notes: "Exceeded all KPIs.", due_date: 3.months.from_now.to_date },
1873
+
1874
+ # USE CASE 7: score < 20 → custom class (lcp-item-low-score)
1875
+ { name: "Underperforming task", status: "draft", priority: "low", score: 12, amount: 100.00, code: "TASK-999", email: nil, notes: nil, due_date: nil },
1876
+
1877
+ # USE CASE 8: blank notes → custom class (lcp-item-missing-notes)
1878
+ { name: "No documentation yet", status: "active", priority: "medium", score: 50, amount: 300.00, code: "DOC-000", email: "writer@example.com", notes: nil, due_date: 1.month.from_now.to_date },
1879
+
1880
+ # USE CASE 9: code matches ^TEMP → custom class (lcp-item-temp-code)
1881
+ { name: "Temporary prototype", status: "draft", priority: "low", score: 40, amount: 0.00, code: "TEMP-alpha", email: nil, notes: "Will be replaced.", due_date: nil },
1882
+
1883
+ # USE CASE 10: overdue (service condition) → custom class (lcp-item-overdue)
1884
+ { name: "Overdue report", status: "active", priority: "high", score: 55, amount: 750.00, code: "RPT-003", email: "analyst@example.com", notes: "Deadline was last week.", due_date: 10.days.ago.to_date },
1885
+
1886
+ # ACCUMULATION: cancelled + low score + blank notes → 3 rules match
1887
+ { name: "Abandoned experiment", status: "cancelled", priority: "low", score: 5, amount: 0.00, code: "EXP-404", email: nil, notes: nil, due_date: 6.months.ago.to_date },
1888
+
1889
+ # ACCUMULATION: completed + high score → success + info
1890
+ { name: "Award-winning campaign", status: "completed", priority: "high", score: 99, amount: 50000.00, code: "MKT-001", email: "cmo@example.com", notes: "Won industry award.", due_date: 1.month.ago.to_date },
1891
+
1892
+ # NO MATCH: plain draft, medium priority, no special conditions
1893
+ { name: "Regular draft item", status: "draft", priority: "medium", score: 50, amount: 200.00, code: "DRAFT-001", email: "user@example.com", notes: "Nothing special here.", due_date: 2.months.from_now.to_date },
1894
+
1895
+ # ACCUMULATION: on_hold + TEMP code + blank notes + overdue
1896
+ { name: "Stalled temp project", status: "on_hold", priority: "medium", score: 45, amount: 0.00, code: "TEMP-stalled", email: nil, notes: nil, due_date: 3.weeks.ago.to_date }
1897
+ ]
1898
+
1899
+ item_class_records.each { |attrs| ItemClassModel.create!(attrs) }
1900
+ puts " Created #{ItemClassModel.count} item_classes demo records"
1901
+
1902
+ # Phase 15: Advanced Conditions Demo
1903
+ ConditionCategoryModel = LcpRuby.registry.model_for("showcase_condition_category")
1904
+ ConditionModel = LcpRuby.registry.model_for("showcase_condition")
1905
+ ConditionTaskModel = LcpRuby.registry.model_for("showcase_condition_task")
1906
+ ConditionThresholdModel = LcpRuby.registry.model_for("showcase_condition_threshold")
1907
+
1908
+ # Categories (for dot-path condition demos)
1909
+ cat_finance = ConditionCategoryModel.create!(name: "Finance", industry: "finance", country_code: "CZ", verified: true)
1910
+ cat_tech = ConditionCategoryModel.create!(name: "Technology", industry: "technology", country_code: "US", verified: true)
1911
+ cat_unverified = ConditionCategoryModel.create!(name: "New Vendor", industry: "retail", country_code: "DE", verified: false)
1912
+ cat_healthcare = ConditionCategoryModel.create!(name: "Healthcare", industry: "healthcare", country_code: "UK", verified: true)
1913
+
1914
+ # Thresholds (for lookup value reference demos)
1915
+ ConditionThresholdModel.create!(key: "high_amount", threshold: 10000, label: "High Amount Threshold")
1916
+ ConditionThresholdModel.create!(key: "critical_amount", threshold: 40000, label: "Critical Amount Threshold")
1917
+ ConditionThresholdModel.create!(key: "min_budget", threshold: 3000, label: "Minimum Budget")
1918
+
1919
+ conditions = [
1920
+ # 1. COMPOUND (all): active + overdue → danger row
1921
+ { title: "Overdue budget review", status: "active", priority: "high", amount: 5000, budget_limit: 10000,
1922
+ author_id: 1, due_date: 2.weeks.ago.to_date, code: "FIN-001", description: "Quarterly review is past due.",
1923
+ showcase_condition_category_id: cat_finance.id },
1924
+
1925
+ # 2. COMPOUND (any): draft → info row
1926
+ { title: "Draft proposal", status: "draft", priority: "medium", amount: 2000, budget_limit: 10000,
1927
+ author_id: 1, due_date: 1.month.from_now.to_date, code: "PROP-001", description: "Initial proposal.",
1928
+ showcase_condition_category_id: cat_tech.id },
1929
+
1930
+ # 3. COMPOUND (any): review → info row
1931
+ { title: "Architecture review", status: "review", priority: "high", amount: 8000, budget_limit: 15000,
1932
+ author_id: 2, due_date: 1.week.from_now.to_date, code: "ARCH-010", description: "Waiting for peer review.",
1933
+ showcase_condition_category_id: cat_tech.id },
1934
+
1935
+ # 4. NOT: closed → muted + strikethrough
1936
+ { title: "Closed procurement", status: "closed", priority: "low", amount: 3000, budget_limit: 5000,
1937
+ author_id: 1, due_date: 3.months.ago.to_date, code: "PROC-099", description: "Completed and archived.",
1938
+ showcase_condition_category_id: cat_finance.id },
1939
+
1940
+ # 5. DOT-PATH: unverified category → warning row
1941
+ { title: "New vendor onboarding", status: "active", priority: "medium", amount: 1500, budget_limit: 5000,
1942
+ author_id: 1, due_date: 2.weeks.from_now.to_date, code: "VEND-003", description: "Vendor not yet verified.",
1943
+ showcase_condition_category_id: cat_unverified.id },
1944
+
1945
+ # 6. FIELD_REF: amount > budget_limit → bold row
1946
+ { title: "Over-budget project", status: "active", priority: "critical", amount: 25000, budget_limit: 15000,
1947
+ author_id: 2, due_date: 1.month.from_now.to_date, code: "PROJ-777", description: "Spending exceeds budget limit.",
1948
+ showcase_condition_category_id: cat_tech.id },
1949
+
1950
+ # 7. STARTS_WITH: code starts with URGENT
1951
+ { title: "Urgent compliance fix", status: "active", priority: "critical", amount: 12000, budget_limit: 20000,
1952
+ author_id: 1, due_date: 3.days.from_now.to_date, code: "URGENT-SEC-01", description: "Security compliance issue.",
1953
+ showcase_condition_category_id: cat_healthcare.id },
1954
+
1955
+ # 8. CONTAINS: code contains "temp" (case-insensitive)
1956
+ { title: "Temporary workaround", status: "draft", priority: "low", amount: 500, budget_limit: 5000,
1957
+ author_id: 1, due_date: nil, code: "fix-temp-patch", description: "Short-term fix, needs permanent solution.",
1958
+ showcase_condition_category_id: cat_tech.id },
1959
+
1960
+ # 9. High-value closed: triggers compound record_rule (deny update/destroy)
1961
+ { title: "Big closed deal", status: "closed", priority: "high", amount: 50000, budget_limit: 30000,
1962
+ author_id: 2, due_date: 2.months.ago.to_date, code: "DEAL-100", description: "High-value closed contract.",
1963
+ showcase_condition_category_id: cat_finance.id },
1964
+
1965
+ # 10. Approved record (collection condition demo target)
1966
+ { title: "Approved initiative", status: "approved", priority: "medium", amount: 7500, budget_limit: 10000,
1967
+ author_id: 1, due_date: 2.months.from_now.to_date, code: "INIT-042", description: "Approved after task reviews.",
1968
+ showcase_condition_category_id: cat_healthcare.id },
1969
+
1970
+ # 11. Review with tasks (collection condition: has approved task → success)
1971
+ { title: "Pending approval with tasks", status: "review", priority: "high", amount: 9000, budget_limit: 12000,
1972
+ author_id: 1, due_date: 3.weeks.from_now.to_date, code: "REV-005", description: "Has tasks for review.",
1973
+ showcase_condition_category_id: cat_finance.id },
1974
+
1975
+ # 12. ACCUMULATION: draft + contains "temp" + overdue
1976
+ { title: "Stale temp draft", status: "draft", priority: "low", amount: 100, budget_limit: 5000,
1977
+ author_id: 1, due_date: 1.month.ago.to_date, code: "TEMP-old-draft", description: "Draft with temp code, past due.",
1978
+ showcase_condition_category_id: cat_unverified.id },
1979
+
1980
+ # 13. Plain record — no rules match
1981
+ { title: "Normal active project", status: "active", priority: "medium", amount: 4000, budget_limit: 10000,
1982
+ author_id: 1, due_date: 3.months.from_now.to_date, code: "PROJ-STD-01", description: "Regular project, no special conditions.",
1983
+ showcase_condition_category_id: cat_tech.id },
1984
+
1985
+ # 14. DATE reference demo: future due date, active
1986
+ { title: "Upcoming deadline", status: "active", priority: "high", amount: 6000, budget_limit: 10000,
1987
+ author_id: 2, due_date: 2.days.from_now.to_date, code: "DEAD-001", description: "Due date is in the future — no overdue highlight.",
1988
+ showcase_condition_category_id: cat_finance.id },
1989
+
1990
+ # 15. LOOKUP: amount (15000) exceeds high_amount threshold (10000) → highlighted row
1991
+ { title: "Above lookup threshold", status: "active", priority: "medium", amount: 15000, budget_limit: 20000,
1992
+ author_id: 1, due_date: 1.month.from_now.to_date, code: "LOOK-001", description: "Amount exceeds the 'high_amount' threshold from showcase_condition_threshold table (lookup value reference).",
1993
+ showcase_condition_category_id: cat_finance.id },
1994
+
1995
+ # 16. LOOKUP: amount (8000) below high_amount threshold (10000) → no highlight
1996
+ { title: "Below lookup threshold", status: "active", priority: "low", amount: 8000, budget_limit: 20000,
1997
+ author_id: 1, due_date: 2.months.from_now.to_date, code: "LOOK-002", description: "Amount is below the lookup threshold — no highlight.",
1998
+ showcase_condition_category_id: cat_tech.id }
1999
+ ]
2000
+
2001
+ condition_records = conditions.map { |attrs| ConditionModel.create!(attrs) }
2002
+
2003
+ # Tasks for collection condition demos
2004
+ # Record 11 ("Pending approval with tasks") — has approved task → success row
2005
+ ConditionTaskModel.create!(title: "Technical review", status: "approved", reviewer_name: "Alice", showcase_condition_id: condition_records[10].id)
2006
+ ConditionTaskModel.create!(title: "Budget review", status: "pending", reviewer_name: "Bob", showcase_condition_id: condition_records[10].id)
2007
+
2008
+ # Record 10 ("Approved initiative") — all tasks approved
2009
+ ConditionTaskModel.create!(title: "Compliance check", status: "approved", reviewer_name: "Carol", showcase_condition_id: condition_records[9].id)
2010
+ ConditionTaskModel.create!(title: "Legal review", status: "approved", reviewer_name: "Dave", showcase_condition_id: condition_records[9].id)
2011
+
2012
+ # Record 3 ("Architecture review") — has tasks but none approved yet
2013
+ ConditionTaskModel.create!(title: "Code review", status: "pending", reviewer_name: "Eve", showcase_condition_id: condition_records[2].id)
2014
+ ConditionTaskModel.create!(title: "Security audit", status: "rejected", reviewer_name: "Frank", showcase_condition_id: condition_records[2].id)
2015
+
2016
+ # Record 6 ("Over-budget project") — mixed tasks
2017
+ ConditionTaskModel.create!(title: "Vendor sign-off", status: "approved", reviewer_name: "Grace", showcase_condition_id: condition_records[5].id)
2018
+ ConditionTaskModel.create!(title: "Finance approval", status: "rejected", reviewer_name: "Hank", showcase_condition_id: condition_records[5].id)
2019
+
2020
+ puts " Created #{ConditionCategoryModel.count} condition categories, #{ConditionThresholdModel.count} condition thresholds, #{ConditionModel.count} condition records, #{ConditionTaskModel.count} condition tasks"
2021
+
2022
+ # Phase: Workflow Showcase
2023
+ if LcpRuby.registry.registered?("showcase_workflow")
2024
+ WorkflowModel = LcpRuby.registry.model_for("showcase_workflow")
2025
+
2026
+ # Set current user for userstamps
2027
+ LcpRuby::Current.user = admin_user if defined?(admin_user)
2028
+
2029
+ # Create records in various workflow states to demonstrate the full lifecycle.
2030
+ # For seeding, bypass the workflow guard by setting the transition-active flag.
2031
+ bypass_guard = ->(record) { record.instance_variable_set(LcpRuby::Workflow::TransitionExecutor::TRANSITION_ACTIVE_IVAR, true) }
2032
+
2033
+ wf_records = [
2034
+ # Draft records (initial state, editable)
2035
+ { title: "New landing page design", description: "Redesign the main landing page with modern UI components.", priority: "high", category: "feature", amount: 5000.00, reviewer_id: 1, status: "draft", workflow_version: 1 },
2036
+ { title: "Fix login timeout bug", description: "Users report session timeout after 5 minutes instead of 30.", priority: "urgent", category: "bugfix", amount: 200.00, reviewer_id: 1, status: "draft", workflow_version: 1 },
2037
+ { title: "Update API documentation", description: "Add missing endpoints and update examples for v3 API.", priority: "normal", category: "documentation", amount: 0, reviewer_id: 1, status: "draft", workflow_version: 1 },
2038
+
2039
+ # Pending review (submitted, key fields locked) — has active approval requests
2040
+ { title: "Database migration to PostgreSQL", description: "Migrate from MySQL to PostgreSQL for better JSON support.", priority: "high", category: "improvement", amount: 15000.00, reviewer_id: 1, status: "pending_review", submitted_at: 2.days.ago, submitted_by: 1, submitted_by_name: "Admin User", workflow_version: 1 },
2041
+ { title: "Add dark mode support", description: "Implement dark mode toggle with CSS variables.", priority: "normal", category: "feature", amount: 3000.00, reviewer_id: 1, status: "pending_review", submitted_at: 1.day.ago, submitted_by: 1, submitted_by_name: "Admin User", workflow_version: 1 },
2042
+
2043
+ # Approved (readonly: all) — approved_amount captured at approval time via field_ref
2044
+ { title: "Performance optimization sprint", description: "Optimize database queries and add Redis caching layer.", priority: "high", category: "improvement", amount: 8000.00, approved_amount: 8000.00, reviewer_id: 1, status: "approved", submitted_at: 5.days.ago, submitted_by: 1, submitted_by_name: "Admin User", approved_at: 3.days.ago, reviewed_at: 3.days.ago, workflow_version: 1 },
2045
+
2046
+ # In progress (some fields locked)
2047
+ { title: "Mobile responsive redesign", description: "Make all pages responsive for mobile devices.", priority: "high", category: "feature", amount: 12000.00, approved_amount: 12000.00, reviewer_id: 1, status: "in_progress", submitted_at: 7.days.ago, submitted_by: 1, submitted_by_name: "Admin User", approved_at: 5.days.ago, reviewed_at: 5.days.ago, workflow_version: 1 },
2048
+
2049
+ # Rejected (can be reworked) — has completed (rejected) approval request
2050
+ { title: "Replace React with Vue", description: "Migrate frontend framework from React to Vue.js.", priority: "normal", category: "improvement", amount: 50000.00, reviewer_id: 1, status: "rejected", submitted_at: 10.days.ago, submitted_by: 1, submitted_by_name: "Admin User", reviewed_at: 8.days.ago, rejection_reason: "Cost too high. Please break into smaller phases.", workflow_version: 1 },
2051
+
2052
+ # On hold (waiting for external input)
2053
+ { title: "Third-party API integration", description: "Integrate with Stripe API for payment processing.", priority: "high", category: "feature", amount: 7500.00, approved_amount: 7500.00, reviewer_id: 1, status: "on_hold", submitted_at: 14.days.ago, submitted_by: 1, submitted_by_name: "Admin User", approved_at: 12.days.ago, reviewed_at: 12.days.ago, notes: "Waiting for Stripe sandbox credentials.", workflow_version: 1 },
2054
+
2055
+ # Completed (terminal, fully locked) — has completed (approved) approval request
2056
+ { title: "Set up CI/CD pipeline", description: "GitHub Actions workflow for automated testing and deployment.", priority: "high", category: "improvement", amount: 2000.00, approved_amount: 2000.00, reviewer_id: 1, status: "completed", submitted_at: 30.days.ago, submitted_by: 1, submitted_by_name: "Admin User", approved_at: 28.days.ago, reviewed_at: 28.days.ago, completed_at: 20.days.ago, workflow_version: 1 },
2057
+ { title: "Add unit test coverage", description: "Increase test coverage from 40% to 80%.", priority: "normal", category: "improvement", amount: 4000.00, approved_amount: 4000.00, reviewer_id: 1, status: "completed", submitted_at: 25.days.ago, submitted_by: 1, submitted_by_name: "Admin User", approved_at: 23.days.ago, reviewed_at: 23.days.ago, completed_at: 15.days.ago, workflow_version: 1 },
2058
+
2059
+ # Cancelled (terminal)
2060
+ { title: "Legacy system migration", description: "Migrate data from legacy COBOL system.", priority: "low", category: "improvement", amount: 100000.00, reviewer_id: 1, status: "cancelled", submitted_at: 20.days.ago, submitted_by: 1, submitted_by_name: "Admin User", notes: "Project cancelled due to budget cuts.", workflow_version: 1 }
2061
+ ].map do |attrs|
2062
+ record = WorkflowModel.new(attrs)
2063
+ bypass_guard.call(record)
2064
+ record.save!
2065
+ record
2066
+ end
2067
+
2068
+ puts " Created #{WorkflowModel.count} showcase_workflow records"
2069
+
2070
+ # Create audit log entries to demonstrate the audit trail
2071
+ if LcpRuby.registry.registered?("workflow_audit_log")
2072
+ AuditLogModel = LcpRuby.registry.model_for("workflow_audit_log")
2073
+
2074
+ user_snapshot = { id: 1, email: "admin@example.com", name: "Admin User", role: "admin" }
2075
+
2076
+ audit_entries = [
2077
+ # Completed request lifecycle: draft → pending_review → approved → in_progress → completed
2078
+ { record_type: "showcase_workflow", record_id: wf_records[9].id, workflow_name: "showcase_workflow", workflow_version: 1,
2079
+ transition_name: "submit", from_state: "draft", to_state: "pending_review",
2080
+ user_id: 1, user_snapshot: user_snapshot, created_at: 30.days.ago },
2081
+ { record_type: "showcase_workflow", record_id: wf_records[9].id, workflow_name: "showcase_workflow", workflow_version: 1,
2082
+ transition_name: "approve", from_state: "pending_review", to_state: "approved",
2083
+ user_id: 1, user_snapshot: user_snapshot, created_at: 28.days.ago },
2084
+ { record_type: "showcase_workflow", record_id: wf_records[9].id, workflow_name: "showcase_workflow", workflow_version: 1,
2085
+ transition_name: "start_work", from_state: "approved", to_state: "in_progress",
2086
+ user_id: 1, user_snapshot: user_snapshot, created_at: 27.days.ago },
2087
+ { record_type: "showcase_workflow", record_id: wf_records[9].id, workflow_name: "showcase_workflow", workflow_version: 1,
2088
+ transition_name: "complete", from_state: "in_progress", to_state: "completed",
2089
+ user_id: 1, user_snapshot: user_snapshot, created_at: 20.days.ago },
2090
+
2091
+ # Rejected request: draft → pending_review → rejected
2092
+ { record_type: "showcase_workflow", record_id: wf_records[7].id, workflow_name: "showcase_workflow", workflow_version: 1,
2093
+ transition_name: "submit", from_state: "draft", to_state: "pending_review",
2094
+ user_id: 1, user_snapshot: user_snapshot, created_at: 10.days.ago },
2095
+ { record_type: "showcase_workflow", record_id: wf_records[7].id, workflow_name: "showcase_workflow", workflow_version: 1,
2096
+ transition_name: "reject", from_state: "pending_review", to_state: "rejected",
2097
+ user_id: 1, user_snapshot: user_snapshot, comment: "Cost too high. Please break into smaller phases.",
2098
+ created_at: 8.days.ago },
2099
+
2100
+ # On-hold request: draft → pending_review → approved → in_progress → on_hold
2101
+ { record_type: "showcase_workflow", record_id: wf_records[8].id, workflow_name: "showcase_workflow", workflow_version: 1,
2102
+ transition_name: "submit", from_state: "draft", to_state: "pending_review",
2103
+ user_id: 1, user_snapshot: user_snapshot, created_at: 14.days.ago },
2104
+ { record_type: "showcase_workflow", record_id: wf_records[8].id, workflow_name: "showcase_workflow", workflow_version: 1,
2105
+ transition_name: "approve", from_state: "pending_review", to_state: "approved",
2106
+ user_id: 1, user_snapshot: user_snapshot, created_at: 12.days.ago },
2107
+ { record_type: "showcase_workflow", record_id: wf_records[8].id, workflow_name: "showcase_workflow", workflow_version: 1,
2108
+ transition_name: "start_work", from_state: "approved", to_state: "in_progress",
2109
+ user_id: 1, user_snapshot: user_snapshot, created_at: 11.days.ago },
2110
+ { record_type: "showcase_workflow", record_id: wf_records[8].id, workflow_name: "showcase_workflow", workflow_version: 1,
2111
+ transition_name: "hold", from_state: "in_progress", to_state: "on_hold",
2112
+ user_id: 1, user_snapshot: user_snapshot, comment: "Waiting for Stripe sandbox credentials.",
2113
+ created_at: 10.days.ago },
2114
+
2115
+ # Cancelled request: draft → pending_review → cancelled
2116
+ { record_type: "showcase_workflow", record_id: wf_records[11].id, workflow_name: "showcase_workflow", workflow_version: 1,
2117
+ transition_name: "submit", from_state: "draft", to_state: "pending_review",
2118
+ user_id: 1, user_snapshot: user_snapshot, created_at: 20.days.ago },
2119
+ { record_type: "showcase_workflow", record_id: wf_records[11].id, workflow_name: "showcase_workflow", workflow_version: 1,
2120
+ transition_name: "cancel", from_state: "pending_review", to_state: "cancelled",
2121
+ user_id: 1, user_snapshot: user_snapshot, comment: "Project cancelled due to budget cuts.",
2122
+ created_at: 19.days.ago },
2123
+
2124
+ # Pending review submissions
2125
+ { record_type: "showcase_workflow", record_id: wf_records[3].id, workflow_name: "showcase_workflow", workflow_version: 1,
2126
+ transition_name: "submit", from_state: "draft", to_state: "pending_review",
2127
+ user_id: 1, user_snapshot: user_snapshot, created_at: 2.days.ago },
2128
+ { record_type: "showcase_workflow", record_id: wf_records[4].id, workflow_name: "showcase_workflow", workflow_version: 1,
2129
+ transition_name: "submit", from_state: "draft", to_state: "pending_review",
2130
+ user_id: 1, user_snapshot: user_snapshot, created_at: 1.day.ago },
2131
+
2132
+ # System trigger example (auto_approve)
2133
+ { record_type: "showcase_workflow", record_id: wf_records[5].id, workflow_name: "showcase_workflow", workflow_version: 1,
2134
+ transition_name: "submit", from_state: "draft", to_state: "pending_review",
2135
+ user_id: 1, user_snapshot: user_snapshot, created_at: 5.days.ago },
2136
+ { record_type: "showcase_workflow", record_id: wf_records[5].id, workflow_name: "showcase_workflow", workflow_version: 1,
2137
+ transition_name: "auto_approve", from_state: "pending_review", to_state: "approved",
2138
+ user_id: 1, user_snapshot: user_snapshot,
2139
+ metadata: { triggered_by: "approval_engine" },
2140
+ created_at: 3.days.ago },
2141
+
2142
+ # Wildcard reset_to_draft example (admin emergency reset from on_hold)
2143
+ { record_type: "showcase_workflow", record_id: wf_records[2].id, workflow_name: "showcase_workflow", workflow_version: 1,
2144
+ transition_name: "reset_to_draft", from_state: "pending_review", to_state: "draft",
2145
+ user_id: 1, user_snapshot: user_snapshot,
2146
+ comment: "Emergency reset — requirements changed, needs full rework.",
2147
+ created_at: 1.day.ago }
2148
+ ]
2149
+
2150
+ audit_entries.each { |attrs| AuditLogModel.create!(attrs) }
2151
+ puts " Created #{AuditLogModel.count} workflow audit log entries"
2152
+ end
2153
+
2154
+ # Create approval request/step/task seed data to demonstrate the multi-step approval engine
2155
+ if LcpRuby.registry.registered?("workflow_approval_request") &&
2156
+ LcpRuby.registry.registered?("workflow_approval_step") &&
2157
+ LcpRuby.registry.registered?("workflow_approval_task")
2158
+ ApprovalRequestModel = LcpRuby.registry.model_for("workflow_approval_request")
2159
+ ApprovalStepModel = LcpRuby.registry.model_for("workflow_approval_step")
2160
+ ApprovalTaskModel = LcpRuby.registry.model_for("workflow_approval_task")
2161
+
2162
+ user_snapshot = { id: 1, email: "admin@example.com", name: "Admin User", role: "admin" }
2163
+
2164
+ # --- Active approval request for "Database migration to PostgreSQL" (wf_records[3]) ---
2165
+ # Step 1: reviewer_check is active with pending task
2166
+ # Step 2: admin_sign_off is pending (sequential, waits for step 1)
2167
+ req1 = ApprovalRequestModel.create!(
2168
+ record_type: "showcase_workflow", record_id: wf_records[3].id,
2169
+ workflow_name: "showcase_workflow", workflow_version: 1,
2170
+ state_name: "pending_review", strategy: "sequential",
2171
+ status: "pending", initiated_by_id: 1, initiated_by_snapshot: user_snapshot,
2172
+ restart_count: 0
2173
+ )
2174
+ step1_req1 = ApprovalStepModel.create!(
2175
+ approval_request_id: req1.id, step_name: "reviewer_check", label: "Reviewer Check",
2176
+ position: 1, status: "active", required_approvals: 1, assignment: "all",
2177
+ activated_at: 2.days.ago
2178
+ )
2179
+ step2_req1 = ApprovalStepModel.create!(
2180
+ approval_request_id: req1.id, step_name: "admin_sign_off", label: "Admin Sign-off",
2181
+ position: 2, status: "pending", required_approvals: 1, assignment: "all"
2182
+ )
2183
+ # Pending task for current user (approver_id: 1 matches reviewer_id field)
2184
+ ApprovalTaskModel.create!(
2185
+ approval_step_id: step1_req1.id, approver_id: 1,
2186
+ approver_snapshot: user_snapshot, status: "pending"
2187
+ )
2188
+
2189
+ # --- Active approval request for "Add dark mode support" (wf_records[4]) ---
2190
+ # Step 1: reviewer_check completed (approved), step 2: admin_sign_off active
2191
+ req2 = ApprovalRequestModel.create!(
2192
+ record_type: "showcase_workflow", record_id: wf_records[4].id,
2193
+ workflow_name: "showcase_workflow", workflow_version: 1,
2194
+ state_name: "pending_review", strategy: "sequential",
2195
+ status: "pending", initiated_by_id: 1, initiated_by_snapshot: user_snapshot,
2196
+ restart_count: 0
2197
+ )
2198
+ step1_req2 = ApprovalStepModel.create!(
2199
+ approval_request_id: req2.id, step_name: "reviewer_check", label: "Reviewer Check",
2200
+ position: 1, status: "completed", required_approvals: 1, assignment: "all",
2201
+ activated_at: 1.day.ago, completed_at: 12.hours.ago
2202
+ )
2203
+ step2_req2 = ApprovalStepModel.create!(
2204
+ approval_request_id: req2.id, step_name: "admin_sign_off", label: "Admin Sign-off",
2205
+ position: 2, status: "active", required_approvals: 1, assignment: "all",
2206
+ activated_at: 12.hours.ago
2207
+ )
2208
+ # Completed task (approved) from step 1
2209
+ ApprovalTaskModel.create!(
2210
+ approval_step_id: step1_req2.id, approver_id: 1,
2211
+ approver_snapshot: user_snapshot, status: "approved",
2212
+ comment: "Looks good, dark mode is a frequently requested feature.",
2213
+ decided_at: 12.hours.ago
2214
+ )
2215
+ # Pending task for admin sign-off (step 2)
2216
+ ApprovalTaskModel.create!(
2217
+ approval_step_id: step2_req2.id, approver_id: 1,
2218
+ approver_snapshot: user_snapshot, status: "pending"
2219
+ )
2220
+
2221
+ # --- Completed (approved) approval request for "Set up CI/CD pipeline" (wf_records[9]) ---
2222
+ # Both steps completed with approved tasks
2223
+ req3 = ApprovalRequestModel.create!(
2224
+ record_type: "showcase_workflow", record_id: wf_records[9].id,
2225
+ workflow_name: "showcase_workflow", workflow_version: 1,
2226
+ state_name: "pending_review", strategy: "sequential",
2227
+ status: "approved", initiated_by_id: 1, initiated_by_snapshot: user_snapshot,
2228
+ restart_count: 0, resolved_at: 28.days.ago
2229
+ )
2230
+ step1_req3 = ApprovalStepModel.create!(
2231
+ approval_request_id: req3.id, step_name: "reviewer_check", label: "Reviewer Check",
2232
+ position: 1, status: "completed", required_approvals: 1, assignment: "all",
2233
+ activated_at: 30.days.ago, completed_at: 29.days.ago
2234
+ )
2235
+ step2_req3 = ApprovalStepModel.create!(
2236
+ approval_request_id: req3.id, step_name: "admin_sign_off", label: "Admin Sign-off",
2237
+ position: 2, status: "completed", required_approvals: 1, assignment: "all",
2238
+ activated_at: 29.days.ago, completed_at: 28.days.ago
2239
+ )
2240
+ ApprovalTaskModel.create!(
2241
+ approval_step_id: step1_req3.id, approver_id: 1,
2242
+ approver_snapshot: user_snapshot, status: "approved",
2243
+ comment: "CI/CD is critical infrastructure, approved.", decided_at: 29.days.ago
2244
+ )
2245
+ ApprovalTaskModel.create!(
2246
+ approval_step_id: step2_req3.id, approver_id: 1,
2247
+ approver_snapshot: user_snapshot, status: "approved",
2248
+ comment: "Final sign-off. Budget approved.", decided_at: 28.days.ago
2249
+ )
2250
+
2251
+ # --- Completed (rejected) approval request for "Replace React with Vue" (wf_records[7]) ---
2252
+ # Step 1 completed (rejected) — short_circuit stopped further steps
2253
+ req4 = ApprovalRequestModel.create!(
2254
+ record_type: "showcase_workflow", record_id: wf_records[7].id,
2255
+ workflow_name: "showcase_workflow", workflow_version: 1,
2256
+ state_name: "pending_review", strategy: "sequential",
2257
+ status: "rejected", initiated_by_id: 1, initiated_by_snapshot: user_snapshot,
2258
+ restart_count: 0, resolved_at: 8.days.ago
2259
+ )
2260
+ step1_req4 = ApprovalStepModel.create!(
2261
+ approval_request_id: req4.id, step_name: "reviewer_check", label: "Reviewer Check",
2262
+ position: 1, status: "completed", required_approvals: 1, assignment: "all",
2263
+ activated_at: 10.days.ago, completed_at: 8.days.ago
2264
+ )
2265
+ step2_req4 = ApprovalStepModel.create!(
2266
+ approval_request_id: req4.id, step_name: "admin_sign_off", label: "Admin Sign-off",
2267
+ position: 2, status: "skipped", required_approvals: 1, assignment: "all"
2268
+ )
2269
+ ApprovalTaskModel.create!(
2270
+ approval_step_id: step1_req4.id, approver_id: 1,
2271
+ approver_snapshot: user_snapshot, status: "rejected",
2272
+ comment: "Cost too high. Please break into smaller phases.",
2273
+ decided_at: 8.days.ago
2274
+ )
2275
+
2276
+ # --- Delegated task example for "Database migration" (add a delegated task to req1) ---
2277
+ # Shows delegation: original task delegated, new task created for delegate
2278
+ delegated_task = ApprovalTaskModel.create!(
2279
+ approval_step_id: step1_req1.id, approver_id: 1,
2280
+ approver_snapshot: user_snapshot, status: "delegated",
2281
+ comment: "Delegating to editor for technical review.",
2282
+ decided_at: 1.day.ago
2283
+ )
2284
+ ApprovalTaskModel.create!(
2285
+ approval_step_id: step1_req1.id, approver_id: 1,
2286
+ approver_snapshot: { id: 1, email: "admin@example.com", name: "Admin User (delegate)", role: "admin" },
2287
+ status: "pending", delegated_from_id: delegated_task.id
2288
+ )
2289
+
2290
+ puts " Created #{ApprovalRequestModel.count} approval requests, #{ApprovalStepModel.count} approval steps, #{ApprovalTaskModel.count} approval tasks"
2291
+ end
2292
+
2293
+ LcpRuby::Current.user = nil
2294
+ end
2295
+
2296
+ # Phase: Background Jobs Showcase
2297
+ if LcpRuby.registry.registered?("showcase_job_execution")
2298
+ JobExecModel = LcpRuby.registry.model_for("showcase_job_execution")
2299
+
2300
+ user_snapshot = { id: 1, email: "admin@example.com", name: "Admin User", role: "admin" }
2301
+ now = Time.current
2302
+
2303
+ [
2304
+ # Completed successfully — cleanup job
2305
+ {
2306
+ job_type: "showcase_cleanup",
2307
+ status: "completed",
2308
+ progress: 100,
2309
+ attempt: 1,
2310
+ params: { model: "showcase_job_execution", scope: "completed_jobs", operation: "count" }.to_json,
2311
+ log: [
2312
+ { at: (now - 2.hours).iso8601, level: "info", message: "Starting run_scope: showcase_job_execution.completed_jobs" },
2313
+ { at: (now - 2.hours + 3.seconds).iso8601, level: "info", message: "Operation count completed: 12 records matched" }
2314
+ ].to_json,
2315
+ started_at: now - 2.hours,
2316
+ last_attempt_at: now - 2.hours,
2317
+ completed_at: now - 2.hours + 3.seconds,
2318
+ triggered_by_id: 1,
2319
+ triggered_by_snapshot: user_snapshot.to_json,
2320
+ unique_key: Digest::SHA256.hexdigest("job_type=showcase_cleanup")[0..63],
2321
+ active_job_id: SecureRandom.uuid,
2322
+ created_at: now - 2.hours - 5.seconds
2323
+ },
2324
+ # Completed — webhook job
2325
+ {
2326
+ job_type: "showcase_webhook",
2327
+ status: "completed",
2328
+ progress: 100,
2329
+ attempt: 1,
2330
+ params: { source: "lcp_showcase", job_type: "webhook_demo" }.to_json,
2331
+ log: [
2332
+ { at: (now - 1.hour).iso8601, level: "info", message: "POST https://httpbin.org/post" },
2333
+ { at: (now - 1.hour + 2.seconds).iso8601, level: "info", message: "Response: 200 OK (1.8s)" }
2334
+ ].to_json,
2335
+ started_at: now - 1.hour,
2336
+ last_attempt_at: now - 1.hour,
2337
+ completed_at: now - 1.hour + 2.seconds,
2338
+ triggered_by_id: 1,
2339
+ triggered_by_snapshot: user_snapshot.to_json,
2340
+ active_job_id: SecureRandom.uuid,
2341
+ created_at: now - 1.hour - 3.seconds
2342
+ },
2343
+ # Completed — multi-step job
2344
+ {
2345
+ job_type: "showcase_multi_step",
2346
+ status: "completed",
2347
+ progress: 100,
2348
+ attempt: 1,
2349
+ log: [
2350
+ { at: (now - 45.minutes).iso8601, level: "info", message: "Step 1/3: fire_event(step_one_started)" },
2351
+ { at: (now - 45.minutes + 1.second).iso8601, level: "info", message: "Step 2/3: fire_event(step_two_started)" },
2352
+ { at: (now - 45.minutes + 2.seconds).iso8601, level: "info", message: "Step 3/3: fire_event(step_three_started)" },
2353
+ { at: (now - 45.minutes + 3.seconds).iso8601, level: "info", message: "All 3 steps completed" }
2354
+ ].to_json,
2355
+ started_at: now - 45.minutes,
2356
+ last_attempt_at: now - 45.minutes,
2357
+ completed_at: now - 45.minutes + 3.seconds,
2358
+ triggered_by_id: 1,
2359
+ triggered_by_snapshot: user_snapshot.to_json,
2360
+ active_job_id: SecureRandom.uuid,
2361
+ created_at: now - 45.minutes - 2.seconds
2362
+ },
2363
+ # Failed with retries exhausted — webhook to bad endpoint
2364
+ {
2365
+ job_type: "showcase_webhook",
2366
+ status: "failed",
2367
+ progress: 0,
2368
+ attempt: 2,
2369
+ error_message: "Net::ReadTimeout: execution expired after 10s",
2370
+ params: { source: "lcp_showcase", job_type: "webhook_demo" }.to_json,
2371
+ log: [
2372
+ { at: (now - 3.hours).iso8601, level: "info", message: "POST https://httpbin.org/post", attempt: 1 },
2373
+ { at: (now - 3.hours + 10.seconds).iso8601, level: "error", message: "Net::ReadTimeout: execution expired after 10s", attempt: 1 },
2374
+ { at: (now - 3.hours + 40.seconds).iso8601, level: "info", message: "Retry 2/1 — re-enqueuing with 30s delay", attempt: 1 },
2375
+ { at: (now - 2.hours - 30.minutes).iso8601, level: "info", message: "POST https://httpbin.org/post", attempt: 2 },
2376
+ { at: (now - 2.hours - 30.minutes + 10.seconds).iso8601, level: "error", message: "Net::ReadTimeout: execution expired after 10s", attempt: 2 },
2377
+ { at: (now - 2.hours - 30.minutes + 10.seconds).iso8601, level: "error", message: "Max retries (1) exhausted — marking as failed" }
2378
+ ].to_json,
2379
+ started_at: now - 3.hours,
2380
+ last_attempt_at: now - 2.hours - 30.minutes,
2381
+ triggered_by_id: 1,
2382
+ triggered_by_snapshot: user_snapshot.to_json,
2383
+ active_job_id: SecureRandom.uuid,
2384
+ created_at: now - 3.hours - 2.seconds
2385
+ },
2386
+ # Currently running — long cleanup
2387
+ {
2388
+ job_type: "showcase_cleanup",
2389
+ status: "running",
2390
+ progress: 65,
2391
+ attempt: 1,
2392
+ params: { model: "showcase_job_execution", scope: "completed_jobs", operation: "destroy_all", batch_size: 100 }.to_json,
2393
+ log: [
2394
+ { at: (now - 5.minutes).iso8601, level: "info", message: "Starting run_scope: showcase_job_execution.completed_jobs" },
2395
+ { at: (now - 4.minutes).iso8601, level: "info", message: "Batch 1: destroyed 100 records" },
2396
+ { at: (now - 3.minutes).iso8601, level: "info", message: "Batch 2: destroyed 100 records" }
2397
+ ].to_json,
2398
+ started_at: now - 5.minutes,
2399
+ last_attempt_at: now - 5.minutes,
2400
+ triggered_by_id: 1,
2401
+ triggered_by_snapshot: user_snapshot.to_json,
2402
+ active_job_id: SecureRandom.uuid,
2403
+ created_at: now - 5.minutes - 1.second
2404
+ },
2405
+ # Pending — queued but not started
2406
+ {
2407
+ job_type: "showcase_event_triggered",
2408
+ status: "pending",
2409
+ progress: 0,
2410
+ attempt: 0,
2411
+ params: { event: "showcase_job_triggered" }.to_json,
2412
+ log: [].to_json,
2413
+ scheduled_at: now + 10.minutes,
2414
+ triggered_by_id: 1,
2415
+ triggered_by_snapshot: user_snapshot.to_json,
2416
+ active_job_id: SecureRandom.uuid,
2417
+ created_at: now - 30.seconds
2418
+ },
2419
+ # Cancelled — user requested cancellation
2420
+ {
2421
+ job_type: "showcase_multi_step",
2422
+ status: "cancelled",
2423
+ progress: 33,
2424
+ attempt: 1,
2425
+ cancellation_requested: true,
2426
+ log: [
2427
+ { at: (now - 6.hours).iso8601, level: "info", message: "Step 1/3: fire_event(step_one_started)" },
2428
+ { at: (now - 6.hours + 1.second).iso8601, level: "info", message: "Cancellation requested — stopping after step 1" },
2429
+ { at: (now - 6.hours + 1.second).iso8601, level: "warn", message: "Job cancelled by user" }
2430
+ ].to_json,
2431
+ started_at: now - 6.hours,
2432
+ last_attempt_at: now - 6.hours,
2433
+ completed_at: now - 6.hours + 1.second,
2434
+ triggered_by_id: 1,
2435
+ triggered_by_snapshot: user_snapshot.to_json,
2436
+ active_job_id: SecureRandom.uuid,
2437
+ created_at: now - 6.hours - 3.seconds
2438
+ },
2439
+ # Completed with unique key — deduplicated
2440
+ {
2441
+ job_type: "showcase_cleanup",
2442
+ status: "completed",
2443
+ progress: 100,
2444
+ attempt: 1,
2445
+ params: { model: "showcase_job_execution", scope: "failed_jobs", operation: "count" }.to_json,
2446
+ log: [
2447
+ { at: (now - 30.minutes).iso8601, level: "info", message: "Starting run_scope: showcase_job_execution.failed_jobs" },
2448
+ { at: (now - 30.minutes + 1.second).iso8601, level: "info", message: "Operation count completed: 3 records matched" }
2449
+ ].to_json,
2450
+ started_at: now - 30.minutes,
2451
+ last_attempt_at: now - 30.minutes,
2452
+ completed_at: now - 30.minutes + 1.second,
2453
+ triggered_by_id: 1,
2454
+ triggered_by_snapshot: user_snapshot.to_json,
2455
+ unique_key: Digest::SHA256.hexdigest("job_type=showcase_cleanup|scope=failed_jobs")[0..63],
2456
+ active_job_id: SecureRandom.uuid,
2457
+ created_at: now - 30.minutes - 2.seconds
2458
+ }
2459
+ ].each { |attrs| JobExecModel.create!(attrs) }
2460
+
2461
+ puts " Created #{JobExecModel.count} job execution records (background jobs showcase)"
2462
+ end
2463
+
2464
+ # Phase: Audited Records Showcase
2465
+ if LcpRuby.registry.registered?("showcase_audited_record")
2466
+ AuditedRecordModel = LcpRuby.registry.model_for("showcase_audited_record")
2467
+
2468
+ LcpRuby::Current.user = admin_user if defined?(admin_user)
2469
+
2470
+ [
2471
+ { title: "Server Configuration Update", description: "Updated production server settings for improved throughput.", status: "active", priority: "high", amount: 2500.00, completion: 85.0 },
2472
+ { title: "Security Audit Report", description: "Quarterly security audit findings and remediation plan.", status: "active", priority: "high", amount: 15000.00, completion: 100.0 },
2473
+ { title: "API Rate Limiting Policy", description: "New rate limiting rules for public API endpoints.", status: "draft", priority: "normal", amount: 500.00, completion: 30.0 },
2474
+ { title: "Database Backup Procedure", description: "Daily automated backup with 30-day retention.", status: "active", priority: "normal", amount: 1200.00, completion: 95.0 },
2475
+ { title: "Legacy Code Cleanup", description: "Remove deprecated modules from the codebase.", status: "archived", priority: "low", amount: 3000.00, completion: 100.0 },
2476
+ { title: "Performance Benchmarks", description: "Baseline performance metrics for all API endpoints.", status: "draft", priority: "high", amount: 800.00, completion: 10.0 }
2477
+ ].each { |attrs| AuditedRecordModel.create!(attrs) }
2478
+
2479
+ # Simulate an update to generate audit trail
2480
+ record = AuditedRecordModel.first
2481
+ record.update!(status: "archived", completion: 100.0) if record
2482
+
2483
+ LcpRuby::Current.user = nil
2484
+ puts " Created #{AuditedRecordModel.count} audited records (with audit trail)"
2485
+ end
2486
+
2487
+ # Phase: DB-Stored Page Definitions
2488
+ if LcpRuby.registry.registered?("page_config")
2489
+ PageConfigModel = LcpRuby.registry.model_for("page_config")
2490
+
2491
+ [
2492
+ {
2493
+ name: "custom_employee_dashboard",
2494
+ definition: {
2495
+ "name" => "custom_employee_dashboard",
2496
+ "slug" => "employee-dashboard",
2497
+ "model" => "employee",
2498
+ "layout" => "semantic",
2499
+ "zones" => [
2500
+ { "name" => "main", "presenter" => "employees", "area" => "main" },
2501
+ { "name" => "department_kpi", "type" => "widget", "area" => "sidebar", "width" => 4,
2502
+ "widget" => { "type" => "kpi_card", "model" => "department", "aggregate" => "count", "label_key" => "Departments" } }
2503
+ ]
2504
+ },
2505
+ active: false,
2506
+ notes: "Example DB-stored composite page — activate to override auto-generated employee page."
2507
+ },
2508
+ {
2509
+ name: "project_overview_db",
2510
+ definition: {
2511
+ "name" => "project_overview_db",
2512
+ "slug" => "project-overview",
2513
+ "model" => "project",
2514
+ "layout" => "semantic",
2515
+ "zones" => [
2516
+ { "name" => "main", "presenter" => "projects", "area" => "main" },
2517
+ { "name" => "project_count", "type" => "widget", "area" => "sidebar", "width" => 4,
2518
+ "widget" => { "type" => "kpi_card", "model" => "project", "aggregate" => "count", "label_key" => "Total Projects" } }
2519
+ ]
2520
+ },
2521
+ active: false,
2522
+ notes: "Example DB-stored page for projects with a KPI sidebar."
2523
+ }
2524
+ ].each { |attrs| PageConfigModel.create!(attrs) }
2525
+
2526
+ puts " Created #{PageConfigModel.count} page config records"
2527
+ end
2528
+
2529
+ # Phase: Feature Catalog — Auditing entries
2530
+
2531
+ # Phase: Feature Catalog — DB-Stored Page Definitions entries
2532
+
2533
+ # Phase: Feature Catalog — Custom Types entries
2534
+
2535
+ # Phase: Model Inheritance Showcase
2536
+ if LcpRuby.registry.registered?("showcase_report")
2537
+ ReportModel = LcpRuby.registry.model_for("showcase_report")
2538
+
2539
+ [
2540
+ { title: "Q1 Revenue Analysis", description: "Quarterly revenue breakdown by region and product line.", status: "published", priority: "high", category: "financial", tags: "Q1, revenue, quarterly", report_date: Date.current - 30, author_name: "Alice Johnson", confidential: false, page_count: 24 },
2541
+ { title: "Cloud Migration Technical Review", description: "Architecture assessment for migrating on-premise services to AWS.", status: "approved", priority: "high", category: "technical", tags: "cloud, aws, migration", report_date: Date.current - 14, author_name: "Bob Smith", confidential: false, page_count: 42 },
2542
+ { title: "Employee Satisfaction Survey 2024", description: "Annual employee engagement and satisfaction survey results.", status: "review", priority: "normal", category: "hr", tags: "survey, engagement, annual", report_date: Date.current - 7, author_name: "Carol Davis", confidential: true, page_count: 18 },
2543
+ { title: "GDPR Compliance Audit", description: "Data privacy compliance assessment for European operations.", status: "draft", priority: "high", category: "legal", tags: "gdpr, privacy, compliance", report_date: Date.current, author_name: "David Lee", confidential: true, page_count: 0 },
2544
+ { title: "Product Roadmap H2 2024", description: "Feature planning and prioritization for the second half of the year.", status: "published", priority: "normal", category: "general", tags: "roadmap, planning, H2", report_date: Date.current - 60, author_name: "Eve Martinez", confidential: false, page_count: 15 },
2545
+ { title: "Security Penetration Test Results", description: "External pen-test findings and remediation recommendations.", status: "approved", priority: "high", category: "technical", tags: "security, pentest, vulnerabilities", report_date: Date.current - 3, author_name: "Frank Wilson", confidential: true, page_count: 31 },
2546
+ { title: "Budget Forecast FY2025", description: "Projected expenses and revenue for the next fiscal year.", status: "draft", priority: "normal", category: "financial", tags: "budget, forecast, FY2025", report_date: Date.current + 30, author_name: "Grace Chen", confidential: false, page_count: 0 },
2547
+ { title: "Onboarding Process Improvement", description: "Recommendations for streamlining new employee onboarding.", status: "review", priority: "low", category: "hr", tags: "onboarding, process, improvement", report_date: Date.current - 21, author_name: "Henry Brown", confidential: false, page_count: 12 }
2548
+ ].each { |attrs| ReportModel.create!(attrs) }
2549
+
2550
+ puts " Created #{ReportModel.count} showcase report records"
2551
+ end
2552
+
2553
+ if LcpRuby.registry.registered?("showcase_memo")
2554
+ MemoModel = LcpRuby.registry.model_for("showcase_memo")
2555
+
2556
+ [
2557
+ { title: "Office Closure — Holiday Schedule", description: "Reminder: the office will be closed December 24-26. Remote work is available for urgent matters.", status: "active", priority: "normal", recipient: "All Employees", urgency: "routine", due_date: Date.current + 10 },
2558
+ { title: "Production Incident: Database Failover", description: "Production DB failed over to replica at 03:22 UTC. Investigating root cause. All services restored.", status: "active", priority: "high", recipient: "Engineering Team", urgency: "immediate", due_date: Date.current, read_at: Time.current - 2.hours },
2559
+ { title: "Quarterly Planning Kick-off", description: "Please prepare your team's OKRs for Q2 by end of week. Template attached.", status: "active", priority: "normal", recipient: "Department Heads", urgency: "normal", due_date: Date.current + 5 },
2560
+ { title: "New VPN Configuration Required", description: "IT is rolling out new VPN certificates. Install by Friday to maintain remote access.", status: "active", priority: "normal", recipient: "Remote Workers", urgency: "urgent", due_date: Date.current + 3 },
2561
+ { title: "Parking Lot Maintenance Notice", description: "Section B of the parking lot will be resurfaced next week. Use Section C temporarily.", status: "archived", priority: "low", recipient: "Building Occupants", urgency: "routine", due_date: Date.current - 7, read_at: Time.current - 5.days },
2562
+ { title: "Budget Approval Needed — Marketing Campaign", description: "Requesting sign-off on the $50K Q2 digital marketing campaign budget.", status: "draft", priority: "high", recipient: "CFO", urgency: "urgent", due_date: Date.current + 2 },
2563
+ { title: "Team Lunch — Friday Celebration", description: "Join us for a team lunch to celebrate the successful product launch!", status: "active", priority: "low", recipient: "Product Team", urgency: "routine", due_date: Date.current + 1, read_at: Time.current - 1.hour },
2564
+ { title: "Security Training Reminder", description: "Annual security awareness training is due by end of month. Complete the online module.", status: "active", priority: "normal", recipient: "All Employees", urgency: "normal", due_date: Date.current + 15 }
2565
+ ].each { |attrs| MemoModel.create!(attrs) }
2566
+
2567
+ puts " Created #{MemoModel.count} showcase memo records"
2568
+ end
2569
+
2570
+ # Phase: STI (Single Table Inheritance) Showcase
2571
+ if LcpRuby.registry.registered?("showcase_person")
2572
+ PersonModel = LcpRuby.registry.model_for("showcase_person")
2573
+
2574
+ [
2575
+ { first_name: "Alice", last_name: "Johnson", display_name: "Alice Johnson", email: "alice@example.com", phone: "+1-555-0101", job_title: "CTO", status: "active", source: "manual", notes: "Key decision maker for technology purchases." },
2576
+ { first_name: "Bob", last_name: "Smith", display_name: "Bob Smith", email: "bob.smith@acme.com", phone: "+1-555-0102", job_title: "Sales Director", status: "active", source: "referral", notes: "Referred by Alice Johnson." },
2577
+ { first_name: "Carol", last_name: "Davis", display_name: "Carol Davis", email: "carol.d@startup.io", phone: "+44-20-7946-0958", job_title: "Product Manager", status: "active", source: "import" },
2578
+ { first_name: "David", last_name: "Lee", display_name: "David Lee", email: "david.lee@corp.com", phone: "+1-555-0104", job_title: "VP Engineering", status: "inactive", source: "api", notes: "Left the company. Contact successor." },
2579
+ { first_name: "Eve", last_name: "Martinez", display_name: "Eve Martinez", email: "eve@consulting.com", phone: "+1-555-0105", job_title: "Senior Consultant", status: "active", source: "manual", birth_date: Date.new(1985, 3, 15) },
2580
+ { first_name: "Frank", last_name: "Wilson", display_name: "Frank Wilson", email: "frank.w@techco.com", phone: "+49-30-12345678", job_title: "CEO", status: "active", source: "referral", birth_date: Date.new(1978, 11, 22) }
2581
+ ].each { |attrs| PersonModel.create!(attrs) }
2582
+
2583
+ puts " Created #{PersonModel.count} showcase person records"
2584
+ end
2585
+
2586
+ if LcpRuby.registry.registered?("showcase_organization")
2587
+ OrgModel = LcpRuby.registry.model_for("showcase_organization")
2588
+
2589
+ [
2590
+ { company_name: "Acme Corporation", display_name: "Acme Corporation", email: "info@acme.com", phone: "+1-555-1000", website: "https://acme.com", registration_number: "US-12345678", industry: "technology", status: "active", source: "manual", notes: "Enterprise customer since 2020." },
2591
+ { company_name: "Global Finance Ltd", display_name: "Global Finance Ltd", email: "contact@globalfinance.com", phone: "+44-20-7946-0100", website: "https://globalfinance.com", registration_number: "UK-87654321", industry: "finance", status: "active", source: "import" },
2592
+ { company_name: "HealthFirst Medical", display_name: "HealthFirst Medical", email: "partnerships@healthfirst.org", phone: "+1-555-2000", website: "https://healthfirst.org", registration_number: "US-99887766", industry: "healthcare", status: "active", source: "referral", notes: "Interested in our enterprise plan." },
2593
+ { company_name: "TechStart Inc", display_name: "TechStart Inc", email: "hello@techstart.io", phone: "+1-555-3000", website: "https://techstart.io", registration_number: "US-11223344", industry: "technology", status: "active", source: "api" },
2594
+ { company_name: "Old Manufacturing Co", display_name: "Old Manufacturing Co", email: "sales@oldmfg.com", phone: "+1-555-4000", industry: "manufacturing", status: "archived", source: "manual", notes: "Acquired by Acme Corporation. No longer active." },
2595
+ { company_name: "City University", display_name: "City University", email: "it@cityuni.edu", phone: "+1-555-5000", website: "https://cityuni.edu", registration_number: "EDU-55667788", industry: "education", status: "active", source: "manual" }
2596
+ ].each { |attrs| OrgModel.create!(attrs) }
2597
+
2598
+ puts " Created #{OrgModel.count} showcase organization records"
2599
+ end
2600
+
2601
+ # STI Feature Catalog entries
2602
+
2603
+ # === Custom Render Blocks showcase data ===
2604
+ if LcpRuby.registry.registered?("showcase_custom_render")
2605
+ CustomRenderModel = LcpRuby.registry.model_for("showcase_custom_render")
2606
+
2607
+ [
2608
+ { name: "Bratislava Office", description: "Main headquarters in the capital city. Handles all central operations and management.", latitude: 48.1486, longitude: 17.1077, status: "active", priority: "high", tags_json: '["headquarters","slovakia","main-office"]', score: 92, category: "infrastructure", due_date: Date.current + 30, notes: "Renovations planned for Q3." },
2609
+ { name: "Prague Branch", description: "Czech Republic satellite office focusing on client relationships and regional sales.", latitude: 50.0755, longitude: 14.4378, status: "active", priority: "medium", tags_json: '["branch","czech-republic","sales"]', score: 78, category: "infrastructure", due_date: Date.current + 60 },
2610
+ { name: "Vienna Research Lab", description: "R&D facility for experimental projects and prototyping.", latitude: 48.2082, longitude: 16.3738, status: "review", priority: "critical", tags_json: '["research","austria","lab","r-and-d"]', score: 65, category: "research", due_date: Date.current - 5, notes: "Budget review overdue. Needs immediate attention." },
2611
+ { name: "Budapest Hub", description: "Engineering hub for backend infrastructure and platform development.", latitude: 47.4979, longitude: 19.0402, status: "active", priority: "high", tags_json: '["engineering","hungary","platform"]', score: 88, category: "engineering" },
2612
+ { name: "Warsaw Office", description: "New office opening to support growing Polish market.", latitude: 52.2297, longitude: 21.0122, status: "draft", priority: "medium", tags_json: '["new","poland","expansion"]', score: 30, category: "infrastructure", due_date: Date.current + 120 },
2613
+ { name: "Berlin Design Studio", description: "Creative team workspace for UX research and product design.", latitude: 52.5200, longitude: 13.4050, status: "active", priority: "low", tags_json: '["design","germany","ux"]', score: 71, category: "design" },
2614
+ { name: "Remote Team Alpha", description: "Fully remote distributed team working on mobile applications.", status: "active", priority: "medium", tags_json: '["remote","mobile","distributed"]', score: 85, category: "engineering", notes: "No physical location — fully distributed." },
2615
+ { name: "Legacy System Migration", description: "Project to migrate legacy monolith to microservices architecture.", status: "completed", priority: "critical", tags_json: '["migration","legacy","microservices"]', score: 100, category: "engineering", due_date: Date.current - 30 },
2616
+ { name: "Annual Report 2025", description: "Preparation of the company-wide annual report with financial summaries.", status: "archived", priority: "low", tags_json: '["report","annual","finance"]', score: 50, category: "management" },
2617
+ { name: "Security Audit Q1", description: "Quarterly security assessment covering infrastructure and application layers.", status: "review", priority: "high", tags_json: '["security","audit","compliance"]', score: 45, category: "security", due_date: Date.current + 14 }
2618
+ ].each { |attrs| CustomRenderModel.create!(attrs) }
2619
+
2620
+ puts " Created #{CustomRenderModel.count} custom render showcase records"
2621
+ end
2622
+
2623
+ # Custom Render Blocks Feature Catalog entries
2624
+
2625
+ # Phase: Feature Catalog — Batch Actions entries
2626
+
2627
+ # === Monitoring & Error Log Showcase Data ===
2628
+ if LcpRuby.registry.registered?("lcp_error_log")
2629
+ ErrorLogModel = LcpRuby.registry.model_for("lcp_error_log")
2630
+
2631
+ now = Time.current
2632
+ [
2633
+ {
2634
+ fingerprint: Digest::SHA256.hexdigest("ActiveRecord::RecordNotFound|app/controllers/lcp_ruby/resources_controller.rb:42")[0..63],
2635
+ error_class: "ActiveRecord::RecordNotFound",
2636
+ message: "Couldn't find LcpRuby::Dynamic::Employee with 'id'=999",
2637
+ subsystem: "controller",
2638
+ stacktrace: [
2639
+ "app/controllers/lcp_ruby/resources_controller.rb:42:in `show'",
2640
+ "actionpack/lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action'"
2641
+ ],
2642
+ context: { url: "/showcase/employees/999", user_id: 1, method: "GET" },
2643
+ occurrence_count: 12,
2644
+ first_occurred_at: now - 7.days,
2645
+ last_occurred_at: now - 2.hours
2646
+ },
2647
+ {
2648
+ fingerprint: Digest::SHA256.hexdigest("NoMethodError|app/renderers/custom_chart.rb:15")[0..63],
2649
+ error_class: "NoMethodError",
2650
+ message: "undefined method `data_points' for nil:NilClass",
2651
+ subsystem: "renderer",
2652
+ stacktrace: [
2653
+ "app/renderers/custom_chart.rb:15:in `render'",
2654
+ "lib/lcp_ruby/display/renderer_registry.rb:38:in `call'"
2655
+ ],
2656
+ context: { model: "article", field: "analytics_chart", presenter: "articles" },
2657
+ occurrence_count: 5,
2658
+ first_occurred_at: now - 3.days,
2659
+ last_occurred_at: now - 1.day
2660
+ },
2661
+ {
2662
+ fingerprint: Digest::SHA256.hexdigest("Timeout::Error|lib/lcp_ruby/data_source/rest_json.rb:78")[0..63],
2663
+ error_class: "Timeout::Error",
2664
+ message: "execution expired — GET https://api.example.com/weather/stations",
2665
+ subsystem: "data_source",
2666
+ stacktrace: [
2667
+ "lib/lcp_ruby/data_source/rest_json.rb:78:in `fetch_records'",
2668
+ "lib/lcp_ruby/data_source/resilient_wrapper.rb:22:in `call'"
2669
+ ],
2670
+ context: { adapter: "rest_json", endpoint: "https://api.example.com/weather/stations", timeout: 5 },
2671
+ occurrence_count: 47,
2672
+ first_occurred_at: now - 14.days,
2673
+ last_occurred_at: now - 30.minutes
2674
+ },
2675
+ {
2676
+ fingerprint: Digest::SHA256.hexdigest("Pundit::NotAuthorizedError|app/controllers/lcp_ruby/application_controller.rb:55")[0..63],
2677
+ error_class: "Pundit::NotAuthorizedError",
2678
+ message: "not allowed to update? this LcpRuby::Dynamic::ShowcasePermission",
2679
+ subsystem: "authorization",
2680
+ stacktrace: [
2681
+ "app/controllers/lcp_ruby/application_controller.rb:55:in `authorize_action!'",
2682
+ "app/controllers/lcp_ruby/resources_controller.rb:88:in `update'"
2683
+ ],
2684
+ context: { user_id: 3, role: "viewer", action: "update", model: "showcase_permission" },
2685
+ occurrence_count: 3,
2686
+ first_occurred_at: now - 1.day,
2687
+ last_occurred_at: now - 6.hours
2688
+ },
2689
+ {
2690
+ fingerprint: Digest::SHA256.hexdigest("ActiveRecord::StatementInvalid|lib/lcp_ruby/virtual_columns/builder.rb:102")[0..63],
2691
+ error_class: "ActiveRecord::StatementInvalid",
2692
+ message: "SQLite3::SQLException: no such column: departments.budget_total",
2693
+ subsystem: "virtual_columns",
2694
+ stacktrace: [
2695
+ "lib/lcp_ruby/virtual_columns/builder.rb:102:in `inject_subquery'",
2696
+ "lib/lcp_ruby/virtual_columns/collector.rb:45:in `apply!'"
2697
+ ],
2698
+ context: { model: "department", virtual_column: "budget_total", sql_fragment: "SUM(projects.budget)" },
2699
+ occurrence_count: 1,
2700
+ first_occurred_at: now - 5.hours,
2701
+ last_occurred_at: now - 5.hours
2702
+ },
2703
+ {
2704
+ fingerprint: Digest::SHA256.hexdigest("JSON::ParserError|lib/lcp_ruby/custom_fields/utils.rb:28")[0..63],
2705
+ error_class: "JSON::ParserError",
2706
+ message: "unexpected token at '{invalid json'",
2707
+ subsystem: "custom_fields",
2708
+ stacktrace: [
2709
+ "lib/lcp_ruby/custom_fields/utils.rb:28:in `parse_json_value'",
2710
+ "lib/lcp_ruby/custom_fields/applicator.rb:65:in `apply_accessors!'"
2711
+ ],
2712
+ context: { model: "employee", field: "metadata", raw_value: "{invalid json" },
2713
+ occurrence_count: 8,
2714
+ first_occurred_at: now - 10.days,
2715
+ last_occurred_at: now - 4.hours
2716
+ },
2717
+ {
2718
+ fingerprint: Digest::SHA256.hexdigest("ActionController::RoutingError|middleware/routing.rb:12")[0..63],
2719
+ error_class: "ActionController::RoutingError",
2720
+ message: "No route matches [GET] '/showcase/nonexistent-page'",
2721
+ subsystem: "routing",
2722
+ stacktrace: [
2723
+ "actionpack/lib/action_dispatch/middleware/debug_exceptions.rb:65",
2724
+ "actionpack/lib/action_dispatch/middleware/show_exceptions.rb:33"
2725
+ ],
2726
+ context: { url: "/showcase/nonexistent-page", method: "GET" },
2727
+ occurrence_count: 22,
2728
+ first_occurred_at: now - 30.days,
2729
+ last_occurred_at: now - 1.hour
2730
+ },
2731
+ {
2732
+ fingerprint: Digest::SHA256.hexdigest("Net::ReadTimeout|lib/lcp_ruby/background_jobs/executor_job.rb:34")[0..63],
2733
+ error_class: "Net::ReadTimeout",
2734
+ message: "Net::ReadTimeout with #<TCPSocket:(closed)>",
2735
+ subsystem: "background_jobs",
2736
+ stacktrace: [
2737
+ "lib/lcp_ruby/background_jobs/executor_job.rb:34:in `perform'",
2738
+ "activejob/lib/active_job/execution.rb:53:in `perform_now'"
2739
+ ],
2740
+ context: { job_name: "sync_weather_data", attempt: 3, max_retries: 5 },
2741
+ occurrence_count: 15,
2742
+ first_occurred_at: now - 5.days,
2743
+ last_occurred_at: now - 3.hours
2744
+ }
2745
+ ].each { |attrs| ErrorLogModel.create!(attrs) }
2746
+
2747
+ puts " Created #{ErrorLogModel.count} error log entries (monitoring showcase)"
2748
+ end
2749
+
2750
+ # Monitoring Feature Catalog entries
2751
+
2752
+ # Phase: Export Features
2753
+
2754
+ # Phase: Import Features
2755
+
2756
+ # === Form Submit Actions showcase data ===
2757
+
2758
+ if LcpRuby.registry.registered?("showcase_form_action")
2759
+ FormActionModel = LcpRuby.registry.model_for("showcase_form_action")
2760
+ form_action_records = [
2761
+ { title: "Fix login page CSS", description: "The login button is misaligned on mobile Safari.", status: "draft", priority: "high", category: "bug" },
2762
+ { title: "Add dark mode support", description: "Implement system-preference-aware dark mode toggle.", status: "draft", priority: "normal", category: "feature" },
2763
+ { title: "Update API docs", description: "Document the new /v2/users endpoint.", status: "submitted", priority: "low", category: "docs" },
2764
+ { title: "Refactor auth middleware", description: "Extract token validation into a shared concern.", status: "approved", priority: "normal", category: "chore" },
2765
+ { title: "Write onboarding guide", description: "Step-by-step guide for new developers.", status: "published", priority: "normal", category: "docs", published_at: 2.days.ago },
2766
+ { title: "Fix N+1 in dashboard", description: "Add eager loading for the stats widget queries.", status: "draft", priority: "urgent", category: "bug" },
2767
+ { title: "Add CSV export", description: "Allow users to export filtered results as CSV.", status: "submitted", priority: "high", category: "feature" },
2768
+ { title: "Clean up dead code", description: "Remove unused helper methods from ApplicationHelper.", status: "rejected", priority: "low", category: "chore" }
2769
+ ]
2770
+ form_action_records.each do |attrs|
2771
+ target_status = attrs.delete(:status)
2772
+ record = FormActionModel.create!(attrs.merge(status: "draft"))
2773
+ # Bypass workflow guard to set non-draft status for seed data
2774
+ FormActionModel.where(id: record.id).update_all(status: target_status) if target_status != "draft"
2775
+ end
2776
+ puts " Added #{form_action_records.size} Form Action Task demo records"
2777
+ end
2778
+
2779
+ # Phase: Form Submit Actions Feature Catalog
2780
+
2781
+
2782
+
2783
+ # Sample inventory data for the bind_to / host_controller demo presenters
2784
+ if defined?(HostInventoryItem)
2785
+ HostInventoryItem.delete_all
2786
+ inventory_seeds = [
2787
+ { name: "Wireless Headphones", sku: "WH-001", description: "Noise cancelling bluetooth headphones.", status: "active", color: "black", price_cents: 12_999, quantity: 42 },
2788
+ { name: "USB-C Hub", sku: "HUB-7", description: "7-in-1 hub with HDMI, SD, and USB-A.", status: "active", color: "gray", price_cents: 4_500, quantity: 17 },
2789
+ { name: "Mechanical Keyboard", sku: "KB-MX", description: "Hot-swappable switches, RGB.", status: "active", color: "white", price_cents: 15_900, quantity: 8 },
2790
+ { name: "Office Chair (Mesh)", sku: "CH-MS", description: "Ergonomic office chair, lumbar support.", status: "active", color: "black", price_cents: 32_900, quantity: 3 },
2791
+ { name: "Laptop Stand", sku: "LS-V1", description: "Aluminum laptop stand, foldable.", status: "active", color: "silver", price_cents: 5_900, quantity: 0 },
2792
+ { name: "Pre-launch Widget", sku: "PRE-1", description: "Coming next quarter.", status: "draft", color: "blue", price_cents: 0, quantity: 0 },
2793
+ { name: "Old Smartphone Case", sku: "OLD-PH", description: "End of life — do not reorder.", status: "discontinued", color: "red", price_cents: 1_500, quantity: 12 }
2794
+ ]
2795
+ inventory_seeds.each_with_index do |attrs, i|
2796
+ HostInventoryItem.create!(attrs.merge(position: i + 1))
2797
+ end
2798
+ puts " Created #{HostInventoryItem.count} HostInventoryItem records"
2799
+ end
2800
+
2801
+ # Sample platform_profile data for the lcp_managed: demo
2802
+ if defined?(PlatformProfile)
2803
+ PlatformProfile.delete_all
2804
+
2805
+ # Pick a category target (LCP dynamic model) for the managed FK rows
2806
+ category_class = LcpRuby.registry.model_for("category") if LcpRuby.registry.registered?("category")
2807
+ cat = category_class&.first
2808
+
2809
+ profile_seeds = [
2810
+ { name: "Alice Anderson", bio: "Backend engineer who likes coffee.", favorite_color: "blue" },
2811
+ { name: "Bob Bauer", bio: "Frontend dev, Storybook enthusiast.", favorite_color: "green" },
2812
+ { name: "Cynthia Chen", bio: "Designer with a degree in topology.", favorite_color: "magenta" },
2813
+ { name: "Dimitri Dvorak", bio: "Data scientist who lives in spreadsheets.", favorite_color: "yellow" }
2814
+ ]
2815
+ profile_seeds.each do |attrs|
2816
+ PlatformProfile.create!(attrs.merge(category_id: cat&.id))
2817
+ end
2818
+ puts " Created #{PlatformProfile.count} PlatformProfile records (lcp_managed: demo)"
2819
+ end
2820
+
2821
+ # Phase: Association primary_key Showcase
2822
+ #
2823
+ # Demonstrates joining on a business key (vema_code) instead of the
2824
+ # default surrogate id. The chain is:
2825
+ #
2826
+ # showcase_hr_employee.division_code → showcase_division.vema_code
2827
+ # showcase_division.bu_code → showcase_business_unit.vema_code
2828
+ #
2829
+ # Plus a has_one :through over the custom-key belongs_to so
2830
+ # `employee.showcase_business_unit` resolves transitively without any
2831
+ # extra plumbing on the through-side declaration.
2832
+ if LcpRuby.registry.registered?("showcase_business_unit")
2833
+ BUModel = LcpRuby.registry.model_for("showcase_business_unit")
2834
+ DivisionModel = LcpRuby.registry.model_for("showcase_division")
2835
+ HrEmpModel = LcpRuby.registry.model_for("showcase_hr_employee")
2836
+
2837
+ business_units = [
2838
+ { vema_code: "HQ01", name: "Headquarters", description: "Central administration and corporate functions." },
2839
+ { vema_code: "TEC01", name: "Technology", description: "Engineering, product, IT operations." },
2840
+ { vema_code: "OPS01", name: "Operations", description: "Manufacturing, logistics, supply chain." },
2841
+ { vema_code: "SAL01", name: "Sales & Marketing", description: "Customer-facing revenue functions." }
2842
+ ]
2843
+ business_units.each { |attrs| BUModel.create!(attrs) }
2844
+
2845
+ divisions = [
2846
+ # bu_code stores the business unit's vema_code (not its id) — the
2847
+ # FK column was auto-created as string because the association
2848
+ # declares primary_key: :vema_code.
2849
+ { vema_code: "D-FIN", name: "Finance", bu_code: "HQ01" },
2850
+ { vema_code: "D-LEG", name: "Legal & Compliance", bu_code: "HQ01" },
2851
+ { vema_code: "D-ENG", name: "Engineering", bu_code: "TEC01" },
2852
+ { vema_code: "D-PRD", name: "Product", bu_code: "TEC01" },
2853
+ { vema_code: "D-IT", name: "IT Operations", bu_code: "TEC01" },
2854
+ { vema_code: "D-MFG", name: "Manufacturing", bu_code: "OPS01" },
2855
+ { vema_code: "D-LOG", name: "Logistics", bu_code: "OPS01" },
2856
+ { vema_code: "D-SAL", name: "Sales", bu_code: "SAL01" },
2857
+ { vema_code: "D-MKT", name: "Marketing", bu_code: "SAL01" }
2858
+ ]
2859
+ divisions.each { |attrs| DivisionModel.create!(attrs) }
2860
+
2861
+ # Lookup BU code per division — used to populate the denormalized
2862
+ # bu_code on each employee so the dashboard's BU filter is a single
2863
+ # WHERE on showcase_hr_employees instead of a JOIN.
2864
+ division_bu = divisions.to_h { |d| [ d[:vema_code], d[:bu_code] ] }
2865
+
2866
+ # ── Hand-curated leadership roster ──────────────────────────────
2867
+ # Kept so the existing association-primary-key feature card and the
2868
+ # presenter index still show familiar named records. All twelve are
2869
+ # currently active (no terminated_at); they form the dashboard's
2870
+ # "always present" management cohort.
2871
+ curated = [
2872
+ { name: "Alice Johnson", email: "alice.j@example.com", job_title: "CFO", division_code: "D-FIN", is_management: true, hired_at: Date.new(2019, 3, 14) },
2873
+ { name: "Bob Smith", email: "bob.s@example.com", job_title: "Controller", division_code: "D-FIN", is_management: false, hired_at: Date.new(2020, 7, 1) },
2874
+ { name: "Carol Davis", email: "carol.d@example.com", job_title: "General Counsel", division_code: "D-LEG", is_management: true, hired_at: Date.new(2018, 9, 2) },
2875
+ { name: "David Lee", email: "david.l@example.com", job_title: "VP Engineering", division_code: "D-ENG", is_management: true, hired_at: Date.new(2017, 1, 15) },
2876
+ { name: "Eve Martinez", email: "eve.m@example.com", job_title: "Senior Engineer", division_code: "D-ENG", is_management: false, hired_at: Date.new(2021, 4, 12) },
2877
+ { name: "Frank Wilson", email: "frank.w@example.com", job_title: "Engineering Lead", division_code: "D-ENG", status: "on_leave", is_management: true, hired_at: Date.new(2020, 11, 30) },
2878
+ { name: "Grace Chen", email: "grace.c@example.com", job_title: "Product Manager", division_code: "D-PRD", is_management: true, hired_at: Date.new(2022, 2, 7) },
2879
+ { name: "Henry Brown", email: "henry.b@example.com", job_title: "Head of IT", division_code: "D-IT", is_management: true, hired_at: Date.new(2019, 6, 19) },
2880
+ { name: "Iris Patel", email: "iris.p@example.com", job_title: "Plant Manager", division_code: "D-MFG", is_management: true, hired_at: Date.new(2018, 10, 5) },
2881
+ { name: "Jacob Romero", email: "jacob.r@example.com", job_title: "Logistics Lead", division_code: "D-LOG", is_management: true, hired_at: Date.new(2021, 8, 23) },
2882
+ { name: "Karen White", email: "karen.w@example.com", job_title: "Account Executive", division_code: "D-SAL", is_management: false, hired_at: Date.new(2022, 5, 11) },
2883
+ { name: "Liam Garcia", email: "liam.g@example.com", job_title: "Marketing Director", division_code: "D-MKT", is_management: true, hired_at: Date.new(2020, 1, 8) }
2884
+ ]
2885
+ curated.each do |attrs|
2886
+ HrEmpModel.create!(attrs.merge(bu_code: division_bu.fetch(attrs[:division_code])))
2887
+ end
2888
+
2889
+ # ── Synthetic cohort that drives the turnover dashboard ─────────
2890
+ #
2891
+ # ~200 employees seeded across BUs/divisions with hires + terminations
2892
+ # spread across the last three years (anchored on Date.today). The
2893
+ # distributions are tilted so each chart on hr_turnover_dashboard
2894
+ # has something to show: visible trend per month, a BU/Division
2895
+ # ranking, and a non-trivial mix of termination reasons.
2896
+ #
2897
+ # Knobs to tweak:
2898
+ # ANNUAL_TURNOVER_RATE — fraction of cohort that turns over per year
2899
+ # DIVISION_WEIGHTS — relative size of each division
2900
+ # REASON_WEIGHTS — mix of voluntary/involuntary/etc.
2901
+ rng = Random.new(20260517) # deterministic seed for reproducible demo
2902
+
2903
+ # Weights chosen so each reason has ~weekly density over the 3-year
2904
+ # window — keeps the stacked-area "Voluntary vs involuntary over
2905
+ # time" chart continuous instead of pointillistic. Real HR turnover
2906
+ # is usually voluntary-heavy, but the showcase prioritises chart
2907
+ # readability over realism.
2908
+ #
2909
+ # Locals (not top-level constants) so re-running `rails db:seed`
2910
+ # doesn't trigger `already initialized constant` warnings.
2911
+ annual_turnover_rate = 0.28
2912
+ reason_weights = {
2913
+ "voluntary" => 45,
2914
+ "involuntary" => 35,
2915
+ "retired" => 10,
2916
+ "transfer" => 10
2917
+ }.freeze
2918
+
2919
+ # Larger divisions get more synthetic employees — keeps the per-BU
2920
+ # chart from being a flat row of equal bars.
2921
+ division_weights = {
2922
+ "D-FIN" => 12, "D-LEG" => 6,
2923
+ "D-ENG" => 30, "D-PRD" => 14, "D-IT" => 12,
2924
+ "D-MFG" => 35, "D-LOG" => 18,
2925
+ "D-SAL" => 25, "D-MKT" => 18
2926
+ }.freeze
2927
+
2928
+ first_names = %w[Adam Beth Carl Dana Eric Fiona Greg Hana Ivan Jana Karel Lara Marek Nina Oleg Petra
2929
+ Quinn Roman Sara Tomas Ula Viktor Wenda Xena Yuri Zora Alex Brigit Cyril Diana
2930
+ Emil Filip Greta Hugo Ines Jonas Klara Linda Olga Pavel Renata Samuel Tereza
2931
+ Vaclav Walter Yvona Zlata Adela Bohumil Daniela Egon Frantisek Gerda Helena
2932
+ Igor Jolana Kamil Ludek Marcel Nora Otakar Pavla].freeze
2933
+ last_names = %w[Novak Svoboda Dvorak Cerny Prochazka Kucera Vesely Horak Krejci Marek Pokorny
2934
+ Pospisil Hajek Jelinek Polak Soukup Konecny Sedlacek Dolezal Stastny Holub Cermak
2935
+ Janda Stepan Vlcek Kratochvil Beran Hejna Hruska Pavlik Zeman Tomanek Houska Bartos
2936
+ Smid Hruby Vacek Stejskal Pesek Maly Kratky].freeze
2937
+ job_pool = {
2938
+ management: [ "Director", "Head of Department", "Senior Manager", "Team Lead", "Principal" ],
2939
+ individual: [ "Specialist", "Senior Specialist", "Analyst", "Associate", "Engineer", "Coordinator", "Consultant" ]
2940
+ }.freeze
2941
+
2942
+ weighted_pick = lambda do |weights|
2943
+ total = weights.values.sum
2944
+ pick = rng.rand(total)
2945
+ cumulative = 0
2946
+ weights.each do |key, weight|
2947
+ cumulative += weight
2948
+ return key if pick < cumulative
2949
+ end
2950
+ weights.keys.last
2951
+ end
2952
+
2953
+ today = Date.today
2954
+ hires_window_start = today - (365 * 3) # three full years of history
2955
+ email_seq = 0
2956
+
2957
+ division_weights.each do |division_code, count|
2958
+ bu_code = division_bu.fetch(division_code)
2959
+ count.times do
2960
+ first = first_names.sample(random: rng)
2961
+ last = last_names.sample(random: rng)
2962
+ email_seq += 1
2963
+ is_mgmt = rng.rand < 0.18 # ~18% management
2964
+ job_title = (is_mgmt ? job_pool[:management] : job_pool[:individual]).sample(random: rng)
2965
+
2966
+ hired_offset = rng.rand((today - hires_window_start).to_i)
2967
+ hired_at = hires_window_start + hired_offset
2968
+
2969
+ # Probability of having left within the (today - hired_at) window
2970
+ # using a simple geometric approximation of annual_turnover_rate.
2971
+ months_employed = ((today - hired_at).to_f / 30.4).clamp(1, 36)
2972
+ term_prob = 1 - (1 - annual_turnover_rate) ** (months_employed / 12.0)
2973
+ terminated_at = nil
2974
+ termination_reason = nil
2975
+ status = "active"
2976
+ if rng.rand < term_prob
2977
+ terminated_at = hired_at + rng.rand((today - hired_at).to_i)
2978
+ termination_reason = weighted_pick.call(reason_weights)
2979
+ status = "terminated"
2980
+ elsif rng.rand < 0.04
2981
+ status = "on_leave"
2982
+ end
2983
+
2984
+ HrEmpModel.create!(
2985
+ name: "#{first} #{last}",
2986
+ email: "#{first.downcase}.#{last.downcase}.#{email_seq}@example.com",
2987
+ job_title: job_title,
2988
+ status: status,
2989
+ division_code: division_code,
2990
+ bu_code: bu_code,
2991
+ is_management: is_mgmt,
2992
+ hired_at: hired_at,
2993
+ terminated_at: terminated_at,
2994
+ termination_reason: termination_reason
2995
+ )
2996
+ end
2997
+ end
2998
+
2999
+ puts " Created #{BUModel.count} business units, #{DivisionModel.count} divisions, " \
3000
+ "#{HrEmpModel.count} HR employees " \
3001
+ "(#{HrEmpModel.where.not(terminated_at: nil).count} with terminations) " \
3002
+ "— powers hr_turnover_dashboard"
3003
+ end
3004
+
3005
+ # Feature catalog entries — Association primary_key
3006
+
3007
+ # Phase: Record Aliases & Slug Fields Showcase
3008
+ if LcpRuby.registry.registered?("showcase_announcement")
3009
+ AnnouncementModel = LcpRuby.registry.model_for("showcase_announcement")
3010
+
3011
+ [
3012
+ { title: "Platform v2.0 Released", body: "We are excited to announce the release of Platform v2.0 with record aliases, slug fields, and many more improvements.", priority: "high", published_at: 3.days.ago },
3013
+ { title: "Scheduled Maintenance Window", body: "The platform will undergo scheduled maintenance this Saturday from 2:00 AM to 6:00 AM UTC. Please save your work.", priority: "urgent", published_at: 1.day.ago },
3014
+ { title: "New Team Members", body: "Please welcome our three new engineers joining the platform team this month.", priority: "normal", published_at: 5.days.ago },
3015
+ { title: "Q1 All-Hands Meeting", body: "Join us for the quarterly all-hands meeting next Friday at 3 PM. We will cover roadmap updates and team achievements.", priority: "normal", published_at: 2.weeks.ago },
3016
+ { title: "Security Policy Update", body: "Our security policy has been updated to require MFA for all accounts. Please enable it by end of month.", priority: "high", published_at: 6.hours.ago }
3017
+ ].each { |attrs| AnnouncementModel.create!(attrs) }
3018
+
3019
+ puts " Created #{AnnouncementModel.count} announcement records (record_aliases showcase)"
3020
+ end
3021
+
3022
+ # API Tokens — seed one active and one revoked token for the admin user.
3023
+ # The plaintext is generated by the model's before_validation callback and
3024
+ # printed here for the demo curl flow. On reseed the plaintext changes;
3025
+ # fetch it again from `rails console` (the hash overwrites in DB).
3026
+ if LcpRuby.registry.registered?("api_token")
3027
+ TokenModel = LcpRuby.registry.model_for("api_token")
3028
+ TokenModel.delete_all
3029
+
3030
+ admin = if LcpRuby.configuration.authentication == :built_in
3031
+ LcpRuby::User.find_by(email: "admin@example.com")
3032
+ end
3033
+
3034
+ if admin
3035
+ active = TokenModel.create!(name: "Demo curl token", user_id: admin.id)
3036
+ puts " Created active API token — plaintext: #{active.plaintext_token}"
3037
+
3038
+ revoked = TokenModel.create!(name: "Old laptop (revoked)", user_id: admin.id)
3039
+ revoked.update!(revoked_at: Time.current)
3040
+ puts " Created revoked API token (#{revoked.token_prefix})"
3041
+ else
3042
+ puts " Skipped api_token seeds — admin@example.com user not found."
3043
+ end
3044
+ end
3045
+
3046
+ # ── Type-system defaults demo ────────────────────────────────────────────
3047
+ # Seeds for the showcase_type_default model (config/lcp_ruby/models/
3048
+ # showcase_type_default.rb). Demonstrates Phases 1-4b of
3049
+ # docs/design/type_system_defaults.md visibly: bare boolean/enum columns
3050
+ # auto-renderer via runtime_type_renderers, type-name synonym for renderer:,
3051
+ # display_in_index opt-in, and custom :rating type with multi-rule
3052
+ # null_false_validation.
3053
+ if defined?(LcpRuby::Dynamic::ShowcaseTypeDefault)
3054
+ TypeDefaultModel = LcpRuby::Dynamic::ShowcaseTypeDefault
3055
+ TypeDefaultModel.delete_all
3056
+ [
3057
+ { title: "Phase 4 enrichment baseline", active: true, stage: "draft", score: 4.5,
3058
+ short_summary: "Baseline scenario showing all four enrichment paths.",
3059
+ body: ("This is a long body field demonstrating row-unfriendly :text type behavior. " * 5),
3060
+ notes: "Notes are :text but only shown in form/show, not index." },
3061
+ { title: "C7 type-name synonym demo", active: true, stage: "review", score: 5.0,
3062
+ short_summary: "Stage column declared with renderer: :enum (a type name) — runtime resolves to badge.",
3063
+ body: "Phase 4 added a runtime fallback in Display::RendererRegistry.renderer_for that consults Types::TypeRegistry for type-name keys.",
3064
+ notes: "Decision 4 revised in docs/design/type_system_defaults.md." },
3065
+ { title: "Boolean+presence anti-pattern (avoided)", active: false, stage: "published", score: 3.0,
3066
+ short_summary: "active is :boolean null:false WITHOUT explicit validates :presence — Phase 4b auto-applies inclusion.",
3067
+ body: "If a configurator wrote validates :presence on :boolean, ConfigurationValidator rejects per Decision 15.",
3068
+ notes: "" },
3069
+ { title: "Custom :rating type — multi-rule null_false_validation", active: true, stage: "published", score: 4.0,
3070
+ short_summary: "score is :rating, declared null:false. Type definition carries presence AND numericality 1..5.",
3071
+ body: "Decision 12: null_false_validation is an array, not a single hash — composes multiple rules per type.",
3072
+ notes: "Try editing this record and setting score to 0 or 6 — Rails-shaped error." },
3073
+ { title: "display_in_index opt-in", active: true, stage: "draft", score: 3.5,
3074
+ short_summary: "Body column uses display_in_index: true (validator-silencer; runtime renders raw text).",
3075
+ body: "Sometimes you want long text in the row anyway. The opt-in says: I know this is row-unfriendly, render it as is.",
3076
+ notes: "" },
3077
+ { title: "Stale archived scenario", active: false, stage: "archived", score: 2.0,
3078
+ short_summary: "Old example kept for completeness — boolean shows as red ✗ via boolean_icon enrichment.",
3079
+ body: "Phase 4 enrichment is gated behind LcpRuby.configuration.runtime_type_renderers — showcase has it on.",
3080
+ notes: "" }
3081
+ ].each { |attrs| TypeDefaultModel.create!(attrs) }
3082
+ puts "Created #{TypeDefaultModel.count} ShowcaseTypeDefault demo records (Phase 1-4b type-system defaults)"
3083
+ end
3084
+
3085
+ puts "Seeding complete!"