Something went wrong. Try again.
The agentic engineering control plane for the posthuman future
Something went wrong. Try again.
8.2 kB · 168 lines
C
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169#pragma once
// Phase 3 sub-step 3: the jj/git bridge-flow controller.//// Absorbs Shell.qml's async flow state (pickMode/pickItems/pendingAddMode/// renameTarget/forgetTarget) and the bridge `Connections` handlers (Shell.qml 1764-1862) as C++// signal connections. TerminalPage.qml instantiates one of these; it owns// the JjBridge + GitBridge instances (QML never instantiates them) and// routes every workspace add/rename/forget/set-directory through AppModel// mutators. It never opens dialogs: it emits decision-point signals// (setDirectoryRequested, pickDialogClose, pickDialogError) and the// normalized pick list (jjWorkspacesReady / gitWorktreesReady) so the page// owns the FolderDialog / PickWorkspaceDialog wiring.//// Workspace-kind detection is per-workspace and asynchronous: detect*// probe both bridges for a workspace path and the identify() results// reseat the workspace kind / store its VCS capabilities. The per-// workspace kind IS the gate; there is no global repo-root state.
#include <QHash>#include <QObject>#include <QString>#include <QVariantList>#include <QVariantMap>
#include <QtQml/qqmlregistration.h>
class AppModel;class GitBridge;class JjBridge;class Workspace;
class WorkspaceController : public QObject { Q_OBJECT QML_ELEMENT Q_DISABLE_COPY_MOVE(WorkspaceController)
public: explicit WorkspaceController(QObject* parent = nullptr); ~WorkspaceController() override;
// --- page-facing API (Q_INVOKABLE; called via the page from // SidebarView / AddWorkspaceMenu / PickWorkspaceDialog) --------------
// Probe the workspace at `index` (async, both bridges): reseat its kind // (jj wins over git; git decides worktree-vs-repository) and store the // detected VCS capabilities. Q_INVOKABLE void detectWorkspaceKind(int index); // Probe every workspace (each runs async; several may be in flight). Q_INVOKABLE void detectAllWorkspaces(); // Add a workspace directly. `kind` is the WorkspaceKind enum value (0 // Directory, 1 Repository, 2 JjWorkspace, 3 GitWorktree). The model // selects the new workspace, then its kind is re-detected (async) and // reseated idempotently from the probe results. Q_INVOKABLE void addWorkspaceDirect(int kind, const QString& path, const QString& name, const QString& repoRoot); // Start the jj/git pick flows: list the workspaces/worktrees under // `repoRoot`, emitting jjWorkspacesReady/gitWorktreesReady when done. Q_INVOKABLE void addJjWorkspace(const QString& repoRoot); Q_INVOKABLE void addGitWorktree(const QString& repoRoot); // Menu-command conveniences: start the pick flow for workspace `index` // (a sidebar-row right-click), or for the active workspace when `index` // is < 0 (the AddWorkspaceMenu). No-op when the workspace has no stored // repo root. Q_INVOKABLE void pickJjWorkspace(int index = -1); Q_INVOKABLE void pickGitWorktree(int index = -1); // Reset the pick session and ask the bridge to list items for `mode` // ("jj" | "git") under `repoRoot`. Q_INVOKABLE void requestPickList(const QString& mode, const QString& repoRoot); // Add a workspace from a pick-dialog item ({name, path}); the repo root // comes from the pick session so forget/rename work on jj workspaces. Q_INVOKABLE void addPickedWorkspace(const QString& mode, const QVariantMap& item); // "Add another": create a memorable-named workspace in the workspaces // dir, disambiguating against the current pick items and existing dirs. Q_INVOKABLE void addMemorableWorkspace(const QString& mode); // Optimistically rename through the model; mirror to jj when the // workspace is a jj workspace (reverted on jj failure). Q_INVOKABLE void renameWorkspace(int index, const QString& newName); // Forget a Repository/JjWorkspace in jj terms (JjWorkspace -> // RepositoryWorkspace reseat on success). Q_INVOKABLE void forgetJjWorkspace(int index); // Ask the page to open the set-directory FolderDialog for `index`. Q_INVOKABLE void setWorkspaceDirectory(int index); Q_INVOKABLE static QString defaultNameFromPath(const QString& path); // Shell.qml localPath port: strip a file:// prefix + percent-decode. Q_INVOKABLE static QString localPath(const QUrl& url); // The spawn directory for a new workspace: the active workspace's path // when it has one (the startup default workspace's path == the startup // cwd), else the user's home. Q_INVOKABLE static QString currentDir();
signals: // Normalized ({name, path}) pick list ready — the page feeds it to // PickWorkspaceDialog.items. void jjWorkspacesReady(const QVariantList& items); void gitWorktreesReady(const QVariantList& items); // Decision points the controller deliberately hands to the page. void pickDialogClose(); // memorable-add succeeded void pickDialogError(const QString& error); // memorable-add failed void setDirectoryRequested(int index); // open the FolderDialog
private slots: // JjBridge async results (Shell.qml 1769-1830). void onJjIdentified(const QString& path, const QString& repoRoot, const QString& workspaceName); void onJjWorkspacesListed(const QVariantList& items); void onWorkspaceAdded(bool ok, const QString& error, const QString& root, const QString& name); void onWorkspaceRenamed(bool ok, const QString& error); void onWorkspaceForgotten(bool ok, const QString& error); // GitBridge async results (Shell.qml 1837-1862). void onGitIdentified(const QString& path, const QString& repoRoot, bool isWorktree); void onGitWorktreesListed(const QVariantList& items); void onWorktreeAdded(bool ok, const QString& error, const QString& root, const QString& name);
private: // NOLINT(readability-redundant-access-specifiers) - members must // live outside the `private slots:` region for moc to parse them. // Pending workspace-kind detection bookkeeping: one entry per identify() // echo path. detect* launches probes whose results come back keyed by // the requesting path, so concurrent detections (detectAllWorkspaces) // stay distinguishable. Several workspaces can share one path (jj // workspaces of the same repo), so the entry tracks EVERY pending // index for that path; identify results apply to all of them. // jjFound records the jj probe outcome once known so the (possibly // later) git result defers to jj when jj already claimed the // workspaces. The pending flags track which bridge results are still // outstanding (shared across the entry: all indices for a path are // probed together). struct PendingDetect { QList<int> indexes; bool jjPending = false; bool gitPending = false; bool jjFound = false; }; QHash<QString, PendingDetect> m_pendingDetects; // Pick-dialog session state. QString m_pickMode; QString m_pickRepoRoot; QVariantList m_pickItems; // "Add another" flow: a memorable-name add in flight ("jj" | "git"). QString m_pendingAddMode; // Optimistic-rename rollback bookkeeping. int m_renameTarget = -1; QString m_renameOldName; // forget-in-jj bookkeeping. int m_forgetTarget = -1;
// Detection internals: look up the active workspace (or nullptr) and // release the path-keyed pending entry once no probe is outstanding. [[nodiscard]] static const Workspace* activeWorkspace(); [[nodiscard]] static const Workspace* workspaceAt(int index); void maybeFinishDetect(const QString& path, const PendingDetect& pending); // The greatest common directory of the workspace's panes' live cwds // (the model's pane records; falls back to spawn cwds). "" when the // workspace has no panes or no common root. Mirrors the sidebar // preview's QML inference — this C++ copy exists so detection can // promote the root onto the workspace path. [[nodiscard]] static QString inferWorkspaceRoot(const Workspace& ws);
JjBridge* m_jjBridge; GitBridge* m_gitBridge;};