From 5303cf27747590008277bace53537ed88736c1e6 Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:31:07 +0900 Subject: [PATCH] Docs: No em-dash, uv/ppp/tcl examples only, and a house style note Three of the maintainer's instructions (2026-10-03), relayed by the ecosystem lead: - No em-dash anywhere in documents or code. All 122 lines that had one are rewritten by context (two sentences, a comma, a colon or parentheses), English and Korean alike; the en-dash in ranges stays. AGENTS.md section 17 makes it a rule. - Install and run examples use only uv, ppp (pypackpack) and tcl (toolchain-lite): `uv add --prerelease allow pythonx-compose`, then `ppp core add "pythonx-compose==0.1.0a1"` and `tcl install pythonx-compose`; tests and scripts run with `uv run`. The status FAQ asks "Can I install it from PyPI?" instead of naming pip. Records of what was run are rephrased so they are not instructions. AGENTS.md section 17 makes it a rule; the 0.1.0a1 Release notes stay as they are, a record. - The maintainer named this repository's guide the reference for every repository's documentation, so its style is written down in docs/style/writing.md (voice, the English/Korean pairing and registers, terms, mechanics), and AGENTS.md section 17 points at it. Also fixed: the Korean getting-started line said 230 tests pass where the English says 251. The em-dash and example passes were drafted by a subagent in a worktree and reviewed here; the style note is written here. documentation corrected: AGENTS.md, PROJECT.md, README.md, README_ko.md, INTENT, SPEC, every guide page and its CSS comment; added docs/style/writing.md. git grep finds no em-dash. check_guide: 0 findings. Tests unchanged. Refs #77 --- AGENTS.md | 50 +++++++++++++++----------- PROJECT.md | 26 +++++++------- README.md | 41 +++++++++++++--------- docs/INTENT.md | 28 +++++++-------- docs/SPEC.md | 58 +++++++++++++++---------------- docs/guide/assets/style.css | 4 +-- docs/guide/concepts.html | 12 +++---- docs/guide/ecosystem.html | 2 +- docs/guide/getting-started.html | 18 +++++----- docs/guide/guide-composables.html | 12 +++---- docs/guide/guide-manifest.html | 4 +-- docs/guide/guide-modifiers.html | 6 ++-- docs/guide/index.html | 12 +++---- docs/guide/status.html | 6 ++-- docs/locale/README_ko.md | 41 +++++++++++++--------- docs/style/writing.md | 51 +++++++++++++++++++++++++++ 16 files changed, 222 insertions(+), 149 deletions(-) create mode 100644 docs/style/writing.md diff --git a/AGENTS.md b/AGENTS.md index 2a9ffe9..dc54f9a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,8 +14,8 @@ overrides any default your tooling has. ## 2. Nothing is created outside this repository -Everything your work produces — worktrees, agent prompts, logs, measurements, experiments, scratch -files — lives **inside this repository's root directory.** +Everything your work produces (worktrees, agent prompts, logs, measurements, experiments, scratch +files) lives **inside this repository's root directory.** | What | Where | |---|---| @@ -27,8 +27,8 @@ files — lives **inside this repository's root directory.** Before writing a file, check that its absolute path starts with this repository's root. If it does not, stop. The only exceptions are a path the user names explicitly, and caches that build tools -manage themselves. **Re-pointing a shared cache or a home-directory symlink reaches other projects — -ask first.** +manage themselves. **Re-pointing a shared cache or a home-directory symlink reaches other projects. +Ask first.** Writing to *another* repository is not an exception either. Do it only when told to work there. @@ -41,8 +41,8 @@ existing modules and directories: source inside the package (`pythonx/`), CI scr The standing root entries are `pyproject.toml`, `README.md`, `LICENSE`, `PROJECT.md`, `AGENTS.md`, `pythonx/`, `scripts/` (approved by the maintainer, 2026-10-03), `tests/`, `docs/`, `.github/`, `.gitignore` and `.gitattributes`, plus the maintainer's -untracked `UI.ipynb`. If a new top-level entry seems necessary, propose it — what it is, why, and -why it cannot live inside an existing directory — and wait for approval. +untracked `UI.ipynb`. If a new top-level entry seems necessary, propose it (what it is, why, and +why it cannot live inside an existing directory) and wait for approval. ## 3. Worktrees link large artefacts instead of copying them @@ -80,7 +80,7 @@ Every new feature goes through an issue and a pull request: 1. Before starting, search the repository's issues (`gh issue list --state all --search ""`). 2. If no issue covers the work, open one (`gh issue create`) stating what and why, and the - completion criterion — which tests must pass. + completion criterion, that is, which tests must pass. 3. Work on a `feat/` branch, push every commit, and open a pull request into `develop` whose body contains `Closes #`. 4. Merge into `develop` through that pull request (`gh pr merge --merge --delete-branch`), not by a @@ -104,18 +104,18 @@ If a request conflicts with `docs/INTENT.md`, say so instead of implementing it. ## 6. User-authored files are specification -Files the user wrote by hand — notebooks, example build files, sample apps — are the specification. +Files the user wrote by hand (notebooks, example build files, sample apps) are the specification. Read them **first**. Never delete, rewrite, or `git add` them without being told to. Generated documentation (roadmaps, design notes) is a record of work, not a requirement; when the two disagree, the user's file wins. ## 7. Show a conclusion before acting on it -Anything beyond the immediate request — another repository, a public API signature, deleting -files, killing processes, force-pushing, changing branch protection — state what you would do and +Anything beyond the immediate request (another repository, a public API signature, deleting +files, killing processes, force-pushing, changing branch protection): state what you would do and why, and wait. Investigating, measuring, and reporting are always fine. -**Push every commit right away.** After you commit — on a work branch or on `develop` — push it to +**Push every commit right away.** After you commit, on a work branch or on `develop`, push it to the remote immediately; no confirmation is needed. Never push to `main` or `release` by hand, and never force-push without the user's explicit approval. @@ -155,17 +155,17 @@ to implement", say what you counted against. - Give every agent prompt the absolute paths it may write to, and repeat rule 2 in it. - **Subagents do not run heavy local builds.** Subagents write code, design, investigate, review and document. Gradle builds, cargo builds, the test gate and model runs are done by the session - itself — one at a time on this machine — or by CI (GitHub Actions) on a pushed branch. Several + itself (one at a time on this machine) or by CI (GitHub Actions) on a pushed branch. Several sessions share one machine; parallel local builds slow every one of them. --- -# Repository-specific rules — `pythonx-compose` +# Repository-specific rules: `pythonx-compose` ## 11. The specification is the user's notebook `UI.ipynb` is this project's user-facing specification. It lives only in the maintainer's main -checkout, is git-ignored, and is **never** copied, moved, edited, or `git add`ed — not even into a +checkout, is git-ignored, and is **never** copied, moved, edited, or `git add`ed, not even into a worktree so a test can see it. Read it in place. - Its function signatures are **naming examples**. The rule they illustrate is: expose the original @@ -181,7 +181,7 @@ worktree so a test can see it. Read it in place. - `python-multiplatform` exposes Kotlin declarations under their **Kotlin** names (`androidx.compose.material3`). It must never map `androidx` to `pythonx` or rename any Kotlin - namespace. Do not propose, add, or depend on such a feature there — including in test harnesses. + namespace. Do not propose, add, or depend on such a feature there, including in test harnesses. - `pythonx/` is a **real package on disk**. Its modules import the `androidx.compose.*` modules the binder exposes and restructure them Pythonically. A design in which something else synthesises `pythonx.*` (for example with `__path__ = []`) and the on-disk files cannot be imported is ruled @@ -200,7 +200,7 @@ worktree so a test can see it. Read it in place. ## 14. Running the tests - python3 -m pytest tests -q + uv run --with pytest --with mypy pytest tests -q - The suite needs `pytest`; if the system Python has none, create a virtual environment under `.tmp/` (rule 2), not in the home directory. @@ -229,11 +229,11 @@ worktree so a test can see it. Read it in place. publishes through PyPI trusted publishing (environment `pypi`). Pushing a tag alone publishes nothing. Never upload by hand, and never store a PyPI token. - `.pyi` stubs, `py.typed` and the manifest must end up **inside the wheel**. A `package-data` - pattern is not proof — build the wheel and list it before claiming something ships. + pattern is not proof: build the wheel and list it before claiming something ships. ## 16. Things you do not change without the maintainer -- **The root layout** — see section 2, "Do not add top-level folders". +- **The root layout**: see section 2, "Do not add top-level folders". - The retired `test/` sample, `pythonx/compose/lite/` and the `pythonx/compose/native` submodule were removed in #60; they stay reachable through the tag `archive/pre-restructure`. Do not bring them back. @@ -241,10 +241,20 @@ worktree so a test can see it. Read it in place. ## 17. Documentation layout - `README.md` (English) and `docs/locale/README_ko.md` (Korean) say the same thing; change both. +- Write in the house style, `docs/style/writing.md`: voice, the English and Korean pairing, terms + and mechanics. It is the reference for every thisisthepy repository's documentation. - `docs/guide/` is the bilingual GitHub Pages site. Every visible string exists in English and - Korean. `python3 .github/scripts/check_guide.py` is its checker, and `tests/test_guide.py` runs it + Korean. `uv run python .github/scripts/check_guide.py` is its checker, and `tests/test_guide.py` runs it in the suite. - No Markdown files directly under `docs/` other than `INTENT.md` and `SPEC.md`; other documents go in a topic subdirectory. `README.md` must not link to `AGENTS.md`, `PROJECT.md`, - `docs/INTENT.md` or `docs/SPEC.md` — those files do not exist on `main`. + `docs/INTENT.md` or `docs/SPEC.md`, because those files do not exist on `main`. - There is no `CLAUDE.md`: Claude Code reads this file directly. Rules go here. +- No em-dash (U+2014) anywhere in documents or code: Markdown, the guide's HTML and CSS, workflows, + `pyproject.toml`, Python comments, docstrings and strings, tests. Split the sentence or use a + comma, colon or parentheses, whichever reads best. The en-dash in ranges (2023–2024, 3.11–3.13) + is fine. +- Install and run examples use only uv, ppp (pypackpack) and tcl (toolchain-lite): `uv add + --prerelease allow pythonx-compose`, `ppp core add "pythonx-compose==0.1.0a1"`, `tcl install + pythonx-compose`, `uv run ...`. No `pip` examples. The published 0.1.0a1 Release notes are a + record and stay as they are. diff --git a/PROJECT.md b/PROJECT.md index bb2d1a1..d064889 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -1,4 +1,4 @@ -# PROJECT — pythonx-compose +# PROJECT: pythonx-compose 프로젝트 운영에 필요한 핵심 사항을 정리한 문서입니다. 의도는 `docs/INTENT.md`, 동작 계약은 `docs/SPEC.md`, 에이전트 규정은 `AGENTS.md` 에 있습니다. @@ -28,13 +28,13 @@ 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; 숫자·문자열 상태는 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 증거는 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) | | 계획 | `remember_saveable`·색 스킴·코루틴 스코프 | ### 테스트 기준선 ``` -python3 -m pytest tests -q +uv run --with pytest --with mypy pytest tests -q PythonMultiplatform 체크아웃 없음 140 passed, 112 skipped python-multiplatform develop 31c092f0+ 251 passed, 1 skipped describe_member 없는 체크아웃 같은 수, 그중 7 개 건너뜀 (python-multiplatform #54 이전) @@ -84,9 +84,9 @@ docs/INTENT.md docs/SPEC.md docs/locale/ docs/guide/ ## 5. 빌드·테스트 ```bash -python3 -m pytest tests -q # 파이썬 테스트 -PYTHONMULTIPLATFORM_HOME= python3 -m pytest tests -q # 바인더 계층 포함 -python3 .github/scripts/check_guide.py # 가이드: HTML 파싱, 링크, en/ko 짝 (tests/test_guide.py 도 실행) +uv run --with pytest --with mypy pytest tests -q # 파이썬 테스트 +PYTHONMULTIPLATFORM_HOME= uv run --with pytest --with mypy pytest tests -q # 바인더 계층 포함 +uv run python .github/scripts/check_guide.py # 가이드: HTML 파싱, 링크, en/ko 짝 (tests/test_guide.py 도 실행) ``` - pytest 가 없으면 `.tmp/` 아래에 가상환경을 만든다 (홈 디렉터리 금지). @@ -106,10 +106,10 @@ python3 .github/scripts/check_guide.py # 가이드: HTML 파싱, 링 ## 7. 열린 질문 (메인테이너 결정 필요) -1. **코루틴 스코프** — 노트북이 import 하는 `DefaultCoroutineScope`·`MainCoroutineScope`. -2. **`pythonx-map.toml` 의 이중 표기** — `pythonx.compose.layout` 과 +1. **코루틴 스코프**: 노트북이 import 하는 `DefaultCoroutineScope`·`MainCoroutineScope`. +2. **`pythonx-map.toml` 의 이중 표기**: `pythonx.compose.layout` 과 `pythonx.compose.foundation.layout` 를 둘 다 유지할지. -3. **릴리스·Pages 활성화** — `.github/scripts/release/` 와 `.github/workflows/`(release-sync, pages)는 +3. **릴리스·Pages 활성화**: `.github/scripts/release/` 와 `.github/workflows/`(release-sync, pages)는 들어와 있다. main 보호는 메인테이너가 저장소 설정에서 직접 관리하며 main 은 잠겨 있다. 스크립트나 에이전트는 보호 설정을 만들거나 바꾸지 않는다. release→main PR 머지는 메인테이너가 한다. @@ -120,7 +120,7 @@ python3 .github/scripts/check_guide.py # 가이드: HTML 파싱, 링 태그 `archive/pre-restructure` 로 되찾을 수 있다. `tools/` 는 없앴다: 스텁 생성기는 `scripts/`(메인테이너 승인), 가이드 검사기와 릴리스 스크립트는 `.github/scripts/`. -### 결정됨 (2026-10-03, #62) — 첫 PyPI 릴리스 +### 결정됨 (2026-10-03, #62): 첫 PyPI 릴리스 - 워크플로 이름: `tests.yml` → `test.yml`, PyPI 배포는 `publish-pypi.yml`. - 첫 버전은 `0.1.0a1`(알파, 프리릴리스). PyPI 메타데이터(설명·README·라이선스·저자·URL·분류자)는 채웠고, @@ -129,8 +129,8 @@ python3 .github/scripts/check_guide.py # 가이드: HTML 파싱, 링 `publish-pypi.yml` 이 Release 태그와 `pyproject.toml` 버전을 대조하고, sdist·wheel 을 빌드하고, `twine check`·새 venv 스모크를 거쳐 트러스티드 퍼블리싱으로 올린다. 태그만 푸시해서는 아무것도 올라가지 않는다(같은 버전은 PyPI 에 다시 올릴 수 없으므로 의도적인 단계로 둠). **현황: 0.1.0a1 배포됨**(2026-10-03, develop `134a641` 에서 pre-release - `v0.1.0a1`, publish-pypi run 37120641414; https://pypi.org/project/pythonx-compose/). 새 venv 에서 - `pip install pythonx-compose==0.1.0a1` 후 import 스모크 통과. + `v0.1.0a1`, publish-pypi run 37120641414; https://pypi.org/project/pythonx-compose/). PyPI 에서 새로 + 설치한 0.1.0a1 이 import 스모크를 통과했다. ### 결정됨 (2026-10-03, `docs/INTENT.md` §5) @@ -142,7 +142,7 @@ python3 .github/scripts/check_guide.py # 가이드: HTML 파싱, 링 - **정렬 상수** 는 `Alignment.End` 와 `Alignment.Horizontal.End` 둘 다 지원한다. - 소문자 `modifier` 는 두지 않는다(`Modifier` 로 쓴다). ARGB 정수 색은 받지 않는다 (`Color(0xFFFF0000)`). `Spacer(start=...)` 는 지원하지 않는다(`Modifier.padding`). `DefaultIcons` 는 - `Icons.Default` 의 alias(매니페스트 `[aliases]` 의 `{ DefaultIcons = "Icons.Default" }`, 읽을 때마다 해석). — `docs/INTENT.md` §5.4–5.7 + `Icons.Default` 의 alias(매니페스트 `[aliases]` 의 `{ DefaultIcons = "Icons.Default" }`, 읽을 때마다 해석). 근거는 `docs/INTENT.md` §5.4–5.7. - 합성된 `pythonx` 를 전제한 테스트·독스트링은 #7 에서 정리했다. ## 8. 관련 저장소 diff --git a/README.md b/README.md index 6591f20..93fb193 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ English | [한국어](https://github.com/thisisthepy/pythonx-compose/blob/main/d # pythonx-compose -**Write Compose user interfaces in Python — with Compose's own widgets, spelled the Python way.** +**Write Compose user interfaces in Python, with Compose's own widgets, spelled the Python way.** [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-7c4dff.svg)](https://github.com/thisisthepy/pythonx-compose/blob/main/LICENSE) [![Python](https://img.shields.io/badge/python-3.11%2B-7c4dff.svg)](https://github.com/thisisthepy/pythonx-compose/blob/main/pyproject.toml) @@ -19,7 +19,7 @@ English | [한국어](https://github.com/thisisthepy/pythonx-compose/blob/main/d ## Why -Jetpack Compose and Compose Multiplatform are a superb way to build UI — if you write Kotlin. +Jetpack Compose and Compose Multiplatform are a superb way to build UI, if you write Kotlin. `pythonx-compose` is for people who write Python. It does not reimplement Compose and it does not invent a new widget set: it takes the real `androidx.compose.*` API, which [python-multiplatform](https://github.com/thisisthepy/python-multiplatform) exposes to Python under @@ -55,7 +55,7 @@ proofs are still pending. See [Status](#-status). `Alignment.Horizontal.End` is `Alignment.End`. - **`@Composable` stays.** Screens are decorated Python functions. - **A real package.** `pythonx/` is ordinary Python source that imports `androidx.compose.*` and - reshapes it. The binder never renames anything — renaming is this package's job. + reshapes it. The binder never renames anything. Renaming is this package's job. - **One manifest.** [`pythonx-map.toml`](https://github.com/thisisthepy/pythonx-compose/blob/main/pythonx/compose/pythonx-map.toml) says which `pythonx.compose.*` module stands for which Kotlin package. It lives inside the package and ships in the wheel. The runtime and the `.pyi` generator read the same file, so what your editor completes is what the interpreter @@ -87,15 +87,22 @@ Each module is a real file whose `__init__.py` calls one re-export rule; names r > [!NOTE] > `pythonx-compose` is an **alpha**. `0.1.0a1` is on [PyPI](https://pypi.org/project/pythonx-compose/); -> as a pre-release it installs with `pip install --pre pythonx-compose`. +> as a pre-release it installs with uv (or ppp, or tcl): + +```bash +uv add --prerelease allow pythonx-compose +# or, with pypackpack, into a package of a ppp workspace: +ppp core add "pythonx-compose==0.1.0a1" +# or, with toolchain-lite: +tcl install pythonx-compose +``` From a clone, to run the tests: ```bash git clone https://github.com/thisisthepy/pythonx-compose cd pythonx-compose -python3 -m pip install pytest # inside a virtual environment -python3 -m pytest tests -q +uv run --with pytest --with mypy pytest tests -q ``` What runs today, from the repository root: @@ -155,15 +162,15 @@ standalone desktop toolkit. | `pythonx` as a real on-disk package re-exporting `androidx.compose.*` by one rule | ✅ implemented and tested | | `Modifier` chains with snake_case methods, overload dispatch, `Dp` as a number | ✅ implemented and tested against the binder's layer | | `Column`, `Row`, `Spacer` from `material3` as well as `layout` | ✅ implemented and tested | -| Method keyword arguments in `snake_case` | ✅ implemented and tested — module functions and methods (methods need python-multiplatform's `describe_member`) | -| The empty `Modifier` | 🟡 partial — `Modifier.padding(16)` from the class needs an app-supplied factory against real Compose | -| `Alignment` / `Arrangement` | ✅ implemented and tested — `Alignment.Center`, `Arrangement.spaced_by(8)`, and grouped `Alignment.Horizontal.End` beside `Alignment.End` | -| Material 3 widgets (`Text`, `Button`, `Card`, `TextField`, …) | 🟡 partial — re-exported by rule; render proofs per widget pending (#9) | +| Method keyword arguments in `snake_case` | ✅ implemented and tested: module functions and methods (methods need python-multiplatform's `describe_member`) | +| The empty `Modifier` | 🟡 partial: `Modifier.padding(16)` from the class needs an app-supplied factory against real Compose | +| `Alignment` / `Arrangement` | ✅ implemented and tested: `Alignment.Center`, `Arrangement.spaced_by(8)`, and grouped `Alignment.Horizontal.End` beside `Alignment.End` | +| Material 3 widgets (`Text`, `Button`, `Card`, `TextField`, …) | 🟡 partial: re-exported by rule; render proofs per widget pending (#9) | | Type stubs (`.pyi`) from real Compose 1.11.1, `py.typed` | ✅ implemented and checked with mypy; many types are still `Any` (#12) | -| 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); 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) | -| `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 | +| 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); 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) | +| `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 | @@ -171,15 +178,15 @@ The full list is on the guide's [Status page](https://thisisthepy.github.io/pyth ## 📖 Documentation -- **Guide** — [`docs/guide/`](https://thisisthepy.github.io/pythonx-compose/), bilingual (English / 한국어) -- **Korean README** — [`docs/locale/README_ko.md`](https://github.com/thisisthepy/pythonx-compose/blob/main/docs/locale/README_ko.md) +- **Guide**: [`docs/guide/`](https://thisisthepy.github.io/pythonx-compose/), bilingual (English / 한국어) +- **Korean README**: [`docs/locale/README_ko.md`](https://github.com/thisisthepy/pythonx-compose/blob/main/docs/locale/README_ko.md) ## 🔌 Ecosystem | Repository | Role | |---|---| | [python-multiplatform](https://github.com/thisisthepy/python-multiplatform) | The binder: CPython embedded in Kotlin Multiplatform, exposing Kotlin to Python under Kotlin names | -| **pythonx-compose** | Compose, restructured for Python — this repository | +| **pythonx-compose** | Compose, restructured for Python (this repository) | | [toolchain](https://github.com/thisisthepy/toolchain) | Gradle build plugin for Python Multiplatform apps | | [pypackpack](https://github.com/thisisthepy/pypackpack) | Distributing Python projects across platforms | | [torchnative](https://github.com/thisisthepy/torchnative) | Run the real PyTorch ecosystem on device | diff --git a/docs/INTENT.md b/docs/INTENT.md index 4ea3e61..d99274c 100644 --- a/docs/INTENT.md +++ b/docs/INTENT.md @@ -9,31 +9,31 @@ and the user's own words disagree, the user's words win. Everything below is derived from these, in this order of authority: 1. **The user's statements**, quoted verbatim where they are used. -2. **`UI.ipynb`** — the user's notebook and this project's user-facing specification. It is +2. **`UI.ipynb`**: the user's notebook and this project's user-facing specification. It is untracked and git-ignored; it lives only in the maintainer's main checkout and is never copied, moved, or added. 3. The repository's original README (2023–2024): *"pythonx is a python extension for kotlin - integration"* — *"Python Wrapper for Kotlin Compose Multiplatform."* + integration"*, *"Python Wrapper for Kotlin Compose Multiplatform."* 4. Recorded decisions in `python-multiplatform` (`docs/ecosystem.md` §5b, `docs/kotlin-extensions-in-python.md` §4). These are records of work, not requirements; they are used only where they agree with 1–3. Anything that is an inference rather than a statement is marked: -> Inferred — confirm with the maintainer. +> Inferred: confirm with the maintainer. --- ## 1. What this project is for **Writing Compose user interfaces in Python.** A Python programmer imports `pythonx.compose.*`, -writes `@Composable` functions, and gets real Jetpack / Compose Multiplatform UI — the same widgets, +writes `@Composable` functions, and gets real Jetpack / Compose Multiplatform UI: the same widgets, the same parameters, spelled the way Python spells things. The notebook shows the experience this is for: a running app whose screen is replaced from a notebook cell (`main.App.update(Practice)`), whose state is read and written from Python -(`main.App.messages.getValue()` / `setValue(...)`), and whose widgets — `Text`, `Button`, `Card`, -`Icon`, `TextField`, `Column`, `Row`, `Spacer` — are called from ordinary Python functions. The +(`main.App.messages.getValue()` / `setValue(...)`), and whose widgets (`Text`, `Button`, `Card`, +`Icon`, `TextField`, `Column`, `Row`, `Spacer`) are called from ordinary Python functions. The notebook is titled *"Python 앱 개발 특강"* (a lecture on Python app development): the audience is people who know Python and want to build an app without first learning Kotlin. @@ -45,7 +45,7 @@ These are constraints the user stated, not design options. > "pythonx-compose는 pip 패키지 pythonx-compose로 배포될 물건이야" -It ships as the distribution **`pythonx-compose`**, installed with `pip`, providing the import +It ships as the distribution **`pythonx-compose`**, installed from PyPI, providing the import package `pythonx.compose`. ### 2.2 `pythonx` is a real Python package on disk @@ -60,7 +60,7 @@ something else synthesises at run time. ### 2.3 The binder never renames; renaming is this package's job `python-multiplatform` (the binder) exposes Kotlin declarations to Python **under their Kotlin -names** — `androidx.compose.material3` in Kotlin is `androidx.compose.material3` in Python. The +names**: `androidx.compose.material3` in Kotlin is `androidx.compose.material3` in Python. The binder must never turn `androidx` into `pythonx` or rename any other Kotlin namespace. Mapping `androidx.compose.*` to `pythonx.compose.*` is done **here, in real Python code**. @@ -92,8 +92,8 @@ Screens are written as `@Composable`-decorated Python functions, as the notebook ### 2.7 Type stubs ship in the package -`.pyi` stubs ship inside the wheel so an editor sees the full Pythonic surface — names, parameters, -types — even though the runtime resolves bindings on demand. +`.pyi` stubs ship inside the wheel so an editor sees the full Pythonic surface (names, parameters, +types) even though the runtime resolves bindings on demand. --- @@ -107,10 +107,10 @@ types — even though the runtime resolves bindings on demand. are named and reached from Python, not what they do. - **Not an invented API.** Parameters that do not exist in Kotlin (for example the notebook's `corner_radius` on `Button`) are not added just because an example used them. - > Inferred — confirm with the maintainer. (Follows from §2.4; the user has not ruled on + > Inferred: confirm with the maintainer. (Follows from §2.4; the user has not ruled on > convenience parameters explicitly.) -- **Not runtime reflection.** The chaquopy-era approach — `jclass(...)` lookups and scanning - mangled JVM names such as `Text-fLXpl1I` — is a removed option. Bindings are produced at build +- **Not runtime reflection.** The chaquopy-era approach (`jclass(...)` lookups and scanning + mangled JVM names such as `Text-fLXpl1I`) is a removed option. Bindings are produced at build time because GraalVM native images and Kotlin/Native have no reflection (`python-multiplatform` `docs/ecosystem.md` §5b). - **Not a hand-written Kotlin wrapper layer.** The Pythonic restructuring happens in Python, over @@ -125,7 +125,7 @@ These are recorded so that `docs/SPEC.md` does not settle them by accident. 1. **Coroutine scopes** the notebook imports, `DefaultCoroutineScope` / `MainCoroutineScope`. (The other convenience spellings are decided, §5.4–5.7.) -2. *(Decided, #60.)* **The `test/` directory** — a 2023–2024 Kotlin Multiplatform sample +2. *(Decided, #60.)* **The `test/` directory**: a 2023–2024 Kotlin Multiplatform sample (`pycomposeui`). Deleted; the tag `archive/pre-restructure` keeps it. ## 5. Decided (2026-10-03) diff --git a/docs/SPEC.md b/docs/SPEC.md index 7b4a34d..a7b81c1 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -1,8 +1,8 @@ # Specification -What `pythonx-compose` does — the behavioural contract. Every item stays inside `docs/INTENT.md`; +What `pythonx-compose` does: the behavioural contract. Every item stays inside `docs/INTENT.md`; anything found in the repository that INTENT does not cover is listed at the end under -**Outside intent — needs a decision** rather than written into the contract. +**Outside intent: needs a decision** rather than written into the contract. Each item carries a status: @@ -16,7 +16,7 @@ A behaviour change starts here, then becomes a failing test, then code (`AGENTS. ## 0. Test baseline (2026-10-03, after the a5028618 stubs) -Run from a worktree with `python -m pytest tests -q -rs` (pytest 8, mypy 2.4, CPython 3.13), where +Run from a worktree with `uv run --with pytest --with mypy pytest tests -q -rs` (pytest 8, mypy 2.4, CPython 3.13), where `UI.ipynb` is absent and its one test skips: | Environment | Result | @@ -44,7 +44,7 @@ in §9 and are not counted as features. ## 1. Distribution -### S1.1 Installed as `pythonx-compose`, importing `pythonx.compose` — `implemented` +### S1.1 Installed as `pythonx-compose`, importing `pythonx.compose`: `implemented` `pyproject.toml` declares `name = "pythonx-compose"`, version `0.1.0a1`, setuptools build, `requires-python >= 3.11` (the runtime reads the manifest with `tomllib`), and @@ -58,7 +58,7 @@ in §9 and are not counted as features. `LICENSE` names them (no emails), keywords, classifiers (Alpha, Python 3.11-3.13, Typed) and project URLs. `build-system.requires` is `setuptools>=77`, which understands that license form. - **0.1.0a1 is on PyPI** (2026-10-03: pre-release `v0.1.0a1` on develop `134a641`, publish-pypi run - 37120641414; a fresh-venv `pip install pythonx-compose==0.1.0a1` imports cleanly). + 37120641414; a fresh install of 0.1.0a1 from PyPI imports cleanly). - Released by publishing a GitHub Release tagged `v` (`.github/workflows/publish-pypi.yml`, trigger `release: published`; a bare tag push publishes nothing): the workflow checks the Release's tag equals `v` + the `pyproject.toml` version (`.github/scripts/check_release_version.py`, @@ -67,7 +67,7 @@ in §9 and are not counted as features. token). Nothing is uploaded by hand. - Typed: the wheel carries `py.typed` and the generated `.pyi` stubs (S1.2, issue #12). -### S1.2 Type stubs ship in the wheel — `implemented` +### S1.2 Type stubs ship in the wheel: `implemented` Stubs carry the Pythonic names and signatures the runtime resolves, so the name an editor completes and the name the interpreter resolves cannot drift. @@ -77,7 +77,7 @@ and the name the interpreter resolves cannot drift. regenerating them from the same input produces no diff. - **From what.** python-multiplatform's CI artefact `kotlin-stubs` (workflow run 37103737430, commit `a5028618`): Kotlin-named stubs for Compose 1.11.1, one `androidx/compose/.../__init__.pyi` - per Kotlin package. `python3 scripts/gen_stubs.py ` converts them; + per Kotlin package. `uv run python scripts/gen_stubs.py ` converts them; the first line of every generated stub records the artefact, run and commit it came from. - **The rule is the runtime's** (`_reexport.py`), applied by `scripts/gen_stubs.py`: - a module function or constant is renamed with `python_name` (upper-case names kept, others @@ -159,7 +159,7 @@ and the name the interpreter resolves cannot drift. attribute, and are left out. - Regenerating needs the artefact downloaded by hand into `.tmp/`; CI does not fetch it. -## 2. The mapping manifest (`pythonx-map.toml`) — `implemented` +## 2. The mapping manifest (`pythonx-map.toml`): `implemented` The manifest is this package's statement of which Kotlin package each `pythonx.compose.*` module stands for, so the binder does not have to know Compose exists (INTENT §2.3). @@ -201,7 +201,7 @@ notebook is git-ignored, so in a worktree or CI checkout it is absent, the expec and **the test passes without checking anything.** It is only meaningful in the maintainer's main checkout. -## 3. `pythonx` is a real package — `implemented` (module level) +## 3. `pythonx` is a real package: `implemented` (module level) Required by INTENT §2.2: `import pythonx.compose.material3` (and every other mapped module) loads **this repository's files on disk**, and those files import the binder-exposed @@ -236,7 +236,7 @@ only, and a method's keywords are Kotlin's. ## 4. Naming -### S4.1 Kotlin parameters, `snake_case` — `implemented` +### S4.1 Kotlin parameters, `snake_case`: `implemented` `onClick` → `on_click`, `fillMaxWidth` → `fill_max_width`, `zIndex` → `z_index`, `toURLString` → `to_url_string`; type names (`Modifier`) unchanged; an explicit overload keeps its @@ -249,14 +249,14 @@ Implemented for module-level names and keywords and for method names on a binder (`tests/test_chain.py::MethodKeywords`, 7 tests, and `MethodKeywordsNeedTheBindersDescription`, 2; needs python-multiplatform's `describe_member`, #54). Disabling the keyword map fails 4 tests. -### S4.2 The notebook's spellings are examples, not the contract — `implemented` as a rule +### S4.2 The notebook's spellings are examples, not the contract: `implemented` as a rule Where `UI.ipynb` writes `onclick`, the surface is `on_click` (`tests/test_chain.py::Names::test_on_click_not_onclick` records the decision). ## 5. Composables -### S5.1 `@Composable` decorator — `implemented` +### S5.1 `@Composable` decorator: `implemented` `pythonx.compose.runtime.Composable` is an identity decorator: it returns the exact function it was given, which still runs and keeps its name and docstring. Composer threading is not the caller's @@ -265,18 +265,18 @@ 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 against a fake host shaped after python-multiplatform #38; real Compose is the E2E module, issue #19) The host draws with `PythonContent("pythonx.compose.runtime", "app_root")` (python-multiplatform #18, issue #11). `pythonx.compose.runtime` provides: -- `app_root` — a Compose `MutableState` whose `.value` is a zero-argument callable, or `None` (the +- `app_root`: a Compose `MutableState` whose `.value` is a zero-argument callable, or `None` (the host draws nothing). It is created on first read, by the binder's `androidx.compose.runtime.mutableStateOf(None)`, because the binder may be installed after `pythonx` is imported; afterwards it is an ordinary module attribute, the same object every time. -- `@app` — `app(fn)` sets `app_root.value = fn` and returns `fn` unchanged. Declaring the root again +- `@app`: `app(fn)` sets `app_root.value = fn` and returns `fn` unchanged. Declaring the root again replaces the value, so the screen follows. There is no update or refresh function (INTENT §5.1). -- `state(initial)` — `mutableStateOf(initial)`, read and written through `.value`; no `getValue` or +- `state(initial)`: `mutableStateOf(initial)`, read and written through `.value`; no `getValue` or `setValue`. Both states come from one internal function, `_new_state`. When the binder cannot supply @@ -297,7 +297,7 @@ walked from a jar. It skips against a binder older than #38. That Compose observ recomposes is not shown here; it is the E2E module (python-multiplatform #26, issue #19). The stub carries `app`, `state` and `app_root` (`tests/test_stubs.py`). -### S5.2 Material 3 composables reach Python without per-widget wrappers — `partial` +### S5.2 Material 3 composables reach Python without per-widget wrappers: `partial` `Text`, `Button`, `Card`, `ListItem`, `Badge`, `BadgedBox`, `MaterialTheme`, `IconButton` and its toggle family, `TextField`, `Checkbox` and `Switch` are meant to be reached through the generated @@ -313,7 +313,7 @@ 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 `DefaultIcons`, `Icon` and colour schemes — `partial` +### 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` @@ -334,7 +334,7 @@ Still `planned`: the two colour-scheme factories take 36 `Color` parameters agai 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). -### S5.5 `TextField(state=...)` and `TextFieldState` — `partial` +### S5.5 `TextField(state=...)` and `TextFieldState`: `partial` INTENT §5.8. `TextField` uses Compose 1.11's state-based overload, so Compose owns the text buffer and the IME composing region and no Python callback runs per keystroke. There is no per-widget @@ -373,9 +373,9 @@ and the field's text asserted afterwards, is python-multiplatform's E2E test (#2 four frames; it is not run here. Not modelled: a function-typed slot (`label`, `on_value_change`), which needs the binder's `NewFunction` rows. -## 6. Modifiers — extension functions as methods +## 6. Modifiers: extension functions as methods -### S6.1 `Modifier` extensions are methods on the receiver proxy — `implemented` +### S6.1 `Modifier` extensions are methods on the receiver proxy: `implemented` `Modifier.padding(16).size(24)` chains; each link returns a new receiver; an unbound name raises `AttributeError` naming where it looked; the class-object spelling without a registered empty @@ -386,7 +386,7 @@ Evidence: `tests/test_chain.py::TheChain` (8 tests) and `tests/test_modifier_module.py::TheModifierSeam` (5 tests), against python-multiplatform `ba4c6f49`. A snake_case method resolved through the member rule leaves the binder's class Kotlin-named. -### S6.2 The empty-`Modifier` seam — `partial` +### S6.2 The empty-`Modifier` seam: `partial` `pythonx/compose/ui/modifier.py` provides `install(empty_factory)` to register which Kotlin function returns an empty `Modifier`, and resolves `Modifier` lazily. **No such function exists in Compose**: @@ -396,14 +396,14 @@ one-line Kotlin factory. The instance spelling (`m.padding(16)` on a `Modifier` does not need it. `tests/test_modifier_module.py::TheShellIsGone` (3 tests, passing) checks the old hand-written shell is gone. -### S6.3 Overload dispatch — `implemented` +### S6.3 Overload dispatch: `implemented` Among Kotlin overloads of one name, a call is dispatched by keyword name, argument count, then declared type; a non-match names the candidates; an explicit overload spelling (`padding__Dp`) bypasses dispatch; the module function takes snake_case keywords. Evidence: `tests/test_chain.py::OverloadDispatch` (7 tests). Status: `implemented`. -### S6.4 Value classes — `implemented` +### S6.4 Value classes: `implemented` A raw number is accepted for a `Dp` parameter; a `Dp` value is accepted too; a plain `Float` parameter is not treated as a value class; a packed value class (`TextUnit`) refuses a raw number and @@ -411,14 +411,14 @@ says why; the allow-list can be extended at run time. The allow-list itself is t (S2, implemented) and reaches the binder when a `pythonx.compose` module first resolves a name. Runtime evidence: `tests/test_chain.py::ValueClasses` (6 tests). -### S6.5 Lazy resolution and handle lifetime — `implemented` +### S6.5 Lazy resolution and handle lifetime: `implemented` A mapped module is a file on disk and imports without a binder; one with no file is not importable; a name is adapted once and then lives in the module dict; `dir()` reports the Pythonic names only; without the binding layer a name read says the host never installed it; dropping a proxy releases its Kotlin handle. Evidence: `tests/test_chain.py::Laziness` (5 tests), `::Handles` (1 test). -## 7. Layout constants — `Alignment` and `Arrangement` — `implemented` +## 7. Layout constants, `Alignment` and `Arrangement`: `implemented` The bound constant names are documented in `pythonx/compose/ui/alignment.py` (15 `Alignment` names) and `pythonx/compose/layout/arrangement.py` (8 `Arrangement` names), checked by @@ -436,7 +436,7 @@ every access; a function inside the object is snake_case (`Arrangement.spaced_by `tests/test_chain.py::ObjectNamespaces` (6 tests); without the sub-package step 6 fail. The binder now lists these objects itself (python-multiplatform #35), and either way reaches them. -### S7.1 Grouped constants — `Alignment.Horizontal.End` — `implemented` +### S7.1 Grouped constants, `Alignment.Horizontal.End`: `implemented` INTENT §5.3: the notebook groups constants by type, `Alignment.Horizontal.End`, and Kotlin writes them flat, `Alignment.End` (which *is* an `Alignment.Horizontal`). Both spellings are served, by one @@ -492,7 +492,7 @@ with the group lookup disabled 11 fail, and the one that stays green checks the Compose surface is an extension receiver named `Alignment.Horizontal` or `Arrangement.Horizontal`, so the binder lists no member that would win over these groups. -## 8. The notebook surface not yet covered — `planned` +## 8. The notebook surface not yet covered: `planned` `UI.ipynb` imports or uses these, and nothing in this repository provides them: @@ -522,7 +522,7 @@ These tests pass and assert that retired 2024 mechanisms are gone. They are not --- -## Outside intent — needs a decision +## Outside intent: needs a decision Found in the repository; not covered by `docs/INTENT.md`, or in conflict with it. diff --git a/docs/guide/assets/style.css b/docs/guide/assets/style.css index 932d708..3d0beb1 100644 --- a/docs/guide/assets/style.css +++ b/docs/guide/assets/style.css @@ -1,4 +1,4 @@ -/* pythonx-compose guide — shared thisisthepy family tokens, accent #7c4dff. */ +/* pythonx-compose guide: shared thisisthepy family tokens, accent #7c4dff. */ :root { --bg: #fbfbfa; --fg: #16181d; --muted: #5b6270; --line: #e6e6e3; --code-bg: #f3f3f1; --radius: 10px; --maxw: 72rem; @@ -146,7 +146,7 @@ a.card:hover { text-decoration: none; border-color: var(--accent); } ul.clean { padding-left: 1.1rem; margin: 0 0 1rem; } ul.clean li { margin: 0.3rem 0; } -.not li::marker { content: "— "; color: var(--muted); } +.not li::marker { content: "– "; color: var(--muted); } /* Tables */ .table-wrap { overflow-x: auto; margin: 1rem 0 1.5rem; border: 1px solid var(--line); border-radius: var(--radius); } diff --git a/docs/guide/concepts.html b/docs/guide/concepts.html index e3715d3..b18981e 100644 --- a/docs/guide/concepts.html +++ b/docs/guide/concepts.html @@ -32,14 +32,14 @@

