Good First Issues in Swift
Explore curated starter issues in high-scoring Swift repositories. Every listed issue belongs to a welcoming repository scored by C-Rank™ on PR merge rates, review responsiveness, and first-timer acceptance.
Arm Issue Sniper for fresh Swift beginner issues
Beginner issues in welcoming S/A-Tier repositories get snatched within 15 minutes. Receive instant alerts on Discord, Telegram, or Email the minute a maintainer opens a new task.
Live Good First Issues (30)
Would it be possible to use iPhone shortcuts to see how long a Profile session has already been active for?, and how long is left in the session? I’d really like a way to give myself pauses that are only allowed after a certain amount of time in the session has passed.
.github/workflows/ci.yml filters on: paths: - "Sources/**" - "Tests/**" - "Example/**" - ".github/workflows/**" - "Package.swift" Scripts/ isn't listed, but it holds the snapshot contract: Scripts/snapshot-env.sh pins the reference simulator (iPhone 16 / iOS 26.2) and the CI Xcode, and carries the comment "Change ONLY via a PR that also re-records every image". Scripts/test.sh controls how the suite is invoked, including the deliberate -parallel-testing-enabled NO. A PR touching only Scripts/ skips CI entirely, so nothing enforces that rule at exactly the moment it matters most. Suggested fix Add - "Scripts/**" to both the push and pull_request path filters. Found while reviewing #47 (v3.0.0). Not blocking the release.
Sources/NeoBrutalism/Components/Dialog.swift:17-21: /// Tapping the scrim dismisses the dialog; actions must call /// your binding manually (just like SwiftUI's `alert` does). To apply destructive /// styling to a button, pass `.buttonStyle(.neoBrutalism(type: .neutral))` with a /// red-tinted theme, or document the limitation for your consumers. "…or document the limitation for your consumers" is an instruction to whoever was writing the library, not guidance for someone using it. It ships to the hosted DocC page for nbDialog. Worth rewriting that sentence to just state the limitation: Button(role: .destructive) inside nbDialog doesn't get destructive styling, and here's what to do about it. Found while reviewing #47 (v3.0.0). Not blocking the release.
Sources/NeoBrutalism/Components/Menu/NBMenu.swift:193: let screenHeight = UIScreen.main.bounds.height let scrollCap = screenHeight * 0.6 UIScreen.main is not scene-aware — it reports the device screen, not the window the menu is actually in — so the 60% cap is wrong under iPad Split View / Slide Over, in a resized window, or on an external display. It's also deprecated. The value is already available: NBMenuOverlayContent.body wraps everything in a GeometryReader whose proxy.size is passed to placement(in:) a few lines above. Suggested fix Thread that same size into dropdown(elevated:) instead of reaching for UIScreen.main. Found while reviewing #47 (v3.0.0). Not blocking the release.
NBGaugeStyle.makeBody reads only configuration.value and configuration.label: public func makeBody(configuration: Configuration) -> some View { let value = configuration.value HStack(spacing: theme.spacing) { configuration.label NBBarMeter(fraction: value) … } } — Sources/NeoBrutalism/Components/Gauge/GaugeStyle.swift So a Gauge built with any of the value labels silently loses them: Gauge(value: temp, in: 0...100) { Text("Temp") } currentValueLabel: { Text("\(temp)°") // never rendered } minimumValueLabel: { Text("0") // never rendered } maximumValueLabel: { Text("100") // never rendered } Related: #3 asks for the same thing on NBProgressViewStyle, so the two could share a layout decision. NBGaugeStyle's static var neoBrutalism is also the only style entry point in the library without a doc comment — worth adding in the same pass. Found while reviewing #47 (v3.0.0). Not blocking the rele
NBSegmentBuilder only implements the variadic buildBlock: @resultBuilder public struct NBSegmentBuilder<Value: Hashable> { public static func buildBlock(_ components: NBSegmentItem<Value>...) -> [NBSegmentItem<Value>] { components } } — Sources/NeoBrutalism/Components/SegmentedPicker/NBSegmentedPicker.swift So neither of these compiles inside an NBSegmentedPicker: NBSegmentedPicker(selection: $size) { ForEach(sizes) { Text($0.name).nbSegment($0) } // ✗ if showExtras { Text("XL").nbSegment(3) } // ✗ } NBMenuBuilder and NBTabBuilder in the same release both implement buildExpression / buildArray / buildOptional / buildEither, so this is an inconsistency between three builders that otherwise look alike. Suggested fix Mirror NBTabBuilder — it's the closest analogue and can be copied nearly verbatim. Found while reviewing #47 (v3.0.0). Not blocking the release.
If the cursor is at the end of a non-empty line and you tap the "spoiler" button, it adds the spoiler tag on the current line, which is invalid. It should make a new line for it instead in this case.
The iOS companion is killed by the kernel when a terminal surface is torn down. Two TestFlight crash reports carry the full log, and both are the same: Exception Type: EXC_GUARD (SIGKILL) Exception Subtype: GUARD_TYPE_FD Exception Message: CLOSE on file descriptor 0 Termination Reason: GUARD 0x4000000100000000 Something calls close() on fd 0. iOS guards stdin/stdout/stderr, so the process is killed outright — no exception to catch, no chance to recover. That is why the Mac side sees every companion socket for the phone drop in the same millisecond. Stack The iPad report symbolicates cleanly and pins the path: -[UIView removeFromSuperview] → UITerminalView.didMoveToWindow() UITerminalView+Lifecycle.swift:70 → TerminalSurfaceCoordinator.freeSurface() :361 → TerminalSurfaceCoordinator.tearDownSurface(removingBridgeFrom:) :386 → TerminalSurface.free()
Received the below as an App Store TestFlight Bug Report. App Version / Build v1.0.9 Build 300 What happened? I am unable to see transactions from my Wallet when trying to import. I was able to set up the shortcut feature to add transactions, but I have not been able to get the import feature to work. Steps to Reproduce N/A Screenshots / Additional Context
On some Macs the toolbar buttons — the navigator toggle, the sort pull-down, +, and the inspector toggle — draw as bare glyphs with no capsule behind them. On other Macs the same buttons carry the Liquid Glass capsule. Same source, sometimes even the same machine running two builds side by side. Nobody has reproduced this on a machine we control, so this issue is a call for data rather than a diagnosis. If your toolbar buttons have no capsule, please post the output of the two commands below. Screenshots What we know The buttons are plain NSToolbarItems with isBordered = true (Sources/termio/App/App.swift, the .toggleNavigator / .sortProjects / .newTerminal / .toggleInspector cases). The capsule is drawn by AppKit, not by us — we never paint it, and there is no setting that turns it off. AppKit decides that chrome from two numbers: the macOS version the app runs on and the SDK version the binary was linked against. Our own code only branches at runtime (if #available(macOS 26,
macOS 26.5.1, MacBook Pro M2 Pro, built-in display with a physical notch Talkify 0.6.0 Right ⌘, held Dictated into Brave in native full screen Dictation works and the text is inserted, but the HUD never appears. Leave full screen and it shows normally from the notch on the same display. Possibly relevant: HUDPanel.swift:30 sets .fullScreenAuxiliary, which only admits a window to the full-screen Space of a full-screen window in the same app, and .canJoinAllSpaces does not cover another app's full-screen Space. Not verified. Diagnosis produced with Claude Code.
This would remove the need to scroll to access it. I'm guessing this one is used quite frequently, and should get higher priority as a result It could go just after the undo/redo buttons, maybe with a divider following it
LocalAdditionalUsageReader.kiroEntries (Sources/PokeTokenBar/Core/LocalAdditionalUsageProvider.swift:800) re-reads and re-parses every stored Kiro conversation on every poll. Unlike the Cursor and Copilot readers, it has no watermark to advance — Kiro rewrites a conversation's whole history JSON in place on each turn and deletes turns outright on /clear, so there is no monotonic row id to resume from. That was the right call for #170; the cost of it is what this issue is about. Measured here on a synthetic database of 30 conversations x 80 turns (user 500 B, assistant 2 KB per turn), 6.3 MB on disk: rescan : 209–360 ms (2400 entries) empty window : 37 ms (0 entries, `modifiedSince` past every turn) The 37 ms floor is the point: modifiedSince is applied inside kiroTurnEntries (:860), after the row has been read out of SQLite and jsonObject has parsed it. Narrowing the window skips entry construction, not the read or the parse. In normal operation since is the s
Every provider resolves its log location on its own, and only Claude can be pointed somewhere else from inside the app. #162 adds a settings field for extra Claude scan folders; this issue tracks extending that to the rest. Where each provider looks today Provider Location Override Claude computed multi-root CLAUDE_CONFIG_DIR + settings field (#162) OpenCode ~/.local/share/opencode OPENCODE_DATA_DIR Hermes ~/.hermes HERMES_HOME Copilot ~/.copilot COPILOT_HOME Grok ~/.grok/sessions GROK_HOME Codex ~/.codex/sessions none Gemini ~/.gemini/tmp none Antigravity ~/.gemini/antigravity-cli/conversations none Cursor ~/Library/Application Support/Cursor/User/globalStorage none Environment overrides are now read through the login shell (#176), so they work from a GUI-launched .app. What remains is that four providers have no override at all, and that an environment variable is an awkward thing to ask a menu bar app's user for. Why this is worth doing The failure mode is
UpdateChecker.applyUpdate() calls NSApp.terminate(nil) (UpdateChecker.swift:66), which ends the instance whose popover was used. The detached script then waits for the app to be gone (UpdateChecker.swift:123): for i in $(seq 1 40); do pgrep -x PokeTokenBar >/dev/null 2>&1 || break; sleep 0.5; done pgrep -x matches on process name, so it matches any instance. With one instance the loop breaks in well under a second, which is the case it was written for. With two, it can never break: the surviving instance keeps the name alive, so the wait always burns its full 20s and falls through. Two things then follow, both of which the wait was there to prevent (:116): brew upgrade --cask replaces the bundle underneath a still-running app — the bundle-replacement race the loop was added to avoid. launchctl kickstart -k (:130) starts a new instance. If the survivor is the launchd job's process, -k replaces it and the update comes up. If the survivor is the GUI-launched one — which is the case w
AppLog.write hands the line to a background queue (AppLog.swift:28) and returns. When the caller is on its way out of the process, the line races the teardown and is frequently lost. The site that matters today is CrashReporter.markClean() (CrashReporter.swift:65-68), which runs from the NSApplication.willTerminateNotification observer registered in install() (:45-47). AppKit posts that notification and then exits, so clean shutdown is written only if the log queue happens to get scheduled first. Measured with an AppKit app reproducing the sequence — enqueue on a serial DispatchQueue, then NSApp.terminate(nil): runs=100 landed=58 lost=42 Consistent with what is on disk here: the current 1.2M PokeTokenBar.log has 1 launch: line and 0 clean shutdown lines; the rotated 2.0M PokeTokenBar.old.log has 1 and 1. The consequence is not just a missing line. clean shutdown is the human-readable half of the shutdown record, so the log reads as if sessions end abruptly even when they don't. The
What startOutsideClickMonitor() (Sources/PokeTokenBar/PokeTokenBarApp.swift:375) overwrites outsideClickMonitor without removing whatever token is already stored: private func startOutsideClickMonitor() { outsideClickMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown]) { ... } } Start and stop are not symmetric either: registration happens at the call site (togglePopover(), PokeTokenBarApp.swift:360) while removal happens in the delegate callback (popoverDidClose, :368). Why it matters Nothing is broken today. togglePopover() is the only path that calls popover.show, and openPopover() (:336) guards on popover.isShown, so the monitor cannot be registered twice. The point is what happens when a second show path is added: the previous token is dropped on the floor, NSEvent.removeMonitor is never called for it, and a global mouse monitor stays installed for the rest of the process — outliving the popover it was meant to track. That failure is silen
What testLocalizedAlertBubbleFitsDefaultPanel (Tests/PokeTokenBarTests/PerformanceTests.swift:204) is the guard that keeps alert copy from being clipped in the floating pet's speech bubble — it exists because Japanese copy once clipped by ~9pt (#124). It measures with FloatingPetController.measureSpeechBubble (Sources/PokeTokenBar/UI/FloatingPetPanel.swift:112), which lays the text out with no height constraint (height: 10_000) and then asserts the result fits bubbleHeadroom (72pt, minus 2). The view it is guarding renders with .lineLimit(2) (FloatingPetPanel.swift:417). So the guard answers "does the bubble overflow the panel?" while the view's actual failure mode is "does the copy exceed two lines and get truncated?" Those are different thresholds, and the gap is one line wide. Evidence Reproducing measureSpeechBubble's math on a 164pt content column (bubbleMinWidth 180 − bubbleHorizontalPadding 8 × 2), title Límite inminente at 11pt bold, body at 10pt: body measured height
Background / 背景 Batch delay testing is currently issued one node at a time, while the core has a whole-group endpoint /group/{name}/delay. MihomoAPIClient does not implement it yet. See roadmap item A6 (ROADMAP.md, 中文). 目前批量测速是逐节点打的,而内核有整组接口 /group/{name}/delay,MihomoAPIClient 还没实现。见路线图 A6。 Scope of this issue is the API client method and its tests only. Switching the batch-testing flow over to it is not part of this issue — see "Why the rewiring is out of scope" below. 本 issue 的范围只有 API 客户端方法及其测试。 把批量测速流程切换过去不属于本 issue——原因见下面"为什么切换不在范围内"。 Verified API contract / 已实测的接口约定 Probed against the bundled core (Resources/Core/mihomo, v1.19.29) using a select group holding one reachable node (DIRECT) and two dead nodes: 针对随包内核实测(v1.19.29),用一个含 1 个可达节点(DIRECT)和 2
Background / 背景 A stale fake-ip mapping currently can only be cleared by restarting the core. Mihomo exposes /cache/fakeip/flush for exactly this, and MihomoAPIClient does not implement it yet. See roadmap item A3 (ROADMAP.md, 中文). 目前 fake-ip 映射脏了只能靠重启内核清除。Mihomo 本来就有 /cache/fakeip/flush 这个接口,而 MihomoAPIClient 还没实现。见路线图 A3。 Scope of this issue is the API client method and its tests only. Surfacing it as a diagnosis fix action (the rest of A3) is deliberately not part of this issue. 本 issue 的范围只有 API 客户端方法及其测试。 把它做成诊断里的修复动作(A3 的其余部分)刻意不包含在内。 This is the smallest of the three API-client issues — a good one to pick if it is your first change here. 这是三个 API 客户端 issue 里最小的一个,如果是你在本仓库的第一次改动,推荐从这个开始。 Verified API contract / 已实测的接口约定 Probed
Background / 背景 MihomoAPIClient covers most of the Mihomo RESTful API, but /dns/query is missing. Without it the app can show rules and the exit IP, but not what DNS actually returned — a hole in the middle of the routing story. See roadmap item A2 (ROADMAP.md, 中文). MihomoAPIClient 已经覆盖了 Mihomo RESTful API 的大部分,但缺 /dns/query。缺了它,应用能展示规则和出口 IP,却展示不了 DNS 实际返回了什么。见路线图 A2。 Scope of this issue is the API client method and its tests only. The DNS panel UI described in A2 is deliberately not part of this issue. 本 issue 的范围只有 API 客户端方法及其测试。 A2 里描述的 DNS 面板 UI 刻意不包含在内。 Verified API contract / 已实测的接口约定 Probed against the bundled core (Resources/Core/mihomo, v1.19.29), so these are real responses, not guesses: 针对随包内核实测(v1.19.29),以下是真实返回,不是猜的: GET /dns/query?name=example.com&t
Hi, I would love to be able to quick start a profile By long pressing the app icon. User story: As a user I wan to start a profile with as few clicks as possible and as fast as possible. I would expect a long press on the app icon to open a context menu which says "Start <profile_name>" Currently there is no context menu implemented:
At the moment, there's a single trailing "mark read" swipe action. It does not follow the inbox swipe configuration
I can't discern a difference between 'fast' and 'instant', and all of them are too fast for my liking. If not a millisecond value selector, then maybe a 'medium' setting halfway between 'normal' and 'fast'? Thanks for making this.
Required Reading Confirmed SDK Version 4.1.8 Mobile operating-system(s) iOS Android Device Manufacturer(s) and Model(s) Google Pixel-5, Google Pixel-7, Google Pixel-9, Samsung Device operating-systems(s) 12+ Build tooling versions Android Gradle Plugin Version : 9.2.1, Gradle Version: 9.6.1 What happened? Core Issues Summary Inconsistent 3–5 Second Location Delivery on Android: Our app logic relies on the SDK to emit a location update every 3 to 5 seconds so we can aggregate and persist a record every 10 seconds (~6 records per minute / 60 records per 10 minutes). On Android, the SDK skips intervals and drops location fixes, producing 15–30+ second gaps. Activity Recognition Freezes in Previous State on Android: When an Android device halts for longer than 3 minutes, the SDK enters the stationary state. Upon moving again (e.g., riding a bike at 30–40 km/h), the SDK continues emitting stationary/still as the active triggerActivity instead of transitioning. Conversely, when
The problem Settings ▸ Usage only reads four agents: Claude Code, Codex, Kimi, and Grok. Every other agent termio can launch — cursor-agent, Amp, OpenCode, Antigravity, Hermes, Prime — shows nothing, even when its CLI is installed and logged in. What you'd want instead One UsageProvider per additional agent, so the Usage tab reads its plan limits and token totals the same way it reads the four it already supports. How Usage is wired Sources/termio/Companion/Usage/ is one file per agent. Each provider holds its own credential read, its own plan-limit endpoint, and its own session-log scanner; UsageMonitor knows only the list. Adding an agent is a new file plus one line — not an edit threaded through the fetch and scan paths. protocol UsageProvider: Sendable { var agent: AgentPreset { get } func planLimits(allowKeychain: Bool) async -> PlanLimitReading func tokenUsage(in windows: DateWindows) -> AgentTokenUsage } UsageProvider's protocol extension already gives you th
Before continuing, please confirm the following I have read the FAQ and searched for my issue. Describe the bug It’s so difficult to type in Reynard browser without errors. For some reason, the 3 suggestions the keyboard offers just keeps flashing (so they usually can’t be selected) indicating as though it’s processing something using the CPU in the background. The cursor many times lags behind the input. Tapping to reposition cursor usually is difficult then when trying to go back to correct typos. It’s almost as though it’s using a different keyboard compared to other parts of iOS where you need to type. I’m not sure if it’s my old non-powerful iPhone 6S, or because it’s iOS 15, but I’ve not experienced this kinda typing issues on any app in all my years of using this phone. I’ve waited to see if someone else has been having similar issues, but it appears no one uses these older phones anymore. Scrolling and other performance issues have been getting better and
Instant app switch [cmd+tab] (between spaces) is unreliable [MacOS 27 Version 27.0 Beta (26A5388g)] – sometimes it works instantly, sometimes I see the animation.
It's inconsistent with the rest of the subscription list
Top Swift Repositories with Beginner Issues (20)
Updated Daily via GitHub GraphQLNative Swift 6 + Metal map rendering engine for SwiftUI. Globe and flat vector-tile maps with labels, SwiftUI markers and avatars, on iOS and macOS.
Mlem for Lemmy
ClashFX — macOS proxy tool with Enhanced Mode (TUN)
Mac Sai: the open-source Mac cleaner, optimizer, and malware scanner. A free, Apple-notarized alternative to CleanMyMac, built with Swift 6 and SwiftUI.
Java interopability support for Swift
Foqos allows you to lock apps behind the tap of a NFC tag or scan of a QR code. Free and open source alternative to Brick, Opal, ScreenZen, Unpluq, Scrolly, Blok and more!
Swift implementation of AWS Lambda Runtime
AI that sees your screen, listens to your conversations and tells you what to do
Firefox for iOS
The Swift Programming Language
Local speech-to-text for macOS on-device AI, fully private, optional cloud
Vocello: a local, private voice studio for Apple Silicon. Write a script, pick or describe a voice, and generate speech on-device, faster than realtime on an 8 GB M2 Mac mini. Native Swift + MLX, no Python. Mac app out now, iPhone beta on TestFlight. (Formerly QwenVoice.)
Open-source iOS app connecting Meta Ray-Ban smart glasses to AI — 5 backends (on-device MLX models, Apple Intelligence, OpenAI, Gemini Live, OpenClaw), on-device neural voice, face recognition & live web search. Private and offline-capable.
Use your tokens to raise, evolve, and collect Pokémon! 🥚
PostHog iOS SDK
Space Rabbit removes animations when switching macOS Spaces. Reclaim hours of your time every month!
A native macOS menu bar app that proxies Claude/Codex/Gemini subscriptions for use with Factory Droid CLI. Always updated with new model releases!
macOS menu bar app that tells you, in plain English, what each USB-C cable plugged into your Mac can actually do
Metrics API for Swift
Own your AI. The native macOS harness for AI agents -- any model, persistent memory, autonomous execution, cryptographic identity. Built in Swift. Fully offline. Open source.
How to make your first Swift open-source pull request
Finding approachable Good First Issues in Swift allows you to build real-world software engineering experience. Instead of submitting PRs to abandoned repositories, GetMerged verifies maintainer review speeds and first-timer acceptance rates before you write a single line of code.
Select any issue above to claim it directly on GitHub, or click a repository to inspect full maintainer review turnaround metrics and triage guidance!
Get this week's top welcoming repos + fresh Good First Issues for Swift
Free weekly email, scoped to Swift. No account needed - confirm once and unsubscribe anytime.