Documentation
¶
Overview ¶
Package app wires the aggregator together: it aggregates the active profile's subscriptions across the provider registry and renders the unified feed into the go-widgets UI scene. It is windowing-agnostic — the same App is driven by the CLI (render to PNG / serve) and by the native window front-end.
Index ¶
- Constants
- func AccountsToOptions(base feeds.Options, accts []settings.Account) feeds.Options
- type App
- func (a *App) ActivateAfterVault(onLoaded func())
- func (a *App) AdjustPreviewTextScale(delta float64)
- func (a *App) ApplyAccounts()
- func (a *App) ApplySceneSettings()
- func (a *App) AutoImportSessions()
- func (a *App) ClearDownloads()
- func (a *App) ClearRenderCache()
- func (a *App) DamageRects() []toolkit.Rect
- func (a *App) DeferSceneWrites()
- func (a *App) DeleteProfile(i int)
- func (a *App) Frame() (buf []byte, changed bool)
- func (a *App) ImportFollows(ctx context.Context, kind source.Kind) (int, error)
- func (a *App) ImportRedditSessionFromFirefox() (bool, error)
- func (a *App) ImportRedditSubscriptions(ctx context.Context) (int, error)
- func (a *App) ImportSessionFromFirefox() (bool, error)
- func (a *App) Items() []source.Item
- func (a *App) LaunchRedditSignIn() error
- func (a *App) LoadActiveTab(ctx context.Context) []error
- func (a *App) LoadGroups()
- func (a *App) LoadMore(ctx context.Context) []error
- func (a *App) MarkSubSeen(index int)
- func (a *App) NeedsPresent() bool
- func (a *App) OpenDefaultTab()
- func (a *App) OpenSelected()
- func (a *App) PersistSettings()
- func (a *App) PrefetchImages()
- func (a *App) PrefetchMedia()
- func (a *App) PresentImmediate() bool
- func (a *App) PresentThrottle() bool
- func (a *App) PreviewGroup(base string)
- func (a *App) PullRefresh(ctx context.Context) []error
- func (a *App) ReconstructGroup(base string)
- func (a *App) Refresh(ctx context.Context) []error
- func (a *App) RefreshGroups()
- func (a *App) RefreshStreaming(ctx context.Context) []error
- func (a *App) RenderCacheStats() (pages, bytes int)
- func (a *App) RenderPNG() ([]byte, error)
- func (a *App) RunSearch()
- func (a *App) RunSearchFetch(ctx context.Context, query string, posts bool)
- func (a *App) ScanBrowseGroup()
- func (a *App) Scene() *ui.Scene
- func (a *App) SelectAdjacent(dir int)
- func (a *App) SelectPreview(it source.Item)
- func (a *App) SelectProfile(i int)
- func (a *App) SetBiometricUnlock(v bool)
- func (a *App) SetCommentFetchHook(f func(it source.Item))
- func (a *App) SetCookieFinder(f cookieImporter)
- func (a *App) SetDownloadSync()
- func (a *App) SetGroupStatsFetchHook(f func(name string))
- func (a *App) SetLoadGroupsHook(f func(force bool))
- func (a *App) SetLoadMoreHook(f func())
- func (a *App) SetMediaFetchHook(f func(reqs []ui.MediaRequest))
- func (a *App) SetMediaSync()
- func (a *App) SetPrefetchHook(f func(url string, width int))
- func (a *App) SetPreviewFetchHook(f func(id string, parts []usenet.ReconstructPart))
- func (a *App) SetPullRefreshHook(f func())
- func (a *App) SetReconstructHook(f func(base string))
- func (a *App) SetRefreshHook(f func())
- func (a *App) SetRegistryBuilder(f func(feeds.Options) *source.Registry)
- func (a *App) SetSearchFetchHook(f func(query string, posts bool))
- func (a *App) SetSystemAppearance(dark bool, accent color.RGBA, hasAccent bool, fontTTF []byte)
- func (a *App) SetTheme(osName string, dark bool)
- func (a *App) SetURLOpener(f func(browser, url string) error)
- func (a *App) SetWebFetchHook(f func(target string, width int, created int64))
- func (a *App) SetWebRenderer(r webrender.Renderer)
- func (a *App) SubscribeChannel(src source.Kind, channel string)
- func (a *App) SubscribeGroup(group string)
- func (a *App) SubscribePostSearch(query string)
- func (a *App) ToggleDownload(base string)
- func (a *App) UnsubscribeGroup(group string)
- func (a *App) VM() *viewmodel.ViewModel
- func (a *App) ViewSub(index int)
- type Config
Constants ¶
const PreviewTextStep = 0.1
PreviewTextStep is how much one A−/A+ preview-toolbar tap changes the reader/preview text zoom.
Variables ¶
This section is empty.
Functions ¶
func AccountsToOptions ¶
AccountsToOptions overlays stored per-provider credentials onto base feeds options, mapping each account's well-known field keys onto the matching Options fields. Absent fields leave the base value untouched, so flags still apply where no account overrides them.
Types ¶
type App ¶
type App struct {
// contains filtered or unexported fields
}
App holds the runtime state: the provider registry, the user's persisted settings (profiles/theme/cache), the active profile's subscriptions, and the UI scene they render into.
func (*App) ActivateAfterVault ¶
func (a *App) ActivateAfterVault(onLoaded func())
ActivateAfterVault performs the deferred startup that reads the vault, but keeps the keychain prompt OFF the render thread so the window does not freeze behind it. The blocking keychain read runs on a goroutine; the state changes it enables — reactivating the authenticated providers, importing any missing browser session — are applied on the render thread via post (like every other background result), and onLoaded (also on the render thread) starts the feed load once the providers are live.
When biometric unlock is enabled, the vault read is gated by a LocalAuthentication (Touch ID / device password) prompt run on the same background goroutine. A denial leaves the accounts unauthenticated rather than failing the launch — the reader still opens, just without the sign-ins.
func (*App) AdjustPreviewTextScale ¶
AdjustPreviewTextScale nudges the reader/preview text zoom by delta (the A−/A+ preview-toolbar pills pass ±PreviewTextStep). The scene clamps it to the supported range; the change persists straight away (a pure-display preference, so no theme re-resolve or feed re-aggregation), and the new size is reported on the status line.
func (*App) ApplyAccounts ¶
func (a *App) ApplyAccounts()
ApplyAccounts snapshots the scene's edited accounts into the settings, persists them, rebuilds the provider registry with the new credentials (Reddit switches to session-cookie authentication) while keeping the same HTTP recorder wired so the Network log keeps updating, then re-aggregates the feed. The front-end calls it after the accounts editor is committed.
func (*App) ApplySceneSettings ¶
func (a *App) ApplySceneSettings()
func (*App) AutoImportSessions ¶
func (a *App) AutoImportSessions()
func (*App) ClearDownloads ¶
func (a *App) ClearDownloads()
ClearDownloads removes finished rows from the download panel.
func (*App) ClearRenderCache ¶
func (a *App) ClearRenderCache()
ClearRenderCache empties the web-preview render cache (freeing its pixels) and reports it on the status line. Wired to the Settings view's "Clear render cache" control.
func (*App) DamageRects ¶
DamageRects reports which rectangles of the current frame differ from the one before it, in the framebuffer's pixel coordinates, so a damage-aware host (toolkit.Surface.Damage) presents only what changed instead of re-blitting and re-presenting the whole window. During a streaming load that is the difference between re-presenting a small spinner and re-presenting the entire feed on every animation tick — the reader's single biggest load-time cost.
It is exact by construction: rather than enumerate which widgets animate (and risk freezing one it forgot), it diffs the two frame buffers the double- buffered Frame already keeps, via toolkit.DiffRects. Every changed pixel is inside a returned rectangle, so a host can never miss an update.
It must be called right after Frame, on the same (render) goroutine, so the "current" buffer is the frame Frame just produced. A size mismatch — the tick straddling a resize, when the buffers were reallocated — returns nil, which a host reads as "assume everything changed" and presents in full. Nothing changed returns no rectangles.
func (*App) DeferSceneWrites ¶
func (a *App) DeferSceneWrites()
DeferSceneWrites routes view-model→scene mutations through a render-thread queue instead of applying them inline. The native-window front-end enables it so the background streaming-aggregation goroutine never mutates the scene the render thread is drawing (Frame drains the queue on the render thread). It must be called before the present loop and the aggregation goroutine start, so deferScene is set before any concurrent reader exists.
func (*App) DeleteProfile ¶
DeleteProfile removes the profile at i from the settings editor, re-syncs the active-profile observable with the scene's re-clamped selection, and persists + re-aggregates. Front-ends route the settings editor's delete affordance here.
func (*App) Frame ¶
Frame returns the current framebuffer, redrawing into the back buffer only when the scene's damage sequence has advanced since the last call. While the scene is animating (a refresh is streaming in), it advances the animation clock by the real time elapsed since the last advance, so the moving indicator keeps a steady real-time speed however often the present loop repaints it; when nothing is loading the scene is idle and the damage gate skips the redraw entirely.
func (*App) ImportFollows ¶
ImportFollows pulls every account/subreddit/feed the connected provider for kind follows — via its source.FollowImporter capability — and adds any not-yet-subscribed ones to the active profile, then persists, re-aggregates, and reports on the status line. Each returned source.Subscription carries its own source+channel, so a provider that follows several channel forms (Reddit's r/ subreddits and u/ redditors) imports them all in one action. It requires a connected/authenticated provider; without one it prompts the user to connect an account. The network call runs on the caller's goroutine (the front-end launches it off the render thread) and the scene mutations are applied through a.post so they land on the render thread. The returned count is meaningful only on the inline (CLI/test) path.
func (*App) ImportRedditSessionFromFirefox ¶
ImportRedditSessionFromFirefox lifts the user's logged-in reddit_session cookie out of their Firefox profile and installs it as the Reddit account's session cookie: it writes the value into the accounts editor buffer (so the editor shows it), then commits through ApplyAccounts — persisting the account, rebuilding the provider registry so authenticated reads take effect immediately, and re-aggregating. It reports whether a cookie was imported and surfaces success/failure on the status line. A failure (Firefox not installed, no profile, or not logged into Reddit) leaves the existing credentials untouched.
func (*App) ImportRedditSubscriptions ¶
ImportRedditSubscriptions pulls every subreddit the connected Reddit account follows and adds any not-yet-subscribed ones as r/<name> subscriptions on the active profile, then persists, re-aggregates, and reports on the status line. It requires a connected (session-cookie) Reddit provider; without one it prompts the user to connect an account. It runs the network call on the caller's goroutine (the front-end launches it off the render thread) and applies the scene mutations through a.post so they land on the render thread. The returned count is meaningful only on the inline (CLI/test) path.
func (*App) ImportSessionFromFirefox ¶
ImportSessionFromFirefox lifts the logged-in session cookie of the account currently selected in the editor (Instagram, TikTok or X) out of the user's Firefox profile and installs it as that provider's "session" field, then commits through ApplyAccounts so the authenticated home/following timeline takes effect immediately. Reddit uses its own richer flow (ImportRedditSessionFromFirefox); an unsupported selection is a no-op. It reports whether a cookie was imported and surfaces success/failure on the status line.
func (*App) LaunchRedditSignIn ¶
LaunchRedditSignIn opens Reddit's login page in the browser configured by the SignInBrowser setting, so the user can sign in there and (with Firefox) import the resulting session cookie. It surfaces progress/failure on the status line.
func (*App) LoadActiveTab ¶
LoadActiveTab loads the tab the sidebar currently shows: a single subscription when one is selected (only that source is fetched — the per-tab lazy model), or a full streaming aggregate for the "All" filter. It backs both the initial load and pull-to-refresh, and always re-fetches (unlike the lazy first-visit load).
func (*App) LoadGroups ¶
func (a *App) LoadGroups()
LoadGroups asynchronously fetches the configured Usenet server's full group list into the browse view (served from the provider cache after the first fetch). The front-end calls it when opening the browser.
func (*App) LoadMore ¶
LoadMore fetches the next page of every subscription that still has a live cursor and appends the new, deduplicated items to the shown feed. It is the infinite-scroll seam the scene's OnReachBottom trigger drives. It is a no-op when infinite scroll is off, when a load-more is already in flight (the guard coalesces the burst of at-bottom wheel events), or when no source has an unexhausted cursor. Returns the collected per-subscription failures.
func (*App) MarkSubSeen ¶
MarkSubSeen records a subscription's current high-water marker as seen and persists it, dropping its unseen count to zero — without changing which filter is active. ViewSub uses it when a group is opened; the sidebar context menu's "mark as read" uses it in place, leaving the current view untouched. A bad index or a marker-less sub (e.g. AllFilter) is a no-op.
func (*App) NeedsPresent ¶
NeedsPresent reports whether the next Frame would do anything worth blitting: a background write is queued, or the last frame was still animating or waiting on a debounce. A native present loop that can ask this before it repaints will stop re-blitting an idle, unchanged window every tick. It is safe from the loop's own goroutine (both flags are atomic). It is deliberately conservative about staying dark — appearance changes are only noticed inside Frame, so a gated loop keeps a slow heartbeat rather than trusting this alone.
func (*App) OpenDefaultTab ¶
func (a *App) OpenDefaultTab()
OpenDefaultTab selects the first source as the active tab so a launch loads only that source (the per-tab lazy model) rather than aggregating everything. A no-op with no subscriptions, leaving the "All" filter in place.
func (*App) OpenSelected ¶
func (a *App) OpenSelected()
OpenSelected opens the currently previewed post in the full reading view (the keyboard equivalent of the pane's Open button). A no-op when nothing is selected.
func (*App) PersistSettings ¶
func (a *App) PersistSettings()
PersistSettings snapshots the scene's settings and writes them through the store, WITHOUT re-aggregating the feed. It is the persistence path for pure-display preference changes such as editing the sidebar's virtual folders (which regroup the sidebar but never change what the feed fetches).
func (*App) PrefetchImages ¶
func (a *App) PrefetchImages()
PrefetchImages enqueues every shown Usenet image post for parallel background download, so their thumbnails stream into the feed cards and preview pane. Safe to call repeatedly (it de-duplicates); must run on the UI thread.
func (*App) PrefetchMedia ¶
func (a *App) PrefetchMedia()
PrefetchMedia downloads the remote thumbnails the shown feed is missing and hands each decoded image to the scene. Runs on the UI thread (posted after a refresh), the same thread that drains SetThumb, so the HasThumb reads inside MediaPrefetch are race-free; the downloads themselves are asynchronous.
func (*App) PresentImmediate ¶
PresentImmediate reports that a background scene write is queued, so the next present must not be throttled — the new content has to reach the screen at once. A gated present loop checks it to override its spinner throttle. Safe from the loop's goroutine (the flag is atomic).
func (*App) PresentThrottle ¶
PresentThrottle reports that the only thing animating is the indeterminate loading spinner, whose clock is real-time, so a gated present loop may redraw it below its tick rate to spare the CPU a full-window blit on every tick. It is false the moment anything else needs the full cadence (a queued write, a playing GIF, a web-preview debounce) or nothing is animating at all. Safe from the loop's goroutine (the flag is atomic).
func (*App) PreviewGroup ¶
PreviewGroup previews a Usenet multipart post (a group card, which is not a single Item): it synthesises a preview item and reconstructs the post's image into the pane.
func (*App) PullRefresh ¶
PullRefresh is the pull-to-refresh seam the scene's OnPullRefresh trigger drives. When the feed is filtered to a single subscription (the sidebar has one selected), it re-fetches ONLY that subscription — the one the user is looking at — instead of re-aggregating every source. With no single subscription selected ("All"), it falls back to a full streaming refresh.
func (*App) ReconstructGroup ¶
ReconstructGroup triggers reconstruction of the Usenet post group with the given release base. It is non-blocking: the default hook runs on a goroutine so the UI keeps presenting the loading indicator; tests substitute a synchronous hook.
func (*App) Refresh ¶
Refresh aggregates the active profile's subscriptions (concurrently, newest-first) and loads the merged items into the scene. It splits the failures: every provider that needs sign-in/configuration becomes a clickable in-feed prompt (de-duplicated, in subscription order), while any remaining non-auth failure is shown in the status line.
func (*App) RefreshGroups ¶
func (a *App) RefreshGroups()
RefreshGroups asynchronously re-fetches the group list, bypassing the provider cache so newly-carried groups appear. The browser's Refresh control calls it.
func (*App) RefreshStreaming ¶
RefreshStreaming aggregates the active profile's subscriptions incrementally, updating the scene as each source returns instead of waiting for the slowest. It marks every source pending and sets the scene loading, then on each completed source refreshes the merged feed, clears that source's pending marker, updates the progress counters, and re-derives the auth prompts and status line (in stable subscription order, independent of completion order). When the last source lands it turns loading off. The window front-end uses it so the feed fills in live with a moving indicator; the CLI keeps the blocking [Refresh]. It returns the collected failures (subscription order).
func (*App) RenderCacheStats ¶
RenderCacheStats reports how many pages the render cache holds and their total pixel bytes (shown as the Settings caption).
func (*App) RunSearch ¶
func (a *App) RunSearch()
RunSearch reads the search view's current query + tab on the render thread, marks the view loading, and kicks off the async search through the (test- substitutable) searchFetch seam. A blank query just reports a friendly hint instead of hitting the network.
func (*App) RunSearchFetch ¶
RunSearchFetch performs the active search and delivers the results to the search view on the render thread (via a.post): on the posts tab a Reddit keyword post-search; otherwise channel discovery across EVERY provider that implements source.Searcher (Reddit subreddits, X accounts/hashtags, Instagram accounts/tags, ...), merged into one list — each source.ChannelResult carries its own platform, so a mixed list stays unambiguous. The default searchFetch runs this on its own goroutine; tests call it directly.
func (*App) ScanBrowseGroup ¶
func (a *App) ScanBrowseGroup()
ScanBrowseGroup kicks off a sampled content-mix scan for the newsgroup under the browser's keyboard selection, unless one is already cached. A no-op when the selected row isn't a group. Called after browse-tree navigation.
func (*App) SelectAdjacent ¶
SelectAdjacent moves the feed selection one card up (dir<0) or down (dir>0) through the virtual.CardList and previews the newly selected post — the keyboard equivalent of clicking it, so its image is fetched and it scrolls into view. The preview itself is driven by the feed select hook (installed in New), which the CardList fires on the cursor move; the web-page render is debounced there so holding an arrow key does not render every item passed over.
func (*App) SelectPreview ¶
SelectPreview shows it in the right preview pane. For a Usenet item that carries an image and has not been fetched yet, it kicks off an async reconstruct → decode → SetThumb so the picture appears in the pane.
func (*App) SelectProfile ¶
SelectProfile switches the active profile: it updates the scene (which re-derives the sidebar subscriptions), keeps the view-model's Profile observable in step, and re-aggregates the newly-active profile's feed. The window/CLI front-ends route a profile-tab click here instead of poking the scene, so the active profile flows through the app rather than the view.
func (*App) SetBiometricUnlock ¶
ApplySceneSettings snapshots the scene's edited settings, persists them (when a store is configured), reselects the active profile's subscriptions and theme, and triggers a re-aggregate. Front-ends call it after any profile switch or settings-view edit. SetBiometricUnlock toggles biometric unlock: it records the preference on the scene, then persists and re-applies. Secrets are always stored UNGATED (the SecAccessControl user-presence flag needs a keychain entitlement AMFI rejects on a self-signed build); the preference instead gates the vault READ behind the LAContext prompt in ActivateAfterVault, so the change takes effect on the next launch's read.
func (*App) SetCommentFetchHook ¶
SetCommentFetchHook overrides the async comment fetch (tests use a synchronous variant for determinism).
func (*App) SetCookieFinder ¶
func (a *App) SetCookieFinder(f cookieImporter)
SetCookieFinder overrides the browser-cookie importer (tests inject a fake).
func (*App) SetDownloadSync ¶
func (a *App) SetDownloadSync()
SetDownloadSync makes the file-download manager reconstruct inline instead of in a goroutine — a determinism seam for tests (mirrors SetPreviewFetchHook).
func (*App) SetGroupStatsFetchHook ¶
SetGroupStatsFetchHook overrides the async group scan (tests use a synchronous variant for determinism).
func (*App) SetLoadGroupsHook ¶
SetLoadGroupsHook overrides the asynchronous group-list fetch trigger (tests use a synchronous variant for determinism).
func (*App) SetLoadMoreHook ¶
func (a *App) SetLoadMoreHook(f func())
SetLoadMoreHook overrides the infinite-scroll (OnReachBottom) trigger's async launch (tests use a synchronous variant for determinism).
func (*App) SetMediaFetchHook ¶
func (a *App) SetMediaFetchHook(f func(reqs []ui.MediaRequest))
SetMediaFetchHook overrides the async thumbnail fetch (tests use a synchronous, network-free variant for determinism).
func (*App) SetMediaSync ¶
func (a *App) SetMediaSync()
SetMediaSync makes PrefetchMedia block until every thumbnail has been fetched. The one-shot CLI paths need it: they render (or serialise) the feed the moment Refresh returns, so an asynchronous fetch would always lose the race and emit placeholder-only images. The interactive window keeps the async default, where thumbnails filling in a frame or two later is exactly the wanted behaviour.
func (*App) SetPrefetchHook ¶
SetPrefetchHook overrides the background prefetch trigger (tests use a synchronous variant so a warmed neighbour is populated deterministically).
func (*App) SetPreviewFetchHook ¶
func (a *App) SetPreviewFetchHook(f func(id string, parts []usenet.ReconstructPart))
SetPreviewFetchHook overrides the async image fetch (tests use a synchronous variant for determinism).
func (*App) SetPullRefreshHook ¶
func (a *App) SetPullRefreshHook(f func())
SetPullRefreshHook overrides the pull-to-refresh (OnPullRefresh) trigger's async launch (tests use a synchronous variant for determinism).
func (*App) SetReconstructHook ¶
SetReconstructHook overrides the asynchronous reconstruction trigger (tests use a synchronous variant for determinism).
func (*App) SetRefreshHook ¶
func (a *App) SetRefreshHook(f func())
SetRefreshHook overrides the asynchronous re-aggregate trigger (tests use a synchronous variant for determinism).
func (*App) SetRegistryBuilder ¶
SetRegistryBuilder overrides how ApplyAccounts rebuilds the provider registry (a seam so tests inject a fake registry instead of real providers).
func (*App) SetSearchFetchHook ¶
SetSearchFetchHook overrides the async Reddit search (tests use a synchronous variant for determinism).
func (*App) SetSystemAppearance ¶
SetSystemAppearance applies look-and-feel harvested from the host UI: the effective dark/light mode (honoured only when the user's theme is "system"), the accent colour, and — when fontTTF parses — the system font. Called by the native window back-end at launch and whenever the system appearance changes.
func (*App) SetURLOpener ¶
SetURLOpener installs the browser opener used by browser sign-in flows: f launches a URL in the named browser (default|firefox|chrome|safari|edge). The window front-end wires it to the platform opener; tests inject a fake.
func (*App) SetWebFetchHook ¶
SetWebFetchHook overrides the async page render (tests use a synchronous variant for determinism).
func (*App) SetWebRenderer ¶
SetWebRenderer overrides the page renderer (tests inject a fake that returns a canned image without touching the network).
func (*App) SubscribeChannel ¶
SubscribeChannel adds a channel-discovery result — its handle already carrying the platform form (r/<name>, @account, #tag) — as a subscription on the active profile (dedup + normalization handled by the scene), then persists and re-aggregates. A no-op (already subscribed / no active profile) skips the re-aggregate.
func (*App) SubscribeGroup ¶
SubscribeGroup adds a usenet:<group> subscription to the active profile (when not already present), then persists and re-aggregates so the group appears in the sidebar and feed. A click in the browser routes here.
func (*App) SubscribePostSearch ¶
SubscribePostSearch saves a post keyword search as a live "search:<query>" subscription on the active profile so fresh matches keep flowing into the aggregated feed, then persists and re-aggregates. A blank query, an existing subscription, or no active profile is a no-op.
func (*App) ToggleDownload ¶
ToggleDownload queues (or cancels) the download of the complete Usenet post with the given release base. Parts + the cache dir are captured here on the UI thread so the worker never reads the scene.
func (*App) UnsubscribeGroup ¶
UnsubscribeGroup removes the usenet:<group> subscription from the active profile (when present), then persists and re-aggregates so it leaves the sidebar and feed. Clicking a subscribed group's ✓ in the browser routes here.
type Config ¶
type Config struct {
Registry *source.Registry
// Settings supplies the profiles/theme/cache. When nil, a single "Home"
// profile is synthesized from Subscriptions (the CLI PNG/JSON/serve paths).
Settings *settings.Settings
// Store persists settings edits/profile switches. Optional (nil = no disk).
Store *settings.Store
// Recorder, when set, feeds the scene's Network-log view. It should be the
// same recorder the provider registry logs into.
Recorder *httplog.Recorder
// Subscriptions seeds the synthesized profile when Settings is nil.
Subscriptions []source.Subscription
// Options is the base (flag-derived) feeds configuration. The window path
// rebuilds the registry from Options + Settings.Accounts + Recorder whenever
// accounts change; the CLI paths leave it zero and pass a prebuilt Registry.
Options feeds.Options
Width, Height int
OS string // ui.OSMac | ui.OSLinux | ui.OSWindows
Dark bool
}
Config configures a new App.