Commit Graph

230 Commits

Author SHA1 Message Date
Yusuf Suleman
e28c4d0003 fix: camera crash — add NSCameraUsageDescription + availability check
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
Crash: iOS terminates the app when accessing camera without the
privacy description in Info.plist.

Fix:
- Added NSCameraUsageDescription to Info.plist
- "Take Photo" option only shows if camera is available
  (UIImagePickerController.isSourceTypeAvailable(.camera))

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 22:40:43 -05:00
Yusuf Suleman
6a694fcbcf fix: #31 — changing quantity auto-recalculates macros in draft card
All checks were successful
Security Checks / dependency-audit (push) Successful in 12s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
Captures base per-unit values (calories/protein/carbs/fat/sugar/fiber
divided by original quantity) when draft first appears. When quantity
changes, recalculateMacros() multiplies base values by new quantity.

Example: AI drafts "1 medium banana" at 105 cal. User changes
quantity to 2.0 → calories becomes 210, protein doubles, etc.

Values round to integers for calories, 1 decimal for macros.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 22:33:21 -05:00
Yusuf Suleman
b5d734efe1 fix: #29 — edit food name, macros, unit in food library
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
Long-press a food → "Edit" opens EditFoodSheet with:
- Name and brand fields
- All macros: calories, protein, carbs, fat, sugar, fiber
- Base unit field
- Save calls PATCH /api/fitness/foods/{id}
- List refreshes after save

Also added updateFood() to FitnessAPI and UpdateFoodRequest model.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 22:31:46 -05:00
Yusuf Suleman
55ef010370 fix: Gitea issues #27, #28, #30
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
#28 — Return key now inserts new line in AI chat:
TextField replaced with TextField(axis: .vertical) + lineLimit(1...5).
Return = new line, send button submits. Removed .onSubmit.

#27 — Delete foods in food library:
Long-press context menu with "Delete" option on each food item.
Calls DELETE /api/fitness/foods/{id} and reloads.

#30 — Camera + photo library options:
Replaced PhotosPicker with a Menu offering "Take Photo" (camera)
and "Photo Library" (picker). CameraView wraps UIImagePickerController.
handleCameraImage() in ViewModel resizes and sends same as photo picker.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 22:21:13 -05:00
Yusuf Suleman
fa3932c597 feat: Trip Detail as chronological timeline (matches web dashboard)
All checks were successful
Security Checks / dependency-audit (push) Successful in 34s
Security Checks / secret-scanning (push) Successful in 3s
Security Checks / dockerfile-lint (push) Successful in 4s
Replaced type-grouped sections with a unified timeline:
- All items (flights, lodging, places) sorted by date + time
- Grouped by day with "Tuesday, Jan 20" headers
- Vertical timeline line connecting items within each day
- Color-coded icons: blue (flights), warm (lodging), red (places)
- Time shown in 12h format (8:50 AM)
- Category badges for places
- Subtitle with route/location details

Same layout order as the web dashboard — not grouped by type.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 20:31:07 -05:00
Yusuf Suleman
a739e0de80 feat: Trip Detail screen — real data, Wishlist-quality polish
All checks were successful
Security Checks / dependency-audit (push) Successful in 25s
Security Checks / secret-scanning (push) Successful in 7s
Security Checks / dockerfile-lint (push) Successful in 6s
Adapted from Apple's Wishlist TripDetailView:
- 510pt hero image with .scrollEdgeEffectStyle(.soft)
- Trip stats overlay (glass material + MeshGradient, same pattern)
- Title in .largeTitle toolbar (expanded font)
- .toolbarRole(.editor) for clean back navigation

Content sections with real API data:
- Lodging: hotel name, location, check-in/out dates
- Flights & Transport: route, type icon, flight number
- Places: name, category, visit date
- AI Recommendations: first 500 chars of suggestions
- Empty state when no details added

Models: TripDetail, TripTransportation, TripLodging, TripLocation,
TripNote — matching the /api/trip/{id} response shape

GradientOverlay: MeshGradient mask from Wishlist's GradientView

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:35:25 -05:00
Yusuf Suleman
d680af0547 cleanup: remove trips debug logging — zoom transition confirmed stable
All checks were successful
Security Checks / dependency-audit (push) Successful in 27s
Security Checks / secret-scanning (push) Successful in 8s
Security Checks / dockerfile-lint (push) Successful in 7s
2026-04-04 18:21:01 -05:00
Yusuf Suleman
e4a079bf11 debug: trips navigation logging round 2
All checks were successful
Security Checks / dependency-audit (push) Successful in 14s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
2026-04-04 15:28:32 -05:00
Yusuf Suleman
583138fbe2 fix: stabilize view tree for zoom transition recovery
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 3s
ROOT CAUSE: @State TripsViewModel inside TripsHomeView was recreated
during navigation transitions. Each recreation set isLoading=true,
flipping the Group conditional, destroying the ScrollView + namespace.
The zoom transition's matchedTransitionSource evaporated.

FIXES (matching Apple's Wishlist pattern):
1. ViewModel owned by MainTabView (like ReaderVM) — survives
   view recreation. Pre-loaded on app launch.
2. Loading state rendered INSIDE ScrollView — no conditional
   Group wrapper that swaps the entire view tree.
3. Single @Namespace in TripsHomeView, passed to both
   UpcomingTripsPageView and PastTripsSection.
4. Zoom transition restored on all NavigationLinks.

Why Apple's sample works: they use @Environment(DataSource.self)
which is app-level stable. Our equivalent: @State at MainTabView.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 14:43:46 -05:00
Yusuf Suleman
864ca679ce fix: remove .zoom transition causing trip card disappearance on back
All checks were successful
Security Checks / dockerfile-lint (push) Successful in 4s
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
ROOT CAUSE: .navigationTransition(.zoom) + .matchedTransitionSource
in a paged TabView. When navigating back, the zoom tries to animate
to the source card, but the TabView may have recycled/repositioned
the page. The animation targets a stale frame, causing the card to
flash black or disappear.

FIX: Removed .zoom transitions and .matchedTransitionSource from
both UpcomingTripsPageView and PastTripsSection. Standard push/pop
navigation is stable with paged TabViews.

Also removed all debug logging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 14:37:37 -05:00
Yusuf Suleman
48b6522cf5 debug: trips navigation lifecycle logging
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 3s
2026-04-04 14:33:10 -05:00
4621d4f606 fix: resolve duplicate B10060 ID — entitlements vs TripModels collision
All checks were successful
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / dockerfile-lint (push) Successful in 3s
2026-04-04 14:26:16 -05:00
Yusuf Suleman
97f4ac5150 force: ensure pbxproj with Trips entries is latest
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 3s
2026-04-04 14:21:50 -05:00
Yusuf Suleman
4d6960c508 feat: Trips home screen — inspired by Apple's Wishlist sample
All checks were successful
Security Checks / dockerfile-lint (push) Successful in 3s
Security Checks / dependency-audit (push) Successful in 14s
Security Checks / secret-scanning (push) Successful in 4s
Mapped Wishlist → Trips:
- WishlistView → TripsHomeView (NavigationStack + ScrollView)
- RecentTripsPageView → UpcomingTripsPageView (paged TabView hero)
- TripCollectionView → PastTripsSection (horizontal scroll compact)
- TripCard → TripCard (.compact/.expanded sizes)
- TripImageView → TripImageView (Rectangle overlay + AsyncImage)
- ExpandedNavigationTitle → same pattern for "Trips" title
- AddTripView → Plan Trip button (.glassProminent, Phase 2)

Structure (9 files):
- TripModels.swift: Trip model with date helpers, image URL builder
- TripsAPI.swift: getTrips() via gateway /api/trips/trips
- TripsViewModel.swift: upcoming/past sorting, @Observable
- TripsHomeView.swift: main screen, Wishlist layout pattern
- UpcomingTripsPageView.swift: full-width paged hero cards
- PastTripsSection.swift: horizontal compact card scroll
- TripCard.swift: reusable card with compact/expanded sizes
- TripImageView.swift: AsyncImage with gradient placeholder
- TripPlaceholderView.swift: simple detail for Phase 1

New tab: Trips (airplane icon) in tab bar for all users.
Images served via gateway: /api/trips/images/{filename}

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 14:16:15 -05:00
Yusuf Suleman
1cfb729cae fix: auto-scroll loads more when near bottom
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
onAppear doesn't fire during programmatic scrolling (UIScrollView
contentOffset changes don't trigger SwiftUI lifecycle). Added
onNearBottom callback to ScrollViewDriver — fires when within 500pt
of bottom during auto-scroll tick. 3s cooldown prevents rapid-fire.

Auto-scroll no longer stops at bottom — idles at maxOffset while
loadMore fetches. When new entries arrive, contentSize grows and
scrolling resumes automatically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 13:39:55 -05:00
Yusuf Suleman
ae3b3f11bf fix: card thumbnail overflow — use GeometryReader to constrain image
All checks were successful
Security Checks / dependency-audit (push) Successful in 14s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
Images with .fill were expanding beyond the 180pt frame because
SwiftUI's .clipped() doesn't constrain the layout, only the
rendering. Now uses GeometryReader to set explicit width + height
on the image, then clips the container. Guarantees 180pt max
regardless of image aspect ratio.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 13:15:19 -05:00
Yusuf Suleman
92a44faac3 fix: three Reader bugs — image overflow, load more, refresh read state
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
#22 Image overflow: added .frame(maxWidth: .infinity) before
.frame(height: 180) on AsyncImage to constrain width within card.
Card's .clipShape already clips corners.

#23 Load more not triggering: added loadMoreIfNeeded(for:) that
fires onAppear for entries 5 from the bottom. No longer relies
solely on the bottom sentinel Color.clear which could be missed.
Also increased sentinel height from 1pt to 40pt.

#24 Refresh not updating read state: flushDeferredReads() now
called before vm.refresh() in .refreshable. Deferred marks are
synced to API before re-fetching, so the server returns correct
read states. Also clears markedByScroll set.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 13:08:27 -05:00
Yusuf Suleman
d8f0e5d845 feat: animated macro rings, bars, and staggered meal card entrance
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
MacroRing:
- Animates from 0 to target on appear (spring, 1.0s response)
- Center value fades in + scales up with 0.4s delay
- Updates animate smoothly on data change
- .contentTransition(.numericText()) on calorie count

MacroBar:
- Bar width animates from 0 to target on appear (spring, 0.3s delay)
- Updates animate smoothly on data change
- .contentTransition(.numericText()) on values

TodayView:
- Meal sections stagger in: fade up with 0.08s delay per card
- Re-animates on tab switch (onAppear resets animated flag)
- Re-animates on date change
- Spring physics for natural feel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 12:20:21 -05:00
Yusuf Suleman
bf2ff59ade feat: auto-focus search field when Quick Add opens in sheet
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
Keyboard appears automatically when opening Quick Add from the
food assistant sheet. 300ms delay lets the sheet animation finish
first so the keyboard doesn't interfere with the presentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 12:02:39 -05:00
Yusuf Suleman
0a10d297cd fix: keep pill selector + swipeable pages (Quick Add first)
All checks were successful
Security Checks / dependency-audit (push) Successful in 14s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
Pill tabs (Quick Add / AI Chat) stay visible and tappable.
Pages are also swipeable. Pills sync with swipe position.
Quick Add shown first, swipe left for AI Chat.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:53:32 -05:00
Yusuf Suleman
66ab375ee0 feat: swipeable food sheet — Quick Add first, swipe for AI Chat
All checks were successful
Security Checks / dockerfile-lint (push) Successful in 4s
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Replaced pill tab selector with swipeable TabView (.page style):
- Page 0: Quick Add (food search list) — shown first
- Page 1: AI Chat — swipe right to access
- Custom dot indicators replace the old pill tabs
- Swipe gesture between pages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:52:01 -05:00
Yusuf Suleman
a5c95c2e5f feat: widget has two tap targets — fitness + add food
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 3s
Security Checks / dockerfile-lint (push) Successful in 4s
Widget background tap → platform://fitness → opens Fitness tab
Widget + button tap → platform://add-food → opens Fitness + food assistant

Small widget: + button in bottom-right corner (emerald green)
Medium widget: + button in bottom-right corner
Lock screen widgets: single tap → fitness (no room for + button)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:45:08 -05:00
Yusuf Suleman
7cfe3eeed5 feat: tap widget → opens app to food search
All checks were successful
Security Checks / secret-scanning (push) Successful in 3s
Security Checks / dockerfile-lint (push) Successful in 4s
Security Checks / dependency-audit (push) Successful in 13s
Widget: .widgetURL(platform://add-food) on all widget sizes
App: .onOpenURL handles platform://add-food → switches to Fitness
tab and opens the food assistant sheet

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:41:21 -05:00
3002c1f59d add widget Info.plist with NSExtension
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
2026-04-04 11:36:21 -05:00
Yusuf Suleman
5d51ac6833 harden: widget edge cases — expired session, account switch, cache
All checks were successful
Security Checks / dependency-audit (push) Successful in 14s
Security Checks / secret-scanning (push) Successful in 3s
Security Checks / dockerfile-lint (push) Successful in 4s
1. Session expires: widget gets 401 → clears stale cookie from
   App Group → stops retrying with bad auth → shows cached data
   until user opens app and re-authenticates

2. Account switch: login() now calls clearWidgetAuth() BEFORE
   syncCookieToWidget() — clears previous user's cached calories
   before writing new user's cookie. No brief display of wrong data.

3. Logout: already correct — clearWidgetAuth removes cookie +
   cached data, widget shows 0/2000

4. Minimum data: only session cookie + 2 cached numbers + timestamp
   in App Group. No passwords, no user IDs, no PII.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:31:44 -05:00
Yusuf Suleman
e21a26db18 feat: widget fetches calories from API independently + shared auth
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 3s
Security Checks / dockerfile-lint (push) Successful in 4s
Widget:
- Fetches /api/fitness/entries/totals and /api/fitness/goals/for-date
  directly from gateway using shared session cookie
- Falls back to cached data in App Group UserDefaults if network fails
- Refreshes every 15 minutes via WidgetKit timeline
- Each phone shows the logged-in user's own data

Auth sharing:
- AuthManager.syncCookieToWidget() copies the session cookie to
  App Group UserDefaults on login and auth check
- Widget reads cookie and makes authenticated API calls
- Logout clears widget auth + cached data

Data in App Group (group.com.quadjourney.platform):
- widget_sessionCookie: auth token for API calls
- widget_totalCalories: cached fallback
- widget_calorieGoal: cached fallback
- widget_lastUpdate: cache timestamp

HomeViewModel also writes cache on each loadTodayData() as fallback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:29:52 -05:00
2d4cafa16e add App Group entitlements for both targets
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
2026-04-04 11:29:26 -05:00
Yusuf Suleman
9965b1d634 feat: calorie ring widget — home screen + lock screen
All checks were successful
Security Checks / dockerfile-lint (push) Successful in 4s
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Widget displays:
- systemSmall: calorie ring + "X left" text
- systemMedium: ring + "Calories" / "X of Y" / "X remaining"
- accessoryCircular: gauge ring for lock screen
- accessoryInline: "🔥 845 / 2000 cal" text for lock screen
- accessoryRectangular: linear gauge + calorie count

Data flow: main app writes totalCalories + calorieGoal to
UserDefaults on each loadTodayData(), then calls
WidgetCenter.shared.reloadAllTimelines(). Widget reads on
15-minute refresh cycle.

Note: currently uses standard UserDefaults (same app container).
For production, migrate to App Group UserDefaults so widget
process can read the data. Requires Xcode App Group setup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:21:40 -05:00
a4ebe77973 feat: add PlatformWidget extension target
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 3s
2026-04-04 11:19:18 -05:00
Yusuf Suleman
c13259c2b5 polish: depth, contrast, and layering refinements
All checks were successful
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
Security Checks / dependency-audit (push) Successful in 13s
1. Background — reduced warmth, more neutral:
   Dark:  #0e0d0b → #0d0d0b (less warm, more true black)
   Light: #EDE6DA → #EBE6DE (cooler sand, less yellow)

2. Cards — increased elevation:
   Shadow: 0.04/6/2 → 0.08/8/3 (more visible lift)
   Spacing: 12pt → 14pt between cards, 4pt → 8pt top padding

3. Text — more neutral for glass legibility:
   Primary light: 0.12 → 0.10 (darker)
   Secondary: warmer gray → neutral gray (0.40 uniform)
   Tertiary: warmer → neutral (0.58 uniform)

4. Accent — slightly deeper in light mode:
   #8B6914 → #805E0F (more contrast against glass)

5. Dark mode accent — slightly brighter:
   0.78/0.62/0.25 → 0.82/0.65/0.28

All changes are color/shadow/spacing only. No layout or
architectural changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:54:32 -05:00
Yusuf Suleman
f4b527e70b fix: increase surface contrast — cards now clearly separate from background
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
Dark mode:
  Canvas: #1a1714 → #0e0d0b (deeper black, more separation)
  Card:   #26231f → #1e1b17 (warmer, 7% brighter than bg)

Light mode:
  Canvas: #F5EFE6 → #EDE6DA (cooler sand, slightly darker)
  Card:   #FFFCF8 → #FFFFFF (clean white, max contrast)

Both modes now have ~7% brightness gap + temperature contrast
(warm bg, cleaner card). Cards visually float without needing
borders.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:40:45 -05:00
Yusuf Suleman
c74f36a94d fix: widen speed gaps — Slow 1.0x, Med 2.0x, Fast 3.5x (was 1.5/1.75/2.0)
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
2026-04-04 10:31:55 -05:00
Yusuf Suleman
01c63d69d0 feat: auto-scroll speed cycles via tab bar button (Slow/Med/Fast)
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
Tab bar action button cycles through speeds on each tap:
- ▶ (play) → tap → Slow (1.5x, hare icon)
- Slow → tap → Med (1.75x, walk icon)
- Med → tap → Fast (2.0x, bolt icon)
- Fast → tap → back to Slow

Touch feed to stop → icon returns to ▶

Removed speed controls from Reader toolbar — all speed control
is now in the single tab bar button. Clean, no overlays.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:28:03 -05:00
Yusuf Suleman
e0ae9cb95f fix: inline title (no large title drop) + restore canvas background
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 3s
Security Checks / dockerfile-lint (push) Successful in 4s
1. .navigationBarTitleDisplayMode(.inline) — title stays at top
2. .background(Color.canvas) — restores warm cream background

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:11:09 -05:00
Yusuf Suleman
0f1a35ab84 simplify: remove sub-tabs, starred, feed filter chips from Reader
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
Reader now shows only unread entries. Removed:
- Unread/Starred/All sub-tab selector
- Feed filter chip bar (categories)
- All related state (selectedSubTab) and helpers

Glass nav bar shows: "Reader" title + "74 unread" subtitle
Trailing toolbar: grid/list toggle + ellipsis menu
Auto-scroll: speed controls in leading toolbar

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:07:40 -05:00
Yusuf Suleman
d75fb870d7 feat: Liquid Glass navigation bar for Reader (iOS 26 standard APIs)
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
Removed .navigationBarHidden(true) and all custom header layout.
Now uses standard iOS 26 navigation APIs that get Liquid Glass free:

- .navigationTitle("Reader") + .navigationSubtitle("74 unread")
- ToolbarItemGroup for sub-tabs (Unread/Starred/All) on leading
- ToolbarSpacer between groups
- ToolbarItemGroup for grid/list + menu on trailing
- Feed filter chips in .bottomBar toolbar
- When auto-scrolling: toolbar swaps to speed controls

The glass nav bar is translucent — content scrolls underneath.
Collapses to inline glass pill on scroll (system behavior).
No custom backgrounds, no custom layout — all system-managed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:03:58 -05:00
Yusuf Suleman
61cd78e080 feat: speed controls in collapsed tab bar via safeAreaBar
All checks were successful
Security Checks / dependency-audit (push) Successful in 27s
Security Checks / secret-scanning (push) Successful in 7s
Security Checks / dockerfile-lint (push) Successful in 6s
Uses .safeAreaBar(edge: .bottom) on the Reader content — the iOS 26
API that places content in the collapsed tab bar area, same position
as the Now Playing mini bar. Speed controls [ - ] 1.50x [ + ] appear
in the glass bar when auto-scrolling.

Removed the floating speed pill overlay from ContentView.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 09:00:42 -05:00
Yusuf Suleman
e37444c62e fix: speed pill and feedback in same VStack — no more guessing position
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
Speed pill and feedback button were in separate VStacks with
independent absolute padding, causing misalignment. Now they share
one VStack with .padding(.bottom, 70) at the container level.
The speed pill sits directly above the tab bar area, positioned
relative to the same anchor as all other bottom controls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 08:55:30 -05:00
Yusuf Suleman
a452c0d4f2 fix: move speed pill up to not overlap cards (padding.bottom 50)
All checks were successful
Security Checks / dependency-audit (push) Successful in 14s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
2026-04-04 08:52:14 -05:00
Yusuf Suleman
416a6ed3f8 fix: adjust speed pill position — was clipped at bottom, now 16pt above safe area
All checks were successful
Security Checks / dependency-audit (push) Successful in 14s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 3s
2026-04-04 08:47:07 -05:00
Yusuf Suleman
a39e0377b5 fix: move speed controls into tab bar area (tab bar hidden during scroll)
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 08:43:52 -05:00
Yusuf Suleman
63b6027902 fix: move speed controls to bottom of screen, use regularMaterial
Some checks failed
Security Checks / dependency-audit (push) Successful in 14s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Has been cancelled
Speed pill was floating mid-screen (padding.bottom 90). Moved to
bottom (padding.bottom 8) to sit just above the tab bar area.
Switched from ultraThinMaterial to regularMaterial for better
Liquid Glass look with more opacity and stronger blur.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 08:43:33 -05:00
Yusuf Suleman
17d10ec4c1 fix: eliminate auto-scroll jitter by deferring mark-as-read visual updates
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
ROOT CAUSE (confirmed by instrumentation):
Every markRead during auto-scroll mutated entries[idx].status,
causing SwiftUI to re-render the row (bold→regular, dot removed).
This changed card height, causing contentSize jumps of 10-128pt
per entry — visible as jitter.

FIX: During auto-scroll, collect entry IDs in deferredReadIDs
instead of mutating entries array. When auto-scroll stops,
flushDeferredReads() applies all status changes at once and
sends a single batched API call.

Manual scroll still marks immediately (no deferral needed since
the user controls the scroll position).

Removed all debug instrumentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 08:27:37 -05:00
Yusuf Suleman
39b9303918 debug: auto-scroll jitter instrumentation — tick timing, contentSize, loadMore, markRead
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 3s
2026-04-04 08:19:30 -05:00
Yusuf Suleman
976469f5fe revert: remove fake delegate calls for tab bar minimize
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
Calling scrollViewWillBeginDragging/DidEndDragging on the delegate
didn't trigger tabBarMinimizeBehavior — iOS 26 likely tracks actual
touch events, not delegate calls. Reverted to avoid side effects.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 08:08:55 -05:00
Yusuf Suleman
a82ae267b6 fix: tab bar collapses during auto-scroll
All checks were successful
Security Checks / dependency-audit (push) Successful in 14s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 5s
tabBarMinimizeBehavior needs scrollViewWillBeginDragging to recognize
a scroll session. Now ScrollViewDriver calls it on the original
delegate when auto-scroll starts, and scrollViewDidEndDragging +
scrollViewDidEndDecelerating when it stops.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 08:03:23 -05:00
Yusuf Suleman
395cca08dd fix: action button on Reader now toggles auto-scroll (not food assistant)
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
Bug: when action tab (value=3) was tapped, selectedTab was already 3
by the time handleActionTap ran. The check 'if selectedTab == 2'
always failed, falling through to food assistant.

Fix: use onChange(of: selectedTab) oldValue to capture which tab the
user was on BEFORE tapping the action button. Pass that to
handleActionTap(from:). If from Reader (2), toggle auto-scroll.
If from Home/Fitness, open food assistant.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 08:00:43 -05:00
Yusuf Suleman
e2fc87b6aa feat: Tab(role: .search) with context-dependent action per tab
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
Single separated circle in tab bar (like Photos search icon):
- Home/Fitness: shows + icon, taps opens food assistant
- Reader idle: shows play icon, taps starts auto-scroll
- Reader playing: shows pause icon, taps stops auto-scroll

Icon updates dynamically via computed actionIcon property.
handleActionTap() routes the tap based on selectedTab.
After action, selectedTab returns to the previous tab (doesn't
stay on the invisible "action" tab).

Speed controls still appear as glass capsule overlay when playing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 07:55:50 -05:00
8a8f865702 xcode update
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s
2026-04-04 07:42:56 -05:00
144f24b7a0 resolve pbxproj conflict 2026-04-04 07:42:56 -05:00