Skip to content

State, events and navigation ​

Reactive stores ​

Apsule uses three conceptual scopes:

ScopeLifetimeIntended use
stateView/component runtimeUI state, loading flags, current list data.
globalRunning Apsule ProjectSession/account/shared state across routes.
storagePersistentSettings and data that survive process/project restarts. Accessed through Host API rather than magical synchronous disk writes.

Changes to state and global are reactive:

javascript
state.loading = true;
global.currentUser = user;

Bound SwiftUI nodes should update automatically.

Actions ​

JSON references named actions; it should not contain arbitrary JavaScript:

json
{
  "id": "refresh",
  "type": "Button",
  "props": { "text": "Refresh" },
  "events": {
    "tap": { "action": "refreshMatches" }
  }
}
javascript
actions.refreshMatches = async () => {
  state.loading = true;
  try {
    state.matches = await api.getMatches();
  } finally {
    state.loading = false;
  }
};

Actions may accept an optional event payload as their first JavaScript argument. Most ordinary static controls currently invoke actions without domain data. Controls rendered inside ForEach receive the active iteration scope, so an action can read values such as event.match.id or event.item.content without generating a separate static action for every row.

The entry view may define an appear action. The Host invokes it through the normal Promise-aware action path when the running Project root appears, which makes it the preferred place for asynchronous cache loading and initial synchronization that must publish state back to SwiftUI.

Event vocabulary ​

Implemented event vocabulary includes:

  • appear
  • disappear
  • foreground
  • inactive
  • background
  • tap
  • doubleTap
  • longPress
  • change
  • submit
  • refresh
  • drag
  • drop
  • swipe
  • focus
  • blur
  • confirm
  • reorder
  • context
  • navigate
  • error
  • message
  • regionChange
  • mapTap
  • top-level actions named openURL, notification, and push for external lifecycle events

Events carry JSON-serializable payloads. Gesture/map/web events place event-specific fields under event; ForEach also preserves the active iteration scope.

Prefer route names over embedding filesystem paths throughout views:

json
{
  "routes": {
    "home": "views/home.json",
    "player": "views/player.json"
  }
}

JavaScript API:

javascript
router.push("player", { id: player.id });
router.pop();
router.replace("home");
router.sheet("settings");
router.dismiss();

The JavaScript router above is implemented. Route parameters are read-only inputs under the route.params scope unless copied into state/global. Declarative NavigationLink also accepts props.params.

The Host additionally handles its apsule:// deep-link scheme. A running Project receives ordinary external URLs through actions.openURL(payload). Scene transitions invoke top-level actions.foreground, actions.inactive, or actions.background. Project-push notifications invoke actions.push(payload) only when the Project declared and received the push permission; local-notification taps invoke actions.notification(payload).

This route model is also suitable for a future visual navigation graph in Apsule Builder.

Subscriptions ​

Long-lived Host API operations return cancellable subscriptions:

javascript
const sub = host.location.watch(position => {
  global.position = position;
});

// Later
sub.cancel();

The runtime should automatically dispose view-scoped subscriptions when their owning view is destroyed unless explicitly promoted to app scope.