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
5 changes: 3 additions & 2 deletions src/Drawd.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,8 @@ export default function Drawd({ initialRoomCode }) {
});

// ── Import / export ────────────────────────────────────────────────────────────────
const { importConfirm, setImportConfirm, importFileRef, onExport, onImport, onImportFileChange, onImportReplace, onImportMerge } =
useImportExport({ screens, connections, documents, dataModels, stickyNotes, screenGroups, comments, pan, zoom, featureBrief, taskLink, techStack, replaceAll, mergeAll, setPan, setZoom, setStickyNotes, setScreenGroups, setComments });
const { importConfirm, setImportConfirm, importFileRef, onExport, onExportPrototype, onImport, onImportFileChange, onImportReplace, onImportMerge } =
useImportExport({ screens, connections, documents, dataModels, stickyNotes, screenGroups, comments, pan, zoom, featureBrief, taskLink, techStack, replaceAll, mergeAll, setPan, setZoom, setStickyNotes, setScreenGroups, setComments, scopeScreenIds, connectedFileName });

// ── Toast notification ─────────────────────────────────────────────────────────────
const [toast, setToast] = useState(null);
Expand Down Expand Up @@ -460,6 +460,7 @@ export default function Drawd({ initialRoomCode }) {
documentCount={documents.length}
dataModelCount={dataModels.length}
onExport={onExport}
onExportPrototype={onExportPrototype}
onImport={onImport}
onGenerate={onGenerate}
onDocuments={() => setShowDocuments(true)}
Expand Down
11 changes: 10 additions & 1 deletion src/components/TopBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ function ShareIcon() {
);
}

export function TopBar({ screenCount, connectionCount, onExport, onImport, onGenerate, canUndo, canRedo, onUndo, onRedo, connectedFileName, saveStatus, isFileSystemSupported, onNew, onOpen, onSaveAs, onDocuments, documentCount = 0, onDataModels, dataModelCount = 0, collabState, onShare, collabBadge, collabPresence, onToggleParticipants, showParticipants, onTemplates, onToggleComments, showComments, unresolvedCommentCount = 0, canComment }) {
export function TopBar({ screenCount, connectionCount, onExport, onExportPrototype, onImport, onGenerate, canUndo, canRedo, onUndo, onRedo, connectedFileName, saveStatus, isFileSystemSupported, onNew, onOpen, onSaveAs, onDocuments, documentCount = 0, onDataModels, dataModelCount = 0, collabState, onShare, collabBadge, collabPresence, onToggleParticipants, showParticipants, onTemplates, onToggleComments, showComments, unresolvedCommentCount = 0, canComment }) {
const [fileMenuOpen, setFileMenuOpen] = useState(false);
const fileMenuRef = useRef(null);

Expand Down Expand Up @@ -430,6 +430,15 @@ export function TopBar({ screenCount, connectionCount, onExport, onImport, onGen
>
<span>Export</span>
</button>

<button
className="ff-menu-item"
onClick={() => { if (screenCount > 0) { setFileMenuOpen(false); onExportPrototype(); } }}
disabled={screenCount === 0}
style={menuItemStyle(screenCount === 0)}
>
<span>Export Prototype</span>
</button>
</div>
)}
</div>
Expand Down
13 changes: 13 additions & 0 deletions src/hooks/useImportExport.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState, useRef, useCallback } from "react";
import { exportFlow } from "../utils/exportFlow";
import { importFlow } from "../utils/importFlow";
import { mergeFlow } from "../utils/mergeFlow";
import { generatePrototype, downloadPrototype } from "../utils/generatePrototype";

export function useImportExport({
screens,
Expand All @@ -23,6 +24,8 @@ export function useImportExport({
setStickyNotes,
setScreenGroups,
setComments,
scopeScreenIds,
connectedFileName,
}) {
const [importConfirm, setImportConfirm] = useState(null);
const importFileRef = useRef(null);
Expand Down Expand Up @@ -78,10 +81,20 @@ export function useImportExport({
setImportConfirm(null);
}, [importConfirm, screens, mergeAll]);

const onExportPrototype = useCallback(() => {
if (screens.length === 0) return;
const html = generatePrototype(screens, connections, {
title: connectedFileName || "Prototype",
scopeScreenIds,
});
downloadPrototype(html);
}, [screens, connections, scopeScreenIds, connectedFileName]);

return {
importConfirm, setImportConfirm,
importFileRef,
onExport,
onExportPrototype,
onImport,
onImportFileChange,
onImportReplace,
Expand Down
33 changes: 33 additions & 0 deletions src/pages/docs/userGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,39 @@ Click "Download ZIP" in the Instructions panel to download all generated files p
> [!TIP]
> Requirement IDs (SCR-XXXXXXXX, HSP-XXXXXXXX-XXXX, NAV-XXXX) are stable across regenerations as long as screen and hotspot names stay the same. You can reference them in follow-up prompts to your AI assistant.

## Exporting an Interactive Prototype

Export your flow as a clickable HTML prototype that stakeholders can tap through in any browser — no code, no server, no dependencies.

### How to export

- Open the **File** menu in the top bar and click **Export Prototype**
- A single `.html` file downloads with all screen images and navigation logic embedded

### What the prototype includes

- Each screen is displayed as a full-viewport image inside a phone-frame container (430px max-width on desktop, full-width on mobile)
- Hotspots are invisible tap areas positioned over the screen image — tap one to navigate to the connected screen
- A floating **Back** button appears after navigating and returns to the previous screen
- A **sidebar screen list** (toggle via the hamburger icon) lets you jump to any screen directly
- Conditional hotspots show a **choice popup** with labeled options so reviewers can explore different paths

### Navigation behavior

- `navigate` and `modal` hotspots go to the connected screen
- `back` hotspots return to the previous screen in the navigation history
- `api` hotspots follow the success path (there is no real backend in the prototype)
- `conditional` hotspots present a menu of labeled branches
- Hotspots without a target are rendered but do nothing when tapped
- Keyboard navigation: `Backspace` or `Alt+Left` to go back, `Escape` to close menus

### Scope filtering

If a scope root is active (you are viewing a sub-flow), only the screens in that scope are included in the prototype.

> [!TIP]
> The exported file is entirely self-contained — share it via email, Slack, or any file host. Recipients just open it in a browser to tap through the flow.

## Keyboard Shortcuts

Press `?` anywhere on the canvas to open the full keyboard shortcuts panel. The shortcuts below are organized by category.
Expand Down
Loading
Loading