feat: flexible intake — deferred variables + prepared sessions (#103)

* feat: flexible intake — deferred variables + prepared sessions

Remove blocking intake form modal. Variables are now filled inline during
flow execution or pre-filled via prepared sessions. Adds PATCH /sessions/{id}/variables
endpoint, POST /sessions/prepare for session pre-staging, inline variable prompts
in StepDetail, editable Session Variables panel, and "Prepared for You" dashboard section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: pass treeData directly to startSession to avoid stale state

setTree(treeData) hasn't committed when startSession runs immediately
after, so tree is still null and getStepsFromTree returns []. This
caused the step detail area to render empty on new session start.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: wire PrepareSessionModal entry point in Flow Library

Add "Prepare session" button (clipboard icon) to grid, list, and table
views for procedural/maintenance flows. Clicking fetches tree intake
fields and account members, then opens PrepareSessionModal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #103.
This commit is contained in:
chihlasm
2026-03-10 09:49:51 -04:00
committed by GitHub
parent 4727106141
commit ccd06c9ed4
25 changed files with 1214 additions and 102 deletions

View File

@@ -7,11 +7,13 @@ import { treesApi } from '@/api/trees'
import { categoriesApi } from '@/api/categories'
import { foldersApi } from '@/api/folders'
import { sessionsApi } from '@/api/sessions'
import type { TreeListItem, CategoryListItem, FolderListItem, Session } from '@/types'
import type { TreeListItem, CategoryListItem, FolderListItem, Session, IntakeFormField } from '@/types'
import { FolderEditModal } from '@/components/library/FolderEditModal'
import { ForkModal } from '@/components/library/ForkModal'
import { ExportFlowModal } from '@/components/library/ExportFlowModal'
import { ImportFlowModal } from '@/components/library/ImportFlowModal'
import { PrepareSessionModal } from '@/components/procedural/PrepareSessionModal'
import { accountsApi } from '@/api/accounts'
import { ConfirmDialog } from '@/components/common/ConfirmDialog'
import { TreeGridView } from '@/components/library/TreeGridView'
import { TreeListView } from '@/components/library/TreeListView'
@@ -83,6 +85,13 @@ export function TreeLibraryPage() {
const [showImportModal, setShowImportModal] = useState(false)
const [exportTarget, setExportTarget] = useState<TreeListItem | null>(null)
// Prepare session modal state
const [prepareTarget, setPrepareTarget] = useState<{
tree: TreeListItem
intakeFields: IntakeFormField[]
teamMembers: { id: string; name: string; email: string }[]
} | null>(null)
// AI builder state
const { aiEnabled } = useCachedQuota()
@@ -268,6 +277,24 @@ export function TreeLibraryPage() {
if (tree) setExportTarget(tree)
}
const handlePrepareSession = async (tree: TreeListItem) => {
try {
const [treeDetail, members] = await Promise.all([
treesApi.get(tree.id),
accountsApi.getMembers().catch(() => []),
])
setPrepareTarget({
tree,
intakeFields: treeDetail.intake_form || [],
teamMembers: members
.filter(m => m.is_active)
.map(m => ({ id: m.id, name: m.name, email: m.email })),
})
} catch {
toast.error('Failed to load flow details')
}
}
const hasActiveFilters =
selectedCategoryId || selectedTags.length > 0 || searchQuery || selectedFolderId
@@ -436,7 +463,7 @@ export function TreeLibraryPage() {
</p>
<p className="text-sm text-muted-foreground">
{s.client_name && `${s.client_name} · `}
Started {formatTimeAgo(s.started_at)}
{s.started_at ? `Started ${formatTimeAgo(s.started_at)}` : 'Not started'}
</p>
</div>
<div className="flex items-center gap-2">
@@ -498,6 +525,7 @@ export function TreeLibraryPage() {
<TreeGridView
trees={trees}
onStartSession={handleStartSession}
onPrepareSession={handlePrepareSession}
onTagClick={handleTagClick}
onFolderCreated={handleCreateFolder}
onDeleteTree={(tree) => {
@@ -515,6 +543,7 @@ export function TreeLibraryPage() {
<TreeListView
trees={trees}
onStartSession={handleStartSession}
onPrepareSession={handlePrepareSession}
onTagClick={handleTagClick}
onFolderCreated={handleCreateFolder}
onDeleteTree={(tree) => {
@@ -532,6 +561,7 @@ export function TreeLibraryPage() {
<TreeTableView
trees={trees}
onStartSession={handleStartSession}
onPrepareSession={handlePrepareSession}
onTagClick={handleTagClick}
onFolderCreated={handleCreateFolder}
onDeleteTree={(tree) => {
@@ -604,6 +634,18 @@ export function TreeLibraryPage() {
onClose={() => { setShowImportModal(false); loadTrees() }}
/>
)}
{prepareTarget && (
<PrepareSessionModal
isOpen
onClose={() => setPrepareTarget(null)}
treeId={prepareTarget.tree.id}
treeName={prepareTarget.tree.name}
intakeFields={prepareTarget.intakeFields}
teamMembers={prepareTarget.teamMembers}
onPrepared={() => setPrepareTarget(null)}
/>
)}
</div>
</div>
)