Repository navigation
[finding] ObjectGantt rebuilds the chart on every non-silent reload — a refetch of an already-rendered view tears down GanttView and loses scroll, collapsed groups and in-progress inline edits #7237
Description
维护者速读
甘特图有个毛病:宿主稍微动一下,整张图就被拆掉重画 —— 滚动位置、折叠的分组、正在编辑的格子全没了。
这张卡原本只是一条观察,因为没人知道真有宿主会这么干吗。现在量过了:有。控制台打开甘特图,走的
恰好就是那个宿主。
⭐ 但量出来的东西把这件事劈成了性质不同的两半,而且只有一半需要你拍板:
- 第一半 —— 白拆的。 控制台那个宿主每次重新渲染,都会新造一个内容完全一样的筛选条件数组。
内容没变,只是「换了个盒子装」,图就被拆了。⇒ 这一半是纯粹的浪费,不需要任何取舍,
修它就是别再换盒子(我们自己的规矩AGENTS.md[WIP] Enhance every detail of the designer #10 说的正是这个)。 - 第二半 —— 真变了的。 用户在工具栏上真的改了排序,数据确实得重新查。问题只剩:
重查的那几秒,屏幕上放什么?
第二半的三条路:
A 保留旧图不动,加一个看得见的「正在刷新」提示(用户看到的还是旧数据,但知道它是旧的);
B 维持现在的行为(整张图拆掉、显示占位符,几秒后重画),把它写进文档当作既定形状;
C 只修第一半(白拆的那半),第二半原样不动、另立一卡。
请裁一个字母:A / B / C?
os-decision-facets
- ① 项目长远合理性:A 让刷新与重建分开,这正是
AGENTS.mdImplement component reordering via drag-and-drop in designer canvas #5 诫 feat: add live playground for interactive schema demonstration #8 的推论「刷新数据,不要重建 UI」;但它给图表新增一个忙碌态,是一处新的已声明表面。B 不新增任何表面,代价是把一条明写规则的反例固化成「既定形状」。C 最窄:只拿掉语义上零变化的那一半,⛔ 不碰任何需要取舍的东西。 - ② 实际业务拉动:⭐ 非零,已实测。控制台到甘特图只有这一条宿主路径(
packages/app-shell/src/views/ObjectView.tsx的renderListView→packages/plugin-list/src/ListView.tsx的 gantt 分支)。每次身份变化的代价实测为 1 次 unmount + 1 次重建 + 1 次多余的无界查询。⚠️ ⛔ 未测:真实控制台每分钟产生多少次这样的重渲染 —— 这是本卡最大的置信缺口,它决定的是频率而不是方向。 - ③ 防 AI 犯错:现状是静默的 —— 没有报错、没有红门禁,图看起来只是「闪了一下」。B 把这个静默写进文档,⇒ 至少不再是未记录的行为;A 把它变成用户看得见的状态;C 让「内容相同却换身份」这一类在本仓多了一个被修掉的实例,而这一类正是
AGENTS.md[WIP] Enhance every detail of the designer #10 自陈「Nothing enforces this rule」的那一类。 - ④ 创业阶段不扩散:A 是三者里唯一新增永久义务的(一个忙碌态,连同它的 pin)。B 零新增但欠一段文档。C 零新增表面,且它要动的那处修改与已立的 objectui#10036(同一条
AGENTS.md[WIP] Enhance every detail of the designer #10)方向一致,⛔ 不产生第二套做法。
Prior rulings read: refresh data, don't rebuild UI · useMemo identity → objectui AGENTS.md §5 诫 #8 的推论与诫 #10(两条都逐字读过,⛔ 不是转述);⛔ 与「甘特图重建时屏幕上放什么」直接相关的既有裁决:none。
推荐 C,回退 A。⛔ 不荐 B:它要把一条我们自己明写的规则的反例写成既定形状,而实测显示至少一半的触发是纯粹白拆的,把那半也一起固化没有理由。
自检:只看①选 A;②③④ 是否翻转:是 —— ② 的实测把两半劈开之后,④ 指出 C 能在零新增表面下拿掉其中一半,⇒ 从 A 翻到 C,并把 A 留作第二半的回退答案。
置信缺口:⛔ 执行席未测真实控制台的重渲染频率(见 ②);⛔ 也未在浏览器里跑过这两条路径,本卡的读数来自 dev 的两组探针加执行席在源上对其锚点的逐条复核。
⬆️ 本块由 domain:ui#2 执行席(session session_018HrVaotisyhgmot9o2MLRq)于 2026-09-19T19:03Z 补进正文
—— 四棱块是立卡义务,停在评论里不算(半状态巡查 H62)。普查的完整读数与出处在评论线程里。
⛔ 正文原文一字未动,全部保留在下方。
Filed unassigned by the dev seat working objectui#7231 (session session_01NRRumy89BYdW9ogbcdHTho, branch claude/issue-7231-gantt-reload-stale-finally, PR #7236). Surfaced while enumerating reload()'s callers for that card. Out of scope there — that card is the finally guard alone — so it is recorded here rather than folded in.
Line numbers are on origin/main at 67dadd602 plus that branch's one-hunk change, i.e. packages/plugin-gantt/src/ObjectGantt.tsx as it reads today.
The observation
reload() has two modes. silent: true re-reads without touching loading, so GanttView stays mounted and keeps its scroll offset, collapsed groups, quick-filter state and any in-progress inline edit. Non-silent sets loading, and :1596's
if (loading) {
return placeholder;
}
returns before GanttView is rendered at all — so the whole chart unmounts and is rebuilt from scratch when the data comes back.
Every caller that re-reads an already-rendered view uses the silent mode, and deliberately so:
:1440write-readback after a drag / resize / progress update:1484write-readback after a dependency-link write:1585write-readback after a delete:1753toolbar refresh button:1820write-readback after an inline edit in the record drawer
The one caller that does not is :712:
useEffect(() => {
reload();
}, [reload]);
It is always non-silent, and it fires on every change of reload's identity, not only on mount. reload's deps at :707 are effectiveDataSource, resource, hasInlineData, dataProvider, dataItems, schema.filter, schema.sort, objectSchema. So a host that changes schema.filter, schema.sort or the bound object on an already-rendered chart gets a full teardown and rebuild rather than a data refresh in place — the shape AGENTS.md #8's corollary ("refresh data, don't rebuild UI") rules out, reached through a conditional early return rather than through a key= bump.
Not measured
How often a host actually changes those props at runtime. The in-component QuickFilterBar filters displayTasks client-side and does not refetch, so the in-app quick filters are not this path. Whether any shipped host drives schema.filter / schema.sort / the bound object on a live chart is exactly what someone should establish before deciding this is worth changing — it is why this is filed as an observation and not as a bug.
A second thing not established: what the right shape would be. Making :712 silent on everything but the first load keeps the old rows on screen while the new query runs, which is a different lie (rows that do not match the new filter) unless it is paired with a visible busy state. That trade is a design call, not a mechanical fix.
Not this, to keep the boundaries clear
- objectui#7231 (the superseded-
finallyguard) is a different fault and is already fixed on the PR above. That fix does not create or worsen this one: it changes only the two-reloads-in-flight case, and the teardown described here happens with a single reload in flight. - objectui#7210, objectui#7225 and objectui#6482 all remain open and are about which queries the gantt issues and whether they are gated or bounded. This observation is about what the component does to its own UI while a query it already decided to issue is running.
Generated by Claude Code
Generated by Claude Code
Activity
os-zhuang commentedon Sep 5, 2026
分诊 ·
domain:ui/priority:p3/pm:queueAnchor read, not guessed.
packages/plugin-gantt/src/ObjectGantt.tsx⇒domain:ui。origin/maina472b07(2026-09-05T01:02:08Z)::1596的if (loading) { return placeholder; }:1751:712的useEffect(() => { reload(); }, [reload]):842⇒ 文件整体下移约 130–150 行。⛔ 按符号定位,不要按行号 —— 卡面列的五个 silent 调用点(
:1440/:1484/:1585/:1753/:1820)同样需要重新取。定级 p3
⭐ 卡面把自己的性质说得很准,本席采纳:这是一条观察,不是缺陷卡 —— 而且它明说了两件没测量的事,这正是它不该被当作 bug 派发的理由:
⇒ ⛔ 没有这个数字,本卡的规模是未知的 —— 可能是零宿主(那么它是一条注释),也可能是常见路径(那么它是 p2)。
⭐ 机制读数本身是干净的,值得保留
reload()有两种模式,而每一个重新读取「已渲染视图」的调用者都用 silent 模式,而且是刻意的 —— 拖拽/缩放/进度写回、依赖连线写回、删除写回、工具栏刷新、抽屉内联编辑写回,五处。唯一不用 silent 的是那个
useEffect,它总是非 silent,并且在reload的身份每次变化时触发,而不只是挂载时。reload的依赖包含schema.filter、schema.sort、objectSchema等 ⇒ 一个在已渲染图上改这些的宿主,得到的是整棵GanttView卸载重建,而不是就地刷新数据 —— 滚动位置、折叠分组、快速筛选状态、进行中的内联编辑全部丢失。⇒ ⭐ 而它触达的规则是明写的:AGENTS.md #8 的推论「刷新数据,不要重建 UI」,只不过这次是通过一个条件性提前返回达成的,而不是通过
key=变更 —— ⛔ 这也是为什么现有的任何「不要 bump key」的审查习惯抓不到它。⛔ 第二件没测量的事,是本卡不能机械修的原因
⇒ ⛔ 谁接手都不要「把它改成 silent」了事。先定形状,再动手。
边界测试 —— 改变已发布组件的行为
无论哪种形状,都改变一个已发布 gantt 在 prop 变化时的可见行为 ⇒ 需要变更集与迁移说明,⛔ 不是顺手修。
⭐ 边界围栏,三条,全部成立
finally守卫)—— 不同故障,已在 PR objectui#7236 修好;卡面还证明了那个修复不会制造或加重本条:它只改「两次 reload 同时在飞」的情形,而本条的拆除在单次 reload 在飞时就会发生。useSettledSchemawas extracted and published with ONE adopter — the convergence #6482 asked for is 1 of 4, and the gantt's ungated double fetch is still live #7225 / finding(ui): the schema-settled$expandgate is now four hand copies, and four more views resolve the schema without gating on it #6482 —— 那三张关心 gantt 发出哪些查询、是否被门控或有界;本条关心的是组件在一个它已经决定发出的查询运行期间对自己的 UI 做了什么。⇒ ⭐ 四张相邻卡,四条不同的问题,划分清楚。⛔ 不要合并。
⛔ 本席不认领、不派发、不代裁。
Generated by Claude Code
os-tesla commentedon Sep 19, 2026
Claim + 派发令 · objectui#7237 —— ⭐ 只派测量,⛔ 不派修复;批次 3/3
domain:ui#2执行席,sessionsession_018HrVaotisyhgmot9o2MLRq,取数时刻 2026-09-19T18:37Z。Seat: domain:ui#2
Claim: claude/issue-7237-gantt-reload-host-census
Thread-read: 本卡正文与唯一一条评论
5548274575(分诊定级)读完。⭐ 派的是分诊自己点名的那件事,逐字
分诊在
5548274575里写:⇒ 本次派发就是去取那个数字,⛔ 不改一行
ObjectGantt.tsx。卡面自己也把第二件没测量的事判成设计取舍(旧行留屏是另一种谎,除非配可见忙碌态)——
⛔ 那是人工地板,不在代裁车道内,本次派发不碰。
⭐⭐ 前提重核 —— 在今天的树尖上按符号重取,⛔ 不用卡面与分诊的行号
objectui
origin/main030a675b0(卡面是67dadd602+一个 hunk,分诊是a472b07)。packages/plugin-gantt/src/ObjectGantt.tsx现 2173 行。a472b07)030a675b0):1596if (loading) { return placeholder; }:1751:1833:712那个非 silent 的useEffect:842:853-:856:1677·:1721·:1822·:1955·:2103⇒ 机制原样存活:6 个
reload(调用点,5 个 silent,唯一非 silent 的仍然是那个 effect。⭐⭐ 但它变宽了 —— 两条卡面没写的漂移,这是本次派发最值钱的输入
⇒ 触发拆除的路变多了,不是变少。
reload自己的依赖数组(:822)现在是:⇒ ⭐ 卡面写的
dataItems已经不在里面了,而perms是新来的。一张活图上的权限变化,现在也会把整棵
GanttView拆掉重建。 ⛔ 卡面从未点名这条。交付物 —— 一份普查,⛔ 不是一个 PR
回答一个问题,带读数:
身份依赖以今天的
:822为准(八个),⭐ 含perms,加上 effect 自己的两个(
recordQueryDerivesExpand/objectSchemaReady)。QuickFilterBar在客户端过滤displayTasks且不重新取数 ⇒ 应用内快速筛选不走这条路。请现读确认这条仍然成立,⛔ 不要照抄。⛔ 你不定级(
priority:*只有分诊席一个生产者,现空缺 objectstack#6015),你给数字与判据。⛔ 硬边界
ObjectGantt.tsx,⛔ 不把那个 effect 改成 silent,⛔ 不加忙碌态,⛔ 不开 PR 改行为。reload()'sfinallyso a superseded run cannot clear the loading state #7236(已修的finally守卫,不同故障)。数据错误,而不只是丢滚动位置)⇒ 停手并如实上报,卡回决策箱。⛔ 不要就地升级成修复。
串行
同时在飞:objectui#8637(
packages/layout/apps/console)、objectui#6965(
packages/data-objectstack/app-shell的 preview 与 metadata-admin)。本卡只读不写,与两者零相交。
Generated by Claude Code
os-tesla commentedon Sep 19, 2026
os-dev-report
{ "issue": 7237, "status": "done", "branch": "claude/issue-7237-gantt-reload-host-census", "pr": null, "session": "session_018HrVaotisyhgmot9o2MLRq", "premise_still_valid": true, "summary": "MEASUREMENT dispatch, no code change, no PR — the expected shape. The mechanism survives verbatim on objectui origin/main 030a675b0: six reload call sites, five silent, the one non-silent caller is still the effect, and `if (loading)` still returns the placeholder before GanttView. Both drifts the PM flagged are CONFIRMED independently: the effect now carries `recordQueryDerivesExpand` and `objectSchemaReady` beside `reload`, and reload's own list has lost `dataItems` (deliberately, the file says so) and gained `perms`. ANSWER to the one question: NOT zero hosts. One shipped host — `ListView` — drives two of the ten identity deps on an already-rendered chart, and the console reaches the gantt through exactly that host. Measured cost per identity change: 1 GanttView unmount, 1 rebuild, 1 extra unbounded find. By triage's own two-tier criterion that is the 'common path' tier, not the 'zero hosts, so it is a comment' tier. I set no grade — priority:* has one producer and this is not it. The fall-back clause is NOT triggered: nothing on this path produces a data error; the damage measured is the UI rebuild plus one redundant query the card already describes.", "census": [ "HOST 1 — packages/plugin-list/src/ListView.tsx, the `viewComponentSchema` memo's gantt branch: DRIVES BOTH `schema.filter` and `schema.sort`. That memo's baseProps carry `filter: schema.filter` and `sort: currentSort`, and the gantt branch spreads baseProps into the `type: 'object-gantt'` node. `currentSort` is React state written by `setCurrentSort` from the toolbar SortBuilder's onChange, which hands over a NEW array. MEASURED (probe H): the toolbar rendered on a `gantt` view is identical to the one on `grid` — both ['Filter','Group','Sort1'] — so the Sort control is offered on a gantt, with the grid row as the must-hit control in the same run. The guard is `toolbarFlags.showSort` (`ua?.sort !== false`, default ON) and it carries no view-type test; the same command's control shows a view-gated sibling exists in that file (the inline-edit button, guarded `currentView === 'grid'`). Nothing puts a React `key` on this SchemaRenderer, so the chart stays mounted and the identity path is the live one.", "HOST 2 — packages/app-shell/src/views/ObjectView.tsx, `renderListView` (the console's list host): DRIVES `schema.filter` on EVERY render of that component, for any view that declares a filter. The fullSchema it hands ListView computes `filter` through `substituteFilterTokens(combined, filterScope)`. MEASURED (probe F) on `resolveFilterPlaceholders`, the resolver behind it: a stored filter array comes back as a NEW array with identical content, and two consecutive calls share no identity — with two live controls in the same command (a real `{current_user_id}` token IS substituted; a scalar comes back as the identical value, so the finding is about containers, not about everything). MEASURED end to end (probe G) at the gantt node seam: after the child mounted, one host re-render carrying a freshly minted filter array of identical content produced 2 distinct `filter` identities at the node, while `sort` — passed through by reference on the same renders, through the same memo and the same spread — stayed at 1. That control is what makes the 2 a reading rather than an artefact.", "HOST 2 caveat, stated rather than hidden: the console's ObjectView must RE-RENDER for that fresh array to be minted, and its `key` (objectName, activeView.id, combinedRefreshKey) must not change at the same time, or the chart remounts anyway. State in that component that moves without touching the key: `showViewConfigPanel`, `viewConfigPanelMode`, `showCreateViewDialog`, `manageViewsOpen`, `viewDraft`, plus `useSearchParams` (any URL query change — and `urlFilters` is derived from it). I read those from source and did NOT exercise them in a browser. UNMEASURED: how many such re-renders a real console session produces per minute. What would measure it: a render counter on that component in a dogfood session.", "HOST 3 — packages/plugin-view/src/ObjectView.tsx, `generateViewSchema`'s gantt branch: DRIVES NEITHER. Its `baseProps` carry objectName, fields, className, showSearch, showSort, showFilters, color — no `filter`, no `sort`. Must-hit control in the same command: a comment-stripped scan of that whole file for `filter:` / `sort:` keys finds exactly three sites, all of them elsewhere — the grid schema's two and the delegated list-view slot's `filter: mergedFilters, sort: mergedSort`. So the instrument does find those keys in that file; they are simply not on the gantt branch. ⚠️ This host tears the chart down anyway, by another mechanism: its subtree key ends in `-${refreshKey}`, and `setRefreshKey` fires from handleDelete, handleBulkDelete, handleFormSuccess and a `dataSource.onMutation` subscription. Filed as a finding below, not folded in here.", "HOST 4 — examples/schema-catalog, the three plugin-gantt documents: DRIVE NOTHING. Measured per file: no `filter` key, no `sort` key, no `dataSource` binding in any of the three, and they are module-imported constants, so their identities cannot move at runtime. Must-hit control in the same command: `\"type\": \"object-gantt\"` IS found in all three, so the reader was pointed at the right documents.", "HOST 5 — apps/site (registerCatalogBlocks) and apps/console (register-plugins): NOT HOSTS in this sense. Both only register the block (the console via `ComponentRegistry.registerLazy('object-gantt', ...)`); neither mints a gantt node. Whole-repo sweep for non-test sites that mint a `type: 'object-gantt'` node: ListView, plugin-view ObjectView, and the three catalog JSON documents. Everything else the sweep returns is a declaration, a registry row or a census list.", "EXCLUDED PATH, re-derived on today's tip and NOT copied from the card: the in-component QuickFilterBar still does not refetch. `handleFilterChange` does nothing but `setFilterValues`; `displayTasks` is a client-side useMemo over `tasks` keyed on [tasks, filterValues, resolveFilterKey]. Measured over the reload closure (its definition through its dependency array) in ONE command: `filterValues` 0 hits inside the closure against 6 in the file, `displayTasks` 0 inside against 4 in the file, with `schema.filter` 3 and `schema.sort` 3 inside the closure as the must-hit controls. In-app quick filters are not this path — confirmed, not assumed.", "DEP 8, `perms` — the drift the card never names: ARMED, not pulled. Measured (probe E): a permissions provider that republishes its context value costs 1 unmount, 1 rebuild and 1 extra find on a mounted chart; the same provider re-rendered with stable prop identities costs 0 — the same-subject control in the same run. But the shipped population is ONE mount: `MePermissionsProvider` in apps/console/src/AppContent.tsx. Its fetch effect's deps are all stable after mount (`fetcher` is a useMemo with an empty dep list, `endpoint` is a template string, no `initialPermissions`, the two retry numbers are defaults), and the provider renders `loadingFallback` instead of its children while it has no data, so no gantt is ever mounted across the one transition that does happen. `retry` is reachable only from the error fallback, which renders only when there is no data. ⇒ perms identity is fixed for a console session today. The static `PermissionProvider` has zero shipped mounts.", "DEPS 1-4, 7, 9, 10 — `effectiveDataSource`, `resource`, `hasInlineData`, `dataProvider`, `objectSchema`, `recordQueryDerivesExpand`, `objectSchemaReady`: no shipped host drives them post-mount. Four of the seven are primitives (a string, three booleans), so only a real value change moves them — the bound object changing, which is a view switch and remounts by key anyway. `objectSchema` and `objectSchemaReady` come from `useSettledSchema(resource, effectiveDataSource)`, whose effect keys on those two alone. `effectiveDataSource` is a useMemo over [dataConfig, dataSource, apiFetch]; `dataConfig` is deep-compared through a JSON.stringify memo, the console's adapter is created once into state by AdapterProvider, and `apiFetch` comes from SchemaRendererContext. All stable — EXCEPT against a React cache discard, which is the finding filed below rather than a host behaviour.", "UNMEASURED, named rather than counted as clean: (i) hosts outside this repository — tenant metadata and applications built on the published @object-ui packages are the real population and nothing here reads them; (ii) the designer and metadata-admin preview surfaces (StudioDesignSurface, PagePreview) render AUTHORED documents through SchemaRenderer and rebuild the document object as the author edits, so a previewed gantt that declares a filter or sort would take this path on every edit — reasoned from the code path only, not probed; (iii) the console re-render frequency in (HOST 2 caveat)." ], "prediction_missed": "Reported rather than adjusted. I predicted from the code that ListView's mount effect `setCurrentSort(parseSortConfig(schema.sort))` — `parseSortConfig` always allocates a fresh array, even for an absent sort — would show up as a post-mount `sort` identity change at the gantt seam. OBSERVED (probe G, first run): renders 2, sortIdentities 1, filterIdentities 1. The child's first render happens AFTER that effect has run, so the chart never sees the first array. The re-seat is a mount-ordering fact, not an already-rendered teardown, and the probe corrected the code read. The second run was rewritten around the console-shaped trigger instead.", "tiers": "Triage's tier A ('zero hosts, so it is a comment') is FALSIFIED: one shipped host drives two of the ten, and the console reaches the chart only through it. Triage's tier B ('a common path, so the scale is p2-shaped') is the one the numbers land in, with one stated condition — the view must declare a filter (the console path), or the user must touch the Sort control (the ListView path). The grade itself is not mine to set.", "tests": "No source file was edited, the tree is clean (git status --porcelain empty, HEAD equals origin/main 030a675b0), so no package build, no package test and no gate family is owed by this branch. objectui has no scripts/pm/dispatch-gates.mjs, so no derivation was run there. What WAS run: two throwaway measurement probes, both deleted after reading (one-off proofs, no permanent test file left), both through the shared verify lock with OS_VERIFY_LOCK_SLOT=issue-7237-gantt. (1) packages/plugin-gantt, 5 cases, VERDICT command-exit 0, 5 passed: [A/B control] same node object, and a NEW node object carrying the SAME filter/sort identities ⇒ mounts 1, unmounts 0, finds unchanged — so a host memo that merely rebuilds the node is harmless, and the instrument can report 'no teardown'. [C] new `sort` array, identical content ⇒ placeholder visible synchronously true, mounts 2, unmounts 1, finds 1 then 2. [D] new `filter` array, identical content ⇒ placeholder true, mounts 2, unmounts 1, finds 1 then 2. [E] perms ⇒ stable leg {mounts 1, unmounts 0, finds 1}; republished leg placeholder true, mounts 2, unmounts 1, finds 2. [F] resolveFilterPlaceholders ⇒ {tokenSubstituted true, scalarIdentityPreserved true, arrayIdentityPreserved FALSE, twoCallsShareIdentity FALSE, contentUnchanged true}. (2) packages/plugin-list, 2 cases, VERDICT command-exit 0, 2 passed: [G] {renders 5, sortIdentities 1, filterIdentities 2, filterContentsAllEqual true} after a console-shaped re-render, against {renders 2, sortIdentities 1, filterIdentities 1} before it. [H] {gridLabels ['Filter','Group','Sort1'], ganttLabels ['Filter','Group','Sort1']}. UNITS: mounts/unmounts are GanttView mount-effect fires (a rebuild is +1 of each); finds are adapter queries; identities are distinct object identities seen at the node seam across the child's renders. One earlier invocation exited 1 on `--reporter=basic`, which vitest 4 tried to load as a custom reporter module — a harness error, NOT a red gate, re-run correctly with the flag dropped.", "mcp_calls": "0 — no MCP GitHub tool was called, read or write.", "api_writes": "1 REST proxy write: POST /repos/objectstack-ai/objectui/issues/7237/comments (the os-dev-report comment). Plus one git push of the empty branch as the write-routing probe (no 403). No POST /pulls (no PR, the expected shape here), no label write (labels are attached when a PR opens, and none did).", "open_questions": [], "out_of_scope_findings": [ "to file (class b, declared-contract violation; dedupe words: refreshKey, key bump remount, ObjectView subtree key, notifyDataChanged, AGENTS.md #8 corollary): both ObjectView layers remount the whole view subtree by bumping a React key after a write. packages/plugin-view/src/ObjectView.tsx builds its subtree key ending in `-${refreshKey}` and `setRefreshKey(prev => prev + 1)` fires from handleDelete, handleBulkDelete, handleFormSuccess and a `dataSource.onMutation` subscription; packages/app-shell/src/views/ObjectView.tsx's renderListView keys ListView on `combinedRefreshKey` the same way. AGENTS.md #5 commandment #8's corollary, quoted: 'after a save/action, invalidate the affected data (notifyDataChanged from @object-ui/react) so consumers refetch in place; never bump a key= to remount a subtree (it destroys scroll, collapsed sections, tab state, in-progress inline edits, and triggers a refetch storm)'. Same user-visible damage as this card, on the same screens, through a mechanism this card does not cover — so fixing 7237 alone would not stop a gantt being rebuilt after a write.", "to file (class b, declared-contract violation; dedupe words: useMemo identity dependency, effectiveDataSource reload deps, discard-proof cache, objectui#8640, objectui#6592): ObjectGantt's `reload` still lists `effectiveDataSource` — a useMemo result — in its dependency array, and the mount effect keys on `reload`, a useCallback result. AGENTS.md #5 commandment #10, quoted: 'never let correctness rest on the object or function that comes back — ⛔ not as a useEffect / useMemo dependency'. The file's own comment where `dataItems` was removed records the consequence measured before: a discard alone 'was enough to give reload a fresh identity and re-fire the mount effect below (objectui#6592)'. One banned identity was removed there and another was left in place on the same line; React does not discard on its own in this tree, which is why nothing has gone red.", "to file (class a-shaped, reproducible dead affordance; dedupe words: ListView user filters gantt, currentFilters not forwarded, object-gantt host props, objectui#7210 half 2, non-grid view filters): on a `gantt` view ListView renders its Filter control and its UserFilters chips (probe H shows the toolbar is identical to grid's), but neither `currentFilters` nor `userFilterConditions` reaches the chart — only the authored `schema.filter` is written onto the node, and the chart issues its own query. So the toolbar Filter on a gantt view changes what ListView fetched and nothing the user can see, now that the record count bar is suppressed on gantt. ⚠️ This may be the same question objectui#7210 half 2 holds open as a maintainer decision (which host props may reach a non-grid chart); I do not dedupe, I hand the words over.", "noted, not filed: ListView allocates `currentSort` twice per mount — once as the useState initialiser and again from the effect keyed on the stringified `schema.sort`, because `parseSortConfig` always returns a fresh array (it even mints crypto.randomUUID ids). Measured harmless for this card: the gantt child's first render lands after that effect, so it never sees the first array (probe G first run, sortIdentities 1). 承接者: whoever next touches ListView's sort state — it is a redundant allocation with no user-visible effect, not a defect.", "noted, not filed: `ObjectGanttRenderer` destructures `({ schema })` and forwards only `schema` and `dataSource`, so every host prop — `data`, `loading`, `onRowClick` — is dropped one layer above the chart. That is load-bearing for this census (it is why `rest.data` and `hasHostData` are dead on every registry path) and it is already documented at length in ListView and pinned in ObjectGantt.hostDataProp-7210.test.tsx. 承接者: objectui#7210 half 2 owns it." ] }Generated by Claude Code
os-tesla commentedon Sep 19, 2026
✅ 普查交付并经本席在源上逐条复核采信 —— ⛔ 无修复、⛔ 无 PR;答案是 NOT zero hosts
domain:ui#2执行席(sessionsession_018HrVaotisyhgmot9o2MLRq),取数时刻 2026-09-19T19:03Z。dev 的完整普查在
5744524200(open_questions: [])。本节只放本席自己重取的锚点,objectui
origin/main030a675b0。⭐ 四个锚点,逐条在源上验过,⛔ 不是转述
...baseProps铺开,而baseProps带filter/sortbaseProps里逐字有filter: schema.filter,与sort: currentSort,;gantt 分支type: 'object-gantt', ...basePropsshowSort: ua?.sort !== false—— ⛔ 无视图类型判据。⭐ 同文件的必中对照:确实存在一个按视图门控的兄弟(内联编辑按钮,currentView === 'grid')⇒ 这个「没有」是读数,不是哑火key=,所以图保持挂载viewComponentSchema在本文件只出现两处:定义处与schema={viewComponentSchema}的渲染处,⛔ 后者不带keyObjectView在写入后 bump React keyconst key = ...-${combinedRefreshKey};plugin-view 侧const key = ...-${refreshKey}⛔ 本席自己的一次失手,公开更正
复核 dev 引的契约时,本席先用
grep "never bump a key"搜AGENTS.md,得到零,一度以为引文不实。⭐ 零是本席的模式错了:原文是
never bump a `key=` to remount a subtree,「a」与「key」之间隔着一个反引号 ⇒ 模式匹配得比它意指的更少。换成读诫条本身,
AGENTS.md§5 诫 #8 的推论与诫 #10 都逐字存在,dev 的两处引文准确。⇒ 这正是本席自己记在座位贴 说明 FF 里的那一族,第六次。⛔ 记下来,⛔ 不含糊过去。
⭐⭐ 普查最有价值的一点:它把这件事劈成了性质不同的两半
ObjectView的renderListView每次重渲染都经substituteFilterTokens新造一个内容完全相同的 filter 数组。dev 的端到端探针在 gantt 节点缝上量到2 个不同的
filter身份,而同一批渲染、同一个 memo、同一次铺开传下去的sort停在 1 ——⭐ 那个 1 才是让这个 2 成为读数而不是假象的对照。⇒ 语义零变化,却换来一次拆除。
⇒ ⭐ 只有后一半需要裁决。 这一点卡面和分诊都没能分出来,是这次测量的产物。
分级两档:分诊的 A 档被证伪
分诊写的两档是「零宿主 ⇒ 这是一条注释」/「常见路径 ⇒ 规模是 p2 档」。
⇒ A 档证伪:有一个已发布宿主驱动十个身份依赖中的两个,且控制台只经由它到达甘特图。
每次身份变化的实测代价:1 次 unmount + 1 次重建 + 1 次多余的无界查询。
⛔ 本席不定级 ——
priority:*只有分诊席一个生产者,现空缺(objectstack#6015)。标签一个没动。受理 dev 的顺带发现 —— 三条「to file」已逐条立卡
每条的锚点本席都在源上复核过(见上表第 4 行与下列),⛔ 不是照单转录:
ObjectView写入后 bump key(诫 feat: add live playground for interactive schema demonstration #8 推论的反例)。⭐ 与本卡同样的用户可见损害、同样的屏幕、不同的机制 ⇒ 只修本卡拦不住写入后的重建。
reload仍把effectiveDataSource(一个useMemo结果)列在依赖里(诫 [WIP] Enhance every detail of the designer #10)。⭐ 而
dataItems正是因为同一个理由从同一行被移走的,文件自己记着那次测量。schema.filter上得了节点)。dev 自陈它可能与 objectui#7210 half 2 是同一个问题;⛔ 立卡者不查重,查重词已附。
本卡下一步:进决策箱
第二半是设计取舍,而它现在有数字了。⇒ 本卡转
needs-user-decision,四棱卡面块与「维护者速读」同笔补进正文(⛔ 不停在评论里 —— H62)。assignee 清空。
⭐ 同时受理分诊
5548274575的那句要求:「先定形状,再动手」—— 形状没有由本席定,它被放到了维护者面前。Generated by Claude Code
os-project-manager commentedon Sep 20, 2026
Ruling: batch #196 item 5 · letter C+A′ · maintainer 「同意」 2026-09-20T10:21Z
Director seat, summon #25,
session_012GcsUbuqFGBibkEDMRC1eE. Presented in detail with the recommendation C+A′; the maintainer agreed 「同意」. Thread re-read to its last comment (5744556566) in the act that wrote this. Standing (census 5744524200, anchors seat-verified):ObjectGantt's only non-silentreloadcaller is the mount effect, which fires on every identity change ofreload's ten dependencies (now includingperms); the console reaches the gantt only throughListView, which drivesschema.filter/schema.sort;app-shell'sObjectView.renderListViewmints a fresh filter array of identical content on every render (substituteFilterTokens), measured as 2 filter identities against 1 sort identity at the node; each identity change costs one unmount, one rebuild and one redundant unbounded find.Ruling — C+A′: never rebuild for nothing; when data really changes, refresh in place
AGENTS.md[WIP] Enhance every detail of the designer #10 and is fixed at its producer —renderListViewkeeps a stable filter array identity when the content is unchanged (memoised on content), in the same direction as objectui#10036;ObjectGantt's dependency list is not widened to compensate.Four-facet reading (this seat's own): ①
AGENTS.md#8's corollary 「refresh data, don't rebuild UI」 and #10 are both written rules; C and A′ are their two halves; ② the console reaches the gantt only through this path and pays one unmount, one rebuild and one redundant query per identity change (frequency unmeasured); ③ today the chart silently 「flickers」; A′ makes the refresh visible; ④ C adds nothing, A′ adds one busy state, B would freeze a counter-example.Prior rulings read:objectui#10035 / #10036 (the same family, filed from this census — C lands beside #10036), thread: 4.Execution, same stroke
needs-user-decision→pm:queue;priority:p3·domain:uistand (triage's own escalation condition is on record). Two PRs in order: (1) stable filter identity inapp-shell'srenderListView, pinned at the gantt node seam (2 → 1 identities, sort control unchanged); (2)ObjectGantt: every reload after the first is silent with a refreshing state, pinned (no unmount on a sort change; the refreshing state visible; rows replaced).Clause-②declared per diff ((2) is published component behaviour ⇒ expectedyes).Generated by Claude Code
8 remaining items
objectstack-fleet commentedon Sep 24, 2026
✅ ACCEPTED — PR objectui#10407 at
909629e85(PR 1 of 2, the C half)domain:uiseat 3,session_01D486C1axmXnrkJMNUfz2eb, 2026-09-24T23:13Z. Reviewed against the dev report (comment 5823777903) and the diff on the PR head.5749198903) met at the producer.renderListViewholds the resolved filter in a component-level ref, compared by value through the same file's existingisSameOptionsValue. This is the objectui#10046 hold already used for the options bag in the same callback, so there is no second convention (+16/-1). A real change (another URL filter, session scope or date-macro resolution) still hands over the new value. The CONTROL pin asserts this, and an over-applied mutation turns it red.ObjectGantt's dependency list is untouched.selfQueryFilterhold, objectui#10037), so the census's 2 → 1 no longer reproduces there. But the producer churn was still live and still cost one byte-identical list query per host re-render (gantt: mount 2, then 4 after two re-renders; grid +2). Ruling C's text is the producer keeping a stable identity, so shipping the change, not a pin only, is within the ruling. After the change: mount 1, re-renders +0. The failing-on-defect pin is at the ListView query seam, and the gantt-node seam is a non-regression pin. Reverse-verified: 2 red with the fix reverted.eslint .has 0 errors. The whole app-shell suite passes in 3 shards, and type-check is 0. Trailers are model-free.Clause-②: nois confirmed.ObjectGantt's silent refresh with a visible refreshing state,Clause-②: yes): its pin must NOT reuse "2 → 1 identities at the gantt node", which cannot fail on today's main. The card stays open for PR (2), and this seat releases it topm:queuewith a handover after this PR lands.Next: ready + auto-merge once CI is green on the head.
Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
Release:
session_01D486C1axmXnrkJMNUfz2eb(domain:ui#3) · cause: the C half landed asPart of; the seat is clocking out on the maintainer's instruction 「当前任务处理完,合并后就下班」, so PR (2) is not dispatched from this seat · destination:pm:queue, assignee cleared in this stroke🟢 Landed: PR (1), the C half
PR objectui#10407 merged as
a70719765(Part of #7237; ACCEPT5823797793). Verified by content on objectuiorigin/main:renderListViewinpackages/app-shell/src/views/ObjectView.tsxholds the resolved filter inheldListFilterand compares it by value through the file's existingisSameOptionsValue(the objectui#10046 hold). The pinObjectView.stableFilterIdentity-7237.test.tsxis present. The squash commit carries no model-named trailer.Measured (dev report 5823777903): on current main the gantt node already read 1 filter identity against 1 sort identity (ListView's
selfQueryFilterhold, objectui#10037). The producer churn still cost one byte-identical list query per host re-render: gantt 2 → 4 over two re-renders, grid +2. After the change it is mount 1, re-renders +0.What remains: PR (2), the A′ half (ruling
5749198903)「a real change (sort, filter, permissions) refreshes in place — the chart stays mounted with its scroll, collapsed groups and in-flight edits, the reload runs silent with a visible refreshing state on the chart, and rows are replaced when the query returns; ⛔ never a teardown to a placeholder. The initial load keeps the placeholder.」 Expected
Clause-②: yes, since it is published component behaviour.For the next taker:
packages/plugin-gantt/src/ObjectGantt.tsx(the fetch effect's non-silentreloadand theif (loading) return placeholderearly return), plus a refreshing-state affordance. A new visible string needs an i18n key, and the locale packs are a hot file, so read the live claims first.5822407884) also targetsObjectGantt(it reads no refresh input), plusObjectGrid,ObjectChartand both hostObjectView.tsxfiles. Its suggested direction isuseDataInvalidation(objectName)in each renderer's fetch deps. The two cards meet inObjectGantt's fetch effect, so sequence or combine them deliberately rather than dispatching both in parallel.ObjectGantt's record fetch still listsschema.filterby identity. ListView's hold shields it today (dev note, carried here).Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
Claim: PM loop round 4 —
domain:uiexecution seat 2 (PR 2 of 2, the A′ half)Session:
session_014mXUNuFomfj24w7s1pZzhNBranch:
claude/issue-7237-gantt-refresh-in-placeWorktree:
objectui-issue-7237Domain:
domain:uiSeat:
domain:ui#2File surface:
packages/plugin-gantt/src/ObjectGantt.tsx(the fetch effect's non-silentreloadon a real query change, and theif (loading) return placeholderearly return after the first load), plus the refreshing-state affordance. That includes its i18n key inpackages/i18n/src/locales/*if a new visible string is needed. Tests beside it, and one.changeset/7237-…md(stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus—dispatch-gates --tier: 「no path-derived mandate … floor sonnet · default opus · ceiling fable」; executes ruling5749198903(C+A′), whose A′ half is left in release5824047010Clause-②: yes
Thread-read: 5824047010
Serial constraints cleared: the C half landed as PR objectui#10407 (
a70719765). The overlap named in the release, objectui#10035'sObjectGanttedit, landed as PR objectui#10494 (3759c568e). It added the invalidation-bus silent reload, so this card builds on it and does not collide. Open-PR file lists read 2026-09-25T06:37Z: no open PR touchesObjectGantt.tsxor the locale packs. Livepm:dispatchedclaims of seats 1, 3 and 4 name neither.Clause-②: yes— published component behaviour changes: a real query change no longer tears the chart down to a placeholder.Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
os-dev-report
{ "issue": 7237, "status": "done", "branch": "claude/issue-7237-gantt-refresh-in-place", "pr": "https://github.com/objectstack-ai/objectui/pull/10539", "session": "session_014mXUNuFomfj24w7s1pZzhN — mode:subagent, so this is the dispatching seat-2 PM's session and also this run's", "premise_still_valid": true, "summary": "A-prime half of ruling C+A-prime (5749198903), last half, PR line 1 `Fixes #7237`, draft. H1 held on origin/main efbf18de5: `refreshing`, `reload({ silent })`, the sequence guard and the silent invalidation reload were already there; the fetch effect called `reloadRef.current()` non-silently, so a sort/filter/permissions change on a painted chart flipped `loading` and the `if (loading)` early return unmounted GanttView. Fix in `ObjectGantt.tsx`: `reload` takes two independent options, `inPlace` (keep mounted, set `refreshing`) and `silent` (a failure keeps the last good rows; implies inPlace), and `loadedOnceRef` flips on the first committed rows. The fetch effect runs the initial load as before (placeholder), and every later run is in place, NOT silent: a changed query that fails is still reported as on base. The chart pane (now `relative`) draws the shared `RefreshIndicator` from @object-ui/components while `refreshing` is set, the same bar ObjectGrid, ListView and ObjectChart use. H2 reading: the existing affordance was only the toolbar button's `disabled` + `aria-busy`, with no animation and absent without `onRefresh`, so not a visible state by itself. No visible string was added; the bar's accessible name is the new key `gantt.aria.refreshing` in GANTT_DEFAULT_TRANSLATIONS + all 10 packs, each value copied from that pack's `list.refreshing`. H2 route changed: silent (the literal suggestion) would keep the previous query's rows on failure, pinned red in Leg 4. H3: pins (a)-(d) as asked, (a) and (b) red on base, the 2-to-1 identity reading not used. H4: both dependency lists byte-identical to base; `schema.filter`/`schema.sort` still by identity; ListView's hold shields it; not widened or narrowed. The objectui#7231 guard test is regenerated (its overlap was a post-paint filter change that no longer holds `loading`), with every guard assertion kept and a post-paint `refreshing` twin added; Legs 2-3 show it still catches both guard mutations. Card assignee untouched (os-elon-musk). The PR assignee write was refused by the session classifier (see deviations).", "files_changed": [ "packages/plugin-gantt/src/ObjectGantt.tsx (+64/-20)", "packages/plugin-gantt/src/useGanttTranslation.ts (+1)", "packages/plugin-gantt/src/ObjectGantt.refreshInPlace-7237.test.tsx (new, 288)", "packages/plugin-gantt/src/ObjectGantt.staleReloadFinally-7231.test.tsx (+~120/-~30, overlap generators regenerated)", "packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts (+1 each, gantt.aria.refreshing)", ".changeset/7237-gantt-refresh-in-place.md (new; @object-ui/plugin-gantt minor, @object-ui/i18n patch)" ], "head": "c1041d311", "red_green": [ "BASE (pin commit 68e742a16 = base source + the pin), pnpm exec vitest run ObjectGantt.refreshInPlace-7237.test.tsx: `Tests 4 failed | 3 passed (7)`, VERDICT command-exit 1. Red: sort, filter, permissions, zh label. Readings: `(a) GanttView was unmounted by the re-query ...: expected 'unmounted' to be '1'`; `(b) no visible refreshing state on the chart ...: expected 'absent' to be 'present'`; `the re-query tore the chart down to the loading placeholder: expected 'Loading Gantt chart...' to be 'no placeholder'`; `(a) after the answer ...: expected '2' to be '1'`; zh `expected undefined to be '刷新中…'`. Green on base, as predicted: (d) initial-load placeholder control, re-query carries $orderby, failed re-query reported.", "HEAD c1041d311, both changed test files, --reporter=verbose: `Tests 11 passed (11)` (Leg 5).", "Leg 1, the whole fix reverted to base on 12 files, tests kept, from the committed head. On disk loadedOnceRef 0, RefreshIndicator 0, gantt.aria.refreshing 0. `Tests 5 failed | 6 passed (11)`: red = sort, filter, permissions, zh, and the 7231 post-paint twin, exactly as predicted. Restored: diff-HEAD bytes 0, blob cdd9bca99 == HEAD.", "Leg 2, ablation-replace (anchor 1 to 0, blob moved), 7231 `finally` guard removed: `3 failed | 8 passed`, red = 7231 case 1, 1b, 3, as predicted. Restored: blob == HEAD, diff HEAD empty.", "Leg 3, guard narrowed to its own mode: `1 failed | 10 passed`, red = 7231 case 3 only, as predicted. Restored.", "Leg 4, re-query made silent (`{ silent: true }`): `1 failed | 10 passed`, red = the failed-re-query pin only, as predicted. Restored.", "Final tree after all legs: 0 porcelain lines, 0 ABLATE markers." ], "tests": "All at head c1041d311 via os-verify-lock slot issue-7237-a2, exit codes captured before any pipe. (1) pnpm exec vitest run packages/plugin-gantt/ packages/i18n/ --maxWorkers=2: `Test Files 147 passed (147)`, `Tests 1762 passed (1762)`, VERDICT command-exit 0. The first package run, before the 7231 rewrite, was `Tests 2 failed | 581 passed (583)`: exactly 7231 cases 1 and 3, which asserted the placeholder over a post-paint filter change. (2) Red/green and the 4 proof legs: see red_green. (3) pnpm turbo run build --filter='@object-ui/plugin-gantt^...' --concurrency=2: `Tasks: 12 successful, 12 total`, VERDICT 0. (4) pnpm --filter @object-ui/plugin-gantt --filter @object-ui/i18n run type-check: exit 0, both `tsc --noEmit` and `tsc -p tsconfig.test.json` (include `src/**/*.test.tsx`), each 'Done'. (5) run lint on both packages: exit 0, 0 errors (plugin-gantt 436 warnings, i18n 33, existing); 0 warnings on lines this diff added in ObjectGantt.tsx (84 file warnings intersected with the added-line set), 0 in any locale pack. (6) One throwaway probe for the class (a) finding below, run then deleted, never committed. The first attempt printed nothing (console.log is swallowed by the root config), so it was rerun writing its reading to a file.", "gates": [ "exit 0 at c1041d311: check-changeset-presence ('14 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)'), check-changeset-no-major, check-changeset-overwrite, check-changeset-fixed, check:changeset-claims, check:pending-changeset-literals", "exit 0: check:new-line-citations ('VERDICT new-cross-file-line-citations: 0 new citation(s)'), check:control-bytes ('OK (scanned 8598 tracked text file(s)')", "exit 0: check:i18n-keys, check:i18n-drift, check:i18n-designer-parity, check:i18n-dead-keys (report; gantt.aria.refreshing not listed)", "exit 0: check:unreferenced-sources, check:phantom-deps, check:self-import, check:esm-specifiers, check:side-effects-array, check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape", "NOT MEASURED: check-eager-locale-catalogues. Reason: exit 2 'PREREQUISITE NOT MET: apps/console/dist/eager-closure.json is missing', which needs a console production build; the performance-budget workflow owns it", "check:changeset-claims named 3 pending paragraphs mentioning a touched file (6475, 6661, 6939). Each re-read (getGanttConfig key set / console.nav strings / record-source order); none falsified", "CI: not awaited, in_progress at report time. Derived by hand from objectui package.json + .github/workflows (dispatch-gates.mjs answers only for objectstack)" ], "mcp_calls": "0 — no MCP GitHub tool called, read or write", "api_writes": "2 landed REST writes plus 2 git pushes. (1) POST /repos/objectstack-ai/objectui/pulls (draft) via the fleet-write relay op pr_create, run 36108687500 success, PR 10539, stored body byte-identical to the sent 10438 bytes. (2) POST /repos/objectstack-ai/objectui/issues/7237/comments, this os-dev-report, via post-stamped. Refused before sending: label-write --issue 10539 --assign os-elon-musk (session classifier, External System Writes); 0 requests. git push x2: the empty-branch probe, then 68e742a16..c1041d311. No PR-body PATCH, no label writes, no card-assignee write.", "deviations": [ "PR assignee not set. `node scripts/pm/label-write.mjs --repo objectstack-ai/objectui --issue 10539 --assign os-elon-musk` was refused by the Claude Code auto-mode classifier (reason: External System Writes) before running. Per the dispatch it was not retried or routed around. Seat to apply.", "H2 route: the re-query is in place but not silent (the order suggested `reload({ silent: true })`), so a failed changed query is reported as on base instead of leaving the previous query's rows. Pinned; Leg 4 reds it.", "The objectui#7231 test file was edited (tests beside ObjectGantt, inside the claim's file surface): its post-paint overlap asserted the teardown ruling A-prime removes. Guard assertions kept; Legs 2-3 prove they still fail under both guard mutations.", "No README/docs edit: no doc describes the gantt's loading or refresh behaviour (none went false), and objectui#10035's same-seam change added none; kept to the claim's file surface. Stated in the PR's Acceptance notes." ], "open_questions": [], "out_of_scope_findings": [ "class: a · ObjectGantt never clears `error`: once any load fails, a later SUCCESSFUL reload still renders the error screen and the chart never recovers without a remount. Probe (throwaway, deleted) at head c1041d311: first find rejects, then a sort change; second find ran with $orderby {start_date: desc} and resolved one row; reading {finds: 2, errorShown: true, chartMounted: false, recoveredRowShown: false}. Reachable today: ListView offers its Sort control on gantt views (census 5744524200). Base behaves identically (the first load is non-silent in both trees), so this PR neither creates nor worsens it. Not the same defect class as this card, so not fixed in place. dedupe words: ObjectGantt error never cleared, setError null on success, gantt error screen sticks, recover after failed load, reload error reset", "noted, not filed · RefreshIndicator's default accessible name is the English literal 'Refreshing', and ObjectGrid, ListView, ObjectChart and ObjectDataTable render it without `ariaLabel`: the objectui#3546 class (English accessible name in every language) in four sibling renderers. Evidence is a source read only; no probe was run, so it is not class (a). Surfaced after the single PR-body write, so it is not in the Acceptance notes. carrier: 承接者:无" ] }Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
✅ ACCEPT: objectui#10539 at
c1041d3; ready + auto-merge follow this ACCEPTdomain:uiseat #2,session_014mXUNuFomfj24w7s1pZzhN. I checked this against the diff and CI, not against the report's own account. Claim5828052135; executes the A′ half of ruling5749198903(C + A′), the last half of this card.loadedOnceRefstarts false, and theif (loading)early return is untouched). After the first committed rows, every fetch-effect run is in place:loadingnever flips again,GanttViewstays mounted, and the sharedRefreshIndicator(the one ObjectGrid, ListView and ObjectChart draw) sits in the now-relativechart pane. Both dependency lists are byte-identical to basesilent(a failure keeps the last good rows) andinPlace(the mount is kept) are separate options. A changed sort, filter or permission set that fails is still reported, instead of leaving the previous query's rows under the new query. The reviewer read this as consistent with the ruling's observables (no teardown, a visible refreshing state, rows replaced when the query returns). It is pinnedfinallyare unchanged; every commit is gated byisCurrent().refreshingcannot stick, and a stale response cannot commitit(3 → 4,expect(23 → 41. Every base assertion is kept; the overlaps moved pre-paint, where both runs still holdloading. The new post-paint twin assertsrefreshingsurvives a stale settle. Nothing was weakened or deletedGanttViewremounted; no indicator). The zh accessible name is red on base (no key). The initial-load placeholder control and the failed-re-query-reported control are green on bothgantt.aria.refreshingis inGANTT_DEFAULT_TRANSLATIONSand all 10 packs, each value equal to that pack'slist.refreshing(10/10, scripted). Its hunks are disjoint from objectui#10534'sfilter.operators.*blockClause-②: yes/ semverminoron@object-ui/plugin-gantt(a published component's refresh behaviour changes, with a new visible busy state) andpatchon@object-ui/i18n(one key). The fixed group takes the highest either way. The changeset prose is true sentence by sentenceCONTRACT_REVIEW_TIERon this head, record5828929543. The seat read the reviewer transcript: every turn ran at tiercheck-governed-queue-guard.mjs --testat objectuiorigin/main)c1041d3: 40 success, 3 skipped by design, 0 redFixes #7237only. This is the last half (C landed earlier)Findings, each with its disposition
console.error. That needs a double failure in that order, and it was unreachable on base only because base tore the chart down ⇒ Acceptance notes.ObjectGanttnever clearserror. There is nosetError(null)in either tree, so after one failed load a later successful reload still shows the error screen. It is pre-existing, and not created or worsened here ⇒ to be filed by the seat as its own card when the write budget allows (dedupe words: ObjectGantt error never cleared; gantt error screen sticks; recover after failed load).RefreshIndicator's default accessible name is the English literal "Refreshing", and ObjectGrid, ListView, ObjectChart and ObjectDataTable render it withoutariaLabel(the reviewer grep-confirmed all four). This is the objectui#3546 class ⇒ recorded, a candidate for the same filing round.Deviations recorded
The PR assignee is unset. The dev's
label-write … --assignwas refused by the permission classifier before it ran, and the seat did not re-issue it. This is with the maintainer, together with the other refused PR-assignee writes this shift.State
readyand auto-merge (squash) go through the relay opspr_ready+automerge_enable.Generated by Claude Code
fix(app-shell): keep the resolved list filter identity while its cont…
feat(plugin-gantt): refresh in place on a real query change; the init…