@samitouri / QOS-React / commits / f7254efc5c

[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>