kohakutui 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. kohakutui-0.1.0/.github/workflows/ci.yml +75 -0
  2. kohakutui-0.1.0/.github/workflows/nightly.yml +60 -0
  3. kohakutui-0.1.0/.github/workflows/release.yml +220 -0
  4. kohakutui-0.1.0/.gitignore +40 -0
  5. kohakutui-0.1.0/CLAUDE.md +106 -0
  6. kohakutui-0.1.0/Cargo.lock +1275 -0
  7. kohakutui-0.1.0/Cargo.toml +48 -0
  8. kohakutui-0.1.0/LICENSE +201 -0
  9. kohakutui-0.1.0/PKG-INFO +228 -0
  10. kohakutui-0.1.0/README.md +193 -0
  11. kohakutui-0.1.0/docs/README.md +21 -0
  12. kohakutui-0.1.0/docs/components.md +139 -0
  13. kohakutui-0.1.0/docs/concepts.md +117 -0
  14. kohakutui-0.1.0/docs/dialogs.md +111 -0
  15. kohakutui-0.1.0/docs/getting-started.md +137 -0
  16. kohakutui-0.1.0/docs/images/agent.png +0 -0
  17. kohakutui-0.1.0/docs/images/approval.png +0 -0
  18. kohakutui-0.1.0/docs/images/dashboard.png +0 -0
  19. kohakutui-0.1.0/docs/images/picker.png +0 -0
  20. kohakutui-0.1.0/docs/images/train.png +0 -0
  21. kohakutui-0.1.0/docs/releasing.md +78 -0
  22. kohakutui-0.1.0/docs/testing.md +90 -0
  23. kohakutui-0.1.0/docs/themes.md +47 -0
  24. kohakutui-0.1.0/pyproject.toml +65 -0
  25. kohakutui-0.1.0/rustfmt.toml +10 -0
  26. kohakutui-0.1.0/scripts/release_version.py +68 -0
  27. kohakutui-0.1.0/scripts/screenshots.py +361 -0
  28. kohakutui-0.1.0/src/kohakutui/README.md +27 -0
  29. kohakutui-0.1.0/src/kohakutui/__init__.py +67 -0
  30. kohakutui-0.1.0/src/kohakutui/__main__.py +40 -0
  31. kohakutui-0.1.0/src/kohakutui/_rs.pyi +37 -0
  32. kohakutui-0.1.0/src/kohakutui/app/README.md +26 -0
  33. kohakutui-0.1.0/src/kohakutui/app/__init__.py +6 -0
  34. kohakutui-0.1.0/src/kohakutui/app/app.py +69 -0
  35. kohakutui-0.1.0/src/kohakutui/app/capture.py +138 -0
  36. kohakutui-0.1.0/src/kohakutui/app/control.py +145 -0
  37. kohakutui-0.1.0/src/kohakutui/app/core.py +167 -0
  38. kohakutui-0.1.0/src/kohakutui/app/dialogs.py +165 -0
  39. kohakutui-0.1.0/src/kohakutui/app/events.py +216 -0
  40. kohakutui-0.1.0/src/kohakutui/demo/README.md +14 -0
  41. kohakutui-0.1.0/src/kohakutui/demo/__init__.py +1 -0
  42. kohakutui-0.1.0/src/kohakutui/demo/agent.py +308 -0
  43. kohakutui-0.1.0/src/kohakutui/demo/chat.py +90 -0
  44. kohakutui-0.1.0/src/kohakutui/demo/dashboard.py +162 -0
  45. kohakutui-0.1.0/src/kohakutui/demo/parts.py +118 -0
  46. kohakutui-0.1.0/src/kohakutui/demo/tasks.py +193 -0
  47. kohakutui-0.1.0/src/kohakutui/demo/train.py +101 -0
  48. kohakutui-0.1.0/src/kohakutui/py.typed +0 -0
  49. kohakutui-0.1.0/src/kohakutui/ui/README.md +16 -0
  50. kohakutui-0.1.0/src/kohakutui/ui/__init__.py +55 -0
  51. kohakutui-0.1.0/src/kohakutui/ui/components.py +339 -0
  52. kohakutui-0.1.0/src/kohakutui/ui/node.py +77 -0
  53. kohakutui-0.1.0/src/kohakutui/ui/widget.py +39 -0
  54. kohakutui-0.1.0/src/kohakutui-rust/README.md +33 -0
  55. kohakutui-0.1.0/src/kohakutui-rust/lib.rs +45 -0
  56. kohakutui-0.1.0/src/kohakutui-rust/model/apply.rs +145 -0
  57. kohakutui-0.1.0/src/kohakutui-rust/model/mod.rs +83 -0
  58. kohakutui-0.1.0/src/kohakutui-rust/model/tests.rs +120 -0
  59. kohakutui-0.1.0/src/kohakutui-rust/model/tree.rs +207 -0
  60. kohakutui-0.1.0/src/kohakutui-rust/proto/decode.rs +133 -0
  61. kohakutui-0.1.0/src/kohakutui-rust/proto/encode.rs +158 -0
  62. kohakutui-0.1.0/src/kohakutui-rust/proto/mod.rs +159 -0
  63. kohakutui-0.1.0/src/kohakutui-rust/proto/python.rs +103 -0
  64. kohakutui-0.1.0/src/kohakutui-rust/proto/tests.rs +117 -0
  65. kohakutui-0.1.0/src/kohakutui-rust/proto/value.rs +93 -0
  66. kohakutui-0.1.0/src/kohakutui-rust/runtime/engine.rs +515 -0
  67. kohakutui-0.1.0/src/kohakutui-rust/runtime/input.rs +64 -0
  68. kohakutui-0.1.0/src/kohakutui-rust/runtime/mod.rs +105 -0
  69. kohakutui-0.1.0/src/kohakutui-rust/runtime/python.rs +277 -0
  70. kohakutui-0.1.0/src/kohakutui-rust/runtime/session.rs +148 -0
  71. kohakutui-0.1.0/src/kohakutui-rust/runtime/tests.rs +243 -0
  72. kohakutui-0.1.0/src/kohakutui-rust/term/guard.rs +64 -0
  73. kohakutui-0.1.0/src/kohakutui-rust/term/history.rs +117 -0
  74. kohakutui-0.1.0/src/kohakutui-rust/term/mod.rs +88 -0
  75. kohakutui-0.1.0/src/kohakutui-rust/term/out.rs +261 -0
  76. kohakutui-0.1.0/src/kohakutui-rust/term/screen.rs +357 -0
  77. kohakutui-0.1.0/src/kohakutui-rust/term/tests.rs +122 -0
  78. kohakutui-0.1.0/src/kohakutui-rust/text/markdown.rs +461 -0
  79. kohakutui-0.1.0/src/kohakutui-rust/text/mod.rs +25 -0
  80. kohakutui-0.1.0/src/kohakutui-rust/text/stream.rs +107 -0
  81. kohakutui-0.1.0/src/kohakutui-rust/text/tests.rs +244 -0
  82. kohakutui-0.1.0/src/kohakutui-rust/text/theme.rs +443 -0
  83. kohakutui-0.1.0/src/kohakutui-rust/text/wrap.rs +170 -0
  84. kohakutui-0.1.0/src/kohakutui-rust/view/chord.rs +70 -0
  85. kohakutui-0.1.0/src/kohakutui-rust/view/flush.rs +111 -0
  86. kohakutui-0.1.0/src/kohakutui-rust/view/input.rs +396 -0
  87. kohakutui-0.1.0/src/kohakutui-rust/view/layout.rs +534 -0
  88. kohakutui-0.1.0/src/kohakutui-rust/view/mod.rs +148 -0
  89. kohakutui-0.1.0/src/kohakutui-rust/view/render.rs +376 -0
  90. kohakutui-0.1.0/src/kohakutui-rust/view/tests.rs +749 -0
  91. kohakutui-0.1.0/src/kohakutui-rust/widgets/button.rs +165 -0
  92. kohakutui-0.1.0/src/kohakutui-rust/widgets/cell.rs +248 -0
  93. kohakutui-0.1.0/src/kohakutui-rust/widgets/chart.rs +327 -0
  94. kohakutui-0.1.0/src/kohakutui-rust/widgets/composer.rs +335 -0
  95. kohakutui-0.1.0/src/kohakutui-rust/widgets/data.rs +248 -0
  96. kohakutui-0.1.0/src/kohakutui-rust/widgets/dialogs.rs +435 -0
  97. kohakutui-0.1.0/src/kohakutui-rust/widgets/editor.rs +202 -0
  98. kohakutui-0.1.0/src/kohakutui-rust/widgets/layout.rs +77 -0
  99. kohakutui-0.1.0/src/kohakutui-rust/widgets/markdown.rs +103 -0
  100. kohakutui-0.1.0/src/kohakutui-rust/widgets/mod.rs +183 -0
  101. kohakutui-0.1.0/src/kohakutui-rust/widgets/picker.rs +897 -0
  102. kohakutui-0.1.0/src/kohakutui-rust/widgets/props.rs +55 -0
  103. kohakutui-0.1.0/src/kohakutui-rust/widgets/registry.rs +62 -0
  104. kohakutui-0.1.0/src/kohakutui-rust/widgets/scroll.rs +188 -0
  105. kohakutui-0.1.0/src/kohakutui-rust/widgets/status.rs +310 -0
  106. kohakutui-0.1.0/src/kohakutui-rust/widgets/tests.rs +808 -0
  107. kohakutui-0.1.0/src/kohakutui-rust/widgets/text.rs +225 -0
  108. kohakutui-0.1.0/tests/README.md +44 -0
  109. kohakutui-0.1.0/tests/__init__.py +0 -0
  110. kohakutui-0.1.0/tests/conftest.py +32 -0
  111. kohakutui-0.1.0/tests/e2e/__init__.py +0 -0
  112. kohakutui-0.1.0/tests/e2e/test_chat_journey.py +107 -0
  113. kohakutui-0.1.0/tests/e2e/test_demos.py +199 -0
  114. kohakutui-0.1.0/tests/e2e/test_readme.py +45 -0
  115. kohakutui-0.1.0/tests/integration/__init__.py +0 -0
  116. kohakutui-0.1.0/tests/integration/test_app.py +364 -0
  117. kohakutui-0.1.0/tests/static/__init__.py +0 -0
  118. kohakutui-0.1.0/tests/static/test_file_sizes.py +54 -0
  119. kohakutui-0.1.0/tests/static/test_layout.py +72 -0
  120. kohakutui-0.1.0/tests/static/test_stub.py +38 -0
  121. kohakutui-0.1.0/tests/unit/__init__.py +0 -0
  122. kohakutui-0.1.0/tests/unit/app/__init__.py +0 -0
  123. kohakutui-0.1.0/tests/unit/app/test_events.py +29 -0
  124. kohakutui-0.1.0/tests/unit/test_init.py +32 -0
  125. kohakutui-0.1.0/tests/unit/test_release_version.py +47 -0
  126. kohakutui-0.1.0/tests/unit/ui/__init__.py +0 -0
  127. kohakutui-0.1.0/tests/unit/ui/test_components.py +33 -0
@@ -0,0 +1,75 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ workflow_dispatch:
9
+
10
+ concurrency:
11
+ group: ci-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ lint:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.13"
22
+ - run: pip install ruff black
23
+ - run: ruff check src/ tests/ scripts/
24
+ - run: black --check src/ tests/ scripts/
25
+
26
+ rust:
27
+ name: Rust (fmt, clippy, stage tests)
28
+ runs-on: ubuntu-latest
29
+ steps:
30
+ - uses: actions/checkout@v4
31
+ - uses: actions/setup-python@v5
32
+ with:
33
+ python-version: "3.13"
34
+ - uses: dtolnay/rust-toolchain@stable
35
+ with:
36
+ components: rustfmt, clippy
37
+ - uses: Swatinem/rust-cache@v2
38
+ - run: cargo fmt --check
39
+ - run: cargo clippy --all-targets -- -D warnings
40
+ - run: cargo clippy --all-targets --features python -- -D warnings
41
+ # Without the python feature the test binary links nothing of Python
42
+ - run: cargo test --no-default-features
43
+ - name: One crossterm in the dependency graph
44
+ run: |
45
+ n=$(cargo tree -e normal -i crossterm --depth 0 2>/dev/null | grep -c '^crossterm' || true)
46
+ test "$n" -le 1 || { cargo tree -d; exit 1; }
47
+
48
+ test:
49
+ runs-on: ${{ matrix.os }}
50
+ strategy:
51
+ fail-fast: false
52
+ matrix:
53
+ os: [ubuntu-latest, windows-latest, macos-latest]
54
+ python-version: ["3.10", "3.14"]
55
+ steps:
56
+ - uses: actions/checkout@v4
57
+ - uses: actions/setup-python@v5
58
+ with:
59
+ python-version: ${{ matrix.python-version }}
60
+ - uses: dtolnay/rust-toolchain@stable
61
+ - uses: Swatinem/rust-cache@v2
62
+ - uses: astral-sh/setup-uv@v6
63
+ - name: Build the wheel
64
+ shell: bash
65
+ run: |
66
+ uv venv
67
+ uv pip install maturin
68
+ uv run --no-project maturin build --release --out dist
69
+ - name: Install the built wheel
70
+ shell: bash
71
+ run: |
72
+ WHEEL=$(ls dist/*.whl)
73
+ uv pip install "${WHEEL}[dev]"
74
+ - name: Tests
75
+ run: uv run --no-project pytest tests/ -q --tb=short
@@ -0,0 +1,60 @@
1
+ name: Nightly
2
+
3
+ on:
4
+ schedule:
5
+ - cron: '0 3 * * *'
6
+ workflow_dispatch:
7
+ inputs:
8
+ force_rebuild:
9
+ description: Build even if this commit already has nightly artifacts
10
+ type: boolean
11
+ default: false
12
+
13
+ # Like KohakuVault and KohakuTerrarium: nightly only decides; release.yml owns every build and
14
+ # upload, so there is one matrix and one PyPI trusted publisher.
15
+ concurrency:
16
+ group: nightly-dispatch
17
+ cancel-in-progress: false
18
+
19
+ permissions:
20
+ contents: read
21
+ actions: write
22
+
23
+ jobs:
24
+ dispatch:
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ with:
29
+ ref: main
30
+ - name: Require green CI, skip a commit already published
31
+ id: gate
32
+ env:
33
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34
+ FORCE: ${{ inputs.force_rebuild || false }}
35
+ shell: bash
36
+ run: |
37
+ set -euo pipefail
38
+ sha=$(git rev-parse HEAD)
39
+ result=$(gh run list --workflow ci.yml --commit "$sha" --event push --limit 1 --json conclusion --jq '.[0].conclusion // "missing"')
40
+ if [ "$result" != success ]; then
41
+ echo "::error::CI for $sha is $result; nightly not dispatched"
42
+ exit 1
43
+ fi
44
+ previous=$(gh release list --limit 100 --json tagName --jq '[.[] | select(.tagName | startswith("nightly-"))][0].tagName // ""')
45
+ if [ -n "$previous" ] && [ "$FORCE" != true ]; then
46
+ body=$(gh release view "$previous" --json body --jq .body)
47
+ if printf '%s' "$body" | grep -Fq "Source commit: $sha"; then
48
+ echo "Commit $sha already published" >> "$GITHUB_STEP_SUMMARY"
49
+ exit 0
50
+ fi
51
+ fi
52
+ echo "sha=$sha" >> "$GITHUB_OUTPUT"
53
+ - name: Dispatch the release builder (nightly channel)
54
+ if: steps.gate.outputs.sha != ''
55
+ env:
56
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
57
+ SOURCE_SHA: ${{ steps.gate.outputs.sha }}
58
+ run: |
59
+ gh workflow run release.yml --ref main -f ref="$SOURCE_SHA" -f channel=nightly
60
+ echo "Dispatched nightly for $SOURCE_SHA." >> "$GITHUB_STEP_SUMMARY"
@@ -0,0 +1,220 @@
1
+ name: Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ push:
7
+ tags:
8
+ - 'v*'
9
+ workflow_dispatch:
10
+ inputs:
11
+ ref:
12
+ description: Exact commit or ref to build
13
+ default: main
14
+ type: string
15
+ channel:
16
+ description: Build only, or publish a nightly (never a stable manual release)
17
+ default: build
18
+ type: choice
19
+ options: [build, nightly]
20
+
21
+ concurrency:
22
+ group: release-${{ inputs.channel || github.ref }}
23
+ cancel-in-progress: false
24
+
25
+ permissions:
26
+ contents: read
27
+ actions: read
28
+
29
+ defaults:
30
+ run:
31
+ shell: bash
32
+
33
+ # The only workflow that uploads kohakutui, so PyPI needs one trusted publisher: this file
34
+ # (release.yml), environment `pypi`. Nightly runs come here too, dispatched by nightly.yml.
35
+ jobs:
36
+ meta:
37
+ name: Resolve the version
38
+ if: github.event_name != 'release' || startsWith(github.event.release.tag_name, 'v')
39
+ runs-on: ubuntu-latest
40
+ outputs:
41
+ sha: ${{ steps.source.outputs.sha }}
42
+ cargo_version: ${{ steps.version.outputs.cargo_version }}
43
+ python_version: ${{ steps.version.outputs.python_version }}
44
+ tag: ${{ steps.version.outputs.tag }}
45
+ nightly: ${{ steps.version.outputs.nightly }}
46
+ steps:
47
+ - uses: actions/checkout@v4
48
+ with:
49
+ ref: ${{ inputs.ref || github.ref }}
50
+ - uses: actions/setup-python@v5
51
+ with:
52
+ python-version: '3.13'
53
+ - name: Resolve the source; a nightly needs green CI on it
54
+ id: source
55
+ env:
56
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
57
+ CHANNEL: ${{ inputs.channel }}
58
+ run: |
59
+ sha=$(git rev-parse HEAD)
60
+ echo "sha=$sha" >> "$GITHUB_OUTPUT"
61
+ if [ "$CHANNEL" = nightly ]; then
62
+ result=$(gh run list --workflow ci.yml --commit "$sha" --event push --limit 1 --json conclusion --jq '.[0].conclusion // "missing"')
63
+ test "$result" = success || { echo "::error::CI for $sha is $result"; exit 1; }
64
+ fi
65
+ - name: One version for every platform
66
+ id: version
67
+ env:
68
+ CHANNEL: ${{ inputs.channel }}
69
+ run: |
70
+ args=()
71
+ if [ "$CHANNEL" = nightly ]; then args+=(--nightly); fi
72
+ python scripts/release_version.py "${args[@]}" --github-output "$GITHUB_OUTPUT"
73
+ # A tag that disagrees with Cargo.toml is a mis-cut release: stop before anything is built
74
+ - name: The tag matches Cargo.toml
75
+ if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'release'
76
+ env:
77
+ TAG: ${{ github.event.release.tag_name || github.ref_name }}
78
+ EXPECTED: ${{ steps.version.outputs.tag }}
79
+ run: |
80
+ test "$TAG" = "$EXPECTED" || { echo "::error::tag $TAG != Cargo.toml $EXPECTED"; exit 1; }
81
+
82
+ # abi3: one wheel per platform serves every CPython from 3.10 on
83
+ build-wheels:
84
+ name: Wheel for ${{ matrix.target }}
85
+ needs: meta
86
+ runs-on: ${{ matrix.os }}
87
+ strategy:
88
+ fail-fast: false
89
+ matrix:
90
+ include:
91
+ - { os: ubuntu-latest, target: x86_64-unknown-linux-gnu, manylinux: '2_28', native: true }
92
+ - { os: ubuntu-24.04-arm, target: aarch64-unknown-linux-gnu, manylinux: '2_28', native: true }
93
+ - { os: windows-latest, target: x86_64-pc-windows-msvc, manylinux: '', native: true }
94
+ - { os: macos-latest, target: aarch64-apple-darwin, manylinux: '', native: true }
95
+ # Intel macs: cross-built on the arm runner (GitHub retired its Intel macOS images)
96
+ - { os: macos-latest, target: x86_64-apple-darwin, manylinux: '', native: false }
97
+ steps:
98
+ - uses: actions/checkout@v4
99
+ with:
100
+ ref: ${{ needs.meta.outputs.sha }}
101
+ - uses: actions/setup-python@v5
102
+ with:
103
+ python-version: '3.13'
104
+ - name: Stamp the version
105
+ env:
106
+ ARTIFACT_VERSION: ${{ needs.meta.outputs.cargo_version }}
107
+ run: python scripts/release_version.py --stamp "$ARTIFACT_VERSION"
108
+ - uses: PyO3/maturin-action@v1
109
+ with:
110
+ target: ${{ matrix.target }}
111
+ manylinux: ${{ matrix.manylinux || 'auto' }}
112
+ args: --release --out dist
113
+ - name: Smoke-test the wheel in a clean venv
114
+ if: matrix.native
115
+ env:
116
+ EXPECTED_VERSION: ${{ needs.meta.outputs.python_version }}
117
+ run: |
118
+ python -m venv .venv-wheel
119
+ if [ "${RUNNER_OS}" = "Windows" ]; then . .venv-wheel/Scripts/activate; else . .venv-wheel/bin/activate; fi
120
+ pip install dist/*.whl
121
+ # away from src/, so the import is the installed wheel
122
+ cd dist
123
+ python -c "import importlib.metadata as m, os; assert m.version('kohakutui') == os.environ['EXPECTED_VERSION']"
124
+ python -c "import kohakutui; print(kohakutui.version(), kohakutui.PROTOCOL)"
125
+ # a whole demo turn on the emulated terminal: the engine, not just the import
126
+ python -c "
127
+ from kohakutui import App
128
+ from kohakutui.demo.agent import agent
129
+ app = App(backend='test', size=(90, 30), interrupt='event')
130
+ agent(speed=0.0, interactive=False, app=app)
131
+ assert any(l.startswith('● Done.') for l in app.history()), app.history()
132
+ print('demo ok')
133
+ "
134
+ - uses: actions/upload-artifact@v4
135
+ with:
136
+ name: wheels-${{ matrix.target }}
137
+ path: dist/*.whl
138
+ if-no-files-found: error
139
+ retention-days: 7
140
+
141
+ build-sdist:
142
+ name: Build the sdist
143
+ needs: meta
144
+ runs-on: ubuntu-latest
145
+ steps:
146
+ - uses: actions/checkout@v4
147
+ with:
148
+ ref: ${{ needs.meta.outputs.sha }}
149
+ - uses: actions/setup-python@v5
150
+ with:
151
+ python-version: '3.13'
152
+ - name: Stamp the version
153
+ env:
154
+ ARTIFACT_VERSION: ${{ needs.meta.outputs.cargo_version }}
155
+ run: python scripts/release_version.py --stamp "$ARTIFACT_VERSION"
156
+ - name: Build
157
+ run: |
158
+ pip install maturin twine
159
+ maturin sdist --out dist
160
+ twine check dist/*
161
+ - uses: actions/upload-artifact@v4
162
+ with:
163
+ name: sdist
164
+ path: dist/*.tar.gz
165
+ if-no-files-found: error
166
+ retention-days: 7
167
+
168
+ publish-to-pypi:
169
+ name: Publish to PyPI
170
+ needs: [meta, build-wheels, build-sdist]
171
+ runs-on: ubuntu-latest
172
+ if: needs.meta.outputs.nightly == 'true' || startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'release' && startsWith(github.event.release.tag_name, 'v'))
173
+ environment:
174
+ name: pypi
175
+ url: https://pypi.org/p/kohakutui
176
+ permissions:
177
+ contents: write
178
+ id-token: write
179
+ steps:
180
+ - uses: actions/download-artifact@v4
181
+ with:
182
+ path: dist
183
+ merge-multiple: true
184
+ - name: List artifacts
185
+ run: ls -lh dist/
186
+
187
+ - name: Nightly notes and checksums
188
+ if: needs.meta.outputs.nightly == 'true'
189
+ env:
190
+ SOURCE_SHA: ${{ needs.meta.outputs.sha }}
191
+ ARTIFACT_VERSION: ${{ needs.meta.outputs.python_version }}
192
+ run: |
193
+ printf 'Automatic nightly build (not a stable release).\n\nVersion: %s\n\nSource commit: %s\n' "$ARTIFACT_VERSION" "$SOURCE_SHA" > nightly-notes.md
194
+ sha256sum dist/* > dist/SHA256SUMS.txt
195
+ - name: Nightly as a dated prerelease
196
+ if: needs.meta.outputs.nightly == 'true'
197
+ uses: softprops/action-gh-release@v2
198
+ with:
199
+ tag_name: ${{ needs.meta.outputs.tag }}
200
+ target_commitish: ${{ needs.meta.outputs.sha }}
201
+ name: Nightly ${{ needs.meta.outputs.python_version }}
202
+ prerelease: true
203
+ make_latest: false
204
+ body_path: nightly-notes.md
205
+ files: dist/*
206
+ - name: Keep the checksum file off PyPI
207
+ if: needs.meta.outputs.nightly == 'true'
208
+ run: rm dist/SHA256SUMS.txt
209
+
210
+ - name: Upload to the GitHub release
211
+ if: startsWith(github.ref, 'refs/tags/')
212
+ uses: softprops/action-gh-release@v2
213
+ with:
214
+ files: dist/*
215
+ fail_on_unmatched_files: false
216
+ - name: Publish to PyPI
217
+ uses: pypa/gh-action-pypi-publish@release/v1
218
+ with:
219
+ skip-existing: true
220
+ verbose: true
@@ -0,0 +1,40 @@
1
+ # Local working notes
2
+ /plans/
3
+ .claude/
4
+ temp/
5
+
6
+ # Rust
7
+ /target/
8
+ *.pdb
9
+
10
+ # Built extension (maturin develop / editable installs)
11
+ *.so
12
+ *.pyd
13
+ *.dylib
14
+
15
+ # Python
16
+ __pycache__/
17
+ *.py[codz]
18
+ *$py.class
19
+ build/
20
+ dist/
21
+ wheels/
22
+ *.egg-info/
23
+ .eggs/
24
+ .venv/
25
+ .venv-*/
26
+ venv/
27
+
28
+ # Tests and tooling
29
+ .pytest_cache/
30
+ .ruff_cache/
31
+ .coverage
32
+ .coverage.*
33
+ htmlcov/
34
+ coverage.xml
35
+ *.snap.new
36
+ *.pending-snap
37
+
38
+ # Editors
39
+ .vscode/
40
+ .idea/
@@ -0,0 +1,106 @@
1
+ # KohakuTUI
2
+
3
+ A terminal UI engine in Rust, driven from Python through a retained node-tree protocol. Python
4
+ describes what is on screen; Rust owns the terminal, the frame loop and every keystroke.
5
+
6
+ The architecture is `plans/design.md` (local, gitignored, like all of `plans/`). Section references
7
+ such as "design §5" in code and READMEs point into it. Read §1–§5 before changing anything in
8
+ `proto/` or `model/`.
9
+
10
+ ## Project layout
11
+
12
+ ```text
13
+ src/kohakutui-rust/ engine crate; lib.rs holds the `_rs` pymodule (feature `python`)
14
+ src/kohakutui/ Python SDK over kohakutui._rs
15
+ tests/ static/, e2e/, integration/, unit/ (Python); Rust tests in <module>/tests.rs
16
+ docs/ public docs (protocol, sdk), added as they are written
17
+ plans/ local design and working plans (gitignored)
18
+ ```
19
+
20
+ ## Development setup
21
+
22
+ - `uv venv`, then `uv pip install -e ".[dev]"`. maturin builds the extension into the venv;
23
+ rerun after any Rust change.
24
+ - **Never use `sys.path.insert` hacks** in examples or tests; import `kohakutui.*` from the
25
+ installed package.
26
+
27
+ ## Rust rules
28
+
29
+ 1. **Every module is a directory whose only entry point is `mod.rs`**: submodule declarations,
30
+ the module's public types and re-exports. Logic goes in sibling files. Never `name.rs` beside
31
+ `name/` (`tests/static/test_layout.py` enforces this).
32
+ 2. Unit tests live in `<module>/tests.rs` (`#[cfg(test)] mod tests;` in `mod.rs`).
33
+ 3. pyo3 code lives in `<module>/python.rs` behind `#[cfg(feature = "python")]`, exposes
34
+ `register(m)`, and is registered from `lib.rs`. Pure Rust never imports pyo3, so
35
+ `cargo test --no-default-features` links no Python.
36
+ 4. pyclasses are underscore-prefixed (`_Session`) and wrapped by a public Python class.
37
+ 5. Errors: one `thiserror` enum per module, with `From<Error> for PyErr` in its `python.rs`.
38
+ The engine never panics on input from Python or the wire; a bad op becomes `Event::Error`.
39
+ 6. `//!` docs at the top of every file say what the module is for. `pub(crate)` unless the
40
+ item is part of the crate API.
41
+ 7. `cargo fmt` (max width 100) and `cargo clippy --all-targets -- -D warnings`, with and without
42
+ `--features python`.
43
+ 8. Code adapted from Codex (Apache-2.0) or ratatui (MIT) keeps its license header.
44
+
45
+ ## Python rules
46
+
47
+ ### Style
48
+ - Target Python 3.10+ (abi3 wheel). Modern hints: `list`, `dict`, `X | None`.
49
+ - Prefer `match`/`case` over deep `if`/`elif`.
50
+ - No `print()` in library code. CLI entry points (`__main__.py`) may print.
51
+ - black (default width) and `ruff check` over `src/` and `tests/`.
52
+
53
+ ### Imports
54
+ 1. No imports inside functions (except an optional dependency, such as `tqdm`).
55
+ `tests/static/test_layout.py` enforces this.
56
+ 2. Groups: built-in, third-party, `kohakutui`.
57
+ 3. Within a group: `import` before `from`, shorter dotted paths first, then a–z.
58
+
59
+ ### Files
60
+ - One responsibility per module. Every package folder has a `README.md` with a file table.
61
+ - File size is capped by AST statements (imports and docstrings excluded): over 500 warns,
62
+ over 1000 fails (`tests/static/test_file_sizes.py`).
63
+
64
+ ## Comments and docstrings
65
+
66
+ Comment, code and docstring all say **what**. *How* and *why* live in `docs/` and `plans/`.
67
+
68
+ 1. No memo, note or editorial comments.
69
+ 2. A comment says what the code itself cannot say clearly.
70
+ 3. A docstring says what the class or function is *for*, so a reader can place it.
71
+ 4. Soft limits: inline comment blocks ≤ 2 lines, docstrings ≤ 14 lines.
72
+ 5. Doc comments (`//!`, `///`, docstrings) say what a file, type or function is for. An inline
73
+ comment (1–2 lines) heads a logic block only when the code cannot say what it does (a tricky
74
+ API, a non-obvious algorithm step).
75
+ 6. Separate logic blocks inside a function with one blank line.
76
+ 7. Formatting is the formatter's: rustfmt for Rust, black for Python (black already wraps long
77
+ expressions in parentheses).
78
+
79
+ ## Tests
80
+
81
+ The engine is a pipeline of stages (proto → model → text → widgets/view → term, then the SDK on
82
+ top). **A test proves one stage on the level below it and nothing further.** `tests/README.md`
83
+ has the stage table.
84
+
85
+ - Write tiers **e2e first, then integration, then unit**: the journey fixes what must work, the
86
+ lower tiers pin the parts.
87
+ - `static/`: layout and contract guards (module layout, imports, file sizes, the `_rs.pyi` stub).
88
+ - Behavior asserts, not shape asserts; real collaborators, not mocks. The only seam is the
89
+ terminal: `_Session(backend="test")`.
90
+ - A check that cannot fail on a broken stage is not a check.
91
+
92
+ ## Post-implementation
93
+
94
+ 1. Check the rules above, especially in-function imports and the `mod.rs` layout.
95
+ 2. `black src/ tests/`, `ruff check src/ tests/`, `cargo fmt`, both clippy runs.
96
+ 3. `pytest tests/ -q` and `cargo test --no-default-features`.
97
+ 4. New behaviour has tests at the right stage and tier.
98
+ 5. Logically separate commits (conventional style: `feat(model): ...`). **Do not commit unless
99
+ asked.**
100
+
101
+ ## Audit loop (multi-step work: required)
102
+
103
+ Implement → write tests that pin the behaviour (negative cases first) → run every suite and lint
104
+ → audit the diff for clear bugs, integrity bugs (broken invariants, races, stale caches) and
105
+ behaviour bugs (wrong default, swallowed error) → if the audit finds a bug the tests missed,
106
+ first make a test fail on it, then fix → repeat until the audit is clean and everything is green.