Skip to content

Refactor package listing (TS-2913)#1686

Open
Roffenlund wants to merge 48 commits intomasterfrom
refactor-packageListing
Open

Refactor package listing (TS-2913)#1686
Roffenlund wants to merge 48 commits intomasterfrom
refactor-packageListing

Conversation

@Roffenlund
Copy link
Copy Markdown
Contributor

Summary
This PR refactors packageListing.tsx to improve readability, maintainability, and type safety by decomposing the component into focused sub-components and cleaning up related logic.

Changes

  • Refactored packageListing.tsx into smaller, well-defined sub-components:
  • PackageHeader
  • PackageDrawer
  • PackageActions
  • PackageTabs
  • PackageMeta
  • PackageBox
  • PackageSidebar
  • Moved and refactored helper functions from packageListing.tsx into their respective sub-components
  • Removed unused imports
  • Updated and clarified TypeScript types and props across all new sub-components

Roffenlund and others added 30 commits December 11, 2025 14:43
- Implement object schema for package listing status
- Implement response schema for package listing status
- Implement a function for fetching a package listing's status from API
- Update package details fetch function with useSession parameter
- Implement a getter function for dapper for getting listing status
- Update getPackageListing details with useSession parameter
Implement two functions responsible for getting public or private
package listings from the same endpoint.

This is required for users who should be able to see for instance rejected
package listings.

The function for getting public package listings is to be used in the
SSR loader function, and the function for getting private package listings
is to be used in the clientLoader function.
- Move management tooling into an own file
- Move ReviewPackageForm into an own file
- Use revalidate in form to handle state updates
- Update state usage in form
- Add dynamic fetching of status colors
- Add type interfaces
- Small refactor to code for readability
- Update clientLoader and loader to use utils
- Get public package listing with loader
- Get public/private package listing in clientLoader
- Get package listing status in clientLoader
- Utilize getPackagePermissions in order to view/hide management tooling
- Remove shouldRevalidate function
- Utilize components split into other files
- Simplify component code with less nested Suspense/Await blocks
- Remove usage of useMemo for Promises
- Add and fix missing types
- Fix styling issues
- Fix report form
- Move baseUrl to index.ts for re-usability
- Simplify if statement in packageListing.tsx
- Remove useSession boolean variable and just pass true to getPackageListingDetails
Use nested Promise/Await instead of Promise.all in component
…-and-panel-visbility

Moderation tools review and panel visbility (TS-2907 & TS-2908 & TS-2910)
Add styling for displaying internal notes and rejection reason
- Add component for internal notes
- Add component for rejection reason
- Permissions handled in backend
Add InternalNotes and RejectionReason in packageListing.tsx under
the ManagementTools as these components require the status data.
- Update loader and clientloader
- Use getPrivateListing & getPublicListing
- Get private package listing for authorized users else 404
- Add checks for if listing is undefined to prevent not expected 404's
- Fix the url for unlisting package listings
Ensure the category select field is properly populated when listing data loads.
- Wrap the categories section in a permissions check for managing categories
PackageListing as a concept is version-agnostic, but to ensure we don't
serve pages for rejected package versions, they need to be
community-scoped, as rejection is tied to PackageListing object.

This commit adds the support to Dapper and listingUtils, the actual
usage will be done in a separate commit.

Since by default a PackageListing shows the latest package version, the
version argument remains optional.
- Use Dapper's getPackageListingDetails method rather than
  getPackageVersionDetails as the latter may serve rejected content
  which we don't want to show
- SSR loader and clientLoader mimic what their counterparts in
  packageListing.tsx does, namely loader makes the request
  unauthenticated and returns undefined if the response is 404, while
  clientLoader first does an unauthenticated request, and should that
  fail, another one as authenticated. This is done to improve cache
  hits
- Since both loaders now await the responses for listing, it's never
  a promise and lot of the Suspense/Await elements become obsolete
