Skip to content

Commit 993c979

Browse files
author
DavidQ
committed
Fix Session Inspector V2 workspace return layout accordions and V2 header styling - PR_26128_013-session-inspector-v2-layout-fix
1 parent 77f7442 commit 993c979

9 files changed

Lines changed: 252 additions & 37 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# PR_26128_013 Playwright Session Inspector V2 Layout Fix
2+
3+
## Command
4+
`npm run test:workspace-v2`
5+
6+
## Result
7+
PASS: 14/14 tests passed.
8+
9+
## Targeted Coverage
10+
- Session Inspector V2 launches from `tools/session-inspector-v2/index.html`.
11+
- `Return to Workspace` is present and returns to Workspace Manager V2 with launch context preserved.
12+
- The old loose action nav is absent.
13+
- The left column shows `Controls` before `Filters`.
14+
- `Refresh`, `Delete All`, and `Clear Status` are inside the `Controls` accordion.
15+
- The count display renders `(N) Entries shown.`, `(N) SessionStorage.`, and `(N) LocalStorage.` as separate lines.
16+
- Controls, Filters, Entries, Details, and Status accordions all open and close from header label and icon clicks.
17+
- Header frame and app shell use V2 boxed frame styling and existing theme tokens.
18+
- Per-entry Delete still removes the selected session entry, refreshes the UI immediately, and logs `OK`.
19+
- A simulated delete failure still logs `FAIL`.
20+
- Delete All still clears displayed entries, refreshes counts/details immediately, and logs `OK` or `WARN`.
21+
22+
## Skipped
23+
Full samples smoke test was skipped as requested because this PR changes Session Inspector V2 layout/control behavior and does not touch sample JSON or sample runtime paths.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# PR_26128_013 Session Inspector V2 Layout Fix
2+
3+
## Summary
4+
Session Inspector V2 layout was tightened so controls, counts, and the local shell match the V2 tool frame pattern.
5+
6+
## Changes
7+
- Added a `Return to Workspace` control to Session Inspector V2.
8+
- Moved `Refresh`, `Delete All`, and `Clear Status` into a `Controls` accordion above `Filters`.
9+
- Updated the count display to the requested three-line format:
10+
- `(N) Entries shown.`
11+
- `(N) SessionStorage.`
12+
- `(N) LocalStorage.`
13+
- Kept all Session Inspector V2 accordions mounted through the shared accordion behavior.
14+
- Fixed accordion header child-click handling so clicking header labels or icons toggles button-backed accordions, matching the Status accordion behavior.
15+
- Boxed the Session Inspector V2 header frame and app shell with V2 frame/display-box styling and existing theme tokens.
16+
- Preserved per-entry Delete and Delete All behavior.
17+
18+
## Boundaries
19+
- No cross-tool communication was added.
20+
- Preview Generator V2 behavior was not changed.
21+
- No sample JSON was modified.
22+
- Roadmap content was not modified.
23+
24+
## Validation
25+
- `npm run test:workspace-v2`: PASS, 14 tests passed.
26+
- Verified Session Inspector V2 has `Return to Workspace`.
27+
- Verified the count display uses the requested parenthesized three-line format.
28+
- Verified `Refresh`, `Delete All`, and `Clear Status` are inside the `Controls` accordion above `Filters`.
29+
- Verified every Session Inspector V2 accordion opens and closes from header label and icon clicks.
30+
- Verified below-header content uses boxed V2 frame styling.
31+
- Verified per-item Delete and Delete All still work and update the UI immediately.
32+
33+
## Skipped
34+
Full samples smoke test was skipped as requested. This PR is scoped to Session Inspector V2 layout, controls, and targeted Workspace Manager V2 launch coverage; sample runtime behavior is outside the changed surface.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ async function openToolsIndex(page) {
2525
return server;
2626
}
2727

