From f2662b35db08ec2e3051a4ece5eafc4b9831c2bf Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sun, 4 Oct 2026 01:05:33 +0900 Subject: [PATCH] Docs: Cite the python-multiplatform notebook E2E as the evidence for S5.2 to S5.5 python-multiplatform #26 (develop 844cf291) passes 20 of 20 against this package's develop wheel. SPEC S5.2 to S5.5 and section 8, and PROJECT section 3, now cite it for the widget render proofs, the declared root and shared state, and the TextField IME composition. Statuses stay partial, because the evidence is outside this repository. Docs only: counts unchanged (149/112 and 261 in the main checkout). Closes #95 --- PROJECT.md | 4 ++-- docs/SPEC.md | 26 +++++++++++++++++--------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/PROJECT.md b/PROJECT.md index f06716e..38c78da 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -28,7 +28,7 @@ Compose(`androidx.compose.*`)를 파이썬에서 쓰기 위한 pip 패키지 **` | 상태 | 항목 | |---|---| | 구현 | 매핑 매니페스트 `pythonx-map.toml`, `@Composable` 항등 데코레이터, 실제 디스크 패키지 `pythonx` 와 모듈 수준 규칙 기반 재노출(snake_case 이름·키워드·시그니처, 오버로드 디스패치, 값 클래스 허용 목록), 실제 Compose 1.11.1 에서 생성한 `.pyi` 스텁과 `py.typed`(wheel 포함, mypy 로 검사), `Alignment`/`Arrangement` 상수(평면 `Alignment.End` 와 묶음 `Alignment.Horizontal.End` 모두, #9), proxy 메서드의 snake_case 이름과 키워드 인자(`m.padding(padding_values=...)`; python-multiplatform #131 이 바인더 쪽에서 제공하므로 #131 이상 필요; Kotlin 이름도 실행 시 그대로 통하고 스텁은 snake_case 만), 함수이기도 한 Kotlin object 의 호출(`Color(0xFFFFFFFF)` 과 `Color.Red` 둘 다; #88, 설치한 wheel 에서도 테스트됨. Int/Long 오버로드 선택은 python-multiplatform #146) | -| 부분 | 선언형 앱 루트 `@app`·`state`·`app_root`(#11; 로직은 테스트됨, 바인더 경로는 #38 모양의 가짜 호스트로 테스트됨, 실제 Compose 증거는 E2E 모듈 #19; 숫자·문자열 상태는 python-multiplatform #69 부터 왕복됨), 배포 설정, Material 3 위젯(렌더 증거는 python-multiplatform 에만), `Icon`(import 만 됨)·`DefaultIcons`(`Icons.Default` alias, 가짜 호스트로만 테스트됨, 스텁에서는 `DefaultIcons.Add` 가 `ImageVector`; `DefaultIcons.Add` 로 쓴다; 노트북의 `DefaultIcons.Add()` 가 아님), `TextField(state=...)`·`TextFieldState`(#10; `pythonx.compose.foundation.text.input`, 가짜 호스트로 테스트됨, 노트북의 `text_state=`/`padding=` 는 `state=`/`modifier=Modifier.padding(8)`; IME 조합 증거는 python-multiplatform E2E #26) | +| 부분 | 선언형 앱 루트 `@app`·`state`·`app_root`(#11; 로직은 테스트됨, 바인더 경로는 #38 모양의 가짜 호스트로 테스트됨, 실제 Compose 증거는 python-multiplatform 노트북 E2E #26 에서 통과; 숫자·문자열 상태는 python-multiplatform #69 부터 왕복됨), 배포 설정, Material 3 위젯(렌더 증거는 python-multiplatform 노트북 E2E 에만, 설치한 wheel 에서 Kotlin 과 픽셀 일치), `Icon`(import 만 됨)·`DefaultIcons`(`Icons.Default` alias, 가짜 호스트로만 테스트됨, 스텁에서는 `DefaultIcons.Add` 가 `ImageVector`; `DefaultIcons.Add` 로 쓴다; 노트북의 `DefaultIcons.Add()` 가 아님), `TextField(state=...)`·`TextFieldState`(#10; `pythonx.compose.foundation.text.input`, 가짜 호스트로 테스트됨, 노트북의 `text_state=`/`padding=` 는 `state=`/`modifier=Modifier.padding(8)`; IME 조합 증거는 python-multiplatform 노트북 E2E #26 에서 통과) | | 계획 | `remember_saveable`·색 스킴·코루틴 스코프 | ### 테스트 기준선 @@ -61,7 +61,7 @@ GitHub 마일스톤과 같은 내용이다. 날짜는 2026-10-03 에 정했고, 날짜 근거: - **M1 (2주):** 바인더 계약(`inspect.signature`, `python_multiplatform.describe`)이 PythonMultiplatform `d00f413f` 에 착지했다. 남은 일은 이 저장소의 순수 Python 과 가짜 호스트 하네스뿐이라 JVM 빌드가 필요 없다. -- **M2 (M1 뒤 3주):** 위젯마다 JVM 렌더 테스트가 필요하다. 처음엔 Gradle 캐시 링크가 끊겼다고 보고 4주로 잡았으나, 캐시는 `PythonMultiplatform/.caches` 로 다시 걸려 Compose 픽스처가 통과하고 있었고, 노트북 관련 질문도 INTENT §5 로 결정됐다. 남은 위험은 `TextField(state=...)` 의 IME 조합 증거(python-multiplatform E2E #26)와, 앱 루트 교체를 Kotlin 컴포지션이 관찰하는 진입점이다. 진입점은 바인더 몫으로 정해졌다(2026-10-04): python-multiplatform 의 `PythonAppView` 가 `app_root` State 를 컴포지션 안에서 읽고, #26 진단에서 다시 선언한 루트가 실제로 다시 그려짐을 확인했다. +- **M2 (M1 뒤 3주):** 위젯마다 JVM 렌더 테스트가 필요하다. 처음엔 Gradle 캐시 링크가 끊겼다고 보고 4주로 잡았으나, 캐시는 `PythonMultiplatform/.caches` 로 다시 걸려 Compose 픽스처가 통과하고 있었고, 노트북 관련 질문도 INTENT §5 로 결정됐다. 남은 위험이던 `TextField(state=...)` 의 IME 조합 증거와 앱 루트 교체 진입점은 python-multiplatform 노트북 E2E 로 해소됐다(#26, develop `844cf291`; 2026-10-04 에 이 패키지의 `develop` wheel 로 20개 중 20개 통과, 아직 CI 는 돌리지 않음). 그래서 #9·#10·#11·#19 를 닫았다. 진입점은 바인더 몫으로 정해졌다(2026-10-04): python-multiplatform 의 `PythonAppView` 가 `app_root` State 를 컴포지션 안에서 읽고, #26 진단에서 다시 선언한 루트가 실제로 다시 그려짐을 확인했다. - **M3 (M2 뒤 2주):** 스텁은 M1 규칙만 있으면 만들 수 있어 M2 와 겹쳐 진행할 수 있다. 다만 노트북 위젯의 최종 이름이 M2 에서 정해지므로 마감은 M2 뒤로 둔다. ## 4. 구조 diff --git a/docs/SPEC.md b/docs/SPEC.md index 2f3112e..0f309f0 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -284,7 +284,7 @@ job. Tests: `tests/test_runtime_module.py::TheRuntimeSeam` (4 tests) and `::TheChaquopyMechanismIsGone` (3 tests), importing `pythonx.compose.runtime` the ordinary way. -### S5.4 The app root, `app` and `state`: `partial` (the binder path is tested against a fake host shaped after python-multiplatform #38; real Compose is the E2E module, issue #19) +### S5.4 The app root, `app` and `state`: `partial` (the binder path is tested here against a fake host shaped after python-multiplatform #38; the real-Compose evidence is outside this repository, in the notebook E2E module) The host draws with `PythonAppView(module = "pythonx.compose.runtime", attribute = "app_root")` (python-multiplatform #18 and #105; issue #11). The binder owns the redraw: `PythonAppView` reads @@ -315,7 +315,10 @@ reason. `pythonx` does none of this itself. Tests: `tests/test_app_root.py::TheL real `_new_state` through the binder's Python layer and `tests/fake_host.py`, whose `mutableStateOf` and `MutableState.value` rows are shaped after #38 (`KotlinSurface.kt`, `PythonxAdapter.kt`), not walked from a jar. It skips against a binder older than #38. That Compose observes a write and -recomposes is not shown here; it is the E2E module (python-multiplatform #26, issue #19). The stub carries `app`, +recomposes is not shown here. It is shown in python-multiplatform `ksp-fixtures/notebook-e2e` (#26, develop `844cf291`; 20 of 20 passed against this package's `develop` wheel on 2026-10-04, not run here, and no CI workflow runs it yet): `NotebookRootTest` declares the root, shares one state between the +notebook and the screen, and redeclares the root with `@app`, which reaches the screen within four +frames with no update call; with `@app` stubbed out, or the host handed the root once, the screen +does not change. The stub carries `app`, `state` and `app_root` (`tests/test_stubs.py`). ### S5.2 Material 3 composables reach Python without per-widget wrappers: `partial` @@ -331,8 +334,10 @@ toggle family, `TextField`, `Checkbox` and `Switch` are meant to be reached thro (commits `a6742a1c`, `3fde8bd6`, `cac8243f`, cited in `pythonx/compose/material3/__init__.py`) and was not re-run for this document. - `pythonx.compose.material3` re-exports `androidx.compose.material3` by the §3 rule, but the fake - host binds no material3 declaration, so no test here exercises one; the render proof per widget - is issue #9. + host binds no material3 declaration, so no test here exercises one. +- The render proof per widget is in python-multiplatform `ksp-fixtures/notebook-e2e` (#26, develop `844cf291`; 20 of 20 passed against this package's `develop` wheel on 2026-10-04, not run here, and no CI workflow runs it yet): `NotebookPracticeTest` (13 tests) draws `Text`, + `Button`, `Card`, `Icon`, `Column`, `Row` (with an `Arrangement` and an `Alignment.Horizontal`) + and `Spacer` from the installed wheel, each pixel-equal to the same composables drawn from Kotlin. ### S5.3 `DefaultIcons`, `Icon` and colour schemes: `partial` @@ -346,7 +351,7 @@ the notebook's `DefaultIcons.Add()` would call an `ImageVector`, which is not ca says the intent is `DefaultIcons.Add`. The rows are shaped after python-multiplatform `31c092f0` (`_PROPERTIES`, `_read_property`) and the ecosystem report for #37; no walked `material-icons-core` row exists here, and `Icon(DefaultIcons.Add, ...)` is drawn only in -python-multiplatform's render test. Tests: `tests/test_chain.py::DefaultIconsAlias`; stubs: the +python-multiplatform's render test and its notebook E2E (cells 28 to 30). Tests: `tests/test_chain.py::DefaultIconsAlias`; stubs: the `material3` stub assigns `DefaultIcons = Icons.Default` (typed `Icons.Filled`; `Add` is an `ImageVector`, python-multiplatform #68, whose package `androidx.compose.ui.graphics.vector` is the manifest row `pythonx.compose.ui.graphics.vector`), checked by `tests/test_typing.py::test_default_icons_imports_from_material3` and `test_a_default_icon_is_an_image_vector` @@ -391,7 +396,10 @@ Status `partial`: this is wired and tested against the fake host, whose rows are python-multiplatform `26485a02` (#73), not a walked jar (`tests/fake_host.py` says which fields). The proof that matters, typing a composing input-method sequence with no Python callback per keystroke and the field's text asserted afterwards, is python-multiplatform's E2E test (#26), judged over up to -four frames; it is not run here. Not modelled: a function-typed slot (`label`, `on_value_change`), which needs +four frames. It passes there (`NotebookTextFieldTest`; python-multiplatform `ksp-fixtures/notebook-e2e` (#26, develop `844cf291`; 20 of 20 passed against this package's `develop` wheel on 2026-10-04, not run here, and no CI workflow runs it yet)): a Hangul sequence +keeps its composing range in Compose, no Python function starts while it types, and the notebook +reads the committed text. Not exercised there: the AWT layer that decodes an `InputMethodEvent`, +which needs a window. Not modelled here: a function-typed slot (`label`, `on_value_change`), which needs the binder's `NewFunction` rows. ## 6. Modifiers: extension functions as methods @@ -532,10 +540,10 @@ the binder lists no member that would win over these groups. | `modifier` | lower-case instance from `pythonx.compose.ui` | INTENT §5.4: not provided; written `Modifier` | | `color=0xFFFF0000` | ARGB integer for a colour | INTENT §5.5: not accepted; written `Color(0xFFFF0000)` | | `Spacer(start=..., top=...)` | spacing parameters | INTENT §5.6: not supported; written `Spacer(modifier=Modifier.padding(...))` | -| `Column`, `Row`, `Spacer` | imported from `pythonx.compose.material3` | INTENT §5.2: served from both, by the manifest's `[aliases]` (`tests/test_chain.py::ManifestAliases`); render proof pending (#9) | -| `TextField(text_state=..., padding=8)` | state object and spacing parameter | INTENT §5.8: written `TextField(state=..., modifier=Modifier.padding(8))`; S5.5, `partial` (#10) | +| `Column`, `Row`, `Spacer` | imported from `pythonx.compose.material3` | INTENT §5.2: served from both, by the manifest's `[aliases]` (`tests/test_chain.py::ManifestAliases`); rendered from the installed wheel in python-multiplatform's notebook E2E (#9, S5.2) | +| `TextField(text_state=..., padding=8)` | state object and spacing parameter | INTENT §5.8: written `TextField(state=..., modifier=Modifier.padding(8))`; S5.5, `partial`: IME evidence only in python-multiplatform's notebook E2E (#10) | | `Card`, `Button`, `Text`, `TextField` | as above | S5.2 | -| `main.App`, `App.update(...)` | live screen replacement from a cell | INTENT §5.1: `@app` declares the root, a redeclaration replaces it, no update function (S5.4, `partial`, issue #11) | +| `main.App`, `App.update(...)` | live screen replacement from a cell | INTENT §5.1: `@app` declares the root, a redeclaration replaces it, no update function (S5.4, `partial`: real-Compose evidence only in python-multiplatform's notebook E2E, #11) | ## 9. Repository hygiene (not behaviour)