System architecture and boundaries
The architecture
Wingman coordinates temporary, platonic meetups without a permanent account or inbox. A request is an availability record describing a person’s plan, proposed place, time and travel radius. The central design choice is to keep shared decisions on the server while the browser retains enough local state to make editing and short interruptions manageable.
Diagram
Presence · caches · leases
Access through photo API
Photon reverse / cities
Overpass screening / entrances
Text version
- Browser: forms, radar, local recovery and tab signals send HTTPS actions and polls to the application API and receive authoritative snapshots.
- Cloudflare Worker: resolves session ownership, checks inputs and applies shared-state transitions.
- D1: stores requests, matches, presence, caches and coordination leases.
- R2: stores images and thumbnails, accessed through the photo API.
- Providers: Geoapify supplies address autocomplete and nearby business lists, Photon supplies reverse geocoding and city lookups, and Overpass supplies suitability geometry and current-business entrance queries. The browser separately loads OpenStreetMap tiles and an approximate IP lookup.
| Component | Owns | Boundary |
|---|---|---|
| React / TypeScript browser | Editing, maps, local convenience/recovery state and display of server snapshots. | A locally displayed request is not proof of a successful shared-state write. |
| Cloudflare Worker API | Validation, session resolution, renewals, matching rules, photo access and snapshot construction. | All shared mutations go through application routes. |
| D1 (SQLite) | Live records, memberships, operational metadata, lookup caches and coordination leases. | Database constraints and conditional updates protect shared transitions. |
| R2 | Uploaded photos and smaller display versions. | Application endpoints check availability and access before serving images. |
The application uses Vinext with Vite and a Next.js-style route structure. The rest of this document follows the request lifecycle: establishing a session, discovering and agreeing on a place, preserving or retiring data, then recovering from operational failures. Revision links are grouped in collapsed blocks at the bottom of their sections so implementation history can be followed without interrupting the main explanation.
Identity, presence and synchronization
How it works without a login
There is no username, password, email verification or account-registration flow. On initialization, the page generates 32 cryptographically random bytes with crypto.getRandomValues, represented as a 64-character hexadecimal token. That token supplies a temporary session capability when the browser does not already have a valid session cookie.
Capability-based request ownership
The server prefers the wingman-session cookie over the token supplied in a request body. It computes a SHA-256 hash of the selected token and uses that hash to look up the browser’s live request. The database stores the hash, not the original session token. Possessing the session capability lets a browser manage that request; the displayed name is not a credential.
Responses with an established session set an HttpOnly, SameSite=Strict cookie; HTTPS adds Secure. An unfinished request receives up to 13 hours of cookie continuity so a participant can return even if the other person finalized just before their browser stopped checking in. This capability does not extend an unfinished request’s 90-second availability lease. A finalized response retains the cookie through the latest retained meeting deadline, with a minimum 90 seconds. Without a request or retained connection it returns to 90 seconds. The server stores only its hash. Clearing cookies or switching browser profiles loses this ownership; there is no cross-device account recovery.
Reload and recovery boundaries
This lets a reload recover a still-live request without asking the person to log in. A random UUID identifies the public request separately from the private session capability. Database uniqueness on the token hash prevents multiple request rows for the same token. An expired request is not a recoverable account: saved form details can help post a new request, but do not recreate the expired server record.
Login-free does not mean anonymous or identity-verified. Public requests contain the information the user enters, and infrastructure receives network information such as IP addresses. Names, ages, photos and intentions are not independently verified. There is no cross-device account recovery or identity-based “one person, one request” guarantee.
Synchronization and heartbeats
Shared state follows a request–snapshot protocol. A browser sends an action to /api/wingman; the Worker checks the session, applies any permitted change, and returns the current view of the request, match, radar and counts. A successful mutation therefore updates the initiating page immediately, while another visitor normally observes it on their next successful poll.
Routine state polls target a ten-second interval. Each successful renewal moves the live request’s expiry to 90 seconds after server time. This margin allows brief interruptions without turning browser timers into the authority for availability. The transport is HTTP polling: a suspended browser cannot rely on receiving a change or renewing its request.
Sequence · Renewal and shared snapshots
Read from top to bottom. Dashed arrows are responses. Choose the diagram or its text version below. Wide diagrams keep sideways scrolling when fitting would make their labels too small.
Diagram
Text version
- Browser A → Worker / D1: Send state poll or explicit action
- Resolve session; renew a live request. Renew visitor presence only when supplied by a visible homepage.
- Worker / D1 → Browser A: Return current request, match and counts
- Browser B → Worker / D1: Send next state poll (about every 10 s)
- Worker / D1 → Browser B: Return the latest shared snapshot
- A timeout or suspended tab delays observation. Later checks reconcile with server state.
Three clocks with different meanings
Other people’s live request views separate the age of a request from server renewal and browser interaction. The owner’s request views and saved form history show request age without check-in or browser-activity fields. Open request details use the newest matching request from each snapshot. Their labels answer different questions, so none should be used as a substitute for the others.
| Indicator | Derived from | Interpretation |
|---|---|---|
| Request age | Original request creation time; locally refreshed against the latest server-clock observation. | Editing and renewal do not reset age. Reposting starts a new request. |
| Last check-in | Observed lease expiry minus the 90-second lifetime. | Delayed after 30 seconds; possible-expiry warning after 60 seconds. At the deadline, await a new server observation. |
| Last browser activity | Newest browser-observed interaction timestamp accepted for that live request. | Approximate interaction age, not proof of attention; does not itself renew the lease. |
The activity observer retains one page-memory timestamp for trusted pointer, wheel, touch and keyboard interaction, including mobile tap completion and scrolling that continues from trusted input. Each accepted scroll extends a two-second continuation window, so a continuous gesture is not cut off two seconds after its first input. It does not retain event contents, coordinates or a history, and ignores synthetic events, hidden-page events, visibility changes and automatic scrolling alone. Existing live-request/create/wingfam calls carry elapsed milliseconds; there is no event-triggered network call. The server bounds the age to seven days and ignores older same-request reports from other tabs.
Missing activity observations leave the stored value unchanged. Old clients show Not observed; missing clock values show Unavailable. Previous-request form history retains original request age but no ongoing heartbeat or activity metadata. Explicitly saved wingmatch details instead show a separate counterpart session status from the latest successful snapshot. The request’s activity field is removed with that request, although an already received snapshot or temporary final-match copy can retain it.
Concurrency and recovery
Page-level guards prevent overlapping routine polls and avoid applying a poll during a mutation. Matching calls time out after 12 seconds; failures and an aging last-success timestamp put the interface into a connection-problem state. Later checks, reconnection and page-return events refresh it. Map movement and radius changes ride the next routine poll, while explicit actions and cross-tab signals can trigger additional refreshes.
Hidden pages can continue request heartbeats even though they omit visitor renewal. Closing or suspending a page is therefore different from explicit withdrawal: another tab may still maintain the request, and the server lease governs unfinished availability. Finalized details use their fixed meeting deadline instead.
Revision history and change links
- Finalized-meeting retention and recovery updated in v122.
- Mobile activity observation and owner-only display cleanup updated in v112.
- Candidate recovery and Wingfam decline behavior updated in v102.
- Member controls and session lifecycle updated in v101.
- The current 10-second polling cadence was introduced in v9.
- Request age added in v70.
Multiple tabs in one browser
Ordinary tabs in the same browser profile share the site’s cookies and local storage. After a successful modifying action, Wingman writes a small change signal to local storage. Other tabs receive the browser’s storage event and schedule a server refresh. The signal contains timing information and a random marker, not a copy of someone’s request or photo.
A separate posting marker lasts 15 seconds and discourages another tab from starting a simultaneous new request. A tab only releases a marker it owns; an abandoned marker expires. This is deliberately lightweight coordination. Local storage does not provide an atomic distributed lock, simultaneous races are still possible, and unavailable storage does not prevent basic use. Separate browser profiles, devices and private-browsing contexts are not coordinated by this mechanism.
Each tab still keeps its own editing controls. The shared live request is synchronized through server snapshots; this is not collaborative character-by-character form editing. The database does not store a permanent browser identity for tab coordination.
Revision history and change links
- Introduced in v31.
Worldwide visitor presence
Presence measures browsers recently seen by the service. It supports a worldwide summary and nearby discovery without requiring someone to post a request. The design intentionally keeps this measure separate from the number of live requests: a browser with a request can appear in both populations.
| Count | Population | Boundary |
|---|---|---|
| Worldwide requests | Discoverable live requests, excluding satisfied ordinary requests. | Does not include visitors who are only browsing. |
| Worldwide viewers | Unexpired browser-presence records, including request owners. | A 90-second observation window; not an exact count of people looking at a screen. |
| Nearby viewers without requests | Located presence records in the search area with no associated live request. | Includes the viewing browser when eligible; returns an aggregate, not individual locations. |
Presence renewal and location
A visible homepage supplies a random UUID with its existing API calls. Ordinary tabs share that UUID and a 90-second reuse deadline in local storage under wingman-viewer. The server hashes the identifier and upserts a D1 row containing expiry, available location and a temporary request-session association. The server also atomically removes older visitor identifiers associated with the same request-session hash before recording the newest observation. This prevents a rotated identifier from leaving a second count at its previous IP-fallback location. Different sessions sharing an IP address or city remain distinct. With unavailable storage, a page-memory identifier remains stable only within that tab.
The request session and visitor identifier have different jobs. A hidden page omits visitor renewal even if it continues maintaining a request. The presence deadline is 90 seconds after the API request reaches the server, before geographic lookups. A lookup that finishes after that deadline cannot renew presence, and an older observation cannot replace a newer session location or identifier. Expired presence rows stop counting immediately and are physically removed on later API activity. A later visible visit replaces an expired local identifier; an inactive browser’s storage is not erased by a timer.
Without a live request, discovery uses the selected meetup point, then the device location, then the approximate IP area. Device detection can refresh discovery even when that point is unsuitable for a meetup. Nearby visitor counting uses the same great-circle distance and search area as requests, but returns only an aggregate. Coordinates are client-supplied, and neither the count nor a location label verifies physical presence.
Posted requests by city
The worldwide requester number opens a separate Requests by city popover. It groups the same discoverable requests used by the global total: expired requests and satisfied ordinary requests are excluded, while open Wingfams remain included. Grouping uses the approximate city of each selected meeting spot, regardless of the viewing browser’s search radius, preferences or current city. It does not infer the requester’s physical location.
Each snapshot reads the existing rounded-coordinate geography cache for those meeting spots, in batches of at most 80 keys. Missing labels count as City pending or unavailable. After the response, up to four missing cells and four missing city points are scheduled for resolution, with Worker-local in-flight deduplication; later snapshots can show the resulting labels. Resolution uses the same Photon city rules and cache lifetimes below, with no fallback to the browsing visitor’s IP city. Lookup failure leaves the request in the unknown total and respects the negative-cache cooldown. No extra request-person association or geographic history is stored.
Most requests and Nearest cities each return their own top 50 city groups plus an Other cities total. Displayed rows, hidden-row totals and unknown cities together account for the global request total. City distances use the browsing device/IP origin and public city points, never a precise distance to another person or meeting spot. The popover shares the viewer breakdown’s responsive layout, keyboard dismissal and focus return, while keeping its sort and open state independent.
City resolution and aggregation
The worldwide viewer number opens a city breakdown. When device coordinates are available, the server rounds them to a 0.001-degree cell (about 100 metres at the equator) and asks Photon for a city label. Genuine city, town or village fields and city-layer names qualify; districts and subdivisions do not become cities. If the first response has no usable label, a second query considers up to five city-layer features within 20 km and accepts the first matching the known country and state/province.
This produces an approximate nearby municipality/community label. Missing or failed device results fall back to Cloudflare’s IP geography; approximate IP coordinates can also be reverse-geocoded when hosting city labels are absent. The server ignores geographic labels supplied in the request body. Mobile networks, VPNs and incomplete map data can make fallback labels inaccurate, so missing results remain unavailable.
| Concern | Implemented behavior |
|---|---|
| Label cache | Rule-versioned rounded-coordinate keys in D1; successes 24 hours, negative results 30 seconds. Worker memory: up to 500 entries, at most five minutes. Shared in-flight work and a six-second uncached lookup budget. |
| City ordering | Server groups active viewers by city, region and country; returns up to 50 groups per order plus other/unknown totals. Most viewers uses count then alphabetical ties. Nearest cities uses distance, unknown distances last. |
| City distance | Straight-line distance from the supplied device/IP origin to a public representative city point. It is not distance to another viewer or to the selected meetup pin. |
| Popover layout | Shows up to eight rows as height allows. Matching country and region labels can be omitted visually while the group identity remains complete. The chosen sort is kept in page memory. |
Public city-center lookups use Photon searches constrained by the city, state/province and country identity. Only matching city-layer point results are accepted.
Their geographic-name keys store public coordinates and expiry, with 24-hour successful reuse and 30-second negative reuse. A three-second provider timeout and shared same-city in-flight work bound lookup effort; shared cleanup removes expired rows. No visitor identifiers are stored in that cache. Missing origins disable distance sorting, and missing city points show Distance unavailable. Distances use browser-locale measurement conventions, including an explicit measurement-system extension when supplied.
Presence labels expire with the visitor row; the reusable geography caches are independent of visitor identifiers. Separate devices/profiles count separately, shared browsers can represent several people, first-visit races without a shared session can briefly duplicate identifiers, and automated clients can inflate counts. The service does not fingerprint visitors or deduplicate by IP address.
Revision history and change links
- Session duplicate prevention and delayed presence updates corrected in v105.
- Search-center fallback corrected in v38.
- Worldwide request counts added in v4.
- Worldwide and nearby visitor counts added in v32.
- Posted-request meeting-city breakdown added in v106.
- Worldwide city counts added in v43.
- Persistent city-lookup caching added in v44.
- Broader city selection added in v45.
- Count and distance sorting added in v46.
Discovery and meetup agreement
Maps, location and business search
Location is a staged workflow: find a browsing origin, discover a candidate meeting place, validate that candidate, then publish it as a fixed point. Keeping these stages separate lets someone browse from home or recover from a location failure without silently advertising that location as a meetup.
| Value | What updates it | What it controls |
|---|---|---|
| Device estimate | Browser geolocation at startup and a watch while visible. A failed later reading retains the last precise coordinates in page memory. | Current-location marker, distance estimates and automatic suitability/business preparation. |
| Approximate IP area | Session-cached result or api.ipapi.is before a precise fix is available. | Fallback browsing origin and approximate-area display; never a replacement for an already acquired precise fix. |
| Selected meetup point | An explicit eligible address, business, saved location or map-pin selection. | The fixed public meeting address and coordinates when a request is posted. |
| Map viewport | Saved center/zoom, manual gestures, explicit selection and defined automatic framing. | Presentation only; moving or zooming the map does not change the meetup point. |
Device acquisition and map presentation
The geolocation watch stops while hidden and restarts on return. The browser and operating system determine delivery frequency; reported accuracy is an estimate. Once a precise reading has arrived, a later failure retains it and displays “precise location unavailable - using previously obtained location.” A fresh success clears the warning. A delayed IP lookup cannot replace a precise fix that arrived while it was pending.
Leaflet renders OpenStreetMap tiles, the search radius and interactive request markers. A blue device dot and accuracy halo remain visible at every zoom when their coordinates are inside the viewport, even if suitability is unknown or unsuitable. IP-only positioning uses a 30-mile approximate-area overlay instead. Search-radius and approximate-area shading use clipped 360-point geodesic polygons; distance eligibility still uses its own great-circle calculation.
| State or event | Framing behavior |
|---|---|
| No selected point; first precise fix | Overrides a saved wide IP view and centers the device, including when the fix precedes map initialization. |
| Current point allowed | Opens a close view up to zoom 17; widens for the accuracy halo and tightens as accuracy improves. Movement beyond 5 metres controls following. |
| Current check pending, unknown or failed | Uses the radius view until an allowed result has been observed; thereafter preserves the close view. Only a confirmed unsuitable result returns automatic browsing to the radius view. |
| Explicit address/business/history selection | Centers the checked pin at zoom 17. Later pre-posting device readings do not move or refocus that pin. |
| Double-click or double-tap pin drop | Checks the point and preserves map center, zoom and page position. A failed replacement preserves the prior selection. |
| Active request with device coordinates | Fits only the meetup and latest device point with 30-pixel padding, up to zoom 17. Fits immediately on activation/first fix, then at most every ten seconds with latest coordinates. |
| Active request without device coordinates | Keeps the meeting-pin view. IP coordinates are not used as the device endpoint. Withdrawal or loss of the request cancels pending fits. |
Touch pin placement uses two completed single-finger pointer taps, each lasting at most 350 milliseconds, with at most 350 milliseconds between taps and at most 24 pixels between their endpoints. Movement beyond 12 pixels, a held touch, cancellation, multiple fingers and interactive map controls or markers cancel the gesture. Desktop pin placement uses a native double-click; compatibility double-clicks within 600 milliseconds of touch input are ignored to avoid duplicate checks. Both paths use the same location and suitability checks. The map displays pending checks and pin-selection failures without moving the page or replacing a previously accepted point.
The saved main-map viewport is validated before restoration; storage failure does not block the map. Compact match maps use zoom 17. Active-request fits tighten as the device approaches the selected meetup point, leave manual pan/zoom available between updates, and do nothing when coordinates are unchanged. Fractional zoom reduces spare space on narrow screens. In the radius browsing view, routine device updates preserve the viewport; accuracy-only changes in the close view adjust zoom without deliberate recentering.
A five-second acquisition notice appears when a loaded map first receives fresh device coordinates or recovers from failure. Periodic successful readings do not restart it; selecting a meetup or showing the retained-location warning hides it. Distance from your meeting spot is shown only when a meeting point or active request exists. The address and distance sit above the map, and the legend identifies pins, requests and the device marker. The radius control supplies the shading’s distance. It spans ¼–50 miles on a gentle, shifted logarithmic scale, giving finer control nearby and gradually larger increases toward the upper end. Slider selections snap to ¼-mile increments through 5 miles, ½-mile increments above 5 through 10 miles, 1-mile increments above 10 through 20 miles, and 5-mile increments above 20 through 50 miles. Arrow keys move to the next or previous allowed distance, including across band boundaries; Page Up and Page Down move ten allowed steps, and Home and End select the limits. The editable number field stays synchronized in miles. A live match disables both controls.
Address autocomplete
The address field starts searching after at least three characters and a 450-millisecond pause. Enter or the search button submits immediately. The Worker sends the partial text to Geoapify, together with the detected browsing origin rounded to two decimal places when available. The provider receives both a proximity bias and a 50-mile circle bias, asking it to search nearby first and then worldwide. These hints affect ranking, not the separate 50-mile validation boundary; explicit city names remain in the query. An active query refreshes after the same debounce when a location first arrives or its rounded hint changes, including a change from IP fallback to device coordinates. Updates within the same rounded area and temporary loss of location do not trigger a request. Dismissal, successful selection and disabled search clear the active query; selection or another busy action defers refresh. Location refresh never moves the selected pin.
On phones, the field occupies a full row below Use my location; its aligned suggestion list scrolls within the available height. The field supports arrow-key navigation, Enter to choose, and Escape or an outside click to dismiss. New input cancels the previous browser request and invalidates its completion. Dismissal also cancels pending debounce work and prevents a late response reopening suggestions. Selecting an entry follows the existing exact-point suitability flow below. An active match or locked Wingfam disables search. Successful selection fills the address without starting another search.
Geoapify labels and coordinates are used as returned. The server requests up to ten candidates and returns up to five valid, permitted suggestions. It orders nearer results within groups sharing result type, house-number presence, provider match type and confidence values rounded to five-percentage-point bands (overall, city, street and building when supplied). Other relevance groups retain their provider-ranked slots; missing overall confidence preserves provider order. Distance is measured from the rounded origin. No radius filter excludes a city or ZIP explicitly included in the query. This is nearby preference among returned candidates, not a guarantee of the nearest address in the world. A street-only result is labelled as such; a typed street number is never attached to a street centroid. House-number coverage varies, and even a numbered address can have approximate coordinates. Search recognition does not approve a residential or otherwise unsuitable place. Service failures show an inline retryable message without changing the prior pin or becoming cached rejections. Search calls time out after 12 seconds in the browser and 8 seconds at the provider. The address route permits 60 requests per IP-derived key per minute, including reverse lookups.
Nearby business selection
The dropdown is a user-activated discovery tool. Its center is the selected meeting point when present, otherwise the detected browsing origin. Distance choices are constrained by the search radius; the control asks for at least a one-mile radius when no business-distance option is available. Both text suggestions and the nearby list use Geoapify. The list calls the Places API for named shops, catering venues and cinemas, with a circle filter at the selected radius and a proximity bias at the center. The browser sends that center rounded to five decimal places; this can be a selected meeting spot, device estimate or IP fallback. Three parallel category requests return up to 100 candidates each; Wingman removes duplicates, invalid points, out-of-radius places, known adult venues and places marked private or inaccessible, then returns up to 30 in distance order. This is a bounded discovery list, not an exhaustive inventory or an opening-hours check. Photon continues to supply reverse labels and city resolution.
Sequence · Discovering and selecting a nearby business
Read from top to bottom. Dashed arrows are responses. Choose the diagram or its text version below. Wide diagrams keep sideways scrolling when fitting would make their labels too small.
Diagram
Text version
- Browser → Worker / caches: Open list or change distance: request businesses
- Use a fresh business-list result, or coordinate one lookup for the provider, center, radius and category key.
- Worker / caches → Geoapify / screening: On cache miss, query Geoapify Places within the radius
- Geoapify / screening → Worker / caches: Return up to 100 named place candidates
- Remove duplicates, private/adult venues and out-of-radius results; sort nearest first, limit to 30.
- Worker / caches → Browser: Return choices; an empty list is valid
- Main-page selection: keep choices open and show a spinner over the clicked row. Reuse an unexpired exact-point decision; cached unsuitable choices are disabled.
- Browser → Worker / caches: On browser cache miss: check exact business point
- Worker / caches → Browser: Return eligibility decision or unavailable
- Latest allowed result: populate the meeting form, fix and focus the pin, and close choices. Unsuitable: cross out and disable the row until decision expiry. Service failure: retain retryable choices and show its reason. Preserve the previous pin on failure; ignore superseded responses.
The browser’s list cache lasts two minutes, uses the center rounded to five decimal places plus distance, and clears when it reaches ten entries. Opening a stale list, changing distance or changing center after activation can fetch again. A failed lookup clears the list, closes it and schedules another attempt about every two minutes while enabled; the browser can delay that retry. Try nearby businesses again starts an immediate retry and is disabled while loading or another action is busy. The list re-enables after a successful response, including a valid empty result. Address search and manual pin selection remain alternatives. The Geoapify request has an eight-second timeout; the browser request has a twenty-second timeout. The route permits ten lookups per IP-derived key per minute.
Adult-entertainment categories and recognizable names are filtered from business/address results and obvious submitted addresses. Incomplete tags or neutral names can evade that screen. A candidate’s exact coordinates still pass the independent suitability check before becoming a meeting point.
Preparing “Use my location”
This button selects a prepared mapped business; it does not use a raw device coordinate as an automatic meetup. Current-location suitability samples the latest device point initially and about every 30 seconds while visible. Only a current allowed result enables automatic business preparation. Unsuitable, checking, unknown and unavailable states suppress preparation but do not disable explicit discovery tools.
Sequence · Preparing a business before the click
Read from top to bottom. Dashed arrows are responses. Choose the diagram or its text version below. Wide diagrams keep sideways scrolling when fitting would make their labels too small.
Diagram
Text version
- Gate: precise or retained device coordinates, allowed current-location check, no active match or locked wingfam.
- Browser → Current-business API: Sample latest point; prepare nearby business
- Query within a fixed 50 metres. Try only the nearest candidate, not every listed business.
- Current-business API → Suitability service: Check associated public main entrance if present
- Suitability service → Current-business API: Allowed + business, or try mapped center
- Current-business API → Suitability service: If needed, check candidate node / center
- Suitability service → Current-business API: Return allowed + business, rejection or error
- Current-business API → Browser: Return checked selection or no ready business
- Only a later user click copies the prepared selection to the fixed meetup pin; the click starts no lookup.
This separate preparation path continues to use Overpass for venue geometry and entrances. For a mapped way, an associated public entrance=main within the same radius is considered before its center. A checked point must be both allowed and identified as a business. A no-business result or failure leaves the button disabled; a delayed response never selects a pin automatically. A ready result can still be used with retained precise coordinates, with the stale-location warning visible.
The latest current-location classification and prepared business/no-business result each have a page-memory reuse window of up to 24 hours within 5 metres of their lookup origin. Movement beyond that tolerance or expiry causes work at a later sampling step. Failures wait at least 30 seconds before retry on a later device update; obsolete requests are cancelled. Active matches and locked wingfams suspend this work. This tolerance is for presentation and reuse of a checked canonical business point, not approval spreading to nearby raw coordinates.
Current-location notices appear beside the controls only while no meeting point is selected. Once a point is selected or restored, its own status takes that position. Diagnostics keep the separate results available. Pending replacement preparation does not keep showing an old business as newly ready.
Meeting-point suitability
The server validates coordinates when creating or updating a request and when reopening a wingfam. The main meeting-point controls require a successful suitability decision before accepting a point. A still-valid exact-point approval already obtained in this page can satisfy that check; otherwise they call /api/meetup-location with a 15-second browser timeout per attempt, including when checking restored coordinates. A network error, server 5xx response or timeout gets one automatic retry after one second; the total wait can reach about 31 seconds, or longer if the browser suspends timers. Validation failures, rate limits and suitability rejections do not retry. A saved draft or history address never restores an approval by itself. Wingfam setup retains its separate confirmation and server-screening step when reopening.
Selecting a result beneath the address/business text field or choosing a nearby business on the main page keeps its result list open and shows a loading spinner over the clicked row. Further choices in that list wait for the check. Text-search input and submission wait while a text result is checked; the nearby-business distance control waits while a nearby result is checked. Success populates the meeting address, pins that location and closes its list. Clicking or tapping outside text-search results, or pressing Escape, dismisses them. Dismissing a pending text selection invalidates its completion so it cannot change the form or reopen the list. A confirmed unsuitable result removes the spinner, crosses out and disables that row, and shows its reason. The row stays disabled while the exact-point rejection is cached. Service errors and timeouts remove the spinner and leave choices available to retry with the returned error. A check that reaches the browser’s time limit shows “request timed out”. If no error message is available, it asks the visitor to retry the check. A rejected point never becomes the selected pin. A newer location selection supersedes an older response, so the older result cannot move the pin or reopen the picker with a stale failure.
Sequence · Acquiring evidence for a suitability decision
Read from top to bottom. Dashed arrows are responses. Choose the diagram or its text version below. Wide diagrams keep sideways scrolling when fitting would make their labels too small.
Diagram
Text version
- Business selection: keep dropdown open with progress over the clicked row. A valid page-memory decision for these exact coordinates skips the request.
- Browser → Worker / D1: If needed, check exact coordinates (15 s per attempt)
- Fresh exact-point cache hit: return its decision. Otherwise share local work and acquire / wait on the D1 lookup lease.
- Lease owner rechecks cache, then reuses suitable nearby geometry if available; classify this point independently.
- Worker / D1 → Overpass providers: If geometry is unavailable, query mapped areas
- Fallbacks start after 1 s and 2 s, or sooner after failure; cancel remaining work on a complete response.
- Overpass providers → Worker / D1: Complete geometry, or provider failure
- Complete data: classify and cache allowed/rejected result. Transport error or partial response: do not cache a decision.
- Worker / D1 → Browser: Return decision + provenance, or unavailable
- Browser transient failure: wait 1 s and retry once. Rejections and 4xx responses do not retry. Keep progress visible until the final outcome.
- Latest success: fill form, pin and close picker. Confirmed unsuitable: cache decision, cross out and disable row. Service failure: leave choices retryable and show the actual error. End progress in every case; superseded results do not change the pin.
- Manual replacement failure keeps the old pin. Failed restored-point validation keeps the form locked.
Decision flow · Classifying one coordinate
Start only after complete map data is available. Evaluate these tests in order; an outcome ends classification.
Diagram
- 1Adult venue, residential building or private access at the point?Yes → RejectNo → Check the public venue
- 2Named eligible public-facing venue at the point?Yes → Allow; determine business flagNo → Check the containing land use
- 3Unsuitable land use, military area or water?Yes → RejectNo → Check commercial classification
- 4Mapped commercial or retail area or building?Yes → Allow (business flag not implied)No → Reject as unconfirmed
Text version
- Adult venue, residential building or private access at the point?
Yes → Reject. No → Check the public venue. - Named eligible public-facing venue at the point?
Yes → Allow; determine business flag. No → Check the containing land use. - Unsuitable land use, military area or water?
Yes → Reject. No → Check commercial classification. - Mapped commercial or retail area or building?
Yes → Allow (business flag not implied). No → Reject as unconfirmed.
The classifier considers containing land use and footprints, access, adult-venue tags and eligible public-facing venues. A public venue represented as a node must be within about 15 metres; footprints use point-in-polygon checks. The business flag is narrower than general eligibility, which is why a commercial area alone cannot enable the front-door recognition option.
Until a point is selected and checked, the form is inert and an overlay directs the visitor to location controls. Saved-request selection remains available above it. Notices use neutral for work in progress, green for a completed ready/eligible result, yellow for an unsuitable or missing point or no suitable business, and red for an unavailable or failed check. Text carries the meaning independently of color. Selecting a point unlocks editing; it does not post a request.
Overpass requests use Private.coffee, overpass-api.de and VK Maps (maps.mail.ru), with a ten-second client timeout per provider; suitability queries permit eight seconds of server execution. HTTP errors, timeout remarks and incomplete data are not classification results. These checks send coordinates and query instructions, not names, photos or plan text. Eligibility is based on mapped data and can have false positives or negatives; it is not an inspection, opening-hours check or guarantee of safety.
Cache boundaries and coordinated lookup ownership
Caching reduces repeated provider work while preserving each decision’s spatial boundary. Positive reuse windows are deliberately finite, and a read never extends the original server expiry. The following layers answer different questions.
| Layer / key | Lifetime | Limits and reuse rules |
|---|---|---|
| Geoapify autocomplete: ranking-policy version + provider + normalized Unicode/case/whitespace + two-decimal proximity hint; Photon reverse: exact coordinates | Nonempty and valid empty results: 24 hours. | D1 plus up to 500 Worker entries. Preserve city names and the remaining query. Separate provider keys prevent reuse of old Photon text results. No cached credentials. A selected result still needs suitability validation. |
| Browser selected-point approvals and rejections: exact latitude and longitude | Until the original server expiry, up to 24 hours; never renewed on reuse. | Up to 100 approvals and 100 rejections in page memory, cleared on reload. Expired entries are ignored and removed on lookup or evicted at the cap. Rejection reasons disable matching rows in both text-search results and Nearby businesses; a one-second display refresh while the list is open releases expired rows without a network call. No localStorage writes or cached transport errors. Missing expiry prevents reuse. |
| Suitability: exact coordinates + rule version | Allowed and rejected results: 24 hours. | D1 plus up to 500 Worker entries. Never spread approval to a neighboring point. |
| Reusable map geometry: source point + rule version | 24 hours; derived decisions inherit source expiry. | D1 plus up to 100 Worker entries. Reuse within 5 metres only with supported closed-way boundaries; relevant relation geometry or missing boundaries prevents reuse. |
| Geoapify business list: provider/rule version + hash of center, radius and categories | Successful lists, including valid empty lists: 24 hours. | D1 plus up to 100 Worker entries. Store normalized businesses, never credentials. The five-decimal browser origin is part of the key; changing radius or origin cannot reuse another list. |
| Current-business raw data: hash of complete Overpass query | Complete results, including valid empty results: 24 hours. | D1 plus up to 100 separate Worker results. Venue geometry and associated entrances support Use my location; provider-specific keys keep these records separate from Geoapify lists. |
| Browser current-location / prepared-business result: lookup origin | Up to 24 hours within 5 metres; failures have a 30-second retry cooldown. | Latest result in page memory. No localStorage suitability cache; selected meetup points are validated independently. |
An uncached suitability, address or business lookup acquires a 20-second D1 lease keyed to that query. The owner rechecks the cache and publishes only while its random owner token remains current and unexpired. Waiters recheck with delays from 150 milliseconds to one second plus up to 99 milliseconds of jitter, with a 12.5-second deadline. During their first 1.5 seconds they can acquire a released lease; afterwards they only await a result or return a retryable error.
The owner releases on failure without caching the error. A late owner cannot overwrite a replacement’s result or release its lease. A crashed owner stops blocking acquisition after expiry; coordination-storage failure falls back to per-Worker sharing and provider work. This reduces duplicate calls but does not guarantee exactly-once execution. A matching action’s shorter client timeout may end the browser wait while server work continues.
Provider errors and malformed/incomplete results are not cached. D1 caches have no separate row-eviction cap, but storage is finite. Expired entries are ignored and removed by shared background cleanup rather than a deletion timer. Cache failures can fall back to provider checks. Reusable coordinates can still describe sensitive places, and map data may remain outdated until expiry. Address/business HTTP responses use no-store instructions; browser-list and page-memory reuse are separate application caches.
Classification follows the provider’s Overpass query semantics and OpenStreetMap land-use tags.
Business categories are requested independently in three parallel Geoapify calls (shops, food/drink and cinemas), each limited to 100 candidates. The combined list is deduplicated, distance-filtered, sorted and capped at 30. All three responses must succeed before the result is cached; a failed category cannot silently create a partial cached list. The v2 cache namespace retires the earlier combined-category results.
Clear meeting point is available beside the selected address above the map and in the selection notice. Both controls reset the draft pin, address-search text, selected-point approval and pending selection state, dismiss suggestions and return focus to address search. A selection sequence prevents a late check from restoring the cleared point. Cached suitability results and other form details remain. Clearing is disabled while busy, during a match or while a request is posted; withdraw the posted request first.
Revision history and change links
- v125 — Direct touch pin gestures and on-map selection feedback.
- Separate category searches and merged ordering corrected in v124.
- Draft-point clearing, Geoapify nearby lists and immediate retry added in v123.
- Provider nearby-first circle preference strengthened in v121.
- Location refresh and nearby relevance ranking added in v120.
- Phone autocomplete layout refined in v119.
- Geoapify autocomplete and provider-separated caching added in v118.
- Text-search result feedback corrected in v116.
- Clicked-row progress and cached unsuitable choices added in v115.
- Actual business-selection errors and one transient-failure retry added in v114.
- Business selection progress, recovery and browser approval reuse added in v113.
- Address search and pins launched in v1.
- Nearby businesses added in v15.
- Radius-aware business distances added in v20.
- Logarithmic radius slider added in v92.
- Radius curve softened and quarter-mile snapping added in v94.
- Distance-dependent radius increments added in v95.
- Adult-venue screening added in v31.
- Map labels simplified in v67.
- Close-zoom rendering improved in v42.
- Precise-location initialization revised in v53.
- Independent device centering added in v54.
- Phone framing and acquisition notices refined in v55.
- Device-marker visibility and selection zoom updated in v62.
- Periodic device refresh introduced in v41.
- Continuous walking updates added in v55.
- Click-only business selection and retained-location recovery added in v52.
- Prepared business selection added in v55.
- Current-location notice visibility refined in v68.
- Shared suitability caching introduced in v48.
- 24-hour results, geometry reuse and earlier provider fallback added in v51.
- Meeting-point screening introduced in v34.
- The required-point form overlay was added in v39.
- Suitability notice styling added in v65.
- Provider recovery improved in v56.
- Cross-Worker deduplication added in v58.
- Shared text-search caching added in v56.
- Shared query caching and deduplication added in v58.
- Valid empty-result caching extended in v60.
Meeting times and travel radius
A request can express either a scheduled appointment or a rolling willingness to meet after a travel allowance. The distinction matters because discovery compares proposed times, while finalization must produce one fixed time that both participants can rely on reviewing.
| Mode | Stored meaning | Validation and finalization |
|---|---|---|
| Specify a time | Absolute timestamp, entered in 15-minute increments with AM/PM controls. | Must be between server now and 12 hours ahead. The browser chooses the next local occurrence of the clock time; there is no 6 AM cutoff. |
| Now / travel time | Reserved meet_at = 0 plus meet_delay_minutes. Now is zero; nonzero delays use five-minute increments. | Discovery evaluates now + delay. Finalization records a fixed final_meet_at; later heartbeats and phone updates preserve it. |
When both requests are relative, finalization uses the larger selected delay regardless of the chosen meeting location: 10 and 25 minutes produce a meeting 25 minutes after finalization. Two Now requests finalize for the current time. If only one request is relative, the selected meeting plan supplies the time. There is no separate grace allowance. Before mutual finalization, a changed plan or withdrawn readiness clears the tentative time. Once finalized, the plan must be cancelled to change it.
A fresh scheduled selection starts at the next whole hour in the browser’s local time. Restored scheduled drafts start at that next hour. Relative drafts retain Now / travel time, their arrival allowance and transportation choice. Automatic draft restoration never restores an absolute meetup clock time or date; the controls and calendar-date label use the same selected timestamp, including a selection after midnight. A recovered live request instead takes its meeting time from the server snapshot. Relative controls also display the local arrival date and time from the current observed clock plus the selected delay. The existing one-second display tick keeps this label current without adding network calls. Adding the delay before local formatting handles midnight, year and daylight-saving transitions.
Deriving a travel allowance
The duration dropdown precedes transportation. Choosing Now selects Already there, and Already there returns the duration to Now. Moving to a nonzero duration initially selects driving; walking, cycling and public transit are also available. The browser and posting API calculate the available range from straight-line distance between supplied detected coordinates and the meetup point.
| Mode | Lower estimate | Upper estimate |
|---|---|---|
| Walking | 20 | 25 |
| Cycling | 4 | 8 |
| Driving / rideshare | 1.5 | 5 |
| Public transit | 3 | 12 |
The lower estimate is rounded up to five minutes, then adds five minutes. The upper estimate is rounded up to five minutes with a ten-minute floor, then adds two five-minute increments. Both bounds are capped at 720 minutes. The dropdown offers Now and the resulting nonzero range; these heuristics shape the choices and add nothing to the delay after selection. They are not route calculations, traffic predictions or transit timetables.
Changing mode or meeting point can adjust an unsaved duration into range. A live request keeps its stored value until updated. The server rejects out-of-range nonzero submissions; wingfam reopening clamps a reused allowance for its confirmed point. Saved requests retain mode and delay. This calculation reuses coordinates already supplied for the 50-mile posting check and adds neither a routing provider nor a stored journey origin.
Expiry and distance
Relative requests have no appointment-age expiry, but still expire after 90 seconds without server renewal. An unmatched scheduled request becomes unavailable as soon as its meeting time arrives, even if its browser continues checking in. It is not advanced automatically. The next successful snapshot notifies an open page that the meeting time has passed and asks the visitor to submit a new request with a new time or Now / travel time. Network failure or browser suspension delays that notice.
An invitation or arrangement keeps the existing 90-second renewal requirement and scheduled-time-plus-one-hour cutoff. Once finalized, both ordinary request rows and the private connection use the locked final_meet_at plus exactly 60 minutes, regardless of either original proposal or missed check-ins. This applies equally to scheduled and formerly relative plans. Reading, saving and polling never move that deadline. Wingfam availability still needs check-ins; an expired host lease stops new public discovery and admission while existing finalized connections remain recoverable until their own deadlines.
Search radii range from a quarter mile to 50 miles. With unequal radii, one person may be able to travel to the other’s place even when the reverse is not allowed. Matching and finalization therefore validate the selected place rather than assuming both directions are interchangeable. Posting also requires the chosen point to be within 50 miles of the supplied detected location.
Form and overview layout
Required fields use an asterisk with a nearby legend: name, adult age, gender response, matching preference, plan, timing, meeting point and radius. Prefer not to say is a valid gender response. A photo is optional. Browser controls provide immediate feedback; server schemas independently validate the submission.
- Age requests a whole-number numeric keypad, radius a decimal keypad, and phones a telephone keypad. The device decides the actual keyboard.
- Reset form and Clear cached form data share a centered row immediately above Let’s meet at, separated by a middle dot. Reset clears the editable form and photo; Clear cached form data removes only the four cached profile fields, leaving the displayed form, photo and previous-request history unchanged.
- The radius value and unit stay together. Above 800 pixels, the radius panel and worldwide summary use the same column proportions, 23-pixel gap and centered content width as the meeting-spot and request panels below. Their left edges align with those panels, and text wraps within its column.
- At 800 pixels and below, the overview retains its content-aware wrapping: the whole summary moves below the radius panel with a 20-pixel gap when both blocks cannot fit.
- Worldwide counts break after “and”. The reminder keeps “Stay here. Stay on the radar.” together, followed by separate lines for staying open and the unfinished request’s 90-second reopening window; narrow layouts can wrap naturally.
Revision history and change links
- Finalized-meeting retention and recovery updated in v122.
- Form-cache controls and scheduled-request expiry updated in v117.
- Fresh local defaults and calendar-date updates fixed in v108.
- Relative timing added in v37.
- Timing layout refined in v68.
- The separate grace allowance was removed in v40.
- Control order and Already there added in v39.
- Distance-based starting durations and two extra upper choices added in v46.
- Required markers and Prefer not to say were added in v32.
- Mobile keypad hints added in v71.
- Radius control alignment refined in v77.
- Content-aware overview layout added in v80.
- Summary line breaks added in v81.
- Wide overview aligned with the panels below in v88.
- The former tomorrow-before-6-AM restriction was removed in v34.
- Auto-advance added in v10.
Matching and mutual agreement
Matching reserves two live requests while their owners agree on a place and a recognition plan. Discovery supplies candidates; the server independently checks compatibility and owns the state transitions. An ordinary request or guest has one match at a time; an auto-allow Wingfam host can admit multiple guests.
Posting, choosing and retrying
A posted request remains its own server record. The snapshot returns all compatible candidates independently of the map viewport. Several candidates open a chooser with a separate Try matching and Decline control for each; one opens the wingmatch workflow directly; none returns to the main interface with the request active. A choice is revalidated on the server before reservation or automatic admission.
Diagram
Text version
- Post or update an active request
- Compute remaining eligible requests: several → chooser; one → wingmatch; none → active request on radar
- Try the selected person, or decline and recompute the remaining choices
- Unsuccessful unfinished attempt → return remaining candidates if the request is still live; an elapsed scheduled request needs resubmission
- Finalization → satisfy ordinary requests; preserve an existing Wingfam host
Compatibility and reservation
Compatibility requires mutual gender preferences, proposed meeting times within one hour, and at least one permitted meeting location. Relative times are evaluated as now plus the selected allowance. Wingfam admission additionally requires exactly identical selected latitude and longitude and both effective meeting times between now and one hour from now, inclusive. A same-name address or nearby pin does not satisfy this rule; a later request remains separate. These admission limits also apply to automatic joins and queued admission. Distance uses a great-circle calculation, not a driving route. The “Looking for” preference supports comfort and personal safety in platonic company; each person must satisfy the other’s choice or the other must select Anyone.
A fresh/reset form defaults to Prefer not to say, while a valid saved response is restored. That response does not satisfy a specific-gender preference: the other visitor must choose Anyone. Self-reported gender does not verify identity or guarantee safety.
An ordinary invitation creates the match and both participant rows in one atomic database batch. Each request’s unique participant key is its reservation lock: a competing batch rolls back completely if either slot is already held. A later compatible attempt on the posted-request owner joins a server waiting record instead. Repeated attempts from the same request count once; a request can wait on one target at a time. Unfinished busy requests remain discoverable so a visitor can express interest while a match is in progress. Finalized ordinary requests are satisfied and excluded from discovery and request counts immediately; their underlying rows remain temporarily for the pair’s details exchange and normal completion cleanup. An active Wingfam host stays discoverable. At widths of at least 1,000 pixels, radar results occupy a scrollable region below the map beside the request panel; on smaller screens they follow the form in normal page flow. The heading remains visible and the results region is keyboard-focusable.
| State | Entry / permitted work | Failure or return path |
|---|---|---|
| Invited | The invitation reserves both requests; participants can accept or cancel. | A later attempt waits behind the posted-request owner’s match. Cancel-one can advance the owner’s queue; an unfinished failed attempt otherwise preserves requests and returns remaining candidates. Cancel-all closes the cancelling request. |
| Arranging | Both accept, agree on an allowed address and save recognition details. | A plan edit resets both ready flags and advances the plan revision. |
| One participant ready | Readiness refers to the reviewed plan revision. | A stale revision, invalid place or recognition conflict prevents readiness. Confirmed cancellation ends this match; an owner with a queue can advance to the next eligible guest. |
| Final | Both ready for the same plan; server locks the meeting timestamp. | Finalization hides ordinary requests from discovery. Passive closure preserves the final screen until meeting time plus 60 minutes. Saved dismissal preserves its card; confirmed unsaved deletion or withdrawal ends the connection sooner. |
Meeting finalization and its view
Ordinary finalization is a mutual confirmation protocol. Auto-allow uses the Wingfam owner’s prior explicit consent and fixed details. Before marking ready, the page saves pending recognition details. The server then checks that the submitted plan revision still matches, that both requests remain live, that the selected address is permitted and that recognition requirements are met. Conditional database updates prevent a stale screen from silently confirming a revised plan.
Sequence · Two participants finalize one plan
Read from top to bottom. Dashed arrows are responses. Choose the diagram or its text version below. Wide diagrams keep sideways scrolling when fitting would make their labels too small.
Diagram
Text version
- Both accepted; both chose the same permitted meeting point.
- Participant A → Worker / D1: Save pending recognition details
- Worker / D1 → Participant A: Return current plan revision
- Participant A → Worker / D1: Ready: confirm reviewed plan revision
- Validate live participants, place and recognition. Reject stale revision or two “I’ll find you” choices.
- Worker / D1 → Participant A: Record A ready; wait for B
- Participant B → Worker / D1: Poll / review plan; confirm ready
- If plan details changed: clear both ready flags and require review again. Otherwise second readiness locks final_meet_at.
- Worker / D1 → Participant B: Return finalized match view
- Participant A → Worker / D1: Next successful state poll
- Worker / D1 → Participant A: Return same finalized plan and time
The finalized view presents the agreed place, locked time and recognition instructions. The server preserves each participant’s private final screen and its fixed deadline. Reopening in the same browser checks authenticated server state before displaying recovered details, even if the other participant already saved and dismissed. A local recovery copy follows the same meeting-time-plus-60-minute deadline and cannot override withdrawal or server expiry. Reopening requires connectivity and the session cookie; installing the app does not add an offline matching engine.
Recognition and optional contact exchange
Recognition mode is stored separately from text, with older records defaulting to a written description. Participants can describe themselves, choose I’ll find you, or choose Meet outside the front door. The latter requires a mapped named business at the agreed point; the browser checks before enabling it and the server checks again when saving or finalizing. A commercial district alone is insufficient, and map data does not identify a guaranteed accessible entrance.
The partner’s I’ll find you choice is visible before finalization, while private written recognition details remain for the final exchange. If both choose I’ll find you, both pages explain the conflict and the server refuses readiness until one changes their choice. Changing recognition mode or details resets both ready flags and increments the plan revision.
Phone exchange is optional. Numbers are parsed and validated. Ordinary pairs reveal numbers only when both supply one. A Wingfam owner explicitly agrees to share their supplied number with approved guests even if those guests provide none. Auto-allow expands this disclosure to everyone browsing the live Wingfam. Phone updates after finalization preserve the locked meeting time. The service processes these details; this is not end-to-end encrypted messaging.
Completion and wingfam continuation
Explicitly dismissing a finalized view without a saved copy first asks whether to save or delete. Confirmed Delete ends the connection for both people and revokes their saved copies. Saving then dismissing records a per-participant dismissal and removes completed ordinary request rows, while the private connection and saved radar card remain available until 60 minutes after the meeting. The other person’s undismissed final screen can still be restored from the server. A failed dismissal keeps the screen open. Queue decisions remain explicit: an owner with waiters can keep the match, cancel one and advance, or cancel all. Closing the browser, navigating away or locking a phone sends no completion or withdrawal action.
Only the owner of the posted request receiving the invitation can activate Wingfam. The permission is bound to that owner’s session-token hash and lasts through the finalized meeting deadline. It can survive ordinary request cleanup. Explicit owner dismissal closes the permission; a copied permission does not authorize a different session. Reopening requires explicit location confirmation, current eligibility or a valid cache result, and a point within 50 miles of the supplied browsing origin. The host’s address is fixed and guests must be able to travel to it.
Waiting notices and close confirmations
The posted-request owner sees a singular or counted waiting notice during the invitation, arrangement and finalized views, with a suggestion to consider Wingfam. Waiters receive only their waiting status and the eventual outcome, never the pair’s recognition details or phone exchange. The count excludes expired requests and people who have since entered another match. A new invitation to another target replaces the old wait; joining a match clears it.
Sequence · Competing wingmatches and the owner’s decision
Read from top to bottom. Dashed arrows are responses. Choose the diagram or its text version below. Wide diagrams keep sideways scrolling when fitting would make their labels too small.
Diagram
Text version
- Wingmatchers → Worker / D1: Concurrent invitations for the same posted request
- One batch claims both unique participant slots. Later compatible attempts become deduplicated waiters.
- Worker / D1 → Wingmatchers: Winner receives match; later attempts receive waiting status
- Request owner → Worker / D1: Poll current invitation, arrangement or final view
- Worker / D1 → Request owner: Return waiting count and requester-only Wingfam control
- Owner can keep this match, cancel one and advance FIFO, or cancel all. After finalization, only the owner can enable manual or automatic Wingfam.
- Request owner → Worker / D1: Confirm chosen continuation or closure
- Wingmatchers → Worker / D1: Next successful state poll
- Worker / D1 → Wingmatchers: Return next match, Wingfam admission or closed-request outcome
For a queued owner, closing or cancelling presents Keep this wingmatch, Cancel this one & move to next, and Cancel all & close my request. Cancel-one chooses the earliest still-compatible live waiter by queue time and insertion order. A database batch transfers the reservation and remaining queue; the owner must approve the new match. If the former partner already completed cleanup, an authenticated open permission can restore the original owner request for this transition. A stale cancellation cannot cancel the replacement match. Cancel-all ends the owner request and reports closed-without-Wingfam to waiters. Without a next eligible waiter, ending an unfinished attempt returns remaining candidates if the request is still live; an elapsed scheduled request instead expires. Explicit cancel-all and cancellation of a finalized ordinary match close the cancelling request and require a new submission.
The server checks waiting state again when recording a close, including within the conditional permission update. An arrival after the browser’s last poll cannot silently bypass confirmation. Atomic permission decisions prevent close from overwriting a successful Wingfam activation. Hiding or leaving a page only saves the bounded recovery copy. Passive page lifecycle events never express a no-Wingfam decision or withdraw finalized connections. Outcomes arrive on successful polling, with no background-push guarantee.
Manual and automatic Wingfam admission
Wingfam setup requires explicit agreement to the sharing scope. In manual mode, approved guests receive the owner’s meetup description and supplied phone even without offering their own. Auto-allow uses the owner’s chosen address, fixed meeting time, written recognition description and supplied phone for subsequent compatible invitations. These details are also public in the live Wingfam listing, including to the original partner through their authenticated continuation. The owner sees this disclosure before enabling it. Auto-allow requires a written description and rejects the front-door and I’ll-find-you presets in both setup and server validation.
An auto-allow join atomically creates a finalized guest match, reserves only the guest’s unique participant slot and writes one owner notification. The host can therefore admit multiple guests without serializing each behind a host slot. Retrying the same live join returns the existing match without another event. Auto-allowed guests are explicitly told how they were admitted. Host details cannot be edited in that mode; the host must close the Wingfam to replace them.
Declining a Wingfam
A candidate decline or a guest cancelling a Wingfam attempt preserves the guest request and the host, excludes that pair and returns the guest to remaining candidates. Manual-approval hosts receive a dismissible notice with the matcher’s display name. Auto-allow hosts receive no decline notice. If an auto-allowed guest declines after admission, the connection and any unread admission notification are removed quietly. Ended saved-session state revokes saved details and rejects later saves, with the owner’s ordinary ended-match toast suppressed for this decline. Delivery and removal require an updated browser’s successful snapshot; external copies cannot be recalled.
Wingfam members and optional saving
The owner’s compact, scrollable members list uses finalized session records, not saved cards or unread notifications. It includes the original match when Wingfam is enabled and later admitted matches, even after guests close their modal or the owner dismisses a toast. Each row has a name, cautious session-status label and confirmed Unmatch control. It does not claim that listed people are physically present. Saving is never automatic.
Unmatching a member preserves that Wingfam and its other connections. A request-scoped exclusion associates the host request ID with the unmatched partner’s session hash, preventing that same session from immediately rejoining. The exclusion is removed with that host request. It is not an identity ban: a new browser/session can evade it, and public auto-allow meetup details remain visible in the public listing.
The owner must keep checking in to continue admitting guests. Explicitly closing the auto-allow dialog or using Close Wingfam & stop auto-allow withdraws the host and revokes its retained connections. Merely leaving the browser sends no withdrawal. After 90 seconds without renewal, public availability and new admissions stop; existing finalized details survive until their fixed deadlines. Retained rows support those private connections without keeping an offline host in discovery. Received external copies cannot be recalled.
Revision history and change links
- Finalized-meeting retention and recovery updated in v122.
- Candidate recovery and Wingfam decline behavior updated in v102.
- Member controls and session lifecycle updated in v101.
- Waiting, ownership and lifecycle behavior reviewed in v99.
- Responsive radar layout added in v81.
- Mutual matching launched in v1.
- Asymmetric travel support added in v4.
- Mutual gender preferences launched in v1.
- The default gender became “Prefer not to say” in v38.
- Recognition choices introduced in v34.
- The finalized-view phone exchange was added in v12.
- Wingfam reopening introduced in v16.
Data lifecycle and recovery
What is stored where
The storage design separates authoritative shared records from browser conveniences and reusable provider data. Expiring a live request ends its availability; it does not automatically erase every local draft or cached lookup related to the page.
| Layer | Contents | Lifecycle / access boundary |
|---|---|---|
| Page memory | Address suggestions and cancellable searches, edits, snapshots, last interaction timestamp, diagnostics, provider logs, in-flight work, device readings, prepared business, bounded exact-point suitability approvals/rejections and fallback session token. | Lost on a full reload; not a durable account. |
| HttpOnly cookie | Temporary request-control capability. | Up to 13 hours for an unfinished request; finalized responses retain the latest meeting deadline; ordinary page JavaScript cannot read it. |
| Local storage | Place/timing draft, four-field profile cache and processed photo; up to five previous requests; theme/sound/haptic preferences; viewport; visitor identifier; tab markers; final-match recovery/dismissals and ended-match markers; up to 30 explicitly saved wingmatches; photo association. | Place/timing drafts expire 12 hours after their last changed save. Profile fields expire 30 days after the latest successful request submission; other records have separate lifetimes. Accessible to someone using the same browser profile. |
| Session storage | Approximate IP browsing area. | One-hour reuse window within the tab session; separate from server city caches. |
| D1 | Requests, token hashes, latest reported activity, matches, participants, visitor presence/geography/session association, rate limits, session-bound wingfam permissions and decisions, waiting status/outcomes, automatic admission notification snapshots, fixed-deadline final-screen recovery records, saved-detail authorization, independent presence and unmatch records, request-scoped Wingfam re-entry exclusions, failed-pair exclusions and manual Wingfam decline notices, photo metadata/removal receipts, lookup caches and leases. | Server authority; each record category has its own expiry or cleanup behavior. |
| R2 | Photo originals and display variants. | Access through the photo endpoint; unused or removed files are subject to cleanup/retry. |
Failed-pair records store two request IDs and disappear when either request is deleted; an explicit request update clears related exclusions. Decline notices store the manual host request ID and matcher’s display name until dismissal or host deletion. Auto-allow declines use an ended-session flag to suppress the owner notice while still revoking copies.
Waiting records contain the waiting request ID, target request ID, match ID, queue time, status, optional reopened Wingfam request ID and expiry. They persist through target cleanup but are removed with the waiting request. Polls renew them for 90 seconds while that request stays live; expired records are removed by bounded background cleanup. No waiter identities are listed to the owner: only an active count is returned.
Local storage is not a private account vault. Clearing site data removes its conveniences; leaving them in place can retain information after a request ends. The Privacy Policy describes public fields, provider processing and retention in more detail.
Previous requests and finalized-copy recovery
Deduplication and draft reuse
The Previous requests box is hidden while this browser owns an active request, including while Update your request is shown. It returns after the live request ends if saved history or a history notice remains; hiding it does not erase saved entries.
Previous requests are parsed through the public request schema before being saved. This strips photos, session credentials, request IDs inside the saved form data, and private match details. The history is limited to five distinct entries, each with a 30-day local-storage expiry. Selecting an unexpired entry for reuse renews only that entry to 30 days from selection, before it is posted again. Reads, reloads and repeated observations of the same posted request do not renew expiry. Existing records without an expiry use their original save time plus 30 days. Expired records are filtered and pruned on reads, once a minute while the page is open, and on focus; selection checks storage again so a stale displayed row cannot revive an expired or removed entry. A closed browser cannot physically erase storage until it runs again. If a renewal write fails, reuse can continue with a visible notice that the saved expiry could not be renewed. Requests whose saved fields all match except the meeting time or relative travel allowance are duplicates; photo differences do not count because photos are not stored in those entries. Other request fields remain part of the comparison. Legacy auto-advance values are stripped during schema parsing.
Existing duplicates are cleaned up when history is read, retaining the most recently saved entry. Selecting an entry fills the draft, including the saved plan description, restores its saved meeting coordinates and address, refocuses the map, collapses the list and scrolls to the form. The saved meeting point is checked again; an eligible point unlocks the form and focuses the name field. If it is rejected or cannot be checked, the form stays locked and asks for another point. A delayed automatic location lookup cannot replace or clear an explicitly restored point. An out-of-range old scheduled meeting time moves to the next quarter hour. Relative requests retain their relative setting and travel allowance. Selection does not post anything; the user can customize the restored fields before submitting. The displayed previous date and time are italicized. Reset form sits beside Clear cached form data above Let’s meet at. It clears the editable form, photo and saved draft; it does not clear the separate profile cache, previous-request history or withdraw a live request.
Finalized view and persistent dismissal
A finalized match has one optional local recovery copy, usable until exactly 60 minutes after the locked meeting time. Rewriting it while open does not extend expiry. Startup waits for authenticated server validation before displaying recovery, and server recovery can supply an undismissed final view even if the local copy is missing. Explicit dismissal is recorded on the server per participant and locally for 24 hours; up to 30 local markers prevent an older tab from reopening a dismissed screen. Browser closure is not dismissal. The open page clears an expired final screen on its local clock tick, while the server independently enforces the same deadline.
Explicitly saved wingmatch details
Either participant can choose Save wingmatch details. The button changes to Saved without closing. Clicking X before saving offers Save or Delete with a warning that unsaved details will be discarded. Save returns to the final view; Delete proceeds through the applicable queue or completion flow. A Previous wingmatch radar card reopens the received name, place, time, recognition and exchanged phone, with any Wingfam shared details; it stores no new photo or authorization token. Dismiss keeps the card; its trash icon and the saved view’s Delete control remove that browser’s entry. After a matcher saves, their finalized modal keeps Close and Unmatch, plus Decline this Wingfam when applicable; the owner also keeps the continuation controls. Saving remains optional for both manual and auto-allowed matches. At most 30 saved entries are retained per browser.
The server keeps the match ID, participant token hashes, display names, owner request ID, save flags, per-participant dismissal flags, presence deadlines and end initiator/time. It also retains both private final-screen snapshots for authenticated recovery, without raw session tokens and without disclosing an unexchanged phone number to the other person. The immutable connection deadline is the locked meeting time plus 60 minutes. Either participant receives a save notice on their next successful snapshot. Local saved entries use that same deadline, with a maximum of 30; successful responses reconcile revocations and a one-second check prunes expired entries. A saved card can be opened after server validation. A closed browser cannot physically delete storage until it runs again. External copies remain outside this control.
Counterpart status and explicit unmatch
Saved details show whether the other participant has checked in recently or their status cannot be checked. Owner and matcher presence each use a separate 90-second timestamp refreshed by their own successful check-in, including after request cleanup. A returning participant can refresh presence without extending the connection deadline. Missing legacy evidence is unknown until a check-in. A failed or stale connection shows Unable to check. Presence is not proof of attention, physical attendance or willingness to meet, and a missed check-in does not delete finalized details.
Dismiss closes the saved view, Delete removes only the local entry, and confirmed Unmatch ends the connection for both sides. Unmatch is also available before saving in finalized details and from the owner’s Wingfam members list. The authenticated server transaction records who ended the match, clears both save flags, removes that match and its obsolete admission notification, and preserves active Wingfam hosts and other connections. For an ordinary finalized pair, remaining ordinary request rows and any open continuation permission are retired; an existing waiting line is closed. The confirmation explains that queue consequence when relevant.
Sequence · Ending a connection on both sides
Read from top to bottom. Dashed arrows are responses. Choose the diagram or its text version below. Wide diagrams keep sideways scrolling when fitting would make their labels too small.
Diagram
Text version
- Either participant → Worker / D1: Confirm Unmatch for this match ID
- Authorize participant; atomically end connection and reject later saves. Preserve other Wingfam connections.
- Worker / D1 → Either participant: Return ended state; remove local copies and close its modal
- Other participant → Worker / D1: Next successful check-in
- Worker / D1 → Other participant: Return ended state and initiator notice
- Other browser removes its copies and closes that match. Offline or outdated clients cannot be erased remotely.
Ended-connection markers remain until the original meeting deadline and bounded server cleanup; exchanged recovery payloads and save flags are cleared when the connection is explicitly ended. Updated browsers remember up to 100 ended match IDs for 24 hours to reject delayed responses and restored copies. Same-browser storage events close matching views in other tabs. Without browser storage, current-page protection still works but cross-tab and reload defenses are limited. Screenshots, manual copies, device backups and old clients remain outside remote removal guarantees.
Revision history and change links
- Finalized-meeting retention and recovery updated in v122.
- Form-cache controls and scheduled-request expiry updated in v117.
- Active-request history visibility updated in v112.
- Thirty-day previous-request expiry and reuse renewal added in v109.
- Session status and bilateral unmatch added in v101.
- Explicit saving, radar cards and reciprocal notices added in v99.
- History introduced in v13.
- Deduplication and whole-entry reuse refined in v32.
- Saved meeting-point restoration protected in v48.
- Reset control repositioned in v65.
- Local recovery added in v11.
- Persistent dismissal and stale-copy protection refined in v31.
Live expiry is different from physical deletion
Live eligibility and physical deletion are separate stages. Unfinished requests and public Wingfam availability normally end after 90 seconds without renewal. Finalized connections instead become inaccessible exactly 60 minutes after their locked meeting time, even if either browser keeps polling. Explicit withdrawal, Unmatch or confirmed unsaved deletion can end them sooner. Already loaded screens can remain stale until their clock or successful server update is processed.
Sequence · From live expiry to physical cleanup
Read from top to bottom. Dashed arrows are responses. Choose the diagram or its text version below. Wide diagrams keep sideways scrolling when fitting would make their labels too small.
Diagram
Text version
- Browser → Live API / D1: Renew a live request
- Live API / D1 → Browser: Unfinished lease = now + 90 s; final deadline = meeting + 60 min
- Applicable lease, unmatched meeting time or finalized deadline reached: live views exclude the request before physical deletion.
- Browser → Live API / D1: Later qualifying API activity
- Live API / D1 → Browser: Return live snapshot without expired rows
- Live API / D1 → Background cleanup: After response, attempt shared cleanup lease
- One lease owner performs bounded row/file cleanup. Failure or backlog waits for later traffic.
Matching, meetup-check, business-search and place-search responses schedule cleanup using the hosting runtime’s after-response lifetime. Live views enforce unfinished-request leases, unmatched scheduled meeting-time expiry and finalized meeting deadlines before physical deletion. Retained Wingfam rows can support private finalized matches while the separate presence filter excludes their offline host from public discovery. Final recovery and saved-detail authorization records are independently checked against their fixed expiry, then removed by bounded cleanup.
| Control | Implemented bound |
|---|---|
| Ownership | Atomic shared D1 lease; 120-second expiry, owner-checked release, 30-second successful-sweep cooldown and per-instance throttling. |
| Request batch | At most 200 expired requests per pass, plus retiring ordinary finalized partners and cascading dependent records. |
| Other expired records | Bounded batches of up to 200 per table for presence, rate limits, permissions, waiting records, saved-detail session leases, geography, city centers, address search and map/suitability caches; lookup leases also receive bounded cleanup. |
| Photo batch | Up to 20 unused assets, including their variants. Failed deletions can retry. |
| Time budget | Stop starting new stages after 20 seconds; the host can cancel unfinished work after its 30-second post-response allowance. |
Competing Workers skip a sweep when another owns the lease. Ownership checks stop an interrupted old job releasing a newer lease, and failed work is logged for retry on later traffic. Large backlogs take multiple passes; without qualifying traffic, expired data can remain stored. Deferred work still shares hosting/database resources, so it removes direct response waiting rather than all contention.
Correctness-critical operations remain in the live path: releasing an expired session slot before posting, releasing stale match membership before inviting, withdrawal, completion and visitor-requested photo removal. Cache reads/writes are also awaited. Backups, logs, removal receipts and browser copies have separate lifetimes; availability expiry is not a guarantee that every copy disappears.
Photo processing, access and visitor removal
Upload pipeline
The upload control accepts JPEG, PNG and WebP inputs up to 3.5 MB. The browser prepares a resized JPEG and smaller display copies; this is format/size processing, not image-content moderation. The server validates the supplied image data, checks dimensions and enforces a maximum 800-pixel image dimension and 250 KB processed-image size. Re-encoding and quality reduction are used when needed. Small and medium thumbnails are validated separately, with 160- and 400-pixel bounds.
Serving and access checks
Images use opaque photo IDs and are served through /api/photos/…, rather than exposing a public bucket listing. Small and medium previews can be viewed with live requests. Opening the full image requires the viewer’s own active request to contain a photo. The image endpoint uses Cache-Control: no-store and checks that the image remains live and has not been removed.
Removal and propagation
Any visitor can use a photo’s three-dot menu and red X to remove it for the stated rights, copyright or legal concern. The server accepts that action without requiring an account or an active request. It checks the request origin and confirmation value, records removal, detaches the image from requests and wingfam permissions, and attempts deletion of the original and both thumbnails. If file deletion fails, later cleanup retries while the live endpoint keeps the image unavailable.
The reporting page clears its visible copies immediately after success. Other pages learn about removal through snapshots or photo-status checks, normally around every 10 seconds. A saved photo association lets the uploader’s browser recognize and clear its local copy when it reconnects. Offline, suspended and outdated browsers cannot be remotely erased immediately, and older copies may lack that association. Screenshots and external downloads are beyond the app’s control.
Removal receipts retain the photo ID, an associated request ID when available, and removal time without a fixed expiry. They contain no image or reporter identity. The tool removes references to the selected upload, not every separately uploaded duplicate, and it does not decide whether a legal violation actually occurred. See the removal terms for the rationale and reporting options.
Content and access controls
Text and emoji screening
A deterministic phrase-and-symbol filter screens submitted names, plans, addresses, recognition details and relevant phone input for prohibited dating, marriage, sexual and drug-related content. It checks common slang, some disguised spellings, Unicode lookalikes and a defined set of emoji, including arrow symbols. Drug screening covers explicit drug names, common use and dealing phrases, some separated or disguised spellings, and associated emoji. Ambiguous everyday words require drug-related context, while the defined drug-associated emoji are blocked even when intended innocently. Numeric and fixed-choice fields have their own schema validation.
Checks run in the browser for feedback and on server submission paths, including request updates and wingfam reopening. Server validation matters because browser checks can be bypassed. No external AI moderation service receives the text for this filter. It does not interpret every innuendo, understand every language, inspect image meaning or establish intent. False positives and evasions are possible. The screening release history below records the banned emoji inventory introduced with screening.
Revision history and change links
- Introduced in v30.
- Drug screening added in v50.
- v30 changelog
Security and practical limits
HTTPS, restricted session cookies, hashed token lookup, parameterized database queries, input validation, origin checks, rate limits and conditional database updates provide layers of protection. They do not prove someone’s identity, make posted data private, prevent every misuse, or guarantee that multiple browsers belong to different people.
Names, locations and photos visible to other visitors can be copied. Browser storage can be cleared or edited. Map data can be incomplete, network responses can be delayed, and a successfully hidden record can still exist in backup history. The implementation deliberately favors temporary live availability and simple coordination over permanent accounts and guaranteed background delivery.
For the rules governing use, read the Terms of Service. For data handling and retention, read the Privacy Policy. For a problem or rights concern, use Report a concern.
Client experience and operations
Live connection and suitability diagnostics
Diagnostics explain what the page last observed and which policy it is configured to follow. Opening the masthead badge does not run an independent health check or inspect database cache rows. This distinction matters when a failed latest lookup coexists with a previously selected, still-retained meeting point.
The masthead diagnostics button keeps a 44-pixel minimum width and cannot shrink in the header. At viewport widths of 420 pixels or less it is a 44-by-44-pixel circle with the text hidden; larger views retain the expanded status label.
The connection icon uses a steady radar grid with a clockwise sweep from 12 o’clock back to 12 o’clock every ten seconds. A 135-degree gradient tail fades behind the beam. Six fixed decorative blips brighten as the beam passes and gradually fade; they do not represent people or measured activity. The animation is a local display cycle, does not trigger network checks, and continues smoothly across successful polls. Connecting and failed states pause the animation and keep their existing status colours. Retry attempts while disconnected keep it still until a successful response restores the connection. Reduced-motion preferences show a still radar.
Panel behavior and interpretation
The blurred panel opens at the right below the visible quick links bar, 330 pixels wide when space permits, and scrolls within the remaining viewport height. Drag its header with a mouse, pen or touch to move it; the Drag to move control also accepts arrow keys (10 pixels, or 40 with Shift). Movement stays within the visible viewport and below the quick links bar. Resizing, scrolling and mobile viewport changes recheck those bounds. Closing and reopening resets the position. Pointer movement is combined into one direct transform update per animation frame using cached bounds, without rerendering diagnostic content or measuring layout on each movement. Available height changes only when needed; the size observer watches the header and quick links rather than the moving panel. The header and close button remain visible while the diagnostic body scrolls. Its stationary background layer blurs the page behind it while the content scrolls separately; a tinted surface keeps the text readable, with an opaque fallback when backdrop filtering is unsupported. The static theme stylesheet preserves both standard and WebKit blur declarations through publication. Its close button and Escape return focus to the badge. Sections expand independently. Live counts and Connection start expanded; Sound & vibration, Your session, Meeting-place suitability, Browser details & timing notes, and Log start collapsed on each opening.
| Value | What it represents | What it does not establish |
|---|---|---|
| Connection / lease / snapshot age | Last successful API response, attempt timing and server-derived lease countdown. | An uninterrupted connection or one-second network polling. |
| Selected-point check | Last client selection/validation attempt, reason and age; the current pin is listed separately. | That a failed latest replacement removed an older valid selection. |
| Current-location suitability | Separate device screening result, reuse deadline and original source/data time when supplied. | Approval of the selected meetup point. |
| Cache policy | Configured lifetimes and coordinate tolerances. | A cache hit for a particular lookup. |
| Result source | Observed exact-point memory/D1 hit, reused geometry, fresh provider or shared in-flight work. | An independently verified provider-health result. |
Round-trip duration includes matching-server processing and JSON handling. The lease countdown combines server time with elapsed time since receipt. Counts become labeled stale as their observation ages. A local one-second display timer runs only while the panel is open; opening or expanding sections adds no provider calls, telemetry or persistent diagnostic record. Location, radius and map-bound changes normally ride the next ten-second state poll.
Location and cache provenance
The session section separates the active approximate-IP-area flag from the last IP attempt. The latter records fresh-provider versus sessionStorage provenance, original result time, reuse deadline and whether saving succeeded. Expiry prevents later reuse but does not erase an already displayed fallback or trigger another lookup. A late IP result that loses to device acquisition is reported as not applied.
City diagnostics distinguish location basis from resolution method: Worker memory, shared D1 cache, fresh lookup, in-flight sharing or hosting IP geography. Negative cached results identify the server layer and up-to-30-second cooldown. There is no browser city-resolution cache; the separate tab sessionStorage cache holds an approximate IP browsing area for one hour.
Suitability diagnostics identify page-memory exact-point decision reuse and distinguish exact-point server reuse (up to 24 hours), geometry reuse (up to 24 hours within 5 metres, with a new classification), and current-location page-memory reuse (up to 24 hours within 5 metres, with a 30-second failed-check cooldown). Derived classifications inherit geometry expiry. An exact-point result may no longer expose its earlier geometry provenance, so no separate geometry-source field is claimed. Shared in-flight timestamps can be unavailable after persistence failure.
Restored draft/history coordinates never restore an approval. Use my location can select an already prepared business. A paused device check sends no request, and suitability is not persisted in localStorage or the HTTP browser cache. The selected-location details show distance from the current location rather than distance to themselves; diagnostic units spell out meters.
Bounded provider and coordination log
The page log retains failed provider attempts even when a later fallback succeeds. Each entry has a UTC timestamp, one-word purpose and hostname, followed by HTTP status and provider output, or a network-failure explanation. HTTP 200 timeout remarks and malformed JSON count as provider failures. Map-tile and script loads are outside this fetch-based log.
- Provider output is capped at 4,096 characters per attempt, with at most 20 attempts per response and the latest 100 entries in page memory.
- Lookup coordination entries record cache hits, starts, waits, expired-lease recovery, deadlines and storage fallback. They omit coordinates, query text and owner tokens; provider failures take priority within the response limit.
- Query strings are excluded from provider labels. Geoapify failure bodies and exception text are replaced by a generic message so echoed credentials or address queries cannot reach the browser; other provider output can echo searched locations. IP-fallback event entries contain no IP address or coordinates.
- Concurrent shared work is attributed to the initiating request; cancelled losing fallback attempts are omitted. Lost or cancelled responses can prevent their logs reaching the page.
- The log survives closing the panel and clears on reload. It creates no shared visitor log or persistent diagnostic database.
The read-only log starts at 180 pixels high and can resize between 120 and 360 pixels within the scrolling panel. Entries are adjacent without extra blank lines; line breaks within provider messages remain. These presentation controls do not change the collection limits.
Revision history and change links
- Credentialed address-provider failure redaction added in v118.
- Ten-second radar sweep and fading decorative signals added in v96.
- Interactive diagnostics introduced in v44.
- Separate blur layer and collapsed sound controls added in v88.
- Published Chrome blur declaration restored in v89.
- Draggable diagnostics and quick links clearance added in v90.
- Dragging rendering and resize feedback optimized in v93.
- Circular compact badge sizing with an expanded label on larger screens corrected in v87.
- Suitability and device-reading information added in v52.
- Collapsible sections added in v53.
- Live counts opens expanded by default from v60.
- Location diagnostics clarified in v70.
- Separate suitability cache policies added in v66.
- City cache labels clarified in v63.
- The static Browser city cache and Persistent browser cache rows were removed in v64.
- Suitability provenance added in v61.
- One-second local display updates restored in v65.
- Provider failure log added in v56.
- Log scrolling and spacing improved in v68.
- Lookup coordination events added in v58.
Appearance preferences
Appearance is a browser preference shared by the homepage, document pages and standalone changelog. A header control cycles Auto → Light → Dark → Auto; Auto follows the device color-scheme setting and is the default.
An appearance controller embedded in the initial document applies the saved preference before a separate script download is needed. It reapplies the mode when the interactive page is ready, follows device changes, and synchronizes ordinary tabs through storage events. Repeated initialization reuses the controller and current preference. Dark mode retains blue/yellow accents on charcoal/slate surfaces.
The preference is stored under wingman-theme and is not sent to the server. If storage is unavailable, switching still works for the current page but does not survive reload.
Active request form
While the server snapshot contains an active request, the connection is healthy and no wingmatch is underway, the request panel shows a moving gold dashed border. The border is decorative and does not intercept pointer input. It is static when the browser requests reduced motion and disappears when those update-mode conditions end. The request panel’s lightning icons are filled yellow only while its update-mode marquee is active; new-request and other lightning icons retain their outline appearance.
Searchable visitor FAQ
The /faq page contains 40 questions from one shared content collection, including gender inclusion, device installation guides, sponsorship, matching, request continuity and privacy. The request heading, submit button, agreement notice and restored-request instructions consistently say Request a wingperson. The homepage introduction and its full company-finding section use gender-inclusive language, including wingperson, and welcome people of every gender. The inclusion answer explains the familiar site name, welcomes all genders with a non-exhaustive list and invites FAQ-only additions by email; the dropdown retains Self-described. A dedicated Wingfam topic covers setup, admission modes, public sharing, waiting, browser requirements, notifications, members, optional saving, unmatch/removal, session status and closing the host. Search checks question text, answers, linked resource labels and keywords without sending the query to the server. Topic and search filters combine. All answers start collapsed, with a single keyboard-accessible question-and-arrow button per answer; multiple answers may stay open. Filtering keeps expansion state in page memory, and Expand all and Collapse all affect only visible results. No-results recovery clears both filters.
Stable question fragments open and focus the requested answer after clearing search and topic filters. A separate Copy link button copies only the page address and question fragment, removing any query string; success is announced after the Clipboard API resolves, and failure gives retry feedback. Search and expansion state are not stored across reloads. All question and answer text is rendered in the initial HTML, including collapsed answers. Every answer ends with Added and Last updated records showing its date, time to the second and explicit Central Time label, with each version linking to its changelog anchor. Historical dates use the Git committer timestamp of the entry’s introduction or latest question, paragraph or resource-link change; the version is the first saved Sites release containing that commit. These timestamps record source changes, not exact publication times. Formatting, search metadata, reviews and the footer backfill do not reset answer dates. The page uses the shared document shell, themes, back-to-top and refresh behavior. Footer links open a new browser tab or the existing installed-app overlay. The homepage installation instructions link directly to /faq#install. Device instruction links open externally and remain subject to browser and operating-system behavior.
Revision history and change links
- v125 — Explain map pin checks, feedback and retained selections.
- Nearby business and draft-point clearing guidance updated in v123.
- Finalized-meeting retention and recovery updated in v122.
- Nearby address ranking and refresh guidance updated in v120.
- Address autocomplete guidance added in v118.
- Form-cache controls and scheduled-request expiry updated in v117.
- Text-search result feedback corrected in v116.
- Clicked-row progress and cached unsuitable choices added in v115.
- Actual business-selection errors and one transient-failure retry added in v114.
- Business selection and timeout recovery guidance updated in v113.
- Owner activity display and active-request history visibility clarified in v112.
- Previous-request expiry and renewal documented in v109.
- Draft expiry and fresh time defaults documented in v108.
- Per-answer addition and update history added in v107.
- Visitor-count and fallback-city guidance clarified in v105.
- Request-city popover guidance added in v106.
- Inclusive request-action labels aligned in v104.
- Inclusive homepage company-finding copy aligned with the FAQ in v103.
- Candidate selection, admission limits and decline guidance updated in v102.
- Detailed Wingfam operations added in v101.
- Introduced in v78.
In-page alerts, sound and haptics
Notifications report events observed by the open page. Toasts and relevant match transitions can trigger sounds and haptics, but the application has no native notification-permission flow, push subscription or background browser-notification delivery.
Playback and independent controls
Sound uses small first-party PCM files and one reusable HTML audio element. The first interaction primes it with silence. Test sound invokes playback directly from the tap; rejection is handled and diagnostics report failure. The speaker animation follows media events, while prioritization and brief coalescing reduce overlapping bursts.
| Channel | Preference and test | Platform boundary |
|---|---|---|
| Sound | Defaults on; speaker switch beside theme control. Independent diagnostic switch/test and feedback. | Browser interaction may be required. A playing element does not prove physical audibility. |
| Haptics | Defaults on; independent switch/test. Disabling cancels queued/running vibration. | Uses supported Vibration API after interaction while visible; unsupported channel shows Unavailable. |
Both preferences persist locally and synchronize across same-site tabs. Tests are disabled when their channel is off or unsupported. The speaker help popover is available by hover or its question-mark control, and diagnostics group both channels under Sound & vibration.
Where supported, enabled sound requests the Audio Session API’s media-playback category and restores the earlier category when disabled or removed. Media volume and platform behavior still apply. Installing the app does not remove those restrictions or enable guaranteed background alerts.
Persistent auto-allow notifications
Each automatic admission writes a private host event containing the guest’s public quick-info snapshot. The oldest unacknowledged event appears as a persistent toast, with an additional-notification count card below it. Dismissing acknowledges that exact event and shows the next. A guest closing does not erase an unread event; explicitly unmatching or declining the Wingfam removes that connection’s obsolete admission event; host withdrawal or deletion clears its event queue. Photos are looked up from the current public request instead of retained in the event. Server acknowledgements survive reload and another tab’s polls. Save and unmatch notices are also shown in-page to the other participant. A dismissed admission event does not remove its member-list entry. These notifications are not background push; an unmatch notice can be missed after the finalized meeting deadline.
Revision history and change links
- Finalized-meeting retention and recovery updated in v122.
- Form-cache controls and scheduled-request expiry updated in v117.
- Candidate recovery and Wingfam decline behavior updated in v102.
- Member controls and session lifecycle updated in v101.
- Waiting, ownership and lifecycle behavior reviewed in v99.
- Sound cues introduced in v25.
- Speaker animation added in v27.
- Haptics and supported media playback added in v28.
- Media-element playback replaced Web Audio chimes in v41.
- Sound controls simplified in v73.
Installation and offline behavior
The site can be added to a device’s home screen on supporting browsers. The browser title is “Wingman! - The original free service”; the existing installation names remain “Find a Wingman!” and “Wingman!”. Its service worker caches a small set of installation assets and an offline page. Navigation attempts the network first and can fall back to that offline page.
The service worker does not cache live request responses or contact details and does not run a background matching engine. Installing the app does not remove browser suspension, connectivity or permission limits. Matching and posting require the server; saved drafts are convenience data, not an offline queue of requests waiting to go live.
Versions and updates
Startup readiness and release discovery are separate mechanisms. The loading screen protects the initial transition into a usable page; the later update banner lets an existing visitor decide when to reload a newer release.
Document startup
The initial HTML activates the loading screen before showing the app. Homepage and shared documents wait for the document load event, observed scripts/styles, fonts and interactive-page readiness. The standalone changelog does not require application hydration. A resource failure or a 20-second delay offers Try again, which performs an ordinary reload; a slow successful startup can still finish automatically.
Readiness is retained for that document, including repeated or late initialization. Once ready, loading listeners and resource observers are removed. Theme changes, later resource failures, ordinary updates and returning to the same retained document do not restart the loader. A new document or explicit reload starts a new cycle, and browser suspension can delay it.
Release discovery and asset freshness
The version badge opens the generated changelog in a new tab. While visible, the page normally checks for a release every 30 seconds and on relevant return/connection events. A newer release announces “A new version of Wingman! is available.” and offers a refresh and changelog link; the app does not force a reload during a request.
| Item | Freshness contract |
|---|---|
| First-party static assets | Theme styles/scripts, document-refresh code, sounds and icons use hashes of their contents. Only a changed file gets a changed URL. |
| Installation manifest | References hashed icons and has its own content hash. |
| Framework JavaScript / CSS | Use build-generated hashed filenames. |
| Refresh action | Ordinary reload obtains current asset references while preserving cookies, local storage, saved requests and preferences. |
The homepage, document shell and standalone changelog use the same generated asset map. Releases build the application and include database migrations before publication. Significant design/workflow changes require a full review of this document; smaller changes update the relevant passages, diagrams and revision links in place.
Revision history and change links
- Version badge and changelog introduced in v12.
- Optional refresh banner added in v23.
- Banner changelog link added in v30.
- Loading screen introduced in v79.
- Document-lifetime loading and delayed startup recovery fixed in v83.
- Version-badge navigation updated in v76.
- Content-based static-asset URLs added in v49.
How it’s Built
Vibe coding as an ongoing conversation
Wingman was developed through a conversation between Todd Hendricks and an AI coding agent, using the Codex desktop app and ChatGPT Sites tools. Todd describes the intended experience, tries the result, and steers the next change. The agent reads and edits the application source, investigates behavior, runs checks and publishes versions. The visible footer credits “ChatGPT 6 Astra/Medium”; that credit identifies the project’s stated development attribution, rather than documenting the model settings of every individual tool call or past session.
Here, “vibe coding” means expressing much of the product direction in ordinary language while the agent handles implementation. The initial brief was substantial: a login-free way to find company, a radius slider and map, temporary requests, reciprocal matching, agreement on a meeting place, recognition details and optional mutual phone exchange. It also specified a superhero-inspired identity. The midnight-blue and yellow interface and bold Wingman! wordmark grew from that brief.
The result is maintained as source code with a database schema and release history. Conversational development still requires decisions about race conditions, browser restrictions, validation and retention. A screen that looks plausible is only one part of the result: the shared state must behave correctly when two people click, disconnect, return or change their minds.
The tools and their responsibilities
ChatGPT Sites and the Codex desktop workspace
ChatGPT Sites provides the site project and publication workflow. The agent works with a local source checkout, then uses Sites tools to save a version associated with a pushed Git commit and deploy that saved version. The published application runs on the hosting infrastructure described in the architecture section: Cloudflare Workers, D1 and R2. Development conversations and publication tools are distinct from the request-matching code visitors use.
Codex desktop provides the development conversation, access to the working files and terminal, and an in-app browser for inspecting the site. Todd can comment on an exact element in the preview instead of describing its position from memory. That feedback can arrive during ongoing work: a wording correction, a new constraint or an additional example becomes part of the current change before it is released.
MCP and tool integrations actually used
MCP—Model Context Protocol—provides a common way for an AI application to expose tools and context from other systems. In Wingman’s development records, tool calls cover several distinct jobs. These integrations assist development; visitors do not need to install these servers to use Wingman.
- Sites tools through the codex_apps interface
- Create and inspect the site project, obtain short-lived source-repository credentials, save versions, deploy them and check deployment status. Later changes reuse the existing project and preserve its audience.
- codebase-memory-mcp
- Index the source into a knowledge graph and locate relevant code by function, route or relationship. Workspace guidance prefers this graph for code discovery, with targeted text searches when the graph is insufficient or the task concerns literal text or configuration.
- Computer-use/browser tools
- The cua_repl browser interface has been used to inspect rendered pages and operate controls. Browser comments supply the selected element and visual context. Separate Playwright scripts also exercise isolated browser scenarios; those scripts are test tooling, rather than another production matching service.
- Codex app tools
- Open the relevant preview and read earlier development tasks when established decisions or history are needed. This section was checked against Wingman’s earlier creation and refinement conversations as well as the current source and changelog.
Ordinary development tools also matter: Git records source changes; Node.js runs the application toolchain and JavaScript tests; TypeScript checks types; SQL migrations evolve the database; and browser automation checks actual controls and layouts. Tool availability does not establish that a particular tool was used. This account names integrations evidenced in Wingman’s development, rather than listing every server available in the author’s environment.
Rosetta for memory and task tracking
Rosetta (opens in a new tab) keeps project notes and outstanding work available across Wingman’s development conversations. Through the Rosetta plug-in, the agent retrieves relevant notes and tracks bugs and feature requests as reminders, using the #bug and #feature tags. This gives each task a durable record of the requested change and its context.
Once a bug is fixed or a feature is complete, its reminder is marked completed. Related notes are archived only when their contents are no longer relevant to other outstanding notes or reminders. Rosetta supports project memory and development task tracking; visitors do not need it to use Wingman.
Revision history and change links
- Rosetta attribution and development workflow documented in v110.
How human steering changes the implementation
Feedback ranges from visual detail to product semantics. A comment attached to the nearby-visitor count can specify the spacing above and below it. A follow-up can reorder three footer lines and then ask that they read as one compact block. These are precise refinements of the rendered experience, and the agent checks the resulting layout at phone and desktop widths.
Other instructions alter shared behavior. Previous requests became duplicates when all saved fields except photo and meeting time matched. Gender preferences gained an explicit explanation about comfort and personal safety when choosing platonic company. Recognition choices introduced a new conflict: two people both choosing “I’ll find you.” Resolving that request required a stored recognition mode, a message visible to both participants, and server-side refusal to finalize until one chooses another option.
Steering can also correct an earlier rule. The technical document exposed an obsolete “tomorrow before 6 AM” restriction. Inspection showed that it still existed in both the browser and server, so the implementation was changed to remove the cutoff and the rolling 12-hour window was tested. The document was corrected alongside the implementation. Conversation history helps explain why a feature exists, but current code and verified behavior determine what the site can honestly claim.
Diagram
Text version
- Describe a goal or annotate an element in the preview
- Inspect the relevant source and translate feedback into behavior and acceptance checks
- Edit the UI, server rules, storage and documentation as needed
- Test normal behavior, failure cases and relevant screen sizes
- Publish a version, inspect the live result, and use the next observation to steer again
Revision history and change links
Project instructions that survive the conversation
An AGENTS.md file keeps recurring project expectations beside the source so a later coding session can find them. It is guidance for development agents, not executable enforcement. Its value comes from making obligations explicit and then checking that the resulting changes follow them.
Outstanding work tracking
Agents manage outstanding bugs and feature requests as Rosetta reminders. Bugs carry the #bug tag and feature requests carry #feature, so each kind of work can be filtered consistently.
Release traceability
Every release needs clear, user-facing changelog entries. Versions stay in descending order with dated records, related edits in one unpublished batch share a release, and the generated changelog pages must agree with the source history. Agents must not invent historical fixes or publication claims.
Visitor FAQ maintenance
FAQ review is a release requirement alongside this design document and policy maintenance. New features, meaningful behavior changes, changed controls, sharing, notifications, expiry and recovery rules must be checked against existing answers in the same release. Major features need practical coverage of the visitor journey, roles, choices, starting and stopping, information shared, saving/deletion and meaningful limits; several focused questions and a dedicated topic can make that easier to use.
Smaller changes update affected answers in place. The agent preserves question IDs and valid links, adds useful search terms, removes superseded advice and avoids filler or unrelated rewrites. Each expanded answer must carry Added and Last updated footers with a date, time to the second, explicit America/Chicago zone label and the appropriate changelog link. Original addition history is preserved. Historical backfills compare each entry through Git history and verify its first containing saved Sites release; they use recorded commit times, not guessed publication times. Question, answer and linked-resource changes update that entry’s latest revision and content digest, while formatting, search metadata, unchanged reviews and metadata-only backfills leave its dates intact. The release check validates coverage, chronology, content digests and version anchors. Changed FAQ content receives its actual America/Chicago edit date; a review that needs no changes is recorded without changing that date. Before publication, answers are checked against the code and related documents, with search, topic and direct-link checks and narrow rendered inspection when needed. A major visitor-facing feature with missing or outdated FAQ guidance is not ready to publish.
Choosing the review scope
A significant design or workflow change requires a review of the entire How it works page against the final implementation, including sections that were not directly edited. This covers changes to user flows, state transitions, architectural responsibilities, synchronization, recovery, data lifecycles and privacy boundaries. The review follows their effects through prose, diagrams, tables, caches, storage, diagnostics and limitations. Several small changes that collectively alter a flow also require this full review.
Smaller wording, styling, label, threshold and isolated bug-fix changes keep the existing update-in-place workflow. The agent updates affected passages and related diagrams, tables and revision links in their established sections, then checks nearby explanations and cross-references for contradictions. A focused review expands to a full-page review when it reveals wider effects or accumulated inconsistencies. Superseded statements are corrected or removed instead of being followed by contradictory additions.
Consistent revision history
Every section with introduction or change-version links ends with one collapsed block labeled “Revision history and change links,” with the links presented as list items. That block follows the section’s prose, diagrams, tables and any child sections. Each subsection owns its own references; a parent’s separate history belongs at the bottom of the parent. Sections without revision links need no empty block.
Change-version links do not appear before the body, inline in explanatory paragraphs or in a second revision block. Moving a link must leave complete sentences and preserve its descriptive label and historical destination. Before release, the agent checks the entire rendered explainer for links outside these blocks, duplicate or misplaced blocks, expanded defaults and broken changelog anchors. The implementation-review label and ordinary navigation have separate purposes and are not feature-history entries.
Keeping the instructions and this account synchronized
Every modification to AGENTS.md must also be documented in the appropriate How it works section during the same unpublished release. This applies to additions, revisions, removals, reorganizations and editorial clarifications, including changes that do not affect the running application. Guidance about agent work, review scope, documentation conventions and release obligations is maintained here; other affected sections are updated when the instruction also changes a documented workflow or product behavior.
The explanation describes the resulting rule and its practical effect. Editorial-only changes are identified as clarifications rather than new obligations or runtime features. The agent records the change in the changelog, places its revision link in the owning section’s collapsed history, and checks the final instruction diff against the corresponding passages before publishing. Both focused updates and full-page reviews require this synchronization; an instructions-only commit does not complete a release.
Safety documentation and review cadence
At the start of each change, the agent checks whether a safeguard is being added, changed or removed. The safety page is updated in that same release for affected privacy and sharing controls, consent, matching, location and content screening, photos, activity signals, expiry, recovery or reporting. New safeguards are documented immediately rather than waiting for a periodic review.
A full safety-page review becomes due when ten versions have passed since its last full review and must be completed before publishing a release that reaches a fifteen-version gap. The agent compares both the current verified release and the intended release with the safety page’s explicit full-review version. The How it works review label and the page’s last edit do not establish that baseline. A missing, invalid or future baseline must be resolved or replaced by a completed full review, and concurrent publication requires checking the gap again against the final release number.
The full review reads every safety section against the final source, including safeguards untouched by the current change. It checks public and private boundaries, normal operation, failures and recovery, limitations, reporting routes and relevant Privacy and Terms references, supported by appropriate tests and rendered inspection. A changelog or earlier review label is not sufficient evidence, and gaps are corrected without inventing protections or guarantees.
Every completed safety review records its actual America/Chicago date, examined release and scope/outcome, even when no prose needs changing. The full-review label uses “Full safety review for Wingman vN · Month Day, Year.” A focused review can have a separate dated and versioned label, but preserves the last full-review baseline so it does not restart the ten-to-fifteen-version clock. Builds, page views and unrelated releases never advance these labels automatically; incomplete reviews are not marked complete. Safety review dates remain separate from policy modification dates, and the agent verifies the rendered label, links, section navigation and release references before completing publication.
Policy accuracy, navigation and verification
Changes to collection, sharing, retention or user controls require review of the Privacy Policy and affected Terms. Policy modification dates reflect the actual date that policy was edited; a routine build or unrelated feature does not advance them. Footer and policy links open in a new tab, and section anchors and the sitemap remain usable.
For either review scope, the agent checks normal operation and failure/recovery paths, including delayed results, restored state, permissions, provider failures, retained values and retries. Location descriptions distinguish device readings, IP fallback, the viewport, the selected pin, business selection and suitability. Cache descriptions identify layers, keys, positive and negative lifetimes, finite limits, stale reuse and physical cleanup. Diagnostic descriptions separate configured policy from observed results and explain defaults and retry triggers.
The implementation-review label names the release actually reviewed. Appropriate checks and rendered inspection accompany publication, with material verification limits reported honestly. Simulated tests do not establish physical-device or live-provider behavior, and documentation must not promise identity verification, delivery, moderation or deletion that the implementation cannot provide.
Revision history and change links
- Rosetta bug and feature tagging required in v108.
- Per-answer FAQ history maintenance required in v107.
- Same-release visitor FAQ maintenance required in v101.
- Full-page reviews for significant changes and focused updates for smaller changes established in v84.
- Uniform collapsed revision histories and release checks required in v85.
- Same-release documentation of every AGENTS.md modification and explicit safety-page review requirements established in v86.
Examples from the earlier build
Live status needed database evidence
Early feedback questioned whether a request was truly live. An early correction required database confirmation before displaying that status, and a subsequent change added live-request recovery. This is a recurring design lesson: a local interface state is not proof that a shared server operation succeeded.
A business dropdown needed a failure path
Nearby businesses began as a convenience feature in an earlier release. A later report reproduced a failing lookup on the published site. The fix improved the query, provider fallback and caching, and made the unavailable state recover through repeated checks. The creation chats distinguish a broken local preview from a confirmed production failure; one is not sufficient evidence of the other.
Photo limits became a processing pipeline
The photo feature evolved from its initial upload control into a tile interface, browser preparation, server validation and compression, and multiple thumbnail sizes. The input allowance eventually reached 3.5 MB while keeping a smaller processed-image limit. Asking for a larger upload was therefore a change to both usability and resource handling.
Small words can encode different events
A request disappearing after deliberate withdrawal is different from a request expiring because its heartbeat stopped. A later correction removed the misleading expiry notification for intentional withdrawal. That change is a useful example of why testing the reason for a state transition matters, not just the final empty screen.
From an edited preview to a published release
The release workflow builds the application, checks types and runs tests appropriate to the change. For behavior involving shared state, tests can run actual SQL migrations against an isolated SQLite database and exercise the API. Browser tests can supply controlled snapshots to check dialogs, permission-dependent behavior and narrow layouts without posting artificial meetup requests to the live service. Provider checks and production readbacks cover different questions from those controlled tests.
The source is committed and pushed before a Sites version is saved. When a deployable archive is supplied, it contains built output and required migration/configuration files associated with that exact commit. Otherwise Sites builds the saved source remotely. A saved version is then deployed; the same deployment is checked until it succeeds or fails. After success, the live version endpoint and relevant pages or flows are inspected. Existing visitors receive the optional refresh banner rather than a forced reload during a meetup flow.
Passing a test demonstrates the scenario it exercised. A mobile-sized desktop browser test does not prove every behavior on a physical iPhone or Android device, and a mocked provider response does not establish that the provider is healthy. The process therefore combines source inspection, targeted tests, rendered checks and live verification, while documenting the limits of each.
Where’s the source?
Wingman’s source code is not publicly available yet. Development uses a private source repository, as described in the release workflow above. The ability to work with that repository does not itself make the project public or establish a general user-facing Sites export feature. There is no announced public source release date or public source-code license.
This document and the changelog provide a view of the design and how it evolves in the meantime. You can follow those pages for updates about the project and source availability.
Revision history and change links
- Added in v37.
What the AI does—and what the running site does
The AI is used to build and maintain the software. The matching decisions, expiry rules, content screening and location classification described above run as application code and database operations. The current matching flow does not ask a language model to choose a companion, interpret a person’s intentions or approve their safety.
Todd supplies the product direction and remains the site operator. The agent can implement and investigate changes, but generated code, tests and policy prose can still be wrong. This document and the changelog make the design and its evolution inspectable; they are not a substitute for operational experience, independent review or appropriate professional advice when needed.
Revision history and change links
- This development-process account was added in v35.