Skip to content

feat(tracker): Gantt — #7 tier-4 (mobile, tree-view, predecessor column, visual deps, shift notify)#10859

Draft
MichaelUray wants to merge 330 commits into
hcengineering:developfrom
MichaelUray:feat/gantt-upstream-pr7-tier4
Draft

feat(tracker): Gantt — #7 tier-4 (mobile, tree-view, predecessor column, visual deps, shift notify)#10859
MichaelUray wants to merge 330 commits into
hcengineering:developfrom
MichaelUray:feat/gantt-upstream-pr7-tier4

Conversation

@MichaelUray

@MichaelUray MichaelUray commented May 18, 2026

Copy link
Copy Markdown

Summary

Final tier-4 batch covering five mostly-independent features:

  1. Mobile drawer + responsive popups — Gantt becomes usable on phones / tablets; touch-action disabled on bars, drawer header offset, confirm popups adapt to viewport.
  2. Tree view — collapsible per-project tree expand-state, persisted in localStorage; chevron-icon glyphs (from PR4 polish) drive the visual.
  3. Predecessors column — new column for Tracker's List view that shows each issue's predecessors.
  4. Visual DependencyEditor2×2 mini-diagram picker for switching FS / SS / FF / SF on existing dependencies, with the DependencyKindHint* i18n family.
  5. Shift notifications — when a cascade shifts a downstream issue, the assignee receives an inbox notification; aggregated by recipient via a "cascade-shift bundle" so a single drag doesn't flood the inbox.

Also lifts the Gantt toolbar into SpaceHeader row 2 (so it lives in the same toolbar surface as the other viewlets) and adds a global Fullscreen toggle.

What's in this PR (+17 incremental commits)

Mobile / touch

  • Mobile drawer + responsive confirm popup
  • touch-action: none on bars + drawer header offset

Tree view

  • Per-project tree expand-state store backed by localStorage

Predecessors column

  • feat(tracker) Predecessors column in Tracker list view

Visual DependencyEditor

  • diagram-helpers for Visual DependencyEditor
  • Visual DependencyEditor with 2×2 mini-diagram picker
  • i18n: DependencyKindHint*, DependencyPickKind, DependencyChangeKind

Shift notifications

  • feat(models/tracker) register DependencyShiftedNotification class + notification type
  • DependencyShiftedPresenter for inbox bundle rendering
  • Cascade-shift bundle aggregator with grouping by recipient
  • i18n: DependencyShifted notification strings (en + de)
  • docs(gantt) clarify cascade-token JSDoc with v1 dependency-shift wiring

Toolbar hoist & fullscreen

  • Hoist Gantt toolbar into SpaceHeader row 2
  • Correct visual order of merged Gantt toolbar
  • Global Fullscreen toggle + Lupe-first toolbar + milestone labels in extended sidebar

Coverage

  • test cover three previously-untested Gantt lib files

Stack overview

# Branch Scope Status
PR1 …pr1-schema schema + version bump open
PR2 …pr2-readonly read-only viewlet draft
PR3a …pr3a-edit drag / resize / dnd draft
PR3b …pr3b-deps-cascade-cp dependencies + cascade + critical path draft
PR4 …pr4-polish polish draft
PR5 …pr5-tier2 undo, saved views, bulk, auto-scheduling draft
PR6 …pr6-tier3-virtualization virtualization draft
PR7 …pr7-tier4 this PR — mobile + tree + predecessor col + visual deps + notifications draft
Docs (huly-docs#70) MichaelUray:feat/gantt-section reference + screenshots open

Marked as draft to signal stack dependency on PR1 → PR6.

How to review

Fork compare: PR6...PR7 — 17 commits, 31 files

The five features touch different files and can be reviewed mostly independently. Largest single addition is the cascade-shift bundle aggregator + presenter for the notification path.

Testing

  • New tests cover three previously-untested Gantt lib files.
  • Hand-tested mobile drawer on touch device; Visual DependencyEditor on all 4 kinds.
  • Notification bundle verified: 30-issue cascade produces 1 grouped notification per recipient.
  • Validated on a production-style deployment on 2026-05-18.

DCO

All commits are Signed-off-by.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Iterates IssueRelation list, renders one GanttDependencyArrow each, plus
a live bezier preview tied to connector-drawing / connector-target-hover
drag states.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Kind dropdown (FS/SS/FF/SF), lag NumberInput (-30..+90), delete with
inline confirm, save/cancel. canEdit gates write actions; writes use
the same client.apply().commit() pattern as PR3's drag-controller.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…comments

Strips reviewer-attribution markers ('review-N <date>') from source-code
comments. Comments are kept and rephrased as 'review note' so the noted
constraint or hidden invariant survives, only the internal attribution
is dropped.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…ot :none (PR4a)

'none' is not a valid stroke-dasharray value; browsers fall back to
solid silently. Use the canonical '0' reset so the CSS is portable.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…PR4a)

- Local `hovered` flag, surfaced via barHover event for hover-emphasize.
- ConnectorDot mounts only when editable && hovered && issue-target,
  excluding milestone and summary-claw bars from connector dragging.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…R4a follow-up)

PR3.3 renamed export issueObj → dragTarget but left this $: block
referencing the now-undeclared issueObj. The expression always
evaluated true (undefined === undefined), so isMilestoneSummary
collapsed to isSummary and parent-issue summary claws lost their
hit-rect. Replace with the dragTarget-discriminated check.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…s (PR4a)

Reactive Map<Ref<Issue>, BarRect> computed from the same row geometry
that positions GanttBar; threaded into GanttDependencyLayer so arrows
can anchor without a separate measurement pass.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Conditional cell rendered when ganttShowPredecessors ViewOption is on;
shows formatPredecessors(issue, relations, issueNumberOf) with ellipsis
truncation at 14ch.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
- IssueRelation live query (\$or both directions, scoped by space).
- hoveredIssue / hoveredEdge state + connectedIds reactive derivation.
- Connector reducer handlers (down/move/up); mouseup branches:
  · drawing without target → idle (drag cancelled).
  · target-hover → wouldCreateCycle gate → addCollection or error toast.
- showPredecessors ViewOption threaded into GanttSidebar.
- handleOpenEditor resolves canEdit from canEditIssue(sourceIssue) and
  showPopup(DependencyEditor, ...).

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Task 1 placed the same 13 keys in both plugins/tracker/src/index.ts AND
plugins/tracker-resources/src/plugin.ts. mergeIds() in the tracker-resources
plugin then threw 'identify overwrites DependencyKindFS for tracker:string'
during workspace upgrade (tool image bundle load).

The keys are UI-only (DependencyEditor labels, predecessor column,
cycle-toast, ViewOption label). Following the existing convention
(e.g. GanttShowIssueCode is in tracker-resources only, GanttDragFailed
is in tracker only), the dependency keys all belong in
tracker-resources/src/plugin.ts. Removed from the base tracker namespace.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…eIds (PR4a)

CockroachDB adapter rejects {$in: []} with 'unsupported comparison
operator: <string> = <string[]>' which surfaces as an error toast and
blocks the entire Gantt render. Empty array also semantically means
'no relations possible' — set relations = [] and skip the query
until issues load.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…R4a)

The Huly/CockroachDB adapter doesn't translate top-level $or — it
serializes the operator as a JSONB path (`data#>>'{$or}'`) and crashes
with 'unsupported comparison operator: <string> = <string[]>'.

Query the entire space's relations and let GanttDependencyLayer filter
client-side via barRects (only relations whose endpoints are in
visible barRects render). Relations are typically sparse so the
performance cost is negligible. predecessor-format does its own
client-side filter.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…dencies

- viewport.computeAdaptivePxPerDay stretches the time-scale when the
  data range is narrower than the visible canvas so the right side
  fills correctly. New unit test covers the stretch + identity cases.
- createTimeScale accepts an optional pxPerDayOverride for the
  adaptive path (default falls through to ZoomLevel-derived value).
- Optimistic dependency creation: GanttView prepends the new
  IssueRelation to a local list right after addCollection() so the
  arrow renders before the live query roundtrip. Rolled back on
  commit failure and de-duplicated when the canonical doc arrives.
- Connector-dot moves to the bar's bottom-right corner with a 12px
  outset + 10px hit area, plus a fallback native pointer-listener
  registered at document level (covers shadow-DOM / portal edge cases
  where Svelte's event-forwarding path doesn't surface mousedown).

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Wrapping the live-preview path computation in livePath() hid dragState
from Svelte's reactivity tracker, so the bezier between the connector
dot and the cursor never redrew while the user dragged. Inline the
condition into the $: block so dragState.kind, dragState.originPx,
and dragState.cursorPx are all visible direct dependencies.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
… const

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
The optional-chained `getAttribute('content-type')?.includes(...)` yields
`boolean | undefined`; guard the nullish case explicitly with `?? false`
to satisfy @typescript-eslint/strict-boolean-expressions without changing
behaviour (absent/non-JSON content-type still marks recording unavailable).

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…cade tier

Apply the reviewer-approved fix patterns to the pre-existing eslint errors
surfaced by the CI formatting job in the PR3b feature code:
- no-confusing-void-expression: block-body arrows for void event handlers
  (GanttCanvas/GanttSidebar/ConfirmCascadePopup/GanttHelpPopup dblclick/click/
  cleanup handlers).
- no-misused-promises: single stable void-wrapper (onWindowPointerUp) shared by
  attach/detach so the listener reference stays paired.
- prefer-optional-chain: nested {#if} guards preserving TS narrowing
  (GanttCanvas connector overlay, GanttDependencyLayer) and loose == null form.
- no-invalid-void-type: dispatcher payload void -> undefined (matches the
  codebase convention).
- strict-boolean-expressions: explicit zero / nullish handling in
  dependency-router and exporter.
- test files: drop non-null assertions in favour of optional chaining, remove
  unused imports/vars, rename snake_case locals to camelCase.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…ter develop merge

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…useup wrapper

The cascade merge + eslint import consolidation left three type errors
svelte-check flags in the PR3b GanttView:
- the './lib/types' import got merged into an `import type { ... }` with
  redundant inner `type` modifiers (invalid syntax) — flatten to plain names;
- `getEventPositionElement` was dropped from the @hcengineering/ui import —
  restore it (used by openGanttMenu);
- an onWindowMouseUp wrapper carried over from the PR3a edit tier referenced
  handleCanvasMouseUp, which PR3b renamed to handleCanvasPointerUp (wrapped as
  onWindowPointerUp) — remove the now-dead, dangling wrapper.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>

# Conflicts:
#	plugins/tracker-resources/src/components/gantt/GanttBar.svelte
#	plugins/tracker-resources/src/components/gantt/GanttCanvas.svelte
#	plugins/tracker-resources/src/components/gantt/GanttDependencyArrow.svelte
#	plugins/tracker-resources/src/components/gantt/GanttDependencyLayer.svelte
#	plugins/tracker-resources/src/components/gantt/GanttSidebar.svelte
#	plugins/tracker-resources/src/components/gantt/GanttView.svelte
#	plugins/tracker-resources/src/components/gantt/lib/critical-path.ts
#	plugins/tracker-resources/src/components/gantt/lib/exporter.ts
#	plugins/tracker-resources/src/components/gantt/lib/layout.ts
#	plugins/tracker-resources/src/components/gantt/lib/scheduler.ts
#	plugins/tracker-resources/src/components/gantt/lib/types.ts
…r gantt polish tier

Reviewer-approved fix patterns applied to the pre-existing eslint errors the
CI formatting job surfaces in the PR4 polish-tier code, alongside the prettier /
eslint --fix normalisation after the develop merge:
- no-confusing-void-expression: block-body arrows / drop redundant void operator
  (GanttCanvas dblclick, GanttView reactive recompute);
- no-non-null-assertion: drop redundant `!` on already-any-cast dragTarget;
- strict-boolean-expressions: explicit nullish handling in exporter;
- no-case-declarations: brace case blocks with lexical declarations (bar-labels);
- naming-convention: __resetConfirmGate -> resetConfirmGate (test reset helper);
- require-array-sort-compare: explicit localeCompare in build-rows test;
- balance the connector-overlay nested {#if} (prefer-optional-chain split) that
  the merge left one {/if} short.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…lector types

svelte-check surfaced type errors from the cascade merge + eslint import
consolidation in the PR4 polish tier:
- './lib/types' merged into an `import type { ... }` with redundant inner
  `type` modifiers (invalid) — flatten to plain names;
- descendantsWithDates dropped from the './lib/scheduler' import (used in the
  scheduler/cascade paths) — restore it;
- getEventPositionElement dropped from the @hcengineering/ui import (used by
  openGanttMenu) — restore it;
- exporter expandForCapture passed Element (untyped querySelector) where
  HTMLElement is required — add the <HTMLElement> generic, matching the
  querySelectorAll call in the same function.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…zation

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>

# Conflicts:
#	plugins/tracker-resources/src/components/gantt/lib/__tests__/dependency-router.test.ts
#	plugins/tracker-resources/src/components/gantt/lib/filter-predicate.ts
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>

# Conflicts:
#	plugins/tracker-resources/src/components/DependencyEditor.svelte
#	plugins/tracker-resources/src/components/gantt/GanttSidebarColumn.svelte
#	plugins/tracker-resources/src/components/gantt/GanttView.svelte
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
lang.test enforces en/ru structural parity. Gantt feature keys added to
en.json were missing in ru.json; filled with the en value as a
placeholder pending translation (Huly convention). No existing ru
translations changed.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…nto feat/gantt-upstream-pr3b-deps-cascade-cp

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…scade-cp' into feat/gantt-upstream-pr4-polish

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…into feat/gantt-upstream-pr5-tier2

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…nto feat/gantt-upstream-pr6-tier3-virtualization

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…rtualization' into feat/gantt-upstream-pr7-tier4

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Der Add-Sub-Issue-Button oeffnet seit bc2414a (unified hierarchy add
chooser) nicht mehr direkt das CreateIssue-Formular, sondern zuerst den
HierarchyAddPopup mit den Optionen 'Create new sub-issue' /
'Link existing as sub-issue'. Die Playwright-page-objects clicken den
Zwischenschritt jetzt mit, bevor das Formular gefuellt wird:

 - IssuesPage.clickOnSubIssues        (subissues.spec 'create sub-issue')
 - IssuesDetailsPage.clickButtonAddSubIssue
   (subissues.spec 'Edit a sub-issue' / 'Delete a sub-issue')

Keine Assertion abgeschwaecht, kein Timeout erhoeht — nur der neue
UI-Pfad wird durchlaufen (analog zur milestone-page-object-Anpassung in

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
hcengineering#10851).
…scade-cp' into feat/gantt-upstream-pr4-polish

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…into feat/gantt-upstream-pr5-tier2

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…nto feat/gantt-upstream-pr6-tier3-virtualization

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…rtualization' into feat/gantt-upstream-pr7-tier4

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…ional

The Gantt schema PR (ControlPanel) adds several unconditional rows between
Assignee and Estimation in the issue side panel (Start date, Due date,
Deadline, Scheduling mode). The prior positional locator
(//span[text()="Estimation"]/../div/button)[N] had to be re-indexed each
time a row was added and had drifted to [5], which no longer resolves to the
Estimation editor button — so editIssue's estimation step could never open
the EditBoxPopup and 'div.selectPopup input' never appeared, failing
issues.spec:49 'Edit an issue' and subissues.spec:52 'Edit a sub-issue'.

Switch buttonEstimation to the same following-sibling path already used by
textEstimation, which targets the Estimation label's own editor button
regardless of how many rows precede it.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…scade-cp' into feat/gantt-upstream-pr4-polish

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…into feat/gantt-upstream-pr5-tier2

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…nto feat/gantt-upstream-pr6-tier3-virtualization

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…rtualization' into feat/gantt-upstream-pr7-tier4

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants