Architecture

dsviper-components is what every Commit-based application built on dsviper ends up needing: the standard administrative, collaborative and scripting surface of a Commit application, already implemented and ready to compose into a domain-specific UI. Rather than a collection of loose widgets, it is a coherent set of components that share two conventions: signal-based state updates and constructor-based store injection.

The Notifier Bridge

See also

This page describes the Python / Qt implementation. For the language-agnostic statement of the same pattern — UI-framework notifications layered over the runtime’s CommitStoreNotifying interface, with parallel adapters for AppKit, Qt C++ and Python — see DSM — The Notifier Pattern.

The dsviper runtime exposes change notifications through the CommitStoreNotifying protocol — a plain Python interface with methods like notify_state_did_change() and notify_database_did_open(). For Qt to consume these notifications, they must become Qt Signals.

DSCommitStoreNotifier is the adapter that bridges the two worlds. It is a QObject whose signals mirror the protocol’s notification methods one-for-one:

# dsviper_components/ds_commit_store_notifier.py
class DSCommitStoreNotifier(QObject):

    # Database
    database_did_open      = Signal()
    database_did_close     = Signal()
    state_did_change       = Signal()
    definitions_did_change = Signal()

    # Dispatch
    dispatch_error = Signal(Error)

    # Live Mode
    stop_live = Signal()

    # Reset
    reset_database     = Signal()
    database_will_reset = Signal()
    database_did_reset  = Signal()

    message = Signal(str)

    def notify_state_did_change(self):
        self.state_did_change.emit()
    # … one method per Signal

The pattern is the same on every platform that has its own notification mechanism (NSNotificationCenter on AppKit, signals/slots on Qt C++, QObject Signals here). The runtime stays platform-agnostic; each UI adapter translates the abstract notifications into the platform’s native event mechanism.

Composition over inheritance

Every widget in the library accepts the running CommitStore (or one of its lower-level cousins like Databasing) as a constructor argument:

# A typical dialog
class DSCommitsDialog(QDialog):
    def __init__(self, store: CommitStore, *args, **kwargs):
        super().__init__(*args, **kwargs)
        component = self._setup_ui()
        component.set_store(store)

The widget does not subclass anything domain-specific; it does not reach out to a global. The application owns the store and injects it into each component. This makes the library equally usable in:

  • a single-store application (one CommitStore.instance(), used by ge-py),

  • a multi-store application (one CommitStore per document, used by ge-qml),

  • and headless tools that build a CommitStore for a one-off task.

Tip

The instance() classmethods present on some components (DSCommitStoreNotifier.instance()) are a convenience for single-store applications, not a requirement. ge-qml deliberately avoids them and injects an explicit notifier into every component.

Integration recipe

A minimal Commit-based application built on dsviper-components does five things, in this order:

from dsviper import CommitStore, CommitDatabase
from dsviper_components.ds_commit_store_notifier import DSCommitStoreNotifier
from dsviper_components.ds_commits_dialog       import DSCommitsDialog
from dsviper_components.ds_commit_undo_dialog   import DSCommitUndoDialog
# … any other dialog the application wants

# 1. Build the store (one or many).
store = CommitStore()

# 2. Wire the notifier so the runtime can emit Qt Signals.
notifier = DSCommitStoreNotifier()
store.set_notifier(notifier)        # CommitStore consumes CommitStoreNotifying

# 3. Open or create the database, then attach it to the store.
database = CommitDatabase.open("model.cdb")
store.set_database(database)
store.notify_database_did_open()

# 4. Instantiate the dialogs the application wants. Each receives the
#    same store. They subscribe to the notifier on their own and react
#    to state_did_change, database_did_open, etc.
commits_dialog = DSCommitsDialog(store)
undo_dialog    = DSCommitUndoDialog(store)

# 5. Wire the application's own UI to the same notifier, so the domain
#    panels redraw in lock-step with the shared dialogs.
notifier.state_did_change.connect(self._refresh_my_panel)

Steps 1–3 are the runtime side. Step 4 is what dsviper-components adds: every shared widget instantiated with the running store. Step 5 is the only application-specific wiring left.

Note

The notifier is subscribed in two directions: the runtime tells it “state changed”, and the UI listens for the corresponding Qt Signal. The notifier never reaches into the runtime — it only translates notifications.

Why this matters

The library’s value is not “widgets you can re-use”. It is the realisation that the administrative surface of a Commit application — DAG browsing, undo navigation, blob inspection, remote sync, embedded scripting — is identical across applications, because it operates on the runtime concepts, not on any specific domain. Adopting dsviper-components means delegating that entire surface to the shared library and writing only the part that is genuinely domain-specific.

For the catalogue of what the library exposes, see Widgets. For a worked example of the integration in a real application, see the ge-py walk-through.