Architecture

The library’s components share two conventions: signal-based state updates (the Notifier Bridge) and constructor-based store injection (composition over inheritance).

The Notifier Bridge

See also

The framework-agnostic CommitStoreNotifying protocol — what notifications exist, when each fires — is documented on CommitStore — The notification protocol. This section covers the Python / Qt adapter that translates those notifications into Qt Signals.

The dsviper runtime emits change notifications through the CommitStoreNotifying protocol. For Qt to consume them, 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, so it imposes no single topology. Both ge-py and ge-qml run one CommitStore behind a Context singleton; what differs is how each supplies the notifier:

  • ge-py has each component reach the DSCommitStoreNotifier.instance() convenience singleton;

  • ge-qml injects an explicit notifier into every component instead;

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

Tip

The instance() classmethods on some components (DSCommitStoreNotifier.instance()) are a convenience, not a requirement.

Integration recipe

A minimal Commit-based application built on dsviper-components does four 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) and attach a database.
store = CommitStore()
store.use(CommitDatabase.open("model.cdb"))

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

# 3. 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)

# 4. 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–2 are the runtime side. Step 3 is what dsviper-components adds: every shared widget instantiated with the running store. Step 4 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 administrative surface of a Commit application — DAG browsing, undo navigation, blob inspection, remote sync, embedded scripting — operates on the runtime concepts, not on any specific domain, so it is identical across applications. Adopting dsviper-components delegates that surface and leaves only the domain-specific part to write.

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.