Skip to content

Commit 3c1ac3a

Browse files
author
DavidQ
committed
Polish Session Inspector V2 controls filters and details copy action - PR_26128_017-session-inspector-v2-controls-polish
1 parent 239892b commit 3c1ac3a

8 files changed

Lines changed: 164 additions & 5 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# PR_26128_017 Playwright Session Inspector V2 Controls Polish
2+
3+
## Command
4+
`npm run test:workspace-v2`
5+
6+
## Result
7+
PASS: 14/14 tests passed.
8+
9+
## Targeted Coverage
10+
- Controls accordion contains `Refresh` and `Delete All`.
11+
- Controls accordion no longer contains `Clear Status`.
12+
- Status header contains `Status` and `Clear Status`.
13+
- Refresh, Delete All, and Clear Status fit without text overflow.
14+
- Storage label and dropdown share a row.
15+
- Filter label and textbox share a row.
16+
- Details header includes `Copy` after the collapse/X control.
17+
- Copy writes the current Details JSON to the clipboard.
18+
- Successful copy logs `OK`.
19+
- Copy with empty Details logs `WARN`.
20+
- Per-tile Delete and Delete All still work.
21+
- Fullscreen shell behavior and workspace Return nav behavior are still covered by the Session Inspector V2 launch test.
22+
23+
## Skipped
24+
Full samples smoke test was skipped as requested because this PR changes Session Inspector V2 controls and Details copy behavior only, without touching sample JSON or sample runtime paths.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# PR_26128_017 Session Inspector V2 Controls Polish
2+
3+
## Summary
4+
Session Inspector V2 controls were tightened and Details now supports clipboard copy.
5+
6+
## Changes
7+
- Moved `Clear Status` out of the Controls accordion.
8+
- Added `Clear Status` to the Status header/action area.
9+
- Reduced Session Inspector V2 button text padding for compact control rows.
10+
- Kept `Refresh` and `Delete All` side by side in Controls.
11+
- Put the Storage label and dropdown on the same row.
12+
- Put the Filter label and textbox on the same row.
13+
- Added a `Copy` button in the Details header immediately after the collapse/X control.
14+
- Copy writes the currently shown Details JSON to the clipboard.
15+
- Copy logs:
16+
- `OK` when Details content is copied.
17+
- `WARN` when no Details content is shown.
18+
- `FAIL` when the clipboard API fails or is unavailable.
19+
- Preserved Delete All, per-tile Delete, fullscreen behavior, and workspace Return nav behavior.
20+
21+
## Boundaries
22+
- No cross-tool communication was added.
23+
- No sample JSON was modified.
24+
- Roadmap content was not modified.
25+
26+
## Validation
27+
- `npm run test:workspace-v2`: PASS, 14 tests passed.
28+
- Verified `Clear Status` moved from Controls to Status.
29+
- Verified Refresh, Delete All, and Clear Status fit without text overflow.
30+
- Verified Storage label/dropdown are on the same line.
31+
- Verified Filter label/textbox are on the same line.
32+
- Verified Details Copy appears after the collapse control.
33+
- Verified Copy copies the Details content and logs the result.
34+
35+
## Skipped
36+
Full samples smoke test was skipped as requested. This PR is scoped to Session Inspector V2 control placement, filter layout, and Details copy behavior; sample runtime behavior is outside the changed surface.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,14 @@ test.describe("Workspace Manager V2 bootstrap", () => {
382382
const pageErrors = [];
383383
await page.setViewportSize({ height: 900, width: 1440 });
384384
await page.addInitScript(() => {
385+
Object.defineProperty(window.navigator, "clipboard", {
386+
configurable: true,
387+
value: {
388+
async writeText(value) {
389+
window.__sessionInspectorV2ClipboardText = value;
390+
}
391+
}
392+
});
385393
window.sessionStorage.setItem("session-inspector-v2-alpha", "true");
386394
window.sessionStorage.setItem("session-inspector-v2-beta", "plain beta value");
387395
window.sessionStorage.setItem("session-inspector-v2-gamma", JSON.stringify({ index: 3, wraps: true }));
@@ -415,7 +423,10 @@ test.describe("Workspace Manager V2 bootstrap", () => {
415423
await expect(page.locator("#clearSessionInspectorV2StatusButton")).toHaveText("Clear Status");
416424
await expect(page.locator("#sessionInspectorV2ControlsContent")).toContainText("Refresh");
417425
await expect(page.locator("#sessionInspectorV2ControlsContent")).toContainText("Delete All");
418-
await expect(page.locator("#sessionInspectorV2ControlsContent")).toContainText("Clear Status");
426+
await expect(page.locator("#sessionInspectorV2ControlsContent")).not.toContainText("Clear Status");
427+
await expect(page.locator(".session-inspector-v2__status-accordion-header")).toContainText("Status");
428+
await expect(page.locator(".session-inspector-v2__status-accordion-header #clearSessionInspectorV2StatusButton")).toHaveText("Clear Status");
429+
await expect(page.locator(".session-inspector-v2__details-accordion-header #copySessionInspectorV2DetailsButton")).toHaveText("Copy");
419430
expect(await page.locator(".session-inspector-v2__panel--left > .accordion-v2 > .accordion-v2__header > span:first-child").evaluateAll((labels) => labels.map((label) => label.textContent.trim()))).toEqual([
420431
"Controls",
421432
"Filters"
@@ -427,6 +438,46 @@ test.describe("Workspace Manager V2 bootstrap", () => {
427438
"(0) LocalStorage."
428439
]);
429440
await expect(page.locator("#statusLog")).toHaveValue(/OK Session Inspector V2 ready\. Storage is read\/delete\./);
441+
const controlsLayout = await page.evaluate(() => {
442+
const rectFor = (selector) => {
443+
const element = document.querySelector(selector);
444+
const rect = element.getBoundingClientRect();
445+
return {
446+
bottom: Math.round(rect.bottom),
447+
clientWidth: element.clientWidth,
448+
height: Math.round(rect.height),
449+
left: Math.round(rect.left),
450+
right: Math.round(rect.right),
451+
scrollWidth: element.scrollWidth,
452+
top: Math.round(rect.top)
453+
};
454+
};
455+
const refresh = rectFor("#refreshSessionInspectorV2Button");
456+
const deleteAll = rectFor("#deleteAllSessionInspectorV2Button");
457+
const clearStatus = rectFor("#clearSessionInspectorV2StatusButton");
458+
const storageLabel = rectFor('label[for="storageScopeSelect"] span');
459+
const storageSelect = rectFor("#storageScopeSelect");
460+
const filterLabel = rectFor('label[for="sessionInspectorV2FilterInput"] span');
461+
const filterInput = rectFor("#sessionInspectorV2FilterInput");
462+
const detailsIcon = rectFor(".session-inspector-v2__details-accordion-header .accordion-v2__icon");
463+
const copyButton = rectFor("#copySessionInspectorV2DetailsButton");
464+
return {
465+
buttonsFit: [refresh, deleteAll, clearStatus].every((rect) => rect.scrollWidth <= rect.clientWidth + 1),
466+
clearStatusCompact: clearStatus.height <= 34,
467+
copyAfterCollapseIcon: copyButton.left >= detailsIcon.right,
468+
deleteAllRightOfRefresh: deleteAll.left > refresh.right,
469+
filterSameLine: filterInput.top <= filterLabel.bottom && filterInput.bottom >= filterLabel.top,
470+
refreshDeleteSameLine: refresh.top === deleteAll.top,
471+
storageSameLine: storageSelect.top <= storageLabel.bottom && storageSelect.bottom >= storageLabel.top
472+
};
473+
});
474+
expect(controlsLayout.buttonsFit).toBe(true);
475+
expect(controlsLayout.clearStatusCompact).toBe(true);
476+
expect(controlsLayout.refreshDeleteSameLine).toBe(true);
477+
expect(controlsLayout.deleteAllRightOfRefresh).toBe(true);
478+
expect(controlsLayout.storageSameLine).toBe(true);
479+
expect(controlsLayout.filterSameLine).toBe(true);
480+
expect(controlsLayout.copyAfterCollapseIcon).toBe(true);
430481
const tileText = (await page.locator(".session-inspector-v2__entry-card").allTextContents()).join("\n");
431482
expect(tileText).not.toContain("plain beta value");
432483
expect(tileText).not.toContain("delta value that is long enough");
@@ -563,6 +614,10 @@ test.describe("Workspace Manager V2 bootstrap", () => {
563614

564615
await page.locator('[data-session-inspector-v2-entry-id="sessionStorage:session-inspector-v2-alpha"]').click();
565616
await expect(page.locator("#sessionInspectorV2DetailsOutput")).toContainText('"key": "session-inspector-v2-alpha"');
617+
const copiedDetailsText = await page.locator("#sessionInspectorV2DetailsOutput").textContent();
618+
await page.locator("#copySessionInspectorV2DetailsButton").click();
619+
await expect(page.locator("#statusLog")).toHaveValue(/OK Copied Details content to clipboard\./);
620+
expect(await page.evaluate(() => window.__sessionInspectorV2ClipboardText)).toBe(copiedDetailsText);
566621
await page.locator('[data-session-inspector-v2-delete-entry-id="sessionStorage:session-inspector-v2-alpha"]').click();
567622
await expect(page.locator("#sessionInspectorV2EntryList [data-session-inspector-v2-entry-id]")).toHaveCount(4);
568623
await expect(page.locator("#sessionInspectorV2DetailsOutput")).toContainText('"key": "session-inspector-v2-beta"');
@@ -595,6 +650,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
595650
"(0) LocalStorage."
596651
]);
597652
await expect(page.locator("#statusLog")).toHaveValue(/OK Deleted 4 shown storage entries\./);
653+
await page.locator("#copySessionInspectorV2DetailsButton").click();
654+
await expect(page.locator("#statusLog")).toHaveValue(/WARN Copy skipped: no Details content is shown\./);
598655
await page.locator("#deleteAllSessionInspectorV2Button").click();
599656
await expect(page.locator("#statusLog")).toHaveValue(/WARN Delete All skipped: no matching storage entries are shown\./);
600657
expect(await page.evaluate(() => window.localStorage.getItem("session-inspector-v2-local"))).toBe("local value");

tools/session-inspector-v2/index.html

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ <h2 class="tools-platform-frame__eyebrow">Session storage visibility</h2>
5252
<div class="session-inspector-v2__actions" aria-label="Session Inspector V2 controls">
5353
<button id="refreshSessionInspectorV2Button" type="button">Refresh</button>
5454
<button id="deleteAllSessionInspectorV2Button" type="button">Delete All</button>
55-
<button id="clearSessionInspectorV2StatusButton" type="button">Clear Status</button>
5655
</div>
5756
</div>
5857
</section>
@@ -98,10 +97,13 @@ <h2 class="tools-platform-frame__eyebrow">Session storage visibility</h2>
9897

9998
<aside class="session-inspector-v2__panel session-inspector-v2__panel--right tool-shell-common__fullscreen-panel tool-shell-common__fullscreen-side-panel tool-shell-common__fullscreen-panel-right" aria-label="Session details">
10099
<section class="accordion-v2 session-inspector-v2__accordion session-inspector-v2__accordion--fill is-open" data-accordion-v2-open="true">
101-
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="sessionInspectorV2DetailsContent">
100+
<div class="accordion-v2__header session-inspector-v2__details-accordion-header" role="button" tabindex="0" aria-expanded="true" aria-controls="sessionInspectorV2DetailsContent">
102101
<span>Details</span>
103-
<span class="accordion-v2__icon" aria-hidden="true">+</span>
104-
</button>
102+
<div class="session-inspector-v2__details-header-actions">
103+
<span class="accordion-v2__icon" aria-hidden="true">+</span>
104+
<button id="copySessionInspectorV2DetailsButton" type="button">Copy</button>
105+
</div>
106+
</div>
105107
<div id="sessionInspectorV2DetailsContent" class="accordion-v2__content">
106108
<pre id="sessionInspectorV2DetailsOutput" class="session-inspector-v2__output">{}</pre>
107109
</div>
@@ -112,6 +114,7 @@ <h2 class="tools-platform-frame__eyebrow">Session storage visibility</h2>
112114
<span>Status</span>
113115
<div class="session-inspector-v2__status-header-actions">
114116
<span class="accordion-v2__icon" aria-hidden="true">+</span>
117+
<button id="clearSessionInspectorV2StatusButton" type="button">Clear Status</button>
115118
</div>
116119
</div>
117120
<div id="sessionInspectorV2StatusContent" class="accordion-v2__content">

tools/session-inspector-v2/js/SessionInspectorV2App.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export class SessionInspectorV2App {
22
constructor({
33
accordions,
4+
copyDetailsButton,
45
deleteAllButton,
56
details,
67
entryList,
@@ -13,6 +14,7 @@ export class SessionInspectorV2App {
1314
windowRef = window
1415
}) {
1516
this.accordions = accordions;
17+
this.copyDetailsButton = copyDetailsButton;
1618
this.deleteAllButton = deleteAllButton;
1719
this.details = details;
1820
this.entries = [];
@@ -42,6 +44,9 @@ export class SessionInspectorV2App {
4244
onSelected: (entryId) => this.selectEntry(entryId)
4345
});
4446
this.refreshButton.addEventListener("click", () => this.refresh());
47+
this.copyDetailsButton.addEventListener("click", () => {
48+
void this.copyDetails();
49+
});
4550
this.deleteAllButton.addEventListener("click", () => this.deleteAllShownEntries());
4651
this.returnToWorkspaceButton.addEventListener("click", () => this.returnToWorkspace());
4752
this.refresh({ silent: true });
@@ -117,6 +122,24 @@ export class SessionInspectorV2App {
117122
this.refresh({ silent: true });
118123
}
119124

125+
async copyDetails() {
126+
const detailsText = this.details.text().trim();
127+
if (!detailsText || detailsText === "{}") {
128+
this.statusLog.warn("Copy skipped: no Details content is shown.");
129+
return;
130+
}
131+
if (typeof this.window.navigator?.clipboard?.writeText !== "function") {
132+
this.statusLog.fail("Copy failed: clipboard API is unavailable.");
133+
return;
134+
}
135+
try {
136+
await this.window.navigator.clipboard.writeText(detailsText);
137+
this.statusLog.ok("Copied Details content to clipboard.");
138+
} catch (error) {
139+
this.statusLog.fail(`Copy failed: ${error.message}`);
140+
}
141+
}
142+
120143
workspaceManagerUrl() {
121144
const url = new URL("../workspace-manager-v2/index.html", this.window.location.href);
122145
const params = new URLSearchParams(this.window.location.search || "");

tools/session-inspector-v2/js/bootstrap.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ function requireElement(selector) {
1818
window.addEventListener("DOMContentLoaded", () => {
1919
const app = new SessionInspectorV2App({
2020
accordions: Array.from(document.querySelectorAll(".accordion-v2"), (section) => new AccordionSection(section)),
21+
copyDetailsButton: requireElement("#copySessionInspectorV2DetailsButton"),
2122
details: new DetailsControl({
2223
output: requireElement("#sessionInspectorV2DetailsOutput")
2324
}),

tools/session-inspector-v2/js/controls/DetailsControl.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ export class DetailsControl {
77
this.output.textContent = "{}";
88
}
99

10+
text() {
11+
return String(this.output.textContent || "");
12+
}
13+
1014
render(entry) {
1115
if (!entry) {
1216
this.clear();

tools/session-inspector-v2/styles/sessionInspectorV2.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ button {
4545
background: var(--session-inspector-v2-surface);
4646
color: var(--session-inspector-v2-text);
4747
cursor: pointer;
48+
padding: 3px 7px;
4849
}
4950

5051
button:hover {
@@ -254,6 +255,8 @@ button:hover {
254255

255256
.session-inspector-v2__field {
256257
display: grid;
258+
grid-template-columns: 68px minmax(0, 1fr);
259+
align-items: center;
257260
gap: 6px;
258261
}
259262

@@ -275,6 +278,7 @@ button:hover {
275278

276279
.session-inspector-v2__field input,
277280
.session-inspector-v2__field select {
281+
width: 100%;
278282
min-height: 38px;
279283
padding: 6px 8px;
280284
}
@@ -387,12 +391,19 @@ button:hover {
387391
resize: vertical;
388392
}
389393

394+
.session-inspector-v2__details-header-actions,
390395
.session-inspector-v2__status-header-actions {
391396
display: inline-flex;
392397
align-items: center;
393398
gap: 8px;
394399
}
395400

401+
.session-inspector-v2__details-header-actions button,
402+
.session-inspector-v2__status-header-actions button {
403+
min-height: 30px;
404+
padding: 2px 7px;
405+
}
406+
396407
@media (max-width: 980px) {
397408
.session-inspector-v2__layout {
398409
grid-template-columns: 1fr;

0 commit comments

Comments
 (0)