dylanroscover / manage-annotations
MUST READ before creating, modifying, or querying annotations. Contains parameter names, coordinate model, and API quirks.
<!-- Generated by Embody/Envoy - Do not remove this comment - sha:ba9799e3ea1e5758 -->
Manage Annotations Workflow
Creating Annotations
create_annotation(
parent_path='/path/to/network',
mode='annotate', # 'annotate' (title bar), 'comment', or 'networkbox'
title='My Group',
text='Description of this group',
x=..., y=..., width=..., height=...,
color=[r, g, b], # Optional, floats 0-1
opacity=0.5 # Optional
)Always favor annotations over OP comments for documenting operators or groups.
Enclosing Operators
To create an annotation that encloses a group of operators:
- Get positions:
get_network_layouton the parent COMP (includes all operator positions and bounding box) - Calculate bounding box: Find
min_x,max_x,min_y,max_y(max includes operator width/height:max_x = max(op_x + op_w),max_y = max(op_y + op_h)) - Add padding: 70 units on left/right/bottom, 170 units on top (title bar + body text)
Set coordinates:
nodeX = min_x - 70nodeY = min_y - 70(BELOW operators, not above!)nodeWidth = max_x - min_x + 140nodeHeight = max_y - min_y + 240(70 bottom + 170 top)
Coordinate Model
nodeX/nodeY= bottom-left corner, width/height extend rightward and upward- Title bar renders at the top of the rectangle
- Common mistake: Setting
nodeYabove the operators.nodeYmust be BELOW (less than) the lowest operator's Y.
Querying
get_annotations- list all annotations in a COMP with properties and enclosed operatorsget_enclosed_ops- get operators enclosed by an annotation, or annotations enclosing an operatorset_annotation- modify text, title, color, opacity, position, or size
Deleting Annotations
Delete via delete_op, never via raw .destroy() in execute_python.delete_op resolves utility annotations, purges any tracking, and arms an
auto-save checkpoint that re-exports the parent TDXN COMP's .tdxn without
the annotation -- the deletion is durable. A raw .destroy() leaves the
stale annotations: entry in the parent's .tdxn on disk, and the next
reimport of that COMP (import_network, manager Reload, or cold open)
resurrects the annotation with its pre-delete text.
annotateCOMP Quirks
utilityisTruefor every annotation -- TD UI-drawn ones are born
that way,create_annotationsets it, and TDXN import applies it on every
annotation it recreates. (A bare Pythonparent.create('annotateCOMP')
isutility=False-- setann.utility = Trueimmediately to match; a
non-utility annotation is an ordinary COMP subtree that enumeration
sweeps will walk into.)**
utility=Truehides the op fromop(),parent.op(), AND.children** -- onlyfindChildren(includeUtility=True)sees it, and a
deepfindChildrendoes not even DESCEND into a utility annotate's
subtree unlessincludeUtility=Trueis passed (verified live, TD
2025.33070). Paths THROUGH a utility annotate to its interior ops still
resolve viaop().Every Envoy op-path tool resolves utility annotations (
delete_op,set_parameter,set_op_position,get_op, ... -- they share one
utility-aware resolver). For DISCOVERY, useget_annotations(always
sees them) or passinclude_utility=Truetoquery_network/find_children-- with the defaultFalse, annotations
are invisible in those listings.Annotations are never externalized per-op -- they round-trip through
the parent TDXN COMP's semanticannotations:section.externalize_op
refuses them, and tagging sweeps skip them and their internals (the
widget internals are TD-managed stock content cloned from TDAnnotate).envoy_bot_*is reserved for Embot, the mascot Envoy stands on the
operator it is working on (theEmbotparameter). The read tools hide
those parts and report how many asembot_hidden; Embody strips them
from every saved file and deletes loose ones on save. Never create,
move, edit or delete an annotation with that prefix -- and never name
one of yours that way, or it will be deleted as an artifact..typereturns'annotate'(not'annotateCOMP')findChildren(type=annotateCOMP)requires the class object, not the stringCannot be reliably renamed after creation (TD also ignores a name passed
at create time -- rename right after creating instead)