@ihabdevteam/core 0.322.0 → 0.323.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/CHANGELOG.md +517 -0
  2. package/dist/components.cjs.js +183 -183
  3. package/dist/components.esm.css +1 -1
  4. package/dist/hooks.cjs.js +3 -3
  5. package/dist/i18n.cjs.js +3 -3
  6. package/dist/ihabdevteam-core.cjs.js +187 -187
  7. package/dist/ihabdevteam-core.d.ts +54 -16
  8. package/dist/packages/core/src/components/Accordion.js +3 -3
  9. package/dist/packages/core/src/components/Alert.js +2 -2
  10. package/dist/packages/core/src/components/BarChart.js +120 -119
  11. package/dist/packages/core/src/components/BottomNav.js +18 -18
  12. package/dist/packages/core/src/components/BulkBar.js +25 -25
  13. package/dist/packages/core/src/components/Button.js +16 -16
  14. package/dist/packages/core/src/components/Calendar.js +17 -17
  15. package/dist/packages/core/src/components/ChartSection.js +30 -30
  16. package/dist/packages/core/src/components/CountTile.js +38 -38
  17. package/dist/packages/core/src/components/DatePicker.js +59 -59
  18. package/dist/packages/core/src/components/Dialog.js +14 -14
  19. package/dist/packages/core/src/components/Effect/EffectPartyPopperHost.js +8 -8
  20. package/dist/packages/core/src/components/FilterPanel.js +6 -6
  21. package/dist/packages/core/src/components/FilterSectionInfo.js +6 -6
  22. package/dist/packages/core/src/components/GameIntro.js +1 -1
  23. package/dist/packages/core/src/components/Icon.js +2 -2
  24. package/dist/packages/core/src/components/ItemActions.js +16 -16
  25. package/dist/packages/core/src/components/LandingHeader.js +6 -6
  26. package/dist/packages/core/src/components/LineChart.js +24 -24
  27. package/dist/packages/core/src/components/Loader.js +5 -5
  28. package/dist/packages/core/src/components/NoticeBox.js +3 -3
  29. package/dist/packages/core/src/components/PageTitle.js +2 -2
  30. package/dist/packages/core/src/components/Pagination.js +4 -4
  31. package/dist/packages/core/src/components/Panel.js +2 -2
  32. package/dist/packages/core/src/components/PanelRow.js +6 -13
  33. package/dist/packages/core/src/components/PassCard/PassCard.js +9 -9
  34. package/dist/packages/core/src/components/PassCard/passCardProduct.js +2 -2
  35. package/dist/packages/core/src/components/PaymentPlan.js +5 -5
  36. package/dist/packages/core/src/components/Popup/ConfirmPopup.js +29 -29
  37. package/dist/packages/core/src/components/Popup/CouponInputPopup.js +2 -2
  38. package/dist/packages/core/src/components/Popup/DeviceSideTable.js +18 -25
  39. package/dist/packages/core/src/components/Popup/ListPopup.js +3 -3
  40. package/dist/packages/core/src/components/Popup/LoadTablePopup.js +7 -7
  41. package/dist/packages/core/src/components/Popup/Popup.js +27 -27
  42. package/dist/packages/core/src/components/Popup/SaveTablePopup.js +5 -5
  43. package/dist/packages/core/src/components/Popup/TrainingResultPopup.js +4 -4
  44. package/dist/packages/core/src/components/Popup/customerDetailUtils.js +18 -18
  45. package/dist/packages/core/src/components/Popup/useDiscardGuard.js +4 -4
  46. package/dist/packages/core/src/components/ProgressIndicator.js +5 -5
  47. package/dist/packages/core/src/components/PromoBannerBg/promoBannerBg.js +16 -16
  48. package/dist/packages/core/src/components/Rating.js +20 -20
  49. package/dist/packages/core/src/components/Report/ReportDocument.js +21 -21
  50. package/dist/packages/core/src/components/RingChart.js +5 -5
  51. package/dist/packages/core/src/components/SectionHeadFloat.js +15 -15
  52. package/dist/packages/core/src/components/SegmentedControl.js +34 -34
  53. package/dist/packages/core/src/components/SideNav.js +37 -37
  54. package/dist/packages/core/src/components/Slider.js +2 -2
  55. package/dist/packages/core/src/components/SoundCompass.js +5 -5
  56. package/dist/packages/core/src/components/Table.js +257 -98
  57. package/dist/packages/core/src/components/Tabs.js +12 -12
  58. package/dist/packages/core/src/components/TextField.js +34 -34
  59. package/dist/packages/core/src/components/Toast.js +6 -6
  60. package/dist/packages/core/src/components/Tooltip/MenuTooltip.js +4 -4
  61. package/dist/packages/core/src/components/Tooltip/Tooltip.js +22 -22
  62. package/dist/packages/core/src/components/Tooltip/TrainingTooltip.js +4 -4
  63. package/dist/packages/core/src/components/TopBanner.js +28 -28
  64. package/dist/packages/core/src/components/Training/AudioPlayerWaveForm.js +11 -11
  65. package/dist/packages/core/src/components/Training/CategoryIcon.js +2 -2
  66. package/dist/packages/core/src/components/Training/NoisePlayerContext.js +12 -12
  67. package/dist/packages/core/src/components/Training/NoiseSettingsTooltip.js +2 -2
  68. package/dist/packages/core/src/components/Training/TrainingBlock.js +11 -11
  69. package/dist/packages/core/src/components/Training/TrainingBlockButton.js +10 -10
  70. package/dist/packages/core/src/components/Training/TrainingBlockGrid.js +21 -21
  71. package/dist/packages/core/src/components/Training/TrainingCard.css +13 -1
  72. package/dist/packages/core/src/components/Training/TrainingCard.js +2 -2
  73. package/dist/packages/core/src/components/Training/TrainingCardGroup.js +6 -6
  74. package/dist/packages/core/src/components/Training/TrainingPagePopup.js +81 -81
  75. package/dist/packages/core/src/components/Training/TrainingPagePopupBottom.js +17 -17
  76. package/dist/packages/core/src/components/Training/TrainingStepAudioPlayer.js +127 -127
  77. package/dist/packages/core/src/components/Training/TrainingStepCard.js +18 -18
  78. package/dist/packages/core/src/components/Training/TrainingStepConfirmWithText.js +4 -4
  79. package/dist/packages/core/src/components/Training/TrainingStepOrderList.js +12 -12
  80. package/dist/packages/core/src/components/Training/TrainingStepTextBox.js +12 -12
  81. package/dist/packages/core/src/components/Training/TrainingStepTitle.js +4 -4
  82. package/dist/packages/core/src/components/Training/TrainingWordCard.js +17 -17
  83. package/dist/packages/core/src/components/Training/WordTablePopup.js +5 -5
  84. package/dist/packages/core/src/components/Training/audioPlaybackRules.js +8 -8
  85. package/dist/packages/core/src/components/Training/audioTimeline.js +25 -25
  86. package/dist/packages/core/src/components/Training/oneShotSound.js +13 -13
  87. package/dist/packages/core/src/components/Training/trainingMissStats.js +20 -20
  88. package/dist/packages/core/src/components/Training/trainingPopupUtils.js +7 -7
  89. package/dist/packages/core/src/components/Training/useAudioPreview.js +15 -15
  90. package/dist/packages/core/src/components/Training/useFloatingFocus.js +14 -14
  91. package/dist/packages/core/src/components/Training/useNoiseFade.js +22 -22
  92. package/dist/packages/core/src/components/Training/useObjectUrls.js +2 -2
  93. package/dist/packages/core/src/components/Training/usePlaybackSequencer.d.ts +2 -2
  94. package/dist/packages/core/src/components/Training/usePlaybackSequencer.js +23 -23
  95. package/dist/packages/core/src/components/Training/useProgressTicker.js +33 -33
  96. package/dist/packages/core/src/components/Training/useTTSPlayback.js +17 -17
  97. package/dist/packages/core/src/components/Training/useWaveReady.js +11 -11
  98. package/dist/packages/core/src/components/UpdateAlert.js +6 -6
  99. package/dist/packages/core/src/components/Word/PhonemeSelectBlock.js +7 -1
  100. package/dist/packages/core/src/components/Word/TableFilterPanel.js +3 -3
  101. package/dist/packages/core/src/components/Word/WordSearchResultBlock.js +10 -10
  102. package/dist/packages/core/src/components/Word/WordTableBlock.js +28 -28
  103. package/dist/packages/core/src/components/Word/WordTableButton.css +16 -2
  104. package/dist/packages/core/src/components/Word/WordTableThumbnail.css +6 -1
  105. package/dist/packages/core/src/components/Word/generatedDraft.js +14 -14
  106. package/dist/packages/core/src/components/Word/useWordTableDisplaySettings.js +7 -7
  107. package/dist/packages/core/src/components/Word/wordScoreStats.js +15 -15
  108. package/dist/packages/core/src/components/Word/wordTablePrint.js +10 -10
  109. package/dist/packages/core/src/components/Word/wordTableRecommend.js +11 -11
  110. package/dist/packages/core/src/components/Word/wordTableReview.js +28 -28
  111. package/dist/packages/core/src/components/Word/wordTableSettings.d.ts +8 -11
  112. package/dist/packages/core/src/components/Word/wordTableSettings.js +48 -36
  113. package/dist/packages/core/src/components/WordTable.js +130 -130
  114. package/dist/packages/core/src/components/barChartData.d.ts +16 -1
  115. package/dist/packages/core/src/components/barChartData.js +49 -29
  116. package/dist/packages/core/src/components/barChartTypes.js +8 -8
  117. package/dist/packages/core/src/components/tableTrackList.js +11 -11
  118. package/dist/packages/core/src/components/updateAlertStack.js +28 -28
  119. package/dist/packages/core/src/components/useBarChartControl.js +4 -6
  120. package/dist/packages/core/src/components/useComboboxSuggestions.js +17 -17
  121. package/dist/packages/core/src/components/useToastLifecycle.js +18 -18
  122. package/dist/packages/core/src/components/wordTableLayout.d.ts +17 -4
  123. package/dist/packages/core/src/components/wordTableLayout.js +5 -5
  124. package/dist/packages/core/src/context/I18nProvider.js +6 -6
  125. package/dist/packages/core/src/hooks/focusReturn.js +27 -27
  126. package/dist/packages/core/src/hooks/inputModality.js +13 -13
  127. package/dist/packages/core/src/hooks/touchToggle.js +39 -39
  128. package/dist/packages/core/src/hooks/useAnchoredMenu.js +30 -30
  129. package/dist/packages/core/src/hooks/useDailyTrainingSelection.js +28 -28
  130. package/dist/packages/core/src/hooks/useDocumentTitle.js +10 -10
  131. package/dist/packages/core/src/hooks/useEditableList.js +4 -4
  132. package/dist/packages/core/src/hooks/useFocusTrap.js +2 -2
  133. package/dist/packages/core/src/hooks/useFontScale.js +6 -6
  134. package/dist/packages/core/src/hooks/useIcon.js +7 -7
  135. package/dist/packages/core/src/hooks/useListControls.js +2 -2
  136. package/dist/packages/core/src/hooks/usePageResetOnChange.js +3 -3
  137. package/dist/packages/core/src/hooks/usePasswordChange.js +4 -4
  138. package/dist/packages/core/src/hooks/usePendingPress.js +7 -7
  139. package/dist/packages/core/src/hooks/useRelativeDay.js +4 -4
  140. package/dist/packages/core/src/hooks/useSoundCompassExam.js +6 -6
  141. package/dist/packages/core/src/hooks/useTablePaging.d.ts +0 -11
  142. package/dist/packages/core/src/hooks/useTablePaging.js +29 -34
  143. package/dist/packages/core/src/hooks/useTrainingSessionLog.js +35 -35
  144. package/dist/packages/core/src/hooks/useVisitSchedule.js +4 -4
  145. package/dist/packages/core/src/hooks/useWordAttributeFilter.js +14 -14
  146. package/dist/packages/core/src/hooks/useWordMeanings.js +5 -5
  147. package/dist/packages/core/src/hooks/useWordTrainingFilter.js +7 -7
  148. package/dist/packages/core/src/i18n/locales/en.json +13 -2
  149. package/dist/packages/core/src/i18n/locales/ko.json +12 -1
  150. package/dist/packages/core/src/types/wordSearch.d.ts +9 -0
  151. package/dist/packages/core/src/utils/audioMeter.js +5 -5
  152. package/dist/packages/core/src/utils/audioPeaks.d.ts +1 -1
  153. package/dist/packages/core/src/utils/audioPeaks.js +18 -18
  154. package/dist/packages/core/src/utils/blockGrid.js +4 -4
  155. package/dist/packages/core/src/utils/chunkSkew.js +9 -9
  156. package/dist/packages/core/src/utils/clipboard.js +3 -3
  157. package/dist/packages/core/src/utils/dailySelectionUtils.js +12 -12
  158. package/dist/packages/core/src/utils/date.js +12 -12
  159. package/dist/packages/core/src/utils/devWarn.js +4 -4
  160. package/dist/packages/core/src/utils/deviceKind.d.ts +37 -7
  161. package/dist/packages/core/src/utils/deviceKind.js +53 -10
  162. package/dist/packages/core/src/utils/errorMessage.js +7 -7
  163. package/dist/packages/core/src/utils/filterPanelSummary.js +14 -14
  164. package/dist/packages/core/src/utils/format.js +4 -4
  165. package/dist/packages/core/src/utils/getTrainingData.js +9 -9
  166. package/dist/packages/core/src/utils/imageConfig.js +17 -17
  167. package/dist/packages/core/src/utils/koreanSyllable.js +18 -18
  168. package/dist/packages/core/src/utils/navScroll.js +2 -2
  169. package/dist/packages/core/src/utils/phonemeConvert.d.ts +7 -0
  170. package/dist/packages/core/src/utils/phonemeConvert.js +19 -10
  171. package/dist/packages/core/src/utils/phonemeGroups.js +2 -2
  172. package/dist/packages/core/src/utils/phonemePairSearch.d.ts +1 -0
  173. package/dist/packages/core/src/utils/phonemePairSearch.js +118 -102
  174. package/dist/packages/core/src/utils/phonemeSectionCodes.d.ts +52 -0
  175. package/dist/packages/core/src/utils/phonemeSectionCodes.js +76 -0
  176. package/dist/packages/core/src/utils/prefetchChunk.js +6 -6
  177. package/dist/packages/core/src/utils/safeHref.js +4 -4
  178. package/dist/packages/core/src/utils/savedWordTables.js +5 -5
  179. package/dist/packages/core/src/utils/speak.js +12 -12
  180. package/dist/packages/core/src/utils/textSort.d.ts +0 -10
  181. package/dist/packages/core/src/utils/textSort.js +38 -48
  182. package/dist/packages/core/src/utils/theme.js +3 -3
  183. package/dist/packages/core/src/utils/trainingItemResults.js +3 -3
  184. package/dist/packages/core/src/utils/ttsConfig.js +2 -2
  185. package/dist/packages/core/src/utils/wordAttributeFilterCodes.js +45 -45
  186. package/dist/packages/core/src/utils/wordContrast.js +3 -3
  187. package/dist/packages/core/src/utils/wordPairIndex.js +4 -4
  188. package/dist/packages/core/src/utils/wordTrainingGenerators.js +4 -4
  189. package/dist/packages/core/src/utils/wordTrainingPool.js +15 -15
  190. package/dist/packages/core/src/utils/wordTrainingRuntime.js +6 -6
  191. package/dist/packages/core/src/utils/wornDate.d.ts +3 -3
  192. package/dist/packages/core/src/utils/wornDate.js +14 -14
  193. package/dist/packages/core/src/version.d.ts +1 -1
  194. package/dist/packages/core/src/version.js +1 -1
  195. package/dist/utils.cjs.js +4 -4
  196. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,523 @@ All notable changes to this project will be documented in this file.
