poc(flutter): web stability, simulation tests & docs (#29)#30
Conversation
Closes #27 - Add assets/poc-simulation.json (copy of docs/poc/poc-simulation.json) - Add lib/poc_simulation.dart: PocSimStep sealed classes + runPocSimStep executor for fetch / host / logout_provider steps - Add lib/widgets/mock_api_sheet.dart: bottom sheet with all 9 simulation steps as buttons + integrated HTTP trace log - Add lib/widgets/provider_config_sheet.dart: shows getProviderMeta() as formatted JSON - Add lib/widgets/simulation_panel.dart: sequential auto-loop runner with per-step result rows, 404-probe toggle, and session-dead guard - Refactor home_screen.dart: status grouped by provider, per-row dynamic actions driven by grantHint, Config button, token exchange dropdown - Add getMockApiBase() to morph_init.dart (Android 10.0.2.2 aware) - Add http as direct dependency; fix morph_realm.json localhost:4200 URIs - flutter analyze: no issues Co-authored-by: Cursor <cursoragent@cursor.com>
…bility Co-authored-by: Cursor <cursoragent@cursor.com>
… new tab Co-authored-by: Cursor <cursoragent@cursor.com>
Debug mode loads 596 separate JS files (30-60s) causing the Keycloak authorization code (60s default) to expire before completeOAuthCallback can exchange it, resulting in a blank screen / no-token state. - run_web.sh: default to --profile (single bundled JS, ~2s load); --debug flag available when hot-reload is needed - morph-realm.json: accessCodeLifespan 60s→300s so even slower loads won't race the code expiry window Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
… log panel Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…rashes Co-authored-by: Cursor <cursoragent@cursor.com>
…tity deadlock ContextStore.setData with Boundary.user silently drops writes when _activeUser is not set. On web the OAuth redirect reloads the page so the user identity is never established before the token write, causing all session-scoped tokens to be silently discarded. Use memoryStorageMorphPlugin on web: completeOAuthCallback() runs in main() before runApp(), so the token is in the same JS heap the UI then reads from — no persistence needed for the current page load. Co-authored-by: Cursor <cursoragent@cursor.com>
…lank screen The blank screen was caused by completeOAuthCallback hanging or crashing silently before runApp() was called. Render the UI first, then process the OAuth callback in initState via addPostFrameCallback. HomeScreen exposes a public refreshStatus() that the parent app calls after the callback completes. Co-authored-by: Cursor <cursoragent@cursor.com>
- simulation_panel: fix &&/|| precedence in session-dead guard - simulation_panel: drop duplicate Simulation title; use Spacer in toolbar - mock_api_sheet: message color from result.isError; remove redundant ternary - poc_simulation: 10s timeout on http.get; document PoC string-matching tradeoff Co-authored-by: Cursor <cursoragent@cursor.com>
- Extract parsePocSimulationJson for JSON parsing without rootBundle - Add isPocSessionDeadStop (used by SimulationPanel) with regression tests - Refactor _runFetch to optional http.Client override + runPocSimFetchForTesting - Add 15 tests: models, parser, session guard, mocked fetch, asset load - Replace empty widget_test placeholder with minimal smoke test Co-authored-by: Cursor <cursoragent@cursor.com>
- dart-parity: Flutter PoC parity (#27/#28), web vs native storage, Keycloak webOrigins, run_web.sh profile default, unit tests, CI scope for flutter-poc - README: dart-parity description reflects current status - poc-guide: new Flutter PoC section (run paths, OAuth, CORS, simulation, tests) - poc/simulation: Flutter executor + isPocSessionDeadStop + test pointer Co-authored-by: Cursor <cursoragent@cursor.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Reviewer's GuideAdds Flutter web support and a JSON-driven simulation UI to the Flutter PoC, reworking OAuth callback handling, storage strategy, and the home screen UI while documenting the Dart parity and PoC behavior. Sequence diagram for web OAuth callback handling in the Flutter PoCsequenceDiagram
actor User
participant Browser
participant Flutter_main as Flutter_main
participant MorphPocAppState
participant MorphClient
participant HomeScreenState
participant Keycloak
User->>Browser: Tap Login (2fa)
Browser->>Keycloak: Open authorization_url(morph-auth/2fa)
Keycloak-->>Browser: Redirect http://localhost:4200/?code&state
Browser->>Flutter_main: Load app at Uri.base
Flutter_main->>Flutter_main: _captureWebOAuthParams()
note right of Flutter_main: Extract code,state
Flutter_main->>MorphClient: initMorph()
MorphClient-->>Flutter_main: MorphClient instance
Flutter_main->>MorphPocAppState: runApp(pendingOAuth)
MorphPocAppState->>MorphPocAppState: initState()
alt kIsWeb and pendingOAuth != null
MorphPocAppState->>MorphPocAppState: _processPendingWebOAuth()
MorphPocAppState->>MorphClient: completeOAuthCallback(code,state)
MorphClient-->>MorphPocAppState: OAuthResult(status,message)
MorphPocAppState->>MorphPocAppState: setState(pendingOAuthMessage)
MorphPocAppState->>HomeScreenState: refreshStatus()
HomeScreenState->>MorphClient: getTokenStatus()
MorphClient-->>HomeScreenState: List<MorphTokenStatus>
HomeScreenState->>HomeScreenState: setState(_tokenStatus)
end
Class diagram for simulation, mock API, and updated home screen structureclassDiagram
class PocSimStep {
<<sealed>>
+String label
}
class PocSimFetchStep {
+String label
+String path
+int expectStatus
+bool skipInAutoSim
+PocSimFetchStep(String label, String path, int expectStatus, bool skipInAutoSim)
}
class PocSimHostStep {
+String label
+String hostKey
+String method
+String path
+String auth
+Object body
+Map~String,String~ headers
+bool skipInAutoSim
+PocSimHostStep(String label, String hostKey, String method, String path, String auth, Object body, Map~String,String~ headers, bool skipInAutoSim)
}
class PocSimLogoutStep {
+String label
+String providerKey
+PocSimLogoutStep(String label, String providerKey)
}
PocSimStep <|-- PocSimFetchStep
PocSimStep <|-- PocSimHostStep
PocSimStep <|-- PocSimLogoutStep
class PocSimStepResult {
+String label
+Object status
+String detail
+Object body
+bool isError()
+PocSimStepResult(String label, Object status, String detail, Object body)
}
class PocSimulationConfig {
+String mockApiBaseUrl
+List~PocSimStep~ steps
+List~String~ sessionDeadAuthIds
+String sessionDeadMessage
+PocSimulationConfig(String mockApiBaseUrl, List~PocSimStep~ steps, List~String~ sessionDeadAuthIds, String sessionDeadMessage)
}
PocSimulationConfig --> PocSimStep : contains
class PocSimulationFunctions {
+PocSimulationConfig loadPocSimulation(String mockApiBase)
+PocSimulationConfig parsePocSimulationJson(String raw, String mockApiFallback)
+PocSimStepResult runPocSimStep(MorphClient morph, PocSimulationConfig cfg, PocSimStep step)
+PocSimStepResult runPocSimFetchForTesting(PocSimFetchStep step, String mockApiBase, http.Client httpClient)
+bool isPocSessionDeadStop(PocSimStepResult result, PocSimStep step, List~String~ sessionDeadAuthIds)
}
PocSimulationFunctions --> PocSimulationConfig
PocSimulationFunctions --> PocSimStepResult
PocSimulationFunctions --> PocSimStep
class SimulationPanel {
+MorphClient morph
+PocSimulationConfig cfg
+VoidCallback onStatusChanged
}
class _SimulationPanelState {
-bool _running
-bool _probe404Enabled
-List~PocSimStepResult~ _results
-String _sessionDeadMessage
+List~PocSimStep~ get _autoSteps()
+Future~void~ _runAll()
+Widget build(BuildContext context)
}
SimulationPanel --> _SimulationPanelState : creates
_SimulationPanelState --> PocSimulationConfig
_SimulationPanelState --> PocSimStepResult
_SimulationPanelState --> PocSimulationFunctions
class MockApiSheet {
+MorphClient morph
+PocSimulationConfig cfg
}
class _MockApiSheetState {
-bool _busy
-String _message
-bool _lastIsError
-Object _lastBody
+Future~void~ _run(PocSimStep step)
+Widget build(BuildContext context)
}
MockApiSheet --> _MockApiSheetState : creates
_MockApiSheetState --> PocSimulationConfig
_MockApiSheetState --> PocSimStepResult
class ProviderConfigSheet {
+MorphClient morph
+String providerKey
+Widget build(BuildContext context)
}
ProviderConfigSheet --> MorphClient : uses
class HomeScreen {
+MorphClient morph
+State createState()
}
class HomeScreenState {
+List~MorphTokenStatus~ _tokenStatus
+String _message
+bool _busy
+PocSimulationConfig _simCfg
+Future~void~ refreshStatus()
+Future~void~ _init()
+List~_ContextAction~ _actionsForRow(MorphTokenStatus row)
+Future~void~ _runAcquire(String authId)
+Future~void~ _startLogin()
+Future~void~ _runLogout(String authId)
+Future~void~ _runExchange(String sourceAuthId, String targetAuthId)
+void _openMockApi()
+void _openProviderConfig(String providerKey)
+Map~String,List~MorphTokenStatus~~ get _byProvider
+Widget build(BuildContext context)
}
HomeScreen --> HomeScreenState : creates
HomeScreenState --> MorphClient
HomeScreenState --> PocSimulationConfig
HomeScreenState --> SimulationPanel
HomeScreenState --> MockApiSheet
HomeScreenState --> ProviderConfigSheet
class _ProviderSection {
+String providerKey
+List~MorphTokenStatus~ rows
+bool busy
+MorphClient morph
+List~_ContextAction~ Function(MorphTokenStatus) actionsForRow
+Future~void~ Function(String,String) onExchange
+VoidCallback onConfigTap
+void Function(MorphTokenStatus) onJwtTap
}
class _ProviderSectionState {
-Map~String,String~ _exchangePick
+void _syncExchangePicks()
+Widget build(BuildContext context)
}
_ProviderSection --> _ProviderSectionState : creates
_ProviderSectionState --> _ProviderSection
class _ExchangeRow {
+String targetAuthId
+List~String~ sources
+String picked
+bool busy
+void Function(String) onPickChanged
+VoidCallback onExchange
+Widget build(BuildContext context)
}
class _ContextAction {
+String label
+VoidCallback onPressed
+bool danger
+String tooltip
}
class _SmallButton {
+String label
+VoidCallback onPressed
+bool danger
+String tooltip
+Widget build(BuildContext context)
}
HomeScreenState --> _ProviderSection
HomeScreenState --> _ContextAction
_ProviderSectionState --> _ExchangeRow
_ExchangeRow --> _SmallButton
Architecture flow diagram for platform-specific storage and mock API configurationflowchart TD
A_initMorph[initMorph]
A_initMorph --> B_detectPlatform[kIsWeb and defaultTargetPlatform]
B_detectPlatform --> C_webBranch{kIsWeb}
C_webBranch -->|true| D_webStorage[memoryStorageMorphPlugin]
D_webStorage --> D1_noteWebStorage[Tokens in memory only\nSession scoped to JS heap]
C_webBranch -->|false| E_nativeTryContextStore[ContextStore.create]
E_nativeTryContextStore --> F_nativeSuccess{success?}
F_nativeSuccess -->|yes| G_contextStorePlugin[contextStoreStoragePlugin]
G_contextStorePlugin --> G1_noteNativeStorage[Persistent storage via ContextStore]
F_nativeSuccess -->|no| H_fallbackMemory[memoryStorageMorphPlugin]
H_fallbackMemory --> H1_noteFallback[Fallback when ContextStore init fails]
%% MorphOptions wiring
D_webStorage --> I_buildMorphOptions[MorphOptions\nwith storagePlugin]
G_contextStorePlugin --> I_buildMorphOptions
H_fallbackMemory --> I_buildMorphOptions
I_buildMorphOptions --> J_createMorphClient[MorphClient.create]
%% Mock API base resolution
A_initMorph --> K_getMockApiBase[getMockApiBase]
K_getMockApiBase --> L_androidCheck{platform == android and !kIsWeb}
L_androidCheck -->|yes| M_androidHost[host = 10.0.2.2]
L_androidCheck -->|no| N_otherHost[host = localhost]
M_androidHost --> O_mockApiBase[mockApi.baseUrl = http://host:3000]
N_otherHost --> O_mockApiBase
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Code Review
This pull request significantly enhances the Flutter PoC to achieve feature parity with the TypeScript implementation. Key changes include the introduction of a JSON-driven simulation executor, improved OAuth handling for web platforms (including synchronous parameter capture and CORS updates), and a refactored UI that groups token statuses by provider with dynamic actions. Feedback focuses on improving encapsulation by using public APIs, addressing a fragile error-matching heuristic in the simulation logic, and correcting an ineffective error-handling pattern in the UI rendering logic.
I am having trouble creating individual review comments. Click here to see my feedback.
poc/flutter-poc/lib/poc_simulation.dart (257-265)
Use the public MorphClient.host() API instead of accessing the internal runtime and http properties directly. This maintains better encapsulation and adheres to the intended SDK usage patterns.
final res = await morph.host(step.hostKey).fetch<dynamic>(
step.path,
method: step.method,
body: step.body != null ? jsonEncode(step.body) : null,
auth: step.auth,
headers: step.headers,
);
poc/flutter-poc/lib/poc_simulation.dart (275-278)
This heuristic for identifying authentication errors is fragile as it relies on string matching against e.toString(). This can lead to false positives if these substrings appear elsewhere in the error message (e.g., in a URL or a different part of the stack trace). While acceptable for a PoC, consider implementing a more robust check using typed errors once they are available in the core SDK.
poc/flutter-poc/lib/screens/home_screen.dart (250-272)
The try-catch block here is ineffective for catching errors that occur during the rendering of _ProviderSection. In Flutter, errors during a widget's build phase are not caught by a try-catch surrounding its constructor. If the goal is to handle potential UI errors, consider using an ErrorWidget or handling errors within the _ProviderSection widget itself. Removing this block will also make the code cleaner.
return _ProviderSection(
providerKey: entry.key,
rows: entry.value,
busy: _busy,
morph: _morph,
actionsForRow: _actionsForRow,
onExchange: _runExchange,
onConfigTap: () => _openProviderConfig(entry.key),
onJwtTap: (s) => showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) => TokenClaimsSheet(status: s),
),
);There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The logic for deriving the mock API base host (Android emulator vs localhost) is duplicated between
getMockApiBase()and_buildVariables(); consider routing_buildVariables()throughgetMockApiBase()(or a shared helper) so the behavior stays aligned in one place.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The logic for deriving the mock API base host (Android emulator vs localhost) is duplicated between `getMockApiBase()` and `_buildVariables()`; consider routing `_buildVariables()` through `getMockApiBase()` (or a shared helper) so the behavior stays aligned in one place.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Closes #29
Summary
Follow-ups after the first merge slice of Flutter PoC parity (#28).
refreshStatus, Keycloak webOrigins + auth code lifetime, profilerun_web.sh, in-memory storage on web (ContextStore identity)web/scaffold&&/||precedence, duplicate header, timeouts, result coloring)poc/flutter-poc/test/poc_simulation_test.dart+ fetch client injection helpersdocs/dart-parity.md,docs/poc-guide.md,docs/poc/simulation.md,docs/README.mdVerification
dart analyze/dart test:morph_core(representative Dart package touched transitively via PoC deps)flutter testinpoc/flutter-poc: 16 passingMade with Cursor
Summary by Sourcery
Add a richer, provider-grouped Flutter PoC home screen with web OAuth handling, mock API tools, and JSON-driven simulation, while aligning docs and scripts around Flutter web usage.
New Features:
Enhancements:
Documentation:
Tests: