The collapsed sidebar rail, and Focus's front door - #11
Merged
Conversation
The 48px rail was never designed — it is the expanded sidebar with three display:none rules switching things off, which is why area buttons have no accessible name at all, renaming is unusable, and the active tint overflows a column that only has 16px of content width. Rebuild it as a status-and-navigation strip with no loss of function: one DOM rendered as a CSS variant (sidebarCollapsed is also true on mobile, so the view cannot branch on it), labels clipped instead of removed, counts as corner badges, the menu trigger as a hover-revealed corner affordance, and rename as a popover anchored to the tile. The wordmark becomes the Focus hero card, giving the landing surface a real front door without adding a second control pointing at one page. Stress-tested across security, privacy, accessibility and loopholes; 11 findings folded in, including the hover-reveal being dead on touchscreen laptops, the sidebar toggle never being specified despite being the only way out of the rail, and overflow-y:visible being load-bearing for popover escape. Covers follow-up items 5 and 6. Item 3 (section titles) is a named non-goal.
Two claims survived the stress-test edits into the summary rows while the body already contradicted them: the header still listed item 3 as covered when §9 makes it a non-goal, and D5 still said hover-reveal "costs nothing on touch" when §3.3 now specifies @media (hover: none) precisely because a touchscreen laptop gets the rail with no hover state.
Nine tasks, each with its own test cycle and commit: the shared focus-counts helper, accessible names, new-area section seeding, rail geometry, count badges, the Focus hero card, the corner menu trigger with its motion and coarse-pointer guards, rename as a popover, and the verification sweep. Orders the accessibility fix ahead of every CSS task so no later change can reintroduce the unnamed-button defect.
The focusSectionIds filter existed byte-for-byte in controller.js and views/focus.js. The sidebar hero card needs the same numbers, and a third copy is how the page header and the sidebar start disagreeing on screen.
Area buttons had no accessible name at all when collapsed: the icon span is aria-hidden and the label and count were display:none, which removed the button's entire name. Clipping the label cannot fix it, because that span is the visible label when expanded. aria-label states the name once, correctly, in both states.
onAddArea created an area with no section, so nothing could be filed into it and it was not a valid move target until a section was added by hand.
The rail kept #sidebar's 1rem padding, leaving 48px with 16px of content width, so tiles, the wordmark and the active tint all overflowed it. Pad the rail itself, centre its children, and make the icon the painted 36px tile while the button stays a 44px target. The toggle and + become tiles instead of an unstyled box and a hidden row.
…y order Task 4 stopped hiding .sidebar__menu-btn but its out-of-flow positioning doesn't land until Task 7, so a real area row centred two 44px children (the area tile and the menu button) inside the 48px rail and both bled off the edges. Restore the hide for both .sidebar__menu-btn and .sidebar__rename-input, per the plan's corrected Task 4 Step 2, with a comment explaining the hide and Task 7's position: absolute are one change split across two tasks. Also move the collapsed-rail block after the base .sidebar__* rules it overrides instead of before them, so specificity stays ascending in source order (matching the convention already documented elsewhere in this file) and Biome's 5 noDescendingSpecificity warnings clear without touching any selector or declaration.
…tile .sidebar__icon declared inline-size/block-size: 36px in the collapsed rail, but flex children default to flex-shrink: 1, so the still-visible .sidebar__count sibling was squeezing it to ~28px. Add flex-shrink: 0 so the tile actually renders at its declared 36px, since Task 5's badge positioning is specified against that real size. Plan doc corrected to match (docs/superpowers/plans/2026-08-17-ignite-sidebar-rail.md).
.sidebar__count was still a flex sibling of the icon inside the 44px button. Fixing the icon to a true 36px (flex-shrink: 0) exposed the same root cause differently: the count's min-content width now pushed the row past 44px, bleeding the icon ~2.3px past the rail's left edge and the count ~1.3px past the right. Add .sidebar__count to the collapsed display: none rule alongside .sidebar__name and .sidebar__add-text; Task 5 un-hides it in the same change that gives it position: absolute.
Same number the expanded row shows, inset so it cannot overhang the rail's border. Zero is not painted in the rail.
Focus is the landing surface but had no visible front door — the wordmark read as a logo. Promote it into a bordered, tinted card carrying the day's counts, still one control so there is no second door onto the same page.
The ⋯ was display:none when collapsed, which removed it from the tab order entirely. Move it to the tile's corner, reveal it with opacity so it stays keyboard-reachable, keep it permanently visible where hover does not exist, and anchor rail popovers above the capture picker and toast layers. Also brings the collapse animation under prefers-reduced-motion: it lives on body, which the existing block did not cover.
The rename field was display:none while the row still entered rename mode, so the user was put into a state with nothing to type into and focus fell to <body>. Position the field and the icon picker beside the tile instead. Drops the expanded-to-collapsed force-close with it: it existed only because collapsing destroyed the input, and keeping it would now commit a rename the user is still typing.
…ents - .sidebar__home-badge used --surface-1 on --accent (4.38:1 in light theme, under the 4.5:1 AA floor) — copied from .sidebar__count before that same pairing was fixed to --surface-2. Match it. - Delete the dead `display: none` rule for .sidebar__menu-btn: an identical selector later in the same block already sets display: block/position: absolute, so the hide never did anything. Rewrite the block comment above it to describe the actual mechanism (opacity-only reveal, out of flow via the live rule). - Rewrite the .sidebar__icon flex-shrink comment: it cited a "until Task 5" state that no longer exists now the count is out of flow. - Spec: replace remaining "clipped label" references (§3.7, §8, §9) with the aria-label mechanism that actually shipped (D3/§3.3); leave D3's own rationale, which correctly describes the rejected alternative, as is.
Spec 3.6 specifies the toggle as a rail tile like any other: a 44px hit target with a 36px painted surface. It shipped with the 44px box and border: 0, leaving it the only rail control with no painted affordance and a hover that was a colour shift on a bare glyph. The glyph span becomes the painted 36px tile, mirroring .sidebar__icon. Verified in the rail at 1926px: toggle 44x44 at [1.5, 45.5], glyph 36x36, radius 10, surface-3 paint; 258 tests and Biome clean.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebuilds the collapsed 48px sidebar as a status-and-navigation rail with no loss of function, and promotes the wordmark into the Focus hero card. Implements
docs/superpowers/specs/2026-08-17-ignite-sidebar-rail-design.md(follow-up items 5 and 6).What changed
aria-labelwith painted childrenaria-hidden— area buttons previously had no accessible name at all when collapsed.src/utils/focus-counts.js, used by the page header, the Focus tabs and the card, so the three cannot disagree.display/visibility, so it stays in the tab order), permanently visible under@media (hover: none), popovers layered above the capture picker and toast.onAddAreaseeds a "Tasks" section (composed in the controller, keeping one-model-one-store).bodycarries the grid transition).Verification
258 tests / 17 files, Biome clean. Spec §10 records the sweep: 10 of 12 manual steps PASS plus light-theme repeats; reduced-motion and coarse-pointer-at-desktop are recorded as NOT VERIFIED (tooling cannot emulate them) with static CSS reads instead. Still owed on a real device: Tab traversal, Enter on the ⋯ menu, arrow keys in the icon picker. Axe was not run (axe-core deliberately not added); a manual DOM/ARIA/contrast check stands in.