diff --git a/api-base/src/jvmMain/kotlin/com/lhwdev/fetch/fetch.kt b/api-base/src/jvmMain/kotlin/com/lhwdev/fetch/fetch.kt
index 7f3e86fd..4a40da40 100644
--- a/api-base/src/jvmMain/kotlin/com/lhwdev/fetch/fetch.kt
+++ b/api-base/src/jvmMain/kotlin/com/lhwdev/fetch/fetch.kt
@@ -46,6 +46,8 @@ interface FetchResult {
 	val responseCodeMessage: String
 	val rawResponse: InputStream
 	
+	fun getHeader(key: String): String
+	
 	fun close()
 }
 
diff --git a/api-base/src/jvmMain/kotlin/com/lhwdev/fetch/http/httpFetch.kt b/api-base/src/jvmMain/kotlin/com/lhwdev/fetch/http/httpFetch.kt
index 2204e20d..faef886a 100644
--- a/api-base/src/jvmMain/kotlin/com/lhwdev/fetch/http/httpFetch.kt
+++ b/api-base/src/jvmMain/kotlin/com/lhwdev/fetch/http/httpFetch.kt
@@ -3,6 +3,7 @@ package com.lhwdev.fetch.http
 
 import com.lhwdev.fetch.*
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runInterruptible
 import kotlinx.coroutines.withContext
 import java.io.*
 import java.net.HttpURLConnection
@@ -34,7 +35,7 @@ fun interface HttpInterceptor : FetchInterceptor {
 }
 
 val HttpInterceptorImpl: HttpInterceptor = HttpInterceptor { url, method, headers, session, body ->
-	if(sDebugFetch) {
+	if(sDebugFetch) runInterruptible {
 		println("")
 		
 		val lastCookieManager = threadLocalCookieHandler
@@ -141,7 +142,7 @@ val HttpInterceptorImpl: HttpInterceptor = HttpInterceptor { url, method, header
 		} finally {
 			if(session != null) setThreadLocalCookieHandler(lastCookieManager)
 		}
-	} else { // without debug logging
+	} else runInterruptible { // without debug logging
 		val lastCookieManager = threadLocalCookieHandler
 		if(session != null) setThreadLocalCookieHandler(session.cookieManager)
 		
@@ -201,6 +202,8 @@ private class HttpResultImpl(val connection: HttpURLConnection) :
 			return connection.inputStream
 		}
 	
+	override fun getHeader(key: String): String = connection.getHeaderField(key)
+	
 	override fun close() {
 		connection.disconnect()
 	}
diff --git a/api/src/main/kotlin/com/lhwdev/selfTestMacro/api/getUserGroup.kt b/api/src/main/kotlin/com/lhwdev/selfTestMacro/api/getUserGroup.kt
index abc7de87..dd459e6b 100644
--- a/api/src/main/kotlin/com/lhwdev/selfTestMacro/api/getUserGroup.kt
+++ b/api/src/main/kotlin/com/lhwdev/selfTestMacro/api/getUserGroup.kt
@@ -21,10 +21,18 @@ data class User(
 	@SerialName("token") val token: UserToken
 )
 
+// a temporary hack to pass clientVersion where proper api design is not done
+class UserGroup(val users: List<User>, val clientVersion: String) : List<User> by users
 
-suspend fun Session.getUserGroup(institute: InstituteInfo, token: UsersToken): List<User> = fetch(
+
+suspend fun Session.getUserGroup(institute: InstituteInfo, token: UsersToken): UserGroup = fetch(
 	institute.requestUrl["selectUserGroup"],
 	method = HttpMethod.post,
 	headers = sDefaultFakeHeader + mapOf("Content-Type" to ContentTypes.json, "Authorization" to token.token),
 	body = "{}"
-).toJsonLoose(ListSerializer(User.serializer()))
+).let {
+	UserGroup(
+		users = it.toJsonLoose(ListSerializer(User.serializer())),
+		clientVersion = it.getHeader("X-Client-Version")
+	)
+}
diff --git a/app/src/main/java/com/lhwdev/selfTestMacro/MainActivity.kt b/app/src/main/java/com/lhwdev/selfTestMacro/MainActivity.kt
index bb623630..94daec8a 100644
--- a/app/src/main/java/com/lhwdev/selfTestMacro/MainActivity.kt
+++ b/app/src/main/java/com/lhwdev/selfTestMacro/MainActivity.kt
@@ -110,7 +110,7 @@ class MainActivity : AppCompatActivity() {
 			} else {
 				time.text = "시간 예약 안 됨"
 			}
-			updateTime(intent, reset = true)
+			updateTime(intent)
 		}
 		
 		fun pickTime() {
diff --git a/app/src/main/java/com/lhwdev/selfTestMacro/selfTestUtils.kt b/app/src/main/java/com/lhwdev/selfTestMacro/selfTestUtils.kt
index 1c03d20b..b101aa06 100644
--- a/app/src/main/java/com/lhwdev/selfTestMacro/selfTestUtils.kt
+++ b/app/src/main/java/com/lhwdev/selfTestMacro/selfTestUtils.kt
@@ -15,7 +15,6 @@ import net.gotev.cookiestore.InMemoryCookieStore
 import java.net.CookieManager
 import java.net.CookiePolicy
 import java.util.Calendar
-import java.util.Date
 import kotlin.random.Random
 
 
@@ -40,7 +39,7 @@ suspend fun Context.singleOfUserGroup(list: List<User>) = if(list.size == 1) lis
 	null
 }
 
-suspend fun Context.surveyData(user: User, usersIdentifier: UserIdentifier): SurveyData {
+suspend fun Context.surveyData(user: User, usersIdentifier: UserIdentifier, clientVersion: String): SurveyData {
 	val pref = preferenceState
 	
 	val quickTestNegative = pref.quickTest?.let {
@@ -53,8 +52,6 @@ suspend fun Context.surveyData(user: User, usersIdentifier: UserIdentifier): Sur
 	} ?: false
 	val isIsolated = pref.isIsolated
 	
-	val clientVersion = pref.appMeta().hcsVersion
-	
 	return SurveyData(
 		userToken = user.token,
 		upperUserName = usersIdentifier.mainUserName,
@@ -93,7 +90,7 @@ suspend fun Context.submitSuspend(session: Session, notification: Boolean = true
 			
 			val users = session.getUserGroup(institute, usersToken)
 			val user = singleOfUserGroup(users) ?: return@trial
-			val surveyData = surveyData(user, usersIdentifier)
+			val surveyData = surveyData(user, usersIdentifier, users.clientVersion)
 			
 			val result = session.registerSurvey(
 				pref.institute!!,
@@ -118,7 +115,7 @@ suspend fun Context.submitSuspend(session: Session, notification: Boolean = true
 	}
 }
 
-fun Context.updateTime(intent: PendingIntent, reset: Boolean = false) {
+fun Context.updateTime(intent: PendingIntent) {
 	val preferenceState = preferenceState
 	val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
 	if(Build.VERSION.SDK_INT >= 21)
@@ -132,13 +129,9 @@ fun Context.updateTime(intent: PendingIntent, reset: Boolean = false) {
 			isRandom = preferenceState.isRandomEnabled,
 			nextDay = false
 		)
-		
-		if(reset) preferenceState.lastSubmit = -1
 	}
 }
 
-private val random = Random
-
 fun millisToDaysCumulative(millis: Long) =
 	// ms     sec   min hour day
 	millis / 1000 / 60 / 60 / 24
@@ -152,67 +145,61 @@ fun Context.scheduleNextAlarm(
 	nextDay: Boolean
 ) {
 	val pref = preferenceState
-	val currentTime: Long
+	val now = Calendar.getInstance()
 	
-	var newTime = Calendar.getInstance().run {
-		currentTime = timeInMillis
-		
-		val new = clone() as Calendar
-		
-		// Submitted today
-		val last = pref.lastSubmit
-		val lastDay = millisToDaysCumulative(last)
-		
-		val targetMin = hour * 60 + min
-		val currentMin = this[Calendar.HOUR_OF_DAY] * 60 + this[Calendar.MINUTE]
-		
-		// update date
-		val quick = pref.quickTest
-		
-		if(quick?.behavior == QuickTestInfo.Behavior.doNotSubmit && quick.days.size >= 7) {
-			// refuse to schedule
-			return
-		}
-		
-		val newDay = millisToDaysCumulative(new.timeInMillis)
-		if(nextDay || lastDay == newDay) {
-			new.add(Calendar.DAY_OF_YEAR, 1)
-		}
+	val new = now.clone() as Calendar
+	
+	// Submitted today
+	val last = pref.lastSubmit
+	val lastDay = millisToDaysCumulative(last)
+	
+	// update date
+	val quick = pref.quickTest
+	
+	if(quick?.behavior == QuickTestInfo.Behavior.doNotSubmit && quick.days.size >= 7) {
+		// refuse to schedule
+		return
+	}
+	
+	if(nextDay || lastDay == millisToDaysCumulative(new.timeInMillis)) {
+		new.add(Calendar.DATE, 1)
+	}
+	
+	var iteration = 0
+	while(iteration < 10) {
+		val days = millisToDaysCumulative(new.timeInMillis)
+		val day = new[Calendar.DAY_OF_WEEK]
 		
-		var iteration = 0
-		while(iteration < 10) {
-			val days = millisToDaysCumulative(new.timeInMillis)
-			val day = new[Calendar.DAY_OF_WEEK]
-			
-			when {
-				!pref.includeWeekend && (day == Calendar.SATURDAY || day == Calendar.SUNDAY) -> Unit
-				quick != null && quick.behavior == QuickTestInfo.Behavior.doNotSubmit && day in quick.days -> Unit
-				else -> break
-			}
-			new.add(Calendar.DAY_OF_YEAR, 1)
-			iteration++
-		}
-		if(iteration == 10) {
-			return
+		when {
+			!pref.includeWeekend && (day == Calendar.SATURDAY || day == Calendar.SUNDAY) -> Unit
+			quick != null && quick.behavior == QuickTestInfo.Behavior.doNotSubmit && day in quick.days -> Unit
+			else -> break
 		}
-		
-		new[Calendar.HOUR_OF_DAY] = hour
-		new[Calendar.MINUTE] = min
-		new[Calendar.SECOND] = 0
-		new[Calendar.MILLISECOND] = 0
-		selfLog("schedule time selection (nextDay=$nextDay lastDay=$lastDay, current=$newDay)")
-		new
-	}.timeInMillis
+		new.add(Calendar.DAY_OF_WEEK, 1)
+		iteration++
+	}
+	if(iteration == 10) { // guard against looping forever
+		return
+	}
+	
+	new[Calendar.HOUR_OF_DAY] = hour
+	new[Calendar.MINUTE] = min
+	new[Calendar.SECOND] = 0
+	new[Calendar.MILLISECOND] = 0
+	var newTime = new.timeInMillis
+	
+	selfLog("schedule time selection (nextDay=$nextDay lastDay=$lastDay, newDay=${millisToDaysCumulative(newTime)})")
 	
 	if(isRandom) {
 		newTime += 1000 * 60 * (Random.nextFloat() * 10 - 5).toInt()
-		if(newTime - currentTime < 10000) {
-			selfLog("scheduling: coerced time from $newTime")
-			newTime = currentTime + 10000
-		}
 	}
 	
-	selfLog("scheduling next alarm at ${Date(newTime)}", force = true)
+	if(newTime - now.timeInMillis < 10000) {
+		selfLog("scheduling: coerced time from $newTime (diff = ${newTime - now.timeInMillis})")
+		newTime = now.timeInMillis + 10000
+	}
+	
+	selfLog("scheduling next alarm at ${new.time}", force = true)
 	
 	val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
 	if(Build.VERSION.SDK_INT < 21) {
diff --git a/app/src/main/java/com/lhwdev/selfTestMacro/utils.kt b/app/src/main/java/com/lhwdev/selfTestMacro/utils.kt
index 1138644d..89bd13d1 100644
--- a/app/src/main/java/com/lhwdev/selfTestMacro/utils.kt
+++ b/app/src/main/java/com/lhwdev/selfTestMacro/utils.kt
@@ -20,7 +20,6 @@ import androidx.core.view.doOnPreDraw
 import androidx.core.view.setPadding
 import com.google.android.material.snackbar.Snackbar
 import com.lhwdev.fetch.http.Session
-import com.lhwdev.fetch.http.fetch
 import com.lhwdev.selfTestMacro.api.*
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.suspendCancellableCoroutine
@@ -29,7 +28,6 @@ import kotlinx.serialization.ExperimentalSerializationApi
 import kotlinx.serialization.KSerializer
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.json.Json
-import java.net.URL
 import java.util.WeakHashMap
 import kotlin.coroutines.resume
 import kotlin.properties.ReadWriteProperty
@@ -155,7 +153,7 @@ class PreferenceState(val pref: SharedPreferences) {
 			putLong("lastSubmit", value)
 		}
 	
-	var appMeta: AppMeta? by pref.preferenceSerialized("appMeta", AppMeta.serializer())
+	// var appMeta: AppMeta? by pref.preferenceSerialized("appMeta", AppMeta.serializer())
 	
 	var lastQuestion: String? by pref.preferenceString("lastQuestion")
 	
@@ -171,24 +169,24 @@ class PreferenceState(val pref: SharedPreferences) {
 		}
 }
 
-suspend fun PreferenceState.appMeta(): AppMeta.Data {
-	val day = millisToDaysCumulative(System.currentTimeMillis())
-	val last = appMeta
-	if(last != null && last.at == day) return last.data
-	
-	val data = fetch(URL("https://raw.githubusercontent.com/wiki/lhwdev/covid-selftest-macro/app_meta.json"))
-		.toJsonLoose(AppMeta.Data.serializer())
-	appMeta = AppMeta(data, at = day)
-	return data
-}
-
-@Serializable
-class AppMeta(val data: Data, val at: Long) {
-	@Serializable
-	class Data(
-		val hcsVersion: String
-	)
-}
+// suspend fun PreferenceState.appMeta(): AppMeta.Data {
+// 	val day = millisToDaysCumulative(System.currentTimeMillis())
+// 	val last = appMeta
+// 	if(last != null && last.at == day) return last.data
+//	
+// 	val data = fetch(URL("https://raw.githubusercontent.com/wiki/lhwdev/covid-selftest-macro/app_meta.json"))
+// 		.toJsonLoose(AppMeta.Data.serializer())
+// 	appMeta = AppMeta(data, at = day)
+// 	return data
+// }
+
+// @Serializable
+// class AppMeta(val data: Data, val at: Long) {
+// 	@Serializable
+// 	class Data(
+// 		val hcsVersion: String
+// 	)
+// }
 
 @Serializable
 data class QuickTestInfo(