Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
0e6d0df
feat(tracker): add Gantt scheduling schema (startDate + IssueRelation)
MichaelUray May 15, 2026
5c9faf7
test(model-tracker): add migrateAddStartDate jest tests
MichaelUray May 15, 2026
8641423
feat(model-tracker): add migrateAddStartDate + wire into trackerOpera…
MichaelUray May 15, 2026
8d932e8
feat(tracker): expose Issue.startDate / Milestone.startDate in UI; ti…
MichaelUray May 15, 2026
c94a5cc
fix(tracker): set explicit @Prop ranks for Milestone date fields
MichaelUray May 15, 2026
f84e497
fix(tracker-resources): EditMilestone renders Status/Start/Target in …
MichaelUray May 15, 2026
7d745f9
fix(fulltext): bump model version to 0.7.423 to match deployed worksp…
MichaelUray May 17, 2026
32cef1e
feat(tracker): register Gantt view tab (placeholder UI)
MichaelUray May 15, 2026
cd7a58a
feat(tracker-resources): add Gantt lib types
MichaelUray May 15, 2026
e9dd999
test(tracker-resources): add failing Gantt time-scale tests
MichaelUray May 15, 2026
7d5b84f
feat(tracker-resources): implement Gantt time-scale lib
MichaelUray May 15, 2026
a8e88a3
test(tracker-resources): add failing Gantt layout tests
MichaelUray May 15, 2026
1c63a84
feat(tracker-resources): implement Gantt layout lib
MichaelUray May 15, 2026
bd9a07c
feat(tracker-resources): add GanttHeader.svelte
MichaelUray May 15, 2026
dcfd750
feat(tracker-resources): add GanttBar.svelte (regular + summary claws)
MichaelUray May 15, 2026
e48b940
feat(tracker-resources): add GanttTodayMarker.svelte
MichaelUray May 15, 2026
291e524
feat(tracker-resources): add GanttMilestoneFlag.svelte
MichaelUray May 15, 2026
51c81c7
feat(tracker-resources): add GanttCanvas.svelte
MichaelUray May 15, 2026
c74ea95
feat(tracker-resources): add GanttSidebar.svelte
MichaelUray May 15, 2026
e5f4e0d
feat(tracker-resources): add GanttToolbar.svelte (zoom buttons)
MichaelUray May 15, 2026
a91cf34
feat(tracker-resources): GanttView wires Sidebar+Canvas+Toolbar with …
MichaelUray May 15, 2026
d0f6744
- layout.ts: orphan child issues (parent filtered out) now emit as roots
MichaelUray May 15, 2026
fa3b39c
Toolbar (replaces previous floating zoom):
MichaelUray May 15, 2026
83feba5
- SVG horizontal-scroll bug: GanttHeader and GanttCanvas now render at
MichaelUray May 15, 2026
7916e68
feat(tracker-resources): wire Gantt sidebar toggles into Customize-View
MichaelUray May 15, 2026
2996127
fix(tracker-resources): clip GanttSidebar inside its host
MichaelUray May 15, 2026
6131bbc
fix(tracker-resources): unified Gantt scroll container (Plane-style s…
MichaelUray May 15, 2026
5105650
feat(tracker-resources): + New issue button in Gantt toolbar
MichaelUray May 15, 2026
e67d341
chore(tracker): localize Gantt UI strings + minimize locale diffs
MichaelUray May 15, 2026
2e2da19
feat(tracker-resources): blue plus-only NewIssueHeader
MichaelUray May 15, 2026
1ddb3c5
fix(tracker-resources): resize handle no longer fights canvas pan
MichaelUray May 15, 2026
efc1cf6
feat(tracker-resources): sticky-bottom horizontal scrollbar (Plane-st…
MichaelUray May 15, 2026
ae74b0e
feat(tracker-resources): custom DOM scrollbar thumb (visible everywhere)
MichaelUray May 15, 2026
6f21093
feat(tracker-resources): hardened scrollbars + status colors + tight …
MichaelUray May 15, 2026
ad9c23d
feat(tracker-resources): status badge column + ganttShowStatus toggle
MichaelUray May 15, 2026
9be5b4e
feat(tracker-resources): Tracker UI redesign polish v27
MichaelUray May 15, 2026
1ef25b4
feat(tracker-resources): inline-add row in Gantt sidebar (PR2.1)
MichaelUray May 15, 2026
f5368f8
feat(model-tracker): register startDate + dueDate as Issue filters
MichaelUray May 15, 2026
f8f7daf
fix(tracker-resources): hide Gantt h-scrollbar when there's no overflow
MichaelUray May 15, 2026
6d74120
chore(tracker-resources): remove internal review markers from inline …
MichaelUray May 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/scripts/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
"0.7.422"
"0.7.423"
49 changes: 49 additions & 0 deletions models/tracker/src/__tests__/migration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// Copyright © 2026 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
//

import { DOMAIN_TASK } from '@hcengineering/model-task'
import tracker from '@hcengineering/tracker'

import { DOMAIN_TRACKER } from '../types'
import { migrateAddStartDate } from '../migration'

describe('migrateAddStartDate', () => {
it('sets startDate=null on every Issue lacking the field (DOMAIN_TASK)', async () => {
const update = jest.fn().mockResolvedValue(undefined)
const client: any = { update }

await migrateAddStartDate(client)

expect(update).toHaveBeenCalledWith(
DOMAIN_TASK,
{ _class: tracker.class.Issue, startDate: { $exists: false } },
{ startDate: null }
)
})

it('sets startDate=null on every Milestone lacking the field (DOMAIN_TRACKER)', async () => {
const update = jest.fn().mockResolvedValue(undefined)
const client: any = { update }

await migrateAddStartDate(client)

expect(update).toHaveBeenCalledWith(
DOMAIN_TRACKER,
{ _class: tracker.class.Milestone, startDate: { $exists: false } },
{ startDate: null }
)
})

it('issues exactly two update calls (one per class)', async () => {
const update = jest.fn().mockResolvedValue(undefined)
const client: any = { update }

await migrateAddStartDate(client)

expect(update).toHaveBeenCalledTimes(2)
})
})
6 changes: 5 additions & 1 deletion models/tracker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
TClassicProjectTypeData,
TComponent,
TIssue,
TIssueRelation,
TIssueStatus,
TIssueTemplate,
TIssueTypeData,
Expand Down Expand Up @@ -195,7 +196,9 @@ function defineFilters (builder: Builder): void {
key: 'milestone',
component: view.component.ObjectFilter,
showNested: false
}
},
'startDate',
'dueDate'
],
ignoreKeys: ['number', 'estimation', 'attachedTo'],
getVisibleFilters: tracker.function.GetVisibleFilters
Expand Down Expand Up @@ -441,6 +444,7 @@ export function createModel (builder: Builder): void {
TProject,
TComponent,
TIssue,
TIssueRelation,
TIssueTemplate,
TIssueStatus,
TTypeIssuePriority,
Expand Down
20 changes: 20 additions & 0 deletions models/tracker/src/migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import tracker, {
} from '@hcengineering/tracker'

import { classicIssueTaskStatuses } from '.'
import { DOMAIN_TRACKER } from './types'

async function createDefaultProject (tx: TxOperations): Promise<void> {
const current = await tx.findOne(tracker.class.Project, {
Expand Down Expand Up @@ -170,6 +171,20 @@ async function migrateIdentifiers (client: MigrationClient): Promise<void> {
}
}

export async function migrateAddStartDate (client: MigrationClient): Promise<void> {
// Issues live in DOMAIN_TASK; Milestones live in DOMAIN_TRACKER.
await client.update(
DOMAIN_TASK,
{ _class: tracker.class.Issue, startDate: { $exists: false } },
{ startDate: null }
)
await client.update(
DOMAIN_TRACKER,
{ _class: tracker.class.Milestone, startDate: { $exists: false } },
{ startDate: null }
)
}

async function migrateDefaultStatuses (client: MigrationClient, logger: ModelLogger): Promise<void> {
const defaultTypeId = tracker.ids.ClassingProjectType
const typeDescriptor = tracker.descriptors.ProjectType
Expand Down Expand Up @@ -398,6 +413,11 @@ export const trackerOperation: MigrateOperation = {
state: 'migrateDefaultTypeMixins',
mode: 'upgrade',
func: migrateDefaultTypeMixins
},
{
state: 'gantt-add-startdate',
mode: 'upgrade',
func: migrateAddStartDate
}
])
},
Expand Down
31 changes: 31 additions & 0 deletions models/tracker/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ import time, { type ToDo } from '@hcengineering/time'
import {
type ProjectTargetPreference,
type Component,
type DependencyKind,
type Issue,
type IssueChildInfo,
type IssueParentInfo,
type IssuePriority,
type IssueRelation,
type IssueStatus,
type IssueTemplate,
type IssueTemplateChild,
Expand Down Expand Up @@ -233,6 +235,10 @@ export class TIssue extends TTask implements Issue {
@ReadOnly()
declare space: Ref<Project>

@Prop(TypeDate(DateRangeMode.DATETIME), tracker.string.IssueStartDate)
@Index(IndexKind.Indexed)
declare startDate: Timestamp | null

@Prop(TypeDate(DateRangeMode.DATETIME), tracker.string.DueDate)
declare dueDate: Timestamp | null

Expand Down Expand Up @@ -340,6 +346,28 @@ export class TTimeSpendReport extends TAttachedDoc implements TimeSpendReport {
@Prop(TypeString(), tracker.string.TimeSpendReportDescription)
description!: string
}

/**
* @public
*/
@Model(tracker.class.IssueRelation, core.class.AttachedDoc, DOMAIN_TRACKER)
@UX(tracker.string.GanttDependency, tracker.icon.Issue)
export class TIssueRelation extends TAttachedDoc implements IssueRelation {
@Prop(TypeRef(tracker.class.Issue), tracker.string.Issue)
declare attachedTo: Ref<Issue>

declare collection: 'relations'

@Prop(TypeRef(tracker.class.Issue), tracker.string.Issue)
@Index(IndexKind.Indexed)
target!: Ref<Issue>

@Prop(TypeString(), tracker.string.GanttDependency)
kind!: DependencyKind

@Prop(TypeNumber(), tracker.string.GanttLag)
lag!: number
}
/**
* @public
*/
Expand Down Expand Up @@ -389,6 +417,9 @@ export class TMilestone extends TDoc implements Milestone {
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
attachments?: number

@Prop(TypeDate(), tracker.string.StartDate)
startDate!: Timestamp | null

@Prop(TypeDate(), tracker.string.TargetDate)
targetDate!: Timestamp

Expand Down
81 changes: 80 additions & 1 deletion models/tracker/src/viewlets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,51 @@ export function issueConfig (
]
}

export function ganttViewOptions (): ViewOptionsModel {
// PR 2 ships a minimal read-only Gantt. Group-by + Show-colors are
// intentionally NOT advertised — the canvas does not honour them yet.
// The two sidebar-column toggles below ARE wired up to GanttSidebar.
return {
groupBy: [],
orderBy: [
['startDate', SortingOrder.Ascending],
['rank', SortingOrder.Ascending],
['dueDate', SortingOrder.Ascending]
],
other: [
{
key: 'ganttShowIssueCode',
type: 'toggle',
defaultValue: false,
actionTarget: 'display',
label: tracker.string.GanttShowIssueCode
},
{
key: 'ganttShowTitle',
type: 'toggle',
defaultValue: true,
actionTarget: 'display',
label: tracker.string.GanttShowTitle
},
{
key: 'ganttShowStatus',
type: 'toggle',
defaultValue: true,
actionTarget: 'display',
label: tracker.string.GanttShowStatus
}
]
}
}

export function ganttConfig (): BuildModelKey[] {
// Minimal config — Gantt drives its own column layout.
return [
{ key: '', presenter: tracker.component.PriorityEditor, label: tracker.string.Priority, props: { kind: 'list', size: 'small' } },
{ key: '', presenter: tracker.component.IssuePresenter, label: tracker.string.Issue }
]
}

export function defineViewlets (builder: Builder): void {
builder.createDoc(
view.class.ViewletDescriptor,
Expand All @@ -224,6 +269,17 @@ export function defineViewlets (builder: Builder): void {
tracker.viewlet.Kanban
)

builder.createDoc(
view.class.ViewletDescriptor,
core.space.Model,
{
label: tracker.string.Gantt,
icon: tracker.icon.Gantt,
component: tracker.component.GanttView
},
tracker.viewlet.Gantt
)

builder.createDoc(
view.class.Viewlet,
core.space.Model,
Expand Down Expand Up @@ -501,6 +557,23 @@ export function defineViewlets (builder: Builder): void {
tracker.viewlet.IssueKanban
)

// Gantt is registered AFTER List + Kanban so List remains the default
// viewlet (ViewletSelector falls back to viewlets[0] when no preference
// is saved). Putting Gantt last avoids surprising users with an empty
// canvas on first visit.
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: tracker.class.Issue,
descriptor: tracker.viewlet.Gantt,
viewOptions: ganttViewOptions(),
configOptions: { strict: true, hiddenKeys: ['title'] },
config: ganttConfig()
},
tracker.viewlet.IssueGantt
)

const componentListViewOptions: ViewOptionsModel = {
groupBy: ['lead', 'createdBy', 'modifiedBy'],
orderBy: [
Expand Down Expand Up @@ -663,7 +736,7 @@ export function defineViewlets (builder: Builder): void {
viewOptions: milestoneOptions,
configOptions: {
strict: true,
hiddenKeys: ['targetDate', 'label', 'description']
hiddenKeys: ['startDate', 'targetDate', 'label', 'description']
},
config: [
{
Expand All @@ -672,6 +745,12 @@ export function defineViewlets (builder: Builder): void {
},
{ key: '', presenter: tracker.component.MilestonePresenter, props: { shouldUseMargin: true } },
{ key: '', displayProps: { grow: true } },
{
key: '',
label: tracker.string.StartDate,
presenter: tracker.component.MilestoneDatePresenter,
props: { field: 'startDate' }
},
{
key: '',
label: tracker.string.TargetDate,
Expand Down
1 change: 1 addition & 0 deletions packages/importer/src/importer/importer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,7 @@ export class WorkspaceImporter {
rank,
comments: issue.comments?.length ?? 0,
subIssues: issue.subdocs.length,
startDate: null,
dueDate: null,
parents: parentsInfo,
remainingTime,
Expand Down
24 changes: 23 additions & 1 deletion plugins/tracker-assets/lang/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@
"NoAssignee": "Bez přiřazení",
"LastUpdated": "Poslední aktualizace",
"DueDate": "Datum splnění",
"IssueStartDate": "Datum zahájení",
"GanttDependency": "Dependency",
"GanttLag": "Lag",
"Manual": "Manuální",
"All": "Vše",
"PastWeek": "Minulý týden",
Expand Down Expand Up @@ -280,7 +283,26 @@
"UnsetParentIssue": "Odebrat nadřazený úkol",
"ForbidCreateProjectPermission": "Zakázat vytvoření projektu",
"ForbidCreateProjectPermissionDescription": "Zakazuje uživatelům vytvářet nové projekty",
"AllowCreatingIssues": "Povolit vytváření úkolů"
"AllowCreatingIssues": "Povolit vytváření úkolů",
"Day": "Day",
"Week": "Week",
"Month": "Month",
"Quarter": "Quarter",
"Gantt": "Gantt",
"GanttShowIssueCode": "Show issue code",
"GanttShowTitle": "Show title",
"GanttShowStatus": "Show status",
"GanttToday": "Today",
"GanttJumpToStart": "Jump to start",
"GanttJumpToEnd": "Jump to end",
"GanttJumpToDate": "Jump to date",
"GanttPreviousPeriod": "Previous period",
"GanttNextPeriod": "Next period",
"GanttScrollLeftToBar": "Scroll left to bar",
"GanttScrollRightToBar": "Scroll right to bar",
"GanttExpand": "Expand",
"GanttCollapse": "Collapse"

},
"status": {}
}
24 changes: 23 additions & 1 deletion plugins/tracker-assets/lang/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@
"NoAssignee": "Nicht zugewiesen",
"LastUpdated": "Zuletzt aktualisiert",
"DueDate": "Fälligkeitsdatum",
"IssueStartDate": "Startdatum",
"GanttDependency": "Abhängigkeit",
"GanttLag": "Verzögerung",
"Manual": "Manuell",
"All": "Alle",
"PastWeek": "Letzte Woche",
Expand Down Expand Up @@ -290,7 +293,26 @@
"UnsetParentIssue": "Übergeordnete Aufgabe entfernen",
"ForbidCreateProjectPermission": "Projekterstellung verbieten",
"ForbidCreateProjectPermissionDescription": "Verbietet Benutzern das Erstellen neuer Projekte",
"AllowCreatingIssues": "Erstellen von Aufgaben erlauben"
"AllowCreatingIssues": "Erstellen von Aufgaben erlauben",
"Day": "Day",
"Week": "Week",
"Month": "Month",
"Quarter": "Quarter",
"Gantt": "Gantt",
"GanttShowIssueCode": "Show issue code",
"GanttShowTitle": "Show title",
"GanttShowStatus": "Show status",
"GanttToday": "Today",
"GanttJumpToStart": "Jump to start",
"GanttJumpToEnd": "Jump to end",
"GanttJumpToDate": "Jump to date",
"GanttPreviousPeriod": "Previous period",
"GanttNextPeriod": "Next period",
"GanttScrollLeftToBar": "Scroll left to bar",
"GanttScrollRightToBar": "Scroll right to bar",
"GanttExpand": "Expand",
"GanttCollapse": "Collapse"

},
"status": {}
}
Loading