- FeatureControlManager/NotificationsClient: pass debug: false at every
api.request(...) call site — the API default (debug: true) was printing
Authorization: Bearer <jwt> on every request, release builds included.
- Package.swift: drop macOS/tvOS from platforms — LCEssentials.API (used by
LCFeatureControl) is iOS/watchOS-only and the vendored xcframework has no
macOS/tvOS slice.
- FeatureControlManager: coalesce concurrent evaluate() calls on a cold cache
into a single in-flight request per key instead of firing one per caller.
- FeatureControlManager/NotificationsClient: treat a 200 response carrying
{"error": true} as a failure instead of caching/returning it as success.
- FeatureControlNotificationsClient: thread a cursor param through list() so
the already-decoded nextCursor can actually be used to page.
- Tests: 9 new tests (coalescing, envelope-error-on-200, cursor param, a real
object payload decoded through a full evaluate response, date-decode
failures) and removed the 7 remaining force-unwraps in test scaffolding.
- Documentation/FeatureControl.md: new guide for the LCFeatureControl product,
cross-linked from README.md and Extensions.md.
LCEssentials installs standalone; each sub target-depends on it so linking a
sub's product always pulls LCEssentials in too, without the consumer having to
declare it separately.
- LCECryptoKit: internalized from the remote LCECryptoKitBinary git dependency
(embedded token URL removed) into a local binaryTarget vendoring
Frameworks/LCECryptoKit.xcframework. LCECryptoKitManager moved out of
LCEssentials core into its own LCECryptoKitManager target/product; the
no-op fallback for when the binary wasn't linked is gone (breaking change
for existing consumers, see decisions/2026-09-15-sub-spm-optional-products.md).
- LCFeatureControl: new product wrapping Atomenta's Feature Control API
(flag evaluation with TTL cache + safe-degrade fallback to defaults,
notifications inbox, batched exposure telemetry). 45 new tests.
Dummy mirrored button used to balance title centering rendered as a
visible empty glass pill under iOS 26's automatic button chrome. Side
buttons now only render when actually configured, title centers via
ZStack independent of button widths, and title padding is derived from
measured button width so long titles truncate instead of overlapping.
Glass button style is opt-in via setGlassButtonsEnabled(_:), off by
default.
- Package.swift: .iOS(.v13) -> .iOS(.v15), .watchOS(.v6) -> .v8
(README already stated iOS 15; watchOS 8 needed for URLSession.upload(for:fromFile:))
- drop the iOS-13 uploadTask continuation bridge; both upload overloads now
share one runUpload using session.upload(for:fromFile:delegate:)
- remove now-redundant @available(iOS 15) from the progress overload and test
- Documentation/API.md: drop iOS-version caveats
- upload<T>(url:method:form:pathParams:headers:debug:timeoutInterval:networkServiceType:)
serialises MultipartForm to a temp file, streams it via uploadTask(with:fromFile:)
bridged to async (works on iOS 13+, unlike URLSession.upload(for:fromFile:))
- temp body file always removed via defer (success and throw)
- move requestLOG/responseLOG to LCEssentials+API+Logging.swift; extract
logResponseError helper; keeps API file at 473 lines
- APIUploadTests: 4 cases
- progress-reporting overload deferred (no stub seam for didSendBodyData)