Skip to content

feat(poc): Flutter POC feature parity with TS Vue POC#28

Merged
cemal-yilmaz-bt merged 11 commits into
f/pluginfrom
feat/issue-27-flutter-poc-parity
May 4, 2026
Merged

feat(poc): Flutter POC feature parity with TS Vue POC#28
cemal-yilmaz-bt merged 11 commits into
f/pluginfrom
feat/issue-27-flutter-poc-parity

Conversation

@cemal-yilmaz-bt
Copy link
Copy Markdown

@cemal-yilmaz-bt cemal-yilmaz-bt commented May 4, 2026

Closes #27

Summary

Brings the Flutter POC at poc/flutter-poc to feature parity with poc/ts-vue.

What's new

  • lib/poc_simulation.dartPocSimStep sealed classes + runPocSimStep executor for fetch, host, and logout_provider steps, driven by assets/poc-simulation.json
  • lib/widgets/mock_api_sheet.dart — bottom sheet with one button per simulation step (all 9 real endpoints) + integrated HTTP trace log
  • lib/widgets/provider_config_sheet.dart — shows morph.getProviderMeta() as formatted JSON
  • lib/widgets/simulation_panel.dart — sequential auto-loop runner with per-step result rows, 404-probe toggle, and session-dead guard
  • home_screen.dart refactored — status grouped by provider, per-row dynamic actions from grantHint, Config button, token exchange dropdown, "Open Mock API & HTTP log" button

Also

  • Added getMockApiBase() to morph_init.dart (Android 10.0.2.2 aware)
  • Added http as direct dep (was transitive)
  • Updated morph-realm.json with localhost:4200 redirect URIs (Chrome/web)
  • flutter analyze --no-fatal-infos: no issues

Test note

Run ./poc/flutter-poc/run_web.sh with 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:

  • Introduce a JSON-driven PoC simulation model with typed steps and an executor for fetch, host, and logout operations.
  • Add a simulation control panel to run sequences of PoC steps with result summaries and session-expiry detection.
  • Add a mock API bottom sheet that exposes all simulation steps, shows responses, and integrates the HTTP trace log.
  • Add a provider config bottom sheet that displays morph provider metadata in formatted JSON.
  • Add a web run script to start Keycloak, the mock API, and the Flutter web app together.

Enhancements:

  • Refactor the home screen into provider-grouped status sections with per-row actions, exchange dropdowns, and links to config and simulation tooling.
  • Update token status cards to support custom labels and better visual integration in grouped layouts.
  • Centralize mock API base URL resolution with Android emulator awareness and introduce a dedicated web OAuth callback URI.
  • Adjust OAuth callback handling to support both deep-link schemes on mobile/desktop and HTTP redirects on web, including web-specific code/state detection.

Build:

  • Add the http package as a direct dependency and register the poc-simulation.json asset for the Flutter PoC.

Deployment:

  • Add localhost:4200-based redirect URIs to the Keycloak realm configuration for the Flutter web 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>
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 4, 2026

Reviewer's Guide

Adds 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 SimulationPanel

sequenceDiagram
  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
Loading

Sequence diagram for running a single Mock API step from MockApiSheet

sequenceDiagram
  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
Loading

Class diagram for PocSimStep simulation model and UI integration

classDiagram
  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
Loading

File-Level Changes

Change Details Files
Introduce JSON-driven PoC simulation model and executor for fetch/host/logout steps, used by multiple UI surfaces.
  • Define sealed PocSimStep hierarchy (fetch, host, logout) and PocSimStepResult to model simulation steps and outcomes.
  • Implement loadPocSimulation to read assets/poc-simulation.json and build PocSimulationConfig, including conditional blocks and session-dead checks.
  • Implement runPocSimStep and helpers to execute steps using http and MorphClient hostFetch/logout, mapping failures into structured status codes.
poc/flutter-poc/lib/poc_simulation.dart
poc/flutter-poc/assets/poc-simulation.json
Refactor HomeScreen into provider-grouped status with per-row actions, provider config and mock API/simulation panels, and web-aware OAuth handling.
  • Replace direct MorphRuntime usage with MorphClient, add initialisation that loads PocSimulationConfig via getMockApiBase, and group token status by providerKey.
  • Add _ProviderSection with exchange dropdowns, dynamic per-row actions from grantHint (acquire/login/logout), and integrate TokenStatusCard with new label override.
  • Replace global actions with context-aware buttons, add MockApiSheet and SimulationPanel hooks, and wire provider Config/JWT claim sheets per row.
  • Make login/start OAuth web-aware by using kIsWeb and kWebOAuthCallbackUri and adjusting token exchange/logout flows to use morph.auth().
poc/flutter-poc/lib/screens/home_screen.dart
poc/flutter-poc/lib/widgets/token_status_card.dart
Add SimulationPanel widget to run auto-looped simulations with optional 404 probe and session-dead detection UX.
  • Implement SimulationPanel with run-all sequence over PocSimulationConfig steps, honoring skipInAutoSim and 404 probe flag.
  • Track and render PocSimStepResult rows with status icons/text and optional detail, plus a session-dead message when AUTH failures match configured authIds.
  • Expose clear/reset controls and call back into parent when simulation completes to refresh token status.
