From 52ea335714f2b495b92f87636c269b73b4067066 Mon Sep 17 00:00:00 2001 From: Adam Mathes Date: Mon, 16 Feb 2026 11:28:01 -0800 Subject: v3: persist sidebar state via cookie, default closed on tablet/mobile Sidebar open/close state is now saved to a cookie (neko_sidebar) so it persists across page reloads. On first visit without a cookie, the sidebar defaults to closed on tablet and mobile (<=1024px viewport) and open on desktop. Removes the auto-open-on-resize behavior that was overriding user preference. Co-Authored-By: Claude Opus 4.6 --- frontend-vanilla/src/main.ts | 6 +----- frontend-vanilla/src/store.test.ts | 42 +++++++++++++++++++++++++++++++++++++- frontend-vanilla/src/store.ts | 20 +++++++++++++++++- 3 files changed, 61 insertions(+), 7 deletions(-) (limited to 'frontend-vanilla/src') diff --git a/frontend-vanilla/src/main.ts b/frontend-vanilla/src/main.ts index 7d75e14..3dc0574 100644 --- a/frontend-vanilla/src/main.ts +++ b/frontend-vanilla/src/main.ts @@ -87,11 +87,7 @@ export function attachLayoutListeners() { store.setSidebarVisible(false); }); - window.addEventListener('resize', () => { - if (window.innerWidth > 768 && !store.sidebarVisible) { - store.setSidebarVisible(true); - } - }); + // Sidebar state is persisted via cookie; no auto-open on resize // Collapsible sections document.querySelectorAll('.sidebar-section.collapsible h3').forEach(header => { diff --git a/frontend-vanilla/src/store.test.ts b/frontend-vanilla/src/store.test.ts index 33deb7f..799db83 100644 --- a/frontend-vanilla/src/store.test.ts +++ b/frontend-vanilla/src/store.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi } from 'vitest'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; import { Store } from './store'; describe('Store', () => { @@ -105,4 +105,44 @@ describe('Store', () => { expect(localStorage.getItem('neko-font-theme')).toBe('serif'); expect(callback).toHaveBeenCalled(); }); + + describe('sidebar cookie persistence', () => { + beforeEach(() => { + // Clear sidebar cookie + document.cookie = 'neko_sidebar=; path=/; max-age=0'; + }); + + it('should persist sidebar state to cookie', () => { + const store = new Store(); + store.setSidebarVisible(false); + expect(document.cookie).toContain('neko_sidebar=0'); + + store.setSidebarVisible(true); + expect(document.cookie).toContain('neko_sidebar=1'); + }); + + it('should read sidebar state from cookie on init', () => { + document.cookie = 'neko_sidebar=1; path=/'; + const store = new Store(); + expect(store.sidebarVisible).toBe(true); + + document.cookie = 'neko_sidebar=0; path=/'; + const store2 = new Store(); + expect(store2.sidebarVisible).toBe(false); + }); + + it('should default to closed on tablet/mobile when no cookie', () => { + // jsdom defaults innerWidth to 0, which is <= 1024 + const store = new Store(); + expect(store.sidebarVisible).toBe(false); + }); + + it('should emit sidebar-toggle when toggling', () => { + const store = new Store(); + const callback = vi.fn(); + store.on('sidebar-toggle', callback); + store.toggleSidebar(); + expect(callback).toHaveBeenCalled(); + }); + }); }); diff --git a/frontend-vanilla/src/store.ts b/frontend-vanilla/src/store.ts index a23934a..c92059a 100644 --- a/frontend-vanilla/src/store.ts +++ b/frontend-vanilla/src/store.ts @@ -4,6 +4,23 @@ export type StoreEvent = 'feeds-updated' | 'tags-updated' | 'items-updated' | 'a export type FilterType = 'unread' | 'all' | 'starred'; +function getSidebarCookie(): boolean | null { + const match = document.cookie.split('; ').find(row => row.startsWith('neko_sidebar=')); + if (!match) return null; + return match.split('=')[1] === '1'; +} + +function setSidebarCookie(visible: boolean) { + document.cookie = `neko_sidebar=${visible ? '1' : '0'}; path=/; max-age=31536000; SameSite=Lax`; +} + +function getInitialSidebarVisible(): boolean { + const saved = getSidebarCookie(); + if (saved !== null) return saved; + // Default: closed on tablet+mobile (<=1024px), open on desktop + return window.innerWidth > 1024; +} + export class Store extends EventTarget { feeds: Feed[] = []; tags: Category[] = []; @@ -16,7 +33,7 @@ export class Store extends EventTarget { hasMore: boolean = true; theme: string = localStorage.getItem('neko-theme') || 'light'; fontTheme: string = localStorage.getItem('neko-font-theme') || 'default'; - sidebarVisible: boolean = window.innerWidth > 768; + sidebarVisible: boolean = getInitialSidebarVisible(); setFeeds(feeds: Feed[]) { this.feeds = feeds; @@ -86,6 +103,7 @@ export class Store extends EventTarget { setSidebarVisible(visible: boolean) { this.sidebarVisible = visible; + setSidebarCookie(visible); this.emit('sidebar-toggle'); } -- cgit v1.2.3