Option-click on split buttons opens a new window right/down (macOS-style) #280

Open
opened 2026-11-01 07:46:27 +00:00 by kayg · 6 comments
Owner

Owner feedback on the mockups (kayg, 2026-11-01), job J52. Related: #242 (row menu and swipes), where the other two items of this round live.

"also I feel like option click on the split window down/right buttons at the top right of a terminal window should (macos-like) switch to creating a new window to the right/down instead of split. pressing option and taking the cursor near those buttons should smoothly animate them to the new window right/down buttons, and of course the warm tooltip should change."

What this asks for

  • Holding Option (⌥ on Apple keyboards, Alt elsewhere) while the pointer is near or over the window header's split right / split down buttons morphs the two icons smoothly into "new window right" / "new window below". The warm tooltip changes with them. Releasing the key morphs them back.
  • Option-click runs the new-window action instead of the split. TASTE (row "Split divides; new opens beside", owner 2026-10-10) keeps the four actions apart: the modifier is a shortcut to the other pair, not a fifth action.
  • Reduced motion: no morph, the glyph swaps at once.
  • Touch has no modifier; phones keep the canvas row for the other pair.

Mockup

Branch mockups-2026-11-01-j52, served with the other mockups at http://10.69.69.117:47650/mockups.html (card "Option on split buttons"). Screenshots and recordings are attached to the comments of this issue and of #242.

Owner feedback on the mockups (kayg, 2026-11-01), job J52. Related: #242 (row menu and swipes), where the other two items of this round live. > "also I feel like option click on the split window down/right buttons at the top right of a terminal window should (macos-like) switch to creating a new window to the right/down instead of split. pressing option and taking the cursor near those buttons should smoothly animate them to the new window right/down buttons, and of course the warm tooltip should change." ## What this asks for - Holding Option (⌥ on Apple keyboards, Alt elsewhere) while the pointer is near or over the window header's split right / split down buttons morphs the two icons smoothly into "new window right" / "new window below". The warm tooltip changes with them. Releasing the key morphs them back. - Option-click runs the new-window action instead of the split. TASTE (row "Split divides; new opens beside", owner 2026-10-10) keeps the four actions apart: the modifier is a shortcut to the other pair, not a fifth action. - Reduced motion: no morph, the glyph swaps at once. - Touch has no modifier; phones keep the canvas row for the other pair. ## Mockup Branch `mockups-2026-11-01-j52`, served with the other mockups at http://10.69.69.117:47650/mockups.html (card "Option on split buttons"). Screenshots and recordings are attached to the comments of this issue and of #242.
Author
Owner

J57 start: branch ui-approved-splitopt-224, base origin/dev f4b61cd6c. Owner 2026-11-01: split buttons with Option approved (must work with Alt on Windows/Linux); account card mostly approved with changes (tag cards + gradient machine tints, concerns shown in the value field). First commit 49fbf2312 (the Option/Alt split buttons, product). Account card follows; it shares sidebar-card/kv-list with J56, so I rebase on its push.

J57 start: branch `ui-approved-splitopt-224`, base origin/dev `f4b61cd6c`. Owner 2026-11-01: split buttons with Option approved (must work with Alt on Windows/Linux); account card mostly approved with changes (tag cards + gradient machine tints, concerns shown in the value field). First commit `49fbf2312` (the Option/Alt split buttons, product). Account card follows; it shares sidebar-card/kv-list with J56, so I rebase on its push.
Author
Owner

J52 evidence: Option on the split buttons (branch mockups-2026-10-31, head 7e0497605; production build, Chromium 155 headless; 1440×900 DPR 2, cropped header shots at DPR 2 plus a full frame; Playwright video). A phone has no Option key, so there is no phone capture (its canvas row carries the other pair). Not judged as final visuals. Related: #242.

What it does: with Option (Alt on Windows and Linux) held and the pointer within 28 px of a window's header actions, the split right / split down icons morph into new window right / below (the frame shrinks to what the first window keeps, the divider becomes the plus's upright, the crossbar grows out of it; one shared spring, interruptible), the warm tooltip glides to "New window right" and the accessible name follows. Releasing, or moving away, turns them back. Option-click opens a full-size window beside (below), plain click divides. Reduced motion swaps at once. Key listeners exist only while the pointer is over a window.
Attached: hover-split (tooltip "Split right"), morph-mid (70 ms after pressing Option), option-held, option-held-full, release-mid, released, option-click-new-window, video option-split-buttons.
window-option-new.spec.ts 6/6: icons, tooltip and name change and revert; frames between the two sizes exist (a continuous motion, min frame width stays above 7 of 8); far from the buttons nothing happens, moving in/out while held morphs; Option-click beside grows the footprint by a full column and a plain click keeps it; Option-click below adds a full-size window; reduced motion.
Note on the tooltip: a first capture showed "New window rig…" while the tooltip eased to its longer width; fixed in 7e0497605 (the label clips instead of ellipsizing during the transition).

**J52 evidence: Option on the split buttons** (branch `mockups-2026-10-31`, head `7e0497605`; production build, Chromium 155 headless; 1440×900 DPR 2, cropped header shots at DPR 2 plus a full frame; Playwright video). A phone has no Option key, so there is no phone capture (its canvas row carries the other pair). Not judged as final visuals. Related: #242. What it does: with Option (Alt on Windows and Linux) held and the pointer within 28 px of a window's header actions, the split right / split down icons morph into new window right / below (the frame shrinks to what the first window keeps, the divider becomes the plus's upright, the crossbar grows out of it; one shared spring, interruptible), the warm tooltip glides to "New window right" and the accessible name follows. Releasing, or moving away, turns them back. Option-click opens a full-size window beside (below), plain click divides. Reduced motion swaps at once. Key listeners exist only while the pointer is over a window. Attached: `hover-split` (tooltip "Split right"), `morph-mid` (70 ms after pressing Option), `option-held`, `option-held-full`, `release-mid`, `released`, `option-click-new-window`, video `option-split-buttons`. `window-option-new.spec.ts` 6/6: icons, tooltip and name change and revert; frames between the two sizes exist (a continuous motion, min frame width stays above 7 of 8); far from the buttons nothing happens, moving in/out while held morphs; Option-click beside grows the footprint by a full column and a plain click keeps it; Option-click below adds a full-size window; reduced motion. Note on the tooltip: a first capture showed "New window rig…" while the tooltip eased to its longer width; fixed in `7e0497605` (the label clips instead of ellipsizing during the transition).
Author
Owner

J57 progress (branch ui-approved-splitopt-224, pushed; base ui-approved-189-212-216 @ 995d28722, which is J56's work on the shared sidebar card and key/value list; origin/dev is f4b61cd6c).

Done and pushed:

  • 51d39758b Option (macOS) / Alt (Windows, Linux) on the split buttons, in the product (#280).
  • f25ade825 the shared pieces the card stands on (card morph, scroll fade, inline rename, value runs; entity palette tokens for tag cards).
  • dcfa9692f the card from the person's pill, concerns in the value, a card per tag in "Logs in as".
  • 6222dd1c5 the mock double with the worst-case fixture (14 tags, 1,202 machines, 12 accounts) + account-card.spec.ts.
  • 425a3aa00 TASTE: new row (concern stated in the value; no "checked N days ago") and row 71 reaffirmed for this card.

Findings (evidence):

  1. The product has no data for most of the card. identity carries only display_name and username; the server has no endpoint for passkey count, recovery-key state, devices, email, picture, or "logs in as" (grep of crates/infraternal-server routes, and settings-body.on-demand.ts says "the product has no facts to show yet"). So the product card shows name, username and sign out only, and draws no row or control it cannot fill (identityModel in canvas/account/index.ts). The full card is reachable on the mock double (?mock=1&proto=clusters&account=1, &data=worst|empty|one). Needs a backend grill before the rows can be real.
  2. Why every machine was one colour: the mockup's fixtures had no tags at all, so every host chip drew the neutral fill. TASTE row 71 already says host = linear gradient of its tags. The fixtures now carry tags.
  3. Overlap with J56: both of us port the sidebar card / key-value list. I rebased on J56's pushed commits and copied only files J56 does not have (identical copies of the mockup's files), so they should merge cleanly; the orchestrator may see small styles.css conflicts.

Choices for the owner (not decided by me): recovery key counts as stale after 90 days (RECOVERY_STALE_DAYS); only variant A (chips) of "Logs in as" ships, B and C were scaffolding; the username-change flow and Settings, Account stay in the mockup (D283 Q3 needs a server design).

Next: specs green, captures at 1440x900 DPR 2 and 393x852 DPR 3, scripts/verify fast on compute01. Logs: ~/j57-pw2.log on infraternal-dev-02-std; evidence in ~/.cache/codex-scratch/j57-evidence.

**J57 progress** (branch `ui-approved-splitopt-224`, pushed; base `ui-approved-189-212-216` @ `995d28722`, which is J56's work on the shared sidebar card and key/value list; origin/dev is `f4b61cd6c`). Done and pushed: - `51d39758b` Option (macOS) / Alt (Windows, Linux) on the split buttons, in the product (#280). - `f25ade825` the shared pieces the card stands on (card morph, scroll fade, inline rename, value runs; entity palette tokens for tag cards). - `dcfa9692f` the card from the person's pill, concerns in the value, a card per tag in "Logs in as". - `6222dd1c5` the mock double with the worst-case fixture (14 tags, 1,202 machines, 12 accounts) + `account-card.spec.ts`. - `425a3aa00` TASTE: new row (concern stated in the value; no "checked N days ago") and row 71 reaffirmed for this card. **Findings (evidence):** 1. The product has no data for most of the card. `identity` carries only `display_name` and `username`; the server has no endpoint for passkey count, recovery-key state, devices, email, picture, or "logs in as" (grep of `crates/infraternal-server` routes, and `settings-body.on-demand.ts` says "the product has no facts to show yet"). So the product card shows name, username and sign out only, and draws no row or control it cannot fill (`identityModel` in `canvas/account/index.ts`). The full card is reachable on the mock double (`?mock=1&proto=clusters&account=1`, `&data=worst|empty|one`). **Needs a backend grill** before the rows can be real. 2. Why every machine was one colour: the mockup's fixtures had no tags at all, so every host chip drew the neutral fill. TASTE row 71 already says host = linear gradient of its tags. The fixtures now carry tags. 3. Overlap with J56: both of us port the sidebar card / key-value list. I rebased on J56's pushed commits and copied only files J56 does not have (identical copies of the mockup's files), so they should merge cleanly; the orchestrator may see small `styles.css` conflicts. **Choices for the owner** (not decided by me): recovery key counts as stale after 90 days (`RECOVERY_STALE_DAYS`); only variant A (chips) of "Logs in as" ships, B and C were scaffolding; the username-change flow and Settings, Account stay in the mockup (D283 Q3 needs a server design). Next: specs green, captures at 1440x900 DPR 2 and 393x852 DPR 3, `scripts/verify fast` on compute01. Logs: `~/j57-pw2.log` on infraternal-dev-02-std; evidence in `~/.cache/codex-scratch/j57-evidence`.
Author
Owner

Head 4c389a2f4 on mockups-2026-10-31: the Option morph's tooltip fix is in 7e0497605; full finish summary and the gate output are on #242 (comment of J52).

Head `4c389a2f4` on `mockups-2026-10-31`: the Option morph's tooltip fix is in `7e0497605`; full finish summary and the gate output are on #242 (comment of J52).
Author
Owner

J57 evidence: Option / Alt on the split buttons (production build, Chromium, 1440x900 DPR 2; Mac keyboard profile and Windows keyboard profile via navigator.platform). Rest, key held (the pair turned into new window right / below, warm tooltip changed), five mid-morph frames, the phone header (two actions, no modifier), and a video: Alt held near the buttons, release, then Alt-click opening a new window beside. Spec: window-option-new.spec.ts (14 tests: Mac/Windows/Linux, key claim, keyboard focus, shortcuts panel wording, reduced motion). Alt-click is intercepted by some Linux window managers (KDE's default Alt-drag); the four actions keep their own keys and palette entries.

**J57 evidence: Option / Alt on the split buttons** (production build, Chromium, 1440x900 DPR 2; Mac keyboard profile and Windows keyboard profile via navigator.platform). Rest, key held (the pair turned into new window right / below, warm tooltip changed), five mid-morph frames, the phone header (two actions, no modifier), and a video: Alt held near the buttons, release, then Alt-click opening a new window beside. Spec: `window-option-new.spec.ts` (14 tests: Mac/Windows/Linux, key claim, keyboard focus, shortcuts panel wording, reduced motion). Alt-click is intercepted by some Linux window managers (KDE's default Alt-drag); the four actions keep their own keys and palette entries.
Author
Owner

J57 finished. Branch ui-approved-splitopt-224, head fbc39ec60 (stacked on J56's ui-approved-189-212-216, which is on dev f4b61cd6c; J56's version pill is in the base, so I made the two cards one-at-a-time). Branch ui-approved-splitopt-224-on-dev (7a64aaa3e) is the same work merged with current origin/dev (7bf03044f), the only conflict being two TASTE rows added at the same spot; it is what the gate below ran on.

Commits (atomic): Option/Alt split buttons (#280); shared pieces for the card; the card from the pill, concerns in values, a card per tag; mock double + worst-case fixture + spec; TASTE rows (separate docs commit); contrast + Escape fix; key/value column fix; value-run redraw fix; count spacing; one card at a time with the version panel; spec waits.

Gate. scripts/verify fast on compute01 (x86_64) at 7a64aaa3e ran to its last row (TypeScript protocol tests: 39 pass, 0 fail); the log has no FAILED/panicked/error line. I did not capture its exit code. compute01 was unreachable for ~1 h in the middle (overloaded, not rebooted) and the first run was lost; the second queued ~75 min. Verbatim from that log:

JavaScript transfer, critical: identity=992782 gzip=308380/327680 brotli=256828/273408 bytes
JavaScript transfer, deferred: identity=172533 gzip=59954/61440 brotli=53231/55296 bytes
JavaScript transfer, onDemand: identity=259835 gzip=103090/20480 brotli=91900/18432 bytes
JavaScript transfer, onDemand largest chunk chunk-wvdhacx3.js: gzip=15467/20480 brotli=13811/18432 bytes

Against J56's head (critical gzip 305,444, identity 986,973) this branch adds +2,936 B gzip / +5,809 B identity to the critical path; that is the Option/Alt morph in window-card (icon-morph, key handling). On J56's old 300 KiB cap it is over (308,385 > 307,200); under dev's new 320 KiB cap (327,680) it passes. The person's card adds nothing critical (own chunk and stylesheet, loaded on idle). No Rust changed, so no static-size delta.

Browser specs (production build, Chromium, dev-02): account-card.spec.ts (17) + window-option-new.spec.ts (14) + captures (17): 48 run, 47 passed first time and the one failure (a spec race on the product mount) re-ran green; the account spec also covers 4.5:1 text contrast measured on the real composited background in ink and paper, the worst-case fixture bounds, a 393x852 phone, the product path, and the version panel.

Decisions for kayg:

  1. The product has no server data for passkeys, recovery-key state, devices, email, picture or "logs in as"; the product card shows name, username and sign out only and draws no row or control it cannot fill. The full card is on the mock double (?mock=1&proto=clusters&account=1, &data=worst|empty|one). Needs a backend grill.
  2. A recovery key counts as stale after 90 days (RECOVERY_STALE_DAYS).
  3. Only variant A (chips) of "Logs in as" shipped; the username-change flow and Settings, Account stay in the mockup (D283 Q3).
  4. A machine with several tags is listed in each of its tags' cards; the palette has 12 colours, so two tags can share one.
  5. Alt-click can be intercepted by some Linux window managers (KDE's default Alt-drag); the four actions keep their own keys and palette entries.
**J57 finished.** Branch `ui-approved-splitopt-224`, head `fbc39ec60` (stacked on J56's `ui-approved-189-212-216`, which is on dev `f4b61cd6c`; J56's version pill is in the base, so I made the two cards one-at-a-time). Branch `ui-approved-splitopt-224-on-dev` (`7a64aaa3e`) is the same work merged with current `origin/dev` (`7bf03044f`), the only conflict being two TASTE rows added at the same spot; it is what the gate below ran on. Commits (atomic): Option/Alt split buttons (#280); shared pieces for the card; the card from the pill, concerns in values, a card per tag; mock double + worst-case fixture + spec; TASTE rows (separate docs commit); contrast + Escape fix; key/value column fix; value-run redraw fix; count spacing; one card at a time with the version panel; spec waits. **Gate.** `scripts/verify fast` on compute01 (x86_64) at `7a64aaa3e` ran to its last row (TypeScript protocol tests: 39 pass, 0 fail); the log has no FAILED/panicked/error line. I did not capture its exit code. compute01 was unreachable for ~1 h in the middle (overloaded, not rebooted) and the first run was lost; the second queued ~75 min. Verbatim from that log: ``` JavaScript transfer, critical: identity=992782 gzip=308380/327680 brotli=256828/273408 bytes JavaScript transfer, deferred: identity=172533 gzip=59954/61440 brotli=53231/55296 bytes JavaScript transfer, onDemand: identity=259835 gzip=103090/20480 brotli=91900/18432 bytes JavaScript transfer, onDemand largest chunk chunk-wvdhacx3.js: gzip=15467/20480 brotli=13811/18432 bytes ``` Against J56's head (critical gzip 305,444, identity 986,973) this branch adds **+2,936 B gzip / +5,809 B identity to the critical path**; that is the Option/Alt morph in `window-card` (icon-morph, key handling). On J56's old 300 KiB cap it is over (308,385 > 307,200); under dev's new 320 KiB cap (327,680) it passes. The person's card adds nothing critical (own chunk and stylesheet, loaded on idle). No Rust changed, so no static-size delta. **Browser specs** (production build, Chromium, dev-02): `account-card.spec.ts` (17) + `window-option-new.spec.ts` (14) + captures (17): 48 run, 47 passed first time and the one failure (a spec race on the product mount) re-ran green; the account spec also covers 4.5:1 text contrast measured on the real composited background in ink and paper, the worst-case fixture bounds, a 393x852 phone, the product path, and the version panel. **Decisions for kayg:** 1. The product has no server data for passkeys, recovery-key state, devices, email, picture or "logs in as"; the product card shows name, username and sign out only and draws no row or control it cannot fill. The full card is on the mock double (`?mock=1&proto=clusters&account=1`, `&data=worst|empty|one`). Needs a backend grill. 2. A recovery key counts as stale after 90 days (`RECOVERY_STALE_DAYS`). 3. Only variant A (chips) of "Logs in as" shipped; the username-change flow and Settings, Account stay in the mockup (D283 Q3). 4. A machine with several tags is listed in each of its tags' cards; the palette has 12 colours, so two tags can share one. 5. Alt-click can be intercepted by some Linux window managers (KDE's default Alt-drag); the four actions keep their own keys and palette entries.
Sign in to join this conversation.
No labels
launch-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/infraternal#280
No description provided.