Skip to content

Rewrite detail as UIKit hybrid hero + promote to production

jedmund requested to merge jedmund/ios26-transitions into main

Created by: jedmund

Summary

The detail view is now a UIKit hybrid driven by a custom HeroTransitionAnimator instead of SwiftUI's matched-geometry / floating-image coordinator, and the HeroTest* harness it was built under has been promoted to the production detail path.

54 commits, +6,127 / −1,196. The short version of what happened across the branch:

  • Abandoned the pure-SwiftUI floating-image coordinator (FloatingHeroCoordinator + CellFrameRegistry + overlay-stack shims) — it had durability bugs that multiplied with nav depth.
  • Tried iOS 26 native NavigationTransition.zoom(sourceID:in:) — dropped it because a non-interactive spring dismiss couldn't match the pull-down feel we wanted.
  • Landed on a UIKit hybrid: PixillateNavigationController per tab, HeroTransitionAnimator talks to any HeroTransitionTarget conforming VC. CellRectRegistry + SwiftUIFeedScroller handle cell-rect lookup / scroll-to-cell at transition time.
  • Grew a HeroTest* harness under a dev flag so the animator could iterate on colored rects, then progressively onto real Pixiv data: images, bookmarks, share sheet, metadata card with tags / caption links (in-app Safari), lightbox with zoom transition, profile with real avatar/bio/counts, related-posts nested grid, multi-page pedestal with swipe-paging, adjacent-illust prefetch.
  • Consolidated at the end: deleted the old IllustDetailViewController / MasonryFeed / HomeView / RelatedIllustsSection / PedestalView / HeroTestHarnessRoot / ProfileDestination (and the AppSettings.Key.useHeroTestHarness flag), renamed HeroTest*Illust* to reclaim production names, and refactored IllustFeedView to be content-only (callers own the ScrollView + feed-scroller registration).

Notable additions

  • pixillate/UIKit/: new hybrid UIKit layer — PixillateNavigationController, HeroTransitionAnimator, HeroTransitionTarget, IllustDetailViewController, IllustProfileViewController, PagingPedestalView, NukeUIImageView, LightboxTransitionAnimator, CrossfadeSnapshotView, WindowChromeInsetting, TabBarVisibility.
  • pixillate/Views/: IllustFeedView (Pinterest masonry), IllustFeedViewModel, CellRectRegistry, SwiftUIFeedScroller, MetadataView, IllustLightboxView, PixivImageSizeRegistry, PixivImageDiskCache, HeroImageStack.
  • pixillate/Transitions/: SpringTuner + SpringTuningOverlay for live spring tuning (surfaced in Settings → Developer).
  • ImagePipeline.pixiv gets a dedicated .pixivLightbox sibling with its own cache so full-resolution originals don't evict grid thumbnails; both use a TTLDataCache for per-pipeline on-disk storage with age-based eviction.

Known trade-offs (tracked as follow-ups, not blockers)

  • Detail swipe-paging dead-ends at the source feed's tail. When a user swipes past the last item captured in the push payload, nothing happens — we should load-more through the source feed's view model and splice new items in. Deferred; saved as project_harness_detail_endless_swipe.md in agent memory.
  • Multi-page cell tap flash under rapid taps — a mitigation shipped but wasn't re-verified post-refactor. Tracked as project_multipage_flash_investigation.md.
  • Two TODO(herotest) markers around estimated-offset fallback when a cell has never been mounted — a niche edge case that silently no-ops today.

Test plan

  • Launch signed-in → Home tab renders the following feed.
  • Tap a cell → hero push to detail; pedestal, metadata card, tag pills, caption links (open in-app Safari), bookmark heart, lightbox via pedestal tap all work.
  • Swipe left/right on the pedestal → feed advance; metadata card stays on screen (no flash), adjacent neighbors are pre-warmed.
  • Tap user row in detail → profile pushes with real avatar, handle, bio, illust/manga counts.
  • Tap a cell in that profile → another detail pushes; 5+ levels deep still pop back to the correct cell.
  • Pull-to-dismiss from detail → animates back to the currently-displayed illust's cell (not just the initially-tapped one).
  • Profile tab (your own): Works + Bookmarks grids render, tap → detail, back works. Bookmark-visibility menu still works.
  • Tag feed (from a tag pill or from a search submit): renders, tap → detail, back works. Sort menu + time-range settings still apply.
  • Lightbox: pinch-zoom, double-tap, seeded image appears instantly, close button adapts to image luminance.
  • Rotate iPad → masonry column count re-buckets cleanly (no squish/stretch animation).
  • grep -r "HeroTest\|useHeroTestHarness\|pushHeroTestDetail\|MasonryFeed\|HomeView\|RelatedIllustsSection" returns zero matches.
  • Clean build from DerivedData.

🤖 Generated with Claude Code

Merge request reports

Loading