Animate RX packet path on live map#191
Open
fromalexx wants to merge 1133 commits into
Open
Conversation
…-tooltip fix: channel-picker tooltip no longer shows phantom 'no audio input device' on KISS-only channels
Imports the design spec from docs/android-phase-4b-spec branch into this feature branch and adds a thin task-decomposition plan that maps each spec section to a discrete subagent task.
Add ptt_android_consts.rs (Rust) and PttMethodConsts.kt (Kotlin) as the single canonical PTT method integer mapping for the Android actuator path. Both declare the five PTT_METHOD_* constants (0–4) derived directly from Appendix B of the phase-4b design spec. The proto/platform.proto PttMethod enum already carries the correct values and is unchanged. T13 will add the cross-language sync integration test that reads all three sources and asserts they agree.
Move Logs out of the Settings group and into the main nav, placed directly below Actions, with a file-text icon matching the inline-SVG pattern used by the other main entries.
feat(web): promote Logs to a top-level nav tab
Adds graywolf-modem/src/android/upcall.rs with two pub(crate) helpers: jni_ptt_set(method, keyed) — invoke UsbPttCallback.pttSet(IZ)Z jni_tx_push_samples(buf) — invoke AudioTxCallback.pushSamples([SI)I Android path: attaches the calling thread to the JVM (re-entrant attach is cheap once the thread is attached), uses cached GlobalRef + JMethodID stored at install time, and handles missing-callback / JNI-failure without panicking — returns Err with a descriptive message. Host stub path (android-test-stub feature): replaces JNI with Mutex<Option<Box<dyn Fn>>> closures. Tests install a mock, call the helper, and assert on observed args + return value. 7 unit tests all pass. Two JNI export functions land in android/mod.rs: Java_com_nw5w_graywolf_jni_ModemBridge_installPttCallback Java_com_nw5w_graywolf_jni_ModemBridge_installAudioTxCallback These are the contract T5 (ModemBridge.kt) must match. The android-test-stub feature is declared in Cargo.toml [features] and pulled into lib.rs via #[path] so the stub unit tests compile on the host without an Android target or live JVM. PTT method constants from T1 (ptt_android_consts.rs, commit c858504) are available for T3 to import; this task does not re-import them as the JNI bridge only passes an opaque i32 through to Kotlin.
Code review of b923b37 surfaced four issues; this commit fixes all of them. 1. Serial test isolation: add serial_test = "3" dev-dependency and annotate every stub-mode test with #[serial] to eliminate intermittent failures from Cargo's parallel runner stomping on shared PTT_MOCK/AUDIO_TX_MOCK global state. 2. Unified re-export path: add pub(crate) use aliases in lib.rs so T3/T4 callers can write `use crate::{jni_ptt_set, jni_tx_push_samples}` once inside their own cfg block, regardless of whether the android target or android-test-stub host path is active. Also re-export the test hooks (install_ptt_mock, install_audio_tx_mock, clear_mocks) from crate:: so T3/T4 unit tests have a single import path. 3. Redundant inner cfg in android/mod.rs: the file is already gated #![cfg(target_os = "android")] at the top; the inner #[cfg(any(target_os = "android", feature = "android-test-stub"))] on pub mod upcall was unreachable for the feature branch. Simplified to a bare pub mod upcall; 4. Unused JValue import: removed JValue from the jni::objects import in android_impl; it was never used (call_method_unchecked takes raw jni::sys::jvalue unions, not JValue). Would have warned on Android builds. Also resolved Minor (5): keyed_jni now spelled as `let keyed_jni: jni::sys::jboolean = keyed as u8;` making the type explicit and dropping the opaque JNI_TRUE.min(1) expression.
…pcall Builds on T1 (ptt_android_consts) and T2 (JNI upcall infra, crate::jni_ptt_set). New `graywolf-modem/src/tx/ptt_android.rs` (cfg-gated to android or android-test-stub) implements the PttDriver trait by delegating to crate::jni_ptt_set(method, keyed). The `method` i32 flows through the new PttMethod::Android variant in build_driver; it is carried in ConfigurePtt.gpio_pin (field reuse, no proto change needed for T3 -- gpio_pin is unused in the Android code path for its original CM108-pin purpose, and adding a proto field would require coordinated changes across the Go and Kotlin stacks). build_driver validates the int against the four Appendix-B constants (CP2102N_RTS=1, CM108_HID=2, AIOC_CDC_DTR=3, VOX=4) and returns Err for PTT_METHOD_UNKNOWN (0) or any other value. 14 new tests cover: key/unkey callback dispatch, error propagation with context (method int in message), all four valid method ints, all four build_driver success paths, unknown-int rejection, and UNKNOWN(0) rejection. Tests are serialized via serial_test to share global mock state safely with T2's tests.
…se arm - Update stale T3/T4 pending comments in upcall.rs: T3 now calls jni_ptt_set, only T4 (jni_tx_push_samples) remains pending - Narrow #[allow(dead_code)] and fix comment on pub(crate) re-export to reflect actual call status - Add assertion in parse_recognizes_known_method_strings_and_returns_none_for_unknown test to guarantee desktop build coverage of PttMethod::Android parse arm Test results: - cargo test --lib (no features): 157 passed + 1 pre-existing failure ✓ - cargo test --features android-test-stub --lib: 173 passed + 1 pre-existing failure ✓
…call Adds `tx_emit_samples(buf: &[i16]) -> Result<usize, String>` in a new `android/audio_tx.rs` module. The function drives the Rust modem TX governor → Kotlin AudioTxPump path introduced in T2's upcall infra (jni_tx_push_samples). Return convention mirrors AudioTrack.write: a non-negative int is the bytes-or-samples written (short writes returned as Ok(n) for the caller to detect); a negative int — ERROR=-1, ERROR_BAD_VALUE=-2, ERROR_INVALID_OPERATION=-3, ERROR_DEAD_OBJECT=-6 — becomes Err with the code and input length included for log attribution. Empty buffers short-circuit before the JNI call. audio_tx.rs is gated any(target_os="android", feature="android-test-stub") so the 5 stub-mode unit tests run on the host, satisfying the same pattern T3 used for ptt_android.rs. audio.rs (RX path) retains its android-only gate since it depends on config_state and other JNI infrastructure. lib.rs wires audio_tx.rs in directly for the host stub path (same technique as upcall.rs), which eliminates the dead-code warning on jni_tx_push_samples that T2 left behind pending T4. The Kotlin side (AudioTxPump, pushSamples signature) is T6; the JNI exports that install the callback are T5.
…installers Add UsbPttCallback and AudioTxCallback interfaces to ModemBridge.kt, along with installPttCallback and installAudioTxCallback external functions. These match the JNI exports already shipped in T2 commit 173a37b (graywolf-modem/src/android/mod.rs). Interface signatures: - UsbPttCallback.pttSet(method: Int, keyed: Boolean): Boolean (JNI: IZ)Z) - AudioTxCallback.pushSamples(samples: ShortArray, count: Int): Int (JNI: ([SI)I) Both interfaces are top-level types in the same file so they can be implemented by UsbPttAdapter (T7) and AudioTxPump (T6) respectively. AudioTxCallback.pushSamples returns bytes written per AudioTrack.write convention. Refs spec §3.2 (AudioTxPump.pushSamples) and §3.4 (installPttCallback snippet).
Implements AudioTxCallback (T5) so the Rust modem TX governor can push PCM samples through an AudioTrack(MODE_STREAM) to the first available USB audio output. Spec §3.2 + §8 Q2. Key points: - Mirrors AudioPump (RX) shape: 22050 Hz, PCM16, CHANNEL_OUT_MONO, 4× min buffer - Auto-routes to TYPE_USB_DEVICE output in start(); falls back to system default with a WARN log if no USB audio dongle is present - AudioManager.AudioDeviceCallback registered in start() / unregistered in stop() handles hot-swap: rebinds setPreferredDevice on USB add; falls back to null (system default) on removal of the currently routed device - start() and stop() are idempotent; pushSamples() returns -1 when stopped - trackFactory seam (nullable, default null) keeps production code clean while allowing hermetic JVM unit tests via Mockito without Robolectric Tests (AudioTxPumpTest, src/test/): - No USB output → system default path, setPreferredDevice never called - USB output present → setPreferredDevice called with TYPE_USB_DEVICE info - pushSamples after stop() returns -1 (no crash) - start() idempotent → single track created on double-call
…GS type Manifest changes per spec §3.6 + Appendix A: - Uncomment FOREGROUND_SERVICE_CONNECTED_DEVICE permission (phase 4b, not 5) - Add USB_DEVICE_ATTACHED intent-filter to MainActivity - Point intent-filter to curated device_filter.xml (15-entry VID/PID + CDC-ACM catch-all) - Expand Service foregroundServiceType bitmap to include mediaPlayback + connectedDevice (gating at runtime per device permission is T10's job, not T8) All XML validates with xmllint. Ready for T9 WebAppInterface + T10 lifecycle.
…tCallback Extend object UsbPttAdapter to implement UsbPttCallback (T5/ModemBridge.kt) and add the pttSet(method, keyed) dispatcher. Dispatch table: PTT_METHOD_CP2102N_RTS (1) → setRts(keyed) PTT_METHOD_AIOC_CDC_DTR (3) → setAiocRts(keyed) PTT_METHOD_CM108_HID (2) → setHidGpio(keyed) PTT_METHOD_VOX (4) → true (audio path drives PTT, no wire) unknown → Log.w + false AIOC path delegates to setAiocRts, NOT setDtr directly. setAiocRts holds RTS=0 in both key/unkey states and drives DTR=keyed, matching AIOC firmware >=1.2.0 (feedback_aioc_ptt_cdc_acm_dtr). Method int constants come from PttMethodConsts (T1). JNI installation of UsbPttAdapter as the callback is T10's responsibility. Adds UsbPttAdapterTest covering all five dispatch cases: CP2102N, AIOC, CM108 (with HID report byte verification), VOX, and unknown.
Implements spec §3.6. WebAppInterface gains two new @JavascriptInterface
methods:
listUsbDevices() — delegates to UsbPttAdapter.enumerateForJs();
returns a JSON array of all attached USB
devices with vid, pid, name, role, and
permission_granted fields.
requestUsbPermission(vid, pid, callbackId)
— delegates to UsbPttAdapter.requestPermissionFor();
fires window.__usbResult(callbackId, granted)
via WebView.post / evaluateJavascript once
the system dialog resolves (or immediately if
no matching device is attached).
UsbPttAdapter gains two new public methods (enumerateForJs, requestPermissionFor)
plus a ConcurrentHashMap (pendingPermissionCallbacks) that the existing
permissionReceiver now consumes after tryOpen so each one-shot callback fires
exactly once on grant or deny.
MainActivity wired to pass the WebView instance to the updated
WebAppInterface constructor.
Tests: WebAppInterfaceTest updated + UsbPttAdapterEnumerateForJsTest added
(Mockito 5 inline; reflection seam for singleton usbManager injection).
T10 will own GraywolfService lifecycle wiring.
…Service lifecycle Connects the pieces built in T5 (ModemBridge JNI), T6 (AudioTxPump), T7 (UsbPttAdapter.pttSet), and T9 (WebAppInterface USB bridge) into GraywolfService's onCreate/onDestroy so they actually run on Service boot. Per spec §3.1: - Install JNI callbacks (installPttCallback, installAudioTxCallback) after modemVersion() triggers loadLibrary, but before bootModem(), so any TX/PTT signal the modem fires on boot finds a registered callback. - Start AudioTxPump, call UsbPttAdapter.init + enumerate after startForeground returns, before bootModem(). - Reverse on onDestroy: audioTxPump.stop + UsbPttAdapter.closeAll between audioPump.stop and platformServer.stop. Per spec §3.6 (CONNECTED_DEVICE FGS runtime gate): - MEDIA_PLAYBACK added unconditionally (no runtime perm required). - CONNECTED_DEVICE added only when at least one USB device already has OS permission at Service start time; probed via UsbManager directly since UsbPttAdapter is not yet init'd at that point. Absence is logged; the bit can only be picked up on the next Service start (Android prohibits expanding FGS type while running). MainActivity.kt and GraywolfApp.kt required no edits: no prior UsbPttAdapter calls existed in those files to remove or adjust.
Implements T11 of the Android Phase 4b plan.
## What was built
- proto/graywolf.proto: add ManualPtt message (field tag 21) to the
IpcMessage oneof. Regenerated pkg/ipcproto/graywolf.pb.go via
`make proto` (protoc-gen-go v1.36.11 / protoc v7.34.1).
- graywolf-modem/src/modem/tx_worker.rs: add TxMessage::ManualKey{channel,
keyed} variant; worker loop arm looks up the driver and calls key/unkey,
logging every attempt. Add TxWorker::manual_key() public method.
- graywolf-modem/src/modem/mod.rs: dispatch Payload::ManualPtt(mp) in
handle_ipc() by calling tx_worker.manual_key(mp.channel, mp.keyed).
- pkg/modembridge/ptt_watchdog.go: per-channel timer that auto-unkeys
after the configured timeout (10s in production). onKey resets the
timer on each heartbeat; onUnkey cancels it; timerFire logs and calls
the unkey closure.
- pkg/modembridge/bridge.go: wire pttWatchdog into Bridge.New; add
ManualPtt (raw IPC, watchdog-free, for tests) and ManualPttWithWatchdog
(REST-facing); add InjectSendFnForTest helper.
- pkg/webapi/channels.go: register POST /api/channels/{id}/ptt; handler
calls bridge.ManualPttWithWatchdog, returns 204 on success.
## Tests
- pkg/modembridge/ptt_watchdog_test.go: four table tests covering timeout
fire, heartbeat reset, explicit unkey cancel, and per-channel isolation.
- pkg/webapi/channels_ptt_test.go: REST tests for happy path (IPC message
captured via InjectSendFnForTest), nil-bridge 503, bad-id 400.
- graywolf-modem/src/modem/tx_worker.rs: manual_key_routes_to_registered_driver
registers a MockPtt, sends ManualKey(true) then ManualKey(false), asserts
Key+Unkey in the mock log.
## Spec deviation (documented)
Spec §3.5 says "Rust modem has a watchdog timeout (~10s)." The watchdog
is placed in Go instead:
- Single-language ownership: one process owns the timer; no cross-language
timer coordination, no prost clock API needed in the modem crate.
- Simpler restart story: the Go watchdog survives a modem child crash+restart
without any state handshake.
- Functionally equivalent: a stuck PTT is always resolved within 10s
regardless of where the timer lives.
Spec refs: §3.5 (request flow), §3.7 (heartbeat semantics), §6.3 (Go tests).
…atus
Implements T12 per spec §3.7, wired to the T11 REST endpoint and T9 USB
JS bridge.
Changes:
- web/src/lib/api.js: add postChannelPtt(channelId, keyed) helper that
POSTs to /api/channels/{id}/ptt; add matching mock branch.
- web/src/routes/Channels.svelte: Android-gated section (Platform.kind
=== 'android') inside the channel edit/create modal:
* PTT method dropdown: CP2102N RTS / CDC-ACM DTR / CM108 HID / VOX,
bound to androidPttMethod (int 1-4 per Appendix B constants).
* Test PTT press-and-hold button: pointerdown keys, pointerup/cancel/
leave unkeys; 2-second heartbeat interval keeps Go watchdog alive.
Interval is cleared on unmount via onDestroy.
* USB hardware status row: polls listUsbDevices() every 2 s, matches
device by role string, shows "Grant access" button when permission
not yet granted; requestUsbPermission dispatches through
window.__usbResult/window.__usbCallbacks bridge.
* Audio routing row: hard-coded "USB audio (auto)" pending a future
status-endpoint ticket.
TX timing fields (TX Delay, Tail, Slot, Persist, Full Duplex) were
already unconditionally visible; no gate was added or removed.
- web/src/routes/Channels.android.ptt.test.js: 9 node:test unit tests
covering postChannelPtt wire shape, press-and-hold heartbeat dispatch
(mock timers), and USB device role matching.
Data-shape decision: androidPttMethod is stored via POST /api/ptt with
method='android' and gpio_pin=<method_int 1-4>. The PttConfig.gpio_pin
column carries the method int as a carrier field (different semantics
from the desktop CM108 use, but zero new schema columns required). The
Go modembridge reads this to configure UsbPttAdapter dispatch.
…ount Math.random() can return 0 (slice(2) of '0' is ''), which the T9 Kotlin validator rejects — prefix with 'cb' to guarantee a non-empty alphanumeric id. Also remove globalThis.__usbResult / __usbCallbacks on component unmount so a late grant callback can't fire into a dead component.
Spec Appendix B calls out the drift hazard: PTT method enum must remain in sync across three sources (proto definition, Rust constants, Kotlin constants). This integration test parses each source file and asserts that all three produce the same int→name mapping at runtime. The Go test runner (via proto reflection) is the canonical baseline; Rust and Kotlin sources are parsed with regexes. Test failure lists exactly which language disagrees on which value, making drift diagnosis trivial.
…roidTxSink Final code review (C2 finding) caught that process_job always routed TX through soundcard::spawn_output → AudioSink::submit. On Android, cpal cannot reach the AudioTrack instance that Kotlin's AudioTxPump holds open and setPreferredDevice-routes to the USB OTG dongle. Result: PTT keys correctly, radio transmits silence, beacon never decodes. This commit is the connector the rest of phase 4b assumed existed but never grew: - Add AndroidTxSink in android/audio_tx.rs implementing crate::modem::TxSink. submit() calls tx_emit_samples (blocking JNI upcall into AudioTrack.write WRITE_BLOCKING), then accumulates drained_samples so drive_tx_cycle's drain loop exits immediately on first check. - Promote TxSink from pub(super) to pub(crate); re-export from crate::modem under the android cfg gate so the android module can implement the trait without exposing the private tx_worker module path. - cfg-split process_job: Android arm constructs AndroidTxSink on the stack and drives drive_tx_cycle directly; non-Android arm is the existing cpal lazy-sink path, unchanged. sinks/pending_devices maps are untouched on Android; a submit error logs but does not attempt sink rebuild (Kotlin manages AudioTrack lifetime independently). - Two new serial tests under android-test-stub: submit forwards buffer + accumulates drained_samples; submit error leaves drained_samples at zero.
… validation, lock scope, watchdog reset
C1: enumerateForJs now emits vid/pid as decimal Ints (for the JS bridge call) plus
vid_hex/pid_hex hex strings (for display). SPA requestGrant updated to read
usbDevice.vid / usbDevice.pid. JS test fixtures migrated from vendor_id/product_id
to the new schema. Kotlin test updated to assert decimal getInt("vid") and hex
getString("vid_hex"). Fixes silent undefined args passed to requestUsbPermission().
I1: PttRequest.Validate now rejects method=android unless gpio_pin is in 1..4
(spec Appendix B: 1=CP2102N_RTS, 2=CM108_HID, 3=AIOC_CDC_DTR, 4=VOX). New
pkg/webapi/dto/ptt_test.go covers all four valid pins plus the 0 and 99 rejection
cases. Deferred: non-Android client setting method=android requires platform-state
plumbing out of scope for this sweep.
I2: jni_ptt_set and jni_tx_push_samples in upcall.rs now clone the GlobalRef and
copy the JMethodID (both are inexpensive — GlobalRef::clone, JMethodID is Copy)
under the Mutex lock and drop the lock guard before attaching the JVM thread and
issuing the call. Prevents a potential deadlock if a future re-entrant upcall path
is added through the Kotlin Log/install-replacement channel.
I3: pttWatchdog gains cancelAll() which stops and removes all active timers.
Bridge.supervise calls cancelAll() at the top of each supervision cycle alongside
the existing dispatcher/status resets. Prevents a stale timer from a mid-press
modem crash firing an auto-unkey into a freshly-spawned child with no keyed state.
New TestPttWatchdog_CancelAll verifies no unkey fires after cancelAll.
…arget Cross-compile via cargo ndk surfaced two issues that only manifest on target_os=android (not on the host stub): - pub(super) install_ptt / install_audio_tx can't be re-exported as pub(crate) at the crate root; widen the originals to pub(crate). - jni 0.21 get_method_id takes a JClass, not a JObject; resolve the class via get_object_class first. Also cfg-gate unused imports (Entry, soundcard::self) and the unused sinks/pending_devices process_job parameters on android, since the android TxSink arm doesn't touch them.
…under AudioManager)
Android 14+ requires the matching FOREGROUND_SERVICE_<TYPE> permission to be declared for every type used in startForeground. T10 added the MEDIA_PLAYBACK bit to the FGS bitmap but the perm itself wasn't on the manifest; live-launch surfaced SecurityException.
The promote-to-closed job hardcoded track: graywolf-beta, which is not a real Play track -- Play track IDs are fixed (internal, alpha, beta, production) and the promote failed with 'Track graywolf-beta could not be found'. A private 15-person beta is closed testing = the alpha track (beta is open testing). Replace the hardcode with a workflow_dispatch 'track' choice input (alpha|beta|internal, default alpha) so the operator picks the target at run time: gh workflow run android.yml --field version=0.13.8 --field track=alpha
…ck-input ci(android): make promote track a dispatch input (fix bad track id)
The r0adkll promote re-uploaded the AAB, which Play rejects once the
versionCode exists ('version code 130800 has already been used'). To
move an existing build between tracks you PROMOTE it, not re-upload.
fastlane integration:
- Gemfile + fastlane/{Appfile,Fastfile}. Two lanes:
- promote: supply with track_promote_to (no re-upload), params
version_code/from/to. Auth via SUPPLY_JSON_KEY (file, local) or
SUPPLY_JSON_KEY_DATA (raw JSON, CI from the GH secret).
- upload_listing: supply images-only (icon, feature graphic, phone +
tablet screenshots) from fastlane/metadata.
- Workflow promote-to-closed: drop the AAB download + r0adkll; set up
Ruby, derive versionCode from X.Y.Z, run fastlane promote to the
chosen track.
- run.sh stages generated assets into fastlane/metadata/.../images/
(icon, featureGraphic, phoneScreenshots, sevenInchScreenshots);
PNGs gitignored, dir structure kept via .gitkeep.
- Makefile: android-promote (VC/TO/JSON) + android-upload-listing (JSON).
Closes the 'screenshots should upload too' ask: make android-screenshots
generates+stages, make android-upload-listing pushes them to Play.
TestConcurrentChainsShareThrottleAndStayBounded asserted both ptt+kiss components survive in the eviction ring after a 2000-write concurrent burst -- but evict() keeps the globally-newest RingSize rows by id, so whichever chain's goroutines committed last can own the entire tail. A legal scheduling outcome, not starvation; the assertion flaked on slow CI runners (saw 'distinct components = 1, want 2'). Fix: after the burst's bound check (which is the real shared-throttle proof), write a short deterministic interleaved tail (20x chainA+chainB, < RingSize) so both components are guaranteed in the survivor window, then assert routing. Passes 6/6 locally.
ci(android): fastlane for track promotion + listing upload
…-test test(logbuffer): de-flake concurrent-chains component assertion
The fastlane-based promote failed with 'Could not locate Gemfile' -- the promote-to-closed job had no Checkout step (the old r0adkll promote didn't need the repo; the fastlane version needs the Gemfile + fastlane/Fastfile). Add actions/checkout before the Ruby setup so bundler-cache and 'bundle exec fastlane' can find them.
ci(android): checkout repo in promote job (fastlane needs Gemfile)
Swiping the app from recents tears down the backend, which releases the radio's USB interfaces; they re-enumerate ~2s later and -- because MainActivity declares a USB_DEVICE_ATTACHED intent-filter -- Android auto-relaunches the app, reviving the station the operator just dismissed. onTaskRemoved now records a deliberate-stop timestamp in graywolf-prefs; MainActivity.onCreate finish()es immediately when launched by USB_DEVICE_ATTACHED within a 15s window of that stop. Launcher taps and genuine re-plugs after the window are unaffected. Guard onDestroy's webView teardown for the early-finish path (lateinit not yet set).
…shutdown Android: guaranteed clean shutdown on swipe and hard-kill
* fix(android): retry platformsvc bind instead of crashing on contention * fix(android): stop duplicate service cleanly on platformsvc contention; expose socket name * feat(android): gate launch on previous instance exiting, with a waiting screen * docs(wiki): record single-instance serialized-startup invariant * fix(android): run blocking audio/USB init off the main thread in onCreate AudioTrack construction + setPreferredDevice and USB device opens are synchronous HAL/binder calls that block for seconds when a USB audio dongle is wedged, ANR'ing GraywolfService.onCreate within 5s. Move txPump.start() and UsbPttAdapter.enumerate() to a worker thread; keep the cheap UsbPttAdapter.init() on the main thread. onDestroy joins the worker (bounded) before tearing the same resources down. * docs(wiki): record off-main-thread audio/USB startup invariant
… is eligible (chrissnell#183) The PTT page rendered detected hardware as clickable cards even when no channel could accept a PttConfig — zero channels, only KISS-TNC channels (input_device_id == null), or every modem-backed channel already configured. Clicking only fired a transient error toast, so the card appeared dead. Add pttDetectionBlockedReason() to classify why configuration is blocked (no-modem-channel / all-configured) and use it to disable the device cards and show an inline notice pointing the operator to the Channels page or to edit an existing config.
) The Enable toggle was a bound form field that only persisted on Save, so flipping it and switching tabs discarded the change (onMount reloads enabled=false). Wire the toggle's onCheckedChange to persist immediately, saving only the enabled bit merged onto the last-saved snapshot so unsaved edits to sibling fields are not committed. Preserve existing guards: the station-callsign-missing handler still blocks the flip before onCheckedChange fires, and iGate refuses an auto-enable onto a non-TX-capable channel (mirroring the disabled-Save behavior). Revert the toggle and toast on API error.
…s permanently deaf) (chrissnell#185) * feat(android): add RestartPolicy with capped-backoff degraded mode * feat(android): supervisor retries in degraded mode instead of halting * feat(android): notify operator when modem enters degraded restart mode * feat(modem): add stop-aware IpcServer::accept_interruptible * fix(modem): re-accept Go client on IPC send error instead of going deaf * feat(modem): log when decoded frames are dropped with no IPC client * docs(wiki): record Android RX IPC resilience + no-halt supervisor invariant * fix(modem): resolve clippy type_complexity in ipc server Factor the repeated (IpcHandle, Receiver<IpcInbound>, JoinHandle) tuple into an AcceptedClient type alias used by accept, finish_accept, and accept_interruptible. Fixes the -D warnings clippy failure.
* proto: add USB serial kind, baud, and available-USB-serial-devices messages * platformsvc: bump wire schema to 3 for USB serial * platformsvc: rename bt-handle multiplex machinery to transport-neutral serial* * platformsvc: extract shared serial stream; slim btserial.go to BT surface * platformsvc: add UsbSerialOpen and AvailableUsbSerialDevices * platformsvc: USB serial round-trip, EOF, typed-error, denied-ack tests * platformsvc: finish bt->serial comment rename in btserial_test.go * configstore: add usbserial KISS interface type * dto: accept usbserial KISS type (device + baud required) * app: route vid:pid KISS device through UsbSerialOpen on Android * app: start usbserial KISS interfaces via the serial supervisor * webapi: GET /api/kiss/available-usb-serial-devices (501 off-Android) * app: wire USB serial device source into webapi (Android) * webapi+web: regenerate swagger and add kissUsb.availableDevices client * android: add UsbDeviceArbiter for USB device ownership claims * android: gate UsbPttAdapter.tryOpen on UsbDeviceArbiter; expose evictDevice * android: revert local-only test workaround in UsbPttAdapterEnumerateForJsTest This file does not compile on the local Mac Kotlin/AGP toolchain (HashMap type inference) but CI's toolchain tolerates the original; keep the PR scoped to the USB-serial feature and out of pre-existing test debt. * android: add UsbSerialFacade (mik3y prober) with arbiter claim + PTT evict * android: add UsbSerialAdapter (USB serial relay over platform UDS) * android: UsbSerialAdapter tests (enumerate, open errors, detach) * android: PlatformServer routes serial frames by kind (BT/USB) * android: wire UsbSerialAdapter + USB detach receiver into GraywolfService * web: add USB Serial KISS interface type, picker, baud, and grant CTA * docs(handbook): USB serial KISS TNC operator page * wiki: document USB serial KISS path, files, and PTT/KISS ownership invariant * docs: link kiss-usb-serial from sibling handbook pages; fix UsbDeviceArbiter code-map note * web: regenerate OpenAPI TypeScript client for USB serial endpoint
…acon) (chrissnell#184) * commit * commit * Redesign map context menu: coord header + compact actions Show the clicked coordinate once in a muted header instead of repeating it inside every label, then group the actions: primary 'Add fixed beacon here' (amber pin icon) above a divider, then a copy group with short labels and a copy icon. Copy grid shows the locator as a right-aligned hint. Narrows the menu and gives it clear visual hierarchy. Also fix the hover background, which used a white-alpha fallback that was nearly invisible on the light theme surface; tint toward --color-text via color-mix so hover feedback shows in both themes. The component gains header, per-item icon/hint/primary, and {divider} support while staying presentational; the parent still owns the model. * Seed context-menu clamp state with 0, not the prop Initializing the adjusted x/y $state from the x/y props was a non-reactive capture (svelte-check state_referenced_locally). The positioning $effect already overwrites both before paint, so seed with 0 to silence the warning; behavior is unchanged.
The delete confirm input lacked autocapitalize/spellcheck overrides, so mobile keyboards capitalized the typed name and the exact-match gate never passed -- the red Delete button stayed disabled and deletion did nothing on Android. Replace the typed-name gate with a single confirm button on both the unreferenced and referenced (cascade) paths; the referrer impact list remains the confirmation surface for cascades.
Owner
|
I need to give this a shot to see if it works well. Thanks for the contribution! |
bdc4d52 to
c6b4bbb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
pkg/webapi/stations.goalready exposespath,path_positions, andviaper fix. The polling data store gains asubscribeNewFix(cb)hook that fires once per genuinely new RX fix during delta polling (initial snapshot is skipped). TX (own beacons) and IS (iGated) packets are intentionally not animated.Disclosure
This was written by AI. Feel free to adjust or reject!