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.

[gantt] Illegal dependency link (cycle/self/locked/group) is silently discarded — no toast, no cursor, no reason shown #4158

Closed

Description

@baozhoutao

Summary

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)

  1. 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).
  2. Expose a rejection path (e.g. surface onBeforeDependencyCreate's reason) so the host can supply a message the gantt renders.
  3. 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

  1. type:'gantt' view with a dependenciesField; create tasks A, B, C.
  2. Connect A→B, then B→C.
  3. 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.

Observed while building an ObjectStack 17 app.

Activity

  1. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    Contributor

    Triage: pm:queue — a user-hit dead-end interaction on the platform-native gantt renderer, with three concrete fix directions; dispatchable.

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


    Generated by Claude Code

  2. self-assigned this
    on Aug 10, 2026
  3. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    Contributor

    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.


    Generated by Claude Code

  4. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    Contributor

    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.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions