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.