reatom-review

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Reatom Review

Reatom 审查

Use this skill to validate an agent's work against Reatom v1001 practices. Be skeptical: the goal is to find incorrect behavior, misleading docs, stale API usage, weak tests, and patterns that only look plausible.
使用此技能验证Agent的工作是否符合Reatom v1001的实践规范。保持质疑态度:目标是找出错误行为、误导性文档、过时API用法、薄弱测试以及看似合理但存在问题的模式。

Reference

参考资料

Before validating API usage, extension options, or documentation claims, also load the
reatom
skill and read the relevant sections of REFERENCE.md. It is the canonical v1001 API reference. For implementation (not review), use the
reatom
skill directly.
在验证API用法、扩展选项或文档声明之前,请同时加载
reatom
技能并阅读REFERENCE.md的相关章节。这是v1001 API的权威参考。若要实现而非审查代码,请直接使用
reatom
技能。

Review Stance

审查立场

  • Lead with findings. Do not praise before checking correctness.
  • Criticize the reviewed work, not the author.
  • Treat REFERENCE.md as the local source of truth when it conflicts with generic frontend habits.
  • Quote concrete files/symbols and explain the failure mode.
  • Prefer one precise fix over vague advice.
  • If a change is acceptable only with a special reason, ask for that reason or mark it as a risk.
  • Do not approve Reatom code just because TypeScript compiles; review context propagation, cancellation, laziness, naming, and subscriptions.
  • 先列出发现的问题。在确认正确性之前不要先表扬。
  • 批评审查的工作内容,而非作者本人。
  • REFERENCE.md与通用前端习惯冲突时,以此文档为本地事实依据。
  • 引用具体的文件/符号并解释失败模式。
  • 优先给出精准的修复方案,而非模糊建议。
  • 如果某项变更仅在特殊理由下才被接受,请要求提供该理由或标记为风险项。
  • 不要仅因为TypeScript编译通过就批准Reatom代码;需审查上下文传播、取消机制、惰性执行、命名规范和订阅逻辑。

Mandatory Checks

强制检查项

  1. Async reads and queries:
    • For idempotent read/query data, expect
      computed(async () => ...).extend(withAsyncData(...))
      .
    • Flag mount-time fetches,
      effect
      fetches, refs, component-local async state, or imperative loaders unless the code has a clear non-query reason.
    • For mutations/commands, expect
      action(async () => ...).extend(withAsync(...))
      , plus
      withAbort
      or transactions when needed.
    • .status()
      is available only when
      withAsync
      /
      withAsyncData
      enables
      { status: true }
      . Otherwise prefer
      .ready()
      ,
      .pending()
      , and
      .error()
      .
    • .retry()
      on an action requires
      withAsync({ cacheParams: true })
      ; without it
      retry
      throws at call time. Computeds can retry without options.
    • Extension order matters:
      withAsync
      /
      withAsyncData
      must be applied before
      withCache
      (attaching
      withAsync
      after
      withCache
      throws). Review every
      .extend(...)
      chain for ordering, not just presence.
    • Async helper atoms are getters: use
      .data()
      ,
      .ready()
      ,
      .error()
      , and
      submit.error()
      . Do not render or destructure atom objects as inert values.
    • Status flags are properties, for example
      status.isPending
      , not functions. Use the target's
      .error()
      atom; do not invent
      status.error
      .
  2. wrap
    and async context:
    • Use
      await wrap(promise)
      at async boundaries that leave the Reatom frame (fetch, timers, DOM promises, etc.). After a wrapped await, the continuation is back in context — call atoms/actions directly.
    • Use
      wrap(fn)
      only when
      fn
      is passed to an external caller (DOM listener, timer, third-party callback). It returns a decorated function; it does not call
      fn
      . Flag bare
      wrap(() => atom.set(...))
      with no assignment/pass-through to an external callback.
    • Flag pointless
      wrap(() => atom.set(...))()
      inside actions/effects/async computeds — an immediate wrapped IIFE adds nothing; just call
      atom.set(...)
      in the Reatom frame.
    • Flag
      await wrap(fetch(url)).then(...)
      ; prefer wrapping the whole promise chain or wrapping each awaited step.
    • Flag
      .then(...)
      callbacks, DOM callbacks, timers,
      requestAnimationFrame
      , and external event listeners that call Reatom state without
      wrap
      or
      onEvent
      .
    • Check after every
      await
      : if later code calls atoms/actions from an async continuation, the awaited promise should usually be wrapped.
    • Prefer
      await wrap(onEvent(...))
      over raw event listeners when awaiting DOM or external events.
    • wrap()
      belongs in Reatom-aware actions/effects/computeds/callbacks, not inside plain reusable API helpers.
    • Downleveled async/await can break context propagation. Flag build/test targets that transform async code to
      .then()
      chains when strict context errors appear.
    • Do not ask to wrap callbacks passed into Reatom hooks such as
      withCallHook
      ; hooks already run in Reatom context.
  3. State modeling:
    • Writes go through
      .set(...)
      . Calling a reactive atom with arguments (
      counter(5)
      ) throws; calling a
      computed
      with arguments throws. Flag any positional-call writes; reads are zero-arg calls.
    • Action state (
      getCalls
      ,
      action()
      return list) is ephemeral — it is cleared in the next cleanup queue tick. Flag code that stores or later reads an action's call list as durable state; persist payloads into atoms instead.
    • Collection primitives (
      reatomMap
      ,
      reatomSet
      ,
      reatomArray
      ,
      reatomRecord
      ,
      reatomLinkedList
      ) update through their actions/immutable methods. Flag in-place mutation of their state (
      map().set(...)
      ,
      array().push(...)
      ): it skips invalidation and corrupts equality checks.
    • .extend(...)
      cannot replace the atom reference and cannot override existing keys — colliding method names throw at runtime. Flag extensions whose assigned keys shadow
      set
      ,
      subscribe
      ,
      extend
      , or earlier extension methods.
    • Mutable fields inside dynamic objects should be atomized.
    • Flag normalized parallel UI state like separate
      selectedIds
      ,
      checkedIds
      , or edit maps when item-local atoms would be clearer.
    • Action vs pure transform:
      • A function that only maps one data shape to another — no IO, no
        atom.set
        , no other action calls — is not an action. Use a plain function or
        computed
        .
      • A function that performs side effects (network, storage, timers, DOM, logging) or changes Reatom state (
        atom.set
        , calling actions) is an action and should be named.
    • Flag thin computed wrappers: a
      computed(() => helper(model))
      where
      helper
      only reads atom getters on the model and returns a derived value. The helper duplicates the computed without adding reuse outside Reatom. Put the derivation in the computed body, or attach it with
      withComputed
      /
      .extend
      on the parent — do not split into a plain helper plus a pass-through computed.
    • Flag plain helpers that take atom-bearing models and call
      .data()
      / atom getters for reactive derivations. They hide the reactive graph, invite thin computed wrappers, and are unsafe if called outside a computed/action frame.
    • Direct
      atom.set
      is fine for local/simple updates. Flag "identity" actions that only forward values to atoms.
    • Complex transformations with side effects and multi-step flows should be actions with names.
    • Relative states and actions should be grouped on their parent with
      .extend(...)
      or
      withActions(...)
      , not scattered as sibling exports.
    • Follow core patterns like
      reatomBoolean
      and
      reatomRoute
      : create the parent atom, then attach related methods, child computeds, loaders, route factories, and helpers through
      .extend
      .
    • For scoped state, consider the computed factory pattern: a
      computed
      reads the scope key and returns the atoms/actions/forms for that scope, so changing the key replaces the inner graph.
    • Computed factories are general, not router-only. Look for them around selected entities, edit sessions, modals, tabs, and any named unit of work with its own state lifetime.
    • Choose the factory contract intentionally: return
      null
      when inactive scope is ordinary state, or throw when reading outside the named scope is a bug.
  4. Naming and traceability:
    • Atoms, computed values, effects, and actions should be named.
    • Atom/action/model factories must use the
      reatom*
      prefix (for example
      reatomFolderTreeNodeUi
      ,
      reatomGalleryImage
      ,
      reatomUser
      ), not
      get*
      ,
      create*
      , or other generic verbs. Flag factory functions that allocate named atoms, computeds, effects, or actions but read like plain getters or constructors.
    • Nested/dynamic names should preserve structure, for example
      users.page
      ,
      users#${id}.name
      , or
      ${target.name}.ready
      .
    • Prefix the trace name of hot-path or noisy relative states and actions with
      _
      on the segment: atoms, computed, effects, and actions tied to pointer move, scroll, resize, or animation ticks. Examples:
      lightbox._panMove
      ,
      lightbox._controlsActivity
      ,
      lightbox._hideControlsAfterInactivity
      ,
      imageGrid._width
      . Keeps logs readable.
    • Flag anonymous primitives in shared models and examples.
  5. Effects, hooks, and subscriptions:
    • Use
      computed
      for derived state and
      effect
      for side effects.
    • computed
      is lazy; check that data expected to load has a subscriber or an explicit route/render path.
    • Unsubscribed computeds revalidate on every read; subscribed ones are push-cached. Flag hot loops reading heavy unsubscribed computeds and expensive computeds without a subscriber on a hot path — consider
      withMemo
      or a subscription.
    • Subscriber callbacks and queue effects flush asynchronously (microtask). Flag code (especially tests) that asserts a subscriber fired synchronously after
      .set(...)
      ; await a microtask/
      sleep(0)
      or read the atom directly.
    • Tests sharing the default global context must isolate state with
      context.reset()
      (or
      context.start
      scoping). Flag test suites where atoms leak state between cases.
    • effect
      is NOT lazy: it self-subscribes at creation, so a module-level
      effect(...)
      connects eagerly at import and runs forever until
      .unsubscribe()
      .
    • Flag component/feature-scoped effects lifted to module scope to "put state in the model": they lose mount/visibility scoping and keep running (and may loop on timers) while the feature is closed.
    • Prefer starting feature-scoped effects at the feature boundary, in this order:
      1. Route loader / route
        render
        init (best): the loader or route-owned init action creates the effect when the feature scope opens; abort/disconnect when the route unmounts or scope changes.
      2. Explicit named
        init
        /
        start
        action on the feature model: called once when the feature opens (lightbox open, panel mount, session start).
      3. Component mount /
        ref
        cleanup (acceptable but weaker architecture): create the effect in the mounted scope and call
        .unsubscribe()
        on teardown.
    • withConnectHook
      +
      effect
      is a good pattern only when the effect does not read the hook target atom, directly or indirectly. If the effect depends on the same atom that owns the connect hook, it can create an infinite connect/subscribe loop. Flag
      target.extend(withConnectHook(() => effect(() => target())))
      and similar.
    • When
      withConnectHook
      is the right tool, attach it to a scope anchor the effect must not depend on (for example
      lightboxOpen
      for a slideshow timer that reads
      slideshowPlaying
      , not
      withConnectHook
      on
      slideshowPlaying
      itself).
    • Flag a component that calls
      .subscribe()
      /
      .unsubscribe()
      on an already module-level
      effect
      : the effect self-subscribed at creation, so the component subscription is redundant and the original self-subscription leaks (the effect never disconnects on unmount).
    • Prefer
      reatomObservable
      to bridge external push sources (
      ResizeObserver
      ,
      IntersectionObserver
      ,
      matchMedia
      , sockets) into a connection-driven atom, instead of an
      effect
      that wires the observer and writes a sibling result atom.
    • Use
      withConnectHook
      for lazy external subscriptions/polling that do not depend on the hook target; verify cleanup and abort behavior.
    • Do not use
      withChangeHook
      to synchronize atoms with other atoms; prefer
      computed
      or
      withComputed
      .
  6. Routing:
    • Prefer
      reatomRoute
      loaders for route data; loaders are async computeds with
      withAsyncData
      .
    • A route loader is the quintessence of computed factory: route match/params name the scope, and loader-created models are replaced when that scope changes.
    • Route loader / route-owned init is also the preferred place to start feature-scoped effects (timers, polling, session wiring). Avoid module-level effects and avoid
      withConnectHook
      on an atom the effect reads.
    • Flag components that manually check
      route.match()
      and return
      null
      ; prefer the route
      render
      option and layouts/outlets.
    • Validate URL params/search with schemas when types matter, and transform string params instead of assuming numbers.
    • Confirm route navigation uses
      .go(...)
      and links use
      .path(...)
      where SPA interception is expected.
    • Route paths have no leading
      /
      ;
      route.go()
      takes params, not a path string.
    • Loader takes one merged params/search object.
      (params, search)
      is wrong.
    • render
      is a route option; after construction
      route.render
      is a computed output, not an assignable callback.
    • Callbacks created inside
      route.render(self)
      and passed to UI still need
      wrap(...)
      .
    • Auth, redirect, and feature gates belong in
      params()
      / parent guards, not nullable loader payloads.
    • Redirects in guards or URL hooks must be idempotent and prove URL ownership before
      .go(..., true)
      .
    • For index routes under layouts, prefer
      exact()
      for active state;
      match()
      stays true for descendants.
  7. Abort, sampling, and concurrency:
    • take(...)
      and
      onEvent(...)
      return promises; inside async actions/effects they should be
      await wrap(...)
      .
    • race(...)
      expects controlled promises from
      abortVar.createAndRun
      , not plain promises.
    • Fetches in abortable Reatom contexts should pass
      signal: abortVar.require().signal
      .
    • For computed factories that return a synchronous model with async actions/effects/polling, expect
      withAbort()
      on the outer factory; async computeds/loaders with
      withAsyncData
      already have abort support.
    • Check factory dependencies: every read can recreate the inner model. Split volatile inputs, move derivations out, or use
      peek
      /
      memo
      when only some inputs should rebuild the scoped graph.
    • Prefer
      withAbort()
      plus
      await wrap(sleep(ms))
      for debounce-like behavior.
    • Flag component/effect timer bookkeeping (
      setTimeout
      ,
      setInterval
      , local timer handles, manual
      clear*
      , unmount
      try/catch
      ). Prefer abortable
      effect(async () => { await wrap(sleep(ms)); ... })
      for state-driven timers and
      action(...).extend(withAbort())
      for debounce/throttle commands.
    • Do not treat abort rejections as business errors unless the flow explicitly needs that.
  8. Forms:
    • Prefer
      reatomForm
      ,
      reatomFieldSet
      , and
      reatomField
      for forms.
    • Async validation should use
      wrap
      , and dependent validation may read other fields reactively.
    • Submit handlers should throw errors for
      submit.error()
      and keep payload types explicit.
    • Route-bound forms should usually be created in route/scoped factories, not as shared module-level singletons.
    • Prefer
      field.value()
      /
      field.change(value)
      for user-facing field values.
      field()
      is the underlying state.
    • Put submit mutations in
      reatomForm({ onSubmit })
      and call
      form.submit()
      ; separate raw submit actions can bypass validation.
  9. Persistence and URL sync:
    • Prefer Reatom helpers such as
      withLocalStorage
      ,
      withSessionStorage
      ,
      withSearchParams
      , or storage-specific persistence extensions over ad hoc effects.
    • Check parse/serialize behavior for URL state and persisted state, especially defaults and invalid input.
    • Persist keys must be unique per atom; flag duplicated keys across models and shape changes without a bumped
      version
      + migration.
    • For cached queries, check
      withCache
      options against intent:
      swr
      semantics,
      staleTime
      /length limits, and
      ignoreAbort
      defaults (true only for empty params). Flag cache on non-idempotent actions.
  10. Migration correctness:
  • Flag v3-era stale APIs:
    ctx.schedule
    ,
    ctx.spy
    ,
    ctx.get
    ,
    reatomAsync
    ,
    reatomResource
    ,
    reaction
    ,
    atom.onChange
    ,
    onConnect
    ,
    withConcurrency
    , and
    onCtxAbort
    .
  • Prefer current equivalents:
    wrap
    , direct atom reads,
    peek
    ,
    action(...).extend(withAsync())
    ,
    computed(...).extend(withAsyncData())
    ,
    effect
    ,
    withChangeHook
    ,
    withConnectHook
    ,
    withAbort
    , and
    abortVar.subscribe
    .
  1. Docs and examples:
  • Docs must not present antipatterns as recommended code.
  • If showing bad code, label it clearly and immediately provide the recommended Reatom version.
  • Check that imports match examples, identifiers used in snippets exist, and narrative claims match the code.
  • Flag mismatches between headings, prose, code, and API behavior.
  • Examples should avoid unsafe casts, anonymous atoms/actions, and fake APIs that hide the important Reatom pattern.
  1. React and adapters:
  • Components that call atom getters should be
    reatomComponent
    ;
    useAtom
    results are plain values, not callable getters.
  • Handwritten UI callbacks that read/write atoms or call actions need
    wrap(...)
    , including third-party control callbacks.
  • Do not call
    wrap(...)
    directly in JSX of a plain function component; create wrapped callbacks inside a Reatom frame or pass them down.
  • Passing atoms as props is valid Reatom decoupling. Do not reject it from Redux intuition.
  • DOM
    ref
    callbacks, observer notifications, and other non-Reatom entry points that write atoms need a Reatom frame:
    context.start(() => ...)
    ,
    wrap(...)
    , or
    onEvent(...)
    . Flag bare
    atom.set
    from a
    ref
    or
    ResizeObserver
    callback.
  1. Browser resource ownership:
  • Treat
    URL.createObjectURL
    results as resources owned by the async computed (or connect hook) that produced them; the URL must be revoked when that computed re-runs/aborts or the model disconnects. Flag object URLs stored as plain strings with no revocation path.
  • Tie
    createImageBitmap
    /
    ImageBitmap.close()
    ,
    OffscreenCanvas
    ,
    Worker
    (and worker pools),
    ResizeObserver
    /
    IntersectionObserver
    , and
    matchMedia
    listeners to Reatom lifecycle (async-computed abort,
    withConnectHook
    /
    withDisconnectHook
    cleanup,
    abortVar.subscribe
    , or
    onEvent
    ).
  • Flag hand-rolled
    dispose()
    /
    cleanup()
    methods that callers must remember to invoke when
    withConnectHook
    /
    withDisconnectHook
    / async-computed abort already express ownership.
  • Shared worker/decoder pools should be named service models with explicit connect/disconnect, so folder/session/route resets terminate stale work instead of leaking it.
  1. Module structure and cycles:
  • Flag
    await import('./peer')
    or
    require('./peer')
    used inside an action/effect only to break a circular import. Dynamic
    import()
    is for code-splitting, not cycle breaking; it also turns a sync flow async and hides the dependency from tracing.
  • The real fix is restructuring: move shared coordination into a higher-level orchestration module, invert the dependency, or pass the dependency in. A reset/teardown that must touch several peers usually belongs in an orchestration action that imports them statically.
  1. 异步读取与查询:
    • 对于幂等的读取/查询数据,应使用
      computed(async () => ...).extend(withAsyncData(...))
    • 除非代码有明确的非查询理由,否则标记挂载时的请求、
      effect
      请求、refs、组件本地异步状态或命令式加载器。
    • 对于变更/命令操作,应使用
      action(async () => ...).extend(withAsync(...))
      ,必要时添加
      withAbort
      或事务处理。
    • 仅当
      withAsync
      /
      withAsyncData
      启用
      { status: true }
      时,
      .status()
      才可用。否则优先使用
      .ready()
      .pending()
      .error()
    • action上的
      .retry()
      需要
      withAsync({ cacheParams: true })
      ;若无此配置,
      retry
      在调用时会抛出错误。Computed无需配置即可重试。
    • 扩展顺序至关重要:
      withAsync
      /
      withAsyncData
      必须在
      withCache
      之前应用(在
      withCache
      之后附加
      withAsync
      会抛出错误)。审查每个
      .extend(...)
      链的顺序,而非仅检查是否存在。
    • 异步辅助atom是getter:使用
      .data()
      .ready()
      .error()
      submit.error()
      。不要将atom对象作为惰性值渲染或解构。
    • 状态标志是属性,例如
      status.isPending
      ,而非函数。使用目标的
      .error()
      atom;不要自行定义
      status.error
  2. wrap
    与异步上下文:
    • 在离开Reatom框架的异步边界(如fetch、定时器、DOM promise等)处,使用
      await wrap(promise)
      。在包装后的await之后,后续代码会回到上下文环境中——可直接调用atoms/actions。
    • 仅当
      fn
      被传递给外部调用者(DOM监听器、定时器、第三方回调)时才使用
      wrap(fn)
      。它返回一个装饰后的函数;不会直接调用
      fn
      。标记未赋值/传递给外部回调的裸
      wrap(() => atom.set(...))
    • 标记在actions/effects/async computeds内部无意义的
      wrap(() => atom.set(...))()
      ——立即执行的包装IIFE没有任何作用;直接在Reatom框架内调用
      atom.set(...)
      即可。
    • 标记
      await wrap(fetch(url)).then(...)
      ;优先包装整个promise链或每个await步骤。
    • 标记未使用
      wrap
      onEvent
      就调用Reatom状态的
      .then(...)
      回调、DOM回调、定时器、
      requestAnimationFrame
      和外部事件监听器。
    • 检查每个
      await
      之后的代码:如果后续代码从异步续调用atoms/actions,通常应包装被await的promise。
    • 当等待DOM或外部事件时,优先使用
      await wrap(onEvent(...))
      而非原始事件监听器。
    • wrap()
      应在感知Reatom的actions/effects/computeds/callbacks中使用,而非在普通可复用API助手内部。
    • 降级处理的async/await可能会破坏上下文传播。当出现严格的上下文错误时,标记将异步代码转换为
      .then()
      链的构建/测试目标。
    • 不要要求包装传递给Reatom钩子(如
      withCallHook
      )的回调;钩子已在Reatom上下文中运行。
  3. 状态建模:
    • 写入操作需通过
      .set(...)
      完成。给响应式atom传递参数调用(如
      counter(5)
      )会抛出错误;调用
      computed
      传递参数也会抛出错误。标记任何位置调用式的写入操作;读取操作是无参数调用。
    • Action状态(
      getCalls
      action()
      返回的列表)是临时的——会在下一个清理队列tick中被清除。标记将action的调用列表存储或后续读取为持久状态的代码;应将负载持久化到atoms中。
    • 集合原语(
      reatomMap
      reatomSet
      reatomArray
      reatomRecord
      reatomLinkedList
      )通过其actions/不可变方法更新。标记对其状态进行原地变更的代码(如
      map().set(...)
      array().push(...)
      ):这会跳过失效检查并破坏相等性验证。
    • .extend(...)
      无法替换atom引用,也无法覆盖现有键——冲突的方法名会在运行时抛出错误。标记扩展中覆盖
      set
      subscribe
      extend
      或早期扩展方法的键。
    • 动态对象内部的可变字段应被atom化。
    • 当项级本地atom更清晰时,标记并行UI状态,如单独的
      selectedIds
      checkedIds
      或编辑映射。
    • Action与纯转换的区别:
      • 仅将一种数据形状映射为另一种的函数——无IO操作、无
        atom.set
        、无其他action调用——不是action。使用普通函数或
        computed
      • 执行副作用(网络、存储、定时器、DOM、日志)或更改Reatom状态(
        atom.set
        、调用actions)的函数action,且应命名。
    • 标记薄computed包装器
      computed(() => helper(model))
      中的
      helper
      仅读取model上的atom getters并返回派生值。该助手复制了computed的功能,但未在Reatom之外增加复用性。将派生逻辑放在computed体内,或通过父级的
      withComputed
      /
      .extend
      附加——不要拆分为普通助手加透传computed。
    • 标记接受带有atom的模型并调用
      .data()
      /atom getters进行响应式派生的普通助手。它们隐藏了响应式图,容易产生薄computed包装器,且在computed/action框架外调用时不安全。
    • 直接使用
      atom.set
      适用于本地/简单更新。标记仅将值转发给atoms的“标识”actions。
    • 带有副作用和多步骤流程的复杂转换应使用命名的actions。
    • 相关状态和actions应通过
      .extend(...)
      withActions(...)
      分组在父级上,而非分散为同级导出。
    • 遵循核心模式,如
      reatomBoolean
      reatomRoute
      :创建父atom,然后通过
      .extend
      附加相关方法、子computeds、加载器、路由工厂和助手。
    • 对于作用域状态,考虑computed工厂模式:
      computed
      读取作用域键并返回该作用域的atoms/actions/forms,因此更改键会替换内部图。
    • Computed工厂是通用的,不仅限于路由。在所选实体、编辑会话、模态框、标签页以及任何具有自身状态生命周期的命名工作单元中寻找它们。
    • 有意选择工厂契约:当非活动作用域是普通状态时返回
      null
      ,或当读取命名作用域外的内容是bug时抛出错误。
  4. 命名与可追踪性:
    • Atoms、computed值、effects和actions应命名。
    • Atom/action/model工厂必须使用
      reatom*
      前缀(例如
      reatomFolderTreeNodeUi
      reatomGalleryImage
      reatomUser
      ),而非
      get*
      create*
      或其他通用动词。标记分配命名atoms、computeds、effects或actions但读起来像普通getter或构造函数的工厂函数。
    • 嵌套/动态名称应保留结构,例如
      users.page
      users#${id}.name
      ${target.name}.ready
    • 对热路径或嘈杂的相关状态和actions的跟踪名称,在段前添加
      _
      前缀:与指针移动、滚动、调整大小或动画tick相关的atoms、computed、effects和actions。示例:
      lightbox._panMove
      lightbox._controlsActivity
      lightbox._hideControlsAfterInactivity
      imageGrid._width
      。保持日志可读性。
    • 标记共享模型和示例中的匿名原语。
  5. Effects、钩子与订阅:
    • 使用
      computed
      处理派生状态,使用
      effect
      处理副作用。
    • computed
      是惰性的;检查预期加载的数据是否有订阅者或明确的路由/渲染路径。
    • 未订阅的computeds在每次读取时都会重新验证;已订阅的computeds是推送缓存的。标记读取繁重的未订阅computeds的热循环,以及热路径上没有订阅者的昂贵computeds——考虑使用
      withMemo
      或订阅。
    • 订阅者回调和队列effects会异步刷新(微任务)。标记在
      .set(...)
      后同步断言订阅者已触发的代码(尤其是测试);等待微任务/
      sleep(0)
      或直接读取atom。
    • 共享默认全局上下文的测试必须使用
      context.reset()
      (或
      context.start
      作用域)隔离状态。标记atoms在测试用例之间泄漏状态的测试套件。
    • effect
      不是惰性的:它在创建时自动订阅,因此模块级的
      effect(...)
      会在导入时立即连接,并一直运行直到调用
      .unsubscribe()
    • 标记为“将状态放入模型”而提升到模块作用域的组件/功能作用域effects:它们失去了挂载/可见性作用域,在功能关闭后仍会继续运行(可能会在定时器上循环)。
    • 优先在功能边界启动功能作用域effects,顺序如下:
      1. 路由加载器/路由
        render
        初始化(最佳):加载器或路由所属的初始化action在功能作用域打开时创建effect;在路由卸载或作用域更改时中止/断开连接。
      2. 功能模型上显式命名的
        init
        /
        start
        action:在功能打开时调用一次(如灯箱打开、面板挂载、会话开始)。
      3. 组件挂载/
        ref
        清理(可接受但架构较弱):在挂载作用域中创建effect,并在销毁时调用
        .unsubscribe()
    • withConnectHook
      +
      effect
      是一种良好模式,仅当effect直接或间接读取钩子目标atom时适用。如果effect依赖于拥有connect钩子的同一atom,可能会创建无限连接/订阅循环。标记
      target.extend(withConnectHook(() => effect(() => target())))
      及类似代码。
    • withConnectHook
      是合适的工具时,将其附加到effect不得依赖的作用域锚点(例如,对于读取
      slideshowPlaying
      的幻灯片定时器,使用
      lightboxOpen
      而非
      slideshowPlaying
      上的
      withConnectHook
      )。
    • 标记对已存在的模块级
      effect
      调用
      .subscribe()
      /
      .unsubscribe()
      的组件:effect在创建时已自动订阅,因此组件的订阅是冗余的,原始的自动订阅会泄漏(effect在卸载时永远不会断开连接)。
    • 优先使用
      reatomObservable
      将外部推送源(
      ResizeObserver
      IntersectionObserver
      matchMedia
      、套接字)桥接到连接驱动的atom,而非使用
      effect
      连接观察者并写入同级结果atom。
    • 使用
      withConnectHook
      处理不依赖钩子目标的惰性外部订阅/轮询;验证清理和中止行为。
    • 不要使用
      withChangeHook
      同步atoms与其他atoms;优先使用
      computed
      withComputed
  6. 路由:
    • 优先使用
      reatomRoute
      加载器处理路由数据;加载器是带有
      withAsyncData
      的异步computeds。
    • 路由加载器是computed工厂的典型代表:路由匹配/参数命名作用域,当作用域更改时,加载器创建的模型会被替换。
    • 路由加载器/路由所属的初始化也是启动功能作用域effects(定时器、轮询、会话连接)的首选位置。避免模块级effects和在effect读取的atom上使用
      withConnectHook
    • 标记手动检查
      route.match()
      并返回
      null
      的组件;优先使用路由
      render
      选项和布局/出口。
    • 当类型重要时,使用模式验证URL参数/搜索内容,并转换字符串参数而非假设为数字。
    • 确认路由导航使用
      .go(...)
      ,链接在预期SPA拦截时使用
      .path(...)
    • 路由路径无前导
      /
      route.go()
      接受参数,而非路径字符串。
    • 加载器接受一个合并的参数/搜索对象。
      (params, search)
      是错误的写法。
    • render
      是路由选项;构造后
      route.render
      是computed输出,而非可赋值的回调。
    • route.render(self)
      内部创建并传递给UI的回调仍需
      wrap(...)
    • 认证、重定向和功能网关应放在
      params()
      /父级守卫中,而非可空的加载器负载中。
    • 守卫或URL钩子中的重定向必须是幂等的,并在调用
      .go(..., true)
      之前证明URL所有权。
    • 对于布局下的索引路由,优先使用
      exact()
      获取活动状态;
      match()
      对子路由仍为true。
  7. 中止、采样与并发:
    • take(...)
      onEvent(...)
      返回promises;在异步actions/effects内部应使用
      await wrap(...)
    • race(...)
      期望来自
      abortVar.createAndRun
      的受控promises,而非普通promises。
    • 可中止Reatom上下文中的请求应传递
      signal: abortVar.require().signal
    • 对于返回带有异步actions/effects/轮询的同步模型的computed工厂,期望外部工厂使用
      withAbort()
      ;带有
      withAsyncData
      的异步computeds/加载器已支持中止。
    • 检查工厂依赖:每次读取都可能重新创建内部模型。拆分易变输入、移出派生逻辑,或仅当某些输入应重建作用域图时使用
      peek
      /
      memo
    • 优先使用
      withAbort()
      await wrap(sleep(ms))
      实现类似防抖的行为。
    • 标记组件/effect的定时器簿记(
      setTimeout
      setInterval
      、本地定时器句柄、手动
      clear*
      、卸载时的
      try/catch
      )。优先使用可中止的
      effect(async () => { await wrap(sleep(ms)); ... })
      处理状态驱动的定时器,使用
      action(...).extend(withAbort())
      处理防抖/节流命令。
    • 除非流程明确需要,否则不要将中止拒绝视为业务错误。
  8. 表单:
    • 优先使用
      reatomForm
      reatomFieldSet
      reatomField
      处理表单。
    • 异步验证应使用
      wrap
      ,依赖验证可响应式读取其他字段。
    • 提交处理程序应抛出错误以触发
      submit.error()
      ,并保持负载类型明确。
    • 路由绑定的表单通常应在路由/作用域工厂中创建,而非作为共享模块级单例。
    • 优先使用
      field.value()
      /
      field.change(value)
      处理用户可见的字段值。
      field()
      是底层状态。
    • 将提交变更放在
      reatomForm({ onSubmit })
      中并调用
      form.submit()
      ;单独的原始提交action可能会绕过验证。
  9. 持久化与URL同步:
    • 优先使用Reatom助手,如
      withLocalStorage
      withSessionStorage
      withSearchParams
      或特定存储的持久化扩展,而非临时effects。
    • 检查URL状态和持久化状态的解析/序列化行为,尤其是默认值和无效输入。
    • 持久化键必须每个atom唯一;标记跨模型重复的键以及未升级
      version
      +迁移的形状变更。
    • 对于缓存查询,检查
      withCache
      选项是否符合意图:
      swr
      语义、
      staleTime
      /长度限制,以及
      ignoreAbort
      默认值(仅空参数时为true)。标记非幂等actions上的缓存。
  10. 迁移正确性:
  • 标记v3时代的过时API:
    ctx.schedule
    ctx.spy
    ctx.get
    reatomAsync
    reatomResource
    reaction
    atom.onChange
    onConnect
    withConcurrency
    onCtxAbort
  • 优先使用当前等效项:
    wrap
    、直接atom读取、
    peek
    action(...).extend(withAsync())
    computed(...).extend(withAsyncData())
    effect
    withChangeHook
    withConnectHook
    withAbort
    abortVar.subscribe
  1. 文档与示例:
  • 文档不得将反模式作为推荐代码呈现。
  • 如果展示错误代码,应清晰标记并立即提供推荐的Reatom版本。
  • 检查导入是否与示例匹配,代码片段中使用的标识符是否存在,以及叙述声明是否与代码一致。
  • 标记标题、正文、代码和API行为之间的不匹配。
  • 示例应避免不安全的类型转换、匿名atoms/actions以及隐藏重要Reatom模式的虚假API。
  1. React与适配器:
  • 调用atom getters的组件应为
    reatomComponent
    useAtom
    的结果是普通值,而非可调用的getters。
  • 读取/写入atoms或调用actions的手写UI回调需要
    wrap(...)
    ,包括第三方控件回调。
  • 不要在普通函数组件的JSX中直接调用
    wrap(...)
    ;在Reatom框架内创建包装后的回调或向下传递。
  • 将atoms作为props传递是有效的Reatom解耦方式。不要基于Redux直觉拒绝这种做法。
  • DOM
    ref
    回调、观察者通知和其他非Reatom入口点写入atoms时需要Reatom框架:
    context.start(() => ...)
    wrap(...)
    onEvent(...)
    。标记来自
    ref
    ResizeObserver
    回调的裸
    atom.set
  1. 浏览器资源所有权:
  • URL.createObjectURL
    的结果视为生成它的异步computed(或connect钩子)所拥有的资源;当该computed重新运行/中止或模型断开连接时,必须撤销该URL。标记存储为普通字符串且无撤销路径的对象URL。
  • createImageBitmap
    /
    ImageBitmap.close()
    OffscreenCanvas
    Worker
    (及worker池)、
    ResizeObserver
    /
    IntersectionObserver
    matchMedia
    监听器与Reatom生命周期绑定(异步computed中止、
    withConnectHook
    /
    withDisconnectHook
    清理、
    abortVar.subscribe
    onEvent
    )。
  • 标记手动实现的
    dispose()
    /
    cleanup()
    方法,而调用者必须记住调用这些方法,实际上
    withConnectHook
    /
    withDisconnectHook
    /异步computed中止已能表达所有权。
  • 共享worker/解码器池应为命名服务模型,具有明确的连接/断开逻辑,以便文件夹/会话/路由重置终止陈旧工作而非泄漏。
  1. 模块结构与循环依赖:
  • 标记在action/effect内部使用
    await import('./peer')
    require('./peer')
    仅为了打破循环依赖的情况。动态
    import()
    用于代码分割,而非打破循环;它还会将同步流程转为异步,并隐藏依赖关系以避免追踪。
  • 真正的修复是重构:将共享协调逻辑移到更高层级的编排模块,反转依赖关系,或传入依赖项。必须触及多个同级模块的重置/销毁逻辑通常属于静态导入它们的编排action。

Document Mismatch Checks

文档不匹配检查

When reviewing docs, tutorials, READMEs, generated summaries, or examples, actively search for these mismatches:
  • Claim says "query/resource/data loading", but code uses
    effect
    , component lifecycle, refs, or manual status atoms instead of
    computed(...).extend(withAsyncData())
    .
  • Claim says "mutation/command", but code uses async
    computed
    for non-idempotent writes instead of
    action(...).extend(withAsync())
    .
  • Claim says "abort-aware" or "race-safe", but code lacks
    withAbort
    ,
    withAsyncData
    , route loader behavior, or
    wrap
    around awaited work.
  • Claim says sync Reatom writes are wrapped, but code uses
    wrap(() => atom.set(...))
    without calling/passing the returned function.
  • Claim says code preserves context with
    wrap
    , but it uses
    wrap(() => atom.set(...))()
    inside an action/effect/computed where a direct
    atom.set(...)
    already runs in frame.
  • Claim says "abortable fetch", but code does not pass
    signal: abortVar.require().signal
    to
    fetch
    .
  • Claim uses
    .status()
    , but the action/computed was not extended with
    { status: true }
    .
  • Claim says "route loader", but data fetching is placed in a rendered component or guarded with
    route.match()
    .
  • Claim shows route loader params, but the code uses
    (params, search)
    instead of one merged object.
  • Claim says route redirect/auth gate, but the code returns nullable loader data instead of blocking in
    params()
    / parent guards.
  • Claim says "current Reatom", but snippet uses legacy
    ctx
    ,
    reatomResource
    ,
    reatomAsync
    ,
    reaction
    , or
    onConnect
    .
  • Claim describes a model with relative state/actions, but snippets export separate sibling atoms/actions instead of grouping them through
    .extend
    .
  • Claim shows an atom/action factory, but the function is named
    get*
    /
    create*
    instead of
    reatom*
    .
  • Claim shows a pure mapper/formatter/normalizer wrapped in
    action(...)
    , but the function has no IO and does not write state.
  • Claim shows derived state as
    computed(() => resolveX(model))
    with a plain
    resolveX
    helper that only reads atom getters — split indirection with no non-Reatom reuse.
  • Claim says form submit validation, but the code bypasses
    form.submit()
    with a separate raw submit action.
  • Heading/prose says one atom/action name while the snippet uses another.
  • Snippet omits essential imports such as
    wrap
    ,
    computed
    ,
    withAsyncData
    ,
    action
    ,
    withAsync
    , or
    reatomRoute
    .
  • Example uses route/search params as typed numbers without schema transform/coercion.
  • Example shows bad code without a "bad/problem" label and a corrected version nearby.
  • Claim says an effect is "scoped to the screen/feature", but it is a module-level
    effect(...)
    that self-subscribes at import and is only nominally re-subscribed from a component.
  • Claim says "moved state into the model", but the move turned a mount-scoped effect into an eager module-level effect with different lifetime semantics.
  • Claim says an effect is "connect-hook scoped", but the effect reads the hook target atom (directly or indirectly), which can cause an infinite connect/subscribe loop.
  • Claim says "no leaks / cleaned up on unmount", but object URLs, bitmaps, workers, or observers rely on a manual
    dispose()
    instead of connect-hook/abort ownership.
  • Claim says modules are decoupled, but cycles are hidden behind
    await import()
    /
    require()
    inside actions.
在审查文档、教程、README、生成的摘要或示例时,主动寻找以下不匹配情况:
  • 声明称“查询/资源/数据加载”,但代码使用
    effect
    、组件生命周期、refs或手动状态atom而非
    computed(...).extend(withAsyncData())
  • 声明称“变更/命令”,但代码使用异步
    computed
    处理非幂等写入而非
    action(...).extend(withAsync())
  • 声明称“支持中止”或“竞争安全”,但代码缺少
    withAbort
    withAsyncData
    、路由加载器行为或在await工作周围使用
    wrap
  • 声明称同步Reatom写入已被包装,但代码使用
    wrap(() => atom.set(...))
    却未调用/传递返回的函数。
  • 声明称代码使用
    wrap
    保留上下文,但在action/effect/computed内部使用
    wrap(() => atom.set(...))()
    ,而直接调用
    atom.set(...)
    已在框架内运行。
  • 声明称“可中止的请求”,但代码未将
    signal: abortVar.require().signal
    传递给
    fetch
  • 声明使用
    .status()
    ,但action/computed未使用
    { status: true }
    扩展。
  • 声明称“路由加载器”,但数据请求放在已渲染的组件中或使用
    route.match()
    保护。
  • 声明展示路由加载器参数,但代码使用
    (params, search)
    而非一个合并对象。
  • 声明称路由重定向/认证网关,但代码返回可空的加载器数据而非在
    params()
    /父级守卫中阻止。
  • 声明称“当前Reatom”,但代码片段使用旧版
    ctx
    reatomResource
    reatomAsync
    reaction
    onConnect
  • 声明描述带有相关状态/actions的模型,但代码片段导出单独的同级atoms/actions而非通过
    .extend
    分组。
  • 声明展示atom/action工厂,但函数命名为
    get*
    /
    create*
    而非
    reatom*
  • 声明展示纯映射器/格式化器/标准化器包装在
    action(...)
    中,但该函数无IO操作且不写入状态。
  • 声明展示派生状态为
    computed(() => resolveX(model))
    ,其中普通
    resolveX
    助手仅读取atom getters——拆分了间接层但无Reatom之外的复用性。
  • 声明称表单提交验证,但代码绕过
    form.submit()
    使用单独的原始提交action。
  • 标题/正文使用一个atom/action名称,而代码片段使用另一个。
  • 代码片段省略必要的导入,如
    wrap
    computed
    withAsyncData
    action
    withAsync
    reatomRoute
  • 示例将路由/搜索参数作为类型化数字使用,而未进行模式转换/强制类型转换。
  • 示例展示错误代码但未标记“错误/问题”标签,且附近未提供修正版本。
  • 声明称effect“作用域到屏幕/功能”,但它是模块级
    effect(...)
    ,在导入时自动订阅,仅从组件名义上重新订阅。
  • 声明称“将状态移到模型中”,但移动将挂载作用域的effect转为了具有不同生命周期语义的急切模块级effect。
  • 声明称effect“作用域到connect钩子”,但effect读取钩子目标atom(直接或间接),这可能导致无限连接/订阅循环。
  • 声明称“无泄漏/在卸载时清理”,但对象URL、位图、workers或观察者依赖手动
    dispose()
    而非connect钩子/中止所有权。
  • 声明称模块已解耦,但循环依赖隐藏在action内部的
    await import()
    /
    require()
    之后。

