diff --git a/crates/compose/src/effects.rs b/crates/compose/src/effects.rs
--- a/crates/compose/src/effects.rs
+++ b/crates/compose/src/effects.rs
@@ -2203,6 +2203,17 @@ pub(crate) fn composite_with_effects_prepared(
     for (p, a) in lay.px.iter_mut().zip(&shape.v) {
         p[3] *= a.min(1.0);
     }
+    // An outer bevel lies beneath the layer's strokes (Photoshop draws the strokes over it), so it
+    // is painted before them: a Normal outside stroke covers it instead of being shaded by it.
+    for (i, e) in rev() {
+        if let Effect::BevelEmboss(b) = e
+            && maps.bevel_paint[i] == BevelPaint::Outer
+        {
+            let (hi, sh) = (fx(i, 0), fx(i, 1));
+            paint_color(&mut work, &hi, rgb(&b.highlight_color), b.highlight.blend, b.highlight.opacity);
+            paint_color(&mut work, &sh, rgb(&b.shadow_color), b.shadow.blend, b.shadow.opacity);
+        }
+    }
     // Shape layers without a filled outline stroke their vector outline, estimated from local
     // coverage; every other layer strokes its shape. The width then picks the field.
     let vector_outline = vector_shape && !maps.outline;
@@ -2270,15 +2281,6 @@ pub(crate) fn composite_with_effects_prepared(
             }
         }
     }
-    for (i, e) in rev() {
-        if let Effect::BevelEmboss(b) = e
-            && maps.bevel_paint[i] == BevelPaint::Outer
-        {
-            let (hi, sh) = (fx(i, 0), fx(i, 1));
-            paint_color(&mut work, &hi, rgb(&b.highlight_color), b.highlight.blend, b.highlight.opacity);
-            paint_color(&mut work, &sh, rgb(&b.shadow_color), b.shadow.blend, b.shadow.opacity);
-        }
-    }
 
     // Layer (with interior effects) onto the exterior result, in the layer's mode.
     let mode = if premerge || layer.blend == BlendMode::PassThrough { BlendMode::Normal } else { layer.blend };
diff --git a/crates/compose/src/tests.rs b/crates/compose/src/tests.rs
--- a/crates/compose/src/tests.rs
+++ b/crates/compose/src/tests.rs
@@ -1248,6 +1248,33 @@ fn stroke(size: f32, position: StrokePosition) -> Effect {
     })
 }
 
