From d90e7389953d0bd26f65ecf324cf3657be7dfdf9 Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Wed, 22 Jul 2026 21:43:03 +0200 Subject: [PATCH] fix(examples): example pages re-render when the overlay registers late examples registerProExamples() now notifies useSyncExternalStore subscribers (same contract as proRoutes): a direct URL to an overlay-registered example resolved before the dynamic @pro import landed and stuck on the 404 branch. The three example pages subscribe to the version counter. --- frontend/src/data/examples.ts | 17 +++++++++++++++++ frontend/src/pages/ExampleDetailPage.tsx | 6 +++++- frontend/src/pages/ExampleEditorPage.tsx | 7 +++++-- frontend/src/pages/ExamplesPage.tsx | 5 +++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/frontend/src/data/examples.ts b/frontend/src/data/examples.ts index 3e1674b2..76f7c0bd 100644 --- a/frontend/src/data/examples.ts +++ b/frontend/src/data/examples.ts @@ -10023,10 +10023,27 @@ export function getExampleById(id: string): ExampleProject | undefined { * registers at runtime. Push-based (the exported array is the single source * the gallery and /example/:slug both read), idempotent per example id. */ +let proExamplesVersion = 0; +const proExamplesListeners = new Set<() => void>(); + export function registerProExamples(examples: ExampleProject[]): void { for (const ex of examples) { if (!exampleProjects.some((e) => e.id === ex.id)) exampleProjects.push(ex); } + // Overlay registration can land AFTER a direct-URL page render resolved the + // array (the @pro import is dynamic) — notify subscribers so example pages + // re-render instead of sticking on a 404 (same contract as proRoutes). + proExamplesVersion++; + for (const l of proExamplesListeners) l(); +} + +export function subscribeProExamples(cb: () => void): () => void { + proExamplesListeners.add(cb); + return () => proExamplesListeners.delete(cb); +} + +export function getProExamplesVersion(): number { + return proExamplesVersion; } // Get all categories diff --git a/frontend/src/pages/ExampleDetailPage.tsx b/frontend/src/pages/ExampleDetailPage.tsx index e55d083e..c77fa13d 100644 --- a/frontend/src/pages/ExampleDetailPage.tsx +++ b/frontend/src/pages/ExampleDetailPage.tsx @@ -10,8 +10,9 @@ */ import React from 'react'; +import { useSyncExternalStore } from 'react'; import { useParams, useNavigate, Link } from 'react-router-dom'; -import { exampleProjects } from '../data/examples'; +import { exampleProjects, subscribeProExamples, getProExamplesVersion } from '../data/examples'; import { AppHeader } from '../components/layout/AppHeader'; import { ExampleThumbnail } from '../components/examples/ExampleThumbnail'; import { useSEO } from '../utils/useSEO'; @@ -44,6 +45,9 @@ const DIFFICULTY_COLOR: Record = { }; export const ExampleDetailPage: React.FC = () => { + // Re-render when the pro overlay registers late examples (dynamic import). + useSyncExternalStore(subscribeProExamples, getProExamplesVersion, getProExamplesVersion); + const { exampleId } = useParams<{ exampleId: string }>(); const navigate = useNavigate(); diff --git a/frontend/src/pages/ExampleEditorPage.tsx b/frontend/src/pages/ExampleEditorPage.tsx index cfc58a08..0fb6f510 100644 --- a/frontend/src/pages/ExampleEditorPage.tsx +++ b/frontend/src/pages/ExampleEditorPage.tsx @@ -21,9 +21,9 @@ * id is set on useProjectStore, so it can't overwrite anything). */ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState, useSyncExternalStore } from 'react'; import { useParams } from 'react-router-dom'; -import { exampleProjects } from '../data/examples'; +import { exampleProjects, subscribeProExamples, getProExamplesVersion } from '../data/examples'; import { loadExample, type LibraryInstallProgress } from '../utils/loadExample'; import { EditorPage } from './EditorPage'; import { AppHeader } from '../components/layout/AppHeader'; @@ -32,6 +32,9 @@ import { useSEO } from '../utils/useSEO'; const DOMAIN = 'https://velxio.dev'; export const ExampleEditorPage: React.FC = () => { + // Re-render when the pro overlay registers late examples (dynamic import). + useSyncExternalStore(subscribeProExamples, getProExamplesVersion, getProExamplesVersion); + const { exampleId } = useParams<{ exampleId: string }>(); const [ready, setReady] = useState(false); const [error, setError] = useState(false); diff --git a/frontend/src/pages/ExamplesPage.tsx b/frontend/src/pages/ExamplesPage.tsx index bbe74740..c4879610 100644 --- a/frontend/src/pages/ExamplesPage.tsx +++ b/frontend/src/pages/ExamplesPage.tsx @@ -8,6 +8,8 @@ */ import React from 'react'; +import { useSyncExternalStore } from 'react'; +import { subscribeProExamples, getProExamplesVersion } from '../data/examples'; import { useNavigate } from 'react-router-dom'; import { ExamplesGallery } from '../components/examples/ExamplesGallery'; import { CommunityProjectsGrid } from '../components/examples/CommunityProjectsGrid'; @@ -18,6 +20,9 @@ import { getSeoMeta } from '../seoRoutes'; import type { ExampleProject } from '../data/examples'; export const ExamplesPage: React.FC = () => { + // Re-render when the pro overlay registers late examples (dynamic import). + useSyncExternalStore(subscribeProExamples, getProExamplesVersion, getProExamplesVersion); + const localize = useLocalizedHref(); useSEO(getSeoMeta('/examples')!);