Skip to main content
All Docs
FeaturesmyProp (AgentOS People Portal)Updated April 4, 2026

Restoring Capacitor Mobile App Compatibility with a sessionStorage Shim

Restoring Capacitor Mobile App Compatibility with a sessionStorage Shim

Release: v0.1.89

Background

The myProp portal ships as both a web app and a native mobile app via Capacitor. The original portal was built with Vue.js, and its Vuex store persisted discovered user accounts to sessionStorage under the key user_accounts every time a user's accounts were fetched. The Capacitor mobile app relied on this specific contract: on startup or page refresh, it read sessionStorage['user_accounts'] to rehydrate the account list without making a network call, giving users an instant, flicker-free experience.

When the portal was rebuilt in Next.js with tRPC and React Query, this behaviour was not carried over. tRPC and React Query handle caching internally and do not write to sessionStorage. The result was that the Capacitor mobile app could no longer find the data it expected, breaking session rehydration.

v0.1.89 restores this contract with a targeted compatibility shim.


How the shim works

The fix is composed of two new files and two small modifications.

useSessionStorageSync hook

src/hooks/use-session-storage-sync.ts is the core of the shim. It is a custom React hook that:

  1. Accepts the result of a successful accountDiscovery.discover tRPC query.
  2. Maps each normalised PortalAccount object back to the legacy AgentOSAccountRecord shape the Capacitor app expects.
  3. Serialises the array to JSON and writes it to sessionStorage['user_accounts'].
  4. Skips the write if the serialised value has not changed since the last write (deduplication via a useRef).
  5. Silently catches any sessionStorage errors — quota exceeded, private browsing, SSR — so the shim never disrupts the web experience.

The same file also exports clearSessionStorageAccounts(), a standalone utility that removes the user_accounts key.

// Simplified hook signature
useSessionStorageSync({
  companyShortName: string,  // Agency slug for reverse-mapping company fields
  accounts: PortalAccount[] | undefined,
  isSuccess: boolean,
});

SessionStorageSync component

src/components/session-storage-sync.tsx is an invisible React component that:

  • Subscribes to the accountDiscovery.discover tRPC query with staleTime: 60_000, matching the value used by the accounts list page.
  • Passes the query result into useSessionStorageSync.
  • Renders null — it has no visible output.

Because it shares the same query key and staleTime as the accounts list page, no additional network requests are made. React Query returns the cached result.

// session-storage-sync.tsx renders nothing
export function SessionStorageSync() {
  // ... subscribes to tRPC, calls useSessionStorageSync
  return null;
}

Mounting in the company layout

<SessionStorageSync /> is mounted inside CompanyLayoutClient, which wraps all company-namespaced pages. This means the sync runs automatically on every page within the portal — no per-page wiring required.

Clearing on sign-out

CompanyShell now calls clearSessionStorageAccounts() before invoking signOut(). This mirrors the original Vue app's clearAccountsData Vuex action, which removed the user_accounts key on logout to prevent stale data from being rehydrated on the next app launch.


sessionStorage data format

The shim writes a JSON array. Each element matches the original AgentOS API response shape:

[
  {
    "OID": "abc123",
    "Name": "Jane Smith",
    "EmailAddress": "jane@example.com",
    "AccountType": "LettingsLandlord",
    "CompanyShortName": "myagency",
    "CompanyMarketingName": "My Agency Ltd",
    "CompanyLogo": "https://cdn.example.com/logo.png",
    "IsActive": true,
    "BranchID": "branch-001"
  }
]

AccountType reverse mapping

The Next.js app uses normalised portal type strings internally. The shim maps these back to the AgentOS AccountType values the Capacitor app expects:

Portal typeAgentOS AccountType
landlordLettingsLandlord
tenantLettingsTenant
buyerSalesApplicantBuyer
vendorSalesVendor
contractorMaintenanceContractor

If the original AgentOS field values are available in the account's metadata object (preserved during account discovery), those are used directly rather than the reverse-mapped values.


Design decisions

DecisionRationale
Share React Query cacheAvoids a duplicate network request. The component uses the same query key and staleTime as the accounts list page.
Deduplicate writes with useRefPrevents repeated sessionStorage.setItem calls on re-renders where account data hasn't changed.
Silent error handlingStorage failures (private browsing, quota limits, SSR) must never break the web portal. The Capacitor app already handles missing storage data gracefully.
Metadata passthroughPreserves fidelity of the original AgentOS field values where available, rather than relying solely on reverse-mapped approximations.
Clear on sign-outMatches the original app's logout behaviour exactly, preventing ghost sessions in the Capacitor app.