From 1501d14ed21cc3eef187433d2ade7e4deae6a22d Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Mon, 1 May 2023 17:36:33 -0400
Subject: [PATCH 01/20] lazy load component logs

---
 .../component/changelog/changelog.section.tsx |   4 +-
 .../changelog/changelog.ui.runtime.tsx        |  12 +-
 .../changelog/ui/change-log-page.tsx          |  58 ++++++--
 scopes/component/component/ui/index.ts        |   1 +
 .../component/ui/use-component-query.ts       | 127 ++++++++++++++++--
 .../component/component/ui/use-component.tsx  |   4 +
 .../ui/version-block/version-block.tsx        |  17 ++-
 7 files changed, 188 insertions(+), 35 deletions(-)

diff --git a/scopes/component/changelog/changelog.section.tsx b/scopes/component/changelog/changelog.section.tsx
index 8ee4fad47aee..966e935a86dc 100644
--- a/scopes/component/changelog/changelog.section.tsx
+++ b/scopes/component/changelog/changelog.section.tsx
@@ -4,9 +4,11 @@ import { MenuWidgetIcon } from '@teambit/ui-foundation.ui.menu-widget-icon';
 import { ChangeLogPage } from './ui/change-log-page';
 
 export class ChangelogSection implements Section {
+  constructor(private host: string) {}
+
   route = {
     path: '~changelog',
-    element: <ChangeLogPage />,
+    element: <ChangeLogPage host={this.host} />,
   };
   navigationLink = {
     href: '~changelog',
diff --git a/scopes/component/changelog/changelog.ui.runtime.tsx b/scopes/component/changelog/changelog.ui.runtime.tsx
index 548b2e69c91d..2b1b49ab30a2 100644
--- a/scopes/component/changelog/changelog.ui.runtime.tsx
+++ b/scopes/component/changelog/changelog.ui.runtime.tsx
@@ -1,5 +1,6 @@
 import { ComponentAspect, ComponentUI } from '@teambit/component';
 import { UIRuntime } from '@teambit/ui';
+import { Harmony } from '@teambit/harmony';
 import React from 'react';
 
 import { ChangelogAspect } from './changelog.aspect';
@@ -7,17 +8,20 @@ import { ChangelogSection } from './changelog.section';
 import { ChangeLogPage } from './ui/change-log-page';
 
 export class ChangeLogUI {
+  constructor(private host: string) {}
   ChangeLog = () => {
-    return <ChangeLogPage />;
+    return <ChangeLogPage host={this.host} />;
   };
 
   static dependencies = [ComponentAspect];
 
   static runtime = UIRuntime;
 
-  static async provider([component]: [ComponentUI]) {
-    const ui = new ChangeLogUI();
-    const section = new ChangelogSection();
+  static async provider([component]: [ComponentUI], _, __, harmony: Harmony) {
+    const { config } = harmony;
+    const host = String(config.get('teambit.harmony/bit'));
+    const ui = new ChangeLogUI(host);
+    const section = new ChangelogSection(host);
 
     component.registerRoute(section.route);
     component.registerWidget(section.navigationLink, section.order);
diff --git a/scopes/component/changelog/ui/change-log-page.tsx b/scopes/component/changelog/ui/change-log-page.tsx
index 9a42b02fa742..daa481badfa3 100644
--- a/scopes/component/changelog/ui/change-log-page.tsx
+++ b/scopes/component/changelog/ui/change-log-page.tsx
@@ -1,4 +1,5 @@
-import { ComponentContext } from '@teambit/component';
+import React, { HTMLAttributes } from 'react';
+import { ComponentContext, useComponent } from '@teambit/component';
 import { H1 } from '@teambit/documenter.ui.heading';
 import { Separator } from '@teambit/design.ui.separator';
 import { VersionBlock } from '@teambit/component.ui.version-block';
@@ -6,19 +7,51 @@ import classNames from 'classnames';
 import { MDXLayout } from '@teambit/mdx.ui.mdx-layout';
 import { ExportingComponents } from '@teambit/component.instructions.exporting-components';
 import { AlertCard } from '@teambit/design.ui.alert-card';
-import React, { HTMLAttributes, useContext } from 'react';
 
 import styles from './change-log-page.module.scss';
 
-type ChangeLogPageProps = {} & HTMLAttributes<HTMLDivElement>;
+type ChangeLogPageProps = {
+  host: string;
+} & HTMLAttributes<HTMLDivElement>;
 
-export function ChangeLogPage({ className }: ChangeLogPageProps) {
-  const component = useContext(ComponentContext);
-  const { logs } = component;
+export function ChangeLogPage({ className, host }: ChangeLogPageProps) {
+  const componentContext = React.useContext(ComponentContext);
+  const {
+    componentLogs: logs = [],
+    component,
+    loading,
+    loadMoreLogs,
+    hasMoreLogs: hasMore,
+  } = useComponent(host, componentContext?.id.toString(), {
+    logFilters: {
+      log: {
+        logLimit: 15,
+      },
+    },
+  });
 
-  if (!logs) return null;
+  const observer = React.useRef<IntersectionObserver>();
+  const handleLoadMore = () => {
+    loadMoreLogs?.();
+  };
 
-  if (logs.length === 0) {
+  const lastLogRef = React.useCallback(
+    (node) => {
+      if (loading) return;
+      if (observer.current) observer.current.disconnect();
+      observer.current = new IntersectionObserver((entries) => {
+        if (entries[0].isIntersecting && hasMore) {
+          handleLoadMore();
+        }
+      });
+      if (node) observer.current.observe(node);
+    },
+    [loading, hasMore]
+  );
+
+  if (loading) return null;
+
+  if (logs?.length === 0) {
     return (
       <div className={classNames(styles.changeLogPage, className)}>
         <H1 className={styles.title}>History</H1>
@@ -42,16 +75,17 @@ export function ChangeLogPage({ className }: ChangeLogPageProps) {
       <H1 className={styles.title}>History</H1>
       <Separator isPresentational className={styles.separator} />
       <div className={styles.logContainer}>
-        {logs.map((snap, index) => {
-          const isLatest = component.latest === snap.tag || component.latest === snap.hash;
-          const isCurrent = component.version === snap.tag || component.version === snap.hash;
+        {(logs || []).map((snap, index) => {
+          const isLatest = component?.latest === snap.tag || component?.latest === snap.hash;
+          const isCurrent = component?.version === snap.tag || component?.version === snap.hash;
           return (
             <VersionBlock
               key={index}
-              componentId={component.id.fullName}
+              componentId={component?.id?.fullName || ''}
               isLatest={isLatest}
               snap={snap}
               isCurrent={isCurrent}
+              ref={index === logs.length - 1 ? lastLogRef : null}
             />
           );
         })}
diff --git a/scopes/component/component/ui/index.ts b/scopes/component/component/ui/index.ts
index bcaf819bbbcc..5c8be5db4d79 100644
--- a/scopes/component/component/ui/index.ts
+++ b/scopes/component/component/ui/index.ts
@@ -6,4 +6,5 @@ export { ComponentContext, ComponentProvider } from './context';
 export { useComponent } from './use-component';
 export { TopBarNav } from './top-bar-nav';
 export { componentIdFields, componentOverviewFields, componentFields } from './use-component-query';
+export type { ComponentQueryResult } from './use-component-query';
 export { useIdFromLocation } from './use-component-from-location';
diff --git a/scopes/component/component/ui/use-component-query.ts b/scopes/component/component/ui/use-component-query.ts
index 41fe7ed7e342..6d2b73f70cf2 100644
--- a/scopes/component/component/ui/use-component-query.ts
+++ b/scopes/component/component/ui/use-component-query.ts
@@ -1,8 +1,9 @@
-import { useMemo, useEffect, useRef } from 'react';
+import React, { useMemo, useEffect, useRef } from 'react';
 import { gql } from '@apollo/client';
 import { useDataQuery } from '@teambit/ui-foundation.ui.hooks.use-data-query';
 import { ComponentID, ComponentIdObj } from '@teambit/component-id';
 import { ComponentDescriptor } from '@teambit/component-descriptor';
+import { LegacyComponentLog } from '@teambit/legacy-component-log';
 
 import { ComponentModel } from './component-model';
 import { ComponentError } from './component-error';
@@ -151,16 +152,96 @@ const SUB_COMPONENT_REMOVED = gql`
 export type Filters = {
   log?: { logType?: string; logOffset?: number; logLimit?: number; logHead?: string; logSort?: string };
 };
+
+export type ComponentQueryResult = {
+  component?: ComponentModel;
+  error?: ComponentError;
+  componentDescriptor?: ComponentDescriptor;
+  loading?: boolean;
+  loadMoreLogs?: () => void;
+  hasMoreLogs?: boolean;
+  componentLogs?: LegacyComponentLog[];
+};
+
 /** provides data to component ui page, making sure both variables and return value are safely typed and memoized */
-export function useComponentQuery(componentId: string, host: string, filters?: Filters, skip?: boolean) {
+export function useComponentQuery(
+  componentId: string,
+  host: string,
+  filtersFromProps?: Filters,
+  skip?: boolean
+): ComponentQueryResult {
   const idRef = useRef(componentId);
   idRef.current = componentId;
-  const { data, error, loading, subscribeToMore, ...rest } = useDataQuery(GET_COMPONENT, {
-    variables: { id: componentId, extensionId: host, ...(filters?.log || {}) },
+  const filters = useMemo(() => filtersFromProps?.log || {}, [filtersFromProps]);
+  const { logOffset, logLimit } = filters;
+
+  const logsRef = React.useRef<LegacyComponentLog[]>([]);
+  const { data, error, loading, subscribeToMore, fetchMore, ...rest } = useDataQuery(GET_COMPONENT, {
+    variables: { id: componentId, extensionId: host, ...filters },
     skip,
     errorPolicy: 'all',
   });
 
+  const rawComponent = data?.getHost?.get;
+
+  logsRef.current = useMemo(() => {
+    if (!logOffset) return rawComponent?.logs;
+
+    if (logsRef.current.length !== (logOffset ?? 0) + (logLimit ?? 0)) {
+      const allLogs = logsRef.current || [];
+      const newLogs = rawComponent?.logs || [];
+      newLogs.forEach((log) => allLogs.push(log));
+      return allLogs;
+    }
+    if (rawComponent?.logs?.length > 0 && logsRef.current?.length === 0) {
+      return rawComponent?.logs;
+    }
+    return logsRef.current;
+  }, [rawComponent?.logs]);
+
+  const hasMoreLogs = React.useRef<boolean | undefined>(undefined);
+
+  hasMoreLogs.current = useMemo(() => {
+    if (!logLimit) return false;
+    if (rawComponent === undefined) return undefined;
+    if (hasMoreLogs.current === undefined) return rawComponent?.logs.length === logLimit;
+    return hasMoreLogs.current;
+  }, [rawComponent?.logs]);
+
+  const loadMoreLogs = React.useCallback(async () => {
+    if (logLimit) {
+      await fetchMore({
+        variables: {
+          logOffset: logsRef.current.length,
+        },
+        updateQuery: (prev, { fetchMoreResult }) => {
+          if (!fetchMoreResult) return prev;
+
+          const prevComponent = prev.getHost.get;
+          const fetchedComponent = fetchMoreResult.getHost.get;
+
+          if (fetchedComponent && ComponentID.isEqualObj(prevComponent.id, fetchedComponent.id)) {
+            const updatedLogs = [...prevComponent.logs, ...fetchedComponent.logs];
+            hasMoreLogs.current = fetchedComponent.logs.length === logLimit;
+            return {
+              ...prev,
+              hasMoreLogs: false,
+              getHost: {
+                ...prev.getHost,
+                get: {
+                  ...prevComponent,
+                  logs: updatedLogs,
+                },
+              },
+            };
+          }
+
+          return prev;
+        },
+      });
+    }
+  }, [logLimit, fetchMore]);
+
   useEffect(() => {
     // @TODO @Kutner fix subscription for scope
     if (host !== 'teambit.workspace/workspace') {
@@ -242,11 +323,28 @@ export function useComponentQuery(componentId: string, host: string, filters?: F
       unsubChanges();
       unsubAddition();
       unsubRemoval();
+      logsRef.current = [];
     };
   }, []);
 
-  const rawComponent = data?.getHost?.get;
-  return useMemo(() => {
+  const idDepKey = rawComponent?.id
+    ? `${rawComponent?.id?.scope}/${rawComponent?.id?.name}@${rawComponent?.id?.version}}`
+    : undefined;
+
+  const id: ComponentID | undefined = useMemo(
+    () => (rawComponent ? ComponentID.fromObject(rawComponent.id) : undefined),
+    [idDepKey]
+  );
+
+  const componentError =
+    error && !data ? new ComponentError(500, error.message) : !rawComponent && !loading && new ComponentError(404);
+
+  const component = useMemo(
+    () => (rawComponent ? ComponentModel.from({ ...rawComponent, host }) : undefined),
+    [id?.toString(), logsRef.current]
+  );
+
+  const componentDescriptor = useMemo(() => {
     const aspectList = {
       entries: rawComponent?.aspects.map((aspectObject) => {
         return {
@@ -256,15 +354,20 @@ export function useComponentQuery(componentId: string, host: string, filters?: F
         };
       }),
     };
-    const id = rawComponent && ComponentID.fromObject(rawComponent.id);
-    const componentError =
-      error && !data ? new ComponentError(500, error.message) : !rawComponent && !loading && new ComponentError(404);
+
+    return id ? ComponentDescriptor.fromObject({ id: id.toString(), aspectList }) : undefined;
+  }, [id?.toString()]);
+
+  return useMemo(() => {
     return {
-      componentDescriptor: id ? ComponentDescriptor.fromObject({ id: id.toString(), aspectList }) : undefined,
-      component: rawComponent ? ComponentModel.from({ ...rawComponent, host }) : undefined,
+      componentDescriptor,
+      component,
       error: componentError || undefined,
       loading,
+      loadMoreLogs,
+      hasMoreLogs: hasMoreLogs.current,
+      componentLogs: logsRef.current,
       ...rest,
     };
-  }, [rawComponent, host, error]);
+  }, [host, component, componentDescriptor, componentError, hasMoreLogs]);
 }
diff --git a/scopes/component/component/ui/use-component.tsx b/scopes/component/component/ui/use-component.tsx
index 9db3b6596a1f..73e9ffa077e8 100644
--- a/scopes/component/component/ui/use-component.tsx
+++ b/scopes/component/component/ui/use-component.tsx
@@ -1,5 +1,6 @@
 import { useQuery } from '@teambit/ui-foundation.ui.react-router.use-query';
 import { ComponentDescriptor } from '@teambit/component-descriptor';
+import { LegacyComponentLog } from '@teambit/legacy-component-log';
 import { ComponentModel } from './component-model';
 import { ComponentError } from './component-error';
 import { Filters, useComponentQuery } from './use-component-query';
@@ -9,6 +10,9 @@ export type Component = {
   error?: ComponentError;
   componentDescriptor?: ComponentDescriptor;
   loading?: boolean;
+  loadMoreLogs?: () => void;
+  hasMoreLogs?: boolean;
+  componentLogs?: LegacyComponentLog[];
 };
 export type UseComponentOptions = {
   version?: string;
diff --git a/scopes/component/ui/version-block/version-block.tsx b/scopes/component/ui/version-block/version-block.tsx
index 90572439b554..a99bf437cb6d 100644
--- a/scopes/component/ui/version-block/version-block.tsx
+++ b/scopes/component/ui/version-block/version-block.tsx
@@ -17,11 +17,11 @@ export type VersionBlockProps = {
   snap: LegacyComponentLog;
   isCurrent: boolean;
 } & HTMLAttributes<HTMLDivElement>;
-/**
- * change log section
- * @name VersionBlock
- */
-export function VersionBlock({ isLatest, className, snap, componentId, isCurrent, ...rest }: VersionBlockProps) {
+
+function _VersionBlock(
+  { isLatest, className, snap, componentId, isCurrent, ...rest }: VersionBlockProps,
+  ref: React.ForwardedRef<HTMLDivElement>
+) {
   const { username, email, message, tag, hash, date } = snap;
   const { lanesModel } = useLanes();
   const currentLaneUrl = lanesModel?.viewedLane
@@ -37,7 +37,7 @@ export function VersionBlock({ isLatest, className, snap, componentId, isCurrent
   const timestamp = useMemo(() => (date ? new Date(parseInt(date)).toString() : new Date().toString()), [date]);
 
   return (
-    <div className={classNames(styles.versionWrapper, className)}>
+    <div className={classNames(styles.versionWrapper, className)} ref={ref}>
       <div className={styles.left}>
         <Labels isLatest={isLatest} isCurrent={isCurrent} />
         <Link className={styles.link} href={`~tests?version=${version}`}>
@@ -62,6 +62,11 @@ export function VersionBlock({ isLatest, className, snap, componentId, isCurrent
     </div>
   );
 }
+/**
+ * change log section
+ * @name VersionBlock
+ */
+export const VersionBlock = React.memo(React.forwardRef<HTMLDivElement, VersionBlockProps>(_VersionBlock));
 
 function commitMessage(message: string) {
   if (!message || message === '') return <div className={styles.emptyMessage}>No commit message</div>;

From c1aff40eba2c10ff7c7da6eef1bb01b3acb16e83 Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Tue, 2 May 2023 10:02:46 -0400
Subject: [PATCH 02/20] simplify lazily fetching component logs

---
 .../changelog/ui/change-log-page.tsx          |  3 +-
 .../component/ui/use-component-query.ts       | 37 +++++++------------
 .../component/component/ui/use-component.tsx  |  2 -
 3 files changed, 16 insertions(+), 26 deletions(-)

diff --git a/scopes/component/changelog/ui/change-log-page.tsx b/scopes/component/changelog/ui/change-log-page.tsx
index daa481badfa3..0563fc356d62 100644
--- a/scopes/component/changelog/ui/change-log-page.tsx
+++ b/scopes/component/changelog/ui/change-log-page.tsx
@@ -17,7 +17,6 @@ type ChangeLogPageProps = {
 export function ChangeLogPage({ className, host }: ChangeLogPageProps) {
   const componentContext = React.useContext(ComponentContext);
   const {
-    componentLogs: logs = [],
     component,
     loading,
     loadMoreLogs,
@@ -30,6 +29,8 @@ export function ChangeLogPage({ className, host }: ChangeLogPageProps) {
     },
   });
 
+  const logs = component?.logs ?? [];
+
   const observer = React.useRef<IntersectionObserver>();
   const handleLoadMore = () => {
     loadMoreLogs?.();
diff --git a/scopes/component/component/ui/use-component-query.ts b/scopes/component/component/ui/use-component-query.ts
index ee45c01acbc8..f7c21d0e27a2 100644
--- a/scopes/component/component/ui/use-component-query.ts
+++ b/scopes/component/component/ui/use-component-query.ts
@@ -3,8 +3,6 @@ import { gql } from '@apollo/client';
 import { useDataQuery } from '@teambit/ui-foundation.ui.hooks.use-data-query';
 import { ComponentID, ComponentIdObj } from '@teambit/component-id';
 import { ComponentDescriptor } from '@teambit/component-descriptor';
-import { LegacyComponentLog } from '@teambit/legacy-component-log';
-
 import { ComponentModel } from './component-model';
 import { ComponentError } from './component-error';
 
@@ -162,7 +160,6 @@ export type ComponentQueryResult = {
   loading?: boolean;
   loadMoreLogs?: () => void;
   hasMoreLogs?: boolean;
-  componentLogs?: LegacyComponentLog[];
 };
 
 /** provides data to component ui page, making sure both variables and return value are safely typed and memoized */
@@ -175,9 +172,8 @@ export function useComponentQuery(
   const idRef = useRef(componentId);
   idRef.current = componentId;
   const filters = useMemo(() => filtersFromProps?.log || {}, [filtersFromProps]);
-  const { logOffset, logLimit } = filters;
-
-  const logsRef = React.useRef<LegacyComponentLog[]>([]);
+  const { logLimit } = filters;
+  const offsetRef = useRef(filters.logOffset);
   const { data, error, loading, subscribeToMore, fetchMore, ...rest } = useDataQuery(GET_COMPONENT, {
     variables: { id: componentId, extensionId: host, ...filters },
     skip,
@@ -185,21 +181,16 @@ export function useComponentQuery(
   });
 
   const rawComponent = data?.getHost?.get;
+  const rawCompLogs = rawComponent?.logs ?? [];
 
-  logsRef.current = useMemo(() => {
-    if (!logOffset) return rawComponent?.logs;
-
-    if (logsRef.current.length !== (logOffset ?? 0) + (logLimit ?? 0)) {
-      const allLogs = logsRef.current || [];
-      const newLogs = rawComponent?.logs || [];
-      newLogs.forEach((log) => allLogs.push(log));
-      return allLogs;
+  offsetRef.current = useMemo(() => {
+    const currentOffset = offsetRef.current;
+    if (!currentOffset) return rawCompLogs.length;
+    if (currentOffset < rawCompLogs?.length) {
+      return rawCompLogs?.length;
     }
-    if (rawComponent?.logs?.length > 0 && logsRef.current?.length === 0) {
-      return rawComponent?.logs;
-    }
-    return logsRef.current;
-  }, [rawComponent?.logs]);
+    return offsetRef.current;
+  }, [rawCompLogs]);
 
   const hasMoreLogs = React.useRef<boolean | undefined>(undefined);
 
@@ -214,7 +205,7 @@ export function useComponentQuery(
     if (logLimit) {
       await fetchMore({
         variables: {
-          logOffset: logsRef.current.length,
+          logOffset: offsetRef.current,
         },
         updateQuery: (prev, { fetchMoreResult }) => {
           if (!fetchMoreResult) return prev;
@@ -225,6 +216,7 @@ export function useComponentQuery(
           if (fetchedComponent && ComponentID.isEqualObj(prevComponent.id, fetchedComponent.id)) {
             const updatedLogs = [...prevComponent.logs, ...fetchedComponent.logs];
             hasMoreLogs.current = fetchedComponent.logs.length === logLimit;
+
             return {
               ...prev,
               hasMoreLogs: false,
@@ -325,7 +317,7 @@ export function useComponentQuery(
       unsubChanges();
       unsubAddition();
       unsubRemoval();
-      logsRef.current = [];
+      offsetRef.current = undefined;
     };
   }, []);
 
@@ -343,7 +335,7 @@ export function useComponentQuery(
 
   const component = useMemo(
     () => (rawComponent ? ComponentModel.from({ ...rawComponent, host }) : undefined),
-    [id?.toString(), logsRef.current]
+    [id?.toString(), rawComponent?.logs.length]
   );
 
   const componentDescriptor = useMemo(() => {
@@ -368,7 +360,6 @@ export function useComponentQuery(
       loading,
       loadMoreLogs,
       hasMoreLogs: hasMoreLogs.current,
-      componentLogs: logsRef.current,
       ...rest,
     };
   }, [host, component, componentDescriptor, componentError, hasMoreLogs]);
diff --git a/scopes/component/component/ui/use-component.tsx b/scopes/component/component/ui/use-component.tsx
index 73e9ffa077e8..5166cb816776 100644
--- a/scopes/component/component/ui/use-component.tsx
+++ b/scopes/component/component/ui/use-component.tsx
@@ -1,6 +1,5 @@
 import { useQuery } from '@teambit/ui-foundation.ui.react-router.use-query';
 import { ComponentDescriptor } from '@teambit/component-descriptor';
-import { LegacyComponentLog } from '@teambit/legacy-component-log';
 import { ComponentModel } from './component-model';
 import { ComponentError } from './component-error';
 import { Filters, useComponentQuery } from './use-component-query';
@@ -12,7 +11,6 @@ export type Component = {
   loading?: boolean;
   loadMoreLogs?: () => void;
   hasMoreLogs?: boolean;
-  componentLogs?: LegacyComponentLog[];
 };
 export type UseComponentOptions = {
   version?: string;

From 28d6c8ff79e0e918478a43d092c739d89695b6b2 Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Tue, 2 May 2023 17:57:53 -0400
Subject: [PATCH 03/20] lazy load version in version dropdown

---
 .../code/ui/code-tab-page/code-tab-page.tsx   |   4 +-
 scopes/component/component/ui/component.tsx   |   2 +-
 scopes/component/component/ui/menu/menu.tsx   | 153 ++++++++--
 .../ui/version-dropdown/version-dropdown.tsx  | 263 +++++++++---------
 .../version-info/version-info.tsx             |  31 ++-
 5 files changed, 287 insertions(+), 166 deletions(-)

diff --git a/scopes/code/ui/code-tab-page/code-tab-page.tsx b/scopes/code/ui/code-tab-page/code-tab-page.tsx
index 0d698d0b02b7..499e63e3eccb 100644
--- a/scopes/code/ui/code-tab-page/code-tab-page.tsx
+++ b/scopes/code/ui/code-tab-page/code-tab-page.tsx
@@ -32,9 +32,10 @@ import styles from './code-tab-page.module.scss';
 export type CodePageProps = {
   fileIconSlot?: FileIconSlot;
   host: string;
+  codeViewClassName?: string;
 } & HTMLAttributes<HTMLDivElement>;
 
-export function CodePage({ className, fileIconSlot, host }: CodePageProps) {
+export function CodePage({ className, fileIconSlot, host, codeViewClassName }: CodePageProps) {
   const urlParams = useCodeParams();
   const component = useContext(ComponentContext);
   const { mainFile, fileTree = [], dependencies, devFiles } = useCode(component.id);
@@ -67,6 +68,7 @@ export function CodePage({ className, fileIconSlot, host }: CodePageProps) {
     <SplitPane layout={sidebarOpenness} size="85%" className={classNames(styles.codePage, className)}>
       <Pane className={styles.left}>
         <CodeView
+          codeSnippetClassName={codeViewClassName}
           componentId={component.id}
           currentFile={currentFile}
           icon={icon}
diff --git a/scopes/component/component/ui/component.tsx b/scopes/component/component/ui/component.tsx
index c1eb8ecc9d6c..8c1763612736 100644
--- a/scopes/component/component/ui/component.tsx
+++ b/scopes/component/component/ui/component.tsx
@@ -54,7 +54,7 @@ export function Component({
   const componentId = _componentIdStr ? ComponentID.fromString(_componentIdStr) : undefined;
   const resolvedComponentIdStr = path || idFromLocation;
   const useComponentOptions = {
-    logFilters: useComponentFilters?.(),
+    logFilters: useComponentFilters?.() || { log: { logOffset: 0, logLimit: 2 } },
     customUseComponent: useComponent,
   };
 
diff --git a/scopes/component/component/ui/menu/menu.tsx b/scopes/component/component/ui/menu/menu.tsx
index e5a7bcaefcc1..914becf5235f 100644
--- a/scopes/component/component/ui/menu/menu.tsx
+++ b/scopes/component/component/ui/menu/menu.tsx
@@ -85,20 +85,74 @@ export function ComponentMenu({
   const resolvedComponentIdStr = path || idFromLocation;
 
   const useComponentOptions = {
-    logFilters: useComponentFilters?.(),
+    logFilters: useComponentFilters?.() || {
+      log: {
+        logLimit: 20,
+      },
+    },
     customUseComponent: useComponent,
   };
 
-  const { component } = useComponentQuery(host, componentId?.toString() || idFromLocation, useComponentOptions);
+  const snapLogOptions = {
+    ...useComponentOptions,
+    logFilters: {
+      ...useComponentOptions.logFilters,
+      log: {
+        ...useComponentOptions.logFilters.log,
+        type: 'snap',
+      },
+    },
+  };
+  const tagLogOptions = {
+    ...useComponentOptions,
+    logFilters: {
+      ...useComponentOptions.logFilters,
+      log: {
+        ...useComponentOptions.logFilters.log,
+        type: 'tag',
+      },
+    },
+  };
+
+  const {
+    component: componentTags,
+    loadMoreLogs: loadMoreTags,
+    hasMoreLogs: hasMoreTags,
+    loading: loadingTags,
+  } = useComponentQuery(host, componentId?.toString() || idFromLocation, tagLogOptions);
+
+  const {
+    component: componentSnaps,
+    loadMoreLogs: loadMoreSnaps,
+    hasMoreLogs: hasMoreSnaps,
+    loading: loadingSnaps,
+  } = useComponentQuery(host, componentId?.toString() || idFromLocation, snapLogOptions);
+
   const mainMenuItems = useMemo(() => groupBy(flatten(menuItemSlot.values()), 'category'), [menuItemSlot]);
 
-  if (!component) return <FullLoader />;
+  if (loadingTags || loadingSnaps) return <FullLoader />;
+  if (!componentSnaps || !componentTags) {
+    // eslint-disable-next-line no-console
+    console.error(`failed loading component tags/snaps for ${idFromLocation}`);
+    return null;
+  }
 
   const RightSide = (
     <div className={styles.rightSide}>
       {RightNode || (
         <>
-          <VersionRelatedDropdowns component={component} consumeMethods={consumeMethodSlot} host={host} />
+          <VersionRelatedDropdowns
+            componentSnaps={componentSnaps}
+            componentTags={componentTags}
+            loadMoreSnaps={loadMoreSnaps}
+            loadMoreTags={loadMoreTags}
+            hasMoreSnaps={hasMoreSnaps}
+            hasMoreTags={hasMoreTags}
+            loadingSnaps={loadingSnaps}
+            loadingTags={loadingTags}
+            consumeMethods={consumeMethodSlot}
+            host={host}
+          />
           <MainDropdown className={styles.hideOnMobile} menuItems={mainMenuItems} />
         </>
       )}
@@ -123,42 +177,46 @@ export function ComponentMenu({
 }
 
 export function VersionRelatedDropdowns({
-  component,
+  componentTags,
+  componentSnaps,
   consumeMethods,
+  loadMoreSnaps,
+  loadMoreTags,
+  hasMoreSnaps,
+  hasMoreTags,
+  loadingSnaps,
+  loadingTags,
   className,
   host,
 }: {
-  component: ComponentModel;
+  componentTags: ComponentModel;
+  componentSnaps: ComponentModel;
+  loadMoreTags?: () => void;
+  loadMoreSnaps?: () => void;
+  loadingSnaps?: boolean;
+  loadingTags?: boolean;
+  hasMoreTags?: boolean;
+  hasMoreSnaps?: boolean;
   consumeMethods?: ConsumeMethodSlot;
   className?: string;
   host: string;
 }) {
   const location = useLocation();
+  const loading = loadingSnaps || loadingTags;
   const { lanesModel } = useLanes();
+  const component = componentTags || componentSnaps;
   const viewedLane =
     lanesModel?.viewedLane?.id && !lanesModel?.viewedLane?.id.isDefault() ? lanesModel.viewedLane : undefined;
-
-  const { logs } = component;
+  // const { logs } = component;
   const isWorkspace = host === 'teambit.workspace/workspace';
 
   const snaps = useMemo(() => {
-    return (logs || []).filter((log) => !log.tag).map((snap) => ({ ...snap, version: snap.hash }));
-  }, [logs]);
+    return (componentSnaps.logs || []).filter((log) => !log.tag).map((snap) => ({ ...snap, version: snap.hash }));
+  }, [componentSnaps.logs]);
 
   const tags = useMemo(() => {
-    const tagLookup = new Map<string, LegacyComponentLog>();
-    (logs || [])
-      .filter((log) => log.tag)
-      .forEach((tag) => {
-        tagLookup.set(tag?.tag as string, tag);
-      });
-    return compact(
-      component.tags
-        ?.toArray()
-        .reverse()
-        .map((tag) => tagLookup.get(tag.version.version))
-    ).map((tag) => ({ ...tag, version: tag.tag as string }));
-  }, [logs]);
+    return (componentTags.logs || []).map((tag) => ({ ...tag, version: tag.tag as string }));
+  }, [componentTags.logs]);
 
   const isNew = snaps.length === 0 && tags.length === 0;
 
@@ -167,8 +225,52 @@ export function VersionRelatedDropdowns({
 
   const currentVersion =
     isWorkspace && !isNew && !location?.search.includes('version') ? 'workspace' : component.version;
+  const VERSION_TAB_NAMES = ['TAG', 'SNAP', 'LANE'] as const;
+  const tabs = VERSION_TAB_NAMES.map((name) => {
+    switch (name) {
+      case 'SNAP':
+        return { name, payload: snaps || [] };
+      case 'LANE':
+        return { name, payload: lanes || [] };
+      default:
+        return { name, payload: tags || [] };
+    }
+  }).filter((tab) => tab.payload.length > 0);
 
   const methods = useConsumeMethods(component, consumeMethods, viewedLane);
+
+  const getActiveTabIndex = () => {
+    if (viewedLane?.components.some((c) => c.version === currentVersion))
+      return tabs.findIndex((tab) => tab.name === 'LANE');
+    if ((snaps || []).some((snap) => snap.version === currentVersion))
+      return tabs.findIndex((tab) => tab.name === 'SNAP');
+    return 0;
+  };
+
+  const [activeTabIndex, setActiveTab] = React.useState<number>(getActiveTabIndex());
+  const activeTabOrSnap: 'SNAP' | 'TAG' | 'LANE' = tabs[activeTabIndex]?.name || tabs[0].name;
+  const hasMore = activeTabOrSnap === 'SNAP' ? hasMoreSnaps : hasMoreTags;
+  const observer = React.useRef<IntersectionObserver>();
+
+  const handleLoadMore = React.useCallback(() => {
+    if (activeTabOrSnap === 'SNAP') loadMoreSnaps?.();
+    if (activeTabOrSnap === 'TAG') loadMoreTags?.();
+  }, [activeTabIndex, tabs.length]);
+
+  const lastLogRef = React.useCallback(
+    (node) => {
+      if (loading) return;
+      if (observer.current) observer.current.disconnect();
+      observer.current = new IntersectionObserver((entries) => {
+        if (entries[0].isIntersecting && hasMore) {
+          handleLoadMore();
+        }
+      });
+      if (node) observer.current.observe(node);
+    },
+    [loading, hasMoreSnaps, hasMoreTags, activeTabIndex]
+  );
+
   return (
     <>
       {consumeMethods && tags.length > 0 && (
@@ -179,9 +281,12 @@ export function VersionRelatedDropdowns({
         />
       )}
       <VersionDropdown
+        ref={lastLogRef}
         tags={tags}
         snaps={snaps}
-        lanes={lanes}
+        tabs={tabs}
+        activeTabIndex={activeTabIndex}
+        setActiveTabIndex={setActiveTab}
         localVersion={localVersion}
         currentVersion={currentVersion}
         latestVersion={component.latest}
diff --git a/scopes/component/ui/version-dropdown/version-dropdown.tsx b/scopes/component/ui/version-dropdown/version-dropdown.tsx
index d2f5c95e70d7..baa50028b101 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown.tsx
+++ b/scopes/component/ui/version-dropdown/version-dropdown.tsx
@@ -17,10 +17,85 @@ export const LOCAL_VERSION = 'workspace';
 
 export type DropdownComponentVersion = Partial<LegacyComponentLog> & { version: string };
 
+const VersionMenu = React.memo(React.forwardRef<HTMLDivElement, VersionMenuProps>(_VersionMenu));
+
+function _VersionMenu(
+  {
+    currentVersion,
+    localVersion,
+    latestVersion,
+    currentLane,
+    overrideVersionHref,
+    showVersionDetails,
+    activeTabIndex,
+    setActiveTab,
+    tabs,
+    ...rest
+  }: VersionMenuProps,
+  ref?: React.ForwardedRef<HTMLDivElement>
+) {
+  const multipleTabs = tabs.length > 1;
+  const message = multipleTabs
+    ? 'Switch to view tags, snaps, or lanes'
+    : `Switch between ${tabs[0].name.toLocaleLowerCase()}s`;
+
+  return (
+    <div {...rest}>
+      <div className={styles.top}>
+        <div className={classNames(styles.titleContainer, styles.title)}>{message}</div>
+        {localVersion && (
+          <MenuLinkItem
+            href={'?'}
+            active={currentVersion === LOCAL_VERSION}
+            className={classNames(styles.versionRow, styles.localVersion)}
+          >
+            <div className={styles.version}>
+              <UserAvatar size={24} account={{}} className={styles.versionUserAvatar} />
+              <span className={styles.versionName}>{LOCAL_VERSION}</span>
+            </div>
+          </MenuLinkItem>
+        )}
+      </div>
+      <div className={classNames(multipleTabs && styles.tabs)}>
+        {multipleTabs &&
+          tabs.map(({ name }, index) => {
+            return (
+              <Tab
+                className={styles.tab}
+                key={name}
+                isActive={activeTabIndex === index}
+                onClick={() => setActiveTab(index)}
+              >
+                {name}
+              </Tab>
+            );
+          })}
+      </div>
+      <div className={styles.versionContainer}>
+        {tabs[activeTabIndex].name === 'LANE' &&
+          tabs[activeTabIndex].payload.map((payload) => (
+            <LaneInfo key={payload.id} currentLane={currentLane} {...payload}></LaneInfo>
+          ))}
+        {tabs[activeTabIndex].name !== 'LANE' &&
+          tabs[activeTabIndex].payload.map((payload, index) => (
+            <VersionInfo
+              ref={index === tabs[activeTabIndex].payload.length - 1 ? ref : null}
+              key={payload.version}
+              currentVersion={currentVersion}
+              latestVersion={latestVersion}
+              overrideVersionHref={overrideVersionHref}
+              showDetails={showVersionDetails}
+              {...payload}
+            ></VersionInfo>
+          ))}
+      </div>
+    </div>
+  );
+}
+
 export type VersionDropdownProps = {
   tags: DropdownComponentVersion[];
   snaps?: DropdownComponentVersion[];
-  lanes?: LaneModel[];
   localVersion?: boolean;
   currentVersion: string;
   currentLane?: LaneModel;
@@ -33,35 +108,61 @@ export type VersionDropdownProps = {
   showVersionDetails?: boolean;
   disabled?: boolean;
   placeholderComponent?: ReactNode;
+  activeTabIndex?: number;
+  setActiveTabIndex: (index: number) => void;
+  tabs: Array<{ name: string; payload: Array<any> }>;
 } & React.HTMLAttributes<HTMLDivElement>;
 
-export function VersionDropdown({
-  snaps,
-  tags,
-  lanes,
-  currentVersion,
-  latestVersion,
-  localVersion,
-  loading,
-  currentLane,
-  overrideVersionHref,
-  className,
-  placeholderClassName,
-  dropdownClassName,
-  menuClassName,
-  showVersionDetails,
-  disabled,
-  placeholderComponent = (
-    <SimpleVersion
-      disabled={disabled}
-      snaps={snaps}
-      tags={tags}
-      className={placeholderClassName}
-      currentVersion={currentVersion}
-    />
-  ),
-  ...rest
-}: VersionDropdownProps) {
+/**
+ * 
+ * @param param0 
+  const getActiveTabIndex = () => {
+    if (currentLane?.components.some((c) => c.version === currentVersion))
+      return tabs.findIndex((tab) => tab.name === 'LANE');
+    if ((snaps || []).some((snap) => snap.version === currentVersion))
+      return tabs.findIndex((tab) => tab.name === 'SNAP');
+    return 0;
+  };
+
+  const [activeTabIndex, setActiveTab] = useState<number>(getActiveTabIndex());
+
+ * @returns 
+ */
+
+export const VersionDropdown = React.memo(React.forwardRef<HTMLDivElement, VersionDropdownProps>(_VersionDropdown));
+
+function _VersionDropdown(
+  {
+    snaps,
+    tags,
+    currentVersion,
+    latestVersion,
+    localVersion,
+    loading,
+    currentLane,
+    overrideVersionHref,
+    className,
+    placeholderClassName,
+    dropdownClassName,
+    menuClassName,
+    showVersionDetails,
+    disabled,
+    activeTabIndex = 0,
+    setActiveTabIndex,
+    tabs,
+    placeholderComponent = (
+      <SimpleVersion
+        disabled={disabled}
+        snaps={snaps}
+        tags={tags}
+        className={placeholderClassName}
+        currentVersion={currentVersion}
+      />
+    ),
+    ...rest
+  }: VersionDropdownProps,
+  ref?: React.ForwardedRef<HTMLDivElement>
+) {
   const [key, setKey] = useState(0);
 
   const singleVersion = (snaps || []).concat(tags).length < 2 && !localVersion;
@@ -88,11 +189,12 @@ export function VersionDropdown({
         {loading && <LineSkeleton className={styles.loading} count={6} />}
         {loading || (
           <VersionMenu
+            ref={ref}
             className={menuClassName}
+            tabs={tabs}
             key={key}
-            tags={tags}
-            snaps={snaps}
-            lanes={lanes}
+            activeTabIndex={activeTabIndex}
+            setActiveTab={setActiveTabIndex}
             currentVersion={currentVersion}
             latestVersion={latestVersion}
             localVersion={localVersion}
@@ -107,106 +209,13 @@ export function VersionDropdown({
 }
 
 type VersionMenuProps = {
-  tags?: DropdownComponentVersion[];
-  snaps?: DropdownComponentVersion[];
-  lanes?: LaneModel[];
   localVersion?: boolean;
   currentVersion?: string;
   latestVersion?: string;
   currentLane?: LaneModel;
   overrideVersionHref?: (version: string) => string;
   showVersionDetails?: boolean;
+  activeTabIndex: number;
+  setActiveTab: (index: number) => void;
+  tabs: Array<{ name: string; payload: Array<any> }>;
 } & React.HTMLAttributes<HTMLDivElement>;
-
-const VERSION_TAB_NAMES = ['TAG', 'SNAP', 'LANE'] as const;
-
-function VersionMenu({
-  tags,
-  snaps,
-  lanes,
-  currentVersion,
-  localVersion,
-  latestVersion,
-  currentLane,
-  overrideVersionHref,
-  showVersionDetails,
-  ...rest
-}: VersionMenuProps) {
-  const tabs = VERSION_TAB_NAMES.map((name) => {
-    switch (name) {
-      case 'SNAP':
-        return { name, payload: snaps || [] };
-      case 'LANE':
-        return { name, payload: lanes || [] };
-      default:
-        return { name, payload: tags || [] };
-    }
-  }).filter((tab) => tab.payload.length > 0);
-
-  const getActiveTabIndex = () => {
-    if (currentLane?.components.some((c) => c.version === currentVersion))
-      return tabs.findIndex((tab) => tab.name === 'LANE');
-    if ((snaps || []).some((snap) => snap.version === currentVersion))
-      return tabs.findIndex((tab) => tab.name === 'SNAP');
-    return 0;
-  };
-
-  const [activeTabIndex, setActiveTab] = useState<number>(getActiveTabIndex());
-
-  const multipleTabs = tabs.length > 1;
-  const message = multipleTabs
-    ? 'Switch to view tags, snaps, or lanes'
-    : `Switch between ${tabs[0].name.toLocaleLowerCase()}s`;
-
-  return (
-    <div {...rest}>
-      <div className={styles.top}>
-        <div className={classNames(styles.titleContainer, styles.title)}>{message}</div>
-        {localVersion && (
-          <MenuLinkItem
-            href={'?'}
-            active={currentVersion === LOCAL_VERSION}
-            className={classNames(styles.versionRow, styles.localVersion)}
-          >
-            <div className={styles.version}>
-              <UserAvatar size={24} account={{}} className={styles.versionUserAvatar} />
-              <span className={styles.versionName}>{LOCAL_VERSION}</span>
-            </div>
-          </MenuLinkItem>
-        )}
-      </div>
-      <div className={classNames(multipleTabs && styles.tabs)}>
-        {multipleTabs &&
-          tabs.map(({ name }, index) => {
-            return (
-              <Tab
-                className={styles.tab}
-                key={name}
-                isActive={activeTabIndex === index}
-                onClick={() => setActiveTab(index)}
-              >
-                {name}
-              </Tab>
-            );
-          })}
-      </div>
-      <div className={styles.versionContainer}>
-        {tabs[activeTabIndex].name === 'LANE' &&
-          tabs[activeTabIndex].payload.map((payload) => (
-            <LaneInfo key={payload.id} currentLane={currentLane} {...payload}></LaneInfo>
-          ))}
-        {tabs[activeTabIndex].name !== 'LANE' &&
-          tabs[activeTabIndex].payload.map((payload) => (
-            <VersionInfo
-              key={payload.version}
-              currentVersion={currentVersion}
-              latestVersion={latestVersion}
-              overrideVersionHref={overrideVersionHref}
-              showDetails={showVersionDetails}
-              {...payload}
-            ></VersionInfo>
-          ))}
-      </div>
-    </div>
-  );
-}
diff --git a/scopes/component/ui/version-dropdown/version-info/version-info.tsx b/scopes/component/ui/version-dropdown/version-info/version-info.tsx
index 85bff8c4a549..10f340bc39ad 100644
--- a/scopes/component/ui/version-dropdown/version-info/version-info.tsx
+++ b/scopes/component/ui/version-dropdown/version-info/version-info.tsx
@@ -16,18 +16,22 @@ export type VersionInfoProps = DropdownComponentVersion & {
   showDetails?: boolean;
 };
 
-export function VersionInfo({
-  version,
-  currentVersion,
-  latestVersion,
-  date,
-  username,
-  email,
-  overrideVersionHref,
-  showDetails,
-  message,
-  tag,
-}: VersionInfoProps) {
+export const VersionInfo = React.memo(React.forwardRef<HTMLDivElement, VersionInfoProps>(_VersionInfo));
+function _VersionInfo(
+  {
+    version,
+    currentVersion,
+    latestVersion,
+    date,
+    username,
+    email,
+    overrideVersionHref,
+    showDetails,
+    message,
+    tag,
+  }: VersionInfoProps,
+  ref?: React.ForwardedRef<HTMLDivElement>
+) {
   const isCurrent = version === currentVersion;
   const author = useMemo(() => {
     return {
@@ -38,6 +42,7 @@ export function VersionInfo({
 
   const timestamp = useMemo(() => (date ? new Date(parseInt(date)).toString() : new Date().toString()), [date]);
   const currentVersionRef = useRef<HTMLDivElement>(null);
+
   useEffect(() => {
     if (isCurrent) {
       currentVersionRef.current?.scrollIntoView({ block: 'nearest' });
@@ -47,7 +52,7 @@ export function VersionInfo({
   const href = overrideVersionHref ? overrideVersionHref(version) : `?version=${version}`;
 
   return (
-    <div ref={currentVersionRef}>
+    <div ref={ref || currentVersionRef}>
       <MenuLinkItem active={isCurrent} href={href} className={styles.versionRow}>
         <div className={styles.version}>
           <UserAvatar size={24} account={author} className={styles.versionUserAvatar} showTooltip={true} />

From 3ae0466ff799141067aec3fbafa58abdbd78095c Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Tue, 2 May 2023 18:10:45 -0400
Subject: [PATCH 04/20] import code view

---
 .bitmap                                       |  6 ++
 components/ui/code-view/code-view.module.scss | 44 +++++++++++
 components/ui/code-view/code-view.tsx         | 76 +++++++++++++++++++
 components/ui/code-view/index.ts              |  2 +
 .../ui/version-dropdown/version-dropdown.tsx  | 16 ----
 workspace.jsonc                               |  1 -
 6 files changed, 128 insertions(+), 17 deletions(-)
 create mode 100644 components/ui/code-view/code-view.module.scss
 create mode 100644 components/ui/code-view/code-view.tsx
 create mode 100644 components/ui/code-view/index.ts

diff --git a/.bitmap b/.bitmap
index 50e850705531..633fb1b1214a 100644
--- a/.bitmap
+++ b/.bitmap
@@ -1329,6 +1329,12 @@
         "mainFile": "index.ts",
         "rootDir": "scopes/code/ui/code-tab-tree"
     },
+    "ui/code-view": {
+        "scope": "teambit.code",
+        "version": "5e2b49420c500d4d88e1b05aa232061abc0fa0a7",
+        "mainFile": "index.ts",
+        "rootDir": "components/ui/code-view"
+    },
     "ui/compare/lane-compare": {
         "scope": "teambit.lanes",
         "version": "0.0.77",
diff --git a/components/ui/code-view/code-view.module.scss b/components/ui/code-view/code-view.module.scss
new file mode 100644
index 000000000000..990a5fe021a0
--- /dev/null
+++ b/components/ui/code-view/code-view.module.scss
@@ -0,0 +1,44 @@
+.codeView {
+  padding: 24px 40px;
+  width: 100%;
+  overflow: hidden;
+  box-sizing: border-box;
+
+  .codeSnippetWrapper {
+    width: 100%;
+    max-width: 100%;
+    .codeSnippet {
+      display: block;
+      overflow: auto;
+      height: 100vh;
+      > code {
+        > code {
+          // this is to design the line numbers culumn
+          border-right: 1px solid #323232;
+          padding-right: 16px !important;
+          margin-right: 16px;
+        }
+      }
+    }
+    // TODO - fix this in code snippet component. it breaks when the component renders in different places
+    > span {
+      top: 13px !important;
+    }
+  }
+}
+
+.img {
+  width: 20px;
+  margin-right: 10px;
+}
+
+.fileName {
+  display: flex;
+  align-items: baseline;
+}
+
+.emptyCodeView {
+  margin: auto;
+  text-align: center;
+  font-size: 24px;
+}
diff --git a/components/ui/code-view/code-view.tsx b/components/ui/code-view/code-view.tsx
new file mode 100644
index 000000000000..26dd854865f5
--- /dev/null
+++ b/components/ui/code-view/code-view.tsx
@@ -0,0 +1,76 @@
+import { H1 } from '@teambit/documenter.ui.heading';
+import classNames from 'classnames';
+import React, { HTMLAttributes, useMemo } from 'react';
+import { CodeSnippet } from '@teambit/documenter.ui.code-snippet';
+import { useFileContent } from '@teambit/code.ui.queries.get-file-content';
+import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-light';
+import markDownSyntax from 'react-syntax-highlighter/dist/esm/languages/prism/markdown';
+import { staticStorageUrl } from '@teambit/base-ui.constants.storage';
+import { ComponentID } from '@teambit/component';
+import styles from './code-view.module.scss';
+
+export type CodeViewProps = {
+  componentId: ComponentID;
+  currentFile?: string;
+  currentFileContent?: string;
+  icon?: string;
+  loading?: boolean;
+  codeSnippetClassName?: string;
+} & HTMLAttributes<HTMLDivElement>;
+
+SyntaxHighlighter.registerLanguage('md', markDownSyntax);
+
+export function CodeView({
+  className,
+  componentId,
+  currentFile,
+  icon,
+  currentFileContent,
+  codeSnippetClassName,
+  loading: loadingFromProps,
+}: CodeViewProps) {
+  const { fileContent: downloadedFileContent, loading: loadingFileContent } = useFileContent(
+    componentId,
+    currentFile,
+    !!currentFileContent
+  );
+  const loading = loadingFromProps || loadingFileContent;
+  const fileContent = currentFileContent || downloadedFileContent;
+  const title = useMemo(() => currentFile?.split('/').pop(), [currentFile]);
+  const lang = useMemo(() => {
+    const langFromFileEnding = currentFile?.split('.').pop();
+
+    // for some reason, SyntaxHighlighter doesnt support scss or sass highlighting, only css. I need to check how to fix this properly
+    if (langFromFileEnding === 'scss' || langFromFileEnding === 'sass') return 'css';
+    if (langFromFileEnding === 'mdx') return 'md';
+    return langFromFileEnding;
+  }, [fileContent]);
+
+  if (!fileContent && !loading && currentFile) return <EmptyCodeView />;
+
+  return (
+    <div className={classNames(styles.codeView, className)}>
+      <H1 size="sm" className={styles.fileName}>
+        {currentFile && <img className={styles.img} src={icon} />}
+        <span>{title}</span>
+      </H1>
+      <CodeSnippet
+        className={styles.codeSnippetWrapper}
+        frameClass={classNames(styles.codeSnippet, codeSnippetClassName)}
+        showLineNumbers
+        language={lang}
+      >
+        {fileContent || ''}
+      </CodeSnippet>
+    </div>
+  );
+}
+
+function EmptyCodeView() {
+  return (
+    <div className={styles.emptyCodeView}>
+      <img src={`${staticStorageUrl}/harmony/empty-code-view.svg`} />
+      <div>Nothing to show</div>
+    </div>
+  );
+}
diff --git a/components/ui/code-view/index.ts b/components/ui/code-view/index.ts
new file mode 100644
index 000000000000..3dd57f347c74
--- /dev/null
+++ b/components/ui/code-view/index.ts
@@ -0,0 +1,2 @@
+export { CodeView } from './code-view';
+export type { CodeViewProps } from './code-view';
diff --git a/scopes/component/ui/version-dropdown/version-dropdown.tsx b/scopes/component/ui/version-dropdown/version-dropdown.tsx
index baa50028b101..135405a69e66 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown.tsx
+++ b/scopes/component/ui/version-dropdown/version-dropdown.tsx
@@ -113,22 +113,6 @@ export type VersionDropdownProps = {
   tabs: Array<{ name: string; payload: Array<any> }>;
 } & React.HTMLAttributes<HTMLDivElement>;
 
-/**
- * 
- * @param param0 
-  const getActiveTabIndex = () => {
-    if (currentLane?.components.some((c) => c.version === currentVersion))
-      return tabs.findIndex((tab) => tab.name === 'LANE');
-    if ((snaps || []).some((snap) => snap.version === currentVersion))
-      return tabs.findIndex((tab) => tab.name === 'SNAP');
-    return 0;
-  };
-
-  const [activeTabIndex, setActiveTab] = useState<number>(getActiveTabIndex());
-
- * @returns 
- */
-
 export const VersionDropdown = React.memo(React.forwardRef<HTMLDivElement, VersionDropdownProps>(_VersionDropdown));
 
 function _VersionDropdown(
diff --git a/workspace.jsonc b/workspace.jsonc
index 4a7bc51afe8f..25eaf6c865ce 100644
--- a/workspace.jsonc
+++ b/workspace.jsonc
@@ -82,7 +82,6 @@
         "@teambit/bvm.config": "0.2.2",
         "@teambit/capsule": "0.0.12",
         "@teambit/code.ui.code-compare-section": "^0.0.5",
-        "@teambit/code.ui.code-view": "^0.0.508",
         "@teambit/code.ui.dependency-tree": "^0.0.546",
         "@teambit/code.ui.hooks.use-code-params": "^0.0.496",
         "@teambit/code.ui.object-formatter": "0.0.1",

From 7a0f3462b442bb881c9d08ad1c001d827276f428 Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Wed, 3 May 2023 19:28:58 -0400
Subject: [PATCH 05/20] lazy load snaps and tags separately in a single query +
 optimize component query

---
 scopes/component/component/ui/component.tsx   |   2 +-
 scopes/component/component/ui/menu/menu.tsx   | 160 +++------
 .../component/ui/use-component-query.ts       | 322 +++++++++++++++---
 .../component/component/ui/use-component.tsx  |  17 +-
 .../ui/version-dropdown/version-dropdown.tsx  |  80 ++++-
 5 files changed, 398 insertions(+), 183 deletions(-)

diff --git a/scopes/component/component/ui/component.tsx b/scopes/component/component/ui/component.tsx
index 8c1763612736..99e03dfc9c96 100644
--- a/scopes/component/component/ui/component.tsx
+++ b/scopes/component/component/ui/component.tsx
@@ -54,7 +54,7 @@ export function Component({
   const componentId = _componentIdStr ? ComponentID.fromString(_componentIdStr) : undefined;
   const resolvedComponentIdStr = path || idFromLocation;
   const useComponentOptions = {
-    logFilters: useComponentFilters?.() || { log: { logOffset: 0, logLimit: 2 } },
+    logFilters: useComponentFilters?.() || { log: { logOffset: 0, logLimit: 10 } },
     customUseComponent: useComponent,
   };
 
diff --git a/scopes/component/component/ui/menu/menu.tsx b/scopes/component/component/ui/menu/menu.tsx
index 914becf5235f..37919172bea5 100644
--- a/scopes/component/component/ui/menu/menu.tsx
+++ b/scopes/component/component/ui/menu/menu.tsx
@@ -1,17 +1,15 @@
 import { Routes, Route } from 'react-router-dom';
 import { MainDropdown, MenuItemSlot } from '@teambit/ui-foundation.ui.main-dropdown';
 import { VersionDropdown } from '@teambit/component.ui.version-dropdown';
-import { FullLoader } from '@teambit/ui-foundation.ui.full-loader';
 import type { ConsumeMethod } from '@teambit/ui-foundation.ui.use-box.menu';
 import { useLocation } from '@teambit/base-react.navigation.link';
-import { flatten, groupBy, compact, isFunction } from 'lodash';
+import { flatten, groupBy, isFunction } from 'lodash';
 import classnames from 'classnames';
 import React, { useMemo } from 'react';
 import { UseBoxDropdown } from '@teambit/ui-foundation.ui.use-box.dropdown';
 import { useLanes } from '@teambit/lanes.hooks.use-lanes';
 import { LaneModel } from '@teambit/lanes.ui.models.lanes-model';
 import { Menu as ConsumeMethodsMenu } from '@teambit/ui-foundation.ui.use-box.menu';
-import { LegacyComponentLog } from '@teambit/legacy-component-log';
 import type { ComponentModel } from '../component-model';
 import { useComponent as useComponentQuery, UseComponentType } from '../use-component';
 import { CollapsibleMenuNav } from './menu-nav';
@@ -20,6 +18,7 @@ import { OrderedNavigationSlot, ConsumeMethodSlot } from './nav-plugin';
 import { useIdFromLocation } from '../use-component-from-location';
 import { ComponentID } from '../..';
 import { Filters } from '../use-component-query';
+import { LegacyComponentLog } from '@teambit/legacy-component-log';
 
 export type MenuProps = {
   className?: string;
@@ -83,73 +82,42 @@ export function ComponentMenu({
   const _componentIdStr = getComponentIdStr(componentIdStr);
   const componentId = _componentIdStr ? ComponentID.fromString(_componentIdStr) : undefined;
   const resolvedComponentIdStr = path || idFromLocation;
-
+  const componentFiltersFromProps = useComponentFilters?.() || {};
   const useComponentOptions = {
-    logFilters: useComponentFilters?.() || {
-      log: {
-        logLimit: 20,
-      },
-    },
-    customUseComponent: useComponent,
-  };
-
-  const snapLogOptions = {
-    ...useComponentOptions,
     logFilters: {
-      ...useComponentOptions.logFilters,
-      log: {
-        ...useComponentOptions.logFilters.log,
-        type: 'snap',
+      snapLog: {
+        logLimit: 10,
       },
-    },
-  };
-  const tagLogOptions = {
-    ...useComponentOptions,
-    logFilters: {
-      ...useComponentOptions.logFilters,
-      log: {
-        ...useComponentOptions.logFilters.log,
-        type: 'tag',
+      tagLog: {
+        logLimit: 10,
       },
+      fetchLogsByTypeSeparately: true,
+      ...componentFiltersFromProps,
     },
+    customUseComponent: useComponent,
   };
 
-  const {
-    component: componentTags,
-    loadMoreLogs: loadMoreTags,
-    hasMoreLogs: hasMoreTags,
-    loading: loadingTags,
-  } = useComponentQuery(host, componentId?.toString() || idFromLocation, tagLogOptions);
-
-  const {
-    component: componentSnaps,
-    loadMoreLogs: loadMoreSnaps,
-    hasMoreLogs: hasMoreSnaps,
-    loading: loadingSnaps,
-  } = useComponentQuery(host, componentId?.toString() || idFromLocation, snapLogOptions);
+  const { component, loading, loadMoreTags, loadMoreSnaps, hasMoreTags, hasMoreSnaps, snaps, tags } = useComponentQuery(
+    host,
+    componentId?.toString() || idFromLocation,
+    useComponentOptions
+  );
 
   const mainMenuItems = useMemo(() => groupBy(flatten(menuItemSlot.values()), 'category'), [menuItemSlot]);
 
-  if (loadingTags || loadingSnaps) return <FullLoader />;
-  if (!componentSnaps || !componentTags) {
-    // eslint-disable-next-line no-console
-    console.error(`failed loading component tags/snaps for ${idFromLocation}`);
-    return null;
-  }
-
   const RightSide = (
     <div className={styles.rightSide}>
       {RightNode || (
         <>
           <VersionRelatedDropdowns
-            componentSnaps={componentSnaps}
-            componentTags={componentTags}
+            component={component}
+            snaps={snaps}
+            tags={tags}
             loadMoreSnaps={loadMoreSnaps}
             loadMoreTags={loadMoreTags}
             hasMoreSnaps={hasMoreSnaps}
             hasMoreTags={hasMoreTags}
-            loadingSnaps={loadingSnaps}
-            loadingTags={loadingTags}
+            loading={loading}
             consumeMethods={consumeMethodSlot}
             host={host}
           />
@@ -177,103 +145,59 @@ export function ComponentMenu({
 }
 
 export function VersionRelatedDropdowns({
-  componentTags,
-  componentSnaps,
+  component,
+  snaps: snapsFromProps = [],
+  tags: tagsFromProps = [],
   consumeMethods,
   loadMoreSnaps,
   loadMoreTags,
   hasMoreSnaps,
   hasMoreTags,
-  loadingSnaps,
-  loadingTags,
   className,
+  loading,
   host,
 }: {
-  componentTags: ComponentModel;
-  componentSnaps: ComponentModel;
+  component?: ComponentModel;
+  tags?: LegacyComponentLog[];
+  snaps?: LegacyComponentLog[];
   loadMoreTags?: () => void;
   loadMoreSnaps?: () => void;
-  loadingSnaps?: boolean;
-  loadingTags?: boolean;
   hasMoreTags?: boolean;
   hasMoreSnaps?: boolean;
+  loading?: boolean;
   consumeMethods?: ConsumeMethodSlot;
   className?: string;
   host: string;
 }) {
   const location = useLocation();
-  const loading = loadingSnaps || loadingTags;
   const { lanesModel } = useLanes();
-  const component = componentTags || componentSnaps;
   const viewedLane =
     lanesModel?.viewedLane?.id && !lanesModel?.viewedLane?.id.isDefault() ? lanesModel.viewedLane : undefined;
-  // const { logs } = component;
   const isWorkspace = host === 'teambit.workspace/workspace';
 
   const snaps = useMemo(() => {
-    return (componentSnaps.logs || []).filter((log) => !log.tag).map((snap) => ({ ...snap, version: snap.hash }));
-  }, [componentSnaps.logs]);
+    return (snapsFromProps || []).map((snap) => ({ ...snap, version: snap.hash }));
+  }, [snapsFromProps]);
 
   const tags = useMemo(() => {
-    return (componentTags.logs || []).map((tag) => ({ ...tag, version: tag.tag as string }));
-  }, [componentTags.logs]);
+    return (tagsFromProps || []).map((tag) => ({ ...tag, version: tag.tag as string }));
+  }, [tagsFromProps]);
 
   const isNew = snaps.length === 0 && tags.length === 0;
 
-  const lanes = lanesModel?.getLanesByComponentId(component.id)?.filter((lane) => !lane.id.isDefault()) || [];
+  const lanes = component?.id
+    ? lanesModel?.getLanesByComponentId(component.id)?.filter((lane) => !lane.id.isDefault()) || []
+    : [];
   const localVersion = isWorkspace && !isNew && (!viewedLane || lanesModel?.isViewingCurrentLane());
 
   const currentVersion =
-    isWorkspace && !isNew && !location?.search.includes('version') ? 'workspace' : component.version;
-  const VERSION_TAB_NAMES = ['TAG', 'SNAP', 'LANE'] as const;
-  const tabs = VERSION_TAB_NAMES.map((name) => {
-    switch (name) {
-      case 'SNAP':
-        return { name, payload: snaps || [] };
-      case 'LANE':
-        return { name, payload: lanes || [] };
-      default:
-        return { name, payload: tags || [] };
-    }
-  }).filter((tab) => tab.payload.length > 0);
+    isWorkspace && !isNew && !location?.search.includes('version') ? 'workspace' : component?.version ?? '';
 
   const methods = useConsumeMethods(component, consumeMethods, viewedLane);
 
-  const getActiveTabIndex = () => {
-    if (viewedLane?.components.some((c) => c.version === currentVersion))
-      return tabs.findIndex((tab) => tab.name === 'LANE');
-    if ((snaps || []).some((snap) => snap.version === currentVersion))
-      return tabs.findIndex((tab) => tab.name === 'SNAP');
-    return 0;
-  };
-
-  const [activeTabIndex, setActiveTab] = React.useState<number>(getActiveTabIndex());
-  const activeTabOrSnap: 'SNAP' | 'TAG' | 'LANE' = tabs[activeTabIndex]?.name || tabs[0].name;
-  const hasMore = activeTabOrSnap === 'SNAP' ? hasMoreSnaps : hasMoreTags;
-  const observer = React.useRef<IntersectionObserver>();
-
-  const handleLoadMore = React.useCallback(() => {
-    if (activeTabOrSnap === 'SNAP') loadMoreSnaps?.();
-    if (activeTabOrSnap === 'TAG') loadMoreTags?.();
-  }, [activeTabIndex, tabs.length]);
-
-  const lastLogRef = React.useCallback(
-    (node) => {
-      if (loading) return;
-      if (observer.current) observer.current.disconnect();
-      observer.current = new IntersectionObserver((entries) => {
-        if (entries[0].isIntersecting && hasMore) {
-          handleLoadMore();
-        }
-      });
-      if (node) observer.current.observe(node);
-    },
-    [loading, hasMoreSnaps, hasMoreTags, activeTabIndex]
-  );
-
   return (
     <>
-      {consumeMethods && tags.length > 0 && (
+      {consumeMethods && tags.length > 0 && component?.id && (
         <UseBoxDropdown
           position="bottom-end"
           className={classnames(styles.useBox, styles.hideOnMobile)}
@@ -281,15 +205,17 @@ export function VersionRelatedDropdowns({
         />
       )}
       <VersionDropdown
-        ref={lastLogRef}
         tags={tags}
         snaps={snaps}
-        tabs={tabs}
-        activeTabIndex={activeTabIndex}
-        setActiveTabIndex={setActiveTab}
+        lanes={lanes}
+        loading={loading}
+        loadMoreTags={loadMoreTags}
+        loadMoreSnaps={loadMoreSnaps}
+        hasMoreTags={hasMoreTags}
+        hasMoreSnaps={hasMoreSnaps}
         localVersion={localVersion}
         currentVersion={currentVersion}
-        latestVersion={component.latest}
+        latestVersion={component?.latest}
         currentLane={viewedLane}
         className={className}
         menuClassName={styles.componentVersionMenu}
diff --git a/scopes/component/component/ui/use-component-query.ts b/scopes/component/component/ui/use-component-query.ts
index f7c21d0e27a2..2bb658977952 100644
--- a/scopes/component/component/ui/use-component-query.ts
+++ b/scopes/component/component/ui/use-component-query.ts
@@ -3,6 +3,7 @@ import { gql } from '@apollo/client';
 import { useDataQuery } from '@teambit/ui-foundation.ui.hooks.use-data-query';
 import { ComponentID, ComponentIdObj } from '@teambit/component-id';
 import { ComponentDescriptor } from '@teambit/component-descriptor';
+import { LegacyComponentLog } from '@teambit/legacy-component-log';
 import { ComponentModel } from './component-model';
 import { ComponentError } from './component-error';
 
@@ -77,7 +78,46 @@ export const componentFields = gql`
     tags {
       version
     }
-    logs(type: $logType, offset: $logOffset, limit: $logLimit, head: $logHead, sort: $logSort) {
+    logs(
+      type: $logType
+      offset: $logOffset
+      limit: $logLimit
+      sort: $logSort
+      takeHeadFromComponent: $takeHeadFromComponent
+      head: $logHead
+    ) @skip(if: $fetchLogsByTypeSeparately) {
+      id
+      message
+      username
+      email
+      date
+      hash
+      tag
+    }
+    tagLogs: logs(
+      type: "tag"
+      offset: $tagLogOffset
+      limit: $tagLogLimit
+      sort: $tagLogSort
+      takeHeadFromComponent: $tagTakeHeadFromComponent
+      head: $tagLogHead
+    ) @include(if: $fetchLogsByTypeSeparately) {
+      id
+      message
+      username
+      email
+      date
+      hash
+      tag
+    }
+    snapLogs: logs(
+      type: "snap"
+      offset: $snapLogOffset
+      limit: $snapLogLimit
+      sort: $snapLogSort
+      takeHeadFromComponent: $snapTakeHeadFromComponent
+      head: $snapLogHead
+    ) @include(if: $fetchLogsByTypeSeparately) {
       id
       message
       username
@@ -91,16 +131,31 @@ export const componentFields = gql`
   ${componentOverviewFields}
 `;
 
-const GET_COMPONENT = gql`
-  query Component(
-    $id: String!
-    $extensionId: String!
-    $logType: String
+const COMPONENT_QUERY_FIELDS = `
     $logOffset: Int
     $logLimit: Int
+    $logType: String
     $logHead: String
     $logSort: String
-  ) {
+    $tagLogOffset: Int
+    $tagLogLimit: Int
+    $tagLogHead: String
+    $tagLogSort: String
+    $snapLogOffset: Int
+    $snapLogLimit: Int
+    $snapLogHead: String
+    $snapLogSort: String
+    $takeHeadFromComponent: Boolean
+    $tagTakeHeadFromComponent: Boolean
+    $snapTakeHeadFromComponent: Boolean
+    $fetchLogsByTypeSeparately: Boolean!`;
+
+const GET_COMPONENT = gql`
+  query Component(
+    ${COMPONENT_QUERY_FIELDS} 
+    $extensionId: String!
+    $id: String!
+    ) {
     getHost(id: $extensionId) {
       id # used for GQL caching
       get(id: $id) {
@@ -112,7 +167,7 @@ const GET_COMPONENT = gql`
 `;
 
 const SUB_SUBSCRIPTION_ADDED = gql`
-  subscription OnComponentAdded($logType: String, $logOffset: Int, $logLimit: Int, $logHead: String, $logSort: String) {
+  subscription OnComponentAdded(${COMPONENT_QUERY_FIELDS}) {
     componentAdded {
       component {
         ...componentFields
@@ -123,13 +178,7 @@ const SUB_SUBSCRIPTION_ADDED = gql`
 `;
 
 const SUB_COMPONENT_CHANGED = gql`
-  subscription OnComponentChanged(
-    $logType: String
-    $logOffset: Int
-    $logLimit: Int
-    $logHead: String
-    $logSort: String
-  ) {
+  subscription OnComponentChanged(${COMPONENT_QUERY_FIELDS}) {
     componentChanged {
       component {
         ...componentFields
@@ -149,57 +198,141 @@ const SUB_COMPONENT_REMOVED = gql`
   }
   ${componentIdFields}
 `;
+
+export type LogFilter = {
+  logOffset?: number;
+  logLimit?: number;
+  logHead?: string;
+  logSort?: string;
+  takeHeadFromComponent?: boolean;
+};
+
 export type Filters = {
-  log?: { logType?: string; logOffset?: number; logLimit?: number; logHead?: string; logSort?: string };
+  log?: LogFilter & { logType?: string };
+  tagLog?: LogFilter;
+  snapLog?: LogFilter;
+  fetchLogsByTypeSeparately?: boolean;
 };
 
 export type ComponentQueryResult = {
   component?: ComponentModel;
-  error?: ComponentError;
   componentDescriptor?: ComponentDescriptor;
-  loading?: boolean;
-  loadMoreLogs?: () => void;
   hasMoreLogs?: boolean;
+  hasMoreSnaps?: boolean;
+  hasMoreTags?: boolean;
+  loadMoreLogs?: () => void;
+  loadMoreTags?: () => void;
+  loadMoreSnaps?: () => void;
+  snaps?: LegacyComponentLog[];
+  tags?: LegacyComponentLog[];
+  loading?: boolean;
+  error?: ComponentError;
 };
 
 /** provides data to component ui page, making sure both variables and return value are safely typed and memoized */
 export function useComponentQuery(
   componentId: string,
   host: string,
-  filtersFromProps?: Filters,
+  filters?: Filters,
   skip?: boolean
 ): ComponentQueryResult {
   const idRef = useRef(componentId);
   idRef.current = componentId;
-  const filters = useMemo(() => filtersFromProps?.log || {}, [filtersFromProps]);
-  const { logLimit } = filters;
-  const offsetRef = useRef(filters.logOffset);
+
+  const { fetchLogsByTypeSeparately = false, log, tagLog, snapLog } = filters || {};
+  const {
+    logHead: tagLogHead,
+    logOffset: tagLogOffset,
+    logSort: tagLogSort,
+    logLimit: tagLogLimit,
+    takeHeadFromComponent: tagLogTakeHeadFromComponent,
+  } = tagLog || {};
+
+  const { logHead, logOffset, logSort, logLimit, takeHeadFromComponent, logType } = log || {};
+  const {
+    logHead: snapLogHead,
+    logOffset: snapLogOffset,
+    logSort: snapLogSort,
+    logLimit: snapLogLimit,
+    takeHeadFromComponent: snapLogTakeHeadFromComponent,
+  } = snapLog || {};
+  const variables = {
+    id: componentId,
+    extensionId: host,
+    fetchLogsByTypeSeparately,
+    snapLogOffset: snapLogOffset ?? snapLogLimit ? 0 : undefined,
+    tagLogOffset: tagLogOffset ?? tagLogLimit ? 0 : undefined,
+    logOffset: logOffset ?? logLimit ? 0 : undefined,
+    logLimit,
+    snapLogLimit,
+    tagLogLimit,
+    logType,
+    logHead,
+    snapLogHead,
+    tagLogHead,
+    logSort,
+    snapLogSort,
+    tagLogSort,
+    takeHeadFromComponent,
+    snapLogTakeHeadFromComponent,
+    tagLogTakeHeadFromComponent,
+  };
+  const offsetRef = useRef(logOffset);
+  const tagOffsetRef = useRef(tagLogOffset);
+  const snapOffsetRef = useRef(snapLogOffset);
+  const hasMoreLogs = useRef<boolean | undefined>(undefined);
+  const hasMoreTagLogs = useRef<boolean | undefined>(undefined);
+  const hasMoreSnapLogs = useRef<boolean | undefined>(undefined);
   const { data, error, loading, subscribeToMore, fetchMore, ...rest } = useDataQuery(GET_COMPONENT, {
-    variables: { id: componentId, extensionId: host, ...filters },
+    variables,
     skip,
     errorPolicy: 'all',
   });
 
   const rawComponent = data?.getHost?.get;
-  const rawCompLogs = rawComponent?.logs ?? [];
-
+  const rawTags = rawComponent?.tagLogs ?? [];
+  const rawSnaps = rawComponent?.snapLogs ?? [];
+  const rawCompLogs = rawComponent?.logs ?? mergeLogs(rawTags, rawSnaps);
   offsetRef.current = useMemo(() => {
     const currentOffset = offsetRef.current;
     if (!currentOffset) return rawCompLogs.length;
-    if (currentOffset < rawCompLogs?.length) {
-      return rawCompLogs?.length;
-    }
     return offsetRef.current;
   }, [rawCompLogs]);
 
-  const hasMoreLogs = React.useRef<boolean | undefined>(undefined);
+  tagOffsetRef.current = useMemo(() => {
+    if (!fetchLogsByTypeSeparately) return offsetRef.current;
+    const currentOffset = tagOffsetRef.current;
+    if (!currentOffset) return rawTags.length;
+    return tagOffsetRef.current;
+  }, [rawCompLogs]);
+
+  snapOffsetRef.current = useMemo(() => {
+    if (!fetchLogsByTypeSeparately) return offsetRef.current;
+    const currentOffset = snapOffsetRef.current;
+    if (!currentOffset) return rawSnaps.length;
+    return snapOffsetRef.current;
+  }, [rawSnaps]);
 
   hasMoreLogs.current = useMemo(() => {
     if (!logLimit) return false;
     if (rawComponent === undefined) return undefined;
-    if (hasMoreLogs.current === undefined) return rawComponent?.logs.length === logLimit;
+    if (hasMoreLogs.current === undefined) return rawComponent?.logs.length >= logLimit;
     return hasMoreLogs.current;
-  }, [rawComponent?.logs]);
+  }, [rawCompLogs]);
+
+  hasMoreTagLogs.current = useMemo(() => {
+    if (!tagLogLimit) return false;
+    if (rawComponent === undefined) return undefined;
+    if (hasMoreTagLogs.current === undefined) return rawComponent?.tagLogs.length >= tagLogLimit;
+    return hasMoreTagLogs.current;
+  }, [rawTags]);
+
+  hasMoreSnapLogs.current = useMemo(() => {
+    if (!snapLogLimit) return false;
+    if (rawComponent === undefined) return undefined;
+    if (hasMoreSnapLogs.current === undefined) return rawComponent?.snapLogs.length === snapLogLimit;
+    return hasMoreSnapLogs.current;
+  }, [rawSnaps]);
 
   const loadMoreLogs = React.useCallback(async () => {
     if (logLimit) {
@@ -212,14 +345,15 @@ export function useComponentQuery(
 
           const prevComponent = prev.getHost.get;
           const fetchedComponent = fetchMoreResult.getHost.get;
-
           if (fetchedComponent && ComponentID.isEqualObj(prevComponent.id, fetchedComponent.id)) {
-            const updatedLogs = [...prevComponent.logs, ...fetchedComponent.logs];
-            hasMoreLogs.current = fetchedComponent.logs.length === logLimit;
+            const updatedLogs = mergeLogs(prevComponent.logs, fetchedComponent.logs);
+            hasMoreLogs.current = fetchedComponent.logs.length >= logLimit;
+            if (updatedLogs.length > prevComponent.logs.length) {
+              offsetRef.current = updatedLogs.length;
+            }
 
             return {
               ...prev,
-              hasMoreLogs: false,
               getHost: {
                 ...prev.getHost,
                 get: {
@@ -236,6 +370,81 @@ export function useComponentQuery(
     }
   }, [logLimit, fetchMore]);
 
+  const loadMoreTags = React.useCallback(async () => {
+    if (tagLogLimit) {
+      await fetchMore({
+        variables: {
+          tagLogOffset: tagOffsetRef.current,
+          tagLogLimit,
+        },
+        updateQuery: (prev, { fetchMoreResult }) => {
+          if (!fetchMoreResult) return prev;
+
+          const prevComponent = prev.getHost.get;
+          const fetchedComponent = fetchMoreResult.getHost.get;
+          const prevCompLogs = prevComponent.tagLogs;
+          if (fetchedComponent && ComponentID.isEqualObj(prevComponent.id, fetchedComponent.id)) {
+            const updatedTags = mergeLogs(prevCompLogs, fetchedComponent.tagLogs);
+            if (updatedTags.length > prevCompLogs.length) {
+              tagOffsetRef.current = updatedTags.length;
+            }
+            hasMoreTagLogs.current = fetchedComponent.tagLogs.length >= tagLogLimit;
+            return {
+              ...prev,
+              getHost: {
+                ...prev.getHost,
+                get: {
+                  ...prevComponent,
+                  tagLogs: updatedTags,
+                },
+              },
+            };
+          }
+
+          return prev;
+        },
+      });
+    }
+  }, [tagLogLimit, fetchMore]);
+
+  const loadMoreSnaps = React.useCallback(async () => {
+    if (snapLogLimit) {
+      await fetchMore({
+        variables: {
+          snapLogOffset: snapOffsetRef.current,
+          snapLogLimit,
+        },
+        updateQuery: (prev, { fetchMoreResult }) => {
+          if (!fetchMoreResult) return prev;
+
+          const prevComponent = prev.getHost.get;
+          const prevCompLogs = prevComponent.snapLogs ?? [];
+
+          const fetchedComponent = fetchMoreResult.getHost.get;
+          if (fetchedComponent && ComponentID.isEqualObj(prevComponent.id, fetchedComponent.id)) {
+            const updatedSnaps = mergeLogs(prevCompLogs, fetchedComponent.snapLogs);
+            if (updatedSnaps.length > prevCompLogs.length) {
+              snapOffsetRef.current = updatedSnaps.length;
+            }
+            hasMoreSnapLogs.current = fetchedComponent.snapLogs.length >= snapLogLimit;
+            return {
+              ...prev,
+              getHost: {
+                ...prev.getHost,
+                get: {
+                  ...prevComponent,
+                  snapLogs: updatedSnaps,
+                },
+              },
+            };
+          }
+
+          return prev;
+        },
+      });
+    }
+  }, [snapLogLimit, fetchMore]);
+
   useEffect(() => {
     // @TODO @Kutner fix subscription for scope
     if (host !== 'teambit.workspace/workspace') {
@@ -244,6 +453,9 @@ export function useComponentQuery(
 
     const unsubAddition = subscribeToMore({
       document: SUB_SUBSCRIPTION_ADDED,
+      variables: {
+        fetchLogsByTypeSeparately,
+      },
       updateQuery: (prev, { subscriptionData }) => {
         const prevComponent = prev?.getHost?.get;
         const addedComponent = subscriptionData?.data?.componentAdded?.component;
@@ -266,6 +478,9 @@ export function useComponentQuery(
 
     const unsubChanges = subscribeToMore({
       document: SUB_COMPONENT_CHANGED,
+      variables: {
+        fetchLogsByTypeSeparately,
+      },
       updateQuery: (prev, { subscriptionData }) => {
         if (!subscriptionData.data) return prev;
 
@@ -290,6 +505,9 @@ export function useComponentQuery(
 
     const unsubRemoval = subscribeToMore({
       document: SUB_COMPONENT_REMOVED,
+      variables: {
+        fetchLogsByTypeSeparately,
+      },
       updateQuery: (prev, { subscriptionData }) => {
         if (!subscriptionData.data) return prev;
 
@@ -334,8 +552,8 @@ export function useComponentQuery(
     error && !data ? new ComponentError(500, error.message) : !rawComponent && !loading && new ComponentError(404);
 
   const component = useMemo(
-    () => (rawComponent ? ComponentModel.from({ ...rawComponent, host }) : undefined),
-    [id?.toString(), rawComponent?.logs.length]
+    () => (rawComponent ? ComponentModel.from({ ...rawComponent, host, logs: rawCompLogs }) : undefined),
+    [id?.toString(), rawCompLogs]
   );
 
   const componentDescriptor = useMemo(() => {
@@ -352,6 +570,14 @@ export function useComponentQuery(
     return id ? ComponentDescriptor.fromObject({ id: id.toString(), aspectList }) : undefined;
   }, [id?.toString()]);
 
+  const snaps = useMemo(() => {
+    return rawComponent?.snapLogs;
+  }, [rawComponent?.snapLogs]);
+
+  const tags = useMemo(() => {
+    return rawComponent?.tagLogs;
+  }, [rawComponent?.tagLogs]);
+
   return useMemo(() => {
     return {
       componentDescriptor,
@@ -359,8 +585,26 @@ export function useComponentQuery(
       error: componentError || undefined,
       loading,
       loadMoreLogs,
+      loadMoreSnaps,
+      loadMoreTags,
+      hasMoreSnaps: hasMoreSnapLogs.current,
+      hasMoreTags: hasMoreTagLogs.current,
       hasMoreLogs: hasMoreLogs.current,
+      snaps,
+      tags,
       ...rest,
     };
-  }, [host, component, componentDescriptor, componentError, hasMoreLogs]);
+  }, [host, component, componentDescriptor, componentError, hasMoreLogs, hasMoreSnapLogs, hasMoreTagLogs, snaps, tags]);
+}
+
+interface Log {
+  id: string;
+  date: string;
+}
+
+function mergeLogs(logs1: Log[] = [], logs2: Log[] = []): Log[] {
+  const mergedLogs: Log[] = [];
+  logs1.forEach((log) => mergedLogs.push(log));
+  logs2.forEach((log) => mergedLogs.push(log));
+  return mergedLogs;
 }
diff --git a/scopes/component/component/ui/use-component.tsx b/scopes/component/component/ui/use-component.tsx
index 5166cb816776..cebbe47852fb 100644
--- a/scopes/component/component/ui/use-component.tsx
+++ b/scopes/component/component/ui/use-component.tsx
@@ -1,17 +1,6 @@
 import { useQuery } from '@teambit/ui-foundation.ui.react-router.use-query';
-import { ComponentDescriptor } from '@teambit/component-descriptor';
-import { ComponentModel } from './component-model';
-import { ComponentError } from './component-error';
-import { Filters, useComponentQuery } from './use-component-query';
+import { ComponentQueryResult, Filters, useComponentQuery } from './use-component-query';
 
-export type Component = {
-  component?: ComponentModel;
-  error?: ComponentError;
-  componentDescriptor?: ComponentDescriptor;
-  loading?: boolean;
-  loadMoreLogs?: () => void;
-  hasMoreLogs?: boolean;
-};
 export type UseComponentOptions = {
   version?: string;
   logFilters?: Filters;
@@ -19,9 +8,9 @@ export type UseComponentOptions = {
   skip?: boolean;
 };
 
-export type UseComponentType = (id: string, host: string, filters?: Filters, skip?: boolean) => Component;
+export type UseComponentType = (id: string, host: string, filters?: Filters, skip?: boolean) => ComponentQueryResult;
 
-export function useComponent(host: string, id?: string, options?: UseComponentOptions): Component {
+export function useComponent(host: string, id?: string, options?: UseComponentOptions): ComponentQueryResult {
   const query = useQuery();
   const { version, logFilters, customUseComponent, skip } = options || {};
   const componentVersion = (version || query.get('version')) ?? undefined;
diff --git a/scopes/component/ui/version-dropdown/version-dropdown.tsx b/scopes/component/ui/version-dropdown/version-dropdown.tsx
index 135405a69e66..68c26f53c4b7 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown.tsx
+++ b/scopes/component/ui/version-dropdown/version-dropdown.tsx
@@ -72,14 +72,14 @@ function _VersionMenu(
           })}
       </div>
       <div className={styles.versionContainer}>
-        {tabs[activeTabIndex].name === 'LANE' &&
-          tabs[activeTabIndex].payload.map((payload) => (
+        {tabs[activeTabIndex]?.name === 'LANE' &&
+          tabs[activeTabIndex]?.payload.map((payload) => (
             <LaneInfo key={payload.id} currentLane={currentLane} {...payload}></LaneInfo>
           ))}
-        {tabs[activeTabIndex].name !== 'LANE' &&
-          tabs[activeTabIndex].payload.map((payload, index) => (
+        {tabs[activeTabIndex]?.name !== 'LANE' &&
+          tabs[activeTabIndex]?.payload.map((payload, index) => (
             <VersionInfo
-              ref={index === tabs[activeTabIndex].payload.length - 1 ? ref : null}
+              ref={index === tabs[activeTabIndex]?.payload.length - 1 ? ref : null}
               key={payload.version}
               currentVersion={currentVersion}
               latestVersion={latestVersion}
@@ -96,6 +96,11 @@ function _VersionMenu(
 export type VersionDropdownProps = {
   tags: DropdownComponentVersion[];
   snaps?: DropdownComponentVersion[];
+  lanes?: LaneModel[];
+  loadMoreTags?: () => void;
+  loadMoreSnaps?: () => void;
+  hasMoreTags?: boolean;
+  hasMoreSnaps?: boolean;
   localVersion?: boolean;
   currentVersion: string;
   currentLane?: LaneModel;
@@ -108,9 +113,6 @@ export type VersionDropdownProps = {
   showVersionDetails?: boolean;
   disabled?: boolean;
   placeholderComponent?: ReactNode;
-  activeTabIndex?: number;
-  setActiveTabIndex: (index: number) => void;
-  tabs: Array<{ name: string; payload: Array<any> }>;
 } & React.HTMLAttributes<HTMLDivElement>;
 
 export const VersionDropdown = React.memo(React.forwardRef<HTMLDivElement, VersionDropdownProps>(_VersionDropdown));
@@ -119,6 +121,7 @@ function _VersionDropdown(
   {
     snaps,
     tags,
+    lanes,
     currentVersion,
     latestVersion,
     localVersion,
@@ -131,9 +134,10 @@ function _VersionDropdown(
     menuClassName,
     showVersionDetails,
     disabled,
-    activeTabIndex = 0,
-    setActiveTabIndex,
-    tabs,
+    loadMoreSnaps,
+    loadMoreTags,
+    hasMoreSnaps,
+    hasMoreTags,
     placeholderComponent = (
       <SimpleVersion
         disabled={disabled}
@@ -147,6 +151,58 @@ function _VersionDropdown(
   }: VersionDropdownProps,
   ref?: React.ForwardedRef<HTMLDivElement>
 ) {
+  const VERSION_TAB_NAMES = ['TAG', 'SNAP', 'LANE'] as const;
+
+  const tabs = VERSION_TAB_NAMES.map((name) => {
+    switch (name) {
+      case 'SNAP':
+        return { name, payload: snaps || [] };
+      case 'LANE':
+        return { name, payload: lanes || [] };
+      default:
+        return { name, payload: tags || [] };
+    }
+  }).filter((tab) => tab.payload.length > 0);
+
+  const getActiveTabIndex = () => {
+    if (currentLane?.components.some((c) => c.version === currentVersion))
+      return tabs.findIndex((tab) => tab.name === 'LANE');
+    if ((snaps || []).some((snap) => snap.version === currentVersion))
+      return tabs.findIndex((tab) => tab.name === 'SNAP');
+    return 0;
+  };
+
+  const [activeTabIndex, setActiveTabIndex] = React.useState<number>(getActiveTabIndex());
+
+  const activeTabOrSnap: 'SNAP' | 'TAG' | 'LANE' | undefined = tabs[activeTabIndex]?.name;
+  const hasMore = activeTabOrSnap === 'SNAP' ? !!hasMoreSnaps : activeTabOrSnap === 'TAG' && !!hasMoreTags;
+  const observer = React.useRef<IntersectionObserver>();
+
+  const handleLoadMore = React.useCallback(() => {
+    if (activeTabOrSnap === 'SNAP') loadMoreSnaps?.();
+    if (activeTabOrSnap === 'TAG') loadMoreTags?.();
+  }, [activeTabOrSnap, tabs.length]);
+
+  const lastLogRef = React.useCallback(
+    (node) => {
+      if (loading) return;
+      if (observer.current) observer.current.disconnect();
+      observer.current = new IntersectionObserver(
+        (entries) => {
+          if (entries[0].isIntersecting && hasMore) {
+            handleLoadMore();
+          }
+        },
+        {
+          threshold: 0.1,
+          rootMargin: '100px',
+        }
+      );
+      if (node) observer.current.observe(node);
+    },
+    [loading, hasMoreSnaps, hasMoreTags, handleLoadMore]
+  );
+
   const [key, setKey] = useState(0);
 
   const singleVersion = (snaps || []).concat(tags).length < 2 && !localVersion;
@@ -173,7 +229,7 @@ function _VersionDropdown(
         {loading && <LineSkeleton className={styles.loading} count={6} />}
         {loading || (
           <VersionMenu
-            ref={ref}
+            ref={ref || lastLogRef}
             className={menuClassName}
             tabs={tabs}
             key={key}

From c6b84912d6d7824f1a24d9997037451cd7766965 Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Thu, 4 May 2023 14:21:07 -0400
Subject: [PATCH 06/20] lazy load versions until dropdown is opened

---
 components/ui/code-view/code-view.module.scss |   2 +-
 .../changelog/ui/change-log-page.tsx          |   9 +-
 scopes/component/component/ui/component.tsx   |   7 +-
 scopes/component/component/ui/menu/menu.tsx   | 133 +++++------
 .../component/ui/use-component-query.ts       |  39 +--
 .../version-dropdown-placeholder.tsx          |  95 ++++----
 .../version-dropdown.composition.tsx          |  15 +-
 .../ui/version-dropdown/version-dropdown.tsx  | 226 ++++++++++--------
 8 files changed, 282 insertions(+), 244 deletions(-)

diff --git a/components/ui/code-view/code-view.module.scss b/components/ui/code-view/code-view.module.scss
index 990a5fe021a0..f5fe367a61fd 100644
--- a/components/ui/code-view/code-view.module.scss
+++ b/components/ui/code-view/code-view.module.scss
@@ -10,7 +10,7 @@
     .codeSnippet {
       display: block;
       overflow: auto;
-      height: 100vh;
+      height: calc(100vh - 200px);
       > code {
         > code {
           // this is to design the line numbers culumn
diff --git a/scopes/component/changelog/ui/change-log-page.tsx b/scopes/component/changelog/ui/change-log-page.tsx
index 0563fc356d62..c518c24aa956 100644
--- a/scopes/component/changelog/ui/change-log-page.tsx
+++ b/scopes/component/changelog/ui/change-log-page.tsx
@@ -16,19 +16,14 @@ type ChangeLogPageProps = {
 
 export function ChangeLogPage({ className, host }: ChangeLogPageProps) {
   const componentContext = React.useContext(ComponentContext);
-  const {
-    component,
-    loading,
-    loadMoreLogs,
-    hasMoreLogs: hasMore,
-  } = useComponent(host, componentContext?.id.toString(), {
+  const { component, loading, componentLogs } = useComponent(host, componentContext?.id.toString(), {
     logFilters: {
       log: {
         logLimit: 15,
       },
     },
   });
-
+  const { loadMoreLogs, hasMoreLogs: hasMore } = componentLogs || {};
   const logs = component?.logs ?? [];
 
   const observer = React.useRef<IntersectionObserver>();
diff --git a/scopes/component/component/ui/component.tsx b/scopes/component/component/ui/component.tsx
index 99e03dfc9c96..ebfc75db008e 100644
--- a/scopes/component/component/ui/component.tsx
+++ b/scopes/component/component/ui/component.tsx
@@ -53,8 +53,13 @@ export function Component({
   const _componentIdStr = getComponentIdStr(componentIdStr);
   const componentId = _componentIdStr ? ComponentID.fromString(_componentIdStr) : undefined;
   const resolvedComponentIdStr = path || idFromLocation;
+  const componentFiltersFromProps = useComponentFilters?.() || {};
+
   const useComponentOptions = {
-    logFilters: useComponentFilters?.() || { log: { logOffset: 0, logLimit: 10 } },
+    logFilters: {
+      log: { logLimit: 1 },
+      ...componentFiltersFromProps,
+    },
     customUseComponent: useComponent,
   };
 
diff --git a/scopes/component/component/ui/menu/menu.tsx b/scopes/component/component/ui/menu/menu.tsx
index 37919172bea5..babf8dd97b84 100644
--- a/scopes/component/component/ui/menu/menu.tsx
+++ b/scopes/component/component/ui/menu/menu.tsx
@@ -18,7 +18,6 @@ import { OrderedNavigationSlot, ConsumeMethodSlot } from './nav-plugin';
 import { useIdFromLocation } from '../use-component-from-location';
 import { ComponentID } from '../..';
 import { Filters } from '../use-component-query';
-import { LegacyComponentLog } from '@teambit/legacy-component-log';
 
 export type MenuProps = {
   className?: string;
@@ -82,27 +81,6 @@ export function ComponentMenu({
   const _componentIdStr = getComponentIdStr(componentIdStr);
   const componentId = _componentIdStr ? ComponentID.fromString(_componentIdStr) : undefined;
   const resolvedComponentIdStr = path || idFromLocation;
-  const componentFiltersFromProps = useComponentFilters?.() || {};
-  const useComponentOptions = {
-    logFilters: {
-      snapLog: {
-        logLimit: 10,
-      },
-      tagLog: {
-        logLimit: 10,
-      },
-      fetchLogsByTypeSeparately: true,
-      ...componentFiltersFromProps,
-    },
-    customUseComponent: useComponent,
-  };
-
-  const { component, loading, loadMoreTags, loadMoreSnaps, hasMoreTags, hasMoreSnaps, snaps, tags } = useComponentQuery(
-    host,
-    componentId?.toString() || idFromLocation,
-    useComponentOptions
-  );
-
   const mainMenuItems = useMemo(() => groupBy(flatten(menuItemSlot.values()), 'category'), [menuItemSlot]);
 
   const RightSide = (
@@ -110,16 +88,11 @@ export function ComponentMenu({
       {RightNode || (
         <>
           <VersionRelatedDropdowns
-            component={component}
-            snaps={snaps}
-            tags={tags}
-            loadMoreSnaps={loadMoreSnaps}
-            loadMoreTags={loadMoreTags}
-            hasMoreSnaps={hasMoreSnaps}
-            hasMoreTags={hasMoreTags}
-            loading={loading}
             consumeMethods={consumeMethodSlot}
             host={host}
+            componentId={componentId?.toString() || idFromLocation}
+            useComponent={useComponent}
+            useComponentFilters={useComponentFilters}
           />
           <MainDropdown className={styles.hideOnMobile} menuItems={mainMenuItems} />
         </>
@@ -144,50 +117,73 @@ export function ComponentMenu({
   );
 }
 
+export type VersionRelatedDropdownsProps = {
+  consumeMethods?: ConsumeMethodSlot;
+  className?: string;
+  host: string;
+  useComponentFilters?: () => Filters;
+  useComponent?: UseComponentType;
+  componentId?: string;
+};
+
 export function VersionRelatedDropdowns({
-  component,
-  snaps: snapsFromProps = [],
-  tags: tagsFromProps = [],
+  componentId,
+  useComponentFilters,
+  useComponent,
   consumeMethods,
-  loadMoreSnaps,
-  loadMoreTags,
-  hasMoreSnaps,
-  hasMoreTags,
   className,
-  loading,
   host,
-}: {
-  component?: ComponentModel;
-  tags?: LegacyComponentLog[];
-  snaps?: LegacyComponentLog[];
-  loadMoreTags?: () => void;
-  loadMoreSnaps?: () => void;
-  hasMoreTags?: boolean;
-  hasMoreSnaps?: boolean;
-  loading?: boolean;
-  consumeMethods?: ConsumeMethodSlot;
-  className?: string;
-  host: string;
-}) {
-  const location = useLocation();
-  const { lanesModel } = useLanes();
-  const viewedLane =
-    lanesModel?.viewedLane?.id && !lanesModel?.viewedLane?.id.isDefault() ? lanesModel.viewedLane : undefined;
-  const isWorkspace = host === 'teambit.workspace/workspace';
+}: VersionRelatedDropdownsProps) {
+  const componentFiltersFromProps = useComponentFilters?.() || {};
+  const componentWithLogsOptions = {
+    logFilters: {
+      snapLog: {
+        logLimit: 10,
+      },
+      tagLog: {
+        logLimit: 10,
+      },
+      fetchLogsByTypeSeparately: true,
+      ...componentFiltersFromProps,
+    },
+    customUseComponent: useComponent,
+  };
 
-  const snaps = useMemo(() => {
-    return (snapsFromProps || []).map((snap) => ({ ...snap, version: snap.hash }));
-  }, [snapsFromProps]);
+  // initially fetch just the component data
+  const initialFetchOptions = {
+    logFilters: {
+      log: {
+        logLimit: 1,
+      },
+      ...componentFiltersFromProps,
+    },
+    customUseComponent: useComponent,
+  };
 
-  const tags = useMemo(() => {
-    return (tagsFromProps || []).map((tag) => ({ ...tag, version: tag.tag as string }));
-  }, [tagsFromProps]);
+  const { component, loading } = useComponentQuery(host, componentId, initialFetchOptions);
 
-  const isNew = snaps.length === 0 && tags.length === 0;
+  const useVersions = () => {
+    const { componentLogs = {}, loading: loadingLogs } = useComponentQuery(host, componentId, componentWithLogsOptions);
+    return {
+      loading: loadingLogs,
+      ...componentLogs,
+      snaps: (componentLogs.snaps || []).map((snap) => ({ ...snap, version: snap.hash })),
+      tags: (componentLogs.tags || []).map((tag) => ({ ...tag, version: tag.tag as string })),
+    };
+  };
 
+  const location = useLocation();
+  const { lanesModel } = useLanes();
   const lanes = component?.id
     ? lanesModel?.getLanesByComponentId(component.id)?.filter((lane) => !lane.id.isDefault()) || []
     : [];
+  const viewedLane =
+    lanesModel?.viewedLane?.id && !lanesModel?.viewedLane?.id.isDefault() ? lanesModel.viewedLane : undefined;
+
+  const isWorkspace = host === 'teambit.workspace/workspace';
+
+  const isNew = component?.logs?.length === 0;
+
   const localVersion = isWorkspace && !isNew && (!viewedLane || lanesModel?.isViewingCurrentLane());
 
   const currentVersion =
@@ -197,7 +193,7 @@ export function VersionRelatedDropdowns({
 
   return (
     <>
-      {consumeMethods && tags.length > 0 && component?.id && (
+      {consumeMethods && (component?.tags?.size ?? 0) > 0 && component?.id && (
         <UseBoxDropdown
           position="bottom-end"
           className={classnames(styles.useBox, styles.hideOnMobile)}
@@ -205,14 +201,10 @@ export function VersionRelatedDropdowns({
         />
       )}
       <VersionDropdown
-        tags={tags}
-        snaps={snaps}
         lanes={lanes}
         loading={loading}
-        loadMoreTags={loadMoreTags}
-        loadMoreSnaps={loadMoreSnaps}
-        hasMoreTags={hasMoreTags}
-        hasMoreSnaps={hasMoreSnaps}
+        useComponentVersions={useVersions}
+        hasMoreVersions={!isNew}
         localVersion={localVersion}
         currentVersion={currentVersion}
         latestVersion={component?.latest}
@@ -229,7 +221,6 @@ function useConsumeMethods(
   consumeMethods?: ConsumeMethodSlot,
   currentLane?: LaneModel
 ): ConsumeMethod[] {
-  // if (!consumeMethods || !componentModel) return [];
   return useMemo(
     () =>
       flatten(consumeMethods?.values())
diff --git a/scopes/component/component/ui/use-component-query.ts b/scopes/component/component/ui/use-component-query.ts
index 2bb658977952..99cc38106175 100644
--- a/scopes/component/component/ui/use-component-query.ts
+++ b/scopes/component/component/ui/use-component-query.ts
@@ -217,14 +217,17 @@ export type Filters = {
 export type ComponentQueryResult = {
   component?: ComponentModel;
   componentDescriptor?: ComponentDescriptor;
-  hasMoreLogs?: boolean;
-  hasMoreSnaps?: boolean;
-  hasMoreTags?: boolean;
-  loadMoreLogs?: () => void;
-  loadMoreTags?: () => void;
-  loadMoreSnaps?: () => void;
-  snaps?: LegacyComponentLog[];
-  tags?: LegacyComponentLog[];
+  // @todo refactor to useComponentLogs
+  componentLogs?: {
+    hasMoreLogs?: boolean;
+    hasMoreSnaps?: boolean;
+    hasMoreTags?: boolean;
+    loadMoreLogs?: () => void;
+    loadMoreTags?: () => void;
+    loadMoreSnaps?: () => void;
+    snaps?: LegacyComponentLog[];
+    tags?: LegacyComponentLog[];
+  };
   loading?: boolean;
   error?: ComponentError;
 };
@@ -238,7 +241,6 @@ export function useComponentQuery(
 ): ComponentQueryResult {
   const idRef = useRef(componentId);
   idRef.current = componentId;
-
   const { fetchLogsByTypeSeparately = false, log, tagLog, snapLog } = filters || {};
   const {
     logHead: tagLogHead,
@@ -247,7 +249,6 @@ export function useComponentQuery(
     logLimit: tagLogLimit,
     takeHeadFromComponent: tagLogTakeHeadFromComponent,
   } = tagLog || {};
-
   const { logHead, logOffset, logSort, logLimit, takeHeadFromComponent, logType } = log || {};
   const {
     logHead: snapLogHead,
@@ -582,16 +583,18 @@ export function useComponentQuery(
     return {
       componentDescriptor,
       component,
+      componentLogs: {
+        loadMoreLogs,
+        loadMoreSnaps,
+        loadMoreTags,
+        hasMoreSnaps: hasMoreSnapLogs.current,
+        hasMoreTags: hasMoreTagLogs.current,
+        hasMoreLogs: hasMoreLogs.current,
+        snaps,
+        tags,
+      },
       error: componentError || undefined,
       loading,
-      loadMoreLogs,
-      loadMoreSnaps,
-      loadMoreTags,
-      hasMoreSnaps: hasMoreSnapLogs.current,
-      hasMoreTags: hasMoreTagLogs.current,
-      hasMoreLogs: hasMoreLogs.current,
-      snaps,
-      tags,
       ...rest,
     };
   }, [host, component, componentDescriptor, componentError, hasMoreLogs, hasMoreSnapLogs, hasMoreTagLogs, snaps, tags]);
diff --git a/scopes/component/ui/version-dropdown/version-dropdown-placeholder.tsx b/scopes/component/ui/version-dropdown/version-dropdown-placeholder.tsx
index 38b4f2892a00..6296aaefdb19 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown-placeholder.tsx
+++ b/scopes/component/ui/version-dropdown/version-dropdown-placeholder.tsx
@@ -1,80 +1,79 @@
-import React, { HTMLAttributes, useMemo } from 'react';
+import React, { HTMLAttributes } from 'react';
 import { Ellipsis } from '@teambit/design.ui.styles.ellipsis';
 import classNames from 'classnames';
+import * as semver from 'semver';
 import { Icon } from '@teambit/evangelist.elements.icon';
 import { TimeAgo } from '@teambit/design.ui.time-ago';
 import { UserAvatar } from '@teambit/design.ui.avatar';
-import { DropdownComponentVersion } from './version-dropdown';
 
 import styles from './version-dropdown-placeholder.module.scss';
 
 export type VersionProps = {
-  tags: DropdownComponentVersion[];
-  snaps?: DropdownComponentVersion[];
-  currentVersion: string;
+  currentVersion?: string;
+  timestamp?: string | number;
+  author?: {
+    displayName?: string;
+    email?: string;
+  };
+  message?: string;
   disabled?: boolean;
+  hasMoreVersions?: boolean;
 } & HTMLAttributes<HTMLDivElement>;
 
-const getVersionDetailFromTags = (version, tags) => tags.find((tag) => tag.tag === version);
-const getVersionDetailFromSnaps = (version, snaps) => (snaps || []).find((snap) => snap.hash === version);
-const getVersionDetails = (version, tags, snaps) => {
-  if (version === 'workspace' || version === 'new') return { version };
-  return getVersionDetailFromTags(version, tags) || getVersionDetailFromSnaps(version, snaps);
-};
-
-export function SimpleVersion({ currentVersion, className, disabled, tags, snaps }: VersionProps) {
-  const showArrowDown = useMemo(() => (snaps || []).concat(tags).length > 1, [tags, snaps]);
-  const versionDetails = useMemo(() => getVersionDetails(currentVersion, tags, snaps), [currentVersion, tags, snaps]);
+export function SimpleVersion({
+  currentVersion,
+  className,
+  disabled,
+  hasMoreVersions,
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  author,
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  message,
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  timestamp,
+  ...rest
+}: VersionProps) {
+  const isTag = semver.valid(currentVersion);
 
   return (
-    <div className={classNames(styles.simple, className, disabled && styles.disabled)}>
+    <div {...rest} className={classNames(styles.simple, className, disabled && styles.disabled)}>
       <Ellipsis
-        className={classNames(
-          styles.versionName,
-          versionDetails?.tag && styles.tag,
-          !versionDetails?.tag && styles.snap
-        )}
+        className={classNames(styles.versionName, isTag && styles.tag, !isTag && styles.snap)}
+        onClick={rest.onClick}
       >
         {currentVersion}
       </Ellipsis>
-      {showArrowDown && <Icon of="fat-arrow-down" />}
+      {hasMoreVersions && <Icon of="fat-arrow-down" onClick={rest.onClick} />}
     </div>
   );
 }
 
-export function DetailedVersion({ currentVersion, className, disabled, snaps, tags }: VersionProps) {
-  const showArrowDown = useMemo(() => (snaps || []).concat(tags).length > 1, [tags, snaps]);
-  const versionDetails = useMemo(() => getVersionDetails(currentVersion, tags, snaps), [currentVersion, tags, snaps]);
-
-  const timestamp = useMemo(
-    () => (versionDetails?.date ? new Date(parseInt(versionDetails.date)).toString() : new Date().toString()),
-    [versionDetails?.date]
-  );
-
-  const author = useMemo(() => {
-    return {
-      displayName: versionDetails?.username,
-      email: versionDetails?.email,
-    };
-  }, [versionDetails]);
+export function DetailedVersion({
+  currentVersion,
+  className,
+  disabled,
+  hasMoreVersions,
+  timestamp,
+  author,
+  message,
+  ...rest
+}: VersionProps) {
+  const isTag = semver.valid(currentVersion);
 
   return (
-    <div className={classNames(styles.detailed, className, disabled && styles.disabled)}>
-      <UserAvatar size={24} account={author} className={styles.versionUserAvatar} showTooltip={true} />
+    <div {...rest} className={classNames(styles.detailed, className, disabled && styles.disabled)}>
+      <UserAvatar size={24} account={author ?? {}} className={styles.versionUserAvatar} showTooltip={true} />
       <Ellipsis
-        className={classNames(
-          styles.versionName,
-          versionDetails?.tag && styles.tag,
-          !versionDetails?.tag && styles.snap
-        )}
+        className={classNames(styles.versionName, isTag && styles.tag, !isTag && styles.snap)}
+        onClick={rest.onClick}
       >
         {currentVersion}
       </Ellipsis>
-      {commitMessage(versionDetails?.message)}
-      <Ellipsis className={styles.versionTimestamp}>
-        <TimeAgo date={timestamp} />
+      {commitMessage(message)}
+      <Ellipsis className={styles.versionTimestamp} onClick={rest.onClick}>
+        <TimeAgo date={timestamp ?? ''} onClick={rest.onClick} />
       </Ellipsis>
-      {showArrowDown && <Icon of="fat-arrow-down" />}
+      {hasMoreVersions && <Icon of="fat-arrow-down" onClick={rest.onClick} />}
     </div>
   );
 }
diff --git a/scopes/component/ui/version-dropdown/version-dropdown.composition.tsx b/scopes/component/ui/version-dropdown/version-dropdown.composition.tsx
index d3a34f1171c5..e6185e683322 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown.composition.tsx
+++ b/scopes/component/ui/version-dropdown/version-dropdown.composition.tsx
@@ -8,17 +8,28 @@ const style = { display: 'flex', justifyContent: 'center', alignContent: 'center
 export const VersionDropdownWithOneVersion = () => {
   return (
     <ThemeCompositions style={style}>
-      <VersionDropdown tags={[{ version: '0.1' }]} currentVersion="0.1" />
+      <VersionDropdown
+        useComponentVersions={() => ({
+          tags: [{ version: '0.1' }],
+        })}
+        currentVersion="0.1"
+      />
     </ThemeCompositions>
   );
 };
 
 export const VersionDropdownWithMultipleVersions = () => {
   const versions = ['0.3', '0.2', '0.1'].map((version) => ({ version }));
+
   return (
     <ThemeCompositions style={style}>
       <MemoryRouter>
-        <VersionDropdown tags={versions} currentVersion={versions[0].version} />
+        <VersionDropdown
+          useComponentVersions={() => ({
+            tags: [{ version: '0.1' }],
+          })}
+          currentVersion={versions[0].version}
+        />
       </MemoryRouter>
     </ThemeCompositions>
   );
diff --git a/scopes/component/ui/version-dropdown/version-dropdown.tsx b/scopes/component/ui/version-dropdown/version-dropdown.tsx
index 68c26f53c4b7..251d915d4da6 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown.tsx
+++ b/scopes/component/ui/version-dropdown/version-dropdown.tsx
@@ -24,20 +24,81 @@ function _VersionMenu(
     currentVersion,
     localVersion,
     latestVersion,
-    currentLane,
     overrideVersionHref,
     showVersionDetails,
-    activeTabIndex,
-    setActiveTab,
-    tabs,
+    useVersions,
+    currentLane,
+    lanes,
+    loading: loadingFromProps,
     ...rest
   }: VersionMenuProps,
   ref?: React.ForwardedRef<HTMLDivElement>
 ) {
+  const {
+    snaps,
+    tags,
+    hasMoreSnaps,
+    hasMoreTags,
+    loadMoreSnaps,
+    loadMoreTags,
+    loading: loadingVersions,
+  } = useVersions?.() || {};
+  const VERSION_TAB_NAMES = ['TAG', 'SNAP', 'LANE'] as const;
+  const loading = loadingFromProps || loadingVersions;
+
+  const tabs = VERSION_TAB_NAMES.map((name) => {
+    switch (name) {
+      case 'SNAP':
+        return { name, payload: snaps || [] };
+      case 'LANE':
+        return { name, payload: lanes || [] };
+      default:
+        return { name, payload: tags || [] };
+    }
+  }).filter((tab) => tab.payload.length > 0);
+
+  const getActiveTabIndex = () => {
+    if (currentLane?.components.some((c) => c.version === currentVersion))
+      return tabs.findIndex((tab) => tab.name === 'LANE');
+    if ((snaps || []).some((snap) => snap.version === currentVersion))
+      return tabs.findIndex((tab) => tab.name === 'SNAP');
+    return 0;
+  };
+
+  const [activeTabIndex, setActiveTab] = React.useState<number>(getActiveTabIndex());
+
+  const activeTabOrSnap: 'SNAP' | 'TAG' | 'LANE' | undefined = tabs[activeTabIndex]?.name;
+  const hasMore = activeTabOrSnap === 'SNAP' ? !!hasMoreSnaps : activeTabOrSnap === 'TAG' && !!hasMoreTags;
+  const observer = React.useRef<IntersectionObserver>();
+
+  const handleLoadMore = React.useCallback(() => {
+    if (activeTabOrSnap === 'SNAP') loadMoreSnaps?.();
+    if (activeTabOrSnap === 'TAG') loadMoreTags?.();
+  }, [activeTabOrSnap, tabs.length]);
+
+  const lastLogRef = React.useCallback(
+    (node) => {
+      if (loading) return;
+      if (observer.current) observer.current.disconnect();
+      observer.current = new IntersectionObserver(
+        (entries) => {
+          if (entries[0].isIntersecting && hasMore) {
+            handleLoadMore();
+          }
+        },
+        {
+          threshold: 0.1,
+          rootMargin: '100px',
+        }
+      );
+      if (node) observer.current.observe(node);
+    },
+    [loading, hasMoreSnaps, hasMoreTags, handleLoadMore]
+  );
   const multipleTabs = tabs.length > 1;
   const message = multipleTabs
     ? 'Switch to view tags, snaps, or lanes'
-    : `Switch between ${tabs[0].name.toLocaleLowerCase()}s`;
+    : `Switch between ${tabs[0]?.name.toLocaleLowerCase()}s`;
 
   return (
     <div {...rest}>
@@ -79,7 +140,7 @@ function _VersionMenu(
         {tabs[activeTabIndex]?.name !== 'LANE' &&
           tabs[activeTabIndex]?.payload.map((payload, index) => (
             <VersionInfo
-              ref={index === tabs[activeTabIndex]?.payload.length - 1 ? ref : null}
+              ref={index === tabs[activeTabIndex]?.payload.length - 1 ? ref || lastLogRef : null}
               key={payload.version}
               currentVersion={currentVersion}
               latestVersion={latestVersion}
@@ -93,19 +154,36 @@ function _VersionMenu(
   );
 }
 
-export type VersionDropdownProps = {
-  tags: DropdownComponentVersion[];
+export type UseComponentVersionsResult = {
+  tags?: DropdownComponentVersion[];
   snaps?: DropdownComponentVersion[];
-  lanes?: LaneModel[];
   loadMoreTags?: () => void;
   loadMoreSnaps?: () => void;
   hasMoreTags?: boolean;
   hasMoreSnaps?: boolean;
+  loading?: boolean;
+};
+
+export type UseComponentVersions = () => UseComponentVersionsResult;
+
+export type VersionDropdownProps = {
   localVersion?: boolean;
-  currentVersion: string;
-  currentLane?: LaneModel;
   latestVersion?: string;
+  currentVersion: string;
+  currentVersionLog?: {
+    timestamp?: string | number;
+    author?: {
+      displayName?: string;
+      email?: string;
+    };
+    message?: string;
+  };
+  hasMoreVersions?: boolean;
+  // currentLane?: LaneModel;
   loading?: boolean;
+  useComponentVersions?: UseComponentVersions;
+  currentLane?: LaneModel;
+  lanes?: LaneModel[];
   overrideVersionHref?: (version: string) => string;
   placeholderClassName?: string;
   dropdownClassName?: string;
@@ -119,14 +197,12 @@ export const VersionDropdown = React.memo(React.forwardRef<HTMLDivElement, Versi
 
 function _VersionDropdown(
   {
-    snaps,
-    tags,
-    lanes,
     currentVersion,
     latestVersion,
     localVersion,
+    currentVersionLog = {},
+    hasMoreVersions,
     loading,
-    currentLane,
     overrideVersionHref,
     className,
     placeholderClassName,
@@ -134,113 +210,71 @@ function _VersionDropdown(
     menuClassName,
     showVersionDetails,
     disabled,
-    loadMoreSnaps,
-    loadMoreTags,
-    hasMoreSnaps,
-    hasMoreTags,
-    placeholderComponent = (
-      <SimpleVersion
-        disabled={disabled}
-        snaps={snaps}
-        tags={tags}
-        className={placeholderClassName}
-        currentVersion={currentVersion}
-      />
-    ),
+    placeholderComponent,
+    currentLane,
+    useComponentVersions,
+    lanes,
     ...rest
   }: VersionDropdownProps,
   ref?: React.ForwardedRef<HTMLDivElement>
 ) {
-  const VERSION_TAB_NAMES = ['TAG', 'SNAP', 'LANE'] as const;
+  const [key, setKey] = useState(0);
+  const singleVersion = hasMoreVersions && !localVersion;
+  const [open, setOpen] = useState(false);
+  const { author, message, timestamp } = currentVersionLog;
+  if (disabled || (singleVersion && !loading)) {
+    return <div className={classNames(styles.noVersions, className)}>{placeholderComponent}</div>;
+  }
 
-  const tabs = VERSION_TAB_NAMES.map((name) => {
-    switch (name) {
-      case 'SNAP':
-        return { name, payload: snaps || [] };
-      case 'LANE':
-        return { name, payload: lanes || [] };
-      default:
-        return { name, payload: tags || [] };
+  const handlePlaceholderClicked = (e: React.MouseEvent<HTMLDivElement>) => {
+    if (e.target === e.currentTarget) {
+      setOpen((o) => !o);
     }
-  }).filter((tab) => tab.payload.length > 0);
-
-  const getActiveTabIndex = () => {
-    if (currentLane?.components.some((c) => c.version === currentVersion))
-      return tabs.findIndex((tab) => tab.name === 'LANE');
-    if ((snaps || []).some((snap) => snap.version === currentVersion))
-      return tabs.findIndex((tab) => tab.name === 'SNAP');
-    return 0;
   };
 
-  const [activeTabIndex, setActiveTabIndex] = React.useState<number>(getActiveTabIndex());
-
-  const activeTabOrSnap: 'SNAP' | 'TAG' | 'LANE' | undefined = tabs[activeTabIndex]?.name;
-  const hasMore = activeTabOrSnap === 'SNAP' ? !!hasMoreSnaps : activeTabOrSnap === 'TAG' && !!hasMoreTags;
-  const observer = React.useRef<IntersectionObserver>();
-
-  const handleLoadMore = React.useCallback(() => {
-    if (activeTabOrSnap === 'SNAP') loadMoreSnaps?.();
-    if (activeTabOrSnap === 'TAG') loadMoreTags?.();
-  }, [activeTabOrSnap, tabs.length]);
-
-  const lastLogRef = React.useCallback(
-    (node) => {
-      if (loading) return;
-      if (observer.current) observer.current.disconnect();
-      observer.current = new IntersectionObserver(
-        (entries) => {
-          if (entries[0].isIntersecting && hasMore) {
-            handleLoadMore();
-          }
-        },
-        {
-          threshold: 0.1,
-          rootMargin: '100px',
-        }
-      );
-      if (node) observer.current.observe(node);
-    },
-    [loading, hasMoreSnaps, hasMoreTags, handleLoadMore]
+  const defaultPlaceholder = (
+    <SimpleVersion
+      author={author}
+      message={message}
+      timestamp={timestamp}
+      disabled={disabled}
+      className={placeholderClassName}
+      currentVersion={currentVersion}
+      onClick={handlePlaceholderClicked}
+      hasMoreVersions={hasMoreVersions}
+    />
   );
 
-  const [key, setKey] = useState(0);
-
-  const singleVersion = (snaps || []).concat(tags).length < 2 && !localVersion;
-
-  if (disabled || (singleVersion && !loading)) {
-    return <div className={classNames(styles.noVersions, className)}>{placeholderComponent}</div>;
-  }
-
   return (
     <div {...rest} className={classNames(styles.versionDropdown, className)}>
       <Dropdown
         className={classNames(styles.dropdown, dropdownClassName)}
         dropClass={classNames(styles.menu, menuClassName)}
-        clickToggles={false}
-        clickPlaceholderToggles={true}
-        onChange={(_e, open) => open && setKey((x) => x + 1)} // to reset menu to initial state when toggling
+        open={open}
+        onClick={handlePlaceholderClicked}
+        onClickOutside={() => setOpen(false)}
+        onChange={(_e, _open) => _open && setKey((x) => x + 1)} // to reset menu to initial state when toggling
         PlaceholderComponent={({ children, ...other }) => (
-          <div {...other} className={placeholderClassName}>
+          <div {...other} className={placeholderClassName} onClick={handlePlaceholderClicked}>
             {children}
           </div>
         )}
-        placeholder={placeholderComponent}
+        placeholder={placeholderComponent || defaultPlaceholder}
       >
         {loading && <LineSkeleton className={styles.loading} count={6} />}
-        {loading || (
+        {!loading && open && (
           <VersionMenu
-            ref={ref || lastLogRef}
+            ref={ref}
             className={menuClassName}
-            tabs={tabs}
             key={key}
-            activeTabIndex={activeTabIndex}
-            setActiveTab={setActiveTabIndex}
             currentVersion={currentVersion}
             latestVersion={latestVersion}
             localVersion={localVersion}
-            currentLane={currentLane}
             overrideVersionHref={overrideVersionHref}
             showVersionDetails={showVersionDetails}
+            currentLane={currentLane}
+            lanes={lanes}
+            useVersions={useComponentVersions}
           />
         )}
       </Dropdown>
@@ -252,10 +286,10 @@ type VersionMenuProps = {
   localVersion?: boolean;
   currentVersion?: string;
   latestVersion?: string;
+  useVersions?: UseComponentVersions;
   currentLane?: LaneModel;
+  lanes?: LaneModel[];
   overrideVersionHref?: (version: string) => string;
   showVersionDetails?: boolean;
-  activeTabIndex: number;
-  setActiveTab: (index: number) => void;
-  tabs: Array<{ name: string; payload: Array<any> }>;
+  loading?: boolean;
 } & React.HTMLAttributes<HTMLDivElement>;

From e61fac95ea413a85a60ea531a752fa2140af69fe Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Thu, 4 May 2023 15:27:32 -0400
Subject: [PATCH 07/20] fix component compare to lazy load

---
 .../component-compare/component-compare.tsx   | 21 +++++-
 .../component-compare-version-picker.tsx      | 75 ++++++++++++-------
 scopes/component/component/ui/component.tsx   |  2 +-
 scopes/component/component/ui/menu/menu.tsx   |  5 +-
 .../ui/version-dropdown/version-dropdown.tsx  | 15 ++--
 5 files changed, 77 insertions(+), 41 deletions(-)

diff --git a/components/ui/component-compare/component-compare/component-compare.tsx b/components/ui/component-compare/component-compare/component-compare.tsx
index ea837623333b..94aa2b7703b8 100644
--- a/components/ui/component-compare/component-compare/component-compare.tsx
+++ b/components/ui/component-compare/component-compare/component-compare.tsx
@@ -79,7 +79,15 @@ export function ComponentCompare(props: ComponentCompareProps) {
     component: base,
     loading: loadingBase,
     componentDescriptor: baseComponentDescriptor,
-  } = useComponent(host, baseId.toString(), { customUseComponent });
+  } = useComponent(host, baseId.toString(), {
+    customUseComponent,
+    logFilters: {
+      log: {
+        logLimit: 3,
+      },
+    },
+  });
+
   const {
     component: compareComponent,
     loading: loadingCompare,
@@ -87,6 +95,11 @@ export function ComponentCompare(props: ComponentCompareProps) {
   } = useComponent(host, _compareId?.toString() || '', {
     skip: !_compareId,
     customUseComponent,
+    logFilters: {
+      log: {
+        logLimit: 3,
+      },
+    },
   });
 
   const loading = loadingBase || loadingCompare;
@@ -170,7 +183,11 @@ function RenderCompareScreen(props: ComponentCompareProps) {
   return (
     <>
       {showVersionPicker && (
-        <div className={styles.top}>{state?.versionPicker?.element || <ComponentCompareVersionPicker />}</div>
+        <div className={styles.top}>
+          {state?.versionPicker?.element || (
+            <ComponentCompareVersionPicker host={props.host} customUseComponent={props.customUseComponent} />
+          )}
+        </div>
       )}
       <div className={styles.bottom}>
         <CompareMenuNav {...props} />
diff --git a/components/ui/component-compare/version-picker/component-compare-version-picker.tsx b/components/ui/component-compare/version-picker/component-compare-version-picker.tsx
index 96f8e60902ba..174940355de0 100644
--- a/components/ui/component-compare/version-picker/component-compare-version-picker.tsx
+++ b/components/ui/component-compare/version-picker/component-compare-version-picker.tsx
@@ -1,36 +1,57 @@
-import React, { HTMLAttributes, useMemo } from 'react';
-import { DropdownComponentVersion, VersionDropdown } from '@teambit/component.ui.version-dropdown';
+import React, { HTMLAttributes } from 'react';
+import { VersionDropdown } from '@teambit/component.ui.version-dropdown';
 import { useUpdatedUrlFromQuery } from '@teambit/component.ui.component-compare.hooks.use-component-compare-url';
 import { useComponentCompare } from '@teambit/component.ui.component-compare.context';
+import { UseComponentType, useComponent } from '@teambit/component';
 import classNames from 'classnames';
 
 import styles from './component-compare-version-picker.module.scss';
 
-export type ComponentCompareVersionPickerProps = {} & HTMLAttributes<HTMLDivElement>;
+export type ComponentCompareVersionPickerProps = {
+  customUseComponent?: UseComponentType;
+  host: string;
+} & HTMLAttributes<HTMLDivElement>;
 
-export function ComponentCompareVersionPicker({ className }: ComponentCompareVersionPickerProps) {
+export function ComponentCompareVersionPicker({
+  className,
+  host,
+  customUseComponent,
+}: ComponentCompareVersionPickerProps) {
   const componentCompare = useComponentCompare();
   const compare = componentCompare?.compare?.model;
-
-  const logs =
-    (compare?.logs || []).filter((log) => {
-      const version = log.tag || log.hash;
-      return componentCompare?.compare?.hasLocalChanges || version !== compare?.id.version;
-    }) || [];
-
-  const [tags, snaps] = useMemo(() => {
-    return (logs || []).reduce(
-      ([_tags, _snaps], log) => {
-        if (!log.tag) {
-          _snaps.push({ ...log, version: log.hash });
-        } else {
-          _tags.push({ ...log, version: log.tag as string });
-        }
-        return [_tags, _snaps];
+  const componentId = compare?.id.toString();
+  const componentWithLogsOptions = {
+    logFilters: {
+      snapLog: {
+        logLimit: 10,
       },
-      [new Array<DropdownComponentVersion>(), new Array<DropdownComponentVersion>()]
-    );
-  }, [logs]);
+      tagLog: {
+        logLimit: 10,
+      },
+      fetchLogsByTypeSeparately: true,
+    },
+    customUseComponent,
+  };
+
+  const useVersions = () => {
+    const { componentLogs = {}, loading: loadingLogs } = useComponent(host, componentId, componentWithLogsOptions);
+    return {
+      loading: loadingLogs,
+      ...componentLogs,
+      snaps: (componentLogs.snaps || [])
+        .map((snap) => ({ ...snap, version: snap.hash }))
+        .filter((log) => {
+          const version = log.tag || log.hash;
+          return componentCompare?.compare?.hasLocalChanges || version !== compare?.id.version;
+        }),
+      tags: (componentLogs.tags || [])
+        .map((tag) => ({ ...tag, version: tag.tag as string }))
+        .filter((log) => {
+          const version = log.tag || log.hash;
+          return componentCompare?.compare?.hasLocalChanges || version !== compare?.id.version;
+        }),
+    };
+  };
 
   const compareVersion = componentCompare?.compare?.hasLocalChanges ? 'workspace' : compare?.version;
 
@@ -47,15 +68,15 @@ export function ComponentCompareVersionPicker({ className }: ComponentCompareVer
         dropdownClassName={styles.componentCompareDropdown}
         placeholderClassName={styles.componentCompareVersionPlaceholder}
         menuClassName={classNames(styles.componentCompareVersionMenu, styles.showMenuOverNav)}
-        snaps={snaps}
-        tags={tags}
         currentVersion={baseVersion as string}
         loading={componentCompare?.loading}
         overrideVersionHref={(_baseVersion) => {
           return useUpdatedUrlFromQuery({ baseVersion: _baseVersion });
         }}
-        disabled={snaps.concat(tags).length < 2}
+        disabled={(compare?.logs?.length ?? 0) < 2}
+        hasMoreVersions={(compare?.logs?.length ?? 0) > 1}
         showVersionDetails={true}
+        useComponentVersions={useVersions}
       />
       <div className={styles.titleText}>with</div>
       <VersionDropdown
@@ -63,8 +84,6 @@ export function ComponentCompareVersionPicker({ className }: ComponentCompareVer
         dropdownClassName={styles.componentCompareDropdown}
         placeholderClassName={styles.componentCompareVersionPlaceholder}
         menuClassName={styles.componentCompareVersionMenu}
-        snaps={snaps}
-        tags={tags}
         disabled={true}
         loading={componentCompare?.loading}
         currentVersion={compareVersion as string}
diff --git a/scopes/component/component/ui/component.tsx b/scopes/component/component/ui/component.tsx
index ebfc75db008e..1fb97abedd6b 100644
--- a/scopes/component/component/ui/component.tsx
+++ b/scopes/component/component/ui/component.tsx
@@ -57,7 +57,7 @@ export function Component({
 
   const useComponentOptions = {
     logFilters: {
-      log: { logLimit: 1 },
+      log: { logLimit: 3 },
       ...componentFiltersFromProps,
     },
     customUseComponent: useComponent,
diff --git a/scopes/component/component/ui/menu/menu.tsx b/scopes/component/component/ui/menu/menu.tsx
index babf8dd97b84..6a1866a62bc7 100644
--- a/scopes/component/component/ui/menu/menu.tsx
+++ b/scopes/component/component/ui/menu/menu.tsx
@@ -52,9 +52,8 @@ export type MenuProps = {
 
   useComponent?: UseComponentType;
 
-  path?: string;
-
   useComponentFilters?: () => Filters;
+  path?: string;
 };
 function getComponentIdStr(componentIdStr?: string | (() => string | undefined)): string | undefined {
   if (isFunction(componentIdStr)) return componentIdStr();
@@ -153,7 +152,7 @@ export function VersionRelatedDropdowns({
   const initialFetchOptions = {
     logFilters: {
       log: {
-        logLimit: 1,
+        logLimit: 3,
       },
       ...componentFiltersFromProps,
     },
diff --git a/scopes/component/ui/version-dropdown/version-dropdown.tsx b/scopes/component/ui/version-dropdown/version-dropdown.tsx
index 251d915d4da6..69a42f48d784 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown.tsx
+++ b/scopes/component/ui/version-dropdown/version-dropdown.tsx
@@ -179,7 +179,6 @@ export type VersionDropdownProps = {
     message?: string;
   };
   hasMoreVersions?: boolean;
-  // currentLane?: LaneModel;
   loading?: boolean;
   useComponentVersions?: UseComponentVersions;
   currentLane?: LaneModel;
@@ -219,13 +218,10 @@ function _VersionDropdown(
   ref?: React.ForwardedRef<HTMLDivElement>
 ) {
   const [key, setKey] = useState(0);
-  const singleVersion = hasMoreVersions && !localVersion;
+  const singleVersion = !hasMoreVersions;
   const [open, setOpen] = useState(false);
-  const { author, message, timestamp } = currentVersionLog;
-  if (disabled || (singleVersion && !loading)) {
-    return <div className={classNames(styles.noVersions, className)}>{placeholderComponent}</div>;
-  }
 
+  const { author, message, timestamp } = currentVersionLog;
   const handlePlaceholderClicked = (e: React.MouseEvent<HTMLDivElement>) => {
     if (e.target === e.currentTarget) {
       setOpen((o) => !o);
@@ -245,6 +241,11 @@ function _VersionDropdown(
     />
   );
 
+  const PlaceholderComponent = placeholderComponent || defaultPlaceholder;
+  if (disabled || (singleVersion && !loading)) {
+    return <div className={classNames(styles.noVersions, className)}>{PlaceholderComponent}</div>;
+  }
+
   return (
     <div {...rest} className={classNames(styles.versionDropdown, className)}>
       <Dropdown
@@ -259,7 +260,7 @@ function _VersionDropdown(
             {children}
           </div>
         )}
-        placeholder={placeholderComponent || defaultPlaceholder}
+        placeholder={PlaceholderComponent}
       >
         {loading && <LineSkeleton className={styles.loading} count={6} />}
         {!loading && open && (

From a1e492b7b456c50b540db659ce5fde617dbf6951 Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Fri, 5 May 2023 15:08:09 -0400
Subject: [PATCH 08/20] fix tests

---
 .../version-dropdown.spec.tsx                 | 19 ++++++++++---------
 1 file changed, 10 insertions(+), 9 deletions(-)

diff --git a/scopes/component/ui/version-dropdown/version-dropdown.spec.tsx b/scopes/component/ui/version-dropdown/version-dropdown.spec.tsx
index cc8f31778b21..4f67e26029ac 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown.spec.tsx
+++ b/scopes/component/ui/version-dropdown/version-dropdown.spec.tsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import { render } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
 import { expect } from 'chai';
 import { VersionDropdownWithOneVersion, VersionDropdownWithMultipleVersions } from './version-dropdown.composition';
 
@@ -16,13 +16,14 @@ describe('version dropdown tests', () => {
     const textVersion = getByText(/^0.1$/);
     expect(textVersion).to.exist;
   });
-  it('should return multiple versions', () => {
-    const { getByText, getAllByText } = render(<VersionDropdownWithMultipleVersions />);
-    const textVersionOne = getByText(/^0.1$/);
-    const textVersionTwo = getByText(/^0.2$/);
-    const textVersionThree = getAllByText(/^0.3$/);
-    expect(textVersionOne).to.exist;
-    expect(textVersionTwo).to.exist;
-    expect(textVersionThree).to.exist;
+  it('should not return multiple versions when mounted (lazy loading)', () => {
+    render(<VersionDropdownWithMultipleVersions />);
+    const textVersionOne = screen.queryByText(/^0.1$/);
+    const textVersionTwo = screen.queryByText(/^0.2$/);
+    const textVersionThree = screen.getAllByText(/^0.3$/);
+
+    expect(textVersionOne).to.be.null;
+    expect(textVersionTwo).to.be.null;
+    expect(textVersionThree).to.have.lengthOf.at.least(1);
   });
 });

From 239adc2a34a0283799c103c8907780896660a440 Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Mon, 8 May 2023 14:23:51 -0400
Subject: [PATCH 09/20] fix loader - when lazy loading versions

---
 .../version-dropdown-placeholder.module.scss          |  7 +++++++
 .../version-dropdown/version-dropdown-placeholder.tsx |  7 +++++++
 .../ui/version-dropdown/version-dropdown.module.scss  |  9 ++++++++-
 .../ui/version-dropdown/version-dropdown.tsx          | 11 +++++++----
 4 files changed, 29 insertions(+), 5 deletions(-)

diff --git a/scopes/component/ui/version-dropdown/version-dropdown-placeholder.module.scss b/scopes/component/ui/version-dropdown/version-dropdown-placeholder.module.scss
index 7d33937585b7..36155c16fdf6 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown-placeholder.module.scss
+++ b/scopes/component/ui/version-dropdown/version-dropdown-placeholder.module.scss
@@ -95,3 +95,10 @@
     flex: none;
   }
 }
+
+.loader {
+  color: var(--bit-bg-dent, #f6f6f6);
+  > span {
+    padding: 4px;
+  }
+}
diff --git a/scopes/component/ui/version-dropdown/version-dropdown-placeholder.tsx b/scopes/component/ui/version-dropdown/version-dropdown-placeholder.tsx
index 6296aaefdb19..f1176151cd37 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown-placeholder.tsx
+++ b/scopes/component/ui/version-dropdown/version-dropdown-placeholder.tsx
@@ -5,6 +5,7 @@ import * as semver from 'semver';
 import { Icon } from '@teambit/evangelist.elements.icon';
 import { TimeAgo } from '@teambit/design.ui.time-ago';
 import { UserAvatar } from '@teambit/design.ui.avatar';
+import { WordSkeleton } from '@teambit/base-ui.loaders.skeleton';
 
 import styles from './version-dropdown-placeholder.module.scss';
 
@@ -18,6 +19,7 @@ export type VersionProps = {
   message?: string;
   disabled?: boolean;
   hasMoreVersions?: boolean;
+  loading?: boolean;
 } & HTMLAttributes<HTMLDivElement>;
 
 export function SimpleVersion({
@@ -31,8 +33,11 @@ export function SimpleVersion({
   message,
   // eslint-disable-next-line @typescript-eslint/no-unused-vars
   timestamp,
+  loading,
   ...rest
 }: VersionProps) {
+  if (loading) return <WordSkeleton className={styles.loader} length={9} />;
+
   const isTag = semver.valid(currentVersion);
 
   return (
@@ -56,8 +61,10 @@ export function DetailedVersion({
   timestamp,
   author,
   message,
+  loading,
   ...rest
 }: VersionProps) {
+  if (loading) return <WordSkeleton className={styles.loader} length={9} />;
   const isTag = semver.valid(currentVersion);
 
   return (
diff --git a/scopes/component/ui/version-dropdown/version-dropdown.module.scss b/scopes/component/ui/version-dropdown/version-dropdown.module.scss
index f5f46def1e0e..1a89c23168a1 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown.module.scss
+++ b/scopes/component/ui/version-dropdown/version-dropdown.module.scss
@@ -103,5 +103,12 @@
 }
 
 .loading {
-  color: var(--bit-bg-heavy, #f6f6f6);
+  color: var(--bit-bg-dent, #f6f6f6);
+}
+
+.loader {
+  color: var(--bit-bg-dent, #f6f6f6);
+  > div {
+    padding: 8px 0px;
+  }
 }
diff --git a/scopes/component/ui/version-dropdown/version-dropdown.tsx b/scopes/component/ui/version-dropdown/version-dropdown.tsx
index 69a42f48d784..62061a63b386 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown.tsx
+++ b/scopes/component/ui/version-dropdown/version-dropdown.tsx
@@ -4,7 +4,7 @@ import { Dropdown } from '@teambit/evangelist.surfaces.dropdown';
 import { Tab } from '@teambit/ui-foundation.ui.use-box.tab';
 import { LegacyComponentLog } from '@teambit/legacy-component-log';
 import { UserAvatar } from '@teambit/design.ui.avatar';
-import { LineSkeleton } from '@teambit/base-ui.loaders.skeleton';
+import { LineSkeleton, WordSkeleton } from '@teambit/base-ui.loaders.skeleton';
 import { LaneModel } from '@teambit/lanes.ui.models.lanes-model';
 import classNames from 'classnames';
 
@@ -103,7 +103,8 @@ function _VersionMenu(
   return (
     <div {...rest}>
       <div className={styles.top}>
-        <div className={classNames(styles.titleContainer, styles.title)}>{message}</div>
+        {loading && <LineSkeleton count={6} className={styles.loader} />}
+        {!loading && <div className={classNames(styles.titleContainer, styles.title)}>{message}</div>}
         {localVersion && (
           <MenuLinkItem
             href={'?'}
@@ -223,6 +224,7 @@ function _VersionDropdown(
 
   const { author, message, timestamp } = currentVersionLog;
   const handlePlaceholderClicked = (e: React.MouseEvent<HTMLDivElement>) => {
+    if (loading) return;
     if (e.target === e.currentTarget) {
       setOpen((o) => !o);
     }
@@ -238,10 +240,12 @@ function _VersionDropdown(
       currentVersion={currentVersion}
       onClick={handlePlaceholderClicked}
       hasMoreVersions={hasMoreVersions}
+      loading={loading}
     />
   );
 
   const PlaceholderComponent = placeholderComponent || defaultPlaceholder;
+
   if (disabled || (singleVersion && !loading)) {
     return <div className={classNames(styles.noVersions, className)}>{PlaceholderComponent}</div>;
   }
@@ -262,8 +266,7 @@ function _VersionDropdown(
         )}
         placeholder={PlaceholderComponent}
       >
-        {loading && <LineSkeleton className={styles.loading} count={6} />}
-        {!loading && open && (
+        {open && (
           <VersionMenu
             ref={ref}
             className={menuClassName}

From 2ae24b088aed6b7a44bacb3513e40a9f480b1f2d Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Tue, 9 May 2023 13:17:20 -0400
Subject: [PATCH 10/20] query from head when on a version + fix loading state

---
 scopes/component/component/ui/menu/menu.tsx   | 38 ++++++++++++-------
 .../ui/version-dropdown/version-dropdown.tsx  |  8 +++-
 .../workspace/workspace.ui.drawer.tsx         |  2 +-
 3 files changed, 33 insertions(+), 15 deletions(-)

diff --git a/scopes/component/component/ui/menu/menu.tsx b/scopes/component/component/ui/menu/menu.tsx
index 6a1866a62bc7..ec6a6a2e8071 100644
--- a/scopes/component/component/ui/menu/menu.tsx
+++ b/scopes/component/component/ui/menu/menu.tsx
@@ -10,6 +10,8 @@ import { UseBoxDropdown } from '@teambit/ui-foundation.ui.use-box.dropdown';
 import { useLanes } from '@teambit/lanes.hooks.use-lanes';
 import { LaneModel } from '@teambit/lanes.ui.models.lanes-model';
 import { Menu as ConsumeMethodsMenu } from '@teambit/ui-foundation.ui.use-box.menu';
+import { useQuery } from '@teambit/ui-foundation.ui.react-router.use-query';
+import * as semver from 'semver';
 import type { ComponentModel } from '../component-model';
 import { useComponent as useComponentQuery, UseComponentType } from '../use-component';
 import { CollapsibleMenuNav } from './menu-nav';
@@ -134,19 +136,10 @@ export function VersionRelatedDropdowns({
   host,
 }: VersionRelatedDropdownsProps) {
   const componentFiltersFromProps = useComponentFilters?.() || {};
-  const componentWithLogsOptions = {
-    logFilters: {
-      snapLog: {
-        logLimit: 10,
-      },
-      tagLog: {
-        logLimit: 10,
-      },
-      fetchLogsByTypeSeparately: true,
-      ...componentFiltersFromProps,
-    },
-    customUseComponent: useComponent,
-  };
+  const query = useQuery();
+  const componentVersion = query.get('version');
+  const isTag = componentVersion ? semver.valid(componentVersion) : undefined;
+  const isSnap = componentVersion ? !isTag : undefined;
 
   // initially fetch just the component data
   const initialFetchOptions = {
@@ -161,6 +154,25 @@ export function VersionRelatedDropdowns({
 
   const { component, loading } = useComponentQuery(host, componentId, initialFetchOptions);
 
+  const componentWithLogsOptions = React.useMemo(
+    () => ({
+      logFilters: {
+        snapLog: {
+          logLimit: 10,
+          logHead: isSnap ? componentVersion : undefined,
+        },
+        tagLog: {
+          logLimit: 10,
+          logHead: isTag ? componentVersion : undefined,
+        },
+        fetchLogsByTypeSeparately: true,
+        ...componentFiltersFromProps,
+      },
+      customUseComponent: useComponent,
+    }),
+    [componentVersion, isTag, isSnap]
+  );
+
   const useVersions = () => {
     const { componentLogs = {}, loading: loadingLogs } = useComponentQuery(host, componentId, componentWithLogsOptions);
     return {
diff --git a/scopes/component/ui/version-dropdown/version-dropdown.tsx b/scopes/component/ui/version-dropdown/version-dropdown.tsx
index 62061a63b386..86a4a725a22b 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown.tsx
+++ b/scopes/component/ui/version-dropdown/version-dropdown.tsx
@@ -105,7 +105,7 @@ function _VersionMenu(
       <div className={styles.top}>
         {loading && <LineSkeleton count={6} className={styles.loader} />}
         {!loading && <div className={classNames(styles.titleContainer, styles.title)}>{message}</div>}
-        {localVersion && (
+        {!loading && localVersion && (
           <MenuLinkItem
             href={'?'}
             active={currentVersion === LOCAL_VERSION}
@@ -222,6 +222,12 @@ function _VersionDropdown(
   const singleVersion = !hasMoreVersions;
   const [open, setOpen] = useState(false);
 
+  React.useEffect(() => {
+    if (loading && open) {
+      setOpen(false);
+    }
+  }, [loading]);
+
   const { author, message, timestamp } = currentVersionLog;
   const handlePlaceholderClicked = (e: React.MouseEvent<HTMLDivElement>) => {
     if (loading) return;
diff --git a/scopes/workspace/workspace/workspace.ui.drawer.tsx b/scopes/workspace/workspace/workspace.ui.drawer.tsx
index a3d262acff67..3be109b5f723 100644
--- a/scopes/workspace/workspace/workspace.ui.drawer.tsx
+++ b/scopes/workspace/workspace/workspace.ui.drawer.tsx
@@ -66,7 +66,7 @@ export const workspaceDrawer = ({
         !isViewingWorkspaceVersions ? viewedLaneId : undefined
       );
       const { components: mainComponents = [], loading: mainCompsLoading } = useLaneComponents(
-        !isViewingDefaultLane ? defaultLane?.id : undefined
+        isViewingDefaultLane ? defaultLane?.id : undefined
       );
 
       const workspace = useContext(WorkspaceContext);

From 9e14c51b0af9fa8a7a3ced5e755ae7ebb23d7108 Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Fri, 12 May 2023 14:09:01 -0400
Subject: [PATCH 11/20] refator getLogs to allow fetching versions with
 advanced filters

---
 .../inputs/lane-selector/lane-placeholder.tsx |   6 +
 .../lane-selector/lane-selector-list.tsx      |   3 +
 .../ui/inputs/lane-selector/lane-selector.tsx |   9 +-
 .../lane-switcher/lane-switcher.module.scss   |   9 +
 .../lane-switcher/lane-switcher.tsx           |  57 ++--
 pnpm-lock.yaml                                | 113 ++++----
 .../component/component/component-factory.ts  |  10 +-
 .../component/component/component.graphql.ts  |  10 +
 scopes/component/component/component.ts       |  27 +-
 scopes/component/component/index.ts           |   2 +-
 scopes/component/component/ui/component.tsx   |  24 +-
 scopes/component/component/ui/index.ts        |   7 +-
 scopes/component/component/ui/menu/menu.tsx   |  61 ++--
 .../component/ui/use-component-query.ts       | 270 +++++++++++-------
 .../ui/version-dropdown/version-dropdown.tsx  |  62 ++--
 .../lanes/hooks/use-lanes/lanes-provider.tsx  |  40 ++-
 scopes/lanes/hooks/use-lanes/use-lanes.tsx    |   2 +-
 scopes/lanes/lanes/lanes.ui.runtime.tsx       |  70 +++--
 scopes/scope/scope/scope.main.runtime.ts      |  97 ++++++-
 scopes/workspace/workspace/workspace.ts       |  12 +-
 src/scope/component-ops/traverse-versions.ts  |   4 +-
 src/scope/models/model-component.ts           |   4 +-
 src/scope/scope.ts                            |  13 +-
 23 files changed, 598 insertions(+), 314 deletions(-)

diff --git a/components/ui/inputs/lane-selector/lane-placeholder.tsx b/components/ui/inputs/lane-selector/lane-placeholder.tsx
index b3220287eb35..9a896a204c56 100644
--- a/components/ui/inputs/lane-selector/lane-placeholder.tsx
+++ b/components/ui/inputs/lane-selector/lane-placeholder.tsx
@@ -11,6 +11,7 @@ export type LanePlaceholderProps = {
   selectedLaneId?: LaneId;
   disabled?: boolean;
   showScope?: boolean;
+  loading?: boolean;
 } & HTMLAttributes<HTMLDivElement>;
 
 export function LanePlaceholder({
@@ -18,12 +19,17 @@ export function LanePlaceholder({
   disabled,
   className,
   showScope = true,
+  loading,
   ...rest
 }: LanePlaceholderProps) {
   const laneIdStr = selectedLaneId?.isDefault()
     ? selectedLaneId.name
     : (showScope && selectedLaneId?.toString()) || selectedLaneId?.name;
 
+  if (loading) {
+    return null;
+  }
+
   return (
     <div {...rest} className={classnames(styles.placeholder, className, disabled && styles.disabled)}>
       <LaneIcon className={styles.icon} />
diff --git a/components/ui/inputs/lane-selector/lane-selector-list.tsx b/components/ui/inputs/lane-selector/lane-selector-list.tsx
index c6d1e69bef31..87dd227ad493 100644
--- a/components/ui/inputs/lane-selector/lane-selector-list.tsx
+++ b/components/ui/inputs/lane-selector/lane-selector-list.tsx
@@ -39,6 +39,7 @@ export function LaneSelectorList({
   listNavigator,
   // eslint-disable-next-line @typescript-eslint/no-unused-vars
   forceCloseOnEnter,
+  loading,
   ...rest
 }: LaneSelectorListProps) {
   const navigate = useNavigate();
@@ -156,6 +157,8 @@ export function LaneSelectorList({
     }
   }, [selectedLaneId?.toString()]);
 
+  if (loading) return null;
+
   return (
     <div {...rest} className={classnames(className, styles.laneSelectorList)}>
       {groupByScope &&
diff --git a/components/ui/inputs/lane-selector/lane-selector.tsx b/components/ui/inputs/lane-selector/lane-selector.tsx
index d3e9cca881f4..e12d723298d9 100644
--- a/components/ui/inputs/lane-selector/lane-selector.tsx
+++ b/components/ui/inputs/lane-selector/lane-selector.tsx
@@ -29,6 +29,7 @@ export type LaneSelectorProps = {
   sortOptions?: LaneSelectorSortBy[];
   scopeIconLookup?: Map<string, React.ReactNode>;
   forceCloseOnEnter?: boolean;
+  loading?: boolean;
 } & HTMLAttributes<HTMLDivElement>;
 
 export type GroupedLaneDropdownItem = [scope: string, lanes: LaneModel[]];
@@ -61,6 +62,7 @@ export function LaneSelector(props: LaneSelectorProps) {
     scopeIcon,
     scopeIconLookup,
     forceCloseOnEnter,
+    loading,
     ...rest
   } = props;
 
@@ -235,7 +237,12 @@ export function LaneSelector(props: LaneSelectorProps) {
           });
         }}
         placeholderContent={
-          <LanePlaceholder disabled={!multipleLanes} selectedLaneId={selectedLaneId} showScope={groupByScope} />
+          <LanePlaceholder
+            loading={loading}
+            disabled={!multipleLanes}
+            selectedLaneId={selectedLaneId}
+            showScope={groupByScope}
+          />
         }
         className={classnames(styles.dropdown, !multipleLanes && styles.disabled)}
       >
diff --git a/components/ui/navigation/lane-switcher/lane-switcher.module.scss b/components/ui/navigation/lane-switcher/lane-switcher.module.scss
index 08e4c1795efe..14ae9c1fa63e 100644
--- a/components/ui/navigation/lane-switcher/lane-switcher.module.scss
+++ b/components/ui/navigation/lane-switcher/lane-switcher.module.scss
@@ -35,3 +35,12 @@
   flex: 0;
   width: 36px;
 }
+
+.loader {
+  color: var(--bit-bg-dent, #f6f6f6);
+  padding: 4px 0px;
+
+  > span {
+    padding: 8px;
+  }
+}
diff --git a/components/ui/navigation/lane-switcher/lane-switcher.tsx b/components/ui/navigation/lane-switcher/lane-switcher.tsx
index 14842a91de37..b44afc7addae 100644
--- a/components/ui/navigation/lane-switcher/lane-switcher.tsx
+++ b/components/ui/navigation/lane-switcher/lane-switcher.tsx
@@ -1,9 +1,10 @@
-import React, { HTMLAttributes, useEffect, useState, useRef } from 'react';
+import React, { HTMLAttributes, useRef } from 'react';
 import { useLanes as defaultUseLanes } from '@teambit/lanes.hooks.use-lanes';
 import { LaneSelector, LaneSelectorSortBy } from '@teambit/lanes.ui.inputs.lane-selector';
 import { LanesModel } from '@teambit/lanes.ui.models.lanes-model';
 import { MenuLinkItem } from '@teambit/design.ui.surfaces.menu.link-item';
 import { LaneId } from '@teambit/lane-id';
+import { WordSkeleton } from '@teambit/base-ui.loaders.skeleton';
 import classnames from 'classnames';
 
 import styles from './lane-switcher.module.scss';
@@ -29,10 +30,8 @@ export function LaneSwitcher({
   getHref = LanesModel.getLaneUrl,
   ...rest
 }: LaneSwitcherProps) {
-  const { lanesModel } = useLanes();
-  const [viewedLane, setViewedLane] = useState(lanesModel?.viewedLane);
   const containerRef = useRef<HTMLDivElement>(null);
-
+  const { lanesModel, loading } = useLanes();
   const mainLane = lanesModel?.getDefaultLane();
   const nonMainLanes = lanesModel?.getNonMainLanes() || [];
 
@@ -45,37 +44,37 @@ export function LaneSwitcher({
       : []
   );
 
-  useEffect(() => {
-    if (lanesModel?.viewedLane?.id.toString() !== viewedLane?.id.toString()) {
-      setViewedLane(lanesModel?.viewedLane);
-    }
-  }, [lanesModel?.viewedLane?.id.toString()]);
-
-  const selectedLane = viewedLane || mainLane;
+  const selectedLane = lanesModel?.viewedLane || mainLane;
   const selectedLaneGalleryHref = selectedLane && getHref(selectedLane.id);
 
   return (
     <div className={classnames(styles.laneSwitcherContainer, className)} ref={containerRef}>
       <div className={styles.laneSelectorContainer}>
-        <LaneSelector
-          selectedLaneId={selectedLane?.id}
-          nonMainLanes={nonMainLanes}
-          mainLane={mainLane}
-          mainIcon={mainIcon?.()}
-          scopeIcon={scopeIcon}
-          groupByScope={groupByScope}
-          sortBy={sortBy}
-          sortOptions={sortOptions}
-          scopeIconLookup={scopeIconLookup}
-          getHref={getHref}
-          {...rest}
-        />
-      </div>
-      <div className={styles.laneIconContainer}>
-        <MenuLinkItem exact={true} className={styles.laneGalleryIcon} href={selectedLaneGalleryHref}>
-          <img src="https://static.bit.dev/bit-icons/corner-up-left.svg" />
-        </MenuLinkItem>
+        {loading && <WordSkeleton className={styles.loader} length={24} />}
+        {
+          <LaneSelector
+            selectedLaneId={selectedLane?.id}
+            nonMainLanes={nonMainLanes}
+            mainLane={mainLane}
+            mainIcon={mainIcon?.()}
+            scopeIcon={scopeIcon}
+            groupByScope={groupByScope}
+            sortBy={sortBy}
+            sortOptions={sortOptions}
+            scopeIconLookup={scopeIconLookup}
+            getHref={getHref}
+            loading={loading}
+            {...rest}
+          />
+        }
       </div>
+      {!loading && (
+        <div className={styles.laneIconContainer}>
+          <MenuLinkItem exact={true} className={styles.laneGalleryIcon} href={selectedLaneGalleryHref}>
+            <img src="https://static.bit.dev/bit-icons/corner-up-left.svg" />
+          </MenuLinkItem>
+        </div>
+      )}
     </div>
   );
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8fab5202d027..28f4a49fdda9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -39120,7 +39120,7 @@ packages:
     dev: false
 
   /@teambit/component.modules.component-url@0.0.128(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-E5szS9hGfk/lAwEma3/ViMkm32A=, tarball: https://node-registry.bit.cloud/@teambit/component.modules.component-url/-/teambit-component.modules.component-url-0.0.128.tgz}
+    resolution: {integrity: sha1-E5szS9hGfk/lAwEma3/ViMkm32A=, tarball: https://node-registry.bit.cloud/@teambit/component.modules.component-url/-/@teambit-component.modules.component-url-0.0.128.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: 17.0.2
@@ -39134,7 +39134,7 @@ packages:
     dev: false
 
   /@teambit/component.modules.component-url@0.0.140(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-cmcWwISlL+PSKta3Q4HkpLTARBc=, tarball: https://node-registry.bit.cloud/@teambit/component.modules.component-url/-/teambit-component.modules.component-url-0.0.140.tgz}
+    resolution: {integrity: sha1-cmcWwISlL+PSKta3Q4HkpLTARBc=, tarball: https://node-registry.bit.cloud/@teambit/component.modules.component-url/-/@teambit-component.modules.component-url-0.0.140.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: 17.0.2
@@ -39148,7 +39148,7 @@ packages:
     dev: false
 
   /@teambit/component.modules.component-url@0.0.151(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-/dj3W/oKQC/p0X74zRJ+Mov/bec=, tarball: https://node-registry.bit.cloud/@teambit/component.modules.component-url/-/teambit-component.modules.component-url-0.0.151.tgz}
+    resolution: {integrity: sha1-/dj3W/oKQC/p0X74zRJ+Mov/bec=, tarball: https://node-registry.bit.cloud/@teambit/component.modules.component-url/-/@teambit-component.modules.component-url-0.0.151.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: 17.0.2
@@ -39175,7 +39175,7 @@ packages:
     dev: false
 
   /@teambit/component.ui.badges.component-count@0.0.10(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-/KG3gyg1xt3Gk+7z1Yf2Ww9qd50=, tarball: https://node-registry.bit.cloud/tarballs/teambit.component/ui/badges/component-count@0.0.10.tgz}
+    resolution: {integrity: sha1-/KG3gyg1xt3Gk+7z1Yf2Ww9qd50=, tarball: https://node-registry.bit.cloud/@teambit/component.ui.badges.component-count/-/@teambit-component.ui.badges.component-count-0.0.10.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -39207,7 +39207,7 @@ packages:
     dev: false
 
   /@teambit/component.ui.component-compare.hooks.use-component-compare-url@0.0.3(@testing-library/react@12.1.5)(@types/react@17.0.8)(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-mNono316l0rxMgrR/xuTDv4Rp/E=, tarball: https://node-registry.bit.cloud/@teambit/component.ui.component-compare.hooks.use-component-compare-url/-/teambit-component.ui.component-compare.hooks.use-component-compare-url-0.0.3.tgz}
+    resolution: {integrity: sha1-mNono316l0rxMgrR/xuTDv4Rp/E=, tarball: https://node-registry.bit.cloud/@teambit/component.ui.component-compare.hooks.use-component-compare-url/-/@teambit-component.ui.component-compare.hooks.use-component-compare-url-0.0.3.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -39237,7 +39237,7 @@ packages:
     dev: false
 
   /@teambit/component.ui.component-compare.models.component-compare-change-type@0.0.1(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-2Er9FIkcKdYqxW2aeyZ5Gd370Kc=, tarball: https://node-registry.bit.cloud/@teambit/component.ui.component-compare.models.component-compare-change-type/-/teambit-component.ui.component-compare.models.component-compare-change-type-0.0.1.tgz}
+    resolution: {integrity: sha1-2Er9FIkcKdYqxW2aeyZ5Gd370Kc=, tarball: https://node-registry.bit.cloud/@teambit/component.ui.component-compare.models.component-compare-change-type/-/@teambit-component.ui.component-compare.models.component-compare-change-type-0.0.1.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -39249,7 +39249,7 @@ packages:
     dev: false
 
   /@teambit/component.ui.component-compare.models.component-compare-hooks@0.0.4(@testing-library/react@12.1.5)(@types/react@17.0.8)(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-kVPksYnom+gsZNPMZVx6uJqAHlI=, tarball: https://node-registry.bit.cloud/@teambit/component.ui.component-compare.models.component-compare-hooks/-/teambit-component.ui.component-compare.models.component-compare-hooks-0.0.4.tgz}
+    resolution: {integrity: sha1-kVPksYnom+gsZNPMZVx6uJqAHlI=, tarball: https://node-registry.bit.cloud/@teambit/component.ui.component-compare.models.component-compare-hooks/-/@teambit-component.ui.component-compare.models.component-compare-hooks-0.0.4.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -39328,7 +39328,7 @@ packages:
     dev: false
 
   /@teambit/component.ui.component-compare.models.component-compare-state@0.0.2(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-KOvqyfEiwlEHJBWwxZZtLxWBc74=, tarball: https://node-registry.bit.cloud/@teambit/component.ui.component-compare.models.component-compare-state/-/teambit-component.ui.component-compare.models.component-compare-state-0.0.2.tgz}
+    resolution: {integrity: sha1-KOvqyfEiwlEHJBWwxZZtLxWBc74=, tarball: https://node-registry.bit.cloud/@teambit/component.ui.component-compare.models.component-compare-state/-/@teambit-component.ui.component-compare.models.component-compare-state-0.0.2.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -39354,7 +39354,7 @@ packages:
     dev: false
 
   /@teambit/component.ui.component-compare.utils.lazy-loading@0.0.1(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-K4iYPn+ng7gCPPwwQ3C3vdAWzDE=, tarball: https://node-registry.bit.cloud/@teambit/component.ui.component-compare.utils.lazy-loading/-/teambit-component.ui.component-compare.utils.lazy-loading-0.0.1.tgz}
+    resolution: {integrity: sha1-K4iYPn+ng7gCPPwwQ3C3vdAWzDE=, tarball: https://node-registry.bit.cloud/@teambit/component.ui.component-compare.utils.lazy-loading/-/@teambit-component.ui.component-compare.utils.lazy-loading-0.0.1.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -39367,7 +39367,7 @@ packages:
     dev: false
 
   /@teambit/component.ui.deprecation-icon@0.0.494(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-h1I4/6SkzcVxwWznLLLEW4M8r6g=, tarball: https://node-registry.bit.cloud/tarballs/teambit.component/ui/deprecation-icon@0.0.494.tgz}
+    resolution: {integrity: sha1-h1I4/6SkzcVxwWznLLLEW4M8r6g=, tarball: https://node-registry.bit.cloud/@teambit/component.ui.deprecation-icon/-/@teambit-component.ui.deprecation-icon-0.0.494.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -39384,7 +39384,7 @@ packages:
     dev: false
 
   /@teambit/component.ui.deprecation-icon@0.0.500(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-KGzcqsYSN1S/mTpBE7Ohs8qm414=, tarball: https://node-registry.bit.cloud/tarballs/teambit.component/ui/deprecation-icon@0.0.500.tgz}
+    resolution: {integrity: sha1-KGzcqsYSN1S/mTpBE7Ohs8qm414=, tarball: https://node-registry.bit.cloud/@teambit/component.ui.deprecation-icon/-/@teambit-component.ui.deprecation-icon-0.0.500.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -40368,7 +40368,7 @@ packages:
     dev: false
 
   /@teambit/design.ui.pill-label@0.0.350(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-gPPLmQYfh6MMno1X4Hnu3eG+Pqw=, tarball: https://node-registry.bit.cloud/tarballs/teambit.design/ui/pill-label@0.0.350.tgz}
+    resolution: {integrity: sha1-gPPLmQYfh6MMno1X4Hnu3eG+Pqw=, tarball: https://node-registry.bit.cloud/@teambit/design.ui.pill-label/-/@teambit-design.ui.pill-label-0.0.350.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -40999,7 +40999,7 @@ packages:
     dev: false
 
   /@teambit/documenter.ui.heading@4.1.6(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-nCoruiI/7n5xcXd4GEMdtuU5nIU=, tarball: https://node-registry.bit.cloud/tarballs/teambit.documenter/ui/heading@4.1.6.tgz}
+    resolution: {integrity: sha1-nCoruiI/7n5xcXd4GEMdtuU5nIU=, tarball: https://node-registry.bit.cloud/@teambit/documenter.ui.heading/-/@teambit-documenter.ui.heading-4.1.6.tgz}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
       react-dom: ^16.8.0 || ^17.0.0
@@ -41062,7 +41062,7 @@ packages:
     dev: false
 
   /@teambit/documenter.ui.linked-heading@4.1.8(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-LGpmPIXZSWsXbYgkS/WR1VxJcCg=, tarball: https://node-registry.bit.cloud/tarballs/teambit.documenter/ui/linked-heading@4.1.8.tgz}
+    resolution: {integrity: sha1-LGpmPIXZSWsXbYgkS/WR1VxJcCg=, tarball: https://node-registry.bit.cloud/@teambit/documenter.ui.linked-heading/-/@teambit-documenter.ui.linked-heading-4.1.8.tgz}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
       react-dom: ^16.8.0 || ^17.0.0
@@ -41276,7 +41276,7 @@ packages:
     dev: false
 
   /@teambit/envs.ui.env-icon@0.0.486(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-w7KPvgzoXLcmnBjlu8gNuRffmsA=, tarball: https://node-registry.bit.cloud/tarballs/teambit.envs/ui/env-icon@0.0.486.tgz}
+    resolution: {integrity: sha1-w7KPvgzoXLcmnBjlu8gNuRffmsA=, tarball: https://node-registry.bit.cloud/@teambit/envs.ui.env-icon/-/@teambit-envs.ui.env-icon-0.0.486.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -41288,7 +41288,7 @@ packages:
     dev: false
 
   /@teambit/envs.ui.env-icon@0.0.492(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-WSxQWxQY/fhy5bqXSjp3fz3XUDY=, tarball: https://node-registry.bit.cloud/tarballs/teambit.envs/ui/env-icon@0.0.492.tgz}
+    resolution: {integrity: sha1-WSxQWxQY/fhy5bqXSjp3fz3XUDY=, tarball: https://node-registry.bit.cloud/@teambit/envs.ui.env-icon/-/@teambit-envs.ui.env-icon-0.0.492.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -41619,7 +41619,7 @@ packages:
       react-dom: 17.0.2(react@17.0.2)
 
   /@teambit/explorer.ui.gallery.component-card@0.0.495(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-gkm3X+OtPpzJSabL3xj7V6z75XA=, tarball: https://node-registry.bit.cloud/tarballs/teambit.explorer/ui/gallery/component-card@0.0.495.tgz}
+    resolution: {integrity: sha1-gkm3X+OtPpzJSabL3xj7V6z75XA=, tarball: https://node-registry.bit.cloud/@teambit/explorer.ui.gallery.component-card/-/@teambit-explorer.ui.gallery.component-card-0.0.495.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -41649,7 +41649,7 @@ packages:
     dev: false
 
   /@teambit/explorer.ui.gallery.component-card@0.0.507(@testing-library/react@12.1.5)(@types/react@17.0.8)(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-p3EePsVfQINlcsiNPimNv3dK1Po=, tarball: https://node-registry.bit.cloud/tarballs/teambit.explorer/ui/gallery/component-card@0.0.507.tgz}
+    resolution: {integrity: sha1-p3EePsVfQINlcsiNPimNv3dK1Po=, tarball: https://node-registry.bit.cloud/@teambit/explorer.ui.gallery.component-card/-/@teambit-explorer.ui.gallery.component-card-0.0.507.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -41829,7 +41829,7 @@ packages:
       user-home: 2.0.0
 
   /@teambit/html.modules.create-element-from-string@0.0.104:
-    resolution: {integrity: sha1-yk2HNfv5z+0qh75v4SL0qkRNP9U=, tarball: https://node-registry.bit.cloud/tarballs/teambit.html/modules/create-element-from-string@0.0.104.tgz}
+    resolution: {integrity: sha1-yk2HNfv5z+0qh75v4SL0qkRNP9U=, tarball: https://node-registry.bit.cloud/@teambit/html.modules.create-element-from-string/-/@teambit-html.modules.create-element-from-string-0.0.104.tgz}
     engines: {node: '>=12.22.0'}
     dev: false
 
@@ -41858,7 +41858,7 @@ packages:
     dev: false
 
   /@teambit/lanes.hooks.use-lane-components@0.0.149(@apollo/client@3.6.9)(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-QnNvne623/kMU2CO4PdxzQk1JKo=, tarball: https://node-registry.bit.cloud/@teambit/lanes.hooks.use-lane-components/-/teambit-lanes.hooks.use-lane-components-0.0.149.tgz}
+    resolution: {integrity: sha1-QnNvne623/kMU2CO4PdxzQk1JKo=, tarball: https://node-registry.bit.cloud/@teambit/lanes.hooks.use-lane-components/-/@teambit-lanes.hooks.use-lane-components-0.0.149.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       '@apollo/client': ^3.6.0
@@ -42343,7 +42343,7 @@ packages:
     dev: false
 
   /@teambit/preview.ui.preview-placeholder@0.0.496(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-MgSJUaYFpLjSPyLWttgR63sM/Xg=, tarball: https://node-registry.bit.cloud/tarballs/teambit.preview/ui/preview-placeholder@0.0.496.tgz}
+    resolution: {integrity: sha1-MgSJUaYFpLjSPyLWttgR63sM/Xg=, tarball: https://node-registry.bit.cloud/@teambit/preview.ui.preview-placeholder/-/@teambit-preview.ui.preview-placeholder-0.0.496.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -42368,7 +42368,6 @@ packages:
       react-dom: 17.0.2(react@17.0.2)
     transitivePeerDependencies:
       - '@teambit/legacy'
-    dev: true
 
   /@teambit/react.instructions.react-native.adding-tests@0.0.1(react-dom@17.0.2)(react@17.0.2):
     resolution: {integrity: sha1-Ej1pIaYbWdwLNUbsHMjEQQ/utnQ=, tarball: https://node-registry.bit.cloud/tarballs/teambit.react/instructions/react-native/adding-tests@0.0.1.tgz}
@@ -42522,7 +42521,7 @@ packages:
     dev: false
 
   /@teambit/scope.ui.hooks.use-scope@0.0.240(@apollo/client@3.6.9)(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-ArQ+DdQNq/Aw2rjpUZcWpcveyck=, tarball: https://node-registry.bit.cloud/tarballs/teambit.scope/ui/hooks/use-scope@0.0.240.tgz}
+    resolution: {integrity: sha1-ArQ+DdQNq/Aw2rjpUZcWpcveyck=, tarball: https://node-registry.bit.cloud/@teambit/scope.ui.hooks.use-scope/-/@teambit-scope.ui.hooks.use-scope-0.0.240.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       '@apollo/client': ^3.6.0
@@ -42590,7 +42589,7 @@ packages:
     dev: false
 
   /@teambit/scope.ui.scope-title@0.0.508(@testing-library/react@12.1.5)(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-2lOC51kJif9bWAma5M4yaPPo/U4=, tarball: https://node-registry.bit.cloud/@teambit/scope.ui.scope-title/-/teambit-scope.ui.scope-title-0.0.508.tgz}
+    resolution: {integrity: sha1-2lOC51kJif9bWAma5M4yaPPo/U4=, tarball: https://node-registry.bit.cloud/@teambit/scope.ui.scope-title/-/@teambit-scope.ui.scope-title-0.0.508.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -42662,16 +42661,16 @@ packages:
     dev: false
 
   /@teambit/toolbox.string.ellipsis@0.0.172:
-    resolution: {integrity: sha1-nbLwbPi2d8Qeg6aCX2TYlARSRew=, tarball: https://node-registry.bit.cloud/tarballs/teambit.toolbox/string/ellipsis@0.0.172.tgz}
+    resolution: {integrity: sha1-nbLwbPi2d8Qeg6aCX2TYlARSRew=, tarball: https://node-registry.bit.cloud/@teambit/toolbox.string.ellipsis/-/@teambit-toolbox.string.ellipsis-0.0.172.tgz}
     engines: {node: '>=12.22.0'}
     dev: true
 
   /@teambit/toolbox.string.ellipsis@0.0.173:
-    resolution: {integrity: sha1-CY+uj2sRmPejtTCee4HDvLFeeI8=, tarball: https://node-registry.bit.cloud/tarballs/teambit.toolbox/string/ellipsis@0.0.173.tgz}
+    resolution: {integrity: sha1-CY+uj2sRmPejtTCee4HDvLFeeI8=, tarball: https://node-registry.bit.cloud/@teambit/toolbox.string.ellipsis/-/@teambit-toolbox.string.ellipsis-0.0.173.tgz}
     engines: {node: '>=12.22.0'}
 
   /@teambit/toolbox.string.ellipsis@0.0.181:
-    resolution: {integrity: sha1-85eglHHFlGiDBGQi9V5m4z9Tr+w=, tarball: https://node-registry.bit.cloud/tarballs/teambit.toolbox/string/ellipsis@0.0.181.tgz}
+    resolution: {integrity: sha1-85eglHHFlGiDBGQi9V5m4z9Tr+w=, tarball: https://node-registry.bit.cloud/@teambit/toolbox.string.ellipsis/-/@teambit-toolbox.string.ellipsis-0.0.181.tgz}
     engines: {node: '>=12.22.0'}
     dev: false
 
@@ -42681,17 +42680,17 @@ packages:
     dev: false
 
   /@teambit/toolbox.string.get-initials@0.0.491:
-    resolution: {integrity: sha1-dMkF7hxhtmK2aXz7QCkAAajSrKI=, tarball: https://node-registry.bit.cloud/tarballs/teambit.toolbox/string/get-initials@0.0.491.tgz}
+    resolution: {integrity: sha1-dMkF7hxhtmK2aXz7QCkAAajSrKI=, tarball: https://node-registry.bit.cloud/@teambit/toolbox.string.get-initials/-/@teambit-toolbox.string.get-initials-0.0.491.tgz}
     engines: {node: '>=12.22.0'}
     dev: false
 
   /@teambit/toolbox.types.serializable@0.0.483:
-    resolution: {integrity: sha1-bUTnjhsNOvMScS8+fSEk80Bvq3E=, tarball: https://node-registry.bit.cloud/tarballs/teambit.toolbox/types/serializable@0.0.483.tgz}
+    resolution: {integrity: sha1-bUTnjhsNOvMScS8+fSEk80Bvq3E=, tarball: https://node-registry.bit.cloud/@teambit/toolbox.types.serializable/-/@teambit-toolbox.types.serializable-0.0.483.tgz}
     engines: {node: '>=12.22.0'}
     dev: true
 
   /@teambit/toolbox.types.serializable@0.0.491:
-    resolution: {integrity: sha1-VTrj6yH43qYR2efWo5lAnh4dizI=, tarball: https://node-registry.bit.cloud/tarballs/teambit.toolbox/types/serializable@0.0.491.tgz}
+    resolution: {integrity: sha1-VTrj6yH43qYR2efWo5lAnh4dizI=, tarball: https://node-registry.bit.cloud/@teambit/toolbox.types.serializable/-/@teambit-toolbox.types.serializable-0.0.491.tgz}
     engines: {node: '>=12.22.0'}
     dev: false
 
@@ -42701,7 +42700,7 @@ packages:
     dev: false
 
   /@teambit/toolbox.url.add-avatar-query-params@0.0.492:
-    resolution: {integrity: sha1-dECMYTXTkMlAIUaK22cpmNlTsak=, tarball: https://node-registry.bit.cloud/tarballs/teambit.toolbox/url/add-avatar-query-params@0.0.492.tgz}
+    resolution: {integrity: sha1-dECMYTXTkMlAIUaK22cpmNlTsak=, tarball: https://node-registry.bit.cloud/@teambit/toolbox.url.add-avatar-query-params/-/@teambit-toolbox.url.add-avatar-query-params-0.0.492.tgz}
     engines: {node: '>=12.22.0'}
     dev: false
 
@@ -42729,7 +42728,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.constants.z-indexes@0.0.498(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-q5rrOHpQsr+mATrJu4oRiyU2M2E=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/constants/z-indexes@0.0.498.tgz}
+    resolution: {integrity: sha1-q5rrOHpQsr+mATrJu4oRiyU2M2E=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.constants.z-indexes/-/@teambit-ui-foundation.ui.constants.z-indexes-0.0.498.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -42760,7 +42759,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.empty-component-gallery@0.0.502(@testing-library/react@12.1.5)(@types/react@17.0.8)(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-nbagGhpo63WfemtMvlC2tuGRsok=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/empty-component-gallery@0.0.502.tgz}
+    resolution: {integrity: sha1-nbagGhpo63WfemtMvlC2tuGRsok=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.empty-component-gallery/-/@teambit-ui-foundation.ui.empty-component-gallery-0.0.502.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -42804,7 +42803,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.get-icon-from-file-name@0.0.495(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-KzxZCIVyVGbjJHC01V/8RI6cyhY=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/get-icon-from-file-name@0.0.495.tgz}
+    resolution: {integrity: sha1-KzxZCIVyVGbjJHC01V/8RI6cyhY=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.get-icon-from-file-name/-/@teambit-ui-foundation.ui.get-icon-from-file-name-0.0.495.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -42817,7 +42816,7 @@ packages:
       vscode-icons-js: 11.0.0
 
   /@teambit/ui-foundation.ui.global-loader@0.0.474(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-7X0x1qLPQV7ySEUh5Rs1iXt+AX4=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/global-loader@0.0.474.tgz}
+    resolution: {integrity: sha1-7X0x1qLPQV7ySEUh5Rs1iXt+AX4=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.global-loader/-/@teambit-ui-foundation.ui.global-loader-0.0.474.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       '@teambit/legacy': 1.0.183
@@ -42831,7 +42830,7 @@ packages:
     dev: true
 
   /@teambit/ui-foundation.ui.global-loader@0.0.487(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-t9IdwYuJBCHPUYK73ELRUHQHPVs=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/global-loader@0.0.487.tgz}
+    resolution: {integrity: sha1-t9IdwYuJBCHPUYK73ELRUHQHPVs=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.global-loader/-/@teambit-ui-foundation.ui.global-loader-0.0.487.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -42844,7 +42843,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.global-loader@0.0.493(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-0DiiKiGSijunFyRRRSMD4R2iQMk=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/global-loader@0.0.493.tgz}
+    resolution: {integrity: sha1-0DiiKiGSijunFyRRRSMD4R2iQMk=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.global-loader/-/@teambit-ui-foundation.ui.global-loader-0.0.493.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -42857,7 +42856,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.global-loader@0.0.497(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-5fU/29iOW7vvZBaoyaRiSUCyl2I=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/global-loader@0.0.497.tgz}
+    resolution: {integrity: sha1-5fU/29iOW7vvZBaoyaRiSUCyl2I=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.global-loader/-/@teambit-ui-foundation.ui.global-loader-0.0.497.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -42886,7 +42885,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.hooks.use-data-query@0.0.496(@apollo/client@3.6.9)(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-oL6ZvMzY0Y/1C9rivf+CdsmJFdI=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/hooks/use-data-query@0.0.496.tgz}
+    resolution: {integrity: sha1-oL6ZvMzY0Y/1C9rivf+CdsmJFdI=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.hooks.use-data-query/-/@teambit-ui-foundation.ui.hooks.use-data-query-0.0.496.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       '@apollo/client': ^3.6.0
@@ -42902,7 +42901,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.hooks.use-data-query@0.0.500(@apollo/client@3.6.9)(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-mKHB9+tWg+S+JJ5s4peW17vUf3s=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/hooks/use-data-query@0.0.500.tgz}
+    resolution: {integrity: sha1-mKHB9+tWg+S+JJ5s4peW17vUf3s=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.hooks.use-data-query/-/@teambit-ui-foundation.ui.hooks.use-data-query-0.0.500.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       '@apollo/client': ^3.6.0
@@ -42918,7 +42917,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.is-browser@0.0.486(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-KCR60Lp5r8XU/qN5aU6JnKyCo0U=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/is-browser@0.0.486.tgz}
+    resolution: {integrity: sha1-KCR60Lp5r8XU/qN5aU6JnKyCo0U=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.is-browser/-/@teambit-ui-foundation.ui.is-browser-0.0.486.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -42930,7 +42929,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.is-browser@0.0.492(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-K8F9fjln4vfhtryfNuzr8RulqLo=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/is-browser@0.0.492.tgz}
+    resolution: {integrity: sha1-K8F9fjln4vfhtryfNuzr8RulqLo=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.is-browser/-/@teambit-ui-foundation.ui.is-browser-0.0.492.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -42942,7 +42941,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.keycap@0.0.486(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-8os8TC+l8rJUXtJe4ehMT7H6+98=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/keycap@0.0.486.tgz}
+    resolution: {integrity: sha1-8os8TC+l8rJUXtJe4ehMT7H6+98=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.keycap/-/@teambit-ui-foundation.ui.keycap-0.0.486.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -42956,7 +42955,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.keycap@0.0.492(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-/XlMitYklsPYhMXE0hPJZw+1JHM=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/keycap@0.0.492.tgz}
+    resolution: {integrity: sha1-/XlMitYklsPYhMXE0hPJZw+1JHM=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.keycap/-/@teambit-ui-foundation.ui.keycap-0.0.492.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -43077,7 +43076,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.notifications.notification-context@0.0.487(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-WvEy3IkkGkdqrKLZvWO5ay+2LH4=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/notifications/notification-context@0.0.487.tgz}
+    resolution: {integrity: sha1-WvEy3IkkGkdqrKLZvWO5ay+2LH4=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.notifications.notification-context/-/@teambit-ui-foundation.ui.notifications.notification-context-0.0.487.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -43090,7 +43089,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.notifications.notification-context@0.0.493(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-LqvYMR2i+ZTOqyqWpBg/tXCzdgU=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/notifications/notification-context@0.0.493.tgz}
+    resolution: {integrity: sha1-LqvYMR2i+ZTOqyqWpBg/tXCzdgU=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.notifications.notification-context/-/@teambit-ui-foundation.ui.notifications.notification-context-0.0.493.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -43103,7 +43102,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.notifications.notification-context@0.0.496(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-x23R91AzUscSV2WS2yZC2LY8CHE=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/notifications/notification-context@0.0.496.tgz}
+    resolution: {integrity: sha1-x23R91AzUscSV2WS2yZC2LY8CHE=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.notifications.notification-context/-/@teambit-ui-foundation.ui.notifications.notification-context-0.0.496.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -43116,7 +43115,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.notifications.store@0.0.486(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-ZQ4iOfpuzj+jE12XS3b51WELT7I=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/notifications/store@0.0.486.tgz}
+    resolution: {integrity: sha1-ZQ4iOfpuzj+jE12XS3b51WELT7I=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.notifications.store/-/@teambit-ui-foundation.ui.notifications.store-0.0.486.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -43128,7 +43127,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.notifications.store@0.0.492(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-8IWuSf9Qt6QHdudIbjqQ7IHiMFg=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/notifications/store@0.0.492.tgz}
+    resolution: {integrity: sha1-8IWuSf9Qt6QHdudIbjqQ7IHiMFg=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.notifications.store/-/@teambit-ui-foundation.ui.notifications.store-0.0.492.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -43140,7 +43139,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.notifications.store@0.0.495(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-SciOZ58oyKDy8q/0nvQ4OpvuPz8=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/notifications/store@0.0.495.tgz}
+    resolution: {integrity: sha1-SciOZ58oyKDy8q/0nvQ4OpvuPz8=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.notifications.store/-/@teambit-ui-foundation.ui.notifications.store-0.0.495.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -43172,7 +43171,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.react-router.slot-router@0.0.501(@testing-library/react@12.1.5)(@types/react@17.0.8)(react-dom@17.0.2)(react-router-dom@6.3.0)(react@17.0.2):
-    resolution: {integrity: sha1-uUc0/zKzRjSFXhWhBUDlDnIJuO0=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/react-router/slot-router@0.0.501.tgz}
+    resolution: {integrity: sha1-uUc0/zKzRjSFXhWhBUDlDnIJuO0=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.react-router.slot-router/-/@teambit-ui-foundation.ui.react-router.slot-router-0.0.501.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -43192,7 +43191,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.react-router.use-query@0.0.496(@testing-library/react@12.1.5)(@types/react@17.0.8)(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-rCJcyZss+uCe7COvKqfs0UQ0TME=, tarball: https://node-registry.bit.cloud/tarballs/teambit.ui-foundation/ui/react-router/use-query@0.0.496.tgz}
+    resolution: {integrity: sha1-rCJcyZss+uCe7COvKqfs0UQ0TME=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.react-router.use-query/-/@teambit-ui-foundation.ui.react-router.use-query-0.0.496.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -43312,7 +43311,7 @@ packages:
     dev: false
 
   /@teambit/ui-foundation.ui.tree.drawer@0.0.512(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-bU5bUChvqZ6FgR2iDflWJU+hU8U=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.tree.drawer/-/teambit-ui-foundation.ui.tree.drawer-0.0.512.tgz}
+    resolution: {integrity: sha1-bU5bUChvqZ6FgR2iDflWJU+hU8U=, tarball: https://node-registry.bit.cloud/@teambit/ui-foundation.ui.tree.drawer/-/@teambit-ui-foundation.ui.tree.drawer-0.0.512.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -43445,7 +43444,7 @@ packages:
       - '@teambit/legacy'
 
   /@teambit/workspace.ui.load-preview@0.0.489(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-e9OonGao9UxvvbCfsJKdGTPbXIg=, tarball: https://node-registry.bit.cloud/tarballs/teambit.workspace/ui/load-preview@0.0.489.tgz}
+    resolution: {integrity: sha1-e9OonGao9UxvvbCfsJKdGTPbXIg=, tarball: https://node-registry.bit.cloud/@teambit/workspace.ui.load-preview/-/@teambit-workspace.ui.load-preview-0.0.489.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -43459,7 +43458,7 @@ packages:
     dev: false
 
   /@teambit/workspace.ui.load-preview@0.0.499(@testing-library/react@12.1.5)(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-lvfO7zmO9ZnlI+NPWAFOW3ViZwo=, tarball: https://node-registry.bit.cloud/tarballs/teambit.workspace/ui/load-preview@0.0.499.tgz}
+    resolution: {integrity: sha1-lvfO7zmO9ZnlI+NPWAFOW3ViZwo=, tarball: https://node-registry.bit.cloud/@teambit/workspace.ui.load-preview/-/@teambit-workspace.ui.load-preview-0.0.499.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       '@testing-library/react': ^12.1.5
@@ -43493,7 +43492,7 @@ packages:
     dev: false
 
   /@teambit/workspace.ui.workspace-component-card@0.0.510(@testing-library/react@12.1.5)(@types/react@17.0.8)(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha1-bO15xcj1TSQ5iEfw8QBu6GxTigM=, tarball: https://node-registry.bit.cloud/tarballs/teambit.workspace/ui/workspace-component-card@0.0.510.tgz}
+    resolution: {integrity: sha1-bO15xcj1TSQ5iEfw8QBu6GxTigM=, tarball: https://node-registry.bit.cloud/@teambit/workspace.ui.workspace-component-card/-/@teambit-workspace.ui.workspace-component-card-0.0.510.tgz}
     engines: {node: '>=12.22.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0
@@ -69482,6 +69481,7 @@ packages:
       '@teambit/design.ui.input.option-button': 1.0.0(react-dom@17.0.2)(react@17.0.2)
       '@teambit/design.ui.tooltip': 0.0.361(react-dom@17.0.2)(react@17.0.2)
       '@teambit/harmony': 0.4.6
+      '@teambit/react.content.react-overview': 1.95.0(react-dom@17.0.2)(react@17.0.2)
       '@teambit/react.instructions.react.adding-compositions': registry.npmjs.org/@teambit/react.instructions.react.adding-compositions@0.0.6(react-dom@17.0.2)(react@17.0.2)
       '@teambit/react.instructions.react.adding-tests': registry.npmjs.org/@teambit/react.instructions.react.adding-tests@0.0.6(react-dom@17.0.2)(react@17.0.2)
       '@teambit/react.rendering.ssr': 0.0.3(react-dom@17.0.2)(react@17.0.2)
@@ -71471,6 +71471,7 @@ packages:
     version: 0.0.10
     dependencies:
       expose-loader: 3.1.0(webpack@5.82.1)
+      json-formatter-js: 2.3.4
     transitivePeerDependencies:
       - webpack
     dev: false
diff --git a/scopes/component/component/component-factory.ts b/scopes/component/component/component-factory.ts
index 7b2437dffa3b..4bbd20a5392b 100644
--- a/scopes/component/component/component-factory.ts
+++ b/scopes/component/component/component-factory.ts
@@ -115,7 +115,15 @@ export interface ComponentFactory {
    */
   getGraphIds(ids?: ComponentID[], shouldThrowOnMissingDep?: boolean): Promise<CompIdGraph>;
 
-  getLogs(id: ComponentID, shortHash?: boolean, startsFrom?: string): Promise<ComponentLog[]>;
+  getLogs(
+    id: ComponentID,
+    shortHash?: boolean,
+    head?: string,
+    startFrom?: string,
+    stopAt?: string,
+    startFromOffset?: number,
+    stopAtOffset?: number
+  ): Promise<ComponentLog[]>;
 
   /**
    * returns a specific state of a component by hash or semver.
diff --git a/scopes/component/component/component.graphql.ts b/scopes/component/component/component.graphql.ts
index 1dc97ec2a591..4177e7895fea 100644
--- a/scopes/component/component/component.graphql.ts
+++ b/scopes/component/component/component.graphql.ts
@@ -115,6 +115,14 @@ export function componentSchema(componentExtension: ComponentMain) {
           start traversing logs from the fetched component's head
           """
           takeHeadFromComponent: Boolean
+          """
+          start slicing logs from this version
+          """
+          startFrom: String
+          """
+          end slicing logs until this version
+          """
+          until: String
         ): [LogEntry]!
 
         aspects(include: [String]): [Aspect]
@@ -200,6 +208,8 @@ export function componentSchema(componentExtension: ComponentMain) {
             head?: string;
             sort?: string;
             takeHeadFromComponent: boolean;
+            startFrom?: string;
+            until?: string;
           }
         ) => {
           let head = filter?.head;
diff --git a/scopes/component/component/component.ts b/scopes/component/component/component.ts
index 77063bf01935..c1b489f9cd36 100644
--- a/scopes/component/component/component.ts
+++ b/scopes/component/component/component.ts
@@ -114,26 +114,31 @@ export class Component implements IComponent {
     return this.state.aspects.get(id)?.serialize();
   }
 
-  async getLogs(filter?: { type?: string; offset?: number; limit?: number; head?: string; sort?: string }) {
-    const allLogs = await this.factory.getLogs(this.id, false, filter?.head);
-
-    if (!filter) return allLogs;
-
-    const { type, limit, offset, sort } = filter;
-
+  async getLogs(filter?: {
+    type?: string;
+    offset?: number;
+    limit?: number;
+    head?: string;
+    sort?: string;
+    startFrom?: string;
+    until?: string;
+  }) {
+    const { type, limit, offset, sort, head, startFrom, until } = filter || {};
     const typeFilter = (snap) => {
       if (type === 'tag') return snap.tag;
       if (type === 'snap') return !snap.tag;
       return true;
     };
 
-    let filteredLogs = (type && allLogs.filter(typeFilter)) || allLogs;
-    if (sort !== 'asc') filteredLogs = filteredLogs.reverse();
+    const allLogs = await this.factory.getLogs(this.id, false, head, startFrom, until, offset, limit);
 
-    if (limit) {
-      filteredLogs = slice(filteredLogs, offset, limit + (offset || 0));
+    if (!filter) {
+      return allLogs;
     }
 
+    let filteredLogs = (type && allLogs.filter(typeFilter)) || allLogs;
+    if (sort !== 'asc') filteredLogs = filteredLogs.reverse();
+
     return filteredLogs;
   }
 
diff --git a/scopes/component/component/index.ts b/scopes/component/component/index.ts
index e7a01c62cab0..9aebd0b6ddb7 100644
--- a/scopes/component/component/index.ts
+++ b/scopes/component/component/index.ts
@@ -33,7 +33,7 @@ export { Section } from './section';
 export { ComponentContext, ComponentDescriptorContext, useComponentDescriptor } from './ui/context/component-context';
 export type { ComponentProviderProps, ComponentDescriptorProviderProps } from './ui/context';
 export { ComponentProvider, ComponentDescriptorProvider } from './ui/context';
-export { componentFields, componentIdFields, componentOverviewFields } from './ui';
+export { componentFields, componentIdFields, componentOverviewFields, COMPONENT_QUERY_FIELDS } from './ui';
 export {
   NavPlugin,
   ConsumePlugin,
diff --git a/scopes/component/component/ui/component.tsx b/scopes/component/component/ui/component.tsx
index 1fb97abedd6b..47c83781d88c 100644
--- a/scopes/component/component/ui/component.tsx
+++ b/scopes/component/component/ui/component.tsx
@@ -55,19 +55,27 @@ export function Component({
   const resolvedComponentIdStr = path || idFromLocation;
   const componentFiltersFromProps = useComponentFilters?.() || {};
 
-  const useComponentOptions = {
-    logFilters: {
-      log: { logLimit: 3 },
-      ...componentFiltersFromProps,
-    },
-    customUseComponent: useComponent,
-  };
+  const useComponentOptions = componentFiltersFromProps.loading
+    ? {
+        logFilters: {
+          ...componentFiltersFromProps,
+        },
+        customUseComponent: useComponent,
+      }
+    : {
+        logFilters: {
+          ...componentFiltersFromProps,
+          log: { logLimit: 3, ...componentFiltersFromProps.log },
+        },
+        customUseComponent: useComponent,
+      };
 
   const { component, componentDescriptor, error } = useComponentQuery(
     host,
     componentId?.toString() || idFromLocation,
     useComponentOptions
   );
+
   // trigger onComponentChange when component changes
   useEffect(() => onComponentChange?.(component), [component]);
   // cleanup when unmounting component
@@ -77,7 +85,7 @@ export function Component({
   const before = useMemo(() => pageItems.filter((x) => x.type === 'before').map((x) => x.content), [pageItems]);
   const after = useMemo(() => pageItems.filter((x) => x.type === 'after').map((x) => x.content), [pageItems]);
 
-  if (error) return error.renderError();
+  if (error) return error?.renderError();
   if (!component) return <div></div>;
 
   return (
diff --git a/scopes/component/component/ui/index.ts b/scopes/component/component/ui/index.ts
index 5c8be5db4d79..c64fa8d42593 100644
--- a/scopes/component/component/ui/index.ts
+++ b/scopes/component/component/ui/index.ts
@@ -5,6 +5,11 @@ export { ComponentModel, ComponentModelProps } from './component-model';
 export { ComponentContext, ComponentProvider } from './context';
 export { useComponent } from './use-component';
 export { TopBarNav } from './top-bar-nav';
-export { componentIdFields, componentOverviewFields, componentFields } from './use-component-query';
+export {
+  componentIdFields,
+  componentOverviewFields,
+  componentFields,
+  COMPONENT_QUERY_FIELDS,
+} from './use-component-query';
 export type { ComponentQueryResult } from './use-component-query';
 export { useIdFromLocation } from './use-component-from-location';
diff --git a/scopes/component/component/ui/menu/menu.tsx b/scopes/component/component/ui/menu/menu.tsx
index ec6a6a2e8071..8056b027b1fb 100644
--- a/scopes/component/component/ui/menu/menu.tsx
+++ b/scopes/component/component/ui/menu/menu.tsx
@@ -55,6 +55,7 @@ export type MenuProps = {
   useComponent?: UseComponentType;
 
   useComponentFilters?: () => Filters;
+
   path?: string;
 };
 function getComponentIdStr(componentIdStr?: string | (() => string | undefined)): string | undefined {
@@ -83,6 +84,7 @@ export function ComponentMenu({
   const componentId = _componentIdStr ? ComponentID.fromString(_componentIdStr) : undefined;
   const resolvedComponentIdStr = path || idFromLocation;
   const mainMenuItems = useMemo(() => groupBy(flatten(menuItemSlot.values()), 'category'), [menuItemSlot]);
+  const { loading, ...componentFiltersFromProps } = useComponentFilters?.() || {};
 
   const RightSide = (
     <div className={styles.rightSide}>
@@ -93,7 +95,8 @@ export function ComponentMenu({
             host={host}
             componentId={componentId?.toString() || idFromLocation}
             useComponent={useComponent}
-            useComponentFilters={useComponentFilters}
+            componentFilters={componentFiltersFromProps}
+            loading={loading}
           />
           <MainDropdown className={styles.hideOnMobile} menuItems={mainMenuItems} />
         </>
@@ -122,58 +125,68 @@ export type VersionRelatedDropdownsProps = {
   consumeMethods?: ConsumeMethodSlot;
   className?: string;
   host: string;
-  useComponentFilters?: () => Filters;
+  componentFilters?: Filters;
+  loading?: boolean;
   useComponent?: UseComponentType;
   componentId?: string;
 };
 
 export function VersionRelatedDropdowns({
   componentId,
-  useComponentFilters,
+  componentFilters: componentFiltersFromProps = {},
   useComponent,
   consumeMethods,
   className,
+  loading: loadingFromProps,
   host,
 }: VersionRelatedDropdownsProps) {
-  const componentFiltersFromProps = useComponentFilters?.() || {};
   const query = useQuery();
   const componentVersion = query.get('version');
   const isTag = componentVersion ? semver.valid(componentVersion) : undefined;
   const isSnap = componentVersion ? !isTag : undefined;
 
   // initially fetch just the component data
-  const initialFetchOptions = {
-    logFilters: {
-      log: {
-        logLimit: 3,
+  const initialFetchOptions = React.useMemo(
+    () => ({
+      logFilters: {
+        ...componentFiltersFromProps,
+        log: {
+          logLimit: 3,
+          ...componentFiltersFromProps.log,
+        },
       },
-      ...componentFiltersFromProps,
-    },
-    customUseComponent: useComponent,
-  };
+      skip: loadingFromProps,
+      customUseComponent: useComponent,
+    }),
+    [loadingFromProps, componentFiltersFromProps, componentVersion]
+  );
 
-  const { component, loading } = useComponentQuery(host, componentId, initialFetchOptions);
+  const { component, loading: loadingComponent } = useComponentQuery(host, componentId, initialFetchOptions);
 
-  const componentWithLogsOptions = React.useMemo(
-    () => ({
+  const loading = React.useMemo(() => loadingComponent || loadingFromProps, [loadingComponent, loadingFromProps]);
+
+  const useVersions = React.useCallback(() => {
+    const componentWithLogsOptions = {
       logFilters: {
+        fetchLogsByTypeSeparately: true,
+        ...componentFiltersFromProps,
         snapLog: {
           logLimit: 10,
-          logHead: isSnap ? componentVersion : undefined,
+          logStartFrom: isSnap ? componentVersion ?? undefined : undefined,
+          logOffset: isSnap ? -3 : undefined,
+          ...componentFiltersFromProps.snapLog,
         },
         tagLog: {
           logLimit: 10,
-          logHead: isTag ? componentVersion : undefined,
+          logStartFrom: isTag ? componentVersion ?? undefined : undefined,
+          logOffset: isTag ? -3 : undefined,
+          ...componentFiltersFromProps.tagLog,
         },
-        fetchLogsByTypeSeparately: true,
-        ...componentFiltersFromProps,
       },
+      skip: loadingFromProps,
       customUseComponent: useComponent,
-    }),
-    [componentVersion, isTag, isSnap]
-  );
+    };
 
-  const useVersions = () => {
     const { componentLogs = {}, loading: loadingLogs } = useComponentQuery(host, componentId, componentWithLogsOptions);
     return {
       loading: loadingLogs,
@@ -181,7 +194,7 @@ export function VersionRelatedDropdowns({
       snaps: (componentLogs.snaps || []).map((snap) => ({ ...snap, version: snap.hash })),
       tags: (componentLogs.tags || []).map((tag) => ({ ...tag, version: tag.tag as string })),
     };
-  };
+  }, [componentVersion, isTag, isSnap, componentFiltersFromProps, loadingFromProps]);
 
   const location = useLocation();
   const { lanesModel } = useLanes();
diff --git a/scopes/component/component/ui/use-component-query.ts b/scopes/component/component/ui/use-component-query.ts
index 99cc38106175..aea5653c451f 100644
--- a/scopes/component/component/ui/use-component-query.ts
+++ b/scopes/component/component/ui/use-component-query.ts
@@ -85,6 +85,8 @@ export const componentFields = gql`
       sort: $logSort
       takeHeadFromComponent: $takeHeadFromComponent
       head: $logHead
+      startFrom: $logStartFrom
+      until: $logUntil
     ) @skip(if: $fetchLogsByTypeSeparately) {
       id
       message
@@ -101,6 +103,8 @@ export const componentFields = gql`
       sort: $tagLogSort
       takeHeadFromComponent: $tagTakeHeadFromComponent
       head: $tagLogHead
+      startFrom: $tagStartFrom
+      until: $tagUntil
     ) @include(if: $fetchLogsByTypeSeparately) {
       id
       message
@@ -117,6 +121,8 @@ export const componentFields = gql`
       sort: $snapLogSort
       takeHeadFromComponent: $snapTakeHeadFromComponent
       head: $snapLogHead
+      startFrom: $snapStartFrom
+      until: $snapUntil
     ) @include(if: $fetchLogsByTypeSeparately) {
       id
       message
@@ -131,20 +137,26 @@ export const componentFields = gql`
   ${componentOverviewFields}
 `;
 
-const COMPONENT_QUERY_FIELDS = `
+export const COMPONENT_QUERY_FIELDS = `
     $logOffset: Int
     $logLimit: Int
     $logType: String
     $logHead: String
     $logSort: String
+    $logStartFrom: String
+    $logUntil: String
     $tagLogOffset: Int
     $tagLogLimit: Int
     $tagLogHead: String
     $tagLogSort: String
+    $tagStartFrom: String
+    $tagUntil: String
     $snapLogOffset: Int
     $snapLogLimit: Int
     $snapLogHead: String
     $snapLogSort: String
+    $snapStartFrom: String
+    $snapUntil: String
     $takeHeadFromComponent: Boolean
     $tagTakeHeadFromComponent: Boolean
     $snapTakeHeadFromComponent: Boolean
@@ -203,6 +215,8 @@ export type LogFilter = {
   logOffset?: number;
   logLimit?: number;
   logHead?: string;
+  logStartFrom?: string;
+  logUntil?: string;
   logSort?: string;
   takeHeadFromComponent?: boolean;
 };
@@ -212,6 +226,7 @@ export type Filters = {
   tagLog?: LogFilter;
   snapLog?: LogFilter;
   fetchLogsByTypeSeparately?: boolean;
+  loading?: boolean;
 };
 
 export type ComponentQueryResult = {
@@ -222,16 +237,24 @@ export type ComponentQueryResult = {
     hasMoreLogs?: boolean;
     hasMoreSnaps?: boolean;
     hasMoreTags?: boolean;
-    loadMoreLogs?: () => void;
-    loadMoreTags?: () => void;
-    loadMoreSnaps?: () => void;
+    loadMoreLogs?: (backwards?: boolean) => void;
+    loadMoreTags?: (backwards?: boolean) => void;
+    loadMoreSnaps?: (backwards?: boolean) => void;
     snaps?: LegacyComponentLog[];
     tags?: LegacyComponentLog[];
   };
   loading?: boolean;
   error?: ComponentError;
 };
-
+function getOffsetValue(offset, limit) {
+  if (offset !== undefined) {
+    return offset;
+  }
+  if (limit !== undefined) {
+    return 0;
+  }
+  return undefined;
+}
 /** provides data to component ui page, making sure both variables and return value are safely typed and memoized */
 export function useComponentQuery(
   componentId: string,
@@ -248,21 +271,25 @@ export function useComponentQuery(
     logSort: tagLogSort,
     logLimit: tagLogLimit,
     takeHeadFromComponent: tagLogTakeHeadFromComponent,
+    logStartFrom: tagStartFrom,
+    logUntil: tagUntil,
   } = tagLog || {};
-  const { logHead, logOffset, logSort, logLimit, takeHeadFromComponent, logType } = log || {};
+  const { logHead, logOffset, logSort, logLimit, takeHeadFromComponent, logType, logStartFrom, logUntil } = log || {};
   const {
     logHead: snapLogHead,
     logOffset: snapLogOffset,
     logSort: snapLogSort,
     logLimit: snapLogLimit,
     takeHeadFromComponent: snapLogTakeHeadFromComponent,
+    logStartFrom: snapStartFrom,
+    logUntil: snapUntil,
   } = snapLog || {};
   const variables = {
     id: componentId,
     extensionId: host,
     fetchLogsByTypeSeparately,
-    snapLogOffset: snapLogOffset ?? snapLogLimit ? 0 : undefined,
-    tagLogOffset: tagLogOffset ?? tagLogLimit ? 0 : undefined,
+    snapLogOffset: getOffsetValue(snapLogOffset, snapLogLimit),
+    tagLogOffset: getOffsetValue(tagLogOffset, tagLogLimit),
     logOffset: logOffset ?? logLimit ? 0 : undefined,
     logLimit,
     snapLogLimit,
@@ -271,6 +298,12 @@ export function useComponentQuery(
     logHead,
     snapLogHead,
     tagLogHead,
+    logStartFrom,
+    snapStartFrom,
+    tagStartFrom,
+    logUntil,
+    snapUntil,
+    tagUntil,
     logSort,
     snapLogSort,
     tagLogSort,
@@ -335,116 +368,131 @@ export function useComponentQuery(
     return hasMoreSnapLogs.current;
   }, [rawSnaps]);
 
-  const loadMoreLogs = React.useCallback(async () => {
-    if (logLimit) {
-      await fetchMore({
-        variables: {
-          logOffset: offsetRef.current,
-        },
-        updateQuery: (prev, { fetchMoreResult }) => {
-          if (!fetchMoreResult) return prev;
-
-          const prevComponent = prev.getHost.get;
-          const fetchedComponent = fetchMoreResult.getHost.get;
-          if (fetchedComponent && ComponentID.isEqualObj(prevComponent.id, fetchedComponent.id)) {
-            const updatedLogs = mergeLogs(prevComponent.logs, fetchedComponent.logs);
-            hasMoreLogs.current = fetchedComponent.logs.length >= logLimit;
-            if (updatedLogs.length > prevComponent.logs.length) {
-              offsetRef.current = updatedLogs.length;
+  const loadMoreLogs = React.useCallback(
+    async (backwards = false) => {
+      const offset = offsetRef.current ? (backwards && -offsetRef.current) || offsetRef.current : undefined;
+
+      if (logLimit) {
+        await fetchMore({
+          variables: {
+            logOffset: offset,
+          },
+          updateQuery: (prev, { fetchMoreResult }) => {
+            if (!fetchMoreResult) return prev;
+
+            const prevComponent = prev.getHost.get;
+            const fetchedComponent = fetchMoreResult.getHost.get;
+            if (fetchedComponent && ComponentID.isEqualObj(prevComponent.id, fetchedComponent.id)) {
+              const updatedLogs = mergeLogs(prevComponent.logs, fetchedComponent.logs);
+              hasMoreLogs.current = fetchedComponent.logs.length >= logLimit;
+              if (updatedLogs.length > prevComponent.logs.length) {
+                offsetRef.current = updatedLogs.length;
+              }
+
+              return {
+                ...prev,
+                getHost: {
+                  ...prev.getHost,
+                  get: {
+                    ...prevComponent,
+                    logs: updatedLogs,
+                  },
+                },
+              };
             }
 
-            return {
-              ...prev,
-              getHost: {
-                ...prev.getHost,
-                get: {
-                  ...prevComponent,
-                  logs: updatedLogs,
-                },
-              },
-            };
-          }
+            return prev;
+          },
+        });
+      }
+    },
+    [logLimit, fetchMore]
+  );
 
-          return prev;
-        },
-      });
-    }
-  }, [logLimit, fetchMore]);
-
-  const loadMoreTags = React.useCallback(async () => {
-    if (tagLogLimit) {
-      await fetchMore({
-        variables: {
-          tagLogOffset: tagOffsetRef.current,
-          tagLogLimit,
-        },
-        updateQuery: (prev, { fetchMoreResult }) => {
-          if (!fetchMoreResult) return prev;
-
-          const prevComponent = prev.getHost.get;
-          const fetchedComponent = fetchMoreResult.getHost.get;
-          const prevCompLogs = prevComponent.tagLogs;
-          if (fetchedComponent && ComponentID.isEqualObj(prevComponent.id, fetchedComponent.id)) {
-            const updatedTags = mergeLogs(prevCompLogs, fetchedComponent.tagLogs);
-            if (updatedTags.length > prevCompLogs.length) {
-              tagOffsetRef.current = updatedTags.length;
-            }
-            hasMoreTagLogs.current = fetchedComponent.tagLogs.length >= tagLogLimit;
-            return {
-              ...prev,
-              getHost: {
-                ...prev.getHost,
-                get: {
-                  ...prevComponent,
-                  tagLogs: updatedTags,
+  const loadMoreTags = React.useCallback(
+    async (backwards = false) => {
+      const offset = tagOffsetRef.current ? (backwards && -tagOffsetRef.current) || tagOffsetRef.current : undefined;
+
+      if (tagLogLimit) {
+        await fetchMore({
+          variables: {
+            tagLogOffset: offset,
+            tagLogLimit,
+          },
+          updateQuery: (prev, { fetchMoreResult }) => {
+            if (!fetchMoreResult) return prev;
+
+            const prevComponent = prev.getHost.get;
+            const fetchedComponent = fetchMoreResult.getHost.get;
+            const prevCompLogs = prevComponent.tagLogs;
+            if (fetchedComponent && ComponentID.isEqualObj(prevComponent.id, fetchedComponent.id)) {
+              const updatedTags = mergeLogs(prevCompLogs, fetchedComponent.tagLogs);
+              if (updatedTags.length > prevCompLogs.length) {
+                tagOffsetRef.current = updatedTags.length;
+              }
+              hasMoreTagLogs.current = fetchedComponent.tagLogs.length >= tagLogLimit;
+              return {
+                ...prev,
+                getHost: {
+                  ...prev.getHost,
+                  get: {
+                    ...prevComponent,
+                    tagLogs: updatedTags,
+                  },
                 },
-              },
-            };
-          }
-
-          return prev;
-        },
-      });
-    }
-  }, [tagLogLimit, fetchMore]);
-
-  const loadMoreSnaps = React.useCallback(async () => {
-    if (snapLogLimit) {
-      await fetchMore({
-        variables: {
-          snapLogOffset: snapOffsetRef.current,
-          snapLogLimit,
-        },
-        updateQuery: (prev, { fetchMoreResult }) => {
-          if (!fetchMoreResult) return prev;
-
-          const prevComponent = prev.getHost.get;
-          const prevCompLogs = prevComponent.snapLogs ?? [];
-
-          const fetchedComponent = fetchMoreResult.getHost.get;
-          if (fetchedComponent && ComponentID.isEqualObj(prevComponent.id, fetchedComponent.id)) {
-            const updatedSnaps = mergeLogs(prevCompLogs, fetchedComponent.snapLogs);
-            if (updatedSnaps.length > prevCompLogs.length) {
-              snapOffsetRef.current = updatedSnaps.length;
+              };
             }
-            hasMoreSnapLogs.current = fetchedComponent.snapLogs.length >= snapLogLimit;
-            return {
-              ...prev,
-              getHost: {
-                ...prev.getHost,
-                get: {
-                  ...prevComponent,
-                  snapLogs: updatedSnaps,
+
+            return prev;
+          },
+        });
+      }
+    },
+    [tagLogLimit, fetchMore]
+  );
+
+  const loadMoreSnaps = React.useCallback(
+    async (backwards = false) => {
+      const offset = snapOffsetRef.current ? (backwards && -snapOffsetRef.current) || snapOffsetRef.current : undefined;
+
+      if (snapLogLimit) {
+        await fetchMore({
+          variables: {
+            snapLogOffset: offset,
+            snapLogLimit,
+          },
+          updateQuery: (prev, { fetchMoreResult }) => {
+            if (!fetchMoreResult) return prev;
+
+            const prevComponent = prev.getHost.get;
+            const prevCompLogs = prevComponent.snapLogs ?? [];
+
+            const fetchedComponent = fetchMoreResult.getHost.get;
+            if (fetchedComponent && ComponentID.isEqualObj(prevComponent.id, fetchedComponent.id)) {
+              const updatedSnaps = mergeLogs(prevCompLogs, fetchedComponent.snapLogs);
+              if (updatedSnaps.length > prevCompLogs.length) {
+                snapOffsetRef.current = updatedSnaps.length;
+              }
+              hasMoreSnapLogs.current = fetchedComponent.snapLogs.length >= snapLogLimit;
+              return {
+                ...prev,
+                getHost: {
+                  ...prev.getHost,
+                  get: {
+                    ...prevComponent,
+                    snapLogs: updatedSnaps,
+                  },
                 },
-              },
-            };
-          }
+              };
+            }
 
-          return prev;
-        },
-      });
-    }
-  }, [snapLogLimit, fetchMore]);
+            return prev;
+          },
+        });
+      }
+    },
+    [snapLogLimit, fetchMore]
+  );
 
   useEffect(() => {
     // @TODO @Kutner fix subscription for scope
diff --git a/scopes/component/ui/version-dropdown/version-dropdown.tsx b/scopes/component/ui/version-dropdown/version-dropdown.tsx
index 86a4a725a22b..19698a8d7b00 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown.tsx
+++ b/scopes/component/ui/version-dropdown/version-dropdown.tsx
@@ -71,10 +71,13 @@ function _VersionMenu(
   const hasMore = activeTabOrSnap === 'SNAP' ? !!hasMoreSnaps : activeTabOrSnap === 'TAG' && !!hasMoreTags;
   const observer = React.useRef<IntersectionObserver>();
 
-  const handleLoadMore = React.useCallback(() => {
-    if (activeTabOrSnap === 'SNAP') loadMoreSnaps?.();
-    if (activeTabOrSnap === 'TAG') loadMoreTags?.();
-  }, [activeTabOrSnap, tabs.length]);
+  const handleLoadMore = React.useCallback(
+    (backwards?: boolean) => {
+      if (activeTabOrSnap === 'SNAP') loadMoreSnaps?.(backwards);
+      if (activeTabOrSnap === 'TAG') loadMoreTags?.(backwards);
+    },
+    [activeTabOrSnap, tabs.length]
+  );
 
   const lastLogRef = React.useCallback(
     (node) => {
@@ -95,6 +98,27 @@ function _VersionMenu(
     },
     [loading, hasMoreSnaps, hasMoreTags, handleLoadMore]
   );
+
+  const firstLogRef = React.useCallback(
+    (node) => {
+      if (loading) return;
+      if (observer.current) observer.current.disconnect();
+      observer.current = new IntersectionObserver(
+        (entries) => {
+          if (entries[0].isIntersecting && hasMore) {
+            handleLoadMore(true);
+          }
+        },
+        {
+          threshold: 0.1,
+          rootMargin: '100px',
+        }
+      );
+      if (node) observer.current.observe(node);
+    },
+    [loading, hasMoreSnaps, hasMoreTags, handleLoadMore]
+  );
+
   const multipleTabs = tabs.length > 1;
   const message = multipleTabs
     ? 'Switch to view tags, snaps, or lanes'
@@ -139,17 +163,21 @@ function _VersionMenu(
             <LaneInfo key={payload.id} currentLane={currentLane} {...payload}></LaneInfo>
           ))}
         {tabs[activeTabIndex]?.name !== 'LANE' &&
-          tabs[activeTabIndex]?.payload.map((payload, index) => (
-            <VersionInfo
-              ref={index === tabs[activeTabIndex]?.payload.length - 1 ? ref || lastLogRef : null}
-              key={payload.version}
-              currentVersion={currentVersion}
-              latestVersion={latestVersion}
-              overrideVersionHref={overrideVersionHref}
-              showDetails={showVersionDetails}
-              {...payload}
-            ></VersionInfo>
-          ))}
+          tabs[activeTabIndex]?.payload.map((payload, index) => {
+            const _ref =
+              index === 0 ? firstLogRef : (index === tabs[activeTabIndex]?.payload.length - 1 && lastLogRef) || ref;
+            return (
+              <VersionInfo
+                ref={_ref}
+                key={payload.version}
+                currentVersion={currentVersion}
+                latestVersion={latestVersion}
+                overrideVersionHref={overrideVersionHref}
+                showDetails={showVersionDetails}
+                {...payload}
+              ></VersionInfo>
+            );
+          })}
       </div>
     </div>
   );
@@ -158,8 +186,8 @@ function _VersionMenu(
 export type UseComponentVersionsResult = {
   tags?: DropdownComponentVersion[];
   snaps?: DropdownComponentVersion[];
-  loadMoreTags?: () => void;
-  loadMoreSnaps?: () => void;
+  loadMoreTags?: (backwards?: boolean) => void;
+  loadMoreSnaps?: (backwards?: boolean) => void;
   hasMoreTags?: boolean;
   hasMoreSnaps?: boolean;
   loading?: boolean;
diff --git a/scopes/lanes/hooks/use-lanes/lanes-provider.tsx b/scopes/lanes/hooks/use-lanes/lanes-provider.tsx
index 5c395fc5b592..182f6a8df9f7 100644
--- a/scopes/lanes/hooks/use-lanes/lanes-provider.tsx
+++ b/scopes/lanes/hooks/use-lanes/lanes-provider.tsx
@@ -27,6 +27,16 @@ export function LanesProvider({
 
   const [lanesState, setLanesState] = useState<LanesModel | undefined>(lanesModel);
   const [viewedLaneId, setViewedLaneId] = useState<LaneId | undefined>(viewedIdFromProps);
+  const updateViewedLane = useCallback(
+    (lane?: LaneId) => {
+      setViewedLaneId(lane);
+      setLanesState((state) => {
+        state?.setViewedOrDefaultLane(lane);
+        return state;
+      });
+    },
+    [lanesModel]
+  );
 
   const location = useLocation();
   const query = useQuery();
@@ -37,7 +47,7 @@ export function LanesProvider({
   }, []);
 
   useEffect(() => {
-    if (viewedIdFromProps) setViewedLaneId(viewedIdFromProps);
+    if (viewedIdFromProps) updateViewedLane(viewedIdFromProps);
   }, [viewedIdFromProps?.toString()]);
 
   useEffect(() => {
@@ -50,26 +60,28 @@ export function LanesProvider({
     const viewedLaneIdToSet =
       viewedLaneIdFromUrl || lanesModel?.currentLane?.id || lanesModel?.lanes.find((lane) => lane.id.isDefault())?.id;
 
-    setViewedLaneId(viewedLaneIdToSet);
+    updateViewedLane(viewedLaneIdToSet);
   }, [location?.pathname]);
 
   useEffect(() => {
-    if (viewedLaneId === undefined && lanesModel?.currentLane?.id) {
-      setViewedLaneId(lanesModel.currentLane.id);
-      lanesModel?.setViewedOrDefaultLane(lanesModel.currentLane.id);
-      setLanesState(lanesModel);
-      return;
-    }
-    lanesModel?.setViewedOrDefaultLane(viewedLaneId);
-    setLanesState(lanesModel);
-  }, [loading, lanesModel?.lanes.length]);
-
-  lanesState?.setViewedOrDefaultLane(viewedLaneId);
+    setLanesState((existing) => {
+      if (!loading && lanesModel?.lanes.length) {
+        const state = new LanesModel({ ...lanesModel });
+        if (viewedLaneId === undefined && lanesModel?.currentLane?.id) {
+          state.setViewedOrDefaultLane(lanesModel?.currentLane?.id);
+        } else {
+          state.setViewedOrDefaultLane(viewedLaneId);
+        }
+        return state;
+      }
+      return existing;
+    });
+  }, [loading, lanesModel?.lanes.length, viewedLaneId]);
 
   const lanesContextModel: LanesContextModel = {
     lanesModel: lanesState,
     updateLanesModel: setLanesState,
-    updateViewedLane: setViewedLaneId,
+    updateViewedLane,
   };
 
   return <LanesContext.Provider value={lanesContextModel}>{children}</LanesContext.Provider>;
diff --git a/scopes/lanes/hooks/use-lanes/use-lanes.tsx b/scopes/lanes/hooks/use-lanes/use-lanes.tsx
index 4f30976048eb..4a4b2d6348c3 100644
--- a/scopes/lanes/hooks/use-lanes/use-lanes.tsx
+++ b/scopes/lanes/hooks/use-lanes/use-lanes.tsx
@@ -50,7 +50,7 @@ export function useLanes(
   skip?: boolean
 ): LanesContextModel & Omit<QueryResult<LanesQuery & { getHost: { id: string } }>, 'data'> {
   const lanesContext = useLanesContext();
-  const shouldSkip = skip || !!targetLanes || !!lanesContext;
+  const shouldSkip = skip || !!targetLanes || !!lanesContext?.lanesModel;
 
   const { data, loading, ...rest } = useDataQuery<LanesQuery & { getHost: { id: string } }>(GET_LANES, {
     skip: shouldSkip,
diff --git a/scopes/lanes/lanes/lanes.ui.runtime.tsx b/scopes/lanes/lanes/lanes.ui.runtime.tsx
index 95e55d1c79a5..0d0ca89bc775 100644
--- a/scopes/lanes/lanes/lanes.ui.runtime.tsx
+++ b/scopes/lanes/lanes/lanes.ui.runtime.tsx
@@ -30,6 +30,41 @@ import styles from './lanes.ui.module.scss';
 
 export type LaneCompareProps = Partial<DefaultLaneCompareProps>;
 export type LaneProviderIgnoreSlot = SlotRegistry<IgnoreDerivingFromUrl>;
+export function useComponentFilters() {
+  const idFromLocation = useIdFromLocation();
+  const { lanesModel, loading } = useLanes();
+  const laneFromUrl = useViewedLaneFromUrl();
+  const laneComponentId =
+    idFromLocation && !laneFromUrl?.isDefault()
+      ? lanesModel?.resolveComponentFromUrl(idFromLocation, laneFromUrl) ?? null
+      : null;
+
+  if (laneComponentId === null || loading) {
+    return {
+      loading: true,
+    };
+  }
+
+  return {
+    loading: false,
+    log: {
+      logHead: laneComponentId.version,
+    },
+  };
+}
+export function useLaneComponentIdFromUrl() {
+  const idFromLocation = useIdFromLocation();
+  const { lanesModel, loading } = useLanes();
+  const laneFromUrl = useViewedLaneFromUrl();
+  const laneComponentId =
+    idFromLocation && !laneFromUrl?.isDefault()
+      ? lanesModel?.resolveComponentFromUrl(idFromLocation, laneFromUrl) ?? null
+      : null;
+  return loading ? undefined : laneComponentId;
+}
+export function useComponentId() {
+  return useLaneComponentIdFromUrl()?.toString();
+}
 
 export class LanesUI {
   static dependencies = [UIAspect, ComponentAspect, WorkspaceAspect, ScopeAspect, ComponentCompareAspect];
@@ -112,42 +147,17 @@ export class LanesUI {
   //   return <LaneReadmeOverview host={this.host} overviewSlot={this.overviewSlot} routeSlot={this.routeSlot} />;
   // }
 
-  getLaneComponentIdFromUrl = () => {
-    const idFromLocation = useIdFromLocation();
-    const { lanesModel } = useLanes();
-    const laneFromUrl = useViewedLaneFromUrl();
-    const laneComponentId =
-      idFromLocation && !laneFromUrl?.isDefault()
-        ? lanesModel?.resolveComponentFromUrl(idFromLocation, laneFromUrl)
-        : undefined;
-    return laneComponentId;
-  };
-
-  useComponentId = () => {
-    return this.getLaneComponentIdFromUrl()?.toString();
-  };
-
-  useComponentFilters = () => {
-    const laneComponentId = this.getLaneComponentIdFromUrl();
-
-    return {
-      log: laneComponentId && {
-        logHead: laneComponentId.version,
-      },
-    };
-  };
-
   getLaneComponent() {
     return this.componentUI.getComponentUI(this.host, {
-      componentId: this.useComponentId,
-      useComponentFilters: this.useComponentFilters,
+      componentId: useComponentId,
+      useComponentFilters,
     });
   }
 
   getLaneComponentMenu() {
     return this.componentUI.getMenu(this.host, {
-      componentId: this.useComponentId,
-      useComponentFilters: this.useComponentFilters,
+      componentId: useComponentId,
+      useComponentFilters,
     });
   }
 
@@ -252,7 +262,7 @@ export class LanesUI {
       <LaneSwitcher
         groupByScope={this.lanesHost === 'workspace'}
         mainIcon={this.lanesHost === 'scope' ? mainIcon : undefined}
-        useLanes={this.getUseLanes()}
+        useLanes={useLanes}
       />
     );
 
diff --git a/scopes/scope/scope/scope.main.runtime.ts b/scopes/scope/scope/scope.main.runtime.ts
index 0d8b3bc19144..5f00b95a3a68 100644
--- a/scopes/scope/scope/scope.main.runtime.ts
+++ b/scopes/scope/scope/scope.main.runtime.ts
@@ -681,11 +681,104 @@ export class ScopeMain implements ComponentFactory {
     return this.componentLoader.getSnap(id, ref.toString());
   }
 
+  /**
+   * Performs a Depth-First Search (DFS) to find a node in a graph by offset.
+   *
+   * This function starts from a given node and moves either forward or backward in the graph, depending on the provided getNodes function.
+   * The goal is to reach a node that is at a given offset from the starting node. The offset is decremented each time a new node is visited.
+   * The function will return the node that corresponds to the exact offset if it exists.
+   *
+   * If the graph cannot go any deeper (i.e., there are no more successors or predecessors), or the offset becomes zero, the function will return the last visited node.
+   * This ensures that the function always returns a node, either the exact offset node (if it exists) or the closest node by offset.
+   *
+   * @param _node The node from where to start the search.
+   * @param _versionGraph The graph in which to perform the search.
+   * @param _offset The offset from the starting node to find. It is always an absolute value.
+   * @param _getNodesFunc A function that, given a node id, returns the successors (if moving forward) or predecessors (if moving backward) of that node in the graph.
+   *
+   * @returns The node that corresponds to the exact offset if it exists, or the closest node by offset otherwise.
+   */
+  private findNodeByOffset(_node, _versionGraph, _offset, _getNodesFunc) {
+    if (_offset === 0 || !_node) return _node;
+
+    const nextNodes = _getNodesFunc(_node.id, { edgeFilter: (edge) => edge.attr === 'parent' });
+
+    if (!nextNodes || nextNodes.length === 0) {
+      return _node;
+    }
+
+    for (const nextNode of nextNodes) {
+      const foundNode = this.findNodeByOffset(nextNode, _versionGraph, _offset - 1, _getNodesFunc);
+      if (foundNode) {
+        return foundNode;
+      }
+    }
+
+    return _node;
+  }
+
   /**
    * get component log sorted by the timestamp in ascending order (from the earliest to the latest)
    */
-  async getLogs(id: ComponentID, shortHash = false, startsFrom?: string): Promise<ComponentLog[]> {
-    return this.legacyScope.loadComponentLogs(id._legacy, shortHash, startsFrom);
+  async getLogs(
+    id: ComponentID,
+    shortHash = false,
+    head?: string,
+    startFrom?: string,
+    stopAt?: string,
+    startFromOffset?: number,
+    stopAtOffset?: number
+  ): Promise<ComponentLog[]> {
+    const componentModel = await this.legacyScope.getModelComponentIfExist(id._legacy);
+
+    if (!componentModel) return [];
+
+    const headRef = head ? componentModel.getRef(head) : componentModel.head;
+
+    if (!headRef) return [];
+
+    if (!startFromOffset && !stopAtOffset) {
+      return this.legacyScope.loadComponentLogs(id._legacy, shortHash, startFrom, stopAt ? [stopAt] : undefined);
+    }
+    const versionHistory = await componentModel.getAndPopulateVersionHistory(this.legacyScope.objects, headRef);
+    const versionGraph = versionHistory.getGraph();
+    const startFromRef = startFrom ? componentModel.getRef(startFrom) : undefined;
+    let startNode = versionGraph.node(startFromRef?.hash ?? headRef.hash);
+
+    if (!startNode) {
+      this.logger.error(`Node with id ${headRef.hash} not found`);
+      return [];
+    }
+
+    const startOffset = startFromOffset || 0;
+
+    if (startOffset !== 0) {
+      startNode = this.findNodeByOffset(
+        startNode,
+        versionGraph,
+        Math.abs(startOffset),
+        startOffset > 0 ? versionGraph.successors.bind(versionGraph) : versionGraph.predecessors.bind(versionGraph)
+      );
+    }
+
+    const stopOffset = stopAtOffset || 0;
+    const stopRef = stopAt ? componentModel.getRef(stopAt) : (stopOffset !== 0 && { hash: startNode?.id }) || undefined;
+    let stopNode = stopRef?.hash ? versionGraph.node(stopRef.hash) : undefined;
+    if (stopOffset !== 0) {
+      stopNode = this.findNodeByOffset(
+        stopNode,
+        versionGraph,
+        Math.abs(stopOffset),
+        stopOffset > 0 ? versionGraph.successors.bind(versionGraph) : versionGraph.predecessors.bind(versionGraph)
+      );
+    }
+
+    return this.legacyScope.loadComponentLogs(
+      id._legacy,
+      shortHash,
+      startNode?.id,
+      stopNode ? [stopNode.id] : undefined
+    );
   }
 
   async getStagedConfig() {
diff --git a/scopes/workspace/workspace/workspace.ts b/scopes/workspace/workspace/workspace.ts
index 861f34f98775..553385e2b5a0 100644
--- a/scopes/workspace/workspace/workspace.ts
+++ b/scopes/workspace/workspace/workspace.ts
@@ -426,8 +426,16 @@ export class Workspace implements ComponentFactory {
     return this.getMany(ids);
   }
 
-  async getLogs(id: ComponentID, shortHash = false, startsFrom?: string): Promise<ComponentLog[]> {
-    return this.scope.getLogs(id, shortHash, startsFrom);
+  async getLogs(
+    id: ComponentID,
+    shortHash = false,
+    head?: string,
+    startFrom?: string,
+    stopAt?: string,
+    startFromOffset?: number,
+    stopAtOffset?: number
+  ): Promise<ComponentLog[]> {
+    return this.scope.getLogs(id, shortHash, head, startFrom, stopAt, startFromOffset, stopAtOffset);
   }
 
   async getGraph(ids?: ComponentID[], shouldThrowOnMissingDep = true): Promise<Graph<Component, string>> {
diff --git a/src/scope/component-ops/traverse-versions.ts b/src/scope/component-ops/traverse-versions.ts
index 5265cbcdfcb9..6fb540ade05c 100644
--- a/src/scope/component-ops/traverse-versions.ts
+++ b/src/scope/component-ops/traverse-versions.ts
@@ -48,8 +48,10 @@ export async function getAllVersionsInfo({
   repo?: Repository;
   throws?: boolean; // in case objects are missing
   versionObjects?: Version[];
-  startFrom?: Ref | null; // by default, start from the head
+  startFrom?: Ref | null; // by default, start from the head //
+  startFromOffset?: number; // -5
   stopAt?: Ref[] | null; // by default, stop when the parents is empty
+  stopAtOffset?: number; // 5
 }): Promise<VersionInfo[]> {
   const results: VersionInfo[] = [];
   const isAlreadyProcessed = (ref: Ref): boolean => {
diff --git a/src/scope/models/model-component.ts b/src/scope/models/model-component.ts
index ffcfa9b66b59..88d2268228ac 100644
--- a/src/scope/models/model-component.ts
+++ b/src/scope/models/model-component.ts
@@ -480,9 +480,9 @@ export default class Component extends BitObject {
   /**
    * get component log and sort by the timestamp in ascending order (from the earliest to the latest)
    */
-  async collectLogs(scope: Scope, shortHash = false, startFrom?: Ref): Promise<ComponentLog[]> {
+  async collectLogs(scope: Scope, shortHash = false, startFrom?: Ref, stopAt?: Ref[]): Promise<ComponentLog[]> {
     const repo = scope.objects;
-    let versionsInfo = await getAllVersionsInfo({ modelComponent: this, repo, throws: false, startFrom });
+    let versionsInfo = await getAllVersionsInfo({ modelComponent: this, repo, throws: false, startFrom, stopAt });
 
     // due to recent changes of getting version-history object rather than fetching the entire history, some version
     // objects might be missing. import the component from the remote
diff --git a/src/scope/scope.ts b/src/scope/scope.ts
index 1676687e5033..8627d140da7d 100644
--- a/src/scope/scope.ts
+++ b/src/scope/scope.ts
@@ -2,6 +2,7 @@ import fs from 'fs-extra';
 import * as pathLib from 'path';
 import R from 'ramda';
 import { LaneId } from '@teambit/lane-id';
+import { compact } from 'lodash';
 import semver from 'semver';
 import { isTag } from '@teambit/component-version';
 import { Analytics } from '../analytics/analytics';
@@ -535,11 +536,19 @@ once done, to continue working, please run "bit cc"`
     return removeNils(components);
   }
 
-  async loadComponentLogs(id: BitId, shortHash = false, startFrom?: string): Promise<ComponentLog[]> {
+  async loadComponentLogs(
+    id: BitId,
+    shortHash = false,
+    startFrom?: string,
+    stopsAt?: string[]
+  ): Promise<ComponentLog[]> {
     const componentModel = await this.getModelComponentIfExist(id);
     if (!componentModel) return [];
+
     const startFromRef = startFrom ? componentModel.getRef(startFrom) ?? undefined : undefined;
-    const logs = await componentModel.collectLogs(this, shortHash, startFromRef);
+    const stopsAtRef = stopsAt ? compact(stopsAt.map((s) => componentModel.getRef(s) ?? undefined)) : undefined;
+
+    const logs = await componentModel.collectLogs(this, shortHash, startFromRef, stopsAtRef);
     return logs;
   }
 

From 1fa4b10188766b3673d718dc43e98b9c8b21fb08 Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Fri, 12 May 2023 14:38:17 -0400
Subject: [PATCH 12/20] clean up

---
 .../ui/component-tooltip/Untitled-1.yml       | 789 ++++++++++++++++++
 scopes/scope/scope/scope.main.runtime.ts      |  59 +-
 src/scope/component-ops/traverse-versions.ts  |   4 +-
 3 files changed, 833 insertions(+), 19 deletions(-)
 create mode 100644 scopes/component/ui/component-tooltip/Untitled-1.yml

diff --git a/scopes/component/ui/component-tooltip/Untitled-1.yml b/scopes/component/ui/component-tooltip/Untitled-1.yml
new file mode 100644
index 000000000000..5f2bc6150471
--- /dev/null
+++ b/scopes/component/ui/component-tooltip/Untitled-1.yml
@@ -0,0 +1,789 @@
+[
+  {
+    id: "060c458da72872729a87d8051ef08e190a25d751",
+    attr: { hash: "060c458da72872729a87d8051ef08e190a25d751" },
+    _inEdges:
+      [
+        "ff44ae21a26570f6395bc316417f0b6fb1bca930->060c458da72872729a87d8051ef08e190a25d751",
+      ],
+    _outEdges:
+      [
+        "060c458da72872729a87d8051ef08e190a25d751->484ba5c2ebcbb22ef6d5090eec6032d6a4fd1482",
+      ],
+  },
+
+  {
+    id: "484ba5c2ebcbb22ef6d5090eec6032d6a4fd1482",
+    attr: { hash: "484ba5c2ebcbb22ef6d5090eec6032d6a4fd1482" },
+    _inEdges:
+      [
+        "060c458da72872729a87d8051ef08e190a25d751->484ba5c2ebcbb22ef6d5090eec6032d6a4fd1482",
+      ],
+    _outEdges:
+      [
+        "484ba5c2ebcbb22ef6d5090eec6032d6a4fd1482->7803e482a4943a6e31deb9b38c80d735f75916ae",
+      ],
+  },
+
+  {
+    id: "7803e482a4943a6e31deb9b38c80d735f75916ae",
+    attr: { hash: "7803e482a4943a6e31deb9b38c80d735f75916ae" },
+    _inEdges:
+      [
+        "484ba5c2ebcbb22ef6d5090eec6032d6a4fd1482->7803e482a4943a6e31deb9b38c80d735f75916ae",
+      ],
+    _outEdges:
+      [
+        "7803e482a4943a6e31deb9b38c80d735f75916ae->7451e0eb12563c8c63b53dc9ddb141de5918b1d7",
+      ],
+  },
+
+  {
+    id: "7451e0eb12563c8c63b53dc9ddb141de5918b1d7",
+    attr: { hash: "7451e0eb12563c8c63b53dc9ddb141de5918b1d7" },
+    _inEdges:
+      [
+        "7803e482a4943a6e31deb9b38c80d735f75916ae->7451e0eb12563c8c63b53dc9ddb141de5918b1d7",
+      ],
+    _outEdges:
+      [
+        "7451e0eb12563c8c63b53dc9ddb141de5918b1d7->8293c41d17d451d758725c7531c98138b9370b09",
+      ],
+  },
+
+  {
+    id: "8293c41d17d451d758725c7531c98138b9370b09",
+    attr: { hash: "8293c41d17d451d758725c7531c98138b9370b09" },
+    _inEdges:
+      [
+        "7451e0eb12563c8c63b53dc9ddb141de5918b1d7->8293c41d17d451d758725c7531c98138b9370b09",
+      ],
+    _outEdges:
+      [
+        "8293c41d17d451d758725c7531c98138b9370b09->de5b08e7da761f619d2499eaa0720ad29fc77eb2",
+      ],
+  },
+
+  {
+    id: "de5b08e7da761f619d2499eaa0720ad29fc77eb2",
+    attr: { hash: "de5b08e7da761f619d2499eaa0720ad29fc77eb2" },
+    _inEdges:
+      [
+        "8293c41d17d451d758725c7531c98138b9370b09->de5b08e7da761f619d2499eaa0720ad29fc77eb2",
+      ],
+    _outEdges:
+      [
+        "de5b08e7da761f619d2499eaa0720ad29fc77eb2->f94061d4ba59bef342b2ffba7cbe7d3568a9c52c",
+      ],
+  },
+
+  {
+    id: "f94061d4ba59bef342b2ffba7cbe7d3568a9c52c",
+    attr: { hash: "f94061d4ba59bef342b2ffba7cbe7d3568a9c52c" },
+    _inEdges:
+      [
+        "de5b08e7da761f619d2499eaa0720ad29fc77eb2->f94061d4ba59bef342b2ffba7cbe7d3568a9c52c",
+      ],
+    _outEdges:
+      [
+        "f94061d4ba59bef342b2ffba7cbe7d3568a9c52c->6e1e136d27542e751f0390d2c33b722f34d89a10",
+        "f94061d4ba59bef342b2ffba7cbe7d3568a9c52c->edbb65f181cceb87d9dda7c3c37d08ea601ec651",
+      ],
+  },
+
+  {
+    id: "6e1e136d27542e751f0390d2c33b722f34d89a10",
+    attr: { hash: "6e1e136d27542e751f0390d2c33b722f34d89a10" },
+    _inEdges:
+      [
+        "f94061d4ba59bef342b2ffba7cbe7d3568a9c52c->6e1e136d27542e751f0390d2c33b722f34d89a10",
+      ],
+    _outEdges:
+      [
+        "6e1e136d27542e751f0390d2c33b722f34d89a10->e8f760d67664a04c4fde116183a4ca6bd7c9bfce",
+      ],
+  },
+
+  {
+    id: "e8f760d67664a04c4fde116183a4ca6bd7c9bfce",
+    attr: { hash: "e8f760d67664a04c4fde116183a4ca6bd7c9bfce" },
+    _inEdges:
+      [
+        "6e1e136d27542e751f0390d2c33b722f34d89a10->e8f760d67664a04c4fde116183a4ca6bd7c9bfce",
+      ],
+    _outEdges:
+      [
+        "e8f760d67664a04c4fde116183a4ca6bd7c9bfce->d7c6b760ceb136780fcabe2c16dffcbf2a7456f6",
+      ],
+  },
+
+  {
+    id: "d7c6b760ceb136780fcabe2c16dffcbf2a7456f6",
+    attr: { hash: "d7c6b760ceb136780fcabe2c16dffcbf2a7456f6" },
+    _inEdges:
+      [
+        "e8f760d67664a04c4fde116183a4ca6bd7c9bfce->d7c6b760ceb136780fcabe2c16dffcbf2a7456f6",
+      ],
+    _outEdges:
+      [
+        "d7c6b760ceb136780fcabe2c16dffcbf2a7456f6->136aab756693cfa6283f45f02b2b6aedb71e3199",
+        "d7c6b760ceb136780fcabe2c16dffcbf2a7456f6->16be08d1f9207f58a3b13b643a5f6df83b00851c",
+      ],
+  },
+
+  {
+    id: "136aab756693cfa6283f45f02b2b6aedb71e3199",
+    attr: { hash: "136aab756693cfa6283f45f02b2b6aedb71e3199" },
+    _inEdges:
+      [
+        "d7c6b760ceb136780fcabe2c16dffcbf2a7456f6->136aab756693cfa6283f45f02b2b6aedb71e3199",
+      ],
+    _outEdges:
+      [
+        "136aab756693cfa6283f45f02b2b6aedb71e3199->d18b9728f2cd80571ea7f070ec2a62e40461b51e",
+      ],
+  },
+
+  {
+    id: "d18b9728f2cd80571ea7f070ec2a62e40461b51e",
+    attr: { hash: "d18b9728f2cd80571ea7f070ec2a62e40461b51e" },
+    _inEdges:
+      [
+        "136aab756693cfa6283f45f02b2b6aedb71e3199->d18b9728f2cd80571ea7f070ec2a62e40461b51e",
+      ],
+    _outEdges:
+      [
+        "d18b9728f2cd80571ea7f070ec2a62e40461b51e->cea5d5c39bb9a33b3af8e4f45df5722d58acae36",
+        "d18b9728f2cd80571ea7f070ec2a62e40461b51e->7fa90951d4ec0646f3368ccdbbbe89fe3de09212",
+      ],
+  },
+
+  {
+    id: "cea5d5c39bb9a33b3af8e4f45df5722d58acae36",
+    attr: { hash: "cea5d5c39bb9a33b3af8e4f45df5722d58acae36" },
+    _inEdges:
+      [
+        "d18b9728f2cd80571ea7f070ec2a62e40461b51e->cea5d5c39bb9a33b3af8e4f45df5722d58acae36",
+      ],
+    _outEdges:
+      [
+        "cea5d5c39bb9a33b3af8e4f45df5722d58acae36->c2a1ea45e7e2906d888355ea069303de347ef968",
+      ],
+  },
+
+  {
+    id: "c2a1ea45e7e2906d888355ea069303de347ef968",
+    attr: { hash: "c2a1ea45e7e2906d888355ea069303de347ef968" },
+    _inEdges:
+      [
+        "edbb65f181cceb87d9dda7c3c37d08ea601ec651->c2a1ea45e7e2906d888355ea069303de347ef968",
+        "cea5d5c39bb9a33b3af8e4f45df5722d58acae36->c2a1ea45e7e2906d888355ea069303de347ef968",
+      ],
+    _outEdges:
+      [
+        "c2a1ea45e7e2906d888355ea069303de347ef968->0b6993ef2933137d9f009e1a55ac1603072c0065",
+      ],
+  },
+
+  {
+    id: "0b6993ef2933137d9f009e1a55ac1603072c0065",
+    attr: { hash: "0b6993ef2933137d9f009e1a55ac1603072c0065" },
+    _inEdges:
+      [
+        "c2a1ea45e7e2906d888355ea069303de347ef968->0b6993ef2933137d9f009e1a55ac1603072c0065",
+      ],
+    _outEdges:
+      [
+        "0b6993ef2933137d9f009e1a55ac1603072c0065->33d5a8b8bf37f022b906740d2cd22d505073a34a",
+      ],
+  },
+
+  {
+    id: "33d5a8b8bf37f022b906740d2cd22d505073a34a",
+    attr: { hash: "33d5a8b8bf37f022b906740d2cd22d505073a34a" },
+    _inEdges:
+      [
+        "28cce4514ec9a3aa0d0cda4ab84cf7a6c8df0ca3->33d5a8b8bf37f022b906740d2cd22d505073a34a",
+        "0b6993ef2933137d9f009e1a55ac1603072c0065->33d5a8b8bf37f022b906740d2cd22d505073a34a",
+        "5f61a84fd0303994d1a31830b829c3d0f9ddeffa->33d5a8b8bf37f022b906740d2cd22d505073a34a",
+      ],
+    _outEdges:
+      [
+        "33d5a8b8bf37f022b906740d2cd22d505073a34a->8c18ba98ff5fa4becf72e22602d108a061d39c96",
+      ],
+  },
+
+  {
+    id: "8c18ba98ff5fa4becf72e22602d108a061d39c96",
+    attr: { hash: "8c18ba98ff5fa4becf72e22602d108a061d39c96" },
+    _inEdges:
+      [
+        "33d5a8b8bf37f022b906740d2cd22d505073a34a->8c18ba98ff5fa4becf72e22602d108a061d39c96",
+        "10c7a8a81f5c7c86def9e2af55f3172d44a778cc->8c18ba98ff5fa4becf72e22602d108a061d39c96",
+      ],
+    _outEdges:
+      [
+        "8c18ba98ff5fa4becf72e22602d108a061d39c96->30f947c6b332ea104cb643b2a635952d9a4b1774",
+      ],
+  },
+
+  {
+    id: "30f947c6b332ea104cb643b2a635952d9a4b1774",
+    attr: { hash: "30f947c6b332ea104cb643b2a635952d9a4b1774" },
+    _inEdges:
+      [
+        "8c18ba98ff5fa4becf72e22602d108a061d39c96->30f947c6b332ea104cb643b2a635952d9a4b1774",
+      ],
+    _outEdges:
+      [
+        "30f947c6b332ea104cb643b2a635952d9a4b1774->3c14f556f1d32bc459963b8ea7464e6b896c3531",
+      ],
+  },
+
+  {
+    id: "3c14f556f1d32bc459963b8ea7464e6b896c3531",
+    attr: { hash: "3c14f556f1d32bc459963b8ea7464e6b896c3531" },
+    _inEdges:
+      [
+        "30f947c6b332ea104cb643b2a635952d9a4b1774->3c14f556f1d32bc459963b8ea7464e6b896c3531",
+      ],
+    _outEdges:
+      [
+        "3c14f556f1d32bc459963b8ea7464e6b896c3531->97354a80b5aa0a430f89827f6810afd2ab4900ca",
+      ],
+  },
+
+  {
+    id: "97354a80b5aa0a430f89827f6810afd2ab4900ca",
+    attr: { hash: "97354a80b5aa0a430f89827f6810afd2ab4900ca" },
+    _inEdges:
+      [
+        "3c14f556f1d32bc459963b8ea7464e6b896c3531->97354a80b5aa0a430f89827f6810afd2ab4900ca",
+      ],
+    _outEdges:
+      [
+        "97354a80b5aa0a430f89827f6810afd2ab4900ca->4ea6caed5281a2bd2b1d249708b08430b6c1a845",
+      ],
+  },
+
+  {
+    id: "4ea6caed5281a2bd2b1d249708b08430b6c1a845",
+    attr: { hash: "4ea6caed5281a2bd2b1d249708b08430b6c1a845" },
+    _inEdges:
+      [
+        "97354a80b5aa0a430f89827f6810afd2ab4900ca->4ea6caed5281a2bd2b1d249708b08430b6c1a845",
+      ],
+    _outEdges:
+      [
+        "4ea6caed5281a2bd2b1d249708b08430b6c1a845->67d4381ff07c0f4f71c7ca093d6a617d98f62fd9",
+      ],
+  },
+
+  {
+    id: "67d4381ff07c0f4f71c7ca093d6a617d98f62fd9",
+    attr: { hash: "67d4381ff07c0f4f71c7ca093d6a617d98f62fd9" },
+    _inEdges:
+      [
+        "4ea6caed5281a2bd2b1d249708b08430b6c1a845->67d4381ff07c0f4f71c7ca093d6a617d98f62fd9",
+        "8d97eff47eb7809463564fc3d861ac85be87dcc1->67d4381ff07c0f4f71c7ca093d6a617d98f62fd9",
+      ],
+    _outEdges:
+      [
+        "67d4381ff07c0f4f71c7ca093d6a617d98f62fd9->e75cd8c849caf103897a68aa8f6ec3ae7717a384",
+      ],
+  },
+
+  {
+    id: "e75cd8c849caf103897a68aa8f6ec3ae7717a384",
+    attr: { hash: "e75cd8c849caf103897a68aa8f6ec3ae7717a384" },
+    _inEdges:
+      [
+        "67d4381ff07c0f4f71c7ca093d6a617d98f62fd9->e75cd8c849caf103897a68aa8f6ec3ae7717a384",
+      ],
+    _outEdges:
+      [
+        "e75cd8c849caf103897a68aa8f6ec3ae7717a384->d8d7ba85f8ae204a28ec370774c856ea98ea9a52",
+      ],
+  },
+
+  {
+    id: "d8d7ba85f8ae204a28ec370774c856ea98ea9a52",
+    attr: { hash: "d8d7ba85f8ae204a28ec370774c856ea98ea9a52" },
+    _inEdges:
+      [
+        "e75cd8c849caf103897a68aa8f6ec3ae7717a384->d8d7ba85f8ae204a28ec370774c856ea98ea9a52",
+      ],
+    _outEdges:
+      [
+        "d8d7ba85f8ae204a28ec370774c856ea98ea9a52->b9f744da953bdab1724fd48b259a42b3476d7a7d",
+      ],
+  },
+
+  {
+    id: "b9f744da953bdab1724fd48b259a42b3476d7a7d",
+    attr: { hash: "b9f744da953bdab1724fd48b259a42b3476d7a7d" },
+    _inEdges:
+      [
+        "d8d7ba85f8ae204a28ec370774c856ea98ea9a52->b9f744da953bdab1724fd48b259a42b3476d7a7d",
+      ],
+    _outEdges:
+      [
+        "b9f744da953bdab1724fd48b259a42b3476d7a7d->6fa6d42b8580f301b9910a49c70d7b11d2fc357e",
+      ],
+  },
+
+  {
+    id: "6fa6d42b8580f301b9910a49c70d7b11d2fc357e",
+    attr: { hash: "6fa6d42b8580f301b9910a49c70d7b11d2fc357e" },
+    _inEdges:
+      [
+        "b9f744da953bdab1724fd48b259a42b3476d7a7d->6fa6d42b8580f301b9910a49c70d7b11d2fc357e",
+      ],
+    _outEdges:
+      [
+        "6fa6d42b8580f301b9910a49c70d7b11d2fc357e->17eae8caf411633e4553ff7ee6ceab4a0dac835c",
+      ],
+  },
+
+  {
+    id: "17eae8caf411633e4553ff7ee6ceab4a0dac835c",
+    attr: { hash: "17eae8caf411633e4553ff7ee6ceab4a0dac835c" },
+    _inEdges:
+      [
+        "6fa6d42b8580f301b9910a49c70d7b11d2fc357e->17eae8caf411633e4553ff7ee6ceab4a0dac835c",
+      ],
+    _outEdges:
+      [
+        "17eae8caf411633e4553ff7ee6ceab4a0dac835c->e72c0d0b1d31ed52999def993ca425bc8b6eec8d",
+      ],
+  },
+
+  {
+    id: "e72c0d0b1d31ed52999def993ca425bc8b6eec8d",
+    attr: { hash: "e72c0d0b1d31ed52999def993ca425bc8b6eec8d" },
+    _inEdges:
+      [
+        "17eae8caf411633e4553ff7ee6ceab4a0dac835c->e72c0d0b1d31ed52999def993ca425bc8b6eec8d",
+      ],
+    _outEdges:
+      [
+        "e72c0d0b1d31ed52999def993ca425bc8b6eec8d->d2ab69219d828c8822444f5c8a7b92763f4bf9b8",
+      ],
+  },
+
+  {
+    id: "d2ab69219d828c8822444f5c8a7b92763f4bf9b8",
+    attr: { hash: "d2ab69219d828c8822444f5c8a7b92763f4bf9b8" },
+    _inEdges:
+      [
+        "e72c0d0b1d31ed52999def993ca425bc8b6eec8d->d2ab69219d828c8822444f5c8a7b92763f4bf9b8",
+      ],
+    _outEdges:
+      [
+        "d2ab69219d828c8822444f5c8a7b92763f4bf9b8->f1cff5c303f39c5047c6bab98baab1156c48c1b6",
+      ],
+  },
+
+  {
+    id: "f1cff5c303f39c5047c6bab98baab1156c48c1b6",
+    attr: { hash: "f1cff5c303f39c5047c6bab98baab1156c48c1b6" },
+    _inEdges:
+      [
+        "d2ab69219d828c8822444f5c8a7b92763f4bf9b8->f1cff5c303f39c5047c6bab98baab1156c48c1b6",
+      ],
+    _outEdges:
+      [
+        "f1cff5c303f39c5047c6bab98baab1156c48c1b6->2ae41ac40604cb7d68ecfa1ad7432e090bac3f01",
+      ],
+  },
+
+  {
+    id: "2ae41ac40604cb7d68ecfa1ad7432e090bac3f01",
+    attr: { hash: "2ae41ac40604cb7d68ecfa1ad7432e090bac3f01" },
+    _inEdges:
+      [
+        "f1cff5c303f39c5047c6bab98baab1156c48c1b6->2ae41ac40604cb7d68ecfa1ad7432e090bac3f01",
+      ],
+    _outEdges:
+      [
+        "2ae41ac40604cb7d68ecfa1ad7432e090bac3f01->241832c97051733499b87d742286a811711dfcfb",
+      ],
+  },
+
+  {
+    id: "241832c97051733499b87d742286a811711dfcfb",
+    attr: { hash: "241832c97051733499b87d742286a811711dfcfb" },
+    _inEdges:
+      [
+        "2ae41ac40604cb7d68ecfa1ad7432e090bac3f01->241832c97051733499b87d742286a811711dfcfb",
+      ],
+    _outEdges:
+      [
+        "241832c97051733499b87d742286a811711dfcfb->efca1e0bfe4a8347e46048ee9dae0cc726461cb8",
+      ],
+  },
+
+  {
+    id: "efca1e0bfe4a8347e46048ee9dae0cc726461cb8",
+    attr: { hash: "efca1e0bfe4a8347e46048ee9dae0cc726461cb8" },
+    _inEdges:
+      [
+        "241832c97051733499b87d742286a811711dfcfb->efca1e0bfe4a8347e46048ee9dae0cc726461cb8",
+      ],
+    _outEdges:
+      [
+        "efca1e0bfe4a8347e46048ee9dae0cc726461cb8->f9f52eb71d5d020f53a67ebf2c98049969553772",
+      ],
+  },
+
+  {
+    id: "f9f52eb71d5d020f53a67ebf2c98049969553772",
+    attr: { hash: "f9f52eb71d5d020f53a67ebf2c98049969553772" },
+    _inEdges:
+      [
+        "efca1e0bfe4a8347e46048ee9dae0cc726461cb8->f9f52eb71d5d020f53a67ebf2c98049969553772",
+      ],
+    _outEdges:
+      [
+        "f9f52eb71d5d020f53a67ebf2c98049969553772->8eb3a70cf0752b54fa2f9651ccde79563112bad3",
+      ],
+  },
+
+  {
+    id: "8eb3a70cf0752b54fa2f9651ccde79563112bad3",
+    attr: { hash: "8eb3a70cf0752b54fa2f9651ccde79563112bad3" },
+    _inEdges:
+      [
+        "f9f52eb71d5d020f53a67ebf2c98049969553772->8eb3a70cf0752b54fa2f9651ccde79563112bad3",
+      ],
+    _outEdges:
+      [
+        "8eb3a70cf0752b54fa2f9651ccde79563112bad3->6164e5aa0f92ac872eb54641b4284fc3ec94aa0f",
+      ],
+  },
+
+  {
+    id: "6164e5aa0f92ac872eb54641b4284fc3ec94aa0f",
+    attr: { hash: "6164e5aa0f92ac872eb54641b4284fc3ec94aa0f" },
+    _inEdges:
+      [
+        "8eb3a70cf0752b54fa2f9651ccde79563112bad3->6164e5aa0f92ac872eb54641b4284fc3ec94aa0f",
+      ],
+    _outEdges:
+      [
+        "6164e5aa0f92ac872eb54641b4284fc3ec94aa0f->73d23a8fa2214261cacd174afdcf12d5cca86acb",
+      ],
+  },
+
+  {
+    id: "73d23a8fa2214261cacd174afdcf12d5cca86acb",
+    attr: { hash: "73d23a8fa2214261cacd174afdcf12d5cca86acb" },
+    _inEdges:
+      [
+        "6164e5aa0f92ac872eb54641b4284fc3ec94aa0f->73d23a8fa2214261cacd174afdcf12d5cca86acb",
+      ],
+    _outEdges: [],
+  },
+
+  {
+    id: "7fa90951d4ec0646f3368ccdbbbe89fe3de09212",
+    attr: { hash: "7fa90951d4ec0646f3368ccdbbbe89fe3de09212" },
+    _inEdges:
+      [
+        "d18b9728f2cd80571ea7f070ec2a62e40461b51e->7fa90951d4ec0646f3368ccdbbbe89fe3de09212",
+        "d2b7f87cb7f217f31076cd23ad903d73aeabb85d->7fa90951d4ec0646f3368ccdbbbe89fe3de09212",
+      ],
+    _outEdges:
+      [
+        "7fa90951d4ec0646f3368ccdbbbe89fe3de09212->0e42b353336a3b03c6c63765b7cf3258b725c559",
+      ],
+  },
+
+  {
+    id: "0e42b353336a3b03c6c63765b7cf3258b725c559",
+    attr: { hash: "0e42b353336a3b03c6c63765b7cf3258b725c559" },
+    _inEdges:
+      [
+        "7fa90951d4ec0646f3368ccdbbbe89fe3de09212->0e42b353336a3b03c6c63765b7cf3258b725c559",
+      ],
+    _outEdges:
+      [
+        "0e42b353336a3b03c6c63765b7cf3258b725c559->5f61a84fd0303994d1a31830b829c3d0f9ddeffa",
+      ],
+  },
+
+  {
+    id: "5f61a84fd0303994d1a31830b829c3d0f9ddeffa",
+    attr: { hash: "5f61a84fd0303994d1a31830b829c3d0f9ddeffa" },
+    _inEdges:
+      [
+        "0e42b353336a3b03c6c63765b7cf3258b725c559->5f61a84fd0303994d1a31830b829c3d0f9ddeffa",
+      ],
+    _outEdges:
+      [
+        "5f61a84fd0303994d1a31830b829c3d0f9ddeffa->33d5a8b8bf37f022b906740d2cd22d505073a34a",
+      ],
+  },
+
+  {
+    id: "16be08d1f9207f58a3b13b643a5f6df83b00851c",
+    attr: { hash: "16be08d1f9207f58a3b13b643a5f6df83b00851c" },
+    _inEdges:
+      [
+        "d7c6b760ceb136780fcabe2c16dffcbf2a7456f6->16be08d1f9207f58a3b13b643a5f6df83b00851c",
+      ],
+    _outEdges:
+      [
+        "16be08d1f9207f58a3b13b643a5f6df83b00851c->b45b33d7c424984790725b545e9f8826a4a26e90",
+      ],
+  },
+
+  {
+    id: "b45b33d7c424984790725b545e9f8826a4a26e90",
+    attr: { hash: "b45b33d7c424984790725b545e9f8826a4a26e90" },
+    _inEdges:
+      [
+        "16be08d1f9207f58a3b13b643a5f6df83b00851c->b45b33d7c424984790725b545e9f8826a4a26e90",
+      ],
+    _outEdges:
+      [
+        "b45b33d7c424984790725b545e9f8826a4a26e90->2c6f1ef1663049a478ac0b7e55df37b9c1c9a3c8",
+      ],
+  },
+
+  {
+    id: "2c6f1ef1663049a478ac0b7e55df37b9c1c9a3c8",
+    attr: { hash: "2c6f1ef1663049a478ac0b7e55df37b9c1c9a3c8" },
+    _inEdges:
+      [
+        "b45b33d7c424984790725b545e9f8826a4a26e90->2c6f1ef1663049a478ac0b7e55df37b9c1c9a3c8",
+      ],
+    _outEdges:
+      [
+        "2c6f1ef1663049a478ac0b7e55df37b9c1c9a3c8->d1f10da8f64d3023c23428e7d65be0e633ce8f04",
+      ],
+  },
+
+  {
+    id: "d1f10da8f64d3023c23428e7d65be0e633ce8f04",
+    attr: { hash: "d1f10da8f64d3023c23428e7d65be0e633ce8f04" },
+    _inEdges:
+      [
+        "2c6f1ef1663049a478ac0b7e55df37b9c1c9a3c8->d1f10da8f64d3023c23428e7d65be0e633ce8f04",
+      ],
+    _outEdges:
+      [
+        "d1f10da8f64d3023c23428e7d65be0e633ce8f04->dd7a7047f2c1c0f899f396e8769d3380073b4243",
+      ],
+  },
+
+  {
+    id: "dd7a7047f2c1c0f899f396e8769d3380073b4243",
+    attr: { hash: "dd7a7047f2c1c0f899f396e8769d3380073b4243" },
+    _inEdges:
+      [
+        "d1f10da8f64d3023c23428e7d65be0e633ce8f04->dd7a7047f2c1c0f899f396e8769d3380073b4243",
+      ],
+    _outEdges:
+      [
+        "dd7a7047f2c1c0f899f396e8769d3380073b4243->b372ba9e83dcb0b73f86ce733cff60a0895ec1e3",
+      ],
+  },
+
+  {
+    id: "b372ba9e83dcb0b73f86ce733cff60a0895ec1e3",
+    attr: { hash: "b372ba9e83dcb0b73f86ce733cff60a0895ec1e3" },
+    _inEdges:
+      [
+        "dd7a7047f2c1c0f899f396e8769d3380073b4243->b372ba9e83dcb0b73f86ce733cff60a0895ec1e3",
+      ],
+    _outEdges:
+      [
+        "b372ba9e83dcb0b73f86ce733cff60a0895ec1e3->8c89c7ff1dc8da4795ab4b173549d074dd564aa7",
+      ],
+  },
+
+  {
+    id: "8c89c7ff1dc8da4795ab4b173549d074dd564aa7",
+    attr: { hash: "8c89c7ff1dc8da4795ab4b173549d074dd564aa7" },
+    _inEdges:
+      [
+        "b372ba9e83dcb0b73f86ce733cff60a0895ec1e3->8c89c7ff1dc8da4795ab4b173549d074dd564aa7",
+      ],
+    _outEdges:
+      [
+        "8c89c7ff1dc8da4795ab4b173549d074dd564aa7->596c3f35749b39d4ec449e0f382521da5747f081",
+      ],
+  },
+
+  {
+    id: "596c3f35749b39d4ec449e0f382521da5747f081",
+    attr: { hash: "596c3f35749b39d4ec449e0f382521da5747f081" },
+    _inEdges:
+      [
+        "8c89c7ff1dc8da4795ab4b173549d074dd564aa7->596c3f35749b39d4ec449e0f382521da5747f081",
+      ],
+    _outEdges:
+      [
+        "596c3f35749b39d4ec449e0f382521da5747f081->f33f4b872f92bee46b104d94a7aad3474c91c53d",
+      ],
+  },
+
+  {
+    id: "f33f4b872f92bee46b104d94a7aad3474c91c53d",
+    attr: { hash: "f33f4b872f92bee46b104d94a7aad3474c91c53d" },
+    _inEdges:
+      [
+        "596c3f35749b39d4ec449e0f382521da5747f081->f33f4b872f92bee46b104d94a7aad3474c91c53d",
+      ],
+    _outEdges:
+      [
+        "f33f4b872f92bee46b104d94a7aad3474c91c53d->49a178a6d8b41b0bf4fb31008ddcc2e9e914662e",
+      ],
+  },
+
+  {
+    id: "49a178a6d8b41b0bf4fb31008ddcc2e9e914662e",
+    attr: { hash: "49a178a6d8b41b0bf4fb31008ddcc2e9e914662e" },
+    _inEdges:
+      [
+        "f33f4b872f92bee46b104d94a7aad3474c91c53d->49a178a6d8b41b0bf4fb31008ddcc2e9e914662e",
+      ],
+    _outEdges:
+      [
+        "49a178a6d8b41b0bf4fb31008ddcc2e9e914662e->ec1e05ae1f7b0f835b890b359459228d89b3788e",
+      ],
+  },
+
+  {
+    id: "ec1e05ae1f7b0f835b890b359459228d89b3788e",
+    attr: { hash: "ec1e05ae1f7b0f835b890b359459228d89b3788e" },
+    _inEdges:
+      [
+        "49a178a6d8b41b0bf4fb31008ddcc2e9e914662e->ec1e05ae1f7b0f835b890b359459228d89b3788e",
+      ],
+    _outEdges:
+      [
+        "ec1e05ae1f7b0f835b890b359459228d89b3788e->7c49a291b011c7d9c67a3c44f09cb018021b1d81",
+      ],
+  },
+
+  {
+    id: "7c49a291b011c7d9c67a3c44f09cb018021b1d81",
+    attr: { hash: "7c49a291b011c7d9c67a3c44f09cb018021b1d81" },
+    _inEdges:
+      [
+        "ec1e05ae1f7b0f835b890b359459228d89b3788e->7c49a291b011c7d9c67a3c44f09cb018021b1d81",
+      ],
+    _outEdges:
+      [
+        "7c49a291b011c7d9c67a3c44f09cb018021b1d81->aa081b26db3039b4a39be9fde0d0595b2770625c",
+      ],
+  },
+
+  {
+    id: "aa081b26db3039b4a39be9fde0d0595b2770625c",
+    attr: { hash: "aa081b26db3039b4a39be9fde0d0595b2770625c" },
+    _inEdges:
+      [
+        "7c49a291b011c7d9c67a3c44f09cb018021b1d81->aa081b26db3039b4a39be9fde0d0595b2770625c",
+      ],
+    _outEdges:
+      [
+        "aa081b26db3039b4a39be9fde0d0595b2770625c->7f06967106c0370d08cc8294b4adbb1d1ec0e3a1",
+      ],
+  },
+
+  {
+    id: "7f06967106c0370d08cc8294b4adbb1d1ec0e3a1",
+    attr: { hash: "7f06967106c0370d08cc8294b4adbb1d1ec0e3a1" },
+    _inEdges:
+      [
+        "aa081b26db3039b4a39be9fde0d0595b2770625c->7f06967106c0370d08cc8294b4adbb1d1ec0e3a1",
+      ],
+    _outEdges:
+      [
+        "7f06967106c0370d08cc8294b4adbb1d1ec0e3a1->ad88d251fdef37f9e4b619b346099475c2a6a13d",
+      ],
+  },
+
+  {
+    id: "ad88d251fdef37f9e4b619b346099475c2a6a13d",
+    attr: { hash: "ad88d251fdef37f9e4b619b346099475c2a6a13d" },
+    _inEdges:
+      [
+        "7f06967106c0370d08cc8294b4adbb1d1ec0e3a1->ad88d251fdef37f9e4b619b346099475c2a6a13d",
+      ],
+    _outEdges:
+      [
+        "ad88d251fdef37f9e4b619b346099475c2a6a13d->336c861d90196d45d3df2b6c553e56810765fb4b",
+      ],
+  },
+
+  {
+    id: "336c861d90196d45d3df2b6c553e56810765fb4b",
+    attr: { hash: "336c861d90196d45d3df2b6c553e56810765fb4b" },
+    _inEdges:
+      [
+        "ad88d251fdef37f9e4b619b346099475c2a6a13d->336c861d90196d45d3df2b6c553e56810765fb4b",
+      ],
+    _outEdges:
+      [
+        "336c861d90196d45d3df2b6c553e56810765fb4b->8d97eff47eb7809463564fc3d861ac85be87dcc1",
+      ],
+  },
+
+  {
+    id: "8d97eff47eb7809463564fc3d861ac85be87dcc1",
+    attr: { hash: "8d97eff47eb7809463564fc3d861ac85be87dcc1" },
+    _inEdges:
+      [
+        "336c861d90196d45d3df2b6c553e56810765fb4b->8d97eff47eb7809463564fc3d861ac85be87dcc1",
+      ],
+    _outEdges:
+      [
+        "8d97eff47eb7809463564fc3d861ac85be87dcc1->67d4381ff07c0f4f71c7ca093d6a617d98f62fd9",
+      ],
+  },
+
+  {
+    id: "edbb65f181cceb87d9dda7c3c37d08ea601ec651",
+    attr: { hash: "edbb65f181cceb87d9dda7c3c37d08ea601ec651" },
+    _inEdges:
+      [
+        "f94061d4ba59bef342b2ffba7cbe7d3568a9c52c->edbb65f181cceb87d9dda7c3c37d08ea601ec651",
+      ],
+    _outEdges:
+      [
+        "edbb65f181cceb87d9dda7c3c37d08ea601ec651->28cce4514ec9a3aa0d0cda4ab84cf7a6c8df0ca3",
+        "edbb65f181cceb87d9dda7c3c37d08ea601ec651->c2a1ea45e7e2906d888355ea069303de347ef968",
+      ],
+  },
+
+  {
+    id: "28cce4514ec9a3aa0d0cda4ab84cf7a6c8df0ca3",
+    attr: { hash: "28cce4514ec9a3aa0d0cda4ab84cf7a6c8df0ca3" },
+    _inEdges:
+      [
+        "edbb65f181cceb87d9dda7c3c37d08ea601ec651->28cce4514ec9a3aa0d0cda4ab84cf7a6c8df0ca3",
+      ],
+    _outEdges:
+      [
+        "28cce4514ec9a3aa0d0cda4ab84cf7a6c8df0ca3->10c7a8a81f5c7c86def9e2af55f3172d44a778cc",
+        "28cce4514ec9a3aa0d0cda4ab84cf7a6c8df0ca3->33d5a8b8bf37f022b906740d2cd22d505073a34a",
+      ],
+  },
+
+  {
+    id: "10c7a8a81f5c7c86def9e2af55f3172d44a778cc",
+    attr: { hash: "10c7a8a81f5c7c86def9e2af55f3172d44a778cc" },
+    _inEdges:
+      [
+        "28cce4514ec9a3aa0d0cda4ab84cf7a6c8df0ca3->10c7a8a81f5c7c86def9e2af55f3172d44a778cc",
+      ],
+    _outEdges:
+      [
+        "10c7a8a81f5c7c86def9e2af55f3172d44a778cc->8c18ba98ff5fa4becf72e22602d108a061d39c96",
+      ],
+  },
+]
diff --git a/scopes/scope/scope/scope.main.runtime.ts b/scopes/scope/scope/scope.main.runtime.ts
index 5f00b95a3a68..f6e0df5db639 100644
--- a/scopes/scope/scope/scope.main.runtime.ts
+++ b/scopes/scope/scope/scope.main.runtime.ts
@@ -27,7 +27,7 @@ import { UIAspect } from '@teambit/ui';
 import { BitId } from '@teambit/legacy-bit-id';
 import { BitIds, BitIds as ComponentsIds } from '@teambit/legacy/dist/bit-id';
 import { ModelComponent, Lane } from '@teambit/legacy/dist/scope/models';
-import { Repository } from '@teambit/legacy/dist/scope/objects';
+import { Ref, Repository } from '@teambit/legacy/dist/scope/objects';
 import LegacyScope, { LegacyOnTagResult } from '@teambit/legacy/dist/scope/scope';
 import { ComponentLog } from '@teambit/legacy/dist/scope/models/model-component';
 import { loadScopeIfExist } from '@teambit/legacy/dist/scope/scope-loader';
@@ -691,34 +691,61 @@ export class ScopeMain implements ComponentFactory {
    * If the graph cannot go any deeper (i.e., there are no more successors or predecessors), or the offset becomes zero, the function will return the last visited node.
    * This ensures that the function always returns a node, either the exact offset node (if it exists) or the closest node by offset.
    *
-   * @param _node The node from where to start the search.
-   * @param _versionGraph The graph in which to perform the search.
-   * @param _offset The offset from the starting node to find. It is always an absolute value.
-   * @param _getNodesFunc A function that, given a node id, returns the successors (if moving forward) or predecessors (if moving backward) of that node in the graph.
+   * @param node The node from where to start the search.
+   * @param versionGraph The graph in which to perform the search.
+   * @param offset The offset from the starting node to find. It is always an absolute value.
+   * @param getNodesFunc A function that, given a node id, returns the successors (if moving forward) or predecessors (if moving backward) of that node in the graph.
    *
    * @returns The node that corresponds to the exact offset if it exists, or the closest node by offset otherwise.
    */
-  private findNodeByOffset(_node, _versionGraph, _offset, _getNodesFunc) {
-    if (_offset === 0 || !_node) return _node;
+  private findNodeByOffset(
+    versionGraph: Graph<Ref, string>,
+    offset: number,
+    getNodesFunc: (
+      id: string,
+      {
+        nodeFilter,
+        edgeFilter,
+      }?: {
+        nodeFilter?: (node: Node<Ref>) => boolean;
+        edgeFilter?: (edge: Edge<string>) => boolean;
+      }
+    ) => Array<Node<Ref>>,
+    node?: Node<Ref>
+  ) {
+    if (offset === 0 || !node) return node;
 
-    const nextNodes = _getNodesFunc(_node.id, { edgeFilter: (edge) => edge.attr === 'parent' });
+    const nextNodes = getNodesFunc(node.id, { edgeFilter: (edge) => edge.attr === 'parent' });
 
     if (!nextNodes || nextNodes.length === 0) {
-      return _node;
+      return node;
     }
 
     for (const nextNode of nextNodes) {
-      const foundNode = this.findNodeByOffset(nextNode, _versionGraph, _offset - 1, _getNodesFunc);
+      const foundNode = this.findNodeByOffset(versionGraph, offset - 1, getNodesFunc, nextNode);
       if (foundNode) {
         return foundNode;
       }
     }
 
-    return _node;
+    return node;
   }
 
   /**
-   * get component log sorted by the timestamp in ascending order (from the earliest to the latest)
+   * Fetches the logs for a given component.
+   *
+   * @param id - The ComponentID of the component for which to fetch logs.
+   * @param shortHash - If true, returns a shorter version of the hash. Defaults to false.
+   * @param head - The specific version to start fetching logs from. If not provided, starts from the head.
+   * @param startFrom - The specific version to start slicing logs from.
+   * @param stopAt - The specific version to stop fetching logs at.
+   * @param startFromOffset - Offset from the start version to fetch logs from.
+   * @param stopAtOffset - Offset from the stop version to fetch logs at.
+   *
+   * @returns A promise that resolves to an array of ComponentLog objects representing the filtered logs for the component.
+   *
+   * @throws Error - Throws an error if the node with given headRef hash is not found.
+   *
    */
   async getLogs(
     id: ComponentID,
@@ -754,10 +781,10 @@ export class ScopeMain implements ComponentFactory {
 
     if (startOffset !== 0) {
       startNode = this.findNodeByOffset(
-        startNode,
         versionGraph,
         Math.abs(startOffset),
-        startOffset > 0 ? versionGraph.successors.bind(versionGraph) : versionGraph.predecessors.bind(versionGraph)
+        startOffset > 0 ? versionGraph.successors.bind(versionGraph) : versionGraph.predecessors.bind(versionGraph),
+        startNode
       );
     }
 
@@ -766,10 +793,10 @@ export class ScopeMain implements ComponentFactory {
     let stopNode = stopRef?.hash ? versionGraph.node(stopRef.hash) : undefined;
     if (stopOffset !== 0) {
       stopNode = this.findNodeByOffset(
-        stopNode,
         versionGraph,
         Math.abs(stopOffset),
-        stopOffset > 0 ? versionGraph.successors.bind(versionGraph) : versionGraph.predecessors.bind(versionGraph)
+        stopOffset > 0 ? versionGraph.successors.bind(versionGraph) : versionGraph.predecessors.bind(versionGraph),
+        stopNode
       );
     }
 
diff --git a/src/scope/component-ops/traverse-versions.ts b/src/scope/component-ops/traverse-versions.ts
index 6fb540ade05c..5265cbcdfcb9 100644
--- a/src/scope/component-ops/traverse-versions.ts
+++ b/src/scope/component-ops/traverse-versions.ts
@@ -48,10 +48,8 @@ export async function getAllVersionsInfo({
   repo?: Repository;
   throws?: boolean; // in case objects are missing
   versionObjects?: Version[];
-  startFrom?: Ref | null; // by default, start from the head //
-  startFromOffset?: number; // -5
+  startFrom?: Ref | null; // by default, start from the head
   stopAt?: Ref[] | null; // by default, stop when the parents is empty
-  stopAtOffset?: number; // 5
 }): Promise<VersionInfo[]> {
   const results: VersionInfo[] = [];
   const isAlreadyProcessed = (ref: Ref): boolean => {

From 93c92d65c9d605459dd9ca7117c7ab40cec92358 Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Fri, 12 May 2023 15:21:49 -0400
Subject: [PATCH 13/20] lint fix

---
 scopes/component/component/component.ts                   | 1 -
 scopes/component/ui/version-dropdown/version-dropdown.tsx | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/scopes/component/component/component.ts b/scopes/component/component/component.ts
index c1b489f9cd36..1dcab2d3b268 100644
--- a/scopes/component/component/component.ts
+++ b/scopes/component/component/component.ts
@@ -5,7 +5,6 @@ import { ComponentID } from '@teambit/component-id';
 import { BitError } from '@teambit/bit-error';
 import { BuildStatus } from '@teambit/legacy/dist/constants';
 
-import { slice } from 'lodash';
 import { ComponentFactory } from './component-factory';
 import ComponentFS from './component-fs';
 // import { NothingToSnap } from './exceptions';
diff --git a/scopes/component/ui/version-dropdown/version-dropdown.tsx b/scopes/component/ui/version-dropdown/version-dropdown.tsx
index 19698a8d7b00..ca3441cce9a2 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown.tsx
+++ b/scopes/component/ui/version-dropdown/version-dropdown.tsx
@@ -4,7 +4,7 @@ import { Dropdown } from '@teambit/evangelist.surfaces.dropdown';
 import { Tab } from '@teambit/ui-foundation.ui.use-box.tab';
 import { LegacyComponentLog } from '@teambit/legacy-component-log';
 import { UserAvatar } from '@teambit/design.ui.avatar';
-import { LineSkeleton, WordSkeleton } from '@teambit/base-ui.loaders.skeleton';
+import { LineSkeleton } from '@teambit/base-ui.loaders.skeleton';
 import { LaneModel } from '@teambit/lanes.ui.models.lanes-model';
 import classNames from 'classnames';
 

From ae77ed87d334c1991bc7a39eea9b84f71b662d65 Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Fri, 12 May 2023 16:37:52 -0400
Subject: [PATCH 14/20] fix updating offset while lazy loading

---
 .../component/ui/use-component-query.ts       | 145 +++++++++++-------
 1 file changed, 90 insertions(+), 55 deletions(-)

diff --git a/scopes/component/component/ui/use-component-query.ts b/scopes/component/component/ui/use-component-query.ts
index aea5653c451f..123c5bae98e9 100644
--- a/scopes/component/component/ui/use-component-query.ts
+++ b/scopes/component/component/ui/use-component-query.ts
@@ -255,6 +255,51 @@ function getOffsetValue(offset, limit) {
   }
   return undefined;
 }
+/**
+ * Calculates the new offset based on initial offset, current offset, and the number of logs.
+ *
+ * @param {boolean} [fetchLogsByTypeSeparately] A flag to determine if logs are fetched by type separately.
+ * @param {number} [initialOffset] The initial offset.
+ * @param {number} [currentOffset] The current offset.
+ * @param {any[]} [logs=[]] The array of logs.
+ *
+ * @returns {number | undefined} -  new offset
+ */
+function calculateNewOffset(
+  fetchLogsByTypeSeparately?: boolean,
+  initialOffset = 0,
+  currentOffset = 0,
+  logs: any[] = []
+): number | undefined {
+  if (!fetchLogsByTypeSeparately) return currentOffset;
+
+  const logCount = logs.length;
+
+  if (initialOffset !== currentOffset && logCount + initialOffset >= currentOffset) return currentOffset;
+  return logCount + initialOffset;
+}
+
+/**
+ * Calculate the availability of more logs.
+ *
+ * @param {number | undefined} logLimit - The limit for the logs.
+ * @param {any} rawComponent - The raw component object containing logs.
+ * @param {string} logType - Type of log ('logs', 'tagLogs', 'snapLogs').
+ * @param {boolean | undefined} currentHasMoreLogs - Current state of having more logs.
+ *
+ * @returns {boolean | undefined} - Whether there are more logs available.
+ */
+function calculateHasMoreLogs(
+  logLimit?: number,
+  rawComponent?: any,
+  logType = 'logs',
+  currentHasMoreLogs?: boolean
+): boolean | undefined {
+  if (!logLimit) return false;
+  if (rawComponent === undefined) return undefined;
+  if (currentHasMoreLogs === undefined) return rawComponent?.[logType]?.length >= logLimit;
+  return currentHasMoreLogs;
+}
 /** provides data to component ui page, making sure both variables and return value are safely typed and memoized */
 export function useComponentQuery(
   componentId: string,
@@ -324,49 +369,38 @@ export function useComponentQuery(
   });
 
   const rawComponent = data?.getHost?.get;
-  const rawTags = rawComponent?.tagLogs ?? [];
-  const rawSnaps = rawComponent?.snapLogs ?? [];
-  const rawCompLogs = rawComponent?.logs ?? mergeLogs(rawTags, rawSnaps);
-  offsetRef.current = useMemo(() => {
-    const currentOffset = offsetRef.current;
-    if (!currentOffset) return rawCompLogs.length;
-    return offsetRef.current;
-  }, [rawCompLogs]);
-
-  tagOffsetRef.current = useMemo(() => {
-    if (!fetchLogsByTypeSeparately) return offsetRef.current;
-    const currentOffset = tagOffsetRef.current;
-    if (!currentOffset) return rawTags.length;
-    return tagOffsetRef.current;
-  }, [rawCompLogs]);
-
-  snapOffsetRef.current = useMemo(() => {
-    if (!fetchLogsByTypeSeparately) return offsetRef.current;
-    const currentOffset = snapOffsetRef.current;
-    if (!currentOffset) return rawSnaps.length;
-    return snapOffsetRef.current;
-  }, [rawSnaps]);
-
-  hasMoreLogs.current = useMemo(() => {
-    if (!logLimit) return false;
-    if (rawComponent === undefined) return undefined;
-    if (hasMoreLogs.current === undefined) return rawComponent?.logs.length >= logLimit;
-    return hasMoreLogs.current;
-  }, [rawCompLogs]);
-
-  hasMoreTagLogs.current = useMemo(() => {
-    if (!tagLogLimit) return false;
-    if (rawComponent === undefined) return undefined;
-    if (hasMoreTagLogs.current === undefined) return rawComponent?.tagLogs.length >= tagLogLimit;
-    return hasMoreTagLogs.current;
-  }, [rawTags]);
-
-  hasMoreSnapLogs.current = useMemo(() => {
-    if (!snapLogLimit) return false;
-    if (rawComponent === undefined) return undefined;
-    if (hasMoreSnapLogs.current === undefined) return rawComponent?.snapLogs.length === snapLogLimit;
-    return hasMoreSnapLogs.current;
-  }, [rawSnaps]);
+  const rawTags: Array<any> = rawComponent?.tagLogs ?? [];
+  const rawSnaps: Array<any> = rawComponent?.snapLogs ?? [];
+  const rawCompLogs: Array<any> = rawComponent?.logs ?? mergeLogs(rawTags, rawSnaps);
+  offsetRef.current = useMemo(
+    () => calculateNewOffset(fetchLogsByTypeSeparately, logOffset, offsetRef.current, rawCompLogs),
+    [rawCompLogs, fetchLogsByTypeSeparately, logOffset]
+  );
+
+  tagOffsetRef.current = useMemo(
+    () => calculateNewOffset(fetchLogsByTypeSeparately, tagLogOffset, tagOffsetRef.current, rawTags),
+    [rawTags, fetchLogsByTypeSeparately, tagLogOffset]
+  );
+
+  snapOffsetRef.current = useMemo(
+    () => calculateNewOffset(fetchLogsByTypeSeparately, snapLogOffset, snapOffsetRef.current, rawSnaps),
+    [rawSnaps, fetchLogsByTypeSeparately, snapLogOffset]
+  );
+
+  hasMoreLogs.current = useMemo(
+    () => calculateHasMoreLogs(logLimit, rawComponent, 'logs', hasMoreLogs.current),
+    [rawCompLogs]
+  );
+
+  hasMoreTagLogs.current = useMemo(
+    () => calculateHasMoreLogs(tagLogLimit, rawComponent, 'tagLogs', hasMoreTagLogs.current),
+    [rawTags]
+  );
+
+  hasMoreSnapLogs.current = useMemo(
+    () => calculateHasMoreLogs(snapLogLimit, rawComponent, 'snapLogs', hasMoreSnapLogs.current),
+    [rawSnaps]
+  );
 
   const loadMoreLogs = React.useCallback(
     async (backwards = false) => {
@@ -386,7 +420,7 @@ export function useComponentQuery(
               const updatedLogs = mergeLogs(prevComponent.logs, fetchedComponent.logs);
               hasMoreLogs.current = fetchedComponent.logs.length >= logLimit;
               if (updatedLogs.length > prevComponent.logs.length) {
-                offsetRef.current = updatedLogs.length;
+                offsetRef.current = fetchedComponent.logs.length + offset;
               }
 
               return {
@@ -424,13 +458,14 @@ export function useComponentQuery(
 
             const prevComponent = prev.getHost.get;
             const fetchedComponent = fetchMoreResult.getHost.get;
-            const prevCompLogs = prevComponent.tagLogs;
+            const prevTags = prevComponent.tagLogs;
+            const fetchedTags = fetchedComponent.tagLogs ?? [];
             if (fetchedComponent && ComponentID.isEqualObj(prevComponent.id, fetchedComponent.id)) {
-              const updatedTags = mergeLogs(prevCompLogs, fetchedComponent.tagLogs);
-              if (updatedTags.length > prevCompLogs.length) {
-                tagOffsetRef.current = updatedTags.length;
+              const updatedTags = mergeLogs(prevTags, fetchedTags);
+              if (updatedTags.length > prevTags.length) {
+                tagOffsetRef.current = fetchedTags.length + offset;
               }
-              hasMoreTagLogs.current = fetchedComponent.tagLogs.length >= tagLogLimit;
+              hasMoreTagLogs.current = fetchedTags.length >= tagLogLimit;
               return {
                 ...prev,
                 getHost: {
@@ -465,15 +500,15 @@ export function useComponentQuery(
             if (!fetchMoreResult) return prev;
 
             const prevComponent = prev.getHost.get;
-            const prevCompLogs = prevComponent.snapLogs ?? [];
-
+            const prevSnaps = prevComponent.snapLogs ?? [];
             const fetchedComponent = fetchMoreResult.getHost.get;
+            const fetchedSnaps = fetchedComponent.snapLogs ?? [];
             if (fetchedComponent && ComponentID.isEqualObj(prevComponent.id, fetchedComponent.id)) {
-              const updatedSnaps = mergeLogs(prevCompLogs, fetchedComponent.snapLogs);
-              if (updatedSnaps.length > prevCompLogs.length) {
-                snapOffsetRef.current = updatedSnaps.length;
+              const updatedSnaps = mergeLogs(prevSnaps, fetchedSnaps);
+              if (updatedSnaps.length > prevSnaps.length) {
+                snapOffsetRef.current = fetchedSnaps.length + offset;
               }
-              hasMoreSnapLogs.current = fetchedComponent.snapLogs.length >= snapLogLimit;
+              hasMoreSnapLogs.current = fetchedSnaps.length >= snapLogLimit;
               return {
                 ...prev,
                 getHost: {

From 72b64f2a609af822ee084b28ecb25a40f1f92503 Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Mon, 15 May 2023 17:35:10 -0400
Subject: [PATCH 15/20] allow lazy loading by type

---
 .../changelog/ui/change-log-page.tsx          |  1 +
 .../component/component/component-factory.ts  |  3 +-
 .../component/component/component.graphql.ts  |  2 +-
 scopes/component/component/component.ts       | 16 +---
 .../component/ui/use-component-query.ts       | 71 +++++++++-----
 .../ui/version-dropdown/version-dropdown.tsx  | 17 ++--
 scopes/lanes/lanes/lanes.ui.runtime.tsx       | 13 ++-
 scopes/scope/scope/scope.main.runtime.ts      | 95 ++++++++++++++-----
 scopes/workspace/workspace/workspace.ts       |  5 +-
 9 files changed, 149 insertions(+), 74 deletions(-)

diff --git a/scopes/component/changelog/ui/change-log-page.tsx b/scopes/component/changelog/ui/change-log-page.tsx
index c518c24aa956..a63ab88788ee 100644
--- a/scopes/component/changelog/ui/change-log-page.tsx
+++ b/scopes/component/changelog/ui/change-log-page.tsx
@@ -20,6 +20,7 @@ export function ChangeLogPage({ className, host }: ChangeLogPageProps) {
     logFilters: {
       log: {
         logLimit: 15,
+        logOffset: 0,
       },
     },
   });
diff --git a/scopes/component/component/component-factory.ts b/scopes/component/component/component-factory.ts
index 4bbd20a5392b..2adb3d40d316 100644
--- a/scopes/component/component/component-factory.ts
+++ b/scopes/component/component/component-factory.ts
@@ -122,7 +122,8 @@ export interface ComponentFactory {
     startFrom?: string,
     stopAt?: string,
     startFromOffset?: number,
-    stopAtOffset?: number
+    stopAtOffset?: number,
+    type?: 'tag' | 'snap'
   ): Promise<ComponentLog[]>;
 
   /**
diff --git a/scopes/component/component/component.graphql.ts b/scopes/component/component/component.graphql.ts
index 4177e7895fea..1c3cc4e78875 100644
--- a/scopes/component/component/component.graphql.ts
+++ b/scopes/component/component/component.graphql.ts
@@ -202,7 +202,7 @@ export function componentSchema(componentExtension: ComponentMain) {
         logs: async (
           component: Component,
           filter?: {
-            type?: string;
+            type?: 'tag' | 'snap';
             offset?: number;
             limit?: number;
             head?: string;
diff --git a/scopes/component/component/component.ts b/scopes/component/component/component.ts
index 1dcab2d3b268..0505e8bced7f 100644
--- a/scopes/component/component/component.ts
+++ b/scopes/component/component/component.ts
@@ -114,7 +114,7 @@ export class Component implements IComponent {
   }
 
   async getLogs(filter?: {
-    type?: string;
+    type?: 'tag' | 'snap';
     offset?: number;
     limit?: number;
     head?: string;
@@ -123,20 +123,10 @@ export class Component implements IComponent {
     until?: string;
   }) {
     const { type, limit, offset, sort, head, startFrom, until } = filter || {};
-    const typeFilter = (snap) => {
-      if (type === 'tag') return snap.tag;
-      if (type === 'snap') return !snap.tag;
-      return true;
-    };
-
-    const allLogs = await this.factory.getLogs(this.id, false, head, startFrom, until, offset, limit);
 
-    if (!filter) {
-      return allLogs;
-    }
+    const filteredLogs = await this.factory.getLogs(this.id, false, head, startFrom, until, offset, limit, type);
 
-    let filteredLogs = (type && allLogs.filter(typeFilter)) || allLogs;
-    if (sort !== 'asc') filteredLogs = filteredLogs.reverse();
+    if (sort !== 'asc') filteredLogs.reverse();
 
     return filteredLogs;
   }
diff --git a/scopes/component/component/ui/use-component-query.ts b/scopes/component/component/ui/use-component-query.ts
index 123c5bae98e9..945136ad5784 100644
--- a/scopes/component/component/ui/use-component-query.ts
+++ b/scopes/component/component/ui/use-component-query.ts
@@ -246,9 +246,9 @@ export type ComponentQueryResult = {
   loading?: boolean;
   error?: ComponentError;
 };
-function getOffsetValue(offset, limit) {
+function getOffsetValue(offset, limit, backwards = false) {
   if (offset !== undefined) {
-    return offset;
+    return backwards ? -offset : offset;
   }
   if (limit !== undefined) {
     return 0;
@@ -265,14 +265,7 @@ function getOffsetValue(offset, limit) {
  *
  * @returns {number | undefined} -  new offset
  */
-function calculateNewOffset(
-  fetchLogsByTypeSeparately?: boolean,
-  initialOffset = 0,
-  currentOffset = 0,
-  logs: any[] = []
-): number | undefined {
-  if (!fetchLogsByTypeSeparately) return currentOffset;
-
+function calculateNewOffset(initialOffset = 0, currentOffset = 0, logs: any[] = []): number | undefined {
   const logCount = logs.length;
 
   if (initialOffset !== currentOffset && logCount + initialOffset >= currentOffset) return currentOffset;
@@ -335,7 +328,7 @@ export function useComponentQuery(
     fetchLogsByTypeSeparately,
     snapLogOffset: getOffsetValue(snapLogOffset, snapLogLimit),
     tagLogOffset: getOffsetValue(tagLogOffset, tagLogLimit),
-    logOffset: logOffset ?? logLimit ? 0 : undefined,
+    logOffset: getOffsetValue(logOffset, logLimit),
     logLimit,
     snapLogLimit,
     tagLogLimit,
@@ -373,17 +366,17 @@ export function useComponentQuery(
   const rawSnaps: Array<any> = rawComponent?.snapLogs ?? [];
   const rawCompLogs: Array<any> = rawComponent?.logs ?? mergeLogs(rawTags, rawSnaps);
   offsetRef.current = useMemo(
-    () => calculateNewOffset(fetchLogsByTypeSeparately, logOffset, offsetRef.current, rawCompLogs),
+    () => calculateNewOffset(logOffset, offsetRef.current, rawCompLogs),
     [rawCompLogs, fetchLogsByTypeSeparately, logOffset]
   );
 
   tagOffsetRef.current = useMemo(
-    () => calculateNewOffset(fetchLogsByTypeSeparately, tagLogOffset, tagOffsetRef.current, rawTags),
+    () => calculateNewOffset(tagLogOffset, tagOffsetRef.current, rawTags),
     [rawTags, fetchLogsByTypeSeparately, tagLogOffset]
   );
 
   snapOffsetRef.current = useMemo(
-    () => calculateNewOffset(fetchLogsByTypeSeparately, snapLogOffset, snapOffsetRef.current, rawSnaps),
+    () => calculateNewOffset(snapLogOffset, snapOffsetRef.current, rawSnaps),
     [rawSnaps, fetchLogsByTypeSeparately, snapLogOffset]
   );
 
@@ -404,7 +397,7 @@ export function useComponentQuery(
 
   const loadMoreLogs = React.useCallback(
     async (backwards = false) => {
-      const offset = offsetRef.current ? (backwards && -offsetRef.current) || offsetRef.current : undefined;
+      const offset = getOffsetValue(offsetRef.current, logLimit, backwards);
 
       if (logLimit) {
         await fetchMore({
@@ -445,7 +438,7 @@ export function useComponentQuery(
 
   const loadMoreTags = React.useCallback(
     async (backwards = false) => {
-      const offset = tagOffsetRef.current ? (backwards && -tagOffsetRef.current) || tagOffsetRef.current : undefined;
+      const offset = getOffsetValue(tagOffsetRef.current, tagLogLimit, backwards);
 
       if (tagLogLimit) {
         await fetchMore({
@@ -488,7 +481,7 @@ export function useComponentQuery(
 
   const loadMoreSnaps = React.useCallback(
     async (backwards = false) => {
-      const offset = snapOffsetRef.current ? (backwards && -snapOffsetRef.current) || snapOffsetRef.current : undefined;
+      const offset = getOffsetValue(snapOffsetRef.current, snapLogLimit, backwards);
 
       if (snapLogLimit) {
         await fetchMore({
@@ -687,10 +680,44 @@ interface Log {
   id: string;
   date: string;
 }
-
 function mergeLogs(logs1: Log[] = [], logs2: Log[] = []): Log[] {
-  const mergedLogs: Log[] = [];
-  logs1.forEach((log) => mergedLogs.push(log));
-  logs2.forEach((log) => mergedLogs.push(log));
-  return mergedLogs;
+  const logMap = new Map<string, Log>();
+  const result: Log[] = [];
+
+  let index1 = 0;
+  let index2 = 0;
+
+  while (index1 < logs1.length && index2 < logs2.length) {
+    if (Number(logs1[index1].date) >= Number(logs2[index2].date)) {
+      if (!logMap.has(logs1[index1].id)) {
+        logMap.set(logs1[index1].id, logs1[index1]);
+        result.push(logs1[index1]);
+      }
+      index1 += 1;
+    } else {
+      if (!logMap.has(logs2[index2].id)) {
+        logMap.set(logs2[index2].id, logs2[index2]);
+        result.push(logs2[index2]);
+      }
+      index2 += 1;
+    }
+  }
+
+  while (index1 < logs1.length) {
+    if (!logMap.has(logs1[index1].id)) {
+      logMap.set(logs1[index1].id, logs1[index1]);
+      result.push(logs1[index1]);
+    }
+    index1 += 1;
+  }
+
+  while (index2 < logs2.length) {
+    if (!logMap.has(logs2[index2].id)) {
+      logMap.set(logs2[index2].id, logs2[index2]);
+      result.push(logs2[index2]);
+    }
+    index2 += 1;
+  }
+
+  return result;
 }
diff --git a/scopes/component/ui/version-dropdown/version-dropdown.tsx b/scopes/component/ui/version-dropdown/version-dropdown.tsx
index ca3441cce9a2..1ce97e72aae0 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown.tsx
+++ b/scopes/component/ui/version-dropdown/version-dropdown.tsx
@@ -69,7 +69,8 @@ function _VersionMenu(
 
   const activeTabOrSnap: 'SNAP' | 'TAG' | 'LANE' | undefined = tabs[activeTabIndex]?.name;
   const hasMore = activeTabOrSnap === 'SNAP' ? !!hasMoreSnaps : activeTabOrSnap === 'TAG' && !!hasMoreTags;
-  const observer = React.useRef<IntersectionObserver>();
+  const firstObserver = React.useRef<IntersectionObserver>();
+  const lastObserver = React.useRef<IntersectionObserver>();
 
   const handleLoadMore = React.useCallback(
     (backwards?: boolean) => {
@@ -82,8 +83,8 @@ function _VersionMenu(
   const lastLogRef = React.useCallback(
     (node) => {
       if (loading) return;
-      if (observer.current) observer.current.disconnect();
-      observer.current = new IntersectionObserver(
+      if (lastObserver.current) lastObserver.current.disconnect();
+      lastObserver.current = new IntersectionObserver(
         (entries) => {
           if (entries[0].isIntersecting && hasMore) {
             handleLoadMore();
@@ -94,7 +95,7 @@ function _VersionMenu(
           rootMargin: '100px',
         }
       );
-      if (node) observer.current.observe(node);
+      if (node) lastObserver.current.observe(node);
     },
     [loading, hasMoreSnaps, hasMoreTags, handleLoadMore]
   );
@@ -102,8 +103,8 @@ function _VersionMenu(
   const firstLogRef = React.useCallback(
     (node) => {
       if (loading) return;
-      if (observer.current) observer.current.disconnect();
-      observer.current = new IntersectionObserver(
+      if (firstObserver.current) firstObserver.current.disconnect();
+      firstObserver.current = new IntersectionObserver(
         (entries) => {
           if (entries[0].isIntersecting && hasMore) {
             handleLoadMore(true);
@@ -111,10 +112,10 @@ function _VersionMenu(
         },
         {
           threshold: 0.1,
-          rootMargin: '100px',
+          rootMargin: '50px',
         }
       );
-      if (node) observer.current.observe(node);
+      if (node) firstObserver.current.observe(node);
     },
     [loading, hasMoreSnaps, hasMoreTags, handleLoadMore]
   );
diff --git a/scopes/lanes/lanes/lanes.ui.runtime.tsx b/scopes/lanes/lanes/lanes.ui.runtime.tsx
index 0d0ca89bc775..93402a945c31 100644
--- a/scopes/lanes/lanes/lanes.ui.runtime.tsx
+++ b/scopes/lanes/lanes/lanes.ui.runtime.tsx
@@ -16,6 +16,7 @@ import {
   LanesOrderedNavigationSlot,
   LanesOverviewMenu,
 } from '@teambit/lanes.ui.menus.lanes-overview-menu';
+import { useQuery } from '@teambit/ui-foundation.ui.react-router.use-query';
 import { UseLaneMenu } from '@teambit/lanes.ui.menus.use-lanes-menu';
 import { LanesHost, LanesModel } from '@teambit/lanes.ui.models.lanes-model';
 import { LanesProvider, useLanes, IgnoreDerivingFromUrl } from '@teambit/lanes.hooks.use-lanes';
@@ -52,16 +53,26 @@ export function useComponentFilters() {
     },
   };
 }
-export function useLaneComponentIdFromUrl() {
+export function useLaneComponentIdFromUrl(): ComponentID | undefined | null {
   const idFromLocation = useIdFromLocation();
   const { lanesModel, loading } = useLanes();
   const laneFromUrl = useViewedLaneFromUrl();
+  const query = useQuery();
+  const componentVersion = query.get('version');
+
+  if (componentVersion && laneFromUrl) {
+    const componentId = ComponentID.fromString(`${idFromLocation}@${componentVersion}`);
+    console.log('🚀 ~ file: lanes.ui.runtime.tsx:66 ~ useLaneComponentIdFromUrl ~ componentId:', componentId);
+    return componentId;
+  }
   const laneComponentId =
     idFromLocation && !laneFromUrl?.isDefault()
       ? lanesModel?.resolveComponentFromUrl(idFromLocation, laneFromUrl) ?? null
       : null;
+
   return loading ? undefined : laneComponentId;
 }
+
 export function useComponentId() {
   return useLaneComponentIdFromUrl()?.toString();
 }
diff --git a/scopes/scope/scope/scope.main.runtime.ts b/scopes/scope/scope/scope.main.runtime.ts
index f6e0df5db639..2efe6ced8a5a 100644
--- a/scopes/scope/scope/scope.main.runtime.ts
+++ b/scopes/scope/scope/scope.main.runtime.ts
@@ -51,6 +51,7 @@ import ConsumerComponent from '@teambit/legacy/dist/consumer/component';
 import { resumeExport } from '@teambit/legacy/dist/scope/component-ops/export-scope-components';
 import { ExtensionDataEntry, ExtensionDataList } from '@teambit/legacy/dist/consumer/config';
 import EnvsAspect, { EnvsMain } from '@teambit/envs';
+import { isSnap, isTag } from '@teambit/component-version';
 import { compact, slice, difference } from 'lodash';
 import { ComponentNotFound } from './exceptions';
 import { ScopeAspect } from './scope.aspect';
@@ -680,23 +681,24 @@ export class ScopeMain implements ComponentFactory {
     if (!ref) throw new Error(`ref was not found: ${id.toString()} with tag ${hash}`);
     return this.componentLoader.getSnap(id, ref.toString());
   }
-
   /**
    * Performs a Depth-First Search (DFS) to find a node in a graph by offset.
    *
-   * This function starts from a given node and moves either forward or backward in the graph, depending on the provided getNodes function.
-   * The goal is to reach a node that is at a given offset from the starting node. The offset is decremented each time a new node is visited.
-   * The function will return the node that corresponds to the exact offset if it exists.
+   * This function starts from a given node and moves either forward or backward in the graph, depending on the provided getNodesFunc function.
+   * The goal is to reach a node that is at a given offset from the starting node
    *
    * If the graph cannot go any deeper (i.e., there are no more successors or predecessors), or the offset becomes zero, the function will return the last visited node.
    * This ensures that the function always returns a node, either the exact offset node (if it exists) or the closest node by offset.
    *
-   * @param node The node from where to start the search.
    * @param versionGraph The graph in which to perform the search.
    * @param offset The offset from the starting node to find. It is always an absolute value.
    * @param getNodesFunc A function that, given a node id, returns the successors (if moving forward) or predecessors (if moving backward) of that node in the graph.
+   * @param node The node from where to start the search. If not provided, the function will return undefined.
+   * @param nodeFilter Optional. A function that, given a node, determines if it should be included in the search. If not provided, all nodes are included.
+   * @param edgeFilter Optional. A function that, given an edge, determines if it should be included in the search. If not provided, all edges are included.
+   * @param skipNode Optional. A function that, given a node, determines if it should be skipped (not decrease the offset). If not provided, no nodes are skipped.
    *
-   * @returns The node that corresponds to the exact offset if it exists, or the closest node by offset otherwise.
+   * @returns The node that corresponds to the exact offset if it exists, or the closest node by offset otherwise. If no starting node is provided, the function will return undefined.
    */
   private findNodeByOffset(
     versionGraph: Graph<Ref, string>,
@@ -711,18 +713,33 @@ export class ScopeMain implements ComponentFactory {
         edgeFilter?: (edge: Edge<string>) => boolean;
       }
     ) => Array<Node<Ref>>,
-    node?: Node<Ref>
+    node?: Node<Ref>,
+    nodeFilter?: (node: Node<Ref>) => boolean,
+    edgeFilter?: (edge: Edge<string>) => boolean,
+    skipNode?: (node: Node<Ref>) => boolean
   ) {
     if (offset === 0 || !node) return node;
 
-    const nextNodes = getNodesFunc(node.id, { edgeFilter: (edge) => edge.attr === 'parent' });
+    const nextNodes = getNodesFunc(node.id, { edgeFilter, nodeFilter });
 
     if (!nextNodes || nextNodes.length === 0) {
       return node;
     }
 
     for (const nextNode of nextNodes) {
-      const foundNode = this.findNodeByOffset(versionGraph, offset - 1, getNodesFunc, nextNode);
+      const skip = skipNode && skipNode(nextNode);
+      const nextOffset = skip ? offset : offset - 1;
+
+      const foundNode = this.findNodeByOffset(
+        versionGraph,
+        nextOffset,
+        getNodesFunc,
+        nextNode,
+        nodeFilter,
+        edgeFilter,
+        skipNode
+      );
+
       if (foundNode) {
         return foundNode;
       }
@@ -734,19 +751,20 @@ export class ScopeMain implements ComponentFactory {
   /**
    * Fetches the logs for a given component.
    *
-   * @param id - The ComponentID of the component for which to fetch logs.
-   * @param shortHash - If true, returns a shorter version of the hash. Defaults to false.
-   * @param head - The specific version to start fetching logs from. If not provided, starts from the head.
-   * @param startFrom - The specific version to start slicing logs from.
-   * @param stopAt - The specific version to stop fetching logs at.
-   * @param startFromOffset - Offset from the start version to fetch logs from.
-   * @param stopAtOffset - Offset from the stop version to fetch logs at.
+   * @param id The ComponentID of the component for which to fetch logs.
+   * @param shortHash If true, returns a shorter version of the hash. Defaults to false.
+   * @param head The specific version to start fetching logs from. If not provided, starts from the head.
+   * @param startFrom The specific version to start slicing logs from.
+   * @param stopAt The specific version to stop fetching logs at.
+   * @param startFromOffset Offset from the start version to fetch logs from.
+   * @param stopAtOffset Offset from the stop version to fetch logs at.
+   * @param type Optional. The type of logs to fetch - 'snap' or 'tag'
    *
    * @returns A promise that resolves to an array of ComponentLog objects representing the filtered logs for the component.
    *
    * @throws Error - Throws an error if the node with given headRef hash is not found.
-   *
    */
+
   async getLogs(
     id: ComponentID,
     shortHash = false,
@@ -754,7 +772,8 @@ export class ScopeMain implements ComponentFactory {
     startFrom?: string,
     stopAt?: string,
     startFromOffset?: number,
-    stopAtOffset?: number
+    stopAtOffset?: number,
+    type?: 'snap' | 'tag'
   ): Promise<ComponentLog[]> {
     const componentModel = await this.legacyScope.getModelComponentIfExist(id._legacy);
 
@@ -779,12 +798,28 @@ export class ScopeMain implements ComponentFactory {
 
     const startOffset = startFromOffset || 0;
 
+    const componentVersionSet = new Set<string>(componentModel.versionArray.map((v) => v.hash));
+
+    const skipNode = (node: Node<Ref>) => {
+      if (type === 'snap') {
+        return componentVersionSet.has(node.id);
+      }
+      if (type === 'tag') {
+        return !componentVersionSet.has(node.id);
+      }
+
+      return false;
+    };
+
     if (startOffset !== 0) {
       startNode = this.findNodeByOffset(
         versionGraph,
         Math.abs(startOffset),
         startOffset > 0 ? versionGraph.successors.bind(versionGraph) : versionGraph.predecessors.bind(versionGraph),
-        startNode
+        startNode,
+        undefined,
+        (edge) => edge.attr === 'parent',
+        skipNode
       );
     }
 
@@ -796,16 +831,24 @@ export class ScopeMain implements ComponentFactory {
         versionGraph,
         Math.abs(stopOffset),
         stopOffset > 0 ? versionGraph.successors.bind(versionGraph) : versionGraph.predecessors.bind(versionGraph),
-        stopNode
+        stopNode,
+        undefined,
+        (edge) => edge.attr === 'parent',
+        skipNode
       );
     }
 
-    return this.legacyScope.loadComponentLogs(
-      id._legacy,
-      shortHash,
-      startNode?.id,
-      stopNode ? [stopNode.id] : undefined
-    );
+    return this.legacyScope
+      .loadComponentLogs(id._legacy, shortHash, startNode?.id, stopNode ? [stopNode.id] : undefined)
+      .then((logs) => {
+        if (type === 'snap') {
+          return logs.filter((log) => !log.tag);
+        }
+        if (type === 'tag') {
+          return logs.filter((log) => log.tag);
+        }
+        return logs;
+      });
   }
 
   async getStagedConfig() {
diff --git a/scopes/workspace/workspace/workspace.ts b/scopes/workspace/workspace/workspace.ts
index 553385e2b5a0..5f4ca3475dbb 100644
--- a/scopes/workspace/workspace/workspace.ts
+++ b/scopes/workspace/workspace/workspace.ts
@@ -433,9 +433,10 @@ export class Workspace implements ComponentFactory {
     startFrom?: string,
     stopAt?: string,
     startFromOffset?: number,
-    stopAtOffset?: number
+    stopAtOffset?: number,
+    type?: 'snap' | 'tag'
   ): Promise<ComponentLog[]> {
-    return this.scope.getLogs(id, shortHash, head, startFrom, stopAt, startFromOffset, stopAtOffset);
+    return this.scope.getLogs(id, shortHash, head, startFrom, stopAt, startFromOffset, stopAtOffset, type);
   }
 
   async getGraph(ids?: ComponentID[], shouldThrowOnMissingDep = true): Promise<Graph<Component, string>> {

From 9027cd6ed036f3db8a1f85853c574873e3647dcb Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Tue, 16 May 2023 11:17:57 -0400
Subject: [PATCH 16/20] fix lazy loading backwards

---
 .../component/ui/use-component-query.ts       |  20 +-
 .../version-dropdown.module.scss              |  21 +++
 .../ui/version-dropdown/version-dropdown.tsx  | 178 ++++++++++++------
 scopes/lanes/lanes/lanes.ui.runtime.tsx       |   1 -
 scopes/scope/scope/scope.main.runtime.ts      |   1 -
 5 files changed, 153 insertions(+), 68 deletions(-)

diff --git a/scopes/component/component/ui/use-component-query.ts b/scopes/component/component/ui/use-component-query.ts
index 945136ad5784..07aec5957045 100644
--- a/scopes/component/component/ui/use-component-query.ts
+++ b/scopes/component/component/ui/use-component-query.ts
@@ -237,9 +237,9 @@ export type ComponentQueryResult = {
     hasMoreLogs?: boolean;
     hasMoreSnaps?: boolean;
     hasMoreTags?: boolean;
-    loadMoreLogs?: (backwards?: boolean) => void;
-    loadMoreTags?: (backwards?: boolean) => void;
-    loadMoreSnaps?: (backwards?: boolean) => void;
+    loadMoreLogs?: (backwards?: boolean, offset?: number) => void;
+    loadMoreTags?: (backwards?: boolean, offset?: number) => void;
+    loadMoreSnaps?: (backwards?: boolean, offset?: number) => void;
     snaps?: LegacyComponentLog[];
     tags?: LegacyComponentLog[];
   };
@@ -283,6 +283,7 @@ function calculateNewOffset(initialOffset = 0, currentOffset = 0, logs: any[] =
  * @returns {boolean | undefined} - Whether there are more logs available.
  */
 function calculateHasMoreLogs(
+  // @todo account for limit (the API gives the nearest nodes to the limit, not the exact limit)
   logLimit?: number,
   rawComponent?: any,
   logType = 'logs',
@@ -290,7 +291,7 @@ function calculateHasMoreLogs(
 ): boolean | undefined {
   if (!logLimit) return false;
   if (rawComponent === undefined) return undefined;
-  if (currentHasMoreLogs === undefined) return rawComponent?.[logType]?.length >= logLimit;
+  if (currentHasMoreLogs === undefined) return !!rawComponent?.[logType]?.length;
   return currentHasMoreLogs;
 }
 /** provides data to component ui page, making sure both variables and return value are safely typed and memoized */
@@ -411,9 +412,10 @@ export function useComponentQuery(
             const fetchedComponent = fetchMoreResult.getHost.get;
             if (fetchedComponent && ComponentID.isEqualObj(prevComponent.id, fetchedComponent.id)) {
               const updatedLogs = mergeLogs(prevComponent.logs, fetchedComponent.logs);
-              hasMoreLogs.current = fetchedComponent.logs.length >= logLimit;
               if (updatedLogs.length > prevComponent.logs.length) {
                 offsetRef.current = fetchedComponent.logs.length + offset;
+                // @todo account for limit (the API gives the nearest nodes to the limit, not the exact limit)
+                hasMoreLogs.current = true;
               }
 
               return {
@@ -457,8 +459,10 @@ export function useComponentQuery(
               const updatedTags = mergeLogs(prevTags, fetchedTags);
               if (updatedTags.length > prevTags.length) {
                 tagOffsetRef.current = fetchedTags.length + offset;
+                // @todo account for limit (the API gives the nearest nodes to the limit, not the exact limit)
+                hasMoreTagLogs.current = true;
               }
-              hasMoreTagLogs.current = fetchedTags.length >= tagLogLimit;
+
               return {
                 ...prev,
                 getHost: {
@@ -500,8 +504,10 @@ export function useComponentQuery(
               const updatedSnaps = mergeLogs(prevSnaps, fetchedSnaps);
               if (updatedSnaps.length > prevSnaps.length) {
                 snapOffsetRef.current = fetchedSnaps.length + offset;
+                // @todo account for limit (the API gives the nearest nodes to the limit, not the exact limit)
+                hasMoreSnapLogs.current = true;
               }
-              hasMoreSnapLogs.current = fetchedSnaps.length >= snapLogLimit;
+
               return {
                 ...prev,
                 getHost: {
diff --git a/scopes/component/ui/version-dropdown/version-dropdown.module.scss b/scopes/component/ui/version-dropdown/version-dropdown.module.scss
index 1a89c23168a1..d8e431adac2c 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown.module.scss
+++ b/scopes/component/ui/version-dropdown/version-dropdown.module.scss
@@ -32,6 +32,7 @@
   max-height: 240px;
   overflow-y: scroll;
   padding-bottom: 8px;
+  position: relative;
 }
 
 .versionRow {
@@ -112,3 +113,23 @@
     padding: 8px 0px;
   }
 }
+
+.pullDownIndicator {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  height: 40px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: #f2f2f2;
+  font-size: 14px;
+  color: #666;
+  transform: translateY(-100%);
+  transition: transform 0.3s ease-in-out;
+  z-index: 1;
+  &.active {
+    transform: translateY(0);
+  }
+}
diff --git a/scopes/component/ui/version-dropdown/version-dropdown.tsx b/scopes/component/ui/version-dropdown/version-dropdown.tsx
index 1ce97e72aae0..f38a0872bc2e 100644
--- a/scopes/component/ui/version-dropdown/version-dropdown.tsx
+++ b/scopes/component/ui/version-dropdown/version-dropdown.tsx
@@ -17,23 +17,20 @@ export const LOCAL_VERSION = 'workspace';
 
 export type DropdownComponentVersion = Partial<LegacyComponentLog> & { version: string };
 
-const VersionMenu = React.memo(React.forwardRef<HTMLDivElement, VersionMenuProps>(_VersionMenu));
-
-function _VersionMenu(
-  {
-    currentVersion,
-    localVersion,
-    latestVersion,
-    overrideVersionHref,
-    showVersionDetails,
-    useVersions,
-    currentLane,
-    lanes,
-    loading: loadingFromProps,
-    ...rest
-  }: VersionMenuProps,
-  ref?: React.ForwardedRef<HTMLDivElement>
-) {
+const VersionMenu = React.memo(_VersionMenu);
+
+function _VersionMenu({
+  currentVersion,
+  localVersion,
+  latestVersion,
+  overrideVersionHref,
+  showVersionDetails,
+  useVersions,
+  currentLane,
+  lanes,
+  loading: loadingFromProps,
+  ...rest
+}: VersionMenuProps) {
   const {
     snaps,
     tags,
@@ -65,12 +62,52 @@ function _VersionMenu(
     return 0;
   };
 
-  const [activeTabIndex, setActiveTab] = React.useState<number>(getActiveTabIndex());
-
-  const activeTabOrSnap: 'SNAP' | 'TAG' | 'LANE' | undefined = tabs[activeTabIndex]?.name;
-  const hasMore = activeTabOrSnap === 'SNAP' ? !!hasMoreSnaps : activeTabOrSnap === 'TAG' && !!hasMoreTags;
+  const [activeTabIndex, setActiveTab] = React.useState<number | undefined>(undefined);
   const firstObserver = React.useRef<IntersectionObserver>();
   const lastObserver = React.useRef<IntersectionObserver>();
+  const currentVersionRef = React.useRef<HTMLDivElement | null>();
+
+  React.useEffect(() => {
+    if (activeTabIndex !== undefined) return;
+    if (!currentLane) return;
+    if (tabs.length === 0) return;
+    const _activeTabIndex = getActiveTabIndex();
+    if (_activeTabIndex !== activeTabIndex) setActiveTab(_activeTabIndex);
+  }, [currentLane, currentVersion, snaps?.length, tags?.length, tabs.length]);
+
+  const activeTabOrSnap: 'SNAP' | 'TAG' | 'LANE' | undefined = React.useMemo(
+    () => (activeTabIndex !== undefined ? tabs[activeTabIndex]?.name : undefined),
+    [activeTabIndex, tabs.length]
+  );
+
+  const hasMore = React.useMemo(
+    () => (activeTabOrSnap === 'SNAP' ? !!hasMoreSnaps : activeTabOrSnap === 'TAG' && !!hasMoreTags),
+    [hasMoreSnaps, activeTabOrSnap, hasMoreTags]
+  );
+
+  const [isIndicatorActive, setIsIndicatorActive] = React.useState(false);
+
+  const handleScroll = (event) => {
+    if (event.target.scrollTop === 0) {
+      setIsIndicatorActive(true);
+    } else {
+      setIsIndicatorActive(false);
+    }
+  };
+
+  React.useEffect(() => {
+    if (!currentVersion) return;
+    if (currentVersionRef.current) {
+      currentVersionRef.current.scrollIntoView({
+        behavior: 'smooth',
+        block: 'nearest',
+      });
+    }
+  }, [currentVersion]);
+
+  React.useEffect(() => {
+    setIsIndicatorActive(false);
+  }, [activeTabIndex]);
 
   const handleLoadMore = React.useCallback(
     (backwards?: boolean) => {
@@ -79,7 +116,6 @@ function _VersionMenu(
     },
     [activeTabOrSnap, tabs.length]
   );
-
   const lastLogRef = React.useCallback(
     (node) => {
       if (loading) return;
@@ -97,7 +133,7 @@ function _VersionMenu(
       );
       if (node) lastObserver.current.observe(node);
     },
-    [loading, hasMoreSnaps, hasMoreTags, handleLoadMore]
+    [loading, hasMore, handleLoadMore]
   );
 
   const firstLogRef = React.useCallback(
@@ -111,20 +147,35 @@ function _VersionMenu(
           }
         },
         {
-          threshold: 0.1,
-          rootMargin: '50px',
+          threshold: 1,
+          rootMargin: '0px',
         }
       );
       if (node) firstObserver.current.observe(node);
     },
-    [loading, hasMoreSnaps, hasMoreTags, handleLoadMore]
+    [loading, hasMore, handleLoadMore]
   );
 
+  const versionRef = (node, version, index, versions) => {
+    if (index === 0) {
+      firstLogRef(node);
+      return { current: firstObserver.current };
+    }
+    if (index === versions.length - 1) {
+      lastLogRef(node);
+      return { current: lastObserver.current };
+    }
+    if (version === currentVersion) return currentVersionRef;
+    return null;
+  };
+
   const multipleTabs = tabs.length > 1;
   const message = multipleTabs
     ? 'Switch to view tags, snaps, or lanes'
     : `Switch between ${tabs[0]?.name.toLocaleLowerCase()}s`;
 
+  const showTab = activeTabIndex !== undefined && tabs[activeTabIndex]?.payload.length > 0;
+
   return (
     <div {...rest}>
       <div className={styles.top}>
@@ -158,18 +209,31 @@ function _VersionMenu(
             );
           })}
       </div>
-      <div className={styles.versionContainer}>
-        {tabs[activeTabIndex]?.name === 'LANE' &&
+      <div className={styles.versionContainer} onScroll={handleScroll}>
+        {showTab && tabs[activeTabIndex]?.name !== 'LANE' && (
+          <div
+            className={classNames(styles.pullDownIndicator, isIndicatorActive && styles.active)}
+            ref={(node) =>
+              versionRef(node, (tabs[activeTabIndex]?.payload?.[0] as any).version, 0, tabs[activeTabIndex]?.payload)
+            }
+          >
+            Pull down to load more
+          </div>
+        )}
+        {showTab &&
+          tabs[activeTabIndex]?.name === 'LANE' &&
           tabs[activeTabIndex]?.payload.map((payload) => (
             <LaneInfo key={payload.id} currentLane={currentLane} {...payload}></LaneInfo>
           ))}
-        {tabs[activeTabIndex]?.name !== 'LANE' &&
-          tabs[activeTabIndex]?.payload.map((payload, index) => {
-            const _ref =
-              index === 0 ? firstLogRef : (index === tabs[activeTabIndex]?.payload.length - 1 && lastLogRef) || ref;
+        {showTab &&
+          tabs[activeTabIndex]?.name !== 'LANE' &&
+          tabs[activeTabIndex]?.payload.map((payload, index, versions) => {
+            // const _ref = (index === tabs[activeTabIndex]?.payload.length - 1 && lastLogRef) || ref;
             return (
               <VersionInfo
-                ref={_ref}
+                ref={(node) =>
+                  versionRef(node, index === 0 ? undefined : payload.version, index === 0 ? undefined : index, versions)
+                }
                 key={payload.version}
                 currentVersion={currentVersion}
                 latestVersion={latestVersion}
@@ -222,31 +286,28 @@ export type VersionDropdownProps = {
   placeholderComponent?: ReactNode;
 } & React.HTMLAttributes<HTMLDivElement>;
 
-export const VersionDropdown = React.memo(React.forwardRef<HTMLDivElement, VersionDropdownProps>(_VersionDropdown));
-
-function _VersionDropdown(
-  {
-    currentVersion,
-    latestVersion,
-    localVersion,
-    currentVersionLog = {},
-    hasMoreVersions,
-    loading,
-    overrideVersionHref,
-    className,
-    placeholderClassName,
-    dropdownClassName,
-    menuClassName,
-    showVersionDetails,
-    disabled,
-    placeholderComponent,
-    currentLane,
-    useComponentVersions,
-    lanes,
-    ...rest
-  }: VersionDropdownProps,
-  ref?: React.ForwardedRef<HTMLDivElement>
-) {
+export const VersionDropdown = React.memo(_VersionDropdown);
+
+function _VersionDropdown({
+  currentVersion,
+  latestVersion,
+  localVersion,
+  currentVersionLog = {},
+  hasMoreVersions,
+  loading,
+  overrideVersionHref,
+  className,
+  placeholderClassName,
+  dropdownClassName,
+  menuClassName,
+  showVersionDetails,
+  disabled,
+  placeholderComponent,
+  currentLane,
+  useComponentVersions,
+  lanes,
+  ...rest
+}: VersionDropdownProps) {
   const [key, setKey] = useState(0);
   const singleVersion = !hasMoreVersions;
   const [open, setOpen] = useState(false);
@@ -303,7 +364,6 @@ function _VersionDropdown(
       >
         {open && (
           <VersionMenu
-            ref={ref}
             className={menuClassName}
             key={key}
             currentVersion={currentVersion}
diff --git a/scopes/lanes/lanes/lanes.ui.runtime.tsx b/scopes/lanes/lanes/lanes.ui.runtime.tsx
index 93402a945c31..8fa270298fdb 100644
--- a/scopes/lanes/lanes/lanes.ui.runtime.tsx
+++ b/scopes/lanes/lanes/lanes.ui.runtime.tsx
@@ -62,7 +62,6 @@ export function useLaneComponentIdFromUrl(): ComponentID | undefined | null {
 
   if (componentVersion && laneFromUrl) {
     const componentId = ComponentID.fromString(`${idFromLocation}@${componentVersion}`);
-    console.log('🚀 ~ file: lanes.ui.runtime.tsx:66 ~ useLaneComponentIdFromUrl ~ componentId:', componentId);
     return componentId;
   }
   const laneComponentId =
diff --git a/scopes/scope/scope/scope.main.runtime.ts b/scopes/scope/scope/scope.main.runtime.ts
index 2efe6ced8a5a..ed48e65e6a16 100644
--- a/scopes/scope/scope/scope.main.runtime.ts
+++ b/scopes/scope/scope/scope.main.runtime.ts
@@ -51,7 +51,6 @@ import ConsumerComponent from '@teambit/legacy/dist/consumer/component';
 import { resumeExport } from '@teambit/legacy/dist/scope/component-ops/export-scope-components';
 import { ExtensionDataEntry, ExtensionDataList } from '@teambit/legacy/dist/consumer/config';
 import EnvsAspect, { EnvsMain } from '@teambit/envs';
-import { isSnap, isTag } from '@teambit/component-version';
 import { compact, slice, difference } from 'lodash';
 import { ComponentNotFound } from './exceptions';
 import { ScopeAspect } from './scope.aspect';

From 3516bf59f9fd341c92c21714ef06d5b3a6d4c1f7 Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Tue, 16 May 2023 18:46:09 -0400
Subject: [PATCH 17/20] clean up

---
 .../changelog/ui/change-log-page.tsx          |   6 +-
 .../component/component/get-component-opts.ts |   3 +-
 scopes/component/component/index.ts           |   2 +-
 scopes/component/component/ui/component.tsx   |   9 +-
 scopes/component/component/ui/index.ts        |  11 +-
 scopes/component/component/ui/menu/menu.tsx   |   6 +-
 .../ui/use-component-from-location.tsx        |  22 -
 .../component/ui/use-component-query.ts       | 729 ------------------
 .../component/component/ui/use-component.tsx  |  28 -
 .../use-component/use-component.fragments.ts  |   8 +-
 .../ui/use-component/use-component.model.ts   |   3 +-
 11 files changed, 25 insertions(+), 802 deletions(-)
 delete mode 100644 scopes/component/component/ui/use-component-from-location.tsx
 delete mode 100644 scopes/component/component/ui/use-component-query.ts
 delete mode 100644 scopes/component/component/ui/use-component.tsx

diff --git a/scopes/component/changelog/ui/change-log-page.tsx b/scopes/component/changelog/ui/change-log-page.tsx
index a63ab88788ee..68c904155842 100644
--- a/scopes/component/changelog/ui/change-log-page.tsx
+++ b/scopes/component/changelog/ui/change-log-page.tsx
@@ -28,8 +28,8 @@ export function ChangeLogPage({ className, host }: ChangeLogPageProps) {
   const logs = component?.logs ?? [];
 
   const observer = React.useRef<IntersectionObserver>();
-  const handleLoadMore = () => {
-    loadMoreLogs?.();
+  const handleLoadMore = async () => {
+    await loadMoreLogs?.();
   };
 
   const lastLogRef = React.useCallback(
@@ -38,7 +38,7 @@ export function ChangeLogPage({ className, host }: ChangeLogPageProps) {
       if (observer.current) observer.current.disconnect();
       observer.current = new IntersectionObserver((entries) => {
         if (entries[0].isIntersecting && hasMore) {
-          handleLoadMore();
+          handleLoadMore().catch(() => {});
         }
       });
       if (node) observer.current.observe(node);
diff --git a/scopes/component/component/get-component-opts.ts b/scopes/component/component/get-component-opts.ts
index 995c910e796c..ffbc6bcb1918 100644
--- a/scopes/component/component/get-component-opts.ts
+++ b/scopes/component/component/get-component-opts.ts
@@ -1,7 +1,6 @@
 import React from 'react';
 import { RouteProps } from 'react-router-dom';
-import type { UseComponentType } from './ui/use-component';
-import { Filters } from './ui/use-component-query';
+import type { Filters, UseComponentType } from './ui/use-component';
 
 export type GetComponentsOptions = {
   useComponent?: UseComponentType;
diff --git a/scopes/component/component/index.ts b/scopes/component/component/index.ts
index 9aebd0b6ddb7..e7a01c62cab0 100644
--- a/scopes/component/component/index.ts
+++ b/scopes/component/component/index.ts
@@ -33,7 +33,7 @@ export { Section } from './section';
 export { ComponentContext, ComponentDescriptorContext, useComponentDescriptor } from './ui/context/component-context';
 export type { ComponentProviderProps, ComponentDescriptorProviderProps } from './ui/context';
 export { ComponentProvider, ComponentDescriptorProvider } from './ui/context';
-export { componentFields, componentIdFields, componentOverviewFields, COMPONENT_QUERY_FIELDS } from './ui';
+export { componentFields, componentIdFields, componentOverviewFields } from './ui';
 export {
   NavPlugin,
   ConsumePlugin,
diff --git a/scopes/component/component/ui/component.tsx b/scopes/component/component/ui/component.tsx
index 47c83781d88c..642515d35737 100644
--- a/scopes/component/component/ui/component.tsx
+++ b/scopes/component/component/ui/component.tsx
@@ -4,13 +4,12 @@ import flatten from 'lodash.flatten';
 import { RouteSlot, SlotRouter } from '@teambit/ui-foundation.ui.react-router.slot-router';
 import { SlotRegistry } from '@teambit/harmony';
 import { isFunction } from 'lodash';
-import styles from './component.module.scss';
+import { ComponentID } from '@teambit/component-id';
 import { ComponentProvider, ComponentDescriptorProvider } from './context';
-import { useComponent as useComponentQuery, UseComponentType } from './use-component';
+import { Filters, useComponent as useComponentQuery, UseComponentType, useIdFromLocation } from './use-component';
 import { ComponentModel } from './component-model';
-import { useIdFromLocation } from './use-component-from-location';
-import { ComponentID } from '..';
-import { Filters } from './use-component-query';
+
+import styles from './component.module.scss';
 
 export type ComponentPageSlot = SlotRegistry<ComponentPageElement[]>;
 export type ComponentPageElement = {
diff --git a/scopes/component/component/ui/index.ts b/scopes/component/component/ui/index.ts
index c64fa8d42593..53573ae7cf2a 100644
--- a/scopes/component/component/ui/index.ts
+++ b/scopes/component/component/ui/index.ts
@@ -3,13 +3,14 @@ export { Component } from './component';
 export { ConsumeMethodSlot, ComponentMenu, VersionRelatedDropdowns } from './menu';
 export { ComponentModel, ComponentModelProps } from './component-model';
 export { ComponentContext, ComponentProvider } from './context';
-export { useComponent } from './use-component';
 export { TopBarNav } from './top-bar-nav';
 export {
   componentIdFields,
   componentOverviewFields,
   componentFields,
-  COMPONENT_QUERY_FIELDS,
-} from './use-component-query';
-export type { ComponentQueryResult } from './use-component-query';
-export { useIdFromLocation } from './use-component-from-location';
+  useIdFromLocation,
+  useComponent,
+  useComponentLogs,
+  useComponentQuery,
+} from './use-component';
+export type { ComponentQueryResult } from './use-component';
diff --git a/scopes/component/component/ui/menu/menu.tsx b/scopes/component/component/ui/menu/menu.tsx
index 8056b027b1fb..cb9241a391d8 100644
--- a/scopes/component/component/ui/menu/menu.tsx
+++ b/scopes/component/component/ui/menu/menu.tsx
@@ -11,15 +11,13 @@ import { useLanes } from '@teambit/lanes.hooks.use-lanes';
 import { LaneModel } from '@teambit/lanes.ui.models.lanes-model';
 import { Menu as ConsumeMethodsMenu } from '@teambit/ui-foundation.ui.use-box.menu';
 import { useQuery } from '@teambit/ui-foundation.ui.react-router.use-query';
+import { ComponentID } from '@teambit/component-id';
 import * as semver from 'semver';
 import type { ComponentModel } from '../component-model';
-import { useComponent as useComponentQuery, UseComponentType } from '../use-component';
+import { Filters, useComponent as useComponentQuery, UseComponentType, useIdFromLocation } from '../use-component';
 import { CollapsibleMenuNav } from './menu-nav';
 import styles from './menu.module.scss';
 import { OrderedNavigationSlot, ConsumeMethodSlot } from './nav-plugin';
-import { useIdFromLocation } from '../use-component-from-location';
-import { ComponentID } from '../..';
-import { Filters } from '../use-component-query';
 
 export type MenuProps = {
   className?: string;
diff --git a/scopes/component/component/ui/use-component-from-location.tsx b/scopes/component/component/ui/use-component-from-location.tsx
deleted file mode 100644
index f385489e3037..000000000000
--- a/scopes/component/component/ui/use-component-from-location.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useParams } from 'react-router-dom';
-
-/** component url is comprised of letters, numbers, "_", "-", "/" but should not include trailing "/", and should not include "~" */
-const componentRegex = /^[\w/-]*[\w-]/;
-
-export function useIdFromLocation(url?: string): string | undefined {
-  const params = useParams();
-  const splat = url || params['*'];
-  if (!splat) return undefined;
-
-  const [maybeOrgWithScope, ...maybeFullName] = splat.split('/');
-  const hasScope = maybeOrgWithScope.split('.').length > 1;
-  const fullNameFromUrl = hasScope ? maybeFullName.join('/') : splat;
-  let scope: string | undefined;
-  if (hasScope) {
-    scope = maybeOrgWithScope;
-  }
-  const match = componentRegex.exec(fullNameFromUrl);
-  if (!match?.[0]) return undefined;
-  if (scope) return `${scope}/${match[0]}`;
-  return match[0];
-}
diff --git a/scopes/component/component/ui/use-component-query.ts b/scopes/component/component/ui/use-component-query.ts
deleted file mode 100644
index 07aec5957045..000000000000
--- a/scopes/component/component/ui/use-component-query.ts
+++ /dev/null
@@ -1,729 +0,0 @@
-import React, { useMemo, useEffect, useRef } from 'react';
-import { gql } from '@apollo/client';
-import { useDataQuery } from '@teambit/ui-foundation.ui.hooks.use-data-query';
-import { ComponentID, ComponentIdObj } from '@teambit/component-id';
-import { ComponentDescriptor } from '@teambit/component-descriptor';
-import { LegacyComponentLog } from '@teambit/legacy-component-log';
-import { ComponentModel } from './component-model';
-import { ComponentError } from './component-error';
-
-export const componentIdFields = gql`
-  fragment componentIdFields on ComponentID {
-    name
-    version
-    scope
-  }
-`;
-
-export const componentOverviewFields = gql`
-  fragment componentOverviewFields on Component {
-    id {
-      ...componentIdFields
-    }
-    aspects(include: ["teambit.preview/preview", "teambit.envs/envs"]) {
-      # 'id' property in gql refers to a *global* identifier and used for caching.
-      # this makes aspect data cache under the same key, even when they are under different components.
-      # renaming the property fixes that.
-      id
-      data
-    }
-    elementsUrl
-    description
-    deprecation {
-      isDeprecate
-      newId
-    }
-    labels
-    displayName
-    server {
-      env
-      url
-      host
-      basePath
-    }
-    buildStatus
-    env {
-      id
-      icon
-    }
-    size {
-      compressedTotal
-    }
-    preview {
-      includesEnvTemplate
-      legacyHeader
-      isScaling
-      skipIncludes
-    }
-    compositions {
-      identifier
-      displayName
-    }
-  }
-  ${componentIdFields}
-`;
-
-export const componentFields = gql`
-  fragment componentFields on Component {
-    id {
-      ...componentIdFields
-    }
-    ...componentOverviewFields
-    packageName
-    latest
-    compositions {
-      identifier
-      displayName
-    }
-    tags {
-      version
-    }
-    logs(
-      type: $logType
-      offset: $logOffset
-      limit: $logLimit
-      sort: $logSort
-      takeHeadFromComponent: $takeHeadFromComponent
-      head: $logHead
-      startFrom: $logStartFrom
-      until: $logUntil
-    ) @skip(if: $fetchLogsByTypeSeparately) {
-      id
-      message
-      username
-      email
-      date
-      hash
-      tag
-    }
-    tagLogs: logs(
-      type: "tag"
-      offset: $tagLogOffset
-      limit: $tagLogLimit
-      sort: $tagLogSort
-      takeHeadFromComponent: $tagTakeHeadFromComponent
-      head: $tagLogHead
-      startFrom: $tagStartFrom
-      until: $tagUntil
-    ) @include(if: $fetchLogsByTypeSeparately) {
-      id
-      message
-      username
-      email
-      date
-      hash
-      tag
-    }
-    snapLogs: logs(
-      type: "snap"
-      offset: $snapLogOffset
-      limit: $snapLogLimit
-      sort: $snapLogSort
-      takeHeadFromComponent: $snapTakeHeadFromComponent
-      head: $snapLogHead
-      startFrom: $snapStartFrom
-      until: $snapUntil
-    ) @include(if: $fetchLogsByTypeSeparately) {
-      id
-      message
-      username
-      email
-      date
-      hash
-      tag
-    }
-  }
-  ${componentIdFields}
-  ${componentOverviewFields}
-`;
-
-export const COMPONENT_QUERY_FIELDS = `
-    $logOffset: Int
-    $logLimit: Int
-    $logType: String
-    $logHead: String
-    $logSort: String
-    $logStartFrom: String
-    $logUntil: String
-    $tagLogOffset: Int
-    $tagLogLimit: Int
-    $tagLogHead: String
-    $tagLogSort: String
-    $tagStartFrom: String
-    $tagUntil: String
-    $snapLogOffset: Int
-    $snapLogLimit: Int
-    $snapLogHead: String
-    $snapLogSort: String
-    $snapStartFrom: String
-    $snapUntil: String
-    $takeHeadFromComponent: Boolean
-    $tagTakeHeadFromComponent: Boolean
-    $snapTakeHeadFromComponent: Boolean
-    $fetchLogsByTypeSeparately: Boolean!`;
-
-const GET_COMPONENT = gql`
-  query Component(
-    ${COMPONENT_QUERY_FIELDS} 
-    $extensionId: String!
-    $id: String!
-    ) {
-    getHost(id: $extensionId) {
-      id # used for GQL caching
-      get(id: $id) {
-        ...componentFields
-      }
-    }
-  }
-  ${componentFields}
-`;
-
-const SUB_SUBSCRIPTION_ADDED = gql`
-  subscription OnComponentAdded(${COMPONENT_QUERY_FIELDS}) {
-    componentAdded {
-      component {
-        ...componentFields
-      }
-    }
-  }
-  ${componentFields}
-`;
-
-const SUB_COMPONENT_CHANGED = gql`
-  subscription OnComponentChanged(${COMPONENT_QUERY_FIELDS}) {
-    componentChanged {
-      component {
-        ...componentFields
-      }
-    }
-  }
-  ${componentFields}
-`;
-
-const SUB_COMPONENT_REMOVED = gql`
-  subscription OnComponentRemoved {
-    componentRemoved {
-      componentIds {
-        ...componentIdFields
-      }
-    }
-  }
-  ${componentIdFields}
-`;
-
-export type LogFilter = {
-  logOffset?: number;
-  logLimit?: number;
-  logHead?: string;
-  logStartFrom?: string;
-  logUntil?: string;
-  logSort?: string;
-  takeHeadFromComponent?: boolean;
-};
-
-export type Filters = {
-  log?: LogFilter & { logType?: string };
-  tagLog?: LogFilter;
-  snapLog?: LogFilter;
-  fetchLogsByTypeSeparately?: boolean;
-  loading?: boolean;
-};
-
-export type ComponentQueryResult = {
-  component?: ComponentModel;
-  componentDescriptor?: ComponentDescriptor;
-  // @todo refactor to useComponentLogs
-  componentLogs?: {
-    hasMoreLogs?: boolean;
-    hasMoreSnaps?: boolean;
-    hasMoreTags?: boolean;
-    loadMoreLogs?: (backwards?: boolean, offset?: number) => void;
-    loadMoreTags?: (backwards?: boolean, offset?: number) => void;
-    loadMoreSnaps?: (backwards?: boolean, offset?: number) => void;
-    snaps?: LegacyComponentLog[];
-    tags?: LegacyComponentLog[];
-  };
-  loading?: boolean;
-  error?: ComponentError;
-};
-function getOffsetValue(offset, limit, backwards = false) {
-  if (offset !== undefined) {
-    return backwards ? -offset : offset;
-  }
-  if (limit !== undefined) {
-    return 0;
-  }
-  return undefined;
-}
-/**
- * Calculates the new offset based on initial offset, current offset, and the number of logs.
- *
- * @param {boolean} [fetchLogsByTypeSeparately] A flag to determine if logs are fetched by type separately.
- * @param {number} [initialOffset] The initial offset.
- * @param {number} [currentOffset] The current offset.
- * @param {any[]} [logs=[]] The array of logs.
- *
- * @returns {number | undefined} -  new offset
- */
-function calculateNewOffset(initialOffset = 0, currentOffset = 0, logs: any[] = []): number | undefined {
-  const logCount = logs.length;
-
-  if (initialOffset !== currentOffset && logCount + initialOffset >= currentOffset) return currentOffset;
-  return logCount + initialOffset;
-}
-
-/**
- * Calculate the availability of more logs.
- *
- * @param {number | undefined} logLimit - The limit for the logs.
- * @param {any} rawComponent - The raw component object containing logs.
- * @param {string} logType - Type of log ('logs', 'tagLogs', 'snapLogs').
- * @param {boolean | undefined} currentHasMoreLogs - Current state of having more logs.
- *
- * @returns {boolean | undefined} - Whether there are more logs available.
- */
-function calculateHasMoreLogs(
-  // @todo account for limit (the API gives the nearest nodes to the limit, not the exact limit)
-  logLimit?: number,
-  rawComponent?: any,
-  logType = 'logs',
-  currentHasMoreLogs?: boolean
-): boolean | undefined {
-  if (!logLimit) return false;
-  if (rawComponent === undefined) return undefined;
-  if (currentHasMoreLogs === undefined) return !!rawComponent?.[logType]?.length;
-  return currentHasMoreLogs;
-}
-/** provides data to component ui page, making sure both variables and return value are safely typed and memoized */
-export function useComponentQuery(
-  componentId: string,
-  host: string,
-  filters?: Filters,
-  skip?: boolean
-): ComponentQueryResult {
-  const idRef = useRef(componentId);
-  idRef.current = componentId;
-  const { fetchLogsByTypeSeparately = false, log, tagLog, snapLog } = filters || {};
-  const {
-    logHead: tagLogHead,
-    logOffset: tagLogOffset,
-    logSort: tagLogSort,
-    logLimit: tagLogLimit,
-    takeHeadFromComponent: tagLogTakeHeadFromComponent,
-    logStartFrom: tagStartFrom,
-    logUntil: tagUntil,
-  } = tagLog || {};
-  const { logHead, logOffset, logSort, logLimit, takeHeadFromComponent, logType, logStartFrom, logUntil } = log || {};
-  const {
-    logHead: snapLogHead,
-    logOffset: snapLogOffset,
-    logSort: snapLogSort,
-    logLimit: snapLogLimit,
-    takeHeadFromComponent: snapLogTakeHeadFromComponent,
-    logStartFrom: snapStartFrom,
-    logUntil: snapUntil,
-  } = snapLog || {};
-  const variables = {
-    id: componentId,
-    extensionId: host,
-    fetchLogsByTypeSeparately,
-    snapLogOffset: getOffsetValue(snapLogOffset, snapLogLimit),
-    tagLogOffset: getOffsetValue(tagLogOffset, tagLogLimit),
-    logOffset: getOffsetValue(logOffset, logLimit),
-    logLimit,
-    snapLogLimit,
-    tagLogLimit,
-    logType,
-    logHead,
-    snapLogHead,
-    tagLogHead,
-    logStartFrom,
-    snapStartFrom,
-    tagStartFrom,
-    logUntil,
-    snapUntil,
-    tagUntil,
-    logSort,
-    snapLogSort,
-    tagLogSort,
-    takeHeadFromComponent,
-    snapLogTakeHeadFromComponent,
-    tagLogTakeHeadFromComponent,
-  };
-  const offsetRef = useRef(logOffset);
-  const tagOffsetRef = useRef(tagLogOffset);
-  const snapOffsetRef = useRef(snapLogOffset);
-  const hasMoreLogs = useRef<boolean | undefined>(undefined);
-  const hasMoreTagLogs = useRef<boolean | undefined>(undefined);
-  const hasMoreSnapLogs = useRef<boolean | undefined>(undefined);
-  const { data, error, loading, subscribeToMore, fetchMore, ...rest } = useDataQuery(GET_COMPONENT, {
-    variables,
-    skip,
-    errorPolicy: 'all',
-  });
-
-  const rawComponent = data?.getHost?.get;
-  const rawTags: Array<any> = rawComponent?.tagLogs ?? [];
-  const rawSnaps: Array<any> = rawComponent?.snapLogs ?? [];
-  const rawCompLogs: Array<any> = rawComponent?.logs ?? mergeLogs(rawTags, rawSnaps);
-  offsetRef.current = useMemo(
-    () => calculateNewOffset(logOffset, offsetRef.current, rawCompLogs),
-    [rawCompLogs, fetchLogsByTypeSeparately, logOffset]
-  );
-
-  tagOffsetRef.current = useMemo(
-    () => calculateNewOffset(tagLogOffset, tagOffsetRef.current, rawTags),
-    [rawTags, fetchLogsByTypeSeparately, tagLogOffset]
-  );
-
-  snapOffsetRef.current = useMemo(
-    () => calculateNewOffset(snapLogOffset, snapOffsetRef.current, rawSnaps),
-    [rawSnaps, fetchLogsByTypeSeparately, snapLogOffset]
-  );
-
-  hasMoreLogs.current = useMemo(
-    () => calculateHasMoreLogs(logLimit, rawComponent, 'logs', hasMoreLogs.current),
-    [rawCompLogs]
-  );
-
-  hasMoreTagLogs.current = useMemo(
-    () => calculateHasMoreLogs(tagLogLimit, rawComponent, 'tagLogs', hasMoreTagLogs.current),
-    [rawTags]
-  );
-
-  hasMoreSnapLogs.current = useMemo(
-    () => calculateHasMoreLogs(snapLogLimit, rawComponent, 'snapLogs', hasMoreSnapLogs.current),
-    [rawSnaps]
-  );
-
-  const loadMoreLogs = React.useCallback(
-    async (backwards = false) => {
-      const offset = getOffsetValue(offsetRef.current, logLimit, backwards);
-
-      if (logLimit) {
-        await fetchMore({
-          variables: {
-            logOffset: offset,
-          },
-          updateQuery: (prev, { fetchMoreResult }) => {
-            if (!fetchMoreResult) return prev;
-
-            const prevComponent = prev.getHost.get;
-            const fetchedComponent = fetchMoreResult.getHost.get;
-            if (fetchedComponent && ComponentID.isEqualObj(prevComponent.id, fetchedComponent.id)) {
-              const updatedLogs = mergeLogs(prevComponent.logs, fetchedComponent.logs);
-              if (updatedLogs.length > prevComponent.logs.length) {
-                offsetRef.current = fetchedComponent.logs.length + offset;
-                // @todo account for limit (the API gives the nearest nodes to the limit, not the exact limit)
-                hasMoreLogs.current = true;
-              }
-
-              return {
-                ...prev,
-                getHost: {
-                  ...prev.getHost,
-                  get: {
-                    ...prevComponent,
-                    logs: updatedLogs,
-                  },
-                },
-              };
-            }
-
-            return prev;
-          },
-        });
-      }
-    },
-    [logLimit, fetchMore]
-  );
-
-  const loadMoreTags = React.useCallback(
-    async (backwards = false) => {
-      const offset = getOffsetValue(tagOffsetRef.current, tagLogLimit, backwards);
-
-      if (tagLogLimit) {
-        await fetchMore({
-          variables: {
-            tagLogOffset: offset,
-            tagLogLimit,
-          },
-          updateQuery: (prev, { fetchMoreResult }) => {
-            if (!fetchMoreResult) return prev;
-
-            const prevComponent = prev.getHost.get;
-            const fetchedComponent = fetchMoreResult.getHost.get;
-            const prevTags = prevComponent.tagLogs;
-            const fetchedTags = fetchedComponent.tagLogs ?? [];
-            if (fetchedComponent && ComponentID.isEqualObj(prevComponent.id, fetchedComponent.id)) {
-              const updatedTags = mergeLogs(prevTags, fetchedTags);
-              if (updatedTags.length > prevTags.length) {
-                tagOffsetRef.current = fetchedTags.length + offset;
-                // @todo account for limit (the API gives the nearest nodes to the limit, not the exact limit)
-                hasMoreTagLogs.current = true;
-              }
-
-              return {
-                ...prev,
-                getHost: {
-                  ...prev.getHost,
-                  get: {
-                    ...prevComponent,
-                    tagLogs: updatedTags,
-                  },
-                },
-              };
-            }
-
-            return prev;
-          },
-        });
-      }
-    },
-    [tagLogLimit, fetchMore]
-  );
-
-  const loadMoreSnaps = React.useCallback(
-    async (backwards = false) => {
-      const offset = getOffsetValue(snapOffsetRef.current, snapLogLimit, backwards);
-
-      if (snapLogLimit) {
-        await fetchMore({
-          variables: {
-            snapLogOffset: offset,
-            snapLogLimit,
-          },
-          updateQuery: (prev, { fetchMoreResult }) => {
-            if (!fetchMoreResult) return prev;
-
-            const prevComponent = prev.getHost.get;
-            const prevSnaps = prevComponent.snapLogs ?? [];
-            const fetchedComponent = fetchMoreResult.getHost.get;
-            const fetchedSnaps = fetchedComponent.snapLogs ?? [];
-            if (fetchedComponent && ComponentID.isEqualObj(prevComponent.id, fetchedComponent.id)) {
-              const updatedSnaps = mergeLogs(prevSnaps, fetchedSnaps);
-              if (updatedSnaps.length > prevSnaps.length) {
-                snapOffsetRef.current = fetchedSnaps.length + offset;
-                // @todo account for limit (the API gives the nearest nodes to the limit, not the exact limit)
-                hasMoreSnapLogs.current = true;
-              }
-
-              return {
-                ...prev,
-                getHost: {
-                  ...prev.getHost,
-                  get: {
-                    ...prevComponent,
-                    snapLogs: updatedSnaps,
-                  },
-                },
-              };
-            }
-
-            return prev;
-          },
-        });
-      }
-    },
-    [snapLogLimit, fetchMore]
-  );
-
-  useEffect(() => {
-    // @TODO @Kutner fix subscription for scope
-    if (host !== 'teambit.workspace/workspace') {
-      return () => {};
-    }
-
-    const unsubAddition = subscribeToMore({
-      document: SUB_SUBSCRIPTION_ADDED,
-      variables: {
-        fetchLogsByTypeSeparately,
-      },
-      updateQuery: (prev, { subscriptionData }) => {
-        const prevComponent = prev?.getHost?.get;
-        const addedComponent = subscriptionData?.data?.componentAdded?.component;
-
-        if (!addedComponent || prevComponent) return prev;
-
-        if (idRef.current === addedComponent.id.name) {
-          return {
-            ...prev,
-            getHost: {
-              ...prev.getHost,
-              get: addedComponent,
-            },
-          };
-        }
-
-        return prev;
-      },
-    });
-
-    const unsubChanges = subscribeToMore({
-      document: SUB_COMPONENT_CHANGED,
-      variables: {
-        fetchLogsByTypeSeparately,
-      },
-      updateQuery: (prev, { subscriptionData }) => {
-        if (!subscriptionData.data) return prev;
-
-        const prevComponent = prev?.getHost?.get;
-        const updatedComponent = subscriptionData?.data?.componentChanged?.component;
-
-        const isUpdated = updatedComponent && ComponentID.isEqualObj(prevComponent?.id, updatedComponent?.id);
-
-        if (isUpdated) {
-          return {
-            ...prev,
-            getHost: {
-              ...prev.getHost,
-              get: updatedComponent,
-            },
-          };
-        }
-
-        return prev;
-      },
-    });
-
-    const unsubRemoval = subscribeToMore({
-      document: SUB_COMPONENT_REMOVED,
-      variables: {
-        fetchLogsByTypeSeparately,
-      },
-      updateQuery: (prev, { subscriptionData }) => {
-        if (!subscriptionData.data) return prev;
-
-        const prevComponent = prev?.getHost?.get;
-        const removedIds: ComponentIdObj[] | undefined = subscriptionData?.data?.componentRemoved?.componentIds;
-        if (!prevComponent || !removedIds?.length) return prev;
-
-        const isRemoved = removedIds.some((removedId) => ComponentID.isEqualObj(removedId, prevComponent.id));
-
-        if (isRemoved) {
-          return {
-            ...prev,
-            getHost: {
-              ...prev.getHost,
-              get: null,
-            },
-          };
-        }
-
-        return prev;
-      },
-    });
-
-    return () => {
-      unsubChanges();
-      unsubAddition();
-      unsubRemoval();
-      offsetRef.current = undefined;
-    };
-  }, []);
-
-  const idDepKey = rawComponent?.id
-    ? `${rawComponent?.id?.scope}/${rawComponent?.id?.name}@${rawComponent?.id?.version}}`
-    : undefined;
-
-  const id: ComponentID | undefined = useMemo(
-    () => (rawComponent ? ComponentID.fromObject(rawComponent.id) : undefined),
-    [idDepKey]
-  );
-
-  const componentError =
-    error && !data ? new ComponentError(500, error.message) : !rawComponent && !loading && new ComponentError(404);
-
-  const component = useMemo(
-    () => (rawComponent ? ComponentModel.from({ ...rawComponent, host, logs: rawCompLogs }) : undefined),
-    [id?.toString(), rawCompLogs]
-  );
-
-  const componentDescriptor = useMemo(() => {
-    const aspectList = {
-      entries: rawComponent?.aspects.map((aspectObject) => {
-        return {
-          ...aspectObject,
-          aspectId: aspectObject.id,
-          aspectData: aspectObject.data,
-        };
-      }),
-    };
-
-    return id ? ComponentDescriptor.fromObject({ id: id.toString(), aspectList }) : undefined;
-  }, [id?.toString()]);
-
-  const snaps = useMemo(() => {
-    return rawComponent?.snapLogs;
-  }, [rawComponent?.snapLogs]);
-
-  const tags = useMemo(() => {
-    return rawComponent?.tagLogs;
-  }, [rawComponent?.tagLogs]);
-
-  return useMemo(() => {
-    return {
-      componentDescriptor,
-      component,
-      componentLogs: {
-        loadMoreLogs,
-        loadMoreSnaps,
-        loadMoreTags,
-        hasMoreSnaps: hasMoreSnapLogs.current,
-        hasMoreTags: hasMoreTagLogs.current,
-        hasMoreLogs: hasMoreLogs.current,
-        snaps,
-        tags,
-      },
-      error: componentError || undefined,
-      loading,
-      ...rest,
-    };
-  }, [host, component, componentDescriptor, componentError, hasMoreLogs, hasMoreSnapLogs, hasMoreTagLogs, snaps, tags]);
-}
-
-interface Log {
-  id: string;
-  date: string;
-}
-function mergeLogs(logs1: Log[] = [], logs2: Log[] = []): Log[] {
-  const logMap = new Map<string, Log>();
-  const result: Log[] = [];
-
-  let index1 = 0;
-  let index2 = 0;
-
-  while (index1 < logs1.length && index2 < logs2.length) {
-    if (Number(logs1[index1].date) >= Number(logs2[index2].date)) {
-      if (!logMap.has(logs1[index1].id)) {
-        logMap.set(logs1[index1].id, logs1[index1]);
-        result.push(logs1[index1]);
-      }
-      index1 += 1;
-    } else {
-      if (!logMap.has(logs2[index2].id)) {
-        logMap.set(logs2[index2].id, logs2[index2]);
-        result.push(logs2[index2]);
-      }
-      index2 += 1;
-    }
-  }
-
-  while (index1 < logs1.length) {
-    if (!logMap.has(logs1[index1].id)) {
-      logMap.set(logs1[index1].id, logs1[index1]);
-      result.push(logs1[index1]);
-    }
-    index1 += 1;
-  }
-
-  while (index2 < logs2.length) {
-    if (!logMap.has(logs2[index2].id)) {
-      logMap.set(logs2[index2].id, logs2[index2]);
-      result.push(logs2[index2]);
-    }
-    index2 += 1;
-  }
-
-  return result;
-}
diff --git a/scopes/component/component/ui/use-component.tsx b/scopes/component/component/ui/use-component.tsx
deleted file mode 100644
index cebbe47852fb..000000000000
--- a/scopes/component/component/ui/use-component.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { useQuery } from '@teambit/ui-foundation.ui.react-router.use-query';
-import { ComponentQueryResult, Filters, useComponentQuery } from './use-component-query';
-
-export type UseComponentOptions = {
-  version?: string;
-  logFilters?: Filters;
-  customUseComponent?: UseComponentType;
-  skip?: boolean;
-};
-
-export type UseComponentType = (id: string, host: string, filters?: Filters, skip?: boolean) => ComponentQueryResult;
-
-export function useComponent(host: string, id?: string, options?: UseComponentOptions): ComponentQueryResult {
-  const query = useQuery();
-  const { version, logFilters, customUseComponent, skip } = options || {};
-  const componentVersion = (version || query.get('version')) ?? undefined;
-
-  const componentIdStr = id && withVersion(id, componentVersion);
-  const targetUseComponent = customUseComponent || useComponentQuery;
-
-  return targetUseComponent(componentIdStr || '', host, logFilters, skip || !id);
-}
-
-function withVersion(id: string, version?: string) {
-  if (!version) return id;
-  if (id.includes('@')) return id;
-  return `${id}@${version}`;
-}
diff --git a/scopes/component/component/ui/use-component/use-component.fragments.ts b/scopes/component/component/ui/use-component/use-component.fragments.ts
index 120b168bf223..adc4fd494d2a 100644
--- a/scopes/component/component/ui/use-component/use-component.fragments.ts
+++ b/scopes/component/component/ui/use-component/use-component.fragments.ts
@@ -55,7 +55,6 @@ export const componentOverviewFields = gql`
   }
   ${componentIdFields}
 `;
-console.log('🚀 ~ file: use-component.fragments.ts:58 ~ componentOverviewFields:', componentOverviewFields);
 
 export const componentFields = gql`
   fragment componentFields on Component {
@@ -69,6 +68,7 @@ export const componentFields = gql`
     tags {
       version
     }
+  }
   ${componentOverviewFields}
 `;
 
@@ -172,7 +172,11 @@ export const GET_COMPONENT = gql`
 `;
 
 export const GET_COMPONENT_WITH_LOGS = gql`
-  query Component($extensionId: String!, $id: String!) {
+  query Component(
+    $extensionId: String!
+    $id: String!
+    ${COMPONENT_QUERY_LOG_FIELDS}
+    ) {
     getHost(id: $extensionId) {
       id # used for GQL caching
       get(id: $id) {
diff --git a/scopes/component/component/ui/use-component/use-component.model.ts b/scopes/component/component/ui/use-component/use-component.model.ts
index f7dbc7945b69..fd8c5f655ad4 100644
--- a/scopes/component/component/ui/use-component/use-component.model.ts
+++ b/scopes/component/component/ui/use-component/use-component.model.ts
@@ -1,6 +1,7 @@
 import { ComponentDescriptor } from '@teambit/component-descriptor';
 import { LegacyComponentLog } from '@teambit/legacy-component-log';
-import { ComponentError, ComponentModel } from '..';
+import { ComponentError } from '../component-error';
+import { ComponentModel } from '../component-model';
 
 export type LogFilter = {
   logOffset?: number;

From 2c1ebd1f207ac84aba09e737de8e475393a468c8 Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Tue, 16 May 2023 18:57:12 -0400
Subject: [PATCH 18/20] clean up

---
 .../ui/use-component/use-component-logs.ts    | 25 -------------------
 .../use-component/use-component.fragments.ts  |  6 +++--
 .../ui/use-component/use-component.model.ts   |  1 -
 3 files changed, 4 insertions(+), 28 deletions(-)

diff --git a/scopes/component/component/ui/use-component/use-component-logs.ts b/scopes/component/component/ui/use-component/use-component-logs.ts
index 41d73686d495..ce286f69ec7c 100644
--- a/scopes/component/component/ui/use-component/use-component-logs.ts
+++ b/scopes/component/component/ui/use-component/use-component-logs.ts
@@ -1,7 +1,6 @@
 import React, { useMemo, useRef } from 'react';
 import { LegacyComponentLog } from '@teambit/legacy-component-log';
 import { useDataQuery } from '@teambit/ui-foundation.ui.hooks.use-data-query';
-import { ComponentDescriptor } from '@teambit/component-descriptor';
 import { ComponentID } from '@teambit/component-id';
 import { calculateHasMoreLogs, calculateNewOffset, getOffsetValue, mergeLogs } from './use-component.utils';
 import { ComponentLogsResult, Filters } from './use-component.model';
@@ -210,31 +209,7 @@ export function useComponentLogs(
       ? new ComponentError(500, error.message)
       : (!rawComponent && !loading && new ComponentError(404)) || undefined;
 
-  const idDepKey = rawComponent?.id
-    ? `${rawComponent?.id?.scope}/${rawComponent?.id?.name}@${rawComponent?.id?.version}}`
-    : undefined;
-
-  const id: ComponentID | undefined = useMemo(
-    () => (rawComponent ? ComponentID.fromObject(rawComponent.id) : undefined),
-    [idDepKey]
-  );
-
-  const componentDescriptor = useMemo(() => {
-    const aspectList = {
-      entries: rawComponent?.aspects.map((aspectObject) => {
-        return {
-          ...aspectObject,
-          aspectId: aspectObject.id,
-          aspectData: aspectObject.data,
-        };
-      }),
-    };
-
-    return id ? ComponentDescriptor.fromObject({ id: id.toString(), aspectList }) : undefined;
-  }, [id?.toString()]);
-
   return {
-    componentDescriptor,
     loading,
     error: componentError,
     componentLogs: {
diff --git a/scopes/component/component/ui/use-component/use-component.fragments.ts b/scopes/component/component/ui/use-component/use-component.fragments.ts
index adc4fd494d2a..59e05424f1ce 100644
--- a/scopes/component/component/ui/use-component/use-component.fragments.ts
+++ b/scopes/component/component/ui/use-component/use-component.fragments.ts
@@ -74,7 +74,9 @@ export const componentFields = gql`
 
 export const componentFieldsWithLogs = gql`
   fragment componentFieldWithLogs on Component {
-    ...componentFields
+    id {
+      ...componentIdFields
+    }
     logs(
       type: $logType
       offset: $logOffset
@@ -130,7 +132,7 @@ export const componentFieldsWithLogs = gql`
       tag
     }
   }
-  ${componentFields}
+  ${componentIdFields}
 `;
 
 export const COMPONENT_QUERY_LOG_FIELDS = `
diff --git a/scopes/component/component/ui/use-component/use-component.model.ts b/scopes/component/component/ui/use-component/use-component.model.ts
index fd8c5f655ad4..cfd208c066b9 100644
--- a/scopes/component/component/ui/use-component/use-component.model.ts
+++ b/scopes/component/component/ui/use-component/use-component.model.ts
@@ -37,7 +37,6 @@ export type ComponentQueryResult = {
 };
 
 export type ComponentLogsResult = {
-  componentDescriptor?: ComponentDescriptor;
   componentLogs?: ComponentLogs;
   error?: ComponentError;
   loading?: boolean;

From 09acbaf37845057ef96494d0487ff8c492a1db3a Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Tue, 16 May 2023 18:59:30 -0400
Subject: [PATCH 19/20] clean up

---
 .../ui/component-tooltip/Untitled-1.yml       | 789 ------------------
 1 file changed, 789 deletions(-)
 delete mode 100644 scopes/component/ui/component-tooltip/Untitled-1.yml

diff --git a/scopes/component/ui/component-tooltip/Untitled-1.yml b/scopes/component/ui/component-tooltip/Untitled-1.yml
deleted file mode 100644
index 5f2bc6150471..000000000000
--- a/scopes/component/ui/component-tooltip/Untitled-1.yml
+++ /dev/null
@@ -1,789 +0,0 @@
-[
-  {
-    id: "060c458da72872729a87d8051ef08e190a25d751",
-    attr: { hash: "060c458da72872729a87d8051ef08e190a25d751" },
-    _inEdges:
-      [
-        "ff44ae21a26570f6395bc316417f0b6fb1bca930->060c458da72872729a87d8051ef08e190a25d751",
-      ],
-    _outEdges:
-      [
-        "060c458da72872729a87d8051ef08e190a25d751->484ba5c2ebcbb22ef6d5090eec6032d6a4fd1482",
-      ],
-  },
-
-  {
-    id: "484ba5c2ebcbb22ef6d5090eec6032d6a4fd1482",
-    attr: { hash: "484ba5c2ebcbb22ef6d5090eec6032d6a4fd1482" },
-    _inEdges:
-      [
-        "060c458da72872729a87d8051ef08e190a25d751->484ba5c2ebcbb22ef6d5090eec6032d6a4fd1482",
-      ],
-    _outEdges:
-      [
-        "484ba5c2ebcbb22ef6d5090eec6032d6a4fd1482->7803e482a4943a6e31deb9b38c80d735f75916ae",
-      ],
-  },
-
-  {
-    id: "7803e482a4943a6e31deb9b38c80d735f75916ae",
-    attr: { hash: "7803e482a4943a6e31deb9b38c80d735f75916ae" },
-    _inEdges:
-      [
-        "484ba5c2ebcbb22ef6d5090eec6032d6a4fd1482->7803e482a4943a6e31deb9b38c80d735f75916ae",
-      ],
-    _outEdges:
-      [
-        "7803e482a4943a6e31deb9b38c80d735f75916ae->7451e0eb12563c8c63b53dc9ddb141de5918b1d7",
-      ],
-  },
-
-  {
-    id: "7451e0eb12563c8c63b53dc9ddb141de5918b1d7",
-    attr: { hash: "7451e0eb12563c8c63b53dc9ddb141de5918b1d7" },
-    _inEdges:
-      [
-        "7803e482a4943a6e31deb9b38c80d735f75916ae->7451e0eb12563c8c63b53dc9ddb141de5918b1d7",
-      ],
-    _outEdges:
-      [
-        "7451e0eb12563c8c63b53dc9ddb141de5918b1d7->8293c41d17d451d758725c7531c98138b9370b09",
-      ],
-  },
-
-  {
-    id: "8293c41d17d451d758725c7531c98138b9370b09",
-    attr: { hash: "8293c41d17d451d758725c7531c98138b9370b09" },
-    _inEdges:
-      [
-        "7451e0eb12563c8c63b53dc9ddb141de5918b1d7->8293c41d17d451d758725c7531c98138b9370b09",
-      ],
-    _outEdges:
-      [
-        "8293c41d17d451d758725c7531c98138b9370b09->de5b08e7da761f619d2499eaa0720ad29fc77eb2",
-      ],
-  },
-
-  {
-    id: "de5b08e7da761f619d2499eaa0720ad29fc77eb2",
-    attr: { hash: "de5b08e7da761f619d2499eaa0720ad29fc77eb2" },
-    _inEdges:
-      [
-        "8293c41d17d451d758725c7531c98138b9370b09->de5b08e7da761f619d2499eaa0720ad29fc77eb2",
-      ],
-    _outEdges:
-      [
-        "de5b08e7da761f619d2499eaa0720ad29fc77eb2->f94061d4ba59bef342b2ffba7cbe7d3568a9c52c",
-      ],
-  },
-
-  {
-    id: "f94061d4ba59bef342b2ffba7cbe7d3568a9c52c",
-    attr: { hash: "f94061d4ba59bef342b2ffba7cbe7d3568a9c52c" },
-    _inEdges:
-      [
-        "de5b08e7da761f619d2499eaa0720ad29fc77eb2->f94061d4ba59bef342b2ffba7cbe7d3568a9c52c",
-      ],
-    _outEdges:
-      [
-        "f94061d4ba59bef342b2ffba7cbe7d3568a9c52c->6e1e136d27542e751f0390d2c33b722f34d89a10",
-        "f94061d4ba59bef342b2ffba7cbe7d3568a9c52c->edbb65f181cceb87d9dda7c3c37d08ea601ec651",
-      ],
-  },
-
-  {
-    id: "6e1e136d27542e751f0390d2c33b722f34d89a10",
-    attr: { hash: "6e1e136d27542e751f0390d2c33b722f34d89a10" },
-    _inEdges:
-      [
-        "f94061d4ba59bef342b2ffba7cbe7d3568a9c52c->6e1e136d27542e751f0390d2c33b722f34d89a10",
-      ],
-    _outEdges:
-      [
-        "6e1e136d27542e751f0390d2c33b722f34d89a10->e8f760d67664a04c4fde116183a4ca6bd7c9bfce",
-      ],
-  },
-
-  {
-    id: "e8f760d67664a04c4fde116183a4ca6bd7c9bfce",
-    attr: { hash: "e8f760d67664a04c4fde116183a4ca6bd7c9bfce" },
-    _inEdges:
-      [
-        "6e1e136d27542e751f0390d2c33b722f34d89a10->e8f760d67664a04c4fde116183a4ca6bd7c9bfce",
-      ],
-    _outEdges:
-      [
-        "e8f760d67664a04c4fde116183a4ca6bd7c9bfce->d7c6b760ceb136780fcabe2c16dffcbf2a7456f6",
-      ],
-  },
-
-  {
-    id: "d7c6b760ceb136780fcabe2c16dffcbf2a7456f6",
-    attr: { hash: "d7c6b760ceb136780fcabe2c16dffcbf2a7456f6" },
-    _inEdges:
-      [
-        "e8f760d67664a04c4fde116183a4ca6bd7c9bfce->d7c6b760ceb136780fcabe2c16dffcbf2a7456f6",
-      ],
-    _outEdges:
-      [
-        "d7c6b760ceb136780fcabe2c16dffcbf2a7456f6->136aab756693cfa6283f45f02b2b6aedb71e3199",
-        "d7c6b760ceb136780fcabe2c16dffcbf2a7456f6->16be08d1f9207f58a3b13b643a5f6df83b00851c",
-      ],
-  },
-
-  {
-    id: "136aab756693cfa6283f45f02b2b6aedb71e3199",
-    attr: { hash: "136aab756693cfa6283f45f02b2b6aedb71e3199" },
-    _inEdges:
-      [
-        "d7c6b760ceb136780fcabe2c16dffcbf2a7456f6->136aab756693cfa6283f45f02b2b6aedb71e3199",
-      ],
-    _outEdges:
-      [
-        "136aab756693cfa6283f45f02b2b6aedb71e3199->d18b9728f2cd80571ea7f070ec2a62e40461b51e",
-      ],
-  },
-
-  {
-    id: "d18b9728f2cd80571ea7f070ec2a62e40461b51e",
-    attr: { hash: "d18b9728f2cd80571ea7f070ec2a62e40461b51e" },
-    _inEdges:
-      [
-        "136aab756693cfa6283f45f02b2b6aedb71e3199->d18b9728f2cd80571ea7f070ec2a62e40461b51e",
-      ],
-    _outEdges:
-      [
-        "d18b9728f2cd80571ea7f070ec2a62e40461b51e->cea5d5c39bb9a33b3af8e4f45df5722d58acae36",
-        "d18b9728f2cd80571ea7f070ec2a62e40461b51e->7fa90951d4ec0646f3368ccdbbbe89fe3de09212",
-      ],
-  },
-
-  {
-    id: "cea5d5c39bb9a33b3af8e4f45df5722d58acae36",
-    attr: { hash: "cea5d5c39bb9a33b3af8e4f45df5722d58acae36" },
-    _inEdges:
-      [
-        "d18b9728f2cd80571ea7f070ec2a62e40461b51e->cea5d5c39bb9a33b3af8e4f45df5722d58acae36",
-      ],
-    _outEdges:
-      [
-        "cea5d5c39bb9a33b3af8e4f45df5722d58acae36->c2a1ea45e7e2906d888355ea069303de347ef968",
-      ],
-  },
-
-  {
-    id: "c2a1ea45e7e2906d888355ea069303de347ef968",
-    attr: { hash: "c2a1ea45e7e2906d888355ea069303de347ef968" },
-    _inEdges:
-      [
-        "edbb65f181cceb87d9dda7c3c37d08ea601ec651->c2a1ea45e7e2906d888355ea069303de347ef968",
-        "cea5d5c39bb9a33b3af8e4f45df5722d58acae36->c2a1ea45e7e2906d888355ea069303de347ef968",
-      ],
-    _outEdges:
-      [
-        "c2a1ea45e7e2906d888355ea069303de347ef968->0b6993ef2933137d9f009e1a55ac1603072c0065",
-      ],
-  },
-
-  {
-    id: "0b6993ef2933137d9f009e1a55ac1603072c0065",
-    attr: { hash: "0b6993ef2933137d9f009e1a55ac1603072c0065" },
-    _inEdges:
-      [
-        "c2a1ea45e7e2906d888355ea069303de347ef968->0b6993ef2933137d9f009e1a55ac1603072c0065",
-      ],
-    _outEdges:
-      [
-        "0b6993ef2933137d9f009e1a55ac1603072c0065->33d5a8b8bf37f022b906740d2cd22d505073a34a",
-      ],
-  },
-
-  {
-    id: "33d5a8b8bf37f022b906740d2cd22d505073a34a",
-    attr: { hash: "33d5a8b8bf37f022b906740d2cd22d505073a34a" },
-    _inEdges:
-      [
-        "28cce4514ec9a3aa0d0cda4ab84cf7a6c8df0ca3->33d5a8b8bf37f022b906740d2cd22d505073a34a",
-        "0b6993ef2933137d9f009e1a55ac1603072c0065->33d5a8b8bf37f022b906740d2cd22d505073a34a",
-        "5f61a84fd0303994d1a31830b829c3d0f9ddeffa->33d5a8b8bf37f022b906740d2cd22d505073a34a",
-      ],
-    _outEdges:
-      [
-        "33d5a8b8bf37f022b906740d2cd22d505073a34a->8c18ba98ff5fa4becf72e22602d108a061d39c96",
-      ],
-  },
-
-  {
-    id: "8c18ba98ff5fa4becf72e22602d108a061d39c96",
-    attr: { hash: "8c18ba98ff5fa4becf72e22602d108a061d39c96" },
-    _inEdges:
-      [
-        "33d5a8b8bf37f022b906740d2cd22d505073a34a->8c18ba98ff5fa4becf72e22602d108a061d39c96",
-        "10c7a8a81f5c7c86def9e2af55f3172d44a778cc->8c18ba98ff5fa4becf72e22602d108a061d39c96",
-      ],
-    _outEdges:
-      [
-        "8c18ba98ff5fa4becf72e22602d108a061d39c96->30f947c6b332ea104cb643b2a635952d9a4b1774",
-      ],
-  },
-
-  {
-    id: "30f947c6b332ea104cb643b2a635952d9a4b1774",
-    attr: { hash: "30f947c6b332ea104cb643b2a635952d9a4b1774" },
-    _inEdges:
-      [
-        "8c18ba98ff5fa4becf72e22602d108a061d39c96->30f947c6b332ea104cb643b2a635952d9a4b1774",
-      ],
-    _outEdges:
-      [
-        "30f947c6b332ea104cb643b2a635952d9a4b1774->3c14f556f1d32bc459963b8ea7464e6b896c3531",
-      ],
-  },
-
-  {
-    id: "3c14f556f1d32bc459963b8ea7464e6b896c3531",
-    attr: { hash: "3c14f556f1d32bc459963b8ea7464e6b896c3531" },
-    _inEdges:
-      [
-        "30f947c6b332ea104cb643b2a635952d9a4b1774->3c14f556f1d32bc459963b8ea7464e6b896c3531",
-      ],
-    _outEdges:
-      [
-        "3c14f556f1d32bc459963b8ea7464e6b896c3531->97354a80b5aa0a430f89827f6810afd2ab4900ca",
-      ],
-  },
-
-  {
-    id: "97354a80b5aa0a430f89827f6810afd2ab4900ca",
-    attr: { hash: "97354a80b5aa0a430f89827f6810afd2ab4900ca" },
-    _inEdges:
-      [
-        "3c14f556f1d32bc459963b8ea7464e6b896c3531->97354a80b5aa0a430f89827f6810afd2ab4900ca",
-      ],
-    _outEdges:
-      [
-        "97354a80b5aa0a430f89827f6810afd2ab4900ca->4ea6caed5281a2bd2b1d249708b08430b6c1a845",
-      ],
-  },
-
-  {
-    id: "4ea6caed5281a2bd2b1d249708b08430b6c1a845",
-    attr: { hash: "4ea6caed5281a2bd2b1d249708b08430b6c1a845" },
-    _inEdges:
-      [
-        "97354a80b5aa0a430f89827f6810afd2ab4900ca->4ea6caed5281a2bd2b1d249708b08430b6c1a845",
-      ],
-    _outEdges:
-      [
-        "4ea6caed5281a2bd2b1d249708b08430b6c1a845->67d4381ff07c0f4f71c7ca093d6a617d98f62fd9",
-      ],
-  },
-
-  {
-    id: "67d4381ff07c0f4f71c7ca093d6a617d98f62fd9",
-    attr: { hash: "67d4381ff07c0f4f71c7ca093d6a617d98f62fd9" },
-    _inEdges:
-      [
-        "4ea6caed5281a2bd2b1d249708b08430b6c1a845->67d4381ff07c0f4f71c7ca093d6a617d98f62fd9",
-        "8d97eff47eb7809463564fc3d861ac85be87dcc1->67d4381ff07c0f4f71c7ca093d6a617d98f62fd9",
-      ],
-    _outEdges:
-      [
-        "67d4381ff07c0f4f71c7ca093d6a617d98f62fd9->e75cd8c849caf103897a68aa8f6ec3ae7717a384",
-      ],
-  },
-
-  {
-    id: "e75cd8c849caf103897a68aa8f6ec3ae7717a384",
-    attr: { hash: "e75cd8c849caf103897a68aa8f6ec3ae7717a384" },
-    _inEdges:
-      [
-        "67d4381ff07c0f4f71c7ca093d6a617d98f62fd9->e75cd8c849caf103897a68aa8f6ec3ae7717a384",
-      ],
-    _outEdges:
-      [
-        "e75cd8c849caf103897a68aa8f6ec3ae7717a384->d8d7ba85f8ae204a28ec370774c856ea98ea9a52",
-      ],
-  },
-
-  {
-    id: "d8d7ba85f8ae204a28ec370774c856ea98ea9a52",
-    attr: { hash: "d8d7ba85f8ae204a28ec370774c856ea98ea9a52" },
-    _inEdges:
-      [
-        "e75cd8c849caf103897a68aa8f6ec3ae7717a384->d8d7ba85f8ae204a28ec370774c856ea98ea9a52",
-      ],
-    _outEdges:
-      [
-        "d8d7ba85f8ae204a28ec370774c856ea98ea9a52->b9f744da953bdab1724fd48b259a42b3476d7a7d",
-      ],
-  },
-
-  {
-    id: "b9f744da953bdab1724fd48b259a42b3476d7a7d",
-    attr: { hash: "b9f744da953bdab1724fd48b259a42b3476d7a7d" },
-    _inEdges:
-      [
-        "d8d7ba85f8ae204a28ec370774c856ea98ea9a52->b9f744da953bdab1724fd48b259a42b3476d7a7d",
-      ],
-    _outEdges:
-      [
-        "b9f744da953bdab1724fd48b259a42b3476d7a7d->6fa6d42b8580f301b9910a49c70d7b11d2fc357e",
-      ],
-  },
-
-  {
-    id: "6fa6d42b8580f301b9910a49c70d7b11d2fc357e",
-    attr: { hash: "6fa6d42b8580f301b9910a49c70d7b11d2fc357e" },
-    _inEdges:
-      [
-        "b9f744da953bdab1724fd48b259a42b3476d7a7d->6fa6d42b8580f301b9910a49c70d7b11d2fc357e",
-      ],
-    _outEdges:
-      [
-        "6fa6d42b8580f301b9910a49c70d7b11d2fc357e->17eae8caf411633e4553ff7ee6ceab4a0dac835c",
-      ],
-  },
-
-  {
-    id: "17eae8caf411633e4553ff7ee6ceab4a0dac835c",
-    attr: { hash: "17eae8caf411633e4553ff7ee6ceab4a0dac835c" },
-    _inEdges:
-      [
-        "6fa6d42b8580f301b9910a49c70d7b11d2fc357e->17eae8caf411633e4553ff7ee6ceab4a0dac835c",
-      ],
-    _outEdges:
-      [
-        "17eae8caf411633e4553ff7ee6ceab4a0dac835c->e72c0d0b1d31ed52999def993ca425bc8b6eec8d",
-      ],
-  },
-
-  {
-    id: "e72c0d0b1d31ed52999def993ca425bc8b6eec8d",
-    attr: { hash: "e72c0d0b1d31ed52999def993ca425bc8b6eec8d" },
-    _inEdges:
-      [
-        "17eae8caf411633e4553ff7ee6ceab4a0dac835c->e72c0d0b1d31ed52999def993ca425bc8b6eec8d",
-      ],
-    _outEdges:
-      [
-        "e72c0d0b1d31ed52999def993ca425bc8b6eec8d->d2ab69219d828c8822444f5c8a7b92763f4bf9b8",
-      ],
-  },
-
-  {
-    id: "d2ab69219d828c8822444f5c8a7b92763f4bf9b8",
-    attr: { hash: "d2ab69219d828c8822444f5c8a7b92763f4bf9b8" },
-    _inEdges:
-      [
-        "e72c0d0b1d31ed52999def993ca425bc8b6eec8d->d2ab69219d828c8822444f5c8a7b92763f4bf9b8",
-      ],
-    _outEdges:
-      [
-        "d2ab69219d828c8822444f5c8a7b92763f4bf9b8->f1cff5c303f39c5047c6bab98baab1156c48c1b6",
-      ],
-  },
-
-  {
-    id: "f1cff5c303f39c5047c6bab98baab1156c48c1b6",
-    attr: { hash: "f1cff5c303f39c5047c6bab98baab1156c48c1b6" },
-    _inEdges:
-      [
-        "d2ab69219d828c8822444f5c8a7b92763f4bf9b8->f1cff5c303f39c5047c6bab98baab1156c48c1b6",
-      ],
-    _outEdges:
-      [
-        "f1cff5c303f39c5047c6bab98baab1156c48c1b6->2ae41ac40604cb7d68ecfa1ad7432e090bac3f01",
-      ],
-  },
-
-  {
-    id: "2ae41ac40604cb7d68ecfa1ad7432e090bac3f01",
-    attr: { hash: "2ae41ac40604cb7d68ecfa1ad7432e090bac3f01" },
-    _inEdges:
-      [
-        "f1cff5c303f39c5047c6bab98baab1156c48c1b6->2ae41ac40604cb7d68ecfa1ad7432e090bac3f01",
-      ],
-    _outEdges:
-      [
-        "2ae41ac40604cb7d68ecfa1ad7432e090bac3f01->241832c97051733499b87d742286a811711dfcfb",
-      ],
-  },
-
-  {
-    id: "241832c97051733499b87d742286a811711dfcfb",
-    attr: { hash: "241832c97051733499b87d742286a811711dfcfb" },
-    _inEdges:
-      [
-        "2ae41ac40604cb7d68ecfa1ad7432e090bac3f01->241832c97051733499b87d742286a811711dfcfb",
-      ],
-    _outEdges:
-      [
-        "241832c97051733499b87d742286a811711dfcfb->efca1e0bfe4a8347e46048ee9dae0cc726461cb8",
-      ],
-  },
-
-  {
-    id: "efca1e0bfe4a8347e46048ee9dae0cc726461cb8",
-    attr: { hash: "efca1e0bfe4a8347e46048ee9dae0cc726461cb8" },
-    _inEdges:
-      [
-        "241832c97051733499b87d742286a811711dfcfb->efca1e0bfe4a8347e46048ee9dae0cc726461cb8",
-      ],
-    _outEdges:
-      [
-        "efca1e0bfe4a8347e46048ee9dae0cc726461cb8->f9f52eb71d5d020f53a67ebf2c98049969553772",
-      ],
-  },
-
-  {
-    id: "f9f52eb71d5d020f53a67ebf2c98049969553772",
-    attr: { hash: "f9f52eb71d5d020f53a67ebf2c98049969553772" },
-    _inEdges:
-      [
-        "efca1e0bfe4a8347e46048ee9dae0cc726461cb8->f9f52eb71d5d020f53a67ebf2c98049969553772",
-      ],
-    _outEdges:
-      [
-        "f9f52eb71d5d020f53a67ebf2c98049969553772->8eb3a70cf0752b54fa2f9651ccde79563112bad3",
-      ],
-  },
-
-  {
-    id: "8eb3a70cf0752b54fa2f9651ccde79563112bad3",
-    attr: { hash: "8eb3a70cf0752b54fa2f9651ccde79563112bad3" },
-    _inEdges:
-      [
-        "f9f52eb71d5d020f53a67ebf2c98049969553772->8eb3a70cf0752b54fa2f9651ccde79563112bad3",
-      ],
-    _outEdges:
-      [
-        "8eb3a70cf0752b54fa2f9651ccde79563112bad3->6164e5aa0f92ac872eb54641b4284fc3ec94aa0f",
-      ],
-  },
-
-  {
-    id: "6164e5aa0f92ac872eb54641b4284fc3ec94aa0f",
-    attr: { hash: "6164e5aa0f92ac872eb54641b4284fc3ec94aa0f" },
-    _inEdges:
-      [
-        "8eb3a70cf0752b54fa2f9651ccde79563112bad3->6164e5aa0f92ac872eb54641b4284fc3ec94aa0f",
-      ],
-    _outEdges:
-      [
-        "6164e5aa0f92ac872eb54641b4284fc3ec94aa0f->73d23a8fa2214261cacd174afdcf12d5cca86acb",
-      ],
-  },
-
-  {
-    id: "73d23a8fa2214261cacd174afdcf12d5cca86acb",
-    attr: { hash: "73d23a8fa2214261cacd174afdcf12d5cca86acb" },
-    _inEdges:
-      [
-        "6164e5aa0f92ac872eb54641b4284fc3ec94aa0f->73d23a8fa2214261cacd174afdcf12d5cca86acb",
-      ],
-    _outEdges: [],
-  },
-
-  {
-    id: "7fa90951d4ec0646f3368ccdbbbe89fe3de09212",
-    attr: { hash: "7fa90951d4ec0646f3368ccdbbbe89fe3de09212" },
-    _inEdges:
-      [
-        "d18b9728f2cd80571ea7f070ec2a62e40461b51e->7fa90951d4ec0646f3368ccdbbbe89fe3de09212",
-        "d2b7f87cb7f217f31076cd23ad903d73aeabb85d->7fa90951d4ec0646f3368ccdbbbe89fe3de09212",
-      ],
-    _outEdges:
-      [
-        "7fa90951d4ec0646f3368ccdbbbe89fe3de09212->0e42b353336a3b03c6c63765b7cf3258b725c559",
-      ],
-  },
-
-  {
-    id: "0e42b353336a3b03c6c63765b7cf3258b725c559",
-    attr: { hash: "0e42b353336a3b03c6c63765b7cf3258b725c559" },
-    _inEdges:
-      [
-        "7fa90951d4ec0646f3368ccdbbbe89fe3de09212->0e42b353336a3b03c6c63765b7cf3258b725c559",
-      ],
-    _outEdges:
-      [
-        "0e42b353336a3b03c6c63765b7cf3258b725c559->5f61a84fd0303994d1a31830b829c3d0f9ddeffa",
-      ],
-  },
-
-  {
-    id: "5f61a84fd0303994d1a31830b829c3d0f9ddeffa",
-    attr: { hash: "5f61a84fd0303994d1a31830b829c3d0f9ddeffa" },
-    _inEdges:
-      [
-        "0e42b353336a3b03c6c63765b7cf3258b725c559->5f61a84fd0303994d1a31830b829c3d0f9ddeffa",
-      ],
-    _outEdges:
-      [
-        "5f61a84fd0303994d1a31830b829c3d0f9ddeffa->33d5a8b8bf37f022b906740d2cd22d505073a34a",
-      ],
-  },
-
-  {
-    id: "16be08d1f9207f58a3b13b643a5f6df83b00851c",
-    attr: { hash: "16be08d1f9207f58a3b13b643a5f6df83b00851c" },
-    _inEdges:
-      [
-        "d7c6b760ceb136780fcabe2c16dffcbf2a7456f6->16be08d1f9207f58a3b13b643a5f6df83b00851c",
-      ],
-    _outEdges:
-      [
-        "16be08d1f9207f58a3b13b643a5f6df83b00851c->b45b33d7c424984790725b545e9f8826a4a26e90",
-      ],
-  },
-
-  {
-    id: "b45b33d7c424984790725b545e9f8826a4a26e90",
-    attr: { hash: "b45b33d7c424984790725b545e9f8826a4a26e90" },
-    _inEdges:
-      [
-        "16be08d1f9207f58a3b13b643a5f6df83b00851c->b45b33d7c424984790725b545e9f8826a4a26e90",
-      ],
-    _outEdges:
-      [
-        "b45b33d7c424984790725b545e9f8826a4a26e90->2c6f1ef1663049a478ac0b7e55df37b9c1c9a3c8",
-      ],
-  },
-
-  {
-    id: "2c6f1ef1663049a478ac0b7e55df37b9c1c9a3c8",
-    attr: { hash: "2c6f1ef1663049a478ac0b7e55df37b9c1c9a3c8" },
-    _inEdges:
-      [
-        "b45b33d7c424984790725b545e9f8826a4a26e90->2c6f1ef1663049a478ac0b7e55df37b9c1c9a3c8",
-      ],
-    _outEdges:
-      [
-        "2c6f1ef1663049a478ac0b7e55df37b9c1c9a3c8->d1f10da8f64d3023c23428e7d65be0e633ce8f04",
-      ],
-  },
-
-  {
-    id: "d1f10da8f64d3023c23428e7d65be0e633ce8f04",
-    attr: { hash: "d1f10da8f64d3023c23428e7d65be0e633ce8f04" },
-    _inEdges:
-      [
-        "2c6f1ef1663049a478ac0b7e55df37b9c1c9a3c8->d1f10da8f64d3023c23428e7d65be0e633ce8f04",
-      ],
-    _outEdges:
-      [
-        "d1f10da8f64d3023c23428e7d65be0e633ce8f04->dd7a7047f2c1c0f899f396e8769d3380073b4243",
-      ],
-  },
-
-  {
-    id: "dd7a7047f2c1c0f899f396e8769d3380073b4243",
-    attr: { hash: "dd7a7047f2c1c0f899f396e8769d3380073b4243" },
-    _inEdges:
-      [
-        "d1f10da8f64d3023c23428e7d65be0e633ce8f04->dd7a7047f2c1c0f899f396e8769d3380073b4243",
-      ],
-    _outEdges:
-      [
-        "dd7a7047f2c1c0f899f396e8769d3380073b4243->b372ba9e83dcb0b73f86ce733cff60a0895ec1e3",
-      ],
-  },
-
-  {
-    id: "b372ba9e83dcb0b73f86ce733cff60a0895ec1e3",
-    attr: { hash: "b372ba9e83dcb0b73f86ce733cff60a0895ec1e3" },
-    _inEdges:
-      [
-        "dd7a7047f2c1c0f899f396e8769d3380073b4243->b372ba9e83dcb0b73f86ce733cff60a0895ec1e3",
-      ],
-    _outEdges:
-      [
-        "b372ba9e83dcb0b73f86ce733cff60a0895ec1e3->8c89c7ff1dc8da4795ab4b173549d074dd564aa7",
-      ],
-  },
-
-  {
-    id: "8c89c7ff1dc8da4795ab4b173549d074dd564aa7",
-    attr: { hash: "8c89c7ff1dc8da4795ab4b173549d074dd564aa7" },
-    _inEdges:
-      [
-        "b372ba9e83dcb0b73f86ce733cff60a0895ec1e3->8c89c7ff1dc8da4795ab4b173549d074dd564aa7",
-      ],
-    _outEdges:
-      [
-        "8c89c7ff1dc8da4795ab4b173549d074dd564aa7->596c3f35749b39d4ec449e0f382521da5747f081",
-      ],
-  },
-
-  {
-    id: "596c3f35749b39d4ec449e0f382521da5747f081",
-    attr: { hash: "596c3f35749b39d4ec449e0f382521da5747f081" },
-    _inEdges:
-      [
-        "8c89c7ff1dc8da4795ab4b173549d074dd564aa7->596c3f35749b39d4ec449e0f382521da5747f081",
-      ],
-    _outEdges:
-      [
-        "596c3f35749b39d4ec449e0f382521da5747f081->f33f4b872f92bee46b104d94a7aad3474c91c53d",
-      ],
-  },
-
-  {
-    id: "f33f4b872f92bee46b104d94a7aad3474c91c53d",
-    attr: { hash: "f33f4b872f92bee46b104d94a7aad3474c91c53d" },
-    _inEdges:
-      [
-        "596c3f35749b39d4ec449e0f382521da5747f081->f33f4b872f92bee46b104d94a7aad3474c91c53d",
-      ],
-    _outEdges:
-      [
-        "f33f4b872f92bee46b104d94a7aad3474c91c53d->49a178a6d8b41b0bf4fb31008ddcc2e9e914662e",
-      ],
-  },
-
-  {
-    id: "49a178a6d8b41b0bf4fb31008ddcc2e9e914662e",
-    attr: { hash: "49a178a6d8b41b0bf4fb31008ddcc2e9e914662e" },
-    _inEdges:
-      [
-        "f33f4b872f92bee46b104d94a7aad3474c91c53d->49a178a6d8b41b0bf4fb31008ddcc2e9e914662e",
-      ],
-    _outEdges:
-      [
-        "49a178a6d8b41b0bf4fb31008ddcc2e9e914662e->ec1e05ae1f7b0f835b890b359459228d89b3788e",
-      ],
-  },
-
-  {
-    id: "ec1e05ae1f7b0f835b890b359459228d89b3788e",
-    attr: { hash: "ec1e05ae1f7b0f835b890b359459228d89b3788e" },
-    _inEdges:
-      [
-        "49a178a6d8b41b0bf4fb31008ddcc2e9e914662e->ec1e05ae1f7b0f835b890b359459228d89b3788e",
-      ],
-    _outEdges:
-      [
-        "ec1e05ae1f7b0f835b890b359459228d89b3788e->7c49a291b011c7d9c67a3c44f09cb018021b1d81",
-      ],
-  },
-
-  {
-    id: "7c49a291b011c7d9c67a3c44f09cb018021b1d81",
-    attr: { hash: "7c49a291b011c7d9c67a3c44f09cb018021b1d81" },
-    _inEdges:
-      [
-        "ec1e05ae1f7b0f835b890b359459228d89b3788e->7c49a291b011c7d9c67a3c44f09cb018021b1d81",
-      ],
-    _outEdges:
-      [
-        "7c49a291b011c7d9c67a3c44f09cb018021b1d81->aa081b26db3039b4a39be9fde0d0595b2770625c",
-      ],
-  },
-
-  {
-    id: "aa081b26db3039b4a39be9fde0d0595b2770625c",
-    attr: { hash: "aa081b26db3039b4a39be9fde0d0595b2770625c" },
-    _inEdges:
-      [
-        "7c49a291b011c7d9c67a3c44f09cb018021b1d81->aa081b26db3039b4a39be9fde0d0595b2770625c",
-      ],
-    _outEdges:
-      [
-        "aa081b26db3039b4a39be9fde0d0595b2770625c->7f06967106c0370d08cc8294b4adbb1d1ec0e3a1",
-      ],
-  },
-
-  {
-    id: "7f06967106c0370d08cc8294b4adbb1d1ec0e3a1",
-    attr: { hash: "7f06967106c0370d08cc8294b4adbb1d1ec0e3a1" },
-    _inEdges:
-      [
-        "aa081b26db3039b4a39be9fde0d0595b2770625c->7f06967106c0370d08cc8294b4adbb1d1ec0e3a1",
-      ],
-    _outEdges:
-      [
-        "7f06967106c0370d08cc8294b4adbb1d1ec0e3a1->ad88d251fdef37f9e4b619b346099475c2a6a13d",
-      ],
-  },
-
-  {
-    id: "ad88d251fdef37f9e4b619b346099475c2a6a13d",
-    attr: { hash: "ad88d251fdef37f9e4b619b346099475c2a6a13d" },
-    _inEdges:
-      [
-        "7f06967106c0370d08cc8294b4adbb1d1ec0e3a1->ad88d251fdef37f9e4b619b346099475c2a6a13d",
-      ],
-    _outEdges:
-      [
-        "ad88d251fdef37f9e4b619b346099475c2a6a13d->336c861d90196d45d3df2b6c553e56810765fb4b",
-      ],
-  },
-
-  {
-    id: "336c861d90196d45d3df2b6c553e56810765fb4b",
-    attr: { hash: "336c861d90196d45d3df2b6c553e56810765fb4b" },
-    _inEdges:
-      [
-        "ad88d251fdef37f9e4b619b346099475c2a6a13d->336c861d90196d45d3df2b6c553e56810765fb4b",
-      ],
-    _outEdges:
-      [
-        "336c861d90196d45d3df2b6c553e56810765fb4b->8d97eff47eb7809463564fc3d861ac85be87dcc1",
-      ],
-  },
-
-  {
-    id: "8d97eff47eb7809463564fc3d861ac85be87dcc1",
-    attr: { hash: "8d97eff47eb7809463564fc3d861ac85be87dcc1" },
-    _inEdges:
-      [
-        "336c861d90196d45d3df2b6c553e56810765fb4b->8d97eff47eb7809463564fc3d861ac85be87dcc1",
-      ],
-    _outEdges:
-      [
-        "8d97eff47eb7809463564fc3d861ac85be87dcc1->67d4381ff07c0f4f71c7ca093d6a617d98f62fd9",
-      ],
-  },
-
-  {
-    id: "edbb65f181cceb87d9dda7c3c37d08ea601ec651",
-    attr: { hash: "edbb65f181cceb87d9dda7c3c37d08ea601ec651" },
-    _inEdges:
-      [
-        "f94061d4ba59bef342b2ffba7cbe7d3568a9c52c->edbb65f181cceb87d9dda7c3c37d08ea601ec651",
-      ],
-    _outEdges:
-      [
-        "edbb65f181cceb87d9dda7c3c37d08ea601ec651->28cce4514ec9a3aa0d0cda4ab84cf7a6c8df0ca3",
-        "edbb65f181cceb87d9dda7c3c37d08ea601ec651->c2a1ea45e7e2906d888355ea069303de347ef968",
-      ],
-  },
-
-  {
-    id: "28cce4514ec9a3aa0d0cda4ab84cf7a6c8df0ca3",
-    attr: { hash: "28cce4514ec9a3aa0d0cda4ab84cf7a6c8df0ca3" },
-    _inEdges:
-      [
-        "edbb65f181cceb87d9dda7c3c37d08ea601ec651->28cce4514ec9a3aa0d0cda4ab84cf7a6c8df0ca3",
-      ],
-    _outEdges:
-      [
-        "28cce4514ec9a3aa0d0cda4ab84cf7a6c8df0ca3->10c7a8a81f5c7c86def9e2af55f3172d44a778cc",
-        "28cce4514ec9a3aa0d0cda4ab84cf7a6c8df0ca3->33d5a8b8bf37f022b906740d2cd22d505073a34a",
-      ],
-  },
-
-  {
-    id: "10c7a8a81f5c7c86def9e2af55f3172d44a778cc",
-    attr: { hash: "10c7a8a81f5c7c86def9e2af55f3172d44a778cc" },
-    _inEdges:
-      [
-        "28cce4514ec9a3aa0d0cda4ab84cf7a6c8df0ca3->10c7a8a81f5c7c86def9e2af55f3172d44a778cc",
-      ],
-    _outEdges:
-      [
-        "10c7a8a81f5c7c86def9e2af55f3172d44a778cc->8c18ba98ff5fa4becf72e22602d108a061d39c96",
-      ],
-  },
-]

From 46ba4bac1d12b21dd56868e38392313934d0d2bf Mon Sep 17 00:00:00 2001
From: Luv Kapur <luv@bit.dev>
Date: Wed, 17 May 2023 07:54:11 -0400
Subject: [PATCH 20/20] fix handling getting logs for lane components without
 head filter

---
 scopes/scope/scope/scope.main.runtime.ts | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/scopes/scope/scope/scope.main.runtime.ts b/scopes/scope/scope/scope.main.runtime.ts
index ed48e65e6a16..cda63331ccf3 100644
--- a/scopes/scope/scope/scope.main.runtime.ts
+++ b/scopes/scope/scope/scope.main.runtime.ts
@@ -780,11 +780,10 @@ export class ScopeMain implements ComponentFactory {
 
     const headRef = head ? componentModel.getRef(head) : componentModel.head;
 
-    if (!headRef) return [];
-
-    if (!startFromOffset && !stopAtOffset) {
+    if (!headRef || (startFromOffset === undefined && stopAtOffset === undefined)) {
       return this.legacyScope.loadComponentLogs(id._legacy, shortHash, startFrom, stopAt ? [stopAt] : undefined);
     }
+
     const versionHistory = await componentModel.getAndPopulateVersionHistory(this.legacyScope.objects, headRef);
     const versionGraph = versionHistory.getGraph();
     const startFromRef = startFrom ? componentModel.getRef(startFrom) : undefined;