Skip to content

Commit af66d9f

Browse files
committed
fix(sidebar): match the collapse cookie value strictly (not a substring)
A substring search for 'sidebar_collapsed=1' also matched 'sidebar_collapsed=10', desyncing the pre-paint sidebar rail and client store from the strict server read. Parse the cookie value and compare it to '1' exactly, in both the pre-paint inline script and readCollapsedCookie. Added a store test.
1 parent dd3fe50 commit af66d9f

3 files changed

Lines changed: 43 additions & 6 deletions

File tree

apps/sim/app/layout.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,11 @@ export default function RootLayout({ children }: { children: React.ReactNode })
7979
var defaultSidebarWidth = 248;
8080
try {
8181
// Collapse comes from the cookie (independent of localStorage
82-
// parsing); the persisted width is read defensively below.
83-
var hasCookie = document.cookie.indexOf('sidebar_collapsed=') !== -1;
84-
var collapsed = document.cookie.indexOf('sidebar_collapsed=1') !== -1;
82+
// parsing); the persisted width is read defensively below. Match the
83+
// value strictly so 'sidebar_collapsed=10' isn't read as collapsed.
84+
var cookieMatch = document.cookie.match(/(?:^|;\s*)sidebar_collapsed=([^;]*)/);
85+
var hasCookie = cookieMatch !== null;
86+
var collapsed = cookieMatch !== null && cookieMatch[1] === '1';
8587
8688
var state = null;
8789
try {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { afterEach, describe, expect, it } from 'vitest'
5+
import { readCollapsedCookie } from './store'
6+
7+
function setCookie(value: string) {
8+
document.cookie = `sidebar_collapsed=${value}; path=/`
9+
}
10+
11+
afterEach(() => {
12+
document.cookie = 'sidebar_collapsed=; path=/; max-age=0'
13+
})
14+
15+
describe('readCollapsedCookie', () => {
16+
it('is true only for an exact value of 1', () => {
17+
setCookie('1')
18+
expect(readCollapsedCookie()).toBe(true)
19+
})
20+
21+
it('is false for 0', () => {
22+
setCookie('0')
23+
expect(readCollapsedCookie()).toBe(false)
24+
})
25+
26+
it('does not treat a substring value like 10 as collapsed', () => {
27+
setCookie('10')
28+
expect(readCollapsedCookie()).toBe(false)
29+
})
30+
31+
it('is false when the cookie is absent', () => {
32+
expect(readCollapsedCookie()).toBe(false)
33+
})
34+
})

apps/sim/stores/sidebar/store.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ function applyCollapsedCookie(collapsed: boolean) {
3535
document.cookie = `sidebar_collapsed=${collapsed ? '1' : '0'}; path=/; max-age=31536000; samesite=lax`
3636
}
3737

38-
/** Reads the collapse state the server saw, so the client store seeds identically. */
39-
function readCollapsedCookie(): boolean {
38+
/** Reads the collapse state the server saw, so the client store seeds identically. Matches the
39+
* cookie value strictly (`=1`) so `sidebar_collapsed=10` and the like aren't read as collapsed. */
40+
export function readCollapsedCookie(): boolean {
4041
if (typeof document === 'undefined') return false
41-
return document.cookie.includes('sidebar_collapsed=1')
42+
return document.cookie.match(/(?:^|;\s*)sidebar_collapsed=([^;]*)/)?.[1] === '1'
4243
}
4344

4445
export const useSidebarStore = create<SidebarState>()(

0 commit comments

Comments
 (0)