Appearance
Target Abbreviation System
How change operators (ChangeImage, ChangeText, ChangeUrl, ChangeVideo) identify which block or flow component to modify.
Abbreviation Generation
Block abbreviations
Blocks use classNameFromBlockName() from BuilderVisuals/Blocks/utils.ts:
graphicProperties-1 → g1 (first char + trailing digits)
textProperties-2 → t2
buttonProperties-1 → b1
groupProperties-1 → gp1 (special prefix to avoid clash with graphic 'g')Special-case singleton blocks (hardcoded in TargetOpSelector):
formProperties → f1
sliderProperties → sl1
conversationProperties → co1
videoProperties → v1Prefix conflicts
The default prefix is the first character of the block type name. When two types share a first character (e.g., graphicProperties and groupProperties both start with g), a manual override is needed.
Application-Frontend — classNameFromBlockName() in Blocks/utils.ts checks baseType === 'group' and returns 'gp'.
Creative-Engine — Two places handle this:
getBlockPrefix()inutils/blockUtils.ts— used bygetBlockClassNamesObject()for CSS class generationgetBlockClassNames()inmixins/BlockMixin.ts— has aCreativeGroupBlockswitch case that returnsgp{N}class namesAnimationMixin.ts— usesgetBlockPrefix()for animation target matching
CSS text-transform: uppercase on .abbrev-op-name makes these display as G1, T2, etc. — but the stored value is lowercase.
Flow component abbreviations
Flow components use getAbbrevOpName() from OperatorBase.vue:
- For change operators: strips lowercase chars after first char →
changeImage→ci,changeFeedSortingOrder→cfso - For others: uses the component title's first letter(s) + per-type counter
TargetOpSelector Data Flow
blockNameTypeDictionary()Vuex getter → mapsblockName → blockTypefor top-level blocks only (not sub-blocks)- Filters by
changeableBlockTypesprop (e.g.,['graphicProperties']) - Maps through
classNameFromBlockName()(or special cases) to get abbreviations - Combines with
flowComponentAbbreviationsfromoperatorListprop - Groups by type prefix for palette display
Block Data Access
getBlockByName(name)Vuex getter — searches all blocks including sub-blocks (2 levels deep viagetSubBlocks())- GraphicProperties blocks store images at:
block.backgroundSettings.url(withmode: 'image') - VideoProperties blocks store video at:
block.video.streamId(nested undervideoobject, NOT top-levelstreamId)
Important: blockNameTypeDictionary vs getBlockByName scope difference
blockNameTypeDictionary(): only top-level blocks increativeBlocksgetBlockByName(): searches top-level + nested sub-blocks
This means sub-blocks (like graphic elements inside a slider) are findable by name but not listed in the dictionary. The TargetOpSelector relies on the dictionary, so it only shows top-level blocks as targets.
Vue 2 Reactivity Gotcha
targetAbbrevOpName is initialized in OperatorHelper.ts only for changeText operators (line ~172). Other change operators (changeImage, changeUrl, changeVideo) do NOT initialize this property. This means:
- When a new ChangeImage operator is created,
op.properties.body.targetAbbrevOpNamedoes not exist in the blob - The
@changehandler sets it directly:op.properties.body.targetAbbrevOpName = $event - In Vue 2, dynamically added properties are NOT reactive unless added via
Vue.set() - Template bindings still pick up the value on re-render, but computed properties will not re-evaluate
Current Solution (ChangeImageOp)
Use a reactive local data property (selectedTarget) as the source of truth for UI, while still persisting to the blob:
typescript
data() { return { selectedTarget: null } },
created() { this.selectedTarget = this.op.properties.body.targetAbbrevOpName || null },
// In template: bind TargetOpSelector to selectedTarget, NOT the blob property
// :target-abbrev-op-name="selectedTarget" (not .sync on blob)
methods: {
onTargetChange(abbrev) {
this.op.properties.body.targetAbbrevOpName = abbrev // persist
this.selectedTarget = abbrev // drive UI reactively
}
}Important: Do NOT use .sync on the non-reactive blob property — bind directly to the reactive selectedTarget instead. Using .sync="op.properties.body.targetAbbrevOpName" causes the palette to not update when re-selecting targets.
Alternative Workarounds
- Initialize
targetAbbrevOpName = nullin OperatorHelper for all change types that use TargetOpSelector - Use
Vue.set(this.op.properties.body, 'targetAbbrevOpName', $event)in the change handler
Reverse Lookup (abbreviation → block name)
To go from a stored abbreviation (e.g., "g1") back to a block name (e.g., "graphicProperties-1"):
- Get
blockNameTypeDictionary() - Filter by changeable block types
- For each block, compute its abbreviation using the same special-case +
classNameFromBlockName()logic - Match against the stored
targetAbbrevOpName
Flow Graph Traversal (linkList)
Change operators can show a "before → after" preview. When multiple change operators of the same type are chained (e.g., CV1 → CV2), each one's "before" should show the previous change operator's content, not the original block content.
linkList prop
linkList is an array of directional links stored in CavaiFlow.vue:
typescript
{ fromOperator: string, toOperator: string, color: string }Passed through: CavaiFlow.vue → OperatorBase.vue → operator components as a prop.
Backward traversal pattern
Walk backwards from the current operator to find the most recent operator of the same type:
typescript
previousChangeImageUrl() {
let currentOpKey = this.opKey
while (currentOpKey) {
const incoming = this.linkList.find(link => link.toOperator === currentOpKey)
if (!incoming) break
currentOpKey = incoming.fromOperator
if (currentOpKey.startsWith('changeImage')) {
return this.operatorList[currentOpKey]?.properties?.body?.inputText?.value || null
}
}
return null // No previous change operator → fall back to block content
}TargetOpSelector z-index
TargetOpSelector uses position: absolute; bottom: 0; right: 0 and needs z-index: 2 to stay above operator content elements (like ImageUploader which has position: relative).
Key Files
| File | Purpose |
|---|---|
src/pages/Chatbots/components/BuilderVisuals/Blocks/utils.ts | classNameFromBlockName(), idFromBlockName() |
src/pages/Chatbots/components/CavaiFlow/flowactors/OperatorBase.vue | getAbbrevOpName() |
src/pages/Chatbots/components/CavaiFlow/operators/TargetOpSelector/TargetOpSelector.vue | Palette + abbreviation resolution |
src/pages/Chatbots/components/CavaiFlow/OperatorHelper.ts | Operator blob initialization |
src/store/modules/blocks.ts | blockNameTypeDictionary, getBlockByName getters |
src/pages/Chatbots/components/BuilderVisuals/Blocks/data/defaults.ts | Block default structures |