Skip to content

feat: custom feed icons & different edit interaction - #2284

Closed
Nirzhuk wants to merge 15 commits into
devfrom
feat/custom-feed-icons
Closed

feat: custom feed icons & different edit interaction#2284
Nirzhuk wants to merge 15 commits into
devfrom
feat/custom-feed-icons

Conversation

@Nirzhuk

@Nirzhuk Nirzhuk commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

resolves #1898
resolves #1897

Description

Added Custom Icons following Lucide react library to Custom Feeds, added custom dialog to decide icons.
Modified different edit interaction of custom feeds

Demo video

Screen.Recording.2026-08-03.at.11.06.18.mov

Nirzhuk added 4 commits July 31, 2026 10:43
- Updated pubky-app-specs dependency in package.json and package-lock.json to version 0.7.0.
- Added icon picker feature in multiple language files (ar.json, de.json, en.json, es.json, fr.json, it.json, ja.json, pt-BR.json, zh.json) to allow users to select custom icons for feeds.
- Implemented tests for the new DynamicLucideIcon component to ensure proper rendering and fallback behavior.
- Enhanced CustomFeedDialog to support icon selection and display appropriate icons based on user input.
- Enhanced the edit button styles in the FeedNavigation component by adding new classes for rounded corners, transparent background, and transition effects.
- Updated the SVG icon size for consistency.
- Adjusted snapshots to reflect the new button styles and icon size changes.
- Improved tests for the IconPickerDialog component by adding checks for loading states and ensuring proper rendering after animations.
- Updated the test suite to include mobile-specific snapshots for the icon picker grid.
- Refactored the component to support virtualized rendering for better performance on mobile and desktop views.
- Adjusted the handling of scroll and query changes to improve user experience.
- Removed unnecessary mock for useCustomFeed hook and adjusted tests to directly pass the feed prop to CustomFeedDialog.
- Updated tests to ensure the dialog behaves correctly in both edit and create modes, including enabling/disabling inputs based on feed presence.
- Enhanced snapshot tests to reflect the latest changes in the dialog's rendering logic.
- Cleaned up redundant test cases to streamline the test suite.
@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds selectable custom Lucide icons to custom feeds.

  • Upgrades pubky-app-specs and extends feed normalization, persistence, and bootstrap compatibility with icon metadata.
  • Adds a searchable, virtualized icon picker and dynamic icon renderer.
  • Refactors custom-feed create/edit handling into a React Hook Form hook and displays selected icons in feed navigation.

Confidence Score: 4/5

The unknown-icon overwrite should be fixed before merging because an unrelated feed edit can silently destroy icon metadata created by another client.

The edit form replaces unrecognized stored icons with the default and submits all fields, defeating the lower-layer preservation behavior and persisting the replacement locally and remotely.

Files Needing Attention: src/hooks/useCustomFeedForm/useCustomFeedForm.types.ts, src/hooks/useCustomFeedForm/useCustomFeedForm.ts

Important Files Changed

Filename Overview
src/hooks/useCustomFeedForm/useCustomFeedForm.types.ts Defines form validation and stored-feed mapping, but converting unknown icons to the default causes destructive round trips.
src/hooks/useCustomFeedForm/useCustomFeedForm.ts Centralizes create, update, delete, navigation, and toast behavior; full-form update submission contributes to overwriting unknown icons.
src/core/application/feed/feed.ts Persists icon metadata and provides legacy defaults while preserving the existing local/homeserver migration flow.
src/components/organisms/IconPickerDialog/IconPickerDialog.tsx Adds a searchable and virtualized responsive Lucide icon picker.
src/components/organisms/FeedNavigation/FeedNavigation.tsx Displays custom icons and exposes separate accessible edit actions for every saved feed.
src/components/organisms/CustomFeedDialog/CustomFeedDialog.tsx Integrates the icon picker and refactored form controls into create and edit dialogs.
src/core/pipes/feed/feed.validators.ts Sanitizes missing or oversized icons while deliberately preserving valid names unknown to this client.

Sequence Diagram

sequenceDiagram
  participant User
  participant Dialog as CustomFeedDialog
  participant Form as useCustomFeedForm
  participant Controller as FeedController
  participant App as FeedApplication
  participant Local as IndexedDB
  participant HS as Homeserver
  User->>Dialog: Select icon and save
  Dialog->>Form: submit()
  Form->>Controller: commitCreate/commitUpdate
  Controller->>App: persist normalized feed
  App->>Local: write feed with icon
  App->>HS: PUT feed JSON with icon
Loading

Reviews (1): Last reviewed commit: "test: refactor CustomFeedDialog tests fo..." | Re-trigger Greptile

Comment thread src/hooks/useCustomFeedForm/useCustomFeedForm.types.ts Outdated
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

🚀 Preview Deployment

URL: https://pubky-app-pr-2284-fzxmjul7ya-oa.a.run.app

