# CalcMax Web / 웹 버전

CalcMax's existing Python/SymPy engine runs in the browser through CPython WebAssembly (Pyodide 314.0.7, Python 3.14.2, SymPy 1.14.0). The site consists entirely of static files; a calculation server, Android SDK, Node.js build, API key, and special cross-origin isolation headers are not required.

기존 Python/SymPy 계산 엔진을 브라우저의 WebAssembly 런타임에서 실행합니다. 계산 서버 없이 정적 웹 호스팅으로 배포할 수 있습니다.

## Build and run / 빌드 및 실행

### GitHub Pages — open index.html / GitHub에서 바로 실행

The repository includes `.github/workflows/pages.yml`. In **Settings → Pages → Build and deployment → Source**, select **GitHub Actions** once. Push the changes to `main` (or run **Actions → Deploy CalcMax Web → Run workflow**). The workflow builds and publishes a complete static site, including the WASM runtime and engine archive. No Python server or build command is needed by visitors.

저장소의 **Settings → Pages → Source**를 **GitHub Actions**로 한 번 설정하세요. 이후 `main`에 업로드하면 자동으로 배포됩니다. 배포가 완료되면 아래 주소에서 `index.html`이 바로 실행됩니다.

- [CalcMax Web](https://kirinonakar.github.io/calcmax/)
- [index.html](https://kirinonakar.github.io/calcmax/index.html)

These are deployment addresses, not confirmation that the site has already been published. GitHub's repository file viewer shows HTML source; use the Pages URL to run the application. All files are relative to `index.html`, so project Pages paths such as `/calcmax/` are supported. For forks, replace the owner/repository in the URL.

### Local development / 로컬 개발

From the repository root, with Python 3.10 or newer:

```powershell
python web/build.py
python web/serve.py
```

Open [http://localhost:8080](http://localhost:8080). The first build needs an internet connection to download the pinned runtime and wheels. Subsequent builds reuse downloaded files. `--skip-download` updates the bundled engine, function catalog, help files, and cache manifest without downloading dependencies.

저장소 루트에서 위 명령을 실행한 뒤 브라우저로 접속하세요. 최초 빌드에만 런타임 다운로드가 필요하며, 계산은 브라우저 안에서 처리합니다. `index.html`을 더블 클릭하는 `file://` 방식은 Worker/모듈 로딩 제한 때문에 지원하지 않습니다.

Upload the **complete contents of `web/`**, including generated `vendor/`, `engine.zip`, and `assets.js`, to any static HTTP(S) host. Nested paths such as `/calcmax/` work. Serve `.wasm` as `application/wasm`, `.js`/`.mjs` as JavaScript, and preserve binary archives. Do not replace missing files with an HTML SPA fallback. GitHub Pages, nginx, or a standard static file server can serve the site.

For deployment, `node_modules/`, `tests/`, `package*.json`, `build.py`, and `serve.py` can be omitted. `serve.py` is only a development static file server and sets portable `.mjs`/`.wasm` MIME types; Windows' default MIME mappings may serve `.mjs` as `text/plain`. The generated runtime and engine archive are intentionally Git-ignored; run the build after cloning and whenever the Android engine changes. The small catalog, help files, fixture snapshot, and cache manifest are generated by the same script.

## Features / 기능

- Scientific/CAS expressions use a JavaScript port of the Kotlin Pratt parser and the **same validated AST protocol and Python engine** as Android. Exact decimal literals, fractions, complex arithmetic, calculus, solving, matrices, vectors, units, statistics, distributions, transforms, and finance catalog functions remain available. No JavaScript `eval` or SymPy `parse_expr` is used for math input.
- Korean browsers default to Korean; other browsers default to English. Choose either language in Setup.
- Light, dark, and system themes, selectable in Setup. System mode responds to operating system changes. Preferences are saved locally.
- Android keypad layout: direction pad, six-column scientific/CAS rows, five-column numeric rows, SHIFT/ALPHA, and 2nd/1st page switching. Hold a key for 500 ms to use its SHIFT function directly; holding SHIFT itself toggles SHIFT as on Android.
- DEG/RAD/GRAD, internal precision from 3–200 digits, display digits from 2–200 (limited by internal precision).
- Math input supports plain-text/LaTeX paste. Missing trailing brackets close automatically on calculation; this can be disabled in Setup.
- Scroll up to browse up to ten previous calculations; tap a formula to reuse it. The separate History dialog retains up to 500 calculations. Clearing History keeps starred entries.
- The result notation button cycles OFF → ENG → SCI → OFF, as on Android. OFF shows a dimmed ENG label; ENG uses powers of three and SCI uses powers of ten. The preference is saved and applies to current results and history without changing exact values.
- CALC prompts for input variables one at a time using the regular keypad or Keyboard entry. Saved formulas and symbolic STO results expose their underlying inputs: after `C=A+B`, enter `C` and press CALC to enter A and B, even if they already have numeric values. Formula assignments retain their source; self-updates such as `B=B+1` still store the computed value. Confirm each value with `=` or CALC; a blank value recalls its stored value or defaults to zero. Values must be numeric. AC cancels and restores the formula.
- Python editor with local `.py` open/save, `calcmax_catalog`, standard library/SymPy/mpmath, bounded output, and pre-entered `input()` values (one per line).
- Statistics, matrices, and vectors use spreadsheet tables with row/column headers and arrow-key cell navigation. Statistics columns and independent sample selectors are named x/y/z; controls unused by the selected analysis are disabled. Group/value data select group names from the data.
- Statistics include grouped and categorical data, custom regression, distribution queries, and scatter/histogram/box plots.
- History, variables, custom functions, datasets, drafts, and settings are saved in browser local storage. Setup can export/import a full backup. No account is required.
- After the calculation engine is ready, the service worker caches the complete static application for offline use on HTTPS or localhost. The cache version changes when any shipped source or engine file changes. Calculation itself does not require the internet; the optional currency download does.

## Execution and scope / 실행 및 범위

The WASM interpreter runs in a dedicated **module** Web Worker, as required by Pyodide 314. Stop or the 20-second deadline terminates the Worker and starts a new interpreter, including for scripts such as `while True: pass`. Stored browser state survives this restart. No SharedArrayBuffer or COOP/COEP headers are needed. Service-worker upgrades reload old cached pages once after saving their drafts, so obsolete classic-worker scripts do not survive an update. Activation does not wait for those navigations: browsers defer their fetch events until activation finishes, so awaiting navigation there would deadlock page and engine loading. Cancelling a navigation or closing a tab does not fail activation.

웹은 MathML 수학 입출력과 텍스트 편집을 사용합니다. Android의 문서 제공자 권한과 공유 창은 웹 파일 선택·다운로드·클립보드로 대체합니다. Python `input()`은 대화 상자 대신 미리 입력한 값을 순서대로 받습니다. 그래프는 SVG이며 3D 축·눈금이 표면과 함께 회전합니다. 와이어프레임·표면·표면+격자 렌더링, 표면 색상, 격자 밀도(12×12~96×96), 회전·고도·확대, 자동·수동 z 범위를 조절하고 설정을 저장할 수 있습니다. SymPy의 계산 한도와 미해결 기호 결과는 Android와 동일하게 유지합니다.

Only the standard library, SymPy, and mpmath are bundled; arbitrary native Python packages are not installed. A runtime's first load transfers approximately 18 MB of local static assets and can take longer on mobile devices. Browser storage and offline cache availability depend on the browser. Clearing site data removes locally saved work; use full backups to keep a copy.

Engine asset requests have a 30-second deadline covering headers and the complete response body, and retry once with the HTTP cache bypassed. Startup installs the bundled pure-Python mpmath and SymPy wheels directly, in dependency order, with the pinned integrity hashes, avoiding the runtime package manager's initialization locks. Download and installation errors fail startup explicitly. Engine startup has a two-minute deadline per attempt. A stalled or failed startup restarts the Worker automatically once without reloading the page; if both attempts fail, the status shows an error and Reload engine can start a fresh attempt. Drafts and saved data survive these retries.

## Validation / 검증

### JavaScript structure / 코드 구조

`app.js` composes the controllers, switches workspaces, and manages page lifecycle. Modules communicate through explicit callbacks; each controller owns its transient state.

- `app-state.js`: saved-state defaults, field restoration, and debounced persistence.
- `app-ui.js` / `app-dialogs.js`: shared UI helpers, dialogs, catalog, and settings.
- `engine-ui.js`: engine status, busy/Stop controls, and offline registration.
- `calculator.js` / `calculator-keypad.js`: expression editing, evaluation, previews, CALC, results/tape, and keypad modifiers.
- `workspaces.js`: workspace execution routing, shared formula previews, and simple tool forms.
- `matrix-workspace.js`, `statistics-workspace.js`, `python-workspace.js`, `functions-workspace.js`, and `graph-workspace.js`: each workspace's controls and state.

### Run tests / 테스트 실행

```powershell
cd web
npm ci
npm test
```

Node.js 22 or newer is needed only for development tests. jsdom is a test-only dependency; the shipped application has no npm runtime dependencies. Tests compare the web parser to 114 actual Kotlin-exported AST fixtures, run the actual WASM engine, and exercise production Worker requests and DOM workflows including language/theme switches, math, graphs, Python input, variables/custom functions, and hard cancellation/recovery. They run without computer use or browser automation. These tests do not establish pixel-level browser rendering or cross-browser compatibility.

To refresh Kotlin fixtures after parser changes, run `./gradlew.bat :math:exportCases` and then `python web/build.py`. Follow the root README for Android/desktop engine validation.

Pyodide's [self-hosting documentation](https://pyodide.org/en/stable/usage/downloading-and-deploying.html) describes its static runtime distribution. See [THIRD_PARTY.md](THIRD_PARTY.md) for license notices.