commit 5001ec05889189ad6f7463133ec7d80e5bb4da34
parent 2c9154ea19a8d2bf0878dcdc41467af482242fa9
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date: Mon, 21 Sep 2026 07:30:54 -0300
appicon: arrange the oracle the way a shipping icon is arranged
The first fixture crashed actool, which the note about that crash had said
adding lighting or a blend mode would avoid. It does not: the same values
across two groups still fall over, so the note now says what was seen.
Diffstat:
2 files changed, 58 insertions(+), 40 deletions(-)
diff --git a/appicon/appicon.go b/appicon/appicon.go
@@ -13,17 +13,23 @@
// # A crash worth knowing about
//
// actool from Xcode 26.6 crashes, rather than reporting a problem, on some
-// groups that specialise translucency. A group holding only a shadow and
-// translucency specialised for the default appearance and for tinted brings
-// it down with an exception from inside its own asset selection. The same
-// group compiles once it also carries lighting, a blend mode, or a
-// translucency for dark, and so does the equivalent group taken from a
-// shipping app.
+// bundles that specialise translucency for the default appearance and for
+// tinted. It falls over with an exception from inside its own asset
+// selection, naming nothing.
//
-// The trigger is not fully characterised, so this package writes what it is
-// given and leaves the judgement to the compiler. A bundle that fails to
-// compile with an exception rather than an error is worth reshaping before
-// it is worth debugging.
+// Two arrangements are known to bring it down: a group carrying only a
+// shadow and that pair of translucencies, and a bundle spreading the pair
+// and the layer they apply to across two groups. Two are known to compile:
+// the same values gathered into one group with the layer they apply to,
+// which is how a shipping app arranges them, and the pair with a
+// translucency for dark added.
+//
+// The trigger is not characterised beyond that, and it is not simply the
+// group's own fields: a group carrying lighting and a blend mode alongside
+// the pair compiles on its own and crashes in a bundle of two groups. So
+// this package writes what it is given and leaves the judgement to the
+// compiler. A bundle that fails with an exception rather than an error is
+// worth rearranging before it is worth debugging.
package appicon
import (
diff --git a/appicon/oracle_darwin_test.go b/appicon/oracle_darwin_test.go
@@ -97,47 +97,59 @@ func TestActoolTakesASingleLayer(t *testing.T) {
}
// TestActoolTakesTheAppearanceFields covers the fields that vary by
-// appearance together, in the arrangement a shipping app uses. They have
-// nowhere else to be checked: a manifest actool ignores looks exactly like
-// one it honours.
+// appearance together, arranged the way a shipping app arranges them: one
+// group holding the specialised values and the layer they apply to. Spread
+// across two groups the same values crash actool, which is the arrangement
+// the package documentation warns about.
func TestActoolTakesTheAppearanceFields(t *testing.T) {
- material := 1.0
bundle := written(t, "Composed", Bundle{
Fills: []Specialized[Fill]{
{Value: NamedFill("system-light")},
{Appearance: AppearanceDark, Value: NamedFill("system-dark")},
},
+ Groups: []Group{{
+ BlendModes: []Specialized[string]{{Appearance: AppearanceTinted, Value: "normal"}},
+ Lighting: "individual",
+ Specular: true,
+ Shadow: &Shadow{Kind: "layer-color", Opacity: 0.5},
+ Translucencies: []Specialized[Translucency]{
+ {Value: Translucency{Enabled: true, Value: 0.84}},
+ {Appearance: AppearanceTinted, Value: Translucency{Enabled: false, Value: 0.84}},
+ },
+ Layers: []Layer{{
+ Name: "Front",
+ Image: layer(210, color.NRGBA{R: 40, G: 70, B: 200, A: 255}),
+ Glass: true,
+ Position: &Position{Scale: 1.24, Translation: [2]float64{0, 0}},
+ Fills: []Specialized[Fill]{
+ {Appearance: AppearanceDark, Value: NamedFill("automatic")},
+ {Appearance: AppearanceTinted, Value: GradientFill(
+ "display-p3:0.90000,0.90000,0.90000,0.83000",
+ "srgb:1.00000,1.00000,1.00000,0.41987",
+ )},
+ },
+ BlendModes: []Specialized[string]{{Appearance: AppearanceDark, Value: "lighten"}},
+ }},
+ }},
+ })
+ compiled(t, compile(t, bundle))
+}
+
+// TestActoolTakesSeveralGroups covers a stack of groups, which is how an
+// icon with a background and a mark on top is built.
+func TestActoolTakesSeveralGroups(t *testing.T) {
+ bundle := written(t, "Stacked", Bundle{
Groups: []Group{
{
- BlendModes: []Specialized[string]{{Appearance: AppearanceTinted, Value: "normal"}},
- BlurMaterials: []Specialized[float64]{{Value: material}},
- Lighting: "individual",
- Specular: true,
- Shadow: &Shadow{Kind: "layer-color", Opacity: 0.5},
- Translucencies: []Specialized[Translucency]{
- {Value: Translucency{Enabled: true, Value: 0.84}},
- {Appearance: AppearanceTinted, Value: Translucency{Enabled: false, Value: 0.84}},
- },
- Layers: []Layer{{
- Name: "Back",
- Image: layer(210, color.NRGBA{R: 40, G: 70, B: 200, A: 255}),
- }},
+ Layers: []Layer{{Name: "Back", Image: layer(210, color.NRGBA{R: 40, G: 70, B: 200, A: 255})}},
+ Shadow: &Shadow{Kind: ShadowNeutral, Opacity: 0.5},
},
{
Layers: []Layer{{
- Name: "Front",
- Image: layer(130, color.NRGBA{R: 240, G: 240, B: 250, A: 255}),
- Glass: true,
- Hidden: false,
- Position: &Position{Scale: 1.24, Translation: [2]float64{0, -12}},
- Fills: []Specialized[Fill]{
- {Appearance: AppearanceDark, Value: NamedFill("automatic")},
- {Appearance: AppearanceTinted, Value: GradientFill(
- "display-p3:0.90000,0.90000,0.90000,0.83000",
- "srgb:1.00000,1.00000,1.00000,0.41987",
- )},
- },
- BlendModes: []Specialized[string]{{Appearance: AppearanceDark, Value: "lighten"}},
+ Name: "Front",
+ Image: layer(130, color.NRGBA{R: 240, G: 240, B: 250, A: 255}),
+ Glass: true,
+ Hidden: false,
}},
},
},