@genesislcap/blank-app-seed 5.11.0 → 5.12.0-prerelease.1

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",
4
+ "version": "5.12.0-prerelease.1",
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) => {
package/CHANGELOG.md CHANGED
@@ -1,28 +1,25 @@
1
1
  # Changelog
2
2
 
3
- ## [5.11.0](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.10.3...v5.11.0) (2026-04-29)
3
+ ## [5.12.0-prerelease.1](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.11.0...v5.12.0-prerelease.1) (2026-05-05)
4
4
 
5
5
 
6
6
  ### Features
7
7
 
8
- * flexlayout + react wrappers + GSF/FUI update [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) (#566) ed36d1f
9
- * replace foundation-layout with flex-layout in React [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) (#561) 13a521f
10
- * replace foundation-layout with flex-layout in React templates [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 9c13246
11
- * update FUI version [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 13373e5
12
- * update FUI version [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 11096f0
13
- * update FUI version [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) (#562) 9aa31ba
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
14
10
 
15
11
 
16
12
  ### Bug Fixes
17
13
 
18
- * backport main [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) (#560) f956a18
19
- * FUI update [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 8a984be
20
- * GSF update [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 4ffdbe6
21
- * hide close button in layout items [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 58dd5ec
22
- * update GSF versions [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) c771f3f
23
- * update GSF versions [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) (#564) def3ad3
24
- * update slack notifications [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 19dd19a
25
- * update slack notifications [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) (#563) 8f51a0b
14
+ * backport main [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) (#568) 2ef601d
15
+
16
+ ## [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)
17
+
18
+
19
+ ### Features
20
+
21
+ * add default error boundary for components [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 0353f8d
22
+ * add default error boundary for React generated apps (#567) fd39752
26
23
 
27
24
  ## [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)
28
25
 
@@ -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",
4
+ "version": "5.12.0-prerelease.1",
5
5
  "license": "Apache-2.0",
6
6
  "scripts": {
7
7
  "release": "semantic-release"