Skip to content

feat: add opt-in spaces hiding, bottom nav bar, and a window title - #10

Open
marcioviniciusspiridigliozzi-dot wants to merge 1 commit into
nicojan:mainfrom
marcioviniciusspiridigliozzi-dot:feat/layout-options
Open

feat: add opt-in spaces hiding, bottom nav bar, and a window title#10
marcioviniciusspiridigliozzi-dot wants to merge 1 commit into
nicojan:mainfrom
marcioviniciusspiridigliozzi-dot:feat/layout-options

Conversation

@marcioviniciusspiridigliozzi-dot

Copy link
Copy Markdown
Contributor

Three layout options, all defaulting to today's behaviour so an upgrade
changes nothing until the user asks for it.

Hide the spaces bar
I keep every service in one space, so the spaces rail is permanent
dead weight in my daily use. Adds a persisted hideSpacesUI
preference with three ways to flip it: a Settings toggle,
View > Hide Spaces Bar (Ctrl-Cmd-S), and a button in the services rail
footer for the everyday case.

That button lives in the SERVICES rail deliberately — the spaces rail
is what disappears, so a control hosted there would remove itself and
leave only the menu as a way back.

Hiding is purely visual: the selected space is untouched, and Cmd-K,
Ctrl-Tab and the menu bar still switch spaces. Services in a space you
aren't viewing stay out of the sidebar as before, so the Settings
caption points at Cmd-K.

Navigation buttons at the bottom
At the top they compete with the traffic lights and the badge cluster.
ToolbarPosition (top/bottom) moves them to a bar of their own. At
.bottom both the sidebar row and the tab-bar buttons are suppressed
so they never render twice.

Window title
With the spaces rail hidden there was nothing naming the service in
front. Two things about this are worth recording, because both bit me:

First, the obvious approach fails silently.
.windowStyle(.hiddenTitleBar) hides the title but keeps the bar, and
AppKit draws that bar over SwiftUI content — I rendered a solid
rectangle into that strip and nothing appeared on screen. So the title
can't be a SwiftUI view; WindowTitleConfigurator sets the window's
own title and makes it visible again, leaving the bar transparent so
layout is unchanged.

Second, it must be per-layout. Only .sidebar leaves that strip
empty. .topBars and .hybrid put the spaces chips and service tabs
there, and the title lands on top of them — I had this wrong until I
screenshotted those layouts, where "Chorus — Slack" was drawn straight
through the Personal/Work chips. The configurator now takes an
isVisible flag and only shows the title in .sidebar.

Layout corrections that came with the above

  • The rails' vertical rules ran the full height and cut through the
    traffic-light strip; they now start below it.
  • .topBars and .hybrid inset their service tabs on the assumption
    that the spaces rail was there to clear the traffic lights. With it
    hidden they take the full inset instead.
  • The traffic-light metrics were repeated as literals in two files;
    extracted to WindowChrome so a rail and the view beside it can't
    drift apart.

The new preferences are Optional, matching autoDarkModeEnabled and
contentBlockingEnabled, so SwiftData lightweight migration handles
existing stores. Verified against a populated store rather than a fresh
one, in both directions — opening it with an older model that lacks
these fields drops the columns and leaves the services, spaces and links
intact.

Checked by running the app and screenshotting all six states: sidebar,
top-bars and hybrid, each with the spaces rail shown and hidden, plus
both toolbar positions. Two new tests cover the preference resolution.

One thing I could not exercise properly: the window drag band. In
.topBars the OS drag is off and WindowDragHandles stand in for it. I
confirmed by reading the code that ServiceSidebarView.horizontalBody
carries its own handle, so hiding the spaces rail doesn't remove the
only draggable strip — but I could not simulate a real drag to prove it.

@nicojan

nicojan commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Thanks for these. Six focused PRs, each with a clear reason and its own tests. I have merged five: #6, #7, #8, #9, and #11.

I am holding this one for two reasons.

First, it overlaps #9. Both add the same prefs property to PrivacySettingsView, so now that #9 is in, this branch needs a rebase to drop the duplicate.

Second, it is the largest change here and it touches the hidden-titlebar chrome, which has been a repeated source of subtle bugs in this app. You noted yourself that you could not exercise the window drag band. Before it lands I want to run all six layout states and a real drag by hand, and I have not done that yet.

None of this is a rejection. The work looks careful and the reasoning in each description helped. Rebase onto current main when you get a chance, and I will take it through the layout checks.

@marcioviniciusspiridigliozzi-dot

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough look, and for merging the other five.

Rebased onto current main. The duplicate prefs in PrivacySettingsView is gone — #9 added it there, so the rebase drops mine and keeps the one already merged. The preference additions in AppPreferences, AppState and the tests are reconciled alongside the favicon ones from #9. It is a single commit again and now shows mergeable; the suite is green (111 tests).

On the second point, you are right to gate it on the drag band, and I would not want it to land without that check either. I could not exercise a real window drag from where I was testing, so the .topBars / .hybrid behaviour with the spaces rail hidden rests on reading the code, not on dragging the window. ServiceSidebarView.horizontalBody carries its own WindowDragHandle, so hiding the spaces rail should not remove the only draggable strip in those layouts — but "should" is the operative word, and your by-hand pass across the six states is exactly the right gate. No rush on my end.

@nicojan

nicojan commented Jul 22, 2026

Copy link
Copy Markdown
Owner

An update, and it is not a rejection: I am parking this one for now, pending some UX exploration.

I ran the build on macOS 26 and checked what works. Hiding the spaces bar reflows the content correctly and is reversible. Switching across all three layouts works. The drag band works too in the horizontal layouts: the window moves when you drag the empty top strip.

The window title is what gives me pause, for two reasons. First, on macOS 26 it does not render at all. The window's title property gets set, but nothing draws, so the whole configurator is a no-op here. My guess is a version difference in how .hiddenTitleBar handles titleVisibility; it clearly rendered on your machine. Second, a text title duplicates the services rail. The active service is already shown by its highlighted icon, so naming it in words adds little.

Rather than merge the layout options and settle the title piecemeal, I want to think through the rail and title UX as a whole first. So I am leaving this open but tabled for the time being. The hide-spaces and bottom-nav work is solid, and I expect it to land once the UX is settled.

Thanks again, and sorry to park it after you took the trouble to rebase.

Two layout options, both defaulting to today's behaviour so an upgrade
changes nothing until the user asks.

Hide the spaces bar
  Some setups keep every service in one space, leaving the spaces rail as
  permanent dead weight. Adds a persisted `hideSpacesUI` preference with
  three ways to flip it: a Settings toggle, View > Hide Spaces Bar
  (Ctrl-Cmd-S), and a button in the services rail footer.

  That button lives in the SERVICES rail deliberately — the spaces rail
  is what disappears, so a control hosted there would remove itself and
  leave only the menu as a way back.

  Hiding is purely visual: the selected space is untouched, and Cmd-K,
  Ctrl-Tab and the menu bar still switch spaces. Services in a space you
  aren't viewing stay out of the sidebar as before, so the Settings
  caption points at Cmd-K.

Navigation buttons at the bottom
  At the top they compete with the traffic lights and the badge cluster.
  `ToolbarPosition` (top/bottom) moves them to a bar of their own. At
  `.bottom` both the sidebar row and the tab-bar buttons are suppressed
  so they never render twice.

Layout corrections that came with the above
  - The rails' vertical rules ran the full height and cut through the
    traffic-light strip; they now start below it.
  - `.topBars` and `.hybrid` inset their service tabs on the assumption
    that the spaces rail was there to clear the traffic lights. With it
    hidden they take the full inset instead.
  - The traffic-light metrics were repeated as literals in two files;
    extracted to `WindowChrome` so a rail and the view beside it can't
    drift apart.

The window title from the earlier revision of this PR is dropped: it was
a no-op on macOS 26 (the window's title property set, but nothing drew),
and it duplicated the highlighted service icon anyway. The traffic-light
strip is still reserved as empty space so the top nav row clears the
window buttons, but nothing is drawn into it.

New preferences are `Optional`, matching the surrounding pattern, so
SwiftData lightweight migration is a no-op on existing stores. Two tests
cover the preference resolution. Suite green (109).

Verified by the maintainer on macOS 26 in the prior revision: hiding the
spaces bar reflows correctly and reverses, all three layouts switch, and
the window drag band still works in the horizontal layouts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@marcioviniciusspiridigliozzi-dot

Copy link
Copy Markdown
Contributor Author

That is fair, and thanks for taking the time to run it on macOS 26 — the title findings are both real, and I would rather know than have it merged half-working.

You are right on both counts. It being a no-op under .hiddenTitleBar on 26 means the whole configurator is dead weight there, and the point about it duplicating the highlighted icon lands — the active service is already named by what is lit up in the rail, so the words add little.

So I have pushed a revision that simply drops the window title. No WindowTitleConfigurator, no title-setting, nothing that depends on how .hiddenTitleBar treats titleVisibility across OS versions. What is left is only the two pieces you called solid: hide-spaces and the bottom navigation bar. The traffic-light strip is still reserved as empty space so the top nav row clears the window buttons, but nothing draws into it. Rebased onto current main (so it carries the #12 merge cleanly), single commit, suite green (109).

None of this is meant to jump ahead of the rail-and-title UX rethink you mentioned — if you would still rather hold the whole thing until that is settled, that is completely reasonable and I am happy to leave it parked. I mostly wanted to take the broken and redundant piece off the table, so that whenever you do come back to it, what is open is only the part you already verified works. No rush at all, and thanks again for the careful reviews across all of these.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants