Skip to content

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        → v1

Prefix 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-FrontendclassNameFromBlockName() in Blocks/utils.ts checks baseType === 'group' and returns 'gp'.

Creative-Engine — Two places handle this:

  1. getBlockPrefix() in utils/blockUtils.ts — used by getBlockClassNamesObject() for CSS class generation
  2. getBlockClassNames() in mixins/BlockMixin.ts — has a CreativeGroupBlock switch case that returns gp{N} class names
  3. AnimationMixin.ts — uses getBlockPrefix() 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 → changeImageci, changeFeedSortingOrdercfso
  • For others: uses the component title's first letter(s) + per-type counter

TargetOpSelector Data Flow

  1. blockNameTypeDictionary() Vuex getter → maps blockName → blockType for top-level blocks only (not sub-blocks)
  2. Filters by changeableBlockTypes prop (e.g., ['graphicProperties'])
  3. Maps through classNameFromBlockName() (or special cases) to get abbreviations
  4. Combines with flowComponentAbbreviations from operatorList prop
  5. Groups by type prefix for palette display

Block Data Access

  • getBlockByName(name) Vuex getter — searches all blocks including sub-blocks (2 levels deep via getSubBlocks())
  • GraphicProperties blocks store images at: block.backgroundSettings.url (with mode: 'image')
  • VideoProperties blocks store video at: block.video.streamId (nested under video object, NOT top-level streamId)

Important: blockNameTypeDictionary vs getBlockByName scope difference

  • blockNameTypeDictionary(): only top-level blocks in creativeBlocks
  • getBlockByName(): 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.targetAbbrevOpName does not exist in the blob
  • The @change handler 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

  1. Initialize targetAbbrevOpName = null in OperatorHelper for all change types that use TargetOpSelector
  2. 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"):

  1. Get blockNameTypeDictionary()
  2. Filter by changeable block types
  3. For each block, compute its abbreviation using the same special-case + classNameFromBlockName() logic
  4. Match against the stored targetAbbrevOpName

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 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

FilePurpose
src/pages/Chatbots/components/BuilderVisuals/Blocks/utils.tsclassNameFromBlockName(), idFromBlockName()
src/pages/Chatbots/components/CavaiFlow/flowactors/OperatorBase.vuegetAbbrevOpName()
src/pages/Chatbots/components/CavaiFlow/operators/TargetOpSelector/TargetOpSelector.vuePalette + abbreviation resolution
src/pages/Chatbots/components/CavaiFlow/OperatorHelper.tsOperator blob initialization
src/store/modules/blocks.tsblockNameTypeDictionary, getBlockByName getters
src/pages/Chatbots/components/BuilderVisuals/Blocks/data/defaults.tsBlock default structures

Internal documentation