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
12 changes: 6 additions & 6 deletions PROJECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ 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 `describe_member` 필요; Kotlin 이름도 실행 시 그대로 통하고 스텁은 snake_case 만) |
| 부분 | 선언형 앱 루트 `@app`·`state`·`app_root`(#11; 로직은 테스트됨, 바인더 경로는 #38 모양의 가짜 호스트로 테스트됨, 실제 Compose 증거는 E2E 모듈 #19, 바인더가 `Any?` 칸의 int 를 거부하므로 `state(1)` 은 아직 TypeError, python-multiplatform #69 대기), 배포 설정, Material 3 위젯(렌더 증거는 python-multiplatform 에만), `Icon`·색 스킴(import 만 됨) |
| 계획 | `remember_saveable`·`DefaultIcons`·코루틴 스코프 |
| 부분 | 선언형 앱 루트 `@app`·`state`·`app_root`(#11; 로직은 테스트됨, 바인더 경로는 #38 모양의 가짜 호스트로 테스트됨, 실제 Compose 증거는 E2E 모듈 #19, 바인더가 `Any?` 칸의 int 를 거부하므로 `state(1)` 은 아직 TypeError, python-multiplatform #69 대기), 배포 설정, Material 3 위젯(렌더 증거는 python-multiplatform 에만), `Icon`(import 만 됨)·`DefaultIcons`(`Icons.Default` alias, 가짜 호스트로만 테스트됨; `DefaultIcons.Add` 로 쓴다 — 노트북의 `DefaultIcons.Add()` 가 아님) |
| 계획 | `remember_saveable`·색 스킴·코루틴 스코프 |

### 테스트 기준선

```
python3 -m pytest tests -q
PythonMultiplatform 체크아웃 없음 116 passed, 87 skipped
python-multiplatform develop 31c092f0+ 202 passed, 1 skipped
PythonMultiplatform 체크아웃 없음 124 passed, 98 skipped
python-multiplatform develop 31c092f0+ 221 passed, 1 skipped
describe_member 없는 체크아웃 같은 수, 그중 7 개 건너뜀 (python-multiplatform #54 이전)
describe(module, name) 없는 체크아웃 같은 수, 그중 13 개 건너뜀 (python-multiplatform #36 이전)
그보다 오래된 체크아웃 거기에 5 개 더 건너뜀 (member resolver 없음)
Expand All @@ -45,7 +45,7 @@ python3 -m pytest tests -q
`describe_member`(python-multiplatform #54)가 없는 바인더에서는 메서드 키워드를 확인하는 7 개가(Kotlin 키워드로 되돌아가는 2 개는 실행됨), `describe(module, name)`(python-multiplatform #36)이 없는 바인더에서는 묶음 상수를 확인하는 13 개가,
member resolver(python-multiplatform `ba4c6f49`)가 없는 바인더에서는 proxy 의 snake_case 메서드를 부르는
5 개가 더 건너뛴다. 두 환경 모두의 건너뜀 1 개는 worktree 에 없는 `UI.ipynb` 의 테스트다. 건너뜀은 통과가 아니다.
체크아웃 없이 통과하는 116 개 중 48 개는 타입 스텁과 wheel 을 검사하고(SPEC S1.2), 나머지 다수는 *부재*(옛 토큰·삭제된
체크아웃 없이 통과하는 124 개 중 51 개는 타입 스텁과 wheel 을 검사하고(SPEC S1.2), 나머지 다수는 *부재*(옛 토큰·삭제된
파일이 없음)를 확인하는 것이라 기능 진척으로 세지 않는다.

### 마일스톤
Expand Down Expand Up @@ -128,7 +128,7 @@ python3 tools/check_guide.py # 가이드: HTML 파싱, 링
- **정렬 상수** 는 `Alignment.End` 와 `Alignment.Horizontal.End` 둘 다 지원한다.
- 소문자 `modifier` 는 두지 않는다(`Modifier` 로 쓴다). ARGB 정수 색은 받지 않는다
(`Color(0xFFFF0000)`). `Spacer(start=...)` 는 지원하지 않는다(`Modifier.padding`). `DefaultIcons` 는
`Icons.Default` 의 alias(python-multiplatform #37 뒤). — `docs/INTENT.md` §5.4–5.7
`Icons.Default` 의 alias(매니페스트 `[aliases]` 의 `{ DefaultIcons = "Icons.Default" }`, 읽을 때마다 해석). — `docs/INTENT.md` §5.4–5.7
- 합성된 `pythonx` 를 전제한 테스트·독스트링은 #7 에서 정리했다.

## 8. 관련 저장소
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ standalone desktop toolkit.
| Distribution (`pythonx-compose`) | 🟡 partial — the wheel carries the manifest, the re-export rule, the stubs and `py.typed` |
| 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); `state(1)` is refused by the binder (int in an `Any?` slot) |
| `TextField` input-method (IME) handling | ⏳ planned (#10) |
| `Icon`, `DefaultIcons`, colour schemes | ⏳ planned (python-multiplatform #37) |
| `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 |
| Colour schemes | ⏳ planned |
| `remember_saveable`, coroutine scopes | ⏳ planned |

The full list is on the guide's [Status page](docs/guide/status.html).
Expand Down
53 changes: 40 additions & 13 deletions docs/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,29 @@ Each item carries a status:

A behaviour change starts here, then becomes a failing test, then code (`AGENTS.md` §5).

## 0. Test baseline (2026-10-03, after method keywords)
## 0. Test baseline (2026-10-03, after DefaultIcons)

Run from a worktree with `python -m pytest tests -q -rs` (pytest 8, mypy 2.4, CPython 3.13), where
`UI.ipynb` is absent and its one test skips:

| Environment | Result |
|---|---|
| No `PythonMultiplatform` checkout found | **116 passed, 87 skipped**, 128 subtests passed |
| python-multiplatform `develop` at `31c092f0` or later (property rows, #38; `describe_member`, #54; besides `add_member_resolver` and `describe(module, name)`) | **202 passed, 1 skipped**, 155 subtests passed |
| No `PythonMultiplatform` checkout found | **124 passed, 98 skipped**, 157 subtests passed |
| python-multiplatform `develop` at `31c092f0` or later (property rows, #38; `describe_member`, #54; besides `add_member_resolver` and `describe(module, name)`) | **221 passed, 1 skipped**, 184 subtests passed |
| an older checkout, without `describe_member` (python-multiplatform #54) | the same, with 7 more skipped |
| an older one, without `describe(module, name)` (python-multiplatform #36) | with 13 more skipped again |
| an older one still, without `add_member_resolver` | with 5 more skipped again |

Measured in a worktree, which has `.tmp/kotlin-stubs.zip` but no `UI.ipynb`. Without a checkout, the
86 tests that install the binder's layers through `tests/adapter.py` skip, plus the notebook test.
97 tests that install the binder's layers through `tests/adapter.py` skip, plus the notebook test.
Against a binder without `describe(module, name)`, the 13 that check grouped constants (§7, S7.1)
skip. Against a binder without `describe_member`, the 7 that check a method's keywords (S4.1) skip; the 2
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
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
`TheBinderPath` tests (S5.4). `tests/test_typing.py` skips where mypy is not installed. A skip is not a
pass.

Of the 116 that pass without a checkout, 48 check the type stubs and the wheel (S1.2); most of the
Of the 124 that pass without a checkout, 51 check the type stubs and the wheel (S1.2); most of the
rest assert **absence** (a retired token, a deleted file, a docstring that exists). Those are listed
in §9 and are not counted as features.

Expand Down Expand Up @@ -104,7 +104,8 @@ and the name the interpreter resolves cannot drift.
- a module that maps the same Kotlin package as an earlier one (`pythonx.compose.foundation.layout`)
re-exports it (`from pythonx.compose.layout import *`), since at run time both names reach the
same objects; `[aliases]` become re-exports too (`material3` exports `Column`, `Row`, `Spacer`
from `pythonx.compose.layout`); names a package defines itself (`runtime.Composable`) are
from `pythonx.compose.layout`; a name lent under another one is assigned its path in the
source's stub, `DefaultIcons = Icons.Default`, which upstream types as `Icons.Filled`); names a package defines itself (`runtime.Composable`) are
carried over.
- **How it is verified.** `tests/test_stubs.py` converts three fixtures: the fake host's
declarations in the old and the current upstream format (and the current format with an object
Expand All @@ -126,7 +127,7 @@ and the name the interpreter resolves cannot drift.
- Many parameter and return types are `Any`: upstream emits `Any` for a class that shares its
name with a function (`PaddingValues`, `TextStyle`, `Color`), for packages it does not stub, and
for an object's own type (`Alignment.Center: Alignment`, python-multiplatform's object self-type);
`Icons.Default.Add` is not stubbed upstream (python-multiplatform #68); `Dp` is `float`;
`Icons.Default` is typed `Icons.Filled`, but each icon on it (`Add`) is `Any` (python-multiplatform #68); `Dp` is `float`;
`Arrangement.HorizontalOrVertical` constants and `spaced_by` return `Any` (above).
- The `pythonx.compose` root module maps `androidx.compose`, which has no stub, so its stub is
empty.
Expand Down Expand Up @@ -154,6 +155,21 @@ Current mapping:
| `pythonx.compose.ui` (+ `.unit`, `.text`, `.graphics`) | `androidx.compose.ui` (+ same) |
| `pythonx.compose.layout` | `androidx.compose.foundation.layout` |
| `pythonx.compose.material3` | `androidx.compose.material3` |
| `pythonx.compose.material.icons` | `androidx.compose.material.icons` |

**`[aliases]`** lends a module names from another mapped module, as data. Two forms, both under
`[aliases."<owner module>"]` keyed by the source module: a list of names lent under the **same**
name (`"pythonx.compose.layout" = ["Column", "Row", "Spacer"]`), or a table `{ lent = "Path.To.Attr" }`
that lends the attribute path under a **different** name
(`"pythonx.compose.material.icons" = { DefaultIcons = "Icons.Default" }`, INTENT §5.7). The first
segment of a path is a name of the source module; each further one is an attribute read. A name with
no dot is resolved once and kept in the owner's namespace, as before. **A path with a dot is resolved
on every read and never kept**: `Icons.Default` is a property of an object, which the binder serves
live (a `STATIC_GETTER` is never cached, python-multiplatform `_adapt`), and the alias reads as the
binder does, so `from pythonx.compose.material3 import DefaultIcons` gives the Kotlin object
`Icons.Default` gives (a new proxy for the same Kotlin object on each read). `dir()` lists every
lent name. `tests/test_pythonx_map.py::TestAliasSection` checks both forms against the manifest;
`tests/test_chain.py::DefaultIconsAlias` checks them at run time.

Caveat: `test_the_specification_resolves` reads `UI.ipynb` from the checkout it runs in. The
notebook is git-ignored, so in a worktree or CI checkout it is absent, the expected set is empty,
Expand Down Expand Up @@ -272,11 +288,22 @@ toggle family, `TextField`, `Checkbox` and `Switch` are meant to be reached thro
host binds no material3 declaration, so no test here exercises one; the render proof per widget
is issue #9.

### S5.3 `Icon` and colour schemes — `planned`

`Icon` needs an `ImageBitmap` / `ImageVector` / `Painter`, and nothing bound produces one until the
binder walks `material-icons-core` (python-multiplatform #37; then `DefaultIcons` is
`Icons.Default`, INTENT §5.7). The two colour-scheme factories take 36 `Color` parameters against
### S5.3 `DefaultIcons`, `Icon` and colour schemes — `partial`

`DefaultIcons` is `implemented` against the fake host: `pythonx.compose.material.icons`
(`androidx.compose.material.icons`) serves `Icons`; `Icons.Default` resolves to `Icons.Filled`
and `Icons.Default.Add` is a top-level extension-property getter on `Icons.Filled` (a `GETTER` row,
python-multiplatform #37/#38, read on the proxy); `from pythonx.compose.material3 import DefaultIcons`
is `Icons.Default` by the manifest's `[aliases]` (§2), so `DefaultIcons.Add` is the `ImageVector` and
an unknown icon is an `AttributeError`. An icon is a **property read, written without parentheses**:
the notebook's `DefaultIcons.Add()` would call an `ImageVector`, which is not callable; INTENT §5.7
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
`material3` stub assigns `DefaultIcons = Icons.Default` (typed `Icons.Filled`; `Add` is `Any` until
python-multiplatform #68), checked by `tests/test_typing.py::test_default_icons_imports_from_material3`. `Icon` itself is a Material 3 composable, served by the §3 rule.
Still `planned`: the two colour-scheme factories take 36 `Color` parameters against
the binding's omission cap (python-multiplatform `a6742a1c`). The hand-written `icon.py` /
`color_scheme.py` that recorded this were dead code and are deleted (#31).

Expand Down Expand Up @@ -406,7 +433,7 @@ the binder lists no member that would win over these groups.
|---|---|---|
| `remember_saveable` | imported from `pythonx.compose.runtime`; state read/written with `getValue()` / `setValue()` | INTENT §5.1: `state(initial)` read and written through `.value` (S5.4, `partial`, #11; an `int` initial value is refused by the binder until python-multiplatform #69); keeping a value across recreation is not provided |
| `DefaultCoroutineScope`, `MainCoroutineScope` | imported from `pythonx.compose.runtime` | INTENT §4.1, open |
| `DefaultIcons` | `DefaultIcons.Add()` | INTENT §5.7: `Icons.Default` by alias, once python-multiplatform #37 binds `material-icons-core` |
| `DefaultIcons` | `DefaultIcons.Add()` | INTENT §5.7, `implemented` as `DefaultIcons.Add`. The notebook's `Add()` is the spelling of its time; the current spelling is `Add`, a property read without parentheses, as for every constant (decided with the ecosystem lead; S5.3, `tests/test_chain.py::DefaultIconsAlias`, fake host) |
| `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(...))` |
Expand Down
4 changes: 2 additions & 2 deletions docs/guide/guide-composables.html
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,10 @@ <h2><span data-lang="en">Spellings that changed</span><span data-lang="ko">바
<section>
<h2><span data-lang="en">What does not work yet</span><span data-lang="ko">아직 안 되는 것</span></h2>
<ul class="clean">
<li><span data-lang="en"><code>Icon</code> and the colour-scheme factories cannot be called yet: nothing bound can produce an <code>ImageVector</code> until the binder walks <code>material-icons-core</code> (python-multiplatform #37; then <code>DefaultIcons</code> is <code>Icons.Default</code>), and the factories take 36 colour parameters.</span><span data-lang="ko"><code>Icon</code> 과 색 스킴 팩토리는 아직 호출할 수 없습니다: 바인더가 <code>material-icons-core</code> 를 읽기 전에는(python-multiplatform #37, 그 뒤 <code>DefaultIcons</code> 는 <code>Icons.Default</code>) 바인딩된 어떤 것도 <code>ImageVector</code> 를 만들 수 없고, 팩토리는 색 매개변수 36 개를 받습니다.</span></li>
<li><span data-lang="en">The colour-scheme factories cannot be called yet: they take 36 colour parameters. <code>DefaultIcons</code> is <code>Icons.Default</code>, an icon is a value read without parentheses — <code>Icon(DefaultIcons.Add, content_description=None)</code>, not the notebook's <code>DefaultIcons.Add()</code> — and it is tested here against a fake host only; the real draw is in python-multiplatform's render test.</span><span data-lang="ko">색 스킴 팩토리는 색 매개변수를 36 개 받아 아직 호출할 수 없습니다. <code>DefaultIcons</code> 는 <code>Icons.Default</code> 이고, 아이콘은 괄호 없이 읽는 값입니다 — 노트북의 <code>DefaultIcons.Add()</code> 가 아니라 <code>Icon(DefaultIcons.Add, content_description=None)</code>. 여기서는 가짜 호스트로만 테스트되며, 실제 그리기는 python-multiplatform 의 렌더 테스트에 있습니다.</span></li>
<li><span data-lang="en">A declared app root and Pythonic state — redefining a screen replaces it, with no update call — are planned (issue #11).</span><span data-lang="ko">선언형 앱 루트와 파이썬다운 상태 — 화면을 다시 정의하면 교체되고, 갱신 호출은 없음 — 는 계획 단계입니다(이슈 #11).</span></li>
<li><span data-lang="en"><code>TextField</code> input-method (IME) handling is pending (issue #10).</span><span data-lang="ko"><code>TextField</code> 의 입력기(IME) 처리는 대기 중입니다(이슈 #10).</span></li>
<li><span data-lang="en"><code>remember_saveable</code>, <code>DefaultIcons</code> and the coroutine scopes the notebook imports are planned.</span><span data-lang="ko">노트북이 import 하는 <code>remember_saveable</code>, <code>DefaultIcons</code>, 코루틴 스코프는 계획 단계입니다.</span></li>
<li><span data-lang="en"><code>remember_saveable</code> and the coroutine scopes the notebook imports are planned.</span><span data-lang="ko">노트북이 import 하는 <code>remember_saveable</code>, 코루틴 스코프는 계획 단계입니다.</span></li>
</ul>
<div class="pager"><a href="concepts.html">← <span data-lang="en">Concepts</span><span data-lang="ko">개념</span></a><a href="guide-modifiers.html"><span data-lang="en">Modifiers</span><span data-lang="ko">모디파이어</span> →</a></div>
</section>
Expand Down
Loading
Loading