[playground] Persist open tabs on compiler error (#34673)
This change allows it so that tabs that were open before a compiler error are automatically opened again when the error is resolved. Quality of life change for those especially working with the advanced view of the playground. https://github.com/user-attachments/assets/cd2dc117-e6fc-4f57-a08f-259757c4f5e8
Eugene Choi committed
Oct 1, 2025 at 21:26 UTC
f7254efc5c43ebae57afa32a52d4653878ef23d0
3 files changed
+20
-11
compiler/apps/playground/components/AccordionWindow.tsx
+5
-1
@@ -23,6 +23,7 @@ export default function AccordionWindow(props: {
23
tabsOpen: Set<string>;
24
setTabsOpen: (newTab: Set<string>) => void;
25
changedPasses: Set<string>;
26
+ isFailure: boolean;
27
}): React.ReactElement {
28
return (
29
<div className="flex-1 min-w-[550px] sm:min-w-0">
@@ -36,6 +37,7 @@ export default function AccordionWindow(props: {
37
tabsOpen={props.tabsOpen}
38
setTabsOpen={props.setTabsOpen}
39
hasChanged={props.changedPasses.has(name)}
40
+ isFailure={props.isFailure}
41
/>
42
);
43
})}
@@ -50,15 +52,17 @@ function AccordionWindowItem({
52
tabsOpen,
53
setTabsOpen,
54
hasChanged,
55
+ isFailure,
56
}: {
57
name: string;
58
tabs: TabsRecord;
59
tabsOpen: Set<string>;
60
setTabsOpen: (newTab: Set<string>) => void;
61
hasChanged: boolean;
62
+ isFailure: boolean;
63
}): React.ReactElement {
64
const id = useId();
61
- const isShow = tabsOpen.has(name);
65
+ const isShow = isFailure ? name === 'Output' : tabsOpen.has(name);
66
67
const transitionName = `accordion-window-item-${id}`;
68
compiler/apps/playground/components/Editor/Output.tsx
+5
-8
@@ -265,14 +265,8 @@ function OutputContent({store, compilerOutput}: Props): JSX.Element {
265
* Update the active tab back to the output or errors tab when the compilation state
266
* changes between success/failure.
267
*/
268
- const [previousOutputKind, setPreviousOutputKind] = useState(
269
- compilerOutput.kind,
270
- );
271
- if (compilerOutput.kind !== previousOutputKind) {
272
- setPreviousOutputKind(compilerOutput.kind);
273
- setTabsOpen(new Set(['Output']));
274
- setActiveTab('Output');
275
- }
268
+
269
+ const isFailure = compilerOutput.kind !== 'ok';
270
const changedPasses: Set<string> = new Set(['Output', 'HIR']); // Initial and final passes should always be bold
271
let lastResult: string = '';
272
for (const [passName, results] of compilerOutput.results) {
@@ -301,6 +295,8 @@ function OutputContent({store, compilerOutput}: Props): JSX.Element {
295
tabs={tabs}
296
activeTab={activeTab}
297
onTabChange={setActiveTab}
298
+ // Display the Output tab on compilation failure
299
+ activeTabOverride={isFailure ? 'Output' : undefined}
300
/>
301
</ViewTransition>
302
);
@@ -319,6 +315,7 @@ function OutputContent({store, compilerOutput}: Props): JSX.Element {
315
tabsOpen={tabsOpen}
316
tabs={tabs}
317
changedPasses={changedPasses}
318
+ isFailure={isFailure}
319
/>
320
</ViewTransition>
321
);
compiler/apps/playground/components/TabbedWindow.tsx
+10
-2
@@ -17,11 +17,15 @@ export default function TabbedWindow({
17
tabs,
18
activeTab,
19
onTabChange,
20
+ activeTabOverride,
21
}: {
22
tabs: Map<string, React.ReactNode>;
23
activeTab: string;
24
onTabChange: (tab: string) => void;
25
+ activeTabOverride?: string;
26
}): React.ReactElement {
27
+ const currentActiveTab = activeTabOverride ? activeTabOverride : activeTab;
28
+
29
const id = useId();
30
const transitionName = `tab-highlight-${id}`;
31
@@ -37,7 +41,7 @@ export default function TabbedWindow({
41
<div className="flex flex-col h-full max-w-full">
42
<div className="flex p-2 flex-shrink-0">
43
{Array.from(tabs.keys()).map(tab => {
40
- const isActive = activeTab === tab;
44
+ const isActive = currentActiveTab === tab;
45
return (
46
<button
47
key={tab}
@@ -49,6 +53,8 @@ export default function TabbedWindow({
53
{isActive && (
54
<ViewTransition
55
name={transitionName}
56
+ enter={{default: 'none'}}
57
+ exit={{default: 'none'}}
58
share={{
59
[TOGGLE_TAB_TRANSITION]: 'tab-highlight',
60
default: 'none',
@@ -58,6 +64,8 @@ export default function TabbedWindow({
64
</ViewTransition>
65
)}
66
<ViewTransition
67
+ enter={{default: 'none'}}
68
+ exit={{default: 'none'}}
69
update={{
70
[TOGGLE_TAB_TRANSITION]: 'tab-text',
71
default: 'none',
@@ -69,7 +77,7 @@ export default function TabbedWindow({
77
})}
78
</div>
79
<div className="flex-1 overflow-hidden w-full h-full">
72
- {tabs.get(activeTab)}
80
+ {tabs.get(currentActiveTab)}
81
</div>
82
</div>
83
</div>