careermate 0.0.9 → 0.0.12
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/AGENTS.md +3 -2
- package/INSTALL.md +6 -6
- package/LICENSE +1 -1
- package/README.md +18 -16
- package/dist/career-os/README.md +2 -2
- package/dist/doctor.mjs +17857 -6595
- package/dist/mcp.mjs +14545 -3839
- package/dist/migrate.mjs +14258 -3709
- package/dist/pdf.worker.min.mjs +4 -4
- package/dist/public/app.js +3 -18
- package/dist/public/i18n/en/documents.js +1 -0
- package/dist/public/i18n/en/home.js +8 -1
- package/dist/public/i18n/en/jobs.js +2 -0
- package/dist/public/i18n/en/nav.js +1 -0
- package/dist/public/i18n/en/settings.js +1 -6
- package/dist/public/i18n/ko/documents.js +1 -0
- package/dist/public/i18n/ko/home.js +8 -1
- package/dist/public/i18n/ko/jobs.js +2 -0
- package/dist/public/i18n/ko/nav.js +1 -0
- package/dist/public/i18n/ko/settings.js +1 -6
- package/dist/public/index.html +0 -1
- package/dist/public/lib.js +13 -4
- package/dist/public/pages/documents.js +27 -10
- package/dist/public/pages/home.js +15 -5
- package/dist/public/pages/insights.js +156 -0
- package/dist/public/pages/jobs.js +21 -3
- package/dist/public/pages/settings.js +13 -12
- package/dist/public/styles.css +33 -0
- package/dist/web.mjs +16890 -5492
- package/docs/FAQ.md +3 -4
- package/docs/LOCAL_FIRST.md +32 -0
- package/docs/MCP_TOOLS.md +6 -3
- package/docs/REVIEW_IMPLEMENTATION_PLAN.md +22 -0
- package/docs/career-os/README.md +2 -2
- package/package.json +7 -12
- package/packages/core/src/data-health.ts +161 -0
- package/packages/core/src/fit-explain.ts +63 -0
- package/packages/core/src/index.ts +4 -0
- package/packages/core/src/job-categories.data.ts +140 -0
- package/packages/core/src/job-categories.ts +106 -0
- package/packages/core/src/jobsearch.ts +374 -66
- package/packages/core/src/reuse-insights.ts +126 -0
- package/packages/db/src/connection.ts +46 -26
- package/packages/db/src/index.ts +3 -0
- package/packages/db/src/interview-retrospectives.ts +145 -0
- package/packages/db/src/job-search.ts +100 -0
- package/packages/db/src/provenance.ts +125 -0
- package/packages/db/src/repositories.ts +1 -0
- package/packages/mcp-tools/src/tools.ts +115 -11
- package/packages/prompts/src/system.ts +21 -64
- package/scripts/init.ts +2 -0
- package/tsconfig.json +9 -10
|
@@ -314,11 +314,11 @@ async function renderDetail(ctx, jobId) {
|
|
|
314
314
|
]);
|
|
315
315
|
|
|
316
316
|
// --- Page head ------------------------------------------------------------
|
|
317
|
-
// 수정은 상단바(ctx.setActions)에만 둔다 — 여기엔 상태
|
|
317
|
+
// 수정은 상단바(ctx.setActions)에만 둔다 — 여기엔 상태 변경·원문만.
|
|
318
|
+
// 삭제는 위험 액션이라 페이지 최하단 '위험 구역'으로 강등한다(아래 DangerZone).
|
|
318
319
|
const headActions = el('div', { class: 'flex gap-2 center wrap' },
|
|
319
320
|
statusSelect,
|
|
320
321
|
job.url ? el('a', { class: 'btn btn--ghost btn--sm', href: job.url, attrs: { target: '_blank', rel: 'noopener noreferrer' } }, icon('external'), el('span', {}, t('jobs.detail.openPosting'))) : null,
|
|
321
|
-
delBtn,
|
|
322
322
|
);
|
|
323
323
|
|
|
324
324
|
const head = el('div', { class: 'page-head' },
|
|
@@ -344,10 +344,23 @@ async function renderDetail(ctx, jobId) {
|
|
|
344
344
|
FitCard(job.fit),
|
|
345
345
|
TimelineCard(job.timeline),
|
|
346
346
|
RelatedCard(job.related),
|
|
347
|
+
DangerZone(delBtn),
|
|
347
348
|
);
|
|
348
349
|
mount(ctx.view, wrap);
|
|
349
350
|
}
|
|
350
351
|
|
|
352
|
+
// Delete is destructive, so it sits at the very bottom in a quiet 위험 구역 row
|
|
353
|
+
// (not a card, per the design guide) instead of competing in the top-right header.
|
|
354
|
+
function DangerZone(delBtn) {
|
|
355
|
+
return el('div', { class: 'stack-2', style: { marginTop: '8px' } },
|
|
356
|
+
el('div', { class: 'divider' }),
|
|
357
|
+
el('div', { class: 'flex between center wrap gap-3' },
|
|
358
|
+
el('div', { style: { maxWidth: '440px' } },
|
|
359
|
+
el('div', { class: 'strong text-sm' }, t('jobs.detail.dangerZone')),
|
|
360
|
+
el('div', { class: 'muted text-sm', style: { lineHeight: '1.6' } }, t('jobs.detail.dangerZoneHint'))),
|
|
361
|
+
delBtn));
|
|
362
|
+
}
|
|
363
|
+
|
|
351
364
|
/* ----------------------------------------------------------- detail cards */
|
|
352
365
|
|
|
353
366
|
function PostingCard(job) {
|
|
@@ -534,7 +547,12 @@ function CoverLettersCard(coverLetters) {
|
|
|
534
547
|
icon('file', 'muted'),
|
|
535
548
|
el('div', {},
|
|
536
549
|
el('div', { class: 'strong' }, cl.title),
|
|
537
|
-
|
|
550
|
+
// Preview line: version count + last-updated date so the row isn't a bare
|
|
551
|
+
// title. (The latest version's note isn't in this response — listByJob maps
|
|
552
|
+
// without versions — so we fall back to the date per the design.)
|
|
553
|
+
el('div', { class: 'muted text-sm' },
|
|
554
|
+
t('jobs.cover.versions', { count: cl.version_count ?? 1 }),
|
|
555
|
+
cl.updated_at ? ` · ${fmtDate(cl.updated_at)}` : ''))),
|
|
538
556
|
icon('chevronRight', 'muted')))),
|
|
539
557
|
});
|
|
540
558
|
}
|
|
@@ -30,12 +30,16 @@ export async function render(ctx) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/* ----------------------------------------------------- 데이터 저장 위치 */
|
|
33
|
-
function kvRow(label, value, copyable) {
|
|
33
|
+
function kvRow(label, value, copyable, { wrapValue = false } = {}) {
|
|
34
34
|
return [
|
|
35
35
|
el('dt', {}, label),
|
|
36
36
|
el('dd', {},
|
|
37
37
|
el('div', { class: 'flex between gap-3' },
|
|
38
|
-
el('span', {
|
|
38
|
+
el('span', {
|
|
39
|
+
class: `${wrapValue ? 'kv__value-wrap' : 'truncate'} tnum`,
|
|
40
|
+
style: { fontFamily: 'var(--mono, ui-monospace, monospace)' },
|
|
41
|
+
title: value,
|
|
42
|
+
}, value || '—'),
|
|
39
43
|
copyable && value
|
|
40
44
|
? IconBtn('copy', { title: t('settings.copy'), onClick: () => copyText(value) })
|
|
41
45
|
: null)),
|
|
@@ -70,9 +74,9 @@ function Connection(info) {
|
|
|
70
74
|
async function createShortcut() {
|
|
71
75
|
try {
|
|
72
76
|
const { shortcut } = await post('/api/settings/dashboard-shortcut', { open: true });
|
|
73
|
-
toastOk(isDemo ? t('settings.connection.shortcutDemoToast') : t('settings.connection.shortcutToast'));
|
|
74
77
|
openModal({
|
|
75
78
|
title: isDemo ? t('settings.connection.shortcutDemoTitle') : t('settings.connection.shortcutTitle'),
|
|
79
|
+
intent: 'contextual',
|
|
76
80
|
body: el('div', { class: 'stack-3' },
|
|
77
81
|
el('div', { class: 'callout' },
|
|
78
82
|
icon('info'),
|
|
@@ -83,11 +87,11 @@ function Connection(info) {
|
|
|
83
87
|
? t('settings.connection.shortcutDemoCalloutBody')
|
|
84
88
|
: t('settings.connection.shortcutCalloutBody')))),
|
|
85
89
|
el('dl', { class: 'kv' },
|
|
86
|
-
...kvRow(t('settings.connection.shortcutFolder'), shortcut.shortcut_dir, true),
|
|
87
|
-
...kvRow(t('settings.connection.shortcutLauncher'), shortcut.launcher_path, true))),
|
|
90
|
+
...kvRow(t('settings.connection.shortcutFolder'), shortcut.shortcut_dir, true, { wrapValue: true }),
|
|
91
|
+
...kvRow(t('settings.connection.shortcutLauncher'), shortcut.launcher_path, true, { wrapValue: true }))),
|
|
88
92
|
footer: (close) => [
|
|
89
|
-
Btn(t('settings.copyPath'), { icon: 'copy', onClick: () => copyText(shortcut.launcher_path) }),
|
|
90
|
-
Btn(t('
|
|
93
|
+
Btn(t('settings.copyPath'), { icon: 'copy', variant: 'primary', onClick: () => copyText(shortcut.launcher_path) }),
|
|
94
|
+
Btn(t('common.close'), { variant: 'ghost', onClick: close }),
|
|
91
95
|
],
|
|
92
96
|
});
|
|
93
97
|
} catch (e) {
|
|
@@ -360,6 +364,7 @@ function openImportModal({ fileName, backup, preview, rerender }) {
|
|
|
360
364
|
openModal({
|
|
361
365
|
title: t('settings.import.title'),
|
|
362
366
|
size: 'lg',
|
|
367
|
+
intent: 'alert',
|
|
363
368
|
body: el('div', { class: 'stack-3' },
|
|
364
369
|
el('div', { class: 'callout' },
|
|
365
370
|
icon('info'),
|
|
@@ -409,15 +414,10 @@ function openImportModal({ fileName, backup, preview, rerender }) {
|
|
|
409
414
|
/* -------------------------------------------------- 위험 구역 (Danger zone) */
|
|
410
415
|
/* ----------------------------------------------------- 도움말 · 문제 신고 */
|
|
411
416
|
function Support() {
|
|
412
|
-
const ext = (href, iconName, label) =>
|
|
413
|
-
el('a', { class: 'btn btn--ghost btn--sm', href, attrs: { target: '_blank', rel: 'noopener noreferrer' } },
|
|
414
|
-
icon(iconName), el('span', {}, label));
|
|
415
417
|
return Card({
|
|
416
418
|
title: t('settings.support.title'),
|
|
417
419
|
sub: t('settings.support.sub'),
|
|
418
420
|
body: el('div', { class: 'flex gap-2 wrap' },
|
|
419
|
-
ext('https://github.com/osntak/careermate/issues/new?template=bug_report.yml', 'external', t('settings.support.report')),
|
|
420
|
-
ext('https://github.com/osntak/careermate/discussions', 'external', t('settings.support.ask')),
|
|
421
421
|
el('a', { class: 'btn btn--ghost btn--sm', href: 'mailto:osntak@gmail.com' }, icon('mail'), el('span', {}, t('settings.support.email'))),
|
|
422
422
|
),
|
|
423
423
|
});
|
|
@@ -437,6 +437,7 @@ function DangerZone(ctx) {
|
|
|
437
437
|
|
|
438
438
|
openModal({
|
|
439
439
|
title: t('settings.reset.title'),
|
|
440
|
+
intent: 'alert',
|
|
440
441
|
body: el('div', { class: 'stack-3' },
|
|
441
442
|
el('div', { class: 'callout' },
|
|
442
443
|
icon('info'),
|
package/dist/public/styles.css
CHANGED
|
@@ -406,6 +406,10 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
|
406
406
|
.list-row:focus-within .row-action { opacity: 1; pointer-events: auto; }
|
|
407
407
|
@media (hover: none) { .list-row .row-action { opacity: 1; pointer-events: auto; } }
|
|
408
408
|
|
|
409
|
+
/* Home's "next action" lane is scanned repeatedly on mobile, so keep it tighter
|
|
410
|
+
than generic cards while preserving the shared ListRow structure. */
|
|
411
|
+
.home-action-card .list-row__trail .badge { max-width: 116px; overflow: hidden; text-overflow: ellipsis; }
|
|
412
|
+
|
|
409
413
|
/* ----------------------------------------------------------------- pipeline bar + funnel
|
|
410
414
|
Visualizes the application status_breakdown. Segment color == badge--{status} hue,
|
|
411
415
|
so a colour means the same stage everywhere (board, badges, here). */
|
|
@@ -419,6 +423,9 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
|
419
423
|
.pipefunnel__value { font-size: 24px; font-weight: 700; letter-spacing: -.02em; margin-top: 5px; line-height: 1.1; }
|
|
420
424
|
.pipefunnel__conv { font-size: 12px; color: var(--text-tertiary); margin-top: 2px; }
|
|
421
425
|
@media (max-width: 560px){ .pipefunnel { grid-template-columns: repeat(2, 1fr); } }
|
|
426
|
+
/* Home KPI tiles (the only .grid--4) read better as a 2×2 on phones than a single
|
|
427
|
+
full-width column; overrides the ≤900px collapse for this narrow range. */
|
|
428
|
+
@media (max-width: 560px){ .grid--4 { grid-template-columns: repeat(2, 1fr); } }
|
|
422
429
|
|
|
423
430
|
/* ----------------------------------------------------------------- check row (onboarding)
|
|
424
431
|
The ONE place setup guidance lives. Outcome rows auto-detect completion. */
|
|
@@ -464,6 +471,7 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
|
464
471
|
/* min-width:0 overrides a grid item's default min-width:auto so the 1fr column
|
|
465
472
|
can shrink below its content — otherwise a long path/value overflows the grid. */
|
|
466
473
|
.kv dd { margin: 0; min-width: 0; }
|
|
474
|
+
.kv__value-wrap { min-width: 0; white-space: normal; word-break: keep-all; overflow-wrap: anywhere; line-height: 1.5; }
|
|
467
475
|
.settings-action-row { align-items: flex-start; min-width: 0; }
|
|
468
476
|
.settings-action-row__main { flex: 1 1 auto; min-width: 0; }
|
|
469
477
|
.settings-action-row__main > div:last-child { min-width: 0; }
|
|
@@ -473,10 +481,13 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
|
473
481
|
.modal-root { position: fixed; inset: 0; z-index: 60; display: none; }
|
|
474
482
|
.modal-root.is-open { display: block; }
|
|
475
483
|
.modal__scrim { position: absolute; inset: 0; background: rgba(16,16,20,.42); backdrop-filter: blur(2px); animation: fade .15s; }
|
|
484
|
+
.modal-root[data-intent="contextual"] .modal__scrim { background: rgba(16,16,20,.22); backdrop-filter: blur(1px); }
|
|
485
|
+
.modal-root[data-intent="alert"] .modal__scrim { background: rgba(16,16,20,.52); }
|
|
476
486
|
.modal { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); width: min(640px, calc(100vw - 32px));
|
|
477
487
|
max-height: calc(100vh - 48px); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
|
|
478
488
|
box-shadow: var(--shadow-lg); display: flex; flex-direction: column; animation: pop .16s ease; }
|
|
479
489
|
.modal--lg { width: min(880px, calc(100vw - 32px)); }
|
|
490
|
+
.modal--alert { width: min(520px, calc(100vw - 32px)); }
|
|
480
491
|
.modal__head { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; align-items: center; }
|
|
481
492
|
.modal__head h3 { font-size: 15px; }
|
|
482
493
|
.modal__body { padding: 20px; overflow-y: auto; }
|
|
@@ -485,6 +496,7 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
|
485
496
|
.modal__error[hidden] { display: none; }
|
|
486
497
|
.modal__foot { padding: 14px 20px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 8px; }
|
|
487
498
|
@keyframes pop { from { transform: translate(-50%,-46%); opacity: 0; } to { transform: translate(-50%,-50%); opacity: 1; } }
|
|
499
|
+
@keyframes sheetup { from { transform: translateY(18px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
|
488
500
|
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
|
|
489
501
|
|
|
490
502
|
/* Dropdown menu (Menu/ExportMenu) — fixed popover anchored to its trigger so it
|
|
@@ -504,6 +516,7 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
|
504
516
|
|
|
505
517
|
/* ----------------------------------------------------------------- toast */
|
|
506
518
|
.toasts { position: fixed; bottom: 20px; right: 20px; z-index: 80; display: flex; flex-direction: column; gap: 10px; }
|
|
519
|
+
body[data-modal-intent] .toasts { top: 20px; bottom: auto; }
|
|
507
520
|
.toast { background: var(--surface); border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: var(--radius);
|
|
508
521
|
box-shadow: var(--shadow-md); padding: 12px 16px; min-width: 260px; max-width: 380px; animation: slidein .2s ease; font-size: 14px; }
|
|
509
522
|
.toast--success { border-left-color: var(--green); }
|
|
@@ -575,7 +588,27 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
|
575
588
|
.settings-action-row > .btn{ width: 100%; justify-content: center; }
|
|
576
589
|
.card__head{ align-items: flex-start; flex-wrap: wrap; }
|
|
577
590
|
.card__head .right{ margin-left: 0; width: 100%; justify-content: flex-start; }
|
|
591
|
+
.home-action-card .card__head{ align-items: center; flex-wrap: nowrap; padding: 12px 14px; }
|
|
592
|
+
.home-action-card .card__head .right{ margin-left: auto; width: auto; justify-content: flex-end; }
|
|
593
|
+
.home-action-card .card__body{ padding: 8px 14px; }
|
|
594
|
+
.home-action-card .list-row{ gap: 10px; padding: 8px 0; }
|
|
595
|
+
.home-action-card .list-row__trail{ gap: 6px; }
|
|
578
596
|
.page-head{ flex-wrap: wrap; }
|
|
597
|
+
.modal-root[data-intent="contextual"] .modal{
|
|
598
|
+
top: auto; left: 0; right: 0; bottom: 0; transform: none; width: 100%;
|
|
599
|
+
max-height: min(86vh, calc(100vh - 24px)); border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
|
600
|
+
border-bottom: 0; animation: sheetup .16s ease;
|
|
601
|
+
}
|
|
602
|
+
.modal-root[data-intent="contextual"] .modal__foot{ justify-content: stretch; }
|
|
603
|
+
.modal-root[data-intent="contextual"] .modal__foot .btn{ flex: 1 1 auto; justify-content: center; }
|
|
604
|
+
.modal__foot{
|
|
605
|
+
justify-content: stretch; flex-wrap: wrap;
|
|
606
|
+
padding-bottom: max(14px, env(safe-area-inset-bottom));
|
|
607
|
+
}
|
|
608
|
+
.modal__foot .btn{ flex: 1 1 0; min-width: 0; min-height: 44px; justify-content: center; }
|
|
609
|
+
.toasts{ left: 16px; right: 16px; bottom: max(16px, env(safe-area-inset-bottom)); }
|
|
610
|
+
body[data-modal-intent] .toasts{ top: calc(var(--topbar-h) + 16px); bottom: auto; }
|
|
611
|
+
.toast{ width: 100%; min-width: 0; max-width: none; }
|
|
579
612
|
}
|
|
580
613
|
|
|
581
614
|
/* ---------- reduced motion ----------
|