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,2324 @@
1
+ # Presenters Guide
2
+
3
+ Presenters define the UI layer of your LCP Ruby application: how records are listed, displayed, edited, searched, and what actions are available. A single model can have multiple presenters -- for example, one for full administration and another for a read-only pipeline view.
4
+
5
+ This guide walks through building presenters step-by-step, from a minimal setup to advanced features. Every example is shown in both YAML and Ruby DSL.
6
+
7
+ For the full attribute reference, see the [Presenters Reference](../reference/presenters.md) and the [Presenter DSL Reference](../reference/presenter-dsl.md).
8
+
9
+ ## File Locations
10
+
11
+ Presenter files live in `config/lcp_ruby/presenters/`:
12
+
13
+ ```
14
+ config/lcp_ruby/presenters/
15
+ todo_list.yml # YAML format
16
+ deal.rb # Ruby DSL format
17
+ contact.yml
18
+ ```
19
+
20
+ Both formats produce the same internal representation. A project can mix `.yml` and `.rb` files, but each presenter name must be unique across all files.
21
+
22
+ ---
23
+
24
+ ## Your First Presenter
25
+
26
+ A minimal presenter needs a `name`, a `model`, a `slug` (for URL routing), and at least an index configuration to list records.
27
+
28
+ **YAML:**
29
+
30
+ ```yaml
31
+ # config/lcp_ruby/presenters/todo_list.yml
32
+ presenter:
33
+ name: todo_list
34
+ model: todo_list
35
+ label: "Todo Lists"
36
+ slug: todo-lists
37
+ icon: check-square
38
+
39
+ index:
40
+ table_columns:
41
+ - { field: name, link_to: show, sortable: true }
42
+ - { field: created_at, renderer: relative_date }
43
+
44
+ show:
45
+ layout:
46
+ - section: "Details"
47
+ fields:
48
+ - { field: name, renderer: heading }
49
+ - { field: created_at, renderer: datetime }
50
+
51
+ form:
52
+ sections:
53
+ - title: "Todo List"
54
+ fields:
55
+ - { field: name, placeholder: "List name...", autofocus: true }
56
+
57
+ actions:
58
+ collection:
59
+ - { name: create, type: built_in, label: "New List", icon: plus }
60
+ single:
61
+ - { name: show, type: built_in, icon: eye }
62
+ - { name: edit, type: built_in, icon: pencil }
63
+ - { name: destroy, type: built_in, icon: trash, confirm: true, style: danger }
64
+ ```
65
+
66
+ **Ruby DSL:**
67
+
68
+ ```ruby
69
+ # config/lcp_ruby/presenters/todo_list.rb
70
+ define_presenter :todo_list do
71
+ model :todo_list
72
+ label "Todo Lists"
73
+ slug "todo-lists"
74
+ icon "check-square"
75
+
76
+ index do
77
+ column :name, link_to: :show, sortable: true
78
+ column :created_at, renderer: :relative_date
79
+ end
80
+
81
+ show do
82
+ section "Details" do
83
+ field :name, renderer: :heading
84
+ field :created_at, renderer: :datetime
85
+ end
86
+ end
87
+
88
+ form do
89
+ section "Todo List" do
90
+ field :name, placeholder: "List name...", autofocus: true
91
+ end
92
+ end
93
+
94
+ action :create, type: :built_in, on: :collection, label: "New List", icon: "plus"
95
+ action :show, type: :built_in, on: :single, icon: "eye"
96
+ action :edit, type: :built_in, on: :single, icon: "pencil"
97
+ action :destroy, type: :built_in, on: :single, icon: "trash", confirm: true, style: :danger
98
+ end
99
+ ```
100
+
101
+ With this in place, navigating to `/todo-lists` renders a paginated table. Each record has show, edit, and destroy buttons.
102
+
103
+ ---
104
+
105
+ ## Index Configuration
106
+
107
+ The index controls the record list view: which columns appear, how they sort, pagination, and visual formatting.
108
+
109
+ ### Columns, Sorting, and Pagination
110
+
111
+ **YAML:**
112
+
113
+ ```yaml
114
+ index:
115
+ default_sort: { field: created_at, direction: desc }
116
+ per_page: 20
117
+ row_click: show
118
+ table_columns:
119
+ - field: name
120
+ width: "30%"
121
+ link_to: show
122
+ sortable: true
123
+ - field: email
124
+ width: "25%"
125
+ renderer: email_link
126
+ sortable: true
127
+ - field: status
128
+ width: "15%"
129
+ renderer: badge
130
+ options:
131
+ color_map:
132
+ active: green
133
+ inactive: gray
134
+ sortable: true
135
+ - field: revenue
136
+ width: "15%"
137
+ renderer: currency
138
+ options:
139
+ currency: "$"
140
+ precision: 2
141
+ sortable: true
142
+ summary: sum
143
+ - { field: updated_at, renderer: relative_date }
144
+ ```
145
+
146
+ **Ruby DSL:**
147
+
148
+ ```ruby
149
+ index do
150
+ default_sort :created_at, :desc
151
+ per_page 20
152
+ row_click :show
153
+
154
+ column :name, width: "30%", link_to: :show, sortable: true
155
+ column :email, width: "25%", renderer: :email_link, sortable: true
156
+ column :status, width: "15%", renderer: :badge, sortable: true,
157
+ options: { color_map: { active: "green", inactive: "gray" } }
158
+ column :revenue, width: "15%", renderer: :currency, sortable: true, summary: :sum,
159
+ options: { currency: "$", precision: 2 }
160
+ column :updated_at, renderer: :relative_date
161
+ end
162
+ ```
163
+
164
+ Key options:
165
+
166
+ | Option | Description |
167
+ |--------|-------------|
168
+ | `default_sort` | Default column and direction for ordering |
169
+ | `per_page` | Records per page (Kaminari pagination) |
170
+ | `row_click: show` | Makes the entire table row clickable |
171
+ | `link_to: show` | **Legacy.** Makes a specific column's cell a link to the current row's show page. Preserved for backward compatibility; prefer `link: true` for new configurations |
172
+ | `link: true` | Wrap the cell in a link. On dot-path or plain `belongs_to` columns links to the associated record; on scalar columns links to the current row. See [Linking dot-path fields and columns](#linking-dot-path-fields-and-columns) |
173
+ | `link_through: :x` | Follow `belongs_to :x` on the current model for the link target. Used when the displayed field's name doesn't correspond to the linked entity |
174
+ | `sortable` | Enables column-header sort toggle |
175
+ | `renderer` | Visual renderer for the cell value (see [Renderers Guide](display-types.md)) |
176
+ | `summary` | Adds a footer row with `sum`, `avg`, or `count` |
177
+
178
+ ### Aggregate Columns
179
+
180
+ Columns can reference [aggregate fields](../reference/models.md#aggregates) defined on the model. Aggregate columns are computed via SQL subqueries at query time — no database column is needed. They support sorting and renderers just like regular columns.
181
+
182
+ **YAML:**
183
+
184
+ ```yaml
185
+ index:
186
+ table_columns:
187
+ - { field: name, sortable: true }
188
+ - { field: contacts_count, sortable: true }
189
+ - { field: total_deal_value, renderer: currency, sortable: true }
190
+ ```
191
+
192
+ **Ruby DSL:**
193
+
194
+ ```ruby
195
+ index do
196
+ column :name, sortable: true
197
+ column :contacts_count, sortable: true
198
+ column :total_deal_value, renderer: :currency, sortable: true
199
+ end
200
+ ```
201
+
202
+ Aggregates are also usable in show page sections:
203
+
204
+ ```ruby
205
+ show do
206
+ section "Statistics", columns: 2 do
207
+ field :contacts_count
208
+ field :total_deal_value, renderer: :currency
209
+ end
210
+ end
211
+ ```
212
+
213
+ Aggregate columns are visible to all roles regardless of field-level permissions. Only aggregates referenced in the current presenter's columns are included in the query — unreferenced aggregates add zero overhead.
214
+
215
+ ### Linking dot-path fields and columns
216
+
217
+ A dot-path column like `"manager.display_name"` renders the manager's name but by default the cell is plain text. Add `link: true` to wrap the value in a link to the manager's own show page. The same options work on show page fields.
218
+
219
+ **Auto-detection.** `link: true` resolves the target from the displayed value:
220
+
221
+ - **Dot-path** (`"manager.display_name"`) — links to the manager record.
222
+ - **Multi-level dot-path** (`"manager.organizational_unit.name"`) — follows the full chain and links to the org unit (where the displayed value lives).
223
+ - **Terminal `belongs_to`** (`"manager.organizational_unit"`) — links to that org unit; the displayed value defaults to the associated record's `to_label` / `to_s`.
224
+ - **Plain `belongs_to` field** (`field :manager`) — links to the manager record.
225
+
226
+ Use `link_through: :x` when the field name doesn't match the link target (e.g. a precomputed virtual column).
227
+
228
+ **YAML:**
229
+
230
+ ```yaml
231
+ index:
232
+ table_columns:
233
+ - { field: display_name, link_to: show } # legacy, unchanged
234
+ - { field: "manager.display_name", label: "Manager", link: true }
235
+ - { field: "organizational_unit.name", label: "Org Unit", link: true }
236
+ - { field: manager_label, link_through: manager } # virtual column
237
+
238
+ show:
239
+ layout:
240
+ - section: "Work Details"
241
+ columns: 2
242
+ fields:
243
+ - { field: "manager.display_name", label: "Manager", link: true }
244
+ - { field: "organizational_unit.name", label: "Org Unit", link: true }
245
+ - { field: position, link: true } # plain belongs_to
246
+ ```
247
+
248
+ **Ruby DSL:**
249
+
250
+ ```ruby
251
+ index do
252
+ column :display_name, link_to: :show
253
+ column "manager.display_name", label: "Manager", link: true
254
+ column "organizational_unit.name", label: "Org Unit", link: true
255
+ column :manager_label, link_through: :manager
256
+ end
257
+
258
+ show do
259
+ section "Work Details", columns: 2 do
260
+ field "manager.display_name", label: "Manager", link: true
261
+ field "organizational_unit.name", label: "Org Unit", link: true
262
+ field :position, link: true
263
+ end
264
+ end
265
+ ```
266
+
267
+ **Safe-by-default.** A link renders only when the target record exists, is not soft-deleted, and the current user has `:show` on the target model; otherwise the value renders as plain text. A blank displayed value (the empty-value placeholder "—") is never wrapped.
268
+
269
+ **Legacy `link_to: :show`.** On columns, `link_to: :show` always links to the **current row** regardless of the column field. It is preserved unchanged for backward compatibility — new configurations should prefer `link: true`, which auto-detects the target. Combining `link_to: :show` with `link_through:` is a boot error.
270
+
271
+ **Grouped queries.** Grouped-query presenters reject `link:` / `link_through:` / `link_to:` on columns — their rows are aggregate wrappers, not records.
272
+
273
+ **Eager loading.** Dot-path fields already trigger eager loading of their associations. Adding `link: true` on a plain `belongs_to` field (or using `link_through:` on a non-dot-path field) automatically includes the referenced association — no manual `includes:` needed. See [Eager Loading Reference](../reference/eager-loading.md) for the full auto-detection table.
274
+
275
+ ### Empty State and Actions Position
276
+
277
+ **YAML:**
278
+
279
+ ```yaml
280
+ index:
281
+ empty_message: "No contacts found. Create your first contact to get started."
282
+ actions_position: dropdown
283
+ ```
284
+
285
+ **Ruby DSL:**
286
+
287
+ ```ruby
288
+ index do
289
+ empty_message "No contacts found. Create your first contact to get started."
290
+ actions_position :dropdown
291
+ end
292
+ ```
293
+
294
+ `actions_position: dropdown` groups all single-record actions into a dropdown menu instead of rendering them as inline buttons. This is useful when you have many actions per row.
295
+
296
+ ### Responsive Columns and Pinning
297
+
298
+ Hide columns on small screens and pin important columns to stay visible during horizontal scroll.
299
+
300
+ **YAML:**
301
+
302
+ ```yaml
303
+ table_columns:
304
+ - field: name
305
+ pinned: left
306
+ sortable: true
307
+ - field: email
308
+ hidden_on: [mobile, tablet]
309
+ - field: phone
310
+ hidden_on: mobile
311
+ - field: status
312
+ renderer: badge
313
+ ```
314
+
315
+ **Ruby DSL:**
316
+
317
+ ```ruby
318
+ index do
319
+ column :name, pinned: :left, sortable: true
320
+ column :email, hidden_on: [:mobile, :tablet]
321
+ column :phone, hidden_on: :mobile
322
+ column :status, renderer: :badge
323
+ end
324
+ ```
325
+
326
+ ### Conditional Row Styling (`item_classes`)
327
+
328
+ Apply CSS classes to table rows, tile cards, or tree nodes based on record field values. This provides immediate visual cues — red rows for overdue items, strikethrough for completed records, bold for high priority.
329
+
330
+ **YAML:**
331
+
332
+ ```yaml
333
+ index:
334
+ table_columns:
335
+ - { field: title, link_to: show }
336
+ - { field: status, renderer: badge }
337
+ - { field: priority }
338
+ item_classes:
339
+ - class: "lcp-row-muted lcp-row-strikethrough"
340
+ when: { field: status, operator: eq, value: "done" }
341
+ - class: "lcp-row-danger"
342
+ when: { field: status, operator: eq, value: "overdue" }
343
+ - class: "lcp-row-bold"
344
+ when: { field: priority, operator: eq, value: "critical" }
345
+ ```
346
+
347
+ **DSL:**
348
+
349
+ ```ruby
350
+ index do
351
+ column :title, link_to: :show
352
+ column :status, renderer: :badge
353
+ column :priority
354
+
355
+ item_class "lcp-row-muted lcp-row-strikethrough",
356
+ when: { field: :status, operator: :eq, value: "done" }
357
+ item_class "lcp-row-danger",
358
+ when: { field: :status, operator: :eq, value: "overdue" }
359
+ item_class "lcp-row-bold",
360
+ when: { field: :priority, operator: :eq, value: "critical" }
361
+ end
362
+ ```
363
+
364
+ All matching rules accumulate — a record can be both bold and red at the same time. Rules work across all index layouts (table, tiles, tree).
365
+
366
+ Built-in utility classes: `lcp-row-danger`, `lcp-row-warning`, `lcp-row-success`, `lcp-row-info`, `lcp-row-muted`, `lcp-row-bold`, `lcp-row-strikethrough`. All background classes use CSS custom properties for theming. Custom CSS classes are also supported.
367
+
368
+ For complex conditions that require server-side logic, use service conditions:
369
+
370
+ ```yaml
371
+ item_classes:
372
+ - class: "lcp-row-danger"
373
+ when: { service: overdue_checker }
374
+ ```
375
+
376
+ See [Presenters Reference — item_classes](../reference/presenters.md#item_classes) for the full attribute reference.
377
+
378
+ ### Pagination Footer
379
+
380
+ The pagination footer is a horizontal bar below the index content with up to three elements: a per-page size selector (left), a record count (center), and Kaminari page links (right).
381
+
382
+ **YAML:**
383
+
384
+ ```yaml
385
+ index:
386
+ per_page: 25
387
+ per_page_options: [10, 25, 50, 100]
388
+ show_record_count: true # default: true
389
+ pagination_ends: false # default: false (from global config)
390
+ ```
391
+
392
+ **Ruby DSL:**
393
+
394
+ ```ruby
395
+ index do
396
+ per_page 25
397
+ per_page_options 10, 25, 50, 100
398
+ show_record_count true
399
+ pagination_ends false
400
+ end
401
+ ```
402
+
403
+ Key options:
404
+
405
+ | Option | Description |
406
+ |--------|-------------|
407
+ | `per_page_options` | Array of page sizes for the dropdown selector. When absent, no dropdown renders. |
408
+ | `show_record_count` | Shows "Showing 1-25 of 312 records" text. Default `true`. Set to `false` to hide. |
409
+ | `pagination_ends` | Shows "First"/"Last" page links. Default from global [`LcpRuby.configuration.pagination_ends`](../reference/engine-configuration.md#pagination_ends). |
410
+
411
+ Behavior per layout:
412
+
413
+ | Layout | Per-page selector | Record count | Page links |
414
+ |--------|-------------------|--------------|------------|
415
+ | table | yes (if configured) | "records" | yes |
416
+ | tiles | yes (if configured) | "records" | yes |
417
+ | grouped | yes (if configured) | "groups" | yes |
418
+ | tree | no | total only (no range) | no |
419
+
420
+ When all records fit on one page the text simplifies to e.g. "12 records" (no "Showing X-Y" prefix). Empty result sets render no footer at all.
421
+
422
+ See [Presenters Reference — show_record_count](../reference/presenters.md#show_record_count) and [pagination_ends](../reference/presenters.md#pagination_ends) for full attribute details.
423
+
424
+ ### Reorderable Index (Record Positioning)
425
+
426
+ For models with [`positioning`](../reference/models.md#positioning), you can enable drag-and-drop reordering:
427
+
428
+ **YAML:**
429
+
430
+ ```yaml
431
+ index:
432
+ reorderable: true
433
+ table_columns:
434
+ - { field: name, link_to: show }
435
+ - { field: position, sortable: true }
436
+ ```
437
+
438
+ **DSL:**
439
+
440
+ ```ruby
441
+ index do
442
+ reorderable true
443
+ column :name, link_to: :show
444
+ column :position, sortable: true
445
+ end
446
+ ```
447
+
448
+ When `reorderable: true`:
449
+ - Drag handles appear as the first column
450
+ - Records are sorted by position by default (no need for explicit `default_sort`)
451
+ - The frontend sends `PATCH /:slug/:id/reorder` requests with relative positioning (`{ after: id }` or `{ before: id }`)
452
+ - Handles are automatically disabled when a search query is active or when sorting by a non-position column
453
+ - Reordering requires `update` CRUD permission and the position field in writable fields
454
+
455
+ The position column is optional in `table_columns` — drag-and-drop works regardless. Include it when users want to see the numeric order.
456
+
457
+ See [Record Positioning](../design/record_positioning.md) for the full design, including scoped positioning, concurrent edit detection, and permission patterns.
458
+
459
+ ---
460
+
461
+ ## Show Configuration
462
+
463
+ The show view displays a single record's details organized into sections.
464
+
465
+ ### Sections and Fields
466
+
467
+ **YAML:**
468
+
469
+ ```yaml
470
+ show:
471
+ layout:
472
+ - section: "Contact Details"
473
+ columns: 2
474
+ fields:
475
+ - { field: name, renderer: heading, col_span: 2 }
476
+ - { field: email, renderer: email_link }
477
+ - { field: phone, renderer: phone_link }
478
+ - field: status
479
+ renderer: badge
480
+ options:
481
+ color_map:
482
+ active: green
483
+ inactive: gray
484
+ - section: "Notes"
485
+ fields:
486
+ - { field: notes, renderer: rich_text }
487
+ ```
488
+
489
+ **Ruby DSL:**
490
+
491
+ ```ruby
492
+ show do
493
+ section "Contact Details", columns: 2 do
494
+ field :name, renderer: :heading, col_span: 2
495
+ field :email, renderer: :email_link
496
+ field :phone, renderer: :phone_link
497
+ field :status, renderer: :badge,
498
+ options: { color_map: { active: "green", inactive: "gray" } }
499
+ end
500
+
501
+ section "Notes" do
502
+ field :notes, renderer: :rich_text
503
+ end
504
+ end
505
+ ```
506
+
507
+ Use `columns` to control the grid layout and `col_span` to make a field stretch across multiple columns.
508
+
509
+ ### Responsive Sections
510
+
511
+ Override the column count at different breakpoints so the layout adapts to smaller screens.
512
+
513
+ **YAML:**
514
+
515
+ ```yaml
516
+ show:
517
+ layout:
518
+ - section: "Deal Overview"
519
+ columns: 3
520
+ responsive:
521
+ tablet:
522
+ columns: 2
523
+ mobile:
524
+ columns: 1
525
+ fields:
526
+ - { field: title, renderer: heading, col_span: 3 }
527
+ - { field: stage, renderer: badge }
528
+ - { field: value, renderer: currency }
529
+ - { field: close_date, renderer: date }
530
+ ```
531
+
532
+ **Ruby DSL:**
533
+
534
+ ```ruby
535
+ show do
536
+ section "Deal Overview", columns: 3, responsive: { tablet: 2, mobile: 1 } do
537
+ field :title, renderer: :heading, col_span: 3
538
+ field :stage, renderer: :badge
539
+ field :value, renderer: :currency
540
+ field :close_date, renderer: :date
541
+ end
542
+ end
543
+ ```
544
+
545
+ ### Association Lists
546
+
547
+ Display related records as a list within the show page using `type: association_list`. Records can render with rich display templates (title, subtitle, icon, badge), links, sorting, and limits.
548
+
549
+ **YAML:**
550
+
551
+ ```yaml
552
+ show:
553
+ layout:
554
+ - section: "Company Info"
555
+ fields:
556
+ - { field: name, renderer: heading }
557
+ - section: "Contacts"
558
+ type: association_list
559
+ association: contacts
560
+ display_template: default # Uses display template from contact model
561
+ link: true # Wrap each record in a link to its show page
562
+ sort: { last_name: asc }
563
+ limit: 10
564
+ empty_message: "No contacts yet."
565
+ - section: "Deals"
566
+ type: association_list
567
+ association: deals
568
+ ```
569
+
570
+ **Ruby DSL:**
571
+
572
+ ```ruby
573
+ show do
574
+ section "Company Info" do
575
+ field :name, renderer: :heading
576
+ end
577
+
578
+ association_list "Contacts", association: :contacts,
579
+ display_template: :default, link: true,
580
+ sort: { last_name: :asc }, limit: 10,
581
+ empty_message: "No contacts yet."
582
+ association_list "Deals", association: :deals
583
+ end
584
+ ```
585
+
586
+ The association must be defined as `has_many` in the model.
587
+
588
+ #### Linking Through Join Models
589
+
590
+ When displaying a join/bridge model (e.g., group memberships) but the user expects to click through to the related entity (e.g., the employee), use `link_through`:
591
+
592
+ **YAML:**
593
+
594
+ ```yaml
595
+ - section: "Members"
596
+ type: association_list
597
+ association: group_memberships
598
+ link_through: employee # Links to employee, not group_membership
599
+ display_template: default
600
+ ```
601
+
602
+ **Ruby DSL:**
603
+
604
+ ```ruby
605
+ association_list "Members", association: :group_memberships,
606
+ link_through: :employee, display_template: :default
607
+ ```
608
+
609
+ The `link_through` value must be a non-polymorphic `belongs_to` association on the target model (group_membership). If the related record is nil or soft-deleted, the item renders without a link. The user must have `show` permission on the target model for links to appear.
610
+
611
+ **Display templates** are defined on the target model (see [Models Reference](../reference/models.md#display-templates)). For example, the contact model might define:
612
+
613
+ **YAML:**
614
+
615
+ ```yaml
616
+ display_templates:
617
+ default:
618
+ template: "{first_name} {last_name}"
619
+ subtitle: "{position} at {company.name}"
620
+ icon: user
621
+ ```
622
+
623
+ **Ruby DSL:**
624
+
625
+ ```ruby
626
+ define_model :contact do
627
+ # ... fields, associations ...
628
+
629
+ display_template :default,
630
+ template: "{first_name} {last_name}",
631
+ subtitle: "{position} at {company.name}",
632
+ icon: "user"
633
+ end
634
+ ```
635
+
636
+ This renders each contact with a structured layout showing the name, position, and company. Without a display template, records fall back to plain `to_label` text.
637
+
638
+ Options reference: `display`, `link`, `sort`, `limit`, `empty_message`, `scope` — see [Presenters Reference](../reference/presenters.md#association-list-sections).
639
+
640
+ ---
641
+
642
+ ## Form Configuration
643
+
644
+ Forms control how records are created and edited. You define one or more sections, each containing fields.
645
+
646
+ ### Basic Form
647
+
648
+ **YAML:**
649
+
650
+ ```yaml
651
+ form:
652
+ sections:
653
+ - title: "Contact Information"
654
+ columns: 2
655
+ fields:
656
+ - { field: first_name, placeholder: "First name...", autofocus: true }
657
+ - { field: last_name, placeholder: "Last name..." }
658
+ - { field: email, placeholder: "email@example.com" }
659
+ - { field: phone }
660
+ ```
661
+
662
+ **Ruby DSL:**
663
+
664
+ ```ruby
665
+ form do
666
+ section "Contact Information", columns: 2 do
667
+ field :first_name, placeholder: "First name...", autofocus: true
668
+ field :last_name, placeholder: "Last name..."
669
+ field :email, placeholder: "email@example.com"
670
+ field :phone
671
+ end
672
+ end
673
+ ```
674
+
675
+ ### Flat vs Tabs Layout
676
+
677
+ By default, sections render as stacked cards (`flat` layout). Set `layout: tabs` to render each section as a tab.
678
+
679
+ **YAML:**
680
+
681
+ ```yaml
682
+ form:
683
+ layout: tabs
684
+ sections:
685
+ - title: "General"
686
+ fields:
687
+ - { field: title }
688
+ - { field: description, input_type: textarea }
689
+ - title: "Pricing"
690
+ fields:
691
+ - { field: price, input_type: number, prefix: "$" }
692
+ - { field: currency, input_type: select }
693
+ - title: "Advanced"
694
+ fields:
695
+ - { field: notes, input_type: rich_text_editor }
696
+ ```
697
+
698
+ **Ruby DSL:**
699
+
700
+ ```ruby
701
+ form do
702
+ layout :tabs
703
+
704
+ section "General" do
705
+ field :title
706
+ field :description, input_type: :textarea
707
+ end
708
+
709
+ section "Pricing" do
710
+ field :price, input_type: :number, prefix: "$"
711
+ field :currency, input_type: :select
712
+ end
713
+
714
+ section "Advanced" do
715
+ field :notes, input_type: :rich_text_editor
716
+ end
717
+ end
718
+ ```
719
+
720
+ ### Input Types
721
+
722
+ LCP Ruby picks a default input type based on the field's model type, but you can override it.
723
+
724
+ | Input Type | Renders | Default For |
725
+ |------------|---------|-------------|
726
+ | `text` | Single-line text input | `string` fields |
727
+ | `textarea` | Multi-line text area | `text` fields |
728
+ | `select` | Dropdown (from `enum_values`) | `enum` fields |
729
+ | `number` | Numeric input | `integer`, `float`, `decimal` |
730
+ | `date` / `date_picker` | Date picker | `date` fields |
731
+ | `datetime` | Datetime picker | `datetime` fields |
732
+ | `boolean` | Checkbox | `boolean` fields |
733
+ | `association_select` | Dropdown from associated model | FK fields |
734
+ | `rich_text_editor` | Rich text editor | `rich_text` fields |
735
+ | `slider` | Range slider | - |
736
+ | `toggle` | Toggle switch | - |
737
+ | `rating` | Star rating input | - |
738
+ | `array_input` | Tag-style chip input | `array` fields |
739
+
740
+ Array fields (`type: array`) automatically use `array_input` in forms and `collection` renderer in display. Configure suggestions and limits via `input_options`:
741
+
742
+ ```ruby
743
+ field :tags, input_type: :array_input, input_options: {
744
+ placeholder: "Add a tag...",
745
+ max: 10,
746
+ suggestions: %w[ruby rails javascript python]
747
+ }
748
+ ```
749
+
750
+ ### Field Options
751
+
752
+ Fields support prefix, suffix, hints, placeholders, and input-specific options.
753
+
754
+ **YAML:**
755
+
756
+ ```yaml
757
+ fields:
758
+ - field: title
759
+ placeholder: "Enter title..."
760
+ autofocus: true
761
+ col_span: 2
762
+ hint: "A short, descriptive name"
763
+ - field: price
764
+ input_type: number
765
+ prefix: "$"
766
+ suffix: "USD"
767
+ input_options:
768
+ min: 0
769
+ step: 0.01
770
+ - field: description
771
+ input_type: textarea
772
+ input_options:
773
+ rows: 6
774
+ max_length: 500
775
+ show_counter: true
776
+ - field: priority
777
+ input_type: slider
778
+ input_options:
779
+ min: 1
780
+ max: 10
781
+ step: 1
782
+ show_value: true
783
+ - field: internal_code
784
+ readonly: true
785
+ hint: "Auto-generated, cannot be changed"
786
+ ```
787
+
788
+ **Ruby DSL:**
789
+
790
+ ```ruby
791
+ form do
792
+ section "Details", columns: 2 do
793
+ field :title, placeholder: "Enter title...", autofocus: true,
794
+ col_span: 2, hint: "A short, descriptive name"
795
+ field :price, input_type: :number, prefix: "$", suffix: "USD",
796
+ input_options: { min: 0, step: 0.01 }
797
+ field :description, input_type: :textarea,
798
+ input_options: { rows: 6, max_length: 500, show_counter: true }
799
+ field :priority, input_type: :slider,
800
+ input_options: { min: 1, max: 10, step: 1, show_value: true }
801
+ field :internal_code, readonly: true, hint: "Auto-generated, cannot be changed"
802
+ end
803
+ end
804
+ ```
805
+
806
+ ### Dynamic Defaults
807
+
808
+ Set default values that are resolved at form render time.
809
+
810
+ **YAML:**
811
+
812
+ ```yaml
813
+ fields:
814
+ - { field: start_date, input_type: date, default: current_date }
815
+ - { field: assigned_to_id, input_type: association_select, default: current_user.id }
816
+ ```
817
+
818
+ **Ruby DSL:**
819
+
820
+ ```ruby
821
+ field :start_date, input_type: :date, default: "current_date"
822
+ field :assigned_to_id, input_type: :association_select, default: "current_user.id"
823
+ ```
824
+
825
+ | Value | Description |
826
+ |-------|-------------|
827
+ | `current_date` | Today's date |
828
+ | `current_datetime` | Current date and time |
829
+ | `current_user.id` | The current user's ID |
830
+
831
+ ### Dividers
832
+
833
+ Use dividers to visually separate groups of fields within a section.
834
+
835
+ **YAML:**
836
+
837
+ ```yaml
838
+ fields:
839
+ - { field: first_name }
840
+ - { field: last_name }
841
+ - { type: divider, label: "Contact Information" }
842
+ - { field: email }
843
+ - { field: phone }
844
+ - { type: divider }
845
+ - { field: notes, input_type: textarea }
846
+ ```
847
+
848
+ **Ruby DSL:**
849
+
850
+ ```ruby
851
+ section "Contact Details", columns: 2 do
852
+ field :first_name
853
+ field :last_name
854
+ divider label: "Contact Information"
855
+ field :email
856
+ field :phone
857
+ divider
858
+ field :notes, input_type: :textarea
859
+ end
860
+ ```
861
+
862
+ A divider with a `label` renders a labeled horizontal rule. Without a label, it renders a plain separator line.
863
+
864
+ ### Association Selects
865
+
866
+ Foreign key fields can render as dropdowns populated from the associated model's records.
867
+
868
+ **YAML:**
869
+
870
+ ```yaml
871
+ fields:
872
+ - { field: company_id, input_type: association_select }
873
+ - { field: contact_id, input_type: association_select }
874
+ ```
875
+
876
+ **Ruby DSL:**
877
+
878
+ ```ruby
879
+ field :company_id, input_type: :association_select
880
+ field :contact_id, input_type: :association_select
881
+ ```
882
+
883
+ The dropdown display text uses `to_label` (if defined on the target model) or `to_s`. The target model must be registered in the LCP registry.
884
+
885
+ ### Collapsible Sections
886
+
887
+ Sections can be collapsible, optionally starting collapsed. This is useful for "Advanced" or rarely-edited fields.
888
+
889
+ **YAML:**
890
+
891
+ ```yaml
892
+ form:
893
+ sections:
894
+ - title: "Basic Information"
895
+ columns: 2
896
+ fields:
897
+ - { field: name }
898
+ - { field: email }
899
+ - title: "Advanced Options"
900
+ collapsible: true
901
+ collapsed: true
902
+ fields:
903
+ - { field: api_key }
904
+ - { field: webhook_url }
905
+ ```
906
+
907
+ **Ruby DSL:**
908
+
909
+ ```ruby
910
+ form do
911
+ section "Basic Information", columns: 2 do
912
+ field :name
913
+ field :email
914
+ end
915
+
916
+ section "Advanced Options", collapsible: true, collapsed: true do
917
+ field :api_key
918
+ field :webhook_url
919
+ end
920
+ end
921
+ ```
922
+
923
+ ### Responsive Form Sections
924
+
925
+ Override the column count at different breakpoints.
926
+
927
+ **YAML:**
928
+
929
+ ```yaml
930
+ form:
931
+ sections:
932
+ - title: "Contact Details"
933
+ columns: 3
934
+ responsive:
935
+ tablet:
936
+ columns: 2
937
+ mobile:
938
+ columns: 1
939
+ fields:
940
+ - { field: first_name }
941
+ - { field: last_name }
942
+ - { field: email }
943
+ ```
944
+
945
+ **Ruby DSL:**
946
+
947
+ ```ruby
948
+ form do
949
+ section "Contact Details", columns: 3, responsive: { tablet: 2, mobile: 1 } do
950
+ field :first_name
951
+ field :last_name
952
+ field :email
953
+ end
954
+ end
955
+ ```
956
+
957
+ ---
958
+
959
+ ## Nested Fields
960
+
961
+ Use nested fields to manage a list of structured items inline within the parent form. Users can add, remove, and reorder items without leaving the page. There are three data sources:
962
+
963
+ ### Association Source (has_many)
964
+
965
+ Edits child records from a `has_many` association:
966
+
967
+ **YAML:**
968
+
969
+ ```yaml
970
+ form:
971
+ sections:
972
+ - title: "Line Items"
973
+ type: nested_fields
974
+ association: line_items
975
+ allow_add: true
976
+ allow_remove: true
977
+ add_label: "Add Line Item"
978
+ min: 1
979
+ max: 20
980
+ columns: 3
981
+ fields:
982
+ - { field: product_id, input_type: association_select }
983
+ - { field: quantity, input_type: number }
984
+ - { field: unit_price, input_type: number, prefix: "$" }
985
+ ```
986
+
987
+ **Ruby DSL:**
988
+
989
+ ```ruby
990
+ form do
991
+ nested_fields "Line Items", association: :line_items,
992
+ allow_add: true, allow_remove: true,
993
+ add_label: "Add Line Item", min: 1, max: 20, columns: 3 do
994
+ field :product_id, input_type: :association_select
995
+ field :quantity, input_type: :number
996
+ field :unit_price, input_type: :number, prefix: "$"
997
+ end
998
+ end
999
+ ```
1000
+
1001
+ ### JSON Field Source (Inline)
1002
+
1003
+ Stores items as a JSON array of hashes in a single column. Field types and labels are declared directly in the presenter — no model needed:
1004
+
1005
+ **YAML:**
1006
+
1007
+ ```yaml
1008
+ form:
1009
+ sections:
1010
+ - title: "Workflow Steps"
1011
+ type: nested_fields
1012
+ json_field: steps
1013
+ allow_add: true
1014
+ allow_remove: true
1015
+ columns: 2
1016
+ fields:
1017
+ - { field: name, type: string, label: "Step Name" }
1018
+ - field: action_type
1019
+ type: string
1020
+ input_type: select
1021
+ options: [review, approve, notify]
1022
+ - field: timeout_days
1023
+ type: integer
1024
+ label: "Timeout (days)"
1025
+ visible_when: { field: action_type, operator: eq, value: review }
1026
+ ```
1027
+
1028
+ **Ruby DSL:**
1029
+
1030
+ ```ruby
1031
+ form do
1032
+ nested_fields "Workflow Steps", json_field: :steps,
1033
+ allow_add: true, allow_remove: true, columns: 2 do
1034
+ field :name, type: :string, label: "Step Name"
1035
+ field :action_type, type: :string, input_type: :select,
1036
+ input_options: { values: %w[review approve notify] }
1037
+ field :timeout_days, type: :integer, label: "Timeout (days)",
1038
+ visible_when: { field: :action_type, operator: :eq, value: "review" }
1039
+ end
1040
+ end
1041
+ ```
1042
+
1043
+ The parent model must have a `json` type field. Inline mode is best for simple structures where creating a model definition is overkill.
1044
+
1045
+ ### JSON Field Source (Model-Backed)
1046
+
1047
+ For complex structures with validations, transforms, or custom types. Define a virtual model for the item structure:
1048
+
1049
+ ```yaml
1050
+ # config/lcp_ruby/models/address.yml
1051
+ model:
1052
+ name: address
1053
+ table_name: _virtual # no DB table — metadata only
1054
+ fields:
1055
+ - name: street
1056
+ type: string
1057
+ validations: [{ type: presence }]
1058
+ - name: city
1059
+ type: string
1060
+ validations: [{ type: presence }]
1061
+ - name: zip
1062
+ type: string
1063
+ ```
1064
+
1065
+ Then reference it with `target_model:`:
1066
+
1067
+ **YAML:**
1068
+
1069
+ ```yaml
1070
+ form:
1071
+ sections:
1072
+ - title: "Addresses"
1073
+ type: nested_fields
1074
+ json_field: addresses
1075
+ target_model: address
1076
+ allow_add: true
1077
+ allow_remove: true
1078
+ columns: 2
1079
+ fields:
1080
+ - { field: street }
1081
+ - { field: city }
1082
+ - { field: zip }
1083
+ ```
1084
+
1085
+ **Ruby DSL:**
1086
+
1087
+ ```ruby
1088
+ form do
1089
+ nested_fields "Addresses", json_field: :addresses, target_model: :address,
1090
+ allow_add: true, allow_remove: true, columns: 2 do
1091
+ field :street
1092
+ field :city
1093
+ field :zip
1094
+ end
1095
+ end
1096
+ ```
1097
+
1098
+ Field metadata (type, label, validations) comes from the virtual model. Items are validated per-row on save. See [Virtual Models](../reference/models.md#virtual-models).
1099
+
1100
+ ### Conditional Fields in Nested Rows
1101
+
1102
+ Fields inside nested rows support `visible_when` and `disable_when`, evaluated against the **current row's data**:
1103
+
1104
+ **YAML:**
1105
+
1106
+ ```yaml
1107
+ - title: "Line Items"
1108
+ type: nested_fields
1109
+ association: line_items
1110
+ columns: 4
1111
+ fields:
1112
+ - { field: item_type, input_type: select }
1113
+ - { field: description }
1114
+ - field: discount_percent
1115
+ input_type: number
1116
+ visible_when: { field: item_type, operator: eq, value: discount }
1117
+ hint: "Enter discount percentage"
1118
+ - field: notes
1119
+ visible_when: { field: item_type, operator: in, value: "service,discount" }
1120
+ ```
1121
+
1122
+ **Ruby DSL:**
1123
+
1124
+ ```ruby
1125
+ nested_fields "Line Items", association: :line_items, columns: 4 do
1126
+ field :item_type, input_type: :select
1127
+ field :description
1128
+ field :discount_percent, input_type: :number,
1129
+ visible_when: { field: :item_type, operator: :eq, value: "discount" },
1130
+ hint: "Enter discount percentage"
1131
+ field :notes,
1132
+ visible_when: { field: :item_type, operator: :in, value: "service,discount" }
1133
+ end
1134
+ ```
1135
+
1136
+ Each row evaluates conditions independently — changing `item_type` in one row does not affect other rows. See [Row-Scoped Conditions](conditional-rendering.md#row-scoped-conditions-in-nested-fields).
1137
+
1138
+ ### Sub-Sections in Nested Rows
1139
+
1140
+ For complex items with many fields, use `section` blocks (in DSL) or `sub_sections` (in YAML) to group fields:
1141
+
1142
+ **YAML:**
1143
+
1144
+ ```yaml
1145
+ - title: "Addresses"
1146
+ type: nested_fields
1147
+ json_field: addresses
1148
+ target_model: address
1149
+ sub_sections:
1150
+ - title: "Location"
1151
+ columns: 2
1152
+ fields:
1153
+ - { field: street }
1154
+ - { field: city }
1155
+ - title: "Additional"
1156
+ collapsible: true
1157
+ collapsed: true
1158
+ fields:
1159
+ - { field: notes }
1160
+ ```
1161
+
1162
+ **Ruby DSL:**
1163
+
1164
+ ```ruby
1165
+ nested_fields "Addresses", json_field: :addresses, target_model: :address do
1166
+ section "Location", columns: 2 do
1167
+ field :street
1168
+ field :city
1169
+ end
1170
+ section "Additional", collapsible: true, collapsed: true do
1171
+ field :notes
1172
+ end
1173
+ end
1174
+ ```
1175
+
1176
+ You cannot mix `field` and `section` calls in the same `nested_fields` block — use one or the other.
1177
+
1178
+ ### Nested Fields Options
1179
+
1180
+ | Option | Default | Description |
1181
+ |--------|---------|-------------|
1182
+ | `association` | - | has_many association name (mutually exclusive with `json_field`) |
1183
+ | `json_field` | - | JSON column name (mutually exclusive with `association`) |
1184
+ | `target_model` | - | Virtual model for item structure (only with `json_field`) |
1185
+ | `allow_add` | `true` | Show a button to add new items |
1186
+ | `allow_remove` | `true` | Show a remove button on each row |
1187
+ | `add_label` | `"Add"` | Label for the add button |
1188
+ | `min` | - | Minimum number of items |
1189
+ | `max` | - | Maximum number of items |
1190
+ | `columns` | - | Grid columns for each row's field layout |
1191
+ | `empty_message` | - | Message when there are no items |
1192
+ | `sortable` | `false` | Enable drag-and-drop reordering |
1193
+
1194
+ ### Sortable Nested Forms
1195
+
1196
+ Enable drag-and-drop reordering by setting `sortable: true`. The position field is automatically hidden from the visible form and managed via hidden inputs.
1197
+
1198
+ **YAML:**
1199
+
1200
+ ```yaml
1201
+ - title: "Checklist Items"
1202
+ type: nested_fields
1203
+ association: checklist_items
1204
+ sortable: true
1205
+ add_label: "Add Item"
1206
+ fields:
1207
+ - { field: title }
1208
+ - { field: completed, input_type: boolean }
1209
+ ```
1210
+
1211
+ **Ruby DSL:**
1212
+
1213
+ ```ruby
1214
+ nested_fields "Checklist Items", association: :checklist_items,
1215
+ sortable: true, add_label: "Add Item" do
1216
+ field :title
1217
+ field :completed, input_type: :boolean
1218
+ end
1219
+ ```
1220
+
1221
+ The child model should have an integer `position` field, and the parent association should specify `order: { position: asc }`. For a custom position field name, pass a string instead of `true`: `sortable: "sort_order"`. For JSON field items, array order is the position — no position key needed.
1222
+
1223
+ ---
1224
+
1225
+ ## Search and Filters
1226
+
1227
+ The search configuration adds a search bar and optional predefined filter buttons to the index page.
1228
+
1229
+ > **Looking for dropdown/selectbox filters?** Use [Search Parameters](#search-parameters) below to add always-visible select controls (e.g., "Department" or "Status" dropdowns) directly in the toolbar. For filters that apply across **all zones** on a composite page, use [Page Parameters](../reference/pages.md#page-parameters) instead.
1230
+
1231
+ **YAML:**
1232
+
1233
+ ```yaml
1234
+ search:
1235
+ enabled: true
1236
+ searchable_fields: [title, description, email]
1237
+ placeholder: "Search contacts..."
1238
+ predefined_filters:
1239
+ - { name: all, label: "All", default: true }
1240
+ - { name: active, label: "Active", scope: active }
1241
+ - { name: vip, label: "VIP Clients", scope: vip_clients }
1242
+ - { name: recent, label: "Recent", scope: recent }
1243
+ ```
1244
+
1245
+ **Ruby DSL:**
1246
+
1247
+ ```ruby
1248
+ search do
1249
+ searchable_fields :title, :description, :email
1250
+ placeholder "Search contacts..."
1251
+ filter :all, label: "All", default: true
1252
+ filter :active, label: "Active", scope: :active
1253
+ filter :vip, label: "VIP Clients", scope: :vip_clients
1254
+ filter :recent, label: "Recent", scope: :recent
1255
+ end
1256
+ ```
1257
+
1258
+ Each filter (except the default "all") maps to a named scope defined in the model YAML. The `all` filter shows unfiltered results.
1259
+
1260
+ To disable search entirely:
1261
+
1262
+ **YAML:**
1263
+
1264
+ ```yaml
1265
+ search:
1266
+ enabled: false
1267
+ ```
1268
+
1269
+ **Ruby DSL:**
1270
+
1271
+ ```ruby
1272
+ search enabled: false
1273
+ ```
1274
+
1275
+ ### Type-Aware Quick Search
1276
+
1277
+ The quick search bar (`?qs=` parameter) is type-aware. Each `searchable_fields` entry is checked by type:
1278
+
1279
+ - **String/text** — substring match (`ILIKE '%term%'`)
1280
+ - **Integer/float/decimal** — exact match only when the search term is numeric
1281
+ - **Date/datetime** — range match only when the search term parses as a date
1282
+ - **Enum** — matches against display labels, not raw values (e.g., searching "won" matches `closed_won` if its humanized label contains "won")
1283
+
1284
+ Fields that don't match the search term's type are silently skipped, so searching for "hello" on a deals index won't error on the `value` (decimal) field.
1285
+
1286
+ ### Search Parameters
1287
+
1288
+ Search parameters add typed filter controls — dropdowns, toggles, and range inputs — directly in the search toolbar. They generate standard Ransack `?f[...]` URL params, so they work seamlessly with the existing filter pipeline, saved filters, and export.
1289
+
1290
+ **YAML:**
1291
+
1292
+ ```yaml
1293
+ search:
1294
+ enabled: true
1295
+ columns: 2
1296
+ searchable_fields: [name, email]
1297
+ parameters:
1298
+ - name: department
1299
+ type: association_select
1300
+ field: department_id
1301
+
1302
+ - name: status
1303
+ type: enum_select
1304
+ field: status
1305
+
1306
+ - name: active
1307
+ type: boolean_select
1308
+ field: active
1309
+ ```
1310
+
1311
+ **Ruby DSL:**
1312
+
1313
+ ```ruby
1314
+ search do
1315
+ columns 2
1316
+ searchable_fields :name, :email
1317
+ parameter :department, type: :association_select, field: :department_id
1318
+ parameter :status, type: :enum_select, field: :status
1319
+ parameter :active, type: :boolean_select, field: :active
1320
+ end
1321
+ ```
1322
+
1323
+ Five parameter types are available: `enum_select`, `association_select`, `boolean_select`, `number_range`, and `date_range`. Enum options and association models are auto-derived from the model definition when not explicitly specified. Parameters render in a CSS Grid with configurable `columns` (default: 1). Labels display inline beside controls. Range types auto-span 2 columns when `columns >= 2`; use `col_span` per parameter to override.
1324
+
1325
+ For large association datasets, opt into remote server-side search with `search: true`:
1326
+
1327
+ ```yaml
1328
+ - name: company
1329
+ type: association_select
1330
+ field: company_id
1331
+ search: true
1332
+ per_page: 25
1333
+ ```
1334
+
1335
+ Parameters support role-based visibility via `visible_when`. Hidden parameters are not rendered and their filter params are stripped server-side for security:
1336
+
1337
+ ```yaml
1338
+ - name: tenant
1339
+ type: association_select
1340
+ field: tenant_id
1341
+ visible_when:
1342
+ role: admin
1343
+ ```
1344
+
1345
+ See [Presenters Reference — Search Parameters](../reference/presenters.md#search-parameters) for the full attribute reference.
1346
+
1347
+ ### Advanced Filter Builder
1348
+
1349
+ The advanced filter builder adds a visual UI for constructing field-level filter conditions. Users select a field, an operator, and a value. Multiple conditions combine with AND logic, with optional OR grouping.
1350
+
1351
+ **YAML:**
1352
+
1353
+ ```yaml
1354
+ search:
1355
+ enabled: true
1356
+ searchable_fields: [title, description]
1357
+
1358
+ advanced_filter:
1359
+ enabled: true
1360
+ max_conditions: 20
1361
+ max_association_depth: 3
1362
+ allow_or_groups: true
1363
+
1364
+ filterable_fields:
1365
+ - title
1366
+ - stage
1367
+ - value
1368
+ - expected_close_date
1369
+ - company.name
1370
+ - company.industry
1371
+ - contact.email
1372
+
1373
+ field_options:
1374
+ stage:
1375
+ operators: [eq, not_eq, in, not_in]
1376
+ value:
1377
+ operators: [eq, gt, gteq, lt, lteq, between]
1378
+
1379
+ presets:
1380
+ - name: high_value_open
1381
+ label: "High-value open deals"
1382
+ conditions:
1383
+ - { field: stage, operator: not_in, value: [closed_won, closed_lost] }
1384
+ - { field: value, operator: gteq, value: 10000 }
1385
+
1386
+ custom_filters:
1387
+ - name: region
1388
+ label: "Region"
1389
+ type: string
1390
+ ```
1391
+
1392
+ **Ruby DSL:**
1393
+
1394
+ ```ruby
1395
+ search do
1396
+ searchable_fields :title, :description
1397
+
1398
+ advanced_filter do
1399
+ max_conditions 20
1400
+ max_association_depth 3
1401
+ allow_or_groups true
1402
+
1403
+ filterable_field :title
1404
+ filterable_field :stage, operators: [:eq, :not_eq, :in, :not_in]
1405
+ filterable_field :value, operators: [:eq, :gt, :gteq, :lt, :lteq, :between]
1406
+ filterable_field :company, :name
1407
+ filterable_field :contact, :email
1408
+
1409
+ preset "high_value_open", label: "High-value open deals" do
1410
+ condition field: :stage, operator: :not_in, value: %w[closed_won closed_lost]
1411
+ condition field: :value, operator: :gteq, value: 10000
1412
+ end
1413
+ end
1414
+ end
1415
+ ```
1416
+
1417
+ If `filterable_fields` is omitted, all readable model fields are auto-detected based on the current user's permissions.
1418
+
1419
+ ### Filter Presets
1420
+
1421
+ Presets appear as pill-shaped buttons at the top of the filter panel. Clicking a preset immediately applies its conditions (page navigates with filter URL params). If the current URL filters match a preset exactly, the button is highlighted.
1422
+
1423
+ Presets support all operator types: equality, comparison, text matching, no-value operators (`present`, `true`, `this_month`), and multi-value operators (`in`, `not_in`). See the [presets reference](../reference/presenters.md#presets) for the full configuration format.
1424
+
1425
+ ### Cascading Field Picker
1426
+
1427
+ When filtering by association fields (e.g., `company.country.name`), the filter builder renders a cascading drill-down selector instead of a flat dropdown. The first select shows direct fields and association names; selecting an association reveals a second select with that association's fields and sub-associations, and so on up to `max_association_depth`.
1428
+
1429
+ This approach keeps the initial dropdown manageable even for models with many associations and deep hierarchies.
1430
+
1431
+ ### Operators by Field Type
1432
+
1433
+ Available operators are auto-detected from the field type. The full matrix is documented in the [Advanced Search design document](../design/advanced_search.md). Key highlights:
1434
+
1435
+ | Field Type | Notable Operators |
1436
+ |------------|-------------------|
1437
+ | string | `eq`, `cont`, `start`, `end`, `in`, `present`, `blank` |
1438
+ | integer, float, decimal | `eq`, `gt`, `gteq`, `lt`, `lteq`, `between`, `in` |
1439
+ | date, datetime | `eq`, `gt`, `lt`, `between`, `this_week`, `this_month`, `this_year` |
1440
+ | boolean | `true`, `false`, `null` |
1441
+ | enum | `eq`, `in`, `not_in`, `present`, `blank` |
1442
+
1443
+ Use `field_options` to restrict or customize operators for specific fields.
1444
+
1445
+ ### Filtering by Association Fields
1446
+
1447
+ Dot notation lets users filter by fields on related models:
1448
+
1449
+ ```yaml
1450
+ filterable_fields:
1451
+ - company.name # 1 level deep (belongs_to :company)
1452
+ - company.industry # 1 level deep
1453
+ - contact.company.country # 2 levels deep (contact → company → country)
1454
+ ```
1455
+
1456
+ The `max_association_depth` setting (default: 3) limits how deep association chains can go. Each association segment is permission-checked — users can only filter on associations they have read access to.
1457
+
1458
+ ### Query Language (QL) Mode
1459
+
1460
+ The query language provides a text-based alternative to the visual filter builder. Enable it via `query_language: true`:
1461
+
1462
+ ```yaml
1463
+ search:
1464
+ advanced_filter:
1465
+ enabled: true
1466
+ query_language: true
1467
+ ```
1468
+
1469
+ Users toggle between visual and QL mode with the "Edit as QL" button. The two modes are bidirectional — changes in one are reflected in the other.
1470
+
1471
+ **QL syntax examples:**
1472
+
1473
+ ```
1474
+ stage = 'lead' # equals
1475
+ value > 10000 # greater than
1476
+ title ~ 'Acme' # contains
1477
+ company.name = 'Acme Corp' # association field
1478
+ stage in ['lead', 'qualified'] # list membership
1479
+ expected_close_date is null # null check
1480
+ active is true # boolean check
1481
+ stage = 'lead' and value > 5000 # AND combinator
1482
+ (a = 1 or b = 2) and (c = 3 or d = 4) # nested groups
1483
+ @open_deals and value > 5000 # scope reference + condition
1484
+ ```
1485
+
1486
+ See the [Advanced Search design document](../design/advanced_search.md#8-query-language-ql) for the full operator mapping.
1487
+
1488
+ ### Recursive Condition Nesting
1489
+
1490
+ By default, conditions combine with AND at the top level, with optional OR groups (two-level nesting). For deeper nesting, increase `max_nesting_depth`:
1491
+
1492
+ ```yaml
1493
+ search:
1494
+ advanced_filter:
1495
+ enabled: true
1496
+ allow_or_groups: true
1497
+ max_nesting_depth: 3 # Default: 2 (AND with OR groups)
1498
+ ```
1499
+
1500
+ - `max_nesting_depth: 1` — flat AND only (no OR groups)
1501
+ - `max_nesting_depth: 2` — AND with OR groups (default)
1502
+ - `max_nesting_depth: 3+` — full recursive nesting (e.g., `(A AND B) OR (C AND D)`)
1503
+
1504
+ ### Auto-Detect with Exclusions
1505
+
1506
+ Instead of listing all filterable fields explicitly, you can auto-detect all readable fields and exclude specific ones:
1507
+
1508
+ ```yaml
1509
+ search:
1510
+ advanced_filter:
1511
+ enabled: true
1512
+ max_association_depth: 3
1513
+ filterable_fields_except:
1514
+ - internal_notes # exclude a direct field
1515
+ - audit_log # exclude entire association subtree
1516
+ - company.tax_id # exclude a specific association field
1517
+ ```
1518
+
1519
+ This is mutually exclusive with `filterable_fields` — use one or the other.
1520
+
1521
+ ### Saved Filters
1522
+
1523
+ Saved filters let users name and store filter combinations for reuse. Enable via `saved_filters`:
1524
+
1525
+ ```yaml
1526
+ search:
1527
+ saved_filters:
1528
+ enabled: true
1529
+ sharing: true # Allow sharing with roles
1530
+ ```
1531
+
1532
+ Full saved filter support (user-created, shared by role) is planned for a future release. Currently, filter presets defined in YAML serve as the static equivalent. See the [Advanced Search design document](../design/advanced_search.md) for details.
1533
+
1534
+ ---
1535
+
1536
+ ## Actions
1537
+
1538
+ Actions define the buttons available for creating, viewing, editing, deleting, and performing custom operations on records.
1539
+
1540
+ ### Built-In CRUD Actions
1541
+
1542
+ **YAML:**
1543
+
1544
+ ```yaml
1545
+ actions:
1546
+ collection:
1547
+ - { name: create, type: built_in, label: "New Contact", icon: plus }
1548
+ single:
1549
+ - { name: show, type: built_in, icon: eye }
1550
+ - { name: edit, type: built_in, icon: pencil }
1551
+ - { name: destroy, type: built_in, icon: trash, confirm: true, style: danger }
1552
+ ```
1553
+
1554
+ **Ruby DSL:**
1555
+
1556
+ ```ruby
1557
+ action :create, type: :built_in, on: :collection, label: "New Contact", icon: "plus"
1558
+ action :show, type: :built_in, on: :single, icon: "eye"
1559
+ action :edit, type: :built_in, on: :single, icon: "pencil"
1560
+ action :destroy, type: :built_in, on: :single, icon: "trash", confirm: true, style: :danger
1561
+ ```
1562
+
1563
+ Actions are grouped into three categories:
1564
+
1565
+ | Category | Description |
1566
+ |----------|-------------|
1567
+ | `collection` / `on: :collection` | Actions above the table (e.g., "New") |
1568
+ | `single` / `on: :single` | Actions per row (e.g., show, edit, destroy) |
1569
+ | `batch` / `on: :batch` | Actions on multiple selected records |
1570
+
1571
+ ### Custom Actions
1572
+
1573
+ Custom actions trigger domain-specific operations. Use `type: custom` and register an action class in `app/actions/`.
1574
+
1575
+ **YAML:**
1576
+
1577
+ ```yaml
1578
+ actions:
1579
+ single:
1580
+ - name: close_won
1581
+ type: custom
1582
+ label: "Close as Won"
1583
+ icon: check-circle
1584
+ confirm: true
1585
+ confirm_message: "Mark this deal as won?"
1586
+ - name: send_invoice
1587
+ type: custom
1588
+ label: "Send Invoice"
1589
+ icon: mail
1590
+ ```
1591
+
1592
+ **Ruby DSL:**
1593
+
1594
+ ```ruby
1595
+ action :close_won, type: :custom, on: :single,
1596
+ label: "Close as Won", icon: "check-circle",
1597
+ confirm: true, confirm_message: "Mark this deal as won?"
1598
+
1599
+ action :send_invoice, type: :custom, on: :single,
1600
+ label: "Send Invoice", icon: "mail"
1601
+ ```
1602
+
1603
+ See the [Custom Actions Guide](custom-actions.md) for how to implement action classes.
1604
+
1605
+ ### Conditional Actions
1606
+
1607
+ Control when actions are visible or disabled using `visible_when` and `disable_when`.
1608
+
1609
+ **YAML:**
1610
+
1611
+ ```yaml
1612
+ single:
1613
+ - name: close_won
1614
+ type: custom
1615
+ label: "Close as Won"
1616
+ icon: check-circle
1617
+ visible_when: { field: stage, operator: not_in, value: [closed_won, closed_lost] }
1618
+ disable_when: { field: value, operator: blank }
1619
+ - name: reopen
1620
+ type: custom
1621
+ label: "Reopen"
1622
+ icon: refresh-cw
1623
+ visible_when: { field: stage, operator: in, value: [closed_won, closed_lost] }
1624
+ ```
1625
+
1626
+ **Ruby DSL:**
1627
+
1628
+ ```ruby
1629
+ action :close_won, type: :custom, on: :single,
1630
+ label: "Close as Won", icon: "check-circle",
1631
+ visible_when: { field: :stage, operator: :not_in, value: [:closed_won, :closed_lost] },
1632
+ disable_when: { field: :value, operator: :blank }
1633
+
1634
+ action :reopen, type: :custom, on: :single,
1635
+ label: "Reopen", icon: "refresh-cw",
1636
+ visible_when: { field: :stage, operator: :in, value: [:closed_won, :closed_lost] }
1637
+ ```
1638
+
1639
+ An action can use both `visible_when` and `disable_when` together. Visibility is evaluated first -- if the action is hidden, `disable_when` has no effect.
1640
+
1641
+ ### Form Actions
1642
+
1643
+ Form actions replace the default Save/Cancel buttons on create/edit forms with configurable submit buttons. Each button can have its own redirect target, set field values on submit, and be filtered by role or form context (create vs. update).
1644
+
1645
+ When `actions.form` is not configured, the default Save and Cancel buttons render automatically.
1646
+
1647
+ **YAML:**
1648
+
1649
+ ```yaml
1650
+ actions:
1651
+ form:
1652
+ - name: save
1653
+ type: built_in
1654
+ style: primary
1655
+ redirect: show
1656
+
1657
+ - name: save_and_new
1658
+ label: "Save & New"
1659
+ redirect: new
1660
+ only_on: create
1661
+
1662
+ - name: save_and_stay
1663
+ label: "Save & Continue Editing"
1664
+ redirect: edit
1665
+ only_on: update
1666
+
1667
+ - name: cancel
1668
+ type: built_in
1669
+ style: secondary
1670
+ ```
1671
+
1672
+ **Ruby DSL:**
1673
+
1674
+ ```ruby
1675
+ form_action :save, type: :built_in, style: :primary, redirect: :show
1676
+ form_action :save_and_new, label: "Save & New", redirect: :new, only_on: :create
1677
+ form_action :save_and_stay, label: "Save & Continue Editing",
1678
+ redirect: :edit, only_on: :update
1679
+ form_action :cancel, type: :built_in, style: :secondary
1680
+ ```
1681
+
1682
+ Use `set_fields` to inject values when a specific button is pressed. This is useful for "Save as Draft" vs. "Publish" patterns:
1683
+
1684
+ ```yaml
1685
+ actions:
1686
+ form:
1687
+ - name: save_draft
1688
+ label: "Save Draft"
1689
+ redirect: edit
1690
+
1691
+ - name: save_and_publish
1692
+ label: "Publish"
1693
+ style: primary
1694
+ icon: globe
1695
+ set_fields:
1696
+ status: published
1697
+ published_at: { date: now }
1698
+ confirm: true
1699
+ redirect: show
1700
+ visible_when: { field: status, operator: not_eq, value: published }
1701
+
1702
+ - { name: cancel, type: built_in }
1703
+ ```
1704
+
1705
+ `set_fields` values override user input — if the user submits `status: draft` but the form action says `status: published`, the configured value wins.
1706
+
1707
+ ### Pipeline: Save + Workflow Transition
1708
+
1709
+ Form actions can chain multiple operations in a single transaction using `pipeline` or the `transition:` / `action:` sugar attributes.
1710
+
1711
+ **YAML:**
1712
+
1713
+ ```yaml
1714
+ actions:
1715
+ form:
1716
+ - name: save_draft
1717
+ label: "Save Draft"
1718
+ style: secondary
1719
+ redirect: edit
1720
+
1721
+ - name: save_and_submit
1722
+ label: "Save & Submit"
1723
+ style: primary
1724
+ icon: send
1725
+ transition: submit
1726
+ confirm: true
1727
+ confirm_message: "Submit for approval?"
1728
+ visible_when: { field: status, operator: eq, value: draft }
1729
+ redirect: show
1730
+
1731
+ - { name: cancel, type: built_in }
1732
+ ```
1733
+
1734
+ **Ruby DSL:**
1735
+
1736
+ ```ruby
1737
+ form_action :save_draft, label: "Save Draft", style: :secondary, redirect: :edit
1738
+ form_action :save_and_submit, label: "Save & Submit", style: :primary, icon: "send",
1739
+ transition: :submit, confirm: true,
1740
+ confirm_message: "Submit for approval?",
1741
+ visible_when: { field: :status, operator: :eq, value: "draft" },
1742
+ redirect: :show
1743
+ form_action :cancel, type: :built_in, style: :secondary
1744
+ ```
1745
+
1746
+ The `transition: submit` sugar expands to `pipeline: [save, { transition: submit }]`. For more complex sequences, use `pipeline:` explicitly:
1747
+
1748
+ ```yaml
1749
+ - name: save_submit_and_notify
1750
+ label: "Submit & Notify"
1751
+ pipeline:
1752
+ - save
1753
+ - { transition: submit }
1754
+ - { action: notify_approvers }
1755
+ confirm: true
1756
+ redirect: show
1757
+ ```
1758
+
1759
+ All pipeline steps run in a single transaction — if any step fails, the entire operation rolls back.
1760
+
1761
+ Form actions support `only_roles` / `except_roles` for role-based visibility, and the controller re-validates authorization at submit time to prevent parameter tampering.
1762
+
1763
+ See the [Action Buttons & Pipelines Guide](action-buttons.md) for the full walkthrough, including pipelines, dialog form actions, cross-presenter redirect, and show page pipelines. See the [Presenters Reference](../reference/presenters.md#form-actions) for the full attribute table.
1764
+
1765
+ ---
1766
+
1767
+ ## Conditional Rendering
1768
+
1769
+ Form fields and sections can be conditionally shown/hidden or enabled/disabled based on record values.
1770
+
1771
+ ### Field-Value Conditions
1772
+
1773
+ Evaluated client-side with JavaScript for instant reactivity.
1774
+
1775
+ **YAML:**
1776
+
1777
+ ```yaml
1778
+ fields:
1779
+ - field: expected_revenue
1780
+ input_type: number
1781
+ prefix: "$"
1782
+ visible_when: { field: stage, operator: not_in, value: [lead] }
1783
+ - field: close_reason
1784
+ input_type: textarea
1785
+ disable_when: { field: stage, operator: not_in, value: [closed_won, closed_lost] }
1786
+ ```
1787
+
1788
+ **Ruby DSL:**
1789
+
1790
+ ```ruby
1791
+ field :expected_revenue, input_type: :number, prefix: "$",
1792
+ visible_when: { field: :stage, operator: :not_in, value: ["lead"] }
1793
+
1794
+ field :close_reason, input_type: :textarea,
1795
+ disable_when: { field: :stage, operator: :not_in, value: ["closed_won", "closed_lost"] }
1796
+ ```
1797
+
1798
+ ### Service Conditions
1799
+
1800
+ For server-side logic (database lookups, complex business rules), use service conditions.
1801
+
1802
+ **YAML:**
1803
+
1804
+ ```yaml
1805
+ fields:
1806
+ - field: internal_code
1807
+ visible_when: { service: persisted_check }
1808
+ ```
1809
+
1810
+ **Ruby DSL:**
1811
+
1812
+ ```ruby
1813
+ field :internal_code, visible_when: { service: :persisted_check }
1814
+ ```
1815
+
1816
+ ### Section-Level Conditions
1817
+
1818
+ Apply conditions to entire form sections.
1819
+
1820
+ **YAML:**
1821
+
1822
+ ```yaml
1823
+ sections:
1824
+ - title: "Revenue Details"
1825
+ visible_when: { field: stage, operator: not_eq, value: lead }
1826
+ fields:
1827
+ - { field: expected_revenue, input_type: number }
1828
+ - { field: probability, input_type: slider }
1829
+ ```
1830
+
1831
+ **Ruby DSL:**
1832
+
1833
+ ```ruby
1834
+ section "Revenue Details",
1835
+ visible_when: { field: :stage, operator: :not_eq, value: "lead" } do
1836
+ field :expected_revenue, input_type: :number
1837
+ field :probability, input_type: :slider
1838
+ end
1839
+ ```
1840
+
1841
+ For full details on condition operators and service conditions, see the [Conditional Rendering Guide](conditional-rendering.md) and [Condition Operators Reference](../reference/condition-operators.md).
1842
+
1843
+ ---
1844
+
1845
+ ## Read-Only and Embeddable Presenters
1846
+
1847
+ ### Read-Only Presenters
1848
+
1849
+ Set `read_only: true` to disable create, edit, and destroy operations. The model data is still writable through other presenters or direct code. Use this for dashboards or reporting views.
1850
+
1851
+ **YAML:**
1852
+
1853
+ ```yaml
1854
+ presenter:
1855
+ name: deal_pipeline
1856
+ model: deal
1857
+ label: "Pipeline"
1858
+ slug: pipeline
1859
+ icon: bar-chart
1860
+ read_only: true
1861
+
1862
+ index:
1863
+ table_columns:
1864
+ - { field: title, link_to: show, sortable: true }
1865
+ - field: stage
1866
+ renderer: badge
1867
+ options:
1868
+ color_map:
1869
+ open: blue
1870
+ closed_won: green
1871
+ closed_lost: red
1872
+ - { field: value, renderer: currency, summary: sum }
1873
+ ```
1874
+
1875
+ **Ruby DSL:**
1876
+
1877
+ ```ruby
1878
+ define_presenter :deal_pipeline do
1879
+ model :deal
1880
+ label "Pipeline"
1881
+ slug "pipeline"
1882
+ icon "bar-chart"
1883
+ read_only true
1884
+
1885
+ index do
1886
+ column :title, link_to: :show, sortable: true
1887
+ column :stage, renderer: :badge,
1888
+ options: { color_map: { open: "blue", closed_won: "green", closed_lost: "red" } }
1889
+ column :value, renderer: :currency, summary: :sum
1890
+ end
1891
+
1892
+ action :show, type: :built_in, on: :single, icon: "eye"
1893
+ end
1894
+ ```
1895
+
1896
+ ### Embeddable Presenters
1897
+
1898
+ Set `embeddable: true` to mark a presenter for embedding within other views (e.g., as an inline table within a parent record's show page). This is a metadata flag for the UI layer.
1899
+
1900
+ **YAML:**
1901
+
1902
+ ```yaml
1903
+ presenter:
1904
+ name: deal_embed
1905
+ model: deal
1906
+ label: "Deals"
1907
+ embeddable: true
1908
+ # no slug -- not directly routable
1909
+ ```
1910
+
1911
+ **Ruby DSL:**
1912
+
1913
+ ```ruby
1914
+ define_presenter :deal_embed do
1915
+ model :deal
1916
+ label "Deals"
1917
+ embeddable true
1918
+ end
1919
+ ```
1920
+
1921
+ ---
1922
+
1923
+ ## DSL Inheritance
1924
+
1925
+ The Ruby DSL supports inheritance, where a child presenter copies the parent's configuration and overrides specific sections. This avoids duplication when you need multiple views of the same model.
1926
+
1927
+ **Ruby DSL:**
1928
+
1929
+ ```ruby
1930
+ # config/lcp_ruby/presenters/deal.rb
1931
+ define_presenter :deal do
1932
+ model :deal
1933
+ label "Deals"
1934
+ slug "deals"
1935
+ icon "dollar-sign"
1936
+
1937
+ index do
1938
+ default_sort :created_at, :desc
1939
+ per_page 25
1940
+ column :title, link_to: :show, sortable: true
1941
+ column :stage, renderer: :badge, sortable: true
1942
+ column :value, renderer: :currency, sortable: true
1943
+ column :updated_at, renderer: :relative_date
1944
+ end
1945
+
1946
+ show do
1947
+ section "Deal Information", columns: 2 do
1948
+ field :title, renderer: :heading
1949
+ field :stage, renderer: :badge
1950
+ field :value, renderer: :currency
1951
+ end
1952
+ end
1953
+
1954
+ form do
1955
+ section "Details", columns: 2 do
1956
+ field :title, placeholder: "Deal title..."
1957
+ field :stage, input_type: :select
1958
+ field :value, input_type: :number, prefix: "$"
1959
+ end
1960
+ end
1961
+
1962
+ search do
1963
+ searchable_fields :title
1964
+ placeholder "Search deals..."
1965
+ filter :all, label: "All", default: true
1966
+ filter :open, label: "Open", scope: :open_deals
1967
+ end
1968
+
1969
+ action :create, type: :built_in, on: :collection, label: "New Deal", icon: "plus"
1970
+ action :show, type: :built_in, on: :single, icon: "eye"
1971
+ action :edit, type: :built_in, on: :single, icon: "pencil"
1972
+ action :destroy, type: :built_in, on: :single, icon: "trash", confirm: true, style: :danger
1973
+ end
1974
+ ```
1975
+
1976
+ ```ruby
1977
+ # config/lcp_ruby/presenters/deal_pipeline.rb
1978
+ define_presenter :deal_pipeline, inherits: :deal do
1979
+ label "Deal Pipeline"
1980
+ slug "pipeline"
1981
+ icon "bar-chart"
1982
+ read_only true
1983
+
1984
+ # Completely replaces the parent's index
1985
+ index do
1986
+ default_view :table
1987
+ per_page 50
1988
+ column :title, link_to: :show, sortable: true
1989
+ column :stage, renderer: :badge, sortable: true
1990
+ column :value, renderer: :currency, summary: :sum
1991
+ end
1992
+
1993
+ # Disables search
1994
+ search enabled: false
1995
+
1996
+ # Replaces parent's actions
1997
+ action :show, type: :built_in, on: :single, icon: "eye"
1998
+ end
1999
+ ```
2000
+
2001
+ ### Inheritance Rules
2002
+
2003
+ - **Section-level replace**: When a child defines `index`, `show`, `form`, `search`, or `actions`, it completely replaces the parent's version. There is no deep merge.
2004
+ - **Unset sections are inherited**: If the child does not define a section, the parent's version is used as-is.
2005
+ - **Top-level attributes**: `name`, `label`, `slug`, `icon`, `read_only`, `embeddable` are taken from the child when defined. The `model` is inherited from the parent unless overridden.
2006
+ - **Parent must exist**: The parent must be defined in a DSL file in the same directory.
2007
+ - **No circular inheritance**: Circular chains are detected and raise `MetadataError`.
2008
+
2009
+ Inheritance is a DSL-only convenience -- YAML files cannot inherit. The result is always a flat hash identical to what you could write in full YAML.
2010
+
2011
+ ---
2012
+
2013
+ ## Common Patterns
2014
+
2015
+ ### CRM Contact List
2016
+
2017
+ A contact list with search, filters, responsive columns, and multiple renderers.
2018
+
2019
+ **YAML:**
2020
+
2021
+ ```yaml
2022
+ presenter:
2023
+ name: contact
2024
+ model: contact
2025
+ label: "Contacts"
2026
+ slug: contacts
2027
+ icon: users
2028
+
2029
+ index:
2030
+ default_sort: { field: name, direction: asc }
2031
+ per_page: 25
2032
+ row_click: show
2033
+ table_columns:
2034
+ - { field: name, width: "25%", link_to: show, sortable: true, pinned: left }
2035
+ - { field: email, renderer: email_link, hidden_on: mobile }
2036
+ - { field: phone, renderer: phone_link, hidden_on: [mobile, tablet] }
2037
+ - field: company_name
2038
+ sortable: true
2039
+ hidden_on: mobile
2040
+ - field: status
2041
+ renderer: badge
2042
+ options:
2043
+ color_map:
2044
+ active: green
2045
+ inactive: gray
2046
+
2047
+ show:
2048
+ layout:
2049
+ - section: "Contact Details"
2050
+ columns: 2
2051
+ responsive:
2052
+ mobile:
2053
+ columns: 1
2054
+ fields:
2055
+ - { field: name, renderer: heading, col_span: 2 }
2056
+ - { field: email, renderer: email_link }
2057
+ - { field: phone, renderer: phone_link }
2058
+ - { field: company_name }
2059
+ - field: status
2060
+ renderer: badge
2061
+ - section: "Deals"
2062
+ type: association_list
2063
+ association: deals
2064
+
2065
+ form:
2066
+ sections:
2067
+ - title: "Contact Information"
2068
+ columns: 2
2069
+ responsive:
2070
+ mobile:
2071
+ columns: 1
2072
+ fields:
2073
+ - { field: first_name, autofocus: true }
2074
+ - { field: last_name }
2075
+ - { type: divider, label: "Communication" }
2076
+ - { field: email, placeholder: "email@example.com" }
2077
+ - { field: phone }
2078
+ - { field: company_id, input_type: association_select }
2079
+ - title: "Notes"
2080
+ collapsible: true
2081
+ fields:
2082
+ - { field: notes, input_type: textarea, input_options: { rows: 4 } }
2083
+
2084
+ search:
2085
+ enabled: true
2086
+ searchable_fields: [first_name, last_name, email]
2087
+ placeholder: "Search contacts..."
2088
+ predefined_filters:
2089
+ - { name: all, label: "All", default: true }
2090
+ - { name: active, label: "Active", scope: active }
2091
+ - { name: inactive, label: "Inactive", scope: inactive }
2092
+
2093
+ actions:
2094
+ collection:
2095
+ - { name: create, type: built_in, label: "New Contact", icon: plus }
2096
+ single:
2097
+ - { name: show, type: built_in, icon: eye }
2098
+ - { name: edit, type: built_in, icon: pencil }
2099
+ - { name: destroy, type: built_in, icon: trash, confirm: true, style: danger }
2100
+ ```
2101
+
2102
+ ### Order Form with Line Items
2103
+
2104
+ A form with nested fields for inline editing of child records.
2105
+
2106
+ **Ruby DSL:**
2107
+
2108
+ ```ruby
2109
+ define_presenter :order do
2110
+ model :order
2111
+ label "Orders"
2112
+ slug "orders"
2113
+ icon "shopping-cart"
2114
+
2115
+ index do
2116
+ default_sort :created_at, :desc
2117
+ per_page 20
2118
+ column :order_number, link_to: :show, sortable: true
2119
+ column :customer_name, sortable: true
2120
+ column :total, renderer: :currency, sortable: true, summary: :sum,
2121
+ options: { currency: "$", precision: 2 }
2122
+ column :status, renderer: :badge,
2123
+ options: { color_map: { pending: "yellow", shipped: "blue", delivered: "green" } }
2124
+ column :created_at, renderer: :relative_date, hidden_on: :mobile
2125
+ end
2126
+
2127
+ form do
2128
+ section "Order Details", columns: 2, responsive: { mobile: 1 } do
2129
+ field :customer_id, input_type: :association_select
2130
+ field :order_date, input_type: :date, default: "current_date"
2131
+ field :status, input_type: :select
2132
+ field :notes, input_type: :textarea, col_span: 2,
2133
+ input_options: { rows: 3 }
2134
+ end
2135
+
2136
+ nested_fields "Line Items", association: :line_items,
2137
+ sortable: true, allow_add: true, allow_remove: true,
2138
+ add_label: "Add Line Item", min: 1, max: 50,
2139
+ empty_message: "Add at least one line item." do
2140
+ field :product_id, input_type: :association_select
2141
+ field :quantity, input_type: :number, input_options: { min: 1 }
2142
+ field :unit_price, input_type: :number, prefix: "$",
2143
+ input_options: { min: 0, step: 0.01 }
2144
+ end
2145
+ end
2146
+
2147
+ search do
2148
+ searchable_fields :order_number, :customer_name
2149
+ placeholder "Search orders..."
2150
+ filter :all, label: "All", default: true
2151
+ filter :pending, label: "Pending", scope: :pending
2152
+ filter :shipped, label: "Shipped", scope: :shipped
2153
+ end
2154
+
2155
+ action :create, type: :built_in, on: :collection, label: "New Order", icon: "plus"
2156
+ action :show, type: :built_in, on: :single, icon: "eye"
2157
+ action :edit, type: :built_in, on: :single, icon: "pencil"
2158
+ action :destroy, type: :built_in, on: :single, icon: "trash", confirm: true, style: :danger
2159
+ end
2160
+ ```
2161
+
2162
+ ---
2163
+
2164
+ ## Grouped Presenters
2165
+
2166
+ A grouped presenter runs a GROUP BY query instead of a flat record scan. Use it for reporting views, summary tables, and drill-through analytics — for example, showing product counts and total revenue broken down by category, or order volume grouped by month.
2167
+
2168
+ Grouped presenters are read-only: they have no show, edit, or form views, and no CRUD actions. They are typically paired with a regular presenter via `row_click` so users can drill into the underlying records.
2169
+
2170
+ ### Basic Grouped Presenter
2171
+
2172
+ **YAML:**
2173
+
2174
+ ```yaml
2175
+ presenter:
2176
+ name: products_by_category
2177
+ model: product
2178
+ label: "Products by Category"
2179
+ slug: products-by-category
2180
+
2181
+ index:
2182
+ query_mode: grouped
2183
+ group_by: [category]
2184
+ default_sort: { field: product_count, direction: desc }
2185
+ row_click:
2186
+ mode: filter
2187
+ target: products
2188
+ table_columns:
2189
+ - { field: category, sortable: true }
2190
+ - { field: product_count, aggregate: count, sortable: true }
2191
+ - { field: total_price, aggregate: { function: sum, field: price }, sortable: true }
2192
+ ```
2193
+
2194
+ **Ruby DSL:**
2195
+
2196
+ ```ruby
2197
+ define_presenter :products_by_category do
2198
+ model :product
2199
+ label "Products by Category"
2200
+ slug "products-by-category"
2201
+
2202
+ index do
2203
+ query_mode :grouped
2204
+ group_by [:category]
2205
+ default_sort :product_count, :desc
2206
+ row_click :filter, target: "products"
2207
+
2208
+ column :category, sortable: true
2209
+ column :product_count, aggregate: :count, sortable: true
2210
+ column :total_price, aggregate: { function: :sum, field: :price }, sortable: true
2211
+ end
2212
+ end
2213
+ ```
2214
+
2215
+ `row_click: { mode: filter, target: "products" }` means clicking a row navigates to the `products` presenter with Ransack filter params pre-filled from the group's field values. The user sees all products in that category without building a filter manually.
2216
+
2217
+ ### HAVING and Limit
2218
+
2219
+ Use `having` to filter groups after aggregation, and `limit` to cap the number of rows (useful for "top N" tables):
2220
+
2221
+ **Ruby DSL:**
2222
+
2223
+ ```ruby
2224
+ index do
2225
+ query_mode :grouped
2226
+ group_by [:category]
2227
+ having { { field: :product_count, operator: :gte, value: 5 } }
2228
+ limit 10
2229
+ default_sort :total_price, :desc
2230
+
2231
+ column :category, sortable: true
2232
+ column :product_count, aggregate: :count, sortable: true
2233
+ column :total_price, aggregate: { function: :sum, field: :price }, sortable: true
2234
+ end
2235
+ ```
2236
+
2237
+ **YAML:**
2238
+
2239
+ ```yaml
2240
+ index:
2241
+ query_mode: grouped
2242
+ group_by: [category]
2243
+ having:
2244
+ - { field: product_count, operator: gte, value: 5 }
2245
+ limit: 10
2246
+ default_sort: { field: total_price, direction: desc }
2247
+ table_columns:
2248
+ - { field: category, sortable: true }
2249
+ - { field: product_count, aggregate: count, sortable: true }
2250
+ - { field: total_price, aggregate: { function: sum, field: price }, sortable: true }
2251
+ ```
2252
+
2253
+ With `limit: 10`, pagination is disabled and only the top 10 groups are returned. This is ideal for embedding in dashboard zones.
2254
+
2255
+ ### Date Period Grouping
2256
+
2257
+ Group by a date field truncated to a period. The column uses `group_field` to reference the source field when the column name differs:
2258
+
2259
+ **Ruby DSL:**
2260
+
2261
+ ```ruby
2262
+ define_presenter :orders_by_month do
2263
+ model :order
2264
+ label "Orders by Month"
2265
+ slug "orders-by-month"
2266
+
2267
+ index do
2268
+ query_mode :grouped
2269
+ group_by [{ field: :created_at, period: :month }]
2270
+ default_sort :month, :desc
2271
+
2272
+ column :month, group_field: :created_at, sortable: true
2273
+ column :order_count, aggregate: :count, sortable: true
2274
+ column :revenue, aggregate: { function: :sum, field: :total }, sortable: true,
2275
+ renderer: :currency, options: { currency: "$", precision: 2 }
2276
+ end
2277
+ end
2278
+ ```
2279
+
2280
+ Available periods: `year`, `quarter`, `month`, `week`, `day`. The database truncates the timestamp to the period boundary using the appropriate SQL function (`DATE_TRUNC` on PostgreSQL, `strftime` on SQLite).
2281
+
2282
+ ### Aggregate Functions
2283
+
2284
+ | Short form | Full form | Description |
2285
+ |------------|-----------|-------------|
2286
+ | `count` | `{ function: count }` | Count of rows in the group |
2287
+ | — | `{ function: sum, field: amount }` | Sum of a numeric field |
2288
+ | — | `{ function: avg, field: price }` | Average of a numeric field |
2289
+ | — | `{ function: min, field: created_at }` | Minimum value |
2290
+ | — | `{ function: max, field: score }` | Maximum value |
2291
+ | — | `{ function: count, distinct: true, field: user_id }` | Count of distinct values |
2292
+
2293
+ All aggregate columns support `sortable: true` and renderers (e.g., `renderer: :currency`, `renderer: :number`).
2294
+
2295
+ ### Enum Sort Order
2296
+
2297
+ Enum group columns default to sorting by **definition order** — the position in the model's enum values list (e.g., pipeline stages appear in logical order, not alphabetical). Override with `sort_strategy: value` for alphabetical sorting:
2298
+
2299
+ ```yaml
2300
+ # Sorts by enum definition order (default for enums)
2301
+ - { field: stage, sortable: true }
2302
+
2303
+ # Sorts alphabetically by raw value
2304
+ - { field: stage, sortable: true, sort_strategy: value }
2305
+ ```
2306
+
2307
+ ```ruby
2308
+ # DSL equivalent
2309
+ column :stage, sortable: true, sort_strategy: :value
2310
+ ```
2311
+
2312
+ This applies to both grouped and non-grouped index views. The implementation uses `CASE WHEN` SQL for definition-order sorting, so NULL values sort after all defined enum values.
2313
+
2314
+ ---
2315
+
2316
+ ## What's Next
2317
+
2318
+ - [Presenters Reference](../reference/presenters.md) -- complete attribute reference for all presenter YAML options
2319
+ - [Presenter DSL Reference](../reference/presenter-dsl.md) -- complete reference for the Ruby DSL with inheritance
2320
+ - [Renderers Guide](display-types.md) -- visual guide to all built-in renderers
2321
+ - [Conditional Rendering Guide](conditional-rendering.md) -- deep dive into `visible_when` and `disable_when`
2322
+ - [Custom Actions Guide](custom-actions.md) -- writing domain-specific action classes
2323
+ - [View Groups Guide](view-groups.md) -- navigation menu and view switching between presenters
2324
+ - [Condition Operators Reference](../reference/condition-operators.md) -- full list of supported operators