feat(v029): settings:* IPC (ai-enabled/onboarding-completed/get) + App.tsx 첫 launch 분기

This commit is contained in:
altair823
2026-05-09 16:18:27 +09:00
parent d2c7bf1b39
commit c65d6c810e
7 changed files with 98 additions and 9 deletions

View File

@@ -202,7 +202,7 @@ app.whenReady().then(async () => {
// v0.2.7 Task 10 — 설정 페이지 IPC (autostart + backup/export/import/sync/telemetry).
// backup / exportSvc / importSvc / syncSvc / telemetry 가 모두 준비된 뒤 등록.
registerSettingsApi({
backup, exportSvc, importSvc, syncSvc, telemetry, getInboxWindow
backup, exportSvc, importSvc, syncSvc, telemetry, settings: settingsSvc, getInboxWindow
});
let backupOnQuitDone = false;

View File

@@ -8,6 +8,7 @@ import type { ExportService } from '../services/ExportService.js';
import type { ImportService } from '../services/ImportService.js';
import type { SyncService } from '../services/SyncService.js';
import type { TelemetryService } from '../services/TelemetryService.js';
import type { SettingsService } from '../services/SettingsService.js';
import { collectAutostartState } from '../services/AutostartDiagnostic.js';
import { getInboxWindow as getInboxWindowSingleton } from '../windows/inboxWindow.js';
@@ -33,6 +34,7 @@ export interface SettingsIpcDeps {
importSvc: ImportService;
syncSvc: SyncService;
telemetry: TelemetryService;
settings: SettingsService;
getInboxWindow: () => BrowserWindow | null;
}
@@ -88,7 +90,21 @@ export function registerSettingsApi(deps?: SettingsIpcDeps): void {
});
if (!deps) return;
const { backup, exportSvc, importSvc, syncSvc, telemetry, getInboxWindow } = deps;
const { backup, exportSvc, importSvc, syncSvc, telemetry, settings, getInboxWindow } = deps;
// v0.2.9 Cut B Task 12 — settings read + AI/onboarding 토글.
// 첫 launch 시 OnboardingWizard 분기 (App.tsx) 와 SettingsPage 의 ai_enabled 토글 통합.
ipcMain.handle('settings:get', async () => settings.getAll());
ipcMain.handle('settings:set-ai-enabled', async (_e, enabled: boolean) => {
await settings.setAiEnabled(enabled);
return { ok: true as const };
});
ipcMain.handle('settings:set-onboarding-completed', async (_e, completed: boolean) => {
await settings.setOnboardingCompleted(completed);
return { ok: true as const };
});
ipcMain.handle('settings:run-backup', async () => {
try {

View File

@@ -39,6 +39,14 @@ export class SettingsService {
return this.cache;
}
/**
* v0.2.9 Cut B Task 12 — settings:get IPC 핸들러용 read-only accessor.
* 첫 launch onboarding 분기에서 onboarding_completed 키 확인.
*/
async getAll(): Promise<Settings> {
return this.load();
}
async setOllama(value: OllamaSettings): Promise<void> {
const validated = OllamaSettingsSchema.parse(value);
const current = await this.load();

View File

@@ -74,6 +74,10 @@ const api: InklingApi = {
// v0.2.9 Cut B Task 8 — 4분기 status 전이 + AI 자동 분류 추천.
setStatus: (id, status, reason) => ipcRenderer.invoke('inbox:set-status', id, status, reason),
classifyStatus: (id, reason) => ipcRenderer.invoke('ai:classify-status', id, reason),
// v0.2.9 Cut B Task 12 — settings read + AI/onboarding 토글 (첫 launch wizard 분기 포함).
getSettings: () => ipcRenderer.invoke('settings:get'),
setAiEnabled: (enabled: boolean) => ipcRenderer.invoke('settings:set-ai-enabled', enabled),
setOnboardingCompleted: (completed: boolean) => ipcRenderer.invoke('settings:set-onboarding-completed', completed),
}
};

View File

@@ -13,6 +13,7 @@ import { ExpiryBanner } from './components/ExpiryBanner.js';
import { FailedBanner } from './components/FailedBanner.js';
import { RecallBanner } from './components/RecallBanner.js';
import { SettingsPage } from './components/SettingsPage.js';
import { OnboardingWizard } from './components/OnboardingWizard.js';
export function App(): React.ReactElement {
const {
@@ -28,6 +29,20 @@ export function App(): React.ReactElement {
const counts = useInbox((s) => s.counts);
const setView = useInbox((s) => s.setView);
const [recoveryDismissed, setRecoveryDismissed] = useState(isRecoveryDismissedToday());
// v0.2.9 Cut B Task 12 — 첫 launch onboarding 분기. null = 로딩, true = 표시, false = 미표시.
const [showOnboarding, setShowOnboarding] = useState<boolean | null>(null);
useEffect(() => {
void (async () => {
try {
const settings = await inboxApi.getSettings();
setShowOnboarding(!settings.onboarding_completed);
} catch {
// 안전한 fallback — settings 읽기 실패 시 wizard 미표시 (기존 사용자 무영향).
setShowOnboarding(false);
}
})();
}, []);
useEffect(() => {
void loadInitial();
@@ -49,6 +64,9 @@ export function App(): React.ReactElement {
// deps array 에 추가 불필요. mount 시 1회 구독 + unmount 시 해제.
}, [loadInitial, refreshMeta, upsertNote]);
if (showOnboarding === null) return <></>;
if (showOnboarding) return <OnboardingWizard onClose={() => setShowOnboarding(false)} />;
if (showSettings) return <SettingsPage />;
const showRecovery = continuity.showRecoveryToast && !recoveryDismissed;

View File

@@ -145,6 +145,14 @@ export interface InboxApi {
reason: string | null
): Promise<{ ok: true } | { ok: false; reason: string }>;
classifyStatus(id: string, reason: string): Promise<{ recommended: NoteStatus; rationale: string }>;
// v0.2.9 Cut B Task 12 — settings read + AI/onboarding 토글.
getSettings(): Promise<{
ollama?: { endpoint: string; model: string };
ai_enabled?: boolean;
onboarding_completed?: boolean;
}>;
setAiEnabled(enabled: boolean): Promise<{ ok: true }>;
setOnboardingCompleted(completed: boolean): Promise<{ ok: true }>;
}
export interface InklingApi {