Skip to content

feat!: restore the widget components library and prepare 2.0.0 - #28

Merged
muhittincamdali merged 59 commits into
mainfrom
feature/restore-components-library
Oct 14, 2026
Merged

muhittincamdali merged 59 commits into
mainfrom
feature/restore-components-library

Conversation

@muhittincamdali

Copy link
Copy Markdown
Owner

Summary

The v1.0.0 manifest does not load, and on main the library target held only about 240 lines; most of the Swift code sat outside every target and the test target did not compile. This branch restores the views and helpers from history into the WidgetDevelopmentKit target, reworks the parts that were broken, removes the code that could not work, adds tests, documentation, an example app and CI, and prepares the 2.0.0 release.

Changes

  • Package: Swift 6 tools and language mode, iOS 17 and macOS 14, no dependencies. The CocoaPods podspec is removed.
  • Views restored by theme: Home Screen templates, Swift Charts views, Lock Screen accessories (iOS), StandBy views, interactive widgets and Live Activity views (iOS).
  • Timelines and data: WidgetUpdatePolicy, TimelineBuilder, TimelineSchedule and WidgetSnapshotStore, reworked from TimelineManager.
  • Networking: WidgetDataLoader, RetryPolicy and Endpoint; 4xx responses are no longer retried and requests no longer wait for connectivity inside the extension.
  • Configuration and interactivity: WidgetDeepLink, HexColor, App Entities for refresh interval and accent color, WidgetStateStore, SetValueIntent and RecordActionIntent.
  • Live Activities: LiveActivityManager becomes the LiveActivities namespace; LiveActivityStateMachine uses typed throws; a paused timer shows a fixed remaining time.
  • Fixes: a string literal that did not compile, division by zero and NaN progress, force-unwrapped colors and URLs, a trap when an end date precedes the start date, calendar-day counting, month grids that ignored the first weekday, duplicate ForEach identifiers, and a .swiftlint.yml that did not parse.
  • Removed: about 10,500 lines in Sources/Enterprise, Sources/Integration, Sources/Analytics, Sources/Widgets and Sources/LiveData that no target compiled, the Quick/Nimble tests, the old demo app, examples and Documentation/ folder, and ten workflows that did not build the package.
  • Tests: Swift Testing suites for the helpers, timelines, snapshot store, deep links, colors, entities, data loader, state store and state machine; ReadmeSyncTests keeps README code blocks in line with compiled code.
  • Docs: README rewritten around a three-step quick start, nine translations, DocC catalog with two articles deployed to GitHub Pages, CONTRIBUTING.md updated. Installation instructions now use from: "2.0.0".
  • Example: Examples/WidgetKitExample, an iOS app with a widget extension that builds against the package in the same commit.
  • CI: shared workflows from muhittincamdali/.github (build and test on macOS, iOS simulator tests, DocC to Pages, links, wording, translations), plus SwiftLint and the example app build.
  • Release: CHANGELOG.md moves the notes into a 2.0.0 section.

Testing

Run locally with Xcode 27.0 and Swift 6.4:

Command Result
swift build --build-tests succeeded, 0 warnings
swift test 77 tests in 19 suites passed
xcodebuild test on an iOS 27 simulator 83 tests, 80 passed, 3 opt-in screenshot tests skipped, 0 warnings
xcodebuild docbuild succeeded, 0 warnings
xcodebuild build of Examples/WidgetKitExample succeeded
swiftlint lint --strict (0.63.2) 0 violations
actionlint no findings
swift build --build-tests on every code commit succeeded

The GitHub runners use macos-26 with its default Xcode, which the local runs did not cover; the checks on this pull request are the first run with that toolchain.

Breaking changes

The public API changes substantially, so the next release is 2.0.0.

  • Removed: WidgetConfiguration (it shadowed SwiftUI's protocol), the WidgetDevelopmentKit singleton, TimelinePrewarmer, WidgetGallery, SmartTimelineProvider, ActionIntent (use RecordActionIntent) and the Enterprise, Integration and Analytics sources.
  • LiveActivityManager is now the LiveActivities namespace without a singleton.
  • Countdown views take a referenceDate; LockScreenTimerWidget takes startTime and endTime.
  • The CocoaPods podspec is gone; install with Swift Package Manager.

Screenshots

Rendered by scripts/render-screenshots.sh from the views in this branch.

Light Dark
Home Screen templates, light Home Screen templates, dark
Charts, light Charts, dark
Lock Screen accessories, light Lock Screen accessories, dark

The Enterprise, Integration, Analytics, Widgets and LiveData folders,
the demo app, examples, templates and the Documentation folder were not
included by any target in Package.swift, and much of the code referenced
types that do not exist. The Quick/Nimble tests in Tests/Unit belonged
to the only test target, which did not compile because Quick and Nimble
were never declared as dependencies; that test target is removed with
them and a new one is added together with the first new tests. The
podspec, Turkish README stub and support files described APIs that are
not in the package.
Drops the swift-async-algorithms dependency, which no source file used.
- Remove the no-op WidgetDevelopmentKit singleton, the hard-coded version
  constant and the WidgetConfiguration struct, which shadowed SwiftUI's
  WidgetConfiguration protocol for any file importing both modules.
- Remove TimelinePrewarmer: it only called reloadTimelines and did not do
  the usage-based scheduling its documentation described.
- Remove WidgetGallery; the restored template views cover the same cases.
…eMachine

- Drop the print call, which logged a literal "\(newState)" because the
  interpolation was escaped.
- Throw StateMachineError.invalidTransition(from:to:) with typed throws so
  callers can see which transition was rejected.
- Store allowed transitions as sets, expose currentState as a read-only
  property instead of getCurrentState(), and add canTransition(to:) and
  allowedNextStates().
- Clamp the selected index to the entry range, so the view cannot index
  out of bounds when the entries array shrinks.
- Hide the slider when there is only one entry instead of building a
  0...0 slider.
- Allow a custom preview size instead of a fixed 158 x 158 frame.
- Show the entry date with a short date format, and replace the
  deprecated foregroundColor and cornerRadius modifiers.
WidgetMath, WidgetFormat, MonthGrid and CountdownComponents hold the
arithmetic the views need (progress fractions, zero totals, reversed
timer ranges, locale-aware month grids) so it can be unit tested.
Tests use a fixed Gregorian calendar and cover NaN, negative and zero
inputs, Monday-first weeks, a leap month and a DST transition.

Adds the WidgetDevelopmentKitTests target for these tests.
Restores the chart views that were outside every build target and fixes:
sparkline x values no longer depend on Date(), progress ring no longer
force-unwraps an empty colour list or divides by a zero total, multi-line
series use a stable order, horizontal bars clamp their width, and the
activity heat map computes its maximum once and uses discrete levels.
Accessory families are unavailable on macOS, so the views compile for
iOS and watchOS only. Timer views no longer build a reversed date range
(which traps), countdowns count calendar days from the timeline entry
date, progress values are clamped and duplicate labels no longer collide
as ForEach identifiers.
The month grid now follows the calendar's first weekday and uses unique
column identifiers, fitness rings guard against zero goals, and the
countdown is computed from the timeline entry date instead of Date(),
dropping the seconds column a static widget snapshot cannot update.
Split into themed files. Fixes a string literal that did not compile,
zero-goal and zero-duration divisions, a force-unwrapped fallback URL in
the shortcuts widget and the water tracker's fixed eight-glass row.
Adds tests for the habit streak and water tracker arithmetic.
Reworked from the TimelineManager removed in e3bcc65. Kept the ideas
that hold up and fixed their bugs:
- WidgetUpdatePolicy uses calendar arithmetic for next-hour and next-day
  reloads instead of adding 3600/86400 seconds, and maps to a WidgetKit
  TimelineReloadPolicy.
- TimelineBuilder is a value type and sorts entries by date.
- TimelineSchedule generates evenly spaced or boundary-aligned dates.
- WidgetSnapshotStore replaces TimelineCacheManager: Codable values with
  an expiry in injectable UserDefaults (no hard-coded App Group).
Dropped SmartTimelineProvider (its placeholder force-cast nil to the data
type), the Timer-based reload manager, which cannot run in a widget
extension, and the battery and metrics helpers.
Reworked from the orphaned WidgetConfiguration.swift:
- WidgetDeepLink builds and parses widget URLs as a value type, with
  sorted query items, scheme checking and first-wins duplicate keys,
  replacing the non-Sendable handler-registry singleton.
- HexColor validates 3, 6 and 8 digit input; the old initializer accepted
  partial matches and ignored alpha, and hexString indexed past the end
  of grayscale colour components.
- RefreshIntervalOption and AccentColorOption keep the AppEntity choices,
  plus an AppIntentsPackage so apps can register them.
Dropped the storage and migration singletons with hard-coded App Group
names; WidgetSnapshotStore covers persistence.
Reworked from the orphaned NetworkDataProvider.swift. The old provider
retried every non-2xx status (including 404), waited for connectivity
inside a widget extension and used non-Sendable singletons. Now:
- WidgetDataLoader retries only transient errors (5xx, 408, timeouts,
  offline) with doubling backoff, and takes an injectable transport and
  sleep so the retry logic is unit tested.
- URLSession.widgetDefault is ephemeral with short timeouts.
- Endpoint replaces APIEndpointBuilder as a value type with encoded path
  segments and ordered query items.
Dropped the polling, sync and background-refresh singletons.
The orphaned intents wrote to a hard-coded App Group that no app owns,
and ActionIntent posted a NotificationCenter notification inside the
widget extension, where the app never receives it. Now:
- WidgetStateStore holds flags, counters and a pending-action queue in
  the App Group named by the WidgetDevelopmentKitAppGroup Info.plist key.
- ToggleIntent, IncrementIntent, SetValueIntent, RecordActionIntent and
  RefreshWidgetIntent use that store; counters saturate and can be
  clamped at zero.
- The rating, water, timer and quick-action widgets use these intents,
  and the rating view no longer traps for a maximum below one.
Restores ten ActivityAttributes types (delivery, order, ride, flight,
timer, workout, parking, sports, now playing, meeting) with their Lock
Screen and Dynamic Island compact views. The files compile only where
ActivityKit exists (iOS). LiveActivityManager becomes a LiveActivities
namespace without a singleton, with optional stale dates; the unused
LiveActivityTemplate protocol is removed and time labels use
WidgetFormat.playbackTime.
SwiftUI views are main-actor isolated in the iOS SDK, so the tests
produced isolation warnings when built for the simulator.
scripts/render-screenshots.sh runs an opt-in test suite on an iOS
simulator that renders home screen templates, charts and lock screen
widgets in light and dark mode into docs/images. The suite is skipped
unless WDK_SCREENSHOT_DIR is set.
ReadmeExamplesTests holds the code shown in the README (timeline
provider, widget, interactive button, networking) so it is compiled and
the provider's timeline is checked on every test run.
The old workflows pinned Xcode 15.2, ran SwiftLint with || true, and
included a README guard, link checkers, a Pages deploy and a release job
that overwrote release notes with the commit list. None of them built
the package successfully.

The new workflow runs swift build and swift test on macOS, xcodebuild
test on an iOS simulator and xcodebuild docbuild, with checkout pinned
to a commit SHA and read-only permissions. Also drops the duplicate
Markdown issue templates, points CODEOWNERS at the maintainer and limits
Dependabot to GitHub Actions.
…ticles

Also completes parameter documentation so xcodebuild docbuild reports
no warnings.
Replaces the ASCII banner, static shields, comparison table and
template counts that did not match the code with: a feature table
counted from the sources, requirements and installation that match
Package.swift, compiled usage examples, rendered screenshots, an
architecture note, test instructions and a short project history.
The previous file listed releases 1.0.0 to 3.2.0 dated 2023-2024; the
repository was created in 2025 and only v1.0.0 was ever tagged. The new
file records this work under Unreleased and notes that v1.0.0 does not
build.
The library target at the previous main built; only the v1.0.0
manifest failed to load. What did not work was the test target, whose
Quick/Nimble tests used packages the manifest never declared, and most
of the Swift code, which was outside every target.
The AppIntentsPackage example declared includedPackages as a stored
static property, which Swift 6 reports as not concurrency-safe; it is
now a computed property, and the example is compiled in the test
target. The getting-started example falls back to standard defaults
instead of force-unwrapping the App Group suite, as the README does.
xcodebuild stops with "multiple devices matched" when two simulators
share a name. The script now also accepts a UDID, or a full destination
in the DESTINATION environment variable.
The header always read "7 Day Streak", even when the streaks shown in
the rows were 4, 1 and 0. The seven dots are the last seven days, so the
header now says that.
"Xcode 16 or later" was not verified; the package has only been built
and tested with Xcode 27. The requirement now names the manifest's
tools version and the tested Xcode instead.
The iOS simulator runs 77 tests; the 3 screenshot tests are skipped
unless scripts/render-screenshots.sh runs them.
The first commit is from August 2025; the license said 2024.
SwiftLint could not parse .swiftlint.yml: the unquoted "*/Generated/*"
entry is a YAML alias, so every run fell back to the default rules.

- Drop the excluded paths that do not exist in this package (Pods,
  DerivedData, Generated, Package.swift) and lint Sources and Tests only.
- Add Tests/.swiftlint.yml that turns off force_unwrapping for the tests,
  which unwrap fixed URLs and dates they create themselves.

swiftlint lint now reports 0 violations.
Replace the repository's own CI with callers of the reusable workflows in
muhittincamdali/.github, pinned to commit 5d3c443:

- ci.yml: swift-ci.yml runs swift build --build-tests and swift test on
  macOS, builds the package for iOS and macOS with xcodebuild, and runs the
  tests on an iOS simulator (test-on-simulator: true).
- docs.yml: docc-pages.yml builds the DocC archive for the iOS Simulator,
  so the Lock Screen and Live Activity symbols are included, and deploys it
  to GitHub Pages on pushes to main.
- quality.yml: link check (lychee), wording scan of files and commit
  messages, and the README translation check with strict: true.
- .lychee.toml: the shared link check settings.

All jobs use GitHub's macos-26 image with its default Xcode, or
ubuntu-24.04.
.swiftlint.yml was only used locally. Lint runs SwiftLint 0.63.2 in its
official container image, pinned by digest, with --strict so a warning
fails the job.
Built with the branding script of the repository kit (muhittincamdali/.github) from
.github/brand.json:

- banner in light and dark, with the tagline in all ten README languages;
- badges for platforms, Swift tools version and license, read from
  Package.swift and LICENSE. The release badge is turned off
  (badgeOverrides.version) because the only tag, v1.0.0, does not build;
- Get started, Documentation and Examples buttons in ten languages;
- social preview (1280 x 640) as SVG and PNG. docs/social-preview.png is
  the copy to upload in the repository settings.

All graphics are SVG files served from this repository; the CI badge is
GitHub's own badge.svg.
Examples/WidgetKitExample is an iOS 17 app with a widget extension that
uses the package through a local package reference, so it builds against
the code in the same commit.

- LaunchCountdownWidget: TimelineSchedule entry dates and CountdownWidget,
  the code the README quick start shows.
- StepsWidget: a provider that reads WidgetSnapshotStore and builds its
  timeline with TimelineBuilder, rendered with WidgetProgressRing.
- WaterWidget: InteractiveWaterWidget, whose button runs IncrementIntent
  and writes to the App Group store.
- The app saves the step count for the widgets, previews the steps
  timeline with WidgetTimelineScrubber and shows the water counter.

The project is generated from project.yml with XcodeGen and committed, so
building it needs only Xcode. The new Examples workflow builds it for the
iOS Simulator on every push and pull request, and SwiftLint now covers
Examples/ too.
- Explain how to add a widget extension target, and list the Add Package
  Dependencies steps with the dialog's button and column names.
- Say that the App Groups capability is needed on both targets.
- Note that WidgetSnapshotStore and WidgetStateStore keep values in
  unencrypted UserDefaults, so they must not hold secrets. This moves the
  note from the repository's SECURITY.md into the documentation.
- Use the App Group name of the example app and point to
  Examples/WidgetKitExample, which contains the article's code.
- Show the WidgetTimelineScrubber call the example app uses.
GitHub falls back to the files in muhittincamdali/.github when a
repository has none, so remove the local copies:

- CODE_OF_CONDUCT.md: same Contributor Covenant text as the shared one.
- SECURITY.md: the shared policy adds private reporting through the
  Security tab. Its note about unencrypted widget storage is now in the
  Getting Started article.
- Issue forms and the pull request template: the local ones were generic
  ("Change 1 / Change 2", emoji link labels).
- FUNDING.yml: it pointed at a GitHub Sponsors profile that does not exist.

CONTRIBUTING.md stays, since it has this package's build and test steps.
CODEOWNERS and dependabot.yml stay, since they are not inherited.
Follow the shared README layout of the repository kit:

- Generated header: banner, language selector, badges and buttons. The
  shields.io license badge is replaced by a badge served from this
  repository.
- Highlights with the counts from the code (20 Home Screen templates,
  15 Lock Screen and 7 StandBy views, 5 App Intents, 8 interactive views,
  10 ActivityAttributes types).
- Quick start in three steps, with the Xcode menu path, the Add Package
  dialog and a complete countdown widget taken from
  Examples/WidgetKitExample.
- Usage examples taken from the example app, or from
  ReadmeExamplesTests.swift. The README used to say all of them were
  copied from ReadmeExamplesTests.swift, which was not true for the
  store.save snippet. The networking example no longer force-unwraps the
  URL, and the examples use the App Group of the example app.
- Links to the DocC site on GitHub Pages and its two articles.
- Requirements, architecture, roadmap, contributing and license sections.
  The test commands no longer carry test counts, which change with every
  new test.
ReadmeSyncTests reads README.md and fails when a Swift block does not
appear, line by line, in a Swift file under Examples/ (built by the
Examples workflow) or in ReadmeExamplesTests.swift (built with the tests).
Indentation and blank lines are ignored; only the Package.swift fragment
is exempt. Two more tests cover the block extraction and the matching.

ReadmeExamplesTests now uses the example app's App Group, unwraps the
example URL with guard instead of !, and its header comment says which
README examples it holds.
README.tr.md translates README.md at blob 21d649e: same sections, code blocks and links, headings and fixed sentences from the kit's tr strings. Stamped status=reviewed, so the translation check in quality.yml reports it as current.
README.zh-CN.md translates README.md at blob 21d649e: same sections, code blocks and links, headings and fixed sentences from the kit's zh-CN strings. Stamped status=reviewed, so the translation check in quality.yml reports it as current.
README.ja.md translates README.md at blob 21d649e: same sections, code blocks and links, headings and fixed sentences from the kit's ja strings. Stamped status=reviewed, so the translation check in quality.yml reports it as current.
README.ko.md translates README.md at blob 21d649e: same sections, code blocks and links, headings and fixed sentences from the kit's ko strings. Stamped status=reviewed, so the translation check in quality.yml reports it as current.
README.es.md translates README.md at blob 21d649e: same sections, code blocks and links, headings and fixed sentences from the kit's es strings. Stamped status=reviewed, so the translation check in quality.yml reports it as current.
README.de.md translates README.md at blob 21d649e: same sections, code blocks and links, headings and fixed sentences from the kit's de strings. Stamped status=reviewed, so the translation check in quality.yml reports it as current.
README.fr.md translates README.md at blob 21d649e: same sections, code blocks and links, headings and fixed sentences from the kit's fr strings. Stamped status=reviewed, so the translation check in quality.yml reports it as current.
README.pt-BR.md translates README.md at blob 21d649e: same sections, code blocks and links, headings and fixed sentences from the kit's pt-BR strings. Stamped status=reviewed, so the translation check in quality.yml reports it as current.
README.ru.md translates README.md at blob 21d649e: same sections, code blocks and links, headings and fixed sentences from the kit's ru strings. Stamped status=reviewed, so the translation check in quality.yml reports it as current.
…UTING

- Add the commands that build the example app and run SwiftLint the way CI does.
- Say that README Swift code must come from Examples/ or ReadmeExamplesTests.swift, and how the translations record which README.md they follow.
- List the workflows a pull request has to pass, and point to the shared community files in muhittincamdali/.github.
…elog

Under Unreleased: the example app, ReadmeSyncTests, the README translations, CI through the shared workflows plus the Examples and Lint workflows, the SwiftLint configuration fix and the removal of the local community files. The added-tests entry no longer gives test counts.
The README, its nine translations and the DocC Getting Started article
told readers to depend on `main` until a tagged release that builds
existed. With 2.0.0 that release exists, so the Package.swift snippets
now use `from: "2.0.0"` and the paragraph below them says that releases
start at 2.0.0 and that the older v1.0.0 tag does not build.

Each translation's i18n-source marker moves to the new README.md blob,
since the only English change is translated in the same commit.
Move the Unreleased notes into a 2.0.0 section dated 2026-10-14, state
that this is the major version the notes asked for, and add the
Keep a Changelog comparison links for 2.0.0 and the new Unreleased
section. The note about removed changelog entries now says that the old
2.x and 3.x entries have nothing to do with this release.
@muhittincamdali
muhittincamdali merged commit 8e0b678 into main Oct 14, 2026
9 of 10 checks passed
@muhittincamdali
muhittincamdali deleted the feature/restore-components-library branch October 14, 2026 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant