Skip to content

feat(tracker): Gantt — #4 visual polish + UX refinement#10856

Draft
MichaelUray wants to merge 257 commits into
hcengineering:developfrom
MichaelUray:feat/gantt-upstream-pr4-polish
Draft

feat(tracker): Gantt — #4 visual polish + UX refinement#10856
MichaelUray wants to merge 257 commits into
hcengineering:developfrom
MichaelUray:feat/gantt-upstream-pr4-polish

Conversation

@MichaelUray

@MichaelUray MichaelUray commented May 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Visual + UX polish across the Gantt view: theme-variable colours instead of hex, sidebar tree glyphs become Chevron icons, the corner cell hosts Expand-all / Collapse-all (left-aligned), priority filter shows Urgent / High / Medium / Low labels, continuous Ctrl+wheel zoom, mobile-friendly long-press routing, data-driven PNG/PDF export with full sidebar + chart, and a dedicated Customize-View vs Configure-Columns split with distinct tooltips.

Also lands the Estimation row in the bar quick-info popover, the GanttSaveViewPopup modal scaffold (Save-View UI shell — the persistence layer lands in PR5), and the IssueRelation activity feed so dependency add/remove/update events show in the standard activity stream.

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

Look & feel

  • Replace hex colours in Gantt with theme variables
  • Replace tree glyphs with ChevronRight / ChevronDown icons
  • Add Estimation row to bar quick-info popover
  • Resolve Gantt aria-labels via translateCB; add aria-label + title to date-nav buttons
  • Add xmlns to canvas SVG root
  • Left-align Expand/Collapse-all in the sidebar corner cell

Toolbar & view-option UX

  • Collapse 4 zoom-preset buttons into a Dropdown + numeric days-input
  • Keep both Customize-View + Configure-Columns buttons in Gantt mode, with split tooltips
  • Hide standard Customize-View buttons in Gantt mode; viewlet renders its own
  • Hide group-by / sort in ViewOptions popup when the viewlet renders its own
  • Wrap Gantt toolbar on tablet viewports
  • Drop Gantt-toolbar Filter button → use FilterBar as single source

Interaction

  • Continuous Ctrl+wheel zoom over Gantt canvas (works in Month and Quarter)
  • GanttBar — pointer-only events + long-press routing (touch friendliness)
  • Wire layoutMode + pinch-zoom into GanttView
  • Replace Gantt double-click on mobile with quick-info "open editor" button
  • Defer scroll-driven translateX sync via rAF (silences Firefox scroll-linked positioning warning)
  • Freeze Gantt drag preview while confirm popup is open; keep drag preview alive

Sidebar extension

  • Wire per-column toggles for the extended Gantt sidebar
  • Thread nameLookup map through group-by helpers
  • Sidebar-sort cycle + comparators with full Jest coverage
  • Align Gantt extended sidebar grid width and sticky header

Save-View scaffold

  • GanttSaveViewPopup modal — name + fix-window + public toggles (persistence in PR5)
  • Close popup on X-click via onCancel

Export

  • Data-driven PNG / PDF export with full sidebar + chart
  • Drop duplicate Phase-1 Gantt toolbar to unblock PNG/PDF export

Activity feed

  • Register IssueRelation add/remove/update activity viewlets
  • RelationActivityPresenter for activity feed rendering
  • Migrate legacy IssueRelation remove activity docs
  • Use removeCollection for relation deletion so activity feed renders detail
  • Predecessor-list-format helper

i18n

  • Relation activity strings (en + de)

Stack overview

# Branch Scope Status
PR1 …pr1-schema schema + version bump merged ✅
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 this PR — visual polish + UX draft
PR5 …pr5-tier2 undo, saved views, bulk, auto-scheduling draft
PR6 …pr6-tier3-virtualization virtualization draft
PR7 …pr7-tier4 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 → PR3b.

How to review

Fork compare: PR3b...PR4 — 105 commits, 118 files

This is largely incremental UX work — most commits are small and self-explanatory. Largest single concern is the sidebar-sort cycle + comparators, which has its own test fixture.

