Skip to content

Feat/comfyhub redesign#950

Open
MaanilVerma wants to merge 65 commits into
Comfy-Org:mainfrom
MaanilVerma:feat/comfyhub-redesign
Open

Feat/comfyhub redesign#950
MaanilVerma wants to merge 65 commits into
Comfy-Org:mainfrom
MaanilVerma:feat/comfyhub-redesign

Conversation

@MaanilVerma

@MaanilVerma MaanilVerma commented Jun 18, 2026

Copy link
Copy Markdown

Summary

End-to-end visual redesign of the ComfyHub site to the new Figma direction — a real marketing navbar, a much stronger search, a single browse toolbar replacing the sidebar/drawer filters, a featured carousel, and reworked cards and detail page. SEO and i18n are preserved across all 11 locales; the whole hub now reads as one consistent unit instead of a basic filtered grid.

Figma Link

Changes

What

  • Marketing navbar, ported from the current comfy.org nav so it matches upstream exactly: a desktop mega-menu (multi-column dropdowns with a featured image card per section, "NEW" badges, animated CTA pill) and a mobile slide-in sheet with drill-down sub-menus and a back button. Keeps the hub-only "Top creators" shortcut. Built on reka-ui navigation-menu/sheet primitives.
  • Search that stops silently failing. It used to intermittently return nothing and miss obvious matches. Index and query now share one tokenizer config so they can't drift; a failed first index fetch no longer poisons the cache (it retries and shows a clean empty state); the tokenizer splits hyphen and underscore terms so flux_image-to-video is found by "flux"/"image"/"video"; common abbreviations expand at query time ("txt2img" → text to image, "i2v" → image to video, "cn" → controlnet); fuzziness scales with term length so short queries stay tight and long typos still match; multi-word queries match all terms first and only fall back to any-term when that's empty. Real usage counts now feed ranking, so popular workflows surface.
  • Browse toolbar replaces the sidebar and the mobile filter drawer with one component (tabs + sort + facet filters), so filtering is the same on every screen size.
  • Featured carousel — a usage-ranked hero on the landing page.
  • Workflow card — 4:3 thumbnail with the title and provider logo overlaid on a scrim, a creator line, a circular CTA, and a tag row that scrolls with chevrons that only appear when the tags overflow. Author-name clipping is fixed, cards are a bit wider, and "Load more" matches Figma.
  • Detail page — a breadcrumb trail in place of the back button, an expandable description whose full text is always in the SSR DOM (so crawlers see it while it's collapsed), an accordion FAQ, and a "View all workflows" related grid.
  • SEO — FAQ and breadcrumb JSON-LD are escaped before injection (no script-breakout) and applied consistently across the workflow/category/tag/model pages; breadcrumb labels are localized and mirror the visible trail; collection pages carry an ItemList count.
  • Search a11y — wrapped in <form role="search">, Enter activates the highlighted (or first) result with no page reload, and a live region announces the result count.

Breaking

None. Protected infra is restyled around, never removed — SEOHead/HreflangTags, canonical {name}-{shareId} links, getCloudCtaUrl()/UTM params, and <Analytics /> are all intact, and the new JSON-LD matches what's rendered.

Review Focus

  • Search is the riskiest area — see the AND→OR fallback and the index-fetch recovery; both change behavior users will notice. The shared tokenizer config is the one file to read for why index and query stay in sync.
  • Navbar is a faithful port, not a fresh design — it intentionally mirrors the upstream comfy.org nav (mega-menu structure, mobile sheet, featured cards) so it stays in sync; the only deliberate addition is the hub's "Top creators" section in the mobile sheet.
  • Filter migration — the browse toolbar fully replaces the removed sidebar and mobile filter drawer; worth confirming facet-count and active-filter parity.
  • Island architecture held throughout — cross-island state via shared composables, no custom-event bridges, islands get plain data + locale and don't import i18n.

Testing

Search is the part most likely to regress silently, so it has the most coverage; the rest is verified by the type/lint gate plus manual passes against the Figma.

  • tests/unit/search-config.test.ts — tokenizer sub-parts, synonym expansion, length-aware fuzzy thresholds, and the AND→OR fallback (including the title-over-description ranking).
  • tests/unit/use-facets.test.ts — facet derivation and dedup from the template set.
  • tests/unit/featured.test.ts / tests/unit/provider-logos.test.ts / tests/unit/routes.test.ts — usage-ranked selection, word-boundary logo matching, and canonical slug composition.
  • Gate: pnpm run check (0 errors), pnpm test (168 passing), pnpm run lint, pnpm run build all green.
  • Manual (pnpm run dev): search txt2img / i2v / cn and flux; block the first search-index.json request once and search again to confirm it recovers; open a desktop dropdown and the mobile sheet drill-down; scroll a card's tag row via the chevrons; spot-check a /zh/… and the RTL /ar/… page for i18n + dir.

Screen Recording

Screen.Recording.2026-06-23.at.8.38.49.PM.mov

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR overhauls the hub site: it adds an Embla-powered featured carousel, rebuilds the site navigation (desktop dropdowns, mobile sheet drawer, GitHub star badge), introduces a BrowseToolbar with facet filter popover and sort cycling, refactors the template detail page with breadcrumbs/FAQ accordion/expandable text, centralizes MiniSearch configuration, and wires localized toolbar labels and structured-data helpers across all workflow route pages.

Changes

Hub Site Redesign: Carousel, Navigation, Browse Toolbar & Detail Page

Layer / File(s) Summary
Shared utilities, composables, design tokens, and i18n
site/src/lib/search-config.ts, site/src/lib/routes.ts, site/src/lib/provider-logos.ts, site/src/lib/featured.ts, site/src/lib/hub-api.ts, site/src/lib/github.ts, site/src/lib/structured-data.ts, site/src/lib/toolbar.ts, site/src/lib/social-links.ts, site/src/config/nav-routes.ts, site/src/composables/scrollLock.ts, site/src/composables/useHubStore.ts, site/src/composables/useFacets.ts, site/src/i18n/ui.ts, site/src/styles/..., site/src/components/ui/button/index.ts, site/src/components/ui/badge/..., site/src/components/ui/button-pill/..., site/src/components/ui/icons/...
Introduces all foundational utilities, composables, i18n keys (11 locales), design tokens, and extended button/badge/ButtonPill UI primitives that every higher layer depends on — the roots of the dependency tree!
Search configuration consolidation
site/src/lib/search-config.ts, site/src/lib/search.ts, site/scripts/lib/search/build-index.ts
Extracts shared MiniSearch config (field lists, tokenizer, query expansion, searchWithFallback) into search-config.ts; refactors the client and build-index script to consume it; adds usage data and fetch timeouts.
Site navigation system
site/src/config/main-navigation.ts, site/src/components/ui/navigation-menu/..., site/src/components/ui/sheet/..., site/src/components/hub/nav/..., site/src/composables/useCurrentPath.ts, site/src/components/hub/HubNavbar.astro
Builds a full responsive navigation system: navigation model types, NavigationMenu and Sheet UI primitives, NavColumn/NavFeaturedCard/NavLinkContent, NodeBadge/GitHubStarBadge, HeaderMainDesktop/HeaderMainMobile with scroll-lock, HeaderMain orchestrator, and HubNavbar refactored to a two-row header with GitHub stars.
Featured carousel and HubHero
site/package.json, site/src/components/hub/FeaturedCarousel.vue, site/src/components/hub/HubHero.astro
Adds Embla carousel dependencies and FeaturedCarousel (autoplay, RTL, video fallback, slide view-model) along with a HubHero that shows a localized template count — quite a carousel of changes! 🎠
Browse toolbar, WorkflowGrid refactor, and HubBrowse filter pipeline
site/src/components/hub/BrowseToolbar.vue, site/src/components/hub/TagScroller.vue, site/src/components/hub/WorkflowGrid.vue, site/src/components/hub/HubBrowse.vue
Adds BrowseToolbar with tab/filter-popover/sort controls wired to useHubStore/useFacets; adds TagScroller; refactors WorkflowGrid to use the shared toolbar and store-driven tab/sort/pagination; simplifies HubBrowse to badge-driven filteredTemplates.
HubWorkflowCard restyling and SearchPopover improvements
site/src/components/hub/HubWorkflowCard.vue, site/src/components/hub/SearchPopover.vue, site/src/components/ThumbnailDisplay.astro
Refactors HubWorkflowCard to shared path/logo helpers and a landscape layout with scrim overlay; updates SearchPopover to thumbnailPath, adds <form role="search"> submission and a screen-reader live region; refactors ThumbnailDisplay to a shared mediaClass.
Template detail page: Breadcrumbs, ExpandableText, FaqAccordion
site/src/components/template-detail/Breadcrumbs.astro, site/src/components/template-detail/ExpandableText.vue, site/src/components/template-detail/FaqAccordion.vue, site/src/components/template-detail/TemplateDetailPage.astro
Adds Breadcrumbs (matching JSON-LD), ExpandableText (animated max-height with ResizeObserver and prefers-reduced-motion), and FaqAccordion (reka-ui); refactors TemplateDetailPage to fixed media-frame layout with optional faqItems.
Workflow route pages: toolbarLabels, carousel, related workflows
site/src/pages/workflows/..., site/src/pages/[locale]/workflows/...
Wires all workflow route pages to pass buildToolbarLabels/buildFacetsConfig into grids, adds FeaturedCarousel and HubHero count to index pages, adds related workflows to detail pages, and switches all breadcrumb/FAQ structured data to shared helpers with serializeJsonLdForScript.
Unit tests and Vitest config
site/vitest.config.ts, site/tests/unit/featured.test.ts, site/tests/unit/provider-logos.test.ts, site/tests/unit/routes.test.ts, site/tests/unit/search-config.test.ts, site/tests/unit/use-facets.test.ts
Adds @ alias to Vitest config and unit/integration tests for getFeatured, featuredPreloadImage, getLogoPath/providerName, tagPath/creatorPath, tokenize/expandQuery/searchOptions/searchWithFallback, and useFacets reactivity and active-state helpers.

Sequence Diagram(s)

sequenceDiagram
  participant Page as workflows/index.astro
  participant Featured as getFeatured / featuredPreloadImage
  participant Carousel as FeaturedCarousel (client:load)
  participant Browse as HubBrowse (client:load)
  participant Toolbar as BrowseToolbar
  participant Store as useHubStore
  participant Grid as WorkflowGrid
  participant Facets as useFacets

  Page->>Featured: loadSerializedTemplates() → getFeatured(templates)
  Featured-->>Page: featured[], featuredLcpImage
  Page->>Carousel: templates=featured, labels, locale
  Carousel->>Carousel: initEmbla + autoplay plugin
  Page->>Browse: templates, facetsConfig, toolbarLabels
  Browse->>Grid: filteredTemplates, facetTemplates, facetsConfig, toolbarLabels
  Grid->>Toolbar: facetSource, facetsConfig, toolbarLabels
  Toolbar->>Store: setTab / cycleSort / toggleBadge
  Toolbar->>Facets: facetsByType, isBadgeActive, activeCountForType
  Store-->>Grid: activeTab, sortBy → sorted/filtered display
Loading
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

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.

@socket-security

socket-security Bot commented Jun 18, 2026

Copy link
Copy Markdown

No dependency changes detected. Learn more about Socket for GitHub.

👍 No dependency changes detected in pull request

@MaanilVerma MaanilVerma marked this pull request as ready for review June 18, 2026 15:10

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
site/src/components/hub/HubBrowse.vue (1)

63-67: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle dual mode badges as a union (not first-match wins).

At Line 65 and Line 66, when both app and nodeGraph are selected, the first if returns early and filters to apps only. In rhyme-time: two badges in, one badge out. Treat both-selected as “show all modes.”

🔧 Suggested fix
   if (modeBadges.length > 0) {
+    const includeApps = modeBadges.includes('app');
+    const includeNodeGraphs = modeBadges.includes('nodeGraph');
     result = result.filter((t) => {
-      if (modeBadges.includes('app')) return t.isApp;
-      if (modeBadges.includes('nodeGraph')) return !t.isApp;
+      if (includeApps && includeNodeGraphs) return true;
+      if (includeApps) return t.isApp;
+      if (includeNodeGraphs) return !t.isApp;
       return true;
     });
   }
🤖 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 `@site/src/components/hub/HubBrowse.vue` around lines 63 - 67, The filter logic
in the modeBadges length check uses early return statements that cause only the
first matching condition to apply, so when both 'app' and 'nodeGraph' badges are
selected, only one mode is displayed. Modify the conditional logic to treat
multiple selected badges as a union by combining the conditions with OR logic
instead of separate if statements - return true if (modeBadges includes 'app'
AND t.isApp) OR (modeBadges includes 'nodeGraph' AND !t.isApp), ensuring that
when both badges are selected, items matching either criterion are included in
the results.
site/src/components/hub/SearchPopover.vue (1)

549-561: ⚠️ Potential issue | 🟠 Major

Verify thumbnail normalization cannot double-prefix local paths.

Lines 549-550 and line 560 always route thumbnails through thumbnailPath(file), but the function only guards against full HTTP/HTTPS URLs. If upstream data contains an already-prefixed path like /workflows/thumbnails/... or workflows/thumbnails/..., this produces broken doubled paths.

The same vulnerability exists in ThumbnailDisplay.astro (line 44) with an identical thumbUrl() helper.

Fix: Expand the URL check in both thumbnailPath() and thumbUrl() to reject paths that already start with /workflows/thumbnails/ or contain that segment:

export const thumbnailPath = (asset: string) =>
  asset.startsWith('http://') || asset.startsWith('https://') || asset.startsWith('/workflows/thumbnails/')
    ? asset
    : `/workflows/thumbnails/${asset}`;
🤖 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 `@site/src/components/hub/SearchPopover.vue` around lines 549 - 561, The
`thumbnailPath()` function in SearchPopover.vue (used in lines 549-550 and 560)
and the identical `thumbUrl()` helper in ThumbnailDisplay.astro (line 44) only
guard against full HTTP/HTTPS URLs but fail to prevent double-prefixing when
paths already contain the `/workflows/thumbnails/` segment. Expand the URL check
in both functions to also reject paths that already start with
`/workflows/thumbnails/` by adding an additional condition to the existing
startsWith checks, so that upstream data containing already-prefixed paths will
be returned as-is instead of being double-prefixed.
🤖 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 `@site/src/components/hub/nav/MobileMenu.vue`:
- Around line 76-89: The watcher for the `open` property has a race condition
where if `open` changes before `await nextTick()` resolves, the stale branch can
still execute the focus and event listener attachment. Guard against stale
callbacks by adding a check after the `await nextTick()` in the isOpen branch to
verify that `isOpen` is still true before proceeding with the focus() call and
addEventListener('keydown', trapFocus) call. This ensures that if the menu was
closed while waiting for the next tick, the stale focus and event handling code
won't execute.
- Line 44: The template ref variable menuRef is initialized with ref<HTMLElement
| undefined>() but should follow the repository's convention of using
ref<HTMLElement | null>(null) for DOM element refs. Update the menuRef
declaration to change the type from HTMLElement | undefined to HTMLElement |
null, and explicitly initialize it with null as the default value to maintain
consistency with other template refs in the codebase like SearchPopover.vue and
HubWorkflowCard.vue.

In `@site/src/components/hub/nav/NavDesktopLink.vue`:
- Around line 48-50: The active navigation state detection in NavDesktopLink.vue
is comparing currentPath (a pathname) directly against href values (which may be
absolute URLs), causing the comparison to fail. Extract the pathname from the
href values before comparing them with currentPath to ensure the string equality
check works correctly. Apply this normalization to all three locations where
this comparison occurs: the ternary operator around line 48-50, the check around
line 66-68, and the condition around line 79-84.

In `@site/src/components/hub/nav/SiteNav.vue`:
- Around line 114-117: The closeMobileMenu function is forcing focus on the
hamburger element every time it is called, and since onNavigate calls
closeMobileMenu on every astro:after-swap route transition, this steals focus
from the user during normal navigation. Remove the hamburgerRef.value?.focus()
call from the closeMobileMenu function to prevent unwanted focus changes during
route transitions. If hamburger focus is needed for keyboard navigation
scenarios, consider conditionally focusing only when the menu is closed via
explicit user interaction rather than during automatic transitions.
- Line 112: The hamburgerRef variable declaration uses HTMLButtonElement |
undefined as the type without initializing the ref, which does not align with
the project convention. Change the hamburgerRef declaration to use
HTMLButtonElement | null as the type and explicitly initialize it with null
value, matching the pattern used in other components like HubWorkflowCard.vue
and SearchPopover.vue for consistency across the codebase.

In `@site/src/composables/useFacets.ts`:
- Around line 52-55: The counting logic in the nested loop where field(t) is
iterated currently counts duplicate values multiple times if they appear in the
same template, causing incorrect rankings. Deduplicate the values returned by
field(t) before counting by converting them to a Set first, then iterate through
the deduplicated Set to increment the counts in the counts map, ensuring each
unique value is counted only once per template regardless of how many times it
appears in that template.

In `@site/src/lib/github.ts`:
- Around line 21-23: The `inflight` cache stores request entries but never
removes them, causing transient failures or null results to be cached
permanently, preventing future retries. After setting the request in the
`inflight` map using `inflight.set(key, request)`, attach a handler to the
request promise that removes the entry from the `inflight` map when the request
settles (using `.finally()` or `.then()` combined with `.catch()`), ensuring
that future calls can retry the request instead of returning the cached failed
result.

In `@site/src/lib/provider-logos.ts`:
- Around line 42-45: The substring matching logic in the loop iterating through
MODEL_TO_LOGO entries is too greedy because it uses includes() to check if the
lowercase input contains the lowercase key, which matches partial substrings
unintentionally (e.g., "Swan AI" matches "Wan"). Replace this loose substring
matching with a more precise approach that checks for exact word boundaries or
exact matches, such as by splitting the input name into words and checking if
any word exactly matches the key, rather than using a simple includes() check.

In `@site/src/lib/structured-data.ts`:
- Around line 23-27: The faqItems mapping in the mainEntity section includes
unsanitized user-provided strings (item.question and item.answer) that flow into
JSON-LD script sinks. These strings could contain `</script>` sequences that
break out of the script tag context and enable markup/script injection. Create
or use a safe serializer function that escapes these potentially dangerous
characters in item.question and item.answer before they are included in the
acceptedAnswer object's text field and the name field to prevent script-breakout
attacks.

In `@site/src/pages/`[locale]/workflows/[slug].astro:
- Around line 226-227: The detailRelated variable assignment at line 226-227
lacks error handling for the listRelatedWorkflows function call. Wrap the
listRelatedWorkflows invocation in a try-catch block to gracefully handle Hub
API failures, returning an empty array in the catch block instead of allowing
the error to propagate and crash the entire detail page. This ensures the detail
page remains functional even when fetching related workflows fails, since this
is a non-critical section.
- Around line 265-267: The JSON-LD payload in the script block with `detailFaq`
and `set:html` is vulnerable to script-breakout XSS because the stringified JSON
is injected directly without escaping. Fix this by escaping the JSON string
before passing it to `set:html`, specifically escaping the `<` character (or
`</` sequence) in the JSON.stringify(detailFaq) result to prevent content from
breaking out of the JSON-LD script block. Replace the direct JSON.stringify call
with a safe serialization that escapes angle brackets or use a JSON
serialization method that produces XSS-safe output.

In `@site/src/styles/global.css`:
- Around line 194-197: The multi-line CSS comment starting with "Compact variant
for small controls" is missing required whitespace before the closing comment
delimiter `*/`. To fix this, add a space before `*/` at the end of the comment
block where it currently reads `breakpoints.*/` so it becomes `breakpoints. */`.
This will satisfy the stylelint whitespace requirement for comment closing
delimiters.

In `@site/tests/unit/provider-logos.test.ts`:
- Around line 20-22: Add regression test cases to the getLogoPath test suite to
prevent false-positive logo matches from short substring collisions. After the
existing test that checks for unknown providers using 'Totally Unknown Co', add
additional test cases that verify inputs like 'Swan AI' and 'Conflux Labs'
return null to ensure these provider names don't accidentally match against
shorter keys like 'Wan' or 'Flux' through substring matching.

In `@site/tests/unit/use-facets.test.ts`:
- Around line 34-40: The test for the useFacets function with the test case
"ranks tags by template count and counts each template once per tag" does not
actually verify the "once per template" behavior because the makeTemplates()
fixture never includes duplicate tags within the same template. Modify the test
fixture passed to useFacets (either by updating makeTemplates() or creating a
new specialized fixture) to include at least one template that contains
duplicate occurrences of the same tag, then add assertions to verify that the
tag count still reflects only one count per template even when duplicates exist
within that template.

---

Outside diff comments:
In `@site/src/components/hub/HubBrowse.vue`:
- Around line 63-67: The filter logic in the modeBadges length check uses early
return statements that cause only the first matching condition to apply, so when
both 'app' and 'nodeGraph' badges are selected, only one mode is displayed.
Modify the conditional logic to treat multiple selected badges as a union by
combining the conditions with OR logic instead of separate if statements -
return true if (modeBadges includes 'app' AND t.isApp) OR (modeBadges includes
'nodeGraph' AND !t.isApp), ensuring that when both badges are selected, items
matching either criterion are included in the results.

In `@site/src/components/hub/SearchPopover.vue`:
- Around line 549-561: The `thumbnailPath()` function in SearchPopover.vue (used
in lines 549-550 and 560) and the identical `thumbUrl()` helper in
ThumbnailDisplay.astro (line 44) only guard against full HTTP/HTTPS URLs but
fail to prevent double-prefixing when paths already contain the
`/workflows/thumbnails/` segment. Expand the URL check in both functions to also
reject paths that already start with `/workflows/thumbnails/` by adding an
additional condition to the existing startsWith checks, so that upstream data
containing already-prefixed paths will be returned as-is instead of being
double-prefixed.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2d07f80f-17a8-47b8-a38d-816d4f639fdc

📥 Commits

Reviewing files that changed from the base of the PR and between d7d8394 and 8be464e.

⛔ Files ignored due to path filters (11)
  • site/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • site/public/brand/comfy-hub-logo.svg is excluded by !**/*.svg
  • site/public/icons/arrow-right.svg is excluded by !**/*.svg
  • site/public/icons/arrow-up-right.svg is excluded by !**/*.svg
  • site/public/icons/breadthumb.svg is excluded by !**/*.svg
  • site/public/icons/close.svg is excluded by !**/*.svg
  • site/public/icons/logomark.svg is excluded by !**/*.svg
  • site/public/icons/node-left.svg is excluded by !**/*.svg
  • site/public/icons/node-right.svg is excluded by !**/*.svg
  • site/public/icons/node-union.svg is excluded by !**/*.svg
  • site/public/icons/social/github.svg is excluded by !**/*.svg
📒 Files selected for processing (55)
  • site/package.json
  • site/scripts/lib/search/build-index.ts
  • site/src/components/ThumbnailDisplay.astro
  • site/src/components/hub/BrowseToolbar.vue
  • site/src/components/hub/FeaturedCarousel.vue
  • site/src/components/hub/HubBrowse.vue
  • site/src/components/hub/HubHero.astro
  • site/src/components/hub/HubNavbar.astro
  • site/src/components/hub/HubWorkflowCard.vue
  • site/src/components/hub/MobileFilterDrawer.vue
  • site/src/components/hub/SearchPopover.vue
  • site/src/components/hub/WorkflowGrid.vue
  • site/src/components/hub/nav/GitHubStarBadge.vue
  • site/src/components/hub/nav/MobileMenu.vue
  • site/src/components/hub/nav/NavDesktopLink.vue
  • site/src/components/hub/nav/NodeBadge.vue
  • site/src/components/hub/nav/SiteNav.vue
  • site/src/components/template-detail/Breadcrumbs.astro
  • site/src/components/template-detail/ExpandableText.vue
  • site/src/components/template-detail/FaqAccordion.vue
  • site/src/components/template-detail/TemplateDetailPage.astro
  • site/src/components/ui/button/index.ts
  • site/src/composables/scrollLock.ts
  • site/src/composables/useFacets.ts
  • site/src/composables/useHubStore.ts
  • site/src/config/nav-routes.ts
  • site/src/i18n/ui.ts
  • site/src/lib/featured.ts
  • site/src/lib/github.ts
  • site/src/lib/hub-api.ts
  • site/src/lib/provider-logos.ts
  • site/src/lib/routes.ts
  • site/src/lib/search-config.ts
  • site/src/lib/search.ts
  • site/src/lib/structured-data.ts
  • site/src/lib/toolbar.ts
  • site/src/pages/[locale]/workflows/[slug].astro
  • site/src/pages/[locale]/workflows/category/[type].astro
  • site/src/pages/[locale]/workflows/index.astro
  • site/src/pages/[locale]/workflows/model/[name].astro
  • site/src/pages/[locale]/workflows/tag/[tag].astro
  • site/src/pages/workflows/[slug].astro
  • site/src/pages/workflows/[username].astro
  • site/src/pages/workflows/category/[type].astro
  • site/src/pages/workflows/index.astro
  • site/src/pages/workflows/model/[name].astro
  • site/src/pages/workflows/tag/[tag].astro
  • site/src/styles/design-tokens.css
  • site/src/styles/global.css
  • site/tests/unit/featured.test.ts
  • site/tests/unit/provider-logos.test.ts
  • site/tests/unit/routes.test.ts
  • site/tests/unit/search-config.test.ts
  • site/tests/unit/use-facets.test.ts
  • site/vitest.config.ts
💤 Files with no reviewable changes (1)
  • site/src/components/hub/MobileFilterDrawer.vue

Comment thread site/src/components/hub/nav/MobileMenu.vue Outdated
Comment thread site/src/components/hub/nav/MobileMenu.vue Outdated
Comment thread site/src/components/hub/nav/NavDesktopLink.vue Outdated
Comment thread site/src/components/hub/nav/SiteNav.vue Outdated
Comment thread site/src/components/hub/nav/SiteNav.vue Outdated
Comment thread site/src/pages/[locale]/workflows/[slug].astro
Comment thread site/src/pages/[locale]/workflows/[slug].astro
Comment thread site/src/styles/global.css Outdated
Comment thread site/tests/unit/provider-logos.test.ts
Comment thread site/tests/unit/use-facets.test.ts

@dante01yoon dante01yoon left a comment

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.

Strict review — ComfyHub redesign (read-only, not approving per request)

Reviewed at head 37f0634. I focused on the areas the description flags as risky (search, navbar, filter migration, structured-data escaping) and on the design-system alignment raised in the Hub-consolidation RFC thread. Verified against the actual current files (note: the earlier CodeRabbit pass references MobileMenu.vue/SiteNav.vue/NavDesktopLink.vue, which no longer exist — they were refactored into HeaderMain*.vue, so several of those comments are stale).

Primary ask (strategic, from the RFC / Slack thread)

The team agreed the Hub will be migrated into the main comfy.org website with shared design components. To get a soft landing (minimal churn at migration time), this PR should reuse the website's design tokens rather than defining a parallel Comfy palette/typography here. Left as an inline suggestion on design-tokens.css. This is the highest-leverage change to make before merge — happy to pair on which token set is canonical.

Blocking-class issues (inline)

  • issue Search abbreviation expansion replaces the literal token, regressing exact-abbreviation title matches (Wan2.1 Alpha T2V, LTX-2 I2V become unfindable by t2v/i2v). — search-config.ts:64
  • issue GitHub stars override throw violates the file's own "returns null on any error" contract and can crash the whole site build on a bad env value, because the only caller is un-guarded. — github.ts:70
  • issue Model-page breadcrumb regressed (Models → /workflows/model/ became Workflows → /workflows/), contradicting the "consistent across model pages" claim. — workflows/model/[name].astro:69 (+ localized variant)

Questions / suggestions (inline)

  • question Active-nav highlighting is inert: every nav-routes href is an absolute http(s) URL, so isHrefActive always returns false. — useCurrentPath.ts:26
  • suggestion a11y: mobile icon-only inactive tabs have no accessible name. — BrowseToolbar.vue:101

What checked out well (verified, no action)

  • Search does not drift index/query config (shared tokenize/SEARCH_FIELDS/STORE_FIELDS), and the index-fetch cache does recover from a failed first fetch (indexPromise is cleared on error) — both core claims hold.
  • serializeJsonLdForScript is a correct escaper (</>/&/U+2028/U+2029 → \uXXXX) and every changed inline JSON-LD site (FAQ, breadcrumb, softwareApp) routes through it; listRelatedWorkflows is wrapped in try/catch → [].
  • useFacets dedups values per-template (new Set(field(t))) — fixes latent count inflation vs. the old drawer.
  • provider-logos.ts uses word-boundary regex matching — fixes the old includes() substring bug (e.g. Swan no longer matches wan).
  • ExpandableText keeps full text in SSR DOM while collapsed (crawler-visible), disconnects its ResizeObserver on unmount, and honors prefers-reduced-motion; FeaturedCarousel destroys Embla/autoplay on unmount, handles RTL + 0 slides; scroll-lock ref-counts and restores correctly on unmount.

Non-blocking notes (not inline)

  • CI: Build Site and build-and-test (168 tests) pass. preview fails only on a missing VERCEL_TOKEN (fork PR — secrets unavailable; the build itself completed). SEO Audit fails on a missing dist/ in its own job. Lint & Format is red but dominated by pre-existing parse errors in files this PR doesn't touch (BaseLayout.astro arguments/is:inline, and the [username].astro line-15 HTML comment that is unchanged from main); 0 new ESLint errors are introduced. Worth confirming none of these gate the merge.
  • Pre-existing / out of scope: the dual-mode badge union bug at HubBrowse.vue:63-67 (selecting both app+nodeGraph shows only apps) that CodeRabbit flagged is byte-for-byte unchanged from main — not introduced here, but a good follow-up.
  • Escaping scope gap: the co-located TechArticle/SoftwareApplication graph (same data.title/data.description) still serializes raw via the unchanged SEOHead.astro JSON.stringify, so the "title/description-derived JSON-LD is escaped" invariant isn't fully true post-PR. Pre-existing path, but consider migrating SEOHead to the new escaper too.
  • nit Dead code: mobileDrawerOpen/toggleMobileDrawer/closeMobileDrawer in useHubStore.ts have no consumers after MobileFilterDrawer.vue deletion. formatStarCount renders 1000K in the 999.5k–1M window.

Not approving — strict review only, as requested.

Comment thread site/src/lib/search-config.ts Outdated
Comment thread site/src/lib/github.ts Outdated
Comment thread site/src/pages/workflows/model/[name].astro Outdated
Comment thread site/src/composables/useCurrentPath.ts Outdated
Comment thread site/src/styles/design-tokens.css
Comment thread site/src/components/hub/BrowseToolbar.vue

@dante01yoon dante01yoon left a comment

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.

Re-reviewed at head 13fba4f (read-only). Strong, well-documented redesign. I re-verified the substantive CodeRabbit findings against the current head and they are all resolved, and I confirmed the red CI checks are fork-PR infrastructure failures rather than code problems. Findings below are non-blocking nits/suggestions, so approving.

CI red checks are infra, not code — worth noting so they aren't mistaken for blockers:

  • Lint & Format: ESLint and Prettier both passed; the job only failed at the Comment on PR step with 403 Resource not accessible by integration (read-only token on a fork PR).
  • SEO Audit: Sitemap Validation and SEO Audit both passed; same 403 on the comment step.
  • preview: the Vercel deploy step needs VERCEL_TOKEN, which isn't exposed to fork PRs.
  • The real quality gates — Build Site, build-and-test, CodeRabbit, Socket Security, CLA — are green.

CodeRabbit findings re-verified as resolved on this head:

  • JSON-LD XSS: every workflow route page now serializes via serializeJsonLdForScript (escapes </>/&/U+2028-9); the only remaining raw JSON.stringify is in unchanged SEOHead.astro, out of scope.
  • github.ts inflight cache now clears via .finally(() => inflight.delete(key)) — no permanent caching of failures.
  • provider-logos.ts uses word-boundary regex, so Swan AI no longer matches Wan.
  • useFacets.ts dedups per-template values with new Set(field(t)) before counting.
  • listRelatedWorkflows already wraps its fetch in try/catch returning [], so the detail page can't crash on a Hub API failure.
  • The nav was rewritten (MobileMenu.vue/NavDesktopLink.vue/SiteNav.vue -> HeaderMain*/NavColumn), so CodeRabbit's nav comments are stale/moot.
  • The dual-mode (app+nodeGraph) first-match-wins filter CodeRabbit flagged is unchanged pre-existing code and currently unreachable (the discovery panel that offers a second mode chip hides once any badge is active), so it isn't introduced here.

Verified: all t('...') keys referenced by new code resolve in ui.ts (a missing key would render the literal key); FAQ JSON-LD and the visible FaqAccordion share the same faqItems source; the breadcrumb JSON-LD mirrors the visible Breadcrumbs.astro trail in label and order.

Not run: static review + CI log analysis only — I did not run the app or re-run the build/tests locally, and did not verify visual/Figma fidelity.

Comment thread site/src/components/hub/FeaturedCarousel.vue Outdated
Comment thread site/src/components/hub/FeaturedCarousel.vue Outdated
Comment thread site/src/components/hub/TagScroller.vue Outdated

@dante01yoon dante01yoon left a comment

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.

Re-review at 13fba4f — all 6 prior findings verified resolved ✅

Thanks for the fast turnaround, @MaanilVerma. I re-reviewed the follow-up commits since my last pass (37f0634..13fba4f) and adversarially verified each fix (rebuilt the MiniSearch index with the current shared config to test queries empirically, ran the unit suites, and grepped for dangling references/tokens). Every prior finding is genuinely resolved:

Prior finding Fix Verified
issue search abbrev regression searchWorkflows now searches the literal query first, then appends the expansion (deduped); expandQuery returns null when nothing expands t2v→ returns Wan2.1 Alpha T2V literal and the expansion, literal ranked first; new test asserts ordering; 58/58 pass
issue GitHub override throw console.warn + return undefined (falls through to live fetch) ✅ every invalid input returns instead of throwing; new github.test.ts 4/4 pass; contract upheld
issue Models breadcrumb section override restores Models → /workflows/model/ in both default + localized pages ✅ localized for all 11 locales, no raw-key leak (see one non-blocking note below)
question inert active-nav useCurrentPath.ts deleted, all consumers cleaned ✅ zero dangling refs; NavigationMenuTrigger.active is optional so the contract holds
suggestion design tokens --text-2xs/3xs, --color-site-dropdown, ppformula-text-center tokens added; text-[10px]/[12px] removed ✅ no dangling token refs; 0.625rem=10px exact (no visual regression); good step toward the migration soft-landing
suggestion tab a11y :aria-label="labels[tab.labelKey]" on TabsTrigger ✅ localized name now always present

Not approving (review-only, as before). Two small non-blocking follow-ups inline — neither blocks merge.

Verification notes
  • Build/lint sanity sweep of the follow-up commits found no dangling imports, unused-var lint failures, or undefined token classes.
  • The named search templates (Wan2.1 Alpha T2V, LTX-2 I2V) are only findable if present in the hub-API search index (it indexes hub workflows, not templates/index.json) — the fix is correct for any indexed doc carrying a literal abbreviation regardless.

{ name: modelName },
{
name: t('labels.models', locale),
item: absoluteUrl(localizeUrl('/workflows/model/', locale)),

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.

suggestion (non-blocking, follow-up to my earlier breadcrumb finding): restoring the Models crumb is exactly right and resolves the regression. One thing I missed in the original review — this crumb's item URL /workflows/model/ doesn't resolve to a page: there's only [name].astro here (no index.astro) and no redirect, so the bare path is a 404. Note this is not introduced by this PRmain pointed the same crumb at /workflows/model/ too, so it's a pre-existing latent issue, and there's no user-facing broken link (only the JSON-LD BreadcrumbList, no visible trail on model pages).

Still, Google expects breadcrumb item URLs to resolve, so while we're touching this line it'd be cheap to either drop the item (schema.org allows a name-only crumb — same as the leaf crumbs on the category/tag pages) or add a /workflows/model/ index. Same line in the localized variant [locale]/workflows/model/[name].astro:75.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@dante01yoon This is actually addressed in a follow-up PR that comes right after this one is merged (and I don't think we autodeploy). In that follow-up PR, we've introduced /models as its own page along with several SEO-related updates, so this is already taken care of there.

Comment thread site/src/lib/search-config.ts
@dante01yoon

Copy link
Copy Markdown
Contributor

🚀 Preview deployed for this PR (head 2fcc956):

https://workflow-templates-h3x07jaod-comfyui.vercel.app

Deployed manually by a maintainer as a one-off preview. Note: built without the preview API env vars, so data-driven sections may render empty — the comfyhub redesign UI itself is fully viewable.

Going forward, fork PRs will get automatic previews once a maintainer adds the preview label (see #965).

@dante01yoon

Copy link
Copy Markdown
Contributor

Verify actual CI passed - failures happen due to token issue

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.

2 participants