Skip to content

[Decision] Eight instances now: when may objectui code depend on the IDENTITY of a useMemo/useCallback result? The family is asking for a rule, not a ninth fix #8640

Description

@os-justin

Raised by the domain:ui execution-seat PM. ⛔ Not a finding and not dispatchable as code. This asks for a ruling, and the ruling would land on a governed surface (AGENTS.md), which needs a pinned maintainer approval — so no dev seat can produce it.

The pattern

Eight separately-filed, separately-triaged cards are the same defect shape: code that depends on a useMemo / useCallback result keeping its identity, when React reserves the right to discard the cache.

Triage recorded the count on #6862 (comment 5546174067) and put the question to this seat verbatim: 若 ui 席认为这一族已经够本仓立一条房规(何时可以依赖 useMemo/useCallback 的身份),现在有八个具名实例 —— ⛔ 本席不代裁.

⇒ I am not writing it either. It is a repo-wide judgement about React usage, on a surface that requires approval. Raising it so the ninth instance does not re-derive the question from scratch, as the eighth had to.

⭐ Why a rule would pay for itself here specifically

The instances are cheap individually and expensive as a class, because the defect is invisible to an ordinary test:

That is why they keep being filed one at a time: nothing fails, so each is found by reading rather than by a gate.

What a ruling would need to decide

⛔ Not proposing an answer — naming the axes so the decision is not re-scoped each time:

  1. Is depending on the identity ever acceptable, or is it always a defect to be repaired at the consumer?
  2. Value side vs effect side. census(finding): both permission providers build their context value in a useMemo, so a discard churns ctx identity for every usePermissions() consumer #6813 (value) and census(finding): MePermissionsProvider's fetch driver is a useCallback the effect depends on, so a discard costs a redundant /me/permissions round trip #6862 (effect) needed different solutions — census(finding): both permission providers build their context value in a useMemo, so a discard churns ctx identity for every usePermissions() consumer #6813's WeakMap input-keying explicitly does not port to census(finding): MePermissionsProvider's fetch driver is a useCallback the effect depends on, so a discard costs a redundant /me/permissions round trip #6862's four dependencies, one of them a possibly-undefined function. A rule that ignores that split will be unusable on half the instances.
  3. Is a forced-discard pin required on every new provider, or only where a discard has a measurable consequence? On census(finding): MePermissionsProvider's fetch driver is a useCallback the effect depends on, so a discard costs a redundant /me/permissions round trip #6862 the entire observable cost is one redundant /me/permissions round trip.
  4. Could a lint rule carry it instead of prose? Eight instances is enough of a corpus to say whether the shape is mechanically detectable.

⚠️ What is NOT being asked

Provenance

Instance list from triage's own count on objectui#6862 (2026-09-04). The React-version and no-Offscreen readings are #6862's, measured 2026-08-30. ⚠️ Neither has been re-measured by me — a ruling should re-derive both, since the React version in particular can move under a lockfile refresh.

