new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Expo · Router · all subjects

advanced-patterns: middleware

13 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

AsyncFunction off main queue must explicitly hop to main thread when accessing UIKit

A module-level (non-View) AsyncFunction whose body touches UIKit, presents a view controller, or calls currentViewController(), Utilities.keyWindow(), or SceneGeometry.keyWindow(...) must be dispatched to the main thread. Either use the closure form ending in .runOnQueue(.main) or use the async form with explicit MainActor.run { ... } or @MainActor helper. AsyncFunctionDefinition dispatches onto its own userInitiated queue, and currentViewController() on Utilities is declared nonisolated with MainActor.assumeIsolated in its body, which traps at runtime off the main actor. This failure is runtime-only without a diagnostic in either Swift 5 or Swift 6 mode.

AsyncFunction in View block must take view instance as first parameter or use explicit main queue hop

An AsyncFunction inside a View { ... } block that touches UIKit, SwiftUI state, or the view hierarchy must either take the view instance as its first closure parameter or use explicit .runOnQueue(.main). If the view instance is provided as the first parameter, the DSL already forces those onto the main queue.

Synchronous Function, Property, or Constant must not perform blocking I/O or network operations

A synchronous Function, Property getter/setter, or Constant closure must not perform file I/O, network requests, database queries, image decoding, Thread.sleep, or semaphore or DispatchQueue.sync waits. These blocking operations belong in AsyncFunction.

Do not hold locks across thread hops in AsyncFunction

A lock, semaphore, or transaction must never be held across a hop to another thread. The severe case is an AsyncFunction on moduleQueue holding a DispatchSemaphore while blocking on the JavaScript thread, where the JS thread then waits on the same semaphore. DispatchSemaphore.wait() does not pump the run loop, so neither side can recover.

Long-running work must not use the default AsyncFunction queue

Long-running or unbounded work must not be left on the default AsyncFunction queue. That queue is a single serial queue shared process-wide by every async function in every Expo module (DispatchQueue(label: "expo.modules.AsyncFunctionQueue", qos: .userInitiated)), so a large file copy head-of-line-blocks unrelated calls app-wide. Use explicit .runOnQueue with a private queue instead.

GCD and Swift concurrency must not be mixed in one type

GCD and Swift concurrency must not be mixed inside one type—a DispatchQueue plus async/await plus an actor in the same class creates correctness hazards. Prefer one concurrency model; an actor is usually the right consolidation.

expo-modules-core Swift 6: @unchecked Sendable needs synchronization proof in diff

In packages/expo-modules-core only (Swift 6 mode), a newly added @unchecked Sendable conformance or nonisolated(unsafe) declaration on a type with at least one mutable stored property is only acceptable if the diff also shows the synchronization that makes it sound.

Cross-cutting correctness defects: iOS/Android platform divergence

Cross-cutting correctness review focuses on defects visible only when reading files across two languages or platforms. The key categories are: behavior, default values, event payload shapes, or error codes that changed on one platform while the other still uses the old implementation when the TypeScript API exposes both as one function. New capabilities or Record fields added to only one platform where the TypeScript type offers it unconditionally result in silent no-ops on the other platform rather than errors. Error identity divergence occurs when the same bad input produces different JavaScript errors on each platform—a raw IllegalArgumentException or NSError on one side versus a named CodedException or Exception subclass on the other, causing JavaScript that branches on error.code to work on only one platform.

Detect fixes applied to sibling code copies only

When a diff changes an expression, search for identical shapes in sibling paths: packages/expo-router/src/fork/ versus packages/expo-router/src/react-navigation/ (vendored upstream code), iOS and Android implementations of the same module API, other navigators, codemods or templates that emit the same code, and translated documentation mirrors of edited English pages. Flag when a changed expression still exists in identical form at a sibling path, indicating a fix landed in one copy but not its sibling.

Verify JavaScript contract agreement with native implementation

Cross-cutting defects include: TypeScript string-union members with no matching Swift enum raw values or Kotlin constants; Record fields whose Kotlin @Field(key = …) or Swift @Field name no longer matches the key the TypeScript side sends; event names in sendEvent(…) / emit(event:) absent from TypeScript listener types or vice versa; TSDoc or documentation stating behavior the implementation contradicts such as documented defaults, fallbacks that never run, or platform availability the native code does not honor; nullable TypeScript fields that one platform can never return as null, or non-nullable fields a platform can leave absent.

Detect changed defaults affecting existing call sites

Changed default values, default queues, default storage modes, or default code paths in shared infrastructure alter behavior at existing call sites without source changes. Severe defects can result from decode defaults that turn ArrayBuffer reads under locks into blocking hops onto the JavaScript thread. Also detect when functions move between synchronous and asynchronous execution or between queues, where a caller holds a lock, semaphore, or transaction across the call. Trace at least two existing callers before reporting and name them.

Cross-platform declaration and manifest gaps for permissions and capabilities

Flag when a new runtime permission request or implicit-Intent resolution (resolveActivity, queryIntentActivities, querying another package) appears without matching <uses-permission> or <queries> in the package's android/src/main/AndroidManifest.xml. Also flag new iOS capabilities or privacy-sensitive API usage without matching Info.plist usage descriptions in the package's config plugin when the Android side declares its permission.

Do not report single-file logic as cross-cutting defects

Do not flag logic inside a single file in a single language—correctness-ios, correctness-android, or correctness-js own that work. Reporting it duplicates their findings and wastes coordination. Also do not flag intentional platform differences documented with @platform ios, Platform.OS branches, or TSDoc notes stating the API is platform-specific. Web implementations diverging from native where the TypeScript type already narrows by platform should not be flagged. Pre-existing divergence that is merely moved or reformatted should not be reported.

Give your agent this brain