The two layers두 계층

-

python-multiplatform — the binder — embeds CPython in a Kotlin Multiplatform app and exposes Kotlin declarations to Python under their Kotlin names: androidx.compose.material3 is androidx.compose.material3. It never renames a namespace.

python-multiplatform — 바인더 — 는 Kotlin Multiplatform 앱에 CPython 을 임베딩하고, Kotlin 선언을 Kotlin 이름 그대로 파이썬에 노출합니다: androidx.compose.material3 는 androidx.compose.material3 입니다. 네임스페이스 이름을 절대 바꾸지 않습니다.

-

pythonx-compose — this package — is real Python on disk. Its modules import those androidx.compose.* modules and restructure them into pythonx.compose.*.

pythonx-compose — 이 패키지 — 는 디스크에 있는 실제 파이썬입니다. 이 패키지의 모듈이 androidx.compose.* 를 import 해 pythonx.compose.* 로 재구성합니다.

+

python-multiplatform, the binder, embeds CPython in a Kotlin Multiplatform app and exposes Kotlin declarations to Python under their Kotlin names: androidx.compose.material3 is androidx.compose.material3. It never renames a namespace.

python-multiplatform(바인더)는 Kotlin Multiplatform 앱에 CPython 을 임베딩하고, Kotlin 선언을 Kotlin 이름 그대로 파이썬에 노출합니다: androidx.compose.material3 는 androidx.compose.material3 입니다. 네임스페이스 이름을 절대 바꾸지 않습니다.

+

pythonx-compose, this package, is real Python on disk. Its modules import those androidx.compose.* modules and restructure them into pythonx.compose.*.

pythonx-compose(이 패키지)는 디스크에 있는 실제 파이썬입니다. 이 패키지의 모듈이 androidx.compose.* 를 import 해 pythonx.compose.* 로 재구성합니다.

@Composable def Screen()your Pythonpythonx.composethis package · real .pyandroidx.compose.*Kotlin names · binderComposeJetpack / Multiplatformpythonx-map.tomlthe one mapping.pyi stubsin the wheel@Composable def Screen()your Pythonpythonx.composethis package · real .pyandroidx.compose.*Kotlin names · binderComposeJetpack / Multiplatformpythonx-map.toml→ pythonx.compose, .pyi

The naming rule이름 규칙

-

Expose the original Kotlin declarations and parameters with Pythonic names. A name that starts upper-case — a type, an object, a composable — keeps its Kotlin spelling. Every other name is reached by its snake_case spelling only; the camelCase spelling is not a second name. An explicit overload keeps its type suffix.

Kotlin 의 원래 선언과 매개변수를 파이썬다운 이름으로 노출합니다. 대문자로 시작하는 이름 — 타입, 객체, 컴포저블 — 은 Kotlin 표기를 유지합니다. 그 밖의 이름은 snake_case 표기로만 접근하며, camelCase 표기가 또 하나의 이름이 되지는 않습니다. 명시적 오버로드는 타입 접미사를 유지합니다.

