Friction Structural Knowledge - cprima-forks/uipath-ai-skills GitHub Wiki
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.
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.
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.
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.
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.
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".
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>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.
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.
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)