// ========================================== // Switchboard Core — Workflow Demo Surface // ========================================== // Interactive walkthrough of the four example workflow packages. // Shows workflow cards with feature badges, stage flow diagrams, // Starlark code viewer, and API curl examples. // ========================================== (function () { 'use strict'; const SURFACE_ID = 'workflow-demo'; if (window.__SURFACE__ !== SURFACE_ID) return; const base = window.__BASE__ || ''; // ── Workflow catalog ────────────────────────── const WORKFLOWS = [ { slug: 'bug-report-triage', title: 'Bug Report Triage', icon: '🐛', description: 'Public bug report submission with severity-based routing, team assignment, and SLA timers.', badges: ['Public Entry', 'Branch Rules', 'SLA Timer', 'Team Assignment'], tier: 'browser', stages: [ { name: 'submit', mode: 'form', audience: 'public' }, { name: 'classify', mode: 'form', audience: 'team', note: 'branch rules' }, { name: 'fix-critical', mode: 'form', audience: 'team', note: 'SLA 1h', branch: true }, { name: 'fix-normal', mode: 'form', audience: 'team', branch: true }, { name: 'verify', mode: 'review', audience: 'team' }, ], curl: [ '# 1. Start public instance (no auth required)\ncurl -X POST $BASE/api/v1/public/workflows/$WF_ID/start \\\n -H "Content-Type: application/json" \\\n -d \'{"data": {"reporter_email": "user@example.com", "summary": "Login broken", "component": "ui", "severity": "critical", "steps": "1. Click login", "expected": "Login page", "actual": "500 error"}}\'', '# 2. Resume (check status)\ncurl $BASE/api/v1/public/workflows/resume/$TOKEN', '# 3. Team member claims classify assignment\ncurl -X POST $BASE/api/v1/teams/$TEAM/assignments/$ASSIGN_ID/claim \\\n -H "Authorization: Bearer $TOKEN"', '# 4. Advance classify (triggers branch rule)\ncurl -X POST $BASE/api/v1/teams/$TEAM/instances/$INST/advance \\\n -H "Authorization: Bearer $TOKEN" \\\n -H "Content-Type: application/json" \\\n -d \'{"data": {"severity": "critical", "notes": "Confirmed production issue"}}\'', ], }, { slug: 'employee-onboarding', title: 'Employee Onboarding', icon: '🏢', description: 'Automated provisioning with Starlark hooks, manager signoff gate, and welcome notification.', badges: ['Starlark', 'Signoff Gate', 'Rejection Reroute', 'db.insert', 'notifications.send'], tier: 'starlark', stages: [ { name: 'new-hire-info', mode: 'form', audience: 'team' }, { name: 'provision-accounts', mode: 'automated', audience: 'system', note: 'Starlark' }, { name: 'manager-signoff', mode: 'review', audience: 'team', note: 'signoff gate' }, { name: 'it-setup', mode: 'form', audience: 'team' }, { name: 'welcome', mode: 'automated', audience: 'system', note: 'Starlark' }, ], starlark: 'on_provision(ctx): db.insert("provisions", ...), notifications.send(...)\non_welcome(ctx): db.insert("onboarding_log", ...), notifications.send(...)', curl: [ '# 1. Start instance (team auth required)\ncurl -X POST $BASE/api/v1/teams/$TEAM/workflows/$WF_ID/start \\\n -H "Authorization: Bearer $TOKEN" \\\n -H "Content-Type: application/json" \\\n -d \'{"data": {"full_name": "Alice Smith", "department": "engineering", "needs_vpn": "true", "needs_admin": "false", "equipment": "developer workstation"}}\'', '# 2. After auto-provision, submit manager signoff\ncurl -X POST $BASE/api/v1/instances/$INST/signoffs \\\n -H "Authorization: Bearer $MANAGER_TOKEN" \\\n -H "Content-Type: application/json" \\\n -d \'{"action": "approve"}\'', ], }, { slug: 'content-approval', title: 'Content Approval', icon: '📝', description: 'Multi-party review with quorum signoff (2 approvals) and revision cycle loop.', badges: ['Multi-party Signoff', 'Quorum', 'Rejection Reroute', 'Review Cycle'], tier: 'browser', stages: [ { name: 'draft', mode: 'form', audience: 'team' }, { name: 'review', mode: 'review', audience: 'team', note: '2 approvals' }, { name: 'revision', mode: 'form', audience: 'team', note: 'loops to review' }, { name: 'publish', mode: 'form', audience: 'team' }, ], curl: [ '# 1. Submit draft\ncurl -X POST $BASE/api/v1/teams/$TEAM/workflows/$WF_ID/start \\\n -H "Authorization: Bearer $TOKEN" \\\n -H "Content-Type: application/json" \\\n -d \'{"data": {"title": "Q1 Update", "body": "...", "category": "blog"}}\'', '# 2. Reviewer A approves\ncurl -X POST $BASE/api/v1/instances/$INST/signoffs \\\n -H "Authorization: Bearer $REVIEWER_A" \\\n -d \'{"action": "approve"}\'', '# 3. Reviewer B rejects -> routes to revision\ncurl -X POST $BASE/api/v1/instances/$INST/signoffs \\\n -H "Authorization: Bearer $REVIEWER_B" \\\n -d \'{"action": "reject"}\'', ], }, { slug: 'webhook-notifier', title: 'Webhook Notifier', icon: '🔔', description: 'Outbound HTTP delivery via Starlark with connection credentials and delivery logging.', badges: ['Starlark', 'http.post', 'connections.get', 'db.insert'], tier: 'starlark', stages: [ { name: 'configure', mode: 'form', audience: 'team' }, { name: 'fire-webhook', mode: 'automated', audience: 'system', note: 'http.post' }, { name: 'result', mode: 'review', audience: 'team' }, ], starlark: 'on_fire(ctx): http.post(url, body=payload), db.insert("webhook_log", ...)', curl: [ '# 1. Configure and start\ncurl -X POST $BASE/api/v1/teams/$TEAM/workflows/$WF_ID/start \\\n -H "Authorization: Bearer $TOKEN" \\\n -H "Content-Type: application/json" \\\n -d \'{"data": {"webhook_url": "https://webhook.site/test", "event_name": "deploy.complete", "payload": "{}"}}\'', ], }, ]; // ── Init ───────────────────────────────────── async function _init() { const mount = document.getElementById('extension-mount'); if (!mount) return; const surface = document.createElement('div'); surface.className = 'surface-workflow-demo'; mount.appendChild(surface); // Topbar with user menu const topbar = document.createElement('div'); topbar.className = 'demo-topbar'; topbar.innerHTML = '

Workflow Demo

'; surface.appendChild(topbar); sw.userMenu(topbar, { flyout: 'down' }); // Subtitle const subtitle = document.createElement('p'); subtitle.className = 'demo-subtitle'; subtitle.textContent = 'Four example workflows that prove the engine works end-to-end. Install any package via Admin > Packages.'; surface.appendChild(subtitle); // Workflow cards const grid = document.createElement('div'); grid.className = 'demo-grid'; surface.appendChild(grid); // Fetch installed workflows to check status // The API returns { data: [...], total, page } — _unwrap keeps the // full object when siblings exist, so extract .data ourselves. let installed = {}; try { const resp = await sw.api.get('/api/v1/workflows'); const list = Array.isArray(resp) ? resp : (resp && resp.data ? resp.data : []); for (const wf of list) { if (wf.slug) installed[wf.slug] = wf; } } catch (e) { /* ignore — show all as not-installed */ } for (const wf of WORKFLOWS) { grid.appendChild(_buildCard(wf, installed[wf.slug])); } } // ── Card builder ───────────────────────────── function _buildCard(wf, installedWf) { const card = document.createElement('div'); card.className = 'demo-card'; // Title row const titleRow = document.createElement('div'); titleRow.className = 'card-title-row'; titleRow.innerHTML = '' + wf.icon + '' + '

' + _esc(wf.title) + '

' + '' + wf.tier + ''; card.appendChild(titleRow); // Description const desc = document.createElement('p'); desc.className = 'card-desc'; desc.textContent = wf.description; card.appendChild(desc); // Badges const badges = document.createElement('div'); badges.className = 'card-badges'; for (const b of wf.badges) { const span = document.createElement('span'); span.className = 'badge'; span.textContent = b; badges.appendChild(span); } card.appendChild(badges); // Stage flow diagram card.appendChild(_buildStageDiagram(wf)); // Starlark viewer (if applicable) if (wf.starlark) { card.appendChild(_buildCollapsible('Starlark Hooks', '
' + _esc(wf.starlark) + '
')); } // API examples if (wf.curl && wf.curl.length) { const curlHtml = wf.curl.map(function (c) { return '
' + _esc(c) + '
'; }).join(''); card.appendChild(_buildCollapsible('API Examples', curlHtml)); } // Status + action const actions = document.createElement('div'); actions.className = 'card-actions'; if (installedWf) { const isActive = installedWf.is_active; const version = installedWf.version || 0; // version > 1 means published at least once (version increments on edit, publish snapshots it) const isPublished = version >= 2; const isReady = isActive && isPublished; // Installed badge const status = document.createElement('span'); status.className = 'badge badge-installed'; status.textContent = 'Installed'; actions.appendChild(status); if (!isActive) { const hint = document.createElement('span'); hint.className = 'badge badge-needs-publish'; hint.textContent = 'Not Active'; actions.appendChild(hint); } else if (!isPublished) { const hint = document.createElement('span'); hint.className = 'badge badge-needs-publish'; hint.textContent = 'Not Published'; actions.appendChild(hint); } const tryBtn = document.createElement('button'); tryBtn.className = 'btn btn-primary'; tryBtn.textContent = 'Try It'; tryBtn.disabled = !isReady; if (!isReady) { tryBtn.title = 'Activate and publish this workflow in Team Admin first'; } tryBtn.onclick = function () { if (!isReady) return; if (wf.slug === 'bug-report-triage') { window.open(base + '/api/v1/public/workflows/' + installedWf.id + '/form', '_blank'); } else { sw.toast('Navigate to your team admin to start a ' + wf.title + ' instance.', 'info'); } }; actions.appendChild(tryBtn); // Public link for public_link workflows if (installedWf.entry_mode === 'public_link' && isReady) { const linkRow = document.createElement('div'); linkRow.className = 'public-link-row'; const linkInput = document.createElement('input'); linkInput.type = 'text'; linkInput.readOnly = true; linkInput.value = window.location.origin + base + '/api/v1/public/workflows/' + installedWf.id + '/start'; linkRow.appendChild(linkInput); const copyBtn = document.createElement('button'); copyBtn.className = 'btn-copy'; copyBtn.textContent = 'Copy'; copyBtn.onclick = function () { navigator.clipboard.writeText(linkInput.value).then(function () { copyBtn.textContent = 'Copied!'; setTimeout(function () { copyBtn.textContent = 'Copy'; }, 1500); }); }; linkRow.appendChild(copyBtn); card.appendChild(linkRow); } } else { const status = document.createElement('span'); status.className = 'badge badge-not-installed'; status.textContent = 'Not Installed'; actions.appendChild(status); } card.appendChild(actions); return card; } // ── Stage flow diagram ─────────────────────── function _buildStageDiagram(wf) { const container = document.createElement('div'); container.className = 'stage-flow'; const stages = wf.stages; let hasBranch = false; for (let i = 0; i < stages.length; i++) { const s = stages[i]; const node = document.createElement('div'); node.className = 'stage-node stage-' + s.audience; const label = document.createElement('div'); label.className = 'stage-label'; label.textContent = s.name; node.appendChild(label); const meta = document.createElement('div'); meta.className = 'stage-meta'; meta.textContent = s.mode + (s.audience !== 'team' ? ' (' + s.audience + ')' : ''); node.appendChild(meta); if (s.note) { const note = document.createElement('div'); note.className = 'stage-note'; note.textContent = s.note; node.appendChild(note); } container.appendChild(node); // Arrow (except after last stage) if (i < stages.length - 1 && !stages[i + 1].branch) { const arrow = document.createElement('div'); arrow.className = 'stage-arrow'; arrow.textContent = '\u2192'; container.appendChild(arrow); } else if (stages[i + 1] && stages[i + 1].branch) { if (!hasBranch) { const split = document.createElement('div'); split.className = 'stage-arrow stage-branch'; split.textContent = '\u2193\u2197'; container.appendChild(split); hasBranch = true; } } } return container; } // ── Collapsible section ────────────────────── function _buildCollapsible(title, innerHtml) { const details = document.createElement('details'); details.className = 'card-collapsible'; const summary = document.createElement('summary'); summary.textContent = title; details.appendChild(summary); const content = document.createElement('div'); content.className = 'collapsible-content'; content.innerHTML = innerHtml; details.appendChild(content); return details; } // ── Helpers ─────────────────────────────────── function _esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; } // ── Boot ────────────────────────────────────── if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', _init); } else { _init(); } })();