AimdRecorder
Use AimdRecorder when you want AIMD inputs rendered inline inside the protocol flow.
Example
<script setup lang="ts">
import { ref } from "vue"
import {
AimdRecorder,
createEmptyProtocolRecordData,
type AimdProtocolRecordData,
} from "@airalogy/aimd-recorder"
import "@airalogy/aimd-recorder/styles"
const content = ref(`# Protocol
Sample: {{var|sample_name: str, title="Sample name", description="Human-readable sample label", examples=["S-001"]}}
Operator: {{var|operator: UserName}}
Record Time: {{var|current_time: CurrentTime}}
Temperature: {{var|temperature: float = 25.0, title="Temperature (C)", description="Ambient temperature in Celsius", examples=[25.0, 37.0]}}
Notes: {{var|notes: AiralogyMarkdown}}
Plasmid: {{var|plasmid: DNASequence}}`)
const record = ref<AimdProtocolRecordData>(createEmptyProtocolRecordData())
</script>
<template>
<AimdRecorder
v-model="record"
:content="content"
locale="en-US"
current-user-name="Alice"
/>
</template>record shape:
{
"var": {},
"step": {},
"check": {},
"quiz": {}
}Built-In Recorder Behaviors
CurrentTimeandUserNamecan fill recorder values automatically from runtime context.AiralogyMarkdownrenders as a full-width embedded AIMD/Markdown field withPreviewandSourcemodes; preview uses the AIMD renderer and renders Mermaid code blocks, while source editing still supports switching toWYSIWYG.DNASequencerenders a dedicated sequence widget with interactive and raw-structure modes, file import/export, topology switching, feature editing, andSeqViz-based visualization.EntityRefandlist[EntityRef]render as entity-reference controls when the host passesentityResolvers; resolver keys can match the AIMDsourceconnector id or theentitynamespace. Hosts can usecreateAimdEntityResolversFromConnectors()from@airalogy/aimd-core/utilsto build these resolvers from parsedconnectorsmetadata.- Collector-bound
Observation[T]andlist[Observation[T]]fields render as acquisition controls when the host passescollectorProviders, with snapshot reads, manual polling controls, authorization, cancellation, provenance, and explicit manual fallback. ref_varreferences display current var values as readonly inline content when available.varandvar_tablelabels display AIMDtitle, keep the canonical id visible when a title is present, and showdescriptionplusexample/examplesdetails only on hover or keyboard focus. The first scalar example becomes the default placeholder when no explicit placeholder override is provided.- Select-backed nullable types such as
bool | None,Literal[...] | None, andBloodType | Noneshow a localizedNot setoption and store that choice asnull. Required selects omit the empty option, so choosing a real enum value remains mandatory. list[str],list[int],list[float], and equivalent optional scalar-list vars render as full-row fields with repeatable, drag-reorderable item inputs plus JSON array mode, then save scalar arrays instead of forcing the generic structured textarea.AimdRecorderincludes a collapsed-by-default protocol-aware current-record search control that can search all fields or one selected field; when expanded it stays sticky at the top of the recorder, highlights matching controls, jumps between matches, and selects matched text inside native text inputs when possible.choice,true_false,blank,open, andscalequiz types all have built-in recorder inputs.- Numeric
varinputs honor Pydantic-style constraints such asgt,ge,lt,le, andmultiple_of; these constraints apply toint,integer,float, andnumbervar types. - Client assigners use the same numeric constraints for dependency readiness and skip execution while a dependent numeric field violates its declared bounds.
Record Validation
Use the Pydantic JSON Schema returned by protocol parsing as the shared contract, and call the component ref when the host needs a submit gate:
<AimdRecorder
ref="recorderRef"
v-model="record"
:content="content"
:validation-schema="parseResult.data?.json_schema"
/>const result = await recorderRef.value?.validate()
if (!result?.valid) returnThe schema contract accepts both engine keys (vars, steps, checks) and legacy platform aliases (research_variable, research_step, research_check). It validates required, type, format, pattern, enum, numeric, array, object, and built-in-type constraints, including normalized structured values such as uploaded files. Schema-required non-nullable recorder inputs reject empty strings and empty arrays even when the property already exists in the in-memory Record; nullable properties accept null and do not show a required marker. Without a schema, Recorder falls back to AIMD declarations, infers fields without defaults as required unless their type includes None or uses Optional[...], and applies explicit required overrides from AIMD or fieldMeta.
Recorder validates only the changed or blurred field by default and preserves unrelated errors; configure this with validationTriggers. Table errors are tracked, rendered, and focused by exact row and column keys such as var_table:samples:0:concentration.
AimdRecorder and AimdRecorderEditor expose validate(), validateField(fieldKey), clearValidation(fieldKey?), and focusFirstInvalidField(). The validation result contains issues plus a fieldState map. Run authoritative server-side Pydantic validation after the client submit gate, and pass server errors through the existing fieldState prop.
Collector Providers
Collector declarations are parsed from the Protocol, while the host controls real device and network access by injecting a provider map keyed by connector id:
const collectorProviders = {
lab_sensor_gateway: {
async read({ collector, signal }) {
const response = await fetch(`/api/sensors/${collector.channel}`, { signal })
return response.json()
},
},
}<AimdRecorder
v-model="record"
:content="content"
:collector-providers="collectorProviders"
:request-collector-permission="requestCollectorPermission"
:collector-record-key="recordId"
collector-actor-id="user-123"
/>The provider may return a raw value or an observation envelope. Recorder supplies received_at and trusted source metadata before writing the Record. The permission callback may return false, true/"once", or "record"; record-scoped approval is cleared when the record key or Protocol content changes. See the normative Collector syntax and runtime documentation.
The current browser runtime supports snapshot and manually started/stopped polling. It parses but does not execute stream, automatic record/step lifecycle triggers, or file-backed ObservationSeriesRef[T] acquisition.
Client Assigner
Frontend-only client assigners run locally inside recorder mode.
Water: {{var|water_volume_ml: float}}
Lemon: {{var|lemon_juice_ml: float}}
Total: {{var|total_liquid_ml: float}}
```assigner runtime=client
assigner(
{
mode: "auto",
dependent_fields: ["water_volume_ml", "lemon_juice_ml"],
assigned_fields: ["total_liquid_ml"],
},
function calculate_total_liquid_ml({ water_volume_ml, lemon_juice_ml }) {
return {
total_liquid_ml: Math.round((water_volume_ml + lemon_juice_ml) * 100) / 100,
};
}
);
```For mode: "manual", AimdRecorder exposes explicit trigger methods through the component ref:
recorderRef.value?.runClientAssigner("calculate_total_liquid_ml")
recorderRef.value?.runManualClientAssigners()Locale And Quiz Recorder
Both AimdRecorder and AimdQuizRecorder accept locale to switch built-in recorder labels:
<AimdRecorder locale="zh-CN" />
<AimdQuizRecorder :quiz="quiz" locale="zh-CN" />Standalone quiz usage:
<script setup lang="ts">
import { ref } from "vue"
import { AimdQuizRecorder } from "@airalogy/aimd-recorder"
import "@airalogy/aimd-recorder/styles"
const answer = ref("")
const quiz = {
id: "quiz_single_1",
type: "choice",
mode: "single",
stem: "Choose one option",
options: [
{ key: "A", text: "Option A" },
{ key: "B", text: "Option B" },
],
}
</script>
<template>
<AimdQuizRecorder v-model="answer" :quiz="quiz" />
</template>If a choice or true_false option defines followups, the recorder shows those inputs only after the option is selected. For these quizzes the answer value is structured as { selected, followups }, while ordinary choice quizzes continue to use the legacy string or string-array shape and ordinary true/false quizzes continue to use booleans.
Showing Grade Results
If the host has already graded the answers elsewhere, pass the result back into AimdRecorder or AimdQuizRecorder for inline display. Deterministic scale quizzes can also compute a local score/classification inside the recorder.
Whole recorder:
<AimdRecorder
v-model="record"
:content="content"
:quiz-grades="quizGrades"
choice-option-explanation-mode="selected"
scale-grade-display-mode="submitted"
/>Standalone quiz:
<AimdQuizRecorder
v-model="answer"
:quiz="quiz"
choice-option-explanation-mode="graded"
:grade="{
quiz_id: 'quiz_single_1',
earned_score: 4,
max_score: 5,
status: 'partial',
method: 'keyword_rubric',
feedback: 'The answer is mostly correct but still misses one point.',
review_required: true,
}"
/>Recommended usage:
- grade
choiceand standardblankitems locally - grade deterministic
scaleitems locally, then usescaleGradeDisplayModeto control whether the result appears on completion or only after submit - use a backend provider for
openitems or highly flexible blanks - for practice, pass
quizGradesin real time so learners can immediately see status, score, and feedback - if a
choiceoption definesexplanation, usechoiceOptionExplanationMode="selected"to show that explanation immediately after the learner selects the option - if option explanations should appear only after the learner submits, combine
:submitted="isSubmitted"withchoiceOptionExplanationMode="submitted" - if a
scaleresult should appear only after submit, combine:submitted="isSubmitted"withscaleGradeDisplayMode="submitted" - if you want option explanations to appear only after grading is available, use
choiceOptionExplanationMode="graded" - for exams, omit
quizGradesuntil grading is finalized - unanswered quizzes with status
ungradeddo not show a grading panel by default - do not expose real model secrets in the browser for formal assessment flows
Homework / reveal-after-submit example:
<AimdRecorder
v-model="record"
:content="content"
:submitted="isSubmitted"
choice-option-explanation-mode="submitted"
/>submitted is controlled by the host app. AimdRecorder does not infer submission state on its own and does not include a built-in submit button.