Previously, in Part 9, the app learned to remember a visit and to survive a dead signal. The phone is done. This part is about the host that has been quietly compiling since Part 2 without ever being designed: Windows. The fix is smaller than you'd guess — a window size, one CSS breakpoint, one new page — and the interesting part is what it deliberately isn't.

The prompt: no desktop-specific screens

Verbatim from the commit in the repo (tag pocket-10):

"Make the Windows build feel like a desktop app without writing
desktop-specific screens. Open at a reception-desk size with a sensible
minimum (desktop only — phones ignore it). Turn the bottom tab bar into a
side rail past 900px with ONE CSS breakpoint on the same DOM, so the browser
host gets the desk layout on a laptop for free and the Windows window flips
between layouts as it's resized. Add a Waiting-room page — the public board,
now serving and up next with masked names, on the live hub — as a fifth tab
everywhere. Prove it by shrinking the window to phone width."

The constraint is the whole prompt: without writing desktop-specific screens. The temptation with a MAUI app is to check DeviceInfo.Idiom in a layout and branch — and then you have two layouts to keep honest, and the browser host, which has no idiom, gets neither. So the desk layout had to be a property of width, not of host.

Model pick: Sonnet, medium effort. This part is CSS and a window size; the only judgment call was "one breakpoint, no host-specific layout code", and that was made in the prompt. The cheaper model executes a settled decision perfectly well — the season's rule since the design retro.

A window with opinions

The whole of the native change is in App.CreateWindow, and it is guarded by idiom rather than platform, because a tablet is not a desk either:

// Desktop only (ignored on phones): open at a reception-desk size, never
// shrink below a phone's worth of width. Part 2's first Windows screenshot was
// a phone layout stretched across a 4K monitor — this is the fix.
if (DeviceInfo.Idiom == DeviceIdiom.Desktop)
{
    window.Width = 1100;
    window.Height = 760;
    window.MinimumWidth = 420;
    window.MinimumHeight = 640;
}
Part 2's Windows build: a very wide dark window with the 'Your visit, in your pocket' hero and the code card floating in a narrow column in the middle, and four tab icons — Home, My visit, Find us, Settings — spread hundreds of pixels apart along the bottom edge. Before
The same app after Part 10: an 1100 by 760 dark window with a side rail on the left listing Home, My visit, Waiting room, Find us and Settings — Home highlighted in a soft petrol pill — and the hero and code card in a wider content column to the right. After
Before: Part 2's frame, a phone stretched to a monitor, tabs a hand-span apart. After: a desk-sized window with a rail. Both are dark because the desktop's OS theme is dark and the tokens from season two follow it. Honest notes on the right-hand frame: the harness's capture rectangle outgrew the window, so there's a white margin to the right and below, and a faint smudge along the bottom edge that isn't the app.

One breakpoint, not one layout per host

The layout component didn't change shape at all. Its nav still says what it said in Part 2 — @* Bottom tab bar on a phone; a side rail on anything wide (pure CSS, see pocket.css). *@ — and the whole desk treatment is one media query in the shared stylesheet:

/* ---------- wide screens: the desk layout (Part 10) ----------
   Same DOM, same components. Past 900px the tab bar becomes a side rail and
   the content column stops pretending to be a phone. The browser host gets
   this for free on a laptop; the Windows app gets it whenever the window is
   wide enough — and drops back to the phone layout when it isn't. */
@media (min-width: 900px) {
    .app {
        display: grid;
        grid-template-columns: 15rem minmax(0, 1fr);
        grid-template-areas:
            "top    top"
            "banner banner"
            "nav    main";
    }
    .app-main { grid-area: main; max-width: 44rem; margin: 0; }
    .tabbar {
        grid-area: nav;
        position: static; height: auto;
        flex-direction: column; justify-content: flex-start;
        border-top: 0; border-right: 1px solid var(--line);
    }
    .tab { flex-direction: row; justify-content: flex-start; border-radius: var(--radius); }
    .tab.active { background: var(--primary-soft); }
    /* … */
}

The tab bar stops being position: fixed and becomes a grid column; each tab turns from a stacked icon-over-label into a row with a rounded active state; the content column grows from 32rem to 44rem. Part 9's offline strip gets its own grid row so it still spans the window. Nothing in C# knows this happened. Which is exactly why the proof the prompt asked for works in both directions:

The Windows window dragged down to roughly a phone's width: the Waiting room page — 'NOW SERVING Ava C.', 'Up next 1 Noah B.' — with the side rail gone and the five bottom tabs back, Waiting room highlighted. Windows at its 420px minimum
The web host in a browser at 1280 pixels, light theme: the same Waiting room page with the side rail on the left and the queue cards in the content column. Browser, 1280px
Left: the Windows window shrunk to its 420-pixel minimum — the rail is gone and the bottom tabs are back, live, without a restart. Right: the plain web host in a browser at 1280 pixels, no MAUI anywhere, wearing the rail it never asked for. Same stylesheet, same 900-pixel line. (The Windows frame has the same white margin as before; the browser frame is light because the web-host harness shoots light unless you pass its --dark flag.)

The waiting-room page

A desk needs something to glance at, and the app already knew how to be a wall TV — the phone joined the same QueueHub as the board back in Part 4. So the fifth page is the public board, re-rendered as a card: now serving, then the numbered up-next list, first name and initial only, exactly as season two's signage shows it. The whole page is forty lines because the live plumbing already existed:

protected override async Task OnInitializedAsync()
{
    Live.Changed += OnQueueChanged;
    Live.ConnectionChanged += OnConnectionChanged;
    await Live.EnsureStartedAsync();
    _connected = Live.IsConnected;
    await LoadAsync();
}

private void OnQueueChanged() => _ = InvokeAsync(async () =>
{
    await LoadAsync();
    StateHasChanged();
});

Notify, don't ship state — the queue arrives by re-reading /api/pocket/queue when the hub says something changed, the same single signal the TV listens for. It's a fifth tab on every host, because phones allow five, and the copy under the list explains why it exists in two places: on a desk it's the glanceable version; on a phone it's context.

The Waiting room page in the Windows window, dark theme: the rail on the left with Waiting room selected, 'NOW SERVING Ava C.' in large teal type, an Up next card with '1 Noah B.', and a note that it's the same list as the waiting-room TV. Desk
The same Waiting room page on the Android emulator, light theme, with a five-tab bar at the bottom — Home, My visit, Waiting room, Find us, Settings — and the Waiting room tab active. Phone
The same component on the desk and in a hand: Ava C. being served, Noah B. next. On the phone the fifth tab squeezes the labels a little — "Waiting room" is the longest label in the bar and it shows.

One note for the browser host that the log insisted on. The web host holds one hub connection for the whole process and shares it across every visitor's circuit — a fan-out singleton. For a public board that's ideal; it's also a thing to remember before anyone puts per-user state on that connection.

The toast that stayed open

This is the part where a Windows post would normally show a notification banner. It can't. Part 5 left Windows toasts open: Register() succeeds, the OS setting reads Enabled, Show() returns without complaint, and no banner ever appears — and a toast sent to the same identity from PowerShell is just as silent while PowerShell's own banners work. The diagnosis, now that the desktop part is here, is identity. An unpackaged debug build has no package identity, and Windows is entitled to suppress notifications from an app it can't name. There are two real fixes, and both are ship-time work: package the app as MSIX, or have an installer create a Start-menu shortcut carrying an explicit AppUserModelID. Neither belongs in a debug build, so both are Part 11's problem — and Part 11 will be honest about how far it gets.

What the AI got wrong: nothing in this part's code, which is itself the entry. The mistake this part pays for is older: in Part 2 the AI's default Windows window opened maximized with the phone layout inside it, and "it compiles for Windows" got reported as if it were "it runs on Windows." A desktop app you get for free isn't a desktop app; it's a phone in a very large frame. And on toasts, the honest record is that the AI's Part 5 diagnosis chased the registration code — moved it, re-ordered it, re-registered it — when the answer was never in C#. Some problems are about who the OS thinks you are, and no amount of code changes that.

The meter: ≈ $6.50 on season three's running meter. The cheapest part of the season by some distance: forty lines of CSS, ten of C# and one page. The resize-and-reshoot proof cost more than the code did, which is the right way round.

Checkpoint: git checkout pocket-10 in the repo: build the Windows target and the window opens at 1100×760 with a rail; drag it narrower than 900 pixels and the tabs come back. Run the web host and widen the browser — same rail. Tap Waiting room on any host and call next from the staff page; it moves. Toasts on Windows still don't show, and the app will tell you "Allowed" while not showing them. 28 tests green.

Three hosts, one set of screens, every capability answered honestly — and none of it shippable. The Android build is signed with a debug key, the Windows exe needs a developer's machine, and CI doesn't know the app exists. Time to make it real, and to be plain about the one thing that stays unpackaged: Part 11: Ship It: Signed Android, Self-Contained Windows, CI for Both.