Skip to content

Release 1.8.0 - #2398

Open
catch-21 wants to merge 39 commits into
masterfrom
release-1.8.0
Open

Release 1.8.0#2398
catch-21 wants to merge 39 commits into
masterfrom
release-1.8.0

Conversation

@catch-21

Copy link
Copy Markdown
Collaborator

No description provided.

secondl1ght and others added 22 commits August 12, 2026 21:02
* fix: stream pagination livelock on long filtered runs

* refactor: keep sentinel mounted until exhausted

* fix: broken tests after merge with dev

* refactor: small cleanups
* docs(adr): align application orchestration rules with runtime

* docs(adr): clarify application orchestration constraints
…2339)

* feat: add JSON-LD for AI agents and crawlers to learn of Nexus-Scout

* fix: update comments inline with review feedback
'My network' (wot) and 'Tagged as' (wot_domain) streams never contain
the viewer's own posts: Nexus excludes them server-side and the local
create path never writes into those streams, so the optimistic entry
flashed at the top and vanished on the next stream reset.

Closes: #2308
* fix(ui): expand truncated post text in place

Clicking Show more now reveals the rest of the post on the feed
instead of navigating to the post page.

Closes: #764

Co-authored-by: Orlando Goncalves <orlando.goncalves@gmail.com>

* test(ui): update Show more snapshots after expand-in-place

Closes: #764

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat(users): add safe moderation bot default follow

Apply the configured moderation follow once during shared bootstrap while preserving explicit unfollows with a durable homeserver marker. Treat expected homeserver misses as ordinary existence results and keep production moderation disabled when no identity is configured.

Fixes: #2255

* fix(config): update staging moderation bot id

* test(config): align moderation id with runtime default

* fix(users): harden moderation follow migration

Preserve moderation opt-outs before unfollowing and cancel detached follow work across authentication lifecycles. Use identity-safe specs builders, SDK existence probes, and visible deployed configuration warnings.

Refs: #2255

---------

Co-authored-by: V <jovanovicv90@gmail.com>
#2285 stretched the trigger across the header row, so hovering empty space next to the timestamp opened the profile card again.
* fix(ui): match profile empty states to Figma

Notifications, Posts, and Replies used generic or outdated placeholders on empty profile tabs.

Closes: #1789

* fix(ui): use correct profile empty-state artwork
* feat(auth): block prod-key sign-in on staging homeserver deploys

Reject mnemonic, encrypted-file, Pubky Ring, and session-restore auth
when the key's PKARR homeserver does not match a staging deploy:

- Declare deploy identity via a required PUBKY_RUNTIME_ENV
  ("production" | "staging") runtime value and drive
  isStagingHomeserverDeploy() from it, so config drift can never
  silently disable the guard. PR previews, the CI smoke test, and local
  dev declare theirs in-repo; deployed configs fail loudly at boot when
  it is missing.
- Fail closed on staging: a mismatched, absent, or unresolvable PKARR
  record blocks sign-in and never republishes the record. Outside
  staging, the sign-in self-heal republishes only a provably absent
  record - a failed lookup now propagates instead of force-overwriting
  a record that may point at another homeserver.
- Enforce the same check when restoring a persisted session: transient
  lookup failures retry like any other restore failure, and definitive
  mismatches clean up local state, surface a wrong-environment toast,
  and sign the rejected session out of its own homeserver (also on the
  Ring path) instead of leaving it dangling.
- Show the rejection toast across both restore dialogs and the Ring QR
  flow (expiring the stale auth URL), localized in all nine locales.

Closes #2126

* fix(auth): single environment assert per flow, quieter guard rejections

Address review findings on the staging sign-in guard:

- Run the environment guard once per flow: keypair sign-ins rely on the
  assert inside HomeserverService.signIn (before the session exists), while
  initializeAuthenticatedSession keeps it as the sole checkpoint for
  externally approved (Ring) sessions and now signs the session out on any
  guard failure, not only wrong-environment rejections.
- Reject an absent PKARR record on staging as WRONG_ENVIRONMENT_HOMESERVER:
  absence is deterministic and cannot prove the key belongs to this deploy,
  so it no longer surfaces as a retryable server error (10x3s retry storm)
  or a misleading file-error message in the restore dialogs.
- Construct the wrong-environment rejection directly instead of via the Err
  factory: an expected user mistake no longer emits an error log plus a
  Sentry error event on every attempt (Logger.info with context instead).
