Skip to main content

Semantic Hooks

Five hooks from @qontinui/ui-bridge/react declare knowledge the DOM cannot express: what an element is for, how elements relate to one another, and what can be dragged where. All of it surfaces in snapshots, so an agent can reason about the page instead of guessing from tag names.

The drag-and-drop model these hooks feed is described in Drag & Drop.

useUIAnnotation​

function useUIAnnotation(elementId: string, annotation: ElementAnnotation): void

Attaches human-authored context to an element id.

import { useUIAnnotation } from '@qontinui/ui-bridge/react';

function SearchBar() {
useUIAnnotation('search-input', {
description: 'Global search input',
purpose: 'Searches across all projects and workflows',
notes: 'Debounces input by 300ms. Supports advanced query syntax.',
tags: ['search', 'global'],
relatedElements: ['search-results-panel', 'search-clear-btn'],
});

return <input placeholder="Search…" />;
}
Annotations are never cleaned up on unmount

Every other hook on these pages undeclares itself when its component unmounts. This one does not, deliberately: an annotation is persistent developer knowledge about an element id, not a property of one mounted instance. It stays in the global annotation store for the lifetime of the page. To change it, write a new annotation for the same id.

Parameters​

ParameterTypeMeaning
elementIdstringThe UI Bridge element id to annotate
annotationElementAnnotationThe annotation payload
interface ElementAnnotation {
description?: string; // what this element is
purpose?: string; // why it exists / what it is for
notes?: string; // behavioural notes, edge cases, caveats
tags?: string[]; // searchable categorisation
relatedElements?: string[]; // ids of related elements
metadata?: Record<string, unknown>;
updatedAt?: number; // set by the store
author?: string;
}

Every field is optional. The hook serialises the annotation and writes to the store only when the serialisation changed, so passing an inline object literal on every render is safe.

The written data is readable at GET /annotations/:elementId, in the export at GET /annotations/export, and directly via the store's get(elementId).

Note that useUIState can write the same annotations for a whole state's elements at once, when its metadata matches the IR-metadata shape.

useUIRelationship​

function useUIRelationship(
sourceId: string,
targetId: string,
type: RelationshipType,
options?: UseUIRelationshipOptions
): void

Declares one typed relationship between two elements. Declared on mount, undeclared on unmount; changing any parameter undeclares the old relationship and declares the new one.

import { useUIRelationship } from '@qontinui/ui-bridge/react';

// The search input filters the results list
useUIRelationship('search-input', 'results-list', 'filters');

// A tab activates a panel, in both directions
useUIRelationship('tab-settings', 'settings-panel', 'activates', { bidirectional: true });

// With metadata
useUIRelationship('sort-dropdown', 'data-table', 'controls', {
metadata: { field: 'sortOrder' },
});

Parameters​

ParameterTypeMeaning
sourceIdstringElement that has the relationship
targetIdstringElement being related to
typeRelationshipTypeSee the vocabulary below
options.bidirectionalboolean (default false)Declare the reverse edge too
options.metadataRecord<string, unknown>Arbitrary extra data

Relationship vocabulary​

RelationshipType is a union of named types plus string, so the list is a vocabulary, not a closed set — an unrecognised string is accepted.

TypeMeaning
controlsA changes B's state (toggle → panel)
filtersA narrows B's content (search → list)
validatesA shows validation state for B (error → input)
labelsA names/titles B (heading → section)
describesA provides detail for B (help text → input)
submitsA triggers submission of B (button → form)
activatesA makes B the active item (tab → panel)
togglesA shows/hides B (button → dropdown)
populatesA provides data for B (source → display)
navigatesToA navigates to B (link → section)
dependsOnA requires B's value (city → country)
ownsA logically contains B (combobox → listbox popup)

Relationships declared this way are recorded with origin declared, which is how snapshots distinguish them from edges auto-detected from ARIA or HTML structure.

The same edges can be declared inline on useUIElement via its relationships option, which is more convenient when the source element is one you already register.

useUIRelationships​

function useUIRelationships(sourceId: string, relationships: InlineRelationship[]): void

Declares many relationships from one source element.

import { useUIRelationships } from '@qontinui/ui-bridge/react';

useUIRelationships('save-button', [
{ target: 'name-input', type: 'submits' },
{ target: 'email-input', type: 'submits' },
{ target: 'form-status', type: 'populates' },
]);
interface InlineRelationship {
target: string;
type: RelationshipType;
bidirectional?: boolean;
metadata?: Record<string, unknown>;
}

The array is compared by serialised value, not identity, so an inline literal is fine. On any change the hook undeclares everything it previously declared before declaring the new set, and it undeclares the whole set on unmount.

useDragSource​

function useDragSource(elementId: string, options?: UseDragSourceOptions): void

Marks an element as draggable so an agent can discover it without inferring drag behaviour from event listeners.

import { useDragSource } from '@qontinui/ui-bridge/react';

useDragSource('step-3', { dataType: 'workflow-step' });
OptionTypeMeaning
dataTypestringThe kind of data this source represents
labelstringHuman-readable label
metadataRecord<string, unknown>Arbitrary extra data

All options are optional, and the second argument itself may be omitted. Declared on mount, undeclared on unmount; a changed dataType, label or metadata value replaces the declaration.

The declaration is a statement about the element, not an implementation: it does not add drag handlers or make anything draggable. Your app still implements the drag; this tells the bridge that it exists.

useDropZone​

function useDropZone(elementId: string, options?: UseDropZoneOptions): void

Marks an element as a drop target and says what it accepts.

import { useDropZone } from '@qontinui/ui-bridge/react';

useDropZone('step-list', {
accepts: ['workflow-step'],
effect: 'reorder',
});
OptionTypeMeaning
acceptsstring[]The dataType values this zone accepts
effectDragEffectWhat dropping here does
labelstringHuman-readable label
metadataRecord<string, unknown>Arbitrary extra data

Same lifecycle as useDragSource: declared on mount, undeclared on unmount, replaced when any option changes. The accepts values are matched against the dataType a drag source declares — keep the two vocabularies in sync, since nothing validates them for you.