Something went wrong. Try again.
The agentic engineering control plane for the posthuman future
Something went wrong. Try again.
27 kB · 522 lines
C
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523#pragma once
// The app model: a Quick-free singleton owning ALL app state — workspaces// (tagged-sum QObject hierarchy) → tabs (XenoTab) → split trees of pane// nodes (SplitNode) — plus the ActivePath (current selection/focus), the// id-keyed path index, the pane metadata table, and session persistence.// The QObject tree IS the model: mutators mutate it in place and QML binds// it directly. All structural mutations happen here and emit the coarse// signals the ordered views subscribe to.
#include <QHash>#include <QList>#include <QObject>#include <QString>#include <QStringList>#include <QVariantList>#include <QVariantMap>#include <QVector>
#include <QJsonArray>#include <QJsonObject>#include <QPointer>#include <QTimer>
#include <QtQml/qqmlregistration.h>
#include "SplitTree.h"#include "Workspace.h"
class QQmlEngine;class QJSEngine;
#include "XenoSettings.h"#include "FontCatalog.h"class PanePool;
// Where a pane or container node lives: the id-keyed path index's value.// The key is either a paneId or a containerId; `path` is the split-tree// child-index route from the tab root to that node (empty = tab root).struct NodeLocation { QString workspaceId; QString tabId; QVector<int> path;};
// The single CURRENT-selection/focus construct. Ids only, so structural// mutations (split/close/move/reorder) never rewrite it — only real focus// or selection moves do.struct ActivePath { QString workspaceId; QString tabId; QString paneId; // "" when nothing focused};
// Per-pane metadata (spawn state + live notes from the pool).struct PaneRecord { QString paneId; QString workspaceId; QString tabId; // Spawn state (session save + restore source). QString spawnCwd; QString spawnCommand; QStringList spawnInitialCommands; QString spawnTitle; bool restart = false; QStringList restartArgv; // Live notes (pool wiring). QString title; QString cwd; // Editor panes: the file, and the caret the pane opens at. editorLine and // editorColumn are transient: they seed one pane creation and are not // saved. editorDirty is transient too. pickerFilesOnly marks the chooser // the arm-editor split creates (files only, no terminal row). QString editorPath; int editorLine = 0; int editorColumn = 0; bool editorDirty = false; bool pickerFilesOnly = false;};
class AppModel : public QObject { Q_DISABLE_COPY_MOVE(AppModel) Q_OBJECT QML_ELEMENT QML_SINGLETON Q_ENUM(SplitAxis) Q_ENUM(SplitDirection) // Ordered list of workspaces (the model objects QML binds directly). Q_PROPERTY(QList<Workspace*> workspaces READ workspaces NOTIFY workspacesChanged) // ActivePath exposure: three NOTIFY id properties (paneId "" when // nothing focused). Selecting a workspace/tab repoints to that tab's // lastActivePaneId; closing the focused pane repoints to the absorbing // sibling (or the new active tab's pane on auto-close). Q_PROPERTY(QString activeWorkspaceId READ activeWorkspaceId NOTIFY activeWorkspaceChanged) Q_PROPERTY(QString activeTabId READ activeTabId NOTIFY activeTabChanged) Q_PROPERTY(QString focusedPaneId READ focusedPaneId NOTIFY focusedPaneChanged) // Model-owned services (constructed in the AppModel constructor). Q_PROPERTY(XenoSettings* settings READ settings CONSTANT) Q_PROPERTY(FontCatalog* fontCatalog READ fontCatalog CONSTANT) // True while a modal surface (mission control) owns the keyboard. // Pooled terminal items gate their key paths on this so a stray focus // hand-off cannot leak keystrokes into a pane beneath the overlay. Q_PROPERTY(bool missionControlOpen READ missionControlOpen WRITE setMissionControlOpen NOTIFY missionControlOpenChanged) // True while the focused pane is maximized (zoom + collapsed chrome). // Chrome = sidebar + tab strip. The model remembers ONLY what the // maximize itself changed (a sidebar that was already collapsed stays // collapsed after restore); restore never clobbers the user's manual // sidebar/tab-strip state. Not session-persisted (transient chrome). Q_PROPERTY(bool chromeMaximized READ chromeMaximized NOTIFY chromeMaximizedChanged)
public: // The QML singleton instance (also the constructor for tests). static AppModel* create(QQmlEngine* engine, QJSEngine* scriptEngine); static AppModel* instance();
explicit AppModel(QObject* parent = nullptr); ~AppModel() override;
// --- properties ----------------------------------------------------------- [[nodiscard]] const QList<Workspace*>& workspaces() const { return m_workspaces; } [[nodiscard]] XenoSettings* settings() const { return m_settings; } [[nodiscard]] FontCatalog* fontCatalog() const { return m_fontCatalog; } [[nodiscard]] bool missionControlOpen() const { return m_missionControlOpen; } void setMissionControlOpen(bool open); [[nodiscard]] bool chromeMaximized() const { return m_chromeMaximized; } [[nodiscard]] const QString& activeWorkspaceId() const { return m_activePath.workspaceId; } [[nodiscard]] const QString& activeTabId() const { return m_activePath.tabId; } [[nodiscard]] const QString& focusedPaneId() const { return m_activePath.paneId; }
// --- pool wiring ---------------------------------------------------------- // QMetaObject connects by signature; keeps models/ Quick-free at compile // time. The pool's paneTitleChanged/paneCwdChanged/paneCloseRequested // signals feed notePaneTitle/notePaneCwd/closePane. Q_INVOKABLE void attachPool(QObject* pool);
// --- startup -------------------------------------------------------------- // Load the session, or build the default workspace when none exists. // `startupCommands` seeds the default workspace's initialCommands, // `command` its spawn command, `workingDir` its cwd/path. Q_INVOKABLE void startup(const QVariantList& startupCommands, const QString& command, const QString& workingDir);
// --- workspace mutators ---------------------------------------------------- Q_INVOKABLE QString addWorkspace(WorkspaceKind kind, const QString& path, const QString& name, const QString& repoRoot); Q_INVOKABLE void deleteWorkspace(int index); Q_INVOKABLE void renameWorkspace(int index, const QString& name); Q_INVOKABLE void selectWorkspace(int index); Q_INVOKABLE void nextWorkspace(); Q_INVOKABLE void previousWorkspace(); Q_INVOKABLE void selectWorkspaceByNumber(int number); Q_INVOKABLE void setWorkspaceDirectory(int index, const QString& path); // JjWorkspace -> RepositoryWorkspace kind mutation: the workspace OBJECT // is replaced (new RepositoryWorkspace, same id, tabs moved over); QML // re-resolves through the list notification. Q_INVOKABLE void forgetJjWorkspace(int index); // Replace the workspace at `index` with a fresh object of `kind` (same // id/name/path, tabs stolen, old object deleteLater'd), carrying // `repoRoot`/`jjWorkspaceName` for the kinds that have them and copying // the old object's isJjRepo/isGitRepo capabilities. No-op when the kind // already matches. Public Q_INVOKABLE so the controllers can reseat after // (re)detection. Q_INVOKABLE void reseatWorkspaceKind(int index, WorkspaceKind kind, const QString& repoRoot, const QString& jjWorkspaceName); // Set the detected VCS capability flags on the workspace at `index`. // No-op on an invalid index or unchanged values; emits workspacesChanged // (Commands gating depends on it) and saves the session when anything // changed. Q_INVOKABLE void setWorkspaceRepoCapabilities(int index, bool isJjRepo, bool isGitRepo);
// --- tab mutators ---------------------------------------------------------- Q_INVOKABLE void newTab(int wsIndex); Q_INVOKABLE void closeTab(int wsIndex, int tabIndex); Q_INVOKABLE void selectTab(int wsIndex, int tabIndex); Q_INVOKABLE void nextTab(); Q_INVOKABLE void previousTab(); // Reorder: move the tab at (wsIndex, tabIndex) to `toIndex` inside the // same workspace (clamped; no-op at the ends or for <2 tabs). The // XenoTab objects travel unchanged; the active tab stays the SAME TAB // (its index follows the move). No pane records or trees are touched. Q_INVOKABLE void moveTab(int wsIndex, int tabIndex, int toIndex); // User title override for the tab at (wsIndex, tabIndex); empty clears. // While set, tab titles show the override and the reported title keeps // tracking underneath. No effect on order, focus, or pane state. Q_INVOKABLE void setTabTitleOverride(int wsIndex, int tabIndex, const QString& title);
// --- pane/tree mutators ----------------------------------------------------- Q_INVOKABLE void splitPane(const QString& paneId, SplitDirection direction); // String-based split/focus overloads for QML. The SplitDirection enum // values attached to the AppModel QML singleton do not resolve in QML // (probing reports "Up=undefined"), so QML callers route directions // through these instead; C++ callers keep using the enum versions. // `direction` accepts "splitLeft"/"splitRight"/"splitUp"/"splitDown" or // the spatial spellings "left"/"right"/"up"/"down" (unknown falls back // to Down, mirroring TerminalPageController::directionFromString). Q_INVOKABLE void splitPaneFromString(const QString& paneId, const QString& direction); // QML passes a kind word ("terminal"|"editor"|"picker"). An unknown word or // an unknown pane id is a no-op. An editor request with an empty `path` // becomes a picker (the pane-kind invariant). Q_INVOKABLE void setPaneKindFromString(const QString& paneId, const QString& kind, const QString& path = QString()); // Split `paneId` on `direction` and create the fresh pane with `kind`. // `path`/`line`/`column` seed an editor pane. Returns the fresh pane id, or // "" when the pane or the direction is unknown. Q_INVOKABLE QString splitPaneWithKind(const QString& paneId, const QString& direction, const QString& kind, const QString& path, int line, int column); // The id of the editor pane showing `path` in `paneId`'s tab, or "". Q_INVOKABLE QString editorPaneShowingPath(const QString& paneId, const QString& path) const; // True while this pane's editor holds unsaved edits. Q_INVOKABLE bool paneDirty(const QString& paneId) const; Q_INVOKABLE void notePaneDirty(const QString& paneId, bool dirty); // The pane's kind as its word ("terminal"|"editor"|"picker"), or "" for an // unknown id. A read surface for QML, the probe and the tests. Q_INVOKABLE QString paneKindOf(const QString& paneId) const; // True for the chooser the arm-editor split creates. Q_INVOKABLE bool panePickerFilesOnly(const QString& paneId) const; // Dirty editor panes of one tab, and of the whole session. Q_INVOKABLE QStringList dirtyEditorPanesInTab(int wsIndex, int tabIndex) const; Q_INVOKABLE QStringList dirtyEditorPanes() const; Q_INVOKABLE void closePane(const QString& paneId); Q_INVOKABLE void keepOnlyPane(const QString& paneId); Q_INVOKABLE void swapPanes(const QString& a, const QString& b); // Move `paneId` to sit beside `targetPaneId` on `direction` ("left"| // "right"|"up"|"down" relative to the target): the model-native pane // drag-drop op. Works across tabs and workspaces: the path index // resolves both panes' full (workspace, tab) locations, and an emptied // source tab auto-closes. No-op when either id is unknown, the ids // match, the dragged pane is its workspace's only tab's only pane, or // (same tab) the tree holds fewer than 2 panes. Emits structureChanged // and saves the session like every other tree mutator. Q_INVOKABLE void movePaneBeside(const QString& paneId, const QString& targetPaneId, const QString& direction); // Append `paneId` as the last pane of the tree at (wsIndex, tabIndex): // the append drop target. The destination is a tab, so it is addressed // by indices; every pane involved is resolved by id through the path // index. An emptied source tab auto-closes. Q_INVOKABLE void movePaneToTab(const QString& paneId, int wsIndex, int tabIndex); // Move `paneId` into a brand-new tab appended to workspace `wsIndex`: // the page-background drop target. No-op when the source workspace // would lose its last tab's only pane to a DIFFERENT workspace (same- // workspace drops are always safe: the new tab exists before the old // one closes). Q_INVOKABLE void movePaneToNewTab(const QString& paneId, int wsIndex); Q_INVOKABLE void focusPane(const QString& paneId); Q_INVOKABLE void focusDirection(const QString& paneId, SplitDirection dir); // String-based focus overload (see splitPaneFromString for why QML uses // strings for directions). Q_INVOKABLE void focusDirectionFromString(const QString& paneId, const QString& direction); // Focus-cycle helpers: step to the next/previous pane in the active // tab's layout order, wrapping around. Unknown/empty focus falls back // to the first pane in layout order (keeps shortcuts usable at boot). Q_INVOKABLE void focusNextPane(); Q_INVOKABLE void focusPreviousPane(); Q_INVOKABLE void revealPane(const QString& paneId); Q_INVOKABLE void setRatios(const QString& containerId, const QVariantList& ratios); // Balance every container in the ACTIVE tab's tree to equal 1/n shares // (nested trees equal at every level). No-op on a single-pane tab: the // root there is a >=1-child container, and equal shares ARE its current // shape — nothing to change, nothing emitted. Q_INVOKABLE void equalizeSplits(); Q_INVOKABLE void resetLayout();
// --- pane zoom -------------------------------------------------------------- // Zoom is per-tab transient view state on XenoTab (zoomedPaneId): the id // of the pane the tab renders alone. It NEVER mutates the split tree, // its ratios, the ActivePath, or the pane pool — clearing it restores // the exact previous layout. togglePaneZoom flips zoom on the focused // pane; maximizePane zooms the focused pane AND claims focus (a maximize // is a zoom with "this is the only thing I want now" semantics); both // clear an existing zoom on the same pane, and either one clearing a // zoom that a maximize rode also restores the collapsed chrome, so a // maximize never outlives its zoom. clearPaneZoom drops the // active tab's zoom (no-op when none). All three emit // zoomedPaneIdChanged via the tab and save nothing (transient state). Q_INVOKABLE void togglePaneZoom(); Q_INVOKABLE void maximizePane(); Q_INVOKABLE void clearPaneZoom(); // The active tab's zoomed pane id ("" when none / no active tab). Q_INVOKABLE QString activeZoomedPaneId() const; // Chrome maximize (sidebar + tab strip collapse) and its restore. Only // chrome the maximize itself hid is restored; see the property note. void maximizeChrome(); void unmaximizeChrome(); // Tab-strip visibility is a model-owned flag (QML binds it) — the // sidebar's collapsed state lives in XenoSettings, but the tab strip // has no persisted equivalent and must not gain one. Q_PROPERTY(bool tabStripHidden READ tabStripHidden NOTIFY chromeMaximizedChanged) [[nodiscard]] bool tabStripHidden() const { return m_tabStripHidden; } // Test hook: pre-hide the tab strip WITHOUT a maximize so the restore // contract (only-what-we-changed) is exercisable. Not compiled into // QML-facing behavior. void setTabStripHiddenForTest(bool hidden) { m_tabStripHidden = hidden; }
// --- pool notes ------------------------------------------------------------- Q_INVOKABLE void notePaneTitle(const QString& paneId, const QString& title); Q_INVOKABLE void notePaneCwd(const QString& paneId, const QString& cwd); // Record the quit-time restart scan result for one pane (the pool's // runRestartScan calls this). No-op for an unknown paneId. void setPaneRestart(const QString& paneId, bool restart, const QStringList& argv);
// --- reads for QML ---------------------------------------------------------- Q_INVOKABLE QVariantMap tabLayout(int wsIndex, int tabIndex); Q_INVOKABLE QVariantList jumpCandidates() const; Q_INVOKABLE QString paneTitle(const QString& paneId) const; Q_INVOKABLE QString tabDisplayTitle(int wsIndex, int tabIndex) const; // The spawn state (cwd, command, initialCommands, title, restart, // restartArgv) for `paneId`, as a fully-keyed map with empty values for // an unknown id. TerminalPane.start() reads it once at creation so // session-restored panes spawn with their saved cwd/command/restart // state. // The directory a chooser pane lists: the pane's live cwd, else its spawn // cwd, else its workspace's path, else the home directory. A pane whose cwd // was never noted still belongs to a workspace, and listing that beats // listing the home directory by accident. Q_INVOKABLE [[nodiscard]] QString paneListingRoot(const QString& paneId) const;
// The pane's workspace as the backend word the repo chooser wants: "jj" // for a jj workspace, "git" for a repository or a worktree, and "" for a // directory workspace or an unknown pane. Together with paneWorkspacePath // it tells the chooser whether to search the repository or walk paths. Q_INVOKABLE [[nodiscard]] QString paneRepoBackend(const QString& paneId) const; // The pane's workspace path, or "" for an unknown pane. The repo chooser // roots its relative file list here. Q_INVOKABLE [[nodiscard]] QString paneWorkspacePath(const QString& paneId) const;
Q_INVOKABLE [[nodiscard]] QVariantMap paneSpawnSpec( const QString& paneId) const;
// Pane ids of the ACTIVE tab in layout order (empty when there is no // active tab/tree). Pure logic over tabTree + paneIdsInLayoutOrder; the // PanePool uses it to decide which panes stream. Q_INVOKABLE [[nodiscard]] QStringList activePaneIds() const;
// Pane ids of every workspace's ACTIVE tab, in layout order. This is the // set whose daemon streams stay attached while a preview surface is // visible: the sidebar cards and the mission-control workspace cards // sample each workspace's active-tab tree, so those panes must keep // receiving output while backgrounded. Q_INVOKABLE [[nodiscard]] QStringList streamingPaneIds() const;
// Session persistence (500 ms debounce; same as today's persistTimer). void saveSessionSoon();
// --- test/driver access (not Q_INVOKABLE; C++ only) ------------------------ [[nodiscard]] const ActivePath& activePath() const { return m_activePath; } [[nodiscard]] const QHash<QString, NodeLocation>& pathIndex() const { return m_paths; } [[nodiscard]] const QHash<QString, PaneRecord>& panes() const { return m_panes; } // Container id -> containerId registry lookups for the controllers/tests. [[nodiscard]] int workspaceIndexOf(const QString& workspaceId) const; [[nodiscard]] int tabIndexOf(const QString& workspaceId, const QString& tabId) const; // The split tree of (wsIndex, tabIndex); nullptr when out of range. // Roots are always containers (the session loader wraps bare leaves). [[nodiscard]] SplitContainerNode* tabTree(int wsIndex, int tabIndex) const; // The pane record (spawn + live notes) for `paneId`; an empty record // when unknown. [[nodiscard]] PaneRecord paneRecord(const QString& paneId) const { return m_panes.value(paneId); } // Emit the whole session map (also the session-save entry point). // Q_INVOKABLE so the dev driver's session() probe can read it. Q_INVOKABLE [[nodiscard]] QVariantMap collectSession(bool scanQuit) const;
signals: // --- coarse (the ordered views + Commands subscribe to these) ------------- void workspacesChanged(); void workspaceDataChanged(int wsIndex); void activeWorkspaceChanged(int wsIndex); void tabsChanged(int wsIndex); void tabDataChanged(int wsIndex, int tabIndex); void activeTabChanged(int wsIndex, int tabIndex); void structureChanged(int wsIndex, int tabIndex); void focusedPaneChanged(const QString& paneId); // Modal keyboard ownership (mission control open/close). void missionControlOpenChanged(); // Chrome maximize entered/left (sidebar + tab strip collapsed). void chromeMaximizedChanged();
public slots: void paneTitleFromPool(const QString& paneId, const QString& title); void paneCwdFromPool(const QString& paneId, const QString& cwd); void paneCloseFromPool(const QString& paneId); // Quit-time save: buildSessionRoot(true) — the ONLY path that runs the // restart scan. Wired to QGuiApplication::aboutToQuit in main.cpp via a // NAME-BASED QMetaObject::invokeMethod (main.cpp has no models/ include // dir), so it MUST be a slot or Q_INVOKABLE to be invokable by name. void saveSessionForQuit();
private: // Session load/save. `fromJson` fills generated ids where absent. void loadSession(const QVariantMap& session); QVariantMap buildSessionRoot(bool scanQuit) const; void saveSessionNow();
// Invariant enforcement helpers (called by every mutator). void rebuildPathIndex(const QString& workspaceId, const QString& tabId); void normalizeTree(SplitNode& tree); // Container collapse cascade: walk the ancestors along `removedPath` // (a container path with the leaf index already taken off) and promote // any container left holding one child into ITS parent — the // >=2-children invariant. Shared by closePane and movePaneBeside. static void collapseSingletons(SplitContainerNode* tree, const QVector<int>& removedPath); void rebuildAllPathIndexes(); // The workspace a pane belongs to, resolved through its record's // workspaceId, or nullptr for an unknown pane. paneListingRoot and the two // repo-mode reads share this one traversal. [[nodiscard]] const Workspace* paneWorkspace(const QString& paneId) const; [[nodiscard]] QString firstPaneIdOfTab(int wsIndex, int tabIndex) const; void removeTabPaneRecords(int wsIndex, const XenoTab& tab); // Rewrite a pane record's live location (workspaceId/tabId) in place. // Spawn fields are untouched so the pane restores with its original // cwd and command. Used by the cross-tree move mutators before an // emptied-source closeTab so removeTabPaneRecords (an empty tree) // leaves the moved record alone. void rehomeRecord(const QString& paneId, const QString& workspaceId, const QString& tabId); // Kill the pool items for the given pane ids (QMetaObject::invokeMethod // on the attached pool's destroyPanes(QStringList) — keeps models/ // compile-time Quick-free). No-op without an attached pool (tests). void destroyPoolPanes(const QStringList& paneIds); // The one split implementation. `kind` is the fresh pane's kind; `path`, // `line` and `column` seed an editor pane. Returns the fresh pane id. QString splitPaneCore(const QString& paneId, SplitDirection direction, PaneKind kind, const QString& path, int line, int column); // The one kind mutation: rewrite the record payload, then the node kind. // The record is seeded BEFORE the node's NOTIFY, because that notify // rebuilds the view and the new view reads the record at creation (the // same seed-first rule splitPaneCore documents). void setPaneKind(const QString& paneId, PaneKind kind, const QString& path); // The kind a plain split of `paneId` creates: the source pane's own kind. // An editor source asks for an editor, and splitPaneCore turns an editor // request with no file into a file-only chooser. A picker source falls back // to a terminal. Unknown ids read as Terminal. [[nodiscard]] PaneKind defaultSplitKindFor(const QString& paneId) const; // Push the tab's computed display title (live pane title of the // remembered pane, else "") into its XenoTab so reactive QML bindings // on tab.title re-fire. Call wherever the title source changes: // pane title notes, focus moves, pane close repoints, rebuilds. void syncTabTitle(int wsIndex, int tabIndex); // Drop the zoom of any tab that references `paneId` (pane close/move // hygiene) and drop the zoom of every tab of (wsIndex, tabIndex) whose // zoomed pane vanished from that tree. No-op without a pool-free model. void clearZoomReferencing(int wsIndex, int tabIndex, const QStringList& removedPaneIds); // The pane a focus request must actually land on inside `tab`: the // tab's zoomed pane when the tab is zoomed and `paneId` is not it, else // `paneId`. A zoomed tab renders one pane alone, so its hidden siblings // must never hold the ActivePath — the keyboard would type into a // terminal nobody sees. Falls back to `paneId` when the zoom id is // stale (not a leaf of that tab's tree). [[nodiscard]] static QString zoomPinInTab(const XenoTab& tab, const QString& paneId);
QList<Workspace*> m_workspaces; QHash<QString, NodeLocation> m_paths; ActivePath m_activePath; QHash<QString, PaneRecord> m_panes; XenoSettings* m_settings; FontCatalog* m_fontCatalog; QPointer<QObject> m_pool; // 500 ms debounced autosave (created in the constructor; single-shot). QTimer* m_saveTimer = nullptr; // The singleton instance (set by the constructor, cleared by the dtor). static AppModel* s_instance; // The active workspace's index (ids are canonical; this is a cache for // the ordered views). int m_activeWorkspaceIndex = 0; // Modal keyboard ownership (mission control open). bool m_missionControlOpen = false; // Chrome-maximize state: what we changed and how to undo it. Only the // pieces the maximize itself flipped are recorded, so restore never // clobbers a sidebar/tab strip the user had already hidden themselves. bool m_chromeMaximized = false; // Pre-maximize snapshots: true means "it was EXPANDED and we collapsed // it"; false means it was already hidden and restore must leave it. bool m_restoreSidebar = false; bool m_restoreTabStrip = false; bool m_tabStripHidden = false;};