Friction Structural Knowledge - cprima-forks/uipath-ai-skills GitHub Wiki

Friction: Structural Knowledge in F-Strings

Beyond attribute names and values, the generator f-strings encode structural knowledge — rules about how XAML elements must be assembled — that cannot be derived from a flat list of attribute names. This knowledge is implicit, undocumented, and scattered across generator functions.

Inventory of Structural Knowledge

Attribute vs Child Element Placement

The selector arg does not become an XML attribute. It goes inside a nested child element structure:

<uix:NTypeInto ...>
  <uix:NTypeInto.Target>
    <uix:TargetAnchorable Selector="..." ... />
  </uix:NTypeInto.Target>
</uix:NTypeInto>

A flat registry of attribute names would list Selector as an attribute of NTypeInto. In reality it is a property of a child TargetAnchorable element two levels deep.

Conditional Attribute Name Substitution

NTypeInto uses either Text or SecureText as the attribute name depending on is_secure:

text_attr = f'SecureText="[{text_variable}]"' if is_secure else f'Text="[{text_variable}]"'

Same structural position, different attribute name. A registry would need to encode this as a conditional, not a static attribute name.

Body/Sequence Wrapping

Container activities require children wrapped in a Sequence element with its own IdRef. This inner Sequence is not visible in the JSON spec — it is generated by the container handler. The body_sequence_idref parameter exists specifically to feed this hidden structural requirement.

NApplicationCard.OcrEngine Mandatory Null Block

Every NApplicationCard variant emits an OcrEngine child element regardless of args:

<uix:NApplicationCard.OcrEngine>
  <x:Null />
</uix:NApplicationCard.OcrEngine>

This is not an attribute — it is a child element that must be present. It does not appear in any spec; the generator always includes it.

TryCatch Catch Block Nesting

Each catch in a TryCatch is a deeply nested structure:

<Catch x:TypeArguments="exceptionType">
  <Catch.Action>
    <ActivityAction x:TypeArguments="exceptionType">
      <ActivityAction.Argument>
        <DelegateInArgument x:TypeArguments="exceptionType" Name="exception" />
      </ActivityAction.Argument>
      <!-- children here -->
    </ActivityAction>
  </Catch.Action>
</Catch>

Five nesting levels for a single catch block. None of this structure is derivable from "TryCatch has a catches[] array".

AddQueueItem.ItemInformation Children

Queue item fields are not attributes — they are InArgument child elements inside a .ItemInformation property element:

<ui:AddQueueItem.ItemInformation>
  <InArgument x:TypeArguments="x:String" x:Key="FieldName">[expr]</InArgument>
</ui:AddQueueItem.ItemInformation>

BrowserType Auto-Detection

gen_napplicationcard_open inspects the target_app_selector string to detect the browser type and conditionally emits BrowserType="Edge" (or Chrome/Firefox) on the TargetApp child element. This logic is entirely within the generator — not in the registry, not in the spec.

sd: Post-Processing

Generator output contains sd2:DataTable and sd:Image as intermediate tokens. A post-processing string replacement in generate_workflow remaps these to the correct prefixes after the full body is assembled. The generators do not emit the final prefix — they emit placeholders resolved outside the generator itself.

Implications for Refactoring

A "thin" registry (attribute names and defaults only) would replace one part of what the generators currently encode. The structural knowledge listed above would still need to reside somewhere — either:

  • In a "thick" registry that encodes element structure, conditional logic, and child placement rules (complex, potentially over-engineered)
  • In a minimal set of per-activity assembly functions that consult the registry for attributes but retain structural logic (a partial separation that reduces the maintenance burden without eliminating it)
⚠️ **GitHub.com Fallback** ⚠️