@allbluecn/web-app 0.18.2 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +66 -51
- package/src/__tests__/components/auth/auth-layout.test.tsx +13 -1
- package/src/__tests__/{knowledge-public → knowledge/public}/canonical-id.test.ts +1 -1
- package/src/__tests__/{knowledge-public → knowledge/public}/favicon.test.ts +1 -1
- package/src/__tests__/{knowledge-public → knowledge/public}/github-sync.test.ts +1 -1
- package/src/__tests__/{knowledge-public → knowledge/public}/share-merge.test.ts +7 -7
- package/src/__tests__/security/xss-sanitize.test.ts +9 -8
- package/src/components/ai/__tests__/chat-thread-id-stability.test.tsx +125 -0
- package/src/components/ai/ai-chat-prompt.tsx +8 -2
- package/src/components/ai/ai-prompt-input/index.tsx +2 -1
- package/src/components/ai/global-harness-sidebar/__tests__/global-ai-prompts.test.ts +1 -2
- package/src/components/ai/global-harness-sidebar/index.tsx +22 -10
- package/src/components/bug/__tests__/board-subgroups.test.tsx +512 -0
- package/src/components/bug/__tests__/board-view.test.tsx +350 -0
- package/src/components/bug/__tests__/bug-list-loading-defer.test.tsx +85 -0
- package/src/components/bug/__tests__/bug-toolbar.test.tsx +7 -1
- package/src/components/bug/__tests__/comment-reactions.test.tsx +182 -0
- package/src/components/bug/__tests__/date-and-lightbox.test.tsx +96 -0
- package/src/components/bug/__tests__/export-entry.test.tsx +217 -0
- package/src/components/bug/__tests__/filters-compat.test.tsx +157 -0
- package/src/components/bug/__tests__/fix-request-race.test.tsx +396 -0
- package/src/components/bug/__tests__/peek-modes.test.tsx +365 -0
- package/src/components/bug/__tests__/saved-views.test.tsx +309 -0
- package/src/components/bug/__tests__/story-bugs-panel.test.tsx +146 -0
- package/src/components/bug/__tests__/subgroup-header.test.tsx +150 -0
- package/src/components/bug/__tests__/toolbar-shell.test.tsx +369 -0
- package/src/components/bug/__tests__/use-paged-bug-groups.test.tsx +58 -0
- package/src/components/bug/__tests__/view-type.test.tsx +247 -0
- package/src/components/bug/bug-list-view.tsx +310 -13
- package/src/components/bug/bug-panel-header.tsx +84 -8
- package/src/components/bug/bug-toolbar.tsx +268 -116
- package/src/components/bug/create-bug-dialog.tsx +1 -1
- package/src/components/bug/detail/__tests__/activity-timeline.test.tsx +1 -0
- package/src/components/bug/detail/__tests__/bug-detail-collab.test.tsx +236 -0
- package/src/components/bug/detail/__tests__/repro-report-card.test.tsx +2 -1
- package/src/components/bug/detail/activity-timeline.tsx +1 -1
- package/src/components/bug/detail/bug-detail-body.tsx +316 -18
- package/src/components/bug/detail/comment-item.tsx +7 -2
- package/src/components/bug/detail/comment-reactions.tsx +138 -0
- package/src/components/bug/detail/error-context.tsx +29 -9
- package/src/components/bug/detail/props-section.tsx +31 -3
- package/src/components/bug/detail/repro-report-card.tsx +1 -1
- package/src/components/bug/display/constants.ts +4 -2
- package/src/components/bug/display/display-dropdown.tsx +76 -280
- package/src/components/bug/display/types.ts +24 -8
- package/src/components/bug/display/use-paged-bug-groups.ts +183 -32
- package/src/components/bug/filters/{filter-value-input.tsx → field-configs.tsx} +218 -106
- package/src/components/bug/filters/index.tsx +14 -5
- package/src/components/bug/inline-editors/assignee-editor.tsx +2 -2
- package/src/components/bug/inline-editors/labels-editor.tsx +1 -1
- package/src/components/bug/inline-editors/severity-editor.tsx +37 -18
- package/src/components/bug/inline-editors/state-editor.tsx +1 -1
- package/src/components/bug/peek-panel.tsx +153 -35
- package/src/components/bug/select-bug-dialog.tsx +500 -0
- package/src/components/bug/story-bugs-panel.tsx +1090 -0
- package/src/components/bug/types.ts +9 -2
- package/src/components/bug/views/board-view.tsx +661 -0
- package/src/components/bug/views/bug-list/batch-toolbar.tsx +25 -34
- package/src/components/bug/views/bug-list/group.tsx +28 -13
- package/src/components/bug/views/bug-list/index.tsx +223 -59
- package/src/components/bug/views/bug-list/row.tsx +7 -1
- package/src/components/bug/views/create-view-dialog.tsx +160 -0
- package/src/components/bug/views/view-list-section.tsx +140 -0
- package/src/components/bug/views/view-selector-button.tsx +145 -0
- package/src/components/bug/views/view-switcher.tsx +8 -6
- package/src/components/bug/views/view-types.ts +162 -0
- package/src/components/collab/__tests__/activity-attribution.test.tsx +211 -0
- package/src/components/collab/__tests__/canvas-collab-panel.test.tsx +118 -0
- package/src/components/collab/__tests__/collab-presence-header.test.tsx +335 -0
- package/src/components/collab/__tests__/entity-presence-badge.test.tsx +226 -0
- package/src/components/collab/activity-attribution.tsx +221 -0
- package/src/components/collab/canvas-collab-panel.tsx +104 -0
- package/src/components/collab/collab-presence-header.tsx +220 -0
- package/src/components/collab/entity-presence-badge.tsx +130 -0
- package/src/components/collab/field-editing-pill.tsx +137 -0
- package/src/components/command/command-palette.tsx +6 -6
- package/src/components/design/canvas/document/__tests__/canvas-tree-fixture-snapshot.ts +589 -0
- package/src/components/design/canvas/document/__tests__/canvas-tree-reconcile.test.ts +159 -0
- package/src/components/design/canvas/panels/toolbar.tsx +3 -2
- package/src/components/design/canvas/plugins/presence/__tests__/canvas-collab-wiring.test.tsx +660 -0
- package/src/components/design/canvas/plugins/presence/__tests__/layout.test.ts +292 -0
- package/src/components/design/canvas/plugins/presence/__tests__/presence-overlay.test.tsx +139 -0
- package/src/components/design/canvas/plugins/presence/layout.ts +222 -0
- package/src/components/design/canvas/plugins/presence/presence-layer.tsx +353 -0
- package/src/components/design/canvas/plugins/presence/presence-overlay.tsx +155 -0
- package/src/components/design/canvas/plugins/presence/viewer-badge.tsx +80 -0
- package/src/components/design/canvas/react/__tests__/hooks.test.tsx +4 -4
- package/src/components/design/editor/collaboration/__tests__/design-agent-panel-regenerate.test.tsx +3 -1
- package/src/components/design/editor/collaboration/__tests__/design-agent-panel-stream.test.tsx +3 -1
- package/src/components/design/editor/collaboration/design-agent-panel.tsx +3 -0
- package/src/components/design/editor/collaboration/design-comment-review-panel.tsx +5 -5
- package/src/components/design/editor/dashboard/create-design-dialog.tsx +1 -1
- package/src/components/design/editor/dashboard/design-dashboard.tsx +1 -10
- package/src/components/design/editor/layout/__tests__/collab-param-wiring.test.tsx +153 -0
- package/src/components/design/editor/layout/__tests__/no-page-save-toast.test.tsx +8 -0
- package/src/components/design/editor/layout/design-editor-layout.tsx +28 -1
- package/src/components/design/editor/layout/design-navigation.tsx +0 -4
- package/src/components/design/editor/layout/hooks/__tests__/use-design-auto-save-collab.test.tsx +130 -0
- package/src/components/design/editor/layout/hooks/use-design-auto-save.ts +18 -1
- package/src/components/design/editor/share/share-comment-panel.tsx +37 -20
- package/src/components/design/editor/share/share-layout.tsx +3 -0
- package/src/components/design/editor/share/share-nav-bar.tsx +7 -1
- package/src/components/design/editor/share/share-theme-toggle.tsx +10 -15
- package/src/components/design/editor/tiptap/node/paragraph-node/paragraph-node.scss +1 -1
- package/src/components/harness/__tests__/composer-draft.test.ts +126 -0
- package/src/components/harness/composer/composer-editor.tsx +64 -6
- package/src/components/harness/composer/harness-composer.tsx +1 -1
- package/src/components/harness/harness-layout.tsx +1 -1
- package/src/components/harness/harness-workbench-page.tsx +6 -1
- package/src/components/harness/turn-group.tsx +1 -1
- package/src/components/hotkeys/shortcut-badge.tsx +4 -4
- package/src/components/knowledge/dialog/knowledge-form-dialog.tsx +1 -1
- package/src/components/knowledge/editor/__tests__/knowledge-doc-editor-collab.test.tsx +542 -0
- package/src/components/knowledge/editor/doc-header.tsx +7 -49
- package/src/components/knowledge/editor/knowledge-doc-editor.tsx +264 -51
- package/src/components/knowledge/editor/mindmap-doc-editor.tsx +3 -19
- package/src/components/knowledge/knowledge-homepage.tsx +3 -3
- package/src/components/knowledge/knowledge-toolbar.tsx +8 -8
- package/src/components/{knowledge-public → knowledge/public}/bookmark-form-dialog.tsx +4 -4
- package/src/components/{knowledge-public → knowledge/public}/knowledge-card.tsx +1 -15
- package/src/components/{knowledge-public → knowledge/public}/knowledge-form-dialog.tsx +9 -9
- package/src/components/{knowledge-public → knowledge/public}/legal-compliance-editor.tsx +35 -28
- package/src/components/{knowledge-public → knowledge/public}/sharers-avatar-group.tsx +3 -2
- package/src/components/layout/layout-responsive-provider.tsx +79 -0
- package/src/components/layout/sidebar-03/__tests__/presence-avatar-stack.test.tsx +148 -0
- package/src/components/layout/sidebar-03/app-sidebar.tsx +9 -3
- package/src/components/layout/sidebar-03/index.tsx +19 -4
- package/src/components/layout/sidebar-03/nav-main.tsx +28 -10
- package/src/components/layout/sidebar-03/presence-avatar-stack.tsx +112 -0
- package/src/components/layout/sidebar-03/user-menu.tsx +3 -3
- package/src/components/project/create-project-dialog.tsx +1 -1
- package/src/components/project/invite-member-dialog.tsx +1 -1
- package/src/components/project/project-activity-list.tsx +2 -2
- package/src/components/project/project-member-manager.tsx +2 -2
- package/src/components/reui/__tests__/kanban-drop-indicator.test.ts +88 -0
- package/src/components/reui/__tests__/kanban-keyboard.test.ts +262 -0
- package/src/components/reui/event-calendar/event-calendar-agenda-view.tsx +236 -0
- package/src/components/reui/event-calendar/event-calendar-content.tsx +80 -0
- package/src/components/reui/event-calendar/event-calendar-dnd.tsx +1062 -0
- package/src/components/reui/event-calendar/event-calendar-event.tsx +555 -0
- package/src/components/reui/event-calendar/event-calendar-i18n.tsx +291 -0
- package/src/components/reui/event-calendar/event-calendar-lib.tsx +677 -0
- package/src/components/reui/event-calendar/event-calendar-month-view.tsx +1287 -0
- package/src/components/reui/event-calendar/event-calendar-nav.tsx +615 -0
- package/src/components/reui/event-calendar/event-calendar-recurrence.tsx +592 -0
- package/src/components/reui/event-calendar/event-calendar-resource-view.tsx +810 -0
- package/src/components/reui/event-calendar/event-calendar-time-grid.tsx +1366 -0
- package/src/components/reui/event-calendar/event-calendar-types.tsx +240 -0
- package/src/components/reui/event-calendar/event-calendar.tsx +1916 -0
- package/src/components/reui/gantt/__tests__/gantt-row-window.test.ts +243 -0
- package/src/components/reui/gantt/__tests__/gantt-view-virtualize.test.tsx +243 -0
- package/src/components/reui/gantt/gantt-bar.tsx +439 -0
- package/src/components/reui/gantt/gantt-dnd.tsx +1000 -0
- package/src/components/reui/gantt/gantt-i18n.tsx +287 -0
- package/src/components/reui/gantt/gantt-lib.tsx +695 -0
- package/src/components/reui/gantt/gantt-nav.tsx +446 -0
- package/src/components/reui/gantt/gantt-recurrence.tsx +402 -0
- package/src/components/reui/gantt/gantt-row-window.ts +119 -0
- package/src/components/reui/gantt/gantt-types.tsx +283 -0
- package/src/components/reui/gantt/gantt-view.tsx +4466 -0
- package/src/components/reui/gantt/gantt.tsx +1822 -0
- package/src/components/reui/gantt/use-gantt-row-window.ts +159 -0
- package/src/components/reui/icon-stack.tsx +90 -0
- package/src/components/reui/kanban-keyboard.ts +268 -0
- package/src/components/reui/kanban.tsx +1227 -0
- package/src/components/settings/personal-team/account/account-client.tsx +3 -2
- package/src/components/settings/personal-team/team/invitation-cards/invitation-history-list.tsx +5 -5
- package/src/components/settings/personal-team/team/team-client.tsx +2 -1
- package/src/components/settings/personal-team/team/team-member-list.tsx +4 -4
- package/src/components/share/share-presence-bar.tsx +182 -0
- package/src/components/shared-kit/auth/auth-client.ts +5 -0
- package/src/components/shared-kit/avatars/__tests__/avatar-shape.test.ts +44 -0
- package/src/components/shared-kit/avatars/__tests__/user-avatar.test.tsx +46 -0
- package/src/components/{tags/tags-collection.ts → shared-kit/avatars/avatar-shape.ts} +14 -20
- package/src/components/shared-kit/avatars/notion-avatar.tsx +62 -11
- package/src/components/shared-kit/avatars/user-avatar.tsx +7 -4
- package/src/components/shared-kit/list-kit/__tests__/batch-toolbar-shell.test.tsx +133 -0
- package/src/components/shared-kit/list-kit/__tests__/frozen-header.test.ts +89 -0
- package/src/components/shared-kit/list-kit/__tests__/level-badge.test.tsx +85 -0
- package/src/components/shared-kit/list-kit/__tests__/toolbar-shell.test.tsx +178 -0
- package/src/components/shared-kit/list-kit/batch-toolbar-shell.tsx +111 -0
- package/src/components/shared-kit/list-kit/csv-download.ts +46 -0
- package/src/components/shared-kit/list-kit/display/__tests__/dropdown.test.tsx +215 -0
- package/src/components/shared-kit/list-kit/display/dropdown.tsx +582 -0
- package/src/components/shared-kit/list-kit/display/types.ts +64 -0
- package/src/components/shared-kit/list-kit/display/values.ts +60 -0
- package/src/components/shared-kit/list-kit/filters/__tests__/create-filters-module.test.tsx +181 -0
- package/src/components/shared-kit/list-kit/filters/__tests__/types.test.ts +133 -0
- package/src/components/shared-kit/list-kit/filters/index.tsx +757 -0
- package/src/components/shared-kit/list-kit/filters/types.ts +68 -0
- package/src/components/shared-kit/list-kit/frozen-header.ts +45 -0
- package/src/components/shared-kit/list-kit/icon-semantics.ts +50 -0
- package/src/components/shared-kit/list-kit/kanban/__tests__/drop-layout.test.ts +139 -0
- package/src/components/shared-kit/list-kit/kanban/__tests__/frozen-header.test.ts +111 -0
- package/src/components/shared-kit/list-kit/kanban/__tests__/instance-key.test.ts +130 -0
- package/src/components/shared-kit/list-kit/kanban/__tests__/kanban-board-view.test.tsx +901 -0
- package/src/components/shared-kit/list-kit/kanban/__tests__/manual-order.test.ts +35 -0
- package/src/components/shared-kit/list-kit/kanban/drop-commit.ts +212 -0
- package/src/components/shared-kit/list-kit/kanban/drop-layout.ts +98 -0
- package/src/components/shared-kit/list-kit/kanban/frozen-header.ts +88 -0
- package/src/components/shared-kit/list-kit/kanban/instance-key.ts +83 -0
- package/src/components/shared-kit/list-kit/kanban/kanban-board-view.tsx +1414 -0
- package/src/components/shared-kit/list-kit/kanban/manual-order.ts +39 -0
- package/src/components/shared-kit/list-kit/level-badge.tsx +71 -0
- package/src/components/shared-kit/list-kit/toolbar-shell.tsx +103 -0
- package/src/components/shared-kit/loading/__tests__/use-deferred-flag.test.tsx +115 -0
- package/src/components/shared-kit/loading/use-deferred-flag.ts +35 -0
- package/src/components/shared-kit/peek/dock-slot.ts +62 -0
- package/src/components/shared-kit/peek/peek-mode.ts +34 -0
- package/src/components/shared-kit/theme/animated-theme-toggler.tsx +13 -7
- package/src/components/shared-kit/theme/theme-toggle.tsx +7 -18
- package/src/components/shared-kit/views/view-icon.tsx +49 -0
- package/src/components/story/__tests__/peek-panel-patch-sync.test.tsx +215 -0
- package/src/components/story/__tests__/story-list-loading-defer.test.tsx +88 -0
- package/src/components/story/__tests__/story-list-view-peek-sync.test.tsx +242 -0
- package/src/components/story/__tests__/story-list-view-restore.test.tsx +45 -3
- package/src/components/story/__tests__/story-list-view-ssr.test.tsx +0 -1
- package/src/components/story/ai/__tests__/action-registry.test.ts +11 -11
- package/src/components/story/ai/action-registry.ts +5 -7
- package/src/components/story/ai/handlers/stream-text.ts +1 -3
- package/src/components/story/ai/story-ai-toolbar.tsx +58 -16
- package/src/components/story/ai/use-toolbar-overflow.ts +4 -7
- package/src/components/story/columns.tsx +2 -2
- package/src/components/story/constants.ts +17 -1
- package/src/components/story/create-story-dialog.tsx +8 -7
- package/src/components/story/detail/__tests__/associated-attachments-panel.test.tsx +38 -4
- package/src/components/story/detail/__tests__/categories.test.tsx +52 -26
- package/src/components/story/detail/__tests__/story-collab.test.tsx +897 -0
- package/src/components/story/detail/activity-tabs.tsx +5 -4
- package/src/components/story/detail/activity-timeline.tsx +15 -3
- package/src/components/story/detail/comment-input.tsx +2 -2
- package/src/components/story/detail/comment-item.tsx +6 -3
- package/src/components/story/detail/comment-thread.tsx +1 -0
- package/src/components/story/detail/reaction-bar.tsx +1 -1
- package/src/components/story/detail/story-detail-body.tsx +501 -76
- package/src/components/story/detail/story-detail-toolbar/__tests__/related-test-cases.test.tsx +173 -10
- package/src/components/story/detail/story-detail-toolbar/associated-attachments-panel.tsx +144 -8
- package/src/components/story/detail/story-detail-toolbar/categories.tsx +82 -38
- package/src/components/story/detail/story-detail-toolbar/children.tsx +254 -174
- package/src/components/story/detail/story-detail-toolbar/dependency-panel.tsx +140 -58
- package/src/components/story/detail/story-detail-toolbar/index.tsx +613 -0
- package/src/components/story/detail/story-detail-toolbar/related-test-cases.tsx +1010 -87
- package/src/components/story/detail/story-detail-toolbar/scroll-hint.tsx +57 -0
- package/src/components/story/detail/story-detail-toolbar/use-panel-frozen-groups.ts +97 -0
- package/src/components/story/detail/story-properties.tsx +9 -5
- package/src/components/story/display/__tests__/display-dropdown-helpers.test.ts +17 -1
- package/src/components/story/display/__tests__/display-dropdown.test.tsx +9 -0
- package/src/components/story/display/__tests__/flatten-story-groups.test.ts +42 -0
- package/src/components/story/display/__tests__/group-fields.test.ts +81 -1
- package/src/components/story/display/__tests__/group-resolver.test.ts +153 -1
- package/src/components/story/display/__tests__/use-collapsed-subgroups.test.ts +158 -0
- package/src/components/story/display/__tests__/use-display-options.test.ts +32 -3
- package/src/components/story/display/__tests__/use-fit-count.test.ts +198 -0
- package/src/components/story/display/__tests__/use-paged-story-groups.test.ts +77 -3
- package/src/components/story/display/constants.ts +23 -2
- package/src/components/story/display/display-dropdown.tsx +95 -397
- package/src/components/story/display/flatten-story-groups.ts +39 -0
- package/src/components/story/display/group-fields.ts +32 -1
- package/src/components/story/display/group-resolver.tsx +128 -5
- package/src/components/story/display/types.ts +23 -42
- package/src/components/story/display/use-collapsed-subgroups.ts +109 -0
- package/src/components/story/display/use-display-options.ts +21 -5
- package/src/components/story/display/use-fit-count.ts +124 -0
- package/src/components/story/display/use-paged-story-groups.ts +174 -31
- package/src/components/story/filters/field-configs.tsx +263 -0
- package/src/components/story/filters/index.tsx +14 -5
- package/src/components/story/inline-editors/__tests__/labels-editor.test.tsx +2 -0
- package/src/components/story/inline-editors/__tests__/priority-editor.test.tsx +15 -0
- package/src/components/story/inline-editors/assignee-editor.tsx +7 -5
- package/src/components/story/inline-editors/date-editor.tsx +7 -2
- package/src/components/story/inline-editors/inline-trigger.tsx +17 -4
- package/src/components/story/inline-editors/labels-editor.tsx +243 -35
- package/src/components/story/inline-editors/parent-story-editor.tsx +1 -1
- package/src/components/story/inline-editors/priority-editor.tsx +6 -4
- package/src/components/story/inline-editors/state-editor.tsx +6 -4
- package/src/components/story/inline-editors/story-select-dialog.tsx +56 -19
- package/src/components/story/peek-panel.tsx +234 -51
- package/src/components/story/relations/__tests__/design-select-dialog.test.tsx +131 -0
- package/src/components/story/relations/attachment-manage-dialog.tsx +1 -1
- package/src/components/story/relations/design-select-dialog.tsx +285 -0
- package/src/components/story/relations/knowledge-select-dialog.tsx +3 -3
- package/src/components/story/relations/link-manage-dialog.tsx +2 -2
- package/src/components/story/story-list-view.tsx +175 -17
- package/src/components/story/story-panel-header.tsx +18 -11
- package/src/components/story/story-toolbar.tsx +177 -126
- package/src/components/story/transfer-story-popover.tsx +1 -1
- package/src/components/story/types.ts +11 -2
- package/src/components/story/views/__tests__/calendar-view.test.tsx +262 -0
- package/src/components/story/views/__tests__/kanban-drop-subgroup.test.ts +68 -0
- package/src/components/story/views/__tests__/kanban-peek-sync.test.tsx +184 -0
- package/src/components/story/views/__tests__/kanban-view-membership-sync.test.tsx +179 -0
- package/src/components/story/views/__tests__/timeline-view.test.tsx +453 -0
- package/src/components/story/views/__tests__/view-icon.test.tsx +45 -0
- package/src/components/story/views/__tests__/view-types.test.ts +0 -8
- package/src/components/story/views/calendar/__tests__/mappers.test.ts +201 -0
- package/src/components/story/views/calendar/__tests__/slot-draft.test.ts +78 -0
- package/src/components/story/views/calendar/drag-blocked.ts +41 -0
- package/src/components/story/views/calendar/i18n.ts +91 -0
- package/src/components/story/views/calendar/mappers.ts +119 -0
- package/src/components/story/views/calendar/slot-draft.ts +48 -0
- package/src/components/story/views/calendar-view.tsx +490 -223
- package/src/components/story/views/create-view-dialog.tsx +6 -6
- package/src/components/story/views/kanban-view.tsx +618 -0
- package/src/components/story/views/{__tests__ → story-list/__tests__}/use-flat-rows.test.ts +1 -1
- package/src/components/story/views/story-list/batch-preview-dialog.tsx +3 -3
- package/src/components/story/views/story-list/batch-toolbar.tsx +17 -34
- package/src/components/story/views/story-list/group.tsx +4 -2
- package/src/components/story/views/story-list/index.tsx +142 -34
- package/src/components/story/views/story-list/row.tsx +4 -0
- package/src/components/story/views/{use-flat-rows.ts → story-list/use-flat-rows.ts} +2 -2
- package/src/components/story/views/timeline/__tests__/mappers.test.ts +742 -0
- package/src/components/story/views/timeline/__tests__/sentinel-geometry.test.ts +76 -0
- package/src/components/story/views/timeline/__tests__/use-date-commit.test.tsx +372 -0
- package/src/components/story/views/timeline/i18n.ts +96 -0
- package/src/components/story/views/timeline/mappers.ts +506 -0
- package/src/components/story/views/timeline/sentinel-geometry.ts +43 -0
- package/src/components/story/views/timeline/use-date-commit.ts +169 -0
- package/src/components/story/views/timeline-view.tsx +593 -222
- package/src/components/story/views/view-list-section.tsx +4 -4
- package/src/components/story/views/view-selector-button.tsx +9 -10
- package/src/components/story/views/view-switcher.tsx +7 -1
- package/src/components/story/views/view-types.ts +1 -5
- package/src/components/testcase/__tests__/batch-toolbar.test.tsx +245 -0
- package/src/components/testcase/__tests__/board-view.test.tsx +241 -0
- package/src/components/testcase/__tests__/case-row.test.tsx +24 -10
- package/src/components/testcase/__tests__/constants.test.ts +151 -0
- package/src/components/testcase/__tests__/create-dialog-scope.test.tsx +173 -0
- package/src/components/testcase/__tests__/detail-properties.test.tsx +19 -0
- package/src/components/testcase/__tests__/display-options.test.tsx +154 -0
- package/src/components/testcase/__tests__/export-entry.test.tsx +279 -0
- package/src/components/testcase/__tests__/filters.test.tsx +280 -0
- package/src/components/testcase/__tests__/list-view-scope.test.tsx +290 -0
- package/src/components/testcase/__tests__/list-view.test.tsx +552 -7
- package/src/components/testcase/__tests__/manual-move.test.ts +69 -0
- package/src/components/testcase/__tests__/peek-panel.test.tsx +89 -8
- package/src/components/testcase/__tests__/toolbar.test.tsx +302 -0
- package/src/components/testcase/__tests__/view-fallback.test.tsx +272 -0
- package/src/components/testcase/__tests__/view-management.test.tsx +179 -0
- package/src/components/testcase/ai/generate-dialog.tsx +1 -1
- package/src/components/testcase/constants.ts +83 -0
- package/src/components/testcase/create-testcase-dialog.tsx +76 -8
- package/src/components/testcase/detail/__tests__/select-cases-dialog.test.tsx +163 -0
- package/src/components/testcase/detail/case-detail-body.tsx +311 -30
- package/src/components/testcase/detail/select-cases-dialog.tsx +465 -0
- package/src/components/testcase/detail/story-link-editor.tsx +1 -1
- package/src/components/testcase/display/display-dropdown.tsx +147 -0
- package/src/components/testcase/display/field-configs.ts +80 -0
- package/src/components/testcase/display/use-testcase-display-options.ts +59 -0
- package/src/components/testcase/filters/field-configs.tsx +748 -0
- package/src/components/testcase/filters/index.tsx +35 -127
- package/src/components/testcase/inline-editors/assignee-editor.tsx +6 -6
- package/src/components/testcase/inline-editors/folder-editor.tsx +9 -9
- package/src/components/testcase/inline-editors/priority-editor.tsx +47 -31
- package/src/components/testcase/inline-editors/state-editor.tsx +14 -12
- package/src/components/testcase/inline-editors/type-editor.tsx +31 -20
- package/src/components/testcase/list/case-row.tsx +60 -2
- package/src/components/testcase/list/grouped-list.tsx +3 -2
- package/src/components/testcase/list-view.tsx +633 -137
- package/src/components/testcase/peek-panel.tsx +69 -11
- package/src/components/testcase/plans/plan-list.tsx +6 -1
- package/src/components/testcase/step-editor.tsx +34 -3
- package/src/components/testcase/testcase-toolbar.tsx +349 -94
- package/src/components/testcase/views/batch-toolbar.tsx +203 -0
- package/src/components/testcase/views/board-view.tsx +546 -0
- package/src/components/testcase/views/create-view-dialog.tsx +26 -14
- package/src/components/testcase/views/move.ts +56 -0
- package/src/components/testcase/views/view-selector-button.tsx +82 -30
- package/src/components/testcase/views/view-switcher.tsx +12 -5
- package/src/components/testcase/views/view-types.ts +50 -27
- package/src/components/tiptap/mindmap/lib/__tests__/shortcuts.test.ts +5 -4
- package/src/components/tiptap/mindmap/lib/shortcuts.ts +4 -3
- package/src/components/tiptap/node/doc-card-node-view.tsx +2 -1
- package/src/components/tiptap/notion-like/editor-extensions.ts +231 -0
- package/src/components/tiptap/notion-like/notion-like-editor.tsx +29 -156
- package/src/components/tiptap/ui/ai-menu/ai-menu.tsx +6 -1
- package/src/components/tiptap/ui/doc-reference-interaction/doc-reference-interaction.tsx +2 -1
- package/src/components/ui/checkbox.tsx +4 -4
- package/src/components/ui/popover.tsx +1 -1
- package/src/components/workspace/dialogs/edit-workspace-dialog.tsx +1 -1
- package/src/components/workspace/kb/workspace-kb-manage-dialog.tsx +1 -1
- package/src/components/workspace/kb/workspace-quick-doc-dialog.tsx +1 -1
- package/src/components/workspace/members/workspace-member-item.tsx +2 -2
- package/src/components/workspace/members/workspace-members-manager.tsx +2 -2
- package/src/config/sidebar-routes.tsx +11 -13
- package/src/design-engine/document/serialize-leafer-json.ts +2 -1
- package/src/design-engine/engine/__tests__/engine-split.test.ts +1 -1
- package/src/design-engine/engine/__tests__/engine.test.ts +20 -0
- package/src/design-engine/engine/app.ts +5 -0
- package/src/design-engine/engine/engine-base.ts +17 -0
- package/src/design-engine/engine/engine-doc.ts +32 -0
- package/src/hooks/__tests__/use-commands.test.tsx +17 -5
- package/src/hooks/use-commands.ts +4 -4
- package/src/hooks/use-toolbar-search.ts +7 -2
- package/src/lib/__tests__/datetime-local.test.ts +46 -0
- package/src/lib/__tests__/datetime.test.ts +7 -0
- package/src/lib/__tests__/layout-responsive.test.ts +123 -0
- package/src/lib/__tests__/sidebar-utils.test.ts +4 -4
- package/src/lib/ai/story-ai-stream.ts +1 -1
- package/src/lib/ai/use-selected-model.ts +15 -5
- package/src/lib/changelog/sdk-versions.ts +22 -22
- package/src/lib/collab/__tests__/bindings-fields.test.ts +496 -0
- package/src/lib/collab/__tests__/bindings-steps.test.ts +332 -0
- package/src/lib/collab/__tests__/presence-store.test.ts +414 -0
- package/src/lib/collab/__tests__/provider-handle-contract.test.ts +89 -0
- package/src/lib/collab/__tests__/provider.test.ts +504 -0
- package/src/lib/collab/__tests__/use-presence-list-refresh.test.tsx +309 -0
- package/src/lib/collab/__tests__/use-presence-report.test.ts +356 -0
- package/src/lib/collab/__tests__/use-share-presence.test.ts +555 -0
- package/src/lib/collab/__tests__/user-color.test.ts +53 -0
- package/src/lib/collab/bindings/__tests__/canvas-local.test.ts +583 -0
- package/src/lib/collab/bindings/__tests__/canvas-presence.test.ts +378 -0
- package/src/lib/collab/bindings/__tests__/canvas-remote.test.ts +454 -0
- package/src/lib/collab/bindings/__tests__/canvas-tree-perf.test.ts +134 -0
- package/src/lib/collab/bindings/canvas-local.ts +415 -0
- package/src/lib/collab/bindings/canvas-presence.ts +420 -0
- package/src/lib/collab/bindings/canvas-remote.ts +226 -0
- package/src/lib/collab/bindings/fields.ts +463 -0
- package/src/lib/collab/bindings/steps.ts +332 -0
- package/src/lib/collab/env.ts +33 -0
- package/src/lib/collab/module-map.ts +131 -0
- package/src/lib/collab/presence-store.ts +350 -0
- package/src/lib/collab/provider.ts +378 -0
- package/src/lib/collab/use-collab-doc.ts +100 -0
- package/src/lib/collab/use-presence-list-refresh.ts +142 -0
- package/src/lib/collab/use-presence-report.ts +176 -0
- package/src/lib/collab/use-self-profile-broadcast.ts +113 -0
- package/src/lib/collab/use-share-presence.ts +561 -0
- package/src/lib/collab/user-color.ts +40 -0
- package/src/lib/collections/index.ts +0 -1
- package/src/lib/collections/reference/bookmark-categories-collection.ts +1 -1
- package/src/lib/commands/__tests__/registry.test.ts +2 -5
- package/src/lib/commands/registry.ts +0 -3
- package/src/lib/commands/types.ts +1 -1
- package/src/lib/datetime.ts +20 -3
- package/src/lib/hooks/__tests__/use-synced-state.test.tsx +54 -0
- package/src/lib/hooks/use-story-panel-open.ts +134 -0
- package/src/lib/hooks/use-synced-state.ts +17 -0
- package/src/lib/hotkeys/__tests__/registry.test.ts +1 -1
- package/src/lib/hotkeys/registry.ts +0 -3
- package/src/lib/layout-responsive.ts +140 -0
- package/src/lib/pacer.ts +10 -1
- package/src/lib/peek-dock.ts +32 -0
- package/src/lib/plugin-runtime/__tests__/plugin-tool-schema-parity.test.ts +140 -0
- package/src/lib/plugin-runtime/plugin-client-tools.ts +28 -46
- package/src/lib/plugin-runtime/plugin-tool-defs.ts +152 -0
- package/src/lib/project-module-meta.ts +1 -8
- package/src/lib/rate-limit.ts +15 -6
- package/src/lib/sanitize.ts +122 -10
- package/src/lib/share-events.ts +20 -11
- package/src/lib/sidebar-utils.ts +1 -1
- package/src/lib/theme/__tests__/theme-mode.test.ts +96 -0
- package/src/lib/theme/theme-mode.ts +77 -0
- package/src/paraglide/messages/_index.js +5312 -1591
- package/src/paraglide/messages/en.js +1880 -685
- package/src/paraglide/messages/zh.js +1877 -682
- package/src/providers/index.tsx +4 -1
- package/src/providers/theme-mode-provider.tsx +135 -0
- package/src/routeTree.gen.ts +78 -101
- package/src/routes/$lang/docs/$/route.lazy.tsx +7 -2
- package/src/routes/-tests/chat-body-validation.test.ts +61 -3
- package/src/routes/__tests__/-edition-routing.test.tsx +12 -8
- package/src/routes/_nav/-components/nav-not-found.tsx +2 -2
- package/src/routes/_nav/bugs/$bugId/route.lazy.tsx +16 -2
- package/src/routes/_nav/bugs/$bugId/route.tsx +11 -2
- package/src/routes/_nav/dashboard/-components/avatar-group.tsx +2 -2
- package/src/routes/_nav/knowledge/$kbId/doc/$docId/route.lazy.tsx +31 -17
- package/src/routes/_nav/knowledge/$kbId/doc/$docId/route.tsx +12 -1
- package/src/routes/_nav/knowledge/$kbId/index.lazy.tsx +4 -2
- package/src/routes/_nav/knowledge/$kbId/route.lazy.tsx +7 -3
- package/src/routes/_nav/knowledge/bookmarks/$id.lazy.tsx +6 -6
- package/src/routes/_nav/knowledge/bookmarks/index.lazy.tsx +10 -10
- package/src/routes/_nav/knowledge/compliance/$id.lazy.tsx +9 -9
- package/src/routes/_nav/knowledge/compliance/edit/$id.lazy.tsx +1 -1
- package/src/routes/_nav/knowledge/compliance/index.lazy.tsx +8 -8
- package/src/routes/_nav/knowledge/dictionaries/$id.lazy.tsx +4 -4
- package/src/routes/_nav/knowledge/dictionaries/index.lazy.tsx +6 -6
- package/src/routes/_nav/knowledge/index.lazy.tsx +1 -1
- package/src/routes/_nav/knowledge/laws/$id.lazy.tsx +9 -9
- package/src/routes/_nav/knowledge/laws/edit/$id.lazy.tsx +1 -1
- package/src/routes/_nav/knowledge/laws/index.lazy.tsx +11 -11
- package/src/routes/_nav/knowledge/summary/index.lazy.tsx +1 -1
- package/src/routes/_nav/projects/$projectId/index.lazy.tsx +14 -6
- package/src/routes/_nav/route.lazy.tsx +11 -6
- package/src/routes/_nav/settings/account/route.tsx +3 -2
- package/src/routes/_nav/stories/$storyId/route.lazy.tsx +52 -8
- package/src/routes/_nav/stories/$storyId/route.tsx +11 -0
- package/src/routes/_nav/stories/-components/story-detail-skeleton.tsx +1 -1
- package/src/{components/tags/pages/tag-detail-page.tsx → routes/_nav/tasks/$taskId/route.lazy.tsx} +20 -10
- package/src/routes/_nav/tasks/$taskId/route.tsx +43 -0
- package/src/routes/_nav/tasks/-components/ai-task-detail.tsx +307 -0
- package/src/routes/_nav/tasks/-components/ai-task-list.tsx +217 -0
- package/src/routes/_nav/tasks/-components/ai-task-status-badge.tsx +46 -0
- package/src/routes/_nav/tasks/-components/assigned-list.tsx +157 -0
- package/src/routes/_nav/tasks/-components/create-ai-task-dialog.tsx +228 -0
- package/src/routes/_nav/tasks/-components/source-ai-exec-dialog.tsx +175 -0
- package/src/routes/_nav/tasks/-components/task-constants.ts +115 -0
- package/src/routes/_nav/tasks/-components/use-task-filter.ts +64 -0
- package/src/routes/_nav/tasks/-tests/create-ai-task-dialog.test.tsx +175 -0
- package/src/routes/_nav/tasks/-tests/task-filter.test.ts +134 -0
- package/src/routes/_nav/tasks/-tests/task-styles.test.tsx +119 -0
- package/src/routes/_nav/tasks/index.lazy.tsx +114 -0
- package/src/routes/_nav/tasks/index.tsx +47 -0
- package/src/routes/_nav/{tags → tasks}/route.tsx +3 -3
- package/src/routes/_nav/test-cases/$caseId/route.lazy.tsx +3 -1
- package/src/routes/_nav/test-cases/$caseId/route.tsx +11 -2
- package/src/routes/api/chat.ts +23 -19
- package/src/routes/api/design/prototype/route.ts +2 -4
- package/src/routes/{_nav/acceptance-criteria/route.tsx → api/internal/collab-reindex.ts} +11 -8
- package/src/routes/api/knowledge/sync-github.ts +1 -1
- package/src/routes/design-files/$projectId/$artboardId/$filename.ts +9 -0
- package/src/routes/invite/$token/route.lazy.tsx +2 -2
- package/src/routes/share/$versionId/route.lazy.tsx +8 -0
- package/src/routes/share/compliance/$id/route.lazy.tsx +2 -2
- package/src/routes/share/compliance/$id/route.tsx +1 -1
- package/src/routes/share/law/$id/route.lazy.tsx +2 -2
- package/src/routes/share/law/$id/route.tsx +1 -1
- package/src/server/__tests__/bug-groups-two-level.test.ts +263 -0
- package/src/server/__tests__/builtin-project.test.ts +45 -1
- package/src/server/__tests__/csv-export.test.ts +319 -0
- package/src/server/__tests__/share-version.test.ts +7 -2
- package/src/server/__tests__/task-assigned-query.test.ts +232 -0
- package/src/server/__tests__/task-context-builder.test.ts +175 -0
- package/src/server/__tests__/task-executor.test.ts +349 -0
- package/src/server/__tests__/task-priority-map.test.ts +41 -0
- package/src/server/__tests__/task-serverfn-crud.test.ts +639 -0
- package/src/server/__tests__/task-serverfn-queries.test.ts +403 -0
- package/src/server/__tests__/testcase-board.test.ts +301 -0
- package/src/server/__tests__/testcase-bulk.test.ts +155 -0
- package/src/server/__tests__/testcase-list-scope.test.ts +215 -0
- package/src/server/ai-gateway/__tests__/chat-handler-prompt-key.test.ts +106 -0
- package/src/server/ai-gateway/__tests__/chat-stream-builder-sandbox.test.ts +9 -3
- package/src/server/ai-gateway/__tests__/conformance.test.ts +4 -3
- package/src/server/ai-gateway/adapter.ts +4 -2
- package/src/server/ai-gateway/chat-handler.ts +13 -5
- package/src/server/ai-gateway/chat-stream-builder.ts +12 -7
- package/src/server/ai-gateway/sandbox/__tests__/harness-sandbox.test.ts +88 -0
- package/src/server/ai-gateway/sandbox/harness-sandbox.ts +1 -1
- package/src/server/ai-gateway/story-handler.ts +7 -2
- package/src/server/ai-gateway/tools/plugin-lifecycle-defs.ts +8 -99
- package/src/server/ai-gateway/tools/plugin-tools.ts +6 -10
- package/src/server/ai-task/assigned-query.ts +167 -0
- package/src/server/ai-task/context-builder.ts +182 -0
- package/src/server/ai-task/executor.ts +296 -0
- package/src/server/ai-task/priority-map.ts +37 -0
- package/src/server/ai-task/types.ts +15 -0
- package/src/server/bug-report/__tests__/report-handler.test.ts +55 -4
- package/src/server/bug-report/report-handler.ts +54 -8
- package/src/server/builtin-project.ts +24 -0
- package/src/server/cloudflare/prisma-wasm-base64-stub.ts +5 -0
- package/src/server/collab/__tests__/collab-permissions.test.ts +412 -0
- package/src/server/collab/__tests__/knowledge-additions.test.ts +121 -0
- package/src/server/collab/__tests__/reindex-endpoint.test.ts +168 -0
- package/src/server/collab/__tests__/resolve.test.ts +249 -0
- package/src/server/collab/__tests__/touch-snapshot.test.ts +58 -0
- package/src/server/collab/__tests__/write-guard-sdk.test.ts +501 -0
- package/src/server/collab/__tests__/write-guard.test.ts +373 -0
- package/src/server/collab/knowledge-additions.ts +37 -0
- package/src/server/collab/reindex-endpoint.ts +75 -0
- package/src/server/collab/resolve.ts +122 -0
- package/src/server/collab/seed.ts +408 -0
- package/src/server/collab/ticket-utils.ts +107 -0
- package/src/server/collab/touch-snapshot.ts +55 -0
- package/src/server/collab/write-guard.ts +258 -0
- package/src/server/collab-permissions.ts +248 -0
- package/src/server/csv.ts +61 -0
- package/src/server/{knowledge-public → knowledge/public}/github-sync.ts +12 -2
- package/src/server/{knowledge-public → knowledge/public}/share-merge.ts +31 -8
- package/src/server/lib/__tests__/sequence-number-retry.test.ts +160 -0
- package/src/server/lib/rate-limit.ts +13 -2
- package/src/server/lib/sequence-number-retry.ts +85 -0
- package/src/server/mcp/__tests__/tools.test.ts +10 -0
- package/src/server/mcp/tools.ts +26 -2
- package/src/server/middlewares/auth-rate-limit.ts +18 -9
- package/src/server/notifications/event-bus.ts +3 -0
- package/src/server/serverFns/__tests__/collab-snapshot.test.ts +665 -0
- package/src/server/serverFns/__tests__/collab.test.ts +459 -0
- package/src/server/serverFns/__tests__/knowledge-delete.test.ts +167 -0
- package/src/server/serverFns/__tests__/ownership-merge.test.ts +34 -2
- package/src/server/serverFns/__tests__/read-write-merge.test.ts +2 -2
- package/src/server/serverFns/__tests__/story-crud-archive-detail.test.ts +12 -2
- package/src/server/serverFns/__tests__/story-crud.test.ts +51 -11
- package/src/server/serverFns/__tests__/story-tree.test.ts +66 -34
- package/src/server/serverFns/__tests__/story-types.test.ts +14 -9
- package/src/server/serverFns/__tests__/update-bug-guard.test.ts +325 -0
- package/src/server/serverFns/__tests__/update-document-guard.test.ts +285 -0
- package/src/server/serverFns/__tests__/update-page-guard.test.ts +276 -0
- package/src/server/serverFns/__tests__/update-story-guard.test.ts +436 -0
- package/src/server/serverFns/__tests__/update-testcase-guard.test.ts +293 -0
- package/src/server/serverFns/bug/__tests__/bug-comments.test.ts +25 -0
- package/src/server/serverFns/bug/__tests__/bug-crud.test.ts +315 -15
- package/src/server/serverFns/bug/__tests__/bug-groups.test.ts +36 -18
- package/src/server/serverFns/bug/__tests__/bug-list-query.test.ts +21 -0
- package/src/server/serverFns/bug/__tests__/bug-reactions.test.ts +335 -0
- package/src/server/serverFns/bug/__tests__/bug-reorder.test.ts +165 -0
- package/src/server/serverFns/bug/__tests__/bug-story.test.ts +4 -1
- package/src/server/serverFns/bug/bug-comments.ts +5 -0
- package/src/server/serverFns/bug/bug-crud.ts +321 -72
- package/src/server/serverFns/bug/bug-display-options.ts +11 -0
- package/src/server/serverFns/bug/bug-export.ts +137 -0
- package/src/server/serverFns/bug/bug-group-query.ts +16 -5
- package/src/server/serverFns/bug/bug-groups.ts +39 -15
- package/src/server/serverFns/bug/bug-list-query.ts +8 -1
- package/src/server/serverFns/bug/bug-reactions.ts +123 -0
- package/src/server/serverFns/bug/bug-reorder.ts +64 -0
- package/src/server/serverFns/bug/bug-story-bulk.ts +280 -0
- package/src/server/serverFns/bug/bug-story.ts +47 -39
- package/src/server/serverFns/bug/index.ts +6 -0
- package/src/server/serverFns/collab-presence.ts +119 -0
- package/src/server/serverFns/collab-snapshot.ts +201 -0
- package/src/server/serverFns/collab.ts +153 -0
- package/src/server/serverFns/design/__tests__/designs.test.ts +0 -2
- package/src/server/serverFns/design/designs.ts +0 -64
- package/src/server/serverFns/design/detail.ts +36 -2
- package/src/server/serverFns/design/pages.ts +73 -10
- package/src/server/serverFns/iteration/iteration-crud.ts +25 -1
- package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/create.ts +3 -3
- package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/detail.ts +2 -2
- package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/list.ts +3 -2
- package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/refresh-favicon.ts +1 -1
- package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/share.ts +1 -1
- package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/sync-github.ts +1 -1
- package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/update.ts +5 -4
- package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/create.ts +1 -1
- package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/detail.ts +2 -2
- package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/list.ts +3 -2
- package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/share.ts +1 -1
- package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/update.ts +3 -2
- package/src/server/serverFns/{knowledge-public → knowledge/public}/dictionary/create.ts +1 -1
- package/src/server/serverFns/{knowledge-public → knowledge/public}/dictionary/detail.ts +2 -2
- package/src/server/serverFns/{knowledge-public → knowledge/public}/dictionary/list.ts +3 -2
- package/src/server/serverFns/{knowledge-public → knowledge/public}/dictionary/share.ts +1 -1
- package/src/server/serverFns/{knowledge-public → knowledge/public}/dictionary/update.ts +3 -2
- package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/create.ts +1 -1
- package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/detail.ts +2 -2
- package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/list.ts +3 -2
- package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/share.ts +1 -1
- package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/update.ts +3 -2
- package/src/server/serverFns/knowledge.ts +135 -19
- package/src/server/serverFns/project/module-overview.ts +1 -2
- package/src/server/serverFns/share/version.ts +7 -1
- package/src/server/serverFns/story/__tests__/bulk.test.ts +6 -0
- package/src/server/serverFns/story/__tests__/create-story-internal.test.ts +112 -3
- package/src/server/serverFns/story/__tests__/story-assoc.test.ts +77 -6
- package/src/server/serverFns/story/__tests__/story-crud-date-key.test.ts +187 -0
- package/src/server/serverFns/story/__tests__/story-gantt-deps.test.ts +163 -0
- package/src/server/serverFns/story/__tests__/story-groups.test.ts +4 -0
- package/src/server/serverFns/story/__tests__/story-list-query.test.ts +20 -0
- package/src/server/serverFns/story/__tests__/story-search.test.ts +5 -0
- package/src/server/serverFns/story/__tests__/story-transfer.test.ts +64 -1
- package/src/server/serverFns/story/index.ts +2 -2
- package/src/server/serverFns/story/story-assoc.ts +44 -14
- package/src/server/serverFns/story/story-crud/archive.ts +52 -28
- package/src/server/serverFns/story/story-crud/bulk.ts +14 -4
- package/src/server/serverFns/story/story-crud/create-story-internal.ts +39 -21
- package/src/server/serverFns/story/story-crud.ts +283 -116
- package/src/server/serverFns/story/story-dependencies.ts +31 -0
- package/src/server/serverFns/story/story-groups.ts +15 -11
- package/src/server/serverFns/story/story-list-query.ts +16 -4
- package/src/server/serverFns/story/story-search.ts +9 -6
- package/src/server/serverFns/story/story-transfer.ts +99 -42
- package/src/server/serverFns/story/story-tree.ts +70 -48
- package/src/server/serverFns/story-display-options.ts +21 -10
- package/src/server/serverFns/story-view.ts +2 -2
- package/src/server/serverFns/tasks/task-crud.ts +264 -0
- package/src/server/serverFns/tasks/task-queries.ts +172 -0
- package/src/server/serverFns/testcase/__tests__/generation.test.ts +3 -0
- package/src/server/serverFns/testcase/__tests__/stats.test.ts +18 -4
- package/src/server/serverFns/testcase/__tests__/testcase-board.test.ts +113 -0
- package/src/server/serverFns/testcase/__tests__/testcase-crud.test.ts +62 -14
- package/src/server/serverFns/testcase/__tests__/testcase-groups.test.ts +32 -10
- package/src/server/serverFns/testcase/bulk.ts +195 -0
- package/src/server/serverFns/testcase/generation.ts +10 -0
- package/src/server/serverFns/testcase/stats.ts +22 -8
- package/src/server/serverFns/testcase/testcase-board.ts +198 -0
- package/src/server/serverFns/testcase/testcase-crud.ts +165 -33
- package/src/server/serverFns/testcase/testcase-display-options.ts +166 -0
- package/src/server/serverFns/testcase/testcase-export.ts +125 -0
- package/src/server/serverFns/testcase/testcase-filter-views.ts +36 -9
- package/src/server/serverFns/testcase/testcase-groups.ts +27 -23
- package/src/server/serverFns/testcase/testcase-reorder.ts +66 -0
- package/src/server/story/__tests__/date-key.test.ts +57 -0
- package/src/server/story/date-key.ts +36 -0
- package/src/server/story/types.ts +3 -7
- package/src/server/testcase/__tests__/list-query.test.ts +269 -0
- package/src/server/testcase/list-query.ts +145 -0
- package/src/server-entry.ts +32 -0
- package/src/shared/bug/__tests__/constants.test.ts +44 -0
- package/src/shared/bug/constants.ts +37 -1
- package/src/start.ts +7 -2
- package/src/styles/globals.css +24 -3
- package/src/types/project.ts +2 -3
- package/vite.shared.ts +110 -3
- package/src/__tests__/tag-badge.test.tsx +0 -41
- package/src/components/bug/__tests__/bug-list-table.test.tsx +0 -289
- package/src/components/bug/bug-list-table.tsx +0 -187
- package/src/components/bug/columns.tsx +0 -193
- package/src/components/bug/filters/add-filter-dropdown.tsx +0 -70
- package/src/components/bug/filters/applied-filters.tsx +0 -214
- package/src/components/bug/filters/filter-condition.tsx +0 -151
- package/src/components/bug/filters/filter-row.tsx +0 -104
- package/src/components/bug/filters/filter-toggle.tsx +0 -61
- package/src/components/bug/filters/filter-types.ts +0 -146
- package/src/components/story/detail/story-detail-toolbar.tsx +0 -382
- package/src/components/story/filters/add-filter-dropdown.tsx +0 -70
- package/src/components/story/filters/applied-filters.tsx +0 -225
- package/src/components/story/filters/filter-condition.tsx +0 -152
- package/src/components/story/filters/filter-row.tsx +0 -116
- package/src/components/story/filters/filter-toggle.tsx +0 -60
- package/src/components/story/filters/filter-types.ts +0 -120
- package/src/components/story/filters/filter-value-input.tsx +0 -182
- package/src/components/story/views/kanban-board.tsx +0 -464
- package/src/components/story/views/kanban-card.tsx +0 -100
- package/src/components/tags/__tests__/tags.test.ts +0 -388
- package/src/components/tags/nav-tag.tsx +0 -121
- package/src/components/tags/pages/tag-settings-page.tsx +0 -32
- package/src/components/tags/pages/tags-index-page.tsx +0 -32
- package/src/components/tags/tag-badge.tsx +0 -56
- package/src/components/tags/tag-color-classes.ts +0 -61
- package/src/components/tags/tag-color-picker.tsx +0 -54
- package/src/components/tags/tag-components-index.ts +0 -23
- package/src/components/tags/tag-manager.tsx +0 -208
- package/src/components/tags/tag-selector.tsx +0 -164
- package/src/components/testcase/__tests__/table-view.test.tsx +0 -328
- package/src/components/testcase/plans/add-to-plan-bar.tsx +0 -64
- package/src/components/testcase/table/columns.tsx +0 -170
- package/src/components/testcase/table/table-view.tsx +0 -152
- package/src/lib/tag-colors.ts +0 -35
- package/src/routes/_nav/tags/$id/route.lazy.tsx +0 -23
- package/src/routes/_nav/tags/$id/route.tsx +0 -20
- package/src/routes/_nav/tags/index.lazy.tsx +0 -23
- package/src/routes/_nav/tags/index.tsx +0 -20
- package/src/routes/_nav/tags/settings/route.lazy.tsx +0 -23
- package/src/routes/_nav/tags/settings/route.tsx +0 -23
- package/src/server/serverFns/tags.ts +0 -138
- /package/src/components/{knowledge-public → knowledge/public}/bookmark-card.tsx +0 -0
- /package/src/components/{knowledge-public → knowledge/public}/category-icon.tsx +0 -0
- /package/src/components/{knowledge-public → knowledge/public}/chip-utils.ts +0 -0
- /package/src/components/{knowledge-public → knowledge/public}/knowledge-card-grid.tsx +0 -0
- /package/src/components/{knowledge-public → knowledge/public}/knowledge-filters.tsx +0 -0
- /package/src/components/{knowledge-public → knowledge/public}/knowledge-search-bar.tsx +0 -0
- /package/src/components/{knowledge-public → knowledge/public}/markdown-editor.tsx +0 -0
- /package/src/components/{knowledge-public → knowledge/public}/markdown-view.tsx +0 -0
- /package/src/components/{knowledge-public → knowledge/public}/tag-filter.tsx +0 -0
- /package/src/components/{story/detail → shared-kit/comments}/emoji-picker.tsx +0 -0
- /package/src/components/story/{schemas/story-create.ts → story-create-schema.ts} +0 -0
- /package/src/server/{knowledge-public → knowledge/public}/canonical-id.ts +0 -0
- /package/src/server/{knowledge-public → knowledge/public}/favicon.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/categories.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/delete.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/favorite.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/unshare.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/delete.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/public.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/unshare.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/dictionary/delete.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/dictionary/unshare.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/entity-tags.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/favorite.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/delete.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/public.ts +0 -0
- /package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/unshare.ts +0 -0
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
import { expandRecurrence } from "@/components/reui/gantt/gantt-recurrence"
|
|
2
|
+
import type {
|
|
3
|
+
GanttBaseline,
|
|
4
|
+
GanttBaselineVariance,
|
|
5
|
+
GanttDateRange,
|
|
6
|
+
GanttEvent,
|
|
7
|
+
GanttOccurrence,
|
|
8
|
+
GanttOffDaysConfig,
|
|
9
|
+
GanttRecurrenceRule,
|
|
10
|
+
GanttResource,
|
|
11
|
+
GanttScale,
|
|
12
|
+
GanttSegment,
|
|
13
|
+
} from "@/components/reui/gantt/gantt-types"
|
|
14
|
+
import { TZDate } from "@date-fns/tz"
|
|
15
|
+
import {
|
|
16
|
+
addDays,
|
|
17
|
+
addMonths,
|
|
18
|
+
addWeeks,
|
|
19
|
+
addYears,
|
|
20
|
+
differenceInMinutes,
|
|
21
|
+
format,
|
|
22
|
+
startOfDay,
|
|
23
|
+
startOfMonth,
|
|
24
|
+
startOfQuarter,
|
|
25
|
+
startOfWeek,
|
|
26
|
+
startOfYear,
|
|
27
|
+
} from "date-fns"
|
|
28
|
+
|
|
29
|
+
type WeekStartsOn = 0 | 1 | 2 | 3 | 4 | 5 | 6
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Packing-effective minimum in minutes so tiny events do not stack invisibly.
|
|
33
|
+
* It is a packing FOOTPRINT, not a render size: two schedules less than this
|
|
34
|
+
* apart are treated as concurrent and split into separate lanes even though
|
|
35
|
+
* their real ranges do not touch.
|
|
36
|
+
*/
|
|
37
|
+
const MIN_PACK_SLOT = 30
|
|
38
|
+
|
|
39
|
+
/** The instant re-expressed in the display time zone (TZDate extends Date). */
|
|
40
|
+
function toZoned(date: Date, timeZone: string): TZDate {
|
|
41
|
+
return new TZDate(date.getTime(), timeZone)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Zoned midnight of the day containing the instant. */
|
|
45
|
+
function zonedStartOfDay(date: Date, timeZone: string): TZDate {
|
|
46
|
+
return startOfDay(toZoned(date, timeZone))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Stable per-day key in the display time zone. */
|
|
50
|
+
function getDayKey(date: Date, timeZone: string): string {
|
|
51
|
+
return format(toZoned(date, timeZone), "yyyy-MM-dd")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Day length in minutes; 1380/1500 on DST transition days - never assume 1440. */
|
|
55
|
+
function getDayTotalMinutes(dayStart: Date, timeZone: string): number {
|
|
56
|
+
const next = zonedStartOfDay(
|
|
57
|
+
addDays(toZoned(dayStart, timeZone), 1),
|
|
58
|
+
timeZone
|
|
59
|
+
)
|
|
60
|
+
return differenceInMinutes(next, dayStart)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function snapMinutes(minutes: number, snap: number): number {
|
|
64
|
+
return Math.round(minutes / snap) * snap
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface ViewRangeOptions {
|
|
68
|
+
timeZone: string
|
|
69
|
+
weekStartsOn: WeekStartsOn
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface ViewDateRanges {
|
|
73
|
+
visibleRange: GanttDateRange
|
|
74
|
+
activeRange: GanttDateRange
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Axis range for the anchor date at the given scale. */
|
|
78
|
+
function getGanttDateRange(
|
|
79
|
+
scale: GanttScale,
|
|
80
|
+
date: Date,
|
|
81
|
+
opts: ViewRangeOptions
|
|
82
|
+
): ViewDateRanges {
|
|
83
|
+
const { timeZone, weekStartsOn } = opts
|
|
84
|
+
const zoned = toZoned(date, timeZone)
|
|
85
|
+
|
|
86
|
+
if (scale === "week") {
|
|
87
|
+
const start = startOfWeek(zoned, { weekStartsOn })
|
|
88
|
+
const range = { start, end: addWeeks(start, 1) }
|
|
89
|
+
return { activeRange: range, visibleRange: range }
|
|
90
|
+
}
|
|
91
|
+
if (scale === "month") {
|
|
92
|
+
// exact month: no outside days on the horizontal axis
|
|
93
|
+
const start = startOfMonth(zoned)
|
|
94
|
+
const range = { start, end: startOfMonth(addMonths(zoned, 1)) }
|
|
95
|
+
return { activeRange: range, visibleRange: range }
|
|
96
|
+
}
|
|
97
|
+
if (scale === "quarter") {
|
|
98
|
+
// week-aligned so the axis partitions into uniform week units
|
|
99
|
+
const quarterStart = startOfQuarter(zoned)
|
|
100
|
+
const quarterEnd = startOfQuarter(addMonths(zoned, 3))
|
|
101
|
+
const start = startOfWeek(quarterStart, { weekStartsOn })
|
|
102
|
+
let end = startOfWeek(quarterEnd, { weekStartsOn })
|
|
103
|
+
if (end < quarterEnd) end = addWeeks(end, 1)
|
|
104
|
+
return {
|
|
105
|
+
activeRange: { start: quarterStart, end: quarterEnd },
|
|
106
|
+
visibleRange: { start, end },
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (scale === "year") {
|
|
110
|
+
const start = startOfYear(zoned)
|
|
111
|
+
const range = { start, end: startOfYear(addYears(zoned, 1)) }
|
|
112
|
+
return { activeRange: range, visibleRange: range }
|
|
113
|
+
}
|
|
114
|
+
const start = startOfDay(zoned)
|
|
115
|
+
const range = { start, end: addDays(start, 1) }
|
|
116
|
+
return { activeRange: range, visibleRange: range }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The anchor date stepped one period forward or backward for the scale. */
|
|
120
|
+
function stepGanttDate(
|
|
121
|
+
scale: GanttScale,
|
|
122
|
+
date: Date,
|
|
123
|
+
direction: 1 | -1,
|
|
124
|
+
opts: Pick<ViewRangeOptions, "timeZone">
|
|
125
|
+
): Date {
|
|
126
|
+
const zoned = toZoned(date, opts.timeZone)
|
|
127
|
+
if (scale === "week") return addWeeks(zoned, direction)
|
|
128
|
+
if (scale === "month") return addMonths(zoned, direction)
|
|
129
|
+
if (scale === "quarter") return addMonths(zoned, direction * 3)
|
|
130
|
+
if (scale === "year") return addYears(zoned, direction)
|
|
131
|
+
return addDays(zoned, direction)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function rangesIntersect(a: GanttDateRange, b: GanttDateRange): boolean {
|
|
135
|
+
return a.start < b.end && a.end > b.start
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function eventsOverlap(
|
|
139
|
+
a: { start: Date; end: Date },
|
|
140
|
+
b: { start: Date; end: Date }
|
|
141
|
+
): boolean {
|
|
142
|
+
return a.start < b.end && a.end > b.start
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Range visibility for an occurrence: half-open like rangesIntersect, except
|
|
147
|
+
* a zero-length occurrence (a milestone). The plain test can never admit an
|
|
148
|
+
* instant sitting exactly on the range start, so milestones compare the
|
|
149
|
+
* start closed; the end stays exclusive - an instant on the range end
|
|
150
|
+
* belongs to the next period. gantt-recurrence inlines this same rule
|
|
151
|
+
* (importing it back here would cycle the modules).
|
|
152
|
+
*/
|
|
153
|
+
function occurrenceIntersects(
|
|
154
|
+
occ: { start: Date; end: Date },
|
|
155
|
+
range: GanttDateRange
|
|
156
|
+
): boolean {
|
|
157
|
+
if (occ.end.getTime() === occ.start.getTime()) {
|
|
158
|
+
return occ.start >= range.start && occ.start < range.end
|
|
159
|
+
}
|
|
160
|
+
return rangesIntersect(occ, range)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function spansMultipleDays(occ: { start: Date; end: Date }): boolean {
|
|
164
|
+
// An event ending exactly at the next midnight is still single-day
|
|
165
|
+
// (exclusive end), so compare against a strictly-later instant.
|
|
166
|
+
return occ.end.getTime() - occ.start.getTime() > 24 * 60 * 60 * 1000
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The subject's validated planned window, or null: both instants present,
|
|
171
|
+
* end not before start, and (for events) non-recurring - a series has no
|
|
172
|
+
* single planned window. An edited single occurrence (recurringEventId
|
|
173
|
+
* without its own rule) is a plain event here and keeps its baseline.
|
|
174
|
+
* Structural on purpose: a GanttResource node's own baseline pair resolves
|
|
175
|
+
* through the same rule.
|
|
176
|
+
*/
|
|
177
|
+
function resolveEventBaseline(subject: {
|
|
178
|
+
baselineStart?: Date
|
|
179
|
+
baselineEnd?: Date
|
|
180
|
+
recurrence?: GanttRecurrenceRule | string
|
|
181
|
+
}): GanttBaseline | null {
|
|
182
|
+
if (!subject.baselineStart || !subject.baselineEnd || subject.recurrence) {
|
|
183
|
+
return null
|
|
184
|
+
}
|
|
185
|
+
const startMs = subject.baselineStart.getTime()
|
|
186
|
+
const endMs = subject.baselineEnd.getTime()
|
|
187
|
+
if (endMs < startMs) return null
|
|
188
|
+
return {
|
|
189
|
+
start: subject.baselineStart,
|
|
190
|
+
end: subject.baselineEnd,
|
|
191
|
+
milestone: endMs === startMs,
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
interface GanttDependencyGeometry {
|
|
196
|
+
/** Orthogonal elbow path for the connector line, in the caller's units. */
|
|
197
|
+
line: string
|
|
198
|
+
/** Filled arrowhead at the target anchor, pointing along +x. */
|
|
199
|
+
arrow: string
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Finish-to-start connector geometry from a predecessor's END anchor to a
|
|
204
|
+
* successor's START anchor, in whatever units the caller works in (the view
|
|
205
|
+
* passes rem). A target with room ahead takes the classic Z route: out by
|
|
206
|
+
* `clearance`, across to the target lane, in. A target at or behind the
|
|
207
|
+
* source's exit loops instead: out, hop `laneStep` toward the target's side
|
|
208
|
+
* (down when level - below reads as "later"), back past the target, in.
|
|
209
|
+
* The line stops `arrowSize` short of the anchor so it never pokes through
|
|
210
|
+
* the head. The exit stub can sit under an outside-after bar label; a
|
|
211
|
+
* consumer that shows those widens the label's start margin instead of the
|
|
212
|
+
* routing changing shape (a drop-first exit was tried and read worse
|
|
213
|
+
* everywhere else).
|
|
214
|
+
*/
|
|
215
|
+
function buildDependencyPath(
|
|
216
|
+
from: { x: number; y: number },
|
|
217
|
+
to: { x: number; y: number },
|
|
218
|
+
opts: { clearance: number; laneStep: number; arrowSize: number }
|
|
219
|
+
): GanttDependencyGeometry {
|
|
220
|
+
const { clearance, laneStep, arrowSize } = opts
|
|
221
|
+
const lineEndX = to.x - arrowSize
|
|
222
|
+
const arrow = `M ${to.x} ${to.y} L ${lineEndX} ${to.y - arrowSize * 0.6} L ${lineEndX} ${to.y + arrowSize * 0.6} Z`
|
|
223
|
+
if (lineEndX >= from.x + clearance) {
|
|
224
|
+
return {
|
|
225
|
+
line: `M ${from.x} ${from.y} H ${from.x + clearance} V ${to.y} H ${lineEndX}`,
|
|
226
|
+
arrow,
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const midY = to.y >= from.y ? from.y + laneStep : from.y - laneStep
|
|
230
|
+
return {
|
|
231
|
+
line: `M ${from.x} ${from.y} H ${from.x + clearance} V ${midY} H ${to.x - clearance} V ${to.y} H ${lineEndX}`,
|
|
232
|
+
arrow,
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Actual end against the planned end; null when no valid baseline exists. */
|
|
237
|
+
function getBaselineVariance<TData>(
|
|
238
|
+
event: GanttEvent<TData>
|
|
239
|
+
): GanttBaselineVariance | null {
|
|
240
|
+
const baseline = resolveEventBaseline(event)
|
|
241
|
+
if (!baseline) return null
|
|
242
|
+
const deltaMs = event.end.getTime() - baseline.end.getTime()
|
|
243
|
+
return deltaMs > 0 ? "late" : deltaMs < 0 ? "early" : "on-time"
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
interface PackedPosition {
|
|
247
|
+
column: number
|
|
248
|
+
columnCount: number
|
|
249
|
+
columnSpan: number
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Identity of a schedule ACROSS time edits. `occurrence.key` embeds the start
|
|
254
|
+
* instant, so it changes the moment a schedule is moved or start-resized -
|
|
255
|
+
* useless as lane memory. This key survives the edit: the event id plus, for a
|
|
256
|
+
* recurring series, the occurrence's position in it.
|
|
257
|
+
*/
|
|
258
|
+
function getLaneKey(occurrence: {
|
|
259
|
+
eventId: string
|
|
260
|
+
recurrenceIndex?: number
|
|
261
|
+
}): string {
|
|
262
|
+
return `${occurrence.eventId}::${occurrence.recurrenceIndex ?? 0}`
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* What one schedule held on the previous layout pass. The TIMES are what make
|
|
267
|
+
* this more than a lane number: they are how the packer tells the schedule the
|
|
268
|
+
* user just edited apart from the ones that merely sat still.
|
|
269
|
+
*/
|
|
270
|
+
interface GanttLaneMemo {
|
|
271
|
+
lane: number
|
|
272
|
+
startMs: number
|
|
273
|
+
endMs: number
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
interface PackOptions {
|
|
277
|
+
/**
|
|
278
|
+
* Where each schedule sat on the previous pass, by getLaneKey.
|
|
279
|
+
*
|
|
280
|
+
* A schedule whose times are UNCHANGED keeps its lane if that lane is still
|
|
281
|
+
* free, so editing one schedule never re-indexes the ones around it. A
|
|
282
|
+
* schedule whose times CHANGED - the one the user just dragged or resized -
|
|
283
|
+
* deliberately forfeits its pin and re-seeks the lowest free lane. That is
|
|
284
|
+
* what makes the arrangement live rather than frozen: a schedule dragged
|
|
285
|
+
* onto its neighbours stacks DOWN into the first free lane, and one dragged
|
|
286
|
+
* clear of them comes back UP inline. Only the edited schedule moves.
|
|
287
|
+
*/
|
|
288
|
+
preferredLanes?: Map<string, GanttLaneMemo>
|
|
289
|
+
/** "single" collapses the row to one track; see GanttScheduleMode. */
|
|
290
|
+
mode?: "single" | "multiple"
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Overlap packing for one row's timed segments.
|
|
295
|
+
* Mutates column/columnCount/columnSpan on the segments, in place.
|
|
296
|
+
* z resolution happens at render: event.zIndex verbatim, else 10 + column.
|
|
297
|
+
*/
|
|
298
|
+
function packTimedSegments<TData>(
|
|
299
|
+
segments: GanttSegment<TData>[],
|
|
300
|
+
options: PackOptions = {}
|
|
301
|
+
): void {
|
|
302
|
+
if (segments.length === 0) return
|
|
303
|
+
|
|
304
|
+
if (options.mode === "single") {
|
|
305
|
+
// one track: every schedule shares lane 0 and the row never grows
|
|
306
|
+
for (const seg of segments) {
|
|
307
|
+
seg.column = 0
|
|
308
|
+
seg.columnCount = 1
|
|
309
|
+
seg.columnSpan = 1
|
|
310
|
+
}
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const preferredLanes = options.preferredLanes
|
|
315
|
+
|
|
316
|
+
type Working = {
|
|
317
|
+
seg: GanttSegment<TData>
|
|
318
|
+
startMin: number
|
|
319
|
+
effEnd: number
|
|
320
|
+
lane: number
|
|
321
|
+
/** The occupancy entry this item added, so a settle can take it back. */
|
|
322
|
+
interval?: { from: number; to: number }
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const items: Working[] = segments
|
|
326
|
+
.map((seg) => {
|
|
327
|
+
const startMin = seg.startMin ?? 0
|
|
328
|
+
const endMin = seg.endMin ?? startMin
|
|
329
|
+
return {
|
|
330
|
+
seg,
|
|
331
|
+
startMin,
|
|
332
|
+
effEnd: Math.max(endMin, startMin + MIN_PACK_SLOT),
|
|
333
|
+
lane: -1,
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
.sort(
|
|
337
|
+
(a, b) =>
|
|
338
|
+
a.startMin - b.startMin ||
|
|
339
|
+
b.effEnd - b.startMin - (a.effEnd - a.startMin) ||
|
|
340
|
+
a.seg.occurrence.key.localeCompare(b.seg.occurrence.key)
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
// Sweep into connected clusters
|
|
344
|
+
const clusters: Working[][] = []
|
|
345
|
+
let current: Working[] = []
|
|
346
|
+
let clusterEnd = -Infinity
|
|
347
|
+
for (const item of items) {
|
|
348
|
+
if (item.startMin >= clusterEnd) {
|
|
349
|
+
current = []
|
|
350
|
+
clusters.push(current)
|
|
351
|
+
clusterEnd = -Infinity
|
|
352
|
+
}
|
|
353
|
+
current.push(item)
|
|
354
|
+
clusterEnd = Math.max(clusterEnd, item.effEnd)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
for (const cluster of clusters) {
|
|
358
|
+
// Per-lane occupancy INTERVALS, not a single running end: pass 1 claims
|
|
359
|
+
// remembered lanes out of time order, so a lane can be free before an
|
|
360
|
+
// occupant and busy after it.
|
|
361
|
+
const laneIntervals: Array<Array<{ from: number; to: number }>> = []
|
|
362
|
+
const isFree = (lane: number, item: Working) =>
|
|
363
|
+
!(laneIntervals[lane] ?? []).some(
|
|
364
|
+
(iv) => iv.from < item.effEnd && iv.to > item.startMin
|
|
365
|
+
)
|
|
366
|
+
const claim = (lane: number, item: Working) => {
|
|
367
|
+
while (laneIntervals.length <= lane) laneIntervals.push([])
|
|
368
|
+
const interval = { from: item.startMin, to: item.effEnd }
|
|
369
|
+
laneIntervals[lane].push(interval)
|
|
370
|
+
item.lane = lane
|
|
371
|
+
item.interval = interval
|
|
372
|
+
}
|
|
373
|
+
const release = (item: Working) => {
|
|
374
|
+
const occupants = laneIntervals[item.lane] ?? []
|
|
375
|
+
const at = occupants.indexOf(item.interval!)
|
|
376
|
+
if (at >= 0) occupants.splice(at, 1)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// pass 1: schedules that did not move keep the lane they had. The one the
|
|
380
|
+
// user just edited is NOT pinned - its times differ from the memo, so it
|
|
381
|
+
// falls through to pass 2 and re-seeks a lane against its new span.
|
|
382
|
+
const pending: Working[] = []
|
|
383
|
+
for (const item of cluster) {
|
|
384
|
+
const memo = preferredLanes?.get(getLaneKey(item.seg.occurrence))
|
|
385
|
+
const untouched =
|
|
386
|
+
memo !== undefined &&
|
|
387
|
+
memo.startMs === item.seg.occurrence.start.getTime() &&
|
|
388
|
+
memo.endMs === item.seg.occurrence.end.getTime()
|
|
389
|
+
if (untouched && memo.lane >= 0 && isFree(memo.lane, item)) {
|
|
390
|
+
claim(memo.lane, item)
|
|
391
|
+
} else {
|
|
392
|
+
pending.push(item)
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// pass 2: the rest take the lowest free lane - overlapping goes DOWN into
|
|
396
|
+
// the first lane with room, fitting comes back UP to lane 0
|
|
397
|
+
for (const item of pending) {
|
|
398
|
+
let lane = 0
|
|
399
|
+
while (!isFree(lane, item)) lane++
|
|
400
|
+
claim(lane, item)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// pass 3: nothing floats above an empty lane. A pin only survives while
|
|
404
|
+
// something above it still needs the space - once the schedule that was
|
|
405
|
+
// there moves away or is deleted, its neighbour settles down into the
|
|
406
|
+
// gap. Without this a row keeps a permanently blank top lane and never
|
|
407
|
+
// shrinks back. Settling in lane order, and only ever DOWNWARD into space
|
|
408
|
+
// that is genuinely free, means two schedules can never trade places -
|
|
409
|
+
// so an edit still moves at most the schedule it touched.
|
|
410
|
+
const byLane = [...cluster].sort(
|
|
411
|
+
(a, b) => a.lane - b.lane || a.startMin - b.startMin
|
|
412
|
+
)
|
|
413
|
+
for (const item of byLane) {
|
|
414
|
+
if (item.lane === 0) continue
|
|
415
|
+
let lane = 0
|
|
416
|
+
while (lane < item.lane && !isFree(lane, item)) lane++
|
|
417
|
+
if (lane < item.lane) {
|
|
418
|
+
release(item)
|
|
419
|
+
claim(lane, item)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Lane memory can leave holes (the schedule that held lane 0 was deleted or
|
|
425
|
+
// moved away). Collapse the row's USED lanes onto 0..n-1: relative stacking
|
|
426
|
+
// order survives, so nothing reshuffles, but the row cannot creep taller
|
|
427
|
+
// than the lanes it actually needs.
|
|
428
|
+
const used = [...new Set(items.map((item) => item.lane))].sort(
|
|
429
|
+
(a, b) => a - b
|
|
430
|
+
)
|
|
431
|
+
const compacted = new Map(used.map((lane, index) => [lane, index]))
|
|
432
|
+
const columnCount = used.length
|
|
433
|
+
for (const item of items) {
|
|
434
|
+
item.lane = compacted.get(item.lane) ?? 0
|
|
435
|
+
item.seg.column = item.lane
|
|
436
|
+
item.seg.columnCount = columnCount
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Partial-overlap expansion: widen rightward into free lanes
|
|
440
|
+
for (const cluster of clusters) {
|
|
441
|
+
for (const item of cluster) {
|
|
442
|
+
let span = 1
|
|
443
|
+
while (item.lane + span < columnCount) {
|
|
444
|
+
const blocked = cluster.some(
|
|
445
|
+
(other) =>
|
|
446
|
+
other !== item &&
|
|
447
|
+
other.lane === item.lane + span &&
|
|
448
|
+
other.startMin < item.effEnd &&
|
|
449
|
+
other.effEnd > item.startMin
|
|
450
|
+
)
|
|
451
|
+
if (blocked) break
|
|
452
|
+
span++
|
|
453
|
+
}
|
|
454
|
+
item.seg.columnSpan = span
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function defaultEventOrder(a: GanttOccurrence, b: GanttOccurrence): number {
|
|
460
|
+
return (
|
|
461
|
+
a.start.getTime() - b.start.getTime() ||
|
|
462
|
+
b.end.getTime() -
|
|
463
|
+
b.start.getTime() -
|
|
464
|
+
(a.end.getTime() - a.start.getTime()) ||
|
|
465
|
+
a.key.localeCompare(b.key)
|
|
466
|
+
)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
interface BuildIndexOptions<TData = unknown> {
|
|
470
|
+
timeZone: string
|
|
471
|
+
/** Escape hatch for exotic recurrence: return the expanded occurrences. */
|
|
472
|
+
getOccurrences?: (
|
|
473
|
+
event: GanttEvent<TData>,
|
|
474
|
+
range: GanttDateRange,
|
|
475
|
+
ctx: { timeZone: string }
|
|
476
|
+
) => Array<{ start: Date; end: Date }> | null | undefined
|
|
477
|
+
eventOrder?: (a: GanttOccurrence<TData>, b: GanttOccurrence<TData>) => number
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
interface GanttIndex<TData = unknown> {
|
|
481
|
+
occurrences: GanttOccurrence<TData>[]
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function buildEventIndex<TData>(
|
|
485
|
+
events: GanttEvent<TData>[],
|
|
486
|
+
visibleRange: GanttDateRange,
|
|
487
|
+
opts: BuildIndexOptions<TData>
|
|
488
|
+
): GanttIndex<TData> {
|
|
489
|
+
const { timeZone } = opts
|
|
490
|
+
const order = opts.eventOrder ?? defaultEventOrder
|
|
491
|
+
|
|
492
|
+
// RECURRENCE-ID override replacement: an event carrying recurringEventId +
|
|
493
|
+
// originalStart is an edited single occurrence of that series. The parent's
|
|
494
|
+
// expansion drops the replaced instant; the override renders as its own
|
|
495
|
+
// occurrence through the normal path below.
|
|
496
|
+
const overrideTimes = new Map<string, Set<number>>()
|
|
497
|
+
for (const event of events) {
|
|
498
|
+
if (!event.recurringEventId || !event.originalStart) continue
|
|
499
|
+
let times = overrideTimes.get(event.recurringEventId)
|
|
500
|
+
if (!times) overrideTimes.set(event.recurringEventId, (times = new Set()))
|
|
501
|
+
times.add(event.originalStart.getTime())
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const occurrences: GanttOccurrence<TData>[] = []
|
|
505
|
+
for (const event of events) {
|
|
506
|
+
const replaced = overrideTimes.get(event.id)
|
|
507
|
+
const custom = opts.getOccurrences?.(event, visibleRange, { timeZone })
|
|
508
|
+
if (custom) {
|
|
509
|
+
custom.forEach((occ, i) => {
|
|
510
|
+
if (replaced?.has(occ.start.getTime())) return
|
|
511
|
+
if (
|
|
512
|
+
!occurrenceIntersects(
|
|
513
|
+
{ start: occ.start, end: occ.end },
|
|
514
|
+
visibleRange
|
|
515
|
+
)
|
|
516
|
+
)
|
|
517
|
+
return
|
|
518
|
+
occurrences.push({
|
|
519
|
+
key: `${event.id}::${occ.start.toISOString()}`,
|
|
520
|
+
eventId: event.id,
|
|
521
|
+
event,
|
|
522
|
+
start: occ.start,
|
|
523
|
+
end: occ.end,
|
|
524
|
+
allDay: event.allDay ?? false,
|
|
525
|
+
isRecurring: true,
|
|
526
|
+
recurrenceIndex: i,
|
|
527
|
+
})
|
|
528
|
+
})
|
|
529
|
+
continue
|
|
530
|
+
}
|
|
531
|
+
const expanded = expandRecurrence(event, visibleRange, { timeZone })
|
|
532
|
+
occurrences.push(
|
|
533
|
+
...(replaced
|
|
534
|
+
? expanded.filter((occ) => !replaced.has(occ.start.getTime()))
|
|
535
|
+
: expanded)
|
|
536
|
+
)
|
|
537
|
+
}
|
|
538
|
+
occurrences.sort(order)
|
|
539
|
+
return { occurrences }
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/** Cache key for index memoization; cheap string compare. */
|
|
543
|
+
function getRangeKey(range: GanttDateRange): string {
|
|
544
|
+
return `${range.start.getTime()}-${range.end.getTime()}`
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/** Depth-first flatten of the resource tree (parents included). */
|
|
548
|
+
function flattenResources(
|
|
549
|
+
resources: GanttResource[],
|
|
550
|
+
depth = 0
|
|
551
|
+
): Array<{ resource: GanttResource; depth: number }> {
|
|
552
|
+
const rows: Array<{ resource: GanttResource; depth: number }> = []
|
|
553
|
+
for (const resource of resources) {
|
|
554
|
+
rows.push({ resource, depth })
|
|
555
|
+
if (resource.children?.length) {
|
|
556
|
+
rows.push(...flattenResources(resource.children, depth + 1))
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return rows
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/** Depth-first lookup of one node in the tree. */
|
|
563
|
+
function findResource(
|
|
564
|
+
resources: GanttResource[],
|
|
565
|
+
id: string
|
|
566
|
+
): GanttResource | null {
|
|
567
|
+
for (const resource of resources) {
|
|
568
|
+
if (resource.id === id) return resource
|
|
569
|
+
const found = resource.children?.length
|
|
570
|
+
? findResource(resource.children, id)
|
|
571
|
+
: null
|
|
572
|
+
if (found) return found
|
|
573
|
+
}
|
|
574
|
+
return null
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Pure tree move: removes `resourceId` from wherever it sits and reinserts it
|
|
579
|
+
* under `parentId` (null = root) at `index`. Returns a new tree; the original
|
|
580
|
+
* is untouched. Returns null for impossible moves (unknown ids, or dropping a
|
|
581
|
+
* node into its own subtree).
|
|
582
|
+
*/
|
|
583
|
+
function reorderResources(
|
|
584
|
+
resources: GanttResource[],
|
|
585
|
+
resourceId: string,
|
|
586
|
+
parentId: string | null,
|
|
587
|
+
index: number
|
|
588
|
+
): GanttResource[] | null {
|
|
589
|
+
let moved: GanttResource | null = null
|
|
590
|
+
|
|
591
|
+
const strip = (nodes: GanttResource[]): GanttResource[] =>
|
|
592
|
+
nodes.flatMap((node) => {
|
|
593
|
+
if (node.id === resourceId) {
|
|
594
|
+
moved = node
|
|
595
|
+
return []
|
|
596
|
+
}
|
|
597
|
+
if (!node.children?.length) return [node]
|
|
598
|
+
return [{ ...node, children: strip(node.children) }]
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
const stripped = strip(resources)
|
|
602
|
+
if (!moved) return null
|
|
603
|
+
|
|
604
|
+
const contains = (node: GanttResource, id: string): boolean =>
|
|
605
|
+
node.id === id || !!node.children?.some((child) => contains(child, id))
|
|
606
|
+
if (parentId !== null && contains(moved, parentId)) return null
|
|
607
|
+
|
|
608
|
+
const insert = (nodes: GanttResource[]): GanttResource[] => {
|
|
609
|
+
if (parentId === null) {
|
|
610
|
+
const next = [...nodes]
|
|
611
|
+
next.splice(Math.min(Math.max(index, 0), next.length), 0, moved!)
|
|
612
|
+
return next
|
|
613
|
+
}
|
|
614
|
+
return nodes.map((node) => {
|
|
615
|
+
if (node.id === parentId) {
|
|
616
|
+
const children = [...(node.children ?? [])]
|
|
617
|
+
children.splice(
|
|
618
|
+
Math.min(Math.max(index, 0), children.length),
|
|
619
|
+
0,
|
|
620
|
+
moved!
|
|
621
|
+
)
|
|
622
|
+
return { ...node, children }
|
|
623
|
+
}
|
|
624
|
+
if (!node.children?.length) return node
|
|
625
|
+
return { ...node, children: insert(node.children) }
|
|
626
|
+
})
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const next = insert(stripped)
|
|
630
|
+
// unknown parentId: the node vanished - reject
|
|
631
|
+
if (parentId !== null) {
|
|
632
|
+
const flat = flattenResources(next)
|
|
633
|
+
if (!flat.some(({ resource }) => resource.id === resourceId)) return null
|
|
634
|
+
}
|
|
635
|
+
return next
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const DEFAULT_WEEKEND_DAYS = [0, 6]
|
|
639
|
+
|
|
640
|
+
/** Resolves whether a day is an off day (non-working) in the display zone. */
|
|
641
|
+
function resolveOffDay(
|
|
642
|
+
day: Date,
|
|
643
|
+
timeZone: string,
|
|
644
|
+
config: boolean | GanttOffDaysConfig | undefined
|
|
645
|
+
): boolean {
|
|
646
|
+
if (!config) return false
|
|
647
|
+
const resolved: GanttOffDaysConfig = config === true ? {} : config
|
|
648
|
+
const weekendDays = resolved.weekendDays ?? DEFAULT_WEEKEND_DAYS
|
|
649
|
+
const zoned = toZoned(day, timeZone)
|
|
650
|
+
if (weekendDays.includes(zoned.getDay())) return true
|
|
651
|
+
if (resolved.dates?.length) {
|
|
652
|
+
const key = getDayKey(day, timeZone)
|
|
653
|
+
if (resolved.dates.some((date) => getDayKey(date, timeZone) === key)) {
|
|
654
|
+
return true
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return resolved.isOffDay?.(day) ?? false
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export {
|
|
661
|
+
buildDependencyPath,
|
|
662
|
+
buildEventIndex,
|
|
663
|
+
defaultEventOrder,
|
|
664
|
+
eventsOverlap,
|
|
665
|
+
findResource,
|
|
666
|
+
flattenResources,
|
|
667
|
+
getBaselineVariance,
|
|
668
|
+
getDayKey,
|
|
669
|
+
getDayTotalMinutes,
|
|
670
|
+
getGanttDateRange,
|
|
671
|
+
getLaneKey,
|
|
672
|
+
getRangeKey,
|
|
673
|
+
MIN_PACK_SLOT,
|
|
674
|
+
occurrenceIntersects,
|
|
675
|
+
packTimedSegments,
|
|
676
|
+
rangesIntersect,
|
|
677
|
+
reorderResources,
|
|
678
|
+
resolveEventBaseline,
|
|
679
|
+
resolveOffDay,
|
|
680
|
+
snapMinutes,
|
|
681
|
+
spansMultipleDays,
|
|
682
|
+
stepGanttDate,
|
|
683
|
+
toZoned,
|
|
684
|
+
zonedStartOfDay,
|
|
685
|
+
}
|
|
686
|
+
export type {
|
|
687
|
+
BuildIndexOptions,
|
|
688
|
+
GanttDependencyGeometry,
|
|
689
|
+
GanttIndex,
|
|
690
|
+
GanttLaneMemo,
|
|
691
|
+
PackOptions,
|
|
692
|
+
ViewDateRanges,
|
|
693
|
+
ViewRangeOptions,
|
|
694
|
+
WeekStartsOn,
|
|
695
|
+
}
|