nomos_flutter 0.55.6 copy "nomos_flutter: ^0.55.6" to clipboard
nomos_flutter: ^0.55.6 copied to clipboard

Drive the real Nomos GitHolon from a Flutter app — a local-first domain runtime. iOS + macOS run the native-AOT kernel (no WebView memory cap); Android uses a hidden secure WebView. Write TypeScript d [...]

example/lib/main.dart

// nomos_flutter example — drives the REAL GitHolon through NomosScope. On iOS/macOS this runs the native-AOT
// kernel over dart:ffi (no WebView); on Android the hidden WebView host; on WEB the in-page @githolon/client
// browser runner (no FFI, no WebView). Same NomosBridge protocol on every platform.
import 'dart:ui' show PlatformDispatcher;

import 'package:flutter/material.dart';
import 'package:nomos_flutter/nomos_flutter.dart';

const cloud = 'https://nomos.captainapp.co.uk';
const workspace = 'todo-dogfood-20260616-codex';
const listId = 'prof-convergence';

void main() {
  // Surface EVERY error legibly (the obfuscated web frames are unreadable): route framework + uncaught
  // platform/async errors through a plain [web-proof] log so a throw anywhere in the write/watch path is
  // visible in the browser console, not just as an opaque aBJ/aBK frame.
  FlutterError.onError = (details) {
    debugPrint('[web-proof] FLUTTER ERROR: ${details.exception}\n${details.stack}');
    FlutterError.presentError(details);
  };
  WidgetsFlutterBinding.ensureInitialized();
  PlatformDispatcher.instance.onError = (error, stack) {
    debugPrint('[web-proof] UNCAUGHT: $error\n$stack');
    return true; // handled — don't let it become an opaque uncaught frame
  };
  runApp(const MaterialApp(debugShowCheckedModeBanner: false, home: _Root()));
}

class _Root extends StatelessWidget {
  const _Root();
  @override
  Widget build(BuildContext context) => NomosScope(
        cloud: cloud,
        workspace: workspace,
        builder: (context, nomos) => TodoPage(nomos.bridge),
        loadingBuilder: (context, bridge) => const Scaffold(
          body: Center(child: Text('booting the Nomos holon…')),
        ),
      );
}

class TodoPage extends StatefulWidget {
  final NomosBridge bridge;
  const TodoPage(this.bridge, {super.key});
  @override
  State<TodoPage> createState() => _TodoPageState();
}

class _TodoPageState extends State<TodoPage> {
  final _input = TextEditingController();
  String? _lastAdded; // the text of the most recent local add — pinned to the top so the proof is unambiguous

  // Decode one watch row to its display text WITHOUT ever throwing (a bad row must not crash the ListView).
  String _rowText(dynamic row) {
    try {
      if (row is Map) {
        final data = row['data'];
        final m = data is Map ? data : row;
        return (m['text'] ?? m['title'] ?? '?').toString();
      }
    } catch (_) {}
    return row.toString();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('nomos_flutter · todo')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(10),
            child: Row(children: [
              Expanded(child: TextField(controller: _input,
                  decoration: const InputDecoration(hintText: 'new todo…', border: OutlineInputBorder(), isDense: true))),
              const SizedBox(width: 8),
              FilledButton(onPressed: () async {
                final t = _input.text.trim();
                if (t.isEmpty) return;
                _input.clear();
                try {
                  // THE WRITE VERB — offerDirective (THE ONE ENCODER): hand the holon the PLANNED directive
                  // (domain/directiveId/payload) and it builds the v2 offer envelope via the single codec. Do
                  // NOT hand-encode + call offer(intentBytes:) — offer() expects opaque base64 envelope bytes,
                  // not raw JSON, so a hand-built JSON string is refused ("not a valid intent envelope").
                  debugPrint('[web-proof] offerDirective start text="$t"');
                  if (mounted) setState(() => _lastAdded = t);
                  final head = await widget.bridge.offerDirective(
                    domain: 'todo',
                    directiveId: 'addTodo',
                    payload: {'listId': listId, 'text': t},
                  );
                  debugPrint('[web-proof] offerDirective ok head=$head');
                  // Push the local write → session branch → edge admission → main (the write reaches the cloud).
                  debugPrint('[web-proof] sync start');
                  final r = await widget.bridge.sync(admit: true);
                  debugPrint('[web-proof] sync ok result=$r');
                } catch (e, st) {
                  debugPrint('[web-proof] WRITE ERROR: $e\n$st');
                  // Surface the REAL error instead of an uncaught obfuscated exception.
                  if (context.mounted) {
                    ScaffoldMessenger.of(context)
                        .showSnackBar(SnackBar(content: Text('add failed: $e')));
                  }
                }
              }, child: const Text('Add')),
            ]),
          ),
          Expanded(
            child: StreamBuilder<List<dynamic>>(
              stream: widget.bridge.watch('todosByList', {'listId': listId}),
              builder: (context, snap) {
                if (snap.hasError) {
                  debugPrint('[web-proof] watch ERROR ${snap.error}');
                  return Center(child: Text('error: ${snap.error}'));
                }
                final rows = List<dynamic>.from(snap.data ?? const []);
                // Decode to display texts (never throws), then pin the most recent local add to the TOP so a
                // new todo is unambiguously visible even among 100+ existing rows.
                final texts = rows.map(_rowText).toList();
                final pinned = _lastAdded;
                final foundPinned = pinned != null && texts.contains(pinned);
                debugPrint(
                    '[web-proof] watch rows=${texts.length} lastAdded=$pinned present=$foundPinned');
                if (foundPinned) {
                  texts.remove(pinned);
                  texts.insert(0, pinned);
                }
                return ListView.builder(
                  itemCount: texts.length,
                  itemBuilder: (c, i) {
                    final isPinned = foundPinned && i == 0;
                    return ListTile(
                      dense: true,
                      leading: isPinned ? const Icon(Icons.check_circle, color: Colors.green) : null,
                      title: Text(texts[i],
                          style: isPinned ? const TextStyle(fontWeight: FontWeight.bold) : null),
                    );
                  },
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}
0
likes
0
points
4.18k
downloads

Publisher

unverified uploader

Weekly Downloads

Drive the real Nomos GitHolon from a Flutter app — a local-first domain runtime. iOS + macOS run the native-AOT kernel (no WebView memory cap); Android uses a hidden secure WebView. Write TypeScript domains, get a typesafe Dart client, build Flutter widgets.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

ffi, flutter, flutter_inappwebview, flutter_js, nomos_client, path_provider, web

More

Packages that depend on nomos_flutter

Packages that implement nomos_flutter