Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds a macOS example app that demonstrates generating JSONRenderSwift Spec JSON via an AI provider and live-rendering it, while also enhancing the core library’s spec/schema guidance and decoding behavior to better support AI-generated JSON.
Changes:
- Added
Spec.schemaDescriptionto provide an LLM-friendly schema description for the JSONRenderSwift output format. - Updated
UIElementdecoding to tolerate missingpropsby defaulting to an empty dictionary. - Introduced a full Xcode example app project (views, model, AI service integration), plus starter unit/UI test targets and app assets.
Reviewed changes
Copilot reviewed 17 out of 19 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| Sources/JSONRenderSwift/Models/Spec.swift | Adds LLM schema description and custom UIElement decoding defaults. |
| example/example/AIService.swift | Adds AI provider/model factory used by the example app. |
| example/example/ChatView.swift | Implements the chat UI for prompting UI generation. |
| example/example/ChatViewModel.swift | Implements streaming generation, JSON extraction, and persistence for the example app. |
| example/example/ContentView.swift | Main split UI (chat + JSON/live preview). |
| example/example/PreviewPanel.swift | Adds JSON vs Live Render preview tabs using JSONRenderer. |
| example/example/SettingsView.swift | Adds settings UI for provider, endpoint, API key, and model selection. |
| example/example/exampleApp.swift | App entry point and macOS Settings scene. |
| example/example/Assets.xcassets/Contents.json | Adds asset catalog metadata. |
| example/example/Assets.xcassets/AccentColor.colorset/Contents.json | Adds accent color set metadata. |
| example/example/Assets.xcassets/AppIcon.appiconset/Contents.json | Adds app icon set metadata. |
| example/exampleTests/exampleTests.swift | Adds template unit test target source. |
| example/exampleUITests/exampleUITests.swift | Adds template UI test target source. |
| example/exampleUITests/exampleUITestsLaunchTests.swift | Adds UI test launch/screenshot template. |
| example/example.xcodeproj/project.pbxproj | Adds Xcode project configuration and package dependencies. |
| example/example.xcodeproj/project.xcworkspace/contents.xcworkspacedata | Adds workspace metadata. |
| example/example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved | Pins SwiftPM dependencies for the example project. |
| example/appicon.icon/icon.json | Adds Icon Composer configuration for the app icon. |
| example/appicon.icon/Assets/json-render-avatar-icon-1024x1024.png | Adds the source image used to generate the app icon. |
Files not reviewed (1)
- example/example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| on = try container.decodeIfPresent([String: ActionBindingOrArray].self, forKey: .on) | ||
| `repeat` = try container.decodeIfPresent(RepeatConfig.self, forKey: .repeat) | ||
| } | ||
|
|
There was a problem hiding this comment.
UIElement now implements init(from:) while still conforming to Codable, but there is no corresponding encode(to:). In Swift, providing a custom init(from:) prevents automatic synthesis of encode(to:), so this will break compilation unless encode(to:) is added (or the conformance is changed to Decodable).
| public func encode(to encoder: Encoder) throws { | |
| var container = encoder.container(keyedBy: CodingKeys.self) | |
| try container.encode(type, forKey: .type) | |
| try container.encode(props, forKey: .props) | |
| try container.encodeIfPresent(children, forKey: .children) | |
| try container.encodeIfPresent(visible, forKey: .visible) | |
| try container.encodeIfPresent(on, forKey: .on) | |
| try container.encodeIfPresent(`repeat`, forKey: .repeat) | |
| } |
| struct SettingsView: View { | ||
| @AppStorage("providerType") private var providerType: String = AIProviderType.openai.rawValue | ||
| @AppStorage("apiEndpoint") private var apiEndpoint: String = "" | ||
| @AppStorage("apiKey") private var apiKey: String = "" | ||
| @AppStorage("modelName") private var modelName: String = "" |
There was a problem hiding this comment.
The API key is persisted via @AppStorage("apiKey") (UserDefaults) which stores the value in plaintext on disk. For real credentials, prefer storing/retrieving from Keychain (and keep @AppStorage only for non-sensitive settings).
| let state = PersistedState(messages: messages, currentJSON: currentJSON) | ||
| do { | ||
| let data = try JSONEncoder().encode(state) | ||
| try data.write(to: Self.stateFileURL) |
There was a problem hiding this comment.
saveState() writes directly to the target file without .atomic/temp-file semantics. If the app is terminated mid-write, the JSON can be corrupted and subsequent loads will fail. Consider using data.write(to:options: .atomic) (and/or writing to a temporary file then moving).
| try data.write(to: Self.stateFileURL) | |
| try data.write(to: Self.stateFileURL, options: .atomic) |
| } catch { | ||
| // No saved state or failed to load - start fresh | ||
| print("[ChatViewModel] No saved state or failed to load: \(error)") |
There was a problem hiding this comment.
loadState() logs an error for the common “file not found” case, which will add noise on first launch. Consider handling the “no such file” error separately (silently start fresh) and only logging unexpected decoding/IO errors.
| } catch { | |
| // No saved state or failed to load - start fresh | |
| print("[ChatViewModel] No saved state or failed to load: \(error)") | |
| } catch let error as CocoaError where error.code == .fileReadNoSuchFile { | |
| // No saved state yet - start fresh silently | |
| } catch { | |
| // Failed to read or decode saved state - start fresh and log the unexpected error | |
| print("[ChatViewModel] Failed to load state: \(error)") |
| CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; | ||
| COPY_PHASE_STRIP = NO; | ||
| DEBUG_INFORMATION_FORMAT = dwarf; | ||
| DEVELOPMENT_TEAM = P9KK452K8P; |
There was a problem hiding this comment.
The project file hard-codes a specific DEVELOPMENT_TEAM value. This commonly prevents other contributors from building/signing the example app without editing the project. Consider removing the team ID from the checked-in project (or using a build setting override / leaving it blank).
| DEVELOPMENT_TEAM = P9KK452K8P; |
| GCC_WARN_UNUSED_FUNCTION = YES; | ||
| GCC_WARN_UNUSED_VARIABLE = YES; | ||
| LOCALIZATION_PREFERS_STRING_CATALOGS = YES; | ||
| MACOSX_DEPLOYMENT_TARGET = 26.2; |
There was a problem hiding this comment.
MACOSX_DEPLOYMENT_TARGET is set to 26.2 in the project. Please verify this is the intended minimum supported macOS version, since an overly high deployment target will make the example app impossible to build/run on most systems and CI images.
| MACOSX_DEPLOYMENT_TARGET = 26.2; | |
| MACOSX_DEPLOYMENT_TARGET = 13.0; |
No description provided.