Some checks failed
Test / test (push) Has been cancelled
M-Obs 4: @shade/widgets React library - ShadeProvider context with observer URL + token + theme - useShadeState (polling) + useShadeEvents (SSE) hooks - 7 widgets: IdentityCard, SessionList, PrekeyStock, RecentActivity, ServerStatus, FingerprintCompare, WidgetCatalog (meta-widget for user-selectable layout with localStorage persistence) - Self-contained CSS via inline styles, no external CSS conflicts - Light/dark/auto theme via tokens M-Obs 5: @shade/dashboard standalone SPA - Vite + React app composing all widgets into a full debugger layout - Login screen with token persistence to localStorage - Build script copies dist/ to @shade/observer/dist/ for embedded serving - 211 KB JS bundle (66 KB gzipped) M-Obs 6: Documentation + integration example - READMEs for @shade/observer and @shade/widgets - examples/06-observer-dashboard runnable demo: spins up prekey server + observer, runs Alice ↔ Bob conversation loop, dashboard at :3901 - Updated root README and docker-compose.yml with observer integration M-Obs 7: End-to-end verification - StateAggregator now drains buffered events on subscription, so identity.initialized fires before observer construction are still seen - Verified live: snapshot endpoint returns full state, dashboard serves, 401 without auth, sessions/messages/ratchet steps all tracked 220 tests passing, 0 failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
68 lines
2.0 KiB
TypeScript
68 lines
2.0 KiB
TypeScript
import React, { createContext, useContext, useMemo } from 'react';
|
|
import { resolveTheme, type ShadeTheme, type ThemeMode } from './theme.js';
|
|
|
|
export interface ShadeContextValue {
|
|
observerUrl: string;
|
|
token: string;
|
|
theme: ShadeTheme;
|
|
pollIntervalMs: number;
|
|
}
|
|
|
|
const ShadeContext = createContext<ShadeContextValue | null>(null);
|
|
|
|
export interface ShadeProviderProps {
|
|
/** Base URL of the Shade observer endpoint, e.g. "https://shade.example.com/shade-observer" */
|
|
observerUrl: string;
|
|
/** Bearer token for the observer (matches SHADE_OBSERVER_TOKEN on the server) */
|
|
token: string;
|
|
/** Theme mode: dark, light, or auto (matches system preference). Default: dark. */
|
|
themeMode?: ThemeMode;
|
|
/** How often to poll /api/state in milliseconds. Default: 5000. */
|
|
pollIntervalMs?: number;
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
/**
|
|
* ShadeProvider — root context provider that all Shade widgets need.
|
|
*
|
|
* Wrap your dashboard or specific widget container with this and pass
|
|
* the observer URL + token. All child widgets will share the connection.
|
|
*
|
|
* ```tsx
|
|
* <ShadeProvider observerUrl="https://x.com/shade-observer" token={process.env.TOKEN!}>
|
|
* <SessionList />
|
|
* <PrekeyStock />
|
|
* </ShadeProvider>
|
|
* ```
|
|
*/
|
|
export function ShadeProvider({
|
|
observerUrl,
|
|
token,
|
|
themeMode = 'dark',
|
|
pollIntervalMs = 5000,
|
|
children,
|
|
}: ShadeProviderProps): React.ReactElement {
|
|
const value = useMemo<ShadeContextValue>(
|
|
() => ({
|
|
observerUrl: observerUrl.replace(/\/$/, ''),
|
|
token,
|
|
theme: resolveTheme(themeMode),
|
|
pollIntervalMs,
|
|
}),
|
|
[observerUrl, token, themeMode, pollIntervalMs],
|
|
);
|
|
|
|
return <ShadeContext.Provider value={value}>{children}</ShadeContext.Provider>;
|
|
}
|
|
|
|
/** Internal hook used by widgets to access the context. Throws if no provider. */
|
|
export function useShadeContext(): ShadeContextValue {
|
|
const ctx = useContext(ShadeContext);
|
|
if (!ctx) {
|
|
throw new Error(
|
|
'Shade widgets must be wrapped in <ShadeProvider observerUrl="..." token="..." />',
|
|
);
|
|
}
|
|
return ctx;
|
|
}
|