- Keep the restored session across restore retries so a transient PKARR
  lookup failure retries only the environment check, not the whole
  restoreSession round-trip; collapse the nested wrong-env classification
  into the single retry catch.
- Log persisted-session restore failures swallowed by logout() instead of
  silently returning.
- Merge useAuthUrl's duplicated catch branches into one toast with a
  conditional description.
- Remove the signIn store's dead error field and setError action (last
  caller was replaced by reset()).

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
)

* feat(ui): simplify and optimize collection cards mobile rendering

* fix(ui): block collection follow while state loads

* fix(ui): keep collection tag action last

* fix(ui): address collection card review feedback

* test(vrt): regenerate baselines 2026-08-19T19:19:14Z

---------

Co-authored-by: V <jovanovicv90@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat(ui): group repeated deleted and edited post notifications

Consecutive delete/edit notifications from one actor collapse into a
single row per content kind: deleted groups render as a flat unlinked
total, while edited groups list every affected post behind a Show/Hide
disclosure on desktop and render permanently expanded on mobile.
Repeats about the same post deduplicate to their newest occurrence.

Edited posts refetch on notification arrival so rows never show
pre-edit content, poll refreshes merge new notifications in place
instead of remounting the list, load failures keep the accumulated
rows with an inline retry, and auto-loading budgets pages that
grouping leaves without new rows, handing control back through a
manual Load more button. Single deleted rows drop their post link —
the deleted post has no destination. Shared row chrome (avatar,
heading, timestamp and icon) is extracted for both row kinds, and
notification previews derive through the app-wide preview policy.

* test(e2e): cover grouped notifications in the cypress spec

The two '…that you reposted' scenarios now create two posts each, so
they form a grouped row on their own and assert the grouped copy. The
edited scenario drives the Show/Hide disclosure on desktop and asserts
the permanently expanded title list on the mobile config.

* fix(notifications): harden refresh, disclosure state and mobile snapshot

Refresh now stands in for a failed initial load by establishing the
pagination cursor, so retrying no longer leaves the list stuck on its
first page, and a refreshed page that filters down to nothing (every
actor muted) no longer looks like a gap and wipes the loaded rows.

Group disclosure state moves to the list, keyed by the run's members
rather than by row identity: a run gains members as pages load and
refreshes arrive, which changes the row key, and the group the user is
reading no longer collapses when that happens.

Also mirror the desktop render in the mobile snapshot and apply the
documented viewport helpers, take the module-level toast in the post
content hook, and use relative imports between same-tier siblings.

* test: drop the obsolete notifications list snapshot

The mobile snapshot was renamed when it started mirroring the desktop
render; its old entry stayed behind and CI fails on obsolete snapshots.
* fix(ui): add bottom padding so profile links clear mobile footer

* Remove desktop spacing.
Update tests.

---------

