first-commit
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
'use client';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
// The product is a fully client-driven SPA — every component reads
|
||||
// localStorage, window.location, etc. — so we opt out of static-time
|
||||
// rendering for the entire tree. This keeps `next build --output export`
|
||||
// from trying to evaluate browser-only code while still emitting a real
|
||||
// shell HTML the daemon can serve as the SPA fallback.
|
||||
const App = dynamic(() => import('../../src/App').then((m) => m.App), {
|
||||
ssr: false,
|
||||
loading: () => <div className="od-loading-shell">Loading Open Design…</div>,
|
||||
});
|
||||
|
||||
export function ClientApp() {
|
||||
return <App />;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ClientApp } from './client-app';
|
||||
|
||||
// The whole product is a client-driven SPA: project IDs and file paths are
|
||||
// unbounded user input, so we route every URL through this single optional
|
||||
// catch-all and let the existing client router (src/router.ts, which reads
|
||||
// window.location at runtime) decide what to render.
|
||||
//
|
||||
// For `output: 'export'` we return a single empty `slug` so Next.js emits
|
||||
// one shell HTML at out/index.html; the daemon's SPA fallback (see
|
||||
// apps/daemon/src/server.ts) serves it for any unknown non-API path so deep links
|
||||
// still hydrate to the right view. In dev we leave `dynamicParams` at its
|
||||
// default (true) so `next dev` happily renders /projects/<id> directly.
|
||||
export function generateStaticParams() {
|
||||
return [{ slug: [] as string[] }];
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
return <ClientApp />;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { Metadata, Viewport } from 'next';
|
||||
import type { ReactNode } from 'react';
|
||||
import { I18nProvider } from '../src/i18n';
|
||||
import '../src/index.css';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Open Design',
|
||||
icons: {
|
||||
icon: '/app-icon.svg',
|
||||
// Safari pinned-tab mask icon — Next.js's Metadata API doesn't have a
|
||||
// dedicated `mask` field, so we surface it via the generic `other`
|
||||
// bucket which renders as a raw <link rel="mask-icon" ...>.
|
||||
other: [{ rel: 'mask-icon', url: '/app-icon.svg', color: '#363636' }],
|
||||
},
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: '#F4EFE6',
|
||||
};
|
||||
|
||||
/**
|
||||
* Inline script that runs before React hydrates to apply the saved theme
|
||||
* preference without a flash of unstyled content. It reads the same
|
||||
* localStorage key used by `state/config.ts` and sets `data-theme` on
|
||||
* `<html>` immediately — before any CSS or React paint.
|
||||
*/
|
||||
const themeInitScript = `(function(){try{var t=JSON.parse(localStorage.getItem('open-design:config')||'{}').theme;if(t==='light'||t==='dark')document.documentElement.setAttribute('data-theme',t);}catch(e){}})();`;
|
||||
|
||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html lang='en' suppressHydrationWarning>
|
||||
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
|
||||
<head>
|
||||
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: intentional theme-init inline script to prevent FOUC */}
|
||||
<script dangerouslySetInnerHTML={{ __html: themeInitScript }} />
|
||||
</head>
|
||||
<body suppressHydrationWarning>
|
||||
<I18nProvider>{children}</I18nProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user