Typical Mismatches And Fixes

典型不匹配与修复方案

Query Implemented As Imperative Effect

查询实现为命令式Effect

Problem:
ts
const users = atom<User[]>([], 'users')

effect(async () => {
  users.set(await api.getUsers(page()))
}, 'users.fetch')
Fix:
ts
const users = computed(async () => {
  return await wrap(api.getUsers(page()))
}, 'users').extend(withAsyncData({ initState: [] }))
Why: query data should be lazy, abort-aware, and expose
data
,
ready
,
error
,
status
,
retry
, and
reset
.
问题:
ts
const users = atom<User[]>([], 'users')

effect(async () => {
  users.set(await api.getUsers(page()))
}, 'users.fetch')
修复:
ts
const users = computed(async () => {
  return await wrap(api.getUsers(page()))
}, 'users').extend(withAsyncData({ initState: [] }))
原因:查询数据应是惰性的、支持中止的,并暴露
data
ready
error
status
retry
reset

wrap
Chained Incorrectly

wrap
链式调用错误

Problem:
ts
const response = await wrap(fetch(url)).then((res) => res.json())
data.set(response)
Fix:
ts
const response = await wrap(fetch(url))
const payload: Payload = await wrap(response.json())
data.set(payload)
Why: each async boundary is visible to Reatom and preserves tracing/cancellation.
问题:
ts
const response = await wrap(fetch(url)).then((res) => res.json())
data.set(response)
修复:
ts
const response = await wrap(fetch(url))
const payload: Payload = await wrap(response.json())
data.set(payload)
原因:每个异步边界对Reatom可见,并保留追踪/取消机制。

wrap
Used As A Statement (Function Not Called)

wrap
用作语句(函数未调用)

Problem:
ts
} finally {
  wrap(() => activeRequests.set((count) => count - 1))
}
Fix:
ts
} finally {
  activeRequests.set((count) => count - 1)
}
Alternative fix when the callback is passed to an external API:
ts
button.addEventListener(
  'click',
  wrap(() => counter.set((value) => value + 1)),
)
Why:
wrap(fn)
decorates
fn
for external callers; it does not execute
fn
. Inside an action/effect/async computed — including
finally
after
await wrap(...)
— context is already restored; call atoms directly.
问题:
ts
} finally {
  wrap(() => activeRequests.set((count) => count - 1))
}
修复:
ts
} finally {
  activeRequests.set((count) => count - 1)
}
当回调传递给外部API时的替代修复:
ts
button.addEventListener(
  'click',
  wrap(() => counter.set((value) => value + 1)),
)
原因:
wrap(fn)
为外部调用者装饰
fn
;不会执行
fn
。在action/effect/async computed内部——包括
await wrap(...)
之后的
finally
块——上下文已恢复;直接调用atoms即可。

Pointless
wrap
IIFE

无意义的
wrap
IIFE

Problem:
ts
} finally {
  wrap(() => activeRequests.set((count) => count - 1))()
}
Fix:
ts
} finally {
  activeRequests.set((count) => count - 1)
}
Why:
wrap(() => ...)()
inside a Reatom frame is just an indirect call. Reserve
wrap(fn)
for callbacks handed to DOM/timers/third-party code; reserve
await wrap(promise)
for async boundaries.
问题:
ts
} finally {
  wrap(() => activeRequests.set((count) => count - 1))()
}
修复:
ts
} finally {
  activeRequests.set((count) => count - 1)
}
原因:在Reatom框架内的
wrap(() => ...)()
只是间接调用。仅在将回调交给DOM/定时器/第三方代码时使用
wrap(fn)
;仅在异步边界处使用
await wrap(promise)

wrap
Missing After Async Boundary

异步边界后缺少
wrap

Problem:
ts
const save = action(async (form: FormState) => {
  const response = await fetch('/api/save', {
    method: 'POST',
    body: JSON.stringify(form),
  })
  savedId.set(await response.text())
}, 'form.save')
Fix:
ts
const save = action(async (form: FormState) => {
  const response = await wrap(
    fetch('/api/save', {
      method: 'POST',
      body: JSON.stringify(form),
    }),
  )
  const savedIdText: string = await wrap(response.text())
  savedId.set(savedIdText)
}, 'form.save')
Why: the state update runs after async work, so the async boundary must preserve Reatom context.
问题:
ts
const save = action(async (form: FormState) => {
  const response = await fetch('/api/save', {
    method: 'POST',
    body: JSON.stringify(form),
  })
  savedId.set(await response.text())
}, 'form.save')
修复:
ts
const save = action(async (form: FormState) => {
  const response = await wrap(
    fetch('/api/save', {
      method: 'POST',
      body: JSON.stringify(form),
    }),
  )
  const savedIdText: string = await wrap(response.text())
  savedId.set(savedIdText)
}, 'form.save')
原因:状态更新在异步工作之后运行,因此异步边界必须保留Reatom上下文。

Callback Calls Reatom Without Context

回调无上下文调用Reatom

Problem:
ts
addEventListener('online', () => {
  online.set(true)
})
Fix:
ts
onEvent(globalThis, 'online', () => {
  online.set(true)
})
Alternative fix when a raw callback API must be used:
ts
addEventListener(
  'online',
  wrap(() => {
    online.set(true)
  }),
)
Why: callbacks are async entry points too. Preserve context or use Reatom's abort-aware event helper.
问题:
ts
addEventListener('online', () => {
  online.set(true)
})
修复:
ts
onEvent(globalThis, 'online', () => {
  online.set(true)
})
必须使用原始回调API时的替代修复:
ts
addEventListener(
  'online',
  wrap(() => {
    online.set(true)
  }),
)
原因:回调也是异步入口点。保留上下文或使用Reatom的支持中止的事件助手。

Awaited Event Not Wrapped

等待的事件未被包装

Problem:
ts
const confirm = action(async (button: HTMLButtonElement) => {
  await onEvent(button, 'click')
  confirmed.set(true)
}, 'confirm')
Fix:
ts
const confirm = action(async (button: HTMLButtonElement) => {
  await wrap(onEvent(button, 'click'))
  confirmed.set(true)
}, 'confirm')
Why:
onEvent(...)
returns a promise. Await it through
wrap
inside async actions/effects.
问题:
ts
const confirm = action(async (button: HTMLButtonElement) => {
  await onEvent(button, 'click')
  confirmed.set(true)
}, 'confirm')
修复:
ts
const confirm = action(async (button: HTMLButtonElement) => {
  await wrap(onEvent(button, 'click'))
  confirmed.set(true)
}, 'confirm')
原因:
onEvent(...)
返回promise。在异步actions/effects内部通过
wrap
等待它。

Abortable Fetch Without Abort Signal

可中止请求无中止信号

