dynim-react 1.0.59 → 1.0.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,42 +1,73 @@
1
1
  /**
2
- * DynimProvider - React wrapper around DynimManager
2
+ * DynimProvider - Single provider for all Dynim functionality
3
3
  *
4
- * Framework-specific responsibilities:
5
- * - Shared context setup (React, ReactDOM, packages)
6
- * - React context provider + hook
7
- * - Rendering TenantApp or children
8
- * - Theme <style> injection
4
+ * Handles:
5
+ * - Loading tenant bundles
6
+ * - Visual builder UI (when user has edit permissions)
7
+ * - Shared context for bundles to access React, packages, etc.
9
8
  */
10
9
  import { type ReactNode } from 'react';
11
- import { type Checkpoint, type RestoreResult, type CodeMessage } from 'dynim-core';
10
+ import type { CodeMessage, Checkpoint, RestoreResult } from 'dynim-core';
12
11
  export interface DynimConfig {
12
+ /** Function to fetch a session token for authentication */
13
13
  getSession?: () => Promise<{
14
14
  token: string;
15
15
  refreshToken?: string;
16
16
  }>;
17
+ /** Called when an error occurs */
17
18
  onError?: (error: Event | Error) => void;
19
+ /** NPM packages to expose to bundles (e.g., { 'react-router-dom': ReactRouterDOM }) */
18
20
  packages?: Record<string, unknown>;
21
+ /** Custom hooks to expose to bundles */
19
22
  hooks?: Record<string, unknown>;
23
+ /** React contexts to expose to bundles */
20
24
  contexts?: Record<string, unknown>;
21
25
  }
22
26
  export interface DynimContextValue {
27
+ /** Enter edit mode (shows builder UI) */
23
28
  enterBuilder: () => void;
29
+ /** Exit edit mode */
24
30
  exitBuilder: () => void;
31
+ /** Whether builder/edit mode is active */
25
32
  isEditing: boolean;
33
+ /** Send a code edit request to AI */
26
34
  sendCode: (query: string) => Promise<void>;
35
+ /** Save current edits */
27
36
  saveCode: () => Promise<void>;
37
+ /** Abandon/discard current edits */
28
38
  abandonCode: () => Promise<void>;
39
+ /** Get active checkpoints for the project */
29
40
  getCheckpoints: () => Promise<Checkpoint[]>;
41
+ /** Restore to a specific checkpoint (reloads bundle on success) */
30
42
  restoreCheckpoint: (checkpointId: string) => Promise<RestoreResult>;
43
+ /** Whether a checkpoint restore is in progress */
31
44
  isRestoring: boolean;
45
+ /** Current code message state (thinking, edits, etc.) */
32
46
  codeMessage: CodeMessage;
47
+ /** Whether a bundle is currently loaded */
33
48
  isBundleLoaded: boolean;
49
+ /** Whether bundle is loading */
34
50
  isBundleLoading: boolean;
35
51
  }
36
52
  export interface DynimProviderProps {
37
53
  children: ReactNode;
38
54
  config?: DynimConfig;
39
55
  }
56
+ /**
57
+ * DynimProvider - Wrap your app with this to enable Dynim functionality
58
+ *
59
+ * @example
60
+ * ```tsx
61
+ * import { DynimProvider } from 'dynim-react';
62
+ *
63
+ * <DynimProvider>
64
+ * <App />
65
+ * </DynimProvider>
66
+ * ```
67
+ */
40
68
  export declare function DynimProvider({ children, config, }: DynimProviderProps): JSX.Element;
69
+ /**
70
+ * Hook to access Dynim functionality
71
+ */
41
72
  export declare function useDynim(): DynimContextValue;
42
73
  //# sourceMappingURL=DynimProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DynimProvider.d.ts","sourceRoot":"","sources":["../src/DynimProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAc,EAMZ,KAAK,SAAS,EAEf,MAAM,OAAO,CAAC;AAEf,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,KAAK,IAAI,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,cAAc,EAAE,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC5C,iBAAiB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IACpE,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,OAAO,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAID,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,MAAW,GACZ,EAAE,kBAAkB,GAAG,GAAG,CAAC,OAAO,CAwGlC;AAED,wBAAgB,QAAQ,IAAI,iBAAiB,CAM5C"}
1
+ {"version":3,"file":"DynimProvider.d.ts","sourceRoot":"","sources":["../src/DynimProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAc,EAQZ,KAAK,SAAS,EAEf,MAAM,OAAO,CAAC;AAWf,OAAO,KAAK,EAKV,WAAW,EACX,UAAU,EACV,aAAa,EAGd,MAAM,YAAY,CAAC;AAIpB,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrE,kCAAkC;IAClC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,KAAK,IAAI,CAAC;IACzC,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,iBAAiB;IAChC,yCAAyC;IACzC,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,qBAAqB;IACrB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,0CAA0C;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,qCAAqC;IACrC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,yBAAyB;IACzB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,oCAAoC;IACpC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,6CAA6C;IAC7C,cAAc,EAAE,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC5C,mEAAmE;IACnE,iBAAiB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IACpE,kDAAkD;IAClD,WAAW,EAAE,OAAO,CAAC;IACrB,yDAAyD;IACzD,WAAW,EAAE,WAAW,CAAC;IACzB,2CAA2C;IAC3C,cAAc,EAAE,OAAO,CAAC;IACxB,gCAAgC;IAChC,eAAe,EAAE,OAAO,CAAC;CAC1B;AAID,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,MAAW,GACZ,EAAE,kBAAkB,GAAG,GAAG,CAAC,OAAO,CAkdlC;AAED;;GAEG;AACH,wBAAgB,QAAQ,IAAI,iBAAiB,CAM5C"}
@@ -1,36 +1,76 @@
1
1
  import { jsxs as _jsxs, Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
2
  /**
3
- * DynimProvider - React wrapper around DynimManager
3
+ * DynimProvider - Single provider for all Dynim functionality
4
4
  *
5
- * Framework-specific responsibilities:
6
- * - Shared context setup (React, ReactDOM, packages)
7
- * - React context provider + hook
8
- * - Rendering TenantApp or children
9
- * - Theme <style> injection
5
+ * Handles:
6
+ * - Loading tenant bundles
7
+ * - Visual builder UI (when user has edit permissions)
8
+ * - Shared context for bundles to access React, packages, etc.
10
9
  */
11
- import React, { createContext, useContext, useEffect, useMemo, useState, } from 'react';
10
+ import React, { createContext, useContext, useEffect, useRef, useCallback, useMemo, useState, } from 'react';
12
11
  import ReactDOM from 'react-dom';
13
- import { createDynimManager, } from 'dynim-core';
12
+ import { createBuilderClient, createBuilder, createCodeClient, createBundleLoader, createCSSLoader, BundleNotFoundError, BundleAuthError, } from 'dynim-core';
14
13
  import { createSharedContext, updateSharedContext, isSharedContextReady } from './inference/sharedContext';
14
+ import { generateThemeCSS } from './theme';
15
15
  const DynimContext = createContext(null);
16
+ /**
17
+ * DynimProvider - Wrap your app with this to enable Dynim functionality
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * import { DynimProvider } from 'dynim-react';
22
+ *
23
+ * <DynimProvider>
24
+ * <App />
25
+ * </DynimProvider>
26
+ * ```
27
+ */
16
28
  export function DynimProvider({ children, config = {}, }) {
17
- // Create manager once (stable across renders)
18
- const manager = useMemo(() => createDynimManager({
19
- getSession: config.getSession,
20
- onError: config.onError,
21
- }),
22
- // eslint-disable-next-line react-hooks/exhaustive-deps
23
- []);
24
- // Subscribe to manager state
25
- const [state, setState] = useState(() => manager.getSnapshot());
29
+ const builderClientRef = useRef(null);
30
+ const builderRef = useRef(null);
31
+ const codeClientRef = useRef(null);
32
+ const bundleLoaderRef = useRef(null);
33
+ const cssLoaderRef = useRef(null);
34
+ // Track when we're exiting to prevent auto-load from re-triggering
35
+ const isExitingRef = useRef(false);
36
+ const hasAttemptedInitialLoadRef = useRef(false);
37
+ const isBuilderActiveRef = useRef(false);
38
+ const [isEditing, setIsEditing] = useState(false);
39
+ const [isBundleLoaded, setIsBundleLoaded] = useState(false);
40
+ const [isBundleLoading, setIsBundleLoading] = useState(false);
41
+ const [isInitialLoadComplete, setIsInitialLoadComplete] = useState(false);
42
+ const [TenantApp, setTenantApp] = useState(null);
43
+ const [bundleError, setBundleError] = useState(null);
44
+ const [isRestoring, setIsRestoring] = useState(false);
45
+ const [codeMessage, setCodeMessage] = useState({
46
+ thinking: '',
47
+ text: '',
48
+ edits: [],
49
+ status: 'idle',
50
+ bundleReady: false,
51
+ bundleError: undefined,
52
+ });
53
+ // Cached auth token
54
+ const cachedTokenRef = useRef(null);
55
+ const tokenPromiseRef = useRef(null);
56
+ // Bundle load signal
57
+ const [bundleLoadSignal, setBundleLoadSignal] = useState(0);
58
+ const pendingBundleProjectIdRef = useRef(null);
59
+ // Theme state (fetched from API per project)
60
+ const [theme, setTheme] = useState(null);
61
+ const hasAttemptedThemeLoadRef = useRef(false);
62
+ // Keep config in ref to avoid re-triggering effects on every render
63
+ const configRef = useRef(config);
64
+ configRef.current = config;
65
+ // Set up shared context for bundles (only once on mount)
26
66
  useEffect(() => {
27
- // Set up shared context for bundles (framework-specific)
28
- const { packages = {}, hooks = {}, contexts = {} } = config;
67
+ const { packages = {}, hooks = {}, contexts = {} } = configRef.current;
29
68
  const sdkPackages = {
30
69
  'dynim-react': { DynimProvider, useDynim },
31
70
  ...packages,
32
71
  };
33
72
  if (!isSharedContextReady()) {
73
+ // Create fresh shared context
34
74
  createSharedContext({
35
75
  React,
36
76
  ReactDOM,
@@ -40,57 +80,359 @@ export function DynimProvider({ children, config = {}, }) {
40
80
  });
41
81
  }
42
82
  else {
83
+ // Update existing context with packages (handles HMR)
43
84
  updateSharedContext({
44
85
  packages: sdkPackages,
45
86
  hooks,
46
87
  contexts,
47
88
  });
48
89
  }
49
- // Subscribe to state changes
50
- const unsubscribe = manager.subscribe(setState);
51
- // Initialize manager
52
- manager.init();
90
+ }, []);
91
+ // Get auth token (cached after first call)
92
+ const getAuthToken = useCallback(async () => {
93
+ const { getSession } = configRef.current;
94
+ // Return cached token if available
95
+ if (cachedTokenRef.current)
96
+ return cachedTokenRef.current;
97
+ // Reuse in-flight promise if one exists
98
+ if (tokenPromiseRef.current)
99
+ return tokenPromiseRef.current;
100
+ if (getSession) {
101
+ tokenPromiseRef.current = (async () => {
102
+ try {
103
+ const session = await getSession();
104
+ cachedTokenRef.current = session.token;
105
+ return session.token;
106
+ }
107
+ catch {
108
+ return null;
109
+ }
110
+ finally {
111
+ tokenPromiseRef.current = null;
112
+ }
113
+ })();
114
+ return tokenPromiseRef.current;
115
+ }
116
+ return null;
117
+ }, []);
118
+ // Initialize code client (only once on mount)
119
+ useEffect(() => {
120
+ const { getSession, onError } = configRef.current;
121
+ codeClientRef.current = createCodeClient({
122
+ getSession,
123
+ onMessageUpdate: setCodeMessage,
124
+ onError: (error) => {
125
+ console.error('[DynimProvider] Code error:', error);
126
+ configRef.current.onError?.(error);
127
+ },
128
+ });
129
+ }, []);
130
+ // Load bundle ref
131
+ const loadBundleRef = useRef(null);
132
+ // Initialize builder (only once on mount)
133
+ useEffect(() => {
134
+ const { getSession } = configRef.current;
135
+ builderRef.current = createBuilder({
136
+ getSession,
137
+ codeClient: codeClientRef.current ?? undefined,
138
+ onExitStart: () => {
139
+ isExitingRef.current = true;
140
+ isBuilderActiveRef.current = false;
141
+ codeClientRef.current?.abort();
142
+ codeClientRef.current?.resetMessage();
143
+ },
144
+ loadBundle: (bundleUrl) => {
145
+ // Note: isExitingRef is already set by onExitStart for the exit flow.
146
+ // Don't set it here — this callback is also used by checkpoint restore
147
+ // while the builder is still active.
148
+ return loadBundleRef.current?.(bundleUrl) ?? Promise.resolve();
149
+ },
150
+ onEnter: () => {
151
+ isExitingRef.current = false;
152
+ isBuilderActiveRef.current = true;
153
+ setIsEditing(true);
154
+ },
155
+ onExit: () => {
156
+ setIsEditing(false);
157
+ },
158
+ });
53
159
  return () => {
54
- unsubscribe();
55
- manager.destroy();
160
+ builderRef.current?.destroy();
161
+ // Clean up CSS link element on unmount
162
+ cssLoaderRef.current?.unload();
163
+ };
164
+ }, []);
165
+ // Initialize builder client (only once on mount)
166
+ useEffect(() => {
167
+ const { getSession } = configRef.current;
168
+ builderClientRef.current = createBuilderClient({
169
+ getSession,
170
+ onError: (error) => {
171
+ console.error('[DynimProvider] Error:', error);
172
+ configRef.current.onError?.(error);
173
+ },
174
+ });
175
+ }, []);
176
+ // Get bundle loader
177
+ const getBundleLoader = useCallback(() => {
178
+ if (!bundleLoaderRef.current) {
179
+ bundleLoaderRef.current = createBundleLoader({
180
+ getAuthToken,
181
+ includeCredentials: true,
182
+ onError: (error) => {
183
+ console.error('[DynimProvider] Bundle load failed:', error);
184
+ configRef.current.onError?.(error);
185
+ },
186
+ });
187
+ }
188
+ return bundleLoaderRef.current;
189
+ }, [getAuthToken]);
190
+ // Get CSS loader
191
+ const getCSSLoader = useCallback(() => {
192
+ if (!cssLoaderRef.current) {
193
+ cssLoaderRef.current = createCSSLoader({
194
+ linkId: 'dynim-bundle-css',
195
+ onError: (error) => {
196
+ console.warn('[DynimProvider] CSS load failed:', error);
197
+ },
198
+ });
199
+ }
200
+ return cssLoaderRef.current;
201
+ }, []);
202
+ // Load bundle (JS + CSS)
203
+ const loadBundle = useCallback(async (bundleUrl, loadCSS = true) => {
204
+ const loader = getBundleLoader();
205
+ if (loader.isLoading())
206
+ return;
207
+ setIsBundleLoading(true);
208
+ setBundleError(null);
209
+ try {
210
+ const { App, cleanup } = await loader.load(bundleUrl);
211
+ setTenantApp(() => App);
212
+ setIsBundleLoaded(true);
213
+ cleanup();
214
+ // Load CSS alongside JS bundle
215
+ if (loadCSS) {
216
+ const cssLoader = getCSSLoader();
217
+ const isTemp = bundleUrl.includes('temp=true');
218
+ const cssUrl = isTemp ? '/api/code/css?temp=true' : '/api/code/css';
219
+ if (isTemp) {
220
+ // Temp/preview CSS - bust cache to get latest
221
+ cssLoader.loadWithCacheBust(cssUrl);
222
+ }
223
+ else {
224
+ // Saved CSS - let browser cache
225
+ cssLoader.load(cssUrl);
226
+ }
227
+ }
228
+ }
229
+ catch (error) {
230
+ // 404 = no bundle, 401 = not authenticated - show children
231
+ if (error instanceof BundleNotFoundError || error instanceof BundleAuthError) {
232
+ setTenantApp(null);
233
+ setIsBundleLoaded(false);
234
+ // Also unload any CSS
235
+ getCSSLoader().unload();
236
+ return;
237
+ }
238
+ setBundleError(error);
239
+ }
240
+ finally {
241
+ setIsBundleLoading(false);
242
+ }
243
+ }, [getBundleLoader, getCSSLoader]);
244
+ // Keep ref updated
245
+ useEffect(() => {
246
+ loadBundleRef.current = loadBundle;
247
+ }, [loadBundle]);
248
+ // Theme is fetched lazily when entering builder mode
249
+ // This avoids making HTTP requests on mount that could trigger auth redirects
250
+ const fetchTheme = useCallback(async () => {
251
+ if (hasAttemptedThemeLoadRef.current)
252
+ return;
253
+ if (!configRef.current.getSession)
254
+ return;
255
+ hasAttemptedThemeLoadRef.current = true;
256
+ try {
257
+ const token = await getAuthToken();
258
+ const headers = {};
259
+ if (token) {
260
+ headers['Authorization'] = `Bearer ${token}`;
261
+ }
262
+ const response = await fetch('/api/code/theme', {
263
+ headers,
264
+ credentials: 'include',
265
+ });
266
+ if (response.ok) {
267
+ const data = await response.json();
268
+ setTheme(data);
269
+ }
270
+ }
271
+ catch (error) {
272
+ // Theme fetch failed - use defaults (non-critical)
273
+ console.warn('[DynimProvider] Failed to fetch theme:', error);
274
+ }
275
+ }, [getAuthToken]);
276
+ // Auto-load saved bundle on mount if auth is available
277
+ useEffect(() => {
278
+ if (hasAttemptedInitialLoadRef.current)
279
+ return;
280
+ hasAttemptedInitialLoadRef.current = true;
281
+ const { getSession } = configRef.current;
282
+ // No auth configured = skip bundle load
283
+ if (!getSession) {
284
+ setIsInitialLoadComplete(true);
285
+ return;
286
+ }
287
+ // Load bundle with the provided token
288
+ const doInitialLoad = async () => {
289
+ try {
290
+ await loadBundleRef.current?.('/api/code/bundle');
291
+ }
292
+ catch (error) {
293
+ // Errors handled in loadBundle (404 = no bundle, etc.)
294
+ console.log('[DynimProvider] Initial bundle load:', error.message);
295
+ }
296
+ finally {
297
+ setIsInitialLoadComplete(true);
298
+ }
56
299
  };
57
- // eslint-disable-next-line react-hooks/exhaustive-deps
300
+ doInitialLoad();
301
+ // Prefetch theme after 3s delay (non-blocking, so it's ready when builder opens)
302
+ const themeTimeout = setTimeout(() => {
303
+ fetchTheme();
304
+ }, 3000);
305
+ return () => clearTimeout(themeTimeout);
306
+ }, [fetchTheme]);
307
+ // Load saved bundle
308
+ const loadSavedBundle = useCallback(async () => {
309
+ // Note: CSS is loaded separately by saveCode/abandonCode with cache busting
310
+ await loadBundle('/api/code/bundle', false);
311
+ }, [loadBundle]);
312
+ // Watch for bundleReady
313
+ useEffect(() => {
314
+ if (codeMessage.bundleReady && codeMessage.projectId) {
315
+ if (isBuilderActiveRef.current && !isExitingRef.current) {
316
+ pendingBundleProjectIdRef.current = codeMessage.projectId;
317
+ setBundleLoadSignal(s => s + 1);
318
+ }
319
+ }
320
+ }, [codeMessage.bundleReady, codeMessage.projectId]);
321
+ // Load temp bundle on signal (JS + CSS with cache busting)
322
+ useEffect(() => {
323
+ if (bundleLoadSignal === 0)
324
+ return;
325
+ const projectId = pendingBundleProjectIdRef.current;
326
+ if (!projectId || isExitingRef.current) {
327
+ pendingBundleProjectIdRef.current = null;
328
+ return;
329
+ }
330
+ // Load JS bundle with cache busting
331
+ const bundleUrl = `/api/code/bundle?temp=true&_t=${Date.now()}`;
332
+ loadBundleRef.current?.(bundleUrl, true); // true = also load CSS
333
+ pendingBundleProjectIdRef.current = null;
334
+ }, [bundleLoadSignal]);
335
+ // Public methods
336
+ const enterBuilder = useCallback(() => {
337
+ if (builderRef.current && !builderRef.current.isActive()) {
338
+ builderRef.current.enter();
339
+ // Fetch theme lazily when entering builder
340
+ fetchTheme();
341
+ }
342
+ }, [fetchTheme]);
343
+ const exitBuilder = useCallback(() => {
344
+ if (builderRef.current && builderRef.current.isActive()) {
345
+ builderRef.current.exit();
346
+ }
347
+ }, []);
348
+ const sendCode = useCallback(async (query) => {
349
+ await codeClientRef.current?.sendCode(query);
350
+ }, []);
351
+ const saveCode = useCallback(async () => {
352
+ await codeClientRef.current?.saveCode();
353
+ await loadSavedBundle();
354
+ // Force reload CSS with cache bust since saved CSS has changed
355
+ getCSSLoader().loadWithCacheBust('/api/code/css');
356
+ }, [loadSavedBundle, getCSSLoader]);
357
+ const abandonCode = useCallback(async () => {
358
+ await codeClientRef.current?.abandonCode();
359
+ await loadSavedBundle();
360
+ // Reload saved CSS (may have been showing temp CSS)
361
+ getCSSLoader().loadWithCacheBust('/api/code/css');
362
+ }, [loadSavedBundle, getCSSLoader]);
363
+ const getCheckpoints = useCallback(async () => {
364
+ return codeClientRef.current?.getCheckpoints() ?? [];
365
+ }, []);
366
+ const restoreCheckpoint = useCallback(async (checkpointId) => {
367
+ if (!codeClientRef.current) {
368
+ throw new Error('Code client not initialized');
369
+ }
370
+ setIsRestoring(true);
371
+ try {
372
+ const result = await codeClientRef.current.restoreCheckpoint(checkpointId);
373
+ // Reload bundle + CSS on success (loadBundle handles CSS when loadCSS=true)
374
+ if (result.bundle_ready) {
375
+ const bundleUrl = `/api/code/bundle?temp=true&_t=${Date.now()}`;
376
+ await loadBundleRef.current?.(bundleUrl, true);
377
+ }
378
+ return result;
379
+ }
380
+ finally {
381
+ setIsRestoring(false);
382
+ }
58
383
  }, []);
59
384
  const contextValue = useMemo(() => ({
60
- enterBuilder: manager.enterBuilder,
61
- exitBuilder: manager.exitBuilder,
62
- isEditing: state.isEditing,
63
- sendCode: manager.sendCode,
64
- saveCode: manager.saveCode,
65
- abandonCode: manager.abandonCode,
66
- getCheckpoints: manager.getCheckpoints,
67
- restoreCheckpoint: manager.restoreCheckpoint,
68
- isRestoring: state.isRestoring,
69
- codeMessage: state.codeMessage,
70
- isBundleLoaded: state.isBundleLoaded,
71
- isBundleLoading: state.isBundleLoading,
72
- }), [manager, state]);
385
+ enterBuilder,
386
+ exitBuilder,
387
+ isEditing,
388
+ sendCode,
389
+ saveCode,
390
+ abandonCode,
391
+ getCheckpoints,
392
+ restoreCheckpoint,
393
+ isRestoring,
394
+ codeMessage,
395
+ isBundleLoaded,
396
+ isBundleLoading,
397
+ }), [
398
+ enterBuilder,
399
+ exitBuilder,
400
+ isEditing,
401
+ sendCode,
402
+ saveCode,
403
+ abandonCode,
404
+ getCheckpoints,
405
+ restoreCheckpoint,
406
+ isRestoring,
407
+ codeMessage,
408
+ isBundleLoaded,
409
+ isBundleLoading,
410
+ ]);
73
411
  // Render content
74
412
  const renderContent = () => {
75
- if (!state.isInitialLoadComplete)
413
+ if (!isInitialLoadComplete)
76
414
  return null;
77
- if (state.bundleError) {
415
+ if (bundleError) {
78
416
  return (_jsxs(_Fragment, { children: [_jsxs("div", { style: {
79
417
  padding: 12,
80
418
  background: '#fef3c7',
81
419
  borderBottom: '1px solid #fcd34d',
82
420
  fontSize: 13,
83
421
  color: '#92400e',
84
- }, children: ["Bundle error: ", state.bundleError.message] }), children] }));
422
+ }, children: ["Bundle error: ", bundleError.message] }), children] }));
85
423
  }
86
- if (state.tenantApp) {
87
- const TenantApp = state.tenantApp;
424
+ if (TenantApp) {
88
425
  return _jsx(TenantApp, {});
89
426
  }
90
427
  return children;
91
428
  };
92
- return (_jsxs(DynimContext.Provider, { value: contextValue, children: [_jsx("style", { children: state.themeCSS }), renderContent()] }));
429
+ // Generate theme CSS from API-fetched theme
430
+ const themeCSS = useMemo(() => generateThemeCSS(theme ?? undefined), [theme]);
431
+ return (_jsxs(DynimContext.Provider, { value: contextValue, children: [_jsx("style", { children: themeCSS }), renderContent()] }));
93
432
  }
433
+ /**
434
+ * Hook to access Dynim functionality
435
+ */
94
436
  export function useDynim() {
95
437
  const context = useContext(DynimContext);
96
438
  if (!context) {
@@ -1,4 +1,54 @@
1
- import { dynimPackages } from 'dynim-core/vite';
2
- export { dynimPackages, type DynimPackagesOptions } from 'dynim-core/vite';
1
+ /**
2
+ * Vite plugin for Dynim package exposure
3
+ *
4
+ * Generates a virtual module that exports all specified packages,
5
+ * so you don't have to manually import and map each one.
6
+ *
7
+ * Usage:
8
+ * ```ts
9
+ * // vite.config.ts
10
+ * import { dynimPackages } from 'dynim-react/vite'
11
+ *
12
+ * export default {
13
+ * plugins: [
14
+ * dynimPackages([
15
+ * 'react-router-dom',
16
+ * '@tanstack/react-query',
17
+ * 'axios',
18
+ * ])
19
+ * ]
20
+ * }
21
+ * ```
22
+ *
23
+ * Then in your app:
24
+ * ```tsx
25
+ * import packages from 'virtual:dynim-packages'
26
+ *
27
+ * <DynimProvider config={{ packages }}>
28
+ * ```
29
+ */
30
+ import type { Plugin } from 'vite';
31
+ export interface DynimPackagesOptions {
32
+ /**
33
+ * List of package names to expose to dynim bundles.
34
+ * These must be installed in your project's node_modules.
35
+ */
36
+ packages: string[];
37
+ }
38
+ /**
39
+ * Vite plugin that generates a virtual module exporting all specified packages.
40
+ *
41
+ * @param packages - Array of package names, or options object
42
+ * @returns Vite plugin
43
+ *
44
+ * @example
45
+ * // Simple usage - array of package names
46
+ * dynimPackages(['react-router-dom', 'axios'])
47
+ *
48
+ * @example
49
+ * // Options object
50
+ * dynimPackages({ packages: ['react-router-dom', 'axios'] })
51
+ */
52
+ export declare function dynimPackages(packagesOrOptions: string[] | DynimPackagesOptions): Plugin;
3
53
  export default dynimPackages;
4
54
  //# sourceMappingURL=plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/vite/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC3E,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/vite/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAKlC,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,iBAAiB,EAAE,MAAM,EAAE,GAAG,oBAAoB,GACjD,MAAM,CAwCR;AAED,eAAe,aAAa,CAAA"}
@@ -1,3 +1,82 @@
1
- import { dynimPackages } from 'dynim-core/vite';
2
- export { dynimPackages } from 'dynim-core/vite';
1
+ /**
2
+ * Vite plugin for Dynim package exposure
3
+ *
4
+ * Generates a virtual module that exports all specified packages,
5
+ * so you don't have to manually import and map each one.
6
+ *
7
+ * Usage:
8
+ * ```ts
9
+ * // vite.config.ts
10
+ * import { dynimPackages } from 'dynim-react/vite'
11
+ *
12
+ * export default {
13
+ * plugins: [
14
+ * dynimPackages([
15
+ * 'react-router-dom',
16
+ * '@tanstack/react-query',
17
+ * 'axios',
18
+ * ])
19
+ * ]
20
+ * }
21
+ * ```
22
+ *
23
+ * Then in your app:
24
+ * ```tsx
25
+ * import packages from 'virtual:dynim-packages'
26
+ *
27
+ * <DynimProvider config={{ packages }}>
28
+ * ```
29
+ */
30
+ const VIRTUAL_MODULE_ID = 'virtual:dynim-packages';
31
+ const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID;
32
+ /**
33
+ * Vite plugin that generates a virtual module exporting all specified packages.
34
+ *
35
+ * @param packages - Array of package names, or options object
36
+ * @returns Vite plugin
37
+ *
38
+ * @example
39
+ * // Simple usage - array of package names
40
+ * dynimPackages(['react-router-dom', 'axios'])
41
+ *
42
+ * @example
43
+ * // Options object
44
+ * dynimPackages({ packages: ['react-router-dom', 'axios'] })
45
+ */
46
+ export function dynimPackages(packagesOrOptions) {
47
+ const packageNames = Array.isArray(packagesOrOptions)
48
+ ? packagesOrOptions
49
+ : packagesOrOptions.packages;
50
+ return {
51
+ name: 'dynim-packages',
52
+ resolveId(id) {
53
+ if (id === VIRTUAL_MODULE_ID) {
54
+ return RESOLVED_VIRTUAL_MODULE_ID;
55
+ }
56
+ },
57
+ load(id) {
58
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) {
59
+ // Generate import statements for each package
60
+ const imports = packageNames
61
+ .map((name, i) => `import * as _pkg${i} from '${name}'`)
62
+ .join('\n');
63
+ // Generate the packages object entries
64
+ // Spread namespace objects into plain objects so exports are enumerable
65
+ const entries = packageNames
66
+ .map((name, i) => ` '${name}': { ..._pkg${i} }`)
67
+ .join(',\n');
68
+ // Return the generated module code
69
+ return `${imports}
70
+
71
+ const packages = {
72
+ ${entries}
73
+ }
74
+
75
+ export default packages
76
+ export { packages }
77
+ `;
78
+ }
79
+ },
80
+ };
81
+ }
3
82
  export default dynimPackages;
package/dist/theme.d.ts CHANGED
@@ -1,2 +1,58 @@
1
- export { generateThemeCSS, defaultTheme, themeVars, type DynimTheme } from 'dynim-core';
1
+ /**
2
+ * Dynim Theme System
3
+ *
4
+ * Theme is automatically fetched from the API per project.
5
+ * CSS variables are injected with defaults matching the original design.
6
+ */
7
+ export interface DynimTheme {
8
+ accent?: string;
9
+ accentHover?: string;
10
+ builderBg?: string;
11
+ builderText?: string;
12
+ builderFont?: string;
13
+ builderRadius?: string;
14
+ builderOpacity?: string;
15
+ chatAccent?: string;
16
+ chatBg?: string;
17
+ chatText?: string;
18
+ chatFont?: string;
19
+ chatRadius?: string;
20
+ chatOpacity?: string;
21
+ selectionColor?: string;
22
+ hoverColor?: string;
23
+ selectionRadius?: string;
24
+ selectionOpacity?: string;
25
+ hoverOpacity?: string;
26
+ }
27
+ /**
28
+ * Default theme values - matches original SDK styling exactly
29
+ */
30
+ export declare const defaultTheme: Required<DynimTheme>;
31
+ /**
32
+ * Generates CSS variable declarations from a theme object
33
+ */
34
+ export declare function generateThemeCSS(theme?: DynimTheme): string;
35
+ /**
36
+ * CSS variable references for use in styles
37
+ */
38
+ export declare const themeVars: {
39
+ readonly accent: "var(--dynim-accent)";
40
+ readonly accentHover: "var(--dynim-accent-hover)";
41
+ readonly builderBg: "var(--dynim-builder-bg)";
42
+ readonly builderText: "var(--dynim-builder-text)";
43
+ readonly builderFont: "var(--dynim-builder-font)";
44
+ readonly builderRadius: "var(--dynim-builder-radius)";
45
+ readonly builderOpacity: "var(--dynim-builder-opacity)";
46
+ readonly chatAccent: "var(--dynim-chat-accent)";
47
+ readonly chatBg: "var(--dynim-chat-bg)";
48
+ readonly chatText: "var(--dynim-chat-text)";
49
+ readonly chatFont: "var(--dynim-chat-font)";
50
+ readonly chatRadius: "var(--dynim-chat-radius)";
51
+ readonly chatOpacity: "var(--dynim-chat-opacity)";
52
+ readonly selectionColor: "var(--dynim-selection-color)";
53
+ readonly hoverColor: "var(--dynim-hover-color)";
54
+ readonly selectionRadius: "var(--dynim-selection-radius)";
55
+ readonly selectionOpacity: "var(--dynim-selection-opacity)";
56
+ readonly hoverOpacity: "var(--dynim-hover-opacity)";
57
+ };
2
58
  //# sourceMappingURL=theme.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,UAAU;IAEzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IAGxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,QAAQ,CAAC,UAAU,CAwB7C,CAAC;AA+BF;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,GAAE,UAAe,GAAG,MAAM,CAW/D;AAED;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;CAwBZ,CAAC"}
package/dist/theme.js CHANGED
@@ -1 +1,97 @@
1
- export { generateThemeCSS, defaultTheme, themeVars } from 'dynim-core';
1
+ /**
2
+ * Dynim Theme System
3
+ *
4
+ * Theme is automatically fetched from the API per project.
5
+ * CSS variables are injected with defaults matching the original design.
6
+ */
7
+ /**
8
+ * Default theme values - matches original SDK styling exactly
9
+ */
10
+ export const defaultTheme = {
11
+ // Builder Bar - original values
12
+ accent: '#3b82f6',
13
+ accentHover: '#2563eb',
14
+ builderBg: '#000000',
15
+ builderText: '#ffffff',
16
+ builderFont: 'system-ui, -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, sans-serif',
17
+ builderRadius: '22px',
18
+ builderOpacity: '0.08',
19
+ // Chat Widget - original values
20
+ chatAccent: '#3b82f6',
21
+ chatBg: '#ffffff',
22
+ chatText: '#1f2937',
23
+ chatFont: 'system-ui, -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, sans-serif',
24
+ chatRadius: '16px',
25
+ chatOpacity: '0.08',
26
+ // Selection - original values
27
+ selectionColor: '#8b5cf6',
28
+ hoverColor: '#0ea5e9',
29
+ selectionRadius: '0',
30
+ selectionOpacity: '0.1',
31
+ hoverOpacity: '0.1',
32
+ };
33
+ /**
34
+ * CSS variable names mapped to theme keys
35
+ */
36
+ const cssVarMap = {
37
+ // Builder Bar
38
+ accent: '--dynim-accent',
39
+ accentHover: '--dynim-accent-hover',
40
+ builderBg: '--dynim-builder-bg',
41
+ builderText: '--dynim-builder-text',
42
+ builderFont: '--dynim-builder-font',
43
+ builderRadius: '--dynim-builder-radius',
44
+ builderOpacity: '--dynim-builder-opacity',
45
+ // Chat Widget
46
+ chatAccent: '--dynim-chat-accent',
47
+ chatBg: '--dynim-chat-bg',
48
+ chatText: '--dynim-chat-text',
49
+ chatFont: '--dynim-chat-font',
50
+ chatRadius: '--dynim-chat-radius',
51
+ chatOpacity: '--dynim-chat-opacity',
52
+ // Selection
53
+ selectionColor: '--dynim-selection-color',
54
+ hoverColor: '--dynim-hover-color',
55
+ selectionRadius: '--dynim-selection-radius',
56
+ selectionOpacity: '--dynim-selection-opacity',
57
+ hoverOpacity: '--dynim-hover-opacity',
58
+ };
59
+ /**
60
+ * Generates CSS variable declarations from a theme object
61
+ */
62
+ export function generateThemeCSS(theme = {}) {
63
+ const merged = { ...defaultTheme, ...theme };
64
+ const vars = Object.entries(merged)
65
+ .map(([key, value]) => {
66
+ const cssVar = cssVarMap[key];
67
+ return `${cssVar}: ${value};`;
68
+ })
69
+ .join('\n ');
70
+ return `:root {\n ${vars}\n}`;
71
+ }
72
+ /**
73
+ * CSS variable references for use in styles
74
+ */
75
+ export const themeVars = {
76
+ // Builder Bar
77
+ accent: 'var(--dynim-accent)',
78
+ accentHover: 'var(--dynim-accent-hover)',
79
+ builderBg: 'var(--dynim-builder-bg)',
80
+ builderText: 'var(--dynim-builder-text)',
81
+ builderFont: 'var(--dynim-builder-font)',
82
+ builderRadius: 'var(--dynim-builder-radius)',
83
+ builderOpacity: 'var(--dynim-builder-opacity)',
84
+ // Chat Widget
85
+ chatAccent: 'var(--dynim-chat-accent)',
86
+ chatBg: 'var(--dynim-chat-bg)',
87
+ chatText: 'var(--dynim-chat-text)',
88
+ chatFont: 'var(--dynim-chat-font)',
89
+ chatRadius: 'var(--dynim-chat-radius)',
90
+ chatOpacity: 'var(--dynim-chat-opacity)',
91
+ // Selection
92
+ selectionColor: 'var(--dynim-selection-color)',
93
+ hoverColor: 'var(--dynim-hover-color)',
94
+ selectionRadius: 'var(--dynim-selection-radius)',
95
+ selectionOpacity: 'var(--dynim-selection-opacity)',
96
+ hoverOpacity: 'var(--dynim-hover-opacity)',
97
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dynim-react",
3
- "version": "1.0.59",
3
+ "version": "1.0.63",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -27,7 +27,7 @@
27
27
  "dev": "tsc --watch"
28
28
  },
29
29
  "dependencies": {
30
- "dynim-core": "^1.0.33"
30
+ "dynim-core": "^1.0.37"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "react": ">=17.0.0",