Skip to content

docs: add PWA migration plan for web compilation support#499

Open
AndreaDiazCorreia wants to merge 1 commit intomainfrom
feat/web-pwa-impl-plan
Open

docs: add PWA migration plan for web compilation support#499
AndreaDiazCorreia wants to merge 1 commit intomainfrom
feat/web-pwa-impl-plan

Conversation

@AndreaDiazCorreia
Copy link
Member

@AndreaDiazCorreia AndreaDiazCorreia commented Feb 24, 2026

Summary

  • Adds comprehensive technical plan (docs/PWA_MIGRATION_PLAN.md) for compiling the Flutter app
    as a PWA without modifying existing mobile code
  • Documents 14 blocking files, 6 implementation phases, and an additive-only strategy using
    conditional imports and stubs

Motivation

While I believe a dedicated web client would be the right long-term approach, a PWA as a first
step makes practical sense:

  • Universal access: Enables all devices — including iPhones without sideloading — to use
    Mostro
  • Integration surface: Projects that depend on Mostro can integrate via the web build
  • Low risk: The additive-only strategy means zero changes to existing mobile code

Test plan

  • Document reviewed for completeness and accuracy (Pending approve)
  • Phases will be implemented and validated incrementally in follow-up PRs

Summary by CodeRabbit

  • Documentation
    • Added comprehensive migration plan outlining a phased approach to expand platform support, detailing objectives and implementation strategy across multiple phases.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

Walkthrough

A new comprehensive PWA migration plan document is added, outlining a six-phase strategy for transitioning a Flutter mobile app to web, including web stubs, conditional imports, database abstraction, WebSocket handling, service degradation, and full PWA setup with implementation details and risk assessment.

Changes

Cohort / File(s) Summary
PWA Migration Plan Documentation
docs/PWA_MIGRATION_PLAN.md
Comprehensive 1100+ line migration plan detailing six-phase rollout for Flutter-to-PWA transition, including web-compatible stubs, conditional imports, database factory patterns, WebSocket handling, service degradation strategies, file operations abstraction, and full PWA deployment guidance with risk matrix and timeline estimates.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A bunny's migration ode...

Six phases hopped with careful care,
From mobile soil to web's vast air,
With stubs and shims and factories new,
A PWA dream comes shining through!
🚀✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: adding a comprehensive PWA migration plan document to the repository.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/web-pwa-impl-plan

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
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/PWA_MIGRATION_PLAN.md`:
- Around line 82-113: The WebSocket stub's listen signature is incorrect—change
WebSocket.listen in io_stub.dart to return a StreamSubscription<dynamic>
(matching dart:io.WebSocket which extends Stream) and return a suitable no-op
StreamSubscription implementation or use Stream<dynamic>.empty().listen(...) to
produce the subscription so code like final sub = ws.listen(...) compiles; and
remove getTemporaryDirectory from io_stub.dart and place it into a new
path_provider_stub.dart (exporting Future<Directory> getTemporaryDirectory()) so
you don't shadow the path_provider API and keep IO and path_provider stubs
separate.
- Around line 527-533: Replace the placeholder "10.x.x" in the Firebase CDN URLs
shown in the web/index.html snippet and the firebase-messaging-sw.js snippet
with the exact pinned Firebase JS SDK version that matches the project's
firebase_core dependency in pubspec.yaml; locate occurrences of
"https://www.gstatic.com/firebasejs/10.x.x/" (search for the literal 10.x.x) and
update both imports to the concrete version string used by firebase_core so the
URLs resolve correctly and remain consistent across files.
- Around line 214-219: The web stub in class BiometricsHelper currently treats
authenticateWithBiometrics() as always succeeding; change this to NOT silently
grant access: modify authenticateWithBiometrics() to return false or throw when
no real biometric/WebAuthn is available, and wire it to a placeholder fallback
flow (e.g., trigger a PIN/passphrase prompt component or call a WebAuthn shim)
so the UI requires explicit user credential entry on web; update any code paths
that assume success to handle failure gracefully (e.g., show the PIN/passphrase
modal) and mark the risk level for web bypass accordingly.
- Around line 1072-1078: The fenced ASCII diagram block that starts with "Phase
1 ──→ Phase 2 ──→ Phase 3 ──→ Phase 4 ──→ Phase 5 ──→ Phase 6" is missing a
language specifier and triggers markdownlint MD040; fix it by updating the
opening fence from ``` to a fenced code block with a language identifier (e.g.,
```text) so the diagram has an explicit language tag in the file and satisfies
the lint rule.
- Around line 679-695: The FileData class uses Uint8List but the file is missing
the import for dart:typed_data; open file_data.dart and add the top-level import
"import 'dart:typed_data';" so the Uint8List type used in the FileData
constructor and fields (filename, bytes, mimeType, size) resolves and the file
compiles.
- Line 36: The heading "Bloqueadores identificados (12 archivos, ~30 issues)" is
inconsistent with the table that lists 14 entries; update the heading to
"Bloqueadores identificados (14 archivos, ~30 issues)" and search for the Phase
1 summary text "7 archivos nuevos, 11 archivos modificados" and reconcile those
numbers with the 14 blockers (adjust to the correct counts) so all references in
the document match the table; ensure you update the exact heading string and the
Phase 1 summary sentence to reflect the corrected counts.
- Around line 711-758: The conditional export fails because each platform file
exposes a different function name; unify the API by renaming the exported
function in lib/features/chat/widgets/file_operations_web.dart
(openFileInBrowser), file_operations_mobile.dart (openFileOnDevice) and
file_operations_stub.dart (openFileOnPlatform) to the same identifier (e.g.
Future<void> openFile(Uint8List bytes, String filename, String? mimeType) async)
and keep the same signature (Future<void>, Uint8List bytes, String filename,
String? mimeType) so the entry-point
lib/features/chat/widgets/file_operations.dart can correctly resolve the symbol
across platforms. Ensure each implementation preserves its platform-specific
behavior but uses the common function name and async signature.
- Around line 447-466: The WebSocket handshake is asynchronous: when using
WebSocketChannel.connect(...) wait for the connection to complete by adding
await channel.ready before calling channel.sink.add(...) (so change the sequence
around WebSocketChannel.connect(uri), await channel.ready, then send the
testReq) and keep the existing stream timeout/close behavior; also update the
markdown fenced code blocks in this section to include the "dart" language
specifier so all examples are language-tagged.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 149010c and 2e7c8f4.

📒 Files selected for processing (1)
  • docs/PWA_MIGRATION_PLAN.md

- `shared_preferences` (tiene soporte web)
- Chrome detectado como device disponible

### Bloqueadores identificados (12 archivos, ~30 issues)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Blocker count mismatch: heading says "12 archivos" but the table lists 14.

The section title reads "Bloqueadores identificados (12 archivos, ~30 issues)" yet the table immediately below has 14 entries (#1#14). The Phase 1 new-files summary (line 963) also repeats "7 archivos nuevos, 11 archivos modificados" which doesn't reconcile with the 14 blockers listed. Update the count to 14.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/PWA_MIGRATION_PLAN.md` at line 36, The heading "Bloqueadores
identificados (12 archivos, ~30 issues)" is inconsistent with the table that
lists 14 entries; update the heading to "Bloqueadores identificados (14
archivos, ~30 issues)" and search for the Phase 1 summary text "7 archivos
nuevos, 11 archivos modificados" and reconcile those numbers with the 14
blockers (adjust to the correct counts) so all references in the document match
the table; ensure you update the exact heading string and the Phase 1 summary
sentence to reflect the corrected counts.

Comment on lines +82 to +113
class WebSocket {
static Future<WebSocket> connect(
String url, {
Map<String, dynamic>? headers,
}) async {
throw UnsupportedError('WebSocket.connect not available on web');
}

void add(dynamic data) {}
Stream<dynamic> listen(void Function(dynamic)? onData) => const Stream.empty();
Future<void> close([int? code, String? reason]) async {}
}

class File {
final String path;
File(this.path);
Future<File> writeAsBytes(List<int> bytes) async => this;
Future<File> writeAsString(String contents) async => this;
Future<List<int>> readAsBytes() async => [];
Future<String> readAsString() async => '';
bool existsSync() => false;
int lengthSync() => 0;
}

Future<Directory> getTemporaryDirectory() async {
throw UnsupportedError('getTemporaryDirectory not available on web');
}

class Directory {
final String path;
Directory(this.path);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

io_stub.dart has two technical errors that will cause compilation failures.

1. Wrong listen return type (Line 91): dart:io.WebSocket extends Stream<dynamic>, so listen() must return StreamSubscription<dynamic>, not Stream<dynamic>. The stub currently declares:

Stream<dynamic> listen(void Function(dynamic)? onData) => const Stream.empty();

Any call site that stores the subscription (e.g. final sub = ws.listen(…)) will fail to compile. The corrected signature should be:

🐛 Proposed fix
-  Stream<dynamic> listen(void Function(dynamic)? onData) => const Stream.empty();
+  StreamSubscription<dynamic> listen(
+    void Function(dynamic)? onData, {
+    Function? onError,
+    void Function()? onDone,
+    bool? cancelOnError,
+  }) =>
+      Stream<dynamic>.empty().listen(onData);

2. getTemporaryDirectory is not from dart:io — it comes from the path_provider package. Placing it in io_stub.dart conflates two separate libraries and will silently shadow any path_provider conditional import added later. Move it to a dedicated path_provider_stub.dart.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/PWA_MIGRATION_PLAN.md` around lines 82 - 113, The WebSocket stub's
listen signature is incorrect—change WebSocket.listen in io_stub.dart to return
a StreamSubscription<dynamic> (matching dart:io.WebSocket which extends Stream)
and return a suitable no-op StreamSubscription implementation or use
Stream<dynamic>.empty().listen(...) to produce the subscription so code like
final sub = ws.listen(...) compiles; and remove getTemporaryDirectory from
io_stub.dart and place it into a new path_provider_stub.dart (exporting
Future<Directory> getTemporaryDirectory()) so you don't shadow the path_provider
API and keep IO and path_provider stubs separate.

Comment on lines +214 to +219
```dart
/// Web stub: biometrics no disponibles en browser.
class BiometricsHelper {
Future<bool> isBiometricsAvailable() async => false;
Future<bool> authenticateWithBiometrics() async => true; // Skip auth en web
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Silently bypassing biometric auth on web is a high-severity security concern for a BTC trading app.

authenticateWithBiometrics() async => true means any web user is automatically considered authenticated without any credential check. The Risk section (line 1056) treats this as "Riesgo Bajo" and mentions PIN as an alternative, but the Phase 1 implementation here simply skips it with no warning.

For a non-custodial P2P exchange where key access and trade execution follow authentication, this should be at minimum a Riesgo Alto item, and a placeholder UI prompt (PIN, passphrase, or browser WebAuthn) should be slotted into Phase 1 or Phase 2 rather than deferred indefinitely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/PWA_MIGRATION_PLAN.md` around lines 214 - 219, The web stub in class
BiometricsHelper currently treats authenticateWithBiometrics() as always
succeeding; change this to NOT silently grant access: modify
authenticateWithBiometrics() to return false or throw when no real
biometric/WebAuthn is available, and wire it to a placeholder fallback flow
(e.g., trigger a PIN/passphrase prompt component or call a WebAuthn shim) so the
UI requires explicit user credential entry on web; update any code paths that
assume success to handle failure gracefully (e.g., show the PIN/passphrase
modal) and mark the risk level for web bypass accordingly.

Comment on lines +447 to +466
final uri = Uri.parse(url);
final channel = WebSocketChannel.connect(uri);

const testReq = '["REQ", "test_conn", {"kinds":[1], "limit":1}]';
channel.sink.add(testReq);

bool receivedResponse = false;

await for (final message in channel.stream.timeout(
const Duration(seconds: 8),
onTimeout: (sink) => sink.close(),
)) {
if (message.toString().startsWith('["')) {
receivedResponse = true;
break;
}
}

await channel.sink.close();
return receivedResponse;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

web_socket_channel 3.x pub.dev channel.ready await requirement

💡 Result:

In web_socket_channel 3.x, WebSocketChannel.ready is a Future<void> that completes when the WebSocket connection has been established, and it must complete before you send data via channel.sink (i.e., you should await channel.ready before sink.add). If the connection can’t be established, ready completes with an error. [1]

You can attach channel.stream.listen(...) immediately, but don’t write to the sink until ready is complete. [1]

Example:

final channel = WebSocketChannel.connect(Uri.parse('ws://example.com'));
await channel.ready;        // required before sending
channel.sink.add('hello');

[1]

Sources:
[1] pub.dev API docs: WebSocketChannel.ready (web_socket_channel 3.0.3) (pub.dev)

Citations:


🌐 Web query:

web_socket_channel latest version pub.dev 2026

💡 Result:

As of February 24, 2026, the latest stable version of the Dart/Flutter package web_socket_channel on pub.dev is 3.0.3. [1][2]

Use:

dependencies:
  web_socket_channel: ^3.0.3

🌐 Web query:

WebSocketChannel.connect handshake ready Dart web_socket_channel

💡 Result:

In package:web_socket_channel, the “handshake ready” signal is the ready Future on the channel.

  • WebSocketChannel.connect(uri) (and IOWebSocketChannel.connect(...)) returns immediately, while the WebSocket opening handshake happens asynchronously.
  • await channel.ready; completes after the channel is connected / handshake is done; if the handshake fails, it completes with an error. [1][2]
import 'package:web_socket_channel/web_socket_channel.dart';

final uri = Uri.parse('wss://example.com/ws');
final channel = WebSocketChannel.connect(uri);

try {
  await channel.ready;          // handshake completed
  channel.sink.add('hello');    // safe to send
} catch (e) {
  // handshake/connection failed
  rethrow;
}

If you’re on an older web_socket_channel version that doesn’t have ready, you must instead wait for the underlying connect Future (for dart:io, WebSocket.connect(...)) before sending. [3]

Sources: [1] [2] [3]


🌐 Web query:

web_socket_channel EventSink.close() double close idempotent error

💡 Result:

In Dart, EventSink.close() is defined to be idempotent: calling it more than once is allowed and “does nothing.” [1]

For web_socket_channel, channel.sink is a WebSocketSink (a StreamSink) whose close() returns a Future (the same future as done). If the sink is already closed, close() just returns that already-completed/being-completed done future. [2][3]

So if you’re seeing an “already closed / double close” error, it’s usually not because close() itself can’t be called twice, but because something else is happening around the same time, e.g.:

  • code still calling sink.add(...) / addError(...) after close (explicitly disallowed after EventSink.close()) [1]
  • multiple owners racing to manage the same socket lifecycle (each “owns” closing)
  • the underlying transport reports an error during shutdown, which surfaces via sink.done/close()’s returned future [2]

Practical pattern:

  • call channel.sink.close(...) once
  • elsewhere, don’t “close again”; just await channel.sink.done (or ignore it) to observe completion/errors. [2]

Sources: [1] Dart EventSink.close() docs, [2] Dart StreamSink.close() docs, [3] web_socket_channel WebSocketSink docs.


🌐 Web query:

Dart stream onTimeout callback sink.close() behavior

💡 Result:

In Stream<T>.timeout(...), the onTimeout callback receives an EventSink<T> that can only be used during that callback. If you call sink.close() inside onTimeout, it closes the returned (timed) stream, and no further events from the source stream are processed/forwarded through that returned stream. [1]

What this does not necessarily mean is “stop the original producer”:

  • The timed stream internally creates a subscription to the source stream; when the returned stream is closed (or otherwise canceled), that subscription is canceled. [1]
  • If the source is a broadcast stream, canceling one subscription does not stop the broadcast source itself—it only stops this listener from receiving further events. This is why you may observe that your original broadcast stream “keeps running” even after sink.close() on the timeout wrapper. [2]

So: sink.close() ends the timeout-wrapped stream (and cancels its subscription), but it won’t “shut down” a broadcast stream’s underlying event production unless that producer is explicitly tied to having zero listeners. [1] [2]

Sources:
[1] Dart SDK API docs: Stream.timeout behavior and implementation notes https://api.dart.dev/dart-async/Stream/timeout.html
[2] Discussion/example showing broadcast stream not being stopped by timeout() wrapper cancellation https://stackoverflow.com/questions/69453514/how-to-cancel-subscription-of-the-original-stream-after-timeout-in-dart


Add await channel.ready before first sink.add() and ensure all code blocks have language specifiers.

In web_socket_channel 3.x, the connection handshake completes asynchronously. Attempting to send on the sink before channel.ready completes may fail. Add the required await:

final channel = WebSocketChannel.connect(uri);
await channel.ready;  // required: handshake must complete first

const testReq = '["REQ", "test_conn", {"kinds":[1], "limit":1}]';
channel.sink.add(testReq);  // now safe to send

Additionally, per the coding guidelines for markdown files, all fenced code blocks must include a language specifier. The code blocks in this section are missing the dart language identifier.

Note: The onTimeout callback in the current code is optional and idempotent—EventSink.close() can be called multiple times safely. However, removing it will change behavior from graceful closure on timeout to throwing a TimeoutException.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/PWA_MIGRATION_PLAN.md` around lines 447 - 466, The WebSocket handshake
is asynchronous: when using WebSocketChannel.connect(...) wait for the
connection to complete by adding await channel.ready before calling
channel.sink.add(...) (so change the sequence around
WebSocketChannel.connect(uri), await channel.ready, then send the testReq) and
keep the existing stream timeout/close behavior; also update the markdown fenced
code blocks in this section to include the "dart" language specifier so all
examples are language-tagged.

Comment on lines +527 to +533
**Crear** (si no existe): Configuración Firebase para web en `web/index.html`:

```html
<!-- Antes del script de flutter_bootstrap.js -->
<script src="https://www.gstatic.com/firebasejs/10.x.x/firebase-app-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/10.x.x/firebase-messaging-compat.js"></script>
```
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Firebase SDK URL placeholder 10.x.x must be replaced with a real pinned version.

Both the web/index.html snippet (lines 531–532) and the firebase-messaging-sw.js snippet (lines 878–879) reference https://www.gstatic.com/firebasejs/10.x.x/.... Deploying with a literal x.x URL will 404. Pin to the actual version used by the firebase_core Flutter package in pubspec.yaml.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/PWA_MIGRATION_PLAN.md` around lines 527 - 533, Replace the placeholder
"10.x.x" in the Firebase CDN URLs shown in the web/index.html snippet and the
firebase-messaging-sw.js snippet with the exact pinned Firebase JS SDK version
that matches the project's firebase_core dependency in pubspec.yaml; locate
occurrences of "https://www.gstatic.com/firebasejs/10.x.x/" (search for the
literal 10.x.x) and update both imports to the concrete version string used by
firebase_core so the URLs resolve correctly and remain consistent across files.

Comment on lines +679 to +695
```dart
/// Representación cross-platform de un archivo.
/// Evita dependencia en dart:io.File.
class FileData {
final String filename;
final Uint8List bytes;
final String? mimeType;
final int size;

FileData({
required this.filename,
required this.bytes,
this.mimeType,
int? size,
}) : size = size ?? bytes.length;
}
```
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

file_data.dart is missing import 'dart:typed_data'.

Uint8List is used for bytes and size fields but dart:typed_data is not imported, so this snippet won't compile as-is.

🐛 Proposed fix
+import 'dart:typed_data';
+
 /// Representación cross-platform de un archivo.
 class FileData {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/PWA_MIGRATION_PLAN.md` around lines 679 - 695, The FileData class uses
Uint8List but the file is missing the import for dart:typed_data; open
file_data.dart and add the top-level import "import 'dart:typed_data';" so the
Uint8List type used in the FileData constructor and fields (filename, bytes,
mimeType, size) resolves and the file compiles.

Comment on lines +711 to +758
```dart
// ignore: avoid_web_libraries_in_flutter
import 'dart:html' as html;
import 'dart:typed_data';

Future<void> openFileInBrowser(Uint8List bytes, String filename, String? mimeType) async {
final blob = html.Blob([bytes], mimeType ?? 'application/octet-stream');
final url = html.Url.createObjectUrlFromBlob(blob);
final anchor = html.AnchorElement(href: url)
..setAttribute('download', filename)
..click();
html.Url.revokeObjectUrl(url);
}
```

**Crear**: `lib/features/chat/widgets/file_operations_mobile.dart`

```dart
import 'dart:io';
import 'dart:typed_data';
import 'package:path_provider/path_provider.dart';
import 'package:open_file/open_file.dart';

Future<void> openFileOnDevice(Uint8List bytes, String filename, String? mimeType) async {
final tempDir = await getTemporaryDirectory();
final tempFile = File('${tempDir.path}/$filename');
await tempFile.writeAsBytes(bytes);
await OpenFile.open(tempFile.path);
}
```

**Crear**: `lib/features/chat/widgets/file_operations_stub.dart`

```dart
import 'dart:typed_data';

Future<void> openFileOnPlatform(Uint8List bytes, String filename, String? mimeType) async {
throw UnsupportedError('Platform not supported');
}
```

**Crear**: `lib/features/chat/widgets/file_operations.dart` (entry point)

```dart
export 'file_operations_stub.dart'
if (dart.library.io) 'file_operations_mobile.dart'
if (dart.library.html) 'file_operations_web.dart';
```
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Conditional import pattern is broken — all three file_operations implementations must export the same function name.

The three files expose different names:

  • file_operations_web.dartopenFileInBrowser
  • file_operations_mobile.dartopenFileOnDevice
  • file_operations_stub.dartopenFileOnPlatform

The entry-point conditional export in file_operations.dart only works when every variant exports an identical API. Consumers importing file_operations.dart would reference a name that doesn't exist in the selected implementation on at least two of the three platforms, causing a compile error.

Standardise on a single name (e.g. openFile) across all three files:

🐛 Proposed fix
-Future<void> openFileInBrowser(Uint8List bytes, String filename, String? mimeType) async { ... }
+Future<void> openFile(Uint8List bytes, String filename, String? mimeType) async { ... }

-Future<void> openFileOnDevice(Uint8List bytes, String filename, String? mimeType) async { ... }
+Future<void> openFile(Uint8List bytes, String filename, String? mimeType) async { ... }

-Future<void> openFileOnPlatform(Uint8List bytes, String filename, String? mimeType) async { ... }
+Future<void> openFile(Uint8List bytes, String filename, String? mimeType) async { ... }
🧰 Tools
🪛 LanguageTool

[grammar] ~742-~742: Elimina el sustantivo
Context: ... OpenFile.open(tempFile.path); } **Crear**: `lib/features/chat/widgets/file_operations_stub.dart` dart import 'dart:typed_data'; Future openFileOnPlatform(Uint8List bytes, String filename, String? mimeType) async { throw UnsupportedError('Platform not supported'); } ``` Crear: `lib/features/chat/widgets/file...

(QB_NEW_ES_OTHER_ERROR_IDS_UNNECESSARY_NOUN)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/PWA_MIGRATION_PLAN.md` around lines 711 - 758, The conditional export
fails because each platform file exposes a different function name; unify the
API by renaming the exported function in
lib/features/chat/widgets/file_operations_web.dart (openFileInBrowser),
file_operations_mobile.dart (openFileOnDevice) and file_operations_stub.dart
(openFileOnPlatform) to the same identifier (e.g. Future<void>
openFile(Uint8List bytes, String filename, String? mimeType) async) and keep the
same signature (Future<void>, Uint8List bytes, String filename, String?
mimeType) so the entry-point lib/features/chat/widgets/file_operations.dart can
correctly resolve the symbol across platforms. Ensure each implementation
preserves its platform-specific behavior but uses the common function name and
async signature.

Comment on lines +1072 to +1078
```
Phase 1 ──→ Phase 2 ──→ Phase 3 ──→ Phase 4 ──→ Phase 5 ──→ Phase 6
3-5 días 2-3 días 3-5 días 3-4 días 4-5 días 3-5 días

Compila DB web Nostr web Services Chat files PWA full
en web funciona funciona no-crash funciona instalable
```
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing language specifier on fenced code block — MD040 violation.

The execution-order ASCII diagram block has no language identifier, which triggers the MD040 lint rule enforced by this repository.

🐛 Proposed fix
-```
+```text
 Phase 1 ──→ Phase 2 ──→ ...

As per coding guidelines: "Add a language specifier to every fenced code block in markdown files. Static analysis (markdownlint MD040) flags blocks without a language identifier."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
Phase 1 ──→ Phase 2 ──→ Phase 3 ──→ Phase 4 ──→ Phase 5 ──→ Phase 6
3-5 días 2-3 días 3-5 días 3-4 días 4-5 días 3-5 días
Compila DB web Nostr web Services Chat files PWA full
en web funciona funciona no-crash funciona instalable
```
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 1072-1072: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/PWA_MIGRATION_PLAN.md` around lines 1072 - 1078, The fenced ASCII
diagram block that starts with "Phase 1 ──→ Phase 2 ──→ Phase 3 ──→ Phase 4 ──→
Phase 5 ──→ Phase 6" is missing a language specifier and triggers markdownlint
MD040; fix it by updating the opening fence from ``` to a fenced code block with
a language identifier (e.g., ```text) so the diagram has an explicit language
tag in the file and satisfies the lint rule.

@Mostrica
Copy link

dart_nostr Web Compatibility — Blocker Confirmed, But Minor Fix

The migration plan correctly identifies dart_nostr web support as a potential blocker in Phase 3. I investigated this.

Status: dart_nostr does NOT compile for web currently.

Root cause: The package imports dart:io in 3 files but doesn't actually use any of its classes — they're dead imports that break web compilation:

File Line Uses dart:io?
lib/nostr/instance/registry.dart 1 ❌ No
lib/nostr/instance/relays/relays.dart 3 ❌ No
lib/nostr/model/relay.dart 3 ❌ No

The WebSocket implementation already uses web_socket_channel (cross-platform), so the actual networking code would work on web. Only the dead imports cause the compilation failure.

Solution: Remove the 3 unused imports:

- import 'dart:io';  // registry.dart
- import 'dart:io';  // relays.dart  
- import 'dart:io';  // relay.dart

Options to unblock Phase 3:

  1. Submit PR to anasfik/nostr — trivial fix, likely quick merge
  2. Fork temporarily while waiting for upstream
  3. Use path dependency override pointing to a patched local copy

Suggest updating the "Riesgos" section to reflect this: the blocker exists but the fix is minimal.

Copy link
Contributor

@mostronatorcoder mostronatorcoder bot left a comment

Choose a reason for hiding this comment

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

Thorough migration plan. The phased approach is sound — additive-only strategy is the right call to avoid regressions on mobile.

A few observations:

  • The blocker inventory (14 files) is well-researched and practical
  • Phase 1 conditional imports approach is the standard Flutter way to handle this
  • Good that sembast_web is already in pubspec but unused — Phase 2 will be smooth
  • Mostrica's finding about dart_nostr dead imports is valuable — worth incorporating into the Risks section as suggested

Minor note: The doc is in Spanish while AGENTS.md says GitHub interactions should be in English. Not a blocker for a planning doc, but worth considering for consistency.

LGTM ✅

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.

2 participants