Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@

## [Unreleased]

## [0.0.12] — 2026-09-28

### Added

- **`patches/react-native@0.86.3.patch`** — Element Inspector 의 Touchables 토글이 앱을 리마운트해
내비게이션·상태가 날아가던 것을 없앤다(dev 전용). 업스트림 PR facebook/react-native#58705 와 같은 코드,
0.88+ 로 올릴 때 제거. 이력·조건은 `patches/README.md`

### Changed

- Expo SDK 57 패치 버전을 `expo-doctor` 기대치로 올렸다 — `expo`, `expo-router`, `expo-constants`,
`expo-build-properties`, `@expo/metro-runtime`. 21/21 통과

## [0.0.11] — 2026-09-22

### Fixed
Expand Down
26 changes: 26 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

| 무엇이 있나 | 그 버전 |
| -------------------------------------------------------------------------------- | --------------------------------- |
| `patches/react-native@0.86.3.patch` 가 있다 | v0.0.12 |
| `semantic.css` 에 `@variant light` 가 있고 `src/lib/theme/use-colors.ts` 가 있다 | v0.0.11 |
| `src/lib/theme/use-colors.ts` 가 있고 `semantic.css` 에 `@variant light` 가 없다 | v0.0.10 (부팅 에러 — 바로 올린다) |
| `targets/notification-service/` 가 있고 `package.json` 에 `engines.pnpm` | v0.0.9 |
Expand All @@ -19,6 +20,31 @@

---

## v0.0.10 · v0.0.11 에서 올리기 — Expo 패치 버전과 RN 인스펙터 패치

둘 다 작다. Expo 패치 버전은 `expo-doctor` 가 알려주는 대로 올린다:

```bash
npx expo install --fix # expo · expo-router · expo-constants · expo-build-properties · @expo/metro-runtime
npx expo-doctor # 21/21
```

`pnpm-workspace.yaml` 에 `minimumReleaseAgeExclude` 가 있으면 새 버전을 거기 넣는다.

RN 패치(dev 전용 — Element Inspector 의 Touchables 토글이 앱을 리마운트하던 것)는 태그에서 파일을 그대로 받는다:

```bash
T=https://raw.githubusercontent.com/LESANF/react-native-template-lesa/v0.0.12
curl -fsSL "$T/patches/react-native@0.86.3.patch" -o "patches/react-native@0.86.3.patch"
curl -fsSL "$T/patches/README.md" -o patches/README.md
# pnpm-workspace.yaml 의 patchedDependencies 에:
# react-native@0.86.3: patches/react-native@0.86.3.patch
pnpm install
grep -c useIsEnabled node_modules/react-native/Libraries/Pressability/PressabilityDebug.js # 1 이상이면 적용
```

안 쓰면 안 넣어도 된다 — 인스펙터를 안 쓰는 프로젝트엔 차이가 없다.

## v0.0.9 에서 올리기 — Input · 탭 색 · 색 규칙

- **`Input` 의 `className` 대상이 바뀌었다.** 입력 자체가 아니라 감싸는 뷰(높이·테두리·배경)에 붙는다.
Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "lesa-expo-template",
"main": "index.js",
"version": "0.0.11",
"version": "0.0.12",
"license": "MIT",
"engines": {
"node": ">=22",
Expand Down Expand Up @@ -56,7 +56,7 @@
},
"dependencies": {
"@bacons/apple-targets": "4.0.6",
"@expo/metro-runtime": "~57.0.15",
"@expo/metro-runtime": "~57.0.16",
"@hot-updater/react-native": "^0.36.8",
"@react-native-community/netinfo": "12.0.1",
"@react-native-firebase/app": "26.3.3",
Expand All @@ -65,18 +65,18 @@
"@tanstack/react-query": "^5.101.2",
"axios": "^1.18.1",
"dayjs": "^1.11.23",
"expo": "~57.0.23",
"expo": "~57.0.24",
"expo-blur": "~57.0.3",
"expo-build-properties": "~57.0.20",
"expo-constants": "~57.0.18",
"expo-build-properties": "~57.0.21",
"expo-constants": "~57.0.19",
"expo-dev-client": "~57.0.19",
"expo-device": "~57.0.2",
"expo-font": "~57.0.4",
"expo-glass-effect": "~57.0.3",
"expo-image": "~57.0.5",
"expo-linking": "~57.0.10",
"expo-localization": "~57.0.2",
"expo-router": "~57.0.21",
"expo-router": "~57.0.22",
"expo-splash-screen": "~57.0.9",
"expo-symbols": "~57.0.3",
"expo-system-ui": "~57.0.4",
Expand Down
38 changes: 34 additions & 4 deletions patches/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ pnpm 패치. `pnpm-workspace.yaml` 의 `patchedDependencies` 에 등록돼 있

패치를 넣거나 뺄 때 한 줄씩 남긴다. 지운 것도 지우지 않는다 — 왜 있었는지가 다음 사람의 판단 근거다.

| 날짜 | 패키지 | 왜 | 상태 |
| ---------- | ----------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
| 2026-09-15 | `expo@57.0.22` | Xcode 27 SDK 가 UIScene 을 요구 — AppDelegate 에 scene 진입점 주입 | **2026-09-16 제거.** 공식 `expo-build-properties` `ios.enableSceneSupport` 로 대체(v0.0.6) |
| 2026-09-17 | `@bacons/apple-targets@4.0.6` | 타깃이 이미 있는 prebuild(`--no-clean`)에서 크래시 — 아래 절 | **유지 중.** 업스트림 #206 미리뷰, 5.0.0 도 같음 |
| 날짜 | 패키지 | 왜 | 상태 |
| ---------- | ----------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| 2026-09-15 | `expo@57.0.22` | Xcode 27 SDK 가 UIScene 을 요구 — AppDelegate 에 scene 진입점 주입 | **2026-09-16 제거.** 공식 `expo-build-properties` `ios.enableSceneSupport` 로 대체(v0.0.6) |
| 2026-09-17 | `@bacons/apple-targets@4.0.6` | 타깃이 이미 있는 prebuild(`--no-clean`)에서 크래시 — 아래 절 | **유지 중.** 업스트림 #206 미리뷰, 5.0.0 도 같음 |
| 2026-09-28 | `react-native@0.86.3` | 인스펙터 Touchables 토글이 앱 트리를 리마운트해 내비게이션·상태가 날아감 — 아래 절 | **유지 중.** 업스트림 PR #58705 리뷰 대기 |

## `@bacons/apple-targets@4.0.6`

Expand All @@ -35,3 +36,32 @@ grep -c previousConfigurationList node_modules/@bacons/apple-targets/build/with-

**검증** — 푸시가 켜진 상태에서 `npx expo prebuild -p ios --no-install` 을 두 번(두 번째는 `--no-clean`)
돌려 둘 다 `Finished prebuild` 면 살아 있다.

## `react-native@0.86.3`

**무엇** — 인앱 Element Inspector 의 **Touchables** 버튼이 `PressabilityDebug` 플래그를 바꾼 뒤
`AppContainer` 루트 뷰의 `key` 를 올려 앱 전체를 리마운트한다. JS 번들은 유지되지만 컴포넌트 상태와
내비게이션 스택이 사라져 첫 화면으로 돌아간다. 2016년(d5c1de770) 구현이 그대로 남은 것.
패치는 플래그를 구독 가능한 스토어로 바꾸고(`useIsEnabled`), 점선 박스(`PressabilityDebugView`)와
`onPress` 있는 `Text` 가 그것을 구독하게 해서 리마운트 호출을 없앤다. `__DEV__` 밖에서는 훅이 붙지 않는다.

**언제 터지나** — Dev Menu → Toggle Element Inspector → Touchables 를 누를 때. 릴리즈 빌드에는
인스펙터가 없어 무관.

**동작 차이 하나** — `onPress` 와 `disabled` 를 같이 가진 `Text` 는 이제 magenta 로 칠해지지 않는다
(누를 수 없으므로).

**업스트림** — PR [#58705](https://github.com/react/react-native/pull/58705)
(react/react-native, 2026-09-28 올림). 반영돼도 `main` → 0.88+ 이고 0.86/0.87 로는 백포트되지
않으므로, 그 버전으로 올릴 때까지 패치가 필요하다. 리뷰에서 코드가 바뀌면 이 패치도 맞춰 갱신한다.
칼하트 앱에도 같은 패치가 있다.

**지우는 조건** — RN 버전을 올린 뒤 패치 미적용 원본에서 아래가 1 이상이면 수정이 들어온 것이다.
그때 패치와 등록 줄을 지운다.

```bash
grep -c useIsEnabled node_modules/react-native/Libraries/Pressability/PressabilityDebug.js
```

**검증** — 시뮬레이터에서 첫 화면이 아닌 화면으로 이동 → Dev Menu → Toggle Element Inspector →
Touchables. 화면이 그대로 남고 터치 영역에 점선 박스가 나오면 살아 있다(패치 전에는 첫 화면으로 튕긴다).
243 changes: 243 additions & 0 deletions patches/react-native@0.86.3.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
diff --git a/Libraries/Pressability/PressabilityDebug.js b/Libraries/Pressability/PressabilityDebug.js
index 90b83e49e51c9d1d4102a37b0b88458983bd52e5..d4c42daef71cc5d7f6fc9d7dd6e6d2f551a43954 100644
--- a/Libraries/Pressability/PressabilityDebug.js
+++ b/Libraries/Pressability/PressabilityDebug.js
@@ -36,8 +36,9 @@ type Props = Readonly<{
*
*/
export function PressabilityDebugView(props: Props): React.Node {
+ const enabled = useIsEnabled();
if (__DEV__) {
- if (isEnabled()) {
+ if (enabled) {
const normalizedColor = normalizeColor(props.color);
if (typeof normalizedColor !== 'number') {
return null;
@@ -70,6 +71,7 @@ export function PressabilityDebugView(props: Props): React.Node {
}

let isDebugEnabled = false;
+const listeners: Set<() => void> = new Set();

export function isEnabled(): boolean {
if (__DEV__) {
@@ -80,6 +82,30 @@ export function isEnabled(): boolean {

export function setEnabled(value: boolean): void {
if (__DEV__) {
+ if (isDebugEnabled === value) {
+ return;
+ }
isDebugEnabled = value;
+ listeners.forEach(listener => listener());
}
}
+
+function subscribe(listener: () => void): () => void {
+ listeners.add(listener);
+ return () => {
+ listeners.delete(listener);
+ };
+}
+
+function useIsEnabledDev(): boolean {
+ return React.useSyncExternalStore(subscribe, isEnabled);
+}
+
+/**
+ * Like `isEnabled`, but re-renders the calling component when the value
+ * changes, so toggling it does not require remounting the app. Outside of
+ * `__DEV__` it is `isEnabled` and uses no hooks.
+ */
+export const useIsEnabled: () => boolean = __DEV__
+ ? useIsEnabledDev
+ : isEnabled;
diff --git a/Libraries/ReactNative/AppContainer-dev.js b/Libraries/ReactNative/AppContainer-dev.js
index db3e0868daa7f66c6f057bd7a7e5a03385af91aa..956cf0d0f28cf8fe97814c5631a2ce95f0b6eca2 100644
--- a/Libraries/ReactNative/AppContainer-dev.js
+++ b/Libraries/ReactNative/AppContainer-dev.js
@@ -27,7 +27,7 @@ import {RootTagContext, createRootTag} from './RootTag';
import * as React from 'react';
import {useRef} from 'react';

-const {useEffect, useState, useCallback} = React;
+const {useEffect, useState} = React;

const reactDevToolsHook: ReactDevToolsGlobalHook = (window as $FlowFixMe)
.__REACT_DEVTOOLS_GLOBAL_HOOK__;
@@ -49,7 +49,6 @@ type ExternalInspection = {

type InspectorDeferredProps = {
inspectedViewRef: InspectedViewRef,
- onInspectedViewRerenderRequest: () => void,
reactDevToolsAgent?: ReactDevToolsAgent,
devMenuInspectorOpen: boolean,
externalInspection: ExternalInspection,
@@ -57,7 +56,6 @@ type InspectorDeferredProps = {

const InspectorDeferred = ({
inspectedViewRef,
- onInspectedViewRerenderRequest,
reactDevToolsAgent,
devMenuInspectorOpen,
externalInspection,
@@ -70,7 +68,6 @@ const InspectorDeferred = ({
return (
<Inspector
inspectedViewRef={inspectedViewRef}
- onRequestRerenderApp={onInspectedViewRerenderRequest}
reactDevToolsAgent={reactDevToolsAgent}
devMenuInspectorOpen={devMenuInspectorOpen}
externalInspection={externalInspection}
@@ -116,7 +113,6 @@ const AppContainer = ({
debuggingOverlayRef,
);

- const [key, setKey] = useState(0);
const [shouldRenderInspector, setShouldRenderInspector] = useState(false);
const [reactDevToolsAgent, setReactDevToolsAgent] =
useState<ReactDevToolsAgent | void>(reactDevToolsHook?.reactDevtoolsAgent);
@@ -155,7 +151,6 @@ const AppContainer = ({
<View
collapsable={reactDevToolsAgent == null && !shouldRenderInspector}
pointerEvents="box-none"
- key={key}
style={rootViewStyle || styles.container}
ref={innerViewRef}>
{children}
@@ -170,11 +165,6 @@ const AppContainer = ({
);
}

- const onInspectedViewRerenderRequest = useCallback(
- () => setKey(k => k + 1),
- [],
- );
-
return (
<RootTagContext.Provider value={createRootTag(rootTag)}>
<View
@@ -196,7 +186,6 @@ const AppContainer = ({
externalInspection.externalInspectingEnabled) && (
<InspectorDeferred
inspectedViewRef={innerViewRef}
- onInspectedViewRerenderRequest={onInspectedViewRerenderRequest}
reactDevToolsAgent={reactDevToolsAgent}
devMenuInspectorOpen={shouldRenderInspector}
externalInspection={externalInspection}
diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js
index 8e7b29cb609698cbd6a1852a4276fda920f860ba..f780a89679a67ee1d8f2eff7bb1f7cd6a22aa38b 100644
--- a/Libraries/Text/Text.js
+++ b/Libraries/Text/Text.js
@@ -168,11 +168,6 @@ const TextImpl: component(
selectionColor != null ? processColor(selectionColor) : undefined;

let _style = style;
- if (__DEV__) {
- if (PressabilityDebug.isEnabled() && onPress != null) {
- _style = [style, {color: 'magenta'}];
- }
- }

let _numberOfLines = numberOfLines;
if (_numberOfLines != null && !(_numberOfLines >= 0)) {
@@ -487,10 +482,15 @@ component PressableVirtualText(
const [isHighlighted, eventHandlersForText] = useTextPressability(
textPressabilityProps,
);
+ const style = usePressabilityDebugStyle(
+ textProps.style,
+ textPressabilityProps.onPress,
+ );

return (
<NativeVirtualText
{...textProps}
+ style={style}
{...eventHandlersForText}
isHighlighted={isHighlighted}
isPressable={true}
@@ -515,12 +515,18 @@ component PressableText(
textPressabilityProps,
);

+ const style = usePressabilityDebugStyle(
+ textProps.style,
+ textPressabilityProps.onPress,
+ );
+
const NativeComponent =
selectable === true ? NativeSelectableText : NativeText;

return (
<NativeComponent
{...textProps}
+ style={style}
{...eventHandlersForText}
isHighlighted={isHighlighted}
isPressable={true}
@@ -529,6 +535,19 @@ component PressableText(
);
}

+/**
+ * Colors pressable text when press targets are shown by the Inspector.
+ */
+function usePressabilityDebugStyle(
+ style: NativeTextProps['style'],
+ onPress: TextPressabilityProps['onPress'],
+): NativeTextProps['style'] {
+ const isDebugEnabled = PressabilityDebug.useIsEnabled();
+ return isDebugEnabled && onPress != null
+ ? [style, {color: 'magenta'}]
+ : style;
+}
+
const userSelectToSelectableMap = {
auto: true,
text: true,
diff --git a/src/private/devsupport/devmenu/elementinspector/Inspector.js b/src/private/devsupport/devmenu/elementinspector/Inspector.js
index e56629c3257445bf9ba06a4f376fcf11513460e0..ade1df78c0db01628a3e993dda5a6b63a356ed40 100644
--- a/src/private/devsupport/devmenu/elementinspector/Inspector.js
+++ b/src/private/devsupport/devmenu/elementinspector/Inspector.js
@@ -54,7 +54,6 @@ type ExternalInspection = {

type Props = {
inspectedViewRef: InspectedViewRef,
- onRequestRerenderApp: () => void,
reactDevToolsAgent?: ReactDevToolsAgent,
devMenuInspectorOpen: boolean,
externalInspection: ExternalInspection,
@@ -62,7 +61,6 @@ type Props = {

function Inspector({
inspectedViewRef,
- onRequestRerenderApp,
reactDevToolsAgent,
devMenuInspectorOpen,
externalInspection,
@@ -77,6 +75,7 @@ function Inspector({
const [selectionIndex, setSelectionIndex] = useState<?number>(null);
const [elementsHierarchy, setElementsHierarchy] =
useState<?ElementsHierarchy>(null);
+ const touchTargeting = PressabilityDebug.useIsEnabled();

// Derive inspecting state: external inspection forces it on, otherwise use local state
const isInspecting = externalInspectingEnabled || inspectingEnabled;
@@ -158,7 +157,6 @@ function Inspector({

const setTouchTargeting = (val: boolean) => {
PressabilityDebug.setEnabled(val);
- onRequestRerenderApp();
};

const panelContainerStyle =
@@ -186,7 +184,7 @@ function Inspector({
hierarchy={elementsHierarchy}
selection={selectionIndex}
setSelection={setSelection}
- touchTargeting={PressabilityDebug.isEnabled()}
+ touchTargeting={touchTargeting}
setTouchTargeting={setTouchTargeting}
/>
</SafeAreaView>
Loading
Loading