feat(v027): AutostartSection 토글 (진단 패널은 후속 task)

This commit is contained in:
altair823
2026-05-07 01:56:58 +09:00
parent 7301f4d73d
commit fca28fb0c4
8 changed files with 101 additions and 2 deletions

View File

@@ -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({

View File

@@ -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 };
});
}

View File

@@ -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),
}
};

View File

@@ -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 {
</section>
<section style={{ marginBottom: 24 }}>
<h2 style={{ fontSize: 14, marginBottom: 8 }}> </h2>
{/* AutostartSection — Task 9 + Task 23/24 */}
<AutostartSection />
</section>
<section style={{ marginBottom: 24 }}>
<h2 style={{ fontSize: 14, marginBottom: 8 }}> / </h2>

View File

@@ -0,0 +1,31 @@
import React, { useEffect, useState } from 'react';
import { inboxApi } from '../../api.js';
export function AutostartSection(): React.ReactElement {
const [openAtLogin, setOpenAtLogin] = useState<boolean | null>(null);
useEffect(() => {
void (async () => {
const r = await inboxApi.getAutostart();
setOpenAtLogin(r.openAtLogin);
})();
}, []);
async function onToggle(e: React.ChangeEvent<HTMLInputElement>): Promise<void> {
const r = await inboxApi.setAutostart(e.target.checked);
setOpenAtLogin(r.openAtLogin);
}
if (openAtLogin === null) {
return <div style={{ fontSize: 12, color: '#666' }}> ...</div>;
}
return (
<div>
<label style={{ display: 'flex', gap: 8, alignItems: 'center', fontSize: 13 }}>
<input type="checkbox" checked={openAtLogin} onChange={onToggle} />
</label>
</div>
);
}

View File

@@ -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 {

View File

@@ -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(<AutostartSection />);
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(<AutostartSection />);
const toggle = await screen.findByRole('checkbox');
fireEvent.click(toggle);
await waitFor(() => expect(inboxApi.setAutostart).toHaveBeenCalledWith(false));
});
});

View File

@@ -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 }))
}
}));