Desktop: always-visible work indication — indeterminate bars while running w/o pct, summarize job row, upload phase on library rows; dev-loop watcher fixes

This commit is contained in:
avi 2026-09-14 11:20:55 -05:00
commit 7b3436837a
3 changed files with 65 additions and 22 deletions

View file

@ -840,6 +840,10 @@ class DesktopState(private val appDir: File = defaultAppDir()) {
val ref = runCatching { val ref = runCatching {
provider.upload(draft) { p -> provider.upload(draft) { p ->
d.uploadProgress = p d.uploadProgress = p
// Row shows the upload too — pressing Transcribe must
// always change something on screen, immediately.
setLive(d.file.name, LiveProgress("uploading…", p))
rescanStatuses()
_detail.value = d.copy() _detail.value = d.copy()
} }
}.getOrElse { }.getOrElse {

View file

@ -196,12 +196,19 @@ fun LibraryScreen(state: DesktopState) {
else -> MaterialTheme.colorScheme.onSurfaceVariant else -> MaterialTheme.colorScheme.onSurfaceVariant
}, },
) )
if (e.progress != null) { if (e.status == FileStatus.RUNNING) {
Spacer(Modifier.height(6.dp)) Spacer(Modifier.height(6.dp))
LinearProgressIndicator( val frac = e.progress
progress = { e.progress }, if (frac != null) {
modifier = Modifier.fillMaxWidth().height(4.dp), LinearProgressIndicator(
) progress = { frac },
modifier = Modifier.fillMaxWidth().height(4.dp),
)
} else {
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth().height(4.dp),
)
}
} }
} }
if (e.status == FileStatus.NEW || e.status == FileStatus.FAILED) { if (e.status == FileStatus.NEW || e.status == FileStatus.FAILED) {
@ -318,14 +325,30 @@ fun DetailScreen(state: DesktopState) {
Text(label, style = MaterialTheme.typography.bodyMedium, Text(label, style = MaterialTheme.typography.bodyMedium,
color = if (it.status == "failed") MaterialTheme.colorScheme.error color = if (it.status == "failed") MaterialTheme.colorScheme.error
else MaterialTheme.colorScheme.primary) else MaterialTheme.colorScheme.primary)
if (running && it.progress != null) { if (running) {
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
LinearProgressIndicator( if (it.progress != null) {
progress = { it.progress / 100f }, LinearProgressIndicator(
modifier = Modifier.fillMaxWidth().height(4.dp), progress = { it.progress / 100f },
) modifier = Modifier.fillMaxWidth().height(4.dp),
)
} else {
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth().height(4.dp),
)
}
} }
} }
val sJob = detail.jobs.firstOrNull { it.jobType == "summarize" }
sJob?.takeIf { it.status == "running" || it.status == "queued" }?.let {
val label = if (it.status == "running") "Summarizing…" else "Summary queued"
Text(label, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary)
Spacer(Modifier.height(4.dp))
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth().height(4.dp),
)
}
detail.error?.let { detail.error?.let {
Text(it, color = MaterialTheme.colorScheme.error) Text(it, color = MaterialTheme.colorScheme.error)
if (tJob?.status == "failed") { if (tJob?.status == "failed") {

View file

@ -4,28 +4,43 @@
# (SHONAR_DEV=1 -> uvicorn --reload), so backend edits apply live too. # (SHONAR_DEV=1 -> uvicorn --reload), so backend edits apply live too.
# #
# Stop with Ctrl-C (or pkill -f desktop-dev.sh). # Stop with Ctrl-C (or pkill -f desktop-dev.sh).
set -euo pipefail set -uo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
export SHONAR_DEV=1 export SHONAR_DEV=1
export DISPLAY="${DISPLAY:-:0}" export DISPLAY="${DISPLAY:-:0}"
WATCH_DIRS=(
"app/src/main/kotlin"
"../../android/app/src/main/java/com/shonar"
)
FLAG="$(pwd)/.dev-restart-flag" FLAG="$(pwd)/.dev-restart-flag"
# Watch targets resolved to absolute paths, non-existent dirs skipped at
# start but retried each cycle (inotifywait -r tolerates a dir appearing).
watch_targets() {
local t=()
for d in "$PWD/app/src/main/kotlin" \
"$PWD/../android/app/src/main/java/com/shonar" \
"$PWD/../backend/shonar"; do
[ -d "$d" ] && t+=("$d")
done
printf '%s\n' "${t[@]}"
}
pkill -f "com.shonar.desktop.MainKt" 2>/dev/null || true pkill -f "com.shonar.desktop.MainKt" 2>/dev/null || true
rm -f "$FLAG" rm -f "$FLAG"
sleep 1 sleep 1
( (
while inotifywait -qq -e close_write,move \ while true; do
--include '\.kt$|\.kts$' "${WATCH_DIRS[@]}" 2>/dev/null; do mapfile -t TARGETS < <(watch_targets)
echo "[dev] source change -> restarting app…" [ ${#TARGETS[@]} -eq 0 ] && { sleep 5; continue; }
touch "$FLAG" if inotifywait -qq -e close_write,move --include '\.kt$|\.kts$' \
pkill -f "com.shonar.desktop.MainKt" 2>/dev/null || true -r "${TARGETS[@]}"; then
echo "[dev] source change -> restarting app…"
touch "$FLAG"
pkill -f "com.shonar.desktop.MainKt" 2>/dev/null || true
sleep 2 # debounce bursts (editor writes can fire several events)
else
sleep 5 # targets vanished; retry with a fresh list
fi
done done
) & ) &
WATCHER=$! WATCHER=$!
@ -39,6 +54,7 @@ while true; do
fi fi
rm -f "$FLAG" rm -f "$FLAG"
# App closed cleanly or build failed — wait for the next edit. # App closed cleanly or build failed — wait for the next edit.
# (Build failure: gradlew prints the error; the loop idles until you fix it.) mapfile -t TARGETS < <(watch_targets)
inotifywait -qq -e close_write,move --include '\.kt$|\.kts$' "${WATCH_DIRS[@]}" [ ${#TARGETS[@]} -eq 0 ] && { sleep 5; continue; }
inotifywait -qq -e close_write,move --include '\.kt$|\.kts$' -r "${TARGETS[@]}" || sleep 5
done done