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 c378e1e
Browse filesBrowse the repository at this point in the historyBrowse files
Feat: remember_saveable with the notebook's getValue()/setValue(), over the host's rememberSaveableWrapper
INTENT 5.9. remember_saveable(initial) calls
python.multiplatform.compose.rememberSaveableWrapper(initial)
(python-multiplatform #174, 8c56f19b), which owns the type rule (Int, Long,
Double, Boolean, String; anything else refused). It returns SaveableState,
a thin wrapper over the Kotlin MutableState with getValue(), setValue(v)
and .value, every read going through MutableState.value. Outside a
composition the binder raises RuntimeError; an unbound wrapper raises
RuntimeError naming #174 and the artifactIncludePackages entry. The app
attaches the state itself (App.messages = messages = remember_saveable("")),
so notebook cells 9 to 13 work as written.
The stub generator now carries a package-defined class with its public
methods and properties, so the stub types SaveableState; regenerated, only
the runtime stub changes.
Tests written first and observed red (AttributeError/ImportError: no
remember_saveable; the stub and typing checks): test_remember_saveable.py
(9), one stub test, two typing tests. Counts, main checkout: 149/113 ->
152/122 without a checkout, 262 -> 274 with one; worktree baselines
151/123 and 273/1. SPEC S5.6 (partial), guide, status page (its stale
baselines too), README en/ko and PROJECT follow.
Closes#102
Copy file name to clipboardExpand all lines: PROJECT.md
+4-4Lines changed: 4 additions & 4 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 #131 이 바인더 쪽에서 제공하므로 #131 이상 필요; Kotlin 이름도 실행 시 그대로 통하고 스텁은 snake_case 만), 함수이기도 한 Kotlin object 의 호출(`Color(0xFFFFFFFF)` 과 `Color.Red` 둘 다; #88, 설치한 wheel 에서도 테스트됨. Int/Long 오버로드 선택은 python-multiplatform #146) |
31
-
| 부분 | 선언형 앱 루트 `@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 에서 통과) |
32
-
| 계획 |`remember_saveable`·색 스킴·코루틴 스코프 |
31
+
| 부분 | 노트북 표기 그대로의 `remember_saveable`(`getValue()`/`setValue()`, `.value` 별칭; #102, 가짜 호스트로 테스트됨, 저장·복원 증거는 python-multiplatform #174), 선언형 앱 루트 `@app`·`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 에서 통과) |
Copy file name to clipboardExpand all lines: README.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -174,7 +174,8 @@ standalone desktop toolkit.
174
174
|`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) |
175
175
|`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 |
|`remember_saveable` with the notebook's `getValue()` / `setValue()`| 🟡 partial: tested against a fake host; save and restore is python-multiplatform's test (#174) |
Copy file name to clipboardExpand all lines: docs/SPEC.md
+42-32Lines changed: 42 additions & 32 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,14 +21,14 @@ Run from a worktree with `uv run --with pytest --with mypy pytest tests -q -rs`
21
21
22
22
| Environment | Result |
23
23
|---|---|
24
-
| No `PythonMultiplatform` checkout found |**148 passed, 114 skipped**, 164 subtests passed |
25
-
| python-multiplatform `develop` at `31c092f0` or later (property rows, #38; `describe_member`, #54; besides `add_member_resolver` and `describe(module, name)`) |**261 passed, 1 skipped**, 195 subtests passed |
24
+
| No `PythonMultiplatform` checkout found |**151 passed, 123 skipped**, 186 subtests passed |
25
+
| python-multiplatform `develop` at `31c092f0` or later (property rows, #38; `describe_member`, #54; besides `add_member_resolver` and `describe(module, name)`) |**273 passed, 1 skipped**, 232 subtests passed |
26
26
| an older checkout, before python-multiplatform #131 (no binder-side snake_case names) | the same, with the snake_case method tests 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
-
113 tests that install the binder's layers through `tests/adapter.py` skip (one of them against the
31
+
122 tests that install the binder's layers through `tests/adapter.py` skip (one of them against the
32
32
installed wheel, #88), plus the notebook test.
33
33
Against a binder without `describe(module, name)`, the 13 that check grouped constants (§7, S7.1)
34
34
skip. Against a binder before python-multiplatform #131, the tests that call a snake_case method or
@@ -406,47 +406,57 @@ reads the committed text. Not exercised there: the AWT layer that decodes an `In
406
406
which needs a window. Not modelled here: a function-typed slot (`label`, `on_value_change`), which needs
407
407
the binder's `NewFunction` rows.
408
408
409
-
### S5.6 `remember_saveable`, and the notebook's state spelling: `planned`
409
+
### S5.6 `remember_saveable`, and the notebook's state spelling: `partial` (the Python half is tested here against the fake host; that Compose saves and restores the state is python-multiplatform's test)
410
410
411
411
INTENT §5.9 (2026-10-04). The 2024 demo the notebook ran against (PyREPL `abc6952`) wrote
412
412
`cls.messages = messages = remember_saveable("")` in its root, and `UI.ipynb` cells 9 to 13 read
413
413
`main.App.messages`, `main.App.messages.getValue()` and `main.App.messages.setValue(...)`.
414
414
415
415
-`remember_saveable(initial)`, from `pythonx.compose.runtime`, calls the host's `@Composable`
@@ -589,7 +599,7 @@ the binder lists no member that would win over these groups.
589
599
590
600
| Name | Notebook use | Note |
591
601
|---|---|---|
592
-
|`remember_saveable`| imported from `pythonx.compose.runtime`; `main.App.messages` read/written with `getValue()` / `setValue()` (cells 9 to 13) | INTENT §5.9: supported as written, `.value` as the alias; S5.6, `planned`|
602
+
|`remember_saveable`| imported from `pythonx.compose.runtime`; `main.App.messages` read/written with `getValue()` / `setValue()` (cells 9 to 13) | INTENT §5.9: supported as written, `.value` as the alias; S5.6, `partial` (#102; save and restore proved in python-multiplatform #174)|
593
603
|`DefaultCoroutineScope`, `MainCoroutineScope`| imported from `pythonx.compose.runtime`| INTENT §5.10: built on pythonx-concurrent; S5.7, `planned`|
594
604
|`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) |
595
605
|`modifier`| lower-case instance from `pythonx.compose.ui`| INTENT §5.4: not provided; written `Modifier`|
main.App.messages.setValue("Changed from the notebook")
127
+
main.App.messages.setValue(backup)</code></pre>
128
+
<pdata-lang="en"><code>remember_saveable(initial)</code> is Compose's <code>rememberSaveable</code>: the value survives recomposition, rotation and process restart. It takes an <code>int</code>, <code>float</code>, <code>bool</code> or <code>str</code>, and works only inside a composition. What it returns reads and writes with <code>getValue()</code> / <code>setValue(...)</code>, as the notebook writes, and with <code>.value</code>. The app attaches the state to its root itself (<code>App.messages = ...</code>), which is how <code>main.App.messages</code> exists after the first composition. A host app binds it by listing <code>python.multiplatform.compose.RememberSaveableKt</code> in <code>artifactIncludePackages</code>.</p><pdata-lang="ko"><code>remember_saveable(initial)</code> 은 Compose 의 <code>rememberSaveable</code> 입니다. 값은 재구성, 화면 회전, 프로세스 재시작 뒤에도 남습니다. <code>int</code>, <code>float</code>, <code>bool</code>, <code>str</code> 을 받으며 컴포지션 안에서만 동작합니다. 돌려받은 값은 노트북처럼 <code>getValue()</code> / <code>setValue(...)</code> 로, 또 <code>.value</code> 로 읽고 씁니다. 앱이 상태를 루트에 직접 붙이므로(<code>App.messages = ...</code>) 첫 컴포지션 뒤에 <code>main.App.messages</code> 가 생깁니다. 호스트 앱은 <code>artifactIncludePackages</code> 에 <code>python.multiplatform.compose.RememberSaveableKt</code> 를 넣어 바인딩합니다.</p>
129
+
<pdata-lang="en"><strong>Status: partial.</strong> The Python half is tested against a fake host; saving and restoring through Compose's <code>SaveableStateRegistry</code> is python-multiplatform's test (#174).</p><pdata-lang="ko"><strong>상태: 부분.</strong> 파이썬 쪽은 가짜 호스트로 테스트되었고, Compose 의 <code>SaveableStateRegistry</code> 를 통한 저장과 복원은 python-multiplatform 의 테스트(#174)입니다.</p>
130
+
</section>
131
+
111
132
<section>
112
133
<h2><spandata-lang="en">What does not work yet</span><spandata-lang="ko">아직 안 되는 것</span></h2>
113
134
<ulclass="clean">
114
135
<li><spandata-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 (an <code>ImageVector</code> to a type checker) read without parentheses: <code>Icon(DefaultIcons.Add, content_description=None)</code>, not the notebook's <code>DefaultIcons.Add()</code>. It is tested here against a fake host only; the real draw is in python-multiplatform's render test.</span><spandata-lang="ko">색 스킴 팩토리는 색 매개변수를 36 개 받아 아직 호출할 수 없습니다. <code>DefaultIcons</code> 는 <code>Icons.Default</code> 이고, 아이콘은 괄호 없이 읽는 값(타입 검사기에는 <code>ImageVector</code>)입니다. 노트북의 <code>DefaultIcons.Add()</code> 가 아니라 <code>Icon(DefaultIcons.Add, content_description=None)</code>. 여기서는 가짜 호스트로만 테스트되며, 실제 그리기는 python-multiplatform 의 렌더 테스트에 있습니다.</span></li>
115
136
<li><spandata-lang="en"><code>TextField(state=…)</code> is wired and tested against a fake host only; that the input method's composing sequence survives, with no Python callback per keystroke, is proved in python-multiplatform's end-to-end test (#26).</span><spandata-lang="ko"><code>TextField(state=…)</code> 는 가짜 호스트로만 테스트되었습니다. 입력기의 조합 입력이 키 입력마다 파이썬 콜백 없이 유지되는지는 python-multiplatform 의 종단 간 테스트(#26)에서 증명합니다.</span></li>
116
-
<li><spandata-lang="en"><code>remember_saveable</code> and the coroutine scopes the notebook imports are planned.</span><spandata-lang="ko">노트북이 import 하는 <code>remember_saveable</code>, 코루틴 스코프는 계획 단계입니다.</span></li>
137
+
<li><spandata-lang="en">The coroutine scopes the notebook imports, <code>DefaultCoroutineScope</code> and <code>MainCoroutineScope</code>, come after pythonx-concurrent.</span><spandata-lang="ko">노트북이 import 하는 코루틴 스코프 <code>DefaultCoroutineScope</code>, <code>MainCoroutineScope</code> 는 pythonx-concurrent 이후에 나옵니다.</span></li>
0 commit comments