@memberjunction/ng-dashboards 5.44.0 → 5.45.1

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 (73) hide show
  1. package/dist/AI/components/autotagging/tabs/sources-tab.component.d.ts +1 -0
  2. package/dist/AI/components/autotagging/tabs/sources-tab.component.d.ts.map +1 -1
  3. package/dist/AI/components/autotagging/tabs/sources-tab.component.js +5 -3
  4. package/dist/AI/components/autotagging/tabs/sources-tab.component.js.map +1 -1
  5. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.d.ts +4 -2
  6. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.d.ts.map +1 -1
  7. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js +13 -11
  8. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js.map +1 -1
  9. package/dist/AI/components/prompts/prompt-version-control.component.d.ts +3 -1
  10. package/dist/AI/components/prompts/prompt-version-control.component.d.ts.map +1 -1
  11. package/dist/AI/components/prompts/prompt-version-control.component.js +12 -10
  12. package/dist/AI/components/prompts/prompt-version-control.component.js.map +1 -1
  13. package/dist/AI/components/tags/tags-resource.component.d.ts +1 -0
  14. package/dist/AI/components/tags/tags-resource.component.d.ts.map +1 -1
  15. package/dist/AI/components/tags/tags-resource.component.js +10 -8
  16. package/dist/AI/components/tags/tags-resource.component.js.map +1 -1
  17. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts +2 -1
  18. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts.map +1 -1
  19. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js +1 -1
  20. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js.map +1 -1
  21. package/dist/ComponentStudio/components/artifact-selection-dialog.component.d.ts +1 -0
  22. package/dist/ComponentStudio/components/artifact-selection-dialog.component.d.ts.map +1 -1
  23. package/dist/ComponentStudio/components/artifact-selection-dialog.component.js +5 -3
  24. package/dist/ComponentStudio/components/artifact-selection-dialog.component.js.map +1 -1
  25. package/dist/Credentials/components/credentials-categories-resource.component.d.ts +3 -1
  26. package/dist/Credentials/components/credentials-categories-resource.component.d.ts.map +1 -1
  27. package/dist/Credentials/components/credentials-categories-resource.component.js +11 -5
  28. package/dist/Credentials/components/credentials-categories-resource.component.js.map +1 -1
  29. package/dist/Credentials/components/credentials-list-resource.component.d.ts +3 -2
  30. package/dist/Credentials/components/credentials-list-resource.component.d.ts.map +1 -1
  31. package/dist/Credentials/components/credentials-list-resource.component.js +23 -9
  32. package/dist/Credentials/components/credentials-list-resource.component.js.map +1 -1
  33. package/dist/Credentials/components/credentials-types-resource.component.d.ts +3 -2
  34. package/dist/Credentials/components/credentials-types-resource.component.d.ts.map +1 -1
  35. package/dist/Credentials/components/credentials-types-resource.component.js +10 -4
  36. package/dist/Credentials/components/credentials-types-resource.component.js.map +1 -1
  37. package/dist/DatabaseDesigner/components/modify/database-modify.component.d.ts +2 -1
  38. package/dist/DatabaseDesigner/components/modify/database-modify.component.d.ts.map +1 -1
  39. package/dist/DatabaseDesigner/components/modify/database-modify.component.js +13 -6
  40. package/dist/DatabaseDesigner/components/modify/database-modify.component.js.map +1 -1
  41. package/dist/FormBuilder/form-builder-resource.component.d.ts +2 -1
  42. package/dist/FormBuilder/form-builder-resource.component.d.ts.map +1 -1
  43. package/dist/FormBuilder/form-builder-resource.component.js +10 -8
  44. package/dist/FormBuilder/form-builder-resource.component.js.map +1 -1
  45. package/dist/MCP/mcp-dashboard.component.d.ts +3 -1
  46. package/dist/MCP/mcp-dashboard.component.d.ts.map +1 -1
  47. package/dist/MCP/mcp-dashboard.component.js +32 -14
  48. package/dist/MCP/mcp-dashboard.component.js.map +1 -1
  49. package/dist/QueryBrowser/query-browser-resource.component.d.ts +4 -2
  50. package/dist/QueryBrowser/query-browser-resource.component.d.ts.map +1 -1
  51. package/dist/QueryBrowser/query-browser-resource.component.js +15 -13
  52. package/dist/QueryBrowser/query-browser-resource.component.js.map +1 -1
  53. package/dist/UserRoutines/user-routines-agent-context.d.ts +44 -0
  54. package/dist/UserRoutines/user-routines-agent-context.d.ts.map +1 -0
  55. package/dist/UserRoutines/user-routines-agent-context.js +81 -0
  56. package/dist/UserRoutines/user-routines-agent-context.js.map +1 -0
  57. package/dist/UserRoutines/user-routines-resource.component.d.ts +60 -0
  58. package/dist/UserRoutines/user-routines-resource.component.d.ts.map +1 -0
  59. package/dist/UserRoutines/user-routines-resource.component.js +328 -0
  60. package/dist/UserRoutines/user-routines-resource.component.js.map +1 -0
  61. package/dist/module.d.ts +16 -15
  62. package/dist/module.d.ts.map +1 -1
  63. package/dist/module.js +7 -0
  64. package/dist/module.js.map +1 -1
  65. package/dist/public-api.d.ts +2 -0
  66. package/dist/public-api.d.ts.map +1 -1
  67. package/dist/public-api.js +3 -0
  68. package/dist/public-api.js.map +1 -1
  69. package/dist/routines-dashboards.module.d.ts +17 -0
  70. package/dist/routines-dashboards.module.d.ts.map +1 -0
  71. package/dist/routines-dashboards.module.js +65 -0
  72. package/dist/routines-dashboards.module.js.map +1 -0
  73. package/package.json +63 -58
@@ -12,6 +12,7 @@ import { MJEnvironmentEntityExtended, UserInfoEngine, ComponentMetadataEngine, I
12
12
  import { NormalizeUUID, RegisterClass, UUIDsEqual } from '@memberjunction/global';
13
13
  import { BaseResourceComponent } from '@memberjunction/ng-shared';
14
14
  import { MJNotificationService } from '@memberjunction/ng-notifications';
15
+ import { MJConfirmService } from '@memberjunction/ng-ui-components';
15
16
  import { AIEngineBase } from '@memberjunction/ai-engine-base';
16
17
  import { combineLatest } from 'rxjs';
17
18
  import { skip } from 'rxjs/operators';
@@ -1640,6 +1641,7 @@ let FormBuilderResourceComponent = class FormBuilderResourceComponent extends Ba
1640
1641
  notifications = inject(MJNotificationService);
1641
1642
  overrideService = inject(EntityFormOverrideService);
1642
1643
  conversationBridge = inject(ConversationBridgeService);
1644
+ confirmService = inject(MJConfirmService);
1643
1645
  /**
1644
1646
  * Name of the Application this cockpit lives in — must match the
1645
1647
  * `Application.Name` column. "Form Studio" is the nav-item LABEL
@@ -3253,7 +3255,7 @@ let FormBuilderResourceComponent = class FormBuilderResourceComponent extends Ba
3253
3255
  return this.Versions.find(v => UUIDsEqual(v.ID, id)) ?? null;
3254
3256
  }
3255
3257
  async OnFormPicked(form) {
3256
- if (this.DirtyFlag && !this.confirmDiscard())
3258
+ if (this.DirtyFlag && !(await this.confirmDiscard()))
3257
3259
  return;
3258
3260
  try {
3259
3261
  const provider = this.provider;
@@ -3432,8 +3434,8 @@ let FormBuilderResourceComponent = class FormBuilderResourceComponent extends Ba
3432
3434
  const entityInfo = provider.Entities?.find(e => UUIDsEqual(e.ID, row.EntityID));
3433
3435
  return entityInfo?.Name ?? null;
3434
3436
  }
3435
- OnNewForm() {
3436
- if (this.DirtyFlag && !this.confirmDiscard())
3437
+ async OnNewForm() {
3438
+ if (this.DirtyFlag && !(await this.confirmDiscard()))
3437
3439
  return;
3438
3440
  this.SelectedFormID = null;
3439
3441
  // Empty default — the user fills the toolbar name input, OR picking
@@ -3467,8 +3469,8 @@ let FormBuilderResourceComponent = class FormBuilderResourceComponent extends Ba
3467
3469
  this.cdr.markForCheck();
3468
3470
  this.registerAgentContext();
3469
3471
  }
3470
- confirmDiscard() {
3471
- return window.confirm('You have unsaved changes. Discard them?');
3472
+ async confirmDiscard() {
3473
+ return this.confirmService.Confirm('You have unsaved changes. Discard them?');
3472
3474
  }
3473
3475
  /**
3474
3476
  * Best-effort parse of the `Specification` JSON column on MJ: Components.
@@ -3854,7 +3856,7 @@ let FormBuilderResourceComponent = class FormBuilderResourceComponent extends Ba
3854
3856
  return;
3855
3857
  const formID = this.SelectedFormID;
3856
3858
  const formName = this.SelectedFormName || 'this form';
3857
- if (!window.confirm(`Delete "${formName}" and any overrides pointing at it? This cannot be undone.`)) {
3859
+ if (!(await this.confirmService.ConfirmDelete({ title: 'Delete Form', message: `Delete "${formName}" and any overrides pointing at it?`, detail: 'This action cannot be undone.' }))) {
3858
3860
  return;
3859
3861
  }
3860
3862
  const provider = this.provider;
@@ -3891,7 +3893,7 @@ let FormBuilderResourceComponent = class FormBuilderResourceComponent extends Ba
3891
3893
  if (!loaded) {
3892
3894
  this.notifications.CreateSimpleNotification(`Form ${formName} not found — may have already been deleted.`, 'warning', 4000);
3893
3895
  await this.loadExistingForms();
3894
- this.OnNewForm();
3896
+ await this.OnNewForm();
3895
3897
  return;
3896
3898
  }
3897
3899
  const deleted = await componentEntity.Delete();
@@ -4565,7 +4567,7 @@ export { FormBuilderResourceComponent };
4565
4567
  type: Component,
4566
4568
  args: [{ standalone: false, selector: 'mj-form-builder-resource', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 FORM BUILDER COCKPIT \u2502\n \u2502 4 panes: Forms list \u2502 Versions \u2502 Code / Preview / Layout \u2502 Chat AI \u2502\n \u2502 Forms-list rail and Chat rail both collapse to a strip. \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n-->\n<div class=\"fb-cockpit\"\n [class.left-collapsed]=\"LeftRailCollapsed\"\n [class.chat-collapsed]=\"ChatPaneCollapsed\">\n\n <!-- Collapsed strip (left). Rendered OUTSIDE the splitter so the\n splitter only ever contains expanded panes. Click the chevron to\n re-expand; persisted via UserInfoEngine. -->\n @if (LeftRailCollapsed) {\n <div class=\"fb-collapsed-strip\" role=\"region\" aria-label=\"Forms list (collapsed)\">\n <button class=\"fb-rail-collapse\" type=\"button\" (click)=\"ToggleLeftRail()\"\n aria-label=\"Expand forms list\">\n <i class=\"fa-solid fa-chevron-right\"></i>\n </button>\n <div class=\"fb-collapsed-strip-label\"><i class=\"fa-solid fa-table-list\"></i></div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n SPLITTER \u2014 wraps the visible (expanded) panes. Percentage-based;\n drag-end persists sizes via OnSplitterDragEnd \u2192 savePrefs.\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <as-split direction=\"horizontal\"\n class=\"fb-splitter\"\n unit=\"percent\"\n gutterSize=\"6\"\n (dragEnd)=\"OnSplitterDragEnd($event)\">\n\n @if (!LeftRailCollapsed) {\n <as-split-area [size]=\"LeftPanePct\" [minSize]=\"12\">\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n LEFT \u2014 Forms list (existing form-role Components, with version rail\n below). Collapses to a strip.\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <aside class=\"fb-forms-rail\">\n <div class=\"fb-rail-header\">\n <button class=\"fb-rail-collapse\" type=\"button\" (click)=\"ToggleLeftRail()\"\n [attr.aria-label]=\"LeftRailCollapsed ? 'Expand forms list' : 'Collapse forms list'\">\n <i class=\"fa-solid\" [class.fa-chevron-left]=\"!LeftRailCollapsed\" [class.fa-chevron-right]=\"LeftRailCollapsed\"></i>\n </button>\n @if (!LeftRailCollapsed) {\n <h3 class=\"fb-rail-title\">Forms</h3>\n <button class=\"fb-rail-icon-btn\" type=\"button\" (click)=\"ToggleFormsViewMode()\"\n [title]=\"FormsViewMode === 'list' ? 'Switch to tree view (Schema \u2192 Entity \u2192 Form)' : 'Switch to flat list'\">\n <i class=\"fa-solid\" [class.fa-list]=\"FormsViewMode === 'list'\" [class.fa-folder-tree]=\"FormsViewMode === 'tree'\"></i>\n </button>\n <button class=\"fb-rail-new\" type=\"button\" (click)=\"OnNewForm()\" title=\"New form\">\n <i class=\"fa-solid fa-plus\"></i>\n <span>New</span>\n </button>\n }\n </div>\n\n @if (!LeftRailCollapsed) {\n\n <!-- Inner vertical splitter: forms list \u2195 versions panel.\n Persisted via `formsListHeightPct` in cockpit prefs. When either\n inner panel is collapsed, only the other one renders (no\n splitter), matching VS-Code's panel collapse behavior. -->\n @if (FormsListCollapsed) {\n <!-- Forms list collapsed \u2192 just show the header strip + full versions -->\n <div class=\"fb-inner-panel-header\" (click)=\"ToggleFormsListCollapsed()\">\n <i class=\"fa-solid fa-chevron-right\"></i>\n <span>Forms</span>\n <span class=\"fb-inner-panel-count\">{{ ExistingForms.length }}</span>\n </div>\n <ng-container *ngTemplateOutlet=\"versionsPanel\"></ng-container>\n } @else if (VersionsCollapsed) {\n <!-- Versions collapsed \u2192 full forms list + header strip for versions -->\n <ng-container *ngTemplateOutlet=\"formsPanel\"></ng-container>\n @if (SelectedFormID) {\n <div class=\"fb-inner-panel-header\" (click)=\"ToggleVersionsCollapsed()\">\n <i class=\"fa-solid fa-chevron-right\"></i>\n <span>Versions</span>\n @if (Versions.length > 0) {\n <span class=\"fb-inner-panel-count\">{{ Versions.length }}</span>\n }\n </div>\n }\n } @else {\n <!-- Both panels open \u2014 use a vertical splitter between them. -->\n <as-split direction=\"vertical\"\n class=\"fb-inner-splitter\"\n unit=\"percent\"\n gutterSize=\"6\"\n (dragEnd)=\"OnInnerSplitterDragEnd($event)\">\n <as-split-area [size]=\"SelectedFormID ? FormsListHeightPct : 100\" [minSize]=\"20\">\n <ng-container *ngTemplateOutlet=\"formsPanel\"></ng-container>\n </as-split-area>\n @if (SelectedFormID) {\n <as-split-area [size]=\"100 - FormsListHeightPct\" [minSize]=\"15\">\n <ng-container *ngTemplateOutlet=\"versionsPanel\"></ng-container>\n </as-split-area>\n }\n </as-split>\n }\n\n <!-- \u2500\u2500\u2500 Forms-list panel template \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <ng-template #formsPanel>\n <div class=\"fb-inner-panel\">\n <div class=\"fb-inner-panel-header fb-inner-panel-header--open\" (click)=\"ToggleFormsListCollapsed()\">\n <i class=\"fa-solid fa-chevron-down\"></i>\n <span>Forms</span>\n <span class=\"fb-inner-panel-count\">{{ filteredForms.length }} / {{ ExistingForms.length }}</span>\n </div>\n <div class=\"fb-rail-search-row\">\n <div class=\"fb-rail-search\">\n <i class=\"fa-solid fa-search\"></i>\n <input type=\"text\"\n placeholder=\"Search forms\u2026\"\n [value]=\"LeftRailFilter\"\n (input)=\"OnLeftRailFilterChange($event)\" />\n </div>\n <div class=\"fb-entity-filter\" [class.open]=\"EntityFilterDropdownOpen\">\n <button type=\"button\" class=\"fb-entity-filter-btn\" (click)=\"ToggleEntityFilterDropdown()\"\n [title]=\"FormsEntityFilter ? 'Entity: ' + FormsEntityFilter : 'All entities'\">\n <i class=\"fa-solid fa-table\"></i>\n <span>{{ FormsEntityFilter || 'All entities' }}</span>\n <i class=\"fa-solid fa-chevron-down chev\"></i>\n </button>\n @if (EntityFilterDropdownOpen) {\n <div class=\"fb-entity-filter-popover\" (click)=\"$event.stopPropagation()\">\n <input type=\"text\" class=\"fb-entity-filter-search\"\n placeholder=\"Search entities\u2026\"\n [value]=\"EntityFilterSearch\"\n (input)=\"OnEntityFilterSearch($event)\" />\n <ul class=\"fb-entity-filter-list\">\n <li class=\"fb-entity-filter-item\"\n [class.fb-entity-filter-item--selected]=\"!FormsEntityFilter\"\n (click)=\"PickEntityFilter('')\">\n <i class=\"fa-solid fa-asterisk\"></i>\n <span>All entities</span>\n <span class=\"fb-entity-filter-count\">{{ ExistingForms.length }}</span>\n </li>\n @for (opt of entityFilterOptions; track opt.name) {\n <li class=\"fb-entity-filter-item\"\n [class.fb-entity-filter-item--selected]=\"FormsEntityFilter === opt.name\"\n (click)=\"PickEntityFilter(opt.name)\">\n @if (opt.icon) { <i [class]=\"opt.icon\"></i> } @else { <i class=\"fa-solid fa-table\"></i> }\n <span>{{ opt.display }}</span>\n <span class=\"fb-entity-filter-count\">{{ opt.count }}</span>\n </li>\n }\n </ul>\n </div>\n }\n </div>\n </div>\n <!-- Status filter chips + sort -->\n <div class=\"fb-rail-chips\">\n <button type=\"button\"\n class=\"fb-chip\"\n [class.fb-chip--on]=\"FormsStatusFilter.has('Active')\"\n (click)=\"ToggleStatusFilter('Active')\"\n title=\"Show forms with Active overrides\">\n <i class=\"fa-solid fa-circle-dot\"></i>Active\n </button>\n <button type=\"button\"\n class=\"fb-chip\"\n [class.fb-chip--on]=\"FormsStatusFilter.has('Pending')\"\n (click)=\"ToggleStatusFilter('Pending')\"\n title=\"Show forms with Pending (draft) overrides\">\n <i class=\"fa-solid fa-circle-dot\"></i>Pending\n </button>\n <button type=\"button\"\n class=\"fb-chip\"\n [class.fb-chip--on]=\"FormsStatusFilter.has('Inactive')\"\n (click)=\"ToggleStatusFilter('Inactive')\"\n title=\"Show forms with Inactive overrides\">\n <i class=\"fa-solid fa-circle-dot\"></i>Inactive\n </button>\n <span class=\"fb-spacer\"></span>\n <select class=\"fb-rail-sort\" [value]=\"FormsSortMode\"\n (change)=\"SetFormsSortMode($any($event.target).value)\" title=\"Sort\">\n <option value=\"updated-desc\">Recently edited</option>\n <option value=\"updated-asc\">Oldest edits</option>\n <option value=\"name-asc\">Name A\u2192Z</option>\n <option value=\"name-desc\">Name Z\u2192A</option>\n </select>\n </div>\n <div class=\"fb-rail-list\">\n @if (IsLoading) {\n <div class=\"fb-rail-empty\">Loading\u2026</div>\n } @else if (filteredForms.length === 0) {\n @if (LeftRailFilter || FormsEntityFilter || FormsStatusFilter.size < 3) {\n <mj-empty-state class=\"fb-rail-empty\" Size=\"compact\" Variant=\"no-results\"\n Icon=\"fa-solid fa-table-list\" Title=\"No forms match the current filters\" />\n } @else {\n <mj-empty-state class=\"fb-rail-empty\" Size=\"compact\" Icon=\"fa-solid fa-table-list\"\n Title=\"No forms yet\" Message=\"Click New to start.\" />\n }\n } @else if (FormsViewMode === 'tree') {\n <!-- Tree view: Schema \u2192 Entity \u2192 Forms -->\n @for (g of treeGroups; track g.schema) {\n <div class=\"fb-tree-schema\">\n <div class=\"fb-tree-schema-header\">\n <i class=\"fa-solid fa-database\"></i>\n <span>{{ g.schema }}</span>\n </div>\n @for (e of g.entities; track e.entity) {\n <div class=\"fb-tree-entity\">\n <div class=\"fb-tree-entity-header\" [title]=\"e.entity\">\n @if (e.icon) { <i [class]=\"e.icon\"></i> } @else { <i class=\"fa-solid fa-table\"></i> }\n <span>{{ e.entityDisplay }}</span>\n <span class=\"fb-tree-entity-count\">{{ e.forms.length }}</span>\n </div>\n @for (form of e.forms; track form.ID) {\n <ng-container *ngTemplateOutlet=\"formRow; context: { $implicit: form, treeIndent: true }\"></ng-container>\n }\n </div>\n }\n </div>\n }\n } @else {\n <!-- Flat list view -->\n @for (form of filteredForms; track form.ID) {\n <ng-container *ngTemplateOutlet=\"formRow; context: { $implicit: form }\"></ng-container>\n }\n }\n </div>\n </div>\n </ng-template>\n\n <!-- \u2500\u2500\u2500 Form row template (used by both list + tree) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <ng-template #formRow let-form let-treeIndent=\"treeIndent\">\n <button class=\"fb-rail-item\"\n type=\"button\"\n [class.active]=\"form.ID === SelectedFormID\"\n [class.fb-rail-item--tree]=\"treeIndent\"\n [class.fb-rail-item--dirty]=\"form.ID === SelectedFormID && DirtyFlag\"\n (click)=\"OnFormPicked(form)\"\n (contextmenu)=\"OnFormRowContextMenu(form, $event)\">\n <button class=\"fb-rail-pin\"\n type=\"button\"\n [class.fb-rail-pin--on]=\"IsFormPinned(form)\"\n (click)=\"TogglePinForm(form, $event)\"\n [attr.aria-label]=\"IsFormPinned(form) ? 'Unpin' : 'Pin'\"\n [title]=\"IsFormPinned(form) ? 'Unpin from top' : 'Pin to top'\">\n <i class=\"fa-solid fa-thumbtack\"></i>\n </button>\n <div class=\"fb-rail-item-icon\">\n @if (form.TargetEntityIcon) { <i [class]=\"form.TargetEntityIcon\"></i> } @else { <i class=\"fa-solid fa-table-list\"></i> }\n </div>\n <div class=\"fb-rail-item-body\">\n <div class=\"fb-rail-item-name\">\n {{ form.Name }}\n @if (form.ID === SelectedFormID && DirtyFlag) { <span class=\"fb-rail-item-dirty-dot\" title=\"Unsaved changes\">\u25CF</span> }\n </div>\n <div class=\"fb-rail-item-sub\">\n @if (form.TargetEntityName) {\n <span class=\"fb-rail-item-entity\">{{ form.TargetEntityName }}</span>\n }\n @if (form.UpdatedAt) {\n <span class=\"fb-rail-item-time\">\u00B7 {{ form.UpdatedAt | date:'short' }}</span>\n }\n </div>\n </div>\n @if (form.OverrideStatus) {\n <span class=\"fb-rail-badge\"\n [class.fb-rail-badge--active]=\"form.OverrideStatus === 'Active'\"\n [class.fb-rail-badge--inactive]=\"form.OverrideStatus === 'Inactive'\"\n [class.fb-rail-badge--pending]=\"form.OverrideStatus === 'Pending'\"\n [title]=\"form.OverrideStatus + ' override'\">\n <i class=\"fa-solid fa-circle-dot\"></i>\n </span>\n }\n </button>\n </ng-template>\n\n <!-- \u2500\u2500\u2500 Versions-panel template \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <ng-template #versionsPanel>\n @if (SelectedFormID) {\n <div class=\"fb-inner-panel\">\n <div class=\"fb-inner-panel-header fb-inner-panel-header--open\" (click)=\"ToggleVersionsCollapsed()\">\n <i class=\"fa-solid fa-chevron-down\"></i>\n <span>Versions</span>\n @if (Versions.length > 0) {\n <span class=\"fb-inner-panel-count\">{{ Versions.length }}</span>\n }\n </div>\n <div class=\"fb-versions-list\">\n @if (VersionsLoading) {\n <div class=\"fb-rail-empty\">Loading\u2026</div>\n } @else if (Versions.length === 0) {\n <mj-empty-state class=\"fb-rail-empty\" Size=\"compact\" Icon=\"fa-solid fa-clock-rotate-left\" Title=\"No version history yet\" />\n } @else {\n @for (v of Versions; track v.ID) {\n <div class=\"fb-version-row\"\n [class.is-active]=\"v.IsActive\"\n [class.is-pending]=\"v.IsPending\"\n [class.is-selected]=\"v.ID === SelectedFormID\"\n [class.is-diff-source]=\"v.ID === DiffSourceVersionID\"\n role=\"button\"\n tabindex=\"0\"\n [title]=\"'Load v' + v.Version + ' into the editor. Right-click for more.'\"\n (click)=\"OnVersionRowClick(v)\"\n (keydown.enter)=\"OnVersionRowClick(v)\"\n (keydown.space)=\"OnVersionRowClick(v); $event.preventDefault()\">\n <span class=\"fb-version-marker\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-circle\"></i>\n </span>\n <div class=\"fb-version-row-main\">\n <div class=\"fb-version-label\">\n v{{ v.Version }}\n @if (v.IsActive) {\n <span class=\"fb-version-tag is-active\">Active</span>\n } @else if (v.IsPending) {\n <span class=\"fb-version-tag is-pending\">Pending</span>\n } @else {\n <span class=\"fb-version-tag\">Inactive</span>\n }\n @if (v.ID === DiffSourceVersionID) {\n <span class=\"fb-version-tag fb-version-tag--diff\">Diff: A</span>\n }\n </div>\n <div class=\"fb-version-meta\">\n @if (v.UpdatedAt) { {{ v.UpdatedAt | date:'short' }} } @else { (no timestamp) }\n </div>\n </div>\n <button class=\"fb-version-action fb-version-action--diff\" type=\"button\"\n (click)=\"OnVersionDiffClick(v, $event)\"\n [title]=\"DiffSourceVersionID ? (DiffSourceVersionID === v.ID ? 'Click another version to compare. Click again to cancel.' : 'Compare with selected version') : 'Start diff: pick this as source, then click another version'\">\n <i class=\"fa-solid fa-code-compare\"></i>\n </button>\n @if (!v.IsActive) {\n <button class=\"fb-version-activate\" type=\"button\"\n (click)=\"$event.stopPropagation(); OnActivateVersion(v)\"\n [title]=\"v.IsPending ? 'Activate this Pending version' : 'Restore: roll back to this version'\">\n <i class=\"fa-solid\" [class.fa-circle-check]=\"v.IsPending\" [class.fa-rotate-left]=\"!v.IsPending\"></i>\n <span class=\"fb-version-activate-label\">{{ v.IsPending ? 'Activate' : 'Restore' }}</span>\n </button>\n }\n </div>\n }\n }\n </div>\n </div>\n }\n </ng-template>\n\n } <!-- end if !LeftRailCollapsed -->\n </aside>\n </as-split-area>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n CENTER \u2014 Tabbed (Preview / Code / Layout) + toolbar\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <as-split-area [size]=\"CenterPanePct\" [minSize]=\"30\">\n <section class=\"fb-center\">\n\n <div class=\"fb-toolbar\">\n <input type=\"text\"\n class=\"fb-name-input\"\n [value]=\"SelectedFormName\"\n placeholder=\"Form name\u2026\"\n (input)=\"OnFormNameInput($event)\"\n title=\"Form name \u2014 used as the JS function identifier in the generated code\" />\n\n <div class=\"fb-entity-picker\" [class.open]=\"IsEntityPickerOpen\">\n <button type=\"button\" class=\"fb-entity-btn\" (click)=\"ToggleEntityPicker()\">\n <i class=\"fa-solid fa-table\"></i>\n <span>\n @if (TargetEntityName) {\n {{ TargetEntityName }}\n } @else {\n <em>Pick entity\u2026</em>\n }\n </span>\n <i class=\"fa-solid fa-chevron-down chev\"></i>\n </button>\n @if (IsEntityPickerOpen) {\n <div class=\"fb-entity-popover\" (click)=\"$event.stopPropagation()\">\n <input type=\"text\"\n class=\"fb-entity-search\"\n placeholder=\"Search entities\u2026\"\n [value]=\"EntityPickerSearch\"\n (input)=\"OnEntityPickerSearch($event)\" />\n <ul class=\"fb-entity-list\">\n @for (e of filteredEntityChoices; track e.Name; let idx = $index) {\n @if (idx < 50) {\n <li class=\"fb-entity-item\" (click)=\"OnEntityPicked(e.Name)\">\n <span class=\"fb-entity-display\">{{ e.DisplayName }}</span>\n <span class=\"fb-entity-name\">{{ e.Name }}</span>\n </li>\n }\n }\n </ul>\n </div>\n }\n </div>\n\n <!-- Override status pill \u2014 only meaningful when an existing\n override is loaded. Controls EntityFormOverride.Status\n (Active / Inactive / Pending). The forms-runtime resolver\n reads this to decide whether to use this form for the user. -->\n @if (ActiveOverrideID && ActiveOverrideStatus) {\n <div class=\"fb-status-picker\" [class.open]=\"OverrideStatusPickerOpen\">\n <button type=\"button\"\n class=\"fb-status-pill\"\n [class.fb-status-pill--active]=\"ActiveOverrideStatus === 'Active'\"\n [class.fb-status-pill--inactive]=\"ActiveOverrideStatus === 'Inactive'\"\n [class.fb-status-pill--pending]=\"ActiveOverrideStatus === 'Pending'\"\n [disabled]=\"OverrideStatusSaving\"\n (click)=\"ToggleOverrideStatusPicker()\"\n [title]=\"'Override status: ' + ActiveOverrideStatus + ' \u2014 click to change'\">\n <i class=\"fa-solid fa-circle-dot\"></i>\n <span>{{ ActiveOverrideStatus }}</span>\n <i class=\"fa-solid fa-chevron-down chev\"></i>\n </button>\n @if (OverrideStatusPickerOpen) {\n <div class=\"fb-status-popover\" (click)=\"$event.stopPropagation()\">\n @for (opt of OverrideStatusOptions; track opt.value) {\n <button type=\"button\"\n class=\"fb-status-option\"\n [class.fb-status-option--selected]=\"ActiveOverrideStatus === opt.value\"\n [disabled]=\"OverrideStatusSaving\"\n (click)=\"OnOverrideStatusPicked(opt.value)\">\n <i [class]=\"opt.icon\"></i>\n <span class=\"fb-status-option-label\">{{ opt.label }}</span>\n <span class=\"fb-status-option-desc\">{{ opt.description }}</span>\n </button>\n }\n </div>\n }\n </div>\n }\n\n <!-- Custom-code chip \u2014 fires when the canvas parser flagged the\n loaded code as containing constructs it can't round-trip (hand-\n authored JSX, computed children, AI-generated patterns, etc.).\n Signals to the user that this form is in \"code-only\" authoring\n mode and is the on-screen explanation for why the Layout tab\n is disabled. Title spells out the contract on hover. -->\n @if (CanvasDiverged && HasActiveForm) {\n <span class=\"fb-custom-code-chip\"\n title=\"This form contains custom code the drag-and-drop canvas cannot round-trip. The Layout tab is disabled to prevent overwriting your code; edit in Code instead.\">\n <i class=\"fa-solid fa-code\"></i>\n Custom code\n </span>\n }\n\n <span class=\"fb-spacer\"></span>\n\n <!-- Center-pane view-mode segmented control -->\n <div class=\"fb-modeseg\" role=\"tablist\" aria-label=\"Center pane view\">\n <button type=\"button\"\n role=\"tab\"\n class=\"fb-modeseg-btn\"\n [class.active]=\"CenterPaneMode === 'preview'\"\n [attr.aria-selected]=\"CenterPaneMode === 'preview'\"\n (click)=\"SetCenterPaneMode('preview')\">\n <i class=\"fa-solid fa-eye\"></i> Preview\n </button>\n <button type=\"button\"\n role=\"tab\"\n class=\"fb-modeseg-btn\"\n [class.active]=\"CenterPaneMode === 'code'\"\n [attr.aria-selected]=\"CenterPaneMode === 'code'\"\n (click)=\"SetCenterPaneMode('code')\">\n <i class=\"fa-solid fa-code\"></i> Code\n </button>\n <!-- Layout tab is disabled when the code has diverged from what\n the canvas can represent (hand-authored JSX, computed\n constructs, etc.). The canvas can't safely round-trip such\n code, so editing on Layout and re-emitting would overwrite\n the user's hand-written bits. CanvasDiverged is set by\n `parseCanvasFromCode` when it sees `hasUnknownConstructs`\n or a parse failure. -->\n <button type=\"button\"\n role=\"tab\"\n class=\"fb-modeseg-btn\"\n [class.active]=\"CenterPaneMode === 'layout'\"\n [attr.aria-selected]=\"CenterPaneMode === 'layout'\"\n [disabled]=\"CanvasDiverged\"\n [title]=\"CanvasDiverged\n ? 'Layout is disabled \u2014 the form has hand-authored code the drag-drop canvas cannot round-trip. Edit in Code instead.'\n : 'Drag-and-drop canvas editor'\"\n (click)=\"SetCenterPaneMode('layout')\">\n <i class=\"fa-solid fa-table-cells\"></i> Layout\n </button>\n </div>\n\n @if (SelectedFormID && ActiveOverrideID) {\n <button type=\"button\" class=\"fb-action\" (click)=\"OpenEditFormDetailsDialog()\"\n title=\"Edit form details: name, description, scope, priority, status\">\n <i class=\"fa-solid fa-pen\"></i>\n <span>Details</span>\n </button>\n }\n @if (SelectedFormID) {\n <button type=\"button\" class=\"fb-action fb-action--danger\" (click)=\"OnDelete()\"\n title=\"Delete this form and its overrides\">\n <i class=\"fa-solid fa-trash\"></i>\n <span>Delete</span>\n </button>\n }\n <button type=\"button\" class=\"fb-action fb-action--primary\" (click)=\"OnSave()\" [disabled]=\"!DirtyFlag\">\n <i class=\"fa-solid fa-floppy-disk\"></i>\n <span>Save</span>\n </button>\n </div>\n\n <div class=\"fb-center-body\">\n <!-- Version overlay \u2014 floating, top-left of the center pane.\n Shows which Component version the editor is currently bound\n to, decoupled from the version-rail's selected highlight.\n When the user clicks an older row to inspect history, this\n chip stays in sync and visually flags \"you are NOT looking\n at what the runtime serves\" by going amber for non-Active\n historical versions. Clicking the chip dismisses focus\n but performs no navigation \u2014 it's a status indicator. -->\n @if (CurrentSelectedVersion && HasActiveForm) {\n <div class=\"fb-version-overlay\"\n [class.fb-version-overlay--active]=\"CurrentSelectedVersion.IsActive\"\n [class.fb-version-overlay--pending]=\"CurrentSelectedVersion.IsPending\"\n [class.fb-version-overlay--historical]=\"!CurrentSelectedVersion.IsActive && !CurrentSelectedVersion.IsPending\"\n [title]=\"CurrentSelectedVersion.IsActive\n ? 'Viewing the version the runtime is currently serving.'\n : CurrentSelectedVersion.IsPending\n ? 'Viewing a Pending version \u2014 AI-authored, not yet Activated.'\n : 'Viewing an Inactive historical version \u2014 the runtime is serving a different version.'\">\n <i class=\"fa-solid fa-clock-rotate-left\"></i>\n <span class=\"fb-version-overlay-label\">Viewing v{{ CurrentSelectedVersion.Version }}</span>\n @if (CurrentSelectedVersion.IsActive) {\n <span class=\"fb-version-overlay-tag fb-version-overlay-tag--active\">Active</span>\n } @else if (CurrentSelectedVersion.IsPending) {\n <span class=\"fb-version-overlay-tag fb-version-overlay-tag--pending\">Pending</span>\n } @else {\n <span class=\"fb-version-overlay-tag fb-version-overlay-tag--historical\">Historical</span>\n }\n </div>\n }\n\n @if (!SelectedFormID && !IsNewForm) {\n <!-- No form picked or being created \u2014 invite the user to pick or\n create one. Lists recently-edited forms for quick access. -->\n <mj-empty-state class=\"fb-empty fb-empty--landing\" Size=\"large\" Icon=\"fa-solid fa-pen-ruler\"\n Title=\"Form Builder\"\n Message=\"Pick a form on the left or click + New above the list to start a new one. The Form Builder agent on the right will help you author and refine via natural language.\">\n @if (ExistingForms.length > 0) {\n <div class=\"fb-empty-recent\">\n <div class=\"fb-empty-recent-label\">Recently edited</div>\n <div class=\"fb-empty-recent-list\">\n @for (form of filteredForms.slice(0, 5); track form.ID) {\n <button class=\"fb-empty-recent-item\" type=\"button\" (click)=\"OnFormPicked(form)\">\n @if (form.TargetEntityIcon) { <i [class]=\"form.TargetEntityIcon\"></i> } @else { <i class=\"fa-solid fa-table-list\"></i> }\n <div class=\"fb-empty-recent-meta\">\n <div class=\"fb-empty-recent-name\">{{ form.Name }}</div>\n <div class=\"fb-empty-recent-entity\">{{ form.TargetEntityName }}</div>\n </div>\n </button>\n }\n </div>\n </div>\n }\n </mj-empty-state>\n } @else if (!TargetEntityName) {\n <mj-empty-state class=\"fb-empty\" Icon=\"fa-solid fa-table-list\"\n Title=\"Pick an entity to start\"\n Message=\"Choose the target entity above, then refine the form via code, preview, or drag-drop layout.\" />\n } @else {\n <!-- Preview pane: live render via mj-interactive-form when we have\n a usable spec + can resolve the entity. For an unsaved canvas,\n the code is what's \"live\" via canvas \u2192 code regen; preview\n will render off that. -->\n @if (CenterPaneMode === 'preview') {\n <div class=\"fb-preview-host\">\n <!-- Preview record picker strip \u2014 same affordance shape as the\n artifact-viewer form-aware branch, so users see one consistent\n pattern across the chat artifact path and the cockpit path. -->\n <div class=\"fb-preview-toolstrip\">\n <i class=\"fa-solid fa-circle-info\" aria-hidden=\"true\"></i>\n <span>Previewing record:</span>\n <button type=\"button\"\n class=\"fb-preview-chip\"\n (click)=\"TogglePreviewPicker()\">\n <i class=\"fa-solid fa-magnifying-glass\"></i>\n <strong>{{ PreviewRecordLabel || 'No record' }}</strong>\n @if (!PreviewRecordIsReal && PreviewRecord) {\n <span class=\"fb-preview-chip-badge\">Mock</span>\n }\n <i class=\"fa-solid fa-chevron-down\"></i>\n </button>\n <span class=\"fb-preview-chip-entity\">{{ TargetEntityName }}</span>\n @if (PreviewRecord && PreviewRecordIsReal) {\n <button type=\"button\" class=\"fb-preview-open-record\"\n (click)=\"OpenPreviewRecord()\"\n title=\"Open this record in a new tab\">\n <i class=\"fa-solid fa-arrow-up-right-from-square\"></i>\n <span>Open record</span>\n </button>\n }\n @if (PreviewError) {\n <span class=\"fb-preview-warning\" [title]=\"PreviewError\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n </span>\n }\n </div>\n\n @if (PreviewPickerOpen) {\n <div class=\"fb-preview-picker-panel\" (click)=\"$event.stopPropagation()\">\n <input type=\"text\"\n class=\"fb-preview-picker-search\"\n placeholder=\"Search records by name\u2026\"\n [value]=\"PreviewSearchTerm\"\n (input)=\"OnPreviewSearchInput($event)\" />\n @if (PreviewSearchResults.length > 0) {\n <ul class=\"fb-preview-picker-results\">\n @for (r of PreviewSearchResults; track r.ID) {\n <li class=\"fb-preview-picker-row\" (click)=\"OnPreviewRecordPicked(r)\">\n <i class=\"fa-solid fa-circle-dot\"></i>\n <span>{{ r.Label }}</span>\n </li>\n }\n </ul>\n } @else if (PreviewSearchTerm.length > 0) {\n <div class=\"fb-preview-picker-empty\">No matches.</div>\n } @else {\n <div class=\"fb-preview-picker-empty\">No records in this entity.</div>\n }\n </div>\n }\n\n <div class=\"fb-preview-frame\">\n @if (PreviewLoading) {\n <div class=\"fb-preview-empty\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <p>Loading preview\u2026</p>\n </div>\n } @else if (!PreviewSpec) {\n <mj-empty-state class=\"fb-preview-empty\" Icon=\"fa-solid fa-eye\"\n Title=\"Live preview\"\n Message=\"Code is empty \u2014 switch to Code or Layout to start.\" />\n } @else if (PreviewRecord) {\n <!--\n Retrospective fix #10: subscribe to LoadErrorChanged so\n spec parse / mount errors surface in the cockpit's own\n error pane instead of only appearing inline inside the\n React component.\n -->\n <!-- previewMode=true \u2192 no toolbar wrap; just the form\n body. Cockpit's own toolbar already handles saving\n the spec; the form's own Save/Cancel/Edit/Delete are\n not relevant in preview. -->\n <mj-interactive-form\n [componentSpec]=\"PreviewSpec\"\n [record]=\"PreviewRecord\"\n [previewMode]=\"true\"\n (LoadErrorChanged)=\"OnPreviewLoadError($event)\">\n </mj-interactive-form>\n } @else {\n <mj-empty-state class=\"fb-preview-empty\" Variant=\"error\"\n Icon=\"fa-solid fa-triangle-exclamation\"\n [Title]=\"PreviewError || 'Could not bind a record for preview.'\" />\n }\n </div>\n </div>\n }\n\n @if (CenterPaneMode === 'code') {\n <div class=\"fb-code-host\">\n <!-- mj-code-editor (CodeMirror under the hood). The 'javascript'\n language extension enables full JS + JSX highlighting \u2014\n enough for the form-role component spec the agent emits. -->\n <mj-code-editor\n class=\"fb-code-editor\"\n [ngModel]=\"EditableCode\"\n (ngModelChange)=\"OnEditableCodeChange($event)\"\n [language]=\"'javascript'\"\n [indentWithTab]=\"true\">\n </mj-code-editor>\n </div>\n }\n\n @if (CenterPaneMode === 'layout') {\n <div class=\"fb-canvas-host\">\n <!--\n Retrospective fix #6: warn the user when the loaded JSX has\n hand-authored content the canvas can't represent. Saving from\n the canvas regenerates code, which would overwrite those edits.\n The banner lets users know to keep editing in Code instead.\n -->\n @if (CanvasDiverged) {\n <div class=\"fb-canvas-divergence-banner\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n <span>\n <strong>Code has hand-authored content the canvas can't display.</strong>\n Saving from the Layout tab will regenerate code from the canvas and\n <strong>overwrite those edits</strong>. To preserve them, edit the Code tab directly.\n </span>\n </div>\n }\n <mj-form-builder-canvas\n [Canvas]=\"Canvas\"\n [Schema]=\"Schema\"\n [SelectedElementId]=\"SelectedElementId\"\n [SelectedSectionId]=\"SelectedSectionId\"\n (CanvasChanged)=\"OnCanvasChanged($event)\"\n (ElementSelected)=\"OnElementSelected($event)\"\n (SectionSelected)=\"OnSectionSelected($event)\"\n (Deselected)=\"OnDeselected()\">\n </mj-form-builder-canvas>\n </div>\n }\n }\n </div>\n </section>\n </as-split-area>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n CHAT PANE \u2014 Form Builder AI cockpit-mate\n Embedded right pane that always sits next to the editor. Mounts the\n chat-area component directly (same interior the floating overlay\n wraps). Collapses to a strip rendered OUTSIDE the splitter so the\n drag handle doesn't appear next to a collapsed pane.\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <!-- Chat pane only renders when a form is actively being shown. Empty\n cockpit (no form selected, no \"New Form\" in progress) hides the\n chat surface entirely so we don't show an agent shell against no\n subject. The collapsed strip below is gated on the same flag. -->\n @if (!ChatPaneCollapsed && HasActiveForm) {\n <as-split-area [size]=\"ChatPanePct\" [minSize]=\"15\">\n <aside class=\"fb-chat-pane\">\n <div class=\"fb-rail-header\">\n @if (!ChatPaneCollapsed) {\n <h3 class=\"fb-rail-title\">\n <i class=\"fa-solid fa-wand-magic-sparkles\"></i> Form Builder AI\n </h3>\n }\n <button class=\"fb-rail-collapse\" type=\"button\" (click)=\"ToggleChatPane()\"\n [attr.aria-label]=\"ChatPaneCollapsed ? 'Expand chat pane' : 'Collapse chat pane'\">\n <i class=\"fa-solid\" [class.fa-chevron-right]=\"!ChatPaneCollapsed\" [class.fa-chevron-left]=\"ChatPaneCollapsed\"></i>\n </button>\n </div>\n\n @if (!ChatPaneCollapsed) {\n <!-- Context strip \u2014 small label above the chat so the user always\n sees which form the agent has context on. Shows the override\n status + version too so the user knows what state they're\n editing (Active live vs Pending draft vs Inactive historical). -->\n @if (SelectedFormID && TargetEntityName) {\n <div class=\"fb-chat-context\">\n <div class=\"fb-chat-context-row\">\n <div class=\"fb-chat-context-label\">Editing</div>\n <div class=\"fb-chat-context-value\">{{ SelectedFormName }}</div>\n @if (CurrentSelectedVersion) {\n <span class=\"fb-chat-context-version\">v{{ CurrentSelectedVersion.Version }}</span>\n }\n @if (ActiveOverrideStatus) {\n <span class=\"fb-rail-badge\"\n [class.fb-rail-badge--active]=\"ActiveOverrideStatus === 'Active'\"\n [class.fb-rail-badge--inactive]=\"ActiveOverrideStatus === 'Inactive'\"\n [class.fb-rail-badge--pending]=\"ActiveOverrideStatus === 'Pending'\">\n <i class=\"fa-solid fa-circle-dot\"></i>\n {{ ActiveOverrideStatus }}\n </span>\n }\n </div>\n <div class=\"fb-chat-context-sub\">{{ TargetEntityName }}</div>\n @if (LineageConversations.length > 1) {\n <div class=\"fb-chat-context-row\">\n <button class=\"fb-chat-context-history\"\n type=\"button\"\n (click)=\"ToggleConversationHistoryDropdown()\"\n title=\"Switch to another conversation about this form\">\n <i class=\"fa-solid fa-clock-rotate-left\"></i>\n <span>{{ LineageConversations.length }} conversations</span>\n <i class=\"fa-solid fa-chevron-down\"></i>\n </button>\n @if (ConversationHistoryDropdownOpen) {\n <div class=\"fb-chat-context-history-popover\" (click)=\"$event.stopPropagation()\">\n @for (c of LineageConversations; track c.ID) {\n <button type=\"button\"\n class=\"fb-chat-context-history-item\"\n [class.fb-chat-context-history-item--active]=\"c.ID === ChatConversationId\"\n (click)=\"PickLineageConversation(c)\">\n <i class=\"fa-solid fa-message\"></i>\n <span class=\"fb-chat-context-history-name\">{{ c.Name || '(unnamed)' }}</span>\n @if (c.UpdatedAt) {\n <span class=\"fb-chat-context-history-time\">{{ c.UpdatedAt | date:'short' }}</span>\n }\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n } @else {\n <div class=\"fb-chat-context fb-chat-context--empty\">\n <div class=\"fb-chat-context-sub\">Pick or create a form to start.</div>\n </div>\n }\n\n <!-- Embedded chat-area \u2014 same interior component the floating overlay\n wraps. Mounting it directly here means the chat lives in the\n cockpit's right pane (no overlay popping). `appContext` carries\n the ActiveForm shape so Sage / Form Builder agent sees the\n cockpit state with every message. -->\n <div class=\"fb-chat-embed\">\n @if (IsNewForm && !SelectedFormID) {\n <!-- New, unsaved form \u2014 disable chat until the form has an ID.\n Without this, the chat would be bound to whatever conversation\n was previously loaded (wrong subject), or would create a\n conversation linked to a record that doesn't exist yet. -->\n <div class=\"fb-chat-context fb-chat-context--empty\">\n <i class=\"fa-solid fa-lock\" style=\"font-size: 1.5rem; opacity: 0.5; margin-bottom: 0.5rem;\"></i>\n <div class=\"fb-chat-context-sub\">Save the form to start a conversation with the Form Builder agent.</div>\n </div>\n } @else if (currentUser) {\n <!-- conversationCreated must be wired \u2014 without it the chat\n stays stuck on the welcome screen after the user sends the\n first message (we never flip out of new-conversation mode). -->\n <!-- pendingMessage + pendingAttachments are the atomic-handoff\n buffer the chat-area emits as part of `conversationCreated`.\n Feeding them back as inputs is what actually posts the\n user's first message after the conversation is created;\n without this, the chat creates the conversation but\n appears stuck on the welcome screen. -->\n <mj-conversation-chat-area\n [Provider]=\"Provider\"\n [environmentId]=\"ChatEnvironmentId\"\n [currentUser]=\"currentUser\"\n [conversationId]=\"ChatConversationId\"\n [conversation]=\"ChatConversation\"\n [isNewConversation]=\"ChatIsNewConversation\"\n [suppressNewConversationEmptyState]=\"true\"\n [showAgentModePicker]=\"true\"\n [allowMentions]=\"false\"\n [pendingMessage]=\"ChatPendingMessage\"\n [pendingAttachments]=\"$any(ChatPendingAttachments)\"\n [overlayMode]=\"true\"\n [showExportButton]=\"false\"\n [showShareButton]=\"false\"\n [showArtifactIndicator]=\"false\"\n [appContext]=\"$any(ChatAppContext)\"\n [defaultAgentId]=\"FormBuilderAgentId\"\n [showAgentPicker]=\"false\"\n [applicationScope]=\"'Application'\"\n [applicationId]=\"CockpitApplicationId\"\n [linkedEntityId]=\"ComponentsEntityID\"\n [linkedRecordId]=\"SelectedFormID\"\n [threadId]=\"ChatThreadId\"\n [pendingArtifactId]=\"ChatPendingArtifactId\"\n [pendingArtifactVersionNumber]=\"ChatPendingArtifactVersionNumber\"\n [emptyStateGreeting]=\"'How can I help with this form?'\"\n (conversationCreated)=\"OnChatConversationCreated($event)\"\n (pendingMessageConsumed)=\"OnChatPendingMessageConsumed()\"\n (openEntityRecord)=\"OnChatOpenEntityRecord($event)\"\n (navigationRequest)=\"OnChatNavigationRequest($event)\"\n (taskClicked)=\"OnChatTaskClicked($event)\"\n (artifactLinkClicked)=\"OnChatArtifactLinkClicked($event)\"\n (conversationRenamed)=\"OnChatConversationRenamed($event)\"\n (threadOpened)=\"OnChatThreadOpened($event)\"\n (threadClosed)=\"OnChatThreadClosed()\"\n (pendingArtifactConsumed)=\"OnChatPendingArtifactConsumed()\">\n </mj-conversation-chat-area>\n } @else {\n <div class=\"fb-chat-context fb-chat-context--empty\">\n <div class=\"fb-chat-context-sub\">Loading\u2026</div>\n </div>\n }\n </div>\n }\n </aside>\n </as-split-area>\n }\n\n </as-split>\n\n <!-- Collapsed strip (chat). Same OUTSIDE-the-splitter pattern as the\n left rail's strip. -->\n @if (ChatPaneCollapsed && HasActiveForm) {\n <div class=\"fb-collapsed-strip\" role=\"region\" aria-label=\"Form Builder AI (collapsed)\">\n <button class=\"fb-rail-collapse\" type=\"button\" (click)=\"ToggleChatPane()\"\n aria-label=\"Expand Form Builder AI\">\n <i class=\"fa-solid fa-chevron-left\"></i>\n </button>\n <div class=\"fb-collapsed-strip-label\"><i class=\"fa-solid fa-wand-magic-sparkles\"></i></div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n POST-SAVE OVERRIDE DIALOG (unchanged)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <mj-form-override-dialog\n [Visible]=\"ShowFormOverrideDialog\"\n [ComponentName]=\"PendingOverrideComponentName\"\n [EntityName]=\"PendingOverrideEntityName\"\n [InitialName]=\"PendingOverrideInitialName\"\n [InitialDescription]=\"PendingOverrideInitialDescription\"\n [InitialNotes]=\"PendingOverrideInitialNotes\"\n [InitialStatus]=\"PendingOverrideInitialStatus\"\n [InitialScope]=\"PendingOverrideInitialScope\"\n [InitialRoleID]=\"PendingOverrideInitialRoleID\"\n [InitialPriority]=\"PendingOverrideInitialPriority\"\n [EditMode]=\"FormOverrideDialogEditMode\"\n (confirmed)=\"OnFormOverrideDialogConfirm($event)\"\n (dismissed)=\"OnFormOverrideDialogDismiss()\">\n </mj-form-override-dialog>\n\n <!-- Inspector pane for Layout mode lives inside the Layout view \u2014 for the\n Preview / Code modes it stays hidden (the field palette is only useful\n in the canvas surface). When we resurrect it as a pop-out, this is\n where it'll re-mount. -->\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n RIGHT-CLICK CONTEXT MENU on form rows\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (ContextMenuOpen && ContextMenuForm) {\n <div class=\"fb-context-menu-backdrop\" (click)=\"CloseContextMenu()\" (contextmenu)=\"CloseContextMenu(); $event.preventDefault()\"></div>\n <div class=\"fb-context-menu\"\n [style.left.px]=\"ContextMenuX\"\n [style.top.px]=\"ContextMenuY\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"fb-context-menu-header\">{{ ContextMenuForm.Name }}</div>\n <button type=\"button\" class=\"fb-context-menu-item\" (click)=\"ContextMenuTogglePin()\">\n <i class=\"fa-solid fa-thumbtack\"></i>\n <span>{{ IsFormPinned(ContextMenuForm) ? 'Unpin from top' : 'Pin to top' }}</span>\n </button>\n <button type=\"button\" class=\"fb-context-menu-item\" (click)=\"ContextMenuDuplicate()\">\n <i class=\"fa-solid fa-copy\"></i>\n <span>Duplicate</span>\n </button>\n <button type=\"button\" class=\"fb-context-menu-item\" (click)=\"ContextMenuCopyID()\">\n <i class=\"fa-solid fa-fingerprint\"></i>\n <span>Copy Component ID</span>\n </button>\n <button type=\"button\" class=\"fb-context-menu-item\" (click)=\"ContextMenuExportSpec()\">\n <i class=\"fa-solid fa-file-export\"></i>\n <span>Export Spec JSON</span>\n </button>\n <hr class=\"fb-context-menu-divider\" />\n <button type=\"button\" class=\"fb-context-menu-item fb-context-menu-item--danger\" (click)=\"ContextMenuDelete()\">\n <i class=\"fa-solid fa-trash\"></i>\n <span>Delete</span>\n </button>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n VERSION DIFF DIALOG\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (ShowDiffDialog) {\n <div class=\"fb-diff-backdrop\" (click)=\"CloseDiffDialog()\">\n <div class=\"fb-diff-modal\" (click)=\"$event.stopPropagation()\">\n <header class=\"fb-diff-header\">\n <h3>\n <i class=\"fa-solid fa-code-compare\"></i>\n Compare versions\n </h3>\n <button class=\"fb-diff-close\" type=\"button\" (click)=\"CloseDiffDialog()\" aria-label=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </header>\n <div class=\"fb-diff-body\">\n <div class=\"fb-diff-pane\">\n <div class=\"fb-diff-pane-label\">\n <span class=\"fb-diff-pane-tag\">A</span>\n <span>v{{ versionByID(DiffSourceVersionID)?.Version }}</span>\n <span class=\"fb-diff-pane-meta\">{{ versionByID(DiffSourceVersionID)?.UpdatedAt | date:'short' }}</span>\n </div>\n <pre class=\"fb-diff-code\">{{ DiffSourceCode }}</pre>\n </div>\n <div class=\"fb-diff-pane\">\n <div class=\"fb-diff-pane-label\">\n <span class=\"fb-diff-pane-tag fb-diff-pane-tag--b\">B</span>\n <span>v{{ versionByID(DiffTargetVersionID)?.Version }}</span>\n <span class=\"fb-diff-pane-meta\">{{ versionByID(DiffTargetVersionID)?.UpdatedAt | date:'short' }}</span>\n </div>\n <pre class=\"fb-diff-code\">{{ DiffTargetCode }}</pre>\n </div>\n </div>\n <footer class=\"fb-diff-footer\">\n <button class=\"btn\" type=\"button\" (click)=\"CloseDiffDialog()\">Close</button>\n </footer>\n </div>\n </div>\n }\n</div>\n", styles: ["/* Form Builder Resource \u2014 full-screen 3-pane workspace. */\n\n:host {\n display: block;\n position: relative;\n width: 100%;\n height: 100%;\n color: var(--mj-text-primary);\n background: var(--mj-bg-page);\n}\n\n.fb-resource {\n position: absolute;\n inset: 0;\n display: grid;\n grid-template-columns: 320px 1fr 340px;\n grid-template-rows: 1fr;\n overflow: hidden;\n background: var(--mj-bg-page);\n}\n\n.fb-resource.left-collapsed {\n grid-template-columns: 48px 1fr 340px;\n}\n\n.fb-resource.right-collapsed {\n grid-template-columns: 320px 1fr 48px;\n}\n\n.fb-resource.left-collapsed.right-collapsed {\n grid-template-columns: 48px 1fr 48px;\n}\n\n/* ---------------- RAILS ---------------- */\n\n.fb-left-rail,\n.fb-right-rail {\n background: var(--mj-bg-surface);\n border-right: 1px solid var(--mj-border-default);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.fb-right-rail {\n border-right: none;\n border-left: 1px solid var(--mj-border-default);\n}\n\n.fb-rail-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n}\n\n.fb-rail-title {\n flex: 1;\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n letter-spacing: 0.02em;\n}\n\n.fb-rail-collapse {\n background: transparent;\n border: 1px solid var(--mj-border-default);\n color: var(--mj-text-secondary);\n border-radius: 4px;\n width: 24px;\n height: 24px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n}\n\n.fb-rail-collapse:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.fb-rail-new {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border: none;\n border-radius: 4px;\n padding: 6px 10px;\n font-size: 12px;\n font-weight: 600;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n cursor: pointer;\n}\n\n.fb-rail-new:hover {\n background: var(--mj-brand-primary-hover);\n}\n\n.fb-rail-search {\n position: relative;\n padding: 8px 12px;\n border-bottom: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface);\n}\n\n.fb-rail-search i {\n position: absolute;\n top: 50%;\n left: 22px;\n transform: translateY(-50%);\n color: var(--mj-text-muted);\n font-size: 12px;\n pointer-events: none;\n}\n\n.fb-rail-search input {\n width: 100%;\n padding: 6px 8px 6px 28px;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n font-size: 13px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n}\n\n.fb-rail-search input:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.fb-rail-list {\n flex: 1;\n overflow-y: auto;\n padding: 8px;\n}\n\n.fb-rail-item {\n display: block;\n width: 100%;\n text-align: left;\n padding: 10px 12px;\n border: 1px solid transparent;\n border-radius: 6px;\n background: transparent;\n cursor: pointer;\n margin-bottom: 4px;\n color: var(--mj-text-primary);\n}\n\n.fb-rail-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.fb-rail-item.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n}\n\n.fb-rail-item-name {\n font-size: 13px;\n font-weight: 600;\n margin-bottom: 4px;\n}\n\n.fb-rail-item-meta {\n display: flex;\n gap: 4px;\n flex-wrap: wrap;\n}\n\n.fb-rail-badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 6px;\n font-size: 10px;\n font-weight: 500;\n border-radius: 9999px;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n border: 1px solid var(--mj-border-subtle);\n}\n.fb-rail-badge i { font-size: 8px; }\n\n.fb-rail-badge--active {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n border-color: var(--mj-status-success-border);\n}\n.fb-rail-badge--active i { color: var(--mj-status-success); }\n\n.fb-rail-badge--inactive {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n border-color: var(--mj-border-strong);\n}\n.fb-rail-badge--inactive i { color: var(--mj-text-muted); }\n\n.fb-rail-badge--pending {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n border-color: var(--mj-status-warning-border);\n}\n.fb-rail-badge--pending i { color: var(--mj-status-warning); }\n\n.fb-rail-empty {\n text-align: center;\n padding: 24px 12px;\n color: var(--mj-text-muted);\n font-size: 13px;\n /* <mj-empty-state> host must span the rail so its content centers */\n width: 100%;\n}\n\n.fb-rail-empty i {\n font-size: 24px;\n color: var(--mj-text-disabled);\n margin-bottom: 8px;\n display: block;\n}\n\n/* ---------------- CENTER ---------------- */\n\n.fb-center {\n display: flex;\n flex-direction: column;\n min-height: 0;\n min-width: 0;\n overflow: hidden;\n background: var(--mj-bg-page);\n}\n\n.fb-toolbar {\n display: flex;\n align-items: center;\n padding: 10px 16px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n gap: 12px;\n}\n\n.fb-name-input {\n flex: 1 1 auto;\n min-width: 0;\n max-width: 320px;\n padding: 6px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n font-size: 14px;\n font-weight: 600;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n}\n\n.fb-name-input:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.fb-name-input::placeholder {\n color: var(--mj-text-muted);\n font-weight: 400;\n font-style: italic;\n}\n\n.fb-toolbar > .fb-action--primary {\n margin-left: auto;\n}\n\n.fb-entity-picker {\n position: relative;\n}\n\n/* \u2500\u2500 Override status pill + popover \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.fb-status-picker { position: relative; display: inline-flex; }\n.fb-status-pill {\n display: inline-flex; align-items: center; gap: 6px;\n padding: 5px 10px; border-radius: 999px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary); font-size: 12px; line-height: 1;\n cursor: pointer;\n transition: background 120ms, border-color 120ms;\n}\n.fb-status-pill:hover:not(:disabled) { background: var(--mj-bg-surface-hover); border-color: var(--mj-border-strong); }\n.fb-status-pill:disabled { opacity: 0.5; cursor: not-allowed; }\n.fb-status-pill .chev { font-size: 9px; opacity: 0.7; }\n.fb-status-pill--active { border-color: var(--mj-status-success-border); }\n.fb-status-pill--active i.fa-circle-dot { color: var(--mj-status-success); }\n.fb-status-pill--inactive { border-color: var(--mj-border-strong); }\n.fb-status-pill--inactive i.fa-circle-dot { color: var(--mj-text-muted); }\n.fb-status-pill--pending { border-color: var(--mj-status-warning-border); }\n.fb-status-pill--pending i.fa-circle-dot { color: var(--mj-status-warning); }\n\n.fb-status-popover {\n position: absolute; top: calc(100% + 4px); left: 0; z-index: 50;\n min-width: 280px;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 4px 16px rgba(0,0,0,0.12);\n padding: 4px 0;\n}\n.fb-status-option {\n display: flex; align-items: flex-start; gap: 10px; width: 100%;\n padding: 8px 12px; background: transparent; border: none; cursor: pointer;\n color: var(--mj-text-primary); font-size: 13px; text-align: left;\n}\n.fb-status-option:hover:not(:disabled) { background: var(--mj-bg-surface-hover); }\n.fb-status-option:disabled { opacity: 0.5; cursor: not-allowed; }\n.fb-status-option > i { margin-top: 2px; color: var(--mj-text-secondary); }\n.fb-status-option--selected { background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent); }\n.fb-status-option--selected > i { color: var(--mj-brand-primary); }\n.fb-status-option-label { font-weight: 600; }\n.fb-status-option-desc {\n display: block; font-size: 11px; color: var(--mj-text-muted); margin-top: 2px;\n white-space: normal;\n}\n.fb-status-option > span { flex: 1; display: block; }\n\n.fb-entity-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.fb-entity-btn:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.fb-entity-btn em {\n color: var(--mj-text-muted);\n font-style: italic;\n}\n\n.fb-entity-btn .chev {\n font-size: 10px;\n color: var(--mj-text-muted);\n}\n\n.fb-entity-popover {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n width: 320px;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 8px 24px color-mix(in srgb, var(--mj-text-primary) 12%, transparent);\n z-index: 50;\n max-height: 400px;\n display: flex;\n flex-direction: column;\n}\n\n.fb-entity-search {\n padding: 8px 10px;\n margin: 8px;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n font-size: 13px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n}\n\n.fb-entity-list {\n flex: 1;\n overflow-y: auto;\n list-style: none;\n margin: 0;\n padding: 0 4px 8px;\n}\n\n.fb-entity-item {\n display: flex;\n flex-direction: column;\n padding: 6px 10px;\n border-radius: 4px;\n cursor: pointer;\n}\n\n.fb-entity-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.fb-entity-display {\n font-size: 13px;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.fb-entity-name {\n font-size: 11px;\n color: var(--mj-text-muted);\n}\n\n.fb-action {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n}\n\n.fb-action:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n}\n\n.fb-action--primary {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary);\n}\n\n.fb-action--primary:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.fb-action--danger {\n color: var(--mj-status-error-text);\n border-color: var(--mj-status-error-border);\n background: var(--mj-status-error-bg);\n}\n\n.fb-action--danger:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface));\n}\n\n.fb-action:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.fb-canvas-host {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n padding: 16px;\n background: var(--mj-bg-surface-sunken);\n}\n\n.fb-empty {\n text-align: center;\n padding: 64px 16px;\n color: var(--mj-text-muted);\n /* <mj-empty-state> host must fill the pane so its content centers */\n width: 100%;\n}\n\n.fb-empty i {\n font-size: 56px;\n color: var(--mj-text-disabled);\n margin-bottom: 16px;\n display: block;\n}\n\n.fb-empty h3 {\n margin: 0 0 8px;\n color: var(--mj-text-primary);\n font-size: 16px;\n}\n\n.fb-empty p {\n margin: 0;\n max-width: 480px;\n margin-inline: auto;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n COCKPIT LAYOUT (v2)\n Forms list | Center (Preview/Code/Layout) | Chat\n Both side rails collapse to a strip. Center always claims the slack.\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n/* \u2500\u2500 Cockpit shell \u2014 flex row with the angular-split inside \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.fb-cockpit {\n display: flex;\n flex-direction: row;\n height: 100%;\n min-height: 0;\n background: var(--mj-border-default);\n}\n.fb-splitter {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n height: 100%;\n}\n/* Each split-area is a column flex container so its inner aside/section\n * can flex-grow to fill. */\n:host as-split-area {\n display: flex;\n flex-direction: column;\n min-height: 0;\n min-width: 0;\n overflow: hidden;\n}\n/* Collapsed strip \u2014 44px column rendered outside the splitter when the\n * left or chat pane is collapsed. Shows only the expand chevron + a\n * tiny icon hint to remind the user what's hidden. */\n.fb-collapsed-strip {\n width: 44px;\n flex: 0 0 44px;\n background: var(--mj-bg-surface);\n border-right: 1px solid var(--mj-border-default);\n border-left: 1px solid var(--mj-border-default);\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 12px;\n padding: 10px 0;\n}\n.fb-collapsed-strip-label {\n color: var(--mj-text-muted);\n font-size: 14px;\n}\n\n.fb-forms-rail,\n.fb-chat-pane {\n background: var(--mj-bg-surface);\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-height: 0;\n overflow: hidden;\n}\n.fb-center {\n background: var(--mj-bg-surface);\n display: flex;\n flex-direction: column;\n min-height: 0;\n min-width: 0;\n}\n\n/* \u2500\u2500 Version rail (lower half of the left forms rail) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.fb-versions-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 14px 4px;\n border-top: 1px solid var(--mj-border-default);\n color: var(--mj-text-secondary);\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.4px;\n}\n.fb-versions-header h4 {\n margin: 0;\n font-size: 11px;\n font-weight: 600;\n flex: 1;\n}\n.fb-versions-count {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 30%, var(--mj-bg-surface));\n border-radius: 999px;\n padding: 1px 8px;\n font-size: 10px;\n font-weight: 700;\n}\n.fb-versions-list {\n flex: 1 1 auto;\n overflow-y: auto;\n padding: 6px 10px 14px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.fb-version-row {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 8px);\n background: var(--mj-bg-surface);\n cursor: pointer;\n transition: background 120ms, border-color 120ms, box-shadow 120ms;\n}\n.fb-version-row:hover {\n background: var(--mj-bg-surface-hover);\n}\n.fb-version-row:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 1px;\n}\n\n/* Lifecycle marker \u2014 left-side dot whose color signals Active/Pending/Inactive.\n Decoupled from the row's selected state so the two axes (what the runtime\n serves vs what the user is currently viewing) don't visually collide. */\n.fb-version-marker {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n flex-shrink: 0;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-strong);\n}\n.fb-version-marker i {\n font-size: 8px;\n color: var(--mj-text-muted);\n}\n.fb-version-row.is-active .fb-version-marker {\n background: color-mix(in srgb, var(--mj-status-success, #16a34a) 25%, transparent);\n border-color: var(--mj-status-success-border, #16a34a);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-status-success, #16a34a) 25%, transparent);\n}\n.fb-version-row.is-active .fb-version-marker i {\n color: var(--mj-status-success, #16a34a);\n font-size: 11px;\n}\n.fb-version-row.is-pending .fb-version-marker {\n background: color-mix(in srgb, var(--mj-status-warning, #f59e0b) 25%, transparent);\n border-color: var(--mj-status-warning-border, #fcd34d);\n}\n.fb-version-row.is-pending .fb-version-marker i {\n color: var(--mj-status-warning, #f59e0b);\n font-size: 11px;\n}\n\n/* Selected = \"this is what the editor is showing right now\". Blue family,\n clearly distinct from the lifecycle green/amber on the marker. */\n.fb-version-row.is-selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n box-shadow: inset 3px 0 0 var(--mj-brand-primary);\n}\n.fb-version-row.is-selected:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 16%, var(--mj-bg-surface));\n}\n.fb-version-row-main {\n flex: 1;\n min-width: 0;\n}\n.fb-version-label {\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.fb-version-meta {\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-top: 2px;\n}\n.fb-version-tag {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n border: 1px solid var(--mj-border-default);\n border-radius: 999px;\n padding: 1px 6px;\n font-size: 10px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n.fb-version-tag.is-active {\n background: var(--mj-status-success-bg, #dcfce7);\n color: var(--mj-status-success-text, #166534);\n border-color: var(--mj-status-success-border, #86efac);\n}\n.fb-version-tag.is-pending {\n background: var(--mj-status-warning-bg, #fef3c7);\n color: var(--mj-status-warning-text, #92400e);\n border-color: var(--mj-status-warning-border, #fcd34d);\n}\n.fb-version-activate {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 8px);\n color: var(--mj-brand-primary);\n cursor: pointer;\n padding: 4px 8px;\n}\n.fb-version-activate:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n/* \u2500\u2500 Center tab segmented control \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.fb-modeseg {\n display: inline-flex;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 8px);\n overflow: hidden;\n}\n.fb-modeseg-btn {\n background: transparent;\n border: none;\n border-right: 1px solid var(--mj-border-default);\n color: var(--mj-text-secondary);\n font-size: 12px;\n padding: 6px 12px;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n.fb-modeseg-btn:last-child { border-right: none; }\n.fb-modeseg-btn:hover:not(:disabled) { background: var(--mj-bg-surface-hover); }\n.fb-modeseg-btn.active {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n font-weight: 600;\n}\n.fb-modeseg-btn:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n background: var(--mj-bg-surface-sunken);\n}\n\n/* \u2500\u2500 Custom-code chip (toolbar) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* Shown when the loaded code has constructs the canvas can't round-trip\n (hand-authored JSX, computed children, etc.). Signals to the user\n that this form is in \"code-only\" mode and explains why the Layout\n tab is disabled. */\n.fb-custom-code-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 10px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.02em;\n border-radius: 999px;\n background: color-mix(in srgb, var(--mj-status-info, #0ea5e9) 12%, var(--mj-bg-surface));\n color: var(--mj-status-info-text, #075985);\n border: 1px solid var(--mj-status-info-border, #38bdf8);\n cursor: help;\n user-select: none;\n}\n.fb-custom-code-chip i { font-size: 10px; }\n\n.fb-spacer { flex: 1; }\n\n/* \u2500\u2500 Center body \u2014 host for Preview / Code / Layout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.fb-center-body {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface-card);\n position: relative; /* anchors .fb-version-overlay */\n}\n\n/* \u2500\u2500 Version overlay (top-right chrome on center pane) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* Floats above the preview/code/layout content, anchored to .fb-center-body.\n Top-RIGHT so it doesn't collide with line numbers / line 1 content in\n the Code tab. Visually clearly NOT part of the form \u2014 sits as a small\n pill with subtle shadow + backdrop blur so it reads as editor chrome. */\n.fb-version-overlay {\n position: absolute;\n top: 10px;\n right: 10px;\n z-index: 10;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 5px 10px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.02em;\n background: color-mix(in srgb, var(--mj-bg-surface-elevated) 90%, transparent);\n border: 1px solid var(--mj-border-default);\n color: var(--mj-text-secondary);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n user-select: none;\n pointer-events: auto;\n}\n.fb-version-overlay i {\n font-size: 10px;\n opacity: 0.8;\n}\n.fb-version-overlay-label {\n color: var(--mj-text-primary);\n}\n.fb-version-overlay-tag {\n font-size: 9px;\n padding: 1px 6px;\n border-radius: 999px;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n.fb-version-overlay-tag--active {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n border: 1px solid var(--mj-status-success-border);\n}\n.fb-version-overlay-tag--pending {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n border: 1px solid var(--mj-status-warning-border);\n}\n.fb-version-overlay-tag--historical {\n background: color-mix(in srgb, var(--mj-status-warning, #f59e0b) 14%, transparent);\n color: var(--mj-status-warning-text, #92400e);\n border: 1px solid var(--mj-status-warning-border, #fcd34d);\n}\n/* Subtle accent on the overlay border to reinforce the lifecycle state. */\n.fb-version-overlay--active { border-color: var(--mj-status-success-border); }\n.fb-version-overlay--pending,\n.fb-version-overlay--historical { border-color: var(--mj-status-warning-border, #fcd34d); }\n.fb-preview-host {\n flex: 1;\n overflow: auto;\n padding: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.fb-preview-empty {\n text-align: center;\n max-width: 380px;\n color: var(--mj-text-secondary);\n /* center the width-capped <mj-empty-state> host within the preview pane */\n width: 100%;\n margin-left: auto;\n margin-right: auto;\n}\n.fb-preview-empty i {\n font-size: 24px;\n color: var(--mj-brand-primary);\n margin-bottom: 10px;\n}\n.fb-preview-empty h4 {\n margin: 0 0 6px;\n font-size: 15px;\n color: var(--mj-text-primary);\n}\n.fb-preview-empty p {\n margin: 4px 0;\n font-size: 13px;\n}\n.fb-muted {\n color: var(--mj-text-muted);\n font-size: 12px;\n}\n\n.fb-code-host {\n flex: 1;\n min-height: 0;\n display: flex;\n}\n/* mj-code-editor (CodeMirror) handles its own theming + caret + colors.\n * We just need to make sure it fills the available space. */\n.fb-code-editor,\nmj-code-editor.fb-code-editor {\n flex: 1 1 auto;\n min-height: 0;\n min-width: 0;\n display: flex;\n}\n.fb-code-editor :where(.cm-editor) {\n flex: 1 1 auto;\n min-height: 0;\n}\n\n/* \u2500\u2500 Chat pane (right) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.fb-chat-pane .fb-rail-header {\n background: linear-gradient(180deg,\n color-mix(in srgb, var(--mj-brand-accent, #7c3aed) 8%, var(--mj-bg-surface)) 0%,\n var(--mj-bg-surface) 100%);\n}\n.fb-chat-pane .fb-rail-title i {\n color: var(--mj-brand-accent, #7c3aed);\n}\n.fb-chat-body {\n flex: 1;\n overflow-y: auto;\n padding: 14px;\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n.fb-chat-context {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 25%, var(--mj-bg-surface));\n border-radius: var(--mj-radius-md, 8px);\n padding: 10px 12px;\n}\n.fb-chat-context--empty {\n background: var(--mj-bg-surface-sunken);\n border-color: var(--mj-border-default);\n}\n.fb-chat-context-label {\n text-transform: uppercase;\n letter-spacing: 0.4px;\n font-size: 10px;\n font-weight: 700;\n color: var(--mj-brand-primary);\n}\n.fb-chat-context-value {\n font-weight: 600;\n font-size: 13px;\n color: var(--mj-text-primary);\n margin-top: 2px;\n}\n.fb-chat-context-sub {\n color: var(--mj-text-muted);\n font-size: 12px;\n margin-top: 2px;\n}\n.fb-chat-refine-btn {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse, #ffffff);\n border: 1px solid var(--mj-brand-primary);\n border-radius: var(--mj-radius-md, 8px);\n padding: 10px 14px;\n font-weight: 600;\n font-size: 13px;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n}\n.fb-chat-refine-btn:hover { background: var(--mj-brand-primary-hover, var(--mj-brand-primary)); }\n.fb-chat-refine-btn:disabled {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-disabled);\n border-color: var(--mj-border-default);\n cursor: not-allowed;\n}\n.fb-chat-tips {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 8px);\n padding: 12px 14px;\n}\n.fb-chat-tips h4 {\n margin: 0 0 8px;\n font-size: 12px;\n font-weight: 700;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n}\n.fb-chat-tips ul {\n margin: 0 0 8px;\n padding-left: 18px;\n font-size: 12px;\n color: var(--mj-text-primary);\n}\n.fb-chat-tips li { margin-bottom: 4px; }\n.fb-chat-tips p { margin: 6px 0 0; }\n\n/* When the chat pane is collapsed, the header is the only visible part */\n.fb-cockpit.chat-collapsed .fb-chat-body { display: none; }\n.fb-cockpit.left-collapsed .fb-rail-search,\n.fb-cockpit.left-collapsed .fb-rail-list,\n.fb-cockpit.left-collapsed .fb-versions-header,\n.fb-cockpit.left-collapsed .fb-versions-list { display: none; }\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n PREVIEW TAB \u2014 toolstrip + record picker + frame\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.fb-preview-host {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n.fb-preview-toolstrip {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 14px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n font-size: 12px;\n color: var(--mj-text-secondary);\n}\n.fb-preview-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 8px);\n padding: 4px 10px;\n font-size: 12px;\n color: var(--mj-text-primary);\n cursor: pointer;\n}\n.fb-preview-chip:hover {\n border-color: var(--mj-border-focus);\n}\n.fb-preview-chip strong { font-weight: 600; }\n.fb-preview-chip-badge {\n background: var(--mj-status-warning-bg, #fef3c7);\n color: var(--mj-status-warning-text, #92400e);\n border: 1px solid var(--mj-status-warning-border, #fcd34d);\n font-size: 10px;\n font-weight: 700;\n padding: 1px 6px;\n border-radius: 999px;\n}\n.fb-preview-chip-entity {\n margin-left: 4px;\n padding: 2px 8px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 30%, var(--mj-bg-surface));\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n}\n.fb-preview-warning {\n color: var(--mj-status-warning-text, #92400e);\n}\n.fb-preview-picker-panel {\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n padding: 10px 14px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.fb-preview-picker-search {\n width: 100%;\n padding: 7px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 8px);\n background: var(--mj-bg-surface);\n font-size: 13px;\n color: var(--mj-text-primary);\n}\n.fb-preview-picker-search:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n.fb-preview-picker-results {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.fb-preview-picker-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 13px;\n}\n.fb-preview-picker-row:hover { background: var(--mj-bg-surface-hover); }\n.fb-preview-picker-empty {\n color: var(--mj-text-muted);\n font-size: 12px;\n font-style: italic;\n}\n.fb-preview-frame {\n flex: 1;\n min-height: 0;\n overflow: auto;\n padding: 18px;\n background: var(--mj-bg-page);\n}\n\n/* Retrospective fix #9 \u2014 labeled rail button (Activate / Restore). */\n.fb-version-activate {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n padding: 3px 8px;\n}\n.fb-version-activate-label {\n font-weight: 600;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n EMBEDDED CHAT (replaces the prior Refine-with-AI CTA panel)\n The chat-area component grows to fill whatever's left after the\n context strip. The pane itself is already a flex column, so we just\n need the embed slot to flex-grow and the host child to fill 100%.\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n.fb-chat-embed {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n overflow: hidden;\n}\n.fb-chat-embed mj-conversation-chat-area {\n display: flex;\n flex: 1 1 auto;\n min-height: 0;\n min-width: 0;\n}\n\n/* The legacy `.fb-chat-body / .fb-chat-refine-btn / .fb-chat-tips` rules\n above target the old CTA layout. They no longer apply (DOM removed)\n but stay in place harmlessly \u2014 kept so any css-encapsulation snapshot\n tests don't break. Safe to delete in a follow-up cleanup. */\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n FORM BUILDER REVAMP \u2014 left-rail enhancements\n Search + entity filter + status chips + sort + tree view + pin +\n row-with-icon-and-subtitle + inner splitter + collapsibles + context\n menu + diff dialog. All tokens via --mj-* design system per\n conventions in CLAUDE.md.\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n/* Forms-list / Versions inner panels ------------------------------- */\n.fb-inner-panel {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n}\n.fb-inner-panel-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-sunken);\n border-bottom: 1px solid var(--mj-border-default);\n cursor: pointer;\n user-select: none;\n flex: 0 0 auto;\n}\n.fb-inner-panel-header:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.fb-inner-panel-header--open {\n border-top: 1px solid var(--mj-border-default);\n}\n.fb-inner-panel-count {\n margin-left: auto;\n font-size: 11px;\n font-weight: 500;\n text-transform: none;\n letter-spacing: 0;\n color: var(--mj-text-muted);\n}\n.fb-inner-splitter { height: 100%; }\n\n/* Rail header: tree/list toggle + new button ---------------------- */\n.fb-rail-icon-btn {\n background: transparent;\n border: 1px solid transparent;\n border-radius: 4px;\n width: 28px;\n height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: background 120ms, color 120ms;\n}\n.fb-rail-icon-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n/* Search row + entity filter dropdown ----------------------------- */\n.fb-rail-search-row {\n display: flex;\n gap: 6px;\n padding: 8px 12px 4px;\n align-items: center;\n}\n.fb-rail-search-row .fb-rail-search {\n flex: 1 1 auto;\n padding: 0;\n}\n.fb-entity-filter {\n flex: 0 0 auto;\n position: relative;\n}\n.fb-entity-filter-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 10px;\n height: 28px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n color: var(--mj-text-primary);\n font-size: 12px;\n cursor: pointer;\n max-width: 140px;\n}\n.fb-entity-filter-btn:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n}\n.fb-entity-filter-btn > span {\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.fb-entity-filter-btn .chev {\n font-size: 9px;\n opacity: 0.6;\n}\n.fb-entity-filter-popover {\n position: absolute;\n top: calc(100% + 4px);\n right: 0;\n z-index: 60;\n min-width: 260px;\n max-height: 340px;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);\n}\n.fb-entity-filter-search {\n padding: 8px 10px;\n border: 0;\n border-bottom: 1px solid var(--mj-border-default);\n background: transparent;\n font-size: 13px;\n color: var(--mj-text-primary);\n outline: none;\n}\n.fb-entity-filter-list {\n list-style: none;\n margin: 0;\n padding: 4px 0;\n overflow-y: auto;\n max-height: 280px;\n}\n.fb-entity-filter-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n font-size: 13px;\n color: var(--mj-text-primary);\n cursor: pointer;\n}\n.fb-entity-filter-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n.fb-entity-filter-item--selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n color: var(--mj-text-primary);\n}\n.fb-entity-filter-item > span:first-of-type {\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.fb-entity-filter-count {\n font-size: 11px;\n color: var(--mj-text-muted);\n}\n\n/* Status filter chips + sort -------------------------------------- */\n.fb-rail-chips {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 12px 8px;\n flex-wrap: wrap;\n}\n.fb-chip {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 8px;\n height: 22px;\n border: 1px solid var(--mj-border-default);\n border-radius: 11px;\n background: transparent;\n color: var(--mj-text-muted);\n font-size: 11px;\n cursor: pointer;\n transition: background 120ms, color 120ms, border-color 120ms;\n}\n.fb-chip:hover {\n background: var(--mj-bg-surface-hover);\n}\n.fb-chip i { font-size: 8px; }\n.fb-chip--on {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n border-color: var(--mj-brand-primary);\n color: var(--mj-text-primary);\n}\n.fb-rail-chips .fb-spacer { flex: 1 1 auto; }\n.fb-rail-sort {\n height: 24px;\n padding: 0 6px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n font-size: 11px;\n color: var(--mj-text-secondary);\n cursor: pointer;\n}\n\n/* Form-list row enhancements (icon + subtitle + pin + dirty dot) -- */\n.fb-rail-item {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 8px 10px 8px 6px;\n width: 100%;\n background: transparent;\n border: 0;\n border-left: 3px solid transparent;\n text-align: left;\n cursor: pointer;\n color: var(--mj-text-primary);\n}\n.fb-rail-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n.fb-rail-item.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, transparent);\n border-left-color: var(--mj-brand-primary);\n}\n.fb-rail-item--tree {\n padding-left: 24px;\n}\n.fb-rail-item--dirty .fb-rail-item-name { font-style: italic; }\n\n.fb-rail-pin {\n background: transparent;\n border: 0;\n width: 18px;\n height: 18px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-disabled);\n cursor: pointer;\n font-size: 10px;\n border-radius: 3px;\n flex: 0 0 auto;\n margin-top: 2px;\n opacity: 0;\n transition: opacity 120ms, color 120ms;\n}\n.fb-rail-item:hover .fb-rail-pin,\n.fb-rail-pin--on {\n opacity: 1;\n}\n.fb-rail-pin--on { color: var(--mj-brand-primary); }\n.fb-rail-pin:hover { color: var(--mj-text-primary); background: var(--mj-bg-surface-hover); }\n\n.fb-rail-item-icon {\n flex: 0 0 auto;\n width: 18px;\n text-align: center;\n color: var(--mj-text-secondary);\n margin-top: 2px;\n}\n.fb-rail-item-body {\n flex: 1 1 auto;\n min-width: 0;\n}\n.fb-rail-item-name {\n font-size: 13px;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n display: flex;\n align-items: center;\n gap: 4px;\n}\n.fb-rail-item-dirty-dot {\n color: var(--mj-status-warning);\n font-size: 11px;\n}\n.fb-rail-item-sub {\n font-size: 11px;\n color: var(--mj-text-muted);\n display: flex;\n align-items: center;\n gap: 4px;\n margin-top: 2px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.fb-rail-item-entity { color: var(--mj-text-secondary); }\n.fb-rail-item-time { color: var(--mj-text-muted); }\n\n/* Tree-view groups ------------------------------------------------- */\n.fb-tree-schema {\n margin-bottom: 4px;\n}\n.fb-tree-schema-header {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px 4px;\n font-size: 10px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--mj-text-muted);\n}\n.fb-tree-entity-header {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 12px;\n font-size: 11px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n.fb-tree-entity-header > span:first-of-type {\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.fb-tree-entity-count {\n font-size: 10px;\n font-weight: 500;\n color: var(--mj-text-muted);\n}\n\n/* Version diff button + source-row highlight ---------------------- */\n.fb-version-action {\n background: transparent;\n border: 0;\n width: 24px;\n height: 24px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 3px;\n color: var(--mj-text-disabled);\n cursor: pointer;\n font-size: 11px;\n opacity: 0;\n transition: opacity 120ms, color 120ms, background 120ms;\n}\n.fb-version-row:hover .fb-version-action {\n opacity: 1;\n}\n.fb-version-action:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.fb-version-row.is-diff-source {\n background: color-mix(in srgb, var(--mj-brand-primary) 16%, transparent);\n outline: 1px dashed var(--mj-brand-primary);\n}\n.fb-version-tag--diff {\n background: color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n color: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n}\n\n/* Chat-header status + version + conversation history --------------- */\n.fb-chat-context-row {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n}\n.fb-chat-context-version {\n font-size: 11px;\n padding: 1px 6px;\n border-radius: 9px;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n font-family: monospace;\n}\n.fb-chat-context-history {\n margin-top: 4px;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 8px;\n background: transparent;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n font-size: 11px;\n color: var(--mj-text-secondary);\n cursor: pointer;\n}\n.fb-chat-context-history:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.fb-chat-context-history-popover {\n position: absolute;\n z-index: 60;\n top: 100%;\n left: 0;\n right: 0;\n margin-top: 4px;\n max-height: 240px;\n overflow-y: auto;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);\n padding: 4px 0;\n}\n.fb-chat-context-history-item {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 6px 10px;\n background: transparent;\n border: 0;\n font-size: 12px;\n color: var(--mj-text-primary);\n cursor: pointer;\n text-align: left;\n}\n.fb-chat-context-history-item:hover { background: var(--mj-bg-surface-hover); }\n.fb-chat-context-history-item--active {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n font-weight: 600;\n}\n.fb-chat-context-history-name {\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.fb-chat-context-history-time {\n font-size: 10px;\n color: var(--mj-text-muted);\n}\n\n/* Empty state \u2014 landing card -------------------------------------- */\n.fb-empty--landing {\n max-width: 520px;\n /* center the 520px-capped landing host within the canvas */\n margin-left: auto;\n margin-right: auto;\n}\n.fb-empty-recent {\n margin-top: 24px;\n width: 100%;\n}\n.fb-empty-recent-label {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--mj-text-muted);\n margin-bottom: 8px;\n}\n.fb-empty-recent-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.fb-empty-recent-item {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 8px 12px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n cursor: pointer;\n text-align: left;\n color: var(--mj-text-primary);\n}\n.fb-empty-recent-item:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n}\n.fb-empty-recent-item > i { color: var(--mj-text-secondary); }\n.fb-empty-recent-meta { flex: 1 1 auto; min-width: 0; }\n.fb-empty-recent-name { font-size: 13px; font-weight: 500; }\n.fb-empty-recent-entity { font-size: 11px; color: var(--mj-text-muted); }\n\n/* Open-record button on preview chip strip ------------------------- */\n.fb-preview-open-record {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 8px;\n background: transparent;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n color: var(--mj-text-secondary);\n font-size: 11px;\n cursor: pointer;\n margin-left: auto;\n}\n.fb-preview-open-record:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n border-color: var(--mj-border-strong);\n}\n\n/* Context menu (right-click) -------------------------------------- */\n.fb-context-menu-backdrop {\n position: fixed;\n inset: 0;\n z-index: 1000;\n}\n.fb-context-menu {\n position: fixed;\n z-index: 1001;\n min-width: 220px;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);\n padding: 4px 0;\n}\n.fb-context-menu-header {\n padding: 6px 12px 4px;\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--mj-text-muted);\n border-bottom: 1px solid var(--mj-border-default);\n margin-bottom: 4px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.fb-context-menu-item {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n padding: 6px 12px;\n background: transparent;\n border: 0;\n font-size: 13px;\n color: var(--mj-text-primary);\n cursor: pointer;\n text-align: left;\n}\n.fb-context-menu-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n.fb-context-menu-item--danger {\n color: var(--mj-status-error-text);\n}\n.fb-context-menu-item--danger:hover {\n background: var(--mj-status-error-bg);\n}\n.fb-context-menu-divider {\n border: 0;\n border-top: 1px solid var(--mj-border-default);\n margin: 4px 0;\n}\n\n/* Version diff dialog --------------------------------------------- */\n.fb-diff-backdrop {\n position: fixed;\n inset: 0;\n z-index: 1000;\n background: var(--mj-bg-overlay);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.fb-diff-modal {\n width: 90vw;\n height: 80vh;\n max-width: 1400px;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n border-radius: 8px;\n box-shadow: 0 12px 36px rgba(0, 0, 0, 0.24);\n}\n.fb-diff-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 20px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n.fb-diff-header h3 {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n.fb-diff-close {\n background: transparent;\n border: 0;\n cursor: pointer;\n color: var(--mj-text-muted);\n font-size: 16px;\n padding: 4px;\n}\n.fb-diff-body {\n flex: 1 1 auto;\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 0;\n min-height: 0;\n}\n.fb-diff-pane {\n display: flex;\n flex-direction: column;\n min-height: 0;\n min-width: 0;\n border-right: 1px solid var(--mj-border-default);\n}\n.fb-diff-pane:last-child { border-right: 0; }\n.fb-diff-pane-label {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 16px;\n background: var(--mj-bg-surface-sunken);\n border-bottom: 1px solid var(--mj-border-default);\n font-size: 12px;\n color: var(--mj-text-secondary);\n}\n.fb-diff-pane-tag {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 11px;\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n font-weight: 700;\n font-size: 11px;\n}\n.fb-diff-pane-tag--b {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n.fb-diff-pane-meta {\n margin-left: auto;\n font-size: 11px;\n color: var(--mj-text-muted);\n}\n.fb-diff-code {\n flex: 1 1 auto;\n min-height: 0;\n margin: 0;\n padding: 12px 16px;\n overflow: auto;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-primary);\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n font-size: 12px;\n line-height: 1.5;\n white-space: pre;\n}\n.fb-diff-footer {\n padding: 12px 20px;\n border-top: 1px solid var(--mj-border-default);\n display: flex;\n justify-content: flex-end;\n}\n"] }]
4567
4569
  }], null, null); })();
4568
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(FormBuilderResourceComponent, { className: "FormBuilderResourceComponent", filePath: "src/FormBuilder/form-builder-resource.component.ts", lineNumber: 179 }); })();
4570
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(FormBuilderResourceComponent, { className: "FormBuilderResourceComponent", filePath: "src/FormBuilder/form-builder-resource.component.ts", lineNumber: 180 }); })();
4569
4571
  /** Tree-shake protection — referenced from the dashboards module loader. */
4570
4572
  export function LoadFormBuilderResourceComponent() {
4571
4573
  // Intentional no-op. Forces the bundler to keep this file's side effects