# ROADMAP-0.38 — Workflow Product Maturity Everything deferred from v0.37.15 plus what the MVP gate actually requires: "Team admins build workflows **visually**." v0.37.15 delivers the plumbing — cancel, unclaim, reassign, stage CRUD in the FE, queue views. This series builds the product on top of that plumbing. --- ## Series Overview ``` v0.37.15 Workflow Ownership & Lifecycle (plumbing) v0.37.16 Projects Surface v0.37.17 Debug / Model Surface │ ── 0.37 tag ── │ v0.38.0 Form Builder ← team admins stop writing JSON v0.38.1 Stage Reorder + Bulk Ops ← queue management at scale v0.38.2 SLA Alerting ← stale queues self-report v0.38.3 Visitor Experience ← the public face v0.38.4 Workflow Templates + Clone ← reuse across teams v0.38.5 Analytics Dashboard ← throughput, bottlenecks, SLA compliance │ ─ ─ ─ MVP v0.50.0 gate ─ ─ ─ ``` **Milestone gates:** | Milestone | Gate | Meaning | |-----------|------|---------| | v0.37 tag | **UI Complete** | Every platform capability has a surface; no features require raw API calls | | v0.38.0 | **Self-service** | A team admin can build a complete workflow without touching JSON or asking a global admin | | v0.38.2 | **Operational** | Stale work surfaces automatically; team admins can manage queues without monitoring dashboards | | v0.38.5 | **Measurable** | Team leads can answer "how long does stage X take" and "where are we bottlenecked" | --- ## v0.38.0 — Form Builder **The single biggest gap.** `form_template` is currently raw JSON in a textarea. The MVP gate says "build workflows visually" — this is the make-or-break. ### Scope A visual form builder embedded in the stage editor (E2 from the .15 design). Not a standalone surface — it's a panel that opens when you click "Edit Form" on a `form_only` or `form_chat` stage. ### Fields supported (matching existing TypedFormTemplate) `text`, `email`, `select`, `number`, `date`, `textarea`, `checkbox`, `file`. Each with the validation rules already defined in `models/workflow.go` (min/max length, pattern, min/max value, etc). ### JSX Exemplar ```jsx /** * FormBuilder — visual editor for stage form_template * * Opens as a slide-out panel from StageEditor. * Produces a TypedFormTemplate JSON blob on save. * * Features: * - Add/remove/reorder fields * - Field type picker with type-specific validation config * - Fieldset grouping (progressive multi-step forms) * - Conditional visibility (when/op/value) * - Live preview pane */ function FormBuilder({ initial, onSave, onCancel }) { const [fields, setFields] = useState(initial?.fields || []); const [fieldsets, setFieldsets] = useState(initial?.fieldsets || []); const [useFieldsets, setUseFieldsets] = useState((initial?.fieldsets?.length || 0) > 0); const [preview, setPreview] = useState(false); function addField(targetFieldset) { const field = { key: `field_${Date.now()}`, type: 'text', label: '', required: false, }; if (useFieldsets && targetFieldset !== undefined) { // Add to specific fieldset setFieldsets(fs => fs.map((f, i) => i === targetFieldset ? { ...f, fields: [...f.fields, field] } : f )); } else { setFields(f => [...f, field]); } } function save() { const template = useFieldsets ? { fieldsets, hooks: initial?.hooks || null } : { fields, hooks: initial?.hooks || null }; onSave(template); } return (

Form Builder

