Repository navigation
feat(cmp): big switch — whole app UI from Kuikly to Compose Multiplatform - #3192
Merged
Merged
Conversation
Move the verifyCoreHasNoKuikly task body into buildSrc as registerNoKuiklyGuard() so the upcoming Compose Multiplatform UI module can register the same source + resolved-graph check. :core keeps its task name, configurations and wiring (check, preBuild, compileKotlin*). Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
New :ui-cmp module (Android + iOS, CMP 1.8.2) on top of :core and :designsystem, guarded by verifyUiCmpHasNoKuikly: - SlockCmpPages/SlockCmpRouter: the single migrated-pages allowlist that decides Kuikly vs CMP per page name. Product list is empty; debug builds admit compose_cmp_hello. Each page carries a rollback flag key and a SlockCmpRouteOverride seam (none installed: code default decides). - SlockCmpRoot: RaftTheme, LocalSlockPageArgs (page name + unchanged pageData JSON) and LocalSlockHost (HostCapabilities + host navigator). - BridgeWireHostCapabilities: HostCapabilities over the HRBridgeModule wire format (same method names and Kuikly-compatible params JSON as BridgeModule), so both native bridges serve CMP pages unchanged. - SlockCmpIos: ComposeUIViewController factory and the Swift/ObjC-facing bridge and navigator protocols. - compose_cmp_hello: debug demo page (theme, args, toast, close). Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
- SlockCmpActivity (ComponentActivity): renders SlockCmpRoot for the pageName/pageData extras SlockKuiklyActivity uses; edge to edge with adjustResize, system-bar icons follow the resolved Raft theme, system back and closePage finish the Activity, openPage goes back through SlockKuiklyActivity.start. - Router split: slockKuiklyHostActivityClass picks SlockCmpActivity for pages SlockCmpRouter routes to CMP, so SlockKuiklyActivity.start (the Kuikly router adapter, MainActivity, notifications) needs no change. Intents naming SlockKuiklyActivity directly (diagnostics deep-link alias, tests) are forwarded once routed. SlockApplication configures the router (debug builds admit the debug-only demo page). - HostCapabilities: BridgeWireHostCapabilities over a SlockKuiklyBridgeModule instance, called on the main thread like the Kuikly renderer does. The module takes an optional host Activity; Kuikly pages pass none, so their module still derives it from the render context. Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
- :shared links :ui-cmp into the iOS framework and exports it (the app keeps a single Kotlin framework); :core stays unexported. - SlockPageControllerFactory (Swift): the iOS router split. KRRouterHandler, HRBridgeModule openPage and the SwiftUI root ask it for a page; routed pages get SlockCmpIos.makeViewController (ComposeUIViewController) with the unchanged pageName/pageData, everything else the same KuiklyRenderViewController as before. Configured at launch (DEBUG admits the debug-only demo page). - SlockCmpPageNavigator: push/present and pop/dismiss like KRRouterHandler. - SlockCmpBridgeHost (ObjC): HostCapabilities transport that forwards each call to a private HRBridgeModule with the Kuikly module argument shape. - Compose resources: compose.ios.resources.sync stays false; the Podfile script phase now also runs :ui-cmp:syncComposeResourcesForXcodeApp, which copies the prepared resources of :designsystem and :ui-cmp (Raft fonts) to <App>.app/compose-resources/composeResources/<package>/, the path the CMP runtime reads. The committed CocoaPods phase in project.pbxproj matches. Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
…cans - test.yml: run :ui-cmp:testDebugUnitTest, compile it for iosSimulatorArm64, upload its reports, and add :designsystem/:ui-cmp sources and build scripts to the build-output cache key and paths (androidApp now depends on both, so a change there must miss the cache). - plan_test_tier.sh: ui-cmp iosMain changes keep the iOS compile on main. - check_auth_session_writes.sh and i18n_code_literal_scan.py include the ui-cmp source roots. Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
…lag forwarding, map-compat scan, host gap doc - iOS: CMP controllers hide the system nav bar and allow full-screen swipe-back, matching KuiklyRenderViewController under the FD swizzle. - Android: forward NEW_TASK/NEW_DOCUMENT/MULTIPLE_TASK when redirecting a routed page from SlockKuiklyActivity to SlockCmpActivity. - check_commonmain_map_compat.sh also scans ui-cmp commonMain. - Document the Android capability gap for SlockCmpActivity hosts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
… instead of Kuikly Phase A of the CMP big switch. :shared drops the Kuikly compose fork, Kuikly core/core-annotations, core-render-android, the Kuikly KSP page processor and mermaid-kuikly, and depends on CMP 1.8.2 (ui, foundation, material3, components-resources, ui-backhandler), JetBrains lifecycle 2.8.4, Coil 3.2.0, :designsystem and :ui-cmp (now api on every target). compileSdk moves to 35 as upstream Compose 1.8 requires. NetworkKMM, resource-core and the KuiklyBase datetime stay. The branch is expected not to compile until the per-file fixes land. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
scripts/cmp_migrate_imports.py rewrites every com.tencent.kuikly.* name in :shared, :androidApp and the vendored markdown sources: the forked compose packages to androidx.compose.*, Kuikly-only symbols and Kuikly core (Pager, Module, nvi JSON, @page) to the build.raft.app.compat layer under their existing simple names, Kuikly lifecycle to JetBrains androidx.lifecycle. It is idempotent so it can be re-run after rebases; --check reports pending changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
…p and markdown Output of scripts/cmp_migrate_imports.py; no hand edits. Remaining com.tencent.kuikly.core.base / core.views references are the native-view DSL and are migrated by hand. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
…nd Size core.views.PlayState / VideoPlayControl and core.base.BorderStyle go to the compat layer; core.base.Size (markdown image sizes) maps to androidx.compose.ui.geometry.Size, which has the same width/height shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
Keeps the Kuikly API surface :shared pages use, under the same simple names, on top of upstream Compose and the CMP hosts, so the per-file fixes stay small: - compat.json: JSONObject / JSONArray / JSONException with Kuikly's coercions and byte-identical toString (via toSlockJsonString). - compat.pager: Pager / ComposeContainer (setContent member), PageData (params + insets filled from WindowInsets), @page, IPagerEventObserver, IModuleCreator, PagerManager, BridgeManager, IPagerId/pagerId, LocalActivity (getPager(), pageData), urlParams. - compat.module: Module (toNative over SlockCmpHost.bridgeTransport, main-thread callbacks), RouterModule (host navigator), CodecModule (sha256 in Kotlin), PerformanceModule (null data), PerformanceData. - compat.ui: KuiklyFont (Android res/font, iOS SystemFont), BackHandler (CMP ui-backhandler), keyboardHeightChange (WindowInsets.ime), nestedScroll(NestedScrollMode), rememberAsyncImageBitmap (Coil), DrawableResource/imageResource, TextInputState, BorderStyle, PlayState, VideoPlayControl; setProp/bouncesEnable/scrollToTop/ keyboardDismissModeInteractiveIOS are documented no-ops. Supporting changes: SlockCmpHost gains an optional bridgeTransport (both hosts pass theirs); BasePager drops Kuikly snapshot/thread verification; the bundled TTFs move from androidApp to :shared res/font so KuiklyFont can resolve them; :shared compile tasks depend on the SQLDelight generator directly (the Kuikly KSP tasks used to carry that edge) and compose resources get a package. Also includes the reference-rewrite output for the PlayState/VideoPlayControl/BorderStyle/Size rules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
scripts/cmp_generate_page_registry.py replaces the Kuikly KSP page processor: it reads each @page("name") class (resolving const page-name constants) and writes pageName -> factory maps, one for commonMain and one for the gated visual fixtures (src/visualFixturesOff holds the empty stand-in for builds without -PslockVisualFixtures). RaftPageRegistry exposes pageName -> @composable for those pages, and RaftPagerHost runs a page inside SlockCmpRoot: creates the Pager with the unchanged pageData, calls willInit/created, fills PageData insets, maps lifecycle resume/pause (and an `active` flag for child pages) to viewDidAppear/viewDidDisappear, and provides LocalActivity and LocalSlockPageArgs. :ui-cmp gets a RaftPageProvider seam (SlockCmpRouter.installPageProvider) that SlockCmpRoot falls back to; hosts do not install it yet and routing (rendersOnCmp) is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
- compat.ui: pull-to-refresh stand-in (PullToRefreshState, rememberPullToRefreshState, pullToRefreshItem; indicator only while refreshing, no gesture), PointerEvent.nativeEvent (always null), withKuiklyIdleContext(pagerId, block) with Kuikly's signature. - Android jvmTarget 11 for :shared (Coil 3 inline functions are JVM 11). - Drop compose components-resources from :shared: it has no compose resources and the generator's source wiring breaks under the afterEvaluate setSrcDirs. - Page registry generator skips the compat package and comments (a KDoc example was registered as a page). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
Foundational files every page group depends on: - SlockLucideIcon: TEMPORARY stub with the same API (rounded outline in the icon colour, rotation/spin kept); the ImageVector port lands separately. - SlockNativePixelAvatar / SlockNativeTiltedBadgeChrome: drawn with Canvas instead of native Kuikly views. - SlockSandboxWebView: placeholder box (TODO(cmp-switch) AndroidView / UIKitView); controller, navigation state and API unchanged. - SlockKuiklyChildPage: composes the child page inline through RaftPagerHost (active -> appear/disappear), same pageData contract. - SlockTabBar: native floating tab bars fall back to the Compose dock. - BasicWidget: square/themed corners without setProp, Modal on upstream Dialog (no ref/scrimColor), text fields without native props, the native editing-state reader reads the Compose TextFieldValue, Hover uses stickyHeader; TabPager keepItemAlive -> beyondViewportPageCount. - Drop Kuikly's beyondBoundsItemCount list argument (12 call sites), stale base.bridgeModule imports and the Android native dispatch capture; key-event extension imports in AuthPages. - SlockInProcessNotifyModule (a Kuikly render module) moves to the androidApp Kuikly host it belongs to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
… on CMP
- SettingsHomePage: replace Kuikly MoveableDrawer with a new Compose
RaftMoveableDrawer (push-style drawer, same open/close/progress/isOpen
API, horizontal drag gated by userScrollEnabled, drawer content kept
composed like the Kuikly pager page); scrim DivView node becomes a plain
Box with the existing clickable; SelectionContainer without Kuikly state;
drop com.tencent.kuikly.core imports.
- ThreadPage: SelectableText -> SelectionContainer { Text }, import the
Modifier testTag (the semantics property was imported instead).
- ThreadComposerPage: Kuikly ModalBottomSheet(visible, dismissOnDrag) ->
M3 ModalBottomSheet with sheet state (no drag handle, no insets,
transparent rectangle container) shown while detailsVisible.
- Visual fixtures: drop the SelectableText import.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
BasicWidget TextField (shared file, additive change): the previously
ignored imeActionRequested / Enter routing props are expressed in Compose.
- New raftResolvedKeyboardOptions: a multi-line field that did not request
an IME action falls back to ImeAction.Default (newline key), matching the
Kuikly text area line-mode contract.
- New Modifier.raftImeEnterAction: when imeAction is Send, hardware Enter
sends on key-up and Shift+Enter inserts a newline (multi-line), porting
SlockKuiklyActivity.handleSlockTextFieldEnterKey. Soft-keyboard Send
keeps going through KeyboardActions.onSend.
TODO(cmp-switch): systemNewlineAction ("Insert newline" selection-menu
item) still ignored; iOS soft Send on multi-line fields unverified.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
Rewrite compat RaftPullToRefreshCompat.kt (owned by this group): the Kuikly-named PullToRefreshState now wraps a material3 pull-to-refresh state; new Modifier.raftPullToRefresh(state, enabled) applies material3 Modifier.pullToRefresh (the PullToRefreshBox engine) to the list, and pullToRefreshItem registers onRefresh/threshold and grows with the pull distance so the existing Slock indicator renders from pullProgress. Wire the modifier at every call site: activity inbox (+ state body), home list, saved panel (SettingsHomePage), members list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
RaftVideoPlayer (expect/actual) replaces Kuikly's native VideoView: - Android: Media3 ExoPlayer + PlayerView in AndroidView, ported from androidApp SlockKRVideoViewAdapter (progress contract/attempt gate, native controller, fullscreen + back handling). - iOS: AVPlayer in an embedded AVPlayerViewController via UIKitViewController, ported from SlockKRVideoViewHandler.m (playback audio session, end/failed/stalled notifications; status and time are polled every 500ms instead of KVO). Keeps the VideoPlayControl/PlayState control surface; seeks are issued once per request id. RaftViewCapture replaces nativeRef + toImage(FILE): Modifier.raftCaptureRef records the subtree into a GraphicsLayer; captureToPngFile() encodes the toImageBitmap() result as PNG into an app-cache/tmp file and returns a file:// ref (admitted by the existing temporary-file retirement). Outside group ownership: shared/build.gradle.kts androidMain adds media3-exoplayer, media3-ui, core-ktx and activity-compose (additive). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
SlockFilePreviewKuiklyVideo now renders RaftVideoPlayer (also used by the inline thread video and hidden inline audio players), replacing MakeKuiklyComposeNode<VideoView> and the seekToTimeMs setProp. The image editor captures through Modifier.raftCaptureRef/captureToPngFile instead of nativeRef/ViewRef/toImage; the capture runs NonCancellable so a late file still reaches the operation-gate cleanup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
…stream Compose Kuikly's SpanStyle carried inlineBoxStyle and textDecoration color/thickness/offset; upstream SpanStyle has none of them. - RaftInlineBoxStyle / RaftInlineUnderlineStyle travel as string annotations over the boxed range; Modifier.raftInlineDecorations paints them behind the text from the TextLayoutResult (per line segment, rounded fill + border, coloured underline). - SlockMarkdownTextProvider.RichText applies the painter; inline code (vendored annotator), rich-text tag chips / ordinary-mention underline and message-permalink chips emit the annotations. - Pressed tag/permalink states use a span background instead of the Kuikly box swap (TODO(cmp-switch)). - Placeholder(...) now passes PlaceholderVerticalAlign (TextCenter for icons/spacers, AboveBaseline for markdown images). TODO(cmp-switch): chip padding/margins no longer reserve advance width, vertical box extent is estimated from the span font size, and markdown link underline thickness/offset is dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
mermaid-kuikly (MermaidKuiklyRenderer on a Kuikly CanvasView) is gone. RaftMermaidCanvas ports it to a Compose Canvas over the same mermaid-layout LayoutScene: rects (rounded), ellipses (fill opacity), lines/polylines (dashed 6/4), polygons (linear gradient), text anchored at the alphabetic baseline, #rgb/#rgba/#rrggbb/#rrggbbaa colours. SlockMermaidDiagramBlock is now a plain commonMain function; the identical android/ios actuals are deleted. Build: the mermaid-core / layout-api / layout-simple deps move from androidMain + iosMain to commonMain (KMP artifacts publish common metadata) — shared/build.gradle.kts is touched for this, outside the markdown ownership. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
# Conflicts: # compose/shared/build.gradle.kts
…and sync bridge calls - SlockCmpRouter: the allowlist becomes "every registered page" (the :ui-cmp debug pages admitted in this build plus everything the installed RaftPageProvider renders). The per-page rollback seam (SlockCmpRouteOverride, rollbackFlagKey, enabledByDefault) is removed: there is no Kuikly host left to roll back to. configure(debugBuild) / rendersOnCmp / page keep their signatures (iOS SlockCmpIos unchanged). - SlockCmpHost gains RaftHostEvents (native host -> page events, the Kuikly sendEvent channel: window insets, adaptive panes); HostBridgeTransport gains callSync (default null) for Kuikly syncCall bridge methods. - :shared compat (additive, shared files): RaftPagerHost forwards host events to its page as pager events; Module.toNative(syncCall = true) answers from HostBridgeTransport.callSync when the host serves the method (falls back to the previous async + empty value otherwise). RaftPageRegistry.install doc. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
Coil 3.2 had no network fetcher, so every remote image (avatars, attachment thumbnails, rememberAsyncImageBitmap / imageResource) stayed blank after the Kuikly removal. Add the Raft singleton ImageLoader: - RaftImageSource mapper/keyer/fetchers: http(s) over SharedBinaryDownloader (single flight per content key, Coil disk cache 128MB + memory cache), assets:// bundled assets (Android APK assets; iOS main bundle, MR bundle, asset catalog, ported from KuiklyRenderComponentExpandHandler), inline <svg>, and slock-avatar-render-v1 managed avatar models read through a host hook (RaftImageHost.managedImageReader, registerDecoded + lease close via RaftManagedImageCommitInterceptor). - task #403 keys: url = slockAvatarContentIdentity fingerprint, managed = nativeBytesKey; commit generation validated (newer commit evicts the decoded entry) instead of being part of the key. - coil-svg 3.2.0 for SVG decoding; androidx.webkit added to androidMain for the upcoming sandbox WebView (build.gradle.kts). Shared compat (additive): rememberAsyncImageBitmap loads via the Raft loader at original size; RaftPagerHost / RaftPageRegistry.install() call raftInstallImageLoader(). TODO(cmp-switch): hosts must install RaftImageHost.managedImageReader; the #403 contentKey wire field (url<->managed sharing) is not emitted yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
…witch, group 7) SlockSandboxWebView was a placeholder box. It now renders through the new expect RaftSandboxWebViewHost with the same parameters, controller commands and events, so SettingsStandaloneWebViewHost, InAppBrowserPage, release notes, server setup and the HTML file preview work unchanged. - Android: RaftAndroidSandboxWebView ports androidApp SlockSandboxWebViewRenderView (settings, navigation fence, document-start auth script + bootstrap fallback, raftHostPort WebMessageListener bridge, runtime JS gating, desktop initial scale, renderer crash recovery, file chooser via RaftWebViewFileChooserHost, host main-document hook). - iOS: RaftIosSandboxWebView ports iosApp SlockSandboxWebView.m (WKWebView rebuild on configuration attrs, ephemeral data store, user scripts, host bridge with origin/document checks, KVO navigation state, pageZoom desktop fit, content-process recovery, loadCommit). - Host message kind mapping moved to raftDecodeWebViewHostMessage (common). TODO(cmp-switch): Android host must implement RaftWebViewFileChooserHost and set RaftAndroidWebViewHooks.mainDocumentProvider (release notes cache). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
Kotlin/Native 2.1 exposes observeValueForKeyPath only as an NSObject category extension, so it cannot be overridden (cross-compiled check: "Unresolved reference NSKeyValueObservingProtocol"). Replace the KVO of the .m port with a 250ms NSTimer poll while a WebView is attached; navigation state callbacks stay de-duplicated. TODO(cmp-switch). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
handleKeyboardSend gated on fieldValue.composition for every onSend, so with Gboard keeping the Latin word in composition the soft Send key did nothing. Composition gating now applies only to a hardware Enter, which raftImeEnterAction marks by invoking onSend with the (renamed, now internal) RaftHardwareEnterKeyboardActionScope receiver; the soft Send key always sends and the live Send snapshot commits the composition. Shared BasicWidget change: RaftSendKeyboardActionScope (private) becomes internal RaftHardwareEnterKeyboardActionScope. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
The 250ms navigation-state poll (the KVO stand-in) kept running when the page was covered by another one. The container now stops the timer when it leaves the window and restarts it (with one catch-up read) when it returns; a WebView built while detached starts polling on attach. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
raftImeEnterAction decided on key-up from the Shift state at that moment, so releasing Shift slightly before Enter sent the message. The decision is now recorded on key-down per field (RaftHardwareEnterRouter) and the key-up only carries it out; a key-up without a matching key-down does nothing. Shared BasicWidget change: raftImeEnterAction becomes @composable (it remembers the router); new internal RaftHardwareEnterRouter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
…IGINAL rememberAsyncImageBitmap decoded every model at the original size; the image preview surface used it per thread row / file / composer preview, so each thumbnail decoded a full-resolution bitmap (OOM risk). The Kuikly adapter downsampled to the screen. - rememberAsyncImageBitmap(model) now bounds the longer edge to the window's long edge (FIT, INEXACT sampling); new overload with an explicit maxDecodeEdgePx; RAFT_IMAGE_DECODE_ORIGINAL is an explicit opt-in used only by imageResource (bundled atlases drawn by sub-rect). - The preview surface only reads the decoded dimensions (render identity), so it decodes a 64px subsampled copy. TODO(cmp-switch): header-only dimension read. Compat layer change (RaftUiCompat: additive overload/helpers; default decode bound changed) and shared BasicWidget call site. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
…clear on logout - The network fetcher only commits bytes that sniff as an image (raftSniffImageMimeType); a 2xx captive-portal / proxy HTML body is no longer pinned on disk under the image's key. - Disk metadata now records the store time; entries of URLs that are not raft immutable avatar paths (Gravatar, external URLs) expire after 24h and are fetched again (the expired entry is still served if the refetch fails). Legacy metadata-only entries revalidate. - raftClearImageCaches() drops the Coil memory + disk caches; the auth data source calls it next to the avatar owner retirement on logout, session clear and account replacement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
RaftVideoPlayer kept video/audio playing after the app was backgrounded. Android: the player observes the host LifecycleOwner and pauses on ON_STOP (not ON_PAUSE, so a visible multi-window Activity keeps playing, per Android media guidance). iOS: the controller pauses on UIApplicationDidEnterBackgroundNotification. The applied play control is left unchanged, so returning does not auto-resume; PAUSED is reported through the usual state callbacks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
Inactive app-shell tabs stay composed (raftPlacedOnlyWhenActive), so their BackHandlers stayed registered and, registered after the shell root handler, won: open a filter menu on Tasks, switch to Members, press Back and the invisible Tasks menu closed. RaftPagerHost now provides LocalRaftPageActive (ANDed with the ancestor pages' value via RaftProvidePageActive); the compat BackHandler and SlockBackHandler AND it into `enabled`. Compat layer change: additive (LocalRaftPageActive, RaftProvidePageActive, raftBackHandlerEnabled) plus BackHandler honouring it; RaftPagerHost provides it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
Module.liveCallbacks was never cleared on page destroy and the callback scope is global, so a late native result ran the callback of a destroyed page (which could openPage/closePage on a finished Activity). PagerManager.unregister (after PAGER_EVENT_WILL_DESTROY, so destroy-time cleanup calls still go out) now detaches the page's modules: pending callbacks are dropped and later calls are still sent but never call back. Compat layer change (Module.detach, Pager.detachModules, unregister). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
getPager returned pagers.values.lastOrNull() for an unknown id, routing a destroyed page's bridge calls to another Activity's page. Explicit ids now resolve only to their own live page (getPager throws, new getPagerOrNull returns null). All callers pass the id of their own LocalActivity pager, which is registered for the whole composition (child effects are disposed before RaftPagerHost unregisters the page). Compat layer change (RaftPagerCompat PagerManager). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
…entHub Every Android app-scope event (multi-MiB realtime batches included) was serialized to JSON on the main thread and published to SlockKuiklyEventHub, whose only listener (the Kuikly render view bridge) was deleted with the CMP switch; no production code registers a listener. The Android app-scope bus is now SlockEnhancedNotifyCenter, in-process only, like js/native. SlockKuiklyEventHub, the dispatcher and its transport test are removed; installAndroidAppScopeEventBus stays as a no-op for its call site. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
…mits RaftMermaidCanvas sized itself scene size * zoom without bound; a huge diagram or zoom produced a node size Compose cannot encode in Constraints (throws during layout). The draw scale is now clamped so both sides stay within 32,000 px (always representable). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
The Kuikly-era SlockSandboxWebViewRenderViewTest was deleted with the Kuikly WebView adapter (0006940). Port its exact-main-document test to raftIsExactMainDocumentUrl and cover raftDocumentStartScriptOriginRule / raftInjectedJavaScriptBootstrapBaseUrl and RaftWebViewBootstrapNavigationFence, which the CMP sandbox WebView uses to fence the native bridge and the bootstrap document. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
… pop and the two-argument host-hooks install Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
This was referenced Oct 20, 2026
stamp-build Bot
pushed a commit
that referenced
this pull request
Oct 20, 2026
Re-targets #3184 onto release/1.14.2, where the gate still ships (main deleted it with the Kuikly→Compose switch, #3192). - `ssl.SSLError` is not a subclass of `urllib.error.URLError`/`ConnectionError`, so it escaped the `get_bytes` retry ladder and surfaced a CDN concurrency flake (UNEXPECTED_EOF_WHILE_READING) as a hard CONFLICT — a transport blip reported as a release incident. It is now retried like the others. - Stale-closure failures said 'unexpected coordinates'/'closure missing' when the closure was merely pinned to an older release; the error now names the release the closure is on. Pure refactor of the request open into `_fetch_once` keeps UA, _NoRedirect and the 60s timeout identical. Self-test green on all arms, including ssl-retry-recovers and ssl-retry-exhausted. Co-authored-by: Mahua <mahua@mail.build>
stamp-build Bot
pushed a commit
that referenced
this pull request
Oct 21, 2026
…nversation Bug A (QA 11500008, release blocker): in two-pane, once the detail pane showed a conversation (cold-start-wide default landing, or a detail carried across a width change), tapping another conversation in the Home or Activity list moved the list highlight and the workspace selection, but the detail kept rendering the old channel until the activity was paused/resumed. Root cause (CMP switch #3192, not #3353): RaftPagerHost creates a new pager when its args change (remember(args)) but invoked the new pager's content in the old pager's composition slot. ThreadPageScreen reads payload, instanceId and the adaptive close request into unkeyed remember, so the new page kept the previous page's values: no new effect_enter, no READY. Kuikly gave every page its own render view; key the page composition by pager to restore that. - RaftPagerHost: key(pager) around the page content. - Shell trace adaptive_trace side=shell hop=detail_mount at the hand-off from selection to detail page; a mount without a matching detail effect_enter now shows a stale detail directly in the log. - Tests: RaftPagerHostPageSwapTest (Robolectric; red before the fix with rendered=[channel-all, channel-all]); AdaptiveDetailNavigationTest covers landing / width-change / user-opened detail -> tap replaces it, and #3353's landing still drops on a compact window. Verified on emulator (wm size 1920x1200 <-> 1200x1920, alpha build, staging): cold-start-wide landing then list tap, portrait->wide with detail then tap, Activity list tap, and landing->compact shows the Home list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>
stamp-build Bot
added a commit
that referenced
this pull request
Oct 21, 2026
…nversation (#3380) Bug A (QA 11500008, release blocker): in two-pane, once the detail pane showed a conversation (cold-start-wide default landing, or a detail carried across a width change), tapping another conversation in the Home or Activity list moved the list highlight and the workspace selection, but the detail kept rendering the old channel until the activity was paused/resumed. Root cause (CMP switch #3192, not #3353): RaftPagerHost creates a new pager when its args change (remember(args)) but invoked the new pager's content in the old pager's composition slot. ThreadPageScreen reads payload, instanceId and the adaptive close request into unkeyed remember, so the new page kept the previous page's values: no new effect_enter, no READY. Kuikly gave every page its own render view; key the page composition by pager to restore that. - RaftPagerHost: key(pager) around the page content. - Shell trace adaptive_trace side=shell hop=detail_mount at the hand-off from selection to detail page; a mount without a matching detail effect_enter now shows a stale detail directly in the log. - Tests: RaftPagerHostPageSwapTest (Robolectric; red before the fix with rendered=[channel-all, channel-all]); AdaptiveDetailNavigationTest covers landing / width-change / user-opened detail -> tap replaces it, and #3353's landing still drops on a compact window. Verified on emulator (wm size 1920x1200 <-> 1200x1920, alpha build, staging): cold-start-wide landing then list tap, portrait->wide with detail then tap, Activity list tap, and landing->compact shows the Home list. Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build> Co-authored-by: Lagoon <raft+lagoon@agents.stamp.build> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
stamp-build Bot
pushed a commit
that referenced
this pull request
Oct 23, 2026
… zero tests Task #18 CI gaps, from the mobile test audit (sections 4 and 5). - test.yml iOS job: drop the "native image loader and media audio session" step (both XCTest classes were deleted in #3192; it executed 0 tests and passed). Run the whole iosAppTests bundle instead of three classes, and fail unless every class ran (assert_xctest_ran.py, with a self-test). Run shared iosTest (*IosTest, *IosSmokeTest) on the simulator and fail on zero cases. - New iOS behaviour tests: RaftSandboxWebViewNavigationFenceIosTest (the iOS fence; counterpart of the Android Robolectric test from #3565) and an adaptiveNavigation: bridge-method check in RaftCmpBridgeSelectorTests. - ios_deterministic_repro.test.mjs runs the real repro script with a fake iOS driver and checks it taps only the QA server row and never falls back to another row. - Android device suite: promote AndroidAdaptivePaneHostConnectedTest, QaSessionBootstrapInboxConnectedTest and RaftComposedCharacterBoundaryRuntimeTest; document two more pointer harnesses as manual; delete RaftMermaidVisualCaptureConnectedTest (no assertions). - Delete three UI tests nothing runs or can run (T258ConsumerUITests, IosUseInteractionTests, RaftAdaptivePaneRuntimeTests). - buildSrc HangWatchdogTestListenerTest: fix for the Gradle 9 TestResult API and run it in the android job. - Wire 13 script tests into scripts-selftest.yml / qa-infra-test.yml, and add the Lucide generated-icon up-to-date check (cached pinned checkout) to design-tokens.yml. qa-infra-test now also triggers on the two workflows stamp_report.test.mjs reads. Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
stamp-build Bot
added a commit
that referenced
this pull request
Oct 23, 2026
… zero tests (#3572) * test(ci): run the tests nothing ran; fail iOS test steps that execute zero tests Task #18 CI gaps, from the mobile test audit (sections 4 and 5). - test.yml iOS job: drop the "native image loader and media audio session" step (both XCTest classes were deleted in #3192; it executed 0 tests and passed). Run the whole iosAppTests bundle instead of three classes, and fail unless every class ran (assert_xctest_ran.py, with a self-test). Run shared iosTest (*IosTest, *IosSmokeTest) on the simulator and fail on zero cases. - New iOS behaviour tests: RaftSandboxWebViewNavigationFenceIosTest (the iOS fence; counterpart of the Android Robolectric test from #3565) and an adaptiveNavigation: bridge-method check in RaftCmpBridgeSelectorTests. - ios_deterministic_repro.test.mjs runs the real repro script with a fake iOS driver and checks it taps only the QA server row and never falls back to another row. - Android device suite: promote AndroidAdaptivePaneHostConnectedTest, QaSessionBootstrapInboxConnectedTest and RaftComposedCharacterBoundaryRuntimeTest; document two more pointer harnesses as manual; delete RaftMermaidVisualCaptureConnectedTest (no assertions). - Delete three UI tests nothing runs or can run (T258ConsumerUITests, IosUseInteractionTests, RaftAdaptivePaneRuntimeTests). - buildSrc HangWatchdogTestListenerTest: fix for the Gradle 9 TestResult API and run it in the android job. - Wire 13 script tests into scripts-selftest.yml / qa-infra-test.yml, and add the Lucide generated-icon up-to-date check (cached pinned checkout) to design-tokens.yml. qa-infra-test now also triggers on the two workflows stamp_report.test.mjs reads. Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(ios,scripts): drive the iOS fence by URL; drop a cross-clock assertion Kotlin/Native imports WKNavigationAction/WKFrameInfo properties as final, so the fence test cannot stand an action in. decidePolicy now only reads the action and hands the URL and frame kind to decideNavigationPolicy, which holds the unchanged fence logic and is what the test drives. test_run_android_visual_capture.sh compared the host runner's monotonic clock with the fake device's (200 s), so it failed on any runner up for less than 200 s. The device-window assertions stay. Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(ci): keep the suite self-test edit to the class counts #3571 deletes the registry greps in this file; adding three more would only conflict with it. Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(ci): run iosTest after the XCTests and print its failure messages The first full run reached iosTest: 20 cases, 1 failure (RaftSqlDatabaseIosSmokeTest file-backed migration rollback), reported without a message. Print each failing case's message and trace from the JUnit XML, upload the reports, and run the step last (and after an XCTest failure) so one red suite does not hide the other. Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(durable): treat thread pending-send objects as core in the shared migration check The iOS smoke test (first run in CI by this PR) failed: after the v1->v9 migration the non-core schema snapshot also held threadPendingSendEntity and its two indexes. #3092 added them to the Android instrumented copy of this filter but not to the shared copy, which only the never-run iOS test uses. Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build> Co-authored-by: Lagoon <raft+lagoon@agents.stamp.build> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
stamp-build Bot
added a commit
that referenced
this pull request
Oct 23, 2026
…nd SVG data URIs (#3583) Android visual capture has failed 12 cases on main since the Kuikly to Compose Multiplatform switch (#3192) and the fixture alignment in #3231 (first full failing run 39440171589 at 86449d0; last full green run 39328865111 at a52d784). - Pin menus and compact message menus: RaftModalAnchoredPopover renders in Modal, which on CMP is its own Dialog window. The long-press render poll and its before frame drew only the activity decor, so the open menu was invisible (changedRatio 0). They now use the fixture-owned window composite, the same one the final capture has used since #3231. - Pending-mention batch partial/details and create-agent dialog-empty: findAccessibilityNodeInfosByText goes through the view's AccessibilityNodeProvider. Compose's provider does not implement by-text search, so the lookup found nothing even with "View" on screen. Walk the accessibility tree for an exact text match instead. - File preview image actions (and the image editor canvas): the fixture image is a percent-encoded data:image/svg+xml URI. Coil's DataUriFetcher accepts only ;base64, and threw, so the preview fell back to its placeholder. raftImageSourceOf now decodes textual SVG data URIs into the existing InlineSvg source. - Status dot states: #3231 aligned the fixture with the web case (four presence dots in 342x92, no pulse row), so a correct frame is 98.8% canvas and tripped the blank-frame proxy. The crop now declares its content (sticker ink plus four presence fills, each >= a 4dp square), which replaces the one-colour proxy for this case only and still fails a blank frame or a missing dot. Co-authored-by: Lagoon <raft+lagoon@agents.stamp.build> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
Moves the whole mobile UI from Tencent Kuikly to JetBrains Compose Multiplatform 1.8.2 in one step, per artin: migrate everything first, fix bugs after. Kuikly UI is removed from Android and iOS.
This supersedes #3191 (the CMP shell; iOS cannot link Kuikly's Compose-1.7 klibs next to the CMP 1.8 runtime) and #3185 (Kuikly skills removal, folded in here).
What changed
:sharedhas no Kuikly UI dependencies. The compose fork, core, render, the KSP page processor and mermaid-kuikly are removed.scripts/cmp_migrate_imports.py, which can be re-run.scripts/cmp_generate_page_registry.py). Page names and the pageData contract are unchanged.compose/shared/.../app/compat/keeps Kuikly names so call sites barely change: JSON, Pager/PageData, the Module bridge, KuiklyFont, keyboard height, BackHandler, pull-to-refresh.SlockCmpActivity, and the Kuikly host is deleted.RaftHostActivitycarries the native capabilities, with a sync bridge path for the sync calls.RaftPageViewControllerembedding the CMP page, and the Kuikly pod and KuiklyExpand are deleted.HRBridgeModule.Verification
:shared7167+ and:androidApp534+ pass.:androidApp:assembleDebugbuilds, and CI "Android compile & tests" is green.Known follow-ups (
TODO(cmp-switch))editor_drift).🤖 Generated with Claude Code