The screens
Every screen the SDK draws, and the line that shows each one.
Every screen takes the hub you built, and every screen belongs inside a NavigationStack your app owns — that stack is what gives it a title bar, a search field, and somewhere to push a request's detail onto.
| Screen | What a person does with it |
|---|---|
DifferentRequestsView(hub:) | The board: ranked by demand, searchable, filterable, paged, votable, and the way in to asking. |
RequestDetailView(hub:requestID:) | One request, its thread, its vote and its follow. Open it straight from a notification. |
RoadmapView(hub:) | Planned, building and shipped, a section per column. |
ChangelogView(hub:) | What shipped, newest first. |
InboxView(hub:) | Status changes, replies and duplicates on what this person follows. |
SubmitRequestView(hub:) | The composer on its own. Call hub.beginSubmission() before you present it. |
VoteControl(voteCount:voted:isWriting:toggle:) | The vote button for your own rows. isWriting makes it go inert while a vote is in flight. |
StatusBadge(state:) | The colored status pill, for your own rows. |
What every screen already handles
Each one draws four outcomes from its own state: reading, failed, read and empty, and read with something to show. A vote, a follow, a comment or a notification marked read that the server refuses puts a line on screen next to the control that was tapped. Nothing fails quietly, and you do not write any of it.
Hide what this app does not include
The roadmap and the changelog are Pro. Both screens gate themselves, so a deep link shows a screen saying what is there instead of a refusal — but an absent tab is a better answer than a tab that explains itself. Build your tabs from what the app config says.
let config = try await requests.client.config().config
if config.roadmapEnabled {
Tab("Roadmap", systemImage: "map") { RoadmapView(hub: requests) }
}