Co-authored-by: Taehwa Kim <hadeath03@gmail.com>
Moving the cursor over a hover popover trigger (post header name,
mention link, feed row avatar) blurred whatever element was focused,
killing tag input focus mid-typing (#2291). Two causes:

- handleMouseLeave unconditionally blurred document.activeElement
  whenever the cursor left a hover trigger, even if the popover never
  opened. Removed.
- Radix non-modal popovers focus the trigger on close, stealing focus
  from the input when a hover popover with a focusable trigger closes.
  PopoverContent now prevents open/close auto-focus in hover mode only;
  click-mode popovers keep Radix's default a11y focus behavior.

Co-authored-by: Taehwa Kim <hadeath03@gmail.com>
* test: 3 new WoT feed E2E

* tests: fix other e2e

* tests: refactor feed tests (WoT)

* tests: refactor cypress slow down in feed e2e

* tests: e2e workflow pubky-homeserver

* tests: revert check for moderation follow, now disabled in testnet
* feat(ui): show status emojis inline with user names

* fix(ui): preserve text-only profile statuses

* fix(ui): restore profile header typography spacing
…load (#2295)

* fix(core): refresh follow-dependent feeds

Invalidate dynamic follow-dependent caches after follow mutations and force-refresh mounted feeds from Nexus using revision signals.

Refs: #2294

* fix(core): harden follow-driven stream refreshes

Serialize same-stream cache work, add the refreshStreamSlice contract, and prevent stale pagination requests from overwriting follow-driven refreshes. Preserve optimistic posts and existing pagination when the automatic refresh fails.

Refs: #2294

* fix(core): keep feeds visible during follow refresh

Preserve rendered posts while follow-driven replacement loads and tighten serializer ownership and stream-switch regression coverage.

Refs: #2294

* revert(core): drop mounted-feed force refresh after follow mutations

Team decision (PR #2295 review + Slack): a follow/unfollow must not
live-refresh the feed the user is reading. Remove the streamInvalidation
store, useFollowDependentStreamRefresh, refreshStreamSlice and the
pagination concurrency machinery that existed only to make the in-place
replacement safe. Keep the additive follow-dependency classifier, stream
id fixtures and grammar tests as groundwork for deferred invalidation.

Refs: #2294

* feat(streams): defer follow and profile-tag feed invalidation to next load

Mutations now only mark dependency scopes dirty in an in-memory registry
(follow_graph on any follow mutation, friends on friendship transitions,
profile_tag on profile-tag writes). Each dependent stream drops its main
and unread cache rows lazily in prepareStreamForInitialLoad, so a mounted
feed keeps its membership and scroll position while navigation back or
pull-to-refresh rebuilds it from Nexus. wot_domain streams reconcile on
profile-tag changes at every depth and on graph changes at depths 1-2.
The activeStreamId store lookup is removed end to end: preserving the
mounted row is now the default behavior, not a carve-out.

Refs: #2294, #2302

---------

Co-authored-by: secondl1ght <85003930+secondl1ght@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(ui): add paste button to collection add-post URL field

The v25 Add Post dialog showed a clipboard control on Paste post url;
keyboard paste already worked, but the visible control was missing.

Closes: #2103

* fix(ui): harden paste-button flow and make input icon actions accessible

- guard submit with synchronous refs so double-clicks cannot start
  concurrent pipelines, and never commit after the dialog closes
- keep a slow clipboard read from clobbering text typed while waiting,
  and cap pasted values before they enter form state
- scope the clipboard catch to the read, log it, and explain how to
  unblock a denied clipboard permission (sticky in Chromium)
- render clickable input icons as real buttons (keyboard/screen-reader
  reachable, focus preserved while pending, errors announced via
  aria-describedby/role=alert) and use that path for the Paste button
- extract readFromClipboard() beside copyToClipboard, collapse the
  duplicated icon slots, and restore clipboard stubs after tests

---------

Co-authored-by: V <jovanovicv90@gmail.com>
* fix(ui): restore collection add-post options

Restore the original responsive four-card dialog and route the new Select action to the current user's profile posts without mutating collection contents.

Refs: #2105

* fix(ui): restore add-post dialog scrolling

Keep the shared DialogContent vertical overflow behavior so every stacked mobile option remains reachable in short viewports.

Refs: #2105
@catch-21 catch-21 self-assigned this Aug 24, 2026
@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

🚀 Preview Deployment

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

pubky-app-pr-2398a551421

secondl1ght and others added 4 commits August 24, 2026 18:46
* feat: attachment edit support for posts and articles

* perf: load images faster

* docs: update

* fix(files): settle all uploads before failing commitCreate

* refactor: pr comments

* tests: edit and remove post image e2e

* refactor: extract kind helper

---------

Co-authored-by: James Browning <jamesbrowning91@gmail.com>
* tests: own profile vrt

* test(vrt): regenerate baselines 2026-08-24T18:31:17Z

* test(vrt): regenerate baselines 2026-08-24T18:31:19Z

* tests: vrt own profile status

* test(vrt): regenerate baselines 2026-08-24T19:13:57Z

* test(vrt): regenerate baselines 2026-08-24T19:13:58Z

* tests: 'Other profile' vrt and orphon screenshot check

* test(vrt): regenerate baselines 2026-08-24T19:55:09Z

* tests: vrt profile thread replies

* test(vrt): regenerate baselines 2026-08-24T20:46:50Z

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix(posts): render more link inline with truncated text

* fix(posts): preserve expansion for truncated code blocks

* fix(posts): keep more control when truncation sentinel is missing

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(posts): anchor the inline more button to the truncated block

The show-more plugin searched the whole tree for the truncation ellipsis
and attached the button to the last node ending with it. Because
truncatePostPreviewText always appends the ellipsis to the very end of
the preview text, the cut is by definition the last top-level block, so
the search could only ever go wrong:

- a user-authored "..." + NBSP (as copied out of another truncated
  preview) took the button, leaving the real cut unmarked
- the same match inside a code block stripped four characters off the
  user's own code
- a cut landing in a blockquote or list rendered the post-level control
  inside the quote or bullet, styled as part of the quoted text
- the no-match fallback appended a second ellipsis where the original
  was still visible, so tables and raw HTML showed "..." twice

Attach to the last block instead, appending the ellipsis paragraph after
blocks that cannot host the button and clearing their own trailing
ellipsis first. Gate the button renderer on data-type="show-more" so only
the plugin's own node triggers expansion, matching the hashtag and
mention convention already used for links.

---------

Co-authored-by: V <jovanovicv90@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(posts): compact URLs in post and article content

* fix(posts): derive the compact URL from the parsed link

The compact label was sliced out of the text the author typed rather than
taken from the parsed destination, so the two could disagree. A post
containing https://аpple.com (Cyrillic а) rendered as a clean apple.com
chip while the browser resolved xn--pple-43d.com, and feed posts have no
link confirmation dialog to catch it. Host extraction now lives in
getUrlHostLabel beside getLabelFromUrl in @/libs/utils/urlToIcon and reads
URL.host, which also normalises casing, drops a default port, and keeps
www. when removing it would leave a bare public suffix (www.com -> com).

The tooltip and the accessible name now render the parsed href with any
credentials stripped, so a password embedded in a URL is no longer read
out or left in the DOM, and a www. autolink reveals the http:// scheme its
visible text omits.

Remove the hand-rolled long-press gesture: four refs, five pointer
handlers, the cleanup effect and the controlled tooltip state. It left a
suppression flag set after a scroll, which swallowed the next keyboard
activation, and it blocked the context menu after any tap, taking Copy
Link away on mobile with nothing in its place. The browser's own
long-press menu now fills that role, and the tooltip is hidden on coarse
pointers where it would otherwise flash open on every tap.

Preview truncation no longer cuts through a URL. A chopped URL still
autolinks and still compacts, so it rendered as a tidy host pointing at an
address that does not resolve. Where keeping a trailing URL whole hides
nothing, no "Show more" is offered.

The e2e spec looked posts up by text containing the full URL; it now uses
a marker that renders verbatim and asserts the host label with the full
destination kept on the anchor.

---------

Co-authored-by: infin1t3 <jovanovicv90@gmail.com>
catch-21 and others added 5 commits August 25, 2026 14:45
* tests: vrt settings

* test(vrt): regenerate baselines 2026-08-24T21:33:16Z

* test(vrt): regenerate baselines 2026-08-24T21:33:17Z

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* tests: single post vrt

* test(vrt): regenerate baselines 2026-08-25T20:19:56Z

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…2355)

* feat(search): add collections section to search results (#1841)

* test(vrt): regenerate baselines 2026-08-16T20:55:03Z

* fix(search): keep pagination path when collections slice settles empty

* feat(search): add people section to search results

* test(vrt): regenerate search baselines after dev merge

* test(vrt): regenerate baselines 2026-08-21T07:10:32Z

* fix(search): link entire people card to profile and trim dead code

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: secondl1ght <85003930+secondl1ght@users.noreply.github.com>
* fix(ui): reveal custom feed tab pencil on hover

Show a muted pencil on the right of editable feed tabs so users can
open Edit without navigating. Clicking the name still opens the feed.

Refs: #1898
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): make custom feed tab link fill the tab

Keep name clicks and empty tab space navigating to the feed while the
pencil stays a separate edit control.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): stretch custom feed link across the whole tab

h-full does not fill a min-height flex wrapper, so clicks on the
tab padding still missed the link. Pin the link to inset-0 instead.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(ui): redesign feed tabs with reach label, icons, and mobile tab bar

- 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.

* fix(ui): apply code-review findings to feed tabs and icon stack

- Pencil: on hover-capable lg+ pointers it hides (and ignores events)
  until hover/focus-within; hover-less devices (iPad, touch laptops)
  keep it visible since :hover can never fire there.
- Feed tab bar: sticky chrome and the gradient fade move to a
  non-scrolling wrapper so the fade paints below the bar instead of
  being clipped into the scrollport with phantom vertical scroll.
- Icon picker: search also matches deprecated Lucide aliases mapped to
  their canonical glyph (querying 'home' finds 'house'); the aliases
  file now stores the alias->canonical map.
- Lucide catalog (~116KB dynamic-import map) is lazily chunked instead
  of shipping in the /home bundle; name checks are shape-based and the
  loader validates against the real catalog.
- One controlled edit CustomFeedDialog per strip instead of a mounted
  react-hook-form instance per feed tab; dialog supports controlled
  open/onOpenChange without a trigger.
- useCustomFeedForm guards submit/delete re-entrancy with a ref so a
  queued second click cannot start a second commit.
- Shared useSelectedReachFilter hook replaces the copied reach
  derivation in HomeFeedSidebar and FeedNavigation; the tab falls back
  to All for an out-of-enum persisted reach instead of crashing.
- CustomFeedDialog derives reach options from REACH_FILTER_META; tab
  width/padding constants unified (active pill pads px-8 everywhere).
- Cleanups: sanitizeIcon at the FeedNormalizer chokepoint, stale JSDoc
  and duplicate import removed in feed.validators, dead
  HomeFeedRightDrawerMobile removed.

* test(vrt): regenerate baselines 2026-08-20T09:21:51Z

* refactor(ui): rework dynamic icon store and icon picker internals

- Icon state moves into a subscribable module store read through
  useSyncExternalStore: no impure map read during render (React
  Compiler-safe), hydration correct by construction, one shared state
  per icon, and a failed chunk heals every mounted instance when any
  retry succeeds. Unknown names are answered from the resident catalog
  instead of stored, so peer-synced garbage cannot grow memory, and
  'unknown' is distinguished from 'error'.
- Loaded dynamic icons carry the same lucide-<name> class as static
  imports so CSS rules and test selectors match both.
- Icon picker drops the hand-rolled scroll virtualizer (per-scroll-event
  renders, Tailwind-coupled constants, Tab-unreachable cells) for
  progressive append via the existing useInfiniteScroll hook, drops the
  open-animation gate that blanked the grid on every open, and defers
  the search query with useDeferredValue so superseded keystrokes abandon
  their work instead of mounting throwaway cells.
- Search now also matches lucide's synonym tags ('delete' finds the
  trash icons) via a generated lucideIcons.tags map loaded lazily with
  the catalog; name matches rank first. Result count is announced via
  role=status.
- Verified on the production build: the catalog (57KB) and tags (148KB
  raw) chunks stay out of the /home initial scripts.

* fix(test): stabilize VRT runs against browser-mode startup races

- Run VRT serialized (--no-file-parallelism): parallel page startup
  intermittently kills the whole run before any test executes with an
  unhandled 'route.fulfill: target closed' rejection from
  @vitest/browser-playwright (reproduced locally on two consecutive
  commits; serialized runs are reliably stable). Documented in
  docs/visual-regression-testing.md.
- Pre-bundle lucide-react/dynamic.js in the vrt project so the lazy
  catalog import cannot trigger a mid-run dependency re-optimization
  and page reload under in-flight requests.
- renderForVRT now waits for dynamic Lucide icons to finish loading
  (no empty svg.lucide in the capture root), so a baseline can never
  nondeterministically bake in a blank icon box; a hung chunk fails
  the test, matching the existing image policy.

* test(vrt): regenerate baselines 2026-08-20T12:55:34Z

* refactor(ui): use Container/Typography atoms and trim single-use type exports

- Replace plain div/p/span in IconPickerDialog and the FeedNavigation
  custom-feed tab wrapper with Container/Typography (overrideDefaults),
  matching house style; sentinel follows the WhoToFollow precedent.
- Un-export IconPickerDialogProps, DynamicLucideIconProps and
  LucideIconState — no external consumers.

