Files
Shade/packages/shade-widgets/src/ShadeProvider.tsx
Sterister 9ceab037ca
Some checks failed
Test / test (push) Has been cancelled
feat(observer): M-Obs 4-7 — widgets, dashboard, docs, integration example
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>
2026-04-10 19:00:21 +02:00

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