+/// The pixel at (`x`, 20) of a red square carrying `effects`, in a white document of `depth`.
+fn effects_px_at_depth(depth: SampleType, effects: Vec<Effect>, x: i32) -> [f32; 4] {
+    let mut d = Document::with_background("t", Size::new(40, 40), ColorMode::Rgb, depth, Color::WHITE);
+    let fmt = d.pixel_format();
+    let mut l = Layer::raster("sq", fmt);
+    l.surface_mut().unwrap().fill_rect(Rect::new(10, 10, 30, 30), &photocraft_raster::from_rgba(&fmt, [1.0, 0.0, 0.0, 1.0]));
+    l.effects.items = effects;
+    d.layers.push(l);
+    px(&d, x, 20)
+}
+
+#[test]
+fn outer_bevel_lies_beneath_an_outside_stroke() {
+    // An outer bevel shades the backdrop around the layer, and Photoshop draws the strokes over
+    // it: a Normal outside stroke keeps its own colour where the bevel's shading reaches.
+    use photocraft_doc::BevelStyle;
+    for depth in [SampleType::U8, SampleType::U16, SampleType::F32] {
+        let bevel_only = vec![bevel_fx(BevelStyle::OuterBevel, 0.0)];
+        // The 5 px bevel is shaded on the side facing away from the light: the square's right
+        // edge is at x = 30, so x = 31 is 1 px out, inside both the bevel and the 4 px stroke.
+        let shaded = effects_px_at_depth(depth, bevel_only, 31);
+        assert!(shaded[0] < 0.9, "{depth:?}: the bevel shades the backdrop here: {shaded:?}");
+        let both = vec![bevel_fx(BevelStyle::OuterBevel, 0.0), stroke(4.0, StrokePosition::Outside)];
+        let under = effects_px_at_depth(depth, both, 31);
+        assert!(close4(under, [0.0, 0.0, 1.0, 1.0]), "{depth:?}: the stroke covers the bevel: {under:?}");
+    }
+}
 #[test]
 fn outside_stroke_width() {
     let d = fx_doc(vec![stroke(3.0, StrokePosition::Outside)]);
diff --git a/crates/gpu/src/plan.rs b/crates/gpu/src/plan.rs
--- a/crates/gpu/src/plan.rs
+++ b/crates/gpu/src/plan.rs
@@ -1298,6 +1298,26 @@ impl<'a> Planner<'a> {
         // The shape's own alpha.
         let c = self.retain(content);
         l = init(self, l, Some(c), INIT_ALPHA, 1.0);
+        // An outer bevel lies beneath the strokes (compose::composite_with_effects_prepared).
+        for &(i, e) in &rev {
+            if let Effect::BevelEmboss(b) = e
+                && bevel_paint(b) == photocraft_compose::effects::BevelPaint::Outer
+            {
+                let k = 0;
+                w = self.paint(
+                    w,
+                    content,
+                    Cov::Map(map(i, k), 0.0),
+                    &Paint::Color(b.highlight_color.to_rgb()),
+                    b.highlight.blend,
+                    b.highlight.opacity,
+                    0,
+                    clip,
+                    sb,
+                );
+                w = self.paint(w, content, Cov::Map(map(i, k + 1), 0.0), &Paint::Color(b.shadow_color.to_rgb()), b.shadow.blend, b.shadow.opacity, 0, clip, sb);
+            }
+        }
         // Outside stroke parts lie beneath the layer and blend onto the exterior result with their
         // own mode; a higher stroke covers the ones below it (each takes its coverage × opacity
         // not yet taken above it, blended over the result as it was before the strokes).
@@ -1356,25 +1376,6 @@ impl<'a> Planner<'a> {
             p.clip = Some(clip);
             w = self.emit(p);
         }
-        for &(i, e) in &rev {
-            if let Effect::BevelEmboss(b) = e
-                && bevel_paint(b) == photocraft_compose::effects::BevelPaint::Outer
-            {
-                let k = 0;
-                w = self.paint(
-                    w,
-                    content,
-                    Cov::Map(map(i, k), 0.0),
-                    &Paint::Color(b.highlight_color.to_rgb()),
-                    b.highlight.blend,
-                    b.highlight.opacity,
-                    0,
-                    clip,
-                    sb,
-                );
-                w = self.paint(w, content, Cov::Map(map(i, k + 1), 0.0), &Paint::Color(b.shadow_color.to_rgb()), b.shadow.blend, b.shadow.opacity, 0, clip, sb);
-            }
-        }
 
         let mode = if premerge || layer.blend == BlendMode::PassThrough { BlendMode::Normal } else { layer.blend };
         let late: Vec<(usize, &'a photocraft_doc::Bevel)> = rev
diff --git a/crates/gpu/tests/it/parity.rs b/crates/gpu/tests/it/parity.rs
--- a/crates/gpu/tests/it/parity.rs
+++ b/crates/gpu/tests/it/parity.rs
@@ -1738,6 +1738,13 @@ fn effect_cases() -> Vec<(&'static str, Vec<Effect>)> {
                 Effect::Stroke(stroke(6.0, StrokePosition::Outside, FxPaint::Color(Color::rgb(0.0, 0.0, 0.0)))),
             ],
         ),
+        (
+            "outer bevel beneath a stroke",
+            vec![
+                Effect::BevelEmboss(bevel(BevelStyle::OuterBevel, true, 6.0, 2.0)),
+                Effect::Stroke(stroke(4.0, StrokePosition::Outside, FxPaint::Color(Color::rgb(0.2, 0.6, 0.2)))),
+            ],
+        ),
         (
             "multiple instances",
             vec![
