Repository navigation
Web - #3
Web#3
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe PR adds CLI self-update and versioned release asset support, updates installers and release automation, and introduces a branded web landing page with documentation routes, authentication layouts, animated UI primitives, theme styling, and supporting configuration. ChangesCLI update and release flow
Bloom web experience
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant CLI
participant GitHub Releases
participant Release Asset
CLI->>GitHub Releases: request latest release metadata
GitHub Releases-->>CLI: return matching versioned asset
CLI->>Release Asset: download binary
Release Asset-->>CLI: return binary bytes
CLI->>CLI: stage and replace executable
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (10)
packages/web/lib/docs/data.ts-131-134 (1)
131-134: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRender ↑/↓ as separate alternatives, not a chord.
KeyCombojoins every key inrow.keyswith+, so this entry renders↑ + ↓. Split it into two rows with shared action text, or change the shortcut representation to distinguish alternatives from chorded composites.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/lib/docs/data.ts` around lines 131 - 134, Update the shortcut definition containing keys ["↑", "↓"] so the up and down arrows render as separate alternatives rather than a single chord, while preserving the shared “Move selection in command menu, mention menu, or dialogs” action text. Use separate rows or the existing alternative representation, and leave true multi-key chords unchanged.packages/web/app/docs/themes/page.tsx-11-17 (1)
11-17: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse WCAG relative luminance for foreground selection.
The weighted RGB threshold selects light text for colors such as Synthwave’s
#E86ACA, producing roughly 3:1 contrast for thetext-xscard content. Compute sRGB relative luminance and choose the foreground with the higher contrast ratio.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/app/docs/themes/page.tsx` around lines 11 - 17, Update contrastText to convert each sRGB channel to linearized values, compute WCAG relative luminance, and compare the contrast ratios of "`#1a0a12`" and "`#fff8fa`" against the background. Return whichever foreground provides the higher contrast ratio, preserving the existing color options.packages/web/components/site/bloom-logo.tsx-42-46 (1)
42-46: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse
outline-hiddenfor the logo link’s accessibility preservation.This project uses Tailwind v4, where
outline-noneis literaloutline-style: noneand no longer provides the forced-colors preservation thatoutline-hiddengives. Keep the existingfocus-visiblering styling, but avoid fully suppressing the outline.Suggested fix
- <Link href={href} className="outline-none focus-visible:ring-2 focus-visible:ring-ring"> + <Link href={href} className="outline-hidden focus-visible:ring-2 focus-visible:ring-ring">🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/site/bloom-logo.tsx` around lines 42 - 46, Update the Link element in the Bloom logo component to use Tailwind’s outline-hidden utility instead of outline-none, while preserving the existing focus-visible:ring-2 and focus-visible:ring-ring classes.packages/web/components/animate-ui/components/buttons/copy.tsx-55-92 (1)
55-92: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winGuard checks the wrong flag; overlapping timers can flicker the copied state.
if (copied) return;(Line 74) reads the externalcopiedprop, not the resolvedisCopiedstate, so in uncontrolled usage (the default) this guard is always a no-op sincecopiedstaysundefined. Rapid re-clicks queue multiple independentsetTimeoutcalls that can resetisCopiedout of order.🐛 Proposed fix
+ const timeoutRef = React.useRef<ReturnType<typeof setTimeout>>(); + const handleCopy = React.useCallback( (e: React.MouseEvent<HTMLButtonElement>) => { onClick?.(e); - if (copied) return; + if (isCopied) return; if (content) { navigator.clipboard .writeText(content) .then(() => { + if (timeoutRef.current) clearTimeout(timeoutRef.current); setIsCopied(true); onCopiedChange?.(true, content); - setTimeout(() => { + timeoutRef.current = setTimeout(() => { setIsCopied(false); onCopiedChange?.(false); }, delay); }) .catch((error) => { console.error('Error copying command', error); }); } }, - [onClick, copied, content, setIsCopied, onCopiedChange, delay], + [onClick, isCopied, content, setIsCopied, onCopiedChange, delay], );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/animate-ui/components/buttons/copy.tsx` around lines 55 - 92, Update CopyButton’s handleCopy guard to use the resolved isCopied state rather than the external copied prop, and prevent overlapping reset timers from independently changing the copied state. Ensure rapid clicks are ignored while already copied and only the active timer resets isCopied and invokes onCopiedChange(false).packages/web/components/animate-ui/primitives/animate/code-block.tsx-78-84 (1)
78-84: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
onDone/onWritefire on every effect re-run in non-writing mode.
onDoneandonWriteare in the dep array (Line 128); with inline callbacks from a consumer, each parent render re-runs this branch and re-emits completion events. Gate on a "already reported" ref or require stable callbacks.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/animate-ui/primitives/animate/code-block.tsx` around lines 78 - 84, Prevent the non-writing branch of the React.useEffect from emitting completion callbacks on every dependency-driven re-run. Use a ref to track whether completion has already been reported for the current code, reset it when a new write begins or the code changes, and only invoke onDone and onWrite once while preserving the existing visible-code update.packages/web/components/ui/text-generate-effect.tsx-39-49 (1)
39-49: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winText is unreadable without JS/animation and ignores reduced-motion.
Words start at
opacity-0+blur(10px); if the animation never runs (JS disabled, motion error, reduced-motion preference) the content is permanently invisible. Consider gating the initial hidden state or honoringuseReducedMotion().🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/ui/text-generate-effect.tsx` around lines 39 - 49, Update the word spans rendered by the text-generate effect so their content remains readable when animation does not run, including for users with reduced motion enabled. Use the component’s motion/reduced-motion handling to avoid applying the permanent opacity-0 and blur initial state in those cases, while preserving the animated reveal for users who allow motion.packages/web/components/ui/typewriter-effect.tsx-159-159 (1)
159-159: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winTypo:
lg:text:3xlis not a valid utility.Should be
lg:text-3xl; as written the class is dropped and the large-screen size jumps straight frommd:text-xltoxl:text-5xl.🐛 Proposed fix
- className="text-xs sm:text-base md:text-xl lg:text:3xl xl:text-5xl font-bold" + className="text-xs sm:text-base md:text-xl lg:text-3xl xl:text-5xl font-bold"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/ui/typewriter-effect.tsx` at line 159, Correct the responsive typography utility in the typewriter effect component’s className by changing the invalid lg:text:3xl token to the valid lg:text-3xl token, preserving the surrounding text-size classes.packages/web/app/globals.css-184-191 (1)
184-191: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winStylelint failure: blank line required before the declarations following
@apply.Also note
background-attachment: fixedis a known scroll-jank/perf hazard on mobile Safari for full-page gradients.🎨 Proposed fix
body { `@apply` bg-background text-foreground; + background-image:🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/app/globals.css` around lines 184 - 191, Update the body rule by adding a blank line between the `@apply` statement and the background declarations, and remove background-attachment: fixed to avoid the mobile Safari scroll-performance issue.Source: Linters/SAST tools
packages/web/components/animate-ui/primitives/texts/typing.tsx-63-124 (1)
63-124: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winInline
textarrays restart the animation on every parent re-render.
textis in the dep array and consumers pass an array literal (e.g.packages/web/components/landing/terminal-chassis.tsxpasses an inlinetext={[...]}), so each re-render of the parent tears down the timer chain and retypes from scratch. Depend on a stable key instead, e.g.JSON.stringify/join of the normalized array.🐛 Proposed fix
+ const texts = React.useMemo( + () => (typeof text === 'string' ? [text] : text), + // eslint-disable-next-line react-hooks/exhaustive-deps + [typeof text === 'string' ? text : text.join('\u0000')], + ); + React.useEffect(() => { if (!started) return; const timeoutIds: Array<ReturnType<typeof setTimeout>> = []; - const texts: string[] = typeof text === 'string' ? [text] : text; @@ - }, [text, duration, started, loop, holdDelay]); + }, [texts, duration, started, loop, holdDelay]);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/animate-ui/primitives/texts/typing.tsx` around lines 63 - 124, Update the React.useEffect dependency handling in the typing animation to avoid using the unstable text array identity. Normalize text once and derive a stable content key, such as a serialized or joined representation, then depend on that key instead of text while preserving restarts when the text contents actually change.packages/web/components/ui/accordion.tsx-59-59 (1)
59-59: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAdd the accordion animation theme tokens for the data-state utilities.
data-open:animate-accordion-downanddata-closed:animate-accordion-upare used inpackages/web/components/ui/accordion.tsx, butpackages/web/app/globals.cssonly defines--animate-spotlight. Without matching--animate-accordion-down/upvariables and@keyframes, these utilities won’t produce the intended open/close animations.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/ui/accordion.tsx` at line 59, Add the missing --animate-accordion-down and --animate-accordion-up theme tokens and their corresponding accordion-down and accordion-up `@keyframes` in globals.css, matching the data-open/data-closed utilities used by the accordion component. Preserve the existing --animate-spotlight definition.
🧹 Nitpick comments (14)
packages/web/public/install.sh (1)
56-73: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
legacy_asset_nameduplicatesdetect_asset_prefix's OS/arch detection.Both functions independently re-run the same
uname -s/uname -mcase logic. Sincedetect_asset_prefixalready validates and exits on unsupported OS/arch beforelegacy_asset_nameis ever called, this isn't a functional bug, but passing the already-resolvedos/archintolegacy_asset_name(or reusing a shared helper) would remove the duplication.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/public/install.sh` around lines 56 - 73, Update legacy_asset_name to reuse the already-resolved OS and architecture from detect_asset_prefix instead of repeating uname-based detection. Pass those values into legacy_asset_name, preserve its existing Windows .exe naming behavior, and remove the duplicate case logic.packages/web/components/landing/hero.tsx (1)
1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the repeated
var(--bloom-shadow)hard-shadow into a shared utility. The same neubrutalist shadow token is hand-rolled as an arbitrary Tailwind value at four sites, differing only by pixel offset; a shared@utilityclass (mirroring the existingbloom-embosspattern) would centralize future theme tuning.
packages/web/components/landing/hero.tsx#L67-90: replaceshadow-[4px_4px_0_0_var(--bloom-shadow)]/shadow-[3px_3px_0_0_var(--bloom-shadow)]with a sharedbloom-shadow-md/bloom-shadow-smutility.packages/web/components/site/theme-toggle.tsx#L20-30: replaceshadow-[2px_2px_0_0_var(--bloom-shadow)]with abloom-shadow-xsutility.packages/web/components/landing/feature-strip.tsx#L63-65: replaceshadow-[3px_3px_0_0_var(--bloom-shadow)]with the sharedbloom-shadow-smutility.packages/web/components/landing/install-cta.tsx#L66-79: replace bothshadow-[3px_3px_0_0_var(--bloom-shadow)]occurrences with the sharedbloom-shadow-smutility.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/landing/hero.tsx` at line 1, Extract the repeated var(--bloom-shadow) Tailwind values into shared `@utility` classes following the existing bloom-emboss pattern, defining bloom-shadow-xs, bloom-shadow-sm, and bloom-shadow-md for 2px, 3px, and 4px offsets. Replace the corresponding arbitrary shadow classes in the landing hero, theme toggle, feature strip, and install CTA while preserving all other styling.packages/web/components/auth/login-form.tsx (1)
116-135: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate brand-card header markup vs.
cli-auth-handoff.tsx.This Card wrapper (gradient bar,
BloomLogoheader, title/description) mirrors the localAuthShellcomponent added inpackages/web/components/auth/cli-auth-handoff.tsx(Lines 402-430), but with extraSpotlight/shadow-nonestyling not present there. Suggest extracting a shared component to keep both auth surfaces in sync.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/auth/login-form.tsx` around lines 116 - 135, The login form card header duplicates the brand-card structure used by AuthShell in cli-auth-handoff.tsx. Extract the shared gradient bar, BloomLogo, title, and description markup into a reusable component, then update both AuthShell and the login form to use it while preserving the login form’s Spotlight and shadow styling.packages/web/components/site/bloom-logo.tsx (1)
22-29: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winAvoid hardcoding
priorityon every logo instance.
BloomLogois reused in the header, footer, login form, and CLI auth card (multiple instances rendered on the same page, e.g. header + footer). Marking every instanceprioritydefeats the purpose of the hint — only the actual above-the-fold/LCP instance should preload.♻️ Suggested fix: make priority opt-in
type BloomLogoProps = { className?: string; href?: string; showWordmark?: boolean; size?: number; + priority?: boolean; }; export function BloomLogo({ className, href = "/", showWordmark = true, size = 36, + priority = false, }: BloomLogoProps) { ... <Image src="/logo.png" alt="Bloom" width={size} height={size} className="object-cover" - priority + priority={priority} />Then pass
priorityonly from the header instance.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/site/bloom-logo.tsx` around lines 22 - 29, Update the BloomLogo component to make the Image priority behavior opt-in rather than always enabled, exposing a priority prop with an appropriate default. Keep existing logo usages non-priority, and pass priority only from the header instance that represents the above-the-fold/LCP logo.packages/web/components/auth/cli-auth-handoff.tsx (1)
402-430: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate brand-card header markup vs.
login-form.tsx.This local
AuthShell(Card + gradient bar +BloomLogoheader) is nearly identical to the Card structure added inpackages/web/components/auth/login-form.tsx(Lines 121-135), but already differs in details (shadow-none,Spotlightbackground) — sign of drift from copy-pasting. Consider extracting a sharedAuthCard/AuthShellcomponent both files import.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/auth/cli-auth-handoff.tsx` around lines 402 - 430, Extract the duplicated branded Card/header structure from AuthShell and the corresponding login-form.tsx markup into a shared AuthCard/AuthShell component, then update both consumers to import and use it. Preserve each screen’s existing title, description, children/footer behavior, shadow styling, and Spotlight background through component props or composition rather than maintaining separate copied implementations.packages/web/components/animate-ui/primitives/animate/slot.tsx (1)
38-59: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
mergePropsoverwrites, rather than composes, matching function props (e.g.onClick).
{ ...childProps, ...slotProps }letsslotPropssilently clobber the child's own event handlers when both define the same prop name. Any futureasChildconsumer relying on the wrapped element's ownonClick/other handlers while also forwarding a handler throughSlotwill have one of them dropped.♻️ Suggested handler composition
function mergeProps<T extends HTMLElement>( childProps: AnyProps, slotProps: DOMMotionProps<T>, ): AnyProps { const merged: AnyProps = { ...childProps, ...slotProps }; + for (const key of Object.keys(slotProps)) { + if ( + key.startsWith('on') && + typeof (childProps as AnyProps)[key] === 'function' && + typeof (slotProps as AnyProps)[key] === 'function' + ) { + const childHandler = (childProps as AnyProps)[key] as (...a: unknown[]) => void; + const slotHandler = (slotProps as AnyProps)[key] as (...a: unknown[]) => void; + merged[key] = (...args: unknown[]) => { + childHandler(...args); + slotHandler(...args); + }; + } + } + if (childProps.className || slotProps.className) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/animate-ui/primitives/animate/slot.tsx` around lines 38 - 59, Update mergeProps to compose matching function-valued props from childProps and slotProps instead of allowing the spread merge to overwrite the child handler. Preserve both callbacks in a stable order when the same prop exists on both objects, while keeping the existing className and style merging behavior unchanged.packages/web/components/ui/sparkles.tsx (1)
28-40: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winRandomized particle positions computed during render risk SSR/CSR hydration mismatches.
particlesis built withMath.random()insideuseMemo, which runs during the initial render. Since this is a'use client'component that Next.js still server-renders for the initial HTML, the server-computed randomleft/top/sizevalues will differ from the client's first render, producing per-particle hydration mismatches on the inlinestyleattributes.♻️ Suggested fix: generate particles client-side only
-import { useMemo } from "react"; +import { useEffect, useState } from "react"; import { motion } from "motion/react"; ... - const particles = useMemo(() => { - return Array.from({ length: particleDensity }, (_, index) => { - const size = minSize + Math.random() * (maxSize - minSize); - return { - id: index, - left: `${Math.random() * 100}%`, - top: `${Math.random() * 100}%`, - size, - delay: Math.random() * 4, - duration: 2 + Math.random() * 3, - }; - }); - }, [maxSize, minSize, particleDensity]); + const [particles, setParticles] = useState< + { id: number; left: string; top: string; size: number; delay: number; duration: number }[] + >([]); + + useEffect(() => { + setParticles( + Array.from({ length: particleDensity }, (_, index) => { + const size = minSize + Math.random() * (maxSize - minSize); + return { + id: index, + left: `${Math.random() * 100}%`, + top: `${Math.random() * 100}%`, + size, + delay: Math.random() * 4, + duration: 2 + Math.random() * 3, + }; + }), + ); + }, [maxSize, minSize, particleDensity]);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/ui/sparkles.tsx` around lines 28 - 40, Update the particles initialization in the Sparkles component so random particle values are generated only after client mount, avoiding Math.random() during the server render and the client’s initial hydration render. Preserve the existing particle shape and density, and render a stable empty or equivalent initial state until the client-side values are created.packages/web/components/animate-ui/components/animate/code.tsx (1)
89-95: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueKeep the important modifiers compatible or switch to the v4 suffix syntax.
The package is configured for Tailwind v4, where
!bg-transparent,!text-[13px], and!px-0work only for backwards-compatibility; the supported form isbg-transparent!,text-[13px]!, andpx-0!. Updating this makes the overrides less fragile against future tooling changes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/animate-ui/components/animate/code.tsx` around lines 89 - 95, Update the Tailwind important modifiers in the className expression of the code component to v4 suffix syntax: use bg-transparent!, text-[13px]!, and px-0! while preserving the existing selectors and override behavior.packages/web/components/ui/background-beams.tsx (1)
8-59: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueHoist the static
pathsarray to module scope.It's a constant 50-element literal rebuilt on every render.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/ui/background-beams.tsx` around lines 8 - 59, Move the static 50-element paths array out of the component render scope to module scope in background-beams.tsx, preserving its contents and making the component reuse the hoisted paths constant on every render.packages/web/package.json (1)
29-29: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winFull
shikiin a client bundle is heavy.
packages/web/components/animate-ui/primitives/animate/code-block.tsxdoesawait import('shiki')and callscodeToHtml, which pulls the full language/theme set into the client chunk (multi-MB). For static docs snippets, highlight on the server (RSC) or useshiki/bundle/webwith an explicitcreateHighlighterlanguage/theme list.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/package.json` at line 29, Update the code-block highlighting flow around the dynamic shiki import and codeToHtml call to avoid bundling the full Shiki package in the client. Prefer moving highlighting to a server component for static snippets; otherwise import shiki/bundle/web and configure createHighlighter with only the required languages and themes.packages/web/app/globals.css (1)
261-263: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valuePrefer
@utilityover@layer utilitiesin Tailwind v4.
@utility font-display { ... }participates correctly in variant/ordering resolution;@layer utilitiesis the v3 idiom. Same applies to the other.bloom-*classes in this block.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/app/globals.css` around lines 261 - 263, Replace the Tailwind v3 utility-layer declaration for font-display and the surrounding bloom-* utility classes with individual `@utility` declarations, preserving each class’s existing styles while enabling Tailwind v4 variant and ordering resolution.packages/web/components/theme-provider.tsx (1)
61-66: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueListener is re-bound on every theme change.
Reading
resolvedThemefrom a ref (or keying offdocument.documentElement.classList) lets you register once with[setTheme].🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/theme-provider.tsx` around lines 61 - 66, Update the theme provider’s keydown effect around onKeyDown so the window listener is registered once rather than re-bound whenever resolvedTheme changes. Read the current theme through a ref or document.documentElement.classList, keep setTheme as the only dependency, and preserve the existing cleanup behavior.packages/web/components/animate-ui/primitives/animate/code-block.tsx (2)
147-156: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueSpread
{...props}before the internal props so callers can't clobberdangerouslySetInnerHTML/ref.Currently a caller passing
childrenordangerouslySetInnerHTMLsilently overrides the highlighted output (and React errors on children + innerHTML).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/animate-ui/primitives/animate/code-block.tsx` around lines 147 - 156, Update the JSX element in the code block component so `{...props}` is spread before the internal `ref`, data attributes, and `dangerouslySetInnerHTML` props. Ensure callers cannot override the generated highlighted output or internal ref, while preserving the existing highlighted rendering behavior.
28-31: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winDefault
themesobject is a new reference each render and is in the effect dep array.Any parent re-render re-triggers the shiki import +
codeToHtmlfor the same content. Hoist the default to a module-level constant (and consider memoizing when callers pass an inline literal).♻️ Proposed refactor
+const DEFAULT_THEMES = { light: 'github-light', dark: 'github-dark' } as const; + function CodeBlock({ ref, code, lang, theme = 'light', - themes = { - light: 'github-light', - dark: 'github-dark', - }, + themes = DEFAULT_THEMES,Also applies to: 76-76
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/components/animate-ui/primitives/animate/code-block.tsx` around lines 28 - 31, Hoist the default themes object used by the code block component to a module-level constant so its reference remains stable across renders and the effect does not rerun unnecessarily. Update the component’s default themes handling to use that constant, and memoize or otherwise stabilize caller-provided inline themes before the effect dependency check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/lib/update.ts`:
- Around line 113-124: Update fetchLatestRelease and the binary download fetch
in applyUpdate to use an AbortController-based timeout signal, ensuring stalled
network requests are aborted after the established timeout duration. Preserve
the existing request headers, response validation, and update flow while
applying the same timeout pattern to both calls.
- Around line 172-179: Update applyUpdate to verify the downloaded bytes against
a trusted checksum or signature published with the GitHub release before calling
Bun.write or replacing the executable. Fetch and parse the release integrity
asset, compare it with the downloaded asset using a secure digest, and abort the
update on missing or mismatched verification.
- Around line 193-232: Update the replacement flow after renameSync(target,
backup) so that if both renameSync(staging, target) and its copyFileSync
fallback fail, it restores backup to target before propagating the error.
Preserve the existing staging cleanup and outer error handling, and ensure
restoration failures do not mask the original installation error.
- Around line 180-184: Update applyUpdate to guard against running under a
script or runtime interpreter: allow self-update only when the process is a
compiled standalone binary, and otherwise refuse the update with an appropriate
warning before using process.execPath as the target. Preserve the existing
compiled-binary update flow and staging behavior.
In `@packages/web/components/animate-ui/primitives/animate/code-block.tsx`:
- Around line 94-122: Update the interval callback in the code-block animation
to increment index and invoke onWrite outside the setVisibleCode updater; make
the updater only derive and return the visible text from the already-advanced
index. Preserve the existing done handling and scrolling behavior while ensuring
updater re-invocation cannot advance state or emit duplicate callbacks.
In `@packages/web/components/animate-ui/primitives/animate/slot.tsx`:
- Around line 61-88: Move the React.isValidElement(children) guard in Slot
before any access to children.type, returning null for invalid or absent
children. Keep the isAlreadyMotion and Base useMemo hooks unconditional for
valid render paths, and preserve the existing ref and prop merging behavior.
In `@packages/web/components/docs/docs-shell.tsx`:
- Around line 27-70: Restore documentation navigation for mobile by adding a
compact navigation control or route list outside the lg-hidden sidebar that
renders DOCS_NAV below the lg breakpoint. Reuse the existing active-route logic
and preserve the current desktop sidebar behavior.
In `@packages/web/components/theme-provider.tsx`:
- Around line 41-59: Update the onKeyDown theme-toggle handler to avoid treating
an unmodified “d” as a global shortcut during interactive type-ahead or browser
find-as-you-type. Prefer requiring a modifier combination; otherwise extend the
existing isTypingTarget guard to skip targets within role-based menus,
listboxes, comboboxes, grids, elements with aria-haspopup, and equivalent
interactive controls.
In `@packages/web/components/ui/background-beams.tsx`:
- Around line 67-90: Update the decorative SVG in the background-beams component
to include aria-hidden="true". Use Motion’s useReducedMotion() to detect
reduced-motion preference and disable or short-circuit the paths.map animation
so the 50 gradient animations do not run when reduced motion is requested, while
preserving the existing animation for other users.
In `@packages/web/components/ui/tabs.tsx`:
- Around line 8-23: Update the Tabs component’s TabsPrimitive.Root invocation to
explicitly forward the destructured orientation prop, while retaining the
existing data-orientation attribute and styling behavior.
In `@packages/web/components/ui/text-generate-effect.tsx`:
- Around line 22-34: Update the reveal effects so content changes re-animate
newly rendered hidden spans: in
packages/web/components/ui/text-generate-effect.tsx lines 22-34, add words to
the useEffect dependency array; in
packages/web/components/ui/typewriter-effect.tsx lines 29-45, add both words and
animate to its dependency array.
In `@packages/web/lib/site.ts`:
- Around line 6-12: Update the APP_ORIGIN environment-variable selection to
treat empty values as unset, so an empty NEXT_PUBLIC_APP_URL falls through to
APP_URL and then the default origin. Preserve trailing-slash removal for
non-empty values and keep INSTALL_UNIX and INSTALL_WINDOWS using the resulting
APP_ORIGIN.
In `@packages/web/package.json`:
- Around line 16-29: Update the gsap dependency in package.json to a minimum
version of 3.6.0, preserving the current dependency declaration style, then
regenerate the lockfile so it resolves only patched versions.
---
Minor comments:
In `@packages/web/app/docs/themes/page.tsx`:
- Around line 11-17: Update contrastText to convert each sRGB channel to
linearized values, compute WCAG relative luminance, and compare the contrast
ratios of "`#1a0a12`" and "`#fff8fa`" against the background. Return whichever
foreground provides the higher contrast ratio, preserving the existing color
options.
In `@packages/web/app/globals.css`:
- Around line 184-191: Update the body rule by adding a blank line between the
`@apply` statement and the background declarations, and remove
background-attachment: fixed to avoid the mobile Safari scroll-performance
issue.
In `@packages/web/components/animate-ui/components/buttons/copy.tsx`:
- Around line 55-92: Update CopyButton’s handleCopy guard to use the resolved
isCopied state rather than the external copied prop, and prevent overlapping
reset timers from independently changing the copied state. Ensure rapid clicks
are ignored while already copied and only the active timer resets isCopied and
invokes onCopiedChange(false).
In `@packages/web/components/animate-ui/primitives/animate/code-block.tsx`:
- Around line 78-84: Prevent the non-writing branch of the React.useEffect from
emitting completion callbacks on every dependency-driven re-run. Use a ref to
track whether completion has already been reported for the current code, reset
it when a new write begins or the code changes, and only invoke onDone and
onWrite once while preserving the existing visible-code update.
In `@packages/web/components/animate-ui/primitives/texts/typing.tsx`:
- Around line 63-124: Update the React.useEffect dependency handling in the
typing animation to avoid using the unstable text array identity. Normalize text
once and derive a stable content key, such as a serialized or joined
representation, then depend on that key instead of text while preserving
restarts when the text contents actually change.
In `@packages/web/components/site/bloom-logo.tsx`:
- Around line 42-46: Update the Link element in the Bloom logo component to use
Tailwind’s outline-hidden utility instead of outline-none, while preserving the
existing focus-visible:ring-2 and focus-visible:ring-ring classes.
In `@packages/web/components/ui/accordion.tsx`:
- Line 59: Add the missing --animate-accordion-down and --animate-accordion-up
theme tokens and their corresponding accordion-down and accordion-up `@keyframes`
in globals.css, matching the data-open/data-closed utilities used by the
accordion component. Preserve the existing --animate-spotlight definition.
In `@packages/web/components/ui/text-generate-effect.tsx`:
- Around line 39-49: Update the word spans rendered by the text-generate effect
so their content remains readable when animation does not run, including for
users with reduced motion enabled. Use the component’s motion/reduced-motion
handling to avoid applying the permanent opacity-0 and blur initial state in
those cases, while preserving the animated reveal for users who allow motion.
In `@packages/web/components/ui/typewriter-effect.tsx`:
- Line 159: Correct the responsive typography utility in the typewriter effect
component’s className by changing the invalid lg:text:3xl token to the valid
lg:text-3xl token, preserving the surrounding text-size classes.
In `@packages/web/lib/docs/data.ts`:
- Around line 131-134: Update the shortcut definition containing keys ["↑", "↓"]
so the up and down arrows render as separate alternatives rather than a single
chord, while preserving the shared “Move selection in command menu, mention
menu, or dialogs” action text. Use separate rows or the existing alternative
representation, and leave true multi-key chords unchanged.
---
Nitpick comments:
In `@packages/web/app/globals.css`:
- Around line 261-263: Replace the Tailwind v3 utility-layer declaration for
font-display and the surrounding bloom-* utility classes with individual
`@utility` declarations, preserving each class’s existing styles while enabling
Tailwind v4 variant and ordering resolution.
In `@packages/web/components/animate-ui/components/animate/code.tsx`:
- Around line 89-95: Update the Tailwind important modifiers in the className
expression of the code component to v4 suffix syntax: use bg-transparent!,
text-[13px]!, and px-0! while preserving the existing selectors and override
behavior.
In `@packages/web/components/animate-ui/primitives/animate/code-block.tsx`:
- Around line 147-156: Update the JSX element in the code block component so
`{...props}` is spread before the internal `ref`, data attributes, and
`dangerouslySetInnerHTML` props. Ensure callers cannot override the generated
highlighted output or internal ref, while preserving the existing highlighted
rendering behavior.
- Around line 28-31: Hoist the default themes object used by the code block
component to a module-level constant so its reference remains stable across
renders and the effect does not rerun unnecessarily. Update the component’s
default themes handling to use that constant, and memoize or otherwise stabilize
caller-provided inline themes before the effect dependency check.
In `@packages/web/components/animate-ui/primitives/animate/slot.tsx`:
- Around line 38-59: Update mergeProps to compose matching function-valued props
from childProps and slotProps instead of allowing the spread merge to overwrite
the child handler. Preserve both callbacks in a stable order when the same prop
exists on both objects, while keeping the existing className and style merging
behavior unchanged.
In `@packages/web/components/auth/cli-auth-handoff.tsx`:
- Around line 402-430: Extract the duplicated branded Card/header structure from
AuthShell and the corresponding login-form.tsx markup into a shared
AuthCard/AuthShell component, then update both consumers to import and use it.
Preserve each screen’s existing title, description, children/footer behavior,
shadow styling, and Spotlight background through component props or composition
rather than maintaining separate copied implementations.
In `@packages/web/components/auth/login-form.tsx`:
- Around line 116-135: The login form card header duplicates the brand-card
structure used by AuthShell in cli-auth-handoff.tsx. Extract the shared gradient
bar, BloomLogo, title, and description markup into a reusable component, then
update both AuthShell and the login form to use it while preserving the login
form’s Spotlight and shadow styling.
In `@packages/web/components/landing/hero.tsx`:
- Line 1: Extract the repeated var(--bloom-shadow) Tailwind values into shared
`@utility` classes following the existing bloom-emboss pattern, defining
bloom-shadow-xs, bloom-shadow-sm, and bloom-shadow-md for 2px, 3px, and 4px
offsets. Replace the corresponding arbitrary shadow classes in the landing hero,
theme toggle, feature strip, and install CTA while preserving all other styling.
In `@packages/web/components/site/bloom-logo.tsx`:
- Around line 22-29: Update the BloomLogo component to make the Image priority
behavior opt-in rather than always enabled, exposing a priority prop with an
appropriate default. Keep existing logo usages non-priority, and pass priority
only from the header instance that represents the above-the-fold/LCP logo.
In `@packages/web/components/theme-provider.tsx`:
- Around line 61-66: Update the theme provider’s keydown effect around onKeyDown
so the window listener is registered once rather than re-bound whenever
resolvedTheme changes. Read the current theme through a ref or
document.documentElement.classList, keep setTheme as the only dependency, and
preserve the existing cleanup behavior.
In `@packages/web/components/ui/background-beams.tsx`:
- Around line 8-59: Move the static 50-element paths array out of the component
render scope to module scope in background-beams.tsx, preserving its contents
and making the component reuse the hoisted paths constant on every render.
In `@packages/web/components/ui/sparkles.tsx`:
- Around line 28-40: Update the particles initialization in the Sparkles
component so random particle values are generated only after client mount,
avoiding Math.random() during the server render and the client’s initial
hydration render. Preserve the existing particle shape and density, and render a
stable empty or equivalent initial state until the client-side values are
created.
In `@packages/web/package.json`:
- Line 29: Update the code-block highlighting flow around the dynamic shiki
import and codeToHtml call to avoid bundling the full Shiki package in the
client. Prefer moving highlighting to a server component for static snippets;
otherwise import shiki/bundle/web and configure createHighlighter with only the
required languages and themes.
In `@packages/web/public/install.sh`:
- Around line 56-73: Update legacy_asset_name to reuse the already-resolved OS
and architecture from detect_asset_prefix instead of repeating uname-based
detection. Pass those values into legacy_asset_name, preserve its existing
Windows .exe naming behavior, and remove the duplicate case logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: bd153ab6-4d63-492d-924b-4b7c72266e25
⛔ Files ignored due to path filters (2)
bun.lockis excluded by!**/*.lockpackages/web/public/logo.pngis excluded by!**/*.png
📒 Files selected for processing (78)
.cursor/settings.json.github/workflows/release-cli.ymldocs/manual-setup.mdpackages/cli/src/components/command-menu/commands.tsxpackages/cli/src/components/input-bar.tsxpackages/cli/src/components/update-notifier.tsxpackages/cli/src/layouts/root-layout.tsxpackages/cli/src/lib/brand.tspackages/cli/src/lib/update.tspackages/web/app/cli/auth/page.tsxpackages/web/app/docs/architecture/page.tsxpackages/web/app/docs/auth/page.tsxpackages/web/app/docs/commands/page.tsxpackages/web/app/docs/configuration/page.tsxpackages/web/app/docs/getting-started/page.tsxpackages/web/app/docs/install/page.tsxpackages/web/app/docs/keyboard/page.tsxpackages/web/app/docs/mentions/page.tsxpackages/web/app/docs/models/page.tsxpackages/web/app/docs/modes/page.tsxpackages/web/app/docs/page.tsxpackages/web/app/docs/quota/page.tsxpackages/web/app/docs/sessions/page.tsxpackages/web/app/docs/themes/page.tsxpackages/web/app/docs/tools/page.tsxpackages/web/app/docs/troubleshooting/page.tsxpackages/web/app/globals.csspackages/web/app/layout.tsxpackages/web/app/login/page.tsxpackages/web/app/page.tsxpackages/web/components.jsonpackages/web/components/animate-ui/components/animate/code.tsxpackages/web/components/animate-ui/components/buttons/copy.tsxpackages/web/components/animate-ui/primitives/animate/code-block.tsxpackages/web/components/animate-ui/primitives/animate/slot.tsxpackages/web/components/animate-ui/primitives/buttons/button.tsxpackages/web/components/animate-ui/primitives/texts/highlight.tsxpackages/web/components/animate-ui/primitives/texts/typing.tsxpackages/web/components/auth/cli-auth-handoff.tsxpackages/web/components/auth/login-form.tsxpackages/web/components/docs/architecture-diagram.tsxpackages/web/components/docs/code-block.tsxpackages/web/components/docs/command-reference.tsxpackages/web/components/docs/docs-shell.tsxpackages/web/components/docs/keycap.tsxpackages/web/components/docs/prose.tsxpackages/web/components/landing/feature-strip.tsxpackages/web/components/landing/hero.tsxpackages/web/components/landing/install-cta.tsxpackages/web/components/landing/terminal-chassis.tsxpackages/web/components/site/bloom-logo.tsxpackages/web/components/site/site-footer.tsxpackages/web/components/site/site-header.tsxpackages/web/components/site/theme-toggle.tsxpackages/web/components/theme-provider.tsxpackages/web/components/ui/accordion.tsxpackages/web/components/ui/background-beams.tsxpackages/web/components/ui/badge.tsxpackages/web/components/ui/moving-border.tsxpackages/web/components/ui/scroll-area.tsxpackages/web/components/ui/sparkles.tsxpackages/web/components/ui/spotlight.tsxpackages/web/components/ui/tabs.tsxpackages/web/components/ui/text-generate-effect.tsxpackages/web/components/ui/typewriter-effect.tsxpackages/web/hooks/use-controlled-state.tsxpackages/web/hooks/use-is-in-view.tsxpackages/web/lib/docs/commands.tspackages/web/lib/docs/data.tspackages/web/lib/docs/nav.tspackages/web/lib/get-strict-context.tsxpackages/web/lib/site.tspackages/web/package.jsonpackages/web/public/install.ps1packages/web/public/install.shscripts/build-cli-release.shscripts/install.ps1scripts/install.sh
| async function fetchLatestRelease(): Promise<GitHubRelease> { | ||
| const response = await fetch(RELEASES_LATEST, { | ||
| headers: { | ||
| Accept: "application/vnd.github+json", | ||
| "User-Agent": "bloom-cli", | ||
| }, | ||
| }); | ||
| if (!response.ok) { | ||
| throw new Error(`GitHub Releases error (${response.status})`); | ||
| } | ||
| return (await response.json()) as GitHubRelease; | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Add timeouts to network calls.
Neither fetchLatestRelease nor the binary download in applyUpdate sets a timeout/abort signal. A stalled connection will hang the update flow (and the CLI process) indefinitely.
⏱️ Suggested fix
- const response = await fetch(RELEASES_LATEST, {
- headers: {
- Accept: "application/vnd.github+json",
- "User-Agent": "bloom-cli",
- },
- });
+ const response = await fetch(RELEASES_LATEST, {
+ headers: {
+ Accept: "application/vnd.github+json",
+ "User-Agent": "bloom-cli",
+ },
+ signal: AbortSignal.timeout(10_000),
+ });Apply the same pattern to the download fetch in applyUpdate.
Also applies to: 172-177
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/lib/update.ts` around lines 113 - 124, Update
fetchLatestRelease and the binary download fetch in applyUpdate to use an
AbortController-based timeout signal, ensuring stalled network requests are
aborted after the established timeout duration. Preserve the existing request
headers, response validation, and update flow while applying the same timeout
pattern to both calls.
| const response = await fetch(update.downloadUrl, { | ||
| headers: { "User-Agent": "bloom-cli" }, | ||
| }); | ||
| if (!response.ok) { | ||
| throw new Error(`Download failed (${response.status})`); | ||
| } | ||
|
|
||
| const bytes = new Uint8Array(await response.arrayBuffer()); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
No integrity verification of downloaded binary before replacing the running executable.
applyUpdate downloads the release asset and directly writes/executes it as a replacement for the CLI binary, without validating a checksum or signature against the GitHub release. If an asset were tampered with, corrupted in transit, or a release were compromised, this would silently install and later execute arbitrary code as the user replacing their own CLI binary.
🔒 Suggested approach
type GitHubAsset = {
name: string;
browser_download_url: string;
+ digest?: string; // e.g. "sha256:..." if published in the release
};Verify bytes against a published checksum (e.g. a SHASUMS256.txt/.sha256 asset from the release) before calling Bun.write(staging, bytes), and abort on mismatch.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/lib/update.ts` around lines 172 - 179, Update applyUpdate to
verify the downloaded bytes against a trusted checksum or signature published
with the GitHub release before calling Bun.write or replacing the executable.
Fetch and parse the release integrity asset, compare it with the downloaded
asset using a secure digest, and abort the update on missing or mismatched
verification.
| const target = process.execPath; | ||
| const staging = join( | ||
| tmpdir(), | ||
| `bloom-update-${update.latestVersion}-${Date.now()}`, | ||
| ); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Does Bun process.execPath return the bun binary path when running a script vs the compiled executable path when built with --compile?
💡 Result:
Yes, Bun's process.execPath returns the path to the executable currently running the script, regardless of whether you are running a script with the bun CLI or running a standalone executable created via bun build --compile. In both scenarios, process.execPath provides the absolute path to the binary that initiated the process [1][2]. Specifically: 1. When running a script (e.g., bun run app.ts), process.execPath returns the path to the bun binary [2]. 2. When running a compiled standalone executable (e.g., ./myapp), process.execPath returns the path to that specific compiled executable file [1][2]. This behavior ensures that if the process spawns child processes (for example, via child_process.fork), it correctly identifies the host binary [3][4]. Note that while process.execPath behaves this way, process.argv[0] is also designed to point to this same executable path to ensure that spawned processes can correctly reference the binary they are running from [1].
Citations:
- 1: bun build --compile: read NODE_ENV at runtime, set argv[0] to the executable path oven-sh/bun#32851
- 2: https://github.com/oven-sh/bun/blob/7abe6c38/src/bun.js/node/node_process.zig
- 3: https://bun.com/docs/guides/process/ipc
- 4: https://bun.com/reference/node/child_process/fork
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate update.ts =="
fd -a 'update\.ts$' . | sed 's#^\./##'
echo "== snippet =="
file="$(fd 'update\.ts$' . | head -n 1)"
wc -l "$file"
sed -n '150,210p' "$file"
echo "== usages of applyUpdate/execPath/bun build/compile =="
rg -n "applyUpdate|process\.execPath|bun build --compile|--compile|dev:cli|manual-setup|run dev:cli" .Repository: Prince-695/bloom
Length of output: 2530
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== update.ts tail =="
sed -n '210,245p' packages/cli/src/lib/update.ts
echo "== command-menu update usage =="
sed -n '135,175p' packages/cli/src/components/command-menu/commands.tsx
echo "== release build context =="
sed -n '90,115p' scripts/build-cli-release.sh
echo "== package scripts =="
sed -n '1,80p' package.json
echo "== docs manual setup context =="
sed -n '115,135p' docs/manual-setup.mdRepository: Prince-695/bloom
Length of output: 4284
Guard applyUpdate against script/runtime interpreters
process.execPath is the interpreter/runtime for script execution, and only points at the standalone CLI when run as a bun build --compile executable. With the documented bun run dev:cli workflow and the update action available through /update, self-updating can target and overwrite the Bun installer/bin path instead of the Bloom CLI, potentially damaging the developer’s tooling. Refuse self-update unless the process is a compiled standalone binary, or otherwise warn when process.execPath would replace the runtime.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/lib/update.ts` around lines 180 - 184, Update applyUpdate to
guard against running under a script or runtime interpreter: allow self-update
only when the process is a compiled standalone binary, and otherwise refuse the
update with an appropriate warning before using process.execPath as the target.
Preserve the existing compiled-binary update flow and staging behavior.
| try { | ||
| // Atomic-ish replace: move aside, then move new into place | ||
| const backup = `${target}.bak`; | ||
| try { | ||
| unlinkSync(backup); | ||
| } catch { | ||
| // no previous backup | ||
| } | ||
| try { | ||
| renameSync(target, backup); | ||
| } catch { | ||
| // If rename of running binary fails, try direct overwrite via copy | ||
| copyFileSync(staging, target); | ||
| try { | ||
| chmodSync(target, 0o755); | ||
| } catch { | ||
| // ignore | ||
| } | ||
| unlinkSync(staging); | ||
| return update; | ||
| } | ||
|
|
||
| try { | ||
| renameSync(staging, target); | ||
| } catch { | ||
| copyFileSync(staging, target); | ||
| unlinkSync(staging); | ||
| } | ||
|
|
||
| try { | ||
| chmodSync(target, 0o755); | ||
| } catch { | ||
| // ignore | ||
| } | ||
|
|
||
| try { | ||
| unlinkSync(backup); | ||
| } catch { | ||
| // keep .bak if delete fails (Windows) | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
Broken-install state if both the rename and copy fallback fail after backup.
Once renameSync(target, backup) succeeds, target no longer exists. If the subsequent renameSync(staging, target) fails and its copyFileSync(staging, target) fallback (Lines 215-220) also throws, that exception propagates to the outer catch (Lines 233-242), which only deletes staging and rethrows — it never restores backup back to target. The user is left with no executable at target at all, only a .bak file, until they manually recover it.
🛠️ Suggested fix
try {
renameSync(staging, target);
} catch {
- copyFileSync(staging, target);
- unlinkSync(staging);
+ try {
+ copyFileSync(staging, target);
+ unlinkSync(staging);
+ } catch (copyError) {
+ try {
+ renameSync(backup, target);
+ } catch {
+ // best effort restore
+ }
+ throw copyError;
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try { | |
| // Atomic-ish replace: move aside, then move new into place | |
| const backup = `${target}.bak`; | |
| try { | |
| unlinkSync(backup); | |
| } catch { | |
| // no previous backup | |
| } | |
| try { | |
| renameSync(target, backup); | |
| } catch { | |
| // If rename of running binary fails, try direct overwrite via copy | |
| copyFileSync(staging, target); | |
| try { | |
| chmodSync(target, 0o755); | |
| } catch { | |
| // ignore | |
| } | |
| unlinkSync(staging); | |
| return update; | |
| } | |
| try { | |
| renameSync(staging, target); | |
| } catch { | |
| copyFileSync(staging, target); | |
| unlinkSync(staging); | |
| } | |
| try { | |
| chmodSync(target, 0o755); | |
| } catch { | |
| // ignore | |
| } | |
| try { | |
| unlinkSync(backup); | |
| } catch { | |
| // keep .bak if delete fails (Windows) | |
| } | |
| try { | |
| // Atomic-ish replace: move aside, then move new into place | |
| const backup = `${target}.bak`; | |
| try { | |
| unlinkSync(backup); | |
| } catch { | |
| // no previous backup | |
| } | |
| try { | |
| renameSync(target, backup); | |
| } catch { | |
| // If rename of running binary fails, try direct overwrite via copy | |
| copyFileSync(staging, target); | |
| try { | |
| chmodSync(target, 0o755); | |
| } catch { | |
| // ignore | |
| } | |
| unlinkSync(staging); | |
| return update; | |
| } | |
| try { | |
| renameSync(staging, target); | |
| } catch { | |
| try { | |
| copyFileSync(staging, target); | |
| unlinkSync(staging); | |
| } catch (copyError) { | |
| try { | |
| renameSync(backup, target); | |
| } catch { | |
| // best effort restore | |
| } | |
| throw copyError; | |
| } | |
| } | |
| try { | |
| chmodSync(target, 0o755); | |
| } catch { | |
| // ignore | |
| } | |
| try { | |
| unlinkSync(backup); | |
| } catch { | |
| // keep .bak if delete fails (Windows) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/lib/update.ts` around lines 193 - 232, Update the
replacement flow after renameSync(target, backup) so that if both
renameSync(staging, target) and its copyFileSync fallback fail, it restores
backup to target before propagating the error. Preserve the existing staging
cleanup and outer error handling, and ensure restoration failures do not mask
the original installation error.
| const timeout = setTimeout(() => { | ||
| intervalId = setInterval(() => { | ||
| if (index < characters.length) { | ||
| setVisibleCode(() => { | ||
| const nextChar = characters.slice(0, index + 1).join(''); | ||
| onWrite?.({ | ||
| index: index + 1, | ||
| length: characters.length, | ||
| done: false, | ||
| }); | ||
| index += 1; | ||
| return nextChar; | ||
| }); | ||
| localRef.current?.scrollTo({ | ||
| top: localRef.current?.scrollHeight, | ||
| behavior: 'smooth', | ||
| }); | ||
| } else { | ||
| clearInterval(intervalId); | ||
| setIsDone(true); | ||
| onDone?.(); | ||
| onWrite?.({ | ||
| index: characters.length, | ||
| length: characters.length, | ||
| done: true, | ||
| }); | ||
| } | ||
| }, interval); | ||
| }, delay); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Side effects inside the state updater break under StrictMode / double invocation.
setVisibleCode(() => {...}) mutates index and calls onWrite inside the updater. React may invoke updaters more than once (StrictMode dev, re-render bail-outs), which double-advances index and emits duplicate onWrite events. Move the mutation and callback out of the updater.
🐛 Proposed fix
intervalId = setInterval(() => {
if (index < characters.length) {
- setVisibleCode(() => {
- const nextChar = characters.slice(0, index + 1).join('');
- onWrite?.({
- index: index + 1,
- length: characters.length,
- done: false,
- });
- index += 1;
- return nextChar;
- });
+ index += 1;
+ setVisibleCode(characters.slice(0, index).join(''));
+ onWrite?.({
+ index,
+ length: characters.length,
+ done: false,
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const timeout = setTimeout(() => { | |
| intervalId = setInterval(() => { | |
| if (index < characters.length) { | |
| setVisibleCode(() => { | |
| const nextChar = characters.slice(0, index + 1).join(''); | |
| onWrite?.({ | |
| index: index + 1, | |
| length: characters.length, | |
| done: false, | |
| }); | |
| index += 1; | |
| return nextChar; | |
| }); | |
| localRef.current?.scrollTo({ | |
| top: localRef.current?.scrollHeight, | |
| behavior: 'smooth', | |
| }); | |
| } else { | |
| clearInterval(intervalId); | |
| setIsDone(true); | |
| onDone?.(); | |
| onWrite?.({ | |
| index: characters.length, | |
| length: characters.length, | |
| done: true, | |
| }); | |
| } | |
| }, interval); | |
| }, delay); | |
| const timeout = setTimeout(() => { | |
| intervalId = setInterval(() => { | |
| if (index < characters.length) { | |
| index += 1; | |
| setVisibleCode(characters.slice(0, index).join('')); | |
| onWrite?.({ | |
| index, | |
| length: characters.length, | |
| done: false, | |
| }); | |
| localRef.current?.scrollTo({ | |
| top: localRef.current?.scrollHeight, | |
| behavior: 'smooth', | |
| }); | |
| } else { | |
| clearInterval(intervalId); | |
| setIsDone(true); | |
| onDone?.(); | |
| onWrite?.({ | |
| index: characters.length, | |
| length: characters.length, | |
| done: true, | |
| }); | |
| } | |
| }, interval); | |
| }, delay); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/components/animate-ui/primitives/animate/code-block.tsx` around
lines 94 - 122, Update the interval callback in the code-block animation to
increment index and invoke onWrite outside the setVisibleCode updater; make the
updater only derive and return the visible text from the already-advanced index.
Preserve the existing done handling and scrolling behavior while ensuring
updater re-invocation cannot advance state or emit duplicate callbacks.
| <svg | ||
| className="pointer-events-none absolute z-0 h-full w-full" | ||
| width="100%" | ||
| height="100%" | ||
| viewBox="0 0 696 316" | ||
| fill="none" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <path | ||
| d="M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803M-310 -269C-310 -269 -242 136 222 263C686 390 754 795 754 795M-303 -277C-303 -277 -235 128 229 255C693 382 761 787 761 787M-296 -285C-296 -285 -228 120 236 247C700 374 768 779 768 779M-289 -293C-289 -293 -221 112 243 239C707 366 775 771 775 771M-282 -301C-282 -301 -214 104 250 231C714 358 782 763 782 763M-275 -309C-275 -309 -207 96 257 223C721 350 789 755 789 755M-268 -317C-268 -317 -200 88 264 215C728 342 796 747 796 747M-261 -325C-261 -325 -193 80 271 207C735 334 803 739 803 739M-254 -333C-254 -333 -186 72 278 199C742 326 810 731 810 731M-247 -341C-247 -341 -179 64 285 191C749 318 817 723 817 723M-240 -349C-240 -349 -172 56 292 183C756 310 824 715 824 715M-233 -357C-233 -357 -165 48 299 175C763 302 831 707 831 707M-226 -365C-226 -365 -158 40 306 167C770 294 838 699 838 699M-219 -373C-219 -373 -151 32 313 159C777 286 845 691 845 691M-212 -381C-212 -381 -144 24 320 151C784 278 852 683 852 683M-205 -389C-205 -389 -137 16 327 143C791 270 859 675 859 675M-198 -397C-198 -397 -130 8 334 135C798 262 866 667 866 667M-191 -405C-191 -405 -123 0 341 127C805 254 873 659 873 659M-184 -413C-184 -413 -116 -8 348 119C812 246 880 651 880 651M-177 -421C-177 -421 -109 -16 355 111C819 238 887 643 887 643M-170 -429C-170 -429 -102 -24 362 103C826 230 894 635 894 635M-163 -437C-163 -437 -95 -32 369 95C833 222 901 627 901 627M-156 -445C-156 -445 -88 -40 376 87C840 214 908 619 908 619M-149 -453C-149 -453 -81 -48 383 79C847 206 915 611 915 611M-142 -461C-142 -461 -74 -56 390 71C854 198 922 603 922 603M-135 -469C-135 -469 -67 -64 397 63C861 190 929 595 929 595M-128 -477C-128 -477 -60 -72 404 55C868 182 936 587 936 587M-121 -485C-121 -485 -53 -80 411 47C875 174 943 579 943 579M-114 -493C-114 -493 -46 -88 418 39C882 166 950 571 950 571M-107 -501C-107 -501 -39 -96 425 31C889 158 957 563 957 563M-100 -509C-100 -509 -32 -104 432 23C896 150 964 555 964 555M-93 -517C-93 -517 -25 -112 439 15C903 142 971 547 971 547M-86 -525C-86 -525 -18 -120 446 7C910 134 978 539 978 539M-79 -533C-79 -533 -11 -128 453 -1C917 126 985 531 985 531M-72 -541C-72 -541 -4 -136 460 -9C924 118 992 523 992 523M-65 -549C-65 -549 3 -144 467 -17C931 110 999 515 999 515M-58 -557C-58 -557 10 -152 474 -25C938 102 1006 507 1006 507M-51 -565C-51 -565 17 -160 481 -33C945 94 1013 499 1013 499M-44 -573C-44 -573 24 -168 488 -41C952 86 1020 491 1020 491M-37 -581C-37 -581 31 -176 495 -49C959 78 1027 483 1027 483M-30 -589C-30 -589 38 -184 502 -57C966 70 1034 475 1034 475M-23 -597C-23 -597 45 -192 509 -65C973 62 1041 467 1041 467M-16 -605C-16 -605 52 -200 516 -73C980 54 1048 459 1048 459M-9 -613C-9 -613 59 -208 523 -81C987 46 1055 451 1055 451M-2 -621C-2 -621 66 -216 530 -89C994 38 1062 443 1062 443M5 -629C5 -629 73 -224 537 -97C1001 30 1069 435 1069 435M12 -637C12 -637 80 -232 544 -105C1008 22 1076 427 1076 427M19 -645C19 -645 87 -240 551 -113C1015 14 1083 419 1083 419" | ||
| stroke="url(#paint0_radial_242_278)" | ||
| strokeOpacity="0.05" | ||
| strokeWidth="0.5" | ||
| ></path> | ||
|
|
||
| {paths.map((path, index) => ( | ||
| <motion.path | ||
| key={`path-` + index} | ||
| d={path} | ||
| stroke={`url(#linearGradient-${index})`} | ||
| strokeOpacity="0.4" | ||
| strokeWidth="0.5" | ||
| ></motion.path> | ||
| ))} |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
Decorative SVG should be hidden from AT and respect reduced motion.
50 infinitely repeating gradient animations run unconditionally. Add aria-hidden="true" to the <svg> and disable/short-circuit the animation when prefers-reduced-motion: reduce (Motion's useReducedMotion()).
♿ Proposed fix
+import { motion, useReducedMotion } from "motion/react";
@@
+ const reduceMotion = useReducedMotion();
@@
<svg
+ aria-hidden="true"
className="pointer-events-none absolute z-0 h-full w-full"
@@
- animate={{
- x1: ["0%", "100%"],
- x2: ["0%", "95%"],
- y1: ["0%", "100%"],
- y2: ["0%", `${93 + Math.random() * 8}%`],
- }}
+ animate={
+ reduceMotion
+ ? undefined
+ : {
+ x1: ["0%", "100%"],
+ x2: ["0%", "95%"],
+ y1: ["0%", "100%"],
+ y2: ["0%", `${93 + Math.random() * 8}%`],
+ }
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <svg | |
| className="pointer-events-none absolute z-0 h-full w-full" | |
| width="100%" | |
| height="100%" | |
| viewBox="0 0 696 316" | |
| fill="none" | |
| xmlns="http://www.w3.org/2000/svg" | |
| > | |
| <path | |
| d="M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803M-310 -269C-310 -269 -242 136 222 263C686 390 754 795 754 795M-303 -277C-303 -277 -235 128 229 255C693 382 761 787 761 787M-296 -285C-296 -285 -228 120 236 247C700 374 768 779 768 779M-289 -293C-289 -293 -221 112 243 239C707 366 775 771 775 771M-282 -301C-282 -301 -214 104 250 231C714 358 782 763 782 763M-275 -309C-275 -309 -207 96 257 223C721 350 789 755 789 755M-268 -317C-268 -317 -200 88 264 215C728 342 796 747 796 747M-261 -325C-261 -325 -193 80 271 207C735 334 803 739 803 739M-254 -333C-254 -333 -186 72 278 199C742 326 810 731 810 731M-247 -341C-247 -341 -179 64 285 191C749 318 817 723 817 723M-240 -349C-240 -349 -172 56 292 183C756 310 824 715 824 715M-233 -357C-233 -357 -165 48 299 175C763 302 831 707 831 707M-226 -365C-226 -365 -158 40 306 167C770 294 838 699 838 699M-219 -373C-219 -373 -151 32 313 159C777 286 845 691 845 691M-212 -381C-212 -381 -144 24 320 151C784 278 852 683 852 683M-205 -389C-205 -389 -137 16 327 143C791 270 859 675 859 675M-198 -397C-198 -397 -130 8 334 135C798 262 866 667 866 667M-191 -405C-191 -405 -123 0 341 127C805 254 873 659 873 659M-184 -413C-184 -413 -116 -8 348 119C812 246 880 651 880 651M-177 -421C-177 -421 -109 -16 355 111C819 238 887 643 887 643M-170 -429C-170 -429 -102 -24 362 103C826 230 894 635 894 635M-163 -437C-163 -437 -95 -32 369 95C833 222 901 627 901 627M-156 -445C-156 -445 -88 -40 376 87C840 214 908 619 908 619M-149 -453C-149 -453 -81 -48 383 79C847 206 915 611 915 611M-142 -461C-142 -461 -74 -56 390 71C854 198 922 603 922 603M-135 -469C-135 -469 -67 -64 397 63C861 190 929 595 929 595M-128 -477C-128 -477 -60 -72 404 55C868 182 936 587 936 587M-121 -485C-121 -485 -53 -80 411 47C875 174 943 579 943 579M-114 -493C-114 -493 -46 -88 418 39C882 166 950 571 950 571M-107 -501C-107 -501 -39 -96 425 31C889 158 957 563 957 563M-100 -509C-100 -509 -32 -104 432 23C896 150 964 555 964 555M-93 -517C-93 -517 -25 -112 439 15C903 142 971 547 971 547M-86 -525C-86 -525 -18 -120 446 7C910 134 978 539 978 539M-79 -533C-79 -533 -11 -128 453 -1C917 126 985 531 985 531M-72 -541C-72 -541 -4 -136 460 -9C924 118 992 523 992 523M-65 -549C-65 -549 3 -144 467 -17C931 110 999 515 999 515M-58 -557C-58 -557 10 -152 474 -25C938 102 1006 507 1006 507M-51 -565C-51 -565 17 -160 481 -33C945 94 1013 499 1013 499M-44 -573C-44 -573 24 -168 488 -41C952 86 1020 491 1020 491M-37 -581C-37 -581 31 -176 495 -49C959 78 1027 483 1027 483M-30 -589C-30 -589 38 -184 502 -57C966 70 1034 475 1034 475M-23 -597C-23 -597 45 -192 509 -65C973 62 1041 467 1041 467M-16 -605C-16 -605 52 -200 516 -73C980 54 1048 459 1048 459M-9 -613C-9 -613 59 -208 523 -81C987 46 1055 451 1055 451M-2 -621C-2 -621 66 -216 530 -89C994 38 1062 443 1062 443M5 -629C5 -629 73 -224 537 -97C1001 30 1069 435 1069 435M12 -637C12 -637 80 -232 544 -105C1008 22 1076 427 1076 427M19 -645C19 -645 87 -240 551 -113C1015 14 1083 419 1083 419" | |
| stroke="url(#paint0_radial_242_278)" | |
| strokeOpacity="0.05" | |
| strokeWidth="0.5" | |
| ></path> | |
| {paths.map((path, index) => ( | |
| <motion.path | |
| key={`path-` + index} | |
| d={path} | |
| stroke={`url(#linearGradient-${index})`} | |
| strokeOpacity="0.4" | |
| strokeWidth="0.5" | |
| ></motion.path> | |
| ))} | |
| <svg | |
| aria-hidden="true" | |
| className="pointer-events-none absolute z-0 h-full w-full" | |
| width="100%" | |
| height="100%" | |
| viewBox="0 0 696 316" | |
| fill="none" | |
| xmlns="http://www.w3.org/2000/svg" | |
| > | |
| <path | |
| d="M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803M-310 -269C-310 -269 -242 136 222 263C686 390 754 795 754 795M-303 -277C-303 -277 -235 128 229 255C693 382 761 787 761 787M-296 -285C-296 -285 -228 120 236 247C700 374 768 779 768 779M-289 -293C-289 -293 -221 112 243 239C707 366 775 771 775 771M-282 -301C-282 -301 -214 104 250 231C714 358 782 763 782 763M-275 -309C-275 -309 -207 96 257 223C721 350 789 755 789 755M-268 -317C-268 -317 -200 88 264 215C728 342 796 747 796 747M-261 -325C-261 -325 -193 80 271 207C735 334 803 739 803 739M-254 -333C-254 -333 -186 72 278 199C742 326 810 731 810 731M-247 -341C-247 -341 -179 64 285 191C749 318 817 723 817 723M-240 -349C-240 -349 -172 56 292 183C756 310 824 715 824 715M-233 -357C-233 -357 -165 48 299 175C763 302 831 707 831 707M-226 -365C-226 -365 -158 40 306 167C770 294 838 699 838 699M-219 -373C-219 -373 -151 32 313 159C777 286 845 691 845 691M-212 -381C-212 -381 -144 24 320 151C784 278 852 683 852 683M-205 -389C-205 -389 -137 16 327 143C791 270 859 675 859 675M-198 -397C-198 -397 -130 8 334 135C798 262 866 667 866 667M-191 -405C-191 -405 -123 0 341 127C805 254 873 659 873 659M-184 -413C-184 -413 -116 -8 348 119C812 246 880 651 880 651M-177 -421C-177 -421 -109 -16 355 111C819 238 887 643 887 643M-170 -429C-170 -429 -102 -24 362 103C826 230 894 635 894 635M-163 -437C-163 -437 -95 -32 369 95C833 222 901 627 901 627M-156 -445C-156 -445 -88 -40 376 87C840 214 908 619 908 619M-149 -453C-149 -453 -81 -48 383 79C847 206 915 611 915 611M-142 -461C-142 -461 -74 -56 390 71C854 198 922 603 922 603M-135 -469C-135 -469 -67 -64 397 63C861 190 929 595 929 595M-128 -477C-128 -477 -60 -72 404 55C868 182 936 587 936 587M-121 -485C-121 -485 -53 -80 411 47C875 174 943 579 943 579M-114 -493C-114 -493 -46 -88 418 39C882 166 950 571 950 571M-107 -501C-107 -501 -39 -96 425 31C889 158 957 563 957 563M-100 -509C-100 -509 -32 -104 432 23C896 150 964 555 964 555M-93 -517C-93 -517 -25 -112 439 15C903 142 971 547 971 547M-86 -525C-86 -525 -18 -120 446 7C910 134 978 539 978 539M-79 -533C-79 -533 -11 -128 453 -1C917 126 985 531 985 531M-72 -541C-72 -541 -4 -136 460 -9C924 118 992 523 992 523M-65 -549C-65 -549 3 -144 467 -17C931 110 999 515 999 515M-58 -557C-58 -557 10 -152 474 -25C938 102 1006 507 1006 507M-51 -565C-51 -565 17 -160 481 -33C945 94 1013 499 1013 499M-44 -573C-44 -573 24 -168 488 -41C952 86 1020 491 1020 491M-37 -581C-37 -581 31 -176 495 -49C959 78 1027 483 1027 483M-30 -589C-30 -589 38 -184 502 -57C966 70 1034 475 1034 475M-23 -597C-23 -597 45 -192 509 -65C973 62 1041 467 1041 467M-16 -605C-16 -605 52 -200 516 -73C980 54 1048 459 1048 459M-9 -613C-9 -613 59 -208 523 -81C987 46 1055 451 1055 451M-2 -621C-2 -621 66 -216 530 -89C994 38 1062 443 1062 443M5 -629C5 -629 73 -224 537 -97C1001 30 1069 435 1069 435M12 -637C12 -637 80 -232 544 -105C1008 22 1076 427 1076 427M19 -645C19 -645 87 -240 551 -113C1015 14 1083 419 1083 419" | |
| stroke="url(`#paint0_radial_242_278`)" | |
| strokeOpacity="0.05" | |
| strokeWidth="0.5" | |
| ></path> | |
| {paths.map((path, index) => ( | |
| <motion.path | |
| key={`path-` + index} | |
| d={path} | |
| stroke={`url(`#linearGradient-`${index})`} | |
| strokeOpacity="0.4" | |
| strokeWidth="0.5" | |
| ></motion.path> | |
| ))} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/components/ui/background-beams.tsx` around lines 67 - 90, Update
the decorative SVG in the background-beams component to include
aria-hidden="true". Use Motion’s useReducedMotion() to detect reduced-motion
preference and disable or short-circuit the paths.map animation so the 50
gradient animations do not run when reduced motion is requested, while
preserving the existing animation for other users.
| function Tabs({ | ||
| className, | ||
| orientation = "horizontal", | ||
| ...props | ||
| }: TabsPrimitive.Root.Props) { | ||
| return ( | ||
| <TabsPrimitive.Root | ||
| data-slot="tabs" | ||
| data-orientation={orientation} | ||
| className={cn( | ||
| "group/tabs flex gap-2 data-horizontal:flex-col", | ||
| className | ||
| )} | ||
| {...props} | ||
| /> | ||
| ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
orientation is swallowed instead of forwarded to the primitive.
It's destructured out of props and only reflected as a data-orientation attribute, so Base UI's root never learns the orientation and keyboard navigation stays horizontal for vertical tab sets.
🐛 Proposed fix
<TabsPrimitive.Root
data-slot="tabs"
+ orientation={orientation}
data-orientation={orientation}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function Tabs({ | |
| className, | |
| orientation = "horizontal", | |
| ...props | |
| }: TabsPrimitive.Root.Props) { | |
| return ( | |
| <TabsPrimitive.Root | |
| data-slot="tabs" | |
| data-orientation={orientation} | |
| className={cn( | |
| "group/tabs flex gap-2 data-horizontal:flex-col", | |
| className | |
| )} | |
| {...props} | |
| /> | |
| ) | |
| function Tabs({ | |
| className, | |
| orientation = "horizontal", | |
| ...props | |
| }: TabsPrimitive.Root.Props) { | |
| return ( | |
| <TabsPrimitive.Root | |
| data-slot="tabs" | |
| orientation={orientation} | |
| data-orientation={orientation} | |
| className={cn( | |
| "group/tabs flex gap-2 data-horizontal:flex-col", | |
| className | |
| )} | |
| {...props} | |
| /> | |
| ) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/components/ui/tabs.tsx` around lines 8 - 23, Update the Tabs
component’s TabsPrimitive.Root invocation to explicitly forward the destructured
orientation prop, while retaining the existing data-orientation attribute and
styling behavior.
| useEffect(() => { | ||
| animate( | ||
| "span", | ||
| { | ||
| opacity: 1, | ||
| filter: filter ? "blur(0px)" : "none", | ||
| }, | ||
| { | ||
| duration: duration ? duration : 1, | ||
| delay: stagger(0.2), | ||
| }, | ||
| ); | ||
| }, [animate, duration, filter]); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Reveal effects omit the content prop from their dep arrays, so re-rendered spans stay hidden. Both components render words at opacity-0 and rely on a one-shot animate("span", ...) effect to reveal them; because the words prop isn't a dependency, changing it produces fresh hidden spans that never animate in.
packages/web/components/ui/text-generate-effect.tsx#L22-L34: addwordsto the dep array.packages/web/components/ui/typewriter-effect.tsx#L29-L45: addwordsandanimateto the dep array.
📍 Affects 2 files
packages/web/components/ui/text-generate-effect.tsx#L22-L34(this comment)packages/web/components/ui/typewriter-effect.tsx#L29-L45
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/components/ui/text-generate-effect.tsx` around lines 22 - 34,
Update the reveal effects so content changes re-animate newly rendered hidden
spans: in packages/web/components/ui/text-generate-effect.tsx lines 22-34, add
words to the useEffect dependency array; in
packages/web/components/ui/typewriter-effect.tsx lines 29-45, add both words and
animate to its dependency array.
| export const APP_ORIGIN = | ||
| process.env.NEXT_PUBLIC_APP_URL?.replace(/\/$/, "") ?? | ||
| process.env.APP_URL?.replace(/\/$/, "") ?? | ||
| "https://bloom-web-amber.vercel.app"; | ||
|
|
||
| export const INSTALL_UNIX = `curl -fsSL ${APP_ORIGIN}/install.sh | bash`; | ||
| export const INSTALL_WINDOWS = `irm ${APP_ORIGIN}/install.ps1 | iex`; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Treat empty URL environment variables as unset.
?.replace(...) ?? preserves "", so an empty NEXT_PUBLIC_APP_URL bypasses both APP_URL and the fallback. The landing CTA then emits invalid commands such as curl -fsSL /install.sh | bash.
Proposed fix
+const normalizeOrigin = (value?: string) =>
+ value?.trim().replace(/\/+$/, "") || undefined;
+
export const APP_ORIGIN =
- process.env.NEXT_PUBLIC_APP_URL?.replace(/\/$/, "") ??
- process.env.APP_URL?.replace(/\/$/, "") ??
+ normalizeOrigin(process.env.NEXT_PUBLIC_APP_URL) ??
+ normalizeOrigin(process.env.APP_URL) ??
"https://bloom-web-amber.vercel.app";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const APP_ORIGIN = | |
| process.env.NEXT_PUBLIC_APP_URL?.replace(/\/$/, "") ?? | |
| process.env.APP_URL?.replace(/\/$/, "") ?? | |
| "https://bloom-web-amber.vercel.app"; | |
| export const INSTALL_UNIX = `curl -fsSL ${APP_ORIGIN}/install.sh | bash`; | |
| export const INSTALL_WINDOWS = `irm ${APP_ORIGIN}/install.ps1 | iex`; | |
| const normalizeOrigin = (value?: string) => | |
| value?.trim().replace(/\/+$/, "") || undefined; | |
| export const APP_ORIGIN = | |
| normalizeOrigin(process.env.NEXT_PUBLIC_APP_URL) ?? | |
| normalizeOrigin(process.env.APP_URL) ?? | |
| "https://bloom-web-amber.vercel.app"; | |
| export const INSTALL_UNIX = `curl -fsSL ${APP_ORIGIN}/install.sh | bash`; | |
| export const INSTALL_WINDOWS = `irm ${APP_ORIGIN}/install.ps1 | iex`; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/lib/site.ts` around lines 6 - 12, Update the APP_ORIGIN
environment-variable selection to treat empty values as unset, so an empty
NEXT_PUBLIC_APP_URL falls through to APP_URL and then the default origin.
Preserve trailing-slash removal for non-empty values and keep INSTALL_UNIX and
INSTALL_WINDOWS using the resulting APP_ORIGIN.
| "@gsap/react": "^2.1.2", | ||
| "better-auth": "^1.6.25", | ||
| "class-variance-authority": "^0.7.1", | ||
| "clsx": "^2.1.1", | ||
| "gsap": "^3.15.0", | ||
| "input-otp": "^1.4.2", | ||
| "lucide-react": "^1.26.0", | ||
| "motion": "^12.42.2", | ||
| "next": "16.2.6", | ||
| "next-themes": "^0.4.6", | ||
| "react": "19.2.4", | ||
| "react-dom": "19.2.4", | ||
| "shadcn": "^4.14.1", | ||
| "shiki": "^4.3.1", |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
for p in gsap `@gsap/react` motion shiki; do
echo "== $p"
curl -s "https://registry.npmjs.org/$p" | jq -r '."dist-tags".latest'
done
gh api graphql -f query='
{
securityVulnerabilities(first: 5, ecosystem: NPM, package: "gsap") {
nodes { advisory { summary severity } vulnerableVersionRange firstPatchedVersion { identifier } }
}
}'Repository: Prince-695/bloom
Length of output: 424
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== package references =="
if [ -x "$(command -v fd)" ]; then
fd -a 'package(-lock)?(\.json|\.mlock|\.pnpm-lock\.yaml|yarn\.lock)$' . | sed 's#^\./##'
else
find . -maxdepth 5 \( -name 'package.json' -o -name 'package-lock.json' -o -name '*.pnpm-lock.yaml' -o -name 'yarn.lock' \) -print | sed 's#^\./##'
fi
echo
echo "== lockfile/package snippets for gsap/@gsap/react/motion/shiki =="
for f in $(fd 'package-(lock)?\.json|.*-lock\.ya?ml' . | sed 's#^\./##'); do
case "$f" in
*node_modules*|*.log) continue ;;
esac
echo "-- $f"
rg -n 'gsap|`@gsap/react`|motion|shiki' "$f" | head -80 || true
done
echo
echo "== npm availability for exact dependency versions =="
node - <<'JS'
const { execSync } = require('child_process');
const pkgs = [
'gsap@3.15.0',
'`@gsap/react`@2.1.2',
'motion@12.42.2',
'shiki@4.3.1'
];
for (const pkg of pkgs) {
try {
const out = execSync(`npm view ${pkg} --json`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
const json = JSON.parse(out);
const arr = Array.isArray(json) ? json : [json];
console.log(`== ${pkg} ==`);
for (const item of arr) {
console.log(`${item.name}@${item.version}`);
console.log(` dist.tarball=${item.dist?.tarball || ''}`);
console.log(` engines=${JSON.stringify(item.engines)}`);
}
} catch (e) {
console.log(`== ${pkg} ==`);
console.log(e.message);
}
}
JS
echo
echo "== GitHub Advisory DB advisory search for exact packages/version ranges =="
for pkg in gsap '`@gsap/react`' motion shiki; do
echo "-- ${pkg}"
gh api graphql -f query='
query($ecosystem: GHSA_Ecosystem!, $name: [String!]!) {
securityAdvisories(first: 20, ecosystem: $ecosystem, identities: { names: $name }) {
nodes { ghsaId summary severity identifiers { type value } firstPatchedVersion { identifier } }
}
}' -f name="$pkg" -f ecosystem=NPM || true
doneRepository: Prince-695/bloom
Length of output: 8631
Pin gsap to a patched version before using this range.
gsap@3.15.0 exists, but the version range in packages/web/package.json includes vulnerable versions (< 3.6.0) from a known HIGH-severity prototype pollution advisory. Bump to gsap@3.6.0 or later, then regenerate the lockfile.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/package.json` around lines 16 - 29, Update the gsap dependency
in package.json to a minimum version of 3.6.0, preserving the current dependency
declaration style, then regenerate the lockfile so it resolves only patched
versions.
Summary by CodeRabbit
New Features
/updatecommand.Documentation