[WEB-4660] chore: replace jsx element with react node#7567
[WEB-4660] chore: replace jsx element with react node#7567sriramveeraghanta merged 4 commits intopreviewfrom
Conversation
WalkthroughThis change standardizes React typing across the repo by replacing many Changes
Sequence Diagram(s)sequenceDiagram
participant Bar as BarChart.renderBars
participant Variant as shapeVariant()
participant React as ReactRenderer
Bar->>Variant: call shapeVariant(props, bar, stackKeys)
Variant-->>Bar: returns (React.ReactNode) (could be element, primitive, array)
alt result is valid React element
Bar->>React: return element directly
else result is non-element (string/array/fragment)
Bar->>React: wrap result in Fragment and return
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~15–20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
|
Pull Request Linked with Plane Work Items
Comment Automatically Generated by Plane |
There was a problem hiding this comment.
Actionable comments posted: 6
🔭 Outside diff range comments (8)
apps/web/core/components/dropdowns/estimate.tsx (1)
258-260: Avoid null key in list rendering
option.valuecan benull(for the “no estimate” option). Using anullkey can trigger React warnings and unstable reconciliation. Provide a stable fallback key.- filteredOptions.map((option) => ( - <Combobox.Option key={option.value} value={option.value}> + filteredOptions.map((option) => ( + <Combobox.Option key={option.value ?? "no_estimate"} value={option.value}>apps/web/core/components/integration/github/single-user-select.tsx (1)
43-44: Fix missing React type imports and avoid React namespace in typesThis file references
React.FCandReact.ReactNodewithout importing React/types. Import types and switch toFC/ReactNodeto avoid namespace reliance.Apply diffs within the selected ranges:
-export const SingleUserSelect: React.FC<Props> = ({ collaborator, index, users, setUsers }) => { +export const SingleUserSelect: FC<Props> = ({ collaborator, index, users, setUsers }) => {- content: React.ReactNode; + content: ReactNode;Add the following import near the top:
import type { FC, ReactNode } from "react";Also applies to: 69-70
apps/web/core/components/dropdowns/module/module-options.tsx (1)
17-23: Import ReactNode and remove React namespace dependencyWithout importing React,
React.ReactNodewill fail type-checking. UseReactNodewith a type import.Apply this diff within the selected range:
- content: React.ReactNode; + content: ReactNode;Add this import near the top:
import type { ReactNode } from "react";apps/web/core/components/dropdowns/cycle/cycle-options.tsx (1)
22-28: Import ReactNode and avoid React namespace in types
React.ReactNodewithout importing React will fail type-checking. UseReactNodewith a type import.Apply this diff within the selected range:
- content: React.ReactNode; + content: ReactNode;Add this import near the top:
import type { ReactNode } from "react";packages/utils/src/editor.ts (1)
40-46:toString()on ReactNode is unreliable — consider safer coercionIf
jsxis a React element or array,.toString()returns[object Object]. Also, if your intent is “strip HTML tags to plain text”, acceptstringinput or handle ReactNode robustly.Two options:
- Minimal: Restrict to string-like inputs.
-export const getTextContent = (jsx: React.ReactNode | null | undefined): string => { +export const getTextContent = (jsx: string | number | null | undefined): string => { if (!jsx) return ""; - const div = document.createElement("div"); - div.innerHTML = jsx.toString(); + const div = document.createElement("div"); + div.innerHTML = String(jsx); return div.textContent?.trim() ?? ""; }
- Robust (supports ReactNode): Use
React.isValidElementand optionallyrenderToStaticMarkup(note: increases bundle; consider only in server or gated usage).apps/web/core/components/gantt-chart/sidebar/gantt-dnd-HOC.tsx (1)
28-33: Fix effect dependencies to avoid stale closuresThe effect captures
idandisDragEnabledbut doesn’t list them as deps, and it depends onblockRef?.current(unstable). This can lead to stalecanDragbehavior or missed re-registrations.- useEffect(() => { + useEffect(() => { const element = blockRef.current; if (!element) return; return combine( draggable({ element, - canDrag: () => isDragEnabled, + canDrag: () => isDragEnabled, getInitialData: () => ({ id, dragInstanceId: "GANTT_REORDER" }), onDragStart: () => { setIsDragging(true); }, onDrop: () => { setIsDragging(false); }, }), dropTargetForElements({ element, canDrop: ({ source }) => source?.data?.id !== id && source?.data?.dragInstanceId === "GANTT_REORDER", @@ - }, [blockRef?.current, isLastChild, onDrop]); + }, [id, isLastChild, onDrop, isDragEnabled]);If you need to re-bind when the element changes, consider a ref callback or an IntersectionObserver rather than depending on
.current.Also applies to: 92-92
apps/admin/core/components/authentication/authentication-method-card.tsx (1)
53-53: Bug: template string injects "false" into className
className={shrink-0 ${disabled && "opacity-70"}}will add a literal "false" whendisabledis false. Usecnfor conditional classes.- <div className={`shrink-0 ${disabled && "opacity-70"}`}>{config}</div> + <div className={cn("shrink-0", { "opacity-70": disabled })}>{config}</div>packages/propel/src/charts/bar-chart/bar.tsx (1)
159-177: Use a typed ContentRenderer for theshapeprop to removeanycastsTo eliminate the
as anyreturn and the untypedshapeProps, extract the inline renderer into aContentRenderer<any>:• File: packages/propel/src/charts/bar-chart/root.tsx
• Lines: ~68–72-import <Bar - shape={(shapeProps: any) => { - const shapeVariant = barShapeVariants[bar.shapeVariant ?? "bar"]; - return shapeVariant(shapeProps, bar, stackKeys) as any; - }} - … /> +import type { ContentRenderer } from "recharts"; + +const shapeRenderer: ContentRenderer<any> = (shapeProps) => { + const shapeVariant = barShapeVariants[bar.shapeVariant ?? "bar"]; + return shapeVariant(shapeProps, bar, stackKeys); +}; + +<Bar + shape={shapeRenderer} + … />This change both types the incoming props and removes the downstream
as anycast.
🧹 Nitpick comments (16)
apps/web/core/components/dropdowns/estimate.tsx (2)
32-38: Use the imported ReactNode type instead of React.ReactNodeYou already import ReactNode; prefer using it directly for consistency and to avoid referencing the React namespace unnecessarily.
type DropdownOptions = | { value: string | null; query: string; - content: React.ReactNode; + content: ReactNode; }[] | undefined;
21-30: Type consistency: onChange should accept nullOptions include
value: string | null, and you add a “no estimate” option withnull. Align the prop and handler signatures to avoid type gaps.type Props = TDropdownProps & { button?: ReactNode; dropdownArrow?: boolean; dropdownArrowClassName?: string; - onChange: (val: string | undefined) => void; + onChange: (val: string | null | undefined) => void; onClose?: () => void; projectId: string | undefined; value: string | undefined | null; renderByDefault?: boolean; }; - const dropdownOnChange = (val: string | undefined) => { + const dropdownOnChange = (val: string | null | undefined) => { onChange(val); handleClose(); };Also applies to: 149-152
apps/web/core/components/analytics/select/select-x-axis.tsx (1)
7-8: Value type should allow null when “No value” is enabledYou render an option with
value={null}, but the prop type forvaluedoesn’t includenull.- value?: ChartXAxisProperty; + value?: ChartXAxisProperty | null;Also applies to: 20-21
apps/web/core/components/core/modals/gpt-assistant-popover.tsx (1)
193-197: Nested interactive element risk in GptAssistantPopover triggerThe
GptAssistantPopoverwraps itsbuttonprop inside a native<button>. If any caller passes an interactive element (e.g.<button>,<a>, or a focusable custom component) as thebuttonprop, you’ll end up with nested controls—leading to broken keyboard navigation, duplicate event handlers, and accessibility violations.Please verify and address as follows:
- Ensure every usage of
<GptAssistantPopover>passes only non-interactive content (plain text, icons, or static<span>elements) as thebuttonprop.- If you need to allow interactive triggers, switch to a clone-and-merge strategy: detect when
buttonis a valid React element, clone it withref,className, and event props instead of wrapping it in an extra<button>.This will protect against nested interactive elements and ensure proper focus/keyboard behavior.
apps/web/ce/components/workflow/state-option.tsx (1)
24-25: Stabilize keys for Combobox.Option
option.valuecan beundefined(per type), which leads to an unstable/invalid React key. Provide a fallback.Example:
- key={option.value} + key={option.value ?? option.query}apps/web/core/components/dropdowns/module/module-options.tsx (1)
95-104: Ensure stable React keys whenvaluecan be nullYou prepend an option with
value: null, but later usekey={option.value}. Keys should be stable strings/numbers.Example change:
- <Combobox.Option - key={option.value} + <Combobox.Option + key={`module-${option.value ?? "none"}`}Also applies to: 135-136
apps/web/core/components/dropdowns/cycle/cycle-options.tsx (1)
108-118: Stabilize keys wherevaluecan be nullYou unshift an option with
value: nulland later usekey={option.value}. Use a deterministic string key.Example:
- <Combobox.Option - key={option.value} + <Combobox.Option + key={`cycle-${option.value ?? "none"}`}Also applies to: 149-150
packages/utils/src/editor.ts (1)
40-40: Fix redundant union: duplicateReact.ReactNodeType should be deduped to keep it clean and accurate.
-export const getTextContent = (jsx: React.ReactNode | React.ReactNode | null | undefined): string => { +export const getTextContent = (jsx: React.ReactNode | null | undefined): string => {packages/ui/src/dropdowns/helper.tsx (1)
13-13: Minor:string | React.ReactNodeis redundant.
stringis already included inReact.ReactNode. You can simplify:- label?: string | React.ReactNode; + label?: React.ReactNode;apps/space/core/components/issues/issue-layouts/issue-layout-HOC.tsx (1)
6-6: Simplify children type toReactNode.
React.ReactNodealready includesstringand arrays, so the union is redundant. Prefer:-interface Props { - children: string | React.ReactNode | React.ReactNode[]; +interface Props { + children: React.ReactNode;Optionally, import the type to avoid the namespaced reference:
import type { ReactNode } from "react"; interface Props { children: ReactNode; }apps/web/core/components/gantt-chart/sidebar/root.tsx (2)
25-25: Prefer concise optional type
quickAdd?: React.ReactNode | undefinedcan be simplified toquickAdd?: React.ReactNode. Optional already impliesundefined.- quickAdd?: React.ReactNode | undefined; + quickAdd?: React.ReactNode;
100-100: Don’t hide valid falsy nodesWhen the type is
ReactNode, values like0or""are valid nodes but will be hidden by a truthy check. Prefer nullish coalescing.- {quickAdd ? quickAdd : null} + {quickAdd ?? null}apps/web/core/components/gantt-chart/root.tsx (1)
17-17: Prefer concise optional type
quickAdd?: React.ReactNode | undefined→quickAdd?: React.ReactNode.- quickAdd?: React.ReactNode | undefined; + quickAdd?: React.ReactNode;apps/web/core/components/issues/issue-layouts/issue-layout-HOC.tsx (1)
34-35: Simplify children type
string | React.ReactNode | React.ReactNode[]is redundant;React.ReactNodealready includesstringand arrays.-interface Props { - children: string | React.ReactNode | React.ReactNode[]; +interface Props { + children: React.ReactNode; layout: EIssueLayoutTypes; }apps/web/core/components/gantt-chart/chart/root.tsx (1)
46-46: Prefer concise optional type
quickAdd?: React.ReactNode | undefined→quickAdd?: React.ReactNode.- quickAdd?: React.ReactNode | undefined; + quickAdd?: React.ReactNode;apps/web/core/components/gantt-chart/chart/main-content.tsx (1)
49-49: Prefer concise optional type
quickAdd?: React.ReactNode | undefined→quickAdd?: React.ReactNode.- quickAdd?: React.ReactNode | undefined; + quickAdd?: React.ReactNode;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (41)
apps/admin/core/components/authentication/authentication-method-card.tsx(1 hunks)apps/admin/core/components/common/controller-input.tsx(2 hunks)apps/admin/core/components/common/copy-field.tsx(1 hunks)apps/space/core/components/issues/issue-layouts/issue-layout-HOC.tsx(1 hunks)apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/activity/page.tsx(1 hunks)apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx(1 hunks)apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/activity/page.tsx(1 hunks)apps/web/app/(all)/profile/activity/page.tsx(1 hunks)apps/web/ce/components/command-palette/helpers.tsx(1 hunks)apps/web/ce/components/workflow/state-option.tsx(1 hunks)apps/web/core/components/analytics/select/select-x-axis.tsx(1 hunks)apps/web/core/components/core/list/list-item.tsx(1 hunks)apps/web/core/components/core/modals/gpt-assistant-popover.tsx(1 hunks)apps/web/core/components/dropdowns/cycle/cycle-options.tsx(1 hunks)apps/web/core/components/dropdowns/estimate.tsx(1 hunks)apps/web/core/components/dropdowns/module/module-options.tsx(1 hunks)apps/web/core/components/gantt-chart/chart/main-content.tsx(1 hunks)apps/web/core/components/gantt-chart/chart/root.tsx(1 hunks)apps/web/core/components/gantt-chart/root.tsx(1 hunks)apps/web/core/components/gantt-chart/sidebar/gantt-dnd-HOC.tsx(1 hunks)apps/web/core/components/gantt-chart/sidebar/root.tsx(1 hunks)apps/web/core/components/integration/github/select-repository.tsx(1 hunks)apps/web/core/components/integration/github/single-user-select.tsx(1 hunks)apps/web/core/components/integration/jira/import-users.tsx(1 hunks)apps/web/core/components/issues/issue-detail-widgets/widget-button.tsx(1 hunks)apps/web/core/components/issues/issue-layouts/issue-layout-HOC.tsx(1 hunks)apps/web/core/components/issues/issue-layouts/quick-add/root.tsx(1 hunks)apps/web/core/components/issues/select/base.tsx(1 hunks)apps/web/core/components/labels/label-drag-n-drop-HOC.tsx(1 hunks)apps/web/core/components/project/member-select.tsx(1 hunks)apps/web/core/components/project/send-project-invitation-modal.tsx(1 hunks)apps/web/core/components/workspace/sidebar/favorites/favorite-items/common/favorite-item-title.tsx(1 hunks)packages/propel/src/charts/bar-chart/bar.tsx(2 hunks)packages/propel/src/charts/bar-chart/root.tsx(1 hunks)packages/types/src/instance/auth.ts(1 hunks)packages/ui/src/breadcrumbs/navigation-search-dropdown.tsx(1 hunks)packages/ui/src/dropdowns/helper.tsx(2 hunks)packages/ui/src/tables/types.ts(1 hunks)packages/ui/src/toast/index.tsx(1 hunks)packages/ui/src/tooltip/tooltip.tsx(1 hunks)packages/utils/src/editor.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-01-24T09:37:19.339Z
Learnt from: mathalav55
PR: makeplane/plane#6452
File: web/helpers/issue.helper.ts:6-6
Timestamp: 2025-01-24T09:37:19.339Z
Learning: In the Plane codebase, `ISSUE_DISPLAY_FILTERS_BY_LAYOUT` and `ISSUE_DISPLAY_FILTERS_BY_PAGE` are two distinct constants serving different purposes - one for layout-level filters and another for page-level filters. They are not interchangeable and should coexist.
Applied to files:
apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Build and lint web apps
- GitHub Check: Analyze (javascript)
- GitHub Check: Analyze (python)
🔇 Additional comments (26)
apps/web/core/components/integration/github/select-repository.tsx (1)
18-18: LGTM: broadened label type to ReactNodeThe prop type change aligns with React 19 compatibility and improves flexibility.
apps/web/core/components/analytics/select/select-x-axis.tsx (1)
13-14: LGTM: label widened to ReactNodeMatches the project-wide type broadening and is compatible with CustomSelect.
apps/web/core/components/core/modals/gpt-assistant-popover.tsx (1)
25-25: LGTM: button widened to ReactNodeThis adds flexibility without affecting behavior.
apps/web/core/components/issues/issue-detail-widgets/widget-button.tsx (1)
7-7: LGTM: icon widened to ReactNodeMakes the component more flexible (icons, text, fragments), no runtime impact.
apps/web/core/components/issues/select/base.tsx (1)
24-25: LGTM: label widened to ReactNodeConsistent with the PR’s objective; usage is already null-safe in render.
apps/web/core/components/issues/issue-layouts/quick-add/root.tsx (1)
45-46: LGTM: customQuickAddButton widened to ReactNodeImproves flexibility for consumers. Rendering is already guarded.
apps/web/ce/components/command-palette/helpers.tsx (1)
21-22: LGTM: icon type broadened to ReactNode | nullMatches return types in
commandGroupsand keeps API consistent.packages/ui/src/breadcrumbs/navigation-search-dropdown.tsx (1)
9-20: LGTM — type broadening is correct and compatibleUsing
React.ReactNodehere is safe since React is imported. Change aligns with React 19-friendly typing.apps/web/core/components/workspace/sidebar/favorites/favorite-items/common/favorite-item-title.tsx (1)
9-13: LGTM — prop type broadened appropriately
icon: React.ReactNodeis appropriate and consistent with usage; imports are present.packages/ui/src/toast/index.tsx (1)
31-33: LGTM — callback return type widened correctly
React.ReactNodereturn aligns withactionItems?: React.ReactNodein SetToastProps. No functional changes.apps/web/core/components/project/member-select.tsx (1)
50-55: Type widening to ReactNode is correctSwitching
contenttoReact.ReactNodealigns with the PR goal and allows more flexible option renderers. LGTM.apps/web/core/components/project/send-project-invitation-modal.tsx (1)
163-168: Good:contentaccepts ReactNodeBroadening
contenttoReact.ReactNodeis consistent with the repo-wide refactor. Looks good.packages/ui/src/tables/types.ts (1)
4-6: Good generalization to ReactNodeAllowing
React.ReactNodefor header/data cell renderers is appropriate and consistent with the migration. LGTM.apps/admin/core/components/common/copy-field.tsx (1)
9-20: Description broadened to ReactNode — OKTyping
descriptionasstring | React.ReactNodeis fine and matches the refactor’s intent. Looks good.apps/admin/core/components/common/controller-input.tsx (2)
11-20: Props type update to ReactNode — good
descriptionnow supports any React node. Aligned with the PR goal.
22-30: Form field type update to ReactNode — goodConsistent with the component props; no issues.
apps/web/core/components/labels/label-drag-n-drop-HOC.tsx (1)
41-46: Render-prop return widened to ReactNode — goodThis broadens flexibility for the
childrenrender function and is safe here. LGTM.apps/web/core/components/core/list/list-item.tsx (1)
15-17: Type broadening to ReactNode is appropriate and consistent.The props now accept any renderable node and match the usage patterns in this component. No logic changes required.
Note: Using "&&" to guard rendering will intentionally drop falsy nodes like 0 or "". If that’s ever a valid input for these props, switch to a nullish check instead.
Also applies to: 25-25
packages/ui/src/dropdowns/helper.tsx (1)
10-10: No type change needed forcustomButtonorfooterOption
After digging into theCustomMenu/CustomSelectimplementations and existing call-sites, we see:
- Neither prop is ever “unwrapped” or cloned—instead the wrapper component applies click, keyboard and focus props (including
tabIndex) to its container.- All current consumers pass interactive elements (buttons, spans with handlers, icons inside wrappers, etc.).
Keeping these as
React.ReactNodelets callers supply any markup while the dropdown internals ensure proper interactivity.apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx (1)
9-9: ISSUE_DISPLAY_FILTERS_BY_PAGE shape is correct – no changes neededConfirmed in packages/constants/src/issue/filter.ts that ISSUE_DISPLAY_FILTERS_BY_PAGE includes an
issuesentry with sub-keys for each layout (e.g.list,kanban,spreadsheet,calendar), matching all values of EIssueLayoutTypes. The import and usage ofISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout]are valid as-is.
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/activity/page.tsx (1)
37-37: LGTM on type broadening.Switching to
React.ReactNode[]is safe here; the array is rendered directly and remains compatible.apps/web/app/(all)/profile/activity/page.tsx (1)
40-40: LGTM on type broadening.
React.ReactNode[]fits usage and aligns with the PR’s goal.apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/activity/page.tsx (1)
34-34: LGTM on type broadening.The change to
React.ReactNode[]is safe and consistent.apps/web/core/components/gantt-chart/sidebar/gantt-dnd-HOC.tsx (1)
16-16: LGTM on broadening render-prop typeAllowing
childrento returnReact.ReactNodeis correct and future-proof.apps/admin/core/components/authentication/authentication-method-card.tsx (1)
10-11: LGTM on broadening to ReactNodeBroadening
iconandconfigtoReact.ReactNodematches usage and increases flexibility.packages/propel/src/charts/bar-chart/bar.tsx (1)
179-186: Record signature update is consistent with the factory; LGTMThe
barShapeVariantsfunction signatures now correctly returnReact.ReactNodeand align withcreateShapeVariant. No further changes needed here.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/types/package.json (1)
36-37: Audit pinned @types/react for React 19 migrationWe found the following packages still peer-depend on React 18 and/or pin @types/react / @types/react-dom in devDependencies:
• packages/editor/package.json
– peerDependencies: react ^18.3.1, react-dom 18.3.1
– devDependencies: @types/react ^18.3.11, @types/react-dom ^18.2.18• packages/types/package.json
– peerDependencies: react ^18.0.0, react-dom ^18.0.0
– devDependencies: @types/react 18.3.11, @types/react-dom 18.3.1• packages/ui/package.json
– peerDependencies: react ^18.3.1, react-dom ^18.3.1
– devDependencies: @types/react ^18.3.11, @types/react-dom ^18.2.18• apps/admin, apps/space, apps/web (all pin @types/react ^18.3.11, @types/react-dom ^18.2.18)
• packages/hooks (pins @types/react ^18.3.11)
• packages/propel (pins @types/react 18.3.1, @types/react-dom 18.3.0)
• packages/utils (pins @types/react ^18.3.11)If you’re upgrading the monorepo to React 19 (which bundles its own types), please either:
- Remove these devDependencies from packages that no longer need standalone typings,
- Or scope/conditionally install them only where React 18 compatibility is required,
to avoid type conflicts in consumer builds.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
packages/types/package.json(1 hunks)packages/ui/src/tooltip/tooltip.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/ui/src/tooltip/tooltip.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Analyze (python)
- GitHub Check: Analyze (javascript)
- GitHub Check: Build and lint web apps
Description
Type of Change
Summary by CodeRabbit