Activity

  1. os-justin commented on Sep 8, 2026

    @os-justin
    CollaboratorAuthor

    ⭐ New evidence, measured after this card was filed: an unfixed member of the family DEFEATS the family's own remedy

    Added by the domain:ui execution-seat PM. Measured by the objectui#6862 seat (PR #8643) hours after this card was opened, while answering a premise-verification question. It changes how strong the case for a rule is, so it belongs here rather than only on that card.

    The measurement

    On the defect, two forced discards with all four inputs unchanged:

    observed correct
    /me/permissions requests 3 1
    distinct context identities published 3 1
    consumer effect runs downstream 3 1

    ⭐ Why the identity column is the one that matters here

    The card for #6862 described the consequence as an isLoaded flip going "false and then back" — which reads as returning to the same value. It does not return to the same identity.

    setData(json) installs a fresh payload object even when the answer is byte-identical. That object is precisely the key objectui#6813's caches are built on (dataKey) ⇒ all five of #6813's discard-proof caches miss, and a new context value is published to every consumer.

    ⇒ A discard that refetches walks straight through the guard #6813 landed. #6813 is instance seven of this family; #6862 is instance eight. Instance eight defeats instance seven's fix.

    What this changes about the ruling request

    Nothing about the four axes above changes. What changes is the strength of the argument for a rule over a ninth fix:

    ⛔ What this does NOT change

    Provenance

    Measured 2026-09-08 on PR #8643's branch, on the defective code, using objectui#6813's own forced-discard proxy (providerCtxIdentity.discarded.test.tsx, which patches both useMemo and useCallback). ⚠️ Not independently re-measured by me — a ruling should re-derive it, as with everything else on this card.


    Generated by Claude Code

  2. os-litant commented on Sep 10, 2026

    @os-litant
    Collaborator

    Triage: lands on AGENTS.md; domain:skills; priority:p2. ⛔ NOT the decision box — and the reason is that React already decided it.

    Eight separately-filed, separately-triaged cards (objectui#6018 · #5976 · #6591 · #6592 · #6697 · #6724 · #6813 · #686x) are one defect shape: code depending on a useMemo / useCallback result keeping its identity.

    The card routes itself to the maintainer because the ruling lands on a governed surface. ⇒ triage's answer: a governed landing surface is a merge gate, not a decision gate. The skills lane writes governed files routinely and the maintainer merges them; that is the normal flow, ⛔ not an escalation.

    The direction is ruled here, because the governing text is React's own contract

    React documents useMemo / useCallback as a performance hint and explicitly reserves the right to discard the cache. ⇒ code that depends on identity is depending on something the library never promised. That makes this 说明书脱节 / 恢复不变量 — SKILL.md's named non-escalating classes — ⛔ not a product decision.

    ⇒ The rule to write down: objectui code may not depend on the identity of a useMemo / useCallback result. Eight instances are the cost evidence; a ninth fix without the rule buys nothing.

    ⛔ Fences

    • ⛔ Do not invent exceptions. If the eight instances show a case where identity-dependence is genuinely unavoidable, propose it in the PR with the instance that forces it — do not write a rule with a carve-out you have not measured.
    • ⚠️ A rule with no instrument is objectui#8953 all over again (a rule landed in AGENTS.md this week with, in its own words, nothing that can stop it). ⇒ say plainly in the PR whether a gate is feasible; if it is not, declare that limitation in the rule text itself rather than leaving readers to assume enforcement.
    • ⛔ This card writes the rule, not the ninth fix. The eight instances keep their own cards.

    Size/model suggestion: M — short text, but it is a governed surface and the exception question needs real reading.

    分诊席位 · session_017VGfRocA8VjczSe84fgjY3 · R+166 · 2026-09-10T14:10Z · 本评论来自分诊座位


    Generated by Claude Code

  3. added theissue type on Sep 10, 2026
  4. claude commented on Sep 12, 2026

    @claude
    Contributor

    Claim: PM loop round 1
    Session: session_01MCLBsUgfykL74aU716rzVK (GitHub os-sales, skills seat), claimed at 2026-09-12T06:26Z
    Branch: claude/issue-8640-memo-identity-rule
    Worktree: objectui-issue-8640
    Domain: domain:skills (triage 5620057836: p2 Task — the direction is RULED there: React documents useMemo / useCallback as a performance hint and reserves the right to discard the cache ⇒ objectui code may not depend on the identity of their results; a governed landing surface is a merge gate, not a decision gate)
    File surface (region-declared): objectui AGENTS.md — ONE rule in the section that governs React / component authoring (the dev names the section after reading the file), stating the rule, the value-side vs effect-side seam (#6813 vs #6862: a value-side cache keyed on an object a refetch replaces is defeated by an effect-side discard), and — in the rule text itself — whether an instrument exists (a lint rule feasibility reading over the eight instances, ⛔ not a rule with an unmeasured carve-out); ⛔ no code, no lint rule landed here (feasibility only, a follow-up card if feasible), ⛔ none of the eight instance cards reopened
    Container & model: M (short text, governed, the exception question needs real reading of the eight instances), mode:subagent, model: opus — default tier (objectui AGENTS.md is not a clause-① file; the skills seat reviews at the contract-review tier); governed ⇒ draft at the human terminal with Fixes #8640
    Clause-②: no
    Thread-read: 5620057836
    Serial constraints cleared: objectui AGENTS.md free — PR #9226 (#8410 + #8492) MERGED 06:24Z aebc3a31; no open objectui PR touches it at 2026-09-12T06:26Z; objectui origin/main bedd7344 (577 lines; no line ratchet script exists in objectui — confirmed by ls scripts); H17 index ∩ this face = ∅.


    Generated by Claude Code

  5. claude commented on Sep 12, 2026

    @claude
    Contributor

    Dispatched — R1 wave 8 (objectui slot freed by PR #9226 landing), 2026-09-12T06:26Z, mode:subagent, build tier opus, skills-seat review at the contract-review tier. Claim 5644186028 is the dev's identity; branch claude/issue-8640-memo-identity-rule, cut from objectui origin/main at dispatch (bedd7344). Deliverable = the rule triage 5620057836 ruled, written once in objectui AGENTS.md: objectui code may not depend on the identity of a useMemo / useCallback result (React's own contract: a performance hint whose cache may be discarded); the value-side vs effect-side seam stated with the two instances that measured it (#6813 / #6862 — a discard that refetches walks through a value-side cache keyed on the replaced payload); the instrument question answered IN the rule text (a lint-rule feasibility reading over the eight instances: feasible ⇒ say so and the seat files the follow-up; not feasible ⇒ the rule declares its own limitation, per the objectui#8953 lesson); ⛔ no exception invented — if an instance forces one, propose it in the PR with that instance, not in the rule. ⛔ No code, no lint rule, no instance card reopened. Terminal: governed ⇒ draft at the human terminal with Fixes #8640. In flight on the lane: 2 devs (#17564, this card) + 2 PRs in queues (#17098, objectui#8696).


    Generated by Claude Code

  6. claude commented on Sep 12, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 8640,
      "status": "done",
      "branch": "claude/issue-8640-memo-identity-rule",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9257",
      "premise_still_valid": true,
      "summary": "Landed the ruled rule once, as objectui AGENTS.md section 5 commandment #10 (+1 bullet, 577 -> 578 lines) plus an empty-frontmatter changeset; no code, no lint rule, no instance card touched. Section 5 was chosen because its neighbours are the React authoring commandments (#6 type safety, #8 'UI state lives where it can survive', which already cites issue numbers in prose) and because the new rule sits immediately after #9, the rule that demands a claim point at its instrument and never write down that instrument's answer - #10's enforcement clause is #9 applied. Section 6 holds only code exemplars and section 9 is Chinese-voiced ops, so neither fits. The rule states the value/effect seam as its consequence: a value-side cache keyed on the payload object is walked through by an effect that still keys on a memoised identity, because a discard re-runs the fetch and setData(json) installs a fresh payload object even for a byte-identical answer, so a provider that refetches may not republish an equal payload as a new object and a consumer that needs stability keys on the data. The rule text also declares its own limitation: nothing enforces it, a lint rule is feasible for the same-file shape only. ACTION FOR THE SEAT: the same-file lint rule IS feasible (a home exists in eslint-rules/, and objectui#6592's census already ran that exact sweep by hand) but it covers 4 of the 8 instances only - the seat may file the follow-up card as a partial net, not as the thing that retires the class. No exception was forced by any of the eight; the boundary they draw (the rule forbids depending on identity, not using the hooks) is already in the rule's second sentence. PR is a DRAFT on a governed surface: no ready flip, no queue, no auto-merge, no approving review.",
      "tests": "Docs-only change; no package source touched, so no package test/typecheck is owed (check-changeset-presence printed: '1 file(s) changed, 0 of them published source of a package the release covers'). Gates run in the worktree after pnpm install, exit codes captured before any pipe: check-control-bytes EXIT=0 (7447 tracked text files); check-doc-links EXIT=0 (17 scan roots); check-changeset-presence EXIT=0; check-changeset-claims EXIT=0 (report-only); check-changeset-overwrite EXIT=0; check-changeset-no-major EXIT=0; check-changeset-fixed EXIT=0; check-new-cross-file-line-citations EXIT=0 ('VERDICT new-cross-file-line-citations: 0 new citation(s)'); check-doc-fence-languages EXIT=0; check-shell-escape-residue EXIT=0; check-bash32-floor EXIT=0; check-doc-expression-carriage EXIT=0 (report-only census; first run was a PREREQUISITE NOT MET before pnpm install, not a red); check-governed-queue-guard --test AGENTS.md EXIT=3 with the expected verdict line 'GOVERNED - 1 of 1 path(s) are on a governed surface: AGENTS.md x1'. Manual control-byte sweep beyond the gate: grep -naP over both changed files, exit 1, no hits. NOT MEASURED: check-doc-snippet-types - its prerequisite is a scoped 34-package turbo build (EXIT=2, PREREQUISITE NOT MET, not a red); declared narrowing, proven: this diff adds no fenced block (AGENTS.md carries 14 fence delimiters at bedd7344 and 14 after; the added line contains zero), so that gate's population is byte-identical and CI's run is the one that counts. Repo-wide pnpm lint is CI's per objectui AGENTS.md. Ablation/reverse verification: not applicable - this PR adds prose to a governed instruction file and lands no executable assertion to mutate; the falsifiable claims it makes were verified directly instead (see premises below). Premises re-derived, none inherited: React 19.2.8 (root package.json pin + pnpm-lock resolving react@19.2.8; the 19.2.18 in the lockfile is @types/react); zero imports of Activity from react and zero unstable_Activity across packages/ and apps/ (grep exit 1); AGENTS.md 577 lines at bedd7344 and no line-ratchet script in objectui; all 12 open objectui PR file lists enumerated (release PR #5400 paged to exhaustion) - none touches AGENTS.md; all eight instance cards confirmed closed, none reopened. Feasibility counts (in the PR body): a same-file AST rule would have caught objectui#6018, #6592, #6697, #6862 and missed objectui#5976 (value never memoised - inverse shape), #6591 (crosses a prop), #6724 (crosses a hook return), #6813 (crosses a context); it also collides with react-hooks/exhaustive-deps, live at warn via reactHooks.configs.recommended.rules, eslint-plugin-react-hooks 7.1.1.",
      "mcp_calls": "0 - every GitHub read and write went through repo-scoped REST from this container",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: the React Compiler-era react-hooks rules (refs, immutability, set-state-in-effect, preserve-manual-memoization, use-memo) are all downgraded to warn in eslint.config.js with the reason 'codebase predates these rules'; preserve-manual-memoization is the nearest neighbour of rule #10 and would block nothing today. Successor: whoever writes the same-file lint-rule follow-up card.",
        "noted, not filed: the positive-control count in providerCtxIdentity.discarded.test.tsx's docblock ('179 files importing useMemo', the denominator for the zero-Activity reading) has moved - the like-for-like population is larger now. Not filed and not repaired: the sentence self-dates ('re-verified for this card'), so it reads as a point-in-time derivation rather than the live claim AGENTS.md #9 forbids. Successor: the ninth-instance author, who is the next reader of that harness."
      ]
    }

    Generated by Claude Code

  7. claude commented on Sep 12, 2026

    @claude
    Contributor

    ACCEPT — objectui PR #9257 (head 6afd0b4c) reviewed in-seat at the contract-review tier; governed (objectui AGENTS.md) ⇒ four-piece at the human terminal (skills seat, session session_01MCLBsUgfykL74aU716rzVK, 2026-09-12T06:41Z).


    Generated by Claude Code

  8. claude commented on Sep 13, 2026

    @claude
    Contributor

    Landed — PR #9257 MERGED 2026-09-13T04:12:05Z as bde07805d (merged by os-zhuang through the merge queue after approval at the human terminal). Skills seat stay-behind record, session session_01MCLBsUgfykL74aU716rzVK (GitHub os-sales), 2026-09-13T04:13Z.

    Verified on objectui origin/main by git: the merge commit is an ancestor of origin/main and single-parent; AGENTS.md :110 carries section 5 commandment #10 「Never depend on the IDENTITY of a useMemo / useCallback result …」 as the ACCEPT 5644254308 specified (578 lines, the +1 bullet; objectui has no line ratchet). Ruling 5620057836 landed once, in the one place.

    Residue cleared in this stroke: pm:dispatched off, assignee cleared (read back). domain:skills and priority:p2 stay (ownership, not state).


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions