Skip to content

Picker appearance: horizontal layout, icon-only mode, icon size - #17

Closed
jnahian wants to merge 5 commits into
fix/browser-profile-accessfrom
feat/picker-appearance
Closed

jnahian wants to merge 5 commits into
fix/browser-profile-accessfrom
feat/picker-appearance

Conversation

@jnahian

@jnahian jnahian commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

Closes #13. Stacked on #16. Merge that first, then retarget this to main. Both PRs add to ## Unreleased in CHANGELOG.md, so expect a small conflict there.

What

A new optional picker block, global only (per-rule was left out, as the issue suggests):

"picker": { "layout": "horizontal", "labels": "none", "iconSize": 40 }

It's edited under Settings → Browsers → Picker appearance: a layout picker, a names/icons-only picker, and an icon-size slider. The slider saves once, when the drag ends, not on every tick.

How the issue's concerns are handled

  • Keyboard nav follows the layout. One pure function, PickerPanelController.movedSelection: ↑/↓ move by a row (one item in the vertical list), and ←/→ only move in the grid. In the vertical list they still fall through as before. 1–9 are unchanged. In a grid the digit sits as a badge on the icon's corner.
  • Horizontal can't overflow. The number of columns comes from the screen's visible width: as many as fit, capped at 9. resizePanel now updates the max width as well as the max height. Cells have fixed sizes, so the scroll area's height is computed exactly, not estimated.
  • Icon-only keeps profiles distinguishable. Every choice has a tooltip, and a line above the choices always names the selected one. In the grid, names under icons show the profile name, since the icon already identifies the browser.
  • Validation. iconSize is clamped to 16–64 on load rather than rejected. An unknown layout or labels value fails to parse. describeDecodingError's .dataCorrupted branch now includes the location, so the error reads Cannot initialize Layout from invalid String value sideways at picker.layout. It previously had no location.
  • Config churn. picker is optional and left out of the file until the user changes it, so existing configs don't gain a block on their next unrelated save. The Swift case for "none" is .iconOnly, because a case named .none on an optional would silently compare against Optional.none.

Vertical + icons only

In this mode the panel narrows to the icon column (120pt at the default size). The footer's text links become icon buttons with tooltips and accessibility labels, and the keyboard-shortcut hint is dropped.

Testing

  • swift test: all passing. New tests:
    • config: a missing block stays absent on save, round trip with clamping, and the unknown-layout error names its location
    • app: arrow-key math for both layouts, plus the issue's overflow case (17 icons at 64pt, horizontal, on an 800pt-wide screen) — the panel stays on screen, nothing is clipped, and →/↓/Return picks the expected choice
  • junction config validate on a bad and an oversized config: clear error for the bad value, clamped size for the oversized one.
  • All four layout combinations rendered through the test seam and checked by eye. They weren't exercised in the running app.
  • cd web && npm test && npm run build pass. Changelog Added: entry and the docs' Settings summary are updated.

🤖 Generated with Claude Code

https://claude.ai/code/session_019Y9gVVaoGd4dCz42XkXkpP

@vercel

vercel Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
junction Ready Ready Preview Sep 27, 2026 5:59am UTC

jnahian and others added 5 commits September 27, 2026 11:54
Playwright and Puppeteer install an automation-only Chrome build that
registers as an https handler, so it showed up next to real Google Chrome
in rules, the picker, and Settings → Browsers. It has no profile support,
and picking it by mistake looked like the Profile dropdown was broken.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Y9gVVaoGd4dCz42XkXkpP
On recent macOS, reading another browser's Local State or profiles.ini is
refused with EPERM unless Junction has Full Disk Access. There is no
permission prompt for it, and the profile readers failed quietly to an
empty list, so the rule editor's Profile dropdown and Settings → Browsers
simply showed no profiles.

BrowserDiscovery.isProfileAccessBlocked tells a denied read (EPERM) apart
from a missing file (ENOENT). AppState refreshes it with the browser list,
and a shared notice with an "Open System Settings…" button and a
"Check Again" button now appears in Settings → Browsers, in the rule editor
in place of the Profile picker, and on the onboarding default-browser step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Y9gVVaoGd4dCz42XkXkpP
First launch, Privacy, and the profile action now say that listing browser
profiles needs Full Disk Access, what Junction reads with it, and what
still works without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Y9gVVaoGd4dCz42XkXkpP
…size

The picker was one fixed shape, a vertical list of 22pt icons with names.
With many browsers and profiles that's a tall column. A new optional
`picker` config block (layout, labels, iconSize) sets it globally, edited
under Settings → Browsers → Picker appearance.

- Horizontal lays choices out in a grid sized to the screen: as many
  columns as fit, capped at 9 so the digit keys cover the first row. ←/→
  step one choice and ↑/↓ jump a row. The vertical list keeps ↑/↓ only.
- Icon-only drops the names. Grid cells and icon-only rows show the digit
  on the icon corner, and a line above the choices names the selected one,
  so same-icon Chrome profiles stay distinguishable.
- iconSize is clamped to 16–64 on load rather than rejected, so a wild
  value can't take routing down with the config. An unknown layout or
  labels value fails to parse, and parse errors from bad values now name
  their location (e.g. "at picker.layout").
- The block is left out of the file until the user changes it, so existing
  configs don't grow a new key on their next save.

Closes #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Y9gVVaoGd4dCz42XkXkpP
Vertical + icons-only kept the 340pt panel with empty space where the
names were, which missed the point of the mode. The footer's text links
now become icon buttons in that mode, and the panel narrows to the icon
column.

Also:
- Choice buttons carry an accessibility label; icon-only cells read as
  just "button" to VoiceOver before.
- The icon-size slider saves keyboard and VoiceOver adjustments directly;
  only a mouse drag is buffered until release. Before, a non-drag change
  sat in the draft unsaved.
- The saved-file assertion parses JSON rather than matching pretty-print
  spacing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Y9gVVaoGd4dCz42XkXkpP
@jnahian

jnahian commented Sep 27, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #20: same commits rebased onto main and merged. This PR closed when its stacked base branch was deleted after #16 merged.

This branch was successfully deployed

1 active deployment
Preview — ed7f43c6 Deployed Sep 27, 2026 by vercel[bot]
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.

Picker appearance settings: horizontal layout and a resizable icon-only mode

1 participant