From fca28fb0c415209f2a1ea4dd05b9f7da3947f40d Mon Sep 17 00:00:00 2001 From: altair823 Date: Thu, 7 May 2026 01:56:58 +0900 Subject: [PATCH] =?UTF-8?q?feat(v027):=20AutostartSection=20=ED=86=A0?= =?UTF-8?q?=EA=B8=80=20(=EC=A7=84=EB=8B=A8=20=ED=8C=A8=EB=84=90=EC=9D=80?= =?UTF-8?q?=20=ED=9B=84=EC=86=8D=20task)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/index.ts | 2 ++ src/main/ipc/settingsApi.ts | 23 +++++++++++++ src/preload/index.ts | 3 ++ .../inbox/components/SettingsPage.tsx | 3 +- .../components/settings/AutostartSection.tsx | 31 +++++++++++++++++ src/shared/types.ts | 3 ++ tests/unit/AutostartSection.test.tsx | 34 +++++++++++++++++++ tests/unit/SettingsPage.test.tsx | 4 ++- 8 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 src/main/ipc/settingsApi.ts create mode 100644 src/renderer/inbox/components/settings/AutostartSection.tsx create mode 100644 tests/unit/AutostartSection.test.tsx diff --git a/src/main/index.ts b/src/main/index.ts index e38197f..9768e0f 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -19,6 +19,7 @@ import { ProviderHolder } from './ai/ProviderHolder.js'; import { AiWorker } from './ai/AiWorker.js'; import { registerCaptureApi } from './ipc/captureApi.js'; import { registerInboxApi, pushNoteUpdated, pushOllamaStatus } from './ipc/inboxApi.js'; +import { registerSettingsApi } from './ipc/settingsApi.js'; import { createInboxWindow, getInboxWindow } from './windows/inboxWindow.js'; import { createQuickCaptureWindow, showQuickCapture, getQuickCaptureWindow @@ -162,6 +163,7 @@ app.whenReady().then(async () => { repo, continuity, capture, health, intent, getInboxWindow, settings: settingsSvc, providerHolder }); + registerSettingsApi(); const hotkeys = new HotkeyService(); const reg = hotkeys.register({ diff --git a/src/main/ipc/settingsApi.ts b/src/main/ipc/settingsApi.ts new file mode 100644 index 0000000..712da9f --- /dev/null +++ b/src/main/ipc/settingsApi.ts @@ -0,0 +1,23 @@ +import electron from 'electron'; +const { ipcMain, app } = electron; + +/** + * v0.2.7 자동 실행 설정 IPC. + * + * 임시 채널명 (`settings:get-autostart` / `settings:set-autostart`). + * Task 22 에서 정식 이름 (`settings:autostart-state` / `settings:autostart-set`) 으로 rename 예정. + * + * args=['--hidden'] 명시 — 자동 실행 시 백그라운드 모드로 시작 (Quick Capture only). + */ +export function registerSettingsApi(): void { + ipcMain.handle('settings:get-autostart', () => { + const r = app.getLoginItemSettings({ args: ['--hidden'] }); + return { openAtLogin: r.openAtLogin }; + }); + + ipcMain.handle('settings:set-autostart', (_e, open: boolean) => { + app.setLoginItemSettings({ openAtLogin: open, args: ['--hidden'] }); + const r = app.getLoginItemSettings({ args: ['--hidden'] }); + return { openAtLogin: r.openAtLogin }; + }); +} diff --git a/src/preload/index.ts b/src/preload/index.ts index 4281f5a..c6ec4ed 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -52,6 +52,9 @@ const api: InklingApi = { ipcRenderer.on('inbox:openOllamaSettings', handler); return () => ipcRenderer.removeListener('inbox:openOllamaSettings', handler); }, + // v0.2.7 자동 실행 (임시 채널 — Task 22 에서 settings:autostart-state / settings:autostart-set 으로 rename) + getAutostart: () => ipcRenderer.invoke('settings:get-autostart'), + setAutostart: (open: boolean) => ipcRenderer.invoke('settings:set-autostart', open), } }; diff --git a/src/renderer/inbox/components/SettingsPage.tsx b/src/renderer/inbox/components/SettingsPage.tsx index c9d921c..d5e4ed6 100644 --- a/src/renderer/inbox/components/SettingsPage.tsx +++ b/src/renderer/inbox/components/SettingsPage.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { useInbox } from '../store.js'; import { AiProviderSection } from './settings/AiProviderSection.js'; +import { AutostartSection } from './settings/AutostartSection.js'; export function SettingsPage(): React.ReactElement { const setShowSettings = useInbox((s) => s.setShowSettings); @@ -27,7 +28,7 @@ export function SettingsPage(): React.ReactElement {

자동 실행

- {/* AutostartSection — Task 9 + Task 23/24 */} +

백업 / 복원

diff --git a/src/renderer/inbox/components/settings/AutostartSection.tsx b/src/renderer/inbox/components/settings/AutostartSection.tsx new file mode 100644 index 0000000..000ecbc --- /dev/null +++ b/src/renderer/inbox/components/settings/AutostartSection.tsx @@ -0,0 +1,31 @@ +import React, { useEffect, useState } from 'react'; +import { inboxApi } from '../../api.js'; + +export function AutostartSection(): React.ReactElement { + const [openAtLogin, setOpenAtLogin] = useState(null); + + useEffect(() => { + void (async () => { + const r = await inboxApi.getAutostart(); + setOpenAtLogin(r.openAtLogin); + })(); + }, []); + + async function onToggle(e: React.ChangeEvent): Promise { + const r = await inboxApi.setAutostart(e.target.checked); + setOpenAtLogin(r.openAtLogin); + } + + if (openAtLogin === null) { + return
로딩 중...
; + } + + return ( +
+ +
+ ); +} diff --git a/src/shared/types.ts b/src/shared/types.ts index dd79b2c..82c076d 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -92,6 +92,9 @@ export interface InboxApi { loadOllamaSettings(): Promise<{ endpoint: string; model: string } | null>; saveOllamaSettings(v: { endpoint: string; model: string }): Promise<{ ok: true } | { ok: false; reason: string }>; onOpenOllamaSettings(cb: () => void): () => void; + // v0.2.7 자동 실행 (임시 채널 — Task 22 에서 정식 이름으로 rename) + getAutostart(): Promise<{ openAtLogin: boolean }>; + setAutostart(open: boolean): Promise<{ openAtLogin: boolean }>; } export interface InklingApi { diff --git a/tests/unit/AutostartSection.test.tsx b/tests/unit/AutostartSection.test.tsx new file mode 100644 index 0000000..7a4c4fe --- /dev/null +++ b/tests/unit/AutostartSection.test.tsx @@ -0,0 +1,34 @@ +// @vitest-environment jsdom +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import '@testing-library/jest-dom/vitest'; +import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react'; + +vi.mock('../../src/renderer/inbox/api.js', () => ({ + inboxApi: { + getAutostart: vi.fn(async () => ({ openAtLogin: true })), + setAutostart: vi.fn(async (open: boolean) => ({ openAtLogin: open })) + } +})); + +import { AutostartSection } from '../../src/renderer/inbox/components/settings/AutostartSection'; + +describe('AutostartSection', () => { + beforeEach(() => { + vi.clearAllMocks(); + cleanup(); + }); + + it('renders toggle reflecting current state', async () => { + render(); + const toggle = await screen.findByRole('checkbox'); + expect(toggle).toBeChecked(); + }); + + it('clicking toggle calls setAutostart', async () => { + const { inboxApi } = await import('../../src/renderer/inbox/api.js'); + render(); + const toggle = await screen.findByRole('checkbox'); + fireEvent.click(toggle); + await waitFor(() => expect(inboxApi.setAutostart).toHaveBeenCalledWith(false)); + }); +}); diff --git a/tests/unit/SettingsPage.test.tsx b/tests/unit/SettingsPage.test.tsx index 2805021..491d0be 100644 --- a/tests/unit/SettingsPage.test.tsx +++ b/tests/unit/SettingsPage.test.tsx @@ -10,7 +10,9 @@ vi.mock('../../src/renderer/inbox/api.js', () => ({ inboxApi: { loadOllamaSettings: vi.fn(async () => null), saveOllamaSettings: vi.fn(async () => ({ ok: true })), - ollamaRecheck: vi.fn(async () => ({ ok: true })) + ollamaRecheck: vi.fn(async () => ({ ok: true })), + getAutostart: vi.fn(async () => ({ openAtLogin: false })), + setAutostart: vi.fn(async (open: boolean) => ({ openAtLogin: open })) } }));