7
7
  > 적은 글이라, 지금 이름으로 고쳐 놓으면 그 시절에 그 이름으로 받은 사람이
8
8
  > 제 이야기를 못 찾는다.
9
9
 
10
+ ## 0.323.0 — 칸 너비 자동맞춤은 **상태**다, 그리고 안쪽 이름을 끝까지 영어로
11
+
12
+ ### `scripts`·`test`·빌드 설정의 이름도 영어로 — **싣는 것은 한 바이트도 안 바뀐다**
13
+
14
+ 사장님 지시(2026-10-14). `scripts/` 548선언(1,973자리) · 시험 6 · 놀이터 5 ·
15
+ `postcss.config.cjs` 2 를 옮겼다. 글로 긁지 않고 **TypeScript 의 rename** 으로 집었다.
16
+
17
+ **배포물은 안 바뀐다.** `src` 를 한 줄도 안 건드렸고, `postcss.config.cjs` 는 빌드 설정이라
18
+ 나오는 CSS 가 달라질 수 있어 **옛 설정과 새 설정으로 각각 뽑아 견주었다 — 바이트까지 같다.**
19
+
20
+ > **못이 이미 있었는데 내가 두 번째를 달 뻔했다.** `test/identifiersAreEnglish1013.test.ts`
21
+ > 가 어제 세워져 있었는데 `.md` 만 뒤지고 *"못이 없다"* 고 적었다. 그 문이 내내 초록이던
22
+ > 까닭은 **훑는 자리를 손으로 골랐기** 때문이다(`src`·`test`·`playground/src` 에 `.tsx?` 만)
23
+ > — 그래서 `scripts/` 1,973곳과 `postcss.config.cjs` 둘이 **한 번도 안 보였다.**
24
+ > 그 문을 **뿌리에서 훑게** 넓히고(`.mjs/.cjs/.js` 까지), `walked > 900` 을 `> 1100` 으로
25
+ > 올려 **자리를 좁히면 먼저 울게** 했다. 내가 만든 쪽은 걷었다.
26
+
27
+ > 연장도 둘 넓혔다 — `fix-leftover-property-reads.mjs` 가 `.tsx?` 만 훑어 스크립트의 남은
28
+ > 성질 읽기를 **하나도 안 집고 "0곳" 으로 조용히** 지나갔다(32곳이 나왔다). 그리고
29
+ > `scripts/` 가 어느 tsconfig 에도 없어 `tsconfig.scripts.json` 을 세웠다.
30
+
31
+ ### 소비자 표를 실측으로 되돌렸다
32
+
33
+ `audit:consumers` 가 빨갰다. 소리방향성·kscan 이 `^0.211.0` 으로 적혀 있는데 실물은
34
+ `^0.183.2` 다(두 저장소 다 나무가 깨끗하고 9/21 이후 안 바뀌었으며 `0.211.0` 이라는 글자가
35
+ **어디에도 없다**). 그리고 *"센 것이 없는데 초록"* 을 글이 아니라 **셈으로** 막았다 —
36
+ 한 줄이라도 쟀으면 지나가고 **하나도 못 쟀으면 막는다**(워크트리에서 돌린 관문이 그 틈으로
37
+ 틀린 표를 실었다).
38
+
39
+ ### 칸 너비 '자동맞춤' 은 한 번 누르는 일이 아니라 **상태**다
40
+
41
+ 사장님 지시(2026-10-13, 말귀 청). 바라는 결 한 줄: **손으로 조절한 적이 없는 표는 늘
42
+ 자동맞춤으로 서고, 조절하면 그 값을 쓰고, '칸 너비 초기화' 를 누르면 다시 자동맞춤 상태로
43
+ 돌아간다.**
44
+
45
+ 전 꼴의 흠은 **초기화가 재서 그 숫자를 저장**한 것이다. 손으로 끈 것과 구별이 없어지고,
46
+ 무엇보다 그때 화면에 있던 줄로 잰 **'한 순간의 사진'** 으로 굳어 그 뒤 창을 넓히거나 긴
47
+ 글자가 든 줄이 들어와도 안 따라왔다.
48
+
49
+ | 자리 | 전 | 후 |
50
+ |---|---|---|
51
+ | 한 번도 안 건드린 표 | 만든 사람의 `gridTemplateColumns` | **자동맞춤** |
52
+ | 손으로 끈 표 | 저장값 | 그대로 |
53
+ | 초기화 단추 | 재서 **저장**(손 모드로 굳는다) | 저장값을 **지우고 자동으로** |
54
+ | 자동일 때 저장 | — | **안 한다**(저장하면 되돌아갈 길이 사라진다) |
55
+ | 줄이 없을 때 | — | 재지 않는다(먼저 선 격자를 그대로 둔다) |
56
+ | 좁은 화면 | — | 아무것도 안 한다(폭 조절을 통째로 끄는 기존 규칙 안) |
57
+
58
+ **다시 재는 계기는 넷이다 — '줄 바뀜' 은 없다.**
59
+
60
+ ```
61
+ 잰다: 줄이 **처음** 들어왔을 때 · 통 폭이 바뀔 때 · 칸 수가 바뀔 때 · 초기화를 눌렀을 때
62
+ 안 잰다: 줄이 바뀌었을 뿐일 때(걸렀다 · 쪽을 넘겼다 · 새로 받았다)
63
+ ```
64
+
65
+ 처음에는 "줄이 바뀔 때마다" 재게 두었다(청의 ①이 그렇게 적혀 있었다). **말귀가 제 앱을 재어
66
+ 그 청을 스스로 물렸다**: 저장열쇠가 있는 표 스물여덟 중 **열아홉**이 찾는 칸이나 쪽 나누기를
67
+ 갖고 있다 — 걸러도, 쪽을 넘겨도 줄이 갈리고 그때마다 칸이 다시 선다. 예외가 아니라 대다수다.
68
+ 쪽 넘기기가 더 눈에 띈다: 치는 것은 내가 하는 일이라 움직임이 내 탓으로 읽히지만, 쪽 넘기기는
69
+ "같은 표의 다음 장" 이라 **같은 표가 다른 모습**으로 보인다.
70
+
71
+ 첫 묶음으로 잰 폭이 뒤 쪽의 긴 글자에 안 맞을 수 있다. 그런데 그것은 **사람이 손으로 끌어 둔
72
+ 표와 똑같은 흠**이다 — 끌어 둔 폭도 다음 쪽 글자에 안 맞는다. 안 맞으면 초기화를 누르면 되고
73
+ 그 단추가 바로 그 자리에 있다. **들쭉날쭉한 맞음보다 가만한 안 맞음이 낫다.** ('늦추기' 는 안
74
+ 썼다 — 움직임을 늦출 뿐 없애지 못하고, 치다 멈춘 뒤 한 박자 있다 칸이 움직이면 내가 한 일과
75
+ 끊겨 보여 더 뜻 모를 움직임이 된다.)
76
+
77
+ **줄이 없을 때 잰 폭을 `null` 로 되돌리지 않는다.** 되돌리면 걸러서 0건이 될 때 만든 사람의
78
+ 틀로 튀고, 줄이 돌아올 때 **다시 재어** 또 튄다(실측: `200px 400px` → `120px 480px`). 그래서
79
+ "이미 이 조건에서 쟀다" 를 걸쇠로 기억한다 — 그 조건에 **저장 열쇠도 든다**(열쇠가 갈리면 다른
80
+ 표이므로, 안 넣으면 새 열쇠의 표가 옛 표의 잼을 물려받는다).
81
+
82
+ ### 줄이 0개일 때도 쥔 폭을 쓴다 — 뜻을 잃은 막음을 걷었다
83
+
84
+ `canApplyWidths` 에 `(resizeMode === 'fit' || items.length > 0)` 가 붙어 있었다. `grow` 에서
85
+ 줄이 없으면 쥔 폭을 버리고 만든 사람의 틀로 되돌리는 조건이다. 처음에는 **까닭을 모르는 채
86
+ 걷지 않겠다**고 두고 말귀에 넘겼는데, 말귀가 캐 와서(그리고 내가 다시 확인해서) 까닭이
87
+ `741e4a61` 이 아니라 **그 앞**에 있고 지워진 주석에 남아 있는 것을 알았다:
88
+
89
+ > 항목이 0개일 때(로딩/빈 상태)는 저장된 절대 px 폭 합이 현재 컨테이너 폭보다 커서
90
+ > 불필요한 가로 스크롤이 생길 수 있어, 이때는 기본(fr 단위) grid로 되돌린다.
91
+
92
+ **그 막음은 뜻을 잃었다.** `grow` 는 끈 만큼 넓어져 **줄이 있을 때 이미** 그 가로 스크롤이
93
+ 서는 모드다 — 그것이 `fit` 과 갈리는 점이고 `741e4a61` 머리말이 그렇게 적고 있다. 저장된 px
94
+ 합이 통보다 크다는 것은 그 사람이 그만큼 넓혀 두었다는 뜻이다. 그러니 이 조건이 없앤 것은
95
+ 가로 스크롤이 아니라 **빈 상태만 다르게 보이는 것**이었다.
96
+
97
+ 어긋남은 두 자리에서 보였다 — ① **화면에 들어갈 때마다**(`loading` 이면 소비처가 보통
98
+ `items=[]` 를 준다) 만든 사람의 틀로 머리가 섰다가 자료가 오면 쥔 폭으로 **튀었다**,
99
+ ② 걸러서 0건이 되면 또 튀었다. 말귀 실측: 저장열쇠 있는 표 서른셋 중 `grow` 가 스물하나,
100
+ 그중 0건이 날 수 있는 것(찾기나 쪽이 있다)이 열일곱.
101
+
102
+ **그리고 자동맞춤이 이것을 모두의 일로 바꿨다** — 전에는 `colWidths` 가 끌어 본 사람에게만
103
+ 있어 이 튐도 그 사람에게만 났는데, 이제는 아무도 안 끌어도 찬다. 그래서 이 판의 몫이다.
104
+
105
+ **한 자리는 바로잡는다.** 말귀는 이것이 ①도 "모두의 일" 이 된다고 보았는데, **자동 모드의
106
+ ①은 걷어도 안 바뀐다** — 받는 중에는 잴 줄이 없으니 쥘 폭도 없다(틀 → 잰 폭은 남는다).
107
+ 걷어서 사라지는 것은 **손 모드의 ①**(마운트 때 저장값이 이미 있는데 눌려 있었다)과 **두
108
+ 모드의 ②**다.
109
+
110
+ 빈 줄(`.table-row--empty`)은 격자 줄이 아니라 `display: flex` 라 칸 폭을 안 쓴다 — 걷어도
111
+ 움직이는 것은 **머리뿐**이다. 말귀가 '혹시 거슬리면' 으로 내놓은 길(빈 줄을 통 폭으로)이
112
+ 이미 그렇게 되어 있다.
113
+
114
+ **치르는 값은 솔직히 적는다** — 빈 상태의 가로 스크롤은 **아무것도 안 보여 준다**(밀어도 빈
115
+ 자리다). 줄이 있을 때의 스크롤은 내용을 보여 주니 그와 같지 않다. 그 값을 치르고 **머리를
116
+ 가만히** 두는 거래다. 사람이 읽는 것은 머리다.
117
+
118
+ **그 조건을 지키는 시험이 있었다** — `Table.test.tsx` 의 *"항목이 없으면 저장된 px 폭 대신
119
+ 기본 폭으로 되돌린다"*. 말귀도 나도 못 보고 있었고 **관문이 울려서 알았다**(걷은 뒤 두 판에서
120
+ 울었다). 읽어 보니 까닭을 그 주석에 다시 적어 둔 것이라 위 셈이 그대로 들어맞았고, 뜻을
121
+ 뒤집어 고쳐 적었다 — 치르는 값까지 함께.
122
+
123
+ 되살려 심으면 셋이 운다(0건 튐 · 받는 중 튐 · 그 시험).
124
+
125
+ **새 prop 을 두지 않았다 — 저장소의 없음 = 자동.** 그래서 이미 끌어 둔 사람의 폭은 판이
126
+ 올라도 그대로 손 모드로 읽힌다(**옛 값을 밀지 않는다**). 앱에서 고칠 것은 없다.
127
+
128
+ **모습이 바뀌는 범위를 세었다.** 말귀·어드민의 `resizable` 표 서른둘 중 서른이 저장열쇠를
129
+ 가졌다. 한 번도 끌지 않은 사람에게는 그 서른이 자동맞춤으로 선다 — 청의 내용이 그것이다.
130
+ 저장열쇠가 없는 둘(`SentenceAnalysisNotes`·`PhonemeMissPopup`)은 열 때마다 자동이다.
131
+
132
+ **자동은 `resizeStorageKey` 가 있는 표만 돈다.** 열쇠가 없으면 '손으로 조절했다' 를 기억할
133
+ 자리가 없어 영영 자동이 되는데, 열쇠 없는 `resizable` 표는 이 저장소에서 **전부 치면서
134
+ 걸러 보는 창**이다 — `ListPopup` 을 거치는 일곱 자리(말귀 셋·훈련 현황·어드민 셋)와
135
+ `SentenceAnalysisNotes`·`PhonemeMissPopup`.
136
+
137
+ 처음에는 그 창들도 자동으로 두었다. 지시의 **글자**("조절한 적이 없으면 자동")에는 맞았다 —
138
+ 열쇠가 없으면 영영 '조절한 적 없음' 이다. **말귀가 그 창들을 열어 재어 반박했고, 그 말이
139
+ 맞았다**(2026-10-13): 거기서는 글자를 칠 때마다 결과 줄이 갈리고, "줄이 바뀌면 다시 잰다" 가
140
+ **보는 사람 눈앞에서 칸을 움직인다.** 폭을 기억하지 않는 것과 폭이 매 글자마다 바뀌는 것은
141
+ 다른 일이고 뒤쪽이 더 거슬린다. 그 창들의 칸 틀에도 까닭이 적혀 있다 — `minmax` 바닥은 좁은
142
+ 창에서 유동 칸이 0px 로 눌려 낱말과 '걸린 소리' 가 포개지는 것을 막고(2026-10-02 실측),
143
+ 백분율은 걸러도 비율이 안 흔들리게 한다. 자동맞춤은 유동 칸을 내용 크기로 바꾸므로 그 결을
144
+ 매번 덮는다.
145
+
146
+ `ListPopup` 머리말(*"창은 열 때마다 새로 짓는 것이라 폭이 따라다니면 엉뚱하다"*)이 정한 것은
147
+ **'기억하지 않는다'** 까지이고 **'누가 칸을 정하나'** 는 아니었다 — 그것을 "그러니 대신
148
+ 자동맞춤" 으로 읽은 것이 내 잘못이었다. 나중에 그 창들도 자동으로 서는 게 낫다면 **그 표에
149
+ 저장 열쇠를 주면** 된다. 그러면 처음엔 자동, 끌면 기억, 초기화하면 다시 자동으로 지시가
150
+ 통째로 들어맞는다.
151
+
152
+ **열쇠가 없어도 초기화 단추는 산다** — 눌렀을 때 그 자리에서 재어 쓴다(저장만 안 한다).
153
+ 신호만 올리면 그 표에는 자동 모드가 없어 **단추가 죽는다**(심어서 확인했다).
154
+
155
+ **내 셈이 틀렸던 것도 적어 둔다.** 열쇠 없는 `resizable` 표를 둘로 세었는데, `<Table
156
+ resizable>` 을 직접 쓴 자리만 훑어서 **`ListPopup` 을 거쳐 서는 일곱**을 놓쳤다. 창을 거치는
157
+ 표는 그 문법으로 안 걸린다.
158
+
159
+ **보개(`ResizeObserver`)는 건드리지 않았다.** 통이 넓어질 때 따라가려면 신호가 필요한데, 통을
160
+ 보는 보개가 이미 `measureOverflow` 를 부른다 — 그 함수 **안에만** 통 폭을 적었다. 보개를 하나
161
+ 더 세우거나 `observe` 의 꼴을 건드리면 0.237.0 의 그 경합(`ref` 콜백과 effect 뒷정리의 차례)을
162
+ 다시 들인다.
163
+
164
+ ### 심어서 확인한 것 여섯 — 그중 하나는 눈먼 시험이었다
165
+
166
+ ① 자동이 저장하게, ② 초기화가 재서 저장하게, ③ 받는 중에도 재게, ④ 좁은 화면에서도 재게,
167
+ ⑤ 끌기가 손 모드로 안 넘기게, ⑥ 통 폭 신호를 안 적게 했을 때 — 처음에는 **④가 안 잡혔다**
168
+ (열아홉이 다 초록이었다). 좁은 화면에서는 모바일 격자가 `colWidths` 와 상관없이 이기므로
169
+ "격자가 모바일 틀인가" 로는 자동이 돌았는지를 **볼 수 없었다.** 재는 동안의
170
+ `--table-grid-cols: max-content` 쓰기를 엿보는 꼴로 바꾸고, **그 엿봄이 넓은 화면에서는
171
+ 반드시 걸리는지**를 함께 재는 시험을 나란히 두었다(안 걸리면 0건은 "안 돌았다" 가 아니라
172
+ "못 본다" 는 뜻이다).
173
+
174
+ 그 과정에 `vi.stubGlobal` 이 `restoreAllMocks` 로 **안 걷히는** 것도 드러났다 — 좁은 화면
175
+ 흉내가 뒤 시험으로 새고 있었다.
176
+
177
+ **옛 시험 셋은 뜻이 바뀌어 다시 적었다** — 초기화가 저장한다고 적은 것, "누르기 전과 달라야
178
+ 한다" 로 잰 것(이제는 마운트 때 이미 자동으로 서 있어 같은 것이 맞다), 못 쓸 저장값이 만든
179
+ 사람의 틀로 떨어진다고 적은 것. 셋 다 **지키던 것은 그대로 지키게** 고쳐 적었다.
180
+
181
+
182
+ ### 기기 종류를 **영어 코드로** 받는다 — 이름표 두 벌은 한 자리로
183
+
184
+ 말귀 요청(2026-10-13). 앱이 `managed_device.type` 을 `'보청기'` → `'hearingAid'` 로 옮긴다.
185
+ 코어가 안 받으면 **터지지 않고 조용히 틀린다** — 배지에 `hearingAid` 가 날것으로 뜨고
186
+ (`defaultValue` 로 떨어진다), 색이 둘 다 무채색이 되어 종류를 색으로 가르던 것이 사라진다.
187
+ 눈으로만 잡히는 꼴이라 앱이 판을 돌리기 **전에** 넣는다.
188
+
189
+ | 저장값 | 배지 이름 | 배지 색 |
190
+ |---|---|---|
191
+ | `'hearingAid'` · `'보청기'` | `device.type.hearingAid` | `main` |
192
+ | `'cochlearImplant'` · `'인공와우'` | `device.type.cochlearImplant` | `sub` |
193
+ | `'other'` · `'기타'` | `device.type.other` **(새로 둔다)** | `default` |
194
+ | 그 밖 | 저장된 그대로 | `default` |
195
+
196
+ **모르는 값을 `'other'` 로 접지 않는다.** 기타는 "기타" 라고 고른 값이고, 모르는 값은 아직
197
+ 뜻을 모르는 것이다. 둘을 섞으면 새 종류가 생긴 날 조용히 '기타' 로 보인다. 모르는 값은
198
+ **저장된 그대로** 보인다 — 화면에 날것이 보이면 자료에 무엇이 들어 있는지 알 수 있고,
199
+ 빈칸이면 아무것도 알 수 없다.
200
+
201
+ **그리고 이름표가 두 벌이었다.** `deviceTypeKeys`·`deviceTypeLabel` 이 `DeviceSideTable` 과
202
+ `PanelRow` 에 **주석까지 똑같이** 있었다(말귀가 짚었다). 색은 `deviceKind.ts` 에 모여 있었는데
203
+ 이름은 아니었다 — 색에 적어 둔 그 까닭이 이름에도 그대로 들어맞는 자리다. 그 둘을
204
+ `deviceKind.ts` 로 모으고 `deviceKindLabel`·`deviceKindOf` 를 더했다.
205
+
206
+ 시험은 **심어서 확인했다** — ① 영어 코드를 안 받게, ② 사전에서 `other` 를 빼고, ③ 모르는
207
+ 값을 '기타' 로 접게, ④ 컴포넌트가 날것을 그리게 했을 때 넷 다 울었고 울음말이 까닭을
208
+ 그대로 말했다.
209
+
210
+ 남은 두 자리는 이 판에 없다 — `direction` 은 사장님이 "코어는 깔끔하게만" 으로 정하신 자리라
211
+ `'left'|'right'` 만 받는다(말귀가 옮긴 뒤 쓴다), `style` 은 코어가 가르지 않고 지나보낸다.
212
+
213
+
214
+ ### `TrainingCard` 설명 줄도 한 급 흐리게 — 같은 판단을 같은 자리에
215
+
216
+ 사장님 지시(2026-10-13, 말귀_2 청). `WordTableButton` 설명 줄과 **같은 일**이다 — 카드 안의
217
+ 읽는 차례.
218
+
219
+ | | 전 | 후 |
220
+ |---|---|---|
221
+ | `.training-card__description` | `--color-gray` #64748b | **`--color-lightGray`** #96a4b8 |
222
+
223
+ 제목이 `--color-darkGray`(#434d56)인데 설명이 #64748b 로 **두 급 차이가 한 급도 안 됐다** —
224
+ 제목과 설명이 한 덩어리로 읽혔다.
225
+
226
+ **소비처가 열 몇이라 한 번 더 세었다.** 숙제 카드·훈련 묶음·플랜 카드·말귀자료실이 이 카드를
227
+ 쓴다. 그런데 **이 색이 닿는 것은 "문자열 한 줄로 거드는 설명" 뿐이다** —
228
+
229
+ ```tsx
230
+ typeof description === 'string'
231
+ ? <p className="training-card__description body-3">{description}</p>
232
+ : description // ← 본문이 필요한 자리는 노드를 넘긴다. 이 클래스가 안 붙는다
233
+ ```
234
+
235
+ 본문을 쓰는 자리는 **구조적으로 이 색을 안 받는다.** 그래서 소비처가 열 몇이어도 무게는
236
+ 단어표 카드와 같다. 그 셈을 사장님께 올리고 기본값으로 정하셨다.
237
+
238
+ > **흰 바탕 2.53:1 로 본문 기준(4.5)에 못 미치는 것을 알고 그렇게 둔다.** 되돌릴 때 볼 것도
239
+ > 적어 뒀다 — `--color-gray`(4.8:1)가 그때의 기본이다. **코어에는 대비를 세는 못이 없다.**
240
+
241
+ 같은 회색을 쓰던 다른 셋(게시자·날짜·그 밖)은 **안 건드렸다** — 자리를 가려 설명 줄만 바꿨다.
242
+
243
+ ### 음소 자리 열쇠를 **코드로** — `'초성'` → `'onset'` (저장된 표는 안 민다)
244
+
245
+ 사장님 지시("동작에 한글이 끼지 말라"). 말귀 두 세션도 같은 자리를 청해 왔다.
246
+
247
+ **그 열쇠는 저장된다** — 단어표의 `searchConfig.phonemeSelected` 가 `{ 초성: 'ㄴ' }` 꼴이고
248
+ 공개 함수(`searchPairs`·`searchWordsByPhonemes`·`convertToPhoneme`)가 그것을 그대로 받는다.
249
+ 그래서 **값을 바꾸지 않고 읽는 쪽을 너그럽게** 했다 — 표준의 *lenient reader · strict writer*.
250
+
251
+ 쓰는 쪽은 코드만 낸다. 읽는 쪽은 둘 다 받는다. 저장된 표는 안 밀어도 된다.
252
+
253
+ | | |
254
+ |---|---|
255
+ | 안쪽 어휘 | `'onset' \| 'vowel' \| 'coda' \| 'glide'` — **훈련 필터가 이미 쓰던 것**과 같게 뒀다(같은 뜻에 어휘가 둘이었다) |
256
+ | 빔 | `'없음'` → `'none'` |
257
+ | 값(자모 `'ㄴ'`) | **그대로** — 한국어 글자 그 자체다 |
258
+ | `diffSection` | **옛 한글 이름 그대로** 내보낸다 + `diffSectionCode` 를 나란히 |
259
+
260
+ `diffSection` 을 혼자 바꾸면 **말귀 대립쌍 화면의 묶음 넷이 조용히 빈다**(`GROUP_ORDER =
261
+ ['초성','모음','활음','종성']` 로 가른다 — 두 세션이 따로 세어 같은 자리를 짚었다). 결과 수는
262
+ 세는데 묶음에는 안 보이는 꼴이라 **터지지 않고 눈으로만 잡힌다.** 소비자가 옮긴 뒤 걷는다.
263
+
264
+ ### 밟은 함정 둘
265
+
266
+ **① 돌림 import 로 시험 338 꾸러미가 무너졌다.** 가름을 `phonemePairSearch` 에 뒀는데 그쪽도
267
+ `phonemeConvert` 를 들여온다. **두 쪽이 함께 쓰는 것은 어느 한쪽이 아니라 그 아래**에 둔다
268
+ (`phonemeSectionCodes.ts`).
269
+
270
+ **② 옮기기에 거르는 줄을 끼워 넣어 뜻이 바뀌었다.** `!value` 로 빈 값을 걸렀는데
271
+ `{ 종성: '' }` 은 **"받침 없음을 골랐다" 는 뜻 있는 고름**이다 — `받침 있음↔없음` 검색이
272
+ 빈손이 됐다. 시험이 잡았다. *옮기는 함수는 옮기기만 한다.*
273
+
274
+ 문을 뒀다(`phonemeLegacyKeysRead1013`) — **옛 꼴과 새 꼴이 같은 결과**인지 재고, 둘 다 빈손이면
275
+ "같다" 가 아무 말도 안 하므로 **센 것이 0 보다 큰지**까지 본다.
276
+
277
+ ### 겸해서 — 만든 것을 치운다
278
+
279
+ 저장소 폴더가 **600MB** 였다. `pack:check` 가 판마다 만든 tarball **59개·97MB** 와 그것을 풀어
280
+ 본 `package/` **9.8MB** 가 9월 3일부터 쌓여 있었다. **둘 다 `.gitignore` 에 있어 `git status`
281
+ 가 조용했다** — 무시 목록은 "커밋하지 말라" 는 뜻이지 "없다" 는 뜻이 아니다. 지우고(600→494MB),
282
+ `pack:check` 가 `trap … EXIT INT TERM` 으로 **스스로 치우게** 했다.
283
+
284
+ ### `WordTableButton` 카드 — 읽는 차례를 세 겹으로 (말귀_2 청)
285
+
286
+ 사장님 지시(2026-10-13). 말귀_2 가 앱 CSS 로 덮어 두고 **껐다 켜 보기로 코어가 그 값을 안
287
+ 쥔 것**을 확인해 청해 왔다(§3-7 이 요구하는 꼴이다).
288
+
289
+ | 자리 | 전 | 후 | 까닭 |
290
+ |---|---|---|---|
291
+ | `.word-table-button` | `width: 240px` | **280px** | 대립쌍표는 한 칸에 낱말이 둘('가게 / 마개') 들어가 240 에서 글자가 칸에 꽉 찼다. 320 은 한 줄에 드는 카드가 줄어 280 |
292
+ | `.word-table-thumb__word` | `--color-black` #111 | **`--color-darkGray`** #434d56 | 제목과 **둘 다 #111** 이라 같은 무게로 섰다. 썸네일은 알아보는 그림이지 읽는 글이 아니다. 9.1:1 |
293
+ | `.word-table-button__desc` | `--color-gray` #64748b | **`--color-lightGray`** #96a4b8 | 제목과 충분히 안 갈려 **한 덩어리로 읽혔다**. 2.53:1 — 아래를 본다 |
294
+
295
+ 셋이 한 가지 일이다 — **카드 안에 읽는 차례를 만드는 것**. 제목 #111 · 썸네일 #434d56 ·
296
+ 설명 #96a4b8 으로 세 겹이 된다.
297
+
298
+ **폭은 손잡이를 내지 않았다.** 이 카드를 쓰는 화면이 셋인데(나의 단어표·말귀자료실·고객 채점
299
+ 담기) 셋이 같은 카드로 같은 일을 한다. 한 화면만 280 이면 *같은 카드가 화면마다 다른 폭*이
300
+ 되고, 나중에 그 까닭을 아무도 설명하지 못한다. 손잡이는 **폭이 다를 까닭이 자리마다 있을 때**
301
+ 내는 것이고, 280 은 자리의 사정이 아니라 **카드 자체의 값**이다.
302
+
303
+ > **설명 줄은 흰 바탕 2.53:1 로 본문 기준(4.5)에 못 미치는 것을 알고 그렇게 뒀다.** 무엇인지는
304
+ > 제목과 썸네일이 이미 말해 주고 이 줄은 곁들이는 설명이다. 손잡이(`descTone`)와 기본값을
305
+ > 나란히 놓고 그 수를 보신 뒤 사장님이 **기본값**으로 정하셨다. **코어에는 대비를 세는 못이
306
+ > 없다** — 막아 줄 것이 없는 자리라 사람이 정했고, 그 판단을 CSS 주석에 함께 적었다.
307
+
308
+ 색을 바꾼 자리에는 **되돌릴 때 볼 것**을 적어 뒀다 — 이 줄을 본문으로 승격하거나 옮길 일이
309
+ 생기면 `--color-gray`(4.8:1)가 그때의 기본이다.
310
+
311
+ 말귀_2 는 받으면 `MyWordTablePage.css` 세 줄과 `colorContrast` 예외 한 줄을 **통째로** 지운다
312
+ (`height: 100%` 만 남긴다 — 그건 그 화면 격자와 짝인 줄이라 성격이 다르다).
313
+
314
+ ### ㉠ 단어표 설정 값을 **코드로** — `'단어'` → `'word'`
315
+
316
+ 사장님 지시("㉠ 가라"). **실측이 가름을 바꿨다 — 내가 "DB 를 미는 일" 이라 한 것이 틀렸다.**
317
+
318
+ | 물음 | 답 |
319
+ |---|---|
320
+ | 저장되나? | **아니오** — `SavedWordTable` 에 그 값이 없고, 설정은 `useState` 뿐이다(`localStorage` 쓰기 자리 없음) |
321
+ | 소비자가 그 값을 **적나**? | **아니오** — 말귀의 세 줄은 `contentTypes={settings.contentTypes}` 처럼 **코어 훅이 준 값을 되넘기는** 것뿐 |
322
+ | `WordTableContentType`·`WordTableCutLine`·`PAGE_NUM_POSITION_VALUE` 등 여섯 이름 | 소비자 넷에서 **모두 0줄** |
323
+
324
+ 그래서 `DeviceSide` 와 성격이 다르다 — **코어가 만들고 코어가 읽는 값**이다. 받침도,
325
+ 마이그레이션도, 기다릴 결정도 없어 그냥 옮겼다. *무엇을 옮길 수 있나는 **어디에 저장되나**로
326
+ 갈린다 — 공개 타입이라는 것만으로는 못 가른다.*
327
+
328
+ | 전 | 후 |
329
+ |---|---|
330
+ | `WordTableContentType = '단어' \| '이미지' \| '표준 발음' \| '구어체 발음'` | `'word' \| 'image' \| 'standardPron' \| 'colloquialPron'` |
331
+ | `WordTableCutLine = '표시' \| '점선' \| '코너' \| '미표시'` | `'show' \| 'dashed' \| 'corner' \| 'hide'` |
332
+ | 켜고 끄는 절의 값 `'표시'`·`'미표시'` | `SHOW`·`HIDE`(= `'show'`·`'hide'`) — 절 여섯이 같은 짝을 쓰므로 한 자리에 뒀다 |
333
+ | 용지 방향 `value: '가로'` | `value: 'landscape'` — 타입(`WordTableOrientation`)이 이미 영어였다 |
334
+ | 쪽 번호 `value: '왼쪽'` | `value: 'left'` |
335
+
336
+ 보일 글은 전부 사전이 쥔다 — `displayLabel: t('wordTable.filter.image', '이미지')`. 사전은
337
+ 한 줄도 안 바꿨다. **FilterPanel 이 `value` 로 가리고 `displayLabel` 로 그리기 때문**에
338
+ 값만 바꾸면 화면은 그대로다.
339
+
340
+ ### 옮기는 표가 **사라졌다**
341
+
342
+ ```
343
+ 전: PAGE_NUM_POSITION_VALUE = { left: '왼쪽', … } ← 코드 → 한글
344
+ parsePageNumberPosition: value === '왼쪽' ? 'left' … ← 한글 → 코드
345
+ 후: parsePageNumberPosition: value === 'left' ? value … ← 모르는 값만 'none' 으로
346
+ ```
347
+
348
+ 변환표와 파서가 **둘 다** 있던 까닭이 "값이 한글" 이었다. 값을 코드로 돌리니 표가 항등식이
349
+ 되어 걷혔다. *옮기는 표가 사라지는 것이 옳게 옮긴 표시다.*
350
+
351
+ ### tsc 가 **같은 값을 두 자리에 적은 것**을 잡았다
352
+
353
+ `WordTableBlock` 이 공개 타입과 **같은 값을 따로** 적고 있었다(`type ContentType = '단어' | …`).
354
+ 한쪽만 고치면 조용히 갈리는 자리다 — 공개 타입을 가리키게 모았다.
355
+
356
+ ### 문을 뒀다 — `test/publicTypeValuesAreCodes1013.test.ts`
357
+
358
+ **내보내는 타입의 글자 값**에 한글이 없는지 센다. 글자가 곧 자료인 타입(조사·한글 자모·음소
359
+ 열쇠)은 자리와 까닭을 적어 허락한다. 심어서 잡히는 것도 봤다.
360
+
361
+ ### ㉣ 말에 따라 글귀를 고르는 가름을 **사전으로** — `KO_ORD` → `weekLabel`
362
+
363
+ 사장님 지시("모두 표준으로 맞춰라")로 **동작에 끼는 한글 값 87개·190자리**를 세어 네 몫으로 갈랐다.
364
+ ㉣(코드에 박힌 화면 글귀)를 먼저 했다.
365
+
366
+ **열어 보니 셋은 이미 표준이었다 — 내 앞 분류가 과장이었다.** 등급 설명 5줄과 요일 배열은
367
+ `t(…, { defaultValue })` **되돌이값**이고 사전(ko·en)에 둘 다 있다. 결과 판정
368
+ (`['ok','true','맞음','정답']`)은 **서버가 주는 값을 너그럽게 읽는 길목**이라 표준이 오히려
369
+ 권하는 꼴이다. 그래서 ㉣ 는 18자리가 아니라 **한 자리**였다.
370
+
371
+ | 전 | 후 |
372
+ |---|---|
373
+ | `export const KO_ORD = ['첫째', …]` + 쓰는 쪽마다 `lang.startsWith('ko') ? …주 : Week …` | `weekLabel(t, week)` — 사전이 쥔다 |
374
+
375
+ **같은 가름이 세 자리에 흩어져 있었다** — 코어 둘(`BarChart`·`useBarChartControl`)과 **말귀 하나**
376
+ (`periodLabel.ts`, 깊은 길로 `KO_ORD` 를 들여온다). 말이 하나 늘면 세 자리를 다 고쳐야 하고,
377
+ 한 자리만 안 고치면 화면에서 갈린다.
378
+
379
+ 사전으로 옮기니 **가름이 한 자리로 모이고, 그 자리로 흘러들던 인자까지 사라졌다** —
380
+ `barsFromControl(…, lang?)` 의 `lang`, `BarChart` 와 `useBarChartControl` 의 `i18n`. 셋 다
381
+ 쓰이지 않게 되어 걷었다. *가름을 옮기면 그 가름을 먹이던 길도 함께 걷힌다.*
382
+
383
+ ### 지킴이가 가르친 것 — 열쇠도 보간 변수도 **짝**이어야 한다
384
+
385
+ 처음엔 ko 에만 `weekOrdinal` 을 두고 `{{ord}}`(ko)·`{{n}}`(en) 으로 갈랐다. 사전 지킴이 셋이
386
+ 울었다 — **ko 에 있는 열쇠는 en 에도**(없으면 영어 화면에 한국어가 뜬다), **보간 변수도 같아야**
387
+ (로케일을 바꾸면 `{{변수}}` 가 그대로 보이거나 값이 빈다). 변수 하나(`ord`)로 모으고 en 에는
388
+ 숫자 표(`['1','2',…]`)를 두어 `Week {{ord}}` 가 같은 변수로 맞게 했다.
389
+
390
+ ### 문을 뒀다 — `test/langBranchInDict1013.test.ts`
391
+
392
+ `src` 에서 **말을 보고 가르는 자리**를 센다. 넷이 걸렸고 **넷 다 글귀 고르기가 아니었다** —
393
+ 로케일을 고르거나(`'ko-KR'`·`'en-US'` → Intl), 읽어 줄 목소리를 고르거나, **조사를 붙인다**
394
+ (붙일 조사가 그때 그 낱말의 받침에 달려 있어 사전이 못 한다). 자리마다 까닭을 적어 허락했다.
395
+
396
+ 그 문도 한 번 틀렸다 — 주석을 걷은 뒤의 줄 번호를 적어 **자리가 밀렸다**(엉뚱한 줄을 가리켰다).
397
+ 줄바꿈만 남기고 지운다.
398
+
399
+ ### 소비자 쪽
400
+
401
+ **말귀의 `periodLabel.ts` 는 `KO_ORD` 를 깊은 길로 들여온다** — 이 판을 받으면 그 import 가
402
+ 깨진다. `weekLabel(t, week)` 로 옮기면 그쪽 `lang` 가름도 함께 사라진다. 알렸다.
403
+
404
+ ### 옛 한글 이름 셋을 걷었다 — `칸순` · `정렬` · `칸`
405
+
406
+ 아침에 영어 이름 옆에 `@deprecated` 로 남겼던 셋을 그날 걷었다(사장님 지시). **걷기 전에
407
+ 소비자 넷을 직접 셌다** — 전해 들은 말로 걷지 않았다:
408
+
409
+ | 저장소 | 코어 `useTableSort` 자리 | `칸순` 들여오는 줄 |
410
+ |---|---|---|
411
+ | speechear-v2 | **열 자리, 전부 `{ sort, field }`** (남은 한글은 주석뿐) | **0** |
412
+ | speechear-v2/admin | 제 훅이다(`useTableSort(rows, pick)` → `{ sorted, sortField }`) | 0 |
413
+ | kscan · sound-localization-system · speechear-fe | 0 | 0 |
414
+
415
+ **이것은 공개 API 를 걷는 변경이다.** 다음 판을 받는 소비자가 그 이름을 쓰고 있으면 깨진다 —
416
+ 위 표가 그 수가 0 임을 센 근거다.
417
+
418
+ 두 지킴이의 표에서도 그 셋을 지웠다 — **걷었으면 표에서도 지운다**(`identifiersAreEnglish1013`
419
+ 의 셋째 조항이 그것을 센다). `publicApiEnglishNames1013` 은 꼴을 뒤집어, 이제 **걷은 이름이
420
+ 돌아오지 않았나**를 센다.
421
+
422
+ **그 자리에서 못 둘을 또 고쳤다.** dist 곁셈이 `length > 1000` 으로 세고 있었는데, 옛 이름 한
423
+ 줄을 걷자 956 이 되어 빨개졌다 — **재려던 것과 아무 관계 없는 숫자**였다(`declare function
424
+ compare` 가 있는지로 바꿨다). 그리고 그 곁셈이 아직 `칸순` 을 찾고 있었다.
425
+
426
+ **이제 저장소에 이름으로 남은 한글은 하나도 없다.** 남은 한글 식별자 42자리는 전부 **값**이다
427
+ (음소 열쇠·필터 절 이름·낱말로 찍는 열쇠).
428
+
429
+ ### 놀이터가 **쓸이를 건너뛰었다** — `tsconfig.test.json` 이 안 싣는다
430
+
431
+ 앞 꼭지에서 `test`·`playground` 를 쓸었다고 적었는데, **놀이터는 손도 안 댔다.**
432
+ 쓸이가 `tsconfig.test.json` 으로 프로그램을 짓는데 그 설정에 **놀이터 파일이 0개**다.
433
+ `지도에없음: 0` 이 떴고 관문 열여섯이 다 초록이었다 — **안 들른 것을 아무도 안 셌다.**
434
+
435
+ 설정이 싣는 것만 훑으면, 설정이 빼먹은 것은 **조용히 통과한다.**
436
+
437
+ 새로 세운 지킴이(`test/identifiersAreEnglish1013.test.ts`)가 그것을 잡았다. 그 문은 설정이
438
+ 아니라 **파일을 직접 훑는다.** 그래서 이번에 놀이터를 따로 돌렸다 — 선언 **640자리·2,182처·95파일**.
439
+
440
+ **저장소에서 한글로 남은 이름은 45자리뿐이고, 자리마다 까닭을 적었다.** 셋은 소비자가 옮기는
441
+ 동안 남긴 옛 이름(`칸순`·`정렬`·`칸`)이고 나머지는 **이름이 아니라 값**이다 — `sel['초성']`
442
+ 처럼 자료 열쇠이거나(`DeviceSide` 와 같은 몫), 필터 절 이름, 낱말로 찍는 열쇠다.
443
+
444
+ ### 놀이터에서 더 밟은 함정 둘
445
+
446
+ - **JSX 태그 둘**(`여백재기`·`차례로듣기`)이 소문자가 되어 HTML 요소로 읽혔다 — 못이 막았다.
447
+ - **가림 여섯** — `비` → `ratio`, `정렬` → `sort` 처럼 안쪽·바깥 묶음에 그 이름이 이미 있었다.
448
+
449
+ `setX` 꼴 **81개**는 밑이름에서 만들었다(`set접힘` → `setCollapsed`) — 손으로 적으면 어긋난다.
450
+
451
+ ### 안쪽 식별자를 영어로 — `test`·`playground` 3,278자리
452
+
453
+ 앞 꼭지가 `src` 였고 이번이 나머지다. **선언 3,278자리·21,460처·504파일.** 남은 한글 선언은 **0**.
454
+ 저장소 전체에서 한글로 남은 이름은 `칸순`(옛 공개 이름)과 음소 자료 열쇠뿐이다.
455
+
456
+ `src` 와 같은 연장을 썼고(`scripts/rename-identifiers.mjs`), 이름은 잦은 것부터 사전으로 지었다
457
+ (`scripts/identifier-map.test.json`, 1,585개). 시험 쪽 이름은 결이 고르다 — `표`·`글`·`줄`·`칸`.
458
+
459
+ ### 이번에 새로 밟은 함정 다섯 — 넷은 `tsc` 가 못 본다
460
+
461
+ | 함정 | 무엇이 깨졌나 | 어떻게 막았나 |
462
+ |---|---|---|
463
+ | **예약어** | `경우` → `case` 로 적어 그 파일이 통째로 문법 오류가 됐다 | 연장이 예약어를 거부한다 |
464
+ | **JSX 태그는 대문자** | `<화면 />` 을 `<page />` 로 만들자 React 가 **HTML 요소**로 읽었다(22자리) | JSX 태그로 서는 이름은 대문자인지 본다 |
465
+ | **React 가 가로채는 prop** | `열쇠` → `key` 로 보냈더니 **컴포넌트가 그 값을 못 받는다** — 쪽 되돌리기가 조용히 죽었다 | 그 자리는 `resetKey` 로 |
466
+ | **나를 가리는 안쪽 이름** | `뿌리` → `root` 가 안쪽 콜백의 postcss `root` 에 가려 `root.length` 가 엉뚱한 것을 가리켰다 | 가림 셈이 **안쪽 묶음**도 본다(아홉을 더 잡았다) |
467
+ | **자료 열쇠** | `{ 가: … }` 의 `가` 는 낱말 `'가'` 다 — 영어로 바꾸자 대립쌍 조회가 빈손이 됐다 | 성질 열쇠 중 **그 글자가 같은 파일의 문자열로도 쓰이는 것**은 값으로 보고 둔다 |
468
+
469
+ **그 가름은 자리별이 아니라 파일·이름 단위여야 했다.** 처음엔 줄 단위로 갈랐는데, 그러면 타입의
470
+ 성질은 한글로 남고 객체 리터럴만 영어가 되어 **반만 바뀐다**(두 파일이 그랬다). 한 파일에서 한
471
+ 이름은 **모두 바꾸거나 모두 둔다.**
472
+
473
+ **셈을 돌리는 자리도 한 번 틀렸다** — 자료 열쇠를 세는 스크립트가 **이미 바뀐 작업본**을 읽어
474
+ 한글 열쇠를 못 찾았다. 셈은 `git show HEAD:` 로 읽는다.
475
+
476
+ ### 안쪽 식별자를 영어로 — `src` 966자리 (사장님 지시 "코어도 전부 영어로")
477
+
478
+ 사장님 지시(2026-10-13 *"코어도 전부 영어로 — 한 번에"*). 말귀가 제 저장소에서 같은 일을 하며
479
+ 코어도 같은 결로 가는 게 좋겠다고 짚어 왔고, 사장님이 범위를 정하셨다.
480
+
481
+ **`src` 안의 한글 식별자 966 선언·3,801처·161파일을 옮겼다.** 공개 API 는 이미 0.322.0 에서
482
+ 영어로 갔으니, 이 판은 **소비자에게 아무 영향이 없다** — 안쪽 이름뿐이다.
483
+
484
+ **정규식으로 하지 않았다.** 한글은 `\w` 가 아니라 `\b` 로 경계를 못 잡고, 주석·문자열에 같은
485
+ 글자가 널려 있다. TypeScript 구문으로 선언 마디를 집고 `findRenameLocations` 로 **그 자리들만**
486
+ 고쳤다(`scripts/rename-identifiers.mjs`). 이름은 자리마다 뜻을 보고 손으로 골랐다 — `칸` 하나가
487
+ 열·셀·슬롯 셋으로 쓰이고 있어 전역 치환은 애초에 쓸 수 없었다(`scripts/identifier-map.src.json`).
488
+
489
+ **남긴 것은 다섯이다 — 넷은 이름이 아니라 값이다.**
490
+
491
+ | 남긴 이름 | 까닭 |
492
+ |---|---|
493
+ | `칸순` | 옛 공개 이름(`@deprecated`) — 소비자가 다 옮긴 뒤 걷는다 |
494
+ | `초성`·`모음`·`종성`·`활음` | `SectionKey` 의 **값**이고 `sel['초성']` 으로 객체를 찍는다 — `phonemeConvert` · `PhonemeSelectBlock` 이 그 글자로 견준다. `DeviceSide` 와 같은 몫으로, 바꾸려면 저장된 자료와 함께 간다 |
495
+
496
+ ### 이번에 잡은 것 — **`tsc` 가 못 보는 가림**
497
+
498
+ 쓸어내다 이런 것이 나왔다:
499
+
500
+ ```
501
+ before: if (끌었다 && 자리 != null && 자리 !== index) 옮기기(index, 자리)
502
+ after: if (didDrag && index != null && index !== index) move(index, index)
503
+ ```
504
+
505
+ `자리` 를 `index` 로 옮겼더니 **바깥 인자 `index` 를 가렸다.** `index !== index` 는 늘 거짓이고
506
+ `tsc` 는 0, 단어표 끌어 옮기기가 **조용히 죽었다.** 행동 시험 하나가 잡았다.
507
+
508
+ 그래서 **옮기기 전에 가림을 세는 문**을 뒀다(`scripts/check-name-shadowing.mjs`) — 선언을 품은
509
+ **바깥 묶음들**에 새 이름이 있나 본다. 아홉이 걸려 그중 넷이 참이었고(다섯은 성질 이름 — 성질은
510
+ 묶음을 안 탄다) 넷을 다른 이름으로 돌렸다. 같은 파일에 **이미 있던** 이름과 겹치나도 따로 센다
511
+ (`scripts/check-name-clash.mjs`) — 같은 묶음이면 `tsc` 가 잡지만 안쪽 묶음이면 조용하다.
512
+
513
+ ### 공개 멤버 둘이 줄임 꼴에 휩쓸렸다 — 또박또박 적는다
514
+
515
+ `return { …, 정렬, 칸 }` 의 **줄임 꼴**이 지역 이름을 따라가 `정렬`·`칸` 두 공개 멤버가
516
+ 사라졌다. 이제 `정렬: sortState` · `칸: fieldHead` 로 적는다 — 지역 이름이 무엇으로 바뀌어도
517
+ 공개 이름은 그대로다. **그리고 내 지킴이가 그것을 못 잡았다**(아래).
518
+
519
+ ### 못 셋을 고쳤다
520
+
521
+ | 못 | 무엇을 헛봤나 |
522
+ |---|---|
523
+ | `publicApiEnglishNames1013` | `toContain('정렬')` — 그 글자가 **화면 글귀 문자열**에 있어 멤버가 사라진 뒤에도 초록이었다. 이제 `정렬\s*:\s*sortState` 처럼 **그 꼴**을 본다 |
524
+ | 같은 못 | **기본값 있는 인자**(`= false`)를 안 봤다 — 그래서 `computePeaksFromUrl(…, 앞무음건너뛰기 = false)` 가 0.322.0 의 `audioPeaks.d.ts` 에 `앞무음건너뛰기?: boolean` 로 나갔다. 못을 고치니 바로 그 하나를 잡았고, 이 판에서 `skipLeadingSilence` 로 옮겼다 |
525
+ | `audioPlayerPadWiring` | 소스를 `search(…) + 40` 으로 끊고 있었다 — **그 숫자가 옛 이름 길이에 매여** 있어, 이름이 길어지자 재려던 `'lead'` 가 잘려 `921 < -1` 을 견주었다. 줄 끝으로 끊는다 |
526
+
10
527
  ## 0.322.0 — 공개 API 를 영어로
11
528
 
12
529
  ### 공개 API 의 이름을 영어로 — **값은 그대로**(그날 아침)