Skip to content

AimdRecorder

Use AimdRecorder when you want AIMD inputs rendered inline inside the protocol flow.

Example

vue
<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:

json
{
  "var": {},
  "step": {},
  "check": {},
  "quiz": {}
}

Built-In Recorder Behaviors

  • CurrentTime and UserName can fill recorder values automatically from runtime context.
  • AiralogyMarkdown renders as a full-width embedded AIMD/Markdown field with Preview and Source modes; preview uses the AIMD renderer and renders Mermaid code blocks, while source editing still supports switching to WYSIWYG.
  • DNASequence renders a dedicated sequence widget with interactive and raw-structure modes, file import/export, topology switching, feature editing, and SeqViz-based visualization.
  • EntityRef and list[EntityRef] render as entity-reference controls when the host passes entityResolvers; resolver keys can match the AIMD source connector id or the entity namespace. Hosts can use createAimdEntityResolversFromConnectors() from @airalogy/aimd-core/utils to build these resolvers from parsed connectors metadata.
  • Collector-bound Observation[T] and list[Observation[T]] fields render as acquisition controls when the host passes collectorProviders, with snapshot reads, manual polling controls, authorization, cancellation, provenance, and explicit manual fallback.
  • ref_var references display current var values as readonly inline content when available.
  • var and var_table labels display AIMD title, keep the canonical id visible when a title is present, and show description plus example/examples details 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, and BloodType | None show a localized Not set option and store that choice as null. 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.
  • AimdRecorder includes 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, and scale quiz types all have built-in recorder inputs.
  • Numeric var inputs honor Pydantic-style constraints such as gt, ge, lt, le, and multiple_of; these constraints apply to int, integer, float, and number var 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:

vue
<AimdRecorder
  ref="recorderRef"
  v-model="record"
  :content="content"
  :validation-schema="parseResult.data?.json_schema"
/>
ts
const result = await recorderRef.value?.validate()
if (!result?.valid) return

The 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:

ts
const collectorProviders = {
  lab_sensor_gateway: {
    async read({ collector, signal }) {
      const response = await fetch(`/api/sensors/${collector.channel}`, { signal })
      return response.json()
    },
  },
}
vue
<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.

aimd
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:

ts
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:

vue
<AimdRecorder locale="zh-CN" />
<AimdQuizRecorder :quiz="quiz" locale="zh-CN" />

Standalone quiz usage:

vue
<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:

vue
<AimdRecorder
  v-model="record"
  :content="content"
  :quiz-grades="quizGrades"
  choice-option-explanation-mode="selected"
  scale-grade-display-mode="submitted"
/>

Standalone quiz:

vue
<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 choice and standard blank items locally
  • grade deterministic scale items locally, then use scaleGradeDisplayMode to control whether the result appears on completion or only after submit
  • use a backend provider for open items or highly flexible blanks
  • for practice, pass quizGrades in real time so learners can immediately see status, score, and feedback
  • if a choice option defines explanation, use choiceOptionExplanationMode="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" with choiceOptionExplanationMode="submitted"
  • if a scale result should appear only after submit, combine :submitted="isSubmitted" with scaleGradeDisplayMode="submitted"
  • if you want option explanations to appear only after grading is available, use choiceOptionExplanationMode="graded"
  • for exams, omit quizGrades until grading is finalized
  • unanswered quizzes with status ungraded do 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:

vue
<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.