Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
165 commits
Select commit Hold shift + click to select a range
c85fcad
test(core): require strict playable stem artifact references
seonghobae Sep 4, 2026
99bc70f
feat(core): validate path-free playable stem references
seonghobae Sep 4, 2026
52f7f3e
test(core): make playable stem contract fixtures structural
seonghobae Sep 4, 2026
3975078
refactor(core): name native playable stem path derivation
seonghobae Sep 4, 2026
c5cfac3
test(core): cover nonfinite stem contract guards
seonghobae Sep 4, 2026
2f48920
test(core): cover every fixed playable stem filename
seonghobae Sep 4, 2026
e362c43
test(core): pin classic RIFF PCM16 sample limit
seonghobae Sep 4, 2026
df2a305
fix(core): enforce classic RIFF PCM16 size limit
seonghobae Sep 4, 2026
a22d890
feat(core): isolate native artifact metadata from renderer status
seonghobae Sep 4, 2026
a40a607
test(core): require native-only process status isolation
seonghobae Sep 4, 2026
ace6947
test(core): make process-status failures type-safe
seonghobae Sep 4, 2026
4955cbe
chore(repair): verify playable stem core integration
seonghobae Sep 4, 2026
8900bb6
chore(ci): remove self-modifying stem repair workflows
seonghobae Sep 4, 2026
64bed35
test(core): bind playable stem contract to production crate
seonghobae Sep 4, 2026
804be80
fix(core): publish playable stem process contracts
seonghobae Sep 4, 2026
0318896
chore(stack): restack playable stem contract on status wiring
seonghobae Sep 4, 2026
27e1818
fix(stems): consume strict process status at Tauri boundary
seonghobae Sep 4, 2026
ce38871
chore(stems): restack native contract on current stem parent
seonghobae Sep 4, 2026
a22a20a
feat(core): add streaming SHA-256 integrity verifier
seonghobae Sep 4, 2026
22b88d7
revert(core): remove unintegrated SHA-256 helper
seonghobae Sep 4, 2026
366024a
feat(stems): establish native playable-stem admission library
seonghobae Sep 4, 2026
8306554
feat(stems): preflight actual WAV artifacts natively
seonghobae Sep 4, 2026
652bedb
refactor(stems): isolate streaming SHA-256 verifier
seonghobae Sep 4, 2026
54d86e4
refactor(stems): separate actual-file admission domain service
seonghobae Sep 4, 2026
f2b73fe
refactor(stems): switch admission module to owned directory layout
seonghobae Sep 4, 2026
20c4a12
docs(traceability): map native stem admission evidence
seonghobae Sep 4, 2026
0f7c774
feat(player): bind preflighted stems to native authority
seonghobae Sep 4, 2026
b6f6173
docs(traceability): record stem authority binding boundary
seonghobae Sep 4, 2026
3f8722f
chore(stems): restack native admission on current materialization
seonghobae Sep 4, 2026
e80061d
test(player): bind stems only from final process status
seonghobae Sep 4, 2026
e545e19
fix(player): retain exact final process status
seonghobae Sep 4, 2026
a9bc5a8
fix(player): couple stem binding to final process envelope
seonghobae Sep 4, 2026
85c474c
fix(player): preserve score workspace calls
seonghobae Sep 4, 2026
df86bac
docs(player): trace final-envelope authority coupling
seonghobae Sep 4, 2026
b3da148
test(player): fail closed on malformed JSONL status
seonghobae Sep 4, 2026
a424243
fix(player): fail closed on invalid process status lines
seonghobae Sep 4, 2026
ee5c208
fix(player): reject malformed analysis JSONL
seonghobae Sep 4, 2026
fe9bb21
test(player): enforce analysis process identity and terminal state
seonghobae Sep 4, 2026
00d0abf
test(player): keep process contract RED focused
seonghobae Sep 4, 2026
d18ede4
fix(player): validate process identity and terminal state
seonghobae Sep 4, 2026
ceea278
fix(player): bind analysis output to requested job
seonghobae Sep 4, 2026
b92291f
test(player): avoid moving terminal state from shared status
seonghobae Sep 4, 2026
68b3083
style(player): keep process contract rustfmt-ready
seonghobae Sep 4, 2026
17f0e88
docs(player): trace fail-closed process contract
seonghobae Sep 4, 2026
d4f6a8e
test(player): buffer terminal process status
seonghobae Sep 4, 2026
1576971
fix(player): classify renderer progress separately
seonghobae Sep 4, 2026
810c4b8
fix(player): buffer producer terminal status until exit
seonghobae Sep 4, 2026
91cc5dd
docs(player): trace terminal-event buffering
seonghobae Sep 4, 2026
25cbd3b
test(player): require canonical stem authority handles
seonghobae Sep 4, 2026
83ebdfd
fix(player): admit canonical opaque stem handles
seonghobae Sep 4, 2026
8093c53
test(player): define fail-closed stem source availability contract
seonghobae Sep 4, 2026
7929c74
feat(player): validate native stem availability before selector proje…
seonghobae Sep 4, 2026
0701d99
fix(player): keep playback source projection type-safe and documented
seonghobae Sep 4, 2026
7839a59
test(player): require atomic native source availability
seonghobae Sep 4, 2026
9e36c83
fix(player): keep native source availability atomic
seonghobae Sep 4, 2026
ca4ed24
feat(player): expose native playback source availability
seonghobae Sep 4, 2026
2ac5978
docs(player): trace native source availability boundary
seonghobae Sep 4, 2026
6bc0b49
test(player): require renderer-safe playback source discovery
seonghobae Sep 4, 2026
42fa249
feat(player): revalidate native playback source discovery in renderer
seonghobae Sep 4, 2026
95f9ee4
docs(player): trace renderer-safe source discovery
seonghobae Sep 4, 2026
d3df376
test(player): require source-switch transport continuity
seonghobae Sep 4, 2026
4b78eab
feat(player): define fail-closed source-switch continuity
seonghobae Sep 4, 2026
899f122
test(player): require stale-safe source discovery session
seonghobae Sep 4, 2026
d37a7b8
feat(player): bind source discovery to current authority
seonghobae Sep 4, 2026
1f933ec
docs(player): trace source discovery lifetime
seonghobae Sep 4, 2026
cf77f57
test(player): reject hostile source discovery objects
seonghobae Sep 4, 2026
5770392
fix(player): fail closed on hostile discovery payloads
seonghobae Sep 4, 2026
0961084
docs(player): record hostile discovery repair
seonghobae Sep 4, 2026
5be9d68
test(player): bind source-switch receipts to target identity
seonghobae Sep 4, 2026
799c6b6
fix(player): reject stale media-switch receipts
seonghobae Sep 4, 2026
9adf2cc
docs(player): trace stale media-switch receipt repair
seonghobae Sep 4, 2026
b41fdb4
chore(stack): adopt current stem-publication parent
seonghobae Sep 4, 2026
c899df4
test(player): reject playback discovery sequence reuse
seonghobae Sep 4, 2026
8a94844
fix(player): never reuse playback discovery sequence identities
seonghobae Sep 4, 2026
d988208
docs(traceability): record playback discovery identity exhaustion repair
seonghobae Sep 4, 2026
e175246
test(player): reject foreign source switch authorities
seonghobae Sep 4, 2026
bf33e41
fix(player): bind source switches to one project authority
seonghobae Sep 4, 2026
b3e3f89
test(player): cover canonical playback authority parsing
seonghobae Sep 4, 2026
7590d43
docs(player): trace same-project source switch authority
seonghobae Sep 4, 2026
102ceb0
test(player): require monotonic source-switch session receipts
seonghobae Sep 4, 2026
7cc68bf
fix(player): invalidate stale media receipts on source switch start
seonghobae Sep 4, 2026
3be991c
docs(player): trace source-switch receipt invalidation
seonghobae Sep 4, 2026
881e53b
test(player): reject mutable source-switch receipts
seonghobae Sep 4, 2026
3fbd43b
fix(player): freeze source-switch receipt identity
seonghobae Sep 4, 2026
d1bf63f
docs(traceability): record immutable playback receipts
seonghobae Sep 4, 2026
a79af75
test(player): freeze playback source session receipts
seonghobae Sep 4, 2026
580677a
fix(player): freeze playback source authority snapshots
seonghobae Sep 4, 2026
3068f01
docs(traceability): record playback source snapshot immutability repair
seonghobae Sep 4, 2026
616cae0
test(player): reject forged playback discovery receipt
seonghobae Sep 4, 2026
c514379
fix(player): bind playback discovery completion to issued receipt
seonghobae Sep 4, 2026
0dbc594
docs(traceability): bind playback discovery to exact receipt
seonghobae Sep 4, 2026
c8c09f3
docs(traceability): record focused receipt verification
seonghobae Sep 4, 2026
a2222a4
test(player): retire only exact admitted switch receipt
seonghobae Sep 4, 2026
73b0e47
fix(player): retire exact admitted source-switch receipt
seonghobae Sep 4, 2026
1b8ace1
docs(traceability): retire consumed playback switch receipts
seonghobae Sep 4, 2026
77a708e
test(player): require failed switch receipt retirement
seonghobae Sep 4, 2026
ba06f0c
fix(player): retire failed switch receipts
seonghobae Sep 4, 2026
594f647
docs(traceability): record failed switch retirement
seonghobae Sep 4, 2026
772810f
test(player): reject copied switch admission receipt
seonghobae Sep 4, 2026
06548e1
fix(player): bind metadata admission to active switch receipt
seonghobae Sep 4, 2026
d9ebc13
test(player): bind switch admission to active session
seonghobae Sep 4, 2026
2592652
test(player): update switch completion admission contract
seonghobae Sep 4, 2026
a998f69
test(player): update switch immutability admission contract
seonghobae Sep 4, 2026
7316b6b
docs(traceability): bind switch admission to issued active plan
seonghobae Sep 4, 2026
6a9f892
test(player): require mounted stem source selection
seonghobae Sep 5, 2026
88ded97
feat(player): mount native stem source selector
seonghobae Sep 5, 2026
ad96e16
test(player): reject stale selector discovery after project rotation
seonghobae Sep 5, 2026
71c03bc
fix(player): hide stale source snapshot during project rotation
seonghobae Sep 5, 2026
edaeb3b
docs(traceability): record mounted stem selector boundary
seonghobae Sep 5, 2026
6e92826
test(player): isolate source controls across mounted players
seonghobae Sep 5, 2026
29b51d7
fix(player): scope playback source radio groups per mount
seonghobae Sep 5, 2026
d4e972e
docs(traceability): record selector instance isolation
seonghobae Sep 5, 2026
7463e3e
test(player): reject malformed switch loop timing
seonghobae Sep 5, 2026
836f159
fix(player): fail closed on malformed switch loop timing
seonghobae Sep 5, 2026
d1d826b
docs(traceability): record switch loop timing admission
seonghobae Sep 5, 2026
dd2c8e3
test(player): reject paused count-in source restoration
seonghobae Sep 5, 2026
b1208bb
fix(player): reject source switch during paused count-in
seonghobae Sep 5, 2026
7b71a36
docs(traceability): record paused count-in source-switch boundary
seonghobae Sep 5, 2026
e5dd45e
test(player): require admitted seamless stem switch
seonghobae Sep 5, 2026
6e99b7c
test(player): use admitted loop position for stem switch
seonghobae Sep 5, 2026
eba21f5
feat(player): bind source switch to media admission
seonghobae Sep 5, 2026
a3ef7f1
test(player): reject failed stem switch receipt reuse
seonghobae Sep 5, 2026
70bb51b
test(player): make source admission receipt single-use
seonghobae Sep 5, 2026
772f9da
fix(player): retire media admission exactly once
seonghobae Sep 5, 2026
5818db1
docs(traceability): record mounted source switch transaction
seonghobae Sep 5, 2026
3725d43
test(player): keep project rotation non-playing
seonghobae Sep 5, 2026
a2de609
test(player): require project-scoped transport remount
seonghobae Sep 5, 2026
0b01c3e
fix(player): reset transport on project authority rotation
seonghobae Sep 5, 2026
52b8c6c
docs(traceability): record project rotation reset
seonghobae Sep 5, 2026
29d9026
test(player): reject stale play errors during source switch
seonghobae Sep 5, 2026
b5c760e
fix(player): retire stale play promises before source replacement
seonghobae Sep 5, 2026
874afdb
docs(traceability): record stale play-promise retirement
seonghobae Sep 5, 2026
4e9276c
test(player): refresh native sources after stem playback failure
seonghobae Sep 5, 2026
24040b4
fix(player): fail closed on revoked mounted stem source
seonghobae Sep 5, 2026
ded2406
docs(traceability): record mounted stem revocation recovery
seonghobae Sep 5, 2026
0926ab8
test(project): expose current .bscope contract drift
seonghobae Sep 5, 2026
38b1328
fix(project): preserve current rehearsal song fields
seonghobae Sep 5, 2026
44601e9
docs(project): trace persistence contract parity repair
seonghobae Sep 5, 2026
9784415
docs(project): align local format with current song contract
seonghobae Sep 5, 2026
84adeb0
test(player): require localized playback source copy
seonghobae Sep 5, 2026
0b25a7f
feat(i18n): add playback source English resource
seonghobae Sep 5, 2026
0e81410
feat(i18n): add playback source Korean resource
seonghobae Sep 5, 2026
87c991c
feat(player): add playback source copy adapter
seonghobae Sep 5, 2026
5addcd4
feat(player): localize playback source selector
seonghobae Sep 5, 2026
0d42457
fix(player): keep playback copy branch-free
seonghobae Sep 5, 2026
90e4add
docs(player): trace playback source localization boundary
seonghobae Sep 5, 2026
f22d1a1
docs(player): ground selector copy in WAI semantics
seonghobae Sep 5, 2026
c55e15d
test(player): require discovery loading feedback
seonghobae Sep 5, 2026
1e2eb0b
fix(player): announce playback source discovery
seonghobae Sep 5, 2026
ff7b418
test(a11y): require atomic discovery status
seonghobae Sep 5, 2026
b959281
fix(a11y): keep discovery status atomic
seonghobae Sep 5, 2026
ae25496
docs(player): trace discovery waiting-state accessibility
seonghobae Sep 5, 2026
1fafe1f
test(player): target discovery status precisely
seonghobae Sep 5, 2026
6fa1bb4
docs(player): record discovery status review repair
seonghobae Sep 5, 2026
6d414bd
test(i18n): restore navigator language after selector regression
seonghobae Sep 5, 2026
298c76b
test(player): require source discovery empty and retry states
seonghobae Sep 5, 2026
d2a2d5f
fix(player): classify playback source discovery outcomes
seonghobae Sep 5, 2026
4599cfa
feat(player): add source discovery empty and error copy
seonghobae Sep 5, 2026
cf29ef0
feat(player): localize source discovery fallback states
seonghobae Sep 5, 2026
0b4d138
feat(player): expose source discovery empty and retry states
seonghobae Sep 5, 2026
447e8a4
test(player): pin playback source discovery result classes
seonghobae Sep 5, 2026
0e1afde
docs(player): trace source discovery fallback states
seonghobae Sep 5, 2026
856d37f
test(player): verify Korean discovery fallback copy
seonghobae Sep 5, 2026
442b87f
test(player): use canonical playback authority in locale fixtures
seonghobae Sep 5, 2026
332240d
docs(player): record locale fallback evidence repair
seonghobae Sep 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions apps/desktop/core/src/analysis_process_status.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
//! Parse analysis-process status without leaking native artifact metadata.
//!
//! Python analysis may emit a path-free playable-stem artifact reference for the
//! trusted native process. The renderer status must not carry that reference's
//! hashes, sizes, or storage-derived identity. This module removes and validates
//! the optional native-only field before deserializing the existing public status.

use crate::{
playable_stem_contract::PlayableStemArtifactSetReference, AnalysisJobState,
AnalysisJobStatus,
};
use serde_json::Value;

const PROCESS_STATUS_ERROR: &str = "Analysis engine returned an invalid response.";

/// One validated analysis-process status split into public and native-only data.
#[derive(Clone, Debug)]
pub struct AnalysisProcessStatus {
renderer_status: AnalysisJobStatus,
playable_stem_artifact_set: Option<PlayableStemArtifactSetReference>,
}

impl AnalysisProcessStatus {
/// Return the renderer-safe status after native-only metadata was removed.
pub const fn renderer_status(&self) -> &AnalysisJobStatus {
&self.renderer_status
}

/// Return the optional native-only playable-stem reference.
pub const fn playable_stem_artifact_set(
&self,
) -> Option<&PlayableStemArtifactSetReference> {
self.playable_stem_artifact_set.as_ref()
}

/// Consume the parsed envelope and return its independently owned parts.
pub fn into_parts(
self,
) -> (
AnalysisJobStatus,
Option<PlayableStemArtifactSetReference>,
) {
(self.renderer_status, self.playable_stem_artifact_set)
}
}

/// Replace the retained native process envelope with the newest validated status.
///
/// Renderer delivery receives a clone containing only public status fields while
/// native callers keep the complete envelope. Replacing the whole envelope is
/// intentional: a later terminal status without a stem reference must revoke an
/// earlier status's native reference instead of leaving stale metadata eligible
/// for playback-authority binding.
pub fn retain_latest_process_status(
latest_process_status: &mut Option<AnalysisProcessStatus>,
process_status: AnalysisProcessStatus,
) -> AnalysisJobStatus {
let renderer_status = process_status.renderer_status.clone();
*latest_process_status = Some(process_status);
renderer_status
}

