diff --git a/.huskies/work/1_backlog/488_story_web_ui_shows_project_name_in_browser_tab_with_huskies_favicon.md b/.huskies/work/1_backlog/488_story_web_ui_shows_project_name_in_browser_tab_with_huskies_favicon.md new file mode 100644 index 00000000..a22a40cc --- /dev/null +++ b/.huskies/work/1_backlog/488_story_web_ui_shows_project_name_in_browser_tab_with_huskies_favicon.md @@ -0,0 +1,21 @@ +--- +name: "Web UI shows project name in browser tab with huskies favicon" +--- + +# Story 488: Web UI shows project name in browser tab with huskies favicon + +## User Story + +As a user running huskies on multiple projects, I want the browser tab to show the project name (e.g. "Reclaimer") instead of the hardcoded "Huskies", and I want a huskies favicon derived from the website logo, so I can distinguish tabs and have proper branding. + +## Acceptance Criteria + +- [ ] Browser tab title shows the project folder name when a project is open (e.g. `/home/user/reclaimer` → `reclaimer | Huskies`) +- [ ] Browser tab title shows `Huskies` when no project is open +- [ ] A huskies-themed SVG favicon is served and shown in the browser tab +- [ ] The Vite default favicon is replaced by the huskies favicon + +## Out of Scope + +- Fetching a user-configured display name from the backend (folder name is sufficient) +- Changing the app name shown in the UI heading diff --git a/frontend/index.html b/frontend/index.html index db1e9d62..3b2ac992 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,7 @@ - + Huskies diff --git a/frontend/public/huskies.svg b/frontend/public/huskies.svg new file mode 100644 index 00000000..9cac7783 --- /dev/null +++ b/frontend/public/huskies.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 07f94fc7..de6408c0 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -345,6 +345,59 @@ describe("App", () => { }); }); + it("sets document title to Huskies when no project is open", async () => { + mockedApi.getCurrentProject.mockResolvedValue(null); + + await renderApp(); + + await waitFor(() => { + expect(document.title).toBe("Huskies"); + }); + }); + + it("sets document title to project name when a project is open", async () => { + mockedApi.getCurrentProject.mockResolvedValue("/home/user/reclaimer"); + + await renderApp(); + + await waitFor(() => { + expect(document.title).toBe("reclaimer | Huskies"); + }); + }); + + it("resets document title to Huskies after closing project", async () => { + mockedApi.openProject.mockResolvedValue("/home/user/myproject"); + mockedApi.closeProject.mockResolvedValue(true); + + await renderApp(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(/\/path\/to\/project/i), + ).toBeInTheDocument(); + }); + + const input = screen.getByPlaceholderText( + /\/path\/to\/project/i, + ) as HTMLInputElement; + await userEvent.clear(input); + await userEvent.type(input, "/home/user/myproject"); + + const openButton = screen.getByRole("button", { name: /open project/i }); + await userEvent.click(openButton); + + await waitFor(() => { + expect(document.title).toBe("myproject | Huskies"); + }); + + const closeButton = await waitFor(() => screen.getByText("✕")); + await userEvent.click(closeButton); + + await waitFor(() => { + expect(document.title).toBe("Huskies"); + }); + }); + it("handles Enter key to trigger project open", async () => { mockedApi.openProject.mockResolvedValue("/home/user/myproject"); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 49533e9a..a4472566 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -51,6 +51,17 @@ function App() { }); }, []); + React.useEffect(() => { + if (projectPath) { + const projectName = + projectPath.replace(/\\/g, "/").split("/").filter(Boolean).pop() ?? + projectPath; + document.title = `${projectName} | Huskies`; + } else { + document.title = "Huskies"; + } + }, [projectPath]); + React.useEffect(() => { api .getKnownProjects()