Skip to content

Commit fca3143

Browse files
authoredDec 2, 2024
Merge pull request #1834 from cardstack/cs-7526-update-the-isolated-card-rendering-for-stack-items-in-an
Add last known good rendering of card in error state to stack item
2 parents 7df6ffe + af119a7 commit fca3143

File tree

16 files changed

+576
-96
lines changed

16 files changed

+576
-96
lines changed
 

‎packages/base/command.gts

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export class ShowCardInput extends CardDef {
7272

7373
export class SwitchSubmodeInput extends CardDef {
7474
@field submode = contains(StringField);
75+
@field codePath = contains(StringField);
7576
}
7677

7778
export class CreateModuleInput extends CardDef {

‎packages/host/app/commands/switch-submode.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,15 @@ export default class SwitchSubmodeCommand extends HostBaseCommand<
4444
this.operatorModeStateService.updateCodePath(null);
4545
break;
4646
case Submodes.Code:
47-
this.operatorModeStateService.updateCodePath(
48-
this.lastCardInRightMostStack
49-
? new URL(this.lastCardInRightMostStack.id + '.json')
50-
: null,
51-
);
47+
if (input.codePath) {
48+
this.operatorModeStateService.updateCodePath(new URL(input.codePath));
49+
} else {
50+
this.operatorModeStateService.updateCodePath(
51+
this.lastCardInRightMostStack
52+
? new URL(this.lastCardInRightMostStack.id + '.json')
53+
: null,
54+
);
55+
}
5256
break;
5357
default:
5458
throw new Error(`invalid submode specified: ${input.submode}`);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { fn } from '@ember/helper';
2+
import { on } from '@ember/modifier';
3+
import { service } from '@ember/service';
4+
5+
import Component from '@glimmer/component';
6+
import { tracked } from '@glimmer/tracking';
7+
8+
import TriangleAlert from '@cardstack/boxel-icons/triangle-alert';
9+
10+
import { dropTask } from 'ember-concurrency';
11+
import perform from 'ember-concurrency/helpers/perform';
12+
13+
import { Accordion, Button } from '@cardstack/boxel-ui/components';
14+
15+
import SwitchSubmodeCommand from '../../commands/switch-submode';
16+
import { type CardError } from '../../resources/card-resource';
17+
18+
import type CommandService from '../../services/command-service';
19+
20+
interface Signature {
21+
Args: {
22+
error: CardError['errors'][0];
23+
title?: string;
24+
};
25+
}
26+
27+
export default class CardErrorDetail extends Component<Signature> {
28+
@tracked private showErrorDetail = false;
29+
@service private declare commandService: CommandService;
30+
31+
private toggleDetail = () => (this.showErrorDetail = !this.showErrorDetail);
32+
33+
private viewInCodeMode = dropTask(async () => {
34+
let switchSubmodeCommand = new SwitchSubmodeCommand(
35+
this.commandService.commandContext,
36+
);
37+
const InputType = await switchSubmodeCommand.getInputType();
38+
let input = new InputType({
39+
submode: 'code',
40+
codePath: `${this.args.error.id}.json`,
41+
});
42+
await switchSubmodeCommand.execute(input);
43+
});
44+
45+
<template>
46+
<Accordion as |A|>
47+
<A.Item
48+
data-test-error-detail-toggle
49+
@onClick={{fn this.toggleDetail 'schema'}}
50+
@isOpen={{this.showErrorDetail}}
51+
>
52+
<:title>
53+
<TriangleAlert />
54+
An error was encountered on this card:
55+
<span class='error-detail' data-test-error-title>{{@title}}</span>
56+
</:title>
57+
<:content>
58+
<div class='actions'>
59+
<Button
60+
data-test-view-in-code-mode-button
61+
@kind='primary'
62+
{{on 'click' (perform this.viewInCodeMode)}}
63+
>View in Code Mode</Button>
64+
</div>
65+
<div class='detail'>
66+
<div class='detail-item'>
67+
<div class='detail-title'>Details:</div>
68+
<div
69+
class='detail-contents'
70+
data-test-error-detail
71+
>{{@error.message}}</div>
72+
</div>
73+
{{#if @error.meta.stack}}
74+
<div class='detail-item'>
75+
<div class='detail-title'>Stack trace:</div>
76+
<pre
77+
data-test-error-stack
78+
>
79+
{{@error.meta.stack}}
80+
</pre>
81+
</div>
82+
{{/if}}
83+
</div>
84+
</:content>
85+
</A.Item>
86+
</Accordion>
87+
88+
<style scoped>
89+
.actions {
90+
display: flex;
91+
justify-content: center;
92+
margin-top: var(--boxel-sp-lg);
93+
}
94+
.detail {
95+
padding: var(--boxel-sp);
96+
}
97+
.detail-item {
98+
margin-top: var(--boxel-sp);
99+
}
100+
.detail-title {
101+
font: 600 var(--boxel-font);
102+
}
103+
.detail-contents {
104+
font: var(--boxel-font);
105+
}
106+
pre {
107+
margin-top: 0;
108+
white-space: pre-wrap;
109+
word-break: break-all;
110+
}
111+
</style>
112+
</template>
113+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { TemplateOnlyComponent } from '@ember/component/template-only';
2+
3+
import FileAlert from '@cardstack/boxel-icons/file-alert';
4+
5+
const CardErrorComponent: TemplateOnlyComponent = <template>
6+
<div class='card-error'>
7+
<FileAlert class='icon' />
8+
<div class='message'>This card contains an error.</div>
9+
</div>
10+
11+
<style scoped>
12+
.icon {
13+
height: 100px;
14+
width: 100px;
15+
}
16+
.card-error {
17+
display: flex;
18+
height: 100%;
19+
align-content: center;
20+
justify-content: center;
21+
flex-wrap: wrap;
22+
}
23+
.message {
24+
width: 100%;
25+
text-align: center;
26+
font: 600 var(--boxel-font);
27+
}
28+
</style>
29+
</template>;
30+
31+
export default CardErrorComponent;

‎packages/host/app/components/operator-mode/code-submode.gts

+4-58
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import {
3232
type ResolvedCodeRef,
3333
PermissionsContextName,
3434
} from '@cardstack/runtime-common';
35-
import { SerializedError } from '@cardstack/runtime-common/error';
3635
import { isEquivalentBodyPosition } from '@cardstack/runtime-common/schema-analysis-plugin';
3736

3837
import RecentFiles from '@cardstack/host/components/editor/recent-files';
@@ -311,57 +310,6 @@ export default class CodeSubmode extends Component<Signature> {
311310
return null;
312311
}
313312

314-
private get fileErrorMessages(): string[] {
315-
if (this.isCard) {
316-
if (this.cardResource.cardError) {
317-
try {
318-
let error = this.cardResource.cardError.error;
319-
320-
if (error.responseText) {
321-
let parsedError = JSON.parse(error.responseText);
322-
323-
// handle instance errors
324-
if (parsedError.errors.find((e: any) => e.message)) {
325-
return parsedError.errors.map((e: any) => e.message);
326-
}
327-
328-
// otherwise handle module errors
329-
let allDetails = parsedError.errors
330-
.concat(
331-
...parsedError.errors.map(
332-
(e: SerializedError) => e.additionalErrors,
333-
),
334-
)
335-
.map((e: SerializedError) => e.detail);
336-
337-
// There’s often a pair of errors where one has an unhelpful prefix like this:
338-
// cannot return card from index: Not Found - http://test-realm/test/non-card not found
339-
// http://test-realm/test/non-card not found
340-
341-
let detailsWithoutDuplicateSuffixes = allDetails.reduce(
342-
(details: string[], currentDetail: string) => {
343-
return [
344-
...details.filter(
345-
(existingDetail) => !existingDetail.endsWith(currentDetail),
346-
),
347-
currentDetail,
348-
];
349-
},
350-
[],
351-
);
352-
353-
return detailsWithoutDuplicateSuffixes;
354-
}
355-
} catch (e) {
356-
console.log('Error extracting card preview errors', e);
357-
return [];
358-
}
359-
}
360-
}
361-
362-
return [];
363-
}
364-
365313
private get currentOpenFile() {
366314
return this.operatorModeStateService.openFile.current;
367315
}
@@ -854,12 +802,10 @@ export default class CodeSubmode extends Component<Signature> {
854802

855803
<hr class='preview-error' />
856804

857-
{{#each this.fileErrorMessages as |error|}}
858-
<pre
859-
class='preview-error'
860-
data-test-card-preview-error
861-
>{{error}}</pre>
862-
{{/each}}
805+
<pre
806+
class='preview-error'
807+
data-test-card-preview-error
808+
>{{this.cardResource.cardError.message}}</pre>
863809
</div>
864810
</div>
865811
{{else if this.fileIncompatibilityMessage}}

‎packages/host/app/components/operator-mode/interact-submode.gts

+5-1
Original file line numberDiff line numberDiff line change
@@ -341,9 +341,13 @@ export default class InteractSubmode extends Component<Signature> {
341341
}
342342

343343
private close = task(async (item: StackItem) => {
344-
let { card, request } = item;
345344
// close the item first so user doesn't have to wait for the save to complete
346345
this.operatorModeStateService.trimItemsFromStack(item);
346+
if (item.cardError) {
347+
return;
348+
}
349+
350+
let { card, request } = item;
347351

348352
// only save when closing a stack item in edit mode. there should be no unsaved
349353
// changes in isolated mode because they were saved when user toggled between

0 commit comments

Comments
 (0)