Releases: FACTSlab/bead
Release list
v0.8.0
Added
N-dimensional, mixed-variable grid stratification
bead.lists.stratificationgained a grid engine that bins items along several
dimensions at once.assign_grid_cellsmaps each item to a tuple of
per-dimension bin indices, andassign_grid_cells_by_uuidreturns the
row-major flattened cell id for the stand-off annotation pattern;
grid_shape,flatten_cell, andunflatten_cellconvert between cell tuples
and ids.- Each dimension declares a binning strategy via the
BinningSpectagged union:
QuantileBinning(equal-frequency),EqualWidthBinning(equal-interval),
ThresholdBinning(caller-supplied cut points), andStdDevBinning
(standard-deviation offsets) for continuous values, andCategoricalBinning
(one bin per value, with optional declared categories and a catch-all) for
finite discrete values. GridStratificationConstraintand itsGridDimensionaxes express a grid as a
list constraint;ListPartitionerstratifies on the grid cell and
QuantileBalancer.balance_by_cellspreads each cell uniformly across lists.
The one-dimensionalassign_quantilespath is now the quantile specialization
of this single engine.
Acceptability-model stratification in the eng gallery
- The
gallery/eng/argument_structureexample trains a 2AFC acceptability model
on MegaAcceptability (single-item Likert ratings mapped to forced-choice pairs
per annotator), uses it together with the language-model score to stratify
items across a two-dimensional grid, and seeds the active-learning loop with
the pretrained model. Gallery artifacts round-trip through thelayersschema
viabead.interop.layers. AcceptabilityScorerinbead.items.scoringscores a forced-choice item by a
trainedForcedChoiceModel, returning the predicted preference margin.
More resource and response layers lenses
-
FilledTemplateFillingLens(FILLED_TEMPLATE_FILLING) maps aFilledTemplate
to and from a layersfillingrecord (resource.Filling): the template
reference, per-slotSlotFillingrecords, rendered text, and filling strategy
form the view, while the bead framework identity, source template name, slot
requirement map, and exact lexical-item fillers travel in the lens complement. -
AnnotationRecordJudgmentLens(ANNOTATION_RECORD_JUDGMENT) maps an
AnnotationRecordto and from a layersjudgment.Judgment, and
records_to_judgment_set/judgment_set_to_recordsgroup a single
annotator's records into ajudgment.JudgmentSet(with the annotator as an
AgentRef). -
ListConstraintLens(LIST_CONSTRAINT) maps a beadListConstraintto and
from a layersjudgment.ListConstraint, andExperimentListLens
(EXPERIMENT_LIST_LAYERS) maps anExperimentListto a layerscollection
with onecollectionMembershipper item plus its list constraints. -
ParticipantAgentLens(PARTICIPANT_AGENT) maps a beadParticipantto and
from a layersAgentRefidentity, andparticipant_featuresrenders the
participant's study fields (demographics, study id, sessions, consent) as the
FeatureMapthat ajudgment.JudgmentSetdocuments as the home for annotator
demographics, session metadata, and payment info.records_to_judgment_set
now takes an optionalparticipant, so a judgment set carries that annotator's
identity and study fields natively. A layerspersona.Personais an annotator
role and interpretive framework, not a concrete enrolled participant, so it is
not the target of this mapping.All of these round-trip exactly.
Forced-choice training: dev-set early stopping
ForcedChoiceModelsupports early stopping on a held-out dev set. A new
early_stopping_patienceconfig field stops training after that many epochs
without dev cross-entropy improvement and restores the best encoder and head.
Theenggallery holds out whole sentences (never seen during training) for the
dev set and, withmax_pairs_per_annotatorunset, trains over every within-rater
same-frame pair.
Fixed
ForcedChoiceModelreports train and validation accuracy, precision, recall,
and F1 from the model's own predict path. The shared mixed-effects trainer's
evaluate()only labels cloze batches, so it could not score forced choice, and
train accuracy previously always read0.0.compute_binary_metricsand
compute_multiclass_metricsno longer passzero_divisiontoevaluate's F1,
which this version rejects.- Forced-choice training and prediction move the per-participant random-effects
parameters to the model device, so the model runs on MPS and CUDA, not only CPU.
v0.7.0
Added
Deep layers integration through lairs
bead.interop.layersbuilds its layers projections from the canonical
lairs.recordsmodels thatlairsgenerates from the layers lexicons, so the
schema has one source of truth. The bridge, parse, graph, and resource lenses
return typedlairsmodels (or small bead-side aggregates of them) as their
view; the lens complement carries the bead-only remainder, and the round-trip
is guaranteed by the didactic GetPut/PutGet laws.ItemLayersLens(ITEM_LAYERS,item_to_layers) maps anItem's standoff
spans and relations to span and relationAnnotationLayerrecords over
per-element expressions and tokenizations: a span anchors by
tokenRefSequence(withhead_indexasanchorTokenIndexand a Wikidata
label_idas aknowledgeRef); a relation carriesArgumentRefsource and
target arguments.BeadCodec, registered on thelairs.codecsentry point, round-trips a bead
ItemCollectionto and from the layers schema.lairs.codec("bead")resolves
it, andencode(decode(x)) == xholds losslessly.bead.interop.layers.corpus_ioingests alairs.data.Corpusinto bead corpus
models (corpus_to_records,corpus_to_graph,corpus_to_items) and emits
bead data as a corpus (items_to_corpus,graph_to_corpus,
materialize_corpus,save_corpus_repo,publish_corpus).bead layersCLI commands:encode,decode,materialize, and an opt-in
publishthat defaults to a dry run.
Changed
lairs>=0.5.0is a dependency; the minimumdidacticis raised to>=0.9.0
andpanprototo>=0.56.0. Importingbeaddoes not importlairs; the
dependency loads only whenbead.interop.layersis used.- Layers record validation is owned by
lairs, whose generated models validate
on construction.
v0.6.0
Added
bead.corpus — streaming corpus ingestion and structural sampling
- New subpackage
bead.corpusfor turning raw text corpora into experimental
Items.CorpusRecordcarries text plus flat provenance;CorpusSourceis
a streaming-source protocol. - Sources:
JsonlCorpusSource(JSON Lines, transparently decompressing
Zstandard.zstfiles),CsvCorpusSource(CSV/TSV), and
CompletionCorpusSource(a language model as a corpus source, via the new
TextGeneratorprotocol on the OpenAI and Anthropic adapters). - Lazy pipeline:
parse_records,filter_by_structure,sample_corpus, and
record_to_itemstream records through a dependency parser and keep only
those whose parse satisfies a structural DSL constraint, producingItems
with standoff parse annotations and source provenance. The pipeline never
loads the full corpus into memory. - New
corpusoptional-dependency extra (zstandard).
Dependency parsing in bead.tokenization
- New
bead.tokenization.parsers:SpacyParser,StanzaParser, and
create_parserproduce a per-sentenceParsedSentenceofParsedToken
records (token, lemma, upos, xpos, head, deprel, morphology, offsets). parse_to_spansprojects a dependency parse onto the standoffSpan+
SpanRelationmodels: one single-token span per token (with its governor as
head_indexand its features inspan_metadata) and one directed
head-to-dependent relation per syntactic arc.
Structural-query builtins in the constraint DSL
- New
bead.dslstandard-library functions query a dependency parse stored on
anItem:upos,xpos,lemma_of,form_of,deprel,morph,head,
dependents,has_relation,root,subtree,path_to_root,
tokens_with_upos,tokens_with_deprel,any_deprel, andfilter_upos.
Constraints can now match syntactic structure, e.g.
upos(self, root(self)) == "VERB" and len(dependents(self, root(self), "obj")) > 0.
Text transforms for corpus cleanup
- New transforms in
bead.transforms.text:MarkdownStripTransform,
RedditCleanupTransform, and thesplit_sentenceshelper (parser-backed or
regex fallback). The first two are registered in the default transform
registry.
bead.corpus buffering graph tier
- New
bead.corpus.graph:CorpusGraph, a typed directed multidigraph of
CorpusNodes andCorpusEdges (parallel typed edges allowed; trees are a
special case), with traversal helpers (children,parents,roots,
out_edges,in_edges,subtree,node_by_id). - New
bead.corpus.assemble:assemble_graphbuffers a record stream into a
CorpusGraph, building edges from declarativeEdgeSpecs or a runtime edge
function. Reconstructs thread structure such as Reddit reply trees from
parent_id/link_id. This tier is opt-in and layered on top of the
streaming pipeline, which is untouched.
bead.interop.layers — lossless layers interop
- New subpackage mapping bead data to and from the
layers linguistic-annotation schema
as law-verified didactic lenses (dx.Isofor bijections,dx.Lenswith a
complement for projections), so every round-trip is exact and verified. - Faithful mirror models for the layers shared defs and record types, each with
a generic losslessMirrorIsoto and from layers-shaped JSON (snake/camel
case, feature maps, slug+uri enums, integer confidence,$typeunions). - Bridge lenses map bead-native models onto layers constructs:
CorpusRecord
to anexpression,CorpusGraphto a property graph (expressions,
graphNodes, and agraphEdgeSet), and a dependency-parsedParsedSentence
to atokenizationplus part-of-speech and dependencyannotationLayers. The
lens complement holds the bead-only remainder (framework identity and fields
layers has no slot for). Resource-overlap lenses map lexical items, lexicons,
and templates to the layers resource constructs. - Mappings are validated against the layers lexicons, vendored as the
vendor/layersgit submodule, using the ATProto lexicon validator
(@atproto/lexicon), proving every mapping produces schema-valid layers.
Changed
- Minimum
didacticraised to>=0.7.2andpanprototo>=0.51.0. - Streaming corpus ingestion is now lossless by default:
JsonlCorpusSource
andCsvCorpusSourceretain every field (not just a configured subset), and
non-scalar values round-trip through JSON rather than being stringified, so
no source information is dropped at ingestion.
v0.5.0
Added
bead.config.compose — didactic-grounded config composer
- New subpackage
bead.config.composereplaces the hand-rolled config
loader. Generic over anydx.Modelschema; supports the full
OmegaConf interpolation grammar (${section.field},${.x}/
${..x}relative,${a.b[0]}and${a.b.0}list indexing,
${a.${b}}nested,\${literal}escape, cycle detection). - Built-in resolvers:
oc.env,oc.env:VAR,default,oc.select,
oc.decode(base64),oc.deprecated,oc.create,
oc.dict.keys,oc.dict.values. Application-specific resolvers
register viabead.config.compose.register_resolver. - Bead-specific resolvers in
bead.config.resolvers:
${bead.path:rel}joins against the active root's
paths.data_dir;${bead.anchor:name[,attr]}post-validation
expansion. defaults: [...]composition at the top of any YAML/TOML config
composes referenced files left-to-right before the primary body.- Strict-merge rejects unknown keys with the dotted path to the
offending site, walking nesteddx.Embed[T]models from
__field_specs__. - TOML configs (
.toml) supported alongside YAML out of the box. bead.config.load_configis now a thin wrapper around
compose(schema=BeadConfig, ...). The previous
load_yaml_file/merge_configshelpers are removed.- CLI: every
bead ...invocation accepts repeatable
--set KEY=VALUEoverrides threaded into the compose pipeline.
ScaleType.FORCED_CHOICE
- New
ScaleType.FORCED_CHOICEvariant covers N-alternative
forced-choice tasks where per-item options vary across items
(response space is a fixed positional label set, e.g.
("first", "second"), but eachItemcarries its own
alternatives).family_to_item_templateand the
active-learning model registry route forced-choice anchors to
ForcedChoiceModel. AnchorSpec.scale_typeis an optional explicit override so config
files declare the task type alongside the response space.
Gallery: gallery/eng/argument_structure/ v0.4.0 wiring
- New
protocol.pymodule exposesbuild_protocol()/
acceptability_family()/acceptability_anchor(). The 2AFC
acceptability question is declared once inconfig.yamlunder
protocol:and consumed by every script. generate_deployment.pyandsimulate_pipeline.pybuild their
ItemTemplateviafamily_to_item_templateinstead of literal
prompt strings.create_2afc_pairs.pythreads the protocol anchor name
("acceptability") into every pair'sitem_metadataso the
JATOS-result →AnnotationRecordbridge can match responses
back to the canonical anchor.make validate-protocolbuilds the liveAnnotationProtocol
fromconfig.yamland prints the family, prompt, and scale
type. Wired in as a prerequisite tomake data.tests/test_protocol.pycovers the config-to-protocol round
trip, the forced-choice scale type, thefamily_to_item_template
prompt agreement, and the active-learning model selection for
the resulting encoding.
v0.4.0
Added
Pipeline-wide integration of the protocol layer
bead.labelsis the single canonical home for the
[[label]]/[[label:text]]/[[label|transform]]syntax.
parse_label_refs,find_label_names, andreplace_label_refs
replace the three independent regex implementations that previously
lived inbead.protocol.drift,bead.deployment.jspsych.trials,
andbead.items.span_labeling.bead.config.protocol.ProtocolConfigplugs intoBeadConfig.protocol
with declarative TOML/YAML configuration: anchor specs, drift
settings, realization strategies (template / contextual / lm), and
family composition.ProtocolConfig.build(lm_client=..., cache=...)
materializes a liveAnnotationProtocol.bead.protocol.itemsprovides the canonical
QuestionRealization → Itemand protocol-wide
family_to_item_template/protocol_to_item_templates/
realize_protocol_to_itemsbridges, plusscale_type_to_task_type
as the single canonical mapping fromScaleTypetoTaskType.bead.active_learning.models.registryexposes
MODEL_CLASSES/CONFIG_CLASSESand
model_class_for_task_type/config_class_for_task_type/
model_class_for_encoding/config_class_for_encodingas the
single canonical task-type → model-class / config-class registry.
bead.cli.modelsandbead.cli.trainingconsume the registry
directly, replacing two parallel string-keyed dicts and a dynamic
_import_classhelper.bead.deployment.protocol_trials.protocol_to_jspsych_trialsis the
canonical end-to-end bridge from anAnnotationProtocoland a
sequence ofProtocolContextrecords to a flat list of jsPsych
trial dicts.bead.data_collection.jatos_results_to_annotation_recordsconverts
raw JATOS results intoAnnotationRecordinstances, the input
shape consumed byannotator_reliabilityand
InterAnnotatorMetrics.bead protocolCLI subcommand:bead protocol validate,
bead protocol realize,bead protocol itemsdrive the
configured protocol from the shell.
Changed
LMRealizationaccepts aModelOutputCache(the bead-wide
content-addressable cache) via its requiredcachekeyword and a
requiredmodel_namekeyword for cache-key isolation. The internal
FIFO dict and thecache/max_cache_size/clear_cache/
cache_sizeparameters and methods are removed; the
ModelOutputCacheis the single canonical caching surface.bead.cli.modelsno longer maintainsTASK_TYPE_MODELS/
TASK_TYPE_CONFIGSstring-path dicts or the_import_class
helper; they are replaced by direct calls into
bead.active_learning.models.registry.bead.cli.trainingfollows
the same pattern.bead.deployment.jspsych.trials._parse_prompt_references,
_SpanReference,_SPAN_REF_PATTERN, and the duplicated
_SPAN_REF_PATTERNinbead.items.span_labelingare removed in
favor ofbead.labels.parse_label_refs/LabelRef.
bead.protocol: annotation protocol primitives
A new top-level package providing a type-theoretic stack for defining
annotation protocols: anchors as types, contexts as dependent
indices, realization strategies as computational content, and drift
guards as type-checkers.
bead.protocol.anchordefinesSemanticAnchor(the type-level
spec of a question, with required span labels, required keywords,
optional embedding center andmax_drift) andResponseSpace/
SemanticPoles.bead.protocol.contextdefines a genericProtocolContextand
ContextItemplus a module-level predicate registry
(register_context_predicate,get_context_predicate,
list_context_predicates) for callers to register named context
predicates at import time.bead.protocol.realizationprovidesRealizationStrategy
(typing.Protocol),TemplateRealization,
ContextualTemplateRealization(rule-based selection from ranked
variants), andLMRealization(with caching and FIFO eviction)
plus anLMClientProtocolwith explicit
temperature/max_tokenskeyword parameters.bead.protocol.driftdefinesDriftScore, theDriftValidator
Protocol, and three concrete validators
(StructuralDriftValidator,EmbeddingDriftValidator,
PerplexityDriftValidator) plus a compositeDriftGuard. The
embedding and perplexity validators consume narrow
EmbeddingAdapter/PerplexityAdapterProtocols, so any object
exposing the right method (including bead's
bead.items.adapters.ModelAdapter) conforms.bead.protocol.familydefinesQuestionFamily(with explicit
depends_onfor conditional dependencies) andAnnotationProtocol
(the iterated dependent product), withrealize_allthreading
responses through the context.AnnotationProtocolrejects
duplicate anchor names, self-dependencies, and forward / unknown
depends_onreferences at construction and onappend.bead.protocol.encodingdefinesScaleType
(StrEnum: binary / ordinal / nominal) andResponseEncoding(with
invariant validators forn_levels == len(labels), label
uniqueness, andBINARYhaving exactly 2 levels), plus
encode_response_spaceas the bridge fromResponseSpace.bead.protocol.diagnosticsdefinesDiagnosticLevel,
DiagnosticRecord,DatasetReport(immutable, withwith_*
mutators),ConditionalObservationValidator(which operates on
AnnotationProtocol.depends_on), and theRecordLikeProtocol
for the structural record shape consumed by the validator.LMRealizationraisesRuntimeErroron backend failures and on
empty / whitespace-only responses (instead of caching an empty
string).
bead.evaluation.reliability: per-annotator reliability
AnnotationRecordis aBeadBaseModelwith the canonical
(annotator_id, item_id, question_name, response_label)shape.annotator_reliability(records, encodings=...)returns
per-annotator response distributions and Shannon entropy in bits,
optionally filtering unrecognized labels.low_entropy_annotators(profiles, threshold=...)flags annotators
who collapse the response space.
Documentation
docs/api/protocol.mdanddocs/api/evaluation.mdupdates expose
the new modules throughmkdocstrings.docs/user-guide/protocols.mdwalks through anchors, contexts
(including the predicate registry and per-dependent attributes),
the three realization strategies, drift validation (with the named
EmbeddingAdapterandPerplexityAdapterProtocols), protocol
composition, the structural construction-time invariants, the
encode_response_spacebridge to modeling, conditional-observation
diagnostics (including theRecordLikeProtocol), and reliability.- The protocol layer is cross-linked from
docs/user-guide/concepts.md,docs/user-guide/index.md,
docs/index.md, the projectREADME.md, and a new "Protocol layer"
paragraph indocs/developer-guide/architecture.mdthat places it
as a cross-cutting layer feeding into the existing 6-stage pipeline.
v0.3.0
Changed
Model layer migrated from Pydantic to didactic
- Every
pydantic.BaseModeland@dataclassmodel inbead.data,bead.items,
bead.tokenization,bead.resources,bead.lists,bead.participants,
bead.config,bead.active_learning.config,bead.dsl.ast,
bead.deployment.distribution,bead.deployment.jspsych.config,
bead.behavioral.analytics,bead.templates.filler.FilledTemplate, and
bead.transforms.base.TransformContextnow extendsdx.Model(or
dx.TaggedUnionfor the discriminated unions inbead.lists.constraintsand
bead.dsl.ast). - All models are frozen by default; mutating
add_*/update_modified_time
methods become purewith_*/touchedmethods that return new instances.
Cross-field@model_validator(mode="after")checks extract to free
validate_*functions that callers invoke explicitly. list[T]field declarations becometuple[T, ...]; nested-Model fields wrap
withdx.Embed[T]. Heterogeneous tuples (e.g.tuple[int, int]for scale
bounds,tuple[UUID, UUID]for ordering precedence pairs) become small
named records (ScaleBounds,OrderingPair).dict[UUID, X]anddict[int, X]mappings become tuples of records
(ConstraintSatisfaction,ScalePointLabel, etc.) — didactic dict keys must
bestr.dict[str, JsonValue]replacesdict[str, Any].Path-typed configuration fields (PathsConfig.data_dir,LoggingConfig.file,
TrainerConfig.logging_dir,TemplateConfig.mlm_cache_dir,
ResourceConfig.{lexicon,templates,constraints}_path,
ModelMetadata.{training_data_path,eval_data_path,best_checkpoint}) are
stored asstr; callers wrap withpathlib.Pathon access. Will revert to
Pathonce panproto/didactic#21 lands.ExperimentConfig.instructionsno longer accepts a barestr; pass
InstructionsConfig.from_text("...")for a single-page string.BeadBaseModel.update_modified_time()(mutating) renamed totouched()
(pure, returns a new instance).
Toolchain
requires-pythonraised to>=3.14; pyrightpythonVersionand ruff
target-versionfollow.pydanticremoved from project dependencies;didactic>=0.4.3and
panproto>=0.43added.
Added
Transforms (bead.transforms)
- TransformContext carrying span metadata (lemma, POS, head index, tokens) for value-level transformations
- TransformRegistry for registering and resolving named transform pipelines
- morphology transforms: inflection adjustments driven by
InflectionSpec - text transforms: case and whitespace normalizers
- Pipeline syntax
[[label|transform1|transform2]]in prompt span references; transforms resolve display text against the registry at trial generation time - 69 tests covering registry, morphology, text, and prompt integration
Changed
bead.deployment.jspsych.trialsaccepts an optionalTransformRegistryand passes it through prompt resolution- Prompt span reference regex now recognizes the
|transformsuffix on[[label]]and[[label:text]]forms
v0.2.1
[0.2.1] - 2026-03-26
Fixed
- Local (non-JATOS) jsPsych deployments now run correctly in the browser
- Strip ESM
exportstatements from scripts loaded as classic<script>tags - Conditional JATOS/local initialization in experiment template
- Copy gallery bundle with all bead plugins into deployment output
- Resolve plugin type strings to class references at runtime
- Add forced-choice CSS to experiment stylesheet
- Strip ESM
Changed
- Gallery bundle copy is now a warning (not an error) when the bundle is not built
- CI test job now builds the gallery bundle (
pnpm build:gallery) stratification.pyuses PEP 695 type parameter syntax
v0.2.0
[0.2.0] - 2026-02-09
Added
Span Labeling Data Model (bead.items)
- Span, SpanLabel, SpanSegment models for stand-off token-level annotation
- SpanSpec for defining label vocabularies and relation types
- SpanRelation for directed labeled relations between spans
add_spans_to_item()composability function for attaching spans to any item type- Prompt span references:
[[label]]and[[label:text]]template syntax- Auto-fills span token text or uses explicit display text
- Colors match between stimulus highlighting and prompt highlighting
- Resolved Python-side at trial generation; plugins receive pre-rendered HTML
- Early validation warning in
add_spans_to_item(), hard validation at trial generation
Tokenization (bead.tokenization)
- Token model with
text,whitespace,index,token_space_afterfields - TokenizedText container with token-level access and reconstruction
- Tokenizer backends: whitespace (default), spaCy, Stanza
- Lazy imports for optional NLP dependencies
jsPsych Plugins (bead.deployment.jspsych)
- 8 new TypeScript plugins following the
JsPsychPluginpattern:- bead-binary-choice: two-alternative forced choice with keyboard support
- bead-categorical: labeled category selection (radio buttons)
- bead-free-text: open-ended text input with optional word count
- bead-magnitude: numeric magnitude estimation with reference stimulus
- bead-multi-select: checkbox-based multi-selection with min/max constraints
- bead-slider-rating: continuous slider with labeled endpoints
- bead-rating: Likert-scale ordinal rating with keyboard shortcuts
- bead-span-label: interactive span highlighting with label assignment, relations, and search
- span-renderer library for token-level span highlighting with overlap support
- gallery-bundle IIFE build aggregating all plugins for standalone HTML demos
- Keyboard navigation support in forced-choice, rating, and binary-choice plugins
- Material Design styling with responsive layout
Deployment Pipeline
SpanDisplayConfigwithcolor_paletteanddark_color_palettefor consistent span coloringSpanColorMapdataclass for deterministic color assignment (same label = same color pair)_assign_span_colors()shared between stimulus and prompt renderers_generate_span_stimulus_html()for token-level highlighting in deployed experiments- Prompt span reference resolution integrated into all 5 composite trial creators (likert, slider, binary, forced-choice, span-labeling)
- Deployment CSS for
.bead-q-highlight,.bead-q-chip,.bead-span-subscriptin experiment template
Interactive Gallery
- 17 demo pages using stimuli from MegaAcceptability, MegaVeridicality, and Semantic Proto-Roles
- Demos cover all plugin types and composite span+task combinations
- Gallery documentation with tabbed Demo / Python / Trial JSON views
- Standalone HTML demos with gallery-bundle.js (no build step required)
Tests
- 79 Python span-related tests (items, tokenization, deployment)
- 42 TypeScript tests (20 plugin + 22 span-renderer)
- Prompt span reference tests: parser, color assignment, resolver, integration
Changed
- Trial generation now supports span-aware stimulus rendering for all task types
- Forced-choice and rating plugins updated with keyboard shortcut support
- Span-label plugin enhanced with searchable fixed labels, interactive relation creation, and relation cleanup on span deletion