* 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>
88 lines
2.6 KiB
TypeScript
88 lines
2.6 KiB
TypeScript
import type { IntakeFormField } from '@/types'
|
|
import { InlineVariablePrompt } from './InlineVariablePrompt'
|
|
|
|
interface ResolvedTextProps {
|
|
text: string
|
|
variables: Record<string, string>
|
|
intakeFields?: IntakeFormField[]
|
|
onVariableSubmit?: (variableName: string, value: string) => void
|
|
className?: string
|
|
}
|
|
|
|
/**
|
|
* Renders text with resolved variables inline. Unresolved [VAR:x] tokens
|
|
* are replaced with interactive InlineVariablePrompt components.
|
|
*/
|
|
export function ResolvedText({
|
|
text,
|
|
variables,
|
|
intakeFields,
|
|
onVariableSubmit,
|
|
className,
|
|
}: ResolvedTextProps) {
|
|
if (!text) return null
|
|
|
|
// Split on variable tokens, keeping the tokens in the result
|
|
const tokenPattern = /(\[(?:VAR|USER_INPUT):([^\]]+)\])/g
|
|
const parts: Array<{ type: 'text'; value: string } | { type: 'var'; variableName: string; token: string }> = []
|
|
let lastIndex = 0
|
|
let match
|
|
|
|
// Remove [SAVE_AS:...] tokens first
|
|
const cleaned = text.replace(/\[SAVE_AS:[^\]]+\]/g, '')
|
|
|
|
while ((match = tokenPattern.exec(cleaned)) !== null) {
|
|
// Add text before the token
|
|
if (match.index > lastIndex) {
|
|
parts.push({ type: 'text', value: cleaned.slice(lastIndex, match.index) })
|
|
}
|
|
|
|
const variableName = match[2].trim()
|
|
const resolvedValue = variables[variableName]
|
|
|
|
if (resolvedValue && resolvedValue.trim()) {
|
|
// Variable is resolved — render as plain text
|
|
parts.push({ type: 'text', value: resolvedValue })
|
|
} else {
|
|
// Variable is unresolved — render as inline prompt
|
|
parts.push({ type: 'var', variableName, token: match[1] })
|
|
}
|
|
|
|
lastIndex = match.index + match[0].length
|
|
}
|
|
|
|
// Add remaining text
|
|
if (lastIndex < cleaned.length) {
|
|
parts.push({ type: 'text', value: cleaned.slice(lastIndex) })
|
|
}
|
|
|
|
// If no unresolved variables, just render plain text
|
|
const hasUnresolved = parts.some(p => p.type === 'var')
|
|
if (!hasUnresolved) {
|
|
const fullText = parts.map(p => p.type === 'text' ? p.value : '').join('')
|
|
return <span className={className}>{fullText}</span>
|
|
}
|
|
|
|
// Find field metadata helper
|
|
const getFieldMeta = (varName: string) =>
|
|
intakeFields?.find(f => f.variable_name === varName)
|
|
|
|
return (
|
|
<span className={className}>
|
|
{parts.map((part, i) => {
|
|
if (part.type === 'text') {
|
|
return <span key={i}>{part.value}</span>
|
|
}
|
|
return (
|
|
<InlineVariablePrompt
|
|
key={`${part.variableName}-${i}`}
|
|
variableName={part.variableName}
|
|
fieldMeta={getFieldMeta(part.variableName)}
|
|
onSubmit={onVariableSubmit || (() => {})}
|
|
/>
|
|
)
|
|
})}
|
|
</span>
|
|
)
|
|
}
|