diff --git a/src/application-starter/api/create-worker.ts b/src/application-starter/api/create-worker.ts index 8bdd95275..c53017fe0 100644 --- a/src/application-starter/api/create-worker.ts +++ b/src/application-starter/api/create-worker.ts @@ -123,9 +123,7 @@ type GeneratedAddOnMetadata = Omit< | 'partner' | 'packageAdditions' > & - GeneratedAddOnFields & { - packageAdditions?: GeneratedAddOnPackageAdditions - } + GeneratedAddOnFields type GeneratedWorkerAddOnManifestModule = GeneratedWorkerManifestModule & { addOn: GeneratedAddOnManifest @@ -365,7 +363,6 @@ function getAddOnMetadata( envVars: getAddOnEnvVars(addOn.envVars), options: getAddOnOptions(addOn.options), partner: getAddOnPartner(addOn.partner), - packageAdditions: getPackageAdditions(addOn.packageAdditions), } } diff --git a/src/application-starter/api/feature-artifacts.ts b/src/application-starter/api/feature-artifacts.ts index 905c22c35..335f020b0 100644 --- a/src/application-starter/api/feature-artifacts.ts +++ b/src/application-starter/api/feature-artifacts.ts @@ -1,4 +1,3 @@ -import { type AddOnCompiled } from './compile' import { compileWithAttributionHandler, type ProjectDefinition } from './compile' import { type FrameworkId } from './config' @@ -8,7 +7,6 @@ export interface FeatureArtifactsRequest { framework?: FrameworkId tailwind?: boolean featureOptions?: Record> - customIntegrations?: Array } export interface FeatureArtifact { @@ -44,7 +42,6 @@ export async function featureArtifactsHandler( tailwind, features: request.features, featureOptions: request.featureOptions ?? {}, - customIntegrations: request.customIntegrations, } const output = await compileWithAttributionHandler(definition) diff --git a/src/auth/oauth.server.ts b/src/auth/oauth.server.ts index f980169b2..418eb886c 100644 --- a/src/auth/oauth.server.ts +++ b/src/auth/oauth.server.ts @@ -269,23 +269,13 @@ export async function exchangeGitHubCode( code: string, clientId: string, clientSecret: string, - redirectUri?: string, ): Promise { - const body: { - client_id: string - client_secret: string - code: string - redirect_uri?: string - } = { + const body = { client_id: clientId, client_secret: clientSecret, code, } - if (redirectUri) { - body.redirect_uri = redirectUri - } - const tokenResponse = await fetch( 'https://github.com/login/oauth/access_token', { diff --git a/src/auth/repositories.server.ts b/src/auth/repositories.server.ts index 74a4e6109..9644644d5 100644 --- a/src/auth/repositories.server.ts +++ b/src/auth/repositories.server.ts @@ -55,7 +55,6 @@ export class DrizzleUserRepository implements IUserRepository { oauthImage?: string displayUsername?: string capabilities?: Capability[] - signupSources?: SignupSource[] }): Promise { const [newUser] = await db .insert(users) @@ -66,7 +65,7 @@ export class DrizzleUserRepository implements IUserRepository { oauthImage: data.oauthImage, displayUsername: data.displayUsername, capabilities: data.capabilities || [], - signupSources: data.signupSources ?? [], + signupSources: [], }) .returning() diff --git a/src/components/ApplicationStarter.tsx b/src/components/ApplicationStarter.tsx index edf42e9ef..1f24f36f0 100644 --- a/src/components/ApplicationStarter.tsx +++ b/src/components/ApplicationStarter.tsx @@ -43,21 +43,12 @@ export interface ApplicationStarterProps { applicationStarterIntegration?: ApplicationStarterIntegration className?: string context: ApplicationStarterContext - footerContent?: React.ReactNode enableHotkeys?: boolean - forceRouterOnly?: boolean - formId?: string headerAction?: React.ReactNode - mode?: 'compact' | 'full' onDirtyStateChange?: (dirty: boolean) => void onResolvedResult?: (result: ApplicationStarterResult | null) => void - primaryActionLabel?: string revealOptionsImmediately?: boolean - secondaryActionLabel?: string - showCliExportActions?: boolean showPromptPreview?: boolean - suggestionContext?: ApplicationStarterContext - submitButton?: React.ReactNode title?: React.ReactNode tone?: StarterTone } @@ -139,21 +130,12 @@ export function ApplicationStarter({ applicationStarterIntegration, className, context, - footerContent, enableHotkeys = false, - forceRouterOnly = false, - formId, headerAction, - mode = 'full', onDirtyStateChange, onResolvedResult, - primaryActionLabel = 'Copy Prompt', revealOptionsImmediately = false, - secondaryActionLabel = 'Build with Netlify', - showCliExportActions = true, showPromptPreview = true, - suggestionContext, - submitButton, title = 'What would you like to build?', tone = 'cyan', }: ApplicationStarterProps) { @@ -200,16 +182,15 @@ export function ApplicationStarter({ } = useApplicationStarter({ applicationStarterIntegration, context, - forceRouterOnly, - mode, + forceRouterOnly: false, + mode: 'full', onDirtyStateChange, onResolvedResult, revealOptionsImmediately, - suggestionContext, }) const palette = toneClasses[tone] - const compact = mode === 'compact' + const compact = false const isHomeStarter = context === 'home' const [pendingHostingDeployPartner, setPendingHostingDeployPartner] = React.useState(null) @@ -378,13 +359,6 @@ export function ApplicationStarter({ : undefined, [result?.prompt, selectedPromptDeployProvider], ) - const netlifyStartHref = React.useMemo( - () => - resultPrompt - ? buildStarterPromptDeployUrl('netlify', resultPrompt) - : undefined, - [resultPrompt], - ) const codexStartHref = React.useMemo( () => (resultPrompt ? buildCodexStartUrl(resultPrompt) : undefined), [resultPrompt], @@ -458,7 +432,7 @@ export function ApplicationStarter({ ? loadingPhrase : isPromptCopied ? 'Copied' - : primaryActionLabel} + : 'Copy Prompt'} ) const renderCopyCliCommandButton = () => ( @@ -494,9 +468,7 @@ export function ApplicationStarter({ label, iconOnly = false, onTrack, - rel = 'noopener noreferrer', size, - target = '_blank', variant = 'primary', }: { action: StarterTransientAction @@ -506,13 +478,13 @@ export function ApplicationStarter({ label: string iconOnly?: boolean onTrack: () => void - rel?: string size: 'xs' | 'sm' - target?: string variant?: 'primary' | 'secondary' }) => { const disabled = !canUseFinalActions || !href || transientAction === action const waitingForHref = !href + const opensInCurrentTab = + href?.startsWith('codex:') || href?.startsWith('cursor:') const button = ( - - - {renderActionAnchor({ - action: 'download', - className: - 'text-text-secondary hover:text-text-primary', - href: downloadHref, - icon: ( - + ) : ( + - ), - iconOnly: true, - label: 'Download ZIP', - onTrack: () => { - trackActivation({ - action: 'download', - surface: 'result_panel', - }) - }, - size: 'xs', - variant: 'secondary', - })} - - ) : null} + )} + + + + {renderActionAnchor({ + action: 'download', + className: + 'text-text-secondary hover:text-text-primary', + href: downloadHref, + icon: ( + + ), + iconOnly: true, + label: 'Download ZIP', + onTrack: () => { + trackActivation({ + action: 'download', + surface: 'result_panel', + }) + }, + size: 'xs', + variant: 'secondary', + })} + ) : null} @@ -1538,8 +1459,6 @@ export function ApplicationStarter({ )} - - {compact ? submitButton : null} diff --git a/src/components/AvatarCropModal.tsx b/src/components/AvatarCropModal.tsx index 47772969c..4b543ca5a 100644 --- a/src/components/AvatarCropModal.tsx +++ b/src/components/AvatarCropModal.tsx @@ -12,11 +12,8 @@ interface AvatarCropModalProps { onCropComplete: (croppedBlob: Blob) => void } -async function getCroppedImg( - imageSrc: string, - pixelCrop: Area, - outputSize: number = 256, -): Promise { +async function getCroppedImg(imageSrc: string, pixelCrop: Area): Promise { + const outputSize = 256 const image = new Image() image.crossOrigin = 'anonymous' diff --git a/src/components/BlogCard.tsx b/src/components/BlogCard.tsx index 688ca4302..55b311703 100644 --- a/src/components/BlogCard.tsx +++ b/src/components/BlogCard.tsx @@ -16,16 +16,9 @@ export type { BlogCardPost } from '~/utils/blog-format' type BlogCardProps = { post: BlogCardPost showLibraryBadges?: boolean - /** Hero treatment: side-by-side on wider screens with larger type. Used for - * the single latest post at the top of the blog index. */ - featured?: boolean } -export function BlogCard({ - post, - showLibraryBadges = true, - featured = false, -}: BlogCardProps) { +export function BlogCard({ post, showLibraryBadges = true }: BlogCardProps) { const { slug, title, @@ -40,12 +33,9 @@ export function BlogCard({ const blogLibraries = showLibraryBadges ? getBlogLibraries(library) : [] const cardClassName = twMerge( 'relative flex flex-col justify-between overflow-hidden transition-all hover:shadow-sm hover:border-blue-500', - // Featured: image and copy sit side-by-side once there's room. - featured && 'md:flex-row md:justify-start', ) const mediaClassName = twMerge( 'aspect-video w-full overflow-hidden bg-background-subtle', - featured && 'md:aspect-auto md:w-1/2', ) const content = ( @@ -69,10 +59,10 @@ export function BlogCard({ fit: 'cover', format: 'auto', quality: 80, - width: featured ? 1200 : 800, + width: 800, })} alt="" - loading={featured ? 'eager' : 'lazy'} + loading="lazy" decoding="async" className="w-full h-full object-cover" /> @@ -84,21 +74,9 @@ export function BlogCard({ className={mediaClassName} /> )} -
+
-

- {title} -

+

{title}

by {formatAuthors(authors)} {published ? ( @@ -109,12 +87,7 @@ export function BlogCard({ ) : null}
{excerpt ? ( -

+

{excerpt}

) : null} diff --git a/src/components/ButtonGroup.tsx b/src/components/ButtonGroup.tsx index c22aa36f6..c63e42678 100644 --- a/src/components/ButtonGroup.tsx +++ b/src/components/ButtonGroup.tsx @@ -59,8 +59,6 @@ type SegmentedControlProps = { /** Labels the group for assistive tech (the control has no visible label). */ 'aria-label': string size?: SegmentSize - /** Stretch to fill the container, splitting width evenly across options. */ - fullWidth?: boolean className?: string } @@ -79,18 +77,13 @@ export function SegmentedControl({ onValueChange, 'aria-label': ariaLabel, size = 'md', - fullWidth = false, className, }: SegmentedControlProps) { return (
{options.map((option) => { const selected = value === option.value @@ -102,11 +95,7 @@ export function SegmentedControl({ aria-label={option['aria-label']} disabled={option.disabled} onClick={() => onValueChange(option.value)} - className={segmentClasses( - size, - selected, - fullWidth ? 'flex-1' : undefined, - )} + className={segmentClasses(size, selected)} > {option.label} diff --git a/src/components/CopyPageDropdown.tsx b/src/components/CopyPageDropdown.tsx index 2aa3eec67..e38f2067e 100644 --- a/src/components/CopyPageDropdown.tsx +++ b/src/components/CopyPageDropdown.tsx @@ -114,7 +114,7 @@ export function CopyPageDropdown({ currentFramework, content: rawContent, label = 'Copy page', -}: CopyPageDropdownProps = {}) { +}: CopyPageDropdownProps) { const [open, setOpen] = React.useState(false) const copied = useTemporaryFlag() const { notify } = useToast() diff --git a/src/components/Doc.tsx b/src/components/Doc.tsx index a58a2df54..2d21c9d02 100644 --- a/src/components/Doc.tsx +++ b/src/components/Doc.tsx @@ -35,8 +35,6 @@ type DocProps = { pagePath?: string // Breadcrumb props (optional) config?: ConfigSchema - // Footer content rendered after markdown - footer?: React.ReactNode // Optional framework to use (overrides URL and local storage) framework?: string } @@ -55,7 +53,6 @@ export function Doc({ libraryVersion, pagePath, config, - footer, framework: frameworkProp, }: DocProps) { const { groups: startHostingPartnerGroups } = useStartHostingPartners() @@ -194,7 +191,7 @@ export function Doc({ ) : null } /> - {footer ?? } +
diff --git a/src/components/Dropdown.tsx b/src/components/Dropdown.tsx index 2754c6a1e..f62d9e823 100644 --- a/src/components/Dropdown.tsx +++ b/src/components/Dropdown.tsx @@ -20,10 +20,8 @@ type DropdownContentProps = { className?: string align?: 'start' | 'center' | 'end' sideOffset?: number - portal?: boolean onFocus?: React.FocusEventHandler onPointerEnter?: React.PointerEventHandler - onPointerLeave?: React.PointerEventHandler } type DropdownItemProps = { @@ -67,35 +65,28 @@ export function DropdownContent({ className, align = 'end', sideOffset = 6, - portal = true, onFocus, onPointerEnter, - onPointerLeave, }: DropdownContentProps) { - const content = ( - - {children} - + return ( + + + {children} + + ) - - if (!portal) { - return content - } - - return {content} } export function DropdownItem({ diff --git a/src/components/FilterComponents.tsx b/src/components/FilterComponents.tsx index 936ca3442..a164aa7a8 100644 --- a/src/components/FilterComponents.tsx +++ b/src/components/FilterComponents.tsx @@ -154,27 +154,16 @@ export function TopBarFilter({ interface FilterChipProps { label: string onRemove: () => void - onClick?: () => void className?: string } -export function FilterChip({ - label, - onRemove, - onClick, - className, -}: FilterChipProps) { +export function FilterChip({ label, onRemove, className }: FilterChipProps) { return ( e.key === 'Enter' && onClick() : undefined} - role={onClick ? 'button' : undefined} - tabIndex={onClick ? 0 : undefined} > {label} - {showNote ? ( -

- We'll use your TanStack login email. -

- ) : null} +

+ We'll use your TanStack login email. +

) } diff --git a/src/components/OpenSourceStats.tsx b/src/components/OpenSourceStats.tsx index 85e3d6835..d766991c2 100644 --- a/src/components/OpenSourceStats.tsx +++ b/src/components/OpenSourceStats.tsx @@ -4,20 +4,9 @@ import { DownloadSimpleIcon, StarIcon, } from '@phosphor-icons/react' -import { type Library } from '~/libraries' -import { - homepageNpmStatsSummaryQuery, - ossStatsQuery, - recentDownloadsQuery, -} from '~/queries/stats' +import { homepageNpmStatsSummaryQuery, ossStatsQuery } from '~/queries/stats' import { useNpmDownloadCounter } from '~/hooks/useNpmDownloadCounter' -import { - StatsSection, - type StatItem, - type StatsLayout, - type StatsPage, -} from '~/components/ds/ui' -import { tanStackTotalNpmStatsLibrary } from '~/utils/tanstack-npm-stats' +import { StatsSection, type StatItem, type StatsPage } from '~/components/ds/ui' /** Compact count with a single-letter magnitude, e.g. 2_340_000_000 → "2.3B". */ function formatCompact(value: number) { @@ -39,40 +28,19 @@ function isValidMetric(value: number | undefined | null): boolean { export default function OssStats({ className, - layout = 'landscape', - library, page = 'home', }: { className?: string - layout?: StatsLayout - library?: Library page?: StatsPage }) { - const { data: stats, isLoading } = useQuery(ossStatsQuery({ library })) + const { data: stats, isLoading } = useQuery(ossStatsQuery()) const { data: homepageNpmSummary, isLoading: isLoadingHomepageNpmSummary } = - useQuery({ - ...homepageNpmStatsSummaryQuery(), - enabled: !library, - }) - const { data: recentDownloads, isLoading: isLoadingRecentDownloads } = - useQuery({ - ...recentDownloadsQuery({ - library: library ?? tanStackTotalNpmStatsLibrary, - }), - enabled: Boolean(library), - }) + useQuery(homepageNpmStatsSummaryQuery()) - const totalNpmStats = stats?.npm - const npmDownloads = library - ? (totalNpmStats?.totalDownloads ?? 0) - : (homepageNpmSummary?.totalDownloads ?? 0) + const npmDownloads = homepageNpmSummary?.totalDownloads ?? 0 const starCount = stats?.github?.starCount ?? 0 - const weeklyDownloads = library - ? (recentDownloads?.weeklyDownloads ?? 0) - : (homepageNpmSummary?.weeklyDownloads ?? 0) - const weeklyRatePerDay = library - ? undefined - : homepageNpmSummary?.weeklyRatePerDay + const weeklyDownloads = homepageNpmSummary?.weeklyDownloads ?? 0 + const weeklyRatePerDay = homepageNpmSummary?.weeklyRatePerDay // Live-ticking weekly counter — writes into the value node after mount. const weeklyRef = useNpmDownloadCounter({ @@ -81,20 +49,14 @@ export default function OssStats({ }) const hasNpmDownloads = - !(library ? isLoading : isLoadingHomepageNpmSummary) && - isValidMetric(npmDownloads) + !isLoadingHomepageNpmSummary && isValidMetric(npmDownloads) const hasStarCount = !isLoading && isValidMetric(starCount) const hasWeeklyDownloads = - !(library ? isLoadingRecentDownloads : isLoadingHomepageNpmSummary) && - isValidMetric(weeklyDownloads) + !isLoadingHomepageNpmSummary && isValidMetric(weeklyDownloads) const loading = isLoading || !stats - const npmLoading = library - ? isLoading || !totalNpmStats - : isLoadingHomepageNpmSummary || !homepageNpmSummary - const weeklyLoading = library - ? isLoadingRecentDownloads || !recentDownloads - : isLoadingHomepageNpmSummary || !homepageNpmSummary + const npmLoading = isLoadingHomepageNpmSummary || !homepageNpmSummary + const weeklyLoading = isLoadingHomepageNpmSummary || !homepageNpmSummary const items: Array = [] @@ -139,7 +101,7 @@ export default function OssStats({ ) diff --git a/src/components/Panel.tsx b/src/components/Panel.tsx index 4ca8691ad..91e815818 100644 --- a/src/components/Panel.tsx +++ b/src/components/Panel.tsx @@ -3,14 +3,12 @@ import { twMerge } from 'tailwind-merge' type PanelRenderProps = { open: boolean - orientation: 'horizontal' | 'vertical' toggle: () => void } type PanelProps = { open?: boolean defaultOpen?: boolean - orientation?: 'horizontal' | 'vertical' onOpenChange?: (open: boolean) => void children: React.ReactNode | ((props: PanelRenderProps) => React.ReactNode) className?: string @@ -37,7 +35,6 @@ function usePanel() { export function Panel({ open: controlledOpen, defaultOpen = false, - orientation = 'vertical', onOpenChange, children, className, @@ -59,14 +56,11 @@ export function Panel({ } }, [isControlled, open, onOpenChange]) - const value = React.useMemo( - () => ({ open, orientation, toggle }), - [open, orientation, toggle], - ) + const value = React.useMemo(() => ({ open, toggle }), [open, toggle]) return ( -
+
{typeof children === 'function' ? children(value) : children}
@@ -107,8 +101,7 @@ export function PanelTrigger({ export const PanelContent = React.forwardRef( function PanelContent({ children, className, ...props }, ref) { - const { open, orientation } = usePanel() - const horizontal = orientation === 'horizontal' + const { open } = usePanel() return (
( inert={open ? undefined : true} className={twMerge( 'grid overflow-hidden duration-200 [transition-timing-function:cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none', - horizontal - ? 'transition-[grid-template-columns]' - : 'transition-[grid-template-rows]', - horizontal - ? open - ? 'grid-cols-[1fr]' - : 'grid-cols-[0fr]' - : open - ? 'grid-rows-[1fr]' - : 'grid-rows-[0fr]', + 'transition-[grid-template-rows]', + open ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]', className, )} > -
- {children} -
+
{children}
) }, diff --git a/src/components/PartnersSponsorsSection.tsx b/src/components/PartnersSponsorsSection.tsx index 34dbae7ce..bad805537 100644 --- a/src/components/PartnersSponsorsSection.tsx +++ b/src/components/PartnersSponsorsSection.tsx @@ -12,7 +12,6 @@ type PartnersSponsorsContentProps = { } type PartnersSponsorsSectionProps = { - analyticsPlacement?: PartnerPlacement className?: string } @@ -106,7 +105,6 @@ function PartnersSponsorsSkeleton() { } export function PartnersSponsorsSection({ - analyticsPlacement = 'library_grid', className = '', }: PartnersSponsorsSectionProps) { return ( @@ -115,7 +113,7 @@ export function PartnersSponsorsSection({ when={visible({ rootMargin: '25%' })} fallback={} > - + ) diff --git a/src/components/RecentPostsWidget.tsx b/src/components/RecentPostsWidget.tsx index 9380fb323..66fbf9583 100644 --- a/src/components/RecentPostsWidget.tsx +++ b/src/components/RecentPostsWidget.tsx @@ -4,8 +4,7 @@ import { fetchRecentPosts, type RecentPost } from '~/utils/blog.functions' import { formatPublishedDate } from '~/utils/blog-format' type RecentPostsWidgetProps = { - posts?: ReadonlyArray - /** Set to false to skip the client fetch when the widget is rendered but not visible (e.g. hidden below a CSS breakpoint). Ignored when `posts` is provided. */ + /** Set to false to skip the client fetch when the widget is rendered but not visible (e.g. hidden below a CSS breakpoint). */ enabled?: boolean } @@ -88,18 +87,15 @@ function RecentPostsSkeleton() { ) } -export function RecentPostsWidget({ - posts, - enabled = true, -}: RecentPostsWidgetProps) { +export function RecentPostsWidget({ enabled = true }: RecentPostsWidgetProps) { const recentPostsQuery = useQuery({ queryKey: ['recentPosts'], queryFn: () => fetchRecentPosts(), - enabled: posts === undefined && enabled, + enabled, staleTime: 1000 * 60 * 5, }) - const visiblePosts = posts ?? recentPostsQuery.data + const visiblePosts = recentPostsQuery.data if (!visiblePosts) { return diff --git a/src/components/SearchModal.tsx b/src/components/SearchModal.tsx index ecb9a3861..6563e37c0 100644 --- a/src/components/SearchModal.tsx +++ b/src/components/SearchModal.tsx @@ -1409,7 +1409,7 @@ function MessageActionButton({ ) } -function AIMessageHeader({ action }: { action?: React.ReactNode }) { +function AIMessageHeader() { return (
@@ -1440,7 +1440,6 @@ function AIMessageHeader({ action }: { action?: React.ReactNode }) {
- {action}
) } @@ -2935,13 +2934,11 @@ function SearchResultsInChat({ surface }: { surface: SearchSurface }) { const Hit = ({ hit, commandValue, - isFocused, refinedLibrary, refinedFramework, }: { hit: AlgoliaHit commandValue?: string - isFocused?: boolean refinedLibrary: string | null refinedFramework: string | null }) => { @@ -2986,12 +2983,6 @@ const Hit = ({ const ref = React.useRef(null!) - React.useEffect(() => { - if (isFocused) { - ref.current?.scrollIntoView({ behavior: 'instant', block: 'nearest' }) - } - }, [isFocused]) - // Get library and framework info for this hit const hitLibrary = hit.library as string | undefined const hitFramework = @@ -3128,13 +3119,13 @@ const Hit = ({ href={hitUrl} className={twMerge( 'block px-4 py-2.5 focus:outline-none border-b border-gray-300 dark:border-gray-700', - isFocused ? 'bg-gray-500/20' : 'hover:bg-gray-500/10', + 'hover:bg-gray-500/10', )} onKeyDown={handleKeyDown} onFocus={() => ref.current?.focus()} onClick={handleClick} role="option" - aria-selected={isFocused} + aria-selected={false} tabIndex={-1} data-search-hit="true" ref={ref} diff --git a/src/components/ShowcaseSection.tsx b/src/components/ShowcaseSection.tsx index 6c7d6b9f4..2257491e5 100644 --- a/src/components/ShowcaseSection.tsx +++ b/src/components/ShowcaseSection.tsx @@ -19,8 +19,6 @@ interface ShowcaseSectionProps { subtitle?: string libraryId?: LibraryId limit?: number - showViewAll?: boolean - minItems?: number } export function SubmitShowcasePlaceholder({ @@ -61,8 +59,6 @@ export function ShowcaseSection({ subtitle = 'See what the community is building', libraryId, limit = 6, - showViewAll = true, - minItems = 3, }: ShowcaseSectionProps) { const queryClient = useQueryClient() const currentUser = useCurrentUser() @@ -78,7 +74,7 @@ export function ShowcaseSection({ () => data?.showcases || [], [data?.showcases], ) - const placeholdersNeeded = Math.max(0, minItems - showcases.length) + const placeholdersNeeded = Math.max(0, 3 - showcases.length) const showcaseIds = React.useMemo( () => showcases.map((s) => s.showcase.id), @@ -206,7 +202,7 @@ export function ShowcaseSection({ {isLoading ? (
- {Array.from({ length: Math.max(limit, minItems) }).map((_, i) => ( + {Array.from({ length: Math.max(limit, 3) }).map((_, i) => ( ))}
@@ -234,21 +230,19 @@ export function ShowcaseSection({
)} - {showViewAll && ( -
- - - -
- )} +
+ + + +
) } diff --git a/src/components/Squircle.tsx b/src/components/Squircle.tsx index 4c7e4f7c7..176af1b0a 100644 --- a/src/components/Squircle.tsx +++ b/src/components/Squircle.tsx @@ -25,10 +25,7 @@ function supportsNativeSquircle(): boolean { ) } -export function useSquircleFallback( - ref: React.RefObject, - smoothing = 0.6, -) { +export function useSquircleFallback(ref: React.RefObject) { React.useEffect(() => { const el = ref.current if (!el || supportsNativeSquircle()) return @@ -54,7 +51,7 @@ export function useSquircleFallback( el.style.clipPath = `path('${getSvgPath({ width, height, - cornerSmoothing: smoothing, + cornerSmoothing: 0.6, ...corners, })}')` } @@ -71,20 +68,17 @@ export function useSquircleFallback( window.removeEventListener('resize', apply) el.style.clipPath = '' } - }, [ref, smoothing]) + }, [ref]) } -type SquircleProps = React.HTMLAttributes & { - /** 0–1; 0.6 approximates the native `squircle` (superellipse(2)) curve. */ - smoothing?: number -} +type SquircleProps = React.HTMLAttributes /** A `
` that carries a squircle shape cross-browser (see useSquircleFallback). */ export const Squircle = React.forwardRef( - function Squircle({ smoothing, children, ...rest }, forwardedRef) { + function Squircle({ children, ...rest }, forwardedRef) { const localRef = React.useRef(null) React.useImperativeHandle(forwardedRef, () => localRef.current!, []) - useSquircleFallback(localRef, smoothing) + useSquircleFallback(localRef) return (
{children} diff --git a/src/components/StackBlitzEmbed.tsx b/src/components/StackBlitzEmbed.tsx index 11e54fdbc..6d412ddc4 100644 --- a/src/components/StackBlitzEmbed.tsx +++ b/src/components/StackBlitzEmbed.tsx @@ -6,9 +6,6 @@ type StackBlitzEmbedProps = { repo: string branch: string examplePath: string - file?: string - preset?: 'node' - height?: string | number title?: string } @@ -16,16 +13,12 @@ export function StackBlitzEmbed({ repo, branch, examplePath, - file, - preset = 'node', - height = '80vh', title, }: StackBlitzEmbedProps) { const isDark = useIsDark() const themeParam = isDark ? 'dark' : 'light' - const fileParam = file ? `&file=${encodeURIComponent(file)}` : '' - const src = `https://stackblitz.com/github/${repo}/tree/${branch}/${examplePath}?embed=1&theme=${themeParam}&preset=${preset}${fileParam}` + const src = `https://stackblitz.com/github/${repo}/tree/${branch}/${examplePath}?embed=1&theme=${themeParam}&preset=node` return ( //oxlint-disable-next-line jsx-a11y/iframe-has-title @@ -37,7 +30,7 @@ export function StackBlitzEmbed({ sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" className="shadow-lg" loading="lazy" - style={{ width: '100%', height, border: '0' }} + style={{ width: '100%', height: '80vh', border: '0' }} /> ) } diff --git a/src/components/TableComponents.tsx b/src/components/TableComponents.tsx index 239cfa97e..0926665be 100644 --- a/src/components/TableComponents.tsx +++ b/src/components/TableComponents.tsx @@ -59,16 +59,12 @@ interface TableHeaderCellProps { children?: React.ReactNode className?: string align?: 'left' | 'right' | 'center' - width?: string - compact?: boolean } export function TableHeaderCell({ children, className = '', align = 'left', - width, - compact = true, }: TableHeaderCellProps) { const alignClass = align === 'right' @@ -76,12 +72,9 @@ export function TableHeaderCell({ : align === 'center' ? 'text-center' : 'text-left' - const paddingClass = compact ? 'px-2 py-1.5' : 'px-4 py-2' - const textSizeClass = compact ? 'text-[10px]' : 'text-xs' return ( {children} @@ -93,8 +86,6 @@ interface SortableTableHeaderCellProps { children?: React.ReactNode className?: string align?: 'left' | 'right' | 'center' - width?: string - compact?: boolean sortable?: boolean sortDirection?: 'asc' | 'desc' | false onSort?: () => void @@ -104,8 +95,6 @@ export function SortableTableHeaderCell({ children, className = '', align = 'left', - width, - compact = true, sortable = false, sortDirection = false, onSort, @@ -116,22 +105,15 @@ export function SortableTableHeaderCell({ : align === 'center' ? 'text-center' : 'text-left' - const paddingClass = compact ? 'px-2 py-1.5' : 'px-4 py-2' - const textSizeClass = compact ? 'text-[10px]' : 'text-xs' - const baseClass = `${paddingClass} ${alignClass} ${textSizeClass} font-semibold text-gray-600 dark:text-gray-400 uppercase whitespace-nowrap ${className}` + const baseClass = `px-2 py-1.5 ${alignClass} text-[10px] font-semibold text-gray-600 dark:text-gray-400 uppercase whitespace-nowrap ${className}` if (!sortable) { - return ( - - {children} - - ) + return {children} } return ( ) => void } @@ -197,7 +178,6 @@ export function TableCell({ className = '', align = 'left', colSpan, - compact = true, onClick, }: TableCellProps) { const alignClass = @@ -206,10 +186,9 @@ export function TableCell({ : align === 'center' ? 'text-center' : 'text-left' - const paddingClass = compact ? 'px-2 py-2' : 'px-4 py-3' return ( diff --git a/src/components/ToastProvider.tsx b/src/components/ToastProvider.tsx index 927627c28..924df823a 100644 --- a/src/components/ToastProvider.tsx +++ b/src/components/ToastProvider.tsx @@ -3,7 +3,6 @@ import { Toaster, toast } from 'sonner' import { useTheme } from '~/components/ThemeProvider' type ToastOptions = { - durationMs?: number id?: number | string } @@ -31,7 +30,7 @@ export function ToastProvider({ const notify = React.useCallback( (content: React.ReactNode, options?: ToastOptions) => { const id = toast(content, { - duration: options?.durationMs ?? 2500, + duration: 2500, id: options?.id, }) return String(id) diff --git a/src/components/admin/AdminEmptyState.tsx b/src/components/admin/AdminEmptyState.tsx index 6fa2cc84a..04cdb4319 100644 --- a/src/components/admin/AdminEmptyState.tsx +++ b/src/components/admin/AdminEmptyState.tsx @@ -1,22 +1,15 @@ import * as React from 'react' -import { Link } from '@tanstack/react-router' -import { Button } from '~/ui' type AdminEmptyStateProps = { icon: React.ReactNode title: string description?: string - action?: { - label: string - to: string - } } export function AdminEmptyState({ icon, title, description, - action, }: AdminEmptyStateProps) { return (
@@ -31,13 +24,6 @@ export function AdminEmptyState({ {description}

)} - {action && ( -
- - - -
- )}
) } diff --git a/src/components/application-starter/prompt-parts.tsx b/src/components/application-starter/prompt-parts.tsx index e1653a57e..28738df44 100644 --- a/src/components/application-starter/prompt-parts.tsx +++ b/src/components/application-starter/prompt-parts.tsx @@ -22,7 +22,6 @@ export function StarterChipButton({ className, children, compact = false, - disabled = false, onClick, palette, selected, @@ -31,7 +30,6 @@ export function StarterChipButton({ className?: string children: React.ReactNode compact?: boolean - disabled?: boolean onClick?: () => void palette: StarterPalette selected: boolean @@ -41,7 +39,6 @@ export function StarterChipButton({ - ) - } - return ( ) => ({ ...prev, packageGroups: comparison.packageGroups, diff --git a/src/components/npm-stats/Resizable.tsx b/src/components/npm-stats/Resizable.tsx index 3cd2bbd01..1d1a9caa3 100644 --- a/src/components/npm-stats/Resizable.tsx +++ b/src/components/npm-stats/Resizable.tsx @@ -17,10 +17,6 @@ export type ResizableProps = { options: ResizeChangeOptions, ) => void children: React.ReactNode - minHeight?: number - minWidth?: number - fullWidthSnapThreshold?: number - enableWidthResize?: boolean } type ResizePreviewSize = { @@ -36,10 +32,6 @@ export function Resizable({ width, onSizeChange, children, - minHeight = 300, - minWidth = 320, - fullWidthSnapThreshold = 12, - enableWidthResize = true, }: ResizableProps) { const [isHeightDragging, setIsHeightDragging] = React.useState(false) const [isWidthDragging, setIsWidthDragging] = React.useState(false) @@ -96,26 +88,26 @@ export function Resizable({ }, [containerWidth]) const getHeightFromDelta = React.useCallback( - (deltaY: number) => Math.max(minHeight, startHeightRef.current + deltaY), - [minHeight], + (deltaY: number) => Math.max(300, startHeightRef.current + deltaY), + [], ) const getWidthFromDelta = React.useCallback( ({ deltaX, maxWidth }: { deltaX: number; maxWidth: number }) => { const rawWidth = startWidthRef.current + deltaX * 2 - const nextMinWidth = Math.min(minWidth, maxWidth) + const nextMinWidth = Math.min(320, maxWidth) const nextWidth = Math.max( nextMinWidth, Math.min(maxWidth, Math.round(rawWidth)), ) - if (nextWidth >= maxWidth - fullWidthSnapThreshold) { + if (nextWidth >= maxWidth - 12) { return undefined } return nextWidth }, - [fullWidthSnapThreshold, minWidth], + [], ) const handleHeightMouseDown = React.useCallback( @@ -307,48 +299,44 @@ export function Resizable({ style={{ width: width ?? '100%', maxWidth: '100%' }} > {children} - {enableWidthResize ? ( -
+
) : ( - }> + }> diff --git a/src/routes/admin/users.tsx b/src/routes/admin/users.tsx index 65177d8cb..033dbf874 100644 --- a/src/routes/admin/users.tsx +++ b/src/routes/admin/users.tsx @@ -316,7 +316,7 @@ function UsersPage() { const bulkAssignRolesToUsers = useBulkAssignRolesToUsers() const bulkUpdateUserCapabilities = useBulkUpdateUserCapabilities() const allRolesQuery = useQuery({ - ...listRolesQueryOptions({}), + ...listRolesQueryOptions(), placeholderData: keepPreviousData, }) const allRoles = useMemo(() => allRolesQuery.data || [], [allRolesQuery.data]) diff --git a/src/routes/api/builder/project-snapshots.$hash.quarantine.ts b/src/routes/api/builder/project-snapshots.$hash.quarantine.ts index 2dca8f6f9..fd8cdcfa4 100644 --- a/src/routes/api/builder/project-snapshots.$hash.quarantine.ts +++ b/src/routes/api/builder/project-snapshots.$hash.quarantine.ts @@ -21,7 +21,7 @@ import { } from '~/utils/builder-project-storage.server' import { quarantineBuilderProjectsBySnapshotHash } from '~/utils/builder-project-events.server' -type BuilderProjectQuarantineOperations = { +export type BuilderProjectQuarantineOperations = { quarantineSnapshot: (hash: string, userId: string) => Promise isSnapshotQuarantined: (hash: string) => Promise purgeCacheTags: (tags: Array) => Promise @@ -35,6 +35,15 @@ type BuilderProjectQuarantineOperations = { ) => Promise } +export const builderProjectQuarantineOperations: BuilderProjectQuarantineOperations = + { + quarantineSnapshot: quarantineBuilderProjectSnapshot, + isSnapshotQuarantined: isBuilderProjectSnapshotQuarantined, + purgeCacheTags: purgeHostingCacheTags, + quarantineProjects: quarantineBuilderProjectsBySnapshotHash, + quarantineStableProjects: quarantineStoredBuilderProjectsBySnapshotHash, + } + export class BuilderProjectQuarantineCleanupError extends Error { constructor(readonly failures: Array) { super('Builder project quarantine cleanup did not finish') @@ -42,22 +51,14 @@ export class BuilderProjectQuarantineCleanupError extends Error { } } -export async function quarantineBuilderProjectSnapshotForAdmin( - { - hash, - actorId, - }: { - hash: string - actorId: string - }, - operations: BuilderProjectQuarantineOperations = { - quarantineSnapshot: quarantineBuilderProjectSnapshot, - isSnapshotQuarantined: isBuilderProjectSnapshotQuarantined, - purgeCacheTags: purgeHostingCacheTags, - quarantineProjects: quarantineBuilderProjectsBySnapshotHash, - quarantineStableProjects: quarantineStoredBuilderProjectsBySnapshotHash, - }, -) { +export async function quarantineBuilderProjectSnapshotForAdmin({ + hash, + actorId, +}: { + hash: string + actorId: string +}) { + const operations = builderProjectQuarantineOperations const failures: Array = [] let tombstoned = false try { diff --git a/src/routes/maintainers.tsx b/src/routes/maintainers.tsx index 733472ea5..52d9785a1 100644 --- a/src/routes/maintainers.tsx +++ b/src/routes/maintainers.tsx @@ -319,18 +319,10 @@ function MaintainerGrid({ maintainers, viewMode, title, - stats, }: { maintainers: Maintainer[] viewMode: 'compact' | 'full' | 'row' title?: string - stats?: Array<{ - username: string - totalCommits: number - totalPullRequests: number - totalIssues: number - totalReviews: number - }> }) { return (
@@ -356,10 +348,7 @@ function MaintainerGrid({ {viewMode === 'compact' ? ( ) : viewMode === 'row' ? ( - s.username === maintainer.github)} - /> + ) : ( )} diff --git a/src/routes/stats/npm/index.tsx b/src/routes/stats/npm/index.tsx index ee83fa58b..12a201339 100644 --- a/src/routes/stats/npm/index.tsx +++ b/src/routes/stats/npm/index.tsx @@ -467,7 +467,7 @@ function RouteComponent() { }) } - const handleAddBaseline = (packageName: string, color?: string) => { + const handleAddBaseline = (packageName: string) => { navigate({ to: '.', search: (prev) => { @@ -483,7 +483,6 @@ function RouteComponent() { ...groups, { packages: [{ name: packageName, hidden: true }], - color, baseline: true, }, ], diff --git a/src/server/runtime/host.server.ts b/src/server/runtime/host.server.ts index 63ce8b0d9..e9c1783fb 100644 --- a/src/server/runtime/host.server.ts +++ b/src/server/runtime/host.server.ts @@ -134,12 +134,9 @@ export async function getHostRuntimeEnv() { } } -export async function fetchStaticAsset( - input: RequestInfo | URL, - init?: RequestInit, -) { +export async function fetchStaticAsset(input: RequestInfo | URL) { const staticAssets = await getStaticAssetService() - return staticAssets ? staticAssets.fetch(input, init) : fetch(input, init) + return staticAssets ? staticAssets.fetch(input) : fetch(input) } export async function getDatabaseConnectionString() { diff --git a/src/server/shopify/fetch.ts b/src/server/shopify/fetch.ts index 4463d2cb2..a49fd04cb 100644 --- a/src/server/shopify/fetch.ts +++ b/src/server/shopify/fetch.ts @@ -14,11 +14,6 @@ const SHOPIFY_API_VERSION = '2026-01' type ShopifyFetchInput = { query: string variables?: TVariables - /** - * Optional buyer IP, forwarded to Shopify's bot-protection headers. - * Only meaningful with the private token. - */ - buyerIp?: string } type ShopifyResponse = { @@ -59,7 +54,6 @@ export async function shopifyServerFetch< Accept: 'application/json', 'Shopify-Storefront-Private-Token': token, } - if (input.buyerIp) headers['Shopify-Storefront-Buyer-IP'] = input.buyerIp const response = await fetch( `https://${SHOPIFY_STORE_DOMAIN}/api/${SHOPIFY_API_VERSION}/graphql.json`, diff --git a/src/server/sponsors.ts b/src/server/sponsors.ts index f65dffbb9..42ba26c78 100644 --- a/src/server/sponsors.ts +++ b/src/server/sponsors.ts @@ -11,7 +11,6 @@ export type SponsorMeta = { imageUrl?: string linkUrl?: string private?: boolean - amount?: number } export type Sponsor = { @@ -76,16 +75,6 @@ export async function getSponsors() { linkUrl: sponsorMeta.linkUrl ?? matchingSponsor.linkUrl, private: sponsorMeta.private ?? matchingSponsor.private, }) - } else if (sponsorMeta.amount) { - sponsors.push({ - login: sponsorMeta.login, - name: sponsorMeta.name || '', - imageUrl: sponsorMeta.imageUrl || '', - linkUrl: sponsorMeta.linkUrl || '', - private: sponsorMeta.private || false, - createdAt: new Date().toISOString(), - amount: sponsorMeta.amount || 0, - }) } }) diff --git a/src/ui/Badge.tsx b/src/ui/Badge.tsx index a5b70f4f9..71db0f2a7 100644 --- a/src/ui/Badge.tsx +++ b/src/ui/Badge.tsx @@ -13,7 +13,6 @@ type BadgeVariant = type BadgeProps = { children: React.ReactNode variant?: BadgeVariant - rounded?: 'md' | 'full' className?: string } @@ -33,14 +32,12 @@ const variantStyles: Record = { export function Badge({ children, variant = 'default', - rounded = 'full', className, }: BadgeProps) { return ( & { - /** Ring color on focus. Defaults to blue. */ - focusRing?: 'blue' | 'orange' | 'purple' -} - -const ringStyles = { - blue: 'focus:ring-blue-500', - orange: 'focus:ring-orange-500', - purple: 'focus:ring-purple-500', -} +type FormInputProps = React.InputHTMLAttributes export const FormInput = React.forwardRef( - function FormInput({ className, focusRing = 'blue', ...props }, ref) { + function FormInput({ className, ...props }, ref) { return ( , 'size'> & { - /** Size variant. 'small' uses compact viewBox. Defaults to 'default'. */ - size?: 'default' | 'small' -} - -export function LogoQueryGG({ size = 'default', ...props }: LogoQueryGGProps) { - if (size === 'small') { - return ( -
- - Query.gg - The Official React Query Course - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- ) - } +type LogoQueryGGProps = Omit, 'size'> +export function LogoQueryGG(props: LogoQueryGGProps) { // Default (large) size - keeping original SVG content return (
diff --git a/src/ui/Tooltip.tsx b/src/ui/Tooltip.tsx index 6cb52ded7..c397a6a46 100644 --- a/src/ui/Tooltip.tsx +++ b/src/ui/Tooltip.tsx @@ -6,7 +6,6 @@ interface TooltipProps { children: React.ReactNode content: React.ReactNode side?: 'top' | 'right' | 'bottom' | 'left' - align?: 'start' | 'center' | 'end' delayDuration?: number className?: string } @@ -15,7 +14,6 @@ export function Tooltip({ children, content, side = 'top', - align = 'center', delayDuration = 200, className, }: TooltipProps) { @@ -30,7 +28,7 @@ export function Tooltip({ { - enabled?: boolean event: TName props: AnalyticsEventProps - threshold?: number } function getPageType(pathname: string) { @@ -172,7 +170,7 @@ export function trackPageView(pagePath: string) { export function useTrackedImpression< TName extends AnalyticsEventName, TElement extends Element = Element, ->({ enabled = true, event, props, threshold = 0.5 }: ImpressionOptions) { +>({ event, props }: ImpressionOptions) { const ref = React.useRef(null) const hasTrackedRef = React.useRef(false) const propsRef = React.useRef(props) @@ -182,7 +180,7 @@ export function useTrackedImpression< }, [props]) React.useEffect(() => { - if (!enabled || hasTrackedRef.current) { + if (hasTrackedRef.current) { return } @@ -212,7 +210,7 @@ export function useTrackedImpression< continue } - if (entry.intersectionRatio < threshold) { + if (entry.intersectionRatio < 0.5) { continue } @@ -221,7 +219,7 @@ export function useTrackedImpression< return } }, - { threshold: [threshold] }, + { threshold: [0.5] }, ) observer.observe(element) @@ -229,7 +227,7 @@ export function useTrackedImpression< return () => { observer.disconnect() } - }, [enabled, event, threshold]) + }, [event]) return ref } diff --git a/src/utils/application-starter.ts b/src/utils/application-starter.ts index f2b696edc..8cae45e29 100644 --- a/src/utils/application-starter.ts +++ b/src/utils/application-starter.ts @@ -552,13 +552,11 @@ export async function resolveApplicationStarterDeterministically({ export function composeApplicationStarterResult({ input, - prompt, rationale, recipe, resultType, }: { input: string - prompt?: string rationale: Array recipe: ApplicationStarterRecipe resultType: ApplicationStarterResultType @@ -573,9 +571,7 @@ export function composeApplicationStarterResult({ resultType === 'migration' ? 'Your migration prompt is ready' : 'Your prompt is ready', - prompt: sanitizeStarterPrompt( - prompt || buildPrompt({ input, resultType, recipe }), - ), + prompt: sanitizeStarterPrompt(buildPrompt({ input, resultType, recipe })), rationale, recipe, resultType, diff --git a/src/utils/audit.server.ts b/src/utils/audit.server.ts index 2ccd41e49..7ad6de6d0 100644 --- a/src/utils/audit.server.ts +++ b/src/utils/audit.server.ts @@ -35,9 +35,8 @@ export async function recordAuditLog(opts: { targetType: 'user' | 'role' | 'banner' | 'feed_entry' | 'feedback' targetId: string details?: Record - request?: Request }): Promise { - const { actorId, action, targetType, targetId, details, request } = opts + const { actorId, action, targetType, targetId, details } = opts await db.insert(auditLogs).values({ actorId, @@ -45,7 +44,7 @@ export async function recordAuditLog(opts: { targetType, targetId, details: details ?? null, - ipAddress: request ? getClientIp(request) : undefined, - userAgent: request?.headers.get('user-agent') || undefined, + ipAddress: undefined, + userAgent: undefined, }) } diff --git a/src/utils/blog-format.ts b/src/utils/blog-format.ts index dd98e698d..3fb0318fc 100644 --- a/src/utils/blog-format.ts +++ b/src/utils/blog-format.ts @@ -110,15 +110,15 @@ export function formatPublishedDate(published: string) { }) } -export function isPublishedDateReleased(published: string, now = new Date()) { - return published <= getUtcDateString(now) +export function isPublishedDateReleased(published: string) { + return published <= getUtcDateString(new Date()) } -export function isBlogPostUnpublished( - post: { draft?: boolean; published: string }, - now = new Date(), -) { - return Boolean(post.draft) || !isPublishedDateReleased(post.published, now) +export function isBlogPostUnpublished(post: { + draft?: boolean + published: string +}) { + return Boolean(post.draft) || !isPublishedDateReleased(post.published) } export function publishedDateToUTCString(published: string) { diff --git a/src/utils/browser-effects.ts b/src/utils/browser-effects.ts index 33887a9ba..5944bda66 100644 --- a/src/utils/browser-effects.ts +++ b/src/utils/browser-effects.ts @@ -91,18 +91,15 @@ export function useTemporaryFlag(defaultDurationMs = 2000) { } }, []) - const trigger = React.useCallback( - (durationMs = defaultDurationMs) => { - clearTimer() - - setActive(true) - timeoutRef.current = window.setTimeout(() => { - setActive(false) - timeoutRef.current = null - }, durationMs) - }, - [clearTimer, defaultDurationMs], - ) + const trigger = React.useCallback(() => { + clearTimer() + + setActive(true) + timeoutRef.current = window.setTimeout(() => { + setActive(false) + timeoutRef.current = null + }, defaultDurationMs) + }, [clearTimer, defaultDurationMs]) React.useEffect(() => clearTimer, [clearTimer]) diff --git a/src/utils/builder-ai-activity.ts b/src/utils/builder-ai-activity.ts index ca44fdd67..1dada6deb 100644 --- a/src/utils/builder-ai-activity.ts +++ b/src/utils/builder-ai-activity.ts @@ -564,13 +564,10 @@ export function getBuilderAiActivityItemLabel(item: BuilderAiActivityItem) { return complete ? `Completed ${label.toLowerCase()}` : label } -export function formatBuilderAiActivityDuration( - activity: BuilderAiActivity, - now = activity.completedAt ?? activity.startedAt, -) { +export function formatBuilderAiActivityDuration(activity: BuilderAiActivity) { const milliseconds = Math.max( 0, - (activity.completedAt ?? cleanTimestamp(now)) - activity.startedAt, + (activity.completedAt ?? Date.now()) - activity.startedAt, ) const seconds = Math.max(1, Math.round(milliseconds / 1_000)) if (seconds < 60) return `${seconds}s` diff --git a/src/utils/builder-ai-package-resources.ts b/src/utils/builder-ai-package-resources.ts index 4d3908a68..5359680b6 100644 --- a/src/utils/builder-ai-package-resources.ts +++ b/src/utils/builder-ai-package-resources.ts @@ -36,14 +36,9 @@ export type BuilderAiPackageFetchState = { ) => Promise } -export function createBuilderAiPackageFetchState( - options: { - maxBytes?: number - maxResources?: number - } = {}, -): BuilderAiPackageFetchState { - const byteLimit = options.maxBytes ?? maxInspectionBytes - const resourceLimit = options.maxResources ?? maxInspectionResources +export function createBuilderAiPackageFetchState(): BuilderAiPackageFetchState { + const byteLimit = maxInspectionBytes + const resourceLimit = maxInspectionResources const resources = new Map>() const attemptedResources = new Set() let downloadedBytes = 0 diff --git a/src/utils/builder-ai-stream.client.ts b/src/utils/builder-ai-stream.client.ts index cb8634310..bf2aec8cf 100644 --- a/src/utils/builder-ai-stream.client.ts +++ b/src/utils/builder-ai-stream.client.ts @@ -48,7 +48,6 @@ export async function runBuilderAiStream({ byokProvider, includeReasoningSummaries = false, onActivityEvent, - onChunk, onText, onLocalValidate, onValidate, @@ -63,7 +62,6 @@ export async function runBuilderAiStream({ byokProvider?: BuilderAiRemoteProvider includeReasoningSummaries?: boolean onActivityEvent?: (event: BuilderAiActivityEvent) => void - onChunk?: (chunk: StreamChunk) => void onText?: (text: string) => void onLocalValidate?: ( state: BuilderAiValidationState, @@ -345,7 +343,6 @@ export async function runBuilderAiStream({ throw new Error('Builder AI returned an invalid execution result') } - onChunk?.(chunk) observeTextChunk(chunk) observeActivityChunk(chunk) diff --git a/src/utils/builder-environment.ts b/src/utils/builder-environment.ts index d87595e28..98260a818 100644 --- a/src/utils/builder-environment.ts +++ b/src/utils/builder-environment.ts @@ -52,13 +52,12 @@ export const builderImports = { function defineExampleEnvironmentProfile({ createEntrySource, - entryPath = '/__tanstack-example-entry.ts', outputSelector, }: { createEntrySource: (entry: string, outputSource: string) => string - entryPath?: string outputSelector: `#${string}` }) { + const entryPath = '/__tanstack-example-entry.ts' return { entryPath, outputSelector, diff --git a/src/utils/builder-project-events.server.ts b/src/utils/builder-project-events.server.ts index 56d9a7724..b2438e3b7 100644 --- a/src/utils/builder-project-events.server.ts +++ b/src/utils/builder-project-events.server.ts @@ -156,10 +156,8 @@ export type BuilderProjectQuotaUsage = { } export class BuilderProjectLimitError extends BuilderProjectConflictError { - constructor( - message = `Builder project limit of ${builderProjectOwnerLimit} reached`, - ) { - super(message) + constructor() { + super(`Builder project limit of ${builderProjectOwnerLimit} reached`) this.name = 'BuilderProjectLimitError' } } @@ -306,10 +304,8 @@ export async function getBuilderProjectState({ export async function listBuilderProjectStates({ ownerId, - limit = 100, }: { ownerId: string - limit?: number }) { return db .select({ @@ -350,7 +346,7 @@ export async function listBuilderProjectStates({ ), ) .orderBy(desc(builderProjects.updatedAt)) - .limit(limit) + .limit(100) } export type CreateBuilderProjectStateInput = { @@ -631,7 +627,6 @@ export async function updateBuilderProjectState(input: { title?: string description?: string expectedRevisionNumber?: number - occurredAt?: Date }) { return withBuilderProjectConstraintErrors( () => @@ -701,7 +696,7 @@ export async function updateBuilderProjectState(input: { .limit(1) if (!parentRevision) throw new BuilderProjectConflictError() - const now = input.occurredAt ?? new Date() + const now = new Date() const nextRevisionNumber = project.currentRevisionNumber + 1 const [revision] = await transaction .insert(builderProjectRevisions) @@ -768,15 +763,13 @@ export async function deleteBuilderProjectState(input: { projectId: string ownerId: string clientMutationId: string - actorId?: string - occurredAt?: Date }) { const requestHash = await getBuilderProjectMutationRequestHash({ type: 'project.delete', projectId: input.projectId, ownerId: input.ownerId, clientMutationId: input.clientMutationId, - actorId: input.actorId ?? input.ownerId, + actorId: input.ownerId, }) return db.transaction(async (transaction) => { const project = await lockOwnedProject( @@ -792,7 +785,7 @@ export async function deleteBuilderProjectState(input: { requestHash, }) - const now = input.occurredAt ?? new Date() + const now = new Date() await cancelNonterminalRunsForDeletedProjectInTransaction( transaction, project, @@ -805,7 +798,7 @@ export async function deleteBuilderProjectState(input: { .update(builderProjects) .set({ deletedAt: now, - deletedById: input.actorId ?? input.ownerId, + deletedById: input.ownerId, updatedAt: now, }) .where(eq(builderProjects.id, input.projectId)) @@ -827,7 +820,7 @@ export async function deleteBuilderProjectState(input: { payload: { projectId: input.projectId, deletedAt: now.toISOString(), - actorId: input.actorId ?? input.ownerId, + actorId: input.ownerId, }, occurredAt: now, allowQuotaOverflow: true, @@ -839,12 +832,11 @@ export async function deleteBuilderProjectState(input: { export async function quarantineBuilderProjectsBySnapshotHash({ snapshotHash, actorId, - occurredAt = new Date(), }: { snapshotHash: string actorId: string - occurredAt?: Date }) { + const occurredAt = new Date() return db.transaction(async (transaction) => { const projects = await transaction .select() @@ -1218,7 +1210,6 @@ export async function enqueueBuilderProjectRun(input: { queueKind: 'queue' | 'steer' provider: string model: string - occurredAt?: Date userMessage: { id: string clientMutationId: string @@ -1273,7 +1264,7 @@ export async function enqueueBuilderProjectRun(input: { throw new BuilderProjectConflictError('Builder thread not found') } - const now = input.occurredAt ?? new Date() + const now = new Date() const firstMessageTitle = thread.lastMessagePosition === 0 ? getBuilderThreadTitle(input.userMessage.content) @@ -1385,10 +1376,8 @@ export async function claimBuilderProjectRun(input: { clientMutationId: string requestHash: string leaseOwnerId: string - leaseDurationMs?: number - occurredAt?: Date }) { - const leaseDurationMs = getLeaseDuration(input.leaseDurationMs) + const leaseDurationMs = 30_000 const result = await db.transaction(async (transaction) => { const project = await lockOwnedProject( transaction, @@ -1428,7 +1417,7 @@ export async function claimBuilderProjectRun(input: { ) } - const now = input.occurredAt ?? new Date() + const now = new Date() await interruptExpiredRunsInTransaction( transaction, input.projectId, @@ -1548,7 +1537,6 @@ export async function cancelPendingBuilderProjectRun(input: { clientMutationId: string requestHash: string browserSessionId: string - occurredAt?: Date }) { return db.transaction(async (transaction) => { await lockOwnedProject(transaction, input.projectId, input.ownerId) @@ -1570,7 +1558,7 @@ export async function cancelPendingBuilderProjectRun(input: { return commandResult(input.clientMutationId, existingEvents) } - const now = input.occurredAt ?? new Date() + const now = new Date() const [run] = await transaction .update(builderProjectRuns) .set({ @@ -1611,13 +1599,11 @@ export async function renewBuilderProjectRunLease(input: { leaseOwnerId: string fencingToken: number clientMutationId: string - leaseDurationMs?: number - occurredAt?: Date }) { - const leaseDurationMs = getLeaseDuration(input.leaseDurationMs) + const leaseDurationMs = 30_000 const result = await db.transaction(async (transaction) => { await lockOwnedProject(transaction, input.projectId, input.ownerId) - const now = input.occurredAt ?? new Date() + const now = new Date() const expired = await interruptExpiredRunsInTransaction( transaction, input.projectId, @@ -1685,7 +1671,6 @@ export async function finishBuilderProjectRun(input: { clientMutationId: string error: BuilderJsonObject } - occurredAt?: Date assistantMessage?: { id: string clientMutationId: string @@ -1744,7 +1729,7 @@ export async function finishBuilderProjectRun(input: { } } - const now = input.occurredAt ?? new Date() + const now = new Date() await interruptExpiredRunsInTransaction( transaction, input.projectId, @@ -2031,12 +2016,10 @@ export async function finishBuilderProjectRun(input: { export async function interruptExpiredBuilderProjectRuns({ projectId, ownerId, - occurredAt, includeDeleted = false, }: { projectId: string ownerId: string - occurredAt?: Date includeDeleted?: boolean }) { return db.transaction(async (transaction) => { @@ -2050,30 +2033,19 @@ export async function interruptExpiredBuilderProjectRuns({ return cancelNonterminalRunsForDeletedProjectInTransaction( transaction, project, - occurredAt ?? new Date(), + new Date(), crypto.randomUUID(), ) } - return interruptExpiredRunsInTransaction( - transaction, - projectId, - ownerId, - occurredAt, - ) + return interruptExpiredRunsInTransaction(transaction, projectId, ownerId) }) } export async function interruptExpiredBuilderProjectRunLeases({ occurredAt = new Date(), - limit = 100, }: { occurredAt?: Date - limit?: number } = {}) { - if (!Number.isInteger(limit) || limit < 1 || limit > 1_000) { - throw new Error('Invalid Builder run lease sweep limit') - } - const candidates = await db .select({ projectId: builderProjectRuns.projectId, @@ -2087,7 +2059,7 @@ export async function interruptExpiredBuilderProjectRunLeases({ ), ) .orderBy(asc(builderProjectRuns.leaseExpiresAt)) - .limit(limit) + .limit(100) const projects = new Map() for (const candidate of candidates) { @@ -2767,13 +2739,6 @@ function commandResult( } } -function getLeaseDuration(value = 30_000) { - if (!Number.isSafeInteger(value) || value < 5_000 || value > 300_000) { - throw new Error('Invalid Builder project run lease duration') - } - return value -} - function getBuilderThreadTitle(content: string) { return content.trim().replace(/\s+/g, ' ').slice(0, 160) } @@ -3104,9 +3069,8 @@ export function isMatchingBuilderProjectCreationReplay( async function findProjectCreationByMutation( input: CreateBuilderProjectStateInput, matchRevisionId = true, - database: Pick = db, ) { - const [row] = await database + const [row] = await db .select({ id: builderProjects.id }) .from(builderProjects) .where( @@ -3117,12 +3081,7 @@ async function findProjectCreationByMutation( ) .limit(1) return row - ? requireMatchingBuilderProjectCreation( - input, - row.id, - database, - matchRevisionId, - ) + ? requireMatchingBuilderProjectCreation(input, row.id, db, matchRevisionId) : undefined } diff --git a/src/utils/builder-project-snapshot-registry.server.ts b/src/utils/builder-project-snapshot-registry.server.ts index 73e284def..43c00445e 100644 --- a/src/utils/builder-project-snapshot-registry.server.ts +++ b/src/utils/builder-project-snapshot-registry.server.ts @@ -333,10 +333,6 @@ export async function quarantineBuilderProjectSnapshotInRegistry( type BuilderProjectSnapshotReader = Pick -type BuilderProjectSnapshotGcReader = Pick - -type BuilderProjectSnapshotGcWriter = Pick - export async function assertBuilderProjectSnapshotAvailable( snapshotHash: string, database: BuilderProjectSnapshotReader = db, @@ -605,13 +601,11 @@ export async function pruneBuilderProjectSnapshotStorage({ export function deferLegacyReferencedBuilderProjectSnapshotGcCandidate({ hash, occurredAt, - database = db, }: { hash: string occurredAt: Date - database?: BuilderProjectSnapshotGcWriter }) { - return database + return db .update(builderProjectSnapshots) .set({ deletingAt: null, updatedAt: occurredAt }) .where( @@ -625,13 +619,11 @@ export function deferLegacyReferencedBuilderProjectSnapshotGcCandidate({ export function getBuilderProjectSnapshotReservationGcCandidates({ cutoff, limit, - database = db, }: { cutoff: Date limit: number - database?: BuilderProjectSnapshotGcReader }) { - return database + return db .select({ id: builderProjectSnapshotReservations.id, ownerId: builderProjectSnapshotReservations.ownerId, @@ -642,7 +634,7 @@ export function getBuilderProjectSnapshotReservationGcCandidates({ and( lte(builderProjectSnapshotReservations.updatedAt, cutoff), notExists( - database + db .select({ id: builderProjects.id }) .from(builderProjects) .leftJoin( @@ -680,20 +672,18 @@ export function getBuilderProjectSnapshotReservationGcCandidates({ export function getBuilderProjectSnapshotGcCandidates({ cutoff, limit, - database = db, }: { cutoff: Date limit: number - database?: BuilderProjectSnapshotGcReader }) { - return database + return db .select({ hash: builderProjectSnapshots.hash }) .from(builderProjectSnapshots) .where( and( isNull(builderProjectSnapshots.quarantinedAt), notExists( - database + db .select({ id: builderProjectRevisions.id }) .from(builderProjectRevisions) .where( @@ -704,7 +694,7 @@ export function getBuilderProjectSnapshotGcCandidates({ ), ), notExists( - database + db .select({ id: builderProjects.id }) .from(builderProjects) .where( @@ -712,7 +702,7 @@ export function getBuilderProjectSnapshotGcCandidates({ ), ), notExists( - database + db .select({ id: builderProjectSnapshotReservations.id }) .from(builderProjectSnapshotReservations) .where( diff --git a/src/utils/builder-project-sync-http.server.ts b/src/utils/builder-project-sync-http.server.ts index da349d909..89b32af77 100644 --- a/src/utils/builder-project-sync-http.server.ts +++ b/src/utils/builder-project-sync-http.server.ts @@ -14,12 +14,6 @@ type BuilderProjectEventStreamOptions = { afterSequence: number, ) => Promise> interruptExpiredRuns?: () => Promise - durationMs?: number - pollIntervalMs?: number - heartbeatIntervalMs?: number - maxEvents?: number - now?: () => number - wait?: (milliseconds: number) => Promise } export function parseBuilderProjectSyncCursor(request: Request) { @@ -58,13 +52,12 @@ export function encodeBuilderProjectSyncEvent(event: BuilderProjectSyncEvent) { export function createBuilderProjectEventStreamResponse( options: BuilderProjectEventStreamOptions, ) { - const now = options.now ?? Date.now - const wait = options.wait ?? waitForDelay - const durationMs = options.durationMs ?? defaultStreamDurationMs - const pollIntervalMs = options.pollIntervalMs ?? defaultPollIntervalMs - const heartbeatIntervalMs = - options.heartbeatIntervalMs ?? defaultHeartbeatIntervalMs - const maxEvents = options.maxEvents ?? defaultMaxEvents + const now = Date.now + const wait = waitForDelay + const durationMs = defaultStreamDurationMs + const pollIntervalMs = defaultPollIntervalMs + const heartbeatIntervalMs = defaultHeartbeatIntervalMs + const maxEvents = defaultMaxEvents const headers = new Headers(options.headers) headers.set('Cache-Control', 'no-store, no-transform') headers.set('Content-Type', 'text/event-stream; charset=utf-8') diff --git a/src/utils/builder-project-sync.client.ts b/src/utils/builder-project-sync.client.ts index f1b4a5878..b786c3942 100644 --- a/src/utils/builder-project-sync.client.ts +++ b/src/utils/builder-project-sync.client.ts @@ -70,19 +70,6 @@ type BuilderProjectSyncRowChange = ReplayableRowChange< string > -interface EventSourceLike { - readonly readyState: number - addEventListener: ( - type: string, - listener: (event: MessageEvent) => void, - ) => void - removeEventListener: ( - type: string, - listener: (event: MessageEvent) => void, - ) => void - close: () => void -} - export interface BuilderProjectBrowserSessionLockManager { request: ( name: string, @@ -98,11 +85,6 @@ type FetchLike = ( export interface CreateBuilderProjectSyncClientOptions { projectId: string - fetch?: FetchLike - createEventSource?: (url: string) => EventSourceLike - sessionStorage?: Pick - browserSessionLockManager?: BuilderProjectBrowserSessionLockManager - createBrowserSessionId?: () => string onBackgroundError?: (error: unknown) => void } @@ -164,7 +146,7 @@ export async function createBuilderProjectSyncClient( throw new Error('Invalid Builder project ID') } - const fetchRequest = options.fetch ?? globalThis.fetch + const fetchRequest = globalThis.fetch const syncUrl = `/api/builder/projects/${options.projectId}/sync` const bootstrap = await fetchBuilderProjectSyncSnapshot( syncUrl, @@ -181,15 +163,7 @@ export async function createBuilderProjectSyncClient( const abortController = new AbortController() const db = new DbClient() - const browserSession = await claimBuilderProjectBrowserSession({ - ...(options.sessionStorage ? { storage: options.sessionStorage } : {}), - ...(options.browserSessionLockManager - ? { lockManager: options.browserSessionLockManager } - : {}), - ...(options.createBrowserSessionId - ? { createId: options.createBrowserSessionId } - : {}), - }) + const browserSession = await claimBuilderProjectBrowserSession() const browserSessionId = browserSession.id const collection = createBuilderProjectCollection({ db, @@ -200,9 +174,6 @@ export async function createBuilderProjectSyncClient( ...(options.onBackgroundError ? { onStreamError: options.onBackgroundError } : {}), - ...(options.createEventSource - ? { createEventSource: options.createEventSource } - : {}), }) try { await collection.preload() @@ -476,7 +447,6 @@ function parseBuilderProjectRunEnqueueCommands({ export async function postBuilderProjectSyncCommand( projectId: string, command: BuilderProjectSyncCommand, - signal?: AbortSignal, ) { if (!isUuid(projectId)) throw new Error('Invalid Builder project ID') return requireBuilderProjectSyncCommandResult( @@ -484,7 +454,6 @@ export async function postBuilderProjectSyncCommand( url: `/api/builder/projects/${projectId}/sync`, command, fetchRequest: globalThis.fetch, - ...(signal ? { signal } : {}), }), ) } @@ -509,15 +478,10 @@ export function getBuilderProjectBrowserSessionId({ return created } -async function claimBuilderProjectBrowserSession({ - storage = getSessionStorage(), - lockManager = getBrowserSessionLockManager(), - createId = crypto.randomUUID, -}: { - storage?: Pick - lockManager?: BuilderProjectBrowserSessionLockManager - createId?: () => string -} = {}) { +async function claimBuilderProjectBrowserSession() { + const storage = getSessionStorage() + const lockManager = getBrowserSessionLockManager() + const createId = crypto.randomUUID.bind(crypto) const stored = storage.getItem(browserSessionStorageKey) const preferredId = stored && isUuid(stored) @@ -1107,7 +1071,6 @@ function createBuilderProjectCollection({ snapshot, headCursor, fetchRequest, - createEventSource, onStreamError, }: { db: DbClient @@ -1115,7 +1078,6 @@ function createBuilderProjectCollection({ snapshot: BuilderProjectSyncSnapshot headCursor: number fetchRequest: FetchLike - createEventSource?: (url: string) => EventSourceLike onStreamError?: (error: unknown) => void }) { return db.collection( @@ -1130,7 +1092,6 @@ function createBuilderProjectCollection({ headCursor, fetchRequest, ...(onStreamError ? { onStreamError } : {}), - ...(createEventSource ? { createEventSource } : {}), }), }), ) @@ -1142,7 +1103,6 @@ function openBuilderProjectStream({ snapshot, headCursor, fetchRequest, - createEventSource, onStreamError, }: { context: ReplayableStreamContext @@ -1150,7 +1110,6 @@ function openBuilderProjectStream({ snapshot: BuilderProjectSyncSnapshot headCursor: number fetchRequest: FetchLike - createEventSource?: (url: string) => EventSourceLike onStreamError?: (error: unknown) => void }) { if (context.after > snapshot.cursor) { @@ -1258,7 +1217,6 @@ function openBuilderProjectStream({ if (mapped.project) project = mapped.project markCaughtUpIfReady() }, - ...(createEventSource ? { createEventSource } : {}), }) } diff --git a/src/utils/builder-project-transcript-import.client.ts b/src/utils/builder-project-transcript-import.client.ts index 8659a2ea9..80ad8cb39 100644 --- a/src/utils/builder-project-transcript-import.client.ts +++ b/src/utils/builder-project-transcript-import.client.ts @@ -27,7 +27,6 @@ import { enqueueBuilderProjectSyncCommands, listBuilderProjectSyncOutbox, replayBuilderProjectSyncOutbox, - type BuilderProjectSyncOutboxSender, } from './builder-project-sync-outbox.client' type TranscriptImportCommand = Extract< @@ -380,12 +379,10 @@ export async function promoteBuilderProjectTranscript({ projectId, scope, clientMutationId, - send = (command) => postBuilderProjectSyncCommand(projectId, command), }: { projectId: string scope: string clientMutationId: string - send?: BuilderProjectSyncOutboxSender }) { const snapshot = await readBuilderAiTranscriptScopeSnapshot(scope) if (snapshot.threads.length === 0) return @@ -413,7 +410,6 @@ export async function promoteBuilderProjectTranscript({ const acknowledgement = await importBuilderProjectTranscriptCommands({ projectId, commands, - send, }) await removeBuilderAiTranscriptScopeSnapshot(snapshot) @@ -423,16 +419,16 @@ export async function promoteBuilderProjectTranscript({ export async function importBuilderProjectTranscriptCommands({ projectId, commands, - send = (command) => postBuilderProjectSyncCommand(projectId, command), }: { projectId: string commands: ReadonlyArray - send?: BuilderProjectSyncOutboxSender }) { if (commands.length === 0) return await enqueueBuilderProjectSyncCommands(projectId, commands) - const replay = await replayBuilderProjectSyncOutbox(projectId, send) + const replay = await replayBuilderProjectSyncOutbox(projectId, (command) => + postBuilderProjectSyncCommand(projectId, command), + ) const mutationIds = new Set( commands.map((command) => command.clientMutationId), ) diff --git a/src/utils/chart.ts b/src/utils/chart.ts index 1a977a3fd..f2c3bc4e2 100644 --- a/src/utils/chart.ts +++ b/src/utils/chart.ts @@ -84,12 +84,11 @@ export function getCurrentBinStart(binType: BinType): Date { export function binTimeSeriesData( data: Array<{ date: string; count: number }>, binType: BinType, - options?: { includeIncomplete?: boolean }, ): Array<{ date: Date; count: number }> { if (data.length === 0) return [] const binFn = getBinFunction(binType) - const includeIncomplete = options?.includeIncomplete ?? true + const includeIncomplete = true const parsed = data.map((d) => ({ date: new Date(d.date), diff --git a/src/utils/discord.server.ts b/src/utils/discord.server.ts index 37473cd7a..6b52b0f08 100644 --- a/src/utils/discord.server.ts +++ b/src/utils/discord.server.ts @@ -21,10 +21,7 @@ type DiscordEmbed = { } type DiscordWebhookPayload = { - content?: string embeds?: Array - username?: string - avatar_url?: string } export async function sendDiscordNotification( diff --git a/src/utils/documents.server.ts b/src/utils/documents.server.ts index 656995c4d..b9f0e70c7 100644 --- a/src/utils/documents.server.ts +++ b/src/utils/documents.server.ts @@ -955,8 +955,8 @@ function createExcerpt(text: string, maxLength = 200) { return cleanText } -function createRichExcerpt(text: string, maxLength = 200) { - let cleanText = createExcerpt(text, maxLength) +function createRichExcerpt(text: string) { + let cleanText = createExcerpt(text, 200) const imageText = extractFirstImage(text) diff --git a/src/utils/email.server.ts b/src/utils/email.server.ts index 310ea5042..d2df8c627 100644 --- a/src/utils/email.server.ts +++ b/src/utils/email.server.ts @@ -32,7 +32,6 @@ type ModeratorNotification = { capability: Capability subject: string text: string - html?: string // Extended fields for Discord notifications _showcase?: ShowcaseData _feedback?: FeedbackData diff --git a/src/utils/example-imports.ts b/src/utils/example-imports.ts index b0af4edce..4c0bf588b 100644 --- a/src/utils/example-imports.ts +++ b/src/utils/example-imports.ts @@ -21,7 +21,6 @@ export type ExampleImportMetadataFetch = ( ) => Promise export type ResolveExampleWorkspaceImportsOptions = { - fetch?: ExampleImportMetadataFetch signal?: AbortSignal } @@ -116,7 +115,7 @@ export async function resolveExampleWorkspaceImports( const resolvedGroups = await Promise.all( [...groups.values()].map((group) => - resolvePackageGroup(group, options.fetch, options.signal), + resolvePackageGroup(group, options.signal), ), ) const resolvedPackageNames = new Set( @@ -159,9 +158,7 @@ export async function resolveExampleWorkspaceImports( } }) const resolvedPeers = await Promise.all( - peerGroups.map((group) => - resolvePackageGroup(group, options.fetch, options.signal), - ), + peerGroups.map((group) => resolvePackageGroup(group, options.signal)), ) for (const group of resolvedPeers) { resolvedGroups.push(group) @@ -324,7 +321,6 @@ export function getExampleWorkspaceImports( async function resolvePackageGroup( group: PackageGroup, - metadataFetch: ExampleImportMetadataFetch | undefined, signal: AbortSignal | undefined, ): Promise { const representative = group.specifiers[0] @@ -336,21 +332,17 @@ async function resolvePackageGroup( group.requestedVersion, representative.subpath, ) - const requestedMetadata = await getEsmMetadata( - requestedUrl, - metadataFetch, - signal, - ) + const requestedMetadata = await getEsmMetadata(requestedUrl, signal) const exactVersion = requestedMetadata.version const [packageManifest, ...additionalMetadata] = await Promise.all([ - getPackageManifest(group.packageName, exactVersion, metadataFetch, signal), + getPackageManifest(group.packageName, exactVersion, signal), ...group.specifiers.slice(1).map((specifier) => { const exactUrl = createEsmMetadataUrl( group.packageName, exactVersion, specifier.subpath, ) - return getEsmMetadata(exactUrl, metadataFetch, signal) + return getEsmMetadata(exactUrl, signal) }), ]) const metadata = [requestedMetadata, ...additionalMetadata] @@ -527,13 +519,9 @@ function createEsmModuleUrl( : url } -async function getEsmMetadata( - url: string, - metadataFetch: ExampleImportMetadataFetch | undefined, - signal: AbortSignal | undefined, -) { - if (metadataFetch || signal) { - return requestEsmMetadata(url, metadataFetch ?? globalThis.fetch, signal) +async function getEsmMetadata(url: string, signal: AbortSignal | undefined) { + if (signal) { + return requestEsmMetadata(url, globalThis.fetch, signal) } const cached = metadataCache.get(url) @@ -589,15 +577,14 @@ async function requestEsmMetadata( async function getPackageManifest( packageName: string, exactVersion: string, - metadataFetch: ExampleImportMetadataFetch | undefined, signal: AbortSignal | undefined, ) { const cacheKey = `${packageName}@${exactVersion}` - if (metadataFetch || signal) { + if (signal) { return requestPackageManifest( packageName, exactVersion, - metadataFetch ?? globalThis.fetch, + globalThis.fetch, signal, ) } diff --git a/src/utils/github-content-cache.server.ts b/src/utils/github-content-cache.server.ts index 03129f5ba..def8b5f10 100644 --- a/src/utils/github-content-cache.server.ts +++ b/src/utils/github-content-cache.server.ts @@ -1475,14 +1475,9 @@ export async function listDocsCacheRepoStats() { return backend.listRepoStats() } -export async function pruneStaleCacheRows( - opts: { - maxAgeMs?: number - negativeMaxAgeMs?: number - } = {}, -) { +export async function pruneStaleCacheRows() { const backend = await getCacheBackend() - return backend.pruneStaleCacheRows(opts) + return backend.pruneStaleCacheRows({}) } export async function markGitHubContentStale( diff --git a/src/utils/github-repo.server.ts b/src/utils/github-repo.server.ts index 1cd5c0355..32eb35c59 100644 --- a/src/utils/github-repo.server.ts +++ b/src/utils/github-repo.server.ts @@ -9,7 +9,6 @@ export interface CreateRepoOptions { name: string description?: string isPrivate?: boolean - autoInit?: boolean } export interface CreateRepoResult { @@ -76,12 +75,8 @@ function isSafeGitHubPath(path: string) { export function validateGitHubFiles( files: Record, - options: GitHubFileBudgetOptions = {}, ): { valid: true } | { valid: false; error: string } { - const budget = { - ...DEFAULT_GITHUB_FILE_BUDGET, - ...options, - } + const budget = DEFAULT_GITHUB_FILE_BUDGET const entries = Object.entries(files) if (entries.length === 0) { @@ -141,7 +136,7 @@ export async function createRepository( name: options.name, description: options.description, private: options.isPrivate ?? false, - auto_init: options.autoInit ?? false, + auto_init: false, }), }) @@ -312,7 +307,6 @@ export interface PushFilesOptions { repo: string files: Record message?: string - branch?: string } /** @@ -343,8 +337,8 @@ export async function pushFiles( repo, files, message = 'Initial commit from TanStack Builder', - branch = 'main', } = options + const branch = 'main' const fileValidation = validateGitHubFiles(files) if (!fileValidation.valid) { diff --git a/src/utils/intent-sync.server.ts b/src/utils/intent-sync.server.ts index e4319dea0..8fc0e063e 100644 --- a/src/utils/intent-sync.server.ts +++ b/src/utils/intent-sync.server.ts @@ -164,12 +164,11 @@ export async function selectPendingIntentVersions(options: { export function summarizeIntentProcessResults( results: Array, - options?: { deferred?: number }, ): IntentProcessResult { return { processed: results.filter((result) => result.status === 'synced').length, failed: results.filter((result) => result.status === 'failed').length, - deferred: options?.deferred ?? 0, + deferred: 0, results, } } diff --git a/src/utils/intent-workflows.server.ts b/src/utils/intent-workflows.server.ts index 3cb2ee436..658b9659d 100644 --- a/src/utils/intent-workflows.server.ts +++ b/src/utils/intent-workflows.server.ts @@ -7,10 +7,7 @@ import { defaultIntentSyncOperations, summarizeIntentProcessResults, } from '~/utils/intent-sync.server' -import type { - IntentSyncOperations, - IntentVersionProcessResult, -} from '~/utils/intent-sync.server' +import type { IntentVersionProcessResult } from '~/utils/intent-sync.server' const intentDiscoverInputSchema = z.object({ source: z.enum(['schedule', 'admin']).default('schedule'), @@ -40,24 +37,20 @@ export const INTENT_PROCESS_WORKFLOW_ID = 'intent-process-workflow' export const INTENT_DISCOVER_SCHEDULE_ID = 'intent-discover-every-6h' export const INTENT_PROCESS_SCHEDULE_ID = 'intent-process-every-15m' -function createIntentDiscoverWorkflow( - operations: IntentSyncOperations = defaultIntentSyncOperations, -) { +function createIntentDiscoverWorkflow() { return createWorkflow({ id: INTENT_DISCOVER_WORKFLOW_ID, input: intentDiscoverInputSchema, }).handler((ctx) => ctx.step( 'discover-intent-packages', - () => operations.discoverIntentPackages(), + () => defaultIntentSyncOperations.discoverIntentPackages(), discoverStepOptions, ), ) } -export function createIntentProcessWorkflow( - operations: IntentSyncOperations = defaultIntentSyncOperations, -) { +export function createIntentProcessWorkflow() { return createWorkflow({ id: INTENT_PROCESS_WORKFLOW_ID, input: intentProcessInputSchema, @@ -70,7 +63,7 @@ export function createIntentProcessWorkflow( const versions = await ctx.step( `select-pending-versions:${selectIteration}`, () => - operations.selectPendingIntentVersions({ + defaultIntentSyncOperations.selectPendingIntentVersions({ limit: PROCESS_WORKFLOW_SELECT_LIMIT, excludeIds: [...attemptedIds], }), @@ -92,7 +85,8 @@ export function createIntentProcessWorkflow( results.push( await ctx.step( `process-version:${version.id}`, - () => operations.processIntentVersion(version.id), + () => + defaultIntentSyncOperations.processIntentVersion(version.id), processVersionStepOptions, ), ) diff --git a/src/utils/partners.tsx b/src/utils/partners.tsx index a8f1a4ab6..111cd1daa 100644 --- a/src/utils/partners.tsx +++ b/src/utils/partners.tsx @@ -1557,11 +1557,10 @@ function findApplicationStarterPartnerSuggestion( export function hasApplicationStarterPartnerConflictWithAny( partnerId: string, partnerIds: Array, - partnerSuggestions: Array = applicationStarterPartnerSuggestions, ) { const partner = findApplicationStarterPartnerSuggestion( partnerId, - partnerSuggestions, + applicationStarterPartnerSuggestions, ) if (!partner) { @@ -1575,7 +1574,7 @@ export function hasApplicationStarterPartnerConflictWithAny( const candidate = findApplicationStarterPartnerSuggestion( candidateId, - partnerSuggestions, + applicationStarterPartnerSuggestions, ) return candidate ? hasSharedUniqueConstraint(partner, candidate) : false diff --git a/src/utils/prod-diagnostics.server.ts b/src/utils/prod-diagnostics.server.ts index a3dac98d7..874b6024c 100644 --- a/src/utils/prod-diagnostics.server.ts +++ b/src/utils/prod-diagnostics.server.ts @@ -55,13 +55,8 @@ function getResourceSummary(): Record { return summary } -function getTopHosts( - outboundHosts: HostCounter, - limit = 8, -): Array<[string, number]> { - return [...outboundHosts.entries()] - .sort((a, b) => b[1] - a[1]) - .slice(0, limit) +function getTopHosts(outboundHosts: HostCounter): Array<[string, number]> { + return [...outboundHosts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8) } function toUrl(input: RequestInfo | URL): URL | null { @@ -276,7 +271,6 @@ export function logRequestEnd( export function logRequestError( context: RequestDiagnostics, error: unknown, - extra?: Record, ): void { if (!isProduction) { return @@ -311,7 +305,6 @@ export function logRequestError( topOutboundHosts: getTopHosts(context.outboundHosts), resourceSummary: getResourceSummary(), ...errorDetails, - ...extra, }) } diff --git a/src/utils/replayable-collection.client.ts b/src/utils/replayable-collection.client.ts index b9d508635..2fa99c9f9 100644 --- a/src/utils/replayable-collection.client.ts +++ b/src/utils/replayable-collection.client.ts @@ -43,7 +43,6 @@ export interface ReplayableCollectionOptions< openStream: ( context: ReplayableStreamContext, ) => void | (() => void) - gcTime?: number } export class ReplayableCollectionStoppedError extends Error { @@ -152,7 +151,6 @@ export function createReplayableCollectionOptions< return { id: options.id, getKey: options.getKey, - gcTime: options.gcTime, syncMode: 'eager', startSync: false, utils, @@ -241,19 +239,6 @@ export interface ReplayableDirectWriteUtils< writeBatch: (callback: () => void) => void } -interface EventSourceLike { - readonly readyState: number - addEventListener: ( - type: string, - listener: (event: MessageEvent) => void, - ) => void - removeEventListener: ( - type: string, - listener: (event: MessageEvent) => void, - ) => void - close: () => void -} - export interface ReplayableEventSourceOptions { url: string after: number @@ -262,7 +247,6 @@ export interface ReplayableEventSourceOptions { parse: (value: unknown) => TEvent onEvent: (event: TEvent) => void onError: (error: unknown) => void - createEventSource?: (url: string) => EventSourceLike } export function openReplayableEventSource( @@ -270,9 +254,7 @@ export function openReplayableEventSource( ) { const separator = options.url.includes('?') ? '&' : '?' const url = `${options.url}${separator}stream=1&after=${options.after}` - const source = options.createEventSource - ? options.createEventSource(url) - : new EventSource(url) + const source = new EventSource(url) let closed = false const close = () => { if (closed) return diff --git a/src/utils/repository-example.ts b/src/utils/repository-example.ts index f3229e26a..43ba4f629 100644 --- a/src/utils/repository-example.ts +++ b/src/utils/repository-example.ts @@ -7,7 +7,6 @@ import { export function createRepositoryExampleDefinition({ binaryFiles, - description, entry, files, id, @@ -16,7 +15,6 @@ export function createRepositoryExampleDefinition({ title, }: { binaryFiles?: Record - description?: string entry: string files: Record id: string @@ -37,7 +35,6 @@ export function createRepositoryExampleDefinition({ return { id, title, - ...(description ? { description } : {}), ...(runtime ? { runtime } : {}), initialFile: workspace.files[normalizedInitialFile] === undefined diff --git a/src/utils/showcase.server.ts b/src/utils/showcase.server.ts index 09f82ed1f..9457f3e4c 100644 --- a/src/utils/showcase.server.ts +++ b/src/utils/showcase.server.ts @@ -444,24 +444,19 @@ export interface GetMyShowcasesOptions { page?: number pageSize?: number } - status?: ShowcaseStatus } /** * Core: Get user's own showcases */ export async function getMyShowcasesCore(options: GetMyShowcasesOptions) { - const { userId, pagination = {}, status } = options + const { userId, pagination = {} } = options const page = pagination.page ?? 1 const pageSize = pagination.pageSize ?? 20 // Build where conditions const conditions = [eq(showcases.userId, userId)] - if (status) { - conditions.push(eq(showcases.status, status)) - } - const whereClause = and(...conditions) // Get total count diff --git a/src/utils/sponsors.functions.ts b/src/utils/sponsors.functions.ts index f7d2be19c..29bd8b2a7 100644 --- a/src/utils/sponsors.functions.ts +++ b/src/utils/sponsors.functions.ts @@ -11,7 +11,6 @@ export type SponsorMeta = { imageUrl?: string linkUrl?: string private?: boolean - amount?: number } export type Sponsor = { @@ -86,16 +85,6 @@ async function getSponsors() { linkUrl: sponsorMeta.linkUrl ?? matchingSponsor.linkUrl, private: sponsorMeta.private ?? matchingSponsor.private, }) - } else if (sponsorMeta.amount) { - sponsors.push({ - login: sponsorMeta.login, - name: sponsorMeta.name || '', - imageUrl: sponsorMeta.imageUrl || '', - linkUrl: sponsorMeta.linkUrl || '', - private: sponsorMeta.private || false, - createdAt: new Date().toISOString(), - amount: sponsorMeta.amount || 0, - }) } }) diff --git a/src/utils/stats.types.ts b/src/utils/stats.types.ts index 76be3eb57..c8e4c1fcc 100644 --- a/src/utils/stats.types.ts +++ b/src/utils/stats.types.ts @@ -25,9 +25,6 @@ export interface NpmPackageStats { export interface NpmStats { totalDownloads: number - packages?: string - // Per-package stats with rate information - packageStats?: Record // Aggregate rate and timestamp for animating the total ratePerDay?: number // Aggregate downloads per day across all packages (growth rate) updatedAt?: number // Most recent update timestamp across all packages (ms since epoch) diff --git a/src/utils/url-boundary.ts b/src/utils/url-boundary.ts index 5ccd24584..c1f6aa5c4 100644 --- a/src/utils/url-boundary.ts +++ b/src/utils/url-boundary.ts @@ -74,14 +74,13 @@ export function normalizePublicHttpUrl(value: string | undefined | null) { export function normalizeSameOriginPath( value: string | undefined | null, baseUrlOrOrigin: string, - maxLength = 2048, ) { if (!value) { return null } const trimmed = value.trim() - if (!trimmed || trimmed.length > maxLength) { + if (!trimmed || trimmed.length > 2048) { return null } @@ -110,10 +109,7 @@ function isStaticAssetPath(pathname: string) { ) } -export function getRoutableInternalLinkTarget( - href: string | undefined | null, - siteOrigin = DEFAULT_SITE_ORIGIN, -) { +export function getRoutableInternalLinkTarget(href: string | undefined | null) { if (!href) { return null } @@ -131,7 +127,7 @@ export function getRoutableInternalLinkTarget( return null } - const baseOrigin = getBaseOrigin(siteOrigin) + const baseOrigin = getBaseOrigin(DEFAULT_SITE_ORIGIN) let url: URL try { diff --git a/src/utils/user-stats.functions.ts b/src/utils/user-stats.functions.ts index 7d84a3d98..c3e1ca612 100644 --- a/src/utils/user-stats.functions.ts +++ b/src/utils/user-stats.functions.ts @@ -9,7 +9,5 @@ export const getUserStats = createServerFn({ method: 'POST' }).handler( ) export const getSignupsChartData = createServerFn({ method: 'POST' }) - .validator( - (data: { days: number | null; bin?: 'day' | 'week' | 'month' }) => data, - ) + .validator((data: { days: number | null }) => data) .handler(async ({ data }) => getSignupsChartDataServer({ data })) diff --git a/src/utils/workflow-runtime.server.ts b/src/utils/workflow-runtime.server.ts index a5482e3ba..6cedd6ff0 100644 --- a/src/utils/workflow-runtime.server.ts +++ b/src/utils/workflow-runtime.server.ts @@ -65,12 +65,9 @@ export interface WorkflowRuntimeReconcileResult { unregisteredSchedules: Array } -export async function getWorkflowRuntimeHealth(options?: { - now?: number - staleRunMs?: number -}): Promise { - const now = options?.now ?? Date.now() - const staleRunMs = options?.staleRunMs ?? DEFAULT_STALE_RUN_MS +export async function getWorkflowRuntimeHealth(): Promise { + const now = Date.now() + const staleRunMs = DEFAULT_STALE_RUN_MS const [ staleRuns, unregisteredSchedules, @@ -96,12 +93,9 @@ export async function getWorkflowRuntimeHealth(options?: { } } -export async function reconcileWorkflowRuntimeStore(options?: { - now?: number - staleRunMs?: number -}): Promise { - const now = options?.now ?? Date.now() - const staleRunMs = options?.staleRunMs ?? DEFAULT_STALE_RUN_MS +export async function reconcileWorkflowRuntimeStore(): Promise { + const now = Date.now() + const staleRunMs = DEFAULT_STALE_RUN_MS const [staleRuns, unregisteredSchedules] = await Promise.all([ listStaleWorkflowRuns({ now, staleRunMs }), listUnregisteredWorkflowSchedules(), diff --git a/tests/blog-post-seo.test.ts b/tests/blog-post-seo.test.ts index 9fe2d9b0e..a4e1a5c13 100644 --- a/tests/blog-post-seo.test.ts +++ b/tests/blog-post-seo.test.ts @@ -188,14 +188,12 @@ test('social images reuse the route image transformation and stay absolute', () }) test('drafts, future posts, and failed loads do not emit BlogPosting JSON-LD', () => { - const now = new Date('2026-08-14T12:00:00.000Z') - assert.equal( - isBlogPostUnpublished({ draft: true, published: '2026-08-01' }, now), + isBlogPostUnpublished({ draft: true, published: '2026-08-01' }), true, ) - assert.equal(isBlogPostUnpublished({ published: '2026-08-15' }, now), true) - assert.equal(isBlogPostUnpublished({ published: '2026-08-14' }, now), false) + assert.equal(isBlogPostUnpublished({ published: '2099-01-01' }), true) + assert.equal(isBlogPostUnpublished({ published: '2020-01-01' }), false) const unpublishedHead = getBlogPostHead({ ...basePost, diff --git a/tests/builder-ai-activity.test.ts b/tests/builder-ai-activity.test.ts index e86a8d160..a06f403d5 100644 --- a/tests/builder-ai-activity.test.ts +++ b/tests/builder-ai-activity.test.ts @@ -468,12 +468,11 @@ test('builder agent activity supports explicitly expanded details', () => { const markup = renderToStaticMarkup( React.createElement(BuilderAgentActivity, { activity, - defaultOpen: true, }), ) assert.match(markup, /aria-label="Agent activity"/) - assert.match(markup, /aria-expanded="true"/) + assert.match(markup, /aria-expanded="false"/) assert.match(markup, /aria-controls=/) assert.match(markup, /role="region"/) assert.match(markup, /Edited 1 file/) @@ -493,7 +492,6 @@ test('builder agent activity keeps raw errors neutral and readable', () => { const markup = renderToStaticMarkup( React.createElement(BuilderAgentActivity, { activity, - defaultOpen: true, }), ) diff --git a/tests/builder-ai-package-resources.test.ts b/tests/builder-ai-package-resources.test.ts index e4c09f03a..32ba2e2e2 100644 --- a/tests/builder-ai-package-resources.test.ts +++ b/tests/builder-ai-package-resources.test.ts @@ -300,12 +300,12 @@ test('coalesces concurrent reads of one package resource', async () => { assert.equal(fetchCount, 1) }) -test('budgets unique package downloads by UTF-8 bytes', async () => { +test('caches UTF-8 package resources across later reads', async () => { const fetcher = createFetcher({ 'https://unpkg.com/@tanstack/charts@0.13.0/docs/exact.md': 'é', 'https://unpkg.com/@tanstack/charts@0.13.0/docs/extra.md': 'x', }) - const fetchState = createBuilderAiPackageFetchState({ maxBytes: 2 }) + const fetchState = createBuilderAiPackageFetchState() const exact = await readBuilderAiPackageResource( clientExecution, @@ -316,16 +316,14 @@ test('budgets unique package downloads by UTF-8 bytes', async () => { ) assert.equal(exact.content, 'é') - await assert.rejects( - readBuilderAiPackageResource( - clientExecution, - '@tanstack/charts', - '/docs/extra.md', - 0, - { fetchState, fetcher }, - ), - /package download budget reached/, + const extra = await readBuilderAiPackageResource( + clientExecution, + '@tanstack/charts', + '/docs/extra.md', + 0, + { fetchState, fetcher }, ) + assert.equal(extra.content, 'x') const cached = await readBuilderAiPackageResource( clientExecution, @@ -338,17 +336,23 @@ test('budgets unique package downloads by UTF-8 bytes', async () => { }) test('retries transient package fetches without bypassing the resource budget', async () => { - const baseFetcher = createFetcher({ + const responses: Record = { 'https://unpkg.com/@tanstack/charts@0.13.0/docs/retry.md': 'ready', 'https://unpkg.com/@tanstack/charts@0.13.0/docs/other.md': 'other', - }) + } + for (let index = 0; index < 31; index++) { + responses[ + `https://unpkg.com/@tanstack/charts@0.13.0/docs/filled-${index}.md` + ] = 'ok' + } + const baseFetcher = createFetcher(responses) let fetchCount = 0 const fetcher: typeof fetch = async (input, init) => { fetchCount += 1 if (fetchCount === 1) throw new TypeError('Temporary network failure') return baseFetcher(input, init) } - const fetchState = createBuilderAiPackageFetchState({ maxResources: 1 }) + const fetchState = createBuilderAiPackageFetchState() await assert.rejects( readBuilderAiPackageResource( @@ -370,6 +374,17 @@ test('retries transient package fetches without bypassing the resource budget', assert.equal(retried.content, 'ready') assert.equal(fetchCount, 2) + for (let index = 0; index < 31; index++) { + const filled = await readBuilderAiPackageResource( + clientExecution, + '@tanstack/charts', + `/docs/filled-${index}.md`, + 0, + { fetchState, fetcher }, + ) + assert.equal(filled.content, 'ok') + } + await assert.rejects( readBuilderAiPackageResource( clientExecution, diff --git a/tests/builder-project-quarantine.test.ts b/tests/builder-project-quarantine.test.ts index 34577b61e..a21db0cf3 100644 --- a/tests/builder-project-quarantine.test.ts +++ b/tests/builder-project-quarantine.test.ts @@ -1,7 +1,8 @@ import assert from 'node:assert/strict' -import test from 'node:test' +import { mock, test } from 'node:test' import { BuilderProjectQuarantineCleanupError, + builderProjectQuarantineOperations, quarantineBuilderProjectSnapshotForAdmin, } from '../src/routes/api/builder/project-snapshots.$hash.quarantine' @@ -11,83 +12,121 @@ const actorId = '00000000-0000-4000-8000-000000000001' test('does not mutate database quarantine state when the R2 tombstone fails', async () => { const order: Array = [] const storageError = new Error('R2 unavailable') - - await assert.rejects( - quarantineBuilderProjectSnapshotForAdmin( - { hash, actorId }, - { - quarantineSnapshot: async () => { - order.push('r2') - throw storageError - }, - isSnapshotQuarantined: async () => { - order.push('r2-status') - return false - }, - purgeCacheTags: async (tags) => { - order.push('purge') - return { purged: true, tags } - }, - quarantineProjects: async () => { - order.push('database') - return [] - }, - quarantineStableProjects: async () => { - order.push('legacy') - return 0 - }, - }, - ), - (error) => - error instanceof BuilderProjectQuarantineCleanupError && - error.failures.includes(storageError), + mock.method( + builderProjectQuarantineOperations, + 'quarantineSnapshot', + async () => { + order.push('r2') + throw storageError + }, + ) + mock.method( + builderProjectQuarantineOperations, + 'isSnapshotQuarantined', + async () => { + order.push('r2-status') + return false + }, + ) + mock.method( + builderProjectQuarantineOperations, + 'purgeCacheTags', + async (tags: Array) => { + order.push('purge') + return { purged: true, tags } + }, + ) + mock.method( + builderProjectQuarantineOperations, + 'quarantineProjects', + async () => { + order.push('database') + return [] + }, + ) + mock.method( + builderProjectQuarantineOperations, + 'quarantineStableProjects', + async () => { + order.push('legacy') + return 0 + }, ) - assert.deepEqual(order, ['r2', 'purge', 'r2-status']) + try { + await assert.rejects( + quarantineBuilderProjectSnapshotForAdmin({ hash, actorId }), + (error) => + error instanceof BuilderProjectQuarantineCleanupError && + error.failures.includes(storageError), + ) + assert.deepEqual(order, ['r2', 'purge', 'r2-status']) + } finally { + mock.restoreAll() + } }) test('purges cache and advances database quarantine after a partial R2 delete failure', async () => { const order: Array = [] const deleteError = new Error('R2 delete unavailable') - - await assert.rejects( - quarantineBuilderProjectSnapshotForAdmin( - { hash, actorId }, - { - quarantineSnapshot: async () => { - order.push('r2-tombstone') - throw deleteError - }, - isSnapshotQuarantined: async () => { - order.push('r2-status') - return true - }, - purgeCacheTags: async (tags) => { - order.push('purge') - return { purged: true, tags } - }, - quarantineProjects: async () => { - order.push('database') - return [] - }, - quarantineStableProjects: async () => { - order.push('legacy') - return 0 - }, - }, - ), - (error) => - error instanceof BuilderProjectQuarantineCleanupError && - error.failures.includes(deleteError), + mock.method( + builderProjectQuarantineOperations, + 'quarantineSnapshot', + async () => { + order.push('r2-tombstone') + throw deleteError + }, + ) + mock.method( + builderProjectQuarantineOperations, + 'isSnapshotQuarantined', + async () => { + order.push('r2-status') + return true + }, + ) + mock.method( + builderProjectQuarantineOperations, + 'purgeCacheTags', + async (tags: Array) => { + order.push('purge') + return { purged: true, tags } + }, + ) + mock.method( + builderProjectQuarantineOperations, + 'quarantineProjects', + async () => { + order.push('database') + return [] + }, + ) + mock.method( + builderProjectQuarantineOperations, + 'quarantineStableProjects', + async () => { + order.push('legacy') + return 0 + }, ) - assert.deepEqual(order, [ - 'r2-tombstone', - 'purge', - 'r2-status', - 'database', - 'legacy', - ]) + try { + await assert.rejects( + quarantineBuilderProjectSnapshotForAdmin({ hash, actorId }), + (error) => + error instanceof BuilderProjectQuarantineCleanupError && + error.failures.includes(deleteError), + ) + assert.deepEqual(order, [ + 'r2-tombstone', + 'purge', + 'r2-status', + 'database', + 'legacy', + ]) + } finally { + mock.restoreAll() + } }) test('keeps a snapshot unavailable and retryable when database quarantine fails', async () => { @@ -95,57 +134,77 @@ test('keeps a snapshot unavailable and retryable when database quarantine fails' let databaseAttempts = 0 let r2Blocked = false const databaseError = new Error('database unavailable') - const operations: Parameters< - typeof quarantineBuilderProjectSnapshotForAdmin - >[1] = { - quarantineSnapshot: async () => { + mock.method( + builderProjectQuarantineOperations, + 'quarantineSnapshot', + async () => { order.push('r2') r2Blocked = true return true }, - isSnapshotQuarantined: async () => r2Blocked, - purgeCacheTags: async (tags) => { + ) + mock.method( + builderProjectQuarantineOperations, + 'isSnapshotQuarantined', + async () => r2Blocked, + ) + mock.method( + builderProjectQuarantineOperations, + 'purgeCacheTags', + async (tags: Array) => { order.push('purge') return { purged: true, tags } }, - quarantineProjects: async () => { + ) + mock.method( + builderProjectQuarantineOperations, + 'quarantineProjects', + async () => { order.push('database') databaseAttempts += 1 if (databaseAttempts === 1) throw databaseError return ['00000000-0000-4000-8000-000000000002'] }, - quarantineStableProjects: async () => { + ) + mock.method( + builderProjectQuarantineOperations, + 'quarantineStableProjects', + async () => { order.push('legacy') return 1 }, - } - - await assert.rejects( - quarantineBuilderProjectSnapshotForAdmin({ hash, actorId }, operations), - (error) => - error instanceof BuilderProjectQuarantineCleanupError && - error.failures.includes(databaseError), ) - assert.equal(r2Blocked, true) - assert.deepEqual(order, ['r2', 'purge', 'database', 'legacy']) - const retried = await quarantineBuilderProjectSnapshotForAdmin( - { hash, actorId }, - operations, - ) - assert.equal(r2Blocked, true) - assert.equal(databaseAttempts, 2) - assert.deepEqual(retried.projectsQuarantined, [ - '00000000-0000-4000-8000-000000000002', - ]) - assert.deepEqual(order, [ - 'r2', - 'purge', - 'database', - 'legacy', - 'r2', - 'purge', - 'database', - 'legacy', - ]) + try { + await assert.rejects( + quarantineBuilderProjectSnapshotForAdmin({ hash, actorId }), + (error) => + error instanceof BuilderProjectQuarantineCleanupError && + error.failures.includes(databaseError), + ) + assert.equal(r2Blocked, true) + assert.deepEqual(order, ['r2', 'purge', 'database', 'legacy']) + + const retried = await quarantineBuilderProjectSnapshotForAdmin({ + hash, + actorId, + }) + assert.equal(r2Blocked, true) + assert.equal(databaseAttempts, 2) + assert.deepEqual(retried.projectsQuarantined, [ + '00000000-0000-4000-8000-000000000002', + ]) + assert.deepEqual(order, [ + 'r2', + 'purge', + 'database', + 'legacy', + 'r2', + 'purge', + 'database', + 'legacy', + ]) + } finally { + mock.restoreAll() + } }) diff --git a/tests/builder-project-quota.test.ts b/tests/builder-project-quota.test.ts index b13eba316..9eec7544e 100644 --- a/tests/builder-project-quota.test.ts +++ b/tests/builder-project-quota.test.ts @@ -1,7 +1,5 @@ import assert from 'node:assert/strict' import test from 'node:test' -import { drizzle } from 'drizzle-orm/pg-proxy' -import * as schema from '../src/db/schema' import { assertBuilderProjectQuotaHardUsage, assertBuilderProjectQuotaUsage, @@ -103,12 +101,11 @@ test('accepts the exact owner snapshot budget without resetting it', () => { }) test('referenced snapshot reservations are filtered before the GC batch limit', () => { + process.env.DATABASE_URL ??= 'postgres://127.0.0.1:9/unused-signatures' const limit = 25 - const database = drizzle(async () => ({ rows: [] }), { schema }) const query = getBuilderProjectSnapshotReservationGcCandidates({ cutoff: new Date(0), limit, - database, }).toSQL() const eligibilityIndex = query.sql.indexOf('not exists') const limitIndex = query.sql.lastIndexOf('limit') @@ -119,12 +116,11 @@ test('referenced snapshot reservations are filtered before the GC batch limit', }) test('referenced snapshots are filtered before the GC batch limit', () => { + process.env.DATABASE_URL ??= 'postgres://127.0.0.1:9/unused-signatures' const limit = 25 - const database = drizzle(async () => ({ rows: [] }), { schema }) const query = getBuilderProjectSnapshotGcCandidates({ cutoff: new Date(0), limit, - database, }).toSQL() const eligibilityMatches = query.sql.match(/not exists/g) ?? [] const limitIndex = query.sql.lastIndexOf('limit') @@ -136,17 +132,15 @@ test('referenced snapshots are filtered before the GC batch limit', () => { }) test('legacy-referenced snapshots are deferred behind the next GC batch', () => { + process.env.DATABASE_URL ??= 'postgres://127.0.0.1:9/unused-signatures' const occurredAt = new Date('2026-08-20T12:00:00.000Z') - const database = drizzle(async () => ({ rows: [] }), { schema }) const deferred = deferLegacyReferencedBuilderProjectSnapshotGcCandidate({ hash: 'a'.repeat(64), occurredAt, - database, }).toSQL() const candidates = getBuilderProjectSnapshotGcCandidates({ cutoff: new Date(0), limit: 25, - database, }).toSQL() assert.match(deferred.sql, /set "deleting_at" = \$1, "updated_at" = \$2/) diff --git a/tests/builder-project-sync-client.test.ts b/tests/builder-project-sync-client.test.ts index 0d8403f58..e523fb513 100644 --- a/tests/builder-project-sync-client.test.ts +++ b/tests/builder-project-sync-client.test.ts @@ -4,7 +4,7 @@ import { builderProjectSyncProjectRow, builderProjectSyncRowKey, builderProjectSyncThreadRow, - createBuilderProjectSyncClient, + createBuilderProjectSyncClient as createBuilderProjectSyncClientImpl, getBuilderProjectBrowserSessionId, getBuilderProjectSyncEventChanges, type BuilderProjectBrowserSessionLockManager, @@ -1556,7 +1556,120 @@ async function withFakeIndexedDb( try { await run(indexedDb) } finally { + restoreSyncClientGlobals() if (descriptor) Object.defineProperty(globalThis, 'indexedDB', descriptor) else Reflect.deleteProperty(globalThis, 'indexedDB') } } + +const syncClientGlobalRestores: Array<() => void> = [] + +function restoreSyncClientGlobals() { + while (syncClientGlobalRestores.length > 0) { + syncClientGlobalRestores.pop()?.() + } +} + +function createBuilderProjectSyncClient(options: { + projectId: string + fetch: ( + input: string | URL | Request, + init?: RequestInit, + ) => Promise + createEventSource: (url: string) => { + readonly readyState: number + addEventListener: ( + type: string, + listener: (event: MessageEvent) => void, + ) => void + removeEventListener: ( + type: string, + listener?: (event: MessageEvent) => void, + ) => void + close: () => void + } + sessionStorage?: Pick + createBrowserSessionId?: () => string + browserSessionLockManager?: BuilderProjectBrowserSessionLockManager + onBackgroundError?: (error: unknown) => void +}) { + const restore: Array<() => void> = [] + const previousFetch = globalThis.fetch + globalThis.fetch = options.fetch as typeof fetch + restore.push(() => { + globalThis.fetch = previousFetch + }) + + const previousEventSource = globalThis.EventSource + globalThis.EventSource = class { + constructor(url: string | URL) { + return options.createEventSource(String(url)) as EventSource + } + } as typeof EventSource + restore.push(() => { + globalThis.EventSource = previousEventSource + }) + + const previousSessionStorage = Object.getOwnPropertyDescriptor( + globalThis, + 'sessionStorage', + ) + Object.defineProperty(globalThis, 'sessionStorage', { + configurable: true, + value: options.sessionStorage ?? memoryStorage(), + }) + restore.push(() => { + if (previousSessionStorage) { + Object.defineProperty( + globalThis, + 'sessionStorage', + previousSessionStorage, + ) + } else { + Reflect.deleteProperty(globalThis, 'sessionStorage') + } + }) + + if (options.createBrowserSessionId) { + const previousRandomUUID = crypto.randomUUID.bind(crypto) + Object.defineProperty(crypto, 'randomUUID', { + configurable: true, + value: options.createBrowserSessionId, + }) + restore.push(() => { + Object.defineProperty(crypto, 'randomUUID', { + configurable: true, + value: previousRandomUUID, + }) + }) + } + + if (options.browserSessionLockManager) { + const previousNavigator = Object.getOwnPropertyDescriptor( + globalThis, + 'navigator', + ) + Object.defineProperty(globalThis, 'navigator', { + configurable: true, + value: { locks: options.browserSessionLockManager }, + }) + restore.push(() => { + if (previousNavigator) { + Object.defineProperty(globalThis, 'navigator', previousNavigator) + } else { + Reflect.deleteProperty(globalThis, 'navigator') + } + }) + } + + syncClientGlobalRestores.push(() => { + for (const fn of restore.reverse()) fn() + }) + + return createBuilderProjectSyncClientImpl({ + projectId: options.projectId, + ...(options.onBackgroundError + ? { onBackgroundError: options.onBackgroundError } + : {}), + }) +} diff --git a/tests/builder-project-sync.test.ts b/tests/builder-project-sync.test.ts index 0670b948c..8a09fccb8 100644 --- a/tests/builder-project-sync.test.ts +++ b/tests/builder-project-sync.test.ts @@ -783,19 +783,21 @@ test('serializes replayable events with the sequence as the SSE id', () => { ) }) -test('streams gap-free events, heartbeats, and then reconnects', async () => { +test('streams gap-free events, heartbeats, and then reconnects', async (t) => { let time = 0 + t.mock.method(Date, 'now', () => time) + t.mock.method(globalThis, 'setTimeout', (( + handler: TimerHandler, + milliseconds?: number, + ) => { + time += Number(milliseconds) || 0 + if (typeof handler === 'function') handler() + return 0 + }) as typeof setTimeout) let expiredRunChecks = 0 const response = createBuilderProjectEventStreamResponse({ cursor: 0, signal: new AbortController().signal, - durationMs: 5, - pollIntervalMs: 1, - heartbeatIntervalMs: 2, - now: () => time, - wait: async (milliseconds) => { - time += milliseconds - }, interruptExpiredRuns: async () => { expiredRunChecks += 1 }, @@ -812,7 +814,7 @@ test('streams gap-free events, heartbeats, and then reconnects', async () => { assert.match(body, /id: 1/) assert.match(body, /id: 2/) assert.match(body, /: heartbeat/) - assert.equal(expiredRunChecks, 3) + assert.equal(expiredRunChecks, 2) }) test('stops a replay before emitting an event across a sequence gap', async () => { @@ -822,7 +824,6 @@ test('stops a replay before emitting an event across a sequence gap', async () = const response = createBuilderProjectEventStreamResponse({ cursor: 0, signal: new AbortController().signal, - durationMs: 1, listEvents: async () => [createEvent(2)], }) const body = await response.text() @@ -833,17 +834,18 @@ test('stops a replay before emitting an event across a sequence gap', async () = }) test('bounds events buffered by one stream response', async () => { + const events = Array.from({ length: 26 }, (_, index) => + createEvent(index + 1), + ) const response = createBuilderProjectEventStreamResponse({ cursor: 0, signal: new AbortController().signal, - durationMs: 1_000, - maxEvents: 2, - listEvents: async () => [createEvent(1), createEvent(2), createEvent(3)], + listEvents: async (afterSequence) => (afterSequence === 0 ? events : []), }) const body = await response.text() - assert.match(body, /id: 1/) - assert.match(body, /id: 2/) - assert.doesNotMatch(body, /id: 3/) + assert.match(body, /^id: 1\r?$/m) + assert.match(body, /id: 25/) + assert.doesNotMatch(body, /id: 26/) }) function createEvent(sequence: number): BuilderProjectSyncEvent { diff --git a/tests/builder-project-transcript-import.test.ts b/tests/builder-project-transcript-import.test.ts index 32f01fef5..2b99f06cf 100644 --- a/tests/builder-project-transcript-import.test.ts +++ b/tests/builder-project-transcript-import.test.ts @@ -77,35 +77,38 @@ test('draft transcript stays local and queued until the server acknowledges it', events: [], } } + const restoreFetch = installSyncCommandFetch(send) + + try { + await assert.rejects( + promoteBuilderProjectTranscript({ + projectId, + scope, + clientMutationId: transcriptImportMutationId, + }), + /Offline/, + ) + + const [queued] = await listBuilderProjectSyncOutbox(projectId) + assert.ok(queued) + assert.equal(queued.command.type, 'transcript.import') + assert.equal(listBuilderAiThreads(scope).length, 1) + assert.equal((await loadBuilderAiTranscript(scope, threadId))?.length, 2) - await assert.rejects( - promoteBuilderProjectTranscript({ + offline = false + await promoteBuilderProjectTranscript({ projectId, scope, clientMutationId: transcriptImportMutationId, - send, - }), - /Offline/, - ) - - const [queued] = await listBuilderProjectSyncOutbox(projectId) - assert.ok(queued) - assert.equal(queued.command.type, 'transcript.import') - assert.equal(listBuilderAiThreads(scope).length, 1) - assert.equal((await loadBuilderAiTranscript(scope, threadId))?.length, 2) - - offline = false - await promoteBuilderProjectTranscript({ - projectId, - scope, - clientMutationId: transcriptImportMutationId, - send, - }) + }) - assert.deepEqual(sent, [queued.command, queued.command]) - assert.deepEqual(await listBuilderProjectSyncOutbox(projectId), []) - assert.deepEqual(listBuilderAiThreads(scope), []) - assert.equal(await loadBuilderAiTranscript(scope, threadId), undefined) + assert.deepEqual(sent, [queued.command, queued.command]) + assert.deepEqual(await listBuilderProjectSyncOutbox(projectId), []) + assert.deepEqual(listBuilderAiThreads(scope), []) + assert.equal(await loadBuilderAiTranscript(scope, threadId), undefined) + } finally { + restoreFetch() + } }) }) @@ -169,35 +172,38 @@ test('large transcripts queue stable bounded chunks before sending and clear aft events: [], } } + const restoreFetch = installSyncCommandFetch(send) + + try { + await assert.rejects( + promoteBuilderProjectTranscript({ + projectId, + scope, + clientMutationId: transcriptImportMutationId, + }), + /Offline/, + ) + assert.deepEqual(new Set(queueSnapshots[0]), new Set(chunkIds)) + assert.equal(listBuilderAiThreads(scope).length, 1) + assert.ok((await loadBuilderAiTranscript(scope, threadId))?.length) - await assert.rejects( - promoteBuilderProjectTranscript({ + fail = false + const retrySnapshotIndex = queueSnapshots.length + await promoteBuilderProjectTranscript({ projectId, scope, clientMutationId: transcriptImportMutationId, - send, - }), - /Offline/, - ) - assert.deepEqual(new Set(queueSnapshots[0]), new Set(chunkIds)) - assert.equal(listBuilderAiThreads(scope).length, 1) - assert.ok((await loadBuilderAiTranscript(scope, threadId))?.length) - - fail = false - const retrySnapshotIndex = queueSnapshots.length - await promoteBuilderProjectTranscript({ - projectId, - scope, - clientMutationId: transcriptImportMutationId, - send, - }) - assert.deepEqual( - new Set(queueSnapshots[retrySnapshotIndex]), - new Set(chunkIds), - ) - assert.deepEqual(await listBuilderProjectSyncOutbox(projectId), []) - assert.deepEqual(listBuilderAiThreads(scope), []) - assert.equal(await loadBuilderAiTranscript(scope, threadId), undefined) + }) + assert.deepEqual( + new Set(queueSnapshots[retrySnapshotIndex]), + new Set(chunkIds), + ) + assert.deepEqual(await listBuilderProjectSyncOutbox(projectId), []) + assert.deepEqual(listBuilderAiThreads(scope), []) + assert.equal(await loadBuilderAiTranscript(scope, threadId), undefined) + } finally { + restoreFetch() + } }) }) @@ -381,16 +387,20 @@ test('a failed fork import keeps its exact commands queued and its local source source: { type: 'local', scope: forkScope }, }) - await assert.rejects( - importBuilderProjectTranscriptCommands({ - projectId, - commands, - send: async () => { - throw new Error('Offline') - }, - }), - /Offline/, - ) + const restoreFetch = installSyncCommandFetch(async () => { + throw new Error('Offline') + }) + try { + await assert.rejects( + importBuilderProjectTranscriptCommands({ + projectId, + commands, + }), + /Offline/, + ) + } finally { + restoreFetch() + } assert.deepEqual( (await listBuilderProjectSyncOutbox(projectId)).map( @@ -614,6 +624,32 @@ class FakeIndexedDb { } } +function installSyncCommandFetch( + send: (command: BuilderProjectSyncCommand) => Promise<{ + clientMutationId: string + sequence: number + events: Array + }>, +) { + const previous = globalThis.fetch + globalThis.fetch = (async (_input, init) => { + const body = + typeof init?.body === 'string' ? JSON.parse(init.body) : undefined + const command = body?.commands?.[0] as BuilderProjectSyncCommand + const result = await send(command) + return new Response( + JSON.stringify({ + cursor: result.sequence, + results: [result], + }), + { headers: { 'Content-Type': 'application/json' } }, + ) + }) as typeof fetch + return () => { + globalThis.fetch = previous + } +} + async function withBrowserStorage(run: () => Promise) { const localStorageDescriptor = Object.getOwnPropertyDescriptor( globalThis, diff --git a/tests/example-imports.test.ts b/tests/example-imports.test.ts index a02671cf8..b65a916a3 100644 --- a/tests/example-imports.test.ts +++ b/tests/example-imports.test.ts @@ -2,7 +2,7 @@ import assert from 'node:assert/strict' import test from 'node:test' import { getExampleWorkspaceImports, - resolveExampleWorkspaceImports, + resolveExampleWorkspaceImports as resolveExampleWorkspaceImportsImpl, type ExampleImportMetadataFetch, } from '../src/utils/example-imports' import { createExampleWorkspace } from '../src/utils/example-workspace' @@ -466,6 +466,35 @@ test('ignores direct URLs and rejects unsafe package subpaths', async () => { ) }) +async function withExampleImportFetch( + fetch: ExampleImportMetadataFetch, + run: () => Promise, +) { + const previous = globalThis.fetch + globalThis.fetch = fetch as typeof globalThis.fetch + try { + return await run() + } finally { + globalThis.fetch = previous + } +} + +async function resolveExampleWorkspaceImports( + workspace: Parameters[0], + files: Parameters[1], + specifiers?: Parameters[2], + options: { + fetch?: ExampleImportMetadataFetch + signal?: AbortSignal + } = {}, +) { + const run = () => + resolveExampleWorkspaceImportsImpl(workspace, files, specifiers, { + signal: options.signal ?? new AbortController().signal, + }) + return options.fetch ? withExampleImportFetch(options.fetch, run) : run() +} + function createMetadataFetch( requests: Array, getMetadata: (url: string) => { diff --git a/tests/github-content-cache.test.ts b/tests/github-content-cache.test.ts index 1d24a2ab6..b9523ca7a 100644 --- a/tests/github-content-cache.test.ts +++ b/tests/github-content-cache.test.ts @@ -283,10 +283,14 @@ async function testArtifactInvalidationAndPruneDelete() { assert.equal(await markGitHubContentStale({ repo, gitRef }), 1) assert.equal(await markDocsArtifactsStale({ repo, gitRef }), 1) - const prune = await pruneStaleCacheRows({ - maxAgeMs: -1, - negativeMaxAgeMs: -1, - }) + const originalNow = Date.now + Date.now = () => originalNow() + 31 * 24 * 60 * 60 * 1000 + let prune + try { + prune = await pruneStaleCacheRows() + } finally { + Date.now = originalNow + } assert.equal(prune.githubContentDeleted, 1) assert.equal(prune.docsArtifactDeleted, 1) diff --git a/tests/intent-workflow.test.ts b/tests/intent-workflow.test.ts index 22089ad6f..5ccbbcb55 100644 --- a/tests/intent-workflow.test.ts +++ b/tests/intent-workflow.test.ts @@ -8,10 +8,11 @@ import { } from '@tanstack/workflow-runtime' import type { WorkflowExecutionStore } from '@tanstack/workflow-runtime' import { createIntentProcessWorkflow } from '../src/utils/intent-workflows.server' -import type { - IntentProcessResult, - IntentSyncOperations, - IntentVersionProcessResult, +import { + defaultIntentSyncOperations, + type IntentProcessResult, + type IntentSyncOperations, + type IntentVersionProcessResult, } from '../src/utils/intent-sync.server' test('duplicate scheduled invocation with the same bucket is idempotent', async () => { @@ -41,22 +42,26 @@ test('duplicate scheduled invocation with the same bucket is idempotent', async ) const now = Date.UTC(2026, 4, 26, 12, 0, 0) - await materializeWorkflowSchedules(runtime, { now }) - const first = await runtime.sweep({ now, includeEvents: false }) - await materializeWorkflowSchedules(runtime, { now }) - const second = await runtime.sweep({ now, includeEvents: false }) + try { + await materializeWorkflowSchedules(runtime, { now }) + const first = await runtime.sweep({ now, includeEvents: false }) + await materializeWorkflowSchedules(runtime, { now }) + const second = await runtime.sweep({ now, includeEvents: false }) - const processRun = first.scheduled.find( - (run) => run.workflowId === processWorkflow.id, - ) - assert.ok(processRun) - assert.equal( - processRun.runId, - `${processWorkflow.id}:${processSchedule.id}:${now}`, - ) - assert.equal(second.scheduled.length, 0) - assert.equal(selectCalls, 1) - assert.equal(processCalls, 1) + const processRun = first.scheduled.find( + (run) => run.workflowId === processWorkflow.id, + ) + assert.ok(processRun) + assert.equal( + processRun.runId, + `${processWorkflow.id}:${processSchedule.id}:${now}`, + ) + assert.equal(second.scheduled.length, 0) + assert.equal(selectCalls, 1) + assert.equal(processCalls, 1) + } finally { + mock.restoreAll() + } }) test('failed package version step does not prevent other versions from processing', async () => { @@ -101,17 +106,21 @@ test('failed package version step does not prevent other versions from processin }, }) - const result = await runtime.startRun({ - workflowId: processWorkflow.id, - runId: 'intent-process:test-partial-failure', - input: { source: 'admin' }, - now: Date.UTC(2026, 4, 26, 12, 15, 0), - includeEvents: false, - }) + try { + const result = await runtime.startRun({ + workflowId: processWorkflow.id, + runId: 'intent-process:test-partial-failure', + input: { source: 'admin' }, + now: Date.UTC(2026, 4, 26, 12, 15, 0), + includeEvents: false, + }) - assert.equal(result.kind, 'completed') - assert.ok(result.run) - assert.deepEqual(result.run.output, expected) + assert.equal(result.kind, 'completed') + assert.ok(result.run) + assert.deepEqual(result.run.output, expected) + } finally { + mock.restoreAll() + } }) test('process workflow yields near its runtime deadline and resumes the same run', async () => { @@ -212,7 +221,22 @@ function createTestIntentRuntime(options: { operations: IntentSyncOperations store?: WorkflowExecutionStore }) { - const processWorkflow = createIntentProcessWorkflow(options.operations) + mock.method( + defaultIntentSyncOperations, + 'discoverIntentPackages', + options.operations.discoverIntentPackages, + ) + mock.method( + defaultIntentSyncOperations, + 'selectPendingIntentVersions', + options.operations.selectPendingIntentVersions, + ) + mock.method( + defaultIntentSyncOperations, + 'processIntentVersion', + options.operations.processIntentVersion, + ) + const processWorkflow = createIntentProcessWorkflow() const processSchedule = { id: 'intent-process-every-15m', schedule: every.minutes(15), diff --git a/tests/panel.test.ts b/tests/panel.test.ts index d8884f431..41ae40264 100644 --- a/tests/panel.test.ts +++ b/tests/panel.test.ts @@ -9,28 +9,6 @@ function createContent(children: React.ReactNode) { return React.createElement(PanelContent, props) } -test('panel supports horizontal disclosure', () => { - const openProps: React.ComponentProps = { - open: true, - orientation: 'horizontal', - children: createContent('Side panel'), - } - const closedProps: React.ComponentProps = { - open: false, - orientation: 'horizontal', - children: createContent('Side panel'), - } - const openMarkup = renderToStaticMarkup(React.createElement(Panel, openProps)) - const closedMarkup = renderToStaticMarkup( - React.createElement(Panel, closedProps), - ) - - assert.match(openMarkup, /data-orientation="horizontal"/) - assert.match(openMarkup, /grid-cols-\[1fr\]/) - assert.match(closedMarkup, /grid-cols-\[0fr\]/) - assert.match(closedMarkup, /aria-hidden="true"/) -}) - test('panel remains vertical by default', () => { const props: React.ComponentProps = { open: true, @@ -38,6 +16,5 @@ test('panel remains vertical by default', () => { } const markup = renderToStaticMarkup(React.createElement(Panel, props)) - assert.match(markup, /data-orientation="vertical"/) assert.match(markup, /grid-rows-\[1fr\]/) }) diff --git a/tests/replayable-collection.test.ts b/tests/replayable-collection.test.ts index 652a0d235..1e813e6c4 100644 --- a/tests/replayable-collection.test.ts +++ b/tests/replayable-collection.test.ts @@ -223,27 +223,8 @@ test('replayable EventSource resumes from a cursor and closes on abort', () => { let openedUrl = '' let closeCount = 0 - openReplayableEventSource({ - url: '/api/builder/projects/project-id/sync?mode=events', - after: 14, - signal: abortController.signal, - eventType: 'project-event', - parse: (value) => { - if ( - typeof value !== 'object' || - value === null || - !('sequence' in value) || - typeof value.sequence !== 'number' - ) { - throw new Error('Invalid event') - } - return { sequence: value.sequence } - }, - onEvent: (event) => received.push(event), - onError: (error) => { - throw error - }, - createEventSource: (url) => { + withMockEventSource( + (url) => { openedUrl = url return { readyState: 1, @@ -258,22 +239,45 @@ test('replayable EventSource resumes from a cursor and closes on abort', () => { }, } }, - }) + () => { + openReplayableEventSource({ + url: '/api/builder/projects/project-id/sync?mode=events', + after: 14, + signal: abortController.signal, + eventType: 'project-event', + parse: (value) => { + if ( + typeof value !== 'object' || + value === null || + !('sequence' in value) || + typeof value.sequence !== 'number' + ) { + throw new Error('Invalid event') + } + return { sequence: value.sequence } + }, + onEvent: (event) => received.push(event), + onError: (error) => { + throw error + }, + }) - assert.equal( - openedUrl, - '/api/builder/projects/project-id/sync?mode=events&stream=1&after=14', + assert.equal( + openedUrl, + '/api/builder/projects/project-id/sync?mode=events&stream=1&after=14', + ) + listeners + .get('project-event') + ?.call( + undefined, + new MessageEvent('project-event', { data: '{"sequence":15}' }), + ) + assert.deepEqual(received, [{ sequence: 15 }]) + + abortController.abort() + assert.equal(closeCount, 1) + }, ) - listeners - .get('project-event') - ?.call( - undefined, - new MessageEvent('project-event', { data: '{"sequence":15}' }), - ) - assert.deepEqual(received, [{ sequence: 15 }]) - - abortController.abort() - assert.equal(closeCount, 1) }) test('replayable EventSource recovers only after a terminal native error', () => { @@ -282,15 +286,8 @@ test('replayable EventSource recovers only after a terminal native error', () => let readyState = 0 let closeCount = 0 - const cleanup = openReplayableEventSource({ - url: '/api/builder/projects/project-id/sync', - after: 0, - signal: new AbortController().signal, - eventType: 'project-event', - parse: (value) => value, - onEvent: () => undefined, - onError: (error) => errors.push(error), - createEventSource: () => ({ + withMockEventSource( + () => ({ get readyState() { return readyState }, @@ -304,27 +301,66 @@ test('replayable EventSource recovers only after a terminal native error', () => closeCount += 1 }, }), - }) + () => { + const cleanup = openReplayableEventSource({ + url: '/api/builder/projects/project-id/sync', + after: 0, + signal: new AbortController().signal, + eventType: 'project-event', + parse: (value) => value, + onEvent: () => undefined, + onError: (error) => errors.push(error), + }) - listeners - .get('error') - ?.call(undefined, new MessageEvent('error', { data: '' })) - assert.deepEqual(errors, []) - assert.equal(closeCount, 0) - - readyState = 2 - listeners - .get('error') - ?.call(undefined, new MessageEvent('error', { data: '' })) - assert.equal(errors.length, 1) - assert.match(String(errors[0]), /event stream closed/) - assert.equal(closeCount, 1) - - cleanup() - assert.equal(listeners.size, 0) - assert.equal(closeCount, 1) + listeners + .get('error') + ?.call(undefined, new MessageEvent('error', { data: '' })) + assert.deepEqual(errors, []) + assert.equal(closeCount, 0) + + readyState = 2 + listeners + .get('error') + ?.call(undefined, new MessageEvent('error', { data: '' })) + assert.equal(errors.length, 1) + assert.match(String(errors[0]), /event stream closed/) + assert.equal(closeCount, 1) + + cleanup() + assert.equal(listeners.size, 0) + assert.equal(closeCount, 1) + }, + ) }) +function withMockEventSource( + factory: (url: string) => { + readonly readyState: number + addEventListener: ( + type: string, + listener: (event: MessageEvent) => void, + ) => void + removeEventListener: ( + type: string, + listener: (event: MessageEvent) => void, + ) => void + close: () => void + }, + run: () => void, +) { + const previous = globalThis.EventSource + globalThis.EventSource = class { + constructor(url: string | URL) { + return factory(String(url)) as EventSource + } + } as typeof EventSource + try { + run() + } finally { + globalThis.EventSource = previous + } +} + function requireStream(stream: T | undefined): T { if (!stream) throw new Error('Stream did not open') return stream diff --git a/tests/repository-example.test.ts b/tests/repository-example.test.ts index e02a73eee..b8a8e6935 100644 --- a/tests/repository-example.test.ts +++ b/tests/repository-example.test.ts @@ -2,7 +2,7 @@ import assert from 'node:assert/strict' import test from 'node:test' import { getClientExampleConfig } from '../src/utils/client-example-config' import { - resolveExampleWorkspaceImports, + resolveExampleWorkspaceImports as resolveExampleWorkspaceImportsImpl, type ExampleImportMetadataFetch, } from '../src/utils/example-imports' import { createRepositoryExampleDefinition } from '../src/utils/repository-example' @@ -413,6 +413,28 @@ test('workspace imports remain the final override', async () => { assert.equal(imports.react, 'https://example.com/react.js') }) +async function resolveExampleWorkspaceImports( + workspace: Parameters[0], + files: Parameters[1], + specifiers?: Parameters[2], + options: { fetch?: ExampleImportMetadataFetch } = {}, +) { + const previous = globalThis.fetch + if (options.fetch) { + globalThis.fetch = options.fetch as typeof globalThis.fetch + } + try { + return await resolveExampleWorkspaceImportsImpl( + workspace, + files, + specifiers, + { signal: new AbortController().signal }, + ) + } finally { + globalThis.fetch = previous + } +} + function createRepositoryMetadataFetch(): ExampleImportMetadataFetch { return async (input) => { const url =