onDisappear fires when scrolling in BOTH directions and when
navigating, making it impossible to reliably detect scroll direction.
Reverted to simple behavior: articles only mark as read when you
tap into them (handled in ArticleView). Will revisit mark-on-scroll
with a proper ScrollViewReader approach later.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
loadInitial() had guard !isLoading but isLoading defaults to true,
so it returned immediately without loading. Replaced with hasLoadedOnce
flag to prevent double-loading without blocking the first call.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- FitnessDraft properties changed from let to var (mutable)
- New EditableDraftCard with Edit/Done toggle:
- Editable food name
- Editable macros (calories, protein, carbs, fat, sugar, fiber)
- Meal type picker (dropdown menu)
- Editable quantity
- Edited values flow through draftToDict → apply endpoint
- No backend changes needed — purely iOS UI
- Default view is read-only (same as before), tap Edit to modify
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ReaderViewModel starts with isLoading=true (shows spinner, not "No articles")
- MainTabView owns ReaderViewModel and pre-fetches in background on launch
- Sub-tabs and feed chips hidden during initial load (no tiny squished layout)
- VStack fills full screen with frame(maxWidth/maxHeight: .infinity)
- WebKit warmer triggers when Reader tab appears
- By the time user taps Reader, data is already loaded
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
iOS:
- PhotosPicker with maxSelectionCount: 5
- Horizontal scroll preview strip with individual remove buttons
- Sends "images" array instead of single "image"
Server:
- Gateway accepts both "image" (single, backwards compat) and
"images" (array) fields
- Uploads each as separate Gitea issue attachment
Also closed Gitea issues #11, #12, #13, #14.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reader mark-as-read:
- Track visible entry IDs with onAppear/onDisappear
- Only mark as read when entry disappears AND other entries are still
visible (meaning user is scrolling, not navigating to an article)
- Prevents the bug where opening an article marked all visible entries
Goals (#11):
- Added .scrollDismissesKeyboard(.interactively) for drag-to-dismiss
- Added tap-to-dismiss keyboard on background
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- AppearanceManager with UserDefaults persistence
- Three modes: System (follows iOS), Light, Dark
- Toggle in Home screen profile menu under "Appearance"
- Applied via .preferredColorScheme at app root
- Persists across app launches
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Uses .onDisappear instead of .onAppear — entries are marked as read
only when they scroll past the top of the viewport, not when the
list first renders. Same behavior as proper RSS readers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
.onAppear fires for all visible rows when the list renders, marking
everything as read immediately. Removed — entries only mark as read
when you tap into the article (handled in ArticleView).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dark mode:
- All colors in Color+Extensions now adaptive (UIColor with traits)
- Warm dark palette: dark brown canvas, brighter gold accent, warm cards
- Article HTML CSS supports prefers-color-scheme: dark
- Meal/macro colors unchanged (vivid on both themes)
Reader fixes:
- .contentShape(Rectangle()) on cards/rows — fixes tap target issues
where small cards couldn't be clicked
- Context menu moved from card/row to the NavigationLink wrapper
so it doesn't interfere with taps
- Mark as read on scroll via .onAppear on each entry
- Cards no longer pass vm (cleaner, context menu handled at list level)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Slim list mode means list responses no longer include content.
Now selectArticle() fetches the full entry via GET /entries/{id}
before displaying, then optionally crawls for full content if short.
Also uses API thumbnail field instead of extracting from empty content.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
First WKWebView creation in a session is slow (~2-3s) because iOS
lazily initializes the WebKit rendering engine. WebKitWarmer creates
a hidden 1x1 WKWebView with empty HTML on Reader tab load, forcing
the engine to initialize. Subsequent article opens are instant.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server: add slim query param (default true) to entries list endpoint,
returning EntrySlimOut without content/full_content HTML — cuts payload
size dramatically for list views. Single entry endpoint still returns
full content.
iOS: ArticleView now fetches full entry content on demand when opened
instead of relying on list data. Shows loading indicator while fetching.
Mark-as-read is fire-and-forget to avoid blocking the view.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server-side (dashboard + iOS + any client):
- Added thumbnail column to reader_entries
- Worker extracts from media:thumbnail, media:content, enclosures, HTML img
- API returns thumbnail in EntryOut with & decoding
- Backfilled 260 existing entries
iOS:
- Prefers API thumbnail, falls back to client-side extraction
- Decodes HTML entities in URLs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ArticleWebView: remove dangerous intrinsicContentSize override, use
height binding measured via JS after render
- ReaderViewModel: add @MainActor, replace didSet filter with explicit
applyFilter() to avoid property observer reentrancy
- Thumbnail extraction: use precompiled NSRegularExpression, limit search
to first 2000 chars, skip placeholder when no image found
- Card view: only show thumbnail when image exists (no placeholder)
- Feedback: add guard against double-tap, @MainActor on Task
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Card view: large thumbnail from article content, feed name, title, author, reading time
- List view: compact rows with mini thumbnail on right
- Toggle button in toolbar (grid/list icon)
- Thumbnail extracted from first <img> in article HTML
- Card view is default, warm Atelier styling preserved
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New third tab in the iOS app with:
- ReaderModels matching Reader API response shapes
- ReaderAPI with all endpoints (entries, feeds, categories, counters)
- ReaderViewModel with filters (Unread/Starred/All), pagination, feed management
- ReaderTabView with sub-tabs and feed filter chips
- EntryListView with infinite scroll, context menus, read/unread state
- ArticleView with WKWebView HTML rendering, star/read toggles, Save to Brain
- ArticleWebView (UIViewRepresentable WKWebView wrapper)
- FeedManagementSheet with add/delete/refresh feeds, categories
- Warm Atelier design consistent with existing app
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reverts all Liquid Glass styling back to the original warm beige/brown
Atelier design system. Deployment target back to iOS 17.0.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
iOS:
- Photo picker in feedback sheet (screenshot/photo attachment)
- Image sent as base64, uploaded to Gitea issue as attachment
Web:
- Feedback button in sidebar rail
- Modal with text area + send
- Auto-labels same as iOS
Gateway:
- Multipart image upload to Gitea issue assets API
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
F10020 was used for both Products and Feedback groups.
Xcode resolved it as Products, so FeedbackView path was wrong.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Goals PUT returns partial JSON, use putVoid + reload
- Home shows 'Hi, Yusuf' instead of 'Home'
- EmptyResponse type for void-like endpoints
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Sugar + Fiber bars in TodayView macro summary card
2. Goals page: editable text fields + Save button (PUT /api/fitness/goals)
3. AI Chat: keyboard dismisses on send + tap chat area to dismiss
4. Entry detail: edit quantity (stepper) + meal type picker + Save
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- DragGesture with minimumDistance:15 as highPriorityGesture
- Tap only navigates when not swiping
- Tapping while swiped closes the delete button
- Hidden NavigationLink for programmatic navigation
- Reverted FitnessTabView back to page-style TabView
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaced .tabViewStyle(.page) with Group/switch.
Tab switching only via top buttons, no horizontal page swipe.
This prevents swipe-to-delete from accidentally switching tabs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Button outside ScrollView using safeAreaInset(edge: .bottom)
- No more scroll gesture eating the first tap
- scrollDismissesKeyboard(.immediately) for keyboard handling
- Swipe to delete on meal entries
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Custom SwipeToDeleteRow with drag gesture
- Swipe left reveals red trash button
- Tap still opens EntryDetailView with delete option
- Both paths call the same onDelete callback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Exact replica of ReactFlux confetti:
- 200 tiny rectangles scattered across full screen width
- Drop from above the screen, fall down with gravity
- Gentle horizontal drift + 3D rotation (tumbling effect)
- 10 vivid colors matching ReactFlux palette
- Staggered delays (0-0.4s) for natural rain effect
- 1.5-2.5s fall duration, fade at 80%
- No overlay/checkmark — just pure confetti rain
- Haptic feedback on trigger
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>