You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit e6db167
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: PROJECT.md
+5-5Lines changed: 5 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -28,15 +28,15 @@ Compose(`androidx.compose.*`)를 파이썬에서 쓰기 위한 pip 패키지 **`
28
28
| 상태 | 항목 |
29
29
|---|---|
30
30
| 구현 | 매핑 매니페스트 `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 `describe_member` 필요; Kotlin 이름도 실행 시 그대로 통하고 스텁은 snake_case 만) |
31
-
| 부분 | 선언형 앱 루트 `@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()` 가 아님) |
31
+
| 부분 | 선언형 앱 루트 `@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)|
describe_member 없는 체크아웃 같은 수, 그중 7 개 건너뜀 (python-multiplatform #54 이전)
41
41
describe(module, name) 없는 체크아웃 같은 수, 그중 13 개 건너뜀 (python-multiplatform #36 이전)
42
42
그보다 오래된 체크아웃 거기에 5 개 더 건너뜀 (member resolver 없음)
@@ -45,7 +45,7 @@ python3 -m pytest tests -q
45
45
`describe_member`(python-multiplatform #54)가 없는 바인더에서는 메서드 키워드를 확인하는 7 개가(Kotlin 키워드로 되돌아가는 2 개는 실행됨), `describe(module, name)`(python-multiplatform #36)이 없는 바인더에서는 묶음 상수를 확인하는 13 개가,
46
46
member resolver(python-multiplatform `ba4c6f49`)가 없는 바인더에서는 proxy 의 snake_case 메서드를 부르는
47
47
5 개가 더 건너뛴다. 두 환경 모두의 건너뜀 1 개는 worktree 에 없는 `UI.ipynb` 의 테스트다. 건너뜀은 통과가 아니다.
48
-
체크아웃 없이 통과하는 132 개 중 59 개는 타입 스텁과 wheel 을 검사하고(SPEC S1.2), 나머지 다수는 *부재*(옛 토큰·삭제된
48
+
체크아웃 없이 통과하는 134 개 중 61 개는 타입 스텁과 wheel 을 검사하고(SPEC S1.2), 나머지 다수는 *부재*(옛 토큰·삭제된
49
49
파일이 없음)를 확인하는 것이라 기능 진척으로 세지 않는다.
50
50
51
51
### 마일스톤
@@ -61,7 +61,7 @@ GitHub 마일스톤과 같은 내용이다. 날짜는 2026-10-03 에 정했고,
61
61
날짜 근거:
62
62
63
63
-**M1 (2주):** 바인더 계약(`inspect.signature`, `python_multiplatform.describe`)이 PythonMultiplatform `d00f413f` 에 착지했다. 남은 일은 이 저장소의 순수 Python 과 가짜 호스트 하네스뿐이라 JVM 빌드가 필요 없다.
64
-
-**M2 (M1 뒤 3주):** 위젯마다 JVM 렌더 테스트가 필요하다. 처음엔 Gradle 캐시 링크가 끊겼다고 보고 4주로 잡았으나, 캐시는 `PythonMultiplatform/.caches` 로 다시 걸려 Compose 픽스처가 통과하고 있었고, 노트북 관련 질문도 INTENT §5 로 결정됐다. 남은 위험은 Compose 1.11 의 `TextFieldState` 확인과, 앱 루트 교체를 Kotlin 컴포지션이 관찰하는 진입점(바인더 몫일 수 있음)이다.
64
+
-**M2 (M1 뒤 3주):** 위젯마다 JVM 렌더 테스트가 필요하다. 처음엔 Gradle 캐시 링크가 끊겼다고 보고 4주로 잡았으나, 캐시는 `PythonMultiplatform/.caches` 로 다시 걸려 Compose 픽스처가 통과하고 있었고, 노트북 관련 질문도 INTENT §5 로 결정됐다. 남은 위험은 `TextField(state=...)` 의 IME 조합 증거(python-multiplatform E2E #26)와, 앱 루트 교체를 Kotlin 컴포지션이 관찰하는 진입점(바인더 몫일 수 있음)이다.
65
65
-**M3 (M2 뒤 2주):** 스텁은 M1 규칙만 있으면 만들 수 있어 M2 와 겹쳐 진행할 수 있다. 다만 노트북 위젯의 최종 이름이 M2 에서 정해지므로 마감은 M2 뒤로 둔다.
Copy file name to clipboardExpand all lines: README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -159,7 +159,7 @@ standalone desktop toolkit.
159
159
| Type stubs (`.pyi`) from real Compose 1.11.1, `py.typed`| ✅ implemented and checked with mypy; many types are still `Any` (#12) |
160
160
| Distribution (`pythonx-compose`) | 🟡 partial — the wheel carries the manifest, the re-export rule, the stubs and `py.typed`|
161
161
| Declared app root (`@app`) and Pythonic state (`state`), no update call | 🟡 partial — binder path tested against a fake host shaped after python-multiplatform #38; real Compose is the E2E module (#11, #19); numbers and strings round-trip through `state`|
|`TextField(state=...)` with `TextFieldState` (`pythonx.compose.foundation.text.input`) | 🟡 partial — tested against a fake host shaped after python-multiplatform #73; the input-method (IME) composing proof is python-multiplatform E2E #26 (#10) |
163
163
|`DefaultIcons` (`Icons.Default`), written `DefaultIcons.Add`| 🟡 partial — tested against a fake host shaped after python-multiplatform #37/#38; `Icon(DefaultIcons.Add, …)` is drawn in python-multiplatform's render test |
Copy file name to clipboardExpand all lines: docs/SPEC.md
+46-5Lines changed: 46 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,22 +21,22 @@ Run from a worktree with `python -m pytest tests -q -rs` (pytest 8, mypy 2.4, CP
21
21
22
22
| Environment | Result |
23
23
|---|---|
24
-
| No `PythonMultiplatform` checkout found |**132 passed, 99 skipped**, 165 subtests passed |
25
-
| python-multiplatform `develop` at `31c092f0` or later (property rows, #38; `describe_member`, #54; besides `add_member_resolver` and `describe(module, name)`) |**230 passed, 1 skipped**, 196 subtests passed |
24
+
| No `PythonMultiplatform` checkout found |**134 passed, 112 skipped**, 173 subtests passed |
25
+
| python-multiplatform `develop` at `31c092f0` or later (property rows, #38; `describe_member`, #54; besides `add_member_resolver` and `describe(module, name)`) |**245 passed, 1 skipped**, 204 subtests passed |
26
26
| an older checkout, without `describe_member` (python-multiplatform #54) | the same, with 7 more skipped |
27
27
| an older one, without `describe(module, name)` (python-multiplatform #36) | with 13 more skipped again |
28
28
| an older one still, without `add_member_resolver`| with 5 more skipped again |
29
29
30
30
Measured in a worktree, which has `.tmp/kotlin-stubs.zip` but no `UI.ipynb`. Without a checkout, the
31
-
97 tests that install the binder's layers through `tests/adapter.py` skip, plus the notebook test.
31
+
110 tests that install the binder's layers through `tests/adapter.py` skip, plus the notebook test.
32
32
Against a binder without `describe(module, name)`, the 13 that check grouped constants (§7, S7.1)
33
33
skip. Against a binder without `describe_member`, the 7 that check a method's keywords (S4.1) skip; the 2
34
34
that check the fallback (Kotlin keywords, names resolved) run. Against a binder older than `ba4c6f49`, the 5 that call a snake_case method on a proxy the
35
35
binder returned skip as well, because that needs its member resolver (python-multiplatform #17). The skip against a current checkout is `UI.ipynb`'s (§2). A binder older than #38 skips the 5
36
-
`TheBinderPath` tests (S5.4). `tests/test_typing.py` skips where mypy is not installed. A skip is not a
36
+
`TheBinderPath` tests (S5.4). A binder without the `@Composable` binding (`push_composer`) skips the 13 text-field tests (S5.5). `tests/test_typing.py` skips where mypy is not installed. A skip is not a
37
37
pass.
38
38
39
-
Of the 132 that pass without a checkout, 59 check the type stubs and the wheel (S1.2); most of the
39
+
Of the 134 that pass without a checkout, 61 check the type stubs and the wheel (S1.2); most of the
40
40
rest assert **absence** (a retired token, a deleted file, a docstring that exists). Those are listed
`TextField(text_state=...)` and `TextField(..., padding=8)` are a `TypeError` (the binder reports no such
343
+
parameter) and a type error in the stubs; the notebook's spelling is `state=` and `modifier=Modifier.padding(8)`
344
+
(INTENT §5.8). The `value` / `on_value_change` overloads still exist and are reached by those keywords.
345
+
A `TextRange` parameter is the binder's, and `TextRange(2)` is shadowed by a companion module
346
+
(python-multiplatform #78); `TextRange__Int(2)` is the spelling meanwhile.
347
+
348
+
| Behaviour | Test |
349
+
|---|---|
350
+
|`TextFieldState("hi").text == "hi"`, a `str`; the initial text is optional; Kotlin's `initialText=` works |`tests/test_text_field.py::TheTextFieldState`|
351
+
|`set_text_and_place_cursor_at_end` and `clear_text` write |`…::test_set_text_and_place_cursor_at_end_writes_it`, `test_clear_text_empties_it`|
352
+
|`remember_text_field_state` is a composable: it needs a composer |`…::test_remember_text_field_state_is_the_composable_that_makes_one`, `…_needs_a_composition`|
353
+
|`state=` selects the state overload; keywords are snake_case; `modifier=` reaches it; `value=` selects the `String` overload |`tests/test_text_field.py::TheStateOverload`|
354
+
|`text_state=` and `padding=` are a `TypeError`|`…::test_text_state_is_not_a_parameter`, `test_padding_is_not_a_parameter`|
355
+
| The stubs type `TextFieldState`, `.text` as `str`, and `TextField(state=...)`; `text_state=` fails |`tests/test_typing.py::test_a_text_field_state_types_the_state_overload`, `test_a_text_field_without_a_kotlin_counterpart_fails`|
356
+
357
+
Status `partial`: this is wired and tested against the fake host, whose rows are shaped after the stubs of
358
+
python-multiplatform `26485a02` (#73), not a walked jar (`tests/fake_host.py` says which fields). The
359
+
proof that matters, typing a composing input-method sequence with no Python callback per keystroke
360
+
and the field's text asserted afterwards, is python-multiplatform's E2E test (#26), judged over up to
361
+
four frames; it is not run here. Not modelled: a function-typed slot (`label`, `on_value_change`), which needs
362
+
the binder's `NewFunction` rows.
363
+
324
364
## 6. Modifiers — extension functions as methods
325
365
326
366
### S6.1 `Modifier` extensions are methods on the receiver proxy — `implemented`
@@ -453,6 +493,7 @@ the binder lists no member that would win over these groups.
453
493
|`color=0xFFFF0000`| ARGB integer for a colour | INTENT §5.5: not accepted; written `Color(0xFFFF0000)`|
454
494
|`Spacer(start=..., top=...)`| spacing parameters | INTENT §5.6: not supported; written `Spacer(modifier=Modifier.padding(...))`|
455
495
|`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) |
496
+
|`TextField(text_state=..., padding=8)`| state object and spacing parameter | INTENT §5.8: written `TextField(state=..., modifier=Modifier.padding(8))`; S5.5, `partial` (#10) |
456
497
|`Card`, `Button`, `Text`, `TextField`| as above | S5.2 |
457
498
|`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) |
<pdata-lang="en">Expect <strong>132 passed, 99 skipped</strong>. One skipped test reads the maintainer's <code>UI.ipynb</code>, which a clone does not have; mypy type-checks the stubs. The other skipped tests drive the binder's adaptation layer and need a <ahref="https://github.com/thisisthepy/python-multiplatform">python-multiplatform</a> checkout beside this one, or:</p><pdata-lang="ko"><strong>132 passed, 99 skipped</strong> 가 정상입니다. 건너뛴 테스트 하나는 클론에는 없는 메인테이너의 <code>UI.ipynb</code> 를 읽으며, mypy 는 스텁을 타입 검사합니다. 나머지 건너뛴 테스트는 바인더의 적응 계층을 구동하며, 옆에 <ahref="https://github.com/thisisthepy/python-multiplatform">python-multiplatform</a> 체크아웃이 있거나 다음이 필요합니다:</p>
46
+
<pdata-lang="en">Expect <strong>134 passed, 112 skipped</strong>. One skipped test reads the maintainer's <code>UI.ipynb</code>, which a clone does not have; mypy type-checks the stubs. The other skipped tests drive the binder's adaptation layer and need a <ahref="https://github.com/thisisthepy/python-multiplatform">python-multiplatform</a> checkout beside this one, or:</p><pdata-lang="ko"><strong>134 passed, 112 skipped</strong> 가 정상입니다. 건너뛴 테스트 하나는 클론에는 없는 메인테이너의 <code>UI.ipynb</code> 를 읽으며, mypy 는 스텁을 타입 검사합니다. 나머지 건너뛴 테스트는 바인더의 적응 계층을 구동하며, 옆에 <ahref="https://github.com/thisisthepy/python-multiplatform">python-multiplatform</a> 체크아웃이 있거나 다음이 필요합니다:</p>
<pdata-lang="en">With a current one (<code>31c092f0</code> or later) <strong>230 pass</strong> and 1 skips (the <code>UI.ipynb</code> test); an older one skips 5 that call a snake_case method on a proxy the binder returned. That is expected and recorded.</p><pdata-lang="ko">최신 체크아웃(<code>31c092f0</code> 이후)이면 <strong>230 개가 통과</strong>하고 1 개(<code>UI.ipynb</code> 테스트)를 건너뛰며, 오래된 체크아웃은 바인더가 돌려준 proxy 의 snake_case 메서드를 부르는 5 개를 <strong>건너뜁니다</strong>. 예상된 상태이며 기록되어 있습니다.</p>
48
+
<pdata-lang="en">With a current one (<code>31c092f0</code> or later) <strong>245 pass</strong> and 1 skips (the <code>UI.ipynb</code> test); an older one skips 5 that call a snake_case method on a proxy the binder returned. That is expected and recorded.</p><pdata-lang="ko">최신 체크아웃(<code>31c092f0</code> 이후)이면 <strong>230 개가 통과</strong>하고 1 개(<code>UI.ipynb</code> 테스트)를 건너뛰며, 오래된 체크아웃은 바인더가 돌려준 proxy 의 snake_case 메서드를 부르는 5 개를 <strong>건너뜁니다</strong>. 예상된 상태이며 기록되어 있습니다.</p>
0 commit comments