feat(preload): expose typed InklingApi (v0.2: intent + continuity)

Task 3 of the slice plan. Replaces the minimum @shared/types
augmentation with the full domain model: Note (with v0.2
intent + edited-flag fields), NoteTag, NoteMedia, AiStatus,
WeeklyContinuity, CaptureApi, InboxApi, and the InklingApi
aggregate exposed on window.inkling. Adds the preload bridge
that wires every InklingApi method to its IPC channel via
contextBridge.exposeInMainWorld.

Verification: `npm run typecheck` exits 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
altair823
2026-04-25 11:59:39 +09:00
parent 4b16b873c6
commit c480cc0ace
2 changed files with 98 additions and 3 deletions

27
src/preload/index.ts Normal file
View File

@@ -0,0 +1,27 @@
import { contextBridge, ipcRenderer } from 'electron';
import type { InklingApi, Note } from '@shared/types';
const api: InklingApi = {
capture: {
submit: (payload) => ipcRenderer.invoke('capture:submit', payload),
hide: () => ipcRenderer.send('capture:hide')
},
inbox: {
listNotes: (opts) => ipcRenderer.invoke('inbox:list', opts),
updateAiFields: (noteId, fields) =>
ipcRenderer.invoke('inbox:updateAi', { noteId, fields }),
deleteNote: (noteId) => ipcRenderer.invoke('inbox:delete', noteId),
setIntent: (noteId, text) => ipcRenderer.invoke('inbox:setIntent', { noteId, text }),
dismissIntent: (noteId) => ipcRenderer.invoke('inbox:dismissIntent', noteId),
getContinuity: () => ipcRenderer.invoke('inbox:continuity'),
getPendingCount: () => ipcRenderer.invoke('inbox:pendingCount'),
getOllamaStatus: () => ipcRenderer.invoke('inbox:ollamaStatus'),
onNoteUpdated: (cb) => {
const listener = (_e: unknown, note: Note) => cb(note);
ipcRenderer.on('note:updated', listener);
return () => ipcRenderer.off('note:updated', listener);
}
}
};
contextBridge.exposeInMainWorld('inkling', api);

View File

@@ -1,6 +1,74 @@
declare global {
namespace Electron {
interface App { isQuitting?: boolean; }
}
namespace Electron { interface App { isQuitting?: boolean; } }
interface Window { inkling: InklingApi; }
}
export interface NoteMedia {
id: string;
kind: 'image';
relPath: string;
mime: string;
bytes: number;
}
export type AiStatus = 'pending' | 'done' | 'failed';
export interface NoteTag {
name: string;
source: 'ai' | 'user';
}
export interface Note {
id: string;
rawText: string;
aiTitle: string | null;
aiSummary: string | null;
aiStatus: AiStatus;
aiError: string | null;
aiProvider: string | null;
aiGeneratedAt: string | null;
titleEditedByUser: boolean;
summaryEditedByUser: boolean;
userIntent: string | null;
intentPromptedAt: string | null;
createdAt: string;
updatedAt: string;
tags: NoteTag[];
media: NoteMedia[];
}
export interface WeeklyContinuity {
weekStart: string; // ISO date (KST 월요일)
weekCount: number;
weekTarget: number; // 7
consecutiveCompleteWeeks: number;
showRecoveryToast: boolean;
lastNoteAt: string | null;
}
export interface CaptureApi {
submit(payload: { text: string; images: ArrayBuffer[] }): Promise<{ noteId: string }>;
hide(): void;
}
export interface InboxApi {
listNotes(opts: { limit: number; cursor?: string }): Promise<Note[]>;
updateAiFields(
noteId: string,
fields: { title?: string; summary?: string; tags?: string[] }
): Promise<void>;
deleteNote(noteId: string): Promise<void>;
setIntent(noteId: string, text: string): Promise<void>;
dismissIntent(noteId: string): Promise<void>;
getContinuity(): Promise<WeeklyContinuity>;
getPendingCount(): Promise<number>;
getOllamaStatus(): Promise<{ ok: boolean; reason?: string }>;
onNoteUpdated(cb: (note: Note) => void): () => void;
}
export interface InklingApi {
capture: CaptureApi;
inbox: InboxApi;
}
export {};