Files
automiz_app_full/development/planxo/PLANXO_SYSTEM_DOC.md
2026-08-28 19:24:04 +05:30

12 KiB
Raw Permalink Blame History

PlanXO Desktop Sync System Documentation & Issue Report

Last reviewed: March 2026
Target platform: macOS Desktop (Flutter); Windows Desktop planned


1. System Architecture

┌─────────────────────────────────────────────────────────────────┐
│                  PlanXO Desktop App (Flutter/Dart)               │
│                         main.dart                                │
└───────────────────────────┬─────────────────────────────────────┘
            WebSocket (wss)  │          HTTP (https / dio)
          ┌──────────────────┘──────────────────────┐
          ▼                                          ▼
┌─────────────────────┐               ┌──────────────────────────┐
│  WebSocket Bridge   │    axios (HTTP)│   Asset Manager (Python) │
│  server.js (Node)   │◄──────────────┤  asset_manager_app.py    │
│  Port: WS_PORT  |   │               │  Port: 5002               │
│  Port: HTTP_PORT    │               │  S3 + MongoDB             │
└─────────┬───────────┘               └──────────────────────────┘
          │ HTTP (axios)
          ▼
┌─────────────────────┐
│   CMS Backend       │
│   app.py (Flask)    │
│   + MongoDB + JWT   │
└─────────────────────┘

Component Roles

Component Language Responsibility
main.dart Flutter/Dart Desktop UI, file watch, upload/download sync
server.js Node.js (Express + WS) WebSocket relay, auth key registration/approval, message routing
asset_manager_app.py Python (Flask) S3 operations: upload presign, download folder as ZIP, asset CRUD
app.py Python (Flask) CMS logic: users, projects, auth key CRUD, JWT, role enforcement

Frontend Admin Settings

File Purpose
DesktopSyncSettings.js Sets global sync interval (stored in CMS backend via /settings/desktop-sync)
FolderSyncSettings.js Sets admin folder age filter (how many days back to sync; via /settings/folder-sync)

2. Authentication Flow

Desktop App                  WebSocket Server              CMS Backend              Browser
    │                               │                           │                       │
    │─── WSS connect (apiKey, unique_id) ───────────────────►  │                       │
    │                               │                           │                       │
    │─── register_auth_key ─────────►                           │                       │
    │                               │─── POST /planxo/app/auth/register-key ──────────► │
    │                               │◄── {auth_key, browser_url} ─────────────────────  │
    │◄── auth_key_registered ───────│                           │                       │
    │                               │                           │                       │
    │─── open browser with /planxo-auth/{auth_key} ─────────────────────────────────►  │
    │                               │                           │                       │
    │  (polling validate_auth every 2s) ──────────────────────► │                       │
    │                               │                           │                       │
    │                               │        Admin approves in browser ◄────────────────│
    │                               │◄── POST /api/planxo/app/auth/{key}/approve ────── │
    │◄── auth_approved (user, sync_interval) ────────────────── │                       │
    │                               │                           │                       │
    │  store auth key, start sync                               │                       │

3. Sync Logic (Download Path)

  1. After successful auth, fetchFileList() is called.
  2. App fetches filtered folders list: GET /planxo/folders/filtered (HTTP, with X-Auth-Key header).
  3. For each folder in the list, _downloadAndExtractFolder(folder) is called:
    • GET /folders/download?folder=<encoded> → returns a ZIP stream.
    • ZIP is decoded with archive package.
    • For each file in ZIP: if local file does NOT exist OR remote mtime is newer → write to disk.
    • After extraction, _primeKnownKeys(prefix) fetches the S3 asset list into _knownS3Keys.
    • A DirectoryWatcher is started on the local folder.
  4. Periodic sync (per-folder timer or global timer) calls _downloadAndExtractFolder again to check for remote changes.

4. Sync Logic (Upload Path)

  1. DirectoryWatcher fires ADD or MODIFY events.
  2. _onLocalFileChanged(path, folderPrefix) is called:
    • Debounces: skip if same file was processed < 1 second ago.
    • Computes SHA-256 hash. Compares to _syncMeta[path]['hash']. Skips if unchanged.
    • If file key is in _knownS3Keys: calls _replaceExisting() (presign-replace → PUT to S3 → complete-replace).
    • Else: calls _uploadNew() (presign-upload → PUT to S3 → register-upload).
    • Updates _syncMeta and persists to ~/.planxo_sync_meta.json.
  3. Periodic _syncLocalChanges() also scans all files and uploads modified ones (same logic).

5. Persistence Files (macOS $HOME)

File Content
~/.planxo_client Last used client name (subdomain)
~/.planxo_auth_key Saved auth key (plaintext)
~/.planxo_storage Storage base path
~/.planxo_sync_settings.json Per-folder sync enabled/interval
~/.planxo_sync_meta.json Per-file {lastModified, hash} for change detection
~/Library/.../sync_data.db SQLite event log via DatabaseHelper

6. Role-Based Sync Intent (Design Goal)

Role Intended Behaviour
Normal user Sync only folders/projects assigned to them
Admin Sync all projects created within folder_age_days (configured in Frontend FolderSyncSettings.js)

The filtering is supposed to happen at GET /planxo/folders/filtered on the CMS backend. The desktop app currently passes X-Auth-Key so the backend knows who the user is.


7. Issues Found

🔴 Critical Bugs

🟠 Logic Bugs

7.16 _buildLoginScreen Shows WS Status But Never Initiates Connection

The login screen shows a red/green dot for WS status, but _connectWs() is never called on app launch. The dot will always be red until the user clicks "Login".

See also issue 7.14 above.


7.17 MyApp (Root Widget) Holds All State

The entire app state including network connections, timers, and file watchers lives in _MyAppState which wraps MaterialApp. This is an anti-pattern in Flutter. A rebuild of MyApp disposes and re-creates all state including open WebSockets.

Recommendation: Break out a HomePage stateful widget and keep MyApp as a thin shell containing only MaterialApp configuration.


7.18 Auth Key Stored as Plaintext File

~/.planxo_auth_key is a plaintext file readable by any process running as the same user. On a shared machine this is a security risk.

Recommendation (macOS): Use the macOS Keychain via the flutter_secure_storage package.


7.19 _storageController and _clientController Are Leaked

These TextEditingController instances are initialized in initState but the _storageController.text is only set later in async callbacks (_loadStoragePath, _loadClientName). If the widget is disposed before these complete, there could be a "setState called after dispose" error. The async loaders call setState() without a mounted guard.

Fix: Wrap all setState() calls inside async methods with if (!mounted) return;.


7.20 _syncInterval from Backend Not Passed to Per-Folder Timers

_syncInterval (received from auth_approved or auth_success) is used only for _periodicSyncTimer. The per-folder timers (_syncTimers) use a locally configured interval. There is no reconciliation between the admin-set global interval from the frontend (DesktopSyncSettings.js) and the per-folder interval.

Fix: When _syncInterval is received from the server, apply it as the default for any new folder timers and update existing ones.


8. Windows Desktop Readiness

Item Status
dart:io WebSocket Works on Windows
file_selector Has Windows support
watcher Uses native FS events
sqflite + FFI ⚠️ Needs sqflite_common_ffi + Windows SQLite DLL
Process.run('open', [...]) macOS only all openInFinder, _openUrl, _ensureStorageBaseExists use open command
Platform.environment['HOME'] ⚠️ On Windows use Platform.environment['USERPROFILE']
Sandbox / path permissions Windows has fewer restrictions than macOS

Recommended abstraction:

String getHomeDir() {
  if (Platform.isMacOS || Platform.isLinux) {
    return Platform.environment['HOME'] ?? Directory.current.path;
  } else if (Platform.isWindows) {
    return Platform.environment['USERPROFILE'] ?? Platform.environment['HOMEPATH'] ?? 'C:\\Users\\Default';
  }
  return Directory.current.path;
}

Future<void> openInExplorer(String path) async {
  if (Platform.isMacOS) {
    await Process.run('open', ['-R', path]);
  } else if (Platform.isWindows) {
    await Process.run('explorer', ['/select,', path]);
  } else {
    await Process.run('xdg-open', [p.dirname(path)]);
  }
}

9. Summary Table

# Severity Category Issue
7.1 🔴 Critical Architecture unique_id hardcoded breaks multi-machine
7.2 🔴 Critical Platform sqflite won't work on macOS/Windows desktop without FFI
7.3 🔴 Critical Logic _uploadFile ignores relativePath param
7.4 🔴 Critical Logic ZIP mtime is DOS format, not Unix skip-overwrite logic wrong
7.5 🔴 Critical Stability _ws!.add without null/open guard → potential crash
7.6 🟠 Bug Stability Double reconnect scheduling, exponential reconnect storms
7.7 🟠 Bug Logic _handleAuthRevoked leaks per-folder timers
7.8 🟠 Bug Performance Auth polling sends WS validate during pending phase (noise)
7.9 🟠 Bug Logic _primeKnownKeys expects List, gets Map → always empty
7.10 🟠 Bug Logic Role-based folder filtering skipped when WS is connected
7.11 🟠 Bug Logic Two overlapping periodic sync timers cause race conditions
7.12 🟡 Quality State _knownS3Keys lost on restart → all files re-uploaded as new
7.13 🟡 Quality Deps http and web_socket_channel are unused dependencies
7.14 🟡 Quality UX WS not auto-connected on startup with saved auth
7.15 🟡 Quality Feature guessContentType missing most media file types
7.16 🟡 Quality UX Login screen WS indicator always red at launch
7.17 🟡 Quality Architecture All state in root MyApp widget (anti-pattern)
7.18 🟡 Quality Security Auth key stored as plaintext file
7.19 🟡 Quality Stability setState after dispose possible in async loaders
7.20 🟡 Quality Feature Backend sync interval not applied to per-folder timers
W.1 🟠 Platform Windows All Process.run('open', ...) calls are macOS-only
W.2 🟠 Platform Windows HOME env var not set on Windows use USERPROFILE