careermate 0.1.1 → 0.1.3
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/LICENSE +21 -0
- package/README.md +14 -10
- package/THIRD_PARTY_NOTICES.md +15 -0
- package/apps/mcp/src/index.ts +4 -5
- package/apps/web/demo/demo-shim.js +536 -0
- package/apps/web/demo/demo.css +75 -0
- package/apps/web/demo/seed.js +269 -0
- package/apps/web/public/app.js +34 -2
- package/apps/web/public/index.html +10 -2
- package/apps/web/public/lib.js +134 -22
- package/apps/web/public/pages/applications.js +2 -9
- package/apps/web/public/pages/documents.js +73 -52
- package/apps/web/public/pages/home.js +16 -18
- package/apps/web/public/pages/interview.js +9 -12
- package/apps/web/public/pages/jobs.js +40 -41
- package/apps/web/public/pages/profile.js +69 -89
- package/apps/web/public/pages/settings.js +15 -10
- package/apps/web/public/styles.css +63 -4
- package/apps/web/src/http.ts +31 -4
- package/apps/web/src/info.ts +3 -3
- package/apps/web/src/routes.ts +2 -1
- package/apps/web/src/server.ts +1 -4
- package/dist/doctor.mjs +72715 -697
- package/dist/install-page/analytics.js +97 -0
- package/dist/install-page/index.html +94 -112
- package/dist/install-page/landing.js +54 -0
- package/dist/install-page/og.png +0 -0
- package/dist/install-page/robots.txt +4 -0
- package/dist/install-page/sitemap.xml +21 -0
- package/dist/install-page/start.html +26 -46
- package/dist/install-page/style.css +105 -37
- package/dist/mcp.mjs +74044 -2062
- package/dist/migrate.mjs +110 -90
- package/dist/pdf.worker.min.mjs +28 -0
- package/dist/public/app.js +34 -2
- package/dist/public/index.html +10 -2
- package/dist/public/lib.js +134 -22
- package/dist/public/pages/applications.js +2 -9
- package/dist/public/pages/documents.js +73 -52
- package/dist/public/pages/home.js +16 -18
- package/dist/public/pages/interview.js +9 -12
- package/dist/public/pages/jobs.js +40 -41
- package/dist/public/pages/profile.js +69 -89
- package/dist/public/pages/settings.js +15 -10
- package/dist/public/styles.css +63 -4
- package/dist/web.mjs +39740 -326
- package/package.json +6 -3
- package/packages/db/src/paths.ts +11 -0
- package/packages/db/src/repositories.ts +18 -1
- package/packages/mcp-tools/src/bridge.ts +55 -32
- package/packages/mcp-tools/src/index.ts +9 -0
- package/packages/mcp-tools/src/tools.ts +198 -75
- package/packages/mcp-tools/src/update.ts +330 -0
- package/packages/parsers/src/documents.ts +533 -0
- package/packages/parsers/src/index.ts +8 -5
- package/packages/prompts/src/onboarding.ts +2 -2
- package/packages/shared/src/build-info.ts +22 -0
- package/packages/shared/src/index.ts +1 -0
- package/packages/shared/src/schemas.ts +138 -83
- package/packages/workflows/src/definitions.ts +4 -2
- package/scripts/build-demo.mjs +130 -0
- package/scripts/build-dist.mjs +26 -1
- package/scripts/doctor.ts +16 -3
- package/scripts/gen-og.mjs +1 -1
- package/scripts/test.ts +25 -0
- package/scripts/ui-smoke.ts +32 -4
- package/docs/ARCHITECTURE.md +0 -208
- package/docs/CHANGES_V1.md +0 -103
- package/docs/DATA_MODEL.md +0 -460
- package/docs/DECISIONS.md +0 -277
- package/docs/DEMO.md +0 -242
- package/docs/MCP_TOOLS.md +0 -252
- package/docs/ROADMAP.md +0 -134
- package/docs/RUNTIME_DISTRIBUTION_RESEARCH.md +0 -135
- package/docs/START_WORKFLOW.md +0 -131
- package/docs/SUPPORTED_AI_APPS.md +0 -112
- package/docs/TODO.md +0 -57
- package/docs/UX_NOTES.md +0 -247
- package/docs/WORKFLOWS.md +0 -200
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from '/lib.js';
|
|
7
7
|
|
|
8
8
|
const COUNT_LABELS = {
|
|
9
|
-
profile: '프로필', experiences: '경력', projects: '프로젝트', skills: '
|
|
9
|
+
profile: '프로필', experiences: '경력', projects: '프로젝트', skills: '기술',
|
|
10
10
|
documents: '문서', cover_letters: '자기소개서', jobs: '채용공고',
|
|
11
11
|
applications: '지원', interview_preps: '면접 준비',
|
|
12
12
|
};
|
|
@@ -90,7 +90,8 @@ function Connection(info) {
|
|
|
90
90
|
|
|
91
91
|
/* ------------------------------------------------------------- 테마 */
|
|
92
92
|
function currentTheme() {
|
|
93
|
-
|
|
93
|
+
// New key with legacy 'cf-theme' fallback so a saved theme survives the rename.
|
|
94
|
+
const saved = localStorage.getItem('careermate-theme') || localStorage.getItem('cf-theme');
|
|
94
95
|
return saved === 'light' || saved === 'dark' ? saved : 'system';
|
|
95
96
|
}
|
|
96
97
|
|
|
@@ -106,10 +107,12 @@ function Theme() {
|
|
|
106
107
|
function applyTheme(value) {
|
|
107
108
|
if (value === 'system') {
|
|
108
109
|
delete document.documentElement.dataset.theme;
|
|
109
|
-
localStorage.removeItem('
|
|
110
|
+
localStorage.removeItem('careermate-theme');
|
|
111
|
+
localStorage.removeItem('cf-theme'); // also clear legacy key
|
|
110
112
|
} else {
|
|
111
113
|
document.documentElement.dataset.theme = value;
|
|
112
|
-
localStorage.setItem('
|
|
114
|
+
localStorage.setItem('careermate-theme', value);
|
|
115
|
+
localStorage.removeItem('cf-theme'); // migrate off legacy key
|
|
113
116
|
}
|
|
114
117
|
paint();
|
|
115
118
|
}
|
|
@@ -150,7 +153,7 @@ function Backup(backups = [], rerender) {
|
|
|
150
153
|
async function createBackup() {
|
|
151
154
|
try {
|
|
152
155
|
const res = await post('/api/settings/backup');
|
|
153
|
-
toastOk('백업을
|
|
156
|
+
toastOk('백업을 생성했어요.');
|
|
154
157
|
await rerender();
|
|
155
158
|
} catch (e) {
|
|
156
159
|
toastError(e);
|
|
@@ -181,10 +184,12 @@ function Backup(backups = [], rerender) {
|
|
|
181
184
|
function DangerZone(ctx) {
|
|
182
185
|
function openResetModal() {
|
|
183
186
|
let confirmBtn;
|
|
187
|
+
// Gate on the Korean word so there's no IME switch and it matches the '초기화' button.
|
|
188
|
+
// (The API still expects the fixed 'DELETE' token; we send that on the wire below.)
|
|
184
189
|
const input = Input({
|
|
185
|
-
placeholder: '
|
|
186
|
-
onInput: (e) => { if (confirmBtn) confirmBtn.disabled = e.target.value.trim() !== '
|
|
187
|
-
onKeydown: (e) => { if (e.key === 'Enter' && e.target.value.trim() === '
|
|
190
|
+
placeholder: '초기화',
|
|
191
|
+
onInput: (e) => { if (confirmBtn) confirmBtn.disabled = e.target.value.trim() !== '초기화'; },
|
|
192
|
+
onKeydown: (e) => { if (e.key === 'Enter' && e.target.value.trim() === '초기화' && confirmBtn && !confirmBtn.disabled) confirmBtn.click(); },
|
|
188
193
|
});
|
|
189
194
|
|
|
190
195
|
openModal({
|
|
@@ -197,7 +202,7 @@ function DangerZone(ctx) {
|
|
|
197
202
|
el('div', { class: 'callout__body' },
|
|
198
203
|
'프로필·공고·지원·문서·면접 준비를 포함한 모든 데이터가 영구적으로 삭제됩니다. 안전을 위해 초기화 직전에 자동으로 백업이 한 번 생성됩니다.'))),
|
|
199
204
|
el('p', { class: 'text-secondary', style: { margin: 0, lineHeight: '1.6' } },
|
|
200
|
-
'계속하려면 아래 입력란에 ', el('span', { class: 'strong' }, '
|
|
205
|
+
'계속하려면 아래 입력란에 ', el('span', { class: 'strong' }, '초기화'), ' 를 입력하세요.'),
|
|
201
206
|
input),
|
|
202
207
|
footer: (close) => {
|
|
203
208
|
confirmBtn = Btn('모든 데이터 초기화', {
|
|
@@ -208,7 +213,7 @@ function DangerZone(ctx) {
|
|
|
208
213
|
try {
|
|
209
214
|
await post('/api/settings/reset', { confirm: 'DELETE' });
|
|
210
215
|
close();
|
|
211
|
-
toastOk('모든
|
|
216
|
+
toastOk('모든 데이터를 초기화했어요.');
|
|
212
217
|
navigate('/');
|
|
213
218
|
await ctx.refreshNav();
|
|
214
219
|
} catch (e) {
|
package/dist/public/styles.css
CHANGED
|
@@ -155,6 +155,13 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
|
155
155
|
.topbar__title { font-size: 15px; font-weight: 650; }
|
|
156
156
|
.topbar__crumb { color: var(--text-tertiary); }
|
|
157
157
|
.topbar__spacer { flex: 1; }
|
|
158
|
+
/* mobile nav: hamburger (hidden ≥900px) + off-canvas drawer scrim */
|
|
159
|
+
.nav-toggle { display: none; width: 34px; height: 34px; flex: none; place-items: center;
|
|
160
|
+
border: 1px solid var(--border); background: var(--surface); border-radius: var(--radius-sm);
|
|
161
|
+
color: var(--text-secondary); cursor: pointer; margin-right: 4px; }
|
|
162
|
+
.nav-toggle:hover { background: var(--surface-hover); color: var(--text); }
|
|
163
|
+
.nav-toggle svg { width: 20px; height: 20px; }
|
|
164
|
+
.nav-scrim { display: none; position: fixed; inset: 0; z-index: 40; background: rgba(0,0,0,.4); }
|
|
158
165
|
.view { padding: 24px; max-width: var(--maxw); width: 100%; margin: 0 auto; }
|
|
159
166
|
.view--wide { max-width: 1280px; }
|
|
160
167
|
|
|
@@ -168,7 +175,16 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
|
168
175
|
.grid--2 { grid-template-columns: repeat(2, 1fr); }
|
|
169
176
|
.grid--3 { grid-template-columns: repeat(3, 1fr); }
|
|
170
177
|
.grid--4 { grid-template-columns: repeat(4, 1fr); }
|
|
171
|
-
@media (max-width: 900px){
|
|
178
|
+
@media (max-width: 900px){
|
|
179
|
+
.grid--2,.grid--3,.grid--4{ grid-template-columns: 1fr; }
|
|
180
|
+
.app{ grid-template-columns: 1fr; }
|
|
181
|
+
/* sidebar slides in as an off-canvas drawer; the topbar hamburger toggles it */
|
|
182
|
+
.sidebar{ position: fixed; top: 0; left: 0; bottom: 0; height: 100%; z-index: 50;
|
|
183
|
+
transform: translateX(-100%); transition: transform .22s ease; box-shadow: var(--shadow-lg); }
|
|
184
|
+
.app.nav-open .sidebar{ transform: none; }
|
|
185
|
+
.app.nav-open .nav-scrim{ display: block; }
|
|
186
|
+
.nav-toggle{ display: inline-grid; }
|
|
187
|
+
}
|
|
172
188
|
|
|
173
189
|
/* ----------------------------------------------------------------- card */
|
|
174
190
|
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); }
|
|
@@ -229,7 +245,14 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
|
229
245
|
background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
|
|
230
246
|
padding: 8px 11px; transition: border-color .12s, box-shadow .12s;
|
|
231
247
|
}
|
|
232
|
-
.input:focus, .textarea:focus, .select:focus {
|
|
248
|
+
.input:focus, .textarea:focus, .select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
|
|
249
|
+
/* keyboard focus must be clearly visible (the soft ring alone fails WCAG 1.4.11/2.4.11) */
|
|
250
|
+
.input:focus-visible, .textarea:focus-visible, .select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; box-shadow: none; }
|
|
251
|
+
/* skip link — hidden until focused, then drops into the top-left */
|
|
252
|
+
.skip-link { position: fixed; top: 8px; left: 8px; z-index: 200; transform: translateY(-200%);
|
|
253
|
+
background: var(--accent); color: var(--text-on-accent); padding: 8px 14px; border: 0;
|
|
254
|
+
border-radius: var(--radius-sm); font: inherit; font-weight: 600; cursor: pointer; transition: transform .15s; }
|
|
255
|
+
.skip-link:focus { transform: none; outline: 2px solid var(--text-on-accent); outline-offset: 2px; }
|
|
233
256
|
.textarea { resize: vertical; min-height: 90px; line-height: 1.6; }
|
|
234
257
|
.textarea--lg { min-height: 280px; font-size: 15px; }
|
|
235
258
|
.input::placeholder, .textarea::placeholder { color: var(--text-tertiary); }
|
|
@@ -268,6 +291,9 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
|
268
291
|
.table tbody tr.is-clickable:hover { background: var(--surface-hover); }
|
|
269
292
|
.table .muted { color: var(--text-tertiary); }
|
|
270
293
|
.table .strong { font-weight: 600; }
|
|
294
|
+
/* keyboard-reachable lead-cell link that looks identical to plain text */
|
|
295
|
+
.cell-link { color: inherit; text-decoration: none; }
|
|
296
|
+
.cell-link:hover { text-decoration: none; }
|
|
271
297
|
|
|
272
298
|
/* ----------------------------------------------------------------- progress */
|
|
273
299
|
.progress { height: 7px; background: var(--bg-sunken); border-radius: 999px; overflow: hidden; }
|
|
@@ -377,7 +403,9 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
|
377
403
|
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 22px 26px; max-height: 60vh; overflow-y: auto; }
|
|
378
404
|
.kv { display: grid; grid-template-columns: 132px 1fr; gap: 8px 16px; }
|
|
379
405
|
.kv dt { color: var(--text-tertiary); font-size: 13px; }
|
|
380
|
-
|
|
406
|
+
/* min-width:0 overrides a grid item's default min-width:auto so the 1fr column
|
|
407
|
+
can shrink below its content — otherwise a long path/value overflows the grid. */
|
|
408
|
+
.kv dd { margin: 0; min-width: 0; }
|
|
381
409
|
|
|
382
410
|
/* ----------------------------------------------------------------- modal */
|
|
383
411
|
.modal-root { position: fixed; inset: 0; z-index: 60; display: none; }
|
|
@@ -390,6 +418,9 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
|
390
418
|
.modal__head { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; align-items: center; }
|
|
391
419
|
.modal__head h3 { font-size: 15px; }
|
|
392
420
|
.modal__body { padding: 20px; overflow-y: auto; }
|
|
421
|
+
.modal__error { margin: 0 20px 4px; padding: 10px 12px; border-radius: var(--radius); border: 1px solid var(--red-border);
|
|
422
|
+
background: var(--red-soft); color: var(--red); font-size: 13px; line-height: 1.5; }
|
|
423
|
+
.modal__error[hidden] { display: none; }
|
|
393
424
|
.modal__foot { padding: 14px 20px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 8px; }
|
|
394
425
|
@keyframes pop { from { transform: translate(-50%,-46%); opacity: 0; } to { transform: translate(-50%,-50%); opacity: 1; } }
|
|
395
426
|
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
|
|
@@ -421,11 +452,15 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
|
421
452
|
.between { justify-content: space-between; } .center { align-items: center; } .wrap { flex-wrap: wrap; }
|
|
422
453
|
.ml-auto { margin-left: auto; } .mt-2{margin-top:8px;} .mt-3{margin-top:12px;} .mt-4{margin-top:16px;} .mb-2{margin-bottom:8px;} .mb-3{margin-bottom:12px;} .mb-4{margin-bottom:16px;}
|
|
423
454
|
.tnum { font-variant-numeric: tabular-nums; }
|
|
424
|
-
|
|
455
|
+
/* min-width:0 lets a truncating element actually shrink inside a flex row;
|
|
456
|
+
without it a long unbreakable value (e.g. a data-folder path) forces the
|
|
457
|
+
flex item to its content width and overflows the viewport on narrow screens. */
|
|
458
|
+
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; min-width: 0; }
|
|
425
459
|
.divider { height: 1px; background: var(--border); margin: 16px 0; }
|
|
426
460
|
.skeleton { background: linear-gradient(90deg, var(--bg-sunken) 25%, var(--surface-hover) 37%, var(--bg-sunken) 63%); background-size: 400% 100%; animation: shimmer 1.4s infinite; border-radius: 6px; }
|
|
427
461
|
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
|
|
428
462
|
.hide { display: none !important; }
|
|
463
|
+
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
|
|
429
464
|
|
|
430
465
|
/* score color helper */
|
|
431
466
|
.score-strong { color: var(--green); } .score-mid { color: var(--amber); } .score-weak { color: var(--red); }
|
|
@@ -437,3 +472,27 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
|
437
472
|
.callout--privacy svg { color: var(--text-tertiary); }
|
|
438
473
|
.callout__title { font-weight: 600; margin-bottom: 2px; }
|
|
439
474
|
.callout__body { color: var(--text-secondary); font-size: 13px; }
|
|
475
|
+
|
|
476
|
+
/* ----------------------------------------------------------------- phone layout */
|
|
477
|
+
/* Placed last so these win over the base widths (media queries add no specificity). */
|
|
478
|
+
@media (max-width: 640px){
|
|
479
|
+
.topbar{ padding: 0 12px; gap: 8px; }
|
|
480
|
+
/* title takes the room between hamburger and actions and truncates instead of
|
|
481
|
+
collapsing to vertical text; the flex spacer isn't needed at this width */
|
|
482
|
+
.topbar__title{ flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
483
|
+
.topbar__spacer{ display: none; }
|
|
484
|
+
.input--inline{ width: 110px; max-width: 32vw; }
|
|
485
|
+
.view{ padding: 16px; }
|
|
486
|
+
/* jobs table: drop the secondary 위치·마감일 column so 회사·직무·적합도·상태 all fit */
|
|
487
|
+
.table--jobs th:nth-child(2), .table--jobs td:nth-child(2){ display: none; }
|
|
488
|
+
/* keep the 적합도 score ("88점") on one line in the narrow column */
|
|
489
|
+
.table--jobs th:nth-child(3), .table--jobs td:nth-child(3){ white-space: nowrap; }
|
|
490
|
+
.table--jobs td:nth-child(3) .tnum{ font-size: 13px; }
|
|
491
|
+
/* any remaining wide table can still scroll instead of crushing columns */
|
|
492
|
+
.table-scroll{ overflow-x: auto; -webkit-overflow-scrolling: touch; }
|
|
493
|
+
/* stack key/value so long emails·paths·URLs aren't clipped by the fixed label column */
|
|
494
|
+
.kv{ grid-template-columns: 1fr; gap: 0; }
|
|
495
|
+
.kv dt{ margin-top: 10px; }
|
|
496
|
+
.kv dd{ margin: 0 0 2px; }
|
|
497
|
+
.page-head{ flex-wrap: wrap; }
|
|
498
|
+
}
|