From 9ba1ca387cf6fd74747acf9baad5e3a270446d29 Mon Sep 17 00:00:00 2001 From: oluwadareab12 Date: Fri, 24 Jul 2026 00:18:35 +0100 Subject: [PATCH 1/2] docs: fix broken anchor links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Several internal links pointed to anchors that do not exist on their target pages, so they landed at the top of the page instead of the intended section. Most were headings that had since been renamed — for example "Handling ETH Balance Checks" became "Handling ETH (Native Token) Balance Checks", and "Rebalance liquidity" became "Rebalance pools". Point each link at the anchor that actually exists. Link text and surrounding prose are unchanged. --- src/pages/docs/guide/issuance/create-a-stablecoin.mdx | 2 +- src/pages/docs/guide/issuance/manage-stablecoin.mdx | 4 ++-- src/pages/docs/guide/issuance/use-for-fees.mdx | 2 +- src/pages/docs/guide/stablecoin-dex/providing-liquidity.mdx | 2 +- src/pages/docs/protocol/tip20/overview.mdx | 4 ++-- src/pages/docs/protocol/tips/tip-1007.mdx | 2 +- src/pages/docs/quickstart/connection-details.mdx | 2 +- src/pages/docs/quickstart/developer-tools.mdx | 2 +- src/pages/docs/quickstart/evm-compatibility.mdx | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/pages/docs/guide/issuance/create-a-stablecoin.mdx b/src/pages/docs/guide/issuance/create-a-stablecoin.mdx index 6b6eb36b..d13fd7ce 100644 --- a/src/pages/docs/guide/issuance/create-a-stablecoin.mdx +++ b/src/pages/docs/guide/issuance/create-a-stablecoin.mdx @@ -44,7 +44,7 @@ Ensure that you have set up your project with Wagmi, a Tempo chain config, and a Before we send off a transaction to deploy our stablecoin to the Tempo testnet, we need to make sure our account is funded with a stablecoin to cover the transaction fee. As we have configured our project to use `AlphaUSD` (`0x20c000…0001`) -as the [default fee token](/docs/quickstart/integrate-tempo#default-fee-token), we will need to add some `AlphaUSD` to our account. +as the [default fee token](/docs/quickstart/evm-compatibility#consideration-1-setting-a-user-default-fee-token), we will need to add some `AlphaUSD` to our account. Luckily, the built-in Tempo testnet faucet supports funding accounts with `AlphaUSD`. diff --git a/src/pages/docs/guide/issuance/manage-stablecoin.mdx b/src/pages/docs/guide/issuance/manage-stablecoin.mdx index e67042a6..9f91edd0 100644 --- a/src/pages/docs/guide/issuance/manage-stablecoin.mdx +++ b/src/pages/docs/guide/issuance/manage-stablecoin.mdx @@ -23,7 +23,7 @@ import { Cards, Card } from 'vocs' Configure your stablecoin's permissions, supply limits, and compliance policies after deployment. This guide covers granting roles to manage token operations, setting supply caps, configuring transfer policies, and controlling token transfers through pause/unpause functionality. -TIP-20 tokens use a role-based access control system that allows you to delegate different administrative functions to different addresses. For detailed information about the role system, see the [TIP-20 specification](/docs/protocol/tip20/spec#role-based-access-control). +TIP-20 tokens use a role-based access control system that allows you to delegate different administrative functions to different addresses. For detailed information about the role system, see the [TIP-20 specification](/docs/protocol/tip20/spec#tip-20-roles). ## Stablecoin management steps @@ -639,7 +639,7 @@ Ensure pause and unpause roles are assigned to trusted addresses and that your t diff --git a/src/pages/docs/guide/issuance/use-for-fees.mdx b/src/pages/docs/guide/issuance/use-for-fees.mdx index f09a289d..f13616ea 100644 --- a/src/pages/docs/guide/issuance/use-for-fees.mdx +++ b/src/pages/docs/guide/issuance/use-for-fees.mdx @@ -311,7 +311,7 @@ Regularly check your token's fee pool reserves to ensure users can consistently Keep sufficient validator token reserves in your fee pool to handle expected transaction volume. Consider your user base size and typical transaction frequency when determining reserve levels. -As fees accrue in your token, the pool will run low on validator tokens and need to be rebalanced. Use [`rebalanceSwap`](/docs/guide/stablecoin-dex/managing-fee-liquidity#rebalance-liquidity) to replenish validator token reserves when they become depleted. +As fees accrue in your token, the pool will run low on validator tokens and need to be rebalanced. Use [`rebalanceSwap`](/docs/guide/stablecoin-dex/managing-fee-liquidity#rebalance-pools) to replenish validator token reserves when they become depleted. ### Test before launch diff --git a/src/pages/docs/guide/stablecoin-dex/providing-liquidity.mdx b/src/pages/docs/guide/stablecoin-dex/providing-liquidity.mdx index 902f9ad5..9c03fad0 100644 --- a/src/pages/docs/guide/stablecoin-dex/providing-liquidity.mdx +++ b/src/pages/docs/guide/stablecoin-dex/providing-liquidity.mdx @@ -199,7 +199,7 @@ For more details on querying orders, see the [`Hooks.dex.useOrder`](https://wagm Cancel an order using its order ID. -When you cancel an order, any remaining funds are credited to your exchange balance (not directly to your wallet). To move funds back to your wallet, you can [withdraw them to your wallet](/docs/protocol/exchange/exchange-balance#withdrawing-funds). +When you cancel an order, any remaining funds are credited to your exchange balance (not directly to your wallet). To move funds back to your wallet, you can [withdraw them to your wallet](/docs/protocol/exchange/exchange-balance#withdrawing-from-the-dex). diff --git a/src/pages/docs/protocol/tip20/overview.mdx b/src/pages/docs/protocol/tip20/overview.mdx index d472ba1d..b1b60fc1 100644 --- a/src/pages/docs/protocol/tip20/overview.mdx +++ b/src/pages/docs/protocol/tip20/overview.mdx @@ -99,13 +99,13 @@ Tempo has dedicated payment lanes: reserved blockspace for payment TIP-20 transa ### Role-Based Access Control (RBAC) -TIP-20 includes a built-in [RBAC system](/docs/protocol/tip403/spec#tip-20-token-roles) that separates administrative responsibilities: +TIP-20 includes a built-in [RBAC system](/docs/protocol/tip20/spec#tip-20-roles) that separates administrative responsibilities: - **ISSUER_ROLE**: Grants permission to mint and burn tokens, enabling controlled token issuance - **PAUSE_ROLE** / **UNPAUSE_ROLE**: Allows pausing and unpausing token transfers for emergency controls - **BURN_BLOCKED_ROLE**: Permits burning tokens from blocked addresses (e.g., for compliance actions) -Roles can be granted, revoked, and delegated without custom contract changes. This enables issuers to separate operational roles (e.g., who can mint) from administrative roles (e.g., who can pause). Learn more in the [TIP-20 specification](/docs/protocol/tip20/spec#roles). +Roles can be granted, revoked, and delegated without custom contract changes. This enables issuers to separate operational roles (e.g., who can mint) from administrative roles (e.g., who can pause). Learn more in the [TIP-20 specification](/docs/protocol/tip20/spec#tip-20-roles). ### Tempo Policy Registry (TIP-403) diff --git a/src/pages/docs/protocol/tips/tip-1007.mdx b/src/pages/docs/protocol/tips/tip-1007.mdx index fc838204..3e7d2cd5 100644 --- a/src/pages/docs/protocol/tips/tip-1007.mdx +++ b/src/pages/docs/protocol/tips/tip-1007.mdx @@ -50,7 +50,7 @@ interface IFeeManager { ### Fee Token Resolution -The fee token returned by `getFeeToken()` is the same token that was resolved by the protocol during transaction validation, following the [fee token preference rules](/docs/protocol/fees/spec-fee#fee-token-resolution). +The fee token returned by `getFeeToken()` is the same token that was resolved by the protocol during transaction validation, following the [fee token preference rules](/docs/protocol/fees/spec-fee#fee-token-preferences). ### Storage diff --git a/src/pages/docs/quickstart/connection-details.mdx b/src/pages/docs/quickstart/connection-details.mdx index 1c733a8a..28795eb6 100644 --- a/src/pages/docs/quickstart/connection-details.mdx +++ b/src/pages/docs/quickstart/connection-details.mdx @@ -20,7 +20,7 @@ Click on your browser wallet below to automatically connect it to the Tempo netw :::warning -Note that on some wallets, you might see an unusually high "balance". This is because, historically, blockchain wallets have always assumed that a blockchain has a "native gas token". On Tempo, there is no native gas token, and so the value shown is a placeholder. See [EVM Differences](/docs/quickstart/evm-compatibility#handling-eth-balance-checks) for more information on this quirk. +Note that on some wallets, you might see an unusually high "balance". This is because, historically, blockchain wallets have always assumed that a blockchain has a "native gas token". On Tempo, there is no native gas token, and so the value shown is a placeholder. See [EVM Differences](/docs/quickstart/evm-compatibility#handling-eth-native-token-balance-checks) for more information on this quirk. ::: ## Connect via CLI diff --git a/src/pages/docs/quickstart/developer-tools.mdx b/src/pages/docs/quickstart/developer-tools.mdx index 85bcb9bc..ce4de6da 100644 --- a/src/pages/docs/quickstart/developer-tools.mdx +++ b/src/pages/docs/quickstart/developer-tools.mdx @@ -99,7 +99,7 @@ Get started with the [Squid docs](https://docs.squidrouter.com/) or try the [bri [TIDX](/docs/api/indexer-api) is Tempo's hosted indexer for querying blocks, transactions, logs, token balances, and decoded events through SQL. Use the public mainnet endpoint at `https://indexer.tempo.xyz` or the testnet endpoint at `https://indexer.testnet.tempo.xyz`. -The hosted indexer powers explorer-style reads and supports ClickHouse-backed analytical queries for expensive reads like holder lists and token activity. Try the [interactive TIDX example](/docs/api/indexer-api#interactive-tidx-sql-example) or read the [TIDX README](https://github.com/tempoxyz/tidx) to run your own indexer. +The hosted indexer powers explorer-style reads and supports ClickHouse-backed analytical queries for expensive reads like holder lists and token activity. Try the [interactive TIDX example](/docs/api/indexer-api#interactive-example) or read the [TIDX README](https://github.com/tempoxyz/tidx) to run your own indexer. ### Allium diff --git a/src/pages/docs/quickstart/evm-compatibility.mdx b/src/pages/docs/quickstart/evm-compatibility.mdx index 57b3e2be..197db37a 100644 --- a/src/pages/docs/quickstart/evm-compatibility.mdx +++ b/src/pages/docs/quickstart/evm-compatibility.mdx @@ -91,7 +91,7 @@ If the user is calling a contract that is not a TIP-20 token, the EVM transactio On the Tempo Testnet, pathUSD is available from the [faucet](/docs/quickstart/faucet). -If a wallet wants to submit a non-TIP20 transaction without having to submit the above transaction, we recommend investing in using [Tempo Transactions](/docs/quickstart/integrate-tempo#tempo-transactions) instead. +If a wallet wants to submit a non-TIP20 transaction without having to submit the above transaction, we recommend investing in using [Tempo Transactions](/docs/guide/tempo-transaction) instead. ## VM Layer Differences From 4959c9eaec3aaa504595dc46c3337183e5ca0bfd Mon Sep 17 00:00:00 2001 From: Brendan Ryan <1572504+brendanjryan@users.noreply.github.com> Date: Fri, 31 Jul 2026 08:54:03 -0700 Subject: [PATCH 2/2] ci: validate internal documentation anchors --- .github/workflows/verify.yml | 3 + package.json | 2 + pnpm-lock.yaml | 3 + scripts/check-internal-anchors.ts | 18 ++ src/lib/internal-anchor-links.test.ts | 94 ++++++++++ src/lib/internal-anchor-links.ts | 230 +++++++++++++++++++++++++ src/pages/docs/protocol/tip20/spec.mdx | 2 +- 7 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 scripts/check-internal-anchors.ts create mode 100644 src/lib/internal-anchor-links.test.ts create mode 100644 src/lib/internal-anchor-links.ts diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 05aad876..238bd747 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -48,6 +48,9 @@ jobs: - name: Check types run: pnpm run check:types + - name: Check internal anchors + run: pnpm run check:anchors + - name: Test run: pnpm run test diff --git a/package.json b/package.json index afd48299..34c2a97e 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "dev": "node --env-file-if-exists=.env --import=tsx node_modules/vite/bin/vite.js", "build": "node scripts/guard-vocs-config.mjs && pnpm run check:types && vite build && node --experimental-strip-types scripts/finalize-sitemap.ts", "check": "biome check --write --unsafe .", + "check:anchors": "node --experimental-strip-types scripts/check-internal-anchors.ts", "check:markdown": "node scripts/check-markdown-components.mjs", "check:types": "tsgo --project tsconfig.json --noEmit", "preview": "node dist/preview.js", @@ -80,6 +81,7 @@ "@typescript/native-preview": "7.0.0-dev.20260624.1", "@vitejs/plugin-react": "^6.0.3", "anser": "^2.3.5", + "github-slugger": "2.0.0", "remark-stringify": "^11.0.0", "tsx": "^4.22.4", "typescript": "^6.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b198539..78db63c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -181,6 +181,9 @@ importers: anser: specifier: ^2.3.5 version: 2.3.5 + github-slugger: + specifier: 2.0.0 + version: 2.0.0 node: specifier: runtime:24.12.0 version: runtime:24.12.0 diff --git a/scripts/check-internal-anchors.ts b/scripts/check-internal-anchors.ts new file mode 100644 index 00000000..6c7d09c8 --- /dev/null +++ b/scripts/check-internal-anchors.ts @@ -0,0 +1,18 @@ +import path from 'node:path' +import { checkInternalAnchors } from '../src/lib/internal-anchor-links.ts' + +const result = checkInternalAnchors(path.resolve('src/pages')) + +if (result.failures.length > 0) { + console.error('Broken internal anchor links:') + for (const failure of result.failures) { + console.error( + `- ${path.relative(process.cwd(), failure.file)}:${failure.line} ${failure.href} (missing #${failure.fragment} on ${failure.targetRoute})`, + ) + } + process.exit(1) +} + +console.log( + `Internal anchor check passed: ${result.linksChecked} links across ${result.pagesChecked} pages.`, +) diff --git a/src/lib/internal-anchor-links.test.ts b/src/lib/internal-anchor-links.test.ts new file mode 100644 index 00000000..ceaf3aba --- /dev/null +++ b/src/lib/internal-anchor-links.test.ts @@ -0,0 +1,94 @@ +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { afterEach, describe, expect, it } from 'vitest' +import { checkInternalAnchors } from './internal-anchor-links' + +const temporaryDirectories: string[] = [] + +afterEach(() => { + for (const directory of temporaryDirectories.splice(0)) { + fs.rmSync(directory, { recursive: true, force: true }) + } +}) + +function pages(files: Record): string { + const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'internal-anchor-links-')) + temporaryDirectories.push(directory) + for (const [file, content] of Object.entries(files)) { + const target = path.join(directory, file) + fs.mkdirSync(path.dirname(target), { recursive: true }) + fs.writeFileSync(target, content) + } + return directory +} + +describe('internal anchor links', () => { + it('accepts generated, duplicate, and JSX anchors', () => { + const directory = pages({ + 'docs/index.mdx': ` +[Generated](/docs/target#hello-world) +[Duplicate](/docs/target#repeat-1) + +`, + 'docs/target.mdx': ` +## Hello, world! +## Repeat +## Repeat + +`, + }) + + expect(checkInternalAnchors(directory)).toMatchObject({ linksChecked: 3, failures: [] }) + }) + + it('reports stale Markdown and JSX fragments with source locations', () => { + const directory = pages({ + 'docs/index.mdx': '[Old](/docs/target#old-heading)\n\n', + 'docs/target.mdx': '## New heading\n', + }) + + expect(checkInternalAnchors(directory).failures).toEqual([ + expect.objectContaining({ line: 1, href: '/docs/target#old-heading' }), + expect.objectContaining({ line: 2, href: '/docs/target#also-old' }), + ]) + }) + + it('expands imported MDX snippets at their render position', () => { + const directory = pages({ + 'docs/index.mdx': '[Imported heading](/docs/target#imported-heading)\n', + 'docs/target.mdx': ` +import Shared from '../snippets/shared.mdx' + +# Target + + +`, + 'snippets/shared.mdx': '### Imported heading\n', + }) + + expect(checkInternalAnchors(directory).failures).toEqual([]) + }) + + it('resolves same-page, relative, and developers-mounted links', () => { + const directory = pages({ + 'docs/guide/index.mdx': ` +# Guide +[Same](#guide) +[Relative](./target#target) +[Mounted](/developers/docs/guide/target#target) +`, + 'docs/guide/target.mdx': '# Target\n', + }) + + expect(checkInternalAnchors(directory)).toMatchObject({ linksChecked: 3, failures: [] }) + }) + + it('leaves missing pages to the Vocs dead-link check', () => { + const directory = pages({ + 'docs/index.mdx': '[Missing](/docs/not-a-page#heading)\n', + }) + + expect(checkInternalAnchors(directory)).toMatchObject({ linksChecked: 0, failures: [] }) + }) +}) diff --git a/src/lib/internal-anchor-links.ts b/src/lib/internal-anchor-links.ts new file mode 100644 index 00000000..55bef4f6 --- /dev/null +++ b/src/lib/internal-anchor-links.ts @@ -0,0 +1,230 @@ +import fs from 'node:fs' +import path from 'node:path' +import GithubSlugger from 'github-slugger' +import remarkMdx from 'remark-mdx' +import remarkParse from 'remark-parse' +import { unified } from 'unified' + +type AstNode = { + type: string + name?: string | null + value?: unknown + url?: string + alt?: string | null + attributes?: Array<{ type: string; name: string; value?: unknown }> + children?: AstNode[] + position?: { start: { line: number } } +} + +type SourceEvent = + | { type: 'heading'; file: string; line: number; text: string } + | { type: 'anchor'; file: string; line: number; id: string } + | { type: 'link'; file: string; line: number; href: string } + +type Page = { + file: string + route: string + routeDirectory: string +} + +export type BrokenInternalAnchor = { + file: string + line: number + href: string + targetRoute: string + fragment: string +} + +export type InternalAnchorCheck = { + pagesChecked: number + linksChecked: number + failures: BrokenInternalAnchor[] +} + +const markdownExtensions = new Set(['.md', '.mdx']) + +function markdownFiles(directory: string): string[] { + return fs + .readdirSync(directory, { withFileTypes: true }) + .flatMap((entry) => { + const file = path.join(directory, entry.name) + if (entry.isDirectory()) return markdownFiles(file) + return entry.isFile() && markdownExtensions.has(path.extname(file)) ? [file] : [] + }) + .sort() +} + +function pageFor(file: string, pagesDirectory: string): Page { + const relativeFile = path.relative(pagesDirectory, file).split(path.sep).join('/') + const withoutExtension = relativeFile.replace(/\.(?:md|mdx)$/, '') + const isIndex = withoutExtension === 'index' || withoutExtension.endsWith('/index') + const route = normalizeRoute(`/${withoutExtension.replace(/(?:^|\/)index$/, '')}`) + + return { + file, + route, + routeDirectory: isIndex ? route : path.posix.dirname(route), + } +} + +function normalizeRoute(route: string): string { + const withoutExtension = route.replace(/\.(?:md|mdx)$/, '') + const withoutIndex = withoutExtension.replace(/\/index$/, '') + if (withoutIndex === '/') return '/' + return withoutIndex.replace(/\/$/, '') +} + +function nodeText(node: AstNode): string { + if ((node.type === 'text' || node.type === 'inlineCode') && typeof node.value === 'string') { + return node.value + } + if (node.type === 'image' && typeof node.alt === 'string') return node.alt + return node.children?.map(nodeText).join('') ?? '' +} + +function staticAttribute(node: AstNode, names: Set): string | undefined { + const attribute = node.attributes?.find( + (candidate) => candidate.type === 'mdxJsxAttribute' && names.has(candidate.name), + ) + return typeof attribute?.value === 'string' ? attribute.value : undefined +} + +function importsFor(node: AstNode, file: string): Map { + const imports = new Map() + const source = typeof node.value === 'string' ? node.value : '' + const pattern = /import\s+([A-Za-z_$][\w$]*)\s+from\s+['"]([^'"]+\.(?:md|mdx))['"]/g + + for (const match of source.matchAll(pattern)) { + const [, localName, importPath] = match + if (!localName || !importPath) continue + imports.set(localName, path.resolve(path.dirname(file), importPath)) + } + return imports +} + +function parseFile(file: string): AstNode { + try { + return unified().use(remarkParse).use(remarkMdx).parse(fs.readFileSync(file, 'utf8')) as AstNode + } catch (error) { + throw new Error(`Unable to parse ${file}`, { cause: error }) + } +} + +function sourceEvents(file: string, stack: string[] = []): SourceEvent[] { + if (stack.includes(file)) { + throw new Error(`Circular MDX import: ${[...stack, file].join(' -> ')}`) + } + + const tree = parseFile(file) + const imports = new Map() + for (const child of tree.children ?? []) { + if (child.type !== 'mdxjsEsm') continue + for (const [localName, importFile] of importsFor(child, file)) { + imports.set(localName, importFile) + } + } + + const visit = (node: AstNode): SourceEvent[] => { + const line = node.position?.start.line ?? 1 + const events: SourceEvent[] = [] + + if (node.type === 'heading') { + events.push({ type: 'heading', file, line, text: nodeText(node) }) + } + + if (node.type === 'link' || node.type === 'definition') { + if (typeof node.url === 'string') { + events.push({ type: 'link', file, line, href: node.url }) + } + } + + if (node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement') { + const id = staticAttribute(node, new Set(['id'])) + if (id) events.push({ type: 'anchor', file, line, id }) + + const href = staticAttribute(node, new Set(['href', 'to'])) + if (href) events.push({ type: 'link', file, line, href }) + + const importedFile = node.name ? imports.get(node.name) : undefined + if (importedFile) { + if (!fs.existsSync(importedFile)) { + throw new Error(`${file}:${line} imports missing Markdown file ${importedFile}`) + } + events.push(...sourceEvents(importedFile, [...stack, file])) + } + } + + for (const child of node.children ?? []) events.push(...visit(child)) + return events + } + + return visit(tree) +} + +function headingAnchor(text: string, slugger: GithubSlugger): string { + return slugger.slug(text) +} + +function targetFor(href: string, page: Page): { route: string; fragment: string } | undefined { + if (/^(?:https?:|mailto:|tel:)/.test(href)) return + + const basePath = href.startsWith('#') ? page.route : `${page.routeDirectory}/` + const base = `https://tempo.local${basePath}` + const url = new URL(href, base) + if (!url.hash || url.origin !== 'https://tempo.local') return + + let route = url.pathname + if (route === '/developers/docs' || route.startsWith('/developers/docs/')) { + route = route.slice('/developers'.length) + } + route = normalizeRoute(route) + if (route !== '/docs' && !route.startsWith('/docs/')) return + + return { route, fragment: decodeURIComponent(url.hash.slice(1)) } +} + +export function checkInternalAnchors( + pagesDirectory = path.resolve('src/pages'), +): InternalAnchorCheck { + const pages = markdownFiles(pagesDirectory).map((file) => pageFor(file, pagesDirectory)) + const docsPages = pages.filter( + (page) => page.route === '/docs' || page.route.startsWith('/docs/'), + ) + const eventsByRoute = new Map(docsPages.map((page) => [page.route, sourceEvents(page.file)])) + const anchorsByRoute = new Map>() + + for (const page of docsPages) { + const slugger = new GithubSlugger() + const anchors = new Set() + for (const event of eventsByRoute.get(page.route) ?? []) { + if (event.type === 'heading') anchors.add(headingAnchor(event.text, slugger)) + if (event.type === 'anchor') anchors.add(event.id) + } + anchorsByRoute.set(page.route, anchors) + } + + const failures: BrokenInternalAnchor[] = [] + let linksChecked = 0 + for (const page of docsPages) { + for (const event of eventsByRoute.get(page.route) ?? []) { + if (event.type !== 'link') continue + const target = targetFor(event.href, page) + if (!target) continue + + const anchors = anchorsByRoute.get(target.route) + if (!anchors) continue // Vocs checks whether the target page exists. + + linksChecked += 1 + if (anchors.has(target.fragment)) continue + failures.push({ + file: event.file, + line: event.line, + href: event.href, + targetRoute: target.route, + fragment: target.fragment, + }) + } + } + + return { pagesChecked: docsPages.length, linksChecked, failures } +} diff --git a/src/pages/docs/protocol/tip20/spec.mdx b/src/pages/docs/protocol/tip20/spec.mdx index 1ce0862b..d1fe342b 100644 --- a/src/pages/docs/protocol/tip20/spec.mdx +++ b/src/pages/docs/protocol/tip20/spec.mdx @@ -534,7 +534,7 @@ When creating a token, the factory performs several checks to guarantee consiste `transferPolicyId = 1`, `supplyCap = type(uint128).max`, `paused = false`, and `totalSupply = 0`. - The provided `admin` address receives `DEFAULT_ADMIN_ROLE`, enabling it to manage roles and token configurations. -The factory provides two `createToken` overloads: the original 6-argument form, and a 7-argument form that additionally sets an initial [`logoURI`](#logo-uri-tip-1026) at creation (validated with the same rules as `setLogoURI`). The 6-argument overload is unchanged. +The factory provides two `createToken` overloads: the original 6-argument form, and a 7-argument form that additionally sets an initial [`logoURI`](#logo-uri) at creation (validated with the same rules as `setLogoURI`). The 6-argument overload is unchanged. The complete `TIP20Factory` interface is defined below: