Finishing a Development Branch
obra/superpowers
Walks the last step of a branch: confirm tests pass, detect the git environment, ask how to integrate, carry out your choice and clean up the worktree.
Reviews code, docs, examples, and agent-produced changes that use Reatom.
$ npx skills add reatom/reatom --skill reatom-review -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install reatom/reatom reatom-review --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/reatom/reatom.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/reatom-review .claude/skills/reatom-review && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "reatom-review" agent skill from https://github.com/reatom/reatom/tree/v1001/skills/reatom-review into .claude/skills/reatom-review/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "reatom-review", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/reatom/reatom/tree/v1001/skills/reatom-reviewType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add reatom/reatom --skill reatom-review -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install reatom/reatom reatom-review --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/reatom/reatom.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/reatom-review .agents/skills/reatom-review && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "reatom-review" agent skill from https://github.com/reatom/reatom/tree/v1001/skills/reatom-review into .agents/skills/reatom-review/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "reatom-review", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add reatom/reatom --skill reatom-review -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install reatom/reatom reatom-review --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/reatom/reatom.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/reatom-review .cursor/skills/reatom-review && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "reatom-review" agent skill from https://github.com/reatom/reatom/tree/v1001/skills/reatom-review into .cursor/skills/reatom-review/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "reatom-review", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/reatom/reatom.git --path skills/reatom-review--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add reatom/reatom --skill reatom-review -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install reatom/reatom reatom-review --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/reatom/reatom.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/reatom-review .gemini/skills/reatom-review && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "reatom-review" agent skill from https://github.com/reatom/reatom/tree/v1001/skills/reatom-review into .gemini/skills/reatom-review/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "reatom-review", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install reatom/reatom reatom-reviewInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add reatom/reatom --skill reatom-review -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/reatom/reatom.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/reatom-review .github/skills/reatom-review && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "reatom-review" agent skill from https://github.com/reatom/reatom/tree/v1001/skills/reatom-review into .github/skills/reatom-review/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "reatom-review", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add reatom/reatom --skill reatom-review -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install reatom/reatom reatom-review --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/reatom/reatom.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/reatom-review .opencode/skills/reatom-review && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "reatom-review" agent skill from https://github.com/reatom/reatom/tree/v1001/skills/reatom-review into .opencode/skills/reatom-review/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "reatom-review", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
reatom-reviewReviews code, docs, examples, and agent-produced changes that use Reatom.
Reatom Review is an agent skill from reatom/reatom. Reviews code, docs, examples, and agent-produced changes that use Reatom. Use when reviewing Reatom pull requests, examples, migrations, model files, async flows, routing, forms, persistence, or documentation that claims Reatom behavior.
Its SKILL.md is about 10k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Development, covering Pull requests. The repository describes itself as: Reatom - the ultimate state manager. The licence is MIT.
10 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 1b853c5. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are typescript and markdown).
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Reatom Review loads about 10k tokens when it runs. Until then it costs about 63 tokens; SKILL.md has 4,173 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from reatom/reatom at commit 1b853c5, republished under its MIT licence (© reatom). 4,173 words, ~10,229 tokens.
.claude/skills/reatom-review/SKILL.md (or your agent's skills folder).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.
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.
Async reads and queries:
computed(async () => ...).extend(withAsyncData(...)).effect fetches, refs, component-local async state, or imperative loaders unless the code has a clear non-query reason.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.withAsync / withAsyncData must be applied before withCache (attaching withAsync after withCache throws). Review every .extend(...) chain for ordering, not just presence..data(), .ready(), .error(), and submit.error(). Do not render or destructure atom objects as inert values.status.isPending, not functions. Use the target's .error() atom; do not invent status.error.wrap and async context:
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.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.wrap(() => atom.set(...))() inside actions/effects/async computeds — an immediate wrapped IIFE adds nothing; just call atom.set(...) in the Reatom frame.await wrap(fetch(url)).then(...); prefer wrapping the whole promise chain or wrapping each awaited step..then(...) callbacks, DOM callbacks, timers, requestAnimationFrame, and external event listeners that call Reatom state without wrap or onEvent.await: if later code calls atoms/actions from an async continuation, the awaited promise should usually be wrapped.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..then() chains when strict context errors appear.withCallHook; hooks already run in Reatom context.State modeling:
.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.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.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.selectedIds, checkedIds, or edit maps when item-local atoms would be clearer.atom.set, no other action calls — is not an action. Use a plain function or computed.atom.set, calling actions) is an action and should be named.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..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.atom.set is fine for local/simple updates. Flag "identity" actions that only forward values to atoms..extend(...) or withActions(...), not scattered as sibling exports.reatomBoolean and reatomRoute: create the parent atom, then attach related methods, child computeds, loaders, route factories, and helpers through .extend.computed reads the scope key and returns the atoms/actions/forms for that scope, so changing the key replaces the inner graph.null when inactive scope is ordinary state, or throw when reading outside the named scope is a bug.Naming and traceability:
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.users.page, users#${id}.name, or ${target.name}.ready._ 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.Effects, hooks, and subscriptions:
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.withMemo or a subscription..set(...); await a microtask/sleep(0) or read the atom directly.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().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.init / start action on the feature model: called once when the feature opens (lightbox open, panel mount, session start).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.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)..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).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.withConnectHook for lazy external subscriptions/polling that do not depend on the hook target; verify cleanup and abort behavior.withChangeHook to synchronize atoms with other atoms; prefer computed or withComputed.Routing:
reatomRoute loaders for route data; loaders are async computeds with withAsyncData.withConnectHook on an atom the effect reads.route.match() and return null; prefer the route render option and layouts/outlets..go(...) and links use .path(...) where SPA interception is expected./; route.go() takes params, not a path string.(params, search) is wrong.render is a route option; after construction route.render is a computed output, not an assignable callback.route.render(self) and passed to UI still need wrap(...).params() / parent guards, not nullable loader payloads..go(..., true).exact() for active state; match() stays true for descendants.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.signal: abortVar.require().signal.withAbort() on the outer factory; async computeds/loaders with withAsyncData already have abort support.peek / memo when only some inputs should rebuild the scoped graph.withAbort() plus await wrap(sleep(ms)) for debounce-like behavior.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.Forms:
reatomForm, reatomFieldSet, and reatomField for forms.wrap, and dependent validation may read other fields reactively.submit.error() and keep payload types explicit.field.value() / field.change(value) for user-facing field values. field() is the underlying state.reatomForm({ onSubmit }) and call form.submit(); separate raw submit actions can bypass validation.Persistence and URL sync:
withLocalStorage, withSessionStorage, withSearchParams, or storage-specific persistence extensions over ad hoc effects.version + migration.withCache options against intent: swr semantics, staleTime/length limits, and ignoreAbort defaults (true only for empty params). Flag cache on non-idempotent actions.Migration correctness:
ctx.schedule, ctx.spy, ctx.get, reatomAsync, reatomResource, reaction, atom.onChange, onConnect, withConcurrency, and onCtxAbort.wrap, direct atom reads, peek, action(...).extend(withAsync()), computed(...).extend(withAsyncData()), effect, withChangeHook, withConnectHook, withAbort, and abortVar.subscribe.reatomComponent; useAtom results are plain values, not callable getters.wrap(...), including third-party control callbacks.wrap(...) directly in JSX of a plain function component; create wrapped callbacks inside a Reatom frame or pass them down.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.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.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).dispose() / cleanup() methods that callers must remember to invoke when withConnectHook / withDisconnectHook / async-computed abort already express ownership.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.When reviewing docs, tutorials, READMEs, generated summaries, or examples, actively search for these mismatches:
effect, component lifecycle, refs, or manual status atoms instead of computed(...).extend(withAsyncData()).computed for non-idempotent writes instead of action(...).extend(withAsync()).withAbort, withAsyncData, route loader behavior, or wrap around awaited work.wrap(() => atom.set(...)) without calling/passing the returned function.wrap, but it uses wrap(() => atom.set(...))() inside an action/effect/computed where a direct atom.set(...) already runs in frame.signal: abortVar.require().signal to fetch..status(), but the action/computed was not extended with { status: true }.route.match().(params, search) instead of one merged object.params() / parent guards.ctx, reatomResource, reatomAsync, reaction, or onConnect..extend.get* / create* instead of reatom*.action(...), but the function has no IO and does not write state.computed(() => resolveX(model)) with a plain resolveX helper that only reads atom getters — split indirection with no non-Reatom reuse.form.submit() with a separate raw submit action.wrap, computed, withAsyncData, action, withAsync, or reatomRoute.effect(...) that self-subscribes at import and is only nominally re-subscribed from a component.dispose() instead of connect-hook/abort ownership.await import() / require() inside actions.Problem:
const users = atom<User[]>([], 'users')
effect(async () => {
users.set(await api.getUsers(page()))
}, 'users.fetch')Fix:
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.
wrap Chained IncorrectlyProblem:
const response = await wrap(fetch(url)).then((res) => res.json())
data.set(response)Fix:
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.
wrap Used As A Statement (Function Not Called)Problem:
} finally {
wrap(() => activeRequests.set((count) => count - 1))
}Fix:
} finally {
activeRequests.set((count) => count - 1)
}Alternative fix when the callback is passed to an external API:
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.
wrap IIFEProblem:
} finally {
wrap(() => activeRequests.set((count) => count - 1))()
}Fix:
} 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.
wrap Missing After Async BoundaryProblem:
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:
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.
Problem:
addEventListener('online', () => {
online.set(true)
})Fix:
onEvent(globalThis, 'online', () => {
online.set(true)
})Alternative fix when a raw callback API must be used:
addEventListener(
'online',
wrap(() => {
online.set(true)
}),
)Why: callbacks are async entry points too. Preserve context or use Reatom's abort-aware event helper.
Problem:
const confirm = action(async (button: HTMLButtonElement) => {
await onEvent(button, 'click')
confirmed.set(true)
}, 'confirm')Fix:
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.
Problem:
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:
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.
Problem:
const submit = action(async () => {
await wrap(api.save(form()))
}, 'form.submit').extend(withAsync())
const status = submit.status()Fix:
const submit = action(async () => {
await wrap(api.save(form()))
}, 'form.submit').extend(withAsync({ status: true }))
const status = submit.status()Alternative fix:
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.
Problem:
const query = atom('', 'search.query')
const setQuery = action((next: string) => query.set(next), 'search.query.set')Fix:
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.
Problem:
export function resolveDownloadUrl(image: ReatomImage): string {
return image.fullImageUrl.data() ?? image.thumbnail.data()?.url ?? ''
}
const downloadUrl = computed(
() => resolveDownloadUrl(imageModel),
`${name}.display.downloadUrl`,
)Fix:
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.
Problem:
export const getFolderTreeNodeUi = (folderPath: string) => ({
expanded: reatomBoolean(false, `folderTree.${folderPath}.expanded`),
isSelected: computed(
() => currentFolder()?.path === folderPath,
`folderTree.${folderPath}.isSelected`,
),
})Fix:
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.
Problem:
export const search = atom('', 'search')
export const searchIsEmpty = computed(
() => search().trim() === '',
'search.isEmpty',
)
export const clearSearch = action(() => search.set(''), 'search.clear')Fix:
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.
Problem:
const users = atom<UserDto[]>([], 'users')
const selectedUserIds = atom<Set<string>>(new Set(), 'users.selectedIds')Fix:
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.
Problem:
export function UsersPage() {
if (!usersRoute.match()) return null
return <Users />
}Fix:
export const usersRoute = layoutRoute.reatomRoute({
path: 'users',
render() {
return <Users />
},
})Why: route render handles mounting, exact matching, loaders, layouts, and outlets.
Problem:
// 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):
// 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):
// 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.
Problem:
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):
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):
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.
Problem:
const previewUrl = computed(() => {
const blob = imageBlob.data()
return blob ? URL.createObjectURL(blob) : ''
}, 'image.previewUrl')Fix:
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.
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.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.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..set(...) expecting a subscriber to have fired -> await a microtask; notifications are queued.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.params() { return allowed ? {} : null }; guards should block route ownership before loaders run.route.go('/users') -> usersRoute.go(params); route paths are declared without leading /.Problem:
const users = computed(async () => {
return await wrap(api.getUsers())
}, 'users').extend(withCache(), withAsyncData())Fix:
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.
Problem:
const addToast = action((toast: Toast) => toast, 'addToast')
const toasts = computed(
() => getCalls(addToast).map((call) => call.payload),
'toasts',
)Fix:
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.
Problem:
const resource = reatomResource(async (ctx) => {
const response = await ctx.schedule(fetch('/api/users'))
return response.json()
}, 'users')Fix:
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.
Use this format for each issue:
- [Severity] `path-or-symbol`: Problem statement.
Why it matters: concrete Reatom rule or failure mode.
Fix: specific code-level change.Severity guide:
If no findings remain, say so directly and list residual risks, especially untested async cancellation, route loader behavior, or documentation examples not executed.
wrap after every await/callback boundary?wrap(() => ...) statements and pointless wrap(() => ...)() IIFEs inside Reatom frames?action used for pure mappers with no IO or state writes?computed(() => helper(model)) wrappers where the helper only reads atom getters?get* / create* instead of reatom*?effect eagerness, feature init (route loader / init action / component mount), connect-hook dependency traps, and redundant component subscriptions?dispose()?.extend(...) ordering (async before cache), retry/status option requirements, and key collisions in extensions?.set vs positional call), in-place mutation of collection primitives, and reliance on ephemeral action call lists?context.reset()?© reatom, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in skills/reatom-review of reatom/reatom.
Open the folder on GitHubat commit 1b853c5
Reatom Review next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Reatom Review this skillreatom/reatom | 1.4k | — | ~10k | Automated safety check: Pass | MIT | |
| Finishing a Development Branchobra/superpowers | 297k | 5 repos | ~1.9k | Automated safety check: Pass | MIT | |
| PR Babysitteropeninterpreter/openinterpreter | 69k | 3 repos | ~4.2k | Automated safety check: Pass | Apache-2.0 | |
| Check PRonyx-dot-app/onyx | 32k | 2 repos | ~2.3k | Automated safety check: Pass | MIT | |
| PR Design DocOpenHands/OpenHands | 91k | — | ~2.4k | Automated safety check: Pass | MIT | |
| WooCommerce Code Reviewwoocommerce/woocommerce | 11k | 3 repos | ~1.1k | Automated safety check: Pass | Custom licence |
obra/superpowers
Walks the last step of a branch: confirm tests pass, detect the git environment, ask how to integrate, carry out your choice and clean up the worktree.
openinterpreter/openinterpreter
Watches an open GitHub pull request until it merges, handling review comments, diagnosing CI failures and retrying flaky checks along the way.
onyx-dot-app/onyx
Checks a GitHub, GitLab, or Perforce (p4) pull request (or merge request, or shelved changelist) for unresolved review comments, failing status checks, and incomplete PR descriptions.
OpenHands/OpenHands
For a non-trivial pull request, write a self-contained HTML design doc under the temporary .pr/ directory and link a visibility-appropriate preview in the PR description, so maintainers grasp the…
woocommerce/woocommerce
Reviews WooCommerce code changes against the project's standards, flagging backend PHP architecture, naming, documentation, data integrity and testing violations.
payloadcms/payload
A skill your agent uses when a Payload pull request needs a concise visual walkthrough for reviewers.
reatom/reatom
Implements and documents Reatom v1001 (@reatom/core) using project conventions.
reatom/reatom
Masters Reatom v1001 async flows. An agent skill from reatom/reatom.
reatom/reatom
Implements and documents @reatom/jsx — reactive native DOM JSX.
Categories
Reviews code, docs, examples, and agent-produced changes that use Reatom. Reatom Review is an agent skill from reatom/reatom. Reviews code, docs, examples, and agent-produced changes that use Reatom.
Reatom Review fits situations like: reviewing Reatom pull requests; documentation that claims Reatom behavior.
Run `npx skills add reatom/reatom --skill reatom-review -a claude-code`. Or copy the skill folder (skills/reatom-review in reatom/reatom) into .claude/skills/reatom-review in your project. Claude Code loads it when a task matches its description.
Run `npx skills add reatom/reatom --skill reatom-review -a codex`. Or copy the skill folder (skills/reatom-review in reatom/reatom) into .agents/skills/reatom-review in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add reatom/reatom --skill reatom-review -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/reatom-review, .gemini/skills/reatom-review, .github/skills/reatom-review and .opencode/skills/reatom-review in your project.
SKILL.md names no scripts, command-line tools or credentials: Reatom Review is instructions for the agent only.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Reatom Review is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 10k tokens (SKILL.md is roughly 41k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Reatom Review: Finishing a Development Branch (obra/superpowers, 297k stars), PR Babysitter (openinterpreter/openinterpreter, 69k stars), Check PR (onyx-dot-app/onyx, 32k stars) and PR Design Doc (OpenHands/OpenHands, 91k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
reatom (a GitHub organization) maintains it in reatom/reatom, which has 1,376 GitHub stars. The repository holds 4 skills in this directory. The repository was last updated on October 8, 2026.
Source: reatom/reatom on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.