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.
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.
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.
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).
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.
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.
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.
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).
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.
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.
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.
Right-click context-menu state.
Protecteddestroy$When the user starts a diff via right-click on a version row, this holds the first selection. Click another row to compare.
When set, render the diff modal showing both specs side-by-side.
Entity-filter dropdown open state.
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.
Edit-mode flag — when true, the dialog updates the existing
override (EditingOverrideID) instead of creating a new one.
Selected entity filter. Empty string = "All entities".
Percent (0-100) height of the forms-list section within the left rail. The versions panel takes the remainder.
Status chips currently enabled. Active+Pending+Inactive by default so the list shows everything. Toggle a chip OFF to hide that bucket.
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.
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.
ProtectednavigationReadonlyOverrideAllowed 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.
Whether the cockpit's override-status popover is open.
Inline saving flag — disables the picker buttons mid-save.
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.
Pre-fill values forwarded to the override dialog. Populated by OnSave (for post-save flow) or OpenEditFormDetailsDialog (for the edit-existing-override flow).
Pinned form Component IDs (kept sticky at the top of the list).
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.
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.
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.
Version rail data. Lineage = all Component rows sharing the active form's Name. Ordered by VersionSequence DESC so the newest is on top.
StaticɵdirStaticɵfacEnvironment ID for the embedded chat-area. Resource components in Explorer accept this via ResourceData.Configuration; we fall back to the default environment when not supplied.
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.
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.
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.
True when a form is "actively being shown" — either an existing form is loaded (SelectedFormID set) or the user clicked New Form (IsNewForm). The right-side chat pane is gated on this so empty cockpits don't render an agent shell against no subject.
Live ComponentSpec for the Preview pane. Derived from the in-memory
EditableCode so the preview reflects unsaved edits — switching to
Preview while typing in Code shows the in-progress version, not the
last-saved one. Falls back to null when there's no code yet, in which
case the Preview pane shows a friendly empty state.
Returns either the default Metadata provider or the one specified in the Provider property, if it was specified
Returns either the default RunQuery provider or the one specified in the Provider property, if it was specified
Returns either the default RunReport provider or the one specified in the Provider property, if it was specified
Returns either the default RunView provider or the one specified in the Provider property, if it was specified
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.
ProtectedGetRead current query params from tab configuration. Use in initDashboard() / ngOnInit() to get initial URL state.
Get this component's tab ID. Checks ParentTabId input first (set by resource wrappers for child dashboards), then falls back to Data.Configuration.tabId.
Load a Top-1 record from the target entity to bind into the preview. If the entity has no rows, we still mount the form against a synthetic NewRecord() so the layout / styling / event handlers can be evaluated.
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.
A callback method that is invoked immediately after Angular has completed initialization of a component's view. It is invoked only once when the view is instantiated.
A callback method that performs custom clean-up, invoked immediately before a directive, pipe, or service instance is destroyed.
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.
ProtectedNotifyAsk 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.
ProtectedNotifyCall 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.
ProtectedNotifyProtectedNotifyRe-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).
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).
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.
Chat-area emitted a generic navigationRequest (cross-resource
navigation, e.g. "go to Collections with this artifact"). Route
via NavigationService.OpenNavItemByName.
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).
Pending artifact handed off to the chat-area (e.g. via deep link) has been consumed.
Chat-area finished delivering the pending message — clear the buffer so a re-render doesn't try to send it again.
Chat-area emitted taskClicked — user clicked a task pill in an
agent reply. The workspace switches to its Tasks tab; the cockpit
has no Tasks tab, so open the task record directly.
Thread drill-down: chat-area closed the sub-thread. Clear ID.
Thread drill-down: chat-area opened a sub-thread. Track ID so the chat-area renders it.
Delete the currently-loaded form's Component AND any EntityFormOverride rows pointing at it. Without the cascade, deleting just the Component would leave orphan overrides whose resolver lookups fail silently.
Right-click context menu on a form row. Opens at click coords.
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.
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.
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.
Handle a LoadErrorChanged emit from
User picked a different record from the search results — re-bind preview.
Search-as-you-type for the preview record picker.
ProtectedOnCalled 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.
The new query params from the URL
'popstate' for back/forward, 'deeplink' for external URL entry
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?].
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.
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.).
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.
Open the preview record (the entity row currently bound to
<mj-interactive-form>) in a fresh Explorer tab. Useful sanity-check
— render the form against this same record in the production
resolver path to confirm the cockpit's preview matches reality.
Switch the embedded chat to a different conversation in the same lineage. Used by the header's "N conversations" dropdown.
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.
OptionalrecordId: stringProtectedRefreshMarks 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.
ProtectedResourceToggle the override status popover. No-op when no override is loaded.
Optionalevent: EventProtectedUpdatePush query param changes to the URL. Creates a browser history entry. Safe to call during OnQueryParamsChanged — auto-suppressed to prevent loops.
Helper: lookup the version row for a given ID, used in the diff title.
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 callNotifyLoadComplete()after the initial load — without it, the shell loading screen hangs forever on direct URL navigation (e.g./app/form-studio/Form%20Builder).