Skip to content

feat(store): add selector equality comparators #488

Description

@Karanjot786

For AI agents (Claude Code, Copilot, Cursor): read AGENTS.md and packages/store/AGENTS.md before writing code. This issue is a complete spec. Follow it exactly.

Objective

Add an optional equalityFn argument to the store selector hook. When the new selected slice is equal to the old one under equalityFn, skip the re-render. Ship a shallow comparator.

Package scope

packages/store. All work is confined here. Import shared types from @termuijs/core.

Files to create or modify

CREATE: packages/store/src/shallow.ts
CREATE: packages/store/src/shallow.test.ts
MODIFY: packages/store/src/store.ts   (accept equalityFn in the hook, gate setState on it)
MODIFY: packages/store/src/index.ts   (add the shallow export)

API contract

export type EqualityFn<U> = (a: U, b: U) => boolean;

export function shallow<U>(a: U, b: U): boolean;

export interface UseStore<T> {
  (): T;
  <U>(selector: Selector<T, U>, equalityFn?: EqualityFn<U>): U;
  getState: GetState<T>;
  setState: SetState<T>;
  subscribe(listener: Listener<T>): () => void;
  destroy(): void;
  computed<U>(selector: Selector<T, U>): Computed<U>;
}

Acceptance criteria

  • useStore(selector) keeps Object.is behavior when no equalityFn is passed
  • useStore(selector, shallow) does not re-render when a new slice is shallow-equal to the previous
  • useStore(selector, shallow) re-renders when a shallow-compared key changes
  • A custom equalityFn is honored instead of the default
  • shallow returns true for objects with the same keys and Object.is values, false otherwise
  • equalityFn is read through a ref so a changed function does not leak stale comparisons
  • bun vitest run packages/store passes
  • bun run typecheck passes

Test expectations

File: packages/store/src/shallow.test.ts
Run:  bun vitest run packages/store

Named cases:

  • shallow returns true for equal flat objects
  • shallow returns false when a value differs
  • shallow returns false when key sets differ
  • selector with shallow skips notify on equal slice
  • selector with shallow notifies on changed slice

Use the real store from createStore. Use vi.fn() as the subscribe listener to count notifications. Never mutate shared module state.

Reference pattern to follow

Follow packages/store/src/store.ts, match the useStore hook shape, the useRef pattern already used for selectorRef, and the comment style.

Not included

  • Deep equality
  • Per-store default comparators
  • Memoized selector factories
  • Any change to computed

Do not touch

  • Any package other than packages/store
  • packages/core (import shared types, do not modify them)
  • bun.lock
  • .github/

Agent prompt (copy this into your AI agent)

Read AGENTS.md at the repo root and packages/store/AGENTS.md.
Read the reference file packages/store/src/store.ts and copy its structure.
Task: add an optional equalityFn to the selector hook and ship a shallow comparator, exactly to the API contract in this issue.
Plan first. Create the files. Export shallow from packages/store/src/index.ts.
Confine all work to packages/store. Do not edit bun.lock. Do not add dependencies.
When done run: bun vitest run packages/store && bun run typecheck. Both must pass.

Before you open the PR

  • I run bun run build && bun vitest run && bun run typecheck and all pass
  • My change is confined to packages/store
  • bun.lock has no unrelated changes
  • I starred the repo

GSSoC 2026

This is an intermediate-level issue. Comment "I would like to work on this" to get assigned. You have 7 days to open a PR after assignment.

Metadata

Metadata

Labels

GSSoC 2026Open for GSSoC 2026 contributors.ai-readyStructured issue with full spec for AI coding agentsarea:store@termuijs/storeassignedIssue claimed by a contributor.level:intermediate+35 pts. Moderate task.type:feature+10 pts. New feature.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions