ReviewOS

also looking at this

craft-native/craft

feat: finish native customization and platform seams

#401
Merged glennmichael123 wants to merge codex/issues-388-400-344 into main
21 files +707 -17

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

packages/zig/src/ios_config.zigmodified+20-3
Changes to packages/zig/src/ios_config.zig
@@ -185,7 +185,7 @@ const number_keys = [_][]const u8{ "splashMaxSeconds", "requestTimeoutSeconds" }
185185/// value inside it throws; only a null at the top level is dropped.
186186const optional_string_map_keys = [_][]const u8{ "nativeScreens", "shareStorage" };
187187/// Swift `Codable` structs, whose fields are checked by `checkObject`.
188const optional_object_keys = [_]Nested{ .{ .key = "backgroundRefresh", .fields = &background_refresh_fields }, .{ .key = "crashReporting", .fields = &crash_reporting_fields } };
188const optional_object_keys = [_]Nested{ .{ .key = "backgroundRefresh", .fields = &background_refresh_fields }, .{ .key = "crashReporting", .fields = &crash_reporting_fields }, .{ .key = "tabBar", .fields = &tab_bar_fields } };
189189/// Swift arrays of `Codable` structs. Every element is checked.
190190const optional_array_of_object_keys = [_]Nested{ .{ .key = "tabs", .fields = &tab_fields }, .{ .key = "appShortcuts", .fields = &app_shortcut_fields } };
191191
@@ -199,8 +199,8 @@ const Nested = struct { key: []const u8, fields: []const Field };
199199/// ignores a property's initial value, so `BackgroundRefresh.enabled`, which
200200/// defaults to false, still throws when it is missing or null. An optional
201201/// field may be missing or null. Both checked against `swiftc`.
202const Field = struct { name: []const u8, kind: Kind, required: bool };
203const Kind = enum { string, bool, number, string_array };
202const Field = struct { name: []const u8, kind: Kind, required: bool, object_fields: []const Field = &.{} };
203const Kind = enum { string, bool, number, string_array, object };
204204
205205/// `CraftConfig.BackgroundRefresh`.
206206const background_refresh_fields = [_]Field{
@@ -219,6 +219,15 @@ const crash_reporting_fields = [_]Field{
219219 .{ .name = "sessions", .kind = .bool, .required = false },
220220};
221221
222/// `CraftConfig.TabBar` and its optional selected-indicator styling.
223const tab_selection_fields = [_]Field{
224 .{ .name = "cornerRadius", .kind = .number, .required = false },
225 .{ .name = "color", .kind = .string, .required = false },
226};
227const tab_bar_fields = [_]Field{
228 .{ .name = "selection", .kind = .object, .required = false, .object_fields = &tab_selection_fields },
229};
230
222231/// `CraftConfig.Tab`.
223232const tab_fields = [_]Field{
224233 .{ .name = "id", .kind = .string, .required = true },
@@ -260,6 +269,7 @@ fn isKind(v: std.json.Value, kind: Kind) bool {
260269 .bool => v == .bool,
261270 .number => isNumber(v),
262271 .string_array => isStringArray(v),
272 .object => v == .object,
263273 };
264274}
265275
@@ -277,6 +287,7 @@ fn checkObject(v: std.json.Value, fields: []const Field) bool {
277287 continue;
278288 }
279289 if (!isKind(present, field.kind)) return false;
290 if (field.kind == .object and !checkObject(present, field.object_fields)) return false;
280291 }
281292 return true;
282293}
@@ -840,6 +851,9 @@ test "nested types decode field by field, defaults or not" {
840851 "\"tabs\":[{\"id\":\"health\",\"title\":\"Health\",\"selectedSymbol\":null,\"selectedIconTint\":null,\"selectedIconPulse\":null}]",
841852 "\"crashReporting\":{\"key\":\"bughq_k\"}",
842853 "\"crashReporting\":{\"key\":\"bughq_k\",\"host\":\"https://bughq.org\",\"environment\":null,\"sessions\":false}",
854 "\"tabBar\":{}",
855 "\"tabBar\":{\"selection\":{\"cornerRadius\":8,\"color\":\"#123456\"}}",
856 "\"tabBar\":{\"selection\":null}",
843857 }) |good| {
844858 const json = try completeConfig(testing.allocator, good);
845859 defer testing.allocator.free(json);
@@ -862,6 +876,9 @@ test "nested types decode field by field, defaults or not" {
862876 "\"tabs\":[{\"id\":\"a\",\"title\":\"A\",\"selectedIconPulse\":\"false\"}]",
863877 "\"tabs\":[null]",
864878 "\"tabs\":{}",
879 "\"tabBar\":[]",
880 "\"tabBar\":{\"selection\":{\"cornerRadius\":\"8\"}}",
881 "\"tabBar\":{\"selection\":{\"color\":42}}",
865882 }) |bad| {
866883 const json = try completeConfig(testing.allocator, bad);
867884 defer testing.allocator.free(json);
packages/zig/test/ios_conformance_test.zigmodified+3-1
Changes to packages/zig/test/ios_conformance_test.zig
@@ -1123,13 +1123,15 @@ test "Zig validates every field of the nested types Swift decodes, the way Swift
11231123 "bool"
11241124 else if (std.mem.eql(u8, base, "Double"))
11251125 "number"
1126 else if (std.mem.eql(u8, base, "TabSelection"))
1127 "object"
11261128 else {
11271129 std.debug.print("CraftConfig.{s}.{s} is a {s}, which ios_config.zig has no Kind for.\n", .{ struct_name, member.name, base });
11281130 return error.NestedFieldKindUnknown;
11291131 };
11301132
11311133 var expected_buf: [256]u8 = undefined;
1132 const expected = try std.fmt.bufPrint(&expected_buf, ".,", .{
1134 const expected = try std.fmt.bufPrint(&expected_buf, ".{{ .name = \"{s}\", .kind = .{s}, .required = {s}", .{
11331135 member.name, kind, if (optional) "false" else "true",
11341136 });
11351137 if (std.mem.indexOf(u8, zig_fields, expected) == null) {