A Love Wins Media product

Engineering plans / Proposed

Garage for Windows — UI plan

The user interface half of windows.md. That plan covers the whole port (services, Python bridge, Postgres, packaging); this one covers the app people see:

Written against v1.5-beta (2454039) plus the windows/python-bridge work: winapp/, Garage.Python and the winapp CI job. Re-checked for parity against main (b82c6d2) and v1.5-beta (839bc39) on 2026-09-29; §8 records what changed and what the plan took from it.

The approach: port the Mac app’s structure page for page, and its presentation logic line for line, while the controls, layout and behaviour follow Windows conventions (windows.md §3). Build the UI against the gRPC contract that already exists, so it does not wait for the Windows service host: every page except ingest can be developed against garage serve running from a venv.


0. Scope

In scope:

Out of scope, in windows.md:

1. What the Mac UI is (grounding)

macapp/Sources/GarageApp, which the Windows UI mirrors:

Mac What
Views/ContentView.swift AppSection (10 pages) and SidebarGroup: Status on top, then Configuration (Sources, Models, MCP Server), Data (Documents, Facts, Graph, Search), Advanced (Database, Logs). A unit test holds the order
Views/*View.swift One view per page, split into extensions for the large ones (ModelsView+*, StatusView+Services, MCPServerView+TryIt)
Views/*Presentation.swift Plain presentation values with the page’s wording and state logic (StatusPagePresentation, SourcesPresentation, ModelsPresentation, MCPServerPresentation, DatabasePresentation, and others), each unit-tested
Views/MenuBar*.swift, Components/MenuBar* The menu-bar extra: status, quick search, Ask Garage (MenuBarAnswer: the local model’s answer from the rag_agent MCP tool, with the tool calls it made and its citations), navigation, Ingest Now, Stop
Views/StatusView.swift (Library box) Update Everything and Automatic Updates (scheduled maintenance), moved to Status from the Sources page; the launch run of automatic updates waits until every service is up
Views/FirstRunView.swift + Services/FirstRunCoordinator.swift Setup assistant: wait for services → pick sources → pick models → connect MCP clients
Views/DatabaseResetSheet.swift, BugReportView.swift, SplashView.swift, CheckForUpdatesButton.swift Overlays and dialogs
AppState.swift (+ +Facts, +LlamaModels) Shared state. Includes the ingest queue with cancelAll() / cancel(source:), updateEverything() (scan → ingest → embed → glean facts), scheduled maintenance, and database reset and relaunch
Services/OperationRunner.swift Runs an operation as a gRPC call with a busy flag and a rolling log; dedicated runners for backfill and enrich-facts
Services/GarageGRPCService+Operations.swift The typed gRPC calls every page uses

The contract is already there. proto/garage.proto covers every page except ingest:

Ingest has no RPC. On the Mac it runs in GarageIngestXPCService, which persists through the facade’s ingest-session RPCs, so on Windows it arrives with the service host’s Garage.Ingest (§4, phase U3).

Ask Garage is not gRPC either. The Mac’s GarageMCPService sends MCP JSON-RPC (initialize, then tools/call for rag_agent) to Garage’s own MCP server, and decodes the result into MenuBarAnswer. The Windows app does the same, so it needs an MCP endpoint as well as the gRPC channel (§2.3).

2. Architecture

winapp/
  src/Garage.Python/          (exists) CPython bridge
  src/Garage.Grpc/            generated GarageService client (proto/garage.proto via Grpc.Tools)
                              + the x-garage-token interceptor (GarageGRPCAuth's counterpart)
  src/Garage.App.Core/        net10.0, no UI references:
                                AppState, OperationRunner, IngestQueue, pipelines
                                view models (CommunityToolkit.Mvvm)
                                presentation types ported from Views/*Presentation.swift
                                IBackend: where the endpoints are (dev connection or service host)
  src/Garage.App/             net10.0-windows10.0.19041.0, WinUI 3:
                                MainWindow (NavigationView), pages, dialogs, tray icon and flyout,
                                first-run window, notifications, single instance, startup task
  tests/Garage.App.Core.Tests/    xUnit v3: presentations, view models, pipelines (fake backend)
  tests/Garage.App.UITests/       FlaUI UI automation, against a fake backend

2.1 Why a separate Core

2.2 State and threading

2.3 Backend connections

IBackend supplies the gRPC channel (address plus token), the MCP endpoint that Ask Garage calls rag_agent through, and service status. There are two implementations:

3. Pages and surfaces

Each row lists the Mac source it ports, the WinUI treatment, and the RPCs behind it.

Surface Mac source Windows RPCs
Shell ContentView NavigationView, left mode. Status first; NavigationViewItemHeaders for Configuration, Data, Advanced; Settings in the footer. Mica backdrop, custom title bar, Segoe Fluent Icons in place of SF Symbols. Keep the AppSection order test —
Status StatusView, +Services, StatusPagePresentation, IndexingPresentation, ServiceRowPresentation Summary cards. A Library box holds Update Everything and the Automatic Updates switch and schedule; the launch run waits until every service is up. An indexing progress InfoBar; service rows with self-test results (“7 passed, 1 skipped”) and icon-button actions with tooltips; quick add GetStatus, GetStats; service status from IBackend
Sources SourcesView, SourcesPresentation ListView of sources with per-row status and actions; Add Source through FolderPicker in a ContentDialog (slug, class, trust). Automatic Updates is no longer here; it moved to Status ListSources, AddSource, RemoveSource, Scan, SyncSources, Reconcile, ImportSourcesToConfig; ingest via IBackend
Models ModelsView + extensions, ModelsPresentation Model list with default and provider, row actions as icon buttons with tooltips; register-model dialog; embedding inspection; backfill with progress. Local llama models appear once Garage.Llama exists ListModels, RegisterModel, SetDefaultModel, DropModel, Backfill, EnsureLlamaModel
MCP Server MCPServerView, +TryIt, MCPServerPresentation, MCPClientRowPresentation Client rows (Claude Desktop, Claude Code, …) with Install / Update / Remove; stdio by default; the HTTP server behind an explicit toggle (garage.mcp.httpEnabled behaviour); “Try it” panel McpStatus, McpInstall, McpUninstall, Search
Documents DocumentsView, CorpusTaxonomy List/detail (TwoPaneView or split): filters by class and trust; the document’s chunks and authors ListDocuments, GetDocument
Facts FactsView, FactPromptsSection Fact counts per prompt and model; fact list with grounding spans; prompts section; “Glean facts” with progress GetFactStats, ListFacts, ListFactPrompts, EnrichFacts
Graph GraphView, GraphLayout, GraphPresentation, GraphItems Search on the left, the chosen vertex’s neighbourhood drawn on a Canvas with the Mac’s radial layout (click to inspect, double-click to center), and an inspector with connections and properties; label chips filter vertices and edges. Documents and Facts link in with Show in Graph, and the inspector’s Open Document links back GetGraphLabels, FindGraphVertices, GetGraphNeighborhood
Search SearchView AutoSuggestBox query, trust/class filters, results with excerpts; Ctrl+F from anywhere Search
Database DatabaseView, DatabasePresentation, DatabaseResetSheet Schema and contents summaries; Reset Database as a ContentDialog with typed confirmation, then relaunch (AppInstance.Restart) GetStats, InitDb; reset via IBackend
Logs LogsView, LogTableView Virtualized list with level filter and search. Under it, a status bar with the counts (“showing the latest …”) and the copy/export/clear actions, as the Mac’s table now has. Sources: the app log, then the services’ log streams (U3) — / service log streams
Settings (scattered in the Mac app) Footer page: backend and connection, launch at startup (StartupTask), notifications, update channel (stable or beta, as the Mac’s beta channel; windows.md §6 for the feeds), about and third-party notices. The maintenance schedule stays on Status with Automatic Updates GetSetting, SetSetting
Tray icon MenuBarView, MenuBarStatus, MenuBarQuickSearch, MenuBarAnswer, MenuBarNavigation Notification-area icon (H.NotifyIcon.WinUI). Left-click opens a flyout with status, quick search, Ask Garage, page links, Ingest Now and Stop. Ask shows the answer with the tool calls it took and citations that open the document; it is available when a generating model is configured (the Mac’s canAsk). Right-click opens a context menu (Open Garage, Pause maintenance, Quit). The icon reflects idle, working or error GetStatus, Search; MCP tools/call rag_agent
First run FirstRunView, FirstRunCoordinator Full-window assistant with a step header: services → sources (Windows templates: Documents, Desktop, OneDrive, a code folder) → models → MCP clients. Uses the same AppState operations as the pages as the pages
Bug report BugReportView, BugReport* Dialog that gathers logs and self-test output into a folder and opens it in Explorer; nothing is sent automatically —
Notifications — App notifications (AppNotificationManager) with progress for Update Everything, backfill and enrich-facts; completion and failure toasts —
Taskbar — Jump list: Search, Ask Garage, Add Source, Update Everything —
Tip jar (Store build only) TipJarView (on v1.5-beta, not yet on main) The Mac App Store build offers tips as in-app purchases. The Microsoft Store counterpart is Store add-ons (consumables) through Windows.Services.Store.StoreContext; the .appinstaller build shows no tip jar, as the Developer ID build has none Store API

Keyboard:

4. Phases

Estimates assume one engineer familiar with WinUI and are rough until U0 is done.

Phase Work Depends on Estimate
U0 — Shell Garage.Grpc (generated client + token interceptor), Garage.App.Core skeleton (AppState, OperationRunner, IUiDispatcher, IBackend, DevBackend), Garage.App shell (window, NavigationView with groups, Mica, title bar, single instance, tray icon with an empty flyout), AppSection order test, CI build of the app in the winapp job nothing new 1 week
U1 — Reading pages Search, Documents, Facts, Database (read-only), Status (backend and corpus only), Logs (app log); presentation types ported with their Mac test cases U0 2–3 weeks
U2 — Operations OperationRunner wired to pages; Sources (without ingest), Models (register, default, drop, backfill with progress), MCP Server (install, update, remove, HTTP toggle), Settings, Facts’ Glean; notifications U1 2–3 weeks
U3 — Service host ServiceHostBackend; Status service rows and self-tests; the ingest queue, Ingest Now, Stop, cancel per source; the Library box (Update Everything, Automatic Updates with the launch run held until services are up); Database Reset and relaunch; service log streams windows.md Phase 2 (service platform, Garage.Ingest) 2–3 weeks
U4 — Onboarding and tray First-run assistant; complete tray flyout (quick search, Ask Garage over MCP rag_agent, Ingest Now, Stop, state icon); bug report; jump list; launch at startup; update channel setting U3 (Ask can be built earlier against garage mcp-serve) 2–3 weeks
U5 — Finish Accessibility pass (Narrator, keyboard, high contrast, text scaling); strings in .resw resources; FlaUI UI tests (§5); performance with a large corpus (virtualization); Store build’s tip jar (Store add-ons), once the Mac’s reaches main U4 2 weeks

The llama-dependent parts of Models and first run (local models, downloads) follow windows.md Phase 4 and slot into U2–U4 once they exist. Until then those pages offer Ollama and LM Studio only.

Progress

U0 — done (2026-09-29, uncommitted on windows/python-bridge, based on v1.5-beta 839bc39).

U1 — done (2026-09-29, uncommitted, same branch). The reading pages, each a Core view model kept for the session, so a page’s query, filters and selection survive navigation.

U2 — done (2026-09-29, uncommitted, same branch). The operations pages. Ordinary operations run on AppState.Operations; backfill and fact distillation have their own runners (AppState.Backfill, AppState.Facts), as on the Mac, so a long job never blocks the rest. Toasts announce a finished scan, backfill or Glean (ToastNotifier over AppNotificationManager; a refused registration drops notifications rather than failing the operation).

U3 — done (2026-09-29, uncommitted, same branch), with the carry-overs at the end. The app now runs Garage’s own service processes instead of a garage serve started by hand, and ingests.

U4 — done (2026-09-30, uncommitted, same branch). Onboarding and the notification area.

U5 — done (2026-09-30, uncommitted, same branch), except what needs a Store or a packaged build.

A usable UI (read, search, manage sources and models, connect Claude, against garage serve plus the CLI for ingest) exists after U2, about 5–7 weeks in. Parity with the Mac app comes after U5, about 11–14 weeks, overlapping windows.md Phases 2–5.

5. Testing

6. Decisions needed

  1. Tray icon: H.NotifyIcon.WinUI (MIT, recommended; handles the flyout and context menu), or an own Shell_NotifyIcon wrapper.
  2. Packaged during development: develop unpackaged, with the Windows App SDK bootstrapper, for a fast inner loop, and package MSIX only in CI (recommended). The alternative is packaged from the start, which exercises MSIX-only behaviour (execution aliases, LocalState, Credential Locker) earlier.
  3. Ingest before the service host: wait for Garage.Ingest in U3 (recommended, keeps parity with the Mac), or add an Ingest streaming RPC to garage.proto so DevBackend can ingest sooner. The RPC is a cross-platform contract change and would duplicate the Mac’s XPC path.
  4. Minimum Windows version: Windows 10 1809 (the Windows App SDK floor), or Windows 11 only. Mica has a fallback on Windows 10; the AF_UNIX sockets windows.md wants need 1803 or later either way.
  5. Shared wording: port the presentation types’ strings into C# and keep them in step by tests (recommended for now), or move page wording into a shared resource both apps load (more machinery; worth it only if the apps drift).
  6. MCP client for Ask Garage (taken in U4: the minimal client, McpHttpClient): a minimal JSON-RPC client (initialize, tools/call) like the Mac’s GarageMCPService (recommended: two methods, no dependency), or the official C# MCP SDK (ModelContextProtocol), which is more complete and heavier.

7. Risks

8. Parity checks

Each check records the commits compared and what the plan took from them. The next check diffs from the last row.

Date Compared Changes that reached the plan
2026-09-27 v1.5-beta 2454039 The plan’s baseline
2026-09-29 main b82c6d2 (97 commits past the baseline, which it contains), v1.5-beta 839bc39 See below
2026-09-29 (U3) v1.5-beta b809c88 The App Store build asks for the home folder before the disk, and reads Contacts only for communication sources. Neither has a Windows counterpart: nothing is sandboxed, and ingest passes no contact names
2026-10-12 next 481f0a7, page by page and by the RPCs each UI calls See below

What the 2026-09-29 check found:

On main (and v1.5-beta):

On v1.5-beta only (not yet on main):

What the 2026-10-12 check found, and what the windows/ui-parity branch added:

For this branch: windows/python-bridge forked from 2454039, so it has none of the above yet. Merge v1.5-beta into it before opening the PR. Expect conflicts in README.md (main changed the mcp-install lines this branch’s rewrite keeps) and possibly in CLAUDE.md (edits beside the insertion point of the “Windows app” section).