Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions frontend/common/code-help/create-user/create-user-next.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default function HomePage() {

export default function App({ Component, pageProps, flagsmithState } {
return (
<FlagsmithProvider
<FlagsmithProvider
serverState={flagsmithState}
options={{
environmentID: "${envId}",${
Expand All @@ -32,9 +32,9 @@ export default function App({ Component, pageProps, flagsmithState } {
: ''
}
}}
flagsmith={flagsmith}&gt;
&lt;Component {...pageProps} />
&lt;/FlagsmithProvider>
flagsmith={flagsmith}>
<Component {...pageProps} />
</FlagsmithProvider>
);
}

Expand Down
6 changes: 3 additions & 3 deletions frontend/common/code-help/create-user/create-user-react.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { FlagsmithProvider } from '${NPM_CLIENT}/react';

export default function App() {
return (
&lt;FlagsmithProvider
<FlagsmithProvider
options={{
environmentID: '${envId}',${
Constants.isCustomFlagsmithUrl()
Expand All @@ -20,9 +20,9 @@ export default function App() {
identity: '${userId || USER_ID}',
traits: {${TRAIT_NAME}: 21},
}}
flagsmith={flagsmith}&gt;
flagsmith={flagsmith}>
{...Your app}
&lt;/FlagsmithProvider>
</FlagsmithProvider>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
);
}

Expand Down
32 changes: 16 additions & 16 deletions frontend/common/code-help/init/init-next-app-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { FeatureFlagProvider } from "./components/FeatureFlagProvider";

export default async function RootLayout({
children,
}: Readonly&lt;{
}: Readonly<{
children: React.ReactNode;
}&gt;) {
}>) {
await flagsmith.init({
environmentID: "${envId}",${
Constants.isCustomFlagsmithUrl()
Expand All @@ -21,16 +21,16 @@ export default async function RootLayout({
const serverState = flagsmith.getState();

return (
&lt;html lang="en"&gt;
&lt;head&gt;
&lt;meta name="viewport" content="initial-scale=1, width=device-width" /&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;FeatureFlagProvider serverState={serverState}&gt;
<html lang="en">
<head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</head>
<body>
<FeatureFlagProvider serverState={serverState}>
{children}
&lt;/FeatureFlagProvider&gt;
&lt;/body&gt;
&lt;/html&gt;
</FeatureFlagProvider>
</body>
</html>
);
}

Expand All @@ -48,13 +48,13 @@ export const FeatureFlagProvider = ({
}: {
serverState: IState;
children: ReactNode;
}) =&gt; {
}) => {
const flagsmithInstance = useRef(createFlagsmithInstance());

return (
&lt;FlagsmithProvider flagsmith={flagsmithInstance.current} serverState={serverState}>
&lt;&gt;{children}&lt;/&gt;
&lt;/FlagsmithProvider&gt;
<FlagsmithProvider flagsmith={flagsmithInstance.current} serverState={serverState}>
<>{children}</>
</FlagsmithProvider>
);
};

Expand All @@ -69,6 +69,6 @@ export default function HomePage() {
const ${FEATURE_NAME_ALT} = flags.${FEATURE_NAME_ALT}.value

return (
&lt;&gt;{...}&lt;/&gt;
<>{...}</>
);
}`
10 changes: 5 additions & 5 deletions frontend/common/code-help/init/init-next-pages-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { FlagsmithProvider } from '@flagsmith/flagsmith/react';

export default function App({ Component, pageProps, flagsmithState } {
return (
&lt;FlagsmithProvider
<FlagsmithProvider
serverState={flagsmithState}
options={{
environmentID: "${envId}",${
Expand All @@ -17,9 +17,9 @@ export default function App({ Component, pageProps, flagsmithState } {
: ''
}
}}
flagsmith={flagsmith}&gt;
&lt;Component {...pageProps} />
&lt;/FlagsmithProvider>
flagsmith={flagsmith}>
<Component {...pageProps} />
</FlagsmithProvider>
);
}

Expand All @@ -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 (
&lt;>{...}&lt;/>
<>{...}</>
);
}`
38 changes: 18 additions & 20 deletions frontend/common/code-help/init/init-react.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,33 @@ 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
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return (
<>
{\`${FEATURE_NAME}: \${${FEATURE_NAME}}\`}
{\`${FEATURE_NAME_ALT}: \${${FEATURE_NAME_ALT}}\`}
</>
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

export default function App() {
return (
&lt;FlagsmithProvider
<FlagsmithProvider
options={{
environmentID: '${envId}',${
Constants.isCustomFlagsmithUrl()
? `\n api: '${Constants.getFlagsmithSDKUrl()}',`
: ''
}
}}
flagsmith={flagsmith}&gt;
{...Your app}
&lt;/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 (
&lt;>{...}&lt;/>
flagsmith={${LIB_NAME}}>
<HomePage />
</FlagsmithProvider>
);
}`
4 changes: 2 additions & 2 deletions frontend/common/code-help/install/install-curl.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export default () => `You can access the API directly with tools like <a href="https://curl.haxx.se/">curl</a> or <a href='https://httpie.org/'>httpie</a>,
export default () => `You can access the API directly with tools like curl or httpie,

@talissoncosta talissoncosta Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dropped <a> tags have been dead since at least 2021: the highlighter replaces the block content with highlighted plain text, stripping anchor elements, so prod shows exactly this text with no working links (the old .hljs a CSS was styling nothing). Copy Code also stops shipping the markup. @kyle-ssg , as the original author: sanity-check welcome that nothing else relied on these.

or with clients for languages that we do not currently have SDKs for.
You can view the API via Swagger at <a href="https://api.flagsmith.com/api/v1/docs/">https://api.flagsmith.com/api/v1/docs/</a>.
You can view the API via Swagger at https://api.flagsmith.com/api/v1/docs/.
`
4 changes: 1 addition & 3 deletions frontend/common/code-help/install/install-dotnet.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import Utils from 'common/utils/utils'

export default () => `// Package Manager
PM> Install-Package Flagsmith -Version 4.0.0

// .NET CLI
dotnet add package Flagsmith --version 4.0.0

// PackageReference
${Utils.escapeHtml('<PackageReference Include="Flagsmith" Version="4.0.0" />')}
<PackageReference Include="Flagsmith" Version="4.0.0" />

// Paket CLI
paket add Flagsmith --version 4.0.0
Expand Down
2 changes: 1 addition & 1 deletion frontend/common/code-help/install/install-flutter.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default () => `The client library is available from the <a href='https://pub.dev/packages/flagsmith'>https://pub.dev/packages/flagsmith</a>:
export default () => `The client library is available from the https://pub.dev/packages/flagsmith:
dependencies:
flagsmith:
Expand Down
12 changes: 5 additions & 7 deletions frontend/common/code-help/install/install-java.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import Utils from 'common/utils/utils'

export default () => `// Maven
${Utils.escapeHtml('<dependency>')}
${Utils.escapeHtml('<groupId>com.flagsmith</groupId>')}
${Utils.escapeHtml('<artifactId>flagsmith-java-client</artifactId>')}
${Utils.escapeHtml('<version>7.4.1</version>')}
${Utils.escapeHtml('</dependency>')}
<dependency>

@talissoncosta talissoncosta Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java and .NET didn't have literal entities in source; they escaped at runtime via Utils.escapeHtml, so the first sweep missed them and they double-escaped (showed &lt;dependency&gt; on screen), plus the same clipboard bug. After this, Utils.escapeHtml had no callers left, so a later commit on this branch removes it.

<groupId>com.flagsmith</groupId>
<artifactId>flagsmith-java-client</artifactId>
<version>7.4.1</version>
</dependency>

// Gradle
implementation 'com.flagsmith:flagsmith-java-client:7.4.1'
Expand Down
2 changes: 1 addition & 1 deletion frontend/common/code-help/install/install-rust.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export default () =>
'The package can be found at <a href="https://crates.io/crates/flagsmith">https://crates.io/crates/flagsmith</a>;'
'The package can be found at https://crates.io/crates/flagsmith;'
10 changes: 5 additions & 5 deletions frontend/common/code-help/traits/traits-next.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ export default function HomePage() {
};

return (
&lt;>{...}&lt;/>
<>{...}</>
);
}

//Option 2: Alternatively, if you wish to do this serverside

export default function App({ Component, pageProps, flagsmithState } {
return (
&lt;FlagsmithProvider
<FlagsmithProvider
serverState={flagsmithState}
options={{
environmentID: "${envId}",${
Expand All @@ -40,9 +40,9 @@ export default function App({ Component, pageProps, flagsmithState } {
: ''
}
}}
flagsmith={flagsmith}&gt;
&lt;Component {...pageProps} />
&lt;/FlagsmithProvider>
flagsmith={flagsmith}>
<Component {...pageProps} />
</FlagsmithProvider>
);
}

Expand Down
8 changes: 4 additions & 4 deletions frontend/common/code-help/traits/traits-react.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { FlagsmithProvider } from '${NPM_CLIENT}/react';

export default function App() {
return (
&lt;FlagsmithProvider
<FlagsmithProvider
options={{
environmentID: '${envId}',${
Constants.isCustomFlagsmithUrl()
Expand All @@ -20,9 +20,9 @@ export default function App() {
identity: '${userId || USER_ID}',
traits: {${TRAIT_NAME}: 21},
}}
flagsmith={flagsmith}&gt;
flagsmith={flagsmith}>
{...Your app}
&lt;/FlagsmithProvider>
</FlagsmithProvider>
);
}

Expand All @@ -46,6 +46,6 @@ export default function HomePage() {
};

return (
&lt;>{...}&lt;/>
<>{...}</>
);
}`
6 changes: 0 additions & 6 deletions frontend/common/utils/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,6 @@ const Utils = Object.assign({}, BaseUtils, {
}
return null
},
escapeHtml(html: string) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zero callers after this branch (Highlight has its own local escapeHtml for rendering). Removed so the next snippet author doesn't reach for it to pre-escape content again, which is the pattern that caused the Java/.NET bug.

const text = document.createTextNode(html)
const p = document.createElement('p')
p.appendChild(text)
return p.innerHTML
},
featureStateToValue(featureState: FeatureStateValue) {
if (!featureState) {
return null
Expand Down
1 change: 0 additions & 1 deletion frontend/web/components/CodeHelp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ const SnippetItem: FC<SnippetItemProps> = ({
/>
<Highlight
forceExpanded
preventEscape
className={
Constants.codeHelp.keys[
languageKey as keyof typeof Constants.codeHelp.keys
Expand Down
2 changes: 1 addition & 1 deletion frontend/web/components/Highlight.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ class Highlight extends React.Component {
}

const raw = this.getRawHtml()
const html = this.props.preventEscape ? raw : escapeHtml(raw)
const html = escapeHtml(raw)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always escaping is safe: the only two consumers that opted out (CodeHelp, MCPSnippet) did so because their sources were pre-escaped, and those sources are now plain text. Every other consumer already went through this path.

return (
<div className={this.state.expandable ? 'expandable' : ''}>
<pre
Expand Down
2 changes: 1 addition & 1 deletion frontend/web/components/integrations/mcp/MCPSnippet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type MCPSnippetProps = {

const MCPSnippet: FC<MCPSnippetProps> = ({ code, language = 'bash' }) => (
<div className='hljs-container mt-2'>
<Highlight forceExpanded preventEscape className={language}>
<Highlight forceExpanded className={language}>
{code}
</Highlight>
<div className='flex-column hljs-docs'>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (&lt; etc.). We render via <Highlight> (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'
Expand All @@ -14,14 +11,6 @@ import { SdkLang } from './sdkLangs'
// exported); if that placeholder ever changes, update this too.
const PLACEHOLDER_FLAG = 'my_cool_feature'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removed unescapeHtml helper existed only to undo the pre-escaped sources before the onboarding card rendered them. Dead once the sources are clean.


const unescapeHtml = (s: string): string =>
s
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&#39;/g, "'")
.replace(/&quot;/g, '"')
.replace(/&amp;/g, '&')

export type SdkSnippet = {
install: string
// Present for npm-based SDKs (codeHelp ships both managers in one block);
Expand Down Expand Up @@ -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(),
}
}
Loading