diff --git a/.prettierignore b/.prettierignore index 32acf1b2e..304c757ee 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,3 +4,8 @@ **/*.md content/api-specs/ pnpm-lock.yaml + +# Generated by UpdateFeatureSupportFromDaikonWorkflow; the generator owns its +# one-entity-per-line format (prettier reflowing it would make every daily bot +# write a diff) +content/feature-support.json diff --git a/content/api-reference/pricing-resources/resources/feature-support-by-chain.mdx b/content/api-reference/pricing-resources/resources/feature-support-by-chain.mdx index a761592c3..167eb6121 100644 --- a/content/api-reference/pricing-resources/resources/feature-support-by-chain.mdx +++ b/content/api-reference/pricing-resources/resources/feature-support-by-chain.mdx @@ -5,8 +5,43 @@ subtitle: Alchemy's current feature availability for each of its supported chain slug: reference/feature-support-by-chain --- - - Check the [Chains](https://dashboard.alchemy.com/chains) page for details about product and chain support! +See the [Chains page](https://dashboard.alchemy.com/chains) in the dashboard for Alchemy's current product and chain support. - ![](https://alchemyapi-res.cloudinary.com/image/upload/v1764179964/docs/api-reference/alchemy-transact/transaction-simulation/523fb8a9a9d899921ee1046d0ff1b389967a9976d1c6112ebbbe071ddd1ef374-image.png) - +{/* + The visible sentence above is a placeholder so this nav-listed page is + never blank: the stacked follow-up replaces it with the interactive + widget when the generated data lands. + + Data freshness: the widget's data is server-rendered, not fetched by the + browser and not baked into this MDX. The docs-site renders + on the server from content/feature-support.json + in this repo, cached under the "feature-support" Next.js cache tag. When + that file changes on main — the daily Daikon bot PR merge — the + revalidate-content workflow calls the docs-site + /api/revalidate/feature-support endpoint to invalidate the tag, so the + statically generated page re-renders with fresh data (an hourly TTL + backstops a missed webhook). JSON-only daily updates therefore reach + readers without this MDX being edited. + + The summary table below cannot drift from the JSON: the daily workflow + regenerates both from the same resolved data and commits them together, + so any change that affects the table is itself an MDX edit and + revalidates through the markdown path. A JSON-only commit means the + table's coarser product-by-chain view genuinely did not change. + + The summary block is intentionally wrapped in a hidden div: it exists for + search engines, LLMs, and markdown consumers, while the interactive + widget is the human-facing view. The h4 keeps it + out of the page table of contents. +*/} + + diff --git a/src/content-indexer/utils/__tests__/truncate-record.test.ts b/src/content-indexer/utils/__tests__/truncate-record.test.ts index 4489f2feb..596c6564c 100644 --- a/src/content-indexer/utils/__tests__/truncate-record.test.ts +++ b/src/content-indexer/utils/__tests__/truncate-record.test.ts @@ -21,6 +21,54 @@ describe("truncateRecord", () => { expect(result.content).toBe("Short content"); }); + test("should strip non-rendered MDX comments from indexed content", () => { + const record: AlgoliaRecord = { + objectID: "abc123", + indexerType: "docs", + path: "reference/feature-support-by-chain", + pageType: "Guide", + title: "Feature Support By Chain", + content: [ + "Visible intro.", + "{/*\n Data freshness: operational note that never renders.\n*/}", + "{/* feature-support:auto */}", + "| Product | Chains |", + "{/* feature-support:auto end */}", + "Visible outro.", + ].join("\n\n"), + breadcrumbs: ["Reference"], + }; + + const result = truncateRecord(record); + expect(result.content).toContain("Visible intro."); + expect(result.content).toContain("Visible outro."); + expect(result.content).not.toContain("Data freshness"); + expect(result.content).not.toContain("feature-support:auto"); + }); + + test("should preserve JSX comments inside fenced code examples", () => { + const record: AlgoliaRecord = { + objectID: "abc123", + indexerType: "docs", + path: "wallets/react/mfa/email-otp", + pageType: "Guide", + title: "Email OTP", + content: [ + "{/* operational note that never renders */}", + "```tsx\nreturn (\n
\n {/* OTP input component */}\n \n
\n);\n```", + "After the example.", + ].join("\n\n"), + breadcrumbs: ["Wallets"], + }; + + const result = truncateRecord(record); + // The comment in the rendered code block is real page content... + expect(result.content).toContain("OTP input component"); + // ...while the non-rendered MDX comment outside it is stripped. + expect(result.content).not.toContain("operational note"); + expect(result.content).toContain("After the example."); + }); + test("should truncate content for oversized record", () => { // Create a record with content exceeding 100KB const largeContent = "x".repeat(150_000); diff --git a/src/content-indexer/utils/truncate-record.ts b/src/content-indexer/utils/truncate-record.ts index 3fa7d8a75..348adc376 100644 --- a/src/content-indexer/utils/truncate-record.ts +++ b/src/content-indexer/utils/truncate-record.ts @@ -6,13 +6,33 @@ const MAX_RECORD_BYTES = 100_000; // Algolia imposes a 100KB limit on each recor const BUFFER_BYTES = 1_000; const MAX_ITERATIONS = 5; +/** + * MDX expression comments never render on the page, but remove-markdown + * leaves them intact, so implementation notes and generator markers + * (cu:auto, feature-support:auto, data-freshness notes) would otherwise be + * uploaded into public search records and snippets. + * + * Fenced code blocks are skipped: a JSX comment inside a rendered code + * example (e.g. `{/* OTP input component *\/}` in the wallets MFA guides) + * is real page content that must stay searchable. + */ +const stripMdxComments = (content: string): string => + content + .split(/(```[\s\S]*?```)/) + .map((segment, index) => + // Odd indexes are the captured fenced code blocks. + index % 2 === 1 ? segment : segment.replace(/\{\/\*[\s\S]*?\*\/\}/g, ""), + ) + .join(""); + /** * Truncate record content to ensure entire JSON payload fits within Algolia limit. * Also strips markdown formatting from content for better search experience. */ export const truncateRecord = (rawRecord: AlgoliaRecord): AlgoliaRecord => { - // Strip markdown formatting to get clean, searchable text - const cleanedContent = removeMd(rawRecord.content); + // Strip non-rendered MDX comments and markdown formatting to get clean, + // searchable text + const cleanedContent = removeMd(stripMdxComments(rawRecord.content)); const record = { ...rawRecord, content: cleanedContent }; const fullRecordJson = JSON.stringify(record);