Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions frontend/src/app/ai-hub/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const aiHubSurface = {
prompt_cards: [
{
prompt_key: 'prompt_safe',
prompt_title: '의사결정 로그 요약',
prompt_title: '의사결정 로그 맥락 종합',
description_text: '메일에서 판단 포인트를 추출합니다.',
shared_scope: false,
owner_label: 'alice',
Expand All @@ -55,7 +55,7 @@ const aiHubSurface = {
workflow_cards: [
{
workflow_key: 'workflow_prompt_safe',
workflow_title: '의사결정 로그 요약 실행 흐름',
workflow_title: '의사결정 로그 맥락 종합 실행 흐름',
trigger_source: 'prompt_template',
state_code: 'ready',
evidence_text: 'active organization provider is available',
Expand Down Expand Up @@ -164,10 +164,10 @@ describe('AIHubPage', () => {
expect(lowerHeaderNames.has(headerName)).toBe(false);
}
expect(container.textContent).toContain('AI 허브');
expect(container.textContent).toContain('의사결정 로그 요약');
expect(container.textContent).toContain('의사결정 로그 맥락 종합');

clickButton(container, '워크플로우');
expect(container.textContent).toContain('의사결정 로그 요약 실행 흐름');
expect(container.textContent).toContain('의사결정 로그 맥락 종합 실행 흐름');

clickButton(container, 'AI 에이전트');
expect(container.textContent).toContain('Primary OpenAI');
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/layout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { metadata } from "./layout";
describe("root layout metadata", () => {
it("describes the Korean-first Naruon workspace and local icons", () => {
expect(metadata.title).toBe("Naruon | AI Email Workspace");
expect(metadata.description).toContain("이메일");
expect(metadata.description).toContain("메일");
expect(metadata.icons).toMatchObject({
icon: "/brand/naruon-app-icon.svg",
});
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import "./globals.css";

export const metadata: Metadata = {
title: "Naruon | AI Email Workspace",
description: "이메일, 일정, 관계, 판단 포인트를 하나의 맥락으로 연결하는 AI 이메일 워크스페이스",
description: "메일, 일정, 관계, 판단 포인트를 하나의 맥락으로 연결하는 AI 메일 워크스페이스",
icons: {
icon: "/brand/naruon-app-icon.svg",
},
Expand Down
16 changes: 8 additions & 8 deletions frontend/src/app/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ describe("Home workspace action bridge", () => {
await flushAsyncWork();

expect(container.querySelector('#mobile-calendar')?.className).toContain("flex");
expect(container.textContent).toContain("캘린더 반영 대기");
expect(container.textContent).toContain("일정 반영 대기");
expect(window.location.hash).toBe("");
});

Expand Down Expand Up @@ -401,7 +401,7 @@ describe("Home workspace action bridge", () => {
ok: true,
json: async () => ({
emails: [
{ id: 102, subject: "출시 리뷰 일정 조율", sender: "pm@example.com", date: "2026-05-17T10:00:00Z", snippet: "캘린더 반영 후보" },
{ id: 102, subject: "출시 리뷰 일정 조율", sender: "pm@example.com", date: "2026-05-17T10:00:00Z", snippet: "일정 반영 후보" },
],
}),
});
Expand Down Expand Up @@ -547,8 +547,8 @@ describe("Home workspace action bridge", () => {
await flushAsyncWork();

expect(container.querySelector('#mobile-calendar')?.className).toContain("flex");
expect(container.textContent).toContain("캘린더 반영 대기");
expect(container.textContent).not.toContain("오늘의 실행 대시보드");
expect(container.textContent).toContain("일정 반영 대기");
expect(container.textContent).not.toContain("오늘의 실행 홈");
});

it("lets hashless mobile workspace events override a saved dashboard startup view", async () => {
Expand Down Expand Up @@ -577,8 +577,8 @@ describe("Home workspace action bridge", () => {

expect(window.location.hash).toBe("");
expect(container.querySelector('#mobile-calendar')?.className).toContain("flex");
expect(container.textContent).toContain("캘린더 반영 대기");
expect(container.textContent).not.toContain("오늘의 실행 대시보드");
expect(container.textContent).toContain("일정 반영 대기");
expect(container.textContent).not.toContain("오늘의 실행 홈");
});

it("ignores malformed hashless mobile workspace events for dashboard overrides", async () => {
Expand Down Expand Up @@ -682,7 +682,7 @@ describe("Home workspace action bridge", () => {
root?.render(<Home />);
});
await flushAsyncWork();
expect(container.textContent).toContain("캘린더 반영 대기");
expect(container.textContent).toContain("일정 반영 대기");

await act(async () => {
window.history.replaceState(null, "", "/");
Expand All @@ -691,7 +691,7 @@ describe("Home workspace action bridge", () => {
await flushAsyncWork();

expect(container.textContent).toContain("김나루님");
expect(container.textContent).not.toContain("캘린더 반영 대기");
expect(container.textContent).not.toContain("일정 반영 대기");
});

it("renders an API-backed loading state for the mobile search panel instead of placeholders", async () => {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/prompt-studio/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default function PromptStudioPage() {
is_shared: false
});

const [testVariable, setTestVariable] = useState('이메일 내용 예시입니다.');
const [testVariable, setTestVariable] = useState('메일 내용 예시입니다.');
const [testResult, setTestResult] = useState('');
const [testing, setTesting] = useState(false);
const [saving, setSaving] = useState(false);
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/app/tasks/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ describe("TasksPage", () => {
});
await flushAsyncWork();

expect(container.querySelector("h1")?.textContent).toContain("할 일 추적");
expect(container.textContent).toContain("할 일 추적");
expect(container.querySelector("h1")?.textContent).toContain("실행 항목 추적");
expect(container.textContent).toContain("실행 항목 추적");
expect(container.textContent).toContain("위임한 작업");
expect(container.textContent).toContain("실제 티켓 큐");
expect(container.textContent).toContain("연결된 티켓 없음");
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/components/DashboardLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ describe("DashboardLayout", () => {
expect(main).not.toBeNull();
expect(skipLink).not.toBeNull();
expect(logo?.getAttribute("src")).toBe("/brand/naruon-symbol.svg");
expect(headerActionButtons).toEqual(["캘린더 반영", "답장 초안", "할 일 만들기"]);
expect(headerActionButtons).toEqual(["일정 반영", "답장 초안", "실행 항목 생성"]);
expect(headerActionGroup?.className).toContain("lg:flex");
expect(headerActionGroup?.className).not.toContain("xl:flex");
expect(skipLink?.textContent).toBe("Skip to main content");
Expand All @@ -103,8 +103,8 @@ describe("DashboardLayout", () => {
const desktopStartupPreference = container.querySelector<HTMLElement>('section[aria-label="Desktop startup preference"]');
expect(desktopStartupPreference).not.toBeNull();
expect(desktopStartupPreference?.textContent ?? "").toContain("시작 화면");
expect(desktopStartupPreference?.querySelector<HTMLButtonElement>('button[data-desktop-startup-view="dashboard"]')?.textContent).toContain("대시보드");
expect(desktopStartupPreference?.querySelector<HTMLButtonElement>('button[data-desktop-startup-view="email"]')?.textContent).toContain("이메일");
expect(desktopStartupPreference?.querySelector<HTMLButtonElement>('button[data-desktop-startup-view="dashboard"]')?.textContent).toContain("홈");
expect(desktopStartupPreference?.querySelector<HTMLButtonElement>('button[data-desktop-startup-view="email"]')?.textContent).toContain("메일");
expect(desktopStartupPreference?.querySelector<HTMLButtonElement>('button[data-desktop-startup-view="calendar"]')?.textContent).toContain("일정");

act(() => {
Expand All @@ -124,8 +124,8 @@ describe("DashboardLayout", () => {
expect(mobileMenu?.className).toContain("inset-y-0");
expect(mobileMenu?.querySelector<HTMLButtonElement>('button[aria-label="모바일 워크스페이스 메뉴 닫기"]')).not.toBeNull();
expect(mobileMenu?.textContent ?? "").toContain("시작 화면");
expect(mobileMenu?.textContent ?? "").toContain("대시보드");
expect(mobileMenu?.textContent ?? "").toContain("이메일");
expect(mobileMenu?.textContent ?? "").toContain("홈");
expect(mobileMenu?.textContent ?? "").toContain("메일");
expect(mobileMenu?.textContent ?? "").toContain("일정");
expect(mobileMenu?.textContent ?? "").toContain("메일");
expect(mobileMenu?.textContent ?? "").toContain("워크스페이스");
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/components/DashboardLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,22 @@ const primaryNavItems = [
];

const startupViewItems = [
{ label: '대시보드', view: 'dashboard' as const, description: '오늘의 실행 요약' },
{ label: '이메일', view: 'email' as const, description: '받은편지함 작업공간' },
{ label: '홈', view: 'dashboard' as const, description: '오늘의 실행 맥락 종합' },
{ label: '메일', view: 'email' as const, description: '받은편지함 작업공간' },
{ label: '일정', view: 'calendar' as const, description: '캘린더 연결 먼저 보기' },
];

const mobileWorkspaceMenuItems = [
{ label: '받은편지함', description: '메일 스레드', icon: Inbox, href: '#mobile-inbox', view: 'inbox' as const },
{ label: '맥락 검색', description: '메일, 첨부, 일정, 사람 검색', icon: Search, href: '#mobile-search', view: 'search' as const },
{ label: '일정 연결', description: '캘린더 반영 후보', icon: CalendarDays, href: '#mobile-calendar', view: 'calendar' as const },
{ label: '일정 연결', description: '일정 반영 후보', icon: CalendarDays, href: '#mobile-calendar', view: 'calendar' as const },
{ label: 'AI 실행', description: '관계 맥락과 실행 항목', icon: Sparkles, href: '#mobile-actions', view: 'actions' as const },
];

const headerActions = [
{ label: '캘린더 반영', action: 'calendar-sync', message: '메일 상세 패널에서 실행 항목을 캘린더에 반영합니다.', icon: CalendarDays },
{ label: '일정 반영', action: 'calendar-sync', message: '메일 상세 패널에서 실행 항목을 캘린더에 반영합니다.', icon: CalendarDays },
{ label: '답장 초안', action: 'reply-draft', message: '메일 상세 패널에서 답장 초안을 생성합니다.', icon: PenLine },
{ label: '할 일 만들기', action: 'create-task', message: '메일 상세 패널에서 실행 항목을 할 일로 정리합니다.', icon: CheckCircle2 },
{ label: '실행 항목 생성', action: 'create-task', message: '메일 상세 패널에서 실행 항목을 실행 항목으로 정리합니다.', icon: CheckCircle2 },
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
];


Expand Down Expand Up @@ -449,7 +449,7 @@ export function DashboardLayout({
<div className="mb-3 flex items-center justify-between">
<div>
<p className="text-sm font-black text-foreground">워크스페이스 메뉴</p>
<p className="mt-1 text-xs text-muted-foreground">메일, 일정, 할 일, 데이터와 보안 메뉴로 이동합니다.</p>
<p className="mt-1 text-xs text-muted-foreground">메일, 일정, 실행 항목, 데이터와 보안 메뉴로 이동합니다.</p>
</div>
<button
type="button"
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/components/EmailDetail.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ describe("EmailDetail", () => {
expect(container.textContent).not.toContain("Thread A stale sibling body");
});

it("renders AI summary, action items, and reply drafting in reusable insight cards", async () => {
it("renders 맥락 종합, action items, and reply drafting in reusable insight cards", async () => {
const email: TestEmail = {
id: 7,
message_id: "<insight@example.com>",
Expand Down Expand Up @@ -320,7 +320,7 @@ describe("EmailDetail", () => {
(card) => card.getAttribute("aria-label") === "실행 항목",
);
const createTaskButton = Array.from(actionCard?.querySelectorAll<HTMLButtonElement>("button") ?? []).find(
(button) => button.textContent?.includes("할 일 만들기"),
(button) => button.textContent?.includes("실행 항목 생성"),
);

expect(createTaskButton).not.toBeUndefined();
Expand All @@ -330,7 +330,7 @@ describe("EmailDetail", () => {
});

expect(fetchMock.mock.calls.map(([input]) => String(input))).toContain("/api/tasks/from-email");
expect(actionCard?.textContent).toContain("2개 실행 항목을 티켓형 할 일로 추적합니다.");
expect(actionCard?.textContent).toContain("2개 실행 항목을 티켓형 실행 항목으로 추적합니다.");
});

it("clears conversation loading when the latest email has no thread", async () => {
Expand Down Expand Up @@ -411,7 +411,7 @@ describe("EmailDetail", () => {
expect(container.textContent).not.toContain("대화 흐름을 불러오는 중입니다...");
});

it("uses Korean-first labels for AI summary and execution actions", async () => {
it("uses Korean-first labels for 맥락 종합 and execution actions", async () => {
const email: TestEmail = {
id: 5,
message_id: "<label@example.com>",
Expand Down
14 changes: 7 additions & 7 deletions frontend/src/components/EmailDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export function EmailDetail({ emailId, actionCommand = null }: { emailId: number
setLlmData(llmJson);
} catch (llmErr) {
console.error("Error generating LLM summary:", llmErr);
if (isMounted) setLlmError("요약을 생성하지 못했습니다.");
if (isMounted) setLlmError("맥락 종합을 생성하지 못했습니다.");
}
} catch (err) {
console.error("Error fetching email details:", err);
Expand Down Expand Up @@ -190,7 +190,7 @@ export function EmailDetail({ emailId, actionCommand = null }: { emailId: number
setSendStatus({
type: 'success',
message: data.simulated
? '개발 모드에서 답장을 시뮬레이션했습니다. 실제 이메일은 전송되지 않았습니다.'
? '개발 모드에서 답장을 시뮬레이션했습니다. 실제 메일은 전송되지 않았습니다.'
: '답장을 전송했습니다.',
});
setDraft('');
Expand Down Expand Up @@ -250,10 +250,10 @@ export function EmailDetail({ emailId, actionCommand = null }: { emailId: number
items: llmData.todos,
});
if (!isCurrentEmail()) return;
setTaskStatus(`${data.created}개 실행 항목을 티켓형 할 일로 추적합니다.`);
setTaskStatus(`${data.created}개 실행 항목을 티켓형 실행 항목으로 추적합니다.`);
} catch {
if (!isCurrentEmail()) return;
setTaskStatus('티켓형 할 일 생성에 실패했습니다.');
setTaskStatus('티켓형 실행 항목 생성에 실패했습니다.');
} finally {
if (isCurrentEmail()) setIsCreatingTask(false);
}
Expand Down Expand Up @@ -290,7 +290,7 @@ export function EmailDetail({ emailId, actionCommand = null }: { emailId: number
<div className="max-w-md rounded-3xl border border-primary/15 bg-card p-8 shadow-[0_24px_70px_rgba(15,23,42,0.08)]">
<div className="mx-auto mb-4 grid size-14 place-items-center rounded-2xl bg-primary/10 text-2xl" aria-hidden="true">✦</div>
<h2 className="text-xl font-black tracking-tight text-foreground">메일을 선택하세요</h2>
<p className="mt-2 text-sm leading-6">왼쪽 받은편지함에서 메일을 선택하면 Naruon이 요약, 판단 포인트, 실행 항목을 연결합니다.</p>
<p className="mt-2 text-sm leading-6">왼쪽 받은편지함에서 메일을 선택하면 Naruon이 맥락 종합, 판단 포인트, 실행 항목을 연결합니다.</p>
</div>
</div>
);
Expand Down Expand Up @@ -367,7 +367,7 @@ export function EmailDetail({ emailId, actionCommand = null }: { emailId: number
className="h-9 rounded-xl bg-emerald-600 px-4 text-white hover:bg-emerald-700"
>
{isSyncing && <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />}
{isSyncing ? "동기화 중" : "캘린더 반영"}
{isSyncing ? "동기화 중" : "일정 반영"}
</Button>
)}
{llmData.todos.length > 0 && (
Expand All @@ -379,7 +379,7 @@ export function EmailDetail({ emailId, actionCommand = null }: { emailId: number
className="h-9 rounded-xl border-emerald-500/30 px-4 text-emerald-700 hover:bg-emerald-500/10"
>
{isCreatingTask && <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />}
{isCreatingTask ? "추적 중" : "할 일 만들기"}
{isCreatingTask ? "추적 중" : "실행 항목 생성"}
</Button>
)}
{syncStatus && (
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/EmailList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,12 @@ export function EmailList({
}}
className="flex gap-2"
>
<label htmlFor="email-search" className="sr-only">이메일 검색</label>
<label htmlFor="email-search" className="sr-only">메일 검색</label>
<div className="relative min-w-0 flex-1">
<Search className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" aria-hidden="true" />
<Input
id="email-search"
aria-label="이메일 검색"
aria-label="메일 검색"
placeholder="메일, 사람, 키워드 검색..."
value={searchQuery}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchQuery(e.target.value)}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/NetworkGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export default function NetworkGraph() {
{nodes.length}개 노드와 {edges.length}개 관계가 이 스레드 맥락에 연결되어 있습니다.
</p>
<div className="mt-3 rounded-xl border border-primary/10 bg-primary/5 p-3 text-xs text-muted-foreground">
<p className="font-semibold text-foreground">텍스트 관계 요약</p>
<p className="font-semibold text-foreground">텍스트 관계 맥락 종합</p>
<p className="mt-1">
관련 노드: {nodeLabels.join(', ')}
</p>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/ProjectsLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const projectStatusClass = {
} satisfies Record<ProjectSummary['status'], string>;

const taskStatusLabel: Record<TaskStatus, string> = {
open: '할 일',
open: '실행 항목',
in_progress: '진행 중',
blocked: '검토 필요',
done: '완료',
Expand Down Expand Up @@ -287,7 +287,7 @@ export function ProjectsLayout() {
</div>
<div className="grid gap-4 p-5 md:grid-cols-4">
{[
{ label: '할 일', count: openCount, status: 'open' as const },
{ label: '실행 항목', count: openCount, status: 'open' as const },
{ label: '진행 중', count: inProgressCount, status: 'in_progress' as const },
{ label: '검토 필요', count: blockedCount, status: 'blocked' as const },
{ label: '완료', count: doneCount, status: 'done' as const },
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/SettingsLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ describe("SettingsLayout", () => {
expect(operationalCall?.[1]?.headers).not.toHaveProperty("X-User-Role");
expect(operationalCall?.[1]?.headers).not.toHaveProperty("X-Dev-Auth-Token");
expect(container.textContent).toContain("Self-hosted connector manifest");
expect(container.textContent).toContain("Naruon은 이메일 서버가 아닙니다");
expect(container.textContent).toContain("Naruon은 메일 서버가 아닙니다");
expect(container.textContent).toContain("self-hosted_connector");
expect(container.textContent).toContain("outbound_only");
expect(container.textContent).toContain("naruon.net");
Expand Down
Loading