diff --git a/app/src/debug/java/com/afternote/afternote_fe/debug/DebugSettingsActivity.kt b/app/src/debug/java/com/afternote/afternote_fe/debug/DebugSettingsActivity.kt
index e897bf0b21..7863368128 100644
--- a/app/src/debug/java/com/afternote/afternote_fe/debug/DebugSettingsActivity.kt
+++ b/app/src/debug/java/com/afternote/afternote_fe/debug/DebugSettingsActivity.kt
@@ -4,7 +4,6 @@ import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
-import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
@@ -19,6 +18,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.afternote.afternote_fe.EXTRA_DEBUG_START_TIMELETTER
import com.afternote.afternote_fe.MainActivity
+import com.afternote.afternote_fe.enableLightEdgeToEdge
import dagger.hilt.android.AndroidEntryPoint
/**
@@ -30,7 +30,7 @@ import dagger.hilt.android.AndroidEntryPoint
class DebugSettingsActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- enableEdgeToEdge()
+ enableLightEdgeToEdge()
setContent {
Surface(
modifier = Modifier.fillMaxSize(),
diff --git a/app/src/main/java/com/afternote/afternote_fe/LightEdgeToEdge.kt b/app/src/main/java/com/afternote/afternote_fe/LightEdgeToEdge.kt
new file mode 100644
index 0000000000..ca6d4034c4
--- /dev/null
+++ b/app/src/main/java/com/afternote/afternote_fe/LightEdgeToEdge.kt
@@ -0,0 +1,25 @@
+package com.afternote.afternote_fe
+
+import android.graphics.Color
+import androidx.activity.ComponentActivity
+import androidx.activity.SystemBarStyle
+import androidx.activity.enableEdgeToEdge
+
+/**
+ * 시스템바 아이콘까지 라이트로 고정한 edge-to-edge (#1719).
+ *
+ * 무인자 [enableEdgeToEdge] 는 `SystemBarStyle.auto` 라 `uiMode == NIGHT_YES` 면
+ * `isAppearanceLightStatusBars` 를 `false` 로 잡아 **흰 아이콘**을 고른다. 그런데
+ * `AfternoteTheme` 이 라이트로 잠긴 뒤로 Compose 는 다크 기기에서도 `gray1`(#FAFAFA) 을
+ * 상태바 밑까지 그린다. 둘을 그대로 두면 밝은 배경 위에 흰 시계·배터리·제스처 바가 얹혀
+ * 모든 화면에서 보이지 않는다.
+ *
+ * 그래서 시스템바 스타일도 함께 라이트로 못박는다. **`AfternoteTheme` 의 `isDarkTheme`
+ * 기본값을 시스템 추종으로 되돌릴 때 이 함수도 같이 되돌린다** — 두 자리는 한 쌍이다.
+ */
+fun ComponentActivity.enableLightEdgeToEdge() {
+ enableEdgeToEdge(
+ statusBarStyle = SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT),
+ navigationBarStyle = SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT),
+ )
+}
diff --git a/app/src/main/java/com/afternote/afternote_fe/MainActivity.kt b/app/src/main/java/com/afternote/afternote_fe/MainActivity.kt
index 1a41c2a638..6590d6a330 100644
--- a/app/src/main/java/com/afternote/afternote_fe/MainActivity.kt
+++ b/app/src/main/java/com/afternote/afternote_fe/MainActivity.kt
@@ -3,7 +3,6 @@ package com.afternote.afternote_fe
import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.setContent
-import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.runtime.getValue
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
@@ -24,7 +23,7 @@ class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)
- enableEdgeToEdge()
+ enableLightEdgeToEdge()
enqueueNotificationIntent(intent)
diff --git a/app/src/main/res/values-v27/themes.xml b/app/src/main/res/values-v27/themes.xml
new file mode 100644
index 0000000000..c929763c79
--- /dev/null
+++ b/app/src/main/res/values-v27/themes.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 2fe44078a0..9b2b71e273 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -9,5 +9,14 @@
- @color/splash_background
- @mipmap/ic_launcher
- @style/Theme.Afternotefe.Main
+
+
+ - true
\ No newline at end of file
diff --git a/core/ui/src/main/kotlin/com/afternote/core/ui/theme/Theme.kt b/core/ui/src/main/kotlin/com/afternote/core/ui/theme/Theme.kt
index acb514a961..236ee06ea4 100644
--- a/core/ui/src/main/kotlin/com/afternote/core/ui/theme/Theme.kt
+++ b/core/ui/src/main/kotlin/com/afternote/core/ui/theme/Theme.kt
@@ -38,8 +38,22 @@ fun AfternoteTheme(
colors: AfternoteColors = lightColors(),
darkColors: AfternoteColors = darkColors(),
typography: AfternoteTypography = AfternoteTypography(),
- // 시스템 설정이 다크 모드인지 확인
- isDarkTheme: Boolean = isSystemInDarkTheme(),
+ /**
+ * 다크 팔레트를 물릴지. **기본값은 라이트 고정이다** (#1719).
+ *
+ * 배선만 있고 화면이 안 맞춰진 중간 상태가 양쪽 끝보다 나빴다 — 실측에서 「마음의 기록」
+ * 헤더가 검정 배경에 검정 글자로 거의 보이지 않았고, 주간 요약 카드는 배경만 밝은 채로
+ * 남아 라벨이 저대비로 흐려졌다. 토큰을 쓰지 않고 색을 박은 자리가 반전을 따라가지
+ * 못해서다(`feature/mindrecord/presentation` main 에만 29곳).
+ *
+ * 시안의 다크 팔레트가 확정되고 하드코딩 색이 토큰으로 옮겨지면 기본값을
+ * [isSystemInDarkTheme] 으로 되돌린다. [darkColors] 와 이 파라미터는 그때를 위해 남겨
+ * 둔다 — 값을 넘기면 지금도 다크로 그릴 수 있다.
+ *
+ * **되돌릴 때 `app` 의 `enableLightEdgeToEdge()` 도 같이 되돌린다.** 이 잠금만 풀면
+ * 시스템바 아이콘이 라이트로 고정된 채 배경만 다크가 되어 반대 방향으로 안 보인다.
+ */
+ isDarkTheme: Boolean = false,
content: @Composable () -> Unit,
) {
val currentColor = if (isDarkTheme) darkColors else colors
diff --git a/core/ui/src/test/kotlin/com/afternote/core/ui/theme/ThemeDarkModeLockTest.kt b/core/ui/src/test/kotlin/com/afternote/core/ui/theme/ThemeDarkModeLockTest.kt
new file mode 100644
index 0000000000..906d178e25
--- /dev/null
+++ b/core/ui/src/test/kotlin/com/afternote/core/ui/theme/ThemeDarkModeLockTest.kt
@@ -0,0 +1,64 @@
+package com.afternote.core.ui.theme
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.junit4.v2.createComposeRule
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+/**
+ * 시스템 다크모드를 따라가지 않는다는 계약 (#1719).
+ *
+ * 종전 기본값은 `isSystemInDarkTheme()` 이었는데, 다크 팔레트를 물려도 **화면이 따라오지
+ * 않았다** — 색을 토큰이 아니라 코드에 박은 자리가 반전되지 않아, 「마음의 기록」 헤더가
+ * 검정 배경에 검정 글자로 남고 주간 요약 카드는 배경만 밝은 채로 라벨이 흐려졌다.
+ * 「배선은 됐고 화면은 안 맞은」 중간 상태가 라이트 고정보다도, 완전한 다크 지원보다도
+ * 나빴다. 그래서 팔레트가 확정될 때까지 라이트로 잠근다.
+ *
+ * 이 테스트가 지키는 것은 **잠금이 조용히 풀리지 않는 것**이다. 기본값을
+ * `isSystemInDarkTheme()` 으로 되돌리면 첫 번째 테스트가 night 한정자에서 실패한다.
+ * 되돌릴 때는 하드코딩 색 29곳을 토큰으로 옮긴 뒤 이 테스트도 같이 고쳐야 한다.
+ */
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [35])
+class ThemeDarkModeLockTest {
+ @get:Rule
+ val composeRule = createComposeRule()
+
+ private fun resolveIsLightMode(theme: @Composable (@Composable () -> Unit) -> Unit): Boolean {
+ var isLightMode: Boolean? = null
+ composeRule.setContent {
+ theme { isLightMode = AfternoteDesign.colors.isLightMode }
+ }
+ composeRule.waitForIdle()
+ return requireNotNull(isLightMode) { "테마가 content 를 부르지 않았다" }
+ }
+
+ @Test
+ @Config(qualifiers = "night")
+ fun `시스템이 다크여도 기본값은 라이트 팔레트다`() {
+ assertEquals(true, resolveIsLightMode { content -> AfternoteTheme(content = content) })
+ }
+
+ @Test
+ @Config(qualifiers = "notnight")
+ fun `시스템이 라이트일 때도 당연히 라이트 팔레트다`() {
+ assertEquals(true, resolveIsLightMode { content -> AfternoteTheme(content = content) })
+ }
+
+ /**
+ * 잠금은 **기본값** 에만 걸린다. 다크 팔레트와 파라미터는 팔레트 확정 뒤를 위해 살아 있어야
+ * 하고, 그때 이 값을 다시 시스템에 묶는 것으로 되돌린다.
+ */
+ @Test
+ @Config(qualifiers = "notnight")
+ fun `명시로 넘기면 다크 팔레트를 여전히 그릴 수 있다`() {
+ assertEquals(
+ false,
+ resolveIsLightMode { content -> AfternoteTheme(isDarkTheme = true, content = content) },
+ )
+ }
+}
diff --git a/konsist/src/test/kotlin/com/afternote/konsist/LightSystemBarLockKonsistTest.kt b/konsist/src/test/kotlin/com/afternote/konsist/LightSystemBarLockKonsistTest.kt
new file mode 100644
index 0000000000..e0c0b1a297
--- /dev/null
+++ b/konsist/src/test/kotlin/com/afternote/konsist/LightSystemBarLockKonsistTest.kt
@@ -0,0 +1,38 @@
+package com.afternote.konsist
+
+import com.lemonappdev.konsist.api.verify.assertFalse
+import org.junit.Test
+
+/**
+ * 시스템바가 라이트로 잠긴 채 남는지 (#1719).
+ *
+ * `AfternoteTheme` 이 라이트로 잠긴 동안 Compose 는 다크 기기에서도 `gray1`(#FAFAFA) 을
+ * 상태바 밑까지 그린다. 무인자 `enableEdgeToEdge()` 는 `SystemBarStyle.auto` 라 그 기기에서
+ * 흰 아이콘을 고르므로, 밝은 배경 위에 흰 시계·배터리가 얹혀 사라진다.
+ *
+ * 그래서 `app` 의 호출부는 [com.afternote.afternote_fe.enableLightEdgeToEdge] 하나로 모았다.
+ * 이 가드는 **무인자 호출이 다시 스며드는 것**을 막는다 — 새 Activity 를 만들 때 손이 먼저
+ * 기억하는 형태가 그쪽이라서다.
+ *
+ * 다크 팔레트가 확정돼 잠금을 풀 때는 이 가드도 함께 지운다. 되돌릴 자리는 셋이다 —
+ * `AfternoteTheme` 의 `isDarkTheme` 기본값 · `enableLightEdgeToEdge()` · `themes.xml` 의
+ * `windowLightStatusBar`.
+ */
+class LightSystemBarLockKonsistTest {
+ @Test
+ fun `app 은 무인자 enableEdgeToEdge 를 쓰지 않는다`() {
+ AfternoteKonsistScope
+ .files
+ .filter { file -> file.path.contains("/app/src/") }
+ .filterNot { file -> file.path.endsWith("/$HELPER_FILE") }
+ .assertFalse { file -> BARE_CALL.containsMatchIn(file.text) }
+ }
+
+ private companion object {
+ /** 잠금을 실제로 거는 자리. 여기만 `enableEdgeToEdge` 를 인자와 함께 부른다. */
+ const val HELPER_FILE = "LightEdgeToEdge.kt"
+
+ /** `enableEdgeToEdge()` — 인자 없는 호출. 공백만 허용한다. */
+ val BARE_CALL = Regex("""\benableEdgeToEdge\s*\(\s*\)""")
+ }
+}