jobizzness / jobizzness/bench

Roster: let the developer drag the left pane wider, and start it 50px wider than it is

Open
#145 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
1
Forks
0
Avg merge
1h 21m
Merged PRs (30d)
41

Description

The roster column is a fixed width nobody can change, and it is slightly too narrow for the labels it carries.

## Evidence

Line numbers are on `main` — read with `git show main:src/client/styles.css`. **Do not read the working tree at `/var/www/bench` as `main`**: it is checked out on an unmerged branch with uncommitted changes.

**1. The width is a constant in one place.** `styles.css:453`:

```css
#app {
display: grid;
grid-template-columns: 276px 1fr;
```

**2. Below 720px there is no second column to resize.** `styles.css:4126` turns `#app` into a flex column showing one pane at a time, chosen by `data-pane`. Whatever this adds must be inert there.

**3. The edge to grab already exists.** `#roster` (`styles.css:475`) carries `border-right: 1px solid var(--line)`, which is the line a resize handle should sit on.

**4. There is a house pattern for every piece of this.**

- Remembering a per-browser preference: `src/client/remembered.ts` — `recall(key, fallback)` / `remember(key, value)`, both guarded, prefix `bench:`. This is explicitly for "how one person has arranged the view in front of them", which is exactly this.
- Driving CSS from JS: `useVisualViewportHeight.ts:19` sets a custom property on `document.documentElement` and removes it on teardown. `styles.css:4138` then reads `var(--visual-viewport-height, 100dvh)` with a fallback, so the CSS still stands alone.
- A pointer drag: `useSheetDismissGestures.ts` wires the DOM events, and the arithmetic lives in `sheetGesture.ts` as pure functions, "because jsdom has no layout, no compositor and no touch". Do the same here — the clamping is testable, the dragging is not.

## What to build

A draggable divider between the roster and the stage, a wider default, and a width this browser remembers.

## Acceptance criteria

- [ ] With nothing remembered, the roster is **326px** — the current 276px plus 50.
- [ ] Dragging the roster's right edge resizes it live, and the stage takes the rest.
- [ ] The width is clamped: no narrower than **240px**, no wider than **min(560px, 45vw)**. A window narrowed until the clamp bites must not leave the stage unusable.
- [ ] The chosen width is remembered per browser through `remembered.ts` and restored on reload. It is **not** a daemon setting and does not travel between machines.
- [ ] No flash of the old width on load: the CSS default is the new one, so a restore either matches it or replaces it before anything reads as wrong.
- [ ] The handle is reachable without a mouse: focusable, `role="separator"`, `aria-orientation="vertical"`, and Left/Right arrows move it in steps.
- [ ] Double-clicking the handle returns the roster to 326px and forgets the remembered value.
- [ ] Dragging does not select text, and the cursor reads `col-resize` over the handle.
- [ ] Below 720px nothing changes: no handle, no custom property applied, the one-pane-at-a-time layout untouched. `#app`'s grid override at `styles.css:4126` is not edited.
- [ ] The clamping is a pure function in its own module with unit tests: below the minimum, above the maximum, above the viewport-relative maximum, and a remembered value that is now out of range because the window shrank.
- [ ] Tests also cover the remembered round trip and the default when storage is empty or holds nonsense.
- [ ] New code lives in its own small modules — a hook and a handle component — rather than growing `App.tsx`. The cockpit's components are kept small and single-purpose.

## Out of scope

- Resizing anything else: the stage's inner layout, the composer, dialogs.
- Touch dragging on a phone. Below the breakpoint there is no second pane.
- Collapsing the roster to zero, or a hide/show toggle. Only a resize.
- Making the width a daemon setting, or syncing it between devices.
- Any change to what the roster contains.
- Animating the resize.

## Verification

```
pnpm typecheck
pnpm test
pnpm build
```

Manual, which a green build will not catch:

1. Drag the divider both ways; the stage keeps up and nothing overlaps.
2. Reload — the width you left it at is the width it comes back at.
3. Double-click the divider — back to 326px, and a reload still says 326px.
4. Narrow the window below 720px — the divider is gone and the panes behave as they do today.
5. Tab to the divider and press Left/Right — it moves.

## Branch

**Branch from `main`.** Work in your own worktree; do not build on whatever `/var/www/bench` currently has checked out.

## Related

- #141 — also rewriting client UI, but in `ModelDialog.tsx`; no overlap with `styles.css:453` or `App.tsx`'s grid.

Contributor guide

Open the contributing guide

Research direction

Start from styles.css:453 and #roster at styles.css:475, reading main with git show as instructed. Then inspect src/client/remembered.ts, useVisualViewportHeight.ts, useSheetDismissGestures.ts, sheetGesture.ts, and App.tsx for the existing patterns. Done means the responsive divider, keyboard behavior, persistence, clamping tests, and pnpm typecheck, pnpm test, and pnpm build all pass without changing the below-720px layout.

Written by the indexing model from the issue text.

Assessment

Tech stack
css, typescript
Domain
accessibility, frontend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.