diff --git a/.agent_memory/session_context.md b/.agent_memory/session_context.md
index cb07fa3664..6c7a1f5c1c 100644
--- a/.agent_memory/session_context.md
+++ b/.agent_memory/session_context.md
@@ -130,6 +130,12 @@
- Cleaned up leftover speed-up workarounds: completely removed the Flatpak Gradle Generator plugin application and tasks from the root project and all other library/feature subprojects (`core:ble`, `core:common`, `core:database`, `core:model`, `core:navigation`, `core:proto`, and `feature:messaging`), including deleting the unused `flatpakKmpAndroidMeta` configuration from `feature:messaging`.
- Verified that local execution of `:desktopApp:flatpakGradleGenerator` runtimeClasspath resolution speed dropped from 46 seconds to 12 seconds, and all Spotless and Detekt linting checks passed.
+## 2026-05-17 — Added provisional mesh discovery beacon support
+- Added `MeshDiscoveryBeacon` in `core:model` with conservative fixed-width decoding for the discovery/config beacon discussed in meshtastic/firmware#7183 and #10243.
+- Surfaced decoded beacons passively on the LoRa settings screen and in debug payload decoding; the app never applies radio settings automatically.
+- Guarded decoding to candidate discovery ports (`PRIVATE_APP` and `UNKNOWN_APP`) and accepted both sub-GHz and LORA_24 frequencies.
+- Verified with `:core:model:jvmTest`, `:feature:settings:compileKotlinJvm`, scoped `spotlessCheck`, and clean `codex review --base origin/main`.
+
## 2026-05-12 — Implemented Apple alignment for docs feature (FR-038)
- Branch: `feat/20260507-161858-app-docs-markdown`
- Gap analysis against `meshtastic-apple` completed. Implemented 4 alignment items:
diff --git a/.skills/compose-ui/strings-index.txt b/.skills/compose-ui/strings-index.txt
index f6884c99d5..80fe827193 100644
--- a/.skills/compose-ui/strings-index.txt
+++ b/.skills/compose-ui/strings-index.txt
@@ -710,6 +710,10 @@ mark_as_read
match_all
match_any
max
+### MESH ###
+mesh_discovery_beacon_summary
+mesh_discovery_beacons
+mesh_discovery_beacons_summary
mesh_map_location
mesh_map_location_description
### MESHTASTIC ###
diff --git a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/MeshDiscoveryBeacon.kt b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/MeshDiscoveryBeacon.kt
new file mode 100644
index 0000000000..1aa86f0663
--- /dev/null
+++ b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/MeshDiscoveryBeacon.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2026 Meshtastic LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+@file:Suppress("MagicNumber")
+
+package org.meshtastic.core.model
+
+import okio.ByteString
+import org.meshtastic.proto.PortNum
+
+/**
+ * Compact app-side representation of the config discovery beacon proposed in meshtastic/firmware#7183 and
+ * meshtastic/firmware#10243.
+ *
+ * The firmware-side protocol is still under discussion, so this parser is intentionally conservative and side-effect
+ * free. It only accepts a tiny fixed-width payload and never applies radio settings automatically.
+ */
+data class MeshDiscoveryBeacon(
+ val version: Int,
+ val roleHint: RoleHint,
+ val forwardingHint: ForwardingHint,
+ val frequencyKHz: Int,
+ val bandwidth: Bandwidth,
+ val spreadingFactor: Int,
+ val codingRate: Int,
+ val nodeId: Int,
+ val primaryChannelHash: Int,
+ val primaryChannelName: String,
+) {
+ val nodeIdString: String
+ get() = DataPacket.nodeNumToDefaultId(nodeId)
+
+ val frequencyMHz: Float
+ get() = frequencyKHz / KHZ_PER_MHZ
+
+ fun toDebugString(): String = buildString {
+ appendLine("MeshDiscoveryBeacon:")
+ appendLine(" version: $version")
+ appendLine(" role_hint: $roleHint")
+ appendLine(" forwarding_hint: $forwardingHint")
+ appendLine(" frequency_mhz: $frequencyMHz")
+ appendLine(" bandwidth: ${bandwidth.label}")
+ appendLine(" spreading_factor: $spreadingFactor")
+ appendLine(" coding_rate: 4/$codingRate")
+ appendLine(" node_id: $nodeIdString")
+ appendLine(" primary_channel_hash: $primaryChannelHash")
+ appendLine(" primary_channel_name: $primaryChannelName")
+ }
+
+ enum class RoleHint {
+ MIGHT_FORWARD,
+ WILL_FORWARD,
+ WILL_NOT_FORWARD,
+ UNKNOWN,
+ }
+
+ enum class ForwardingHint {
+ ALL,
+ CORE,
+ KNOWN,
+ NONE,
+ }
+
+ enum class Bandwidth(val label: String) {
+ BW_31("31.25 kHz"),
+ BW_62("62.5 kHz"),
+ BW_125("125 kHz"),
+ BW_250("250 kHz"),
+ BW_500("500 kHz"),
+ BW_812("812.5 kHz"),
+ BW_1625("1625 kHz"),
+ UNKNOWN("Unknown"),
+ }
+
+ companion object {
+ const val ENCODED_SIZE = 22
+ private const val KHZ_PER_MHZ = 1000f
+ private const val MAX_VERSION = 0
+ private const val MIN_FREQUENCY_KHZ = 400_000
+ private const val MAX_FREQUENCY_KHZ = 2_500_000
+ private const val CHANNEL_NAME_BYTES = 12
+ private const val MIN_SPREADING_FACTOR = 5
+ private const val MIN_CODING_RATE = 5
+
+ fun decode(portnumValue: Int, payload: ByteString): MeshDiscoveryBeacon? {
+ if (!isCandidatePort(portnumValue)) return null
+ return decode(payload)
+ }
+
+ fun isCandidatePort(portnumValue: Int): Boolean =
+ portnumValue == PortNum.PRIVATE_APP.value || portnumValue == PortNum.UNKNOWN_APP.value
+
+ fun decode(payload: ByteString): MeshDiscoveryBeacon? {
+ if (payload.size != ENCODED_SIZE) return null
+ val bytes = payload.toByteArray()
+
+ val header = bytes[0].unsigned
+ val version = header shr 6
+ val reserved = (header shr 4) and 0x03
+ if (version > MAX_VERSION || reserved != 0) return null
+
+ val frequencyKHz = bytes.uint24At(1)
+ if (frequencyKHz !in MIN_FREQUENCY_KHZ..MAX_FREQUENCY_KHZ) return null
+
+ val radio = bytes[4].unsigned
+ val bandwidth = Bandwidth.entries.getOrNull((radio shr 5) and 0x07) ?: Bandwidth.UNKNOWN
+ if (bandwidth == Bandwidth.UNKNOWN) return null
+ val spreadingFactor = ((radio shr 2) and 0x07) + MIN_SPREADING_FACTOR
+ val codingRate = (radio and 0x03) + MIN_CODING_RATE
+
+ val channelName = bytes.decodeChannelName()
+ if (channelName == null) return null
+
+ return MeshDiscoveryBeacon(
+ version = version,
+ roleHint = RoleHint.entries[(header shr 2) and 0x03],
+ forwardingHint = ForwardingHint.entries[header and 0x03],
+ frequencyKHz = frequencyKHz,
+ bandwidth = bandwidth,
+ spreadingFactor = spreadingFactor,
+ codingRate = codingRate,
+ nodeId = bytes.int32At(5),
+ primaryChannelHash = bytes[9].unsigned,
+ primaryChannelName = channelName,
+ )
+ }
+
+ private val Byte.unsigned: Int
+ get() = toInt() and 0xff
+
+ private fun ByteArray.uint24At(offset: Int): Int =
+ (this[offset].unsigned shl 16) or (this[offset + 1].unsigned shl 8) or this[offset + 2].unsigned
+
+ private fun ByteArray.int32At(offset: Int): Int = (this[offset].unsigned shl 24) or
+ (this[offset + 1].unsigned shl 16) or
+ (this[offset + 2].unsigned shl 8) or
+ this[offset + 3].unsigned
+
+ private fun ByteArray.decodeChannelName(): String? {
+ val end = indexOfFirstZero(startIndex = 10).takeIf { it >= 0 } ?: ENCODED_SIZE
+ if (end - 10 > CHANNEL_NAME_BYTES) return null
+ val nameBytes = copyOfRange(10, end)
+ if (nameBytes.any { it.unsigned !in 0x20..0x7e }) return null
+ return nameBytes.decodeToString()
+ }
+
+ private fun ByteArray.indexOfFirstZero(startIndex: Int): Int {
+ for (index in startIndex until ENCODED_SIZE) {
+ if (this[index] == 0.toByte()) return index
+ }
+ return -1
+ }
+ }
+}
diff --git a/core/model/src/commonTest/kotlin/org/meshtastic/core/model/MeshDiscoveryBeaconTest.kt b/core/model/src/commonTest/kotlin/org/meshtastic/core/model/MeshDiscoveryBeaconTest.kt
new file mode 100644
index 0000000000..400f37fe62
--- /dev/null
+++ b/core/model/src/commonTest/kotlin/org/meshtastic/core/model/MeshDiscoveryBeaconTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2026 Meshtastic LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+@file:Suppress("MagicNumber")
+
+package org.meshtastic.core.model
+
+import okio.ByteString.Companion.toByteString
+import org.meshtastic.proto.PortNum
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+
+class MeshDiscoveryBeaconTest {
+ @Test
+ fun decode_validPayload() {
+ val payload = discoveryPayload()
+
+ val beacon = MeshDiscoveryBeacon.decode(PortNum.PRIVATE_APP.value, payload)
+
+ requireNotNull(beacon)
+ assertEquals(0, beacon.version)
+ assertEquals(MeshDiscoveryBeacon.RoleHint.WILL_FORWARD, beacon.roleHint)
+ assertEquals(MeshDiscoveryBeacon.ForwardingHint.CORE, beacon.forwardingHint)
+ assertEquals(915_000, beacon.frequencyKHz)
+ assertEquals(915f, beacon.frequencyMHz)
+ assertEquals(MeshDiscoveryBeacon.Bandwidth.BW_250, beacon.bandwidth)
+ assertEquals(7, beacon.spreadingFactor)
+ assertEquals(5, beacon.codingRate)
+ assertEquals(0x12345678, beacon.nodeId)
+ assertEquals("!12345678", beacon.nodeIdString)
+ assertEquals(0x5a, beacon.primaryChannelHash)
+ assertEquals("ShortFast", beacon.primaryChannelName)
+ }
+
+ @Test
+ fun decode_rejectsWrongSize() {
+ assertNull(MeshDiscoveryBeacon.decode(byteArrayOf(0).toByteString()))
+ }
+
+ @Test
+ fun decode_rejectsNonCandidatePort() {
+ assertNull(MeshDiscoveryBeacon.decode(PortNum.LORAWAN_BRIDGE.value, discoveryPayload()))
+ }
+
+ @Test
+ fun decode_acceptsLora24Frequency() {
+ val payload = discoveryPayload().toByteArray()
+ payload[1] = 0x25
+ payload[2] = 0x16
+ payload[3] = 0xa0.toByte() // 2430624 kHz
+
+ val beacon = MeshDiscoveryBeacon.decode(PortNum.PRIVATE_APP.value, payload.toByteString())
+
+ requireNotNull(beacon)
+ assertEquals(2_430_624, beacon.frequencyKHz)
+ }
+
+ @Test
+ fun decode_rejectsReservedHeaderBits() {
+ val payload = discoveryPayload().toByteArray()
+ payload[0] = 0b0001_0000
+
+ assertNull(MeshDiscoveryBeacon.decode(payload.toByteString()))
+ }
+
+ @Test
+ fun decode_rejectsImplausibleFrequency() {
+ val payload = discoveryPayload().toByteArray()
+ payload[1] = 0
+ payload[2] = 0
+ payload[3] = 1
+
+ assertNull(MeshDiscoveryBeacon.decode(payload.toByteString()))
+ }
+
+ @Test
+ fun decode_rejectsNonAsciiChannelName() {
+ val payload = discoveryPayload().toByteArray()
+ payload[10] = 0x01
+
+ assertNull(MeshDiscoveryBeacon.decode(payload.toByteString()))
+ }
+
+ private fun discoveryPayload(): okio.ByteString {
+ val payload = ByteArray(MeshDiscoveryBeacon.ENCODED_SIZE)
+ payload[0] = 0b0000_0101 // version 0, role WILL_FORWARD, forwarding CORE
+ payload[1] = 0x0d
+ payload[2] = 0xf6.toByte()
+ payload[3] = 0x38 // 915000 kHz
+ payload[4] = 0b0110_1000 // 250 kHz, SF7, CR 4/5
+ payload[5] = 0x12
+ payload[6] = 0x34
+ payload[7] = 0x56
+ payload[8] = 0x78
+ payload[9] = 0x5a
+ "ShortFast".encodeToByteArray().copyInto(payload, destinationOffset = 10)
+ return payload.toByteString()
+ }
+}
diff --git a/core/resources/src/commonMain/composeResources/values/strings.xml b/core/resources/src/commonMain/composeResources/values/strings.xml
index 6c59d355d7..08e387ba2d 100644
--- a/core/resources/src/commonMain/composeResources/values/strings.xml
+++ b/core/resources/src/commonMain/composeResources/values/strings.xml
@@ -740,6 +740,10 @@
Match All | Any
Match Any | All
Max
+
+ %1$s MHz • %2$s • SF%3$d CR4/%4$d • %5$s • %6$s
+ Nearby mesh beacons
+ These passive beacons advertise nearby mesh settings. Review them before changing your radio configuration.
Mesh Map Location
Enables the blue location dot for your phone in the mesh map.
diff --git a/docs/en/user/settings-radio-user.md b/docs/en/user/settings-radio-user.md
index 7876ba4069..8ab0e89003 100644
--- a/docs/en/user/settings-radio-user.md
+++ b/docs/en/user/settings-radio-user.md
@@ -2,7 +2,7 @@
title: Settings — Radio & User
parent: User Guide
nav_order: 7
-last_updated: 2026-05-20
+last_updated: 2026-05-24
description: Configure your radio hardware, LoRa presets, user profile, position sharing, power management, and security.
aliases:
- settings
@@ -53,6 +53,12 @@ After modifying settings, tap **Save** to write the configuration to your radio.
> ⚠️ **Important:** You **must** set your region before transmitting. Operating without the correct region may violate local radio regulations. See the [region configuration guide](https://meshtastic.org/docs/getting-started/initial-config) on meshtastic.org for details.
+#### Mesh Discovery Beacons
+
+When compatible firmware sends mesh discovery beacons nearby, the app can show them at the top of **LoRa Config**. Each beacon summarizes the transmitting node, primary channel name, frequency, bandwidth, spreading factor, coding rate, and forwarding hint.
+
+Discovery beacons are informational only. Review them as nearby mesh context; the app does not automatically change your radio configuration from a received beacon.
+
### Modem Presets
| Preset | Range | Speed | SNR Limit | Best For |
@@ -171,4 +177,3 @@ Settings use standard preference controls — dropdowns, toggles, and sliders:
- [Initial configuration](https://meshtastic.org/docs/getting-started/initial-config) — region setup guide on meshtastic.org
---
-
diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/debugging/DebugViewModel.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/debugging/DebugViewModel.kt
index 0a72e1545d..c2a93c517e 100644
--- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/debugging/DebugViewModel.kt
+++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/debugging/DebugViewModel.kt
@@ -34,6 +34,7 @@ import org.meshtastic.core.common.util.DateFormatter
import org.meshtastic.core.common.util.ioDispatcher
import org.meshtastic.core.common.util.nowInstant
import org.meshtastic.core.database.entity.Packet
+import org.meshtastic.core.model.MeshDiscoveryBeacon
import org.meshtastic.core.model.MeshLog
import org.meshtastic.core.model.getTracerouteResponse
import org.meshtastic.core.model.util.decodeOrNull
@@ -495,7 +496,9 @@ class DebugViewModel(
PortNum.TRACEROUTE_APP.value -> decodeTraceroute(packet, payload)
- else -> payload.joinToString(" ") { it.toHex() }
+ else ->
+ MeshDiscoveryBeacon.decode(portnumValue, decoded.payload)?.toDebugString()
+ ?: payload.joinToString(" ") { it.toHex() }
}
} catch (e: Exception) {
"Failed to decode payload: ${e.message}"
diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt
index 4b9fdcdd1b..35b951099e 100644
--- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt
+++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt
@@ -49,6 +49,7 @@ import org.meshtastic.core.domain.usecase.settings.RadioResponseResult
import org.meshtastic.core.domain.usecase.settings.ToggleAnalyticsUseCase
import org.meshtastic.core.domain.usecase.settings.ToggleHomoglyphEncodingUseCase
import org.meshtastic.core.model.ConnectionState
+import org.meshtastic.core.model.MeshDiscoveryBeacon
import org.meshtastic.core.model.MqttConnectionState
import org.meshtastic.core.model.MqttProbeStatus
import org.meshtastic.core.model.MyNodeInfo
@@ -109,6 +110,7 @@ data class RadioConfigState(
val analyticsAvailable: Boolean = true,
val analyticsEnabled: Boolean = true,
val nodeDbResetPreserveFavorites: Boolean = false,
+ val meshDiscoveryBeacons: List = emptyList(),
)
@KoinViewModel
@@ -259,7 +261,12 @@ open class RadioConfigViewModel(
.onEach { manifest -> _radioConfigState.update { it.copy(fileManifest = manifest) } }
.launchIn(viewModelScope)
- serviceRepository.meshPacketFlow.onEach(::processPacketResponse).launchIn(viewModelScope)
+ serviceRepository.meshPacketFlow
+ .onEach {
+ processPacketResponse(it)
+ processMeshDiscoveryBeacon(it)
+ }
+ .launchIn(viewModelScope)
combine(serviceRepository.connectionState, radioConfigState) { connState, _ ->
_radioConfigState.update { it.copy(connected = connState == ConnectionState.Connected) }
@@ -776,4 +783,22 @@ open class RadioConfigViewModel(
}
}
}
+
+ private fun processMeshDiscoveryBeacon(packet: MeshPacket) {
+ val decoded = packet.decoded ?: return
+ val beacon = MeshDiscoveryBeacon.decode(decoded.portnum.value, decoded.payload) ?: return
+ _radioConfigState.update { state ->
+ val withoutPrevious =
+ state.meshDiscoveryBeacons.filterNot {
+ it.nodeId == beacon.nodeId &&
+ it.primaryChannelHash == beacon.primaryChannelHash &&
+ it.primaryChannelName == beacon.primaryChannelName
+ }
+ state.copy(meshDiscoveryBeacons = (listOf(beacon) + withoutPrevious).take(MAX_DISCOVERY_BEACONS))
+ }
+ }
+
+ companion object {
+ private const val MAX_DISCOVERY_BEACONS = 8
+ }
}
diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/LoRaConfigItemList.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/LoRaConfigItemList.kt
index ced63fff66..13a5c95be5 100644
--- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/LoRaConfigItemList.kt
+++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/LoRaConfigItemList.kt
@@ -27,8 +27,10 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalFocusManager
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.jetbrains.compose.resources.stringResource
+import org.meshtastic.core.common.util.NumberFormatter
import org.meshtastic.core.model.Channel
import org.meshtastic.core.model.ChannelOption
+import org.meshtastic.core.model.MeshDiscoveryBeacon
import org.meshtastic.core.model.RegionInfo
import org.meshtastic.core.model.numChannels
import org.meshtastic.core.resources.Res
@@ -43,6 +45,9 @@ import org.meshtastic.core.resources.frequency_slot
import org.meshtastic.core.resources.hop_limit
import org.meshtastic.core.resources.ignore_mqtt
import org.meshtastic.core.resources.lora
+import org.meshtastic.core.resources.mesh_discovery_beacon_summary
+import org.meshtastic.core.resources.mesh_discovery_beacons
+import org.meshtastic.core.resources.mesh_discovery_beacons_summary
import org.meshtastic.core.resources.modem_preset
import org.meshtastic.core.resources.ok_to_mqtt
import org.meshtastic.core.resources.options
@@ -57,6 +62,7 @@ import org.meshtastic.core.resources.tx_power_dbm
import org.meshtastic.core.resources.use_modem_preset
import org.meshtastic.core.ui.component.DropDownPreference
import org.meshtastic.core.ui.component.EditTextPreference
+import org.meshtastic.core.ui.component.ListItem
import org.meshtastic.core.ui.component.SignedIntegerEditTextPreference
import org.meshtastic.core.ui.component.SwitchPreference
import org.meshtastic.core.ui.component.TitledCard
@@ -89,6 +95,10 @@ fun LoRaConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) {
viewModel.setConfig(config)
},
) {
+ if (state.meshDiscoveryBeacons.isNotEmpty()) {
+ item { MeshDiscoveryBeaconCard(beacons = state.meshDiscoveryBeacons) }
+ }
+
item {
TitledCard(title = stringResource(Res.string.options)) {
DropDownPreference(
@@ -280,3 +290,29 @@ private fun ManualModemSettings(
)
}
}
+
+@Composable
+private fun MeshDiscoveryBeaconCard(beacons: List) {
+ TitledCard(title = stringResource(Res.string.mesh_discovery_beacons)) {
+ ListItem(text = stringResource(Res.string.mesh_discovery_beacons_summary), trailingIcon = null)
+ beacons.forEach { beacon ->
+ HorizontalDivider()
+ val frequency = NumberFormatter.format(beacon.frequencyMHz, 3)
+ ListItem(
+ text = beacon.primaryChannelName.ifBlank { beacon.nodeIdString },
+ supportingText =
+ stringResource(
+ Res.string.mesh_discovery_beacon_summary,
+ frequency,
+ beacon.bandwidth.label,
+ beacon.spreadingFactor,
+ beacon.codingRate,
+ beacon.forwardingHint.name,
+ beacon.nodeIdString,
+ ),
+ trailingIcon = null,
+ copyable = true,
+ )
+ }
+ }
+}