diff --git a/CHANGELOG.md b/CHANGELOG.md index 641f2065..3a4e04eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,9 @@ written while it was being built. See [RELEASING.md](RELEASING.md). ### Changed +- For organizers, the "Participant View" section of a hackathon's sidebar is now + folded every time you open the hackathon. Open it with its arrow when you need + it. - Manage Teams no longer has a "Suggest teams" button. Build teams by dragging people onto them, or by downloading the spreadsheet, filling in the team column and uploading it again. diff --git a/components/frontend/src/lib/components/layout/HackathonSidebar.svelte b/components/frontend/src/lib/components/layout/HackathonSidebar.svelte index 163f53bd..b47f5696 100644 --- a/components/frontend/src/lib/components/layout/HackathonSidebar.svelte +++ b/components/frontend/src/lib/components/layout/HackathonSidebar.svelte @@ -113,6 +113,12 @@ // the default and start being what they are — the other side of the // hackathon, one click away. // + // Participant View is folded on every arrival, wherever that is: it neither + // opens for the route nor remembers being opened. Every link into a + // hackathon lands on Overview, a participant page, so opening the section + // around the lit row opened it for an owner on every visit. The chevron + // still opens it, and it stays open while this layout is mounted. + // // Both are ignored for a participant: with no Manage section there is no // heading and no chevron on the entries above it, so they are simply always // out, exactly as before. @@ -160,10 +166,6 @@ // one of the pages, not the way in. The way in is the heading, which is drawn // whatever the fold state. const insideManage = $derived(activeId?.startsWith('manage:') ?? false); - // Its counterpart, and deliberately not `!insideManage`: with no entry lit at - // all — a route neither section owns — neither section is forced open, and - // whatever the reader last chose stands. - const insideMember = $derived(activeId?.startsWith('member:') ?? false); // `membership.role` is sourced from casbin. It is absent for a global admin who // never joined, hence the second argument. @@ -182,22 +184,17 @@ $effect(() => { collapsed = stored('sidebar-collapsed') ?? collapsed; manageOpen = stored('sidebar-manage-open') ?? manageOpen; - memberOpen = stored('sidebar-member-open') ?? memberOpen; }); - // Entering a section opens it, rather than pinning it open while you are in + // Entering Manage opens it, rather than pinning it open while you are in // there: deriving the fold state from the route instead made the chevron a // no-op on every Manage page, which reads as a broken control. Declared after - // the restore above so the route wins over the saved preference on arrival — - // the section holding the lit row is never the folded one. + // the restore above so the route wins over the saved preference on arrival. + // Participant View has no counterpart — see `memberOpen`. $effect(() => { if (insideManage) manageOpen = true; }); - $effect(() => { - if (insideMember) memberOpen = true; - }); - $effect(() => { if (typeof window === 'undefined') return; const mq = window.matchMedia('(min-width: 768px)'); @@ -243,7 +240,6 @@ function toggleMember() { memberOpen = !memberOpen; - remember('sidebar-member-open', memberOpen); } diff --git a/components/frontend/src/lib/components/layout/HackathonSidebar.test.ts b/components/frontend/src/lib/components/layout/HackathonSidebar.test.ts index 1388030c..2f34a53c 100644 --- a/components/frontend/src/lib/components/layout/HackathonSidebar.test.ts +++ b/components/frontend/src/lib/components/layout/HackathonSidebar.test.ts @@ -22,10 +22,10 @@ const OWNER = 1 * starts open — Settings included, since it is one of the screens in the * section and not the way in — and Participant View starts folded, which is the * whole point of labelling it: an owner arriving gets their own section, not a - * participant's. Arriving anywhere inside either section brings that one out, so - * the lit row is never hidden, and the chevron works wherever you are — an - * earlier version derived the state from the route and so dead-ended on every - * Manage page. + * participant's. Arriving inside Manage brings it out; Participant View stays + * folded wherever an owner arrives, Overview included, and does not remember + * being opened. The chevron works wherever you are — an earlier version derived + * the state from the route and so dead-ended on every Manage page. * * A participant has no Manage section, and so no heading and no chevron over * their entries either: for them this is one plain list, exactly as before. @@ -204,17 +204,29 @@ describe("HackathonSidebar's Participant View section", () => { expect(memberEntry()).toBeInTheDocument() }) - // Never fold away the lit row: an owner who navigates to a participant page - // finds the section around it already open, whatever it was before. - it("opens itself when an owner is on a participant page", async () => { + // Every link into a hackathon lands on Overview, a participant page. Opening + // the section around the lit row there opened it on every visit. + it("stays folded for an owner arriving on a participant page", async () => { await renderAt("/my/hackathon/h1/overview") - expect(memberEntry()).toBeInTheDocument() + expect(memberHeading()).toBeInTheDocument() + expect(memberEntry()).toBeNull() }) - it("closes again from the chevron while inside it", async () => { + // Earlier versions saved the choice; a browser still holding it is ignored. + it("ignores a saved preference to be open", async () => { + window.localStorage.setItem("sidebar-member-open", "true") await renderAt("/my/hackathon/h1/overview") + expect(memberEntry()).toBeNull() + }) + + it("closes again from the chevron", async () => { + await renderAt("/my/hackathon/h1/overview") + + await fireEvent.click( + screen.getByRole("button", { name: /Show Participant View/ }), + ) await fireEvent.click( screen.getByRole("button", { name: /Hide Participant View/ }), )