Member Junction
    Preparing search index...

    Form Builder resource — the standalone Form Studio canvas reachable from the top-left Application rail. Provides a drag-drop canvas, field-binding inspector, and EntityFormOverride activation flow that mirrors Component Studio's contextual Form Builder tab but as its own dedicated workspace.

    Owns its OWN state — does NOT depend on ComponentStudioStateService, which is provided per-Component-Studio-dashboard. The canvas and right panel children are state-agnostic and accept everything via @Input/@Output.

    Per dashboards/CLAUDE.md, this resource MUST call NotifyLoadComplete() after the initial load — without it, the shell loading screen hangs forever on direct URL navigation (e.g. /app/form-studio/Form%20Builder).

    Hierarchy (View Summary)

    Implements

    • AfterViewInit
    • OnDestroy
    Index

    Constructors

    Properties

    ActiveOverrideID ActiveOverrideScope ActiveOverrideStatus ActiveVersionID Canvas CanvasDiverged CenterPaneMode CenterPanePct ChatAppContext ChatConversation ChatConversationId ChatIsNewConversation ChatPaneCollapsed ChatPanePct ChatPendingArtifactId ChatPendingArtifactVersionNumber ChatPendingAttachments ChatPendingMessage ChatThreadId CockpitApplicationId ComponentsEntityID ConfirmDialog ContextMenuForm ContextMenuOpen ContextMenuX ContextMenuY ConversationHistoryDropdownOpen destroy$ DiffSourceCode DiffSourceVersionID DiffTargetCode DiffTargetVersionID DirtyFlag EditableCode EditingOverrideID EntityChoices EntityFilterDropdownOpen EntityFilterSearch EntityPickerSearch ExistingForms FormBuilderAgentId FormOverrideDialogEditMode FormsEntityFilter FormsListCollapsed FormsListHeightPct FormsSortMode FormsStatusFilter FormsViewMode IsEntityPickerOpen IsLoading IsNewForm LeftPanePct LeftRailCollapsed LeftRailFilter LineageConversations navigationService OverrideStatusOptions OverrideStatusPickerOpen OverrideStatusSaving ParentTabId PendingOverrideComponentID PendingOverrideComponentName PendingOverrideEntityName PendingOverrideInitialDescription PendingOverrideInitialName PendingOverrideInitialNotes PendingOverrideInitialPriority PendingOverrideInitialRoleID PendingOverrideInitialScope PendingOverrideInitialStatus PinnedFormIds PreviewError PreviewLoading PreviewPickerOpen PreviewRecord PreviewRecordIsReal PreviewRecordLabel PreviewSearchResults PreviewSearchTerm Provider RightRailCollapsed SavedSpec Schema SelectedElementId SelectedFormID SelectedFormName SelectedSectionId ShowDiffDialog ShowFormOverrideDialog TargetEntityName Versions VersionsCollapsed VersionsLoading ɵdir ɵfac

    Accessors

    Methods

    CloseContextMenu CloseDiffDialog ContextMenuCopyID ContextMenuDelete ContextMenuDuplicate ContextMenuExportSpec ContextMenuTogglePin GetQueryParams GetResourceDisplayName GetResourceIconClass getTabId IsFormPinned loadPreviewRecord loadVersionsForActiveForm ngAfterViewInit ngOnDestroy ngOnInit NotifyCloseRequested NotifyDisplayNameChanged NotifyLoadComplete NotifyLoadStarted OnActivateVersion OnCanvasChanged OnChatArtifactLinkClicked OnChatConversationCreated OnChatConversationRenamed OnChatNavigationRequest OnChatOpenEntityRecord OnChatPendingArtifactConsumed OnChatPendingMessageConsumed OnChatTaskClicked OnChatThreadClosed OnChatThreadOpened OnDelete OnDeselected OnEditableCodeChange OnElementChanged OnElementDeleted OnElementSelected OnEntityFilterSearch OnEntityPicked OnEntityPickerSearch OnFieldAddedFromPalette OnFormNameInput OnFormOverrideDialogConfirm OnFormOverrideDialogDismiss OnFormPicked OnFormRowContextMenu OnInnerSplitterDragEnd OnLeftRailFilterChange OnNewForm OnOpenInChat OnOverrideStatusPicked OnPreviewLoadError OnPreviewRecordPicked OnPreviewSearchInput OnQueryParamsChanged OnSave OnSectionChanged OnSectionDeleted OnSectionSelected OnSplitterDragEnd OnVersionDiffClick OnVersionRowClick OpenEditFormDetailsDialog OpenPreviewRecord PickEntityFilter PickLineageConversation previewForm RefreshView ResourceRecordSaved SetCenterPaneMode SetFormsSortMode ToggleChatPane ToggleConversationHistoryDropdown ToggleEntityFilterDropdown ToggleEntityPicker ToggleFormsListCollapsed ToggleFormsViewMode ToggleLeftRail ToggleOverrideStatusPicker TogglePinForm TogglePreviewPicker ToggleRightRail ToggleStatusFilter ToggleVersionsCollapsed UpdateQueryParams versionByID

    Constructors

    Properties

    ActiveOverrideID: string | null = null

    Override metadata for the active form — captured at form-load time so the agent context can carry it inline (no Get Active Form For Entity round-trip needed). The Form Builder agent prompt's lifecycle rules branch on whether an active override exists and what its scope/status is; passing this snapshot upfront lets the agent jump straight to Modify Interactive Form instead of guessing.

    ActiveOverrideScope: string | null = null
    ActiveOverrideStatus: string | null = null
    ActiveVersionID: string | null = null
    Canvas: FormCanvasModel | null = null
    CanvasDiverged: boolean = false

    True when the in-memory code (EditableCode) has diverged from what the canvas can represent — e.g. hand-authored sections, computed JSX the canvas-parser can't round-trip. Set by hydrateCanvasFromCode. The Layout tab shows a warning banner so the user knows the canvas will overwrite parts of the code on save. Retrospective fix #6.

    CenterPaneMode: "code" | "preview" | "layout" = 'preview'

    Center-pane view mode for the cockpit. Default is 'preview' because the preview is the most useful single view when a form is loaded; users tab to 'code' to edit and 'layout' for the (legacy) drag-drop canvas.

    CenterPanePct: number = 56
    ChatAppContext: AppContextSnapshot | null = null

    AppContext snapshot fed to the embedded <mj-conversation-chat-area> so the agent sees the cockpit's current form + entity. Sourced from NavigationService.AppContextSnapshot$ — the same snapshot the floating overlay uses — so both surfaces see identical context. Our cockpit- specific state is pushed in via SetAgentContext() (which the Explorer app shell merges into the snapshot's AdditionalContext).

    ChatConversation: MJConversationEntity | null = null

    Conversation state for the embedded chat. Initially null/new; when the user sends the first message, the chat-area emits conversationCreated and we flip these so subsequent renders keep the same conversation. Without this binding, the chat appears "stuck" on the welcome message because the wrapper never picks up the new conversation ID.

    ChatConversationId: string | null = null
    ChatIsNewConversation: boolean = true
    ChatPaneCollapsed: boolean = false
    ChatPanePct: number = 22
    ChatPendingArtifactId: string | null = null

    Pending artifact pointer for the chat-area to scroll to / highlight after navigation. Set when an artifact link inside the chat is clicked (the chat-area asks us to surface a specific artifact version) and cleared when consumed.

    ChatPendingArtifactVersionNumber: number | null = null
    ChatPendingAttachments: unknown[] | null = null
    ChatPendingMessage: string | null = null

    Pending message + attachments that came back on conversationCreated. The chat-area emits these as part of its atomic state-handoff so the parent can re-feed them after the conversation has an ID. The chat-area's pendingMessageConsumed event tells us when to clear. Without this loop, the user's first message creates the conversation but never actually posts.

    ChatThreadId: string | null = null

    Thread state for the embedded chat. Mirrors the workspace shell's pattern: chat-area emits threadOpened with a thread ID when the user drills into a sub-thread, and threadClosed when they leave. Forwarded back as [threadId] so the chat-area knows which thread to render. Without these handlers, clicking a thread message in the cockpit's chat appears to do nothing (the chat-area emits but no one binds back).

    CockpitApplicationId: string | null = null

    ID of the Application that owns this cockpit. Resolved at init from Metadata.Applications (the in-memory cache populated at provider bootstrap — NO RunView round-trip) and forwarded to the embedded chat-area as [applicationId], so conversations created inside this cockpit are scoped to the Form Studio app and don't pollute the main Chat list.

    ComponentsEntityID: string | null = null

    EntityID for MJ: Components, resolved from Metadata.Entities at init. Bound to <mj-conversation-chat-area>'s [linkedEntityId] so conversations created in the cockpit are stamped as "about a Component" — enabling the future "show prior conversations about THIS form" surface. In-memory lookup, no RunView.

    ConfirmDialog:
        | {
            Body: string;
            CancelLabel: string;
            ConfirmLabel: string;
            Danger: boolean;
            OnCancel?: () => void;
            OnConfirm: () => void | Promise<void>;
            Open: boolean;
            Title: string;
        }
        | null = null

    State for the cockpit's reusable confirm dialog. Replaces the browser-native window.confirm() calls (delete form, discard unsaved edits) with a styled in-app dialog. Three reasons for the switch: (a) consistent visual language with the override + diff dialogs, (b) confirm/cancel button order matches MJ convention (primary left), (c) browser confirm is blocked by some browsers / tools when triggered from event handlers without recent user gesture (e.g. agent-driven flows).

    Hold the resolver callback alongside the state so multiple confirm call-sites can share the same dialog without each one re-binding.

    ContextMenuForm: FormComponentSummary | null = null

    Right-click context-menu state.

    ContextMenuOpen: boolean = false
    ContextMenuX: number = 0
    ContextMenuY: number = 0
    ConversationHistoryDropdownOpen: boolean = false
    destroy$: Subject<void>
    DiffSourceCode: string = ''
    DiffSourceVersionID: string | null = null

    When the user starts a diff via right-click on a version row, this holds the first selection. Click another row to compare.

    DiffTargetCode: string = ''
    DiffTargetVersionID: string | null = null

    When set, render the diff modal showing both specs side-by-side.

    DirtyFlag: boolean = false
    EditableCode: string = ''
    EditingOverrideID: string | null = null
    EntityChoices: readonly { DisplayName: string; Name: string }[] = []
    EntityFilterDropdownOpen: boolean = false

    Entity-filter dropdown open state.

    EntityFilterSearch: string = ''
    EntityPickerSearch: string = ''
    ExistingForms: FormComponentSummary[] = []
    FormBuilderAgentId: string | null = null

    Resolved ID of the Form Builder agent. Looked up once at init via RunView. Bound to <mj-conversation-chat-area>'s [defaultAgentId] so messages route directly to the Form Builder agent instead of going through Sage — Sage doesn't author forms, the Form Builder agent does. Null until the lookup resolves (or if the agent isn't installed); in that case the chat falls back to Sage routing.

    FormOverrideDialogEditMode: boolean = false

    Edit-mode flag — when true, the dialog updates the existing override (EditingOverrideID) instead of creating a new one.

    FormsEntityFilter: string = ''

    Selected entity filter. Empty string = "All entities".

    FormsListCollapsed: boolean = false
    FormsListHeightPct: number = 65

    Percent (0-100) height of the forms-list section within the left rail. The versions panel takes the remainder.

    FormsSortMode: "updated-desc" | "updated-asc" | "name-asc" | "name-desc" = 'updated-desc'
    FormsStatusFilter: Set<"Active" | "Pending" | "Inactive"> = ...

    Status chips currently enabled. Active+Pending+Inactive by default so the list shows everything. Toggle a chip OFF to hide that bucket.

    FormsViewMode: "list" | "tree" = 'list'
    IsEntityPickerOpen: boolean = false
    IsLoading: boolean = true
    IsNewForm: boolean = false
    LeftPanePct: number = 22

    Pane sizes (percent) for the 3-pane horizontal split. Initialized from FormBuilderPrefs loaded out of UserInfoEngine on init; mutated by the splitter's drag-end event; written back via savePrefs.

    LeftRailCollapsed: boolean = false
    LeftRailFilter: string = ''
    LineageConversations: {
        ID: string;
        Name: string | null;
        UpdatedAt: Date | null;
    }[] = []

    Lightweight list of all conversations across the active form's lineage — feeds the chat header's "N conversations" dropdown so the user can switch back to a prior thread instead of being locked into just the most-recent one. Populated alongside loadLinkedConversation.

    navigationService: NavigationService
    OverrideStatusOptions: readonly {
        description: string;
        icon: string;
        label: string;
        value: "Active" | "Pending" | "Inactive";
    }[] = ...

    Allowed values for EntityFormOverride.Status mirrored from the DB CHECK constraint / EntityFieldValue rows. Hardcoded for two reasons: (a) they're stable schema enums, and (b) the picker UI needs them statically to render labels + icons without round-trip.

    OverrideStatusPickerOpen: boolean = false

    Whether the cockpit's override-status popover is open.

    OverrideStatusSaving: boolean = false

    Inline saving flag — disables the picker buttons mid-save.

    ParentTabId: string | null

    Tab ID for query param notification scoping. Set by resource wrappers that render child dashboards, so the child knows which tab it belongs to. If not set, falls back to Data.Configuration.tabId.

    PendingOverrideComponentID: string | null = null
    PendingOverrideComponentName: string = ''
    PendingOverrideEntityName: string = ''
    PendingOverrideInitialDescription: string | null = null
    PendingOverrideInitialName: string | null = null

    Pre-fill values forwarded to the override dialog. Populated by OnSave (for post-save flow) or OpenEditFormDetailsDialog (for the edit-existing-override flow).

    PendingOverrideInitialNotes: string | null = null
    PendingOverrideInitialPriority: number | undefined = undefined
    PendingOverrideInitialRoleID: string | null | undefined = undefined
    PendingOverrideInitialScope: "User" | "Role" | "Global" | null = null
    PendingOverrideInitialStatus: "Active" | "Pending" | "Inactive" | null = null
    PinnedFormIds: Set<string> = ...

    Pinned form Component IDs (kept sticky at the top of the list).

    PreviewError: string | null = null
    PreviewLoading: boolean = false
    PreviewPickerOpen: boolean = false
    PreviewRecord: BaseEntity<unknown> | null = null

    Live preview state. When the user switches to the Preview tab, we load a Top-1 record from TargetEntityName and mount <mj-interactive-form> with the working componentSpec derived from the current EditableCode. Failure modes (no record, RunView error) fall back to a synthetic NewRecord() so the form still mounts — the user just sees an empty draft instead of a broken pane.

    PreviewRecordIsReal: boolean = false
    PreviewRecordLabel: string = ''
    PreviewSearchResults: { ID: string; Label: string }[] = []
    PreviewSearchTerm: string = ''
    Provider: IMetadataProvider | null

    If specified, this provider will be used for communication and for all metadata purposes. By default, if not provided, the Metadata and RunView classes are used for this and the default GraphQLDataProvider is used which is connected to the same back-end MJAPI instance as the Metadata and RunView classes. If you want to have this component connect to a different MJAPI back-end, create an instance of a ProviderBase sub-class like GraphQLDataProvider/etc, and configure it as appropriate to connect to the MJAPI back-end you want to use, and then pass it in here.

    RightRailCollapsed: boolean = false
    SavedSpec: ComponentSpec | null = null

    The saved ComponentSpec from the last loaded form, preserved so the Preview pane can merge live code edits over the rich metadata (dataRequirements / description / functionalRequirements / technicalDesign) rather than rebuilding from EditableCode alone. Retrospective fix #5.

    Schema: CuratedFormSchema | null = null
    SelectedElementId: string | null = null
    SelectedFormID: string | null = null
    SelectedFormName: string = ''
    SelectedSectionId: string | null = null
    ShowDiffDialog: boolean = false
    ShowFormOverrideDialog: boolean = false
    TargetEntityName: string | null = null
    Versions: ComponentVersionRow[] = []

    Version rail data. Lineage = all Component rows sharing the active form's Name. Ordered by VersionSequence DESC so the newest is on top.

    VersionsCollapsed: boolean = false
    VersionsLoading: boolean = false
    "ɵdir": unknown
    "ɵfac": unknown

    Accessors

    • get CurrentSelectedVersion(): ComponentVersionRow | null

      The version row matching the Component currently loaded in the editor (SelectedFormID). Drives the floating "Viewing v1.0.0" overlay on the center pane so the user always knows which version's spec they're looking at — particularly useful when they've clicked an older row in the version rail to inspect historical work, and the on-screen form differs from what the runtime is actually serving.

      Returns ComponentVersionRow | null

    • get DisplayNameChangedEvent(): ((newName: string) => void) | null

      Returns ((newName: string) => void) | null

    • set DisplayNameChangedEvent(value: ((newName: string) => void) | null): void

      Parameters

      • value: ((newName: string) => void) | null

      Returns void

    • get entityFilterOptions(): readonly {
          count: number;
          display: string;
          icon: string
          | null;
          name: string;
      }[]

      Distinct entity names that appear in the current forms list — used to populate the entity-filter dropdown. Empty string ("") = "All entities", added at the top. Search-text-filtered when the user is typing into the dropdown's own search box.

      Returns readonly { count: number; display: string; icon: string | null; name: string }[]

    • get filteredForms(): readonly FormComponentSummary[]

      Forms list filtered + sorted per the user's left-rail prefs. Pipeline: search text → entity filter → status chips → sort → pinned-first. The result drives both the flat-list view and is grouped by schema/entity for the tree view.

      Returns readonly FormComponentSummary[]

    • get LoadComplete(): boolean

      Returns boolean

    • get LoadStarted(): boolean

      Returns boolean

    • get LoadStartedEvent(): any

      Returns any

    • set LoadStartedEvent(value: any): void

      Parameters

      • value: any

      Returns void

    • get ProviderToUse(): IMetadataProvider

      Returns either the default Metadata provider or the one specified in the Provider property, if it was specified

      Returns IMetadataProvider

    • get ResourceCloseRequestedEvent(): (() => void) | null

      Returns (() => void) | null

    • set ResourceCloseRequestedEvent(value: (() => void) | null): void

      Parameters

      • value: (() => void) | null

      Returns void

    • get ResourceRecordSavedEvent(): any

      Returns any

    • set ResourceRecordSavedEvent(value: any): void

      Parameters

      • value: any

      Returns void

    • get RunQueryToUse(): IRunQueryProvider

      Returns either the default RunQuery provider or the one specified in the Provider property, if it was specified

      Returns IRunQueryProvider

    • get RunReportToUse(): IRunReportProvider

      Returns either the default RunReport provider or the one specified in the Provider property, if it was specified

      Returns IRunReportProvider

    • get RunViewToUse(): IRunViewProvider

      Returns either the default RunView provider or the one specified in the Provider property, if it was specified

      Returns IRunViewProvider

    • get treeGroups(): readonly {
          entities: readonly {
              entity: string;
              entityDisplay: string;
              forms: readonly FormComponentSummary[];
              icon: string
              | null;
          }[];
          schema: string;
      }[]

      Tree-view groups: forms grouped by SchemaName → EntityName. Falls back to a synthetic "(no entity)" group for forms without a user-scope override bound to an entity.

      Returns readonly {
          entities: readonly {
              entity: string;
              entityDisplay: string;
              forms: readonly FormComponentSummary[];
              icon: string | null;
          }[];
          schema: string;
      }[]

    Methods

    • Read current query params from tab configuration. Use in initDashboard() / ngOnInit() to get initial URL state.

      Returns Record<string, string>

    • Get this component's tab ID. Checks ParentTabId input first (set by resource wrappers for child dashboards), then falls back to Data.Configuration.tabId.

      Returns string

    • Recompute the version-rail rows from InteractiveFormsEngine's caches — all Component rows in the active form's Name lineage, joined against the current user's override rows to tag Active / Pending. No DB round-trip. Auto-refreshed by the engine subscription in ngAfterViewInit on any save/delete/remote-invalidate.

      Kept async for callers that already await it; the body is sync.

      Returns Promise<void>

    • A callback method that is invoked immediately after the default change detector has checked the directive's data-bound properties for the first time, and before any of the view or content children have been checked. It is invoked only once when the directive is instantiated.

      Returns void

    • Ask the host shell to close/dismiss this resource (typically: close the tab). Called by subclasses that hosted a form that emitted a 'dismiss' navigation event — most often, a brand-new record where the user clicked Discard. The record was never saved, the form is empty, and leaving it open serves no purpose. The tab-container listens for this and closes the workspace tab.

      Returns void

    • Call this to notify the tab system that the resource's display name has changed. The tab container will update the tab title and browser title accordingly.

      Parameters

      • newName: string

      Returns void

    • Re-point this form's Active override at the picked version. Called from the version rail "Make Active" button. Delegates to the Activate Interactive Form Version action when the version is Pending, otherwise delegates to Revert Interactive Form (re-point Active override to an older Component row in the same Name lineage).

      Parameters

      • version: ComponentVersionRow

      Returns Promise<void>

    • Chat-area emitted artifactLinkClicked — user clicked an artifact link inside a message. Conversation-type links point at another conversation in main Chat; collection-type links point at a collection. Both route into the main Chat / Collections app via NavigationService so the artifact opens in its native context (the cockpit is form-authoring focused, not the right surface to render arbitrary artifacts inline).

      Parameters

      • event: { id: string; type: "conversation" | "collection" }

      Returns void

    • Chat-area emitted conversationCreated after the user sent the first message — switch the embedded chat out of "new conversation" mode so subsequent renders show the live conversation thread. Without this, the chat appears stuck on its welcome screen.

      Parameters

      • event: {
            conversation: MJConversationEntity;
            pendingAttachments?: unknown[];
            pendingMessage?: string;
        }

      Returns void

    • Chat-area emitted openEntityRecord — user clicked an entity link inside an agent reply. Open the record as a new Explorer tab via NavigationService (same as the workspace's actionable-command path, but executed directly here since the cockpit has no intermediate workspace shell).

      Parameters

      Returns void

    • Drag-end handler for the inner vertical splitter between the forms list and the versions panel inside the left rail. Persists the percent split so the user's preferred layout sticks across reloads.

      Parameters

      • event: { sizes: readonly (number | "*")[] }

      Returns void

    • Open the chat overlay with the active-form context already set. The overlay is mounted at the app root; we don't construct it here. The agent context is kept fresh by registerAgentContext() (called on every form change).

      Side effect: also un-collapses the chat pane so the user sees the surface they just clicked.

      Returns void

    • Persist a new status on the currently-loaded EntityFormOverride. Loads the entity, sets the status, saves, then mirrors the new value onto the cockpit's local snapshot fields so the agent context + UI stay in sync. The BaseEntity event subscription above will also fire as a side effect, but our local guard (ActiveOverrideStatus already up to date) keeps the cockpit from triggering a spurious refresh.

      Parameters

      • next: "Active" | "Pending" | "Inactive"

      Returns Promise<void>

    • Handle a LoadErrorChanged emit from in the Preview pane. Surfaces the error in the cockpit's own error state so users see "Preview failed" in the cockpit chrome instead of having to look inside the form's React shell. Retrospective fix #10.

      Parameters

      • message: string | null

      Returns void

    • Called by the framework when query params change from an external source (browser back/forward, deep link navigation). Override in subclasses to react to query param changes.

      Parameters

      • params: Record<string, string>

        The new query params from the URL

      • source: "popstate" | "deeplink"

        'popstate' for back/forward, 'deeplink' for external URL entry

      Returns void

    • Splitter drag-end handler. sizes from angular-split is an array of SplitAreaSize values (number | '*' for auto-size); for our percent-unit splitter only numbers come through, but the union forces us to filter. Order matches the visible-pane order: [left?, center, chat?].

      Parameters

      • event: { sizes: readonly (number | "*")[] }

      Returns void

    • Begin a diff selection from the version rail. The clicked version becomes the "source"; the next clicked version becomes the "target" and we render a side-by-side diff modal.

      Click the same version twice → cancels the selection.

      Parameters

      • v: ComponentVersionRow
      • event: Event

      Returns Promise<void>

    • Version-rail row click. Loads that version's Component into the cockpit's editor (Preview / Code / Layout) — does NOT change which version the runtime resolver picks for users (that's still driven by the Active override). To promote an older version to Active, the user clicks the row's "Activate" / "Restore" button.

      No-op when the row is already loaded. Builds a synthetic summary and delegates to OnFormPicked so the load path is exactly the same as picking from the left rail (handles dirty-discard confirmation, schema rebuild, version rail refresh, agent context push, etc.).

      Parameters

      • v: ComponentVersionRow

      Returns Promise<void>

    • Open the form-override dialog in EDIT MODE — pre-fills all fields from the active override row so the user can rename, retag, or adjust scope/priority/status without retyping. On confirm, the existing override is updated in place rather than a new one being created.

      Available from the cockpit's toolbar via the "Edit details" pencil icon next to the form name.

      Returns Promise<void>

    • Agent-facing preview entry point: switch the center pane to the live preview and, when a record id is supplied, bind that specific record. Maps the PreviewForm(recordId?) client tool onto the existing SetCenterPaneMode('preview') + record-load lifecycle. When no record id is given, the standard Top-1 lazy load in SetCenterPaneMode runs.

      Parameters

      • OptionalrecordId: string

      Returns void

    • Marks this component's view (and its ancestors) as needing change detection and schedules a tick — the reliable way for a resource component to re-render after updating state from async work (RunView/RunQuery continuations, timers, websocket pushes, RxJS subscriptions).

      WHY THIS EXISTS: the Explorer shell hosts resource components dynamically — createComponent()

      • ApplicationRef.attachView() — because tab content mounts into Golden Layout / cached DOM containers, not into an Angular template. Since Angular 18's change-detection scheduler rework (Explorer is on Angular 21), ApplicationRef.tick() only refreshes attached views that are FLAGGED dirty; a root-level attached view whose component mutates plain fields from an async continuation is never flagged, so the view silently never re-renders — even though the component uses default (non-OnPush) change detection (issue #3106).

      The framework calls this automatically from NotifyLoadStarted() / NotifyLoadComplete(), so the standard load lifecycle re-renders without any subclass action. Call it yourself after any LATER async state change that must reach the DOM outside those signals.

      Returns void

    • Push query param changes to the URL. Creates a browser history entry. Safe to call during OnQueryParamsChanged — auto-suppressed to prevent loops.

      Parameters

      • params: Record<string, string | null>

      Returns void