Update#42
Open
mgod wants to merge 1273 commits into
Open
Conversation
…ememberTransitionCompat in NavigationDisplay
3.x compat updates
…from being opened by the container
…egular NavigationKeys to be launched into a NavigationFlow, awaiting a complete response, added alwaysAfterPreviousStep configuration option, updated tests, added compat functionality
…step referencing and management
…o release.yml to allow processor to publish correctly
…, even if there are no bindings
…ing platform specific information
…s through from NavigationProcessor
…e platform info and annotations correctly, and remove GeneratedNavigationModule
… this appears to cause compilation failures
Remove comments that narrated the AGP 9 migration as it was being performed,
keeping the forward-looking notes that explain current decisions (why the
androidLibrary accessor, the namespace override, the device-test manifest, etc.).
Update the docs site and README links from the old recipes/src/... paths to the
new module layout: recipes/common/src for shared code and recipes/app/{desktop,
ios,web}/src for the per-platform entry points. All updated links were verified
to resolve to existing files.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update agp 9.0.0 (originally 9.2.0)
The binding generator already had a value-class code path (unwrapping to the inner primitive for serialize, wrapping for deserialize), but isValueClass() only checked the Kotlin `value` modifier. KSP does not reliably report that modifier for a declaration resolved from a compiled dependency module (jar/klib) — so a value class defined in a different module than the one where the destination's binding is generated was treated as a plain type and fell through to the primitive-only createPathBinding helper. That compiles, but throws at runtime: "Property ... is not supported as a path parameter. Must be a primitive." Detect value classes cross-module via the @JvmInline annotation matched by simple name (on native targets KSP cannot resolve the annotation's fully-qualified type across modules, so we avoid resolve()). Also accept the legacy INLINE modifier. Verified the explicit binding is now generated for String-backed value-class path params on jvm, wasmJs, and iOS targets. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-push The WebHistoryPlugin's sync loop had three bugs that made browser back navigation jumpy on wasmJs: 1. Updates arriving while a sync job was in flight were silently dropped, desyncing the in-memory history mirror from the real session history — after which a single browser back could traverse multiple app screens. Lifecycle callbacks and popstate events now feed a single serial processor channel and are never dropped. 2. history.go() is asynchronous but was followed by delay(1) and a time-based listener-disable for echo suppression. The traversal's popstate echo usually arrived after suppression lifted and, when state comparison failed (e.g. during exit animations), was handled as a second user back. Traversals now await their popstate echo and consume it explicitly (with a timeout fallback). 3. Any state not present in the mirror was unconditionally pushState-ed, so full-backstack replacements (loading gate -> home, section truncate-and-open) accreted stale browser entries — making screens like a startup/loading destination reachable via back. The previously-dead isSubset helper is now wired up: a state is pushed only when the previous state is a prefix of it; otherwise the current entry is replaced. Also: blind history.back() retry replaced with a bounded step-past loop, popstate listener removed and processor cancelled on detach, and the unused isNewState/collectInstructionIds helpers removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The serial processor loop introduced in the previous commit had no exception isolation: one throwing sync (serializer, interceptor, path computation) ended the for-loop permanently, after which browser back updated the URL natively but the app never restored state. Each event is now handled in its own try/catch (CancellationException rethrown) and logged via EnroLog.error so the underlying failure is visible. decodeState failures are also logged instead of silently ignored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
History entries persist on a browser tab across app builds, so a serialization-format change (or a metadata value that doesn't round-trip — e.g. kotlinx silently encodes polymorphic value classes as bare literals it then cannot decode) leaves entries whose recorded state is unreadable. Previously these no-opped: back updated the URL natively but the app never moved. Undecodable entries now fall back to resolving the entry's URL through the controller's path bindings (single-entry, same semantics as a cold-load deep link) and self-heal by overwriting the entry's state with the freshly serialized equivalent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kotlinx encodes a polymorphic value class as its bare literal (no
discriminator can attach to a non-object body) and then cannot decode
it back — an asymmetric round-trip that silently produces persisted
state that can't restore. BoxedValueClassSerializer gives a value class
an object-shaped single-field envelope ({"type": fqn, "value": ...})
so the discriminator attaches and round-trips become symmetric; the
valueClassSubclass helper registers it in a polymorphic(Any) block.
The debug-mode metadata verification in EnroController now also does a
full encode->decode round-trip instead of only checking a serializer
exists, so any non-restorable type fails at metadata-set time with an
error naming the type and pointing at valueClassSubclass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e time kotlinx can encode shapes it cannot decode, which writes history entries that silently fail to restore. Decode-verify every state at write time and log the failure with the full payload, so the offending shape is diagnosable at the source instead of surfacing later as a dead back button on a stale entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…iagnose When the serialized state fails round-trip verification, log the live in-memory metadata (key names + value classes — the serialized JSON mangles the offending entry, the in-memory map has the truth) and write a metadata-stripped state instead. Instance ids and keys survive, which is all back/forward restoration strictly requires, so browser back keeps working even while an unserializable metadata value exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Root cause of unreadable web-history entries: with
ClassDiscriminatorMode.ALL_JSON_OBJECTS, kotlinx defers each
discriminator write until the next beginStructure — and a value-class
field never opens a structure. The pending discriminator for an inline
field (e.g. a typed-id value class on a NavigationKey) therefore leaked
into the next object that opened: Instance.metadata, which gained a
bogus {"type": "<value class fqn>"} entry and failed to decode with
'Expected JsonObject, but had JsonLiteral'. Every instance whose key
carried a value-class field was affected, even with empty metadata.
jsonConfiguration now uses the default POLYMORPHIC discriminator mode,
which writes discriminators exactly where polymorphic deserialization
reads them (Instance.key, metadata values) and nowhere else. Pinned by
HistoryStateSerializationTests.
WebHistoryPlugin's write-time verification is now a hard error instead
of degrading: a state that can't restore is never written (and metadata
is never stripped — silently losing result-channel wiring is worse than
failing loudly). The error carries the live in-memory metadata and the
serialized payload for diagnosis.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ncoder The discriminator leak that corrupted Instance.metadata is specific to kotlinx's STREAMING encoder: under ClassDiscriminatorMode.ALL_JSON_OBJECTS it defers each discriminator write until the next beginStructure, and a value-class field never opens one — so under polymorphic dispatch (Instance.key) the pending discriminator for an inline field leaks into the next object that opens. The TREE encoder (encodeToJsonElement) does not share the deferral and produces clean, decodable output for the same configuration. WebHistoryPlugin now encodes history state via the tree encoder, transparently fixing the corruption for all consumers without changing jsonConfiguration or any public API. The write-time round-trip verification remains as a hard error (never degrade or strip data — a state that can't restore is never written). The public jsonConfiguration accessor documents the streaming-encoder caveat. HistoryStateSerializationTests pin the tree-encode round-trip and include a documenting test that fails when kotlinx fixes the streaming encoder, signalling the workaround can be retired. Also removes the BoxedValueClassSerializer/valueClassSubclass additions and the metadata round-trip debug check from earlier on this branch — superseded: the corruption was never caused by polymorphic value-class metadata values, and nothing registers such values today. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The savedstate path (rememberNavigationContainer, FlowResultManager, enroSaver) uses androidx.savedstate serialization with ALL_OBJECTS — a different encoder from kotlinx's streaming JSON. Verifies it does NOT share the deferred-discriminator leak documented in HistoryStateSerializationTests, so container restoration is unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ALL_JSON_OBJECTS is unusable with kotlinx 1.11 for realistic NavigationKey shapes under polymorphic dispatch — BOTH encoders fail: - STREAMING: a value-class field's deferred discriminator leaks into the next-opened object (corrupting Instance.metadata so it can't decode), and collection fields produce outright INVALID JSON (a 'type' key:value pair inside an array, with the wrong class name). - TREE (encodeToJsonElement, the previous workaround): collection fields crash the encoder with NumberFormatException — the deferred discriminator tag is applied inside a list context where it's parsed as an array index. Surfaced by any destination with Set/List fields (e.g. an entity-picker popup with allowedTypes/excludeIds). POLYMORPHIC mode (the kotlinx default) writes discriminators exactly where polymorphic deserialization reads them (Instance.key, metadata values) and handles all of these shapes correctly. WebHistoryPlugin returns to plain encodeToString (the tree-encode workaround is obsolete and was itself broken for collection fields), keeping the write-time round-trip verification as a hard error. HistoryStateSerializationTests pins the POLYMORPHIC round-trip for the full problem shape (value class + enum set + value-class set) and keeps documenting tests asserting both ALL_JSON_OBJECTS failures, so a kotlinx upgrade that fixes them is detected and the mode can be revisited. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3.0.0-beta02 has been released; these changes land in the next version. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On the androidHostTest target, the Bundle-backed SavedState
implementation is not representative of a real device: ANY polymorphic
Instance round-trip through encodeToSavedState/decodeFromSavedState
fails there ('No valid saved state was found for the key, including
keys with no value-class fields at all (verified with a plain-String
control key). The serialization logic the test pins is common code;
desktop's Map-backed SavedState exercises it without the
unrepresentative host-Bundle layer. Device-faithful Android coverage
would need an instrumented test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This reverts commit 316002d.
The Android host-test target compiles commonTest against android.jar
stubs, and isReturnDefaultValues makes stubbed framework methods return
defaults silently — savedstate's Bundle round-trip 'succeeds' on write
and returns null on read ('No valid saved state was found for the key
...'). Any polymorphic Instance round-trip through
encodeToSavedState/decodeFromSavedState failed this way on the host
target, including keys with no value-class fields.
Adds dev.enro.test.platform.RobolectricHostTest, an expect/actual base
class that runs the test under RobolectricTestRunner on the Android
host target (no-op everywhere else), with Robolectric on the host-test
classpath. SavedStateSerializationTests extends it and now passes on
testAndroidHostTest with a functional Bundle — also confirming that
real-device container state restoration round-trips polymorphic
instances correctly.
The SceneHarnessSmokeTest/SceneIntegrationTests/BackstackSavedStateTests
exclusions are left as-is; they may be removable with the same pattern.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ci.yml gains a concurrency group keyed by PR number (falling back to ref): a new push to a PR cancels the in-flight run for the superseded commit. Pushes to main are never cancelled, so every main commit keeps its complete CI record. The changelog now stacks changes under a standing '## Unreleased' header. The updateVersion task stamps that header with the version and date at release time, inserts a fresh empty '## Unreleased' above it, and writes the released section's body to build/release-notes.md — failing before any file is written when the Unreleased section is empty. release.yml commits CHANGELOG.md alongside version.properties and attaches the extracted notes to the GitHub release via 'gh release create --notes-file' (the 'changes' input becomes an optional prefix). Also modernises the workflow's actions: checkout@v4, setup-java@v4, gradle/actions/setup-gradle@v4, add-and-commit@v9, and the archived create-release@v1 replaced with the gh CLI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…source Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fix web browser-history sync (WasmJS) + switch Json discriminator mode to POLYMORPHIC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.