commit c6a3ce4974104d42eb8f3e8d11391f19017c36ff
parent a1a994c8a21ebbe5fac5575e5cdedb8520f5bac3
Author: triesap <tyson@radroots.org>
Date: Wed, 22 Apr 2026 21:47:55 +0000
app: add pack day print failure contracts
- add a typed avery 5160 overflow failure enum and pack day print state payload
- expose localized overflow failure copy in shared i18n keys and english messages
- map customer-label overflow failures to a dedicated desktop status title and source guard
- extend shared model, state, i18n, and window tests for the hardening contract
Diffstat:
7 files changed, 143 insertions(+), 42 deletions(-)
diff --git a/crates/launchers/desktop/src/source_guards.rs b/crates/launchers/desktop/src/source_guards.rs
@@ -486,6 +486,7 @@ const REQUIRED_WINDOW_COPY_KEYS: &[&str] = &[
"AppTextKey::PackDayExportFolderLabel",
"AppTextKey::PackDayExportFilesLabel",
"AppTextKey::PackDayExportErrorLabel",
+ "AppTextKey::PackDayPrintCustomerLabelsAvery5160OverflowFailedTitle",
"AppTextKey::PackDayHostHandoffRevealAction",
"AppTextKey::PackDayHostHandoffRevealActionRunning",
"AppTextKey::PackDayHostHandoffOpenPackSheetAction",
diff --git a/crates/launchers/desktop/src/window.rs b/crates/launchers/desktop/src/window.rs
@@ -21,8 +21,8 @@ use radroots_app_models::{
OrderDetailItemRow, OrderDetailProjection, OrderId, OrderListRow, OrderPrimaryAction,
OrderRecoveryProjection, OrderStatus, OrdersFilter, OrdersListRow, PackDayExportBundle,
PackDayExportStatus, PackDayHostHandoffKind, PackDayHostHandoffStatus, PackDayPackListRow,
- PackDayPrintKind, PackDayPrintStatus, PackDayProductTotalRow, PackDayRosterRow,
- PersonalEntryState, PersonalSection, PickupLocationId, PickupLocationRecord,
+ PackDayPrintFailureKind, PackDayPrintKind, PackDayPrintStatus, PackDayProductTotalRow,
+ PackDayRosterRow, PersonalEntryState, PersonalSection, PickupLocationId, PickupLocationRecord,
ProductAttentionState, ProductEditorDraft, ProductId, ProductListRow, ProductPricePresentation,
ProductPublishBlocker, ProductStatus, ProductsFilter, ProductsListRow, ProductsSort,
RecoveryKind, RecoveryState, ReminderDeadlineProjection, ReminderDeliveryState, ReminderId,
@@ -10419,58 +10419,67 @@ fn pack_day_print_status_presentation(
) -> Option<PackDayPrintStatusPresentation> {
let print = &runtime.pack_day_projection.print;
let kind = print.request.as_ref()?.kind;
+ let failure = print.failure;
- let status = match (print.status, kind) {
- (PackDayPrintStatus::Idle, _) => return None,
- (PackDayPrintStatus::Running, PackDayPrintKind::PrintPackSheet) => {
+ let status = match (print.status, kind, failure) {
+ (PackDayPrintStatus::Idle, _, _) => return None,
+ (PackDayPrintStatus::Running, PackDayPrintKind::PrintPackSheet, _) => {
PackDayPrintStatusPresentation {
indicator_color: APP_UI_THEME.foundation.text.accent,
title_key: AppTextKey::PackDayPrintPackSheetQueuedTitle,
}
}
- (PackDayPrintStatus::Running, PackDayPrintKind::PrintPickupRoster) => {
+ (PackDayPrintStatus::Running, PackDayPrintKind::PrintPickupRoster, _) => {
PackDayPrintStatusPresentation {
indicator_color: APP_UI_THEME.foundation.text.accent,
title_key: AppTextKey::PackDayPrintPickupRosterQueuedTitle,
}
}
- (PackDayPrintStatus::Running, PackDayPrintKind::PrintCustomerLabels) => {
+ (PackDayPrintStatus::Running, PackDayPrintKind::PrintCustomerLabels, _) => {
PackDayPrintStatusPresentation {
indicator_color: APP_UI_THEME.foundation.text.accent,
title_key: AppTextKey::PackDayPrintCustomerLabelsQueuedTitle,
}
}
- (PackDayPrintStatus::Succeeded, PackDayPrintKind::PrintPackSheet) => {
+ (PackDayPrintStatus::Succeeded, PackDayPrintKind::PrintPackSheet, _) => {
PackDayPrintStatusPresentation {
indicator_color: APP_UI_THEME.components.app_status_indicator.online,
title_key: AppTextKey::PackDayPrintPackSheetSubmittedTitle,
}
}
- (PackDayPrintStatus::Succeeded, PackDayPrintKind::PrintPickupRoster) => {
+ (PackDayPrintStatus::Succeeded, PackDayPrintKind::PrintPickupRoster, _) => {
PackDayPrintStatusPresentation {
indicator_color: APP_UI_THEME.components.app_status_indicator.online,
title_key: AppTextKey::PackDayPrintPickupRosterSubmittedTitle,
}
}
- (PackDayPrintStatus::Succeeded, PackDayPrintKind::PrintCustomerLabels) => {
+ (PackDayPrintStatus::Succeeded, PackDayPrintKind::PrintCustomerLabels, _) => {
PackDayPrintStatusPresentation {
indicator_color: APP_UI_THEME.components.app_status_indicator.online,
title_key: AppTextKey::PackDayPrintCustomerLabelsSubmittedTitle,
}
}
- (PackDayPrintStatus::Failed, PackDayPrintKind::PrintPackSheet) => {
+ (PackDayPrintStatus::Failed, PackDayPrintKind::PrintPackSheet, _) => {
PackDayPrintStatusPresentation {
indicator_color: APP_UI_THEME.components.app_status_indicator.attention,
title_key: AppTextKey::PackDayPrintPackSheetFailedTitle,
}
}
- (PackDayPrintStatus::Failed, PackDayPrintKind::PrintPickupRoster) => {
+ (PackDayPrintStatus::Failed, PackDayPrintKind::PrintPickupRoster, _) => {
PackDayPrintStatusPresentation {
indicator_color: APP_UI_THEME.components.app_status_indicator.attention,
title_key: AppTextKey::PackDayPrintPickupRosterFailedTitle,
}
}
- (PackDayPrintStatus::Failed, PackDayPrintKind::PrintCustomerLabels) => {
+ (
+ PackDayPrintStatus::Failed,
+ PackDayPrintKind::PrintCustomerLabels,
+ Some(PackDayPrintFailureKind::CustomerLabelsAvery5160Overflow),
+ ) => PackDayPrintStatusPresentation {
+ indicator_color: APP_UI_THEME.components.app_status_indicator.attention,
+ title_key: AppTextKey::PackDayPrintCustomerLabelsAvery5160OverflowFailedTitle,
+ },
+ (PackDayPrintStatus::Failed, PackDayPrintKind::PrintCustomerLabels, _) => {
PackDayPrintStatusPresentation {
indicator_color: APP_UI_THEME.components.app_status_indicator.attention,
title_key: AppTextKey::PackDayPrintCustomerLabelsFailedTitle,
@@ -12619,11 +12628,12 @@ mod tests {
FulfillmentWindowSummary, LoggedOutStartupPhase, LoggedOutStartupProjection,
OrderDetailProjection, OrderId, OrderPrimaryAction, OrderStatus, OrdersListRow,
PackDayExportArtifact, PackDayExportArtifactKind, PackDayExportBundle,
- PackDayHostHandoffKind, PackDayHostHandoffStatus, PackDayPrintKind, PackDayPrintStatus,
- PackDayProductTotalRow, PackDayProjection, PersonalSection, ReminderDeadlineProjection,
- ReminderDeliveryState, ReminderId, ReminderKind, ReminderSurface, ReminderUrgency,
- RepeatDemandEligibility, RepeatDemandHandoffProjection, ShellSection,
- TodayAgendaProjection, TodaySetupTask, TodaySetupTaskKind,
+ PackDayHostHandoffKind, PackDayHostHandoffStatus, PackDayPrintFailureKind,
+ PackDayPrintKind, PackDayPrintStatus, PackDayProductTotalRow, PackDayProjection,
+ PersonalSection, ReminderDeadlineProjection, ReminderDeliveryState, ReminderId,
+ ReminderKind, ReminderSurface, ReminderUrgency, RepeatDemandEligibility,
+ RepeatDemandHandoffProjection, ShellSection, TodayAgendaProjection, TodaySetupTask,
+ TodaySetupTaskKind,
};
use radroots_app_remote_signer::{
RadrootsAppRemoteSignerApprovedSession, RadrootsAppRemoteSignerPendingSession,
@@ -13691,6 +13701,20 @@ mod tests {
title_key: AppTextKey::PackDayPrintCustomerLabelsFailedTitle,
})
);
+
+ let overflow_request =
+ PackDayPrintRequest::for_bundle(PackDayPrintKind::PrintCustomerLabels, &bundle);
+ runtime.pack_day_projection.print = PackDayPrintProjection::failed_with_kind(
+ overflow_request,
+ PackDayPrintFailureKind::CustomerLabelsAvery5160Overflow,
+ );
+ assert_eq!(
+ pack_day_print_status_presentation(&runtime),
+ Some(PackDayPrintStatusPresentation {
+ indicator_color: APP_UI_THEME.components.app_status_indicator.attention,
+ title_key: AppTextKey::PackDayPrintCustomerLabelsAvery5160OverflowFailedTitle,
+ })
+ );
}
#[test]
diff --git a/crates/shared/i18n/src/keys.rs b/crates/shared/i18n/src/keys.rs
@@ -258,6 +258,7 @@ define_app_text_keys! {
PackDayPrintCustomerLabelsQueuedTitle => "pack_day.print.customer_labels.queued.title",
PackDayPrintCustomerLabelsSubmittedTitle => "pack_day.print.customer_labels.submitted.title",
PackDayPrintCustomerLabelsFailedTitle => "pack_day.print.customer_labels.failed.title",
+ PackDayPrintCustomerLabelsAvery5160OverflowFailedTitle => "pack_day.print.customer_labels.avery_5160_overflow.failed.title",
PackDayHostHandoffRevealAction => "pack_day.host_handoff.reveal.action",
PackDayHostHandoffRevealActionRunning => "pack_day.host_handoff.reveal.action.running",
PackDayHostHandoffOpenPackSheetAction => "pack_day.host_handoff.open_pack_sheet.action",
diff --git a/crates/shared/i18n/src/lib.rs b/crates/shared/i18n/src/lib.rs
@@ -544,6 +544,10 @@ mod tests {
"Couldn't print customer labels"
);
assert_eq!(
+ app_text(AppTextKey::PackDayPrintCustomerLabelsAvery5160OverflowFailedTitle),
+ "Customer labels do not fit Avery 5160"
+ );
+ assert_eq!(
app_text(AppTextKey::PackDayHostHandoffRevealAction),
"Show in Finder"
);
diff --git a/crates/shared/models/src/lib.rs b/crates/shared/models/src/lib.rs
@@ -1669,6 +1669,20 @@ impl PackDayPrintLabelStock {
}
}
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub enum PackDayPrintFailureKind {
+ CustomerLabelsAvery5160Overflow,
+}
+
+impl PackDayPrintFailureKind {
+ pub const fn storage_key(self) -> &'static str {
+ match self {
+ Self::CustomerLabelsAvery5160Overflow => "customer_labels_avery_5160_overflow",
+ }
+ }
+}
+
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PackDayPrintStatus {
@@ -2474,19 +2488,21 @@ mod tests {
OrderDetailProjection, OrderId, OrderListRow, OrderPrimaryAction, OrderRecoveryProjection,
OrderStatus, OrdersFilter, OrdersListProjection, OrdersListRow, OrdersListSummary,
OrdersScreenQueryState, PackDayExportArtifact, PackDayExportArtifactKind,
- PackDayExportBundle, PackDayExportStatus, PackDayHostHandoffKind, PackDayHostHandoffStatus,
- PackDayOutputCustomerOrder, PackDayOutputOrderState, PackDayOutputPackListEntry,
- PackDayOutputProductTotal, PackDayOutputQuantity, PackDayOutputSource, PackDayOutputWindow,
- PackDayPackListRow, PackDayProductTotalRow, PackDayProjection, PackDayRosterRow,
- PackDayScreenQueryState, ParseStartupSignerSourceError, PersonalEntryProjection,
- PersonalEntryState, PersonalSection, PickupLocationId, ProductAttentionState,
- ProductAvailabilityState, ProductAvailabilitySummary, ProductEditorDraft, ProductListRow,
- ProductPricePresentation, ProductPublishBlocker, ProductStatus, ProductStockState,
- ProductStockSummary, ProductsFilter, ProductsListProjection, ProductsListRow,
- ProductsListSummary, ProductsSort, RecoveryKind, RecoveryQueueProjection, RecoveryRecordId,
- RecoveryState, ReminderDeadlineProjection, ReminderDeliveryState, ReminderFeedProjection,
- ReminderId, ReminderKind, ReminderLogEntryProjection, ReminderLogProjection,
- ReminderSurface, ReminderUrgency, RepeatDemandEligibility, RepeatDemandHandoffProjection,
+ PackDayExportBundle, PackDayExportInstanceId, PackDayExportStatus, PackDayHostHandoffKind,
+ PackDayHostHandoffStatus, PackDayOutputCustomerOrder, PackDayOutputOrderState,
+ PackDayOutputPackListEntry, PackDayOutputProductTotal, PackDayOutputQuantity,
+ PackDayOutputSource, PackDayOutputWindow, PackDayPackListRow, PackDayPrintFailureKind,
+ PackDayPrintKind, PackDayPrintLabelStock, PackDayPrintStatus, PackDayProductTotalRow,
+ PackDayProjection, PackDayRosterRow, PackDayScreenQueryState,
+ ParseStartupSignerSourceError, PersonalEntryProjection, PersonalEntryState,
+ PersonalSection, PickupLocationId, ProductAttentionState, ProductAvailabilityState,
+ ProductAvailabilitySummary, ProductEditorDraft, ProductListRow, ProductPricePresentation,
+ ProductPublishBlocker, ProductStatus, ProductStockState, ProductStockSummary,
+ ProductsFilter, ProductsListProjection, ProductsListRow, ProductsListSummary, ProductsSort,
+ RecoveryKind, RecoveryQueueProjection, RecoveryRecordId, RecoveryState,
+ ReminderDeadlineProjection, ReminderDeliveryState, ReminderFeedProjection, ReminderId,
+ ReminderKind, ReminderLogEntryProjection, ReminderLogProjection, ReminderSurface,
+ ReminderUrgency, RepeatDemandEligibility, RepeatDemandHandoffProjection,
SelectedAccountProjection, SelectedSurfaceProjection, SettingsPreference, SettingsSection,
ShellSection, StartupSignerEntryProjection, StartupSignerSource, StartupSignerSourceKind,
TodayAgendaProjection, TodaySetupTask, TodaySetupTaskKind, TodaySummary,
@@ -3158,6 +3174,10 @@ mod tests {
PackDayPrintLabelStock::Avery5160Letter30Up.storage_key(),
"avery_5160_letter_30_up"
);
+ assert_eq!(
+ PackDayPrintFailureKind::CustomerLabelsAvery5160Overflow.storage_key(),
+ "customer_labels_avery_5160_overflow"
+ );
assert_eq!(PackDayPrintStatus::default(), PackDayPrintStatus::Idle);
assert_eq!(PackDayPrintStatus::Running.storage_key(), "running");
assert_eq!(PackDayPrintStatus::Succeeded.storage_key(), "succeeded");
diff --git a/crates/shared/state/src/lib.rs b/crates/shared/state/src/lib.rs
@@ -16,9 +16,9 @@ use radroots_app_models::{
LoggedOutStartupProjection, OrderDetailProjection, OrderId, OrdersFilter, OrdersListProjection,
OrdersScreenQueryState, PackDayExportArtifactKind, PackDayExportBundle,
PackDayExportInstanceId, PackDayExportStatus, PackDayHostHandoffKind, PackDayHostHandoffStatus,
- PackDayPrintKind, PackDayPrintLabelStock, PackDayPrintStatus, PackDayProjection,
- PackDayScreenQueryState, PersonalEntryProjection, ProductEditorDraft, ProductId,
- ProductPublishBlocker, ProductsFilter, ProductsListProjection, ProductsSort,
+ PackDayPrintFailureKind, PackDayPrintKind, PackDayPrintLabelStock, PackDayPrintStatus,
+ PackDayProjection, PackDayScreenQueryState, PersonalEntryProjection, ProductEditorDraft,
+ ProductId, ProductPublishBlocker, ProductsFilter, ProductsListProjection, ProductsSort,
RecoveryQueueProjection, ReminderFeedProjection, ReminderLogProjection,
SelectedSurfaceProjection, SettingsAccountProjection, SettingsPreference, SettingsSection,
ShellSection, TodayAgendaProjection, TodaySetupTask, TodaySetupTaskKind,
@@ -437,6 +437,7 @@ impl PackDayPrintRequest {
pub struct PackDayPrintProjection {
pub status: PackDayPrintStatus,
pub request: Option<PackDayPrintRequest>,
+ pub failure: Option<PackDayPrintFailureKind>,
}
impl PackDayPrintProjection {
@@ -444,6 +445,7 @@ impl PackDayPrintProjection {
Self {
status: PackDayPrintStatus::Running,
request: Some(request),
+ failure: None,
}
}
@@ -451,13 +453,29 @@ impl PackDayPrintProjection {
Self {
status: PackDayPrintStatus::Succeeded,
request: Some(request),
+ failure: None,
}
}
pub fn failed(request: PackDayPrintRequest) -> Self {
+ Self::failed_with_failure(request, None)
+ }
+
+ pub fn failed_with_kind(
+ request: PackDayPrintRequest,
+ failure: PackDayPrintFailureKind,
+ ) -> Self {
+ Self::failed_with_failure(request, Some(failure))
+ }
+
+ fn failed_with_failure(
+ request: PackDayPrintRequest,
+ failure: Option<PackDayPrintFailureKind>,
+ ) -> Self {
Self {
status: PackDayPrintStatus::Failed,
request: Some(request),
+ failure,
}
}
}
@@ -966,6 +984,10 @@ pub enum AppStateCommand {
BeginPackDayPrint(PackDayPrintRequest),
SucceedPackDayPrint(PackDayPrintRequest),
FailPackDayPrint(PackDayPrintRequest),
+ FailPackDayPrintWithKind {
+ request: PackDayPrintRequest,
+ failure: PackDayPrintFailureKind,
+ },
ResetPackDayPrint,
BeginPackDayHostHandoff(PackDayHostHandoffRequest),
SucceedPackDayHostHandoff(PackDayHostHandoffRequest),
@@ -1130,6 +1152,13 @@ impl AppStateCommand {
Self::FailPackDayPrint(request)
}
+ pub fn fail_pack_day_print_with_kind(
+ request: PackDayPrintRequest,
+ failure: PackDayPrintFailureKind,
+ ) -> Self {
+ Self::FailPackDayPrintWithKind { request, failure }
+ }
+
pub const fn reset_pack_day_print() -> Self {
Self::ResetPackDayPrint
}
@@ -1680,6 +1709,11 @@ fn apply_command(projection: &mut AppProjection, command: AppStateCommand) -> Ap
.pack_day
.replace_print(PackDayPrintProjection::failed(request));
}
+ AppStateCommand::FailPackDayPrintWithKind { request, failure } => {
+ projection
+ .pack_day
+ .replace_print(PackDayPrintProjection::failed_with_kind(request, failure));
+ }
AppStateCommand::ResetPackDayPrint => {
projection
.pack_day
@@ -2052,13 +2086,13 @@ mod tests {
OrdersListSummary, OrdersScreenQueryState, PackDayExportArtifact,
PackDayExportArtifactKind, PackDayExportBundle, PackDayExportInstanceId,
PackDayExportStatus, PackDayHostHandoffKind, PackDayHostHandoffStatus, PackDayPackListRow,
- PackDayPrintKind, PackDayPrintLabelStock, PackDayPrintStatus, PackDayProductTotalRow,
- PackDayProjection, PackDayRosterRow, PackDayScreenQueryState, PersonalEntryState,
- PersonalSection, ProductEditorDraft, ProductId, ProductPublishBlocker, ProductsFilter,
- ProductsListProjection, ProductsSort, ReminderDeliveryState, ReminderFeedProjection,
- ReminderKind, ReminderLogEntryProjection, ReminderLogProjection, SelectedAccountProjection,
- SelectedSurfaceProjection, SettingsSection, ShellSection, TodayAgendaProjection,
- TodaySetupTask, TodaySetupTaskKind,
+ PackDayPrintFailureKind, PackDayPrintKind, PackDayPrintLabelStock, PackDayPrintStatus,
+ PackDayProductTotalRow, PackDayProjection, PackDayRosterRow, PackDayScreenQueryState,
+ PersonalEntryState, PersonalSection, ProductEditorDraft, ProductId, ProductPublishBlocker,
+ ProductsFilter, ProductsListProjection, ProductsSort, ReminderDeliveryState,
+ ReminderFeedProjection, ReminderKind, ReminderLogEntryProjection, ReminderLogProjection,
+ SelectedAccountProjection, SelectedSurfaceProjection, SettingsSection, ShellSection,
+ TodayAgendaProjection, TodaySetupTask, TodaySetupTaskKind,
};
use radroots_app_sync::{
AppSyncProjection, AppSyncRunStatus, SyncCheckpointState, SyncCheckpointStatus,
@@ -2507,6 +2541,7 @@ mod tests {
PackDayPrintProjection {
status: PackDayPrintStatus::Idle,
request: None,
+ failure: None,
}
);
assert_eq!(
@@ -2616,7 +2651,22 @@ mod tests {
);
assert_eq!(
store.pack_day_projection().print,
- PackDayPrintProjection::failed(request)
+ PackDayPrintProjection::failed(request.clone())
+ );
+
+ assert_eq!(
+ store.apply(AppStateCommand::fail_pack_day_print_with_kind(
+ request.clone(),
+ PackDayPrintFailureKind::CustomerLabelsAvery5160Overflow,
+ )),
+ Ok(true)
+ );
+ assert_eq!(
+ store.pack_day_projection().print,
+ PackDayPrintProjection::failed_with_kind(
+ request,
+ PackDayPrintFailureKind::CustomerLabelsAvery5160Overflow,
+ )
);
assert_eq!(
diff --git a/i18n/locales/en/messages.json b/i18n/locales/en/messages.json
@@ -238,6 +238,7 @@
"pack_day.print.customer_labels.queued.title": "Queueing customer labels",
"pack_day.print.customer_labels.submitted.title": "Sent customer labels to the printer",
"pack_day.print.customer_labels.failed.title": "Couldn't print customer labels",
+ "pack_day.print.customer_labels.avery_5160_overflow.failed.title": "Customer labels do not fit Avery 5160",
"pack_day.host_handoff.reveal.action": "Show in Finder",
"pack_day.host_handoff.reveal.action.running": "Showing in Finder...",
"pack_day.host_handoff.open_pack_sheet.action": "Open pack sheet",