From ce8b1da27423c1a9dce50436b224146dc0211715 Mon Sep 17 00:00:00 2001 From: beck <34204218+beck-8@users.noreply.github.com> Date: Thu, 30 Jul 2026 14:05:19 +0800 Subject: [PATCH 01/11] fix: temporarily hide the Service Providers page (#347) * Temporarily hide the Service Providers page Redirect /service-providers to the homepage and remove its links from the header/mobile nav, the homepage product grid, and the warm storage service page. The route itself is left in place so this is easy to revert once the page is ready to come back. * Address review comment from @rjan90 Switch the homepage "Compose the Building Blocks" grid to the three-column variant now that only three cards remain, and add short comments marking the hidden /service-providers route and its redirect so it isn't mistaken for dead code. --- next.config.ts | 11 +++++++++++ .../data/filecoin-onchain-cloud-products.ts | 11 ----------- src/app/(homepage)/page.tsx | 2 +- src/app/service-providers/page.tsx | 2 ++ .../components/WarmStorageServicesClient.tsx | 7 ------- src/components/Navigation/constants/navigation.ts | 8 -------- 6 files changed, 14 insertions(+), 27 deletions(-) diff --git a/next.config.ts b/next.config.ts index d7543fb6..0416fa00 100644 --- a/next.config.ts +++ b/next.config.ts @@ -19,6 +19,17 @@ const nextConfig: NextConfig = { images: { qualities: [75, 100], }, + async redirects() { + return [ + // Page temporarily hidden; route code kept at src/app/service-providers. + // Remove this entry to restore it. + { + source: '/service-providers', + destination: '/', + permanent: false, + }, + ] + }, webpack: (config) => { config.module.rules.push(svgrRule) config.module.rules.push(markdownRule) diff --git a/src/app/(homepage)/data/filecoin-onchain-cloud-products.ts b/src/app/(homepage)/data/filecoin-onchain-cloud-products.ts index 13aa1a3e..39dd5a93 100644 --- a/src/app/(homepage)/data/filecoin-onchain-cloud-products.ts +++ b/src/app/(homepage)/data/filecoin-onchain-cloud-products.ts @@ -4,7 +4,6 @@ import { PATHS } from '@/constants/paths' import { FIL_BEAM_URL, FOC_URLS } from '@/constants/site-metadata' import aurora from '@/public/assets/aurora.webp' import spaceStation from '@/public/assets/space-station.webp' -import spiralGalaxy from '@/public/assets/spiral-galaxy.webp' import stellarExplosionNebula from '@/public/assets/stellar-explosion-nebula.webp' const CTA_TEXT = 'Learn more' @@ -49,14 +48,4 @@ export const filecoinOnchainCloudProducts: Array = [ alt: "View of colorful aurora over Earth's atmosphere seen from space with part of a spacecraft arm visible.", }, }, - { - title: 'Service Providers', - description: - 'A global network of operators running Filecoin Onchain Cloud nodes delivering resilient, high-availability storage infrastructure.', - cta: { href: PATHS.SERVICE_PROVIDERS.path, text: CTA_TEXT }, - image: { - data: spiralGalaxy, - alt: 'Bright spiral galaxy with glowing core and extended spiral arms filled with stars.', - }, - }, ] as const satisfies Array diff --git a/src/app/(homepage)/page.tsx b/src/app/(homepage)/page.tsx index 21ce3f19..0306dfdf 100644 --- a/src/app/(homepage)/page.tsx +++ b/src/app/(homepage)/page.tsx @@ -143,7 +143,7 @@ export default function Homepage() { title="Compose the Building Blocks" description="Modular services you can mix, match, and deploy, all built for openness, performance, and control." > - + {filecoinOnchainCloudProducts.map( ({ title, description, cta, image }) => ( diff --git a/src/app/warm-storage-service/components/WarmStorageServicesClient.tsx b/src/app/warm-storage-service/components/WarmStorageServicesClient.tsx index c80f35c5..40a96704 100644 --- a/src/app/warm-storage-service/components/WarmStorageServicesClient.tsx +++ b/src/app/warm-storage-service/components/WarmStorageServicesClient.tsx @@ -16,7 +16,6 @@ import { Navigation } from '@/components/Navigation/Navigation' import { ProvidersEmptyLoadingState } from '@/components/ProvidersEmptyLoadingState' import { ProvidersLoadingError } from '@/components/ProvidersLoadingError' -import { PATHS } from '@/constants/paths' import { FIL_BEAM_URL, FOC_URLS } from '@/constants/site-metadata' import { getNetworkId } from '@/utils/get-network-id' @@ -128,16 +127,10 @@ export function WarmStorageServicesClient({ - View all service providers - - } >
diff --git a/src/components/Navigation/constants/navigation.ts b/src/components/Navigation/constants/navigation.ts index e7d77295..3087969d 100644 --- a/src/components/Navigation/constants/navigation.ts +++ b/src/components/Navigation/constants/navigation.ts @@ -11,10 +11,6 @@ export const headerNavigationItems: Array = [ label: PATHS.WARM_STORAGE_SERVICE.label, href: PATHS.WARM_STORAGE_SERVICE.path, }, - { - label: PATHS.SERVICE_PROVIDERS.label, - href: PATHS.SERVICE_PROVIDERS.path, - }, { label: 'Resources', items: [ @@ -63,10 +59,6 @@ export const mobileNavigationItems: Array = [ label: PATHS.WARM_STORAGE_SERVICE.label, href: PATHS.WARM_STORAGE_SERVICE.path, }, - { - label: PATHS.SERVICE_PROVIDERS.label, - href: PATHS.SERVICE_PROVIDERS.path, - }, { label: PATHS.SHOWCASE.label, href: PATHS.SHOWCASE.path, From d20c39a161aec05c73e11dc5842dd191b1cbce36 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 09:41:55 +0200 Subject: [PATCH 02/11] chore: update @filoz/synapse-sdk to v1.1.1 (#348) Co-authored-by: github-actions[bot] --- package-lock.json | 70 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6b7a4a59..68fbadd7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@filecoin-foundation/ui-filecoin": "^0.9.0", - "@filoz/synapse-sdk": "^1.0.1", + "@filoz/synapse-sdk": "^1.1.1", "@headlessui/react": "^2.2.9", "@phosphor-icons/react": "^2.1.10", "@radix-ui/react-accordion": "^1.2.12", @@ -2259,9 +2259,9 @@ } }, "node_modules/@filoz/synapse-core": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@filoz/synapse-core/-/synapse-core-0.7.0.tgz", - "integrity": "sha512-54G+yH1DrqXpCrtSZF5SYvNma3W2iA/LH6hLWmADy5tW1CkPlvtHZ5essqrJtlk3lfNRYMWk3+13ejzXxDAWWw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@filoz/synapse-core/-/synapse-core-0.7.1.tgz", + "integrity": "sha512-4vZZJbumv64ZKR+BjfhLJvP57ga/GhSHy/k2jIWuXInXGwM5qPmKvaCS7CkpzQwiSIXahO161DBNRLVH8SrVfQ==", "license": "Apache-2.0 OR MIT", "dependencies": { "dnum": "^2.15.0", @@ -2312,9 +2312,9 @@ } }, "node_modules/@filoz/synapse-core/node_modules/ox": { - "version": "0.14.29", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.29.tgz", - "integrity": "sha512-M5j87Ec4V99MQdRct/g09eWXW60g6zhHTUs1lr4deUtrPDnezBdCJTgKd7pxqTpSZBFveV0ALi9jMMuT1qKyNg==", + "version": "0.14.33", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.33.tgz", + "integrity": "sha512-rooA/4o7bBof4Ge2VH/eovfNPb/AEEYyrNj03wggc55g5HZD8Pjs/OeWhttgjic3dDcqn0r29bDuvQEdTiUemQ==", "funding": [ { "type": "github", @@ -2342,12 +2342,12 @@ } }, "node_modules/@filoz/synapse-sdk": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@filoz/synapse-sdk/-/synapse-sdk-1.0.1.tgz", - "integrity": "sha512-zUAFPVPit9CNcOfjzv4oH+zqj0FkyVHJkXbeWpuJo4ZQxlCTieILpgEn7zR5LFnGKBpukzM0iIzW1mOEqGZ6jA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@filoz/synapse-sdk/-/synapse-sdk-1.1.1.tgz", + "integrity": "sha512-m+DqCbjTID2VDBGa2iFQqmF7xjTVoANQjsdk54ikNSolZDLAvZK3TtoArlp0CKp9/L67bAw5ptm4SLJJPE+Rmw==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@filoz/synapse-core": "^0.7.0", + "@filoz/synapse-core": "^0.7.1", "multiformats": "^14.0.0" }, "peerDependencies": { @@ -5013,9 +5013,9 @@ } }, "node_modules/dot-prop": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-10.1.0.tgz", - "integrity": "sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-10.2.0.tgz", + "integrity": "sha512-BTJ9aZYL3vCfZlZOBLy9v8TUqWGQ0pzFnygKwFZt5udj6viBoFIBviKPUoZLDCPn1FoXffv6McQFDenrm5Krfw==", "license": "MIT", "dependencies": { "type-fest": "^5.0.0" @@ -5280,9 +5280,9 @@ } }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", "funding": [ { "type": "github", @@ -5643,9 +5643,9 @@ } }, "node_modules/idb-keyval": { - "version": "6.2.5", - "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.5.tgz", - "integrity": "sha512-eKQkTnS0relYsSOYomx8ozIbmdsQCKUdhyuIaQ2DZgKuaxtyQQMkyD/wlnQN32pO3yutN1b1L8uqwcDKaJd7/Q==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.3.0.tgz", + "integrity": "sha512-um+2dgAWmYsu615EXpWVwSmapJhON0G43t3Ka/EVaohzPQXSMqKEqeDK/oIW3Ow+BXaF2PvSc+oBTFp793A5Ow==", "license": "Apache-2.0" }, "node_modules/import-fresh": { @@ -5903,9 +5903,9 @@ } }, "node_modules/kysely": { - "version": "0.29.2", - "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.29.2.tgz", - "integrity": "sha512-s6WVJyEZrbm6jhBpiKHsGHyePMrVQKJ85wZCFCr9W4QHv6WTjWIrdvTmO9hDEA3bNK0xkrE2DqrHsXMLWuZpQg==", + "version": "0.29.4", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.29.4.tgz", + "integrity": "sha512-y5mVgQNkMbs1eK9Xyc0pmNdabN2wHhRYY/5r4W5HrUT1rYCEPeVNSj1RUJeSDKT3U0p+mXCvLgkrFuIafYI6BA==", "license": "MIT", "engines": { "node": ">=22.0.0" @@ -7089,9 +7089,9 @@ "license": "MIT" }, "node_modules/multiformats": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.0.tgz", - "integrity": "sha512-iWK1RrAS58p2NDfeZFuSUSv3ZPewTIhsGbh/5NgeGGJwJmRljLxGtjRR3nkn+loG3zl+IrfR/W1590QnrSK+Gg==", + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", + "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", "license": "Apache-2.0 OR MIT" }, "node_modules/nanoid": { @@ -7350,9 +7350,9 @@ } }, "node_modules/p-limit": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.0.tgz", - "integrity": "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.1.tgz", + "integrity": "sha512-0trZaiG7Y7kN/Egy9a8j47t9osC0Tch4PaIWd9yGF6bvmlk7muExRvGNYb8sXBwEKMoNKsbNN9P8EefuQekE4Q==", "license": "MIT", "dependencies": { "yocto-queue": "^1.2.1" @@ -7380,9 +7380,9 @@ } }, "node_modules/p-queue": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.0.tgz", - "integrity": "sha512-7NED7xhQ74Ngp4JP/2e0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P03YoTKa9dDXyZ3r6P616gUXttrB6nnHYKang==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.3.tgz", + "integrity": "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==", "license": "MIT", "dependencies": { "eventemitter3": "^5.0.4", @@ -8315,9 +8315,9 @@ "license": "MIT" }, "node_modules/type-fest": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.7.0.tgz", - "integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.8.0.tgz", + "integrity": "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==", "license": "(MIT OR CC0-1.0)", "dependencies": { "tagged-tag": "^1.0.0" diff --git a/package.json b/package.json index 09ca7352..fb50a5c3 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@filecoin-foundation/ui-filecoin": "^0.9.0", - "@filoz/synapse-sdk": "^1.0.1", + "@filoz/synapse-sdk": "^1.1.1", "@headlessui/react": "^2.2.9", "@phosphor-icons/react": "^2.1.10", "@radix-ui/react-accordion": "^1.2.12", From bec64c4309ddcbd66d0f2e308d0c623db9121ece Mon Sep 17 00:00:00 2001 From: Steve Loeppky Date: Sat, 1 Aug 2026 00:55:58 -0700 Subject: [PATCH 03/11] chore: set CODEOWNERS (#349) --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..845cc6e1 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# Codeowners per the FOC Engineering Workstreams proposal. +# https://app.notion.com/p/filecoindev/FOC-Engineering-Workstreams-342dc41950c1818f99d2e857a7f3cb2e +* @TippyFlitsUK @rjan90 From 58ee008e643cf8b827fa80f86507f5bbbae443ca Mon Sep 17 00:00:00 2001 From: G Date: Fri, 31 Jul 2026 18:26:43 +0100 Subject: [PATCH 04/11] fix(faq): make accordion dividers and chevrons background-aware Faq hardcoded divide-white/20, and Accordion hardcoded text-zinc-200 on the chevron and text-zinc-300 on the body. All three assume a dark background, which held while the homepage was the only consumer since it mounts the FAQ on a dark section. On a light or gray section the dividers and chevrons are effectively invisible. They now use --color-border-base, --color-text-base and --color-paragraph-text, which flip off the .light-section / .dark-section class that Section already sets, so no isDark branching is needed. Faq keeps useBackground purely for prose-invert. Moving the colour onto the item rather than the parent also fixes the first divider. divide-* targets every child after the first, so item one's border was falling back to currentColor and rendering at full brightness on dark. The homepage FAQ shifts slightly as a result: chevron zinc-200 to zinc-50, body zinc-300 to zinc-400, both now matching the tokens used everywhere else. That page is worth a look alongside this one. Review: @filipagr Co-Authored-By: Claude Opus 5 (1M context) --- src/components/Accordion.tsx | 6 +++--- src/components/Faq.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Accordion.tsx b/src/components/Accordion.tsx index 43cabcff..731fccf1 100644 --- a/src/components/Accordion.tsx +++ b/src/components/Accordion.tsx @@ -22,7 +22,7 @@ function AccordionItem({ {children} - + @@ -62,7 +62,7 @@ function AccordionContent({ return (
{children}
diff --git a/src/components/Faq.tsx b/src/components/Faq.tsx index 1e08609a..d91ca49a 100644 --- a/src/components/Faq.tsx +++ b/src/components/Faq.tsx @@ -21,7 +21,7 @@ export function Faq({ questions }: FaqProps) { return (
- + {questions.map(({ question, answer }) => { return ( From 8b19c3c1076347f6913b47351687fe394f8e3610 Mon Sep 17 00:00:00 2001 From: G Date: Fri, 31 Jul 2026 18:35:34 +0100 Subject: [PATCH 05/11] fix(ipfs2filecoin): correct layout and type hierarchy from design review Follow-ups from a walkthrough of the page with Filipa Ribeiro. Alignment and spacing - "Estimate your deposit" and the hero sat 60px inset from their siblings, because PageSection already wraps children in a Container and page.tsx nested a second one, doubling the horizontal padding. Removed both, plus the now-unused import. - The Unit and Funded-for selects had the native arrow jammed against the border with a cavern between it and the value. A local SelectField goes appearance-none with a Phosphor caret inset to match the field's text, and the grid drops from three equal columns to 2fr/1fr/2fr so Unit is no longer a full-width field holding three characters. - "What this does not do" is constrained to max-w-5xl; lines ran past 15 words at desktop. Type - Both tables move off text-sm to text-base, with service names at text-lg. - Monospace figures dropped for the body font with tabular-nums. The reported problem was numbers appearing larger than the names beside them, which was a typeface illusion rather than a size difference, so matching the typeface removes it at the root while tabular-nums keeps the columns aligned. Flagging this one as the most debatable call in the batch. - The highlighted Filecoin row carries weight instead of a marginally darker grey, which read as a mistake rather than emphasis. Visual interest - How it works renders 2x2 rather than 3-up with an orphaned fourth, and step numbers sit above each title in the accent colour. This needs a new StepList component because the shared Card types title as a plain string, so the number could only ever be inlined as "01. Hand over the list". - Reason cards take icons, following how agents/ and warm-storage-service/ already pass Phosphor icons to Card. - The agent column takes two thirds and "Talk to us" becomes a one third call-out, giving the path with more to say more room. The external-link arrow beside Filebase and Pinata is tightened with a local override. It is sized for button text inside ExternalTextLink in ui-filecoin, so the real fix belongs upstream; this is a commented stopgap. Still open from the review and deliberately not attempted here: table whitespace now that the type sizes are settled, the hero background treatment, and the optional illustration beside the agent heading. Review: @filipagr Co-Authored-By: Claude Opus 5 (1M context) --- .../components/ComparisonTable.tsx | 18 ++- .../components/CostEstimator.tsx | 122 +++++++++++------- src/app/ipfs2filecoin/components/StepList.tsx | 34 +++++ src/app/ipfs2filecoin/data/reasons.ts | 11 ++ src/app/ipfs2filecoin/page.tsx | 76 +++++------ 5 files changed, 169 insertions(+), 92 deletions(-) create mode 100644 src/app/ipfs2filecoin/components/StepList.tsx diff --git a/src/app/ipfs2filecoin/components/ComparisonTable.tsx b/src/app/ipfs2filecoin/components/ComparisonTable.tsx index 9b7a73b7..1acf0eed 100644 --- a/src/app/ipfs2filecoin/components/ComparisonTable.tsx +++ b/src/app/ipfs2filecoin/components/ComparisonTable.tsx @@ -5,9 +5,9 @@ import { pricingComparison } from '../data/pricing-comparison' export function ComparisonTable() { return (
- +
- + @@ -26,10 +26,15 @@ export function ComparisonTable() { key={service} className="border-(--color-border-muted) border-b last:border-b-0" > + {/* + Service names carry the same typeface as the figures on purpose. + They were previously monospace, which read as a size jump and + made the names look smaller than the numbers beside them. + */} - diff --git a/src/app/ipfs2filecoin/components/CostEstimator.tsx b/src/app/ipfs2filecoin/components/CostEstimator.tsx index f8476e95..39da69ad 100644 --- a/src/app/ipfs2filecoin/components/CostEstimator.tsx +++ b/src/app/ipfs2filecoin/components/CostEstimator.tsx @@ -1,9 +1,12 @@ 'use client' import { Button } from '@filecoin-foundation/ui-filecoin/Button' +import { Icon } from '@filecoin-foundation/ui-filecoin/Icon' import { Field, Input, Label, Select } from '@headlessui/react' +import { CaretDownIcon } from '@phosphor-icons/react/dist/ssr' +import { clsx } from 'clsx' import { usePlausible } from 'next-plausible' -import { useId, useState } from 'react' +import { type ChangeEvent, type ReactNode, useId, useState } from 'react' import { BUFFER_DAYS, @@ -29,10 +32,11 @@ const DURATION_OPTIONS = [ const fieldClassName = 'focus:brand-outline block w-full rounded-lg border border-(--input-border-color) p-3 text-(--color-text-base) placeholder:text-(--input-placeholder-color)' +const labelClassName = + 'mb-1 inline-block font-medium text-(--color-text-base) text-sm' + export function CostEstimator() { const volumeId = useId() - const unitId = useId() - const durationId = useId() const plausible = usePlausible() const [volume, setVolume] = useState('1') @@ -67,12 +71,9 @@ export function CostEstimator() { return (
-
+
- - - - - + setUnit(event.target.value as VolumeUnit)} + > + + + +

+ Total across everything you are storing, not per CID. 1 TiB is 1,024 + GiB. +

- - - - + setDays(Number(event.target.value))} + > + {DURATION_OPTIONS.map(({ label, days: value }) => ( + + ))} +
Where the data lives @@ -37,13 +42,14 @@ export function ComparisonTable() { {storagePerTbMonth} + {egress}
+
- + @@ -198,13 +222,13 @@ function EstimateBreakdown({ estimate }: { estimate: CostEstimate }) { {rows.map(({ label, amount, note }) => ( - + ))} -
Line
{label}{amount}{amount} {note}
Deposit + {formatUsd(estimate.deposit)} diff --git a/src/app/ipfs2filecoin/components/StepList.tsx b/src/app/ipfs2filecoin/components/StepList.tsx new file mode 100644 index 00000000..ba4308b2 --- /dev/null +++ b/src/app/ipfs2filecoin/components/StepList.tsx @@ -0,0 +1,34 @@ +import { Heading } from '@filecoin-foundation/ui-filecoin/Heading' + +import type { steps } from '../data/steps' + +type StepListProps = { + steps: typeof steps +} + +/** + * Not the shared `Card`: its `title` is a plain string, so the step number could + * only ever be inlined as "01. Hand over the list". Lifting the number above the + * title and giving it the accent colour is what makes the section read as a + * sequence rather than four unrelated cards. + */ +export function StepList({ steps }: StepListProps) { + return ( +
    + {steps.map(({ number, title, description }) => ( +
  1. + + + {title} + +

    {description}

    +
  2. + ))} +
+ ) +} diff --git a/src/app/ipfs2filecoin/data/reasons.ts b/src/app/ipfs2filecoin/data/reasons.ts index 8eefd413..d78810fa 100644 --- a/src/app/ipfs2filecoin/data/reasons.ts +++ b/src/app/ipfs2filecoin/data/reasons.ts @@ -1,22 +1,33 @@ +import { + CurrencyDollarIcon, + FingerprintIcon, + SealCheckIcon, + WalletIcon, +} from '@phosphor-icons/react/dist/ssr' + export const reasons = [ { title: 'Your CIDs do not change', description: 'Nothing is re-chunked. Every CID stays byte-identical and keeps resolving from any public IPFS gateway, so existing links, IPNS names, and NFT metadata keep working exactly as they do today.', + icon: FingerprintIcon, }, { title: 'A fraction of pinning-service pricing', description: '$2.50 per TiB per month per copy, two copies by default. You pay storage providers directly, streamed per epoch, with no plan tiers and no minimum commitment.', + icon: CurrencyDollarIcon, }, { title: 'Proof, not a dashboard', description: 'Storage providers prove possession onchain on a schedule. You get a receipt with transaction links, and you can verify any piece yourself at any time without asking anyone.', + icon: SealCheckIcon, }, { title: 'Your wallet owns it, not an account', description: 'No signup and no vendor account to lose. The data set belongs to your wallet address and payment streams from your wallet straight to the providers. Reading your data never depends on your key, so the content stays reachable either way.', + icon: WalletIcon, }, ] as const diff --git a/src/app/ipfs2filecoin/page.tsx b/src/app/ipfs2filecoin/page.tsx index 439d7521..da5b9d57 100644 --- a/src/app/ipfs2filecoin/page.tsx +++ b/src/app/ipfs2filecoin/page.tsx @@ -1,7 +1,6 @@ import { Button } from '@filecoin-foundation/ui-filecoin/Button' import { Card } from '@filecoin-foundation/ui-filecoin/Card' import { CardGrid } from '@filecoin-foundation/ui-filecoin/CardGrid' -import { Container } from '@filecoin-foundation/ui-filecoin/Container' import { Heading } from '@filecoin-foundation/ui-filecoin/Heading' import { PageHeader } from '@filecoin-foundation/ui-filecoin/PageHeader' import { PageSection } from '@filecoin-foundation/ui-filecoin/PageSection' @@ -22,6 +21,7 @@ import { AgentPrompt } from './components/AgentPrompt' import { CidListChecker } from './components/CidListChecker' import { ComparisonTable } from './components/ComparisonTable' import { CostEstimator } from './components/CostEstimator' +import { StepList } from './components/StepList' import { COORDINATION_VOLUME_LABEL, RUNBOOK_PATH } from './constants/migration' import { IPFS2FILECOIN_SEO } from './constants/seo' import { faqs } from './data/faqs' @@ -33,6 +33,14 @@ import { generateStructuredData } from './utils/generate-structured-data' const ghostOnDark = '!border-zinc-50/40 !bg-transparent hover:!border-zinc-50 hover:!bg-zinc-50/5' +/** + * `ExternalTextLink` sizes its arrow for button-style links: a fixed 16px glyph + * set `ml-1` off the label. Inside a 14px paragraph that reads as oversized and + * detached, so pull it in and scale it to the text. Fix belongs upstream in + * ui-filecoin; this keeps the footnote legible until then. + */ +const inlineExternalLink = '[&>span]:ml-0.5 [&_svg]:size-3.5' + export default function IpfsToFilecoin() { return ( <> @@ -49,11 +57,9 @@ export default function IpfsToFilecoin() { title="Move your pinned IPFS data to Filecoin" description="Same CIDs, a fraction of what pinning services charge, and an onchain receipt you can verify yourself." /> - -
- -
-
+
+ +
@@ -63,12 +69,13 @@ export default function IpfsToFilecoin() { description="Your data keeps working the way it does today. What changes is the price, the proof, and who owns it." > - {reasons.map(({ title, description }) => ( + {reasons.map(({ title, description, icon }) => ( ))} @@ -88,11 +95,17 @@ export default function IpfsToFilecoin() { Filecoin Warm Storage is $2.50 per TiB per month per copy at two copies. Other rates are published list overage rates as of 27 July 2026, from{' '} - + Filebase {' '} and{' '} - + Pinata . Filecoin includes two replicas; the other services do not @@ -103,20 +116,18 @@ export default function IpfsToFilecoin() { - -
- - Estimate your deposit - -

- Storage is charged continuously while your data sits there, so you - deposit for a period rather than paying a monthly bill. Enter - roughly how much you are holding and how long you want to be - covered. This runs in your browser and nothing is sent anywhere. -

- -
-
+
+ + Estimate your deposit + +

+ Storage is charged continuously while your data sits there, so you + deposit for a period rather than paying a monthly bill. Enter + roughly how much you are holding and how long you want to be + covered. This runs in your browser and nothing is sent anywhere. +

+ +
@@ -125,16 +136,7 @@ export default function IpfsToFilecoin() { title="How it works" description="Four steps, and the first two are free." > - - {steps.map(({ number, title, description }) => ( - - ))} - + @@ -144,8 +146,8 @@ export default function IpfsToFilecoin() { title="Nobody should migrate an archive by clicking" description="Real migrations run unattended, so that is the path this is built around. Check a list for free, get your account funded in one pass, then hand the work to your agent or to us." > -
-
+
+
Ask your agent to do it @@ -167,7 +169,7 @@ export default function IpfsToFilecoin() {
-
+
Talk to us @@ -195,7 +197,7 @@ export default function IpfsToFilecoin() { title="What this does not do" description="The things worth knowing up front, so nothing surprises you halfway through a run." > -
+
{limits.map(({ title, description }) => (
Date: Fri, 31 Jul 2026 18:35:48 +0100 Subject: [PATCH 06/11] feat(ipfs2filecoin): turn a checked CID list into something actionable parseCidList already validated, deduped and stripped gateway prefixes, but the verdict box reported only a count. The successful path gave you nothing to act on, so the top of the page had no purpose: you still assembled cids.txt by hand. The prompt was in fact offered only on the over-cap branch, so the one outcome that succeeded was the one outcome with no output. A checked list now produces the agent prompt with your own CIDs inlined, via buildAgentPrompt, plus a cids.txt download of the cleaned and deduped list. AGENT_PROMPT stays as the fallback for the empty and over-cap cases, where pointing at a file is the right shape rather than inlining hundreds of lines. The instruction to "estimate what they cost to store below" is replaced. A check reads the CIDs and not the bytes behind them, so it cannot price anything on its own, and implying otherwise was the most confusing part of the flow. Resolving a CID to its actual size needs backend capability and is a separate conversation. Hero polish in the same component: "one per line" sits beside the label in the accent colour rather than surfacing only after a failed check, the button is centred, the empty textarea is shorter, and the free-check facts read as a scannable list instead of fine print. Copy prompt demotes from a full-width button to an icon in the code block's corner. Verified against deliberately messy input: a bare CID, a gateway URL, a duplicate and a junk line resolve to three unique CIDs, with the URL prefix stripped and both the skipped line and the duplicate reported. Review: @filipagr Co-Authored-By: Claude Opus 5 (1M context) --- .../ipfs2filecoin/components/AgentPrompt.tsx | 38 +++++++--- .../components/CidListChecker.tsx | 72 ++++++++++++++----- src/app/ipfs2filecoin/constants/migration.ts | 14 ++++ 3 files changed, 97 insertions(+), 27 deletions(-) diff --git a/src/app/ipfs2filecoin/components/AgentPrompt.tsx b/src/app/ipfs2filecoin/components/AgentPrompt.tsx index 1569d9f6..c83bd1a9 100644 --- a/src/app/ipfs2filecoin/components/AgentPrompt.tsx +++ b/src/app/ipfs2filecoin/components/AgentPrompt.tsx @@ -1,38 +1,54 @@ 'use client' -import { Button } from '@filecoin-foundation/ui-filecoin/Button' +import { Icon } from '@filecoin-foundation/ui-filecoin/Icon' +import { CheckIcon, CopyIcon } from '@phosphor-icons/react/dist/ssr' import { usePlausible } from 'next-plausible' import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard' -import { AGENT_PROMPT, PLAUSIBLE_EVENTS } from '../constants/migration' +import { buildAgentPrompt, PLAUSIBLE_EVENTS } from '../constants/migration' type AgentPromptProps = { /** Where on the page the prompt was copied from, so the two spots stay distinguishable. */ source: 'verdict' | 'agent-door' + /** + * The user's checked list. When present it is inlined into the prompt so the + * copied line carries the actual CIDs instead of pointing at a cids.txt the + * user would have to assemble themselves. + */ + cids?: ReadonlyArray } -export function AgentPrompt({ source }: AgentPromptProps) { +export function AgentPrompt({ source, cids }: AgentPromptProps) { const { copy, isCopied } = useCopyToClipboard() const plausible = usePlausible() + const prompt = buildAgentPrompt(cids) + async function handleCopy() { - const copied = await copy(AGENT_PROMPT) + const copied = await copy(prompt) if (copied) { - plausible(PLAUSIBLE_EVENTS.promptCopied, { props: { source } }) + plausible(PLAUSIBLE_EVENTS.promptCopied, { + props: { source, cidCount: cids?.length ?? 0 }, + }) } } return ( -
- - {AGENT_PROMPT} +
+ + {prompt} - + {isCopied ? 'Prompt copied to clipboard' : ''} diff --git a/src/app/ipfs2filecoin/components/CidListChecker.tsx b/src/app/ipfs2filecoin/components/CidListChecker.tsx index 14e25136..487daaa3 100644 --- a/src/app/ipfs2filecoin/components/CidListChecker.tsx +++ b/src/app/ipfs2filecoin/components/CidListChecker.tsx @@ -22,6 +22,15 @@ const PLACEHOLDER = [ 'QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG', ].join('\n') +/** The free-check facts, broken out so they scan as features rather than fine print. */ +const CHECK_FACTS = [ + 'Free, no wallet', + `Up to ${BROWSER_CHECK_ITEM_CAP.toLocaleString()} items`, + `${MAX_ITEM_SIZE_LABEL} per item`, + 'Runs in your browser', + 'Your list is never sent anywhere', +] + type Verdict = | { kind: 'empty' } | { kind: 'unreadable'; summary: CidListSummary } @@ -90,22 +99,23 @@ export function CidListChecker() {