Skip to content

feat: first commit#1

Merged
sirily11 merged 1 commit intomainfrom
first-pr
Apr 3, 2026
Merged

feat: first commit#1
sirily11 merged 1 commit intomainfrom
first-pr

Conversation

@sirily11
Copy link
Copy Markdown
Contributor

@sirily11 sirily11 commented Apr 3, 2026

Implement the json-render from vercel

Copilot AI review requested due to automatic review settings April 3, 2026 16:52
@autopilot-project-manager autopilot-project-manager bot added the enhancement New feature or request label Apr 3, 2026
Copy link
Copy Markdown

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

Initial import of JSONRenderSwift, a Swift Package that renders AI-generated JSON UI specs into native SwiftUI views, with a macro-based component catalog and schema generation tooling.

Changes:

  • Adds core models (Spec/UIElement/PropValue/JSONValue), resolution (templates/conditions), and rendering pipeline (renderer, repeat, registry, built-in components).
  • Introduces state system with backend routing (in-memory + SwiftData persisted) and an action execution layer.
  • Adds schema generation tooling (macro + build plugin + CLI) plus a comprehensive test suite and expanded README/CI scaffolding.

Reviewed changes

Copilot reviewed 68 out of 70 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
Tests/JSONRenderSwiftTests/State/StateStoreTests.swift Unit tests for StateStore get/set/remove/update + backend routing
Tests/JSONRenderSwiftTests/Resolution/VisibilityEvaluatorTests.swift Tests for visibility evaluation logic
Tests/JSONRenderSwiftTests/Resolution/TemplateInterpolatorTests.swift Tests for ${...} template interpolation
Tests/JSONRenderSwiftTests/Resolution/PropResolverTests.swift Tests for resolving PropValue expressions and bindings
Tests/JSONRenderSwiftTests/Renderer/RendererTests.swift Component-level + integration renderer tests (ViewInspector)
Tests/JSONRenderSwiftTests/Models/VisibilityConditionTests.swift Codable tests for visibility condition model
Tests/JSONRenderSwiftTests/Models/SpecDecodingTests.swift Codable tests for Spec/UIElement decoding
Tests/JSONRenderSwiftTests/Models/PropValueDecodingTests.swift Codable tests for PropValue decoding/encoding
Tests/JSONRenderSwiftTests/Models/JSONPointerTests.swift Tests for RFC6901 pointer parsing/resolve/set/remove
Tests/JSONRenderSwiftTests/Models/AnyCodableTests.swift Tests for JSONValue decoding, truthiness, display string
Tests/JSONRenderSwiftTests/Actions/ActionExecutorTests.swift Tests for built-in actions + custom handler
Tests/JSONRenderMacroTests/ComponentMacroTests.swift Macro expansion tests for @Component
Sources/SchemaGeneratorTool/main.swift CLI that scans sources for @Component and emits schema JSON
Sources/JSONRenderSwift/State/StateStore.swift Central observable state store + backend routing
Sources/JSONRenderSwift/State/StateBackend.swift State backend protocol
Sources/JSONRenderSwift/State/PersistedStateEntry.swift SwiftData model for persisted state entries
Sources/JSONRenderSwift/State/PersistedStateBackend.swift SwiftData-backed state backend implementation
Sources/JSONRenderSwift/State/LocalStateBackend.swift In-memory backend + deepMerge helper
Sources/JSONRenderSwift/Resolution/VisibilityEvaluator.swift Visibility evaluation + ResolutionContext
Sources/JSONRenderSwift/Resolution/TemplateInterpolator.swift Template interpolation implementation
Sources/JSONRenderSwift/Resolution/PropResolver.swift PropValue → JSONValue resolution + bindings extraction
Sources/JSONRenderSwift/Renderer/RepeatScope.swift Repeat context environment plumbing
Sources/JSONRenderSwift/Renderer/RepeatRenderer.swift Repeat rendering for array state paths
Sources/JSONRenderSwift/Renderer/JSONRenderer.swift Top-level SwiftUI renderer view
Sources/JSONRenderSwift/Renderer/ElementRenderer.swift Recursive element rendering + action dispatch
Sources/JSONRenderSwift/Preview/PreviewDemos.swift Demo previews/spec examples
Sources/JSONRenderSwift/Models/VisibilityCondition.swift Visibility condition model + decoding operators
Sources/JSONRenderSwift/Models/Spec.swift Spec/UIElement model definitions
Sources/JSONRenderSwift/Models/RepeatConfig.swift Repeat configuration model
Sources/JSONRenderSwift/Models/PropValue.swift PropValue union + Codable implementation
Sources/JSONRenderSwift/Models/JSONPointer.swift JSON pointer implementation
Sources/JSONRenderSwift/Models/AnyCodable.swift JSONValue type-erased JSON representation
Sources/JSONRenderSwift/Models/ActionBinding.swift Action binding model (single or multiple)
Sources/JSONRenderSwift/JSONRenderSwift.swift Package “surface” documentation stub
Sources/JSONRenderSwift/Components/JRZStack.swift Built-in ZStack component
Sources/JSONRenderSwift/Components/JRVStack.swift Built-in VStack component
Sources/JSONRenderSwift/Components/JRToggle.swift Built-in Toggle component w/ binding
Sources/JSONRenderSwift/Components/JRTextField.swift Built-in TextField component w/ binding
Sources/JSONRenderSwift/Components/JRText.swift Built-in Text component + parsing helpers
Sources/JSONRenderSwift/Components/JRSpacer.swift Built-in Spacer component
Sources/JSONRenderSwift/Components/JRSlider.swift Built-in Slider component w/ binding
Sources/JSONRenderSwift/Components/JRSection.swift Built-in Section component
Sources/JSONRenderSwift/Components/JRProgressView.swift Built-in ProgressView component
Sources/JSONRenderSwift/Components/JRList.swift Built-in List component
Sources/JSONRenderSwift/Components/JRLink.swift Built-in Link component
Sources/JSONRenderSwift/Components/JRLabel.swift Built-in Label component
Sources/JSONRenderSwift/Components/JRImage.swift Built-in Image/AsyncImage component
Sources/JSONRenderSwift/Components/JRHStack.swift Built-in HStack component
Sources/JSONRenderSwift/Components/JRForm.swift Built-in Form component
Sources/JSONRenderSwift/Components/JRDivider.swift Built-in Divider component
Sources/JSONRenderSwift/Components/JRCard.swift Built-in Card component + style handling
Sources/JSONRenderSwift/Components/JRButton.swift Built-in Button component + press event
Sources/JSONRenderSwift/Components/JRBadge.swift Built-in Badge component
Sources/JSONRenderSwift/Components/BuiltInComponents.swift Registers built-ins into registry
Sources/JSONRenderSwift/Catalog/RenderableComponent.swift Bridge protocol for @Component views into renderer
Sources/JSONRenderSwift/Catalog/ComponentRegistry.swift Component registry + environment wiring + schema export
Sources/JSONRenderSwift/Actions/ActionExecutor.swift Action dispatch + built-in state actions
Sources/JSONRenderMacros/Plugin.swift Macro plugin entry point
Sources/JSONRenderMacros/ComponentMacro.swift @Component macro implementation
Sources/JSONRenderClient/Prop.swift @Prop property wrapper
Sources/JSONRenderClient/ComponentMacro.swift Public macro declaration for clients
Sources/JSONRenderClient/ComponentDefinition.swift ComponentDefinition + prop metadata types
Sources/JSONRenderClient/ComponentCatalog.swift Component catalog protocol
README.md Full documentation for spec format, components, state, schema
Plugins/JSONRenderSchemaPlugin/JSONRenderSchemaPlugin.swift Build tool plugin to generate components.json
Package.swift SwiftPM manifest (targets, macros, tool, plugin, tests)
Package.resolved Dependency lockfile
.vscode/launch.json VS Code debug configs
.gitignore Ignore build/artifact files
.github/workflows/ci.yml CI workflow (build + test)

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

Comment on lines +24 to +27
/// Get a value at a JSON Pointer path.
public func get(_ path: String) -> JSONValue? {
JSONPointer(path).resolve(in: state)
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

State mutations are locked, but rebuildState() reads defaultBackend.stateSlice / backend.stateSlice and writes state without holding the lock, and get(_:) reads state without synchronization. Since StateStore is @unchecked Sendable, this can lead to data races; consider protecting rebuildState and get with the same lock (or making StateStore @MainActor).

Copilot uses AI. Check for mistakes.
Comment thread README.md

## Built-in Components

18 components ship out of the box. All are extensible.
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

README says "18 components ship out of the box", but BuiltInComponents.registerAll registers 19 components (including Form and Section), and tests assert 19. Update the documented count to match the actual registry.

Suggested change
18 components ship out of the box. All are extensible.
19 components ship out of the box. All are extensible.

Copilot uses AI. Check for mistakes.
Comment thread README.md
Comment on lines +350 to +351
stateSlice = deepMerge(base: stateSlice, overlay: state)
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

This example uses deepMerge(...), but deepMerge is declared internal in LocalStateBackend.swift, so package consumers can’t call it. Either make deepMerge public (and document it), or update the README to show an inlined merge implementation for custom backends.

Suggested change
stateSlice = deepMerge(base: stateSlice, overlay: state)
}
stateSlice = merge(stateSlice, with: state)
}
private func merge(_ base: JSONValue, with overlay: JSONValue) -> JSONValue {
guard case let .object(baseObject) = base,
case let .object(overlayObject) = overlay else {
return overlay
}
var merged = baseObject
for (key, overlayValue) in overlayObject {
if let baseValue = merged[key] {
merged[key] = merge(baseValue, with: overlayValue)
} else {
merged[key] = overlayValue
}
}
return .object(merged)
}

Copilot uses AI. Check for mistakes.
Comment thread README.md
| `setState` | `path`, `value` | Set a value at a state path |
| `pushState` | `path`, `value` | Append to a state array |
| `removeState` | `path`, `index` | Remove item from a state array by index |
| `toggleState` | `path` | Toggle a boolean state value |
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The "Built-in actions" list omits incrementState and decrementState, which are implemented in ActionExecutor.registerBuiltIns() and used in the previews. Consider documenting them here (including the optional amount param).

Suggested change
| `toggleState` | `path` | Toggle a boolean state value |
| `toggleState` | `path` | Toggle a boolean state value |
| `incrementState` | `path`, `amount` (optional) | Increment a numeric state value |
| `decrementState` | `path`, `amount` (optional) | Decrement a numeric state value |

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +89
for backend in backends {
let prefix = backend.pathPrefix
if !prefix.isEmpty && path.hasPrefix(prefix) && prefix.count > bestLen {
best = backend
bestLen = prefix.count
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

routeBackend(for:) uses path.hasPrefix(prefix), which can mis-route when a prefix is a substring of another path segment (e.g. "/local" also matches "/locality"). Consider matching on path-segment boundaries (exact match or prefix + "/").

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +17
ForEach(Array(items.enumerated()), id: \.offset) { index, item in
let scope = RepeatScopeValue(
item: item,
index: index,
basePath: "\(config.statePath)/\(index)"
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

RepeatConfig.key is ignored here, so repeated rows won’t have stable identity when items are inserted/removed/reordered. Prefer using the key field (when present) to derive a stable id from each item, falling back to index only when necessary.

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +82
case .bindItemRef(let field):
if let basePath = context.repeatItem != nil ? "" : nil {
// For $bindItem, construct the full state path from repeat context
// This requires the basePath from the repeat scope
if field.isEmpty {
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

This $bindItem binding-path construction uses an empty-string basePath, which yields incorrect/ambiguous bindings (and ignores the repeat scope’s base path). Consider removing this overload or threading a real repeat base path through ResolutionContext.

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +15
public init(key: String, value: JSONValue) {
self.key = key
self.jsonData = (try? JSONEncoder().encode(value)) ?? Data()
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

Encoding failures are silently swallowed by writing Data() (and later decoding back to .null), which can hide persistence corruption and overwrite valid values. Consider surfacing/logging encoding errors and avoiding Data() as a sentinel.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +66
let modified: any View = if resizable {
image.resizable()
.aspectRatio(contentMode: parseContentMode())
.frame(
width: ctx.resolvedProps["width"]?.doubleValue.map { CGFloat($0) },
height: ctx.resolvedProps["height"]?.doubleValue.map { CGFloat($0) }
)
} else {
image.frame(
width: ctx.resolvedProps["width"]?.doubleValue.map { CGFloat($0) },
height: ctx.resolvedProps["height"]?.doubleValue.map { CGFloat($0) }
)
}

if let color {
AnyView(modified).foregroundColor(parseColor(color))
} else {
AnyView(modified)
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

Using let modified: any View = ... and then type-erasing later is likely to fail compilation because any View existentials typically can’t be passed to AnyView’s generic initializer. Prefer building the conditional view directly in a @ViewBuilder, or type-erase each branch to AnyView explicitly.

Suggested change
let modified: any View = if resizable {
image.resizable()
.aspectRatio(contentMode: parseContentMode())
.frame(
width: ctx.resolvedProps["width"]?.doubleValue.map { CGFloat($0) },
height: ctx.resolvedProps["height"]?.doubleValue.map { CGFloat($0) }
)
} else {
image.frame(
width: ctx.resolvedProps["width"]?.doubleValue.map { CGFloat($0) },
height: ctx.resolvedProps["height"]?.doubleValue.map { CGFloat($0) }
)
}
if let color {
AnyView(modified).foregroundColor(parseColor(color))
} else {
AnyView(modified)
}
if resizable {
if let color {
image.resizable()
.aspectRatio(contentMode: parseContentMode())
.frame(
width: ctx.resolvedProps["width"]?.doubleValue.map { CGFloat($0) },
height: ctx.resolvedProps["height"]?.doubleValue.map { CGFloat($0) }
)
.foregroundColor(parseColor(color))
} else {
image.resizable()
.aspectRatio(contentMode: parseContentMode())
.frame(
width: ctx.resolvedProps["width"]?.doubleValue.map { CGFloat($0) },
height: ctx.resolvedProps["height"]?.doubleValue.map { CGFloat($0) }
)
}
} else {
if let color {
image.frame(
width: ctx.resolvedProps["width"]?.doubleValue.map { CGFloat($0) },
height: ctx.resolvedProps["height"]?.doubleValue.map { CGFloat($0) }
)
.foregroundColor(parseColor(color))
} else {
image.frame(
width: ctx.resolvedProps["width"]?.doubleValue.map { CGFloat($0) },
height: ctx.resolvedProps["height"]?.doubleValue.map { CGFloat($0) }
)
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +57
// Merge resolved params from the action binding with emitted params
let actionContext = ResolutionContext(
state: store.state,
repeatItem: nil,
repeatIndex: nil
)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

Action-binding params are resolved with a ResolutionContext that hard-codes repeatItem/repeatIndex to nil, so $item / $index can’t be used inside action params for repeated elements. Consider propagating the current repeat scope into actionContext (e.g. using the same repeatItem/repeatIndex as the element render context).

Copilot uses AI. Check for mistakes.
# Conflicts:
#	README.md
@sirily11 sirily11 merged commit d383377 into main Apr 3, 2026
1 check passed
@sirily11 sirily11 deleted the first-pr branch April 3, 2026 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants