axmap-cli 1.3.2 → 1.4.0

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.
Files changed (67) hide show
  1. package/CLAUDE.md +15 -9
  2. package/README.ko.md +89 -85
  3. package/README.md +90 -86
  4. package/bin/axmap.mjs +9 -3
  5. package/mcp/README.md +8 -8
  6. package/mcp/install.ps1 +6 -3
  7. package/mcp/install.sh +5 -3
  8. package/mcp/server.mjs +81 -32
  9. package/package.json +7 -11
  10. package/src/agentcli.mjs +19 -0
  11. package/src/bridge.mjs +245 -0
  12. package/src/invariants.mjs +1 -1
  13. package/src/managed-session.mjs +178 -0
  14. package/src/protocol.mjs +1 -1
  15. package/src/session-dialogue.mjs +131 -0
  16. package/src/session-usage.mjs +34 -0
  17. package/src/state.mjs +8 -0
  18. package/src/terminal-bridge.mjs +224 -0
  19. package/src/update.mjs +1 -1
  20. package/tools/bridge.mjs +85 -0
  21. package/tools/mcp-register.mjs +22 -3
  22. package/tools/run-tests.mjs +10 -3
  23. package/tools/run.mjs +60 -0
  24. package/tools/setup.mjs +11 -0
  25. package/app/README.md +0 -366
  26. package/app/eval/edges.mjs +0 -242
  27. package/app/lib/adjacent.mjs +0 -125
  28. package/app/lib/agentcli.mjs +0 -153
  29. package/app/lib/analyze.mjs +0 -1159
  30. package/app/lib/cochange.mjs +0 -421
  31. package/app/lib/datanodes.mjs +0 -127
  32. package/app/lib/entry.mjs +0 -192
  33. package/app/lib/featuregraph.mjs +0 -389
  34. package/app/lib/features.mjs +0 -645
  35. package/app/lib/fetchrepo-run.mjs +0 -37
  36. package/app/lib/fetchrepo.mjs +0 -164
  37. package/app/lib/flow.mjs +0 -1089
  38. package/app/lib/ladder.mjs +0 -387
  39. package/app/lib/langs.mjs +0 -630
  40. package/app/lib/live.mjs +0 -346
  41. package/app/lib/llm.mjs +0 -594
  42. package/app/lib/newfile.mjs +0 -126
  43. package/app/lib/prdiff.mjs +0 -651
  44. package/app/lib/reveal.mjs +0 -316
  45. package/app/lib/roots.mjs +0 -186
  46. package/app/lib/scope.mjs +0 -342
  47. package/app/lib/session.mjs +0 -389
  48. package/app/lib/slots.mjs +0 -233
  49. package/app/lib/ssot.mjs +0 -277
  50. package/app/lib/teamview.mjs +0 -975
  51. package/app/lib/terms.ko.mjs +0 -169
  52. package/app/server.mjs +0 -1959
  53. package/app/web/shell.css +0 -538
  54. package/app/web/shell.html +0 -197
  55. package/app/web/shell.js +0 -638
  56. package/app/web/stage.js +0 -347
  57. package/app/web/words.js +0 -85
  58. package/src/repotarget.mjs +0 -84
  59. package/tools/cluster-experiment.mjs +0 -256
  60. package/tools/cluster-sweep.mjs +0 -226
  61. package/tools/make-icon.mjs +0 -108
  62. package/tools/persona-bench.mjs +0 -362
  63. package/tools/pick-repo.mjs +0 -229
  64. package/tools/reveal-demo.mjs +0 -158
  65. package/tools/shortcut.mjs +0 -121
  66. package/tools/smoke.mjs +0 -174
  67. package/tools/topicgraph.py +0 -154
package/app/web/shell.css DELETED
@@ -1,538 +0,0 @@
1
- /*
2
- 새 껍데기의 스타일.
3
-
4
- 🔴 `style.css` 와 **따로 둔다.** 그쪽은 기존 화면(`/classic`)의 것이고
5
- 검은 배경에 밀도 높은 목록을 전제로 짜여 있다. 한 파일에 두 방향을 담으면
6
- 한쪽을 고칠 때마다 다른 쪽이 깨진다.
7
-
8
- 이 화면의 방향은 하나다 — **여백**. 기능을 덜어낸 자리를 다시 채우지 않는다.
9
- */
10
-
11
- /**
12
- * 색과 치수는 Claude 브랜드 토큰에서 가져왔다.
13
- * 출처: github.com/ricocc/brands-design-md · brands/claude/variables.css
14
- *
15
- * 눈으로 고른 값이 아니라 **출처가 있는 값**이라는 것이 요점이다.
16
- * 고칠 일이 생기면 여기서 고치고, 쓰는 쪽은 토큰 이름만 쓴다 —
17
- * 하드코딩된 색이 한 곳이라도 남으면 다음에 바꿀 때 그것만 안 바뀐다.
18
- */
19
- :root {
20
- --bg: #faf9f5; /* canvas */
21
- --side: #f5f0e8; /* surface-soft */
22
- --card: #efe9de; /* surface-card */
23
- --ink: #141413; /* ink — 제목 */
24
- --ink2: #6c6a64; /* muted */
25
- --ink3: #8e8b82; /* muted-soft */
26
- --line: #e6dfd8; /* hairline */
27
- --hover: #efe9de;
28
- --accent: #cc785c; /* primary */
29
- --accent-down: #a9583e; /* primary-active */
30
- --accent-tint: #f7ede8; /* primary 를 canvas 위에 옅게 깐 것 */
31
- --warn: #c64545; /* error */
32
- --on-accent: #ffffff; /* on-primary — 주황 위에 얹는 글자 */
33
-
34
- --r-sm: 6px;
35
- --r-md: 8px;
36
- --r-lg: 12px;
37
- --r-xl: 16px;
38
-
39
- --mono: "JetBrains Mono", "Cascadia Mono", "D2Coding", Consolas, monospace;
40
- /**
41
- * 🔴 브랜드의 표시용 세리프(Copernicus · Tiempos Headline)는 **안 쓴다.**
42
- *
43
- * 이 화면은 한국어가 먼저다. 그 서체들은 한글 글리프가 없어서 제목의 한글만
44
- * 시스템 서체로 떨어지고, 그러면 한 줄 안에서 라틴과 한글의 인상이 갈린다.
45
- * 색은 브랜드를 따르되 서체는 한글이 읽히는 쪽을 고른다.
46
- */
47
- --ui: "Pretendard", "StyreneB", Inter, -apple-system, "Segoe UI", system-ui, sans-serif;
48
- }
49
-
50
- * { box-sizing: border-box; }
51
-
52
- /**
53
- * 🔴 hidden 속성이 항상 이기게 한다.
54
- *
55
- * hidden 은 UA 스타일시트의 display:none 으로 동작하는데, 우리가 그 요소에
56
- * display:flex 를 직접 주면 **작성자 스타일이 이겨서 안 숨는다.**
57
- *
58
- * 실제로 #onboard 가 그랬다 — hidden = true 를 줘도 온보딩이 화면을 덮은 채
59
- * 남아 있었고, 자바스크립트는 아무 오류도 내지 않았다. 눌러 보기 전에는
60
- * 알 방법이 없는 종류의 실패다.
61
- */
62
- [hidden] { display: none !important; }
63
-
64
- html, body {
65
- height: 100%; margin: 0;
66
- background: var(--bg); color: var(--ink);
67
- font: 14px/1.6 var(--ui);
68
- -webkit-font-smoothing: antialiased;
69
- }
70
-
71
- button { font: inherit; cursor: pointer; }
72
-
73
- #app { display: flex; height: 100%; }
74
-
75
- /* 분할선 — 평소에는 보이지 않고 잡을 때만 드러난다 */
76
- .gut { width: 1px; flex: none; background: var(--line); cursor: col-resize; position: relative; }
77
- .gut::after { content: ''; position: absolute; inset: 0 -3px; }
78
- .gut:hover { background: var(--ink3); }
79
-
80
- /* ── 왼쪽 ──────────────────────────────────────────────────────────────── */
81
-
82
- #side {
83
- width: 260px; min-width: 200px; flex: none;
84
- background: var(--side);
85
- display: flex; flex-direction: column;
86
- padding: 10px;
87
- }
88
-
89
- #tabs { display: flex; gap: 4px; margin-bottom: 14px; }
90
- .tab {
91
- flex: 1; padding: 7px 0; border: 0; border-radius: 8px;
92
- background: transparent; color: var(--ink2); font-size: 13px;
93
- }
94
- .tab:hover { background: var(--hover); color: var(--ink); }
95
- .tab.on { background: var(--bg); color: var(--ink); box-shadow: 0 1px 2px rgba(0,0,0,.06); }
96
-
97
- #new {
98
- display: flex; align-items: center; gap: 10px; width: 100%;
99
- padding: 8px 10px; margin-bottom: 22px;
100
- border: 0; border-radius: 8px; background: transparent;
101
- color: var(--ink); font-size: 14px; text-align: left;
102
- }
103
- #new:hover { background: var(--hover); }
104
- #new span { flex: 1; }
105
-
106
- .sec {
107
- font-size: 12px; color: var(--ink3);
108
- padding: 0 10px 8px;
109
- }
110
-
111
- #chats { flex: 1; overflow: auto; margin: 0 -4px; }
112
- .chat {
113
- display: flex; align-items: center; gap: 9px;
114
- padding: 7px 10px; border-radius: 8px;
115
- font-size: 13px; color: var(--ink2);
116
- overflow: hidden; white-space: nowrap;
117
- }
118
- .chat:hover { background: var(--hover); color: var(--ink); }
119
- .chat.on { background: var(--hover); color: var(--ink); }
120
- .chat .dot {
121
- width: 5px; height: 5px; border-radius: 50%; flex: none;
122
- border: 1px solid var(--ink3);
123
- }
124
- .chat.run .dot { background: var(--accent); border-color: var(--accent); }
125
- .chat.err .dot { background: var(--warn); border-color: var(--warn); }
126
- .chat .name { flex: 1; overflow: hidden; text-overflow: ellipsis; }
127
- .chat .n { font: 10px var(--mono); color: var(--accent); flex: none; }
128
- .empty { padding: 4px 10px; font-size: 12px; color: var(--ink3); }
129
-
130
- #foot {
131
- border-top: 1px solid var(--line);
132
- padding: 10px 10px 2px; margin-top: 8px;
133
- display: flex; flex-direction: column; gap: 2px;
134
- font-size: 11px; color: var(--ink3);
135
- }
136
- #foot span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
137
-
138
- /* ── 가운데 ────────────────────────────────────────────────────────────── */
139
-
140
- /*
141
- 🔴 지금은 비어 있다. 그래프가 들어올 자리다.
142
-
143
- 기존 화면의 캔버스를 그대로 옮겨 오지 않은 이유: 그 렌더러는 검은 배경을
144
- 전제로 색을 고른다(`graph.js` 의 명암·안개). 밝은 바탕에 그대로 얹으면
145
- 읽히지 않는다. 옮길 때 색 채널을 다시 정하고 옮긴다.
146
- */
147
- #center { flex: 1; min-width: 0; display: flex; align-items: center; justify-content: center; }
148
- #stage { color: var(--ink3); font-size: 12px; }
149
-
150
- /* ── 오른쪽 · 대화 ─────────────────────────────────────────────────────── */
151
-
152
- #chat {
153
- width: 520px; min-width: 360px; flex: none;
154
- display: flex; flex-direction: column; min-height: 0;
155
- }
156
-
157
- #head {
158
- flex: none; display: flex; align-items: center; gap: 10px;
159
- padding: 14px 22px 6px; font-size: 13px; color: var(--ink2);
160
- min-height: 42px;
161
- }
162
- #title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
163
- #stop {
164
- border: 1px solid var(--line); background: var(--bg); color: var(--ink2);
165
- border-radius: 7px; padding: 3px 10px; font-size: 12px;
166
- }
167
- #stop:hover { color: var(--warn); border-color: var(--warn); }
168
-
169
- #touched {
170
- flex: none; margin: 0 22px 10px; padding: 9px 12px;
171
- border: 1px solid var(--line); border-radius: 10px;
172
- font-size: 12px; color: var(--ink2); background: var(--bg);
173
- }
174
- #touched b { color: var(--ink); font-weight: 600; }
175
- #touched .f {
176
- display: block; font: 11px var(--mono); color: var(--ink);
177
- padding-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
178
- }
179
-
180
- #thread {
181
- flex: 1; overflow: auto; padding: 6px 22px 10px;
182
- display: flex; flex-direction: column; gap: 16px;
183
- }
184
- #thread:empty { flex: 0; padding: 0; }
185
-
186
- /* 대화가 없을 때 — 인사말과 입력창이 칸 가운데에 뜬다 */
187
- #tail { flex: 0; }
188
- #chat.blank #tail { flex: 1; }
189
-
190
- /* 대화가 없을 때의 인사. 스크린샷의 가운데 그 화면이다. */
191
- #hello {
192
- flex: 1; display: flex; flex-direction: column;
193
- align-items: center; justify-content: flex-end;
194
- padding-bottom: 26px; gap: 14px;
195
- }
196
- #hello .mark { color: var(--accent); font-size: 26px; line-height: 1; }
197
- #hello h1 { margin: 0; font-size: 28px; font-weight: 400; letter-spacing: -.01em; }
198
-
199
- .msg { font-size: 14px; line-height: 1.7; white-space: pre-wrap; word-break: break-word; }
200
- .msg.user {
201
- align-self: flex-end; max-width: 85%;
202
- background: var(--side); padding: 10px 15px; border-radius: 16px 16px 4px 16px;
203
- }
204
- .msg.assistant { align-self: flex-start; }
205
- .msg.error {
206
- align-self: flex-start; color: var(--warn); font-size: 13px;
207
- border-left: 2px solid var(--warn); padding-left: 10px;
208
- }
209
- .msg.note { align-self: center; color: var(--ink3); font-size: 12px; }
210
-
211
- /* 도구 한 줄 — 대화가 아니라 **행동**이라 다르게 그린다 */
212
- .msg.tool {
213
- align-self: flex-start; display: flex; gap: 8px; align-items: baseline;
214
- font: 11px/1.5 var(--mono); color: var(--ink3); max-width: 100%;
215
- }
216
- .msg.tool .t { flex: none; }
217
- .msg.tool .a { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
218
- .msg.tool.edits .t { color: var(--accent); }
219
- .msg.tool.edits .a { color: var(--ink); }
220
-
221
- /* 계측·잡음은 버리지 않고 접어 둔다 */
222
- details.msg { align-self: flex-start; max-width: 100%; }
223
- details.msg summary { font-size: 11px; color: var(--ink3); cursor: pointer; list-style: none; }
224
- details.msg pre {
225
- font: 11px var(--mono); color: var(--ink2); white-space: pre-wrap;
226
- word-break: break-all; margin: 6px 0 0;
227
- }
228
-
229
- .think { align-self: flex-start; color: var(--ink3); font-size: 13px; }
230
- .think::after { content: ''; animation: d 1.4s steps(4, end) infinite; }
231
- @keyframes d { 0% { content: '' } 25% { content: '.' } 50% { content: '..' } 75% { content: '...' } }
232
-
233
- /* ── 입력 ──────────────────────────────────────────────────────────────── */
234
-
235
- #composer {
236
- flex: none; margin: 0 22px 22px; padding: 14px 14px 10px;
237
- border: 1px solid var(--line); border-radius: var(--r-xl); background: var(--bg);
238
- box-shadow: 0 1px 3px rgba(0,0,0,.04);
239
- }
240
- #composer:focus-within { border-color: var(--ink3); }
241
- #input {
242
- width: 100%; border: 0; outline: none; resize: none; background: transparent;
243
- font: 14px/1.6 var(--ui); color: var(--ink); max-height: 200px;
244
- }
245
- #input::placeholder { color: var(--ink3); }
246
- #composer .row { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
247
- #note { flex: 1; font-size: 11px; color: var(--ink3); }
248
- #send {
249
- flex: none; width: 30px; height: 30px; border-radius: 9px; border: 0;
250
- background: var(--accent); color: var(--on-accent); font-size: 14px;
251
- }
252
- #send:hover { background: var(--accent-down); }
253
- #send:disabled { background: var(--line); color: var(--ink3); cursor: default; }
254
-
255
- /* ══ 창 테두리 = 툴바 ═══════════════════════════════════════════════════════
256
-
257
- 🔴 이 줄은 **새로 만든 줄이 아니라 창의 제목표시줄**이다 (`frame: false`).
258
-
259
- 그래서 높이를 32px 로 낮추고 아래 테두리를 없앴다. 사이드바와 같은 색이라
260
- 왼쪽 위 모서리가 한 덩어리로 이어져 보인다 — 띠가 하나 더 얹힌 것처럼
261
- 보이면 그 줄은 낭비다.
262
-
263
- 빈 곳은 끌어서 창을 옮길 수 있어야 하고(`app-region: drag`), 버튼은 반대로
264
- 끌기에서 빼야 한다 — 안 빼면 눌리지 않는다. 실제로 그렇게 안 눌렸다.
265
- ────────────────────────────────────────────────────────────────────────── */
266
-
267
- body { display: flex; flex-direction: column; }
268
-
269
- #bar {
270
- flex: none; display: flex; align-items: center; gap: 2px;
271
- height: 32px; padding-left: 6px;
272
- background: var(--side);
273
- -webkit-app-region: drag;
274
- user-select: none;
275
- }
276
-
277
- /* 아이콘 단추 — 인라인 SVG. 아이콘 글꼴도 이미지도 안 쓴다 */
278
- .ico {
279
- -webkit-app-region: no-drag;
280
- display: grid; place-items: center;
281
- width: 30px; height: 26px; padding: 0;
282
- border: 0; border-radius: var(--r-sm); background: transparent;
283
- color: var(--ink2);
284
- }
285
- .ico:hover, .ico.open { background: var(--hover); color: var(--ink); }
286
- .ico svg {
287
- width: 16px; height: 16px;
288
- fill: none; stroke: currentColor; stroke-width: 1.4;
289
- stroke-linecap: round; stroke-linejoin: round;
290
- }
291
- /* 폴더는 채워야 폴더로 읽힌다 */
292
- #openrepo svg { fill: currentColor; fill-opacity: .13; }
293
-
294
- /*
295
- 지금 보고 있는 저장소. 창 제목이 놓이는 자리이고, **글자로 남긴 유일한 것**이다.
296
- VS Code 가 여기에 파일 이름을 두는 것과 같은 이유 — 창 제목은 "무엇을 보고 있나" 다.
297
- */
298
- #where {
299
- flex: 1; text-align: center; font-size: 11px; color: var(--ink3);
300
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
301
- padding: 0 12px;
302
- }
303
-
304
- #wctl { display: flex; -webkit-app-region: no-drag; }
305
- #wctl button {
306
- display: grid; place-items: center;
307
- width: 44px; height: 32px; border: 0; background: transparent; color: var(--ink2);
308
- }
309
- #wctl svg {
310
- width: 12px; height: 12px;
311
- fill: none; stroke: currentColor; stroke-width: 1.1; stroke-linecap: round;
312
- }
313
- #wctl button:hover { background: var(--hover); color: var(--ink); }
314
- #wctl button[data-win="close"]:hover { background: var(--warn); color: var(--on-accent); }
315
-
316
- /* 메뉴 펼침 */
317
- #drop {
318
- position: fixed; z-index: 40; min-width: 220px; padding: 5px;
319
- background: var(--bg); border: 1px solid var(--line); border-radius: var(--r-lg);
320
- box-shadow: 0 8px 24px rgba(0,0,0,.12);
321
- }
322
- #drop button {
323
- display: flex; width: 100%; align-items: center; gap: 10px;
324
- border: 0; background: transparent; color: var(--ink);
325
- font-size: 13px; padding: 7px 10px; border-radius: var(--r-sm); text-align: left;
326
- }
327
- #drop button:hover { background: var(--hover); }
328
- #drop button:disabled { color: var(--ink3); }
329
- #drop .k { margin-left: auto; font: 10px var(--mono); color: var(--ink3); }
330
- #drop hr { border: 0; border-top: 1px solid var(--line); margin: 5px 2px; }
331
-
332
- #app { flex: 1; min-height: 0; }
333
-
334
- /* 사이드바 접기 — 가운데 칸에 자리를 준다 */
335
- body.no-side #side,
336
- body.no-side .gut[data-pane="side"] { display: none; }
337
-
338
- /* ══ 처음 오는 사람 ═════════════════════════════════════════════════════════ */
339
-
340
- #onboard {
341
- position: fixed; inset: 0; z-index: 60;
342
- background: var(--bg);
343
- display: flex; align-items: center; justify-content: center;
344
- padding: 24px;
345
- }
346
- #onboard .card { width: 100%; max-width: 460px; text-align: center; }
347
- #onboard .mark { color: var(--accent); font-size: 26px; margin-bottom: 12px; }
348
- #onboard h2 { margin: 0 0 8px; font-size: 24px; font-weight: 400; }
349
- #onboard p { margin: 0 0 22px; font-size: 13px; color: var(--ink2); line-height: 1.8; }
350
-
351
- .steps { display: flex; gap: 6px; justify-content: center; margin-bottom: 26px; }
352
- .steps i { width: 22px; height: 3px; border-radius: 2px; background: var(--line); }
353
- .steps i.on { background: var(--accent); }
354
-
355
- /* CLI 고르기 — 설치·로그인 상태를 카드에 그대로 적는다 */
356
- #picks { display: flex; flex-direction: column; gap: 8px; margin-bottom: 22px; }
357
- .pick {
358
- display: flex; align-items: center; gap: 12px; width: 100%;
359
- padding: 13px 15px; border: 1px solid var(--line); border-radius: 12px;
360
- background: var(--bg); text-align: left;
361
- }
362
- .pick:hover:not(:disabled) { border-color: var(--ink3); }
363
- .pick.on { border-color: var(--accent); background: var(--accent-tint); }
364
- .pick:disabled { opacity: .5; cursor: default; }
365
- .pick .nm { flex: 1; font-size: 14px; color: var(--ink); }
366
- .pick .st { font-size: 11px; color: var(--ink3); }
367
- .pick .st.ok { color: var(--accent); }
368
- .pick .st.warn { color: #a8742a; }
369
-
370
- .open { display: flex; gap: 8px; margin-bottom: 12px; }
371
- #url {
372
- flex: 1; padding: 11px 14px; border: 1px solid var(--line); border-radius: var(--r-lg);
373
- font: 13px var(--ui); color: var(--ink); outline: none; background: var(--bg);
374
- }
375
- #url:focus { border-color: var(--ink3); }
376
- #go, #s1next {
377
- padding: 11px 20px; border: 0; border-radius: 11px;
378
- background: var(--accent); color: var(--on-accent); font-size: 13px;
379
- }
380
- #go:disabled, #s1next:disabled { background: var(--line); color: var(--ink3); cursor: default; }
381
- #pick {
382
- width: 100%; padding: 11px; margin-bottom: 12px;
383
- border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--bg);
384
- color: var(--ink2); font-size: 13px;
385
- }
386
- #pick:hover { border-color: var(--ink3); color: var(--ink); }
387
-
388
- #omsg { min-height: 20px; font-size: 12px; color: var(--ink2); margin-bottom: 14px; }
389
- #omsg.err { color: var(--warn); }
390
-
391
- .act { display: flex; gap: 8px; justify-content: center; align-items: center; }
392
- .ghost {
393
- border: 0; background: transparent; color: var(--ink3);
394
- font-size: 12px; padding: 8px 10px; border-radius: 8px;
395
- }
396
- .ghost:hover { background: var(--hover); color: var(--ink2); }
397
-
398
- /* ══ 모달 ═══════════════════════════════════════════════════════════════════
399
- 처음 오는 사람에게는 화면 전체를 쓰고, 그 뒤 `저장소 열기` 로 부를 때는
400
- 모달로 뜬다. 같은 markup 을 쓰되 겉모습만 바꾼다 — 두 벌을 만들면
401
- 한쪽만 고쳐서 어긋난다.
402
- ────────────────────────────────────────────────────────────────────────── */
403
-
404
- #onboard.as-modal { background: rgba(20, 20, 19, .34); }
405
- #onboard.as-modal .card {
406
- background: var(--bg);
407
- border: 1px solid var(--line);
408
- border-radius: var(--r-xl);
409
- box-shadow: 0 18px 48px rgba(0, 0, 0, .18);
410
- padding: 34px 34px 26px;
411
- max-width: 480px;
412
- }
413
- /* 모달일 때는 걸음 표시가 뜻이 없다 — 한 걸음짜리다 */
414
- #onboard.as-modal .steps { display: none; }
415
-
416
- #oclose {
417
- position: absolute; top: 14px; right: 16px;
418
- border: 0; background: transparent; color: var(--ink3);
419
- font-size: 15px; line-height: 1; padding: 6px 8px; border-radius: var(--r-sm);
420
- }
421
- #oclose:hover { background: var(--hover); color: var(--ink); }
422
- #onboard:not(.as-modal) #oclose { display: none; }
423
- #onboard .card { position: relative; }
424
-
425
- /* ══ 가운데 칸 — ladder 를 번호 카드로 ══════════════════════════════════════
426
- 조판 규율은 Zap 편집기에서 가져왔다. 실측값과 근거는 stage.js 머리말과
427
- docs/RESEARCH-FLOWCANVAS.md 1.1 에 있다.
428
- ────────────────────────────────────────────────────────────────────────── */
429
-
430
- #center { flex: 1; min-width: 0; display: block; overflow: auto; }
431
-
432
- #stage { position: relative; padding: 22px 26px 40px; min-height: 100%; }
433
- #stage.loading, #stage.note {
434
- display: flex; align-items: center; justify-content: center;
435
- color: var(--ink3); font-size: 12px; text-align: center; padding: 40px;
436
- }
437
-
438
- /* 선은 카드 아래에 깔린다. 클릭을 막지 않는다. */
439
- .wires { position: absolute; left: 0; top: 0; pointer-events: none; }
440
- /*
441
- 🔴 선이 조용해야 한다. 실측한 Zapier 연결선은 바탕 대비 4.4:1 밖에 안 된다 —
442
- 주인공은 카드의 글자이고 선은 배경 구조물이라는 선언이다.
443
- */
444
- .w { fill: none; stroke: var(--line); stroke-width: 1.5; }
445
-
446
- .board { position: relative; }
447
-
448
- /* ── 겹 ─────────────────────────────────────────────────────────────────── */
449
-
450
- .layer { display: flex; gap: 14px; align-items: flex-start; margin-bottom: 34px; }
451
-
452
- .lhead {
453
- flex: none; width: 54px; padding-top: 4px; text-align: right;
454
- display: flex; flex-direction: column; gap: 1px;
455
- }
456
- .lno { font-size: 12px; color: var(--ink); }
457
- .lcount { font-size: 10px; color: var(--ink3); }
458
-
459
- .cols { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; gap: 12px 22px; align-items: flex-start; }
460
-
461
- /* ── 묶음 ───────────────────────────────────────────────────────────────── */
462
-
463
- .grp { display: flex; flex-direction: column; gap: 8px; }
464
-
465
- /* 가지 머리의 알약 칩. Zap 편집기가 조건문을 두는 자리에 우리는 디렉터리를 둔다. */
466
- .chip {
467
- display: inline-flex; align-items: baseline; gap: 6px; align-self: flex-start;
468
- padding: 3px 10px; border-radius: var(--r-pill, 999px);
469
- border: 1px solid var(--line); background: var(--bg);
470
- font: 11px var(--mono); color: var(--ink2);
471
- max-width: 210px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
472
- }
473
- .chip .cn { color: var(--accent); font-size: 10px; }
474
-
475
- /* ── 카드 ───────────────────────────────────────────────────────────────── */
476
-
477
- /*
478
- 폭을 고정한다. 실측한 Zapier 카드는 폭:높이 = 4.2:1 이고, 카드 사이 세로 여백이
479
- 카드 높이보다 크다(1.24배). 그 비율이 이 화면이 빽빽하지 않게 느껴지는 이유다.
480
- */
481
- .card {
482
- width: 210px; box-sizing: border-box;
483
- padding: 8px 11px 9px;
484
- border: 1px solid var(--line); border-radius: var(--r-md);
485
- background: var(--bg);
486
- position: relative; z-index: 1;
487
- }
488
- .card:hover { border-color: var(--ink3); }
489
- .card.aux { opacity: .62; }
490
-
491
- .card .r1 {
492
- display: flex; gap: 8px; align-items: baseline;
493
- font-size: 10px; color: var(--ink3); margin-bottom: 3px;
494
- }
495
- .card .r1 .opens { color: var(--accent); }
496
- .card .r1 .lines { margin-left: auto; }
497
- .card .r1 .tag { color: var(--ink3); }
498
-
499
- .card .r2 { display: flex; gap: 7px; align-items: baseline; }
500
- /* 🔴 번호가 이 카드의 정체다. 그림을 안 봐도 이 번호로 서로를 가리킬 수 있다. */
501
- .card .r2 .no { font: 11px var(--mono); color: var(--ink3); flex: none; }
502
- .card .r2 .nm {
503
- font-size: 13px; color: var(--ink); min-width: 0;
504
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
505
- }
506
- .card .dir {
507
- font: 10px var(--mono); color: var(--ink3); padding-top: 2px;
508
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
509
- }
510
-
511
- .more { font-size: 11px; color: var(--ink3); align-self: center; }
512
-
513
- .foot2 {
514
- display: flex; flex-wrap: wrap; gap: 14px;
515
- padding-top: 8px; border-top: 1px solid var(--line);
516
- font-size: 11px; color: var(--ink3);
517
- }
518
- /* 자른 것은 잘랐다고 말한다. 조용히 자르면 "이게 전부" 로 읽힌다. */
519
- .foot2 .warn2 { color: var(--accent); }
520
-
521
- /* 프로젝트 고르는 줄 — 모노레포에서 "어디서부터" 의 첫 답이다 */
522
- .projs {
523
- display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
524
- margin-bottom: 22px; padding-bottom: 16px; border-bottom: 1px solid var(--line);
525
- }
526
- .proj {
527
- display: inline-flex; align-items: baseline; gap: 7px;
528
- padding: 5px 12px; border-radius: var(--r-pill, 999px);
529
- border: 1px solid var(--line); background: var(--bg); color: var(--ink2);
530
- font-size: 12px;
531
- }
532
- .proj:hover { border-color: var(--ink3); color: var(--ink); }
533
- .proj.on { border-color: var(--accent); background: var(--accent-tint); color: var(--ink); }
534
- .proj .pn { font-family: var(--mono); font-size: 11px; }
535
- .proj .pf { color: var(--accent); font-size: 10px; }
536
- /* 🔴 선언된 것과 짐작한 것을 눈으로 구분한다 (D5) */
537
- .proj .guess { color: var(--ink3); font-size: 10px; }
538
- .pout { font-size: 11px; color: var(--ink3); margin-left: 4px; }