{preview ? ( ) : (
{useFieldsets ? ( ) : ( )}
)}
); } /** * FieldEditor — single field configuration row * * Inline editing of key, type, label, required, validation, condition. * Type-specific validation options appear contextually. */ function FieldEditor({ field, onChange, onRemove, allFields }) { function set(k, v) { onChange({ ...field, [k]: v }); } return (
set('label', e.target.value)} />
set('key', e.target.value)} placeholder="field_name" />
{/* Type-specific validation — only render what applies */} {(field.type === 'text' || field.type === 'textarea') && ( set('validation', v)} /> )} {field.type === 'number' && ( set('validation', v)} /> )} {field.type === 'select' && ( set('options', o)} /> )}
{/* Conditional visibility */} set('condition', c)} allFields={allFields} />
); } ``` ### BE changes None. The form builder is pure FE — it produces the same `TypedFormTemplate` JSON that already exists. The BE validation in `models/workflow.go` is unchanged. ### Deliverables | File | What | |------|------| | `src/js/sw/components/form-builder/index.js` | FormBuilder root | | `src/js/sw/components/form-builder/field-editor.js` | FieldEditor, type-specific validators | | `src/js/sw/components/form-builder/fieldset-editor.js` | Fieldset grouping | | `src/js/sw/components/form-builder/condition-editor.js` | Conditional visibility | | `src/js/sw/components/form-builder/preview.js` | Live form preview | | `src/css/form-builder.css` | Styles | | Integration into StageEditor (E2 from .15) | "Edit Form" button opens builder | --- ## v0.38.1 — Stage Reorder + Bulk Ops ### Stage drag-and-drop Replace button-based reorder with drag-and-drop in the stage editor. No external library — use the HTML5 Drag and Drop API with `draggable`, `ondragstart`, `ondragover`, `ondrop`. The reorder PATCH endpoint already exists: `PATCH /api/v1/teams/:teamId/workflows/:wfId/stages/reorder` ### Bulk assignment operations Team admins managing 20+ assignments need batch actions. ```jsx /** * BulkAssignmentBar — selection-based bulk actions * * Appears above the queue when 1+ assignments are selected. * Actions: bulk cancel, bulk reassign (to specific user). */ function BulkAssignmentBar({ selected, teamId, onAction }) { const [reassignTo, setReassignTo] = useState(''); async function bulkCancel() { const ok = await sw.confirm(`Cancel ${selected.length} assignments?`, true); if (!ok) return; await Promise.all(selected.map(id => sw.api.workflowAssignments.cancel(id))); sw.toast(`${selected.length} cancelled`, 'success'); onAction(); } async function bulkReassign() { if (!reassignTo) return; await Promise.all(selected.map(id => sw.api.workflowAssignments.reassign(id, reassignTo) )); sw.toast(`${selected.length} reassigned`, 'success'); onAction(); } return (
{selected.length} selected
); } ``` ### BE changes None. Bulk ops are client-side `Promise.all` over existing single endpoints. If performance becomes an issue at scale (50+ assignments), add a `POST /api/v1/workflow-assignments/bulk` endpoint in a future patch. YAGNI for now. --- ## v0.38.2 — SLA Alerting The monitor already computes `sla_breached`. This version makes it proactive instead of requiring someone to check the dashboard. ### Scheduled SLA scanner A new periodic task (Go scheduler, not Starlark) that runs every N minutes: 1. Query active workflow channels with `sla_seconds` configured 2. Compute breach status from `stage_entered_at` 3. For newly breached instances (not yet notified): - Create notification for the assigned team members - Emit `workflow.sla_breached` WS event - If webhook_url configured, fire webhook ### Data changes Add `sla_notified_at` (nullable timestamp) to the channels table. The scanner only fires notifications when `sla_notified_at IS NULL` and the SLA is breached, then sets the timestamp. Prevents duplicate alerts. ### FE changes The queue items (E1, E4) already render `sla_breached` badges. Add: - Pulsing animation on breached badges - Toast on `workflow.sla_breached` WS event - Notification bell integration (already wired in .15 via `notifications.NotifyWorkflowClaimed` pattern) ### Deliverables | File | What | |------|------| | `server/scheduler/sla_scanner.go` | Periodic SLA check | | `server/notifications/workflow_sla.go` | SLA notification builder | | Migration: `sla_notified_at` column | Both PG + SQLite | | FE: badge animation CSS | `src/css/sw-primitives.css` | --- ## v0.38.3 — Visitor Experience The visitor currently gets a raw workflow surface. After .15 they get a terminal screen (E5) when their stages are done. This version makes the visitor experience feel like a product. ### Scope 1. **Branded entry page** — the workflow landing page already supports branding (`accent_color`, `logo_url`, `tagline`). Extend with: - Custom welcome text (markdown) - Background image/gradient - "Powered by" toggle (hide/show Switchboard branding) 2. **Progress indicator** — visitor sees "Step 1 of N" for their visible stages only. Internal team stages are invisible. The progress bar counts only stages where the visitor is the audience. 3. **Email notifications** — optional. When configured on the workflow: - Collect email at stage 0 (or extract from form data) - Send "request received" confirmation - Send "your request is complete" when workflow completes - No notifications for internal stage transitions 4. **Session resume** — visitor who closes the browser can return to `/w/:id/:slug` and resume where they left off. Already partially implemented via `sb_session` cookie. Polish the UX: show "Welcome back, pick up where you left off" instead of starting fresh. ### BE changes ```go // Workflow model additions: type WorkflowBranding struct { AccentColor string `json:"accent_color"` LogoURL string `json:"logo_url"` Tagline string `json:"tagline"` WelcomeText string `json:"welcome_text"` // NEW: markdown BackgroundCSS string `json:"background_css"` // NEW: gradient or image URL HidePoweredBy bool `json:"hide_powered_by"` // NEW } // Email notification config (in workflow settings): type WorkflowEmailConfig struct { Enabled bool `json:"enabled"` FromName string `json:"from_name"` OnSubmit string `json:"on_submit_template"` // markdown template OnComplete string `json:"on_complete_template"` // markdown template } ``` Email sending requires an SMTP config at the platform level (admin settings). If not configured, email toggle is disabled in the workflow editor with a hint. ### JSX Exemplar ```jsx /** * VisitorProgress — step indicator for visitor-facing stages * * Mount: workflow surface, above the stage content * Only counts stages where audience = visitor. */ function VisitorProgress({ stages, currentStage }) { // Filter to visitor-facing stages only const visitorStages = stages.filter(s => !s.assignment_team_id); const visitorIndex = visitorStages.findIndex(s => s.ordinal === currentStage); const total = visitorStages.length; if (total <= 1) return null; // Single stage = no progress bar return (
{visitorStages.map((s, i) => (
{i < visitorIndex ? '✓' : i + 1}
{s.name}
))}
); } ``` --- ## v0.38.4 — Workflow Templates + Clone Team admins shouldn't rebuild common patterns from scratch. Two features: ### Clone workflow "Duplicate" button on the workflow editor. Creates a new workflow with the same stages, form templates, and routing rules. New slug, new name (appended "- Copy"). No versions carried over (must re-publish). ``` POST /api/v1/teams/:teamId/workflows/:id/clone → 201 { ...newWorkflow } ``` Pure BE operation — deep-copies workflow + stages, generates new IDs. ### Built-in templates Seed a `workflow_templates` table with 3-5 common patterns: | Template | Stages | Description | |----------|--------|-------------| | Customer Intake | form → review | Public form, team reviews | | IT Help Desk | LLM chat → triage → resolution | AI-assisted intake | | Approval Chain | form → approve → approve → notify | Multi-level approval | | Feedback Survey | form (multi-step) → analytics | Data collection | | Onboarding | form → team A → team B → team A | Multi-team handoff | "New from template" in the team-admin workflow creation flow. Templates are read-only platform data, not user-editable. They serve as starting points — clone and customize. ### FE changes - "Duplicate" button in workflow editor header - "New from Template" option in workflow creation flow - Template picker modal with descriptions --- ## v0.38.5 — Analytics Dashboard Team leads need to answer: how long does each stage take, where are we bottlenecked, what's our SLA compliance rate. ### Data source No new data collection. Everything is derivable from existing columns: - `channels.created_at` — instance start time - `channels.stage_entered_at` — current stage entry - `channels.workflow_status` — terminal state - `workflow_assignments.created_at / claimed_at / completed_at` — assignment lifecycle timestamps ### Metrics | Metric | Derivation | |--------|------------| | Throughput | Count of `completed` instances per time period | | Average cycle time | `completed_at - created_at` across instances | | Stage dwell time | `next_stage_entered_at - stage_entered_at` (requires computing from history) | | SLA compliance | `breached / total` per stage with SLA configured | | Assignment claim time | `claimed_at - created_at` on assignments | | Queue depth over time | Snapshot of unassigned count (requires periodic sampling or derive from events) | ### JSX Exemplar ```jsx /** * WorkflowAnalytics — team-admin analytics tab * * Mount: team-admin workflows surface, "Analytics" tab * Data: GET /api/v1/teams/:teamId/workflows/analytics * ?workflow_id=...&period=7d|30d|90d */ function WorkflowAnalytics({ teamId }) { const [data, setData] = useState(null); const [workflowId, setWorkflowId] = useState(''); const [period, setPeriod] = useState('30d'); // ... load, filter controls ... return (
{/* Stage funnel with dwell times — uses existing funnel endpoint + dwell data */}
); } ``` ### BE changes New endpoint: ``` GET /api/v1/teams/:teamId/workflows/analytics ?workflow_id=...&period=7d|30d|90d ``` Aggregation query over channels + assignments tables. No new tables. Consider materialized views or periodic snapshot if query cost is too high on large datasets (unlikely at 50-user scale). --- ## Changeset Budget | Version | Estimated CS | Heaviest piece | |---------|-------------|----------------| | v0.38.0 | 4 | Form builder component tree (5+ files) | | v0.38.1 | 2 | Drag-and-drop is fiddly but small | | v0.38.2 | 3 | SLA scanner + notification plumbing | | v0.38.3 | 4 | Visitor branding + email + progress | | v0.38.4 | 2 | Clone is one BE endpoint + FE button | | v0.38.5 | 3 | Analytics query + chart components | | **Total** | **~18** | | --- ## Risk Notes **v0.38.0 (Form Builder) is the long pole.** If this slips, the MVP gate phrase "build workflows visually" fails. Prioritize this over everything else in the series. If time is tight, ship a simplified version (no fieldsets, no conditional visibility) and add those in a patch. **v0.38.3 (Email) depends on platform SMTP.** If SMTP isn't configured, the feature is invisible. Consider making the email config a prerequisite admin step with a setup wizard, or defer email to v0.38.5 and keep .3 focused on branding + progress. **v0.38.5 (Analytics) can be cut.** If the series is running long, analytics is the most deferrable item — it's nice-to-have, not gate-blocking. The existing monitor + funnel endpoints cover the critical "is anything stuck" question.