Skip to content

[Architecture] Shared state model and incremental SwiftUI app for macOS and iOS #42

Closed as not planned

Description

@mpwg

Proposed decision

A shared SwiftUI presentation with native platform adapters is useful. The recommendation is incremental migration through a shared state and query model. This audit does not justify a complete UI rebuild. This is an architecture proposal, not an additional confirmed product defect or an implementation assignment.

Revision: 7e17aec6406263378f788812d452859016fcd1ed. P2 priority for decoupling and state consistency; replacing working interfaces is secondary.

Concrete starting point

Seventeen Swift packages already share persistence, synchronization, parsing and other services. Platform interfaces instead concentrate selection, navigation, fetching, filtering and presentation in large controllers:

File Lines at the reviewed revision
macOS MainWindowController 1,902
iOS SceneCoordinator 2,760
macOS TimelineViewController 1,438
iOS MainTimelineViewController 1,497
macOS SidebarViewController 1,000
iOS MainFeedCollectionViewController 1,551

These 10,148 lines indicate concentrated responsibilities; file size alone does not prove a defect. Independently established consequences include synchronous UI queries (#12, #41), stale asynchronous results (#33), inconsistent updates (#7, #26) and ownership cycles (#16, #34). SwiftUI alone fixes neither these causes nor database/sync failures (#2–#6, #17–#29, #40).

SwiftUI is already integrated: macOS account hosting, iOS dialog hosting and an Observable view model. The macOS 15 / iOS 18 minimum versions allow Observation and NavigationSplitView without raising deployment requirements for that reason alone.

Target structure

  1. One ReaderSession/TimelineStore model per window or scene, on MainActor with focused Observation adoption. It owns selection, filter/sort/search, navigation, current query generation and typed presentations. Use account-bound stable IDs rather than IndexPath, table row or view instance identity. Mac multiple selection and phone single selection are explicit platform rules.
  2. Shared article queries and filter semantics for the timeline, counts and widgets. Consolidate the existing FetchRequestOperation and platform paths. Explicitly model global settings, feed overrides and window selection. Adopt results only for the still-current query generation; cancellation and stale-result rejection are part of the contract.
  3. Small presentation-ready records and shared commands, with injectable database, sync and article-view services. Do not make the entire Account/Feed graph observable indiscriminately. Lifetimes, Sendable boundaries and status-change events remain explicit.
  4. Clear module boundaries: separate platform-neutral core/HTTP/image processing from AppKit/UIKit, browser integration and NSImage/UIImage adapters. Extract rendering and extraction through testable request/result contracts, limiting both UI dependencies and back references.
  5. Shared SwiftUI views, native platform shells: settings/inspector/forms first, then sidebar, timeline and navigation. Keep WKWebView connected through representables. AppleScript, menu/responder behavior, keyboard, drag and drop, windows, sharing and background execution need appropriate native adapters.

Tradeoffs

Option Benefit Costs and limits
Continue existing controllers Low immediate migration cost, mature platform behavior Separate state/query paths remain
Complete shared SwiftUI rebuild Consistent new structure Large simultaneous regression surface in windows, selection, undo, keyboard, WebKit and restoration; core failures remain without separate repairs
Incremental shared state models and SwiftUI Reuse with verifiable checkpoints; existing platform features remain usable through adapters Temporarily mixed interface; explicit ownership and state contracts required

One shared codebase does not require identical interfaces on iPhone, iPad and Mac. A measured fast native outline/table adapter can remain longer. SwiftUI is not an automatic performance improvement.

Migration sequence

  1. Secure persistence/sync contracts and audit-proven data-loss paths through independent repairs, without assuming a UI rewrite.
  2. Introduce a shared store and article query underneath existing controllers; unify identity, cancellation, events and tests first.
  3. Migrate settings or inspector as a vertical SwiftUI slice using the same store. Do not create another source of truth in local view state.
  4. Connect sidebar/timeline and NavigationSplitView incrementally. Compare existing tables with the same representative datasets.
  5. Remove old state/query paths only after functional and measured acceptance.

Upstream context — read only

  • PR #3154 was merged into experimental swiftui3, not main. PR #3152 closed unmerged. Neither establishes an already-shipped unified app.
  • PR #3778 merged into main and is a concrete precedent for settings/inspector migration.
  • ArticleFilter #3777 includes explicit agreement to implement it; #4894, #4113 and #4111 align with shared queries and renderer/extractor boundaries.
  • Open PRs #5316, #5409, #5342, #5149 and #5336 supply partial ideas, not a complete design suitable without adaptation. The separate PR assessment documents regressions and dependencies.
  • Changes already present locally, including static AppDefaults (#5503), reused settings hosting (#5434) and SQL IN queries (#5428), are not treated as outstanding architecture defects.

Acceptance for later implementation

  • Stable-ID selection and restoration across account changes, renaming and folder changes.
  • Rapid A→B→C with deliberately delayed responses shows only C; no blocking fetch during menu construction.
  • Two Mac windows have independent selection, search and sheets. Undo, initial account selection, toolbar overflow and iPad popovers work.
  • Consistent read/unread/starred, search, global filter, feed override, count and widget results.
  • Reader View, scroll position, WebKit media and extraction behave correctly across selection changes and closing.
  • Actually verify VoiceOver, Dynamic Type, keyboard, multiple selection and drag and drop on target platforms.
  • Measure large feeds and many accounts against a documented baseline: menu response, switching, scrolling, memory and background load.

This acceptance was not executed for a new SwiftUI design; no such rebuild existed in the audit.

Primary sources for the framework strategy

Apple explicitly describes incremental integration and shared data models in Use SwiftUI with AppKit and UIKit. Also see AppKit integration and TN3154: Adopting SwiftUI navigation split view. The recommendation above is an assessment of this repository, not a guarantee of framework parity for every existing feature.


Adopted fork architecture — 2026-10-24

For #45, the owner explicitly chose a substantial core rebuild with SwiftData and a shared SwiftUI app starting at OS 27. FreshRSS is the only V1 provider; installation is separate, there is no legacy database migration, and intelligence settings are per device. The earlier incremental-migration proposal beneath existing controllers is therefore not pursued. Its still-valid identity/async/scene contracts inform #47–#52. This issue's strategy is superseded, not implemented.

Later source retirement — 2026-10-25

The original and superseded source identified by the audit was removed at e61b2fa26 under explicit owner authorization. This does not repair the historical implementation or accept unfinished Reader requirements. Retirement inventory and evidence.

Activity

  1. changed the title [-][Architektur] Gemeinsames Zustandsmodell und schrittweise SwiftUI-App für macOS und iOS[/-] [+][Architecture] Shared state model and incremental SwiftUI app for macOS and iOS[/+] on Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions