File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -28,7 +28,7 @@ import {
2828 normalizeThemePreference ,
2929 type ThemePreference ,
3030} from "~/utils/themePreference" ;
31- import { flag } from "~/v3/featureFlags.server" ;
31+ import { cachedFlag } from "~/v3/featureFlags.server" ;
3232import { getTimezonePreference } from "./services/preferences/uiPreferences.server" ;
3333import { appEnvTitleTag } from "./utils" ;
3434
@@ -80,9 +80,10 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
8080
8181 const user = await getUser ( request ) ;
8282 // Theme switching is feature-flagged; while off, everyone stays on the
83- // classic theme even if a preference was saved earlier.
83+ // classic theme even if a preference was saved earlier. Cached: the root
84+ // loader runs on every document request and client navigation.
8485 const showThemeSwitcher = user
85- ? await flag ( { key : "hasThemeSwitcher" , defaultValue : false } )
86+ ? await cachedFlag ( { key : "hasThemeSwitcher" , defaultValue : false } )
8687 : false ;
8788 // Logged-out pages (login, invites) always render the branded Classic look.
8889 const themePreference : ThemePreference = showThemeSwitcher
Original file line number Diff line number Diff line change @@ -140,7 +140,9 @@ export default function Page() {
140140 >
141141 < div >
142142 < FormTitle
143- LeadingIcon = { < EnvelopeIcon className = "size-6 text-cyan-500" /> }
143+ LeadingIcon = {
144+ < EnvelopeIcon className = "size-6 text-cyan-500 system:text-text-bright" />
145+ }
144146 className = "mb-0 text-sky-500 system:text-text-bright"
145147 title = { simplur `You have ${ invites . length } new invitation[|s]` }
146148 />
Original file line number Diff line number Diff line change @@ -55,6 +55,28 @@ export function makeFlag(_prisma: PrismaClientOrTransaction = prisma) {
5555 return flag ;
5656}
5757
58+ const cachedFlagStore = new Map < string , { value : unknown ; expiresAt : number } > ( ) ;
59+
60+ /**
61+ * flag() behind a short process-level TTL cache, for global flags read on hot
62+ * paths (e.g. the root loader) where a database round-trip per request is too
63+ * expensive. Flips propagate within ttlMs per process. Not for org- or
64+ * user-scoped decisions.
65+ */
66+ export async function cachedFlag < T extends FeatureFlagKey > (
67+ opts : FlagsOptions < T > & { defaultValue : z . infer < ( typeof FeatureFlagCatalog ) [ T ] > } ,
68+ ttlMs = 30_000
69+ ) : Promise < z . infer < ( typeof FeatureFlagCatalog ) [ T ] > > {
70+ const hit = cachedFlagStore . get ( opts . key ) ;
71+ if ( hit && hit . expiresAt > Date . now ( ) ) {
72+ return hit . value as z . infer < ( typeof FeatureFlagCatalog ) [ T ] > ;
73+ }
74+
75+ const value = await flag ( opts ) ;
76+ cachedFlagStore . set ( opts . key , { value, expiresAt : Date . now ( ) + ttlMs } ) ;
77+ return value ;
78+ }
79+
5880export function makeSetFlag ( _prisma : PrismaClientOrTransaction = prisma ) {
5981 return async function setFlag < T extends FeatureFlagKey > (
6082 opts : FlagsOptions < T > & { value : z . infer < ( typeof FeatureFlagCatalog ) [ T ] > }
You can’t perform that action at this time.
0 commit comments