Skip to content

feat: Feed core — env-provisioned, input-driven paging/search engine - #233

Open
danieltmbr wants to merge 1 commit into
mainfrom
feature/feed-core
Open

feat: Feed core — env-provisioned, input-driven paging/search engine#233
danieltmbr wants to merge 1 commit into
mainfrom
feature/feed-core

Conversation

@danieltmbr

Copy link
Copy Markdown
Owner

Summary

Foundational layer for offline-first paged feeds (stack 1/4 — see #229 for the full reference).

  • PageableQuery — the domain query drives the feed: isSearch/cursor/page(after:) conformances on PostsQuery/QuotesQuery/CatalogueSearchQuery, plus an input-less CatalogueSyncQuery for the SyncGroup-backed catalogue refresh
  • FeedModelfetch(input) is the one entry point; the input alone decides browse vs search vs continuation. Page-1 requests fire immediately and supersede in-flight ones; superseded callers converge on the winner's receipt so late resumptions can't clobber newer commits. Continuations fail quietly. Committed fetches return FeedPage receipts — the owning view keeps paging state
  • FeedConfiguration + .feed(_:) — one call provisions a subtree: owns the model, wires the pipeline from apiBaseURL/urlSession/modelContext, persists the freshness stamp, scopes model/state/actions
  • Self-contained componentsFeedStatusLine/FeedLoadMoreCell/FeedEmptyView read \.feedState + action keys, nothing through inits
  • tmbr-core DTO upgrades (Hashable, page(after:), languages/sources params), closure-based RequestLoader, descriptor-rename fixes, test-debt fixes

Tabs are intentionally untouched here — they migrate in the stacked follow-ups (blog → quotes → catalogue). Two surgical patches keep the old tabs compiling next to the new core (LoadingState moved to FeedState; a secondaryInfo unwrap in the old CatalogueTab).

Verification

  • swift test --package-path app-core — 65 tests green (new FeedModel/FeedSeam suites + existing suites)
  • Reader scheme builds (macOS)

🤖 Generated with Claude Code

The foundational layer for offline-first paged feeds. One call — .feed(.posts) —
owns a FeedModel, wires its fetch pipeline from the environment, and scopes the
model + FeedState + control actions for the subtree; children read the model
back with @Feed(PostsQuery.self).

- PageableQuery: the domain query itself drives the feed (isSearch/cursor/
  page(after:)); conformances for Posts/Quotes/CatalogueSearch queries and an
  input-less CatalogueSyncQuery for the SyncGroup-backed catalogue refresh
- FeedModel: fetch(input) is the one entry point — the input decides browse vs
  search vs continuation; page-1 supersedes in-flight requests and superseded
  callers converge on the winner's receipt (race-proof); continuations fail
  quietly; returns FeedPage receipts so the owning view keeps paging state
- FeedConfiguration: label + composed fetch pipeline (loader → upsert →
  nextCursor), with real pipelines as statics and stub injection for previews
- Self-contained components: FeedStatusLine, FeedLoadMoreCell, FeedEmptyView
  read \.feedState + \.loadFeed/\.retryFeed — nothing passed through inits
- tmbr-core: query DTOs gain Hashable, page(after:), languages/sources params,
  and view-facing convenience inits; closure-based RequestLoader in app-api
- Groundwork: CatalogueItemSyncDescriptor rename + .book descriptor fix,
  CatalogueSearchEngine, detail-section pre-work, test-target dependency and
  container-lifetime fixes

Tabs are intentionally untouched — they migrate in the stacked follow-ups
(blog → quotes → catalogue).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant