You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
[gantt] Illegal dependency link (cycle/self/locked/group) is silently discarded — no toast, no cursor, no reason shown #4158
In a type:'gantt' view, when a user drags a dependency link that would be illegal (would form a cycle, e.g. A→B→C then C→A; or self-link / onto a locked / group row), the link is correctly not created — but there is zero user feedback: no dialog, no toast, no not-allowed cursor, no red target outline, and the drag hint shows only the source end, never → target. The user cannot tell why the link won't connect. The same drag onto a legal target succeeds first try, so it isn't an interaction-technique problem.
Why this is a platform (not app) issue
The gantt view is the platform-native type:'gantt' renderer; the app only supplies read/write endpoints.
The app already implements a server-side cycle guard that throws a business message with status: 400, and the write endpoint surfaces it as 422 { message }. So a human-readable rejection reason exists server-side.
The gantt renderer has a write-failure toast channel (locale key gantt.writeFailed = "保存失败,已恢复修改前状态" is present in the bundle).
Yet for illegal-link rejection, the client appears to short-circuit before issuing the PATCH, so neither the server message nor any toast is ever shown. The app cannot intervene: there is no exposed "rejection reason" / onBeforeDependencyCreate output path on the public gantt component.
Impact
Users repeatedly try to draw a legitimate-looking dependency and get silent non-response, with no way to learn it's a cycle/locked/group constraint. Severity: medium (usability / dead-end interaction on an otherwise-correct guard).
Fix direction (any one; first two preferred)
On an illegal drop target, show a negative affordance (cursor:not-allowed + target-row outline) during hover, and a explanatory toast on drop (e.g. new keys gantt.link.rejected.cycle / .locked / .group).
Expose a rejection path (e.g. surface onBeforeDependencyCreate's reason) so the host can supply a message the gantt renders.
At minimum, stop rejecting silently — today there isn't even a console warning.
Environment
@objectstack/console@17.0.0-rc.6 (also present in rc.5).
Steps to reproduce
type:'gantt' view with a dependenciesField; create tasks A, B, C.
Connect A→B, then B→C.
Drag from C's link handle onto A (would close the cycle).
Expected: a message/affordance explaining the link is rejected (cycle). Actual: nothing happens — no dialog, no toast, no cursor/outline change; the drag hint never shows the target end.
Triage: pm:queue — a user-hit dead-end interaction on the platform-native gantt renderer, with three concrete fix directions; dispatchable.
Why queue, not finding: the guard itself is correct, but users drawing a legitimate-looking dependency get zero feedback (no toast, no cursor, no console warning) and cannot learn the constraint — a today-hit usability defect on a shipped surface, not dormant drift. The client short-circuits before the PATCH, so the server's human-readable 422 reason (which the app already produces) never surfaces, and no public output path (onBeforeDependencyCreate reason) exists for the host to intervene.
Anchors on origin/main @ 521a37b: packages/plugin-gantt/ is the landing package; the write-failure toast channel the body cites exists (gantt.writeFailed in the locale packs), confirming the renderer has a feedback channel that the illegal-link path simply never reaches.
CLAIM (objectui whole-repo seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3) — dispatched as the second half of the gantt pack with #4157 (same dev agent, same worktree objectui-gantt-pack; branch claude/issue-4158-gantt-link-feedback, stacked on #4157's branch if locale files overlap).
Delegated scope ruling (veto window open): direction 1 of the card's three — negative affordance during hover (not-allowed cursor + target outline) plus an explanatory per-reason toast on drop (new gantt.link.rejected.* keys through the full locale-pack discipline). Direction 2 (public rejection-reason API such as onBeforeDependencyCreate output) is an API-surface addition that deserves its own contract-shaped card if wanted — not a rider here; direction 3 is subsumed by 1.
ACCEPT (objectui seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3, review of record) — PR #4210, part 2 of the gantt pack, implementing the recorded direction-1 ruling exactly.
The structural core is right: canReceiveLink's four-branch boolean became classifyLinkTarget returning WHICH branch refused, with the boolean derived from it — hover affordance and drop toast are two consumers of one verdict, so no second classifier exists to drift (the constraint the dispatch demanded). The inline-style choice carries its measured justification (the cursor-not-allowed/ring-alpha utilities are not emitted in the prebuilt components CSS — a class would pass a DOM test and render nothing in a browser). Host veto via onBeforeDependencyCreate stays silent BY DESIGN and is pinned as a control — that reason belongs to the host, i.e. direction 2's territory, correctly not smuggled in. Reverse verification predicted 7 red / 3 green and landed exactly, with the three greens being the intended controls. New gantt.link.rejected.* keys through the full ten-pack discipline (4 keys, all nine packs following, zero unexplained en drift).
Merge order note (PM-held):#4210 is STACKED on #4157's branch, so it has no CI by design (full local ladder quoted in its body). It stays as-is until PR #4205 lands; the seat will then retarget its base to main, verify mergeability, and flip ready + auto-merge. Finding #4209 (the group branch is unreachable today — group rows render no bar — plus the adjacent fact that the context-menu dependency picker creates links without consulting this policy at all) is graded: held as a finding for now; the picker-bypass half is real inconsistency but not silent corruption (the server guard still rejects), and it will ride the next gantt-area dispatch.
Activity
os-zhuang commentedon Aug 10, 2026
Triage:
pm:queue— a user-hit dead-end interaction on the platform-native gantt renderer, with three concrete fix directions; dispatchable.onBeforeDependencyCreatereason) exists for the host to intervene.origin/main@521a37b:packages/plugin-gantt/is the landing package; the write-failure toast channel the body cites exists (gantt.writeFailedin the locale packs), confirming the renderer has a feedback channel that the illegal-link path simply never reaches.convertSortToQueryParams(三份逐字副本),已有@object-ui/core的共享 sink 可迁移;副本会丢掉不带 order 的 sort 项 #4022 is code triplication).本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
yinlianghui commentedon Aug 10, 2026
CLAIM (objectui whole-repo seat PM, session
session_017Qqyix2QcnpUC9XeYVDzx3) — dispatched as the second half of the gantt pack with #4157 (same dev agent, same worktreeobjectui-gantt-pack; branchclaude/issue-4158-gantt-link-feedback, stacked on #4157's branch if locale files overlap).Delegated scope ruling (veto window open): direction 1 of the card's three — negative affordance during hover (not-allowed cursor + target outline) plus an explanatory per-reason toast on drop (new
gantt.link.rejected.*keys through the full locale-pack discipline). Direction 2 (public rejection-reason API such asonBeforeDependencyCreateoutput) is an API-surface addition that deserves its own contract-shaped card if wanted — not a rider here; direction 3 is subsumed by 1.Generated by Claude Code
yinlianghui commentedon Aug 10, 2026
ACCEPT (objectui seat PM, session
session_017Qqyix2QcnpUC9XeYVDzx3, review of record) — PR #4210, part 2 of the gantt pack, implementing the recorded direction-1 ruling exactly.The structural core is right:
canReceiveLink's four-branch boolean becameclassifyLinkTargetreturning WHICH branch refused, with the boolean derived from it — hover affordance and drop toast are two consumers of one verdict, so no second classifier exists to drift (the constraint the dispatch demanded). The inline-style choice carries its measured justification (thecursor-not-allowed/ring-alpha utilities are not emitted in the prebuilt components CSS — a class would pass a DOM test and render nothing in a browser). Host veto viaonBeforeDependencyCreatestays silent BY DESIGN and is pinned as a control — that reason belongs to the host, i.e. direction 2's territory, correctly not smuggled in. Reverse verification predicted 7 red / 3 green and landed exactly, with the three greens being the intended controls. Newgantt.link.rejected.*keys through the full ten-pack discipline (4 keys, all nine packs following, zero unexplained en drift).Merge order note (PM-held): #4210 is STACKED on #4157's branch, so it has no CI by design (full local ladder quoted in its body). It stays as-is until PR #4205 lands; the seat will then retarget its base to
main, verify mergeability, and flip ready + auto-merge. Finding #4209 (thegroupbranch is unreachable today — group rows render no bar — plus the adjacent fact that the context-menu dependency picker creates links without consulting this policy at all) is graded: held as a finding for now; the picker-bypass half is real inconsistency but not silent corruption (the server guard still rejects), and it will ride the next gantt-area dispatch.Generated by Claude Code
fix(gantt): an illegal dependency link says why it was refused (objec…