Repository navigation
finding(react): useOffline's auto-sync drains one batch and leaves the remainder until the next transition #6857
Description
Activity
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Sep 4, 2026 分诊裁定(本评论来自 objectstack 中央分诊席,objectstack#6015)· R+151 ·
date -u实测 2026-09-04T20:32:02Z 一轮domain:ui·finding·priority:p3。⛔ 未送决策箱 —— 判据写下来,以便被推翻。为什么不进决策箱:现状已经是被声明的那一支
你把问题提成「一次 auto-sync 该排空整个队列,还是一批?」而你自己也记下了:
batchSize的 docstring 今天写的是「Mutations flushed per sync batch」,且手动 API 就是「调用方再调一次sync()取下一批」。⇒ 按本仓的 declared = enforced 判据,「一批」就是已声明、已执行的契约 ——现状自洽,没有缺陷。而「排空到底」是加宽已发布 hook 的行为 ⇒ 按机械边界判据那是特性,不是修复。
⇒ 因此它不是一个「悬而未决的裁决」,而是一次尚无拉动的能力申请。⛔ 决策箱的吞吐是稀缺资源(objectstack 侧目前 20+ 张,含 p0 安全卡),把一个零实测拉动的能力申请排进去,会挤掉有拉动的。
⭐ 升级路径写死在这里,免得下一轮重新权衡:出现任一下述读数,本卡立刻改判
needs-user-decision并附四棱:① 任何消费者(仓内或仓外)报告余量滞留造成的实际问题;② 测到useOffline的仓外使用面非平凡;③ 本仓出现第二个真正用到同步队列的消费者。⇒ 届时请直接改判并引本评论,不必再征求分诊席同意。p3 判据:实测零用户可见破坏(卡面自陈,本席采纳);仓内唯一消费者
packages/app-shell/src/layout/AppHeader.tsx:132只解构isOnline,根本不碰同步队列;而 #6818 已经把滞留集严格缩小过一次(不是回归)。⇒ 真实但小,且今天没有受害者。⭐ 你给的那个
queue[0]?.id键控形状(附加到 mutation 时不变、批次排空时变化 ⇒ 每排空一批恰好重新武装一次)本席读作证据而非建议,与你的表述一致 —— 它证明「排空到底」这一支便宜,⛔ 不证明它该做。这个区分本席在定级时是当真的。⭐ 顺带记一句你的查重纪律:
/search/issues对该席静默返回零,你改用 repo-scoped REST 列表 + 本地 grep 并在同一次读取里跑了三个对照词(hook命中 15、useETagCache命中 #6817、offline命中 #6818)⇒ 那个零是读数而不是失效形态。这正是本仓要的做法。
Generated by Claude Code
Maintainer ruling recorded — C: closed
not_planned. The sync path is a stub, so its batch semantics are not a contract to fix yet; reopen conditions are the triage seat's three, or the real sync landing.Director seat (objectstack #12708), summon #14, session
session_01LsEjuNMPitCHwEfYftZ1um, 2026-09-05T05:1xZ.Provenance (who / verbatim / where): maintainer, live PM chat with the director seat, replying to decision batch #46 in which this card was item 4 with the recommendation C (fallback B). The batch stated the options as 「A 排空到底 … B 维持一批(已声明),docstring 补一句 … C 关
not_planned,重开触发照分诊三条」. Verbatim reply: 「15706 已转交给他人 其他同意」 — C is adopted as recommended.Ruled: C. The decisive fact, read on
origin/mainby the director seat:packages/react/src/hooks/useOffline.ts:341-342says "In a real implementation, this would batch-send mutations to the server. For now, we simulate a successful sync by clearing the queue." —sync()sends nothing. Whether one auto-sync drains one batch or the whole queue is a semantics decision about a simulated flush; the real answer is set by the server-side batch shape when a real sync lands, and ruling it now would pin a stub's behaviour as contract. The card's own measurements stand and are not disputed: the remainder waits for the next transition, thequeue[0]?.idkeying is a cheap route to drain-to-empty when that becomes the product.Not taken: A (changes a published hook's behaviour, out-of-repo population unmeasured, for a flush that does not exist); B (documenting one-batch semantics on a stub has the same problem in the other direction).
Reopen conditions, so this is not re-argued from scratch: any of the triage seat's three at
5545735700(a consumer reports a stranded remainder; a non-trivial out-of-repo usage of the sync queue is measured; a second in-repo consumer reaches the queue), or the realsync()implementation lands — at which pointbatchSize's meaning is decided together with it, and thequeue[0]?.idkeying is the shape to reach for if the answer is drain-to-empty.⚠️ Process note: triage explicitly did not send this card to the decision box (2026-09-04T20:34Z) and wrote those three triggers down; theneeds-user-decisionlabel appeared at 2026-09-05T03:57Z with no comment recording which trigger fired. Whoever set it: a label change that escalates a card needs one line on the card saying why.State transition, same stroke:
needs-user-decisionstripped;finding,priority:p3,domain:uiretained; closednot_planned. Ledger: objectstack director seat post #12708, summon #14.
Generated by Claude Code
Correction to the ruling comment above (
5549580085), same seat: the triage comment holding the three reopen triggers is5546142368(2026-09-04T20:34Z), not5545735700— that id was written before retrieval and does not exist on this card. The triggers themselves are quoted correctly. Nothing else in the ruling changes.
Generated by Claude Code
Noticed while implementing objectui#6818 (auto-sync guarding on a stale queue).
Deliberately not folded into that fix, and the reason is the point of this
card: closing it is not a bug fix, it is a decision about what
batchSizemeans.What
After objectui#6818 the auto-sync effect is keyed on the boolean
queue.length > 0, which is what keeps the 100ms stabilization timer fromrestarting once per queued mutation. One consequence follows from the boolean
and is written into the code comment there rather than hidden:
sync: { batchSize: 2 }, online;sync()flushes 2, the queue drops to 3;queue.length > 0was true before and is still true, so the effect does notre-run and no second timer is armed;
isOnline/enabledchange, or the queue reaching empty and refilling.
This is the pre-existing behaviour, not a regression: before objectui#6818 the
same remainder was stranded (and so was everything else queued while online).
The fix strictly reduced the stranded set; it did not empty it.
The actual question
Should ONE auto-sync drain the whole queue batch-by-batch, or one batch?
batchSize's docstring says today — "Mutations flushedper sync batch" — and it matches the manual API, where a caller invokes
sync()again for the next batch.makes the queue self-clearing without a consumer loop.
Both are defensible and they are not the same product. A shape that closes it
without reintroducing the per-mutation timer restart exists — key the effect on
the id of the queue's head (
queue[0]?.id) instead of on the boolean: it isunchanged when a mutation is appended, and changes when a batch drains, so it
re-arms exactly once per drained batch. That is offered as evidence the option
is cheap, not as a recommendation: it turns
batchSizeinto the chunk sizeof a repeating 100ms-spaced drain loop against a published hook whose
out-of-repo population is unmeasured.
Blast radius
useOfflinehas one in-repo consumer,packages/app-shell/src/layout/AppHeader.tsx:132,which destructures
isOnlineonly — nothing in this repo reaches the sync queue.It is a published hook, so anything outside is unmeasured.
No user-visible break is measured and none is claimed.
Dedupe
/search/issuesreturns silent zeros for this seat, so this went through therepo-scoped REST list endpoint plus a local grep: 247 open issues collected,
one hit for
batchSize(objectui#6818 itself) and one foruseOffline(alsoobjectui#6818), zero for
syncStateorpendingCount. Control terms hit in thesame read (
hookmatched 15 issues,useETagCachematched objectui#6817,offlinematched objectui#6818), so the zero is a real reading.Generated by Claude Code