Appearance
JumpTo Operator & Flow Fast-Forward Implementation Plan
For agentic workers: REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Add a JumpTo operator that jumps to any flow operator, then build a "content-aware first" mode that fast-forwards through the flow accumulating all state (CSS, JS, ShowHide, etc.) instead of skipping it.
Architecture: Two features sharing infrastructure. Phase 1 adds the JumpTo operator (new functional operator with target palette). Phase 2 adds a fast-forward mode to startingComponent that replays all operators up to the "first" operator instantly, skipping delays and auto-resolving choices. Both use emitProgression(targetStepId) to land on the target step.
Tech Stack: Vue 2 (Options API), TypeScript, Vuex, Creative-Engine (vanilla TS), Creative-Composer (vanilla TS)
File Structure
Phase 1: JumpTo Operator
| Action | File | Repo | Responsibility |
|---|---|---|---|
| Create | src/components/blocks/functional/JumpTo.ts | Creative-Engine | Runtime: reset state + jump to target step |
| Create | src/pages/Chatbots/components/CavaiFlow/operators/JumpToOp.vue | Application-Frontend | Builder UI: target selector palette for picking destination |
| Modify | src/utils/funcBlocks.ts | Creative-Engine | Register JumpTo in the functional blocks map |
| Modify | src/utils/_temp_buildercomponents.ts | Application-Frontend | Add to COMPONENTLIST, switchComponents |
| Modify | src/pages/Chatbots/components/CavaiFlow/flowactors/OperatorBase.vue | Application-Frontend | Import + register JumpToOp component |
| Modify | src/pages/Chatbots/components/CavaiFlow/OperatorHelper.ts | Application-Frontend | Initialize jumpTarget in operator props |
| Modify | src/remapper/remapData.ts | Creative-Composer | Map body.jumpTarget to comp.payload.jumpTarget |
| Modify | src/assets/i18n/en.js | Application-Frontend | i18n keys for JumpTo |
Phase 2: Flow Fast-Forward ("Content-Aware First")
| Action | File | Repo | Responsibility |
|---|---|---|---|
| Create | src/logic-system/processors/flowFastForward.ts | Creative-Engine | Fast-forward engine: replay operators instantly to accumulate state |
| Modify | src/logic-system/processors/conversationFlow.ts | Creative-Engine | Add fast-forward entry point, integrate with startflow |
| Modify | src/pages/Chatbots/components/CavaiFlow/CavaiFlow.vue | Application-Frontend | UI toggle for content-aware vs fresh-start first |
| Modify | src/pages/Chatbots/components/CavaiFlow/DataHelper.ts | Application-Frontend | Always include all operators in step mapping when fast-forward is on |
| Modify | src/remapper/computeComponents.ts | Creative-Composer | Pass fast-forward metadata to engine |
Phase 1: JumpTo Operator
Task 1: Engine - Create JumpTo functional block
Files:
- Create:
Creative-Engine/src/components/blocks/functional/JumpTo.ts - Modify:
Creative-Engine/src/utils/funcBlocks.ts
This is modeled after Restart.ts but uses emitProgression(targetStepId) instead of emitStartFlow().
Key design decision: JumpTo resets visual state (like Restart) before jumping. This is the safe default. If we later want a "soft jump" that preserves state, we add a boolean flag.
- [ ] Step 1: Create JumpTo.ts
typescript
// Creative-Engine/src/components/blocks/functional/JumpTo.ts
import eventEmitter from '@/logic-system/eventEmitter'
import { ConversationFlow } from '@/logic-system/processors/conversationFlow'
import { LogicManager } from '@/logic-system/logicManager'
import { DimensionManager } from '@/style-engine/dimensionmanager/dimensionManager'
import { DataStore } from '@/services/dataStore'
import { resetCreative } from './Script'
import { nextTick } from 'vue'
import type { FlowComponentInterface } from '@/interfaces/components/FlowComponent/FlowComponentInterface'
export const JumpTo = {
init(componentData: FlowComponentInterface[]) {
const targetStepId = componentData[0]?.payload?.jumpTarget
if (!targetStepId) return
const fadeDuration = 120
const bufferTimeout = 10
const container = document.getElementById('creative-container')
const baseBlock = container?.querySelector('[class*="base-block"]') as HTMLElement | null
// Fade out children of base-block (same pattern as Restart)
if (baseBlock) {
for (const child of Array.from(baseBlock.children) as HTMLElement[]) {
child.style.transition = `opacity ${fadeDuration}ms ease`
child.style.opacity = '0'
}
}
setTimeout(() => {
resetCreative()
DataStore.restartCount.value++
DataStore.runtimeHidden.value = {}
DataStore.currentBgClickUrl.value = ''
DataStore.currentBgImageUrl.value = ''
DataStore.videoState.shouldRestart.value = true
ConversationFlow.flowQueue = []
DataStore.liveFlow.length = 0
DimensionManager.triggerEvent('update-scrollcontent-minht', { reset: true })
DataStore.isRestarting = true
void nextTick(() => {
if (baseBlock) {
for (const child of Array.from(baseBlock.children) as HTMLElement[]) {
child.style.opacity = '0'
}
}
setTimeout(() => {
ConversationFlow.currStep = 0
DataStore.isRestarting = false
ConversationFlow.currSubscription = {
ticker: false,
dom: false,
components: true,
consent: true,
textinput: true,
}
LogicManager.subscribeEvents(
'process:conversationflow',
ConversationFlow.currSubscription,
)
// Key difference from Restart: jump to target step, not start
eventEmitter.emitProgression(Number(targetStepId))
DataStore.videoState.shouldRestart.value = false
requestAnimationFrame(() => {
if (baseBlock) {
for (const child of Array.from(baseBlock.children) as HTMLElement[]) {
child.style.transition = `opacity ${fadeDuration}ms ease`
child.style.opacity = '1'
setTimeout(() => {
child.style.transition = ''
}, fadeDuration)
}
}
})
}, bufferTimeout)
})
}, fadeDuration)
},
}- [ ] Step 2: Register in funcBlocks.ts
Add to Creative-Engine/src/utils/funcBlocks.ts:
typescript
// #include when blocks have "JumpTo"
import { JumpTo } from '@/components/blocks/functional/JumpTo'
// #end
// In the funcBlocks map:
JumpTo: JumpTo.init,- [ ] Step 3: Verify engine builds
Run: cd Creative-Engine && npm run build Expected: Build succeeds
- [ ] Step 4: Commit
bash
git add src/components/blocks/functional/JumpTo.ts src/utils/funcBlocks.ts
git commit -m "feat: add JumpTo functional block to engine"Task 2: Composer - Map JumpTo payload
Files:
- Modify:
Creative-Composer/src/remapper/remapData.ts
The composer auto-resolves jumpTo -> JumpTo via getComponentType() default case (capitalize first letter). And getBlockType() will return 'functional' since jumpTo is not in NON_FUNC_COMPONENTS. So we only need to map the payload.
- [ ] Step 1: Add jumpTo mapping to remapData.ts
In Creative-Composer/src/remapper/remapData.ts, add after the showHide mapping (line ~192):
typescript
} else if (opKey.includes('jumpTo') && body.jumpTarget) {
comp.payload.jumpTarget = body.jumpTarget
}- [ ] Step 2: Verify composer builds
Run: cd Creative-Composer && npm run build Expected: Build succeeds
- [ ] Step 3: Commit
bash
git add src/remapper/remapData.ts
git commit -m "feat: map jumpTo payload in composer"Task 3: Frontend - Create JumpToOp builder component
Files:
- Create:
Application-Frontend/src/pages/Chatbots/components/CavaiFlow/operators/JumpToOp.vue
The JumpToOp component reuses TargetOpSelector with changeableComponentTypes set to all flow operator types (statements, answers, links, etc.) and changeableBlockTypes: [] (no blocks, only flow operators).
- [ ] Step 1: Create JumpToOp.vue
vue
<template>
<div class="jump-to-op" @mouseover="setParentDrag(false)" @mouseleave="setParentDrag(true)">
<TargetOpSelector
:target-abbrev-op-name="op.properties.body.jumpTarget"
:operator-list="operatorList"
:changeable-component-types="changeableComponentTypes"
:changeable-block-types="[]"
@change="onTargetChange"
/>
<div class="jump-to-body">
<Icon icon="forward" :size="20" />
<span class="jump-to-label">{{ $t('flow.jumpTo.label') }}</span>
<span v-if="op.properties.body.jumpTarget" class="jump-to-target">
{{ op.properties.body.jumpTarget }}
</span>
<span v-else class="jump-to-placeholder">
{{ $t('flow.jumpTo.noTarget') }}
</span>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import Icon from '@/components/common/Icon.vue'
import TargetOpSelector from './TargetOpSelector/TargetOpSelector.vue'
export default Vue.extend({
name: 'JumpToOp',
components: { Icon, TargetOpSelector },
props: {
op: {
type: Object,
default() {
return {}
},
},
opKey: {
type: String,
default: '',
},
operatorList: {
type: Object,
default() {
return {}
},
},
linkList: {
type: Array,
default() {
return []
},
},
},
data() {
return {
changeableComponentTypes: [
'statement',
'answer',
'link',
'image',
'slider',
'shop',
'text_input',
'video',
'delay',
'tag',
'css',
'showHide',
'restart',
'reset',
'changeText',
'changeImage',
'changeUrl',
'changeVideo',
],
}
},
methods: {
onTargetChange(target: string) {
this.$set(this.op.properties.body, 'jumpTarget', target)
},
setParentDrag(val: boolean) {
this.$parent.setDraggable(val)
},
validateInput() {
if (!this.op.properties.body.jumpTarget) {
return 'Jump To operator must have a target selected'
}
return ''
},
},
})
</script>
<style lang="scss" scoped>
.jump-to-op {
position: relative;
}
.jump-to-body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
padding: 8px;
color: $illustration-minus4;
}
.jump-to-label {
font-size: 12px;
font-weight: 400;
line-height: 1.2;
}
.jump-to-target {
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
color: $primary-default;
}
.jump-to-placeholder {
font-size: 11px;
font-style: italic;
color: $secondary-font-color;
}
</style>- [ ] Step 2: Add i18n keys
In Application-Frontend/src/assets/i18n/en.js, add under the flow section (or create it if needed):
javascript
flow: {
jumpTo: {
label: 'Jump To',
noTarget: 'No target',
},
},- [ ] Step 3: Commit
bash
git add src/pages/Chatbots/components/CavaiFlow/operators/JumpToOp.vue src/assets/i18n/en.js
git commit -m "feat: add JumpToOp builder component with target palette"Task 4: Frontend - Register JumpTo operator
Files:
Modify:
Application-Frontend/src/utils/_temp_buildercomponents.tsModify:
Application-Frontend/src/pages/Chatbots/components/CavaiFlow/flowactors/OperatorBase.vueModify:
Application-Frontend/src/pages/Chatbots/components/CavaiFlow/OperatorHelper.ts[ ] Step 1: Add to COMPONENTLIST in _temp_buildercomponents.ts
typescript
// After the Restart entry in COMPONENTLIST:
{ title: 'Jump To', type: 'jumpTo', apiKey: 'JumpTo' },- [ ] Step 2: Add to switchComponents
typescript
export const switchComponents = ['reset', 'delay', 'tag', 'css', 'resetChanges', 'showHide', 'restart', 'jumpTo', 'libraryScript']
export const switchComponentsTitles = ['Wipe', 'Delay', 'JS', 'CSS', 'Reset', 'Show/Hide', 'Restart', 'Jump To', 'Library Script']Note: Do NOT add to FIRSTCOMPONENTLIST (JumpTo as first operator makes no sense).
- [ ] Step 3: Import and register in OperatorBase.vue
Add import:
typescript
import JumpToOp from '../operators/JumpToOp.vue'Add to components object:
typescript
components: {
// ... existing
JumpToOp,
}- [ ] Step 4: Initialize jumpTarget in OperatorHelper.ts
In getOperatorProps(), add:
typescript
} else if (opType === 'jumpTo') {
props.body.jumpTarget = null
}- [ ] Step 5: Verify frontend builds
Run: cd Application-Frontend && npm run build Expected: Build succeeds
- [ ] Step 6: Commit
bash
git add src/utils/_temp_buildercomponents.ts \
src/pages/Chatbots/components/CavaiFlow/flowactors/OperatorBase.vue \
src/pages/Chatbots/components/CavaiFlow/OperatorHelper.ts
git commit -m "feat: register JumpTo operator in builder"Task 5: Manual test - JumpTo end-to-end
- [ ] Step 1: Create a test creative with a flow
Build a simple flow: Message -> Choice -> Message (branch A) / Message (branch B), add a JumpTo after branch A that targets the first Message.
- [ ] Step 2: Verify the target palette shows only flow operators
Open the JumpTo operator, click the target palette. Should show abbreviations for all operators in the flow (s1, a1, s2, s3, etc.) but no block abbreviations.
- [ ] Step 3: Preview and verify the jump works
Preview the creative, go through branch A, verify that the JumpTo resets and lands on the first Message.
- [ ] Step 4: Verify save/load roundtrip
Save the creative, reload, verify the JumpTo operator still has its target set.
Phase 2: Flow Fast-Forward ("Content-Aware First")
Note: Phase 2 depends on Phase 1 being complete and tested. The fast-forward feature is more complex and can be a separate branch.
Task 6: Engine - Create flowFastForward processor
Files:
- Create:
Creative-Engine/src/logic-system/processors/flowFastForward.ts
This is the core of the feature. It replays all operators from step 1 up to a target step, executing them instantly:
Functional operators (CSS, JS, ShowHide, Change*) execute normally (they're already synchronous)
Delay operators are skipped (customDelay = 0)
Visual operators (Message, Choice, etc.) are skipped but their side effects (animation triggers) are noted
Choice operators: auto-resolve by finding which answer leads toward the target step
[ ] Step 1: Create flowFastForward.ts
typescript
// Creative-Engine/src/logic-system/processors/flowFastForward.ts
import { DataStore } from '@/services/dataStore'
import { funcBlocks } from '@/utils/funcBlocks'
import type { FlowComponentInterface } from '@/interfaces/components/FlowComponent/FlowComponentInterface'
/**
* Finds a path from startStep to targetStep through the component graph.
* Returns an ordered array of step IDs, or null if no path exists.
*/
function findPathToTarget(
components: Record<number, FlowComponentInterface[]>,
startStep: number,
targetStep: number,
): number[] | null {
const visited = new Set<number>()
const queue: { step: number, path: number[] }[] = [
{ step: startStep, path: [startStep] },
]
while (queue.length > 0) {
const { step, path } = queue.shift()!
if (step === targetStep) return path
if (visited.has(step)) continue
visited.add(step)
const compSet = components[step]
if (!compSet || compSet.length === 0) continue
// Collect all possible next steps
const nextSteps = new Set<number>()
for (const comp of compSet) {
if (comp.nextStepId && comp.nextStepId > 0) {
nextSteps.add(comp.nextStepId)
}
}
for (const next of nextSteps) {
if (!visited.has(next)) {
queue.push({ step: next, path: [...path, next] })
}
}
}
return null
}
/**
* Fast-forwards through the flow from step 1 to targetStep,
* executing all functional operators instantly and skipping
* visual operators and delays.
*
* Returns true if fast-forward succeeded, false if path not found.
*/
export function fastForwardToStep(targetStep: number): boolean {
const components = DataStore.componentData.components
const path = findPathToTarget(components, 1, targetStep)
if (!path) {
console.warn('[flowFastForward] No path found to target step', targetStep)
return false
}
// Execute each step along the path (except the target itself)
for (const step of path) {
if (step === targetStep) break
const compSet = components[step]
if (!compSet || compSet.length === 0) continue
const firstComp = compSet[0]
// Only execute functional operators
if (firstComp.blockType === 'functional') {
// Skip delays entirely
if (firstComp.type === 'Delay') continue
// Skip Restart/JumpTo (would cause loops)
if (firstComp.type === 'Restart' || firstComp.type === 'JumpTo') continue
// Execute the functional operator
const handler = funcBlocks[firstComp.type]
if (handler) {
handler(compSet)
DataStore.customDelay = 0 // Reset any delay set by the operator
}
}
// Visual operators: skip (don't add to liveFlow)
}
return true
}- [ ] Step 2: Commit
bash
git add src/logic-system/processors/flowFastForward.ts
git commit -m "feat: add flowFastForward processor for content-aware first"Task 7: Engine - Integrate fast-forward with conversationFlow
Files:
- Modify:
Creative-Engine/src/logic-system/processors/conversationFlow.ts
When the flow starts and a fast-forward target is set, run the fast-forward processor before beginning normal flow execution.
- [ ] Step 1: Add fast-forward integration to conversationFlow.ts
In handleComponentsEvent, modify the startflow case:
typescript
case 'startflow':
if (DataStore.fastForwardTarget?.value) {
const targetStep = DataStore.fastForwardTarget.value
DataStore.fastForwardTarget.value = 0 // Clear to prevent re-triggering
const success = fastForwardToStep(targetStep)
if (success) {
// Jump directly to the target step
this.currStep = targetStep - 1 // -1 because addComponentsToFlow increments
this.addComponentsToFlow({})
break
}
}
this.addComponentsToFlow({})
breakThis requires adding fastForwardTarget to DataStore (a ref(0) reactive value). When set to a non-zero step ID, the flow will fast-forward to that step on start.
- [ ] Step 2: Add fastForwardTarget to DataStore
Add to DataStore's reactive refs:
typescript
fastForwardTarget: ref(0),- [ ] Step 3: Commit
bash
git add src/logic-system/processors/conversationFlow.ts src/services/dataStore.ts
git commit -m "feat: integrate fast-forward with conversation flow startup"Task 8: Frontend + Composer - Add content-aware first UI and data path
Files:
- Modify:
Application-Frontend/src/pages/Chatbots/components/CavaiFlow/CavaiFlow.vue - Modify:
Application-Frontend/src/pages/Chatbots/components/CavaiFlow/DataHelper.ts - Modify:
Creative-Composer/src/remapper/computeComponents.ts
This task adds a toggle in the "Make First" context menu or operator properties that switches between "Fresh Start" (current behavior) and "Content-Aware" (fast-forward) mode.
- [ ] Step 1: Add
firstModeproperty to operator data
In CavaiFlow.vue, modify the makeFirst() method to also set a firstMode property:
typescript
// On the operator marked as first:
Vue.set(props, 'firstMode', props.firstMode || 'fresh')
// 'fresh' = current behavior (skip everything before)
// 'contentAware' = fast-forward through everything before- [ ] Step 2: Add context menu option to toggle firstMode
In the context menu (where "Make First" appears), add a sub-option:
- "Fresh Start" (default, current behavior)
- "Content-Aware" (fast-forward mode)
Only visible when the operator is already the starting component.
- [ ] Step 3: Modify DataHelper.mapStepsAndIDs for content-aware mode
When firstMode === 'contentAware', the step mapping should:
- Start from the actual first operator in the flow (the topmost connected operator), NOT from the
startingComponent - Mark the
startingComponent's step ID as thefastForwardTarget - Pass this metadata through to the composer/engine
typescript
// In mapStepsAndIDs:
const startOp = opKeyList.find((el) => opList[el].properties.startingComponent === 'true')
const firstMode = opList[startOp]?.properties.firstMode || 'fresh'
if (firstMode === 'contentAware') {
// Find the true root operator (first operator that nothing links TO, or lowest curStepId)
// Map from the root, but mark startOp's step as fastForwardTarget
// ... (implementation depends on how root is determined)
}- [ ] Step 4: Pass fastForwardTarget through composer to engine
In computeComponents.ts, when firstMode === 'contentAware', set a metadata field that the engine reads during initialization to populate DataStore.fastForwardTarget.
- [ ] Step 5: Add i18n keys
javascript
flow: {
firstMode: {
fresh: 'Fresh Start',
contentAware: 'Content-Aware',
},
},- [ ] Step 6: Commit
bash
git add -A
git commit -m "feat: add content-aware first mode with UI toggle"Task 9: Manual test - Fast-forward end-to-end
- [ ] Step 1: Create a test creative
Build a flow with CSS operators early on that move elements, then set "first" on an operator later in the flow.
- [ ] Step 2: Test fresh start mode
Set first mode to "Fresh Start". Preview. Verify elements are in their default positions (CSS not applied). This is the current behavior.
- [ ] Step 3: Test content-aware mode
Toggle to "Content-Aware". Preview. Verify:
CSS changes from before the first operator are applied
ShowHide states are correct
No delays are visible
The flow starts from the first operator with correct accumulated state
[ ] Step 4: Test with choices in the path
Build a flow with a Choice operator before the "first" operator. Verify the pathfinding auto-resolves through the correct answer to reach the first operator.
Open Questions (to resolve during implementation)
Naming: "Content-Aware" vs "Fresh Start"? Or something else? This is a UX decision. Options:
- "Content-Aware" / "Fresh Start"
- "With History" / "Clean Start"
- "Resume" / "Skip"
- Could also be a single toggle: "Apply prior state" checkbox
Choice auto-resolution: When multiple paths lead to the target, which one to take? Options:
- First path found (BFS - shortest path)
- Always take position 1 (first answer)
- Let the user specify which answer to auto-select (most flexible, most complex)
Should JumpTo support soft-jump? (Jump without resetting state). This would be a separate property on the JumpTo operator. Defer to Phase 3 if needed.
OnEvent operators: When the OnEvent system is eventually implemented, fast-forward will need to handle event subscriptions that would have been registered during fast-forwarded steps. Not a concern now, but worth noting.