f hardcoding tool-specific workflows, teams should implement a strategy-based router that enforces privacy boundaries, normalizes step metadata, and dispatches to the appropriate distribution channel.
Below is a TypeScript implementation that demonstrates how to architect this layer. It abstracts capture methods, applies redaction rules, and routes outputs based on compliance and format requirements.
// types.ts
export type CaptureStrategy = 'perClick' | 'videoStream' | 'viewportComposite';
export type OutputDestination = 'hostedLink' | 'localFile' | 'videoCDN' | 'interactiveOverlay';
export type PrivacyLevel = 'public' | 'internal' | 'restricted';
export interface CaptureConfig {
strategy: CaptureStrategy;
destination: OutputDestination;
privacy: PrivacyLevel;
stepPrefix?: number;
redactSelectors?: string[];
}
export interface StepMetadata {
id: string;
sequence: number;
elementSelector: string;
coordinates: { x: number; y: number };
aiDescription?: string;
timestamp: number;
}
export interface OutputPayload {
format: 'png' | 'html' | 'mp4' | 'json';
uri: string;
metadata: StepMetadata[];
privacyTag: PrivacyLevel;
}
// router.ts
import { CaptureConfig, StepMetadata, OutputPayload, PrivacyLevel } from './types';
export class DocumentationRouter {
private captureQueue: StepMetadata[] = [];
private config: CaptureConfig;
constructor(config: CaptureConfig) {
this.config = config;
}
public ingestStep(step: StepMetadata): void {
const normalizedSequence = this.config.stepPrefix
? step.sequence + this.config.stepPrefix
: step.sequence;
this.captureQueue.push({
...step,
sequence: normalizedSequence,
aiDescription: step.aiDescription || this.generateFallbackDescription(step)
});
}
public async dispatch(): Promise<OutputPayload> {
const redactedSteps = this.applyRedaction(this.captureQueue);
const payload = await this.routeOutput(redactedSteps);
this.captureQueue = [];
return payload;
}
private applyRedaction(steps: StepMetadata[]): StepMetadata[] {
if (this.config.privacy === 'public' || !this.config.redactSelectors) return steps;
return steps.map(step => {
const isRedacted = this.config.redactSelectors!.some(sel =>
step.elementSelector.includes(sel)
);
return isRedacted ? { ...step, coordinates: { x: 0, y: 0 }, aiDescription: '[REDACTED]' } : step;
});
}
private async routeOutput(steps: StepMetadata[]): Promise<OutputPayload> {
switch (this.config.strategy) {
case 'perClick':
return this.composeHostedGuide(steps);
case 'videoStream':
return this.encodeVideoStream(steps);
case 'viewportComposite':
return this.renderLocalComposite(steps);
default:
throw new Error(`Unsupported capture strategy: ${this.config.strategy}`);
}
}
private async composeHostedGuide(steps: StepMetadata[]): Promise<OutputPayload> {
const assetBundle = steps.map(s => ({
selector: s.elementSelector,
position: s.coordinates,
label: s.aiDescription
}));
const hostedUri = await this.uploadToDocService(assetBundle);
return {
format: 'html',
uri: hostedUri,
metadata: steps,
privacyTag: this.config.privacy
};
}
private async encodeVideoStream(steps: StepMetadata[]): Promise<OutputPayload> {
const timeline = steps.map(s => ({
timecode: s.timestamp,
action: s.elementSelector,
narration: s.aiDescription
}));
const videoUri = await this.submitToEncodingPipeline(timeline);
return {
format: 'mp4',
uri: videoUri,
metadata: steps,
privacyTag: this.config.privacy
};
}
private async renderLocalComposite(steps: StepMetadata[]): Promise<OutputPayload> {
const compositeData = {
viewport: 'current',
annotations: steps.map(s => ({
number: s.sequence,
target: s.coordinates,
label: s.aiDescription
}))
};
const localUri = await this.generatePNG(compositeData);
return {
format: 'png',
uri: localUri,
metadata: steps,
privacyTag: this.config.privacy
};
}
private generateFallbackDescription(step: StepMetadata): string {
return `Interaction at ${step.coordinates.x},${step.coordinates.y} on ${step.elementSelector}`;
}
private async uploadToDocService(bundle: any[]): Promise<string> {
// Simulates server-side composition (Scribe/Tango model)
return `https://docs.internal/guides/${crypto.randomUUID()}`;
}
private async submitToEncodingPipeline(timeline: any[]): Promise<string> {
// Simulates video encoding + AI voice synthesis (Guidde/Floik model)
return `https://cdn.media/videos/${crypto.randomUUID()}`;
}
private async generatePNG(data: any): Promise<string> {
// Simulates client-side canvas rendering (ClickTrek model)
return `local://exports/workflow_${Date.now()}.png`;
}
}
Architecture Decisions & Rationale
- Strategy Pattern for Capture Methods: The router isolates
perClick, videoStream, and viewportComposite into distinct routing branches. This prevents tight coupling between recording logic and distribution logic, allowing teams to swap tools without rewriting downstream pipelines.
- Privacy-First Redaction Middleware: Steps are filtered before routing. Sensitive selectors are masked, coordinates are zeroed, and descriptions are replaced with
[REDACTED]. This mirrors the compliance boundary that local-first tools enforce natively, while giving server-dependent tools a programmatic escape hatch.
- Sequence Continuity Handling: The
stepPrefix parameter solves a common multi-page documentation failure. When workflows span multiple URLs, per-click tools reset numbering. The router normalizes sequences before composition, preserving logical flow across viewport transitions.
- Output Abstraction: Each strategy returns a unified
OutputPayload with format, URI, metadata, and privacy tag. Downstream systems (Confluence, Jira, LMS platforms, or local storage) consume a consistent contract regardless of the capture method.
Pitfall Guide
1. Ignoring Data Residency Requirements
Explanation: Server-side tools upload every frame or video chunk to external infrastructure. Internal dashboards, billing panels, or PII-heavy workflows violate compliance policies when routed through these pipelines.
Fix: Route sensitive workflows through local compositing or implement pre-upload redaction middleware. Audit tool data retention policies before onboarding.
2. Mismatching Capture Granularity to Audience
Explanation: Per-click snapshots work for step-by-step SOPs but overwhelm readers with asset fatigue. Single-page composites preserve spatial context but lack temporal sequencing for complex multi-state interactions.
Fix: Map audience to capture method. Ops teams need sequential granularity. Sales and onboarding benefit from composite visuals or interactive overlays.
3. Over-Indexing on AI Narration Quality
Explanation: AI voice generation (200+ voices/languages) reduces recording friction but introduces latency, pronunciation errors, and cost multipliers. Free tiers typically lock this feature behind paid plans.
Fix: Treat AI narration as an enhancement, not a foundation. Validate generated scripts against technical accuracy before encoding. Budget for per-minute rendering costs at scale.
4. Breaking Step Continuity Across Pages
Explanation: Most tools reset step numbering when the URL changes. Multi-page workflows lose logical flow, forcing readers to mentally reconstruct sequences.
Fix: Use tools that support custom starting numbers or implement a sequence normalizer in your routing layer. Document page boundaries explicitly in step labels.
5. Assuming Free Tiers Support Team Scaling
Explanation: Free tiers cap at 15–25 items or apply watermarks. Teams that onboard without capacity planning hit hard limits during peak documentation cycles.
Fix: Audit monthly workflow volume before tool selection. Reserve free tiers for prototyping. Negotiate team licenses before production rollout.
6. Neglecting Interactive vs Static Handoff Needs
Explanation: Static guides require readers to navigate independently. Interactive overlays (like Tango's Nuggets or Floik's demo mode) guide users in real-time but require app integration and maintenance.
Fix: Choose static for reference documentation. Choose interactive for onboarding and training. Maintain separate pipelines for each to avoid feature bloat.
7. Treating Documentation as Static Artifacts
Explanation: UI changes break step sequences, selectors, and spatial coordinates. Tools that don't version or diff documentation create stale references that erode team trust.
Fix: Implement change detection triggers. Tag documentation with app version numbers. Schedule quarterly audits for high-traffic workflows.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Internal SOPs with strict compliance | Local composite + redaction middleware | Eliminates server egress, preserves spatial context, avoids data residency flags | $19.99 one-time or internal hosting |
| Customer onboarding & in-app guidance | Interactive overlay routing | Guides users live, reduces support tickets, scales with product updates | $22–$39/mo subscription |
| External training & L&D content | Video extraction + AI narration | High engagement, multi-language support, LMS compatibility | $25/mo + rendering costs |
| Sales demos & GTM collateral | Multi-format export pipeline | One capture yields guide, demo, and video; reduces content production time | $39/mo |
| Rapid bug reporting & Jira handoffs | Single PNG composite | Zero friction, universally supported, no account or upload required | Free tier or one-time license |
Configuration Template
// config/documentation-pipeline.config.ts
import { CaptureConfig, PrivacyLevel } from '../types';
export const pipelineConfig: Record<string, CaptureConfig> = {
internalSOP: {
strategy: 'viewportComposite',
destination: 'localFile',
privacy: 'restricted',
redactSelectors: ['.user-id', '.billing-info', '.admin-panel']
},
customerOnboarding: {
strategy: 'perClick',
destination: 'interactiveOverlay',
privacy: 'public',
stepPrefix: 0
},
trainingVideo: {
strategy: 'videoStream',
destination: 'videoCDN',
privacy: 'public',
redactSelectors: []
},
salesDemo: {
strategy: 'videoStream',
destination: 'multiFormat',
privacy: 'internal',
redactSelectors: ['.pricing-tier', '.internal-metrics']
}
};
export function getWorkflowConfig(workflowType: string): CaptureConfig {
const config = pipelineConfig[workflowType];
if (!config) {
throw new Error(`Unknown workflow type: ${workflowType}`);
}
return config;
}
Quick Start Guide
- Define Workflow Categories: Classify your documentation needs into internal SOPs, customer onboarding, training, or sales collateral. Map each to a capture strategy using the decision matrix.
- Initialize the Router: Import the
DocumentationRouter and pass the appropriate CaptureConfig for your workflow type. Set stepPrefix if documenting multi-page processes.
- Configure Privacy Boundaries: Add CSS selectors or DOM patterns to
redactSelectors for restricted workflows. Verify that local compositing routes bypass external upload endpoints.
- Dispatch & Validate: Call
dispatch() after capturing interactions. Inspect the returned OutputPayload to confirm format, URI, and privacy tag match expectations. Route to your distribution channel (Confluence, Jira, CDN, or local storage).
- Monitor & Iterate: Track asset volume against free-tier limits. Schedule quarterly reviews to update selectors, redaction rules, and step sequences as the application evolves.