marionette / src /shared /types.ts
Thibaud Frère
feat: initial marionette v2 release
44aec57
/**
* App-wide structural types. Kept narrow on purpose: the SDK is the
* source of truth for robot state, and views only need a small slice
* (poses, motion frames, the saved-move record).
*/
/** Row-major flat-16 head matrix. The SDK's wire shape. */
export type FlatHead = number[];
/** Nested 4x4 head matrix. The app's internal shape (see motor.ts). */
export type NestedHead = number[][];
/** [left, right] antenna angles in radians. */
export type Antennas = [number, number];
/**
* A single recorded actuator frame. Stored in nested + camelCase shape;
* the SDK boundary (motor.ts) converts to/from the flat wire form.
*/
export interface MotionFrame {
head: NestedHead | null;
antennas: Antennas | null;
body_yaw: number;
check_collision: boolean;
}
/**
* A full recorded motion. The Python Marionette + downstream tooling
* expects this exact shape on disk (snake_case `set_target_data`), so
* we keep it 1:1 with the v1 schema even though we're now in TS.
*/
export interface Motion {
time: number[];
set_target_data: MotionFrame[];
duration?: number;
frameCount?: number;
capturedHz?: number;
}
/**
* Audio source attached to a move. `silent` means the move records
* pure motion with no audio reference.
*/
export type AudioMode = "silent" | "upload" | "record_now" | "record_live";
/** Where audio playback should land. Mirrors v1's settings.audioOutput. */
export type AudioOutput = "robot" | "device";
/** Where the recording mic should pull from. Mirrors v1's state.audioMic. */
export type AudioMic = "device" | "robot";
/**
* Persisted move row in IndexedDB. Kept flat on purpose (no folders
* in the MVP) - community provenance is carried inline so we can
* show a "from @author" chip on the row.
*/
export interface MoveRecord {
id: string;
label: string;
emoji: string;
motion: Motion;
audioBlob: Blob | null;
audioMode: AudioMode;
duration: number;
createdAt: number;
// ─── Community provenance ─────────────────────────────────────
/** Set on community-imported moves, never on user-recorded ones. */
communityAuthor?: string;
/** Original HF dataset id (`org/name`). */
communityRepoId?: string;
/** File path inside the repo, e.g. `data/wave.json`. Used as the
* idempotency key when an import targets a move already present. */
communityMovePath?: string;
/** Free-form description from the dataset's JSON, if any. */
description?: string;
// ─── Share-to-HF state ────────────────────────────────────────
/** True once the user has pushed this move to their own dataset. */
shared?: boolean;
/** HF repo the move was last shared to (`<owner>/<repo>`). */
sharedRepoId?: string;
}
/**
* One Marionette community dataset as listed by HF
* (`?filter=reachy_mini_community_moves`).
*/
export interface CommunityDatasetSummary {
/** `org/name` - the canonical HF repo id. */
repoId: string;
/** Display name (last path segment of `repoId`). */
name: string;
/** First path segment of `repoId`. */
author: string;
emoji: string;
downloads: number;
/** ISO timestamp from HF (`lastModified` / `last_modified`). */
updated: string | null;
/** Lazy field: filled in by `HF.countMoves` after the list arrives. */
movesCount: number | null;
}
/**
* A single `.json` inside a dataset, paired with a side-by-side
* `.wav` when one exists.
*/
export interface CommunityMoveSummary {
/** Stable id derived from `repoId` + path (used for idempotency). */
id: string;
/** `data/wave.json` - the path inside the repo. */
path: string;
/** Display name (filename without extension). */
label: string;
hasAudio: boolean;
}
/** Persisted user preferences. */
export interface AppSettings {
audioOutput: AudioOutput;
robotVolume: number;
debug: boolean;
}