Skip to content

Repository files navigation

HalfmarbleKit

The shared iOS/tvOS layer behind halfmarble's apps — the chrome, the plumbing, and the hard-won platform workarounds that every one of them needs and none of them should own a private copy of.

Extracted 2026-07-25, when a second app's landing screen set out to pixel-match the first's and the two menu-button implementations had already drifted apart.

What's in it

AudioHost The AVAudioEngine host: engine graph, session config, interruption / route-change / media-services-reset recovery, and a 20 Hz fader that doubles as an engine watchdog. Apps supply the content; the host renders it through one callback.
StoreUnlock / UnlockPrompt StoreKit 2 one-time unlock: product load, the lifelong Transaction.updates listener, exact entitlement reconciliation (revocation- and absence-aware), purchase with verify-then-finish, and three-state restore — plus the purchase card in front of it, including the status line every no-UI failure path needs.
MenuButtons / MenuButtonViews The house buttons — pills and the big CTA, black-outline treatment, press feedback, tremor tap-debounce, breathing pulse. UIKit core with thin SwiftUI wrappers.
Brand / BrandSplash Wordmark, ring mark, the charitable-giving pledge, and the lockup metrics both splash choreographies share.
GameCenter Authentication, leaderboard and achievement submission, with the app supplying its own IDs.
ArrowKeys / HoldKeys Hardware-keyboard and game-controller input, including hold-to-repeat.
Frost / Outline / OutlineSwiftUI The two blur recipes and the outline treatment, so every surface matches.
Texture The scale-1 renderer format for procedurally generated art that gets stretched — the rule that keeps a full-screen gradient from becoming a 21 MB bitmap.
Share The system share sheet, with the presenter walk and the iPad popover anchor that a missing sourceView turns into a crash.
BarStrip A row of bars drawn in one Canvas pass, so bar count stops being a per-frame cost — the HStack of N capsules it replaces re-laid-out all N on every tick. Deliberately knows nothing about what it plots: the caller supplies a 0…1 height and a colour per bar, which is what lets one view serve a microphone trace and a thermal-tinted memory trace without either leaking into the other.
PerfProbe / FPSTimelineView / StartupProf The FPS · RAM · BUILD diagnostic strip and startup profiling.
SessionLog A timestamped event log that survives being killed: write-through with synchronizeFile (SIGKILL flushes nothing for you), and rotation that archives rather than overwrites — the second promise the first version silently failed. CSV export, one row per event whatever the detail contains.
ConsoleLog / ConsoleView The in-app console. Tees stdout/stderr rather than redirecting them, so a tethered devicectl --console keeps working and third-party prints are captured too. Tag colours are per-app.
Footprint Phase-tagged memory logging on top of PerfProbe — which phase produced the peak, written before the kill rather than read off a gauge after it. An app with a second allocator (ML runtime, Metal pool) supplies it through one closure; the kit never imports a GPU framework.
Haptics / UISound / DefaultsKeys / Version / ReleaseChannel The small shared utilities.
HalfmarbleTestKit Test-only harnesses (imports XCTest). App test targets depend on this; app targets never do.

Using it

.package(url: "https://github.com/halfmarble/HalfmarbleKit.git", from: "1.0.0")

Platforms are iOS 16+ and tvOS 26+. The Mac ships as Mac Catalyst, which inherits the iOS entry — native AppKit would mean a second implementation of every button, which is the one thing this package exists to prevent.

Building and testing

The kit is iOS-only, so swift build / swift test on macOS cannot build it. Use an iOS simulator destination:

xcodebuild test -scheme HalfmarbleKit-Package -destination 'platform=iOS Simulator,name=iPhone 17 Pro'

HalfmarbleKit-Package, not HalfmarbleKit: the per-target scheme SwiftPM generates has no test action, so the obvious spelling fails with "Scheme HalfmarbleKit is not currently configured for the test action". The -Package scheme is the one that carries the test targets.

A simulator NAME can match more than one installed device — xcrun simctl list devices available shows the duplicates — and xcodebuild then picks one for you. Where that matters, target the UDID instead:

xcodebuild test -scheme HalfmarbleKit-Package -destination 'platform=iOS Simulator,id=<UDID>'

A note on the comments

Many of the comments in here are longer than the code they sit above, and they name dates, field reports and the reasoning behind a specific constant. That is deliberate: nearly every non-obvious line in this package exists because something failed on a real device, and the comment is the record of what. Please keep that style — a value with a reason is maintainable, a magic number is not.

Prior art

The tremor debounce in MenuButtons.swift — an input filter whose window is derived from the characteristic frequency of a hand tremor, scoped per control so it never eats a fast deliberate sequence — is published as a defensive publication and dedicated to the public domain: PRIOR_ART_TREMOR_DEBOUNCE.md, lodged in Technical Disclosure Commons at dpubs_series/11329 so an examiner searching the usual databases will find it.

It is there so the method stays freely practicable by anyone and cannot be patented by a third party. It is an accessibility technique and makes no health claim.

License

Apache 2.0. The halfmarble name and ring mark are trademarks of Halfmarble LLC and are not licensed for use by the Apache 2.0 grant — fork the code freely, but ship it under your own brand.

About

Shared iOS/tvOS layer for halfmarble apps — audio host, StoreKit unlock, menu chrome, brand.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages