Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

[finding] The gantt link guard's group branch is unreachable: a type:'group' row renders no bar, so the drag can never target it #4209

Closed as not planned

Description

@yinlianghui

Measured while implementing objectui#4158 (PR for the link-rejection feedback) — not in that card's scope, which is user feedback for the guard's verdicts, not an audit of the verdicts themselves. Observation-class: nothing a user hits today, and the guard is not wrong — the branch simply cannot fire.

The branch

packages/plugin-gantt/src/GanttView.tsx, the built-in drop-target policy, refuses four kinds of dependency link. One of them:

if (target.type === 'group') return 'group';

Why it cannot fire

The policy is consulted from exactly two places, both on the drag-to-link path: captureLinkTarget (a bar registering itself as the hover target) and the release handler. Both require a bar to exist for the target row.

A type: 'group' row renders none. From the row renderer:

if (task.type === 'group') {
  // 分组层级 (项目/产品): a pure tree header — the left list still
  // shows the caret + label, but the timeline row carries NO bar.
  return (
    <div … onPointerMove={clearLinkTarget}>
      {tooltip}
    </div>
  );
}

So for a group row there is no bar to hover, nothing that can call captureLinkTarget, and the row's own onPointerMove is clearLinkTarget — which actively clears any target. The branch is dead on this path.

Note the neighbouring case does not cover it: row.isSummary (a task with children, or type: 'summary') does render a bar and does register as a drop target — and the policy accepts it, because isSummary is not type === 'group'. So "you cannot link to a summary row" is not what the code does; only the explicit group type is refused, and only that type renders no bar. The type: 'group' check sits before the isSummary branch in the renderer, so a group row can never take the bar path either.

Why it is not simply dead code to delete

Two directions, and the choice is a product question rather than a cleanup:

The adjacent question worth asking

The context-menu path (Add predecessor… / Add successor… → dependency picker) creates links without consulting this policy at all — it is a separate call site that does not call canReceiveLink. Whether the picker should offer group rows as candidates, and whether it should be subject to the same four-branch policy as the drag path, is a real question this finding does not answer. That is the direction in which the group branch might actually acquire a reachable meaning.

Options

  • A. Leave it. The branch is correct-but-dormant, and [gantt] Illegal dependency link (cycle/self/locked/group) is silently discarded — no toast, no cursor, no reason shown #4158's test now pins the reason. Zero cost, keeps the insurance.
  • B. Route the context-menu picker through the same policy. Gives the branch a live meaning and closes the inconsistency between the two link-creation paths. Needs a product call on whether the picker should filter illegal candidates or reject them with the same toast.
  • C. Remove the branch and its message. Cleanest by enforce-or-remove, but it deletes the insurance and would have to be re-added the moment either of the above changes.

Recommendation: A for now, B as its own card if the picker/drag inconsistency is judged worth closing — B is the only one that turns the branch from dormant into meaningful, but it is a behaviour change to a second interaction and deserves its own scope rather than riding a feedback card.

Related: objectui#4158 (the feedback card that measured this).


Generated by Claude Code

Activity

  1. claude commented on Aug 11, 2026

    @claude
    Contributor

    Findings triage (first grade; premise check @ origin/main e1ade8f): HOLD — finding stands.

    Anchors verified live in packages/plugin-gantt/src/GanttView.tsx: the refusal branch at :1378 (if (target.type === 'group') return 'group';) and the row renderer's group short-circuit at :3989, which returns a bar-less row — so the unreachability argument reads off the source, not off inference. The neighbouring :2983 and :4921 group checks are separate paths and do not change it.

    Held rather than closed as dead code, which is the disposition the card argues for and it is the right one: the branch is correct-but-dormant, #4158 now pins the reachability fact so the pin goes red the day group rows gain a bar, and its user-visible message is deliberately retained so the branch cannot render a raw i18n key if it ever fires. Option C (delete it) would trade that insurance for nothing measurable.

    The useful half is the adjacent question, and it is what a future round should grade, not this branch: the context-menu picker (Add predecessor… / Add successor…) creates links without consulting canReceiveLink at all. That inconsistency between the two link-creation paths is where the group branch could acquire a live meaning — and it is a behaviour change to a second interaction, so it deserves its own card (the card's option B) rather than riding this one or #4158. Recorded here so the next grader does not re-derive it.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Findings triage (cadence re-verify @ origin/main 5e2e9fa): HOLD as finding. The group vocabulary is unchanged: GanttLinkRejection still enumerates 'group' (GanttView.tsx:122), GanttTaskType still carries 'group' (:136), and the docs still say group rows render as pure tree headers with no bar (:159) and synthetic group rows are never draggable (:433) — so the guard's group branch remains unreachable by drag exactly as filed. Dormant dead-branch class; promote only if a gantt sweep touches the link-guard region.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  3. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Findings triage (cadence re-check): HOLD as finding — premise intact.

    • Re-verified on origin/main @ b954120: the refusal branch (GanttView.tsx:1378) and the no-bar group renderer (:3989) are both live; the drag path still cannot reach the branch. The [gantt] Illegal dependency link (cycle/self/locked/group) is silently discarded — no toast, no cursor, no reason shown #4158 reachability pin remains the tripwire.
    • One new observation for the card's "adjacent question": :4921 now shows a candidate filter if (c.type === 'group' || c.locked) return false — if that is the dependency-picker path, the picker already FILTERS group rows rather than consulting canReceiveLink, which narrows option B (the inconsistency may be smaller than filed — filter-vs-policy alignment rather than a missing check). Worth one look at promotion time.
    • Recommendation unchanged: A now, B as its own card if the two link-creation paths are judged worth unifying.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  4. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

    Concentrate round (state transition, no re-grade): finding → pm:on-hold, executing the three standing HOLD verdicts (2026-08-11): Option A — the branch is correct-but-dormant, #4158's reachability pin is the mechanical tripwire, and its retained message prevents a raw i18n key if the branch ever fires. Option B (route/align the context-menu picker with the link policy) remains its-own-card material — note the 08-11 re-check already narrowed it: the picker appears to FILTER group rows (GanttView.tsx candidate filter), so B may be filter-vs-policy alignment rather than a missing check.

    Restart-when: the #4158 reachability pin goes red (group rows gained a bar, or a second link path appeared). Trigger file: packages/plugin-gantt/src/GanttView.tsx (row renderer / link policy / dependency-picker regions) — dispatchers touching it read this card first.

    This comment is from the triage seat Routine — session session_01Y6RnGGr6eNirCHrU7QmFRd.


    Generated by Claude Code

  5. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Aug 23, 2026
  6. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    Contributor

    Triage: domain:ui (lane only — pm:on-hold untouched). Landing: packages/plugin-gantt — the link guard's unreachable group branch.

    Routed via the maintainer direct-dispatch channel, this session, verbatim: 「然后 批 4–5」. PM session session_0124Qg8rLvpXnQDwCmpKUmaJ. objectui three-stream split (maintainer 2026-08-21); not a Routine triage fire — the triage seat may re-grade.


    Generated by Claude Code

  7. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    关闭 not_planned —— 维护者同意:按卡片自己的结论不需要改,树里已有测试守着

    分诊席(session_01Tw7jnJinGHvoGSi8aFkhPJ,objectstack 座位贴 #6015),2026-09-24T11:10Z。objectui 清理第七批(#8587 的挂起卡一次性清点):本席读完了卡面和评论(5 条,全部读到),并在本地 main(62597c5)上重新测量,把建议报给维护者。维护者答:「objectui 第⑦批 同意」。

    本卡说的是什么

    甘特图连线规则里的 if (target.type === 'group') return 'group' 永远走不到:分组行不渲染条形,拖拽根本够不到它。卡面自己的推荐是 A:保留这段代码。它是正确但休眠的保险,配套的提示文案也保留着,万一哪天走到这里,不会在界面上显示原始 key。

    本席实测(本地 main 62597c5)

    • 分支在 packages/plugin-gantt/src/GanttView.tsx:1526。
    • 测试 GanttView.linkrejection.test.tsx:154("group: the row renders no bar, so the drag cannot reach the branch")仍在,注释里引用了 objectui#4209。
    • ⇒ 5316301859 的重启条件(这条测试变红)没有发生。

    为什么关

    按卡片自己的结论,这里没有要做的事。如果情况变化,树里的那条测试会直接变红,它才是真正的报警器。

    什么时候重开

    那条测试变红时重开(分组行有了条形,或者出现了第二条建立连线的路径)。如果有人想让右键菜单里的依赖选择和拖拽规则保持一致(卡面方向 B),另开一张卡。

    标签:摘 pm:on-hold;以 not_planned 关闭。


    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

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions