Desktop: dev loop (desktop-dev.sh, auto-restart on source edits), SHONAR_DEV engine --reload, build stamp in window title, detail screen reloads report rewrites + surfaces fetch errors

This commit is contained in:
avi 2026-09-14 11:10:36 -05:00
commit 31e03262fd
3 changed files with 77 additions and 4 deletions

View file

@ -328,7 +328,12 @@ class DesktopState(private val appDir: File = defaultAppDir()) {
}
stopEngine()
apiProc = spawn(backend, File(venv, "uvicorn").absolutePath,
listOf("shonar.main:app", "--port", "8000"), env)
listOf("shonar.main:app", "--port", "8000") +
// Dev mode: engine hot-reloads on backend source edits.
(if (System.getenv("SHONAR_DEV") == "1")
listOf("--reload", "--reload-dir", "shonar")
else emptyList()),
env)
var up = false
var waited = 0.0
while (waited < 120.0) {
@ -746,9 +751,23 @@ class DesktopState(private val appDir: File = defaultAppDir()) {
// If the file is mid-pipeline (pump running, or reprocess started
// elsewhere), show live stage/progress instead of a stale report.
while (true) {
val jobs = runCatching { parseJobs(provider.fetchJobs(remoteId)) }
.getOrNull().orEmpty()
val fetched = runCatching { parseJobs(provider.fetchJobs(remoteId)) }
if (fetched.isFailure && d.jobs.isEmpty()) {
// Don't fail silently: a dead session used to render as
// an idle screen, indistinguishable from "never started".
d.error = "Couldn't read job status from the engine: " +
(fetched.exceptionOrNull()?.message ?: "unknown error")
} else if (fetched.isSuccess) {
d.error = null
}
val jobs = fetched.getOrNull().orEmpty()
if (_detail.value?.file != file) break
// Pick up report rewrites made by the pump/completion paths
// while this screen sits open (they write the file, not us).
if (d.transcript == null) {
val fresh = runCatching { reportFile(file).readText() }.getOrNull()
if (fresh != null && fresh != d.reportText) d.reportText = fresh
}
d.jobs = jobs
jobLabel(jobs)?.let { d.busy = it } ?: run { d.busy = null }
_detail.value = d.copy()

View file

@ -37,7 +37,17 @@ fun main() = application {
var zoom by remember { mutableStateOf(1f) }
Window(
onCloseRequest = ::exitApplication,
title = "SHONAR Desktop",
title = "SHONAR Desktop" + run {
// Build stamp so "which window is this?" is answerable at a glance.
val jar = java.io.File(
System.getProperty("java.class.path", "").split(":")
.firstOrNull { it.endsWith(".jar") } ?: "")
if (jar.exists()) {
val fmt = java.time.format.DateTimeFormatter.ofPattern("HH:mm")
.withZone(java.time.ZoneId.systemDefault())
" · build " + fmt.format(java.time.Instant.ofEpochMilli(jar.lastModified()))
} else ""
},
state = rememberWindowState(width = 1100.dp, height = 800.dp),
) {
val baseDensity = LocalDensity.current

44
desktop/desktop-dev.sh Executable file
View file

@ -0,0 +1,44 @@
#!/usr/bin/env bash
# SHONAR desktop dev loop: runs the app and auto-restarts it when any
# Kotlin/Gradle source changes (inotifywait). Engine runs in dev mode
# (SHONAR_DEV=1 -> uvicorn --reload), so backend edits apply live too.
#
# Stop with Ctrl-C (or pkill -f desktop-dev.sh).
set -euo pipefail
cd "$(dirname "$0")"
export SHONAR_DEV=1
export DISPLAY="${DISPLAY:-:0}"
WATCH_DIRS=(
"app/src/main/kotlin"
"../../android/app/src/main/java/com/shonar"
)
FLAG="$(pwd)/.dev-restart-flag"
pkill -f "com.shonar.desktop.MainKt" 2>/dev/null || true
rm -f "$FLAG"
sleep 1
(
while inotifywait -qq -e close_write,move \
--include '\.kt$|\.kts$' "${WATCH_DIRS[@]}" 2>/dev/null; do
echo "[dev] source change -> restarting app…"
touch "$FLAG"
pkill -f "com.shonar.desktop.MainKt" 2>/dev/null || true
done
) &
WATCHER=$!
trap 'kill $WATCHER 2>/dev/null || true; rm -f "$FLAG"' EXIT
while true; do
rm -f "$FLAG"
./gradlew run --offline
if [ -f "$FLAG" ]; then
continue # killed by a source edit: rebuild now
fi
rm -f "$FLAG"
# App closed cleanly or build failed — wait for the next edit.
# (Build failure: gradlew prints the error; the loop idles until you fix it.)
inotifywait -qq -e close_write,move --include '\.kt$|\.kts$' "${WATCH_DIRS[@]}"
done