# FONUMA 고급 웹 개발 — Application 0.2 dev.3

2026-10-05 · 개발 후보 `fonuma-app 0.2.0-dev.3` · 공개 dev.2와 별도.
이 문서는 구현 계약과 사용법이다. 실제 검증 결과는 `APPLICATION_ADVANCED_VALIDATION_2026-10-05.md`에 따로 기록한다.

## 지금 확장한 범위

기존 언어 코어의 모듈·레코드·리스트·Result·storage 효과를 바탕으로 공통 호스트, UI 검증기, 렌더러, SDK를 확장했다. 이번 변경은 새로운 코어 문법을 발명하거나 기존 실행 예산을 완화하지 않는다. 화면과 업무 규칙은 `.fonuma`, 인증·트랜잭션·HTTP·DOM은 공통 Rust/JavaScript가 담당한다.

| 템플릿 | 저장 모드 | 실제 업무 흐름 |
|---|---|---|
| operations | personal | 상품·고객·여러 품목 주문·취소·재고 원복 |
| commerce | shared | 관리자 상품/자원 등록·회원 주문·취소·공유 시간 구간 예약 |
| community | shared | 게시글·댓글·작성자 수정·관리자 숨김·숨긴 글/댓글 조회 제외 |
| taskboard / inventory / booking | personal | 기존 개인 작업·재고·정수 슬롯 예약 |

`operations`는 조직 전체 ERP가 아니라 개인 업무 공간이다. `commerce`의 주문은 금액 계산과 상태·재고 변경이며 결제/환불/배송이 아니다. 예약은 정수 반열린 구간 `[start,end)`이며 실제 달력·시간대·반복 예약이 아니다. `community`는 로그인 회원끼리 공유하며 익명 공개 게시판은 지원하지 않는다.

```sh
fonuma-app new operations-demo --template operations
fonuma-app new commerce-demo --template commerce
fonuma-app new community-demo --template community
fonuma-app check operations-demo
fonuma-app test operations-demo
fonuma-app serve operations-demo --data-dir operations-demo/.fonuma/data --port 3441
```

서로 다른 앱은 다른 데이터 디렉터리와 포트를 사용한다. 기존 프로그램·DB를 덮어쓰지 않는다. commerce/community의 관리자는 `user-add PROJECT --data-dir DIR --role admin`으로 만들고 자격증명 JSON을 표준입력으로 전달한다. 비밀번호를 명령줄·소스·프롬프트에 남기지 않는다. 일반 회원가입으로 관리자 권한을 얻을 수 없다.

## 여러 화면과 중첩 레이아웃

기존 `page`는 앱 전체 제목이며 첫 행에 정확히 하나다. `view`가 있으면 모든 본문 요소는 view 안에 들어가야 한다. 아래는 화면 계약의 일부이며 `products` 읽기 경로가 별도로 필요하다.

```fonuma
fn screen() -> List<List<String>> effects [pure] {
 return [
  ["page", "업무관리", "상품과 주문"],
  ["view", "products", "상품", "상품 목록"],
  ["group", "product-layout", "상품 관리", "columns"],
  ["text", "같은 그룹 안에 폼과 목록을 배치할 수 있습니다."],
  ["table", "products-table", "products"],
  ["column", "products-table", "name", "상품명"],
  ["endgroup"],
  ["endview"],
  ["view", "help", "도움말", "사용법"],
  ["text", "왼쪽 화면에서 상품을 확인하세요."],
  ["endview"]
 ];
}
```

- `view,id,title,description` / `endview`: 최대 16개, `#id`로 이동. 새로고침·뒤로/앞으로 이동은 브라우저 hash를 사용한다. 알 수 없는 hash는 첫 화면을 표시한다.
- `group,id,title,columns|stack` / `endgroup`: 최대 4단계 중첩. columns는 넓은 화면에서 2열, 760px 이하에서 1열이다.
- UI 총 512행, 한 문자열 최대 4096 UTF-8바이트. 모든 view/group/form/table/collection ID는 전역에서 유일하다.
- form의 input과 table의 column/action은 같은 view/group에 있어야 한다. 열린 그룹·화면을 끝내지 않거나 다른 화면의 요소를 참조하면 check가 거부한다.
- 늦은 비동기 렌더 결과는 새 화면이나 로그아웃 상태를 덮어쓰지 않는다. 폼 초안·검색·페이지 상태는 화면 이동 동안 메모리에 유지되며 세션이 바뀌면 비운다. 새로고침 후 초안 복원은 없다.

## 입력과 데이터 연결

기존 input(String), number(Int), checkbox(Bool)에 다음을 추가했다.

| 행 | 의미 |
|---|---|
| `textarea,form,param,label,default` | String 여러 줄 입력 |
| `select,form,param,label,default,value,label,...` | String/Int/Bool 고정 선택지 1–64개. 중복과 타입·기본값 오류 거부 |
| `lookup,form,param,label,route,keyField,labelField` | 읽기 전용·외부 인자 없는 List<Record>에서 선택. key는 입력과 같은 Int/String, label은 String |

lookup도 실제 로그인 권한으로 조회한다. 클라이언트가 선택지를 조작할 수 있으므로 관계 존재·재고·소유자 검사는 항상 업무 함수에서 수행한다. 표 column은 scalar만 표시한다. 리스트/중첩 레코드는 typed API로 사용하거나 별도 scalar 뷰를 작성한다.

## 검색과 서버 페이지 나누기

`collection,id,route,pageSize`에 column/action을 연결한다. pageSize는 1–100이다. 서버 함수는 클라이언트 인자 `(query:String, offset:Int, limit:Int)`를 이 순서로 받고 아래 형태의 레코드를 반환한다. shared route의 첫 actor는 외부 인자에서 제외한다.

```fonuma
record ProductPage { items: List<Product>, total: Int, offset: Int, limit: Int }
```

서버 업무 함수가 검색·권한 필터를 먼저 적용하고 total과 items를 계산한다. 예제는 대소문자를 구분하지 않는 포함 검색을 사용하며 최대 100 Unicode scalar 질의를 받는다. SDK `client.page(route,{query,offset,limit})`는 십진 문자열 범위, 응답 구조·인자 일치·실제 항목 수를 검증한다. Int를 JS Number로 변환해 총 개수를 손실시키지 않는다. 마지막 페이지가 사라지면 UI가 마지막 유효 페이지로 한 번 이동한다.

이 구현은 저장된 리스트를 순회한다. SQL 인덱스 검색·커서 페이지네이션·여러 요청 사이의 snapshot 고정·범용 쿼리 언어를 제공하지 않는다. 요청마다 최신 트랜잭션을 읽으므로 동시 변경 중 offset 페이지 사이의 중복/누락은 가능하다.

## 공유 저장과 신뢰할 수 있는 사용자 정보

manifest의 `storage_scope`는 `personal`(생략 시 기본) 또는 `shared`다. 기존 DB의 모드는 메타데이터에 고정한다. 키가 없는 예전 DB는 personal로 취급하며 shared로 자동 전환하지 않는다. 공유 공간은 앱/데이터 디렉터리 전체에 하나이고, 여러 조직·팀을 독립시키는 tenant 모델은 아니다.

```json
{"id":"buy","entry":"buy","access":"user","write":true,"actor":true}
```

```fonuma
record Actor { id: String, username: String, role: String }
fn buy(actor: Actor, product: Int, quantity: Int) -> Result<Int> effects [storage] {
 // actor.id를 주문 소유자로 기록하고 업무 규칙을 검사한다.
 return err("이 조각은 계약 예시입니다. commerce 템플릿의 실제 함수를 사용하세요.");
}
```

actor:true 함수의 첫 인자는 이름 actor, 필드가 정확히 id/username/role:String인 레코드다. shared의 모든 storage 경로는 actor:true여야 한다. public storage와 public actor는 거부한다. 호스트가 인증된 세션에서 Actor를 만들며 클라이언트는 나머지 인자만 보낸다. inspect·생성 SDK·UI에서도 actor를 입력으로 노출하지 않는다. 인자를 추가해 actor를 주입하려 하면 거부한다.

shared는 자동 행 수준 권한이 아니다. 앱 작성자가 actor.id/role로 조회·변경 권한을 검사해야 한다. 예제는 주문 소유자, 게시글 작성자, 관리자 경계를 구현한다. 읽기 목록에서도 타인의 비공개 주문을 필터링한다. 사용자 스크립트가 의도적으로 모든 공유 데이터를 반환하면 호스트가 이를 추측해 차단하지 않는다.

공유 kv는 로그인할 수 없는 잠긴 내부 주체에 속한다. 실제 계정·세션·재시도 영수증은 회원별이다. 동일 request_id를 다른 회원이 사용하는 것은 서로 다른 요청이며, 같은 회원의 같은 요청은 기존 1시간 재전송 계약을 따른다. 공유 쓰기는 기존 SQLite IMMEDIATE 트랜잭션에서 직렬화하여 마지막 재고나 겹치는 예약에 하나만 성공하도록 한다. 런타임 오류/최상위 Err는 상태를 취소한다.

저장 공간당 4MiB·128키, 함수당 100,000 step·16,000,000 work unit 한도는 유지한다. 예제의 상품/고객/주문/게시글/댓글은 각각 최대 500건, 자원은 100건, 주문은 1–20품목이다. 건수와 별개로 크기·실행 한도에 먼저 도달할 수 있다. 상품/게시글 ID의 순차 삽입·삭제 없음 불변식을 이용해 연결 레코드를 인덱스로 찾는다. 삭제 방식이나 저장 형태를 바꾸면 이 불변식과 migration을 함께 바꿔야 한다.

## 프로젝트별 JavaScript·TypeScript SDK

```sh
fonuma-app sdk commerce-demo --output commerce-sdk
```

새 디렉터리에 project-client.mjs/.d.mts, 공통 클라이언트 .mjs/.d.mts, schema.json, README.md를 생성한다. 기존 폴더나 심볼릭 링크는 덮어쓰지 않는다. 프로젝트 검증을 통과한 타입을 사용하고, 같은 입력은 같은 결과를 생성한다.

```javascript
import { createClient } from './commerce-sdk/fonuma-client.mjs';
import { connect } from './commerce-sdk/project-client.mjs';
const client = createClient();
await client.login(username, password);
const api = await connect(client);
const reply = await api.buy(['1', '2']);
if (!reply.result.ok) console.error(reply.result.error);
```

TypeScript에서 Int는 `${bigint}` 십진 문자열이며 레코드·리스트·Result·인자 개수도 생성한다. connect는 서버와 프로젝트 hash가 다르면 거부한다. 연결 시 한 번 확인하므로 서버 교체 후 다시 연결한다. 소스·manifest가 바뀌면 SDK를 재생성한다. 기본 앱에는 SDK가 내장되어 npm 설치가 필요 없다. 별도 프런트엔드에서 생성 SDK를 사용하면 해당 프로젝트의 빌드 도구는 별도로 필요하다. 정적 파일을 같은 origin에 배치하거나 기존 프런트엔드 빌드에 통합한다.

## 완성도와 다음 경계

이번 범위는 세 종류의 실제 업무 흐름을 공통 실행기로 만드는 개발 프리뷰다. 아래는 별도 구현·운영 검증이 필요한 범위다.

1. 결제·환불·배송·이메일/문자·외부 API와 비밀 관리, 파일 업로드·검사·객체 저장.
2. 조직/팀/세부 역할·초대·감사 기록, 계정 복구·이메일 검증·MFA.
3. WebSocket·실시간 갱신·백그라운드 작업·스케줄러, 실제 날짜/시간대와 반복 예약.
4. 자유로운 컴포넌트·서버 경로 라우팅·SSR/SEO·접근성/브라우저 시각 검증.
5. 큰 DB의 인덱스·관계 질의·여러 서버 확장·관측·운영 부하/장애·외부 보안 감사.
6. Windows 새 프로필, 깨끗한 macOS 설치, 독립 사용자 재현과 정식 출시 판정.

이를 완료했다거나 모든 복잡한 프로그램을 완벽히 개발할 수 있다고 표시하지 않는다. 기존 공식 dev.2 설치파일의 기능을 새 작업본의 기능과 혼동하지 않는다.
