fix: ColorList.clone not cloning colors_c (#9613)

### Problem
Custom icon configuration (`macos-icon = custom-style` with
`macos-icon-screen-color`) stopped working, reverting to the default
icon.

### Root cause
The `ColorList.clone()` method only cloned the `colors` array but not
the `colors_c` array. The Swift code reads from `colors_c` via the C API
(`ghostty_config_get`), so when configs were cloned, the C-accessible
color list was empty.

### Why it broke
This bug was introduced in the original implementation in 29929a473 (Dec
2024), but remained dormant until commit f60bdb0fa (Sep 19, 2025), which
moved the icon-setting `switch` statement into `syncAppearance()`. Since
`syncAppearance()` is called with cloned configs from
`ghosttyConfigDidChange()` (which receives cloned configs from
`Ghostty.App.swift:1639`), the icon code now ran with cloned configs
that had empty `colors_c` arrays.

### Fix
Clone both arrays in `ColorList.clone()`:
```zig
.colors = try self.colors.clone(alloc),
.colors_c = try self.colors_c.clone(alloc),  // Added
```

### Testing
- Added ColorList.test.clone test case that verifies both colors and
colors_c arrays are properly cloned
- Verified test fails without the fix (expected 3 colors_c items, found
0)
- Verified test passes with the fix
- Confirmed custom icon now persists correctly with both initial config
load and subsequent config change notifications

### Discussion
I opened a discussion to report this
([9616](https://github.com/ghostty-org/ghostty/discussions/9616)) that
this PR will resolve.

> [!NOTE]
> **LLM Usage Disclosure** 
> This bug was investigated and debugged with assistance from Claude
Code. The root cause analysis and fix were developed through interactive
debugging.
pull/9626/head
Mitchell Hashimoto 2025-11-17 06:41:03 -08:00 committed by GitHub
commit b7be27b1f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 21 additions and 0 deletions

View File

@ -5203,6 +5203,7 @@ pub const ColorList = struct {
) Allocator.Error!Self {
return .{
.colors = try self.colors.clone(alloc),
.colors_c = try self.colors_c.clone(alloc),
};
}
@ -5281,6 +5282,26 @@ pub const ColorList = struct {
try p.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = #000000,#ffffff\n", buf.written());
}
test "clone" {
const testing = std.testing;
var arena = ArenaAllocator.init(testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
var source: Self = .{};
try source.parseCLI(alloc, "#ff0000,#00ff00,#0000ff");
const cloned = try source.clone(alloc);
try testing.expect(source.equal(cloned));
try testing.expectEqual(source.colors_c.items.len, cloned.colors_c.items.len);
for (source.colors_c.items, cloned.colors_c.items) |src_c, clone_c| {
try testing.expectEqual(src_c.r, clone_c.r);
try testing.expectEqual(src_c.g, clone_c.g);
try testing.expectEqual(src_c.b, clone_c.b);
}
}
};
/// Palette is the 256 color palette for 256-color mode. This is still