diff --git a/scripts/generate-md.mjs b/scripts/generate-md.mjs index b7546c93dc..7525f76e93 100644 --- a/scripts/generate-md.mjs +++ b/scripts/generate-md.mjs @@ -168,8 +168,10 @@ ${content} // 4. Remove other self-closing component tags that we might want to strip? // For now, let's keep others unless specific instruction, but user said "Remove everything extra". - // Let's strip standard HTML comments - processed = processed.replace(//g, ''); + // Strip HTML and MDX comments. Both render to nothing on the site, so authors + // use them to hide unreleased features and TODOs — without this they leak into + // the .md exports and llms-full.txt and read as shipped documentation. + processed = stripComments(processed); // 5. Clean extra empty lines created by stripping processed = processed.replace(/\n{3,}/g, '\n\n'); @@ -177,6 +179,66 @@ ${content} return processed.trim(); } +// Remove `{/* ... */}` and `` comments, including multi-line ones, +// while leaving fenced code blocks untouched. Snippets legitimately contain both +// — an AndroidManifest example, a JSX sample — and those must survive verbatim. +export function stripComments(content) { + const PAIRS = [['{/*', '*/}'], ['']]; + const lines = content.split('\n'); + const out = []; + let inFence = false; + let closer = null; + + for (const line of lines) { + if (/^\s*(```|~~~)/.test(line)) { + inFence = !inFence; + out.push(line); + continue; + } + if (inFence) { + out.push(line); + continue; + } + + let rest = line; + let kept = ''; + + // Finish a comment that opened on an earlier line. + if (closer) { + const end = rest.indexOf(closer); + if (end === -1) continue; + rest = rest.slice(end + closer.length); + closer = null; + } + + // Consume every comment that opens on this line. + for (;;) { + let next = null; + for (const [open, close] of PAIRS) { + const at = rest.indexOf(open); + if (at !== -1 && (next === null || at < next.at)) next = { at, open, close }; + } + if (!next) { + kept += rest; + break; + } + kept += rest.slice(0, next.at); + const after = rest.slice(next.at + next.open.length); + const end = after.indexOf(next.close); + if (end === -1) { + closer = next.close; + break; + } + rest = after.slice(end + next.close.length); + } + + // Drop lines that were nothing but a comment; keep genuine blank lines. + if (kept.trim() || !line.trim()) out.push(kept.trimEnd()); + } + + return out.join('\n'); +} + function cleanFrontmatter(content) { const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n/; const match = content.match(frontmatterRegex); @@ -202,6 +264,13 @@ function cleanFrontmatter(content) { return content.replace(frontmatterRegex, `---\n${keptLines.join('\n')}\n---\n\n`); } +// Mirrors the check in generate-platform-llms-full.mjs. +function isDraft(content) { + const match = content.match(/^---\s*\n([\s\S]*?)\n---/); + if (!match) return false; + return /^draft:\s*true\s*$/m.test(match[1]); +} + async function processFiles(dir, reusableComponents, englishFiles) { const entries = await fs.readdir(dir, { withFileTypes: true }); @@ -213,6 +282,11 @@ async function processFiles(dir, reusableComponents, englishFiles) { } else if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.mdx'))) { const rawContent = await fs.readFile(fullPath, 'utf-8'); + // `draft: true` pages are skipped by the site router, so they must not + // get a .md export either — otherwise the content stays fetchable at + // /docs/.md even though the page itself 404s. + if (isDraft(rawContent)) continue; + // Clean Frontmatter let content = cleanFrontmatter(rawContent); @@ -257,6 +331,8 @@ async function processLocaleFiles(locale, baseComponents, englishFiles) { if (!entry.isFile() || (!entry.name.endsWith('.md') && !entry.name.endsWith('.mdx'))) continue; const rawContent = await fs.readFile(path.join(localeDir, entry.name), 'utf-8'); + if (isDraft(rawContent)) continue; + let content = cleanFrontmatter(rawContent); content = stripContent(content, components); diff --git a/scripts/generate-platform-llms-full.mjs b/scripts/generate-platform-llms-full.mjs index 2dc3591e3c..1656d31a35 100644 --- a/scripts/generate-platform-llms-full.mjs +++ b/scripts/generate-platform-llms-full.mjs @@ -1,3 +1,4 @@ +import { stripComments } from './generate-md.mjs'; import fs from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -92,6 +93,10 @@ async function getLocaleReusableComponents(baseComponents, locale) { function stripContent(content, reusableComponents) { let processed = content; + // 0. Remove MDX/HTML comments before anything else, so hidden unreleased + // features and TODOs never reach the LLM bundles. + processed = stripComments(processed); + // 1. Remove imports processed = processed.replace(/^import\s+.*?;?\s*$/gm, ''); @@ -115,8 +120,7 @@ function stripContent(content, reusableComponents) { processed = processed.replace(regex, componentContent); } - // 4. Remove HTML comments - processed = processed.replace(//g, ''); + // 4. HTML comments are already gone — stripComments() handled them in step 0. // 5. Clean extra empty lines processed = processed.replace(/\n{3,}/g, '\n\n'); diff --git a/src/assets/Inline/_gallery.html b/src/assets/Inline/_gallery.html index 24b8111472..29b17dcb3a 100644 --- a/src/assets/Inline/_gallery.html +++ b/src/assets/Inline/_gallery.html @@ -105,6 +105,7 @@

