Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions frontend/src/app/BootGate.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use client';

// InGen Studio — boot / persistence guard
//
// Runs once per full page load, on the client, before any child effect reads the store. Saved
// pipelines persist ACROSS reloads (autosave writes them to localStorage); this gate's only job is
// a one-time migration: if the stored data version differs from the code's DATA_VERSION, clear our
// namespace once so we never try to load models written against an incompatible schema.

import { STORAGE_NAMESPACE, DATA_VERSION } from '../models/constants.js';

const NS = STORAGE_NAMESPACE;
const VERSION_KEY = `${NS}:version`;

let booted = false;

function migrateIfStale() {
if (localStorage.getItem(VERSION_KEY) === DATA_VERSION) return; // up to date — keep saved work

const oldVersion = localStorage.getItem(VERSION_KEY) ?? 'none';
const staleKeys = Object.keys(localStorage).filter((k) => k.startsWith(NS + ':'));

// Snapshot old data into a backup key before wiping so a future recovery path is possible.
if (staleKeys.length > 0) {
const backup = {};
staleKeys.forEach((k) => { backup[k] = localStorage.getItem(k); });
try {
localStorage.setItem(`${NS}:backup:${oldVersion}`, JSON.stringify(backup));
} catch {
// Storage full — skip backup silently, still need to migrate.
}
console.warn(
`[InGen] Data schema changed (${oldVersion} → ${DATA_VERSION}). ` +
`${staleKeys.length} key(s) cleared. Backup saved to "${NS}:backup:${oldVersion}".`
);
}

staleKeys.forEach((k) => localStorage.removeItem(k));
localStorage.setItem(VERSION_KEY, DATA_VERSION);
}

export default function BootGate({ children }) {
// localStorage is client-only; guard so this is inert during server rendering.
if (typeof window !== 'undefined' && !booted) {
booted = true;
migrateIfStale();
}
return children;
}
7 changes: 7 additions & 0 deletions frontend/src/app/configs/[configId]/history/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client';

import HistoryView from '../../../../components/run/HistoryView.jsx';

export default function HistoryPage() {
return <HistoryView />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client';

import InterfaceEditor from '../../../../../components/editor/InterfaceEditor.jsx';

export default function InterfaceEditorPage() {
return <InterfaceEditor />;
}
25 changes: 25 additions & 0 deletions frontend/src/app/configs/[configId]/layout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use client';

// InGen Studio — config workspace layout (segment: /configs/[configId])
//
// Composes the providers (catalog + the config document) around the WorkspaceLayout. Every nested
// route renders into WorkspaceLayout's content slot and shares this config context. This is the
// Next App Router equivalent of the old <ConfigWorkspace> react-router element.

import { useParams } from 'next/navigation';

import { CatalogProvider } from '../../../state/CatalogContext.jsx';
import { ConfigProvider } from '../../../state/ConfigContext.jsx';
import WorkspaceLayout from '../../../components/layout/WorkspaceLayout.jsx';

export default function ConfigLayout({ children }) {
const { configId } = useParams();
return (
<CatalogProvider>
{/* key={configId} remounts the document store on config switch — clean load state per id. */}
<ConfigProvider key={configId} configId={configId}>
<WorkspaceLayout configId={configId}>{children}</WorkspaceLayout>
</ConfigProvider>
</CatalogProvider>
);
}
28 changes: 28 additions & 0 deletions frontend/src/app/configs/[configId]/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use client';

// Workspace index. New flow: if the first interface has no sources yet, show the source-first
// Start screen (pick a source → choose inFlow/inChat). Once it has a source, redirect into the
// interface editor so reloads land where you left off.

import { useEffect } from 'react';
import { useParams, useRouter } from 'next/navigation';

import { useConfig } from '../../../state/ConfigContext.jsx';
import Start from '../../../components/start/Start.jsx';

export default function ConfigIndex() {
const { configId } = useParams();
const router = useRouter();
const { model, status } = useConfig();

const first = model?.interfaceOrder?.[0];
const firstHasSources = first && (model.interfacesByName[first]?.sources?.length ?? 0) > 0;

useEffect(() => {
if (firstHasSources) router.replace(`/configs/${configId}/interfaces/${first}`);
}, [configId, first, firstHasSources, router]);

if (status === 'loading' || !model) return <div className="placeholder">Loading config…</div>;
if (firstHasSources) return <div className="placeholder">Opening editor…</div>;
return <Start configId={configId} />;
}
7 changes: 7 additions & 0 deletions frontend/src/app/configs/[configId]/run/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client';

import RunConsole from '../../../../components/run/RunConsole.jsx';

export default function RunPage() {
return <RunConsole />;
}
32 changes: 32 additions & 0 deletions frontend/src/app/layout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// InGen Studio — root layout
//
// Server component that owns the document shell. Global styles (which also pull in the Inter
// webfont via @import) are imported here. BootGate seeds localStorage on the client; AppShell is
// the persistent brand-bar frame that used to be the top-level react-router layout route.

import '../index.css';
import BootGate from './BootGate.jsx';
import AppShell from '../components/layout/AppShell.jsx';

export const metadata = {
title: 'InGen Data Transformation',
description: 'YAML interface authoring for the InGen data transformation pipeline.',
icons: { icon: '/favicon.svg' },
};

export const viewport = {
width: 'device-width',
initialScale: 1,
};

export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<BootGate>
<AppShell>{children}</AppShell>
</BootGate>
</body>
</html>
);
}
10 changes: 10 additions & 0 deletions frontend/src/app/not-found.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Link from 'next/link';

export default function NotFound() {
return (
<div className="placeholder">
<p>Page not found.</p>
<Link href="/">Back to editor</Link>
</div>
);
}
Loading