Target: E:\GitHub\ComposeHooks
Date: 2026-06-23
Scope: hooks, ai, app Compose/KMP source sets; Android release compiler diagnostics for hooks and ai.
Excluded from scoring: tests, generated/build outputs, and demo-only issues where they do not affect the reusable library surface.
Confidence: High
Overall Score: 86/100
| Category | Score | Weight | Status | Notes |
|---|---|---|---|---|
| Performance | 9/10 | 35% | excellent | Strong Skipping is on, measured named skippability is 100%, and the remaining mutable/demo lazy lists now use stable keys. |
| State management | 9/10 | 25% | excellent | Hooks mostly expose clear holder/state APIs; FormRef now uses private backing maps and explicit operations, app examples use FormItemWithState, and the deprecated legacy FormItem Triple API remains only for compatibility. |
| Side effects | 8/10 | 20% | solid | useSse, useStateAsync, Fetch, and AI streaming paths now rethrow cancellation; polling no longer forces Dispatchers.Default. |
| Composable API quality | 8/10 | 20% | solid | The audited extracted components now expose/root-apply modifier; a few demo-local private UI helpers remain lighter-weight than a full shared UI kit. |
No unresolved critical findings remain from the prioritized remediation pass. The remaining item is a compatibility cleanup opportunity for the deprecated form API.
-
Fixed: cancellation is propagated from SSE and async-state hooks
- Evidence:
hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/usses/useSse.kt:107,hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/usses/useSse.kt:113,hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/useState.kt:146. - References: https://developer.android.com/kotlin/coroutines/coroutines-best-practices, https://developer.android.com/develop/ui/compose/side-effects
- Evidence:
-
Fixed: polling no longer overrides the caller/hook dispatcher with
Dispatchers.Default- Evidence:
hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/userequest/plugins/usePollingPlugin.kt:89. - References: https://developer.android.com/develop/ui/compose/side-effects, https://developer.android.com/kotlin/coroutines/coroutines-best-practices
- Evidence:
-
Fixed: audited extracted components now expose/root-apply
modifier- Evidence:
app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseChatExample.kt:432,app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseTtsExample.kt:428,app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseGenerateObjectExample.kt:445,app/src/commonMain/kotlin/xyz/junerver/composehooks/ui/component/DividerSpacer.kt:21. - References: https://developer.android.com/develop/ui/compose/api-guidelines, https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.md
- Evidence:
-
Fixed: remaining mutable/demo lazy lists now use stable keys
- Evidence:
app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseImmutableListExample.kt:224,app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseSelectableExample.kt:75. - References: https://developer.android.com/develop/ui/compose/lists
- Evidence:
-
Fixed:
FormRefinternal mutation surfaces are now explicit operations- Evidence:
hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/useform/FormRef.kt:23,hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/useform/FormRef.kt:57,hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/useform/FormRef.kt:84,hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/useform/FormInstance.kt:62,hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/useform/FormInstance.kt:100. - References: https://developer.android.com/develop/ui/compose/state, https://developer.android.com/develop/ui/compose/state-hoisting
- Evidence:
-
Fixed: app form examples were migrated off the deprecated
FormItemoverload- Evidence:
app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseFormExample.kt:100,app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseFormExample.kt:120,app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseFormExample.kt:339,app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseFormExample.kt:450; source search shows no actualFormItemcalls underapp/srcorai/src. - References: https://developer.android.com/develop/ui/compose/state, https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.md
- Evidence:
- Android 12+ splash icon status: not configured.
- Evidence: no
windowSplashScreenAnimatedIconitem was found underapp/src,hooks/src, orai/src; launcher icons are adaptive/static launcher resources only. - Finding: no Android 12+ splash-icon blur finding.
- References: https://developer.android.com/develop/ui/views/launch/splash-screen, https://developer.android.com/reference/androidx/core/splashscreen/SplashScreen, https://issuetracker.google.com/issues/520672537
Ceiling check
- Strong Skipping: on for measured modules (
gradle/libs.versions.toml:3uses Kotlin2.3.21; module reports showfeatureFlags.StrongSkipping = true). - Ceiling table applied: SSM-on.
- Module-wide
skippable%:ai4/4 = 100.0%;hooks51/51 = 100.0%. - Named-only
skippable%:ai3/3 = 100.0%;hooks46/46 = 100.0%. - Unstable shared types from compiler:
ai82 inferred unstable classes;hooks38 inferred unstable classes. Under SSM-on this count is not a cap by itself. - SSM-on binding evidence: no widespread source churn in measured
hooks/aicomposable bodies; previously flagged mutable/demo lazy-list keys have been fixed. - Qualitative score: 9/10.
- Ceiling: none. Remaining demo-level small allocations do not bind the SSM-on ceiling.
- Applied score: 9/10.
What is working
- The Android release compiler reports for
hooksandaiare clean on named skippability. - Previously risky app examples now use stable keys in the chat/agent/table message lists, table rows, immutable-list demo, and selectable demo.
useTablenow keeps a stableTableInstanceand returns stable state wrappers, reducing holder churn in the reusable library surface.
What is hurting the score
- A few composable bodies still allocate small option lists or perform string aggregation for display. They are sample-level, not systemic.
Animation performance signals
- Status: clean in audited hotspots. No
Animatable-without-rememberor composition-phase animated modifier reads were found in the library surface.animateContentSize()usage in cards/messages is ordinary declarative animation.
Evidence
hooks/build/compose_audit/hooks_release-composables.csv- 46/46 named restartable composables are skippable under SSM-on. References: https://developer.android.com/develop/ui/compose/performance/tooling, https://developer.android.com/develop/ui/compose/performance/stability/strongskippingai/build/compose_audit/ai_release-composables.csv- 3/3 named restartable composables are skippable under SSM-on. References: https://developer.android.com/develop/ui/compose/performance/tooling, https://developer.android.com/develop/ui/compose/performance/stability/strongskippingapp/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseImmutableListExample.kt:224- duplicate-prone random values are wrapped inDemoImmutableItem(id, value)and rendered withkey = { item.id }. References: https://developer.android.com/develop/ui/compose/listsapp/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseSelectableExample.kt:75- selectable demo renders withkey = { demo.userId }. References: https://developer.android.com/develop/ui/compose/lists
What is working
- Hook holders consistently expose observable
Statefields first and operations after them, matching the repository's own hook API standard. useTableanduseRequestkeep table/request state in holder abstractions rather than scattering ownership through UI examples.- AI hooks use immutable message lists (
ImmutableList/ persistent collections) for chat-like state, which is a strong signal for observable state boundaries. FormRefnow keeps its mutable maps as private backing state and exposes named internal operations for registration, validation, errors, touched/dirty state, pending validation, and submit callbacks.- The app showcase now uses
FormItemWithStatein its form examples instead of demonstrating the deprecated Triple overload.
What is hurting the score
- The legacy
FormItemTriple API remains for external compatibility, although it now has a deprecation warning pointing callers toFormItemWithState. Removing it would be a breaking API change and should be handled as a planned migration.
Evidence
hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/useform/Form.kt:177- legacyFormItemis deprecated and directs callers toFormItemWithState. References: https://developer.android.com/develop/ui/compose/state, https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.mdhooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/useform/Form.kt:216- newerFormItemWithStatenarrows usage throughFormItemState, which is the better direction. References: https://developer.android.com/develop/ui/compose/state-hoistinghooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/useform/FormRef.kt:23- field state is now held in a private backing map rather than a mutable map property exposed to the rest of the module.hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/useform/FormRef.kt:57- read access is exposed as a read-onlyMap<String, State<Any?>>.hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/useform/FormRef.kt:84- field registration is explicit throughregisterField.hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/useform/FormRef.kt:110- validation state is updated throughsetValidation.hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/useform/FormRef.kt:120- error state is updated throughsetErrorMessages.hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/useform/FormRef.kt:183- submit callback registration is explicit throughsetOnSubmitCallback. References: https://developer.android.com/develop/ui/compose/state, https://developer.android.com/develop/ui/compose/state-hoistingapp/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseFormExample.kt:100- app examples now useFormItemWithStatefor ordinary fields.app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseFormExample.kt:450- watched-field examples also useFormItemWithState; there are no remaining actualFormItemcalls underapp/srcorai/src.ai/src/commonMain/kotlin/xyz/junerver/compose/ai/usechat/useChat.kt:69- chat state usesState<ImmutableList<ChatMessage>>, which is a positive observable/immutable state signal. References: https://developer.android.com/develop/ui/compose/state, https://developer.android.com/develop/ui/compose/performance/stability/fix
What is working
Fetch,useSse, anduseStateAsyncnow rethrowCancellationExceptionfrom broad exception paths.- AI streaming hooks reviewed here (
useAsr,useTts,useChat,AgentLoop) mostly rethrow cancellation before converting errors to UI state. - Long-lived async work generally starts from event callbacks or Compose-owned scopes rather than directly from composition bodies.
- Polling delayed refresh now stays in the selected hook/plugin scope instead of unconditionally switching to
Dispatchers.Default.
What is hurting the score
- Some plugin and callback surfaces still use broad exception isolation by design, so future async additions should keep the
CancellationExceptionrethrow rule as a regression guard.
Animation side-effect signals
- Status: clean. No target-driven
Animatable.animateTo()launched directly from composition was found in audited library paths.
Evidence
hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/usses/useSse.kt:113- cancellation is rethrown before converting stream failures to hook error state. References: https://developer.android.com/kotlin/coroutines/coroutines-best-practices, https://developer.android.com/develop/ui/compose/side-effectshooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/userequest/plugins/usePollingPlugin.kt:89- delayed polling usesusedScope.launchwithout an unconditional dispatcher override. References: https://developer.android.com/develop/ui/compose/side-effects, https://developer.android.com/kotlin/coroutines/coroutines-best-practiceshooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/useState.kt:146-useStateAsyncrethrowsCancellationExceptionbefore handling normal exceptions. References: https://developer.android.com/kotlin/coroutines/coroutines-best-practiceshooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/userequest/Fetch.kt:196- broad catch now rethrows cancellation, a positive fix pattern to reuse. References: https://developer.android.com/kotlin/coroutines/coroutines-best-practicesai/src/commonMain/kotlin/xyz/junerver/compose/ai/usechat/useChat.kt:466- broad catch in the main chat path rethrowsCancellationException, showing the desired pattern is already present in adjacent code. References: https://developer.android.com/kotlin/coroutines/coroutines-best-practices
What is working
- Core shared app components such as
TButton,SimpleContainer,ExampleCard,LogCard, andScrollColumnexposemodifier: Modifier = Modifier. - Several selector/card/message components now pass the modifier to their root node.
- The hook library intentionally returns non-UI holders from composable hook functions; that is a domain-specific API shape and was not treated as a generic UI-emitting composable violation.
What is hurting the score
- Some private demo-only UI helpers remain intentionally compact and do not yet follow full shared-component API rigor.
Evidence
app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseChatExample.kt:432-ChatMessageBubblenow acceptsmodifier: Modifier = Modifierand applies it to the rootRow. References: https://developer.android.com/develop/ui/compose/api-guidelines, https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.mdapp/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseTtsExample.kt:428-VoiceSelectornow accepts and root-appliesmodifier. References: https://developer.android.com/develop/ui/compose/api-guidelines, https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.mdapp/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseGenerateObjectExample.kt:445-RecipeCardnow accepts and root-appliesmodifier. References: https://developer.android.com/develop/ui/compose/api-guidelines, https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.mdapp/src/commonMain/kotlin/xyz/junerver/composehooks/ui/component/DividerSpacer.kt:21-modifieris now the first optional parameter. References: https://developer.android.com/develop/ui/compose/api-guidelines, https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.md
- Remove the deprecated legacy
FormItemoverload in a future breaking release after external consumers have had a migration window. References: https://developer.android.com/develop/ui/compose/state, https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.md - Keep cancellation propagation as a regression rule for future hooks: any new broad
catch (Exception)/catch (Throwable)around suspend work should rethrowCancellationExceptionfirst. References: https://developer.android.com/kotlin/coroutines/coroutines-best-practices
COMPOSE-AUDIT-REPORT.mdwas deleted before this audit and recreated with current findings.- Prioritized remediation was applied after the audit:
useSse,useStateAsync, polling, auditedmodifierAPIs, mutable/demo lazy-list keys,FormRefinternal mutation surfaces, and appFormItemWithStatemigration were fixed. - The Gradle build with the audit init script completed successfully for
:hooks:compileReleaseKotlinAndroid,:ai:compileReleaseKotlinAndroid, and:app:compileReleaseKotlinAndroidafter the final form refactor. - Latest compiler diagnostics: named restartable skippability remains
hooks46/46 andai3/3. - Post-remediation verification completed successfully:
formatKotlin,lintKotlin,:hooks:compileReleaseKotlinAndroid,:app:compileReleaseKotlinAndroid,:hooks:compileTestKotlinDesktop,:hooks:desktopTest --tests xyz.junerver.compose.hooks.test.UseSseTest, and the targeted form tests (UseFormRefTest,UseFormInstanceTest,UseFormSubmitTest,UseFormTouchedDirtyTest,UseFormValidationTriggerTest). - A strict source search found no remaining direct assignments/removals through the old
FormRefmap properties in production or form tests, and no remaining actualFormItemcalls underapp/srcorai/src. - Compiler diagnostics used: yes, but only
hooksandaiproduced files underbuild/compose_audit;appwas audited from source because noapp/build/compose_auditreport was generated. - Strong Skipping mode: on in measured modules (
featureFlags.StrongSkipping = true). - Weight choice: default 35/25/20/20.
- Renormalization: none.
- Adjacent coverage notes: this is a Compose Multiplatform/KMP repository with
commonMain, Android, Desktop, and iOS source sets. Focus/keyboard and UI-test coverage were noted but not scored in this skill. - Android Launch UX resources: no splash-screen theme item found; no static splash icon risk was scored or prioritized.
material-3audit is optional; this skill did not score visual design or Material 3 token quality.- Run
compose-agent focus on kmpif platform-boundary quality is the next priority; this repo has significant CMP/KMP surface. - Run
compose-agent focus on testingif UI test, preview, and screenshot coverage should be assessed beyond the source-level audit. - Run
compose-agent focus on focusonly if Desktop/keyboard behavior becomes a product requirement beyond the current demo coverage.