/// Require a process envelope to belong to the native job that requested it.
pub fn validate_analysis_process_status_for_job(
process_status: &AnalysisProcessStatus,
expected_job_id: &str,
) -> Result<(), &'static str> {
if process_status.renderer_status.job_id == expected_job_id {
Ok(())
} else {
Err(PROCESS_STATUS_ERROR)
}
}

/// Return whether a validated status is safe to expose as in-flight progress.
///
/// Producer terminal states are withheld until the subprocess exits and the
/// complete JSONL stream is known to be valid. This prevents an early succeeded
/// event from being observed before a later malformed line fails the process.
pub fn is_analysis_process_progress_status(process_status: &AnalysisProcessStatus) -> bool {
matches!(
&process_status.renderer_status.state,
AnalysisJobState::Queued | AnalysisJobState::Running
)
}

/// Require the process's final retained envelope to be terminal and job-local.
pub fn validate_final_analysis_process_status<'a>(
process_status: Option<&'a AnalysisProcessStatus>,
expected_job_id: &str,
) -> Result<&'a AnalysisProcessStatus, &'static str> {
let process_status = process_status.ok_or(PROCESS_STATUS_ERROR)?;
validate_analysis_process_status_for_job(process_status, expected_job_id)?;
if matches!(
&process_status.renderer_status.state,
AnalysisJobState::Succeeded | AnalysisJobState::Failed
) {
Ok(process_status)
} else {
Err(PROCESS_STATUS_ERROR)
}
}

/// Parse one stdout JSONL line, ignoring only whitespace-only separators.
///
/// A non-empty malformed line is a process-contract failure. Returning an error
/// instead of skipping it prevents an earlier valid envelope from becoming the
/// apparent final result after corrupted or future producer output.
pub fn parse_analysis_process_status_line(
process_status_line: &str,
) -> Result<Option<AnalysisProcessStatus>, &'static str> {
let trimmed = process_status_line.trim();
if trimmed.is_empty() {
Ok(None)
} else {
parse_analysis_process_status(trimmed).map(Some)
}
}

/// Parse one JSONL status and isolate its optional native-only artifact reference.
pub fn parse_analysis_process_status(
process_status_json: &str,
) -> Result<AnalysisProcessStatus, &'static str> {
let mut process_status_value =
serde_json::from_str::<Value>(process_status_json).map_err(|_| PROCESS_STATUS_ERROR)?;
let process_status_object = process_status_value
.as_object_mut()
.ok_or(PROCESS_STATUS_ERROR)?;
let playable_stem_value = process_status_object.remove("playableStemArtifactSet");

let renderer_status =
serde_json::from_value::<AnalysisJobStatus>(process_status_value)
.map_err(|_| PROCESS_STATUS_ERROR)?;
let playable_stem_artifact_set = playable_stem_value
.map(serde_json::from_value::<PlayableStemArtifactSetReference>)
.transpose()
.map_err(|_| PROCESS_STATUS_ERROR)?;

let state_payload_is_valid = match &renderer_status.state {
AnalysisJobState::Succeeded => {
renderer_status.result.is_some() && renderer_status.error.is_none()
}
AnalysisJobState::Failed => {
renderer_status.result.is_none() && renderer_status.error.is_some()
}
AnalysisJobState::Queued | AnalysisJobState::Running => {
renderer_status.result.is_none() && renderer_status.error.is_none()
}
};
if !state_payload_is_valid {
return Err(PROCESS_STATUS_ERROR);
}

if playable_stem_artifact_set.is_some()
&& !matches!(&renderer_status.state, AnalysisJobState::Succeeded)
{
return Err(PROCESS_STATUS_ERROR);
}

Ok(AnalysisProcessStatus {
renderer_status,
playable_stem_artifact_set,
})
}
67 changes: 67 additions & 0 deletions apps/desktop/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
//! measured for coverage) on any platform without a windowing system or a
//! bundled frontend.

pub mod analysis_process_status;
pub mod playable_stem_contract;

use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use std::{
Expand Down Expand Up @@ -122,12 +125,59 @@ pub enum AnalysisCacheStatus {
pub struct RehearsalSongPayload {
id: String,
title: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
tempo: Option<f64>,
sections: Vec<RehearsalSectionPayload>,
export_summary: ExportSummaryPayload,
#[serde(default, skip_serializing_if = "Option::is_none")]
collaboration: Option<RehearsalCollaborationPayload>,
#[serde(default, skip_serializing_if = "Option::is_none")]
score_attachments: Option<Vec<ScoreAttachmentMetadataPayload>>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct RehearsalAssignmentPayload {
id: String,
assignee: String,
summary: String,
section_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
role_id: Option<String>,
status: String,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct RehearsalCommentPayload {
id: String,
author: String,
body: String,
section_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
role_id: Option<String>,
status: String,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct RehearsalApprovalPayload {
id: String,
scope: String,
owner: String,
status: String,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct RehearsalCollaborationPayload {
sync_mode: String,
sync_note: String,
assignments: Vec<RehearsalAssignmentPayload>,
comments: Vec<RehearsalCommentPayload>,
approvals: Vec<RehearsalApprovalPayload>,
}

/// Score attachment metadata persisted inside the song payload. Only the
/// locally minted score id and the display file name cross the IPC boundary;
/// the PDF bytes stay in the app-owned scores directory keyed by that id.
Expand Down Expand Up @@ -176,21 +226,38 @@ pub struct ManualOverridePayload {
source: String,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct TranscriptionNotePayload {
pitch: String,
onset: f64,
offset: f64,
velocity: f64,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct RehearsalRolePayload {
id: String,
name: String,
role_type: String,
harmony: HarmonyPayload,
#[serde(default, skip_serializing_if = "Option::is_none")]
harmonic_explanation: Option<String>,
cue: CuePayload,
range: RangePayload,
confidence: ConfidencePayload,
rehearsal_priority: String,
simplification: String,
setup_note: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
transposition_plan: Option<String>,
manual_overrides: Vec<ManualOverridePayload>,
overlap_warnings: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
transcription: Option<Vec<TranscriptionNotePayload>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
practice_progress: Option<u8>,
}

#[derive(Clone, Debug, Serialize)]
Expand Down
Loading