@morit/cli 1.3.0 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -5
- package/assets/plugin_contract.json +33 -8
- package/bin/morit.js +0 -0
- package/package.json +1 -1
- package/src/cli.js +5 -0
- package/src/preview.js +254 -17
- package/src/workspace.js +218 -19
- package/assets/docs/README.md +0 -107
- package/assets/docs/ai-response-and-timeline.md +0 -203
- package/assets/docs/ai-skill-and-docx-workflow.md +0 -83
- package/assets/docs/app-builder.md +0 -56
- package/assets/docs/authentication.md +0 -140
- package/assets/docs/components.md +0 -216
- package/assets/docs/design-tokens-responsive.md +0 -171
- package/assets/docs/docs-index.json +0 -94
- package/assets/docs/examples-notion.md +0 -83
- package/assets/docs/examples-school-life.md +0 -79
- package/assets/docs/getting-started.md +0 -132
- package/assets/docs/information-hierarchy.md +0 -81
- package/assets/docs/instances-and-connectors.md +0 -93
- package/assets/docs/lifecycle-and-api.md +0 -169
- package/assets/docs/local-cli.md +0 -125
- package/assets/docs/manifest.md +0 -234
- package/assets/docs/packaging-and-testing.md +0 -121
- package/assets/docs/permissions-and-data.md +0 -149
- package/assets/docs/platform-compatibility.md +0 -62
- package/assets/docs/plugin-storage.md +0 -175
- package/assets/docs/project-structure.md +0 -102
- package/assets/docs/remote-mcp.md +0 -158
- package/assets/docs/school-life-privacy.md +0 -55
- package/assets/docs/screens-layout-navigation.md +0 -95
- package/assets/docs/sdk-and-mcp.md +0 -199
- package/assets/docs/tool-and-skill.md +0 -172
- package/assets/docs/troubleshooting.md +0 -121
- package/assets/docs/ui-extensions.md +0 -75
- package/assets/docs/ui-runtime-v2.md +0 -343
- package/assets/docs/verification.md +0 -133
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
# 기본·커스텀 컴포넌트
|
|
2
|
-
|
|
3
|
-
UI Runtime v2 node는 공통으로 `type`, 선택적 `id`, `props`, `children`,
|
|
4
|
-
`visible_when`을 가집니다. `action`은 명확한 탭 영역을 제공하는 `surface`, `card`, `button`,
|
|
5
|
-
`chip`에서만 허용됩니다. 구조·표시·입력 node에 action을 붙이면 Host, CLI, MCP 검증이 모두
|
|
6
|
-
거부하므로 버튼이나 탭 가능한 surface로 감싸세요. Host가 Material 3 widget으로 렌더링하며
|
|
7
|
-
플러그인은 Flutter widget이나 HTML을 전달하지 않습니다.
|
|
8
|
-
|
|
9
|
-
아래 모든 node는 현재 Android Host에서 지원됩니다. iOS·Desktop도 같은 JSON 계약을 재사용하도록
|
|
10
|
-
설계되어 있지만 Host 구현과 실기기 검증 전에는 지원 완료로 표시하지 않습니다. 플랫폼별 상태는
|
|
11
|
-
[Android, iOS, Desktop 호환](platform-compatibility.md)을 참고하세요.
|
|
12
|
-
|
|
13
|
-
## 레이아웃 컴포넌트
|
|
14
|
-
|
|
15
|
-
| type | 목적 | 주요 props | 제약 |
|
|
16
|
-
|---|---|---|---|
|
|
17
|
-
| `column` | 세로 읽기 흐름 | `spacing`, `padding`, axis 정렬 | children 최대 32개 |
|
|
18
|
-
| `row` | 짧은 항목의 가로 배치 | `spacing`, `stack_at`, axis 정렬 | 좁은 화면 전환을 정의 |
|
|
19
|
-
| `wrap` | chip·필터의 자동 줄바꿈 | `spacing`, `alignment` | 의미 순서는 children 순서 |
|
|
20
|
-
| `grid` | 같은 중요도의 반복 카드 | `columns`, `min_item_width`, `spacing` | 열 수보다 최소 너비 우선 |
|
|
21
|
-
| `stack` | 겹치는 장식·badge 배치 | `alignment`, `clip` | `positioned`의 직접 부모 |
|
|
22
|
-
| `positioned` | stack 안의 위치 지정 | `left`, `top`, `right`, `bottom` | 정확히 한 child, offset 하나 이상 |
|
|
23
|
-
| `scroll` | 제한된 영역의 스크롤 | `scroll_direction`, `shrink_wrap` | 정확히 한 child, 중첩 스크롤 자제 |
|
|
24
|
-
| `padding` | 한 subtree의 내부 여백 | `padding` | 정확히 한 child |
|
|
25
|
-
| `center` | 한 subtree 정렬 | `alignment` | 정확히 한 child |
|
|
26
|
-
| `expanded` | row/column의 남은 공간 | `flex` | row/column의 직접 child, child 하나 |
|
|
27
|
-
|
|
28
|
-
`row`의 `stack_at`은 0~1200이고, 0은 자동 세로 전환을 끕니다. `grid`의
|
|
29
|
-
`min_item_width`는 96~600입니다. `expanded.flex`는 1~24입니다.
|
|
30
|
-
|
|
31
|
-
## Surface와 구조
|
|
32
|
-
|
|
33
|
-
| type | 목적 | 주요 props | children |
|
|
34
|
-
|---|---|---|---|
|
|
35
|
-
| `surface` | 안전한 커스텀 시각 surface | 크기, 여백, 색 token, border, elevation, opacity | 1개 이상 필수 |
|
|
36
|
-
| `card` | 독립된 요약·선택 영역 | `title`, `subtitle`, `tone`, `action` | 선택 |
|
|
37
|
-
| `section` | 제목이 있는 정보 그룹 | `title`, `subtitle`, `spacing` | 선택 |
|
|
38
|
-
| `divider` | 같은 흐름 안의 구분 | `color`, `margin` | 없음 |
|
|
39
|
-
| `spacer` | 제한적인 빈 공간 | `size`, `width`, `height` | 없음 |
|
|
40
|
-
|
|
41
|
-
`surface`가 Runtime v2의 커스텀 컴포넌트 경계입니다. 허용된 primitive와 Material token을 조합할
|
|
42
|
-
수 있지만 실행 코드, HTML, CSS, WebView, native view, 임의 shader는 넣을 수 없습니다. surface
|
|
43
|
-
props는 `spacing`, `padding`, `margin`, 크기 제약, `alignment`, 색상, border,
|
|
44
|
-
`elevation`, `opacity`, `clip`, `enabled`, `tooltip`, semantics로 제한됩니다.
|
|
45
|
-
|
|
46
|
-
```json
|
|
47
|
-
{
|
|
48
|
-
"type": "surface",
|
|
49
|
-
"props": {
|
|
50
|
-
"padding": {"horizontal": 16, "vertical": 12},
|
|
51
|
-
"background_color": "primary_container",
|
|
52
|
-
"foreground_color": "on_primary_container",
|
|
53
|
-
"border_radius": 20
|
|
54
|
-
},
|
|
55
|
-
"children": [
|
|
56
|
-
{"type": "text", "props": {"text": "오늘 일정 3개", "style": "heading"}}
|
|
57
|
-
]
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## 텍스트와 데이터 표시
|
|
62
|
-
|
|
63
|
-
| type | 목적 | 주요 props |
|
|
64
|
-
|---|---|---|
|
|
65
|
-
| `text` | 제목·본문·라벨 | `text`, `style`, `align`, `max_lines`, `color` |
|
|
66
|
-
| `icon` | Host icon token | `icon`, `size`, `color`, `semantic_label` |
|
|
67
|
-
| `image` | package 또는 capability 결과 이미지 | `asset`/`url`, `fit`, 크기, `semantic_label` |
|
|
68
|
-
| `avatar` | 사람·계정·공간의 작은 이미지 | `asset`/`url`, `size`, `semantic_label` |
|
|
69
|
-
| `badge` | child 위의 짧은 상태 표식 | child 하나, `alignment` |
|
|
70
|
-
| `metric` | 라벨·큰 값·보조 문구 | `label`, `value`, `supporting`, `tone` |
|
|
71
|
-
| `progress` | 결정/비결정 진행률 | `label`, `value` |
|
|
72
|
-
| `empty` | 빈 데이터와 다음 행동 안내 | `title`, `supporting`, `icon` |
|
|
73
|
-
|
|
74
|
-
`image`와 `avatar`는 source를 정확히 하나만 가집니다.
|
|
75
|
-
|
|
76
|
-
- `asset`: package 안의 `assets/` GIF/JPEG/PNG/WebP 경로
|
|
77
|
-
- `url`: `{{data.<source>...}}` 또는 목록 안의 `{{item...}}` 전체 binding 하나
|
|
78
|
-
|
|
79
|
-
literal 외부 URL, `data:`, `file:`, `javascript:`는 사용할 수 없습니다. `url` binding에는 capability
|
|
80
|
-
결과의 이미지 URL만 넣습니다. 이 이미지를 쓰는 package는 Manifest `permissions`에 `network`를
|
|
81
|
-
요청해야 하며 활성 Instance에도 `network` grant가 있어야 합니다.
|
|
82
|
-
|
|
83
|
-
앱은 binding으로 해석한 URL을 직접 요청하지 않습니다. Host가 인증된 이미지 proxy를 통해 공개
|
|
84
|
-
HTTPS URL만 가져오며 DNS/IP SSRF 검사와 redirect 차단을 적용합니다. 응답은 PNG/JPEG/GIF/WebP 중
|
|
85
|
-
하나여야 하고 MIME type, magic byte, 실제 이미지 포맷이 일치해야 합니다. 최대 크기는 512 KiB,
|
|
86
|
-
가로·세로는 각각 4096 px 이하입니다. 정적 이미지는 1 frame, 애니메이션은 최대 128 frame이며
|
|
87
|
-
`width × height × frame 수`로 계산한 frame 합산 pixel이 16,000,000 이하이어야 합니다. 조건을
|
|
88
|
-
통과하지 못하면 해당 이미지에만 오류 fallback을 표시하며 다른 UI와 capability 결과는 유지합니다.
|
|
89
|
-
|
|
90
|
-
중요한 이미지에는 `semantic_label`을 쓰고 순수 장식 이미지는 `exclude_semantics: true`를 사용합니다.
|
|
91
|
-
|
|
92
|
-
### Icon token
|
|
93
|
-
|
|
94
|
-
`icon`, App bar action, navigation item의 `icon`과 `selected_icon`은 아래 Host token만 사용합니다.
|
|
95
|
-
임의 Material icon 이름이나 code point는 허용하지 않습니다. 목록에 없는 값은 Host, CLI, MCP와 앱
|
|
96
|
-
schema parser가 거부하며 다른 아이콘으로 조용히 바꾸지 않습니다. 모든 token은 현재 Android
|
|
97
|
-
Host에서 같은 의미의 Material 3 아이콘으로 표시되고, iOS·Desktop Host도 이름과 의미를 그대로
|
|
98
|
-
유지해야 합니다.
|
|
99
|
-
|
|
100
|
-
| 목적 | 지원 token |
|
|
101
|
-
|---|---|
|
|
102
|
-
| 추가·편집·삭제 | `add`, `edit`, `delete`, `close`, `check` |
|
|
103
|
-
| 이동·메뉴 | `arrow_back`, `arrow_forward`, `menu`, `more`, `home`, `home_filled` |
|
|
104
|
-
| 파일·공유 | `file`, `folder`, `description`, `download`, `upload`, `share`, `link` |
|
|
105
|
-
| 일정·데이터 | `calendar`, `clock`, `event`, `list`, `analytics` |
|
|
106
|
-
| 상태·안내 | `info`, `help`, `error`, `favorite`, `inbox`, `sparkle` |
|
|
107
|
-
| 사람·기능 | `person`, `extension`, `search`, `settings`, `refresh` |
|
|
108
|
-
| 알림 | `notification`, `notifications`, `bell` |
|
|
109
|
-
| 교육·급식 | `school`, `education`, `meal` |
|
|
110
|
-
| 기존 package 호환 alias | `arrow`, `schedule`, `school.settings` |
|
|
111
|
-
|
|
112
|
-
호환 alias는 각각 `arrow_forward`, `clock`, `settings`와 같은 의미입니다. 새 화면은 의미가 더
|
|
113
|
-
명확한 기본 token을 우선 사용하되 기존 서명 package의 alias도 계속 렌더링됩니다. 아이콘만 있는
|
|
114
|
-
action에는 반드시 `label`, 일반 `icon` node에는 `semantic_label`을 함께 제공합니다.
|
|
115
|
-
|
|
116
|
-
## 행동과 입력
|
|
117
|
-
|
|
118
|
-
| type | 목적 | 주요 props |
|
|
119
|
-
|---|---|---|
|
|
120
|
-
| `button` | 명시적인 주·보조 행동 | `label`, `icon`, `style`, `full_width`, `enabled` |
|
|
121
|
-
| `chip` | 필터·짧은 선택 | `label`, `icon`, `selected`, `enabled` |
|
|
122
|
-
| `field` | 텍스트·숫자 입력 | `state_key`, `label`, `placeholder`, `input_type`, `persist` |
|
|
123
|
-
| `select` | 고정 선택지 | `state_key`, `label`, `options`, `persist` |
|
|
124
|
-
| `switch` | boolean 설정 | `state_key`, `label`, `persist` |
|
|
125
|
-
| `form` | 관련 입력 묶음 | `spacing`, children, submit action을 가진 button |
|
|
126
|
-
| `dialog` | 짧고 집중된 확인·편집 | `title`, `label`, children |
|
|
127
|
-
| `sheet` | 모바일 중심의 보조 작업 | `title`, `label`, children |
|
|
128
|
-
|
|
129
|
-
`field`, `select`, `switch`의 `state_key`는 `initial_state`에 먼저 선언해야 합니다. select options는
|
|
130
|
-
1~32개의 `{ "value": ..., "label": "..." }` 객체입니다. `enabled: false`는 이유를 주변 문구로
|
|
131
|
-
설명할 때만 사용합니다.
|
|
132
|
-
|
|
133
|
-
## 반복과 데이터 시각화
|
|
134
|
-
|
|
135
|
-
| type | 목적 | 주요 props | 제약 |
|
|
136
|
-
|---|---|---|---|
|
|
137
|
-
| `list` | 일반 항목 반복 | `source`, `empty_text`, `limit`, `dense` | item template child 정확히 하나 |
|
|
138
|
-
| `timeline` | 시간 순서 사건 | `source`, `empty_text`, `limit` | item template child 정확히 하나 |
|
|
139
|
-
| `calendar` | 날짜별 데이터 | `source`, `date_key`, `title_key`, `state_key` | source 필수 |
|
|
140
|
-
| `chart` | 수치 비교·추세·분포 | `source`, `chart_type`, `x_key`, `y_key`, `show_legend` | `bar`, `line`, `donut`, `scatter` |
|
|
141
|
-
| `table` | 행·열 구조 데이터 | `source`, `empty_text`, `limit`, `dense` | 최대 6열, 가로 스크롤 |
|
|
142
|
-
|
|
143
|
-
반복 template 안에서는 `{{item.title}}`처럼 `item` binding을 사용합니다. 큰 목록을 한 번에 렌더링하지
|
|
144
|
-
말고 capability에서 페이지나 기간을 나눕니다.
|
|
145
|
-
|
|
146
|
-
## 공통 UX 프리셋
|
|
147
|
-
|
|
148
|
-
프리셋은 새 실행 타입이 아니라 검증된 primitive 조합입니다. 그래서 Preview와 Flutter Host가 같은
|
|
149
|
-
node를 렌더링하고 기존 플러그인도 별도 migration 없이 사용합니다.
|
|
150
|
-
|
|
151
|
-
| 프리셋 | 조합 | Host가 맡는 상태 |
|
|
152
|
-
|---|---|---|
|
|
153
|
-
| 목록·검색 | `field` + `button` + `list`/`timeline` | source별 loading/error/retry, 빈 목록 |
|
|
154
|
-
| 상세 | `app_bar` + `section` + `card`/`metric` | route back, stale response 차단 |
|
|
155
|
-
| 폼 | `form` + `field`/`select`/`switch` + submit `button` | focus, keyboard, disabled feedback |
|
|
156
|
-
| 설정 | `section` + persisted controls | state 복원, permission/connection 관리 링크 |
|
|
157
|
-
| 빈 상태 | `empty` + 다음 행동 `button` | 의미 있는 title/supporting/icon |
|
|
158
|
-
| 반응형 dashboard | `grid.min_item_width` + `row.stack_at` + adaptive navigation | bar/rail/drawer 전환 |
|
|
159
|
-
|
|
160
|
-
목록·검색의 최소 tree:
|
|
161
|
-
|
|
162
|
-
```json
|
|
163
|
-
{
|
|
164
|
-
"type": "column",
|
|
165
|
-
"props": {"spacing": 12},
|
|
166
|
-
"children": [
|
|
167
|
-
{"type": "field", "props": {"state_key": "query", "label": "검색", "placeholder": "검색어"}},
|
|
168
|
-
{
|
|
169
|
-
"type": "button",
|
|
170
|
-
"props": {"label": "찾기", "icon": "search"},
|
|
171
|
-
"action": {"type": "invoke", "capability": "com.example.search", "query": "{{state.query}}", "arguments": {}, "store": "results"}
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
"type": "list",
|
|
175
|
-
"props": {"source": "data.results.data.items", "empty_text": "검색 결과가 없어요.", "limit": 50},
|
|
176
|
-
"children": [
|
|
177
|
-
{"type": "card", "props": {"title": "{{item.title}}", "subtitle": "{{item.summary}}"}}
|
|
178
|
-
]
|
|
179
|
-
}
|
|
180
|
-
]
|
|
181
|
-
}
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
권한 요청, 연결 필요, 첫 loading, capability error와 retry는 플러그인이 비슷한 경고 카드를 다시
|
|
185
|
-
만들지 않고 Host 공통 상태를 사용합니다. Plugin 상세의 권한/Connection 화면으로 이동한 뒤 같은
|
|
186
|
-
route state와 persisted control을 복원합니다. 데이터가 정상적으로 비었을 때만 `empty` 또는
|
|
187
|
-
`empty_text`를 사용하며 오류를 빈 목록으로 숨기지 않습니다.
|
|
188
|
-
|
|
189
|
-
모든 프리셋은 light/dark, 320px 폭, tablet/desktop 폭, 큰 글자에서 확인합니다. node별 tooltip,
|
|
190
|
-
semantic label, 최소 탭 영역과 색 이외의 상태 표시는 Host Material 3 규칙을 따릅니다.
|
|
191
|
-
|
|
192
|
-
## A2UI Response catalog와의 관계
|
|
193
|
-
|
|
194
|
-
위 node는 Plugin Runtime을 구성하는 primitive입니다. AI가 직접 선택하는 Response catalog는
|
|
195
|
-
`weather`, `chart`, `image_gallery`, `exchange_rate`, `article_list`, `article_card`,
|
|
196
|
-
`file_result`, `image_preview`, `video_preview`와 활성 Plugin이 등록한 `plugin.<extension-id>`로
|
|
197
|
-
구성됩니다. AI는 primitive tree나 실제 결과 값을 생성하지 않고 catalog ID만 선택합니다. Host는
|
|
198
|
-
기본 컴포넌트 데이터 또는 Plugin의 `a2ui.schema`를 검증한 뒤 Runtime tree에 실제 값을 주입합니다.
|
|
199
|
-
자세한 등록·fallback 규칙은 [Response UI와 Agent Timeline](ai-response-and-timeline.md)을
|
|
200
|
-
참고하세요.
|
|
201
|
-
|
|
202
|
-
## 공통 크기·정렬·접근성 props
|
|
203
|
-
|
|
204
|
-
- 크기: `width`, `height`, `min_width`, `max_width`, `min_height`, `max_height`
|
|
205
|
-
- 여백: `padding`, `margin`, `spacing`
|
|
206
|
-
- 정렬: `alignment`, `main_axis_alignment`, `cross_axis_alignment`, `main_axis_size`
|
|
207
|
-
- 시각: `color`, `background_color`, `foreground_color`, `border_color`, `border_width`,
|
|
208
|
-
`border_radius`, `elevation`, `opacity`
|
|
209
|
-
- 이미지: `aspect_ratio`, `fit`
|
|
210
|
-
- 접근성: `tooltip`, `semantic_label`, `exclude_semantics`
|
|
211
|
-
|
|
212
|
-
텍스트 `align`의 실제 렌더 값은 `start`, `end`, `left`, `right`, `center`, `justify`입니다. 이전
|
|
213
|
-
UI v2 package가 사용한 다른 안전한 identifier도 호환을 위해 검증 단계에서는 수용하지만 Host는
|
|
214
|
-
기본 정렬로 처리합니다. 새 manifest에는 위 열거 값만 사용하세요.
|
|
215
|
-
|
|
216
|
-
크기와 색상 범위는 [토큰과 반응형](design-tokens-responsive.md)에 정리되어 있습니다.
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
# 토큰, 크기, 색, 여백, 반응형
|
|
2
|
-
|
|
3
|
-
Runtime v2는 기본적으로 Morit의 Material 3 theme를 사용합니다. 플러그인은 전체 앱의 theme를
|
|
4
|
-
바꾸지 않고 자신의 extension 범위 안에서만 제한된 `theme`와 node props를 적용합니다.
|
|
5
|
-
|
|
6
|
-
## Extension theme
|
|
7
|
-
|
|
8
|
-
```json
|
|
9
|
-
{
|
|
10
|
-
"theme": {
|
|
11
|
-
"radius": 18,
|
|
12
|
-
"spacing": 12,
|
|
13
|
-
"density": "standard",
|
|
14
|
-
"surface": {"elevation": 1},
|
|
15
|
-
"border": {"width": 1},
|
|
16
|
-
"icon": {"size": 22},
|
|
17
|
-
"typography": {"scale": 1.0, "body_weight": 400, "title_weight": 700},
|
|
18
|
-
"states": {"disabled_opacity": 0.38},
|
|
19
|
-
"light": {
|
|
20
|
-
"color_scheme": {
|
|
21
|
-
"primary": "#315DA8",
|
|
22
|
-
"on_primary": "#FFFFFF",
|
|
23
|
-
"surface": "#F9F9FF",
|
|
24
|
-
"on_surface": "#1A1B20"
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
"dark": {
|
|
28
|
-
"color_scheme": {
|
|
29
|
-
"primary": "#AFC6FF",
|
|
30
|
-
"on_primary": "#002F66",
|
|
31
|
-
"surface": "#111318",
|
|
32
|
-
"on_surface": "#E2E2E9"
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
- `radius`: 0~64
|
|
40
|
-
- `spacing`: 0~32
|
|
41
|
-
- `density`: `compact`, `standard`, `comfortable`
|
|
42
|
-
- `color_scheme`: Material role과 `#RRGGBB` 또는 `#AARRGGBB`
|
|
43
|
-
- `typography`: `scale` 0.8~1.4, body/title weight 100~900
|
|
44
|
-
- `surface`: `color`, `container_color`, `elevation` 0~24
|
|
45
|
-
- `border`: `color`, `width` 0~8, `radius` 0~64
|
|
46
|
-
- `icon`: `color`, `size` 12~64
|
|
47
|
-
- `states`: `disabled_opacity` 0.2~0.8, `selected_color`, `focus_color`
|
|
48
|
-
- `light`, `dark`: 위 필드의 mode별 partial override
|
|
49
|
-
|
|
50
|
-
최상위 base → 현재 mode variant → Host Material 3 theme 순서로 병합됩니다. 누락한 role은 Host가
|
|
51
|
-
채우므로 기존 플러그인은 선언을 추가하지 않아도 앱의 라이트·다크 모드를 따릅니다. 명백히 같은
|
|
52
|
-
literal 전경/배경은 validate 오류이며 계산 가능한 4.5:1 미만 조합은 경고입니다. 대비가 확인된 작은
|
|
53
|
-
palette만 override하고 모든 role을 임의로 복제하지 않습니다.
|
|
54
|
-
|
|
55
|
-
## 색상 token
|
|
56
|
-
|
|
57
|
-
node의 `color`, `background_color`, `foreground_color`, `border_color`는 다음 semantic token이나
|
|
58
|
-
hex를 사용합니다.
|
|
59
|
-
|
|
60
|
-
```text
|
|
61
|
-
primary, on_primary, primary_container, on_primary_container
|
|
62
|
-
secondary, on_secondary, secondary_container, on_secondary_container
|
|
63
|
-
tertiary, on_tertiary, tertiary_container, on_tertiary_container
|
|
64
|
-
error, on_error, error_container, on_error_container
|
|
65
|
-
surface, on_surface, surface_variant, on_surface_variant
|
|
66
|
-
outline, outline_variant
|
|
67
|
-
inverse_surface, inverse_on_surface, inverse_primary
|
|
68
|
-
shadow, scrim, transparent
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
semantic token을 우선합니다. hex는 브랜드 식별이나 데이터 범례처럼 의미가 명확하고 light/dark
|
|
72
|
-
대비를 직접 확인한 경우에만 사용합니다. 상태를 색 하나로만 표현하지 말고 아이콘·문구를 함께
|
|
73
|
-
제공합니다.
|
|
74
|
-
|
|
75
|
-
## 여백
|
|
76
|
-
|
|
77
|
-
`padding`과 `margin`은 0~128 숫자 하나 또는 다음 세 형태 중 하나입니다.
|
|
78
|
-
|
|
79
|
-
```json
|
|
80
|
-
16
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
```json
|
|
84
|
-
{"all": 16}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
```json
|
|
88
|
-
{"horizontal": 16, "vertical": 12}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
```json
|
|
92
|
-
{"left": 16, "top": 8, "right": 16, "bottom": 20}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
형태를 섞을 수 없습니다. `spacing`은 children 사이 간격이며 0~128입니다. 화면 가장자리 여백은
|
|
96
|
-
Host가 제공하므로 root에 과도한 padding을 중복하지 않습니다.
|
|
97
|
-
|
|
98
|
-
## 크기와 surface
|
|
99
|
-
|
|
100
|
-
| prop | 범위 |
|
|
101
|
-
|---|---|
|
|
102
|
-
| `width`, `height`, min/max variants | 0~4096 |
|
|
103
|
-
| `border_width` | 0~8 |
|
|
104
|
-
| `border_radius` | 0~64 |
|
|
105
|
-
| `elevation` | 0~24 |
|
|
106
|
-
| `opacity` | 0~1 |
|
|
107
|
-
| `aspect_ratio` | 0.1~20 |
|
|
108
|
-
| `size`, `max_lines`, `columns`, `limit` | 정수 0~100 |
|
|
109
|
-
|
|
110
|
-
`min_width <= max_width`, `min_height <= max_height`여야 합니다. 고정 `width`와 `height`는 아이콘,
|
|
111
|
-
avatar, 썸네일처럼 크기 의미가 있는 항목에만 쓰고, 본문 카드에는 min/max 제약과 자연 크기를
|
|
112
|
-
사용합니다.
|
|
113
|
-
|
|
114
|
-
## 정렬
|
|
115
|
-
|
|
116
|
-
`alignment`:
|
|
117
|
-
|
|
118
|
-
```text
|
|
119
|
-
top_left, top_center, top_right
|
|
120
|
-
center_left, center, center_right
|
|
121
|
-
bottom_left, bottom_center, bottom_right
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
`main_axis_alignment`은 `start`, `end`, `center`, `space_between`, `space_around`,
|
|
125
|
-
`space_evenly`를 사용합니다. `cross_axis_alignment`은 `start`, `end`, `center`, `stretch`,
|
|
126
|
-
`baseline`을 사용합니다. `main_axis_size`는 `min` 또는 `max`입니다.
|
|
127
|
-
|
|
128
|
-
텍스트 `align`은 `start`, `end`, `left`, `right`, `center`, `justify`입니다. 다국어 화면에는
|
|
129
|
-
물리 방향 `left`/`right`보다 논리 방향 `start`/`end`가 안전합니다.
|
|
130
|
-
|
|
131
|
-
## 반응형 규칙
|
|
132
|
-
|
|
133
|
-
### Row 전환
|
|
134
|
-
|
|
135
|
-
```json
|
|
136
|
-
{
|
|
137
|
-
"type": "row",
|
|
138
|
-
"props": {"spacing": 12, "stack_at": 480},
|
|
139
|
-
"children": []
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
가용 폭이 `stack_at`보다 작으면 세로로 배치합니다. 입력과 버튼, 두 개 이상의 긴 텍스트가 있는
|
|
144
|
-
row에는 480 전후를 시작점으로 사용하고 실제 큰 글자 크기에서 확인합니다.
|
|
145
|
-
|
|
146
|
-
### Grid 열 축소
|
|
147
|
-
|
|
148
|
-
```json
|
|
149
|
-
{
|
|
150
|
-
"type": "grid",
|
|
151
|
-
"props": {"columns": 3, "min_item_width": 160, "spacing": 12},
|
|
152
|
-
"children": []
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
`columns`는 희망 최대 열 수이고, Host는 `min_item_width` 96~600을 지키도록 열을 줄입니다. 모바일
|
|
157
|
-
가로 폭을 채우기 위해 글자와 숫자를 지나치게 압축하지 않습니다.
|
|
158
|
-
|
|
159
|
-
### Adaptive navigation
|
|
160
|
-
|
|
161
|
-
`navigation.type: "adaptive"`는 좁은 화면의 navigation bar와 넓은 화면의 rail/drawer를 Host가
|
|
162
|
-
선택하게 합니다. `rail_breakpoint`는 480~1600이며 꼭 필요한 경우에만 기본값을 조정합니다.
|
|
163
|
-
|
|
164
|
-
## 접근성과 동적 크기
|
|
165
|
-
|
|
166
|
-
- 중요한 텍스트는 `max_lines`로 잘라 의미를 잃지 않게 합니다.
|
|
167
|
-
- 버튼은 icon만 두지 말고 `label`을 제공합니다.
|
|
168
|
-
- 이미지와 의미 있는 icon에는 `semantic_label`을 제공합니다.
|
|
169
|
-
- 작은 `dense` 목록은 스캔 중심 화면에만 사용합니다.
|
|
170
|
-
- light/dark theme, 시스템 큰 글자, 320px급 폭, tablet/desktop 폭에서 확인합니다.
|
|
171
|
-
- animation이나 색 변화가 없어도 현재 선택과 진행 상태를 알 수 있어야 합니다.
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schema_version": 1,
|
|
3
|
-
"service": {
|
|
4
|
-
"name": "Morit Plugin",
|
|
5
|
-
"description": "Morit 플러그인을 설계하고 구현해 검증·배포하는 공식 개발 문서"
|
|
6
|
-
},
|
|
7
|
-
"categories": [
|
|
8
|
-
{
|
|
9
|
-
"slug": "start",
|
|
10
|
-
"name": "1. 시작과 개발 흐름",
|
|
11
|
-
"description": "개발 방식을 고르고 첫 패키지를 만드는 순서",
|
|
12
|
-
"documents": [
|
|
13
|
-
{ "file": "README.md", "slug": "index", "title": "개발 문서 인덱스" },
|
|
14
|
-
{ "file": "getting-started.md", "slug": "getting-started", "title": "시작하기와 개발 흐름" },
|
|
15
|
-
{ "file": "app-builder.md", "slug": "app-builder", "title": "앱에서 플러그인 만들기" }
|
|
16
|
-
]
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
"slug": "project",
|
|
20
|
-
"name": "2. 프로젝트와 계약",
|
|
21
|
-
"description": "프로젝트 구조, manifest, instance와 연결 모델",
|
|
22
|
-
"documents": [
|
|
23
|
-
{ "file": "project-structure.md", "slug": "project-structure", "title": "프로젝트 구조와 fragment" },
|
|
24
|
-
{ "file": "manifest.md", "slug": "manifest", "title": "Manifest 레퍼런스" },
|
|
25
|
-
{ "file": "instances-and-connectors.md", "slug": "instances-and-connectors", "title": "Instance, Connector, 복합 패키지" }
|
|
26
|
-
]
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"slug": "ui",
|
|
30
|
-
"name": "3. 화면과 사용자 경험",
|
|
31
|
-
"description": "화면 구조에서 컴포넌트, 반응형, Response UI까지",
|
|
32
|
-
"documents": [
|
|
33
|
-
{ "file": "screens-layout-navigation.md", "slug": "screens-layout-navigation", "title": "화면, 레이아웃, 내비게이션" },
|
|
34
|
-
{ "file": "components.md", "slug": "components", "title": "기본·커스텀 컴포넌트" },
|
|
35
|
-
{ "file": "design-tokens-responsive.md", "slug": "design-tokens-responsive", "title": "토큰, 크기, 색, 여백, 반응형" },
|
|
36
|
-
{ "file": "information-hierarchy.md", "slug": "information-hierarchy", "title": "화면 분리와 정보 계층" },
|
|
37
|
-
{ "file": "ui-extensions.md", "slug": "ui-extensions", "title": "UI extension point" },
|
|
38
|
-
{ "file": "ui-runtime-v2.md", "slug": "ui-runtime-v2", "title": "UI Runtime v2 레퍼런스" },
|
|
39
|
-
{ "file": "ai-response-and-timeline.md", "slug": "response-ui", "title": "Response UI와 Agent Timeline" }
|
|
40
|
-
]
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
"slug": "capabilities",
|
|
44
|
-
"name": "4. 기능, 데이터, 사용자 제어",
|
|
45
|
-
"description": "Tool, Skill, 권한, 설정, 알림과 외부 인증",
|
|
46
|
-
"documents": [
|
|
47
|
-
{ "file": "tool-and-skill.md", "slug": "tool-and-skill", "title": "Tool, Skill, Search, Slash Command" },
|
|
48
|
-
{ "file": "permissions-and-data.md", "slug": "permissions-settings-notifications", "title": "권한, 설정, 저장소, 알림" },
|
|
49
|
-
{ "file": "plugin-storage.md", "slug": "plugin-storage", "title": "Plugin Local Storage와 AI 접근" },
|
|
50
|
-
{ "file": "authentication.md", "slug": "authentication", "title": "외부 서비스 인증과 Cloud Secrets" },
|
|
51
|
-
{ "file": "ai-skill-and-docx-workflow.md", "slug": "ai-skill-artifacts", "title": "AI Skill과 파일 산출물" }
|
|
52
|
-
]
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
"slug": "platforms",
|
|
56
|
-
"name": "5. 플랫폼",
|
|
57
|
-
"description": "Android 우선 구현과 iOS·Desktop 호환 원칙",
|
|
58
|
-
"documents": [
|
|
59
|
-
{ "file": "platform-compatibility.md", "slug": "platform-compatibility", "title": "Android, iOS, Desktop 호환" }
|
|
60
|
-
]
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
"slug": "delivery",
|
|
64
|
-
"name": "6. 검증과 배포",
|
|
65
|
-
"description": "validate, preview, build, deploy와 오류 해결",
|
|
66
|
-
"documents": [
|
|
67
|
-
{ "file": "local-cli.md", "slug": "local-cli", "title": "공식 CLI 개발 흐름" },
|
|
68
|
-
{ "file": "packaging-and-testing.md", "slug": "packaging-and-testing", "title": "패키징과 테스트" },
|
|
69
|
-
{ "file": "troubleshooting.md", "slug": "troubleshooting", "title": "오류 해결" },
|
|
70
|
-
{ "file": "verification.md", "slug": "verification", "title": "예제 검증 기록과 경계" }
|
|
71
|
-
]
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
"slug": "automation",
|
|
75
|
-
"name": "7. SDK, MCP, API",
|
|
76
|
-
"description": "AI 에이전트 연결과 Host API 운영",
|
|
77
|
-
"documents": [
|
|
78
|
-
{ "file": "sdk-and-mcp.md", "slug": "sdk-and-mcp", "title": "CLI와 AI 에이전트 MCP" },
|
|
79
|
-
{ "file": "remote-mcp.md", "slug": "remote-mcp", "title": "원격 Plugin MCP" },
|
|
80
|
-
{ "file": "lifecycle-and-api.md", "slug": "lifecycle-and-api", "title": "수명주기와 HTTP API" }
|
|
81
|
-
]
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
"slug": "examples",
|
|
85
|
-
"name": "8. 실제 예제",
|
|
86
|
-
"description": "완성된 플러그인의 구조와 실사용 검증",
|
|
87
|
-
"documents": [
|
|
88
|
-
{ "file": "examples-school-life.md", "slug": "school-life", "title": "학교 생활 플러그인" },
|
|
89
|
-
{ "file": "school-life-privacy.md", "slug": "school-life-privacy", "title": "학교 생활 개인정보 처리" },
|
|
90
|
-
{ "file": "examples-notion.md", "slug": "notion", "title": "Notion 플러그인" }
|
|
91
|
-
]
|
|
92
|
-
}
|
|
93
|
-
]
|
|
94
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
# Notion 예제
|
|
2
|
-
|
|
3
|
-
위치: `examples/plugins/notion/`
|
|
4
|
-
|
|
5
|
-
이 예제는 사용자가 개인 token을 붙여 넣는 방식이 아니라 Notion의 공식 OAuth 화면에서
|
|
6
|
-
Morit에 공유할 page/data source를 선택하는 실제 연결 흐름을 사용합니다. Client ID와
|
|
7
|
-
Client Secret은 package가 아닌 Plugin Cloud Project에 저장됩니다.
|
|
8
|
-
|
|
9
|
-
## Developer Project 설정
|
|
10
|
-
|
|
11
|
-
1. `developers.moring.co`에서 조직을 선택하고 Notion Plugin Project를 엽니다.
|
|
12
|
-
2. `연결 및 Secrets`에서 다음 두 Secret을 저장합니다.
|
|
13
|
-
- `NOTION_CLIENT_ID` (`oauth_client_id`)
|
|
14
|
-
- `NOTION_CLIENT_SECRET` (`oauth_client_secret`)
|
|
15
|
-
3. Connection ID `notion`, Provider `notion`, 종류 `oauth2`를 추가합니다.
|
|
16
|
-
4. Authorization URL은 `https://api.notion.com/v1/oauth/authorize`, Token URL은
|
|
17
|
-
`https://api.notion.com/v1/oauth/token`으로 설정합니다.
|
|
18
|
-
5. 앞서 만든 두 Secret 이름을 Connection에 연결합니다.
|
|
19
|
-
6. Provider에 `notion`을 입력하면 Developer Platform이 Notion의 공식 Basic + JSON
|
|
20
|
-
token/revoke 형식과 `owner=user`를 자동 적용하고, PKCE는 `공급자가 지원하지 않음`으로
|
|
21
|
-
설정합니다. PKCE를 지원하는 다른 공급자는 기본값인 `사용 (권장)`을 유지합니다.
|
|
22
|
-
|
|
23
|
-
Notion은 현재 공식 token 요청에 `code_challenge`/`code_verifier`를 정의하지 않습니다.
|
|
24
|
-
이 호환 설정에서도 Morit의 일회용 `state`, 정확한 redirect URI, 사용자별 authorization
|
|
25
|
-
transaction 격리와 replay 차단은 그대로 적용됩니다.
|
|
26
|
-
|
|
27
|
-
Notion integration의 redirect URI는 다음과 정확히 같아야 합니다.
|
|
28
|
-
|
|
29
|
-
```text
|
|
30
|
-
https://morit-api.moring.co/v1/plugins/oauth/callback/notion
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Morit 운영 서버의 `.env`에 Notion 전용 Client ID/Secret을 추가하지 않습니다. Plugin
|
|
34
|
-
Runtime에는 Cloud vault 해제용 공통 key와 사용자 token vault용 별도 key만 둡니다.
|
|
35
|
-
|
|
36
|
-
## 기능
|
|
37
|
-
|
|
38
|
-
| 기능 | Capability |
|
|
39
|
-
|---|---|
|
|
40
|
-
| 페이지/데이터 소스 검색과 상위 페이지 본문 | `notion.search_and_read` Tool |
|
|
41
|
-
| 프로젝트 문서 context | `notion.project_context` Skill |
|
|
42
|
-
| 통합 검색 | `notion.search_provider` Provider |
|
|
43
|
-
| Slash | `/notion` |
|
|
44
|
-
|
|
45
|
-
Manifest는 `POST https://api.notion.com/v1/search` 결과 종류에 따라 후속 조회를
|
|
46
|
-
사용합니다.
|
|
47
|
-
|
|
48
|
-
- page: `GET /v1/blocks/{id}/children?page_size=100`
|
|
49
|
-
- data source: `POST /v1/data_sources/{id}/query` (`page_size: 20`)
|
|
50
|
-
|
|
51
|
-
page block의 제목·목록·코드와 data source row의 title/rich text/select/status/
|
|
52
|
-
multi-select/URL/contact 속성을 공용 result/evidence로 정규화합니다. endpoint, token,
|
|
53
|
-
request body 같은 민감 provenance는 결과에 포함하지 않습니다.
|
|
54
|
-
|
|
55
|
-
Notion은 integration과 명시적으로 공유된 page/data source만 반환합니다. 401이면
|
|
56
|
-
refresh token으로 한 번 갱신하고, 갱신이 거부되면 앱에 재연결 안내를 표시합니다.
|
|
57
|
-
|
|
58
|
-
## 빌드와 배포
|
|
59
|
-
|
|
60
|
-
```powershell
|
|
61
|
-
npx -y @morit/cli plugin validate .\examples\plugins\notion
|
|
62
|
-
npx -y @morit/cli plugin build .\examples\plugins\notion
|
|
63
|
-
npx -y @morit/cli plugin deploy .\examples\plugins\notion --visibility private
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
검증된 `.mplg`는 앱의 `파일에서 설치`로 직접 설치할 수도 있습니다. Cloud OAuth를
|
|
67
|
-
사용하려면 Project에 연결된 정확한 Deployment artifact여야 하며, 개인용으로만 쓸
|
|
68
|
-
경우 비공개를 유지합니다. 홈페이지·개인정보처리방침·아이콘·개발자 metadata를 모두
|
|
69
|
-
채운 후에만 공개 Marketplace 배포가 허용됩니다.
|
|
70
|
-
|
|
71
|
-
## 실제 사용 확인
|
|
72
|
-
|
|
73
|
-
1. 앱에서 Plugin을 설치하고 자동 생성된 기본 Instance를 엽니다.
|
|
74
|
-
2. Notion 연결을 눌러 실제 계정을 승인합니다. 다른 계정도 필요하면 같은 Instance 안에
|
|
75
|
-
Connection을 추가합니다.
|
|
76
|
-
3. `/notion Morit 플러그인 계획` 또는 `Notion에서 이번 분기 프로젝트 문서를 찾아줘`를 실행합니다.
|
|
77
|
-
4. 응답의 근거 링크가 승인한 workspace 범위 안에 있는지 확인합니다.
|
|
78
|
-
5. 연결 해제 후 동일 기능이 token을 재사용하지 않고 `연결 필요`로 바뀌는지 확인합니다.
|
|
79
|
-
|
|
80
|
-
자동 테스트는 authorize/token/search/block children/data source query/revoke 계약,
|
|
81
|
-
state replay 방지, 암호화, refresh, 사용자 격리와 Secret 비노출을 검증합니다. 실제
|
|
82
|
-
workspace 결과는 Notion 계정 승인과 공유 페이지가 있어야 하므로 배포 전 별도의 live
|
|
83
|
-
smoke test가 필요합니다.
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# 학교 생활 플러그인
|
|
2
|
-
|
|
3
|
-
위치: `examples/plugins/school-life/`
|
|
4
|
-
|
|
5
|
-
학교 생활 1.5.0은 하드코딩 시간표나 테스트용 JSON을 사용하지 않습니다. 학교명을
|
|
6
|
-
[NEIS 공개 API](https://open.neis.go.kr/)의 `schoolInfo`에서 찾고, 학교 종류에 맞는
|
|
7
|
-
시간표 API와 `SchoolSchedule`, `mealServiceDietInfo`를 호출합니다. 선택한 학교·학년·반과
|
|
8
|
-
알림 설정은 사용자와 Plugin Instance 범위의 저장소에 격리합니다.
|
|
9
|
-
|
|
10
|
-
## 학생용 기능
|
|
11
|
-
|
|
12
|
-
| 경험 | 구현 |
|
|
13
|
-
|---|---|
|
|
14
|
-
| 학교 연결과 후보 선택 | `school_life.setup`, 전용 온보딩 화면 |
|
|
15
|
-
| 오늘·내일 시간표/일정/급식 | `school_life.schedule.lookup`, 대시보드 |
|
|
16
|
-
| 최대 14일 주간 보기 | `school_life.week.lookup`, 차트·달력·타임라인 |
|
|
17
|
-
| 메뉴·열량·알레르기·영양·원산지 | 급식 전용 화면 |
|
|
18
|
-
| 자연어 질문 | `school_life.schedule.ask`, `school_life.week.ask` |
|
|
19
|
-
| AI 답변 안의 시각 카드 | day/week `response` extensions |
|
|
20
|
-
| Morit 통합 검색 | `school_life.search` Provider |
|
|
21
|
-
| 다음 학교일 알림 | `school_life.schedule.reminder` |
|
|
22
|
-
| 6시간 자동 확인·중복 방지 브리핑 | background refresh/briefing |
|
|
23
|
-
| 빠른 명령 | `/school`, `/meal`, `/timetable` |
|
|
24
|
-
| AI와 공유하는 학교 할 일 | `tasks` namespace와 표준 Storage CRUD Tool |
|
|
25
|
-
| 라이트·다크 화면 | Host theme 상속 + mode별 최소 색 override |
|
|
26
|
-
|
|
27
|
-
기간 조회는 시간표·일정·급식 API를 각각 한 번씩 병렬 호출하고 날짜별로 묶으므로 7일
|
|
28
|
-
화면이 날짜마다 네트워크를 반복하지 않습니다. 급식의 숫자 알레르기 코드는 19개 표준
|
|
29
|
-
항목 이름으로 바꾸고, 메뉴와 영양/원산지의 HTML 줄바꿈을 사람이 읽는 텍스트로 정리합니다.
|
|
30
|
-
|
|
31
|
-
## 설치와 첫 설정
|
|
32
|
-
|
|
33
|
-
```text
|
|
34
|
-
examples/plugins/school-life/dist/school-life-1.5.0.mplg
|
|
35
|
-
크기 263433 bytes
|
|
36
|
-
SHA-256 0939f8956037638d56f7672ac4f9c4d8f6472d58d23840c1fd5baab198c41e29
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
1. Morit 플러그인 관리에서 위 `.mplg`를 선택합니다.
|
|
40
|
-
2. `network`, `storage`, `ai_storage`, `notifications`, `background` 권한을 허용합니다.
|
|
41
|
-
3. 학교 이름을 검색하고 후보에서 정확한 학교를 고릅니다.
|
|
42
|
-
4. 학년·반, 알림 시각(기본 07:30), 아침 브리핑 사용 여부를 저장합니다.
|
|
43
|
-
5. 홈의 `학교 생활`, `오늘`, `이번 주`, `급식` 화면을 사용합니다.
|
|
44
|
-
|
|
45
|
-
학교 프로필이 없으면 조회 화면이 원시 오류 대신 설정 안내를 표시합니다. 설정은 다시
|
|
46
|
-
열어 수정할 수 있으며 아침 브리핑을 끄면 자동 알림만 중단됩니다. 수동 `다음 학교일 알림`
|
|
47
|
-
요청은 계속 사용할 수 있습니다.
|
|
48
|
-
|
|
49
|
-
AI 예:
|
|
50
|
-
|
|
51
|
-
- `오늘 수업 순서와 급식 알려줘`
|
|
52
|
-
- `내일 알레르기 5번 메뉴가 있어?`
|
|
53
|
-
- `이번 주 학사일정과 수업량을 정리해줘`
|
|
54
|
-
- `이번 주 학교 할 일에 과학 보고서 추가해줘`
|
|
55
|
-
- `학교 할 일 중 끝낸 과제를 완료 처리해줘`
|
|
56
|
-
- `/timetable 내일`
|
|
57
|
-
- `/meal 앞으로 7일`
|
|
58
|
-
|
|
59
|
-
## 개인정보와 실패 처리
|
|
60
|
-
|
|
61
|
-
- 저장: 공개 학교 식별자, 학년·반, 알림 시각/사용 여부, 사용자가 AI에 요청한 학교 할 일
|
|
62
|
-
- 미수집: 학생 이름, 학번, 성적, NEIS 로그인 정보
|
|
63
|
-
- 외부 전송: 공개 조회 조건만 NEIS에 전달
|
|
64
|
-
- 삭제: profile, background lease/state, host-action outbox를 사용자 범위에서 purge
|
|
65
|
-
- 빈 날짜: 정상 빈 상태로 표시
|
|
66
|
-
- NEIS 장애/형식 오류: 제한된 사용자 메시지로 실패하고 임의 데이터를 만들지 않음
|
|
67
|
-
|
|
68
|
-
## 검증
|
|
69
|
-
|
|
70
|
-
`backend/archive_processing/test_plugin_examples.py`는 서명 package 설치, 권한 전 상태,
|
|
71
|
-
학교 연결, 단일 날짜와 7일 조회, 알레르기/영양 변환, 검색, rich 알림, 자동 브리핑 중복
|
|
72
|
-
방지, Storage CRUD/AI 권한, 재시작 복원, actor·Instance 격리, migration, disable/delete purge를
|
|
73
|
-
검증합니다. day/week Response extension은 A2UI v0.9 schema와 실제 capability data source를
|
|
74
|
-
검사하고 `plugin.school_life.day.response`, `plugin.school_life.week.response`로 동적 등록됩니다.
|
|
75
|
-
|
|
76
|
-
배포 전에는 [예제 검증 방법](verification.md)에 따라 실제 NEIS 네트워크로 공개 시간표·학사일정·
|
|
77
|
-
급식 기간 조회도 실행합니다. 이 live 확인은 API와 parser 경계를 검증하지만 release APK의
|
|
78
|
-
화면·알림을 Android 실기기에서 확인하는 절차를 대신하지 않습니다. 학교가 공개하지 않은 날짜의
|
|
79
|
-
빈 응답은 정상 상태로 처리해야 합니다.
|