Replace the first-run placeholder seed with an onboarding canvas

This commit is contained in:
2026-06-09 14:03:47 +03:00
parent 7d247e8d2c
commit 81f563a3e7
16 changed files with 852 additions and 93 deletions
+56 -35
View File
@@ -2,6 +2,7 @@ import { useEffect, useRef } from 'react';
import { ConfirmDialog } from './components/ConfirmDialog';
import { LivePreview } from './components/LivePreview';
import { ModalShell } from './components/ModalShell';
import { Onboarding } from './components/Onboarding';
import { PaneToggleStrip } from './components/PaneToggleStrip';
import { ResizeHandle } from './components/ResizeHandle';
import { SnippetLibrary } from './components/SnippetLibrary';
@@ -11,6 +12,7 @@ import { Toaster } from './components/Toaster';
import { openModal, setConfirm } from './modals/ModalCoordinator';
import { confirm } from './stores/ConfirmStore';
import { usePanesStore } from './stores/PanesStore';
import { useSnippetStore } from './stores/SnippetStore';
import { exportWorkspace, importWorkspace } from './services/transfer';
import styles from './App.module.css';
@@ -20,9 +22,12 @@ import styles from './App.module.css';
*
* The center editor flexes; the library and preview carry remembered widths and
* are resized via the drag handles between them (spec §01A). Each pane can be
* shown/hidden from the always-present toggle strip (the leftmost rail); a hidden
* pane frees its space and the rest redistribute — when the editor is hidden the
* two side panes flex proportionally to their remembered widths.
* shown/hidden from the toggle strip (the leftmost rail); a hidden pane frees its
* space and the rest redistribute — when the editor is hidden the two side panes
* flex proportionally to their remembered widths.
*
* When the library is empty, this whole pane chrome (strip + panes) is replaced
* by the full-width onboarding canvas (spec §02 → First-Run & Empty Workspace).
*/
export function App() {
const libraryWidth = usePanesStore((s) => s.libraryWidth);
@@ -30,6 +35,10 @@ export function App() {
const libraryVisible = usePanesStore((s) => s.libraryVisible);
const editorVisible = usePanesStore((s) => s.editorVisible);
const previewVisible = usePanesStore((s) => s.previewVisible);
// An empty library is the onboarding surface: the whole pane chrome (toggle
// strip, library list, editor, preview) is replaced by the full-width Onboarding
// canvas (spec §02 → First-Run & Empty Workspace).
const hasSnippets = useSnippetStore((s) => s.snippets.length > 0);
// Side-pane sizing: fixed remembered width while the editor (the flex filler) is
// present; when it's hidden, the side panes grow proportionally to those widths
@@ -120,38 +129,50 @@ export function App() {
{/* tabIndex -1 makes the landmark a focus target for the skip link. */}
<main id="main" className={styles.panes} tabIndex={-1}>
{/* Always-present rail: shows/hides panes and shortcuts to Datasets (§01A). */}
<PaneToggleStrip />
{libraryVisible && (
<section
id="pane-library"
className={styles.pane}
style={sideStyle(libraryWidth)}
aria-label="Snippet library"
>
<SnippetLibrary />
</section>
)}
{/* A resize handle only sits between two visible panes that flank the editor. */}
{libraryVisible && editorVisible && (
<ResizeHandle side="library" label="Resize snippet library" />
)}
{editorVisible && (
<section id="pane-editor" className={styles.paneEditor} aria-label="Spec editor">
<SpecEditor />
</section>
)}
{editorVisible && previewVisible && (
<ResizeHandle side="preview" label="Resize live preview" />
)}
{previewVisible && (
<section
id="pane-preview"
className={styles.pane}
style={sideStyle(previewWidth)}
aria-label="Live preview"
>
<LivePreview />
{hasSnippets ? (
<>
{/* Always-present rail: shows/hides panes and shortcuts to Datasets (§01A). */}
<PaneToggleStrip />
{libraryVisible && (
<section
id="pane-library"
className={styles.pane}
style={sideStyle(libraryWidth)}
aria-label="Snippet library"
>
<SnippetLibrary />
</section>
)}
{/* A resize handle only sits between two visible panes that flank the editor. */}
{libraryVisible && editorVisible && (
<ResizeHandle side="library" label="Resize snippet library" />
)}
{editorVisible && (
<section id="pane-editor" className={styles.paneEditor} aria-label="Spec editor">
<SpecEditor />
</section>
)}
{editorVisible && previewVisible && (
<ResizeHandle side="preview" label="Resize live preview" />
)}
{previewVisible && (
<section
id="pane-preview"
className={styles.pane}
style={sideStyle(previewWidth)}
aria-label="Live preview"
>
<LivePreview />
</section>
)}
</>
) : (
// Empty library → the onboarding canvas takes the full workspace. The
// pane chrome (toggle strip, library list, editor, preview) is hidden:
// with no snippets, Create/Search/Sort/Storage and the pane toggles have
// nothing to act on, so the welcome gets the whole width (spec §02).
<section className={styles.paneOnboarding} aria-label="Getting started">
<Onboarding />
</section>
)}
</main>