app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

source_guards.rs (69621B)


      1 use std::{
      2     collections::BTreeSet,
      3     fs,
      4     path::{Path, PathBuf},
      5 };
      6 
      7 const ALLOWED_MENU_LITERALS: &[&str] = &["cmd-q", "settings window should open"];
      8 
      9 const ALLOWED_WINDOW_LITERALS: &[&str] = &[
     10     "",
     11     "  ",
     12     "${dollars}.{cents:02}",
     13     "${dollars}.{cents:02} / {}",
     14     ", ",
     15     "+",
     16     "-",
     17     "0",
     18     "1111111111111111111111111111111111111111111111111111111111111111",
     19     "127.0.0.1",
     20     "14",
     21     "14.5",
     22     "2",
     23     "2 bags",
     24     "2222222222222222222222222222222222222222222222222222222222222222",
     25     "3333333333333333333333333333333333333333333333333333333333333333",
     26     "../../../platforms/macos/App/Resources/AppIconSource.png",
     27     "2026-04-23T15:00:00Z",
     28     "6",
     29     "6.",
     30     "6.5",
     31     "6.50",
     32     "6.500",
     33     "  Farm Profile  ",
     34     "Farm Profile",
     35     "Salad mix",
     36     "USD",
     37     "[::1]",
     38     "/tmp/radroots/data/apps/app",
     39     "/tmp/radroots/data/apps/app/sdk",
     40     "/tmp/radroots/logs/apps/app",
     41     "{}.{:02}",
     42     "abc",
     43     "app.sqlite3",
     44     "account-add",
     45     "account-open-workspace",
     46     "account-log-out",
     47     "account-more",
     48     "account-profile-change-photo",
     49     "account-profile-remove-photo",
     50     "account-settings-add-relay",
     51     "account-settings-blossom-product-photos",
     52     "account-settings-blossom-profile-farm-media",
     53     "account-settings-relay-localhost-8080",
     54     "account-settings-relay-localhost-8081",
     55     "account-settings-relay-menu-localhost-8080",
     56     "account-settings-relay-menu-localhost-8081",
     57     "account-settings-reset-blossom",
     58     "account-settings-reset-relays",
     59     "account-settings-save",
     60     "account-settings-save-draft",
     61     "account-farm-card-scroll",
     62     "account-farm-details-tabs",
     63     "account-farm-save",
     64     "account-farm-save-draft",
     65     "account-farm-add-pickup-window",
     66     "account-farm-profile-preview",
     67     "account-farm-view-profile",
     68     "account-scroll",
     69     "account-tabs",
     70     "account_1",
     71     "buyer",
     72     "buyer-detail-add-to-cart",
     73     "buyer-detail-back",
     74     "buyer-detail-confirm-replace",
     75     "buyer-detail-keep-current",
     76     "buyer-detail-quantity-decrease",
     77     "buyer-detail-quantity-increase",
     78     "buyer-cart-open-order-review",
     79     "buyer-cart-remove-line",
     80     "buyer-order-review-back",
     81     "buyer-order-review-place-order",
     82     "buyer-listing-open",
     83     "buyer-order-accept-change",
     84     "buyer-order-confirm-replace",
     85     "buyer-order-cancel",
     86     "buyer-order-detail-back",
     87     "buyer-order-keep-current",
     88     "buyer-order-keep-order",
     89     "buyer-order-repeat-demand",
     90     "buyer-orders-retry-coordination",
     91     "personal_orders",
     92     "buyer.add_to_cart_failed",
     93     "buyer.cart_remove_failed",
     94     "buyer.order_review_place_failed",
     95     "buyer.order_review_save_failed",
     96     "buyer.detail_open_failed",
     97     "buyer.order_open_failed",
     98     "buyer.order_cancel_failed",
     99     "buyer.order_coordination_retry_failed",
    100     "buyer.order_revision_accept_failed",
    101     "buyer.order_revision_decline_failed",
    102     "buyer.repeat_demand_failed",
    103     "buyer.section_select_failed",
    104     "buyer_notice",
    105     "bunker://466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27?relay=wss%3A%2F%2Frelay.radroots.example",
    106     "buyer.fulfillment_filter_update_failed",
    107     "buyer.search_query_update_failed",
    108     "CARGO_PKG_VERSION",
    109     "clock",
    110     "configuration",
    111     "configure_relay_targets",
    112     "customer_labels.txt",
    113     "desktop runtime paths should resolve",
    114     "desktop runtime roots require HOME for macos",
    115     "directory",
    116     "disk unavailable",
    117     "eggs",
    118     "event_store.sqlite",
    119     "failed to add buyer product to cart",
    120     "failed to open buyer order detail",
    121     "failed to place buyer order",
    122     "failed to retry buyer order coordination",
    123     "failed to remove buyer cart line",
    124     "failed to reorder buyer order",
    125     "failed to save buyer order review draft",
    126     "failed to select buyer section",
    127     "failed to open buyer product detail",
    128     "failed to update buyer fulfillment filter",
    129     "failed to update buyer search query",
    130     "failed to add relay `{relay_url}`: {error}",
    131     "failed to load farm settings projection",
    132     "failed to accept buyer order change",
    133     "failed to cancel buyer order",
    134     "failed to keep buyer order",
    135     "failed to open existing product editor",
    136     "failed to open new product editor",
    137     "failed to acknowledge reminder",
    138     "failed to export pack day",
    139     "failed to complete pack day batch print",
    140     "failed to complete pack day host handoff",
    141     "failed to complete pack day print",
    142     "failed to prepare pack day batch print",
    143     "failed to prepare pack day host handoff",
    144     "failed to prepare pack day print",
    145     "failed to open order detail",
    146     "failed to route into pack day view",
    147     "failed to route into orders view",
    148     "failed to save farm settings projection",
    149     "failed to save product editor draft",
    150     "failed to switch into farm mode",
    151     "failed to switch into marketplace mode",
    152     "failed to update orders filter",
    153     "failed to route into products view",
    154     "failed to update product stock",
    155     "failed to update products filter",
    156     "failed to update products search query",
    157     "failed to update products sort",
    158     "home-browse-marketplace",
    159     "home-connect-signer",
    160     "home-connect-signer-submit",
    161     "home-continue",
    162     "home-farm-setup-continue",
    163     "home-farm-setup-delivery",
    164     "home-farm-setup-finish",
    165     "home-farm-setup-pickup",
    166     "home-farm-setup-shipping",
    167     "home-farm-setup-start",
    168     "home-generate-key",
    169     "home-sidebar-account-menu",
    170     "home-nav-orders",
    171     "home-nav-pack-day",
    172     "home-nav-products",
    173     "home-nav-today",
    174     "home-orders-scroll",
    175     "home-pack-day-scroll",
    176     "buyer-browse-scroll",
    177     "buyer-cart-scroll",
    178     "buyer-nav-browse",
    179     "buyer-nav-cart",
    180     "buyer-nav-orders",
    181     "buyer-nav-search",
    182     "buyer-order-open",
    183     "buyer-orders-scroll",
    184     "personal-search-delivery",
    185     "personal-search-pickup",
    186     "personal-search-shipping",
    187     "presented reminder",
    188     "buyer-search-scroll",
    189     "home-today-open-pack-day",
    190     "home-today-order-open",
    191     "home-signer-back",
    192     "home-signer-source-input",
    193     "home-today-open-orders",
    194     "home-today-open-products-drafts",
    195     "home-today-open-products-low-stock",
    196     "home-products-scroll",
    197     "home-today-scroll",
    198     "http://",
    199     "http://localhost:8082",
    200     "https://",
    201     "today-reminder-chip",
    202     "https://auth.example/challenge",
    203     "identity",
    204     "localhost",
    205     "npub1",
    206     "npub1qqqqq...qqqqqq",
    207     "npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
    208     "npub1sxczr...5lkheq",
    209     "npub1sxczrq2dp4jtehcm8mtemj975u5ytf2d7mc6dpuuq3rzkjzr76ls5lkheq",
    210     "guest",
    211     "finder unavailable",
    212     "orders",
    213     "orders-reminders",
    214     "orders-detail-back",
    215     "pack-day-export",
    216     "pack-day-open-customer-labels",
    217     "pack-day-open-pack-sheet",
    218     "pack-day-open-pickup-roster",
    219     "pack-day-print-all",
    220     "pack-day-print-customer-labels",
    221     "pack-day-print-pack-sheet",
    222     "pack-day-print-pickup-roster",
    223     "pack_day",
    224     "pack_day.batch_print_failed",
    225     "pack_day.batch_print_prepare_failed",
    226     "pack_day.host_handoff_failed",
    227     "pack_day.host_handoff_prepare_failed",
    228     "pack_day.print_failed",
    229     "pack_day.print_prepare_failed",
    230     "pack-day-reminders",
    231     "pack-day-reveal-bundle",
    232     "pack_day.export_failed",
    233     "pack_day.route_failed",
    234     "orders-filter-all",
    235     "orders-filter-completed",
    236     "orders-filter-needs-action",
    237     "orders-filter-packed",
    238     "orders-filter-scheduled",
    239     "orders-row-action-review",
    240     "orders-row-open",
    241     "orders.detail_open_failed",
    242     "orders.filter_update_failed",
    243     "orders.route_failed",
    244     "outbox.sqlite",
    245     "preview",
    246     "pack_sheet.txt",
    247     "pack_sheet.txt, pickup_roster.txt, customer_labels.txt",
    248     "pickup_roster.txt",
    249     "products",
    250     "reminder-banner-action",
    251     "reminder-banner-dismiss",
    252     "reminders",
    253     "reminders.ack_failed",
    254     "products-filter-all",
    255     "products-filter-archived",
    256     "products-filter-drafts",
    257     "products-filter-live",
    258     "products-filter-need-attention",
    259     "products-filter-paused",
    260     "products-row-stock-action",
    261     "products-row-open",
    262     "products-sort-availability",
    263     "products-sort-name",
    264     "products-sort-price",
    265     "products-sort-stock",
    266     "products-sort-updated",
    267     "products-add-product",
    268     "products-editor-availability",
    269     "products-editor-close",
    270     "products-editor-save",
    271     "products-editor-status-archived",
    272     "products-editor-status-draft",
    273     "products-editor-status-live",
    274     "products-editor-status-paused",
    275     "products.editor_open_failed",
    276     "products.editor_save_failed",
    277     "products.new_editor_open_failed",
    278     "products-stock-editor-cancel",
    279     "products-stock-editor-close",
    280     "products-stock-editor-save",
    281     "products.filter_update_failed",
    282     "products.route_failed",
    283     "products.search_query_update_failed",
    284     "products.stock_update_failed",
    285     "products.sort_update_failed",
    286     "discovery url does not contain a remote signer uri",
    287     "enter a bunker or discovery url to continue",
    288     "enter a bunker or discovery url from the signer; raw nostrconnect client uris are signer-side only",
    289     "exports/pack_day/window-1/20260423T150000Z",
    290     "invalid discovery url:",
    291     "invalid discovery url: relative URL without a base",
    292     "invalid remote signer uri:",
    293     "invalid remote signer uri: invalid public key",
    294     "invalid_relay_url",
    295     "a remote signer connection is already pending approval",
    296     "raw nostrconnect client uris are signer-side only",
    297     "remote signer",
    298     "remote signer connection failed: relay refused the request",
    299     "remote signer did not respond yet",
    300     "retry_startup",
    301     "retry_status_refresh",
    302     "review_runtime_configuration",
    303     "runtime unavailable",
    304     "radroots_home_view_{label}_{suffix}",
    305     "sign_event:kind:1",
    306     "shell",
    307     "shell-account-entry",
    308     "shell-mode-farm",
    309     "shell-mode-marketplace",
    310     "shell.switch_farm_failed",
    311     "shell.switch_marketplace_failed",
    312     "sdk_canonical_stores",
    313     "settings",
    314     "settings-add-blackout-period",
    315     "settings-add-fulfillment-window",
    316     "settings-farm-add-pickup",
    317     "settings-farm-default-pickup",
    318     "settings-farm-remove-pickup",
    319     "settings-farm-save",
    320     "settings-fulfillment-window-pickup-location",
    321     "settings-nav-about",
    322     "settings-nav-accounts",
    323     "settings-nav-farm",
    324     "settings-nav-settings",
    325     "settings-panel-scroll",
    326     "settings-about-acknowledgements",
    327     "settings-about-conflict-action",
    328     "settings-about-privacy-policy",
    329     "settings-about-refresh-sync",
    330     "settings-about-refresh-sync-disabled",
    331     "settings-about-report-issue",
    332     "settings-about-terms",
    333     "settings-account-row",
    334     "settings-remove-blackout-period",
    335     "settings-remove-fulfillment-window",
    336     "settings.farm.load_failed",
    337     "settings.farm.save_failed",
    338     "settings.about.sync_refresh_failed",
    339     "settings.about.conflict_resolution_failed",
    340     "settings.account.select_failed",
    341     "failed to refresh sync from the about panel",
    342     "failed to resolve sync conflict from the about panel",
    343     "failed to select account from settings panel",
    344     "switch_relays",
    345     "startup-title-radroots",
    346     "startup-title-starting",
    347     "wait_for_sdk_lifecycle",
    348     "ws://localhost:8080",
    349     "ws://localhost:8081",
    350     "wss://relay.example",
    351     "wss://relay.radroots.example",
    352     "{currency_code} {dollars}.{cents:02}",
    353     "{prefix}...{suffix}",
    354     "{}, {}",
    355     "{}: {}",
    356     "{} {} {}.",
    357     "{quantity} {unit_label}",
    358     "{} {}",
    359 ];
    360 
    361 const REQUIRED_WINDOW_COPY_KEYS: &[&str] = &[
    362     "AppTextKey::AppName",
    363     "AppTextKey::HomeHeaderMarketplaceMode",
    364     "AppTextKey::HomeHeaderFarmMode",
    365     "AppTextKey::HomeHeaderAccountSetupAction",
    366     "AppTextKey::HomeHeaderGuestLabel",
    367     "AppTextKey::AccountTitle",
    368     "AppTextKey::AccountTabProfile",
    369     "AppTextKey::AccountTabFarmDetails",
    370     "AppTextKey::AccountTabPreferences",
    371     "AppTextKey::AccountTabSettings",
    372     "AppTextKey::AccountNotImplemented",
    373     "AppTextKey::AccountFormSaveAction",
    374     "AppTextKey::AccountFormSaveDraftAction",
    375     "AppTextKey::AccountFarmDetailsTabProfile",
    376     "AppTextKey::AccountFarmDetailsTabLocation",
    377     "AppTextKey::AccountFarmDetailsTabOperations",
    378     "AppTextKey::AccountFarmDetailsTabFulfilment",
    379     "AppTextKey::AccountProfilePersonalDetailsTitle",
    380     "AppTextKey::AccountProfilePictureLabel",
    381     "AppTextKey::AccountProfileChangePhotoAction",
    382     "AppTextKey::AccountProfileRemovePhotoAction",
    383     "AppTextKey::AccountProfileFullNameLabel",
    384     "AppTextKey::AccountProfileEmailLabel",
    385     "AppTextKey::AccountProfilePhoneLabel",
    386     "AppTextKey::AccountProfileRoleLabel",
    387     "AppTextKey::AccountProfileTimeZoneLabel",
    388     "AppTextKey::AccountProfileLanguageLabel",
    389     "AppTextKey::AccountProfileFullNameValue",
    390     "AppTextKey::AccountProfileEmailValue",
    391     "AppTextKey::AccountProfilePhoneValue",
    392     "AppTextKey::AccountProfileRoleValue",
    393     "AppTextKey::AccountProfileRoleFarmManagerValue",
    394     "AppTextKey::AccountProfileRoleTeamMemberValue",
    395     "AppTextKey::AccountProfileTimeZoneValue",
    396     "AppTextKey::AccountProfileTimeZoneMountainValue",
    397     "AppTextKey::AccountProfileTimeZoneEasternValue",
    398     "AppTextKey::AccountProfileLanguageValue",
    399     "AppTextKey::AccountProfileLanguageFrenchValue",
    400     "AppTextKey::AccountProfileLanguageSpanishValue",
    401     "AppTextKey::AccountFarmDetailsTitle",
    402     "AppTextKey::AccountFarmDetailsFarmProfileTitle",
    403     "AppTextKey::AccountFarmDetailsFarmProfileIntro",
    404     "AppTextKey::AccountFarmDetailsFarmNameLabel",
    405     "AppTextKey::AccountFarmDetailsPublicFarmNameLabel",
    406     "AppTextKey::AccountFarmDetailsShortDescriptionLabel",
    407     "AppTextKey::AccountFarmDetailsFarmTypeLabel",
    408     "AppTextKey::AccountFarmDetailsContactEmailLabel",
    409     "AppTextKey::AccountFarmDetailsPublicPhoneLabel",
    410     "AppTextKey::AccountFarmDetailsWebsiteLabel",
    411     "AppTextKey::AccountFarmDetailsEstablishedYearLabel",
    412     "AppTextKey::AccountFarmDetailsAboutFarmLabel",
    413     "AppTextKey::AccountFarmDetailsFarmNameValue",
    414     "AppTextKey::AccountFarmDetailsPublicFarmNameValue",
    415     "AppTextKey::AccountFarmDetailsShortDescriptionValue",
    416     "AppTextKey::AccountFarmDetailsContactEmailValue",
    417     "AppTextKey::AccountFarmDetailsPublicPhoneValue",
    418     "AppTextKey::AccountFarmDetailsWebsiteValue",
    419     "AppTextKey::AccountFarmDetailsEstablishedYearValue",
    420     "AppTextKey::AccountFarmDetailsAboutFarmValue",
    421     "AppTextKey::AccountFarmDetailsFarmLocationValue",
    422     "AppTextKey::AccountFarmDetailsSummaryTitle",
    423     "AppTextKey::AccountFarmDetailsFarmTypeSummaryLabel",
    424     "AppTextKey::AccountFarmDetailsEstablishedSummaryLabel",
    425     "AppTextKey::AccountFarmDetailsViewFarmProfileAction",
    426     "AppTextKey::AccountFarmDetailsFarmTypeVegetableFarm",
    427     "AppTextKey::AccountFarmDetailsFarmTypeFruitOrchard",
    428     "AppTextKey::AccountFarmDetailsFarmTypeBerryFarm",
    429     "AppTextKey::AccountFarmDetailsFarmTypeHerbFarm",
    430     "AppTextKey::AccountFarmDetailsFarmTypeFlowerFarm",
    431     "AppTextKey::AccountFarmDetailsFarmTypeMushroomFarm",
    432     "AppTextKey::AccountFarmDetailsFarmTypeGrainFieldCropFarm",
    433     "AppTextKey::AccountFarmDetailsFarmTypeDairyFarm",
    434     "AppTextKey::AccountFarmDetailsFarmTypeEggPoultryFarm",
    435     "AppTextKey::AccountFarmDetailsFarmTypeLivestockFarm",
    436     "AppTextKey::AccountFarmDetailsFarmTypeHoneyApiary",
    437     "AppTextKey::AccountFarmDetailsFarmTypeNurseryPlantFarm",
    438     "AppTextKey::AccountFarmDetailsFarmTypeMixedFarm",
    439     "AppTextKey::AccountFarmDetailsFarmTypeOther",
    440     "AppTextKey::AccountFarmDetailsLocationTitle",
    441     "AppTextKey::AccountFarmDetailsLocationIntro",
    442     "AppTextKey::AccountFarmDetailsMapNotImplemented",
    443     "AppTextKey::AccountFarmDetailsStreetAddressLabel",
    444     "AppTextKey::AccountFarmDetailsStreetAddressValue",
    445     "AppTextKey::AccountFarmDetailsCityLabel",
    446     "AppTextKey::AccountFarmDetailsCityValue",
    447     "AppTextKey::AccountFarmDetailsProvinceLabel",
    448     "AppTextKey::AccountFarmDetailsProvinceBritishColumbia",
    449     "AppTextKey::AccountFarmDetailsProvinceAlberta",
    450     "AppTextKey::AccountFarmDetailsPostalCodeLabel",
    451     "AppTextKey::AccountFarmDetailsPostalCodeValue",
    452     "AppTextKey::AccountFarmDetailsCountryLabel",
    453     "AppTextKey::AccountFarmDetailsCountryCanada",
    454     "AppTextKey::AccountFarmDetailsCountryUnitedStates",
    455     "AppTextKey::AccountFarmDetailsServiceAreaLabel",
    456     "AppTextKey::AccountFarmDetailsServiceAreaValue",
    457     "AppTextKey::AccountFarmDetailsServiceAreaHelper",
    458     "AppTextKey::AccountFarmDetailsExactAddressPublicLabel",
    459     "AppTextKey::AccountFarmDetailsExactAddressPublicHelper",
    460     "AppTextKey::AccountFarmDetailsLocationPreviewTitle",
    461     "AppTextKey::AccountFarmDetailsLocationPreviewHelper",
    462     "AppTextKey::AccountFarmDetailsOperatingTitle",
    463     "AppTextKey::AccountFarmDetailsOperatingIntro",
    464     "AppTextKey::AccountFarmDetailsGrowingPracticesLabel",
    465     "AppTextKey::AccountFarmDetailsGrowingPracticeRegenerative",
    466     "AppTextKey::AccountFarmDetailsGrowingPracticeOrganic",
    467     "AppTextKey::AccountFarmDetailsProductionMethodsLabel",
    468     "AppTextKey::AccountFarmDetailsProductionMethodOrganicPractices",
    469     "AppTextKey::AccountFarmDetailsProductionMethodNoSpray",
    470     "AppTextKey::AccountFarmDetailsSeasonDatesLabel",
    471     "AppTextKey::AccountFarmDetailsSeasonStartValue",
    472     "AppTextKey::AccountFarmDetailsSeasonEndValue",
    473     "AppTextKey::AccountFarmDetailsOrderDaysLabel",
    474     "AppTextKey::AccountFarmDetailsOrderDaysSummaryValue",
    475     "AppTextKey::AccountFarmDetailsDayMon",
    476     "AppTextKey::AccountFarmDetailsDayTue",
    477     "AppTextKey::AccountFarmDetailsDayWed",
    478     "AppTextKey::AccountFarmDetailsDayThu",
    479     "AppTextKey::AccountFarmDetailsDayFri",
    480     "AppTextKey::AccountFarmDetailsDaySat",
    481     "AppTextKey::AccountFarmDetailsDaySun",
    482     "AppTextKey::AccountFarmDetailsAboutProductsLabel",
    483     "AppTextKey::AccountFarmDetailsAboutProductsValue",
    484     "AppTextKey::AccountFarmDetailsCertificationsTitle",
    485     "AppTextKey::AccountFarmDetailsCertificationsHelper",
    486     "AppTextKey::AccountFarmDetailsCertificationCertifiedOrganic",
    487     "AppTextKey::AccountFarmDetailsCertificationNaturallyGrown",
    488     "AppTextKey::AccountFarmDetailsCertificationSmallFamilyFarm",
    489     "AppTextKey::AccountFarmDetailsCertificationDeliveryAvailable",
    490     "AppTextKey::AccountFarmDetailsCustomerNoteTitle",
    491     "AppTextKey::AccountFarmDetailsCustomerNoteHelper",
    492     "AppTextKey::AccountFarmDetailsCustomerNoteValue",
    493     "AppTextKey::AccountFarmDetailsProfilePreviewTitle",
    494     "AppTextKey::AccountFarmDetailsGrowingPracticesSummaryLabel",
    495     "AppTextKey::AccountFarmDetailsSeasonSummaryLabel",
    496     "AppTextKey::AccountFarmDetailsOrderDaysSummaryLabel",
    497     "AppTextKey::AccountFarmDetailsPickupFulfillmentTitle",
    498     "AppTextKey::AccountFarmDetailsPickupFulfillmentIntro",
    499     "AppTextKey::AccountFarmDetailsFulfillmentModeLabel",
    500     "AppTextKey::AccountFarmDetailsFulfillmentPickupOnly",
    501     "AppTextKey::AccountFarmDetailsFulfillmentDelivery",
    502     "AppTextKey::AccountFarmDetailsFulfillmentBoth",
    503     "AppTextKey::AccountFarmDetailsPrimaryPickupLocationLabel",
    504     "AppTextKey::AccountFarmDetailsPrimaryPickupLocationTitleValue",
    505     "AppTextKey::AccountFarmDetailsPrimaryPickupLocationAddressValue",
    506     "AppTextKey::AccountFarmDetailsPickupInstructionsLabel",
    507     "AppTextKey::AccountFarmDetailsPickupInstructionsValue",
    508     "AppTextKey::AccountFarmDetailsPickupInstructionsHelper",
    509     "AppTextKey::AccountFarmDetailsPickupWindowsLabel",
    510     "AppTextKey::AccountFarmDetailsPickupWindowDayHeader",
    511     "AppTextKey::AccountFarmDetailsPickupWindowStartHeader",
    512     "AppTextKey::AccountFarmDetailsPickupWindowEndHeader",
    513     "AppTextKey::AccountFarmDetailsPickupWindowWednesday",
    514     "AppTextKey::AccountFarmDetailsPickupWindowSaturday",
    515     "AppTextKey::AccountFarmDetailsPickupWindowWednesdayStart",
    516     "AppTextKey::AccountFarmDetailsPickupWindowWednesdayEnd",
    517     "AppTextKey::AccountFarmDetailsPickupWindowSaturdayStart",
    518     "AppTextKey::AccountFarmDetailsPickupWindowSaturdayEnd",
    519     "AppTextKey::AccountFarmDetailsAddPickupWindowAction",
    520     "AppTextKey::AccountFarmDetailsOrderCutoffLabel",
    521     "AppTextKey::AccountFarmDetailsOrderCutoffHelper",
    522     "AppTextKey::AccountFarmDetailsOrderCutoffNoonValue",
    523     "AppTextKey::AccountFarmDetailsDeliveryRadiusTitle",
    524     "AppTextKey::AccountFarmDetailsDeliveryRadiusHelper",
    525     "AppTextKey::AccountFarmDetailsDeliveryRadiusValue",
    526     "AppTextKey::AccountFarmDetailsDeliveryRadiusUnit",
    527     "AppTextKey::AccountFarmDetailsDeliveryRadiusNote",
    528     "AppTextKey::AccountFarmDetailsCustomerExperienceTitle",
    529     "AppTextKey::AccountFarmDetailsCustomerExperienceIntro",
    530     "AppTextKey::AccountFarmDetailsCustomerExperiencePickupTitle",
    531     "AppTextKey::AccountFarmDetailsCustomerExperienceDeliveryTitle",
    532     "AppTextKey::AccountFarmDetailsCustomerExperienceDeliveryBody",
    533     "AppTextKey::AccountSettingsTitle",
    534     "AppTextKey::AccountSettingsNostrRelaysTitle",
    535     "AppTextKey::AccountSettingsNostrRelaysHelper",
    536     "AppTextKey::AccountSettingsRelayAccessReadWrite",
    537     "AppTextKey::AccountSettingsRelayAccessReadOnly",
    538     "AppTextKey::AccountSettingsRelayMenuAbout",
    539     "AppTextKey::AccountSettingsRelayMenuView",
    540     "AppTextKey::AccountSettingsRemoveRelayAction",
    541     "AppTextKey::AccountSettingsRelayMenuCheckConnection",
    542     "AppTextKey::AccountSettingsRelayMenuCopy",
    543     "AppTextKey::AccountSettingsRelayMenuCopyShortcut",
    544     "AppTextKey::AccountSettingsAddRelayLabel",
    545     "AppTextKey::AccountSettingsAddRelayPlaceholder",
    546     "AppTextKey::AccountSettingsAddRelayAction",
    547     "AppTextKey::AccountSettingsResetRelaysAction",
    548     "AppTextKey::AccountSettingsDefaultRelaysNote",
    549     "AppTextKey::AccountSettingsBlossomServerTitle",
    550     "AppTextKey::AccountSettingsBlossomServerHelper",
    551     "AppTextKey::AccountSettingsBlossomServerUrlLabel",
    552     "AppTextKey::AccountSettingsBlossomProductPhotosLabel",
    553     "AppTextKey::AccountSettingsBlossomProfileFarmMediaLabel",
    554     "AppTextKey::AccountSettingsResetBlossomServerAction",
    555     "AppTextKey::AccountSettingsBlossomConnectionHealthy",
    556     "AppTextKey::AccountSettingsBlossomConnectionLocal",
    557     "AppTextKey::AccountSettingsBlossomConnectionInvalid",
    558     "AppTextKey::AccountSettingsBlossomUploadsAvailable",
    559     "AppTextKey::AccountSettingsBlossomUploadsPending",
    560     "AppTextKey::AccountSettingsBlossomUploadsUnavailable",
    561     "AppTextKey::HomeSetupBackAction",
    562     "AppTextKey::HomeSetupBrowseMarketplaceAction",
    563     "AppTextKey::HomeSetupConnectSignerAction",
    564     "AppTextKey::HomeSetupContinueAction",
    565     "AppTextKey::HomeSetupGenerateKeyAction",
    566     "AppTextKey::HomeSetupSignerConnectAction",
    567     "AppTextKey::HomeSetupSignerSourcePlaceholder",
    568     "AppTextKey::HomeSetupSignerReviewTitle",
    569     "AppTextKey::HomeSetupSignerSourceLabel",
    570     "AppTextKey::HomeSetupSignerSignerLabel",
    571     "AppTextKey::HomeSetupSignerRelaysLabel",
    572     "AppTextKey::HomeSetupSignerPermissionsLabel",
    573     "AppTextKey::HomeSetupSignerConnectingTitle",
    574     "AppTextKey::HomeSetupSignerPendingTitle",
    575     "AppTextKey::HomeSetupSignerAuthChallengeTitle",
    576     "AppTextKey::HomeSetupSignerApprovedTitle",
    577     "AppTextKey::HomeSetupIssueUnavailableBody",
    578     "AppTextKey::HomeSetupErrorStartupFailed",
    579     "AppTextKey::HomeSetupSignerSourceValueBunkerUri",
    580     "AppTextKey::HomeSetupSignerSourceValueDiscoveryUrl",
    581     "AppTextKey::HomeSetupSignerPermissionSignEventKind1",
    582     "AppTextKey::HomeSetupSignerPermissionSwitchRelays",
    583     "AppTextKey::HomeSetupSignerPermissionAdditional",
    584     "AppTextKey::HomeSetupSignerErrorEnterSource",
    585     "AppTextKey::HomeSetupSignerErrorUseSignerUri",
    586     "AppTextKey::HomeSetupSignerErrorMissingDiscoveryUri",
    587     "AppTextKey::HomeSetupSignerErrorInvalidDiscoveryUrl",
    588     "AppTextKey::HomeSetupSignerErrorInvalidRemoteSignerUri",
    589     "AppTextKey::HomeSetupSignerErrorPendingApprovalExists",
    590     "AppTextKey::HomeSetupSignerErrorConnectionFailed",
    591     "AppTextKey::HomeFarmSetupOnboardingTitle",
    592     "AppTextKey::HomeFarmSetupOnboardingBody",
    593     "AppTextKey::HomeFarmSetupOnboardingAction",
    594     "AppTextKey::HomeFarmSetupSectionFarm",
    595     "AppTextKey::HomeFarmSetupSectionLocation",
    596     "AppTextKey::HomeFarmSetupSectionOrderMethods",
    597     "AppTextKey::HomeFarmSetupFieldFarmName",
    598     "AppTextKey::HomeFarmSetupFieldLocationOrServiceArea",
    599     "AppTextKey::HomeFarmSetupOrderMethodPickup",
    600     "AppTextKey::HomeFarmSetupOrderMethodDelivery",
    601     "AppTextKey::HomeFarmSetupOrderMethodShipping",
    602     "AppTextKey::HomeFarmSetupBlockerAddFarmName",
    603     "AppTextKey::HomeFarmSetupBlockerAddLocationOrServiceArea",
    604     "AppTextKey::HomeFarmSetupBlockerChooseOrderMethod",
    605     "AppTextKey::HomeFarmSetupSaveAutosavesLocally",
    606     "AppTextKey::HomeFarmSetupSaveSavedLocally",
    607     "AppTextKey::HomeFarmSetupSaveFailedLocally",
    608     "AppTextKey::HomeFarmSetupFinishAction",
    609     "AppTextKey::HomeFarmSetupContinueAction",
    610     "AppTextKey::HomeTodayOpenInProductsAction",
    611     "AppTextKey::HomeNavBrowse",
    612     "AppTextKey::HomeNavSearch",
    613     "AppTextKey::HomeNavCart",
    614     "AppTextKey::HomeNavToday",
    615     "AppTextKey::HomeNavProducts",
    616     "AppTextKey::HomeNavOrders",
    617     "AppTextKey::PersonalSearchFiltersTitle",
    618     "AppTextKey::PersonalSearchPlaceholder",
    619     "AppTextKey::PersonalBrowseEmptyTitle",
    620     "AppTextKey::PersonalBrowseEmptyBody",
    621     "AppTextKey::PersonalSearchEmptyTitle",
    622     "AppTextKey::PersonalSearchEmptyBody",
    623     "AppTextKey::PersonalBrowsePlaceholderBody",
    624     "AppTextKey::PersonalSearchPlaceholderBody",
    625     "AppTextKey::PersonalMarketplaceRefreshFailedNotice",
    626     "AppTextKey::PersonalDetailOpenFailedNotice",
    627     "AppTextKey::PersonalOrderPlaceFailedNotice",
    628     "AppTextKey::PersonalOrderCoordinationFailedNotice",
    629     "AppTextKey::PersonalCartPlaceholderBody",
    630     "AppTextKey::PersonalOrdersSurfaceBody",
    631     "AppTextKey::PersonalOrdersEmptyTitle",
    632     "AppTextKey::PersonalOrdersEmptyBody",
    633     "AppTextKey::PersonalOrdersListTitle",
    634     "AppTextKey::PersonalOrdersDetailTitle",
    635     "AppTextKey::PersonalOrdersDetailFarmLabel",
    636     "AppTextKey::PersonalOrdersDetailFulfillmentLabel",
    637     "AppTextKey::PersonalOrdersDetailTotalLabel",
    638     "AppTextKey::PersonalOrdersDetailNoteLabel",
    639     "AppTextKey::PersonalOrdersDetailItemsTitle",
    640     "AppTextKey::PersonalOrdersActionCancel",
    641     "AppTextKey::PersonalOrdersActionAcceptChange",
    642     "AppTextKey::PersonalOrdersActionKeepOrder",
    643     "AppTextKey::PersonalOrdersRepeatDemandTitle",
    644     "AppTextKey::PersonalOrdersRepeatDemandActionEligible",
    645     "AppTextKey::PersonalOrdersRepeatDemandActionPartial",
    646     "AppTextKey::PersonalOrdersRepeatDemandNotePartialSingle",
    647     "AppTextKey::PersonalOrdersRepeatDemandNotePartialMultiple",
    648     "AppTextKey::PersonalOrdersRepeatDemandNoteUnavailable",
    649     "AppTextKey::PersonalCartSurfaceBody",
    650     "AppTextKey::PersonalOrderSummaryTitle",
    651     "AppTextKey::PersonalFulfillmentTitle",
    652     "AppTextKey::PersonalCartRemoveLineAction",
    653     "AppTextKey::PersonalCartReviewOrderAction",
    654     "AppTextKey::PersonalCartLineQuantityLabel",
    655     "AppTextKey::PersonalCartLineUnitPriceLabel",
    656     "AppTextKey::PersonalCartLineTotalLabel",
    657     "AppTextKey::PersonalSummaryFarmLabel",
    658     "AppTextKey::PersonalSummaryItemsLabel",
    659     "AppTextKey::PersonalSummarySubtotalLabel",
    660     "AppTextKey::PersonalDetailBackAction",
    661     "AppTextKey::PersonalDetailQuantityLabel",
    662     "AppTextKey::PersonalDetailAddToCartAction",
    663     "AppTextKey::PersonalDetailReplaceCartTitle",
    664     "AppTextKey::PersonalDetailReplaceCartBody",
    665     "AppTextKey::PersonalDetailReplaceCartAction",
    666     "AppTextKey::PersonalDetailKeepCurrentCartAction",
    667     "AppTextKey::PersonalOrderReviewTitle",
    668     "AppTextKey::PersonalOrderReviewBackAction",
    669     "AppTextKey::PersonalOrderReviewContactTitle",
    670     "AppTextKey::PersonalOrderReviewFieldName",
    671     "AppTextKey::PersonalOrderReviewFieldEmail",
    672     "AppTextKey::PersonalOrderReviewFieldPhone",
    673     "AppTextKey::PersonalOrderReviewFieldOrderNote",
    674     "AppTextKey::PersonalOrderReviewLocalOnlyBody",
    675     "AppTextKey::PersonalOrderReviewPlaceOrderAction",
    676     "AppTextKey::HomeTodayOpenInOrdersAction",
    677     "AppTextKey::HomeTodayOpenInPackDayAction",
    678     "AppTextKey::OrdersTitle",
    679     "AppTextKey::OrdersFiltersTitle",
    680     "AppTextKey::OrdersSummaryTotal",
    681     "AppTextKey::OrdersFilterAll",
    682     "AppTextKey::OrdersStatusNeedsAction",
    683     "AppTextKey::OrdersStatusScheduled",
    684     "AppTextKey::OrdersStatusInHandoff",
    685     "AppTextKey::OrdersStatusCompleted",
    686     "AppTextKey::OrdersTableTitle",
    687     "AppTextKey::OrdersColumnOrder",
    688     "AppTextKey::OrdersColumnStatus",
    689     "AppTextKey::OrdersColumnWindow",
    690     "AppTextKey::OrdersColumnPickup",
    691     "AppTextKey::OrdersColumnAction",
    692     "AppTextKey::OrdersActionReview",
    693     "AppTextKey::OrdersEmptyTitle",
    694     "AppTextKey::OrdersEmptyBody",
    695     "AppTextKey::OrdersEmptyNeedsActionTitle",
    696     "AppTextKey::OrdersEmptyNeedsActionBody",
    697     "AppTextKey::OrdersDetailTitle",
    698     "AppTextKey::OrdersDetailItemsTitle",
    699     "AppTextKey::OrdersDetailCustomerLabel",
    700     "AppTextKey::OrdersDetailWindowLabel",
    701     "AppTextKey::OrdersDetailPickupLabel",
    702     "AppTextKey::OrdersDetailTotalLabel",
    703     "AppTextKey::TradeValidationReceiptSectionLabel",
    704     "AppTextKey::TradeValidationReceiptRecordedAtLabel",
    705     "AppTextKey::TradeValidationReceiptResultValid",
    706     "AppTextKey::TradeValidationReceiptResultNeedsReview",
    707     "AppTextKey::TradeValidationReceiptTypeListingValidation",
    708     "AppTextKey::TradeValidationReceiptTypeTradeTransition",
    709     "AppTextKey::TradeValidationReceiptTypeInventoryState",
    710     "AppTextKey::TradeValidationReceiptTypeStateCheckpoint",
    711     "AppTextKey::TradeWorkflowAxisAgreement",
    712     "AppTextKey::TradeWorkflowAxisRevision",
    713     "AppTextKey::TradeWorkflowAxisInventory",
    714     "AppTextKey::TradeWorkflowAxisSource",
    715     "AppTextKey::TradeWorkflowAgreementOrdered",
    716     "AppTextKey::TradeWorkflowAgreementConfirmed",
    717     "AppTextKey::TradeWorkflowAgreementDeclined",
    718     "AppTextKey::TradeWorkflowAgreementCancelled",
    719     "AppTextKey::TradeWorkflowAgreementNeedsReview",
    720     "AppTextKey::TradeWorkflowRevisionNone",
    721     "AppTextKey::TradeWorkflowRevisionChangeProposed",
    722     "AppTextKey::TradeWorkflowRevisionUpdated",
    723     "AppTextKey::TradeWorkflowRevisionKeptAsPlaced",
    724     "AppTextKey::TradeWorkflowInventoryAvailable",
    725     "AppTextKey::TradeWorkflowInventoryReserved",
    726     "AppTextKey::TradeWorkflowInventorySoldOut",
    727     "AppTextKey::TradeWorkflowInventoryNeedsReview",
    728     "AppTextKey::TradeWorkflowProvenanceApp",
    729     "AppTextKey::TradeWorkflowProvenanceCli",
    730     "AppTextKey::TradeWorkflowProvenanceRelay",
    731     "AppTextKey::TradeWorkflowProvenanceLocalEvents",
    732     "AppTextKey::TradeWorkflowProvenanceUnknown",
    733     "AppTextKey::OrdersRemindersTitle",
    734     "AppTextKey::OrdersReminderLogTitle",
    735     "AppTextKey::OrdersReminderLogEmptyBody",
    736     "AppTextKey::PackDayTitle",
    737     "AppTextKey::PackDayRemindersTitle",
    738     "AppTextKey::PackDayWindowSummaryTitle",
    739     "AppTextKey::PackDayTotalsTitle",
    740     "AppTextKey::PackDayPackListTitle",
    741     "AppTextKey::PackDayPickupRosterTitle",
    742     "AppTextKey::PackDayEmptyTitle",
    743     "AppTextKey::PackDayEmptyBody",
    744     "AppTextKey::PackDayExportTitle",
    745     "AppTextKey::PackDayExportReadyTitle",
    746     "AppTextKey::PackDayExportReadyBody",
    747     "AppTextKey::PackDayExportUnavailableTitle",
    748     "AppTextKey::PackDayExportUnavailableBody",
    749     "AppTextKey::PackDayExportRunningTitle",
    750     "AppTextKey::PackDayExportRunningBody",
    751     "AppTextKey::PackDayExportSucceededTitle",
    752     "AppTextKey::PackDayExportSucceededBody",
    753     "AppTextKey::PackDayExportFailedTitle",
    754     "AppTextKey::PackDayExportFailedBody",
    755     "AppTextKey::PackDayExportAction",
    756     "AppTextKey::PackDayExportActionRunning",
    757     "AppTextKey::PackDayExportFolderLabel",
    758     "AppTextKey::PackDayExportFilesLabel",
    759     "AppTextKey::PackDayExportErrorLabel",
    760     "AppTextKey::PackDayBatchPrintAction",
    761     "AppTextKey::PackDayBatchPrintActionRunning",
    762     "AppTextKey::PackDayBatchPrintQueuedTitle",
    763     "AppTextKey::PackDayBatchPrintSucceededTitle",
    764     "AppTextKey::PackDayBatchPrintFailedTitle",
    765     "AppTextKey::PackDayBatchPrintFailedPreflightTitle",
    766     "AppTextKey::PackDayBatchPrintFailedQueueLaunchTitle",
    767     "AppTextKey::PackDayBatchPrintFailedQueueExitTitle",
    768     "AppTextKey::PackDayBatchPrintCustomerLabelsAvery5160OverflowFailedTitle",
    769     "AppTextKey::PackDayPrintCustomerLabelsAvery5160OverflowFailedTitle",
    770     "AppTextKey::PackDayHostHandoffRevealAction",
    771     "AppTextKey::PackDayHostHandoffRevealActionRunning",
    772     "AppTextKey::PackDayHostHandoffOpenPackSheetAction",
    773     "AppTextKey::PackDayHostHandoffOpenPackSheetActionRunning",
    774     "AppTextKey::PackDayHostHandoffOpenPickupRosterAction",
    775     "AppTextKey::PackDayHostHandoffOpenPickupRosterActionRunning",
    776     "AppTextKey::PackDayHostHandoffOpenCustomerLabelsAction",
    777     "AppTextKey::PackDayHostHandoffOpenCustomerLabelsActionRunning",
    778     "AppTextKey::PackDayHostHandoffRevealRunningTitle",
    779     "AppTextKey::PackDayHostHandoffRevealSucceededTitle",
    780     "AppTextKey::PackDayHostHandoffRevealFailedTitle",
    781     "AppTextKey::PackDayHostHandoffOpenPackSheetRunningTitle",
    782     "AppTextKey::PackDayHostHandoffOpenPackSheetSucceededTitle",
    783     "AppTextKey::PackDayHostHandoffOpenPackSheetFailedTitle",
    784     "AppTextKey::PackDayHostHandoffOpenPickupRosterRunningTitle",
    785     "AppTextKey::PackDayHostHandoffOpenPickupRosterSucceededTitle",
    786     "AppTextKey::PackDayHostHandoffOpenPickupRosterFailedTitle",
    787     "AppTextKey::PackDayHostHandoffOpenCustomerLabelsRunningTitle",
    788     "AppTextKey::PackDayHostHandoffOpenCustomerLabelsSucceededTitle",
    789     "AppTextKey::PackDayHostHandoffOpenCustomerLabelsFailedTitle",
    790     "AppTextKey::HomeTodayRemindersTitle",
    791     "AppTextKey::ReminderDeadlineLabel",
    792     "AppTextKey::ReminderUrgencyUpcoming",
    793     "AppTextKey::ReminderUrgencyDueSoon",
    794     "AppTextKey::ReminderUrgencyOverdue",
    795     "AppTextKey::ReminderUrgencyBlocking",
    796     "AppTextKey::ReminderPresentationTitle",
    797     "AppTextKey::ReminderPresentationDismissAction",
    798     "AppTextKey::ReminderDeliveryStateScheduled",
    799     "AppTextKey::ReminderDeliveryStatePresented",
    800     "AppTextKey::ReminderDeliveryStateAcknowledged",
    801     "AppTextKey::ReminderDeliveryStateResolved",
    802     "AppTextKey::ProductsTitle",
    803     "AppTextKey::ProductsFiltersTitle",
    804     "AppTextKey::ProductsSearchPlaceholder",
    805     "AppTextKey::ProductsSummaryTotal",
    806     "AppTextKey::ProductsSummaryLive",
    807     "AppTextKey::ProductsSummaryNeedAttention",
    808     "AppTextKey::ProductsSummaryDrafts",
    809     "AppTextKey::ProductsFilterAll",
    810     "AppTextKey::ProductsFilterLive",
    811     "AppTextKey::ProductsFilterDrafts",
    812     "AppTextKey::ProductsFilterNeedAttention",
    813     "AppTextKey::ProductsFilterPaused",
    814     "AppTextKey::ProductsFilterArchived",
    815     "AppTextKey::ProductsSortTitle",
    816     "AppTextKey::ProductsSortUpdated",
    817     "AppTextKey::ProductsSortName",
    818     "AppTextKey::ProductsSortAvailability",
    819     "AppTextKey::ProductsSortStock",
    820     "AppTextKey::ProductsSortPrice",
    821     "AppTextKey::ProductsTableTitle",
    822     "AppTextKey::ProductsColumnProduct",
    823     "AppTextKey::ProductsColumnStatus",
    824     "AppTextKey::ProductsColumnAvailability",
    825     "AppTextKey::ProductsColumnStock",
    826     "AppTextKey::ProductsColumnPrice",
    827     "AppTextKey::ProductsColumnUpdated",
    828     "AppTextKey::ProductsColumnAction",
    829     "AppTextKey::ProductsAddAction",
    830     "AppTextKey::ProductsUpdateStockAction",
    831     "AppTextKey::ProductsEditorTitle",
    832     "AppTextKey::ProductsEditorBody",
    833     "AppTextKey::ProductsEditorFieldTitle",
    834     "AppTextKey::ProductsEditorFieldSubtitle",
    835     "AppTextKey::ProductsEditorFieldCategory",
    836     "AppTextKey::ProductsEditorFieldUnit",
    837     "AppTextKey::ProductsEditorFieldPrice",
    838     "AppTextKey::ProductsEditorFieldStock",
    839     "AppTextKey::ProductsEditorFieldAvailability",
    840     "AppTextKey::ProductsEditorFieldStatus",
    841     "AppTextKey::ProductsEditorAvailabilityEmpty",
    842     "AppTextKey::ProductsEditorCloseAction",
    843     "AppTextKey::ProductsEditorSaveAction",
    844     "AppTextKey::ProductsEditorSaveFailed",
    845     "AppTextKey::ProductsEditorPublishQueueFailed",
    846     "AppTextKey::ProductsEditorInvalidPrice",
    847     "AppTextKey::ProductsEditorInvalidStock",
    848     "AppTextKey::ProductsEditorPublishReadinessTitle",
    849     "AppTextKey::ProductsEditorReady",
    850     "AppTextKey::ProductsEditorBlockerAddProductName",
    851     "AppTextKey::ProductsEditorBlockerChooseCategory",
    852     "AppTextKey::ProductsEditorBlockerChooseUnit",
    853     "AppTextKey::ProductsEditorBlockerSetPrice",
    854     "AppTextKey::ProductsEditorBlockerSetStock",
    855     "AppTextKey::ProductsEditorBlockerAttachAvailability",
    856     "AppTextKey::ProductsUntitledDraft",
    857     "AppTextKey::ProductsStockEditorTitle",
    858     "AppTextKey::ProductsStockEditorFieldLabel",
    859     "AppTextKey::ProductsStockEditorSaveAction",
    860     "AppTextKey::ProductsStockEditorCancelAction",
    861     "AppTextKey::ProductsStockEditorInvalidQuantity",
    862     "AppTextKey::ProductsStockEditorSaveFailed",
    863     "AppTextKey::ProductsStockEditorPublishQueueFailed",
    864     "AppTextKey::ProductsStatusDraft",
    865     "AppTextKey::ProductsStatusLive",
    866     "AppTextKey::ProductsStatusPaused",
    867     "AppTextKey::ProductsStatusArchived",
    868     "AppTextKey::ProductsEmptyTitle",
    869     "AppTextKey::ProductsEmptyBody",
    870     "AppTextKey::ProductsEmptyNeedAttentionTitle",
    871     "AppTextKey::ProductsEmptyNeedAttentionBody",
    872     "AppTextKey::SettingsAccountNoSelectionTitle",
    873     "AppTextKey::SettingsAccountNoSelectionBody",
    874     "AppTextKey::SettingsAccountStatusLoggedOut",
    875     "AppTextKey::SettingsAccountActivationInactive",
    876     "AppTextKey::SettingsAccountAddAction",
    877     "AppTextKey::SettingsAccountLogOutAction",
    878     "AppTextKey::SettingsAccountOpenWorkspaceAction",
    879     "AppTextKey::SettingsAccountImportFileAction",
    880     "AppTextKey::SettingsAccountImportDatabaseAction",
    881     "AppTextKey::SettingsAccountConnectRemoteBunkerAction",
    882     "AppTextKey::SettingsNavFarm",
    883     "AppTextKey::SettingsFarmPanelBody",
    884     "AppTextKey::SettingsFarmUnavailableBody",
    885     "AppTextKey::SettingsFarmSaveAction",
    886     "AppTextKey::SettingsFarmSaveSaved",
    887     "AppTextKey::SettingsFarmSavePending",
    888     "AppTextKey::SettingsFarmSaveBlocked",
    889     "AppTextKey::SettingsFarmSaveFailed",
    890     "AppTextKey::SettingsFarmFieldTimezone",
    891     "AppTextKey::SettingsFarmFieldCurrency",
    892     "AppTextKey::SettingsPickupLocationsSectionLabel",
    893     "AppTextKey::SettingsPickupLocationsEmptyBody",
    894     "AppTextKey::SettingsPickupLocationsAddAction",
    895     "AppTextKey::SettingsPickupLocationsMakeDefaultAction",
    896     "AppTextKey::SettingsPickupLocationsDefaultBadge",
    897     "AppTextKey::SettingsPickupLocationsRemoveAction",
    898     "AppTextKey::SettingsPickupLocationsFieldLabel",
    899     "AppTextKey::SettingsPickupLocationsFieldAddress",
    900     "AppTextKey::SettingsPickupLocationsFieldDirections",
    901     "AppTextKey::SettingsPickupLocationsFieldDefault",
    902     "AppTextKey::SettingsSettingsPanelBody",
    903     "AppTextKey::SettingsOperatingRulesSectionLabel",
    904     "AppTextKey::SettingsOperatingRulesFieldPromiseLeadTime",
    905     "AppTextKey::SettingsOperatingRulesFieldSubstitutionPolicy",
    906     "AppTextKey::SettingsOperatingRulesInvalidPromiseLeadTime",
    907     "AppTextKey::SettingsFulfillmentWindowsSectionLabel",
    908     "AppTextKey::SettingsFulfillmentWindowsEmptyBody",
    909     "AppTextKey::SettingsFulfillmentWindowsPickupLocationsBody",
    910     "AppTextKey::SettingsFulfillmentWindowsAddAction",
    911     "AppTextKey::SettingsFulfillmentWindowsRemoveAction",
    912     "AppTextKey::SettingsFulfillmentWindowsItemLabel",
    913     "AppTextKey::SettingsFulfillmentWindowsFieldLabel",
    914     "AppTextKey::SettingsFulfillmentWindowsFieldPickupLocation",
    915     "AppTextKey::SettingsFulfillmentWindowsFieldStartsAt",
    916     "AppTextKey::SettingsFulfillmentWindowsFieldEndsAt",
    917     "AppTextKey::SettingsFulfillmentWindowsFieldOrderCutoff",
    918     "AppTextKey::SettingsFulfillmentWindowsValidationCompleteBeforeSave",
    919     "AppTextKey::SettingsFulfillmentWindowsValidationChoosePickupLocation",
    920     "AppTextKey::SettingsBlackoutPeriodsSectionLabel",
    921     "AppTextKey::SettingsBlackoutPeriodsEmptyBody",
    922     "AppTextKey::SettingsBlackoutPeriodsAddAction",
    923     "AppTextKey::SettingsBlackoutPeriodsRemoveAction",
    924     "AppTextKey::SettingsBlackoutPeriodsItemLabel",
    925     "AppTextKey::SettingsBlackoutPeriodsFieldLabel",
    926     "AppTextKey::SettingsBlackoutPeriodsFieldStartsAt",
    927     "AppTextKey::SettingsBlackoutPeriodsFieldEndsAt",
    928     "AppTextKey::SettingsBlackoutPeriodsValidationCompleteBeforeSave",
    929     "AppTextKey::SettingsReadinessSectionLabel",
    930     "AppTextKey::SettingsReadinessFieldMissingProfileBasics",
    931     "AppTextKey::SettingsReadinessFieldMissingPickupLocation",
    932     "AppTextKey::SettingsReadinessFieldMissingFulfillmentWindow",
    933     "AppTextKey::SettingsReadinessFieldMissingOperatingRules",
    934     "AppTextKey::SettingsReadinessFieldInvalidTimingConflicts",
    935     "AppTextKey::SettingsReadinessFieldFulfillmentWindowEndsBeforeStart",
    936     "AppTextKey::SettingsReadinessFieldFulfillmentWindowCutoffAfterStart",
    937     "AppTextKey::SettingsReadinessFieldBlackoutPeriodEndsBeforeStart",
    938     "AppTextKey::SettingsReadinessFieldBlackoutOverlapsFulfillmentWindow",
    939     "AppTextKey::SettingsReadinessReady",
    940     "AppTextKey::SettingsAboutCompanyName",
    941     "AppTextKey::SettingsAboutVersionLabel",
    942     "AppTextKey::SettingsAboutVariantLabel",
    943     "AppTextKey::SettingsAboutAcknowledgementsAction",
    944     "AppTextKey::SettingsAboutPrivacyPolicyAction",
    945     "AppTextKey::SettingsAboutTermsAction",
    946     "AppTextKey::SettingsAboutReportIssueAction",
    947     "AppTextKey::SettingsAboutCopyrightNotice",
    948     "AppTextKey::SettingsAboutTrademarkNotice",
    949     "AppTextKey::SettingsAboutStatusSectionLabel",
    950     "AppTextKey::SettingsAboutConflictReviewSectionLabel",
    951     "AppTextKey::SettingsAboutRuntimeSectionLabel",
    952     "AppTextKey::SettingsAboutConflictReviewUnavailable",
    953     "AppTextKey::SettingsAboutConflictReviewClear",
    954     "AppTextKey::SettingsAboutConflictReviewNeedsAttention",
    955     "AppTextKey::SettingsAboutConflictReviewBlocking",
    956     "AppTextKey::MetadataSelectedAccount",
    957     "AppTextKey::MetadataSyncPendingWriteCount",
    958     "AppTextKey::MetadataSyncBlockingConflictCount",
    959 ];
    960 
    961 const FORBIDDEN_LAUNCHER_UI_BYPASS_PATTERNS: &[(&str, &str)] = &[
    962     (
    963         "Button::new(",
    964         "launcher code must use radroots_app_ui button primitives",
    965     ),
    966     (
    967         "Checkbox::new(",
    968         "launcher code must use radroots_app_ui checkbox primitives",
    969     ),
    970     (
    971         "Input::new(",
    972         "launcher code must use radroots_app_ui input primitives",
    973     ),
    974     (
    975         "TextInput::new(",
    976         "launcher code must use radroots_app_ui input primitives",
    977     ),
    978     (
    979         "pub fn app_",
    980         "shared app_* helpers belong in radroots_app_ui, not in launcher code",
    981     ),
    982     (
    983         "fn app_",
    984         "shared app_* helpers belong in radroots_app_ui, not in launcher code",
    985     ),
    986 ];
    987 
    988 const REMOVED_WINDOW_HELPER_FAMILIES: &[&str] = &[
    989     "fn settings_account_detail_row(",
    990     "fn settings_checkbox_row(",
    991     "fn settings_text_field(",
    992     "fn settings_dynamic_action_button(",
    993     "fn settings_inventory_panel(",
    994     "fn settings_inventory_field_row(",
    995     "fn settings_validation_rows(",
    996     "fn home_farm_setup_blocker(",
    997 ];
    998 
    999 const FORBIDDEN_HARDCODED_WORKFLOW_UI_LITERALS: &[&str] = &[
   1000     "Agreement",
   1001     "Change",
   1002     "Fulfillment",
   1003     "Stock",
   1004     "Requested",
   1005     "Agreed",
   1006     "Payment",
   1007     "Source",
   1008     "Ordered",
   1009     "Confirmed",
   1010     "Declined",
   1011     "Cancelled",
   1012     "Completed",
   1013     "Needs review",
   1014     "No change",
   1015     "Change proposed",
   1016     "Updated",
   1017     "Kept as placed",
   1018     "Preparing",
   1019     "Ready for pickup",
   1020     "Out for delivery",
   1021     "Delivered",
   1022     "Available",
   1023     "Reserved",
   1024     "Sold out",
   1025     "Not recorded",
   1026     "Pending",
   1027     "Recorded",
   1028     "Settled",
   1029     "App",
   1030     "CLI",
   1031     "Relay",
   1032     "Local events",
   1033     "Unknown",
   1034 ];
   1035 
   1036 const FORBIDDEN_STALE_SELLER_LIFECYCLE_PATTERNS: &[&str] = &[
   1037     concat!("orders-detail-", "mark-packed"),
   1038     concat!("orders-detail-", "mark-completed"),
   1039     concat!("orders-row-action-", "mark-packed"),
   1040     concat!("orders-row-action-", "mark-completed"),
   1041     concat!("orders.", "mark_delivered_failed"),
   1042     concat!("OrderPrimaryAction::", "MarkPacked"),
   1043     concat!("OrderPrimaryAction::", "MarkCompleted"),
   1044     concat!("mark", "_packed"),
   1045     concat!("mark", "_completed"),
   1046     concat!("AppTextKey::", "OrdersStatus", "Packed"),
   1047     concat!("AppTextKey::", "OrdersAction", "MarkPacked"),
   1048     concat!("AppTextKey::", "OrdersAction", "MarkCompleted"),
   1049     concat!("orders.status.", "packed"),
   1050     concat!("orders.action.", "mark_packed"),
   1051     concat!("orders.action.", "mark_completed"),
   1052 ];
   1053 
   1054 const FORBIDDEN_PAYMENT_DEFERRAL_COPY_PATTERNS: &[&str] = &[
   1055     "payments are deferred",
   1056     "payment is deferred",
   1057     "payment deferred",
   1058     "payments deferred",
   1059     "deferred payment",
   1060     "deferred payments",
   1061     "checkout unavailable",
   1062     "figure it out",
   1063     "payment handling outside the app",
   1064     "refund outside the app",
   1065     "handle any refund outside the app",
   1066     "settle outside the app",
   1067 ];
   1068 
   1069 const FORBIDDEN_PAYMENT_ACTION_COPY_TERMS: &[&str] = &[
   1070     "checkout",
   1071     "pay",
   1072     "refund",
   1073     "settlement",
   1074     "wallet",
   1075     "invoice",
   1076     "bank",
   1077     "card",
   1078     "processor",
   1079     "provider",
   1080     "payment-provider",
   1081     "payment provider",
   1082 ];
   1083 
   1084 const FORBIDDEN_PRODUCTION_EVENT_KIND_LITERALS: &[(&str, &str)] = &[
   1085     ("30340", "KIND_FARM"),
   1086     ("30402", "KIND_LISTING"),
   1087     ("30403", "KIND_LISTING_DRAFT"),
   1088     ("3422", "KIND_ORDER_REQUEST"),
   1089     ("3423", "KIND_ORDER_DECISION"),
   1090     ("3424", "KIND_ORDER_REVISION_PROPOSAL"),
   1091     ("3425", "KIND_ORDER_REVISION_DECISION"),
   1092     ("3426", "KIND_TRADE_QUESTION"),
   1093     ("3427", "KIND_TRADE_ANSWER"),
   1094     ("3428", "KIND_TRADE_DISCOUNT_REQUEST"),
   1095     ("3429", "KIND_TRADE_DISCOUNT_OFFER"),
   1096     ("3430", "KIND_TRADE_DISCOUNT_ACCEPT"),
   1097     ("3431", "RESERVED_ORDER_KIND_3431"),
   1098     ("3432", "KIND_ORDER_CANCELLATION"),
   1099     ("3433", "KIND_ORDER_FULFILLMENT_UPDATE"),
   1100     ("3434", "KIND_ORDER_RECEIPT"),
   1101     ("3435", "KIND_ORDER_PAYMENT_RECORD"),
   1102     ("3436", "KIND_ORDER_SETTLEMENT_DECISION"),
   1103     ("3440", "KIND_TRADE_VALIDATION_RECEIPT"),
   1104 ];
   1105 
   1106 struct SdkBoundaryForbiddenPattern {
   1107     pattern: &'static str,
   1108     reason: &'static str,
   1109 }
   1110 
   1111 struct LegacySdkBoundaryAllowlistEntry {
   1112     path: &'static str,
   1113     pattern: &'static str,
   1114     owner: &'static str,
   1115     reason: &'static str,
   1116     removal_condition: &'static str,
   1117 }
   1118 
   1119 const TEST_MODULE_SENTINEL: &str = "\n#[cfg(test)]\nmod tests {";
   1120 
   1121 const STRICT_SDK_BOUNDARY_FORBIDDEN_PATTERNS: &[SdkBoundaryForbiddenPattern] = &[
   1122     SdkBoundaryForbiddenPattern {
   1123         pattern: "SdkDirectRelayAppSyncTransport",
   1124         reason: "app production sources must use AppSdkRuntime instead of the legacy direct relay sync transport",
   1125     },
   1126     SdkBoundaryForbiddenPattern {
   1127         pattern: "RadrootsSdkClient",
   1128         reason: "app production sources must use the long-lived RadrootsSdk runtime boundary",
   1129     },
   1130     SdkBoundaryForbiddenPattern {
   1131         pattern: "RadrootsSdkConfig",
   1132         reason: "app production sources must use AppSdkConfig-derived runtime construction",
   1133     },
   1134     SdkBoundaryForbiddenPattern {
   1135         pattern: "SdkTransportMode::RelayDirect",
   1136         reason: "app production sources must not configure direct relay publish transport",
   1137     },
   1138     SdkBoundaryForbiddenPattern {
   1139         pattern: "SignerConfig::LocalIdentity",
   1140         reason: "app production sources must not configure local direct-publish signing",
   1141     },
   1142     SdkBoundaryForbiddenPattern {
   1143         pattern: "PendingSyncOperation::from_publish_payload",
   1144         reason: "app production sources must not enqueue legacy app publish payloads",
   1145     },
   1146     SdkBoundaryForbiddenPattern {
   1147         pattern: ".enqueue_pending_operation(",
   1148         reason: "app production sources must not mutate the legacy app outbox",
   1149     },
   1150     SdkBoundaryForbiddenPattern {
   1151         pattern: "INSERT INTO local_outbox",
   1152         reason: "app production sources must not write legacy local outbox rows",
   1153     },
   1154     SdkBoundaryForbiddenPattern {
   1155         pattern: "UPDATE local_outbox",
   1156         reason: "app production sources must not mutate legacy local outbox rows",
   1157     },
   1158     SdkBoundaryForbiddenPattern {
   1159         pattern: "DELETE FROM local_outbox",
   1160         reason: "app production sources must not delete legacy local outbox rows",
   1161     },
   1162     SdkBoundaryForbiddenPattern {
   1163         pattern: "RadrootsOutbox",
   1164         reason: "canonical SDK outbox access belongs inside the SDK crate",
   1165     },
   1166     SdkBoundaryForbiddenPattern {
   1167         pattern: "enqueue_signed_operation",
   1168         reason: "canonical SDK outbox writes must go through SDK APIs",
   1169     },
   1170     SdkBoundaryForbiddenPattern {
   1171         pattern: "claim_ready_events",
   1172         reason: "canonical SDK outbox push claims must go through SDK APIs",
   1173     },
   1174     SdkBoundaryForbiddenPattern {
   1175         pattern: "connected_client_from_identity",
   1176         reason: "app production sources must not connect relay clients directly for publish",
   1177     },
   1178     SdkBoundaryForbiddenPattern {
   1179         pattern: "publish_signed_event",
   1180         reason: "app production sources must not publish signed events directly",
   1181     },
   1182     SdkBoundaryForbiddenPattern {
   1183         pattern: "radroots_nostr_build_event",
   1184         reason: "app production sources must not build protocol events outside SDK-owned publish APIs",
   1185     },
   1186     SdkBoundaryForbiddenPattern {
   1187         pattern: "RadrootsIdentity::from_secret_key_str",
   1188         reason: "app production sources must not parse direct signing keys",
   1189     },
   1190     SdkBoundaryForbiddenPattern {
   1191         pattern: "RawSecretKey",
   1192         reason: "app production sources must not import raw signing-key material",
   1193     },
   1194     SdkBoundaryForbiddenPattern {
   1195         pattern: "EncryptedSecretKey",
   1196         reason: "app production sources must not import encrypted signing-key material",
   1197     },
   1198     SdkBoundaryForbiddenPattern {
   1199         pattern: "publish_with_identity",
   1200         reason: "app production sources must not call legacy direct SDK publish APIs",
   1201     },
   1202     SdkBoundaryForbiddenPattern {
   1203         pattern: "publish_draft_with_identity",
   1204         reason: "app production sources must not encode legacy direct SDK publish targets",
   1205     },
   1206     SdkBoundaryForbiddenPattern {
   1207         pattern: "publish_order_request_with_identity",
   1208         reason: "app production sources must not call legacy direct SDK order publish APIs",
   1209     },
   1210     SdkBoundaryForbiddenPattern {
   1211         pattern: "publish_order_decision_with_identity",
   1212         reason: "app production sources must not call legacy direct SDK order publish APIs",
   1213     },
   1214     SdkBoundaryForbiddenPattern {
   1215         pattern: "publish_order_revision_proposal_with_identity",
   1216         reason: "app production sources must not call legacy direct SDK order publish APIs",
   1217     },
   1218     SdkBoundaryForbiddenPattern {
   1219         pattern: "publish_order_revision_decision_with_identity",
   1220         reason: "app production sources must not call legacy direct SDK order publish APIs",
   1221     },
   1222     SdkBoundaryForbiddenPattern {
   1223         pattern: "publish_order_cancellation_with_identity",
   1224         reason: "app production sources must not call legacy direct SDK order publish APIs",
   1225     },
   1226     SdkBoundaryForbiddenPattern {
   1227         pattern: "publish_fulfillment_update_with_identity",
   1228         reason: "app production sources must not call legacy direct SDK fulfillment publish APIs",
   1229     },
   1230     SdkBoundaryForbiddenPattern {
   1231         pattern: "publish_buyer_receipt_with_identity",
   1232         reason: "app production sources must not call legacy direct SDK receipt publish APIs",
   1233     },
   1234 ];
   1235 
   1236 const LEGACY_SDK_BOUNDARY_ALLOWLIST: &[LegacySdkBoundaryAllowlistEntry] = &[
   1237     LegacySdkBoundaryAllowlistEntry {
   1238         path: "crates/desktop/src/accounts.rs",
   1239         pattern: "RadrootsIdentity::from_secret_key_str",
   1240         owner: "rpv1-app-sdk-hardening.04",
   1241         reason: "desktop account import still accepts local raw secret-key material for account bootstrap",
   1242         removal_condition: "remove when local account import is mediated by protected signer adapters instead of raw key parsing",
   1243     },
   1244     LegacySdkBoundaryAllowlistEntry {
   1245         path: "crates/desktop/src/accounts.rs",
   1246         pattern: "RawSecretKey",
   1247         owner: "rpv1-app-sdk-hardening.04",
   1248         reason: "desktop account import still exposes a raw secret-key import mode for account bootstrap",
   1249         removal_condition: "remove when local account import is mediated by protected signer adapters instead of raw key import modes",
   1250     },
   1251     LegacySdkBoundaryAllowlistEntry {
   1252         path: "crates/desktop/src/accounts.rs",
   1253         pattern: "EncryptedSecretKey",
   1254         owner: "rpv1-app-sdk-hardening.04",
   1255         reason: "desktop account import still exposes an encrypted secret-key import mode for account bootstrap",
   1256         removal_condition: "remove when local account import is mediated by protected signer adapters instead of secret-key import modes",
   1257     },
   1258     LegacySdkBoundaryAllowlistEntry {
   1259         path: "crates/signer/src/protocol.rs",
   1260         pattern: "RadrootsIdentity::from_secret_key_str",
   1261         owner: "rpv1-sdksign.5",
   1262         reason: "remote signer startup custody still reloads the NIP-46 client identity before shared protocol transport execution",
   1263         removal_condition: "remove when startup remote signer custody stores client identities through protected signer-session APIs",
   1264     },
   1265     LegacySdkBoundaryAllowlistEntry {
   1266         path: "crates/store/src/lib.rs",
   1267         pattern: ".enqueue_pending_operation(",
   1268         owner: "rpv1-app-sdk-refactor.07",
   1269         reason: "store facade still accepts legacy app local_outbox publish operations for deferred workflows",
   1270         removal_condition: "remove when app local_outbox enqueue is replaced by SDK canonical outbox enqueue APIs",
   1271     },
   1272     LegacySdkBoundaryAllowlistEntry {
   1273         path: "crates/store/src/sync.rs",
   1274         pattern: "INSERT INTO local_outbox",
   1275         owner: "rpv1-app-sdk-refactor.07",
   1276         reason: "store sync implementation still writes legacy app local_outbox rows for deferred workflows",
   1277         removal_condition: "remove when app local_outbox storage is retired after SDK canonical outbox migration",
   1278     },
   1279     LegacySdkBoundaryAllowlistEntry {
   1280         path: "crates/store/src/sync.rs",
   1281         pattern: "UPDATE local_outbox",
   1282         owner: "rpv1-app-sdk-refactor.07",
   1283         reason: "store sync implementation still updates legacy app local_outbox rows for deferred workflows",
   1284         removal_condition: "remove when app local_outbox storage is retired after SDK canonical outbox migration",
   1285     },
   1286     LegacySdkBoundaryAllowlistEntry {
   1287         path: "crates/store/src/sync.rs",
   1288         pattern: "DELETE FROM local_outbox",
   1289         owner: "rpv1-app-sdk-refactor.07",
   1290         reason: "store sync implementation still deletes legacy app local_outbox rows for deferred workflows",
   1291         removal_condition: "remove when app local_outbox storage is retired after SDK canonical outbox migration",
   1292     },
   1293 ];
   1294 
   1295 #[test]
   1296 fn desktop_menu_source_uses_localized_copy_paths() {
   1297     assert_eq!(
   1298         extract_string_literals(include_str!("menus.rs")),
   1299         ALLOWED_MENU_LITERALS
   1300             .iter()
   1301             .copied()
   1302             .collect::<BTreeSet<_>>()
   1303     );
   1304 }
   1305 
   1306 #[test]
   1307 fn desktop_window_source_uses_localized_copy_paths() {
   1308     assert_eq!(
   1309         extract_string_literals(include_str!("window.rs")),
   1310         ALLOWED_WINDOW_LITERALS
   1311             .iter()
   1312             .copied()
   1313             .collect::<BTreeSet<_>>()
   1314     );
   1315 }
   1316 
   1317 #[test]
   1318 fn desktop_window_source_keeps_shell_reset_copy_keyed() {
   1319     let source = include_str!("window.rs");
   1320 
   1321     for copy_key in REQUIRED_WINDOW_COPY_KEYS {
   1322         assert!(
   1323             source.contains(copy_key),
   1324             "desktop window is missing localized copy key {copy_key}"
   1325         );
   1326     }
   1327 }
   1328 
   1329 #[test]
   1330 fn desktop_window_source_uses_settings_width_theme_token() {
   1331     let source = include_str!("window.rs");
   1332 
   1333     assert!(
   1334         !source.contains("Some(560.0)"),
   1335         "settings panel width caps must use APP_UI_THEME.shells.settings_panel_content_max_width_px"
   1336     );
   1337     assert!(
   1338         source.contains("settings_panel_content_max_width_px"),
   1339         "settings panel width token is not used by window.rs"
   1340     );
   1341 }
   1342 
   1343 #[test]
   1344 fn desktop_launcher_source_keeps_shared_ui_boundary_enforced() {
   1345     for (path, source) in launcher_source_files() {
   1346         for (pattern, reason) in FORBIDDEN_LAUNCHER_UI_BYPASS_PATTERNS {
   1347             assert!(
   1348                 !source.contains(pattern),
   1349                 "{} contains forbidden UI bypass pattern `{pattern}`: {reason}",
   1350                 path.display()
   1351             );
   1352         }
   1353     }
   1354 }
   1355 
   1356 #[test]
   1357 fn desktop_window_source_does_not_reintroduce_removed_ui_helper_families() {
   1358     let source = include_str!("window.rs");
   1359 
   1360     for helper_name in REMOVED_WINDOW_HELPER_FAMILIES {
   1361         assert!(
   1362             !source.contains(helper_name),
   1363             "window.rs reintroduced removed launcher-local helper family `{helper_name}`"
   1364         );
   1365     }
   1366 }
   1367 
   1368 #[test]
   1369 fn app_sources_use_publish_lifecycle_action_identifiers() {
   1370     for (path, source) in seller_lifecycle_action_owner_sources() {
   1371         for pattern in FORBIDDEN_STALE_SELLER_LIFECYCLE_PATTERNS {
   1372             assert!(
   1373                 !source.contains(pattern),
   1374                 "{} still contains stale seller lifecycle action pattern `{pattern}`",
   1375                 path.display()
   1376             );
   1377         }
   1378     }
   1379 }
   1380 
   1381 #[test]
   1382 fn desktop_window_source_does_not_use_about_placeholder_copy() {
   1383     let source = include_str!("window.rs");
   1384 
   1385     assert!(
   1386         !source.contains("SettingsAboutPlaceholder"),
   1387         "window.rs still references retired about placeholder copy"
   1388     );
   1389 }
   1390 
   1391 #[test]
   1392 fn desktop_sources_do_not_hardcode_workflow_ui_copy() {
   1393     for (path, source) in launcher_source_files() {
   1394         let literals = extract_string_literals(&source);
   1395         for literal in literals {
   1396             for forbidden_literal in FORBIDDEN_HARDCODED_WORKFLOW_UI_LITERALS {
   1397                 assert_ne!(
   1398                     literal,
   1399                     *forbidden_literal,
   1400                     "{} hardcodes workflow UI copy `{forbidden_literal}`",
   1401                     path.display()
   1402                 );
   1403             }
   1404         }
   1405     }
   1406 }
   1407 
   1408 #[test]
   1409 fn desktop_sources_do_not_expose_reserved_payment_action_copy() {
   1410     for (path, source) in launcher_source_files() {
   1411         for literal in extract_string_literals(&source) {
   1412             let normalized_literal = literal.to_lowercase();
   1413             for pattern in FORBIDDEN_PAYMENT_DEFERRAL_COPY_PATTERNS {
   1414                 assert!(
   1415                     !normalized_literal.contains(pattern),
   1416                     "{} contains forbidden payment-deferral copy `{pattern}`",
   1417                     path.display()
   1418                 );
   1419             }
   1420             for term in FORBIDDEN_PAYMENT_ACTION_COPY_TERMS {
   1421                 assert!(
   1422                     !contains_reserved_payment_action_term(&normalized_literal, term),
   1423                     "{} contains reserved payment action copy `{term}` in `{literal}`",
   1424                     path.display()
   1425                 );
   1426             }
   1427         }
   1428     }
   1429 }
   1430 
   1431 #[test]
   1432 fn app_production_trade_event_kinds_use_shared_constants() {
   1433     assert_production_source_omits_event_kind_literals(
   1434         "crates/desktop/src/runtime.rs",
   1435         include_str!("runtime.rs"),
   1436     );
   1437 
   1438     let store_interop_path = Path::new(env!("CARGO_MANIFEST_DIR"))
   1439         .parent()
   1440         .and_then(|path| path.parent())
   1441         .expect("desktop crate should live under app crates directory")
   1442         .join("crates/store/src/interop.rs");
   1443     let store_interop_source =
   1444         fs::read_to_string(store_interop_path.as_path()).unwrap_or_else(|error| {
   1445             panic!(
   1446                 "failed to read app store interop source {}: {error}",
   1447                 store_interop_path.display()
   1448             )
   1449         });
   1450 
   1451     assert_production_source_omits_event_kind_literals(
   1452         "crates/store/src/interop.rs",
   1453         store_interop_source.as_str(),
   1454     );
   1455 }
   1456 
   1457 #[test]
   1458 fn app_production_sdk_boundary_usage_is_allowlisted() {
   1459     for (relative_path, source) in app_rust_source_files() {
   1460         let production_source = production_source_without_tests(&source);
   1461         let findings =
   1462             unallowlisted_sdk_boundary_patterns(relative_path.as_str(), production_source);
   1463 
   1464         assert!(
   1465             findings.is_empty(),
   1466             "{} contains unallowlisted SDK boundary pattern `{}`: {}",
   1467             relative_path,
   1468             findings.first().map_or("", |finding| finding.pattern),
   1469             findings.first().map_or("", |finding| finding.reason)
   1470         );
   1471     }
   1472 }
   1473 
   1474 #[test]
   1475 fn strict_sdk_boundary_scanner_rejects_unallowlisted_new_production_paths() {
   1476     let findings = unallowlisted_sdk_boundary_patterns(
   1477         "crates/desktop/src/new_workflow.rs",
   1478         "fn publish() { let _ = RadrootsSdkClient::from_config(config); }",
   1479     );
   1480 
   1481     assert_eq!(findings.len(), 1);
   1482     assert_eq!(findings[0].pattern, "RadrootsSdkClient");
   1483     let runtime_findings = unallowlisted_sdk_boundary_patterns(
   1484         "crates/desktop/src/runtime.rs",
   1485         "fn publish() { let _ = RadrootsSdkClient::from_config(config); }",
   1486     );
   1487     assert_eq!(runtime_findings.len(), 1);
   1488     assert_eq!(runtime_findings[0].pattern, "RadrootsSdkClient");
   1489     assert!(
   1490         unallowlisted_sdk_boundary_patterns(
   1491             "crates/desktop/src/accounts.rs",
   1492             "fn import() { let _ = RawSecretKey; }",
   1493         )
   1494         .is_empty()
   1495     );
   1496 }
   1497 
   1498 #[test]
   1499 fn app_legacy_sdk_boundary_allowlist_entries_are_complete_and_current() {
   1500     let app_root = app_root();
   1501     let mut entries = BTreeSet::new();
   1502 
   1503     for entry in LEGACY_SDK_BOUNDARY_ALLOWLIST {
   1504         assert!(
   1505             entries.insert((entry.path, entry.pattern)),
   1506             "duplicate legacy SDK boundary allowlist entry {} `{}`",
   1507             entry.path,
   1508             entry.pattern
   1509         );
   1510         assert!(
   1511             !entry.owner.trim().is_empty(),
   1512             "{} `{}` is missing an owner",
   1513             entry.path,
   1514             entry.pattern
   1515         );
   1516         assert!(
   1517             !entry.reason.trim().is_empty(),
   1518             "{} `{}` is missing a reason",
   1519             entry.path,
   1520             entry.pattern
   1521         );
   1522         assert!(
   1523             !entry.removal_condition.trim().is_empty(),
   1524             "{} `{}` is missing a removal condition",
   1525             entry.path,
   1526             entry.pattern
   1527         );
   1528 
   1529         let source_path = app_root.join(entry.path);
   1530         let source = read_source_path(source_path.as_path());
   1531         let production_source = production_source_without_tests(&source);
   1532         assert!(
   1533             production_source.contains(entry.pattern),
   1534             "{} allowlists legacy SDK boundary pattern `{}` that is no longer present",
   1535             entry.path,
   1536             entry.pattern
   1537         );
   1538     }
   1539 }
   1540 
   1541 fn extract_string_literals(source: &str) -> BTreeSet<&str> {
   1542     let mut literals = BTreeSet::new();
   1543     let bytes = source.as_bytes();
   1544     let mut start = None;
   1545     let mut escaped = false;
   1546 
   1547     for (index, byte) in bytes.iter().copied().enumerate() {
   1548         match (start, byte, escaped) {
   1549             (None, b'"', _) => start = Some(index + 1),
   1550             (Some(_), b'\\', false) => escaped = true,
   1551             (Some(begin), b'"', false) => {
   1552                 literals.insert(&source[begin..index]);
   1553                 start = None;
   1554             }
   1555             (Some(_), _, true) => escaped = false,
   1556             _ => {}
   1557         }
   1558     }
   1559 
   1560     literals
   1561 }
   1562 
   1563 fn assert_production_source_omits_event_kind_literals(path: &str, source: &str) {
   1564     let production_source = production_source_without_tests(source);
   1565     for (literal, constant_name) in FORBIDDEN_PRODUCTION_EVENT_KIND_LITERALS {
   1566         assert!(
   1567             !contains_numeric_token(production_source, literal),
   1568             "{path} uses raw event kind {literal}; use shared {constant_name} instead"
   1569         );
   1570     }
   1571 }
   1572 
   1573 fn production_source_without_tests(source: &str) -> &str {
   1574     source
   1575         .split_once(TEST_MODULE_SENTINEL)
   1576         .map_or(source, |(production_source, _)| production_source)
   1577 }
   1578 
   1579 fn unallowlisted_sdk_boundary_patterns(
   1580     path: &str,
   1581     production_source: &str,
   1582 ) -> Vec<&'static SdkBoundaryForbiddenPattern> {
   1583     STRICT_SDK_BOUNDARY_FORBIDDEN_PATTERNS
   1584         .iter()
   1585         .filter(|forbidden| production_source.contains(forbidden.pattern))
   1586         .filter(|forbidden| !legacy_sdk_boundary_allowlist_contains(path, forbidden.pattern))
   1587         .collect()
   1588 }
   1589 
   1590 fn legacy_sdk_boundary_allowlist_contains(path: &str, pattern: &str) -> bool {
   1591     LEGACY_SDK_BOUNDARY_ALLOWLIST
   1592         .iter()
   1593         .any(|entry| entry.path == path && entry.pattern == pattern)
   1594 }
   1595 
   1596 fn read_source_path(path: &Path) -> String {
   1597     fs::read_to_string(path)
   1598         .unwrap_or_else(|error| panic!("failed to read source {}: {error}", path.display()))
   1599 }
   1600 
   1601 fn app_root() -> PathBuf {
   1602     Path::new(env!("CARGO_MANIFEST_DIR"))
   1603         .parent()
   1604         .and_then(|path| path.parent())
   1605         .expect("desktop crate should live under app crates directory")
   1606         .to_path_buf()
   1607 }
   1608 
   1609 fn app_rust_source_files() -> Vec<(String, String)> {
   1610     let app_root = app_root();
   1611     let mut paths = Vec::new();
   1612     collect_rust_source_files(app_root.join("crates").as_path(), &mut paths);
   1613     paths.sort();
   1614     paths
   1615         .into_iter()
   1616         .filter(|path| path.file_name().and_then(|name| name.to_str()) != Some("source_guards.rs"))
   1617         .map(|path| {
   1618             let relative_path = path
   1619                 .strip_prefix(app_root.as_path())
   1620                 .unwrap_or_else(|error| {
   1621                     panic!(
   1622                         "failed to derive app-relative source path {}: {error}",
   1623                         path.display()
   1624                     )
   1625                 })
   1626                 .to_string_lossy()
   1627                 .replace('\\', "/");
   1628             let source = read_source_path(path.as_path());
   1629             (relative_path, source)
   1630         })
   1631         .collect()
   1632 }
   1633 
   1634 fn contains_numeric_token(source: &str, literal: &str) -> bool {
   1635     source.match_indices(literal).any(|(start, _)| {
   1636         let end = start + literal.len();
   1637         let before_ok = start == 0 || !is_rust_identifier_byte(source.as_bytes()[start - 1]);
   1638         let after_ok = end == source.len() || !source.as_bytes()[end].is_ascii_digit();
   1639         before_ok && after_ok
   1640     })
   1641 }
   1642 
   1643 fn is_rust_identifier_byte(byte: u8) -> bool {
   1644     byte.is_ascii_alphanumeric() || byte == b'_'
   1645 }
   1646 
   1647 fn contains_reserved_payment_action_term(value: &str, term: &str) -> bool {
   1648     if term.contains(' ') || term.contains('-') {
   1649         return value.contains(term);
   1650     }
   1651 
   1652     value.match_indices(term).any(|(start, _)| {
   1653         let end = start + term.len();
   1654         is_reserved_payment_term_boundary_before(value, start)
   1655             && is_reserved_payment_term_boundary_after(value, end)
   1656     })
   1657 }
   1658 
   1659 fn is_reserved_payment_term_boundary_before(value: &str, index: usize) -> bool {
   1660     if index == 0 {
   1661         return true;
   1662     }
   1663 
   1664     is_reserved_payment_term_boundary_byte(value.as_bytes()[index - 1])
   1665 }
   1666 
   1667 fn is_reserved_payment_term_boundary_after(value: &str, index: usize) -> bool {
   1668     if index == value.len() {
   1669         return true;
   1670     }
   1671 
   1672     is_reserved_payment_term_boundary_byte(value.as_bytes()[index])
   1673 }
   1674 
   1675 fn is_reserved_payment_term_boundary_byte(byte: u8) -> bool {
   1676     !byte.is_ascii_alphanumeric() && byte != b'_' && byte != b'-'
   1677 }
   1678 
   1679 fn launcher_source_files() -> Vec<(PathBuf, String)> {
   1680     let mut paths = Vec::new();
   1681     collect_rust_source_files(
   1682         Path::new(env!("CARGO_MANIFEST_DIR")).join("src").as_path(),
   1683         &mut paths,
   1684     );
   1685     paths.sort();
   1686     paths
   1687         .into_iter()
   1688         .filter(|path| path.file_name().and_then(|name| name.to_str()) != Some("source_guards.rs"))
   1689         .map(|path| {
   1690             let source = fs::read_to_string(&path).unwrap_or_else(|error| {
   1691                 panic!("failed to read launcher source {}: {error}", path.display())
   1692             });
   1693             (path, source)
   1694         })
   1695         .collect()
   1696 }
   1697 
   1698 fn seller_lifecycle_action_owner_sources() -> Vec<(PathBuf, String)> {
   1699     let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
   1700     let app_root = manifest_dir
   1701         .parent()
   1702         .and_then(|path| path.parent())
   1703         .expect("desktop crate should live under app crates directory");
   1704     [
   1705         manifest_dir.join("src/window.rs"),
   1706         manifest_dir.join("src/runtime.rs"),
   1707         app_root.join("crates/view/src/lib.rs"),
   1708         app_root.join("crates/store/src/repo/orders.rs"),
   1709         app_root.join("crates/i18n/src/keys.rs"),
   1710         app_root.join("crates/i18n/src/lib.rs"),
   1711         app_root.join("i18n/locales/en/messages.json"),
   1712     ]
   1713     .into_iter()
   1714     .map(|path| {
   1715         let source = fs::read_to_string(&path).unwrap_or_else(|error| {
   1716             panic!(
   1717                 "failed to read seller lifecycle source {}: {error}",
   1718                 path.display()
   1719             )
   1720         });
   1721         (path, source)
   1722     })
   1723     .collect()
   1724 }
   1725 
   1726 fn collect_rust_source_files(root: &Path, paths: &mut Vec<PathBuf>) {
   1727     let entries = fs::read_dir(root).unwrap_or_else(|error| {
   1728         panic!(
   1729             "failed to read launcher source directory {}: {error}",
   1730             root.display()
   1731         )
   1732     });
   1733 
   1734     for entry in entries {
   1735         let entry = entry.unwrap_or_else(|error| {
   1736             panic!(
   1737                 "failed to inspect launcher source directory {}: {error}",
   1738                 root.display()
   1739             )
   1740         });
   1741         let path = entry.path();
   1742 
   1743         if path.is_dir() {
   1744             collect_rust_source_files(path.as_path(), paths);
   1745             continue;
   1746         }
   1747 
   1748         if path.extension().and_then(|extension| extension.to_str()) == Some("rs") {
   1749             paths.push(path);
   1750         }
   1751     }
   1752 }