Skip to content

Commit 277f126

Browse files
fix: improve worktree path readability (#1311)
Long worktree paths often share a low-signal prefix, while the final checkout or branch segments identify the context a developer actually needs. This keeps that useful tail visible in the compact session-vitals panel and preserves the actual order of mixed-direction paths through explicit LTR isolation. The complete value is available through the shared tooltip for hover and keyboard focus. This worktree-specific tooltip can use up to half the viewport, expand left beyond the sidebar, and wrap long unbroken segments at the cap while remaining viewport-contained with its arrow anchored to the trigger; kit-ui’s generic 280-pixel tooltip default remains unchanged. Existing copy behavior and the empty-path state are preserved. <sup>generated by a clanker</sup>
1 parent 958534b commit 277f126

6 files changed

Lines changed: 503 additions & 9 deletions

File tree

cmd/testfixture/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ func createDurationShowcaseFixture(
449449
Project: project,
450450
Machine: "test-machine",
451451
Agent: "claude",
452+
Cwd: "/workspace/مشروع/.worktrees/שלוםfeaturewithalongcheckoutnamefortooltipwrappingwithoutbreakopportunities",
452453
StartedAt: new(t0.Format(time.RFC3339Nano)),
453454
EndedAt: new(endParent.Format(time.RFC3339Nano)),
454455
MessageCount: len(parentMessages),
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
# Worktree Tooltip Width Implementation Plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans
4+
> to implement this plan directly in the current agent, task-by-task. Never use
5+
> subagent-driven development. Steps use checkbox (`- [ ]`) syntax for tracking.
6+
7+
**Goal:** Let the session-vitals worktree tooltip use up to half of the viewport
8+
so ordinary paths stay on one line while oversized paths wrap.
9+
10+
**Approved spec/design:**
11+
`docs/superpowers/specs/2026-07-30-worktree-tooltip-width-design.md`
12+
13+
**Architecture:** Keep kit-ui's generic tooltip sizing unchanged. Pass a
14+
dedicated class to the existing worktree `Tooltip`, then use the supported class
15+
hook to override only that popover's maximum width while retaining kit-ui's
16+
fixed viewport positioning and end-aligning its arrow with the trigger.
17+
18+
**Tech Stack:** Svelte 5, TypeScript, `@kenn-io/kit-ui`, Playwright, Vitest
19+
20+
## Global Constraints
21+
22+
- The generic kit-ui tooltip must retain its 280-pixel default maximum width.
23+
- The worktree tooltip maximum width is `min(50vw, calc(100vw - 32px))`.
24+
- Paths that fit within the cap stay on one line; longer paths wrap.
25+
- The tooltip may extend left of the analysis sidebar but must remain inside the
26+
viewport.
27+
- Unbroken path segments must wrap rather than overflow, and the tooltip arrow
28+
must remain over the worktree trigger.
29+
- Preserve the existing LTR-isolated path text, start truncation, hover and
30+
keyboard behavior, copy control, and empty state.
31+
32+
______________________________________________________________________
33+
34+
### Task 1: Make the worktree tooltip viewport-relative
35+
36+
**Files:**
37+
38+
- Modify: `cmd/testfixture/main.go`
39+
- Modify: `frontend/e2e/session-timing.spec.ts`
40+
- Modify: `frontend/src/lib/components/content/SessionVitals.svelte`
41+
42+
**Interfaces:**
43+
44+
- Consumes: kit-ui `Tooltip`'s supported `class?: string` and `align` props plus
45+
existing fixed-position viewport clamping.
46+
47+
- Produces: a `worktree-path-tooltip` popover class scoped to the session-vitals
48+
worktree tooltip.
49+
50+
- [ ] **Step 1: Make the fixture deterministic and write the failing browser
51+
test**
52+
53+
In `cmd/testfixture/main.go`, lengthen the showcase session's existing synthetic
54+
worktree path to exactly this value:
55+
56+
```go
57+
Cwd: "/workspace/مشروع/.worktrees/שלוםfeaturewithalongcheckoutnamefortooltipwrappingwithoutbreakopportunities",
58+
```
59+
60+
Update `SHOWCASE_WORKTREE` in `frontend/e2e/session-timing.spec.ts` to the same
61+
literal:
62+
63+
```ts
64+
const SHOWCASE_WORKTREE =
65+
"/workspace/مشروع/.worktrees/שלוםfeaturewithalongcheckoutnamefortooltipwrappingwithoutbreakopportunities";
66+
```
67+
68+
Add this test after the existing mixed-direction worktree-path regression in
69+
`frontend/e2e/session-timing.spec.ts`:
70+
71+
```ts
72+
test("uses available viewport width for the worktree tooltip", async ({
73+
page,
74+
}) => {
75+
await gotoShowcase(page);
76+
77+
const panel = page.locator("aside.vitals");
78+
const path = page.locator(".context-value--path");
79+
const trigger = page.locator(".context-tooltip .kit-tooltip-trigger");
80+
await path.hover();
81+
82+
const tooltip = page.getByRole("tooltip");
83+
await expect(tooltip).toHaveText(SHOWCASE_WORKTREE);
84+
const panelLeft = await panel.evaluate(
85+
(element) => element.getBoundingClientRect().left,
86+
);
87+
const wideLayout = await tooltip.evaluate((element) => {
88+
const range = document.createRange();
89+
range.selectNodeContents(element);
90+
const lineTops = Array.from(
91+
range.getClientRects(),
92+
(rect) => Math.round(rect.top),
93+
);
94+
const rect = element.getBoundingClientRect();
95+
const arrowStyle = getComputedStyle(element, "::before");
96+
const arrowWidth = Number.parseFloat(arrowStyle.width);
97+
const arrowCenter =
98+
arrowStyle.left === "auto"
99+
? rect.right - Number.parseFloat(arrowStyle.right) - arrowWidth / 2
100+
: rect.left + Number.parseFloat(arrowStyle.left) + arrowWidth / 2;
101+
return {
102+
arrowCenter,
103+
left: rect.left,
104+
lineCount: new Set(lineTops).size,
105+
right: rect.right,
106+
viewportWidth: window.innerWidth,
107+
width: rect.width,
108+
};
109+
});
110+
const triggerBounds = await trigger.boundingBox();
111+
expect(triggerBounds).not.toBeNull();
112+
113+
expect(wideLayout.width).toBeLessThanOrEqual(
114+
wideLayout.viewportWidth * 0.5 + 1,
115+
);
116+
expect(wideLayout.lineCount).toBe(1);
117+
expect(wideLayout.left).toBeGreaterThanOrEqual(0);
118+
expect(wideLayout.right).toBeLessThanOrEqual(wideLayout.viewportWidth);
119+
expect(wideLayout.left).toBeLessThan(panelLeft);
120+
expect(wideLayout.arrowCenter).toBeGreaterThanOrEqual(triggerBounds!.x);
121+
expect(wideLayout.arrowCenter).toBeLessThanOrEqual(
122+
triggerBounds!.x + triggerBounds!.width,
123+
);
124+
125+
await page.setViewportSize({ width: 1000, height: 900 });
126+
await expect(path).toBeVisible();
127+
await path.hover();
128+
const narrowLayout = await tooltip.evaluate((element) => {
129+
const range = document.createRange();
130+
range.selectNodeContents(element);
131+
const lineTops = Array.from(
132+
range.getClientRects(),
133+
(rect) => Math.round(rect.top),
134+
);
135+
const rect = element.getBoundingClientRect();
136+
return {
137+
clientWidth: element.clientWidth,
138+
left: rect.left,
139+
lineCount: new Set(lineTops).size,
140+
right: rect.right,
141+
scrollWidth: element.scrollWidth,
142+
viewportWidth: window.innerWidth,
143+
width: rect.width,
144+
};
145+
});
146+
147+
expect(narrowLayout.width).toBeLessThanOrEqual(
148+
narrowLayout.viewportWidth * 0.5 + 1,
149+
);
150+
expect(narrowLayout.lineCount).toBeGreaterThan(1);
151+
expect(narrowLayout.scrollWidth).toBeLessThanOrEqual(
152+
narrowLayout.clientWidth + 1,
153+
);
154+
expect(narrowLayout.left).toBeGreaterThanOrEqual(0);
155+
expect(narrowLayout.right).toBeLessThanOrEqual(narrowLayout.viewportWidth);
156+
});
157+
```
158+
159+
- [ ] **Step 2: Run the browser test to verify it fails**
160+
161+
Run:
162+
163+
```bash
164+
cd frontend
165+
npm run e2e -- session-timing.spec.ts --project=chromium \
166+
--grep "uses available viewport width for the worktree tooltip"
167+
```
168+
169+
Expected: FAIL because the current 280-pixel maximum wraps the fixture path at
170+
the default 1600-pixel viewport, so `wideLayout.lineCount` is greater than 1.
171+
After adding only the width override, the arrow assertion also fails until the
172+
tooltip is end-aligned.
173+
174+
- [ ] **Step 3: Add the per-use width override**
175+
176+
In `frontend/src/lib/components/content/SessionVitals.svelte`, pass the popover
177+
class through the existing tooltip:
178+
179+
```svelte
180+
<Tooltip
181+
text={session.cwd}
182+
focusable
183+
align="end"
184+
class="worktree-path-tooltip"
185+
>
186+
```
187+
188+
Add the scoped global rule next to the existing `.context-tooltip` integration
189+
styles:
190+
191+
```css
192+
.context-tooltip :global(.worktree-path-tooltip) {
193+
max-width: min(50vw, calc(100vw - 32px));
194+
overflow-wrap: anywhere;
195+
}
196+
```
197+
198+
Do not modify kit-ui source or the shared `.kit-tooltip` rule.
199+
200+
- [ ] **Step 4: Run the focused browser regression in both engines**
201+
202+
Run:
203+
204+
```bash
205+
cd frontend
206+
npm run e2e -- session-timing.spec.ts --project=chromium \
207+
--grep "uses available viewport width for the worktree tooltip"
208+
npm run e2e -- session-timing.spec.ts --project=webkit \
209+
--grep "uses available viewport width for the worktree tooltip"
210+
```
211+
212+
Expected: PASS in Chromium and WebKit. The default viewport renders one line and
213+
positions the tooltip left of the sidebar; the 1000-pixel viewport wraps the
214+
same complete path without exceeding 500 pixels or overflowing its unbroken
215+
segment. Both layouts remain viewport-contained, with the arrow over the
216+
trigger.
217+
218+
- [ ] **Step 5: Run the affected component and integration checks**
219+
220+
Run:
221+
222+
```bash
223+
cd frontend
224+
npm test -- --run src/lib/components/content/SessionVitals.test.ts
225+
npm run e2e -- session-timing.spec.ts --project=chromium
226+
npm run check
227+
npm run check:kit-ui
228+
cd ..
229+
go fmt ./...
230+
go vet ./...
231+
```
232+
233+
Expected: 12 component tests and all 7 Chromium session-vitals tests pass,
234+
Svelte reports 0 errors, kit-ui reports 0 findings, and Go formatting and vet
235+
complete successfully. Existing unrelated Svelte unused-selector warnings may
236+
remain.
237+
238+
- [ ] **Step 6: Commit the implementation**
239+
240+
```bash
241+
git add cmd/testfixture/main.go \
242+
frontend/e2e/session-timing.spec.ts \
243+
frontend/src/lib/components/content/SessionVitals.svelte
244+
git commit -m "fix: widen worktree path tooltip" \
245+
-m "The shared 280-pixel default suits prose but forces filesystem paths to wrap inside the analysis sidebar. Give only the worktree tooltip a viewport-relative limit so ordinary paths stay on one line while oversized paths wrap at half the viewport."
246+
```
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Worktree Tooltip Width Design
2+
3+
## Problem
4+
5+
The shared kit-ui tooltip limits all content to 280 pixels. That is useful for
6+
short explanatory copy, but it forces ordinary filesystem paths to wrap inside
7+
the narrow session-analysis sidebar. The tooltip uses fixed positioning and can
8+
already move outside that sidebar, so the generic width limit wastes available
9+
viewport space.
10+
11+
## Design
12+
13+
Keep the shared tooltip default unchanged. Pass a dedicated class through the
14+
worktree tooltip's supported `class` prop and override only that tooltip's
15+
maximum width:
16+
17+
```css
18+
max-width: min(50vw, calc(100vw - 32px));
19+
overflow-wrap: anywhere;
20+
```
21+
22+
The tooltip retains kit-ui's existing `width: max-content`. A path that fits
23+
within half of the viewport therefore stays on one line. A longer path wraps
24+
once it reaches the 50 percent cap, including when a single path segment has no
25+
natural line-breaking opportunity. The tooltip uses end alignment so kit-ui's
26+
fixed-position floating logic anchors its right edge and arrow to the trigger
27+
near the viewport's right edge. This allows the bubble to extend outside the
28+
sidebar without leaving the viewport or visually detaching its arrow from the
29+
worktree row.
30+
31+
The displayed path and tooltip content remain LTR-isolated technical
32+
identifiers. Hover, keyboard focus, copy behavior, and the compact truncated row
33+
remain unchanged.
34+
35+
## Verification
36+
37+
Extend the session-vitals browser coverage to assert that the worktree tooltip:
38+
39+
- contains the complete path;
40+
- never exceeds 50 percent of the viewport width;
41+
- stays on one line when the fixture path fits under that limit;
42+
- extends left of the sidebar when additional width is needed;
43+
- remains inside the viewport with its arrow over the trigger; and
44+
- wraps an unbroken path segment without overflowing when synthetic content
45+
exceeds the viewport-relative cap.
46+
47+
Keep the existing mixed-direction path geometry regression and component tooltip
48+
interaction test.

0 commit comments

Comments
 (0)