Testing

  • Jest coverage on sidebar-sort comparators (+ cycle tests).
  • Hand-tested Ctrl+wheel zoom in Day / Week / Month / Quarter modes.
  • PNG / PDF export tested on a project with sidebar + bars + arrows.
  • Validated on a production-style deployment on 2026-05-18.

DCO

All commits are Signed-off-by.

@huly-github-staging

Copy link
Copy Markdown

Connected to Huly®: UBERF-16445

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>
…reactive queries

PR 2 read-only Gantt is now end-to-end functional:
- Two reactive queries (issues, milestones)
- buildLayout(issues) -> rows with depth + isSummary
- createTimeScale(zoom, dateRange.from) -> date<->px math
- Summary ranges computed per parent issue from children's date span
- Local zoom state (Day/Week/Month/Quarter) - no ViewletPreference plumbing
- Horizontal scroll via canvas-scroller; vertical scroll moves Sidebar via translate

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
- viewlets.ts: register IssueGantt AFTER IssueKanban so List stays default
- viewlets.ts: drop showColorsViewOption (canvas does not honour it yet)
- GanttBar.svelte: normalise reversed startDate>dueDate ranges

UX:
- GanttSidebar adds Title column with sticky two-column header
- Sticky time-scale header decoupled from milestone strip (layout fix)
- Per-row jump-to-bar arrow (Plane-style) when bar is offscreen
- ResizeObserver initialises viewport on mount and on resize

Milestones as Gantt rows + collapse:
- Milestones group their issues as nested children (depth+1)
- Collapsible toggle per parent row, local collapsedIds Set
- Milestone summary bar uses existing GanttBar with aggregated range

Icon:
- Register tracker.icon.Gantt to #timeline svg (Gantt pictogram)

Tests: 25/25 jest pass; svelte-check 0 errors.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
- Visible top row with [«] [Today] [»] | Day Week Month Quarter | ⚙
- Today scrolls to current date, prev/next page-scroll by 80% viewport
- Settings popover toggles Issue-Code and Title columns

Layout:
- Bigger row height (28→36) and bar font (11→13px) for readability
- Sidebar reworked into flex-column with separate clipped rows region so
  scrolled rows can never paint over the sticky header(layout fix)
- Drag handle between sidebar and canvas (120–600px range)
- ResizeObserver re-syncs viewport on drag/resize/zoom changes

Interaction:
- Vertical gridlines aligned to time-scale ticks (Plane-style)
- Row hover highlights both sidebar and canvas, with rich HTML tooltip
  (issue title + start + due + duration)
- Title click in sidebar dispatches openIssue → showPanel(EditIssue)
- Double-click on canvas bar dispatches openIssue
- Wheel forwarding from sidebar to canvas-scroller (vertical scroll
  works while hovering issue list)
- Pointerdown + drag on empty canvas pans both axes
- Jump-to-bar buttons now use the bar's true left-edge target

Tests: 25/25 jest pass; svelte-check 0 errors.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
  totalCanvasWidth (was viewport width), with viewBox in scroll-content
  coordinates. The sticky header lives in the same coordinate system as
  the canvas-stack so horizontal scroll no longer clips the time-axis.
- showPanel uses tracker.component.EditIssue instead of the hardcoded
  string + 'as any' cast.
- Removed unused GanttToolbar.svelte and GanttMilestoneFlag.svelte (dead
  files since toolbar moved into GanttView and milestone flags became
  rows).

Toolbar:
- Time-navigation cluster: ⏮ « Today » ⏭ + native date picker that jumps
  to a specific date. Replaces the lone Today button.

Interaction polish:
- Sidebar wheel forwarding now uses direct scrollTop/scrollLeft mutation
  with deltaMode scaling — same speed as native canvas scroll.
- TodayMarker no longer hides when scrolled offscreen (SVG width handles
  clipping); milestone reference lines render unconditionally for the
  same reason.

Tests: 25/25 jest pass; svelte-check 0 errors.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
- Register two ToggleViewOptions on the Gantt viewlet:
  ganttShowIssueCode (default OFF) and ganttShowTitle (default ON).
  These show up in the standard Customize-View dropdown.
- Drop the per-component settings popover from GanttView; sidebar
  column visibility now reads viewOptions directly.
- Hover tooltip always surfaces the issue code (e.g. OSTRO-31), even
  when the issue-code column is hidden.
- IntlStrings + en/de/es/fr/it/ja/pt/ru/zh/cs translations added.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Bug: .gantt-sidebar rendered at its natural height (≈ rows × 36px), and
.sidebar-host had overflow:visible, so for many issues the sidebar
visually overflowed gantt-body which in turn made gantt-body's
scrollHeight balloon to the sidebar's natural height. The result was
that scrolling over the sidebar appeared to scroll the page area
instead of just the canvas.

Fix:
- .sidebar-host now overflow:hidden + height:100% + flex-column
- .gantt-sidebar height:100% + flex:1 1 auto so it fills the host
  rather than stretching past it

Verified: gantt-body scrollHeight == clientHeight, only
.canvas-scroller has scrollable content. Wheel-forward over the
sidebar moves canvas-scroller and the sidebar transform together.

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

Replaces the dual-scroller architecture (sidebar + canvas as siblings,
manual wheel-forwarding for the sidebar) with a single .gantt-scroller
that wraps both columns in a CSS grid. Sidebar uses position:sticky
left:0 so it stays at the left edge during horizontal scroll, while the
time-axis header uses position:sticky top:0. Browser handles all wheel
events natively at native speed.

Why: the previous design rendered the sidebar at its natural height
(rows × 36px), which could be much larger than the visible viewport.
overflow:visible on the sidebar host let it bleed past gantt-body and
made the page area appear to scroll. The wheel-forward hack only worked
for synthetic events; in real browsers, wheel events over the sidebar
either scrolled too slowly (deltaMode mismatch) or stopped firing.

Layout (CSS grid, 2 rows × 3 cols):
- (1,1) sticky top+left: corner with column titles
- (1,2) sticky top:     resize-corner (5px gutter)
- (1,3) sticky top:     time-axis header
- (2,1) sticky left:0:  GanttSidebar rows
- (2,2) sticky left:Wpx: vertical resize-handle
- (2,3) normal:         GanttCanvas SVG

overflow:scroll on .gantt-scroller forces both scrollbars to always
render — the user explicitly asked for visible scrollbars (Plane parity).

GanttSidebar drops its scrollTop prop and the transform-based row
positioning; rows now flow naturally inside the scroll container.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
The standard NewIssueHeader is rendered by the Tracker shell outside the
viewlet. Inside the Gantt view this looked like the action was missing.
Add a prominent + New issue button in the gantt-toolbar that opens
CreateIssue with the current project space pre-selected, matching the
behaviour of NewIssueHeader.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
- Remove "+ New issue" button from GanttView. PR2 stays read-only;
  issue creation lives in PR3 with edit/drag.
- Localize all visible Gantt strings via use:tooltip + Label, no more
  hardcoded English: Today, Jump to start/end, Previous/Next period,
  Jump to date, Issue, Title, Milestone, Start/Target/Due, Expand,
  Collapse, Scroll left/right to bar.
- Add 9 new IntlStrings (GanttToday, GanttJumpToStart, GanttJumpToEnd,
  GanttJumpToDate, GanttPreviousPeriod, GanttNextPeriod,
  GanttScrollLeftToBar, GanttScrollRightToBar, GanttExpand,
  GanttCollapse) on top of the existing GanttShowIssueCode and
  GanttShowTitle.
- Restore all 13 locale files to their PR1 baseline formatting and
  add only the additive Gantt entries (~17 new lines per locale,
  English fallback). ko.json, pt-br.json, tr.json now ship the same
  Gantt keys as the rest.

Tests: tracker-resources svelte-check 0 errors; tracker-assets locale
test pass.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
User-requested UX consolidation: the same compact "+ New issue" button
should appear above every Tracker viewlet (List, Kanban, Gantt) instead
of the bulky text+dropdown HeaderButton.

