Skip to content

Extract design system into HADesignSystem SPM package#5007

Open
bgoncal wants to merge 6 commits into
mainfrom
bgoncal/design-system-package
Open

Extract design system into HADesignSystem SPM package#5007
bgoncal wants to merge 6 commits into
mainfrom
bgoncal/design-system-package

Conversation

@bgoncal

@bgoncal bgoncal commented Jul 9, 2026

Copy link
Copy Markdown
Member

What

Extracts the design system into a new local Swift package at Sources/HADesignSystem, so tokens, colors, styles, and reusable components live in one portable module that can be shared with current targets (iOS, watchOS, Catalyst) and future Apple apps.

This is the first, foundational slice. It is deliberately scoped to the pieces that are cleanly portable; the components with heavier coupling are called out below as follow-ups.

Why a local SPM package (not an Xcode framework target)

  • Portability is the goal: a package is a folder + Package.swift, so a future app can drop it in or (later) consume it from its own git repo by URL. An Xcode framework target is bound to this .xcodeproj.
  • One multiplatform package replaces the parallel Shared-iOS / Shared-watchOS handling for this code.
  • The repo already uses this exact pattern (Sources/SharedPush, Sources/PromiseKitDynamic), so the wiring is proven.

Zero churn for consumers

Shared re-exports the package via @_exported import HADesignSystem (HADesignSystemExports.swift). Every existing import Shared file (≈135 across App/Extensions/CarPlay/Thread) keeps compiling unchanged. Only the ~10 files inside Shared that reference moved symbols got an explicit import HADesignSystem.

What moved into the package

Layer Files
Tokens DesignSystem (spacing/font/radius/animation), Sizes, HACornerRadius
Colors BaseColors palette, Color+Hex, new Color+Semantic
Helpers View.modify, UIScreen.displayCornerRadius
Styles HAButtonStyles (all HA*ButtonStyle), TextButton
Components CardView, PillView, HAProgressView, HATextField, SheetCloseButton, CloseButton, AppleLikeBottomSheet

Key decisions

  • Semantic colors are now code-defined (Color.haPrimary, .onSurface, .track, .tileBorder, .haColorBorderPrimaryQuiet, plus UIColor.haPrimary) using the exact values from the old colorsets. A shared package can't depend on the consuming app's asset catalog, so the 5 colorsets were removed from Colors.xcassets and reimplemented in Color+Semantic.swift. Adaptive (light/dark) colors use a UIColor dynamic provider on iOS/Catalyst and fall back to the dark value on watchOS. The remaining Background/Domain colorsets are untouched.
  • CloseButton decoupled from Current: Current.isCatalyst#if targetEnvironment(macCatalyst).
  • watchOS guards: SPM has no per-file target membership, so iOS-only files (CardView, PillView, SheetCloseButton, CloseButton, AppleLikeBottomSheet, UIScreen+PerfectCornerRadius) are wrapped in #if !os(watchOS) to match the previous Shared-watchOS membership exactly. DesignSystem, HAButtonStyles, HATextField, HAProgressView stay cross-platform as before.
  • Sizes.maxWidthForLargerScreens is now public (it was implicitly available before only via direct App-target file membership).

Intentionally left in Shared (follow-up PR)

These pull in MaterialDesignIcons, L10n product copy, or Current/Server, which need dedicated abstractions before they can move: ExternalLinkButton/ActionLinkButton, LabsLabel, ModalReusableButton/ModalCloseButton, SettingsButton, PrivacyNoteView, ServersPickerPillList, CollapsibleView.

Verification (needs a local build — not run here)

  • App, WatchApp, and extensions build (the package links into Shared-iOS and Shared-watchOS).
  • @_exported makes DesignSystem.*, Color.haPrimary, button styles, and the moved components visible to import Shared consumers without new imports.
  • Spot-check the 5 code-defined colors render identically to the old catalog colors in light and dark (haPrimary/track are fixed; onSurface/tileBorder/haColorBorderPrimaryQuiet are adaptive).
  • Package unit tests (tokens + haPrimary RGBA components) run on an iOS simulator destination via Xcode. The package targets iOS/watchOS and uses UIKit, so macOS CLI swift test is not supported (unlike the pure-model SharedPush package).

Lint (SwiftFormat + SwiftLint + Rubocop) passes locally.

🤖 Generated with Claude Code

Introduce a local Swift package (Sources/HADesignSystem) holding the
design tokens, palette, semantic colors, button styles, and the core
reusable SwiftUI components, so the design system can be shared across
current and future Apple apps.

- New local package linked into Shared-iOS and Shared-watchOS
- Shared re-exports it via `@_exported import` so existing `import
  Shared` consumers compile unchanged
- Semantic colors (haPrimary/onSurface/track/tileBorder/
  haColorBorderPrimaryQuiet) are now code-defined in the package instead
  of the app asset catalog, removing the consuming-app catalog dependency
- CloseButton decoupled from Current (Current.isCatalyst -> compile flag)
- iOS-only components guarded with #if !os(watchOS) to match the previous
  per-target file membership (SPM has no per-file membership)
- MDI/L10n/Server-coupled components (ExternalLinkButton, LabsLabel,
  Modal*, SettingsButton, PrivacyNoteView, ServersPickerPillList,
  CollapsibleView) intentionally kept in Shared for a follow-up PR

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 9, 2026 08:34
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

⚠️ Unused L10n strings detected

Found 3 unused localization strings in the codebase.

Click to see details
Parsing Strings.swift...
Found 1904 L10n strings

Reading all Swift source code...
Read 6387615 characters of Swift code

Checking for unused strings...
Checked 100/1904 strings...
Checked 200/1904 strings...
Checked 300/1904 strings...
Checked 400/1904 strings...
Checked 500/1904 strings...
Checked 600/1904 strings...
Checked 700/1904 strings...
Checked 800/1904 strings...
Checked 900/1904 strings...
Checked 1000/1904 strings...
Checked 1100/1904 strings...
Checked 1200/1904 strings...
Checked 1300/1904 strings...
Checked 1400/1904 strings...
Checked 1500/1904 strings...
Checked 1600/1904 strings...
Checked 1700/1904 strings...
Checked 1800/1904 strings...
Checked 1900/1904 strings...

================================================================================
UNUSED STRINGS REPORT
================================================================================

Found 3 unused strings:


CAMERAPLAYER:
  - L10n.CameraPlayer.Talkback.microphoneDenied
    Key: camera_player.talkback.microphone_denied
    Line: 820

CONNECTION:
  - L10n.Connection.Permission.InternalUrl.body1
    Key: connection.permission.internal_url.body1
    Line: 1370
  - L10n.Connection.Permission.InternalUrl.body2
    Key: connection.permission.internal_url.body2
    Line: 1372

================================================================================
Total unused: 3
================================================================================

================================================================================
Copy-paste these keys into the "Lokalise: Delete Keys" workflow (keys input):
================================================================================
camera_player.talkback.microphone_denied,connection.permission.internal_url.body1,connection.permission.internal_url.body2

To remove them, run the
Lokalise: Delete Keys
workflow — it deletes the keys from Lokalise and opens a PR removing them from
Localizable.strings and regenerating Strings.swift. Copy-paste these keys into the keys input:

camera_player.talkback.microphone_denied,connection.permission.internal_url.body1,connection.permission.internal_url.body2

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extracts the app’s design-system tokens/colors/styles/components into a new local Swift Package (Sources/HADesignSystem) and re-exports it from Shared via @_exported import HADesignSystem so existing import Shared consumers can keep compiling unchanged. It also replaces several semantic asset-catalog colors with code-defined semantic colors suitable for a reusable package.

Changes:

  • Introduces local SPM package HADesignSystem containing tokens, semantic/base colors, styles, and reusable SwiftUI components (+ a small test target).
  • Re-exports HADesignSystem from Shared and updates a handful of Shared files to import/use the new module.
  • Removes the migrated semantic colorsets from Sources/Shared/Assets/Colors.xcassets and wires the package into the Xcode project.

Reviewed changes

Copilot reviewed 28 out of 38 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
Sources/Shared/MaterialDesignIcons+CarPlay.swift Imports HADesignSystem to use shared colors/tokens.
Sources/Shared/MagicItem/MagicItem.swift Imports HADesignSystem for shared semantic color usage.
Sources/Shared/Environment/Style.swift Uses HADesignSystem tokens/colors for UI styling.
Sources/Shared/EntityIconColorProvider.swift Uses HADesignSystem semantic color fallback.
Sources/Shared/DesignSystem/HADesignSystemExports.swift Re-exports HADesignSystem from Shared.
Sources/Shared/DesignSystem/Constants/Sizes.swift Removes Sizes from Shared (moved to package).
Sources/Shared/DesignSystem/Components/ServersPickerPillList.swift Imports HADesignSystem for moved symbols.
Sources/Shared/DesignSystem/Components/PrivacyNoteView.swift Imports HADesignSystem for moved symbols.
Sources/Shared/DesignSystem/Components/ModalReusableButton.swift Imports HADesignSystem for moved symbols (iOS-only guarded).
Sources/Shared/DesignSystem/Components/LabsLabel.swift Imports HADesignSystem for moved symbols.
Sources/Shared/DesignSystem/Components/ExternalLinkButton.swift Imports HADesignSystem for moved symbols.
Sources/Shared/Common/Extensions/Color+hex.swift Imports HADesignSystem to reference new semantic colors.
Sources/Shared/Assets/Colors.xcassets/trackColor.colorset/Contents.json Removes asset-based semantic color.
Sources/Shared/Assets/Colors.xcassets/onSurface.colorset/Contents.json Removes asset-based semantic color.
Sources/Shared/Assets/Colors.xcassets/haPrimary.colorset/Contents.json Removes asset-based semantic color.
Sources/Shared/Assets/Colors.xcassets/ha-color-border-primary-quiet.colorset/Contents.json Removes asset-based semantic color.
Sources/Shared/Assets/Colors.xcassets/Border/tileBorder.colorset/Contents.json Removes asset-based semantic color.
Sources/HADesignSystem/Package.swift Defines the new local SPM package.
Sources/HADesignSystem/Sources/Tokens/Sizes.swift Adds token moved from Shared.
Sources/HADesignSystem/Sources/Tokens/HACornerRadius.swift Adds corner-radius token moved from Shared.
Sources/HADesignSystem/Sources/Tokens/DesignSystem.swift Adds core token namespace (fonts/spaces/radii/etc).
Sources/HADesignSystem/Sources/Colors/Color+Semantic.swift Implements code-defined semantic colors replacing removed colorsets.
Sources/HADesignSystem/Sources/Colors/Color+Hex.swift Adds hex parsing + UIColor helpers inside the package.
Sources/HADesignSystem/Sources/Colors/BaseColors.swift Adds base palette constants.
Sources/HADesignSystem/Sources/Styles/HAButtonStyles.swift Adds shared button styles.
Sources/HADesignSystem/Sources/Styles/HAButton.swift Adds TextButton style.
Sources/HADesignSystem/Sources/Extensions/View+ConditionalModifier.swift Adds shared conditional modifier helper.
Sources/HADesignSystem/Sources/Extensions/UIScreen+PerfectCornerRadius.swift Moves UIScreen corner-radius helper (watchOS-guarded).
Sources/HADesignSystem/Sources/Components/CardView.swift Moves CardView component (watchOS-guarded).
Sources/HADesignSystem/Sources/Components/PillView.swift Moves PillView component (watchOS-guarded).
Sources/HADesignSystem/Sources/Components/HAProgressView.swift Moves HAProgressView component.
Sources/HADesignSystem/Sources/Components/HATextField.swift Moves HATextField component.
Sources/HADesignSystem/Sources/Components/SheetCloseButton.swift Moves SheetCloseButton component (watchOS-guarded).
Sources/HADesignSystem/Sources/Components/CloseButton.swift Moves CloseButton component + decouples from Current (watchOS-guarded).
Sources/HADesignSystem/Sources/Components/AppleLikeBottomSheet.swift Moves bottom sheet component (watchOS-guarded).
Sources/HADesignSystem/Tests/HADesignSystemTests.swift Adds initial unit tests for tokens/colors.
HomeAssistant.xcodeproj/project.pbxproj Adds the local package reference and updates build phases/groups.
.gitignore Ignores package-local Package.resolved.

Comment thread Sources/HADesignSystem/Sources/Colors/Color+Semantic.swift Outdated
Comment thread Sources/HADesignSystem/Sources/Colors/Color+Semantic.swift Outdated
Comment thread Sources/HADesignSystem/Tests/HADesignSystemTests.swift Outdated
Comment thread Sources/HADesignSystem/Package.swift
bgoncal and others added 5 commits July 9, 2026 10:48
Color+Semantic: route rgb components through helpers with explicit
/ 255.0 float division (behavior unchanged; the previous form was
already float division in Double/CGFloat context, just less obvious).

HADesignSystemTests: drop the Color-equality and non-optional-nil
assertions; verify haPrimary by extracting its RGBA components instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The code-defined colors were on `extension Color`, but implicit-member
uses in a ShapeStyle context (e.g. HATextField's `.stroke(.tileBorder)`,
and `.foregroundStyle(.haPrimary)` / `.fill(.haPrimary)` across consumers)
require the accessor on `ShapeStyle where Self == Color` — which is what
the old asset-catalog codegen provided. Defining them there also keeps
`Color.haPrimary` resolving, so both explicit and implicit uses compile.

Fixes 'type ShapeStyle has no member tileBorder' build failure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a categorized catalog of the package's reusable components:
- ComponentCategory: buttons / controls / inputs / containers / indicators
- DesignSystemComponent: CaseIterable enum, each case exposes a title,
  category, and a @ViewBuilder preview of the live component
- ComponentsLibraryView: reads the enum and renders every component
  grouped by category (the in-app glossary / explorer)
- BottomSheetGalleryDemo: stateful present-button demo for the sheet

Also documents in AGENTS.md that reusable components go in the
HADesignSystem package (not the app) and must be registered in
DesignSystemComponent so they appear in the explorer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants