Skip to content

6.2.8.3. DiscriminatorVM

6.2.8.3.1. When To Use It

Use DiscriminatorVM<TKey> when one VM needs a single source of truth for an active pane, editor mode, route, focus target, or modal precedence stack.

It is intentionally small: state coordination, not child ownership.

6.2.8.3.2. Shape And Ownership

DiscriminatorVM owns one ActiveKey plus an internal modal stack used by ModalOpen(...) and ModalClose(). It does not own the panes, routes, or modal VMs themselves. Those remain external.

Core members:

  • ActiveKey
  • ActiveChanged
  • ModalDepth
  • IsActive(key)
  • SetActiveKey(key)
  • ModalOpen(modalKey)
  • ModalClose()
  • ClearModals()

6.2.8.3.3. Lifecycle And Messaging

The behavior is straightforward and important:

  • every non-modal active-key set abandons saved modal history
  • setting the same key remains notification-free while releasing history
  • setting a different key updates state and emits one change
  • modal opens push the prior active key
  • modal closes restore keys in LIFO order
  • explicit clear releases history without changing the active key
  • dispose releases history, completes the change stream, and makes later mutations inert

6.2.8.3.4. Cross-Language Surface

Concept C# Python TypeScript Swift Rust
Active key ActiveKey active_key activeKey activeKey active_key()
Change stream ActiveChanged active_changed activeChanged activeChanged active_changed()
Modal depth ModalDepth modal_depth modalDepth modalDepth modal_depth()
Open modal ModalOpen(...) modal_open(...) modalOpen(...) modalOpen(...) modal_open(...)
Clear history ClearModals() clear_modals() clearModals() clearModals() clear_modals()

6.2.8.3.5. Example

The Notes Workspace editor mode is the concrete showcase feature:

  • C#: NoteFormVM.cs
  • Python: note_form_vm.py
  • TypeScript: noteFormVM.ts
  • Swift: NoteFormVM.swift

Each editor keeps "edit" and "preview" in a DiscriminatorVM instead of spreading active-mode booleans across unrelated properties.

private readonly DiscriminatorVM<string> _editorMode = new("edit");
public string EditorMode => _editorMode.ActiveKey;
public bool IsPreviewMode => _editorMode.IsActive("preview");
self._editor_mode: DiscriminatorVM[str] = DiscriminatorVM("edit")

@property
def is_preview_mode(self) -> bool:
    return self._editor_mode.is_active("preview")
readonly #editorMode = new DiscriminatorVM<EditorMode>("edit");

get isPreviewMode(): boolean {
  return this.#editorMode.isActive("preview");
}
private let _editorMode = DiscriminatorVM<String>(initial: "edit")

public var isPreviewMode: Bool {
    _editorMode.isActive("preview")
}

6.2.8.3.6. Common Pitfalls

  • Using a discriminator as a container. It coordinates keys; it does not own child VMs.
  • Duplicating the same active-mode state in multiple booleans and then having to reconcile them manually.
  • Forgetting that modal precedence is stack-based; nested modal restore order is LIFO by contract.
  • Treating a non-modal set as a modal close. It deliberately abandons every saved frame; use ModalClose() when restoration is intended.