Implementation reference · macOS 14+
Pingo
Pingo is a native macOS menu bar scratchpad for small HTTP requests. It validates the target, sends the request with URLSession, and keeps the response summary, headers, and text preview in one compact window.
Problem
Small requests without a full API workspace
A quick endpoint check still needs method selection, headers, an optional payload, clear transport state, and enough of the response to diagnose the result. Pingo keeps that loop in a menu bar window while bounding the amount of response data retained for the interface.
Implementation scope
From editable request to response preview
A MenuBarExtra owns one ScratchpadViewModel backed by URLSessionAPIRequestService. The view model validates the URL, parses line-based headers, creates an APIRequest, and starts a cancellable task. The service streams the response with URLSession.bytes(for:), retains at most 512 KiB for display, and returns status, headers, duration, byte counts, and UTF-8 state for presentation.
Runtime flow
From request fields to bounded output
UI state, request coordination, transport work, and response presentation each have a narrow responsibility. Cancellation crosses the view-model and service boundary through the retained task, while the display limit is enforced as the response bytes arrive.
01
Request input
Collect the fields
Method, HTTP(S) URL, line-based headers, and an optional UTF-8 body.
Output
Editable SwiftUI state
02
View model
Validate and prepare
Reject invalid targets, parse headers, prevent concurrent sends, and retain the task.
Output
APIRequest
03
Request service
Send and stream
Build URLRequest, apply timeouts, call URLSession.bytes(for:), and accept cancellation.
Output
Bounded response bytes
04
Response output
Summarize for display
Expose status, duration, content type, sorted headers, and at most 512 KiB of UTF-8 text.
Output
APIResponse + UI state
The essential loop
Stream a bounded response preview
The request service streams bytes from URLSession and stops retaining data at the display limit. The returned model records whether the response was truncated and whether the retained bytes form valid UTF-8 text.
let startDate = Date()
let (bytes, response) = try await session.bytes(for: urlRequest)
let httpResponse = response as? HTTPURLResponse
let bodyByteCount = httpResponse.flatMap(Self.contentLength)
let bodyData = try await displayedBodyData(from: bytes)
let duration = Date().timeIntervalSince(startDate)
let body = Self.utf8String(
from: bodyData.data,
repairingTruncatedData: bodyData.isTruncated
)
let isBodyTruncated = bodyData.isTruncated
|| bodyByteCount.map { $0 > bodyData.data.count } == true
return APIResponse(
statusCode: httpResponse?.statusCode ?? 0,
headers: httpResponse?.allHeaderFields ?? [:],
body: body ?? "",
bodyByteCount: bodyByteCount,
displayedBodyByteCount: bodyData.data.count,
isBodyTruncated: isBodyTruncated,
isBodyText: body != nil,
duration: duration
)Implementation notes
Cancellable state and bounded streaming
PingoApp presents a window-style MenuBarExtra and keeps a single ScratchpadViewModel in SwiftUI state. The app is an LSUIElement, uses App Sandbox with outgoing network access, and has no primary application window.
ScratchpadViewModel owns editable request state and presentation strings. It prevents concurrent sends, retains the current request task for cancellation, and maps cancellation, timeout, validation, and other errors to distinct UI states.
URLSessionAPIRequestService converts the value-type APIRequest into URLRequest, applies headers and an optional UTF-8 body, and measures the request around URLSession.bytes(for:).
The response stream is consumed one byte at a time until completion or the 512 KiB display limit. Truncated UTF-8 data is repaired only by removing up to four incomplete trailing bytes; otherwise non-text data is reported instead of decoded.
Sparkle starts with the app and provides update checks from the menu. Its feed URL and public verification key are stored in the app configuration.