From ba46fe2a2fb59010e42ec608aca71418ea46ce1d Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Tue, 21 Jul 2026 16:52:52 -0300 Subject: [PATCH 1/4] refactor(code-help): escape snippets in Highlight, not in source strings Highlight now always escapes (dropped the preventEscape branch); MCPSnippet and CodeHelp no longer pass preventEscape. Code-help sources use raw JSX () instead of hand-escaped entities (<), so they're readable. Rendered output is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../code-help/create-user/create-user-next.js | 8 ++-- .../create-user/create-user-react.js | 6 +-- .../code-help/init/init-next-app-router.js | 32 ++++++++-------- .../code-help/init/init-next-pages-router.js | 10 ++--- frontend/common/code-help/init/init-react.js | 38 +++++++++---------- .../common/code-help/traits/traits-next.js | 10 ++--- .../common/code-help/traits/traits-react.js | 8 ++-- frontend/web/components/CodeHelp.tsx | 1 - frontend/web/components/Highlight.js | 2 +- .../integrations/mcp/MCPSnippet.tsx | 2 +- 10 files changed, 57 insertions(+), 60 deletions(-) diff --git a/frontend/common/code-help/create-user/create-user-next.js b/frontend/common/code-help/create-user/create-user-next.js index fcd4b1c5c6e8..cfd0890775f8 100644 --- a/frontend/common/code-help/create-user/create-user-next.js +++ b/frontend/common/code-help/create-user/create-user-next.js @@ -23,7 +23,7 @@ export default function HomePage() { export default function App({ Component, pageProps, flagsmithState } { return ( - <FlagsmithProvider + - </FlagsmithProvider> + flagsmith={flagsmith}> + + ); } diff --git a/frontend/common/code-help/create-user/create-user-react.js b/frontend/common/code-help/create-user/create-user-react.js index 4de611b66d58..e44396b5e1a0 100644 --- a/frontend/common/code-help/create-user/create-user-react.js +++ b/frontend/common/code-help/create-user/create-user-react.js @@ -10,7 +10,7 @@ import { FlagsmithProvider } from '${NPM_CLIENT}/react'; export default function App() { return ( - <FlagsmithProvider + {...Your app} - </FlagsmithProvider> + ); } diff --git a/frontend/common/code-help/init/init-next-app-router.js b/frontend/common/code-help/init/init-next-app-router.js index 3cc974fa91f0..c912e46d5665 100644 --- a/frontend/common/code-help/init/init-next-app-router.js +++ b/frontend/common/code-help/init/init-next-app-router.js @@ -9,9 +9,9 @@ import { FeatureFlagProvider } from "./components/FeatureFlagProvider"; export default async function RootLayout({ children, -}: Readonly<{ +}: Readonly<{ children: React.ReactNode; -}>) { +}>) { await flagsmith.init({ environmentID: "${envId}",${ Constants.isCustomFlagsmithUrl() @@ -21,16 +21,16 @@ export default async function RootLayout({ const serverState = flagsmith.getState(); return ( - <html lang="en"> - <head> - <meta name="viewport" content="initial-scale=1, width=device-width" /> - </head> - <body> - <FeatureFlagProvider serverState={serverState}> + + + + + + {children} - </FeatureFlagProvider> - </body> - </html> + + + ); } @@ -48,13 +48,13 @@ export const FeatureFlagProvider = ({ }: { serverState: IState; children: ReactNode; -}) => { +}) => { const flagsmithInstance = useRef(createFlagsmithInstance()); return ( - <FlagsmithProvider flagsmith={flagsmithInstance.current} serverState={serverState}> - <>{children}</> - </FlagsmithProvider> + + <>{children} + ); }; @@ -69,6 +69,6 @@ export default function HomePage() { const ${FEATURE_NAME_ALT} = flags.${FEATURE_NAME_ALT}.value return ( - <>{...}</> + <>{...} ); }` diff --git a/frontend/common/code-help/init/init-next-pages-router.js b/frontend/common/code-help/init/init-next-pages-router.js index 1e60dc327167..87dcf864b5eb 100644 --- a/frontend/common/code-help/init/init-next-pages-router.js +++ b/frontend/common/code-help/init/init-next-pages-router.js @@ -8,7 +8,7 @@ import { FlagsmithProvider } from '@flagsmith/flagsmith/react'; export default function App({ Component, pageProps, flagsmithState } { return ( - <FlagsmithProvider + - </FlagsmithProvider> + flagsmith={flagsmith}> + + ); } @@ -43,6 +43,6 @@ export default function HomePage() { const ${FEATURE_NAME} = flags.${FEATURE_NAME}.enabled const ${FEATURE_NAME_ALT} = flags.${FEATURE_NAME_ALT}.value return ( - <>{...}</> + <>{...} ); }` diff --git a/frontend/common/code-help/init/init-react.js b/frontend/common/code-help/init/init-react.js index 6b1a3412cf0e..8b143701b529 100644 --- a/frontend/common/code-help/init/init-react.js +++ b/frontend/common/code-help/init/init-react.js @@ -3,13 +3,24 @@ import Constants from 'common/constants' export default ( envId, { FEATURE_NAME, FEATURE_NAME_ALT, LIB_NAME, NPM_CLIENT }, -) => `// App root -import ${LIB_NAME} from "${NPM_CLIENT}"; -import { FlagsmithProvider } from '@flagsmith/flagsmith/react'; +) => `import ${LIB_NAME} from "${NPM_CLIENT}"; +import { FlagsmithProvider, useFlags } from '${NPM_CLIENT}/react'; + +export function HomePage() { + const flags = useFlags(['${FEATURE_NAME}','${FEATURE_NAME_ALT}']); // only causes re-render if specified flag values / traits change + const ${FEATURE_NAME} = flags.${FEATURE_NAME}.enabled + const ${FEATURE_NAME_ALT} = flags.${FEATURE_NAME_ALT}.value + return ( + <> + {\`${FEATURE_NAME}: \${${FEATURE_NAME}}\`} + {\`${FEATURE_NAME_ALT}: \${${FEATURE_NAME_ALT}}\`} + + ); +} export default function App() { return ( - <FlagsmithProvider + - ); -} - -// Home Page -import ${LIB_NAME} from '${NPM_CLIENT}'; -import { useFlags, useFlagsmith } from '${NPM_CLIENT}/react'; - -export default function HomePage() { - const flags = useFlags(['${FEATURE_NAME}','${FEATURE_NAME_ALT}']); // only causes re-render if specified flag values / traits change - const ${FEATURE_NAME} = flags.${FEATURE_NAME}.enabled - const ${FEATURE_NAME_ALT} = flags.${FEATURE_NAME_ALT}.value - return ( - <>{...}</> + flagsmith={${LIB_NAME}}> + + ); }` diff --git a/frontend/common/code-help/traits/traits-next.js b/frontend/common/code-help/traits/traits-next.js index 1a096ed39dda..d396cb7e2983 100644 --- a/frontend/common/code-help/traits/traits-next.js +++ b/frontend/common/code-help/traits/traits-next.js @@ -23,7 +23,7 @@ export default function HomePage() { }; return ( - <>{...}</> + <>{...} ); } @@ -31,7 +31,7 @@ export default function HomePage() { export default function App({ Component, pageProps, flagsmithState } { return ( - <FlagsmithProvider + - </FlagsmithProvider> + flagsmith={flagsmith}> + + ); } diff --git a/frontend/common/code-help/traits/traits-react.js b/frontend/common/code-help/traits/traits-react.js index c9bf65a6bff2..05df85d95c20 100644 --- a/frontend/common/code-help/traits/traits-react.js +++ b/frontend/common/code-help/traits/traits-react.js @@ -10,7 +10,7 @@ import { FlagsmithProvider } from '${NPM_CLIENT}/react'; export default function App() { return ( - <FlagsmithProvider + {...Your app} - </FlagsmithProvider> + ); } @@ -46,6 +46,6 @@ export default function HomePage() { }; return ( - <>{...}</> + <>{...} ); }` diff --git a/frontend/web/components/CodeHelp.tsx b/frontend/web/components/CodeHelp.tsx index ad2dbb87e052..47959a888382 100644 --- a/frontend/web/components/CodeHelp.tsx +++ b/frontend/web/components/CodeHelp.tsx @@ -144,7 +144,6 @@ const SnippetItem: FC = ({ />
 = ({ code, language = 'bash' }) => (
   
- + {code}
From 88dd8d4d9bc07448cea0836049c229d290ce5854 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Thu, 23 Jul 2026 14:19:14 -0300 Subject: [PATCH 2/4] fix(code-help): drop runtime escaping from the Java and .NET installs They escaped their XML with Utils.escapeHtml at render-build time, which the entity sweep missed (nothing literal in the source). With Highlight escaping centrally they double-escaped, showing <dependency> on screen, and Copy Code shipped entities. The onboarding sdkSnippets unescape workaround is dead now the sources are clean, so it's gone too. Co-Authored-By: Claude Fable 5 --- .../code-help/install/install-dotnet.js | 4 +--- .../common/code-help/install/install-java.js | 12 +++++------ .../OnboardingConnectPanel/sdkSnippets.ts | 21 +++++-------------- 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/frontend/common/code-help/install/install-dotnet.js b/frontend/common/code-help/install/install-dotnet.js index 4f4fd96e1470..75ba2759359f 100644 --- a/frontend/common/code-help/install/install-dotnet.js +++ b/frontend/common/code-help/install/install-dotnet.js @@ -1,5 +1,3 @@ -import Utils from 'common/utils/utils' - export default () => `// Package Manager PM> Install-Package Flagsmith -Version 4.0.0 @@ -7,7 +5,7 @@ PM> Install-Package Flagsmith -Version 4.0.0 dotnet add package Flagsmith --version 4.0.0 // PackageReference -${Utils.escapeHtml('')} + // Paket CLI paket add Flagsmith --version 4.0.0 diff --git a/frontend/common/code-help/install/install-java.js b/frontend/common/code-help/install/install-java.js index ca178a99420e..06dbf428eca4 100644 --- a/frontend/common/code-help/install/install-java.js +++ b/frontend/common/code-help/install/install-java.js @@ -1,11 +1,9 @@ -import Utils from 'common/utils/utils' - export default () => `// Maven -${Utils.escapeHtml('')} - ${Utils.escapeHtml('com.flagsmith')} - ${Utils.escapeHtml('flagsmith-java-client')} - ${Utils.escapeHtml('7.4.1')} -${Utils.escapeHtml('')} + + com.flagsmith + flagsmith-java-client + 7.4.1 + // Gradle implementation 'com.flagsmith:flagsmith-java-client:7.4.1' diff --git a/frontend/web/components/pages/onboarding/OnboardingConnectPanel/sdkSnippets.ts b/frontend/web/components/pages/onboarding/OnboardingConnectPanel/sdkSnippets.ts index f141a31241ce..9611a45e4f8f 100644 --- a/frontend/web/components/pages/onboarding/OnboardingConnectPanel/sdkSnippets.ts +++ b/frontend/web/components/pages/onboarding/OnboardingConnectPanel/sdkSnippets.ts @@ -1,11 +1,8 @@ // Snippets for every SDK we support, sourced from the maintained // `Constants.codeHelp` (the same install/init the rest of the app uses, so -// they don't drift). Two adaptations for this page: -// 1. codeHelp snippets are authored for innerHTML rendering, so they carry -// HTML entities (< etc.). We render via (escaping on), so -// we unescape first. -// 2. They use a placeholder flag name; we swap it for the user's real flag, -// so the snippet references the flag this onboarding actually created. +// they don't drift). One adaptation for this page: codeHelp uses a placeholder +// flag name; we swap it for the user's real flag, so the snippet references +// the flag this onboarding actually created. // The SDK list itself (labels, logos, codeHelp keys) lives in ./sdkLangs. import Constants from 'common/constants' import { SdkLang } from './sdkLangs' @@ -14,14 +11,6 @@ import { SdkLang } from './sdkLangs' // exported); if that placeholder ever changes, update this too. const PLACEHOLDER_FLAG = 'my_cool_feature' -const unescapeHtml = (s: string): string => - s - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/'/g, "'") - .replace(/"/g, '"') - .replace(/&/g, '&') - export type SdkSnippet = { install: string // Present for npm-based SDKs (codeHelp ships both managers in one block); @@ -55,12 +44,12 @@ export const getSdkSnippet = ( string, string > - const wire = unescapeHtml(inits[lang.initKey ?? lang.codeHelpKey] ?? '') + const wire = (inits[lang.initKey ?? lang.codeHelpKey] ?? '') .split(PLACEHOLDER_FLAG) .join(featureName) return { language: lang.language, - ...parseInstall(unescapeHtml(installs[lang.codeHelpKey] ?? '')), + ...parseInstall(installs[lang.codeHelpKey] ?? ''), wire: wire.trim(), } } From 90b56a68849eec0cc7d3fbcec19c875eee282ce9 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Thu, 23 Jul 2026 14:46:51 -0300 Subject: [PATCH 3/4] refactor(utils): remove the caller-free escapeHtml Highlight is the single escape point now; keeping this helper around invites pre-escaping content again, the pattern this branch removes. Co-Authored-By: Claude Fable 5 --- frontend/common/utils/utils.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/frontend/common/utils/utils.tsx b/frontend/common/utils/utils.tsx index 1d718f5c7d3e..9adb9b7acdc4 100644 --- a/frontend/common/utils/utils.tsx +++ b/frontend/common/utils/utils.tsx @@ -180,12 +180,6 @@ const Utils = Object.assign({}, BaseUtils, { } return null }, - escapeHtml(html: string) { - const text = document.createTextNode(html) - const p = document.createElement('p') - p.appendChild(text) - return p.innerHTML - }, featureStateToValue(featureState: FeatureStateValue) { if (!featureState) { return null From 7e217da6cf352585f8b41faa79c52ac28c5475e6 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Thu, 23 Jul 2026 15:32:46 -0300 Subject: [PATCH 4/4] fix(code-help): drop the dead HTML anchors from the prose installs With Highlight escaping everything, the tags in the curl, Flutter and Rust install notes showed up as raw markup. The links were already dead on prod (the highlighter strips the tags) and Copy Code shipped the markup, so keep the same text prod effectively renders, minus the tags. Co-Authored-By: Claude Fable 5 --- frontend/common/code-help/install/install-curl.js | 4 ++-- frontend/common/code-help/install/install-flutter.js | 2 +- frontend/common/code-help/install/install-rust.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/common/code-help/install/install-curl.js b/frontend/common/code-help/install/install-curl.js index 2fcfd5c7dd22..2fd55704043d 100644 --- a/frontend/common/code-help/install/install-curl.js +++ b/frontend/common/code-help/install/install-curl.js @@ -1,5 +1,5 @@ -export default () => `You can access the API directly with tools like curl or httpie, +export default () => `You can access the API directly with tools like curl or httpie, or with clients for languages that we do not currently have SDKs for. -You can view the API via Swagger at https://api.flagsmith.com/api/v1/docs/. +You can view the API via Swagger at https://api.flagsmith.com/api/v1/docs/. ` diff --git a/frontend/common/code-help/install/install-flutter.js b/frontend/common/code-help/install/install-flutter.js index b3d1008d64c7..98948dd73e11 100644 --- a/frontend/common/code-help/install/install-flutter.js +++ b/frontend/common/code-help/install/install-flutter.js @@ -1,4 +1,4 @@ -export default () => `The client library is available from the https://pub.dev/packages/flagsmith: +export default () => `The client library is available from the https://pub.dev/packages/flagsmith: dependencies: flagsmith: diff --git a/frontend/common/code-help/install/install-rust.js b/frontend/common/code-help/install/install-rust.js index 3b7ebc35a112..48724cb5ae04 100644 --- a/frontend/common/code-help/install/install-rust.js +++ b/frontend/common/code-help/install/install-rust.js @@ -1,2 +1,2 @@ export default () => - 'The package can be found at https://crates.io/crates/flagsmith;' + 'The package can be found at https://crates.io/crates/flagsmith;'