- Replace HeaderButton with two stacked Button instances:
  * primary blue circular icon-only IconAdd (newIssue / newProject)
  * regular grey circular IconDropdown with secondary actions popup
- Always visible regardless of viewlet (kind of a tab-bar shortcut)
- showTooltip surfaces the keybinding from the existing tracker action
- Draft indicator stays as small dot on top-right of the plus button

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
The sidebar resize handle and the canvas pointer-pan handler both lived
on the gantt-scroller, so dragging the handle started a canvas pan in
parallel — the result was visible stutter and barely-controllable
resize. Two fixes:

- Add .resize-cell to onCanvasPanStart's element exclusion list so the
  pan never starts when the handle is the pointerdown target.
- The resize-cell's own pointer handlers now stopPropagation +
  preventDefault, so even if a sibling listener picked up the bubbled
  event it would not double-process it. setPointerCapture stays so the
  drag tracks the pointer outside the cell bounds.

Plus the v16 toolbar consolidation:
- Move the always-visible blue + button to IssuesView.svelte (the
  shared wrapper for List, Kanban and Gantt). Single source — no
  duplication per viewlet.
- NewIssueHeader stays as the original HeaderButton-based component;
  inline category-level + buttons in the issue list are unaffected.

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

The previous architecture put the canvas-stack inside one big
.gantt-scroller with overflow:scroll, so the native horizontal
scrollbar lived at the bottom of the entire scroll content — far
below the viewport once many rows were scrolled past. Noted:
this; Plane uses a separate sticky h-scrollbar at the bottom of the
visible area.

This commit splits the two scroll axes:

- .gantt-scroller now has overflow-y:auto + overflow-x:hidden. It
  carries vertical scrolling for the rows. Vertical native scrollbar
  unchanged.
- A separate .gantt-hscrollbar sits as a sibling at the bottom of
  .gantt-root (always visible regardless of vertical scroll
  position). It contains a thin track of width=totalCanvasWidth.
- The canvas-cell and header-cell become overflow:hidden, with
  inner wrappers translated by -hScroll.scrollLeft. Horizontal scroll
  is therefore controlled by the proxy bar but the visual sync
  matches what native scrolling would do.
- Pan-drag, jump-to-* and all programmatic scrolls now operate on
  the proxy bar instead of the gantt-scroller.
- ResizeObserver watches both scrollers so changes to either side
  re-sync the viewport bounds.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
