@genesislcap/blank-app-seed 5.11.0-prerelease.2 → 5.11.0-prerelease.4

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,7 +1,7 @@
1
1
  {
2
2
  "name": "@genesislcap/blank-app-seed-config",
3
3
  "description": "Genesis Blank App Seed Configuration",
4
- "version": "5.11.0-prerelease.2",
4
+ "version": "5.11.0-prerelease.4",
5
5
  "license": "Apache-2.0",
6
6
  "scripts": {
7
7
  "lint": "eslint .",
@@ -1,6 +1,7 @@
1
1
  import React, { useRef } from 'react';
2
2
  import { Layout, Model, TabNode } from 'flexlayout-react';
3
3
  import { getFlexLayoutStorageKey } from '../../utils/layout';
4
+ import { TileErrorBoundary } from '../../components/error-boundary/ErrorBoundary';
4
5
  {{#each route.tiles}}
5
6
  import { {{pascalCase this.componentName}} } from './{{pascalCase this.title}}{{pascalCase this.componentType}}';
6
7
  {{/each}}
@@ -31,7 +32,14 @@ const {{pascalCase route.name}} = () => {
31
32
 
32
33
  const factory = (node: TabNode) => {
33
34
  const Component = componentMap[node.getComponent() ?? ''];
34
- return Component ? <Component /> : null;
35
+ return Component ? (
36
+ <TileErrorBoundary
37
+ title={node.getName() ?? node.getComponent() ?? ''}
38
+ tileRegistration={node.getComponent() ?? ''}
39
+ >
40
+ <Component />
41
+ </TileErrorBoundary>
42
+ ) : null;
35
43
  };
36
44
 
37
45
  const onModelChange = (model: Model) => {
@@ -1,5 +1,5 @@
1
1
  {
2
- "UI": "14.425.0",
3
- "GSF": "8.15.0",
4
- "Auth": "8.15.0"
2
+ "UI": "14.426.0",
3
+ "GSF": "8.15.12",
4
+ "Auth": "8.15.2"
5
5
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.11.0-prerelease.4](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.11.0-prerelease.3...v5.11.0-prerelease.4) (2026-05-04)
4
+
5
+
6
+ ### Features
7
+
8
+ * add default error boundary for components [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 0353f8d
9
+ * add default error boundary for React generated apps (#567) fd39752
10
+
11
+ ## [5.11.0-prerelease.3](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.11.0-prerelease.2...v5.11.0-prerelease.3) (2026-04-29)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * FUI update [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 8a984be
17
+ * GSF update [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 4ffdbe6
18
+ * update GSF versions [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) c771f3f
19
+ * update GSF versions [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) (#564) def3ad3
20
+
3
21
  ## [5.11.0-prerelease.2](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.11.0-prerelease.1...v5.11.0-prerelease.2) (2026-04-28)
4
22
 
5
23
 
@@ -0,0 +1,250 @@
1
+ import React, { useState } from 'react';
2
+ import { errorBoundaryStyles } from './ErrorBoundaryStyles';
3
+
4
+ type ErrorBoundaryScope = 'application' | 'tile';
5
+
6
+ type ErrorBoundaryFallbackProps = {
7
+ scope: ErrorBoundaryScope;
8
+ referenceId: string;
9
+ title: string;
10
+ subtitle: string;
11
+ details: string;
12
+ onRetry: () => void;
13
+ };
14
+
15
+ type BaseErrorBoundaryProps = {
16
+ scope: ErrorBoundaryScope;
17
+ title: string;
18
+ tileRegistration?: string;
19
+ children: React.ReactNode;
20
+ };
21
+
22
+ type BaseErrorBoundaryState = {
23
+ hasError: boolean;
24
+ error: Error | null;
25
+ componentStack: string;
26
+ capturedAt: string;
27
+ referenceId: string;
28
+ };
29
+
30
+ const initialBoundaryState: BaseErrorBoundaryState = {
31
+ hasError: false,
32
+ error: null,
33
+ componentStack: '',
34
+ capturedAt: '',
35
+ referenceId: '',
36
+ };
37
+
38
+ const toError = (value: unknown): Error => {
39
+ if (value instanceof Error) {
40
+ return value;
41
+ }
42
+
43
+ if (typeof value === 'string') {
44
+ return new Error(value);
45
+ }
46
+
47
+ try {
48
+ return new Error(JSON.stringify(value));
49
+ } catch {
50
+ return new Error('Unknown non-serializable error');
51
+ }
52
+ };
53
+
54
+ const createReferenceId = (): string => {
55
+ const timestamp = new Date().toISOString().split(':').join('-');
56
+ const random = Math.random().toString(36).slice(2, 8).toUpperCase();
57
+ return `APP-${timestamp}-${random}`;
58
+ };
59
+
60
+ const ErrorBoundaryFallback: React.FC<ErrorBoundaryFallbackProps> = ({
61
+ scope,
62
+ referenceId,
63
+ title,
64
+ subtitle,
65
+ details,
66
+ onRetry,
67
+ }) => {
68
+ const [copied, setCopied] = useState(false);
69
+ const [copyHelpVisible, setCopyHelpVisible] = useState(false);
70
+
71
+ const handleCopy = async () => {
72
+ try {
73
+ await navigator.clipboard.writeText(details);
74
+ setCopied(true);
75
+ setCopyHelpVisible(false);
76
+ window.setTimeout(() => {
77
+ setCopied(false);
78
+ }, 2_000);
79
+ } catch {
80
+ setCopyHelpVisible(true);
81
+ }
82
+ };
83
+
84
+ return (
85
+ <section className="error-boundary" role="alert">
86
+ <style>{errorBoundaryStyles}</style>
87
+ <div className="error-boundary__top">
88
+ <span className="error-boundary__status">
89
+ {scope === 'application' ? 'Application incident' : 'Tile incident'}
90
+ </span>
91
+ <span className="error-boundary__reference">Ref: {referenceId}</span>
92
+ </div>
93
+ <h2 className="error-boundary__title">{title}</h2>
94
+ <p className="error-boundary__subtitle">{subtitle}</p>
95
+ <p className="error-boundary__hint">
96
+ Tip: copy diagnostics and paste directly into Cursor to speed up debugging.
97
+ </p>
98
+ <textarea
99
+ className="error-boundary__details"
100
+ readOnly
101
+ value={details}
102
+ aria-label="Error diagnostics details"
103
+ />
104
+ <div className="error-boundary__actions">
105
+ <button type="button" className="error-boundary__button" onClick={onRetry}>
106
+ Retry
107
+ </button>
108
+ <button
109
+ type="button"
110
+ className="error-boundary__button error-boundary__button--secondary"
111
+ onClick={handleCopy}
112
+ >
113
+ {copied ? 'Copied' : 'Copy diagnostics'}
114
+ </button>
115
+ </div>
116
+ {copyHelpVisible ? (
117
+ <p className="error-boundary__manual-copy-help">
118
+ Clipboard access is blocked in this browser context. Please select the diagnostics text
119
+ and copy it manually.
120
+ </p>
121
+ ) : null}
122
+ </section>
123
+ );
124
+ };
125
+
126
+ class BaseErrorBoundary extends React.Component<BaseErrorBoundaryProps, BaseErrorBoundaryState> {
127
+ public constructor(props: BaseErrorBoundaryProps) {
128
+ super(props);
129
+ this.state = initialBoundaryState;
130
+ }
131
+
132
+ public static getDerivedStateFromError(error: unknown): Partial<BaseErrorBoundaryState> {
133
+ const normalized = toError(error);
134
+ return {
135
+ hasError: true,
136
+ error: normalized,
137
+ capturedAt: new Date().toISOString(),
138
+ referenceId: createReferenceId(),
139
+ };
140
+ }
141
+
142
+ public componentDidCatch(error: unknown, errorInfo: React.ErrorInfo): void {
143
+ const normalized = toError(error);
144
+ this.setState({
145
+ componentStack: errorInfo.componentStack ?? '',
146
+ });
147
+
148
+ console.error(`[${this.props.scope}] error boundary captured an error`, {
149
+ referenceId: this.state.referenceId,
150
+ title: this.props.title,
151
+ tileRegistration: this.props.tileRegistration,
152
+ error: normalized,
153
+ componentStack: errorInfo.componentStack,
154
+ });
155
+ }
156
+
157
+ private readonly handleRetry = (): void => {
158
+ this.setState(initialBoundaryState);
159
+ };
160
+
161
+ public render(): React.ReactNode {
162
+ if (!this.state.hasError || !this.state.error) {
163
+ return this.props.children;
164
+ }
165
+
166
+ const { scope, title, tileRegistration } = this.props;
167
+ const diagnostics = [
168
+ `Reference: ${this.state.referenceId}`,
169
+ `Scope: ${scope}`,
170
+ `Title: ${title}`,
171
+ `Tile registration: ${tileRegistration ?? 'N/A'}`,
172
+ `Captured at: ${this.state.capturedAt}`,
173
+ `URL: ${window.location.href}`,
174
+ `User agent: ${window.navigator.userAgent}`,
175
+ `Error name: ${this.state.error.name}`,
176
+ `Error message: ${this.state.error.message}`,
177
+ 'Error stack:',
178
+ this.state.error.stack ?? 'No stack available',
179
+ 'Component stack:',
180
+ this.state.componentStack || 'No component stack available',
181
+ ].join('\n');
182
+
183
+ const fallbackTitle =
184
+ scope === 'application'
185
+ ? 'Something went wrong'
186
+ : `Something went wrong in "${title}"`;
187
+ const fallbackSubtitle =
188
+ scope === 'application'
189
+ ? 'The app hit an unexpected error. You can retry or copy diagnostics for a fast fix.'
190
+ : 'This tile crashed, but the rest of the application keeps running. Retry or copy diagnostics.';
191
+
192
+ return (
193
+ <ErrorBoundaryFallback
194
+ scope={scope}
195
+ referenceId={this.state.referenceId}
196
+ title={fallbackTitle}
197
+ subtitle={fallbackSubtitle}
198
+ details={diagnostics}
199
+ onRetry={this.handleRetry}
200
+ />
201
+ );
202
+ }
203
+ }
204
+
205
+ type AppErrorBoundaryProps = {
206
+ children: React.ReactNode;
207
+ };
208
+
209
+ export const AppErrorBoundary: React.FC<AppErrorBoundaryProps> = ({ children }) => {
210
+ return (
211
+ <BaseErrorBoundary scope="application" title="Application">
212
+ {children}
213
+ </BaseErrorBoundary>
214
+ );
215
+ };
216
+
217
+ type TileErrorBoundaryProps = {
218
+ title: string;
219
+ tileRegistration: string;
220
+ children: React.ReactNode;
221
+ };
222
+
223
+ export const TileErrorBoundary: React.FC<TileErrorBoundaryProps> = ({
224
+ title,
225
+ tileRegistration,
226
+ children,
227
+ }) => {
228
+ return (
229
+ <BaseErrorBoundary scope="tile" title={title} tileRegistration={tileRegistration}>
230
+ {children}
231
+ </BaseErrorBoundary>
232
+ );
233
+ };
234
+
235
+ export const withTileErrorBoundary = (
236
+ Component: React.ComponentType,
237
+ title: string,
238
+ tileRegistration: string,
239
+ ): React.FC => {
240
+ const WrappedWithErrorBoundary: React.FC = () => {
241
+ return (
242
+ <TileErrorBoundary title={title} tileRegistration={tileRegistration}>
243
+ <Component />
244
+ </TileErrorBoundary>
245
+ );
246
+ };
247
+
248
+ WrappedWithErrorBoundary.displayName = `WithTileErrorBoundary(${title}:${tileRegistration}:${Component.displayName || Component.name || 'TileComponent'})`;
249
+ return WrappedWithErrorBoundary;
250
+ };
@@ -0,0 +1,161 @@
1
+ export const errorBoundaryStyles = `
2
+ .error-boundary,
3
+ .error-boundary__tile-wrap {
4
+ width: 100%;
5
+ height: 100%;
6
+ }
7
+
8
+ .error-boundary {
9
+ --eb-bg: #f6f8fb;
10
+ --eb-surface: #ffffff;
11
+ --eb-text: #18212f;
12
+ --eb-text-muted: #5f6f84;
13
+ --eb-border: #d3dae6;
14
+ --eb-accent: #2f6fed;
15
+ --eb-accent-hover: #255dcc;
16
+ --eb-accent-foreground: #ffffff;
17
+ --eb-error: #c53d3d;
18
+ --eb-focus: #2f6fed;
19
+ --eb-font: 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
20
+
21
+ box-sizing: border-box;
22
+ display: flex;
23
+ flex-direction: column;
24
+ gap: 12px;
25
+ align-items: flex-start;
26
+ padding: 16px;
27
+ background: var(--eb-bg);
28
+ color: var(--eb-text);
29
+ }
30
+
31
+ .error-boundary__top,
32
+ .error-boundary__title,
33
+ .error-boundary__subtitle,
34
+ .error-boundary__hint,
35
+ .error-boundary__details,
36
+ .error-boundary__actions,
37
+ .error-boundary__manual-copy-help {
38
+ width: 100%;
39
+ }
40
+
41
+ .error-boundary__top {
42
+ display: flex;
43
+ width: 100%;
44
+ align-items: center;
45
+ justify-content: space-between;
46
+ gap: 10px;
47
+ flex-wrap: wrap;
48
+ }
49
+
50
+ .error-boundary__status {
51
+ display: inline-flex;
52
+ align-items: center;
53
+ border: 1px solid var(--eb-error);
54
+ border-radius: 999px;
55
+ padding: 5px 10px;
56
+ font-family: var(--eb-font);
57
+ text-transform: uppercase;
58
+ letter-spacing: 0.13em;
59
+ font-size: 11px;
60
+ font-weight: 700;
61
+ color: var(--eb-error);
62
+ background: #fff4f4;
63
+ }
64
+
65
+ .error-boundary__reference {
66
+ font-family: 'Menlo', 'Consolas', 'Liberation Mono', monospace;
67
+ font-size: 11px;
68
+ letter-spacing: 0.08em;
69
+ color: var(--eb-text-muted);
70
+ text-transform: uppercase;
71
+ }
72
+
73
+ .error-boundary__title {
74
+ margin: 0;
75
+ font-family: var(--eb-font);
76
+ font-size: clamp(22px, 3vw, 30px);
77
+ line-height: 1.15;
78
+ font-weight: 700;
79
+ color: var(--eb-text);
80
+ text-wrap: balance;
81
+ }
82
+
83
+ .error-boundary__subtitle {
84
+ margin: 0;
85
+ max-width: 80ch;
86
+ font-family: var(--eb-font);
87
+ color: var(--eb-text);
88
+ line-height: 1.45;
89
+ }
90
+
91
+ .error-boundary__hint {
92
+ margin: -2px 0 2px;
93
+ font-family: var(--eb-font);
94
+ color: var(--eb-text-muted);
95
+ font-size: 13px;
96
+ }
97
+
98
+ .error-boundary__details {
99
+ box-sizing: border-box;
100
+ width: 100%;
101
+ min-height: 190px;
102
+ max-height: 360px;
103
+ padding: 12px;
104
+ border: 1px solid var(--eb-border);
105
+ border-radius: 10px;
106
+ background: var(--eb-surface);
107
+ color: var(--eb-text);
108
+ font-family: 'Menlo', 'Consolas', 'Liberation Mono', 'Courier New', monospace;
109
+ font-size: 12px;
110
+ line-height: 1.5;
111
+ resize: vertical;
112
+ }
113
+
114
+ .error-boundary__actions {
115
+ display: flex;
116
+ gap: 10px;
117
+ flex-wrap: wrap;
118
+ }
119
+
120
+ .error-boundary__button {
121
+ border: 1px solid var(--eb-accent);
122
+ border-radius: 999px;
123
+ background: var(--eb-accent);
124
+ color: var(--eb-accent-foreground);
125
+ cursor: pointer;
126
+ padding: 9px 16px;
127
+ font-family: var(--eb-font);
128
+ font-size: 12px;
129
+ font-weight: 700;
130
+ letter-spacing: 0.04em;
131
+ text-transform: uppercase;
132
+ transition: transform 180ms ease, background-color 180ms ease;
133
+ }
134
+
135
+ .error-boundary__button--secondary {
136
+ border-color: var(--eb-border);
137
+ background: var(--eb-surface);
138
+ color: var(--eb-text);
139
+ }
140
+
141
+ .error-boundary__button:hover {
142
+ background: var(--eb-accent-hover);
143
+ transform: translateY(-1px);
144
+ }
145
+
146
+ .error-boundary__button--secondary:hover {
147
+ background: #eef2f8;
148
+ }
149
+
150
+ .error-boundary__button:focus-visible {
151
+ outline: 2px solid var(--eb-focus);
152
+ outline-offset: 2px;
153
+ }
154
+
155
+ .error-boundary__manual-copy-help {
156
+ margin: 2px 0 0;
157
+ font-size: 13px;
158
+ color: var(--eb-text-muted);
159
+ line-height: 1.4;
160
+ }
161
+ `;
@@ -1,6 +1,7 @@
1
1
  import React from 'react'
2
2
  import ReactDOM from 'react-dom/client'
3
3
  import App from './App.tsx'
4
+ import { AppErrorBoundary } from './components/error-boundary/ErrorBoundary'
4
5
 
5
6
  import { registerPBCs } from './pbc/utils';
6
7
  import { createLogger } from '@genesislcap/foundation-logger';
@@ -16,7 +17,9 @@ function bootstrapApp() {
16
17
  if (rootEelement) {
17
18
  ReactDOM.createRoot(rootEelement!).render(
18
19
  <React.StrictMode>
19
- <App rootElement={rootEelement} />
20
+ <AppErrorBoundary>
21
+ <App rootElement={rootEelement} />
22
+ </AppErrorBoundary>
20
23
  </React.StrictMode>,
21
24
  )
22
25
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@genesislcap/blank-app-seed",
3
3
  "description": "Genesis Blank App Seed",
4
- "version": "5.11.0-prerelease.2",
4
+ "version": "5.11.0-prerelease.4",
5
5
  "license": "Apache-2.0",
6
6
  "scripts": {
7
7
  "release": "semantic-release"