Sidebar Preferences
Sidebar Preferences
Per-user setting for which dashboard sidebar navigation items are hidden. Stored in the shared settings table, surfaced in Settings → Display.
Architecture
- Uses
getSetting/setSettingfrom@/lib/db/settings-helpers.serverover the sharedsettingspgTable (composite(user_id, key)primary key) , seearchitecture/postgres-port.mdfor the DB layer. - Stores a JSON array of URL path patterns (e.g.
["/dashboard/ai-chat"]) under a single settings key, serialized withJSON.stringify/JSON.parse, not a dedicated table, since this is one small blob per user rather than rows that need querying individually. - Input is bounded: at most 50 entries, each a
/-prefixed path matching^/[\w\-/]*$, max 200 chars , these are nav item paths, not arbitrary user content. The unbounded version previously let an authenticated user write an unbounded payload into their own settings row, which would then be loaded in full by the data-export path. - Default (no row): all sidebar items visible (
[]) - Dedicated query key in
query-keys.tsfor cache management - Server fn:
getSidebarPreferencesFn(GET),updateSidebarPreferencesFn(POST)
Gate ordering
The write is a single-user settings blob replace , no read-then-write check on existing state, so it's a straightforward mutation inside withUserTransaction, no pre-transaction gate needed beyond the rate limiter:
export const updateSidebarPreferencesFn = createServerFn({ method: 'POST' })
.validator(
z.object({
hiddenUrls: z
.array(z.string().max(200).regex(/^\/[\w\-/]*$/))
.max(50)
})
)
.handler(async ({ data }) => {
const user = await requireUser();
if (!user) return err(ERROR_CODES.UNAUTHORIZED, 'Not authenticated');
if (sidebarPreferencesLimiter.check(user.id))
return err(ERROR_CODES.RATE_LIMITED, 'Too many requests');
return withUserTransaction(user.id, async (tx, userId) => {
await setSetting(
tx,
userId,
HIDDEN_SIDEBAR_ITEMS_KEY,
JSON.stringify(data.hiddenUrls)
);
return ok(undefined);
});
});
Reading (getSidebarPreferencesFn) parses the stored JSON, falling back to [] when no row exists.
Tests
Covered in src/features/sidebar-preferences/server/sidebar-preferences-crud.test.ts.