Skip to content

Feeds + profiles: glass search, floating chrome, Bookmarks tab, profile consolidation

jedmund requested to merge jedmund/sao-paulo-v5 into main

Created by: jedmund

Summary

Five commits, reshaping the top chrome across Search / Tag / Profile feeds, collapsing two profile code paths into one, and landing several foundation pieces they all share.

What changed

  1. Search: custom glass input bar — drops .searchable for a Capsule-clipped TextField with .glassEffect, 60% width, centered. Suggestions render inline below. The big "Search for a tag" empty-state is gone in favor of a longer placeholder string.

  2. FloatingFeedChrome + FloatingChromeButton — shared component with leading/center/trailing slots and a scroll-reactive glass capsule behind the center that fades in as content scrolls under. fadeThresholdOffset lets callers with a hero header (pushed ProfileView) delay the reveal until the header is concealed. FloatingChromeButton is the 51pt glass circle reused everywhere; it uses .contentShape(Circle()) before .clipShape + .glassEffect so iOS 26's glass modifier doesn't break hit-testing.

  3. PixillateHostingController lifecycle flagshidesTabBar, hidesNavigationBar, appliesTrafficLightInset. Each opt-in, each reversible on pop (not on being-covered-by-a-pushed-detail). The nav-bar one hides the UIKit nav imperatively because SwiftUI's .toolbar(.hidden, for: .navigationBar) doesn't bridge through UIHostingController onto our custom UINavigationController. NavigationControllerHost threads the flags through hosted(_:) and exposes root-level variants for tab roots that need custom chrome.

  4. TagFeedView adopts the floating chrome: back chevron leading, scroll-reactive title pill center, filter menu trailing whose icon tracks the current sort (flame / clock) and whose menu holds the sort picker + a "Time range…" entry.

  5. Profile tab → "Bookmarks". Tab root (isCurrentUser: true): bookmarks grid only, no header, no Works picker. On iPad the visibility menu + settings gear render in the top tab-bar row via an overlay on TabView; on iPhone / compact width they fall back to an inline top-trailing cluster inside ProfileView. Shared state via a small BookmarksChromeState @Observable. Pushed ProfileView (isCurrentUser: false, from detail user-row or tag-pill): full profile header + works grid, back chevron leading, and a center title pill (avatar + name) that reveals with a bouncy spring once scroll crosses ~185pt (past the header) and collapses with a quick ease-out. ProfileHeaderView gains a shared paletteColor(forUserID:) helper used for the avatar fallback circle.

  6. Profile consolidation: IllustProfileViewController retired. IllustDetailViewController.handleProfileTapped() now delegates to nav.pushProfile?(userID:) — the same path every other profile push uses. The UIKit profile VC file (~350 lines) is deleted along with its local palette helper. One profile rendering path across the app; tag-pill taps, detail user-row taps, and the Bookmarks tab root all land in the same ProfileView.

Notable debugging notes

  • iOS 26.2's .glassEffect(.regular.tint(.clear)) applied to a Button's label before .clipShape(Circle()) can emit a hit region that doesn't line up with the visible circle — taps land on a surrounding transparent area of the effect and get absorbed by whatever sibling view occupies the adjacent space. The fix is to declare .contentShape(Circle()) before .clipShape(Circle()) + .glassEffect(…). This got buried in chunk 3's debugging session on the pushed-profile back chevron.
  • additionalSafeAreaInsets.top asserted on a UIHostingController that still has a visible UIKit nav bar interacts badly with nav-bar .navigationTitle / .toolbar rendering (items go blank). appliesTrafficLightInset is therefore only meant to pair with hidesNavigationBar.

Test plan

  • Search tab: new glass input bar reads, typeahead works, submit pushes the tag feed.
  • Tap a tag pill from a detail → TagFeedView opens with the floating glass chrome. Back chevron pops. Filter icon reflects current sort (flame/clock); changing sort reloads the feed; "Time range…" opens the settings sheet.
  • Bookmarks tab: no Works picker, no profile header. On iPad the visibility menu + settings gear sit in the top tab-bar row on the trailing edge. On iPhone / split-compact they render inline at the top of the view instead. Visibility change reloads the feed with the correct restrict; tapping the gear opens the sheet.
  • From a detail, tap the user row → pushed ProfileView opens with the full header. Scroll up ~200pt → the center avatar-and-name pill bounces in; scroll back to the top → the pill quickly fades out (no bounce). Back chevron pops cleanly.
  • From a tag pill, tap a user row from the pushed profile's cell → that user's profile pushes on top. Nested nav works.
  • iPad rotation + split-view resize: TabView-row overlay appears/disappears as the horizontal size class changes between regular and compact; buttons don't duplicate.
  • iPadOS 26 windowed mode: floating chrome clears the traffic-light cluster.
  • Hero transitions from feed cells still work end-to-end; second-dismiss cell-rect fix from #20 isn't regressed.

🤖 Generated with Claude Code

Merge request reports

Loading