HTML DSL
Survey는 survey-specific tag와 attribute를 포함한 HTML로 작성합니다. Parser는 model을 만들고 canonical rule을 검증하며, 지원되는 compatibility path만 보존합니다.
최소 문항
<question type="single" name="Q1" required>
<description>Which option do you prefer?</description>
<options>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
</options>
</question>
이름 규칙
| Object | Canonical attribute | Notes |
|---|---|---|
| Question | name | 전역 unique해야 하며 system name과 충돌할 수 없습니다. |
| Section | name | Section name은 jump target이 될 수 있습니다. |
| Loop | name | Loop item은 {{item}} placeholder를 사용할 수 있습니다. |
| Option or attribute | value | Signed integer code. Negative value도 유효합니다. |
타입 경계
| Type | Purpose |
|---|---|
single | 하나의 option answer. attribute로 반복될 수 있습니다. |
multi | 여러 option answer. attribute로 반복될 수 있습니다. |
ranking | Click-to-rank answer. Q1_rk1 같은 flat rank slot에 저장됩니다. |
rating | Scale, star, semantic rating semantic. |
nps | Net Promoter Score scale. |
text | Short 또는 long open text. |
number | Numeric answer. semantic mode와 visual display style이 분리됩니다. |
date | Date 또는 date range. |
video | Uploaded video media gate입니다. 선택적 require-full-watch="true"는 재생이 끝날 때까지 진행을 막습니다. |
glb | model-viewer로 렌더링되는 uploaded GLB/3D media gate입니다. |
dropdown, multi-dropdown | Dropdown style selection입니다. Menu는 이후 문항을 밀지 않고 overlay로 열리며, mobile은 닫기 버튼이 있는 full-screen menu를 사용합니다. |
info | Information-only block. |
run-code | Flow/code execution block이며 일반 answer field를 만들지 않습니다. |
렌더링 class 계약
Rendered DSL element는 question, question-desc, question-options, option-1, option--2, option-input, question-attributes, attr-1 같은 deterministic class를 받습니다. 작성자가 입력한 class attribute는 presentation class로 append되며 logic이나 answer schema를 바꾸지 않습니다.
Template Expression
Survey HTML DSL의 text와 identity가 아닌 attribute는 {{ ... }} 안에서 JavaScript 스타일 Template Expression을 사용할 수 있습니다. Survey Link 렌더링 시점과 Run generate에서 if expression과 동일한 제한 logic context로 평가됩니다.
<script name="helpers">
function answerConvert(value) {
return String(value || "").trim().toUpperCase();
}
</script>
<question type="number" name="BUDGET"
min="{{ SEGMENT.val() === 1 ? 10 : 5 }}"
max="{{ SEGMENT.val() === 1 ? 60 : 45 }}"
step="{{ SEGMENT.val() === 1 ? 5 : 1 }}"
pre-text="{{ answerConvert(SEGMENT.val()) }}">
<description>Hello {{ answerConvert(NAME.val()) || "SHOPPER" }}</description>
</question>
규칙:
- 빈
{{ }}는 hard authoring error이며 Survey HTML Mode와 지원되는 GUI field에서 정확한 expression token만 빨간 오류 highlight로 표시됩니다. - 비어 있지 않은 expression token은 연한 violet authoring highlight로 표시됩니다. HTML Mode에서 fixed enum/dropdown attribute를 Template Expression으로 작성한 경우 GUI Mode의 해당 control은 disabled
EXP로 표시되고 HTML Mode에서만 수정할 수 있습니다. name,type,value같은 question/option/attribute/group identity attribute는 response 저장/export name을 안정적으로 유지해야 하므로 dynamic하게 만들 수 없습니다.<survey-config>attribute는 static-only라서 Template Expression을 사용할 수 없습니다.min,max,step,pre-text,post-text,option-columns,option-columns-min,expanded,hide-group,required, display 관련 attribute처럼 identity가 아닌 attribute는 dynamic하게 사용할 수 있습니다.- Script helper 호출은 같은 Survey HTML DSL의
<script>안에 제한된 top-level function 또는registerSurveyHelper(...)로 선언된 함수만 허용됩니다. - Runtime expression 오류는 가능한 범위에서 fail-open으로 처리되고, 중복 제거 후 TEST console과 Run generate diagnostics에 기록됩니다.
Dynamic number/date attribute는 평가 후 다시 검증합니다. 동적 min / max 조합이 응답 불가능한 상태가 되면 respondent를 막지 않고 응답 가능한 상태로 fallback하며 로그를 남깁니다.
그룹
<group>은 <options>, <attributes>, <share-list>, loop <items> 안에서 option/attribute/item 구조를 중첩 관리할 때 사용합니다.
<options order="random" group-order="random">
<group name="CORE" if="Q1.o1" order="asc" group-order="rotation" fix>
<label>Core brands</label>
<option value="1">Brand A</option>
<option value="2">Brand B</option>
</group>
</options>
Group attribute:
| Attribute | Meaning |
|---|---|
if | group과 모든 descendant에 적용되는 expression-only visibility 조건입니다. Authoring syntax error는 hard error이고 runtime error는 fail-open으로 group을 보여줍니다. |
order | group 내부 leaf item의 local order입니다. options/attributes/share-list group은 random, rotation, asc, desc를 지원하고 loop item group은 random, rotation만 지원합니다. |
group-order | 해당 group의 direct child group만 정렬합니다. |
fix | parent group-order가 적용될 때 이 group의 authored 위치를 고정합니다. |
Container-level order가 기본값이며, group-level order / group-order는 해당 group 내부에서만 override합니다. 숨겨진 group은 required validation과 auto-punch/skip logic 전에 descendant가 제거됩니다. <insert-list>로 삽입된 share-list group도 runtime에서는 reusable group tree로 확장되지만 authored source는 <insert-list> reference를 유지합니다.
Quota DSL
Quota HTML은 하나의 <quota-source> root와 하나 이상의 <quota-sheet> child로 구성합니다. Sheet id는 Workbench tab, Excel sheet name, import validation, runtime quota checkpoint에서 사용하는 canonical quota name입니다.
<quota-source>
<quota-sheet id="gender_age" title="Gender × Age" flat-view="false">
<columns>
<leaf id="male" label="Male" if="Q1.o1" />
<leaf id="female" label="Female" if="Q1.o2" />
</columns>
<rows>
<leaf id="age_18_34" label="18-34" if="AGE.value >= 18 && AGE.value <= 34" />
<leaf id="age_35_plus" label="35+" if="AGE.value >= 35" />
</rows>
</quota-sheet>
<quota-sheet id="concept_quota" title="Concept quota" priority="true" priority-count="1">
<rows>
<leaf id="r1" label="Concept A" if="true" />
<leaf id="r2" label="Concept B" if="true" />
<leaf id="r3" label="Concept C" if="true" />
</rows>
</quota-sheet>
</quota-source>
Quota rule:
| Attribute or tag | Meaning |
|---|---|
<quota-source> | 전체 quota HTML DSL의 필수 root입니다. |
<quota-sheet> | 하나의 quota table입니다. id는 unique하고 programming-style이어야 하며 Excel sheet name으로 그대로 쓰이므로 31자 이하여야 합니다. 중복 sheet id는 hard error입니다. |
flat-view="true" | 해당 sheet를 stacked flat table로 렌더링합니다. 생략하거나 false면 pivot mode입니다. |
priority="true" | 한 응답자가 여러 non-full cell에 동시에 매칭될 때 부족 셀 우선 할당을 활성화합니다. 생략하거나 false면 기본값인 count-all 동작입니다. |
priority-count="1" | priority="true"일 때 필수입니다. 1 이상 정수여야 하며 sheet 전체 cell 수를 초과할 수 없습니다. |
priority-type="count" / priority-type="ratio" | 선택 priority mode입니다. 기본값은 count이며 현재 complete sample 수가 가장 낮은 cell을 선택합니다. ratio는 current/limit 채움 비율이 가장 낮은 cell을 선택하며, limit이 blank/null인 cell이 있으면 hard error입니다. |
<columns> / <rows> | 작성자가 정의한 dimension입니다. Flat mode에서는 column dimension을 먼저 row 방향으로 펼치고 이후 row dimension을 붙입니다. |
<leaf> | count 가능한 quota leaf입니다. Canonical leaf attribute는 id, label, if입니다. |
question, code, type, allocation은 canonical quota leaf attribute가 아닙니다. Single/Multi 응답 구분은 leaf schema가 아니라 if expression이 매칭되는 방식으로 처리합니다. if="true"는 유효하며, quota checkpoint에 도달한 모든 응답자가 해당 leaf에 매칭될 수 있다는 뜻입니다. 이를 priority assignment와 함께 쓰면 visible question 없이 concept/cell을 자동 균등 배정할 수 있습니다. GUI에서 새로 만드는 manual row leaf id는 r{num}, manual column leaf id는 c{num}이며 기존 imported/picker id는 변경하지 않습니다. Excel import template은 visible-structure only입니다. 사용자는 Limit 값만 수정할 수 있으며 label/header/schema 편집은 HTML DSL을 갱신하지 않고 import 오류로 거부됩니다.
Quota runtime/export 동작
Survey Link가 <quota> checkpoint에 도달하면 해당 source에 설정된 cell을 persisted quota snapshot에서 평가하며, authored JavaScript가 직접 database를 조회하지 않습니다. 기본값인 priority="false"에서는 매칭된 non-full cell을 모두 assigned/counted cell로 저장하며, 이미 full인 cell은 counting에서 제외합니다. priority="true"이면 priority-count 개수만큼 가장 부족한 matched non-full cell만 assigned/counted cell로 저장합니다. priority-type="count"는 현재 complete sample 수를 비교하고, priority-type="ratio"는 current/limit 채움 비율을 비교하며 limit이 blank/null인 cell이 있으면 hard error입니다. 동일하게 부족한 eligible cell이 여러 개면 랜덤 선택될 수 있습니다. 매칭된 required cell이 모두 full이면 응답은 quotafull로 종료됩니다. Checkpoint가 활성 상태인데 할당/매칭 가능한 cell이 하나도 없으면 응답은 screenout으로 종료되고 quota 전용 screenout_status가 저장됩니다. 이 값은 quota source의 최초 등장 순서대로 901부터 시작하며 label은 NOT_QUOTA:{quota_name} 형식입니다.
Response export에는 system column 뒤, question column 앞에 trusted quota label column이 추가됩니다.
| Column | Value |
|---|---|
QUOTA_SHEET_{quota_name} | 저장된 quota source definition에서 가져온 assigned/counted quota cell label의 JSON array text입니다. 예: ["Concept A"], ["Concept A","Concept B"]. assigned cell이 없거나 trusted quota definition이 없으면 blank/null입니다. Raw matched-but-full cell과 spoofed QUOTA_SHEET_* answer는 이 column에 export하지 않습니다. |
QUOTA_SHEET_ prefix는 quota export metadata 전용 예약 namespace이므로 authored question, screen-out, share-list, raw-control response name으로 사용할 수 없습니다. 이 array는 tabular export에서 text로 저장되어 CSV, XLSX, JSON projection이 동일한 값 계약을 유지합니다.
Survey config question-name badge
<survey-config />는 Survey Link의 question badge 표시 방식을 제어할 수 있습니다.
<survey-config show-name="none" page-animation="fade" test-back-button="true" />
| Attribute | Values | Default | 의미 |
|---|---|---|---|
show-name | none, name, index | none | none은 badge를 숨깁니다. name은 question 변수명을 표시합니다. index는 현재 visible runtime 순서를 1부터 표시합니다. |
show-name="index"는 실제 렌더링되는 visible question sequence 기준입니다. 숨겨진 문항은 번호를 차지하지 않으므로 branch path에 따라 같은 문항의 index가 달라질 수 있습니다. TEST Audit Mode에서 show-name="name"이면 question name을 중복 표시하지 않고, none/index일 때는 QA를 위해 authored question name을 볼 수 있습니다.
Terminal redirect DSL
<redirect>는 optional Survey Link terminal-routing block입니다. 생략하면 built-in end page를 유지하고 redirectGroup을 null로 저장합니다.
<redirect>
<group title="Default Group" default />
<group name="v2" title="Partner list">
<route url="https://example.com/complete" if="$status.complete" />
</group>
</redirect>
핵심 규칙:
<redirect>가 있으면 정확히 하나의 group이default입니다. Group이 하나뿐이면 저장 시 자동으로default가 serialize됩니다.- Group name은 optional이지만, named group은 unique하고 programming-style이어야 합니다. Unnamed group은 하나만 허용됩니다.
- Route
url과if는 필수이며, 같은 group 안의 중복 route condition은 hard error입니다. - Path selection이 query selection보다 우선합니다.
/s/{surveyId}/v1은?redirectGroup=v2보다 우선합니다. - Named default group은 bare Survey Link URL을 named path로 canonicalize하고 해당 group name을 저장합니다.
- Invalid redirect group selection 또는 unsafe final URL은 response persistence 전에 progression을 차단합니다.
전체 작성 계약은 종료 리다이렉트를 참고합니다.
Option columns와 group display
지원 type은 option-columns, option-columns-min으로 반응형 option/attribute grid를 요청할 수 있습니다.
<question type="single" name="REGION" option-columns="3" option-columns-min="2">
<description>Where do you shop most often?</description>
<options>
<option value="1">North</option>
<option value="2">South</option>
<option value="3">East</option>
</options>
</question>
| Attribute | 지원 위치 | 의미 |
|---|---|---|
option-columns | single, multi, ranking, number, text, date question 및 지원되는 <attr> row | Desktop column count입니다. 생략/빈 값이면 기본 1열입니다. |
option-columns-min | 동일 | Mobile/minimum column count입니다. option-columns가 없으면 이 값이 모든 breakpoint의 effective column count가 됩니다. |
규칙:
- 값은 1 이상의 정수여야 합니다.
- 둘 다 있으면
option-columns-min은option-columns보다 클 수 없습니다. - effective column count는 렌더링되는 item 수를 초과할 수 없습니다.
single,multi,ranking에서는 option card에 적용되며 attr-level 값이 question-level 값을 row별 override합니다.number,text,date에서는 반복 attribute input row 배치에 적용됩니다.<group>panel은 항상 전체 row를 차지하고, group 내부 option body에 grid가 적용됩니다.
single, multi, ranking은 grouped option/attribute accordion도 지원합니다.
<question type="ranking" name="FEATURE_RANK" group-display="accordion" expanded="true" option-columns="2">
<description>Rank the most useful features.</description>
<options>
<group name="DISCOVERY" expanded="true">
<label>Discovery</label>
<option value="1">Search</option>
<option value="2">Recommendations</option>
</group>
<group name="CHECKOUT">
<label>Checkout</label>
<option value="3">Payment speed</option>
<option value="4">Delivery clarity</option>
</group>
</options>
</question>
| Attribute | Values | Default | 의미 |
|---|---|---|---|
group-display | default, accordion | default | <group> panel을 기본형 또는 접힘/펼침 accordion으로 렌더링합니다. 미지원 question type에서는 hard error입니다. |
<question expanded> | true, false | false | accordion group의 기본 open 상태입니다. group-display="default"이면 무시됩니다. |
<group expanded> | true, false | false | 해당 group의 open 상태를 question-level 설정보다 우선 적용합니다. |
<question hide-group> | true, false | false | group 의미, order, shuffle, export 동작은 유지하고 Survey Link의 group chrome만 숨깁니다. |
<group hide-group> | true, false | question 값 | question-level hide-group을 override합니다. 명시적 false는 question이 숨김 기본값이어도 해당 group chrome을 다시 표시합니다. |
Group accordion status icon은 해당 group 안에 응답이 존재함을 나타내며 page-level navigation/submit 동작은 변경하지 않습니다.
Question type별 주요 attribute
| Type | 주요 attribute |
|---|---|
single | required, if, attribute가 있을 때 `display-style=“carousel |
multi | required, min, max, display-style, option-columns, option-columns-min, group-display, expanded, option sa, option oe. |
ranking | required, min, max, rank-all, display-style, option-columns, option-columns-min, group-display, expanded; Q1_rk1 같은 rank slot에 저장됩니다. |
rating | `mode=“scale |
nps | 선택적 <left>, <center>, <right>, attribute가 있을 때 display-style. |
text | `mode=“short |
number | `mode=“int |
date | minDate, maxDate, range-answer="true", 반복 attribute용 option-columns. |
video | source, 선택적 require-full-watch="true"; uploaded video asset gate 전용이며 response/export column이 없습니다. |
glb | source; uploaded GLB asset gate 전용이며 response/export column이 없습니다. |
dropdown, multi-dropdown | Dropdown UI selection입니다. option columns와 group accordion은 지원하지 않습니다. |
info | Description/comment 전용이며 response value가 없습니다. |
run-code | 제한 flow code block입니다. response value가 없고 hidden 상태에서는 jump target이 아닙니다. |
Custom HTML controls
Raw HTML은 framework <question> block과 함께 또는 단독으로 사용할 수 있습니다. Bindable native control은 unique name을 가진 input, select, textarea입니다. output, meter, progress, hidden/file input, name 없는 response input은 여전히 hard error입니다.
<style>
.custom-card { border: 1px solid #dcd7d3; padding: 1rem; }
</style>
<script name="helpers">
function normalizeLabel(value) { return String(value || '').trim(); }
</script>
<div class="custom-card">
<label>Email <input type="email" name="RAW_EMAIL" required /></label>
<label>Channel
<select name="RAW_CHANNEL"><option value="app">App</option></select>
</label>
<label>Feedback <textarea name="RAW_FEEDBACK" minlength="10"></textarea></label>
</div>
Raw <form> tag는 custom HTML survey surface에서 허용되지만, 최종 response submission과 persistence는 Vibe Survey가 소유합니다. Framework question name과 raw control name은 전역 unique해야 합니다.