Phase K — Surface Isolation, Multi-Monitor & Display Management

Architectural Plan — Mcaster1Studio v0.2.0 — Created 2026-03-10

Goal: Transform Mcaster1Studio from a single-database, single-window application into a true multi-surface broadcast suite where each surface operates as an independent broadcast entity with its own database, settings, media library, and display output — while sharing system hardware resources (audio devices, capture cards, monitors).

1. Architecture Overview

Current Architecture (Pre-K)

Target Architecture (Post-K)

Conceptual Diagram

┌─────────────────────────────────────────────────────────────────────────────────┐
│  SYSTEM LAYER (shared)                                                          │
│  ┌──────────┐  ┌──────────┐  ┌──────────────┐  ┌───────────────────────────┐   │
│  │PortAudio │  │ Monitor  │  │ Capture      │  │ DB Server Registry        │   │
│  │ Devices  │  │ Manager  │  │ Device Mgr   │  │  [Server A] [Server B]... │   │
│  └──────────┘  └──────────┘  └──────────────┘  └───────────────────────────┘   │
├─────────────────────────────────────────────────────────────────────────────────┤
│  SURFACE LAYER (isolated per surface)                                           │
│  ┌─────────────────────────┐  ┌─────────────────────────┐                      │
│  │  Surface Alpha          │  │  Surface "GRR FM"       │        ...Nth        │
│  │  ┌─────────────────┐   │  │  ┌─────────────────┐   │                      │
│  │  │ DB: Server A     │   │  │  │ DB: Server B     │   │                      │
│  │  │ Schema: alpha_db │   │  │  │ Schema: grrfm_db │   │                      │
│  │  ├─────────────────┤   │  │  ├─────────────────┤   │                      │
│  │  │ Media Library    │   │  │  │ Media Library    │   │                      │
│  │  │ Playlists        │   │  │  │ Playlists        │   │                      │
│  │  │ Encoder Configs  │   │  │  │ Encoder Configs  │   │                      │
│  │  │ Metadata Presets │   │  │  │ Metadata Presets │   │                      │
│  │  │ Surface Settings │   │  │  │ Surface Settings │   │                      │
│  │  └─────────────────┘   │  │  └─────────────────┘   │                      │
│  │  Window: Embedded Tab   │  │  Window: Monitor 2     │                      │
│  └─────────────────────────┘  └─────────────────────────┘                      │
└─────────────────────────────────────────────────────────────────────────────────┘

2. Deliverable K.1 — Database Server Registry

Concept: System Preferences gains a "Database Servers" management panel where users can define multiple named database connections. Each server entry has a unique name (e.g. "Local SQLite", "Production MySQL", "Backup MariaDB") and connection parameters. Surfaces reference servers by name, not by raw connection strings.

K.1a — DbServerEntry struct

File: Core/include/DbServerEntry.h (NEW)

#pragma once
#include <QString>
#include <QList>
#include <QSettings>
#include <QUuid>

namespace M1 {

struct DbServerEntry {
    QString   id;            ///< Unique ID (UUID, generated on creation)
    QString   displayName;   ///< "Local SQLite", "Production MySQL", etc.

    enum class Backend { SQLite, MySQL };
    Backend   backend = Backend::SQLite;

    // SQLite-specific
    QString   sqlitePath;    ///< Empty = AppData default location

    // MySQL-specific
    QString   host     = "127.0.0.1";
    int       port     = 3306;
    QString   username = "root";
    QString   password;

    bool isSQLite() const { return backend == Backend::SQLite; }
    bool isMySQL()  const { return backend == Backend::MySQL;  }

    /// Generate a unique ID for a new server entry
    static QString newId() { return QUuid::createUuid().toString(QUuid::WithoutBraces); }
};

/// DbServerRegistry — singleton managing all configured database servers.
/// Persisted in QSettings under "dbservers/" prefix.
class DbServerRegistry {
public:
    static DbServerRegistry& instance();

    QList<DbServerEntry>     servers() const;
    const DbServerEntry*      findById(const QString& id) const;
    const DbServerEntry*      findByName(const QString& name) const;
    const DbServerEntry*      defaultServer() const;   ///< First entry, or built-in SQLite

    void addServer(const DbServerEntry& entry);
    void updateServer(const DbServerEntry& entry);
    void removeServer(const QString& id);
    void setDefaultServerId(const QString& id);

    void loadFromSettings();
    void saveToSettings() const;

    /// Test connection to a server. Returns empty string on success, error message on failure.
    QString testConnection(const DbServerEntry& entry) const;

private:
    DbServerRegistry();
    QList<DbServerEntry> m_servers;
    QString               m_defaultServerId;
};

} // namespace M1

K.1b — QSettings Key Layout

KeyTypeDescription
dbservers/countintNumber of configured servers
dbservers/defaultstringUUID of default server
dbservers/{i}/idstringServer UUID
dbservers/{i}/namestringDisplay name
dbservers/{i}/backendstring"sqlite" or "mysql"
dbservers/{i}/sqlite/pathstringSQLite file path (empty=default)
dbservers/{i}/mysql/hoststringMySQL hostname
dbservers/{i}/mysql/portintMySQL port
dbservers/{i}/mysql/userstringMySQL username
dbservers/{i}/mysql/passstringMySQL password (encrypted v2)

K.1c — First-Run Default

On first launch (no dbservers/count key), the registry auto-creates one entry:
{ id: auto-uuid, name: "Local SQLite (Default)", backend: SQLite, sqlitePath: "" }
This preserves backward compatibility — existing single-DB users see no change.

K.1d — Migration from Old Settings

Upgrade path: If database/backend exists (old format) and dbservers/count does not, migrate the old settings into a single DbServerEntry and remove the legacy keys. This runs once at startup in DbServerRegistry::loadFromSettings().

3. Deliverable K.2 — Per-Surface Database Isolation

Concept: Each surface stores a dbServerId + dbSchemaName in its session config. When MediaLibraryModule (or any DB-aware module) is created for a surface, it receives the surface’s DB config — NOT the global default. Surfaces sharing the same server but different schemas are fully isolated. Surfaces on different servers are even more isolated.

K.2a — Surface Database Config

New fields in session v2 QSettings:

KeyTypeDescription
session/v2/{si}/dbServerIdstringUUID of assigned DB server
session/v2/{si}/dbSchemaNamestringUnique schema/DB name (e.g. "mcaster1_alpha")
session/v2/{si}/dbTablePrefixstringTable prefix for shared-server isolation (e.g. "alpha_")

K.2b — SurfaceDbContext

File: Core/include/SurfaceDbContext.h (NEW)

namespace M1 {

/// Encapsulates the database identity for a single surface.
/// Passed to modules at creation time so they connect to the right DB/schema.
struct SurfaceDbContext {
    QString   serverId;      ///< DbServerEntry UUID
    QString   schemaName;    ///< Database name (MySQL) or file stem (SQLite)
    QString   tablePrefix;   ///< Optional prefix for table names in shared DBs

    /// Create an IDatabase* connected to this surface's database.
    /// Caller owns the returned pointer.
    IDatabase* createConnection() const;

    /// Generate default schema name from surface name.
    /// "GRR FM Broadcast" → "mcaster1_grr_fm_broadcast"
    static QString nameToSchema(const QString& surfaceName);

    bool isValid() const { return !serverId.isEmpty() && !schemaName.isEmpty(); }
};

} // namespace M1

K.2c — Schema Naming Convention

BackendSchema Name Maps ToExample
SQLiteSeparate .db file in AppData dirAppData/Mcaster1Studio/mcaster1_alpha.db
MySQLCREATE DATABASE IF NOT EXISTS `{schemaName}`mcaster1_grr_fm_broadcast

K.2d — Extended Schema (Beyond media_items)

Each surface database gets its own set of tables:

K.2e — Module Database Injection

// In MainWindow::createModule(), before module->initialize():
if (auto* dbAware = qobject_cast<IDbAwareModule*>(mod)) {
    dbAware->setSurfaceDbContext(surface->dbContext());
}
New interface: IDbAwareModule (optional mixin) — modules that need database access implement this. MediaLibraryModule, PlaylistModule, EncoderModule, MetadataModule, and PodcastModule variants all become IDbAwareModule implementors.

K.2f — YAML Failover Mirror

Redundancy pattern: Every surface writes a shadow YAML file alongside its DB: AppData/Mcaster1Studio/surfaces/{surfaceName}.yaml
Contains: surface identity, module list, encoder configs, last-known metadata presets.
On startup: if DB connection fails → read from YAML fallback → operate in read-only mode with a banner warning “Database offline — running from local cache”.
YAML sync: written after every saveAllSessionState() call (debounced with the 1.5s timer).

4. Deliverable K.3 — Surface Creation Wizard

Concept: When creating a new custom surface, a multi-step wizard dialog guides the user through: naming, database server selection, schema creation, and module selection. Replaces the current simple CustomSurfaceDialog.

Wizard Steps

StepTitleContents
1Surface IdentitySurface name (used for tab label + schema name), surface type dropdown (or "Custom"), icon color picker
2Database Server Dropdown of all servers from DbServerRegistry + "Add New Server..." button.
Schema name auto-generated from surface name (editable).
"Test Connection" button.
Checkbox: "Create database and tables now" (default: checked).
3Module Selection Dual-list or checkbox grid of available modules.
Pre-populated from surface type template (if not Custom).
"Select All" / "Clear" buttons.
4Confirmation Summary: surface name, DB server, schema, selected modules.
"Create Surface" button → creates DB schema + tables, creates surface, adds modules.

File: UI/SurfaceCreationWizard.h/.cpp (NEW)

class SurfaceCreationWizard : public QWizard {
    Q_OBJECT
public:
    struct Result {
        QString         surfaceName;
        M1::SurfaceType surfaceType;
        QString         dbServerId;
        QString         dbSchemaName;
        QStringList     moduleIds;
        QColor          tabColor;
    };

    explicit SurfaceCreationWizard(QWidget* parent = nullptr);
    Result result() const;

private:
    QWizardPage* buildIdentityPage();
    QWizardPage* buildDatabasePage();
    QWizardPage* buildModulesPage();
    QWizardPage* buildConfirmPage();
};

5. Deliverable K.4 — Pop-Out Surface Windows & Multi-Monitor

Concept: Right-clicking a surface tab shows "Pop Out Window" which detaches the surface into its own QMainWindow that can be resized, maximized, minimized, moved to any monitor, and made fullscreen (F11). The surface tab in the main window becomes a placeholder with a "Bring Back" button. When the popped-out window is closed, the surface returns to its tab.

K.4a — SurfaceWindow class

File: UI/SurfaceWindow.h/.cpp (NEW)

/// A standalone QMainWindow hosting a single popped-out SurfaceWidget.
/// Fully resizable, movable to any monitor, supports maximize/minimize/fullscreen.
class SurfaceWindow : public QMainWindow {
    Q_OBJECT
public:
    explicit SurfaceWindow(SurfaceWidget* surface, QWidget* parent = nullptr);
    ~SurfaceWindow() override;

    SurfaceWidget* surface() const { return m_surface; }

    /// Move this window to a specific monitor by QScreen index.
    void moveToScreen(int screenIndex);

    /// Snap to a monitor's full geometry (maximized on that monitor).
    void maximizeOnScreen(int screenIndex);

signals:
    void dockBackRequested(SurfaceWidget* surface);  ///< User wants to re-embed in main tab

protected:
    void closeEvent(QCloseEvent* event) override;
    void keyPressEvent(QKeyEvent* event) override;   ///< F11 = fullscreen toggle

private:
    SurfaceWidget* m_surface;
    QAction*       m_fullscreenAction = nullptr;
    bool           m_isFullscreen     = false;
};

K.4b — Pop-out / dock-back flow

// In SurfaceTabBar right-click menu:
auto* popOutAct = menu.addAction("Pop Out Window");
auto* sendToMonAct = menu.addMenu("Send to Monitor");

// Pop out:
1. SurfaceWidget* sw = surfaceAt(tabIndex);
2. m_surfaceBar->removeTab(tabIndex);        // Remove from tab widget
3. Insert placeholder tab: "⬒ {name} (popped out)" with "Bring Back" button
4. auto* win = new SurfaceWindow(sw);
5. win->show();
6. m_poppedOutWindows[sw] = win;

// Dock back (close window or right-click "Dock Back"):
1. SurfaceWindow emits dockBackRequested(sw)
2. Remove placeholder tab
3. Re-insert sw into m_surfaceBar at original index
4. Delete SurfaceWindow
5. m_poppedOutWindows.remove(sw)

K.4c — "Send to Monitor" submenu

Dynamically populated from QGuiApplication::screens(): Clicking a monitor: pops out the surface AND moves the window to that monitor, maximized.

K.4d — Window Flags & Behavior

FeatureImplementation
ResizableDefault QMainWindow flags (all edges + corners)
Maximize/MinimizeStandard title bar buttons
Fullscreen (F11)showFullScreen() / showNormal() toggle
Multi-monitor dragNative OS window management (no custom handling needed)
Title bar"Mcaster1Studio — {Surface Name}"
Close behaviorEmits dockBackRequested — does NOT destroy the surface
Session savePopped-out state + monitor index + geometry saved to QSettings
Session restoreOn app startup, restore popped-out surfaces to their saved monitors

K.4e — Session Persistence for Pop-Out Windows

QSettings KeyTypeDescription
session/v2/{si}/poppedOutboolWhether this surface is in a pop-out window
session/v2/{si}/window/screenIndexintWhich monitor the window is on
session/v2/{si}/window/xintWindow X position
session/v2/{si}/window/yintWindow Y position
session/v2/{si}/window/widthintWindow width
session/v2/{si}/window/heightintWindow height
session/v2/{si}/window/maximizedboolWhether window was maximized
session/v2/{si}/window/fullscreenboolWhether window was fullscreen

6. Deliverable K.5 — Monitor & Display Management

Concept: System Preferences gains a "Displays & Capture" page that shows all attached monitors, their properties, and video capture devices. This enables intelligent surface placement, video input selection for church/entertainment surfaces, and virtual camera output.

K.5a — MonitorManager class

File: Core/include/MonitorManager.h (NEW)

namespace M1 {

struct MonitorInfo {
    int       index;           ///< QScreen index (0-based)
    QString   name;            ///< "DELL U2722D", "\\.\DISPLAY1"
    QString   manufacturer;    ///< "Dell Inc."
    QSize     resolution;      ///< Native resolution (e.g. 2560x1440)
    QSize     physicalSizeMm;  ///< Physical size in millimeters
    double    refreshRate;     ///< Hz (e.g. 60.0, 144.0)
    double    dpi;             ///< Effective DPI
    double    scaleFactor;     ///< Qt device pixel ratio
    QRect     geometry;        ///< Virtual desktop position + size
    QRect     availableGeometry; ///< Geometry minus taskbar
    bool      isPrimary;       ///< Primary display?
    QString   gpuName;         ///< GPU driving this display (e.g. "NVIDIA RTX 4070")
    qint64    gpuVramBytes;    ///< GPU VRAM in bytes
};

struct CaptureDeviceInfo {
    QString   deviceId;        ///< System device path / ID
    QString   displayName;     ///< "Logitech C920", "AVerMedia Live Gamer"
    QSize     maxResolution;   ///< Maximum capture resolution
    double    maxFps;          ///< Maximum framerate
    bool      isVirtualCamera; ///< OBS Virtual Camera, etc.
    enum class Type { Webcam, CaptureCard, ScreenCapture, VirtualCamera };
    Type      type;
};

class MonitorManager : public QObject {
    Q_OBJECT
public:
    static MonitorManager& instance();

    QList<MonitorInfo>       monitors() const;
    QList<CaptureDeviceInfo> captureDevices() const;

    const MonitorInfo* monitorAt(int index) const;
    const MonitorInfo* primaryMonitor() const;

    /// Refresh monitor list (call after display configuration changes)
    void refresh();

    /// Refresh capture device list (Windows: DirectShow/MediaFoundation enumeration)
    void refreshCaptureDevices();

signals:
    void monitorsChanged();        ///< Display added/removed/resolution changed
    void captureDevicesChanged();   ///< USB device plugged/unplugged

private:
    MonitorManager(QObject* parent = nullptr);
    void enumerateMonitors();
    void enumerateCaptureDevices();
    void enumerateGpuInfo();       ///< DXGI adapter enumeration (Windows)

    QList<MonitorInfo>       m_monitors;
    QList<CaptureDeviceInfo> m_captureDevices;
};

} // namespace M1

K.5b — GPU Enumeration (Windows)

// DXGI adapter enumeration for GPU info
#include <dxgi1_4.h>
#pragma comment(lib, "dxgi.lib")

IDXGIFactory1* factory = nullptr;
CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory);

IDXGIAdapter1* adapter = nullptr;
for (UINT i = 0; factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND; ++i) {
    DXGI_ADAPTER_DESC1 desc;
    adapter->GetDesc1(&desc);
    // desc.Description  → GPU name (wchar_t[128])
    // desc.DedicatedVideoMemory → VRAM in bytes
    // desc.VendorId → 0x10DE (NVIDIA), 0x1002 (AMD), 0x8086 (Intel)
    adapter->Release();
}
factory->Release();

K.5c — Capture Device Enumeration (Windows)

Primary: MediaFoundation (MFEnumDeviceSources with MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID).
Fallback: DirectShow (ICreateDevEnum + CLSID_VideoInputDeviceCategory).
For each device: read friendly name, max output resolution, max FPS.
Virtual cameras detected by name heuristic ("OBS Virtual Camera", "ManyCam", etc.) or by checking for KSCATEGORY_VIDEO_CAMERA vs custom categories.

K.5d — PreferencesDialog "Displays & Capture" Tab

┌────────────────────────────────────────────────────────────────────────┐
│  DISPLAYS & CAPTURE                                                    │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│  Monitors                                                [↻ Refresh]  │
│  ┌──────────────────────────────────────────────────────────────────┐ │
│  │ # │ Name              │ Resolution  │ Hz   │ DPI │ GPU          │ │
│  │ 1 │ DELL U2722D ★     │ 2560×1440   │ 60   │ 109 │ RTX 4070    │ │
│  │ 2 │ Samsung LU28E570  │ 3840×2160   │ 60   │ 157 │ RTX 4070    │ │
│  │ 3 │ AVerMedia C1      │ 1920×1080   │ 60   │ 96  │ Intel UHD   │ │
│  └──────────────────────────────────────────────────────────────────┘ │
│                                                                        │
│  Video Capture Devices                                   [↻ Refresh]  │
│  ┌──────────────────────────────────────────────────────────────────┐ │
│  │ Device              │ Type         │ Max Res    │ Max FPS │ Virt │ │
│  │ Logitech C920       │ Webcam       │ 1920×1080  │ 30      │      │ │
│  │ AVerMedia Live G.   │ Capture Card │ 1920×1080  │ 60      │      │ │
│  │ OBS Virtual Camera  │ Virtual Cam  │ 1920×1080  │ 30      │  ✓   │ │
│  └──────────────────────────────────────────────────────────────────┘ │
│                                                                        │
│  GPU Information                                                       │
│  ┌──────────────────────────────────────────────────────────────────┐ │
│  │ NVIDIA GeForce RTX 4070 — 12,288 MB VRAM                        │ │
│  │ Intel UHD Graphics 770   — 512 MB shared                        │ │
│  └──────────────────────────────────────────────────────────────────┘ │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

K.5e — QScreen Change Detection

MonitorManager connects to QGuiApplication::screenAdded, screenRemoved, and per-screen geometryChanged signals to auto-refresh the monitor list. If a popped-out surface’s monitor is disconnected, the window is automatically moved back to the primary monitor with a notification toast.

7. Deliverable K.6 — PreferencesDialog Redesign

Updated Tab Structure

TabContentsStatus
1. GeneralTheme, language, startup behaviorExisting
2. Audio DevicesPortAudio device selection, sample rate, buffer sizeExisting
3. Database ServersNEW: Server registry list + Add/Edit/Remove/Test/Set DefaultNew
4. Displays & CaptureNEW: Monitor list, capture devices, GPU infoNew
5. Metrics / GrafanaPrometheus endpoint config, Grafana Cloud pushExisting

Database Servers Tab Layout

┌────────────────────────────────────────────────────────────────────────┐
│  DATABASE SERVERS                                                      │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│  [+ Add Server]  [✎ Edit]  [✕ Remove]  [★ Set Default]  [⚡ Test]    │
│                                                                        │
│  ┌──────────────────────────────────────────────────────────────────┐ │
│  │ ★ │ Name                     │ Backend │ Host       │ Status    │ │
│  │ ★ │ Local SQLite (Default)   │ SQLite  │ (embedded) │ ● OK     │ │
│  │   │ Production MySQL         │ MySQL   │ db.grr.fm  │ ● OK     │ │
│  │   │ Backup MariaDB           │ MySQL   │ backup.lan │ ● Offline│ │
│  └──────────────────────────────────────────────────────────────────┘ │
│                                                                        │
│  [Add Server] dialog:                                                  │
│  ┌──────────────────────────────────────────────┐                     │
│  │  Name: [________________________]            │                     │
│  │  Backend: ( ) SQLite  (•) MySQL/MariaDB      │                     │
│  │                                              │                     │
│  │  ── MySQL Settings ──────────────────────    │                     │
│  │  Host: [db.grr.fm_____________]              │                     │
│  │  Port: [3306___]                             │                     │
│  │  User: [mcaster1_______________]             │                     │
│  │  Pass: [••••••••_______________]             │                     │
│  │                                              │                     │
│  │  [Test Connection]        [Cancel] [Save]    │                     │
│  └──────────────────────────────────────────────┘                     │
│                                                                        │
│  Surface Assignments:                                                  │
│  ┌──────────────────────────────────────────────────────────────────┐ │
│  │ Surface Alpha        → Local SQLite   │ mcaster1_alpha          │ │
│  │ GRR FM Broadcast     → Production MySQL │ mcaster1_grr_fm       │ │
│  │ Church AV            → Local SQLite   │ mcaster1_church_av      │ │
│  └──────────────────────────────────────────────────────────────────┘ │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

8. Deliverable K.7 — Surface Tab Context Menu Enhancement

Updated Right-Click Menu Structure

┌─────────────────────────────────────┐
│  Add Module                      ▸  │
│  Create new Sub Surface          ▸  │
│  ──────────────────────────────────  │
│  Pop Out Window                     │   ◄── NEW: detach to QMainWindow
│  Send to Monitor                 ▸  │   ◄── NEW: submenu of detected monitors
│  ──────────────────────────────────  │
│  Database...                        │   ◄── NEW: view/change surface DB assignment
│  Surface Settings...                │   ◄── NEW: per-surface preferences
│  ──────────────────────────────────  │
│  Save Session                       │
│  Rename...                          │
│  Close Surface                      │
└─────────────────────────────────────┘

"Send to Monitor" Submenu

Dynamically built from MonitorManager::monitors():
  ┌──────────────────────────────────────────────────────┐
  │  ● Monitor 1: DELL U2722D — 2560×1440 (Primary)     │
  │  ● Monitor 2: Samsung LU28E570 — 3840×2160           │
  │  ● Monitor 3: AVerMedia C1 — 1920×1080               │
  └──────────────────────────────────────────────────────┘
  
Clicking a monitor: popOutSurface(sw) + win->maximizeOnScreen(idx).

9. Shared vs Isolated Resources

ResourceScopeNotes
Audio Devices (PortAudio)System (shared)All surfaces share the same audio I/O device
Audio Engine (AudioMixer)System (shared)Single RT callback dispatches to all surface modules
Monitors / DisplaysSystem (shared)Any surface can be sent to any monitor
Capture DevicesSystem (shared)Available to any surface; exclusive access per device
Theme / QSSSystem (shared)One active theme across all surfaces
Plugin RegistrySystem (shared)DLLs loaded once, available to all surfaces
Isolated per surface
Database ConnectionSurfaceEach surface has its own IDatabase* instance
Media LibrarySurfaceOwn media_items table in own schema
PlaylistsSurfaceOwn playlists + playlist_items tables
Encoder ConfigurationsSurfaceOwn encoder_configs table
Metadata PresetsSurfaceOwn metadata_presets table
Broadcast LogSurfaceOwn broadcast_log table
Surface SettingsSurfaceOwn surface_settings key/value table
YAML Failover CacheSurfaceOwn YAML file in AppData/surfaces/
Window StateSurfaceOwn pop-out geometry, monitor, fullscreen state

10. File Manifest

FileActionDescription
New Files
Core/include/DbServerEntry.hNEWDbServerEntry struct + DbServerRegistry singleton
Core/src/DbServerRegistry.cppNEWRegistry implementation: CRUD, QSettings I/O, test, migration
Core/include/SurfaceDbContext.hNEWPer-surface DB identity (serverId + schemaName + prefix)
Core/src/SurfaceDbContext.cppNEWcreateConnection() factory, nameToSchema() helper
Core/include/IDbAwareModule.hNEWOptional mixin interface for DB-aware modules
UI/SurfaceCreationWizard.h/.cppNEWMulti-step QWizard for new surface creation
UI/SurfaceWindow.h/.cppNEWPop-out QMainWindow hosting a SurfaceWidget
UI/DbServerDialog.h/.cppNEWAdd/Edit dialog for a single DB server entry
Core/include/MonitorManager.hNEWMonitor + capture device + GPU enumeration
Core/src/MonitorManager.cppNEWDXGI + MF enumeration, QScreen tracking
Modified Files
Core/include/SurfaceConfig.hMODIFYUpdate DbConfig to reference DbServerEntry by ID
Core/CMakeLists.txtMODIFYAdd new source files, link dxgi.lib
UI/PreferencesDialog.h/.cppMODIFYReplace Database tab with DB Servers; add Displays & Capture tab
UI/SurfaceTabBar.cppMODIFYAdd Pop Out / Send to Monitor / Database context menu items
UI/SurfaceWidget.h/.cppMODIFYAdd dbContext() accessor, YAML failover write
UI/MainWindow.h/.cppMODIFYSurfaceWindow management, DB injection into modules, wizard wiring
Modules/MediaLibraryModule/MediaLibraryModule.cppMODIFYImplement IDbAwareModule, use SurfaceDbContext
UI/MainWindow.cpp (session save/load)MODIFYSave/restore pop-out window state + DB context per surface
Core/include/IDatabase.hMODIFYAdd extended schema methods (playlists, encoder_configs, etc.)
Modules/MediaLibraryModule/SqliteManager.h/.cppMODIFYExtended schema creation, table prefix support
Modules/MediaLibraryModule/DatabaseManager.h/.cppMODIFYExtended schema creation, table prefix support

11. Implementation Order

StepDeliverableDependenciesEst. Files
K.1DbServerEntry + DbServerRegistryNone2 new + 1 mod
K.2MonitorManager + GPU/capture enumNone2 new
K.3PreferencesDialog DB Servers tab + Displays tabK.1, K.23 mod + 1 new
K.4SurfaceDbContext + IDbAwareModuleK.13 new
K.5Per-surface DB injection into modulesK.44 mod
K.6SurfaceCreationWizardK.1, K.41 new + 1 mod
K.7SurfaceWindow (pop-out)K.21 new + 2 mod
K.8Tab context menu (Pop Out + Send to Monitor + Database)K.7, K.21 mod
K.9YAML failover mirrorK.52 mod
K.10Session save/restore for pop-out + DB contextK.7, K.51 mod
K.11Extended DB schema (playlists, encoder_configs, etc.)K.53 mod
K.12Old settings migration + first-run defaultsK.11 mod

12. Verification Checklist

13. Risk Mitigation

RiskImpactMitigation
MySQL server down at startupSurface can’t load media libraryYAML failover cache provides read-only operation with warning banner
Monitor disconnected while surface poppedWindow becomes invisibleQScreen::destroyed signal → auto-move to primary
Multiple surfaces on same MySQL serverSchema name collisionUUID-suffixed schema names + uniqueness check in wizard
Old settings migrationExisting users lose DB configAuto-migrate database/* keys to dbservers/0/* on first run
Pop-out window audio routingAudio might not work in popped windowAudio callback chain is surface-independent — modules are still in the RT chain regardless of window state
DXGI enumeration on non-WindowsCompile error on Linux/macOS#ifdef Q_OS_WIN guard; stub returns empty list on other platforms