본문으로 건너뛰기
SheetForge

SheetForge Web

SheetForge는 **web.sheetforge.workers.dev**에서 컴패니언 웹 앱을 제공한다. 이는 작성, 검증, 시트 리플렉션을 브라우저로 가져온다 — Unity도, 설치도 필요 없다.

동일한 코어, 재구현이 아니다

이 웹 앱은 정확히 동일한 C# 코어 소스 — 헤드리스 .NET 테스트 하네스가 실행하는 바로 그 소스 — 를 .NET browser-wasm 어셈블리로 컴파일한다. 파서나 검증기의 두 번째 사본은 존재하지 않으므로, 둘은 결코 어긋날 수 없다: 한 번 고친 규칙은 두 곳 모두에서 고쳐진다.

이 설계로부터 두 가지가 곧바로 따라온다.

  • 컴파일된 플러그인 DLL이 수정 없이 로드되며, 이곳에서도 에디터와 동일한 슬롯을 켠다(아래 참고).
  • 이 사이트의 시트 문법 규칙은 브라우저에서도 동일하게 유지된다. 마커, 타입 시스템, @overlap, @style, @enum, @loc, 참조 무결성 모두 동일한 코드가 답하므로 동일하게 동작한다. 여기에는 IntId@Tab, LocRef@Tab 패리티도 포함된다 — 시트 문법 참고.

플러그인 하나, 열두 슬롯, 두 호스트

플러그인에는 "웹용 부분집합"이 없다. 어셈블리 — 인스턴스화, 순서, 격리, 호환성 게이트 — 는 두 호스트가 함께 호출하는 하나의 순수 Core 함수다. 오직 발견만 다르다: Unity는 프로젝트의 타입을 색인하고, 브라우저는 업로드된 어셈블리를 스캔한다.

그래서 플러그인이 채울 수 있는 열두 슬롯은 양쪽에서 동일하다:

슬롯브라우저에서
Enum · 셀 파서 · 래퍼 타입셀이 사용자 자신의 표기법을 통해 파싱, 검증, 라운드트립된다
도메인 검증기사용자의 규칙이 코어 규칙 옆의 Problems에 나타난다
엣지 기여자사용자의 표기법 안에 묻힌 링크가 캔버스에 그려지고 참조 인덱스에 집계된다
구조 마커사용자의 @marker 행이 받아들여지고 검증된다
시트 템플릿사용자의 템플릿이 시트 생성에 나타난다
캔버스 augmenter가상 노드, 추가 엣지, 레이어와 표시 힌트
코드 레지스트리코드에 존재하는 키가 더 이상 깨진 참조로 그려지지 않는다
컬러 프리셋앱의 CSS 변수로 매핑된다
선언적 스튜디오 UI사용자의 액션, 패널, 컬럼 배지, 셀 에디터 힌트가 React로 렌더링된다
UI 문자열사용자의 라벨이 t()가 참조하는 동일한 오버레이를 통해 사용자의 언어를 따른다
파이프라인 관찰자에디터에서와 정확히 동일하게, 임포트 사이클이 끝날 때 알림을 받는다

선언적 표면이야말로 플러그인의 작성 확장이 이곳에도 존재하는 이유다. UIToolkit 엘리먼트를 반환하는 계약은 브라우저에서 결코 로드될 수 없으므로, 껍데기는 데이터로 서술된다 — id, 라벨 키, 배치, 톤 — 그리고 각 호스트가 자신의 위젯으로 이를 그린다.

어휘가 말할 수 없는 무언가가 필요할 때, 팩은 동일한 id로 에디터 전용 리치 패널을 등록한다. 에디터는 그것을 그리고, 브라우저는 서술형을 그리며, 확장이 단순히 빠지는 일은 결코 없다.

렌더링 시점에는 두 규칙이 유지된다. 플러그인에서 오는 텍스트는 이스케이프되며, 그려진 이후 상황이 바뀐 액션은 오래된 id에 대해 행동하는 대신 정직한 무동작으로 답한다.

플러그인 어셈블리는 자신이 어느 세대의 플러그인 형식을 대상으로 빌드되었는지도 선언할 수 있다. 이것이 이 호스트가 읽는 범위를 벗어나면, 어셈블리 전체가 절반만 로드되는 대신 읽을 수 있는 사유와 함께 거부된다. 마켓 경로, 사이드로드 경로, 로컬 파일 모두 동일한 선언을 읽는 동일한 게이트다. 플러그인 작성 참고.

Unity에 남는 것

현지화도 같은 정직함을 따른다: 현지화 시트는 여기서 완전히 작성되고 검증되지만, Unity Localization StringTable을 동기화하는 것은 에디터의 몫이다 — 브라우저에는 써넣을 Unity 프로젝트가 없다.

코드젠과 베이크는 여전히 Unity 전용 책임이다. 브라우저는 .cs 파일을 만들거나 ScriptableObject를 쓸 수 없으며, 그런 척하지도 않는다. 프로젝트 자체를 필요로 하는 것도 마찬가지다: 에셋 피커, Project 창에서의 드래그 앤 드롭, 그리고 이들이 스테이징하는 Addressables 등록은 에디터에만 존재한다; AssetRef@Group<Type><Type>은 브라우저에서도 파싱되어 표시되지만 해석 — 그리고 타입 검사 — 은 프로젝트의 타입이 로드된 곳에서만 이루어진다; 그리고 에디터의 에셋 썸네일, 확대 보기, 오디오 재생은 웹에 대응물이 없다 — 브라우저에는 읽어들일 프로젝트 에셋이 없기 때문이다 — 웹 그리드의 에셋 셀은 그저 주소 텍스트일 뿐이다.

웹의 출력물은 리플렉션된 시트 — 검증되고 라운드트립된 소스 — 이지, 베이크된 에셋이 아니다. 브라우저에서 작성하고 검증한 다음, 강타입 클래스와 베이크된 SO가 필요할 때 Unity에서 임포트를 실행한다. 어느 쪽이든 시트가 단일 진실 공급원이므로, 두 표면은 시트에서 만난다.

웹 앱이 하는 일

모든 것은 하나의 JSON 경계 계약을 통해 WebAssembly 코어와 왕복하므로, 브라우저 UI는 코어가 이미 소유한 규칙을 결코 다시 유도하지 않는다.

영역얻는 것
작성 세션임포트, 프로젝션, 그리고 동일한 임포트 검증기가 채우는 Problems 패널 — 별도의 점검이 아니라 사전 검증 진단이다. 임포트는 TSV·CSV 파일과 .xlsx 워크북을 받아들인다 — 워크북의 모든 시트가 에디터가 쓰는 것과 동일한 리더(WebAssembly로 컴파일됨)로 읽혀 각자의 탭으로 도착하며, 무언가를 해석해야 했을 때 — 수식의 캐시된 값, yyyy-MM-dd 텍스트로서의 날짜, 읽히지 않은 서식 — 가져오기 대화상자는 "이 워크북이 어떻게 읽혔는지" 메모에 그 사실을 알린다.
테이블 편집타입별 셀 위젯, undo, 컬럼 고정, 정렬, 검색, @style 그룹화와 색상 — 그리고 에디터가 가진 스프레드시트 컬럼 기능 일체: 헤더 손잡이를 드래그해 컬럼 너비를 정하고(더블클릭으로 맞춤, 이 브라우저에 시트별로 저장됨), 문자와 정직하게 유지되는 경계 표시로 컬럼을 숨기고 보이며, 검색과 결합되는 열별 값 필터, 하나의 undo 단계인 열 데이터 삭제까지. + Row는 제안된 키 — 정수 id 컬럼은 미리 채워짐 — 를 가진 행을 한 번에 스테이징하며 키 셀에서 바로 이름을 바꿀 수 있고, 키 컬럼이 없는 시트에서는 사유와 함께 비활성화된다. Enum과 선택적 불리언 셀은 참조 셀이 쓰는 것과 동일한 검색 가능한 피커를 연다. 그리고 피커 창은 언제나 하나만 열려 있다 — 다른 것을 열면 마지막 것이 닫히고, 다른 곳을 클릭하거나 Esc를 누르면 닫힌다. 행 번호와 컬럼 문자는 선택된다(Ctrl로 토글, Shift로 범위 지정) — 헤더와 본문은 선택을 강조 표시하고, 선택된 셀은 키보드를 따라가는 윤곽선을 그리며, 선택은 삽입선과 함께 새 위치로 드래그된다 — 떨어진 선택은 선택한 순서대로 드롭 지점에 모여 도착하며, 이는 하나의 undo 단계다 — 한편 우클릭 메뉴는 너비·맞춤·숨기기·이동·삭제·데이터 삭제를 선택된 모든 컬럼에 적용한다(이동과 삭제는 선택된 모든 행에), 단일 대상 항목은 각자의 섹션에 따로 선다. Enum 정의 시트는 데이터 시트와 동일한 컬럼 너비 축을 갖는다 — 드래그, 더블클릭으로 맞춤, 정확한 너비, 데이터에 맞추기, 숨기기. 200행을 넘으면 그리드는 보이는 창(윈도)만 렌더링하며(더해 오버스캔), 스페이서가 진짜 스크롤 높이를 유지한다 — 에디터 테이블이 쓰는 것과 동일한 임계값이므로, 큰 시트에서 양쪽이 똑같이 느껴진다.
시각적 값Color, AnimationCurve, Gradient 셀은 셀을 가득 채우는 미리보기를 보여준다 — 스와치, 커브 폴리라인, 그라디언트 바 — 그리고 팝오버에서 완전한 에디터를 연다: HSV 정사각형, 색조·알파 슬라이더, 헥스 필드가 있는 색상 에디터(브라우저의 네이티브 색상 입력에는 알파가 없어 사용하지 않는다); 확대/축소 가능한 그리드, 드래그 가능한 키와 탄젠트 핸들, 탄젠트 모드(Free, Auto, Linear, Constant, ClampedAuto — 키를 우클릭하거나 선택을 사용), 분리(broken)와 가중치 토글, 숫자 필드, 시작 전/종료 후 래핑, 선형 / 이즈 인-아웃 / 상수 프리셋이 있는 커브 에디터; 드래그 가능한 색상 키와 알파 키(각각 최대 8개), 모드 선택, 색 공간 선택이 있는 그라디언트 에디터. 모든 변경은 WebAssembly 코어를 거쳐 라운드트립된다 — 파싱, 탄젠트 재계산, 샘플링은 JavaScript가 아니라 C#이다 — 그리고 커밋되는 텍스트는 에디터가 쓰는 것과 바이트 단위로 동일한 정규 형태다. 변경은 만드는 즉시 커밋된다 — 에디터의 네이티브 필드와 정확히 마찬가지다: 에디터가 열려 있는 동안 셀, 캔버스, Problems가 함께 따라오며, 하나의 편집 세션은 몇 번을 바꾸든 하나의 undo 단계로 합쳐지고, 에디터를 다시 열면 새로운 단계가 시작된다. Apply는 없다 — 유일한 버튼은 닫기다(Esc로도 닫히며, 색상 에디터에서는 Esc가 세션 이전 값도 함께 복원한다); 되돌리기는 Ctrl+Z다. 아무것도 바꾸지 않고 열었다 닫으면 아무것도 스테이징되지 않는다. 선택적 컬럼은 비어 있으면 를 보여주고 우클릭 메뉴에서 **지우기(기본값)**를 제공한다; 셋 중 하나의 List<>는 추가 / 제거 / 순서 변경 / 편집 제스처가 각각 하나의 undo 단계인 칩 목록이다. 미리보기 띠는 에디터의 테이블과 마찬가지로 표준 행 높이 안에 들어간다. 캔버스의 값 행도 동일한 컴포넌트를 사용한다.
구조 편집컬럼 추가 / 제거 / 이동, 네 필드 컬럼 폼, 탭 이름 변경(상호 교환과 순환 포함; xlsx 탭은 차단됨), 시트 생성 / 삭제, 활성 설정 드롭다운 — 각각 하나의 undo 단계로 스테이징된다. Enum 정의 시트도 같은 방식으로 저작된다 — 새 enum 정의, 이름 변경(참조하는 모든 @type이 같은 배치에서 재작성됨), 삭제, 컬럼 순서 변경, 기반 타입과 설명 편집까지 — 에디터와 동일한 규칙 아래에서다. 판정을 내리는 코드가 같기 때문이다.
시트 전체 검색사이드바의 All 항목은 모든 시트의 id와 필드 값을 부분 일치로 검색하며, 시트 · 키 · 필드 · 값을 페이지당 50행씩 나열한다 — 에디터와 동일한 매칭, 정렬, 페이지 크기다. 스테이징된 편집을 포함해 세션이 지금 보여주는 값을 읽으므로, 방금 바꾼 값도 즉시 찾아진다. 결과를 더블클릭하거나 — 또는 선택한 뒤 Enter를 누르면 — 일치한 셀이 선택된 채로 그 시트가 열린다; 검색이 세션 자체를 읽으므로 모든 결과로 이동할 수 있다.
현지화현지화 시트는 여기서도 평범한 시트다 — 작성, 검증, 로케일별 커버리지까지 포함해서. 데이터 시트의 LocRef 셀은 그 항목의 소스 로케일 텍스트를 인라인으로 보여주고 다른 참조와 동일한 키 피커를 연다; 빈 셀에 입력하면 에디터와 정확히 동일하게 키·소스 텍스트·참조를 하나의 undo 단계로 민팅한다. 로케일 렌즈는 보이는 로케일 컬럼을 토글하고(표시 전용), 번역 워크북은 선택한 로케일을 xlsx로 export하고 반환된 파일을 로케일 컬럼 전용 병합으로 재임포트한다. Unity Localization StringTable 동기화는 에디터에 남는다.
레코드 캔버스레코드의 재료와 소비자는 와이어를 드래그해 연결하는 카드다 — 캔버스는 에디터와 동일한 순수 레이아웃, 배선, 편집 체인 코드를 재사용하므로, 픽셀 계산은 JS 그래프 라이브러리가 아니라 C#에 산다. 카드는 테이블과 동일한 프로젝션을 보여준다: 이번 배치에서 스테이징된 컬럼은 이미 대기 중으로 표시된 채 카드 위에 서 있고, 스테이징된 이름 변경은 원래 값 위에 새 이름을 보여주며, 스테이징된 삭제는 여기서도 그 행을 제거한다.
출력시트별 TSV / CSV 다운로드, 선택한 모든 시트를 하나의 파일에 담는 .xlsx 워크북 다운로드(워크북 규칙이 담을 수 없는 시트 이름은 조정되어 보고되며, 결코 조용히 개명되지 않는다), 그리고 Google 시트 읽기/쓰기 — push할 때마다 시트의 데이터 검증 드롭다운이 자동으로 갱신된다(아래 참고).

비슷한 정도가 아니라 동일하게 만들어졌다

웹 UI는 공유되는 로직까지 포함해, 의도적으로 에디터 내 데이터 스튜디오와 일치한다.

  • 답이 공유된다. 헤더 조건(pending / edited), diff 모달, 프로젝션 조립 순서는 모두 공유되는 C#이므로, 두 표면은 동일한 질문에 동일한 답을 준다.
  • 문장이 공유된다. 웹이 스튜디오 화면을 그대로 반영하는 곳에서는 스튜디오 자신의 문자열 키를 사용하며, 그 값은 다시 타이핑되는 대신 빌드 시점에 에디터의 언어 테이블에서 생성된다. 두 표면 모두가 보여주는 문장을 추가하는 것은 Lang*.cs의 키 하나로 끝나는 일이며, 어긋남은 단지 권장되지 않는 정도가 아니라 구조적으로 불가능하다. 두 가지는 의도적으로 분리되어 있으며, 빌드 시점 가드가 그 경계를 강제한다:
    • 진짜로 웹 전용인 문자열은 웹 카탈로그에 남는다;
    • 진단 정보와 리포트는 WebAssembly 코어가 렌더링하며, UI에서 다시 표현되지 않는다.
  • 모습이 공유된다. 그래프와 시트는 에디터의 캔버스 고정 동작을 그대로 반영하는 크기 조절 가능한 분할 패널에 함께 표시되며, 모든 색상은 하드코딩된 팔레트가 아니라 단일 진실 공급원인 에디터의 .aw-root 토큰 집합에서 나온다.
  • 컴포넌트 키트가 공유된다. UI는 자체 제작한 aw 키트 위에 구축되며, 그 클래스 이름·토큰·팝업 배치는 에디터의 USS 및 정본 HTML 목업과 일대일로 일치한다. 이제 하나의 목업이 두 개의 포트 — 웹 앱과 UIToolkit 에디터 — 를 함께 먹여 살리며, 웹이 홀로 표류하지 않는다. web/Docs/ui-parity.md는 스튜디오가 바뀔 때 웹이 이를 어떻게 따라가는지 기록한다.
  • 셀 에디터가 공유된다. 셀이 어떤 위젯을 받을지는 코어의 셀 에디터 힌트가 결정한다 — 팩의 등록이 먼저, 그다음 내장 테이블(BuiltinCellEditorHints) — 그래서 ColorPicker, CurveEditor, GradientEditor 아키타입을 선언하는 플러그인 타입은 여기서도 Unity에서 여는 것과 같은 에디터를 열며, 두 호스트가 같은 타입 이름에 대해 서로 다른 위젯을 고를 수 없다.
  • 캔버스 패리티. 드래그 앤 드롭은 에디터와 완전한 패리티에 도달했다: 가운데 드래그 또는 Alt 드래그로 팬, 빈 공간 계단식 메뉴, 와이어와 노드 인스펙터, 선택된 와이어 고스팅, 확인 대화상자를 동반한 종착점 삭제. 확대/축소 범위는 에디터의 것(25–200%)이며, 카드 수치는 JavaScript의 두 번째 사본이 아니라 코어에서 나온다.
  • 타입 목록이 공유된다. @type 드롭다운은 파서 레지스트리가 실제로 아는 것을 제공하며, 에디터가 그룹화하는 것과 정확히 동일하게 그룹화한다: 내장 스칼라(Color, AnimationCurve, Gradient 포함), RecordId / IntId 키, RecordId@TabIntId@Tab 참조, Enum<…>, AssetRef@Group, 그리고 로드된 플러그인이 등록한 모든 타입이나 래퍼. 그래서 새 타입을 등록하는 플러그인은 웹 쪽 변경 없이도 브라우저에 나타난다. (WebAssembly 코어가 부팅을 마치기 전, 첫 페인트는 네 가지 내장 스칼라로 대체된다.)

언제 어느 쪽을 쓸지

  • 어디서든 빠르게 작성·검증·시트를 편집하려면, 그리고 Unity를 열지 않는 팀원을 위해서는 브라우저를 사용한다.
  • 생성된 C# 클래스와 베이크된 ScriptableObject가 필요하다면 — 임포트, 코드젠, 베이크 단계 — Unity를 사용한다.

둘 다 같은 시트에 기록하므로, 한쪽에서 만든 변경은 다음 읽기에서 다른 쪽이 그대로 받는다.

관련 페이지