The previous implementation had the previous v18 sticky horizontal scrollbar still
relied on a native ::-webkit-scrollbar inside an environment where
Huly globally hides scrollbars (\`* { scrollbar-width: none }\`). On
some browsers/OS combinations the bar rendered as a 6px ghost the
user could not see.

This commit replaces the native bar with a DOM-based custom thumb:

- .gantt-hscrollbar contains an .hscroll-shell (position:relative,
  flex:1, min-width:0, overflow:hidden) that fits the viewport width.
- Inside the shell: an .hscroll-track-custom (overflow-x:scroll) is
  the actual scroll-source — its native scrollbar is forcibly hidden
  on both Firefox (scrollbar-width:none) and WebKit (display:none).
  A 1px wide spacer drives its scrollWidth.
- A sibling .hscroll-thumb is absolutely positioned over the shell
  and its left/width are computed from canvasViewportLeft and
  totalCanvasWidth. Pointer drag on the thumb writes back to
  hScrollEl.scrollLeft (and pointermove updates the position
  reactively).
- Click on empty track area page-scrolls 80% of viewport.

Result: a guaranteed-visible 11px tall thumb that follows the
canvas timeline regardless of browser, OS, or Huly's global scroll
styling.

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

Robustness fix:
- .gantt-hscrollbar is now position:absolute pinned to gantt-root
  bottom (z-index:10) instead of relying on the flex chain to keep
  it visible. .gantt-scroller reserves padding-bottom:16px so the
  bar never paints over the last canvas row.

Vertical custom scrollbar (parity with horizontal):
- Same DOM-thumb pattern: hidden native scroll on .gantt-scroller,
  custom .vscroll-thumb absolutely positioned at the right edge.
- Thumb position derives from scrollTop / vScrollMax; pointer-drag
  writes back to scrollerEl.scrollTop. Auto-hides when content
  fits.

Status-driven bar colors:
- Pull statusStore.byId once into statusCategoryMap; pass through
  GanttCanvas to each GanttBar. Categories map to fills:
  backlog/unstarted -> theme button-default (grey),
  todo -> blue, active -> amber, won -> green, lost -> muted grey.

Tight time-range:
- computeDateRange now pads by exactly one zoom-unit on each side
  (1d / 7d / 30d / 90d) instead of always 14 days.
- totalCanvasWidth = ceil(scaleWidth) only — no longer inflated to
  fill the viewport, which previously stretched the timeline across
  empty quarters with no issues. If the data fits in the viewport,
  the canvas just doesn't horizontally scroll.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
- New StatusBadge.svelte renders a 12px coloured dot per row, mapped
  from the issue's status category via statusStore: backlog grey,
  todo blue, active amber, won green, cancelled muted.
- New ganttShowStatus ToggleViewOption (default ON) joins the existing
  Show-issue-code and Show-title in the Customize-View dropdown.
- Sidebar gains a 22px col-status column rendered before the issue-code
  cell. Milestone rows show a small "MS" tag instead of an issue code.
- Tracker-assets en.json + 12 other locales gain GanttShowStatus
  additively (+1 line per file).

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

The toolbar expand-/collapse-all buttons rendered "▶▶" / "▼▼" double
carets which QA flagged as confusing — they read more like a
fast-forward / jump-to-today affordance than a hierarchy toggle. The
single ▶ / ▼ text glyphs in the sidebar tree-rows suffered from the
same problem at small font sizes.

Replace both with the standard `IconChevronRight` / `IconChevronDown`
icons re-exported from `@hcengineering/ui` (same icons used in
AccordionItem, NestedSelectPopup and the calendar month-picker, so
the affordance is consistent across the platform).  The now-unused
`.gantt-tree-glyph` CSS rule is removed.

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

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Phase 1.E shipped the quick-info popover with Status / Priority /
Assignee / StartDate / DueDate inline editors.  The Gantt spec also
lists Estimation as part of the popover surface but it was missing
from the implementation.

Add an Estimation row that reuses the existing EstimationEditor from
issues/timereport/.  Click on the link opens the same EstimationPopup
the full editor uses, so update semantics (manhours / story points /
formatted display) stay identical.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
When the GanttCanvas SVG is serialized into a standalone .svg file
(e.g. via XMLSerializer in export paths) the resulting blob must
declare the SVG namespace or browsers/PDF renderers reject it. The
in-DOM HTML parser tolerates the missing attribute, but external
consumers do not.

exporter.ts already patches xmlns onto a clone before serialization;
adding it on the source removes that fallback dependency and makes
any direct outerHTML/XMLSerializer dump valid standalone SVG.

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

Hardcoded hex values (#dc2626, #10b981, #f59e0b, #3b82f6, #94a3b8, …)
in GanttBar, GanttCanvas, StatusBadge, GanttDependencyArrow,
GanttDependencyLayer, GanttConnectorDot, GanttSidebar, GanttSidebarColumn
don't react to Huly's dark/light theme and would glare in dark mode.

Map to existing theme vars:
- red/critical/violated/overdue → --theme-state-negative-color
- green/won status → --theme-state-positive-color
- orange/active/warning → --theme-warning-color
- blue/todo/primary → --theme-state-primary-color
- grey/neutral/backlog/slack → --theme-state-regular-color
- indigo connector dot keeps the existing --theme-state-info-color
  fallback pattern (no canonical var exists yet)

Known limitation: ConfirmCascadePopup illustration colors and the
export-renderer/exporter SVG output keep hex literals — the popup is
a fixed-palette diagram, and exported SVG/PNG must be self-contained
(theme vars wouldn't resolve outside Huly).

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

User-Report 2026-05-15: Im Gantt-Mode wird nur 'Configure Columns'
angezeigt (was Spalten fuer die Extended-Sidebar konfiguriert) aber
'Customize View' fehlt — und genau in 'Customize View' liegen die
Bar-Labels und anderen Gantt-relevanten ViewOptions, die der User
braucht.

Wurzel: v121.12 Refactor B hat das FALSCHE versteckt — der
ViewOptionsButton (mit Bar-Labels) wurde rausgenommen, aber genau der
sollte BLEIBEN. Mein Mental-Model zur Refactor-Zeit war invertiert.

Beide Buttons haben in Gantt-Mode echte Funktionen:
- 'Customize view'  (sliders)  → Bar-Labels + ganttQuickInfoOnClick +
                                  ganttSidebarColumnsExtended +
                                  ganttBarLabelLeft/Inside/Right etc.
- 'Configure columns' (eye)    → Phase-3a Extended-Sidebar Spalten-
                                  Konfiguration

Fix: showViewOptions-Prop entfernt, beide Buttons immer sichtbar.
Wording-Split (Customize view vs Configure columns) bleibt.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…o silence Firefox scroll-linked positioning warning

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

User reported v121.12 Refactor B (Customize-View vs Configure-Columns split)
ist nicht durchgängig — die Anzeige stimmt in Listen- und Card-View
weiterhin nicht.

Wurzel: Es gibt DREI separate Components mit derselben 'ViewOptionsButton
plus Configure-ButtonIcon' Logik, aber Refactor B fixte nur EINE:

- view-resources/.../ViewletSettingButton.svelte (singular) ✅ v121.12
- view-resources/.../ViewletsSettingButton.svelte (plural)  ❌ verbleibend
- card-resources/.../ViewSettingButton.svelte (Card-View)   ❌ verbleibend

Die beiden verbleibenden Components zeigten ihren Configure-ButtonIcon noch
mit 'CustomizeView' Tooltip statt 'ConfigureColumns', d.h. Sub-Issue-Liste
(QueryIssuesList) + MeetingMinutes + VacancyApplications + Card-View
hatten weiterhin BEIDE Buttons mit demselben 'Customize View'-Tooltip.

Fix: beide auf view.string.ConfigureColumns umstellen — gleicher
IntlString-Key der bereits in v121.12 in plugins/view-assets/lang/{en,de}.json
+ 11 weitere Locales gepflegt wurde.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
The platform Card.svelte routes X-click through an optional onCancel
prop and only falls back to dispatch('close') when onCancel is unset.
GanttSaveViewPopup was relying on the dispatch path with a wrapping
<Card on:close={...}> forwarder, which empirically did not close the
popup in v121.11 (user report). Wire onCancel explicitly so X-click
calls dispatch('close') directly on the popup component, bypassing
the Card→handler→re-dispatch chain that wasn't reaching PopupInstance.

OK-button continues to dispatch close{detail} from onSave so the
caller in GanttView.openSaveViewPopup() still receives the form
payload synchronously before Card's tail-close fires.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
… + numeric days-input

Replace the legacy Day/Week/Month/Quarter button row in the Gantt
toolbar-center with a single DropdownLabelsIntl + a numeric EditBox
showing the currently visible day-count.

- New helper lib/zoom-dropdown.ts with three pure functions:
  - dropdownSelectionForPxPerDay() maps userPxPerDay+zoom to one of
    day/week/month/quarter/custom with an EPSILON_PPD tolerance.
  - visibleDaysFromPxPerDay() / pxPerDayFromVisibleDays() are mutual
    inverses around the viewport width; defensive against NaN/zero.
- Custom is never directly selectable from the dropdown: the entry is
  only inserted while wheel-zoom has placed userPxPerDay off-preset, so
  clicking the dropdown still reflects the active state but does not
  offer Custom as a no-op pick.
- Editing the days-input drives userPxPerDay = width/days, which flips
  the dropdown to Custom (unless the value happens to land on a preset).
- Keyboard shortcuts D/W/M/Q and Ctrl+Wheel keep their existing wiring.

IntlStrings: GanttZoomDay/Week/Month/Quarter/Custom + GanttZoomLabel +
GanttZoomVisibleDays (plural) + GanttZoomDaysSuffix/Aria, in EN and DE.

Tests: tracker-resources jest 633 -> 645 (12 new in zoom-dropdown).
svelte-check: 0 errors.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…etSettingButton

v121.16 reverted v121.12 #B's only call-site override, leaving the prop
with default true and no remaining caller. Drop the prop and the
{#if showViewOptions} guard; ViewOptionsButton now renders whenever the
viewlet supplies viewOptions, matching observable behaviour since v121.16.

Tracked in 2026-05-15 regression audit, item Bug-1.

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

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…nfo open-editor button

iOS/Android intercept double-tap for system zoom, which collides with
the gantt 'dblclick → open full editor' shortcut and makes opening
issues unreliable on phones.

Phone layout now:
- drops the dblclick handler on bar-wrap groups (still active on tablet/desktop);
- force-enables ganttQuickInfoOnClick regardless of the user's view option
  so the quick-info popover is always reachable via single tap;
- routes the popover's existing 'Open full editor' button through the
  same showPanel(EditIssue) call the desktop dblclick uses.

Result: mobile users get a deterministic single-tap → quick-info →
'Open full editor' path with no system-zoom interference, while
desktop/tablet behaviour is unchanged.

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

An external code review flagged GanttBar's tabindex="-1" + svelte-ignore as a
potential upstream-review red flag. The behaviour is intentional: focus
management is centralised in GanttView.onKey (Tab/Shift-Tab cycles
scheduledIssues in visual order, Arrows shift focused bar). Native
per-rect Tab navigation would cycle in DOM order which doesn't match
chart order, so we route everything through moveFocus() instead.

Spell that contract out in a block comment above the non-summary bar
<rect>, mirroring the existing summary-path comment, with a pointer to
the canonical handler location in GanttView.svelte. Pure documentation,
no runtime change.

Tabindex="0" + native browser focus order remains a possible follow-up
if upstream requests it; leaving as Option-A (documentation) per
v121.19 task scope.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Rebase --onto upstream/develop introduced conflicts that were resolved
by keeping HEAD versions, losing content from later commits in the
branch history. Fixed:

- Restore all gantt/*.svelte and lib/*.ts to pre-rebase-backup state
  (upstream/develop does not touch the gantt directory)
- Remove duplicate function bodies in scheduler.ts and drag-controller.ts
- Restore 71 missing ViewOptions in models/tracker/src/viewlets.ts
- Add missing @hcengineering/notification dep to plugins/tracker/package.json
- Restore missing IntlStrings in tracker plugin index.ts (Deadline,
  BarLabel*, GanttBarLabel*, GanttQuickInfoOnClick, GanttSavedView*,
  GanttMobile*, GanttExport*, GanttFullscreen, GanttMoreActions,
  QuickInfoOpenFullEditor, DependencyShifted*)
- Restore 27 missing en/de lang strings (GanttSavedView*, GanttMobile*,
  GanttExport*, GanttFullscreen, GanttMoreActions)
- Fix trackerModel -> tracker in models/tracker/src/migration.ts
- Restore 31 missing strings in tracker-resources/src/plugin.ts
- Add DeadlineEditor usage back in ControlPanel.svelte

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

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

Add missing pure-logic modules (bulk-boundary, bulk-selection, dependency-shift-send,
dependency-shift-notify, flash-store, gantt-view-options, saved-views, tree-expand-store,
undo-manager, y-viewport) that GanttView.svelte imports.

Fix dependency-router.ts to include full ArrowVisibility/YBounds/clippedEndpointPx exports.
Add multiSelectedIssueIds + layoutMode props to GanttCanvas.
Add hasDeadline/isOverdue import to GanttCanvas.
Fix GanttBar resize handles to use on:pointerdown instead of undefined on:mousedown.
Add coDrag field to DragEvent mousedown-bar and DragState dragging-body types.
Add ShiftedIssuePayload + DependencyShiftedNotification interfaces to tracker plugin.
Add GanttExpandAll/GanttCollapseAll/GanttTreeBreadcrumb i18n strings.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
At low pixels-per-day densities (month=4, quarter=1.5) the chart fits in
the viewport with no horizontal overflow, so the custom h-scrollbar
element is not rendered. The wheel-zoom handler early-returned on the
missing element, leaving Ctrl+Wheel a no-op in those zoom levels.

Fall back to scrollerEl for the cursor-anchor bounding box and skip the
scrollLeft-anchor step entirely when there is no overflow (nothing to
scroll). Wheel-zoom now works at every density.

Also introduce adaptiveWheelFactor: 0.012 for ppd<4 (month/quarter),
0.006 for ppd>=4 (week/day). Exponential math is multiplicatively
adaptive, but in the low-density bands the absolute pixel delta per
notch is small enough that users perceive the same exp step as slower
than at high density. The bumped factor restores subjectively equal
responsiveness across all four presets.

Five new unit tests cover the factor boundaries and the
adaptive-vs-fixed contrast at ppd=1.5 and ppd=14.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…wlet renders its own

The shared View-Options popup renders a Group-by + Order-by dropdown pair
plus a list of viewlet-specific toggles (bar labels, confirm dialogs,
etc.). In Gantt mode the toolbar already provides its own dedicated
group-by and order-by dropdowns wired to the Gantt-internal layout, so
the popup's pair is visually duplicated and not wired to anything — users
see the same control twice and changing the popup version has no effect.

Add a hideGroupingAndOrdering prop that propagates from
ViewletSettingButton → ViewOptionsButton → ViewOptions. When true the
popup skips its grouping and ordering rows (plus the separator before
the toggles) and renders only the 'other' toggles, which remain wired.

IssuesView passes hideGroupingAndOrdering=isGanttMode so list mode keeps
the previous behaviour and Gantt mode shows only the useful toggles.

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

Strips internal-tracking phrases from source-code comments: Tier-N Item M,
Tier-N #M, v121.NN, per Tier-N spec, plus leftover internal review markers
mentions. The comments themselves are preserved — only the attribution
labels are dropped so the noted constraint or hidden invariant survives.

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

The Extended Sidebar toggle (ganttSidebarColumnsExtended) used to ship a
hardcoded identifier/title/predecessors/slack quartet with no UI to add
the other supported columns. The Configure-Columns button is generic
viewlet-config and has no effect on the Gantt sidebar.

Add 8 per-column ViewOption toggles under the Extended Sidebar toggle in
the Customize-View popup: Status / Priority / Assignee / Estimation /
StartDate / DueDate / Deadline / Progress. Each toggle appends its
column to the default set; the order matches the natural reading order
on the sidebar (status..progress..predecessors..slack). Toggles are
collapsed when the Extended Sidebar toggle is off so the popup does not
grow indefinitely.

Also raises the sidebar size limits: total width 600→1200 px,
per-column width cap 400→600 px, so the extra columns actually have
room to render.

Finally adds a showConfigureColumns prop on ViewletSettingButton
(default true). IssuesView passes false in Gantt mode so the
configure-columns icon no longer renders there — it had no effect on
the Gantt sidebar anyway.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
After the PR3a refactor dropped the duplicate \« \» date-nav buttons from
the corner-range, the remaining tree-toggle buttons (collapse-all,
expand-all) still inherited the old `justify-content: center` and
ended up visually floating in the middle of the corner cell. Switch to
`flex-start` so the buttons hug the left edge of the sidebar grid,
matching the alignment of the column-toggle chevrons in the rows below.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…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>
Re-materialize the exact intended tip content for Gantt component and
i18n files that were carried via merge commits. Linearizing onto the new
develop dropped those merges; develop does not touch these files.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
@MichaelUray MichaelUray force-pushed the feat/gantt-upstream-pr4-polish branch from 512fbf3 to a7df7d0 Compare July 9, 2026 07:45
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.

1 participant