28-
async function openSessionInspectorV2(page) {
28+
async function openSessionInspectorV2(page, query = "") {
2929
const server = await startRepoServer();
3030
await coverageReporter.start(page);
31-
await page.goto(`${server.baseUrl}/tools/session-inspector-v2/index.html`, { waitUntil: "networkidle" });
31+
await page.goto(`${server.baseUrl}/tools/session-inspector-v2/index.html${query}`, { waitUntil: "networkidle" });
3232
return server;
3333
}
3434

@@ -173,13 +173,15 @@ async function readWorkspaceSessionHydration(page) {
173173
async function expectSessionInspectorV2AccordionToggles(page, contentId) {
174174
const header = page.locator(`.accordion-v2__header[aria-controls="${contentId}"]`);
175175
const content = page.locator(`#${contentId}`);
176+
const label = header.locator("span").first();
177+
const icon = header.locator(".accordion-v2__icon");
176178
await expect(header).toBeVisible();
177179
await expect(content).toBeVisible();
178180
await expect(header).toHaveAttribute("aria-expanded", "true");
179-
await header.click();
181+
await label.click();
180182
await expect(content).toBeHidden();
181183
await expect(header).toHaveAttribute("aria-expanded", "false");
182-
await header.click();
184+
await icon.click();
183185
await expect(content).toBeVisible();
184186
await expect(header).toHaveAttribute("aria-expanded", "true");
185187
}
@@ -325,7 +327,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
325327
window.sessionStorage.setItem("session-inspector-v2-beta", "plain beta value");
326328
window.localStorage.setItem("session-inspector-v2-local", "local value");
327329
});
328-
const server = await openSessionInspectorV2(page);
330+
const server = await openSessionInspectorV2(page, "?launch=workspace&fromTool=workspace-manager-v2&hostContextId=session-inspector-v2-test-context&workspaceMode=uat");
329331

330332
page.on("pageerror", (error) => {
331333
pageErrors.push(error.message);
@@ -337,16 +339,33 @@ test.describe("Workspace Manager V2 bootstrap", () => {
337339
await expect(page.locator("h1")).not.toHaveText("Session Inspector");
338340
await expect(page.locator('link[href="./styles/sessionInspectorV2.css"]')).toHaveCount(1);
339341
await expect(page.locator('link[href="./styles/sessionInspector.css"]')).toHaveCount(0);
342+
await expect(page.locator(".session-inspector-v2__menu")).toHaveCount(0);
343+
await expect(page.locator("#returnToWorkspaceButton")).toHaveText("Return to Workspace");
340344
await expect(page.locator("#refreshSessionInspectorV2Button")).toHaveText("Refresh");
341345
await expect(page.locator("#deleteAllSessionInspectorV2Button")).toHaveText("Delete All");
342346
await expect(page.locator("#clearSessionInspectorV2StatusButton")).toHaveText("Clear Status");
347+
await expect(page.locator("#sessionInspectorV2ControlsContent")).toContainText("Return to Workspace");
348+
await expect(page.locator("#sessionInspectorV2ControlsContent")).toContainText("Refresh");
349+
await expect(page.locator("#sessionInspectorV2ControlsContent")).toContainText("Delete All");
350+
await expect(page.locator("#sessionInspectorV2ControlsContent")).toContainText("Clear Status");
351+
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([
352+
"Controls",
353+
"Filters"
354+
]);
343355
await expect(page.locator("#sessionInspectorV2EntryList [data-session-inspector-v2-entry-id]")).toHaveCount(2);
344-
await expect(page.locator("#sessionInspectorV2Summary")).toHaveText("2 entries shown. sessionStorage: 2. localStorage: 0.");
356+
await expect(page.locator("#sessionInspectorV2Summary > span")).toHaveText([
357+
"(2) Entries shown.",
358+
"(2) SessionStorage.",
359+
"(0) LocalStorage."
360+
]);
345361
await expect(page.locator("#statusLog")).toHaveValue(/OK Session Inspector V2 ready\. Storage is read\/delete\./);
346362

347363
const themeState = await page.evaluate(async () => {
348364
const css = await fetch("/tools/session-inspector-v2/styles/sessionInspectorV2.css", { cache: "no-store" }).then((response) => response.text());
349365
const bodyStyle = getComputedStyle(document.body);
366+
const shellStyle = getComputedStyle(document.querySelector(".session-inspector-v2.app-shell"));
367+
const headerFrameStyle = getComputedStyle(document.querySelector(".session-inspector-v2__local-shell-frame"));
368+
const headerSummaryStyle = getComputedStyle(document.querySelector(".session-inspector-v2__local-shell-frame .tools-platform-frame__accordion-summary"));
350369
const panelStyle = getComputedStyle(document.querySelector(".session-inspector-v2__panel"));
351370
const inputStyle = getComputedStyle(document.querySelector("#sessionInspectorV2FilterInput"));
352371
const probeStyle = (property, value) => {
@@ -370,19 +389,31 @@ test.describe("Workspace Manager V2 bootstrap", () => {
370389
"--session-inspector-v2-accent: var(--accent);"
371390
].every((snippet) => css.includes(snippet)),
372391
expectedBackground: probeStyle("backgroundImage", "var(--bg-gradient)"),
392+
expectedLine: probeStyle("borderColor", "var(--line)"),
373393
expectedPanel: probeStyle("backgroundColor", "var(--panel)"),
374394
expectedSurface: probeStyle("backgroundColor", "var(--surface-inline)"),
395+
headerBorder: headerFrameStyle.borderTopColor,
396+
headerRadius: headerFrameStyle.borderTopLeftRadius,
397+
headerSummaryBackground: headerSummaryStyle.backgroundColor,
375398
inputBackground: inputStyle.backgroundColor,
399+
shellBorder: shellStyle.borderTopColor,
400+
shellRadius: shellStyle.borderTopLeftRadius,
376401
panelBackground: panelStyle.backgroundColor
377402
};
378403
});
379404
expect(themeState.cssHasHardcodedColors).toBe(false);
380405
expect(themeState.cssUsesThemeTokens).toBe(true);
381406
expect(themeState.bodyBackground).toBe(themeState.expectedBackground);
407+
expect(themeState.headerBorder).toBe(themeState.expectedLine);
408+
expect(themeState.headerRadius).toBe("18px");
409+
expect(themeState.headerSummaryBackground).toBe(themeState.expectedPanel);
410+
expect(themeState.shellBorder).toBe(themeState.expectedLine);
411+
expect(themeState.shellRadius).toBe("20px");
382412
expect(themeState.panelBackground).toBe(themeState.expectedPanel);
383413
expect(themeState.inputBackground).toBe(themeState.expectedSurface);
384414

385415
for (const contentId of [
416+
"sessionInspectorV2ControlsContent",
386417
"sessionInspectorV2FiltersContent",
387418
"sessionInspectorV2EntriesContent",
388419
"sessionInspectorV2DetailsContent",
@@ -419,11 +450,18 @@ test.describe("Workspace Manager V2 bootstrap", () => {
419450
await expect(page.locator("#sessionInspectorV2EntryList [data-session-inspector-v2-entry-id]")).toHaveCount(0);
420451
await expect(page.locator("#sessionInspectorV2EntryList")).toContainText("No matching storage entries.");
421452
await expect(page.locator("#sessionInspectorV2DetailsOutput")).toHaveText("{}");
453+
await expect(page.locator("#sessionInspectorV2Summary > span")).toHaveText([
454+
"(0) Entries shown.",
455+
"(0) SessionStorage.",
456+
"(0) LocalStorage."
457+
]);
422458
await expect(page.locator("#statusLog")).toHaveValue(/OK Deleted 1 shown storage entry\./);
423459
await page.locator("#deleteAllSessionInspectorV2Button").click();
424460
await expect(page.locator("#statusLog")).toHaveValue(/WARN Delete All skipped: no matching storage entries are shown\./);
425461
expect(await page.evaluate(() => window.localStorage.getItem("session-inspector-v2-local"))).toBe("local value");
426462
expect(pageErrors).toEqual([]);
463+
await page.locator("#returnToWorkspaceButton").click();
464+
await expect(page).toHaveURL(/workspace-manager-v2\/index\.html\?hostContextId=session-inspector-v2-test-context&workspace=uat/);
427465
} finally {
428466
await coverageReporter.stop(page);
429467
await server.close();

tools/session-inspector-v2/index.html

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<summary class="is-collapsible__summary" data-session-inspector-v2-summary>Hide Header and Details</summary>
1616
<div class="is-collapsible__content">
1717
<div id="shared-theme-header"></div>
18-
<header class="session-inspector-v2__header" data-session-inspector-v2-header>
18+
<div class="session-inspector-v2__header-frame" data-session-inspector-v2-header>
1919
<section class="tools-platform-frame session-inspector-v2__local-shell-frame">
2020
<div class="tools-platform-frame__accordion-content">
2121
<div class="tools-platform-frame__accordion-summary">
@@ -29,18 +29,27 @@ <h2 class="tools-platform-frame__eyebrow">Session storage visibility</h2>
2929
</div>
3030
</div>
3131
</section>
32-
</header>
32+
</div>
3333
</div>
3434
</details>
3535

36-
<nav class="session-inspector-v2__menu" aria-label="Session Inspector V2 actions">
37-
<button id="refreshSessionInspectorV2Button" type="button">Refresh</button>
38-
<button id="deleteAllSessionInspectorV2Button" type="button">Delete All</button>
39-
<button id="clearSessionInspectorV2StatusButton" type="button">Clear Status</button>
40-
</nav>
41-
4236
<main class="session-inspector-v2 app-shell" data-tool-id="session-inspector-v2">
4337
<aside class="session-inspector-v2__panel session-inspector-v2__panel--left" aria-label="Session filters">
38+
<section class="accordion-v2 session-inspector-v2__accordion session-inspector-v2__accordion--controls is-open" data-accordion-v2-open="true">
39+
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="sessionInspectorV2ControlsContent">
40+
<span>Controls</span>
41+
<span class="accordion-v2__icon" aria-hidden="true">+</span>
42+
</button>
43+
<div id="sessionInspectorV2ControlsContent" class="accordion-v2__content">
44+
<div class="session-inspector-v2__actions" aria-label="Session Inspector V2 controls">
45+
<button id="returnToWorkspaceButton" type="button">Return to Workspace</button>
46+
<button id="refreshSessionInspectorV2Button" type="button">Refresh</button>
47+
<button id="deleteAllSessionInspectorV2Button" type="button">Delete All</button>
48+
<button id="clearSessionInspectorV2StatusButton" type="button">Clear Status</button>
49+
</div>
50+
</div>
51+
</section>
52+
4453
<section class="accordion-v2 session-inspector-v2__accordion is-open" data-accordion-v2-open="true">
4554
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="sessionInspectorV2FiltersContent">
4655
<span>Filters</span>
@@ -59,7 +68,11 @@ <h2 class="tools-platform-frame__eyebrow">Session storage visibility</h2>
5968
<span>Filter</span>
6069
<input id="sessionInspectorV2FilterInput" type="search" autocomplete="off" placeholder="Key or value text">
6170
</label>
62-
<p id="sessionInspectorV2Summary" class="session-inspector-v2__hint">No storage entries loaded.</p>
71+
<div id="sessionInspectorV2Summary" class="session-inspector-v2__summary-counts" aria-live="polite">
72+
<span>(0) Entries shown.</span>
73+
<span>(0) SessionStorage.</span>
74+
<span>(0) LocalStorage.</span>
75+
</div>
6376
</div>
6477
</section>
6578
</aside>

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

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ export class SessionInspectorV2App {
66
entryList,
77
filters,
88
refreshButton,
9+
returnToWorkspaceButton,
910
runtimeContract,
1011
statusLog,
11-
storageService
12+
storageService,
13+
windowRef = window
1214
}) {
1315
this.accordions = accordions;
1416
this.deleteAllButton = deleteAllButton;
@@ -17,10 +19,12 @@ export class SessionInspectorV2App {
1719
this.entryList = entryList;
1820
this.filters = filters;
1921
this.refreshButton = refreshButton;
22+
this.returnToWorkspaceButton = returnToWorkspaceButton;
2023
this.runtimeContract = runtimeContract || { storageAccess: "read-only" };
2124
this.statusLog = statusLog;
2225
this.storageService = storageService;
2326
this.selectedId = "";
27+
this.window = windowRef;
2428
}
2529

2630
start() {
@@ -35,6 +39,7 @@ export class SessionInspectorV2App {
3539
});
3640
this.refreshButton.addEventListener("click", () => this.refresh());
3741
this.deleteAllButton.addEventListener("click", () => this.deleteAllShownEntries());
42+
this.returnToWorkspaceButton.addEventListener("click", () => this.returnToWorkspace());
3843
this.refresh({ silent: true });
3944
this.statusLog.ok(`Session Inspector V2 ready. Storage is ${this.runtimeContract.storageAccess}.`);
4045
}
@@ -49,17 +54,17 @@ export class SessionInspectorV2App {
4954
}
5055
this.entryList.render(this.entries, this.selectedId);
5156
this.details.render(this.entries.find((entry) => entry.id === this.selectedId));
52-
this.filters.setSummary(this.summaryText());
57+
this.filters.setSummary(this.summaryCounts());
5358
if (!silent) {
5459
this.statusLog.ok(`Loaded ${this.entries.length} matching storage entries.`);
5560
}
5661
}
5762

58-
summaryText() {
63+
summaryCounts() {
5964
const sessionCount = this.entries.filter((entry) => entry.storageType === "sessionStorage").length;
6065
const localCount = this.entries.filter((entry) => entry.storageType === "localStorage").length;
6166
const totalCount = this.entries.length;
62-
return `${totalCount} entries shown. sessionStorage: ${sessionCount}. localStorage: ${localCount}.`;
67+
return { localCount, sessionCount, totalCount };
6368
}
6469

6570
selectEntry(entryId) {
@@ -107,4 +112,21 @@ export class SessionInspectorV2App {
107112
this.selectedId = "";
108113
this.refresh({ silent: true });
109114
}
115+
116+
workspaceManagerUrl() {
117+
const url = new URL("../workspace-manager-v2/index.html", this.window.location.href);
118+
const params = new URLSearchParams(this.window.location.search || "");
119+
const hostContextId = params.get("hostContextId") || "";
120+
if (hostContextId) {
121+
url.searchParams.set("hostContextId", hostContextId);
122+
}
123+
if (params.get("workspaceMode")?.toLowerCase() === "uat") {
124+
url.searchParams.set("workspace", "uat");
125+
}
126+
return url.href;
127+
}
128+
129+
returnToWorkspace() {
130+
this.window.location.href = this.workspaceManagerUrl();
131+
}
110132
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ window.addEventListener("DOMContentLoaded", () => {
3131
summary: requireElement("#sessionInspectorV2Summary")
3232
}),
3333
refreshButton: requireElement("#refreshSessionInspectorV2Button"),
34+
returnToWorkspaceButton: requireElement("#returnToWorkspaceButton"),
3435
runtimeContract: sessionInspectorV2RuntimeContract(),
3536
statusLog: new StatusLogControl({
3637
clearButton: requireElement("#clearSessionInspectorV2StatusButton"),

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ export class AccordionSection {
1414
this.header.dataset.accordionV2Bound = "true";
1515
this.setOpen(this.section.dataset.accordionV2Open !== "false");
1616
this.header.addEventListener("click", (event) => {
17-
const clickedNestedButton = event.target instanceof Element
18-
&& event.target.closest("button")
19-
&& event.target !== this.header;
17+
const clickedButton = event.target instanceof Element
18+
? event.target.closest("button")
19+
: null;
20+
const clickedNestedButton = clickedButton && clickedButton !== this.header;
2021
if (clickedNestedButton) {
2122
return;
2223
}

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,17 @@ export class FilterControl {
2222
return String(this.scopeSelect.value || "sessionStorage");
2323
}
2424

25-
setSummary(value) {
26-
this.summary.textContent = value;
25+
setSummary({ localCount = 0, sessionCount = 0, totalCount = 0 } = {}) {
26+
const lines = [
27+
`(${totalCount}) Entries shown.`,
28+
`(${sessionCount}) SessionStorage.`,
29+
`(${localCount}) LocalStorage.`
30+
];
31+
const documentRef = this.summary.ownerDocument || document;
32+
this.summary.replaceChildren(...lines.map((line) => {
33+
const item = documentRef.createElement("span");
34+
item.textContent = line;
35+
return item;
36+
}));
2737
}
2838
}

0 commit comments

Comments
 (0)