pubky-app-pr-2284f0a577e

@Nirzhuk Nirzhuk changed the title Feat/custom feed icons feat: custom feed icons & different edit interaction Aug 3, 2026
@Nirzhuk Nirzhuk self-assigned this Aug 3, 2026
@Nirzhuk
Nirzhuk requested a review from infin1t3 August 3, 2026 09:43
@Nirzhuk Nirzhuk added the 📈 enhancement New feature or request label Aug 3, 2026
@Nirzhuk
Nirzhuk requested review from secondl1ght and removed request for infin1t3 August 3, 2026 14:43
Nirzhuk and others added 4 commits August 5, 2026 18:52
…eedNavigation component

- Introduced a new translation key "moreFeeds" in Arabic, German, English, Spanish, French, Italian, Japanese, Portuguese, and Chinese language files to improve user experience in navigating feeds.
- Updated the FeedNavigation component to support displaying additional feeds in a popover for better accessibility on larger screens.
- Enhanced tests and snapshots to reflect the new functionality and ensure consistent behavior across different screen sizes.
Co-authored-by: Cursor <cursoragent@cursor.com>
…oved reactivity

- Updated the CustomFeedDialog component to utilize useWatch from react-hook-form, enhancing the reactivity of form field values.
- Simplified the handleReachChange function to directly set form values, ensuring consistent updates for the reach selection.
- Adjusted the domainTags initialization to default to an empty array when not set, improving robustness.
…ctor visibility

- Implemented a new test to verify that the profile tags are capped at five and that the emoji selector is hidden when the limit is reached.
- The test simulates user input for profile tags and checks the attributes of the tag input field to ensure correct behavior.

@secondl1ght secondl1ght left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a nice improvement, a few things:

  1. The new dropdown picker to view hidden feeds doesn't match the design (using the video @olivert2025 shared in Slack as an example).
  2. On initial load the icons in the picker are fairly laggy, consider preloading these for a better UX. It's not critical because once they are cached they load faster, but is quite noticeable on first render.
  3. The close (X) button in the search input looks out of place with our app theme, it is blue but I've never seen this style anywhere else. It's also missing the pointer cursor on hover indicating it's clickable. We should use the standard white or grey colors to match. (Or even the primary brand green color would look better.)
  4. There is a visible flicker on page refresh where the default icons are shown temporarily before the real icons are used, we should fix that so the correct icons are always displayed.

And then there are a few comments from Claude that will follow.

@secondl1ght

Copy link
Copy Markdown
Member

From Claude:

1. Editing a feed silently overwrites a foreign client's icon.
FeedValidators.sanitizeIcon deliberately passes unknown-but-valid icon names through, and its docstring promises: "Names that are merely unknown to us pass through untouched so a round-trip through this app does not overwrite them." But the edit dialog breaks that promise: customFeedFormValuesFromFeed (useCustomFeedForm.types.ts:82) coerces any non-Lucide name to DEFAULT_CUSTOM_FEED_ICON, and submit always sends changes.icon, so renaming a feed authored by another client permanently replaces its custom icon with 'activity'. Since DynamicLucideIcon already renders the Activity fallback for unresolvable names, the form could keep the raw stored value instead of coercing it — the trigger button would still render something, the zod z.string().min(1) check still passes, and an untouched icon field would round-trip intact. The picker would simply show no selection, which is arguably more honest.

2. useCustomFeedMutation is now dead code.
Its only consumer (CustomFeedDialog) migrated to useCustomFeedForm, yet the hook and its test remain — the test was even edited in this PR (adding icon: 'activity') just to keep it compiling. Suggest deleting the folder, or stating why it's being kept.

3. Virtualization constants are silently coupled to Tailwind classes.
In IconPickerDialog, ICON_ROW_HEIGHT_PX = 52 is size-9 (36px) + gap-y-4 (16px), the - 16 in virtualGridHeight trims the trailing gap, and MOBILE_COLUMN_COUNT/DESKTOP_COLUMN_COUNT must match grid-cols-6 sm:grid-cols-11 and the useIsMobile({ breakpoint: 'sm' }) query. None of this is stated in the file — a future styling tweak will desync scroll math from layout with no type error. A short comment tying the constants to the class names would cover it. (Hand-rolling the virtualization itself is fine — there's no virtualization library in the dependency tree, and the scale doesn't justify adding one.)

@Nirzhuk

Nirzhuk commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@matthew

  • On initial load the icons in the picker are fairly laggy, consider preloading these for a better UX. It's not critical because once they are cached they load faster, but is quite noticeable on first render.
  • There is a visible flicker on page refresh where the default icons are shown temporarily before the real icons are used, we should fix that so the correct icons are always displayed.

Both are coming from the same problem we're using dynamic which imports dynamic the specific icon without adding them directly to our bundle and makes an independent module in build time.
more info: https://lucide.dev/guide/react/advanced/dynamic-icon-component

The close (X) button in the search input looks out of place with our app theme, it is blue but I've never seen this style anywhere else. It's also missing the pointer cursor on hover indicating it's clickable. We should use the standard white or grey colors to match. (Or even the primary brand green color would look better.)

The new dropdown picker to view hidden feeds doesn't match the design (using the video @olivert2025 shared in Slack as an example).

Updated and Oliver give the okey

@Nirzhuk

Nirzhuk commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author
  1. Editing a feed silently overwrites a foreign client's icon.
    FeedValidators.sanitizeIcon deliberately passes unknown-but-valid icon names through, and its docstring promises: "Names that are merely unknown to us pass through untouched so a round-trip through this app does not overwrite them." But the edit dialog breaks that promise: customFeedFormValuesFromFeed (useCustomFeedForm.types.ts:82) coerces any non-Lucide name to DEFAULT_CUSTOM_FEED_ICON, and submit always sends changes.icon, so renaming a feed authored by another client permanently replaces its custom icon with 'activity'. Since DynamicLucideIcon already renders the Activity fallback for unresolvable names, the form could keep the raw stored value instead of coercing it — the trigger button would still render something, the zod z.string().min(1) check still passes, and an untouched icon field would round-trip intact. The picker would simply show no selection, which is arguably more honest.

This is okey, if is not from Lucide lets rewrite it, it's a more strong guard but I believe it's a good thing. This could change if we stop supporting lucide but I believe it's change in the paradigm of our code in that moment

@infin1t3 infin1t3 assigned infin1t3 and unassigned Nirzhuk Aug 8, 2026
# Conflicts:
#	messages/ar.json
#	messages/de.json
#	messages/es.json
#	messages/fr.json
#	messages/it.json
#	messages/ja.json
#	messages/pt-BR.json
#	messages/zh.json
#	src/test/vrt/feed/__screenshots__/Home.vrt.test.tsx/home-feed-desktop-chromium-darwin.png
#	src/test/vrt/feed/__screenshots__/Home.vrt.test.tsx/home-feed-desktop-firefox-darwin.png
#	src/test/vrt/feed/__screenshots__/Home.vrt.test.tsx/home-feed-desktop-webkit-darwin.png
Specs 0.7.0 createFeed rejects icons with characters outside [a-z0-9-];
sanitizeIcon passing such a value through made the per-feed bootstrap
guard drop the whole feed from the user's list. Falling back to the
default icon keeps the feed.
CustomFeedDialog migrated to useCustomFeedForm; nothing imports the
mutation hook anymore.
…eedback

- Load dynamic Lucide icons through a module-level IconNode cache:
  cached icons render synchronously on first paint, a loading icon shows
  an empty size-preserving svg instead of the wrong default glyph, and a
  failed chunk falls back to the default and retries on remount.
- Preload feed icons when feed data lands and the picker's first window
  on dialog open, so first paint hits the cache.
- Hide deprecated Lucide alias names from the picker grid (same glyph
  twice); aliases stay valid for stored feeds.
- Replace the native webkit search-cancel button with a themed clear
  button (pointer cursor, muted foreground) and refocus on clear.
- Keep unknown-but-valid foreign icon names intact in the edit form so
  an unrelated edit does not overwrite another client's icon.
- Move focus into the edit dialog when its auto-focus is suppressed.
- Document the virtualization constants' coupling to the grid classes.
- Regenerate affected snapshots and darwin VRT baselines (chromium and
  firefox; the local webkit runner crashes before running any test).
@infin1t3

Copy link
Copy Markdown
Collaborator

Closing this PR in favor of #2353 and other PRs that are yet to come.

@infin1t3 infin1t3 closed this Aug 16, 2026
infin1t3 added a commit that referenced this pull request Aug 20, 2026
- Feed navigation is now a tab bar on mobile too (v26): the selected tab
  shows icon + label, other tabs collapse to icon-only, Create is a plus
  icon; the strip sticks under the compact mobile header (Hot pattern)
  and the mobile right drawer falls back to Who to follow / Hot tags.
- The first tab mirrors the selected reach (My network, All, Tagged as,
  ...) with the matching icon, via a shared REACH_FILTER_META map.
- Custom feeds get a selectable Lucide icon (icon picker dialog, dynamic
  icon atom, spec 0.7.0 with the icon field synced to the homeserver),
  ported from PR #2284 and de-i18n-ized; feeds without an icon fall back
  to Activity.
- CustomFeedDialog moves to a useCustomFeedForm hook (react-hook-form +
  zod); edit/delete only navigate away when you are viewing the edited
  feed, and config edits use replace since the old hash id is dead.
- Pencil affordance: hover-revealed on desktop, always visible on the
  selected custom feed tab on mobile, with a larger hit area.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📈 enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[UI]: Custom feeds hover interaction (v23) [UI]: Custom feed edit icons (v23)

3 participants