careermate 0.1.2 → 0.1.4

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 (77) hide show
  1. package/AGENTS.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +3 -1
  4. package/apps/web/public/pages/documents.js +7 -2
  5. package/apps/web/public/pages/home.js +4 -7
  6. package/apps/web/public/pages/jobs.js +1 -5
  7. package/apps/web/public/pages/profile.js +3 -3
  8. package/apps/web/public/styles.css +7 -2
  9. package/dist/doctor.mjs +112 -90
  10. package/dist/install-page/analytics.js +97 -0
  11. package/dist/install-page/index.html +77 -56
  12. package/dist/install-page/og.png +0 -0
  13. package/dist/install-page/robots.txt +4 -0
  14. package/dist/install-page/sitemap.xml +21 -0
  15. package/dist/install-page/start.html +13 -10
  16. package/dist/install-page/style.css +75 -40
  17. package/dist/mcp.mjs +112 -90
  18. package/dist/migrate.mjs +95 -82
  19. package/dist/public/pages/documents.js +7 -2
  20. package/dist/public/pages/home.js +4 -7
  21. package/dist/public/pages/jobs.js +1 -5
  22. package/dist/public/pages/profile.js +3 -3
  23. package/dist/public/styles.css +7 -2
  24. package/dist/web.mjs +113 -86
  25. package/docs/FAQ.md +101 -0
  26. package/docs/career-os/README.md +76 -0
  27. package/docs/career-os/eop/cover-letter.md +46 -0
  28. package/docs/career-os/eop/fit-analysis.md +48 -0
  29. package/docs/career-os/eop/interview-prep.md +39 -0
  30. package/docs/career-os/eop/job-analysis.md +42 -0
  31. package/docs/career-os/eop/profile-extraction.md +37 -0
  32. package/docs/career-os/knowledge/ats.md +131 -0
  33. package/docs/career-os/knowledge/company-research.md +137 -0
  34. package/docs/career-os/knowledge/cover-letter.md +135 -0
  35. package/docs/career-os/knowledge/fit-matching.md +121 -0
  36. package/docs/career-os/knowledge/human-writing.md +118 -0
  37. package/docs/career-os/knowledge/interview-behavioral.md +132 -0
  38. package/docs/career-os/knowledge/interview-technical.md +126 -0
  39. package/docs/career-os/knowledge/linkedin-profile.md +152 -0
  40. package/docs/career-os/knowledge/networking-referrals.md +166 -0
  41. package/docs/career-os/knowledge/offer-evaluation-decision.md +121 -0
  42. package/docs/career-os/knowledge/onboarding-first-90-days.md +126 -0
  43. package/docs/career-os/knowledge/portfolio.md +121 -0
  44. package/docs/career-os/knowledge/recruiter-screen.md +105 -0
  45. package/docs/career-os/knowledge/rejection-triage-iteration.md +115 -0
  46. package/docs/career-os/knowledge/resume.md +123 -0
  47. package/docs/career-os/knowledge/salary-negotiation.md +123 -0
  48. package/docs/career-os/knowledge/verifiers/ats-compat.md +64 -0
  49. package/docs/career-os/knowledge/verifiers/consistency.md +61 -0
  50. package/docs/career-os/knowledge/verifiers/human-voice.md +82 -0
  51. package/docs/career-os/knowledge/verifiers/recency-staleness.md +72 -0
  52. package/docs/career-os/knowledge/verifiers/responsiveness-on-target.md +82 -0
  53. package/docs/career-os/knowledge/verifiers/truthfulness.md +67 -0
  54. package/package.json +11 -3
  55. package/packages/db/src/repositories.ts +18 -1
  56. package/packages/mcp-tools/src/tools.ts +5 -5
  57. package/packages/parsers/src/documents.ts +13 -1
  58. package/packages/shared/src/schemas.ts +115 -81
  59. package/scripts/build-dist.mjs +7 -1
  60. package/scripts/gen-og.mjs +1 -1
  61. package/scripts/test.ts +25 -0
  62. package/scripts/ui-smoke.ts +32 -4
  63. package/dist/install-page/.omc/state/agent-replay-55a58c0d-bb6e-43ea-8a05-4b6c93b80b34.jsonl +0 -12
  64. package/dist/install-page/.omc/state/idle-notif-cooldown.json +0 -3
  65. package/dist/install-page/.omc/state/sessions/55a58c0d-bb6e-43ea-8a05-4b6c93b80b34/mission-state.json +0 -147
  66. package/dist/install-page/.omc/state/sessions/55a58c0d-bb6e-43ea-8a05-4b6c93b80b34/pre-tool-advisory-throttle.json +0 -22
  67. package/dist/install-page/.omc/state/sessions/55a58c0d-bb6e-43ea-8a05-4b6c93b80b34/subagent-tracking-state.json +0 -44
  68. package/docs/ARCHITECTURE.md +0 -208
  69. package/docs/CHANGES_V1.md +0 -103
  70. package/docs/DATA_MODEL.md +0 -460
  71. package/docs/DECISIONS.md +0 -277
  72. package/docs/DEMO.md +0 -242
  73. package/docs/ROADMAP.md +0 -135
  74. package/docs/RUNTIME_DISTRIBUTION_RESEARCH.md +0 -135
  75. package/docs/TODO.md +0 -57
  76. package/docs/UX_NOTES.md +0 -247
  77. package/docs/superpowers/specs/2026-06-16-demo-dashboard-deploy-design.md +0 -64
@@ -1,135 +0,0 @@
1
- # 런타임·배포 조사: 완전 원클릭(Codex .exe / Claude `.mcpb`) 실현 가능성
2
-
3
- - **작성일**: 2026-06-15
4
- - **상태**: 조사 완료 / 큰 작업은 **보류(문서만)**
5
- - **관련**: [ADR #1 `node:sqlite` 채택](./DECISIONS.md), [ADR #6 대시보드·MCP의 SQLite(WAL) 공유 + `server.json` 핸드셰이크](./DECISIONS.md), [ROADMAP](./ROADMAP.md)
6
-
7
- > 이 문서는 "사용자가 Node/npx/git을 미리 깔지 않고도 한 번에 설치되는 경험"을 두 경로
8
- > (① OpenAI **Codex Windows 앱** + 단일 `.exe`, ② Claude Desktop **`.mcpb`**)에서 만들 수
9
- > 있는지 조사한 기록이다. 결론과 근거, 그리고 다음에 누가 이어받든 헛수고하지 않도록
10
- > "무엇이 작은 수정이고 무엇이 큰 작업인지"를 남긴다.
11
-
12
- ---
13
-
14
- ## TL;DR
15
-
16
- - **공통 근본 제약**: CareerMate는 **`node:sqlite`(Node ≥22.5의 최신 내장 기능)**에 의존한다.
17
- "무설치 번들 런타임" 전략의 성패는 전부 **"충분히 새로운 Node를 어떻게 확보하느냐"**로 수렴한다.
18
- - **Codex Windows 앱 + 단일 `.exe`**: 패키징(Node SEA)은 **기술적으로 성립**하나, **자가등록 지속성**과
19
- **샌드박스** 두 개의 (2026-05~06 기준) 외부 버그/제약 때문에 **진정한 무인 원클릭은 현재 불가**. → **보류.**
20
- - **Claude `.mcpb`**: 산출물(`dist/mcp.mjs`)은 **Node ≥22.5에서 정상 동작**함을 실증함. 사용자가 겪은
21
- "설치는 됐는데 도구가 안 뜸/빨간 에러"는 **Claude Desktop 내장 Node가 22.5 미만**일 때의 증상으로 추정되며,
22
- 그 경우의 해결은 **저장 계층 rewrite(= 큰 작업)**다. → **로그로 근본 원인 확정 후 결정.**
23
-
24
- ---
25
-
26
- ## 1. 배경 / 질문
27
-
28
- - CareerMate는 로컬 MCP stdio 서버 + 로컬 웹 대시보드. 저장은 `node:sqlite`.
29
- - 목표: 비개발자가 **아무것도 미리 설치하지 않고** 한 번에 쓰기 시작하는 경험.
30
- - 두 경로를 검토:
31
- 1. **Codex Windows 데스크톱 앱**(2026-03 출시, MS Store, 로컬 샌드박스+PowerShell)에 **Node 런타임을
32
- 동봉한 단일 `.exe`**(Node SEA)를 더블클릭 → 자가등록 → 동작.
33
- 2. **Claude Desktop `.mcpb`**(런타임 동봉형 원클릭) — 이미 빌드 파이프라인 존재(`scripts/build-mcpb.mjs`).
34
-
35
- ---
36
-
37
- ## 2. Codex Windows 앱 + 단일 `.exe` (Node SEA)
38
-
39
- ### 2.1 패키징 자체 — 성립 (조건부 가능)
40
-
41
- | 항목 | 판정 | 근거 |
42
- |---|---|---|
43
- | Windows에서 SEA `.exe` 생성 | 가능 | Node SEA의 **첫 번째 CI 테스트 플랫폼이 Windows**. `.exe` 출력 지원. 안정성 지수는 1.1(Active development). |
44
- | 빌드 워크플로 | 간소화됨 | Node **25.5.0**의 `--build-sea` 단일 플래그로 통합(종전 postject 3단계 불필요). |
45
- | 정적 자산(대시보드 HTML/CSS/JS) 임베드 | 가능 | `sea.getAsset()`/`getRawAsset()` 계열로 번들·런타임 서빙(v20.12.0~). 단 현재 `apps/web`는 디스크에서 자산을 읽으므로 **임베드용 코드 수정 필요**. |
46
- | `node:sqlite` 플래그 | 사실상 비이슈 | **Node ≥22.13 / ≥23.4부터 `--experimental-sqlite` 없이 동작**. 최신 Node를 임베드하면 해결. 필요시 SEA `execArgv`로 플래그 주입도 가능. |
47
-
48
- 근거 출처(주요): nodejs.org `single-executable-applications`, `sqlite` 문서; Node v25.5.0 릴리스 블로그;
49
- Joyee Cheung 블로그(Windows PE 주입 실테스트); commit `55239a48b6`("unflag sqlite module").
50
-
51
- ### 2.2 자가등록·동작을 막는 두 개의 블로커 — 현재 불가
52
-
53
- 1. **🚨 Codex 데스크톱이 시작 시 `~/.codex/config.toml`을 재작성하며 사용자 커스텀 `[mcp_servers]` 블록을 삭제**
54
- - openai/codex **issue #24718** (2026-05-27 OPEN, v26.519.11010.0, Win11 26200): "Codex Desktop overwrites
55
- `%USERPROFILE%\.codex\config.toml` during startup ... removes a manually configured custom MCP server",
56
- "blocking data-loss issue." (보강: #26530, #24387, JetBrains LLM-24906)
57
- - 의미: `.exe`가 `init`으로 스스로 등록해도 **앱이 지워버릴 수 있음** → 자가등록 전략의 핵심 전제가 깨짐.
58
- 2. **샌드박스가 MCP stdio 서브프로세스에도 상속됨**
59
- - 공식 문서: "The sandbox applies to spawned commands ... inherit the same sandbox boundaries", "Every file
60
- write, terminal command, network request, and process spawn goes through the sandbox."
61
- - 기본 `workspace-write`의 쓰기 가능 루트는 `cwd + $TMPDIR + /tmp`뿐 → **워크스페이스 바깥인
62
- `~/.careermate` 쓰기와 127.0.0.1 포트 바인딩이 기본 거부**. `danger-full-access` 또는
63
- `sandbox_workspace_write.writable_roots` 명시 필요. (실증: issue #18243, #24727 "windows sandbox failed: spawn setup")
64
-
65
- > 두 블로커 모두 **CareerMate 코드 문제가 아니라 Codex 앱 측 미해결 버그/설계 제약**이라 우리가 코드로 못 고친다.
66
- > #24718이 패치되고 샌드박스 정책이 정리되면 결론이 뒤집힐 수 있으므로 **추적 대상**.
67
-
68
- ### 2.3 결론(Codex)
69
-
70
- - 패키징은 가능, **무인 원클릭은 현재 불가** → **보류.** 당분간 Codex는 CLI/IDE 확장 경로 또는
71
- "Node 1회 설치 → `npx -y careermate init`" 안내가 현실적.
72
-
73
- ---
74
-
75
- ## 3. Claude Desktop `.mcpb`
76
-
77
- ### 3.1 실증 결과 (이 저장소, 2026-06-15, Node v25)
78
-
79
- - `manifest.json`은 정상: `server.type:"node"`, `args:["--experimental-sqlite","${__dirname}/dist/mcp.mjs"]`.
80
- - **`dist/mcp.mjs` 단독 부팅 성공** — `initialize` 응답 정상, **도구 24개** 로드, 데이터 `~/.careermate` 연결.
81
- - 이 PC의 **Node v25**에서: `--experimental-sqlite`는 **거부되지 않고 경고만**, 플래그 없이도 `node:sqlite` 동작.
82
- → "최신 Node가 플래그를 거부해 죽는다" 시나리오는 (최소 v25까지) **없음**.
83
-
84
- ### 3.2 그렇다면 사용자가 본 "도구 안 뜸/빨간 에러"의 원인
85
-
86
- - 산출물·플래그가 Node ≥22.5에서 멀쩡하므로, **sqlite 경로의 유일한 실패 원인 = Claude Desktop 내장 Node < 22.5**.
87
- 그 경우 `node:sqlite` 모듈이 **아예 없고** `--experimental-sqlite`도 **모르는 옵션**이라 서버가 즉시 죽는다.
88
- - (대안 가설) sqlite 무관 환경 문제 — 단 Claude Desktop 미설치 환경에서는 재현 불가.
89
- - **확정 방법(저비용, 먼저 할 것)**: `.mcpb`를 깐 PC에서 로그 확인
90
- - Windows: `%APPDATA%\Claude\logs\mcp-server-careermate.log` (또는 `mcp.log`)
91
- - `bad option: --experimental-sqlite` / `No such built-in module: node:sqlite` → **내장 Node < 22.5 확정**
92
- - 그 외(경로/ESM/`__dirname`) → 패키징 문제(별개 갈래)
93
-
94
- ### 3.3 내장 Node < 22.5가 확정될 경우 — 저장 계층 대안
95
-
96
- 코드 결합도는 낮다. `node:sqlite`를 만지는 곳은 **3개 파일**뿐:
97
- `packages/db/src/{connection.ts, schema.ts, repositories.ts}`. 사용 API도 작은 부분집합
98
- (`DatabaseSync`, `exec`, `prepare().get()/.all()/.run()`, `?`/`@name` 인자, `.changes`, `close`), **JOIN 없음**.
99
- → `getDb()` 뒤 어댑터 교체 자체는 소규모.
100
-
101
- **그러나 두 개의 지뢰:**
102
-
103
- 1. **2-프로세스 동시성(ADR #6)** — 웹 대시보드와 MCP 서버가 **별도 프로세스로 같은 DB를 WAL 동시 접근**.
104
- - `sql.js`(WASM): 낡은 Node까지 호환되지만 **인메모리+수동 저장**이라 두 프로세스 공유 시 last-writer-wins로 **깨짐**.
105
- - JSON 스토어(lowdb 등): 동일하게 동시성 재설계 필요.
106
- 2. **네이티브 ABI** — `better-sqlite3`는 API가 거의 동일하지만 **네이티브 모듈**이라 내장 Node ABI와
107
- prebuilt 불일치(NODE_MODULE_VERSION) 위험. 버전 문제를 다른 형태로 되살림.
108
-
109
- | 대안 | 낡은 내장 Node 호환 | 2-프로세스 동시성 | 비용 |
110
- |---|---|---|---|
111
- | sql.js (WASM) | ✅ | ❌ 재설계 필요 | 中~大 |
112
- | better-sqlite3 | ❌ ABI 위험 | ✅ | 中(근본 미해결) |
113
- | JSON 스토어 | ✅ | ❌ 재설계 필요 | 中~大 |
114
-
115
- 가장 깔끔한 방향: **MCP 서버가 DB를 직접 열지 않고 웹 서버의 127.0.0.1 HTTP로 데이터를 요청 → 웹 서버가
116
- 단일 DB 소유자**. 이러면 sql.js가 성립하지만 "대시보드가 안 떠 있어도 MCP 동작" UX를 잃는다(진짜 아키텍처 변경).
117
-
118
- → 어느 쪽이든 **"작은 수정"이 아니라 아키텍처 변경**이므로, 근본 원인(로그) 확정 전에는 착수하지 않는다.
119
-
120
- ---
121
-
122
- ## 4. 권고 / 다음 단계
123
-
124
- 1. **(먼저, 저비용)** `.mcpb` 깐 PC에서 로그 한 줄 확인 → 내장 Node < 22.5 여부 확정.
125
- - 플래그류 에러면: (가설과 달리) 단순 수정 가능성 → 재검토.
126
- - `node:sqlite` 부재면: 아래 3.3 rewrite를 정식 과제로.
127
- 2. **단기 우회**(무설치 포기 허용 시): "Node ≥22.5 1회 설치 → `npx -y careermate init`" — Claude/Codex 공통으로 가장 견고.
128
- 3. **추적**: Codex issue #24718(config 재작성) 상태. 닫히면 Codex SEA 원클릭을 재평가.
129
-
130
- ## 5. 검증 메모 / 한계
131
-
132
- - Codex 데스크톱 관련 결론은 **2026-05~06 기준 OPEN 버그**에 의존(시간 민감).
133
- - #24718의 구체 재현은 HTTP MCP였다는 점에서 stdio 삭제는 광범위 재작성 정황으로의 소폭 일반화.
134
- - 미서명 `.exe`의 SmartScreen/Defender 경고가 원클릭 UX를 얼마나 저해하는지는 **이번 라운드에서 확정 근거 미확보**(미검증).
135
- - `.mcpb` 실패의 "내장 Node < 22.5" 결론은 **로그로 직접 확정 전까지 강한 추정**이다.
package/docs/TODO.md DELETED
@@ -1,57 +0,0 @@
1
- # CareerMate TODO (향후 방향)
2
-
3
- 아래 항목은 **v1 범위에서 명시적으로 제외**한다. 다만 패키지 경계·데이터 모델·도구 인터페이스는 이후 추가를 수용하도록 **구조를 열어둔다**.
4
-
5
- > 사이트별 크롤러, 클라우드 동기화, DOCX export, i18n 같은 **기능/패키지 단위 백로그**는 [ROADMAP.md](./ROADMAP.md)가 관리한다(구조 개방 근거 포함). 이 문서는 **제품 방향(v1.5 / v2)과 미해결 과제**만 다루며, 중복 기재하지 않는다.
6
-
7
- ---
8
-
9
- ## v1.5
10
-
11
- 설치형 데스크톱 앱으로의 전환.
12
-
13
- - **Electron** 셸 — 대시보드를 데스크톱 앱으로 래핑(정적 자산이라 셸로 감싸기 적합).
14
- - **Windows / macOS 설치형 앱** — 더블클릭 설치, Node 동봉.
15
- - **자동 업데이트** — 백그라운드 갱신.
16
- - **트레이 상주** — MCP·대시보드 프로세스를 트레이에서 관리(시작/중지/상태).
17
- - **`CareerMate.exe`** — 단일 실행 진입점.
18
-
19
- ## v2
20
-
21
- 로컬 stdio의 한계를 넘어 원격·범용 AI 클라이언트까지 확장.
22
-
23
- - **Gateway Mode** — 로컬 stdio를 원격에서 접근 가능한 형태로 중계.
24
- - **ChatGPT 일반 채팅** — 웹/모바일 ChatGPT에서 사용(현재는 로컬 stdio라 불가).
25
- - **Claude 일반 채팅** — Claude 웹 앱 연동.
26
- - **Gemini** — Gemini 클라이언트 연동.
27
- - **oneTimeToken** — Gateway 접근을 위한 일회용 토큰 핸드셰이크.
28
- - **WebSocket Local Agent** — 로컬 에이전트와 원격 클라이언트 간 양방향 채널.
29
- - **MCP Gateway** — 원격 URL 기반 MCP 엔드포인트 노출.
30
-
31
- ## 미래 방향
32
-
33
- 향후 `CareerMate.exe`가 중심에 설 수 있고(현재는 v1.5 과제, v1 비범위), 그 구도에서 **AI는 "연결 어댑터"** 역할을 한다. ChatGPT / Claude / Gemini / Codex / Claude Code 등은 두뇌를 제공하는 별개 인터페이스일 뿐, 커리어 데이터의 단일 출처(source of truth)는 항상 로컬의 CareerMate다. 사용자는 그날 손에 잡히는 AI를 골라 같은 서랍장에 연결한다. 즉 AI는 **갈아끼우는 부품**, CareerMate는 **고정된 본체**라는 구도다.
34
-
35
- ---
36
-
37
- ## v1에서 제외 (구조는 열어둠)
38
-
39
- 다음은 v1에서 구현하지 않지만 설계상 길을 막지 않는다.
40
-
41
- - Electron
42
- - exe
43
- - Gateway
44
- - WebSocket
45
- - oneTimeToken
46
- - Cloud 저장 / 클라우드 동기화
47
- - 인증 서버
48
-
49
- ## 열린 TODO / 미해결
50
-
51
- 확정·보완이 필요한 알려진 공백. 잊지 않도록 기록한다.
52
-
53
- - **npm 게시 완료** — `careermate@0.1.0` 공개 게시됨. `npx -y careermate init`가 **동작한다**. (소스 기반 `npm install` → `npm run init`도 그대로 유효.)
54
- - **.mcpb GitHub Release 생성됨(접근 제한)** — Release `v0.1.0`에 `careermate.mcpb` 첨부. 단 저장소가 **비공개**라 공개 다운로드 링크는 아직 없음. → 저장소를 public으로 전환하면 공개 다운로드 활성화.
55
- - **공개 repo URL 확정** — git remote 기준 정본은 `osntak/CareerMate`(현재 **비공개**). 공개 전환 여부는 별도 결정 사항.
56
- - **THIRD_PARTY_NOTICES — `im-not-ai`(MIT)** 항목의 저작권자/연도가 비어 있다. 실제 저작권자·연도로 채우기.
57
- - **`install-page/` 폴더명 변경** — 이제 랜딩+시작마법사+데모(`/demo`)+설치안내를 모두 담는 "공개 웹사이트" 폴더라 `install-page`는 이름이 안 맞는다. 후보: `site` / `www` / `landing`. 주의: 이 폴더는 **Vercel 프로젝트의 Root Directory 설정**이므로, 폴더명을 바꾸면 Vercel 대시보드의 Root Directory 값도 같이 바꿔야 배포가 안 깨진다. 레포 내 코드 참조(고쳐야 할 곳): `scripts/build-dist.mjs`·`scripts/build-mcpb.mjs`·`scripts/shots.ts`·`scripts/gen-og.mjs`·`apps/web/src/server.ts`. 데모 완성 후 진행하기로 합의(2026-06-16).
package/docs/UX_NOTES.md DELETED
@@ -1,247 +0,0 @@
1
- # UI/UX 설계 노트
2
-
3
- CareerMate 대시보드의 화면 설계 의도를 정리한 문서다. 무엇을 어떻게 만들었는지뿐 아니라 **왜 그렇게 했는지**를 남겨, 새 화면을 추가하거나 기존 화면을 손볼 때 일관성을 잃지 않도록 한다.
4
-
5
- 대시보드는 프레임워크·번들러·CDN 없이 동작하는 바닐라 JS + 자체 CSS 디자인 시스템으로 만들어졌다. 모든 컴포넌트와 토큰은 다음 두 파일에 모여 있다.
6
-
7
- - `apps/web/public/lib.js` — DOM 헬퍼, API 클라이언트, 공용 컴포넌트
8
- - `apps/web/public/styles.css` — 디자인 토큰과 컴포넌트 스타일
9
-
10
- 화면을 만드는 규칙은 [`apps/web/UI_CONTRACT.md`](../apps/web/UI_CONTRACT.md)에, 기준이 되는 참고 구현은 [`apps/web/public/pages/home.js`](../apps/web/public/pages/home.js)에 있다. 이 문서는 그 둘의 배경이 되는 설계 원칙을 담는다.
11
-
12
- ---
13
-
14
- ## 1. 디자인 원칙
15
-
16
- ### 1.1 "진짜 생산성 도구"를 지향한다
17
-
18
- `styles.css` 첫머리 주석이 톤을 규정한다.
19
-
20
- > CareerMate design system — a calm, dense, real-productivity-tool aesthetic.
21
- > Inspired by the restraint of Linear / Notion / Raycast — not AI-flashy.
22
-
23
- 차분하고(calm), 밀도 있고(dense), 실제 일이 되는(real-productivity) 도구가 목표다. 화려한 그라데이션이나 과장된 모션이 아니라, 매일 들여다봐도 피로하지 않은 절제된 화면을 만든다.
24
-
25
- ### 1.2 AI 슬롭(AI slop)을 회피한다
26
-
27
- CareerMate는 사용자의 AI(ChatGPT·Claude·Gemini)가 MCP로 호출하는 도구이지, 내부에 LLM이 없다. 그래서 화면이 "AI 제품"처럼 보일 이유가 없다. [`UI_CONTRACT.md`](../apps/web/UI_CONTRACT.md)의 컨벤션이 이를 못박는다.
28
-
29
- > Dense, calm, real-product feel. Avoid emoji-heavy or flashy "AI" styling.
30
-
31
- - 반짝이·로봇·마법봉 같은 과시적 표현을 화면 곳곳에 뿌리지 않는다. (`sparkle` 아이콘이 `lib.js`에 존재하지만 강조 포인트로 아껴 쓴다.)
32
- - 이모지를 정보 전달 수단으로 남용하지 않는다.
33
- - 결과를 "AI가 만들었다"는 식으로 포장하지 않는다. 자기소개서 버전 출처는 `직접 입력 / 파일 업로드 / AI 생성 / 직접 수정`(`documents.js`의 `SOURCE_LABELS`)처럼 담백한 라벨로 사실만 표기한다.
34
-
35
- ### 1.3 밀도(density)
36
-
37
- 한 화면에서 많은 정보를 빠르게 훑을 수 있어야 한다.
38
-
39
- - 기본 본문 폰트 14px, 줄간격 1.55 (`body`).
40
- - 컴포넌트마다 의도된 작은 글자 크기 단계가 있다. 예: 네비 라벨 11px, 카드 부제 12.5px, 통계 값 26px.
41
- - `.stat`, `.table`, `.board-card`, `.timeline` 등으로 목록·표·칸반·타임라인을 조밀하게 배치한다.
42
- - 숫자는 `font-variant-numeric: tabular-nums`(`.tnum`)로 자릿수를 고정해 표/통계에서 흔들리지 않게 한다.
43
-
44
- 밀도는 "빽빽함"이 아니라 "여백을 낭비하지 않음"이다. 카드·셀의 패딩은 일정한 토큰 체계를 따른다.
45
-
46
- ### 1.4 친절한 empty state
47
-
48
- 비어 있는 화면이야말로 첫인상이다. 모든 목록은 비었을 때 다음을 제공한다(컨벤션상 필수, [`UI_CONTRACT.md`](../apps/web/UI_CONTRACT.md) "Always provide a friendly empty state").
49
-
50
- 1. 무엇을 하는 공간인지 한 문장 설명
51
- 2. **MCP로 자동 저장된다**는 안내 — 사용자가 AI와 대화하면 채워진다는 점을 알려준다
52
- 3. 직접 추가할 수 있는 1차 액션 버튼
53
-
54
- 예: 자기소개서 탭(`documents.js`)의 빈 상태.
55
-
56
- ```
57
- title: '자기소개서가 없어요'
58
- body: 'ChatGPT·Claude에게 작성을 요청하면 자동으로 여기에 저장되고,
59
- 수정할 때마다 버전이 기록됩니다. 직접 추가할 수도 있어요.'
60
- action: '새 자기소개서'
61
- ```
62
-
63
- 공고 목록(`home.js`의 `RecentJobs`), 문서 탭, 면접 준비 영역도 같은 패턴을 따른다. 빈 상태는 `EmptyState({ iconName, title, body, action })` 컴포넌트(`lib.js`)로만 만든다.
64
-
65
- ### 1.5 비개발자 용어
66
-
67
- 사용자는 개발자가 아닐 수 있다. 화면 문구는 한국어 우선이고, 기술 용어를 노출하지 않는다.
68
-
69
- - 상태 코드는 내부적으로 `document_passed`지만, 사용자에게는 항상 라벨(`서류 합격`)로 보인다. 8단계 라벨은 [`UI_CONTRACT.md`](../apps/web/UI_CONTRACT.md)에 정의돼 있다: `작성 중 / 지원 예정 / 지원 완료 / 서류 합격 / 면접 진행 / 최종 합격 / 불합격 / 보류`.
70
- - 오류 메시지도 한국어다. `api()`(`lib.js`)는 실패 시 `요청 실패 (${status})`로, `toastError`는 제목 `오류`로 표시한다.
71
- - 상대 시간도 사람 말투다: `방금 전 / N분 전 / N시간 전 / N일 전`(`fmtRelative`).
72
-
73
- ### 1.6 MCP·localhost는 설정에서만
74
-
75
- CareerMate의 동작 기반(127.0.0.1 바인딩, MCP stdio 서버, 데이터 디렉터리, server.json 핸드셰이크)은 사용자가 매일 신경 쓸 대상이 아니다. 그래서 이런 기술적 디테일은 **Settings 페이지에만** 둔다. Home·Jobs·Applications 같은 일상 화면에는 포트 번호나 MCP 연결 상태 같은 내부 개념을 노출하지 않는다. 일상 화면은 "내 커리어"라는 도메인 언어로만 이야기한다.
76
-
77
- ---
78
-
79
- ## 2. 정보 구조 (7페이지)
80
-
81
- 좌측 고정 사이드바(`--sidebar-w: 248px`)로 7개 페이지를 오간다. 라우팅은 해시 기반(`navigate(hash)` → `location.hash`)이고, `params`는 페이지 id 뒤 경로 세그먼트다(`#/jobs/abc` → `params=['abc']`).
82
-
83
- | 페이지 | 역할 |
84
- | --- | --- |
85
- | **Home** | "지금 뭘 해야 하지?"를 한눈에. 통계 타일, 온보딩 카드, 최근 공고, 진행 중 지원, 면접 할 일, 최근 활동 |
86
- | **Profile** | 프로필·경험·프로젝트·스킬 관리, 프로필 완성도 |
87
- | **Jobs** | 저장된 공고 목록과 상세(적합도, 분석, 연결된 지원) |
88
- | **Applications** | 지원 현황 칸반(8단계 상태 보드) |
89
- | **Documents** | 자기소개서(버전 타임라인)와 이력서·경력기술서 문서 |
90
- | **Interview** | 면접 준비 — 서류 합격 이상에서 해금 |
91
- | **Settings** | 데이터 위치, MCP 연결, 테마 등 기술적 설정 |
92
-
93
- ### Home이 정보 구조의 중심인 이유
94
-
95
- Home(`home.js`)은 다른 페이지로의 진입점이다. 구성:
96
-
97
- - **PageHead** — 프로필 이름이 있으면 `{name}님, 안녕하세요`, 없으면 `환영합니다`. 부제는 `ChatGPT·Claude와 함께 쓰는 내 커리어 흐름 관리 도구`.
98
- - **온보딩 카드** — 온보딩 미완료이거나 다음 할 일이 남았을 때만 노출(`!s.onboarding.completed || s.onboarding.next_steps.length`). 진행 바로 프로필 완성도를 보여주고, 다음 할 일을 최대 4개까지 나열한다.
99
- - **통계 타일 4개**(`grid--4`) — 저장된 공고 / 진행 중 지원 / 자기소개서 / 면접 준비 필요. 면접 준비가 있으면 힌트 `서류 통과 후 준비하세요`.
100
- - **2열 레이아웃**(`grid--2`) — 좌: 최근 공고 + 진행 중 지원, 우: 면접 할 일 + 최근 활동.
101
-
102
- 핵심은 **"무엇을 보여줄지보다, 무엇을 다음에 할지"**를 안내한다는 점이다.
103
-
104
- ---
105
-
106
- ## 3. 디자인 시스템
107
-
108
- ### 3.1 토큰 (CSS 변수)
109
-
110
- `styles.css`의 `:root`에 모든 토큰이 모여 있다. 화면에서 색·반경·간격을 하드코딩하지 않고 토큰만 쓴다.
111
-
112
- - **중립 팔레트**: `--bg`, `--bg-subtle`, `--bg-sunken`, `--surface`, `--surface-hover`, `--border`, `--border-strong`, `--text`, `--text-secondary`, `--text-tertiary`.
113
- - **악센트**: 차분한 인디고. `--accent: #4f46e5`, `--accent-hover`, `--accent-soft`, `--accent-border`.
114
- - **시맨틱 색**: `green / teal / blue / violet / amber / red / slate` 각각 본색·`-soft`·`-border` 3종. 상태 배지와 점수 색(`score-strong/mid/weak`)에 쓴다.
115
- - **엘리베이션**: `--shadow-sm/md/lg`.
116
- - **지오메트리**: `--radius-sm: 6px`, `--radius: 9px`, `--radius-lg: 14px`, `--sidebar-w`, `--topbar-h: 56px`, `--maxw: 1080px`.
117
- - **타이포**: `--font`(Pretendard 우선, 한글 폴백 포함), `--mono`.
118
-
119
- ### 3.2 다크 모드
120
-
121
- 다크 모드는 **시스템 설정 자동 추종 + 명시적 강제** 두 경로를 모두 지원한다.
122
-
123
- - `@media (prefers-color-scheme: dark)` 안에서 `:root:not([data-theme='light'])`에 다크 토큰을 적용 → 시스템이 다크이고 사용자가 라이트로 고정하지 않았으면 다크.
124
- - `:root[data-theme='dark']` → 사용자가 명시적으로 다크를 고른 경우.
125
- - 즉 `data-theme` 속성(`light`/`dark`/없음)으로 사용자 의도가 시스템 설정을 덮어쓴다.
126
-
127
- 다크 모드는 같은 토큰의 값만 교체하므로 컴포넌트 CSS는 변경 없이 그대로 동작한다. 이것이 토큰 우선 설계의 핵심 이득이다.
128
-
129
- ### 3.3 상태 배지 (Badge)
130
-
131
- `Badge(status, label)`(`lib.js`)는 `badge badge--<status>` 클래스를 가진 캡슐을 만든다. 작은 점(`.dot`) + 라벨 구성이다. 8개 상태가 시맨틱 색과 1:1로 매핑된다(`styles.css`).
132
-
133
- | 상태 | 색 |
134
- | --- | --- |
135
- | `draft` | slate |
136
- | `planned` | blue |
137
- | `applied` | violet |
138
- | `document_passed` | teal |
139
- | `interview` | amber |
140
- | `final_passed` | green |
141
- | `rejected` | red |
142
- | `on_hold` | slate |
143
-
144
- 색은 단계가 진행될수록 따뜻해지고, 합격은 초록, 불합격은 빨강이라는 직관을 따른다. 배지는 "대표 자기소개서/문서" 표시에도 재활용된다(`final_passed` 색으로 `대표` 라벨, `documents.js`).
145
-
146
- ### 3.4 칸반 보드
147
-
148
- Applications 페이지의 보드는 `.board`(가로 스크롤, `grid-auto-flow: column`, 열 너비 272px)와 `.board__col` / `.board__col-head`(상태명 + 개수 카운트) / `.board__cards` / `.board-card`로 구성한다. 카드 한 장은 회사명·직무·메타(상태 배지 등)를 담는다. 열 높이는 `calc(100vh - 200px)`로 묶고 내부 스크롤한다. 8단계 상태가 그대로 보드의 열이 된다.
149
-
150
- ### 3.5 버전 타임라인
151
-
152
- Documents의 자기소개서 상세는 **버전 기록**을 세로 타임라인으로 보여준다(`documents.js`의 `CoverDetailBody` → `VersionItem`). 마크업은 `.timeline` 안에 `.tl-item`(레일 점 `.tl-item__dot` + 연결선 `.tl-item__line` + 본문 `.tl-item__body`)을 쌓는 구조다.
153
-
154
- - 버전은 `version_no` 내림차순으로 정렬(최신이 위).
155
- - 현재 버전은 `.is-current`로 점이 악센트 색으로 강조되고, 헤더에 `현재 버전` 배지가 붙는다.
156
- - 각 항목에 출처 칩(`직접 입력 / 파일 업로드 / AI 생성 / 직접 수정`), 변경 메모, 상대 시간이 표시된다.
157
- - 항목 액션: `이 버전 보기`(상단 미리보기 교체), `현재 버전으로 지정`, `복사`.
158
-
159
- 미리보기(`.doc-preview`)는 현재 버전 본문을 기본 표시하고, "이 버전 보기"로 특정 버전을 끼워 볼 수 있다.
160
-
161
- ### 3.6 그 밖의 공용 컴포넌트
162
-
163
- `lib.js`가 제공하는 컴포넌트만 사용하고, 스타일을 손으로 짜지 않는다. `Card`, `Stat`, `Btn`(`primary/ghost/danger`), `IconBtn`, `EmptyState`, `Chips`, `Field`/`Input`/`Textarea`/`Select`, `PageHead`, `openModal`/`confirmDialog`, `toast`/`toastOk`/`toastError`, `Spinner`(스켈레톤). 아이콘은 `icon(name)`으로 1.7px 스트로크 SVG 세트를 쓴다.
164
-
165
- 피드백 패턴도 통일돼 있다. 변경 작업은 `try/catch`로 감싸 성공 시 `toastOk`, 실패 시 `toastError`, 위험한 삭제는 `confirmDialog`로 한 번 더 묻는다.
166
-
167
- ---
168
-
169
- ## 4. XSS-safe 렌더링 원칙
170
-
171
- 이 원칙은 타협하지 않는다. 데이터(이력서·자기소개서 본문 등)는 사용자/AI가 채우므로, 신뢰할 수 없는 문자열로 취급한다.
172
-
173
- `lib.js` 상단 주석이 규칙을 명문화한다.
174
-
175
- > XSS safety: el() puts strings into textContent (never innerHTML). User content (résumé/cover-letter text) is always rendered as text. Only our own trusted icon SVG strings use innerHTML.
176
-
177
- 구체적으로:
178
-
179
- - `el(tag, props, ...children)`에서 문자열·숫자 자식은 `document.createTextNode`로 들어간다(`appendChildren`). 따라서 본문에 `<script>`가 있어도 코드로 실행되지 않고 글자로 보인다.
180
- - `innerHTML`은 오직 **신뢰된 호출자**만 쓴다. `props.html` 키와 `icon()`의 SVG 주입이 전부이며, 이는 우리가 만든 고정 문자열이다. DB/사용자 문자열을 `html:`로 넘기지 않는다.
181
- - 자기소개서·이력서 본문은 `.doc-preview` 요소에 텍스트로 넣는다. CSS가 `white-space: pre-wrap; word-break: break-word`를 처리하므로 줄바꿈·서식이 살아 있으면서도 안전하다(`documents.js`의 미리보기, 버전 보기).
182
-
183
- 새 페이지를 만들 때도 [`UI_CONTRACT.md`](../apps/web/UI_CONTRACT.md)가 같은 점을 강조한다: *"never use innerHTML with user/DB content."*
184
-
185
- ---
186
-
187
- ## 5. 직접 수정 → 새 버전 저장 UX
188
-
189
- 자기소개서는 **덮어쓰지 않고 버전을 쌓는다**는 것이 핵심 UX다. 사용자가 손으로 고친 결과도 사라지지 않고 기록으로 남는다.
190
-
191
- 흐름(`documents.js`의 `EditNewVersion`):
192
-
193
- 1. 상세 모달에서 `수정하여 새 버전 저장` 버튼을 누르면 편집 영역이 펼쳐지고, 현재 버전 본문이 편집창에 채워진다.
194
- 2. 내용을 고치고 선택적으로 변경 메모(예: `지원동기 보강, 문장 다듬기`)를 적는다.
195
- 3. `새 버전으로 저장`을 누르면 `POST /api/cover-letters/{id}/versions`로 새 버전이 생성된다. 이때 출처는 `source: 'edit'`(`직접 수정`)으로 기록된다.
196
- 4. 저장 후 타임라인이 갱신되고, 방금 저장한 버전이 최신으로 올라온다.
197
-
198
- 이 설계의 의도:
199
-
200
- - **이력 보존** — AI가 생성한 버전과 사람이 고친 버전이 출처 라벨로 구분되어 한 타임라인에 공존한다.
201
- - **되돌리기 가능** — 어떤 버전이든 `현재 버전으로 지정`(`PUT .../current-version`)으로 다시 활성화할 수 있다. 직접 수정이 곧 파괴가 아니다.
202
- - **내보내기 분리** — 보관·열람과 별개로 MD/HTML 내보내기(`/api/export/cover-letter/{id}`)는 읽기 전용 GET이라 토큰 없이 동작한다.
203
-
204
- 문서(이력서·경력기술서)는 버전 타임라인 대신 단일 본문 수정(`PUT /api/documents/{id}`) 모델을 쓴다. 자기소개서만 버전 누적이 의미 있다고 보고 차등 적용했다.
205
-
206
- ---
207
-
208
- ## 6. 접근성
209
-
210
- 복잡한 ARIA를 쌓기보다, 시맨틱 마크업과 키보드·포커스 기본기를 지킨다.
211
-
212
- - **포커스 가시성**: `:focus-visible`에 `2px` 악센트 아웃라인(`outline-offset: 2px`). 키보드 사용자가 현재 위치를 항상 알 수 있다.
213
- - **아이콘 버튼 라벨**: `IconBtn`은 `aria-label`을 자동으로 붙인다(`title || name`). 글자가 없는 버튼도 스크린리더가 읽는다.
214
- - **모달 키보드**: `openModal`은 `Escape`로 닫히고, 열릴 때 첫 입력 요소(`input,textarea,select,button`)에 포커스를 준다. 스크림 클릭으로도 닫힌다.
215
- - **색에만 의존하지 않음**: 상태 배지는 색뿐 아니라 라벨 텍스트와 점을 함께 제공한다. 점수도 색(`score-strong/mid/weak`)과 숫자(`N점`)를 같이 보여준다.
216
- - **시맨틱 요소**: 표는 `table/thead/th/tbody`, 제목은 `h1~h4`, 폼은 `label`과 컨트롤을 `Field`로 묶는다.
217
- - **시스템 테마 존중**: `prefers-color-scheme`를 따라가므로 OS의 다크 선호를 자동 반영한다.
218
-
219
- 라이브 영역(toast 등)과 포커스 트랩 등은 추후 보강 여지가 있는 지점으로 남겨 둔다.
220
-
221
- ---
222
-
223
- ## 7. 참고 서비스의 영향과 차별점
224
-
225
- `styles.css` 주석이 밝히듯 **Linear / Notion / Raycast의 절제**에서 톤을 빌렸다.
226
-
227
- | 서비스 | 빌려온 것 |
228
- | --- | --- |
229
- | **Linear** | 조밀한 정보 밀도, 키보드 친화, 차분한 단색 악센트, 상태 칸반 |
230
- | **Notion** | 중립 팔레트, 부드러운 카드·경계선, 콘텐츠 우선의 차분한 표면 |
231
- | **Raycast** | 군더더기 없는 표면, 빠른 액션, "도구다움"의 미니멀함 |
232
-
233
- 차별점:
234
-
235
- - **완전 오프라인·로컬 전용**: 프레임워크·CDN 없이 동작하고(`styles.css` 주석: *"works fully offline; nothing leaves the machine"*), 모든 데이터가 로컬에만 남는다. 위 서비스들이 클라우드 SaaS인 것과 정반대다.
236
- - **MCP-우선, LLM 내장 없음**: 화면은 AI가 채우지만, 제품 자체는 "AI 앱"으로 보이지 않게 의도적으로 절제했다(§1.2). 분석·작성은 사용자의 AI가, 보관·구조화·열람은 CareerMate가 맡는다.
237
- - **단일 도메인 특화**: 범용 노트(Notion)나 이슈 트래커(Linear)가 아니라, "취업 준비 한 사이클"(공고 → 적합도 → 자기소개서 → 지원 상태 → 면접)에 정확히 맞춘 정보 구조와 상태 모델을 갖는다.
238
- - **빌드리스 바닐라**: 디자인 시스템이 CSS 변수 토큰 + 소수의 JS 컴포넌트로만 이뤄져, 번들러·런타임 의존성이 없다. 무거운 디자인 시스템 패키지 대신 `lib.js` 한 파일이 그 역할을 한다.
239
-
240
- ---
241
-
242
- ## 관련 문서
243
-
244
- - 화면 작성 규칙: [`apps/web/UI_CONTRACT.md`](../apps/web/UI_CONTRACT.md)
245
- - 참고 구현(Home): [`apps/web/public/pages/home.js`](../apps/web/public/pages/home.js)
246
- - 디자인 토큰·스타일: [`apps/web/public/styles.css`](../apps/web/public/styles.css)
247
- - 컴포넌트·API 클라이언트: [`apps/web/public/lib.js`](../apps/web/public/lib.js)
@@ -1,64 +0,0 @@
1
- # 데모 대시보드 배포 설계 (careermate.life/demo)
2
-
3
- 작성일: 2026-06-16
4
-
5
- ## 목표
6
-
7
- 랜딩 방문자가 **로그인·설치 없이** CareerMate 대시보드를 둘러볼 수 있는 데모 페이지를
8
- `careermate.life/demo`에 배포한다. 사용자(개발자)가 원격에서 로컬 대시보드(127.0.0.1)에
9
- 접근할 수 없는 문제도 함께 해소한다.
10
-
11
- ## 결정 사항 (사용자 확인 완료)
12
-
13
- - **상호작용**: 편집 가능. 단 메모리 전용 — 디스크/서버 저장 없음, 새로고침 시 샘플로 리셋.
14
- - **위치**: 기존 Vercel 랜딩(`install-page/`) 하위 `/demo` 서브경로. Vercel 설정·빌드 무변경.
15
-
16
- ## 핵심 구조
17
-
18
- 대시보드는 정적 SPA(`apps/web/public`)가 로컬 Node 서버의 `/api/*` JSON을 호출하는 구조다.
19
- 서버 없이 배포하려면 **클라이언트에서 `fetch('/api/*')`를 가로채 메모리 DB로 응답하는 shim**을 끼운다.
20
-
21
- 대부분의 GET 응답(`/api/summary`, `/api/applications`, `/api/interview`, `/api/jobs/:id` 등)은
22
- 몇 개 기본 컬렉션(jobs, applications, coverLetters, profile, experiences, projects, skills,
23
- documents, interviews, fits, activities)에서 **파생**된다. shim이 그 파생 로직을 재현하므로
24
- "한 곳을 수정하면 홈 카운트·목록이 자동 갱신"되는 동작이 공짜로 따라온다.
25
-
26
- ## 구성 요소
27
-
28
- 1. **데모 오버레이 소스** (`apps/web/demo/`, 원본 대시보드와 분리)
29
- - `seed.js` — 풍부한 샘플 데이터(공고 6+, 지원 단계 다양, 프로필/경력/스킬/문서/자소서/면접).
30
- - `demo-shim.js` — 메모리 가변 DB + `window.fetch` 가로채기. GET=읽기, POST/PUT/DELETE=메모리 변경.
31
- 서버 라우트(`apps/web/src/routes.ts`)와 동일한 응답 형태/파생 로직을 미러링.
32
- - `demo.css` + 하단 고정 배너(데모 안내 + "CareerMate 소개로 →" 링크).
33
-
34
- 2. **빌드 스크립트** (`scripts/build-demo.mjs`)
35
- - `apps/web/public/*` → `install-page/demo/`로 복사.
36
- - 복사본의 루트 절대경로(`/lib.js`, `/app.js`, `/pages/`, `/styles.css`, `/fonts/`,
37
- `/icon-180.png`)를 `/demo/...`로 재작성. `/api/`는 그대로(shim이 처리).
38
- - `index.html` `<head>`에 데모 shim(classic script, fetch 선패치) + `demo.css` 주입,
39
- `<body>` 끝에 데모 배너 주입.
40
- - Node 25의 `fs.rmSync(recursive)` 크래시 회피 → 수동 재귀 삭제 사용.
41
-
42
- 3. **진입 동선**
43
- - 랜딩(`install-page/index.html`)에 "데모 둘러보기" 링크.
44
- - 데모 하단 배너의 "CareerMate 소개로 →" 버튼이 랜딩(`/`)으로 복귀.
45
-
46
- 4. **배포**
47
- - 생성물 `install-page/demo/`를 git 커밋 → 기존 Vercel이 자동 서빙.
48
- - 갱신: `node scripts/build-demo.mjs`.
49
-
50
- ## 후속 작업 (TODO)
51
-
52
- - [ ] **`install-page/` 폴더명 변경** — 이제 랜딩+시작마법사+데모+설치안내를 모두 담는
53
- "공개 웹사이트"라 `install-page`는 이름이 안 맞음. 후보: `site` / `www` / `landing`.
54
- 주의: 이 폴더는 Vercel 프로젝트의 **Root Directory 설정**이라, 폴더명을 바꾸면
55
- Vercel 대시보드의 Root Directory 값도 같이 바꿔야 배포가 안 깨짐(레포 내 참조는 코드 5곳
56
- +문서: `scripts/build-dist.mjs`·`build-mcpb.mjs`·`shots.ts`·`gen-og.mjs`·`apps/web/src/server.ts`).
57
- → **데모 완성 후 진행하기로 사용자와 합의(2026-06-16).** 그때까지는 `install-page/` 유지.
58
-
59
- ## 비고 / 한계
60
-
61
- - 쓰기 동작 중 AI가 생성하는 것(적합도 분석·자소서 본문·면접 질문)은 시드에 미리 채워 보여주고,
62
- 대시보드의 수동 쓰기 경로(공고/프로필/상태/문서 CRUD)만 메모리에 반영한다.
63
- - 데이터는 전부 브라우저 메모리에만 존재. 외부 전송 없음(프라이버시 약속 유지).
64
- - 검증: 로컬에서 `install-page`를 정적 서빙하고 Playwright로 `/demo` 전 페이지 스크린샷 확인.