Skip to content

feat: Adds support for MeshGradient theming#14

Merged
rozd merged 4 commits intomainfrom
feat/mesh-gradients
Feb 25, 2026
Merged

feat: Adds support for MeshGradient theming#14
rozd merged 4 commits intomainfrom
feat/mesh-gradients

Conversation

@rozd
Copy link
Owner

@rozd rozd commented Feb 24, 2026

This pull request adds support for mesh gradients as a new theme category in the ThemeKit system, including full integration into theme configuration, code generation, preview generation, and test coverage. It introduces the MeshGradient type as a first-class citizen alongside existing categories like colors, gradients, and shadows.

The most important changes are:

Mesh Gradient Support in Theme System

  • Added .meshGradients as a new case to ThemeCategory, including its struct name, type name, JSON key, and token extraction logic (ThemeCategory.swift). [1] [2] [3] [4]
  • Updated ThemeConfig to support an optional meshGradients array, including decoding, initialization, and category computation (ThemeConfig.swift).

Mesh Gradient Codable Implementation

  • Added MeshGradient+Codable.swift, implementing Codable conformance and a convenience initializer for mesh gradients (MeshGradient+Codable.swift).
  • Refined Gradient+Codable.swift by removing unnecessary nonisolated keywords for consistency and clarity. [1] [2] [3]

Code Generation Enhancements

  • Updated the code generator to handle mesh gradients, including default values, property generation, and shape style extensions (DefaultsGenerator.swift, ThemeFileGeneratorTests.swift). [1] [2]

Preview Generation

  • Extended the preview generator to add mesh gradient sections and card components, ensuring mesh gradients appear in generated previews if present (ThemePreviewGenerator.swift). [1] [2] [3] [4]

Comprehensive Testing

  • Added and updated tests to verify mesh gradient support in configuration, code generation, and previews, ensuring correct file outputs and preview content (ThemeConfigTests.swift, ThemeFileGeneratorTests.swift, ThemePreviewGeneratorTests.swift). [1] [2] [3] [4] [5] [6] [7] [8]Adds MeshGradient as a new themeable category, enabling the definition and application of mesh gradients within the app's theming system.

This includes codable support and default value generation, along with UI preview capabilities, mirroring existing support for colors, gradients and shadows.

Adds MeshGradient as a new themeable category, enabling the definition and application of mesh gradients within the app's theming system.

This includes codable support and default value generation, along with UI preview capabilities, mirroring existing support for colors, gradients and shadows.
@codecov
Copy link

codecov bot commented Feb 24, 2026

Codecov Report

❌ Patch coverage is 96.82203% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.87%. Comparing base (ec5f406) to head (6968f24).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
Sources/ThemeKit/MeshGradient+Codable.swift 75.00% 15 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #14      +/-   ##
==========================================
- Coverage   99.46%   98.87%   -0.59%     
==========================================
  Files          32       34       +2     
  Lines        1677     2141     +464     
==========================================
+ Hits         1668     2117     +449     
- Misses          9       24      +15     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds comprehensive support for mesh gradients as a new themeable category in the ThemeKit system. MeshGradient becomes a first-class citizen alongside colors, gradients, and shadows, with full integration across the theme configuration pipeline, code generation, and preview system.

Changes:

  • Added MeshGradient as a new themeable category with Codable support, automatic code generation, and UI preview capabilities
  • Extended the theme system infrastructure to handle mesh gradients throughout the configuration, generation, and preview workflows
  • Added comprehensive test coverage for mesh gradient functionality across configuration parsing, code generation, and preview generation

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
theme.schema.json Updated JSON schema to include meshGradients property with proper documentation
Sources/ThemeKit/MeshGradient+Codable.swift Added Codable conformance for MeshGradient with convenience initializer and point generation
Sources/ThemeKit/Gradient+Codable.swift Refactored to use nonisolated extension pattern consistently
Sources/ThemeKitGenerator/ThemeCategory.swift Added meshGradients case with all required properties for code generation
Sources/ThemeKitGenerator/ThemeConfig.swift Added meshGradients property and initialization support
Sources/ThemeKitGenerator/DefaultsGenerator.swift Added mesh gradient-specific default value generation
Sources/ThemeKitGenerator/ThemePreviewGenerator.swift Added mesh gradient preview section and component generation, adjusted heights
Tests/ThemeKitTests/MeshGradientCodableTests.swift Comprehensive test suite for mesh gradient encoding/decoding
Tests/ThemeKitGeneratorTests/ThemeConfigTests.swift Updated tests to verify mesh gradient configuration parsing
Tests/ThemeKitGeneratorTests/ThemeFileGeneratorTests.swift Added tests for mesh gradient code generation
Tests/ThemeKitGeneratorTests/ThemePreviewGeneratorTests.swift Added tests for mesh gradient preview generation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +27 to +39
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let width = try container.decode(Int.self, forKey: .width)
let height = try container.decode(Int.self, forKey: .height)
let colors = try container.decode([Color].self, forKey: .colors)
let points = try container.decodeIfPresent([SIMD2<Float>].self, forKey: .points)
self.init(
width: width,
height: height,
points: points ?? MeshGradient.pointsFrom(width: width, height: height),
colors: colors,
)
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

There is no validation to ensure the number of colors matches the expected count (width × height). The MeshGradient API requires exactly width × height colors for a valid mesh, but the decoding logic doesn't verify this constraint. If a mismatch occurs, the SwiftUI MeshGradient initializer may fail or produce unexpected behavior. Consider adding validation to throw a DecodingError if colors.count != width × height.

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +36
let points = try container.decodeIfPresent([SIMD2<Float>].self, forKey: .points)
self.init(
width: width,
height: height,
points: points ?? MeshGradient.pointsFrom(width: width, height: height),
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

No validation exists to ensure the points array (if provided) has the correct count matching width × height. When points are explicitly provided, they must match the mesh dimensions. If the count doesn't match, the SwiftUI MeshGradient initializer may fail. Consider validating that points.count == width × height when decoding explicit points.

Suggested change
let points = try container.decodeIfPresent([SIMD2<Float>].self, forKey: .points)
self.init(
width: width,
height: height,
points: points ?? MeshGradient.pointsFrom(width: width, height: height),
let decodedPoints = try container.decodeIfPresent([SIMD2<Float>].self, forKey: .points)
let resolvedPoints: [SIMD2<Float>]
if let decodedPoints {
let expectedCount = width * height
guard decodedPoints.count == expectedCount else {
throw DecodingError.dataCorruptedError(
forKey: .points,
in: container,
debugDescription: "Expected \(expectedCount) points for mesh of size \(width)x\(height), but found \(decodedPoints.count)."
)
}
resolvedPoints = decodedPoints
} else {
resolvedPoints = MeshGradient.pointsFrom(width: width, height: height)
}
self.init(
width: width,
height: height,
points: resolvedPoints,

Copilot uses AI. Check for mistakes.
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let width = try container.decode(Int.self, forKey: .width)
let height = try container.decode(Int.self, forKey: .height)
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

No validation exists for negative or zero width/height values. The decoding logic should validate that both width and height are positive integers (at least 2 for proper mesh gradient generation given the current pointsFrom implementation). Consider adding validation that throws a DecodingError for invalid dimensions.

Suggested change
let height = try container.decode(Int.self, forKey: .height)
let height = try container.decode(Int.self, forKey: .height)
// Validate decoded dimensions to avoid invalid mesh configurations and division by zero
guard width >= 2, height >= 2 else {
let invalidKey: CodingKeys = width < 2 ? .width : .height
throw DecodingError.dataCorruptedError(
forKey: invalidKey,
in: container,
debugDescription: "MeshGradient width and height must be at least 2. Received width=\(width), height=\(height)."
)
}

Copilot uses AI. Check for mistakes.
rozd and others added 2 commits February 24, 2026 20:48
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Enhances the MeshGradient Codable implementation to handle unsupported color and location types during encoding by throwing appropriate errors.

Also, avoids potential division by zero when calculating gradient points for 1x1 images.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +83 to +90
return [SIMD2<Float>(0.5, 0.5)]
}
return (0..<height).flatMap { row in
(0..<width).map { col in
SIMD2<Float>(
width > 1 ? Float(col) / Float(width - 1) : 0.5,
height > 1 ? Float(row) / Float(height - 1) : 0.5,
)
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

pointsFrom(width:height:) returns the center point (0.5, 0.5) for a 1×1 mesh, but the tests (and the function name implying a grid origin) expect the single point to be at (0, 0). Align the implementation with the intended coordinate convention (e.g., make 1×1 return (0,0) and handle width==1/height==1 without using 0.5 unless that is explicitly desired).

Suggested change
return [SIMD2<Float>(0.5, 0.5)]
}
return (0..<height).flatMap { row in
(0..<width).map { col in
SIMD2<Float>(
width > 1 ? Float(col) / Float(width - 1) : 0.5,
height > 1 ? Float(row) / Float(height - 1) : 0.5,
)
return [SIMD2<Float>(0.0, 0.0)]
}
return (0..<height).flatMap { row in
(0..<width).map { col in
let x = width == 1 ? 0.0 : Float(col) / Float(width - 1)
let y = height == 1 ? 0.0 : Float(row) / Float(height - 1)
return SIMD2<Float>(x, y)

Copilot uses AI. Check for mistakes.
Comment on lines 5 to 6
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The doc comment for ThemeFile.styles still says it only includes colors/gradients/shadows, but this PR adds mesh gradients as well. Update the comment to keep documentation consistent with the actual supported categories.

Copilot uses AI. Check for mistakes.
Comment on lines +384 to +391
@Test func convenienceInit_1x1_producesSingleOriginPoint() {
// Edge case: 1×1 grid has a single point, but (0/0, 0/0) is NaN;
// the implementation divides by (width-1) which is 0. Verify behavior.
let points = MeshGradient.pointsFrom(width: 1, height: 1)
#expect(points.count == 1)
let point = points[0]
// The single point should be at the origin and have finite coordinates.
#expect(point == SIMD2<Float>(0.0, 0.0))
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

This test asserts that a 1×1 grid produces (0,0), but MeshGradient.pointsFrom(width:height:) currently returns (0.5,0.5) for 1×1. Either update the test expectation/comment to match the chosen convention or adjust pointsFrom so the test matches the implementation.

Suggested change
@Test func convenienceInit_1x1_producesSingleOriginPoint() {
// Edge case: 1×1 grid has a single point, but (0/0, 0/0) is NaN;
// the implementation divides by (width-1) which is 0. Verify behavior.
let points = MeshGradient.pointsFrom(width: 1, height: 1)
#expect(points.count == 1)
let point = points[0]
// The single point should be at the origin and have finite coordinates.
#expect(point == SIMD2<Float>(0.0, 0.0))
@Test func convenienceInit_1x1_producesSingleCenterPoint() {
// Edge case: 1×1 grid has a single point. By convention, this lies at the
// center of the unit square (0.5, 0.5). Verify behavior and finiteness.
let points = MeshGradient.pointsFrom(width: 1, height: 1)
#expect(points.count == 1)
let point = points[0]
// The single point should be at the center and have finite coordinates.
#expect(point == SIMD2<Float>(0.5, 0.5))

Copilot uses AI. Check for mistakes.
Ensures that initial mesh gradient control points are consistently generated from the (0,0) origin for single-dimension grids (e.g., 1x1, 1xN, or Nx1). This provides a more predictable and standard basis for mesh interpolation.

Updates documentation to explicitly list mesh gradients as a supported style token.
@rozd rozd merged commit ef4070d into main Feb 25, 2026
1 of 3 checks passed
@rozd rozd deleted the feat/mesh-gradients branch February 25, 2026 06:36
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.

2 participants