feat(poc): Flutter POC feature parity with TS Vue POC#28
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>
Reviewer's GuideAdds a JSON-driven simulation system and UI to the Flutter PoC, refactors home_screen to group status by provider with per-row dynamic actions, and updates platform-specific OAuth/mock-API wiring to reach feature parity with the TS Vue PoC (including web support). Sequence diagram for running a simulation step from SimulationPanelsequenceDiagram
actor User
participant HomeScreen
participant SimulationPanel
participant PocSimulationConfig as PocSimulationConfig
participant PocSimStep as PocSimStep
participant PocSimStepResult as PocSimStepResult
participant MorphClient
participant MorphRuntime
participant HttpRuntimeClient as HttpRuntimeClient
participant HttpClient as HttpClient
User->>HomeScreen: Tap Run simulation
HomeScreen->>SimulationPanel: _runAll()
activate SimulationPanel
SimulationPanel->>PocSimulationConfig: read steps
PocSimulationConfig-->>SimulationPanel: List steps
loop for each auto step
SimulationPanel->>PocSimStep: select next step
SimulationPanel->>+runPocSimStep: runPocSimStep(morph,cfg,step)
alt step is PocSimFetchStep
runPocSimStep->>+HttpClient: get(mockApiBase + path)
HttpClient-->>-runPocSimStep: HTTP response
else step is PocSimHostStep
runPocSimStep->>+MorphClient: runtime
MorphClient-->>runPocSimStep: MorphRuntime
runPocSimStep->>+MorphRuntime: getHost(hostKey)
MorphRuntime-->>runPocSimStep: host
runPocSimStep->>+HttpRuntimeClient: hostFetch(host,path,method,body,auth,headers)
HttpRuntimeClient-->>-runPocSimStep: HTTP response
else step is PocSimLogoutStep
runPocSimStep->>+MorphClient: auth(providerKey).logout()
MorphClient-->>-runPocSimStep: logout complete
end
runPocSimStep-->>-SimulationPanel: PocSimStepResult
SimulationPanel->>PocSimStepResult: append to _results
end
SimulationPanel-->>HomeScreen: onStatusChanged()
deactivate SimulationPanel
Sequence diagram for running a single Mock API step from MockApiSheetsequenceDiagram
actor User
participant HomeScreen
participant MockApiSheet
participant PocSimulationConfig as PocSimulationConfig
participant PocSimStep as PocSimStep
participant PocSimStepResult as PocSimStepResult
participant MorphClient
participant MorphRuntime
participant HttpRuntimeClient as HttpRuntimeClient
participant HttpClient as HttpClient
User->>HomeScreen: Tap Open Mock API & HTTP log
HomeScreen->>MockApiSheet: showModalBottomSheet(cfg,morph)
activate MockApiSheet
User->>MockApiSheet: Tap simulation step button
MockApiSheet->>+runPocSimStep: runPocSimStep(morph,cfg,step)
alt fetch step
runPocSimStep->>+HttpClient: get(mockApiBase + path)
HttpClient-->>-runPocSimStep: HTTP response
else host step
runPocSimStep->>+MorphClient: runtime
MorphClient-->>runPocSimStep: MorphRuntime
runPocSimStep->>+MorphRuntime: getHost(hostKey)
MorphRuntime-->>runPocSimStep: host
runPocSimStep->>+HttpRuntimeClient: hostFetch(host,path,method,body,auth,headers)
HttpRuntimeClient-->>-runPocSimStep: HTTP response
else logout_provider step
runPocSimStep->>+MorphClient: auth(providerKey).logout()
MorphClient-->>-runPocSimStep: logout complete
end
runPocSimStep-->>-MockApiSheet: PocSimStepResult
MockApiSheet->>PocSimStepResult: update _message and _lastBody
deactivate MockApiSheet
Class diagram for PocSimStep simulation model and UI integrationclassDiagram
class PocSimStep {
<<sealed>>
+String label
}
class PocSimFetchStep {
+String path
+int expectStatus
+bool skipInAutoSim
}
class PocSimHostStep {
+String hostKey
+String method
+String path
+String auth
+Object body
+Map~String,String~ headers
+bool skipInAutoSim
}
class PocSimLogoutStep {
+String providerKey
}
PocSimStep <|-- PocSimFetchStep
PocSimStep <|-- PocSimHostStep
PocSimStep <|-- PocSimLogoutStep
class PocSimStepResult {
+String label
+Object status
+String detail
+Object body
+bool isError()
}
class PocSimulationConfig {
+String mockApiBaseUrl
+List~PocSimStep~ steps
+List~String~ sessionDeadAuthIds
+String sessionDeadMessage
}
class SimulationPanel {
+MorphClient morph
+PocSimulationConfig cfg
+VoidCallback onStatusChanged
-bool _running
-bool _probe404Enabled
-List~PocSimStepResult~ _results
-String _sessionDeadMessage
+_runAll()
}
class MockApiSheet {
+MorphClient morph
+PocSimulationConfig cfg
-bool _busy
-String _message
-Object _lastBody
+_run(step)
}
class ProviderConfigSheet {
+MorphClient morph
+String providerKey
}
class HomeScreen {
+MorphClient morph
}
class _ProviderSection {
+String providerKey
+List~MorphTokenStatus~ rows
+bool busy
+MorphClient morph
+Function actionsForRow
+Function onExchange
+VoidCallback onConfigTap
+Function onJwtTap
-Map~String,String~ _exchangePick
+_syncExchangePicks()
}
class _ExchangeRow {
+String targetAuthId
+List~String~ sources
+String picked
+bool busy
+Function onPickChanged
+VoidCallback onExchange
}
class _ContextAction {
+String label
+VoidCallback onPressed
+bool danger
+String tooltip
}
class _SmallButton {
+String label
+VoidCallback onPressed
+bool danger
+String tooltip
}
class TokenStatusCard {
+MorphTokenStatus status
+String label
+VoidCallback onTap
}
class MorphClient {
+auth(authId)
+getTokenStatus()
+getAuthorizationUrl(authId)
+getExchangeSources(authId)
+getProviderMeta(providerKey)
+MorphRuntime runtime
}
class MorphRuntime {
+getHost(hostKey)
+http
}
class HttpRuntimeClient {
+hostFetch(host, path, method, body, auth, headers)
}
PocSimulationConfig --> PocSimStep : contains
SimulationPanel --> PocSimulationConfig : uses
SimulationPanel --> PocSimStepResult : produces
SimulationPanel --> PocSimStep : iterates
SimulationPanel --> MorphClient : uses
MockApiSheet --> PocSimulationConfig : uses
MockApiSheet --> PocSimStep : triggers
MockApiSheet --> PocSimStepResult : displays
MockApiSheet --> MorphClient : uses
ProviderConfigSheet --> MorphClient : uses
HomeScreen --> SimulationPanel : embeds
HomeScreen --> MockApiSheet : opens
HomeScreen --> ProviderConfigSheet : opens
HomeScreen --> _ProviderSection : composes
_ProviderSection --> MorphClient : uses
_ProviderSection --> _ContextAction : builds
_ProviderSection --> _ExchangeRow : embeds
_ProviderSection --> TokenStatusCard : embeds
_ExchangeRow --> _SmallButton : uses
_SmallButton --> _ContextAction : visualizes
MorphClient --> MorphRuntime : delegates
MorphRuntime --> HttpRuntimeClient : exposes
class HttpClient {
+get(url)
}
PocSimFetchStep ..> HttpClient : uses
PocSimHostStep ..> MorphRuntime : uses
PocSimHostStep ..> HttpRuntimeClient : uses
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
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 |
There was a problem hiding this comment.
Code Review
This pull request introduces a simulation framework and web support for the Flutter PoC. Key changes include the addition of a simulation configuration system, a simulation engine in poc_simulation.dart, and new UI components such as SimulationPanel, MockApiSheet, and ProviderConfigSheet. The HomeScreen has been refactored to group token statuses by provider and offer dynamic authentication actions. Web support is enabled through Uri.base detection for OAuth callbacks and updated Keycloak configurations. Review feedback highlights a logic error in session-dead checks due to operator precedence, the need for timeouts in network requests, fragile error handling based on string matching, and redundant ternary logic in message formatting.
| if (isSessionDead && | ||
| detail.contains('invalid_grant') || | ||
| detail.contains('Token is not active')) { |
There was a problem hiding this comment.
There is a logic error in the session dead check due to operator precedence. In Dart, the logical AND (&&) operator has higher precedence than the logical OR (||) operator. As written, the condition evaluates as (isSessionDead && detail.contains('invalid_grant')) || detail.contains('Token is not active'). This means any error containing "Token is not active" will trigger the session dead message, regardless of whether it's a session-related step.
| if (isSessionDead && | |
| detail.contains('invalid_grant') || | |
| detail.contains('Token is not active')) { | |
| if (isSessionDead && | |
| (detail.contains('invalid_grant') || | |
| detail.contains('Token is not active'))) { |
| PocSimFetchStep step, String mockApiBase) async { | ||
| final url = '$mockApiBase${step.path}'; | ||
| try { | ||
| final res = await http.get(Uri.parse(url)); |
There was a problem hiding this comment.
The http.get call lacks a timeout. In mobile applications, network requests should always have a timeout to prevent the application from hanging indefinitely if the server is unresponsive or the network connection is poor.
| final res = await http.get(Uri.parse(url)); | |
| final res = await http.get(Uri.parse(url)).timeout(const Duration(seconds: 10)); |
| final msg = e.toString(); | ||
| final isAuth = msg.contains('401') || | ||
| msg.contains('403') || | ||
| msg.contains('Unauthorized') || | ||
| msg.contains('invalid_grant'); |
There was a problem hiding this comment.
Determining if an error is an authentication error by performing string matching on e.toString() is fragile and prone to breaking if the underlying library changes its error message format. It is better to check for specific exception types or status codes if available from the morph client's error handling.
| _message = result.isError | ||
| ? '${result.status}${result.detail != null ? ' — ${result.detail}' : ''}' | ||
| : '${result.status}${result.detail != null ? ' — ${result.detail}' : ''}'; |
There was a problem hiding this comment.
The ternary operator used to set _message has identical expressions for both the true and false branches. This makes the condition redundant and suggests a potential copy-paste error or missing logic for distinguishing error vs. success formatting.
| _message = result.isError | |
| ? '${result.status}${result.detail != null ? ' — ${result.detail}' : ''}' | |
| : '${result.status}${result.detail != null ? ' — ${result.detail}' : ''}'; | |
| _message = '${result.status}${result.detail != null ? ' — ${result.detail}' : ''}'; |
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- In
SimulationPanel._runAll, the session-dead guard conditionif (isSessionDead && detail.contains('invalid_grant') || detail.contains('Token is not active'))relies on&&/||precedence and will treatToken is not activeas session-dead even whenisSessionDeadis false; consider adding parentheses to make the intended logic explicit (e.g.if (isSessionDead && (detail.contains('invalid_grant') || detail.contains('Token is not active')))). MockApiSheetimports../../morph_init.dartbut does not use anything from it; consider removing this import to avoid confusion about cross-layer dependencies.- The
SimulationPanelrenders its own 'Simulation' title whileHomeScreenalso renders a 'Simulation'_SectionHeaderimmediately above it, leading to duplicate section labels; consider either dropping one of the labels or lettingSimulationPanelbe a pure panel without its own title.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `SimulationPanel._runAll`, the session-dead guard condition `if (isSessionDead && detail.contains('invalid_grant') || detail.contains('Token is not active'))` relies on `&&`/`||` precedence and will treat `Token is not active` as session-dead even when `isSessionDead` is false; consider adding parentheses to make the intended logic explicit (e.g. `if (isSessionDead && (detail.contains('invalid_grant') || detail.contains('Token is not active')))` ).
- `MockApiSheet` imports `../../morph_init.dart` but does not use anything from it; consider removing this import to avoid confusion about cross-layer dependencies.
- The `SimulationPanel` renders its own 'Simulation' title while `HomeScreen` also renders a 'Simulation' `_SectionHeader` immediately above it, leading to duplicate section labels; consider either dropping one of the labels or letting `SimulationPanel` be a pure panel without its own title.
## Individual Comments
### Comment 1
<location path="poc/flutter-poc/lib/widgets/simulation_panel.dart" line_range="56-61" />
<code_context>
+ setState(() => _results.add(result));
+
+ // Session dead check
+ if (result.status == 'AUTH') {
+ final detail = result.detail ?? '';
+ final isSessionDead = widget.cfg.sessionDeadAuthIds.any(
+ (id) => step is PocSimHostStep && step.auth == id,
+ );
+ if (isSessionDead &&
+ detail.contains('invalid_grant') ||
+ detail.contains('Token is not active')) {
</code_context>
<issue_to_address>
**issue (bug_risk):** The session-dead check condition mixes && and || without parentheses, likely changing the intended logic.
As written, the `if` is evaluated as:
```dart
if ((isSessionDead && detail.contains('invalid_grant')) ||
detail.contains('Token is not active')) {
```
So any "Token is not active" detail will set `_sessionDeadMessage` even when `isSessionDead` is false. If you only want to treat these errors as session-dead when the `auth` is in `sessionDeadAuthIds`, wrap the detail checks:
```dart
if (isSessionDead &&
(detail.contains('invalid_grant') ||
detail.contains('Token is not active'))) {
setState(() => _sessionDeadMessage = widget.cfg.sessionDeadMessage);
break;
}
```
</issue_to_address>
### Comment 2
<location path="poc/flutter-poc/lib/widgets/mock_api_sheet.dart" line_range="77-86" />
<code_context>
+ children: [
+ Text(
+ widget.providerKey.toUpperCase(),
+ style: TextStyle(
+ fontSize: 10,
+ fontWeight: FontWeight.w700,
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Message color is derived from the first character of the string, which won’t align with non-2xx success statuses like 'OK'.
`_message` is checked with `startsWith('2')` to decide green vs red, but `PocSimStepResult.status` can be values like `'OK'`, `'AUTH'`, `'ERR'`. A successful `'OK'` (e.g. logout) will incorrectly render as red.
Please derive the color from the semantic success/error instead of the first character, e.g. by using a boolean like `result.isError` or mapping `result.status` (treating `'OK'` and 2xx as success) before building `_message`, so the color stays aligned with `PocSimStepResult.isError`.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| if (result.status == 'AUTH') { | ||
| final detail = result.detail ?? ''; | ||
| final isSessionDead = widget.cfg.sessionDeadAuthIds.any( | ||
| (id) => step is PocSimHostStep && step.auth == id, | ||
| ); | ||
| if (isSessionDead && |
There was a problem hiding this comment.
issue (bug_risk): The session-dead check condition mixes && and || without parentheses, likely changing the intended logic.
As written, the if is evaluated as:
if ((isSessionDead && detail.contains('invalid_grant')) ||
detail.contains('Token is not active')) {So any "Token is not active" detail will set _sessionDeadMessage even when isSessionDead is false. If you only want to treat these errors as session-dead when the auth is in sessionDeadAuthIds, wrap the detail checks:
if (isSessionDead &&
(detail.contains('invalid_grant') ||
detail.contains('Token is not active'))) {
setState(() => _sessionDeadMessage = widget.cfg.sessionDeadMessage);
break;
}| style: TextStyle( | ||
| fontSize: 11, | ||
| fontFamily: 'monospace', | ||
| color: Colors.grey.shade600), | ||
| ), | ||
| ], | ||
| ), | ||
| ), | ||
| const Divider(height: 1), | ||
| // Step buttons |
There was a problem hiding this comment.
suggestion (bug_risk): Message color is derived from the first character of the string, which won’t align with non-2xx success statuses like 'OK'.
_message is checked with startsWith('2') to decide green vs red, but PocSimStepResult.status can be values like 'OK', 'AUTH', 'ERR'. A successful 'OK' (e.g. logout) will incorrectly render as red.
Please derive the color from the semantic success/error instead of the first character, e.g. by using a boolean like result.isError or mapping result.status (treating 'OK' and 2xx as success) before building _message, so the color stays aligned with PocSimStepResult.isError.
…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>
- 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>
* feat(poc): Flutter POC feature parity with TS Vue POC 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> * fix(poc): complete web OAuth callback before runApp to fix token visibility Co-authored-by: Cursor <cursoragent@cursor.com> * fix(poc): navigate current tab for web OAuth login instead of opening new tab Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web-poc): switch to profile mode and extend OAuth code lifetime 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> * chore(web-poc): add missing web/ platform scaffold Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web-poc): add CORS webOrigins for device+session clients, add SDK log panel Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web-poc): route SDK logs to browser console, remove UI log panel Co-authored-by: Cursor <cursoragent@cursor.com> * debug(web-poc): add exhaustive tracing to catch profile-mode silent crashes Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web-poc): use in-memory storage on web to avoid ContextStore identity 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> * fix(web-poc): render UI before processing OAuth callback to unblock blank 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> * chore(poc): address PR #28 review (precedence, timeouts, UI dupes) - 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> * test(poc): add unit tests for poc_simulation + injectable fetch client - 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> * docs: sync Dart/Flutter PoC parity, web/runtime notes, and test coverage - 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> --------- Co-authored-by: u0b002 <cyilmaz@burgan.com.tr> Co-authored-by: Cursor <cursoragent@cursor.com>
Closes #27
Summary
Brings the Flutter POC at
poc/flutter-pocto feature parity withpoc/ts-vue.What's new
lib/poc_simulation.dart—PocSimStepsealed classes +runPocSimStepexecutor forfetch,host, andlogout_providersteps, driven byassets/poc-simulation.jsonlib/widgets/mock_api_sheet.dart— bottom sheet with one button per simulation step (all 9 real endpoints) + integrated HTTP trace loglib/widgets/provider_config_sheet.dart— showsmorph.getProviderMeta()as formatted JSONlib/widgets/simulation_panel.dart— sequential auto-loop runner with per-step result rows, 404-probe toggle, and session-dead guardhome_screen.dartrefactored — status grouped by provider, per-row dynamic actions fromgrantHint, Config button, token exchange dropdown, "Open Mock API & HTTP log" buttonAlso
getMockApiBase()tomorph_init.dart(Android 10.0.2.2 aware)httpas direct dep (was transitive)morph-realm.jsonwithlocalhost:4200redirect URIs (Chrome/web)flutter analyze --no-fatal-infos: no issuesTest note
Run
./poc/flutter-poc/run_web.shwith Keycloak + Mock API to exercise all steps.Made with Cursor
Summary by Sourcery
Bring the Flutter PoC to parity with the TS/Vue PoC by adding a configurable mock API simulation flow, richer provider status UX, and web-friendly OAuth behaviour.
New Features:
Enhancements:
Build:
Deployment: