Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a12b4f1
First draft
megothss Feb 28, 2026
4d4201f
[BUGFIX] Fix ErrorBoundary runtime: update-phase error handling and i…
megothss Mar 1, 2026
1910faf
[BUGFIX] Fix ErrorBoundary rerender recovery and expand test coverage
megothss Mar 4, 2026
8aca290
[PERF] Remove try-catch from ErrorBoundary hot path and clear stale D…
megothss Mar 4, 2026
e4340ad
Restore accidentally removed ember-template-compiler mapping in packa…
megothss Mar 4, 2026
233fbb0
[BUGFIX] Prevent tracking state corruption during error recovery
megothss Mar 4, 2026
fab9837
Fix CI type checking, linting, and formatting errors
megothss Mar 4, 2026
cf26016
Add DEBUG console.error logging when ErrorBoundary catches errors
megothss Mar 5, 2026
85a03c1
[BUGFIX] Remove ErrorBoundary re-export from @glimmer/component
megothss Mar 5, 2026
7581045
Skip DEBUG-only console.error tests in production builds
megothss Mar 5, 2026
62cddba
[BUGFIX] Isolate modifier install errors in transaction commit
megothss Mar 5, 2026
812e12c
Add @retryWith support to ErrorBoundary for automatic error recovery
megothss Mar 6, 2026
3ddf1a1
Fix CI: prettier formatting, eslint throw error, and TS decorator pla…
megothss Mar 6, 2026
3eb6014
Fix CI: always run runDestroy in ErrorBoundary test afterEach
megothss Mar 6, 2026
9029fc3
[BUGFIX] Fix sub-VM cursor position causing inverted bounds in ErrorB…
megothss Mar 9, 2026
1179634
[PERF] Skip try-catch in UpdatingVM when no ErrorBoundary is active
megothss Mar 9, 2026
76624f7
Clean up ErrorBoundary: extract helpers, improve naming, remove dead …
megothss Mar 9, 2026
8a3fc47
[BUGFIX] Fix ErrorBoundary cleanup of {{#in-element}} remote DOM
megothss Mar 10, 2026
49c179a
[BUGFIX] Fix test regressions caused by dropBlocks in executeGuarded
megothss Mar 10, 2026
f752c7e
Fix CI: revert insertItem to executeGuarded, fix eslint and prettier
megothss Mar 10, 2026
8896fab
Merge remote-tracking branch 'upstream/main' into error-boundaries-7.4
megothss Sep 9, 2026
6d5d7dd
Migrate ErrorBoundary tests off the removed defComponent helper
megothss Sep 9, 2026
fcd2927
Merge remote-tracking branch 'upstream/main' into error-boundaries
megothss Oct 6, 2026
7ffe166
Retry an ErrorBoundary when state its failed render read changes
megothss Oct 6, 2026
5878737
Name ErrorBoundary blocks `<:try>` and `<:catch>`
megothss Oct 6, 2026
fbe0917
Harden ErrorBoundary aborts and fallback errors
megothss Oct 7, 2026
18d9b14
Clean up a failed ErrorBoundary inside an inserted row
megothss Oct 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/@ember/-internals/glimmer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
export { default as template, templateCacheCounters } from '@glimmer/opcode-compiler/lib/template';

export { default as RootTemplate } from './lib/templates/root';
export { default as ErrorBoundary } from './lib/components/error-boundary';
export { default as Input } from './lib/components/input';
export { default as LinkTo } from './lib/components/link-to';
export { default as Textarea } from './lib/components/textarea';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,7 @@ const CURLY_CAPABILITIES: InternalComponentCapabilities = {
wrapped: true,
willDestroy: true,
hasSubOwner: false,
errorBoundary: false,
};

export const CURLY_COMPONENT_MANAGER = /*@__PURE__*/ new CurlyComponentManager();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const CAPABILITIES = {
wrapped: false,
willDestroy: false,
hasSubOwner: true,
errorBoundary: false,
};

class MountManager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ const ROOT_CAPABILITIES: InternalComponentCapabilities = {
wrapped: true,
willDestroy: false,
hasSubOwner: false,
errorBoundary: false,
};

export class RootComponentDefinition implements ComponentDefinition {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const CAPABILITIES: InternalComponentCapabilities = {
wrapped: false,
willDestroy: false,
hasSubOwner: false,
errorBoundary: false,
};

class RouteTemplateManager
Expand Down
103 changes: 103 additions & 0 deletions packages/@ember/-internals/glimmer/lib/components/error-boundary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import type {
Destroyable,
DynamicScope,
Environment,
InternalComponentCapabilities,
InternalComponentManager,
Nullable,
Owner,
VMArguments,
WithCreateInstance,
} from '@glimmer/interfaces';
import type { Reference } from '@glimmer/reference/lib/reference';
import type { VMArgumentsImpl } from '@glimmer/runtime/lib/vm/arguments';
import { DEBUG } from '@glimmer/env';
import { assert } from '@ember/debug';
import { setComponentTemplate } from '@glimmer/manager/lib/public/template';
import { setInternalComponentManager } from '@glimmer/manager/lib/internal/api';
import { createConstRef } from '@glimmer/reference/lib/reference';
import { ErrorBoundaryStateImpl } from '@glimmer/runtime/lib/component/error-boundary';

import type { ErrorBoundaryState } from '@glimmer/runtime/lib/component/error-boundary';

import ErrorBoundaryTemplate from '../templates/error-boundary';

const CAPABILITIES: InternalComponentCapabilities = {
dynamicLayout: false,
dynamicTag: false,
prepareArgs: false,
// Only needed for the DEBUG check of which blocks were passed.
createArgs: true,
attributeHook: false,
elementHook: false,
createCaller: false,
dynamicScope: false,
updateHook: false,
createInstance: true,
wrapped: false,
willDestroy: false,
hasSubOwner: false,
errorBoundary: true,
};

class ErrorBoundaryManager
implements InternalComponentManager<ErrorBoundaryState>, WithCreateInstance<ErrorBoundaryState>
{
getCapabilities(): InternalComponentCapabilities {
return CAPABILITIES;
}

create(
_owner: Owner,
_definition: object,
args: Nullable<VMArguments>,
_env: Environment,
_dynamicScope: Nullable<DynamicScope>,
_caller: Nullable<Reference>,
hasDefaultBlock: boolean
): ErrorBoundaryState {
if (DEBUG) {
// Checked here rather than in the layout: the layout renders inside the
// boundary, which would catch the assertion and show the <:catch> block.
// Implicit content can't be mixed with named blocks, so a default block
// next to <:try> or <:catch> is always an explicit <:default>.
let blocks = (args as VMArgumentsImpl | null)?.blocks;
let hasNamedBlock = blocks !== undefined && (blocks.has('try') || blocks.has('catch'));
assert(
'<ErrorBoundary> accepts a <:default> block only on its own. Use <:try> with <:catch>.',
!(hasDefaultBlock && hasNamedBlock)
);
}

return new ErrorBoundaryStateImpl();
}

didCreate(): void {}
didUpdate(): void {}
didRenderLayout(): void {}
didUpdateLayout(): void {}

getDebugName(): string {
return 'ErrorBoundary';
}

getSelf(instance: ErrorBoundaryState): Reference {
return createConstRef(instance, 'this');
}

getDestroyable(_instance: ErrorBoundaryState): Nullable<Destroyable> {
return null;
}
}

const MANAGER = new ErrorBoundaryManager();

const ErrorBoundary = {
create() {
throw new Error('ErrorBoundary does not support .create(). It is managed internally.');
},
};
setInternalComponentManager(MANAGER, ErrorBoundary);
setComponentTemplate(ErrorBoundaryTemplate, ErrorBoundary);

export default ErrorBoundary;
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ const CAPABILITIES: InternalComponentCapabilities = {
wrapped: false,
willDestroy: false,
hasSubOwner: false,
errorBoundary: false,
};

class InternalManager
Expand Down
2 changes: 2 additions & 0 deletions packages/@ember/-internals/glimmer/lib/setup-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type Registry from '@ember/-internals/container/lib/registry';
import { privatize as P } from '@ember/-internals/container/lib/registry';
import { getOwner } from '@ember/-internals/owner';
import { assert } from '@ember/debug';
import ErrorBoundary from './components/error-boundary';
import Input from './components/input';
import LinkTo from './components/link-to';
import Textarea from './components/textarea';
Expand Down Expand Up @@ -42,6 +43,7 @@ export function setupEngineRegistry(registry: Registry): void {

registry.optionsForType('helper', { instantiate: false });

registry.register('component:error-boundary', ErrorBoundary);
registry.register('component:input', Input);

registry.register('component:link-to', LinkTo);
Expand Down
11 changes: 11 additions & 0 deletions packages/@ember/-internals/glimmer/lib/templates/error-boundary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { precompileTemplate } from '@ember/template-compilation';
export default precompileTemplate(
`{{#if this.hasError}}{{yield this.error this.retry to="catch"}}{{else if (has-block "try")}}{{yield to="try"}}{{else}}{{yield}}{{/if}}`,
{
moduleName: 'packages/@ember/-internals/glimmer/lib/templates/error-boundary.hbs',
strictMode: true,
scope() {
return {};
},
}
);
Loading
Loading