Other small fixes - #182
Closed
UnknownJoe796 wants to merge 65 commits into
Closed
Conversation
Multi-agent review of version-8 covering core view architecture, reactivity, theming/models, navigation/l2, and platforms/build/testing. Prioritized P0-P3 roadmap with verified file:line references. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The file previously existed only in the gitignored buildSrc mirror (regenerated by copying from gradle-plugin/src), so a git clean would have destroyed it. Move it to gradle-plugin/src/main/kotlin/ (the mirror's source of truth) and wire registerAiDriverTasks(project) into KiteUiPlugin.apply, which its own doc comment already documented as the intended call site. Registration is lazy and no-ops for consumers without an :ai-driver-server project. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DependentAction is a DependencyChangeListener (a CoroutineContext.Element), but startAction launched the action coroutine with context = extraContext, omitting `this`. The reactive await()/state() functions locate the listener via currentCoroutineContext()[DependencyChangeListener.Key], so with the listener absent from the context, no dependencies were ever registered and onDependencyChange/onDependencyNotReady never fired. Since DependentAction is the default returned by Action(...), every button that clears its error on dependency change was silently broken. Add `this` to the launched coroutine context. Add a regression test verifying a dependency change clears the error state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s, dead-container log - working/loading extension props cached a per-element FalseWhenSuccessful wrapper into the subtree-shared ElementContext.addons under a constant key, so every sibling element read the first element's process status. The wrapper is trivial; construct it per access. - Clear labelFor/describedBy on shutdown; they hold strong references to arbitrary elements and could keep a whole removed subtree (and its native views) alive. - Replace the bare println in checkIsShutdown with a proper Log.warn including the view path. Kept the no-op-return behavior (a teardown race can legitimately reach it) but made it visible instead of a silent stderr print. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…empty nav stubs - ColorPicker.bindColor compared .green twice and never compared .alpha, so alpha-only changes within tolerance were dropped. Compare alpha as the fourth channel. - Graph drew two println(minX/maxX) debug lines on every single canvas draw. Removed. - Removed navLayout and navSideBar from AppNavV2: empty no-op public functions with zero in-repo usages (navBottomBar, which has a real body, is kept). Source-compat note: any external caller was already getting a no-op. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
onNewIntent decoded the entire key=value pair before splitting on '=', so a value containing an encoded '=' (%3D) would corrupt the key/value split. Split on the raw delimiter first, then decode each half — matching the JS urlLike() behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PageNavigatorBehavior.Separate (the default) kept a HashMap<UrlLikePath, List<Page>> that grew unbounded for the session's lifetime, strongly retaining every Page of every stack ever visited. Replace with a bounded (50-entry) insertion-ordered map with oldest-first eviction and recency refresh on store; beyond the cap an old URL simply reparses into a fresh single-page stack. Note: the non-default Link mode still has a separate dead-code issue (its history-sync reactiveScope sets suppressNav=true immediately before an if(!suppressNav) guard, making the body unreachable) and never cleans its localStorage main-stack-* entries. Collapsing the three history strategies to one is a design decision left for maintainer review; not changed here. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- MIGRATION.md: status changed from 'IN PROGRESS/BROKEN/target version-7' to landed on version-8; removed the nonexistent CanAddDynamicTheme, added the real CanAddListElementModifier stage; replaced all '-' operator modifier examples with the actual dot-chaining syntax; withUnrestrictedModifiers -> withUnsafeModifiers. - CLAUDE.md: dot-chaining in Modifiers/Theming examples; jvmRun -> ssrServerRun. - README.md: Kotlin badge 2.2.0 -> 2.3.20. - docs/TESTING_GUIDE.md: RContext -> ElementContext. - docs/rview-basics.md: prominent notice that RView/RContext/ViewWriter are historical; points at the current Element/NativeElement/ElementWriter model. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…names
Content-neutral renames (Kotlin filename != class name, so no code changes):
- RContext.{ios,android,commonHtml}.kt -> ElementContext.<platform>.kt (they contain the
ElementContext actuals)
- RView.commonHtml.jvm.kt -> FutureElement.commonHtml.jvmSsr.kt (contains FutureElement +
FutureElementStyle/Attributes)
- Deleted RViewTest.kt, which was 100% commented-out dead code referencing the removed
RContext.test() API.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
generateRoutes.kt string-scans Kotlin sources; several defects fixed: - @QueryParameter scan was unbounded to EOF, so the first @Routable in a file collected a later class's query params. Bound the scan to the class body via a new brace-matching afterBraces() helper in parsingHelpers.kt. - Property-name extraction used indexOf("va"), matching the "va" inside `private` etc. Replaced with a word-boundary regex \bva[lr]\b. - Silent break on malformed input (missing path string, unclosed literal, no class/object) dropped the rest of a file's routes; now throws with file path + offset (fail-fast). - Route precedence was filesystem-walk-order dependent (parse takes firstOrNull). Parsers are now sorted specificity-first (fewer variables win), alphabetical tiebreak, and structurally-duplicate route templates throw with both class names. Adds GenerateRoutesKtTest (5 golden tests) covering per-class query attribution, keyword matching, ordering, duplicate detection, and malformed-input errors. Reviewed and verified :gradle-plugin:test green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The theming core previously had zero tests. Lock in the pure model-level invariants so future refactors (deduplicating the Theme setter, converting lambda derivations to Semantics) can't silently break them: - ThemeAndBack.plus OR-combines drawBackground/padding flags (ThemeRules.md contract) - SemanticOverrides precedence: instance override > type override > default - Theme.copy revert propagation (non-cascading sets earliest revert; cascading propagates) - Theme equals/hashCode are id-based (invisible contract), copy chains ids, customize does not - theme[semantic] memoization returns the identical cached instance All 24 pass against current behavior. Invariants needing an element-level harness (cascading no-child-refresh optimization, full ThemePipeline ordering) are noted as out of scope in the file header. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All commented-out of settings.gradle and unbuilt; the swing modules still referenced the removed RView class and could not compile. Recoverable from git history if ever needed. - library-swing (80 files), library-lottie-swing, library-camera-swing, example-app-swing (only referenced each other; removed the commented includes from settings.gradle.kts) - example-app/src/wasmJsMain (6 files) — orphaned; example-app declares no wasmJs target - test-utilities/src/jvmDesktopMain — unwired (jvmDesktop target commented out per KMP single-JVM-target limitation) - library/src/commonJvmMain Example.kt — stray 2-line file in a wrong package; the commonJvmMain source set itself stays (it carries jvmSsr deps) Verified :library and :example-app jvmSsr compile clean after removal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Introduces elementTree { } — a live SSR-backed element tree for tests to build a subtree
via the normal DSL and inspect it (themeAndBack/drawBackground/theme.id, shown, children,
findByName, live-instance count, shutdown). Fills the gap where theming/view invariants
could only be tested at the pure-model level.
Adds 6 tests locking in element-level theme cascade: card draws background, plain child
inside a card does not, important child switches theme and does, nested cards each draw,
redundant refreshTheming is a no-op, and shutdown returns the live-instance count to
baseline (no leak). Prerequisite for safely landing the themeAndBack setter dedup.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ement duality)
Element extended both KiteUiCoroutineScopeHelpers (-> CoroutineScope) and StatusListener
(-> CoroutineContext.Element), so every element was simultaneously a CoroutineScope AND a
CoroutineContext.Element - and NativeElement placed the element into its own coroutineContext
as that StatusListener. That dual identity is the root of the element.job ambiguity in the
Element.kt TODO (child jobs mis-parented).
Fix: Element is now only a CoroutineScope, not a StatusListener. NativeElement supplies a
SEPARATE StatusListener object into its coroutineContext (delegating to the element's own,
now-plain, watch{Background,Foreground}Process methods). Verified nothing consumes
StatusListener via the Element type - all consumers read it from the coroutine context.
This surfaced a latent bug: TelemetryContext.viewPath() called element.outermostElement.viewPath()
which had been silently binding to CoroutineContext.viewPath() (because Element was a
CoroutineContext) and always returning an empty string. It now correctly resolves to
Element.viewPath(); added the import.
Verified: :library:jvmSsrTest green; android/js/ios compile clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Back (Android system back; browser back to follow) should dismiss the top open dialog before popping the page. Adds an app-global stack of dismiss lambdas on ElementContext (shared via the root context, since lazyContextAddon defaults store there): - pushDismissableDialog(dismiss)/dismissTopDialog() in ViewContextExtensions.kt - dialog() registers a dismissable dialog and funnels back/tap-outside/programmatic close through one idempotent dismiss that also unregisters from the stack - Android handleOnBackPressed dismisses the top dialog before mainNavigator.goBack() iOS needs no change (modal UIViewController + no hardware back). The browser popstate guard lands with the Navigator.js history rewrite. Source-compatible: dialog() signature unchanged; alert()/confirmDanger() get back-to-dismiss for free; dismissable=false dialogs stay put. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the sprawl (forEach, forEachUpdating, forEachById, forEachByIdWithoutAnimation, forEachAnimated, childrenLazyLoading, Recycler2.children) with one renderList family: - keyed overload (ID diffing, animate flag, live Reactive<T>) and unkeyed overload (positional slot reuse with placeholders + a poolCap that bounds the hidden-view pool - the old forEachUpdating grew it unboundedly) - Recycler2.renderList / renderListMultipleTypes harmonize the virtualized entry point (virtualization stays separate by necessity) All old functions become @deprecated(ReplaceWith(...)) delegating to renderList, so behavior is identical (they were all @InternalKiteUi). lazyColumn/lazyRow now call renderList internally. Trivial in-repo callers migrated. Adds RenderListTest (8 tests, incl. poolCap eviction). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Recycler2PullToRefreshTest and Recycler2TestPage both claimed @Routable("recycler2-test"), so one page was silently unreachable (the pre-hardening codegen picked one by filesystem order). The new fail-fast duplicate detection caught it. Give the pull-to-refresh page its own route so both are reachable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Behavioral + structural coverage for the interactive elements, using the driver-backed
uiTest {} harness and the elementTree {} structural harness. Each file covers the standard
template: renders, binds content/value, enabled/disabled reflects action presence, action
fires (click/toggle/setValue), theme applied where expected, and no leak after shutdown.
Elements: Button, TextInput, Checkbox, Switch, ToggleButton, Select, Slider, TextArea,
NumberInput (67 tests). Adds assertEnabled/assertDisabled to UiTestScope and findByDebugName
to the element harness. Two test bugs found while verifying and fixed here (the 500ms Action
frequency cap swallows a rapid second click; a slider must set max before min to avoid a
transient inverted range) - both worth remembering when writing element tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sses dialogs Collapses the three PageNavigatorBehavior strategies to just Separate (each navigate() = one browser history entry, closest to native back semantics). Link and Deprecated are kept as @deprecated no-op aliases and PageNavigatorUseExperimentalBehavior is an inert deprecated setter, so nothing external breaks. Removes the dead Link-mode code (the suppressNav guard that made its history sync unreachable) and cleans up the localStorage keys those modes left. - reset() (stack shrinks to 1) now clears browser forward/back via history.go(-(length-1)) + replaceState, so back can't return into the old stack. Detected as size==1 && prev>1. - Browser back dismisses the top open dismissable dialog first (popstate intercept + re-pushState so back keeps working), matching the Android back behavior. - Keeps the bounded lastStackForPath cache. Compiles clean (:library:compileKotlinJs). NB: browser back/forward/reset behavior cannot be verified headlessly and needs a manual browser pass (see checklist in the PR/notes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Turn on explicitApiWarning() for the library and add the explicit `public` modifier at every declaration the compiler flagged for missing visibility across all source sets (commonMain, commonHtmlMain, jsMain, androidMain, iosMain, nativeMain, jvmSsrMain). This is a pure mechanical pass: `public` is the explicit-API default fill and every flagged declaration was already effectively public, so there is ZERO ABI or behavior change. Verified by recompiling every target (JS, JVM/SSR, iosArm64, Android debug) — 0 errors, 0 remaining visibility warnings. Insertions were generated deterministically by tools/usage-scan/fill_public.py from the compiler warning log (column-based prefix insertion, no line shifts). Not yet addressed (follow-up commits): 2,239 "Return type must be specified" sites, which require inferred return types (judgment), and the internal/ visibility-narrowing pass driven by the downstream forced-public floor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
Add explicit return types to all 413 commonMain public declarations flagged by explicit API mode, across 60 files. Generated by Opus workers file-by-file and gated with :library:compileCommonMainKotlinMetadata after each batch — commonMain now reports 0 return-type and 0 visibility warnings, 0 errors. Declared types are the compiler-inferred types, with public supertypes chosen over internal implementation types where that matches the API intent (noted in per-declaration review). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
A Fable audit of the commonMain return-type commit flagged 9 declarations whose
compiler-valid types were poor public API. Fixes:
- Navigator.navigateUrlLikePath/resetUrlLikePath: Unit? -> Unit (the nullability
was an accidental artifact of `?.let {}`; converted to block bodies).
- Six properties leaking concrete ArrayList/HashMap where sibling declarations in
the same commit used the MutableList/MutableMap interfaces; declared the
interface type, kept the concrete initializer (no behavior/perf change).
- Canvas.fallbackView: () -> TextView -> () -> Element, so overrides of this open
accessibility hook aren't forced to return a TextView specifically.
Verified: compileCommonMainKotlinMetadata — 0 errors, 0 warnings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
Internalize 32 SSR declarations provably unused by external repos and in-repo consumers: hydration internals (HydrationCursor, SsrResource state members), SsrContext render/serialize members, SsrResult, SsrPreloadable helper, and SsrDocument/SsrRouter internal render methods + config props. Kept public (consumer API + signature/inline-forced): the page-metadata DSL (pageMeta/PageMetaBuilder/OpenGraphBuilder/TwitterCardBuilder + PageMeta model), SsrRouter (+ renderPageWithPreload/renderOrFallbackWithPreload), SsrDocument, ssrResource + its inline-referenced registry members, and SsrContext class. Verified: library strict (common+jvmSsr+js) + library tests + example-app (jvmSsr+js) compile, 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
The dom package has zero consumer usage (pure internal implementation). Internalize the HTML micro-parser/sanitizer (MPNode, okTags/okAttrs allowlists, parseMPNodes) and the JS ResizeObserver external interop declarations. dom2 expect/actual DOM event types deferred (need coordinated expect+actual handling). Verified: library (js+jvmSsr) + tests + example-app, 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
Internalize concrete built-in-widget glue in androidMain/views that is provably unused by external repos and in-repo consumers: per-widget helper-class members (TwoWayNestedScrollView, NCircularProgress, NSeparator/NSpace, NIconView, FlexboxLayout, NProgrammaticLayout, PathDrawable, TextViewWithGradient, etc.), theming/layout plumbing, and internal convenience helpers. Kept public (custom-native-view extension API — downstream authors build custom views on it, verified against library-camera): NativeElement/.native accessors, NativeContainerElement, InteractiveElement bases, ElementContext.activity, reusable layout infrastructure (SimplifiedLinearLayout, DesiredSizeView, lparams), all expect/actual component classes, and any class exposed as a public `override val native` (only its unused members were internalized) or referenced by a public inline function. Verified: library (debug+release) + example-app + library-camera + library-lottie + test-utilities compile, 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
Internalize concrete built-in-widget glue in iosMain/views that is provably unused by external repos and in-repo consumers: per-widget UIView-subclass members and helper classes (FlexLayout/LinearLayout/ScrollLayout members, FrameLayoutButton members, GlassFrameLayout, TextFieldInput, UILabelWithGradient, blur/drop plumbing, path/vector parsing internals, pusedoframe/sizeThatFits2, etc.). Kept public (custom-native-view extension API, verified against library-camera AND library-lottie which build custom iOS views): NativeElement/.native surface, NativeContainerElement, InteractiveElement bases, FrameLayout/WrapperView, ElementContext presentation API, @ObjCAction selector-target methods, classes exposed via public `override val native` (members internalized instead), declarations referenced by public inline functions (ScrollView.scroller), and DrawingContext2DImpl (used by library-lottie), UILabelWithLayerBackground (used by example-app's custom Code view). Verified: library (iosArm64 + simulator) + example-app + library-camera + library-lottie + test-utilities compile, 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
…g protocol docs Extend the compiler plugin's decls mode to emit `path:line` source locations per declaration and to record only PUBLIC declarations (via IrDeclarationWithVisibility), so the used-set join yields precise, actionable per-file internalization candidates. Add the worker protocol docs used to drive the migration: RETURN-TYPE-WORKER.md, INTERNAL-NARROWING-WORKER.md, and NATIVE-VIEW-KEEP-PUBLIC.md (the custom-native-view extension surface that must stay public). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
Internalize concrete built-in-widget glue in commonHtmlMain/jsMain views that is provably unused by external repos and in-repo consumers: hidden DOM element holders (<input>/<textarea>/floating), widget state backings, CSS-sheet internals (DynamicCss), popover/measurement helpers, hydration/attribute-diff plumbing (NativeElement.commonHtml.js), and non-actual styling extras. Kept public (custom-web-view extension API): the Html/.element/.onElement direct-DOM DSL, FutureElement(Style/Attributes), NativeElement.native, KiteUiCss (public actual constructor param), helpers reused across widgets, ResizeObserver infra, all expect/actual/override members, and everything referenced by public inline functions. commonHtml changes verified on BOTH js and jvmSsr. Verified: library (js+jvmSsr) + tests + example-app (js+jvmSsr) + library-camera + library-lottie + test-utilities compile, 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
Internalize SSR-internal view rendering plumbing (DrawingContext2DStub, FutureElement.render/childrenBack HTML serializer internals, DynamicCss accumulator state + counters) provably unused outside the library module. FutureElement web-view surface and all expect/actual members kept public. Verified: library (jvmSsr) + tests + example-app + library-lottie + library-camera + test-utilities compile, 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
The dialog PageNavigator was a long-deprecated near-duplicate of the main
navigator (differing only in a semantic + ignoreInteraction) that was still
constructed on every app boot and SSR request. Its popover / navClosable
replacement already exists and is what the JS back button integrates with.
- Delete navigatorViewDialog and every dialogPageNavigator accessor/companion
(deprecated.kt, per-platform deprecated.*.kt files removed).
- Drop the dialog: PageNavigator? parameter from appBase/appNav and the second
SSR navigator stack in SsrRouter.
- Port example-app demos off dialogPageNavigator to the real popover/dialog {}
replacement (or a full page where appropriate).
- Sweep the downstream-dead hasPopover (ERROR-deprecated, zero live callers,
built on the dialog nav) and neutralize DismissBackground's default click
(which called dialogPageNavigator.clear()) to a no-op matching the opt-in
onClick contract; every real call site already overrides onClick.
Verified: :example-app:jvmSsrTest, :library:jvmSsrTest,
:example-app:testDebugUnitTest, JS + iOS compile all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
UrlLikePath.render() and both hand-duplicated parsers (Routes.kt and the JS Location.urlLike()) were asymmetric: query keys were decoded on parse but used raw (B10), query keys were never encoded on render, and path segments were joined without encoding and split without decoding. A segment containing '/', a space, '%', or non-ASCII, or a query key containing '='/'&', would corrupt. - Decode query keys symmetrically with values (B10). - Encode path segments and query keys on render; decode segments on parse, in both Routes.kt and Navigator.js.kt (the third copy in KiteUiActivity.kt was already correct — Android pre-decodes Uri.path — left untouched). - Add UrlLikePathTest: 8 round-trip cases (render(parse(x)) == x) covering '/', space, '%', unicode segments and special-char query keys/values. Verified: :library:jvmSsrTest full suite (503) green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
T2 — root-cause and fix the "do not call super.themeAndBack, it breaks everything" workaround. It was the Kotlin two-backing-fields override trap: NativeContainerElement re-overrode themeAndBack as a var, minting a second backing field, so super.themeAndBack wrote the base's hidden field while every read went through the container's frozen field. Fix: base setter now invokes a protected open themeAndBackChanged(previous, current) hook and the property is a final override var (trap permanently closed); the container implements only the hook with the cascade delta. Behavior is byte-for-byte equivalent; the vague comment is replaced with an explanation of the trap. Note: themeAndBack is now final in NativeElementCommonCode — custom views intercept via the hook. V4 — small self-contained cleanups: - Collapse the 4x-duplicated InteractiveElement action-watch body into one private rewatchAction helper. - Replace bare `this as NativeElement` casts (3 sites) with a shared asNativeElementOrFail() that fails fast naming the invariant (no extra cost over the bare checkcast). - Attribute Processes.recalculateState exceptions to outermostElement instead of the inner element (removes a TODO). - Document the iOS willRemoveSubview "cursed GC crash" guard and mark it for a tracked issue. spacingOverrideBeforeNext left intact (wanted, pending redesign). Verified: :library:jvmSsrTest (incl. new ElementThemeCascadeTest cascade tests), :example-app:jvmSsrTest, Robolectric (library + example), JS/iOS/JVM compile. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
…ces (V1/B2) V2 — SimplifiedLinearLayout is a fork of AOSP LinearLayout carrying machinery KiteUI never reaches. Removed the provably-unreachable parts (1454 -> 1082 lines): largest-child measurement, the non-overridable virtual-child hooks and their dead call sites, null-child branches, and literal if(false)/!false conditionals. RTL and baseline-alignment were verified LOAD-BEARING (example-app sets supportsRtl; align() maps to relative Gravity.START/END; per-child align in a row consults childBaseline) and deliberately KEPT — hence ~26%, not >50%. Added SimplifiedLinearLayoutTest (Robolectric, 8 cases) locking weight/gap/ gravity/orientation behavior; validated they pass against the pre-prune file too. V1/B2 — weight/align coordination routed through concrete-class casts inside a broad catch(Throwable) that silently mis-sized a misplaced modifier. Introduced capability interfaces LinearChildHost (isHorizontal) + WeightedLayoutParams (weight), implemented by SimplifiedLinearLayout, mirroring the existing MaxSizeLayoutParams pattern. weight/dynamicWeight/align now check the interface (as?) and cleanly no-op under a non-host parent; the broad catch is gone. Does NOT throw and does NOT add compile-time container enforcement (reserved for a separate type-system design). iOS needed no change (it sets child extension props; the container self-introspects — no foreign cast). Verified: :library:testDebugUnitTest (incl. both new tests), :example-app: testDebugUnitTest, :library/:example-app jvmSsrTest, JS + iOS compile. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
Bump the reactive dependency to 6.0.0-prerelease-52-local (published to mavenLocal) so KiteUI builds against the reactive fixes: DependencyTracker off-by-one + repeated-read dedup (B1/R4), async cache-collision + stale-work cancellation (B6), reactiveState CancellationException rethrow (B7), false-KDoc removal (B8), and the thread-confinement fail-fast guard (R1). NOTE: this pins a -local (unpublished) reactive build. Before shipping, reactive must be committed + published and this pin bumped to the released version. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
- B5: Color's primary constructor defaulted alpha=0f, so Color(red=1f) was
invisible (inconsistent with fromHex's alpha=1). Default alpha to 1f. Only one
site relied on the old default (Color.transparent) — made it explicit
Color(alpha=0f). As a side effect this also fixes fromRgbString's rgb() branch,
which had silently produced transparent colors.
- B3: remove a stray "!important" fragment (dead, followed a semicolon) from the
.circle-progress-background stroke rule in KiteUiCss.
- B4: delete the invalid unitless ".scroll-horizontal * { max-width: 100 }" rule.
Verified: :example-app:jvmSsrTest, :library:compileKotlinJs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
Theme identity is id-only by deliberate design (a major perf advantage); the convention that keeps it safe is that themes are derived through semantics, which guarantees unique ids. Make that checkable without changing the identity model: - Add Theme.Debugger (opt-in, mirroring Element.Debugger): when checkIdCollisions is on, every Theme construction registers its id and throws if a previously-seen id now maps to a structurally different theme. Off by default = one boolean read. - Add internal Theme.structurallyEquals over the 17 rendering-affecting fields (excludes id/provenance and the closure-valued semanticOverrides). equals/ hashCode remain id-only. Derivation review: copy()/withBack/alter and the themeDerivations helpers already chain parent ids correctly; root factories take a manual id by nature. customize (newId) intentionally does not chain (pinned by a passing test + example-app use) — left as-is with an expanded KDoc rather than deprecated. See report for the open question of whether customize should chain. Verified: :library:jvmSsrTest (3 new collision tests), :example-app:jvmSsrTest, Robolectric, JS + iOS compile. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
…h counter (N1) N2 — SsrContext's windowWidth/windowHeight (defaulted 1920x1080) were dead constructor params with zero readers anywhere (verified across library + example-app; rowCollapsingToColumn is already CSS-only, virtualization uses null-safe DOM measurement). Replaced them with getters that throw UnsupportedOperationException, so any future attempt to make DOM-structural layout depend on viewport during SSR fails fast (structural responsiveness must be CSS-only). Pure guardrail, no behavior change. N1 — hydration contract visibility (recover-on-mismatch stays the intended behavior; not adding a strict-throw mode): - Add SsrGoldenSnapshotTest: renders FourOhFour, HomePage, and RecyclerViewPage to SSR HTML and asserts against committed golden fixtures (self-bootstrapping: writes+fails once if a golden is missing). Locks the server serializer. - Make HydrationContext's hydrated/created/mismatched counters publicly readable (private set), and fix a bug where clear() zeroed them before a dev could inspect them — reset now happens in resetStats() at the next page load. Two new HydrationTest cases cover the counter and the clear/resetStats split. Verified: :library:jvmSsrTest, :example-app:jvmSsrTest (incl. golden), :library:compileKotlinJs, :example-app:testDebugUnitTest. NOTE: :library:jsTest (HydrationTest) needs a real browser and was not runnable in this sandbox — to be exercised in the browser verification pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
…totype)
Consumes the new reactive QuiescenceTracker API (reactive 5.1.3-
reactivebugfixes-53) to give SSR a deterministic settle point instead of the
delay(1) "to ensure reactive bindings have propagated" hack.
- Pin reactive to 5.1.3-reactivebugfixes-53 (adds QuiescenceTracker).
- SsrContext installs a per-request QuiescenceTracker on its loading/render
scopes; awaitAllResources() now awaits quiescence.awaitQuiescence() instead of
delay(1). Determinism comes from SsrResource.startLoading registering its load
job with the tracker so the state write + full listener cascade run inside the
tracked job body; the work count only drops after, closing the race delay(1)
papered over.
- Add ElementContext.ssrQuiescence addon (mirrors ssrDispatcher) so freshly-built
element coroutine contexts carry the tracker; NativeElement threads it in.
- New SsrQuiescenceTest: an ssrResource whose binding chains a further async on
load settles correctly (verified to FAIL under the old delay(1)); a never-ready
LateInitSignal binding still settles (work-based, not readiness-based, semantics).
Left as-is: JS hydration queueMicrotask (hydration must run promptly, not block on
all in-flight work — commented in root.kt). Preload-mechanism consolidation
deferred (SsrPreloadable is public API).
PROTOTYPE — flagged for maintainer review: no timeout on the SSR settle (a
never-completing async in a page binding would hang the request; consider
withTimeout at SsrRouter); reactive version string derives from the branch name;
remember{}-internal async isn't tracked. See session notes.
Verified: reactive jvmTest 124/0; kiteui :library:jvmSsrTest 508/0,
:example-app:jvmSsrTest 16/0 (golden passes), :example-app:testDebugUnitTest 28/0,
compileKotlinJs + compileKotlinIosSimulatorArm64. JS hydration runtime unverified
(needs the browser pass).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
Install ReactiveThreadCheck.currentThread on the UI platforms so off-main
reactive-graph mutation fails fast (clear exception) instead of silently
corrupting the unsynchronized listener/dependency lists:
- Android: KiteUiActivity.onCreate installs { Thread.currentThread() }, once per
process, gated on Build.debug (reads FLAG_DEBUGGABLE).
- iOS: UIViewController.setup installs { NSThread.currentThread }, once, gated on
Build.debug (Platform.isDebugBinary).
Deliberately NOT enabled on JVM/jvmSsr: SSR legitimately mutates the graph from
Dispatchers.Default worker threads per request, so the guard would false-positive
there. JS has no real threads. Release builds stay inert (hook left null).
Verified: :library:testDebugUnitTest / :example-app:testDebugUnitTest,
:library:jvmSsrTest (proves JVM unaffected), iOS + JS compile.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
Font has identity equality and systemDefaultFont/systemDefaultFixedWidthFont were
`get() = Font { ... }`, handing out a fresh instance per access. Since FontAndStyle
is a data class comparing its Font by value, two themes built from the default font
compared unequal on Android/iOS — so Theme.Debugger.structurallyEquals flagged the
NORMAL re-derivation case as a collision (ThemeTest.debugger_collidingId_with
EqualContent_doesNotThrow failed under Robolectric; it passed under jvmSsr only
because the commonHtml Font actual is value-comparable — which is why the TH1 pass
missed it).
Make the Android and iOS system-default fonts stable `val` singletons. The Font
lambda defers all platform (Typeface/UIFont) access, so val init is pure and safe;
also removes a wasteful per-access allocation.
Verified: :library:testDebugUnitTest ThemeTest green under Robolectric; iOS compile.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
The quiescence-based settle waits on in-flight reactive WORK, so a page binding that starts an async block which never completes would hang the SSR request forever. Wrap awaitQuiescence() in a 30s withTimeout that throws a descriptive IllegalStateException including the tracker's pendingWorkCount, so the page bug surfaces loudly (as the surrounding comment already promised) instead of hanging. Generous bound so slow-but-legitimate chained loads still finish. Verified: :library:jvmSsrTest, :example-app:jvmSsrTest (quiescence + golden green). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
The reactive-vocabulary migration is done in code but the docs still taught the deprecated names, so new code kept minting them. Verified the old->new mapping from reactive's deprecated.kt before editing (Property->Signal, shared->remember, SharedReadable->Remember, LazyProperty->MutableRemember, LateInitProperty-> LateInitSignal, Readable/Writable->Reactive/MutableReactive, readableState-> reactiveState, reactiveScope->reactive). Swept CLAUDE.md, AGENTS.md, GoodKiteuiCode.md, docs/TESTING_GUIDE.md, the two SSR plan docs, the tracked .claude/skills/kiteui.md, and .junie/guidelines.md. Prose/examples only; semantics unchanged. CheatSheet.kt already used Signal (no change). Note: LateInitProperty's own @deprecated message in reactive points at a nonexistent "LateInitReactiveValue"; the real replacement is LateInitSignal (used here) — worth fixing the reactive deprecation message separately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
Update internal callers to the non-deprecated replacements named by each @deprecated(ReplaceWith), removing the deprecation warnings without any behavior change: - reactiveScope { } -> reactive { } (navComponents, wsretry, Navigator.js, ...) - deprecated ambient helpers -> context form: toast/alert/confirmDanger, closePopovers, pageNavigator (the deprecation's own "use directly through context" target — not a nav-API redesign) - onNext(semantic) -> themed(semantic); dynamicTheme { } -> dynamicThemed { } - RawVideoView.time -> currentTime via a Duration<->Double(seconds) lens (MediaView, VideoView) - CalculationContext -> CoroutineScope (SoundEffectPool.backgroundAudio, wsretry) - ContainingView -> ContainerElement (navComponents setup receivers) - kiteui encode/decodeURIComponent forwarders -> kotlinx.serialization.uri - kotlinx-datetime monthNumber/dayOfMonth -> month.number/day (ExternalServices) The nav-API DESIGN (navigate/goBack return-type asymmetry, the ambient accessor matrix's existence) is deliberately left unchanged. Deleting the now-possibly-dead deprecated shims (PageNavigatorBehavior, encodeToStringMap) is a separate follow-up, not done here. Verified: :library:jvmSsrTest, :example-app:jvmSsrTest, :example-app: testDebugUnitTest, :library:compileKotlinJs, :library:compileKotlinIosSimulatorArm64 all BUILD SUCCESSFUL. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
…r task name - ARCHITECTURE-SUGGESTIONS.md: add an implementation-status section (section 0) mapping each finding to its landed commit, the maintainer's deliberately-skipped items (R2/R3/V3/N4), and the deferred follow-ups (TH2-iOS, N7 part-2, reactive version/tag + remember-hole + LateInitProperty deprecation-message, SSR preload consolidation, remaining reactiveScope uses, browser-only JS hydration tests). Also folds in the prior maintainer Q&A pass. - CLAUDE.md: the JS dev server task is `jsViteDev`, not the stale `viteRun`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
Reverts the QuiescenceTracker work (kiteui commits 54bd8d7 + 1d2531f, and the reactive QuiescenceTracker commit dropped from reactive-bugfixes) at the maintainer's request. Reason: the tracker duplicated instrumentation with the existing StatusListener. Both are CoroutineContext.Element begin/end trackers of reactive activity, wired at adjacent/identical call sites (e.g. load{} is announced to both, 13 lines apart). They answer subtly different questions - work-in-flight vs value-readiness, which matters only for the never-ready case - so a proper implementation would extend StatusListener rather than run parallel to it. Since SSR is not yet in production, we defer rather than carry two parallel mechanisms. - SSR awaitAllResources() is back to delay(1). - Removes SsrContext/SsrResource/ElementContext/NativeElement/root.kt quiescence wiring, the settle timeout, and SsrQuiescenceTest. - Re-pins reactive to 6.0.0-prerelease-52-local (bug-fixes build, no quiescence). - ARCHITECTURE-SUGGESTIONS.md section 0 records the revert + the StatusListener-based path if it's ever revisited. Verified: builds green (see follow-up). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Qeb63y8jqUWG1TiJ9nthH
# Conflicts: # library/src/commonMain/kotlin/com/lightningkite/kiteui/views/NativeContainerElement.kt # library/src/commonMain/kotlin/com/lightningkite/kiteui/views/NativeElement.kt
iHoonter
approved these changes
Jul 28, 2026
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.