Inline Icon Gallery

redo.svg
refresh.svg
remote-config.svg
+
restore.svg
send-to-device.svg
settings.svg
strikethrough.svg
diff --git a/src/assets/Inline/restore.svg b/src/assets/Inline/restore.svg new file mode 100644 index 0000000000..c3ced54181 --- /dev/null +++ b/src/assets/Inline/restore.svg @@ -0,0 +1 @@ + diff --git a/src/assets/shared/img/back-to-active-flow.webp b/src/assets/shared/img/back-to-active-flow.webp new file mode 100644 index 0000000000..0fca96d11e Binary files /dev/null and b/src/assets/shared/img/back-to-active-flow.webp differ diff --git a/src/assets/shared/img/flow-copy-archive.webp b/src/assets/shared/img/flow-copy-archive.webp new file mode 100644 index 0000000000..dd257e9c22 Binary files /dev/null and b/src/assets/shared/img/flow-copy-archive.webp differ diff --git a/src/assets/shared/img/flow-list.webp b/src/assets/shared/img/flow-list.webp new file mode 100644 index 0000000000..d3492fd079 Binary files /dev/null and b/src/assets/shared/img/flow-list.webp differ diff --git a/src/assets/shared/img/flows-filter-by-placement.webp b/src/assets/shared/img/flows-filter-by-placement.webp new file mode 100644 index 0000000000..aa7624b1a4 Binary files /dev/null and b/src/assets/shared/img/flows-filter-by-placement.webp differ diff --git a/src/assets/shared/img/flows-filter-by-state.webp b/src/assets/shared/img/flows-filter-by-state.webp new file mode 100644 index 0000000000..8dac9e5e3f Binary files /dev/null and b/src/assets/shared/img/flows-filter-by-state.webp differ diff --git a/src/content/docs/guides/flow-builder/archive-flows.mdx b/src/content/docs/guides/flow-builder/archive-flows.mdx new file mode 100644 index 0000000000..f92664b7aa --- /dev/null +++ b/src/content/docs/guides/flow-builder/archive-flows.mdx @@ -0,0 +1,41 @@ +--- +title: "Archive flows" +description: "Archive the flows you no longer need, and keep their metrics." +metadataTitle: "Archive flows | Flows | Adapty Docs" +--- + +Archive a flow you stopped using to keep the **Flows** list focused. Archived flows sort to the bottom, and you can filter them out by status. Their metrics stay intact. + +:::warning +An archived flow can't be restored, edited, or [copied](copy-flows). Do not archive the flows you may need in the future. +::: + + + +## Archive a flow + +Find the flow in the [**Flows**](https://app.adapty.io/flows) list. Open the context menu and select **Archive**. + +:::warning +Don't archive a live flow. +::: + +Before you archive the flow: + +- Remove it from every [placement](placements). +- Archive any [A/B tests](ab-tests) that still use it. + +Archived flows stay in the **Flows** list with an **Archived** status. To hide them, [filter the list by status](filter-flows#filter-by-status) and leave **Archived** unselected. + +{/* TODO: uncomment when flow restore ships (ADP-6723). Then rename this article +to "Archive & restore flows", drop "restored" from the warning at the top, and +update the archived-flow row in filter-flows.mdx, which says the menu shows +**Metrics** only. + +## Restore a flow + +To restore a flow, locate it in the [Flows list](filter-flows), and select **Back to active** from the context menu . Adapty restores the flow to its pre-archival status, but doesn't add it to any placements. + + + +*/} diff --git a/src/content/docs/guides/flow-builder/builder-save-publish.mdx b/src/content/docs/guides/flow-builder/builder-save-publish.mdx index 89cdae1f88..484e9449dc 100644 --- a/src/content/docs/guides/flow-builder/builder-save-publish.mdx +++ b/src/content/docs/guides/flow-builder/builder-save-publish.mdx @@ -47,12 +47,12 @@ A flow is ready to publish once every action, screen, and product element is ful :::warning [Custom fonts](using-custom-fonts-in-flow-builder) don't ship with the flow — you must add each font file to your app bundle. Without the file, users see the system fallback. -To change a font on a published flow without breaking older versions: duplicate the flow, change the font in the copy, and target the copy to an [audience](add-audience-paywall-ab-test) on app versions that include the font. +To change a font on a published flow without breaking older versions: [copy the flow](copy-flows), change the font in the copy, and target the copy to an [audience](add-audience-paywall-ab-test) on app versions that include the font. ::: ## Flow status -Each flow shows a status in the Flows list. The status reflects where the flow is in the save and publish lifecycle. +Each flow shows a status in the [Flows list](filter-flows). The status reflects where the flow is in the save and publish lifecycle. | Status | Meaning | | :----- | :------ | @@ -61,7 +61,7 @@ Each flow shows a status in the Flows list. The status reflects where the flow i | **Publishing** | A publish is in progress. | | **Failed** | The last publish attempt failed. Users keep seeing the last published version, if there is one. | | **Published** | The latest saved version is live. There are no unpublished edits. | -| **Archived** | The flow was deleted. | +| **Archived** | The flow was [archived](archive-flows#archive-a-flow) and can't be edited. | ## Troubleshooting diff --git a/src/content/docs/guides/flow-builder/copy-flows.mdx b/src/content/docs/guides/flow-builder/copy-flows.mdx new file mode 100644 index 0000000000..dc94dcf846 --- /dev/null +++ b/src/content/docs/guides/flow-builder/copy-flows.mdx @@ -0,0 +1,51 @@ +--- +title: "Copy flows and screens" +description: "Duplicate a whole flow to tweak it for an audience or an A/B test variant, or copy single screens to reuse them." +metadataTitle: "Copy flows & screens | Flows | Adapty Docs" +--- + +Copy the flow and change a few elements to tweak it for a specific [audience](add-audience-paywall-ab-test), [A/B test](ab-tests), or app version. Copy a single screen to reuse it in another flow. + + + +## Copy a flow + +1. Open the [**Flows**](filter-flows) page. +2. In the flow's row, open the context menu and select **Copy**. You can't copy an archived flow. +3. The Flow Builder opens the copy as a new **Draft** with the name of the original plus a `_copy` suffix. + + The copy contains everything stored in the flow itself: + + - **Screens and elements**: Every screen, with its layout, elements, and interactions. + - **Assigned products**: The connections between your product cards and your store products. + - **Design**: Saved color and text styles, backgrounds, and dark mode values. + - **Custom fonts**: All flows in one app share the same [custom fonts](using-custom-fonts-in-flow-builder), so the copy keeps them. + - **Variables**: [Custom, element, and product variables](onboarding-variables). + - **Localizations**: Every locale, with its translated text and media. + - **Remote config**: Every locale's [JSON payload](customize-flow-with-remote-config). + + Anything stored outside the flow stays behind: + + - **Placements**: The copy belongs to no [placement](placements). + - **A/B tests**: The copy joins no [A/B test](ab-tests). + - **Metrics**: The copy starts with an empty [analytics history](flow-metrics). + +4. Optional: [rename the flow](paywall-builder-templates#create-flow). Adapty allows duplicate flow names — a unique name helps to avoid confusion. +5. Save [or publish](builder-save-publish) your copy. A copy remains a **Draft** until published. +6. Add the copy to a [placement](placements) to show it to users. + +## Copy screens between flows + +To move a screen into another flow, [**Copy**](paywall-layout-and-products#screen-actions) it to your clipboard, open the destination flow, and paste it into the **Screens** panel. The clipboard allows you to transfer the screen between different flows or even browsers. + +Adapty doesn't copy what the screen references at flow level. A pasted screen can end up incomplete, and may include broken references. A flow with broken references may [fail to publish](builder-save-publish#troubleshooting). + +Review each of these after you paste: + +- **Variables**: Text bindings, conditions, and actions no longer resolve. Replace the references with variables from the destination flow. +- **Saved styles**: Elements lose their color and text styles. Apply a style from the destination flow. +- **Navigation actions**: **Navigate to screen** actions lose their destination. Point each one at a screen in the destination flow. +- **Localizations**: Only the locales both flows share keep their translations. Users in any other locale see the destination flow's default. +- **Products and custom fonts**: Every flow in an app can use the same products and fonts. A screen pasted from a different flow in the same app keeps its products and fonts. Pasting a screen from a different app will break its product and font references. + +Replace each broken reference with a working equivalent in the destination flow. diff --git a/src/content/docs/guides/flow-builder/filter-flows.mdx b/src/content/docs/guides/flow-builder/filter-flows.mdx new file mode 100644 index 0000000000..dba8171211 --- /dev/null +++ b/src/content/docs/guides/flow-builder/filter-flows.mdx @@ -0,0 +1,42 @@ +--- +title: "Find and filter flows" +description: "Search, filter, and sort your flows, and open the actions available for each one." +metadataTitle: "Find and filter flows | Flows | Adapty Docs" +--- + +The [**Flows**](https://app.adapty.io/flows) page lists every flow in your app with its [status](#filter-by-status) and main [metrics](flow-metrics). Click a flow to open its full [metrics](flow-metrics), or, if you hold the **Developer** role — the [Flow Builder](adapty-flow-builder). + + + +## Search by name + +**Search by flow name** matches any part of a flow name. It doesn't search placements, products, or screen content. + +## Filter by status + +Open the **All states** filter above the list, select one or more statuses, and click **Apply**. [Save & publish flows](builder-save-publish#flow-status) explains what each status means and how a flow moves between them. + + + +## Filter by placement + +Open the **All placements** filter above the list, select one or more [placements](placements), and click **Apply**. This filter appears when you have at least one flow assigned to a placement. + + + +## Sort + +You can sort by **Flow name** or **Status**. The metric columns aren't sortable. By default, published flows come first and archived flows last. + +## Flow actions + +Open the context menu in a flow's row. + +| Action | What it does | +| :----- | :----------- | +| **Metrics** | Opens the flow's [metrics](flow-metrics). | +| **Edit** | Opens the flow in the [Flow Builder](adapty-flow-builder). | +| **Copy** | [Copies the flow](copy-flows) as a new Draft. | +| **Archive** | [Archives the flow](archive-flows). | + +The menu for an archived flow shows **Metrics** only. diff --git a/src/content/docs/guides/flow-builder/using-custom-fonts-in-flow-builder.mdx b/src/content/docs/guides/flow-builder/using-custom-fonts-in-flow-builder.mdx index f2d85e8e1d..756898bdb5 100644 --- a/src/content/docs/guides/flow-builder/using-custom-fonts-in-flow-builder.mdx +++ b/src/content/docs/guides/flow-builder/using-custom-fonts-in-flow-builder.mdx @@ -9,6 +9,8 @@ import 'react-medium-image-zoom/dist/styles.css'; When building flows, you might want to use a custom font to match the rest of your app. Here's how to add custom fonts and use them in your flows. +All flows in one app share the fonts you upload. That includes flows you [copy](copy-flows). Other apps in your Adapty account each need their own upload. + :::tip [Configure fonts](onboarding-text) in the **Styles** panel before you start designing the flow. This way, any changes you make will apply globally. ::: diff --git a/src/content/docs/version-3.0/flow-metrics.mdx b/src/content/docs/version-3.0/flow-metrics.mdx index 9e57b7de87..6f9731b373 100644 --- a/src/content/docs/version-3.0/flow-metrics.mdx +++ b/src/content/docs/version-3.0/flow-metrics.mdx @@ -13,7 +13,7 @@ Adapty collects a series of metrics to help you measure the performance of your Flow revenue is calculated from all transactions that occurred after the flow has been shown. ::: -Flow metrics are available on the flow list, providing an overview of the performance of all your flows. This consolidated view presents aggregated metrics for each flow, allowing you to compare their effectiveness and identify areas for improvement. +Flow metrics are available in the [flow list](filter-flows), which gives you an overview of all your flows' performance. This consolidated view presents aggregated metrics for each flow, allowing you to compare their effectiveness and identify areas for improvement. For a more granular analysis of each flow, navigate to the flow detail metrics. There you will find comprehensive metrics specific to the selected flow, offering deeper insight into its performance. diff --git a/src/content/docs/version-3.0/paywall-builder-templates.mdx b/src/content/docs/version-3.0/paywall-builder-templates.mdx index 5e85bf0454..ca8446a62e 100644 --- a/src/content/docs/version-3.0/paywall-builder-templates.mdx +++ b/src/content/docs/version-3.0/paywall-builder-templates.mdx @@ -13,7 +13,7 @@ You can create a flow from a template or from scratch. -1. Open the **Flows** page. +1. Open the [**Flows**](filter-flows) page. 2. Click **Create flow**. 3. Pick an option: - **Browse templates** (opens the Template library) @@ -65,4 +65,4 @@ Templates marked with a **Custom font** chip use custom fonts. These fonts don't To see the intended typography on device, add the font files to your app bundle. Older app versions that don't ship the font fall back to a system font. -To swap a font without affecting older versions, duplicate the flow, change the font in the copy, and restrict the copy to [users on app versions that include the font](segments). +To swap a font without affecting older versions, [copy the flow](copy-flows), change the font in the copy, and restrict the copy to [app versions that include the font](segments). diff --git a/src/content/docs/version-3.0/paywall-layout-and-products.mdx b/src/content/docs/version-3.0/paywall-layout-and-products.mdx index 056ae37068..7d63ed75b9 100644 --- a/src/content/docs/version-3.0/paywall-layout-and-products.mdx +++ b/src/content/docs/version-3.0/paywall-layout-and-products.mdx @@ -6,7 +6,6 @@ metadataTitle: Screens and layers | Flow Builder | Adapty Docs import ZoomImage from '@site/src/components/ZoomImage.astro'; - A flow consists of one or more screens. Each screen represents a single step in the user’s journey — for example, a paywall, a quiz, or a slide with product information. The elements on each screen are organized in a layer hierarchy. @@ -44,7 +43,7 @@ Click the three-dot icon on a screen e | **Delete** | ⌘⌫ / Ctrl+Del | Remove the screen from the flow | :::tip -The clipboard persists across flows. Copy a screen or element from one flow, open another, and paste it in. +The clipboard works for individual elements too, not just whole screens. It also crosses flows — see [Copy screens between flows](copy-flows#copy-screens-between-flows) for what a paste leaves behind. ::: When you copy or duplicate a screen, the actions in the copy point at the copied elements rather than at the originals. This applies to targets set in **Show**, **Hide**, and **Select product** actions, including those nested inside [conditional actions](onboarding-actions#conditional-actions). Actions that reference elements outside the copy keep their original targets — check them after pasting. @@ -152,7 +151,6 @@ Click a group entry to rename it, change its type, view the variables it exposes - ## Manage layers Each element on a screen is represented as a layer. The Layers section displays the order of the elements on the active screen. @@ -178,7 +176,6 @@ Click the three-dot icon to open the c - | Action | Shortcut | Description | |--------|----------|-------------| | **Copy** | ⌘C / Ctrl+C | Copy the layer to the clipboard | diff --git a/src/data/sidebars/tutorial.json b/src/data/sidebars/tutorial.json index c50592916c..0256802571 100644 --- a/src/data/sidebars/tutorial.json +++ b/src/data/sidebars/tutorial.json @@ -475,6 +475,21 @@ "id": "builder-save-publish", "label": "Save & publish flows" }, + { + "type": "doc", + "id": "filter-flows", + "label": "Find & filter flows" + }, + { + "type": "doc", + "id": "copy-flows", + "label": "Copy flows & screens" + }, + { + "type": "doc", + "id": "archive-flows", + "label": "Archive flows" + }, { "type": "doc", "id": "flow-metrics",