aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes/workbench/README.md
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-24 17:14:54 +0100
committereunhhu <[email protected]>2026-09-24 17:14:54 +0100
commitb1ce1927022d374198441afafeb27199fc8a052a (patch)
tree6021f175f4c67ed03b44240eaa2f90d846f12f6f /prototypes/workbench/README.md
parentd34f9426d5317a1948f12f229439946728bc342a (diff)
downloadpure-b1ce1927022d374198441afafeb27199fc8a052a.tar.gz
pure-b1ce1927022d374198441afafeb27199fc8a052a.zip
Establish native micro runtime checkpoint
Diffstat (limited to 'prototypes/workbench/README.md')
-rw-r--r--prototypes/workbench/README.md354
1 files changed, 354 insertions, 0 deletions
diff --git a/prototypes/workbench/README.md b/prototypes/workbench/README.md
new file mode 100644
index 0000000..e55f6a2
--- /dev/null
+++ b/prototypes/workbench/README.md
@@ -0,0 +1,354 @@
+# 빈 작업물에서 시작하는 Pure
+
+자신이 만들고 싶은 작은 동작을 작성하는 실험이다. 처음에는 코드·장면·입력이
+모두 비어 있다. 상태, 입력, 도형과 동작을 추가하는 데 실행기 소스를 고칠 필요가 없다.
+
+이 디렉터리는 브라우저·CLI 검증 호스트, 선택 가능한 네이티브 계산 모듈과 Linux GTK 작성 창이다.
+Pure의 최종 목표는 앱 로직·네트워크·RPC·DB·UI를 다루는 독립 네이티브 애플리케이션 런타임이다.
+UI는 그중 하나의 하위 체계이며, 현재 호스트는 전체 런타임이나 일반 네이티브 UI 스택이 아니다.
+네이티브 코어 검사와 Wasm 크기도 네이티브 UI 완성이나 전체 메모리 효율의 근거가 아니다.
+현재 개발 방향은 [로드맵](../../docs/research/010-roadmap.md)을 따른다.
+
+CLI에는 기존 작업물을 실제 네이티브 서버·바이트 저장소에 저장하고 새 실행기에서 복원하는
+`connect`·`push`·`pull`이 추가됐다. [서버 실행과 사용법](../connections/README.md)을 따른다.
+브라우저 저장 버튼은 이 서버에 자동 연결되지 않는다.
+
+같은 `.pure`를 **Node·브라우저 없이 실행하는 네이티브 프로그램**으로 빌드하는 경로도 있다.
+빌드 시 기존 컴파일러를 재사용하며, 입력·상태 스냅샷·저장·복원을 C 실행 코어에 연결했다.
+실행 전용 터미널 프로그램, 선택형 GTK 작성 창, 선택형 TLS 피어 호스트가 있다.
+[빌드와 사용법](native/README.md), [작성 창의 비용과 검증](../../docs/research/025-native-authoring-shell.md),
+[피어 연결과 전체 비용](../../docs/research/026-secure-peer-link.md).
+
+## 열기
+
+저장소 루트에서 다음 명령을 실행한다. Python 3가 필요하며, 포함된 Wasm으로 바로 열린다.
+
+```sh
+python3 prototypes/workbench/manage.py serve --port 8766
+```
+
+[제작 화면](http://127.0.0.1:8766)을 연다. 기존 Dino 실험은 8765 포트를 사용한다.
+서버는 로컬 컴퓨터에서만 접속할 수 있다.
+
+## 직접 만드는 흐름
+
+1. 먼저 만들고 싶은 작은 동작을 정한다. 무엇을 기억하고, 어떤 입력에 어떻게 바뀔지 정하면 된다.
+2. 빈 편집기에 작성한다. 화면 아래 **언어 안내**의 조각을 조합해도 된다.
+3. **코드 적용**을 누르면 도형과 선언한 입력 버튼이 생긴다.
+4. 버튼을 눌러보거나 장면을 클릭한 뒤 지정한 키를 누른다. `on frame`이 있다면 **실행** 또는 **한 프레임**으로 시간을 진행한다.
+5. 필요한 상태·입력·도형·반응을 더 작성하고 다시 적용한다. 같은 이름의 상태 값은 이어진다.
+6. **.pure 저장**으로 코드를, **배치 저장**으로 코드와 현재 배치를 보관한다. 새로고침으로 복원되지 않으므로 저장이 필요하다.
+
+문법이 막힐 때 볼 수 있는 최소 예시다. 이 예시를 실행하는 것 자체가 제작 경험 평가를 뜻하지는 않는다.
+
+```text
+state x = 80
+input right = ArrowRight
+circle seed(x, 180, 20, "#7ed9ae")
+on right.down {
+ x = x + 20
+}
+```
+
+새 반응을 추가할 때 원하는 표현을 찾을 수 있는지, 어디서 막히는지가 이번에 확인할 점이다.
+관찰은 **하려던 일 / 실제로 일어난 일 / 기대했던 일** 정도면 된다.
+완성된 카운터·충전·전환·직접 조작·세 도형 배치 예제는 별도로 선택해 불러오며 자동 적용하지 않는다.
+
+## 적용·정지·오류의 의미
+
+- 편집 중인 코드와 적용한 코드는 별개다. 저장은 **미적용 변경까지 포함한 편집기 내용**을 저장한다.
+- 적용은 같은 이름의 숫자 상태를 보존한다. 새 이름은 초깃값으로 시작하고 삭제한 이름은 사라진다.
+ 이름 변경을 자동으로 추적하지 않는다. 초깃값만 바꿨다면 **초깃값으로**를 눌러야 현재 값도 바뀐다.
+- 문법·이름·용량 오류가 있는 수정은 기존 작업을 유지한 채 거절한다. **문제 위치로**에서 해당 행으로 이동한다.
+- 누르는 중에는 적용할 수 없다. 모든 입력을 놓은 뒤 적용한다.
+- 정지는 시간 진행만 멈춘다. 입력 버튼은 정지 중에도 작동한다.
+- 계산 실패는 해당 이벤트의 모든 상태 변경을 취소한다. 마지막 실패 안내는 놓기 동작으로 사라지지 않으며,
+ 수정 적용 또는 초기화로 지워진다. 잘못된 도형 크기·표시 계산은 별도의 표시 오류다.
+- 편집기나 버튼에 포커스가 있으면 키 입력을 장면에 전달하지 않는다. 장면을 클릭하면 키를 사용할 수 있다.
+ 창에서 포커스가 벗어나면 시간을 멈추고 누른 입력을 놓는다. 키 입력의 `.up`, 직접 조작 입력의 `.cancel` 동작이 실행될 수 있다.
+- 실행은 프레임당 `dt = 1/60`이다. 화면 갱신마다 최대 6프레임을 진행하며, 밀린 나머지는 건너뛰고 횟수를 표시한다.
+
+## 표현할 수 있는 범위
+
+상태는 숫자다. 입력은 키의 누름·놓음과 대상에 연결한 포인터 좌표, 시간은 `on frame`으로 선언한다.
+출력은 사각형·원·숫자 글자이며 좌표·크기·표시 값을 상태 표현식에 연결한다.
+색상은 `"#rrggbb"` 상수다. 화면 좌표는 640×360 기준이다.
+
+```text
+on right.up { x = 80 }
+on frame {
+ x = x + 30 * dt
+ if x > 600 { x = 0 }
+}
+box tile(x, 220, 40, 40, "#356548")
+text label(40, 60, x, "x: ")
+```
+
+연산: `+ - * /`, 비교, `&& || !`, `min`, `max`, `clamp`.
+논리 연산은 양쪽을 모두 계산한다. `if`의 조건은 한 번만 계산하고 대입은 위에서 아래로 실행한다.
+같은 이벤트에 여러 반응을 선언하면 작성 순서대로 실행하며 한 이벤트 전체를 함께 성공·실패 처리한다.
+이벤트의 `dt`는 시간 진행에서 1/60, 입력에서는 0이다. 표시 표현식에서는 `dt`를 쓰지 않는다.
+
+키: Space, Enter, 방향키, KeyA~KeyZ, Digit0~Digit9. 한 키는 하나의 입력에 연결한다.
+주석은 `#`, 문장 구분은 줄바꿈 또는 세미콜론이다.
+
+한도: 상태 32개, 입력 8개, 도형/글자 24개, 반응 64개, 중간 명령 512개,
+계산 스택 64칸, 소스 32,768 UTF-8 바이트. 반복문·일반 함수 호출·문자열 상태·동적 배열은 없다. 매개변수 하나를 받는 공유 반응 규칙은 지원한다.
+충돌·중력·점프 등의 동작은 자동으로 제공되지 않는다.
+정확한 인터페이스와 명령 형식은 [CONTRACT.md](CONTRACT.md)에 있다.
+
+## 도형을 직접 잡아 조작하기
+
+예제 선택에서 **직접 잡아 이동 → 예제 불러오기 → 코드 적용**을 누른다.
+[direct.pure](examples/direct.pure)는 잡은 위치를 유지한 이동, 영역 제한,
+연결된 숫자 출력, 취소 시 복귀를 작성한 코드다. 좌우 방향키로도 위치를 바꿀 수 있다.
+
+```text
+input drag = pointer(tile)
+on drag.move {
+ x = clamp(event_x - grab_x, 20, 564)
+ y = clamp(event_y - grab_y, 100, 276)
+}
+```
+
+전체 예제에서 `.down`은 잡은 위치와 시작 위치를 기억하고, `.up`은 마지막으로 표시한 위치를
+확정하며, `.cancel`은 시작 위치로 되돌린다. 이 동작은 자동 제공되는 물리가 아니다.
+제한 값이나 취소 규칙을 바꾸고 다시 적용하면 동작도 바뀐다.
+`event_x/event_y`는 포인터 반응 안에서만 쓸 수 있는 장면 좌표다.
+서로 다른 대상은 동시에 조작할 수 있고, 같은 대상의 두 번째 접촉은 무시한다.
+초점·캡처를 잃으면 `.cancel`로 끝난다. 하나의 이벤트가 실패하면 그 이벤트만 취소된다.
+
+터미널에서도 같은 정의에 `point drag down 90 170`, `point drag move 360 220`,
+`point drag up 410 240`을 보내 재현할 수 있다.
+기능 검증과 범위는 [직접 조작 보고서](../../docs/research/013-authored-pointer-input.md)에 기록했다.
+
+## 배치를 저장하고 다시 열기
+
+**세 도형 배치 → 예제 불러오기 → 코드 적용**으로 시작한다.
+세 도형을 따로 움직일 수 있고, 마지막으로 잡은 도형은 방향키로 10씩 조정한다.
+시간 진행과 조작을 끝낸 뒤 **배치 저장**을 누르면 현재 상태까지 포함된 JSON을 내려받는다.
+**파일 열기**로 이 파일을 선택하면 새 페이지에서도 같은 배치가 복원된다.
+
+- 배치 파일에는 적용된 코드, 현재 숫자 상태, 아직 적용하지 않은 편집 초안이 들어 있다.
+- 복원은 저장된 코드를 실행 정의로 사용한다. 미완성 초안은 편집기에 남기며 자동 적용하지 않는다.
+- 조작 중인 입력과 실행 시계는 복원하지 않는다. 열린 작업은 정지 상태로 시작한다.
+- **초깃값으로**는 코드의 원래 초깃값으로 돌아간다. 저장 당시 배치로 돌아가려면 파일을 다시 연다.
+- **.pure 저장 / 코드 JSON 저장**은 기존처럼 초안만 저장한다. 배치 저장과 구분한다.
+- 잘못된 파일이나 읽는 동안 작업이 바뀐 파일은 기존 작업을 유지한 채 거절한다.
+
+터미널에서도 같은 파일을 쓸 수 있다.
+
+```sh
+node prototypes/workbench/cli.mjs my-layout.pure.json
+```
+
+명령 `save-project new-layout.json`으로 현재 배치를 저장하고 `restore saved-layout.json`으로
+복원한다. 기존 파일을 덮어쓰지 않는다. 브라우저에서 저장한 미적용 초안도 다시 저장할 때 보존한다.
+
+## 같은 규칙을 여러 대상에 연결하기
+
+**공유 규칙으로 배치** 예제는 앞의 세 도형 배치와 같은 작업을 공통 규칙으로 작성한다.
+`record`는 필드의 기본값, `item`은 대상별 실제 값, `rule`은 재사용할 반응이다.
+
+```text
+record Position { x=0; y=0 }
+item green = Position(x=80,y=120)
+rule move(t: Position) { t.x=event_x; t.y=event_y }
+input drag = pointer(tile)
+box tile(green.x,green.y,56,44,"#c9f08b")
+on drag.move use move(green)
+```
+
+여러 입력을 같은 규칙에 연결해도 규칙의 실행 명령은 한 벌만 저장한다.
+매개변수의 필드는 연결된 대상의 값을 직접 바꾼다. 같은 사건의 여러 반응 중 하나가
+실패하면 앞의 변경도 함께 취소한다. 대상·필드 선언 순서를 바꾸어 적용해도
+`green.x` 같은 이름으로 값을 유지하고, 배치 파일도 그 이름을 저장한다.
+
+규칙은 레코드 매개변수 하나를 받는다. 레코드당 필드는 1~8개이며 모든 대상의 필드는
+기존 상태 32개 한도에 포함된다. 실행 중 대상 생성·삭제는 아직 없다.
+전체 작성물은 [shared-layout.pure](examples/shared-layout.pure), 결과와 한계는
+[공유 규칙 보고서](../../docs/research/015-shared-rules.md)에 있다.
+
+## 여러 도형을 하나의 포인터 입력으로 연결하기
+
+**입력 묶어서 배치 → 예제 불러오기 → 코드 적용**으로 시작한다.
+
+```text
+input drag = pointer { green_box: green; blue_box: blue }
+on drag.down use grab(drag.target)
+on drag.move use move(drag.target)
+on drag.up use drop(drag.target)
+on drag.cancel use cancel(drag.target)
+```
+
+같은 record 타입의 대상을 묶고, 현재 잡은 대상의 필드를 공통 규칙에 전달한다.
+도형을 늘려도 이 네 반응을 대상마다 복제하지 않는다. 서로 다른 도형은 동시에
+조작할 수 있고 하나를 놓거나 취소해도 다른 도형의 조작은 이어진다.
+같은 대상은 한 그룹 연결만 가질 수 있다. 전체 연결 대상은 최대 24개이며
+상태 32개 등 기존 한도는 그대로다. 현재 예제의 키보드 선택 연결은 기존 방식이다.
+
+배치 파일은 이전과 같은 방식으로 저장·복원한다. 마지막 입력에는 대상 도형도 표시한다.
+CLI에서는 `point-target drag green_box down 90 130`을 사용한다.
+[전체 작성물](examples/routed-layout.pure), [구현 결과와 한계](../../docs/research/016-pointer-routing.md).
+
+## 일반 코드와 비교하기
+
+[일반 JavaScript 비교 앱](http://127.0.0.1:8766/baseline.html)은
+[충전 예제](examples/charge.pure)와 같은 상태·출력 계산을
+[독립 JavaScript 모델](web/baseline.mjs)로 작성한 것이다.
+Pure 컴파일러나 실행기를 가져오지 않는다.
+
+JS 모델은 고정된 앱이다. 일반 프로그램의 실행 중 교체나 상태 이전은 제공하지 않는다.
+두 파일의 길이만으로 개발 생산성을 비교하지 않는다. 자세한 실행 비용과 범위는
+[연구 보고서 004](../../docs/research/004-authoring.md)에 있다.
+
+## 터미널에서 같은 작업 실행하기
+
+Node.js가 필요하다. 검증에 사용한 버전은 24.20.0이다.
+
+```sh
+node prototypes/workbench/cli.mjs my-work.pure
+```
+
+```text
+down right
+up right
+frame 10
+show
+load my-work.pure
+reset
+save saved-work.pure
+quit
+```
+
+`load`는 같은 이름의 상태를 보존한다. `save`는 적용한 소스를 저장하고 기존 파일은 덮어쓰지 않는다.
+경로에 공백이 있으면 명령 뒤에 경로 전체를 입력한다. 별도 따옴표는 붙이지 않는다.
+브라우저와 터미널은 동일한 컴파일러·Wasm을 쓰지만 **각자 별도의 실행 상태**를 갖는다.
+
+## 계산 기록과 작업량 제한
+
+표시 계산에 [기록을 버릴 수 있는 실행 계약](../../docs/research/009-bounded-display-integration.md)을
+연결했다. 모든 도형에 하나의 작업 한도를 적용한다. 한도를 넘으면 장면 전체를
+표시하지 않고 오류를 알린다. 그 전에 성공한 입력 이벤트의 상태는 유지된다.
+기본값은 작업량 2,048, 캐시 꺼짐이다. 터미널에서 다음처럼 사용할 수 있다.
+
+```text
+limits 32 8 2048
+show
+costs
+forget all
+show
+limits 32 8 1
+limits 32 8 2048
+```
+
+`limits`의 숫자는 결과 캐시 개수·변경 기록 개수·표시 작업량이다.
+`forget`은 `all`, `values`, `history`를 받는다. `costs`는 마지막 표시 계산의
+작업량·명령 수·캐시 적중을 보여준다. 한도가 부족하면 101 표시 오류가 나고,
+충분히 늘리면 다시 계산한다. 파이프로 실행한 CLI는 중간 실패도 종료 코드 1에 남긴다.
+
+임베딩 API는 `createRuntime(wasmBytes, {cacheEntries:32, historyEntries:8, workLimit:2048})`,
+`configureEvaluation`, `forgetEvaluation`, `evaluationStats`다. 브라우저 작성 화면에는
+이 설정을 위한 새 UI를 추가하지 않았다. 저장 공간 반납이나 앱 전체의 메모리·시간
+상한을 제공하는 기능은 아니다. 자세한 범위는 [계약](CONTRACT.md)을 따른다.
+
+## 빌드·검증 재현
+
+Zig 0.15.2를 설치하고 저장소 루트에서 실행한다. 다른 경로의 Zig는 `--zig /path/to/zig`로 지정한다.
+
+```sh
+python3 prototypes/workbench/manage.py build
+node prototypes/workbench/compiler.test.mjs
+node prototypes/workbench/runtime.test.mjs
+node prototypes/workbench/baseline.test.mjs
+node prototypes/workbench/cli.test.mjs
+node prototypes/workbench/evaluation.test.mjs
+node prototypes/workbench/workspace.test.mjs
+node prototypes/workbench/presentation.test.mjs
+node prototypes/workbench/routing.test.mjs
+node prototypes/workbench/evaluation-check.mjs --output /tmp/pure-evaluation-new.json
+node prototypes/workbench/workspace-check.mjs --output /tmp/pure-workspace-new.json
+node prototypes/workbench/compare.mjs --output /tmp/pure-comparison-new.json
+```
+
+현재 빌드 명령은 네이티브 Zig 검사 37개도 실행한다. 전체 JS 검사는
+`node --test prototypes/workbench/*.test.mjs`로 실행하며 현재 87개다.
+비교 파일은 덮어쓰지 않으므로 매번 새 출력 경로를 지정한다.
+빌드는 `ReleaseSafe`, 디버그 정보 제거, 16 KiB 스택, 초기·최대 선형 메모리 64 KiB다.
+산출물과 빌드 기록의 해시로 재현 대상을 확인할 수 있다.
+
+실제 브라우저 조작 시나리오는 [qa-browser.js](qa-browser.js)에 있다.
+Playwright CLI 0.1.21의 `run-code`에 함수 전체를 전달하고,
+마지막의 `window.__pureWorkbenchQAEvidence`를 후속 읽기로 수집했다.
+대화상자·다운로드에서 CLI가 일찍 반환할 수 있으므로 반환 자체를 완료로 간주하지 않는다.
+저장된 [브라우저 결과](results/2026-09-22-linux-aarch64/browser-checks.json)는 44개 확인을 포함한다.
+
+이 결과는 Linux aarch64에서 얻었다. M3 macOS, 네이티브 GUI·Metal, 센서 입력,
+프로세스 전체 메모리와 실제 사용자의 제작 시간은 아직 측정하지 않았다.
+
+표시 계산의 첫 통합 기록은
+[2026-09-22-evaluation](results/2026-09-22-evaluation/verification.json)에 있다.
+기존 44개 브라우저 확인은 이전 빌드의 기록이다. 이번 변경의 실제 브라우저
+확인 절차는 [qa-evaluation.js](qa-evaluation.js)다.
+최신 빌드의 [브라우저 결과](results/2026-09-23-workspace/browser.json)는 12개 확인을 포함한다.
+이 절차의 결과는 `window.__pureEvaluationEvidence`에서 읽는다.
+
+현재 빌드는 코드 교체 준비 공간과 계산 기록을 공유한다. 교체 준비 중에도
+기존 이벤트와 표시 계산은 실행할 수 있으며, 표시 계산은 기록 없이 재계산한다.
+검사·비교 결과는 [2026-09-23-workspace](results/2026-09-23-workspace/verification.json)에 있다.
+코어 고정 구조는 19,128 B로, 기록 기능을 넣기 전보다 증가한 전용 공간은
+56 B다. 공유 영역을 메모리 합계에 중복해서 더하지 않는다.
+
+현재 화면 갱신은 `present(target)`으로 계산 결과를 기존 DOM 요소에 전달한다.
+매 입력마다 전체 장면 스냅샷이나 도형·상태·비용 요소를 다시 만들지 않는다.
+CLI·검사용 `snapshot()`은 유지한다. [입력→출력 구현 보고서](../../docs/research/011-direct-presentation.md)에
+구조 변경, 추가 공간, 속도 측정의 한계를 기록했다.
+직접 출력 통합 당시의 [52개 JS/CLI/Wasm 검사](results/2026-09-23-presentation/verification.json),
+[브라우저 경로 비교](results/2026-09-23-presentation/browser.json),
+[작성 흐름 검사](results/2026-09-23-presentation/browser-authoring.json),
+[표시 한도 검사](results/2026-09-23-presentation/browser-limit.json)를 함께 보존했다.
+
+브라우저 비교는 Playwright CLI 0.1.21로 로컬 페이지를 연 세션에서 다음처럼 재실행한다.
+이전 구현은 테스트 전용 응답 교체로 불러오며 현재 제품 파일은 바꾸지 않는다.
+
+```sh
+python3 prototypes/workbench/qa-presentation.py --cli /path/to/playwright-cli.js --session pure-presentation --output /tmp/pure-presentation-new.json
+```
+
+후속으로 실행 입력과 코드 편집의 갱신 경로를 분리했다. 실행 중인 값이 바뀌어도 소스의
+줄 번호·크기·커서 정보를 다시 계산하지 않는다. 실제 소스 변경과 적용 상태는 계속 갱신한다.
+[결과 보고서](../../docs/research/012-refresh-ownership.md),
+[브라우저 비교](results/2026-09-23-refresh/browser.json),
+[작성 흐름](results/2026-09-23-refresh/browser-authoring.json),
+[표시 한도](results/2026-09-23-refresh/browser-limit.json)를 보존했다.
+이 변경에서 런타임·컴파일러·Wasm은 그대로이며, 이전 단위 검사를 새 검사로 세지 않는다.
+
+```sh
+python3 prototypes/workbench/qa-refresh.py --cli /path/to/playwright-cli.js --session pure-path --output /tmp/pure-refresh-new.json
+```
+
+## 실제 웹 레이아웃과 문서 편집
+
+[문서 작업 화면](http://127.0.0.1:8766/document.html)은 일반 UTF-8 텍스트를 열고,
+수정하고, 옆에서 읽고, `.txt`로 저장하는 작업이다. 기존 작업 화면에도 링크가 있다.
+
+- 넓은 화면에서는 경계를 잡거나 방향키·슬라이더로 두 패널의 비율을 조절한다.
+- Esc·접촉 취소는 시작 비율로 돌아간다. 창 크기가 바뀌어도 조작 좌표를 다시 맞춘다.
+- 좁은 화면에서는 작성·읽기가 위아래로 놓인다. 각 패널은 브라우저의 스크롤을 사용한다.
+- **분할 규칙 수정**에서 `clamp(event_value,20,80)`의 제한을 더 좁혀 적용할 수 있다.
+- 파일은 텍스트만 저장한다. 패널 비율과 규칙을 저장하는 별도 문서 형식은 만들지 않았다.
+
+이 화면은 직접 작성한 HTML 호스트다. Pure의 현재 DSL만으로 일반 웹 UI 전체를
+작성하게 된 것은 아니다. 레이아웃·텍스트·IME·선택을 VM에 복제하지 않고, 필요한
+숫자 조작만 전달한다. 파일은 2 MiB까지, UTF-8을 사용하며 저장 시 줄바꿈은 LF다.
+현재 읽기 화면은 문서 편집 시 텍스트 전체를 갱신한다. 전체 앱의 메모리 절감이나
+큰 문서 성능 우위는 주장하지 않는다. [결과와 한계](../../docs/research/017-web-layout-boundary.md).
+
+```text
+state ratio = 50
+input split = value
+on split.change { ratio = clamp(event_value,20,80) }
+```
+
+웹 호스트 API는 `runtime.change('split',65,'range')`, CLI는 `change split 65`다.
+숫자 값은 유한해야 하고 상태 배열에 자동 보관되지 않는다. 필요한 값만 규칙에서 저장한다.