Chalak Kin Mai BaengBehind the build
From ideas, art, and sound to a working product and release. Explore each chapter, its challenges, and the solutions.
Project imageBlank ticket artwork; numbers and text are rendered on top at runtime.
Supporting media
Generate the paper; render live data in Flutter
Where it started
Generate the paper and illustration, then let the app render live numbers, credits, and text into reserved areas.
The design challenge
Numbers baked into artwork cannot follow live rounds and become difficult to read when scaled down on a phone.
The solution
Keep artwork separate from data, use blank templates, and limit sharing layouts to information the system actually supplies.
- Brief a ticket template with empty number and information fields.
- Generate paper, print texture, and illustration without baked-in numbers or readable text.
- Render live Flutter text and restrict public-share content to fields actually available from the API.
Technical details & evidence
Development process
The art direction combines Thai market paper, cats, and food in a parody style. Artwork supports live numbers, credits, and round results that must stay legible and editable.
- Brief a ticket template with empty number and information fields.
- Generate paper, print texture, and illustration without baked-in numbers or readable text.
- Render live Flutter text and restrict public-share content to fields actually available from the API.
Design constraints
Baked-in text cannot follow live rounds, loses legibility when scaled, and can imply fields the sharing API does not supply.
Implementation
Separate artwork from dynamic text, keep six blank number boxes, and review sharing concepts against the ShareResult response.
Evidence & limits
The art manifest assigns live composition to TicketPreviewScreen/TicketLeafPreview. Public-share v4 passed concept review, not runtime acceptance.
Source: docs/ticket-template-art-manifest.md + docs/ux-ui-redesign/asset-reviews/29-ticket-public-share-v4.md
Tap feedback must not block the action
Where it started
Play short tap cues through one shared service with a persisted user toggle.
The design challenge
Audio may not be ready on the first tap, but the button and its main action still need to respond immediately.
The solution
Reuse a bounded audio pool, request playback without waiting, and contain audio errors without blocking the action.
- Load the preference and initialize a reusable AudioPool.
- Connect sound through Material interactions while distinguishing disabled controls.
- Bound the pool to three players and request playback without waiting for completion.
Technical details & evidence
Development process
A short ui-click supports buttons and menus, with a persisted Profile toggle. Sound is a shared service rather than a new player for every control.
- Load the preference and initialize a reusable AudioPool.
- Connect sound through Material interactions while distinguishing disabled controls.
- Bound the pool to three players and request playback without waiting for completion.
Design constraints
The pool may not be ready on an initial tap, and native audio plugins may be absent in widget tests. Waiting for sound would couple the main action to a fallible service.
Implementation
Initialize and return immediately if the pool is unavailable, contain playback errors, use an ambient iOS session, and avoid taking Android audio focus.
Evidence & limits
Source includes widget tests for one sound request from an enabled button and none from a disabled button. These verify interaction contracts, not audible speaker output.
Source: apps/mobile/lib/core/feedback/ui_sounds.dart + apps/mobile/test/core/ui_sounds_test.dart
A retry must not debit credits twice
Where it started
Connect search, cart, checkout, and results, with the server owning credit and settlement decisions.
The design challenge
A checkout timeout leaves the client unsure whether it succeeded. Retrying must not debit credits a second time.
The solution
Identify each checkout consistently, return the original order on retry, and commit the order and credit changes together.
- Search local inventory and submit the order only at checkout confirmation.
- Send an idempotency key and fingerprint to identify retries of the same request.
- Commit the order, ticket blob, request record, and credit changes in one transaction.
Technical details & evidence
Development process
The simulation connects local search, cart, checkout, and server settlement in a monorepo where Flutter and Go share Protobuf contracts.
- Search local inventory and submit the order only at checkout confirmation.
- Send an idempotency key and fingerprint to identify retries of the same request.
- Commit the order, ticket blob, request record, and credit changes in one transaction.
Design constraints
A timeout leaves the client unsure whether checkout succeeded. A naive retry can debit twice; settlement recovery must resume without generating a different result.
Implementation
Return the original order for the same key/payload, reject mismatches, keep stored draw results immutable, and mark leaderboard application to prevent double counting.
Evidence & limits
The atomicity contract specifies recovery and rejection behavior. The production API validation lane uses read-only checks rather than creating orders or settling live draws for demonstration.
Source: docs/atomicity-and-idempotency.md + docs/ticket-purchase-ux.md
Audit the screen system before polishing
Where it started
Audit screen structure, colors, terminology, and the ticket journey before polishing individual controls.
What went wrong
Screens used inconsistent visual systems and sometimes exposed technical data that visitors did not need for their task.
The solution
The audit recommends consistent theming and screen ownership, using ticket, credit, and round-result language in the main flow.
- Map hierarchy, typography, color, navigation, and state problems across screens.
- Define screen ownership, domain components, and tokens instead of repeating generic card stacks.
- Review selection, order review, results, and product-facing language.
Technical details & evidence
Development process
Before redesign, the audit used source, widget tests, and existing Android captures because fresh runtime access was unavailable for every target. Visual findings were separated from live interaction evidence.
- Map hierarchy, typography, color, navigation, and state problems across screens.
- Define screen ownership, domain components, and tokens instead of repeating generic card stacks.
- Review selection, order review, results, and product-facing language.
Recorded problem
Home retained an old Offstage implementation beside the active one. Feature cream/navy/red colors diverged from a blue theme, while some screens exposed gRPC, hashes, or UIDs.
Implementation
The audit directs fixes toward shared theming and screen ownership, using tickets, credits, and round results instead of exposing implementation detail in the main flow.
Evidence & limits
These are findings and recommendations from the 5 Aug audit, not a claim that every item was closed. Runtime acceptance belongs to each specific implementation change.
Source: docs/ui-redesign-audit.md · 5 Aug 2026
Usable data should survive a refresh
Where it started
Render meaningful saved data first, then refresh in the background so returning users can continue quickly.
What went wrong
Full-page loading hid usable content, while stale cached orders could appear to belong to the current draw.
The solution
Keep usable content during refresh and remove stale orders when the server confirms none exist for the current draw.
- Persist Protobuf snapshots for bootstrap, draws, orders, results, leaderboards, and profiles.
- Rehydrate on foreground return and preserve content during refresh.
- Remove stale orders when the server reports none for the current draw.
Technical details & evidence
Development process
Startup and navigation render meaningful local snapshots first, then refresh in the background rather than making every route wait for an API.
- Persist Protobuf snapshots for bootstrap, draws, orders, results, leaderboards, and profiles.
- Rehydrate on foreground return and preserve content during refresh.
- Remove stale orders when the server reports none for the current draw.
Recorded problem
Replacing the page with loading hides usable content. Conversely, a cache not reconciled with the current draw can show an old order as today’s.
Implementation
Render meaningful cached snapshots first, reconcile against the server without replacing the whole page, and invalidate stale draw data.
Evidence & limits
The 11 Aug cache report records 126 Flutter tests, Android cache-first E2E, and two purchase E2E checks passing. This is a separate run from the 116-test store-readiness report.
Source: docs/qa/cache-first-startup-e2e-2026-08-11.md
Bring it together before release
From individual parts to one app
Prepare the Flutter production flavor as an AAB, matching the package and Firebase configuration to live services. The 11 Aug report identifies artifact 1.0.1+4.
The app and listing must agree
Keep read-only live API checks separate from isolated purchase and settlement tests. Readiness records 116 Flutter tests and 100 executable journey definitions, not 100 physical-device walkthroughs.
Recorded release status
The report verifies health checks, authenticated read-only APIs, and the prepared AAB. The product page provides Google Play and App Store links; this report is not an approval receipt for both stores.
Dated project evidence, not a live Store lookup.
Build details & records
Integration
- Start with one monorepo for the Flutter app, Go API, Protobuf contracts, infrastructure, and scripts.
- Connect anonymous login, number search, ticket submission, server settlement, results, and leaderboards.
- Separate mutating tests from production read-only checks so live QA does not settle rounds or grant credits.
Evidence
The 11 Aug 2026 report records passing Go tests, 116 Flutter tests, static analysis, and a 100-journey catalog, plus authenticated read-only API checks. This is dated project evidence, not a new test run today.
Store submission
A production flavor and AAB are prepared with package and Firebase configuration matched to the live services. The 11 Aug 2026 report records health checks, authenticated read-only API checks, notifications, and artifact validation. The product page provides Android and iOS download links.
Project records
- docs/ticket-template-art-manifest.md + docs/ux-ui-redesign/asset-reviews/29-ticket-public-share-v4.md
- apps/mobile/lib/core/feedback/ui_sounds.dart + apps/mobile/test/core/ui_sounds_test.dart
- docs/atomicity-and-idempotency.md + docs/ticket-purchase-ux.md
- docs/ui-redesign-audit.md · 5 Aug 2026
- docs/qa/cache-first-startup-e2e-2026-08-11.md
- README.md
- docs/architecture.md
- docs/qa/gcp-e2e-store-readiness-2026-08-11.md