…1672) (#1689)
* fix(gantt): progress % no longer occluded; replace 6 missing prebuilt-CSS utilities with inline styles
The connector dot used `-right-2`, which the prebuilt components stylesheet
never emits, so it collapsed to its auto top-left position and covered the
progress label at the bar's start. Pin it with inline `right:-8px` and give
`cursor:crosshair` inline too.
Audited every GanttView class token against the prebuilt CSS and inlined the
other genuinely-absent utilities: resize-handle `w-1.5`/`cursor-ew-resize`,
progress-fill `rounded-l-sm`, grid `z-0`, and the sm: responsive variants
(`sm:h-[50px]`, `sm:w-20`, `sm:hidden`, `-ml-1`, `h-[45%]`,
`touch-manipulation`) via a scoped media-query style block.
* feat(gantt): configurable hover tooltip via tooltipFields
The 悬浮详情 (hover tooltip) is now dynamically configurable like the
component's other field-bound properties. A view declares tooltipFields on
its gantt config — field names, or { field, label } to override the label —
and ObjectGantt resolves each against the record (select options to their
label, lookups to the embedded record name, dates/numbers/currency/percent
through the shared @object-ui/fields formatters), feeding GanttView a
task.fields array. When present these label/value rows replace the default
start to end / duration / progress line; unconfigured tooltips keep it.
- types: add tooltipFields to GanttConfig (ObjectUI display extension)
- GanttView: render task.fields as a two-column grid in the tooltip
- ObjectGantt: resolve + type-format tooltipFields per record
- tests: +2 ObjectGantt cases (label override, schema label, select option,
date/number formatting; omitted when unconfigured)
- verify: scripts/verify-tooltip-fields.mjs (6/6) + docs screenshot
* feat(gantt): parent summary stretches live while dragging a child
Previously an ancestor summary only re-rolled on drop — mid-drag it stayed at
its pre-drag span, so a child dragged past the parent edge appeared to escape
the bracket until release. Now every ancestor summary of a single dragged task
re-rolls its span over its leaf descendants (substituting the dragged leaf's
previewed dates) on each pointermove, so the parent bar grows/shrinks in real
time. The pinned edge (earliest child) stays put — it stretches, not shifts —
and the live width matches the rollup that commits on drop (no flicker).
- getLiveRowStyle: live rollup branch for stretch-ancestor summary rows
- dragStretchAncestorIds memo + taskById map drive the ancestor lookup
- liveStyle now uses the live path for stretched ancestors too
- verify: scripts/verify-child-stretch.mjs (4/4) + 2 docs screenshots
* docs(skill): document gantt view config in page-builder guide
* feat(gantt): Phase 6 — critical path, auto-schedule, export PNG
Three additive Phase 6 features, all driven from new toolbar buttons:
- Critical path (CPM): new pure `scheduling.ts` runs forward/backward
passes over the dependency graph to find the zero-slack chain. A toolbar
toggle (Activity icon) highlights critical task bars, milestones, summary
bars and the joining link arrows in red. `criticalPathDefault` prop /
`schema.criticalPath` start it on. The diamond fixture proves the maths:
Frontend (23d) is critical, the parallel Backend (20d) is not.
- Auto-schedule (顺延): `computeProjectReschedule` pushes each successor as
late as its links require, preserving durations and never pulling earlier;
honors fs/ss/ff/sf and leaves summary rollups fixed. A toolbar button
(Wand2, shown only when onTaskUpdate is wired) applies it in one shot.
ObjectGantt enables it whenever dependenciesField is configured.
- Export PNG: a toolbar button (Download) rebuilds the WHOLE chart
(virtualization-independent) into a standalone SVG with concrete hex
colors — prebuilt theme CSS vars don't resolve in a detached SVG — then
rasterizes to a 2x PNG via canvas and downloads gantt-<mode>.png. Zero deps.
Tests: 12 new unit tests for scheduling.ts (80 total pass), tsc clean.
Browser verification (verify-phase6.mjs) 13/13 pass; screenshots 15–16
persisted. ROADMAP + verification README updated.
* feat(gantt): Phase 6.2 — baselines, working calendar, undo/redo
- Baselines: baselineStart/baselineEnd render a thin reference strip under
each row (summary/task/milestone); showBaselines prop + ObjectGantt
baselineStartField/baselineEndField mapping.
- Working calendar: WorkingCalendar (skipWeekends + holidays) measures
durations in working days; critical-path and reschedule snap to working-day
boundaries (fs/ss via nextWorkingDay, ff/sf via subWorkingDays). Derived in
ObjectGantt from schema.skipWeekends/holidays.
- Undo/redo: commitTaskUpdates records before/after field deltas (group drag
+ auto-schedule batched), replayed via onTaskUpdate; toolbar buttons +
Ctrl/Cmd+Z / Shift+Z / Ctrl+Y; only shown when onTaskUpdate is wired.
- Tests: 5 working-calendar cases (85 total pass), tsc clean.
- Verification: scripts/verify-phase6b.mjs, 16/16 browser checks pass,
screenshots 17-20 persisted under docs/verification/.
* feat(gantt): explicit readOnly switch (prop, demo, skill, tests)
Add an explicit readOnly prop to GanttView that disables every write
interaction in one place via prop-shadowing — bar drag/resize/progress,
inline edit, context-menu edit/delete, dependency-link drag, row reorder,
auto-schedule, and the Undo/Redo toolbar buttons — regardless of which
callbacks are wired. Non-mutating affordances (task click, view switch,
zoom, critical path, export, fullscreen) stay live.
- ObjectGantt maps the view schema's top-level readOnly to the prop.
- demo: ?readonly=1 nav link.
- 3 unit tests assert no writes fire even when all callbacks + readOnly
are passed (88 tests total).
- verify-phase6b.mjs: +6 read-only browser checks (22/22), screenshot
21-read-only.png.
- page-builder skill + README document readOnly.
* fix(gantt): export PNG now includes baselines and custom markers
The PNG export re-draws the whole chart into a standalone SVG, but it was
written before baselines (Phase 6.2) and never drew custom markers — so the
downloaded image dropped both: the planned-vs-actual baseline strips and the
vertical marker lines/labels (sprint boundaries, deadlines) that the live
chart shows.
exportPng now draws, with concrete colors (CSS vars don't resolve in a
detached SVG):
- baseline strips for tasks carrying baselineStart/baselineEnd, gated by
showBaselines, at the row-bottom strip beneath the live bar;
- custom markers as vertical lines + label chips, with hsl(var(--primary))
falling back to a concrete indigo.
verify-phase6.mjs gains 2 checks (15/15): the export SVG contains 3 baseline
fills and both marker labels (Sprint 2 + Code freeze). Verified in-browser by
capturing the generated SVG (3 baselines, both markers, 0 NaN).
* fix(gantt): export month/year header band; unclip marker labels
Two visual fixes reported against the export PNG and the live chart:
1. Export PNG had no month/year context — the live header is two rows (a
month/year group band over the day/week/… unit labels) but exportPng drew
only the unit row. It now renders both: the headerGroups band on top
(bold month/year labels + group dividers) over the unit labels, taller
header (18+18px). The downloaded image now reads like the on-screen chart.
2. The Today / custom-marker label chips were clipped at the top. They sat at
top:-8 inside the timeline scroll container, so the overflow clipped the
part above the content's top edge. Moved to top:2 with z-30 so each chip
sits fully inside the chart just under the header, no longer occluded.
verify-phase6.mjs gains a month-band assertion (16/16): the export SVG now
contains a 4-digit year from the group band. Verified in-browser — all three
label chips measure clippedAbove:false, and the export header band renders.
* fix(gantt): fill the timeline viewport in week/month/quarter mode
With a fixed per-unit column width, a short date span in week/month/quarter
mode produced a grid far narrower than the timeline area, leaving a large
blank gap on the right (day mode already overflows, so it looked fine there).
When the end date is auto-derived, timelineRange now extends with empty
trailing calendar columns until the grid reaches the right edge — the scale
stays honest (real calendar units), only the visible future grows. No-op in
day mode and when the caller controls endDate.
Also refresh a stale verify-browser assertion: t4 carries configured
tooltipFields (Owner/Status/Effort) which replace the default
date·duration·progress line, so the tooltip check now asserts those fields
instead of '30%'.
verify-browser.mjs: +1 viewport-fill check (19/19); verified week/month/
quarter all fill in-browser (content width ≥ viewport).
* fix(gantt): fit-to-width instead of padding empty calendar columns
The previous viewport-fill extended the timeline with empty trailing
calendar units until the grid reached the right edge. In coarse modes
that meant years of blank columns (a 2.5-month project in month mode
padded out to ~2030). Back that out: keep the natural date span and
STRETCH the column width ("zoom to fit") so a short project fills the
viewport with a handful of real columns. A manual zoom overrides it; a
long project that already overflows keeps the base width and scrolls.
Gated on auto-derived end (a caller-pinned endDate keeps fixed width).
Verify: week ≤24 cols + month ≤8 cols + both fill the viewport
(verify-browser 22/22, phase6 16/16, 88 unit tests). Docs updated.
* fix(gantt): localize dates to i18n language + complete stale gantt locale packs
国际化一致性修复,两处:
1. 日期跟随 i18n 语言,而非浏览器 locale。GanttView 把 provider 的
language 透传到所有面向用户的 toLocaleDateString(表头月份/年、
单位标签、tooltip、拖拽与编辑日期片)经由 dateLocale。此前会出现
chrome 是英文、日历却是中文日期(浏览器为 zh-CN)的割裂;现在两者
始终一致。无 I18nProvider 时 language 为 undefined,回退浏览器
locale,保持独立嵌入/单测行为不变。
2. 补全过期的内置 gantt 语言包。@object-ui/i18n 的 en.ts / zh.ts 中
gantt: 命名空间停留在 Phase 2,缺少 Phase 4–6 新增的 toolbar
(viewMode/criticalPath/autoSchedule/exportPng/undo/redo/fullscreen)、
viewMode、row、aria、tooltip、menu 等键 —— 套了 I18nProvider 的应用
会把这些渲染成原始 key(如 gantt.viewMode.day)。现已对齐插件内
GANTT_DEFAULT_TRANSLATIONS 的完整键集;其余语言经 fallbackLng 优雅
降级为英文。
demo 包一层 I18nProvider 并提供 English · 中文 切换(?lang=en/zh),
新增 scripts/verify-i18n.mjs 断言两种语言下 chrome/日期均完整本地化、
无 gantt.* 原始 key 泄漏(13/13 通过),留痕 22/23 截图。
测试:tsc(i18n+gantt) 干净;单测 88/88(gantt)、113/113(i18n);
verify-browser 22/22、verify-phase6 全绿、verify-i18n 13/13。
* feat(gantt): 动态 Group by — groupBy accessor + ObjectGantt groupByField
将叶子任务按 groupBy(task)=>{key,label} 分桶,在合成的分组汇总行下渲染,
替换原有 parent 层级,复用现有 rollup/折叠/汇总渲染。纯展示层变换:时间轴范围、
关键路径、自动排程仍读取真实 tasks;合成分组行带 data.__group,不可拖拽。
ObjectGantt 暴露为 groupByField(select 选项/lookup 解析为显示标签)。
- GanttView: groupBy / ungroupedLabel props + displayTasks 变换,beginDrag 守卫
- ObjectGantt: groupByField 配置 + groupByAccessor
- demo: ?group=owner|status 切换
- 8 个单测 + verify-groupby.mjs(6/6 通过,截图 24/25)
* feat(gantt): 资源/工作负载视图 — per-resource load histogram with over-allocation
新增 ResourceWorkload 视图:按资源(负责人/状态等)聚合任务负载,每个资源一行直方图,
列负载 = Σ effort(重叠任务),超过 capacity 的列标红(过载)。纯聚合逻辑在 workload.ts
(7 个单测),渲染与时间轴几何在 ResourceWorkload.tsx(5 个渲染测试)。ObjectGantt 通过
resourceView + assigneeField/effortField/capacity 配置开启;demo 提供 ?resource=owner|status。
新增 gantt.resource.* 四个 i18n key(en/zh + 插件内置回退)。
浏览器验证 5/5 通过,留痕 26/27(中英)/28(中文)三张截图。
* feat(gantt): 非线性工作时间轴 — fold weekends/holidays out of the day axis
In day mode, when a workingCalendar marks weekends (skipWeekends) or
explicit holidays as non-working, those columns are now dropped from the
grid entirely — Friday sits against Monday, so the timeline shows only
working time. This makes date→px non-linear, so all positioning (bars,
dependency arrows, milestones, Today line, custom markers) is routed
through a single dateToX/xToDate pair that interpolates within the owning
column. Drag/resize advance by working columns (Fri +1 → Mon). Coarser
scales and the no-calendar path keep the plain linear axis — dateToX is
algebraically identical there.
- 7 unit tests (GanttView.workaxis.test.tsx): fold count, weekend drop,
weekend-straddling bar compression, working-day drag, holiday fold,
day-mode gating, linear equivalence.
- verify-workaxis.mjs browser check (5/5) + 3 screenshots (linear /
folded / folded-zh) under docs/verification/.
* docs(skill): document group-by, resource/workload view, and non-linear axis
Page-builder gantt guide now covers groupByField (swimlanes),
resourceView + assigneeField/effortField/capacity (workload histogram),
and the day-mode weekend/holiday axis folding (非线性工作时间轴) added to
the skipWeekends/holidays options.
* feat(gantt): 快速筛选 — 可配置多维度过滤栏 + 自动缩放时间轴
- QuickFilterBar 展示组件:每个维度一个多选下拉,AND 组合;全选折叠为无约束
- ObjectGantt 集成:选项解析优先级(显式 options → schema select/enum 全域 →
lookup/master_detail 引用对象全量拉取 → 数据去重兜底);lookup 按内嵌记录 id 匹配
- autoZoomToFilter(默认 true):筛选后按剩余任务重算时间轴范围;false 则钉住全量跨度
- 23 个单测(QuickFilterBar + ObjectGantt.quickfilter)+ verify-quickfilter.mjs 9/9
浏览器验证留痕(截图 32–37)
- 文档:page-builder.md 增补 quickFilters/autoZoomToFilter;verification README 新增快速筛选章节
* feat(gantt): 依赖增删 + 类型选择(右键链接菜单 + 添加紧前/紧后)
- GanttView 新增 onDependencyDelete prop(readOnly 时禁用)
- 在依赖连线上叠加透明命中路径(pointer-events: stroke 覆盖 svg 的 none),
右键打开链接菜单:切换 FS/SS/FF/SF 类型 或 移除依赖
- 任务右键菜单新增 添加紧前依赖/添加紧后依赖,弹出候选任务选择器
(排除自身与已连接任务)
- ObjectGantt:handleDependencyCreate 改为按 (source→target) upsert 类型,
新增 handleDependencyDelete;序列化保形(CSV 保持 CSV,出现非 FS 类型时
自动升级为 [{id,type}] 数组),乐观更新 + 失败回滚
- i18n:新增 menu.addPredecessor/addSuccessor/removeDependency/noCandidates
与 linkType.fs/ss/ff/sf(默认英文 + demo 中文)
- demo 工程示例接入 onDependencyDelete 并使 onDependencyCreate 按类型 upsert
- 测试:GanttView 交互 8 例 + ObjectGantt 写回 6 例(共 151 通过),tsc 通过
- 浏览器留痕:verify-dep-edit.mjs 7/7,截图 38-41
* feat(gantt): 拖拽冲突校验 + 顺延确认
拖动任务条后回放依赖前向排程,若新位置违反 FS/SS/FF/SF 约束则弹出顺延
确认框:自动顺延按拓扑顺序重排受影响任务(汇总条固定),取消保留则保留
手动位置。dependenciesField 存在时默认启用,readOnly 下禁用。
- GanttView 新增 rescheduleOnConflict 属性、冲突检测与确认对话框
- ObjectGantt 在 dependenciesField 配置时自动开启
- i18n 新增 gantt.conflict.* (英文默认 + demo 中文)
- 单测 +8 (GanttView 拖拽冲突 6 + ObjectGantt 接线 2),共 159 通过
- 浏览器留痕 scripts/verify-conflict.mjs,截图 42-44
Implements Phases 1–5 of the Gantt feature-parity roadmap (
packages/plugin-gantt/ROADMAP.md). Feature set referenced against svar-widgets/react-gantt (GPLv3 — feature reference only, all code written from scratch under MIT).Progress
fs/ss/ff/sf), live tracking during drag/resize, hover highlight,normalizeDependenciesexportSelf-test results (real Chromium, screenshots committed)
scripts/verify-browser.mjsdrives the newdemo/app and persists screenshots todocs/verification/— 18/18 checks passed:Browser testing also caught (and this PR fixes) utilities missing from the prebuilt
@object-ui/components/style.css— summary brackets, the Today marker, weekend tint, row hover/selection and the drag ring were invisible in consuming apps and now use inline theme-var colors.Validation
pnpm vitest run(plugin-gantt): 60 tests passing; monorepo-wide: 3,600 tests / 220 files greenpnpm type-check: full turbo run green (58 tasks)docs/verification/