



  

<!DOCTYPE html>
<html
  lang="en"
  
  data-color-mode="auto" data-light-theme="light" data-dark-theme="dark"
  data-a11y-animated-images="system" data-a11y-link-underlines="true"
  
  >




  <head>
    <meta charset="utf-8">
  <link rel="dns-prefetch" href="https://github.githubassets.com">
  <link rel="dns-prefetch" href="https://avatars.githubusercontent.com">
  <link rel="dns-prefetch" href="https://github-cloud.s3.amazonaws.com">
  <link rel="dns-prefetch" href="https://user-images.githubusercontent.com/">
  <link rel="preconnect" href="https://github.githubassets.com" crossorigin>
  <link rel="preconnect" href="https://avatars.githubusercontent.com">

<script type="importmap">{"imports":{"react":"https://github.githubassets.com/assets/react-e27d1b3e03961e68.js","react-dom":"https://github.githubassets.com/assets/react-dom-e5fd46a22d5c4058.js","react-dom/client":"https://github.githubassets.com/assets/react-dom-client-1b4a3ee065998cea.js","react-is":"https://github.githubassets.com/assets/react-is-e0b593954b4706d8.js","react-reconciler":"https://github.githubassets.com/assets/react-reconciler-8e99e505c4429605.js","react/compiler-runtime":"https://github.githubassets.com/assets/react-compiler-runtime-4610bd6d3de9c049.js","react/jsx-dev-runtime":"https://github.githubassets.com/assets/react-jsx-dev-runtime-ea55d68667d559e5.js","react/jsx-runtime":"https://github.githubassets.com/assets/react-jsx-runtime-4915cb0f5b3aff04.js","scheduler":"https://github.githubassets.com/assets/scheduler-58b860b049ca307c.js"}}</script>
<meta name="react-profiling" content="0" data-turbo-transient="true" />
<meta name="react-import-map" content="react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@777f63f87cd0" data-turbo-track="reload" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-e27d1b3e03961e68.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-compiler-runtime-4610bd6d3de9c049.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/scheduler-58b860b049ca307c.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-dom-e5fd46a22d5c4058.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-dom-client-1b4a3ee065998cea.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-is-e0b593954b4706d8.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-jsx-runtime-4915cb0f5b3aff04.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-reconciler-8e99e505c4429605.js" />

  


  <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/light-5c4e9fc574bf49f3.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/light_high_contrast-fb37c309e0603a69.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/dark-afff6c53aef9b9d1.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/dark_high_contrast-c409873d7987dffa.css" /><link data-color-theme="light" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light-5c4e9fc574bf49f3.css" /><link data-color-theme="light_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_high_contrast-fb37c309e0603a69.css" /><link data-color-theme="light_colorblind" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_colorblind-0f910d8806d5761b.css" /><link data-color-theme="light_colorblind_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_colorblind_high_contrast-a7abd4d4c49ac593.css" /><link data-color-theme="light_tritanopia" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_tritanopia-5fc4c4a9cb41e596.css" /><link data-color-theme="light_tritanopia_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_tritanopia_high_contrast-ed0cee9214dedabd.css" /><link data-color-theme="dark" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark-afff6c53aef9b9d1.css" /><link data-color-theme="dark_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_high_contrast-c409873d7987dffa.css" /><link data-color-theme="dark_colorblind" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_colorblind-78a2491d538dab5a.css" /><link data-color-theme="dark_colorblind_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_colorblind_high_contrast-c26b63d7c532d0a2.css" /><link data-color-theme="dark_tritanopia" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_tritanopia-a6f60cea68c08405.css" /><link data-color-theme="dark_tritanopia_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_tritanopia_high_contrast-f66faf28b2ea18b6.css" /><link data-color-theme="dark_dimmed" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_dimmed-6701f6218c53cf5b.css" /><link data-color-theme="dark_dimmed_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_dimmed_high_contrast-7037fa46ae124d97.css" />

  <style type="text/css">
    :root {
      --tab-size-preference: 4;
    }

    pre, code {
      tab-size: var(--tab-size-preference);
    }
  </style>

    <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-primitives-97df7784617ce1ea.css" />
    <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-9be9fe6313f476af.css" />
    <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/global-62747e27e61258dc.css" />
    <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/github-b04ca6fccda778e2.css" />
  <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/repository-72bd7d974b5ac1cc.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/code-4c5c0895f723f870.css" />

  

  <script type="application/json" id="client-env">{"locale":"en","featureFlags":["actions_caches_react_shell","actions_enable_background_steps","actions_new_hosted_runner_image_select_sizes_and_versions","actions_runners_react_shell","agent_author_search_expansion","agent_author_search_expansion_ui_pulls","alternate_user_config_repo","async_conversion_coverage_enabled","billing_billable_licenses_cost_center_bucket_fix","billing_budget_expiration","billing_cost_center_list_assigned_resources","billing_discount_threshold_notification","code_quality_enablement_banner_targeting","code_quality_remove_preview","code_view_raf_sticky_lines","codespaces_prebuild_region_target_update","coding_agent_third_party_model_ui","copilot_agent_snippy","copilot_ahp_tool_call_timing","copilot_api_agentic_issue_marshal_yaml","copilot_automation_repo_mcp_servers","copilot_automations_pagination","copilot_base_model_policy_row","copilot_chat_auto_mode_v2","copilot_chat_clear_model_selection_for_default_change","copilot_chat_early_task_provisioning","copilot_chat_persist_session_drafts","copilot_chat_vision_dotcom_chat_ga_gate","copilot_css_textarea_autosize","copilot_custom_copilots","copilot_custom_copilots_feature_preview","copilot_duplicate_thread","copilot_extensions_removal_on_marketplace","copilot_fix_failed_workflows_all_skus","copilot_hide_hovercard","copilot_immersive_task_hyperlinking","copilot_mc_cli_resume_any_users_task","copilot_mission_control_agent_merge_fix_ci","copilot_mission_control_agent_merge_resolve_conflicts","copilot_mission_control_awps_batching","copilot_mission_control_early_stop","copilot_mission_control_managed_sandbox_environments","copilot_mission_control_needs_attention","copilot_mission_control_reasoning_effort","copilot_mission_control_sandbox_remote_bypass","copilot_mission_control_task_alive_updates","copilot_mission_control_task_sharing","copilot_org_policy_page_focus_mode","copilot_share_active_subthread","copilot_spaces_ga","copilot_spaces_individual_policies_ga","copilot_swe_agent_authorization_status_ui","copilot_swe_agent_automation_resource_scoped_writes","copilot_swe_agent_discussion_comment_trigger","copilot_swe_agent_discussion_opened_trigger","copilot_swe_agent_discussion_updated_trigger","copilot_swe_agent_hide_model_picker_if_only_auto","copilot_swe_agent_issue_assigned_trigger","copilot_swe_agent_issue_comment_trigger","copilot_swe_agent_issue_labeled_trigger","copilot_swe_agent_pr_comment_model_picker","copilot_swe_agent_pull_request_assigned_trigger","copilot_swe_agent_pull_request_comment_trigger","copilot_swe_agent_pull_request_labeled_trigger","copilot_swe_agent_pull_request_merged_trigger","copilot_swe_agent_pull_request_opened_trigger","copilot_swe_agent_pull_request_ready_for_review_trigger","copilot_swe_agent_pull_request_review_requested_trigger","copilot_swe_agent_pull_request_review_submitted_trigger","copilot_swe_agent_pull_request_synchronize_trigger","copilot_swe_agent_sub_issue_added_trigger","copilot_swe_agent_use_subagents","copilot_task_api_github_rest_style","copilot_task_scoped_alive_channel","copilot_token_based_billing","copilot_unconfigured_is_inherited","copilot_user_can_upgrade_plan_field","copilot_web_integration_cutover","copilot_workbench_sunset_redirect","dashboard_agents_module_auth_token_check","dashboard_indexeddb_caching","fgpat_permissions_selector_redesign","glc_code_quality_repo_settings_workflow_config","hyperspace_2025_logged_out_batch_1","hyperspace_2025_logged_out_batch_2","hyperspace_2025_logged_out_batch_3","in_product_messaging_datadog_monitoring","ipm_ubb_individual_budget_banner","issue_fields_multi_select","issue_inline_avatars","issue_pinned_views","issue_pinned_views_team_vs_personal","issue_relative_time_micro","issue_viewer_paved_path","issues_expanded_file_types","issues_hide_closed_sub_issues","issues_lazy_load_comment_box_suggestions","issues_react_chrome_container_query_fix","labels_archiving","labels_archiving_info","landing_pages_ninetailed","lifecycle_label_name_updates","marketing_cookie_consent_banner","marketing_pages_search_explore_provider","memex_default_issue_create_repository","memex_live_update_hovercard","memex_mwl_filter_field_delimiter","memex_remove_deprecated_type_issue","merge_queue_restricted_pushers_warning","merge_status_header_feedback","milestone_closed_issues_prioritization","octocaptcha_origin_optimization","primer_react_css_anchor_positioning","primer_react_merged_forwarded_refs","primer_react_timeline_list_semantics","prs_copilot_app_open_action","prs_css_anchor_positioning","pull_request_copilot_attribution_header","pull_request_overview_merge_control","pull_request_overview_panel_edit_description","pull_request_persister","pull_request_stacks_navigation_shortcuts","pull_request_virtualization_image_estimate","pull_request_virtualization_loader_batching","pull_request_virtualization_scroll_compensation","pull_request_virtualization_scroll_intent","quick_search_lazy_suggestions","react_blob_isolate_code_lines","react_blob_ssr_content_visibility","react_data_router_tanstack_allowed","react_logged_out_repository_header","react_query_props_with_key","react_sandbox_future_tanstack","repo_app_turbo","repo_issues_sidebar_layout","repo_pulls_dashboard_declutter","repo_pulls_dashboard_ga","repo_pulls_dashboard_persistence","repos_contributors_limited_default_range","review_involves_filter","rule_ignored_file_paths","rulesets_actor_list_editor","sample_network_conn_type","security_center_artifact_filters_popover","see_who_reacted","semantic_similarity_duplicate_issue_detection","session_logs_ungroup_reasoning_text","set_sha256_on_repo_creation_form","site_banner_desktop_copilot_app","site_ghca_pixel_mona","site_github_app_ga_page","site_github_app_ga_page_highlight","site_github_app_mobile_native_share","site_global_banner_dev_days_attendee","site_global_nav_spark_models_removed","speculation_rules_ui_service","suggest_custom_property_values_copilot","suppress_automated_browser_vitals","swp_forms_disable_octocaptcha","thread_resolution_reason","update_issue_suggestions","viewscreen_sandbox","warn_inaccessible_attachments","webp_support","workstream_plugin_bootstrap"],"githubDomain":"https://github.com","copilotApiOverrideUrl":"https://api.githubcopilot.com","cmcApiUrl":"https://api.github.com/cmc_internal/api"}</script>
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/high-contrast-cookie-3663bcaacc724f4c.js"></script>
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/wp-runtime-721126455a2225e3.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/app-foundation-156c6259e31cc551.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/app-runtime-f6ce38f47c2e1d56.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/fetch-utilities-804a14996977c014.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ser-fb5d13278eb42203.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/tp-ea613009a991e211.js" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/environment-107a7753389a8a2d.js" defer="defer"></script>
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/app-runtime.25915bc98fb322a6.module.css" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/catalyst-52ed81112a548e10.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/selector-observer-e8810f64c443fb9b.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/relative-time-element-fe7e72f699fdf493.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/by-9da36843ba9d191d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ja9-9a1160b939f2cd52.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/3h-55903cf2303047ad.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/hk-0d35a1220587d888.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/jz5-c964b1a3671f2a92.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/hj-1d800d09e9a8720a.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/j0-1ad12374da9777f2.js" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/github-elements-531d35538b61fd0d.js" defer="defer"></script>
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/element-registry-bf5e3b2aee197122.js" defer="defer"></script>
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/runtime-helpers-5e0b3ae3c4036207.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/aria-live-752acdc868e2da64.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/hotkey-e87ffadced20a3c9.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-core-75bc55d9971828b7.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/a9-34620c7b13885011.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ur-643d2814115561a9.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/4t6-5d0869970ac0aac0.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/pb0-a5c5ec36d311aeef.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/6n-afcf86ccabc4cad2.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/zj-f08c728cf00018aa.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/o6-2a1c68d269710ee9.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/h9a-c6405aa090de0e84.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/vy6-3c3e6e96629c2ce4.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/js-4eb97d5803e657c0.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/g7-baf9e817ae102b01.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/unc-96389cade7bad821.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/nu-65322a6997c6c93c.js" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/behaviors-5dd3809b5c8bac3c.js" defer="defer"></script>
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/react-core.3792087592bf207a.module.css" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/code-menu-4318f0f4c608f3dd.js" defer="defer"></script>
  
  <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/primer-react-7afef75d393cf294.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/op-acddf38478d45ef9.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ncx-461e4b09f2e67ee5.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/qmp-c7cfba40cf5a93b0.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/j6-148a74299e5a01ab.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/t7l-a52ba56147f77909.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/4u-ec4874f70bfd5d76.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/s0-481bd34f90bc2bd7.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/y5-ed17e73b4c0bd15e.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/n4-be595475cdbf7d4b.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/j8-23a3c5ff8eef412d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/la-27679061f16e1495.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/iu-9806a0291a782cbe.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ftx-39fb490e3c1d778f.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/zi-32a0c3c081ea5cce.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/sg-7a5d50475874c0e1.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/pd-6a8710840033e042.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/86g-ec28c25ed7d5de06.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/sn-1bab7384818953b7.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/iq-6d4ecf6d2fb6993c.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/p2-7d8e7cee5b46cb45.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/7d-2e3cc25d719f3a95.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/4d-76d8acdf21a33266.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/tsb-e0f6288ab985d9d2.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/lf-910ef220ce85c660.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/2j-2f19c1afd2ceaf93.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/me-f1476ec44a29f34a.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/p2u-e654a57b1a70c47d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/5d-d97cc006d4da4d9b.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/r5-38f013bc2c0fd298.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/r6s-9039f5619230a653.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/9e-cece06724bf6ea3b.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/x9-3e077a3a37a73f9f.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/pj-87629cb5389d7663.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/eq-3764fd5f66494289.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/13-d889936dff7e5c0f.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/44-8193eea4af825ead.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/v4-9c1327d212e52a0d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/id-7f39706ba9007db6.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ah-271362a1ed227d03.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/s1g-d434d712bca59d3a.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/vz-d7f4a583896f872e.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/47-c8c0ce2700acc5be.js" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/code-view-4118a687d0150749.js" defer="defer"></script>
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.2d881b4d4a503d1c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/pj.75627bfeb9ceb1da.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/ah.2623f7c6ecbe2ecd.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/47.78770c57a48a810c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/code-view.37656c95a9729fb9.module.css" />


  <title>moonlight-steam-sync-decky/DEVICE-CHECKLIST.md at main · episode6/moonlight-steam-sync-decky · GitHub</title>



  <meta name="route-pattern" content="/:user_id/:repository/blob/*name(/*path)" data-turbo-transient>
  <meta name="route-controller" content="blob" data-turbo-transient>
  <meta name="route-action" content="show" data-turbo-transient>
  <meta name="fetch-nonce" content="v2:a0f93e72-63bf-76e3-b3fd-64ca6cf16c05">

    
  <meta name="current-catalog-service-hash" content="f3abb0cc802f3d7b95fc8762b94bdcb13bf39634c40c357301c4aa1d67a256fb">


  <meta name="request-id" content="AC78:27A019:B82C671:EFD4FF9:6ACA6C9A" data-pjax-transient="true"/><meta name="html-safe-nonce" content="1b1e056994f04bd1049ade178b721274b2b83ae83e039e45ea5eb355c0e2cfff" data-pjax-transient="true"/><meta name="visitor-payload" content="eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJBQzc4OjI3QTAxOTpCODJDNjcxOkVGRDRGRjk6NkFDQTZDOUEiLCJ2aXNpdG9yX2lkIjoiMjk3ODU0MzYyOTU0MDk0NTA1MCIsInJlZ2lvbl9lZGdlIjoiZnJhIiwicmVnaW9uX3JlbmRlciI6ImZyYSJ9" data-pjax-transient="true"/><meta name="visitor-hmac" content="98d88256bbb090606f86564122b711c7e5773fedfa85fa66958c03cd7223fc59" data-pjax-transient="true"/>


    <meta name="hovercard-subject-tag" content="repository:1376544984" data-turbo-transient>


  <meta name="github-keyboard-shortcuts" content="repository,source-code,file-tree,copilot" data-turbo-transient="true" />
  

  <meta name="selected-link" value="repo_source" data-turbo-transient>
  <link rel="assets" href="https://github.githubassets.com/">

    <meta name="google-site-verification" content="Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I">

<meta name="octolytics-url" content="https://collector.github.com/github/collect" />





  <meta name="analytics-location" content="/&lt;user-name&gt;/&lt;repo-name&gt;/blob/show" data-turbo-transient="true" />

  




    <meta name="user-login" content="">

  

    <meta name="viewport" content="width=device-width">

    

      <meta name="description" content="Contribute to episode6/moonlight-steam-sync-decky development by creating an account on GitHub.">

      <link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub">

    <link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub">
    <meta property="fb:app_id" content="1401488693436528">
    <meta name="apple-itunes-app" content="app-id=1477376905, app-argument=https://github.com/episode6/moonlight-steam-sync-decky/blob/main/DEVICE-CHECKLIST.md" />

      <meta name="twitter:image" content="https://opengraph.githubassets.com/5278ad15588ae2bb5783c1fdb77eb363db8462d9a313ca035f767e3bc154008a/episode6/moonlight-steam-sync-decky" /><meta name="twitter:site" content="@github" /><meta name="twitter:card" content="summary_large_image" /><meta name="twitter:title" content="moonlight-steam-sync-decky/DEVICE-CHECKLIST.md at main · episode6/moonlight-steam-sync-decky" /><meta name="twitter:description" content="Contribute to episode6/moonlight-steam-sync-decky development by creating an account on GitHub." />
  <meta property="og:image" content="https://opengraph.githubassets.com/5278ad15588ae2bb5783c1fdb77eb363db8462d9a313ca035f767e3bc154008a/episode6/moonlight-steam-sync-decky" /><meta property="og:image:alt" content="Contribute to episode6/moonlight-steam-sync-decky development by creating an account on GitHub." /><meta property="og:image:width" content="1200" /><meta property="og:image:height" content="600" /><meta property="og:site_name" content="GitHub" /><meta property="og:type" content="object" /><meta property="og:title" content="moonlight-steam-sync-decky/DEVICE-CHECKLIST.md at main · episode6/moonlight-steam-sync-decky" /><meta property="og:url" content="https://github.com/episode6/moonlight-steam-sync-decky/blob/main/DEVICE-CHECKLIST.md" /><meta property="og:description" content="Contribute to episode6/moonlight-steam-sync-decky development by creating an account on GitHub." />
  




      <meta name="hostname" content="github.com">



        <meta name="expected-hostname" content="github.com">


  <meta http-equiv="x-pjax-version" content="d05be8f8b6905b6460fc264e80447721dfee1e32cf9e4291c4bf58f160c9f705" data-turbo-track="reload">
  <meta http-equiv="x-pjax-csp-version" content="c4a65e47b0c850e1157ae8e66298070851d7e3b558038de5a3b4ff7a93e630ed" data-turbo-track="reload">
  <meta http-equiv="x-pjax-css-version" content="a80b0545b169f440b929ce2b977c76b6988f014cca6513fb83c5df6581635fb3" data-turbo-track="reload">
  <meta http-equiv="x-pjax-js-version" content="af51c6f9d659be0324665373df3ee542f817dbd7f66f352a087935f2ceabe545" data-turbo-track="reload">

  <meta name="turbo-cache-control" content="no-preview" data-turbo-transient="">

      <meta name="turbo-cache-control" content="no-cache" data-turbo-transient>

    <meta data-hydrostats="publish">

  <meta name="go-import" content="github.com/episode6/moonlight-steam-sync-decky git https://github.com/episode6/moonlight-steam-sync-decky.git">

  <meta name="octolytics-dimension-user_id" content="6998849" /><meta name="octolytics-dimension-user_login" content="episode6" /><meta name="octolytics-dimension-repository_id" content="1376544984" /><meta name="octolytics-dimension-repository_nwo" content="episode6/moonlight-steam-sync-decky" /><meta name="octolytics-dimension-repository_public" content="true" /><meta name="octolytics-dimension-repository_is_fork" content="false" /><meta name="octolytics-dimension-repository_network_root_id" content="1376544984" /><meta name="octolytics-dimension-repository_network_root_nwo" content="episode6/moonlight-steam-sync-decky" />
  



    

    <meta name="turbo-body-classes" content="logged-out env-production page-responsive">
  <meta name="disable-turbo" content="false">


  <meta name="browser-stats-url" content="https://api.github.com/_private/browser/stats">


  <meta name="browser-errors-url" content="https://api.github.com/_private/browser/errors">

  <meta name="release" content="3b1bb5b10f5c7fd1769d56e38b15dfddb34052a8" data-turbo-track="reload">
  <meta name="ui-target" content="canary-2">

  <link rel="mask-icon" href="https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg" color="#000000">
  <link rel="alternate icon" class="js-site-favicon" type="image/png" href="https://github.githubassets.com/favicons/favicon.png">
  <link rel="icon" class="js-site-favicon" type="image/svg+xml" href="https://github.githubassets.com/favicons/favicon.svg" data-base-href="https://github.githubassets.com/favicons/favicon">

<meta name="theme-color" content="#1e2327">
<meta name="color-scheme" content="light dark" />


  <link rel="manifest" href="/manifest.json" crossOrigin="use-credentials">

  </head>

  <body class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
    <div data-turbo-body class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
      <div id="__primerPortalRoot__" style="z-index: 1000; position: absolute; width: 100%;" data-turbo-permanent></div>
      

    <div class="position-relative header-wrapper js-header-wrapper ">
      <a href="#start-of-content" data-skip-target-assigned="false" class="px-2 tmp-py-4 color-bg-accent-emphasis color-fg-on-emphasis show-on-focus js-skip-to-content">Skip to content</a>

      <span data-view-component="true" class="progress-pjax-loader Progress position-fixed width-full">
    <span style="width: 0%;" data-view-component="true" class="Progress-item progress-pjax-loader-bar left-0 top-0 color-bg-accent-emphasis"></span>
</span>      
      <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/19-c660652a0a1639be.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/keyboard-shortcuts-dialog-47c5223d8d1af8ef.js" fetchpriority="low" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.2d881b4d4a503d1c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/keyboard-shortcuts-dialog.d53d993320d57e14.module.css" />

<react-partial
  partial-name="keyboard-shortcuts-dialog"
  data-ssr="false"
  data-attempted-ssr="false"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-partial.embeddedData">{"props":{"docsUrl":"https://docs.github.com/get-started/accessibility/keyboard-shortcuts"}}</script>
  <div data-target="react-partial.reactRoot"></div>
</react-partial>





      

          <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/app-install-banner-partial-3c87deb7f0f20434.js" fetchpriority="low" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.2d881b4d4a503d1c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/app-install-banner-partial.7b357012e82003e7.module.css" />

<react-partial
  partial-name="app-install-banner-partial"
  data-ssr="false"
  data-attempted-ssr="false"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-partial.embeddedData">{"props":{}}</script>
  <div data-target="react-partial.reactRoot"></div>
</react-partial>


          

                <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/et9-7eeec247c287fca4.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/94v-e20946127faa8f6e.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/fz5-892a9e99cc92858d.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/jag-441e702bfd4c7904.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/hw-8ce7888b57d0a23c.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/rq-0ec82a12cf538a4d.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/1m-a7a4c022483b2713.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/marketing-header-70703fe17422eb58.js" fetchpriority="low" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.2d881b4d4a503d1c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-brand-css.fbc35b3d20e988ff.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/1m.fec8b575d0e5234f.module.css" />

<react-partial
  partial-name="marketing-header"
  data-ssr="true"
  data-attempted-ssr="true"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-partial.embeddedData">{"props":{"color_mode":"dark","logged_in":false,"marketing_page":false,"home_path":"/","login_path":"/login?return_to=https%3A%2F%2Fgithub.com%2Fepisode6%2Fmoonlight-steam-sync-decky%2Fblob%2Fmain%2FDEVICE-CHECKLIST.md","signup_path":"/signup?ref_cta=Sign+up\u0026ref_loc=header+logged+out\u0026ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fblob%2Fshow\u0026source=header-repo\u0026source_repo=episode6%2Fmoonlight-steam-sync-decky","signup_enabled":true,"is_signup_controller":false,"show_search_and_nav":true,"hide_search":false,"private_mode_enabled":false,"should_use_dotcom_links":true,"auth_hydro_click":"{\"event_type\":\"authentication.click\",\"payload\":{\"location_in_page\":\"site header menu\",\"repository_id\":null,\"auth_type\":\"SIGN_UP\",\"originating_url\":\"https://github.com/episode6/moonlight-steam-sync-decky/blob/main/DEVICE-CHECKLIST.md\",\"user_id\":null}}","auth_hydro_click_hmac":"09f77c38ebccd5bdc694628b8958000ba771de629e8241b51ca4581b653b8fca","overlay":false,"fixed":false}}</script>
  <div data-target="react-partial.reactRoot"><div data-color-mode="dark" data-light-theme="light" data-dark-theme="dark"><header class="MarketingHeader-module__root__Tk7n3 HeaderMktg header-logged-out" role="banner" data-marketing-header="true" data-color-mode="dark" data-light-theme="light" data-dark-theme="dark" data-is-top="true"><h2 class="MarketingHeader-module__visuallyHidden__sqKsl">Navigation Menu</h2><button type="button" class="MarketingHeader-module__backdrop__sw4RU" aria-label="Close navigation menu"></button><div class="MarketingHeader-module__bar__mBSyE"><div class="MarketingHeader-module__topRow__yeury"><div class="MarketingHeader-module__toggleSlot__hDxbh"><button type="button" class="HeaderMenuToggle-module__toggle__i8EiC" aria-label="Toggle navigation" aria-expanded="false"><span class="HeaderMenuToggle-module__toggleBar__jVN0H"></span><span class="HeaderMenuToggle-module__toggleBar__jVN0H"></span><span class="HeaderMenuToggle-module__toggleBar__jVN0H"></span></button></div><a href="/" aria-label="Homepage" class="HeaderLogo-module__logo__UFyHI" data-analytics-event="{&quot;action&quot;:&quot;homepage&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;logo&quot;,&quot;location&quot;:&quot;header&quot;,&quot;label&quot;:&quot;homepage_link_logo_header&quot;}"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-mark-github" viewBox="0 0 24 24" width="32" height="32" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.226 17.284c-2.965-.36-5.054-2.493-5.054-5.256 0-1.123.404-2.336 1.078-3.144-.292-.741-.247-2.314.09-2.965.898-.112 2.111.36 2.83 1.01.853-.269 1.752-.404 2.853-.404 1.1 0 1.999.135 2.807.382.696-.629 1.932-1.1 2.83-.988.315.606.36 2.179.067 2.942.72.854 1.101 2 1.101 3.167 0 2.763-2.089 4.852-5.098 5.234.763.494 1.28 1.572 1.28 2.807v2.336c0 .674.561 1.056 1.235.786 4.066-1.55 7.255-5.615 7.255-10.646C23.5 6.188 18.334 1 11.978 1 5.62 1 .5 6.188.5 12.545c0 4.986 3.167 9.12 7.435 10.669.606.225 1.19-.18 1.19-.786V20.63a2.9 2.9 0 0 1-1.078.224c-1.483 0-2.359-.808-2.987-2.313-.247-.607-.517-.966-1.034-1.033-.27-.023-.359-.135-.359-.27 0-.27.45-.471.898-.471.652 0 1.213.404 1.797 1.235.45.651.921.943 1.483.943.561 0 .92-.202 1.437-.719.382-.381.674-.718.944-.943"></path></svg></a><div class="AuthCTAs-module__mobileActions__NNzeV"><a class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw AuthCTAs-module__cta__WpwQq" href="/login?return_to=https%3A%2F%2Fgithub.com%2Fepisode6%2Fmoonlight-steam-sync-decky%2Fblob%2Fmain%2FDEVICE-CHECKLIST.md" data-analytics-event="{&quot;action&quot;:&quot;sign_in&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;auth_cta&quot;,&quot;location&quot;:&quot;header&quot;,&quot;label&quot;:&quot;sign_in_link_auth_cta_header&quot;}" data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;site header menu&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;SIGN_UP&quot;,&quot;originating_url&quot;:&quot;https://github.com/episode6/moonlight-steam-sync-decky/blob/main/DEVICE-CHECKLIST.md&quot;,&quot;user_id&quot;:null}}" data-hydro-click-hmac="09f77c38ebccd5bdc694628b8958000ba771de629e8241b51ca4581b653b8fca"><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH">Sign in</span></span></a><button class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw HeaderAppearanceSettings-module__trigger__hUheK" type="button" aria-haspopup="dialog" aria-labelledby="_R_3dd_"><span class="Primer_Brand__Button-module__Button__leading-visual___jjtTe" data-testid="Button-leading-visual"><svg data-component="Octicon" focusable="false" aria-hidden="true" class="octicon octicon-sliders Primer_Brand__Button-module__Button__icon-visual____qybb" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M15 2.75a.75.75 0 0 1-.75.75h-4a.75.75 0 0 1 0-1.5h4a.75.75 0 0 1 .75.75Zm-8.5.75v1.25a.75.75 0 0 0 1.5 0v-4a.75.75 0 0 0-1.5 0V2H1.75a.75.75 0 0 0 0 1.5H6.5Zm1.25 5.25a.75.75 0 0 0 0-1.5h-6a.75.75 0 0 0 0 1.5h6ZM15 8a.75.75 0 0 1-.75.75H11.5V10a.75.75 0 1 1-1.5 0V6a.75.75 0 0 1 1.5 0v1.25h2.75A.75.75 0 0 1 15 8Zm-9 5.25v-2a.75.75 0 0 0-1.5 0v1.25H1.75a.75.75 0 0 0 0 1.5H4.5v1.25a.75.75 0 0 0 1.5 0v-2Zm9 0a.75.75 0 0 1-.75.75h-6a.75.75 0 0 1 0-1.5h6a.75.75 0 0 1 .75.75Z"></path></svg></span><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH"></span></span></button><div class="Primer_Brand__Tooltip-module__Tooltip___0Eipx" data-direction="s" aria-hidden="true" id="_R_3dd_">Appearance settings</div></div></div><div class="MarketingHeader-module__menu__GIy3y"><div class="MarketingHeader-module__menuWrapper__owstH"><nav class="MarketingNavigation-module__nav__W0KYY" aria-label="Global"><ul class="MarketingNavigation-module__list__tFbMb"><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_nd_">Platform<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_nd_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5knd_">AI CODE CREATION</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5knd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/copilot" data-analytics-event="{&quot;action&quot;:&quot;github_copilot&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_copilot_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copilot NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.998 15.035c-4.562 0-7.873-2.914-7.998-3.749V9.338c.085-.628.677-1.686 1.588-2.065.013-.07.024-.143.036-.218.029-.183.06-.384.126-.612-.201-.508-.254-1.084-.254-1.656 0-.87.128-1.769.693-2.484.579-.733 1.494-1.124 2.724-1.261 1.206-.134 2.262.034 2.944.765.05.053.096.108.139.165.044-.057.094-.112.143-.165.682-.731 1.738-.899 2.944-.765 1.23.137 2.145.528 2.724 1.261.566.715.693 1.614.693 2.484 0 .572-.053 1.148-.254 1.656.066.228.098.429.126.612.012.076.024.148.037.218.924.385 1.522 1.471 1.591 2.095v1.872c0 .766-3.351 3.795-8.002 3.795Zm0-1.485c2.28 0 4.584-1.11 5.002-1.433V7.862l-.023-.116c-.49.21-1.075.291-1.727.291-1.146 0-2.059-.327-2.71-.991A3.222 3.222 0 0 1 8 6.303a3.24 3.24 0 0 1-.544.743c-.65.664-1.563.991-2.71.991-.652 0-1.236-.081-1.727-.291l-.023.116v4.255c.419.323 2.722 1.433 5.002 1.433ZM6.762 2.83c-.193-.206-.637-.413-1.682-.297-1.019.113-1.479.404-1.713.7-.247.312-.369.789-.369 1.554 0 .793.129 1.171.308 1.371.162.181.519.379 1.442.379.853 0 1.339-.235 1.638-.54.315-.322.527-.827.617-1.553.117-.935-.037-1.395-.241-1.614Zm4.155-.297c-1.044-.116-1.488.091-1.681.297-.204.219-.359.679-.242 1.614.091.726.303 1.231.618 1.553.299.305.784.54 1.638.54.922 0 1.28-.198 1.442-.379.179-.2.308-.578.308-1.371 0-.765-.123-1.242-.37-1.554-.233-.296-.693-.587-1.713-.7Z"></path><path d="M6.25 9.037a.75.75 0 0 1 .75.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 .75-.75Zm4.25.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 1.5 0Z"></path></svg>GitHub Copilot</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Write better code with AI</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/ai/github-app" data-analytics-event="{&quot;action&quot;:&quot;github_copilot_app&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_copilot_app_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-mark-github NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M6.766 11.328c-2.063-.25-3.516-1.734-3.516-3.656 0-.781.281-1.625.75-2.188-.203-.515-.172-1.609.063-2.062.625-.078 1.468.25 1.968.703.594-.187 1.219-.281 1.985-.281.765 0 1.39.094 1.953.265.484-.437 1.344-.765 1.969-.687.218.422.25 1.515.046 2.047.5.593.766 1.39.766 2.203 0 1.922-1.453 3.375-3.547 3.64.531.344.89 1.094.89 1.954v1.625c0 .468.391.734.86.547C13.781 14.359 16 11.53 16 8.03 16 3.61 12.406 0 7.984 0 3.563 0 0 3.61 0 8.031a7.88 7.88 0 0 0 5.172 7.422c.422.156.828-.125.828-.547v-1.25c-.219.094-.5.156-.75.156-1.031 0-1.64-.562-2.078-1.609-.172-.422-.36-.672-.719-.719-.187-.015-.25-.093-.25-.187 0-.188.313-.328.625-.328.453 0 .844.281 1.25.86.313.452.64.655 1.031.655s.641-.14 1-.5c.266-.265.47-.5.657-.656"></path></svg>GitHub Copilot app</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Direct agents from issue to merge</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/mcp" data-analytics-event="{&quot;action&quot;:&quot;mcp_registry&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;mcp_registry_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-mcp NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M5.52 1.12a3.578 3.578 0 0 1 6.078 2.98 3.578 3.578 0 0 1 2.982 6.08l-3.292 3.293a.252.252 0 0 0 0 .354l.843.843a.749.749 0 1 1-1.06 1.06l-.844-.843a1.75 1.75 0 0 1 0-2.474L13.52 9.12a2.08 2.08 0 0 0 0-2.94 2.08 2.08 0 0 0-2.94 0L7.731 9.03A.75.75 0 0 1 6.67 7.97l2.85-2.85a2.08 2.08 0 0 0 0-2.94 2.08 2.08 0 0 0-2.94 0l-4.799 4.8A.75.75 0 0 1 .72 5.92Z"></path><path d="M7.52 3.12a.749.749 0 1 1 1.06 1.06L5.731 7.03A2.079 2.079 0 0 0 8.67 9.97l2.85-2.85a.749.749 0 1 1 1.06 1.06l-2.849 2.85A3.578 3.578 0 0 1 4.67 5.97Z"></path></svg>MCP Registry</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Integrate external tools</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9knd_">DEVELOPER WORKFLOWS</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9knd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/actions" data-analytics-event="{&quot;action&quot;:&quot;actions&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;actions_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-workflow NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 1.75C0 .784.784 0 1.75 0h3.5C6.216 0 7 .784 7 1.75v3.5A1.75 1.75 0 0 1 5.25 7H4v4a1 1 0 0 0 1 1h4v-1.25C9 9.784 9.784 9 10.75 9h3.5c.966 0 1.75.784 1.75 1.75v3.5A1.75 1.75 0 0 1 14.25 16h-3.5A1.75 1.75 0 0 1 9 14.25v-.75H5A2.5 2.5 0 0 1 2.5 11V7h-.75A1.75 1.75 0 0 1 0 5.25Zm1.75-.25a.25.25 0 0 0-.25.25v3.5c0 .138.112.25.25.25h3.5a.25.25 0 0 0 .25-.25v-3.5a.25.25 0 0 0-.25-.25Zm9 9a.25.25 0 0 0-.25.25v3.5c0 .138.112.25.25.25h3.5a.25.25 0 0 0 .25-.25v-3.5a.25.25 0 0 0-.25-.25Z"></path></svg>Actions</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Automate any workflow</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/codespaces" data-analytics-event="{&quot;action&quot;:&quot;codespaces&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;codespaces_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-codespaces NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 11.25c0-.966.784-1.75 1.75-1.75h12.5c.966 0 1.75.784 1.75 1.75v3A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25Zm2-9.5C2 .784 2.784 0 3.75 0h8.5C13.216 0 14 .784 14 1.75v5a1.75 1.75 0 0 1-1.75 1.75h-8.5A1.75 1.75 0 0 1 2 6.75Zm1.75-.25a.25.25 0 0 0-.25.25v5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5a.25.25 0 0 0-.25-.25Zm-2 9.5a.25.25 0 0 0-.25.25v3c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25v-3a.25.25 0 0 0-.25-.25Z"></path><path d="M7 12.75a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Zm-4 0a.75.75 0 0 1 .75-.75h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1-.75-.75Z"></path></svg>Codespaces</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Instant dev environments</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/issues" data-analytics-event="{&quot;action&quot;:&quot;issues&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;issues_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-issue-opened NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z"></path></svg>Issues</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Plan and track work</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/code-review" data-analytics-event="{&quot;action&quot;:&quot;code_review&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;code_review_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-code NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m11.28 3.22 4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L13.94 8l-3.72-3.72a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215Zm-6.56 0a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042L2.06 8l3.72 3.72a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L.47 8.53a.75.75 0 0 1 0-1.06Z"></path></svg>Code Review</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Manage code changes</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/code-quality" data-analytics-event="{&quot;action&quot;:&quot;code_quality&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;code_quality_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-codescan-checkmark NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.28 6.28a.75.75 0 1 0-1.06-1.06L6.25 8.19l-.97-.97a.75.75 0 0 0-1.06 1.06l1.5 1.5a.75.75 0 0 0 1.06 0l3.5-3.5Z"></path><path d="M7.5 15a7.5 7.5 0 1 1 5.807-2.754l2.473 2.474a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215l-2.474-2.473A7.472 7.472 0 0 1 7.5 15Zm0-13.5a6 6 0 1 0 4.094 10.386.748.748 0 0 1 .293-.292 6.002 6.002 0 0 0 1.117-6.486A6.002 6.002 0 0 0 7.5 1.5Z"></path></svg>Code Quality</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Enforce quality at merge</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_dknd_">APPLICATION SECURITY</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_dknd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/security/advanced-security" data-analytics-event="{&quot;action&quot;:&quot;github_advanced_security&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_advanced_security_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-shield-check NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m8.533.133 5.25 1.68A1.75 1.75 0 0 1 15 3.48V7c0 1.566-.32 3.182-1.303 4.682-.983 1.498-2.585 2.813-5.032 3.855a1.697 1.697 0 0 1-1.33 0c-2.447-1.042-4.049-2.357-5.032-3.855C1.32 10.182 1 8.566 1 7V3.48a1.75 1.75 0 0 1 1.217-1.667l5.25-1.68a1.748 1.748 0 0 1 1.066 0Zm-.61 1.429.001.001-5.25 1.68a.251.251 0 0 0-.174.237V7c0 1.36.275 2.666 1.057 3.859.784 1.194 2.121 2.342 4.366 3.298a.196.196 0 0 0 .154 0c2.245-.957 3.582-2.103 4.366-3.297C13.225 9.666 13.5 8.358 13.5 7V3.48a.25.25 0 0 0-.174-.238l-5.25-1.68a.25.25 0 0 0-.153 0ZM11.28 6.28l-3.5 3.5a.75.75 0 0 1-1.06 0l-1.5-1.5a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l.97.97 2.97-2.97a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"></path></svg>GitHub Advanced Security</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Find and fix vulnerabilities</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/security/advanced-security/code-security" data-analytics-event="{&quot;action&quot;:&quot;code_security&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;code_security_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-code-square NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25Zm7.47 3.97a.75.75 0 0 1 1.06 0l2 2a.75.75 0 0 1 0 1.06l-2 2a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L10.69 8 9.22 6.53a.75.75 0 0 1 0-1.06ZM6.78 6.53 5.31 8l1.47 1.47a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215l-2-2a.75.75 0 0 1 0-1.06l2-2a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"></path></svg>Code security</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Secure your code as you build</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/security/advanced-security/secret-protection" data-analytics-event="{&quot;action&quot;:&quot;secret_protection&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;secret_protection_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-lock NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M4 4a4 4 0 0 1 8 0v2h.25c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 12.25 15h-8.5A1.75 1.75 0 0 1 2 13.25v-5.5C2 6.784 2.784 6 3.75 6H4Zm8.25 3.5h-8.5a.25.25 0 0 0-.25.25v5.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25ZM10.5 6V4a2.5 2.5 0 1 0-5 0v2Z"></path></svg>Secret protection</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Stop leaks before they start</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ NavGroup-module__hasSeparator__FnMrN"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_hknd_">EXPLORE</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_hknd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/why-github" data-analytics-event="{&quot;action&quot;:&quot;why_github&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;why_github_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Why GitHub</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://docs.github.com" data-analytics-event="{&quot;action&quot;:&quot;documentation&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;documentation_link_platform_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Documentation</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://github.blog" data-analytics-event="{&quot;action&quot;:&quot;blog&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;blog_link_platform_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Blog</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://github.blog/changelog" data-analytics-event="{&quot;action&quot;:&quot;changelog&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;changelog_link_platform_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Changelog</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/marketplace" data-analytics-event="{&quot;action&quot;:&quot;marketplace&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;marketplace_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Marketplace</span></a></li></ul></div></li></ul><div class="NavDropdown-module__trailingLinkContainer__VgJGL"><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/features" data-analytics-event="{&quot;action&quot;:&quot;view_all_features&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_features_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all features</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></div></div></div></li><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_17d_">Solutions<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_17d_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5l7d_">BY COMPANY SIZE</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5l7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/enterprise" data-analytics-event="{&quot;action&quot;:&quot;enterprises&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;enterprises_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Enterprises</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/team" data-analytics-event="{&quot;action&quot;:&quot;small_and_medium_teams&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;small_and_medium_teams_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Small and medium teams</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/enterprise/startups" data-analytics-event="{&quot;action&quot;:&quot;startups&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;startups_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Startups</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/nonprofits" data-analytics-event="{&quot;action&quot;:&quot;nonprofits&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;nonprofits_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Nonprofits</span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9l7d_">BY USE CASE</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9l7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/use-case/app-modernization" data-analytics-event="{&quot;action&quot;:&quot;app_modernization&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;app_modernization_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">App Modernization</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/use-case/devsecops" data-analytics-event="{&quot;action&quot;:&quot;devsecops&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;devsecops_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">DevSecOps</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/use-case/devops" data-analytics-event="{&quot;action&quot;:&quot;devops&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;devops_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">DevOps</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/use-case/ci-cd" data-analytics-event="{&quot;action&quot;:&quot;ci/cd&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;ci/cd_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">CI/CD</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/solutions/use-case" data-analytics-event="{&quot;action&quot;:&quot;view_all_use_cases&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_use_cases_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all use cases</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_dl7d_">BY INDUSTRY</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_dl7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/healthcare" data-analytics-event="{&quot;action&quot;:&quot;healthcare&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;healthcare_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Healthcare</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/financial-services" data-analytics-event="{&quot;action&quot;:&quot;financial_services&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;financial_services_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Financial services</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/manufacturing" data-analytics-event="{&quot;action&quot;:&quot;manufacturing&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;manufacturing_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Manufacturing</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/government" data-analytics-event="{&quot;action&quot;:&quot;government&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;government_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Government</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/solutions/industry" data-analytics-event="{&quot;action&quot;:&quot;view_all_industries&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_industries_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all industries</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></li></ul></div></li></ul><div class="NavDropdown-module__trailingLinkContainer__VgJGL"><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/solutions" data-analytics-event="{&quot;action&quot;:&quot;view_all_solutions&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_solutions_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all solutions</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></div></div></div></li><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_1nd_">Resources<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_1nd_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5lnd_">EXPLORE BY TOPIC</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5lnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/articles?topic=ai" data-analytics-event="{&quot;action&quot;:&quot;ai&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;ai_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">AI</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/articles?topic=software-development" data-analytics-event="{&quot;action&quot;:&quot;software_development&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;software_development_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Software Development</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/articles?topic=devops" data-analytics-event="{&quot;action&quot;:&quot;devops&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;devops_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">DevOps</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/articles?topic=security" data-analytics-event="{&quot;action&quot;:&quot;security&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;security_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Security</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/resources/articles" data-analytics-event="{&quot;action&quot;:&quot;view_all_topics&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_topics_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all topics</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9lnd_">EXPLORE BY TYPE</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9lnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/customer-stories" data-analytics-event="{&quot;action&quot;:&quot;customer_stories&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;customer_stories_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Customer stories</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/events" data-analytics-event="{&quot;action&quot;:&quot;events__webinars&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;events__webinars_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Events &amp; webinars</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/whitepapers" data-analytics-event="{&quot;action&quot;:&quot;ebooks__reports&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;ebooks__reports_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Ebooks &amp; reports</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/executive-insights" data-analytics-event="{&quot;action&quot;:&quot;business_insights&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;business_insights_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Business insights</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://skills.github.com" data-analytics-event="{&quot;action&quot;:&quot;github_skills&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_skills_link_resources_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">GitHub Skills</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_dlnd_">SUPPORT &amp; SERVICES</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_dlnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://docs.github.com" data-analytics-event="{&quot;action&quot;:&quot;documentation&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;documentation_link_resources_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Documentation</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://support.github.com" data-analytics-event="{&quot;action&quot;:&quot;customer_support&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;customer_support_link_resources_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Customer support</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/orgs/community/discussions" data-analytics-event="{&quot;action&quot;:&quot;community_forum&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;community_forum_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Community forum</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/trust-center" data-analytics-event="{&quot;action&quot;:&quot;trust_center&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;trust_center_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Trust center</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/partners" data-analytics-event="{&quot;action&quot;:&quot;partners&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;partners_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Partners</span></a></li></ul></div></li></ul><div class="NavDropdown-module__trailingLinkContainer__VgJGL"><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/resources" data-analytics-event="{&quot;action&quot;:&quot;view_all_resources&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_resources_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all resources</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></div></div></div></li><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_27d_">Open Source<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_27d_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5m7d_">COMMUNITY</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5m7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/open-source/sponsors" data-analytics-event="{&quot;action&quot;:&quot;github_sponsors&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_sponsors_link_open_source_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-sponsor-tiers NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.586 1C12.268 1 13.5 2.37 13.5 4.25c0 1.745-.996 3.359-2.622 4.831-.166.15-.336.297-.509.438l1.116 5.584a.75.75 0 0 1-.991.852l-2.409-.876a.25.25 0 0 0-.17 0l-2.409.876a.75.75 0 0 1-.991-.852L5.63 9.519a13.78 13.78 0 0 1-.51-.438C3.497 7.609 2.5 5.995 2.5 4.25 2.5 2.37 3.732 1 5.414 1c.963 0 1.843.403 2.474 1.073L8 2.198l.112-.125a3.385 3.385 0 0 1 2.283-1.068L10.586 1Zm-3.621 9.495-.718 3.594 1.155-.42a1.75 1.75 0 0 1 1.028-.051l.168.051 1.154.42-.718-3.592c-.199.13-.37.235-.505.314l-.169.097a.75.75 0 0 1-.72 0 9.54 9.54 0 0 1-.515-.308l-.16-.105ZM10.586 2.5c-.863 0-1.611.58-1.866 1.459-.209.721-1.231.721-1.44 0C7.025 3.08 6.277 2.5 5.414 2.5 4.598 2.5 4 3.165 4 4.25c0 1.23.786 2.504 2.128 3.719.49.443 1.018.846 1.546 1.198l.325.21.076-.047.251-.163a13.341 13.341 0 0 0 1.546-1.198C11.214 6.754 12 5.479 12 4.25c0-1.085-.598-1.75-1.414-1.75Z"></path></svg>GitHub Sponsors</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Fund open source developers</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9m7d_">PROGRAMS</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9m7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://securitylab.github.com" data-analytics-event="{&quot;action&quot;:&quot;security_lab&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;security_lab_link_open_source_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Security Lab</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://maintainers.github.com" data-analytics-event="{&quot;action&quot;:&quot;maintainer_community&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;maintainer_community_link_open_source_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Maintainer Community</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://stars.github.com" data-analytics-event="{&quot;action&quot;:&quot;github_stars&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_stars_link_open_source_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">GitHub Stars</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://archiveprogram.github.com" data-analytics-event="{&quot;action&quot;:&quot;archive_program&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;archive_program_link_open_source_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Archive Program</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_dm7d_">REPOSITORIES</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_dm7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/topics" data-analytics-event="{&quot;action&quot;:&quot;topics&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;topics_link_open_source_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Topics</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/trending" data-analytics-event="{&quot;action&quot;:&quot;trending&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;trending_link_open_source_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Trending</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/collections" data-analytics-event="{&quot;action&quot;:&quot;collections&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;collections_link_open_source_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Collections</span></a></li></ul></div></li></ul></div></div></li><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_2nd_">Enterprise<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_2nd_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5mnd_">ENTERPRISE SOLUTIONS</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5mnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/enterprise" data-analytics-event="{&quot;action&quot;:&quot;enterprise_platform&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;enterprise&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;enterprise_platform_link_enterprise_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-stack NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.122.392a1.75 1.75 0 0 1 1.756 0l5.003 2.902c.83.481.83 1.68 0 2.162L8.878 8.358a1.75 1.75 0 0 1-1.756 0L2.119 5.456a1.251 1.251 0 0 1 0-2.162ZM8.125 1.69a.248.248 0 0 0-.25 0l-4.63 2.685 4.63 2.685a.248.248 0 0 0 .25 0l4.63-2.685ZM1.601 7.789a.75.75 0 0 1 1.025-.273l5.249 3.044a.248.248 0 0 0 .25 0l5.249-3.044a.75.75 0 0 1 .752 1.298l-5.248 3.044a1.75 1.75 0 0 1-1.756 0L1.874 8.814A.75.75 0 0 1 1.6 7.789Zm0 3.5a.75.75 0 0 1 1.025-.273l5.249 3.044a.248.248 0 0 0 .25 0l5.249-3.044a.75.75 0 0 1 .752 1.298l-5.248 3.044a1.75 1.75 0 0 1-1.756 0l-5.248-3.044a.75.75 0 0 1-.273-1.025Z"></path></svg>Enterprise platform</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">AI-powered developer platform</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9mnd_">AVAILABLE ADD-ONS</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9mnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/security/advanced-security" data-analytics-event="{&quot;action&quot;:&quot;github_advanced_security&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;enterprise&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_advanced_security_link_enterprise_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-shield-check NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m8.533.133 5.25 1.68A1.75 1.75 0 0 1 15 3.48V7c0 1.566-.32 3.182-1.303 4.682-.983 1.498-2.585 2.813-5.032 3.855a1.697 1.697 0 0 1-1.33 0c-2.447-1.042-4.049-2.357-5.032-3.855C1.32 10.182 1 8.566 1 7V3.48a1.75 1.75 0 0 1 1.217-1.667l5.25-1.68a1.748 1.748 0 0 1 1.066 0Zm-.61 1.429.001.001-5.25 1.68a.251.251 0 0 0-.174.237V7c0 1.36.275 2.666 1.057 3.859.784 1.194 2.121 2.342 4.366 3.298a.196.196 0 0 0 .154 0c2.245-.957 3.582-2.103 4.366-3.297C13.225 9.666 13.5 8.358 13.5 7V3.48a.25.25 0 0 0-.174-.238l-5.25-1.68a.25.25 0 0 0-.153 0ZM11.28 6.28l-3.5 3.5a.75.75 0 0 1-1.06 0l-1.5-1.5a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l.97.97 2.97-2.97a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"></path></svg>GitHub Advanced Security</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Enterprise-grade security features</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/copilot/copilot-business" data-analytics-event="{&quot;action&quot;:&quot;copilot_for_business&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;enterprise&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;copilot_for_business_link_enterprise_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copilot NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.998 15.035c-4.562 0-7.873-2.914-7.998-3.749V9.338c.085-.628.677-1.686 1.588-2.065.013-.07.024-.143.036-.218.029-.183.06-.384.126-.612-.201-.508-.254-1.084-.254-1.656 0-.87.128-1.769.693-2.484.579-.733 1.494-1.124 2.724-1.261 1.206-.134 2.262.034 2.944.765.05.053.096.108.139.165.044-.057.094-.112.143-.165.682-.731 1.738-.899 2.944-.765 1.23.137 2.145.528 2.724 1.261.566.715.693 1.614.693 2.484 0 .572-.053 1.148-.254 1.656.066.228.098.429.126.612.012.076.024.148.037.218.924.385 1.522 1.471 1.591 2.095v1.872c0 .766-3.351 3.795-8.002 3.795Zm0-1.485c2.28 0 4.584-1.11 5.002-1.433V7.862l-.023-.116c-.49.21-1.075.291-1.727.291-1.146 0-2.059-.327-2.71-.991A3.222 3.222 0 0 1 8 6.303a3.24 3.24 0 0 1-.544.743c-.65.664-1.563.991-2.71.991-.652 0-1.236-.081-1.727-.291l-.023.116v4.255c.419.323 2.722 1.433 5.002 1.433ZM6.762 2.83c-.193-.206-.637-.413-1.682-.297-1.019.113-1.479.404-1.713.7-.247.312-.369.789-.369 1.554 0 .793.129 1.171.308 1.371.162.181.519.379 1.442.379.853 0 1.339-.235 1.638-.54.315-.322.527-.827.617-1.553.117-.935-.037-1.395-.241-1.614Zm4.155-.297c-1.044-.116-1.488.091-1.681.297-.204.219-.359.679-.242 1.614.091.726.303 1.231.618 1.553.299.305.784.54 1.638.54.922 0 1.28-.198 1.442-.379.179-.2.308-.578.308-1.371 0-.765-.123-1.242-.37-1.554-.233-.296-.693-.587-1.713-.7Z"></path><path d="M6.25 9.037a.75.75 0 0 1 .75.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 .75-.75Zm4.25.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 1.5 0Z"></path></svg>Copilot for Business</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Enterprise-grade AI features</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/enterprise/premium-support" data-analytics-event="{&quot;action&quot;:&quot;premium_support&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;enterprise&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;premium_support_link_enterprise_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-comment-discussion NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M1.75 1h8.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 10.25 10H7.061l-2.574 2.573A1.458 1.458 0 0 1 2 11.543V10h-.25A1.75 1.75 0 0 1 0 8.25v-5.5C0 1.784.784 1 1.75 1ZM1.5 2.75v5.5c0 .138.112.25.25.25h1a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h3.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25Zm13 2a.25.25 0 0 0-.25-.25h-.5a.75.75 0 0 1 0-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 14.25 12H14v1.543a1.458 1.458 0 0 1-2.487 1.03L9.22 12.28a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l2.22 2.22v-2.19a.75.75 0 0 1 .75-.75h1a.25.25 0 0 0 .25-.25Z"></path></svg>Premium Support</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Enterprise-grade 24/7 support</span></span></a></li></ul></div></li></ul></div></div></li><li><a href="https://github.com/pricing" data-analytics-event="{&quot;action&quot;:&quot;pricing&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;pricing&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;pricing_link_pricing_navbar&quot;}" class="MarketingNavigation-module__navLink__hUomM">Pricing</a></li></ul></nav><div class="MarketingHeader-module__ctaContainer__tBmPz"><div class="HeaderSearch-module__searchSlot__oVOUS"><button class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw HeaderSearch-module__trigger__zsF9q" type="button" aria-haspopup="dialog" aria-expanded="false" aria-label="Search or jump to, type / to search" data-analytics-event="{&quot;action&quot;:&quot;searchbar&quot;,&quot;tag&quot;:&quot;input&quot;,&quot;context&quot;:&quot;global&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;searchbar_input_global_navbar&quot;}"><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH"><span class="HeaderSearch-module__content__kMpxU"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-search HeaderSearch-module__icon__wcrHX" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z"></path></svg><span class="HeaderSearch-module__label__d1iWG">Search</span><kbd class="HeaderSearch-module__kbd__HNG0o" aria-hidden="true">/</kbd></span></span></span></button><div class="d-none"></div></div><div class="AuthCTAs-module__signInWrap__q2P60"><a class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw AuthCTAs-module__cta__WpwQq AuthCTAs-module__desktopActionGap__UZuXT AuthCTAs-module__hiddenBelowLg__BfKBw" href="/login?return_to=https%3A%2F%2Fgithub.com%2Fepisode6%2Fmoonlight-steam-sync-decky%2Fblob%2Fmain%2FDEVICE-CHECKLIST.md" data-analytics-event="{&quot;action&quot;:&quot;sign_in&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;auth_cta&quot;,&quot;location&quot;:&quot;header&quot;,&quot;label&quot;:&quot;sign_in_link_auth_cta_header&quot;}" data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;site header menu&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;SIGN_UP&quot;,&quot;originating_url&quot;:&quot;https://github.com/episode6/moonlight-steam-sync-decky/blob/main/DEVICE-CHECKLIST.md&quot;,&quot;user_id&quot;:null}}" data-hydro-click-hmac="09f77c38ebccd5bdc694628b8958000ba771de629e8241b51ca4581b653b8fca"><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH">Sign in</span></span></a></div><a class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--secondary___gHnw_ Primer_Brand__Button-module__Button--size-small___zQrEw AuthCTAs-module__cta__WpwQq" href="/signup?ref_cta=Sign+up&amp;ref_loc=header+logged+out&amp;ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fblob%2Fshow&amp;source=header-repo&amp;source_repo=episode6%2Fmoonlight-steam-sync-decky" data-analytics-event="{&quot;action&quot;:&quot;sign_up&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;auth_cta&quot;,&quot;location&quot;:&quot;header&quot;,&quot;label&quot;:&quot;sign_up_link_auth_cta_header&quot;}" data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;site header menu&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;SIGN_UP&quot;,&quot;originating_url&quot;:&quot;https://github.com/episode6/moonlight-steam-sync-decky/blob/main/DEVICE-CHECKLIST.md&quot;,&quot;user_id&quot;:null}}" data-hydro-click-hmac="09f77c38ebccd5bdc694628b8958000ba771de629e8241b51ca4581b653b8fca"><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-secondary___eJ0_a">Sign up</span></span></a><button class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw HeaderAppearanceSettings-module__trigger__hUheK" type="button" aria-haspopup="dialog" aria-labelledby="_R_fbd_"><span class="Primer_Brand__Button-module__Button__leading-visual___jjtTe" data-testid="Button-leading-visual"><svg data-component="Octicon" focusable="false" aria-hidden="true" class="octicon octicon-sliders Primer_Brand__Button-module__Button__icon-visual____qybb" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M15 2.75a.75.75 0 0 1-.75.75h-4a.75.75 0 0 1 0-1.5h4a.75.75 0 0 1 .75.75Zm-8.5.75v1.25a.75.75 0 0 0 1.5 0v-4a.75.75 0 0 0-1.5 0V2H1.75a.75.75 0 0 0 0 1.5H6.5Zm1.25 5.25a.75.75 0 0 0 0-1.5h-6a.75.75 0 0 0 0 1.5h6ZM15 8a.75.75 0 0 1-.75.75H11.5V10a.75.75 0 1 1-1.5 0V6a.75.75 0 0 1 1.5 0v1.25h2.75A.75.75 0 0 1 15 8Zm-9 5.25v-2a.75.75 0 0 0-1.5 0v1.25H1.75a.75.75 0 0 0 0 1.5H4.5v1.25a.75.75 0 0 0 1.5 0v-2Zm9 0a.75.75 0 0 1-.75.75h-6a.75.75 0 0 1 0-1.5h6a.75.75 0 0 1 .75.75Z"></path></svg></span><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH"></span></span></button><div class="Primer_Brand__Tooltip-module__Tooltip___0Eipx" data-direction="s" aria-hidden="true" id="_R_fbd_">Appearance settings</div></div></div></div></div><div class="MarketingHeader-module__bottomBorder__uZT38" aria-hidden="true"></div></header></div></div>
</react-partial>



      <div hidden="hidden" data-view-component="true" class="js-stale-session-flash stale-session-flash flash flash-warn flash-full">
  
        <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
        <span class="js-stale-session-flash-signed-in" hidden>You signed in with another tab or window. <a class="Link--inTextBlock" href="">Reload</a> to refresh your session.</span>
        <span class="js-stale-session-flash-signed-out" hidden>You signed out in another tab or window. <a class="Link--inTextBlock" href="">Reload</a> to refresh your session.</span>
        <span class="js-stale-session-flash-switched" hidden>You switched accounts on another tab or window. <a class="Link--inTextBlock" href="">Reload</a> to refresh your session.</span>

    <button id="icon-button-9bbaeb16-ee95-46a4-ab68-7826ae356cf3" aria-labelledby="tooltip-40537293-7726-4377-81a1-0a3f6a407321" type="button" data-view-component="true" class="Button Button--iconOnly Button--invisible Button--medium flash-close js-flash-close">  <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x Button-visual">
    <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
</button><tool-tip id="tooltip-40537293-7726-4377-81a1-0a3f6a407321" for="icon-button-9bbaeb16-ee95-46a4-ab68-7826ae356cf3" popover="manual" data-direction="s" data-type="label" data-view-component="true" class="sr-only position-absolute">Dismiss alert</tool-tip>


  
</div>
    </div>

  <div id="start-of-content" class="show-on-focus"></div>








    <div id="js-flash-container" class="flash-container" data-turbo-replace>






  <template class="js-flash-template">
    
<div class="flash flash-full   {{ className }}">
  <div >
    <button autofocus class="flash-close js-flash-close" type="button" aria-label="Dismiss this message">
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x">
    <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
    </button>
    <div aria-atomic="true" role="alert" class="js-flash-alert">
      
      <div>{{ message }}</div>

    </div>
  </div>
</div>
  </template>
</div>


    






  <div
    class="application-main "
    data-commit-hovercards-enabled
    data-discussion-hovercards-enabled
    data-issue-and-pr-hovercards-enabled
    data-project-hovercards-enabled
  >
        <div itemscope itemtype="http://schema.org/SoftwareSourceCode" class="">
    <main id="js-repo-pjax-container" >
      
      
    


    








  

    <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ym-6bc2c9bf8112c038.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/sk2-8deeef9351be3911.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ni-3e194d222c7770bf.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/7x-71d30cb240cd901f.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/wwk-ad869086822937b1.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/a1-9f79841ecac1da8e.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/wzg-40a3213b3e83400b.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/5f-64f8e7d5bcbd4787.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/z5-b53a93078d026f1e.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/8h-cef26f68c1331011.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/fq-3ce13a77e863807a.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/a7-596b507bd20acddc.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/xfx-c82d8352413aea0a.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/j4-b3a9e87fa7878a98.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/global-nav-bar-29c7aef68ee9db41.js" fetchpriority="low" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.2d881b4d4a503d1c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/pj.75627bfeb9ceb1da.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/global-nav-bar.2f2560cba06ab359.module.css" />

<react-partial
  partial-name="global-nav-bar"
  data-ssr="true"
  data-attempted-ssr="true"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-partial.embeddedData">{"props":{"contextRegion":{"crumbs":[{"crumb_type":"organization","label":"episode6","is_root":false,"href":"/episode6"},{"crumb_type":"repository","label":"moonlight-steam-sync-decky","is_root":false,"href":"/episode6/moonlight-steam-sync-decky"}],"localNavigation":[{"id":"code","icon":"code","label":"Code","href":"/episode6/moonlight-steam-sync-decky","selectedLinks":["repo_source","repo_downloads","repo_commits","repo_releases","repo_tags","repo_branches","repo_packages","repo_deployments","repo_attestations"],"popoverTarget":false,"commandId":"repositories:go-to-code","reactNav":{"appTarget":"code-view","anchor":"code-view-repo-link"},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"issues","icon":"issue-opened","label":"Issues","href":"/episode6/moonlight-steam-sync-decky/issues","selectedLinks":["repo_issues","repo_labels","repo_milestones"],"count":0,"popoverTarget":false,"commandId":"repositories:go-to-issues","reactNav":{"appTarget":"repo","anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"pull-requests","icon":"git-pull-request","label":"Pull requests","href":"/episode6/moonlight-steam-sync-decky/pulls","selectedLinks":["repo_pulls","checks"],"count":0,"popoverTarget":false,"commandId":"repositories:go-to-pull-requests","reactNav":{"appTarget":null,"anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"actions","icon":"play","label":"Actions","href":"/episode6/moonlight-steam-sync-decky/actions","selectedLinks":["repo_actions"],"popoverTarget":false,"commandId":"repositories:go-to-actions","reactNav":{"appTarget":"actions-workflows","anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"projects","icon":"table","label":"Projects","href":"/episode6/moonlight-steam-sync-decky/projects","selectedLinks":["repo_projects","new_repo_project","repo_project"],"popoverTarget":false,"commandId":"repositories:go-to-projects","reactNav":{"appTarget":"repo","anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"security-and-quality","icon":"shield","label":"Security and quality","href":"/episode6/moonlight-steam-sync-decky/security","selectedLinks":["security","overview","alerts","policy","token_scanning","code_scanning"],"count":0,"popoverTarget":false,"commandId":"repositories:go-to-security","reactNav":{"appTarget":null,"anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"insights","icon":"graph","label":"Insights","href":"/episode6/moonlight-steam-sync-decky/pulse","selectedLinks":["repo_graphs","repo_contributors","dependency_graph","dependabot_updates","pulse","people","community"],"popoverTarget":false,"commandId":"repositories:go-to-insights","reactNav":{"appTarget":null,"anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}}],"localNavigationUpdateChannel":null,"selectedLink":"repo_source"},"owner":null,"headerLogo":{"href":"/","aria-label":"Homepage "},"notifications":{"indicatorMode":"disabled","websocketChannel":null,"fetchIndicatorSrc":"/notifications/indicator","fetchIndicatorEnabled":false},"issues":{"href":"/issues"},"pulls":{"href":"/pulls"},"contributedRepos":{"href":"/repos"},"copilot":{"show":false,"showAgentsButton":false,"showRelaunchAnnouncement":false,"copilotChatUrl":null,"copilotApiUrl":"https://api.githubcopilot.com"},"search":{"show":true,"showCommandPalette":false,"isSearchPage":false,"isJumpToSearch":false,"searchContext":{"scope":"repo:episode6/moonlight-steam-sync-decky","current_repo_name":"moonlight-steam-sync-decky","current_repo_nwo":"episode6/moonlight-steam-sync-decky","current_repo_org":"episode6","user_id":"episode6"}},"commandPalette":null,"enterpriseBar":{"show":false},"globalTransactionalMessage":[],"payloadsUrl":"/_global-navigation/payloads.json?can_toggle_site_admin_and_employee_status=0\u0026is_admin_mode_on=0\u0026is_ui_opted_out=0\u0026show_ui_opt_out=0\u0026v=7","contextRegionUrl":"/_global-navigation/context-region.json"}}</script>
  <div data-target="react-partial.reactRoot"><header aria-label="Global navigation menu" data-component="Stack" class="GlobalNav styles-module__appHeader__YzYWk prc-Stack-Stack-UQ9k6" data-gap="none" data-direction="vertical" data-align="stretch" data-wrap="nowrap" data-justify="start" data-padding="none"><div data-component="Stack" class="prc-Stack-Stack-UQ9k6" data-direction="horizontal" data-align="center" data-wrap="nowrap" data-justify="center" data-padding="none"><div data-testid="top-nav-center" data-component="Stack" class="styles-module__center__R3QRv styles-module__withLocalNavigation__rjTJ_ GlobalNavBar-module__loggedOut__Jv1rk prc-Stack-Stack-UQ9k6" data-gap="condensed" data-direction="horizontal" data-align="stretch" data-wrap="nowrap" data-justify="start" data-padding="normal"><nav class="styles-module__contextRegion__VbSp2 prc-Breadcrumbs-BreadcrumbsBase-3Gb-B" aria-label="Breadcrumbs" data-overflow="menu" data-variant="normal" data-component="Breadcrumbs"><ol class="prc-Breadcrumbs-BreadcrumbsList-BKjpe"><li class="prc-Breadcrumbs-ItemWrapper-k0NLn"><a class="styles-module__contextCrumb__IzGIq prc-Breadcrumbs-Item-jcraJ" data-component="Breadcrumbs.Item" href="/episode6" data-discover="true"><span class="">episode6</span></a></li><li class="prc-Breadcrumbs-ItemWrapper-k0NLn"><a class="styles-module__contextCrumb__IzGIq prc-Breadcrumbs-Item-jcraJ" data-component="Breadcrumbs.Item" href="/episode6/moonlight-steam-sync-decky" data-discover="true"><span class="styles-module__contextCrumbLast__tI2e3">moonlight-steam-sync-decky</span></a></li></ol></nav></div><div data-testid="top-nav-right" data-component="Stack" class="styles-module__right__mlBQg styles-module__withLocalNavigation__rjTJ_ styles-module__rightWithResponsiveCreateButton__SKn2W prc-Stack-Stack-UQ9k6" data-gap="condensed" data-direction="horizontal" data-align="center" data-wrap="nowrap" data-justify="start" data-padding="normal"></div></div><h2 class="prc-src-InternalVisuallyHidden-2YaI6">Repository navigation</h2><nav class="prc-components-UnderlineWrapper-eT-Yj prc-UnderlineNav-UnderlineWrapper-GWONT LocalNavigation-module__LocalNavigation__b0Xc0" aria-label="Repository" data-variant="inset" data-overflow-mode="wrap" data-hide-icons-breakpoint="medium"><ul class="prc-UnderlineNav-ItemsList-oj8gN prc-components-UnderlineItemList-xKlKC" role="list"><li role="presentation" aria-hidden="true" class="prc-UnderlineNav-WrapSpacer--aLgz"></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a aria-current="page" data-tab-item="code" data-react-nav="code-view" data-react-nav-anchor="code-view-repo-link" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/episode6/moonlight-steam-sync-decky" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-code" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m11.28 3.22 4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L13.94 8l-3.72-3.72a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215Zm-6.56 0a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042L2.06 8l3.72 3.72a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L.47 8.53a.75.75 0 0 1 0-1.06Z"></path></svg></span><span data-component="text" data-content="Code">Code</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="issues" data-react-nav="repo" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/episode6/moonlight-steam-sync-decky/issues" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-issue-opened" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z"></path></svg></span><span data-component="text" data-content="Issues">Issues</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="pull-requests" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/episode6/moonlight-steam-sync-decky/pulls" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-git-pull-request" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25 0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251 2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1 0-.354ZM3.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm8.25.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Z"></path></svg></span><span data-component="text" data-content="Pull requests">Pull requests</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="actions" data-react-nav="actions-workflows" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/episode6/moonlight-steam-sync-decky/actions" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-play" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm4.879-2.773 4.264 2.559a.25.25 0 0 1 0 .428l-4.264 2.559A.25.25 0 0 1 6 10.559V5.442a.25.25 0 0 1 .379-.215Z"></path></svg></span><span data-component="text" data-content="Actions">Actions</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="projects" data-react-nav="repo" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/episode6/moonlight-steam-sync-decky/projects" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-table" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25ZM6.5 6.5v8h7.75a.25.25 0 0 0 .25-.25V6.5Zm8-1.5V1.75a.25.25 0 0 0-.25-.25H6.5V5Zm-13 1.5v7.75c0 .138.112.25.25.25H5v-8ZM5 5V1.5H1.75a.25.25 0 0 0-.25.25V5Z"></path></svg></span><span data-component="text" data-content="Projects">Projects</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="security-and-quality" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/episode6/moonlight-steam-sync-decky/security" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-shield" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.467.133a1.748 1.748 0 0 1 1.066 0l5.25 1.68A1.75 1.75 0 0 1 15 3.48V7c0 1.566-.32 3.182-1.303 4.682-.983 1.498-2.585 2.813-5.032 3.855a1.697 1.697 0 0 1-1.33 0c-2.447-1.042-4.049-2.357-5.032-3.855C1.32 10.182 1 8.566 1 7V3.48a1.75 1.75 0 0 1 1.217-1.667Zm.61 1.429a.25.25 0 0 0-.153 0l-5.25 1.68a.25.25 0 0 0-.174.238V7c0 1.358.275 2.666 1.057 3.86.784 1.194 2.121 2.34 4.366 3.297a.196.196 0 0 0 .154 0c2.245-.956 3.582-2.104 4.366-3.298C13.225 9.666 13.5 8.36 13.5 7V3.48a.251.251 0 0 0-.174-.237l-5.25-1.68ZM8.75 4.75v3a.75.75 0 0 1-1.5 0v-3a.75.75 0 0 1 1.5 0ZM9 10.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg></span><span data-component="text" data-content="Security and quality">Security and quality</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="insights" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/episode6/moonlight-steam-sync-decky/pulse" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-graph" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M1.5 1.75V13.5h13.75a.75.75 0 0 1 0 1.5H.75a.75.75 0 0 1-.75-.75V1.75a.75.75 0 0 1 1.5 0Zm14.28 2.53-5.25 5.25a.75.75 0 0 1-1.06 0L7 7.06 4.28 9.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.25-3.25a.75.75 0 0 1 1.06 0L10 7.94l4.72-4.72a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"></path></svg></span><span data-component="text" data-content="Insights">Insights</span></a></li></ul><div class="prc-UnderlineNav-MoreButtonContainer-Dnrq6"><div class="prc-UnderlineNav-MoreButtonDivider-dN0a-"></div><button data-component="overflow-menu-button" type="button" aria-haspopup="true" aria-expanded="false" tabindex="0" class="prc-Button-ButtonBase-9n-Xk prc-UnderlineNav-MoreButton-Y8soj" data-loading="false" data-size="medium" data-variant="invisible" id="_R_2ktp_"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="text" class="prc-Button-Label-FWkx3"><span>More<span class="prc-src-InternalVisuallyHidden-2YaI6"> items</span></span></span></span><span data-component="trailingAction" class="prc-Button-Visual-YNt2F prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-down" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z"></path></svg></span></button></div></nav><div class="d-none"></div></header></div>
</react-partial>


  



<turbo-frame id="repo-content-turbo-frame" target="_top" data-turbo-action="advance" class="">
    <div id="repo-content-pjax-container" class="repository-content " >
    



    
      
    








<react-app
  app-name="code-view"
  initial-path="/episode6/moonlight-steam-sync-decky/blob/main/DEVICE-CHECKLIST.md"
  style="display: block; min-height: calc(100vh - 64px);"
  data-attempted-ssr="true"
  data-ssr="true"
  data-lazy="false"
  data-alternate="false"
  data-data-router-enabled="true"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-app.embeddedData">{"payload":{"codeViewBlobRoute":{"csv":null,"csvError":null,"headerInfo":{"toc":[{"level":1,"text":"Device checklist","anchor":"device-checklist","htmlText":"Device checklist"},{"level":2,"text":"0. Setup","anchor":"0-setup","htmlText":"0. Setup"},{"level":2,"text":"1. PR-0: device probes","anchor":"1-pr-0-device-probes","htmlText":"1. PR-0: device probes"},{"level":2,"text":"2. PR-5: backend, Quick Access panel","anchor":"2-pr-5-backend-quick-access-panel","htmlText":"2. PR-5: backend, Quick Access panel"},{"level":2,"text":"3. PR-6: Titles page, Change match","anchor":"3-pr-6-titles-page-change-match","htmlText":"3. PR-6: Titles page, Change match"},{"level":2,"text":"4. PR-7: Stream button and controller layouts","anchor":"4-pr-7-stream-button-and-controller-layouts","htmlText":"4. PR-7: Stream button and controller layouts"},{"level":2,"text":"5. PR-8: this checklist and install.sh","anchor":"5-pr-8-this-checklist-and-installsh","htmlText":"5. PR-8: this checklist and install.sh"},{"level":2,"text":"6. Host apps: the panel buttons replace the tiles (spec 3.14)","anchor":"6-host-apps-the-panel-buttons-replace-the-tiles-spec-314","htmlText":"6. Host apps: the panel buttons replace the tiles (spec 3.14)"},{"level":2,"text":"7. Hidden shortcuts and the Streaming collection (spec 3.15)","anchor":"7-hidden-shortcuts-and-the-streaming-collection-spec-315","htmlText":"7. Hidden shortcuts and the Streaming collection (spec 3.15)"},{"level":2,"text":"8. The default controller layout (spec 3.16)","anchor":"8-the-default-controller-layout-spec-316","htmlText":"8. The default controller layout (spec 3.16)"},{"level":2,"text":"9. Fixtures vs. real CLI","anchor":"9-fixtures-vs-real-cli","htmlText":"9. Fixtures vs. real CLI"},{"level":2,"text":"10. The Streaming tab (spec 3.17)","anchor":"10-the-streaming-tab-spec-317","htmlText":"10. The Streaming tab (spec 3.17)"},{"level":2,"text":"11. Wake-on-LAN (spec 3.18)","anchor":"11-wake-on-lan-spec-318","htmlText":"11. Wake-on-LAN (spec 3.18)"},{"level":2,"text":"12. The on/off toggle (spec 3.19)","anchor":"12-the-onoff-toggle-spec-319","htmlText":"12. The on/off toggle (spec 3.19)"},{"level":2,"text":"13. Reset match cache (Advanced)","anchor":"13-reset-match-cache-advanced","htmlText":"13. Reset match cache (Advanced)"},{"level":2,"text":"14. The SteamGridDB key from the Game Mode browser (spec 3.20)","anchor":"14-the-steamgriddb-key-from-the-game-mode-browser-spec-320","htmlText":"14. The SteamGridDB key from the Game Mode browser (spec 3.20)"},{"level":2,"text":"15. The panel's launch row and Advanced's layout buttons (spec Decisions 67 and 77)","anchor":"15-the-panels-launch-row-and-advanceds-layout-buttons-spec-decisions-67-and-77","htmlText":"15. The panel's launch row and Advanced's layout buttons (spec Decisions 67 and 77)"},{"level":2,"text":"16. Updating from the plugin (update spec 3.9, V1 to V7)","anchor":"16-updating-from-the-plugin-update-spec-39-v1-to-v7","htmlText":"16. Updating from the plugin (update spec 3.9, V1 to V7)"},{"level":2,"text":"17. The staged hand-off and the channels (update spec 3.12.5, V8 to V10)","anchor":"17-the-staged-hand-off-and-the-channels-update-spec-3125-v8-to-v10","htmlText":"17. The staged hand-off and the channels (update spec 3.12.5, V8 to V10)"}]},"issueTemplate":null,"discussionTemplate":null,"richText":"\u003carticle class=\"markdown-body entry-content container-lg\" itemprop=\"text\"\u003e\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch1 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eDevice checklist\u003c/h1\u003e\u003ca id=\"user-content-device-checklist\" class=\"anchor\" aria-label=\"Permalink: Device checklist\" href=\"#device-checklist\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eEverything below needs a real Steam Deck (or SteamOS machine with Decky\nLoader) in Game Mode; none of it is a merge criterion for any plugin PR\n(spec section 4: \"on device\" acceptance items are transcribed here, not\nenforced by CI). Run it top to bottom the first time a build reaches a\nDeck. Findings that contradict the spec go to a Fable-class model (spec\nsection 5), not decided here.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e0. Setup\u003c/h2\u003e\u003ca id=\"user-content-0-setup\" class=\"anchor\" aria-label=\"Permalink: 0. Setup\" href=\"#0-setup\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003col dir=\"auto\"\u003e\n\u003cli\u003eGet a build onto the Deck: \u003ccode\u003ecurl -fsSL https://raw.githubusercontent.com/episode6/moonlight-steam-sync-decky/main/install.sh | sh\u003c/code\u003e (\u003ccode\u003esudo\u003c/code\u003e prompts, explained by the script; without a password\nset it offers a temporary one, section 5). For a build newer\nthan the latest release, build off-device with \u003ccode\u003epnpm install \u0026amp;\u0026amp; pnpm run build \u0026amp;\u0026amp; backend/entrypoint.sh \u0026amp;\u0026amp; python3 scripts/package.py\u003c/code\u003e, or\ntake the \u003ccode\u003eMoonlight-Sync\u003c/code\u003e artifact from a CI run, and install it by\nhand — \u003ccode\u003eREADME.md\u003c/code\u003e's \"Manual install\". Either way Settings → About\nshould show the bundled and installed CLI both at the plugin's version\n(the CLI is built from \u003ccode\u003ecli/\u003c/code\u003e with it; \u003ccode\u003e0.4.0\u003c/code\u003e before the move); the plugin\ninstalls or upgrades \u003ccode\u003e~/.local/bin/moonlight-steam-sync\u003c/code\u003e on first load.\u003c/li\u003e\n\u003cli\u003eSSH into the Deck while it sits in Game Mode (\u003ccode\u003epasswd\u003c/code\u003e once in Desktop\nMode, then \u003ccode\u003esudo systemctl enable --now sshd\u003c/code\u003e; see \u003ccode\u003eprobes/PROBES.md\u003c/code\u003e\n§1.1 for the full one-time setup, which the PR-0 probes below also\nneed).\u003c/li\u003e\n\u003cli\u003eLogs: \u003ccode\u003e~/homebrew/logs/Moonlight Sync/moonlight-sync.log\u003c/code\u003e (every CLI\ncall with its arguments, the CLI's own stderr verbatim, both CLI\nversions at startup) and Decky's own log\n(\u003ccode\u003e~/homebrew/logs/decky-loader/…\u003c/code\u003e or \u003ccode\u003ejournalctl -u plugin_loader\u003c/code\u003e) for\nbackend tracebacks. Settings → About also shows a log tail and both CLI\nversions without leaving the panel.\u003c/li\u003e\n\u003cli\u003eTo flip the controller-layout strategy by hand (no UI yet, spec §3.10):\nstop the plugin (\u003ccode\u003esudo systemctl stop plugin_loader\u003c/code\u003e or unload it from\nDecky's developer menu), edit \u003ccode\u003e\"layout_strategy\"\u003c/code\u003e in\n\u003ccode\u003e~/homebrew/settings/Moonlight Sync/settings.json\u003c/code\u003e to \u003ccode\u003e\"copy\"\u003c/code\u003e or\n\u003ccode\u003e\"picker\"\u003c/code\u003e (removing the key restores the default, currently \u003ccode\u003e\"copy\"\u003c/code\u003e),\nthen reload.\u003c/li\u003e\n\u003cli\u003eThe CLI must work \u003cem\u003efrom the plugin\u003c/em\u003e, not only from SSH: plugin_loader\nis a PyInstaller binary whose \u003ccode\u003eLD_LIBRARY_PATH=/tmp/_MEI…\u003c/code\u003e once broke\nthe CLI's \u003ccode\u003eflatpak\u003c/code\u003e call (\"moonlight CLI not found\") and its \u003ccode\u003eimport ssl\u003c/code\u003e (\"5 consecutive network failures\"). With Moonlight installed as\nthe Flathub flatpak, pick a host in the panel and search for a title in\nthe match fixer; then \u003ccode\u003egrep -n \"not found\\|network failures\" \"$HOME/homebrew/logs/Moonlight Sync/moonlight-sync.log\"\u003c/code\u003e must find\nnothing newer than the install. To reproduce the loader's environment\nfrom SSH, while plugin_loader is running (its unpack directory goes\nwhen it exits): \u003ccode\u003eenv -i HOME=$HOME PATH=/usr/bin LD_LIBRARY_PATH=$(ls -dt /tmp/_MEI* | head -1) python3 ~/.local/bin/moonlight-steam-sync doctor\u003c/code\u003e — the \u003ccode\u003emoonlight:\u003c/code\u003e line must name the flatpak (CLI 0.3.1 or\nnewer; 0.3.0 prints \u003ccode\u003enot found\u003c/code\u003e there, which the plugin's cleaned\nenvironment hides). \u003ccode\u003els -dt\u003c/code\u003e takes the newest \u003ccode\u003e_MEI*\u003c/code\u003e directory; if\nanother PyInstaller program is running too, check that the one picked\nholds a \u003ccode\u003elibssl.so.3\u003c/code\u003e. The loader's own \u003ccode\u003e/proc/\u0026lt;pid\u0026gt;/environ\u003c/code\u003e is not\nthe shortcut it looks like: the service runs as root, so reading it\nneeds \u003ccode\u003esudo\u003c/code\u003e, and even the backend's deck-owned process refuses.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e1. PR-0: device probes\u003c/h2\u003e\u003ca id=\"user-content-1-pr-0-device-probes\" class=\"anchor\" aria-label=\"Permalink: 1. PR-0: device probes\" href=\"#1-pr-0-device-probes\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThe primary path is the scripted runner; the throwaway \u003ccode\u003esteam-input-probe\u003c/code\u003e\nplugin is the fallback. Full procedure, venv setup and the exact commands:\n\u003ca href=\"/episode6/moonlight-steam-sync-decky/blob/main/probes/PROBES.md\"\u003e\u003ccode\u003eprobes/PROBES.md\u003c/code\u003e\u003c/a\u003e. The fallback button plugin logs\nto \u003ccode\u003e~/homebrew/logs/steam-input-probe/steam-input-probe.log\u003c/code\u003e (the loader's\n\u003ccode\u003eDECKY_PLUGIN_LOG_DIR\u003c/code\u003e; a loader old enough not to set it falls back to\n\u003ccode\u003e~/homebrew/logs/steam-input-probe.log\u003c/code\u003e). In order:\u003c/p\u003e\n\u003cul class=\"contains-task-list\"\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eSetup\u003c/strong\u003e (PROBES.md §1.1): SSH in, create the \u003ccode\u003e~/.cache/msy-probes\u003c/code\u003e\nvenv, confirm the CEF DevTools port answers\n(\u003ccode\u003ecurl -s http://localhost:8080/json | grep -c SharedJSContext\u003c/code\u003e).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eV4\u003c/strong\u003e: \u003ccode\u003erun_probes.py v4\u003c/code\u003e — dump the first 20 of\n\u003ccode\u003ecollectionStore.allAppsCollection.allApps\u003c/code\u003e (\u003ccode\u003eappid\u003c/code\u003e, \u003ccode\u003edisplay_name\u003c/code\u003e,\n\u003ccode\u003eapp_type\u003c/code\u003e) and the current steamid3. Expect real entries and a\nplausible steamid3.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eV1\u003c/strong\u003e: \u003ccode\u003erun_probes.py v1 --appids \u0026lt;owned appid\u0026gt;,\u0026lt;another\u0026gt;\u003c/code\u003e — dump\n\u003ccode\u003eGetConfigForAppAndController\u003c/code\u003e per appid plus the Deck controller\nindex found by type. Expect a \u003ccode\u003etemplate://\u003c/code\u003e or \u003ccode\u003eworkshop://\u003c/code\u003e URL for\na game with a chosen layout, \u003ccode\u003edefault://…\u003c/code\u003e for one left on Steam's\nguess, and the controller index (record it — it decides the\n\u003ccode\u003eDECK_CONTROLLER_TYPE\u003c/code\u003e fallback in \u003ccode\u003esrc/lib/layouts.ts\u003c/code\u003e if it is not\nwhat the constant assumes).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eV2\u003c/strong\u003e (state-changing, needs \u003ccode\u003e--url\u003c/code\u003e; make a throwaway shortcut\nfirst): \u003ccode\u003erun_probes.py v2 --shortcut \u0026lt;APPID\u0026gt; --url 'workshop://…'\u003c/code\u003e\n(an id published for a real game you own) — calls\n\u003ccode\u003eSetSelectedConfigForApp\u003c/code\u003e then reads back. Expect the read-back URL\nto equal what was set. \u003cstrong\u003eThis decides the strategy\u003c/strong\u003e: if it does not\nmatch, flip \u003ccode\u003eDEFAULT_LAYOUT_STRATEGY\u003c/code\u003e to \u003ccode\u003e\"picker\"\u003c/code\u003e in\n\u003ccode\u003esrc/lib/layouts.ts\u003c/code\u003e and rewrite the README's \"Controller layouts\"\nsection — escalate to a Fable-class model rather than doing this\ninline.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eV5\u003c/strong\u003e: \u003ccode\u003erun_probes.py v5 --shortcut \u0026lt;APPID\u0026gt; [--run]\u003c/code\u003e — dump\n\u003ccode\u003eappStore.GetAppOverviewByAppID(appid).gameid\u003c/code\u003e and, with \u003ccode\u003e--run\u003c/code\u003e,\nlaunch it. Expect a non-null overview once Steam has loaded the\nshortcut list, and (with \u003ccode\u003e--run\u003c/code\u003e) the shortcut to actually launch.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eV3\u003c/strong\u003e (state-changing, needs \u003ccode\u003e--yes\u003c/code\u003e or an interactive confirm):\n\u003ccode\u003erun_probes.py v3\u003c/code\u003e — logs \u003ccode\u003epgrep -x steam\u003c/code\u003e every 100 ms for 90 s\naround a \u003ccode\u003eStartShutdown(false)\u003c/code\u003e call, then reconnects to confirm\nSteam came back. Expect the log to show a clear gap between the\nclient going away and coming back, confirming \u003ccode\u003eStartShutdown(false)\u003c/code\u003e\nis the restart from Game Mode (spec §2.3). \u003cstrong\u003eThe measurement is\n\u003ccode\u003egaps\u003c/code\u003e (from the pgrep samples), not \u003ccode\u003eback_after_ms\u003c/code\u003e\u003c/strong\u003e: that field\nis measured from the start of the watch and the reconnect only\nbegins after the baseline plus the whole \u003ccode\u003e--duration\u003c/code\u003e, so it is\nbounded below by roughly 91 s whatever Steam does. Read it only as\n\"Steam came back: yes/no\" (PROBES.md §1.3).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e Run \u003ccode\u003erun_probes.py all --appids … --shortcut \u0026lt;APPID\u0026gt; [--url … --yes]\u003c/code\u003e\nonce everything above passes individually, and paste\n\u003ccode\u003eprobe-results.json\u003c/code\u003e's \u003ccode\u003e--markdown\u003c/code\u003e table into\n\u003ccode\u003e~/specs/moonlight-steam-sync/decky-plugin.md\u003c/code\u003e §2 (a Fable-class\nmodel does the actual spec edit).\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2. PR-5: backend, Quick Access panel\u003c/h2\u003e\u003ca id=\"user-content-2-pr-5-backend-quick-access-panel\" class=\"anchor\" aria-label=\"Permalink: 2. PR-5: backend, Quick Access panel\" href=\"#2-pr-5-backend-quick-access-panel\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul class=\"contains-task-list\"\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eFirst load installs the bundled CLI.\u003c/strong\u003e With no \u003ccode\u003e~/.local/bin/moonlight-steam-sync\u003c/code\u003e\npresent, load the plugin. Expect it to appear in \u003ccode\u003e~/.local/bin\u003c/code\u003e after\nload, and Settings → About to show both the bundled and installed\nversions matching the plugin's own version (About's \u003cem\u003ePlugin\nversion\u003c/em\u003e): the CLI is built from \u003ccode\u003ecli/\u003c/code\u003e at the same version.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eCounters, Sync now, the restart modal.\u003c/strong\u003e Press \u003cem\u003eSync now\u003c/em\u003e. Expect\nprogress lines, then either the restart modal (writes pending) or\n\"nothing to do\"; the four counters and \u003cem\u003eLast sync\u003c/em\u003e update after.\nThe progress bar and its \"ARTWORK: TITLE N OF M\" label stay inside\nthe panel, the bar as wide as the \u003cem\u003eStop\u003c/em\u003e button under it (before\n2026-09-23 the bar's fixed-width column overflowed the panel).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eStop right after Sync.\u003c/strong\u003e Press \u003cem\u003eSync now\u003c/em\u003e, then \u003cem\u003eStop\u003c/em\u003e within about\na second (before the first progress line). Expect the panel to leave\nthe progress view reporting a stopped run (not a run that keeps\ngoing), \u003cem\u003eLast sync\u003c/em\u003e not claiming any added/replaced shortcuts, and\n\u003ccode\u003e~/homebrew/logs/Moonlight Sync/moonlight-sync.log\u003c/code\u003e (or Decky's own\nlog) to show either \"stop: SIGINT to the sync run\" or \"stop: the\nsync run has not spawned yet; it will be interrupted\", exit 130.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eStop during the await-exit wait.\u003c/strong\u003e Start a sync that has writes to\nmake, wait for the \"close Steam\" state, press \u003cem\u003eStop\u003c/em\u003e, reopen the\npanel. Expect the persistent \"Restart Steam to apply\" row to still be\noffered (nothing was written) and \u003cem\u003eLast sync\u003c/em\u003e not to claim any\nadded/replaced titles.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eUnload during a run.\u003c/strong\u003e Start a sync, then reload the plugin\n(Decky's developer menu → reload, or reinstall). Expect\n\u003ccode\u003epgrep -af moonlight-steam-sync\u003c/code\u003e to return nothing a few seconds\nlater, and a fresh \u003cem\u003eSync now\u003c/em\u003e afterwards to start exactly one run.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eOpen Moonlight\u003c/strong\u003e launches the client shortcut without picking a\ngame.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eDesktop\u003c/strong\u003e / \u003cstrong\u003eSteam Big Picture\u003c/strong\u003e show on the panel when the active\nhost publishes them, each starts its stream with the controller\nlayout chosen on that shortcut, and neither shows once its entry is\nignored and synced away.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eBackend not answering at load.\u003c/strong\u003e Force the failure (open the panel\nimmediately at Game Mode start, or temporarily rename\n\u003ccode\u003e~/homebrew/plugins/Moonlight Sync/py_modules\u003c/code\u003e to break a callable).\nExpect a Retry row carrying the backend's message instead of an\nendless \"Loading your library…\" spinner; restore, press Retry, expect\nthe panel to fill in without a plugin reload.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe host is never asked on its own\u003c/strong\u003e (Decision 66). Open the panel,\nclose it, reopen it, toggle the plugin off and on, open Settings →\nTitles: \u003ccode\u003emoonlight-sync.log\u003c/code\u003e shows no \u003ccode\u003e--json list --host\u003c/code\u003e without\n\u003ccode\u003e--cached\u003c/code\u003e for any of it, and (with several hosts) the host row\nreads a grey circle and \"N apps\" with \u003cstrong\u003eCheck\u003c/strong\u003e and (when a MAC is\nknown) \u003cstrong\u003eWake\u003c/strong\u003e under it. A shut-down PC stays down through all of\nit.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eCheck asks once.\u003c/strong\u003e With several hosts, press Check: \"Checking…\",\nthen a green circle and \"N apps\"; the log shows one live \u003ccode\u003e--json list --host\u003c/code\u003e. A second press within 10 s is answered from the memo\n(no new spawn). While a sync runs elsewhere (or the library is\nstill loading) the whole Host row is dimmed, label and count\nincluded, and the dropdown does not open.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eCheck with one host.\u003c/strong\u003e With a single host, press Check: expect\nno \"Checking…\" text anywhere (there is no row to put it in), the\nCheck button unavailable meanwhile, then \u003cstrong\u003eCheck\u003c/strong\u003e / \u003cstrong\u003eWake\u003c/strong\u003e gone\nwhen the host answered, or the red error line alone above them\nwhen it did not; one live \u003ccode\u003e--json list --host\u003c/code\u003e in the log.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eUnreachable host row.\u003c/strong\u003e With several hosts and the active one\nunreachable, press Check: expect a red circle and the cached \"N\napps\" in the Host row, and under the dropdown, above the divider,\nthe CLI's error message and \"last seen \" in red.\nFor a host never listed the row says \"never synced\" and the error\nline is the message alone; with one host the line ends \"· never\nsynced\" instead. A \u003cem\u003eSync now\u003c/em\u003e that fails with\nexit 3 paints the row the same way, and one that lists paints it\ngreen and takes the error line away, with no \u003ccode\u003echeck_host\u003c/code\u003e spawned\nfor either.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eOne host: no Host row.\u003c/strong\u003e With a single host, expect no \u003cem\u003eHost\u003c/em\u003e\nrow and no dropdown: \u003cstrong\u003eCheck\u003c/strong\u003e / \u003cstrong\u003eWake\u003c/strong\u003e directly under \u003cem\u003eEnable\nSync\u003c/em\u003e until the host is known reachable, then \u003cem\u003eSync now\u003c/em\u003e first. An\nunreachable host shows the error line alone above Check / Wake.\nCheck / Wake sit as far below the divider above them as \u003cem\u003eSync\nnow\u003c/em\u003e does when they are gone, with a divider of their own under\nthem. Add a second host on Settings → Host: the row and its dropdown\nappear; Forget it: they go again.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe switcher takes the row's spare width.\u003c/strong\u003e With several hosts,\nexpect the dropdown to start right of \"N apps\", never over it,\nand show a host name of a dozen characters whole, not cut at half\nthe row; a longer name ends in \"…\" rather than pushing the count\nunder the dropdown. The dropdown opens, lists every host and\nswitches as before (the row is a \u003ccode\u003eField\u003c/code\u003e with\n\u003ccode\u003echildrenContainerWidth=\"max\"\u003c/code\u003e around a \u003ccode\u003eDropdown\u003c/code\u003e, not a\n\u003ccode\u003eDropdownItem\u003c/code\u003e; the count running under the dropdown was seen on\na Deck 2026-09-29 and the fix measured there over the debugger).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eIn-game guard.\u003c/strong\u003e While a game is running, expect no restart\ncountdown and the text \"A game is running. Restart Steam when you're\ndone.\"\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eIn-game guard on the persistent row\u003c/strong\u003e (Decision 29). With a\npending \"Restart Steam to apply\" row (either kind: a pending write,\nor artwork), launch a game and open the panel. Expect the row to be\ndisabled with the subtitle \"A game is running; exit it first\", and\npressing it to do nothing. Exit the game: expect the row to become\npressable again. Also press the row just before launching a game so\nthe re-run's \u003ccode\u003eawaiting-steam-exit\u003c/code\u003e lands while the game is up:\nexpect the restart modal (with its in-game text) rather than Steam\nshutting down under the game.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eLater → the persistent row → immediate restart.\u003c/strong\u003e Press \u003cem\u003eLater\u003c/em\u003e on\nthe restart modal, expect the \"Restart Steam to apply\" row; press it,\nexpect the sync to re-run with \u003ccode\u003eimmediate_restart: true\u003c/code\u003e and shut\ndown at once on the next \u003ccode\u003eawaiting-steam-exit\u003c/code\u003e, no modal.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eArt-only restart\u003c/strong\u003e (a run with only artwork filled, no shortcut\nwrites) offers the same modal/row with \"New artwork needs a Steam\nrestart to show\".\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eRestart countdown slider.\u003c/strong\u003e Settings → Advanced: expect the\ncountdown to run 0-30 s and no further (Decision 30 — the CLI stops\nwaiting for Steam after 60 s, so a longer countdown would race it).\nSet 0 and expect the modal to show buttons with no countdown; set 30\nand expect it to count down from 30. A \u003ccode\u003esettings.json\u003c/code\u003e left over\nfrom an older build with \u003ccode\u003e\"restart_countdown_s\": 60\u003c/code\u003e must read back\nas 30, not break the settings page.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eRe-fetch all art with no \u003ccode\u003e--commit\u003c/code\u003e support.\u003c/strong\u003e Install (or\nhand-build) a CLI whose \u003ccode\u003eart --help\u003c/code\u003e lacks \u003ccode\u003e--commit\u003c/code\u003e. Expect\n\u003cem\u003eRe-fetch all art\u003c/em\u003e to be disabled, and if pressed anyway from a stale\nstate, the message to read \"re-fetching art from Game Mode needs a\nCLI whose art command accepts --commit\" — never \"CLI too old (0.4.0,\nneeds 0.4.0)\".\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eNothing about the other hosts.\u003c/strong\u003e Add three hosts (one active,\ntwo not), sync the inactive ones once each so they have cached\nlistings. Expect the Host row to say nothing of them: the circle\nand the active host's count only, no \"parked\" line.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe SteamGridDB key round-trip and Test.\u003c/strong\u003e Set a key on the\nArtwork page, expect only its last four characters ever shown\nanywhere (panel, events, logs); press \u003cem\u003eTest\u003c/em\u003e, expect a pass/fail\nresult with no more of the key revealed.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e3. PR-6: Titles page, Change match\u003c/h2\u003e\u003ca id=\"user-content-3-pr-6-titles-page-change-match\" class=\"anchor\" aria-label=\"Permalink: 3. PR-6: Titles page, Change match\" href=\"#3-pr-6-titles-page-change-match\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul class=\"contains-task-list\"\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eTitles list, idle.\u003c/strong\u003e With nothing running, press the panel\nheader's list button. Expect \"N published by MY-GAMING-PC · sorted by name\"\nand rows with capsules, badges and chips.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eTitles page mid-sync.\u003c/strong\u003e Start \u003cem\u003eSync now\u003c/em\u003e, then open Settings →\nTitles while it runs. Expect \"N published by MY-GAMING-PC[ · listed\n] · refreshes when the sync finishes\", the note \"A\nsync is running; this list refreshes and matches can be changed when\nit finishes\", and \u003cem\u003eChange match\u003c/em\u003e disabled on every row.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003e\u003ccode\u003elist --cached\u003c/code\u003e always, never live \u003ccode\u003elist\u003c/code\u003e\u003c/strong\u003e (Decision 66). Whether\nor not a sync is running, opening Titles spawns only \u003ccode\u003e--json list --cached --host MY-GAMING-PC\u003c/code\u003e, never a live \u003ccode\u003e--json list --host MY-GAMING-PC\u003c/code\u003e; the log proves it.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eRefresh after the sync.\u003c/strong\u003e Let it finish (restart when prompted),\nreopen Titles: it re-lists from the cache the sync just wrote, the\nheadline returns to \"N published by MY-GAMING-PC · listed just now ·\nsorted by name\", \u003cem\u003eChange match\u003c/em\u003e is enabled again.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eNever-synced host.\u003c/strong\u003e On a host that has never been synced, open\nTitles: \"MY-GAMING-PC was never synced; Sync now lists its titles\"\nand a \u003cem\u003eSync now\u003c/em\u003e button, no rows, filters or Retry. Add OFFICE-PC\nand press \u003cem\u003eCheck\u003c/em\u003e on it (both cache its list), make it active and\nopen Titles: the same line, still no rows. Press the page's \u003cem\u003eSync\nnow\u003c/em\u003e: the line reads \"… this list fills in when the sync finishes\",\nthe button goes, and the titles fill in after the run ends.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eChange match races a sync.\u003c/strong\u003e Open \u003cem\u003eChange match\u003c/em\u003e on a title; before\npressing \u003cem\u003eUse this\u003c/em\u003e, start a sync from the panel; then press \u003cem\u003eUse\nthis\u003c/em\u003e. Expect \"A sync is already running\", no pin written\n(\u003ccode\u003ematches.json\u003c/code\u003e unchanged), the row keeps its old match.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003ePin, then apply on next sync.\u003c/strong\u003e After that sync finishes, press\n\u003cem\u003eUse this\u003c/em\u003e again on the same title: expect a pin, the toast \"Pinned;\nsync to apply\", the pinned and \"art refreshes on next sync\" chips,\nand the next \u003cem\u003eSync now\u003c/em\u003e to turn an owned-game pin into a Stream\nbutton (Hades II re-matched from a fuzzy Hades hit is the spec's own\nexample: expect a Stream button on Hades II and the visible tile\ngone).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eIgnore.\u003c/strong\u003e \u003cem\u003eIgnore\u003c/em\u003e on a title removes its tile on the next sync;\n\u003cem\u003eUnignore\u003c/em\u003e restores it.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eOwned-apps mismatch recovery.\u003c/strong\u003e Sign in as a second Steam user on\nthe Deck, open Titles: expect exactly one \u003ccode\u003ewrite_owned_apps\u003c/code\u003e in the\nlog (recovered once), then a successful list and status — not two\nwrite attempts.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003ePaging.\u003c/strong\u003e With 300+ rows, page through with the D-pad; expect pages\nof 50 and correct navigation at both ends.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eRecently added.\u003c/strong\u003e After updating to a build with the chip, open\nTitles: the order is by name and \u003ccode\u003eadded.json\u003c/code\u003e in the settings\ndirectory lists every title with \u003ccode\u003enull\u003c/code\u003e (no \u003ccode\u003eMoonlight\u003c/code\u003e, \u003ccode\u003eDesktop\u003c/code\u003e\nor \u003ccode\u003eSteam Big Picture\u003c/code\u003e in it). Publish a new app on the\nhost, \u003cem\u003eSync now\u003c/em\u003e, restart when prompted, reopen Titles and press\n\u003cstrong\u003eRecently added\u003c/strong\u003e: the chip lights, the headline ends \"sorted by\nrecently added\", the new title is the first row and reads \"added\ntoday h:MM AM/PM\" (or \"HH:MM\" with Steam's 24-hour clock on), every\nother row follows by name with no \"added\" text,\nand \u003ccode\u003eadded.json\u003c/code\u003e has a time for the new title alone. Pick a filter:\nthe order holds. Leave the page and reopen it: still newest first.\nPress the chip again: by name, no \"added\" text. With the chip on,\nstart a sync and reopen Titles: the headline ends \"refreshes when\nthe sync finishes · sorted by recently added\".\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe clock follows Steam's.\u003c/strong\u003e With Steam's \u003cstrong\u003eSettings → System →\n24-hour clock\u003c/strong\u003e off (measured on the Deck 2026-09-29: the toggle\nwrites \u003ccode\u003esettingsStore.friendSettings.b24HourClock\u003c/code\u003e, which the header\nclock reads too), the Titles page's \"added today 2:02 PM\" and the\npanel's \u003cem\u003eLast sync\u003c/em\u003e use the 12-hour clock; turn it on and reopen\nthem: \"added today 14:02\", \"Today 14:02 · …\", with no plugin reload.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eNo layout text on the rows.\u003c/strong\u003e No Titles row says \u003ccode\u003elayout: …\u003c/code\u003e;\nthe note beside a game's Stream button still does while a default is\nset.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eShow more keeps your place.\u003c/strong\u003e Press \u003cem\u003eShow 50 more\u003c/em\u003e with the D-pad.\nExpect focus on the last row that was already shown (not on the\nbutton, now 50 rows further down), and one press of down to land on\nthe first new row.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e4. PR-7: Stream button and controller layouts\u003c/h2\u003e\u003ca id=\"user-content-4-pr-7-stream-button-and-controller-layouts\" class=\"anchor\" aria-label=\"Permalink: 4. PR-7: Stream button and controller layouts\" href=\"#4-pr-7-stream-button-and-controller-layouts\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003cem\u003eThe layout items in this section were written for the copy from the real\nSteam game, which spec 3.16 (§8 below) replaced with a default layout you\nadopt: nothing copies from the real game any more, there is no Advanced\ntoggle, and the Titles rows have no layout button any more (Decision 75:\na title's layout is picked from Steam's controller settings while it\nstreams). Run the button, guard, route-patch, picker-strategy and\ncontroller-index items here as written; the copy-specific ones are marked\nand superseded by §8.\u003c/em\u003e\u003c/p\u003e\n\u003cul class=\"contains-task-list\"\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eButton placement.\u003c/strong\u003e With a host active and a sync done, open the\nlibrary page of an owned game the host publishes: expect a Stream\nrow. Open a game the host does not publish (or one parked / matched\nto nothing): expect no Stream row.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eFocus order beside another plugin.\u003c/strong\u003e With ProtonDB Badges (or any\nplugin that adds to the game page) installed, open such a page and\npress up from \u003cem\u003ePlay\u003c/em\u003e: expect the Stream button first and the other\nplugin's badge after it, as they are drawn, and the same order in\nreverse going down.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cdel\u003e\u003cstrong\u003eFirst press copies a community layout.\u003c/strong\u003e\u003c/del\u003e Superseded by §8\n(nothing copies from the real game any more). With \u003cstrong\u003eno default\nlayout set\u003c/strong\u003e, press \u003cem\u003eStream\u003c/em\u003e on a game whose real entry has a\n\u003ccode\u003eworkshop://\u003c/code\u003e layout: expect the launch with no layout change and no\n\u003ccode\u003elayouts.json\u003c/code\u003e write.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eA hand-picked layout on the hidden entry is never overwritten.\u003c/strong\u003e\nPick a layout on the hidden shortcut (Steam's controller settings\nwhile it streams), set a different default layout\n(§8), press \u003cem\u003eStream\u003c/em\u003e, then \u003cem\u003eSync now\u003c/em\u003e and restart when prompted.\nExpect the hidden shortcut to keep the hand-picked layout after all\nthree, \u003ccode\u003elayouts.json\u003c/code\u003e to read \u003ccode\u003e\"kept\"\u003c/code\u003e with that URL and \u003ccode\u003e\"applied\": null\u003c/code\u003e, the game page's Stream note to read \"own layout\".\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cdel\u003e\u003cstrong\u003eSteam default.\u003c/strong\u003e\u003c/del\u003e Superseded by §8: with a default set, a\nhidden shortcut on Steam's default gets the default layout on its\nfirst press (\u003ccode\u003e\"default\"\u003c/code\u003e); without one, nothing is written.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eOne row, however often the page is opened.\u003c/strong\u003e Open the same owned\ngame's library page several times, backing out to the library and in\nagain between each (and switch to another game's page and back).\nExpect exactly one Stream row every time — never two stacked — and\nno slowdown building up as the page is re-entered. The route patch\nruns \u003ccode\u003eafterPatch\u003c/code\u003e on each render, so a duplicated or accumulating\nrow shows up here and nowhere else.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eNo running-app guard.\u003c/strong\u003e Launch any game, then open another\npublished game's library page and press \u003cem\u003eStream\u003c/em\u003e: expect no toast\nand the Stream shortcut launched as usual (Steam switches to it;\nwith a stream already going, Moonlight's own UI takes over), the\ndefault layout applied and \u003ccode\u003elayouts.json\u003c/code\u003e updated as on any press.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cdel\u003e\u003cstrong\u003eAdvanced toggle.\u003c/strong\u003e\u003c/del\u003e Removed: the toggle is gone (spec Decision\n47). Settings → Advanced shows \u003cem\u003eDefault controller layout\u003c/em\u003e instead\n(§8); \"no default set\" is the off switch.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003ePost-restart layout walk.\u003c/strong\u003e With a default layout set (§8), make a\nsync write shortcuts, restart when prompted, wait up to 90 s after\nSteam is back. Expect \u003ccode\u003elayouts.json\u003c/code\u003e to gain an entry per non-parked\nentry (default / kept / unavailable), the log to show the walk\nstarting, each target, and \"layout walk done\"; the panel to show no\nrestart row afterward.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eA title's layout while it streams.\u003c/strong\u003e Press \u003cem\u003eStream\u003c/em\u003e on a game,\nthen the Steam button: \u003cem\u003eController settings\u003c/em\u003e there must be the hidden\nshortcut's (its title matching the game), and a layout picked there\nmust still be selected at the next stream. The Titles rows have no\n\u003cem\u003eLayout\u003c/em\u003e button (Decision 75); Advanced's \u003cem\u003eLayout\u003c/em\u003e is §15's.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003ePicker strategy.\u003c/strong\u003e Set \u003ccode\u003e\"layout_strategy\": \"picker\"\u003c/code\u003e in\n\u003ccode\u003esettings.json\u003c/code\u003e (§0.4 above) and reload. Expect a Stream press to\nlaunch with no layout change and no \u003ccode\u003elayouts.json\u003c/code\u003e write, the\nAdvanced \u003cem\u003eDefault controller layout\u003c/em\u003e field to read \"Off for this\ndevice…\" with \u003cem\u003eLayout\u003c/em\u003e as its only button (no \u003cem\u003eMake default\u003c/em\u003e, no\n\u003cem\u003eClear\u003c/em\u003e) and the plugin's only layout action, and no walk after a\nsync's restart; remove the key and reload to\nreturn to \u003ccode\u003ecopy\u003c/code\u003e.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eController index by type.\u003c/strong\u003e In the CEF console (or the PR-0 kit)\nwith a Bluetooth pad also paired, run\n\u003ccode\u003eControllerStore.GetControllers().map(c =\u0026gt; [c.nControllerIndex, c.eControllerType, ControllerStore.GetControllerTypeString?.(c.eControllerType)])\u003c/code\u003e\n(the global is \u003ccode\u003eControllerStore\u003c/code\u003e, capital C, on the client probed on\n2026-09-20; the list is empty while no controller is connected).\nExpect the built-in controller not at index 0 when the pad connected\nfirst, its type string \u003ccode\u003e\"controller_steamcontroller_neptune\"\u003c/code\u003e, and\n\u003ccode\u003eeControllerType\u003c/code\u003e \u003ccode\u003e4\u003c/code\u003e; if not 4, correct \u003ccode\u003eDECK_CONTROLLER_TYPE\u003c/code\u003e in\n\u003ccode\u003esrc/lib/layouts.ts\u003c/code\u003e. \u003cstrong\u003eStill open:\u003c/strong\u003e the first probe device was a\nSteamOS box with a separate Steam Controller (index 0, type 10,\n\u003ccode\u003e\"controller_steamcontroller_triton\"\u003c/code\u003e), so this has not been seen on\na Deck.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e5. PR-8: this checklist and \u003ccode\u003einstall.sh\u003c/code\u003e\u003c/h2\u003e\u003ca id=\"user-content-5-pr-8-this-checklist-and-installsh\" class=\"anchor\" aria-label=\"Permalink: 5. PR-8: this checklist and install.sh\" href=\"#5-pr-8-this-checklist-and-installsh\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul class=\"contains-task-list\"\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003e\u003ccode\u003einstall.sh\u003c/code\u003e on a Deck\u003c/strong\u003e: run the\none-liner from a clean Deck (no plugin installed yet), with a\npassword set for the \u003ccode\u003edeck\u003c/code\u003e user (the next item is the Deck without\none). Expect \u003ccode\u003esudo\u003c/code\u003e to ask for it (unzip into \u003ccode\u003e~/homebrew/plugins/\u003c/code\u003e,\nrestart \u003ccode\u003eplugin_loader\u003c/code\u003e),\nthen Moonlight Sync appears in the Quick Access menu's Decky tab,\nand a final line naming the version that actually landed (read from\nthe installed \u003ccode\u003epackage.json\u003c/code\u003e, not the tag asked for). Re-run it;\nexpect it to succeed again (reinstall/overwrite) without asking\nanything unexpected.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003e\u003ccode\u003einstall.sh\u003c/code\u003e on a Deck with no password.\u003c/strong\u003e On a Deck whose\n\u003ccode\u003edeck\u003c/code\u003e user has none (\u003ccode\u003epasswd -S deck\u003c/code\u003e says \u003ccode\u003eNP\u003c/code\u003e; \u003ccode\u003esudo passwd -d deck\u003c/code\u003e gets a Deck that has one back there), run the one-liner.\nExpect the question \u003cem\u003eSet a temporary password for the install?\u003c/em\u003e\nnaming \u003ccode\u003eDecky!\u003c/code\u003e. Answer \u003ccode\u003en\u003c/code\u003e: expect exit 1, \"nothing was installed\",\n\u003ccode\u003epasswd -S deck\u003c/code\u003e still \u003ccode\u003eNP\u003c/code\u003e and no plugin directory. Run it again\nand answer \u003ccode\u003ey\u003c/code\u003e: expect no \u003ccode\u003esudo\u003c/code\u003e prompt at all, no \u003cem\u003eNew password:\u003c/em\u003e\nprompt and no pause after the answer, the install and the\nrestart, \u003cem\u003eRemoved the temporary password\u003c/em\u003e before the final line,\nand \u003ccode\u003epasswd -S deck\u003c/code\u003e back at \u003ccode\u003eNP\u003c/code\u003e afterwards. Run it once more,\nanswer \u003ccode\u003ey\u003c/code\u003e and press Ctrl+C as soon as it says \u003cem\u003eTemporary password\nset\u003c/em\u003e: expect \u003ccode\u003epasswd -S deck\u003c/code\u003e at \u003ccode\u003eNP\u003c/code\u003e again. \u003cstrong\u003eUnmeasured until this is\nwalked:\u003c/strong\u003e that SteamOS's \u003ccode\u003epasswd\u003c/code\u003e takes the new password from a\npipe (run under \u003ccode\u003esetsid\u003c/code\u003e, so with no terminal to ask on) and that\n\u003ccode\u003epasswd -S\u003c/code\u003e answers for one's own account without root (both are\nwhat Decky's installer relies on). Should the pipe not be read, the\nscript says \u003cem\u003esudo did not accept the temporary one\u003c/em\u003e and installs\nnothing.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003e\u003ccode\u003einstall.sh\u003c/code\u003e on a device without Decky Loader.\u003c/strong\u003e On a device\nthat has none (no \u003ccode\u003e~/homebrew/services/PluginLoader\u003c/code\u003e), run the\none-liner. Expect \u003cem\u003eDecky Loader is not installed\u003c/em\u003e and the question\n\u003cem\u003eInstall Decky Loader now?\u003c/em\u003e before anything is downloaded. Answer\n\u003ccode\u003en\u003c/code\u003e: expect exit 1, \"nothing was installed\", no \u003ccode\u003esudo\u003c/code\u003e prompt and\nno \u003ccode\u003e~/homebrew\u003c/code\u003e. Run it again and answer \u003ccode\u003ey\u003c/code\u003e: expect Decky's own\ninstaller's output (\u003cem\u003eInstalling Steam Deck Plugin Loader\nrelease...\u003c/em\u003e), \u003cem\u003eDecky Loader is installed.\u003c/em\u003e, then the plugin's\ninstall as in the items above (on a Deck with no password, the\ntemporary-password question comes between the two downloads and\nDecky's installer), and at the end the two lines saying the Decky\ntab appears once Steam has restarted. Return to Gaming Mode:\nexpect the Decky tab with Moonlight Sync in it. Run the one-liner\nonce more: expect no question about Decky Loader. \u003cstrong\u003eUnmeasured\nuntil this is walked:\u003c/strong\u003e that Decky's \u003ccode\u003einstall_release.sh\u003c/code\u003e runs to\nthe end as \u003ccode\u003esudo bash \u0026lt;file\u0026gt;\u003c/code\u003e with stdin from \u003ccode\u003e/dev/null\u003c/code\u003e (it is\ndocumented as \u003ccode\u003ecurl … | sh\u003c/code\u003e, where it re-executes itself under\nsudo), and that a return to Gaming Mode is enough for the tab to\nappear.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe README's manual install.\u003c/strong\u003e With the plugin not installed,\nfollow README \"Manual install\": Decky's settings → General →\n\u003cem\u003eDeveloper mode\u003c/em\u003e on, then Developer → \u003cem\u003eInstall Plugin from ZIP\nFile\u003c/em\u003e → \u003cem\u003eBrowse\u003c/em\u003e, choose a release's \u003ccode\u003eMoonlight-Sync.zip\u003c/code\u003e, confirm\nDecky's dialog. Expect Moonlight Sync in the Decky tab with no\nrestart, and Settings → About showing that release. Do it again\nover the installed copy: expect Decky to ask to reinstall or\noverwrite, and the plugin to reload. \u003cstrong\u003eUnmeasured until this is\nwalked:\u003c/strong\u003e the labels as the README spells them.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eA missing release is explained.\u003c/strong\u003e Run with\n\u003ccode\u003eMOONLIGHT_SYNC_VERSION=v9.9.9\u003c/code\u003e; expect\n\u003ccode\u003einstall.sh: could not download …/Moonlight-Sync.zip (is v9.9.9 released?)\u003c/code\u003e on stderr, exit 1 and nothing written to\n\u003ccode\u003e~/homebrew/plugins/\u003c/code\u003e.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003e\u003ccode\u003eMOONLIGHT_SYNC_VERSION\u003c/code\u003e pinning.\u003c/strong\u003e Run with\n\u003ccode\u003eMOONLIGHT_SYNC_VERSION=v0.1.0 sh install.sh\u003c/code\u003e (or the exact tag);\nexpect the version-specific download path, not \u003ccode\u003elatest/download\u003c/code\u003e.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eUninstall.\u003c/strong\u003e Follow the README's \"Uninstall\" section\n(\u003ccode\u003esudo rm -rf ~/homebrew/plugins/\"Moonlight Sync\"\u003c/code\u003e + restart\n\u003ccode\u003eplugin_loader\u003c/code\u003e); expect the Quick Access entry to disappear and\n\u003ccode\u003e~/.local/bin/moonlight-steam-sync\u003c/code\u003e (the CLI) to be untouched.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e6. Host apps: the panel buttons replace the tiles (spec 3.14)\u003c/h2\u003e\u003ca id=\"user-content-6-host-apps-the-panel-buttons-replace-the-tiles-spec-314\" class=\"anchor\" aria-label=\"Permalink: 6. Host apps: the panel buttons replace the tiles (spec 3.14)\" href=\"#6-host-apps-the-panel-buttons-replace-the-tiles-spec-314\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eNeeds a host that publishes \u003ccode\u003eDesktop\u003c/code\u003e and \u003ccode\u003eSteam Big Picture\u003c/code\u003e under those\nnames, and ideally an install from before this change: two visible tiles,\none of them with a controller layout chosen on it. Note each tile's appid\nfirst (\u003ccode\u003emoonlight-steam-sync --json status\u003c/code\u003e).\u003c/p\u003e\n\u003cul class=\"contains-task-list\"\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe first sync hides the two tiles in place.\u003c/strong\u003e Upgrade the plugin,\npress \u003cem\u003eSync now\u003c/em\u003e. Expect the plan to count them as hidden, never as\nreplaced (the log's CLI line says \u003ccode\u003eN to hide\u003c/code\u003e; no \u003ccode\u003ereplace\u003c/code\u003e event for\neither name), one Steam restart, and afterwards no \u003ccode\u003eDesktop\u003c/code\u003e /\n\u003ccode\u003eSteam Big Picture\u003c/code\u003e tile anywhere in the library, non-Steam\ncollection included.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eSame appid, nothing lost.\u003c/strong\u003e \u003ccode\u003e--json status --hide-host-apps\u003c/code\u003e shows\nboth entries with the appids noted above, \u003ccode\u003ehidden: true\u003c/code\u003e,\n\u003ccode\u003eparked: false\u003c/code\u003e, \u003ccode\u003ehost_app: true\u003c/code\u003e, and their artwork slots still\nfilled; no image was downloaded for them by that sync.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eBoth panel buttons launch.\u003c/strong\u003e \u003cem\u003eDesktop\u003c/em\u003e and \u003cem\u003eSteam Big Picture\u003c/em\u003e each\nstart their stream from the Quick Access panel; Recent Games and the\nin-game overlay show the entry with its artwork.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cdel\u003e\u003cstrong\u003eEach layout button opens the configurator.\u003c/strong\u003e\u003c/del\u003e Removed: the\npanel has no layout button per host app any more (spec Decision\n67), nor on the Titles page (Decision 75); a host app's layout is\npicked from Steam's controller settings while it streams, and the\none \u003cem\u003eLayout\u003c/em\u003e button, Advanced's since Decision 77, is the Moonlight\nentry's (§15).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eA layout chosen before the upgrade is still selected\u003c/strong\u003e (same\nappid): it applies in the stream, and Steam's controller settings\nduring the stream show it.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eWhile a game runs\u003c/strong\u003e the two launch buttons still work: a press\nruns the host app's shortcut as usual.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eOn a client without \u003ccode\u003eSteamClient.Apps.ShowControllerConfigurator\u003c/code\u003e\u003c/strong\u003e\n(if one turns up) Advanced's \u003cem\u003eDefault controller layout\u003c/em\u003e row has\n\u003cem\u003eMake default\u003c/em\u003e and \u003cem\u003eClear\u003c/em\u003e only.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eCounters.\u003c/strong\u003e Do this one \u003cem\u003ebefore\u003c/em\u003e the first sync: \u003ccode\u003estatus\u003c/code\u003e carries\nthe flag from the upgrade on, so the two still-visible tiles already\nread \u003ccode\u003ehost_app: true\u003c/code\u003e. Right after the upgrade \u003cem\u003eShortcuts\u003c/em\u003e is two\nlower than before (and \u003cem\u003eUnmatched\u003c/em\u003e lower by however many of the two\nhad no match); \u003cem\u003eStream buttons\u003c/em\u003e is unchanged. The sync that hides the\ntwo then moves none of the three.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eTitles page.\u003c/strong\u003e Both rows read \u003cstrong\u003ehost app\u003c/strong\u003e, show under \u003cem\u003eAll\u003c/em\u003e only,\nand have \u003cem\u003eChange match\u003c/em\u003e and \u003cem\u003eIgnore\u003c/em\u003e (no \u003cem\u003eLayout\u003c/em\u003e, as on every row).\nIn \u003cem\u003eChange match\u003c/em\u003e every result reads \u003cstrong\u003eart only\u003c/strong\u003e,\nincluding a game this account owns; pin one, sync, and expect the\nentry to stay a hidden host app with new artwork and no Stream button\non that game's library page.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eNo layout copied onto them.\u003c/strong\u003e \u003cem\u003e(Written for the layout copy that\nspec 3.16 replaced; §8 covers them now.)\u003c/em\u003e Without a default layout\nset, after a sync's restart the layout walk never touches the two\nentries (\u003ccode\u003elayouts.json\u003c/code\u003e has no \u003ccode\u003ecopied\u003c/code\u003e / \u003ccode\u003ekept\u003c/code\u003e / \u003ccode\u003eunavailable\u003c/code\u003e\nrecord for their appids; a \u003ccode\u003epicker\u003c/code\u003e record from an older version's\n\u003cem\u003eChoose layout…\u003c/em\u003e may stay). With a default set they get it, like\nevery other entry (§8).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eIgnore removes the button.\u003c/strong\u003e Ignore \u003ccode\u003eDesktop\u003c/code\u003e on the Titles page and\nsync: the entry is removed and the panel's \u003cem\u003eDesktop\u003c/em\u003e row is gone.\nUnignore and sync: it comes back, hidden from the start.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eSwitching hosts.\u003c/strong\u003e With a second host that does not publish them,\nthe two entries are parked (counted under parked, buttons gone); back\non the first host they are unparked \u003cem\u003ehidden\u003c/em\u003e, never as tiles.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eGetting the tiles back\u003c/strong\u003e (README, \"The Quick Access panel\"): the\nfull command there (\u003ccode\u003e--owned-apps\u003c/code\u003e, \u003ccode\u003e--ignore-file\u003c/code\u003e,\n\u003ccode\u003e--client-shortcut\u003c/code\u003e, \u003ccode\u003e--park-unpublished\u003c/code\u003e, no \u003ccode\u003e--hide-host-apps\u003c/code\u003e), run\nfrom a terminal, shows the two tiles again with art and layout intact\nand changes nothing else: every Stream button stays hidden, no ignored\ntitle comes back, the Moonlight client entry stays. The next \u003cem\u003eSync\nnow\u003c/em\u003e hides the two again.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e7. Hidden shortcuts and the Streaming collection (spec 3.15)\u003c/h2\u003e\u003ca id=\"user-content-7-hidden-shortcuts-and-the-streaming-collection-spec-315\" class=\"anchor\" aria-label=\"Permalink: 7. Hidden shortcuts and the Streaming collection (spec 3.15)\" href=\"#7-hidden-shortcuts-and-the-streaming-collection-spec-315\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eNone of \u003ccode\u003ecollectionStore.SetAppsAsHidden\u003c/code\u003e, \u003ccode\u003eBIsHidden\u003c/code\u003e, \u003ccode\u003euserCollections\u003c/code\u003e,\n\u003ccode\u003eNewUnsavedCollection\u003c/code\u003e, \u003ccode\u003eAsDragDropCollection\u003c/code\u003e or a collection's \u003ccode\u003eSave\u003c/code\u003e /\n\u003ccode\u003eDelete\u003c/code\u003e was probed before this was built; every item here is a first\nmeasurement. \u003cstrong\u003eSpec 3.17 changed the collection items below:\u003c/strong\u003e the\ncollection now exists only with \u003cem\u003eHide Stream shortcuts\u003c/em\u003e off, beside the tab, and holds\nshortcuts only; §10 has the items that replace the collection ones here,\nand the hiding items still stand.\u003c/p\u003e\n\u003cul class=\"contains-task-list\"\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e In the CEF console: \u003ccode\u003etypeof collectionStore.SetAppsAsHidden\u003c/code\u003e,\n\u003ccode\u003etypeof collectionStore.BIsHidden\u003c/code\u003e, \u003ccode\u003etypeof collectionStore.NewUnsavedCollection\u003c/code\u003e are all \u003ccode\u003e\"function\"\u003c/code\u003e, and\nSettings → Advanced shows both toggles enabled (a disabled toggle\nsays the client has no such call).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e After the plugin loads with an existing install: the \u003cem\u003eNon-Steam\u003c/em\u003e tab\nholds only the visible shortcuts (the panel's \u003cstrong\u003eShortcuts\u003c/strong\u003e counter),\nno Stream-button shortcut, no \u003ccode\u003eMoonlight\u003c/code\u003e, no \u003ccode\u003eDesktop\u003c/code\u003e / \u003ccode\u003eSteam Big Picture\u003c/code\u003e, no parked title. They are all under Steam's \u003cem\u003eHidden\u003c/em\u003e.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e A \u003cstrong\u003eStreaming\u003c/strong\u003e collection exists and its size is the panel's\n\u003cstrong\u003eStream buttons\u003c/strong\u003e + \u003cstrong\u003eShortcuts\u003c/strong\u003e. An owned game in it opens the real\ngame's page (with the Stream button); an unowned one is the shortcut.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e A Stream press still launches the (now hidden) shortcut, and the\npanel's \u003cem\u003eOpen Moonlight\u003c/em\u003e, \u003cem\u003eDesktop\u003c/em\u003e and \u003cem\u003eSteam Big Picture\u003c/em\u003e still work.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cem\u003eHide Stream shortcuts\u003c/em\u003e off: the Stream-button shortcuts appear under\n\u003cem\u003eNon-Steam\u003c/em\u003e within a moment, the other hidden entries do not, and the\ncollection is unchanged. Stream a game, quit: its shortcut is at the\nfront of Home. On again: they are gone again.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cem\u003eStreaming collection\u003c/em\u003e off: the collection is deleted. On: it is\nback, complete.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e Sync after the host gains a title: once Steam has restarted, the new\nStream shortcut is hidden and the game is in the collection without\nopening the panel (allow 90 s). Note whether the tile is visible for\na moment first.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e Switch hosts and sync: the first host's visible tiles are hidden\n(parked) and leave the collection; switching back shows them again.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cem\u003eRemove everything\u003c/em\u003e: after the restart the collection is gone and\nSteam's \u003cem\u003eHidden\u003c/em\u003e holds none of the plugin's entries.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e After a host switch (or an \u003cem\u003eIgnore\u003c/em\u003e + sync) that removes a visible\nshortcut: the collection's \u003ccode\u003eallApps\u003c/code\u003e no longer lists its appid (the\nplugin cannot remove a member whose overview is gone, so the client\nhas to drop it itself).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e The first sync on a fresh install creates the collection with every\nmember in it (the port saves a new collection empty, then adds).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e Open the panel and the Titles page a few times: nothing in the\nlibrary flickers and \u003ccode\u003ecollectionStore\u003c/code\u003e is not written again (the\nreconcile only applies differences).\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e8. The default controller layout (spec 3.16)\u003c/h2\u003e\u003ca id=\"user-content-8-the-default-controller-layout-spec-316\" class=\"anchor\" aria-label=\"Permalink: 8. The default controller layout (spec 3.16)\" href=\"#8-the-default-controller-layout-spec-316\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThe plugin never reads the real game's layout any more: one layout you\nadopt from the Moonlight entry with Advanced's \u003cem\u003eMake default\u003c/em\u003e (the one\nplace since Decision 74: the Titles rows and the library gear menu no\nlonger offer it, and since Decision 77 the panel does not either) is put on every entry the plugin manages, and a\nselection the plugin did not make is never overwritten (\u003ccode\u003eapplied\u003c/code\u003e in\n\u003ccode\u003elayouts.json\u003c/code\u003e is what the plugin last set per shortcut). Two things here\nare unmeasured on any device: whether a \u003ccode\u003etemplate://\u003c/code\u003e layout sticks on\nother titles, and the name and arguments of the call that clears a\nselection (\u003ccode\u003eSteamClient.Input.ClearSelectedConfigForApp(appid, controllerIndex)\u003c/code\u003e, spec 3.16.3 \u003cstrong\u003e[verify]\u003c/strong\u003e). Do this section with a\ncontroller connected; \u003cem\u003eMake default\u003c/em\u003e answers \"Connect a controller first\"\notherwise.\u003c/p\u003e\n\u003cul class=\"contains-task-list\"\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eAdopt a community layout.\u003c/strong\u003e Give the Moonlight entry a\n\u003ccode\u003eworkshop://\u003c/code\u003e community layout (Advanced's \u003cem\u003eLayout\u003c/em\u003e), then press\n\u003cem\u003eMake default\u003c/em\u003e beside it. Expect\na confirm titled \u003ccode\u003eUse “\u0026lt;layout title\u0026gt;” as the default layout?\u003c/code\u003e; after\nOK, a toast \u003ccode\u003eDefault layout applied to N titles\u003c/code\u003e (N = every non-parked\nentry that had no layout of its own, host apps and the Moonlight entry\nincluded) plus \u003ccode\u003e · M kept their own\u003c/code\u003e for the ones that had; Settings →\nAdvanced → \u003cem\u003eDefault controller layout\u003c/em\u003e reading \u003ccode\u003e\u0026lt;title\u0026gt; · Workshop layout · set today …\u003c/code\u003e; \u003ccode\u003elayouts.json\u003c/code\u003e with \u003ccode\u003e\"default\"\u003c/code\u003e and \u003ccode\u003e\"applied\"\u003c/code\u003e\nset to the URL for each. A fresh title (a game the host just started\npublishing, after its sync and restart) streams on it without any\npress first.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eNo other place adopts.\u003c/strong\u003e The Titles rows have no \u003cem\u003eLayout\u003c/em\u003e button\n(Decision 75), a streamed game's gear menu has no \u003cem\u003eUse as\nMoonlight Sync default layout\u003c/em\u003e (Decision 74), and the Quick Access\npanel has no layout row (Decision 77).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eAdopt a \u003ccode\u003etemplate://\u003c/code\u003e layout [verify].\u003c/strong\u003e Pick one of Steam's own\ntemplates on the Moonlight entry, adopt it. Expect the same toast; check\n\u003ccode\u003elayouts.json\u003c/code\u003e: every other entry must read \u003ccode\u003e\"default\"\u003c/code\u003e with the\n\u003ccode\u003etemplate://\u003c/code\u003e URL. If they read \u003ccode\u003e\"unavailable\"\u003c/code\u003e instead (the URL did\nnot stick when read back), report it: \u003ccode\u003etemplate://\u003c/code\u003e then has to come\nout of \u003ccode\u003eDEFAULT_LAYOUT_SCHEMES\u003c/code\u003e (spec 3.16.1, Decision 49), a\nFable-class decision.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eAn edited-in-place layout is refused.\u003c/strong\u003e Edit the Moonlight\nentry's layout in Steam's layout screen without exporting it (it\nreads \u003ccode\u003eautosave:///…\u003c/code\u003e), then \u003cem\u003eMake default\u003c/em\u003e. Expect only the toast\n\u003ccode\u003eThis layout was edited in place and only exists for “Moonlight”. In Steam's layout screen choose Export, select the exported copy, then try again.\u003c/code\u003e, no confirm, no change.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eAn exported personal layout works.\u003c/strong\u003e Export that layout, select the\nexported copy on the Moonlight entry (it now reads \u003ccode\u003eworkshop://…\u003c/code\u003e),\nadopt it: expect the normal confirm and toast.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eNo layout is refused.\u003c/strong\u003e With the Moonlight entry never given a\nlayout (reads \u003ccode\u003edefault://…\u003c/code\u003e, or a \u003ccode\u003etemplate://\u003c/code\u003e Steam only offers),\nexpect the toast \u003ccode\u003e“Moonlight” has no layout chosen yet. Use Layout first.\u003c/code\u003e (§15 has the same item.)\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eHand-picked survives, plugin-applied moves.\u003c/strong\u003e With a default set,\npick a different layout by hand on one title (Steam's controller\nsettings while it streams), then give\nthe Moonlight entry another layout and adopt it as a \u003cem\u003esecond\u003c/em\u003e default.\nExpect the hand-picked title unchanged (\u003ccode\u003e\"kept\"\u003c/code\u003e, \u003ccode\u003e\"applied\": null\u003c/code\u003e,\nStream note \"own layout\"), every plugin-applied title on the new default, the\nMoonlight entry (the source) unchanged, and the toast's \u003ccode\u003eM kept their own\u003c/code\u003e counting the hand-picked one and the source.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eA pre-upgrade copied title moves.\u003c/strong\u003e On an install upgraded from a\nversion that copied layouts from the real game (\u003ccode\u003elayouts.json\u003c/code\u003e has\n\u003ccode\u003e\"copied\"\u003c/code\u003e records without an \u003ccode\u003e\"applied\"\u003c/code\u003e key), set a default: expect\nthose titles to move to it (Decision 46) as long as their selection is\nstill the copied URL.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eClear [verify].\u003c/strong\u003e Settings → Advanced → \u003cem\u003eClear\u003c/em\u003e: expect the confirm\n\u003ccode\u003eClear the default layout?\u003c/code\u003e, then the toast \u003ccode\u003eDefault layout cleared · removed from N titles\u003c/code\u003e. Every plugin-applied title must be back on\nSteam's default (the layout screen shows nothing selected; the row\nreads \"Steam default\", \u003ccode\u003e\"applied\": null\u003c/code\u003e), and hand-picked ones must\nkeep their layout. In the CEF console first check \u003ccode\u003etypeof SteamClient.Input.ClearSelectedConfigForApp\u003c/code\u003e: if it is not\n\u003ccode\u003e\"function\"\u003c/code\u003e, the toast reads \u003ccode\u003eDefault layout cleared. This Steam client cannot unset a layout, so titles keep the one they have.\u003c/code\u003e and\nnothing changes on any title; if it is a function but titles are not\nback on Steam's default afterwards (\u003ccode\u003e\"unavailable\"\u003c/code\u003e with the old URL\nin \u003ccode\u003elayouts.json\u003c/code\u003e), the call's name or arguments are wrong: report\nit (spec 3.16.3), do not guess at another.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eA sync's new titles get it after the restart.\u003c/strong\u003e With a default\nset, add a title on the host, \u003cem\u003eSync now\u003c/em\u003e, restart: within 90 s the new\nshortcut is on the default (\u003ccode\u003elayouts.json\u003c/code\u003e, and the layout screen).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe host apps, the client entry and a visible shortcut get it.\u003c/strong\u003e\nAfter any of the above, check \u003ccode\u003elayouts.json\u003c/code\u003e for \u003ccode\u003eDesktop\u003c/code\u003e, \u003ccode\u003eSteam Big Picture\u003c/code\u003e, \u003ccode\u003eMoonlight\u003c/code\u003e and a visible (non-Stream) shortcut:\n\u003ccode\u003e\"default\"\u003c/code\u003e with \u003ccode\u003e\"real_appid\": null\u003c/code\u003e for the first three.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eNot held back.\u003c/strong\u003e Start a game, then adopt a default from the panel\nor clear it from Advanced: it goes through (the toast appears) while the game\nruns; a Stream press is still refused meanwhile.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e9. Fixtures vs. real CLI\u003c/h2\u003e\u003ca id=\"user-content-9-fixtures-vs-real-cli\" class=\"anchor\" aria-label=\"Permalink: 9. Fixtures vs. real CLI\" href=\"#9-fixtures-vs-real-cli\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul class=\"contains-task-list\"\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e Run, on the device, against the real installed CLI:\n\u003ccode\u003emoonlight-steam-sync --json list --hide-host-apps\u003c/code\u003e, \u003ccode\u003e--json status --hide-host-apps\u003c/code\u003e (the plugin always passes that flag, and the\nfixtures carry its keys: \u003ccode\u003ekind: \"host-app\"\u003c/code\u003e, \u003ccode\u003eentry.host_app\u003c/code\u003e), \u003ccode\u003e--json host show\u003c/code\u003e, \u003ccode\u003e--json search \"Portal\"\u003c/code\u003e. Diff each event's key set against\nthe corresponding fixture under \u003ccode\u003etests/fixtures/common/\u003c/code\u003e (§3.6.3 of\nthe spec: \u003ccode\u003etests/test_fixtures.py\u003c/code\u003e checks the fixtures against the\nschema, but never against a real CLI run). A mismatch means the CLI\nand the plugin have drifted from the spec's §3.4.6 schema on one side\nor the other — escalate to a Fable-class model rather than editing\neither side ad hoc.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e10. The Streaming tab (spec 3.17)\u003c/h2\u003e\u003ca id=\"user-content-10-the-streaming-tab-spec-317\" class=\"anchor\" aria-label=\"Permalink: 10. The Streaming tab (spec 3.17)\" href=\"#10-the-streaming-tab-spec-317\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThe \u003ccode\u003e/library\u003c/code\u003e route's render tree was measured on 2026-09-22 through the\nCEF debugger (route element → the library home, which reads the route's\ntab id → the library page, \u003ccode\u003etab\u003c/code\u003e prop, which builds its tab array in a\n\u003ccode\u003euseMemo\u003c/code\u003e and validates the requested id against it in its own render →\nthe tabbed page \u003ccode\u003e{tabs, activeTab, onShowTab, …}\u003c/code\u003e behind an observer\nwrapper → the tab row, which renders and cycles over \u003ccode\u003etabs\u003c/code\u003e; a built-in\ntab is \u003ccode\u003e{id, title, renderTabAddon, content, footer}\u003c/code\u003e). The patch still\ndigs for the tab bar's \u003ccode\u003etabs\u003c/code\u003e array (four component levels at most) so a\nclient that adds a level keeps working, and clones a built-in tab's grid\nelement with a synthetic collection whose surface is a guess at the\nclient's \u003ccode\u003eCollection\u003c/code\u003e. The items after the first two are still first\nmeasurements.\u003c/p\u003e\n\u003cul class=\"contains-task-list\"\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e With an install that has synced: the library's tab bar has a\n\u003cstrong\u003eStreaming\u003c/strong\u003e tab after \u003cem\u003eNon-Steam\u003c/em\u003e, and it opens. If there is no\ntab, note the CEF console: \u003ccode\u003eMoonlight Sync: could not patch the library tabs\u003c/code\u003e names a throw; silence means the walk never found a\n\u003ccode\u003etabs\u003c/code\u003e array (report the route element's shape: \u003ccode\u003etypeof route.children.type\u003c/code\u003e, whether \u003ccode\u003eprops.renderFunc\u003c/code\u003e exists).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e The tab can be \u003cstrong\u003ereached\u003c/strong\u003e: L1/R1 from \u003cem\u003eNon-Steam\u003c/em\u003e lands on it, and\nmoving along the tab row with the D-pad stops on it. Found\n2026-09-22: both asked the library for it (\u003ccode\u003eonShowTab\u003c/code\u003e navigates\nto \u003ccode\u003e/library/tab/MoonlightSyncStreaming\u003c/code\u003e), and the page answered\nwith its first tab, since it validates the id against its own tab\narray in its render, before the injection; \u003ccode\u003eactiveTabFor\u003c/code\u003e puts the\nid back on the bar element. If it is skipped again, report from\nthe CEF console the one \u003ccode\u003eMoonlight Sync: library tabs found on …\u003c/code\u003e\nline (the bar's prop names, \u003ccode\u003eactiveTab\u003c/code\u003e, a built-in tab's keys, the\ntemplate's type and prop names) and whether a \u003ccode\u003eMoonlight Sync: library tabs, onShowTab(\"MoonlightSyncStreaming\")\u003c/code\u003e line appears\nwhen you try: none means the bar never asked for it; one that does\nnot open it means the page's \u003ccode\u003etab\u003c/code\u003e prop is no longer the requested\nid (\u003ccode\u003erequestedTab\u003c/code\u003e in \u003ccode\u003elibraryTabs.tsx\u003c/code\u003e).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e The tab's own panels take focus: with no host, or before a sync,\nthe \u003cem\u003eNo host yet\u003c/em\u003e / \u003cem\u003eNothing to stream\u003c/em\u003e line gets the focus ring.\nThey are a \u003ccode\u003eFocusable\u003c/code\u003e with \u003ccode\u003efocusableIfNoChildren\u003c/code\u003e and a no-op\n\u003ccode\u003eonActivate\u003c/code\u003e (both unmeasured); Settings → About's log panel is a\nplain \u003ccode\u003eFocusable\u003c/code\u003e over text, so whether \u003cem\u003eit\u003c/em\u003e takes focus says\nwhether a plain one already suffices.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e The tab's tiles are the panel's \u003cstrong\u003eStream buttons\u003c/strong\u003e + \u003cstrong\u003eShortcuts\u003c/strong\u003e:\nan owned game opens the real game's page (with the Stream button),\nan unowned one is the shortcut. Sorting, the footer legend and the\nfocus ring behave like \u003cem\u003eNon-Steam\u003c/em\u003e's. A blank tab, or a line that\nstarts \u003cem\u003eMoonlight Sync could not draw this tab on this client\u003c/em\u003e\n(the plugin's own error boundary; the console has the same error\nwith its component stack), means the grid read something the\nsynthetic collection lacks: report the console error, do not guess\nat the field.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e Steam's own tabs are unchanged, and Settings → Moonlight Sync's\ntab bar has no Streaming tab (the injection only takes a tab bar\nsome tab of which renders a collection).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e Nothing remounts: switching between library tabs and back does not\nflash or reload the grid (each patched component type is cached).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e The tab is not a collection: \u003cem\u003eCollections\u003c/em\u003e has no \u003cem\u003eStreaming\u003c/em\u003e entry,\nand on a second machine signed into the account nothing appears.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eUpgrade from v0.4.0\u003c/strong\u003e: the old \u003cem\u003eStreaming\u003c/em\u003e collection loses this\ndevice's owned games and shortcuts at the first load and is gone\nonce empty; a member the plugin never made (add one by hand first)\nstays, and so does the collection.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e After a sync that adds or removes a title, the tab follows without\nreopening the library (its content reads the store).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cem\u003eHide Stream shortcuts\u003c/em\u003e off: the Streaming tab stays (its tiles\nunchanged) and a \u003cem\u003eStreaming\u003c/em\u003e collection appears within a moment\nwith the Stream shortcuts and the visible shortcuts, no owned game.\nOn again: the collection and its shortcuts stay (hidden, so the\ncollection looks empty in the library), and the tab is untouched.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cem\u003eStreaming tab\u003c/em\u003e off: the tab is gone (leave the library and come\nback) and this device's shortcuts leave the collection, parked ones\ntoo (deleted once empty). On: both back.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e The first sync with the collection in effect on a fresh install\ncreates it with every member in it: whether the client lists a\njust-added member at once is unmeasured, so the reconcile never\ndeletes on the pass that added; if the collection is there but\nempty after that pass, the next reconcile (open the Titles page)\nmust \u003cstrong\u003enot\u003c/strong\u003e delete it.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e Switch hosts and sync: the tab shows the new host's titles only.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cem\u003eRemove everything\u003c/em\u003e: after the restart the tab reads \"Nothing to\nstream from … yet\" and, with the collection in effect, the\ncollection is gone once the client has dropped the removed\nshortcuts.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e Unload the plugin (Decky → the plugin's menu → Uninstall, or a\nreload): leave the library and come back: the tab is gone, and the\nlibrary still works if it had been the active tab.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e11. Wake-on-LAN (spec 3.18)\u003c/h2\u003e\u003ca id=\"user-content-11-wake-on-lan-spec-318\" class=\"anchor\" aria-label=\"Permalink: 11. Wake-on-LAN (spec 3.18)\" href=\"#11-wake-on-lan-spec-318\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThe MAC source and the packet were tested off-device against a\nhand-written \u003ccode\u003eMoonlight.conf\u003c/code\u003e and a recording socket (\u003ccode\u003etests/test_wake.py\u003c/code\u003e);\nthe Deck's own file, the network and the PC's firmware are what these\ncheck. Two things ran on the SteamOS box on 2026-09-23: its \u003ccode\u003eMoonlight.conf\u003c/code\u003e\ncarried the MAC as a \u003cem\u003equoted\u003c/em\u003e \u003ccode\u003e@ByteArray\u003c/code\u003e (a raw byte of the MAC was a\ncomma), which the parser then read as unknown (fixed, with that line's\nshape as a test); and a packet capture on the same LAN showed Moonlight's\nown \u003ccode\u003elist\u003c/code\u003e sending a magic packet with the PC's MAC, which is what\nDecision 66 answers.\u003c/p\u003e\n\u003cul class=\"contains-task-list\"\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eMoonlight's MAC is read.\u003c/strong\u003e With the host paired in the Flathub\nMoonlight, the panel's host row shows \u003cstrong\u003eWake\u003c/strong\u003e beside \u003cstrong\u003eCheck\u003c/strong\u003e\nwhile the host is not known reachable. If there is no Wake, open\n\u003ccode\u003e~/.var/app/com.moonlight_stream.Moonlight/config/Moonlight Game Streaming Project/Moonlight.conf\u003c/code\u003e and note the host's \u003ccode\u003emac=\u003c/code\u003e line:\n\u003ccode\u003e@ByteArray()\u003c/code\u003e (quoted or not) means Moonlight has none (a Sunshine\nhost that reports no MAC; there is no field to enter one since\n2026-09-26); anything else that still gives no Wake is a parser gap:\npaste the line's shape (hex masked) into an issue.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eWake appears and sends.\u003c/strong\u003e Shut the PC down (a sleep state\nWake-on-LAN is enabled for). Open the panel: \u003cstrong\u003eCheck\u003c/strong\u003e and \u003cstrong\u003eWake\u003c/strong\u003e are side by\nside, under the host row's cached count when there are several hosts (the PC stays\ndown: nothing was asked). Press Wake: a toast \"Wake-on-LAN packet\nsent to . Give it a minute, then Check.\" and, in\n\u003ccode\u003emoonlight-sync.log\u003c/code\u003e, \u003ccode\u003ewake_host \u0026lt;host\u0026gt;: N packets sent (moonlight MAC)\u003c/code\u003e with N = (1 + the host's addresses) × 8. The PC wakes; Check\nafter a minute goes green.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eCheck wakes it too.\u003c/strong\u003e Shut the PC down again; press Check instead:\nMoonlight's own \u003ccode\u003elist\u003c/code\u003e sends the packet, so the row goes red first\n(the 30 s seek times out) and the PC comes up anyway; Check again\nafter a minute goes green.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eNo MAC in Moonlight:\u003c/strong\u003e the row shows Check alone, and Settings →\nHost has no MAC field (removed 2026-09-26). A \u003ccode\u003ewake_macs\u003c/code\u003e key left\nin \u003ccode\u003esettings.json\u003c/code\u003e by an older build changes nothing: no Wake, no\n\u003ccode\u003e(settings MAC)\u003c/code\u003e in the log.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e12. The on/off toggle (spec 3.19)\u003c/h2\u003e\u003ca id=\"user-content-12-the-onoff-toggle-spec-319\" class=\"anchor\" aria-label=\"Permalink: 12. The on/off toggle (spec 3.19)\" href=\"#12-the-onoff-toggle-spec-319\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eOff-device the controller is covered (\u003ccode\u003econtroller.test.ts\u003c/code\u003e, \"the on/off\ntoggle\"); what the client does with a hidden visible shortcut, the tab\nremoved from a bar it was in, and a Stream row on a page already open\nare what these check. Start with the plugin on, synced, with \u003cem\u003eHide\nStream shortcuts\u003c/em\u003e \u003cstrong\u003eoff\u003c/strong\u003e (so there are visible shortcuts and a\n\u003cem\u003eStreaming\u003c/em\u003e collection to watch).\u003c/p\u003e\n\u003cul class=\"contains-task-list\"\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eOff hides everything.\u003c/strong\u003e Panel → \u003cem\u003eEnable Sync\u003c/em\u003e off. The panel\nshows the toggle alone. In the library: no Moonlight shortcut under\n\u003cem\u003eNon-Steam\u003c/em\u003e (the visible ones and the \"Moonlight\" client entry are\ngone too), no \u003cem\u003eStreaming\u003c/em\u003e tab in the bar, and the \u003cem\u003eStreaming\u003c/em\u003e\ncollection is gone (or holds only entries another device put\nthere). An owned game's page has no Stream row. A page that was open\nwhen the toggle flipped loses its row on the next render (open\nanother game and come back).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eSettings are locked.\u003c/strong\u003e The panel's header buttons and \u003cem\u003eSettings\u003c/em\u003e\nopen a route with the toggle alone: no Host / Artwork / Titles /\nAdvanced / Updates / About.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eA reboot keeps it off.\u003c/strong\u003e Restart Steam: the panel opens on the\ntoggle alone and the library is still clean (the load's reconcile\nhides every entry again; the log has no \u003ccode\u003echeck_host\u003c/code\u003e).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eOn restores.\u003c/strong\u003e Toggle on: the shortcuts are back where the\nsettings put them (Stream entries hidden or shown per \u003cem\u003eHide Stream\nshortcuts\u003c/em\u003e, the client and host apps hidden), the \u003cem\u003eStreaming\u003c/em\u003e tab\nand collection are back, the Stream row and the menu item are\nback, the host row goes green or red. If a sync's restart happened\njust before the toggle went off, the layout toast fires now.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eBusy.\u003c/strong\u003e Start a sync, open the panel while it runs: the progress\nview has no toggle; when it ends the toggle is live again.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e13. Reset match cache (Advanced)\u003c/h2\u003e\u003ca id=\"user-content-13-reset-match-cache-advanced\" class=\"anchor\" aria-label=\"Permalink: 13. Reset match cache (Advanced)\" href=\"#13-reset-match-cache-advanced\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eOff-device the backend (\u003ccode\u003etest_backend.py\u003c/code\u003e, the \u003ccode\u003ereset_match_cache\u003c/code\u003e block)\nand the controller (\u003ccode\u003econtroller.test.ts\u003c/code\u003e, \"Advanced → Reset match cache\")\nare covered; what a real cache and a real CLI do with the reset is what\nthese check. Start synced, with at least one pin (Titles → Change match)\nand one title reading \"no match\".\u003c/p\u003e\n\u003cul class=\"contains-task-list\"\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe file goes, and the toast counts it.\u003c/strong\u003e Note\n\u003ccode\u003e~/.cache/moonlight-steam-sync/matches.json\u003c/code\u003e (over SSH: the \u003ccode\u003etitles\u003c/code\u003e\ncount and how many are \u003ccode\u003e\"how\": \"pinned\"\u003c/code\u003e). Settings → Advanced →\n\u003cem\u003eReset match cache\u003c/em\u003e → \u003cstrong\u003eReset\u003c/strong\u003e in the confirm. The toast says\n\"Match cache reset: N titles forgotten, M pins included…\" with\nthose numbers, the file is gone, \u003ccode\u003ehosts/\u003c/code\u003e beside it is not, and the\nlog has a \u003ccode\u003ereset match cache: deleted …\u003c/code\u003e line. Nothing changes in\nthe library.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eA second press is harmless.\u003c/strong\u003e The toast says the cache was\nalready empty; the log says \u003ccode\u003eno file at\u003c/code\u003e.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe next sync re-matches.\u003c/strong\u003e Sync now: the CLI resolves every\ntitle again (the progress shows lookups, not cached hits), the\ntitles that read \"no match\" get a match where SteamGridDB has one,\nthe pinned title comes back as whatever the CLI resolves for it\n(the pin is gone) and titles that already had artwork keep it. The\nTitles page, opened after the run, agrees.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eBusy.\u003c/strong\u003e Start a sync, open Advanced while it runs: \u003cem\u003eReset\u003c/em\u003e is\ndisabled. Pin a title and press \u003cem\u003eReset\u003c/em\u003e within the moment the pin\nis saving, if you can: the toast says \"A match change is still\nbeing saved\" and the file is untouched.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eOff.\u003c/strong\u003e With the plugin toggled off the Advanced page is not\nreachable (§12); nothing to press.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e14. The SteamGridDB key from the Game Mode browser (spec 3.20)\u003c/h2\u003e\u003ca id=\"user-content-14-the-steamgriddb-key-from-the-game-mode-browser-spec-320\" class=\"anchor\" aria-label=\"Permalink: 14. The SteamGridDB key from the Game Mode browser (spec 3.20)\" href=\"#14-the-steamgriddb-key-from-the-game-mode-browser-spec-320\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eOff-device the backend (\u003ccode\u003etest_cdp.py\u003c/code\u003e, \u003ccode\u003etest_sgdbpage.py\u003c/code\u003e over the\nfixture pages, \u003ccode\u003etest_hard_rules.py\u003c/code\u003e for the key in no event or log line)\nand the frontend flow (\u003ccode\u003econtroller.test.ts\u003c/code\u003e, \"the SteamGridDB key from\nthe Game Mode browser\"; the button's presence per key source in\n\u003ccode\u003estate.test.ts\u003c/code\u003e) are covered; the real pages, the real browser and the\ntwo navigation calls are what these check. The spec calls this section\n§13; that number went to \u003cem\u003eReset match cache\u003c/em\u003e first. Any selector of spec\n§3.20.1 that does not match on the Deck goes to a Fable-class model\n(spec §5), never loosened here.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eStart with no key (Settings → Artwork → \u003cem\u003eRemove\u003c/em\u003e if the key file is set;\n\u003ccode\u003econfig.toml\u003c/code\u003e without \u003ccode\u003e[steamgriddb].api_key\u003c/code\u003e and no \u003ccode\u003eSGDB_API_KEY\u003c/code\u003e in\nplugin_loader's environment), and keep an SSH session tailing\n\u003ccode\u003e~/homebrew/logs/Moonlight Sync/moonlight-sync.log\u003c/code\u003e.\u003c/p\u003e\n\u003cul class=\"contains-task-list\"\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe button is where it should be.\u003c/strong\u003e Settings → Artwork shows\n\u003cem\u003eGet key from SteamGridDB…\u003c/em\u003e under the key field with no key, and\nagain under \u003cem\u003eTest\u003c/em\u003e / \u003cem\u003eRemove\u003c/em\u003e once the key file is set. Put a key in\n\u003ccode\u003econfig.toml\u003c/code\u003e (or \u003ccode\u003eSGDB_API_KEY\u003c/code\u003e in the service's environment),\nreopen the page: no button.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe consent text.\u003c/strong\u003e The button opens \"Sign in to SteamGridDB with\nSteam?\" with spec §3.20.4's text word for word, \u003cem\u003eContinue\u003c/em\u003e and\n\u003cem\u003eCancel\u003c/em\u003e. \u003cem\u003eCancel\u003c/em\u003e does nothing else (no browser, nothing in the\nlog).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eEnd to end, signed in to Steam.\u003c/strong\u003e \u003cem\u003eContinue\u003c/em\u003e: the Game Mode\nbrowser takes the screen on SteamGridDB, then Steam's \u003cem\u003eSign In\u003c/em\u003e\npage, then the API page, with no press of yours. \u003cstrong\u003e[verify]\n\u003ccode\u003eNavigateToExternalWeb\u003c/code\u003e\u003c/strong\u003e opens the same \u003ccode\u003epage\u003c/code\u003e target spec\n§3.20.1 item 2 measured (over the debugger, \u003ccode\u003e/json/list\u003c/code\u003e shows\n\u003ccode\u003ePreferences - SteamGridDB\u003c/code\u003e / \u003ccode\u003e…/profile/preferences/api\u003c/code\u003e); if it\nopens anything else, report it (the fallback is\n\u003ccode\u003esteam://openurl/\u003c/code\u003e through \u003ccode\u003eSteamClient.URL.ExecuteSteamURL\u003c/code\u003e, and\nchoosing it is a spec change). At the end \u003cstrong\u003e[verify]\n\u003ccode\u003eNavigateBack\u003c/code\u003e\u003c/strong\u003e leaves the browser and lands on Settings →\nArtwork; if the browser stays, note it here (it is left open by\ndesign then). The toasts are \"SteamGridDB key saved (…XXXX)\" with\nthe key's last four characters, then \"SteamGridDB accepted the\nkey\". The field reads \"set, ends in …XXXX\", and\n\u003ccode\u003e~/.config/moonlight-steam-sync/sgdb-api-key\u003c/code\u003e is mode 0600.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe field follows the fetch.\u003c/strong\u003e While it runs, back out of the\nbrowser (B) to Settings → Artwork: the field says \u003cem\u003eWaiting for\nSteamGridDB…\u003c/em\u003e / \u003cem\u003eSigning in with Steam…\u003c/em\u003e / \u003cem\u003eSteam is asking you to\nlog in on the page\u003c/em\u003e / \u003cem\u003eReading your key…\u003c/em\u003e as it goes, and \u003cem\u003eCancel\u003c/em\u003e\nstands where \u003cem\u003eSave\u003c/em\u003e was.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eNot signed in to Steam's web session.\u003c/strong\u003e On a Steam account whose\ncommunity session is gone (sign out of steamcommunity.com in the\nbrowser first), Steam's page asks for a password or a Steam Guard\ncode: the field says \u003cem\u003eSteam is asking you to log in on the page\u003c/em\u003e,\nnothing is typed for you, and once you have logged in the plugin\npresses \u003cem\u003eSign In\u003c/em\u003e once and carries on.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eAn account with no key.\u003c/strong\u003e \u003cstrong\u003e[verify]\u003c/strong\u003e (Decision 61) On a\nSteamGridDB account that never generated a key, the API page's\n\u003cem\u003eGenerate\u003c/em\u003e button is pressed once and the key is read after it; on\na page with any key element, or only \u003cem\u003eRegenerate\u003c/em\u003e / \u003cem\u003eRevoke\u003c/em\u003e,\nnothing is pressed and the toast says \"SteamGridDB shows no API\nkey; generate one on its API page, then try again\". \u003cem\u003eRevoke API\nKey\u003c/em\u003e is never pressed in any run (the key you had still works:\n\u003cem\u003eTest\u003c/em\u003e).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eA second attempt after a failure, no Steam restart.\u003c/strong\u003e Make a\nfetch fail (the \u003cem\u003eLeft behind, then timed out\u003c/em\u003e item below, or\n\u003cem\u003eCancel mid-way\u003c/em\u003e), then press \u003cem\u003eGet key from SteamGridDB…\u003c/em\u003e again:\nit succeeds. The log shows \"sgdb key fetch: reset the browser's\ntab (was on \u003ca href=\"http://www.steamgriddb.com\" rel=\"nofollow\"\u003ewww.steamgriddb.com\u003c/a\u003e)\" (or steamcommunity.com) right\nbefore \"started\" (Decision 70), then \u003ccode\u003ewaiting\u003c/code\u003e and the fresh page;\nnever \"failed (sgdb-page): no key and no generate button\" within a\nsecond of \"started\". A device's 2026-09-25 had exactly that until\nSteam was restarted. Should the log say \"could not reset the\nbrowser's tab\", report it with the rest of the line: the fetch\nthen reads the old page for its first polls (Decision 69) and\nshould still succeed.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eCancel mid-way.\u003c/strong\u003e Start again, back out of the browser while\nSteam's sign-in page is up, press \u003cem\u003eCancel\u003c/em\u003e: within a second the\ntoast says \"The key fetch was cancelled\", you stay on Settings →\nArtwork (no navigation back from there), the key file is\nunchanged, and the browser page is left where it was.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eLeft behind, then timed out.\u003c/strong\u003e Start again, back out of the\nbrowser with B and wait three minutes: the toast says \"Timed out\nwaiting for the SteamGridDB page; enter the key by hand or try\nagain\". Note where \u003ccode\u003eNavigateBack\u003c/code\u003e took you (it runs on every done\nof a fetch the plugin opened and was not cancelled from the page);\nif it left a page you did not expect, report it.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eNo debugger.\u003c/strong\u003e With \u003ccode\u003e~/.steam/steam/.cef-enable-remote-debugging\u003c/code\u003e\nremoved and Steam restarted (put it back afterwards; Decky\nrecreates it), \u003cem\u003eContinue\u003c/em\u003e toasts \"Steam's debugger port is not\nreachable; enter the key by hand\" and no browser opens.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe key is never in the log.\u003c/strong\u003e After the runs above, \u003ccode\u003egrep -c\u003c/code\u003e\nyour key in \u003ccode\u003e~/homebrew/logs/Moonlight Sync/moonlight-sync.log\u003c/code\u003e\n(and in Decky's own log, \u003ccode\u003ejournalctl -u plugin_loader\u003c/code\u003e) is \u003ccode\u003e0\u003c/code\u003e;\nthe log has \u003ccode\u003esgdb key fetch: \u0026lt;state\u0026gt;\u003c/code\u003e lines and \"sgdb key fetched\nfrom the browser\", never the key.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eOff.\u003c/strong\u003e With the plugin toggled off the Artwork page is not\nreachable (§12); nothing to press.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e15. The panel's launch row and Advanced's layout buttons (spec Decisions 67 and 77)\u003c/h2\u003e\u003ca id=\"user-content-15-the-panels-launch-row-and-advanceds-layout-buttons-spec-decisions-67-and-77\" class=\"anchor\" aria-label=\"Permalink: 15. The panel's launch row and Advanced's layout buttons (spec Decisions 67 and 77)\" href=\"#15-the-panels-launch-row-and-advanceds-layout-buttons-spec-decisions-67-and-77\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThe panel's launch buttons are icons on one line; the Moonlight entry's\n\u003cem\u003eLayout\u003c/em\u003e and \u003cem\u003eMake default\u003c/em\u003e are in Settings → Advanced, beside \u003cem\u003eClear\u003c/em\u003e.\nNeeds a synced host that publishes \u003ccode\u003eDesktop\u003c/code\u003e and \u003ccode\u003eSteam Big Picture\u003c/code\u003e.\u003c/p\u003e\n\u003cul class=\"contains-task-list\"\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe toggle.\u003c/strong\u003e The top row reads \u003cem\u003eEnable Sync\u003c/em\u003e with no text under\nit, on or off. Settings, opened while off, shows the same toggle\nwith the \"Off: every Moonlight shortcut is hidden…\" line under it.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eSync now\u003c/strong\u003e has no text under it.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eOne launch row.\u003c/strong\u003e The moon, the monitor and the Steam logo share\none row, in that order, equal widths, nothing clipped or overflowing\nthe 268 px column. The moon opens Moonlight; the monitor streams\n\u003cem\u003eDesktop\u003c/em\u003e; the Steam logo streams \u003cem\u003eSteam Big Picture\u003c/em\u003e.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe caption follows focus.\u003c/strong\u003e \u003cstrong\u003e[verify]\u003c/strong\u003e (\u003ccode\u003eonGamepadFocus\u003c/code\u003e on a\n\u003ccode\u003eDialogButton\u003c/code\u003e, unmeasured) D-pad onto each icon: the line under the\nrow changes to \"Open Moonlight · Launch the client…\", \"Desktop ·\nStream the host's desktop\", \"Steam Big Picture · Stream the host's\nSteam in Big Picture\", and the footer's A legend names the same\nbutton. D-pad down off the row: the line goes back to \u003cem\u003eOpen\nMoonlight\u003c/em\u003e's (\u003ccode\u003eonGamepadBlur\u003c/code\u003e). If the line never changes, report it\n(the icons are then unlabelled on the Deck).\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eA host app the host does not publish.\u003c/strong\u003e Ignore \u003ccode\u003eDesktop\u003c/code\u003e on the\nTitles page and sync: the monitor goes and the other two icons widen.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eNo layout row on the panel.\u003c/strong\u003e Under the launch row come the\ncounters; there is no \u003cem\u003eLayout\u003c/em\u003e / \u003cem\u003eMake default\u003c/em\u003e row.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eOne layout row in Advanced.\u003c/strong\u003e Settings → Advanced → \u003cem\u003eDefault\ncontroller layout\u003c/em\u003e has \u003cem\u003eLayout\u003c/em\u003e, \u003cem\u003eMake default\u003c/em\u003e and \u003cem\u003eClear\u003c/em\u003e on one\nline, right of its text, nothing clipped; the D-pad moves across the\nthree and down to \u003cem\u003eHide Stream shortcuts\u003c/em\u003e.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eLayout.\u003c/strong\u003e \u003cem\u003eLayout\u003c/em\u003e opens Steam's controller configurator for the\nMoonlight entry; B comes back to Advanced. Pick a layout there.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eMake default.\u003c/strong\u003e \u003cem\u003eMake default\u003c/em\u003e asks \"Use “” as the default\nlayout?\"; confirm, and the toast counts the titles that took it. The\nrow's text now reads \" ·  · set today …\" and \u003cem\u003eClear\u003c/em\u003e\nis enabled.\nA Stream press on another game puts that layout on its shortcut.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eMake default with nothing chosen.\u003c/strong\u003e On a Moonlight entry whose\nlayout was never picked, \u003cem\u003eMake default\u003c/em\u003e toasts \"“Moonlight” has no\nlayout chosen yet. Use Layout first.\" and changes nothing. An\nedited-in-place layout gets the \u003cem\u003eExport\u003c/em\u003e toast instead.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eBefore the first sync\u003c/strong\u003e (no Moonlight shortcut yet): the moon,\n\u003cem\u003eLayout\u003c/em\u003e and \u003cem\u003eMake default\u003c/em\u003e are disabled; the launch line reads\n\"Open Moonlight · Sync once to enable\" and Advanced's text \"None.\nSync once, then set up Moonlight's Layout and press Make default\".\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eWhile a game runs\u003c/strong\u003e every icon and both layout buttons still work.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e16. Updating from the plugin (update spec 3.9, V1 to V7)\u003c/h2\u003e\u003ca id=\"user-content-16-updating-from-the-plugin-update-spec-39-v1-to-v7\" class=\"anchor\" aria-label=\"Permalink: 16. Updating from the plugin (update spec 3.9, V1 to V7)\" href=\"#16-updating-from-the-plugin-update-spec-39-v1-to-v7\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eEvery item here is the user's check, on a device; none is a merge\ncriterion. Off-device the updater is covered (\u003ccode\u003eupdates.test.ts\u003c/code\u003e over\n\u003ccode\u003etests/fixtures/update/releases.json\u003c/code\u003e, \u003ccode\u003edecky.test.ts\u003c/code\u003e for the allowlist\nand the call's arguments, \u003ccode\u003econtroller.test.ts\u003c/code\u003e \"the updater\" for the\ncheck, the refusals and the order \u003cem\u003echeck (on a branch), stage, leave the\npage, then ask Decky\u003c/em\u003e, \u003ccode\u003etest_hard_rules.py\u003c/code\u003e for hard rule 12's greps,\n\u003ccode\u003etest_backend_updates.py\u003c/code\u003e for the staging); the backend's download on the\ndevice, Decky's own dialog, its install from the staged file, the\nuninstall and the reload are what these check. Since the staged hand-off\n(update spec 3.12.5) every install downloads first: the button reads\n\u003cem\u003eDownloading…\u003c/em\u003e, then the page closes and Decky's dialog opens.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe first release with the updater cannot be updated \u003cem\u003eto\u003c/em\u003e anything, since\nnothing newer exists yet. V1 to V5 therefore use Settings → Updates →\n\u003cem\u003eInstall another version\u003c/em\u003e → the installed version (\u003cem\u003ereinstall\u003c/em\u003e), which\ntakes the same route with install type 1 (Decky's dialog then says\n\"reinstall\" rather than \"update\"). The first real update, with the\npanel's \u003cem\u003eUpdate to …\u003c/em\u003e row and the toast, is checked when the release\nafter it ships; repeat V1, V2 and V5 then.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eStart with the build installed by \u003ccode\u003einstall.sh\u003c/code\u003e (every file root-owned),\nthe plugin on, synced, a SteamGridDB key set and a default layout set,\nand an SSH session tailing Decky's log (\u003ccode\u003ejournalctl -u plugin_loader -f\u003c/code\u003e).\nBefore V1, copy what V4 compares: \u003ccode\u003e~/homebrew/settings/Moonlight Sync/\u003c/code\u003e\n(settings.json, pending.json, layouts.json, ignore.json),\n\u003ccode\u003e~/.config/moonlight-steam-sync/\u003c/code\u003e, \u003ccode\u003e~/.cache/moonlight-steam-sync/\u003c/code\u003e and\nthe library's tile count.\u003c/p\u003e\n\u003cul class=\"contains-task-list\"\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe page.\u003c/strong\u003e Settings shows \u003cem\u003eUpdates\u003c/em\u003e between \u003cem\u003eAdvanced\u003c/em\u003e and\n\u003cem\u003eAbout\u003c/em\u003e: \u003cem\u003eInstalled\u003c/em\u003e is the plugin's version, \u003cem\u003eLatest\u003c/em\u003e says \"Up to\ndate · checked today HH:MM\" (or the newer release, once there is\none), \u003cem\u003eCheck now\u003c/em\u003e asks again (a second press within a minute asks\nnothing), \u003cem\u003eCheck for updates automatically\u003c/em\u003e is on, and \u003cem\u003eInstall\nanother version\u003c/em\u003e lists only releases from 0.12.0 up. About has a\n\u003cem\u003eDecky Loader\u003c/em\u003e row with the loader's version.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eV1. [verify] The download, Decky's dialog, then the install.\u003c/strong\u003e\n\u003cem\u003eInstall another version\u003c/em\u003e → the installed version: the button reads\n\u003cem\u003eDownloading…\u003c/em\u003e while the plugin's backend downloads and checks the\nzip (the plugin's log shows \u003ccode\u003estage_update v…: staged …\u003c/code\u003e), then Decky's\ndialog asks \"Are you sure you want to reinstall Moonlight Sync …?\"\n(on a real update: \"…update Moonlight Sync to version X?\"). Confirm:\nDecky installs the staged zip from\n\u003ccode\u003e~/homebrew/data/Moonlight Sync/update/staged/\u003c/code\u003e, on loader v3.2.9\n(§17's V8 is the same check for a branch build). If the dialog never\nappears, or Decky's log shows an error, escalate with both logs.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eV2. [verify] The page closes first, the plugin comes back.\u003c/strong\u003e The\nsettings page is gone before Decky's dialog shows (Decision U4).\nAfter Confirm the plugin reloads at the chosen version (About), with\nno loader or Steam restart. If the screen shows anything else (a\nblank route, a frozen page), escalate with what it showed.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eV3. [verify] Over both kinds of install.\u003c/strong\u003e V1 over the copy\n\u003ccode\u003einstall.sh\u003c/code\u003e put there (root-owned), then once more over the copy\nDecky just installed. Both work; if either fails, escalate.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eV4. [verify] Nothing of yours changes. Blocking.\u003c/strong\u003e After the\nupdate: settings.json, pending.json, layouts.json and ignore.json\nare what they were, the key file is there with mode 0600 (Artwork →\n\u003cem\u003eTest\u003c/em\u003e accepts it), the CLI's config and caches are unchanged, the\ndefault layout is still set (Advanced), and the library has the same\ntiles, Stream buttons and Streaming tab. Anything different:\nescalate, and it blocks the release.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eV5. Cancel.\u003c/strong\u003e Start an install and press \u003cem\u003eCancel\u003c/em\u003e in Decky's\ndialog: the old version keeps running, the page's buttons are\nusable again, and a second attempt opens the dialog again (the zip\nalready staged is not downloaded twice: the plugin's log says\n\"already staged\"). Then start one and press the page's own \u003cem\u003eCancel\u003c/em\u003e\nunder \u003cem\u003eDownloading…\u003c/em\u003e at once: no toast, no dialog, the buttons are\nusable again.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eV6. Where the plugin lands.\u003c/strong\u003e After a Confirm, note where Moonlight\nSync sits in Decky's plugin list (expected: at the end, §2.3 of the\nupdate spec) and whether a hidden or frozen state it had in Decky's\nlist survived. Write what you see into the README's \"Updating\"\nsection.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eV7. Offline.\u003c/strong\u003e With Wi-Fi off, start an install: the download\nfails before Decky is asked, a toast says \"Could not reach GitHub:\n…\", Decky's dialog never opens, the settings page stays, and the old\nversion still runs (Decky has nothing to download, so its progress\nnever shows). If Decky's dialog or progress appears, escalate. Back\nonline, the install works.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe check's toast and the panel's row\u003c/strong\u003e (once a newer release\nexists). A plugin load (restart Steam) toasts \"Version X is\navailable. Settings → Updates\" once, and the panel's last row reads\n\u003cem\u003eUpdate to X\u003c/em\u003e; pressing it opens Settings → Updates. With \u003cem\u003eCheck for\nupdates automatically\u003c/em\u003e off, a load toasts nothing and adds no row\nuntil \u003cem\u003eCheck now\u003c/em\u003e.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eOff.\u003c/strong\u003e With the plugin toggled off: no toast at load, no row, and\nthe settings route has no \u003cem\u003eUpdates\u003c/em\u003e page (§12).\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e17. The staged hand-off and the channels (update spec 3.12.5, V8 to V10)\u003c/h2\u003e\u003ca id=\"user-content-17-the-staged-hand-off-and-the-channels-update-spec-3125-v8-to-v10\" class=\"anchor\" aria-label=\"Permalink: 17. The staged hand-off and the channels (update spec 3.12.5, V8 to V10)\" href=\"#17-the-staged-hand-off-and-the-channels-update-spec-3125-v8-to-v10\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eEvery item here is the user's check, on a device, before a release with\nthe channel picker is cut; none is a merge criterion. Off-device the\nstaging is covered (\u003ccode\u003etest_fetch.py\u003c/code\u003e, \u003ccode\u003etest_updates.py\u003c/code\u003e,\n\u003ccode\u003etest_backend_updates.py\u003c/code\u003e over \u003ccode\u003efile://\u003c/code\u003e trees), and so are the order\n\u003cem\u003echeck, stage, leave, ask\u003c/em\u003e, the forced check on a branch channel, a failed\nstaging, \u003cem\u003eCancel\u003c/em\u003e and the picker (\u003ccode\u003econtroller.test.ts\u003c/code\u003e), and the\nallowlist's \u003ccode\u003efile://\u003c/code\u003e rule (\u003ccode\u003edecky.test.ts\u003c/code\u003e); Decky's install from a\n\u003ccode\u003efile://\u003c/code\u003e path, the picker's modal and a real branch build are what these\ncheck.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eStart as for §16 (the plugin on, synced, a key and a default layout set,\nDecky's log tailed), with \u003ccode\u003emain\u003c/code\u003e publishing builds (\u003ccode\u003ebuild-main\u003c/code\u003e exists on\nthe repository's releases, with a \u003ccode\u003ebuild.json\u003c/code\u003e among its assets). Keep an\nSSH session for V10's \u003ccode\u003emoonlight-steam-sync --version\u003c/code\u003e and \u003ccode\u003esha256sum ~/.local/bin/moonlight-steam-sync\u003c/code\u003e.\u003c/p\u003e\n\u003cul class=\"contains-task-list\"\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe picker.\u003c/strong\u003e Settings → Updates' second row is \u003cem\u003eChannel\u003c/em\u003e, reading\n\u003cem\u003eReleases\u003c/em\u003e, with the description \"Releases are tested. Builds of a\nbranch are whatever was pushed last.\" Its list has \u003cem\u003eReleases\u003c/em\u003e, then\n\u003ccode\u003emain\u003c/code\u003e, then any other branch with a build. Choosing \u003ccode\u003emain\u003c/code\u003e asks\n\"Follow main?\" (\"Builds of a branch are untested and can break\nsyncing. You can return to Releases here at any time.\", \u003cem\u003eFollow\u003c/em\u003e /\n\u003cem\u003eCancel\u003c/em\u003e). \u003cstrong\u003e[verify]\u003c/strong\u003e \u003cem\u003eCancel\u003c/em\u003e leaves \u003cem\u003eReleases\u003c/em\u003e selected in the\ndropdown (whether Steam's dropdown snaps back to the stored channel\nafter a cancelled confirm is unmeasured) and asks GitHub nothing.\nChoosing \u003cem\u003eReleases\u003c/em\u003e from a branch asks nothing.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eThe branch's \u003ccode\u003ebuild.json\u003c/code\u003e through the loader's fetch.\u003c/strong\u003e The\n\u003ccode\u003ebuild.json\u003c/code\u003e request is the plugin's first request through Decky's\n\u003ccode\u003efetchNoCors\u003c/code\u003e to \u003ccode\u003egithub.com/…/releases/download/…\u003c/code\u003e, which answers\nwith a redirect (302) to another host; every request before it went\nto \u003ccode\u003eapi.github.com\u003c/code\u003e, which does not redirect. Follow \u003ccode\u003emain\u003c/code\u003e (the\npicker checks at once): \u003cem\u003eLatest\u003c/em\u003e must show the build, \u003ccode\u003emain @ \u0026lt;sha7\u0026gt; · built … · checked …\u003c/code\u003e, not \"No build of main is published\".\nIf it shows the latter although \u003ccode\u003ebuild-main\u003c/code\u003e has a \u003ccode\u003ebuild.json\u003c/code\u003e, the\nloader's fetch did not follow the redirect (the check reports no\nerror then, since a failed second request never fails it): escalate\nwith Decky's log.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eA new build of the followed branch is seen at once.\u003c/strong\u003e With \u003ccode\u003emain\u003c/code\u003e\nfollowed and its build installed, merge to \u003ccode\u003emain\u003c/code\u003e (or \u003ccode\u003egh workflow run builds.yml --ref main\u003c/code\u003e) and wait for the \u003cem\u003eBuilds\u003c/em\u003e run to finish;\n\u003cem\u003eCheck now\u003c/em\u003e on the Updates page must show the new commit, \u003ccode\u003emain @ \u0026lt;the new sha7\u0026gt;\u003c/code\u003e, and offer \u003cem\u003eSwitch to\u003c/em\u003e it. Before amendment A6 the\nloader's fetch went through CEF's HTTP cache, which kept the old\n\u003ccode\u003ebuild.json\u003c/code\u003e (served with \u003ccode\u003eLast-Modified\u003c/code\u003e and no \u003ccode\u003eCache-Control\u003c/code\u003e)\nfor hours: the page said \u003cem\u003eUp to date\u003c/em\u003e although \u003ccode\u003emain\u003c/code\u003e had moved,\nand, once the new build was installed through \u003cem\u003eReleases\u003c/em\u003e and back,\noffered the previous commit for as long. Found on the Deck\n2026-09-29; \u003ccode\u003ejournalctl -u plugin_loader\u003c/code\u003e shows a \u003ccode\u003ePreparing GET request to …/build-main/build.json\u003c/code\u003e line for a check that reached\nthe loader, and none for one the cache answered.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eV8. [verify] A \u003ccode\u003efile://\u003c/code\u003e artifact installs.\u003c/strong\u003e On \u003ccode\u003emain\u003c/code\u003e, press\n\u003cem\u003eSwitch to main @ \u003c/em\u003e: \u003cem\u003eDownloading…\u003c/em\u003e, then the page closes and\nDecky's dialog asks to overwrite Moonlight Sync with version\n\" (main @ )\"; Confirm. Decky installs the zip from\n\u003ccode\u003e~/homebrew/data/Moonlight Sync/update/staged/\u003c/code\u003e (a path with a space\nin it) through \u003ccode\u003eutilities/install_plugin\u003c/code\u003e, and the plugin reloads.\nIf Decky refuses the artifact, cannot open the path, or its log shows\nan error: escalate with the log. The fallback the spec allows then\n(update spec 3.12.5, not built): releases keep the URL route of\nupdate spec 3.5, and branch builds hand Decky the release asset's URL\nafter a successful staging, which has then proved what the URL\nserves.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eV9. Follow \u003ccode\u003emain\u003c/code\u003e, install, a second push, back to Releases.\u003c/strong\u003e\nAfter V8: \u003cem\u003eInstalled\u003c/em\u003e reads \u003ccode\u003e\u0026lt;version\u0026gt; · main @ \u0026lt;sha7\u0026gt; · built …\u003c/code\u003e,\n\u003cem\u003eLatest\u003c/em\u003e says \"Up to date · checked …\" and there is no install\nbutton. Push a commit to \u003ccode\u003emain\u003c/code\u003e, wait for \u003ccode\u003ebuilds.yml\u003c/code\u003e to publish it,\nthen restart Steam (or press \u003cem\u003eCheck now\u003c/em\u003e a minute later): the toast\n\"Version main @  is available. Settings → Updates\" and the\npanel's row appear, and \u003cem\u003eSwitch to main @ \u003c/em\u003e installs it.\nThen choose \u003cem\u003eReleases\u003c/em\u003e: \u003cem\u003eLatest\u003c/em\u003e names the newest release and the\nbutton reads \u003cem\u003eSwitch to \u003c/em\u003e; pressing it installs the release\n(Decky asks to overwrite) and \u003cem\u003eInstalled\u003c/em\u003e reads the plain version\nagain. Anything else: escalate.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eV10. The CLI at each step of V9.\u003c/strong\u003e Before V8, after V8, after the\nsecond push's install and after the return to \u003cem\u003eReleases\u003c/em\u003e, note\n\u003ccode\u003emoonlight-steam-sync --version\u003c/code\u003e and the sha256 of\n\u003ccode\u003e~/.local/bin/moonlight-steam-sync\u003c/code\u003e, and About's CLI rows. Expected:\nthe version stays the release's number throughout; the bytes change\nto the branch build's bundled CLI after V8 and after the second\npush's install (a build of the same version with other bytes\nreplaces it, README \"The bundled CLI\"), and go back to the release's\nafter the return to \u003cem\u003eReleases\u003c/em\u003e. Anything else: escalate.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eA failed staging leaves everything as it was.\u003c/strong\u003e With Wi-Fi off,\npress \u003cem\u003eSwitch to …\u003c/em\u003e on \u003ccode\u003emain\u003c/code\u003e: the forced check fails first, a toast\nsays \"Could not reach GitHub: …\", nothing downloads, Decky's dialog\nnever opens. Back online, press it and at once the page's \u003cem\u003eCancel\u003c/em\u003e\nunder \u003cem\u003eDownloading…\u003c/em\u003e: no toast, no dialog, and a second press works.\u003c/li\u003e\n\u003cli class=\"task-list-item\"\u003e\u003cinput type=\"checkbox\" id=\"\" disabled=\"\" class=\"task-list-item-checkbox\" aria-label=\"Incomplete task\"\u003e \u003cstrong\u003eA build made on the device is the commit it is\u003c/strong\u003e (update spec\namendment A5). On the device, clone \u003ccode\u003emain\u003c/code\u003e afresh, build it (\u003ccode\u003epnpm install\u003c/code\u003e, \u003ccode\u003epnpm run build\u003c/code\u003e, \u003ccode\u003ebackend/entrypoint.sh\u003c/code\u003e,\n\u003ccode\u003escripts/package.py --require-cli\u003c/code\u003e) and install the zip by hand\n(README \"Manual install\"). \u003ccode\u003epackage.py\u003c/code\u003e must print \u003ccode\u003epackage.py: build.json from git: main @ \u0026lt;sha12\u0026gt;\u003c/code\u003e; if it prints \u003ccode\u003eno build.json: …\u003c/code\u003e instead, the text in brackets is git's own reason (a clone that\nbelongs to another user is \"dubious ownership\"): report it. Then,\nwith the \u003cem\u003eChannel\u003c/em\u003e on \u003ccode\u003emain\u003c/code\u003e and \u003ccode\u003ebuild-main\u003c/code\u003e published from that\nsame commit: \u003cem\u003eInstalled\u003c/em\u003e reads \u003ccode\u003e\u0026lt;version\u0026gt; · main @ \u0026lt;sha7\u0026gt; · built …\u003c/code\u003e, \u003cem\u003eLatest\u003c/em\u003e says \"Up to date · checked …\" and there is no install\nbutton; Settings → About's \u003cem\u003eBuilt from\u003c/em\u003e reads \u003ccode\u003emain @ \u0026lt;sha7\u0026gt; · built …\u003c/code\u003e and \u003cem\u003eCommit\u003c/em\u003e is the whole commit, the one \u003ccode\u003egit rev-parse HEAD\u003c/code\u003e\nprinted in the clone. Before this, such a build always showed \u003cem\u003eSwitch to main @\n\u003c/em\u003e (found 2026-09-28). With the \u003cem\u003eChannel\u003c/em\u003e on \u003cem\u003eReleases\u003c/em\u003e the\npage offers \u003cem\u003eSwitch to \u003c/em\u003e instead, as for any branch build.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/article\u003e","richTextTruncated":false,"renderedFileInfo":null,"symbols":{"timed_out":false,"not_analyzed":false,"symbols":[{"name":"Device checklist","fully_qualified_name":"Device checklist","kind":"section_1","ident_start":2,"ident_end":18,"extent_start":0,"extent_end":76568,"ident_utf16":{"start":{"line_number":0,"utf16_col":2},"end":{"line_number":0,"utf16_col":18}},"extent_utf16":{"start":{"line_number":0,"utf16_col":0},"end":{"line_number":1203,"utf16_col":0}}},{"name":"0. Setup","fully_qualified_name":"0. Setup","kind":"section_2","ident_start":413,"ident_end":421,"extent_start":410,"extent_end":3464,"ident_utf16":{"start":{"line_number":9,"utf16_col":3},"end":{"line_number":9,"utf16_col":11}},"extent_utf16":{"start":{"line_number":9,"utf16_col":0},"end":{"line_number":57,"utf16_col":0}}},{"name":"1. PR-0: device probes","fully_qualified_name":"1. PR-0: device probes","kind":"section_2","ident_start":3467,"ident_end":3489,"extent_start":3464,"extent_end":6746,"ident_utf16":{"start":{"line_number":57,"utf16_col":3},"end":{"line_number":57,"utf16_col":25}},"extent_utf16":{"start":{"line_number":57,"utf16_col":0},"end":{"line_number":110,"utf16_col":0}}},{"name":"2. PR-5: backend, Quick Access panel","fully_qualified_name":"2. PR-5: backend, Quick Access panel","kind":"section_2","ident_start":6749,"ident_end":6785,"extent_start":6746,"extent_end":15050,"ident_utf16":{"start":{"line_number":110,"utf16_col":3},"end":{"line_number":110,"utf16_col":39}},"extent_utf16":{"start":{"line_number":110,"utf16_col":0},"end":{"line_number":237,"utf16_col":0}}},{"name":"3. PR-6: Titles page, Change match","fully_qualified_name":"3. PR-6: Titles page, Change match","kind":"section_2","ident_start":15053,"ident_end":15087,"extent_start":15050,"extent_end":19688,"ident_utf16":{"start":{"line_number":237,"utf16_col":3},"end":{"line_number":237,"utf16_col":37}},"extent_utf16":{"start":{"line_number":237,"utf16_col":0},"end":{"line_number":309,"utf16_col":0}}},{"name":"4. PR-7: Stream button and controller layouts","fully_qualified_name":"4. PR-7: Stream button and controller layouts","kind":"section_2","ident_start":19691,"ident_end":19736,"extent_start":19688,"extent_end":25014,"ident_utf16":{"start":{"line_number":309,"utf16_col":3},"end":{"line_number":309,"utf16_col":48}},"extent_utf16":{"start":{"line_number":309,"utf16_col":0},"end":{"line_number":392,"utf16_col":0}}},{"name":"5. PR-8: this checklist and `install.sh`","fully_qualified_name":"5. PR-8: this checklist and `install.sh`","kind":"section_2","ident_start":25017,"ident_end":25057,"extent_start":25014,"extent_end":29508,"ident_utf16":{"start":{"line_number":392,"utf16_col":3},"end":{"line_number":392,"utf16_col":43}},"extent_utf16":{"start":{"line_number":392,"utf16_col":0},"end":{"line_number":463,"utf16_col":0}}},{"name":"6. Host apps: the panel buttons replace the tiles (spec 3.14)","fully_qualified_name":"6. Host apps: the panel buttons replace the tiles (spec 3.14)","kind":"section_2","ident_start":29511,"ident_end":29572,"extent_start":29508,"extent_end":33729,"ident_utf16":{"start":{"line_number":463,"utf16_col":3},"end":{"line_number":463,"utf16_col":64}},"extent_utf16":{"start":{"line_number":463,"utf16_col":0},"end":{"line_number":530,"utf16_col":0}}},{"name":"7. Hidden shortcuts and the Streaming collection (spec 3.15)","fully_qualified_name":"7. Hidden shortcuts and the Streaming collection (spec 3.15)","kind":"section_2","ident_start":33732,"ident_end":33792,"extent_start":33729,"extent_end":36719,"ident_utf16":{"start":{"line_number":530,"utf16_col":3},"end":{"line_number":530,"utf16_col":63}},"extent_utf16":{"start":{"line_number":530,"utf16_col":0},"end":{"line_number":578,"utf16_col":0}}},{"name":"8. The default controller layout (spec 3.16)","fully_qualified_name":"8. The default controller layout (spec 3.16)","kind":"section_2","ident_start":36722,"ident_end":36766,"extent_start":36719,"extent_end":42531,"ident_utf16":{"start":{"line_number":578,"utf16_col":3},"end":{"line_number":578,"utf16_col":47}},"extent_utf16":{"start":{"line_number":578,"utf16_col":0},"end":{"line_number":666,"utf16_col":0}}},{"name":"9. Fixtures vs. real CLI","fully_qualified_name":"9. Fixtures vs. real CLI","kind":"section_2","ident_start":42534,"ident_end":42558,"extent_start":42531,"extent_end":43318,"ident_utf16":{"start":{"line_number":666,"utf16_col":3},"end":{"line_number":666,"utf16_col":27}},"extent_utf16":{"start":{"line_number":666,"utf16_col":0},"end":{"line_number":680,"utf16_col":0}}},{"name":"10. The Streaming tab (spec 3.17)","fully_qualified_name":"10. The Streaming tab (spec 3.17)","kind":"section_2","ident_start":43321,"ident_end":43354,"extent_start":43318,"extent_end":48834,"ident_utf16":{"start":{"line_number":680,"utf16_col":3},"end":{"line_number":680,"utf16_col":36}},"extent_utf16":{"start":{"line_number":680,"utf16_col":0},"end":{"line_number":766,"utf16_col":0}}},{"name":"11. Wake-on-LAN (spec 3.18)","fully_qualified_name":"11. Wake-on-LAN (spec 3.18)","kind":"section_2","ident_start":48837,"ident_end":48864,"extent_start":48834,"extent_end":51153,"ident_utf16":{"start":{"line_number":766,"utf16_col":3},"end":{"line_number":766,"utf16_col":30}},"extent_utf16":{"start":{"line_number":766,"utf16_col":0},"end":{"line_number":804,"utf16_col":0}}},{"name":"12. The on/off toggle (spec 3.19)","fully_qualified_name":"12. The on/off toggle (spec 3.19)","kind":"section_2","ident_start":51156,"ident_end":51189,"extent_start":51153,"extent_end":53043,"ident_utf16":{"start":{"line_number":804,"utf16_col":3},"end":{"line_number":804,"utf16_col":36}},"extent_utf16":{"start":{"line_number":804,"utf16_col":0},"end":{"line_number":836,"utf16_col":0}}},{"name":"13. Reset match cache (Advanced)","fully_qualified_name":"13. Reset match cache (Advanced)","kind":"section_2","ident_start":53046,"ident_end":53078,"extent_start":53043,"extent_end":54812,"ident_utf16":{"start":{"line_number":836,"utf16_col":3},"end":{"line_number":836,"utf16_col":35}},"extent_utf16":{"start":{"line_number":836,"utf16_col":0},"end":{"line_number":867,"utf16_col":0}}},{"name":"14. The SteamGridDB key from the Game Mode browser (spec 3.20)","fully_qualified_name":"14. The SteamGridDB key from the Game Mode browser (spec 3.20)","kind":"section_2","ident_start":54815,"ident_end":54877,"extent_start":54812,"extent_end":60762,"ident_utf16":{"start":{"line_number":867,"utf16_col":3},"end":{"line_number":867,"utf16_col":65}},"extent_utf16":{"start":{"line_number":867,"utf16_col":0},"end":{"line_number":962,"utf16_col":0}}},{"name":"15. The panel's launch row and Advanced's layout buttons (spec Decisions 67 and 77)","fully_qualified_name":"15. The panel's launch row and Advanced's layout buttons (spec Decisions 67 and 77)","kind":"section_2","ident_start":60765,"ident_end":60848,"extent_start":60762,"extent_end":63815,"ident_utf16":{"start":{"line_number":962,"utf16_col":3},"end":{"line_number":962,"utf16_col":86}},"extent_utf16":{"start":{"line_number":962,"utf16_col":0},"end":{"line_number":1009,"utf16_col":0}}},{"name":"16. Updating from the plugin (update spec 3.9, V1 to V7)","fully_qualified_name":"16. Updating from the plugin (update spec 3.9, V1 to V7)","kind":"section_2","ident_start":63818,"ident_end":63874,"extent_start":63815,"extent_end":69462,"ident_utf16":{"start":{"line_number":1009,"utf16_col":3},"end":{"line_number":1009,"utf16_col":59}},"extent_utf16":{"start":{"line_number":1009,"utf16_col":0},"end":{"line_number":1098,"utf16_col":0}}},{"name":"17. The staged hand-off and the channels (update spec 3.12.5, V8 to V10)","fully_qualified_name":"17. The staged hand-off and the channels (update spec 3.12.5, V8 to V10)","kind":"section_2","ident_start":69465,"ident_end":69537,"extent_start":69462,"extent_end":76568,"ident_utf16":{"start":{"line_number":1098,"utf16_col":3},"end":{"line_number":1098,"utf16_col":75}},"extent_utf16":{"start":{"line_number":1098,"utf16_col":0},"end":{"line_number":1203,"utf16_col":0}}}]}},"codeViewLayoutRoute":{"repo":{"id":1376544984,"defaultBranch":"main","name":"moonlight-steam-sync-decky","ownerLogin":"episode6","currentUserCanPush":false,"isFork":false,"isEmpty":false,"createdAt":"2026-09-18T23:55:17.000Z","ownerAvatar":"https://avatars.githubusercontent.com/u/6998849?v=4","public":true,"private":false,"isOrgOwned":true,"isArchived":false},"currentUser":null,"uploadToken":"ySkEPiQNQGEoRSNATjLNLS3kCJO-7yFryqwLfD4Vl5RABjMcmaJ8q_y5btOt_w_p7cDoAvZQngLQkeJqHQg0oA","allShortcutsEnabled":false,"treeExpanded":true,"path":"DEVICE-CHECKLIST.md","symbolsExpanded":false,"refInfo":{"name":"main","listCacheKey":"v0:1790988553.0","canEdit":false,"currentOid":"833e083bb80520f1f471726d50647042dcae18c4"},"helpUrl":"https://docs.github.com","githubDevUrl":null},"codeViewFileTreeLayoutRoute":{"fileTree":{"":{"items":[{"name":".github","path":".github","contentType":"directory"},{"name":"backend","path":"backend","contentType":"directory"},{"name":"cli","path":"cli","contentType":"directory"},{"name":"probes","path":"probes","contentType":"directory"},{"name":"py_modules","path":"py_modules","contentType":"directory"},{"name":"scripts","path":"scripts","contentType":"directory"},{"name":"src","path":"src","contentType":"directory"},{"name":"tests","path":"tests","contentType":"directory"},{"name":".gitignore","path":".gitignore","contentType":"file"},{"name":"AGENTS.md","path":"AGENTS.md","contentType":"file"},{"name":"CHANGELOG.md","path":"CHANGELOG.md","contentType":"file"},{"name":"CLAUDE.md","path":"CLAUDE.md","contentType":"symlink_file"},{"name":"DEVICE-CHECKLIST.md","path":"DEVICE-CHECKLIST.md","contentType":"file"},{"name":"LICENSE","path":"LICENSE","contentType":"file"},{"name":"README.md","path":"README.md","contentType":"file"},{"name":"THIRD_PARTY_NOTICES.md","path":"THIRD_PARTY_NOTICES.md","contentType":"file"},{"name":"eslint.config.js","path":"eslint.config.js","contentType":"file"},{"name":"install.sh","path":"install.sh","contentType":"file"},{"name":"main.py","path":"main.py","contentType":"file"},{"name":"package.json","path":"package.json","contentType":"file"},{"name":"plugin.json","path":"plugin.json","contentType":"file"},{"name":"pnpm-lock.yaml","path":"pnpm-lock.yaml","contentType":"file"},{"name":"project-icon.svg","path":"project-icon.svg","contentType":"file"},{"name":"pyproject.toml","path":"pyproject.toml","contentType":"file"},{"name":"requirements-dev.txt","path":"requirements-dev.txt","contentType":"file"},{"name":"rollup.config.js","path":"rollup.config.js","contentType":"file"},{"name":"ruff.toml","path":"ruff.toml","contentType":"file"},{"name":"tsconfig.json","path":"tsconfig.json","contentType":"file"},{"name":"vitest.config.ts","path":"vitest.config.ts","contentType":"file"}],"totalCount":29}},"fileTreeProcessingTime":21.308491999999998,"foldersToFetch":[]},"codeViewBlobLayoutRoute":{"codeLineWrapEnabled":false,"refInfo":{"name":"main","listCacheKey":"v0:1790988553.0","canEdit":false,"refType":"branch","currentOid":"833e083bb80520f1f471726d50647042dcae18c4","canEditOnDefaultBranch":false,"fileExistsOnDefault":true},"path":"DEVICE-CHECKLIST.md","blob":{"copilotSWEAgentEnabled":false,"dependabotInfo":{"showConfigurationBanner":false,"configFilePath":null,"networkDependabotPath":"/episode6/moonlight-steam-sync-decky/network/updates","dismissConfigurationNoticePath":"/settings/dismiss-notice/dependabot_configuration_notice","configurationNoticeDismissed":null},"displayName":"DEVICE-CHECKLIST.md","displayUrl":"https://github.com/episode6/moonlight-steam-sync-decky/blob/main/DEVICE-CHECKLIST.md?raw=true","headerInfo":{"blobSize":"74.8 KB","deleteTooltip":"You must be signed in to make or propose changes","editTooltip":"You must be signed in to make or propose changes","ghDesktopPath":"https://desktop.github.com","isGitLfs":false,"onBranch":true,"shortPath":"b6e01bc","siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2Fepisode6%2Fmoonlight-steam-sync-decky%2Fblob%2Fmain%2FDEVICE-CHECKLIST.md","isCSV":false,"isRichtext":true,"lineInfo":{"truncatedLoc":"1203","truncatedSloc":"1149"},"mode":"file"},"image":false,"isCodeownersFile":null,"isPlain":false,"isValidLegacyIssueTemplate":false,"isIssueTemplate":false,"isDiscussionTemplate":false,"language":"Markdown","languageID":222,"large":false,"planSupportInfo":{"repoIsFork":null,"repoOwnedByCurrentUser":null,"requestFullPath":"/episode6/moonlight-steam-sync-decky/blob/main/DEVICE-CHECKLIST.md","showFreeOrgGatedFeatureMessage":null,"showPlanSupportBanner":null,"upgradeDataAttributes":null,"upgradePath":null},"publishBannersInfo":{"dismissActionNoticePath":"/settings/dismiss-notice/publish_action_from_dockerfile","releasePath":"/episode6/moonlight-steam-sync-decky/releases/new?marketplace=true","showPublishActionBanner":false},"rawBlobUrl":"https://github.com/episode6/moonlight-steam-sync-decky/raw/refs/heads/main/DEVICE-CHECKLIST.md","renderImageOrRaw":false,"shortPath":null,"symbolsEnabled":true,"tabSize":4,"topBannersInfo":{"overridingGlobalFundingFile":false,"globalPreferredFundingPath":null,"showInvalidCitationWarning":false,"citationHelpUrl":"https://docs.github.com/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-citation-files","actionsOnboardingTip":null},"truncated":false,"viewable":true,"workflowRedirectUrl":null},"copilotInfo":null,"copilotAccessAllowed":false,"copilotSpacesEnabled":false,"modelsAccessAllowed":false,"modelsRepoIntegrationEnabled":false,"isMarketplaceEnabled":true},"codeViewBlobLayoutRoute.StyledBlob":{"rawLines":["# Device checklist","","Everything below needs a real Steam Deck (or SteamOS machine with Decky","Loader) in Game Mode; none of it is a merge criterion for any plugin PR","(spec section 4: \"on device\" acceptance items are transcribed here, not","enforced by CI). Run it top to bottom the first time a build reaches a","Deck. Findings that contradict the spec go to a Fable-class model (spec","section 5), not decided here.","","## 0. Setup","","1. Get a build onto the Deck: `curl -fsSL","   https://raw.githubusercontent.com/episode6/moonlight-steam-sync-decky/main/install.sh","   | sh` (`sudo` prompts, explained by the script; without a password","   set it offers a temporary one, section 5). For a build newer","   than the latest release, build off-device with `pnpm install \u0026\u0026 pnpm","   run build \u0026\u0026 backend/entrypoint.sh \u0026\u0026 python3 scripts/package.py`, or","   take the `Moonlight-Sync` artifact from a CI run, and install it by","   hand — `README.md`'s \"Manual install\". Either way Settings → About","   should show the bundled and installed CLI both at the plugin's version","   (the CLI is built from `cli/` with it; `0.4.0` before the move); the plugin","   installs or upgrades `~/.local/bin/moonlight-steam-sync` on first load.","2. SSH into the Deck while it sits in Game Mode (`passwd` once in Desktop","   Mode, then `sudo systemctl enable --now sshd`; see `probes/PROBES.md`","   §1.1 for the full one-time setup, which the PR-0 probes below also","   need).","3. Logs: `~/homebrew/logs/Moonlight Sync/moonlight-sync.log` (every CLI","   call with its arguments, the CLI's own stderr verbatim, both CLI","   versions at startup) and Decky's own log","   (`~/homebrew/logs/decky-loader/…` or `journalctl -u plugin_loader`) for","   backend tracebacks. Settings → About also shows a log tail and both CLI","   versions without leaving the panel.","4. To flip the controller-layout strategy by hand (no UI yet, spec §3.10):","   stop the plugin (`sudo systemctl stop plugin_loader` or unload it from","   Decky's developer menu), edit `\"layout_strategy\"` in","   `~/homebrew/settings/Moonlight Sync/settings.json` to `\"copy\"` or","   `\"picker\"` (removing the key restores the default, currently `\"copy\"`),","   then reload.","5. The CLI must work *from the plugin*, not only from SSH: plugin_loader","   is a PyInstaller binary whose `LD_LIBRARY_PATH=/tmp/_MEI…` once broke","   the CLI's `flatpak` call (\"moonlight CLI not found\") and its `import","   ssl` (\"5 consecutive network failures\"). With Moonlight installed as","   the Flathub flatpak, pick a host in the panel and search for a title in","   the match fixer; then `grep -n \"not found\\|network failures\"","   \"$HOME/homebrew/logs/Moonlight Sync/moonlight-sync.log\"` must find","   nothing newer than the install. To reproduce the loader's environment","   from SSH, while plugin_loader is running (its unpack directory goes","   when it exits): `env -i HOME=$HOME PATH=/usr/bin LD_LIBRARY_PATH=$(ls","   -dt /tmp/_MEI* | head -1) python3 ~/.local/bin/moonlight-steam-sync","   doctor` — the `moonlight:` line must name the flatpak (CLI 0.3.1 or","   newer; 0.3.0 prints `not found` there, which the plugin's cleaned","   environment hides). `ls -dt` takes the newest `_MEI*` directory; if","   another PyInstaller program is running too, check that the one picked","   holds a `libssl.so.3`. The loader's own `/proc/\u003cpid\u003e/environ` is not","   the shortcut it looks like: the service runs as root, so reading it","   needs `sudo`, and even the backend's deck-owned process refuses.","","## 1. PR-0: device probes","","The primary path is the scripted runner; the throwaway `steam-input-probe`","plugin is the fallback. Full procedure, venv setup and the exact commands:","[`probes/PROBES.md`](probes/PROBES.md). The fallback button plugin logs","to `~/homebrew/logs/steam-input-probe/steam-input-probe.log` (the loader's","`DECKY_PLUGIN_LOG_DIR`; a loader old enough not to set it falls back to","`~/homebrew/logs/steam-input-probe.log`). In order:","","- [ ] **Setup** (PROBES.md §1.1): SSH in, create the `~/.cache/msy-probes`","      venv, confirm the CEF DevTools port answers","      (`curl -s http://localhost:8080/json | grep -c SharedJSContext`).","- [ ] **V4**: `run_probes.py v4` — dump the first 20 of","      `collectionStore.allAppsCollection.allApps` (`appid`, `display_name`,","      `app_type`) and the current steamid3. Expect real entries and a","      plausible steamid3.","- [ ] **V1**: `run_probes.py v1 --appids \u003cowned appid\u003e,\u003canother\u003e` — dump","      `GetConfigForAppAndController` per appid plus the Deck controller","      index found by type. Expect a `template://` or `workshop://` URL for","      a game with a chosen layout, `default://…` for one left on Steam's","      guess, and the controller index (record it — it decides the","      `DECK_CONTROLLER_TYPE` fallback in `src/lib/layouts.ts` if it is not","      what the constant assumes).","- [ ] **V2** (state-changing, needs `--url`; make a throwaway shortcut","      first): `run_probes.py v2 --shortcut \u003cAPPID\u003e --url 'workshop://…'`","      (an id published for a real game you own) — calls","      `SetSelectedConfigForApp` then reads back. Expect the read-back URL","      to equal what was set. **This decides the strategy**: if it does not","      match, flip `DEFAULT_LAYOUT_STRATEGY` to `\"picker\"` in","      `src/lib/layouts.ts` and rewrite the README's \"Controller layouts\"","      section — escalate to a Fable-class model rather than doing this","      inline.","- [ ] **V5**: `run_probes.py v5 --shortcut \u003cAPPID\u003e [--run]` — dump","      `appStore.GetAppOverviewByAppID(appid).gameid` and, with `--run`,","      launch it. Expect a non-null overview once Steam has loaded the","      shortcut list, and (with `--run`) the shortcut to actually launch.","- [ ] **V3** (state-changing, needs `--yes` or an interactive confirm):","      `run_probes.py v3` — logs `pgrep -x steam` every 100 ms for 90 s","      around a `StartShutdown(false)` call, then reconnects to confirm","      Steam came back. Expect the log to show a clear gap between the","      client going away and coming back, confirming `StartShutdown(false)`","      is the restart from Game Mode (spec §2.3). **The measurement is","      `gaps` (from the pgrep samples), not `back_after_ms`**: that field","      is measured from the start of the watch and the reconnect only","      begins after the baseline plus the whole `--duration`, so it is","      bounded below by roughly 91 s whatever Steam does. Read it only as","      \"Steam came back: yes/no\" (PROBES.md §1.3).","- [ ] Run `run_probes.py all --appids … --shortcut \u003cAPPID\u003e [--url … --yes]`","      once everything above passes individually, and paste","      `probe-results.json`'s `--markdown` table into","      `~/specs/moonlight-steam-sync/decky-plugin.md` §2 (a Fable-class","      model does the actual spec edit).","","## 2. PR-5: backend, Quick Access panel","","- [ ] **First load installs the bundled CLI.** With no `~/.local/bin/moonlight-steam-sync`","      present, load the plugin. Expect it to appear in `~/.local/bin` after","      load, and Settings → About to show both the bundled and installed","      versions matching the plugin's own version (About's *Plugin","      version*): the CLI is built from `cli/` at the same version.","- [ ] **Counters, Sync now, the restart modal.** Press *Sync now*. Expect","      progress lines, then either the restart modal (writes pending) or","      \"nothing to do\"; the four counters and *Last sync* update after.","      The progress bar and its \"ARTWORK: TITLE N OF M\" label stay inside","      the panel, the bar as wide as the *Stop* button under it (before","      2026-09-23 the bar's fixed-width column overflowed the panel).","- [ ] **Stop right after Sync.** Press *Sync now*, then *Stop* within about","      a second (before the first progress line). Expect the panel to leave","      the progress view reporting a stopped run (not a run that keeps","      going), *Last sync* not claiming any added/replaced shortcuts, and","      `~/homebrew/logs/Moonlight Sync/moonlight-sync.log` (or Decky's own","      log) to show either \"stop: SIGINT to the sync run\" or \"stop: the","      sync run has not spawned yet; it will be interrupted\", exit 130.","- [ ] **Stop during the await-exit wait.** Start a sync that has writes to","      make, wait for the \"close Steam\" state, press *Stop*, reopen the","      panel. Expect the persistent \"Restart Steam to apply\" row to still be","      offered (nothing was written) and *Last sync* not to claim any","      added/replaced titles.","- [ ] **Unload during a run.** Start a sync, then reload the plugin","      (Decky's developer menu → reload, or reinstall). Expect","      `pgrep -af moonlight-steam-sync` to return nothing a few seconds","      later, and a fresh *Sync now* afterwards to start exactly one run.","- [ ] **Open Moonlight** launches the client shortcut without picking a","      game.","- [ ] **Desktop** / **Steam Big Picture** show on the panel when the active","      host publishes them, each starts its stream with the controller","      layout chosen on that shortcut, and neither shows once its entry is","      ignored and synced away.","- [ ] **Backend not answering at load.** Force the failure (open the panel","      immediately at Game Mode start, or temporarily rename","      `~/homebrew/plugins/Moonlight Sync/py_modules` to break a callable).","      Expect a Retry row carrying the backend's message instead of an","      endless \"Loading your library…\" spinner; restore, press Retry, expect","      the panel to fill in without a plugin reload.","- [ ] **The host is never asked on its own** (Decision 66). Open the panel,","      close it, reopen it, toggle the plugin off and on, open Settings →","      Titles: `moonlight-sync.log` shows no `--json list --host` without","      `--cached` for any of it, and (with several hosts) the host row","      reads a grey circle and \"N apps\" with **Check** and (when a MAC is","      known) **Wake** under it. A shut-down PC stays down through all of","      it.","- [ ] **Check asks once.** With several hosts, press Check: \"Checking…\",","      then a green circle and \"N apps\"; the log shows one live `--json","      list --host`. A second press within 10 s is answered from the memo","      (no new spawn). While a sync runs elsewhere (or the library is","      still loading) the whole Host row is dimmed, label and count","      included, and the dropdown does not open.","- [ ] **Check with one host.** With a single host, press Check: expect","      no \"Checking…\" text anywhere (there is no row to put it in), the","      Check button unavailable meanwhile, then **Check** / **Wake** gone","      when the host answered, or the red error line alone above them","      when it did not; one live `--json list --host` in the log.","- [ ] **Unreachable host row.** With several hosts and the active one","      unreachable, press Check: expect a red circle and the cached \"N","      apps\" in the Host row, and under the dropdown, above the divider,","      the CLI's error message and \"last seen \u003crelative time\u003e\" in red.","      For a host never listed the row says \"never synced\" and the error","      line is the message alone; with one host the line ends \"· never","      synced\" instead. A *Sync now* that fails with","      exit 3 paints the row the same way, and one that lists paints it","      green and takes the error line away, with no `check_host` spawned","      for either.","- [ ] **One host: no Host row.** With a single host, expect no *Host*","      row and no dropdown: **Check** / **Wake** directly under *Enable","      Sync* until the host is known reachable, then *Sync now* first. An","      unreachable host shows the error line alone above Check / Wake.","      Check / Wake sit as far below the divider above them as *Sync","      now* does when they are gone, with a divider of their own under","      them. Add a second host on Settings → Host: the row and its dropdown","      appear; Forget it: they go again.","- [ ] **The switcher takes the row's spare width.** With several hosts,","      expect the dropdown to start right of \"N apps\", never over it,","      and show a host name of a dozen characters whole, not cut at half","      the row; a longer name ends in \"…\" rather than pushing the count","      under the dropdown. The dropdown opens, lists every host and","      switches as before (the row is a `Field` with","      `childrenContainerWidth=\"max\"` around a `Dropdown`, not a","      `DropdownItem`; the count running under the dropdown was seen on","      a Deck 2026-09-29 and the fix measured there over the debugger).","- [ ] **In-game guard.** While a game is running, expect no restart","      countdown and the text \"A game is running. Restart Steam when you're","      done.\"","- [ ] **In-game guard on the persistent row** (Decision 29). With a","      pending \"Restart Steam to apply\" row (either kind: a pending write,","      or artwork), launch a game and open the panel. Expect the row to be","      disabled with the subtitle \"A game is running; exit it first\", and","      pressing it to do nothing. Exit the game: expect the row to become","      pressable again. Also press the row just before launching a game so","      the re-run's `awaiting-steam-exit` lands while the game is up:","      expect the restart modal (with its in-game text) rather than Steam","      shutting down under the game.","- [ ] **Later → the persistent row → immediate restart.** Press *Later* on","      the restart modal, expect the \"Restart Steam to apply\" row; press it,","      expect the sync to re-run with `immediate_restart: true` and shut","      down at once on the next `awaiting-steam-exit`, no modal.","- [ ] **Art-only restart** (a run with only artwork filled, no shortcut","      writes) offers the same modal/row with \"New artwork needs a Steam","      restart to show\".","- [ ] **Restart countdown slider.** Settings → Advanced: expect the","      countdown to run 0-30 s and no further (Decision 30 — the CLI stops","      waiting for Steam after 60 s, so a longer countdown would race it).","      Set 0 and expect the modal to show buttons with no countdown; set 30","      and expect it to count down from 30. A `settings.json` left over","      from an older build with `\"restart_countdown_s\": 60` must read back","      as 30, not break the settings page.","- [ ] **Re-fetch all art with no `--commit` support.** Install (or","      hand-build) a CLI whose `art --help` lacks `--commit`. Expect","      *Re-fetch all art* to be disabled, and if pressed anyway from a stale","      state, the message to read \"re-fetching art from Game Mode needs a","      CLI whose art command accepts --commit\" — never \"CLI too old (0.4.0,","      needs 0.4.0)\".","- [ ] **Nothing about the other hosts.** Add three hosts (one active,","      two not), sync the inactive ones once each so they have cached","      listings. Expect the Host row to say nothing of them: the circle","      and the active host's count only, no \"parked\" line.","- [ ] **The SteamGridDB key round-trip and Test.** Set a key on the","      Artwork page, expect only its last four characters ever shown","      anywhere (panel, events, logs); press *Test*, expect a pass/fail","      result with no more of the key revealed.","","## 3. PR-6: Titles page, Change match","","- [ ] **Titles list, idle.** With nothing running, press the panel","      header's list button. Expect \"N published by MY-GAMING-PC · sorted by name\"","      and rows with capsules, badges and chips.","- [ ] **Titles page mid-sync.** Start *Sync now*, then open Settings →","      Titles while it runs. Expect \"N published by MY-GAMING-PC[ · listed","      \u003crelative time\u003e] · refreshes when the sync finishes\", the note \"A","      sync is running; this list refreshes and matches can be changed when","      it finishes\", and *Change match* disabled on every row.","- [ ] **`list --cached` always, never live `list`** (Decision 66). Whether","      or not a sync is running, opening Titles spawns only `--json list","      --cached --host MY-GAMING-PC`, never a live `--json list --host","      MY-GAMING-PC`; the log proves it.","- [ ] **Refresh after the sync.** Let it finish (restart when prompted),","      reopen Titles: it re-lists from the cache the sync just wrote, the","      headline returns to \"N published by MY-GAMING-PC · listed just now ·","      sorted by name\", *Change match* is enabled again.","- [ ] **Never-synced host.** On a host that has never been synced, open","      Titles: \"MY-GAMING-PC was never synced; Sync now lists its titles\"","      and a *Sync now* button, no rows, filters or Retry. Add OFFICE-PC","      and press *Check* on it (both cache its list), make it active and","      open Titles: the same line, still no rows. Press the page's *Sync","      now*: the line reads \"… this list fills in when the sync finishes\",","      the button goes, and the titles fill in after the run ends.","- [ ] **Change match races a sync.** Open *Change match* on a title; before","      pressing *Use this*, start a sync from the panel; then press *Use","      this*. Expect \"A sync is already running\", no pin written","      (`matches.json` unchanged), the row keeps its old match.","- [ ] **Pin, then apply on next sync.** After that sync finishes, press","      *Use this* again on the same title: expect a pin, the toast \"Pinned;","      sync to apply\", the pinned and \"art refreshes on next sync\" chips,","      and the next *Sync now* to turn an owned-game pin into a Stream","      button (Hades II re-matched from a fuzzy Hades hit is the spec's own","      example: expect a Stream button on Hades II and the visible tile","      gone).","- [ ] **Ignore.** *Ignore* on a title removes its tile on the next sync;","      *Unignore* restores it.","- [ ] **Owned-apps mismatch recovery.** Sign in as a second Steam user on","      the Deck, open Titles: expect exactly one `write_owned_apps` in the","      log (recovered once), then a successful list and status — not two","      write attempts.","- [ ] **Paging.** With 300+ rows, page through with the D-pad; expect pages","      of 50 and correct navigation at both ends.","- [ ] **Recently added.** After updating to a build with the chip, open","      Titles: the order is by name and `added.json` in the settings","      directory lists every title with `null` (no `Moonlight`, `Desktop`","      or `Steam Big Picture` in it). Publish a new app on the","      host, *Sync now*, restart when prompted, reopen Titles and press","      **Recently added**: the chip lights, the headline ends \"sorted by","      recently added\", the new title is the first row and reads \"added","      today h:MM AM/PM\" (or \"HH:MM\" with Steam's 24-hour clock on), every","      other row follows by name with no \"added\" text,","      and `added.json` has a time for the new title alone. Pick a filter:","      the order holds. Leave the page and reopen it: still newest first.","      Press the chip again: by name, no \"added\" text. With the chip on,","      start a sync and reopen Titles: the headline ends \"refreshes when","      the sync finishes · sorted by recently added\".","- [ ] **The clock follows Steam's.** With Steam's **Settings → System →","      24-hour clock** off (measured on the Deck 2026-09-29: the toggle","      writes `settingsStore.friendSettings.b24HourClock`, which the header","      clock reads too), the Titles page's \"added today 2:02 PM\" and the","      panel's *Last sync* use the 12-hour clock; turn it on and reopen","      them: \"added today 14:02\", \"Today 14:02 · …\", with no plugin reload.","- [ ] **No layout text on the rows.** No Titles row says `layout: …`;","      the note beside a game's Stream button still does while a default is","      set.","- [ ] **Show more keeps your place.** Press *Show 50 more* with the D-pad.","      Expect focus on the last row that was already shown (not on the","      button, now 50 rows further down), and one press of down to land on","      the first new row.","","## 4. PR-7: Stream button and controller layouts","","*The layout items in this section were written for the copy from the real","Steam game, which spec 3.16 (§8 below) replaced with a default layout you","adopt: nothing copies from the real game any more, there is no Advanced","toggle, and the Titles rows have no layout button any more (Decision 75:","a title's layout is picked from Steam's controller settings while it","streams). Run the button, guard, route-patch, picker-strategy and","controller-index items here as written; the copy-specific ones are marked","and superseded by §8.*","","- [ ] **Button placement.** With a host active and a sync done, open the","      library page of an owned game the host publishes: expect a Stream","      row. Open a game the host does not publish (or one parked / matched","      to nothing): expect no Stream row.","- [ ] **Focus order beside another plugin.** With ProtonDB Badges (or any","      plugin that adds to the game page) installed, open such a page and","      press up from *Play*: expect the Stream button first and the other","      plugin's badge after it, as they are drawn, and the same order in","      reverse going down.","- [ ] ~~**First press copies a community layout.**~~ Superseded by §8","      (nothing copies from the real game any more). With **no default","      layout set**, press *Stream* on a game whose real entry has a","      `workshop://` layout: expect the launch with no layout change and no","      `layouts.json` write.","- [ ] **A hand-picked layout on the hidden entry is never overwritten.**","      Pick a layout on the hidden shortcut (Steam's controller settings","      while it streams), set a different default layout","      (§8), press *Stream*, then *Sync now* and restart when prompted.","      Expect the hidden shortcut to keep the hand-picked layout after all","      three, `layouts.json` to read `\"kept\"` with that URL and `\"applied\":","      null`, the game page's Stream note to read \"own layout\".","- [ ] ~~**Steam default.**~~ Superseded by §8: with a default set, a","      hidden shortcut on Steam's default gets the default layout on its","      first press (`\"default\"`); without one, nothing is written.","- [ ] **One row, however often the page is opened.** Open the same owned","      game's library page several times, backing out to the library and in","      again between each (and switch to another game's page and back).","      Expect exactly one Stream row every time — never two stacked — and","      no slowdown building up as the page is re-entered. The route patch","      runs `afterPatch` on each render, so a duplicated or accumulating","      row shows up here and nowhere else.","- [ ] **No running-app guard.** Launch any game, then open another","      published game's library page and press *Stream*: expect no toast","      and the Stream shortcut launched as usual (Steam switches to it;","      with a stream already going, Moonlight's own UI takes over), the","      default layout applied and `layouts.json` updated as on any press.","- [ ] ~~**Advanced toggle.**~~ Removed: the toggle is gone (spec Decision","      47). Settings → Advanced shows *Default controller layout* instead","      (§8); \"no default set\" is the off switch.","- [ ] **Post-restart layout walk.** With a default layout set (§8), make a","      sync write shortcuts, restart when prompted, wait up to 90 s after","      Steam is back. Expect `layouts.json` to gain an entry per non-parked","      entry (default / kept / unavailable), the log to show the walk","      starting, each target, and \"layout walk done\"; the panel to show no","      restart row afterward.","- [ ] **A title's layout while it streams.** Press *Stream* on a game,","      then the Steam button: *Controller settings* there must be the hidden","      shortcut's (its title matching the game), and a layout picked there","      must still be selected at the next stream. The Titles rows have no","      *Layout* button (Decision 75); Advanced's *Layout* is §15's.","- [ ] **Picker strategy.** Set `\"layout_strategy\": \"picker\"` in","      `settings.json` (§0.4 above) and reload. Expect a Stream press to","      launch with no layout change and no `layouts.json` write, the","      Advanced *Default controller layout* field to read \"Off for this","      device…\" with *Layout* as its only button (no *Make default*, no","      *Clear*) and the plugin's only layout action, and no walk after a","      sync's restart; remove the key and reload to","      return to `copy`.","- [ ] **Controller index by type.** In the CEF console (or the PR-0 kit)","      with a Bluetooth pad also paired, run","      `ControllerStore.GetControllers().map(c =\u003e [c.nControllerIndex,","      c.eControllerType, ControllerStore.GetControllerTypeString?.(c.eControllerType)])`","      (the global is `ControllerStore`, capital C, on the client probed on","      2026-09-20; the list is empty while no controller is connected).","      Expect the built-in controller not at index 0 when the pad connected","      first, its type string `\"controller_steamcontroller_neptune\"`, and","      `eControllerType` `4`; if not 4, correct `DECK_CONTROLLER_TYPE` in","      `src/lib/layouts.ts`. **Still open:** the first probe device was a","      SteamOS box with a separate Steam Controller (index 0, type 10,","      `\"controller_steamcontroller_triton\"`), so this has not been seen on","      a Deck.","","## 5. PR-8: this checklist and `install.sh`","","- [ ] **`install.sh` on a Deck**: run the","      one-liner from a clean Deck (no plugin installed yet), with a","      password set for the `deck` user (the next item is the Deck without","      one). Expect `sudo` to ask for it (unzip into `~/homebrew/plugins/`,","      restart `plugin_loader`),","      then Moonlight Sync appears in the Quick Access menu's Decky tab,","      and a final line naming the version that actually landed (read from","      the installed `package.json`, not the tag asked for). Re-run it;","      expect it to succeed again (reinstall/overwrite) without asking","      anything unexpected.","- [ ] **`install.sh` on a Deck with no password.** On a Deck whose","      `deck` user has none (`passwd -S deck` says `NP`; `sudo passwd -d","      deck` gets a Deck that has one back there), run the one-liner.","      Expect the question *Set a temporary password for the install?*","      naming `Decky!`. Answer `n`: expect exit 1, \"nothing was installed\",","      `passwd -S deck` still `NP` and no plugin directory. Run it again","      and answer `y`: expect no `sudo` prompt at all, no *New password:*","      prompt and no pause after the answer, the install and the","      restart, *Removed the temporary password* before the final line,","      and `passwd -S deck` back at `NP` afterwards. Run it once more,","      answer `y` and press Ctrl+C as soon as it says *Temporary password","      set*: expect `passwd -S deck` at `NP` again. **Unmeasured until this is","      walked:** that SteamOS's `passwd` takes the new password from a","      pipe (run under `setsid`, so with no terminal to ask on) and that","      `passwd -S` answers for one's own account without root (both are","      what Decky's installer relies on). Should the pipe not be read, the","      script says *sudo did not accept the temporary one* and installs","      nothing.","- [ ] **`install.sh` on a device without Decky Loader.** On a device","      that has none (no `~/homebrew/services/PluginLoader`), run the","      one-liner. Expect *Decky Loader is not installed* and the question","      *Install Decky Loader now?* before anything is downloaded. Answer","      `n`: expect exit 1, \"nothing was installed\", no `sudo` prompt and","      no `~/homebrew`. Run it again and answer `y`: expect Decky's own","      installer's output (*Installing Steam Deck Plugin Loader","      release...*), *Decky Loader is installed.*, then the plugin's","      install as in the items above (on a Deck with no password, the","      temporary-password question comes between the two downloads and","      Decky's installer), and at the end the two lines saying the Decky","      tab appears once Steam has restarted. Return to Gaming Mode:","      expect the Decky tab with Moonlight Sync in it. Run the one-liner","      once more: expect no question about Decky Loader. **Unmeasured","      until this is walked:** that Decky's `install_release.sh` runs to","      the end as `sudo bash \u003cfile\u003e` with stdin from `/dev/null` (it is","      documented as `curl … | sh`, where it re-executes itself under","      sudo), and that a return to Gaming Mode is enough for the tab to","      appear.","- [ ] **The README's manual install.** With the plugin not installed,","      follow README \"Manual install\": Decky's settings → General →","      *Developer mode* on, then Developer → *Install Plugin from ZIP","      File* → *Browse*, choose a release's `Moonlight-Sync.zip`, confirm","      Decky's dialog. Expect Moonlight Sync in the Decky tab with no","      restart, and Settings → About showing that release. Do it again","      over the installed copy: expect Decky to ask to reinstall or","      overwrite, and the plugin to reload. **Unmeasured until this is","      walked:** the labels as the README spells them.","- [ ] **A missing release is explained.** Run with","      `MOONLIGHT_SYNC_VERSION=v9.9.9`; expect","      `install.sh: could not download …/Moonlight-Sync.zip (is v9.9.9","      released?)` on stderr, exit 1 and nothing written to","      `~/homebrew/plugins/`.","- [ ] **`MOONLIGHT_SYNC_VERSION` pinning.** Run with","      `MOONLIGHT_SYNC_VERSION=v0.1.0 sh install.sh` (or the exact tag);","      expect the version-specific download path, not `latest/download`.","- [ ] **Uninstall.** Follow the README's \"Uninstall\" section","      (`sudo rm -rf ~/homebrew/plugins/\"Moonlight Sync\"` + restart","      `plugin_loader`); expect the Quick Access entry to disappear and","      `~/.local/bin/moonlight-steam-sync` (the CLI) to be untouched.","","## 6. Host apps: the panel buttons replace the tiles (spec 3.14)","","Needs a host that publishes `Desktop` and `Steam Big Picture` under those","names, and ideally an install from before this change: two visible tiles,","one of them with a controller layout chosen on it. Note each tile's appid","first (`moonlight-steam-sync --json status`).","","- [ ] **The first sync hides the two tiles in place.** Upgrade the plugin,","      press *Sync now*. Expect the plan to count them as hidden, never as","      replaced (the log's CLI line says `N to hide`; no `replace` event for","      either name), one Steam restart, and afterwards no `Desktop` /","      `Steam Big Picture` tile anywhere in the library, non-Steam","      collection included.","- [ ] **Same appid, nothing lost.** `--json status --hide-host-apps` shows","      both entries with the appids noted above, `hidden: true`,","      `parked: false`, `host_app: true`, and their artwork slots still","      filled; no image was downloaded for them by that sync.","- [ ] **Both panel buttons launch.** *Desktop* and *Steam Big Picture* each","      start their stream from the Quick Access panel; Recent Games and the","      in-game overlay show the entry with its artwork.","- [ ] ~~**Each layout button opens the configurator.**~~ Removed: the","      panel has no layout button per host app any more (spec Decision","      67), nor on the Titles page (Decision 75); a host app's layout is","      picked from Steam's controller settings while it streams, and the","      one *Layout* button, Advanced's since Decision 77, is the Moonlight","      entry's (§15).","- [ ] **A layout chosen before the upgrade is still selected** (same","      appid): it applies in the stream, and Steam's controller settings","      during the stream show it.","- [ ] **While a game runs** the two launch buttons still work: a press","      runs the host app's shortcut as usual.","- [ ] **On a client without `SteamClient.Apps.ShowControllerConfigurator`**","      (if one turns up) Advanced's *Default controller layout* row has","      *Make default* and *Clear* only.","- [ ] **Counters.** Do this one *before* the first sync: `status` carries","      the flag from the upgrade on, so the two still-visible tiles already","      read `host_app: true`. Right after the upgrade *Shortcuts* is two","      lower than before (and *Unmatched* lower by however many of the two","      had no match); *Stream buttons* is unchanged. The sync that hides the","      two then moves none of the three.","- [ ] **Titles page.** Both rows read **host app**, show under *All* only,","      and have *Change match* and *Ignore* (no *Layout*, as on every row).","      In *Change match* every result reads **art only**,","      including a game this account owns; pin one, sync, and expect the","      entry to stay a hidden host app with new artwork and no Stream button","      on that game's library page.","- [ ] **No layout copied onto them.** *(Written for the layout copy that","      spec 3.16 replaced; §8 covers them now.)* Without a default layout","      set, after a sync's restart the layout walk never touches the two","      entries (`layouts.json` has no `copied` / `kept` / `unavailable`","      record for their appids; a `picker` record from an older version's","      *Choose layout…* may stay). With a default set they get it, like","      every other entry (§8).","- [ ] **Ignore removes the button.** Ignore `Desktop` on the Titles page and","      sync: the entry is removed and the panel's *Desktop* row is gone.","      Unignore and sync: it comes back, hidden from the start.","- [ ] **Switching hosts.** With a second host that does not publish them,","      the two entries are parked (counted under parked, buttons gone); back","      on the first host they are unparked *hidden*, never as tiles.","- [ ] **Getting the tiles back** (README, \"The Quick Access panel\"): the","      full command there (`--owned-apps`, `--ignore-file`,","      `--client-shortcut`, `--park-unpublished`, no `--hide-host-apps`), run","      from a terminal, shows the two tiles again with art and layout intact","      and changes nothing else: every Stream button stays hidden, no ignored","      title comes back, the Moonlight client entry stays. The next *Sync","      now* hides the two again.","","## 7. Hidden shortcuts and the Streaming collection (spec 3.15)","","None of `collectionStore.SetAppsAsHidden`, `BIsHidden`, `userCollections`,","`NewUnsavedCollection`, `AsDragDropCollection` or a collection's `Save` /","`Delete` was probed before this was built; every item here is a first","measurement. **Spec 3.17 changed the collection items below:** the","collection now exists only with *Hide Stream shortcuts* off, beside the tab, and holds","shortcuts only; §10 has the items that replace the collection ones here,","and the hiding items still stand.","","- [ ] In the CEF console: `typeof collectionStore.SetAppsAsHidden`,","      `typeof collectionStore.BIsHidden`, `typeof","      collectionStore.NewUnsavedCollection` are all `\"function\"`, and","      Settings → Advanced shows both toggles enabled (a disabled toggle","      says the client has no such call).","- [ ] After the plugin loads with an existing install: the *Non-Steam* tab","      holds only the visible shortcuts (the panel's **Shortcuts** counter),","      no Stream-button shortcut, no `Moonlight`, no `Desktop` / `Steam Big","      Picture`, no parked title. They are all under Steam's *Hidden*.","- [ ] A **Streaming** collection exists and its size is the panel's","      **Stream buttons** + **Shortcuts**. An owned game in it opens the real","      game's page (with the Stream button); an unowned one is the shortcut.","- [ ] A Stream press still launches the (now hidden) shortcut, and the","      panel's *Open Moonlight*, *Desktop* and *Steam Big Picture* still work.","- [ ] *Hide Stream shortcuts* off: the Stream-button shortcuts appear under","      *Non-Steam* within a moment, the other hidden entries do not, and the","      collection is unchanged. Stream a game, quit: its shortcut is at the","      front of Home. On again: they are gone again.","- [ ] *Streaming collection* off: the collection is deleted. On: it is","      back, complete.","- [ ] Sync after the host gains a title: once Steam has restarted, the new","      Stream shortcut is hidden and the game is in the collection without","      opening the panel (allow 90 s). Note whether the tile is visible for","      a moment first.","- [ ] Switch hosts and sync: the first host's visible tiles are hidden","      (parked) and leave the collection; switching back shows them again.","- [ ] *Remove everything*: after the restart the collection is gone and","      Steam's *Hidden* holds none of the plugin's entries.","- [ ] After a host switch (or an *Ignore* + sync) that removes a visible","      shortcut: the collection's `allApps` no longer lists its appid (the","      plugin cannot remove a member whose overview is gone, so the client","      has to drop it itself).","- [ ] The first sync on a fresh install creates the collection with every","      member in it (the port saves a new collection empty, then adds).","- [ ] Open the panel and the Titles page a few times: nothing in the","      library flickers and `collectionStore` is not written again (the","      reconcile only applies differences).","","## 8. The default controller layout (spec 3.16)","","The plugin never reads the real game's layout any more: one layout you","adopt from the Moonlight entry with Advanced's *Make default* (the one","place since Decision 74: the Titles rows and the library gear menu no","longer offer it, and since Decision 77 the panel does not either) is put on every entry the plugin manages, and a","selection the plugin did not make is never overwritten (`applied` in","`layouts.json` is what the plugin last set per shortcut). Two things here","are unmeasured on any device: whether a `template://` layout sticks on","other titles, and the name and arguments of the call that clears a","selection (`SteamClient.Input.ClearSelectedConfigForApp(appid,","controllerIndex)`, spec 3.16.3 **[verify]**). Do this section with a","controller connected; *Make default* answers \"Connect a controller first\"","otherwise.","","- [ ] **Adopt a community layout.** Give the Moonlight entry a","      `workshop://` community layout (Advanced's *Layout*), then press","      *Make default* beside it. Expect","      a confirm titled `Use “\u003clayout title\u003e” as the default layout?`; after","      OK, a toast `Default layout applied to N titles` (N = every non-parked","      entry that had no layout of its own, host apps and the Moonlight entry","      included) plus ` · M kept their own` for the ones that had; Settings →","      Advanced → *Default controller layout* reading `\u003ctitle\u003e · Workshop","      layout · set today …`; `layouts.json` with `\"default\"` and `\"applied\"`","      set to the URL for each. A fresh title (a game the host just started","      publishing, after its sync and restart) streams on it without any","      press first.","- [ ] **No other place adopts.** The Titles rows have no *Layout* button","      (Decision 75), a streamed game's gear menu has no *Use as","      Moonlight Sync default layout* (Decision 74), and the Quick Access","      panel has no layout row (Decision 77).","- [ ] **Adopt a `template://` layout [verify].** Pick one of Steam's own","      templates on the Moonlight entry, adopt it. Expect the same toast; check","      `layouts.json`: every other entry must read `\"default\"` with the","      `template://` URL. If they read `\"unavailable\"` instead (the URL did","      not stick when read back), report it: `template://` then has to come","      out of `DEFAULT_LAYOUT_SCHEMES` (spec 3.16.1, Decision 49), a","      Fable-class decision.","- [ ] **An edited-in-place layout is refused.** Edit the Moonlight","      entry's layout in Steam's layout screen without exporting it (it","      reads `autosave:///…`), then *Make default*. Expect only the toast","      `This layout was edited in place and only exists for “Moonlight”.","      In Steam's layout screen choose Export, select the exported copy,","      then try again.`, no confirm, no change.","- [ ] **An exported personal layout works.** Export that layout, select the","      exported copy on the Moonlight entry (it now reads `workshop://…`),","      adopt it: expect the normal confirm and toast.","- [ ] **No layout is refused.** With the Moonlight entry never given a","      layout (reads `default://…`, or a `template://` Steam only offers),","      expect the toast `“Moonlight” has no layout chosen yet. Use Layout","      first.` (§15 has the same item.)","- [ ] **Hand-picked survives, plugin-applied moves.** With a default set,","      pick a different layout by hand on one title (Steam's controller","      settings while it streams), then give","      the Moonlight entry another layout and adopt it as a *second* default.","      Expect the hand-picked title unchanged (`\"kept\"`, `\"applied\": null`,","      Stream note \"own layout\"), every plugin-applied title on the new default, the","      Moonlight entry (the source) unchanged, and the toast's `M kept","      their own` counting the hand-picked one and the source.","- [ ] **A pre-upgrade copied title moves.** On an install upgraded from a","      version that copied layouts from the real game (`layouts.json` has","      `\"copied\"` records without an `\"applied\"` key), set a default: expect","      those titles to move to it (Decision 46) as long as their selection is","      still the copied URL.","- [ ] **Clear [verify].** Settings → Advanced → *Clear*: expect the confirm","      `Clear the default layout?`, then the toast `Default layout cleared ·","      removed from N titles`. Every plugin-applied title must be back on","      Steam's default (the layout screen shows nothing selected; the row","      reads \"Steam default\", `\"applied\": null`), and hand-picked ones must","      keep their layout. In the CEF console first check `typeof","      SteamClient.Input.ClearSelectedConfigForApp`: if it is not","      `\"function\"`, the toast reads `Default layout cleared. This Steam","      client cannot unset a layout, so titles keep the one they have.` and","      nothing changes on any title; if it is a function but titles are not","      back on Steam's default afterwards (`\"unavailable\"` with the old URL","      in `layouts.json`), the call's name or arguments are wrong: report","      it (spec 3.16.3), do not guess at another.","- [ ] **A sync's new titles get it after the restart.** With a default","      set, add a title on the host, *Sync now*, restart: within 90 s the new","      shortcut is on the default (`layouts.json`, and the layout screen).","- [ ] **The host apps, the client entry and a visible shortcut get it.**","      After any of the above, check `layouts.json` for `Desktop`, `Steam","      Big Picture`, `Moonlight` and a visible (non-Stream) shortcut:","      `\"default\"` with `\"real_appid\": null` for the first three.","- [ ] **Not held back.** Start a game, then adopt a default from the panel","      or clear it from Advanced: it goes through (the toast appears) while the game","      runs; a Stream press is still refused meanwhile.","","## 9. Fixtures vs. real CLI","","- [ ] Run, on the device, against the real installed CLI:","      `moonlight-steam-sync --json list --hide-host-apps`, `--json status","      --hide-host-apps` (the plugin always passes that flag, and the","      fixtures carry its keys: `kind: \"host-app\"`, `entry.host_app`), `--json host","      show`, `--json search \"Portal\"`. Diff each event's key set against","      the corresponding fixture under `tests/fixtures/common/` (§3.6.3 of","      the spec: `tests/test_fixtures.py` checks the fixtures against the","      schema, but never against a real CLI run). A mismatch means the CLI","      and the plugin have drifted from the spec's §3.4.6 schema on one side","      or the other — escalate to a Fable-class model rather than editing","      either side ad hoc.","","## 10. The Streaming tab (spec 3.17)","","The `/library` route's render tree was measured on 2026-09-22 through the","CEF debugger (route element → the library home, which reads the route's","tab id → the library page, `tab` prop, which builds its tab array in a","`useMemo` and validates the requested id against it in its own render →","the tabbed page `{tabs, activeTab, onShowTab, …}` behind an observer","wrapper → the tab row, which renders and cycles over `tabs`; a built-in","tab is `{id, title, renderTabAddon, content, footer}`). The patch still","digs for the tab bar's `tabs` array (four component levels at most) so a","client that adds a level keeps working, and clones a built-in tab's grid","element with a synthetic collection whose surface is a guess at the","client's `Collection`. The items after the first two are still first","measurements.","","- [ ] With an install that has synced: the library's tab bar has a","      **Streaming** tab after *Non-Steam*, and it opens. If there is no","      tab, note the CEF console: `Moonlight Sync: could not patch the","      library tabs` names a throw; silence means the walk never found a","      `tabs` array (report the route element's shape: `typeof","      route.children.type`, whether `props.renderFunc` exists).","- [ ] The tab can be **reached**: L1/R1 from *Non-Steam* lands on it, and","      moving along the tab row with the D-pad stops on it. Found","      2026-09-22: both asked the library for it (`onShowTab` navigates","      to `/library/tab/MoonlightSyncStreaming`), and the page answered","      with its first tab, since it validates the id against its own tab","      array in its render, before the injection; `activeTabFor` puts the","      id back on the bar element. If it is skipped again, report from","      the CEF console the one `Moonlight Sync: library tabs found on …`","      line (the bar's prop names, `activeTab`, a built-in tab's keys, the","      template's type and prop names) and whether a `Moonlight Sync:","      library tabs, onShowTab(\"MoonlightSyncStreaming\")` line appears","      when you try: none means the bar never asked for it; one that does","      not open it means the page's `tab` prop is no longer the requested","      id (`requestedTab` in `libraryTabs.tsx`).","- [ ] The tab's own panels take focus: with no host, or before a sync,","      the *No host yet* / *Nothing to stream* line gets the focus ring.","      They are a `Focusable` with `focusableIfNoChildren` and a no-op","      `onActivate` (both unmeasured); Settings → About's log panel is a","      plain `Focusable` over text, so whether *it* takes focus says","      whether a plain one already suffices.","- [ ] The tab's tiles are the panel's **Stream buttons** + **Shortcuts**:","      an owned game opens the real game's page (with the Stream button),","      an unowned one is the shortcut. Sorting, the footer legend and the","      focus ring behave like *Non-Steam*'s. A blank tab, or a line that","      starts *Moonlight Sync could not draw this tab on this client*","      (the plugin's own error boundary; the console has the same error","      with its component stack), means the grid read something the","      synthetic collection lacks: report the console error, do not guess","      at the field.","- [ ] Steam's own tabs are unchanged, and Settings → Moonlight Sync's","      tab bar has no Streaming tab (the injection only takes a tab bar","      some tab of which renders a collection).","- [ ] Nothing remounts: switching between library tabs and back does not","      flash or reload the grid (each patched component type is cached).","- [ ] The tab is not a collection: *Collections* has no *Streaming* entry,","      and on a second machine signed into the account nothing appears.","- [ ] **Upgrade from v0.4.0**: the old *Streaming* collection loses this","      device's owned games and shortcuts at the first load and is gone","      once empty; a member the plugin never made (add one by hand first)","      stays, and so does the collection.","- [ ] After a sync that adds or removes a title, the tab follows without","      reopening the library (its content reads the store).","- [ ] *Hide Stream shortcuts* off: the Streaming tab stays (its tiles","      unchanged) and a *Streaming* collection appears within a moment","      with the Stream shortcuts and the visible shortcuts, no owned game.","      On again: the collection and its shortcuts stay (hidden, so the","      collection looks empty in the library), and the tab is untouched.","- [ ] *Streaming tab* off: the tab is gone (leave the library and come","      back) and this device's shortcuts leave the collection, parked ones","      too (deleted once empty). On: both back.","- [ ] The first sync with the collection in effect on a fresh install","      creates it with every member in it: whether the client lists a","      just-added member at once is unmeasured, so the reconcile never","      deletes on the pass that added; if the collection is there but","      empty after that pass, the next reconcile (open the Titles page)","      must **not** delete it.","- [ ] Switch hosts and sync: the tab shows the new host's titles only.","- [ ] *Remove everything*: after the restart the tab reads \"Nothing to","      stream from … yet\" and, with the collection in effect, the","      collection is gone once the client has dropped the removed","      shortcuts.","- [ ] Unload the plugin (Decky → the plugin's menu → Uninstall, or a","      reload): leave the library and come back: the tab is gone, and the","      library still works if it had been the active tab.","","## 11. Wake-on-LAN (spec 3.18)","","The MAC source and the packet were tested off-device against a","hand-written `Moonlight.conf` and a recording socket (`tests/test_wake.py`);","the Deck's own file, the network and the PC's firmware are what these","check. Two things ran on the SteamOS box on 2026-09-23: its `Moonlight.conf`","carried the MAC as a *quoted* `@ByteArray` (a raw byte of the MAC was a","comma), which the parser then read as unknown (fixed, with that line's","shape as a test); and a packet capture on the same LAN showed Moonlight's","own `list` sending a magic packet with the PC's MAC, which is what","Decision 66 answers.","","- [ ] **Moonlight's MAC is read.** With the host paired in the Flathub","      Moonlight, the panel's host row shows **Wake** beside **Check**","      while the host is not known reachable. If there is no Wake, open","      `~/.var/app/com.moonlight_stream.Moonlight/config/Moonlight Game","      Streaming Project/Moonlight.conf` and note the host's `mac=` line:","      `@ByteArray()` (quoted or not) means Moonlight has none (a Sunshine","      host that reports no MAC; there is no field to enter one since","      2026-09-26); anything else that still gives no Wake is a parser gap:","      paste the line's shape (hex masked) into an issue.","- [ ] **Wake appears and sends.** Shut the PC down (a sleep state","      Wake-on-LAN is enabled for). Open the panel: **Check** and **Wake** are side by","      side, under the host row's cached count when there are several hosts (the PC stays","      down: nothing was asked). Press Wake: a toast \"Wake-on-LAN packet","      sent to \u003chost\u003e. Give it a minute, then Check.\" and, in","      `moonlight-sync.log`, `wake_host \u003chost\u003e: N packets sent (moonlight","      MAC)` with N = (1 + the host's addresses) × 8. The PC wakes; Check","      after a minute goes green.","- [ ] **Check wakes it too.** Shut the PC down again; press Check instead:","      Moonlight's own `list` sends the packet, so the row goes red first","      (the 30 s seek times out) and the PC comes up anyway; Check again","      after a minute goes green.","- [ ] **No MAC in Moonlight:** the row shows Check alone, and Settings →","      Host has no MAC field (removed 2026-09-26). A `wake_macs` key left","      in `settings.json` by an older build changes nothing: no Wake, no","      `(settings MAC)` in the log.","","## 12. The on/off toggle (spec 3.19)","","Off-device the controller is covered (`controller.test.ts`, \"the on/off","toggle\"); what the client does with a hidden visible shortcut, the tab","removed from a bar it was in, and a Stream row on a page already open","are what these check. Start with the plugin on, synced, with *Hide","Stream shortcuts* **off** (so there are visible shortcuts and a","*Streaming* collection to watch).","","- [ ] **Off hides everything.** Panel → *Enable Sync* off. The panel","      shows the toggle alone. In the library: no Moonlight shortcut under","      *Non-Steam* (the visible ones and the \"Moonlight\" client entry are","      gone too), no *Streaming* tab in the bar, and the *Streaming*","      collection is gone (or holds only entries another device put","      there). An owned game's page has no Stream row. A page that was open","      when the toggle flipped loses its row on the next render (open","      another game and come back).","- [ ] **Settings are locked.** The panel's header buttons and *Settings*","      open a route with the toggle alone: no Host / Artwork / Titles /","      Advanced / Updates / About.","- [ ] **A reboot keeps it off.** Restart Steam: the panel opens on the","      toggle alone and the library is still clean (the load's reconcile","      hides every entry again; the log has no `check_host`).","- [ ] **On restores.** Toggle on: the shortcuts are back where the","      settings put them (Stream entries hidden or shown per *Hide Stream","      shortcuts*, the client and host apps hidden), the *Streaming* tab","      and collection are back, the Stream row and the menu item are","      back, the host row goes green or red. If a sync's restart happened","      just before the toggle went off, the layout toast fires now.","- [ ] **Busy.** Start a sync, open the panel while it runs: the progress","      view has no toggle; when it ends the toggle is live again.","","## 13. Reset match cache (Advanced)","","Off-device the backend (`test_backend.py`, the `reset_match_cache` block)","and the controller (`controller.test.ts`, \"Advanced → Reset match cache\")","are covered; what a real cache and a real CLI do with the reset is what","these check. Start synced, with at least one pin (Titles → Change match)","and one title reading \"no match\".","","- [ ] **The file goes, and the toast counts it.** Note","      `~/.cache/moonlight-steam-sync/matches.json` (over SSH: the `titles`","      count and how many are `\"how\": \"pinned\"`). Settings → Advanced →","      *Reset match cache* → **Reset** in the confirm. The toast says","      \"Match cache reset: N titles forgotten, M pins included…\" with","      those numbers, the file is gone, `hosts/` beside it is not, and the","      log has a `reset match cache: deleted …` line. Nothing changes in","      the library.","- [ ] **A second press is harmless.** The toast says the cache was","      already empty; the log says `no file at`.","- [ ] **The next sync re-matches.** Sync now: the CLI resolves every","      title again (the progress shows lookups, not cached hits), the","      titles that read \"no match\" get a match where SteamGridDB has one,","      the pinned title comes back as whatever the CLI resolves for it","      (the pin is gone) and titles that already had artwork keep it. The","      Titles page, opened after the run, agrees.","- [ ] **Busy.** Start a sync, open Advanced while it runs: *Reset* is","      disabled. Pin a title and press *Reset* within the moment the pin","      is saving, if you can: the toast says \"A match change is still","      being saved\" and the file is untouched.","- [ ] **Off.** With the plugin toggled off the Advanced page is not","      reachable (§12); nothing to press.","","## 14. The SteamGridDB key from the Game Mode browser (spec 3.20)","","Off-device the backend (`test_cdp.py`, `test_sgdbpage.py` over the","fixture pages, `test_hard_rules.py` for the key in no event or log line)","and the frontend flow (`controller.test.ts`, \"the SteamGridDB key from","the Game Mode browser\"; the button's presence per key source in","`state.test.ts`) are covered; the real pages, the real browser and the","two navigation calls are what these check. The spec calls this section","§13; that number went to *Reset match cache* first. Any selector of spec","§3.20.1 that does not match on the Deck goes to a Fable-class model","(spec §5), never loosened here.","","Start with no key (Settings → Artwork → *Remove* if the key file is set;","`config.toml` without `[steamgriddb].api_key` and no `SGDB_API_KEY` in","plugin_loader's environment), and keep an SSH session tailing","`~/homebrew/logs/Moonlight Sync/moonlight-sync.log`.","","- [ ] **The button is where it should be.** Settings → Artwork shows","      *Get key from SteamGridDB…* under the key field with no key, and","      again under *Test* / *Remove* once the key file is set. Put a key in","      `config.toml` (or `SGDB_API_KEY` in the service's environment),","      reopen the page: no button.","- [ ] **The consent text.** The button opens \"Sign in to SteamGridDB with","      Steam?\" with spec §3.20.4's text word for word, *Continue* and","      *Cancel*. *Cancel* does nothing else (no browser, nothing in the","      log).","- [ ] **End to end, signed in to Steam.** *Continue*: the Game Mode","      browser takes the screen on SteamGridDB, then Steam's *Sign In*","      page, then the API page, with no press of yours. **[verify]","      `NavigateToExternalWeb`** opens the same `page` target spec","      §3.20.1 item 2 measured (over the debugger, `/json/list` shows","      `Preferences - SteamGridDB` / `…/profile/preferences/api`); if it","      opens anything else, report it (the fallback is","      `steam://openurl/` through `SteamClient.URL.ExecuteSteamURL`, and","      choosing it is a spec change). At the end **[verify]","      `NavigateBack`** leaves the browser and lands on Settings →","      Artwork; if the browser stays, note it here (it is left open by","      design then). The toasts are \"SteamGridDB key saved (…XXXX)\" with","      the key's last four characters, then \"SteamGridDB accepted the","      key\". The field reads \"set, ends in …XXXX\", and","      `~/.config/moonlight-steam-sync/sgdb-api-key` is mode 0600.","- [ ] **The field follows the fetch.** While it runs, back out of the","      browser (B) to Settings → Artwork: the field says *Waiting for","      SteamGridDB…* / *Signing in with Steam…* / *Steam is asking you to","      log in on the page* / *Reading your key…* as it goes, and *Cancel*","      stands where *Save* was.","- [ ] **Not signed in to Steam's web session.** On a Steam account whose","      community session is gone (sign out of steamcommunity.com in the","      browser first), Steam's page asks for a password or a Steam Guard","      code: the field says *Steam is asking you to log in on the page*,","      nothing is typed for you, and once you have logged in the plugin","      presses *Sign In* once and carries on.","- [ ] **An account with no key.** **[verify]** (Decision 61) On a","      SteamGridDB account that never generated a key, the API page's","      *Generate* button is pressed once and the key is read after it; on","      a page with any key element, or only *Regenerate* / *Revoke*,","      nothing is pressed and the toast says \"SteamGridDB shows no API","      key; generate one on its API page, then try again\". *Revoke API","      Key* is never pressed in any run (the key you had still works:","      *Test*).","- [ ] **A second attempt after a failure, no Steam restart.** Make a","      fetch fail (the *Left behind, then timed out* item below, or","      *Cancel mid-way*), then press *Get key from SteamGridDB…* again:","      it succeeds. The log shows \"sgdb key fetch: reset the browser's","      tab (was on www.steamgriddb.com)\" (or steamcommunity.com) right","      before \"started\" (Decision 70), then `waiting` and the fresh page;","      never \"failed (sgdb-page): no key and no generate button\" within a","      second of \"started\". A device's 2026-09-25 had exactly that until","      Steam was restarted. Should the log say \"could not reset the","      browser's tab\", report it with the rest of the line: the fetch","      then reads the old page for its first polls (Decision 69) and","      should still succeed.","- [ ] **Cancel mid-way.** Start again, back out of the browser while","      Steam's sign-in page is up, press *Cancel*: within a second the","      toast says \"The key fetch was cancelled\", you stay on Settings →","      Artwork (no navigation back from there), the key file is","      unchanged, and the browser page is left where it was.","- [ ] **Left behind, then timed out.** Start again, back out of the","      browser with B and wait three minutes: the toast says \"Timed out","      waiting for the SteamGridDB page; enter the key by hand or try","      again\". Note where `NavigateBack` took you (it runs on every done","      of a fetch the plugin opened and was not cancelled from the page);","      if it left a page you did not expect, report it.","- [ ] **No debugger.** With `~/.steam/steam/.cef-enable-remote-debugging`","      removed and Steam restarted (put it back afterwards; Decky","      recreates it), *Continue* toasts \"Steam's debugger port is not","      reachable; enter the key by hand\" and no browser opens.","- [ ] **The key is never in the log.** After the runs above, `grep -c`","      your key in `~/homebrew/logs/Moonlight Sync/moonlight-sync.log`","      (and in Decky's own log, `journalctl -u plugin_loader`) is `0`;","      the log has `sgdb key fetch: \u003cstate\u003e` lines and \"sgdb key fetched","      from the browser\", never the key.","- [ ] **Off.** With the plugin toggled off the Artwork page is not","      reachable (§12); nothing to press.","","## 15. The panel's launch row and Advanced's layout buttons (spec Decisions 67 and 77)","","The panel's launch buttons are icons on one line; the Moonlight entry's","*Layout* and *Make default* are in Settings → Advanced, beside *Clear*.","Needs a synced host that publishes `Desktop` and `Steam Big Picture`.","","- [ ] **The toggle.** The top row reads *Enable Sync* with no text under","      it, on or off. Settings, opened while off, shows the same toggle","      with the \"Off: every Moonlight shortcut is hidden…\" line under it.","- [ ] **Sync now** has no text under it.","- [ ] **One launch row.** The moon, the monitor and the Steam logo share","      one row, in that order, equal widths, nothing clipped or overflowing","      the 268 px column. The moon opens Moonlight; the monitor streams","      *Desktop*; the Steam logo streams *Steam Big Picture*.","- [ ] **The caption follows focus.** **[verify]** (`onGamepadFocus` on a","      `DialogButton`, unmeasured) D-pad onto each icon: the line under the","      row changes to \"Open Moonlight · Launch the client…\", \"Desktop ·","      Stream the host's desktop\", \"Steam Big Picture · Stream the host's","      Steam in Big Picture\", and the footer's A legend names the same","      button. D-pad down off the row: the line goes back to *Open","      Moonlight*'s (`onGamepadBlur`). If the line never changes, report it","      (the icons are then unlabelled on the Deck).","- [ ] **A host app the host does not publish.** Ignore `Desktop` on the","      Titles page and sync: the monitor goes and the other two icons widen.","- [ ] **No layout row on the panel.** Under the launch row come the","      counters; there is no *Layout* / *Make default* row.","- [ ] **One layout row in Advanced.** Settings → Advanced → *Default","      controller layout* has *Layout*, *Make default* and *Clear* on one","      line, right of its text, nothing clipped; the D-pad moves across the","      three and down to *Hide Stream shortcuts*.","- [ ] **Layout.** *Layout* opens Steam's controller configurator for the","      Moonlight entry; B comes back to Advanced. Pick a layout there.","- [ ] **Make default.** *Make default* asks \"Use “\u003clayout\u003e” as the default","      layout?\"; confirm, and the toast counts the titles that took it. The","      row's text now reads \"\u003clayout\u003e · \u003ckind\u003e · set today …\" and *Clear*","      is enabled.","      A Stream press on another game puts that layout on its shortcut.","- [ ] **Make default with nothing chosen.** On a Moonlight entry whose","      layout was never picked, *Make default* toasts \"“Moonlight” has no","      layout chosen yet. Use Layout first.\" and changes nothing. An","      edited-in-place layout gets the *Export* toast instead.","- [ ] **Before the first sync** (no Moonlight shortcut yet): the moon,","      *Layout* and *Make default* are disabled; the launch line reads","      \"Open Moonlight · Sync once to enable\" and Advanced's text \"None.","      Sync once, then set up Moonlight's Layout and press Make default\".","- [ ] **While a game runs** every icon and both layout buttons still work.","","## 16. Updating from the plugin (update spec 3.9, V1 to V7)","","Every item here is the user's check, on a device; none is a merge","criterion. Off-device the updater is covered (`updates.test.ts` over","`tests/fixtures/update/releases.json`, `decky.test.ts` for the allowlist","and the call's arguments, `controller.test.ts` \"the updater\" for the","check, the refusals and the order *check (on a branch), stage, leave the","page, then ask Decky*, `test_hard_rules.py` for hard rule 12's greps,","`test_backend_updates.py` for the staging); the backend's download on the","device, Decky's own dialog, its install from the staged file, the","uninstall and the reload are what these check. Since the staged hand-off","(update spec 3.12.5) every install downloads first: the button reads","*Downloading…*, then the page closes and Decky's dialog opens.","","The first release with the updater cannot be updated *to* anything, since","nothing newer exists yet. V1 to V5 therefore use Settings → Updates →","*Install another version* → the installed version (*reinstall*), which","takes the same route with install type 1 (Decky's dialog then says","\"reinstall\" rather than \"update\"). The first real update, with the","panel's *Update to …* row and the toast, is checked when the release","after it ships; repeat V1, V2 and V5 then.","","Start with the build installed by `install.sh` (every file root-owned),","the plugin on, synced, a SteamGridDB key set and a default layout set,","and an SSH session tailing Decky's log (`journalctl -u plugin_loader -f`).","Before V1, copy what V4 compares: `~/homebrew/settings/Moonlight Sync/`","(settings.json, pending.json, layouts.json, ignore.json),","`~/.config/moonlight-steam-sync/`, `~/.cache/moonlight-steam-sync/` and","the library's tile count.","","- [ ] **The page.** Settings shows *Updates* between *Advanced* and","      *About*: *Installed* is the plugin's version, *Latest* says \"Up to","      date · checked today HH:MM\" (or the newer release, once there is","      one), *Check now* asks again (a second press within a minute asks","      nothing), *Check for updates automatically* is on, and *Install","      another version* lists only releases from 0.12.0 up. About has a","      *Decky Loader* row with the loader's version.","- [ ] **V1. [verify] The download, Decky's dialog, then the install.**","      *Install another version* → the installed version: the button reads","      *Downloading…* while the plugin's backend downloads and checks the","      zip (the plugin's log shows `stage_update v…: staged …`), then Decky's","      dialog asks \"Are you sure you want to reinstall Moonlight Sync …?\"","      (on a real update: \"…update Moonlight Sync to version X?\"). Confirm:","      Decky installs the staged zip from","      `~/homebrew/data/Moonlight Sync/update/staged/`, on loader v3.2.9","      (§17's V8 is the same check for a branch build). If the dialog never","      appears, or Decky's log shows an error, escalate with both logs.","- [ ] **V2. [verify] The page closes first, the plugin comes back.** The","      settings page is gone before Decky's dialog shows (Decision U4).","      After Confirm the plugin reloads at the chosen version (About), with","      no loader or Steam restart. If the screen shows anything else (a","      blank route, a frozen page), escalate with what it showed.","- [ ] **V3. [verify] Over both kinds of install.** V1 over the copy","      `install.sh` put there (root-owned), then once more over the copy","      Decky just installed. Both work; if either fails, escalate.","- [ ] **V4. [verify] Nothing of yours changes. Blocking.** After the","      update: settings.json, pending.json, layouts.json and ignore.json","      are what they were, the key file is there with mode 0600 (Artwork →","      *Test* accepts it), the CLI's config and caches are unchanged, the","      default layout is still set (Advanced), and the library has the same","      tiles, Stream buttons and Streaming tab. Anything different:","      escalate, and it blocks the release.","- [ ] **V5. Cancel.** Start an install and press *Cancel* in Decky's","      dialog: the old version keeps running, the page's buttons are","      usable again, and a second attempt opens the dialog again (the zip","      already staged is not downloaded twice: the plugin's log says","      \"already staged\"). Then start one and press the page's own *Cancel*","      under *Downloading…* at once: no toast, no dialog, the buttons are","      usable again.","- [ ] **V6. Where the plugin lands.** After a Confirm, note where Moonlight","      Sync sits in Decky's plugin list (expected: at the end, §2.3 of the","      update spec) and whether a hidden or frozen state it had in Decky's","      list survived. Write what you see into the README's \"Updating\"","      section.","- [ ] **V7. Offline.** With Wi-Fi off, start an install: the download","      fails before Decky is asked, a toast says \"Could not reach GitHub:","      …\", Decky's dialog never opens, the settings page stays, and the old","      version still runs (Decky has nothing to download, so its progress","      never shows). If Decky's dialog or progress appears, escalate. Back","      online, the install works.","- [ ] **The check's toast and the panel's row** (once a newer release","      exists). A plugin load (restart Steam) toasts \"Version X is","      available. Settings → Updates\" once, and the panel's last row reads","      *Update to X*; pressing it opens Settings → Updates. With *Check for","      updates automatically* off, a load toasts nothing and adds no row","      until *Check now*.","- [ ] **Off.** With the plugin toggled off: no toast at load, no row, and","      the settings route has no *Updates* page (§12).","","## 17. The staged hand-off and the channels (update spec 3.12.5, V8 to V10)","","Every item here is the user's check, on a device, before a release with","the channel picker is cut; none is a merge criterion. Off-device the","staging is covered (`test_fetch.py`, `test_updates.py`,","`test_backend_updates.py` over `file://` trees), and so are the order","*check, stage, leave, ask*, the forced check on a branch channel, a failed","staging, *Cancel* and the picker (`controller.test.ts`), and the","allowlist's `file://` rule (`decky.test.ts`); Decky's install from a","`file://` path, the picker's modal and a real branch build are what these","check.","","Start as for §16 (the plugin on, synced, a key and a default layout set,","Decky's log tailed), with `main` publishing builds (`build-main` exists on","the repository's releases, with a `build.json` among its assets). Keep an","SSH session for V10's `moonlight-steam-sync --version` and `sha256sum","~/.local/bin/moonlight-steam-sync`.","","- [ ] **The picker.** Settings → Updates' second row is *Channel*, reading","      *Releases*, with the description \"Releases are tested. Builds of a","      branch are whatever was pushed last.\" Its list has *Releases*, then","      `main`, then any other branch with a build. Choosing `main` asks","      \"Follow main?\" (\"Builds of a branch are untested and can break","      syncing. You can return to Releases here at any time.\", *Follow* /","      *Cancel*). **[verify]** *Cancel* leaves *Releases* selected in the","      dropdown (whether Steam's dropdown snaps back to the stored channel","      after a cancelled confirm is unmeasured) and asks GitHub nothing.","      Choosing *Releases* from a branch asks nothing.","- [ ] **The branch's `build.json` through the loader's fetch.** The","      `build.json` request is the plugin's first request through Decky's","      `fetchNoCors` to `github.com/…/releases/download/…`, which answers","      with a redirect (302) to another host; every request before it went","      to `api.github.com`, which does not redirect. Follow `main` (the","      picker checks at once): *Latest* must show the build, `main @","      \u003csha7\u003e · built … · checked …`, not \"No build of main is published\".","      If it shows the latter although `build-main` has a `build.json`, the","      loader's fetch did not follow the redirect (the check reports no","      error then, since a failed second request never fails it): escalate","      with Decky's log.","- [ ] **A new build of the followed branch is seen at once.** With `main`","      followed and its build installed, merge to `main` (or `gh workflow","      run builds.yml --ref main`) and wait for the *Builds* run to finish;","      *Check now* on the Updates page must show the new commit, `main @","      \u003cthe new sha7\u003e`, and offer *Switch to* it. Before amendment A6 the","      loader's fetch went through CEF's HTTP cache, which kept the old","      `build.json` (served with `Last-Modified` and no `Cache-Control`)","      for hours: the page said *Up to date* although `main` had moved,","      and, once the new build was installed through *Releases* and back,","      offered the previous commit for as long. Found on the Deck","      2026-09-29; `journalctl -u plugin_loader` shows a `Preparing GET","      request to …/build-main/build.json` line for a check that reached","      the loader, and none for one the cache answered.","- [ ] **V8. [verify] A `file://` artifact installs.** On `main`, press","      *Switch to main @ \u003csha7\u003e*: *Downloading…*, then the page closes and","      Decky's dialog asks to overwrite Moonlight Sync with version","      \"\u003cversion\u003e (main @ \u003csha7\u003e)\"; Confirm. Decky installs the zip from","      `~/homebrew/data/Moonlight Sync/update/staged/` (a path with a space","      in it) through `utilities/install_plugin`, and the plugin reloads.","      If Decky refuses the artifact, cannot open the path, or its log shows","      an error: escalate with the log. The fallback the spec allows then","      (update spec 3.12.5, not built): releases keep the URL route of","      update spec 3.5, and branch builds hand Decky the release asset's URL","      after a successful staging, which has then proved what the URL","      serves.","- [ ] **V9. Follow `main`, install, a second push, back to Releases.**","      After V8: *Installed* reads `\u003cversion\u003e · main @ \u003csha7\u003e · built …`,","      *Latest* says \"Up to date · checked …\" and there is no install","      button. Push a commit to `main`, wait for `builds.yml` to publish it,","      then restart Steam (or press *Check now* a minute later): the toast","      \"Version main @ \u003cnew sha7\u003e is available. Settings → Updates\" and the","      panel's row appear, and *Switch to main @ \u003cnew sha7\u003e* installs it.","      Then choose *Releases*: *Latest* names the newest release and the","      button reads *Switch to \u003cversion\u003e*; pressing it installs the release","      (Decky asks to overwrite) and *Installed* reads the plain version","      again. Anything else: escalate.","- [ ] **V10. The CLI at each step of V9.** Before V8, after V8, after the","      second push's install and after the return to *Releases*, note","      `moonlight-steam-sync --version` and the sha256 of","      `~/.local/bin/moonlight-steam-sync`, and About's CLI rows. Expected:","      the version stays the release's number throughout; the bytes change","      to the branch build's bundled CLI after V8 and after the second","      push's install (a build of the same version with other bytes","      replaces it, README \"The bundled CLI\"), and go back to the release's","      after the return to *Releases*. Anything else: escalate.","- [ ] **A failed staging leaves everything as it was.** With Wi-Fi off,","      press *Switch to …* on `main`: the forced check fails first, a toast","      says \"Could not reach GitHub: …\", nothing downloads, Decky's dialog","      never opens. Back online, press it and at once the page's *Cancel*","      under *Downloading…*: no toast, no dialog, and a second press works.","- [ ] **A build made on the device is the commit it is** (update spec","      amendment A5). On the device, clone `main` afresh, build it (`pnpm","      install`, `pnpm run build`, `backend/entrypoint.sh`,","      `scripts/package.py --require-cli`) and install the zip by hand","      (README \"Manual install\"). `package.py` must print `package.py:","      build.json from git: main @ \u003csha12\u003e`; if it prints `no build.json:","      …` instead, the text in brackets is git's own reason (a clone that","      belongs to another user is \"dubious ownership\"): report it. Then,","      with the *Channel* on `main` and `build-main` published from that","      same commit: *Installed* reads `\u003cversion\u003e · main @ \u003csha7\u003e · built","      …`, *Latest* says \"Up to date · checked …\" and there is no install","      button; Settings → About's *Built from* reads `main @ \u003csha7\u003e · built","      …` and *Commit* is the whole commit, the one `git rev-parse HEAD`","      printed in the clone. Before this, such a build always showed *Switch to main @","      \u003csha7\u003e* (found 2026-09-28). With the *Channel* on *Releases* the","      page offers *Switch to \u003cversion\u003e* instead, as for any branch build."],"stylingDirectives":[[[0,18,"pl-mh"],[2,18,"pl-en"]],[],[],[],[],[],[],[],[],[[0,11,"pl-mh"],[3,11,"pl-en"]],[],[[0,1,"pl-s"],[1,2,"pl-v"]],[[3,88,"pl-corl"]],[[7,8,"pl-s"],[8,10,"pl-c1"],[10,11,"pl-s"]],[],[],[],[[12,13,"pl-s"],[13,27,"pl-c1"],[27,28,"pl-s"]],[[10,11,"pl-s"],[11,20,"pl-c1"],[20,21,"pl-s"]],[],[[26,27,"pl-s"],[27,31,"pl-c1"],[31,32,"pl-s"],[42,43,"pl-s"],[43,48,"pl-c1"],[48,49,"pl-s"]],[[24,25,"pl-s"],[25,58,"pl-c1"],[58,59,"pl-s"]],[[0,1,"pl-s"],[1,2,"pl-v"],[49,50,"pl-s"],[50,56,"pl-c1"],[56,57,"pl-s"]],[[14,15,"pl-s"],[15,47,"pl-c1"],[47,48,"pl-s"],[54,55,"pl-s"],[55,71,"pl-c1"],[71,72,"pl-s"]],[],[],[[0,1,"pl-s"],[1,2,"pl-v"],[9,10,"pl-s"],[10,59,"pl-c1"],[59,60,"pl-s"]],[],[],[[4,5,"pl-s"],[5,35,"pl-c1"],[35,36,"pl-s"],[40,41,"pl-s"],[41,68,"pl-c1"],[68,69,"pl-s"]],[],[],[[0,1,"pl-s"],[1,2,"pl-v"]],[[20,21,"pl-s"],[21,54,"pl-c1"],[54,55,"pl-s"]],[[33,34,"pl-s"],[34,51,"pl-c1"],[51,52,"pl-s"]],[[3,4,"pl-s"],[4,52,"pl-c1"],[52,53,"pl-s"],[57,58,"pl-s"],[58,64,"pl-c1"],[64,65,"pl-s"]],[[3,4,"pl-s"],[4,12,"pl-c1"],[12,13,"pl-s"],[64,65,"pl-s"],[65,71,"pl-c1"],[71,72,"pl-s"]],[],[[0,1,"pl-s"],[1,2,"pl-v"],[21,22,"pl-s"],[37,38,"pl-s"]],[[33,34,"pl-s"],[34,60,"pl-c1"],[60,61,"pl-s"]],[[13,14,"pl-s"],[14,21,"pl-c1"],[21,22,"pl-s"]],[],[],[[44,46,"pl-c1"]],[],[],[],[],[[12,13,"pl-s"],[16,17,"pl-s"],[37,38,"pl-s"]],[[9,10,"pl-s"],[10,17,"pl-c1"],[17,18,"pl-s"]],[[23,24,"pl-s"],[24,33,"pl-c1"],[33,34,"pl-s"]],[[23,24,"pl-s"],[24,30,"pl-c1"],[30,31,"pl-s"],[49,50,"pl-s"],[50,55,"pl-c1"],[55,56,"pl-s"]],[],[[11,12,"pl-s"],[12,23,"pl-c1"],[23,24,"pl-s"],[43,44,"pl-s"],[44,63,"pl-c1"],[63,64,"pl-s"]],[],[[9,10,"pl-s"],[10,14,"pl-c1"],[14,15,"pl-s"]],[],[[0,25,"pl-mh"],[3,25,"pl-en"]],[],[[55,56,"pl-s"],[56,73,"pl-c1"],[73,74,"pl-s"]],[],[[0,1,"pl-s"],[1,2,"pl-s"],[2,18,"pl-c1"],[18,19,"pl-s"],[19,20,"pl-s"],[20,21,"pl-s"],[21,37,"pl-corl"],[37,38,"pl-s"]],[[3,4,"pl-s"],[4,59,"pl-c1"],[59,60,"pl-s"]],[[0,1,"pl-s"],[1,21,"pl-c1"],[21,22,"pl-s"]],[[0,1,"pl-s"],[1,38,"pl-c1"],[38,39,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[13,15,"pl-s"],[53,54,"pl-s"],[54,73,"pl-c1"],[73,74,"pl-s"]],[],[[7,8,"pl-s"],[8,68,"pl-c1"],[68,69,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[10,12,"pl-s"],[14,15,"pl-s"],[15,31,"pl-c1"],[31,32,"pl-s"]],[[6,7,"pl-s"],[7,48,"pl-c1"],[48,49,"pl-s"],[51,52,"pl-s"],[52,57,"pl-c1"],[57,58,"pl-s"],[60,61,"pl-s"],[61,73,"pl-c1"],[73,74,"pl-s"]],[[6,7,"pl-s"],[7,15,"pl-c1"],[15,16,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[10,12,"pl-s"],[14,15,"pl-s"],[15,64,"pl-c1"],[64,65,"pl-s"]],[[6,7,"pl-s"],[7,35,"pl-c1"],[35,36,"pl-s"]],[[36,37,"pl-s"],[37,48,"pl-c1"],[48,49,"pl-s"],[53,54,"pl-s"],[54,65,"pl-c1"],[65,66,"pl-s"]],[[35,36,"pl-s"],[36,47,"pl-c1"],[47,48,"pl-s"]],[],[[6,7,"pl-s"],[7,27,"pl-c1"],[27,28,"pl-s"],[41,42,"pl-s"],[42,60,"pl-c1"],[60,61,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[10,12,"pl-s"],[36,37,"pl-s"],[37,42,"pl-c1"],[42,43,"pl-s"]],[[14,15,"pl-s"],[15,71,"pl-c1"],[71,72,"pl-s"]],[],[[6,7,"pl-s"],[7,30,"pl-c1"],[30,31,"pl-s"]],[[29,31,"pl-s"],[56,58,"pl-s"]],[[18,19,"pl-s"],[19,42,"pl-c1"],[42,43,"pl-s"],[47,48,"pl-s"],[48,56,"pl-c1"],[56,57,"pl-s"]],[[6,7,"pl-s"],[7,25,"pl-c1"],[25,26,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[10,12,"pl-s"],[14,15,"pl-s"],[15,58,"pl-c1"],[58,59,"pl-s"]],[[6,7,"pl-s"],[7,51,"pl-c1"],[51,52,"pl-s"],[63,64,"pl-s"],[64,69,"pl-c1"],[69,70,"pl-s"]],[],[[31,32,"pl-s"],[32,37,"pl-c1"],[37,38,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[10,12,"pl-s"],[36,37,"pl-s"],[37,42,"pl-c1"],[42,43,"pl-s"]],[[6,7,"pl-s"],[7,23,"pl-c1"],[23,24,"pl-s"],[32,33,"pl-s"],[33,47,"pl-c1"],[47,48,"pl-s"]],[[15,16,"pl-s"],[16,36,"pl-c1"],[36,37,"pl-s"]],[],[[52,53,"pl-s"],[53,73,"pl-c1"],[73,74,"pl-s"]],[[49,51,"pl-s"]],[[6,7,"pl-s"],[7,11,"pl-c1"],[11,12,"pl-s"],[43,44,"pl-s"],[44,57,"pl-c1"],[57,58,"pl-s"],[58,60,"pl-s"]],[],[[47,48,"pl-s"],[48,58,"pl-c1"],[58,59,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[10,11,"pl-s"],[11,74,"pl-c1"],[74,75,"pl-s"]],[],[[6,7,"pl-s"],[7,25,"pl-c1"],[25,26,"pl-s"],[29,30,"pl-s"],[30,40,"pl-c1"],[40,41,"pl-s"]],[[6,7,"pl-s"],[7,51,"pl-c1"],[51,52,"pl-s"]],[],[],[[0,39,"pl-mh"],[3,39,"pl-en"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[44,46,"pl-s"],[55,56,"pl-s"],[56,89,"pl-c1"],[89,90,"pl-s"]],[[55,56,"pl-s"],[56,68,"pl-c1"],[68,69,"pl-s"]],[],[[58,59,"pl-s"]],[[13,14,"pl-s"],[39,40,"pl-s"],[40,44,"pl-c1"],[44,45,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[46,48,"pl-s"],[55,56,"pl-s"],[64,65,"pl-s"]],[],[[45,46,"pl-s"],[55,56,"pl-s"]],[],[[40,41,"pl-s"],[45,46,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[30,32,"pl-s"],[39,40,"pl-s"],[48,49,"pl-s"],[56,57,"pl-s"],[61,62,"pl-s"]],[],[],[[14,15,"pl-s"],[24,25,"pl-s"]],[[6,7,"pl-s"],[7,56,"pl-c1"],[56,57,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[40,42,"pl-s"]],[[52,53,"pl-s"],[57,58,"pl-s"]],[],[[40,41,"pl-s"],[50,51,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[28,30,"pl-s"]],[],[[6,7,"pl-s"],[7,37,"pl-c1"],[37,38,"pl-s"]],[[25,26,"pl-s"],[34,35,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[22,24,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[15,17,"pl-s"],[20,22,"pl-s"],[39,41,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[38,40,"pl-s"]],[],[[6,7,"pl-s"],[7,51,"pl-c1"],[51,52,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[42,44,"pl-s"]],[],[[14,15,"pl-s"],[15,33,"pl-c1"],[33,34,"pl-s"],[44,45,"pl-s"],[45,63,"pl-c1"],[63,64,"pl-s"]],[[6,7,"pl-s"],[7,15,"pl-c1"],[15,16,"pl-s"]],[[44,46,"pl-s"],[51,53,"pl-s"]],[[13,15,"pl-s"],[19,21,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[24,26,"pl-s"]],[],[],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[28,30,"pl-s"]],[],[[47,49,"pl-s"],[54,56,"pl-s"],[59,61,"pl-s"],[65,67,"pl-s"]],[],[[32,33,"pl-s"],[33,51,"pl-c1"],[51,52,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[29,31,"pl-s"]],[],[],[[46,54,"pl-ent"],[55,59,"pl-e"]],[],[],[[25,26,"pl-s"],[34,35,"pl-s"]],[],[[51,52,"pl-s"],[52,62,"pl-c1"],[62,63,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[30,32,"pl-s"],[63,64,"pl-s"],[68,69,"pl-s"]],[[27,29,"pl-s"],[34,36,"pl-s"],[39,41,"pl-s"],[45,47,"pl-s"],[63,64,"pl-s"]],[[10,11,"pl-s"],[52,53,"pl-s"],[61,62,"pl-s"]],[],[[62,63,"pl-s"]],[[9,10,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[49,51,"pl-s"]],[],[],[],[],[[39,40,"pl-s"],[40,45,"pl-c1"],[45,46,"pl-s"]],[[6,7,"pl-s"],[7,35,"pl-c1"],[35,36,"pl-s"],[46,47,"pl-s"],[47,55,"pl-c1"],[55,56,"pl-s"]],[[6,7,"pl-s"],[7,19,"pl-c1"],[19,20,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[22,24,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[43,45,"pl-s"]],[],[],[],[],[],[[19,20,"pl-s"],[20,39,"pl-c1"],[39,40,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[55,57,"pl-s"],[64,65,"pl-s"],[70,71,"pl-s"]],[],[[37,38,"pl-s"],[38,61,"pl-c1"],[61,62,"pl-s"]],[[31,32,"pl-s"],[32,51,"pl-c1"],[51,52,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[24,26,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[33,35,"pl-s"]],[],[],[],[[45,46,"pl-s"],[46,59,"pl-c1"],[59,60,"pl-s"]],[[31,32,"pl-s"],[32,57,"pl-c1"],[57,58,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[33,34,"pl-s"],[34,42,"pl-c1"],[42,43,"pl-s"],[52,54,"pl-s"]],[[30,31,"pl-s"],[31,41,"pl-c1"],[41,42,"pl-s"],[49,50,"pl-s"],[50,58,"pl-c1"],[58,59,"pl-s"]],[[6,7,"pl-s"],[23,24,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[38,40,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[48,50,"pl-s"]],[],[[44,45,"pl-s"],[49,50,"pl-s"]],[],[],[[0,37,"pl-mh"],[3,37,"pl-en"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[26,28,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[29,31,"pl-s"],[38,39,"pl-s"],[47,48,"pl-s"]],[[63,64,"pl-s"]],[[7,15,"pl-ent"],[16,20,"pl-e"],[21,22,"pl-s"]],[],[[24,25,"pl-s"],[37,38,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[8,9,"pl-s"],[9,22,"pl-c1"],[22,23,"pl-s"],[43,44,"pl-s"],[44,48,"pl-c1"],[48,49,"pl-s"],[49,51,"pl-s"]],[],[[34,35,"pl-s"],[35,50,"pl-c1"],[50,51,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[31,33,"pl-s"]],[],[],[[23,24,"pl-s"],[36,37,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[26,28,"pl-s"]],[],[[12,13,"pl-s"],[21,22,"pl-s"]],[[16,17,"pl-s"],[22,23,"pl-s"]],[[66,67,"pl-s"]],[[9,10,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[34,36,"pl-s"],[42,43,"pl-s"],[55,56,"pl-s"]],[[15,16,"pl-s"],[24,25,"pl-s"],[67,68,"pl-s"]],[[10,11,"pl-s"]],[[7,8,"pl-s"],[8,20,"pl-c1"],[20,21,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[37,39,"pl-s"]],[[6,7,"pl-s"],[15,16,"pl-s"]],[],[[19,20,"pl-s"],[28,29,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[15,17,"pl-s"],[18,19,"pl-s"],[25,26,"pl-s"]],[[6,7,"pl-s"],[15,16,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[37,39,"pl-s"]],[[48,49,"pl-s"],[49,65,"pl-c1"],[65,66,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[15,17,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[23,25,"pl-s"]],[[39,40,"pl-s"],[40,50,"pl-c1"],[50,51,"pl-s"]],[[39,40,"pl-s"],[40,44,"pl-c1"],[44,45,"pl-s"],[50,51,"pl-s"],[51,60,"pl-c1"],[60,61,"pl-s"],[63,64,"pl-s"],[64,71,"pl-c1"],[71,72,"pl-s"]],[[9,10,"pl-s"],[10,27,"pl-c1"],[27,28,"pl-s"]],[[12,13,"pl-s"],[21,22,"pl-s"]],[[6,8,"pl-s"],[22,24,"pl-s"]],[],[[13,14,"pl-s"],[14,16,"pl-en"],[31,32,"pl-s"],[32,34,"pl-en"]],[],[[10,11,"pl-s"],[11,21,"pl-c1"],[21,22,"pl-s"]],[],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[34,36,"pl-s"],[50,52,"pl-s"]],[[19,21,"pl-s"]],[[13,14,"pl-s"],[14,55,"pl-c1"],[55,56,"pl-s"]],[],[[14,15,"pl-s"],[24,25,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[35,37,"pl-s"],[57,58,"pl-s"],[58,67,"pl-c1"],[67,68,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[35,37,"pl-s"],[44,45,"pl-s"],[57,58,"pl-s"]],[],[],[],[],[[0,48,"pl-mh"],[3,48,"pl-en"]],[],[[0,1,"pl-s"]],[],[],[],[],[],[],[[21,22,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[25,27,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[42,44,"pl-s"]],[],[[20,21,"pl-s"],[25,26,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[8,10,"pl-s"],[48,50,"pl-s"],[50,52,"pl-s"]],[[57,59,"pl-s"]],[[16,18,"pl-s"],[26,27,"pl-s"],[33,34,"pl-s"]],[[6,7,"pl-s"],[7,18,"pl-c1"],[18,19,"pl-s"]],[[6,7,"pl-s"],[7,19,"pl-c1"],[19,20,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[70,72,"pl-s"]],[],[],[[18,19,"pl-s"],[25,26,"pl-s"],[33,34,"pl-s"],[42,43,"pl-s"]],[],[[13,14,"pl-s"],[14,26,"pl-c1"],[26,27,"pl-s"],[36,37,"pl-s"],[37,43,"pl-c1"],[43,44,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[8,10,"pl-s"],[24,26,"pl-s"],[26,28,"pl-s"]],[],[[19,20,"pl-s"],[20,29,"pl-c1"],[29,30,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[50,52,"pl-s"]],[],[],[],[],[[11,12,"pl-s"],[12,22,"pl-c1"],[22,23,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[29,31,"pl-s"]],[[46,47,"pl-s"],[53,54,"pl-s"]],[],[],[[33,34,"pl-s"],[34,46,"pl-c1"],[46,47,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[8,10,"pl-s"],[26,28,"pl-s"],[28,30,"pl-s"]],[[37,38,"pl-s"],[63,64,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[33,35,"pl-s"]],[],[[28,29,"pl-s"],[29,41,"pl-c1"],[41,42,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[42,44,"pl-s"],[51,52,"pl-s"],[58,59,"pl-s"]],[[29,30,"pl-s"],[49,50,"pl-s"]],[],[],[[6,7,"pl-s"],[13,14,"pl-s"],[48,49,"pl-s"],[55,56,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[24,26,"pl-s"],[31,32,"pl-s"],[32,59,"pl-c1"],[59,60,"pl-s"]],[[6,7,"pl-s"],[7,20,"pl-c1"],[20,21,"pl-s"]],[[42,43,"pl-s"],[43,55,"pl-c1"],[55,56,"pl-s"]],[[15,16,"pl-s"],[41,42,"pl-s"]],[[20,21,"pl-s"],[27,28,"pl-s"],[52,53,"pl-s"],[65,66,"pl-s"]],[[6,7,"pl-s"],[12,13,"pl-s"]],[],[[16,17,"pl-s"],[17,21,"pl-c1"],[21,22,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[33,35,"pl-s"]],[],[[49,50,"pl-s"]],[[85,86,"pl-s"]],[[21,22,"pl-s"],[22,37,"pl-c1"],[37,38,"pl-s"]],[],[],[[29,30,"pl-s"],[30,66,"pl-c1"],[66,67,"pl-s"]],[[6,7,"pl-s"],[7,22,"pl-c1"],[22,23,"pl-s"],[24,25,"pl-s"],[25,26,"pl-c1"],[26,27,"pl-s"],[47,48,"pl-s"],[48,68,"pl-c1"],[68,69,"pl-s"]],[[6,7,"pl-s"],[7,25,"pl-c1"],[25,26,"pl-s"],[28,30,"pl-s"],[41,43,"pl-s"]],[],[[6,7,"pl-s"],[7,42,"pl-c1"],[42,43,"pl-s"]],[],[],[[0,43,"pl-mh"],[3,43,"pl-en"],[31,32,"pl-s"],[32,42,"pl-c1"],[42,43,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[8,9,"pl-s"],[9,19,"pl-c1"],[19,20,"pl-s"],[30,32,"pl-s"]],[],[[27,28,"pl-s"],[28,32,"pl-c1"],[32,33,"pl-s"]],[[19,20,"pl-s"],[20,24,"pl-c1"],[24,25,"pl-s"],[52,53,"pl-s"],[53,72,"pl-c1"],[72,73,"pl-s"]],[[14,15,"pl-s"],[15,28,"pl-c1"],[28,29,"pl-s"]],[],[],[[20,21,"pl-s"],[21,33,"pl-c1"],[33,34,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[8,9,"pl-s"],[9,19,"pl-c1"],[19,20,"pl-s"],[48,50,"pl-s"]],[[6,7,"pl-s"],[7,11,"pl-c1"],[11,12,"pl-s"],[28,29,"pl-s"],[29,43,"pl-c1"],[43,44,"pl-s"],[50,51,"pl-s"],[51,53,"pl-c1"],[53,54,"pl-s"]],[],[[26,27,"pl-s"],[68,69,"pl-s"]],[[13,14,"pl-s"],[14,20,"pl-c1"],[20,21,"pl-s"],[30,31,"pl-s"],[31,32,"pl-c1"],[32,33,"pl-s"]],[[6,7,"pl-s"],[7,21,"pl-c1"],[21,22,"pl-s"],[29,30,"pl-s"],[30,32,"pl-c1"],[32,33,"pl-s"]],[[17,18,"pl-s"],[18,19,"pl-c1"],[19,20,"pl-s"],[32,33,"pl-s"],[33,37,"pl-c1"],[37,38,"pl-s"],[57,58,"pl-s"],[71,72,"pl-s"]],[],[[15,16,"pl-s"],[46,47,"pl-s"]],[[10,11,"pl-s"],[11,25,"pl-c1"],[25,26,"pl-s"],[35,36,"pl-s"],[36,38,"pl-c1"],[38,39,"pl-s"]],[[13,14,"pl-s"],[14,15,"pl-c1"],[15,16,"pl-s"],[53,54,"pl-s"]],[[9,10,"pl-s"],[19,20,"pl-s"],[20,34,"pl-c1"],[34,35,"pl-s"],[39,40,"pl-s"],[40,42,"pl-c1"],[42,43,"pl-s"],[51,53,"pl-s"]],[[13,15,"pl-s"],[31,32,"pl-s"],[32,38,"pl-c1"],[38,39,"pl-s"]],[[22,23,"pl-s"],[23,29,"pl-c1"],[29,30,"pl-s"]],[[6,7,"pl-s"],[7,16,"pl-c1"],[16,17,"pl-s"]],[],[[18,19,"pl-s"],[56,57,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[8,9,"pl-s"],[9,19,"pl-c1"],[19,20,"pl-s"],[54,56,"pl-s"]],[[24,25,"pl-s"],[25,57,"pl-c1"],[57,58,"pl-s"]],[[24,25,"pl-s"],[54,55,"pl-s"]],[[6,7,"pl-s"],[32,33,"pl-s"]],[[6,7,"pl-s"],[7,8,"pl-c1"],[8,9,"pl-s"],[54,55,"pl-s"],[55,59,"pl-c1"],[59,60,"pl-s"]],[[9,10,"pl-s"],[10,20,"pl-c1"],[20,21,"pl-s"],[47,48,"pl-s"],[48,49,"pl-c1"],[49,50,"pl-s"]],[[26,27,"pl-s"]],[[16,17,"pl-s"],[20,21,"pl-s"],[47,48,"pl-s"]],[],[],[],[],[],[[56,58,"pl-s"]],[[27,29,"pl-s"],[43,44,"pl-s"],[44,62,"pl-c1"],[62,63,"pl-s"]],[[17,18,"pl-s"],[18,34,"pl-c1"],[34,35,"pl-s"],[52,53,"pl-s"],[53,62,"pl-c1"],[62,63,"pl-s"]],[[20,21,"pl-s"],[21,32,"pl-c1"],[32,33,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[36,38,"pl-s"]],[],[[6,7,"pl-s"],[21,22,"pl-s"],[44,45,"pl-s"]],[[10,11,"pl-s"],[14,15,"pl-s"],[21,22,"pl-s"],[43,44,"pl-s"],[44,62,"pl-c1"],[62,63,"pl-s"]],[],[],[],[[43,45,"pl-s"]],[[13,15,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[39,41,"pl-s"]],[[6,7,"pl-s"],[7,36,"pl-c1"],[36,37,"pl-s"]],[],[],[[6,7,"pl-s"],[7,26,"pl-c1"],[26,27,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[8,9,"pl-s"],[9,31,"pl-c1"],[31,32,"pl-s"],[41,43,"pl-s"]],[[6,7,"pl-s"],[7,50,"pl-c1"],[50,51,"pl-s"]],[[53,54,"pl-s"],[54,69,"pl-c1"],[69,70,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[18,20,"pl-s"]],[[7,8,"pl-s"],[8,55,"pl-c1"],[55,56,"pl-s"]],[[6,7,"pl-s"],[7,20,"pl-c1"],[20,21,"pl-s"]],[[6,7,"pl-s"],[7,40,"pl-c1"],[40,41,"pl-s"]],[],[[0,64,"pl-mh"],[3,64,"pl-en"]],[],[[28,29,"pl-s"],[29,36,"pl-c1"],[36,37,"pl-s"],[42,43,"pl-s"],[43,60,"pl-c1"],[60,61,"pl-s"]],[],[],[[7,8,"pl-s"],[8,42,"pl-c1"],[42,43,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[52,54,"pl-s"]],[[12,13,"pl-s"],[21,22,"pl-s"]],[[40,41,"pl-s"],[41,50,"pl-c1"],[50,51,"pl-s"],[56,57,"pl-s"],[57,64,"pl-c1"],[64,65,"pl-s"]],[[57,58,"pl-s"],[58,65,"pl-c1"],[65,66,"pl-s"]],[[6,7,"pl-s"],[7,24,"pl-c1"],[24,25,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[33,35,"pl-s"],[36,37,"pl-s"],[37,67,"pl-c1"],[67,68,"pl-s"]],[[48,49,"pl-s"],[49,61,"pl-c1"],[61,62,"pl-s"]],[[6,7,"pl-s"],[7,20,"pl-c1"],[20,21,"pl-s"],[23,24,"pl-s"],[24,38,"pl-c1"],[38,39,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[34,36,"pl-s"],[37,38,"pl-s"],[45,46,"pl-s"],[51,52,"pl-s"],[69,70,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[8,10,"pl-s"],[52,54,"pl-s"],[54,56,"pl-s"]],[],[],[],[[10,11,"pl-s"],[17,18,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[60,62,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[25,27,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[28,29,"pl-s"],[29,72,"pl-c1"],[72,73,"pl-s"],[73,75,"pl-s"]],[[35,36,"pl-s"],[61,62,"pl-s"]],[[6,7,"pl-s"],[19,20,"pl-s"],[25,26,"pl-s"],[31,32,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[17,19,"pl-s"],[32,33,"pl-s"],[39,40,"pl-s"],[57,58,"pl-s"],[58,64,"pl-c1"],[64,65,"pl-s"]],[],[[11,12,"pl-s"],[12,26,"pl-c1"],[26,27,"pl-s"],[53,54,"pl-s"],[63,64,"pl-s"]],[[29,30,"pl-s"],[39,40,"pl-s"]],[[21,22,"pl-s"],[36,37,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[20,22,"pl-s"],[38,40,"pl-s"],[48,50,"pl-s"],[63,64,"pl-s"],[67,68,"pl-s"]],[[15,16,"pl-s"],[28,29,"pl-s"],[34,35,"pl-s"],[41,42,"pl-s"],[47,48,"pl-s"],[54,55,"pl-s"]],[[9,10,"pl-s"],[22,23,"pl-s"],[43,45,"pl-s"],[53,55,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[35,37,"pl-s"],[38,39,"pl-s"]],[[46,47,"pl-s"]],[],[[15,16,"pl-s"],[16,28,"pl-c1"],[28,29,"pl-s"],[37,38,"pl-s"],[38,44,"pl-c1"],[44,45,"pl-s"],[48,49,"pl-s"],[49,53,"pl-c1"],[53,54,"pl-s"],[57,58,"pl-s"],[58,69,"pl-c1"],[69,70,"pl-s"]],[[33,34,"pl-s"],[34,40,"pl-c1"],[40,41,"pl-s"]],[[6,7,"pl-s"],[21,22,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[34,36,"pl-s"],[44,45,"pl-s"],[45,52,"pl-c1"],[52,53,"pl-s"]],[[49,50,"pl-s"],[57,58,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[24,26,"pl-s"]],[],[[42,43,"pl-s"],[49,50,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[30,32,"pl-s"]],[[26,27,"pl-s"],[27,39,"pl-c1"],[39,40,"pl-s"],[42,43,"pl-s"],[43,56,"pl-c1"],[56,57,"pl-s"]],[[6,7,"pl-s"],[7,24,"pl-c1"],[24,25,"pl-s"],[27,28,"pl-s"],[28,46,"pl-c1"],[46,47,"pl-s"],[52,53,"pl-s"],[53,69,"pl-c1"],[69,70,"pl-s"]],[],[],[[67,68,"pl-s"]],[[9,10,"pl-s"]],[],[[0,63,"pl-mh"],[3,63,"pl-en"]],[],[[8,9,"pl-s"],[9,40,"pl-c1"],[40,41,"pl-s"],[43,44,"pl-s"],[44,53,"pl-c1"],[53,54,"pl-s"],[56,57,"pl-s"],[57,72,"pl-c1"],[72,73,"pl-s"]],[[0,1,"pl-s"],[1,21,"pl-c1"],[21,22,"pl-s"],[24,25,"pl-s"],[25,45,"pl-c1"],[45,46,"pl-s"],[65,66,"pl-s"],[66,70,"pl-c1"],[70,71,"pl-s"]],[[0,1,"pl-s"],[1,7,"pl-c1"],[7,8,"pl-s"]],[[13,15,"pl-s"],[60,62,"pl-s"]],[[32,33,"pl-s"],[54,55,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[26,27,"pl-s"],[27,65,"pl-c1"],[65,66,"pl-s"]],[[6,7,"pl-s"],[7,39,"pl-c1"],[39,40,"pl-s"]],[[42,43,"pl-s"],[43,52,"pl-c1"],[52,53,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[59,60,"pl-s"],[69,70,"pl-s"]],[[52,54,"pl-s"],[63,65,"pl-s"]],[[36,37,"pl-s"],[37,46,"pl-c1"],[46,47,"pl-s"],[52,53,"pl-s"],[53,60,"pl-c1"],[60,61,"pl-s"]],[[60,61,"pl-s"],[67,68,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[8,10,"pl-s"],[19,21,"pl-s"]],[[6,8,"pl-s"],[22,24,"pl-s"],[27,29,"pl-s"],[38,40,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"]],[[14,15,"pl-s"],[29,30,"pl-s"],[32,33,"pl-s"],[40,41,"pl-s"],[46,47,"pl-s"],[64,65,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,7,"pl-s"],[28,29,"pl-s"]],[[6,7,"pl-s"],[16,17,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,7,"pl-s"],[27,28,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,7,"pl-s"],[24,25,"pl-s"]],[[14,15,"pl-s"],[21,22,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[33,34,"pl-s"],[40,41,"pl-s"]],[[33,34,"pl-s"],[34,41,"pl-c1"],[41,42,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"]],[],[[0,1,"pl-v"],[2,5,"pl-k"]],[[27,28,"pl-s"],[28,43,"pl-c1"],[43,44,"pl-s"]],[],[],[[0,47,"pl-mh"],[3,47,"pl-en"]],[],[],[[47,48,"pl-s"],[60,61,"pl-s"]],[],[],[[56,57,"pl-s"],[57,64,"pl-c1"],[64,65,"pl-s"]],[[0,1,"pl-s"],[1,13,"pl-c1"],[13,14,"pl-s"]],[[40,41,"pl-s"],[41,52,"pl-c1"],[52,53,"pl-s"]],[],[],[[31,33,"pl-s"],[33,34,"pl-s"],[40,41,"pl-s"],[41,43,"pl-s"]],[[22,23,"pl-s"],[35,36,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[33,35,"pl-s"]],[[6,7,"pl-s"],[7,18,"pl-c1"],[18,19,"pl-s"],[49,50,"pl-s"],[56,57,"pl-s"]],[[6,7,"pl-s"],[19,20,"pl-s"]],[[23,24,"pl-s"],[24,67,"pl-c1"],[67,68,"pl-s"]],[[18,19,"pl-s"],[19,53,"pl-c1"],[53,54,"pl-s"]],[],[[21,22,"pl-s"],[22,41,"pl-c1"],[41,42,"pl-s"]],[[17,18,"pl-s"],[43,44,"pl-s"],[55,60,"pl-ent"]],[[26,27,"pl-s"],[27,29,"pl-c1"],[29,30,"pl-s"],[42,43,"pl-s"],[43,49,"pl-c1"],[49,50,"pl-s"],[59,60,"pl-s"],[60,65,"pl-c1"],[65,66,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[30,32,"pl-s"],[57,58,"pl-s"],[64,65,"pl-s"]],[[56,57,"pl-s"]],[[35,36,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[16,17,"pl-s"],[17,28,"pl-c1"],[28,29,"pl-s"],[37,38,"pl-s"],[44,45,"pl-s"],[46,48,"pl-s"]],[],[[6,7,"pl-s"],[7,19,"pl-c1"],[19,20,"pl-s"],[50,51,"pl-s"],[51,60,"pl-c1"],[60,61,"pl-s"]],[[6,7,"pl-s"],[7,18,"pl-c1"],[18,19,"pl-s"],[38,39,"pl-s"],[39,52,"pl-c1"],[52,53,"pl-s"]],[[44,45,"pl-s"],[45,56,"pl-c1"],[56,57,"pl-s"]],[[13,14,"pl-s"],[14,36,"pl-c1"],[36,37,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[45,47,"pl-s"]],[],[[12,13,"pl-s"],[13,26,"pl-c1"],[26,27,"pl-s"],[35,36,"pl-s"],[48,49,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[42,44,"pl-s"]],[[57,58,"pl-s"],[58,70,"pl-c1"],[70,71,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[29,31,"pl-s"]],[[20,21,"pl-s"],[21,32,"pl-c1"],[32,33,"pl-s"],[40,41,"pl-s"],[41,52,"pl-c1"],[52,53,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[51,53,"pl-s"]],[],[],[[59,60,"pl-s"],[66,67,"pl-s"]],[[46,47,"pl-s"],[47,53,"pl-c1"],[53,54,"pl-s"],[56,57,"pl-s"],[57,72,"pl-c1"],[72,73,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[41,43,"pl-s"]],[[54,55,"pl-s"],[55,67,"pl-c1"],[67,68,"pl-s"]],[[6,7,"pl-s"],[7,15,"pl-c1"],[15,16,"pl-s"],[36,37,"pl-s"],[37,46,"pl-c1"],[46,47,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[14,15,"pl-s"],[21,22,"pl-s"],[23,25,"pl-s"],[48,49,"pl-s"],[54,55,"pl-s"]],[[6,7,"pl-s"],[7,32,"pl-c1"],[32,33,"pl-s"]],[],[],[[29,30,"pl-s"],[30,45,"pl-c1"],[45,46,"pl-s"]],[],[],[[6,7,"pl-s"],[7,17,"pl-c1"],[17,18,"pl-s"]],[],[],[[42,43,"pl-s"],[43,56,"pl-c1"],[56,57,"pl-s"]],[[9,10,"pl-s"],[10,22,"pl-c1"],[22,23,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[53,55,"pl-s"]],[[36,37,"pl-s"],[45,46,"pl-s"]],[[34,35,"pl-s"],[35,47,"pl-c1"],[47,48,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[70,72,"pl-s"]],[[36,37,"pl-s"],[37,49,"pl-c1"],[49,50,"pl-s"],[55,56,"pl-s"],[56,63,"pl-c1"],[63,64,"pl-s"]],[[17,18,"pl-s"],[18,20,"pl-c1"],[20,21,"pl-s"]],[[6,7,"pl-s"],[7,16,"pl-c1"],[16,17,"pl-s"],[23,24,"pl-s"],[24,42,"pl-c1"],[42,43,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[22,24,"pl-s"]],[],[],[],[[0,27,"pl-mh"],[3,27,"pl-en"]],[],[[0,1,"pl-v"],[2,5,"pl-k"]],[[6,7,"pl-s"],[7,56,"pl-c1"],[56,57,"pl-s"]],[],[[31,32,"pl-s"],[32,48,"pl-c1"],[48,49,"pl-s"],[51,52,"pl-s"],[52,66,"pl-c1"],[66,67,"pl-s"]],[[10,11,"pl-s"],[11,13,"pl-c1"],[13,14,"pl-s"]],[[38,39,"pl-s"],[39,61,"pl-c1"],[61,62,"pl-s"]],[[16,17,"pl-s"],[17,39,"pl-c1"],[39,40,"pl-s"]],[],[],[],[],[],[[0,36,"pl-mh"],[3,36,"pl-en"]],[],[[4,5,"pl-s"],[5,13,"pl-c1"],[13,14,"pl-s"]],[],[[27,28,"pl-s"],[28,31,"pl-c1"],[31,32,"pl-s"]],[[0,1,"pl-s"],[1,8,"pl-c1"],[8,9,"pl-s"]],[[16,17,"pl-s"],[17,48,"pl-c1"],[48,49,"pl-s"]],[[53,54,"pl-s"],[54,58,"pl-c1"],[58,59,"pl-s"]],[[7,8,"pl-s"],[8,52,"pl-c1"],[52,53,"pl-s"]],[[23,24,"pl-s"],[24,28,"pl-c1"],[28,29,"pl-s"]],[],[],[[9,10,"pl-s"],[10,20,"pl-c1"],[20,21,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"]],[[6,8,"pl-s"],[17,19,"pl-s"],[30,31,"pl-s"],[40,41,"pl-s"]],[],[],[[6,7,"pl-s"],[7,11,"pl-c1"],[11,12,"pl-s"]],[[25,26,"pl-s"],[26,36,"pl-c1"],[36,37,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[21,23,"pl-s"],[30,32,"pl-s"],[45,46,"pl-s"],[55,56,"pl-s"]],[],[[49,50,"pl-s"],[50,59,"pl-c1"],[59,60,"pl-s"]],[[9,10,"pl-s"],[10,45,"pl-c1"],[45,46,"pl-s"]],[],[[49,50,"pl-s"],[50,62,"pl-c1"],[62,63,"pl-s"]],[],[[30,31,"pl-s"],[31,70,"pl-c1"],[70,71,"pl-s"]],[[34,35,"pl-s"],[35,44,"pl-c1"],[44,45,"pl-s"]],[],[],[],[[35,36,"pl-s"],[36,39,"pl-c1"],[39,40,"pl-s"]],[[10,11,"pl-s"],[11,23,"pl-c1"],[23,24,"pl-s"],[28,29,"pl-s"],[29,44,"pl-c1"],[44,45,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"]],[[10,11,"pl-s"],[22,23,"pl-s"],[26,27,"pl-s"],[44,45,"pl-s"]],[[17,18,"pl-s"],[18,27,"pl-c1"],[27,28,"pl-s"],[34,35,"pl-s"],[35,56,"pl-c1"],[56,57,"pl-s"]],[[6,7,"pl-s"],[7,17,"pl-c1"],[17,18,"pl-s"]],[[12,13,"pl-s"],[13,22,"pl-c1"],[22,23,"pl-s"],[46,47,"pl-s"],[49,50,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[38,40,"pl-s"],[54,56,"pl-s"],[59,61,"pl-s"],[70,72,"pl-s"]],[],[],[[29,30,"pl-s"],[39,40,"pl-s"]],[[13,14,"pl-s"],[67,68,"pl-s"]],[],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[35,36,"pl-s"],[47,48,"pl-s"],[56,57,"pl-s"],[66,67,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[27,29,"pl-s"],[39,40,"pl-s"],[49,50,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,7,"pl-s"],[28,29,"pl-s"]],[[23,24,"pl-s"],[33,34,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,7,"pl-s"],[20,21,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"]],[],[],[],[],[[11,13,"pl-s"],[16,18,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,7,"pl-s"],[24,25,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"]],[],[],[],[[0,30,"pl-mh"],[3,30,"pl-en"]],[],[],[[13,14,"pl-s"],[14,28,"pl-c1"],[28,29,"pl-s"],[54,55,"pl-s"],[55,73,"pl-c1"],[73,74,"pl-s"]],[],[[60,61,"pl-s"],[61,75,"pl-c1"],[75,76,"pl-s"]],[[21,22,"pl-s"],[28,29,"pl-s"],[30,31,"pl-s"],[31,41,"pl-c1"],[41,42,"pl-s"]],[],[],[[4,5,"pl-s"],[5,9,"pl-c1"],[9,10,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[32,34,"pl-s"]],[[44,46,"pl-s"],[50,52,"pl-s"],[60,62,"pl-s"],[67,69,"pl-s"]],[],[[7,8,"pl-s"]],[[38,39,"pl-s"],[39,60,"pl-c1"],[60,61,"pl-s"]],[[6,7,"pl-s"],[7,19,"pl-c1"],[19,20,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[31,33,"pl-s"]],[[51,53,"pl-s"],[58,60,"pl-s"],[65,67,"pl-s"],[71,73,"pl-s"]],[],[],[[15,19,"pl-ent"]],[[6,7,"pl-s"],[7,25,"pl-c1"],[25,26,"pl-s"],[40,44,"pl-ent"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[27,29,"pl-s"]],[[22,23,"pl-s"],[23,27,"pl-c1"],[27,28,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[28,30,"pl-s"]],[[52,53,"pl-s"],[53,62,"pl-c1"],[62,63,"pl-s"]],[[9,10,"pl-s"],[10,23,"pl-c1"],[23,24,"pl-s"]],[[6,7,"pl-s"],[7,21,"pl-c1"],[21,22,"pl-s"]],[],[[0,36,"pl-mh"],[3,36,"pl-en"]],[],[[38,39,"pl-s"],[39,57,"pl-c1"],[57,58,"pl-s"]],[],[],[[61,62,"pl-s"]],[[16,17,"pl-s"],[18,20,"pl-s"],[23,25,"pl-s"]],[[0,1,"pl-s"],[10,11,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[29,31,"pl-s"],[40,41,"pl-s"],[52,53,"pl-s"]],[],[[6,7,"pl-s"],[16,17,"pl-s"]],[[20,21,"pl-s"],[30,31,"pl-s"],[56,57,"pl-s"],[66,67,"pl-s"]],[],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[28,30,"pl-s"],[62,63,"pl-s"],[71,72,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[30,32,"pl-s"]],[],[[46,47,"pl-s"],[47,57,"pl-c1"],[57,58,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[20,22,"pl-s"]],[[60,61,"pl-s"]],[[15,16,"pl-s"],[56,57,"pl-s"],[66,67,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[13,15,"pl-s"]],[],[],[[0,35,"pl-mh"],[3,35,"pl-en"]],[],[[24,25,"pl-s"],[25,40,"pl-c1"],[40,41,"pl-s"],[47,48,"pl-s"],[48,65,"pl-c1"],[65,66,"pl-s"]],[[20,21,"pl-s"],[21,39,"pl-c1"],[39,40,"pl-s"]],[],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[47,49,"pl-s"]],[[6,7,"pl-s"],[7,49,"pl-c1"],[49,50,"pl-s"],[66,67,"pl-s"],[67,73,"pl-c1"],[73,74,"pl-s"]],[[29,30,"pl-s"],[30,45,"pl-c1"],[45,46,"pl-s"]],[[6,7,"pl-s"],[24,25,"pl-s"],[28,30,"pl-s"],[35,37,"pl-s"]],[],[[39,40,"pl-s"],[40,46,"pl-c1"],[46,47,"pl-s"]],[[16,17,"pl-s"],[17,45,"pl-c1"],[45,46,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[35,37,"pl-s"]],[[34,35,"pl-s"],[35,45,"pl-c1"],[45,46,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[33,35,"pl-s"]],[],[],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[13,15,"pl-s"],[59,60,"pl-s"],[65,66,"pl-s"]],[[38,39,"pl-s"],[44,45,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[12,14,"pl-s"]],[],[],[[0,65,"pl-mh"],[3,65,"pl-en"]],[],[[24,25,"pl-s"],[25,36,"pl-c1"],[36,37,"pl-s"],[39,40,"pl-s"],[40,56,"pl-c1"],[56,57,"pl-s"]],[[15,16,"pl-s"],[16,34,"pl-c1"],[34,35,"pl-s"]],[[23,24,"pl-s"],[24,42,"pl-c1"],[42,43,"pl-s"]],[],[[0,1,"pl-s"],[1,14,"pl-c1"],[14,15,"pl-s"]],[],[[25,26,"pl-s"],[43,44,"pl-s"]],[],[],[],[[40,41,"pl-s"],[47,48,"pl-s"]],[[0,1,"pl-s"],[1,12,"pl-c1"],[12,13,"pl-s"],[22,23,"pl-s"],[23,44,"pl-c1"],[44,45,"pl-s"],[53,54,"pl-s"],[54,66,"pl-c1"],[66,67,"pl-s"]],[],[[0,1,"pl-s"],[1,50,"pl-c1"],[50,51,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[41,43,"pl-s"]],[[6,7,"pl-s"],[32,33,"pl-s"]],[[18,19,"pl-s"],[23,24,"pl-s"],[27,28,"pl-s"],[34,35,"pl-s"]],[[6,7,"pl-s"],[7,18,"pl-c1"],[18,19,"pl-s"],[24,25,"pl-s"],[25,37,"pl-c1"],[37,38,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[25,27,"pl-s"]],[[54,55,"pl-s"],[63,64,"pl-s"]],[[6,7,"pl-s"],[13,14,"pl-s"],[16,17,"pl-s"],[23,24,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[39,41,"pl-s"],[42,43,"pl-s"],[51,52,"pl-s"]],[[60,61,"pl-s"],[68,69,"pl-s"]],[[55,57,"pl-s"],[57,58,"pl-s"],[64,65,"pl-s"]],[[6,7,"pl-s"],[7,28,"pl-c1"],[28,29,"pl-s"],[29,31,"pl-s"],[47,48,"pl-s"],[48,52,"pl-c1"],[52,53,"pl-s"]],[[50,51,"pl-s"],[51,61,"pl-c1"],[61,62,"pl-s"]],[[6,7,"pl-s"],[7,32,"pl-c1"],[32,33,"pl-s"],[36,37,"pl-s"],[37,62,"pl-c1"],[62,63,"pl-s"]],[],[[6,7,"pl-s"],[7,23,"pl-c1"],[23,24,"pl-s"],[33,34,"pl-s"],[34,65,"pl-c1"],[65,66,"pl-s"]],[[48,50,"pl-s"],[50,51,"pl-s"],[57,58,"pl-s"]],[[6,7,"pl-s"],[7,19,"pl-c1"],[19,20,"pl-s"],[20,22,"pl-s"]],[],[],[],[],[[6,7,"pl-s"],[7,50,"pl-c1"],[50,51,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[36,38,"pl-s"]],[[56,57,"pl-s"]],[[18,19,"pl-s"],[22,23,"pl-s"],[45,46,"pl-s"],[49,50,"pl-s"]],[[24,25,"pl-s"],[28,29,"pl-s"],[46,47,"pl-s"],[64,65,"pl-s"],[71,72,"pl-s"]],[[19,20,"pl-s"],[24,25,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[45,47,"pl-s"]],[],[],[[27,28,"pl-s"],[69,70,"pl-s"]],[],[[14,15,"pl-s"],[22,23,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[31,33,"pl-s"],[34,36,"pl-s"],[36,37,"pl-s"],[43,44,"pl-s"],[44,46,"pl-s"]],[],[[6,7,"pl-s"],[15,16,"pl-s"]],[[43,44,"pl-s"],[54,55,"pl-s"],[58,59,"pl-s"],[65,66,"pl-s"]],[],[[58,59,"pl-s"]],[[9,10,"pl-s"]],[[6,7,"pl-s"],[11,12,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[59,61,"pl-s"]],[[22,23,"pl-s"],[50,51,"pl-s"]],[[6,7,"pl-s"],[21,22,"pl-s"],[36,37,"pl-s"],[62,63,"pl-s"]],[],[[18,37,"pl-corl"]],[[43,44,"pl-s"],[44,51,"pl-c1"],[51,52,"pl-s"]],[],[],[],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[23,25,"pl-s"]],[[40,41,"pl-s"],[47,48,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[36,38,"pl-s"]],[],[],[[25,26,"pl-s"],[26,38,"pl-c1"],[38,39,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[20,22,"pl-s"],[28,29,"pl-s"],[29,72,"pl-c1"],[72,73,"pl-s"]],[],[[21,22,"pl-s"],[30,31,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[36,38,"pl-s"],[61,62,"pl-s"],[62,69,"pl-c1"],[69,70,"pl-s"]],[[18,19,"pl-s"],[19,68,"pl-c1"],[68,69,"pl-s"]],[[31,32,"pl-s"],[32,59,"pl-c1"],[59,60,"pl-s"],[65,66,"pl-s"],[66,67,"pl-c1"],[67,68,"pl-s"]],[[18,19,"pl-s"],[19,42,"pl-c1"],[42,43,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[12,14,"pl-s"]],[],[],[[0,86,"pl-mh"],[3,86,"pl-en"]],[],[],[[0,1,"pl-s"],[7,8,"pl-s"],[13,14,"pl-s"],[26,27,"pl-s"],[63,64,"pl-s"],[69,70,"pl-s"]],[[35,36,"pl-s"],[36,43,"pl-c1"],[43,44,"pl-s"],[49,50,"pl-s"],[50,67,"pl-c1"],[67,68,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[19,21,"pl-s"],[40,41,"pl-s"],[52,53,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[16,18,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[23,25,"pl-s"]],[],[],[[6,7,"pl-s"],[14,15,"pl-s"],[40,41,"pl-s"],[58,59,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[34,36,"pl-s"],[37,39,"pl-s"],[39,40,"pl-s"],[46,47,"pl-s"],[47,49,"pl-s"],[51,52,"pl-s"],[52,66,"pl-c1"],[66,67,"pl-s"]],[[6,7,"pl-s"],[7,19,"pl-c1"],[19,20,"pl-s"]],[],[],[],[[60,61,"pl-s"]],[[15,16,"pl-s"],[20,21,"pl-s"],[21,34,"pl-c1"],[34,35,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[45,47,"pl-s"],[55,56,"pl-s"],[56,63,"pl-c1"],[63,64,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[35,37,"pl-s"]],[[28,29,"pl-s"],[35,36,"pl-s"],[39,40,"pl-s"],[52,53,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[35,37,"pl-s"],[60,61,"pl-s"]],[[23,24,"pl-s"],[29,30,"pl-s"],[36,37,"pl-s"],[39,40,"pl-s"],[52,53,"pl-s"],[58,59,"pl-s"],[64,65,"pl-s"]],[],[[24,25,"pl-s"],[46,47,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[15,17,"pl-s"],[18,19,"pl-s"],[25,26,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[21,23,"pl-s"],[24,25,"pl-s"],[37,38,"pl-s"],[51,57,"pl-ent"]],[],[[29,35,"pl-ent"],[40,44,"pl-ent"],[65,66,"pl-s"],[71,72,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[41,43,"pl-s"]],[[31,32,"pl-s"],[44,45,"pl-s"]],[],[[38,39,"pl-s"],[45,46,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[29,31,"pl-s"]],[[6,7,"pl-s"],[13,14,"pl-s"],[19,20,"pl-s"],[32,33,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[25,27,"pl-s"]],[],[[0,59,"pl-mh"],[3,59,"pl-en"]],[],[],[[46,47,"pl-s"],[47,62,"pl-c1"],[62,63,"pl-s"]],[[0,1,"pl-s"],[1,36,"pl-c1"],[36,37,"pl-s"],[39,40,"pl-s"],[40,53,"pl-c1"],[53,54,"pl-s"]],[[26,27,"pl-s"],[27,45,"pl-c1"],[45,46,"pl-s"]],[[34,35,"pl-s"]],[[20,21,"pl-s"],[23,24,"pl-s"],[24,42,"pl-c1"],[42,43,"pl-s"]],[[0,1,"pl-s"],[1,24,"pl-c1"],[24,25,"pl-s"]],[],[],[],[[0,1,"pl-s"],[13,14,"pl-s"]],[],[[53,54,"pl-s"],[56,57,"pl-s"]],[],[[0,1,"pl-s"],[24,25,"pl-s"],[51,52,"pl-s"],[61,62,"pl-s"]],[],[],[[8,9,"pl-s"],[20,21,"pl-s"]],[],[],[[34,35,"pl-s"],[35,45,"pl-c1"],[45,46,"pl-s"]],[],[[40,41,"pl-s"],[41,71,"pl-c1"],[71,72,"pl-s"]],[[34,35,"pl-s"],[35,70,"pl-c1"],[70,71,"pl-s"]],[],[[0,1,"pl-s"],[1,32,"pl-c1"],[32,33,"pl-s"],[35,36,"pl-s"],[36,66,"pl-c1"],[66,67,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[17,19,"pl-s"],[35,36,"pl-s"],[43,44,"pl-s"],[53,54,"pl-s"],[62,63,"pl-s"]],[[6,7,"pl-s"],[12,13,"pl-s"],[15,16,"pl-s"],[25,26,"pl-s"],[52,53,"pl-s"],[59,60,"pl-s"]],[[29,30,"pl-s"],[30,32,"pl-en"]],[[12,13,"pl-s"],[22,23,"pl-s"]],[[16,17,"pl-s"],[48,49,"pl-s"],[61,62,"pl-s"]],[[21,22,"pl-s"]],[[6,7,"pl-s"],[19,20,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[12,13,"pl-s"],[19,20,"pl-s"],[68,70,"pl-s"]],[[6,7,"pl-s"],[30,31,"pl-s"]],[[6,7,"pl-s"],[19,20,"pl-s"]],[[34,35,"pl-s"],[35,60,"pl-c1"],[60,61,"pl-s"]],[],[],[],[[6,7,"pl-s"],[7,52,"pl-c1"],[52,53,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[12,13,"pl-s"],[19,20,"pl-s"],[66,68,"pl-s"]],[],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[12,13,"pl-s"],[19,20,"pl-s"],[48,50,"pl-s"]],[[6,7,"pl-s"],[7,17,"pl-c1"],[17,18,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[12,13,"pl-s"],[19,20,"pl-s"],[56,58,"pl-s"]],[],[],[[6,7,"pl-s"],[11,12,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[19,21,"pl-s"],[49,50,"pl-s"],[56,57,"pl-s"]],[],[],[],[[65,66,"pl-s"],[72,73,"pl-s"]],[[12,13,"pl-s"],[25,26,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[35,37,"pl-s"]],[],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[20,22,"pl-s"]],[],[],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[45,47,"pl-s"]],[],[],[[6,7,"pl-s"],[18,19,"pl-s"],[64,65,"pl-s"]],[[27,28,"pl-s"]],[[12,13,"pl-s"],[22,23,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[12,14,"pl-s"]],[[32,33,"pl-s"],[40,41,"pl-s"]],[],[[0,75,"pl-mh"],[3,75,"pl-en"]],[],[],[],[[20,21,"pl-s"],[21,34,"pl-c1"],[34,35,"pl-s"],[37,38,"pl-s"],[38,53,"pl-c1"],[53,54,"pl-s"]],[[0,1,"pl-s"],[1,24,"pl-c1"],[24,25,"pl-s"],[31,32,"pl-s"],[32,39,"pl-c1"],[39,40,"pl-s"]],[[0,1,"pl-s"],[25,26,"pl-s"]],[[9,10,"pl-s"],[16,17,"pl-s"],[34,35,"pl-s"],[35,53,"pl-c1"],[53,54,"pl-s"]],[[12,13,"pl-s"],[13,20,"pl-c1"],[20,21,"pl-s"],[28,29,"pl-s"],[29,42,"pl-c1"],[42,43,"pl-s"]],[[0,1,"pl-s"],[1,8,"pl-c1"],[8,9,"pl-s"]],[],[],[],[[26,27,"pl-s"],[27,31,"pl-c1"],[31,32,"pl-s"],[52,53,"pl-s"],[53,63,"pl-c1"],[63,64,"pl-s"]],[[34,35,"pl-s"],[35,45,"pl-c1"],[45,46,"pl-s"]],[[22,23,"pl-s"],[23,53,"pl-c1"],[53,54,"pl-s"]],[[0,1,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[19,21,"pl-s"],[56,57,"pl-s"],[64,65,"pl-s"]],[[6,7,"pl-s"],[15,16,"pl-s"]],[[57,58,"pl-s"],[66,67,"pl-s"]],[[6,7,"pl-s"],[7,11,"pl-c1"],[11,12,"pl-s"],[59,60,"pl-s"],[60,64,"pl-c1"],[64,65,"pl-s"]],[],[[62,63,"pl-s"],[69,70,"pl-s"]],[[6,7,"pl-s"],[13,14,"pl-s"],[17,19,"pl-s"],[19,20,"pl-s"],[26,27,"pl-s"],[27,29,"pl-s"],[30,31,"pl-s"],[37,38,"pl-s"],[46,47,"pl-s"],[55,56,"pl-s"]],[],[],[[15,16,"pl-s"],[24,25,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[21,22,"pl-s"],[22,32,"pl-c1"],[32,33,"pl-s"],[61,63,"pl-s"]],[[6,7,"pl-s"],[7,17,"pl-c1"],[17,18,"pl-s"]],[[6,7,"pl-s"],[7,18,"pl-c1"],[18,19,"pl-s"],[23,24,"pl-s"],[24,56,"pl-c1"],[56,57,"pl-s"]],[],[[9,10,"pl-s"],[10,24,"pl-c1"],[24,25,"pl-s"],[59,60,"pl-s"],[60,64,"pl-c1"],[64,65,"pl-s"]],[[30,31,"pl-s"],[37,38,"pl-s"]],[[7,11,"pl-ent"]],[[38,39,"pl-s"],[39,49,"pl-c1"],[49,50,"pl-s"],[57,58,"pl-s"],[58,68,"pl-c1"],[68,69,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[59,61,"pl-s"],[67,68,"pl-s"],[68,72,"pl-c1"],[72,73,"pl-s"]],[[49,50,"pl-s"],[50,54,"pl-c1"],[54,55,"pl-s"]],[[51,52,"pl-s"],[58,59,"pl-s"]],[[6,7,"pl-s"],[16,17,"pl-s"]],[[7,10,"pl-ent"],[11,14,"pl-e"],[15,19,"pl-e"],[33,34,"pl-s"],[43,44,"pl-s"]],[],[[6,7,"pl-s"],[7,17,"pl-c1"],[17,18,"pl-s"],[32,33,"pl-s"],[33,46,"pl-c1"],[46,47,"pl-s"],[55,56,"pl-s"],[56,69,"pl-c1"],[69,70,"pl-s"]],[[31,32,"pl-s"],[42,43,"pl-s"],[53,54,"pl-s"],[54,58,"pl-c1"],[58,59,"pl-s"]],[[52,53,"pl-s"],[61,62,"pl-s"]],[],[[18,19,"pl-s"],[19,46,"pl-c1"],[46,47,"pl-s"]],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[12,13,"pl-s"],[19,20,"pl-s"],[23,24,"pl-s"],[24,31,"pl-c1"],[31,32,"pl-s"],[51,53,"pl-s"],[57,58,"pl-s"],[58,62,"pl-c1"],[62,63,"pl-s"]],[[6,7,"pl-s"],[25,29,"pl-ent"],[30,31,"pl-s"],[33,34,"pl-s"],[46,47,"pl-s"]],[],[[8,15,"pl-ent"],[26,30,"pl-ent"]],[[6,7,"pl-s"],[7,52,"pl-c1"],[52,53,"pl-s"]],[[21,22,"pl-s"],[22,46,"pl-c1"],[46,47,"pl-s"]],[],[],[],[],[],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[19,20,"pl-s"],[20,24,"pl-c1"],[24,25,"pl-s"],[68,70,"pl-s"]],[[16,17,"pl-s"],[26,27,"pl-s"],[34,35,"pl-s"],[35,70,"pl-c1"],[70,71,"pl-s"]],[[6,7,"pl-s"],[13,14,"pl-s"]],[[31,32,"pl-s"],[32,36,"pl-c1"],[36,37,"pl-s"],[48,49,"pl-s"],[49,59,"pl-c1"],[59,60,"pl-s"]],[[35,36,"pl-s"],[45,46,"pl-s"]],[[23,26,"pl-ent"],[27,31,"pl-e"]],[[30,31,"pl-s"],[49,52,"pl-ent"],[53,57,"pl-e"],[58,59,"pl-s"]],[[18,19,"pl-s"],[27,28,"pl-s"],[30,31,"pl-s"],[37,38,"pl-s"]],[[19,20,"pl-s"],[31,38,"pl-ent"],[39,40,"pl-s"]],[[36,37,"pl-s"],[46,47,"pl-s"]],[],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[40,42,"pl-s"]],[[52,53,"pl-s"],[61,62,"pl-s"]],[[6,7,"pl-s"],[7,37,"pl-c1"],[37,38,"pl-s"]],[[6,7,"pl-s"],[7,40,"pl-c1"],[40,41,"pl-s"]],[],[],[],[],[[26,27,"pl-s"],[35,36,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[53,55,"pl-s"]],[[12,13,"pl-s"],[24,25,"pl-s"],[29,30,"pl-s"],[30,34,"pl-c1"],[34,35,"pl-s"]],[],[[64,65,"pl-s"],[71,72,"pl-s"]],[[12,13,"pl-s"],[25,26,"pl-s"]],[[0,1,"pl-v"],[2,5,"pl-k"],[6,8,"pl-s"],[54,56,"pl-s"]],[[42,43,"pl-s"],[43,47,"pl-c1"],[47,48,"pl-s"]],[[13,14,"pl-s"],[14,16,"pl-c1"],[16,17,"pl-s"],[31,32,"pl-s"],[32,34,"pl-c1"],[34,35,"pl-s"]],[[6,7,"pl-s"],[7,39,"pl-c1"],[39,40,"pl-s"]],[[33,34,"pl-s"],[34,44,"pl-c1"],[44,45,"pl-s"]],[[35,40,"pl-ent"],[41,42,"pl-s"],[42,57,"pl-c1"],[57,58,"pl-s"]],[],[],[[15,16,"pl-s"],[23,24,"pl-s"],[28,29,"pl-s"],[29,33,"pl-c1"],[33,34,"pl-s"],[39,40,"pl-s"],[40,50,"pl-c1"],[50,51,"pl-s"]],[[19,20,"pl-s"],[29,30,"pl-s"],[39,46,"pl-ent"],[58,62,"pl-ent"]],[[10,11,"pl-s"],[17,18,"pl-s"]],[[33,34,"pl-s"],[44,45,"pl-s"],[61,65,"pl-ent"]],[[7,8,"pl-s"],[8,51,"pl-c1"],[51,52,"pl-s"]],[[68,69,"pl-s"]],[[7,11,"pl-ent"],[12,13,"pl-s"],[43,44,"pl-s"],[51,52,"pl-s"],[56,57,"pl-s"],[65,66,"pl-s"]],[[18,19,"pl-s"],[30,37,"pl-ent"],[38,39,"pl-s"]]],"colorizedLines":null}},"title":"moonlight-steam-sync-decky/DEVICE-CHECKLIST.md at main · episode6/moonlight-steam-sync-decky","appPayload":{},"meta":{"title":"moonlight-steam-sync-decky/DEVICE-CHECKLIST.md at main · episode6/moonlight-steam-sync-decky"}}</script>
  <div data-target="react-app.reactRoot"><meta name="github-code-view-meta-stats" id="github-code-view-meta-stats" data-hydrostats="publish"/> <!-- --> <a hidden="" id="code-view-repo-link" href="/episode6/moonlight-steam-sync-decky" data-discover="true"></a> <div class="d-none"></div><div><div style="--spacing:var(--spacing-none)" class="prc-PageLayout-PageLayoutRoot--KH-d" data-component="SplitPageLayout" data-has-sidebar="true"><div class="prc-PageLayout-SidebarWrapper-kLG4B CopilotSidePanelSidebar-module__SidePanel__L3O0C CopilotSidePanelSidebar-module__HiddenSidePanel__TBRGn" style="--spacing-column:var(--spacing-none)" data-is-hidden="false" data-position="end" data-sticky="true" data-responsive-variant="fullscreen"><div class="prc-PageLayout-VerticalDivider-9QRmK prc-PageLayout-SidebarVerticalDivider-0Rl0V" data-component="PageLayout.VerticalDivider" data-variant="line" data-position="end" style="--spacing:var(--spacing-none)"><div class="prc-PageLayout-DraggableHandle-9s6B4" data-component="PageLayout.DragHandle" role="slider" aria-label="Draggable pane splitter" aria-valuemin="450" aria-valuemax="768" aria-valuenow="544" aria-valuetext="Pane width 544 pixels" tabindex="0"></div></div><div class="prc-PageLayout-Sidebar-iciWg" data-component="SplitPageLayout.Sidebar" data-resizable="true" style="--spacing:var(--spacing-normal);--pane-min-width:450px;--pane-max-width:768px;--pane-width-custom:544px;--pane-width-size:var(--pane-width-custom);--pane-width:544px"><div class="height-full" data-testid="copilot-code-view-side-panel"><div id="copilot-side-panel-content" class="height-full"></div></div></div></div><div class="prc-PageLayout-PageLayoutWrapper-2BhU2" data-width="full"><div class="prc-PageLayout-PageLayoutContent-BneH9"><div id="repos-file-tree-sidebar" class="CodeViewFileTreeLayout-module__sidebar__n_Aau" tabindex="0"><div class="prc-PageLayout-PaneWrapper-pHPop ReposFileTreePane-module__Pane__rBZpI ReposFileTreePane-module__HideTree__AYZnm ReposFileTreePane-module__HidePane__VHAVt" style="--offset-header:0px;--spacing-row:var(--spacing-none);--spacing-column:var(--spacing-none)" data-is-hidden="false" data-position="start" data-sticky="true"><div class="prc-PageLayout-HorizontalDivider-JLVqp prc-PageLayout-PaneHorizontalDivider-9tbnE" data-component="PageLayout.HorizontalDivider" data-variant-regular="none" data-variant-narrow="none" data-position="start" style="--spacing-divider:var(--spacing-none);--spacing:var(--spacing-none)"></div><div class="prc-PageLayout-Pane-AyzHK" data-component="SplitPageLayout.Pane" data-resizable="true" style="--spacing:var(--spacing-none);--pane-min-width:256px;--pane-max-width:calc(100vw - var(--pane-max-width-diff));--pane-width-size:var(--pane-width-large);--pane-width:320px"></div><div class="prc-PageLayout-VerticalDivider-9QRmK prc-PageLayout-PaneVerticalDivider-le57g" data-component="PageLayout.VerticalDivider" data-variant-narrow="none" data-variant-regular="line" data-variant-wide="line" data-position="start" style="--spacing:var(--spacing-none)"><div class="prc-PageLayout-DraggableHandle-9s6B4" data-component="PageLayout.DragHandle" role="slider" aria-label="Draggable pane splitter" aria-valuemin="256" aria-valuemax="600" aria-valuenow="320" aria-valuetext="Pane width 320 pixels" tabindex="0"></div></div></div></div><div data-component="SplitPageLayout.Content" class="prc-PageLayout-ContentWrapper-gR9eG"><div class="prc-PageLayout-Content-xWL-A" data-width="full" style="--spacing:var(--spacing-none)"><div class="SharedPageLayout-module__content__IwGAp" data-selector="repos-split-pane-content" id="repos-split-pane-content" tabindex="0"> <!-- --> <div class="container CodeViewHeader-module__Box__JkPOb"><div class="CodeViewHeader-module__StickyHeader__Qn7UN" id="StickyHeader"><div class="CodeViewHeader-module__Box_1__SbNDV"><div class="CodeViewHeader-module__Box_2__TB46f"><div class="react-code-view-header-wrap--narrow CodeViewHeader-module__Box_3__q1zUL"><div class="CodeViewHeader-module__treeToggleWrapper__RQ__9"><h2 class="use-tree-pane-module__Heading__s4QbZ prc-Heading-Heading-MtWFE" data-component="Heading"><button data-component="Button" type="button" aria-label="Expand file tree" data-testid="expand-file-tree-button-mobile" class="prc-Button-ButtonBase-9n-Xk ExpandFileTreeButton-module__Button_1__Svs95" data-loading="false" data-size="medium" data-variant="invisible"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-arrow-left" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.78 12.53a.75.75 0 0 1-1.06 0L2.47 8.28a.75.75 0 0 1 0-1.06l4.25-4.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042L4.81 7h7.44a.75.75 0 0 1 0 1.5H4.81l2.97 2.97a.75.75 0 0 1 0 1.06Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3">Files</span></span></button><button data-component="IconButton" type="button" data-testid="expand-file-tree-button" aria-controls="repos-file-tree" class="prc-Button-ButtonBase-9n-Xk position-relative ExpandFileTreeButton-module__expandButton__hDOcv ExpandFileTreeButton-module__filesButtonBreakpoint__zEvz3 fgColor-muted prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="invisible" aria-labelledby="_R_4lla9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-sidebar-collapse" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M6.823 7.823a.25.25 0 0 1 0 .354l-2.396 2.396A.25.25 0 0 1 4 10.396V5.604a.25.25 0 0 1 .427-.177Z"></path><path d="M1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25V1.75C0 .784.784 0 1.75 0ZM1.5 1.75v12.5c0 .138.112.25.25.25H9.5v-13H1.75a.25.25 0 0 0-.25.25ZM11 14.5h3.25a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25H11Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="se" data-component="Tooltip" aria-hidden="true" id="_R_4lla9lik5_">Expand file tree</span><div class="d-none"></div></h2></div><div class="react-code-view-header-mb--narrow mr-2"><button data-component="Button" type="button" aria-haspopup="true" aria-expanded="false" tabindex="0" aria-label="main branch" data-testid="anchor-button" data-icv-name="Switch branches/tags" class="prc-Button-ButtonBase-9n-Xk ref-selector-class RefSelectorAnchoredOverlay-module__RefSelectorOverlayBtn__a3WK3" data-loading="false" data-size="medium" data-variant="default" id="ref-picker-repos-header-ref-selector-wide"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-git-branch" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628A2.25 2.25 0 0 1 9.5 3.25Zm-6 0a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Zm8.25-.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3"><div class="RefSelectorAnchoredOverlay-module__RefSelectorOverlayContainer__yaf4p"><div style="max-width:125px" class="ref-selector-button-text-container RefSelectorAnchoredOverlay-module__RefSelectorBtnTextContainer__Di3rk"><span class="RefSelectorAnchoredOverlay-module__RefSelectorText__w_fmP">main</span></div></div></span><span data-component="trailingVisual" class="prc-Button-Visual-YNt2F prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-down" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z"></path></svg></span></span></button><div class="d-none"></div></div><div class="react-code-view-header-mb--narrow CodeViewHeader-module__Box_5__MQ0hL"><div class="Breadcrumb-module__container__Vxvev Breadcrumb-module__lg__Rjz0A"><nav data-testid="breadcrumbs" aria-labelledby="repos-header-breadcrumb-heading" id="repos-header-breadcrumb" class="Breadcrumb-module__nav__rQFDj"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading" id="repos-header-breadcrumb-heading">Breadcrumbs</h2><ol class="Breadcrumb-module__list__ZH6zr"><li class="Breadcrumb-module__listItem__Ib0x_"><a class="Breadcrumb-module__repoLink__O2Nbs prc-Link-Link-9ZwDx" data-component="Link" data-testid="breadcrumbs-repo-link" href="/episode6/moonlight-steam-sync-decky/tree/main" data-discover="true">moonlight-steam-sync-decky</a></li></ol></nav><div data-testid="breadcrumbs-filename" class="Breadcrumb-module__filename__equZR"><span class="Breadcrumb-module__separator__eNwsI Breadcrumb-module__lg__Rjz0A" aria-hidden="true">/</span><h1 class="Breadcrumb-module__filenameHeading__MNMtw Breadcrumb-module__lg__Rjz0A prc-Heading-Heading-MtWFE" data-component="Heading" tabindex="-1" id="file-name-id">DEVICE-CHECKLIST.md</h1></div><button data-component="IconButton" type="button" class="prc-Button-ButtonBase-9n-Xk ml-2 prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="invisible" aria-labelledby="_R_7lla9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copy" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg></button><span class="CopyToClipboardIconButton-module__tooltip__WyiwL prc-TooltipV2-Tooltip-tLeuB" data-direction="nw" data-component="Tooltip" aria-label="Copy path" aria-hidden="true" id="_R_7lla9lik5_">Copy path</span></div></div></div><div class="react-code-view-header-element--wide"><div class="CodeViewHeader-module__Box_7___0R6c"><div class="d-flex gap-2"><div><div class="CodeViewHeader-module__FileResultsList__JDzUy"><span class="d-flex FileResultsList-module__FilesSearchBox__ivVkc TextInput-wrapper prc-components-TextInputWrapper-Hpdqi prc-components-TextInputBaseWrapper-wY-n0" data-no-trailing-action="true" data-component="TextInput" data-leading-visual="true" data-trailing-visual="true" aria-busy="false"><span class="TextInput-icon" id="_R_1cpla9lik5_" aria-hidden="true" data-component="TextInput.LeadingVisual"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-search" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z"></path></svg></span><input type="text" aria-label="Go to file" role="combobox" aria-controls="file-results-list" aria-expanded="false" aria-haspopup="dialog" autoCorrect="off" spellCheck="false" placeholder="Go to file" aria-describedby="_R_1cpla9lik5_ _R_1cpla9lik5H1_" data-component="input" class="prc-components-Input-IwWrt" value=""/><span class="TextInput-icon" id="_R_1cpla9lik5H1_" aria-hidden="true" data-component="TextInput.TrailingVisual"></span></span></div><div class="d-none"></div></div><button data-component="Button" type="button" style="display:none" class="prc-Button-ButtonBase-9n-Xk NavigationMenu-module__Button__LpKgm" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="default"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="text" class="prc-Button-Label-FWkx3">Blame</span></span></button><div class="d-none"></div><button data-component="IconButton" type="button" data-testid="more-file-actions-button-nav-menu-wide" aria-haspopup="true" aria-expanded="false" tabindex="0" class="prc-Button-ButtonBase-9n-Xk js-blob-dropdown-click NavigationMenu-module__IconButton__HpX3G prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="default" aria-labelledby="_R_7p9la9lik5_" id="_R_99la9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-kebab-horizontal" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM1.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm13 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="nw" data-component="Tooltip" aria-hidden="true" id="_R_7p9la9lik5_">More file actions</span></div></div></div><div class="react-code-view-header-element--narrow"><div class="CodeViewHeader-module__Box_7___0R6c"><div class="d-flex gap-2"><button data-component="Button" type="button" style="display:none" class="prc-Button-ButtonBase-9n-Xk NavigationMenu-module__Button__LpKgm" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="default"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="text" class="prc-Button-Label-FWkx3">Blame</span></span></button><div class="d-none"></div><button data-component="IconButton" type="button" data-testid="more-file-actions-button-nav-menu-narrow" aria-haspopup="true" aria-expanded="false" tabindex="0" class="prc-Button-ButtonBase-9n-Xk js-blob-dropdown-click NavigationMenu-module__IconButton__HpX3G prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="default" aria-labelledby="_R_7pdla9lik5_" id="_R_9dla9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-kebab-horizontal" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM1.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm13 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="nw" data-component="Tooltip" aria-hidden="true" id="_R_7pdla9lik5_">More file actions</span></div></div></div></div></div></div></div><div class="CodeView-module__contentWrapper__cG2JH"><div class="react-code-view-bottom-padding"><div class="BlobTopBanners-module__Box__v_nvx"></div></div> <div class="d-none"></div><div class="d-flex flex-column border rounded-2 tmp-mb-3 pl-1"><div class="LatestCommit-module__Box__B25ZT"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading">Latest commit</h2><div style="width:120px" class="Skeleton Skeleton--text" data-testid="loading"> </div><div class="d-flex flex-shrink-0 gap-2"><div data-testid="latest-commit-details" class="d-none d-sm-flex flex-items-center"></div><div class="d-flex gap-2"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading">History</h2><a data-component="LinkButton" href="/episode6/moonlight-steam-sync-decky/commits/main/DEVICE-CHECKLIST.md" class="prc-Button-ButtonBase-9n-Xk d-none d-lg-flex LinkButton-module__linkButton__nFnov flex-items-center fgColor-default" data-loading="false" data-size="small" data-variant="invisible"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-history" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m.427 1.927 1.215 1.215a8.002 8.002 0 1 1-1.6 5.685.75.75 0 1 1 1.493-.154 6.5 6.5 0 1 0 1.18-4.458l1.358 1.358A.25.25 0 0 1 3.896 6H.25A.25.25 0 0 1 0 5.75V2.104a.25.25 0 0 1 .427-.177ZM7.75 4a.75.75 0 0 1 .75.75v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5A.75.75 0 0 1 7.75 4Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3"><span class="fgColor-default">History</span></span></span></a><div class="d-sm-none"></div><div class="d-flex d-lg-none"><a data-component="LinkButton" aria-label="View commit history for this file." href="/episode6/moonlight-steam-sync-decky/commits/main/DEVICE-CHECKLIST.md" class="prc-Button-ButtonBase-9n-Xk LinkButton-module__linkButton__nFnov flex-items-center fgColor-default" data-loading="false" data-size="small" data-variant="invisible" aria-describedby="_R_4mlala9lik5_"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-history" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m.427 1.927 1.215 1.215a8.002 8.002 0 1 1-1.6 5.685.75.75 0 1 1 1.493-.154 6.5 6.5 0 1 0 1.18-4.458l1.358 1.358A.25.25 0 0 1 3.896 6H.25A.25.25 0 0 1 0 5.75V2.104a.25.25 0 0 1 .427-.177ZM7.75 4a.75.75 0 0 1 .75.75v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5A.75.75 0 0 1 7.75 4Z"></path></svg></span></span></a><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="s" data-component="Tooltip" role="tooltip" aria-hidden="true" id="_R_4mlala9lik5_">History</span></div></div></div></div></div><div class="d-flex flex-row"><div class="container BlobViewContent-module__blobContainer__DtH2d"><div class="react-code-size-details-banner BlobViewContent-module__codeSizeDetails__e5sUw"><div class="react-code-size-details-banner CodeSizeDetails-module__Box__VcD6l"><div class="text-mono CodeSizeDetails-module__Box_1__GVxQL"><div data-testid="blob-size" class="CodeSizeDetails-module__Truncate_1__lE93V prc-Truncate-Truncate-2G1eo" data-inline="true" title="74.8 KB" style="--truncate-max-width:100%"><span>1203 lines (1149 loc) · 74.8 KB</span></div></div></div></div><div class="react-blob-view-header-sticky BlobViewContent-module__stickyHeader__VwxB5" id="repos-sticky-header"><div class="BlobViewHeader-module__Box__yhm9u"><div class="react-blob-sticky-header"><div class="FileNameStickyHeader-module__outerWrapper__ZL4Xc FileNameStickyHeader-module__outerWrapperHidden__Zpynk"><div class="FileNameStickyHeader-module__Box_1__Hazu5"><div class="FileNameStickyHeader-module__Box_2__hoolP"><div class="FileNameStickyHeader-module__Box_3__MVKsk"><button data-component="Button" type="button" aria-haspopup="true" aria-expanded="false" tabindex="0" aria-label="main branch" data-testid="anchor-button" data-icv-name="Switch branches/tags" class="prc-Button-ButtonBase-9n-Xk ref-selector-class RefSelectorAnchoredOverlay-module__RefSelectorOverlayBtn__a3WK3" data-loading="false" data-size="medium" data-variant="default" id="ref-picker-repos-header-ref-selector"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-git-branch" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628A2.25 2.25 0 0 1 9.5 3.25Zm-6 0a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Zm8.25-.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3"><div class="RefSelectorAnchoredOverlay-module__RefSelectorOverlayContainer__yaf4p"><div style="max-width:125px" class="ref-selector-button-text-container RefSelectorAnchoredOverlay-module__RefSelectorBtnTextContainer__Di3rk"><span class="RefSelectorAnchoredOverlay-module__RefSelectorText__w_fmP">main</span></div></div></span><span data-component="trailingVisual" class="prc-Button-Visual-YNt2F prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-down" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z"></path></svg></span></span></button><div class="d-none"></div></div><div class="FileNameStickyHeader-module__Box_4__FLhtt"><div class="Breadcrumb-module__container__Vxvev Breadcrumb-module__md__Wb1Gs"><nav data-testid="breadcrumbs" aria-labelledby="sticky-breadcrumb-heading" id="sticky-breadcrumb" class="Breadcrumb-module__nav__rQFDj"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading" id="sticky-breadcrumb-heading">Breadcrumbs</h2><ol class="Breadcrumb-module__list__ZH6zr"><li class="Breadcrumb-module__listItem__Ib0x_"><a class="Breadcrumb-module__repoLink__O2Nbs prc-Link-Link-9ZwDx" data-component="Link" data-testid="breadcrumbs-repo-link" href="/episode6/moonlight-steam-sync-decky/tree/main" data-discover="true">moonlight-steam-sync-decky</a></li></ol></nav><div data-testid="breadcrumbs-filename" class="Breadcrumb-module__filename__equZR"><span class="Breadcrumb-module__separator__eNwsI Breadcrumb-module__md__Wb1Gs" aria-hidden="true">/</span><h1 class="Breadcrumb-module__filenameHeading__MNMtw Breadcrumb-module__md__Wb1Gs prc-Heading-Heading-MtWFE" data-component="Heading" tabindex="-1" id="sticky-file-name-id">DEVICE-CHECKLIST.md</h1></div><button data-component="IconButton" type="button" class="prc-Button-ButtonBase-9n-Xk ml-2 prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="invisible" aria-labelledby="_R_7lcpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copy" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg></button><span class="CopyToClipboardIconButton-module__tooltip__WyiwL prc-TooltipV2-Tooltip-tLeuB" data-direction="s" data-component="Tooltip" aria-label="Copy path" aria-hidden="true" id="_R_7lcpala9lik5_">Copy path</span></div></div></div><button data-component="Button" type="button" class="prc-Button-ButtonBase-9n-Xk FileNameStickyHeader-module__Button__LSEU_ FileNameStickyHeader-module__GoToTopButton__nxAFn" data-loading="false" data-size="small" data-variant="invisible"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-arrow-up" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.47 7.78a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0l4.25 4.25a.751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018L9 4.81v7.44a.75.75 0 0 1-1.5 0V4.81L4.53 7.78a.75.75 0 0 1-1.06 0Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3">Top</span></span></button></div></div></div><div class="BlobViewHeader-module__Box_1__VEmuQ"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading">File metadata and controls</h2><div class="BlobViewHeader-module__Box_2__icUs2"><ul aria-label="File view" class="prc-SegmentedControl-SegmentedControl-lqIXp BlobTabButtons-module__SegmentedControl__jen2u" data-variant="default" data-size="small" data-component="SegmentedControl"><li class="prc-SegmentedControl-Item-tSCQh" data-selected="" data-component="SegmentedControl.Button"><button aria-pressed="true" class="prc-SegmentedControl-Button-E48xz" type="button" style="--separator-color:transparent"><span class="prc-SegmentedControl-Content-1COlk segmentedControl-content"><div class="prc-SegmentedControl-Text-7S2y2 segmentedControl-text" data-text="Preview">Preview</div></span></button></li><li class="prc-SegmentedControl-Item-tSCQh" data-component="SegmentedControl.Button"><button aria-pressed="false" class="prc-SegmentedControl-Button-E48xz" type="button" style="--separator-color:var(--borderColor-default)"><span class="prc-SegmentedControl-Content-1COlk segmentedControl-content"><div class="prc-SegmentedControl-Text-7S2y2 segmentedControl-text" data-text="Code">Code</div></span></button></li><li class="prc-SegmentedControl-Item-tSCQh" data-component="SegmentedControl.Button"><button aria-pressed="false" class="prc-SegmentedControl-Button-E48xz" type="button" style="--separator-color:var(--borderColor-default)"><span class="prc-SegmentedControl-Content-1COlk segmentedControl-content"><div class="prc-SegmentedControl-Text-7S2y2 segmentedControl-text" data-text="Blame">Blame</div></span></button></li></ul><div class="d-none"></div><div class="react-code-size-details-in-header CodeSizeDetails-module__Box__VcD6l"><div class="text-mono CodeSizeDetails-module__Box_1__GVxQL"><div data-testid="blob-size" class="CodeSizeDetails-module__Truncate_1__lE93V prc-Truncate-Truncate-2G1eo" data-inline="true" title="74.8 KB" style="--truncate-max-width:100%"><span>1203 lines (1149 loc) · 74.8 KB</span></div></div></div></div><div class="BlobViewHeader-module__Box_3__ng6v2"><div class="d-none"></div><div class="react-blob-header-edit-and-raw-actions BlobViewHeader-module__Box_4__J4Y4W"><div class="d-none"></div><div class="prc-ButtonGroup-ButtonGroup-vFUrY" data-component="ButtonGroup"><div class="prc-ButtonGroup-Item-PqvDl"><a data-component="LinkButton" href="https://github.com/episode6/moonlight-steam-sync-decky/raw/refs/heads/main/DEVICE-CHECKLIST.md" data-testid="raw-button" class="prc-Button-ButtonBase-9n-Xk LinkButton-module__linkButton__nFnov BlobViewHeader-module__LinkButton__X9kx2" data-loading="false" data-no-visuals="true" data-size="small" data-variant="default"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="text" class="prc-Button-Label-FWkx3">Raw</span></span></a></div><div class="prc-ButtonGroup-Item-PqvDl"><button data-component="IconButton" type="button" data-testid="copy-raw-button" class="prc-Button-ButtonBase-9n-Xk prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="default" aria-labelledby="_R_qaucpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copy" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="n" data-component="Tooltip" aria-hidden="true" id="_R_qaucpala9lik5_">Copy raw file</span></div><div class="prc-ButtonGroup-Item-PqvDl"><button data-component="IconButton" type="button" data-testid="download-raw-button" class="prc-Button-ButtonBase-9n-Xk BlobViewHeader-module__downloadButton__ef459 prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="default" aria-labelledby="_R_eaucpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-download" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z"></path><path d="M7.25 7.689V2a.75.75 0 0 1 1.5 0v5.689l1.97-1.969a.749.749 0 1 1 1.06 1.06l-3.25 3.25a.749.749 0 0 1-1.06 0L4.22 6.78a.749.749 0 1 1 1.06-1.06l1.97 1.969Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="n" data-component="Tooltip" aria-hidden="true" id="_R_eaucpala9lik5_">Download raw file</span></div></div></div><button data-component="IconButton" type="button" aria-pressed="false" class="prc-Button-ButtonBase-9n-Xk tmp-mr-2 TableOfContents-module__IconButton__jrlNM prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="invisible" aria-labelledby="_R_3ucpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-list-unordered" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M5.75 2.5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5Zm0 5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5Zm0 5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5ZM2 14a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm1-6a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM2 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="n" data-component="Tooltip" aria-hidden="true" id="_R_3ucpala9lik5_">Outline</span><div class="react-blob-header-edit-and-raw-actions-combined"><button data-component="IconButton" type="button" title="More file actions" data-testid="more-file-actions-button" aria-haspopup="true" aria-expanded="false" tabindex="0" class="prc-Button-ButtonBase-9n-Xk js-blob-dropdown-click BlobViewHeader-module__IconButton__XrMQY prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="invisible" aria-labelledby="_R_fkecpala9lik5_" id="_R_kecpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-kebab-horizontal" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM1.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm13 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="nw" data-component="Tooltip" aria-hidden="true" id="_R_fkecpala9lik5_">Edit and raw actions</span></div></div></div></div><div></div></div><div class="BlobViewContent-module__blobContentWrapper__JS0W6"><section aria-labelledby="file-name-id-wide file-name-id-mobile" class="BlobContent-module__blobContentSection__VOgZq BlobContent-module__blobContentSectionMarkdown__mPLOK" style="margin-top:46px"><div class="js-snippet-clipboard-copy-unpositioned BlobContent-module__markdownBlob__T8jpG" data-hpc="true" containertiming="hpc"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 tabindex="-1" class="heading-element" dir="auto">Device checklist</h1><a id="user-content-device-checklist" class="anchor" aria-label="Permalink: Device checklist" href="#device-checklist"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Everything below needs a real Steam Deck (or SteamOS machine with Decky
Loader) in Game Mode; none of it is a merge criterion for any plugin PR
(spec section 4: "on device" acceptance items are transcribed here, not
enforced by CI). Run it top to bottom the first time a build reaches a
Deck. Findings that contradict the spec go to a Fable-class model (spec
section 5), not decided here.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">0. Setup</h2><a id="user-content-0-setup" class="anchor" aria-label="Permalink: 0. Setup" href="#0-setup"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ol dir="auto">
<li>Get a build onto the Deck: <code>curl -fsSL https://raw.githubusercontent.com/episode6/moonlight-steam-sync-decky/main/install.sh | sh</code> (<code>sudo</code> prompts, explained by the script; without a password
set it offers a temporary one, section 5). For a build newer
than the latest release, build off-device with <code>pnpm install &amp;&amp; pnpm run build &amp;&amp; backend/entrypoint.sh &amp;&amp; python3 scripts/package.py</code>, or
take the <code>Moonlight-Sync</code> artifact from a CI run, and install it by
hand — <code>README.md</code>'s "Manual install". Either way Settings → About
should show the bundled and installed CLI both at the plugin's version
(the CLI is built from <code>cli/</code> with it; <code>0.4.0</code> before the move); the plugin
installs or upgrades <code>~/.local/bin/moonlight-steam-sync</code> on first load.</li>
<li>SSH into the Deck while it sits in Game Mode (<code>passwd</code> once in Desktop
Mode, then <code>sudo systemctl enable --now sshd</code>; see <code>probes/PROBES.md</code>
§1.1 for the full one-time setup, which the PR-0 probes below also
need).</li>
<li>Logs: <code>~/homebrew/logs/Moonlight Sync/moonlight-sync.log</code> (every CLI
call with its arguments, the CLI's own stderr verbatim, both CLI
versions at startup) and Decky's own log
(<code>~/homebrew/logs/decky-loader/…</code> or <code>journalctl -u plugin_loader</code>) for
backend tracebacks. Settings → About also shows a log tail and both CLI
versions without leaving the panel.</li>
<li>To flip the controller-layout strategy by hand (no UI yet, spec §3.10):
stop the plugin (<code>sudo systemctl stop plugin_loader</code> or unload it from
Decky's developer menu), edit <code>"layout_strategy"</code> in
<code>~/homebrew/settings/Moonlight Sync/settings.json</code> to <code>"copy"</code> or
<code>"picker"</code> (removing the key restores the default, currently <code>"copy"</code>),
then reload.</li>
<li>The CLI must work <em>from the plugin</em>, not only from SSH: plugin_loader
is a PyInstaller binary whose <code>LD_LIBRARY_PATH=/tmp/_MEI…</code> once broke
the CLI's <code>flatpak</code> call ("moonlight CLI not found") and its <code>import ssl</code> ("5 consecutive network failures"). With Moonlight installed as
the Flathub flatpak, pick a host in the panel and search for a title in
the match fixer; then <code>grep -n "not found\|network failures" "$HOME/homebrew/logs/Moonlight Sync/moonlight-sync.log"</code> must find
nothing newer than the install. To reproduce the loader's environment
from SSH, while plugin_loader is running (its unpack directory goes
when it exits): <code>env -i HOME=$HOME PATH=/usr/bin LD_LIBRARY_PATH=$(ls -dt /tmp/_MEI* | head -1) python3 ~/.local/bin/moonlight-steam-sync doctor</code> — the <code>moonlight:</code> line must name the flatpak (CLI 0.3.1 or
newer; 0.3.0 prints <code>not found</code> there, which the plugin's cleaned
environment hides). <code>ls -dt</code> takes the newest <code>_MEI*</code> directory; if
another PyInstaller program is running too, check that the one picked
holds a <code>libssl.so.3</code>. The loader's own <code>/proc/&lt;pid&gt;/environ</code> is not
the shortcut it looks like: the service runs as root, so reading it
needs <code>sudo</code>, and even the backend's deck-owned process refuses.</li>
</ol>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">1. PR-0: device probes</h2><a id="user-content-1-pr-0-device-probes" class="anchor" aria-label="Permalink: 1. PR-0: device probes" href="#1-pr-0-device-probes"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">The primary path is the scripted runner; the throwaway <code>steam-input-probe</code>
plugin is the fallback. Full procedure, venv setup and the exact commands:
<a href="/episode6/moonlight-steam-sync-decky/blob/main/probes/PROBES.md"><code>probes/PROBES.md</code></a>. The fallback button plugin logs
to <code>~/homebrew/logs/steam-input-probe/steam-input-probe.log</code> (the loader's
<code>DECKY_PLUGIN_LOG_DIR</code>; a loader old enough not to set it falls back to
<code>~/homebrew/logs/steam-input-probe.log</code>). In order:</p>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Setup</strong> (PROBES.md §1.1): SSH in, create the <code>~/.cache/msy-probes</code>
venv, confirm the CEF DevTools port answers
(<code>curl -s http://localhost:8080/json | grep -c SharedJSContext</code>).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>V4</strong>: <code>run_probes.py v4</code> — dump the first 20 of
<code>collectionStore.allAppsCollection.allApps</code> (<code>appid</code>, <code>display_name</code>,
<code>app_type</code>) and the current steamid3. Expect real entries and a
plausible steamid3.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>V1</strong>: <code>run_probes.py v1 --appids &lt;owned appid&gt;,&lt;another&gt;</code> — dump
<code>GetConfigForAppAndController</code> per appid plus the Deck controller
index found by type. Expect a <code>template://</code> or <code>workshop://</code> URL for
a game with a chosen layout, <code>default://…</code> for one left on Steam's
guess, and the controller index (record it — it decides the
<code>DECK_CONTROLLER_TYPE</code> fallback in <code>src/lib/layouts.ts</code> if it is not
what the constant assumes).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>V2</strong> (state-changing, needs <code>--url</code>; make a throwaway shortcut
first): <code>run_probes.py v2 --shortcut &lt;APPID&gt; --url 'workshop://…'</code>
(an id published for a real game you own) — calls
<code>SetSelectedConfigForApp</code> then reads back. Expect the read-back URL
to equal what was set. <strong>This decides the strategy</strong>: if it does not
match, flip <code>DEFAULT_LAYOUT_STRATEGY</code> to <code>"picker"</code> in
<code>src/lib/layouts.ts</code> and rewrite the README's "Controller layouts"
section — escalate to a Fable-class model rather than doing this
inline.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>V5</strong>: <code>run_probes.py v5 --shortcut &lt;APPID&gt; [--run]</code> — dump
<code>appStore.GetAppOverviewByAppID(appid).gameid</code> and, with <code>--run</code>,
launch it. Expect a non-null overview once Steam has loaded the
shortcut list, and (with <code>--run</code>) the shortcut to actually launch.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>V3</strong> (state-changing, needs <code>--yes</code> or an interactive confirm):
<code>run_probes.py v3</code> — logs <code>pgrep -x steam</code> every 100 ms for 90 s
around a <code>StartShutdown(false)</code> call, then reconnects to confirm
Steam came back. Expect the log to show a clear gap between the
client going away and coming back, confirming <code>StartShutdown(false)</code>
is the restart from Game Mode (spec §2.3). <strong>The measurement is
<code>gaps</code> (from the pgrep samples), not <code>back_after_ms</code></strong>: that field
is measured from the start of the watch and the reconnect only
begins after the baseline plus the whole <code>--duration</code>, so it is
bounded below by roughly 91 s whatever Steam does. Read it only as
"Steam came back: yes/no" (PROBES.md §1.3).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> Run <code>run_probes.py all --appids … --shortcut &lt;APPID&gt; [--url … --yes]</code>
once everything above passes individually, and paste
<code>probe-results.json</code>'s <code>--markdown</code> table into
<code>~/specs/moonlight-steam-sync/decky-plugin.md</code> §2 (a Fable-class
model does the actual spec edit).</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2. PR-5: backend, Quick Access panel</h2><a id="user-content-2-pr-5-backend-quick-access-panel" class="anchor" aria-label="Permalink: 2. PR-5: backend, Quick Access panel" href="#2-pr-5-backend-quick-access-panel"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>First load installs the bundled CLI.</strong> With no <code>~/.local/bin/moonlight-steam-sync</code>
present, load the plugin. Expect it to appear in <code>~/.local/bin</code> after
load, and Settings → About to show both the bundled and installed
versions matching the plugin's own version (About's <em>Plugin
version</em>): the CLI is built from <code>cli/</code> at the same version.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Counters, Sync now, the restart modal.</strong> Press <em>Sync now</em>. Expect
progress lines, then either the restart modal (writes pending) or
"nothing to do"; the four counters and <em>Last sync</em> update after.
The progress bar and its "ARTWORK: TITLE N OF M" label stay inside
the panel, the bar as wide as the <em>Stop</em> button under it (before
2026-09-23 the bar's fixed-width column overflowed the panel).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Stop right after Sync.</strong> Press <em>Sync now</em>, then <em>Stop</em> within about
a second (before the first progress line). Expect the panel to leave
the progress view reporting a stopped run (not a run that keeps
going), <em>Last sync</em> not claiming any added/replaced shortcuts, and
<code>~/homebrew/logs/Moonlight Sync/moonlight-sync.log</code> (or Decky's own
log) to show either "stop: SIGINT to the sync run" or "stop: the
sync run has not spawned yet; it will be interrupted", exit 130.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Stop during the await-exit wait.</strong> Start a sync that has writes to
make, wait for the "close Steam" state, press <em>Stop</em>, reopen the
panel. Expect the persistent "Restart Steam to apply" row to still be
offered (nothing was written) and <em>Last sync</em> not to claim any
added/replaced titles.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Unload during a run.</strong> Start a sync, then reload the plugin
(Decky's developer menu → reload, or reinstall). Expect
<code>pgrep -af moonlight-steam-sync</code> to return nothing a few seconds
later, and a fresh <em>Sync now</em> afterwards to start exactly one run.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Open Moonlight</strong> launches the client shortcut without picking a
game.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Desktop</strong> / <strong>Steam Big Picture</strong> show on the panel when the active
host publishes them, each starts its stream with the controller
layout chosen on that shortcut, and neither shows once its entry is
ignored and synced away.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Backend not answering at load.</strong> Force the failure (open the panel
immediately at Game Mode start, or temporarily rename
<code>~/homebrew/plugins/Moonlight Sync/py_modules</code> to break a callable).
Expect a Retry row carrying the backend's message instead of an
endless "Loading your library…" spinner; restore, press Retry, expect
the panel to fill in without a plugin reload.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The host is never asked on its own</strong> (Decision 66). Open the panel,
close it, reopen it, toggle the plugin off and on, open Settings →
Titles: <code>moonlight-sync.log</code> shows no <code>--json list --host</code> without
<code>--cached</code> for any of it, and (with several hosts) the host row
reads a grey circle and "N apps" with <strong>Check</strong> and (when a MAC is
known) <strong>Wake</strong> under it. A shut-down PC stays down through all of
it.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Check asks once.</strong> With several hosts, press Check: "Checking…",
then a green circle and "N apps"; the log shows one live <code>--json list --host</code>. A second press within 10 s is answered from the memo
(no new spawn). While a sync runs elsewhere (or the library is
still loading) the whole Host row is dimmed, label and count
included, and the dropdown does not open.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Check with one host.</strong> With a single host, press Check: expect
no "Checking…" text anywhere (there is no row to put it in), the
Check button unavailable meanwhile, then <strong>Check</strong> / <strong>Wake</strong> gone
when the host answered, or the red error line alone above them
when it did not; one live <code>--json list --host</code> in the log.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Unreachable host row.</strong> With several hosts and the active one
unreachable, press Check: expect a red circle and the cached "N
apps" in the Host row, and under the dropdown, above the divider,
the CLI's error message and "last seen " in red.
For a host never listed the row says "never synced" and the error
line is the message alone; with one host the line ends "· never
synced" instead. A <em>Sync now</em> that fails with
exit 3 paints the row the same way, and one that lists paints it
green and takes the error line away, with no <code>check_host</code> spawned
for either.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>One host: no Host row.</strong> With a single host, expect no <em>Host</em>
row and no dropdown: <strong>Check</strong> / <strong>Wake</strong> directly under <em>Enable
Sync</em> until the host is known reachable, then <em>Sync now</em> first. An
unreachable host shows the error line alone above Check / Wake.
Check / Wake sit as far below the divider above them as <em>Sync
now</em> does when they are gone, with a divider of their own under
them. Add a second host on Settings → Host: the row and its dropdown
appear; Forget it: they go again.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The switcher takes the row's spare width.</strong> With several hosts,
expect the dropdown to start right of "N apps", never over it,
and show a host name of a dozen characters whole, not cut at half
the row; a longer name ends in "…" rather than pushing the count
under the dropdown. The dropdown opens, lists every host and
switches as before (the row is a <code>Field</code> with
<code>childrenContainerWidth="max"</code> around a <code>Dropdown</code>, not a
<code>DropdownItem</code>; the count running under the dropdown was seen on
a Deck 2026-09-29 and the fix measured there over the debugger).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>In-game guard.</strong> While a game is running, expect no restart
countdown and the text "A game is running. Restart Steam when you're
done."</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>In-game guard on the persistent row</strong> (Decision 29). With a
pending "Restart Steam to apply" row (either kind: a pending write,
or artwork), launch a game and open the panel. Expect the row to be
disabled with the subtitle "A game is running; exit it first", and
pressing it to do nothing. Exit the game: expect the row to become
pressable again. Also press the row just before launching a game so
the re-run's <code>awaiting-steam-exit</code> lands while the game is up:
expect the restart modal (with its in-game text) rather than Steam
shutting down under the game.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Later → the persistent row → immediate restart.</strong> Press <em>Later</em> on
the restart modal, expect the "Restart Steam to apply" row; press it,
expect the sync to re-run with <code>immediate_restart: true</code> and shut
down at once on the next <code>awaiting-steam-exit</code>, no modal.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Art-only restart</strong> (a run with only artwork filled, no shortcut
writes) offers the same modal/row with "New artwork needs a Steam
restart to show".</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Restart countdown slider.</strong> Settings → Advanced: expect the
countdown to run 0-30 s and no further (Decision 30 — the CLI stops
waiting for Steam after 60 s, so a longer countdown would race it).
Set 0 and expect the modal to show buttons with no countdown; set 30
and expect it to count down from 30. A <code>settings.json</code> left over
from an older build with <code>"restart_countdown_s": 60</code> must read back
as 30, not break the settings page.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Re-fetch all art with no <code>--commit</code> support.</strong> Install (or
hand-build) a CLI whose <code>art --help</code> lacks <code>--commit</code>. Expect
<em>Re-fetch all art</em> to be disabled, and if pressed anyway from a stale
state, the message to read "re-fetching art from Game Mode needs a
CLI whose art command accepts --commit" — never "CLI too old (0.4.0,
needs 0.4.0)".</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Nothing about the other hosts.</strong> Add three hosts (one active,
two not), sync the inactive ones once each so they have cached
listings. Expect the Host row to say nothing of them: the circle
and the active host's count only, no "parked" line.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The SteamGridDB key round-trip and Test.</strong> Set a key on the
Artwork page, expect only its last four characters ever shown
anywhere (panel, events, logs); press <em>Test</em>, expect a pass/fail
result with no more of the key revealed.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">3. PR-6: Titles page, Change match</h2><a id="user-content-3-pr-6-titles-page-change-match" class="anchor" aria-label="Permalink: 3. PR-6: Titles page, Change match" href="#3-pr-6-titles-page-change-match"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Titles list, idle.</strong> With nothing running, press the panel
header's list button. Expect "N published by MY-GAMING-PC · sorted by name"
and rows with capsules, badges and chips.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Titles page mid-sync.</strong> Start <em>Sync now</em>, then open Settings →
Titles while it runs. Expect "N published by MY-GAMING-PC[ · listed
] · refreshes when the sync finishes", the note "A
sync is running; this list refreshes and matches can be changed when
it finishes", and <em>Change match</em> disabled on every row.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong><code>list --cached</code> always, never live <code>list</code></strong> (Decision 66). Whether
or not a sync is running, opening Titles spawns only <code>--json list --cached --host MY-GAMING-PC</code>, never a live <code>--json list --host MY-GAMING-PC</code>; the log proves it.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Refresh after the sync.</strong> Let it finish (restart when prompted),
reopen Titles: it re-lists from the cache the sync just wrote, the
headline returns to "N published by MY-GAMING-PC · listed just now ·
sorted by name", <em>Change match</em> is enabled again.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Never-synced host.</strong> On a host that has never been synced, open
Titles: "MY-GAMING-PC was never synced; Sync now lists its titles"
and a <em>Sync now</em> button, no rows, filters or Retry. Add OFFICE-PC
and press <em>Check</em> on it (both cache its list), make it active and
open Titles: the same line, still no rows. Press the page's <em>Sync
now</em>: the line reads "… this list fills in when the sync finishes",
the button goes, and the titles fill in after the run ends.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Change match races a sync.</strong> Open <em>Change match</em> on a title; before
pressing <em>Use this</em>, start a sync from the panel; then press <em>Use
this</em>. Expect "A sync is already running", no pin written
(<code>matches.json</code> unchanged), the row keeps its old match.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Pin, then apply on next sync.</strong> After that sync finishes, press
<em>Use this</em> again on the same title: expect a pin, the toast "Pinned;
sync to apply", the pinned and "art refreshes on next sync" chips,
and the next <em>Sync now</em> to turn an owned-game pin into a Stream
button (Hades II re-matched from a fuzzy Hades hit is the spec's own
example: expect a Stream button on Hades II and the visible tile
gone).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Ignore.</strong> <em>Ignore</em> on a title removes its tile on the next sync;
<em>Unignore</em> restores it.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Owned-apps mismatch recovery.</strong> Sign in as a second Steam user on
the Deck, open Titles: expect exactly one <code>write_owned_apps</code> in the
log (recovered once), then a successful list and status — not two
write attempts.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Paging.</strong> With 300+ rows, page through with the D-pad; expect pages
of 50 and correct navigation at both ends.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Recently added.</strong> After updating to a build with the chip, open
Titles: the order is by name and <code>added.json</code> in the settings
directory lists every title with <code>null</code> (no <code>Moonlight</code>, <code>Desktop</code>
or <code>Steam Big Picture</code> in it). Publish a new app on the
host, <em>Sync now</em>, restart when prompted, reopen Titles and press
<strong>Recently added</strong>: the chip lights, the headline ends "sorted by
recently added", the new title is the first row and reads "added
today h:MM AM/PM" (or "HH:MM" with Steam's 24-hour clock on), every
other row follows by name with no "added" text,
and <code>added.json</code> has a time for the new title alone. Pick a filter:
the order holds. Leave the page and reopen it: still newest first.
Press the chip again: by name, no "added" text. With the chip on,
start a sync and reopen Titles: the headline ends "refreshes when
the sync finishes · sorted by recently added".</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The clock follows Steam's.</strong> With Steam's <strong>Settings → System →
24-hour clock</strong> off (measured on the Deck 2026-09-29: the toggle
writes <code>settingsStore.friendSettings.b24HourClock</code>, which the header
clock reads too), the Titles page's "added today 2:02 PM" and the
panel's <em>Last sync</em> use the 12-hour clock; turn it on and reopen
them: "added today 14:02", "Today 14:02 · …", with no plugin reload.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>No layout text on the rows.</strong> No Titles row says <code>layout: …</code>;
the note beside a game's Stream button still does while a default is
set.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Show more keeps your place.</strong> Press <em>Show 50 more</em> with the D-pad.
Expect focus on the last row that was already shown (not on the
button, now 50 rows further down), and one press of down to land on
the first new row.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">4. PR-7: Stream button and controller layouts</h2><a id="user-content-4-pr-7-stream-button-and-controller-layouts" class="anchor" aria-label="Permalink: 4. PR-7: Stream button and controller layouts" href="#4-pr-7-stream-button-and-controller-layouts"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto"><em>The layout items in this section were written for the copy from the real
Steam game, which spec 3.16 (§8 below) replaced with a default layout you
adopt: nothing copies from the real game any more, there is no Advanced
toggle, and the Titles rows have no layout button any more (Decision 75:
a title's layout is picked from Steam's controller settings while it
streams). Run the button, guard, route-patch, picker-strategy and
controller-index items here as written; the copy-specific ones are marked
and superseded by §8.</em></p>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Button placement.</strong> With a host active and a sync done, open the
library page of an owned game the host publishes: expect a Stream
row. Open a game the host does not publish (or one parked / matched
to nothing): expect no Stream row.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Focus order beside another plugin.</strong> With ProtonDB Badges (or any
plugin that adds to the game page) installed, open such a page and
press up from <em>Play</em>: expect the Stream button first and the other
plugin's badge after it, as they are drawn, and the same order in
reverse going down.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <del><strong>First press copies a community layout.</strong></del> Superseded by §8
(nothing copies from the real game any more). With <strong>no default
layout set</strong>, press <em>Stream</em> on a game whose real entry has a
<code>workshop://</code> layout: expect the launch with no layout change and no
<code>layouts.json</code> write.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>A hand-picked layout on the hidden entry is never overwritten.</strong>
Pick a layout on the hidden shortcut (Steam's controller settings
while it streams), set a different default layout
(§8), press <em>Stream</em>, then <em>Sync now</em> and restart when prompted.
Expect the hidden shortcut to keep the hand-picked layout after all
three, <code>layouts.json</code> to read <code>"kept"</code> with that URL and <code>"applied": null</code>, the game page's Stream note to read "own layout".</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <del><strong>Steam default.</strong></del> Superseded by §8: with a default set, a
hidden shortcut on Steam's default gets the default layout on its
first press (<code>"default"</code>); without one, nothing is written.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>One row, however often the page is opened.</strong> Open the same owned
game's library page several times, backing out to the library and in
again between each (and switch to another game's page and back).
Expect exactly one Stream row every time — never two stacked — and
no slowdown building up as the page is re-entered. The route patch
runs <code>afterPatch</code> on each render, so a duplicated or accumulating
row shows up here and nowhere else.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>No running-app guard.</strong> Launch any game, then open another
published game's library page and press <em>Stream</em>: expect no toast
and the Stream shortcut launched as usual (Steam switches to it;
with a stream already going, Moonlight's own UI takes over), the
default layout applied and <code>layouts.json</code> updated as on any press.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <del><strong>Advanced toggle.</strong></del> Removed: the toggle is gone (spec Decision
47). Settings → Advanced shows <em>Default controller layout</em> instead
(§8); "no default set" is the off switch.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Post-restart layout walk.</strong> With a default layout set (§8), make a
sync write shortcuts, restart when prompted, wait up to 90 s after
Steam is back. Expect <code>layouts.json</code> to gain an entry per non-parked
entry (default / kept / unavailable), the log to show the walk
starting, each target, and "layout walk done"; the panel to show no
restart row afterward.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>A title's layout while it streams.</strong> Press <em>Stream</em> on a game,
then the Steam button: <em>Controller settings</em> there must be the hidden
shortcut's (its title matching the game), and a layout picked there
must still be selected at the next stream. The Titles rows have no
<em>Layout</em> button (Decision 75); Advanced's <em>Layout</em> is §15's.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Picker strategy.</strong> Set <code>"layout_strategy": "picker"</code> in
<code>settings.json</code> (§0.4 above) and reload. Expect a Stream press to
launch with no layout change and no <code>layouts.json</code> write, the
Advanced <em>Default controller layout</em> field to read "Off for this
device…" with <em>Layout</em> as its only button (no <em>Make default</em>, no
<em>Clear</em>) and the plugin's only layout action, and no walk after a
sync's restart; remove the key and reload to
return to <code>copy</code>.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Controller index by type.</strong> In the CEF console (or the PR-0 kit)
with a Bluetooth pad also paired, run
<code>ControllerStore.GetControllers().map(c =&gt; [c.nControllerIndex, c.eControllerType, ControllerStore.GetControllerTypeString?.(c.eControllerType)])</code>
(the global is <code>ControllerStore</code>, capital C, on the client probed on
2026-09-20; the list is empty while no controller is connected).
Expect the built-in controller not at index 0 when the pad connected
first, its type string <code>"controller_steamcontroller_neptune"</code>, and
<code>eControllerType</code> <code>4</code>; if not 4, correct <code>DECK_CONTROLLER_TYPE</code> in
<code>src/lib/layouts.ts</code>. <strong>Still open:</strong> the first probe device was a
SteamOS box with a separate Steam Controller (index 0, type 10,
<code>"controller_steamcontroller_triton"</code>), so this has not been seen on
a Deck.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">5. PR-8: this checklist and <code>install.sh</code></h2><a id="user-content-5-pr-8-this-checklist-and-installsh" class="anchor" aria-label="Permalink: 5. PR-8: this checklist and install.sh" href="#5-pr-8-this-checklist-and-installsh"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong><code>install.sh</code> on a Deck</strong>: run the
one-liner from a clean Deck (no plugin installed yet), with a
password set for the <code>deck</code> user (the next item is the Deck without
one). Expect <code>sudo</code> to ask for it (unzip into <code>~/homebrew/plugins/</code>,
restart <code>plugin_loader</code>),
then Moonlight Sync appears in the Quick Access menu's Decky tab,
and a final line naming the version that actually landed (read from
the installed <code>package.json</code>, not the tag asked for). Re-run it;
expect it to succeed again (reinstall/overwrite) without asking
anything unexpected.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong><code>install.sh</code> on a Deck with no password.</strong> On a Deck whose
<code>deck</code> user has none (<code>passwd -S deck</code> says <code>NP</code>; <code>sudo passwd -d deck</code> gets a Deck that has one back there), run the one-liner.
Expect the question <em>Set a temporary password for the install?</em>
naming <code>Decky!</code>. Answer <code>n</code>: expect exit 1, "nothing was installed",
<code>passwd -S deck</code> still <code>NP</code> and no plugin directory. Run it again
and answer <code>y</code>: expect no <code>sudo</code> prompt at all, no <em>New password:</em>
prompt and no pause after the answer, the install and the
restart, <em>Removed the temporary password</em> before the final line,
and <code>passwd -S deck</code> back at <code>NP</code> afterwards. Run it once more,
answer <code>y</code> and press Ctrl+C as soon as it says <em>Temporary password
set</em>: expect <code>passwd -S deck</code> at <code>NP</code> again. <strong>Unmeasured until this is
walked:</strong> that SteamOS's <code>passwd</code> takes the new password from a
pipe (run under <code>setsid</code>, so with no terminal to ask on) and that
<code>passwd -S</code> answers for one's own account without root (both are
what Decky's installer relies on). Should the pipe not be read, the
script says <em>sudo did not accept the temporary one</em> and installs
nothing.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong><code>install.sh</code> on a device without Decky Loader.</strong> On a device
that has none (no <code>~/homebrew/services/PluginLoader</code>), run the
one-liner. Expect <em>Decky Loader is not installed</em> and the question
<em>Install Decky Loader now?</em> before anything is downloaded. Answer
<code>n</code>: expect exit 1, "nothing was installed", no <code>sudo</code> prompt and
no <code>~/homebrew</code>. Run it again and answer <code>y</code>: expect Decky's own
installer's output (<em>Installing Steam Deck Plugin Loader
release...</em>), <em>Decky Loader is installed.</em>, then the plugin's
install as in the items above (on a Deck with no password, the
temporary-password question comes between the two downloads and
Decky's installer), and at the end the two lines saying the Decky
tab appears once Steam has restarted. Return to Gaming Mode:
expect the Decky tab with Moonlight Sync in it. Run the one-liner
once more: expect no question about Decky Loader. <strong>Unmeasured
until this is walked:</strong> that Decky's <code>install_release.sh</code> runs to
the end as <code>sudo bash &lt;file&gt;</code> with stdin from <code>/dev/null</code> (it is
documented as <code>curl … | sh</code>, where it re-executes itself under
sudo), and that a return to Gaming Mode is enough for the tab to
appear.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The README's manual install.</strong> With the plugin not installed,
follow README "Manual install": Decky's settings → General →
<em>Developer mode</em> on, then Developer → <em>Install Plugin from ZIP
File</em> → <em>Browse</em>, choose a release's <code>Moonlight-Sync.zip</code>, confirm
Decky's dialog. Expect Moonlight Sync in the Decky tab with no
restart, and Settings → About showing that release. Do it again
over the installed copy: expect Decky to ask to reinstall or
overwrite, and the plugin to reload. <strong>Unmeasured until this is
walked:</strong> the labels as the README spells them.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>A missing release is explained.</strong> Run with
<code>MOONLIGHT_SYNC_VERSION=v9.9.9</code>; expect
<code>install.sh: could not download …/Moonlight-Sync.zip (is v9.9.9 released?)</code> on stderr, exit 1 and nothing written to
<code>~/homebrew/plugins/</code>.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong><code>MOONLIGHT_SYNC_VERSION</code> pinning.</strong> Run with
<code>MOONLIGHT_SYNC_VERSION=v0.1.0 sh install.sh</code> (or the exact tag);
expect the version-specific download path, not <code>latest/download</code>.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Uninstall.</strong> Follow the README's "Uninstall" section
(<code>sudo rm -rf ~/homebrew/plugins/"Moonlight Sync"</code> + restart
<code>plugin_loader</code>); expect the Quick Access entry to disappear and
<code>~/.local/bin/moonlight-steam-sync</code> (the CLI) to be untouched.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">6. Host apps: the panel buttons replace the tiles (spec 3.14)</h2><a id="user-content-6-host-apps-the-panel-buttons-replace-the-tiles-spec-314" class="anchor" aria-label="Permalink: 6. Host apps: the panel buttons replace the tiles (spec 3.14)" href="#6-host-apps-the-panel-buttons-replace-the-tiles-spec-314"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Needs a host that publishes <code>Desktop</code> and <code>Steam Big Picture</code> under those
names, and ideally an install from before this change: two visible tiles,
one of them with a controller layout chosen on it. Note each tile's appid
first (<code>moonlight-steam-sync --json status</code>).</p>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The first sync hides the two tiles in place.</strong> Upgrade the plugin,
press <em>Sync now</em>. Expect the plan to count them as hidden, never as
replaced (the log's CLI line says <code>N to hide</code>; no <code>replace</code> event for
either name), one Steam restart, and afterwards no <code>Desktop</code> /
<code>Steam Big Picture</code> tile anywhere in the library, non-Steam
collection included.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Same appid, nothing lost.</strong> <code>--json status --hide-host-apps</code> shows
both entries with the appids noted above, <code>hidden: true</code>,
<code>parked: false</code>, <code>host_app: true</code>, and their artwork slots still
filled; no image was downloaded for them by that sync.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Both panel buttons launch.</strong> <em>Desktop</em> and <em>Steam Big Picture</em> each
start their stream from the Quick Access panel; Recent Games and the
in-game overlay show the entry with its artwork.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <del><strong>Each layout button opens the configurator.</strong></del> Removed: the
panel has no layout button per host app any more (spec Decision
67), nor on the Titles page (Decision 75); a host app's layout is
picked from Steam's controller settings while it streams, and the
one <em>Layout</em> button, Advanced's since Decision 77, is the Moonlight
entry's (§15).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>A layout chosen before the upgrade is still selected</strong> (same
appid): it applies in the stream, and Steam's controller settings
during the stream show it.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>While a game runs</strong> the two launch buttons still work: a press
runs the host app's shortcut as usual.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>On a client without <code>SteamClient.Apps.ShowControllerConfigurator</code></strong>
(if one turns up) Advanced's <em>Default controller layout</em> row has
<em>Make default</em> and <em>Clear</em> only.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Counters.</strong> Do this one <em>before</em> the first sync: <code>status</code> carries
the flag from the upgrade on, so the two still-visible tiles already
read <code>host_app: true</code>. Right after the upgrade <em>Shortcuts</em> is two
lower than before (and <em>Unmatched</em> lower by however many of the two
had no match); <em>Stream buttons</em> is unchanged. The sync that hides the
two then moves none of the three.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Titles page.</strong> Both rows read <strong>host app</strong>, show under <em>All</em> only,
and have <em>Change match</em> and <em>Ignore</em> (no <em>Layout</em>, as on every row).
In <em>Change match</em> every result reads <strong>art only</strong>,
including a game this account owns; pin one, sync, and expect the
entry to stay a hidden host app with new artwork and no Stream button
on that game's library page.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>No layout copied onto them.</strong> <em>(Written for the layout copy that
spec 3.16 replaced; §8 covers them now.)</em> Without a default layout
set, after a sync's restart the layout walk never touches the two
entries (<code>layouts.json</code> has no <code>copied</code> / <code>kept</code> / <code>unavailable</code>
record for their appids; a <code>picker</code> record from an older version's
<em>Choose layout…</em> may stay). With a default set they get it, like
every other entry (§8).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Ignore removes the button.</strong> Ignore <code>Desktop</code> on the Titles page and
sync: the entry is removed and the panel's <em>Desktop</em> row is gone.
Unignore and sync: it comes back, hidden from the start.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Switching hosts.</strong> With a second host that does not publish them,
the two entries are parked (counted under parked, buttons gone); back
on the first host they are unparked <em>hidden</em>, never as tiles.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Getting the tiles back</strong> (README, "The Quick Access panel"): the
full command there (<code>--owned-apps</code>, <code>--ignore-file</code>,
<code>--client-shortcut</code>, <code>--park-unpublished</code>, no <code>--hide-host-apps</code>), run
from a terminal, shows the two tiles again with art and layout intact
and changes nothing else: every Stream button stays hidden, no ignored
title comes back, the Moonlight client entry stays. The next <em>Sync
now</em> hides the two again.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">7. Hidden shortcuts and the Streaming collection (spec 3.15)</h2><a id="user-content-7-hidden-shortcuts-and-the-streaming-collection-spec-315" class="anchor" aria-label="Permalink: 7. Hidden shortcuts and the Streaming collection (spec 3.15)" href="#7-hidden-shortcuts-and-the-streaming-collection-spec-315"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">None of <code>collectionStore.SetAppsAsHidden</code>, <code>BIsHidden</code>, <code>userCollections</code>,
<code>NewUnsavedCollection</code>, <code>AsDragDropCollection</code> or a collection's <code>Save</code> /
<code>Delete</code> was probed before this was built; every item here is a first
measurement. <strong>Spec 3.17 changed the collection items below:</strong> the
collection now exists only with <em>Hide Stream shortcuts</em> off, beside the tab, and holds
shortcuts only; §10 has the items that replace the collection ones here,
and the hiding items still stand.</p>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> In the CEF console: <code>typeof collectionStore.SetAppsAsHidden</code>,
<code>typeof collectionStore.BIsHidden</code>, <code>typeof collectionStore.NewUnsavedCollection</code> are all <code>"function"</code>, and
Settings → Advanced shows both toggles enabled (a disabled toggle
says the client has no such call).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> After the plugin loads with an existing install: the <em>Non-Steam</em> tab
holds only the visible shortcuts (the panel's <strong>Shortcuts</strong> counter),
no Stream-button shortcut, no <code>Moonlight</code>, no <code>Desktop</code> / <code>Steam Big Picture</code>, no parked title. They are all under Steam's <em>Hidden</em>.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> A <strong>Streaming</strong> collection exists and its size is the panel's
<strong>Stream buttons</strong> + <strong>Shortcuts</strong>. An owned game in it opens the real
game's page (with the Stream button); an unowned one is the shortcut.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> A Stream press still launches the (now hidden) shortcut, and the
panel's <em>Open Moonlight</em>, <em>Desktop</em> and <em>Steam Big Picture</em> still work.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <em>Hide Stream shortcuts</em> off: the Stream-button shortcuts appear under
<em>Non-Steam</em> within a moment, the other hidden entries do not, and the
collection is unchanged. Stream a game, quit: its shortcut is at the
front of Home. On again: they are gone again.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <em>Streaming collection</em> off: the collection is deleted. On: it is
back, complete.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> Sync after the host gains a title: once Steam has restarted, the new
Stream shortcut is hidden and the game is in the collection without
opening the panel (allow 90 s). Note whether the tile is visible for
a moment first.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> Switch hosts and sync: the first host's visible tiles are hidden
(parked) and leave the collection; switching back shows them again.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <em>Remove everything</em>: after the restart the collection is gone and
Steam's <em>Hidden</em> holds none of the plugin's entries.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> After a host switch (or an <em>Ignore</em> + sync) that removes a visible
shortcut: the collection's <code>allApps</code> no longer lists its appid (the
plugin cannot remove a member whose overview is gone, so the client
has to drop it itself).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> The first sync on a fresh install creates the collection with every
member in it (the port saves a new collection empty, then adds).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> Open the panel and the Titles page a few times: nothing in the
library flickers and <code>collectionStore</code> is not written again (the
reconcile only applies differences).</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">8. The default controller layout (spec 3.16)</h2><a id="user-content-8-the-default-controller-layout-spec-316" class="anchor" aria-label="Permalink: 8. The default controller layout (spec 3.16)" href="#8-the-default-controller-layout-spec-316"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">The plugin never reads the real game's layout any more: one layout you
adopt from the Moonlight entry with Advanced's <em>Make default</em> (the one
place since Decision 74: the Titles rows and the library gear menu no
longer offer it, and since Decision 77 the panel does not either) is put on every entry the plugin manages, and a
selection the plugin did not make is never overwritten (<code>applied</code> in
<code>layouts.json</code> is what the plugin last set per shortcut). Two things here
are unmeasured on any device: whether a <code>template://</code> layout sticks on
other titles, and the name and arguments of the call that clears a
selection (<code>SteamClient.Input.ClearSelectedConfigForApp(appid, controllerIndex)</code>, spec 3.16.3 <strong>[verify]</strong>). Do this section with a
controller connected; <em>Make default</em> answers "Connect a controller first"
otherwise.</p>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Adopt a community layout.</strong> Give the Moonlight entry a
<code>workshop://</code> community layout (Advanced's <em>Layout</em>), then press
<em>Make default</em> beside it. Expect
a confirm titled <code>Use “&lt;layout title&gt;” as the default layout?</code>; after
OK, a toast <code>Default layout applied to N titles</code> (N = every non-parked
entry that had no layout of its own, host apps and the Moonlight entry
included) plus <code> · M kept their own</code> for the ones that had; Settings →
Advanced → <em>Default controller layout</em> reading <code>&lt;title&gt; · Workshop layout · set today …</code>; <code>layouts.json</code> with <code>"default"</code> and <code>"applied"</code>
set to the URL for each. A fresh title (a game the host just started
publishing, after its sync and restart) streams on it without any
press first.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>No other place adopts.</strong> The Titles rows have no <em>Layout</em> button
(Decision 75), a streamed game's gear menu has no <em>Use as
Moonlight Sync default layout</em> (Decision 74), and the Quick Access
panel has no layout row (Decision 77).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Adopt a <code>template://</code> layout [verify].</strong> Pick one of Steam's own
templates on the Moonlight entry, adopt it. Expect the same toast; check
<code>layouts.json</code>: every other entry must read <code>"default"</code> with the
<code>template://</code> URL. If they read <code>"unavailable"</code> instead (the URL did
not stick when read back), report it: <code>template://</code> then has to come
out of <code>DEFAULT_LAYOUT_SCHEMES</code> (spec 3.16.1, Decision 49), a
Fable-class decision.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>An edited-in-place layout is refused.</strong> Edit the Moonlight
entry's layout in Steam's layout screen without exporting it (it
reads <code>autosave:///…</code>), then <em>Make default</em>. Expect only the toast
<code>This layout was edited in place and only exists for “Moonlight”. In Steam's layout screen choose Export, select the exported copy, then try again.</code>, no confirm, no change.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>An exported personal layout works.</strong> Export that layout, select the
exported copy on the Moonlight entry (it now reads <code>workshop://…</code>),
adopt it: expect the normal confirm and toast.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>No layout is refused.</strong> With the Moonlight entry never given a
layout (reads <code>default://…</code>, or a <code>template://</code> Steam only offers),
expect the toast <code>“Moonlight” has no layout chosen yet. Use Layout first.</code> (§15 has the same item.)</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Hand-picked survives, plugin-applied moves.</strong> With a default set,
pick a different layout by hand on one title (Steam's controller
settings while it streams), then give
the Moonlight entry another layout and adopt it as a <em>second</em> default.
Expect the hand-picked title unchanged (<code>"kept"</code>, <code>"applied": null</code>,
Stream note "own layout"), every plugin-applied title on the new default, the
Moonlight entry (the source) unchanged, and the toast's <code>M kept their own</code> counting the hand-picked one and the source.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>A pre-upgrade copied title moves.</strong> On an install upgraded from a
version that copied layouts from the real game (<code>layouts.json</code> has
<code>"copied"</code> records without an <code>"applied"</code> key), set a default: expect
those titles to move to it (Decision 46) as long as their selection is
still the copied URL.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Clear [verify].</strong> Settings → Advanced → <em>Clear</em>: expect the confirm
<code>Clear the default layout?</code>, then the toast <code>Default layout cleared · removed from N titles</code>. Every plugin-applied title must be back on
Steam's default (the layout screen shows nothing selected; the row
reads "Steam default", <code>"applied": null</code>), and hand-picked ones must
keep their layout. In the CEF console first check <code>typeof SteamClient.Input.ClearSelectedConfigForApp</code>: if it is not
<code>"function"</code>, the toast reads <code>Default layout cleared. This Steam client cannot unset a layout, so titles keep the one they have.</code> and
nothing changes on any title; if it is a function but titles are not
back on Steam's default afterwards (<code>"unavailable"</code> with the old URL
in <code>layouts.json</code>), the call's name or arguments are wrong: report
it (spec 3.16.3), do not guess at another.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>A sync's new titles get it after the restart.</strong> With a default
set, add a title on the host, <em>Sync now</em>, restart: within 90 s the new
shortcut is on the default (<code>layouts.json</code>, and the layout screen).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The host apps, the client entry and a visible shortcut get it.</strong>
After any of the above, check <code>layouts.json</code> for <code>Desktop</code>, <code>Steam Big Picture</code>, <code>Moonlight</code> and a visible (non-Stream) shortcut:
<code>"default"</code> with <code>"real_appid": null</code> for the first three.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Not held back.</strong> Start a game, then adopt a default from the panel
or clear it from Advanced: it goes through (the toast appears) while the game
runs; a Stream press is still refused meanwhile.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">9. Fixtures vs. real CLI</h2><a id="user-content-9-fixtures-vs-real-cli" class="anchor" aria-label="Permalink: 9. Fixtures vs. real CLI" href="#9-fixtures-vs-real-cli"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> Run, on the device, against the real installed CLI:
<code>moonlight-steam-sync --json list --hide-host-apps</code>, <code>--json status --hide-host-apps</code> (the plugin always passes that flag, and the
fixtures carry its keys: <code>kind: "host-app"</code>, <code>entry.host_app</code>), <code>--json host show</code>, <code>--json search "Portal"</code>. Diff each event's key set against
the corresponding fixture under <code>tests/fixtures/common/</code> (§3.6.3 of
the spec: <code>tests/test_fixtures.py</code> checks the fixtures against the
schema, but never against a real CLI run). A mismatch means the CLI
and the plugin have drifted from the spec's §3.4.6 schema on one side
or the other — escalate to a Fable-class model rather than editing
either side ad hoc.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">10. The Streaming tab (spec 3.17)</h2><a id="user-content-10-the-streaming-tab-spec-317" class="anchor" aria-label="Permalink: 10. The Streaming tab (spec 3.17)" href="#10-the-streaming-tab-spec-317"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">The <code>/library</code> route's render tree was measured on 2026-09-22 through the
CEF debugger (route element → the library home, which reads the route's
tab id → the library page, <code>tab</code> prop, which builds its tab array in a
<code>useMemo</code> and validates the requested id against it in its own render →
the tabbed page <code>{tabs, activeTab, onShowTab, …}</code> behind an observer
wrapper → the tab row, which renders and cycles over <code>tabs</code>; a built-in
tab is <code>{id, title, renderTabAddon, content, footer}</code>). The patch still
digs for the tab bar's <code>tabs</code> array (four component levels at most) so a
client that adds a level keeps working, and clones a built-in tab's grid
element with a synthetic collection whose surface is a guess at the
client's <code>Collection</code>. The items after the first two are still first
measurements.</p>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> With an install that has synced: the library's tab bar has a
<strong>Streaming</strong> tab after <em>Non-Steam</em>, and it opens. If there is no
tab, note the CEF console: <code>Moonlight Sync: could not patch the library tabs</code> names a throw; silence means the walk never found a
<code>tabs</code> array (report the route element's shape: <code>typeof route.children.type</code>, whether <code>props.renderFunc</code> exists).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> The tab can be <strong>reached</strong>: L1/R1 from <em>Non-Steam</em> lands on it, and
moving along the tab row with the D-pad stops on it. Found
2026-09-22: both asked the library for it (<code>onShowTab</code> navigates
to <code>/library/tab/MoonlightSyncStreaming</code>), and the page answered
with its first tab, since it validates the id against its own tab
array in its render, before the injection; <code>activeTabFor</code> puts the
id back on the bar element. If it is skipped again, report from
the CEF console the one <code>Moonlight Sync: library tabs found on …</code>
line (the bar's prop names, <code>activeTab</code>, a built-in tab's keys, the
template's type and prop names) and whether a <code>Moonlight Sync: library tabs, onShowTab("MoonlightSyncStreaming")</code> line appears
when you try: none means the bar never asked for it; one that does
not open it means the page's <code>tab</code> prop is no longer the requested
id (<code>requestedTab</code> in <code>libraryTabs.tsx</code>).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> The tab's own panels take focus: with no host, or before a sync,
the <em>No host yet</em> / <em>Nothing to stream</em> line gets the focus ring.
They are a <code>Focusable</code> with <code>focusableIfNoChildren</code> and a no-op
<code>onActivate</code> (both unmeasured); Settings → About's log panel is a
plain <code>Focusable</code> over text, so whether <em>it</em> takes focus says
whether a plain one already suffices.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> The tab's tiles are the panel's <strong>Stream buttons</strong> + <strong>Shortcuts</strong>:
an owned game opens the real game's page (with the Stream button),
an unowned one is the shortcut. Sorting, the footer legend and the
focus ring behave like <em>Non-Steam</em>'s. A blank tab, or a line that
starts <em>Moonlight Sync could not draw this tab on this client</em>
(the plugin's own error boundary; the console has the same error
with its component stack), means the grid read something the
synthetic collection lacks: report the console error, do not guess
at the field.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> Steam's own tabs are unchanged, and Settings → Moonlight Sync's
tab bar has no Streaming tab (the injection only takes a tab bar
some tab of which renders a collection).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> Nothing remounts: switching between library tabs and back does not
flash or reload the grid (each patched component type is cached).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> The tab is not a collection: <em>Collections</em> has no <em>Streaming</em> entry,
and on a second machine signed into the account nothing appears.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Upgrade from v0.4.0</strong>: the old <em>Streaming</em> collection loses this
device's owned games and shortcuts at the first load and is gone
once empty; a member the plugin never made (add one by hand first)
stays, and so does the collection.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> After a sync that adds or removes a title, the tab follows without
reopening the library (its content reads the store).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <em>Hide Stream shortcuts</em> off: the Streaming tab stays (its tiles
unchanged) and a <em>Streaming</em> collection appears within a moment
with the Stream shortcuts and the visible shortcuts, no owned game.
On again: the collection and its shortcuts stay (hidden, so the
collection looks empty in the library), and the tab is untouched.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <em>Streaming tab</em> off: the tab is gone (leave the library and come
back) and this device's shortcuts leave the collection, parked ones
too (deleted once empty). On: both back.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> The first sync with the collection in effect on a fresh install
creates it with every member in it: whether the client lists a
just-added member at once is unmeasured, so the reconcile never
deletes on the pass that added; if the collection is there but
empty after that pass, the next reconcile (open the Titles page)
must <strong>not</strong> delete it.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> Switch hosts and sync: the tab shows the new host's titles only.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <em>Remove everything</em>: after the restart the tab reads "Nothing to
stream from … yet" and, with the collection in effect, the
collection is gone once the client has dropped the removed
shortcuts.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> Unload the plugin (Decky → the plugin's menu → Uninstall, or a
reload): leave the library and come back: the tab is gone, and the
library still works if it had been the active tab.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">11. Wake-on-LAN (spec 3.18)</h2><a id="user-content-11-wake-on-lan-spec-318" class="anchor" aria-label="Permalink: 11. Wake-on-LAN (spec 3.18)" href="#11-wake-on-lan-spec-318"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">The MAC source and the packet were tested off-device against a
hand-written <code>Moonlight.conf</code> and a recording socket (<code>tests/test_wake.py</code>);
the Deck's own file, the network and the PC's firmware are what these
check. Two things ran on the SteamOS box on 2026-09-23: its <code>Moonlight.conf</code>
carried the MAC as a <em>quoted</em> <code>@ByteArray</code> (a raw byte of the MAC was a
comma), which the parser then read as unknown (fixed, with that line's
shape as a test); and a packet capture on the same LAN showed Moonlight's
own <code>list</code> sending a magic packet with the PC's MAC, which is what
Decision 66 answers.</p>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Moonlight's MAC is read.</strong> With the host paired in the Flathub
Moonlight, the panel's host row shows <strong>Wake</strong> beside <strong>Check</strong>
while the host is not known reachable. If there is no Wake, open
<code>~/.var/app/com.moonlight_stream.Moonlight/config/Moonlight Game Streaming Project/Moonlight.conf</code> and note the host's <code>mac=</code> line:
<code>@ByteArray()</code> (quoted or not) means Moonlight has none (a Sunshine
host that reports no MAC; there is no field to enter one since
2026-09-26); anything else that still gives no Wake is a parser gap:
paste the line's shape (hex masked) into an issue.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Wake appears and sends.</strong> Shut the PC down (a sleep state
Wake-on-LAN is enabled for). Open the panel: <strong>Check</strong> and <strong>Wake</strong> are side by
side, under the host row's cached count when there are several hosts (the PC stays
down: nothing was asked). Press Wake: a toast "Wake-on-LAN packet
sent to . Give it a minute, then Check." and, in
<code>moonlight-sync.log</code>, <code>wake_host &lt;host&gt;: N packets sent (moonlight MAC)</code> with N = (1 + the host's addresses) × 8. The PC wakes; Check
after a minute goes green.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Check wakes it too.</strong> Shut the PC down again; press Check instead:
Moonlight's own <code>list</code> sends the packet, so the row goes red first
(the 30 s seek times out) and the PC comes up anyway; Check again
after a minute goes green.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>No MAC in Moonlight:</strong> the row shows Check alone, and Settings →
Host has no MAC field (removed 2026-09-26). A <code>wake_macs</code> key left
in <code>settings.json</code> by an older build changes nothing: no Wake, no
<code>(settings MAC)</code> in the log.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">12. The on/off toggle (spec 3.19)</h2><a id="user-content-12-the-onoff-toggle-spec-319" class="anchor" aria-label="Permalink: 12. The on/off toggle (spec 3.19)" href="#12-the-onoff-toggle-spec-319"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Off-device the controller is covered (<code>controller.test.ts</code>, "the on/off
toggle"); what the client does with a hidden visible shortcut, the tab
removed from a bar it was in, and a Stream row on a page already open
are what these check. Start with the plugin on, synced, with <em>Hide
Stream shortcuts</em> <strong>off</strong> (so there are visible shortcuts and a
<em>Streaming</em> collection to watch).</p>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Off hides everything.</strong> Panel → <em>Enable Sync</em> off. The panel
shows the toggle alone. In the library: no Moonlight shortcut under
<em>Non-Steam</em> (the visible ones and the "Moonlight" client entry are
gone too), no <em>Streaming</em> tab in the bar, and the <em>Streaming</em>
collection is gone (or holds only entries another device put
there). An owned game's page has no Stream row. A page that was open
when the toggle flipped loses its row on the next render (open
another game and come back).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Settings are locked.</strong> The panel's header buttons and <em>Settings</em>
open a route with the toggle alone: no Host / Artwork / Titles /
Advanced / Updates / About.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>A reboot keeps it off.</strong> Restart Steam: the panel opens on the
toggle alone and the library is still clean (the load's reconcile
hides every entry again; the log has no <code>check_host</code>).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>On restores.</strong> Toggle on: the shortcuts are back where the
settings put them (Stream entries hidden or shown per <em>Hide Stream
shortcuts</em>, the client and host apps hidden), the <em>Streaming</em> tab
and collection are back, the Stream row and the menu item are
back, the host row goes green or red. If a sync's restart happened
just before the toggle went off, the layout toast fires now.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Busy.</strong> Start a sync, open the panel while it runs: the progress
view has no toggle; when it ends the toggle is live again.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">13. Reset match cache (Advanced)</h2><a id="user-content-13-reset-match-cache-advanced" class="anchor" aria-label="Permalink: 13. Reset match cache (Advanced)" href="#13-reset-match-cache-advanced"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Off-device the backend (<code>test_backend.py</code>, the <code>reset_match_cache</code> block)
and the controller (<code>controller.test.ts</code>, "Advanced → Reset match cache")
are covered; what a real cache and a real CLI do with the reset is what
these check. Start synced, with at least one pin (Titles → Change match)
and one title reading "no match".</p>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The file goes, and the toast counts it.</strong> Note
<code>~/.cache/moonlight-steam-sync/matches.json</code> (over SSH: the <code>titles</code>
count and how many are <code>"how": "pinned"</code>). Settings → Advanced →
<em>Reset match cache</em> → <strong>Reset</strong> in the confirm. The toast says
"Match cache reset: N titles forgotten, M pins included…" with
those numbers, the file is gone, <code>hosts/</code> beside it is not, and the
log has a <code>reset match cache: deleted …</code> line. Nothing changes in
the library.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>A second press is harmless.</strong> The toast says the cache was
already empty; the log says <code>no file at</code>.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The next sync re-matches.</strong> Sync now: the CLI resolves every
title again (the progress shows lookups, not cached hits), the
titles that read "no match" get a match where SteamGridDB has one,
the pinned title comes back as whatever the CLI resolves for it
(the pin is gone) and titles that already had artwork keep it. The
Titles page, opened after the run, agrees.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Busy.</strong> Start a sync, open Advanced while it runs: <em>Reset</em> is
disabled. Pin a title and press <em>Reset</em> within the moment the pin
is saving, if you can: the toast says "A match change is still
being saved" and the file is untouched.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Off.</strong> With the plugin toggled off the Advanced page is not
reachable (§12); nothing to press.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">14. The SteamGridDB key from the Game Mode browser (spec 3.20)</h2><a id="user-content-14-the-steamgriddb-key-from-the-game-mode-browser-spec-320" class="anchor" aria-label="Permalink: 14. The SteamGridDB key from the Game Mode browser (spec 3.20)" href="#14-the-steamgriddb-key-from-the-game-mode-browser-spec-320"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Off-device the backend (<code>test_cdp.py</code>, <code>test_sgdbpage.py</code> over the
fixture pages, <code>test_hard_rules.py</code> for the key in no event or log line)
and the frontend flow (<code>controller.test.ts</code>, "the SteamGridDB key from
the Game Mode browser"; the button's presence per key source in
<code>state.test.ts</code>) are covered; the real pages, the real browser and the
two navigation calls are what these check. The spec calls this section
§13; that number went to <em>Reset match cache</em> first. Any selector of spec
§3.20.1 that does not match on the Deck goes to a Fable-class model
(spec §5), never loosened here.</p>
<p dir="auto">Start with no key (Settings → Artwork → <em>Remove</em> if the key file is set;
<code>config.toml</code> without <code>[steamgriddb].api_key</code> and no <code>SGDB_API_KEY</code> in
plugin_loader's environment), and keep an SSH session tailing
<code>~/homebrew/logs/Moonlight Sync/moonlight-sync.log</code>.</p>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The button is where it should be.</strong> Settings → Artwork shows
<em>Get key from SteamGridDB…</em> under the key field with no key, and
again under <em>Test</em> / <em>Remove</em> once the key file is set. Put a key in
<code>config.toml</code> (or <code>SGDB_API_KEY</code> in the service's environment),
reopen the page: no button.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The consent text.</strong> The button opens "Sign in to SteamGridDB with
Steam?" with spec §3.20.4's text word for word, <em>Continue</em> and
<em>Cancel</em>. <em>Cancel</em> does nothing else (no browser, nothing in the
log).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>End to end, signed in to Steam.</strong> <em>Continue</em>: the Game Mode
browser takes the screen on SteamGridDB, then Steam's <em>Sign In</em>
page, then the API page, with no press of yours. <strong>[verify]
<code>NavigateToExternalWeb</code></strong> opens the same <code>page</code> target spec
§3.20.1 item 2 measured (over the debugger, <code>/json/list</code> shows
<code>Preferences - SteamGridDB</code> / <code>…/profile/preferences/api</code>); if it
opens anything else, report it (the fallback is
<code>steam://openurl/</code> through <code>SteamClient.URL.ExecuteSteamURL</code>, and
choosing it is a spec change). At the end <strong>[verify]
<code>NavigateBack</code></strong> leaves the browser and lands on Settings →
Artwork; if the browser stays, note it here (it is left open by
design then). The toasts are "SteamGridDB key saved (…XXXX)" with
the key's last four characters, then "SteamGridDB accepted the
key". The field reads "set, ends in …XXXX", and
<code>~/.config/moonlight-steam-sync/sgdb-api-key</code> is mode 0600.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The field follows the fetch.</strong> While it runs, back out of the
browser (B) to Settings → Artwork: the field says <em>Waiting for
SteamGridDB…</em> / <em>Signing in with Steam…</em> / <em>Steam is asking you to
log in on the page</em> / <em>Reading your key…</em> as it goes, and <em>Cancel</em>
stands where <em>Save</em> was.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Not signed in to Steam's web session.</strong> On a Steam account whose
community session is gone (sign out of steamcommunity.com in the
browser first), Steam's page asks for a password or a Steam Guard
code: the field says <em>Steam is asking you to log in on the page</em>,
nothing is typed for you, and once you have logged in the plugin
presses <em>Sign In</em> once and carries on.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>An account with no key.</strong> <strong>[verify]</strong> (Decision 61) On a
SteamGridDB account that never generated a key, the API page's
<em>Generate</em> button is pressed once and the key is read after it; on
a page with any key element, or only <em>Regenerate</em> / <em>Revoke</em>,
nothing is pressed and the toast says "SteamGridDB shows no API
key; generate one on its API page, then try again". <em>Revoke API
Key</em> is never pressed in any run (the key you had still works:
<em>Test</em>).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>A second attempt after a failure, no Steam restart.</strong> Make a
fetch fail (the <em>Left behind, then timed out</em> item below, or
<em>Cancel mid-way</em>), then press <em>Get key from SteamGridDB…</em> again:
it succeeds. The log shows "sgdb key fetch: reset the browser's
tab (was on <a href="http://www.steamgriddb.com" rel="nofollow">www.steamgriddb.com</a>)" (or steamcommunity.com) right
before "started" (Decision 70), then <code>waiting</code> and the fresh page;
never "failed (sgdb-page): no key and no generate button" within a
second of "started". A device's 2026-09-25 had exactly that until
Steam was restarted. Should the log say "could not reset the
browser's tab", report it with the rest of the line: the fetch
then reads the old page for its first polls (Decision 69) and
should still succeed.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Cancel mid-way.</strong> Start again, back out of the browser while
Steam's sign-in page is up, press <em>Cancel</em>: within a second the
toast says "The key fetch was cancelled", you stay on Settings →
Artwork (no navigation back from there), the key file is
unchanged, and the browser page is left where it was.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Left behind, then timed out.</strong> Start again, back out of the
browser with B and wait three minutes: the toast says "Timed out
waiting for the SteamGridDB page; enter the key by hand or try
again". Note where <code>NavigateBack</code> took you (it runs on every done
of a fetch the plugin opened and was not cancelled from the page);
if it left a page you did not expect, report it.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>No debugger.</strong> With <code>~/.steam/steam/.cef-enable-remote-debugging</code>
removed and Steam restarted (put it back afterwards; Decky
recreates it), <em>Continue</em> toasts "Steam's debugger port is not
reachable; enter the key by hand" and no browser opens.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The key is never in the log.</strong> After the runs above, <code>grep -c</code>
your key in <code>~/homebrew/logs/Moonlight Sync/moonlight-sync.log</code>
(and in Decky's own log, <code>journalctl -u plugin_loader</code>) is <code>0</code>;
the log has <code>sgdb key fetch: &lt;state&gt;</code> lines and "sgdb key fetched
from the browser", never the key.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Off.</strong> With the plugin toggled off the Artwork page is not
reachable (§12); nothing to press.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">15. The panel's launch row and Advanced's layout buttons (spec Decisions 67 and 77)</h2><a id="user-content-15-the-panels-launch-row-and-advanceds-layout-buttons-spec-decisions-67-and-77" class="anchor" aria-label="Permalink: 15. The panel's launch row and Advanced's layout buttons (spec Decisions 67 and 77)" href="#15-the-panels-launch-row-and-advanceds-layout-buttons-spec-decisions-67-and-77"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">The panel's launch buttons are icons on one line; the Moonlight entry's
<em>Layout</em> and <em>Make default</em> are in Settings → Advanced, beside <em>Clear</em>.
Needs a synced host that publishes <code>Desktop</code> and <code>Steam Big Picture</code>.</p>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The toggle.</strong> The top row reads <em>Enable Sync</em> with no text under
it, on or off. Settings, opened while off, shows the same toggle
with the "Off: every Moonlight shortcut is hidden…" line under it.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Sync now</strong> has no text under it.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>One launch row.</strong> The moon, the monitor and the Steam logo share
one row, in that order, equal widths, nothing clipped or overflowing
the 268 px column. The moon opens Moonlight; the monitor streams
<em>Desktop</em>; the Steam logo streams <em>Steam Big Picture</em>.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The caption follows focus.</strong> <strong>[verify]</strong> (<code>onGamepadFocus</code> on a
<code>DialogButton</code>, unmeasured) D-pad onto each icon: the line under the
row changes to "Open Moonlight · Launch the client…", "Desktop ·
Stream the host's desktop", "Steam Big Picture · Stream the host's
Steam in Big Picture", and the footer's A legend names the same
button. D-pad down off the row: the line goes back to <em>Open
Moonlight</em>'s (<code>onGamepadBlur</code>). If the line never changes, report it
(the icons are then unlabelled on the Deck).</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>A host app the host does not publish.</strong> Ignore <code>Desktop</code> on the
Titles page and sync: the monitor goes and the other two icons widen.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>No layout row on the panel.</strong> Under the launch row come the
counters; there is no <em>Layout</em> / <em>Make default</em> row.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>One layout row in Advanced.</strong> Settings → Advanced → <em>Default
controller layout</em> has <em>Layout</em>, <em>Make default</em> and <em>Clear</em> on one
line, right of its text, nothing clipped; the D-pad moves across the
three and down to <em>Hide Stream shortcuts</em>.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Layout.</strong> <em>Layout</em> opens Steam's controller configurator for the
Moonlight entry; B comes back to Advanced. Pick a layout there.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Make default.</strong> <em>Make default</em> asks "Use “” as the default
layout?"; confirm, and the toast counts the titles that took it. The
row's text now reads " ·  · set today …" and <em>Clear</em>
is enabled.
A Stream press on another game puts that layout on its shortcut.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Make default with nothing chosen.</strong> On a Moonlight entry whose
layout was never picked, <em>Make default</em> toasts "“Moonlight” has no
layout chosen yet. Use Layout first." and changes nothing. An
edited-in-place layout gets the <em>Export</em> toast instead.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Before the first sync</strong> (no Moonlight shortcut yet): the moon,
<em>Layout</em> and <em>Make default</em> are disabled; the launch line reads
"Open Moonlight · Sync once to enable" and Advanced's text "None.
Sync once, then set up Moonlight's Layout and press Make default".</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>While a game runs</strong> every icon and both layout buttons still work.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">16. Updating from the plugin (update spec 3.9, V1 to V7)</h2><a id="user-content-16-updating-from-the-plugin-update-spec-39-v1-to-v7" class="anchor" aria-label="Permalink: 16. Updating from the plugin (update spec 3.9, V1 to V7)" href="#16-updating-from-the-plugin-update-spec-39-v1-to-v7"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Every item here is the user's check, on a device; none is a merge
criterion. Off-device the updater is covered (<code>updates.test.ts</code> over
<code>tests/fixtures/update/releases.json</code>, <code>decky.test.ts</code> for the allowlist
and the call's arguments, <code>controller.test.ts</code> "the updater" for the
check, the refusals and the order <em>check (on a branch), stage, leave the
page, then ask Decky</em>, <code>test_hard_rules.py</code> for hard rule 12's greps,
<code>test_backend_updates.py</code> for the staging); the backend's download on the
device, Decky's own dialog, its install from the staged file, the
uninstall and the reload are what these check. Since the staged hand-off
(update spec 3.12.5) every install downloads first: the button reads
<em>Downloading…</em>, then the page closes and Decky's dialog opens.</p>
<p dir="auto">The first release with the updater cannot be updated <em>to</em> anything, since
nothing newer exists yet. V1 to V5 therefore use Settings → Updates →
<em>Install another version</em> → the installed version (<em>reinstall</em>), which
takes the same route with install type 1 (Decky's dialog then says
"reinstall" rather than "update"). The first real update, with the
panel's <em>Update to …</em> row and the toast, is checked when the release
after it ships; repeat V1, V2 and V5 then.</p>
<p dir="auto">Start with the build installed by <code>install.sh</code> (every file root-owned),
the plugin on, synced, a SteamGridDB key set and a default layout set,
and an SSH session tailing Decky's log (<code>journalctl -u plugin_loader -f</code>).
Before V1, copy what V4 compares: <code>~/homebrew/settings/Moonlight Sync/</code>
(settings.json, pending.json, layouts.json, ignore.json),
<code>~/.config/moonlight-steam-sync/</code>, <code>~/.cache/moonlight-steam-sync/</code> and
the library's tile count.</p>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The page.</strong> Settings shows <em>Updates</em> between <em>Advanced</em> and
<em>About</em>: <em>Installed</em> is the plugin's version, <em>Latest</em> says "Up to
date · checked today HH:MM" (or the newer release, once there is
one), <em>Check now</em> asks again (a second press within a minute asks
nothing), <em>Check for updates automatically</em> is on, and <em>Install
another version</em> lists only releases from 0.12.0 up. About has a
<em>Decky Loader</em> row with the loader's version.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>V1. [verify] The download, Decky's dialog, then the install.</strong>
<em>Install another version</em> → the installed version: the button reads
<em>Downloading…</em> while the plugin's backend downloads and checks the
zip (the plugin's log shows <code>stage_update v…: staged …</code>), then Decky's
dialog asks "Are you sure you want to reinstall Moonlight Sync …?"
(on a real update: "…update Moonlight Sync to version X?"). Confirm:
Decky installs the staged zip from
<code>~/homebrew/data/Moonlight Sync/update/staged/</code>, on loader v3.2.9
(§17's V8 is the same check for a branch build). If the dialog never
appears, or Decky's log shows an error, escalate with both logs.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>V2. [verify] The page closes first, the plugin comes back.</strong> The
settings page is gone before Decky's dialog shows (Decision U4).
After Confirm the plugin reloads at the chosen version (About), with
no loader or Steam restart. If the screen shows anything else (a
blank route, a frozen page), escalate with what it showed.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>V3. [verify] Over both kinds of install.</strong> V1 over the copy
<code>install.sh</code> put there (root-owned), then once more over the copy
Decky just installed. Both work; if either fails, escalate.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>V4. [verify] Nothing of yours changes. Blocking.</strong> After the
update: settings.json, pending.json, layouts.json and ignore.json
are what they were, the key file is there with mode 0600 (Artwork →
<em>Test</em> accepts it), the CLI's config and caches are unchanged, the
default layout is still set (Advanced), and the library has the same
tiles, Stream buttons and Streaming tab. Anything different:
escalate, and it blocks the release.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>V5. Cancel.</strong> Start an install and press <em>Cancel</em> in Decky's
dialog: the old version keeps running, the page's buttons are
usable again, and a second attempt opens the dialog again (the zip
already staged is not downloaded twice: the plugin's log says
"already staged"). Then start one and press the page's own <em>Cancel</em>
under <em>Downloading…</em> at once: no toast, no dialog, the buttons are
usable again.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>V6. Where the plugin lands.</strong> After a Confirm, note where Moonlight
Sync sits in Decky's plugin list (expected: at the end, §2.3 of the
update spec) and whether a hidden or frozen state it had in Decky's
list survived. Write what you see into the README's "Updating"
section.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>V7. Offline.</strong> With Wi-Fi off, start an install: the download
fails before Decky is asked, a toast says "Could not reach GitHub:
…", Decky's dialog never opens, the settings page stays, and the old
version still runs (Decky has nothing to download, so its progress
never shows). If Decky's dialog or progress appears, escalate. Back
online, the install works.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The check's toast and the panel's row</strong> (once a newer release
exists). A plugin load (restart Steam) toasts "Version X is
available. Settings → Updates" once, and the panel's last row reads
<em>Update to X</em>; pressing it opens Settings → Updates. With <em>Check for
updates automatically</em> off, a load toasts nothing and adds no row
until <em>Check now</em>.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>Off.</strong> With the plugin toggled off: no toast at load, no row, and
the settings route has no <em>Updates</em> page (§12).</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">17. The staged hand-off and the channels (update spec 3.12.5, V8 to V10)</h2><a id="user-content-17-the-staged-hand-off-and-the-channels-update-spec-3125-v8-to-v10" class="anchor" aria-label="Permalink: 17. The staged hand-off and the channels (update spec 3.12.5, V8 to V10)" href="#17-the-staged-hand-off-and-the-channels-update-spec-3125-v8-to-v10"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Every item here is the user's check, on a device, before a release with
the channel picker is cut; none is a merge criterion. Off-device the
staging is covered (<code>test_fetch.py</code>, <code>test_updates.py</code>,
<code>test_backend_updates.py</code> over <code>file://</code> trees), and so are the order
<em>check, stage, leave, ask</em>, the forced check on a branch channel, a failed
staging, <em>Cancel</em> and the picker (<code>controller.test.ts</code>), and the
allowlist's <code>file://</code> rule (<code>decky.test.ts</code>); Decky's install from a
<code>file://</code> path, the picker's modal and a real branch build are what these
check.</p>
<p dir="auto">Start as for §16 (the plugin on, synced, a key and a default layout set,
Decky's log tailed), with <code>main</code> publishing builds (<code>build-main</code> exists on
the repository's releases, with a <code>build.json</code> among its assets). Keep an
SSH session for V10's <code>moonlight-steam-sync --version</code> and <code>sha256sum ~/.local/bin/moonlight-steam-sync</code>.</p>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The picker.</strong> Settings → Updates' second row is <em>Channel</em>, reading
<em>Releases</em>, with the description "Releases are tested. Builds of a
branch are whatever was pushed last." Its list has <em>Releases</em>, then
<code>main</code>, then any other branch with a build. Choosing <code>main</code> asks
"Follow main?" ("Builds of a branch are untested and can break
syncing. You can return to Releases here at any time.", <em>Follow</em> /
<em>Cancel</em>). <strong>[verify]</strong> <em>Cancel</em> leaves <em>Releases</em> selected in the
dropdown (whether Steam's dropdown snaps back to the stored channel
after a cancelled confirm is unmeasured) and asks GitHub nothing.
Choosing <em>Releases</em> from a branch asks nothing.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>The branch's <code>build.json</code> through the loader's fetch.</strong> The
<code>build.json</code> request is the plugin's first request through Decky's
<code>fetchNoCors</code> to <code>github.com/…/releases/download/…</code>, which answers
with a redirect (302) to another host; every request before it went
to <code>api.github.com</code>, which does not redirect. Follow <code>main</code> (the
picker checks at once): <em>Latest</em> must show the build, <code>main @ &lt;sha7&gt; · built … · checked …</code>, not "No build of main is published".
If it shows the latter although <code>build-main</code> has a <code>build.json</code>, the
loader's fetch did not follow the redirect (the check reports no
error then, since a failed second request never fails it): escalate
with Decky's log.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>A new build of the followed branch is seen at once.</strong> With <code>main</code>
followed and its build installed, merge to <code>main</code> (or <code>gh workflow run builds.yml --ref main</code>) and wait for the <em>Builds</em> run to finish;
<em>Check now</em> on the Updates page must show the new commit, <code>main @ &lt;the new sha7&gt;</code>, and offer <em>Switch to</em> it. Before amendment A6 the
loader's fetch went through CEF's HTTP cache, which kept the old
<code>build.json</code> (served with <code>Last-Modified</code> and no <code>Cache-Control</code>)
for hours: the page said <em>Up to date</em> although <code>main</code> had moved,
and, once the new build was installed through <em>Releases</em> and back,
offered the previous commit for as long. Found on the Deck
2026-09-29; <code>journalctl -u plugin_loader</code> shows a <code>Preparing GET request to …/build-main/build.json</code> line for a check that reached
the loader, and none for one the cache answered.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>V8. [verify] A <code>file://</code> artifact installs.</strong> On <code>main</code>, press
<em>Switch to main @ </em>: <em>Downloading…</em>, then the page closes and
Decky's dialog asks to overwrite Moonlight Sync with version
" (main @ )"; Confirm. Decky installs the zip from
<code>~/homebrew/data/Moonlight Sync/update/staged/</code> (a path with a space
in it) through <code>utilities/install_plugin</code>, and the plugin reloads.
If Decky refuses the artifact, cannot open the path, or its log shows
an error: escalate with the log. The fallback the spec allows then
(update spec 3.12.5, not built): releases keep the URL route of
update spec 3.5, and branch builds hand Decky the release asset's URL
after a successful staging, which has then proved what the URL
serves.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>V9. Follow <code>main</code>, install, a second push, back to Releases.</strong>
After V8: <em>Installed</em> reads <code>&lt;version&gt; · main @ &lt;sha7&gt; · built …</code>,
<em>Latest</em> says "Up to date · checked …" and there is no install
button. Push a commit to <code>main</code>, wait for <code>builds.yml</code> to publish it,
then restart Steam (or press <em>Check now</em> a minute later): the toast
"Version main @  is available. Settings → Updates" and the
panel's row appear, and <em>Switch to main @ </em> installs it.
Then choose <em>Releases</em>: <em>Latest</em> names the newest release and the
button reads <em>Switch to </em>; pressing it installs the release
(Decky asks to overwrite) and <em>Installed</em> reads the plain version
again. Anything else: escalate.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>V10. The CLI at each step of V9.</strong> Before V8, after V8, after the
second push's install and after the return to <em>Releases</em>, note
<code>moonlight-steam-sync --version</code> and the sha256 of
<code>~/.local/bin/moonlight-steam-sync</code>, and About's CLI rows. Expected:
the version stays the release's number throughout; the bytes change
to the branch build's bundled CLI after V8 and after the second
push's install (a build of the same version with other bytes
replaces it, README "The bundled CLI"), and go back to the release's
after the return to <em>Releases</em>. Anything else: escalate.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>A failed staging leaves everything as it was.</strong> With Wi-Fi off,
press <em>Switch to …</em> on <code>main</code>: the forced check fails first, a toast
says "Could not reach GitHub: …", nothing downloads, Decky's dialog
never opens. Back online, press it and at once the page's <em>Cancel</em>
under <em>Downloading…</em>: no toast, no dialog, and a second press works.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" aria-label="Incomplete task"> <strong>A build made on the device is the commit it is</strong> (update spec
amendment A5). On the device, clone <code>main</code> afresh, build it (<code>pnpm install</code>, <code>pnpm run build</code>, <code>backend/entrypoint.sh</code>,
<code>scripts/package.py --require-cli</code>) and install the zip by hand
(README "Manual install"). <code>package.py</code> must print <code>package.py: build.json from git: main @ &lt;sha12&gt;</code>; if it prints <code>no build.json: …</code> instead, the text in brackets is git's own reason (a clone that
belongs to another user is "dubious ownership"): report it. Then,
with the <em>Channel</em> on <code>main</code> and <code>build-main</code> published from that
same commit: <em>Installed</em> reads <code>&lt;version&gt; · main @ &lt;sha7&gt; · built …</code>, <em>Latest</em> says "Up to date · checked …" and there is no install
button; Settings → About's <em>Built from</em> reads <code>main @ &lt;sha7&gt; · built …</code> and <em>Commit</em> is the whole commit, the one <code>git rev-parse HEAD</code>
printed in the clone. Before this, such a build always showed <em>Switch to main @
</em> (found 2026-09-28). With the <em>Channel</em> on <em>Releases</em> the
page offers <em>Switch to </em> instead, as for any branch build.</li>
</ul>
</article></div><div class="d-none"></div></section></div></div></div> </div> <!-- --> </div></div></div></div></div></div><div class="ScrollMarksContainer-module__scrollMarksContainer__Eu7uU" id="find-result-marks-container"></div><div class="d-none"></div><div class="d-none"></div></div> <!-- --> <!-- --> </div>
</react-app>




  </div>

</turbo-frame>

    </main>
  </div>

  </div>

          <footer class="footer f6 color-fg-muted color-border-subtle tmp-pt-7 tmp-pb-6 p-responsive" role="contentinfo"  >
  <h2 class='sr-only'>Footer</h2>

  


  <div class="d-flex flex-justify-center flex-items-center flex-column-reverse flex-lg-row flex-wrap flex-lg-nowrap">
    <div class="d-flex flex-items-center flex-shrink-0 mx-2">
      <a aria-label="GitHub Homepage" class="footer-octicon mr-2" href="https://github.com">
        <svg aria-hidden="true" data-component="Octicon" height="24" viewBox="0 0 24 24" version="1.1" width="24" data-view-component="true" class="octicon octicon-mark-github">
    <path d="M10.226 17.284c-2.965-.36-5.054-2.493-5.054-5.256 0-1.123.404-2.336 1.078-3.144-.292-.741-.247-2.314.09-2.965.898-.112 2.111.36 2.83 1.01.853-.269 1.752-.404 2.853-.404 1.1 0 1.999.135 2.807.382.696-.629 1.932-1.1 2.83-.988.315.606.36 2.179.067 2.942.72.854 1.101 2 1.101 3.167 0 2.763-2.089 4.852-5.098 5.234.763.494 1.28 1.572 1.28 2.807v2.336c0 .674.561 1.056 1.235.786 4.066-1.55 7.255-5.615 7.255-10.646C23.5 6.188 18.334 1 11.978 1 5.62 1 .5 6.188.5 12.545c0 4.986 3.167 9.12 7.435 10.669.606.225 1.19-.18 1.19-.786V20.63a2.9 2.9 0 0 1-1.078.224c-1.483 0-2.359-.808-2.987-2.313-.247-.607-.517-.966-1.034-1.033-.27-.023-.359-.135-.359-.27 0-.27.45-.471.898-.471.652 0 1.213.404 1.797 1.235.45.651.921.943 1.483.943.561 0 .92-.202 1.437-.719.382-.381.674-.718.944-.943"></path>
</svg>
</a>
      <span>
        &copy; 2026 GitHub,&nbsp;Inc.
      </span>
    </div>

    <nav aria-label="Footer">
      <h3 class="sr-only" id="sr-footer-heading">Footer navigation</h3>

      <ul class="list-style-none d-flex flex-justify-center flex-wrap mb-2 mb-lg-0" aria-labelledby="sr-footer-heading">


          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to Terms&quot;,&quot;label&quot;:&quot;text:terms&quot;}" href="https://docs.github.com/site-policy/github-terms/github-terms-of-service" data-view-component="true" class="Link--secondary Link">Terms</a>
          </li>

          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to privacy&quot;,&quot;label&quot;:&quot;text:privacy&quot;}" href="https://docs.github.com/site-policy/privacy-policies/github-privacy-statement" data-view-component="true" class="Link--secondary Link">Privacy</a>
          </li>


            <li class="mx-2">
              <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to security&quot;,&quot;label&quot;:&quot;text:security&quot;}" href="https://github.com/security" data-view-component="true" class="Link--secondary Link">Security</a>
            </li>

            <li class="mx-2">
              <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to status&quot;,&quot;label&quot;:&quot;text:status&quot;}" href="https://www.githubstatus.com/" data-view-component="true" class="Link--secondary Link">Status</a>
            </li>

          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to community&quot;,&quot;label&quot;:&quot;text:community&quot;}" href="https://github.community/" data-view-component="true" class="Link--secondary Link">Community</a>
          </li>

          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to docs&quot;,&quot;label&quot;:&quot;text:docs&quot;}" href="https://docs.github.com/" data-view-component="true" class="Link--secondary Link">Docs</a>
          </li>

          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to contact&quot;,&quot;label&quot;:&quot;text:contact&quot;}" href="https://support.github.com?tags=dotcom-footer" data-view-component="true" class="Link--secondary Link">Contact</a>
          </li>

          
<li class="mx-2" >
  <cookie-consent-link>
    <button
      type="button"
      class="Link--secondary underline-on-hover border-0 p-0 color-bg-transparent"
      data-action="click:cookie-consent-link#showConsentManagement"
      data-analytics-event="{&quot;location&quot;:&quot;footer&quot;,&quot;action&quot;:&quot;cookies&quot;,&quot;context&quot;:&quot;subfooter&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;label&quot;:&quot;cookies_link_subfooter_footer&quot;}"
    >
      Manage cookies
    </button>
  </cookie-consent-link>
</li>

  <li class="mx-2">
    <cookie-consent-link>
      <button
        type="button"
        class="Link--secondary underline-on-hover border-0 p-0 color-bg-transparent text-left"
        data-action="click:cookie-consent-link#showConsentManagement"
        data-analytics-event="{&quot;location&quot;:&quot;footer&quot;,&quot;action&quot;:&quot;dont_share_info&quot;,&quot;context&quot;:&quot;subfooter&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;label&quot;:&quot;dont_share_info_link_subfooter_footer&quot;}"
      >
        Do not share my personal information
      </button>
    </cookie-consent-link>
  </li>

      </ul>
    </nav>
  </div>
</footer>



    <ghcc-consent id="ghcc" class="position-fixed bottom-0 left-0" style="z-index: 999999"
      data-locale="en"
      data-initial-cookie-consent-allowed=""
      data-cookie-consent-required="true"
    ></ghcc-consent>




  <div id="ajax-error-message" class="ajax-error-message flash flash-error" hidden>
    <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
    <button type="button" class="flash-close js-ajax-error-dismiss" aria-label="Dismiss error">
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x">
    <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
    </button>
    You can’t perform that action at this time.
  </div>

    <template id="site-details-dialog">
  <details class="details-reset details-overlay details-overlay-dark lh-default color-fg-default hx_rsm" open>
    <summary role="button" aria-label="Close dialog"></summary>
    <details-dialog class="Box Box--overlay d-flex flex-column anim-fade-in fast hx_rsm-dialog hx_rsm-modal">
      <button class="Box-btn-octicon m-0 btn-octicon position-absolute right-0 top-0" type="button" aria-label="Close dialog" data-close-dialog>
        <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x">
    <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
      </button>
      <div class="octocat-spinner tmp-my-6 js-details-dialog-spinner"></div>
    </details-dialog>
  </details>
</template>

    <div class="Popover js-hovercard-content position-absolute" style="display: none; outline: none;">
  <div class="Popover-message Popover-message--bottom-left Popover-message--large Box color-shadow-large" style="width:360px;">
  </div>
</div>

    <template id="snippet-clipboard-copy-button">
  <div class="zeroclipboard-container position-absolute right-0 top-0">
    <clipboard-copy aria-label="Copy code to clipboard" class="ClipboardButton btn js-clipboard-copy m-2 p-0" data-copy-feedback="Copied!" data-tooltip-direction="w">
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copy js-clipboard-copy-icon m-2 tmp-m-2">
    <path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
</svg>
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-check js-clipboard-check-icon color-fg-success d-none m-2 tmp-m-2">
    <path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
</svg>
    </clipboard-copy>
  </div>
</template>
<template id="snippet-clipboard-copy-button-unpositioned">
  <div class="zeroclipboard-container">
    <clipboard-copy aria-label="Copy code to clipboard" class="ClipboardButton btn btn-invisible js-clipboard-copy m-2 p-0 d-flex flex-justify-center flex-items-center" data-copy-feedback="Copied!" data-tooltip-direction="w">
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copy js-clipboard-copy-icon">
    <path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
</svg>
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-check js-clipboard-check-icon color-fg-success d-none">
    <path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
</svg>
    </clipboard-copy>
  </div>
</template>




    </div>
    <div id="js-global-screen-reader-notice" class="sr-only mt-n1" aria-live="polite" aria-atomic="true" ></div>
    <div id="js-global-screen-reader-notice-assertive" class="sr-only mt-n1" aria-live="assertive" aria-atomic="true"></div>
  </body>
</html>