+

Expose the original Kotlin declarations and parameters with Pythonic names. A name that starts upper-case (a type, an object, a composable) keeps its Kotlin spelling. Every other name is reached by its snake_case spelling only; the camelCase spelling is not a second name. An explicit overload keeps its type suffix.

Kotlin 의 원래 선언과 매개변수를 파이썬다운 이름으로 노출합니다. 대문자로 시작하는 이름(타입, 객체, 컴포저블)은 Kotlin 표기를 유지합니다. 그 밖의 이름은 snake_case 표기로만 접근하며, camelCase 표기가 또 하나의 이름이 되지는 않습니다. 명시적 오버로드는 타입 접미사를 유지합니다.

@@ -69,7 +69,7 @@

One re-export rule재노출

Extension functions are methods확장 함수는 메서드

Most of Modifier is Kotlin extension functions. In Python each one is a method on the receiver's proxy, so a chain reads as it does in Kotlin. The binder's proxy class keeps Kotlin names; the same naming rule is registered as the binder's member resolver, so the snake_case spelling reaches the Kotlin member.

Modifier 의 대부분은 Kotlin 확장 함수입니다. 파이썬에서는 각각이 수신 객체 프록시의 메서드가 되어, 체인이 Kotlin 에서처럼 읽힙니다. 바인더의 프록시 클래스는 Kotlin 이름을 유지하며, 같은 이름 규칙이 바인더의 멤버 리졸버로 등록되어 snake_case 표기가 Kotlin 멤버에 닿습니다.

Modifier.padding(16).size(24).fill_max_width()
-

Keyword arguments to such a method are snake_case too — m.padding(padding_values=…), as the module function takes padding(m, padding_values=…). The resolver also hands the binder a keyword map read from the declaration's own parameter names. Kotlin's spelling (paddingValues=) still works at run time; the type stubs offer only the Pythonic one.

이런 메서드의 키워드 인자도 snake_case 입니다 — m.padding(padding_values=…) 이며, 모듈 함수는 padding(m, padding_values=…) 를 받습니다. 리졸버는 선언의 실제 파라미터 이름에서 읽은 키워드 맵도 바인더에 넘깁니다. Kotlin 이름(paddingValues=)도 실행 시에는 그대로 통하지만, 타입 스텁은 Pythonic 이름만 제시합니다.

+

Keyword arguments to such a method are snake_case too: m.padding(padding_values=…), as the module function takes padding(m, padding_values=…). The resolver also hands the binder a keyword map read from the declaration's own parameter names. Kotlin's spelling (paddingValues=) still works at run time; the type stubs offer only the Pythonic one.

이런 메서드의 키워드 인자도 snake_case 입니다: m.padding(padding_values=…) 이며, 모듈 함수는 padding(m, padding_values=…) 를 받습니다. 리졸버는 선언의 실제 파라미터 이름에서 읽은 키워드 맵도 바인더에 넘깁니다. Kotlin 이름(paddingValues=)도 실행 시에는 그대로 통하지만, 타입 스텁은 Pythonic 이름만 제시합니다.

@@ -87,7 +87,7 @@

Kotlin objectsKotlin 객체

Value classes값 클래스

-

Dp wraps a single float, so a raw number may stand for it: padding(16) means padding(16.dp). That permission is the manifest's allowlist, handed to the binder when a pythonx.compose module first resolves a name. Color and TextUnit pack several fields into one value, so a raw number would decode as something else entirely — they are refused, with a message saying why. A colour is written through Kotlin's own factory: Color(0xFFFF0000).

Dp 는 float 하나를 감싸므로 숫자로 쓸 수 있습니다: padding(16) 은 padding(16.dp) 입니다. 이 허용은 매니페스트의 허용 목록이며, pythonx.compose 모듈이 처음 이름을 해석할 때 바인더에 전달됩니다. Color 와 TextUnit 은 여러 필드를 한 값에 담으므로 숫자를 넣으면 전혀 다른 값으로 해석됩니다 — 그래서 이유를 밝히며 거절합니다. 색은 Kotlin 자신의 팩토리로 씁니다: Color(0xFFFF0000).

+

Dp wraps a single float, so a raw number may stand for it: padding(16) means padding(16.dp). That permission is the manifest's allowlist, handed to the binder when a pythonx.compose module first resolves a name. Color and TextUnit pack several fields into one value, so a raw number would decode as something else entirely, so they are refused, with a message saying why. A colour is written through Kotlin's own factory: Color(0xFFFF0000).

Dp 는 float 하나를 감싸므로 숫자로 쓸 수 있습니다: padding(16) 은 padding(16.dp) 입니다. 이 허용은 매니페스트의 허용 목록이며, pythonx.compose 모듈이 처음 이름을 해석할 때 바인더에 전달됩니다. Color 와 TextUnit 은 여러 필드를 한 값에 담으므로 숫자를 넣으면 전혀 다른 값으로 해석됩니다. 그래서 이유를 밝히며 거절합니다. 색은 Kotlin 자신의 팩토리로 씁니다: Color(0xFFFF0000).

@@ -105,7 +105,7 @@

@Composable@Composable
-pythonx-compose — Compose, restructured for Python.pythonx-compose — 파이썬을 위해 재구성한 Compose. +pythonx-compose: Compose, restructured for Python.pythonx-compose: 파이썬을 위해 재구성한 Compose. GitHub · Apache License 2.0Apache 라이선스 2.0
diff --git a/docs/guide/ecosystem.html b/docs/guide/ecosystem.html index 0f0dded..adc4adb 100644 --- a/docs/guide/ecosystem.html +++ b/docs/guide/ecosystem.html @@ -43,7 +43,7 @@

Who owns what누가 무엇
-pythonx-compose — Compose, restructured for Python.pythonx-compose — 파이썬을 위해 재구성한 Compose. +pythonx-compose: Compose, restructured for Python.pythonx-compose: 파이썬을 위해 재구성한 Compose. GitHub · Apache License 2.0Apache 라이선스 2.0
diff --git a/docs/guide/getting-started.html b/docs/guide/getting-started.html index c2e6d4a..946011a 100644 --- a/docs/guide/getting-started.html +++ b/docs/guide/getting-started.html @@ -30,22 +30,20 @@

Getting started시작하기

From clone to first call클론부터 첫 호출까지

Five minutes, honest about what runs.5 분, 동작하는 범위를 솔직하게.

-

pythonx-compose is an alpha: 0.1.0a1 is on PyPI. As a pre-release it installs with pip install --pre pythonx-compose. This page shows how to get the source, run the tests, and what you can call today.

pythonx-compose 는 알파 단계이며 0.1.0a1 이 PyPI 에 있습니다. 프리릴리스이므로 pip install --pre pythonx-compose 로 설치합니다. 이 페이지는 소스를 받고, 테스트를 돌리고, 지금 호출할 수 있는 것을 보여줍니다.

+

pythonx-compose is an alpha: 0.1.0a1 is on PyPI. As a pre-release it installs with uv add --prerelease allow pythonx-compose; with ppp it is ppp core add "pythonx-compose==0.1.0a1", with tcl tcl install pythonx-compose. It needs Python 3.11 or later. This page shows how to get the source, run the tests, and what you can call today.

pythonx-compose 는 알파 단계이며 0.1.0a1 이 PyPI 에 있습니다. 프리릴리스이므로 uv add --prerelease allow pythonx-compose 로 설치하며, ppp 로는 ppp core add "pythonx-compose==0.1.0a1", tcl 로는 tcl install pythonx-compose 입니다. 파이썬 3.11 이상이 필요합니다. 이 페이지는 소스를 받고, 테스트를 돌리고, 지금 호출할 수 있는 것을 보여줍니다.

1. Get the source1. 소스 받기

git clone https://github.com/thisisthepy/pythonx-compose
-cd pythonx-compose
-python3 -m venv .venv && . .venv/bin/activate
-python3 -m pip install pytest mypy      # Python 3.11 or later
+cd pythonx-compose

2. Run the tests2. 테스트 실행

-
python3 -m pytest tests -q
+
uv run --with pytest --with mypy pytest tests -q

Expect 140 passed, 112 skipped. One skipped test reads the maintainer's UI.ipynb, which a clone does not have; mypy type-checks the stubs. The other skipped tests drive the binder's adaptation layer and need a python-multiplatform checkout beside this one, or:

140 passed, 112 skipped 가 정상입니다. 건너뛴 테스트 하나는 클론에는 없는 메인테이너의 UI.ipynb 를 읽으며, mypy 는 스텁을 타입 검사합니다. 나머지 건너뛴 테스트는 바인더의 적응 계층을 구동하며, 옆에 python-multiplatform 체크아웃이 있거나 다음이 필요합니다:

-
PYTHONMULTIPLATFORM_HOME=/path/to/python-multiplatform python3 -m pytest tests -q
-

With a current one (31c092f0 or later) 251 pass and 1 skips (the UI.ipynb test); an older one skips 5 that call a snake_case method on a proxy the binder returned. That is expected and recorded.

최신 체크아웃(31c092f0 이후)이면 230 개가 통과하고 1 개(UI.ipynb 테스트)를 건너뛰며, 오래된 체크아웃은 바인더가 돌려준 proxy 의 snake_case 메서드를 부르는 5 개를 건너뜁니다. 예상된 상태이며 기록되어 있습니다.

+
PYTHONMULTIPLATFORM_HOME=/path/to/python-multiplatform uv run --with pytest --with mypy pytest tests -q
+

With a current one (31c092f0 or later) 251 pass and 1 skips (the UI.ipynb test); an older one skips 5 that call a snake_case method on a proxy the binder returned. That is expected and recorded.

최신 체크아웃(31c092f0 이후)이면 251 개가 통과하고 1 개(UI.ipynb 테스트)를 건너뛰며, 오래된 체크아웃은 바인더가 돌려준 proxy 의 snake_case 메서드를 부르는 5 개를 건너뜁니다. 예상된 상태이며 기록되어 있습니다.

@@ -84,7 +82,7 @@

3. What you can call today3

4. What you write in an app4. 앱 안에서 쓰는 코드

-

Inside an app whose Kotlin host has installed the binder, a screen looks like this — every name and parameter is Compose's own, spelled by the one rule:

Kotlin 호스트가 바인더를 설치한 앱 안에서는, 화면이 이렇게 됩니다 — 모든 이름과 매개변수는 Compose 자신의 것이며, 규칙 하나로 표기됩니다:

+

Inside an app whose Kotlin host has installed the binder, a screen looks like this: every name and parameter is Compose's own, spelled by the one rule:

Kotlin 호스트가 바인더를 설치한 앱 안에서는, 화면이 이렇게 됩니다. 모든 이름과 매개변수는 Compose 자신의 것이며, 규칙 하나로 표기됩니다:

from pythonx.compose.runtime import Composable
 from pythonx.compose.material3 import Text, Button, Column   # Column also from layout
 from pythonx.compose.layout import Arrangement
@@ -101,7 +99,7 @@ 

4. What you write in an app Button(on_click=lambda: None, content=lambda: Text("Press")), ), )

-

Two things to know. A chain that starts from the class, Modifier.padding(16), needs the app to register an empty-Modifier factory against real Compose — see Modifiers. And the screen above runs inside the app; a declared app root that redefining a screen replaces (issue #11) is planned, not built.

알아 둘 것이 두 가지 있습니다. 클래스에서 시작하는 체인 Modifier.padding(16) 은 실제 Compose 에서 앱이 빈 Modifier 팩토리를 등록해야 합니다 — 모디파이어를 보세요. 그리고 위 화면은 앱 안에서 실행됩니다. 화면을 다시 정의하면 교체되는 선언형 앱 루트(이슈 #11)는 계획 단계이며 아직 만들어지지 않았습니다.

+

Two things to know. A chain that starts from the class, Modifier.padding(16), needs the app to register an empty-Modifier factory against real Compose; see Modifiers. And the screen above runs inside the app; a declared app root that redefining a screen replaces (issue #11) is planned, not built.

알아 둘 것이 두 가지 있습니다. 클래스에서 시작하는 체인 Modifier.padding(16) 은 실제 Compose 에서 앱이 빈 Modifier 팩토리를 등록해야 합니다. 모디파이어를 보세요. 그리고 위 화면은 앱 안에서 실행됩니다. 화면을 다시 정의하면 교체되는 선언형 앱 루트(이슈 #11)는 계획 단계이며 아직 만들어지지 않았습니다.

@@ -109,7 +107,7 @@

4. What you write in an app
-pythonx-compose — Compose, restructured for Python.pythonx-compose — 파이썬을 위해 재구성한 Compose. +pythonx-compose: Compose, restructured for Python.pythonx-compose: 파이썬을 위해 재구성한 Compose. GitHub · Apache License 2.0Apache 라이선스 2.0
diff --git a/docs/guide/guide-composables.html b/docs/guide/guide-composables.html index 50fe3cf..0324809 100644 --- a/docs/guide/guide-composables.html +++ b/docs/guide/guide-composables.html @@ -44,7 +44,7 @@

Write a screen화면 하나 def Profile(name: str): Card(content=lambda: Text(f"Hello, {name}"))

A screen is a plain function. @Composable marks it; it does not wrap it. Content slots take a lambda.

화면은 평범한 함수입니다. @Composable 은 표시만 하고 감싸지 않습니다. content 슬롯은 람다를 받습니다.

-

Column, Row and Spacer are declared in Kotlin's layout package, and the notebook imports them from material3. Both work, and both give the same object — the manifest's [aliases] section lends them to material3:

Column, Row, Spacer 는 Kotlin 의 layout 패키지에 선언되어 있고, 노트북은 이들을 material3 에서 import 합니다. 둘 다 되며 같은 객체를 줍니다 — 매니페스트의 [aliases] 섹션이 이들을 material3 에 빌려줍니다:

+

Column, Row and Spacer are declared in Kotlin's layout package, and the notebook imports them from material3. Both work, and both give the same object: the manifest's [aliases] section lends them to material3:

Column, Row, Spacer 는 Kotlin 의 layout 패키지에 선언되어 있고, 노트북은 이들을 material3 에서 import 합니다. 둘 다 되며 같은 객체를 줍니다. 매니페스트의 [aliases] 섹션이 이들을 material3 에 빌려줍니다:

from pythonx.compose.material3 import Column, Row, Spacer   # as the notebook writes
 from pythonx.compose.layout import Column                   # Kotlin's home; same object

@@ -78,7 +78,7 @@

A text field텍스트 필 draft = TextFieldState("hi") draft.set_text_and_place_cursor_at_end("hello") draft.clear_text() -

Compose keeps the text and the input method's composing region, so nothing is sent to Python per keystroke; you read field.text on demand. Keep this state apart from state you stream output into, such as messages = state(""). The notebook's TextField(text_state=…, padding=8) has no Kotlin counterpart and is written TextField(state=…, modifier=Modifier.padding(8)). Status: partial — tested against a fake host; the input-method proof is python-multiplatform's end-to-end test (#26).

Compose 가 텍스트와 입력기의 조합 영역을 보관하므로 키 입력마다 파이썬으로 보내지는 것이 없고, 필요할 때 field.text 를 읽습니다. 이 상태는 출력을 흘려 넣는 상태(예: messages = state(""))와 분리해 두세요. 노트북의 TextField(text_state=…, padding=8) 은 Kotlin 에 대응이 없으며 TextField(state=…, modifier=Modifier.padding(8)) 로 씁니다. 상태: 부분 — 가짜 호스트로 테스트되었으며, 입력기 증거는 python-multiplatform 의 종단 간 테스트(#26)입니다.

+

Compose keeps the text and the input method's composing region, so nothing is sent to Python per keystroke; you read field.text on demand. Keep this state apart from state you stream output into, such as messages = state(""). The notebook's TextField(text_state=…, padding=8) has no Kotlin counterpart and is written TextField(state=…, modifier=Modifier.padding(8)). Status: partial. Tested against a fake host; the input-method proof is python-multiplatform's end-to-end test (#26).

Compose 가 텍스트와 입력기의 조합 영역을 보관하므로 키 입력마다 파이썬으로 보내지는 것이 없고, 필요할 때 field.text 를 읽습니다. 이 상태는 출력을 흘려 넣는 상태(예: messages = state(""))와 분리해 두세요. 노트북의 TextField(text_state=…, padding=8) 은 Kotlin 에 대응이 없으며 TextField(state=…, modifier=Modifier.padding(8)) 로 씁니다. 상태: 부분. 가짜 호스트로 테스트되었으며, 입력기 증거는 python-multiplatform 의 종단 간 테스트(#26)입니다.

@@ -90,7 +90,7 @@

From the notebook's spelling to the real parameters

- +
KotlinPython
Button(onclick=…)onClickon_click
Text(font_size=30)fontSizefont_size
Column(horizontal_alignment=…)horizontalAlignmenthorizontal_alignment
Button(corner_radius=50)none — Compose takes a shape없음 — Compose 는 shape 를 받음shape
Button(corner_radius=50)none: Compose takes a shape없음: Compose 는 shape 를 받음shape
TextField(text_state=…, padding=8)state, modifierstate=…, modifier=Modifier.padding(8)

fontSize is a TextUnit, which packs several fields into one value, so font_size refuses a raw number such as 30.

fontSize 는 여러 필드를 한 값에 담는 TextUnit 이므로, font_size 는 30 같은 숫자를 거절합니다.

@@ -111,8 +111,8 @@

Spellings that changed바

What does not work yet아직 안 되는 것

    -
  • The colour-scheme factories cannot be called yet: they take 36 colour parameters. DefaultIcons is Icons.Default, an icon is a value (an ImageVector to a type checker) read without parentheses — Icon(DefaultIcons.Add, content_description=None), not the notebook's DefaultIcons.Add() — and it is tested here against a fake host only; the real draw is in python-multiplatform's render test.색 스킴 팩토리는 색 매개변수를 36 개 받아 아직 호출할 수 없습니다. DefaultIcons 는 Icons.Default 이고, 아이콘은 괄호 없이 읽는 값(타입 검사기에는 ImageVector)입니다 — 노트북의 DefaultIcons.Add() 가 아니라 Icon(DefaultIcons.Add, content_description=None). 여기서는 가짜 호스트로만 테스트되며, 실제 그리기는 python-multiplatform 의 렌더 테스트에 있습니다.
  • -
  • A declared app root and Pythonic state — redefining a screen replaces it, with no update call — are planned (issue #11).선언형 앱 루트와 파이썬다운 상태 — 화면을 다시 정의하면 교체되고, 갱신 호출은 없음 — 는 계획 단계입니다(이슈 #11).
  • +
  • The colour-scheme factories cannot be called yet: they take 36 colour parameters. DefaultIcons is Icons.Default, an icon is a value (an ImageVector to a type checker) read without parentheses: Icon(DefaultIcons.Add, content_description=None), not the notebook's DefaultIcons.Add(). It is tested here against a fake host only; the real draw is in python-multiplatform's render test.색 스킴 팩토리는 색 매개변수를 36 개 받아 아직 호출할 수 없습니다. DefaultIcons 는 Icons.Default 이고, 아이콘은 괄호 없이 읽는 값(타입 검사기에는 ImageVector)입니다. 노트북의 DefaultIcons.Add() 가 아니라 Icon(DefaultIcons.Add, content_description=None). 여기서는 가짜 호스트로만 테스트되며, 실제 그리기는 python-multiplatform 의 렌더 테스트에 있습니다.
  • +
  • A declared app root and Pythonic state (redefining a screen replaces it, with no update call) are planned (issue #11).선언형 앱 루트와 파이썬다운 상태(화면을 다시 정의하면 교체되고, 갱신 호출은 없음)는 계획 단계입니다(이슈 #11).
  • TextField(state=…) 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).TextField(state=…) 는 가짜 호스트로만 테스트되었습니다. 입력기의 조합 입력이 키 입력마다 파이썬 콜백 없이 유지되는지는 python-multiplatform 의 종단 간 테스트(#26)에서 증명합니다.
  • remember_saveable and the coroutine scopes the notebook imports are planned.노트북이 import 하는 remember_saveable, 코루틴 스코프는 계획 단계입니다.
@@ -123,7 +123,7 @@

What does not work yet아
-pythonx-compose — Compose, restructured for Python.pythonx-compose — 파이썬을 위해 재구성한 Compose. +pythonx-compose: Compose, restructured for Python.pythonx-compose: 파이썬을 위해 재구성한 Compose. GitHub · Apache License 2.0Apache 라이선스 2.0
diff --git a/docs/guide/guide-manifest.html b/docs/guide/guide-manifest.html index 7302722..d4fb26f 100644 --- a/docs/guide/guide-manifest.html +++ b/docs/guide/guide-manifest.html @@ -62,7 +62,7 @@

Lending a name to another module

Keeping it in step with the notebook노트북과 맞춰 두기

-

A test reads every pythonx import in the user's notebook and fails if the manifest does not map it. The notebook lives only in the maintainer's checkout, so elsewhere that test has nothing to check — run it there before claiming coverage.

테스트 하나가 사용자 노트북의 모든 pythonx import 를 읽고, 매니페스트가 대응시키지 않으면 실패합니다. 노트북은 메인테이너의 체크아웃에만 있으므로 다른 곳에서는 그 테스트가 확인할 것이 없습니다 — 커버리지를 주장하기 전에 그곳에서 돌리세요.

+

A test reads every pythonx import in the user's notebook and fails if the manifest does not map it. The notebook lives only in the maintainer's checkout, so elsewhere that test has nothing to check, so run it there before claiming coverage.

테스트 하나가 사용자 노트북의 모든 pythonx import 를 읽고, 매니페스트가 대응시키지 않으면 실패합니다. 노트북은 메인테이너의 체크아웃에만 있으므로 다른 곳에서는 그 테스트가 확인할 것이 없습니다. 커버리지를 주장하기 전에 그곳에서 돌리세요.

@@ -70,7 +70,7 @@

Keeping it in step with the notebook
-pythonx-compose — Compose, restructured for Python.pythonx-compose — 파이썬을 위해 재구성한 Compose. +pythonx-compose: Compose, restructured for Python.pythonx-compose: 파이썬을 위해 재구성한 Compose. GitHub · Apache License 2.0Apache 라이선스 2.0
diff --git a/docs/guide/guide-modifiers.html b/docs/guide/guide-modifiers.html index 271bc19..9c9d891 100644 --- a/docs/guide/guide-modifiers.html +++ b/docs/guide/guide-modifiers.html @@ -32,7 +32,7 @@

partial부분

-

Chains, snake_case method names, overload dispatch and Dp as a number are implemented and pass against the binder's adaptation layer (python-multiplatform ba4c6f49 or later). The partial piece is where a chain starts against real Compose — see the empty Modifier below.

체인, snake_case 메서드 이름, 오버로드 디스패치, 숫자로 쓰는 Dp 는 구현되어 있고 바인더의 적응 계층(python-multiplatform ba4c6f49 이후)에서 통과합니다. 부분적인 것은 실제 Compose 에서 체인이 시작되는 지점입니다 — 아래 빈 Modifier 를 보세요.

+

Chains, snake_case method names, overload dispatch and Dp as a number are implemented and pass against the binder's adaptation layer (python-multiplatform ba4c6f49 or later). The partial piece is where a chain starts against real Compose. See the empty Modifier below.

체인, snake_case 메서드 이름, 오버로드 디스패치, 숫자로 쓰는 Dp 는 구현되어 있고 바인더의 적응 계층(python-multiplatform ba4c6f49 이후)에서 통과합니다. 부분적인 것은 실제 Compose 에서 체인이 시작되는 지점입니다. 아래 빈 Modifier 를 보세요.

@@ -77,7 +77,7 @@

Numbers and value classes

The empty Modifier빈 Modifier

-

In Kotlin, Modifier as an expression is an object, and bindings are functions — so there is no bound name for the empty modifier a chain starts from. pythonx/compose/ui/modifier.py is the seam: an application registers a one-line Kotlin factory.

Kotlin 에서 식으로 쓰인 Modifier 는 객체이고 바인딩은 함수이므로, 체인이 시작하는 빈 모디파이어에는 바인딩된 이름이 없습니다. pythonx/compose/ui/modifier.py 가 그 접점이며, 앱이 한 줄짜리 Kotlin 팩토리를 등록합니다.

+

In Kotlin, Modifier as an expression is an object, and bindings are functions, so there is no bound name for the empty modifier a chain starts from. pythonx/compose/ui/modifier.py is the seam: an application registers a one-line Kotlin factory.

Kotlin 에서 식으로 쓰인 Modifier 는 객체이고 바인딩은 함수이므로, 체인이 시작하는 빈 모디파이어에는 바인딩된 이름이 없습니다. pythonx/compose/ui/modifier.py 가 그 접점이며, 앱이 한 줄짜리 Kotlin 팩토리를 등록합니다.

// in the application's Kotlin
 fun emptyModifier(): Modifier = Modifier
import pythonx.compose.ui.modifier as seam
@@ -91,7 +91,7 @@ 

The empty Modifier빈 Modif
-pythonx-compose — Compose, restructured for Python.pythonx-compose — 파이썬을 위해 재구성한 Compose. +pythonx-compose: Compose, restructured for Python.pythonx-compose: 파이썬을 위해 재구성한 Compose. GitHub · Apache License 2.0Apache 라이선스 2.0
diff --git a/docs/guide/index.html b/docs/guide/index.html index 38a24b4..c3c0ccb 100644 --- a/docs/guide/index.html +++ b/docs/guide/index.html @@ -31,9 +31,9 @@
-pre-alpha · pip install --pre pythonx-composepre-alpha · pip install --pre pythonx-compose +pre-alpha · uv add --prerelease allow pythonx-composepre-alpha · uv add --prerelease allow pythonx-compose

Compose, in Python.Compose 를, 파이썬으로.

-

The real Compose widgets with their real parameters — restructured into a Python package, spelled the way Python spells things.진짜 Compose 위젯과 진짜 매개변수를 — 파이썬 패키지로 재구성해, 파이썬이 쓰는 방식대로 씁니다.

+

The real Compose widgets with their real parameters, restructured into a Python package, spelled the way Python spells things.진짜 Compose 위젯과 진짜 매개변수를, 파이썬 패키지로 재구성해, 파이썬이 쓰는 방식대로 씁니다.

Get started시작하기 → How it works동작 방식 @@ -77,15 +77,15 @@

Two layers, one rule about names.What it holds to지키는 것

onClick → on_click

The original API원래 API 그대로

Compose's own parameters, in snake_case. No convenience parameters Kotlin does not have.

Compose 자신의 매개변수를 snake_case 로. Kotlin 에 없는 편의 매개변수는 만들지 않습니다.

-
Modifier.padding(16).size(24)

Extensions are methods확장 함수는 메서드

A Kotlin extension function is a method on its receiver, so chains read as they do in Kotlin — fill_max_width() included.

Kotlin 확장 함수는 수신 객체의 메서드가 되어, 체인이 Kotlin 에서처럼 읽힙니다 — fill_max_width() 도 마찬가지입니다.

+
Modifier.padding(16).size(24)

Extensions are methods확장 함수는 메서드

A Kotlin extension function is a method on its receiver, so chains read as they do in Kotlin, fill_max_width() included.

Kotlin 확장 함수는 수신 객체의 메서드가 되어, 체인이 Kotlin 에서처럼 읽힙니다. fill_max_width() 도 마찬가지입니다.

import androidx.compose…

A real package실제 패키지

pythonx/ is ordinary Python on disk. Renaming happens here, never in the binder.

pythonx/ 는 디스크에 있는 평범한 파이썬입니다. 이름 바꾸기는 여기서 하고, 바인더에서는 하지 않습니다.

@Composable

Composable stays@Composable 은 그대로

Screens are decorated Python functions, the way the notebook writes them.

화면은 노트북이 쓰는 그대로, 데코레이터가 붙은 파이썬 함수입니다.

What it is not이것이 아닌 것

    -
  • Not the binder — embedding CPython and the FFI belong to python-multiplatform.바인더가 아닙니다 — CPython 임베딩과 FFI 는 python-multiplatform 의 몫입니다.
  • +
  • Not the binder: embedding CPython and the FFI belong to python-multiplatform.바인더가 아닙니다: CPython 임베딩과 FFI 는 python-multiplatform 의 몫입니다.
  • Not a reimplementation of Compose, and not a new widget set.Compose 의 재구현도, 새 위젯 세트도 아닙니다.
  • -
  • Not runtime reflection — bindings are produced at build time, because native images and Kotlin/Native have none.런타임 리플렉션이 아닙니다 — 네이티브 이미지와 Kotlin/Native 에는 리플렉션이 없으므로 바인딩은 빌드 타임에 만듭니다.
  • +
  • Not runtime reflection: bindings are produced at build time, because native images and Kotlin/Native have none.런타임 리플렉션이 아닙니다: 네이티브 이미지와 Kotlin/Native 에는 리플렉션이 없으므로 바인딩은 빌드 타임에 만듭니다.
@@ -93,7 +93,7 @@

What it is not이것이 아
-pythonx-compose — Compose, restructured for Python.pythonx-compose — 파이썬을 위해 재구성한 Compose. +pythonx-compose: Compose, restructured for Python.pythonx-compose: 파이썬을 위해 재구성한 Compose. GitHub · Apache License 2.0Apache 라이선스 2.0
diff --git a/docs/guide/status.html b/docs/guide/status.html index 07d5a54..c0bdeb1 100644 --- a/docs/guide/status.html +++ b/docs/guide/status.html @@ -34,13 +34,13 @@

Implemented means code in this repository and a passing test that exercises it. Tests that only assert an absence are not counted.

구현은 이 저장소의 코드와 그것을 실행하는 통과 테스트가 함께 있다는 뜻입니다. 부재만 확인하는 테스트는 세지 않습니다.

- +
State상태Item항목
implemented구현Mapping manifest pythonx/compose/pythonx-map.toml — inside the package, ships in the wheel매핑 매니페스트 pythonx/compose/pythonx-map.toml — 패키지 안에 있으며 wheel 에 포함
implemented구현@Composable identity decorator@Composable 항등 데코레이터
implemented구현pythonx as a real on-disk package re-exporting androidx.compose.* by one rule — names, keywords, signatures, Dp allowlistandroidx.compose.* 를 규칙 하나로 재노출하는 실제 디스크 패키지 pythonx — 이름, 키워드, 시그니처, Dp 허용 목록
implemented구현Modifier chains with snake_case methods, overload dispatch, Dp as a numbersnake_case 메서드를 쓰는 Modifier 체인, 오버로드 디스패치, 숫자 Dp
implemented구현Column, Row, Spacer importable from material3 as well as layout (manifest [aliases])layout 뿐 아니라 material3 에서도 import 되는 Column, Row, Spacer (매니페스트 [aliases])
implemented구현Type stubs (.pyi) generated from real Compose 1.11.1 by the runtime's naming rule, with py.typed, checked with mypy (#12); many types are still Any런타임과 같은 이름 규칙으로 실제 Compose 1.11.1 에서 생성한 타입 스텁(.pyi)과 py.typed, mypy 로 검사(#12); 아직 많은 타입이 Any 입니다
implemented구현Snake-case names, keywords and signatures — modules and methods; a method's keywords need python-multiplatform's describe_member, and Kotlin's own spelling still works at run timesnake_case 이름·키워드·시그니처 — 모듈과 메서드; 메서드 키워드에는 python-multiplatform 의 describe_member 가 필요하고, Kotlin 이름도 실행 시에는 그대로 통합니다
partial부분The empty Modifier — Modifier.padding(16) from the class needs an app-supplied factory against real Compose빈 Modifier — 클래스에서 시작하는 Modifier.padding(16) 은 실제 Compose 에서 앱이 제공하는 팩토리가 필요
implemented구현Alignment / Arrangement — Alignment.Center, Arrangement.End, Arrangement.spaced_by(8), and the grouped Alignment.Horizontal.End beside Alignment.EndAlignment / Arrangement — Alignment.Center, Arrangement.End, Arrangement.spaced_by(8), 그리고 Alignment.End 와 나란히 묶음 표기 Alignment.Horizontal.End
partial부분Material 3 widgets — re-exported by rule; render proofs per widget pending (#9)Material 3 위젯 — 규칙으로 재노출됨; 위젯별 렌더 증거 대기(#9)
partial부분Distribution as pythonx-compose — the wheel carries the manifest, the re-export rule, the type stubs and py.typedpythonx-compose 배포 — wheel 에 매니페스트, 재노출 규칙, 타입 스텁, py.typed 포함
partial부분A declared app root (@app) and Pythonic state (state), with no update call — binder path tested against a fake host shaped after python-multiplatform #38; real Compose is the E2E module (#11, #19)갱신 호출 없는 선언형 앱 루트(@app)와 파이썬다운 상태(state) — 바인더 경로는 python-multiplatform #38 모양의 가짜 호스트로 테스트됨, 실제 Compose 는 E2E 모듈(#11, #19)
partial부분DefaultIcons is Icons.Default, written DefaultIcons.Add — tested against a fake host shaped after python-multiplatform #37/#38; the real draw is in python-multiplatform's render testDefaultIcons 는 Icons.Default 이며 DefaultIcons.Add 로 씁니다 — python-multiplatform #37/#38 모양의 가짜 호스트로 테스트됨; 실제 그리기는 python-multiplatform 의 렌더 테스트에 있음
partial부분TextField(state=…) with TextFieldState from pythonx.compose.foundation.text.input — tested against a fake host shaped after python-multiplatform #73; the input-method (IME) composing proof is python-multiplatform E2E #26 (#10)pythonx.compose.foundation.text.input 의 TextFieldState 를 쓰는 TextField(state=…) — python-multiplatform #73 모양의 가짜 호스트로 테스트됨; 입력기(IME) 조합 증거는 python-multiplatform E2E #26 (#10)
planned계획Colour schemes (36 colour parameters)색 스킴 (색 매개변수 36 개)
planned계획remember_saveable, coroutine scopesremember_saveable, 코루틴 스코프
implemented구현Mapping manifest pythonx/compose/pythonx-map.toml: inside the package, ships in the wheel매핑 매니페스트 pythonx/compose/pythonx-map.toml: 패키지 안에 있으며 wheel 에 포함
implemented구현@Composable identity decorator@Composable 항등 데코레이터
implemented구현pythonx as a real on-disk package re-exporting androidx.compose.* by one rule: names, keywords, signatures, Dp allowlistandroidx.compose.* 를 규칙 하나로 재노출하는 실제 디스크 패키지 pythonx: 이름, 키워드, 시그니처, Dp 허용 목록
implemented구현Modifier chains with snake_case methods, overload dispatch, Dp as a numbersnake_case 메서드를 쓰는 Modifier 체인, 오버로드 디스패치, 숫자 Dp
implemented구현Column, Row, Spacer importable from material3 as well as layout (manifest [aliases])layout 뿐 아니라 material3 에서도 import 되는 Column, Row, Spacer (매니페스트 [aliases])
implemented구현Type stubs (.pyi) generated from real Compose 1.11.1 by the runtime's naming rule, with py.typed, checked with mypy (#12); many types are still Any런타임과 같은 이름 규칙으로 실제 Compose 1.11.1 에서 생성한 타입 스텁(.pyi)과 py.typed, mypy 로 검사(#12); 아직 많은 타입이 Any 입니다
implemented구현Snake-case names, keywords and signatures: modules and methods; a method's keywords need python-multiplatform's describe_member, and Kotlin's own spelling still works at run timesnake_case 이름·키워드·시그니처: 모듈과 메서드; 메서드 키워드에는 python-multiplatform 의 describe_member 가 필요하고, Kotlin 이름도 실행 시에는 그대로 통합니다
partial부분The empty Modifier: Modifier.padding(16) from the class needs an app-supplied factory against real Compose빈 Modifier: 클래스에서 시작하는 Modifier.padding(16) 은 실제 Compose 에서 앱이 제공하는 팩토리가 필요
implemented구현Alignment / Arrangement: Alignment.Center, Arrangement.End, Arrangement.spaced_by(8), and the grouped Alignment.Horizontal.End beside Alignment.EndAlignment / Arrangement: Alignment.Center, Arrangement.End, Arrangement.spaced_by(8), 그리고 Alignment.End 와 나란히 묶음 표기 Alignment.Horizontal.End
partial부분Material 3 widgets: re-exported by rule; render proofs per widget pending (#9)Material 3 위젯: 규칙으로 재노출됨; 위젯별 렌더 증거 대기(#9)
partial부분Distribution as pythonx-compose: the wheel carries the manifest, the re-export rule, the type stubs and py.typedpythonx-compose 배포: wheel 에 매니페스트, 재노출 규칙, 타입 스텁, py.typed 포함
partial부분A declared app root (@app) and Pythonic state (state), with no update call: binder path tested against a fake host shaped after python-multiplatform #38; real Compose is the E2E module (#11, #19)갱신 호출 없는 선언형 앱 루트(@app)와 파이썬다운 상태(state): 바인더 경로는 python-multiplatform #38 모양의 가짜 호스트로 테스트됨, 실제 Compose 는 E2E 모듈(#11, #19)
partial부분DefaultIcons is Icons.Default, written DefaultIcons.Add, tested against a fake host shaped after python-multiplatform #37/#38; the real draw is in python-multiplatform's render testDefaultIcons 는 Icons.Default 이며 DefaultIcons.Add 로 씁니다. python-multiplatform #37/#38 모양의 가짜 호스트로 테스트됨; 실제 그리기는 python-multiplatform 의 렌더 테스트에 있음
partial부분TextField(state=…) with TextFieldState from pythonx.compose.foundation.text.input: tested against a fake host shaped after python-multiplatform #73; the input-method (IME) composing proof is python-multiplatform E2E #26 (#10)pythonx.compose.foundation.text.input 의 TextFieldState 를 쓰는 TextField(state=…): python-multiplatform #73 모양의 가짜 호스트로 테스트됨; 입력기(IME) 조합 증거는 python-multiplatform E2E #26 (#10)
planned계획Colour schemes (36 colour parameters)색 스킴 (색 매개변수 36 개)
planned계획remember_saveable, coroutine scopesremember_saveable, 코루틴 스코프

Test baseline: 140 passed, 112 skipped without a python-multiplatform checkout; 251 passed, 1 skipped with a current one. The one skip with a current checkout reads the maintainer's UI.ipynb.

테스트 기준선: python-multiplatform 체크아웃이 없으면 132 passed, 99 skipped, 최신 체크아웃이면 251 passed, 1 skipped. 최신 체크아웃에서 건너뛰는 1 개는 메인테이너의 UI.ipynb 를 읽는 테스트입니다.

FAQ

-
Can I pip install it?pip 으로 설치할 수 있나요?

Yes: pip install --pre pythonx-compose installs 0.1.0a1, the first alpha. It needs a host app that embeds python-multiplatform to resolve Compose names.

네: pip install --pre pythonx-compose 로 첫 알파 0.1.0a1 을 설치합니다. Compose 이름을 쓰려면 python-multiplatform 을 품은 호스트 앱이 필요합니다.

Do I need to know Kotlin?Kotlin 을 알아야 하나요?

No. The widgets and parameters are Compose's, so Compose documentation applies — read onClick as on_click.

아니요. 위젯과 매개변수가 Compose 의 것이므로 Compose 문서가 그대로 적용됩니다 — onClick 을 on_click 으로 읽으면 됩니다.

Does it run on its own?단독으로 실행되나요?

No. It runs inside an app that embeds CPython through python-multiplatform.

아니요. python-multiplatform 으로 CPython 을 임베딩한 앱 안에서 동작합니다.

Why not look classes up at run time?왜 런타임에 클래스를 찾지 않나요?

GraalVM native images and Kotlin/Native have no reflection, so bindings are generated at build time.

GraalVM 네이티브 이미지와 Kotlin/Native 에는 리플렉션이 없으므로, 바인딩을 빌드 타임에 생성합니다.

What happened to the test/ directory?test/ 디렉터리는 어떻게 되었나요?

It no longer exists. It was a 2023–2024 Kotlin Multiplatform sample from the project's first generation, and it was deleted; the tag archive/pre-restructure keeps it.

더 이상 없습니다. 프로젝트 첫 세대의 2023–2024 Kotlin Multiplatform 샘플이었고 삭제되었으며, 태그 archive/pre-restructure 로 되찾을 수 있습니다.

+
Can I install it from PyPI?PyPI 에서 설치할 수 있나요?

Yes: uv add --prerelease allow pythonx-compose installs 0.1.0a1, the first alpha; with ppp use ppp core add "pythonx-compose==0.1.0a1", with tcl tcl install pythonx-compose. It needs a host app that embeds python-multiplatform to resolve Compose names.

네: uv add --prerelease allow pythonx-compose 로 첫 알파 0.1.0a1 을 설치합니다. ppp 는 ppp core add "pythonx-compose==0.1.0a1", tcl 은 tcl install pythonx-compose 입니다. Compose 이름을 쓰려면 python-multiplatform 을 품은 호스트 앱이 필요합니다.

Do I need to know Kotlin?Kotlin 을 알아야 하나요?

No. The widgets and parameters are Compose's, so Compose documentation applies; read onClick as on_click.

아니요. 위젯과 매개변수가 Compose 의 것이므로 Compose 문서가 그대로 적용됩니다. onClick 을 on_click 으로 읽으면 됩니다.

Does it run on its own?단독으로 실행되나요?

No. It runs inside an app that embeds CPython through python-multiplatform.

아니요. python-multiplatform 으로 CPython 을 임베딩한 앱 안에서 동작합니다.

Why not look classes up at run time?왜 런타임에 클래스를 찾지 않나요?

GraalVM native images and Kotlin/Native have no reflection, so bindings are generated at build time.

GraalVM 네이티브 이미지와 Kotlin/Native 에는 리플렉션이 없으므로, 바인딩을 빌드 타임에 생성합니다.

What happened to the test/ directory?test/ 디렉터리는 어떻게 되었나요?

It no longer exists. It was a 2023–2024 Kotlin Multiplatform sample from the project's first generation, and it was deleted; the tag archive/pre-restructure keeps it.

더 이상 없습니다. 프로젝트 첫 세대의 2023–2024 Kotlin Multiplatform 샘플이었고 삭제되었으며, 태그 archive/pre-restructure 로 되찾을 수 있습니다.

@@ -48,7 +48,7 @@

FAQ

diff --git a/docs/locale/README_ko.md b/docs/locale/README_ko.md index 959c7fe..b97dc23 100644 --- a/docs/locale/README_ko.md +++ b/docs/locale/README_ko.md @@ -4,7 +4,7 @@ # pythonx-compose -**Compose UI 를 파이썬으로 — Compose 의 위젯 그대로, 파이썬다운 이름으로.** +**Compose UI 를 파이썬으로, Compose 의 위젯 그대로, 파이썬다운 이름으로.** [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-7c4dff.svg)](https://github.com/thisisthepy/pythonx-compose/blob/main/LICENSE) [![Python](https://img.shields.io/badge/python-3.11%2B-7c4dff.svg)](https://github.com/thisisthepy/pythonx-compose/blob/main/pyproject.toml) @@ -19,7 +19,7 @@ ## 왜 만드는가 -Jetpack Compose 와 Compose Multiplatform 은 UI 를 만드는 훌륭한 방법입니다 — Kotlin 을 쓴다면요. +Jetpack Compose 와 Compose Multiplatform 은 UI 를 만드는 훌륭한 방법입니다. Kotlin 을 쓴다면요. `pythonx-compose` 는 파이썬을 쓰는 사람을 위한 것입니다. Compose 를 다시 구현하지도, 새 위젯 세트를 만들지도 않습니다. [python-multiplatform](https://github.com/thisisthepy/python-multiplatform) 이 Kotlin 이름 그대로 파이썬에 노출한 실제 `androidx.compose.*` API 를 가져와, 파이썬다운 패키지 @@ -53,7 +53,7 @@ def Greeting(): 수도 있습니다: `Alignment.Horizontal.End` 는 `Alignment.End` 입니다. - **`@Composable` 은 그대로.** 화면은 데코레이터가 붙은 파이썬 함수입니다. - **실제 패키지.** `pythonx/` 는 `androidx.compose.*` 를 import 해 재구성하는 평범한 파이썬 - 소스입니다. 바인더는 아무 이름도 바꾸지 않습니다 — 이름을 바꾸는 것은 이 패키지의 일입니다. + 소스입니다. 바인더는 아무 이름도 바꾸지 않습니다. 이름을 바꾸는 것은 이 패키지의 일입니다. - **하나의 매니페스트.** [`pythonx-map.toml`](https://github.com/thisisthepy/pythonx-compose/blob/main/pythonx/compose/pythonx-map.toml) 이 어떤 `pythonx.compose.*` 모듈이 어떤 Kotlin 패키지에 대응하는지 적습니다. 이 파일은 패키지 안에 있으며 wheel 에 함께 담깁니다. 런타임과 `.pyi` 생성기가 같은 파일을 읽으므로, 편집기가 자동완성하는 이름과 인터프리터가 @@ -85,15 +85,22 @@ flowchart LR > [!NOTE] > `pythonx-compose` 는 **알파** 단계입니다. `0.1.0a1` 이 [PyPI](https://pypi.org/project/pythonx-compose/) 에 -> 있으며, 프리릴리스이므로 `pip install --pre pythonx-compose` 로 설치합니다. +> 있으며, 프리릴리스이므로 uv(또는 ppp, tcl)로 설치합니다. + +```bash +uv add --prerelease allow pythonx-compose +# 또는 pypackpack 으로, ppp 워크스페이스의 패키지에 추가: +ppp core add "pythonx-compose==0.1.0a1" +# 또는 toolchain-lite 로: +tcl install pythonx-compose +``` 클론에서 테스트를 돌리려면: ```bash git clone https://github.com/thisisthepy/pythonx-compose cd pythonx-compose -python3 -m pip install pytest # 가상환경 안에서 -python3 -m pytest tests -q +uv run --with pytest --with mypy pytest tests -q ``` 저장소 루트에서 지금 동작하는 것: @@ -152,15 +159,15 @@ pip 패키지 **`pythonx-compose`**(파이썬 3.11 이상)로 배포되며, impo | `androidx.compose.*` 를 규칙 하나로 재노출하는 실제 디스크 패키지 `pythonx` | ✅ 구현, 테스트됨 | | snake_case 메서드를 쓰는 `Modifier` 체인, 오버로드 디스패치, 숫자로 쓰는 `Dp` | ✅ 구현, 바인더 계층으로 테스트됨 | | `layout` 뿐 아니라 `material3` 에서도 쓰는 `Column`, `Row`, `Spacer` | ✅ 구현, 테스트됨 | -| `snake_case` 메서드 키워드 인자 | ✅ 구현, 테스트됨 — 모듈 함수와 메서드(메서드는 python-multiplatform 의 `describe_member` 필요) | -| 빈 `Modifier` | 🟡 부분 — 클래스에서 시작하는 `Modifier.padding(16)` 은 실제 Compose 에서 앱이 제공하는 팩토리가 필요 | -| `Alignment` / `Arrangement` | ✅ 구현 및 테스트 — `Alignment.Center`, `Arrangement.spaced_by(8)`, 그리고 `Alignment.End` 와 나란히 묶음 표기 `Alignment.Horizontal.End` | -| Material 3 위젯 (`Text`, `Button`, `Card`, `TextField`, …) | 🟡 부분 — 규칙으로 재노출됨; 위젯별 렌더 증거 대기(#9) | +| `snake_case` 메서드 키워드 인자 | ✅ 구현, 테스트됨: 모듈 함수와 메서드(메서드는 python-multiplatform 의 `describe_member` 필요) | +| 빈 `Modifier` | 🟡 부분: 클래스에서 시작하는 `Modifier.padding(16)` 은 실제 Compose 에서 앱이 제공하는 팩토리가 필요 | +| `Alignment` / `Arrangement` | ✅ 구현 및 테스트: `Alignment.Center`, `Arrangement.spaced_by(8)`, 그리고 `Alignment.End` 와 나란히 묶음 표기 `Alignment.Horizontal.End` | +| Material 3 위젯 (`Text`, `Button`, `Card`, `TextField`, …) | 🟡 부분: 규칙으로 재노출됨; 위젯별 렌더 증거 대기(#9) | | 실제 Compose 1.11.1 에서 생성한 타입 스텁(`.pyi`), `py.typed` | ✅ 구현, mypy 로 검사; 아직 많은 타입이 `Any` 입니다(#12) | -| 배포 (`pythonx-compose`) | 🟡 부분 — wheel 에 매니페스트, 재노출 규칙, 스텁, `py.typed` 포함 | -| 갱신 호출 없는 선언형 앱 루트(`@app`)와 파이썬다운 상태(`state`) | 🟡 부분 — 바인더 경로는 python-multiplatform #38 모양의 가짜 호스트로 테스트됨, 실제 Compose 는 E2E 모듈(#11, #19), 숫자와 문자열은 `state` 로 왕복됨 | -| `TextField(state=...)` 와 `TextFieldState` (`pythonx.compose.foundation.text.input`) | 🟡 부분 — python-multiplatform #73 모양의 가짜 호스트로 테스트됨; 입력기(IME) 조합 증거는 python-multiplatform E2E #26 (#10) | -| `DefaultIcons`(`Icons.Default`), `DefaultIcons.Add` 로 씀 | 🟡 부분 — python-multiplatform #37/#38 모양의 가짜 호스트로 테스트됨; `Icon(DefaultIcons.Add, …)` 의 실제 그리기는 python-multiplatform 의 렌더 테스트에 있음 | +| 배포 (`pythonx-compose`) | 🟡 부분: wheel 에 매니페스트, 재노출 규칙, 스텁, `py.typed` 포함 | +| 갱신 호출 없는 선언형 앱 루트(`@app`)와 파이썬다운 상태(`state`) | 🟡 부분: 바인더 경로는 python-multiplatform #38 모양의 가짜 호스트로 테스트됨, 실제 Compose 는 E2E 모듈(#11, #19), 숫자와 문자열은 `state` 로 왕복됨 | +| `TextField(state=...)` 와 `TextFieldState` (`pythonx.compose.foundation.text.input`) | 🟡 부분: python-multiplatform #73 모양의 가짜 호스트로 테스트됨; 입력기(IME) 조합 증거는 python-multiplatform E2E #26 (#10) | +| `DefaultIcons`(`Icons.Default`), `DefaultIcons.Add` 로 씀 | 🟡 부분: python-multiplatform #37/#38 모양의 가짜 호스트로 테스트됨; `Icon(DefaultIcons.Add, …)` 의 실제 그리기는 python-multiplatform 의 렌더 테스트에 있음 | | 색 스킴 | ⏳ 계획 | | `remember_saveable`, 코루틴 스코프 | ⏳ 계획 | @@ -168,15 +175,15 @@ pip 패키지 **`pythonx-compose`**(파이썬 3.11 이상)로 배포되며, impo ## 📖 문서 -- **가이드** — [`docs/guide/`](https://thisisthepy.github.io/pythonx-compose/), 영어 / 한국어 -- **English README** — [`README.md`](https://github.com/thisisthepy/pythonx-compose/blob/main/README.md) +- **가이드**: [`docs/guide/`](https://thisisthepy.github.io/pythonx-compose/), 영어 / 한국어 +- **English README**: [`README.md`](https://github.com/thisisthepy/pythonx-compose/blob/main/README.md) ## 🔌 생태계 | 저장소 | 역할 | |---|---| | [python-multiplatform](https://github.com/thisisthepy/python-multiplatform) | 바인더: Kotlin Multiplatform 에 임베딩한 CPython, Kotlin 을 Kotlin 이름 그대로 파이썬에 노출 | -| **pythonx-compose** | 파이썬을 위해 재구성한 Compose — 이 저장소 | +| **pythonx-compose** | 파이썬을 위해 재구성한 Compose (이 저장소) | | [toolchain](https://github.com/thisisthepy/toolchain) | Python Multiplatform 앱을 위한 Gradle 빌드 플러그인 | | [pypackpack](https://github.com/thisisthepy/pypackpack) | 파이썬 프로젝트의 멀티플랫폼 배포 | | [torchnative](https://github.com/thisisthepy/torchnative) | 실제 PyTorch 생태계를 기기에서 실행 | diff --git a/docs/style/writing.md b/docs/style/writing.md new file mode 100644 index 0000000..b6c0093 --- /dev/null +++ b/docs/style/writing.md @@ -0,0 +1,51 @@ +# Writing style + +The house style for thisisthepy documentation. The maintainer named this repository's guide +(`docs/guide/`) as the reference for every repository, so other projects align their READMEs, guides +and docs with what is written here. When this file and the guide disagree, the guide's practice wins +and this file gets corrected. + +## Voice + +- **Say what is true now.** Describe what the code does today, in the present tense. Something not + built is marked as such, with its status (`implemented`, `partial`, `planned`) and the issue that + tracks it. Never describe a plan as if it shipped. +- **Plain and concrete.** No marketing words and no hedging. Name the actual thing: a module, a + function, a test, an issue number, a commit. +- **Short declarative sentences.** One idea per sentence, about 12 to 18 words on average in English. + A long sentence is split, not joined with a dash. +- **Reasons beside rules.** When a rule or a limitation is stated, the next clause or sentence gives + the reason ("because ...", "so that ..."). A reader should never have to guess why. +- **Examples over abstraction.** Show the spelling a user writes (`Modifier.padding(16).fill_max_width()`) + rather than describing it. + +## English and Korean + +Every user-facing page exists in both languages (README.md and docs/locale/README_ko.md; each +visible string of the guide in `data-lang="en"` and `data-lang="ko"`). + +- **Same content, same order, not word for word.** Each English paragraph has one Korean paragraph + saying the same thing in natural Korean. A sentence may be split or merged differently, but no + fact is added or dropped in one language only. +- **Korean register.** User-facing text (README_ko, the guide) uses the polite declarative, + 합니다체: "설치합니다", "동작합니다". Internal records (PROJECT.md, which is written in Korean) + use the plain declarative, 한다체: "한다", "두었다". +- **Terms.** Code identifiers, Kotlin names, commands and file names stay in their original spelling + inside code formatting, in both languages. A particle follows code with a space: "`UI.ipynb` 를", + "`state()` 로". Common technical words are written as Korean loanwords when that is how Korean + developers say them ("바인더", "스텁", "컴포저블", "모듈"); otherwise the English term stays + ("snake_case", "wheel", "proxy"). +- **Status words** pair one to one: implemented / 구현, partial / 부분, planned / 계획. +- **Headings** are short noun phrases in both languages ("The naming rule" / "이름 규칙"). + +## Mechanics + +- **No em-dash (U+2014)**, anywhere in documents or code. Split the sentence, or use a comma, a colon + or parentheses. The en-dash in ranges (2023–2024) is fine. +- **Install and run examples use uv, ppp (pypackpack) and tcl (toolchain-lite) only.** No `pip` + examples. Show uv first: `uv add --prerelease allow pythonx-compose`, then + `ppp core add "pythonx-compose==0.1.0a1"` and `tcl install pythonx-compose`; run with `uv run ...`. +- **Numbers carry their source.** A test count names the environment it was measured in; a claim + about upstream names the commit, PR or issue. +- **Links** in README files are absolute (they are read on pypi.org too), and README files never + link the internal documents (AGENTS.md, PROJECT.md, docs/INTENT.md, docs/SPEC.md).