The required information is now available in the listing data too.
I must admit I'm meddling with powers I don't comprehend. Why was this
implemented this way? Is it still needed? At least for me both SSR and
client side loading seem to work without issues.
Considered PackageVersionListing too but I think this pairs nicer with
PackageListing component.
- Return null if status is undefined in InternalNotes
- Return null if status is undefined in RejectionReason
- Add getUserPermissions to listingUtils.ts
- Add getPackageListingStatus to ListingUtils.ts
- Return undefined from getPackageListingStatus if API return 403
- Use functions from listingUtils.ts instead of implementing them in the component
…ting-page

Bug  403 on package listing page (TS-2924)
anttimaki and others added 18 commits January 8, 2026 11:16
Update data fetching of a specific version of a listed package
Content moderators currently reject packages by marking the
PackageListing object rejected. PackageListings are community-scoped.

Some of the endpoints are package scoped, i.e. they are not related to
any communities or listings. These endpoints therefore currently have
no way of telling if a package has been rejected or not. In worst case,
the rejected packages can contain malware, so under no circumstances do
we want to show such packages on the website where unsuspecting users
can be tricked to download the mods.

Therefore the pages of the website that use such endpoints are disabled
until we figure out how to address the problem. Since the old website
doesn't have unscoped endpoints, from feature parity perspective the
new one doesn't need to have them either.

Footgun warning: the website still uses some endpoints that are not
community-scoped, such as "get README for this version of the package".
This is fine as long as the page where the data is shown takes measures
to ensure the content can be shown by querying the package listing
endpoint too and showing 404 error if the package is rejected.
- The only difference between the components was that one expected the
  route to contain communityId which is used to fetch the latest
  version number, while the two other expected packageVersion to be
  included in the URL. Loaders can easily check the route params and
  serve both cases
  - We may want to do something similar to readme and versions
    components that also exist in triplicate
- The part of the code that fetches the package listing details when
  packageVersion is not provided by the URL now uses the same approach
  as the PackageListing component does. This fixes an issue where the
  tab would show 404 when viewing a rejected package's dependencies,
  even if the user was authententicated and had permissions to view it
- React-router uses the component name as the default id, but since we
  have multiple routes for the same component, we need to define ids
  manually
- Parent route PackageListing and sub route Required both request
  package listing data from backend
- Using the recently added juiced up Dapper instance, multiple request
  to same endpoint with same arguments withing a request are
  deduplicated
- Technically this doesn't help in packageListingVersion. While it
  makes the request, its sub route doesn't since it has the required
  data available as URL parameter. I decided to change the parent route
  to use the deduplicating Dapper version anyway for consistency's sake
- Move package meta tags into an own component
- Use PackageMetaTags in the PackageListing component
Add new team interface containing:
- identifier
- name
- donation_link
- Implement PackageHeader comoponent
- Use component in packageListing.tsx
Implement component for package boxes in an own re-usable file.
- Implement component to be used as sub-component for package drawer
- Use PackageDrawer in packageListing.tsx
Implement package actions in an own component. Use this sub-component in the
packageListing component.
Implement and use PackageTabs in packageListing.tsx.
- Removed unused helper functions which are moved into sub-components
- Remove more unused imports
- Use sub-components where applicable
Implement PackageSidebar and use this sub-component in packageListing.tsx
* Add null and make parameters nullable
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 8, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov bot commented Jan 8, 2026

Codecov Report

❌ Patch coverage is 0% with 480 lines in your changes missing coverage. Please review.
✅ Project coverage is 11.67%. Comparing base (326ea87) to head (4ca8e4b).

Files with missing lines Patch % Lines
...ix/app/p/components/PackageListing/PackageTabs.tsx 0.00% 78 Missing ⚠️
...mix/app/p/components/PackageListing/PackageBox.tsx 0.00% 65 Missing ⚠️
...ix/app/p/components/PackageListing/PackageMeta.tsx 0.00% 65 Missing ⚠️
...app/p/components/PackageListing/PackageSidebar.tsx 0.00% 65 Missing ⚠️
...app/p/components/PackageListing/PackageActions.tsx 0.00% 54 Missing ⚠️
.../app/p/components/PackageListing/PackageHeader.tsx 0.00% 44 Missing ⚠️
apps/cyberstorm-remix/app/p/packageListing.tsx 0.00% 38 Missing ⚠️
.../app/p/components/PackageListing/PackageDrawer.tsx 0.00% 37 Missing ⚠️
...pp/p/components/PackageListing/PackageMetaTags.tsx 0.00% 34 Missing ⚠️
Additional details and impacted files
@@                         Coverage Diff                         @@
##           moderation-tools-feature-branch    #1686      +/-   ##
===================================================================
- Coverage                            11.71%   11.67%   -0.04%     
===================================================================
  Files                                  321      329       +8     
  Lines                                22849    22958     +109     
  Branches                               510      521      +11     
===================================================================
+ Hits                                  2676     2681       +5     
- Misses                               20173    20277     +104     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment on lines +17 to +36
function packageTags(
listing: PackageListingDetails,
community: Community | Promise<Community>
) {
return listing.categories.map((category) => {
return (
<NewTag
key={category.name}
csMode="cyberstormLink"
linkId="Community"
community={community.identifier}
queryParams={`includedCategories=${category.id}`}
csSize="small"
csVariant="primary"
>
{category.name}
</NewTag>
);
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type Safety Bug: Accessing property on potentially unresolved Promise

The packageTags function accepts community: Community | Promise<Community> but directly accesses community.identifier on line 27. If community is a Promise<Community> (which the type allows), this will fail at runtime with a TypeError since Promises don't have an identifier property.

// Current (buggy):
function packageTags(
  listing: PackageListingDetails,
  community: Community | Promise<Community>
) {
  // ...
  community={community.identifier}  // Fails if community is Promise
}

// Fix: Change type to only accept resolved Community:
function packageTags(
  listing: PackageListingDetails,
  community: Community  // Remove Promise option
) {
  // ...
  community={community.identifier}  // Now safe
}

While current usage wraps calls in <Await>, the function signature allows direct calls with unresolved Promises, creating a potential runtime crash.

Suggested change
function packageTags(
listing: PackageListingDetails,
community: Community | Promise<Community>
) {
return listing.categories.map((category) => {
return (
<NewTag
key={category.name}
csMode="cyberstormLink"
linkId="Community"
community={community.identifier}
queryParams={`includedCategories=${category.id}`}
csSize="small"
csVariant="primary"
>
{category.name}
</NewTag>
);
});
}
function packageTags(
listing: PackageListingDetails,
community: Community
) {
return listing.categories.map((category) => {
return (
<NewTag
key={category.name}
csMode="cyberstormLink"
linkId="Community"
community={community.identifier}
queryParams={`includedCategories=${category.id}`}
csSize="small"
csVariant="primary"
>
{category.name}
</NewTag>
);
});
}

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change the type to just Community and this should be good

@Roffenlund Roffenlund requested a review from Oksamies January 9, 2026 10:40
Comment on lines +17 to +36
function packageTags(
listing: PackageListingDetails,
community: Community | Promise<Community>
) {
return listing.categories.map((category) => {
return (
<NewTag
key={category.name}
csMode="cyberstormLink"
linkId="Community"
community={community.identifier}
queryParams={`includedCategories=${category.id}`}
csSize="small"
csVariant="primary"
>
{category.name}
</NewTag>
);
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change the type to just Community and this should be good

@Roffenlund Roffenlund force-pushed the moderation-tools-feature-branch branch from 0d74972 to 8e553ae Compare January 14, 2026 11:55
Base automatically changed from moderation-tools-feature-branch to master January 15, 2026 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants