@snowcone-app/ui 0.3.0 → 0.4.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowcone-app/ui",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "React components for merchandise visualization and customization",
5
5
  "keywords": [
6
6
  "react",
@@ -103,7 +103,7 @@
103
103
  "react-instantsearch": "^7.15.5",
104
104
  "react-zoom-pan-pinch": "^3.6.4",
105
105
  "tailwind-merge": "^3.0.0",
106
- "@snowcone-app/sdk": "0.16.0"
106
+ "@snowcone-app/sdk": "0.17.0"
107
107
  },
108
108
  "devDependencies": {
109
109
  "@chromatic-com/storybook": "^4.1.2",
@@ -1044,6 +1044,20 @@ export const HeroProductImage = memo(function HeroProductImage({
1044
1044
  />
1045
1045
  )}
1046
1046
 
1047
+ {/* Server render error badge — the realtime renderer rejected the last
1048
+ render (e.g. asset_not_allowed). The stale image above stays visible
1049
+ on purpose (stale + clearly marked beats blank); the typed code is
1050
+ exposed via data-render-error so agents/tests can assert on it. */}
1051
+ {realtimeContext?.renderError && (
1052
+ <div
1053
+ role="alert"
1054
+ data-render-error={realtimeContext.renderError.code}
1055
+ className="absolute inset-x-0 bottom-0 z-20 bg-red-600/90 text-white text-xs font-medium px-3 py-2 pointer-events-none"
1056
+ >
1057
+ {`Render blocked: ${realtimeContext.renderError.code} — ${realtimeContext.renderError.message}`}
1058
+ </div>
1059
+ )}
1060
+
1047
1061
  {/* Previous image — fades out during crossfade */}
1048
1062
  {prevUrl && (
1049
1063
  <img
@@ -38,6 +38,7 @@ import React, {
38
38
  } from "react";
39
39
  import { useRealtimeMockup } from "@snowcone-app/sdk/react";
40
40
  import { resolveVariantId as resolveVariantIdUtil } from "@snowcone-app/sdk";
41
+ import type { RealtimeRenderError } from "@snowcone-app/sdk";
41
42
  import { readEnv } from "../lib/env";
42
43
  import { useProductOptional } from "./Product";
43
44
  import type { ReactProductContext } from "./Product";
@@ -69,6 +70,15 @@ export interface RealtimeContextValue {
69
70
  // Mockup results (use subscriptions for performance-critical components)
70
71
  mockupResults: MockupResult[];
71
72
 
73
+ /**
74
+ * Most recent server render error (stable machine-readable `code` +
75
+ * human message, e.g. `{code: 'asset_not_allowed', ...}`). Cleared when a
76
+ * later render succeeds. While set, `mockupResults` still holds the LAST
77
+ * GOOD renders — surface the error visibly (badge the stale mockup) rather
78
+ * than blanking it or logging console-only.
79
+ */
80
+ renderError: RealtimeRenderError | null;
81
+
72
82
  // Debug/tracking state (now refs, won't trigger re-renders)
73
83
  isPendingMockups: boolean;
74
84
  canvasBlobsSent: number;
@@ -328,8 +338,10 @@ export function RealtimeProvider({
328
338
  },
329
339
  onAllMockupsRendered: (results) => {
330
340
  },
331
- onError: (error) => {
332
- console.error("[RealtimeProvider] Error:", error);
341
+ onError: (error, detail) => {
342
+ // Logged for traces; the actionable surface is `renderError` on the
343
+ // context (and the hook state), which consumers must render visibly.
344
+ console.error(`[RealtimeProvider] Render error [${detail.code}]:`, error);
333
345
  },
334
346
  });
335
347
 
@@ -341,6 +353,7 @@ export function RealtimeProvider({
341
353
  const {
342
354
  isConnected,
343
355
  isConfigured,
356
+ renderError,
344
357
  mockupResults: rawMockupResults,
345
358
  sendCanvasBlob: sendCanvasBlobRaw,
346
359
  sendCanvasState: sendCanvasStateRaw,
@@ -1067,6 +1080,7 @@ export function RealtimeProvider({
1067
1080
  isEnabled,
1068
1081
  isConnected,
1069
1082
  isConfigured,
1083
+ renderError,
1070
1084
  // Read from ref - this is a snapshot at render time, NOT reactive
1071
1085
  // For reactive updates, use getMockupResultsImmediate() or subscribe functions
1072
1086
  get mockupResults() {
@@ -1110,6 +1124,7 @@ export function RealtimeProvider({
1110
1124
  isEnabled,
1111
1125
  isConnected,
1112
1126
  isConfigured,
1127
+ renderError,
1113
1128
  // mockupResults removed from deps - it's a getter that reads from ref
1114
1129
  canvasExportSize,
1115
1130
  mockupWidth,