feat(tracker): Gantt chart for the issue tracker - #10992
Conversation
|
Hi @MichaelUray packages/ui: SearchInputAdvanced (field-prefixed field:value search), HighlightedText 2. A separate package for Gantt engine |
|
Understood. I'll split the shared search/filter rework into a separate PR. For the Gantt code, I'll extract a tracker-agnostic engine package with generic item, dependency, scheduling, layout and viewport interfaces. Tracker-specific adapters, Huly actions, notifications and the Svelte integration will remain in I'll move this PR back to Draft while restructuring it and update it once both boundaries build and test independently. |
3d959ca to
42c196d
Compare
Builds on the merged search/filter rework (hcengineering#10998). Introduces @hcengineering/gantt, a dependency-free scheduling/zoom/drag/viewport engine package (with a neutral GanttDependency contract), and wires the tracker-specific adapter, Svelte UI, model schema, dependency-shift notifications and sanity coverage in tracker-resources. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
42c196d to
02933ee
Compare
…r view Follow-up to review feedback on hcengineering#10992: - Confirm-gate: the module-global flag is replaced by a createConfirmGate() factory - one instance per mounted GanttView, so two Gantt views mounted at once in the same JS context no longer block each other's pointer input. A two-instance independence test guards the regression. - Sidebar sorting: the stale deadline cast is dropped and issue.deadline is read directly (the field was added to the Issue model in this PR). - HR holidays: the per-project WorkingDaysConfig.holidays field duplicated the workspace-wide hr.class.PublicHoliday concept with no link and could drift from the real HR calendar. The field is removed; the Gantt adapter now sources holidays from HR via a LiveQuery (union across all departments, deduplicated, resolved to UTC midnight) and merges them with the per-project weekday mask into the engine's neutral WorkingCalendar. The soft dependency targets only the hr declaration package and is guarded by hierarchy.hasClass - deployments without the HR model fall back to "no holidays". The gantt engine package stays domain-neutral and functionally unchanged. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…r view Follow-up to review feedback on hcengineering#10992: - Confirm-gate: the module-global flag is replaced by a createConfirmGate() factory - one instance per mounted GanttView, so two Gantt views mounted at once in the same JS context no longer block each other's pointer input. A two-instance independence test guards the regression. - Sidebar sorting: the stale deadline cast is dropped and issue.deadline is read directly (the field was added to the Issue model in this PR). - HR holidays: the per-project WorkingDaysConfig.holidays field duplicated the workspace-wide hr.class.PublicHoliday concept and could drift from the real HR calendar. It is removed. The Gantt now sources holidays from HR with the same semantics HR itself uses (hr-resources ScheduleView): a project's holidays are those of a chosen department plus all of its ancestors up to the company root (hr.ids.Head). WorkingDaysConfig gains an optional holidayDepartment; with none selected the calendar falls back to the root (company-wide) holidays, a deleted/invalid reference fails safe to the root, and never to a union across departments. The ancestor walk has cycle and missing-parent protection. The calendar wiring is extracted into a CalendarStateMachine (lib/calendar-state.ts) that carries a monotonic generation counter, bumped synchronously on every project switch. Calendar-dependent mutations (drag, resize, cascade, auto-schedule) stay disabled until both the project config and the holidays have loaded, and every ops.commit() re-checks the captured mutation ticket against the current generation and target space, so a mutation started under one project can never persist after a switch to another. Plain display keeps working throughout. The manual date-picker / SetDueDate path does no calendar arithmetic and is intentionally not gated. A small standalone WorkingDaysEditor (hosted in the project create/edit dialog, behind the existing project-edit permission) lets an owner enable the mode, pick weekdays, and choose the HR department (default: Company-wide), persisting via TxOperations.updateDoc / $unset. At least one working day must stay active. This is a model-optional runtime integration: @hcengineering/hr is a static package import for the declaration types only; when a deployment ships without the HR model the class is absent from the hierarchy and the calendar resolves to an empty holiday set. The gantt engine package stays domain-neutral (no @hcengineering imports, no runtime deps). Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…r view Follow-up to review feedback on hcengineering#10992: - Confirm-gate: the module-global flag is replaced by a createConfirmGate() factory - one instance per mounted GanttView, so two Gantt views mounted at once in the same JS context no longer block each other's pointer input. A two-instance independence test guards the regression. - Sidebar sorting: the stale deadline cast is dropped and issue.deadline is read directly (the field was added to the Issue model in this PR). - HR holidays: the per-project WorkingDaysConfig.holidays field duplicated the workspace-wide hr.class.PublicHoliday concept and could drift from the real HR calendar. It is removed. The Gantt now sources holidays from HR with the same semantics HR itself uses (hr-resources ScheduleView): a project's holidays are those of a chosen department plus all of its ancestors up to the company root (hr.ids.Head). WorkingDaysConfig gains an optional holidayDepartment; with none selected the calendar falls back to the root (company-wide) holidays, a deleted/invalid reference fails safe to the root, and never to a union across departments. The ancestor walk has cycle and missing-parent protection. The calendar wiring is extracted into a CalendarStateMachine (lib/calendar-state.ts) that carries a monotonic generation counter, bumped synchronously on every project switch. Calendar-dependent mutations (drag, resize, cascade, auto-schedule) stay disabled until both the project config and the holidays have loaded, and every ops.commit() re-checks the captured mutation ticket against the current generation and target space, so a mutation started under one project can never persist after a switch to another. Plain display keeps working throughout. The manual date-picker / SetDueDate path does no calendar arithmetic and is intentionally not gated. A small standalone WorkingDaysEditor (hosted in the project create/edit dialog, behind the existing project-edit permission) lets an owner enable the mode, pick weekdays, and choose the HR department (default: Company-wide), persisting via TxOperations.updateDoc / $unset. At least one working day must stay active. This is a model-optional runtime integration: @hcengineering/hr is a static package import for the declaration types only; when a deployment ships without the HR model the class is absent from the hierarchy and the calendar resolves to an empty holiday set. The gantt engine package stays domain-neutral (no @hcengineering imports, no runtime deps). Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…r view Follow-up to review feedback on hcengineering#10992: - Confirm-gate: the module-global flag is replaced by a createConfirmGate() factory - one instance per mounted GanttView, so two Gantt views mounted at once in the same JS context no longer block each other's pointer input. A two-instance independence test guards the regression. - Sidebar sorting: the stale deadline cast is dropped and issue.deadline is read directly (the field was added to the Issue model in this PR). - HR holidays: the per-project WorkingDaysConfig.holidays field duplicated the workspace-wide hr.class.PublicHoliday concept and could drift from the real HR calendar. It is removed. The Gantt now sources holidays from HR with the same semantics HR itself uses (hr-resources ScheduleView): a project's holidays are those of a chosen department plus all of its ancestors up to the company root (hr.ids.Head). WorkingDaysConfig gains an optional holidayDepartment; with none selected the calendar falls back to the root (company-wide) holidays, a deleted/invalid reference fails safe to the root, and never to a union across departments. The ancestor walk has cycle and missing-parent protection. The calendar wiring is extracted into a CalendarStateMachine (lib/calendar-state.ts) that carries a monotonic generation counter, bumped synchronously on every project switch. Calendar-dependent mutations (drag, resize, cascade, auto-schedule) stay disabled until both the project config and the holidays have loaded, and every ops.commit() re-checks the captured mutation ticket against the current generation and target space, so a mutation started under one project can never persist after a switch to another. The all-projects view has no single project calendar, so calendar-dependent mutations are read-only there; display and the manual date-picker / SetDueDate path (which does no calendar arithmetic and is intentionally not gated) stay available. A small standalone WorkingDaysEditor (hosted in the project create/edit dialog, behind the existing project-edit permission) lets an owner enable the mode, pick weekdays, and choose the HR department (default: Company-wide), persisting via TxOperations.updateDoc / $unset. At least one working day must stay active. This is a model-optional runtime integration: @hcengineering/hr is a static package import for the declaration types only; when a deployment ships without the HR model the class is absent from the hierarchy and the calendar resolves to an empty holiday set. The gantt engine package stays domain-neutral (no @hcengineering imports, no runtime deps). Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…r view Follow-up to review feedback on hcengineering#10992: - Confirm-gate: the module-global flag is replaced by a createConfirmGate() factory - one instance per mounted GanttView, so two Gantt views mounted at once in the same JS context no longer block each other's pointer input. A two-instance independence test guards the regression. - Sidebar sorting: the stale deadline cast is dropped and issue.deadline is read directly (the field was added to the Issue model in this PR). - HR holidays: the per-project WorkingDaysConfig.holidays field duplicated the workspace-wide hr.class.PublicHoliday concept and could drift from the real HR calendar. It is removed. WorkingDaysConfig now carries only the weekday mask and an optional holidayDepartment. The tracker adapter resolves holidays for the selected department plus all ancestors up to hr.ids.Head, matching HR's existing inheritance semantics (hr-resources ScheduleView). A missing or invalid reference falls back to company-wide (root) holidays; unrelated departments are never included. The ancestor walk is cycle/missing-parent safe. A small WorkingDaysEditor in the existing project create/edit dialog lets users with the existing project permission enable the mode, choose weekdays, and select an HR department (default: Company-wide). It persists through the existing project create/update transaction paths (createDoc on create, client.update on edit) and uses $unset when the mode is disabled. At least one working day must stay active. Calendar loading is explicit via a CalendarStateMachine: project-scoped drag, resize, cascade, keyboard-shift and auto-schedule mutations stay disabled until both the project config and the holiday data have loaded. A monotonic generation ticket (bumped synchronously on project switch) is re-checked immediately before every calendar-dependent date commit, so work started in one project can never persist after a switch to another. The all-projects view has no single project calendar, so calendar-dependent edits are read-only there; display, selection, the context menu and the manual date-picker (which does no calendar arithmetic and takes no mutation ticket) stay available. This depends on the lightweight @hcengineering/hr model package, but not on hr-resources; the tracker adapter uses hr.class.* and hr.ids.Head at runtime, and actual HR model presence is checked through the hierarchy - without the HR model, holidays resolve to an empty set. The @hcengineering/gantt engine package stays domain-neutral (no @hcengineering imports, no runtime deps). Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…r view Follow-up to review feedback on hcengineering#10992: - Confirm-gate: the module-global flag is replaced by a createConfirmGate() factory - one instance per mounted GanttView, so two Gantt views mounted at once in the same JS context no longer block each other's pointer input. A two-instance independence test guards the regression. - Sidebar sorting: the stale deadline cast is dropped and issue.deadline is read directly (the field was added to the Issue model in this PR). - HR holidays: the per-project WorkingDaysConfig.holidays field duplicated the workspace-wide hr.class.PublicHoliday concept and could drift from the real HR calendar. It is removed. WorkingDaysConfig now carries only the weekday mask and an optional holidayDepartment. The tracker adapter resolves holidays for the selected department plus all ancestors up to hr.ids.Head, matching HR's existing inheritance semantics (hr-resources ScheduleView). A missing or invalid reference falls back to company-wide (root) holidays; unrelated departments are never included. The ancestor walk is cycle/missing-parent safe. A small WorkingDaysEditor in the existing project create/edit dialog lets users with the existing project permission enable the mode, choose weekdays, and select an HR department (default: Company-wide). It persists through the existing project create/update transaction paths (createDoc on create, client.update on edit) and uses $unset when the mode is disabled. At least one working day must stay active. Calendar loading is explicit via a CalendarStateMachine: project-scoped drag, resize, cascade, keyboard-shift and auto-schedule mutations stay disabled until both the project config and the holiday data have loaded. A monotonic generation ticket (bumped synchronously on project switch) is re-checked immediately before every calendar-dependent date commit, so work started in one project can never persist after a switch to another. The all-projects view has no single project calendar, so calendar-dependent edits are read-only there; display, selection, the context menu and the manual date-picker (which does no calendar arithmetic and takes no mutation ticket) stay available. This depends on the lightweight @hcengineering/hr model package, but not on hr-resources; the tracker adapter uses hr.class.* and hr.ids.Head at runtime, and actual HR model presence is checked through the hierarchy - without the HR model, holidays resolve to an empty set. The @hcengineering/gantt engine package stays domain-neutral (no @hcengineering imports, no runtime deps). Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…r view Follow-up to review feedback on hcengineering#10992: - Confirm-gate: the module-global flag is replaced by a createConfirmGate() factory - one instance per mounted GanttView, so two Gantt views mounted at once in the same JS context no longer block each other's pointer input. A two-instance independence test guards the regression. - Sidebar sorting: the stale deadline cast is dropped and issue.deadline is read directly (the field was added to the Issue model in this PR). - HR holidays: the per-project WorkingDaysConfig.holidays field duplicated the workspace-wide hr.class.PublicHoliday concept and could drift from the real HR calendar. It is removed. WorkingDaysConfig now carries only the weekday mask and an optional holidayDepartment. The tracker adapter resolves holidays for the selected department plus all ancestors up to hr.ids.Head, matching HR's existing inheritance semantics (hr-resources ScheduleView). A missing or invalid reference falls back to company-wide (root) holidays; unrelated departments are never included. The ancestor walk is cycle/missing-parent safe. A small WorkingDaysEditor in the existing project create/edit dialog lets users with the existing project permission enable the mode, choose weekdays, and select an HR department (default: Company-wide). It persists through the existing project create/update transaction paths (createDoc on create, client.update on edit) and uses $unset when the mode is disabled. At least one working day must stay active. Calendar loading is explicit via a CalendarStateMachine: project-scoped drag, resize, cascade, keyboard-shift and auto-schedule mutations stay disabled until both the project config and the holiday data have loaded. A monotonic generation ticket (bumped synchronously on project switch) is re-checked immediately before every calendar-dependent date commit, so work started in one project can never persist after a switch to another. The all-projects view has no single project calendar, so calendar-dependent edits are read-only there; display, selection, the context menu and the manual date-picker (which does no calendar arithmetic and takes no mutation ticket) stay available. This depends on the lightweight @hcengineering/hr model package, but not on hr-resources; the tracker adapter uses hr.class.* and hr.ids.Head at runtime, and actual HR model presence is checked through the hierarchy - without the HR model, holidays resolve to an empty set. The @hcengineering/gantt engine package stays domain-neutral (no @hcengineering imports, no runtime deps). Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
The finish-to-start gap-preservation floor in the cascade scheduler mixed two time measures: the FS snap anchor is working-calendar aware, but the floor added a raw calendar-millisecond start-side delta. With an active working calendar the two diverge, so dragging a predecessor over a weekend over-shifted the successor across non-working days (a Fri->Mon body drag is 3 calendar days but only 1 working day, and could even land a successor on a Saturday). Measure the floor on the DUE side in working days instead: translate the predecessor's shift via the new workingDayDelta helper and advance the floor base with addWorkingDays over the calendar. A day-granular resize gate (utcMidnight comparison, not a raw === 0 test) arms the floor only on a start-DAY move, so a pure due-resize stays a pure snap even when the stored issue carries a time-of-day on the cascade-commit path. The floor base is normalized via utcMidnight(targetAnchor). The legacy path (cfg === undefined) stays byte-identical to today's raw-ms floor. A left-resize in working-days mode now correctly no-ops the floor (the due, and thus the FS gap, did not move) instead of shifting on the raw start delta. Engine: add workingDayDelta (signed, half-open from-exclusive/to-inclusive working-day step count) and export the previously-private utcMidnight from packages/gantt/src/working-days.ts. The engine stays domain-neutral (no @hcengineering imports, empty dependencies). Adds engine and adapter test coverage: workingDayDelta cases and the full FS floor matrix (right-resize no-push, weekend-spanner body drag, floor wins over snap, holiday in the floor path, floor-dominant multi-hop chain, negative delta, legacy no-cfg, SS pure-snap, raw time-of-day right-resize, direct left-resize). Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
The search/filter rework this built on has merged (#10998), so this is now a standalone Gantt PR on current
develop.What it adds
The engine package
Introduces
@hcengineering/gantt, a runtime-dependency-free package for the domain-neutral part of the Gantt library — time-scale and working-day calculations, zoom and viewport primitives, drag/pointer state, responsive toolbar helpers, and the neutralGanttItem,GanttDependency, andWorkingCalendarcontracts. Tracker-specific scheduling, layout, critical-path and dependency-routing logic remains in the Tracker adapter; the Svelte UI, model schema, Huly actions, notifications, persistence and sanity coverage remain in their corresponding Tracker packages.Data model and migrations
Adds optional scheduling fields to Tracker documents, including issue deadlines and scheduling mode, project working-day configuration, and component/milestone colors. It also registers the dependency-shift request and notification documents and includes idempotent upgrades for Gantt view options and legacy relation-activity records. Existing issues and projects retain their previous behaviour because the new scheduling fields are optional.
New runtime dependencies
In
plugins/tracker-resources, both MIT:@tanstack/svelte-virtual(row virtualization) andjspdf(PDF export). Export re-renders the chart from the data model into an SVG rather than capturing the DOM; the PDF is paginated into bounded pages so large charts stay within the browser canvas and jsPDF size limits.Locales
en,deandrucarry the full set of new strings, and a unit test enforces ICU placeholder parity across all locale files. Other locales fall back to English at runtime.Testing
Full CI is green on a fork mirror of this exact head (
02933ee0f0) —build,svelte-check, unittest,formatting,docker-build,uitest,uitest-pg,uitest-workspaces,uitest-qms. Fork-CI run: https://github.com/MichaelUray/huly-platform/actions/runs/31542585071One small cross-cutting change: GanttView is a third writer to the shared result-count store from the search rework, so it uses the owner-token gate (claim/release like List and Kanban); the gate now resets to the pending sentinel on claim, so switching viewlets never shows a stale count.