Skip to content

Commit 5d28773

Browse files
author
DavidQ
committed
Constrain Session Inspector V2 detail panel height and show dirty status in header - PR_26128_023-session-inspector-v2-detail-panel-height
1 parent 1482692 commit 5d28773

7 files changed

Lines changed: 174 additions & 11 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Playwright Session Inspector V2 Detail Panel Height
2+
3+
## Command
4+
`npm run test:workspace-v2`
5+
6+
## Result
7+
- Passed: 15/15
8+
- Runtime: about 1.5 minutes
9+
10+
## Targeted Coverage
11+
- Verified JSON content scrolls inside `#sessionInspectorV2JsonContent` for a long selected object.
12+
- Verified Data content scrolls inside `#sessionInspectorV2DataContent` for a long selected data payload.
13+
- Verified JSON and Data panel heights remain bounded.
14+
- Verified Dirty and Status headers remain visible/reachable with long JSON/Data content.
15+
- Verified Dirty header shows `Dirty: false` for clean selected items.
16+
- Verified Dirty header shows `Dirty: true` for dirty selected items.
17+
- Verified Dirty header shows `Dirty: unknown` when dirty data is missing.
18+
- Verified Copy All still copies JSON/Data/Dirty content.
19+
- Verified Clear Status still clears Session Inspector V2 status output.
20+
- Verified existing JSON/Data/Dirty/Status accordion independence remains covered.
21+
22+
## Skipped
23+
- Full samples smoke test was skipped by request. The relevant Session Inspector V2 layout, accordion, Copy All, Clear Status, and dirty-header behavior is covered by `tests/playwright/tools/WorkspaceManagerV2.spec.mjs`.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Session Inspector V2 Detail Panel Height
2+
3+
## Scope
4+
- Constrained Session Inspector V2 JSON and Data detail bodies so long content scrolls inside those panels.
5+
- Kept lower Dirty and Status headers visible/reachable when JSON/Data contain large payloads.
6+
- Preserved the fresh JSON/Data/Dirty/Status V2 accordion behavior.
7+
- Added live dirty state text to the Dirty header:
8+
- `Dirty: false`
9+
- `Dirty: true`
10+
- `Dirty: unknown`
11+
12+
## Implementation Notes
13+
- JSON and Data sections now use bounded detail-scroll content bodies.
14+
- JSON/Data output content no longer drives uncontrolled right-panel growth.
15+
- Dirty header value is updated by `DirtyControl` from the selected item’s `dirty.isDirty` boolean.
16+
- Missing or non-boolean dirty state displays `Dirty: unknown`.
17+
18+
## Preserved Behavior
19+
- Copy All still copies the labeled JSON/Data/Dirty payload.
20+
- Clear Status still clears the status log.
21+
- JSON, Data, Dirty, and Status accordions still open and close independently.
22+
- Storage tile layout, per-tile Delete, and Delete All behavior were preserved.
23+
- Normalized session object shape was not changed.
24+
25+
## Guardrails
26+
- No cross-tool communication was added.
27+
- No sample JSON was modified.
28+
- No roadmap content was modified.
29+
30+
## Validation
31+
- Passed `npm run test:workspace-v2` with 15/15 tests.
32+
- Verified long JSON content scrolls inside the JSON body.
33+
- Verified long Data content scrolls inside the Data body.
34+
- Verified Dirty and Status headers remain visible/reachable.
35+
- Verified Dirty header values for clean, dirty, and missing dirty data.
36+
- Verified Copy All and Clear Status still work.
37+
38+
## Skipped
39+
- Full samples smoke test was skipped by request. The changed surface is Session Inspector V2 panel layout and header state, covered by `npm run test:workspace-v2`.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
462462
await expect(page.locator(".session-inspector-v2__json-accordion-header #copySessionInspectorV2AllButton")).toHaveText("Copy All");
463463
await expect(page.locator(".session-inspector-v2__data-accordion-header")).toContainText("Data");
464464
await expect(page.locator(".session-inspector-v2__dirty-accordion-header")).toContainText("Dirty");
465+
await expect(page.locator("#sessionInspectorV2DirtyHeaderValue")).toHaveText("Dirty: unknown");
465466
await expect(page.locator(".session-inspector-v2__state-accordion-header")).toHaveCount(0);
466467
await expect(page.locator(".session-inspector-v2__schema-accordion-header")).toHaveCount(0);
467468
await expect(page.locator("#sessionInspectorV2StateOutput")).toHaveCount(0);
@@ -660,6 +661,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
660661
await expect(page.locator("#sessionInspectorV2JsonOutput")).toHaveText("true");
661662
await expect(page.locator("#sessionInspectorV2DataOutput")).toContainText("No data section is present for sessionStorage:session-inspector-v2-alpha.");
662663
await expect(page.locator("#sessionInspectorV2DirtyOutput")).toContainText("No dirty section is present for sessionStorage:session-inspector-v2-alpha.");
664+
await expect(page.locator("#sessionInspectorV2DirtyHeaderValue")).toHaveText("Dirty: unknown");
663665
await page.locator("#copySessionInspectorV2AllButton").click();
664666
await expect(page.locator("#statusLog")).toHaveValue(/WARN Copied JSON, Data, and Dirty sections with empty-state text for missing Data and Dirty\./);
665667
const copiedValidationText = await page.evaluate(() => window.__sessionInspectorV2ClipboardText);
@@ -733,6 +735,16 @@ test.describe("Workspace Manager V2 bootstrap", () => {
733735
}
734736
}
735737
});
738+
const longAssetMap = Object.fromEntries(Array.from({ length: 90 }, (_, index) => {
739+
const paddedIndex = String(index).padStart(2, "0");
740+
return [`assets.image.long.${paddedIndex}`, {
741+
path: `assets/images/long-${paddedIndex}.png`,
742+
type: "image",
743+
kind: "png",
744+
role: "preview",
745+
source: "test-fixture"
746+
}];
747+
}));
736748
window.sessionStorage.setItem("workspace.tools.preview-generator-v2", JSON.stringify({
737749
schema: {
738750
source: "workspace-manager-v2",
@@ -773,14 +785,35 @@ test.describe("Workspace Manager V2 bootstrap", () => {
773785
assetsPath: "games/Asteroids/assets",
774786
repoReferenceKey: "workspace.repo.reference"
775787
},
776-
data: { assets: { "assets.image.preview.preview": { path: "assets/images/preview.png" } } },
788+
data: { assets: { "assets.image.preview.preview": { path: "assets/images/preview.png" }, ...longAssetMap } },
777789
dirty: {
778790
isDirty: false,
779791
reason: null,
780792
changedAt: null,
781793
changedKeys: []
782794
}
783795
}));
796+
window.sessionStorage.setItem("workspace.tools.dirty-test", JSON.stringify({
797+
schema: {
798+
source: "workspace-manager-v2",
799+
toolId: "dirty-test",
800+
schemaRole: "workspace-launch-context",
801+
schemaRef: "tools/schemas/workspace.manifest.schema.json"
802+
},
803+
workspace: {
804+
source: "workspace-manager-v2",
805+
toolId: "dirty-test"
806+
},
807+
data: {
808+
note: "dirty fixture"
809+
},
810+
dirty: {
811+
isDirty: true,
812+
reason: "test dirty flag",
813+
changedAt: "2026-05-08T12:00:00.000Z",
814+
changedKeys: ["data.note"]
815+
}
816+
}));
784817
window.sessionStorage.setItem("workspace.tools.no-data-test", JSON.stringify({
785818
schema: {
786819
source: "workspace-manager-v2",
@@ -822,7 +855,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
822855
});
823856

824857
try {
825-
await expect(page.locator("#sessionInspectorV2EntryList [data-session-inspector-v2-entry-id]")).toHaveCount(4);
858+
await expect(page.locator("#sessionInspectorV2EntryList [data-session-inspector-v2-entry-id]")).toHaveCount(5);
826859
await expect(page.locator(".session-inspector-v2__json-accordion-header")).toContainText("JSON");
827860
await expect(page.locator(".session-inspector-v2__data-accordion-header")).toContainText("Data");
828861
await expect(page.locator(".session-inspector-v2__dirty-accordion-header")).toContainText("Dirty");
@@ -854,6 +887,34 @@ test.describe("Workspace Manager V2 bootstrap", () => {
854887
await expect(page.locator("#sessionInspectorV2DirtyOutput")).not.toContainText('"data"');
855888
await expect(page.locator("#sessionInspectorV2DirtyOutput")).not.toContainText('"workspace"');
856889
await expect(page.locator("#sessionInspectorV2DirtyOutput")).not.toContainText('"schema"');
890+
await expect(page.locator("#sessionInspectorV2DirtyHeaderValue")).toHaveText("Dirty: false");
891+
const detailPanelState = await page.evaluate(() => {
892+
const jsonContent = document.querySelector("#sessionInspectorV2JsonContent");
893+
const dataContent = document.querySelector("#sessionInspectorV2DataContent");
894+
const dirtyHeader = document.querySelector(".session-inspector-v2__dirty-accordion-header");
895+
const statusHeader = document.querySelector(".session-inspector-v2__status-accordion-header");
896+
const rightPanel = document.querySelector(".session-inspector-v2__panel--right");
897+
const rectFor = (element) => element.getBoundingClientRect();
898+
const rightRect = rectFor(rightPanel);
899+
const dirtyHeaderRect = rectFor(dirtyHeader);
900+
const statusHeaderRect = rectFor(statusHeader);
901+
return {
902+
dataContentScrolls: dataContent.scrollHeight > dataContent.clientHeight + 1,
903+
dataHeightBounded: rectFor(dataContent).height <= 170,
904+
dirtyHeaderReachable: dirtyHeaderRect.top >= rightRect.top && dirtyHeaderRect.bottom <= rightRect.bottom,
905+
jsonContentScrolls: jsonContent.scrollHeight > jsonContent.clientHeight + 1,
906+
jsonHeightBounded: rectFor(jsonContent).height <= 170,
907+
statusHeaderReachable: statusHeaderRect.top >= rightRect.top && statusHeaderRect.bottom <= rightRect.bottom
908+
};
909+
});
910+
expect(detailPanelState).toEqual({
911+
dataContentScrolls: true,
912+
dataHeightBounded: true,
913+
dirtyHeaderReachable: true,
914+
jsonContentScrolls: true,
915+
jsonHeightBounded: true,
916+
statusHeaderReachable: true
917+
});
857918
await page.locator("#copySessionInspectorV2AllButton").click();
858919
await expect(page.locator("#statusLog")).toHaveValue(/OK Copied JSON, Data, and Dirty sections to clipboard\./);
859920
const copiedToolPayload = await page.evaluate(() => window.__sessionInspectorV2ClipboardText);
@@ -866,19 +927,23 @@ test.describe("Workspace Manager V2 bootstrap", () => {
866927
expect(copiedToolPayload).toContain('"assets.image.preview.preview"');
867928
expect(copiedToolPayload).toContain('"isDirty": false');
868929

930+
await page.locator('[data-session-inspector-v2-entry-id="sessionStorage:workspace.tools.dirty-test"]').click();
931+
await expect(page.locator("#sessionInspectorV2DirtyHeaderValue")).toHaveText("Dirty: true");
932+
await expect(page.locator("#sessionInspectorV2DirtyOutput")).toContainText('"isDirty": true');
869933
await page.locator('[data-session-inspector-v2-entry-id="sessionStorage:workspace.tools.no-data-test"]').click();
870934
await expect(page.locator("#sessionInspectorV2DataOutput")).toContainText("No data section is present for sessionStorage:workspace.tools.no-data-test.");
871935
await page.locator('[data-session-inspector-v2-entry-id="sessionStorage:workspace.tools.no-dirty-test"]').click();
936+
await expect(page.locator("#sessionInspectorV2DirtyHeaderValue")).toHaveText("Dirty: unknown");
872937
await expect(page.locator("#sessionInspectorV2DirtyOutput")).toContainText("No dirty section is present for sessionStorage:workspace.tools.no-dirty-test.");
873938

874939
await page.locator('[data-session-inspector-v2-delete-entry-id="sessionStorage:workspace.tools.preview-generator-v2"]').click();
875-
await expect(page.locator("#sessionInspectorV2EntryList [data-session-inspector-v2-entry-id]")).toHaveCount(3);
940+
await expect(page.locator("#sessionInspectorV2EntryList [data-session-inspector-v2-entry-id]")).toHaveCount(4);
876941
expect(await page.evaluate(() => window.sessionStorage.getItem("workspace.tools.preview-generator-v2"))).toBeNull();
877942
await expect(page.locator("#statusLog")).toHaveValue(/OK Deleted sessionStorage:workspace\.tools\.preview-generator-v2\./);
878943

879944
await page.locator("#deleteAllSessionInspectorV2Button").click();
880945
await expect(page.locator("#sessionInspectorV2EntryList [data-session-inspector-v2-entry-id]")).toHaveCount(0);
881-
await expect(page.locator("#statusLog")).toHaveValue(/OK Deleted 3 shown storage entries\./);
946+
await expect(page.locator("#statusLog")).toHaveValue(/OK Deleted 4 shown storage entries\./);
882947
expect(pageErrors).toEqual([]);
883948
} finally {
884949
await coverageReporter.stop(page);

tools/session-inspector-v2/index.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ <h2 class="tools-platform-frame__eyebrow">Session storage visibility</h2>
9696
</section>
9797

9898
<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 JSON, data, and dirty tracking">
99-
<section class="accordion-v2 session-inspector-v2__accordion session-inspector-v2__accordion--fill is-open" data-accordion-v2-open="true">
99+
<section class="accordion-v2 session-inspector-v2__accordion session-inspector-v2__accordion--detail session-inspector-v2__accordion--detail-scroll is-open" data-accordion-v2-open="true">
100100
<div class="session-inspector-v2__accordion-header-row session-inspector-v2__json-accordion-header">
101101
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="sessionInspectorV2JsonContent">
102102
<span>JSON</span>
@@ -106,27 +106,27 @@ <h2 class="tools-platform-frame__eyebrow">Session storage visibility</h2>
106106
<button id="copySessionInspectorV2AllButton" type="button">Copy All</button>
107107
</div>
108108
</div>
109-
<div id="sessionInspectorV2JsonContent" class="accordion-v2__content">
109+
<div id="sessionInspectorV2JsonContent" class="accordion-v2__content session-inspector-v2__detail-scroll-content">
110110
<pre id="sessionInspectorV2JsonOutput" class="session-inspector-v2__output">{}</pre>
111111
</div>
112112
</section>
113113

114-
<section class="accordion-v2 session-inspector-v2__accordion session-inspector-v2__accordion--fill is-open" data-accordion-v2-open="true">
114+
<section class="accordion-v2 session-inspector-v2__accordion session-inspector-v2__accordion--detail session-inspector-v2__accordion--detail-scroll is-open" data-accordion-v2-open="true">
115115
<div class="session-inspector-v2__accordion-header-row session-inspector-v2__data-accordion-header">
116116
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="sessionInspectorV2DataContent">
117117
<span>Data</span>
118118
<span class="accordion-v2__icon" aria-hidden="true">+</span>
119119
</button>
120120
</div>
121-
<div id="sessionInspectorV2DataContent" class="accordion-v2__content">
121+
<div id="sessionInspectorV2DataContent" class="accordion-v2__content session-inspector-v2__detail-scroll-content">
122122
<pre id="sessionInspectorV2DataOutput" class="session-inspector-v2__output">Select a normalized tool entry with a top-level data section.</pre>
123123
</div>
124124
</section>
125125

126-
<section class="accordion-v2 session-inspector-v2__accordion session-inspector-v2__accordion--fill is-open" data-accordion-v2-open="true">
126+
<section class="accordion-v2 session-inspector-v2__accordion session-inspector-v2__accordion--detail is-open" data-accordion-v2-open="true">
127127
<div class="session-inspector-v2__accordion-header-row session-inspector-v2__dirty-accordion-header">
128128
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="sessionInspectorV2DirtyContent">
129-
<span>Dirty</span>
129+
<span id="sessionInspectorV2DirtyHeaderValue">Dirty: unknown</span>
130130
<span class="accordion-v2__icon" aria-hidden="true">+</span>
131131
</button>
132132
</div>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ window.addEventListener("DOMContentLoaded", () => {
2828
output: requireElement("#sessionInspectorV2JsonOutput")
2929
}),
3030
dirty: new DirtyControl({
31+
headerValue: requireElement("#sessionInspectorV2DirtyHeaderValue"),
3132
output: requireElement("#sessionInspectorV2DirtyOutput")
3233
}),
3334
deleteAllButton: requireElement("#deleteAllSessionInspectorV2Button"),

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
export class DirtyControl {
2-
constructor({ output }) {
2+
constructor({ headerValue, output }) {
3+
this.headerValue = headerValue;
34
this.output = output;
45
}
56

7+
setHeaderValue(value) {
8+
this.headerValue.textContent = `Dirty: ${value}`;
9+
}
10+
611
clear() {
12+
this.setHeaderValue("unknown");
713
this.output.textContent = "Select a normalized tool entry with a top-level dirty section.";
814
}
915

@@ -18,9 +24,11 @@ export class DirtyControl {
1824
}
1925
const value = entry.parseOk ? entry.parsedValue : null;
2026
if (!value || typeof value !== "object" || Array.isArray(value) || !Object.prototype.hasOwnProperty.call(value, "dirty")) {
27+
this.setHeaderValue("unknown");
2128
this.output.textContent = `No dirty section is present for ${entry.storageType}:${entry.key}. Select a normalized tool entry with a top-level dirty section.`;
2229
return;
2330
}
31+
this.setHeaderValue(typeof value.dirty?.isDirty === "boolean" ? String(value.dirty.isDirty) : "unknown");
2432
this.output.textContent = JSON.stringify(value.dirty, null, 2);
2533
}
2634
}

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,19 @@ button:hover {
168168
flex: 1 1 auto;
169169
}
170170

171+
.session-inspector-v2__panel--right {
172+
overflow: hidden;
173+
}
174+
175+
.session-inspector-v2__accordion--detail.is-open {
176+
flex: 1 1 0;
177+
min-height: 0;
178+
}
179+
180+
.session-inspector-v2__accordion--detail-scroll.is-open {
181+
max-height: clamp(126px, 24vh, 190px);
182+
}
183+
171184
.session-inspector-v2__accordion--controls.is-open {
172185
flex: 0 0 auto;
173186
}
@@ -231,6 +244,20 @@ button:hover {
231244
display: none;
232245
}
233246

247+
.session-inspector-v2__detail-scroll-content {
248+
max-height: clamp(86px, 17vh, 142px);
249+
overflow: auto;
250+
}
251+
252+
.session-inspector-v2__accordion--detail .session-inspector-v2__output {
253+
min-height: 0;
254+
}
255+
256+
.session-inspector-v2__detail-scroll-content > .session-inspector-v2__output {
257+
flex: 0 0 auto;
258+
overflow: visible;
259+
}
260+
234261
.session-inspector-v2__actions {
235262
display: grid;
236263
grid-template-columns: repeat(2, minmax(0, 1fr));

0 commit comments

Comments
 (0)