Feddy Docs

Common Flows

Submit feedback, surface the roadmap, build a custom UI on top of the async API, and handle errors. Covers RequestComposeView, RequestListView, programmatic submit / fetch / vote / comment, and FeddyError patterns.

Once the SDK is installed and configured, this page covers the four patterns most apps need: collecting feedback, displaying the roadmap, building custom UI on top of the async API, and handling errors.

Submit feedback

Default — RequestComposeView

The bundled feedback sheet is fully localized in 7 languages (en / zh-Hans / zh-Hant / es / ja / de / fr, auto-picked from the device locale) and handles validation, board picking, async submit, and the success state itself.

import SwiftUI
import Feddy

struct ProfileView: View {
    @State private var showFeedback = false

    var body: some View {
        Button("Send Feedback") {
            showFeedback = true
        }
        .sheet(isPresented: $showFeedback) {
            RequestComposeView()
        }
    }
}

By default the sheet shows the workspace's two system boards — Feature and Bug.

Custom workspace boards

Pass boards explicitly to surface ones you've added under dashboard.feddy.app/w/<ws>/boards. You're responsible for the display name's localization — the SDK does not know about your custom boards:

.sheet(isPresented: $showFeedback) {
    RequestComposeView(boards: [
        .featureRequest,                       // SDK-localized "Feature"
        .bugReport,                            // SDK-localized "Bug"
        .init(
            key: "discussions",                // matches dashboard board.key
            name: NSLocalizedString("Discussions", comment: "")
        ),
    ])
}

Programmatic submit

If you have your own UI, call Feddy.submitRequest(...) directly. Like all writes, it's synchronous, fire-and-forget, and never throws.

Feddy.submitRequest(title: "Add dark mode")
Feddy.submitRequest(
    title: "Crash on launch",
    description: "Happens after entering passcode on iPhone 15 Pro / iOS 17.4"
)
Feddy.submitRequest(
    title: "Confusing onboarding step 3",
    boardKey: "ux-research"
)

The two system boards every workspace ships with are "features" and "bugs". Omit boardKey to land in the workspace's primary board.

Surface the roadmap

Default — RequestListView

Drop-in roadmap viewer with paginated list, board picker, pull-to-refresh, inline upvote, and tap-to-detail navigation.

.sheet(isPresented: $showRoadmap) {
    RequestListView()
}

Restricting boards

Restrict to specific boards if you want a subset:

RequestListView(boards: [
    .featureRequest,
    .init(
        key: "discussions",
        name: NSLocalizedString("Discussions", comment: "")
    ),
])

The board picker only shows the boards you pass.

Custom UI — async fetch

When RequestListView doesn't fit (custom theming, embedded list, custom empty state), build your own UI on top of the async read methods.

let page = try await Feddy.fetchRequests(boardKey: "features", limit: 20)
for item in page.items {
    print(item.title, item.voteCount, item.attachments.count)
}

// Single request detail (with attachments + official reply)
let detail = try await Feddy.fetchRequest(id: "req_xyz")

Vote and comment

All vote / comment methods are async throws.

Toggle a vote

The server is the source of truth — upvote returns the post-toggle state so you can reconcile against an optimistic UI update:

let state = try await Feddy.upvote(requestId: "req_xyz")
print("voted=\(state.voted) total=\(state.voteCount)")

Read comments

Oldest-first, paginated:

let thread = try await Feddy.fetchComments(requestId: "req_xyz", limit: 50)

Append a comment

let posted = try await Feddy.addComment(
    requestId: "req_xyz",
    body: "Looking forward to this!"
)

The returned posted is the new comment, so you can append to your local thread without re-fetching.

Handle errors

Read methods throw FeddyError. Switch on the cases for typed handling:

public enum FeddyError: Error {
    case network
    case http(status: Int, code: String, message: String)
}
CaseWhenWhat to do
.networkDNS / TLS / offline / timeoutShow retry UI; the user's network is unhappy.
.http(status:code:message:)Server responded with 4xx / 5xxInspect code for typed handling.

The code field is a stable string contract — values like rate_limited, invalid_request, not_found. Match on it rather than parsing message:

do {
    let page = try await Feddy.fetchRequests(boardKey: "features")
    // ...
} catch FeddyError.network {
    showOfflineToast()
} catch FeddyError.http(_, let code, let message) {
    switch code {
    case "rate_limited":
        scheduleRetry(after: .seconds(30))
    case "invalid_request":
        log.error("invalid request: \(message)")
    default:
        showGenericError(message)
    }
}

Offline behavior (writes)

Writes — submitRequest(...) — never throw. On network failure or 5xx the payload is persisted to a local FIFO queue and replayed on the next Feddy.configure(...) call. 4xx responses are dropped with a console log (replaying bad payloads would loop forever).

The queue is bounded at 100 entries and survives app restarts. The host app does not need to manage it.

When to choose programmatic over the bundled views

Use casePick
Roadmap fits a sheet with default themingRequestListView
Embedded inside another screen / custom themeProgrammatic fetchRequests
Different empty state, hero, or onboardingProgrammatic
Showing votes / comments inline elsewhereProgrammatic
Minimal code, no design workBundled views

On this page