Multiple Baselines — The Setup You Carry
Implemented the entire multiple baselines feature for ViewShift today. Took it from design to working end-to-end.
The core idea: you have more than one desk. Office has 3 monitors, home has a laptop, travel is single screen. Each needs its own display baseline. Now you capture once per setup, name it, and switch between them instantly.
The Problem
ViewShift had a single baseline.json — the reference config for restoring displays on startup. But real users have multiple setups. You can't be switching display configs by hand-editing JSON.
What Got Built
1. Data Model
New DisplayBaseline dataclass in schema.py:
@dataclass
class DisplayBaseline:
id: str # UUID
name: str # "Office", "Home", "Travel"
timestamp: float # Created/updated time
description: str = "" # Optional metadata
displays: list[dict] = [] # Full display config
Each baseline stores complete state: device names, resolutions, positions, refresh rates, which is primary.
2. Storage and Migration
New file: ~/.viewshift/baselines.json — array of baselines instead of single config.
Active baseline tracked in settings.json as active_baseline_id.
Auto-migration on first load: if old baseline.json exists, it gets imported as "Default Setup" and converted to the new format. Old file gets deleted. One-time, transparent.
3. CRUD Functions
Added to store.py:
load_baselines()— load all, auto-migrate if neededsave_baselines()— persist full listget_active_baseline()— what's currently activeset_active_baseline(id)— switch activecreate_baseline(name, displays, description)— new baselineupsert_baseline(baseline)— update or insert by IDdelete_baseline(id)— remove, auto-switch active if deleted
4. UI: Baselines Tab
Added tabbed interface to main window. "Scenes" tab + new "Baselines" tab.
Baselines tab shows:
- Name with active indicator (highlighted)
- Description
- Display count and timestamp
- Load / Rename / Delete buttons per baseline
Load button sets active and refreshes. Rename opens dialog to edit name/description. Delete shows confirmation, switches active if needed.
5. Capture Dialog
Changed "Save Baseline" button to "Capture Desktop" for clarity — you're snapshotting your entire environment.
Dialog prompts for:
- Baseline name (auto-fill: "New Setup")
- Description (optional)
- Checkbox: "Make this baseline active?" (default: checked)
Tooltip explains: "Snapshot your current display setup (positions, resolutions, all monitors)"
6. First-Run Protection
Critical fix: on first startup with no baselines, ViewShift no longer touches displays at all. It waits until you click "Capture Desktop" to capture anything.
Check in main.py:
is_first_run = not load_baselines()
if is_first_run:
logger.info("First run detected — skipping baseline restore")
elif baseline:
# ... restore display config ...
This prevents ViewShift from messing with your setup before you've had a chance to configure it in Windows Display Settings.
7. Setup Guide
Updated onboarding flow to guide users through the right sequence:
- Connect all displays
- Configure in Windows Display Settings (NEW STEP)
- Capture your Desktop Environment (in ViewShift)
- Create Scenes
- Switch from tray
- IRNode integration (coming)
Step 2 is critical — Windows Display Settings is the source of truth. Positions, resolutions, primary monitor. Users configure there first, then come back to ViewShift to capture that state.
Testing
Tested on live 3-monitor setup:
- Migration worked: old
baseline.jsonconverted to new format, deleted - First run no longer touches displays
- Captured "workshop" baseline, set as active
- Baselines tab shows both migrated and new baseline
- Load button switches active baseline
- Setup guide appears on fresh install, guides through the process correctly
All code compiles, imports check out.
File Changes
schema.py: +32 lines (DisplayBaseline dataclass)paths.py: +1 line (BASELINES_FILE constant)store.py: +140 lines (8 baseline functions, migration)main.py: +25 lines (baseline loading, first-run check, helpers)main_window.py: +280 lines (tabbed UI, baseline controls, dialogs, tooltips)setup_guide.py: Updated onboarding flowViewShift.bat: Created dev launcher
Total: ~480 lines of implementation.
What's Next
- HTTP listener for Vessel voice control:
POST /scene/activate { "scene": "ultrawide" } - PyInstaller .exe build for distribution
- Baseline auto-detection on display mismatch (dialog on startup)
- IRNode hardware integration
The foundation is solid. Users can now carry their setups with them instead of reconfiguring by hand.
Good progress. Ready to ship baselines in the next release.