* fix(ui): ui fixes

* test(vrt): regenerate baselines 2026-08-24T14:38:51Z

* test(vrt): drop ineffective optimizeDeps entry, correct the parallelism note

Pre-bundling lucide-react/dynamic.js does not prevent the browser-mode
teardown crash — measured with and without it, parallel runs fail either
way. Keep only the serialization flag, which does prevent it, and record
what was actually measured.

* test(vrt): run the suite in parallel again

Drops the serialization flag: the browser-mode teardown race it worked
around is intermittent, and a rerun is preferable to slowing every VRT
run. Leaves the VRT scripts, config and docs identical to dev.

* refactor(ui): move lucide helpers into libs/lucide and fix review findings

- Move the lucide store, alias map, tag map and their tests from
  libs/utils into a dedicated libs/lucide folder.
- Normalize icon-name case in one place: toLucideIconName (UI) and
  FeedValidators.sanitizeIcon (persistence) now agree, so an icon another
  client stored as 'Activity' renders its real glyph instead of silently
  falling back.
- Cancel the VRT dynamic-icon poll on timeout so it stops burning frames
  for the rest of the page.
- Drop the unused reset from useCustomFeedForm.
- Use the shared useControlledState in IconPickerDialog instead of its
  own controlled/uncontrolled handling.
- Close the feed edit dialog when the session ends.
- Drop reach options with no home-store equivalent instead of defaulting
  them onto another option's label and icon.
- Update the docs that referenced the old path and helper.

* refactor(core): move the tagged-as reach constant out of the filter component

TAGGED_AS_FILTER_KEY moves to config/feed and the ReachFilterValue type
next to ReachType in the home store types, so hooks and core-adjacent
code no longer import a constant from a filter component. FilterReach
keeps only the presentational REACH_FILTER_META.

* fix(ui): harden the icon path against peer-controlled names and load failures

- Look up the Lucide catalog with Object.hasOwn: 'constructor' is an
  inherited key that resolves to Object, whose module has no icon node,
  so a synced feed carrying that name crashed the feed pages. Guard the
  resolved node too.
- Handle a rejected catalog load in the icon picker: the loader's own
  catch does not cover the caller's derived promise, so a failed chunk
  became an unhandled rejection and left the grid loading forever.
- Store lucide tags hyphenated so a multi-word query ('air conditioner')
  matches the 227 multi-word synonyms, not just single words.
- Validate stored icons against the same name pattern the UI resolves
  with, now shared from config, so a name cannot be persisted and then
  permanently render as the fallback.
- Retry icons that failed to load when the browser comes back online,
  which is what the component's self-healing promise claimed.
- Cap the active feed tab width below lg so one long feed name truncates
  instead of pushing every other tab off-screen.
- Drop the edit dialog's feed snapshot when a background sync removes
  that feed, and show a stored reach or content this dialog cannot offer
  as a disabled option instead of an apparently empty select.

* perf(ui): load picker icons as one chunk and drop the dead mobile drawer slot

- The icon picker renders ~1700 cells and lucide ships one chunk per
  icon, so scrolling the catalog cost ~1700 requests and ~920KB. Add a
  generated bundle of every canonical icon node and load it once when
  the picker opens: one request, ~109KB, and every cell paints straight
  away. Feed tabs keep the per-icon path — they render a handful of
  icons and must not pull the whole set.
- Stop notifying subscribers when an icon enters the loading state: it
  renders exactly like a name that was never requested, so no output can
  change. Halves the notifications a picker session produces.
- Skip writing an icon node that the bundle already resolved while a
  per-icon request was in flight.
- Remove the mobile right-drawer slot from ContentLayout, the FeedSection
  molecule it used to hold, and the stale mock left behind: nothing has
  passed that slot since feed selection moved into the tab bar.
- Document the three generated lucide files and when to regenerate them.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: secondl1ght <85003930+secondl1ght@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Aldert <81586998+aldertnl@users.noreply.github.com>
Co-authored-by: Taehwa Kim <hadeath03@gmail.com>
aldertnl and others added 6 commits August 26, 2026 09:58
* fix(ui): narrow custom feed dialog

* fix(ui): narrow feed icon picker
* fix(ui): prevent profile name descender clipping

* fix(ui): give profile name room for descenders via clip-margin

4px padding still clipped Inter Tight at 60px. overflow-clip-margin keeps the compact line box and paints the missing ink.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(ui): sync profile header snapshots with prettier class order

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: V <jovanovicv90@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Safari does not support overflow-clip-margin (through v27), so the #2422
fix only worked in Chromium/Firefox and profile name descenders stayed
clipped in Safari — and the webkit VRT assertion on overflowClipMargin
failed, keeping the dev VRT gate red. Swap the mechanism for bottom
padding (clipping happens at the padding edge) cancelled by a matching
negative margin, which every browser honors, and assert the padding pair
in the VRT test instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
@catch-21
catch-21 marked this pull request as ready for review August 28, 2026 14:37
@greptile-apps

greptile-apps Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review (672 files, 100 file limit).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants