From 61b7e1cf3c36c031cdfb990b2b7652fcf0296463 Mon Sep 17 00:00:00 2001
From: Alex TYRODE <alex@tyrode.dev>
Date: Wed, 28 May 2025 14:59:28 +0000
Subject: [PATCH 1/8] refactor: clean up useless comments

---
 src/frontend/src/pad/editors/HtmlEditor.tsx | 2 +-
 src/frontend/src/ui/MainMenu.tsx            | 8 ++++----
 src/frontend/src/ui/TabContextMenu.tsx      | 4 ++--
 src/frontend/src/ui/Tabs.tsx                | 4 ++--
 4 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/frontend/src/pad/editors/HtmlEditor.tsx b/src/frontend/src/pad/editors/HtmlEditor.tsx
index a4b82f4..78a368d 100644
--- a/src/frontend/src/pad/editors/HtmlEditor.tsx
+++ b/src/frontend/src/pad/editors/HtmlEditor.tsx
@@ -33,7 +33,7 @@ export const useHtmlEditor = (
   element: NonDeleted<ExcalidrawEmbeddableElement> | undefined,
   editorRef: React.RefObject<any>,
   excalidrawAPI?: any,
-  isActive: boolean = true // New parameter to control if the hook is active
+  isActive: boolean = true
 ) => {
   // Always initialize these hooks regardless of isActive
   const [createNew, setCreateNew] = useState(false);
diff --git a/src/frontend/src/ui/MainMenu.tsx b/src/frontend/src/ui/MainMenu.tsx
index 10c8066..d416435 100644
--- a/src/frontend/src/ui/MainMenu.tsx
+++ b/src/frontend/src/ui/MainMenu.tsx
@@ -7,7 +7,7 @@ import { LogOut, SquarePlus, LayoutDashboard, User, Text, Settings, Terminal, Fi
 import md5 from 'crypto-js/md5';
 
 // Components
-import SettingsDialog from './SettingsDialog'; // Added import
+import SettingsDialog from './SettingsDialog';
 
 import { useLogout } from '../hooks/useLogout';
 import { useAuthStatus } from '../hooks/useAuthStatus';
@@ -135,7 +135,7 @@ export const MainMenuConfig: React.FC<MainMenuConfigProps> = ({
     setShowSettingsModal(true);
   };
 
-  const handleCloseSettingsModal = () => { // Added handler to close settings modal
+  const handleCloseSettingsModal = () => {
     setShowSettingsModal(false);
   };
   
@@ -151,7 +151,7 @@ export const MainMenuConfig: React.FC<MainMenuConfigProps> = ({
         const keycloakLogoutUrl = data.logout_url;
 
         const createIframeLoader = (url: string, debugName: string): Promise<void> => {
-          return new Promise<void>((resolve, reject) => { // Added reject for error handling
+          return new Promise<void>((resolve, reject) => {
             const iframe = document.createElement("iframe");
             iframe.style.display = "none";
             iframe.src = url;
@@ -204,7 +204,7 @@ export const MainMenuConfig: React.FC<MainMenuConfigProps> = ({
           onClose={() => setShowAccountModal(false)} 
         />
       )}
-      {showSettingsModal && ( // Added conditional rendering for SettingsDialog
+      {showSettingsModal && (
         <SettingsDialog
           excalidrawAPI={excalidrawAPI}
           onClose={handleCloseSettingsModal}
diff --git a/src/frontend/src/ui/TabContextMenu.tsx b/src/frontend/src/ui/TabContextMenu.tsx
index 2ee5478..bdb2f1e 100644
--- a/src/frontend/src/ui/TabContextMenu.tsx
+++ b/src/frontend/src/ui/TabContextMenu.tsx
@@ -219,7 +219,7 @@ class TabActionManager implements ActionManager {
     onRename: (padId: string, newName: string) => void,
     onDelete: (padId: string) => void, // This is for deleteOwnedPad
     onUpdateSharingPolicy: (padId: string, policy: string) => void,
-    onLeaveSharedPad: (padId: string) => void, // Moved before optional param
+    onLeaveSharedPad: (padId: string) => void,
     sharingPolicy?: string
   ) {
     this.padId = padId;
@@ -244,7 +244,7 @@ class TabActionManager implements ActionManager {
         console.debug('[pad.ws] User confirmed delete, calling onDelete');
         this.onDelete(this.padId); // Calls original onDelete for owned pads
       }
-    } else if (action.name === 'leaveSharedPad') { // New action for leaving
+    } else if (action.name === 'leaveSharedPad') {
       console.debug('[pad.ws] Attempting to leave shared pad:', this.padId, this.padName);
       if (window.confirm(`Are you sure you want to leave "${this.padName}"? This will remove it from your list of open pads.`)) {
         this.onLeaveSharedPad(this.padId); // Calls the new handler
diff --git a/src/frontend/src/ui/Tabs.tsx b/src/frontend/src/ui/Tabs.tsx
index d7cdcbc..02af0ce 100644
--- a/src/frontend/src/ui/Tabs.tsx
+++ b/src/frontend/src/ui/Tabs.tsx
@@ -20,7 +20,7 @@ interface TabsProps {
     createNewPadAsync: () => Promise<Tab | null | undefined>;
     renamePad: (args: { padId: string; newName: string }) => void;
     deletePad: (padId: string) => void;
-    leaveSharedPad: (padId: string) => void; // Added prop
+    leaveSharedPad: (padId: string) => void;
     updateSharingPolicy: (args: { padId: string; policy: string }) => void;
     selectTab: (tabId: string) => void;
 }
@@ -470,7 +470,7 @@ const Tabs: React.FC<TabsProps> = ({
                         }
                         deletePad(padId); // Calls the prop for deleting owned pad
                     }}
-                    onLeaveSharedPad={(padId: string) => { // New prop for 'leaveSharedPad'
+                    onLeaveSharedPad={(padId: string) => {
                         leaveSharedPad(padId); // Calls the prop for leaving shared pad
                     }}
                     onUpdateSharingPolicy={(padId: string, policy: string) => {

From 02cfd9123571a7e48e178babda6bdeaca4fc31a5 Mon Sep 17 00:00:00 2001
From: Alex TYRODE <alex@tyrode.dev>
Date: Wed, 28 May 2025 15:00:48 +0000
Subject: [PATCH 2/8] refactor: remove DevTools component and related styles

- Deleted the DevTools component and its associated SCSS file to streamline the codebase.
- Updated the CustomEmbeddableRenderer to remove references to DevTools, enhancing clarity and maintainability.
- Removed DevTools entry from the pad index file and MainMenu, simplifying the user interface.
---
 src/frontend/src/CustomEmbeddableRenderer.tsx |  7 +-
 src/frontend/src/pad/DevTools.scss            | 86 -------------------
 src/frontend/src/pad/DevTools.tsx             | 47 ----------
 src/frontend/src/pad/index.ts                 |  2 -
 src/frontend/src/ui/MainMenu.tsx              | 22 -----
 5 files changed, 1 insertion(+), 163 deletions(-)
 delete mode 100644 src/frontend/src/pad/DevTools.scss
 delete mode 100644 src/frontend/src/pad/DevTools.tsx

diff --git a/src/frontend/src/CustomEmbeddableRenderer.tsx b/src/frontend/src/CustomEmbeddableRenderer.tsx
index 4023add..4eba265 100644
--- a/src/frontend/src/CustomEmbeddableRenderer.tsx
+++ b/src/frontend/src/CustomEmbeddableRenderer.tsx
@@ -9,7 +9,6 @@ import {
   ControlButton,
   Editor,
   Terminal,
-  DevTools,
 } from './pad';
 import { ActionButton } from './pad/buttons';
 import "./CustomEmbeddableRenderer.scss";
@@ -36,7 +35,7 @@ export const renderCustomEmbeddable = (
         title = "HTML Editor";
         break;
       case 'editor':
-        content = <Editor element={element} appState={appState} excalidrawAPI={excalidrawAPI} />;
+        content = <Editor element={element} excalidrawAPI={excalidrawAPI} />;
         title = "Code Editor";
         break;
       case 'terminal':
@@ -64,10 +63,6 @@ export const renderCustomEmbeddable = (
         content = <Dashboard element={element} appState={appState} excalidrawAPI={excalidrawAPI} />;
         title = "Dashboard";
         break;
-      case 'dev':
-        content = <DevTools element={element} appState={appState} excalidrawAPI={excalidrawAPI} />;
-        title = "Dev Tools";
-        break;
       default:
         title = "Untitled";
         return null;
diff --git a/src/frontend/src/pad/DevTools.scss b/src/frontend/src/pad/DevTools.scss
deleted file mode 100644
index faaa2f7..0000000
--- a/src/frontend/src/pad/DevTools.scss
+++ /dev/null
@@ -1,86 +0,0 @@
-.dev-tools {
-    display: flex;
-    flex-direction: column;
-    height: 100%;
-    width: 100%;
-    overflow: hidden;
-    
-    &__content {
-      flex: 1;
-      overflow: hidden;
-      position: relative;
-    }
-    
-    &__collab-container {
-      display: flex;
-      height: 100%;
-      width: 100%;
-      gap: 8px;
-    }
-  
-    &__collab-events-wrapper {
-      display: flex;
-      flex-direction: column;
-      flex: 1;
-      gap: 8px;
-      overflow: hidden;
-    }
-    
-    &__collab-events {
-      border: 1px solid #333;
-      border-radius: 4px;
-      display: flex;
-      flex-direction: column;
-      background-color: #1e1e1e;
-      overflow: hidden;
-  
-      .dev-tools__collab-events-wrapper > & {
-        width: auto;
-        flex-basis: 0;
-        flex-grow: 1;
-        flex-shrink: 1;
-        min-width: 0;
-      }
-    }
-    
-    &__collab-events-header {
-      padding: 6px 10px;
-      background-color: #2d2d2d;
-      border-bottom: 1px solid #444;
-      font-size: 12px;
-      font-weight: 500;
-      color: #e0e0e0;
-    }
-    
-    &__collab-events-list {
-      flex: 1;
-      overflow-y: auto;
-      padding: 4px;
-    }
-    
-    &__collab-empty {
-      padding: 12px;
-      color: #a0a0a0;
-      font-size: 12px;
-      text-align: center;
-      font-style: italic;
-    }
-    
-    &__collab-details {
-      flex: 1;
-      display: flex;
-      flex-direction: column;
-      border: 1px solid #333;
-      border-radius: 4px;
-      overflow: hidden;
-    }
-    
-    &__editor-header {
-      padding: 6px 10px;
-      background-color: #2d2d2d;
-      border-bottom: 1px solid #444;
-      font-size: 12px;
-      font-weight: 500;
-      color: #e0e0e0;
-    }
-}
diff --git a/src/frontend/src/pad/DevTools.tsx b/src/frontend/src/pad/DevTools.tsx
deleted file mode 100644
index d0080b6..0000000
--- a/src/frontend/src/pad/DevTools.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import React from 'react';
-import MonacoEditor from '@monaco-editor/react';
-import './DevTools.scss';
-
-interface DevToolsProps {}
-
-const DevTools: React.FC<DevToolsProps> = () => {
-  return (
-    <div className="dev-tools">
-      <div className="dev-tools__content">
-        <div className="dev-tools__collab-container">
-          <div className="dev-tools__collab-events-wrapper">
-              <div className="dev-tools__collab-events">
-                <div className="dev-tools__collab-events-header">
-                  Events
-                </div>
-                <div className="dev-tools__collab-events-list">
-                  <div className="dev-tools__collab-empty">
-                    Event display area.
-                  </div>
-                </div>
-              </div>
-            </div>
-            <div className="dev-tools__collab-details">
-              <div className="dev-tools__editor-header">Event Details</div>
-              <MonacoEditor
-                height="100%"
-                language="json"
-                theme="vs-dark"
-                value={JSON.stringify({ message: "JSON representation of event data will appear here." }, null, 2)}
-                options={{
-                  readOnly: true,
-                  minimap: { enabled: false },
-                  scrollBeyondLastLine: false,
-                  fontSize: 12,
-                  automaticLayout: true,
-                  wordWrap: 'on'
-                }}
-              />
-            </div>
-          </div>
-      </div>
-    </div>
-  );
-};
-
-export default DevTools;
diff --git a/src/frontend/src/pad/index.ts b/src/frontend/src/pad/index.ts
index 0c2bb78..4da81bb 100644
--- a/src/frontend/src/pad/index.ts
+++ b/src/frontend/src/pad/index.ts
@@ -5,11 +5,9 @@ export * from './Dashboard';
 export * from './Terminal';
 export * from './buttons';
 export * from './editors';
-export * from './DevTools';
 
 // Default exports
 export { default as ControlButton } from './buttons/ControlButton';
 export { default as StateIndicator } from './StateIndicator';
 export { default as Dashboard } from './Dashboard';
 export { default as Terminal } from './Terminal';
-export { default as DevTools } from './DevTools';
\ No newline at end of file
diff --git a/src/frontend/src/ui/MainMenu.tsx b/src/frontend/src/ui/MainMenu.tsx
index d416435..0470edd 100644
--- a/src/frontend/src/ui/MainMenu.tsx
+++ b/src/frontend/src/ui/MainMenu.tsx
@@ -67,22 +67,6 @@ export const MainMenuConfig: React.FC<MainMenuConfigProps> = ({
     });
   };
 
-  const handleDevToolsClick = () => {
-    if (!excalidrawAPI) return;
-    
-    const devToolsElement = ExcalidrawElementFactory.createEmbeddableElement({
-      link: "!dev",
-      width: 800,
-      height: 500
-    });
-    
-    ExcalidrawElementFactory.placeInScene(devToolsElement, excalidrawAPI, {
-      mode: PlacementMode.NEAR_VIEWPORT_CENTER,
-      bufferPercentage: 10,
-      scrollToView: true
-    });
-  };
-
   const handleInsertButtonClick = () => {
     if (!excalidrawAPI) return;
     
@@ -262,12 +246,6 @@ export const MainMenuConfig: React.FC<MainMenuConfigProps> = ({
         >
           Action Button
         </MainMenu.Item>
-        <MainMenu.Item
-          icon={<FlaskConical />}
-          onClick={handleDevToolsClick}
-        >
-          Dev. Tools
-        </MainMenu.Item>
       </MainMenu.Group>
       
       <MainMenu.Separator />

From f0890994ab0a2e4ee004089e169870e49b18118c Mon Sep 17 00:00:00 2001
From: Alex TYRODE <alex@tyrode.dev>
Date: Wed, 28 May 2025 21:11:06 +0000
Subject: [PATCH 3/8] refactor: integrate PadTabsProvider and clean up App
 component

- Replaced usePadTabs hook with PadTabsProvider in App component to streamline tab management and improve context usage.
- Created a new TabsContext to encapsulate tab-related state and actions, enhancing code organization and maintainability.
- Updated Tabs component to utilize the new context, simplifying props and improving clarity.
- Refactored Collab component to access selectedTabId from context, ensuring proper synchronization with the current tab.
- Enhanced Portal class to include a method for retrieving the current pad ID, facilitating better state management.
---
 src/frontend/src/App.tsx                  | 66 +++++++---------------
 src/frontend/src/contexts/TabsContext.tsx | 62 +++++++++++++++++++++
 src/frontend/src/lib/collab/Collab.tsx    | 68 +++++++++++++++++------
 src/frontend/src/lib/collab/Portal.tsx    |  4 ++
 src/frontend/src/ui/Tabs.tsx              | 34 +++++-------
 5 files changed, 153 insertions(+), 81 deletions(-)
 create mode 100644 src/frontend/src/contexts/TabsContext.tsx

diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx
index b2d03d4..0de682a 100644
--- a/src/frontend/src/App.tsx
+++ b/src/frontend/src/App.tsx
@@ -5,7 +5,7 @@ import type { ExcalidrawEmbeddableElement, NonDeleted } from "@atyrode/excalidra
 
 // Hooks
 import { useAuthStatus } from "./hooks/useAuthStatus";
-import { usePadTabs } from "./hooks/usePadTabs";
+import { PadTabsProvider } from "./contexts/TabsContext";
 import { useCallbackRefState } from "./hooks/useCallbackRefState";
 import { useAppConfig } from "./hooks/useAppConfig";
 
@@ -25,19 +25,6 @@ export default function App() {
   const { config, configError } = useAppConfig();
   const { isAuthenticated, isLoading: isLoadingAuth, user } = useAuthStatus();
 
-  const {
-    tabs,
-    selectedTabId,
-    isLoading: isLoadingTabs,
-    createNewPadAsync,
-    isCreating: isCreatingPad,
-    renamePad,
-    deletePad,
-    selectTab,
-    updateSharingPolicy,
-    leaveSharedPad
-  } = usePadTabs(isAuthenticated);
-
   const [excalidrawAPI, excalidrawRefCallback] = useCallbackRefState<ExcalidrawImperativeAPI>();
 
   const handleOnScrollChange = () => {
@@ -57,8 +44,9 @@ export default function App() {
 
   return (
     <>
-      <Excalidraw
-        excalidrawAPI={excalidrawRefCallback}
+      <PadTabsProvider isAuthenticated={isAuthenticated}>
+        <Excalidraw
+          excalidrawAPI={excalidrawRefCallback}
         initialData={INITIAL_APP_DATA}
         UIOptions={{
           hiddenElements: HIDDEN_UI_ELEMENTS,
@@ -83,35 +71,23 @@ export default function App() {
           <AuthDialog />
         )}
 
-        {excalidrawAPI && (
-          <Footer>
-            {isAuthenticated && (
-              <Tabs
-                excalidrawAPI={excalidrawAPI}
-                tabs={tabs}
-                selectedTabId={selectedTabId}
-                isLoading={isLoadingTabs}
-                isCreatingPad={isCreatingPad}
-                createNewPadAsync={createNewPadAsync}
-                renamePad={renamePad}
-                deletePad={deletePad}
-                leaveSharedPad={leaveSharedPad}
-                updateSharingPolicy={updateSharingPolicy}
-                selectTab={selectTab}
-              />
-            )}
-          </Footer>
-        )}
-        {excalidrawAPI && user && (
-          <Collab
-            excalidrawAPI={excalidrawAPI}
-            user={user}
-            isOnline={!!isAuthenticated}
-            isLoadingAuth={isLoadingAuth}
-            padId={selectedTabId}
-          />
-        )}
-      </Excalidraw>
+          {excalidrawAPI && (
+            <Footer>
+              {isAuthenticated && (
+                <Tabs excalidrawAPI={excalidrawAPI} />
+              )}
+            </Footer>
+          )}
+          {excalidrawAPI && user && (
+            <Collab
+              excalidrawAPI={excalidrawAPI}
+              user={user}
+              isOnline={!!isAuthenticated}
+              isLoadingAuth={isLoadingAuth}
+            />
+          )}
+        </Excalidraw>
+      </PadTabsProvider>
     </>
   );
 }
diff --git a/src/frontend/src/contexts/TabsContext.tsx b/src/frontend/src/contexts/TabsContext.tsx
new file mode 100644
index 0000000..68443ef
--- /dev/null
+++ b/src/frontend/src/contexts/TabsContext.tsx
@@ -0,0 +1,62 @@
+import React, { createContext, useContext, ReactNode } from 'react';
+import {
+    usePadTabs,
+    Tab,
+} from '../hooks/usePadTabs';
+
+export interface PadTabsContextType {
+    tabs: Tab[];
+    selectedTabId: string;
+    isLoading: boolean;
+    error: Error | null;
+    isError: boolean;
+    createNewPadAsync: () => Promise<Tab | undefined>;
+    isCreating: boolean;
+    renamePad: (args: { padId: string; newName: string }) => void;
+    isRenaming: boolean;
+    deletePad: (padId: string) => void;
+    isDeleting: boolean;
+    leaveSharedPad: (padId: string) => void;
+    isLeavingSharedPad: boolean;
+    updateSharingPolicy: (args: { padId: string; policy: string }) => void;
+    isUpdatingSharingPolicy: boolean;
+    selectTab: (tabId: string) => void;
+}
+
+export const PadTabsContext = createContext<PadTabsContextType | undefined>(undefined);
+
+export const PadTabsProvider: React.FC<{ children: ReactNode; isAuthenticated?: boolean }> = ({ children, isAuthenticated }) => {
+    const padTabsData = usePadTabs(isAuthenticated);
+
+    const contextValue: PadTabsContextType = {
+        tabs: padTabsData.tabs,
+        selectedTabId: padTabsData.selectedTabId,
+        isLoading: padTabsData.isLoading,
+        error: padTabsData.error,
+        isError: padTabsData.isError,
+        createNewPadAsync: padTabsData.createNewPadAsync,
+        isCreating: padTabsData.isCreating,
+        renamePad: padTabsData.renamePad,
+        isRenaming: padTabsData.isRenaming,
+        deletePad: padTabsData.deletePad,
+        isDeleting: padTabsData.isDeleting,
+        leaveSharedPad: padTabsData.leaveSharedPad,
+        isLeavingSharedPad: padTabsData.isLeavingSharedPad,
+        updateSharingPolicy: padTabsData.updateSharingPolicy,
+        isUpdatingSharingPolicy: padTabsData.isUpdatingSharingPolicy,
+        selectTab: padTabsData.selectTab,
+    };
+    return (
+        <PadTabsContext.Provider value={contextValue}>
+            {children}
+        </PadTabsContext.Provider>
+    );
+};
+
+export const usePadTabsContext = (): PadTabsContextType => {
+    const context = useContext(PadTabsContext);
+    if (context === undefined) {
+        throw new Error('usePadTabsContext must be used within a PadTabsProvider');
+    }
+    return context;
+};
diff --git a/src/frontend/src/lib/collab/Collab.tsx b/src/frontend/src/lib/collab/Collab.tsx
index 518f46f..79724d0 100644
--- a/src/frontend/src/lib/collab/Collab.tsx
+++ b/src/frontend/src/lib/collab/Collab.tsx
@@ -15,6 +15,8 @@ import type { WebSocketMessage, ConnectionStatus } from './Portal';
 import type { UserInfo } from '../../hooks/useAuthStatus';
 import { debounce, type DebouncedFunction } from '../debounce';
 import { POINTER_MOVE_THROTTLE_MS, ENABLE_PERIODIC_FULL_SYNC, PERIODIC_FULL_SYNC_INTERVAL_MS } from '../../constants';
+import { PadTabsContext } from '../../contexts/TabsContext'; // Adjust path
+import type { PadTabsContextType } from '../../contexts/TabsContext'; // Import type
 
 interface PointerData {
   x: number;
@@ -49,7 +51,7 @@ interface CollabProps {
   user: UserInfo | null;
   isOnline: boolean;
   isLoadingAuth: boolean;
-  padId: string | null;
+  // padId: string | null; // This prop will be removed
 }
 
 interface CollabState {
@@ -61,9 +63,12 @@ interface CollabState {
 }
 
 class Collab extends PureComponent<CollabProps, CollabState> {
+  static contextType = PadTabsContext;
+  context!: PadTabsContextType; // Assert context will be available
+
   [x: string]: any;
   readonly state: CollabState;
-  private portal: Portal;
+  private portal!: Portal; // Initialize in constructor or componentDidMount
   private debouncedBroadcastAppState: DebouncedFunction<[AppState]>;
   private lastSentAppState: AppState | null = null;
 
@@ -86,16 +91,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
       lastProcessedSceneVersion: -1,
     };
 
-    this.portal = new Portal(
-      this,
-      props.padId,
-      props.user,
-      props.isOnline, // Passing isOnline as isAuthenticated
-      props.isLoadingAuth,
-      this.handlePortalStatusChange,
-      this.handlePortalMessage
-    );
-
+    // Portal initialization will be done in componentDidMount to access context
     this.throttledOnPointerMove = throttle((event: PointerEvent) => {
       this.handlePointerMove(event);
     }, POINTER_MOVE_THROTTLE_MS);
@@ -185,6 +181,18 @@ class Collab extends PureComponent<CollabProps, CollabState> {
   /* Component Lifecycle */
 
   componentDidMount() {
+    const selectedTabIdFromContext = this.context.selectedTabId;
+
+    this.portal = new Portal(
+      this,
+      selectedTabIdFromContext, 
+      this.props.user,
+      this.props.isOnline,
+      this.props.isLoadingAuth,
+      this.handlePortalStatusChange,
+      this.handlePortalMessage
+    );
+
     if (this.portal) {
       this.portal.initiate();
     }
@@ -192,7 +200,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
     if (this.props.user) {
       this.updateUsername(this.props.user);
     }
-    this.updateExcalidrawCollaborators(); // Initial update for collaborators
+    this.updateExcalidrawCollaborators(); 
     this.addPointerEventListeners();
     this.addSceneChangeListeners();
 
@@ -204,7 +212,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
       // Also set the initial processed version from local state
       this.setState({ lastProcessedSceneVersion: this.lastBroadcastedSceneVersion });
     }
-    if (this.props.isOnline && this.props.padId) {
+    if (this.props.isOnline && selectedTabIdFromContext) {
       // Potentially call a method to broadcast initial scene if this client is the first or needs to sync
       // this.broadcastFullSceneUpdate(true); // Example: true for SCENE_INIT
     }
@@ -218,6 +226,27 @@ class Collab extends PureComponent<CollabProps, CollabState> {
   }
 
   componentDidUpdate(prevProps: CollabProps, prevState: CollabState) {
+    const selectedTabIdFromContext = this.context.selectedTabId;
+    // To get previous context value, you need to pass it to componentDidUpdate if using getDerivedStateFromProps,
+    // or manage it in state if necessary. For direct comparison, we'll compare current context with previous prop-derived ID if that was the logic.
+    // However, the more direct way is to compare this.context.selectedTabId with the previous value of this.context.selectedTabId.
+    // React doesn't pass prevContext to cDU by default for static contextType.
+    // A common workaround is to store relevant context parts in state if you need to compare.
+    // Or, if the update logic for portal.updatePadId only depends on the *current* context.selectedTabId
+    // and its change from the *previous* selectedTabId (which was effectively props.padId),
+    // we need to ensure we have the "previous" selectedTabId.
+    // One way: store selectedTabId in component state and update it from context.
+    // For simplicity here, we'll assume the portal's updatePadId is idempotent or handles changes correctly.
+    // A more robust way:
+    // if (selectedTabIdFromContext !== this.portal.getCurrentPadId()) { // Assuming portal exposes current padId
+    //    this.portal.updatePadId(selectedTabIdFromContext);
+    // }
+    // For now, let's check against the previous value of selectedTabId that would have been derived from props.
+    // This requires a bit of care. The most straightforward is to compare this.context.selectedTabId
+    // with a value stored from the *previous render's context*.
+    // Since `prevContext` isn't passed, we'd typically store `selectedTabId` in `this.state`
+    // and update it in `getDerivedStateFromProps` or `componentDidUpdate` if it changes in context.
+
     if (
       this.props.user !== prevProps.user ||
       this.props.isOnline !== prevProps.isOnline ||
@@ -227,7 +256,14 @@ class Collab extends PureComponent<CollabProps, CollabState> {
       this.portal.updateAuthInfo(this.props.user, this.props.isOnline, this.props.isLoadingAuth);
     }
 
-    if (this.props.padId !== prevProps.padId) {
+    // Check if selectedTabId from context has changed
+    // This is a bit tricky with static contextType as prevContext isn't directly available.
+    // A common pattern is to compare this.context.selectedTabId with a stored previous value.
+    // For this example, we'll assume a simplified check. If this causes issues,
+    // selectedTabId might need to be mirrored in the component's state.
+    const previousPadId = this.portal.getPadId(); // Use the new getter
+
+    if (selectedTabIdFromContext !== previousPadId) { 
       // Portal's updatePadId will handle disconnection from old and connection to new
       this.debouncedBroadcastAppState.cancel(); // Cancel any pending app state updates for the old pad
       this.lastSentAppState = null; // Reset last sent app state for the new pad
@@ -237,7 +273,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
       // Mark as initial load for the new pad
       this.isInitialLoad = true;
 
-      this.portal.updatePadId(this.props.padId);
+      this.portal.updatePadId(selectedTabIdFromContext); 
       this.setState({
         collaborators: new Map(),
         lastProcessedSceneVersion: -1,
diff --git a/src/frontend/src/lib/collab/Portal.tsx b/src/frontend/src/lib/collab/Portal.tsx
index 7f0f715..07fb071 100644
--- a/src/frontend/src/lib/collab/Portal.tsx
+++ b/src/frontend/src/lib/collab/Portal.tsx
@@ -89,6 +89,10 @@ class Portal {
     return this.currentConnectionStatus;
   }
 
+  public getPadId(): string | null {
+    return this.roomId;
+  }
+
   private getSocketUrl(): string | null {
     if (!this.roomId || this.roomId.startsWith('temp-')) {
       return null;
diff --git a/src/frontend/src/ui/Tabs.tsx b/src/frontend/src/ui/Tabs.tsx
index 02af0ce..e5f80b7 100644
--- a/src/frontend/src/ui/Tabs.tsx
+++ b/src/frontend/src/ui/Tabs.tsx
@@ -6,6 +6,7 @@ import { FilePlus2, ChevronLeft, ChevronRight, Users } from "lucide-react";
 
 import { usePad } from "../hooks/usePadData";
 import { useAuthStatus } from "../hooks/useAuthStatus";
+import { usePadTabsContext } from "../contexts/TabsContext"; 
 import type { Tab } from "../hooks/usePadTabs";
 import { capture } from "../lib/posthog";
 import TabContextMenu from "./TabContextMenu";
@@ -13,16 +14,6 @@ import "./Tabs.scss";
 
 interface TabsProps {
     excalidrawAPI: ExcalidrawImperativeAPI;
-    tabs: Tab[];
-    selectedTabId: string | null;
-    isLoading: boolean;
-    isCreatingPad: boolean;
-    createNewPadAsync: () => Promise<Tab | null | undefined>;
-    renamePad: (args: { padId: string; newName: string }) => void;
-    deletePad: (padId: string) => void;
-    leaveSharedPad: (padId: string) => void;
-    updateSharingPolicy: (args: { padId: string; policy: string }) => void;
-    selectTab: (tabId: string) => void;
 }
 
 // Custom hook to check if text is overflowing its container
@@ -48,17 +39,20 @@ const useTextOverflow = () => {
 
 const Tabs: React.FC<TabsProps> = ({
     excalidrawAPI,
-    tabs,
-    selectedTabId,
-    isLoading,
-    isCreatingPad,
-    createNewPadAsync,
-    renamePad,
-    deletePad,
-    leaveSharedPad, // Destructure new prop
-    updateSharingPolicy,
-    selectTab,
 }) => {
+    const {
+        tabs,
+        selectedTabId,
+        isLoading, // This is isLoading for tabs data
+        isCreating: isCreatingPad,
+        createNewPadAsync,
+        renamePad,
+        deletePad,
+        leaveSharedPad,
+        updateSharingPolicy,
+        selectTab
+    } = usePadTabsContext();
+
     const { user: currentUser } = useAuthStatus();
     const { isLoading: isPadLoading, error: padError } = usePad(selectedTabId, excalidrawAPI);
     const [displayPadLoadingIndicator, setDisplayPadLoadingIndicator] = useState(false);

From 3cc04807687b2f146c42efcdaa74473dbf6d33ad Mon Sep 17 00:00:00 2001
From: Alex TYRODE <alex@tyrode.dev>
Date: Wed, 28 May 2025 21:11:43 +0000
Subject: [PATCH 4/8] refactor: rename Tabs to PadTabs to fit hooks, context
 and providers

---
 src/frontend/src/App.tsx                      | 60 +++++++++----------
 .../{TabsContext.tsx => PadTabsContext.tsx}   |  0
 src/frontend/src/lib/collab/Collab.tsx        |  4 +-
 .../src/ui/{Tabs.scss => PadTabs.scss}        |  0
 src/frontend/src/ui/{Tabs.tsx => PadTabs.tsx} |  8 +--
 5 files changed, 36 insertions(+), 36 deletions(-)
 rename src/frontend/src/contexts/{TabsContext.tsx => PadTabsContext.tsx} (100%)
 rename src/frontend/src/ui/{Tabs.scss => PadTabs.scss} (100%)
 rename src/frontend/src/ui/{Tabs.tsx => PadTabs.tsx} (99%)

diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx
index 0de682a..5af1203 100644
--- a/src/frontend/src/App.tsx
+++ b/src/frontend/src/App.tsx
@@ -5,7 +5,7 @@ import type { ExcalidrawEmbeddableElement, NonDeleted } from "@atyrode/excalidra
 
 // Hooks
 import { useAuthStatus } from "./hooks/useAuthStatus";
-import { PadTabsProvider } from "./contexts/TabsContext";
+import { PadTabsProvider } from "./contexts/PadTabsContext";
 import { useCallbackRefState } from "./hooks/useCallbackRefState";
 import { useAppConfig } from "./hooks/useAppConfig";
 
@@ -18,8 +18,8 @@ import Collab from './lib/collab/Collab';
 // Utils
 import { initializePostHog } from "./lib/posthog";
 import { lockEmbeddables, renderCustomEmbeddable } from './CustomEmbeddableRenderer';
-import Tabs from "./ui/Tabs";
 import { INITIAL_APP_DATA, HIDDEN_UI_ELEMENTS } from "./constants";
+import PadTabs from "./ui/PadTabs";
 
 export default function App() {
   const { config, configError } = useAppConfig();
@@ -47,19 +47,19 @@ export default function App() {
       <PadTabsProvider isAuthenticated={isAuthenticated}>
         <Excalidraw
           excalidrawAPI={excalidrawRefCallback}
-        initialData={INITIAL_APP_DATA}
-        UIOptions={{
-          hiddenElements: HIDDEN_UI_ELEMENTS,
-        }}
-        onScrollChange={handleOnScrollChange}
-        validateEmbeddable={true}
-        renderEmbeddable={(element: NonDeleted<ExcalidrawEmbeddableElement>, appState: AppState) => {
-          return renderCustomEmbeddable(element, appState, excalidrawAPI);
-        }}
-        renderTopRightUI={() => (
-          <div style={{ display: "flex", gap: "0.5rem", alignItems: "center" }}>
-            <DiscordButton />
-          </div>
+          initialData={INITIAL_APP_DATA}
+          UIOptions={{
+            hiddenElements: HIDDEN_UI_ELEMENTS,
+          }}
+          onScrollChange={handleOnScrollChange}
+          validateEmbeddable={true}
+          renderEmbeddable={(element: NonDeleted<ExcalidrawEmbeddableElement>, appState: AppState) => {
+            return renderCustomEmbeddable(element, appState, excalidrawAPI);
+          }}
+          renderTopRightUI={() => (
+            <div style={{ display: "flex", gap: "0.5rem", alignItems: "center" }}>
+              <DiscordButton />
+            </div>
         )}
       >
         <MainMenuConfig
@@ -71,21 +71,21 @@ export default function App() {
           <AuthDialog />
         )}
 
-          {excalidrawAPI && (
-            <Footer>
-              {isAuthenticated && (
-                <Tabs excalidrawAPI={excalidrawAPI} />
-              )}
-            </Footer>
-          )}
-          {excalidrawAPI && user && (
-            <Collab
-              excalidrawAPI={excalidrawAPI}
-              user={user}
-              isOnline={!!isAuthenticated}
-              isLoadingAuth={isLoadingAuth}
-            />
-          )}
+        {excalidrawAPI && (
+          <Footer>
+            {isAuthenticated && (
+              <PadTabs excalidrawAPI={excalidrawAPI} />
+            )}
+          </Footer>
+        )}
+        {excalidrawAPI && user && (
+          <Collab
+            excalidrawAPI={excalidrawAPI}
+            user={user}
+            isOnline={!!isAuthenticated}
+            isLoadingAuth={isLoadingAuth}
+          />
+        )}
         </Excalidraw>
       </PadTabsProvider>
     </>
diff --git a/src/frontend/src/contexts/TabsContext.tsx b/src/frontend/src/contexts/PadTabsContext.tsx
similarity index 100%
rename from src/frontend/src/contexts/TabsContext.tsx
rename to src/frontend/src/contexts/PadTabsContext.tsx
diff --git a/src/frontend/src/lib/collab/Collab.tsx b/src/frontend/src/lib/collab/Collab.tsx
index 79724d0..7d262f4 100644
--- a/src/frontend/src/lib/collab/Collab.tsx
+++ b/src/frontend/src/lib/collab/Collab.tsx
@@ -15,8 +15,8 @@ import type { WebSocketMessage, ConnectionStatus } from './Portal';
 import type { UserInfo } from '../../hooks/useAuthStatus';
 import { debounce, type DebouncedFunction } from '../debounce';
 import { POINTER_MOVE_THROTTLE_MS, ENABLE_PERIODIC_FULL_SYNC, PERIODIC_FULL_SYNC_INTERVAL_MS } from '../../constants';
-import { PadTabsContext } from '../../contexts/TabsContext'; // Adjust path
-import type { PadTabsContextType } from '../../contexts/TabsContext'; // Import type
+import { PadTabsContext } from '../../contexts/PadTabsContext'; // Adjust path
+import type { PadTabsContextType } from '../../contexts/PadTabsContext'; // Import type
 
 interface PointerData {
   x: number;
diff --git a/src/frontend/src/ui/Tabs.scss b/src/frontend/src/ui/PadTabs.scss
similarity index 100%
rename from src/frontend/src/ui/Tabs.scss
rename to src/frontend/src/ui/PadTabs.scss
diff --git a/src/frontend/src/ui/Tabs.tsx b/src/frontend/src/ui/PadTabs.tsx
similarity index 99%
rename from src/frontend/src/ui/Tabs.tsx
rename to src/frontend/src/ui/PadTabs.tsx
index e5f80b7..225a829 100644
--- a/src/frontend/src/ui/Tabs.tsx
+++ b/src/frontend/src/ui/PadTabs.tsx
@@ -6,11 +6,11 @@ import { FilePlus2, ChevronLeft, ChevronRight, Users } from "lucide-react";
 
 import { usePad } from "../hooks/usePadData";
 import { useAuthStatus } from "../hooks/useAuthStatus";
-import { usePadTabsContext } from "../contexts/TabsContext"; 
+import { usePadTabsContext } from "../contexts/PadTabsContext"; 
 import type { Tab } from "../hooks/usePadTabs";
 import { capture } from "../lib/posthog";
 import TabContextMenu from "./TabContextMenu";
-import "./Tabs.scss";
+import "./PadTabs.scss";
 
 interface TabsProps {
     excalidrawAPI: ExcalidrawImperativeAPI;
@@ -37,7 +37,7 @@ const useTextOverflow = () => {
     return { overflowMap, checkOverflow };
 };
 
-const Tabs: React.FC<TabsProps> = ({
+const PadTabs: React.FC<TabsProps> = ({
     excalidrawAPI,
 }) => {
     const {
@@ -480,4 +480,4 @@ const Tabs: React.FC<TabsProps> = ({
     );
 };
 
-export default Tabs;
+export default PadTabs;

From 9f068e04ad63d3a790c937b17bd228f019a022e4 Mon Sep 17 00:00:00 2001
From: Alex TYRODE <alex@tyrode.dev>
Date: Wed, 28 May 2025 21:13:33 +0000
Subject: [PATCH 5/8] refactor: remove TabBar component and associated styles

- Deleted the TabBar component and its SCSS file to streamline the codebase and improve maintainability.
- This change eliminates unused code and prepares for a more efficient tab management system.
---
 src/frontend/src/ui/TabBar.scss | 58 ---------------------------------
 src/frontend/src/ui/TabBar.tsx  | 42 ------------------------
 2 files changed, 100 deletions(-)
 delete mode 100644 src/frontend/src/ui/TabBar.scss
 delete mode 100644 src/frontend/src/ui/TabBar.tsx

diff --git a/src/frontend/src/ui/TabBar.scss b/src/frontend/src/ui/TabBar.scss
deleted file mode 100644
index 6801038..0000000
--- a/src/frontend/src/ui/TabBar.scss
+++ /dev/null
@@ -1,58 +0,0 @@
-.tabs-bar {
-    margin-inline-start: 0.6rem;
-    height: var(--lg-button-size);
-    position: relative;
-    display: flex;
-    gap: 8px;
-    align-items: center;
-    padding: 0 8px;
-
-    .tab {
-        height: var(--lg-button-size) !important;
-        width: 100px !important;
-        min-width: 100px !important;
-        margin-right: 0.6rem;
-        text-overflow: ellipsis;
-        overflow: hidden;
-        white-space: nowrap;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        padding: 0 16px;
-        border: none;
-        border-radius: var(--border-radius-lg);
-        background: var(--island-bg-color);
-        color: #bdbdbd;
-        font-size: 14px;
-        font-weight: 500;
-        cursor: pointer;
-        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
-        transition: all 0.2s ease;
-        outline: none;
-
-        &:hover {
-            background: #4a4a54;
-            color: #ffffff;
-        }
-
-        &.active-pad {
-            background-color: #cc6d24 !important;
-            color: var(--color-on-primary) !important;
-            font-weight: bold;
-            border: 1px solid #cccccc !important;
-        }
-
-        &.new-tab {
-            min-width: var(--lg-button-size) !important;
-            width: var(--lg-button-size) !important;
-            padding: 0;
-            background: var(--island-bg-color);
-            color: #bdbdbd;
-
-            &:hover {
-                color: #ffffff;
-                background: #4a4a54;
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/frontend/src/ui/TabBar.tsx b/src/frontend/src/ui/TabBar.tsx
deleted file mode 100644
index a161553..0000000
--- a/src/frontend/src/ui/TabBar.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react';
-import { NewPadIcon } from '../icons';
-import './TabBar.scss';
-
-interface Tab {
-    id: string;
-    label: string;
-}
-
-interface TabBarProps {
-    tabs: Tab[];
-    activeTabId: string;
-    onTabSelect: (tabId: string) => void;
-    onNewTab?: () => void;
-}
-
-const TabBar: React.FC<TabBarProps> = ({ tabs, activeTabId, onTabSelect, onNewTab }) => {
-    return (
-        <div className="tabs-bar">
-            {tabs.map((tab) => (
-                <button
-                    key={tab.id}
-                    className={`tab${tab.id === activeTabId ? ' active-pad' : ''}`}
-                    onClick={() => onTabSelect(tab.id)}
-                >
-                    {tab.label}
-                </button>
-            ))}
-            {onNewTab && (
-                <button
-                    className="tab new-tab"
-                    onClick={onNewTab}
-                    title="New Pad"
-                >
-                    <NewPadIcon />
-                </button>
-            )}
-        </div>
-    );
-};
-
-export default TabBar; 
\ No newline at end of file

From cd3807ea66096010f43606920f1ac2407bc1e81d Mon Sep 17 00:00:00 2001
From: Alex TYRODE <alex@tyrode.dev>
Date: Wed, 28 May 2025 22:36:52 +0000
Subject: [PATCH 6/8] refactor: clean up imports in App component

- Removed unused useState import from App.tsx to enhance code clarity and reduce unnecessary dependencies.
---
 src/frontend/src/App.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx
index 5af1203..0a67c44 100644
--- a/src/frontend/src/App.tsx
+++ b/src/frontend/src/App.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from "react";
+import { useEffect } from "react";
 import { Excalidraw, MainMenu, Footer } from "@atyrode/excalidraw";
 import type { ExcalidrawImperativeAPI, AppState } from "@atyrode/excalidraw/types";
 import type { ExcalidrawEmbeddableElement, NonDeleted } from "@atyrode/excalidraw/element/types";

From db288bd5bfc7a3f09310c0538179e869987be09e Mon Sep 17 00:00:00 2001
From: Alex TYRODE <alex@tyrode.dev>
Date: Wed, 28 May 2025 22:41:40 +0000
Subject: [PATCH 7/8] refactor: add TODO comments for future improvements in
 Collab component

- Marked the Collab component for a future refactor to convert it into a functional component, enhancing maintainability.
- Added comments to clarify the need for better context management in componentDidUpdate, indicating areas for improvement.
---
 src/frontend/src/lib/collab/Collab.tsx | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/frontend/src/lib/collab/Collab.tsx b/src/frontend/src/lib/collab/Collab.tsx
index 7d262f4..7e240f7 100644
--- a/src/frontend/src/lib/collab/Collab.tsx
+++ b/src/frontend/src/lib/collab/Collab.tsx
@@ -62,6 +62,7 @@ interface CollabState {
   lastProcessedSceneVersion: number;
 }
 
+// TODO: Component wide-refactor is needed: this should be a functional component.
 class Collab extends PureComponent<CollabProps, CollabState> {
   static contextType = PadTabsContext;
   context!: PadTabsContextType; // Assert context will be available
@@ -227,6 +228,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
 
   componentDidUpdate(prevProps: CollabProps, prevState: CollabState) {
     const selectedTabIdFromContext = this.context.selectedTabId;
+    // TODO: This is a bit of a hack to get the previous context value.
     // To get previous context value, you need to pass it to componentDidUpdate if using getDerivedStateFromProps,
     // or manage it in state if necessary. For direct comparison, we'll compare current context with previous prop-derived ID if that was the logic.
     // However, the more direct way is to compare this.context.selectedTabId with the previous value of this.context.selectedTabId.
@@ -256,6 +258,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
       this.portal.updateAuthInfo(this.props.user, this.props.isOnline, this.props.isLoadingAuth);
     }
 
+    // TODO: This is a bit of a hack to get the previous context value.
     // Check if selectedTabId from context has changed
     // This is a bit tricky with static contextType as prevContext isn't directly available.
     // A common pattern is to compare this.context.selectedTabId with a stored previous value.

From 6e7c46049bcb80b5c888378bf7891373d1058fb1 Mon Sep 17 00:00:00 2001
From: Alex TYRODE <alex@tyrode.dev>
Date: Wed, 28 May 2025 23:19:26 +0000
Subject: [PATCH 8/8] refactor: simplify onScrollChange handler in App
 component

- Replaced the handleOnScrollChange function with an inline arrow function to streamline the code and improve readability.
- This change enhances maintainability by reducing the number of function declarations in the App component.
---
 src/frontend/src/App.tsx | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx
index 0a67c44..3264acc 100644
--- a/src/frontend/src/App.tsx
+++ b/src/frontend/src/App.tsx
@@ -27,10 +27,6 @@ export default function App() {
 
   const [excalidrawAPI, excalidrawRefCallback] = useCallbackRefState<ExcalidrawImperativeAPI>();
 
-  const handleOnScrollChange = () => {
-    lockEmbeddables(excalidrawAPI?.getAppState());
-  };
-
   useEffect(() => {
     if (!config?.devMode && config?.posthogKey && config?.posthogHost) {
       initializePostHog({
@@ -51,7 +47,7 @@ export default function App() {
           UIOptions={{
             hiddenElements: HIDDEN_UI_ELEMENTS,
           }}
-          onScrollChange={handleOnScrollChange}
+          onScrollChange={() => lockEmbeddables(excalidrawAPI?.getAppState())}
           validateEmbeddable={true}
           renderEmbeddable={(element: NonDeleted<ExcalidrawEmbeddableElement>, appState: AppState) => {
             return renderCustomEmbeddable(element, appState, excalidrawAPI);
