Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
158e609
perf(workspace-ui): ~100x faster time-to-first-render via progressive…
luvkapur Feb 5, 2026
4f9195e
fix(workspace-overview): refresh env icon when heavy query resolves
luvkapur Feb 5, 2026
fd6297b
wip: instant workspace
luvkapur Feb 6, 2026
07ae42a
Merge branch 'master' into perf/runtime-optimizations
luvkapur Feb 6, 2026
e4a709d
wip - instant workspace
luvkapur Feb 6, 2026
7b9a59e
Merge master into runtime optimizations and port UI start path to rspack
luvkapur Feb 10, 2026
b602f20
wip: optimize startup
luvkapur Feb 11, 2026
b15f0e6
wip: optimize startup
luvkapur Feb 11, 2026
0fc5954
checkpoint: stabilize preview/offline runtime flow
luvkapur Feb 17, 2026
1cd0afd
fix: reduce server fallback query churn and preserve virtualized prev…
luvkapur Feb 17, 2026
99d3023
fix: prevent stale apollo runtime server state
luvkapur Feb 17, 2026
efa924c
Merge origin/master into perf/runtime-optimizations
luvkapur Feb 17, 2026
fc99c8b
Revert "revert: rspack migration, manifest plugin fix, and graphql ba…
luvkapur Feb 17, 2026
edd374d
fix(runtime): restore green lint/type gates for pr-10191 scope
luvkapur Feb 18, 2026
3e9afff
chore: remove agent context artifacts from branch
luvkapur Feb 18, 2026
531fd5a
Merge origin/master into perf/runtime-optimizations (resolve rspack/r…
luvkapur Feb 18, 2026
a0db726
fix(component-menu): handle new/single-version logs and align lockfile
luvkapur Feb 18, 2026
6961d20
revert(pnpm): align package-manager.ts with master exactly
luvkapur Feb 18, 2026
ee7610d
ux(workspace): remove boot status strip and retry CTA
luvkapur Feb 18, 2026
4300a5d
revert(webpack): drop runtime-optimization webpack deltas
luvkapur Feb 18, 2026
c055ca5
Merge origin/master into perf/runtime-optimizations
luvkapur Aug 20, 2026
94f9877
chore: remove stray version-dropdown_1 copy and unused react-virtual dep
luvkapur Aug 20, 2026
be6bb2e
fix(workspace-overview): show grid skeleton instead of blank state wh…
luvkapur Aug 20, 2026
18039de
Merge remote-tracking branch 'origin/master' into perf/runtime-optimi…
luvkapur Aug 20, 2026
2802215
perf(ui): paint a boot shell before the bundle loads, and never flash…
luvkapur Aug 20, 2026
301fb43
fix(ui): rebuild the UI bundle when a UI aspect changed under it
luvkapur Aug 20, 2026
c2cbc7d
fix(preview): report which env broke the preview bundle instead of du…
luvkapur Aug 20, 2026
64068e8
fix(bundler): keep one broken env from taking every preview server do…
luvkapur Aug 21, 2026
863a20e
perf(preview): bound the number of live preview realms
luvkapur Aug 21, 2026
eda22f1
perf(preview): render a workspace grid's previews in one realm per env
luvkapur Aug 21, 2026
0cae777
fix(preview): make batched previews actually render
luvkapur Aug 21, 2026
7b1f39a
fix(preview): give each batched preview its own document
luvkapur Aug 21, 2026
aef1420
fix(preview): batched previews cannot isolate portals - document the …
luvkapur Aug 21, 2026
d92da38
perf(preview): recycle a pool of preview iframes instead of one per card
luvkapur Aug 21, 2026
c114421
fix(preview): anchor pooled previews in the scroller, and stop them i…
luvkapur Aug 21, 2026
001f66b
fix(preview): place pooled previews well ahead of the viewport
luvkapur Aug 21, 2026
5e47ea0
fix(preview): show a skeleton while a pooled preview boots, and stop …
luvkapur Aug 21, 2026
23c64e7
test(preview): ship the scrolling measurement harness
luvkapur Aug 21, 2026
eb099c6
fix(graphql): unify the apollo cache type across duplicated package i…
luvkapur Aug 21, 2026
165d150
fix(version-dropdown): do not mount the version menu until the user s…
luvkapur Aug 22, 2026
910f63b
fix(ui): rotate the dev service worker cache token per build
luvkapur Aug 22, 2026
73163f5
perf(preview): boot grid thumbnail realms without the docs app
luvkapur Aug 24, 2026
64fc171
perf(preview): open the pool gates once the first preview has rendered
luvkapur Aug 24, 2026
b6e995a
perf(preview): keep the docs template out of grid realm downloads
luvkapur Aug 24, 2026
7e600e2
fix(preview): single-preview pages broke two ways after the async-tem…
luvkapur Aug 24, 2026
d72251c
Make preview HMR hot end to end for compiled component packages
luvkapur Aug 25, 2026
047548e
Make the link file's hot boundary a self-accept instead of a dependen…
luvkapur Aug 25, 2026
ea8f200
Default to pooled previews; drop the watch-backend delivery fallback
luvkapur Aug 25, 2026
7de3a2c
Keep HMR working across dev-server rebuilds, restarts, and env fan-out
luvkapur Aug 25, 2026
4cbe2fe
fix(workspace): include labels in the componentChanged subscription f…
luvkapur Aug 26, 2026
958c7f8
Address review findings across preview pooling, workspace merge, and …
luvkapur Aug 26, 2026
bbd74dc
Defer pooled-preview teardown past transient re-registrations
luvkapur Aug 26, 2026
4d74e21
Make hydration-slot ownership explicit
luvkapur Aug 26, 2026
87d05ce
Merge branch 'master' into perf/runtime-optimizations
luvkapur Aug 26, 2026
027c348
Address second-round review findings
luvkapur Aug 26, 2026
3dc4986
fix: restore api-reference pins to their published versions
luvkapur Aug 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions components/hooks/use-lane-components/use-lane-components.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useDataQuery } from '@teambit/ui-foundation.ui.hooks.use-data-query';
import { gql } from '@apollo/client';
import { gql, useQuery } from '@apollo/client';
import { ComponentID, ComponentModel, componentOverviewFields } from '@teambit/component';
import type { LaneId } from '@teambit/lane-id';
import { ComponentDescriptor } from '@teambit/component-descriptor';
Expand Down Expand Up @@ -46,19 +45,32 @@ export type UseLaneComponentsResult = {
loading?: boolean;
};

export function useLaneComponents(laneId?: LaneId): UseLaneComponentsResult {
export type UseLaneComponentsOptions = {
skip?: boolean;
};

export function useLaneComponents(laneId?: LaneId, options: UseLaneComponentsOptions = {}): UseLaneComponentsResult {
const shouldSkip = options.skip || !laneId;
// @ts-ignore - remove once graphql versions are aligned (see #8753)
const { data, loading } = useDataQuery(GET_LANE_COMPONENTS, {
variables: { ids: [laneId?.toString()], skipList: laneId?.isDefault() },
skip: !laneId,
const { data, loading } = useQuery(GET_LANE_COMPONENTS, {
variables: { ids: [laneId?.toString()], skipList: laneId?.isDefault() ?? false },
skip: shouldSkip,
fetchPolicy: 'cache-and-network',
nextFetchPolicy: 'cache-first',
returnPartialData: true,
Comment thread
luvkapur marked this conversation as resolved.
});

const rawComps = data?.lanes.list && data?.lanes.list.length > 0 ? data?.lanes.list[0] : data?.lanes.default;

const components = rawComps?.components?.map((component) => {
const componentModel = ComponentModel.from({ ...component, host: data.getHost.id });
return componentModel;
});
// returnPartialData can deliver lane components before the separate getHost
// field arrives - treat that as still loading instead of dereferencing undefined
const hostId = data?.getHost?.id;
const components = hostId
? rawComps?.components?.map((component) => {
const componentModel = ComponentModel.from({ ...component, host: hostId });
return componentModel;
})
: undefined;

const componentDescriptors: ComponentDescriptor[] = compact(
rawComps?.components?.map((rawComponent) => {
Expand Down
24 changes: 17 additions & 7 deletions components/hooks/use-lanes/use-lanes.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { useMemo, useCallback } from 'react';
import { useDataQuery } from '@teambit/ui-foundation.ui.hooks.use-data-query';
import type { LaneModel, LanesQuery } from '@teambit/lanes.ui.models.lanes-model';
import { LanesModel } from '@teambit/lanes.ui.models.lanes-model';
import { gql } from '@apollo/client';
import { gql, useQuery } from '@apollo/client';
import type { LaneId } from '@teambit/lane-id';
import { isEqual } from 'lodash';
import type { LanesContextModel } from './lanes-context';
import { useLanesContext } from './lanes-context';

type LanesQueryCompat = Omit<LanesQuery, 'lanes'> & {
lanes?: Omit<NonNullable<LanesQuery['lanes']>, 'viewedLane'> & {
viewedLane?: NonNullable<LanesQuery['lanes']>['list'];
};
};

const GET_LANES = gql`
query Lanes(
$extensionId: String
Expand Down Expand Up @@ -173,7 +178,7 @@ const useRootLanes: UseRootLanes = (viewedLaneId, skip, options = {}, scope) =>
const { ids, offset, limit, sort } = options;

// @ts-ignore - remove once graphql versions are aligned (see #8753)
const { data, fetchMore, loading } = useDataQuery<LanesQuery>(GET_LANES, {
const { data, fetchMore, loading } = useQuery<LanesQueryCompat>(GET_LANES, {
variables: {
laneIds: ids,
offset,
Expand All @@ -185,11 +190,14 @@ const useRootLanes: UseRootLanes = (viewedLaneId, skip, options = {}, scope) =>
skip,
errorPolicy: 'all',
fetchPolicy: 'cache-and-network',
nextFetchPolicy: 'cache-first',
returnPartialData: true,
notifyOnNetworkStatusChange: false,
});

const lanesModel = useMemo(() => {
if (!loading && !!data) {
const newLanesModel = LanesModel.from({ data, scope });
const newLanesModel = LanesModel.from({ data: data as unknown as LanesQuery, scope });
return newLanesModel;
}
return undefined;
Expand Down Expand Up @@ -219,7 +227,7 @@ const useRootLanes: UseRootLanes = (viewedLaneId, skip, options = {}, scope) =>
const loadingMore = networkStatus === 3;

if (!loadingMore && moreData.lanes) {
const newLanesModel = LanesModel.from({ data: moreData });
const newLanesModel = LanesModel.from({ data: moreData as unknown as LanesQuery });
return {
lanesModel: newLanesModel,
loading: loadingMore,
Expand Down Expand Up @@ -262,7 +270,7 @@ const useRootLanes: UseRootLanes = (viewedLaneId, skip, options = {}, scope) =>

export const useSearchLanes: SearchLanes = (search, skip) => {
// @ts-ignore - remove once graphql versions are aligned (see #8753)
const { data: searchData, loading: loadingSearch } = useDataQuery<LanesQuery>(GET_LANES, {
const { data: searchData, loading: loadingSearch } = useQuery<LanesQueryCompat>(GET_LANES, {
variables: {
search,
skipViewedLane: true,
Expand All @@ -280,7 +288,9 @@ export const useSearchLanes: SearchLanes = (search, skip) => {
loading: loadingSearch,
lanesModel:
searchData &&
LanesModel.from({ data: { lanes: { ...searchData.lanes, current: undefined, default: undefined } } }),
LanesModel.from({
data: { lanes: { ...searchData.lanes, current: undefined, default: undefined } } as unknown as LanesQuery,
}),
};
};

Expand Down
3 changes: 2 additions & 1 deletion components/ui/component-drawer/component-drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ function ComponentsDrawerContent({

const emptyDrawer = <span className={classNames(mutedItalic, ellipsis, styles.emptyDrawer)}>{emptyMessage}</span>;

const loading = loadingComponents || loadingLanesModel || !lanes || !components;
const hasComponents = Array.isArray(components);
const loading = loadingComponents || !hasComponents || (components.length === 0 && loadingLanesModel);

if (loading) return <ComponentTreeLoader />;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import classNames from 'classnames';
import { ComponentID } from '@teambit/component-id';
import type { ComponentModel } from '@teambit/component';
import { ComponentUrl } from '@teambit/component.modules.component-url';
import React, { useCallback, useContext, useEffect, useState } from 'react';
import React, { useCallback, useContext } from 'react';
import { Tooltip } from '@teambit/design.ui.tooltip';
import { TreeContext } from '@teambit/base-ui.graph.tree.tree-context';
import { indentClass } from '@teambit/base-ui.graph.tree.indent';
Expand All @@ -33,14 +33,9 @@ export function ComponentView(props: ComponentViewProps) {
const { onSelect } = useContext(TreeContext);
const lanesContextModel = useContext(LanesContext);
const lanesModel = lanesContextModel?.lanesModel;
const [mounted, setMounted] = useState(false);

useEffect(() => {
setMounted(true);
return () => {
setMounted(false);
};
}, []);
const hasWindow = typeof window !== 'undefined';
const locationOrigin = hasWindow ? window.location.origin : undefined;
const isLocalhost = hasWindow ? window.location.host.startsWith('localhost') : false;

const handleClick = useCallback(
(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
Expand All @@ -65,11 +60,11 @@ export function ComponentView(props: ComponentViewProps) {
? undefined
: (isEnvOnCurrentLane &&
lanesModel?.viewedLane?.id &&
mounted &&
`${window.location.origin}${LanesModel.getLaneComponentUrl(envId, lanesModel.viewedLane?.id, undefined, lanesModel.viewedLane)}`) ||
locationOrigin &&
`${locationOrigin}${LanesModel.getLaneComponentUrl(envId, lanesModel.viewedLane?.id, undefined, lanesModel.viewedLane)}`) ||
ComponentUrl.toUrl(envId, {
includeVersion: true,
useLocationOrigin: mounted ? window.location.host.startsWith('localhost') : false,
useLocationOrigin: isLocalhost,
});

const envTooltip = envId ? (
Expand Down Expand Up @@ -99,7 +94,7 @@ export function ComponentView(props: ComponentViewProps) {
const viewingMainCompOnLane = React.useMemo(() => {
if (isMissingCompOrEnvId) return false;
return (
!component.status?.isNew &&
component.status?.isNew === false &&
!lanesModel?.viewedLane?.id.isDefault() &&
lanesModel?.isComponentOnMainButNotOnLane(component.id as any, undefined, lanesModel?.viewedLane?.id)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function SimpleVersion({
<Ellipsis className={classNames(styles.versionName)} onClick={rest.onClick}>
{formattedVersion}
</Ellipsis>
{hasMoreVersions && <Icon of="fat-arrow-down" onClick={rest.onClick} />}
{hasMoreVersions !== false && <Icon of="fat-arrow-down" onClick={rest.onClick} />}
</div>
);
}
Expand Down Expand Up @@ -95,7 +95,7 @@ export function DetailedVersion({
<Ellipsis className={styles.versionTimestamp} onClick={rest.onClick}>
<TimeAgo date={timestamp} onClick={rest.onClick} />
</Ellipsis>
{hasMoreVersions && <Icon of="fat-arrow-down" onClick={rest.onClick} />}
{hasMoreVersions !== false && <Icon of="fat-arrow-down" onClick={rest.onClick} />}
</div>
);
}
Expand Down
85 changes: 64 additions & 21 deletions components/ui/version-dropdown/version-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type UseComponentDropdownVersionsResult = {
};
export type UseComponentDropdownVersionsProps = {
skip?: boolean;
fetchLogs?: boolean;
};
export type UseComponentDropdownVersions = (
props?: UseComponentDropdownVersionsProps
Expand Down Expand Up @@ -79,8 +80,13 @@ function _VersionDropdown({
...rest
}: VersionDropdownProps) {
const [key, setKey] = useState(0);
const singleVersion = !hasMoreVersions;
const singleVersion = hasMoreVersions === false;
const [open, setOpen] = useState(false);
const [prefetchRequested, setPrefetchRequested] = useState(false);

const requestPrefetch = useCallback(() => {
setPrefetchRequested(true);
}, []);

useEffect(() => {
if (loading && open) {
Expand All @@ -89,6 +95,7 @@ function _VersionDropdown({
}, [loading]);

const handlePlaceholderClicked = (e: React.MouseEvent<HTMLDivElement>) => {
requestPrefetch();
if (loading) return;
if (e.target === e.currentTarget) {
setOpen((o) => !o);
Expand Down Expand Up @@ -128,36 +135,62 @@ function _VersionDropdown({
}

return (
<div {...rest} className={classNames(styles.versionDropdown, className)}>
<div
{...rest}
className={classNames(styles.versionDropdown, className)}
onMouseEnter={requestPrefetch}
onFocus={requestPrefetch}
onPointerDown={requestPrefetch}
onTouchStart={requestPrefetch}
>
<Dropdown
className={classNames(styles.dropdown, dropdownClassName)}
dropClass={classNames(styles.menu, menuClassName)}
open={open}
onClick={handlePlaceholderClicked}
onClickOutside={() => setOpen(false)}
onChange={(_e, _open) => _open && setKey((x) => x + 1)} // to reset menu to initial state when toggling
onChange={(_e, _open) => {
if (_open) {
requestPrefetch();
setKey((x) => x + 1);
}
}} // to reset menu to initial state when toggling
PlaceholderComponent={({ children, ...other }) => (
<div {...other} className={placeholderClassName} onClick={handlePlaceholderClicked}>
<div
{...other}
className={placeholderClassName}
onClick={handlePlaceholderClicked}
onMouseEnter={requestPrefetch}
onFocus={requestPrefetch}
onPointerDown={requestPrefetch}
onTouchStart={requestPrefetch}
>
{children}
</div>
)}
placeholder={PlaceholderComponent}
>
<VersionMenu
className={menuClassName}
key={key}
currentVersion={currentVersion}
latestVersion={latestVersion}
localVersion={localVersion}
overrideVersionHref={overrideVersionHref}
showVersionDetails={showVersionDetails}
currentLane={currentLane}
getActiveTabIndex={getActiveTabIndex}
lanes={lanes}
useVersions={useComponentVersions}
onVersionClicked={() => setOpen(false)}
open={open}
/>
{/* the menu is not mounted until the user shows intent (hover/focus/press/open). versions
load lazily, and mounting the menu eagerly would render its rows into the dom for every
card in a workspace grid before anyone asked for them. */}
{prefetchRequested || open ? (
<VersionMenu
className={menuClassName}
key={key}
currentVersion={currentVersion}
latestVersion={latestVersion}
localVersion={localVersion}
overrideVersionHref={overrideVersionHref}
showVersionDetails={showVersionDetails}
currentLane={currentLane}
getActiveTabIndex={getActiveTabIndex}
lanes={lanes}
useVersions={useComponentVersions}
shouldPrefetchVersions={prefetchRequested}
onVersionClicked={() => setOpen(false)}
open={open}
/>
) : null}
</Dropdown>
</div>
);
Expand All @@ -175,6 +208,7 @@ type VersionMenuProps = {
loading?: boolean;
getActiveTabIndex?: GetActiveTabIndex;
open?: boolean;
shouldPrefetchVersions?: boolean;
onVersionClicked?: () => void;
} & HTMLAttributes<HTMLDivElement>;

Expand Down Expand Up @@ -211,11 +245,20 @@ function _VersionMenu({
getActiveTabIndex = defaultActiveTabIndex,
loading: loadingFromProps,
open,
shouldPrefetchVersions,
onVersionClicked,
...rest
}: VersionMenuProps) {
const { snaps, tags, loading: loadingVersions } = useVersions?.() || {};
const loading = loadingFromProps || loadingVersions;
const shouldFetchVersions = !!(open || shouldPrefetchVersions);
const {
snaps,
tags,
loading: loadingVersions,
} = useVersions?.({
skip: !shouldFetchVersions,
fetchLogs: shouldFetchVersions,
}) || {};
const loading = loadingFromProps || (shouldFetchVersions && loadingVersions);

const tabs = useMemo(
() =>
Expand Down
4 changes: 0 additions & 4 deletions components/ui/version-dropdown_1/index.ts

This file was deleted.

2 changes: 0 additions & 2 deletions components/ui/version-dropdown_1/lane-info/index.ts

This file was deleted.

27 changes: 0 additions & 27 deletions components/ui/version-dropdown_1/lane-info/lane-info.module.scss

This file was deleted.

Loading