diff --git a/README.md b/README.md
index 369d4a0..0a9acae 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,24 @@ Each app's README explains how to run it, how its sync works and what its tests

+### OutageNet
+
+
+
+### YardGate
+
+
+
+### AgriMesh
+
+
+
+### Event Floor
+
+
+
+
+
## Shared packages
| Package | Path | Description |
@@ -81,9 +99,6 @@ pnpm --filter @offline-app-examples/stocksync android
Replace `stocksync` with `tictactogether`, `orderup`, `cowrite`, `outagenet`, `yardgate`, `eventfloor`, or `agrimesh` for the other apps.
-### Event Floor
-
-Record `demo-staff.gif` and `demo-gate.gif` in [`apps/eventfloor`](./apps/eventfloor) and embed here when catalogue-ready (see app README).
For iOS you can also open the app's `ios/*.xcworkspace` in Xcode, choose your team
under Signing & Capabilities, and run on each phone. Debug builds load JavaScript
from Metro; install a Release build to use an app away from your computer.
diff --git a/apps/agrimesh/README.md b/apps/agrimesh/README.md
index 7899505..74ba377 100644
--- a/apps/agrimesh/README.md
+++ b/apps/agrimesh/README.md
@@ -4,6 +4,8 @@ Agriculture demo: **store-and-forward** field readings over **Bluetooth mesh** u
Built on `@offline-app-examples/mesh` (nearby rooms, mesh relay) and `@offline-app-examples/ui`.
+
+
## Roles
| Role | Device | Behavior |
@@ -43,10 +45,6 @@ Use **physical phones** with Bluetooth on. Debug builds need Metro.
Clear app data or reinstall. Seeded plots and reading templates are fixed in `src/domain/plots.ts`.
-## Catalogue GIF
-
-Add `demo.gif` in this folder after recording on devices (root README embed when ready).
-
## Tests
```sh
diff --git a/apps/agrimesh/demo.gif b/apps/agrimesh/demo.gif
new file mode 100644
index 0000000..976c901
Binary files /dev/null and b/apps/agrimesh/demo.gif differ
diff --git a/apps/eventfloor/README.md b/apps/eventfloor/README.md
index feada8c..873bae4 100644
--- a/apps/eventfloor/README.md
+++ b/apps/eventfloor/README.md
@@ -4,6 +4,10 @@ Live-events demo app with **two flows** in one binary: **staff dispatch** when c
Built on `@offline-app-examples/mesh` (rooms, MLS group mode, mesh relay) and `@offline-app-examples/ui`.
+
+
+
+
## Flow A — Staff dispatch
- **Staff sender** hosts an MLS **group** room and broadcasts encrypted staff alerts (demo roster + PIN stand in for OfflineID sign-in).
@@ -56,10 +60,6 @@ Use **physical phones** with Bluetooth on. Debug builds need Metro.
Clear app data or reinstall. Seeded tickets and staff PINs are fixed in `src/domain/`.
-## Catalogue GIFs
-
-Add `demo-staff.gif` and `demo-gate.gif` in this folder after recording on devices (root README embeds them).
-
## Tests
```sh
diff --git a/apps/eventfloor/demo-gate.gif b/apps/eventfloor/demo-gate.gif
new file mode 100644
index 0000000..4684fcc
Binary files /dev/null and b/apps/eventfloor/demo-gate.gif differ
diff --git a/apps/eventfloor/demo-staff.gif b/apps/eventfloor/demo-staff.gif
new file mode 100644
index 0000000..faeba12
Binary files /dev/null and b/apps/eventfloor/demo-staff.gif differ
diff --git a/apps/eventfloor/src/flows/GateFlow.tsx b/apps/eventfloor/src/flows/GateFlow.tsx
index f50b6db..75b7504 100644
--- a/apps/eventfloor/src/flows/GateFlow.tsx
+++ b/apps/eventfloor/src/flows/GateFlow.tsx
@@ -15,6 +15,7 @@ import {
import { ingestToPlatform, unsyncedScanIds } from '../domain/platform';
import { DEMO_EVENT } from '../domain/tickets';
import { loadPersisted, savePersisted, sanitizeHost, sanitizeScanner } from '../services/persist';
+import { ConnectingScreen } from '../ui/common';
import { HostGateScreen } from '../ui/HostGateScreen';
import { RelayScreen } from '../ui/RelayScreen';
import { ScannerGateScreen } from '../ui/ScannerGateScreen';
@@ -271,5 +272,29 @@ export function GateFlow({
);
}
- return null;
+ const leaveSession = () => {
+ void room.leave();
+ onLeave();
+ };
+
+ let message = 'Turn on Bluetooth and keep devices in range.';
+ if (role === 'host') {
+ message = 'Starting the lead scanner and advertising over Bluetooth…';
+ } else if (role === 'scanner') {
+ if (room.status === 'discovering' && room.hosts.length === 0) {
+ message = 'Looking for the lead scanner nearby. Start the lead gate on another phone first.';
+ } else if (room.status === 'joining') {
+ message = 'Joining the lead scanner…';
+ } else {
+ message = 'Connecting to the lead scanner…';
+ }
+ }
+
+ return (
+
+ );
}
diff --git a/apps/eventfloor/src/flows/StaffFlow.tsx b/apps/eventfloor/src/flows/StaffFlow.tsx
index 862a6a7..90e9879 100644
--- a/apps/eventfloor/src/flows/StaffFlow.tsx
+++ b/apps/eventfloor/src/flows/StaffFlow.tsx
@@ -6,6 +6,7 @@ import {
type StaffAlert,
type StaffSession,
} from '../domain/staff';
+import { ConnectingScreen } from '../ui/common';
import { RelayScreen } from '../ui/RelayScreen';
import { StaffReceiverScreen } from '../ui/StaffReceiverScreen';
import { StaffSenderScreen } from '../ui/StaffSenderScreen';
@@ -156,5 +157,29 @@ export function StaffFlow({
);
}
- return null;
+ const leaveSession = () => {
+ void room.leave();
+ onLeave();
+ };
+
+ let message = 'Turn on Bluetooth and keep devices in range.';
+ if (role === 'sender') {
+ message = 'Starting the staff channel…';
+ } else if (role === 'receiver') {
+ if (room.status === 'discovering' && room.hosts.length === 0) {
+ message = 'Looking for the staff sender nearby. Start the sender on another phone first.';
+ } else if (room.status === 'joining') {
+ message = 'Joining the staff channel…';
+ } else {
+ message = 'Connecting to staff dispatch…';
+ }
+ }
+
+ return (
+
+ );
}
diff --git a/apps/eventfloor/src/ui/common.tsx b/apps/eventfloor/src/ui/common.tsx
index eed50c3..6ed8420 100644
--- a/apps/eventfloor/src/ui/common.tsx
+++ b/apps/eventfloor/src/ui/common.tsx
@@ -2,6 +2,7 @@ import { Button, cn, Icon, PersonAvatar, Text } from '@offline-app-examples/ui';
import { LogOut } from 'lucide-react-native';
import React, { useEffect, useState } from 'react';
import { View } from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
type Person = { id: string; name: string };
@@ -43,6 +44,34 @@ export function TopBar({
);
}
+export function ConnectingScreen({
+ title = 'Connecting…',
+ message,
+ error,
+ onLeave,
+}: {
+ title?: string;
+ message?: string;
+ error?: string;
+ onLeave: () => void;
+}) {
+ return (
+
+
+
+
+
+ {error ? 'Could not connect' : title}
+
+ {error ?? message ?? 'Turn on Bluetooth and keep devices in range.'}
+
+
+
+ );
+}
+
export function DebugStrip({ lines }: { lines: string[] }) {
return (
diff --git a/apps/outagenet/README.md b/apps/outagenet/README.md
index 750e800..b72e61b 100644
--- a/apps/outagenet/README.md
+++ b/apps/outagenet/README.md
@@ -4,6 +4,8 @@ Field coordination when towers are down. One phone runs the **command post**; ot
Built on `@offline-app-examples/mesh` and `@offline-app-examples/ui`, Mesh SDK 0.28.
+
+
## What it demonstrates
- Host-authoritative ops log with **stable `operationId`** on every record (retries and mesh resends dedupe).
diff --git a/apps/outagenet/demo.gif b/apps/outagenet/demo.gif
new file mode 100644
index 0000000..e2b785e
Binary files /dev/null and b/apps/outagenet/demo.gif differ
diff --git a/apps/outagenet/ios/OutageNet.xcodeproj/project.pbxproj b/apps/outagenet/ios/OutageNet.xcodeproj/project.pbxproj
index af092f8..57dc9a2 100644
--- a/apps/outagenet/ios/OutageNet.xcodeproj/project.pbxproj
+++ b/apps/outagenet/ios/OutageNet.xcodeproj/project.pbxproj
@@ -13,10 +13,10 @@
4DADA4D9FCF7ACF7CD308512 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
59FA85C7DABD4C424CC92A6D /* dm_serif_display_regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0DDFFF2E5319DC6CB07FB356 /* dm_serif_display_regular.ttf */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
- B2E8F1A12CA45701006654EE /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2E8F1A02CA45701006654EE /* SceneDelegate.swift */; };
77425900FDE6A239FBDE1587 /* space_grotesk_semibold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 16DDDA6F0AE168DD28C46529 /* space_grotesk_semibold.ttf */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
ACF648FCD514FC7967494DCF /* space_grotesk_regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 8077B93F1BA98961C21AE16E /* space_grotesk_regular.ttf */; };
+ B2E8F1A12CA45701006654EE /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2E8F1A02CA45701006654EE /* SceneDelegate.swift */; };
C79EC88C8F01E8069AAC7EB9 /* space_grotesk_bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A7E51F5E996CDBEB4D2962DC /* space_grotesk_bold.ttf */; };
/* End PBXBuildFile section */
@@ -31,11 +31,11 @@
5709B34CF0A7D63546082F79 /* Pods-OutageNet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OutageNet.release.xcconfig"; path = "Target Support Files/Pods-OutageNet/Pods-OutageNet.release.xcconfig"; sourceTree = ""; };
5DCACB8F33CDC322A6C60F78 /* libPods-OutageNet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OutageNet.a"; sourceTree = BUILT_PRODUCTS_DIR; };
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = OutageNet/AppDelegate.swift; sourceTree = ""; };
- B2E8F1A02CA45701006654EE /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = OutageNet/SceneDelegate.swift; sourceTree = ""; };
7D7566AB5B03E0DD2AE8D975 /* space_grotesk_medium.ttf */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = space_grotesk_medium.ttf; path = ../../../packages/ui/fonts/font/space_grotesk_medium.ttf; sourceTree = SOURCE_ROOT; };
8077B93F1BA98961C21AE16E /* space_grotesk_regular.ttf */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = space_grotesk_regular.ttf; path = ../../../packages/ui/fonts/font/space_grotesk_regular.ttf; sourceTree = SOURCE_ROOT; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = OutageNet/LaunchScreen.storyboard; sourceTree = ""; };
A7E51F5E996CDBEB4D2962DC /* space_grotesk_bold.ttf */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = space_grotesk_bold.ttf; path = ../../../packages/ui/fonts/font/space_grotesk_bold.ttf; sourceTree = SOURCE_ROOT; };
+ B2E8F1A02CA45701006654EE /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = OutageNet/SceneDelegate.swift; sourceTree = ""; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
@@ -222,14 +222,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-OutageNet/Pods-OutageNet-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
- inputPaths = (
- );
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-OutageNet/Pods-OutageNet-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
- outputPaths = (
- );
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-OutageNet/Pods-OutageNet-frameworks.sh\"\n";
@@ -265,14 +261,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-OutageNet/Pods-OutageNet-resources-${CONFIGURATION}-input-files.xcfilelist",
);
- inputPaths = (
- );
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-OutageNet/Pods-OutageNet-resources-${CONFIGURATION}-output-files.xcfilelist",
);
- outputPaths = (
- );
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-OutageNet/Pods-OutageNet-resources.sh\"\n";
diff --git a/apps/outagenet/ios/Podfile.lock b/apps/outagenet/ios/Podfile.lock
index 5a2dd3d..b1ea17d 100644
--- a/apps/outagenet/ios/Podfile.lock
+++ b/apps/outagenet/ios/Podfile.lock
@@ -2383,4 +2383,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 511362855f8d78cc3f03e1db12273c7a024db43d
-COCOAPODS: 1.15.2
+COCOAPODS: 1.16.2
diff --git a/apps/yardgate/README.md b/apps/yardgate/README.md
index 38fddac..6a0e622 100644
--- a/apps/yardgate/README.md
+++ b/apps/yardgate/README.md
@@ -4,6 +4,8 @@ Logistics yard check-in when there is no cellular: a **driver** discovers a **ga
Mesh SDK **0.28** — `MeshServices` discover + invoke only (no Proof of Location; invoke bodies are signed plaintext — no secrets). Service responses use SDK status strings **`ok`**, **`error`**, or **`not_found`** (not HTTP codes like `200`).
+
+
## What it demonstrates
- **`yardgate-gate` service** — register on the gate device, discover from the driver (multi-hop when relay is in the middle).
diff --git a/apps/yardgate/demo.gif b/apps/yardgate/demo.gif
new file mode 100644
index 0000000..e0203c4
Binary files /dev/null and b/apps/yardgate/demo.gif differ
diff --git a/apps/yardgate/ios/Podfile.lock b/apps/yardgate/ios/Podfile.lock
index b426b1a..9ff07da 100644
--- a/apps/yardgate/ios/Podfile.lock
+++ b/apps/yardgate/ios/Podfile.lock
@@ -2357,4 +2357,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 2410de8918b31340e61afe18d4d24a52d58b6378
-COCOAPODS: 1.15.2
+COCOAPODS: 1.16.2