Problem:
ts
const user = computed(async () => {
  const response = await wrap(fetch(`/api/users/${userId()}`))
  const payload: unknown = await wrap(response.json())
  return parseUser(payload)
}, 'user').extend(withAsyncData())
Fix:
ts
const user = computed(async () => {
  const response = await wrap(
    fetch(`/api/users/${userId()}`, {
      signal: abortVar.require().signal,
    }),
  )
  const payload: unknown = await wrap(response.json())
  return parseUser(payload)
}, 'user').extend(withAsyncData())
Why:
withAsyncData
, route loaders,
withAbort
, and abort-aware effects can cancel the Reatom frame; fetch should receive the same abort signal.
问题:
ts
const user = computed(async () => {
  const response = await wrap(fetch(`/api/users/${userId()}`))
  const payload: unknown = await wrap(response.json())
  return parseUser(payload)
}, 'user').extend(withAsyncData())
修复:
ts
const user = computed(async () => {
  const response = await wrap(
    fetch(`/api/users/${userId()}`, {
      signal: abortVar.require().signal,
    }),
  )
  const payload: unknown = await wrap(response.json())
  return parseUser(payload)
}, 'user').extend(withAsyncData())
原因:
withAsyncData
、路由加载器、
withAbort
和支持中止的effects可以取消Reatom框架;请求应接收相同的中止信号。

Status Used Without Enabling It

未启用状态就使用

Problem:
ts
const submit = action(async () => {
  await wrap(api.save(form()))
}, 'form.submit').extend(withAsync())

const status = submit.status()
Fix:
ts
const submit = action(async () => {
  await wrap(api.save(form()))
}, 'form.submit').extend(withAsync({ status: true }))

const status = submit.status()
Alternative fix:
ts
const ready = submit.ready()
const pending = submit.pending()
const error = submit.error()
Why:
status
is disabled by default for async extensions. Use
{ status: true }
only when the full status model is needed.
问题:
ts
const submit = action(async () => {
  await wrap(api.save(form()))
}, 'form.submit').extend(withAsync())

const status = submit.status()
修复:
ts
const submit = action(async () => {
  await wrap(api.save(form()))
}, 'form.submit').extend(withAsync({ status: true }))

const status = submit.status()
替代修复:
ts
const ready = submit.ready()
const pending = submit.pending()
const error = submit.error()
原因:异步扩展默认禁用
status
。仅当需要完整状态模型时才使用
{ status: true }

Identity Action

标识Action

Problem:
ts
const query = atom('', 'search.query')
const setQuery = action((next: string) => query.set(next), 'search.query.set')
Fix:
ts
const query = atom('', 'search.query')
query.set('next value')
Why: simple local updates do not need forwarding actions. Use actions for side effects and state-changing flows, not for pure data mapping.
问题:
ts
const query = atom('', 'search.query')
const setQuery = action((next: string) => query.set(next), 'search.query.set')
修复:
ts
const query = atom('', 'search.query')
query.set('next value')
原因:简单的本地更新不需要转发actions。仅在处理副作用和状态变更流程时使用actions,而非纯数据映射。

Thin Computed Wrapper Over Plain Helper

普通助手之上的薄Computed包装器

Problem:
ts
export function resolveDownloadUrl(image: ReatomImage): string {
  return image.fullImageUrl.data() ?? image.thumbnail.data()?.url ?? ''
}

const downloadUrl = computed(
  () => resolveDownloadUrl(imageModel),
  `${name}.display.downloadUrl`,
)
Fix:
ts
const downloadUrl = computed(
  () =>
    imageModel.fullImageUrl.data() ?? imageModel.thumbnail.data()?.url ?? '',
  `${name}.display.downloadUrl`,
)
Alternative fix when the same derivation is reused in tests or non-Reatom code: keep a pure function on plain data (URLs, DTOs), not on atom-bearing models; let the computed map atoms to that shape.
Why: a computed that only delegates to a helper reading atoms adds indirection without traceability benefit. The computed body (or
withComputed
on the parent) is the derivation; plain helpers belong on plain values, not as a shadow layer over atoms.
问题:
ts
export function resolveDownloadUrl(image: ReatomImage): string {
  return image.fullImageUrl.data() ?? image.thumbnail.data()?.url ?? ''
}

const downloadUrl = computed(
  () => resolveDownloadUrl(imageModel),
  `${name}.display.downloadUrl`,
)
修复:
ts
const downloadUrl = computed(
  () =>
    imageModel.fullImageUrl.data() ?? imageModel.thumbnail.data()?.url ?? '',
  `${name}.display.downloadUrl`,
)
当相同派生逻辑在测试或非Reatom代码中复用时的替代修复:保留针对纯数据(URL、DTO)的纯函数,而非针对带有atom的模型;让computed将atoms映射为该形状。
原因:仅委托给读取atoms的助手的computed增加了间接层,但没有可追踪性优势。computed体(或父级上的
withComputed
)是派生逻辑;普通助手应针对纯值,而非作为atoms之上的影子层。

Atom Factory Named Like A Getter

Atom工厂命名为Getter风格

Problem:
ts
export const getFolderTreeNodeUi = (folderPath: string) => ({
  expanded: reatomBoolean(false, `folderTree.${folderPath}.expanded`),
  isSelected: computed(
    () => currentFolder()?.path === folderPath,
    `folderTree.${folderPath}.isSelected`,
  ),
})
Fix:
ts
export const reatomFolderTreeNodeUi = (folderPath: string) => ({
  expanded: reatomBoolean(false, `folderTree.${folderPath}.expanded`),
  isSelected: computed(
    () => currentFolder()?.path === folderPath,
    `folderTree.${folderPath}.isSelected`,
  ),
})
Why: Reatom factories create traced atoms and actions;
reatom*
signals that contract and matches core helpers like
reatomBoolean
,
reatomRoute
, and
reatomForm
. Plain
get*
/
create*
names hide lifecycle and naming rules for nested units.
问题:
ts
export const getFolderTreeNodeUi = (folderPath: string) => ({
  expanded: reatomBoolean(false, `folderTree.${folderPath}.expanded`),
  isSelected: computed(
    () => currentFolder()?.path === folderPath,
    `folderTree.${folderPath}.isSelected`,
  ),
})
修复:
ts
export const reatomFolderTreeNodeUi = (folderPath: string) => ({
  expanded: reatomBoolean(false, `folderTree.${folderPath}.expanded`),
  isSelected: computed(
    () => currentFolder()?.path === folderPath,
    `folderTree.${folderPath}.isSelected`,
  ),
})
原因:Reatom工厂创建可追踪的atoms和actions;
reatom*
前缀表明了这种契约,并与核心助手如
reatomBoolean
reatomRoute
reatomForm
保持一致。普通的
get*
/
create*
名称隐藏了嵌套单元的生命周期和命名规则。

Relative State Scattered As Sibling Exports

相关状态分散为同级导出

Problem:
ts
export const search = atom('', 'search')
export const searchIsEmpty = computed(
  () => search().trim() === '',
  'search.isEmpty',
)
export const clearSearch = action(() => search.set(''), 'search.clear')
Fix:
ts
export const search = atom('', 'search').extend((target) => ({
  isEmpty: computed(() => target().trim() === '', `${target.name}.isEmpty`),
  clear: action(() => target.set(''), `${target.name}.clear`),
}))
Why: relative states and actions should live on the parent model, like
reatomBoolean
groups boolean actions and
reatomRoute
attaches
go
,
loader
,
render
, and child route helpers.
问题:
ts
export const search = atom('', 'search')
export const searchIsEmpty = computed(
  () => search().trim() === '',
  'search.isEmpty',
)
export const clearSearch = action(() => search.set(''), 'search.clear')
修复:
ts
export const search = atom('', 'search').extend((target) => ({
  isEmpty: computed(() => target().trim() === '', `${target.name}.isEmpty`),
  clear: action(() => target.set(''), `${target.name}.clear`),
}))
原因:相关状态和actions应存在于父模型上,就像
reatomBoolean
分组布尔actions,
reatomRoute
附加
go
loader
render
和子路由助手一样。

Parallel UI State Instead Of Atomization

并行UI状态而非Atom化

Problem:
ts
const users = atom<UserDto[]>([], 'users')
const selectedUserIds = atom<Set<string>>(new Set(), 'users.selectedIds')
Fix:
ts
type UserModel = UserDto & {
  selected: Atom<boolean>
}

const users = atom<UserModel[]>([], 'users').extend((target) => ({
  fromDto(items: UserDto[]) {
    target.set(
      items.map((item) => ({
        ...item,
        selected: atom(false, `users#${item.id}.selected`),
      })),
    )
  },
}))
Why: mutable per-item state belongs near the item to avoid parallel structures and broad list updates.
问题:
ts
const users = atom<UserDto[]>([], 'users')
const selectedUserIds = atom<Set<string>>(new Set(), 'users.selectedIds')
修复:
ts
type UserModel = UserDto & {
  selected: Atom<boolean>
}

const users = atom<UserModel[]>([], 'users').extend((target) => ({
  fromDto(items: UserDto[]) {
    target.set(
      items.map((item) => ({
        ...item,
        selected: atom(false, `users#${item.id}.selected`),
      })),
    )
  },
}))
原因:每个项的可变状态应靠近该项,以避免并行结构和广泛的列表更新。

Manual Route Rendering

手动路由渲染

Problem:
tsx
export function UsersPage() {
  if (!usersRoute.match()) return null
  return <Users />
}
Fix:
ts
export const usersRoute = layoutRoute.reatomRoute({
  path: 'users',
  render() {
    return <Users />
  },
})
Why: route
render
handles mounting, exact matching, loaders, layouts, and outlets.
问题:
tsx
export function UsersPage() {
  if (!usersRoute.match()) return null
  return <Users />
}
修复:
ts
export const usersRoute = layoutRoute.reatomRoute({
  path: 'users',
  render() {
    return <Users />
  },
})
原因:路由
render
处理挂载、精确匹配、加载器、布局和出口。

Component-Scoped Effect Lifted To Module Scope (Eager Forever)

组件作用域Effect提升到模块作用域(永久急切运行)

Problem:
ts
// models/slideshow.ts
export const slideshowAutoAdvance = effect(async () => {
  while (slideshowPlaying()) {
    await wrap(sleep(slideshowInterval()))
    navigateLightbox(1)
  }
}, 'slideshow.autoAdvance')

// components/Slideshow.tsx
ref={() => {
  const stop = slideshowAutoAdvance.subscribe()
  return stop
}}
Fix (preferred — route/feature init):
ts
// models/lightbox.ts
export const openLightbox = action((model: GalleryImageModel) => {
  lightboxImage.set(() => model)
  lightboxOpen.setTrue()
  startSlideshowSession()
}, 'openLightbox')

export const startSlideshowSession = action(() => {
  effect(async () => {
    while (slideshowPlaying()) {
      await wrap(sleep(slideshowInterval()))
      navigateLightbox(1)
    }
  }, 'slideshow.autoAdvance')
}, 'slideshow.startSession')
Alternative fix (acceptable — mount in the feature component):
tsx
// components/Slideshow.tsx
ref={() => {
  const {unsubscribe} = effect(async () => {
    while (slideshowPlaying()) {
      await wrap(sleep(slideshowInterval()))
      navigateLightbox(1)
    }
  }, 'slideshow.autoAdvance')

  return unsubscribe
}}
Why:
effect(...)
self-subscribes at creation, so a module-level effect is connected eagerly and never disconnects; the component's extra
.subscribe()
is redundant and the original self-subscription leaks. Start the effect at the feature boundary (route loader, explicit init action, or component mount), not as a forever-connected module singleton.
问题:
ts
// models/slideshow.ts
export const slideshowAutoAdvance = effect(async () => {
  while (slideshowPlaying()) {
    await wrap(sleep(slideshowInterval()))
    navigateLightbox(1)
  }
}, 'slideshow.autoAdvance')

// components/Slideshow.tsx
ref={() => {
  const stop = slideshowAutoAdvance.subscribe()
  return stop
}}
修复(首选——路由/功能初始化):
ts
// models/lightbox.ts
export const openLightbox = action((model: GalleryImageModel) => {
  lightboxImage.set(() => model)
  lightboxOpen.setTrue()
  startSlideshowSession()
}, 'openLightbox')

export const startSlideshowSession = action(() => {
  effect(async () => {
    while (slideshowPlaying()) {
      await wrap(sleep(slideshowInterval()))
      navigateLightbox(1)
    }
  }, 'slideshow.autoAdvance')
}, 'slideshow.startSession')
替代修复(可接受——在功能组件中挂载):
tsx
// components/Slideshow.tsx
ref={() => {
  const {unsubscribe} = effect(async () => {
    while (slideshowPlaying()) {
      await wrap(sleep(slideshowInterval()))
      navigateLightbox(1)
    }
  }, 'slideshow.autoAdvance')

  return unsubscribe
}}
原因:
effect(...)
在创建时自动订阅,因此模块级effect会立即连接且永远不会断开;组件额外的
.subscribe()
是冗余的,原始的自动订阅会泄漏。在功能边界(路由加载器、显式初始化action或组件挂载)启动effect,而非作为永久连接的模块单例。

Effect Inside withConnectHook On Its Own Dependency (Infinite Loop)

withConnectHook内部的Effect依赖自身(无限循环)

Problem:
ts
export const slideshowPlaying = reatomBoolean(false, 'slideshowPlaying').extend(
  withConnectHook(() => {
    effect(async () => {
      while (slideshowPlaying()) {
        await wrap(sleep(slideshowInterval()))
        navigateLightbox(1)
      }
    }, 'slideshow.autoAdvance')
  }),
)
Fix (scope anchor the effect does not read):
ts
export const lightboxOpen = reatomBoolean(false, 'lightboxOpen').extend(
  withConnectHook(() => {
    effect(async () => {
      while (peek(slideshowPlaying)) {
        await wrap(sleep(slideshowInterval()))
        navigateLightbox(1)
      }
    }, 'slideshow.autoAdvance')
  }),
)
Better fix (explicit init at feature open):
ts
export const startSlideshowSession = action(() => {
  effect(async () => {
    while (slideshowPlaying()) {
      await wrap(sleep(slideshowInterval()))
      navigateLightbox(1)
    }
  }, 'slideshow.autoAdvance')
}, 'slideshow.startSession')
Why: a connect hook runs when its target gets subscribers. If the nested effect reads that same target (directly or through a computed), connect/subscribe can feed back forever. Either attach the hook to a different scope anchor, use
peek
for gate checks only, or start the effect from route loader / init action / component mount instead.
问题:
ts
export const slideshowPlaying = reatomBoolean(false, 'slideshowPlaying').extend(
  withConnectHook(() => {
    effect(async () => {
      while (slideshowPlaying()) {
        await wrap(sleep(slideshowInterval()))
        navigateLightbox(1)
      }
    }, 'slideshow.autoAdvance')
  }),
)
修复(使用Effect不读取的作用域锚点):
ts
export const lightboxOpen = reatomBoolean(false, 'lightboxOpen').extend(
  withConnectHook(() => {
    effect(async () => {
      while (peek(slideshowPlaying)) {
        await wrap(sleep(slideshowInterval()))
        navigateLightbox(1)
      }
    }, 'slideshow.autoAdvance')
  }),
)
更好的修复(功能打开时显式初始化):
ts
export const startSlideshowSession = action(() => {
  effect(async () => {
    while (slideshowPlaying()) {
      await wrap(sleep(slideshowInterval()))
      navigateLightbox(1)
    }
  }, 'slideshow.autoAdvance')
}, 'slideshow.startSession')
原因:connect钩子在目标获得订阅者时运行。如果嵌套的effect读取同一个目标(直接或通过computed),连接/订阅可能会永远循环。要么将钩子附加到不同的作用域锚点,仅使用
peek
进行 gate 检查,要么从路由加载器/初始化action/组件挂载启动effect。

Object URL Leaked As A Plain String

对象URL作为普通字符串泄漏

Problem:
ts
const previewUrl = computed(() => {
  const blob = imageBlob.data()
  return blob ? URL.createObjectURL(blob) : ''
}, 'image.previewUrl')
Fix:
ts
const previewUrl = computed(async () => {
  const blob = await wrap(imageBlob())
  if (!blob) return ''
  const url = URL.createObjectURL(blob)
  abortVar.subscribe(() => URL.revokeObjectURL(url))
  return url
}, 'image.previewUrl').extend(withAsyncData({ initState: '' }))
Why:
URL.createObjectURL
allocates a resource. Tie revocation to the owning computed's abort/disconnect so the URL is freed when the model re-runs or disconnects, instead of leaking one URL per recomputation.
问题:
ts
const previewUrl = computed(() => {
  const blob = imageBlob.data()
  return blob ? URL.createObjectURL(blob) : ''
}, 'image.previewUrl')
修复:
ts
const previewUrl = computed(async () => {
  const blob = await wrap(imageBlob())
  if (!blob) return ''
  const url = URL.createObjectURL(blob)
  abortVar.subscribe(() => URL.revokeObjectURL(url))
  return url
}, 'image.previewUrl').extend(withAsyncData({ initState: '' }))
原因:
URL.createObjectURL
分配了资源。将撤销与所属computed的中止/断开连接绑定,以便在模型重新运行或断开连接时释放URL,而非每次重新计算都泄漏一个URL。

Compact Gotcha Fixes

常见问题快速修复

  • Module-level
    effect(...)
    -> eager at import; start it from route loader, explicit init action, or component mount — not as a forever-connected singleton.
  • withConnectHook
    +
    effect
    that reads the hook target -> infinite connect loop; use a different scope anchor,
    peek
    for gates, or route/init/component mount instead.
  • await import('./peer')
    /
    require('./peer')
    to break a cycle -> restructure modules or use an orchestration action; dynamic import is for code-splitting.
  • Hand-rolled
    dispose()
    for object URLs/bitmaps/observers/workers -> tie cleanup to
    withConnectHook
    /
    withDisconnectHook
    or async-computed abort.
  • atom.set
    from a DOM
    ref
    /observer callback -> pass
    wrap(() => atom.set(...))
    to the callback API, or
    context.start(() => atom.set(...))
    .
  • wrap(() => atom.set(...))
    as a standalone statement -> dead code; call
    atom.set(...)
    directly or pass
    wrap(fn)
    externally.
  • wrap(() => atom.set(...))()
    inside action/effect/computed -> pointless; call
    atom.set(...)
    directly.
  • computed(() => resolveX(model))
    with plain
    resolveX
    reading atoms -> inline in computed or attach via
    withComputed
    on the parent.
  • counter(5)
    to write ->
    counter.set(5)
    ; positional-call writes on reactive atoms throw.
  • action.retry()
    without
    cacheParams
    ->
    withAsync({ cacheParams: true })
    , or retry the computed instead.
  • .extend(withCache(), withAsync())
    -> reorder:
    withAsync
    /
    withAsyncData
    first,
    withCache
    after.
  • Reading
    getCalls(someAction)
    later as data -> action call lists are cleared next tick; store payloads in atoms.
  • reatomMap()().set(k, v)
    /
    reatomArray()().push(x)
    -> use the primitive's actions; in-place mutation skips invalidation.
  • Synchronous assertion after
    .set(...)
    expecting a subscriber to have fired -> await a microtask; notifications are queued.
  • Shared state between tests ->
    context.reset()
    in
    beforeEach
    or scope with
    context.start
    .
  • status.isPending()
    ->
    status.isPending
    ; status flags are properties.
  • status.error
    ->
    target.error()
    ; errors stay on the async target atom.
  • async loader(params, search)
    ->
    async loader({ q, userId })
    ; loaders receive one merged params/search object.
  • Nullable auth loader ->
    params() { return allowed ? {} : null }
    ; guards should block route ownership before loaders run.
  • route.go('/users')
    ->
    usersRoute.go(params)
    ; route paths are declared without leading
    /
    .
  • Module-level route form singleton -> route/scoped factory or loader-created form when lifetime follows the route.
  • 模块级
    effect(...)
    -> 导入时急切运行;从路由加载器、显式初始化action或组件挂载启动——而非作为永久连接的单例。
  • withConnectHook
    + 读取钩子目标的effect -> 无限连接循环;使用不同的作用域锚点、
    peek
    进行gate检查,或路由/初始化/组件挂载替代。
  • await import('./peer')
    /
    require('./peer')
    打破循环依赖 -> 重构模块或使用编排action;动态导入用于代码分割。
  • 对象URL/位图/观察者/workers的手动
    dispose()
    -> 将清理与
    withConnectHook
    /
    withDisconnectHook
    或异步computed中止绑定。
  • DOM
    ref
    /观察者回调中的
    atom.set
    -> 将
    wrap(() => atom.set(...))
    传递给回调API,或使用
    context.start(() => atom.set(...))
  • 独立语句的
    wrap(() => atom.set(...))
    -> 死代码;直接调用
    atom.set(...)
    或传递
    wrap(fn)
    给外部。
  • action/effect/computed内部的
    wrap(() => atom.set(...))()
    -> 无意义;直接调用
    atom.set(...)
  • 普通
    resolveX
    读取atoms的
    computed(() => resolveX(model))
    -> 内联到computed中或通过父级的
    withComputed
    附加。
  • counter(5)
    写入 ->
    counter.set(5)
    ;响应式atom的位置调用写入会抛出错误。
  • cacheParams
    action.retry()
    ->
    withAsync({ cacheParams: true })
    ,或重试computed。
  • .extend(withCache(), withAsync())
    -> 重新排序:
    withAsync
    /
    withAsyncData
    在前,
    withCache
    在后。
  • 稍后读取
    getCalls(someAction)
    作为数据 -> action调用列表在下一个tick清除;将负载存储在atoms中。
  • reatomMap()().set(k, v)
    /
    reatomArray()().push(x)
    -> 使用原语的actions;原地变更跳过失效检查。
  • .set(...)
    后同步断言订阅者已触发 -> 等待微任务;通知是排队的。
  • 测试之间共享状态 -> 在
    beforeEach
    中使用
    context.reset()
    或通过
    context.start
    作用域隔离。
  • status.isPending()
    ->
    status.isPending
    ;状态标志是属性。
  • status.error
    ->
    target.error()
    ;错误保留在异步目标atom上。
  • async loader(params, search)
    ->
    async loader({ q, userId })
    ;加载器接收一个合并的参数/搜索对象。
  • 可空认证加载器 ->
    params() { return allowed ? {} : null }
    ;守卫应在加载器运行前阻止路由所有权。
  • route.go('/users')
    ->
    usersRoute.go(params)
    ;路由路径声明时无前导
    /
  • 模块级路由表单单例 -> 路由/作用域工厂或加载器创建的表单,当生命周期跟随路由时。

Async Extensions Ordered After Cache

异步扩展在缓存之后

Problem:
ts
const users = computed(async () => {
  return await wrap(api.getUsers())
}, 'users').extend(withCache(), withAsyncData())
Fix:
ts
const users = computed(async () => {
  return await wrap(api.getUsers())
}, 'users').extend(withAsyncData(), withCache())
Why:
withAsync
/
withAsyncData
refuse to attach after
withCache
(runtime
ReatomError
), and the async middleware must observe cache hits to keep
pending
,
data
, and
status
consistent.
问题:
ts
const users = computed(async () => {
  return await wrap(api.getUsers())
}, 'users').extend(withCache(), withAsyncData())
修复:
ts
const users = computed(async () => {
  return await wrap(api.getUsers())
}, 'users').extend(withAsyncData(), withCache())
原因:
withAsync
/
withAsyncData
拒绝在
withCache
之后附加(运行时
ReatomError
),且异步中间件必须观察缓存命中以保持
pending
data
status
一致。

Action Call List Treated As Durable State

Action调用列表视为持久状态

Problem:
ts
const addToast = action((toast: Toast) => toast, 'addToast')
const toasts = computed(
  () => getCalls(addToast).map((call) => call.payload),
  'toasts',
)
Fix:
ts
const toasts = atom<Toast[]>([], 'toasts').extend(
  withActions((target) => ({
    add: (toast: Toast) => target.set((list) => [...list, toast]),
  })),
)
Why: action state is an autoclearable array, wiped in the next cleanup tick. It is for reacting to calls within a transaction, not for storage; durable data belongs in atoms.
问题:
ts
const addToast = action((toast: Toast) => toast, 'addToast')
const toasts = computed(
  () => getCalls(addToast).map((call) => call.payload),
  'toasts',
)
修复:
ts
const toasts = atom<Toast[]>([], 'toasts').extend(
  withActions((target) => ({
    add: (toast: Toast) => target.set((list) => [...list, toast]),
  })),
)
原因:action状态是自动清除的数组,在下一个清理tick中被擦除。它用于在事务内响应调用,而非存储;持久数据应放在atoms中。

Stale v3 API

过时v3 API

Problem:
ts
const resource = reatomResource(async (ctx) => {
  const response = await ctx.schedule(fetch('/api/users'))
  return response.json()
}, 'users')
Fix:
ts
const users = computed(async () => {
  const response = await wrap(fetch('/api/users'))
  return await wrap(response.json())
}, 'users').extend(withAsyncData())
Why: current Reatom uses implicit context,
wrap
, and async computed resources.
问题:
ts
const resource = reatomResource(async (ctx) => {
  const response = await ctx.schedule(fetch('/api/users'))
  return response.json()
}, 'users')
修复:
ts
const users = computed(async () => {
  const response = await wrap(fetch('/api/users'))
  return await wrap(response.json())
}, 'users').extend(withAsyncData())
原因:当前Reatom使用隐式上下文、
wrap
和异步computed资源。

Finding Format

问题报告格式

Use this format for each issue:
md
- [Severity] `path-or-symbol`: Problem statement.
  Why it matters: concrete Reatom rule or failure mode.
  Fix: specific code-level change.
Severity guide:
  • Critical: incorrect state, lost async context, cancellation/race bug, broken route behavior, or stale API that cannot work.
  • High: recommended Reatom model is bypassed in a way that risks leaks, eager fetches, stale data, or misleading docs.
  • Medium: maintainability, traceability, naming, atomization, or tests are materially weaker.
  • Low: style/docs clarity that could confuse future agents but is not likely to break behavior.
If no findings remain, say so directly and list residual risks, especially untested async cancellation, route loader behavior, or documentation examples not executed.
每个问题使用以下格式:
md
- [严重程度] `路径或符号`: 问题描述。
  重要性:具体的Reatom规则或失败模式。
  修复:具体的代码级变更。
严重程度指南:
  • 关键:状态错误、异步上下文丢失、取消/竞争bug、路由行为损坏,或无法工作的过时API。
  • 高:绕过推荐的Reatom模型,存在泄漏风险、急切请求、陈旧数据或误导性文档。
  • 中:可维护性、可追踪性、命名、atom化或测试明显较弱。
  • 低:可能混淆未来Agent但不太可能破坏行为的风格/文档清晰度问题。
如果没有发现问题,请直接说明,并列出剩余风险,尤其是未测试的异步取消、路由加载器行为或未执行的文档示例。

Final Review Checklist

最终审查清单

  • Did you inspect both code and docs changed by the agent?
  • Did you compare claims against REFERENCE.md, not generic React/Solid/Vue habits?
  • Did you check async context and
    wrap
    after every await/callback boundary?
  • Did you flag bare
    wrap(() => ...)
    statements and pointless
    wrap(() => ...)()
    IIFEs inside Reatom frames?
  • Did you challenge imperative fetching, manual routing, and parallel mutable state?
  • Did you flag
    action
    used for pure mappers with no IO or state writes?
  • Did you flag thin
    computed(() => helper(model))
    wrappers where the helper only reads atom getters?
  • Did you flag atom/action factories named
    get*
    /
    create*
    instead of
    reatom*
    ?
  • Did you check effect lifetime: module-level
    effect
    eagerness, feature init (route loader / init action / component mount), connect-hook dependency traps, and redundant component subscriptions?
  • Did you check that object URLs, bitmaps, workers, and observers are owned by Reatom lifecycle rather than manual
    dispose()
    ?
  • Did you check
    .extend(...)
    ordering (async before cache),
    retry
    /
    status
    option requirements, and key collisions in extensions?
  • Did you check write syntax (
    .set
    vs positional call), in-place mutation of collection primitives, and reliance on ephemeral action call lists?
  • Did you check timing assumptions: queued notifications, unsubscribed computed revalidation, and test context isolation via
    context.reset()
    ?
  • Did you verify the examples are copyable and do not hide key imports?
  • Did you avoid approving without at least considering tests or examples that exercise the Reatom behavior?
  • 是否检查了Agent更改的代码和文档?
  • 是否对照REFERENCE.md而非通用React/Solid/Vue习惯验证声明?
  • 是否检查了每个await/回调边界后的异步上下文和
    wrap
  • 是否标记了Reatom框架内的裸
    wrap(() => ...)
    语句和无意义的
    wrap(() => ...)()
    IIFEs?
  • 是否质疑命令式请求、手动路由和并行可变状态?
  • 是否标记了用于无IO或状态写入的纯映射器的
    action
  • 是否标记了助手仅读取atom getters的薄
    computed(() => helper(model))
    包装器?
  • 是否标记了命名为
    get*
    /
    create*
    而非
    reatom*
    的atom/action工厂?
  • 是否检查了effect生命周期:模块级effect的急切性、功能初始化(路由加载器/初始化action/组件挂载)、connect钩子依赖陷阱和冗余组件订阅?
  • 是否检查了对象URL、位图、workers和观察者是否由Reatom生命周期而非手动
    dispose()
    管理?
  • 是否检查了
    .extend(...)
    顺序(异步在缓存之前)、
    retry
    /
    status
    选项要求以及扩展中的键冲突?
  • 是否检查了写入语法(
    .set
    vs 位置调用)、集合原语的原地变更以及对临时action调用列表的依赖?
  • 是否检查了时序假设:排队通知、未订阅computed的重新验证以及通过
    context.reset()
    隔离测试上下文?
  • 是否验证示例可复制且未隐藏关键导入?
  • 是否在批准前至少考虑了测试或示例是否验证了Reatom行为?