feat: M-Cards — dynamic card-rendering engine (SDUI) - #11
Open
ramonfsk wants to merge 1 commit into
Open
Conversation
Standalone :cards-core (no dependency on :core, by design — a candidate for its own repo later) renders all 16 illustrative card layouts from the commissioned design, generically and data-driven rather than as fixed dedicated types: - schema/CardElement.kt: element vocabulary (TextBlock, Image, Avatar, Media, Container, ColumnSet/Column, FactSet, ActionSet, Input.*, plus custom Carousel/Stepper), inspired by Adaptive Cards' vocabulary without adopting its schema - CardParser: hand-parses the wire JsonObject recursively, never throws — an unrecognized type degrades only that element to Unsupported - CardEngine/CardIntent: per-card runtime state (values/selections/ answered actions), emits CardIntent (Notify/HostAction) rather than touching ChatSession directly — cards-android's/cards-ios's CardEngineStore is the thin adapter that turns a Notify into ChatSession.sendMessage - CardActionHandler: cards-scoped sibling of design doc §9's ChatActionHandler, for genuinely sensitive actions (payment, saving a contact) that never round-trip as a synthesized chat message Four resolutions found by actually composing the 16 cards rather than assumed up front: poll percentages are server-computed (Choice.percentage), not tallied client-side; choices/carousel's "exclusive selection" is two different behaviors (ActionSet.afterSelection); form/checklist gate submission via CardAction.enabledWhenInputsFilled; checklist's aggregate notify sentence comes from modeling it as one multi-select Input.ChoiceSet, not N toggles. :core change: ChatStateReducer only routed card-update to the in-progress message, so a server push to an already-completed message's card (progress/ordertracking, the case those cards exist for) could never reach it. Now routes by card id across all messages. Renderers: :cards-android (Compose, depends on new leaf :design-tokens-android extracted from :ui-android so card chrome matches bubble chrome) and cards-ios (SwiftUI Swift package). Video cards get real playback (AVKit VideoPlayer / Media3 ExoPlayer), not a placeholder. CardEngineStore is owned above the message list in both ChatScreen/ChatView, not inside a recycled row, so a half-typed form survives scrolling off-screen and back. tools/fake-sse-server: one scripted scenario per card, real wire-accurate payloads validated against CardParser. Docs: design-doc.md §8 (real vocabulary replacing the old generic sketch), §9 (CardActionHandler cross-reference), §15.3 (implemented scope, corrected from the earlier draft), new docs/cards-wire-schema.md (full field reference). Also: cards-ios's SPM .build/ output wasn't covered by .gitignore (only Gradle's build/ was) — added **/.build/.
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.
Summary
:cards-coremodule (no dependency on:core) — a generic, data-driven card-rendering engine inspired by Adaptive Cards' vocabulary, not a fixed set of dedicated card types:corechange:card-updatenow routes to a card by id across all messages, not just the in-progress one — needed for server-driven cards (progress/ordertracking) that update after the turn completes:cards-android(Compose) andcards-ios(SwiftUI) renderers, wired into both sample apps via aCardEngineStoreadapter owned above the message list (so scrolling a half-filled form off-screen and back doesn't lose it)VideoPlayer/ Media3 ExoPlayer), not a placeholdertools/fake-sse-serverscenarios, one per card, validated against the real parserdocs/design-doc.md§8/§9/§15.3 updated to match what was actually built; newdocs/cards-wire-schema.mdfield referenceWhat's verified
cards-core's own test suite (CardParserTest,CardEngineTest) — greenCardParser(noUnsupportedfallbacks, valid single-line SSE framing)Test plan
sample-app-android/sample-app-ios, trigger each of the 16 card keywords viatools/fake-sse-server, and visually compare against the commissioned designform/checklistsubmit-gating,pollvoting,carouselselection,ratingstars,payment/contacthost-action routingform) off-screen and back — confirm the values surviveprogress/ordertrackingcards can still be updated viacard-updateafter their message completes🤖 Generated with Claude Code