Skip to content

fixed crasher in 6.3 Embedded - #121

Merged
sliemeobn merged 1 commit into
mainfrom
fix/embedded-crasher
Aug 22, 2026
Merged

sliemeobn merged 1 commit into
mainfrom
fix/embedded-crasher

Conversation

@sliemeobn

@sliemeobn sliemeobn commented Aug 22, 2026 •

Copy link
Copy Markdown
Collaborator

fixes #120

there are still a few weird issues with Embedded on Swift 6.3 going on, but this seems to address a few.
6.4 appears to be a lot more robust

@sliemeobn
sliemeobn merged commit d7de128 into main Aug 22, 2026
11 checks passed
@sliemeobn
sliemeobn deleted the fix/embedded-crasher branch August 22, 2026 12:49
Vithanco added a commit to Vithanco/elementary-ui that referenced this pull request Aug 29, 2026
The revert to the mirrored shape checked this file out of the pre-merge commit
along with the ones the design change actually touched, which undid the attribute
storage rework from "maybe fixed embedded crasher" (elementary-swift#121). This PR never had a
reason to change _ElementNode at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
sliemeobn pushed a commit that referenced this pull request Sep 1, 2026
* Allow DOM modifiers on SVG content

DOMEffectView was constrained to Wrapped: View, but nothing in its body uses
anything HTML-specific — only Wrapped.Tag and the three _Mountable
requirements. The modifier stack it drives attaches to DOM nodes, which are
namespace-agnostic, so the constraint was stricter than the implementation.

Relaxing it to Wrapped: MarkupContent & _Mountable and conditionally conforming
lets the same wrapper carry a modifier over HTML and over SVG alike. Only
SVG.svg conforms to View; everything nested inside it is SVGView, so before
this no inner SVG element could take a handler at all.

MarkupContent is stated once and unconditionally, because a type may conform to
a protocol only once and both HTML and SVGContent inherit it. Tag is the
wrapped tag, Body is Never, and the _render default on MarkupContent supplies
_Renderable, so it costs nothing.

The event modifiers are mirrored onto SVGView as a first slice. The duplicated
declarations are forced by the return type: some View<Tag> and some
SVGView<Tag> are different opaque types, so a single shared extension on
MarkupContent & _Mountable cannot serve both without making DOMEffectView
public API. Happy to go the other way if you would rather expose it.

Tests cover a handler on a nested element, the no-argument form, stacked
modifiers, that the wrapper leaves the wrapped element's ops untouched, and a
recursive SVGView carrying one handler per element.

Remaining if this direction is right: ~15 more modifiers across styles, focus,
binding, lifecycle, onChange, keyed views, transitions and global events.

* trim comments to the ones that earn their place

The blocks above DOMEffectView and in SVGView+DOMEvents largely restated the
signatures they sat on. What is left is what the code cannot say: that the
loose Wrapped constraint is deliberate because DOM nodes are namespace-agnostic,
that MarkupContent must be unconditional since a type may conform only once, and
why the mirrored SVGView declarations cannot be shared.

The two per-test doc comments repeated their own test names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Mirror the remaining DOM event and style modifiers onto SVGView

Completes the two modifier families that `DOMEffectView` drives, so SVG
content is no longer interactively inert:

- events: onMouseDown, onMouseMove, onMouseUp, onKeyDown
- styles: opacity, rotationEffect, offset, scaleEffect (x2), blur,
  saturation, brightness

`onInput` is left out (input events do not fire on SVG elements), as is
the deprecated `Float` overload of `offset`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* trim the new comments to the ones that earn their place

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* match the repo's comment style: no backticks in implementation comments

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* share the DOM event modifiers across HTML and SVG

Return the concrete DOMEffectView instead of some View<Tag> / some SVGView<Tag>,
so its conditional conformances pick the namespace and one declaration serves
both. SVGView+DOMEvents.swift is no longer needed.

Naming DOMEffectView in a public signature means DOMElementModifier has to be
public, which forces its mount requirement public too - and that names DOM.Node,
which is @_spi(Benchmarking). Marking just that one requirement
@_spi(Benchmarking) keeps the two in the same visibility tier. Every conformer is
in-module, so nothing outside can be affected.

onInput stays on View: input events do not fire on SVG elements.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* share the style modifiers across HTML and SVG

Same treatment as the event modifiers: return the concrete DOMEffectView so the
conditional conformances pick the namespace, and drop SVGView+Styles.swift.
View+Styles.swift becomes MarkupContent+Styles.swift; its documentation moves
with it, reworded from "view" to "content" now that it covers both.

The offset(x:Float,y:Float) overload moves too. Splitting it from the Double one
would change overload resolution for integer literals on HTML.

This costs more public surface than the events did: naming the style modifiers in
a return type makes their Value typealiases public, which pulls in CSSOpacity,
CSSTransform and CSSFilter along with their nested function and payload types.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* hide the concrete modifiers behind an opaque return type

Naming the modifier in the return type was what forced the public surface open:
a public DOMEffectView<OpacityModifier, Self> makes OpacityModifier public, which
makes its Value typealias public, which pulls in CSSOpacity, CSSTransform,
CSSFilter and every nested function and payload type.

Returning DOMEffectView<some DOMElementModifier, Self> keeps the wrapper named,
so the conditional conformances still pick the namespace, while the modifier
itself stays hidden. Every concrete modifier, the CSS value vocabulary and
DOMEventHandlers go back to internal: 108 new public declarations become 29, and
the new public types are DOMEffectView, DOMElementModifier, DOMElementModifiers
and AnyUnmountable.

BindingModifier and FocusModifier had been given public witnesses they never
needed - an internal conformer does not require them.

some rather than any: any DOMElementModifier cannot satisfy the constraint at all,
and Embedded Swift allows only class-bound existentials. Opaque types monomorphise,
so embedded output is unchanged across the five size targets and memory is
identical on the performance suite.

Adds a test that only compiles if the modifier really is declared once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* underscore the types that are public only for the compiler

None of these four are meant to be used: with the modifier opaque, _DOMEffectView
cannot even be named outside the module, since its first generic argument is
deliberately unspellable. The underscore matches _Mountable, _AttributeModifier
and the rest of the reconciler surface, and keeps them out of generated docs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* drop accidental pnpm-lock churn

Local pnpm runs rewrote libc fields in three lockfiles; unrelated to this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* go back to explicit per-namespace declarations

Reverts the shared MarkupContent extensions and everything they required: the
opaque return types, the public and underscored DOMEffectView surface, and the
@_spi on the mount requirement. DOMEffectView is internal again, and View and
SVGView each declare their own modifiers returning some View<Tag> and
some SVGView<Tag>.

A little duplication in exchange for no exposed internals and simpler signatures
for package consumers.

The cross-namespace test goes with it: with two declarations there is no longer a
single one to pin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* leave the SVG transform modifiers out for now

rotationEffect and scaleEffect do not work on SVG: transform-origin resolves
against the viewBox rather than the element, so rotate(45deg) on a rect at
(20,20)-(60,60) lands its centre at (0,56.6) instead of (40,40), and a
topLeading anchor puts it off-canvas at (-100,98) - the percentage translate
the anchor uses assumes an origin already at the element's centre.

Both need transform-box: fill-box plus an explicit transform-origin, which is
worth doing on its own rather than smuggling into this PR. offset stays: it
translates by absolute pixels and is unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* record why the duplication is a choice, not a limit

The old note said one extension cannot serve both namespaces. It can - the PR
history proves it - just not while returning some View<Tag> / some SVGView<Tag>,
which is what keeps the wrapper internal. Saying "cannot" invites the next reader
to rediscover that the hard way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* cut the comments that document the PR rather than the code

The notes about rotationEffect, scaleEffect and the Float offset overload
explained absences that only exist relative to this branch's history - on main
those functions were never in the file. The note above DOMEffectView restated
what the four conformances below it say precisely, and tightening the constraint
back would fail to compile anyway.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* answer the embedded TODO on _onEvent

The note asked whether this was still an issue in 6.3. It is: making _onEvent
internal fails the embedded build of Counter in _FunctionNode.swift with "cannot
use metatype of type 'some View<HTMLTag.button>'" and "a protocol type cannot
contain a generic method 'unmount'". Public builds clean on the same toolchain,
so the trigger is the opaque return type reaching the function-node machinery,
not the declaration.

Recheck target moved to 7.0. The SVG copy keeps pointing at this note.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* restore main's _ElementNode - it was reverted by mistake

The revert to the mirrored shape checked this file out of the pre-merge commit
along with the ones the design change actually touched, which undid the attribute
storage rework from "maybe fixed embedded crasher" (#121). This PR never had a
reason to change _ElementNode at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* cover the SVG modifier paths that only mounting exercised

The suites only ever mounted, so _patchNode had no SVG coverage at all - half of
DOMEffectView, and the half a reconciler change is most likely to break. Adds
patching for opacity, offset and filters, patching an attribute through a
wrapping modifier, growing a recursive SVGView, and unmounting a modified
element.

Three assertions matched any value for transform and filter, so a wrong CSS
string would have passed; they now pin the exact string. saturation and
brightness were untested, as was modifier layering - stacking two filters or two
transforms composes through upstream/layerNumber and had no test in either
namespace. Also covers styling an SVG container rather than a leaf.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test the animation behaviour the docs promise

Every SVG style modifier's doc comment ends with "Changes to X are automatically
animated when done in an animated transaction", which nothing checked. It holds:
opacity and transform changes inside withAnimation start an animation, and a
plain change outside one emits a setStyle op instead.

The suite has to be serialized for that, the way TransitionMountRootTests already
is - withAnimation leaks into tests running in parallel, which made three
unrelated patch tests see their ops swallowed by an animation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* back out the animation tests - they make the suite flaky

Transaction._current is a process-global, not task-local, which is fine for the
single-threaded browser runtime but not for swift-testing's parallel suites: a
withAnimation in one test leaks into whatever runs beside it, and tests asserting
setStyle ops see them swallowed by an animation instead.

@suite(.serialized) is not enough - it only orders tests within a suite, and the
leak crosses suites. Measured: 2 failures in 5 runs with these tests, 0 in 6
without, and the failures move around.

The documented "automatically animated in an animated transaction" behaviour is
still worth covering; it needs the leak fixed first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix the formatting left behind by removing the transform modifiers

swift format lint is part of CI and this failed it: two runs of stray blank
lines with trailing whitespace, where rotationEffect and scaleEffect used to be.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

View conditions on @State do not work in embedded mode

1 participant