@m1kapp/kit 0.0.24 → 0.0.26

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 CHANGED
@@ -40,9 +40,11 @@ export default function App() {
40
40
 
41
41
  ## 모듈 구성
42
42
 
43
+ > **하나의 import면 끝.** UI · OG · PWA · Fetch · Utils가 전부 `@m1kapp/kit` 메인 export에 들어 있습니다. `/server`만 별도 서브패스예요. 서브패스를 일일이 외울 필요 없이 `import { ... } from "@m1kapp/kit"` 하나로 다 꺼내 쓰세요.
44
+
43
45
  | 모듈 | import | 설명 |
44
46
  |---|---|---|
45
- | UI | `@m1kapp/kit` | 컴포넌트 24개 + 훅 |
47
+ | UI | `@m1kapp/kit` | 컴포넌트 45개 + 훅 |
46
48
  | OG Image | `@m1kapp/kit` | OG 이미지 생성 (서버) |
47
49
  | PWA | `@m1kapp/kit` | manifest, viewport, 설치 유도 |
48
50
  | Fetch | `@m1kapp/kit` | 캐싱·중복제거·재시도 fetch 유틸 |
@@ -51,6 +53,27 @@ export default function App() {
51
53
 
52
54
  ---
53
55
 
56
+ ## 레시피 — 이럴 땐 이거
57
+
58
+ 손으로 만들기 전에 먼저 찾아보세요. 흔한 화면은 대부분 조합으로 끝납니다.
59
+
60
+ | 하고 싶은 것 | 이렇게 |
61
+ |---|---|
62
+ | 모바일 앱 셸 | `AppShell` + `AppShellHeader` / `AppShellContent` + `TabBar`/`Tab` + `Watermark` |
63
+ | 목록 + 로딩 + 빈 화면 | `useFetch` → 로딩이면 `Skeleton`, 빈 배열이면 `EmptyState` |
64
+ | 폼 저장 후 피드백 | `useFormSubmit` + `useToast` (인라인 텍스트 대신 토스트) |
65
+ | 설정 화면 | `Section` + `Field`(`inline`) + `Switch` |
66
+ | 인라인 토글 (오늘/이번주) | `SegmentedControl` (하단 글로벌 내비는 `TabBar`) |
67
+ | 채팅 / AI 비서 | `MessageList` + `ChatBubble` + `TypingIndicator` |
68
+ | "이렇게 할까요?" 확인 | `ActionCard` (메시지 흐름 안), 모달이면 `Dialog` |
69
+ | 일정 / 타임라인 행 | `ListRow` (`sizeByMinutes`로 소요시간 비례 높이) |
70
+ | 메모 속 URL 링크 | `LinkifiedText` |
71
+ | API 호출 | `createApiClient` (`get`/`post`/`put`/`delete` + `ApiError`) |
72
+ | 주기적 갱신 | `usePolling` (`pauseOnHidden`) |
73
+ | 테마색 한 번에 | `<AppShell accent="#e2603f">` → 모든 컴포넌트가 `--kit-accent`로 따라옴 |
74
+
75
+ ---
76
+
54
77
  ## UI
55
78
 
56
79
  CSS가 import 시 자동 주입됩니다. 별도 스타일시트 import 불필요.
@@ -246,6 +269,169 @@ colors.green // "#22c55e"
246
269
  // blue | purple | green | orange | pink | red | yellow | cyan | slate | zinc
247
270
  ```
248
271
 
272
+ **임의 색도 OK.** 팔레트는 프리셋일 뿐이고, `ThemeDialog`의 `palette` / `onSelect`는 어떤 hex든 받습니다. 그리고 `Switch`·`SegmentedControl`·`ChatBubble`·`ActionCard`·`ListRow`·`LinkifiedText`의 accent는 `--kit-accent` CSS 변수를 따라가므로, 한 곳만 바꾸면 전체 톤이 바뀝니다.
273
+
274
+ ```tsx
275
+ // ① AppShell accent prop — 가장 쉬움 (하위 전체에 적용)
276
+ <AppShell accent="#e2603f">...</AppShell>
277
+
278
+ // ② 아무 래퍼에나 CSS 변수로
279
+ <div style={{ "--kit-accent": "#e2603f" } as React.CSSProperties}>...</div>
280
+
281
+ // ③ 컴포넌트별 override
282
+ <Switch checked={on} onChange={setOn} accent="#e2603f" />
283
+ ```
284
+
285
+ ### 폼 · 설정
286
+
287
+ ```tsx
288
+ import { Field, Switch } from "@m1kapp/kit";
289
+
290
+ // 라벨드 인풋 (stacked 기본 / inline / multiline)
291
+ <Field label="이름" value={name} onChange={setName} placeholder="이름" />
292
+ <Field label="이메일" value={email} inline readOnly />
293
+ <Field label="메모" value={memo} onChange={setMemo} multiline rows={3} hint="자유롭게 적어주세요" />
294
+
295
+ // on/off 토글 — accent는 --kit-accent 자동 연동
296
+ <Switch checked={on} onChange={setOn} aria-label="알림" />
297
+ ```
298
+
299
+ ### 세그먼트 토글
300
+
301
+ ```tsx
302
+ import { SegmentedControl } from "@m1kapp/kit";
303
+
304
+ <SegmentedControl
305
+ value={view}
306
+ onChange={setView}
307
+ options={[{ value: "today", label: "오늘" }, { value: "week", label: "이번 주" }]}
308
+ />
309
+ // 인라인 토글용. 하단 글로벌 내비게이션은 TabBar를 쓰세요.
310
+ ```
311
+
312
+ ### 채팅 / 대화
313
+
314
+ ```tsx
315
+ import { MessageList, ChatBubble, TypingIndicator } from "@m1kapp/kit";
316
+ import type { ChatMessage } from "@m1kapp/kit";
317
+
318
+ const messages: ChatMessage[] = [
319
+ { role: "user", content: "내일 3시 회의 잡아줘", timestamp: Date.now() },
320
+ { role: "assistant", content: "네, 잡아드릴게요." },
321
+ ];
322
+
323
+ // dayDivider: timestamp 기준 날짜가 바뀌면 구분선 자동 삽입
324
+ <MessageList messages={messages} dayDivider>
325
+ {pending && <TypingIndicator />}
326
+ </MessageList>
327
+
328
+ // 직접 쓰려면
329
+ <ChatBubble role="user">오늘 일정 보여줘</ChatBubble>
330
+ <ChatBubble role="assistant">3건 있어요.</ChatBubble>
331
+ ```
332
+
333
+ ### 확인 카드 (propose → confirm → execute)
334
+
335
+ LLM 툴콜처럼 "제안 → 확인 → 실행" 흐름을 메시지 안에 박을 때. 모달이 아니라 흐름에 인라인됩니다.
336
+
337
+ ```tsx
338
+ import { ActionCard } from "@m1kapp/kit";
339
+
340
+ const [state, setState] = useState<"pending" | "loading" | "done" | "cancelled">("pending");
341
+
342
+ <ActionCard
343
+ title="이렇게 기록해둘까요?"
344
+ state={state}
345
+ items={["🗓 6/4 15:00 디자인 리뷰", "📌 6/4 (종일) 마감일"]}
346
+ onConfirm={() => { setState("loading"); /* … */ setState("done"); }}
347
+ onCancel={() => setState("cancelled")}
348
+ />
349
+ // state별 색/문구 자동: pending → done(초록) / cancelled(취소선) / loading
350
+ ```
351
+
352
+ ### 리스트 / 타임라인 행
353
+
354
+ ```tsx
355
+ import { ListRow } from "@m1kapp/kit";
356
+
357
+ <ListRow
358
+ accent="#7fc06a" // 왼쪽 컬러바 (생략 시 --kit-accent)
359
+ lead="14:00" leadSub="15:00"
360
+ title="디자인 리뷰" sub="👥 김상훈"
361
+ trailing="● 지금" active // 현재 항목 강조
362
+ heightScale={60 / 30} // 높이 배율 (1=기본, 46–130px). 일정은 분/30을 넘김
363
+ onClick={open}
364
+ />
365
+ ```
366
+
367
+ ### 텍스트 내 링크
368
+
369
+ ```tsx
370
+ import { LinkifiedText } from "@m1kapp/kit";
371
+
372
+ <LinkifiedText>{"회의록: https://docs.google.com/…\n화상: meet.google.com/abc-def"}</LinkifiedText>
373
+ // http(s) URL + (기본) meet.google.com 같은 도메인/경로를 자동 링크. 줄바꿈 보존.
374
+ ```
375
+
376
+ ### 진행 표시 · 단계
377
+
378
+ ```tsx
379
+ import { Stepper, Collapsible, ProgressRing } from "@m1kapp/kit";
380
+
381
+ // 다단계 진행 표시 (위저드/멀티페이즈 플로우)
382
+ <Stepper current={phase} onStepClick={setPhase} steps={[
383
+ { label: "분석", icon: "📝" }, { label: "준비", icon: "📸" }, { label: "생성", icon: "✨" },
384
+ ]} />
385
+
386
+ // 접기 카드 (헤더+배지+상태)
387
+ <Collapsible leading={1} title="페르소나 분석" subtitle="스타일 파악" completed
388
+ open={open === 1} onToggle={() => setOpen(open === 1 ? null : 1)}>
389
+ <p>본문…</p>
390
+ </Collapsible>
391
+
392
+ // 원형 진행률
393
+ <ProgressRing value={7} max={10}><span className="text-2xl font-black">7</span></ProgressRing>
394
+ ```
395
+
396
+ ### 입력 · 편집
397
+
398
+ ```tsx
399
+ import { Select, ColorPicker, InlineEdit } from "@m1kapp/kit";
400
+
401
+ // 앵커드 드롭다운 (카운트·비활성 옵션, 외부클릭/ESC 닫힘)
402
+ <Select value={cat} onChange={setCat} placeholder="난이도" options={[
403
+ { value: "a", label: "초급", count: 12 }, { value: "b", label: "고급", count: 0, disabled: true },
404
+ ]} />
405
+
406
+ // 컬러 피커 (프리셋 + 커스텀 hex)
407
+ <ColorPicker value={color} onChange={setColor} />
408
+
409
+ // 탭하여 편집 (Enter 저장 · Esc 취소)
410
+ <InlineEdit value={name} onChange={rename} className="text-lg font-bold" />
411
+ ```
412
+
413
+ ### 복사 · 데이터
414
+
415
+ ```tsx
416
+ import { CopyButton, CodeBlock, useCopy, BarList, Countdown, Carousel, Img } from "@m1kapp/kit";
417
+
418
+ <CopyButton text="npm i @m1kapp/kit">설치 복사</CopyButton>
419
+ <CodeBlock label="install" code="npm i @m1kapp/kit" />
420
+ const { copied, copy } = useCopy(); // keyed: copy(text, "row-3")
421
+
422
+ // 가로 막대 분석 차트
423
+ <BarList items={[{ label: "/", value: 120 }, { label: "/about", value: 64, href: "/about" }]} />
424
+
425
+ // 카운트다운 (D-day)
426
+ <Countdown to="2026-12-31" onComplete={celebrate} />
427
+
428
+ // 스와이프 캐러셀 (controlled, 점 인디케이터)
429
+ <Carousel count={slides.length} index={i} onChange={setI}>{slides[i]}</Carousel>
430
+
431
+ // 다중 URL 폴백 이미지
432
+ <Img candidates={[avatarUrl, gravatar]} fallback={<Avatar fallback="MH" />} className="h-10 w-10 rounded-full" />
433
+ ```
434
+
249
435
  ### 워터마크
250
436
 
251
437
  ```tsx
@@ -256,6 +442,32 @@ import { Watermark } from "@m1kapp/kit";
256
442
  </Watermark>
257
443
  ```
258
444
 
445
+ `Watermark`는 하단에 `PoweredByKit` 크레딧을 자동 내장합니다.
446
+
447
+ ### 방문자 트래커 (m1k.app) — 선택, 기본 OFF
448
+
449
+ `Watermark`/`PoweredByKit` 하단 크레딧이 방문자 수를 집계할 수 있습니다. **slug가 있을 때만** 켜지고, 없으면 아무것도 전송하지 않아요(기본 off). 집계는 페이지뷰 카운트뿐 — PII 없음.
450
+
451
+ ```bash
452
+ # 1) 사이트 등록 (무로그인) → slug 발급
453
+ npx m1kkit track https://myside.app
454
+ ```
455
+
456
+ ```bash
457
+ # 2) .env 에 한 줄 → 이후 자동 집계 (스니펫 붙여넣기 불필요)
458
+ NEXT_PUBLIC_M1K_SLUG=your-slug
459
+ ```
460
+
461
+ ```tsx
462
+ // 끄기 / 명시 지정
463
+ <Watermark track={false}>…</Watermark> // 비콘 끔
464
+ <Watermark trackSlug="your-slug">…</Watermark> // env 대신 직접 지정
465
+ // PoweredByKit 단독 사용 시: <PoweredByKit slug="your-slug" track={false} />
466
+ ```
467
+
468
+ > `npx m1kkit claim` 으로 익명 등록 사이트를 내 m1k.app 계정에 귀속할 수 있어요.
469
+ > (`.m1k.json`의 일회용 토큰 + 로그인으로 인증.) 귀속하는 김에 **kit이 이 프로젝트에서 코드를 얼마나 아껴줬는지** 분석도 같이 출력해요 — `--no-stats`로 끌 수 있어요.
470
+
259
471
  ---
260
472
 
261
473
  ## OG Image
@@ -545,11 +757,18 @@ export const GET = handler(async () => {
545
757
  순수 함수 — 의존성 없음, 어디서나 import.
546
758
 
547
759
  ```ts
548
- import { relativeTime, formatNumber, formatPrice, cn } from "@m1kapp/kit";
760
+ import { relativeTime, formatNumber, formatPrice, cn, formatDuration, groupByDay } from "@m1kapp/kit";
549
761
 
550
762
  // 상대 시간
551
763
  relativeTime(post.createdAt) // "3분 전", "어제", "2025. 4. 19."
552
764
 
765
+ // 소요 시간 포맷
766
+ formatDuration(90_000) // "1분 30초"
767
+ formatDuration(3_661_000, { style: "clock" }) // "1:01:01"
768
+
769
+ // 날짜별 그룹핑 — [{ date, label: "오늘"|"어제"|"4월 19일 (토)", items }]
770
+ groupByDay(logs, (l) => l.timestamp)
771
+
553
772
  // 숫자 포맷
554
773
  formatNumber(1_500) // "1.5천"
555
774
  formatNumber(15_000) // "1.5만"
package/bin/claim.mjs ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * m1kkit claim [--token=xxx] [--host=m1k.app] [--no-stats]
4
+ *
5
+ * 익명 등록한 사이트를 내 계정에 귀속한다.
6
+ * claim은 로그인(브라우저 세션)이 필요하므로, 토큰을 들고 브라우저의 claim 페이지를 연다.
7
+ * 토큰은 --token 또는 ./.m1k.json 에서 읽는다.
8
+ * 귀속하는 김에 'stats' 분석도 같이 돌려 kit이 얼마나 아껴줬는지 보여준다 (--no-stats 로 끔).
9
+ */
10
+ import { existsSync, readFileSync } from "fs";
11
+ import { resolve, dirname, join } from "path";
12
+ import { fileURLToPath } from "url";
13
+ import { exec, spawnSync } from "child_process";
14
+
15
+ const args = process.argv.slice(2);
16
+ const flags = Object.fromEntries(
17
+ args.filter((a) => a.startsWith("--")).map((a) => {
18
+ const [k, v] = a.replace(/^--/, "").split("=");
19
+ return [k, v ?? true];
20
+ }),
21
+ );
22
+
23
+ let token = flags.token;
24
+ let host = flags.host || process.env.M1K_HOST || "m1k.app";
25
+
26
+ if (!token) {
27
+ const file = resolve(process.cwd(), ".m1k.json");
28
+ if (existsSync(file)) {
29
+ try {
30
+ const store = JSON.parse(readFileSync(file, "utf8"));
31
+ token = store.claimToken;
32
+ host = flags.host || store.host || host;
33
+ } catch { /* ignore */ }
34
+ }
35
+ }
36
+
37
+ if (!token || flags.help) {
38
+ console.log(`
39
+ m1kkit claim — 익명 등록 사이트를 내 계정에 귀속
40
+
41
+ 사용법:
42
+ npx m1kkit claim [--token=<claimToken>] [--host=m1k.app] [--no-stats]
43
+
44
+ 토큰을 안 주면 현재 폴더의 ./.m1k.json 에서 읽어요.
45
+ 귀속은 로그인이 필요해서 브라우저의 claim 페이지를 엽니다.
46
+ 귀속하는 김에 코드 분석(stats)도 같이 돌려 kit이 얼마나 아껴줬는지 보여줘요. (--no-stats 로 끔)
47
+ `);
48
+ process.exit(token ? 0 : 1);
49
+ }
50
+
51
+ const scheme = /^(localhost|127\.|0\.0\.0\.0)/.test(host) ? "http" : "https";
52
+ const claimUrl = `${scheme}://${host}/claim?token=${encodeURIComponent(token)}`;
53
+ console.log(`\n브라우저에서 로그인 후 귀속하세요:\n ${claimUrl}\n`);
54
+
55
+ // 플랫폼별 브라우저 열기 (실패해도 URL은 위에 출력됨)
56
+ const opener =
57
+ process.platform === "darwin" ? "open" :
58
+ process.platform === "win32" ? 'start ""' :
59
+ "xdg-open";
60
+ exec(`${opener} "${claimUrl}"`, (err) => {
61
+ if (err) console.log("(브라우저 자동 실행 실패 — 위 URL을 직접 열어주세요)");
62
+ });
63
+
64
+ // 인증하는 김에 — 이 프로젝트에서 kit이 얼마나 아껴줬는지 같이 분석 (--no-stats 로 끔)
65
+ if (!flags["no-stats"]) {
66
+ const here = dirname(fileURLToPath(import.meta.url));
67
+ console.log("\n📊 그리고 — 이 프로젝트에서 kit이 얼마나 아껴줬는지 볼게요 👇\n");
68
+ const statsArgs = [join(here, "stats.mjs")];
69
+ if (typeof flags.dir === "string") statsArgs.push(`--dir=${flags.dir}`);
70
+ if (typeof flags.out === "string") statsArgs.push(`--out=${flags.out}`);
71
+ const r = spawnSync(process.execPath, statsArgs, { stdio: "inherit" });
72
+ if (r.status !== 0) {
73
+ console.log("\n(분석은 건너뛰었어요 — 나중에 'npx m1kkit stats' 로 직접 실행할 수 있어요)");
74
+ } else {
75
+ console.log("\n✓ 분석 결과는 kit-stats.json 에 저장됐고, 하단 'powered by' 크레딧 시트에서도 보여요.");
76
+ }
77
+ }
package/bin/m1kkit.mjs CHANGED
@@ -18,6 +18,8 @@ Commands:
18
18
  m1kkit favicon [options] 파비콘 자동 생성
19
19
  m1kkit skills [options] Claude Code 스킬 설치
20
20
  m1kkit stats [options] 코드 분석 & kit 사용 현황 생성
21
+ m1kkit track <url> m1k 방문자 트래커에 사이트 등록(무로그인)
22
+ m1kkit claim [--token=x] 등록한 사이트를 내 계정에 귀속
21
23
 
22
24
  Options:
23
25
  --help 도움말 보기
@@ -28,6 +30,8 @@ Examples:
28
30
  m1kkit skills --list
29
31
  m1kkit skills m1kapp-init
30
32
  m1kkit stats --dir=src --out=public
33
+ m1kkit track https://myside.app
34
+ m1kkit claim
31
35
  `);
32
36
  process.exit(0);
33
37
  }
@@ -48,6 +52,12 @@ if (command === "favicon") {
48
52
  } else if (command === "stats") {
49
53
  process.argv = [process.argv[0], process.argv[1], ...rest];
50
54
  await import(path.join(__dirname, "stats.mjs"));
55
+ } else if (command === "track") {
56
+ process.argv = [process.argv[0], process.argv[1], ...rest];
57
+ await import(path.join(__dirname, "track.mjs"));
58
+ } else if (command === "claim") {
59
+ process.argv = [process.argv[0], process.argv[1], ...rest];
60
+ await import(path.join(__dirname, "claim.mjs"));
51
61
  } else {
52
62
  console.error(`알 수 없는 커맨드: ${command}`);
53
63
  console.error("사용법: m1kkit --help");
@@ -1,18 +1,60 @@
1
1
  ---
2
2
  name: m1kapp-init
3
- description: "@m1kapp/kit 기반 Next.js 프로젝트 초기 설정을 인터랙티브하게 완성합니다."
3
+ description: "@m1kapp/kit 기반 Next.js 프로젝트 초기 설정을 인터랙티브하게 완성합니다. (앱쉘 레이아웃 + SEO + PWA + OG + 파비콘)"
4
4
  ---
5
5
 
6
6
  현재 디렉토리가 Next.js + @m1kapp/kit 프로젝트인지 확인한 뒤, 아래 순서대로 진행한다.
7
7
 
8
+ ---
9
+
10
+ ## ⚠️ 핵심 규칙 — 매번 여기서 틀린다. 먼저 읽어라.
11
+
12
+ @m1kapp/kit은 단순 SEO 유틸이 아니라 **모바일 앱 셸 UI 프레임워크**다. 아래는 한 번에 되게 하려면 반드시 지킬 것:
13
+
14
+ 1. **`app/layout.tsx`에 스타일/셸 import 3종 세트는 필수다.** 하나라도 빠지면 컴포넌트가 스타일 없이 깨지거나 좌측 정렬된다.
15
+ ```ts
16
+ import "@m1kapp/kit/styles.css"; // ← 없으면 AppShell/모든 컴포넌트 스타일 안 먹음 (제일 자주 빠뜨림)
17
+ import { KitStyles, mobileViewport } from "@m1kapp/kit/pwa";
18
+ // <head> 안에 <KitStyles />, 그리고 export const viewport = mobileViewport;
19
+ ```
20
+
21
+ 2. **앱 화면은 반드시 이 트리로 감싼다.** `AppShell`만 쓰면 화면 좌측에 붙고 휑하다. 중앙정렬·풀화면 배경·powered-by 풋터는 `Watermark`가 담당한다.
22
+ ```tsx
23
+ "use client";
24
+ import { Watermark, AppShell, AppShellHeader, AppShellContent, TabBar, Tab } from "@m1kapp/kit";
25
+
26
+ <Watermark color="#0a0d16" text="앱이름" maxWidth={460}>
27
+ <AppShell maxWidth={460}>
28
+ <AppShellHeader>…헤더…</AppShellHeader>
29
+ <AppShellContent>…스크롤 본문…</AppShellContent>
30
+ <TabBar>
31
+ <Tab active={…} onClick={…} label="홈" icon={<span>🏠</span>} activeColor="[테마컬러]" />
32
+ </TabBar>
33
+ </AppShell>
34
+ </Watermark>
35
+ ```
36
+ - `AppShell`/`TabBar`는 인터랙티브 → 이 트리는 **`"use client"` 컴포넌트**에 둔다. 데이터는 서버 컴포넌트(page.tsx)에서 계산해 props로 내려준다.
37
+ - `maxWidth`는 Watermark와 AppShell을 **같은 값**으로 맞춘다(기본 430~460).
38
+
39
+ 3. **이모지는 토스페이스로 통일한다.** 장식용 이모지는 남발하지 말고, 쓰는 이모지는 토스 스타일로 폴백시킨다.
40
+ - layout `<head>`에 `<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/toss/tossface/dist/tossface.css" />`
41
+ - `globals.css` 폰트 스택에 `"Tossface"`를 본문/디스플레이 폰트 **뒤에** 끼우면 이모지 글자만 자동 폴백된다.
42
+
43
+ 4. **🚫 `node_modules/@m1kapp/kit` 디렉토리 안에서 `npm run build`/스크립트를 절대 돌리지 마라.** kit 자체 빌드가 돌아 `dist/`를 날린다. 깨지면 `rm -rf node_modules/@m1kapp/kit && npm install @m1kapp/kit`로 복구. 빌드는 항상 프로젝트 루트에서.
44
+
45
+ 5. **kit 컴포넌트 카탈로그**(필요 시 골라 쓰기): 레이아웃 `AppShell/AppShellHeader/AppShellContent`, 내비 `TabBar/Tab/Fab`, 표시 `Section/SectionHeader/StatChip/Badge/Avatar/EmptyState/Divider`, 브랜딩 `Watermark`, 입력 `Button/EmojiButton`, 테마 `ThemeButton/ThemeDialog`, 모션 `Typewriter`. SEO는 `@m1kapp/kit/seo`, OG는 `@m1kapp/kit/ogimage`, PWA는 `@m1kapp/kit/pwa`.
46
+
47
+ ---
48
+
8
49
  ## Step 0: 프로젝트 파악
9
50
 
10
51
  먼저 조용히 다음을 확인한다 (사용자에게 보고하지 않음):
11
52
  - `package.json` — 프레임워크, @m1kapp/kit 버전
12
- - `app/layout.tsx` — 기존 metadata 여부
13
- - `app/sitemap.ts`, `app/robots.ts` 존재 여부
53
+ - `app/layout.tsx` — 기존 metadata / styles.css import / KitStyles 여부
54
+ - `app/page.tsx` 이미 앱쉘로 감겨 있는지
55
+ - `app/sitemap.ts`, `app/robots.ts`, `app/manifest.ts`, `app/og/route.tsx` 존재 여부
14
56
  - `public/` — 파비콘, OG 이미지 여부
15
- - `tailwind.config.*` — 테마 컬러 여부
57
+ - `globals.css` / `tailwind.config.*` — 테마 컬러, 폰트 스택 여부
16
58
 
17
59
  파악이 끝나면 아래 질문들을 **한 번에 모아서** 사용자에게 묻는다.
18
60
 
@@ -26,134 +68,108 @@ description: "@m1kapp/kit 기반 Next.js 프로젝트 초기 설정을 인터랙
26
68
  @m1kapp/kit 초기 설정을 시작할게요. 몇 가지만 확인할게요!
27
69
 
28
70
  1. 앱 이름이 뭔가요?
29
- (예: "My App", "스타트업 서비스명")
30
-
31
- 2. 메인 테마 컬러가 있나요?
32
- (hex 코드로 알려주세요. 없으면 기본 파란색 #3B82F6 쓸게요)
33
-
34
- 3. 설명이 뭔가요?
35
- (메타 description + OG에 사용됩니다)
36
-
37
- 4. 배포 URL이 있나요?
38
- (예: https://myapp.comSEO canonical, sitemap 등에 사용)
39
-
40
- 5. 유형은 뭔가요?
41
- (1) 커머스/쇼핑
42
- (2) 블로그/콘텐츠
43
- (3) 대시보드/툴
44
- (4) 소셜/커뮤니티
45
- (5) 랜딩/홍보
46
- (6) 기타
47
-
48
- 6. 다음 중 적용할 것을 골라주세요 (복수 선택, 예: 1 2 3)
49
- (1) SEO — metadata, sitemap, robots, JSON-LD
50
- (2) PWA — manifest, 설치 유도 버튼, 아이콘
51
- (3) OG 이미지 — /og 라우트 자동 생성
52
- (4) 파비콘 — 자동 생성 (m1kkit 필요)
71
+ 2. 메인 테마 컬러가 있나요? (hex. 없으면 #3B82F6)
72
+ 3. 앱 한 줄 설명이 뭔가요? (메타 description + OG)
73
+ 4. 배포 URL이 있나요? (예: https://myapp.com)
74
+ 5. 유형은? (1)커머스 (2)블로그 (3)대시보드/툴 (4)소셜 (5)랜딩 (6)기타
75
+
76
+ 6. 적용할 것을 골라주세요 (복수 선택)
77
+ (1) 앱쉘 레이아웃 — Watermark + AppShell + 헤더 + TabBar ← 추천 (앱의 뼈대)
78
+ (2) Tossface 이모지 — 토스 스타일 이모지 폰트 적용
79
+ (3) SEO metadata, sitemap, robots, JSON-LD
80
+ (4) PWAmanifest, 설치 유도 버튼, 아이콘
81
+ (5) OG 이미지 — /og 라우트 자동 생성
82
+ (6) 파비콘 자동 생성 (m1kkit 필요)
53
83
  ```
54
84
 
85
+ 기본 추천은 **(1)(2)(3)(5)(6) 전체** — 앱쉘 없이 SEO만 깔면 "kit으로 만들었는데 화면이 휑하다"는 결과가 된다.
86
+
55
87
  ---
56
88
 
57
89
  ## Step 2: 적용 계획 출력
58
90
 
59
- 답변을 받으면 아래 형식으로 적용 계획을 보여준다:
60
-
61
91
  ```
62
92
  ✓ 확인했어요! 이렇게 적용할게요:
63
93
 
64
- 앱 이름: [이름]
65
- 테마 컬러: [색상] ████
66
- 설명: [한 줄 설명]
67
- 배포 URL: [URL]
94
+ 앱 이름: [이름] 테마: [색] ████ 배포: [URL]
68
95
 
69
96
  적용 항목:
70
- ☐ app/layout.tsx — metadata, titleTemplate 설정
71
- app/sitemap.ts nextSitemap 생성
72
- ☐ app/robots.ts nextRobots 생성
73
- ☐ app/og/route.tsx OG 이미지 라우트
74
- ☐ app/manifest.ts PWA manifest
75
- ☐ tailwind.config — 테마 컬러 등록
97
+ ☐ app/layout.tsx styles.css/KitStyles/Tossface import, metadata, viewport
98
+ components/AppRoot.tsx Watermark>AppShell>Header/Content/TabBar ("use client")
99
+ ☐ app/page.tsx 데이터 계산 후 <AppRoot/> 렌더
100
+ ☐ app/globals.css 테마 컬러 + Tossface 폰트 스택
101
+ ☐ app/sitemap.ts / robots.ts / manifest.ts / og/route.tsx
76
102
 
77
103
  시작할까요? (y/n)
78
104
  ```
79
105
 
80
- y면 Step 3 진행, n이면 수정할 항목 다시 묻기.
106
+ y면 Step 3, n이면 수정할 항목 다시 묻기.
81
107
 
82
108
  ---
83
109
 
84
110
  ## Step 3: 파일 생성/수정
85
111
 
86
- 확인된 답변을 바탕으로 선택한 항목들을 순서대로 적용한다.
87
- 각 파일 완료 시 체크 표시:
88
-
89
- ```
90
- ✓ app/layout.tsx 완료
91
- ✓ app/sitemap.ts 완료
92
- ✓ app/robots.ts 완료
93
- ...
94
- ```
95
-
96
- ### layout.tsx 적용 규칙
97
- - 기존 파일이 있으면 `metadata` export만 교체, 나머지는 보존
98
- - `@m1kapp/kit/seo`의 `createMetadata`, `titleTemplate` 사용
99
- - 앱 유형에 따라 적절한 JSON-LD도 추가 (WebSite, Organization 등)
112
+ 선택한 항목을 순서대로 적용하고, 파일 완료 시 `✓ 파일명 완료` 표시.
100
113
 
114
+ ### layout.tsx — (모든 프로젝트 공통, 항상 적용)
101
115
  ```ts
102
- import { createMetadata, titleTemplate } from "@m1kapp/kit/seo"
116
+ import "@m1kapp/kit/styles.css"; // ★ 필수
117
+ import { createMetadata, titleTemplate } from "@m1kapp/kit/seo";
118
+ import { KitStyles, mobileViewport } from "@m1kapp/kit/pwa";
119
+ import "./globals.css";
103
120
 
104
121
  export const metadata = createMetadata({
105
- title: "[앱 이름]",
106
- description: "[ 설명]",
107
- url: "[배포 URL]",
108
- siteName: "[앱 이름]",
109
- image: "[배포 URL]/og",
110
- })
122
+ title: "[앱 이름]", description: "[설명]", url: "[URL]",
123
+ siteName: "[ 이름]", image: "[URL]/og",
124
+ });
125
+ export const viewport = mobileViewport;
126
+ // <head>에 <KitStyles /> + (Tossface 선택 시) tossface <link>
127
+ // JSON-LD는 앱 유형에 맞게 jsonLd.website / jsonLd.organization 추가
111
128
  ```
129
+ - 기존 파일이 있으면 import/exports만 보강하고 나머지 구조는 보존.
112
130
 
113
- ### sitemap.ts 적용 규칙
114
- - 이미 있으면 건너뛰고 사용자에게 알림
115
- - `app/` 하위 `page.tsx` 파일 목록을 스캔해서 자동으로 경로 추출
116
- - 동적 라우트(`[id]`)는 TODO 주석으로 표시
131
+ ### 앱쉘 레이아웃 — (1) 선택 시
132
+ - `"use client"` 컴포넌트(예: `components/AppRoot.tsx`)에 위 **핵심 규칙 2번** 트리를 그대로 생성.
133
+ - `page.tsx`(서버 컴포넌트)는 데이터만 계산해서 `<AppRoot data={…} />`로 내려준다.
134
+ - 탭이 여러 개면 `useState`로 전환, 각 탭은 별도 패널 컴포넌트로 분리.
135
+ - `AppShellContent` 안의 섹션은 `Section`/`SectionHeader`/`StatChip` 등 kit 컴포넌트를 우선 사용.
117
136
 
118
- ### robots.ts 적용 규칙
119
- - `nextRobots` 사용
120
- - `/api`, `/admin` 일반적인 disallow 패턴 자동 적용
137
+ ### Tossface 이모지 — (2) 선택 시
138
+ - layout `<head>`: `<link rel="preconnect" href="https://cdn.jsdelivr.net" />` + tossface css `<link>`.
139
+ - `globals.css`: 본문/디스플레이 폰트 스택 끝에 `"Tossface"` 추가. 강제용 `.emoji { font-family:"Tossface"; font-style:normal; line-height:1; }`도 정의.
121
140
 
122
- ### OG 이미지 적용 규칙
123
- - `app/og/route.tsx` 생성
124
- - `@m1kapp/kit/ogimage`의 템플릿 사용
125
- - 앱 이름 + 테마 컬러 반영
141
+ ### sitemap.ts / robots.ts — (3)
142
+ - `nextSitemap("[URL]", [{ path:"/", priority:1 }, …])`, `nextRobots({ disallow:["/api","/admin"], sitemap:"[URL]/sitemap.xml" })`.
143
+ - `app/` 하위 `page.tsx`를 스캔해 경로 자동 추출, 동적 라우트는 TODO 주석.
126
144
 
127
- ### PWA manifest 적용 규칙
128
- - `app/manifest.ts` 생성
129
- - `@m1kapp/kit/pwa`의 `createManifest` 사용
130
- - 테마 컬러 자동 반영
145
+ ### OG 이미지 (5)
146
+ - `app/og/route.tsx`에서 `OGImage`(`@m1kapp/kit/ogimage`) + `loadPretendard()`로 `ImageResponse` 반환.
147
+ - 템플릿: 기본 `default`, 대결/스포츠성은 `match`(home/away), 통계는 `stat` 등 앱 성격에 맞게.
131
148
 
132
- ### tailwind 테마 컬러 적용 규칙
133
- - `tailwind.config.ts` 또는 `globals.css`에 CSS 변수로 추가
134
- - `--color-primary: [hex]` 형태로
149
+ ### PWA manifest (4)
150
+ - `app/manifest.ts`: `createManifest({ name, themeColor:"[색]", icon:{ text:"[약자]" } })` (default export).
151
+
152
+ ### globals.css 테마 컬러
153
+ - Tailwind v4면 `@theme inline`에 `--color-primary: var(--primary)`, `:root`에 `--primary:[hex]`. v3면 `tailwind.config`.
135
154
 
136
155
  ---
137
156
 
138
- ## Step 4: 완료 요약
157
+ ## Step 4: 완료 요약 + 검증
139
158
 
140
159
  ```
141
- 🎉 설정 완료!
142
-
143
- 적용된 파일:
144
- ✓ app/layout.tsx
145
- ✓ app/sitemap.ts
146
- ...
160
+ 🎉 설정 완료! 적용: layout / AppRoot / page / globals / sitemap / robots / manifest / og
147
161
 
148
162
  다음 단계:
149
- 배포 https://search.google.com/search-console 사이트 등록
150
- /og 라우트 접속해서 OG 이미지 확인
163
+ npm run build 타입/라우트 검증 (반드시 프로젝트 루트에서!)
164
+ npm run dev 앱쉘·카운트다운 등 실제 화면 확인
151
165
  → npx m1kkit favicon 으로 파비콘 생성
166
+ → 배포 후 search.google.com/search-console 등록
152
167
  ```
168
+ - 마무리로 **프로젝트 루트에서** `npm run build`를 돌려 타입 에러/모듈 누락이 없는지 확인하고 결과를 보고한다.
153
169
 
154
170
  ---
155
171
 
156
172
  ## 주의사항
157
- - 기존 코드를 덮어쓸 때는 반드시 원본 내용을 보존하면서 필요한 부분만 추가/수정
158
- - TypeScript 타입 에러가 없도록 lint 보고
159
- - 파일 생성 이미 있는 경우 사용자에게 알리고 덮어쓸지 확인
173
+ - 기존 코드를 덮어쓸 때는 원본을 보존하며 필요한 부분만 추가/수정. 파일이 이미 있으면 사용자에게 알리고 확인.
174
+ - TypeScript 타입 에러 없이 마치고, 가능하면 빌드까지 통과시킨 뒤 보고.
175
+ - `styles.css` import 누락 / `Watermark` 미적용 / 빌드를 kit 디렉토리에서 돌리는 것 — 이 셋이 가장 흔한 실패 원인이다. (위 핵심 규칙 참조)