poc/flutter-poc/lib/widgets/simulation_panel.dart
Add Mock API bottom sheet integrating simulation step buttons, HTTP trace log, and JSON response viewer.
  • Implement MockApiSheet as a DraggableScrollableSheet showing one button per simulation step (with logout highlighted as dangerous).
  • Wire step execution through runPocSimStep, showing status messages and pretty-printing JSON body in a dark code-style panel.
  • Embed existing HttpTraceLog with in-sheet clear support, preserving global morphHttpTraces semantics.
poc/flutter-poc/lib/widgets/mock_api_sheet.dart
poc/flutter-poc/lib/widgets/http_trace_log.dart
Add provider configuration bottom sheet to introspect Morph provider metadata via morph.getProviderMeta.
  • Implement ProviderConfigSheet that calls morph.getProviderMeta(providerKey), normalizes the object to JSON, and handles error cases.
  • Render formatted JSON in a draggable sheet with a dark code-like view and descriptive header showing the getProviderMeta call.
poc/flutter-poc/lib/widgets/provider_config_sheet.dart
Adjust Morph init and main entry to support web (Chrome) OAuth callbacks and Android emulator host routing, plus add http dependency and assets.
  • Add getMockApiBase and kWebOAuthCallbackUri constants, and use kIsWeb/defaultTargetPlatform instead of dart:io Platform, to choose localhost vs 10.0.2.2 and web vs custom-scheme callbacks.
  • Update _buildVariables to emit correct keycloakBase/mockApiBase and set oauthCallbackUri to kWebOAuthCallbackUri on web.
  • Make MorphPocApp web-aware: on web inspect Uri.base for OAuth callback, and on native use AppLinks uriLinkStream; validate callback prefix using kWebOAuthCallbackUri vs kOAuthCallbackUri.
  • Add http as a direct dependency and register poc-simulation.json as a Flutter asset.
poc/flutter-poc/lib/morph_init.dart
poc/flutter-poc/lib/main.dart
poc/flutter-poc/pubspec.yaml
poc/flutter-poc/pubspec.lock
Add run_web.sh helper and Keycloak realm updates to support Flutter web PoC flow on localhost:4200.
  • Introduce run_web.sh to orchestrate Keycloak (Docker), mock API (Node), and Flutter web app on Chrome with appropriate ports and dart-define secrets.
  • Update morph-realm.json redirect URIs to include http://localhost:4200/ for the morph-login client so web OAuth flow succeeds.
poc/flutter-poc/run_web.sh
poc/keycloak/morph-realm.json

Possibly linked issues

  • feat: Flutter POC feature parity with TS Vue POC #27: PR directly implements the Flutter POC parity requirements from the issue, including simulation, sheets, and refactors.
  • #N/A: PR implements the Flutter PoC app, OAuth flow, mock API integration, and parity features requested in the issue.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 71c773a9-aa64-4bbe-a1a2-d908ac9a7ef1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-27-flutter-poc-parity

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +61 to +63
if (isSessionDead &&
detail.contains('invalid_grant') ||
detail.contains('Token is not active')) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
if (isSessionDead &&
detail.contains('invalid_grant') ||
detail.contains('Token is not active')) {
if (isSessionDead &&
(detail.contains('invalid_grant') ||
detail.contains('Token is not active'))) {

Comment thread poc/flutter-poc/lib/poc_simulation.dart Outdated
PocSimFetchStep step, String mockApiBase) async {
final url = '$mockApiBase${step.path}';
try {
final res = await http.get(Uri.parse(url));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
final res = await http.get(Uri.parse(url));
final res = await http.get(Uri.parse(url)).timeout(const Duration(seconds: 10));

Comment on lines +232 to +236
final msg = e.toString();
final isAuth = msg.contains('401') ||
msg.contains('403') ||
msg.contains('Unauthorized') ||
msg.contains('invalid_grant');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Comment on lines +35 to +37
_message = result.isError
? '${result.status}${result.detail != null ? ' — ${result.detail}' : ''}'
: '${result.status}${result.detail != null ? ' — ${result.detail}' : ''}';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
_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}' : ''}';

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • 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.
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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +56 to +61
if (result.status == 'AUTH') {
final detail = result.detail ?? '';
final isSessionDead = widget.cfg.sessionDeadAuthIds.any(
(id) => step is PocSimHostStep && step.auth == id,
);
if (isSessionDead &&
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
}

Comment on lines +77 to +86
style: TextStyle(
fontSize: 11,
fontFamily: 'monospace',
color: Colors.grey.shade600),
),
],
),
),
const Divider(height: 1),
// Step buttons
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

u0b002 and others added 10 commits May 4, 2026 15:04
…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>
@cemal-yilmaz-bt cemal-yilmaz-bt merged commit 9e138f5 into f/plugin May 4, 2026
8 checks passed
cemal-yilmaz-bt pushed a commit that referenced this pull request May 4, 2026
- 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>
cemal-yilmaz-bt added a commit that referenced this pull request May 4, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant