Compare commits

...

5 Commits

Author SHA1 Message Date
dbczumar 43fb4c9eec test(e2e_ui): cover files-panel collapsed-state persistence across reload
E2E UI Tests / gate (push) Failing after 3s
Integration Tests / gate (push) Failing after 0s
E2E UI Tests / E2E UI Tests (shard ${{ matrix.shard_id }}/${{ matrix.num_shards }}) (push) Has been skipped
E2E UI Tests / setup (push) Has been skipped
Integration Tests / setup (push) Has been skipped
Integration Tests / Integration (${{ matrix.name }}) (push) Has been skipped
E2E UI Tests / Merge Ready rerun (push) Has been cancelled
Integration Tests / Merge Ready rerun (push) Has been cancelled
Signed-off-by: dbczumar <corey.zumar@databricks.com>
2026-06-23 19:42:39 -07:00
dbczumar 7f7c1771a8 test(web): assert persisted files-panel pref includes collapsed field
Signed-off-by: dbczumar <corey.zumar@databricks.com>
2026-06-23 19:31:13 -07:00
dbczumar d002e42a03 Merge remote-tracking branch 'origin/main' into pr-1025 2026-06-23 19:24:04 -07:00
Yuan Tang 5c0eca963f fix: address CI failures — formatting, TS errors, and test updates
- Fix Prettier formatting (collapse short ternaries to single lines)
- Update AppShell to spread existing prefs before overwriting changedOnly
- Update test expectations to include the new collapsed field
2026-06-23 09:19:32 -04:00
Yuan Tang a507101c0b fix(web): persist file browser collapsed state across sessions
The FilesPanel collapsed/expanded toggle was initialized to `false` on
every mount, so collapsing the panel didn't survive a page refresh or
session switch. Store the collapsed flag in the existing
`omnigent:files-panel-preferences` localStorage key alongside `changedOnly`.
2026-06-23 09:11:16 -04:00
6 changed files with 63 additions and 10 deletions
+3 -5
View File
@@ -20,10 +20,8 @@ describe("filesPanelPreferences", () => {
});
it("round-trips a written preference", () => {
writeFilesPanelPreferences({ changedOnly: true });
// The written value must come back — proves the write serialized and the
// read parsed/validated the field.
expect(readFilesPanelPreferences()).toEqual({ changedOnly: true });
writeFilesPanelPreferences({ changedOnly: true, collapsed: false });
expect(readFilesPanelPreferences()).toEqual({ changedOnly: true, collapsed: false });
});
it("falls back to defaults on malformed JSON", () => {
@@ -43,6 +41,6 @@ describe("filesPanelPreferences", () => {
// A record present but with a non-boolean changedOnly must default the
// field rather than pass a garbage value through to the panel.
localStorage.setItem(STORAGE_KEY, JSON.stringify({ changedOnly: "yes" }));
expect(readFilesPanelPreferences()).toEqual({ changedOnly: false });
expect(readFilesPanelPreferences()).toEqual({ changedOnly: false, collapsed: false });
});
});
+5
View File
@@ -16,6 +16,8 @@
export interface FilesPanelPreferences {
/** true = changed-files-only flat list, false = full folder tree ("All"). */
changedOnly: boolean;
/** true = the panel header is collapsed (content hidden). */
collapsed: boolean;
}
const STORAGE_KEY = "omnigent:files-panel-preferences";
@@ -24,6 +26,7 @@ const STORAGE_KEY = "omnigent:files-panel-preferences";
// the working folder, not just the changed subset.
export const DEFAULT_FILES_PANEL_PREFERENCES: FilesPanelPreferences = {
changedOnly: false,
collapsed: false,
};
/**
@@ -46,6 +49,8 @@ export function readFilesPanelPreferences(): FilesPanelPreferences {
typeof p.changedOnly === "boolean"
? p.changedOnly
: DEFAULT_FILES_PANEL_PREFERENCES.changedOnly,
collapsed:
typeof p.collapsed === "boolean" ? p.collapsed : DEFAULT_FILES_PANEL_PREFERENCES.collapsed,
};
} catch {
return DEFAULT_FILES_PANEL_PREFERENCES;
+3 -1
View File
@@ -2083,7 +2083,9 @@ describe("Files scope default and persistence", () => {
fireEvent.click(screen.getByRole("button", { name: /files: switch to changed/i }));
expect(screen.getByTestId("files-panel")).toHaveAttribute("data-flat-view", "true");
// The choice was written to localStorage — that's what makes it sticky.
expect(localStorage.getItem(PREF_KEY)).toBe(JSON.stringify({ changedOnly: true }));
expect(localStorage.getItem(PREF_KEY)).toBe(
JSON.stringify({ changedOnly: true, collapsed: false }),
);
// Re-enter a *different* session fresh: it must open on the remembered
// "Changed" scope. cleanup() unmounts the shell but does NOT touch
+1 -1
View File
@@ -623,7 +623,7 @@ export function AppShell() {
const handleFilesFlatViewChange = useCallback((v: boolean) => {
filesPanelScopePrefRef.current = v;
setFilesPanelFlatView(v);
writeFilesPanelPreferences({ changedOnly: v });
writeFilesPanelPreferences({ ...readFilesPanelPreferences(), changedOnly: v });
}, []);
const openFileViewer = useCallback(
+9 -2
View File
@@ -20,6 +20,7 @@ import {
useWorkspaceFileSearch,
} from "@/hooks/useWorkspaceChangedFiles";
import { cn } from "@/lib/utils";
import { readFilesPanelPreferences, writeFilesPanelPreferences } from "@/lib/filesPanelPreferences";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import {
DropdownMenu,
@@ -287,7 +288,7 @@ export function FilesPanel({
const runnerWentOffline = useChatStore(
(s) => s.conversationId === conversationId && s.sessionStatus === "failed",
);
const [collapsed, setCollapsed] = useState(false);
const [collapsed, setCollapsed] = useState(() => readFilesPanelPreferences().collapsed);
const [changedSearch, setChangedSearch] = useState("");
const [treeSearch, setTreeSearch] = useState("");
const [debouncedTreeSearch, setDebouncedTreeSearch] = useState("");
@@ -389,7 +390,13 @@ export function FilesPanel({
<button
type="button"
className="flex min-w-0 flex-1 cursor-pointer items-center gap-2 text-left"
onClick={() => setCollapsed((v) => !v)}
onClick={() =>
setCollapsed((v) => {
const next = !v;
writeFilesPanelPreferences({ ...readFilesPanelPreferences(), collapsed: next });
return next;
})
}
aria-expanded={!collapsed}
>
<span className="shrink-0 font-medium text-sm">Working folder</span>
+42 -1
View File
@@ -1,4 +1,4 @@
"""E2E: file-viewer and comment state survive a full browser reload.
"""E2E: file-viewer, comment, and files-panel state survive a full browser reload.
These tests prove the durability path that the AppShell unit tests can
only approximate: ``AppShell.test.tsx`` mocks ``FileViewer`` and asserts
@@ -25,6 +25,8 @@ import httpx
import pytest
from playwright.sync_api import Page, expect
from tests.e2e_ui.conftest import open_right_rail
# The agent spec uses ``os_env.cwd: .`` (see ``_TEST_AGENT_YAML`` in
# conftest), so filesystem PUTs land in ``<repo-root>/<session_id>/``
# next to the spawned server. Clean that per-session dir up in teardown
@@ -181,3 +183,42 @@ def test_comment_persists_across_reload(
).json()
assert len(comments) == 1, f"Expected exactly 1 persisted comment, got {comments}"
assert comments[0]["body"] == _COMMENT_BODY
def test_files_panel_collapsed_state_persists_across_reload(
page: Page,
seeded_session: tuple[str, str],
) -> None:
"""Collapse the Working-folder panel, reload, and assert it stays collapsed.
Unlike the file-viewer/comment cases above (server-persisted, URL-
rehydrated), the panel's collapsed state lives in a single app-global
localStorage key with no per-conversation keying — the same preference
the panel reads to carry the choice across *sessions*. A cold reload is
the strongest single-session proxy for that: it re-mounts ``FilesPanel``,
whose initial collapsed state is seeded purely from the stored
preference (``readFilesPanelPreferences().collapsed``). A failure after
reload means the choice wasn't persisted, the durability path the
AppShell unit test can only approximate with a mocked store.
"""
base_url, session_id = seeded_session
page.goto(f"{base_url}/c/{session_id}")
open_right_rail(page)
# The Working-folder header doubles as the collapse toggle; ``aria-expanded``
# tracks the panel's collapsed state and starts expanded by default.
rail = page.get_by_role("complementary", name="Workspace")
header = rail.get_by_role("button", name=re.compile("Working folder"))
expect(header).to_have_attribute("aria-expanded", "true", timeout=30_000)
header.click()
expect(header).to_have_attribute("aria-expanded", "false")
page.reload()
open_right_rail(page)
# A cold reload must restore the collapsed choice from localStorage.
header_after = page.get_by_role("complementary", name="Workspace").get_by_role(
"button", name=re.compile("Working folder")
)
expect(header_after).to_have_attribute("aria-expanded", "false", timeout=30_000)