feat: AI flow builder, visibility model, dashboard tabs, fork UI (#88)

- AI flow builder: scaffold → branch detail → assemble → review flow
- Generate All one-click branch generation with stop/cancel
- Regenerate scaffold suggestions button
- 3-action review screen: Start Flow, Open in Editor, Build Another
- Fix Publish button gated behind !isDirty
- Fix visibility column enforcement in tree access filter
- Add ?visibility filter and author_name to GET /trees
- Dashboard tabbed flows: My Flows / My Team / Public / All
- Create button in My Flows tab, window focus reload (stale data fix)
- Fork UI with optional reason modal
- Fix account_id nullability in User type and schema
- Keep is_public and visibility in sync on updates

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #88.
This commit is contained in:
chihlasm
2026-02-24 07:40:44 -05:00
committed by GitHub
parent 97cd297f46
commit ed4ab059bf
41 changed files with 1909 additions and 315 deletions

View File

@@ -37,6 +37,11 @@ export function AIFlowBuilderModal({ isOpen, onClose }: AIFlowBuilderModalProps)
// Auto-trigger scaffold after conversation starts (ref prevents double-fire)
const hasTriggeredScaffold = useRef(false)
useEffect(() => {
// Reset guard when wizard resets to foundation (Start Over or close)
if (phase === 'foundation') {
hasTriggeredScaffold.current = false
return
}
if (phase === 'scaffolding' && !hasTriggeredScaffold.current && !useAIFlowBuilderStore.getState().suggestedBranches.length) {
hasTriggeredScaffold.current = true
scaffold()
@@ -48,27 +53,48 @@ export function AIFlowBuilderModal({ isOpen, onClose }: AIFlowBuilderModalProps)
onClose()
}
const handleOpenInEditor = async () => {
if (!assembledTree) return
const createTree = async () => {
if (!assembledTree) return null
try {
const tree = await treesApi.create({
return await treesApi.create({
name: assembledTree.suggested_name,
description: assembledTree.suggested_description,
tree_structure: assembledTree.tree_structure,
tree_type: metadata.flow_type,
status: 'draft',
})
handleClose()
const editorPath =
metadata.flow_type === 'procedural'
? `/flows/${tree.id}/edit`
: `/trees/${tree.id}/edit`
navigate(editorPath)
} catch {
toast.error('Failed to create flow. Please try again.')
return null
}
}
const handleOpenInEditor = async () => {
const tree = await createTree()
if (!tree) return
handleClose()
const editorPath =
metadata.flow_type === 'procedural'
? `/flows/${tree.id}/edit`
: `/trees/${tree.id}/edit`
navigate(editorPath)
}
const handleStartFlow = async () => {
const tree = await createTree()
if (!tree) return
handleClose()
const navigatePath =
metadata.flow_type === 'procedural'
? `/flows/${tree.id}/navigate`
: `/trees/${tree.id}/navigate`
navigate(navigatePath)
}
const handleBuildAnother = () => {
reset()
}
const getTitle = () => {
switch (phase) {
case 'foundation':
@@ -102,7 +128,11 @@ export function AIFlowBuilderModal({ isOpen, onClose }: AIFlowBuilderModalProps)
{phase === 'generating' && <GeneratingAnimation />}
{phase === 'detailing' && <BranchDetailView />}
{phase === 'reviewing' && (
<TreePreviewCard onOpenInEditor={handleOpenInEditor} />
<TreePreviewCard
onOpenInEditor={handleOpenInEditor}
onStartFlow={handleStartFlow}
onBuildAnother={handleBuildAnother}
/>
)}
{phase === 'error' && <ErrorView />}
</Modal>