feat(p8): /v1/calendar REST + kernel-client methods + @insignia/iios-meeting-web

Task 8.6: CalendarController exposes schedule/request(+fromCallback/fromClaim)/
list/get/consent/transcript/summarize/action-items/providers(+sync) (session-auth).
kernel-client RestClient gains scheduleMeeting/requestMeeting/listMeetings/
getMeeting/setConsent/generateTranscript/summarizeMeeting/listActionItems/
connect+syncCalendarProvider + Meeting/MeetingParticipant/MeetingActionItem types.
New @insignia/iios-meeting-web (MeetingProvider + useMeetings/useMeeting/useSchedule/
useConsent/useSummarize/useActionItems). Boundary: meeting-web -> contracts +
kernel-client (fails on meeting-web->service).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 18:09:18 +05:30
parent 83369f2055
commit ca9498ce83
12 changed files with 366 additions and 1 deletions
+27
View File
@@ -0,0 +1,27 @@
{
"name": "@insignia/iios-meeting-web",
"version": "0.0.0",
"private": true,
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" } },
"files": ["dist"],
"scripts": {
"build": "tsup",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@insignia/iios-kernel-client": "workspace:*"
},
"peerDependencies": {
"react": ">=18"
},
"devDependencies": {
"@types/react": "^19.0.0",
"react": "^19.0.0",
"tsup": "^8.3.5",
"typescript": "^5.7.3"
}
}
+2
View File
@@ -0,0 +1,2 @@
export { MeetingProvider, useMeetings, useMeeting, useSchedule, useConsent, useSummarize, useActionItems } from './react';
export type { Meeting, MeetingParticipant, MeetingActionItem } from '@insignia/iios-kernel-client';
+97
View File
@@ -0,0 +1,97 @@
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { RestClient, type Meeting, type MeetingActionItem } from '@insignia/iios-kernel-client';
const MeetingContext = createContext<RestClient | null>(null);
export function MeetingProvider({
serviceUrl,
token,
children,
}: {
serviceUrl: string;
token: string;
children: React.ReactNode;
}): React.ReactElement {
const client = useMemo(() => new RestClient({ serviceUrl, token }), [serviceUrl, token]);
return <MeetingContext.Provider value={client}>{children}</MeetingContext.Provider>;
}
function useClient(): RestClient {
const c = useContext(MeetingContext);
if (!c) throw new Error('meeting hooks must be used within <MeetingProvider>');
return c;
}
export function useMeetings(opts?: { pollMs?: number }): { meetings: Meeting[]; refresh: () => Promise<void> } {
const client = useClient();
const [meetings, setMeetings] = useState<Meeting[]>([]);
const refresh = useCallback(async () => {
try {
setMeetings(await client.listMeetings());
} catch {
/* transient */
}
}, [client]);
useEffect(() => {
void refresh();
if (!opts?.pollMs) return;
const t = setInterval(() => void refresh(), opts.pollMs);
return () => clearInterval(t);
}, [refresh, opts?.pollMs]);
return { meetings, refresh };
}
export function useMeeting(id: string | null): { meeting: Meeting | null; refresh: () => Promise<void> } {
const client = useClient();
const [meeting, setMeeting] = useState<Meeting | null>(null);
const refresh = useCallback(async () => {
if (!id) return setMeeting(null);
try {
setMeeting(await client.getMeeting(id));
} catch {
/* transient */
}
}, [client, id]);
useEffect(() => {
void refresh();
}, [refresh]);
return { meeting, refresh };
}
export function useSchedule(): (input: Parameters<RestClient['scheduleMeeting']>[0]) => Promise<Meeting> {
const client = useClient();
return (input) => client.scheduleMeeting(input);
}
export function useConsent(): {
setConsent: (meetingId: string, actorRefId: string, status: string) => Promise<unknown>;
generateTranscript: (meetingId: string) => Promise<unknown>;
} {
const client = useClient();
return {
setConsent: (meetingId, actorRefId, status) => client.setConsent(meetingId, actorRefId, status),
generateTranscript: (meetingId) => client.generateTranscript(meetingId),
};
}
export function useSummarize(): (meetingId: string) => Promise<Meeting> {
const client = useClient();
return (meetingId) => client.summarizeMeeting(meetingId);
}
export function useActionItems(meetingId: string | null): { actionItems: MeetingActionItem[]; refresh: () => Promise<void> } {
const client = useClient();
const [actionItems, setActionItems] = useState<MeetingActionItem[]>([]);
const refresh = useCallback(async () => {
if (!meetingId) return setActionItems([]);
try {
setActionItems(await client.listActionItems(meetingId));
} catch {
/* transient */
}
}, [client, meetingId]);
useEffect(() => {
void refresh();
}, [refresh]);
return { actionItems, refresh };
}
+14
View File
@@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM"],
"jsx": "react-jsx",
"types": ["react"]
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["src/**/*.test.ts", "src/**/*.test.tsx"]
}
+9
View File
@@ -0,0 +1,9 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
external: ['react', 'react-dom', '@insignia/iios-kernel-client'],
});