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:
87
frontend/src/components/procedural/ResolvedText.tsx
Normal file
87
frontend/src/components/procedural/ResolvedText.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user