This site uses one functional cookie to keep feature rollouts consistent for you. Nothing is set until you choose. See the privacy notice.
Dev notes
The angular-paul desktop clone looked the part until you reached for the menu bar — the strip of Apple / File / Edit / View / Window / Help labels along the top was dead paint. This is the pass that turned it into a real macOS menu system: a signal-driven service that derives every menu from the live window and dock state, dropdowns that run actual actions, and keyboard access that holds up to an axe scan with a menu open.
The menu bar was the most conspicuously unfinished thing on the desktop. It rendered the right words, but nothing was clickable, the active-app slot was hardcoded to "Finder" no matter what window you had focused, and there were no dropdowns at all. Every other part of the shell — the dock, the windows, the traffic lights — did something. The menu bar just sat there, and it was the first place your eye went.
The tempting version is to hand-write a static menu tree and wire each item to a handler. That falls apart the moment the menu has to reflect reality: the active app name, whether there’s a window to close or minimize, the live list of open windows under the Window menu. All of that already lives in WindowManagerService and DockService. So the menu shouldn’t own any of it — it should be a projection of state that already exists.
Angular signals make that projection cheap. The whole menu model is a set of computed signals reading off the window and dock services: the active app name derives from the focused window (falling back to “Finder” when nothing is focused, the way real macOS falls back to the Finder), and the per-app File / Edit / View / Window / Help menus rebuild themselves whenever the underlying state changes. Nothing to keep in sync by hand; the menu is always a pure function of the desktop.
The menu model went into a new MenuBarService (providedIn: "root"), not into the component. Two reasons. First, the model is derived data that several things may want to read, so it belongs where the other derived desktop state lives, next to the window and dock services it depends on. Second, it keeps the MenuBar component thin: the component renders the model and reports clicks, and the service decides what a click means.
The actions are the part that makes it feel real rather than decorative. The Apple menu opens About / Settings / README; the app menus close, minimize, zoom, and quit the focused window; the Window menu cycles and focuses among the live open windows; and there’s a Spotlight entry. Each is a small method on the service operating on the same window and dock state the menus are derived from, so the menu you see and the action you get can never drift apart.
A menu bar is a classic a11y trap: it’s trivial to build one that only works with a mouse. This one was built to the menu pattern from the start. Top-level menus are real <button> elements with aria-haspopup and aria-expanded; open dropdowns are role="menu" with role="menuitem" children, each a native button so it’s focusable and operable by keyboard, with the shortcut shown as a hint.
The suite went from 229 to 252 tests: 14 new MenuBarService unit tests covering the derived model and every action, new interaction tests for the MenuBar component, and an added axe scan of the menu bar with a menu open — the state most menu-bar a11y tests skip. All 252 pass, ESLint is clean over src/, tsc --noEmit is clean, and the production build succeeds. WCAG 2.1 AA holds with the menu expanded, not just closed.
The eager bundle grew slightly and the production build now emits a soft budget warning (406 kB against the 400 kB warn threshold, still well under the 500 kB error ceiling). CI has no build step, so nothing gates on it today, but it tells you the shell is getting heavy. If more lands in the desktop chrome, that warning is the first place a real budget problem will show up.